2013-06-15 17:54:27 +01:00

770 lines
27 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.

// MAP DATA //////////////////////////////////////////////////////////
// these functions handle how and which entities are displayed on the
// map. They also keep them up to date, unless interrupted by user
// action.
// cache for data tiles. indexed by the query key (qk)
window._requestCache = {}
// cache entries older than the fresh age, and younger than the max age, are stale. they're used in the case of an error from the server
window.REQUEST_CACHE_FRESH_AGE = 2*60; // if younger than this, use data in the cache rather than fetching from the server
window.REQUEST_CACHE_MAX_AGE = 15*60; // maximum cache age. entries are deleted from the cache after this time
window.storeDataCache = function(qk,data) {
var d = new Date();
window._requestCache[qk] = { time: d.getTime(), data: data };
}
window.getDataCache = function(qk) {
if (qk in window._requestCache) {
return window._requestCache[qk].data;
}
return undefined;
}
window.isDataCacheFresh = function(qk) {
if (qk in window._requestCache) {
var d = new Date();
var t = d.getTime()-REQUEST_CACHE_FRESH_AGE*1000;
if (window._requestCache[qk].time > t) {
return true;
}
}
return false;
}
window.expireDataCache = function() {
// TODO: add a limit on the maximum number of cache entries, and start expiring them sooner than the expiry time
// (might also make sense to approximate the size of cache entries, and have an overall size limit?)
var d = new Date();
var t = d.getTime()-window.REQUEST_CACHE_MAX_AGE*1000;
for(var qk in window._requestCache) {
if (window._requestCache[qk].time < t) {
delete window._requestCache[qk];
}
}
}
// requests map data for current viewport. For details on how this
// works, refer to the description in “MAP DATA REQUEST CALCULATORS”
window.requestData = function() {
console.log('refreshing data');
requests.abort();
cleanUp();
expireDataCache();
//a limit on the number of map tiles to be pulled in a single request
var MAX_TILES_PER_BUCKET = 20;
var bounds = clampLatLngBounds(map.getBounds());
//we query the server as if the zoom level was this. it may not match the actual map zoom level
var z = getPortalDataZoom();
console.log('requesting tiles at zoom '+z+' (L'+getMinPortalLevelForZoom(z)+'+ portals), map zoom is '+map.getZoom());
var x1 = lngToTile(bounds.getWest(), z);
var x2 = lngToTile(bounds.getEast(), z);
var y1 = latToTile(bounds.getNorth(), z);
var y2 = latToTile(bounds.getSouth(), z);
// will group requests by second-last quad-key quadrant
tiles = {};
fullBucketCount = 0;
var cachedData = { result: { map: {} } };
var requestTileCount = 0;
// walk in x-direction, starts right goes left
for (var x = x1; x <= x2; x++) {
for (var y = y1; y <= y2; y++) {
var tile_id = pointToTileId(z, x, y);
if (isDataCacheFresh(tile_id)) {
cachedData.result.map[tile_id] = getDataCache(tile_id);
} else {
var bucket = (Math.floor(x/2) % 2) + ":" + (Math.floor(y/2) % 2);
if (!tiles[bucket]) {
//create empty bucket
tiles[bucket] = [];
}
else if(tiles[bucket].length >= MAX_TILES_PER_BUCKET) {
//too many items in bucket. rename it, and create a new empty one
tiles[bucket+'_'+fullBucketCount] = tiles[bucket];
fullBucketCount++;
tiles[bucket] = [];
}
requestTileCount++;
tiles[bucket].push(generateBoundsParams(
tile_id,
tileToLat(y + 1, z),
tileToLng(x, z),
tileToLat(y, z),
tileToLng(x + 1, z)
));
}
}
}
// Reset previous result of Portal Render Limit handler
portalRenderLimit.init();
// process the requests from the cache
console.log('got '+Object.keys(cachedData.result.map).length+' data tiles from cache');
handleDataResponse(cachedData, true);
// finally send ajax requests
console.log('requesting '+requestTileCount+' tiles in '+Object.keys(tiles).length+' requests');
$.each(tiles, function(ind, tls) {
data = { zoom: z };
data.boundsParamsList = tls;
window.requests.add(window.postAjax('getThinnedEntitiesV2', data, function(data, textStatus, jqXHR) { window.handleDataResponse(data,false); }, window.handleFailedRequest));
});
}
// Handle failed map data request
window.handleFailedRequest = function() {
if(requests.isLastRequest('getThinnedEntitiesV2')) {
var leftOverPortals = portalRenderLimit.mergeLowLevelPortals(null);
handlePortalsRender(leftOverPortals);
}
runHooks('requestFinished', {success: false});
}
// works on map data response and ensures entities are drawn/updated.
window.handleDataResponse = function(data, fromCache) {
// remove from active ajax queries list
if(!data || !data.result) {
window.failedRequestCount++;
console.warn(data);
handleFailedRequest();
return;
}
var m = data.result.map;
// defer rendering of portals because there is no z-index in SVG.
// this means that whats rendered last ends up on top. While the
// portals can be brought to front, this costs extra time. They need
// to be in the foreground, or they cannot be clicked. See
// https://github.com/Leaflet/Leaflet/issues/185
var ppp = {};
var p2f = {};
$.each(m, function(qk, val) {
if('error' in val) {
console.log('map data tile '+qk+' response error: '+val.error);
// try to use data in the cache, even if it's stale
val = getDataCache(qk);
if (!val) {
// no data in cache for this tile - skip it
return true; // $.each 'continue'
} else {
console.log('(using stale cache entry for map tile)');
}
}
if (!fromCache) storeDataCache(qk,val);
$.each(val.deletedGameEntityGuids || [], function(ind, guid) {
if(getTypeByGuid(guid) === TYPE_FIELD && window.fields[guid] !== undefined) {
$.each(window.fields[guid].options.vertices, function(ind, vertex) {
if(window.portals[vertex.guid] === undefined) return true;
fieldArray = window.portals[vertex.guid].options.details.portalV2.linkedFields;
fieldArray.splice($.inArray(guid, fieldArray), 1);
});
}
window.removeByGuid(guid);
});
$.each(val.gameEntities || [], function(ind, ent) {
// ent = [GUID, id(?), details]
// format for links: { controllingTeam, creator, edge }
// format for portals: { controllingTeam, turret }
if(ent[2].turret !== undefined) {
var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6];
if(!window.getPaddedBounds().contains(latlng)
&& selectedPortal !== ent[0]
&& urlPortal !== ent[0]
&& !(urlPortalLL && urlPortalLL[0] === latlng[0] && urlPortalLL[1] === latlng[1])
) return;
if('imageByUrl' in ent[2] && 'imageUrl' in ent[2].imageByUrl) {
if(window.location.protocol === 'https:') {
ent[2].imageByUrl.imageUrl = ent[2].imageByUrl.imageUrl.indexOf('www.panoramio.com') !== -1
? ent[2].imageByUrl.imageUrl.replace(/^http:\/\/www/, 'https://ssl').replace('small', 'medium')
: ent[2].imageByUrl.imageUrl.replace(/^http:\/\//, '//');
}
} else {
ent[2].imageByUrl = {'imageUrl': DEFAULT_PORTAL_IMG};
}
ppp[ent[0]] = ent; // delay portal render
} else if(ent[2].edge !== undefined) {
renderLink(ent);
} else if(ent[2].capturedRegion !== undefined) {
$.each(ent[2].capturedRegion, function(ind, vertex) {
if(p2f[vertex.guid] === undefined)
p2f[vertex.guid] = new Array();
p2f[vertex.guid].push(ent[0]);
});
renderField(ent);
} else {
throw('Unknown entity: ' + JSON.stringify(ent));
}
});
});
$.each(ppp, function(ind, portal) {
// when both source and destination portal are in the same response, no explicit 'edge' is returned
// instead, we need to reconstruct them from the data within the portal details
if ('portalV2' in portal[2] && 'linkedEdges' in portal[2].portalV2) {
$.each(portal[2].portalV2.linkedEdges, function (ind, edge) {
if (!ppp[edge.otherPortalGuid])
return;
renderLink([
edge.edgeGuid,
portal[1],
{
"controllingTeam": portal[2].controllingTeam,
"edge": {
"destinationPortalGuid": edge.isOrigin ? ppp[edge.otherPortalGuid][0] : portal[0],
"destinationPortalLocation": edge.isOrigin ? ppp[edge.otherPortalGuid][2].locationE6 : portal[2].locationE6,
"originPortalGuid": !edge.isOrigin ? ppp[edge.otherPortalGuid][0] : portal[0],
"originPortalLocation": !edge.isOrigin ? ppp[edge.otherPortalGuid][2].locationE6 : portal[2].locationE6
},
}
]);
});
}
if(portal[2].portalV2['linkedFields'] === undefined) {
portal[2].portalV2['linkedFields'] = [];
}
if(p2f[portal[0]] !== undefined) {
$.merge(p2f[portal[0]], portal[2].portalV2['linkedFields']);
portal[2].portalV2['linkedFields'] = uniqueArray(p2f[portal[0]]);
}
});
// Process the portals with portal render limit handler first
// Low level portal will hold until last request
var newPpp = portalRenderLimit.splitOrMergeLowLevelPortals(ppp);
// Clean up level of portal which over render limit after portalRenderLimit handler counted the new portals
portalRenderLimit.cleanUpOverLimitPortalLevel();
handlePortalsRender(newPpp);
resolvePlayerNames();
renderUpdateStatus();
runHooks('requestFinished', {success: true});
}
window.handlePortalsRender = function(portals) {
var portalInUrlAvailable = false;
// Preserve selectedPortal because it will get lost on re-rendering
// the portal
var oldSelectedPortal = selectedPortal;
runHooks('portalDataLoaded', {portals : portals});
$.each(portals, function(guid, portal) {
//~ if(selectedPortal === portal[0]) portalUpdateAvailable = true;
if(urlPortalLL && urlPortalLL[0] === portal[2].locationE6.latE6/1E6 && urlPortalLL[1] === portal[2].locationE6.lngE6/1E6) {
urlPortal = guid;
portalInUrlAvailable = true;
urlPortalLL = null;
}
if(window.portals[guid]) {
highlightPortal(window.portals[guid]);
}
renderPortal(portal);
});
// restore selected portal if still available
var selectedPortalGroup = portals[oldSelectedPortal];
if(selectedPortalGroup) {
selectedPortal = oldSelectedPortal;
renderPortalDetails(selectedPortal);
try {
selectedPortalGroup.bringToFront();
} catch(e) { /* portal is now visible, catch Leaflet error */ }
}
if(portalInUrlAvailable) {
renderPortalDetails(urlPortal);
urlPortal = null; // select it only once
}
}
// removes entities that are still handled by Leaflet, although they
// do not intersect the current viewport.
window.cleanUp = function() {
var cnt = [0,0,0];
var b = getPaddedBounds();
var minlvl = getMinPortalLevel();
for(var i = 0; i < portalsLayers.length; i++) {
// i is also the portal level
portalsLayers[i].eachLayer(function(item) {
var itemGuid = item.options.guid;
// check if 'item' is a portal
if(getTypeByGuid(itemGuid) != TYPE_PORTAL) return true;
// portal must be in bounds and have a high enough level. Also dont
// remove if it is selected.
if(itemGuid == window.selectedPortal ||
(b.contains(item.getLatLng()) && i >= minlvl)) return true;
cnt[0]++;
portalsLayers[i].removeLayer(item);
});
}
linksLayer.eachLayer(function(link) {
if(b.intersects(link.getBounds())) return;
cnt[1]++;
linksLayer.removeLayer(link);
});
fieldsLayer.eachLayer(function(fieldgroup) {
fieldgroup.eachLayer(function(item) {
if(!item.options.guid) return true; // Skip MU div container as this doesn't have the bounds we need
if(b.intersects(item.getBounds())) return;
cnt[2]++;
fieldsLayer.removeLayer(fieldgroup);
});
});
console.log('removed out-of-bounds: '+cnt[0]+' portals, '+cnt[1]+' links, '+cnt[2]+' fields');
}
// removes given entity from map
window.removeByGuid = function(guid) {
switch(getTypeByGuid(guid)) {
case TYPE_PORTAL:
if(!window.portals[guid]) return;
var p = window.portals[guid];
for(var i = 0; i < portalsLayers.length; i++)
portalsLayers[i].removeLayer(p);
break;
case TYPE_LINK:
if(!window.links[guid]) return;
linksLayer.removeLayer(window.links[guid]);
break;
case TYPE_FIELD:
if(!window.fields[guid]) return;
fieldsLayer.removeLayer(window.fields[guid]);
break;
case TYPE_RESONATOR:
if(!window.resonators[guid]) return;
var r = window.resonators[guid];
for(var i = 1; i < portalsLayers.length; i++)
portalsLayers[i].removeLayer(r);
break;
default:
console.warn('unknown GUID type: ' + guid);
//window.debug.printStackTrace();
}
}
// renders a portal on the map from the given entity
window.renderPortal = function(ent) {
if(Object.keys(portals).length >= MAX_DRAWN_PORTALS && ent[0] !== selectedPortal)
return removeByGuid(ent[0]);
// hide low level portals on low zooms
var portalLevel = getPortalLevel(ent[2]);
if(portalLevel < getMinPortalLevel() && ent[0] !== selectedPortal)
return removeByGuid(ent[0]);
var team = getTeam(ent[2]);
// do nothing if portal did not change
var layerGroup = portalsLayers[parseInt(portalLevel)];
var old = findEntityInLeaflet(layerGroup, window.portals, ent[0]);
if(!changing_highlighters && old) {
var oo = old.options;
// Default checks to see if a portal needs to be re-rendered
var u = oo.team !== team;
u = u || oo.level !== portalLevel;
// Allow plugins to add additional conditions as to when a portal gets re-rendered
var hookData = {portal: ent[2], oldPortal: oo.details, portalGuid: ent[0], reRender: false};
runHooks('beforePortalReRender', hookData);
u = u || hookData.reRender;
// nothing changed that requires re-rendering the portal.
if(!u) {
// let resos handle themselves if they need to be redrawn
renderResonators(ent[0], ent[2], old);
// update stored details for portal details in sidebar.
old.options.details = ent[2];
return;
}
}
// there were changes, remove old portal. Dont put this in old, in
// case the portal changed level and findEntityInLeaflet doesnt find
// it.
removeByGuid(ent[0]);
var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6];
// pre-loads player names for high zoom levels
loadPlayerNamesForPortal(ent[2]);
var lvWeight = Math.max(2, Math.floor(portalLevel) / 1.5);
var lvRadius = Math.floor(portalLevel) + 4;
if(team === window.TEAM_NONE) {
lvRadius = 7;
}
var p = L.circleMarker(latlng, {
radius: lvRadius + (L.Browser.mobile ? PORTAL_RADIUS_ENLARGE_MOBILE : 0),
color: ent[0] === selectedPortal ? COLOR_SELECTED_PORTAL : COLORS[team],
opacity: 1,
weight: lvWeight,
fillColor: COLORS[team],
fillOpacity: 0.5,
clickable: true,
level: portalLevel,
team: team,
ent: ent,
details: ent[2],
guid: ent[0]});
p.on('remove', function() {
var portalGuid = this.options.guid
// remove attached resonators, skip if
// all resonators have already removed by zooming
if(isResonatorsShow()) {
for(var i = 0; i <= 7; i++)
removeByGuid(portalResonatorGuid(portalGuid, i));
}
delete window.portals[portalGuid];
if(window.selectedPortal === portalGuid) {
window.unselectOldPortal();
}
});
p.on('add', function() {
// enable for debugging
if(window.portals[this.options.guid]) throw('duplicate portal detected');
window.portals[this.options.guid] = this;
window.renderResonators(this.options.guid, this.options.details, this);
// handles the case where a selected portal gets removed from the
// map by hiding all portals with said level
if(window.selectedPortal !== this.options.guid)
window.portalResetColor(this);
});
p.on('click', function() { window.renderPortalDetails(ent[0]); });
p.on('dblclick', function() {
window.renderPortalDetails(ent[0]);
window.map.setView(latlng, 17);
});
highlightPortal(p);
window.runHooks('portalAdded', {portal: p});
p.addTo(layerGroup);
}
window.renderResonators = function(portalGuid, portalDetails, portalLayer) {
if(!isResonatorsShow()) return;
// only draw when the portal is not hidden
if(portalLayer && !window.map.hasLayer(portalLayer)) return;
var portalLevel = getPortalLevel(portalDetails);
var portalLatLng = [portalDetails.locationE6.latE6/1E6, portalDetails.locationE6.lngE6/1E6];
var reRendered = false;
$.each(portalDetails.resonatorArray.resonators, function(i, rdata) {
// skip if resonator didn't change
var oldRes = window.resonators[portalResonatorGuid(portalGuid, i)];
if(oldRes) {
if(isSameResonator(oldRes.options.details, rdata)) return true;
// remove old resonator if exist
removeByGuid(oldRes.options.guid);
}
// skip and remove old resonator if no new resonator
if(rdata === null) return true;
var resoLatLng = getResonatorLatLng(rdata.distanceToPortal, rdata.slot, portalLatLng);
var resoGuid = portalResonatorGuid(portalGuid, i);
// the resonator
var resoStyle =
portalGuid === selectedPortal ? OPTIONS_RESONATOR_SELECTED : OPTIONS_RESONATOR_NON_SELECTED;
var resoProperty = $.extend({
fillColor: COLORS_LVL[rdata.level],
fillOpacity: rdata.energyTotal/RESO_NRG[rdata.level],
guid: resoGuid
}, resoStyle);
var reso = L.circleMarker(resoLatLng, resoProperty);
// line connecting reso to portal
var connProperty =
portalGuid === selectedPortal ? OPTIONS_RESONATOR_LINE_SELECTED : OPTIONS_RESONATOR_LINE_NON_SELECTED;
var conn = L.polyline([portalLatLng, resoLatLng], connProperty);
// put both in one group, so they can be handled by the same logic.
var r = L.layerGroup([reso, conn]);
r.options = {
level: rdata.level,
details: rdata,
pDetails: portalDetails,
guid: resoGuid
};
// However, LayerGroups (and FeatureGroups) dont fire add/remove
// events, thus this listener will be attached to the resonator. It
// doesnt matter to which element these are bound since Leaflet
// will add/remove all elements of the LayerGroup at once.
reso.on('remove', function() { delete window.resonators[this.options.guid]; });
reso.on('add', function() {
if(window.resonators[this.options.guid]) throw('duplicate resonator detected');
window.resonators[this.options.guid] = r;
});
r.addTo(portalsLayers[parseInt(portalLevel)]);
reRendered = true;
});
// if there is any resonator re-rendered, bring portal to front
if(reRendered && portalLayer) portalLayer.bringToFront();
}
// append portal guid with -resonator-[slot] to get guid for resonators
window.portalResonatorGuid = function(portalGuid, slot) {
return portalGuid + '-resonator-' + slot;
}
window.isResonatorsShow = function() {
return map.getZoom() >= RESONATOR_DISPLAY_ZOOM_LEVEL;
}
window.isSameResonator = function(oldRes, newRes) {
if(!oldRes && !newRes) return true;
if(!oldRes || !newRes) return false;
if(typeof oldRes !== typeof newRes) return false;
if(oldRes.level !== newRes.level) return false;
if(oldRes.energyTotal !== newRes.energyTotal) return false;
if(oldRes.distanceToPortal !== newRes.distanceToPortal) return false;
return true;
}
window.portalResetColor = function(portal) {
portal.setStyle({color: COLORS[getTeam(portal.options.details)]});
resonatorsResetStyle(portal.options.guid);
}
window.resonatorsResetStyle = function(portalGuid) {
window.resonatorsSetStyle(portalGuid, OPTIONS_RESONATOR_NON_SELECTED, OPTIONS_RESONATOR_LINE_NON_SELECTED);
}
window.resonatorsSetSelectStyle = function(portalGuid) {
window.resonatorsSetStyle(portalGuid, OPTIONS_RESONATOR_SELECTED, OPTIONS_RESONATOR_LINE_SELECTED);
}
window.resonatorsSetStyle = function(portalGuid, resoStyle, lineStyle) {
for(var i = 0; i < 8; i++) {
resonatorLayerGroup = resonators[portalResonatorGuid(portalGuid, i)];
if(!resonatorLayerGroup) continue;
// bring resonators and their connection lines to front separately.
// this way the resonators are drawn on top of the lines.
resonatorLayerGroup.eachLayer(function(layer) {
if (!layer.options.guid) // Resonator line
layer.bringToFront().setStyle(lineStyle);
});
resonatorLayerGroup.eachLayer(function(layer) {
if (layer.options.guid) // Resonator
layer.bringToFront().setStyle(resoStyle);
});
}
portals[portalGuid].bringToFront();
}
// renders a link on the map from the given entity
window.renderLink = function(ent) {
if(Object.keys(links).length >= MAX_DRAWN_LINKS)
return removeByGuid(ent[0]);
// some links are constructed from portal linkedEdges data. These have no valid 'creator' data.
// replace with the more detailed data
// (we assume the other values - coordinates, etc - remain unchanged)
var found=findEntityInLeaflet(linksLayer, links, ent[0]);
if (found) {
if (!found.options.data.creator && ent[2].creator) {
//our existing data has no creator, but the new data does - update
found.options.data = ent[2];
}
return;
}
var team = getTeam(ent[2]);
var edge = ent[2].edge;
var latlngs = [
[edge.originPortalLocation.latE6/1E6, edge.originPortalLocation.lngE6/1E6],
[edge.destinationPortalLocation.latE6/1E6, edge.destinationPortalLocation.lngE6/1E6]
];
var poly = L.polyline(latlngs, {
color: COLORS[team],
opacity: 1,
weight:2,
clickable: false,
guid: ent[0],
data: ent[2],
smoothFactor: 0 // doesnt work for two points anyway, so disable
});
// determine which links are very short and dont render them at all.
// in most cases this will go unnoticed, but improve rendering speed.
poly._map = window.map;
poly.projectLatlngs();
var op = poly._originalPoints;
var dist = Math.abs(op[0].x - op[1].x) + Math.abs(op[0].y - op[1].y);
if(dist <= 10) {
return;
}
if(!getPaddedBounds().intersects(poly.getBounds())) return;
poly.on('remove', function() { delete window.links[this.options.guid]; });
poly.on('add', function() {
// enable for debugging
if(window.links[this.options.guid]) throw('duplicate link detected');
window.links[this.options.guid] = this;
this.bringToBack();
});
poly.addTo(linksLayer);
}
// renders a field on the map from a given entity
window.renderField = function(ent) {
if(Object.keys(fields).length >= MAX_DRAWN_FIELDS)
return window.removeByGuid(ent[0]);
var old = findEntityInLeaflet(fieldsLayer, window.fields, ent[0]);
// If this already exists and the zoom level has not changed, we don't need to do anything
if(old && map.getZoom() === old.options.creationZoom) return;
var team = getTeam(ent[2]);
var reg = ent[2].capturedRegion;
var latlngs = [
L.latLng(reg.vertexA.location.latE6/1E6, reg.vertexA.location.lngE6/1E6),
L.latLng(reg.vertexB.location.latE6/1E6, reg.vertexB.location.lngE6/1E6),
L.latLng(reg.vertexC.location.latE6/1E6, reg.vertexC.location.lngE6/1E6)
];
var poly = L.polygon(latlngs, {
fillColor: COLORS[team],
fillOpacity: 0.25,
stroke: false,
clickable: false,
smoothFactor: 0, // hiding small fields will be handled below
guid: ent[0]});
// determine which fields are too small to be rendered and dont
// render them, so they dont count towards the maximum fields limit.
// This saves some DOM operations as well, but given the relatively
// low amount of fields there isnt much to gain.
// The algorithm is the same as used by Leaflet.
poly._map = window.map;
poly.projectLatlngs();
var count = L.LineUtil.simplify(poly._originalPoints, 6).length;
if(count <= 2) return;
if(!getPaddedBounds().intersects(poly.getBounds())) return;
// Curve fit equation to normalize zoom window area
var areaZoomRatio = calcTriArea(latlngs)/Math.exp(14.2714860198866-1.384987247*map.getZoom());
var countForMUDisplay = L.LineUtil.simplify(poly._originalPoints, FIELD_MU_DISPLAY_POINT_TOLERANCE).length
// Do nothing if zoom did not change. We need to recheck the field if the
// zoom level is different then when the field was rendered as it could
// now be appropriate or not to show an MU count
if(old) {
var layerCount = 0;
old.eachLayer(function(item) {
layerCount++;
});
// Don't do anything since we already have an MU display and we still want to
if(areaZoomRatio > FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay > 2 && layerCount === 2) return;
// Don't do anything since we don't have an MU display and don't want to
if(areaZoomRatio <= FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay <= 2 && layerCount === 1) return;
removeByGuid(ent[0]);
}
// put both in one group, so they can be handled by the same logic.
if (areaZoomRatio > FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay > 2) {
// centroid of field for placing MU count at
var centroid = [
(latlngs[0].lat + latlngs[1].lat + latlngs[2].lat)/3,
(latlngs[0].lng + latlngs[1].lng + latlngs[2].lng)/3
];
var fieldMu = L.marker(centroid, {
icon: L.divIcon({
className: 'fieldmu',
iconSize: [70,12],
html: digits(ent[2].entityScore.entityScore)
}),
clickable: false
});
var f = L.layerGroup([poly, fieldMu]);
} else {
var f = L.layerGroup([poly]);
}
f.options = {
vertices: reg,
lastUpdate: ent[1],
creationZoom: map.getZoom(),
guid: ent[0],
data: ent[2]
};
// However, LayerGroups (and FeatureGroups) dont fire add/remove
// events, thus this listener will be attached to the field. It
// doesnt matter to which element these are bound since Leaflet
// will add/remove all elements of the LayerGroup at once.
poly.on('remove', function() { delete window.fields[this.options.guid]; });
poly.on('add', function() {
// enable for debugging
if(window.fields[this.options.guid]) console.warn('duplicate field detected');
window.fields[this.options.guid] = f;
this.bringToBack();
});
f.addTo(fieldsLayer);
}
// looks for the GUID in either the layerGroup or entityHash, depending
// on which is faster. Will either return the Leaflet entity or null, if
// it does not exist.
// For example, to find a field use the function like this:
// field = findEntityInLeaflet(fieldsLayer, fields, 'asdasdasd');
window.findEntityInLeaflet = function(layerGroup, entityHash, guid) {
// fast way
if(map.hasLayer(layerGroup)) return entityHash[guid] || null;
// slow way in case the layer is currently hidden
var ent = null;
layerGroup.eachLayer(function(entity) {
if(entity.options.guid !== guid) return true;
ent = entity;
return false;
});
return ent;
}