this allows us to override the zoom used to retrieve server data, giving options to either retrieve lower zoom levels to reduce the number of requests, or higher zoom levels to retrieve lower level portals from the server
474 lines
16 KiB
JavaScript
474 lines
16 KiB
JavaScript
// UTILS + MISC ///////////////////////////////////////////////////////
|
||
|
||
window.aboutIITC = function(){
|
||
var v = '@@BUILDNAME@@-@@BUILDDATE@@';
|
||
var attrib = '@@INCLUDEMD:ATTRIBUTION.md@@';
|
||
var contrib = '@@INCLUDEMD:CONTRIBS.md@@'
|
||
var a = ''
|
||
+ ' <div><b>About IITC</b></div> '
|
||
+ ' <div>Ingress Intel Total Conversion</div> '
|
||
+ ' <hr>'
|
||
+ ' <div>'
|
||
+ ' <a href="http://iitc.jonatkins.com/" target="_blank">IITC Homepage</a><br />'
|
||
+ ' On the script’s homepage you can:'
|
||
+ ' <ul>'
|
||
+ ' <li>Find Updates</li>'
|
||
+ ' <li>Get Plugins</li>'
|
||
+ ' <li>Report Bugs</li>'
|
||
+ ' <li>Contribute!</li>'
|
||
+ ' </ul>'
|
||
+ ' </div>'
|
||
+ ' <div>'
|
||
+ ' MapQuest OSM tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="https://developer.mapquest.com/content/osm/mq_logo.png">'
|
||
+ ' </div>'
|
||
+ ' <hr>'
|
||
+ ' <div>Version: ' + v + '</div>'
|
||
+ ' <hr>'
|
||
+ ' <div>' + attrib + '</div>'
|
||
+ ' <hr>'
|
||
+ ' <div>' + contrib + '</div>';
|
||
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 ");
|
||
}
|
||
|
||
// 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($.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: '/rpc/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;
|
||
}
|
||
|
||
// converts unix 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()+'-'+(d.getMonth()+1)+'-'+d.getDate();
|
||
if(typeof full !== 'undefined' && full) return date + ' ' + time;
|
||
if(d.toDateString() == new Date().toDateString())
|
||
return time;
|
||
else
|
||
return date;
|
||
}
|
||
|
||
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, encoded_name);
|
||
} else {
|
||
var qrcode = '<div id="qrcode"></div>';
|
||
var script = '<script>$(\'#qrcode\').qrcode({text:\'GEO:'+lat+','+lng+'\'});</script>';
|
||
var gmaps = '<a href="https://maps.google.com/?q='+lat+','+lng+'%20('+encoded_name+')">Google Maps</a>';
|
||
var bingmaps = '<a href="http://www.bing.com/maps/?v=2&cp='+lat+'~'+lng+'&lvl=16&sp=Point.'+lat+'_'+lng+'_'+encoded_name+'___">Bing Maps</a>';
|
||
var osm = '<a href="http://www.openstreetmap.org/?mlat='+lat+'&mlon='+lng+'&zoom=16">OpenStreetMap</a>';
|
||
var latLng = '<span><' + lat + ',' + lng +'></span>';
|
||
dialog({
|
||
html: '<div style="text-align: center;">' + qrcode + script + gmaps + '; ' + bingmaps + '; ' + osm + '<br />' + latLng + '</div>',
|
||
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.reportPortalIssue = function(info) {
|
||
var t = 'Redirecting you to a Google Help Page.\n\nThe text box contains all necessary information. Press CTRL+C to copy it.';
|
||
var d = window.portals[window.selectedPortal].options.details;
|
||
|
||
var info = 'Your Nick: ' + PLAYER.nickname + ' '
|
||
+ 'Portal: ' + d.portalV2.descriptiveText.TITLE + ' '
|
||
+ 'Location: ' + d.portalV2.descriptiveText.ADDRESS
|
||
+' (lat ' + (d.locationE6.latE6/1E6) + '; lng ' + (d.locationE6.lngE6/1E6) + ')';
|
||
|
||
//codename, approx addr, portalname
|
||
if(prompt(t, info) !== null)
|
||
location.href = 'https://support.google.com/ingress?hl=en&contact=1';
|
||
}
|
||
|
||
window._storedPaddedBounds = undefined;
|
||
window.getPaddedBounds = function() {
|
||
if(_storedPaddedBounds === undefined) {
|
||
map.on('zoomstart zoomend movestart moveend', function() {
|
||
window._storedPaddedBounds = null;
|
||
});
|
||
}
|
||
if(renderLimitReached(0.7)) return window.map.getBounds();
|
||
if(window._storedPaddedBounds) return window._storedPaddedBounds;
|
||
|
||
var p = window.map.getBounds().pad(VIEWPORT_PAD_RATIO);
|
||
window._storedPaddedBounds = p;
|
||
return p;
|
||
}
|
||
|
||
// returns true if the render limit has been reached. The default ratio
|
||
// is 1, which means it will tell you if there are more items drawn than
|
||
// acceptable. A value of 0.9 will tell you if 90% of the amount of
|
||
// acceptable entities have been drawn. You can use this to heuristi-
|
||
// cally detect if the render limit will be hit.
|
||
window.renderLimitReached = function(ratio) {
|
||
ratio = ratio || 1;
|
||
if(Object.keys(portals).length*ratio >= MAX_DRAWN_PORTALS) return true;
|
||
if(Object.keys(links).length*ratio >= MAX_DRAWN_LINKS) return true;
|
||
if(Object.keys(fields).length*ratio >= MAX_DRAWN_FIELDS) return true;
|
||
var param = { 'reached': false };
|
||
window.runHooks('checkRenderLimit', param);
|
||
return param.reached;
|
||
}
|
||
|
||
window.getPortalDataZoom = function() {
|
||
var z = map.getZoom();
|
||
|
||
// 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?)
|
||
// a potential downside - we end up requesting more data than we needed from the larger tiles that go off
|
||
// the window edge.
|
||
if (z > 17) z=17;
|
||
|
||
// we could consider similar zoom-level consolidation, as, e.g. level 16 and 15 both return L1+, always
|
||
// request zoom 15 tiles. however, there are quirks in the current data stream, where small fields aren't
|
||
// returned by the server. using larger tiles always would amplify this issue.
|
||
|
||
|
||
//sanity check - should never happen
|
||
if (z < 0) z=0;
|
||
|
||
return z;
|
||
}
|
||
|
||
window.getMinPortalLevel = function() {
|
||
var z = getPortalDataZoom();
|
||
if(z >= 17) return 0;
|
||
if(z < 0) return 8;
|
||
var conv = [8,8,8,8,7,7,6,6,5,4,4,3,3,2,2,1,1];
|
||
var minLevelByRenderLimit = portalRenderLimit.getMinLevel();
|
||
var result = minLevelByRenderLimit > conv[z]
|
||
? minLevelByRenderLimit
|
||
: conv[z];
|
||
return result;
|
||
}
|
||
|
||
// 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 '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,'\\$&');
|
||
}
|
||
|
||
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 '<tr><td>'+detail[1]+'</td><th>'+detail[0]+'</th>';
|
||
else
|
||
return ' <th>'+detail[0]+'</th><td>'+detail[1]+'</td></tr>';
|
||
}).join('');
|
||
if(t.length % 2 === 1) t + '<td></td><td></td></tr>';
|
||
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, '<br>');
|
||
|
||
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 = '<table>';
|
||
$.each(data, function(i, row) {
|
||
table += '<tr>';
|
||
$.each(data[i], function(k, cell) {
|
||
var attributes = '';
|
||
if(k === 0 && data[i].length < columnCount) {
|
||
attributes = ' colspan="'+(columnCount - data[i].length + 1)+'"';
|
||
}
|
||
table += '<td'+attributes+'>'+cell+'</td>';
|
||
});
|
||
table += '</tr>';
|
||
});
|
||
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 > 180.0)
|
||
lng = 180.0
|
||
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()) );
|
||
}
|