UNPKG

leaflet-knn

Version:

next nearest neighbors for leaflet

340 lines (265 loc) 8.89 kB
(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) }); ;