Jon Atkins 01a8dbf4be more work on map data rendering rewrite
marker creation/style changing moved out of rendering code (to allow use from elsewhere, and possible overriding by plugins)
2013-08-26 15:19:21 +01:00

512 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 scripts 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;
}
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 = '<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>&lt;' + lat + ',' + lng +'&gt;</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.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.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.getPortalDataZoom = function() {
var mapZoom = map.getZoom();
var z = 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;
// if the data zoom is above the map zoom we can step back if the detail level is the same
// with the new cache code this works rather well
var minZoom = mapZoom;
// due to the new smaller tiles used for zoom <= 12, we can get away with using further out tiles
// this can mean better use of the cache, and less load on the niantic servers
if (mapZoom <= 12 && mapZoom > 0) minZoom -= 2;
while (z > minZoom && getMinPortalLevelForZoom(z) == getMinPortalLevelForZoom(z-1)) {
z = z-1;
}
//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 '<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()) );
}