leaflet-knn
Version:
next nearest neighbors for leaflet
340 lines (265 loc) • 8.89 kB
JavaScript
(function(e){if("function"==typeof bootstrap)bootstrap("leafletknn",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeLeafletKnn=e}else"undefined"!=typeof window?window.leafletKnn=e():global.leafletKnn=e()})(function(){var define,ses,bootstrap,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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var sphereKnn = require('sphere-knn');
module.exports = function(layer) {
'use strict';
if (!(layer instanceof L.GeoJSON)) throw new Error('must be L.GeoJSON');
var points = [];
layer.eachLayer(collectPoints);
function collectPoints(l) {
points = points.concat(reformat(flat(l.feature.geometry.coordinates), l));
}
var sknn = sphereKnn(points);
sknn.nearest = function(p, n, max_distance) {
if (p instanceof L.LatLng) p = [p.lng, p.lat];
return sknn(p[1], p[0], n, max_distance);
};
sknn.nearestLayer = function(p, n, max_distance) {
if (p instanceof L.LatLng) p = [p.lng, p.lat];
return collapse(sknn(p[1], p[0], n, max_distance));
};
return sknn;
};
function collapse(results) {
var l = [], included = {};
for (var i = 0; i < results.length; i++) {
if (included[L.stamp(results[i].layer)] == undefined) {
l.push(results[i]);
included[L.stamp(results[i].layer)] = true;
}
}
return l;
}
function reformat(coords, layer) {
var l = [];
for (var i = 0; i < coords.length; i++) {
l.push({ lon: coords[i][0], lat: coords[i][1], layer: layer });
}
return l;
}
function flat(coords) {
var i = 0, j = 0, k = 0, l = [];
if (typeof coords[0] === 'object' &&
typeof coords[0][0] === 'object' &&
typeof coords[0][0][0] === 'object') {
for (;i < coords.length; i++) {
for (;j < coords[i].length; j++) {
for (;k < coords[i][j].length; k++) l.push(coords[i][j][k]);
}
}
return l;
} else if (typeof coords[0] === 'object' &&
typeof coords[0][0] === 'object') {
for (;i < coords.length; i++) {
for (;j < coords[i].length; j++) l.push(coords[i][j]);
}
return l;
} else if (typeof coords[0] === 'object') {
return coords;
} else {
return [coords];
}
}
},{"sphere-knn":2}],2:[function(require,module,exports){
var spherekd = require("./lib/spherekd")
module.exports = function(points) {
/* Inflate the toad! */
var root = spherekd.build(points)
/* Lurch off into the sunset! */
return function(lat, lon, n, max) {
return spherekd.lookup(lat, lon, root, n, max)
}
}
},{"./lib/spherekd":5}],3:[function(require,module,exports){
function defaultComparator(a, b) {
return a - b
}
exports.search = function(item, array, comparator) {
if(!comparator)
comparator = defaultComparator
var low = 0,
high = array.length - 1,
mid, comp
while(low <= high) {
mid = (low + high) >>> 1
comp = comparator(array[mid], item)
if(comp < 0)
low = mid + 1
else if(comp > 0)
high = mid - 1
else
return mid
}
return -(low + 1)
}
exports.insert = function(item, array, comparator) {
var i = exports.search(item, array, comparator)
if(i < 0)
i = -(i + 1)
array.splice(i, 0, item)
}
},{}],4:[function(require,module,exports){
var binary = require("./binary")
function Node(axis, split, left, right) {
this.axis = axis
this.split = split
this.left = left
this.right = right
}
function distance(a, b) {
var i = Math.min(a.length, b.length),
d = 0,
k
while(i--) {
k = b[i] - a[i]
d += k * k
}
return d
}
function byDistance(a, b) {
return a.dist - b.dist
}
function buildrec(array, depth) {
/* This should only happen if you request a kd-tree with zero elements. */
if(array.length === 0)
return null
/* If there's only one item, then it's a leaf node! */
if(array.length === 1)
return array[0]
/* Uh oh. Well, we have to partition the data set and recurse. Start by
* finding the bounding box of the given points; whichever side is the
* longest is the one we'll use for the splitting plane. */
var axis = depth % array[0].position.length
/* Sort the points along the splitting plane. */
/* FIXME: For very large trees, it would be faster to use some sort of median
* finding and partitioning algorithm. It'd also be a lot more complicated. */
array.sort(function(a, b) {
return a.position[axis] - b.position[axis]
})
/* Find the median point. It's position is going to be the location of the
* splitting plane. */
var i = Math.floor(array.length * 0.5)
/* Split, recurse, yadda yadda. */
++depth
return new Node(
axis,
array[i].position[axis],
buildrec(array.slice(0, i), depth),
buildrec(array.slice(i ), depth)
)
}
function build(array) {
return buildrec(array, 0)
}
function lookup(position, node, n, max) {
if(!(max > 0))
max = Number.POSITIVE_INFINITY
var array = []
/* Degenerate cases. */
if(node === null || n <= 0)
return array
var stack = [node, 0],
dist, i
while(stack.length) {
dist = stack.pop()
node = stack.pop()
/* If this subtree is further away than we care about, then skip it. */
if(dist > max)
continue
/* If we've already found enough locations, and the furthest one is closer
* than this subtree possibly could be, just skip the subtree. */
if(array.length === n && array[array.length - 1].dist < dist * dist)
continue
/* Iterate all the way down the tree, adding nodes that we need to remember
* to visit later onto the stack. */
while(node instanceof Node) {
if(position[node.axis] < node.split) {
stack.push(node.right, node.split - position[node.axis])
node = node.left
}
else {
stack.push(node.left, position[node.axis] - node.split)
node = node.right
}
}
/* Once we've hit a leaf node, insert it into the array of candidates,
* making sure to keep the array in sorted order. */
dist = distance(position, node.position)
if(dist <= max * max)
binary.insert({object: node, dist: dist}, array, byDistance)
/* If the array's too long, cull it. */
if(array.length > n)
array.pop()
}
/* Strip candidate wrapper objects. */
i = array.length
while(i--)
array[i] = array[i].object
return array
}
exports.build = build
exports.lookup = lookup
},{"./binary":3}],5:[function(require,module,exports){
var kd = require("./kd"),
rad = Math.PI / 180,
invEarthDiameter = 1 / 12742018 /* meters */
function spherical2cartesian(lat, lon) {
lat *= rad
lon *= rad
var cos = Math.cos(lat)
return [cos * Math.cos(lon), Math.sin(lat), cos * Math.sin(lon)]
}
function Position(object) {
var lat, lon;
/* Find latitude. */
if(object.hasOwnProperty("lat"))
lat = object.lat;
else if(object.hasOwnProperty("latitude"))
lat = object.latitude;
else if(object.hasOwnProperty("location") &&
Array.isArray(object.location) &&
object.location.length === 2)
lat = object.location[0];
/* Find longitude. */
if(object.hasOwnProperty("lon"))
lon = object.lon;
else if(object.hasOwnProperty("longitude"))
lon = object.longitude;
else if(object.hasOwnProperty("lng"))
lon = object.lng;
else if(object.hasOwnProperty("long"))
lon = object.long;
else if(object.hasOwnProperty("location") &&
Array.isArray(object.location) &&
object.location.length === 2)
lon = object.location[1];
/* Finally, set this object's properties. */
this.object = object;
this.position = spherical2cartesian(lat, lon);
}
function build(array) {
var i = array.length,
out = new Array(i)
while(i--)
out[i] = new Position(array[i])
return kd.build(out)
}
function lookup(lat, lon, node, n, max) {
var array = kd.lookup(
spherical2cartesian(lat, lon),
node,
n,
max > 0 ? 2 * Math.sin(max * invEarthDiameter) : undefined
),
i = array.length
/* Strip off position wrapper objects. */
while(i--)
array[i] = array[i].object
return array
}
exports.build = build
exports.lookup = lookup
},{"./kd":4}]},{},[1])
(1)
});
;