// UTILS + MISC ///////////////////////////////////////////////////////
window.aboutIITC = function(){
var v = '@@BUILDNAME@@-@@BUILDDATE@@';
var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
var a = ''
+ '
About IITC
'
+ ' Ingress Intel Total Conversion
'
+ '
'
+ ' '
+ '
IITC Homepage'
+ ' On the script’s homepage you can:'
+ '
'
+ ' - Find Updates
'
+ ' - Get Plugins
'
+ ' - Report Bugs
'
+ ' - Contribute!
'
+ '
'
+ '
'
+ ' '
+ ' MapQuest OSM tiles Courtesy of
MapQuest 
'
+ '
'
+ '
'
+ ' Version: ' + v + '
'
+ '
'
+ ' ' + attrib + '
'
+ '
'
+ ' ' + contrib + '
';
dialog({
title: 'IITC ' + v,
html: a,
dialogClass: 'ui-dialog-aboutIITC'
});
}
window.layerGroupLength = function(layerGroup) {
var layersCount = 0;
var layers = layerGroup._layers;
if (layers)
layersCount = Object.keys(layers).length;
return layersCount;
}
// retrieves parameter from the URL?query=string.
window.getURLParam = function(param) {
var v = document.URL;
var i = v.indexOf(param);
if(i <= -1) return '';
v = v.substr(i);
i = v.indexOf("&");
if(i >= 0) v = v.substr(0, i);
return v.replace(param+"=","");
}
// read cookie by name.
// http://stackoverflow.com/a/5639455/1684530 by cwolves
var cookies;
window.readCookie = function(name,c,C,i){
if(cookies) return cookies[name];
c = document.cookie.split('; ');
cookies = {};
for(i=c.length-1; i>=0; i--){
C = c[i].split('=');
cookies[C[0]] = unescape(C[1]);
}
return cookies[name];
}
window.writeCookie = function(name, val) {
document.cookie = name + "=" + val + '; expires=Thu, 31 Dec 2020 23:59:59 GMT; path=/';
}
window.eraseCookie = function(name) {
document.cookie = name + '=; expires=Thu, 1 Jan 1970 00:00:00 GMT; path=/';
}
//certain values were stored in cookies, but we're better off using localStorage instead - make it easy to convert
window.convertCookieToLocalStorage = function(name) {
var cookie=readCookie(name);
if(cookie !== undefined) {
console.log('converting cookie '+name+' to localStorage');
if(localStorage[name] === undefined) {
localStorage[name] = cookie;
}
eraseCookie(name);
}
}
// add thousand separators to given number.
// http://stackoverflow.com/a/1990590/1684530 by Doug Neiner.
window.digits = function(d) {
return (d+"").replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1 ");
}
window.requestParameterMunges = [
// set 0
{
method: '4kr3ofeptwgary2j',
boundsParamsList: 'n27qzc8389kgakyv',
id: '39031qie1i4aq563',
minLatE6: 'pg98bwox95ly0ouu',
minLngE6: 'eib1bkq8znpwr0g7',
maxLatE6: 'ilfap961rwdybv63',
maxLngE6: 'lpf7m1ifx0ieouzq',
timestampMs: '2ewujgywmum1yp49',
qk: 'bgxibcomzoto63sn',
desiredNumItems: 'tmb0vgxgp5grsnhp',
minTimestampMs: 'hljqffkpwlx0vtjt',
maxTimestampMs: 'sw317giy6x2xj9zm',
guids: 'pusjrhxxtyp5nois',
inviteeEmailAddress: 'cltkepgqkepfsyaq',
message: 'q0d6n7t1801bb6xu',
latE6: '5ygbhpxfnt1u9e4t',
lngE6: 'ak6twnljwwcgd7cj',
factionOnly: '0dvtbatgzcfccchh'
},
// set 1
{
method: 'uuo2zqhhy5bw80fu',
boundsParamsList: '5rc0561uauf6x13u',
id: 'bzeizowtguoyrrtt',
minLatE6: '7qej3eqg4sefuaac',
minLngE6: 'yqegc976egk5q9vo',
maxLatE6: '2odsgh99ix9bbtsb',
maxLngE6: 'g9jess8dwa2j8pwi',
timestampMs: '604f34zcu9zna0a5',
qk: 'y853tux9h7cb6xp3',
desiredNumItems: 'sfv5i7l6ouljz8vf',
minTimestampMs: 'y3g07dbnw6sklloj',
maxTimestampMs: '3pdl28aa27xvyhke',
guids: 'xp1pl2jm5hrh3bna',
inviteeEmailAddress: '2pyrttrp3gh38mmu',
message: 'zz54435vfc57nlg9',
latE6: 'cyltxjod3jhxgj8q',
lngE6: 'h9whcgcz6kpqkz80',
factionOnly: '37okcr7gvd5yn2lj'
},
];
window.activeRequestMungeSet = undefined;
// attempt to guess the munge set in use, by looking therough the functions of the stock intel page for one of the munged params
window.detectActiveMungeSet = function() {
for (var m in window) {
// try and find the stock page functions
if (typeof window[m] == 'function' && m.length <= 3) {
var stockFunc = window[m].toString();
for (var i in window.requestParameterMunges) {
if (stockFunc.indexOf (window.requestParameterMunges[i]['id']) >= 0) {
console.log('IITC: found request munge set '+i+' in stock intel function "window.'+m+'()"');
window.activeRequestMungeSet = i;
}
}
}
}
if (window.activeRequestMungeSet===undefined) {
console.error('IITC: failed to find request munge set - IITC will likely fail');
window.activeRequestMungeSet = 0;
}
}
// niantic now add some munging to the request parameters. so far, only two sets of this munging have been seen
window.requestDataMunge = function(data) {
if (window.activeRequestMungeSet===undefined) {
window.detectActiveMungeSet();
}
var activeMunge = window.requestParameterMunges[window.activeRequestMungeSet];
function munge(obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
// an array - munge each element of it
var newobj = [];
for (var i in obj) {
newobj[i] = munge(obj[i]);
}
return newobj;
} else if (typeof obj === 'object') {
// an object: munge each property name, and pass the value through the munge process
var newobj = Object();
for (var p in obj) {
var m = activeMunge[p];
if (m === undefined) {
console.error('Error: failed to find munge for object property '+p);
newobj[p] = obj[p];
} else {
// rename the property
newobj[m] = munge(obj[p]);
}
}
return newobj;
} else {
// neither an array or an object - so must be a simple value. return it unmodified
return obj;
}
};
var newdata = munge(data);
return newdata;
}
// posts AJAX request to Ingress API.
// action: last part of the actual URL, the rpc/dashboard. is
// added automatically
// data: JSON data to post. method will be derived automatically from
// action, but may be overridden. Expects to be given Hash.
// Strings are not supported.
// success: method to call on success. See jQuery API docs for avail-
// able arguments: http://api.jquery.com/jQuery.ajax/
// error: see above. Additionally it is logged if the request failed.
window.postAjax = function(action, data, success, error) {
var post_data = JSON.stringify(window.requestDataMunge($.extend({method: 'dashboard.'+action}, data)));
var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
var result = $.ajax({
url: '/r/dashboard.'+action,
type: 'POST',
data: post_data,
context: data,
dataType: 'json',
success: [remove, success],
error: error ? [errCnt, error] : errCnt,
contentType: 'application/json; charset=utf-8',
beforeSend: function(req) {
req.setRequestHeader('X-CSRFToken', readCookie('csrftoken'));
}
});
result.action = action;
return result;
}
window.zeroPad = function(number,pad) {
number = number.toString();
var zeros = pad - number.length;
return Array(zeros>0?zeros+1:0).join("0") + number;
}
// converts javascript timestamps to HH:mm:ss format if it was today;
// otherwise it returns YYYY-MM-DD
window.unixTimeToString = function(time, full) {
if(!time) return null;
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
var time = d.toLocaleTimeString();
var date = d.getFullYear()+'-'+zeroPad(d.getMonth()+1,2)+'-'+zeroPad(d.getDate(),2);
if(typeof full !== 'undefined' && full) return date + ' ' + time;
if(d.toDateString() == new Date().toDateString())
return time;
else
return date;
}
// converts a javascript time to a precise date and time (optionally with millisecond precision)
// formatted in ISO-style YYYY-MM-DD hh:mm:ss.mmm - but using local timezone
window.unixTimeToDateTimeString = function(time, millisecond) {
if(!time) return null;
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
return d.getFullYear()+'-'+zeroPad(d.getMonth()+1,2)+'-'+zeroPad(d.getDate(),2)
+' '+d.toLocaleTimeString()+(millisecond?'.'+zeroPad(d.getMilliseconds(),3):'');
}
window.unixTimeToHHmm = function(time) {
if(!time) return null;
var d = new Date(typeof time === 'string' ? parseInt(time) : time);
var h = '' + d.getHours(); h = h.length === 1 ? '0' + h : h;
var s = '' + d.getMinutes(); s = s.length === 1 ? '0' + s : s;
return h + ':' + s;
}
window.rangeLinkClick = function() {
if(window.portalRangeIndicator)
window.map.fitBounds(window.portalRangeIndicator.getBounds());
if(window.isSmartphone())
window.smartphone.mapButton.click();
}
window.showPortalPosLinks = function(lat, lng, name) {
var encoded_name = 'undefined';
if(name !== undefined) {
encoded_name = encodeURIComponent(name);
}
if (typeof android !== 'undefined' && android && android.intentPosLink) {
android.intentPosLink(lat, lng, map.getZoom(), name, true);
} else {
var qrcode = '';
var script = '';
var gmaps = 'Google Maps';
var bingmaps = 'Bing Maps';
var osm = 'OpenStreetMap';
var latLng = '<' + lat + ',' + lng +'>';
dialog({
html: '' + qrcode + script + gmaps + '; ' + bingmaps + '; ' + osm + '
' + latLng + '
',
title: name,
id: 'poslinks'
});
}
}
window.androidCopy = function(text) {
if(typeof android === 'undefined' || !android || !android.copy)
return true; // i.e. execute other actions
else
android.copy(text);
return false;
}
window.androidPermalink = function() {
if(typeof android === 'undefined' || !android || !android.copy)
return true; // i.e. execute other actions
var center = map.getCenter();
android.intentPosLink(center.lat, center.lng, map.getZoom(), "Intel Map", false);
return false;
}
window.getPortalDataZoom = function() {
var mapZoom = map.getZoom();
// make sure we're dealing with an integer here
// (mobile: a float somehow gets through in some cases!)
var z = parseInt(mapZoom);
// limiting the mazimum zoom level for data retrieval reduces the number of requests at high zoom levels
// (as all portal data is retrieved at z=17, why retrieve multiple z=18 tiles when fewer z=17 would do?)
// very effective along with the new cache code
if (z > 17) z=17;
//sanity check - should never happen
if (z < 0) z=0;
return z;
}
window.getMinPortalLevelForZoom = function(z) {
//based on code from stock gen_dashboard.js
switch(z) {
case 0:
case 1:
case 2:
case 3:
return 8;
case 4:
case 5:
return 7;
case 6:
case 7:
return 6;
case 8:
return 5;
case 9:
case 10:
return 4;
case 11:
case 12:
return 3;
case 13:
case 14:
return 2;
case 15:
case 16:
return 1;
default:
return 0
}
}
window.getMinPortalLevel = function() {
var z = getPortalDataZoom();
return getMinPortalLevelForZoom(z);
}
// returns number of pixels left to scroll down before reaching the
// bottom. Works similar to the native scrollTop function.
window.scrollBottom = function(elm) {
if(typeof elm === 'string') elm = $(elm);
return elm.get(0).scrollHeight - elm.innerHeight() - elm.scrollTop();
}
window.zoomToAndShowPortal = function(guid, latlng) {
map.setView(latlng, 17);
// if the data is available, render it immediately. Otherwise defer
// until it becomes available.
if(window.portals[guid])
renderPortalDetails(guid);
else
urlPortal = guid;
}
// translates guids to entity types
window.getTypeByGuid = function(guid) {
// All GUID type values, extracted from the ingress app .apk
// some are almost certainly Niantic internal use only - never seen on the network
// .1 == Panoramio portal [deprecated]
// .2 == Random portal
// .3 == Beacon
// .4 == Resource (dropped media - other dropped items?)
// .5 == Inventory item
// .6 == Energy glob (XM)
// .7 == Energy spawn location
// .8 == HMDB portal [deprecated]
// .9 == Link (internally "edge") ** TYPE_LINK
// .a == LocalStore portal [deprecated]
// .b == Control field (internally "captured region") ** TYPE_FIELD
// .c == Player ** TYPE_PLAYER
// .d == COMM message (internally "plext") ** TYPE_CHAT
// .e == Tracking record
// .f == Tracking group
// .10 == Passcode reward [deprecated]
// .11 == SuperOps portal ** TYPE_PORTAL - the batch loaded from panorimo, etc?
// .12 == Anon portal ** TYPE_PORTAL - user submitted by email?
// .13 == Account info
// .14 == GeoStore POI [deprecated]
// .15 == GeoStore mod
// .16 == GeoStore portal ** TYPE_PORTAL - new in-app submissions?
// .17 == Portal image
// .18 == PMRR change
// resonator guid is [portal guid]-resonator-[slot]
switch(guid.slice(33)) {
case '2':
case '11':
case '12':
case '16':
return TYPE_PORTAL;
case '9':
return TYPE_LINK;
case 'b':
return TYPE_FIELD;
case 'c':
return TYPE_PLAYER;
case 'd':
return TYPE_CHAT;
default:
if(guid.slice(-11,-2) == 'resonator') return TYPE_RESONATOR;
return TYPE_UNKNOWN;
}
}
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
}
// http://stackoverflow.com/a/646643/1684530 by Bergi and CMS
if (typeof String.prototype.startsWith !== 'function') {
String.prototype.startsWith = function (str){
return this.slice(0, str.length) === str;
};
}
// escape a javascript string, so quotes and backslashes are escaped with a backslash
// (for strings passed as parameters to html onclick="..." for example)
window.escapeJavascriptString = function(str) {
return (str+'').replace(/[\\"']/g,'\\$&');
}
//escape special characters, such as tags
window.escapeHtmlSpecialChars = function(str) {
var div = document.createElement(div);
var text = document.createTextNode(str);
div.appendChild(text);
return div.innerHTML;
}
window.prettyEnergy = function(nrg) {
return nrg> 1000 ? Math.round(nrg/1000) + ' k': nrg;
}
window.setPermaLink = function(elm) {
var c = map.getCenter();
var lat = Math.round(c.lat*1E6)/1E6;
var lng = Math.round(c.lng*1E6)/1E6;
var qry = 'll='+lat+','+lng+'&z=' + map.getZoom();
$(elm).attr('href', '/intel?' + qry);
}
window.uniqueArray = function(arr) {
return $.grep(arr, function(v, i) {
return $.inArray(v, arr) === i;
});
}
window.genFourColumnTable = function(blocks) {
var t = $.map(blocks, function(detail, index) {
if(!detail) return '';
if(index % 2 === 0)
return ''+detail[1]+' | '+detail[0]+' | ';
else
return ' '+detail[0]+' | '+detail[1]+' |
';
}).join('');
if(t.length % 2 === 1) t + ' | | ';
return t;
}
// converts given text with newlines (\n) and tabs (\t) to a HTML
// table automatically.
window.convertTextToTableMagic = function(text) {
// check if it should be converted to a table
if(!text.match(/\t/)) return text.replace(/\n/g, '
');
var data = [];
var columnCount = 0;
// parse data
var rows = text.split('\n');
$.each(rows, function(i, row) {
data[i] = row.split('\t');
if(data[i].length > columnCount) columnCount = data[i].length;
});
// build the table
var table = '';
$.each(data, function(i, row) {
table += '';
$.each(data[i], function(k, cell) {
var attributes = '';
if(k === 0 && data[i].length < columnCount) {
attributes = ' colspan="'+(columnCount - data[i].length + 1)+'"';
}
table += ''+cell+' | ';
});
table += '
';
});
table += '
';
return table;
}
// Given 3 sets of points in an array[3]{lat, lng} returns the area of the triangle
window.calcTriArea = function(p) {
return Math.abs((p[0].lat*(p[1].lng-p[2].lng)+p[1].lat*(p[2].lng-p[0].lng)+p[2].lat*(p[0].lng-p[1].lng))/2);
}
// Update layerGroups display status to window.overlayStatus and localStorage 'ingress.intelmap.layergroupdisplayed'
window.updateDisplayedLayerGroup = function(name, display) {
overlayStatus[name] = display;
localStorage['ingress.intelmap.layergroupdisplayed'] = JSON.stringify(overlayStatus);
}
// Read layerGroup status from window.overlayStatus if it was added to map,
// read from cookie if it has not added to map yet.
// return 'defaultDisplay' if both overlayStatus and cookie didn't have the record
window.isLayerGroupDisplayed = function(name, defaultDisplay) {
if(typeof(overlayStatus[name]) !== 'undefined') return overlayStatus[name];
convertCookieToLocalStorage('ingress.intelmap.layergroupdisplayed');
var layersJSON = localStorage['ingress.intelmap.layergroupdisplayed'];
if(!layersJSON) return defaultDisplay;
var layers = JSON.parse(layersJSON);
// keep latest overlayStatus
overlayStatus = $.extend(layers, overlayStatus);
if(typeof(overlayStatus[name]) === 'undefined') return defaultDisplay;
return overlayStatus[name];
}
window.addLayerGroup = function(name, layerGroup, defaultDisplay) {
if(isLayerGroupDisplayed(name, defaultDisplay)) map.addLayer(layerGroup);
layerChooser.addOverlay(layerGroup, name);
}
window.clampLat = function(lat) {
if (lat > 90.0)
lat = 90.0;
else if (lat < -90.0)
lat = -90.0;
return lat;
}
window.clampLng = function(lng) {
if (lng > 179.999999)
lng = 179.999999;
else if (lng < -180.0)
lng = -180.0;
return lng;
}
window.clampLatLng = function(latlng) {
return new L.LatLng ( clampLat(latlng.lat), clampLng(latlng.lng) );
}
window.clampLatLngBounds = function(bounds) {
return new L.LatLngBounds ( clampLatLng(bounds.getSouthWest()), clampLatLng(bounds.getNorthEast()) );
}