534 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
 | 
						||
// 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.
 | 
						||
 | 
						||
 | 
						||
// 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();
 | 
						||
 | 
						||
  var magic = convertCenterLat(map.getCenter().lat);
 | 
						||
  var R = calculateR(magic);
 | 
						||
 | 
						||
  var bounds = map.getBounds();
 | 
						||
  // convert to point values
 | 
						||
  topRight = convertLatLngToPoint(bounds.getNorthEast(), magic, R);
 | 
						||
  bottomLeft = convertLatLngToPoint(bounds.getSouthWest() , magic, R);
 | 
						||
  // how many quadrants intersect the current view?
 | 
						||
  quadsX = Math.abs(bottomLeft.x - topRight.x);
 | 
						||
  quadsY = Math.abs(bottomLeft.y - topRight.y);
 | 
						||
 | 
						||
  // will group requests by second-last quad-key quadrant
 | 
						||
  tiles = {};
 | 
						||
 | 
						||
  // walk in x-direction, starts right goes left
 | 
						||
  for(var i = 0; i <= quadsX; i++) {
 | 
						||
    var x = Math.abs(topRight.x - i);
 | 
						||
    var qk = pointToQuadKey(x, topRight.y);
 | 
						||
    var bnds = convertPointToLatLng(x, topRight.y, magic, R);
 | 
						||
    if(!tiles[qk.slice(0, -1)]) tiles[qk.slice(0, -1)] = [];
 | 
						||
    tiles[qk.slice(0, -1)].push(generateBoundsParams(qk, bnds));
 | 
						||
 | 
						||
    // walk in y-direction, starts top, goes down
 | 
						||
    for(var j = 1; j <= quadsY; j++) {
 | 
						||
      var qk = pointToQuadKey(x, topRight.y + j);
 | 
						||
      var bnds = convertPointToLatLng(x, topRight.y + j, magic, R);
 | 
						||
      if(!tiles[qk.slice(0, -1)]) tiles[qk.slice(0, -1)] = [];
 | 
						||
      tiles[qk.slice(0, -1)].push(generateBoundsParams(qk, bnds));
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // finally send ajax requests
 | 
						||
  $.each(tiles, function(ind, tls) {
 | 
						||
    data = { minLevelOfDetail: -1 };
 | 
						||
    data.boundsParamsList = tls;
 | 
						||
    window.requests.add(window.postAjax('getThinnedEntitiesV2', data, window.handleDataResponse));
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
// works on map data response and ensures entities are drawn/updated.
 | 
						||
window.handleDataResponse = function(data, textStatus, jqXHR) {
 | 
						||
  // remove from active ajax queries list
 | 
						||
  if(!data || !data.result) {
 | 
						||
    window.failedRequestCount++;
 | 
						||
    console.warn(data);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  var portalUpdateAvailable = false;
 | 
						||
  var portalInUrlAvailable = false;
 | 
						||
  var m = data.result.map;
 | 
						||
  // defer rendering of portals because there is no z-index in SVG.
 | 
						||
  // this means that what’s 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) {
 | 
						||
    $.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) {
 | 
						||
        if(selectedPortal == ent[0]) portalUpdateAvailable = true;
 | 
						||
        if(urlPortal && ent[0] == urlPortal) portalInUrlAvailable = true;
 | 
						||
 | 
						||
        var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6];
 | 
						||
        if(!window.getPaddedBounds().contains(latlng)
 | 
						||
              && selectedPortal != ent[0]
 | 
						||
              && urlPortal != ent[0]
 | 
						||
          ) return;
 | 
						||
 | 
						||
 | 
						||
 | 
						||
        ppp.push(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) {
 | 
						||
    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]]);
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  $.each(ppp, function(ind, portal) { renderPortal(portal); });
 | 
						||
  if(portals[selectedPortal]) {
 | 
						||
    try {
 | 
						||
      portals[selectedPortal].bringToFront();
 | 
						||
    } catch(e) { /* portal is now visible, catch Leaflet error */ }
 | 
						||
  }
 | 
						||
 | 
						||
  if(portalInUrlAvailable) {
 | 
						||
    renderPortalDetails(urlPortal);
 | 
						||
    urlPortal = null; // select it only once
 | 
						||
  }
 | 
						||
 | 
						||
  if(portalUpdateAvailable) renderPortalDetails(selectedPortal);
 | 
						||
  resolvePlayerNames();
 | 
						||
}
 | 
						||
 | 
						||
// 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 don’t
 | 
						||
      // 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(field) {
 | 
						||
    if(b.intersects(field.getBounds())) return;
 | 
						||
    cnt[2]++;
 | 
						||
    fieldsLayer.removeLayer(field);
 | 
						||
  });
 | 
						||
  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(old) {
 | 
						||
    var oo = old.options;
 | 
						||
    var u = oo.team !== team;
 | 
						||
    u = u || oo.level !== portalLevel;
 | 
						||
    // nothing changed that requires re-rendering the portal.
 | 
						||
    if(!u) {
 | 
						||
      // let resos handle themselves if they need to be redrawn
 | 
						||
      renderResonators(ent, old);
 | 
						||
      // update stored details for portal details in sidebar.
 | 
						||
      old.options.details = ent[2];
 | 
						||
      return;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // there were changes, remove old portal. Don’t put this in old, in
 | 
						||
  // case the portal changed level and findEntityInLeaflet doesn’t 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, portalLevel / 1.5);
 | 
						||
  var lvRadius = Math.max(portalLevel + 3, 5);
 | 
						||
 | 
						||
  var p = L.circleMarker(latlng, {
 | 
						||
    radius: lvRadius,
 | 
						||
    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,
 | 
						||
    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();
 | 
						||
      window.map.removeLayer(window.portalAccessIndicator);
 | 
						||
      window.portalAccessIndicator = null;
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  p.on('add', function() {
 | 
						||
    // enable for debugging
 | 
						||
    if(window.portals[this.options.guid]) throw('duplicate portal detected');
 | 
						||
    window.portals[this.options.guid] = 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);
 | 
						||
  });
 | 
						||
 | 
						||
  window.renderResonators(ent, null);
 | 
						||
 | 
						||
  window.runHooks('portalAdded', {portal: p});
 | 
						||
  p.addTo(layerGroup);
 | 
						||
}
 | 
						||
 | 
						||
window.renderResonators = function(ent, portalLayer) {
 | 
						||
  if(!isResonatorsShow()) return;
 | 
						||
 | 
						||
  var portalLevel = getPortalLevel(ent[2]);
 | 
						||
  if(portalLevel < getMinPortalLevel()  && ent[0] != selectedPortal) return;
 | 
						||
  var portalLatLng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6];
 | 
						||
 | 
						||
  var layerGroup = portalsLayers[parseInt(portalLevel)];
 | 
						||
  var reRendered = false;
 | 
						||
  $.each(ent[2].resonatorArray.resonators, function(i, rdata) {
 | 
						||
    // skip if resonator didn't change
 | 
						||
    if(portalLayer) {
 | 
						||
      var oldRes = findEntityInLeaflet(layerGroup, window.resonators, portalResonatorGuid(ent[0], i));
 | 
						||
      if(oldRes && isSameResonator(oldRes.options.details, rdata)) return true;
 | 
						||
    }
 | 
						||
 | 
						||
    // skip and remove old resonator if no new resonator
 | 
						||
    if(rdata === null) {
 | 
						||
      if(oldRes) removeByGuid(oldRes.options.guid);
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
 | 
						||
    // offset in meters
 | 
						||
    var dn = rdata.distanceToPortal*SLOT_TO_LAT[rdata.slot];
 | 
						||
    var de = rdata.distanceToPortal*SLOT_TO_LNG[rdata.slot];
 | 
						||
 | 
						||
    // Coordinate offset in radians
 | 
						||
    var dLat = dn/EARTH_RADIUS;
 | 
						||
    var dLon = de/(EARTH_RADIUS*Math.cos(Math.PI/180*(ent[2].locationE6.latE6/1E6)));
 | 
						||
 | 
						||
    // OffsetPosition, decimal degrees
 | 
						||
    var lat0 = ent[2].locationE6.latE6/1E6 + dLat * 180/Math.PI;
 | 
						||
    var lon0 = ent[2].locationE6.lngE6/1E6 + dLon * 180/Math.PI;
 | 
						||
    var Rlatlng = [lat0, lon0];
 | 
						||
 | 
						||
    var resoGuid = portalResonatorGuid(ent[0], i);
 | 
						||
 | 
						||
    // the resonator
 | 
						||
    var resoStyle =
 | 
						||
      ent[0] === selectedPortal ? OPTIONS_RESONATOR_SELECTED : OPTIONS_RESONATOR_NON_SELECTED;
 | 
						||
    var resoProperty = $.extend({
 | 
						||
        opacity: 1,
 | 
						||
        fillColor: COLORS_LVL[rdata.level],
 | 
						||
        fillOpacity: rdata.energyTotal/RESO_NRG[rdata.level],
 | 
						||
        clickable: false,
 | 
						||
        guid: resoGuid
 | 
						||
      }, resoStyle);
 | 
						||
 | 
						||
    var reso =  L.circleMarker(Rlatlng, resoProperty);
 | 
						||
 | 
						||
    // line connecting reso to portal
 | 
						||
    var connStyle =
 | 
						||
      ent[0] === selectedPortal ? OPTIONS_RESONATOR_LINE_SELECTED : OPTIONS_RESONATOR_LINE_NON_SELECTED;
 | 
						||
    var connProperty =  $.extend({
 | 
						||
        color: '#FFA000',
 | 
						||
        dashArray: '0,10,8,4,8,4,8,4,8,4,8,4,8,4,8,4,8,4,8,4',
 | 
						||
        fill: false,
 | 
						||
        clickable: false
 | 
						||
      }, connStyle);
 | 
						||
 | 
						||
    var conn = L.polyline([portalLatLng, Rlatlng], 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: ent[2],
 | 
						||
      guid: resoGuid
 | 
						||
    };
 | 
						||
 | 
						||
    // However, LayerGroups (and FeatureGroups) don’t fire add/remove
 | 
						||
    // events, thus this listener will be attached to the resonator. It
 | 
						||
    // doesn’t 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(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]);
 | 
						||
 | 
						||
  // assume that links never change. If they do, they will have a
 | 
						||
  // different ID.
 | 
						||
  if(findEntityInLeaflet(linksLayer, links, ent[0])) 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],
 | 
						||
    smoothFactor: 10
 | 
						||
  });
 | 
						||
 | 
						||
  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]);
 | 
						||
 | 
						||
  // assume that fields never change. If they do, they will have a
 | 
						||
  // different ID.
 | 
						||
  if(findEntityInLeaflet(fieldsLayer, fields, ent[0])) return;
 | 
						||
 | 
						||
  var team = getTeam(ent[2]);
 | 
						||
  var reg = ent[2].capturedRegion;
 | 
						||
  var latlngs = [
 | 
						||
    [reg.vertexA.location.latE6/1E6, reg.vertexA.location.lngE6/1E6],
 | 
						||
    [reg.vertexB.location.latE6/1E6, reg.vertexB.location.lngE6/1E6],
 | 
						||
    [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: 10,
 | 
						||
    vertices: ent[2].capturedRegion,
 | 
						||
    lastUpdate: ent[1],
 | 
						||
    guid: ent[0]});
 | 
						||
 | 
						||
  if(!getPaddedBounds().intersects(poly.getBounds())) return;
 | 
						||
 | 
						||
  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] = this;
 | 
						||
    this.bringToBack();
 | 
						||
  });
 | 
						||
  poly.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;
 | 
						||
}
 |