This commit is contained in:
Jack Miner
2013-11-07 04:54:04 +00:00
92 changed files with 2925 additions and 1697 deletions

View File

@ -540,7 +540,6 @@ window.chat.show = function(name) {
? $('#updatestatus').hide()
: $('#updatestatus').show();
$('#chat, #chatinput').show();
$('#map').css('visibility', 'hidden');
var t = $('<a>'+name+'</a>');
window.chat.chooseAnchor(t);
@ -681,7 +680,18 @@ window.chat.postMsg = function() {
var msg = $.trim($('#chatinput input').val());
if(!msg || msg === '') return;
if(c === 'debug') return new Function (msg)();
if(c === 'debug') {
var result;
try {
result = eval(msg);
} catch(e) {
if(e.stack) console.error(e.stack);
throw e; // to trigger native error message
}
if(result !== undefined)
console.log(result.toString());
return result;
}
var publik = c === 'public';
var latlng = map.getCenter();

View File

@ -23,10 +23,6 @@ window.debug.console = function() {
}
window.debug.console.show = function() {
if (window.isSmartphone()) {
$('#scrollwrapper, #updatestatus').hide();
$('#map').css('visibility', 'hidden');
}
$('#chat, #chatinput').show();
window.debug.console.create();
$('#chatinput mark').css('cssText', 'color: #bbb !important').text('debug:');
@ -88,10 +84,20 @@ window.debug.console.error = function(text) {
window.debug.console.overwriteNative = function() {
window.debug.console.create();
window.console = function() {}
window.console.log = window.debug.console.log;
window.console.warn = window.debug.console.warn;
window.console.error = window.debug.console.error;
var nativeConsole = window.console;
window.console = {};
function overwrite(which) {
window.console[which] = function() {
nativeConsole[which].apply(nativeConsole, arguments);
window.debug.console[which].apply(window.debug.console, arguments);
}
}
overwrite("log");
overwrite("warn");
overwrite("error");
}
window.debug.console.overwriteNativeIfRequired = function() {

View File

@ -14,6 +14,7 @@ window.setupGeosearch = function() {
$('#geosearchwrapper img').click(function(){
map.locate({setView : true, maxZoom: 13});
});
$('#geosearch').keyup(function(){$(this).removeClass('search_not_found')});
}
window.search = function(search) {
@ -22,13 +23,16 @@ window.search = function(search) {
}
$.getJSON(NOMINATIM + encodeURIComponent(search), function(data) {
if(!data || !data[0]) return true;
if(!data || !data[0]) {
$('#geosearch').addClass('search_not_found');
return true;
}
var b = data[0].boundingbox;
if(!b) return true;
var southWest = new L.LatLng(b[0], b[2]),
northEast = new L.LatLng(b[1], b[3]),
bounds = new L.LatLngBounds(southWest, northEast);
window.map.fitBounds(bounds);
if(window.isSmartphone()) window.smartphone.mapButton.click();
if(window.isSmartphone()) window.show('map');
});
}

View File

@ -15,6 +15,9 @@
// required to successfully boot the plugin.
//
// Heres more specific information about each event:
// playerNameResolved: called when unresolved player name get resolved.
// Argument is {names: object} which names[guid] is the
// resolved player name.
// portalSelected: called when portal on map is selected/unselected.
// Provide guid of selected and unselected portal.
// mapDataRefreshStart: called when we start refreshing map data
@ -49,7 +52,7 @@
window._hooks = {}
window.VALID_HOOKS = [
'portalSelected',
'playerNameResolved', 'portalSelected',
'mapDataRefreshStart', 'mapDataRefreshEnd',
'portalAdded', 'linkAdded', 'fieldAdded',
'portalDetailsUpdated',

View File

@ -2,8 +2,16 @@
// cache for map data tiles.
window.DataCache = function() {
this.REQUEST_CACHE_FRESH_AGE = 60; // if younger than this, use data in the cache rather than fetching from the server
this.REQUEST_CACHE_MAX_AGE = 180; // maximum cache age. entries are deleted from the cache after this time
// stock site nemesis.dashboard.DataManager.CACHE_EXPIRY_MS_ = 18E4 - so should be 2 mins cache time
this.REQUEST_CACHE_FRESH_AGE = 120; // if younger than this, use data in the cache rather than fetching from the server
// stale cache entries can be updated (that's what the optional 'timestampMs' field in getThinnedEntnties is
// for, retrieving deltas) so use a long max age to take advantage of this
// however, ther must be an overall limit on the maximum age of data from the servers, otherwise the deletedEntity
// entries would grow indefinitely. an hour seems reasonable from experience with the data, so 55 mins max cache time
// this.REQUEST_CACHE_MAX_AGE = 55*60; // maximum cache age. entries are deleted from the cache after this time
//UPDATE: this timestampMs parameter doesn't work, so reduced max age to limit RAM usage
this.REQUEST_CACHE_MAX_AGE = 15*60; // maximum cache age. entries are deleted from the cache after this time
if (L.Browser.mobile) {
// on mobile devices, smaller cache size
@ -18,15 +26,19 @@ window.DataCache = function() {
}
window.DataCache.prototype.store = function(qk,data,date) {
window.DataCache.prototype.store = function(qk,data,freshTime) {
// fixme? common behaviour for objects is that properties are kept in the order they're added
// this is handy, as it allows easy retrieval of the oldest entries for expiring
// however, this is not guaranteed by the standards, but all our supported browsers work this way
delete this._cache[qk];
if (date === undefined) date = new Date();
this._cache[qk] = { time: date.getTime(), data: data };
var time = new Date().getTime();
if (freshTime===undefined) freshTime = this.REQUEST_CACHE_FRESH_AGE*1000;
var expire = time + freshTime;
this._cache[qk] = { time: time, expire: expire, data: data };
}
window.DataCache.prototype.get = function(qk) {
@ -42,8 +54,8 @@ window.DataCache.prototype.getTime = function(qk) {
window.DataCache.prototype.isFresh = function(qk) {
if (qk in this._cache) {
var d = new Date();
var t = d.getTime() - this.REQUEST_CACHE_FRESH_AGE*1000;
if (this._cache[qk].time >= t) return true;
var t = d.getTime();
if (this._cache[qk].expire >= t) return true;
else return false;
}

View File

@ -9,26 +9,33 @@
// Convertion functions courtesy of
// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
window.lngToTile = function(lng, zoom) {
return Math.floor((lng + 180) / 360 * Math.pow(2, (zoom>12)?zoom:(zoom+2)));
window.levelToTilesPerEdge = function(level) {
var LEVEL_TO_TILES_PER_EDGE = [65536, 65536, 16384, 16384, 4096, 1536, 1024, 256, 32];
return LEVEL_TO_TILES_PER_EDGE[level];
}
window.latToTile = function(lat, zoom) {
window.lngToTile = function(lng, level) {
return Math.floor((lng + 180) / 360 * levelToTilesPerEdge(level));
}
window.latToTile = function(lat, level) {
return Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) +
1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, (zoom>12)?zoom:(zoom+2)));
1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * levelToTilesPerEdge(level));
}
window.tileToLng = function(x, zoom) {
return x / Math.pow(2, (zoom>12)?zoom:(zoom+2)) * 360 - 180;
window.tileToLng = function(x, level) {
return x / levelToTilesPerEdge(level) * 360 - 180;
}
window.tileToLat = function(y, zoom) {
var n = Math.PI - 2 * Math.PI * y / Math.pow(2, (zoom>12)?zoom:(zoom+2));
window.tileToLat = function(y, level) {
var n = Math.PI - 2 * Math.PI * y / levelToTilesPerEdge(level);
return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
window.pointToTileId = function(zoom, x, y) {
return zoom + "_" + x + "_" + y;
window.pointToTileId = function(level, x, y) {
return level + "_" + x + "_" + y;
}
// given tile id and bounds, returns the format as required by the

View File

@ -192,7 +192,9 @@ window.Render.prototype.deletePortalEntity = function(guid) {
window.Render.prototype.deleteLinkEntity = function(guid) {
if (guid in window.links) {
var l = window.links[guid];
linksFactionLayers[l.options.team].removeLayer(l);
if (linksFactionLayers[l.options.team].hasLayer(l)) {
linksFactionLayers[l.options.team].removeLayer(l);
}
delete window.links[guid];
}
}

View File

@ -28,24 +28,27 @@ window.MapDataRequest = function() {
this.MAX_TILE_RETRIES = 3;
// refresh timers
this.MOVE_REFRESH = 0.5; //refresh time to use after a move
this.STARTUP_REFRESH = 5; //refresh time used on first load of IITC
this.MOVE_REFRESH = 1; //time, after a map move (pan/zoom) before starting the refresh processing
this.STARTUP_REFRESH = 3; //refresh time used on first load of IITC
this.IDLE_RESUME_REFRESH = 5; //refresh time used after resuming from idle
// after one of the above, there's an additional delay between preparing the refresh (clearing out of bounds,
// processing cache, etc) and actually sending the first network requests
this.DOWNLOAD_DELAY = 3; //delay after preparing the data download before tile requests are sent
// a short delay between one request finishing and the queue being run for the next request
// a short delay between one request finishing and the queue being run for the next request.
// this gives a chance of other requests finishing, allowing better grouping of retries in new requests
this.RUN_QUEUE_DELAY = 0.5;
// delay before re-queueing tiles
this.TILE_TIMEOUT_REQUEUE_DELAY = 0.5; // short delay before retrying a 'error==TIMEOUT' tile - as this is very common
this.BAD_REQUEST_REQUEUE_DELAY = 4; // longer delay before retrying a completely failed request - as in this case the servers are struggling
// delay before re-queueing tiles in failed requests
this.BAD_REQUEST_REQUEUE_DELAY = 5; // longer delay before retrying a completely failed request - as in this case the servers are struggling
// a delay before processing the queue after requeueing tiles. this gives a chance for other requests to finish
// or other requeue actions to happen before the queue is processed, allowing better grouping of requests
// however, the queue may be processed sooner if a previous timeout was set
this.REQUEUE_DELAY = 1;
// additionally, a delay before processing the queue after requeueing tiles
// (this way, if multiple requeue delays finish within a short time of each other, they're all processed in one queue run)
this.RERUN_QUEUE_DELAY = 2;
this.REFRESH_CLOSE = 120; // refresh time to use for close views z>12 when not idle and not moving
this.REFRESH_FAR = 600; // refresh time for far views z <= 12
@ -89,8 +92,8 @@ window.MapDataRequest.prototype.mapMoveEnd = function() {
if (this.fetchedDataParams) {
// we have fetched (or are fetching) data...
if (this.fetchedDataParams.zoom == zoom && this.fetchedDataParams.bounds.contains(bounds)) {
// ... and the data zoom levels are the same, and the current bounds is inside the fetched bounds
if (this.fetchedDataParams.mapZoom == map.getZoom() && this.fetchedDataParams.bounds.contains(bounds)) {
// ... and the zoom level is the same and the current bounds is inside the fetched bounds
// so, no need to fetch data. if there's time left, restore the original timeout
var remainingTime = (this.timerExpectedTimeoutTime - new Date().getTime())/1000;
@ -169,8 +172,8 @@ window.MapDataRequest.prototype.refresh = function() {
// a 'set' to keep track of hard failures for tiles
this.tileErrorCount = {};
// fill tileBounds with the data needed to request each tile
this.tileBounds = {};
// the 'set' of requested tile QKs
this.queuedTiles = {};
var bounds = clampLatLngBounds(map.getBounds());
@ -183,24 +186,24 @@ window.MapDataRequest.prototype.refresh = function() {
//var debugrect = L.rectangle(bounds,{color: 'red', fill: false, weight: 4, opacity: 0.8}).addTo(map);
//setTimeout (function(){ map.removeLayer(debugrect); }, 10*1000);
var x1 = lngToTile(bounds.getWest(), zoom);
var x2 = lngToTile(bounds.getEast(), zoom);
var y1 = latToTile(bounds.getNorth(), zoom);
var y2 = latToTile(bounds.getSouth(), zoom);
var x1 = lngToTile(bounds.getWest(), minPortalLevel);
var x2 = lngToTile(bounds.getEast(), minPortalLevel);
var y1 = latToTile(bounds.getNorth(), minPortalLevel);
var y2 = latToTile(bounds.getSouth(), minPortalLevel);
// calculate the full bounds for the data - including the part of the tiles off the screen edge
var dataBounds = L.latLngBounds([
[tileToLat(y2+1,zoom), tileToLng(x1,zoom)],
[tileToLat(y1,zoom), tileToLng(x2+1,zoom)]
[tileToLat(y2+1,minPortalLevel), tileToLng(x1,minPortalLevel)],
[tileToLat(y1,minPortalLevel), tileToLng(x2+1,minPortalLevel)]
]);
//var debugrect2 = L.rectangle(dataBounds,{color: 'magenta', fill: false, weight: 4, opacity: 0.8}).addTo(map);
//setTimeout (function(){ map.removeLayer(debugrect2); }, 10*1000);
// store the parameters used for fetching the data. used to prevent unneeded refreshes after move/zoom
this.fetchedDataParams = { bounds: dataBounds, zoom: zoom };
this.fetchedDataParams = { bounds: dataBounds, mapZoom: map.getZoom(), minPortalLevel: minPortalLevel };
window.runHooks ('mapDataRefreshStart', {bounds: bounds, zoom: zoom, tileBounds: dataBounds});
window.runHooks ('mapDataRefreshStart', {bounds: bounds, zoom: zoom, minPortalLevel: minPortalLevel, tileBounds: dataBounds});
this.render.startRenderPass();
this.render.clearPortalsBelowLevel(minPortalLevel);
@ -224,11 +227,11 @@ window.MapDataRequest.prototype.refresh = function() {
for (var y = y1; y <= y2; y++) {
// x goes from bottom to top(?)
for (var x = x1; x <= x2; x++) {
var tile_id = pointToTileId(zoom, x, y);
var latNorth = tileToLat(y,zoom);
var latSouth = tileToLat(y+1,zoom);
var lngWest = tileToLng(x,zoom);
var lngEast = tileToLng(x+1,zoom);
var tile_id = pointToTileId(minPortalLevel, x, y);
var latNorth = tileToLat(y,minPortalLevel);
var latSouth = tileToLat(y+1,minPortalLevel);
var lngWest = tileToLng(x,minPortalLevel);
var lngEast = tileToLng(x+1,minPortalLevel);
this.debugTiles.create(tile_id,[[latSouth,lngWest],[latNorth,lngEast]]);
@ -238,16 +241,17 @@ window.MapDataRequest.prototype.refresh = function() {
this.render.processTileData (this.cache.get(tile_id));
this.cachedTileCount += 1;
} else {
// no fresh data - queue a request
var boundsParams = generateBoundsParams(
tile_id,
latSouth,
lngWest,
latNorth,
lngEast
);
this.tileBounds[tile_id] = boundsParams;
// no fresh data
// render the cached stale data, if we have it. this ensures *something* appears quickly when possible
var old_data = this.cache && this.cache.get(tile_id);
if (old_data) {
this.render.processTileData (old_data);
}
// queue a request
this.queuedTiles[tile_id] = tile_id;
this.requestedTileCount += 1;
}
}
@ -255,10 +259,19 @@ window.MapDataRequest.prototype.refresh = function() {
this.setStatus ('loading');
// technically a request hasn't actually finished - however, displayed portal data has been refreshed
// so as far as plugins are concerned, it should be treated as a finished request
window.runHooks('requestFinished', {success: true});
console.log ('done request preperation (cleared out-of-bounds and invalid for zoom, and rendered cached data)');
// don't start processing the download queue immediately - start it after a short delay
this.delayProcessRequestQueue (this.DOWNLOAD_DELAY,true);
if (Object.keys(this.queuedTiles).length > 0) {
// queued requests - don't start processing the download queue immediately - start it after a short delay
this.delayProcessRequestQueue (this.DOWNLOAD_DELAY,true);
} else {
// all data was from the cache, nothing queued - run the queue 'immediately' so it handles the end request processing
this.delayProcessRequestQueue (0,true);
}
}
@ -273,7 +286,7 @@ window.MapDataRequest.prototype.delayProcessRequestQueue = function(seconds,isFi
window.MapDataRequest.prototype.processRequestQueue = function(isFirstPass) {
// if nothing left in the queue, end the render. otherwise, send network requests
if (Object.keys(this.tileBounds).length == 0) {
if (Object.keys(this.queuedTiles).length == 0) {
this.render.endRenderPass();
@ -301,7 +314,7 @@ window.MapDataRequest.prototype.processRequestQueue = function(isFirstPass) {
// create a list of tiles that aren't requested over the network
var pendingTiles = [];
for (var id in this.tileBounds) {
for (var id in this.queuedTiles) {
if (!(id in this.requestedTiles) ) {
pendingTiles.push(id);
}
@ -351,7 +364,7 @@ window.MapDataRequest.prototype.processRequestQueue = function(isFirstPass) {
window.MapDataRequest.prototype.sendTileRequest = function(tiles) {
var boundsParamsList = [];
var tilesList = [];
for (var i in tiles) {
var id = tiles[i];
@ -360,15 +373,14 @@ window.MapDataRequest.prototype.sendTileRequest = function(tiles) {
this.requestedTiles[id] = true;
var boundsParams = this.tileBounds[id];
if (boundsParams) {
boundsParamsList.push (boundsParams);
if (id in this.queuedTiles) {
tilesList.push (id);
} else {
console.warn('failed to find bounds for tile id '+id);
console.warn('no queue entry for tile id '+id);
}
}
var data = { boundsParamsList: boundsParamsList };
var data = { quadKeys: tilesList };
this.activeRequestCount += 1;
@ -382,7 +394,7 @@ window.MapDataRequest.prototype.sendTileRequest = function(tiles) {
}
window.MapDataRequest.prototype.requeueTile = function(id, error) {
if (id in this.tileBounds) {
if (id in this.queuedTiles) {
// tile is currently wanted...
// first, see if the error can be ignored due to retry counts
@ -408,7 +420,7 @@ window.MapDataRequest.prototype.requeueTile = function(id, error) {
this.failedTileCount += 1;
}
// and delete from the pending requests...
delete this.tileBounds[id];
delete this.queuedTiles[id];
} else {
// if false, was a 'timeout' or we're retrying, so unlimited retries (as the stock site does)
@ -418,9 +430,8 @@ window.MapDataRequest.prototype.requeueTile = function(id, error) {
// proper queue, just an object with guid as properties. Javascript standards don't guarantee the order of properties
// within an object. however, all current browsers do keep property order, and new properties are added at the end.
// therefore, delete and re-add the requeued tile and it will be added to the end of the queue
var boundsData = this.tileBounds[id];
delete this.tileBounds[id];
this.tileBounds[id] = boundsData;
delete this.queuedTiles[id];
this.queuedTiles[id] = id;
}
} // else the tile wasn't currently wanted (an old non-cancelled request) - ignore
@ -463,7 +474,6 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success)
if (val.error == "TIMEOUT") {
// TIMEOUT errors for individual tiles are 'expected'(!) - and result in a silent unlimited retries
timeoutTiles.push (id);
this.debugTiles.setState (id, 'tile-timeout');
} else {
console.warn('map data tile '+id+' failed: error=='+val.error);
errorTiles.push (id);
@ -478,12 +488,12 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success)
// if this tile was in the render list, render it
// (requests aren't aborted when new requests are started, so it's entirely possible we don't want to render it!)
if (id in this.tileBounds) {
if (id in this.queuedTiles) {
this.debugTiles.setState (id, 'ok');
this.render.processTileData (val);
delete this.tileBounds[id];
delete this.queuedTiles[id];
this.successTileCount += 1;
} // else we don't want this tile (from an old non-cancelled request) - ignore
@ -491,6 +501,9 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success)
}
// TODO? check for any requested tiles in 'tiles' not being mentioned in the response - and handle as if it's a 'timeout'?
window.runHooks('requestFinished', {success: true});
}
@ -498,28 +511,27 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success)
console.log ('getThinnedEntities status: '+tiles.length+' tiles: '+successTiles.length+' successful, '+timeoutTiles.length+' timed out, '+errorTiles.length+' failed');
//setTimeout has no way of passing the 'context' (aka 'this') to it's function
var savedContext = this;
// requeue any 'timeout' tiles immediately
if (timeoutTiles.length > 0) {
setTimeout (function() {
for (var i in timeoutTiles) {
var id = timeoutTiles[i];
delete savedContext.requestedTiles[id];
savedContext.requeueTile(id, false);
}
savedContext.delayProcessRequestQueue(this.RERUN_QUEUE_DELAY);
}, this.TILE_TIMEOUT_REQUEUE_DELAY*1000);
for (var i in timeoutTiles) {
var id = timeoutTiles[i];
delete this.requestedTiles[id];
this.requeueTile(id, false);
}
}
// but for other errors, delay before retrying (as the server is having issues)
if (errorTiles.length > 0) {
//setTimeout has no way of passing the 'context' (aka 'this') to it's function
var savedContext = this;
setTimeout (function() {
for (var i in errorTiles) {
var id = errorTiles[i];
delete savedContext.requestedTiles[id];
savedContext.requeueTile(id, true);
}
savedContext.delayProcessRequestQueue(this.RERUN_QUEUE_DELAY);
savedContext.delayProcessRequestQueue(this.REQUEUE_DELAY);
}, this.BAD_REQUEST_REQUEUE_DELAY*1000);
}

View File

@ -1,42 +1,45 @@
// created to start cleaning up "window" interaction
//
window.show = function(id) {
window.hideall();
if (typeof android !== 'undefined' && android && android.switchToPane) {
android.switchToPane(id);
}
switch(id) {
case 'full':
window.chat.show('full');
break;
case 'compact':
window.chat.show('compact');
break;
case 'public':
window.chat.show('public');
break;
case 'faction':
window.chat.show('faction');
break;
case 'debug':
window.debug.console.show();
break;
case 'map':
window.smartphone.mapButton.click();
$('#portal_highlight_select').show();
$('#farm_level_select').show();
break;
case 'info':
window.smartphone.sideButton.click();
break;
default:
window.smartphone.mapButton.click();
break;
}
window.hideall();
switch(id) {
case 'full':
window.chat.show('full');
break;
case 'compact':
window.chat.show('compact');
break;
case 'public':
window.chat.show('public');
break;
case 'faction':
window.chat.show('faction');
break;
case 'debug':
window.debug.console.show();
break;
case 'map':
window.smartphone.mapButton.click();
$('#portal_highlight_select').show();
$('#farm_level_select').show();
break;
case 'info':
window.smartphone.sideButton.click();
break;
default:
window.smartphone.mapButton.click();
break;
}
if (typeof android !== 'undefined' && android && android.switchToPane) {
android.switchToPane(id);
}
}
window.hideall = function() {
$('#chatcontrols, #chat, #chatinput, #sidebartoggle, #scrollwrapper, #updatestatus, #portal_highlight_select').hide();
$('#farm_level_select').hide();
$('#map').css('visibility', 'hidden');
$('#chatcontrols, #chat, #chatinput, #sidebartoggle, #scrollwrapper, #updatestatus, #portal_highlight_select').hide();
$('#farm_level_select').hide();
$('#map').css('visibility', 'hidden');
$('.ui-tooltip').remove();
}

View File

@ -39,6 +39,12 @@ window.playerNameToGuid = function(playerName) {
var cachedGuid = window._playerNameToGuidCache[playerName];
if (cachedGuid !== undefined) return cachedGuid;
// IITC needs our own player GUID, from a lookup by name. so we retrieve this from localstorage (if available)
if (playerName == PLAYER.nickname) {
cachedGuid = localStorage['PLAYER-'+PLAYER.nickname];
if (cachedGuid !== undefined) return cachedGuid;
}
var guid = null;
$.each(Object.keys(sessionStorage), function(ind,key) {
if(playerName === sessionStorage[key]) {
@ -68,9 +74,11 @@ window.resolvePlayerNames = function() {
window.playersInResolving = window.playersInResolving.concat(p);
postAjax('getPlayersByGuids', d, function(dat) {
var resolvedName = {};
if(dat.result) {
$.each(dat.result, function(ind, player) {
window.setPlayerName(player.guid, player.nickname);
resolvedName[player.guid] = player.nickname;
// remove from array
window.playersInResolving.splice(window.playersInResolving.indexOf(player.guid), 1);
});
@ -82,6 +90,9 @@ window.resolvePlayerNames = function() {
//therefore, not a good idea to automatically retry by adding back to the playersToResolve list
}
// Run hook 'playerNameResolved' with the resolved player names
window.runHooks('playerNameResolved', {names: resolvedName});
//TODO: have an event triggered for this instead of hard-coded single function call
if(window.selectedPortal)
window.renderPortalDetails(window.selectedPortal);
@ -107,22 +118,17 @@ window.setPlayerName = function(guid, nick, uncertain) {
alert('You have run into bug #37. Please help me solve it!\nCopy and paste this text and post it here:\nhttps://github.com/breunigs/ingress-intel-total-conversion/issues/37\nIf copy & pasting doesnt work, make a screenshot instead.\n\n\n' + window.debug.printStackTrace() + '\n\n\n' + JSON.stringify(nick));
}
sessionStorage[guid] = nick;
// IITC needs our own player ID early on in startup. the only way we can find this is by something else
// doing a guid->name lookup for our own name. as this doesn't always happen - and likely won't happen when needed
// we'll store our own name->guid lookup in localStorage
if (nick == PLAYER.nickname) {
localStorage['PLAYER-'+PLAYER.nickname] = guid;
PLAYER.guid = guid; // set it in PLAYER in case it wasn't already done
}
}
window.loadPlayerNamesForPortal = function(portal_details) {
if(map.getZoom() < PRECACHE_PLAYER_NAMES_ZOOM) return;
var e = portal_details;
if(e.captured && e.captured.capturingPlayerId)
getPlayerName(e.captured.capturingPlayerId);
if(!e.resonatorArray || !e.resonatorArray.resonators) return;
$.each(e.resonatorArray.resonators, function(ind, reso) {
if(reso) getPlayerName(reso.ownerGuid);
});
}
// test to see if a specific player GUID is a special system account (e.g. __JARVIS__, __ADA__) that shouldn't

View File

@ -90,6 +90,17 @@ window.renderPortalDetails = function(guid) {
portalDetailedDescription += '</table>';
}
var levelDetails = getPortalLevel(d);
if(levelDetails != 8) {
if(levelDetails==Math.ceil(levelDetails))
levelDetails += "\n8";
else
levelDetails += "\n" + (Math.ceil(levelDetails) - levelDetails)*8;
levelDetails += " resonator level(s) needed for next portal level";
} else {
levelDetails += "\nfully upgraded";
}
levelDetails = "Level " + levelDetails;
$('#portaldetails')
.attr('class', TEAM_TO_CSS[getTeam(d)])
@ -98,7 +109,7 @@ window.renderPortalDetails = function(guid) {
+ '<span class="close" onclick="renderPortalDetails(null); if(isSmartphone()) show(\'map\');" title="Close">X</span>'
// help cursor via ".imgpreview img"
+ '<div class="imgpreview" '+imgTitle+' style="background-image: url('+img+')">'
+ '<span id="level">'+Math.floor(getPortalLevel(d))+'</span>'
+ '<span id="level" title="'+levelDetails+'">'+Math.floor(getPortalLevel(d))+'</span>'
+ '<div class="portalDetails">'+ portalDetailedDescription + '</div>'
+ '<img class="hide" src="'+img+'"/></div>'
+ '</div>'
@ -134,9 +145,14 @@ window.setPortalIndicators = function(d) {
var range = getPortalRange(d);
var coord = [d.locationE6.latE6/1E6, d.locationE6.lngE6/1E6];
portalRangeIndicator = (range > 0
? L.geodesicCircle(coord, range, { fill: false, color: RANGE_INDICATOR_COLOR, weight: 3, clickable: false })
: L.circle(coord, range, { fill: false, stroke: false, clickable: false })
portalRangeIndicator = (range.range > 0
? L.geodesicCircle(coord, range.range, {
fill: false,
color: RANGE_INDICATOR_COLOR,
weight: 3,
dashArray: range.isLinkable ? undefined : "10,10",
clickable: false })
: L.circle(coord, range.range, { fill: false, stroke: false, clickable: false })
).addTo(map);
portalAccessIndicator = L.circle(coord, HACK_RANGE,

View File

@ -5,11 +5,20 @@
// returns displayable text+link about portal range
window.getRangeText = function(d) {
var range = getPortalRange(d);
var title = 'Base range:\t' + digits(Math.floor(range.base))+'m'
+ '\nLink amp boost:\t×'+range.boost
+ '\nRange:\t'+digits(Math.floor(range.range))+'m';
if(!range.isLinkable) title += '\nPortal is missing resonators,\nno new links can be made';
return ['range',
'<a onclick="window.rangeLinkClick()">'
+ (range > 1000
? Math.floor(range/1000) + ' km'
: Math.floor(range) + ' m')
'<a onclick="window.rangeLinkClick()"'
+ (range.isLinkable ? '' : ' style="text-decoration:line-through;"')
+ ' title="'+title+'">'
+ (range.range > 1000
? Math.floor(range.range/1000) + ' km'
: Math.floor(range.range) + ' m')
+ '</a>'];
}

View File

@ -47,18 +47,20 @@ window.getPortalRange = function(d) {
$.each(d.resonatorArray.resonators, function(ind, reso) {
if(!reso) {
resoMissing = true;
return false;
return;
}
lvl += parseInt(reso.level);
});
if(resoMissing) return 0;
var range = 160*Math.pow(getPortalLevel(d), 4);
var range = {
base: 160*Math.pow(getPortalLevel(d), 4),
boost: getLinkAmpRangeBoost(d)
};
var boost = getLinkAmpRangeBoost(d);
return range*boost;
range.range = range.boost * range.base;
range.isLinkable = !resoMissing;
return range;
}
window.getLinkAmpRangeBoost = function(d) {

View File

@ -35,7 +35,6 @@ window.runOnSmartphonesBeforeBoot = function() {
}
window.smartphone.mapButton = $('<a>map</a>').click(function() {
$('#chat, #chatinput, #scrollwrapper').hide();
$('#map').css('visibility', 'visible');
$('#updatestatus').show();
$('#chatcontrols a .active').removeClass('active');
@ -43,8 +42,6 @@ window.runOnSmartphonesBeforeBoot = function() {
});
window.smartphone.sideButton = $('<a>info</a>').click(function() {
$('#chat, #chatinput, #updatestatus').hide();
$('#map').css('visibility', 'hidden');
$('#scrollwrapper').show();
$('.active').removeClass('active');
$("#chatcontrols a:contains('info')").addClass('active');
@ -112,11 +109,13 @@ window.runOnSmartphonesAfterBoot = function() {
if(!isSmartphone()) return;
console.warn('running smartphone post boot stuff');
smartphone.mapButton.click();
window.show('map');
// add a div/hook for updating mobile info
$('#updatestatus').prepend('<div id="mobileinfo" onclick="show(\'info\')"></div>');
window.addHook('portalDetailsUpdated', window.smartphoneInfo);
// init msg of status bar. hint for the user that a tap leads to the info screen
$('#mobileinfo').html('<div style="text-align: center"><b>tap here for info screen</b></div>');
// disable img full view
$('#portaldetails').off('click', '**');

View File

@ -1,597 +0,0 @@
// 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 ");
}
window.requestParameterMunges = [
// now obsolete (they don't have some of the new parameters) munge sets deleted
// set 3 - in the update of 2013-09-30 (addition of 'alerts' chat tab)
{
'dashboard.getGameScore': 'fhlzntzkl5v7hcfh', // GET_GAME_SCORE
'dashboard.getPaginatedPlextsV2': 'wzuitnswoda7w028', // GET_PAGINATED_PLEXTS
'dashboard.getThinnedEntitiesV4': 'scgrm4lf2371esgw', // GET_THINNED_ENTITIES
'dashboard.getPlayersByGuids': '81l6usczczoi3lfi', // LOOKUP_PLAYERS
'dashboard.redeemReward': '8kop2koeld9b4c26', // REDEEM_REWARD
'dashboard.sendInviteEmail': 't0ccodsm1nuo5uso', // SEND_INVITE_EMAIL
'dashboard.sendPlext': 'k04cfjwwsg3h3827', // SEND_PLEXT
method: '22ux2z96jwq5zn78',
version: 'kf6hgl9yau03ws0o', //guessed parameter name - only seen munged
version_parameter: '4608f4356a6f55690f127fb542f557f98de66169', // passed as the value to the above parameter
boundsParamsList: '29t16cmsn6l3r2xg',
id: '7rogqhp5pzcqobcw',
minLatE6: 'yzbnp7z9bd28p0yr',
minLngE6: '2pdhntvo85cd90bw',
maxLatE6: 'c4ivr013h4dr68pd',
maxLngE6: '4p8oorcrwalc1mzf',
timestampMs: 'vd2rsa9v6f8q606s',
qk: 'cblh9xe0bgwjy5ij',
desiredNumItems: '3ymaq7slb165porj',
minTimestampMs: 's9jf2seni33y3gyu',
maxTimestampMs: '2kh3vti98rhp3g29',
chatTab: '7n7ocqfq1p18352b', //guessed parameter name - only seen munged
ascendingTimestampOrder: 'p88a2ztchtjhiazl',
message: 'e8qm0kptw2trrcrw',
latE6: 'fja1phtsqxm71dqm',
lngE6: 'iut1tb7c0x726hwn',
guids: '5hyiwhwc0jyljvro',
inviteeEmailAddress: 's9z6zt03eymzxhkj',
},
// set 4 - second update of 2013-09-30
{
'dashboard.getGameScore': 'ija9jgrf5hj7wm9r', // GET_GAME_SCORE
'dashboard.getPaginatedPlextsV2': '0elftx739mkbzi1b', // GET_PAGINATED_PLEXTS
'dashboard.getThinnedEntitiesV4': 'prv0ez8cbsykh63g', // GET_THINNED_ENTITIES
'dashboard.getPlayersByGuids': 'i0lxy6nc695z9ka3', // LOOKUP_PLAYERS
'dashboard.redeemReward': '376oivna8rf8qbfj', // REDEEM_REWARD
'dashboard.sendInviteEmail': '96y930v5q96nrcrw', // SEND_INVITE_EMAIL
'dashboard.sendPlext': 'c04kceytofuqvyqg', // SEND_PLEXT
method: '9we4b31i48ui4sdm',
version: 'q402kn5zqisuo1ym', //guessed parameter name - only seen munged
version_parameter: 'dbad4485024d446ae946e3d287b5d640029ef3e3', // passed as the value to the above parameter
boundsParamsList: '3r5ctyvc2f653zjd',
id: 'izey8ciqg2dz2oqc',
minLatE6: 'cein0n4jrifa7ui2',
minLngE6: 'lbd1juids3johtdo',
maxLatE6: 'h4kyot9kmvd3g284',
maxLngE6: 'sbci6jjc2d5g9uy4',
timestampMs: '2wurn9giagbvv6bt',
qk: 'hq73mwpjqyvcp6ul',
desiredNumItems: 'kyo6vh5n58hmrnua',
minTimestampMs: 'hu4swdftcp7mvkdi',
maxTimestampMs: 'ly6ylae5lv1z9072',
chatTab: 'q5kxut5rmbtlqbf9', //guessed parameter name - only seen munged
ascendingTimestampOrder: 'hvfd0io35rahwjgr',
message: 'z4hf7tzl27o14455',
latE6: 'zyzh3bdxyd47vk1x',
lngE6: 'n5d1f8pql51t641x',
guids: 'gl16ehqoc3i3oi07',
inviteeEmailAddress: 'orc9ufg7rp7g1y9j',
},
];
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() {
if (window.requestParameterMunges.length == 1) {
// no point in searching through the code when there's only one set in use
window.activeRequestMungeSet = 0;
return;
}
// try and find the stock page functions
// FIXME? revert to searching through all the code? is that practical?
var stockFunc = nemesis.dashboard.network.DataFetcher.prototype.sendRequest_.toString()
for (var i in window.requestParameterMunges) {
if (stockFunc.indexOf (window.requestParameterMunges[i]['method']) >= 0) {
console.log('IITC: found request munge set index '+i+' in stock intel function nemesis.dashboard.network.DataFetcher.prototype.sendRequest_');
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) {
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) {
if (window.activeRequestMungeSet===undefined) {
window.detectActiveMungeSet();
}
var activeMunge = window.requestParameterMunges[window.activeRequestMungeSet];
var methodName = 'dashboard.'+action;
var versionStr = 'version_parameter';
// munging of the method name - seen in Set 2 (onwards?)
methodName = activeMunge[methodName];
// and of the 'version' parameter
versionStr = activeMunge[versionStr];
var post_data = JSON.stringify(window.requestDataMunge($.extend({method: methodName, version: versionStr}, 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/'+methodName,
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.formatInterval = function(seconds) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
var s = seconds % 60;
var text = '';
if (h > 0) text += h+'h';
if (m > 0) text += m+'m';
if (s > 0 || text == '') text += s+'s';
return text;
}
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.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;
}
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) {
// the map projection used does not handle above approx +- 85 degrees north/south of the equator
if (lat > 85.051128)
lat = 85.051128;
else if (lat < -85.051128)
lat = -85.051128;
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()) );
}