diff --git a/index.html b/index.html
index 96bd401..7ab384a 100644
--- a/index.html
+++ b/index.html
@@ -363,14 +363,16 @@
Report Recovery
-
-
+
+
+
+
diff --git a/js/leaflet-markers-canvas.min.js b/js/leaflet-markers-canvas.min.js
deleted file mode 100644
index a2d0e36..0000000
--- a/js/leaflet-markers-canvas.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("leaflet"),require("rbush")):"function"==typeof define&&define.amd?define(["leaflet","rbush"],t):t((e=e||self).L,e.RBush)}(this,function(o,e){"use strict";o=o&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o;var t={_map:null,_canvas:null,_context:null,_markers:[],_markersTree:new(e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e),_positionsTree:new e,_icons:{},addTo:function(e){return e.addLayer(this),this},getBounds:function(){var t=new o.LatLngBounds;return this._markers.forEach(function(e){t.extend(e.getLatLng())}),t},redraw:function(){this._redraw(!0)},clear:function(){this._positionsTree=new e,this._markersTree=new e,this._markers=[],this._redraw(!0)},addMarker:function(e){var t=this._addMarker(e),i=t.markerBox,e=t.positionBox,t=t.isVisible;i&&t&&this._markersTree.insert(i),e&&this._positionsTree.insert(e)},addMarkers:function(e){var s=this,a=[],r=[];e.forEach(function(e){var t=s._addMarker(e),i=t.markerBox,e=t.positionBox,t=t.isVisible;i&&t&&a.push(i),e&&r.push(e)}),this._markersTree.load(a),this._positionsTree.load(r)},removeMarker:function(e){var t=e.getLatLng(),i=this._map.getBounds().contains(t),e={minX:t.lng,minY:t.lat,maxX:t.lng,maxY:t.lat,marker:e};this._positionsTree.remove(e,function(e,t){return e.marker._leaflet_id===t.marker._leaflet_id}),i&&this._redraw(!0)},initialize:function(e){o.Util.setOptions(this,e)},onAdd:function(e){this._map=e,this._initCanvas(),this.getPane().appendChild(this._canvas),e.on("moveend",this._reset,this),e.on("resize",this._reset,this),e.on("click",this._fire,this),e.on("mousemove",this._fire,this),e._zoomAnimated&&e.on("zoomanim",this._animateZoom,this)},onRemove:function(e){this.getPane().removeChild(this._canvas),e.off("click",this._fire,this),e.off("mousemove",this._fire,this),e.off("moveend",this._reset,this),e.off("resize",this._reset,this),e._zoomAnimated&&e.off("zoomanim",this._animateZoom,this)},setOptions:function(e){return o.Util.setOptions(this,e),this.redraw()},_initCanvas:function(){var e=this._map.getSize(),t=e.x,i=e.y,e=this._map.options.zoomAnimation&&o.Browser.any3d;this._canvas=o.DomUtil.create("canvas","leaflet-markers-canvas-layer leaflet-layer"),this._canvas.width=t,this._canvas.height=i,this._context=this._canvas.getContext("2d"),o.DomUtil.addClass(this._canvas,"leaflet-zoom-"+(e?"animated":"hide"))},_addMarker:function(e){if("markerPane"!==e.options.pane||!e.options.icon)return console.error("This is not a marker",e),{markerBox:null,positionBox:null,isVisible:null};e._map=this._map,o.Util.stamp(e);var t=e.getLatLng(),i=this._map.getBounds().contains(t),s=this._map.latLngToContainerPoint(t),a=s.x,r=s.y,n=e.options.icon.options,s=n.iconSize,n=n.iconAnchor,n={minX:a-n[0],minY:r-n[1],maxX:a+s[0]-n[0],maxY:r+s[1]-n[1],marker:e},t={minX:t.lng,minY:t.lat,maxX:t.lng,maxY:t.lat,marker:e};return i&&this._drawMarker(e,{x:a,y:r}),this._markers.push(e),{markerBox:n,positionBox:t,isVisible:i}},_drawMarker:function(e,t){var s=this,i=t.x,a=t.y,r=e.options.icon.options.iconUrl;e.image?this._drawImage(e,{x:i,y:a}):this._icons[r]?(e.image=this._icons[r].image,this._icons[r].isLoaded?this._drawImage(e,{x:i,y:a}):this._icons[r].elements.push({marker:e,x:i,y:a})):((t=new Image).src=r,e.image=t,this._icons[r]={image:t,isLoaded:!1,elements:[{marker:e,x:i,y:a}]},t.onload=function(){s._icons[r].isLoaded=!0,s._icons[r].elements.forEach(function(e){var t=e.marker,i=e.x,e=e.y;s._drawImage(t,{x:i,y:e})})})},_drawImage:function(e,t){var i=t.x,s=t.y,a=e.options.icon.options,r=a.rotationAngle,t=a.iconAnchor,a=a.iconSize,r=r||0;this._context.save(),this._context.translate(i,s),this._context.rotate(r*Math.PI/180),this._context.drawImage(e.image,-t[0],-t[1],a[0],a[1]),this._context.restore()},_redraw:function(e){var r,n=this;e&&this._context.clearRect(0,0,this._canvas.width,this._canvas.height),this._map&&this._positionsTree&&(e={minX:(e=this._map.getBounds()).getWest(),minY:e.getSouth(),maxX:e.getEast(),maxY:e.getNorth()},r=[],this._positionsTree.search(e).forEach(function(e){var t=e.marker,i=t.getLatLng(),s=n._map.latLngToContainerPoint(i),a=s.x,e=s.y,i=t.options.icon.options,s=i.iconSize,i=i.iconAnchor,i={minX:a-i[0],minY:e-i[1],maxX:a+s[0]-i[0],maxY:e+s[1]-i[1],marker:t};r.push(i),n._drawMarker(t,{x:a,y:e})}),this._markersTree.clear(),this._markersTree.load(r))},_reset:function(){var e=this._map.containerPointToLayerPoint([0,0]);o.DomUtil.setPosition(this._canvas,e);var t=this._map.getSize(),e=t.x,t=t.y;this._canvas.width=e,this._canvas.height=t,this._redraw()},_fire:function(e){var t,i;this._markersTree&&(t=(i=e.containerPoint).x,i=i.y,(i=this._markersTree.search({minX:t,minY:i,maxX:t,maxY:i}))&&i.length?(this._map._container.style.cursor="pointer",i=i[0].marker,"click"===e.type&&i.listens("click")&&i.fire("click"),"mousemove"===e.type&&(this._mouseOverMarker&&this._mouseOverMarker!==i&&this._mouseOverMarker.listens("mouseout")&&this._mouseOverMarker.fire("mouseout"),this._mouseOverMarker&&this._mouseOverMarker===i||(this._mouseOverMarker=i).listens("mouseover")&&i.fire("mouseover"))):(this._map._container.style.cursor="","mousemove"===e.type&&this._mouseOverMarker&&(this._mouseOverMarker.listens("mouseout")&&this._mouseOverMarker.fire("mouseout"),delete this._mouseOverMarker)))},_animateZoom:function(e){var t=this._map.getZoomScale(e.zoom),e=this._map._latLngBoundsToNewLayerBounds(this._map.getBounds(),e.zoom,e.center).min;o.DomUtil.setTransform(this._canvas,e,t)}};o.MarkersCanvas=o.Layer.extend(t)});
diff --git a/js/leaflet.canvas-markers.js b/js/leaflet.canvas-markers.js
new file mode 100644
index 0000000..ba26b22
--- /dev/null
+++ b/js/leaflet.canvas-markers.js
@@ -0,0 +1,458 @@
+'use strict';
+
+function layerFactory (L) {
+
+ function extend (Parent, props) {
+ var NewClass = function () {
+ if (this.init) { this.init.apply(this, arguments); }
+ };
+ var proto = L.Util.create(Parent.prototype);
+ L.Util.extend(proto, props);
+ NewClass.prototype = proto;
+ return NewClass;
+ }
+
+ var Common = extend(rbush, {
+ init: function () {
+ rbush.apply(this, arguments);
+ this._batch = [];
+ },
+ insert: function (item, batch) {
+ if (batch) {
+ this._batch.push(item);
+ return this;
+ }
+ return rbush.prototype.insert.call(this, item);
+ },
+ flush: function () {
+ this.load(this._batch);
+ this._batch.length = 0;
+ return this;
+ }
+ });
+
+ var LatLngsIndex = extend(Common, {
+ toBBox: function (marker) {
+ var ll = marker._latlng;
+ return {minX: ll.lng, minY: ll.lat, maxX: ll.lng, maxY: ll.lat};
+ },
+ compareMinX: function (a, b) { return a._latlng.lng - b._latlng.lng; },
+ compareMinY: function (a, b) { return a._latlng.lat - b._latlng.lat; },
+ searchIn: function (bounds) {
+ return this.search({
+ minX: bounds.getWest(),
+ minY: bounds.getSouth(),
+ maxX: bounds.getEast(),
+ maxY: bounds.getNorth()
+ });
+ },
+ init: function () {
+ Common.prototype.init.apply(this, arguments);
+ this._dirty = 0;
+ this._total = 0;
+ },
+ // If we are 10% individual inserts\removals, reconstruct lookup for efficiency
+ cleanup: function () {
+ if (this._dirty / this._total >= .1) {
+ var all = this.all();
+ this.clear();
+ this._dirty = 0;
+ this.load(all);
+ }
+ return this;
+ },
+ insert: function () {
+ this._dirty++;
+ this._total++;
+ return Common.prototype.insert.apply(this, arguments);
+ },
+ remove: function () {
+ this._total--;
+ return Common.prototype.remove.apply(this, arguments);
+ },
+ clear: function () {
+ this._dirty = 0;
+ this._total = 0;
+ return rbush.prototype.clear.apply(this);
+ }
+ });
+
+ var PointsIndex = extend(Common, {
+ toBBox: function (marker) {
+ var iconSize = marker.options.icon.options.iconSize;
+ var pos = marker._point;
+ var adj_x = iconSize[0] / 2;
+ var adj_y = iconSize[1] / 2;
+ return {
+ minX: pos.x - adj_x,
+ minY: pos.y - adj_y,
+ maxX: pos.x + adj_x,
+ maxY: pos.y + adj_y,
+ };
+ },
+ compareMinX: function (a, b) { return a._point.x - b._point.x; },
+ compareMinY: function (a, b) { return a._point.y - b._point.y; },
+ searchBy: function (point) {
+ return this.search({
+ minX: point.x, minY: point.y, maxX: point.x, maxY: point.y
+ });
+ }
+ });
+
+ var CanvasIconLayer = L.Layer.extend({ // todo inherit from L.Renderer or L.Canvas
+
+ options: L.Canvas.prototype.options,
+
+ initialize: function (options) {
+ L.Renderer.prototype.initialize.call(this, options);
+ // _pointsIdx contains Points of markers currently displaying on map
+ this._pointsIdx = new PointsIndex();
+ // _latlngsIdx contains Lat\Long coordinates of all markers in layer.
+ this._latlngsIdx = new LatLngsIndex();
+ },
+
+ onAdd: function () {
+ L.Renderer.prototype.onAdd.call(this);
+ L.DomUtil.toBack(this._container);
+ },
+
+ _initContainer: function () {
+ L.Canvas.prototype._initContainer.call(this);
+ this._hideContainer(true);
+ },
+
+ onRemove: function () {
+ L.Renderer.prototype.onRemove.call(this);
+ },
+
+ _destroyContainer: function () {
+ L.Canvas.prototype._destroyContainer.call(this);
+ this._pointsIdx.clear();
+ },
+
+ getEvents: function () { // todo use L.Renderer.prototype.getEvents
+ var events = {
+ viewreset: this._reset,
+ zoom: this._onZoom,
+ moveend: this._update,
+ click: this._onClick,
+ mousemove: this._onMouseMove,
+ mouseout: this._handleMouseOut
+ };
+ if (this._zoomAnimated) {
+ events.zoomanim = this._onAnimZoom;
+ }
+ return events;
+ },
+
+ _onAnimZoom: function (ev) {
+ L.Renderer.prototype._onAnimZoom.call(this, ev);
+ },
+
+ _onZoom: function () {
+ L.Renderer.prototype._onZoom.call(this);
+ },
+
+ _updateTransform: function (center, zoom) {
+ L.Renderer.prototype._updateTransform.call(this, center, zoom);
+ },
+
+ _updatePaths: L.Util.falseFn, // stub for L.Renderer onAdd/onRemove
+
+ _update: function () {
+ L.Canvas.prototype._update.call(this);
+ this._draw();
+ },
+
+ _reset: function () {
+ this._update();
+ this._updateTransform(this._center, this._zoom);
+ },
+
+ _redraw: function () {
+ L.Canvas.prototype._redraw.call(this);
+ },
+
+ _clear: function () {
+ L.Canvas.prototype._clear.call(this);
+ },
+
+ _draw: function () {
+ var bounds = this._redrawBounds;
+ if (bounds) {
+ var size = bounds.getSize();
+ this._ctx.beginPath();
+ this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
+ this._ctx.clip();
+ }
+ this._drawing = true;
+ this._latlngsIdx.cleanup();
+ var mapBounds = this._map.getBounds().pad(this.options.padding);
+
+ // Only re-draw what we are showing on the map.
+ var isEmpty = true;
+ this._latlngsIdx.searchIn(mapBounds).forEach(function (marker) {
+ // Readjust Point Map
+ if (!marker._map) { marker._map = this._map; } // todo ??implement proper handling in (on)add*/remove*
+ this._drawMarker(marker);
+ this._pointsIdx.insert(marker,true);
+ isEmpty = false;
+ }, this);
+ this._drawing = false;
+ // Clear rBush & Bulk Load for performance
+ this._pointsIdx.clear().flush();
+ this._hideContainer(isEmpty);
+ },
+
+ _drawMarker: function (marker) {
+ marker._point = this._map.latLngToLayerPoint(marker.getLatLng());
+ this._imageLookup = this._imageLookup || {};
+
+ var iconUrl = marker.options.icon.options.iconUrl;
+ var queued = this._imageLookup[iconUrl];
+ if (!marker.canvas_img) {
+ if (queued) {
+ marker.canvas_img = queued.img;
+ if (queued.loaded) {
+ this._drawImage(marker);
+ } else {
+ queued.queue.push(marker);
+ }
+ } else {
+ var img = new Image();
+ img.src = iconUrl;
+ marker.canvas_img = img;
+ queued = {
+ loaded: false,
+ img: img,
+ queue: [marker]
+ };
+ this._imageLookup[iconUrl] = queued;
+ img.onload = function () {
+ queued.loaded = true;
+ queued.queue.forEach(function (_marker) {
+ if (this.hasLayer(_marker)) {
+ this._drawImage(_marker);
+ }
+ }, this);
+ }.bind(this);
+ }
+ } else if (queued.loaded) { // image may be not loaded / bad url
+ this._drawImage(marker);
+ }
+ },
+
+ _drawImage: function (marker) {
+ var options = marker.options.icon.options;
+ var pos = marker._point.subtract(options.iconAnchor);
+ this._ctx.drawImage(
+ marker.canvas_img,
+ pos.x,
+ pos.y,
+ options.iconSize[0],
+ options.iconSize[1]
+ );
+ },
+
+ _onClick: function (e) {
+ var point = e.layerPoint || this._map.mouseEventToLayerPoint(e), clickedLayer;
+
+ var layer_intersect = this._pointsIdx && this._pointsIdx.searchBy(point);
+ if (layer_intersect) {
+ layer_intersect.forEach(function (layer) {
+ if (layer.options.interactive && !this._map._draggableMoved(layer)) {
+ clickedLayer = layer;
+ }
+ }, this);
+ }
+ if (clickedLayer) {
+ L.DomEvent.fakeStop(e);
+ this._fireEvent([clickedLayer], e);
+ }
+ },
+
+ _onMouseMove: function (e) {
+ if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
+
+ var point = e.layerPoint || this._map.mouseEventToLayerPoint(e);
+ this._handleMouseHover(e, point);
+ },
+
+ _handleMouseHover: function (e, point) {
+ if (this._mouseHoverThrottled) {
+ return;
+ }
+ var candidateHoveredLayer;
+ var layer_intersect = this._pointsIdx && this._pointsIdx.searchBy(point);
+ if (layer_intersect) {
+ layer_intersect.forEach(function (layer) {
+ if (layer.options.interactive) {
+ candidateHoveredLayer = layer;
+ }
+ }, this);
+ }
+
+ if (candidateHoveredLayer !== this._hoveredLayer) {
+ this._handleMouseOut(e);
+
+ if (candidateHoveredLayer) {
+ L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
+ this._fireEvent([candidateHoveredLayer], e, 'mouseover');
+ this._hoveredLayer = candidateHoveredLayer;
+ }
+ }
+
+ if (this._hoveredLayer) {
+ this._fireEvent([this._hoveredLayer], e);
+ }
+
+ this._mouseHoverThrottled = true;
+ setTimeout(L.bind(function () {
+ this._mouseHoverThrottled = false;
+ }, this), 32);
+ },
+
+ _handleMouseOut: function (e) {
+ L.Canvas.prototype._handleMouseOut.call(this,e);
+ },
+
+ _fireEvent: function (layers, e, type) {
+ if (e.layerPoint) {
+ layers[0].fire(type || e.type, e, true);
+ return;
+ }
+ L.Canvas.prototype._fireEvent.call(this, layers, e, type);
+ },
+
+ _addMarker: function (marker, latlng, isDisplaying, batch) {
+ if (!(marker instanceof L.Marker)) {
+ throw new Error("Layer isn't a marker");
+ }
+ marker._map = this._map; // Needed for pop-up & tooltip to work
+ L.Util.stamp(marker);
+ marker.addEventParent(this);
+
+ if (isDisplaying) {
+ this._drawMarker(marker);
+ this._pointsIdx.insert(marker, batch);
+ this._hideContainer(false);
+ }
+ this._latlngsIdx.insert(marker, batch);
+ },
+
+ // Adds single layer at a time. Less efficient for rBush
+ addMarker: function (marker, groupID) {
+ groupID = groupID ? groupID.toString() : '0';
+ this._groupIDs = this._groupIDs || {};
+
+ var latlng = marker.getLatLng();
+ var isDisplaying = this._map && this._map.getBounds().pad(this.options.padding).contains(latlng);
+ this._addMarker(marker, latlng, isDisplaying);
+ this._groupIDs[groupID] = (this._groupIDs[groupID] || 0) + 1;
+ marker._canvasGroupID = groupID;
+ return this;
+ },
+
+ addLayer: function (layer, groupID) {
+ return this.addMarker(layer,groupID);
+ },
+
+ // Multiple layers at a time for rBush performance
+ addMarkers: function (markers, groupID) {
+ groupID = groupID ? groupID.toString() : '0';
+ this._groupIDs = this._groupIDs || {};
+ this._groupIDs[groupID] = this._groupIDs[groupID] || 0;
+
+ var mapBounds = this._map && this._map.getBounds().pad(this.options.padding);
+ markers.forEach(function (marker) {
+ var latlng = marker.getLatLng();
+ var isDisplaying = mapBounds && mapBounds.contains(latlng);
+ this._addMarker(marker, latlng, isDisplaying, true);
+ this._groupIDs[groupID]++;
+ marker._canvasGroupID = groupID;
+ }, this);
+ this._pointsIdx.flush();
+ this._latlngsIdx.flush();
+ return this;
+ },
+
+ addLayers: function (layers, groupID) {
+ return this.addMarkers(layers,groupID);
+ },
+
+ removeGroups: function (groupIDs) {
+ groupIDs.forEach(function (groupID) {
+ this._removeGroup(groupID);
+ }, this);
+ this._redraw();
+ return this;
+ },
+
+ removeGroup: function (groupID) {
+ this._removeGroup(groupID);
+ this._redraw();
+ return this;
+ },
+
+ _removeGroup: function (groupID) {
+ groupID = groupID.toString();
+ if (!this._groupIDs[groupID]) { return; }
+ delete this._groupIDs[groupID];
+ this._latlngsIdx.all().filter(function (marker) {
+ return marker._canvasGroupID === groupID;
+ }).forEach(function (el) {
+ this.removeMarker(el, false, true);
+ }, this);
+ },
+
+ removeMarker: function (marker, redraw, hasLayer) {
+ if (!hasLayer && !this.hasLayer(marker)) { return; }
+ this._latlngsIdx.remove(marker);
+
+ if (redraw && this._map &&
+ this._map.getBounds().pad(this.options.padding).contains(marker.getLatLng())) {
+ this._redraw();
+ }
+ marker.removeEventParent(this);
+ return this;
+ },
+
+ removeLayer: function (layer) {
+ return this.removeMarker(layer, true);
+ },
+ /*
+ removeLayers: function (layers) {
+ layers.forEach(function (el) {
+ this.removeMarker(el, false);
+ }, this);
+ this._redraw();
+ return this;
+ },
+ */
+ clearLayers: function () {
+ this._latlngsIdx.clear();
+ this._pointsIdx.clear();
+ this._clear();
+ return this;
+ },
+
+ hasLayer: function (layer) {
+ // return this._latlngsIdx.all().indexOf(layer) !== -1;
+ return layer._eventParents[L.Util.stamp(this)]; // !! to cut corners
+ },
+
+ _hideContainer: function (hide) {
+ if (this._isEmpty === hide) { return; }
+ this._isEmpty = hide;
+ this._container.style.display = hide ? 'none' : 'initial';
+ }
+ });
+
+ L.canvasIconLayer = function (options) {
+ return new CanvasIconLayer(options);
+ };
+
+ return CanvasIconLayer;
+}
+
+module.exports = layerFactory;
diff --git a/js/rbush.js b/js/rbush.js
index 4b40722..bf604e5 100644
--- a/js/rbush.js
+++ b/js/rbush.js
@@ -1,475 +1,467 @@
-(function (global, factory) {
-typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
-typeof define === 'function' && define.amd ? define(factory) :
-(global = global || self, global.RBush = factory());
-}(this, function () { 'use strict';
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.rbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o left) {
- if (right - left > 600) {
- var n = right - left + 1;
- var m = k - left + 1;
- var z = Math.log(n);
- var s = 0.5 * Math.exp(2 * z / 3);
- var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
- var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
- var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
- quickselectStep(arr, k, newLeft, newRight, compare);
- }
-
- var t = arr[k];
- var i = left;
- var j = right;
+module.exports = rbush;
+module.exports.default = rbush;
- swap(arr, left, k);
- if (compare(arr[right], t) > 0) { swap(arr, left, right); }
+var quickselect = require('quickselect');
- while (i < j) {
- swap(arr, i, j);
- i++;
- j--;
- while (compare(arr[i], t) < 0) { i++; }
- while (compare(arr[j], t) > 0) { j--; }
- }
+function rbush(maxEntries, format) {
+ if (!(this instanceof rbush)) return new rbush(maxEntries, format);
- if (compare(arr[left], t) === 0) { swap(arr, left, j); }
- else {
- j++;
- swap(arr, j, right);
- }
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
+ this._maxEntries = Math.max(4, maxEntries || 9);
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
- if (j <= k) { left = j + 1; }
- if (k <= j) { right = j - 1; }
+ if (format) {
+ this._initFormat(format);
}
-}
-function swap(arr, i, j) {
- var tmp = arr[i];
- arr[i] = arr[j];
- arr[j] = tmp;
+ this.clear();
}
-function defaultCompare(a, b) {
- return a < b ? -1 : a > b ? 1 : 0;
-}
+rbush.prototype = {
-var RBush = function RBush(maxEntries) {
- if ( maxEntries === void 0 ) maxEntries = 9;
+ all: function () {
+ return this._all(this.data, []);
+ },
- // max entries in a node is 9 by default; min node fill is 40% for best performance
- this._maxEntries = Math.max(4, maxEntries);
- this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
- this.clear();
-};
+ search: function (bbox) {
-RBush.prototype.all = function all () {
- return this._all(this.data, []);
-};
+ var node = this.data,
+ result = [],
+ toBBox = this.toBBox;
-RBush.prototype.search = function search (bbox) {
- var node = this.data;
- var result = [];
+ if (!intersects(bbox, node)) return result;
- if (!intersects(bbox, node)) { return result; }
+ var nodesToSearch = [],
+ i, len, child, childBBox;
- var toBBox = this.toBBox;
- var nodesToSearch = [];
+ while (node) {
+ for (i = 0, len = node.children.length; i < len; i++) {
- while (node) {
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var childBBox = node.leaf ? toBBox(child) : child;
+ child = node.children[i];
+ childBBox = node.leaf ? toBBox(child) : child;
- if (intersects(bbox, childBBox)) {
- if (node.leaf) { result.push(child); }
- else if (contains(bbox, childBBox)) { this._all(child, result); }
- else { nodesToSearch.push(child); }
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf) result.push(child);
+ else if (contains(bbox, childBBox)) this._all(child, result);
+ else nodesToSearch.push(child);
+ }
}
+ node = nodesToSearch.pop();
}
- node = nodesToSearch.pop();
- }
- return result;
-};
+ return result;
+ },
+
+ collides: function (bbox) {
-RBush.prototype.collides = function collides (bbox) {
- var node = this.data;
+ var node = this.data,
+ toBBox = this.toBBox;
- if (!intersects(bbox, node)) { return false; }
+ if (!intersects(bbox, node)) return false;
- var nodesToSearch = [];
- while (node) {
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var childBBox = node.leaf ? this.toBBox(child) : child;
+ var nodesToSearch = [],
+ i, len, child, childBBox;
- if (intersects(bbox, childBBox)) {
- if (node.leaf || contains(bbox, childBBox)) { return true; }
- nodesToSearch.push(child);
+ while (node) {
+ for (i = 0, len = node.children.length; i < len; i++) {
+
+ child = node.children[i];
+ childBBox = node.leaf ? toBBox(child) : child;
+
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf || contains(bbox, childBBox)) return true;
+ nodesToSearch.push(child);
+ }
}
+ node = nodesToSearch.pop();
}
- node = nodesToSearch.pop();
- }
- return false;
-};
+ return false;
+ },
-RBush.prototype.load = function load (data) {
- if (!(data && data.length)) { return this; }
+ load: function (data) {
+ if (!(data && data.length)) return this;
- if (data.length < this._minEntries) {
- for (var i = 0; i < data.length; i++) {
- this.insert(data[i]);
+ if (data.length < this._minEntries) {
+ for (var i = 0, len = data.length; i < len; i++) {
+ this.insert(data[i]);
+ }
+ return this;
}
- return this;
- }
- // recursively build the tree with the given data from scratch using OMT algorithm
- var node = this._build(data.slice(), 0, data.length - 1, 0);
+ // recursively build the tree with the given data from scratch using OMT algorithm
+ var node = this._build(data.slice(), 0, data.length - 1, 0);
+
+ if (!this.data.children.length) {
+ // save as is if tree is empty
+ this.data = node;
- if (!this.data.children.length) {
- // save as is if tree is empty
- this.data = node;
+ } else if (this.data.height === node.height) {
+ // split root if trees have the same height
+ this._splitRoot(this.data, node);
- } else if (this.data.height === node.height) {
- // split root if trees have the same height
- this._splitRoot(this.data, node);
+ } else {
+ if (this.data.height < node.height) {
+ // swap trees if inserted one is bigger
+ var tmpNode = this.data;
+ this.data = node;
+ node = tmpNode;
+ }
- } else {
- if (this.data.height < node.height) {
- // swap trees if inserted one is bigger
- var tmpNode = this.data;
- this.data = node;
- node = tmpNode;
+ // insert the small tree into the large tree at appropriate level
+ this._insert(node, this.data.height - node.height - 1, true);
}
- // insert the small tree into the large tree at appropriate level
- this._insert(node, this.data.height - node.height - 1, true);
- }
-
- return this;
-};
+ return this;
+ },
-RBush.prototype.insert = function insert (item) {
- if (item) { this._insert(item, this.data.height - 1); }
- return this;
-};
+ insert: function (item) {
+ if (item) this._insert(item, this.data.height - 1);
+ return this;
+ },
-RBush.prototype.clear = function clear () {
- this.data = createNode([]);
- return this;
-};
+ clear: function () {
+ this.data = createNode([]);
+ return this;
+ },
-RBush.prototype.remove = function remove (item, equalsFn) {
- if (!item) { return this; }
+ remove: function (item, equalsFn) {
+ if (!item) return this;
- var node = this.data;
- var bbox = this.toBBox(item);
- var path = [];
- var indexes = [];
- var i, parent, goingUp;
+ var node = this.data,
+ bbox = this.toBBox(item),
+ path = [],
+ indexes = [],
+ i, parent, index, goingUp;
- // depth-first iterative tree traversal
- while (node || path.length) {
+ // depth-first iterative tree traversal
+ while (node || path.length) {
- if (!node) { // go up
- node = path.pop();
- parent = path[path.length - 1];
- i = indexes.pop();
- goingUp = true;
- }
+ if (!node) { // go up
+ node = path.pop();
+ parent = path[path.length - 1];
+ i = indexes.pop();
+ goingUp = true;
+ }
- if (node.leaf) { // check current node
- var index = findItem(item, node.children, equalsFn);
+ if (node.leaf) { // check current node
+ index = findItem(item, node.children, equalsFn);
- if (index !== -1) {
- // item found, remove the item and condense tree upwards
- node.children.splice(index, 1);
- path.push(node);
- this._condense(path);
- return this;
+ if (index !== -1) {
+ // item found, remove the item and condense tree upwards
+ node.children.splice(index, 1);
+ path.push(node);
+ this._condense(path);
+ return this;
+ }
}
- }
- if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
- path.push(node);
- indexes.push(i);
- i = 0;
- parent = node;
- node = node.children[0];
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
+ path.push(node);
+ indexes.push(i);
+ i = 0;
+ parent = node;
+ node = node.children[0];
- } else if (parent) { // go right
- i++;
- node = parent.children[i];
- goingUp = false;
+ } else if (parent) { // go right
+ i++;
+ node = parent.children[i];
+ goingUp = false;
- } else { node = null; } // nothing found
- }
+ } else node = null; // nothing found
+ }
- return this;
-};
+ return this;
+ },
-RBush.prototype.toBBox = function toBBox (item) { return item; };
+ toBBox: function (item) { return item; },
-RBush.prototype.compareMinX = function compareMinX (a, b) { return a.minX - b.minX; };
-RBush.prototype.compareMinY = function compareMinY (a, b) { return a.minY - b.minY; };
+ compareMinX: compareNodeMinX,
+ compareMinY: compareNodeMinY,
-RBush.prototype.toJSON = function toJSON () { return this.data; };
+ toJSON: function () { return this.data; },
-RBush.prototype.fromJSON = function fromJSON (data) {
- this.data = data;
- return this;
-};
+ fromJSON: function (data) {
+ this.data = data;
+ return this;
+ },
-RBush.prototype._all = function _all (node, result) {
- var nodesToSearch = [];
- while (node) {
- if (node.leaf) { result.push.apply(result, node.children); }
- else { nodesToSearch.push.apply(nodesToSearch, node.children); }
+ _all: function (node, result) {
+ var nodesToSearch = [];
+ while (node) {
+ if (node.leaf) result.push.apply(result, node.children);
+ else nodesToSearch.push.apply(nodesToSearch, node.children);
- node = nodesToSearch.pop();
- }
- return result;
-};
+ node = nodesToSearch.pop();
+ }
+ return result;
+ },
-RBush.prototype._build = function _build (items, left, right, height) {
+ _build: function (items, left, right, height) {
- var N = right - left + 1;
- var M = this._maxEntries;
- var node;
+ var N = right - left + 1,
+ M = this._maxEntries,
+ node;
- if (N <= M) {
- // reached leaf level; return leaf
- node = createNode(items.slice(left, right + 1));
- calcBBox(node, this.toBBox);
- return node;
- }
+ if (N <= M) {
+ // reached leaf level; return leaf
+ node = createNode(items.slice(left, right + 1));
+ calcBBox(node, this.toBBox);
+ return node;
+ }
- if (!height) {
- // target height of the bulk-loaded tree
- height = Math.ceil(Math.log(N) / Math.log(M));
+ if (!height) {
+ // target height of the bulk-loaded tree
+ height = Math.ceil(Math.log(N) / Math.log(M));
- // target number of root entries to maximize storage utilization
- M = Math.ceil(N / Math.pow(M, height - 1));
- }
+ // target number of root entries to maximize storage utilization
+ M = Math.ceil(N / Math.pow(M, height - 1));
+ }
- node = createNode([]);
- node.leaf = false;
- node.height = height;
+ node = createNode([]);
+ node.leaf = false;
+ node.height = height;
- // split the items into M mostly square tiles
+ // split the items into M mostly square tiles
- var N2 = Math.ceil(N / M);
- var N1 = N2 * Math.ceil(Math.sqrt(M));
+ var N2 = Math.ceil(N / M),
+ N1 = N2 * Math.ceil(Math.sqrt(M)),
+ i, j, right2, right3;
- multiSelect(items, left, right, N1, this.compareMinX);
+ multiSelect(items, left, right, N1, this.compareMinX);
- for (var i = left; i <= right; i += N1) {
+ for (i = left; i <= right; i += N1) {
- var right2 = Math.min(i + N1 - 1, right);
+ right2 = Math.min(i + N1 - 1, right);
- multiSelect(items, i, right2, N2, this.compareMinY);
+ multiSelect(items, i, right2, N2, this.compareMinY);
- for (var j = i; j <= right2; j += N2) {
+ for (j = i; j <= right2; j += N2) {
- var right3 = Math.min(j + N2 - 1, right2);
+ right3 = Math.min(j + N2 - 1, right2);
- // pack each entry recursively
- node.children.push(this._build(items, j, right3, height - 1));
+ // pack each entry recursively
+ node.children.push(this._build(items, j, right3, height - 1));
+ }
}
- }
- calcBBox(node, this.toBBox);
+ calcBBox(node, this.toBBox);
- return node;
-};
+ return node;
+ },
-RBush.prototype._chooseSubtree = function _chooseSubtree (bbox, node, level, path) {
- while (true) {
- path.push(node);
+ _chooseSubtree: function (bbox, node, level, path) {
- if (node.leaf || path.length - 1 === level) { break; }
+ var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
- var minArea = Infinity;
- var minEnlargement = Infinity;
- var targetNode = (void 0);
+ while (true) {
+ path.push(node);
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var area = bboxArea(child);
- var enlargement = enlargedArea(bbox, child) - area;
+ if (node.leaf || path.length - 1 === level) break;
- // choose entry with the least area enlargement
- if (enlargement < minEnlargement) {
- minEnlargement = enlargement;
- minArea = area < minArea ? area : minArea;
- targetNode = child;
+ minArea = minEnlargement = Infinity;
- } else if (enlargement === minEnlargement) {
- // otherwise choose one with the smallest area
- if (area < minArea) {
- minArea = area;
+ for (i = 0, len = node.children.length; i < len; i++) {
+ child = node.children[i];
+ area = bboxArea(child);
+ enlargement = enlargedArea(bbox, child) - area;
+
+ // choose entry with the least area enlargement
+ if (enlargement < minEnlargement) {
+ minEnlargement = enlargement;
+ minArea = area < minArea ? area : minArea;
targetNode = child;
+
+ } else if (enlargement === minEnlargement) {
+ // otherwise choose one with the smallest area
+ if (area < minArea) {
+ minArea = area;
+ targetNode = child;
+ }
}
}
+
+ node = targetNode || node.children[0];
}
- node = targetNode || node.children[0];
- }
+ return node;
+ },
- return node;
-};
+ _insert: function (item, level, isNode) {
-RBush.prototype._insert = function _insert (item, level, isNode) {
- var bbox = isNode ? item : this.toBBox(item);
- var insertPath = [];
+ var toBBox = this.toBBox,
+ bbox = isNode ? item : toBBox(item),
+ insertPath = [];
- // find the best node for accommodating the item, saving all nodes along the path too
- var node = this._chooseSubtree(bbox, this.data, level, insertPath);
+ // find the best node for accommodating the item, saving all nodes along the path too
+ var node = this._chooseSubtree(bbox, this.data, level, insertPath);
- // put the item into the node
- node.children.push(item);
- extend(node, bbox);
+ // put the item into the node
+ node.children.push(item);
+ extend(node, bbox);
- // split on node overflow; propagate upwards if necessary
- while (level >= 0) {
- if (insertPath[level].children.length > this._maxEntries) {
- this._split(insertPath, level);
- level--;
- } else { break; }
- }
+ // split on node overflow; propagate upwards if necessary
+ while (level >= 0) {
+ if (insertPath[level].children.length > this._maxEntries) {
+ this._split(insertPath, level);
+ level--;
+ } else break;
+ }
- // adjust bboxes along the insertion path
- this._adjustParentBBoxes(bbox, insertPath, level);
-};
+ // adjust bboxes along the insertion path
+ this._adjustParentBBoxes(bbox, insertPath, level);
+ },
-// split overflowed node into two
-RBush.prototype._split = function _split (insertPath, level) {
- var node = insertPath[level];
- var M = node.children.length;
- var m = this._minEntries;
+ // split overflowed node into two
+ _split: function (insertPath, level) {
- this._chooseSplitAxis(node, m, M);
+ var node = insertPath[level],
+ M = node.children.length,
+ m = this._minEntries;
- var splitIndex = this._chooseSplitIndex(node, m, M);
+ this._chooseSplitAxis(node, m, M);
- var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
- newNode.height = node.height;
- newNode.leaf = node.leaf;
+ var splitIndex = this._chooseSplitIndex(node, m, M);
- calcBBox(node, this.toBBox);
- calcBBox(newNode, this.toBBox);
+ var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
+ newNode.height = node.height;
+ newNode.leaf = node.leaf;
- if (level) { insertPath[level - 1].children.push(newNode); }
- else { this._splitRoot(node, newNode); }
-};
+ calcBBox(node, this.toBBox);
+ calcBBox(newNode, this.toBBox);
-RBush.prototype._splitRoot = function _splitRoot (node, newNode) {
- // split root node
- this.data = createNode([node, newNode]);
- this.data.height = node.height + 1;
- this.data.leaf = false;
- calcBBox(this.data, this.toBBox);
-};
+ if (level) insertPath[level - 1].children.push(newNode);
+ else this._splitRoot(node, newNode);
+ },
+
+ _splitRoot: function (node, newNode) {
+ // split root node
+ this.data = createNode([node, newNode]);
+ this.data.height = node.height + 1;
+ this.data.leaf = false;
+ calcBBox(this.data, this.toBBox);
+ },
-RBush.prototype._chooseSplitIndex = function _chooseSplitIndex (node, m, M) {
- var index;
- var minOverlap = Infinity;
- var minArea = Infinity;
+ _chooseSplitIndex: function (node, m, M) {
- for (var i = m; i <= M - m; i++) {
- var bbox1 = distBBox(node, 0, i, this.toBBox);
- var bbox2 = distBBox(node, i, M, this.toBBox);
+ var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
- var overlap = intersectionArea(bbox1, bbox2);
- var area = bboxArea(bbox1) + bboxArea(bbox2);
+ minOverlap = minArea = Infinity;
- // choose distribution with minimum overlap
- if (overlap < minOverlap) {
- minOverlap = overlap;
- index = i;
+ for (i = m; i <= M - m; i++) {
+ bbox1 = distBBox(node, 0, i, this.toBBox);
+ bbox2 = distBBox(node, i, M, this.toBBox);
- minArea = area < minArea ? area : minArea;
+ overlap = intersectionArea(bbox1, bbox2);
+ area = bboxArea(bbox1) + bboxArea(bbox2);
- } else if (overlap === minOverlap) {
- // otherwise choose distribution with minimum area
- if (area < minArea) {
- minArea = area;
+ // choose distribution with minimum overlap
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
index = i;
+
+ minArea = area < minArea ? area : minArea;
+
+ } else if (overlap === minOverlap) {
+ // otherwise choose distribution with minimum area
+ if (area < minArea) {
+ minArea = area;
+ index = i;
+ }
}
}
- }
- return index || M - m;
-};
+ return index;
+ },
-// sorts node children by the best axis for split
-RBush.prototype._chooseSplitAxis = function _chooseSplitAxis (node, m, M) {
- var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
- var compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
- var xMargin = this._allDistMargin(node, m, M, compareMinX);
- var yMargin = this._allDistMargin(node, m, M, compareMinY);
+ // sorts node children by the best axis for split
+ _chooseSplitAxis: function (node, m, M) {
- // if total distributions margin value is minimal for x, sort by minX,
- // otherwise it's already sorted by minY
- if (xMargin < yMargin) { node.children.sort(compareMinX); }
-};
+ var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
+ compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
+ xMargin = this._allDistMargin(node, m, M, compareMinX),
+ yMargin = this._allDistMargin(node, m, M, compareMinY);
-// total margin of all possible split distributions where each node is at least m full
-RBush.prototype._allDistMargin = function _allDistMargin (node, m, M, compare) {
- node.children.sort(compare);
+ // if total distributions margin value is minimal for x, sort by minX,
+ // otherwise it's already sorted by minY
+ if (xMargin < yMargin) node.children.sort(compareMinX);
+ },
- var toBBox = this.toBBox;
- var leftBBox = distBBox(node, 0, m, toBBox);
- var rightBBox = distBBox(node, M - m, M, toBBox);
- var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
+ // total margin of all possible split distributions where each node is at least m full
+ _allDistMargin: function (node, m, M, compare) {
- for (var i = m; i < M - m; i++) {
- var child = node.children[i];
- extend(leftBBox, node.leaf ? toBBox(child) : child);
- margin += bboxMargin(leftBBox);
- }
+ node.children.sort(compare);
- for (var i$1 = M - m - 1; i$1 >= m; i$1--) {
- var child$1 = node.children[i$1];
- extend(rightBBox, node.leaf ? toBBox(child$1) : child$1);
- margin += bboxMargin(rightBBox);
- }
+ var toBBox = this.toBBox,
+ leftBBox = distBBox(node, 0, m, toBBox),
+ rightBBox = distBBox(node, M - m, M, toBBox),
+ margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
+ i, child;
- return margin;
-};
+ for (i = m; i < M - m; i++) {
+ child = node.children[i];
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(leftBBox);
+ }
-RBush.prototype._adjustParentBBoxes = function _adjustParentBBoxes (bbox, path, level) {
- // adjust bboxes along the given tree path
- for (var i = level; i >= 0; i--) {
- extend(path[i], bbox);
- }
-};
+ for (i = M - m - 1; i >= m; i--) {
+ child = node.children[i];
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(rightBBox);
+ }
+
+ return margin;
+ },
+
+ _adjustParentBBoxes: function (bbox, path, level) {
+ // adjust bboxes along the given tree path
+ for (var i = level; i >= 0; i--) {
+ extend(path[i], bbox);
+ }
+ },
+
+ _condense: function (path) {
+ // go through the path, removing empty nodes and updating bboxes
+ for (var i = path.length - 1, siblings; i >= 0; i--) {
+ if (path[i].children.length === 0) {
+ if (i > 0) {
+ siblings = path[i - 1].children;
+ siblings.splice(siblings.indexOf(path[i]), 1);
+
+ } else this.clear();
-RBush.prototype._condense = function _condense (path) {
- // go through the path, removing empty nodes and updating bboxes
- for (var i = path.length - 1, siblings = (void 0); i >= 0; i--) {
- if (path[i].children.length === 0) {
- if (i > 0) {
- siblings = path[i - 1].children;
- siblings.splice(siblings.indexOf(path[i]), 1);
+ } else calcBBox(path[i], this.toBBox);
+ }
+ },
+
+ _initFormat: function (format) {
+ // data format (minX, minY, maxX, maxY accessors)
+
+ // uses eval-type function compilation instead of just accepting a toBBox function
+ // because the algorithms are very sensitive to sorting functions performance,
+ // so they should be dead simple and without inner calls
- } else { this.clear(); }
+ var compareArr = ['return a', ' - b', ';'];
- } else { calcBBox(path[i], this.toBBox); }
+ this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
+ this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
+
+ this.toBBox = new Function('a',
+ 'return {minX: a' + format[0] +
+ ', minY: a' + format[1] +
+ ', maxX: a' + format[2] +
+ ', maxY: a' + format[3] + '};');
}
};
function findItem(item, items, equalsFn) {
- if (!equalsFn) { return items.indexOf(item); }
+ if (!equalsFn) return items.indexOf(item);
for (var i = 0; i < items.length; i++) {
- if (equalsFn(item, items[i])) { return i; }
+ if (equalsFn(item, items[i])) return i;
}
return -1;
}
@@ -481,14 +473,14 @@ function calcBBox(node, toBBox) {
// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
- if (!destNode) { destNode = createNode(null); }
+ if (!destNode) destNode = createNode(null);
destNode.minX = Infinity;
destNode.minY = Infinity;
destNode.maxX = -Infinity;
destNode.maxY = -Infinity;
- for (var i = k; i < p; i++) {
- var child = node.children[i];
+ for (var i = k, child; i < p; i++) {
+ child = node.children[i];
extend(destNode, node.leaf ? toBBox(child) : child);
}
@@ -515,10 +507,10 @@ function enlargedArea(a, b) {
}
function intersectionArea(a, b) {
- var minX = Math.max(a.minX, b.minX);
- var minY = Math.max(a.minY, b.minY);
- var maxX = Math.min(a.maxX, b.maxX);
- var maxY = Math.min(a.maxY, b.maxY);
+ var minX = Math.max(a.minX, b.minX),
+ minY = Math.max(a.minY, b.minY),
+ maxX = Math.min(a.maxX, b.maxX),
+ maxY = Math.min(a.maxY, b.maxY);
return Math.max(0, maxX - minX) *
Math.max(0, maxY - minY);
@@ -554,21 +546,81 @@ function createNode(children) {
// combines selection algorithm with binary divide & conquer approach
function multiSelect(arr, left, right, n, compare) {
- var stack = [left, right];
+ var stack = [left, right],
+ mid;
while (stack.length) {
right = stack.pop();
left = stack.pop();
- if (right - left <= n) { continue; }
+ if (right - left <= n) continue;
- var mid = left + Math.ceil((right - left) / n / 2) * n;
+ mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
-return RBush;
+},{"quickselect":2}],2:[function(require,module,exports){
+'use strict';
+
+module.exports = quickselect;
+module.exports.default = quickselect;
+
+function quickselect(arr, k, left, right, compare) {
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
+};
+
+function quickselectStep(arr, k, left, right, compare) {
+
+ while (right > left) {
+ if (right - left > 600) {
+ var n = right - left + 1;
+ var m = k - left + 1;
+ var z = Math.log(n);
+ var s = 0.5 * Math.exp(2 * z / 3);
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
+ quickselectStep(arr, k, newLeft, newRight, compare);
+ }
+
+ var t = arr[k];
+ var i = left;
+ var j = right;
+
+ swap(arr, left, k);
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+ while (i < j) {
+ swap(arr, i, j);
+ i++;
+ j--;
+ while (compare(arr[i], t) < 0) i++;
+ while (compare(arr[j], t) > 0) j--;
+ }
+
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
+ else {
+ j++;
+ swap(arr, j, right);
+ }
+
+ if (j <= k) left = j + 1;
+ if (k <= j) right = j - 1;
+ }
+}
+
+function swap(arr, i, j) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+function defaultCompare(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+}
-}));
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/js/tracker.js b/js/tracker.js
index 164c7f4..9eaa382 100644
--- a/js/tracker.js
+++ b/js/tracker.js
@@ -415,7 +415,7 @@ function load() {
if(currentPosition) updateCurrentPosition(currentPosition.lat, currentPosition.lon);
//Receiver canvas
- receiverCanvas = new L.MarkersCanvas();
+ receiverCanvas = new L.canvasIconLayer();
receiverCanvas.addTo(map);
// initalize nite overlay
@@ -442,19 +442,20 @@ function load() {
if (vehicles[key]["horizon_circle"]["_map"])
{
try {
- var horizonwidth = vehicles[key]["horizon_circle"].getElement().getBoundingClientRect()["width"];
- var subhorizonwidth = vehicles[key]["subhorizon_circle"].getElement().getBoundingClientRect()["width"];
- if (horizonwidth != 0 && horizonwidth < 28) {
+ var zoom = map.getZoom();
+ var horizonzoom = (Math.abs(Math.log(vehicles[key]["horizon_circle"].getRadius()/2000000)/0.75));
+ var subhorizonzoom = (Math.abs(Math.log(vehicles[key]["subhorizon_circle"].getRadius()/2000000)/0.75));
+ if (horizonzoom > zoom) {
map.removeLayer(vehicles[key]["horizon_circle_title"]);
} else {
map.addLayer(vehicles[key]["horizon_circle_title"]);
}
- if (subhorizonwidth != 0 && subhorizonwidth < 28) {
+ if (subhorizonzoom > zoom) {
map.removeLayer(vehicles[key]["subhorizon_circle_title"]);
} else {
map.addLayer(vehicles[key]["subhorizon_circle_title"]);
}
- } catch(e){};
+ } catch(e){console.log(e);};
}
}
}
@@ -1935,6 +1936,7 @@ function addPosition(position) {
fillColor: '#00F',
fillOpacity: 0,
opacity: 0.6,
+ interactive: false,
});
horizon_circle_title_icon = new L.DivIcon({
@@ -1943,7 +1945,8 @@ function addPosition(position) {
});
horizon_circle_title = new L.Marker(point, {
- icon: horizon_circle_title_icon
+ icon: horizon_circle_title_icon,
+ interactive: false,
});
if (!offline.get("opt_hide_horizon")) {
@@ -1965,15 +1968,17 @@ function addPosition(position) {
fillColor: '#0F0',
fillOpacity: 0,
opacity: 0.8,
+ interactive: false,
});
subhorizon_circle_title_icon = new L.DivIcon({
className: "subhorizon_circle_title",
- html: 'km'
+ html: 'km',
});
subhorizon_circle_title = new L.Marker(point, {
- icon: subhorizon_circle_title_icon
+ icon: subhorizon_circle_title_icon,
+ interactive: false,
});
if (!offline.get("opt_hide_horizon")) {