pax_global_header00006660000000000000000000000064125243527270014523gustar00rootroot0000000000000052 comment=2ea3ae8c2d8e588d8672690936883c87df44ca40 Leaflet.GeometryUtil-0.4.0/000077500000000000000000000000001252435272700155305ustar00rootroot00000000000000Leaflet.GeometryUtil-0.4.0/.gitignore000066400000000000000000000000361252435272700175170ustar00rootroot00000000000000/node_modules/ .install.stamp Leaflet.GeometryUtil-0.4.0/.gitmodules000066400000000000000000000001401252435272700177000ustar00rootroot00000000000000[submodule "docs"] path = docs url = https://github.com/makinacorpus/Leaflet.GeometryUtil.git Leaflet.GeometryUtil-0.4.0/.travis.yml000066400000000000000000000000611252435272700176360ustar00rootroot00000000000000language: node_js node_js: - "0.11" - "0.10" Leaflet.GeometryUtil-0.4.0/LICENSE000066400000000000000000000027401252435272700165400ustar00rootroot00000000000000Copyright (c) 2013, Makina Corpus All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of ODE nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Leaflet.GeometryUtil-0.4.0/Makefile000066400000000000000000000005321252435272700171700ustar00rootroot00000000000000INSTALL_STAMP=.install.stamp all: install install: $(INSTALL_STAMP) $(INSTALL_STAMP): npm install touch $(INSTALL_STAMP) test: install @./node_modules/mocha-phantomjs/bin/mocha-phantomjs test/index.html docs: install @./node_modules/jsdoc/jsdoc -d ./docs/ dist/ README.md clean: rm -rf node_modules/ $(INSTALL_STAMP) .PHONY: test docs Leaflet.GeometryUtil-0.4.0/README.md000066400000000000000000000035441252435272700170150ustar00rootroot00000000000000Leaflet.GeometryUtil ==================== [![Build Status](https://travis-ci.org/makinacorpus/Leaflet.GeometryUtil.png?branch=master)](https://travis-ci.org/makinacorpus/Leaflet.GeometryUtil) * Tested with stable Leaflet 0.7.0 Usage ----- Check out [online documentation](http://makinacorpus.github.io/Leaflet.GeometryUtil/). Development ----------- ### Running tests in command-line * Install [nodejs](http://nodejs.org) and [phantomjs](http://phantomjs.org) ``` sudo apt-get install nodejs phantomjs ``` * Ready ! ``` make test ``` Changelog --------- ### master ### * Nothing changed yet. ### 0.4.0 ### * Same version as v0.3.3, new release as v0.4.0 to keep numbering coherent as a new feature has been added ### 0.3.3 ### * Add bearing and destination functions (thanks @doublestranded) ### 0.3.2 ### * Use a soft dependency for Leaflet (thanks Erik Escoffier) ### 0.3.1 ### * Make sure interpolateOnLine() always returns a L.LatLng object (thanks Justin Manley) ### 0.3.0 ### * Added UMD style initialization (thanks @PerLiedman) * Added readable distance (thanks @Mylen) * Fix side effects on latlngs with `closest()` (thanks @AndrewIngram) ### 0.2.0 ### * Locate point on line * Rotate point around center * Fixed bug if closest point was on last segment ### 0.1.0 ### * Line subpart extraction * Line lengths * Angle and slope computation * Line reverse * Line interpolation ### 0.0.1 ### * Initial working version License ------- * BSD New Authors ------- * [Benjamin Becquet](https://github.com/bbecquet) * [Mathieu Leplatre](https://github.com/leplatrem) * [Simon Thépot](https://github.com/djcoin) * [Nhinze](https://github.com/nhinze) * [Frédéric Bonifas](https://github.com/fredericbonifas) * [Alexander Melard](https://github.com/mylen) [![Makina Corpus](http://depot.makina-corpus.org/public/logo.gif)](http://makinacorpus.com) Leaflet.GeometryUtil-0.4.0/dist/000077500000000000000000000000001252435272700164735ustar00rootroot00000000000000Leaflet.GeometryUtil-0.4.0/dist/leaflet.geometryutil.js000066400000000000000000000476121252435272700232070ustar00rootroot00000000000000// Packaging/modules magic dance. (function (factory) { var L; if (typeof define === 'function' && define.amd) { // AMD define(['leaflet'], factory); } else if (typeof module !== 'undefined') { // Node/CommonJS L = require('leaflet'); module.exports = factory(L); } else { // Browser globals if (typeof window.L === 'undefined') throw 'Leaflet must be loaded first'; factory(window.L); } }(function (L) { "use strict"; /** * @fileOverview Leaflet Geometry utilities for distances and linear referencing. * @name L.GeometryUtil */ L.GeometryUtil = L.extend(L.GeometryUtil || {}, { /** Shortcut function for planar distance between two {L.LatLng} at current zoom. @param {L.Map} map @param {L.LatLng} latlngA @param {L.LatLng} latlngB @returns {Number} in pixels */ distance: function (map, latlngA, latlngB) { return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB)); }, /** Shortcut function for planar distance between a {L.LatLng} and a segment (A-B). @param {L.Map} map @param {L.LatLng} latlng @param {L.LatLng} latlngA @param {L.LatLng} latlngB @returns {Number} in pixels */ distanceSegment: function (map, latlng, latlngA, latlngB) { var p = map.latLngToLayerPoint(latlng), p1 = map.latLngToLayerPoint(latlngA), p2 = map.latLngToLayerPoint(latlngB); return L.LineUtil.pointToSegmentDistance(p, p1, p2); }, /** Shortcut function for converting distance to readable distance. @param {Number} distance @param {String} unit ('metric' or 'imperial') @returns {Number} in yard or miles */ readableDistance: function (distance, unit) { var isMetric = (unit !== 'imperial'), distanceStr; if (isMetric) { // show metres when distance is < 1km, then show km if (distance > 1000) { distanceStr = (distance / 1000).toFixed(2) + ' km'; } else { distanceStr = Math.ceil(distance) + ' m'; } } else { distance *= 1.09361; if (distance > 1760) { distanceStr = (distance / 1760).toFixed(2) + ' miles'; } else { distanceStr = Math.ceil(distance) + ' yd'; } } return distanceStr; }, /** Returns true if the latlng belongs to segment. param {L.LatLng} latlng @param {L.LatLng} latlngA @param {L.LatLng} latlngB @param {?Number} [tolerance=0.2] @returns {boolean} */ belongsSegment: function(latlng, latlngA, latlngB, tolerance) { tolerance = tolerance === undefined ? 0.2 : tolerance; var hypotenuse = latlngA.distanceTo(latlngB), delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse; return delta/hypotenuse < tolerance; }, /** * Returns total length of line * @param {L.Polyline|Array|Array} * @returns {Number} in meters */ length: function (coords) { var accumulated = L.GeometryUtil.accumulatedLengths(coords); return accumulated.length > 0 ? accumulated[accumulated.length-1] : 0; }, /** * Returns a list of accumulated length along a line. * @param {L.Polyline|Array|Array} * @returns {Number} in meters */ accumulatedLengths: function (coords) { if (typeof coords.getLatLngs == 'function') { coords = coords.getLatLngs(); } if (coords.length === 0) return []; var total = 0, lengths = [0]; for (var i = 0, n = coords.length - 1; i< n; i++) { total += coords[i].distanceTo(coords[i+1]); lengths.push(total); } return lengths; }, /** Returns the closest point of a {L.LatLng} on the segment (A-B) @param {L.Map} map @param {L.LatLng} latlng @param {L.LatLng} latlngA @param {L.LatLng} latlngB @returns {L.LatLng} */ closestOnSegment: function (map, latlng, latlngA, latlngB) { var maxzoom = map.getMaxZoom(); if (maxzoom === Infinity) maxzoom = map.getZoom(); var p = map.project(latlng, maxzoom), p1 = map.project(latlngA, maxzoom), p2 = map.project(latlngB, maxzoom), closest = L.LineUtil.closestPointOnSegment(p, p1, p2); return map.unproject(closest, maxzoom); }, /** Returns the closest latlng on layer. @param {L.Map} map @param {Array|L.PolyLine} layer - Layer that contains the result. @param {L.LatLng} latlng @param {?boolean} [vertices=false] - Whether to restrict to path vertices. @returns {L.LatLng} */ closest: function (map, layer, latlng, vertices) { if (typeof layer.getLatLngs != 'function') layer = L.polyline(layer); var latlngs = layer.getLatLngs().slice(0), mindist = Infinity, result = null, i, n, distance; // Lookup vertices if (vertices) { for(i = 0, n = latlngs.length; i < n; i++) { var ll = latlngs[i]; distance = L.GeometryUtil.distance(map, latlng, ll); if (distance < mindist) { mindist = distance; result = ll; result.distance = distance; } } return result; } if (layer instanceof L.Polygon) { latlngs.push(latlngs[0]); } // Keep the closest point of all segments for (i = 0, n = latlngs.length; i < n-1; i++) { var latlngA = latlngs[i], latlngB = latlngs[i+1]; distance = L.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB); if (distance <= mindist) { mindist = distance; result = L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB); result.distance = distance; } } return result; }, /** Returns the closest layer to latlng among a list of layers. @param {L.Map} map @param {Array} layers @param {L.LatLng} latlng @returns {object} with layer, latlng and distance or {null} if list is empty; */ closestLayer: function (map, layers, latlng) { var mindist = Infinity, result = null, ll = null, distance = Infinity; for (var i = 0, n = layers.length; i < n; i++) { var layer = layers[i]; // Single dimension, snap on points, else snap on closest if (typeof layer.getLatLng == 'function') { ll = layer.getLatLng(); distance = L.GeometryUtil.distance(map, latlng, ll); } else { ll = L.GeometryUtil.closest(map, layer, latlng); if (ll) distance = ll.distance; // Can return null if layer has no points. } if (distance < mindist) { mindist = distance; result = {layer: layer, latlng: ll, distance: distance}; } } return result; }, /** Returns the closest position from specified {LatLng} among specified layers, with a maximum tolerance in pixels, providing snapping behaviour. @param {L.Map} map @param {Array} layers - A list of layers to snap on. @param {L.LatLng} latlng - The position to snap. @param {?Number} [tolerance=Infinity] - Maximum number of pixels. @param {?boolean} [withVertices=true] - Snap to layers vertices. @returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded. */ closestLayerSnap: function (map, layers, latlng, tolerance, withVertices) { tolerance = typeof tolerance == 'number' ? tolerance : Infinity; withVertices = typeof withVertices == 'boolean' ? withVertices : true; var result = L.GeometryUtil.closestLayer(map, layers, latlng); if (!result || result.distance > tolerance) return null; // If snapped layer is linear, try to snap on vertices (extremities and middle points) if (withVertices && typeof result.layer.getLatLngs == 'function') { var closest = L.GeometryUtil.closest(map, result.layer, result.latlng, true); if (closest.distance < tolerance) { result.latlng = closest; result.distance = L.GeometryUtil.distance(map, closest, latlng); } } return result; }, /** Returns the Point located on a segment at the specified ratio of the segment length. @param {L.Point} pA @param {L.Point} pB @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive. @returns {L.Point} the interpolated point. */ interpolateOnPointSegment: function (pA, pB, ratio) { return L.point( (pA.x * (1 - ratio)) + (ratio * pB.x), (pA.y * (1 - ratio)) + (ratio * pB.y) ); }, /** Returns the coordinate of the point located on a line at the specified ratio of the line length. @param {L.Map} map @param {Array|L.PolyLine} latlngs @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive @returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline (-1 if the interpolated point is the first vertex) */ interpolateOnLine: function (map, latLngs, ratio) { latLngs = (latLngs instanceof L.Polyline) ? latLngs.getLatLngs() : latLngs; var n = latLngs.length; if (n < 2) { return null; } if (ratio === 0) { return { latLng: latLngs[0] instanceof L.LatLng ? latLngs[0] : L.latLng(latLngs[0]), predecessor: -1 }; } if (ratio == 1) { return { latLng: latLngs[latLngs.length -1] instanceof L.LatLng ? latLngs[latLngs.length -1] : L.latLng(latLngs[latLngs.length -1]), predecessor: latLngs.length - 2 }; } // ensure the ratio is between 0 and 1; ratio = Math.max(Math.min(ratio, 1), 0); // project the LatLngs as Points, // and compute total planar length of the line at max precision var maxzoom = map.getMaxZoom(); if (maxzoom === Infinity) maxzoom = map.getZoom(); var pts = []; var lineLength = 0; for(var i = 0; i < n; i++) { pts[i] = map.project(latLngs[i], maxzoom); if(i > 0) lineLength += pts[i-1].distanceTo(pts[i]); } var ratioDist = lineLength * ratio; var a = pts[0], b = pts[1], distA = 0, distB = a.distanceTo(b); // follow the line segments [ab], adding lengths, // until we find the segment where the points should lie on var index = 1; for (; index < n && distB < ratioDist; index++) { a = b; distA = distB; b = pts[index]; distB += a.distanceTo(b); } // compute the ratio relative to the segment [ab] var segmentRatio = ((distB - distA) !== 0) ? ((ratioDist - distA) / (distB - distA)) : 0; var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(a, b, segmentRatio); return { latLng: map.unproject(interpolatedPoint, maxzoom), predecessor: index-2 }; }, /** Returns a float between 0 and 1 representing the location of the closest point on polyline to the given latlng, as a fraction of total 2d line length. (opposite of L.GeometryUtil.interpolateOnLine()) @param {L.Map} map @param {L.PolyLine} polyline @param {L.LatLng} latlng @returns {Number} */ locateOnLine: function (map, polyline, latlng) { var latlngs = polyline.getLatLngs(); if (latlng.equals(latlngs[0])) return 0.0; if (latlng.equals(latlngs[latlngs.length-1])) return 1.0; var point = L.GeometryUtil.closest(map, polyline, latlng, false), lengths = L.GeometryUtil.accumulatedLengths(latlngs), total_length = lengths[lengths.length-1], portion = 0, found = false; for (var i=0, n = latlngs.length-1; i < n; i++) { var l1 = latlngs[i], l2 = latlngs[i+1]; portion = lengths[i]; if (L.GeometryUtil.belongsSegment(point, l1, l2)) { portion += l1.distanceTo(point); found = true; break; } } if (!found) { throw "Could not interpolate " + latlng.toString() + " within " + polyline.toString(); } return portion / total_length; }, /** Returns a clone with reversed coordinates. @param {L.PolyLine} polyline @returns {L.PolyLine} */ reverse: function (polyline) { return L.polyline(polyline.getLatLngs().slice(0).reverse()); }, /** Returns a sub-part of the polyline, from start to end. If start is superior to end, returns extraction from inverted line. @param {L.Map} map @param {L.PolyLine} latlngs @param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive @param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive @returns {Array} */ extract: function (map, polyline, start, end) { if (start > end) { return L.GeometryUtil.extract(map, L.GeometryUtil.reverse(polyline), 1.0-start, 1.0-end); } // Bound start and end to [0-1] start = Math.max(Math.min(start, 1), 0); end = Math.max(Math.min(end, 1), 0); var latlngs = polyline.getLatLngs(), startpoint = L.GeometryUtil.interpolateOnLine(map, polyline, start), endpoint = L.GeometryUtil.interpolateOnLine(map, polyline, end); // Return single point if start == end if (start == end) { var point = L.GeometryUtil.interpolateOnLine(map, polyline, end); return [point.latLng]; } // Array.slice() works indexes at 0 if (startpoint.predecessor == -1) startpoint.predecessor = 0; if (endpoint.predecessor == -1) endpoint.predecessor = 0; var result = latlngs.slice(startpoint.predecessor+1, endpoint.predecessor+1); result.unshift(startpoint.latLng); result.push(endpoint.latLng); return result; }, /** Returns true if first polyline ends where other second starts. @param {L.PolyLine} polyline @param {L.PolyLine} other @returns {bool} */ isBefore: function (polyline, other) { if (!other) return false; var lla = polyline.getLatLngs(), llb = other.getLatLngs(); return (lla[lla.length-1]).equals(llb[0]); }, /** Returns true if first polyline starts where second ends. @param {L.PolyLine} polyline @param {L.PolyLine} other @returns {bool} */ isAfter: function (polyline, other) { if (!other) return false; var lla = polyline.getLatLngs(), llb = other.getLatLngs(); return (lla[0]).equals(llb[llb.length-1]); }, /** Returns true if first polyline starts where second ends or start. @param {L.PolyLine} polyline @param {L.PolyLine} other @returns {bool} */ startsAtExtremity: function (polyline, other) { if (!other) return false; var lla = polyline.getLatLngs(), llb = other.getLatLngs(), start = lla[0]; return start.equals(llb[0]) || start.equals(llb[llb.length-1]); }, /** Returns horizontal angle in degres between two points. @param {L.Point} a @param {L.Point} b @returns {float} */ computeAngle: function(a, b) { return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI); }, /** Returns slope (Ax+B) between two points. @param {L.Point} a @param {L.Point} b @returns {Object} with ``a`` and ``b`` properties. */ computeSlope: function(a, b) { var s = (b.y - a.y) / (b.x - a.x), o = a.y - (s * a.x); return {'a': s, 'b': o}; }, /** Returns LatLng of rotated point around specified LatLng center. @param {L.LatLng} latlngPoint: point to rotate @param {double} angleDeg: angle to rotate in degrees @param {L.LatLng} latlngCenter: center of rotation @returns {L.LatLng} rotated point */ rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) { var maxzoom = map.getMaxZoom(); if (maxzoom === Infinity) maxzoom = map.getZoom(); var angleRad = angleDeg*Math.PI/180, pPoint = map.project(latlngPoint, maxzoom), pCenter = map.project(latlngCenter, maxzoom), x2 = Math.cos(angleRad)*(pPoint.x-pCenter.x) - Math.sin(angleRad)*(pPoint.y-pCenter.y) + pCenter.x, y2 = Math.sin(angleRad)*(pPoint.x-pCenter.x) + Math.cos(angleRad)*(pPoint.y-pCenter.y) + pCenter.y; return map.unproject(new L.Point(x2,y2), maxzoom); }, /** Returns the bearing in degrees clockwise from north (0 degrees) from the first L.LatLng to the second, at the first LatLng @param {L.LatLng} latlng1: origin point of the bearing @param {L.LatLng} latlng2: destination point of the bearing @returns {float} degrees clockwise from north. */ bearing: function(latlng1, latlng2) { var rad = Math.PI / 180, lat1 = latlng1.lat * rad, lat2 = latlng2.lat * rad, lon1 = latlng1.lng * rad, lon2 = latlng2.lng * rad, y = Math.sin(lon2 - lon1) * Math.cos(lat2), x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360; return bearing >= 180 ? bearing-360 : bearing; }, /** Returns the point that is a distance and heading away from the given origin point. @param {L.LatLng} latlng: origin point @param {float}: heading in degrees, clockwise from 0 degrees north. @param {float}: distance in meters @returns {L.latLng} the destination point. Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html for a great reference and examples. */ destination: function(latlng, heading, distance) { heading = (heading + 360) % 360; var rad = Math.PI / 180, radInv = 180 / Math.PI, R = 6378137, // approximation of Earth's radius lon1 = latlng.lng * rad, lat1 = latlng.lat * rad, rheading = heading * rad, sinLat1 = Math.sin(lat1), cosLat1 = Math.cos(lat1), cosDistR = Math.cos(distance / R), sinDistR = Math.sin(distance / R), lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 * sinDistR * Math.cos(rheading)), lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR * cosLat1, cosDistR - sinLat1 * Math.sin(lat2)); lon2 = lon2 * radInv; lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2; return L.latLng([lat2 * radInv, lon2]); } }); return L.GeometryUtil; })); Leaflet.GeometryUtil-0.4.0/docs/000077500000000000000000000000001252435272700164605ustar00rootroot00000000000000Leaflet.GeometryUtil-0.4.0/package.json000066400000000000000000000007161252435272700200220ustar00rootroot00000000000000{ "name": "leaflet-geometryutil" , "version": "0.4.0" , "description": "Leaflet utility functions on geometries" , "keywords": ["Leaflet", "GIS"] , "main": "dist/leaflet.geometryutil.js" , "scripts": { "test": "make test" } , "dependencies": { "leaflet": "^0.7.0" } , "devDependencies": { "mocha": "1.9.0", "chai": "1.6.0", "mocha-phantomjs": "2.0.1", "jsdoc": "https://github.com/jsdoc3/jsdoc/tarball/v3.1.1" } } Leaflet.GeometryUtil-0.4.0/test/000077500000000000000000000000001252435272700165075ustar00rootroot00000000000000Leaflet.GeometryUtil-0.4.0/test/index.html000066400000000000000000000014171252435272700205070ustar00rootroot00000000000000 Mocha Tests
Leaflet.GeometryUtil-0.4.0/test/test.geometryutil.js000066400000000000000000000444671252435272700225730ustar00rootroot00000000000000var assert = chai.assert; assert.almostequal = function (a, b, n) { n = n || 12; return assert.equal(Math.round(a * Math.pow(10, n)) / Math.pow(10, n), Math.round(b * Math.pow(10, n)) / Math.pow(10, n)); }; // use Leaflet equality functions for Point and LatLng assert.pointEqual = function (a, b) { return a.equals(b); }; assert.latLngEqual = function (a, b, n) { n = n || 2; return assert.almostequal(a.lat, b.lat, 2) && assert.almostequal(a.lng, b.lng, n); }; describe('Distance to segment', function() { it('It should be 0 if point on segment', function(done) { assert.equal(0, L.GeometryUtil.distanceSegment(map, L.latLng([10, 5]), L.latLng([10, 0]), L.latLng([10, 10]))); done(); }); it('It should not fail if segment has no length', function(done) { assert.equal(1, L.GeometryUtil.distanceSegment(map, L.latLng([0, 1]), L.latLng([0, 0]), L.latLng([0, 0]))); done(); }); it('It should be the shortest distance', function(done) { assert.equal(1, L.GeometryUtil.distanceSegment(map, L.latLng([0, 1]), L.latLng([0, 0]), L.latLng([10, 0]))); done(); }); }); describe('Length of line', function() { it('It should be 0 for empty line', function(done) { assert.equal(0, L.GeometryUtil.length([])); done(); }); it('It should return length in meters', function(done) { assert.equal(111319.49079327357, L.GeometryUtil.length(L.polyline([[0, 0], [1, 0]]))); done(); }); }); describe('Readable distances', function() { it('It should be meters by default', function(done) { assert.equal("0 m", L.GeometryUtil.readableDistance(0)); done(); }); it('It should be 0 yd if imperial', function(done) { assert.equal("0 yd", L.GeometryUtil.readableDistance(0, 'imperial')); done(); }); it('It should be kilometers if superior to 1000', function(done) { assert.equal("1.01 km", L.GeometryUtil.readableDistance(1010)); done(); }); it('It should be miles if superior to 1760', function(done) { assert.equal("1.24 miles", L.GeometryUtil.readableDistance(2000, 'imperial')); done(); }); }); describe('Accumulated length of line', function() { it('It should be empty for empty line', function(done) { assert.deepEqual([], L.GeometryUtil.accumulatedLengths([])); done(); }); it('It should return 0 and length in meters for a segment', function(done) { assert.deepEqual([0, 111319.49079327357], L.GeometryUtil.accumulatedLengths(L.polyline([[0, 0], [1, 0]]))); done(); }); it('It should return accumulated lengths', function(done) { assert.deepEqual([0, 55659.74539663678, 111319.49079327357], L.GeometryUtil.accumulatedLengths(L.polyline([[0, 0], [0.5, 0], [1, 0]]))); done(); }); }); describe('Closest on segment', function() { it('It should be same point if point on segment', function(done) { var ll = L.latLng([0, 0]), closest = L.GeometryUtil.closestOnSegment(map, ll, L.latLng([0, 0]), L.latLng([10, 10])); assert.equal(ll.toString(), closest.toString()); done(); }); it('It should be exactly on path', function(done) { var ll = L.latLng([-1, 1]), closest = L.GeometryUtil.closestOnSegment(map, ll, L.latLng([-10, -10]), L.latLng([10, 10])); // TODO: should not be almost equal assert.almostequal(0, closest.lat, 2); assert.almostequal(0, closest.lng, 2); done(); }); }); describe('Closest on path with precision', function() { it('It should have distance at 0 if on path', function(done) { var ll = L.latLng([0, 0]), closest = L.GeometryUtil.closest(map, [[-30, -50], [-10, -10], [10, 10], [30, 50]], ll); assert.equal(0, closest.distance); assert.equal(ll.toString(), closest.toString()); done(); }); it('It should return same point if on path', function(done) { var line = L.polyline([[0,0], [1, 1], [2, 2]]); closest = L.GeometryUtil.closest(map, line, [1.7, 1.7]); assert.almostequal(closest.lat, 1.7, 2); assert.almostequal(closest.lng, 1.7, 2); done(); }); it('It should be exactly on path', function(done) { var ll = L.latLng([1, -1]), closest = L.GeometryUtil.closest(map, [[-10, -10], [10, 10]], ll); assert.equal(Math.sqrt(2), closest.distance); // TODO: should not be almost equal assert.almostequal(closest.lat, 0, 2); assert.almostequal(closest.lng, 0, 2); done(); }); it('It should not depend on zoom', function(done) { // Test with plain value var ll = L.latLng([5, 10]), line = L.polyline([[-50, -10], [30, 40]]).addTo(map), closest = L.GeometryUtil.closest(map, line, ll); assert.isTrue(closest.distance > 0); /* SELECT ST_AsText( ST_ClosestPoint( ST_MakeLine('SRID=4326;POINT(-10 -50)'::geometry, 'SRID=4326;POINT(40 30)'::geometry), 'SRID=4326;POINT(10 5)'::geometry)) Gives: "POINT(20.3370786516854 -1.46067415730337)" TODO: find out what's going on with Longitudes :) */ assert.equal('LatLng(-1.46743, 21.57294)', closest.toString()); // Change zoom and check that closest did not change. assert.equal(0, map.getZoom()); L.Util.setOptions(map, {maxZoom: 18}); map.on('moveend', function () { assert.notEqual(0, map.getZoom()); closest = L.GeometryUtil.closest(map, line, ll); assert.equal('LatLng(-1.46743, 21.57294)', closest.toString()); // Restore zoom map.off('moveend'); map._resetView(map.getCenter(), 0); done(); }); map._resetView(map.getCenter(), 17); }); it('It should work with last segment of polygon', function(done) { var polygon = L.polygon([[0, 0], [10, 10], [0, 10]]), ll = [-1, 5], closest = L.GeometryUtil.closest(map, polygon, ll); assert.almostequal(closest.lat, 0, 2); assert.almostequal(closest.lng, 5, 2); done(); }); }); describe('Closest among layers', function() { it('It should return null if list is empty', function(done) { var ll = L.latLng([0, 0]), closest = L.GeometryUtil.closestLayer(map, [], ll); assert.equal(null, closest); done(); }); it('It should return an object with layer, latlng and distance', function(done) { var ll = L.latLng([0, 0]), layers = [L.marker([2, 2])], closest = L.GeometryUtil.closestLayer(map, layers, ll); assert.deepEqual(closest, {layer: layers[0], latlng: layers[0].getLatLng(), distance: Math.sqrt(2)}); done(); }); }); describe('Closest snap', function() { var square, diagonal, d, w, layers; beforeEach(function() { // Snapping distance d = L.GeometryUtil.distance(map, L.latLng([0, 0]), L.latLng([0, 10])); w = 3 * d; square = L.rectangle([[-w, -w], [w, w]]); diagonal = L.polyline([[-w, -w], [0, 0], [w, w]]); layers = [square, diagonal]; }); it('It should snap even if over layer', function(done) { var snap = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([0, 0])); assert.equal(snap.distance, 0); assert.equal(snap.layer, diagonal); done(); }); it('It should not snap if tolerance exceeded', function(done) { var snap = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([-w-d, w+d]), d); assert.equal(null, snap); done(); }); it('It should snap to corners by default', function(done) { var snap = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([-w-d, w+d])); assert.isTrue(snap.distance > d); assert.equal(snap.layer, square); done(); }); it('It should not snap to corners if vertices disabled', function(done) { var corner = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([w-d, -w-d])); assert.equal(corner.layer, square); assert.almostequal(corner.latlng.lat, w); assert.almostequal(corner.latlng.lng, -w); var snap = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([w-d, -w-d]), Infinity, false); assert.almostequal(snap.latlng.lat, w-d); assert.almostequal(snap.latlng.lng, -w); done(); }); it('It should not snap to corners if distance to vertice exceeds tolerance', function(done) { var corner = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([w-d-d/2, -w-d])); assert.equal(corner.layer, square); assert.almostequal(corner.latlng.lat, w); assert.almostequal(corner.latlng.lng, -w); var snap = L.GeometryUtil.closestLayerSnap(map, layers, L.latLng([w-d-d/2, -w-d]), d); assert.almostequal(snap.latlng.lat, w-d-d/2); assert.almostequal(snap.latlng.lng, -w); done(); }); }); describe('Interpolate on point segment', function() { var p1 = L.point(0, 2), p2 = L.point(0, 6); it('It should be the first point if offset is 0', function(done) { assert.pointEqual(p1, L.GeometryUtil.interpolateOnPointSegment(p1, p2, 0)); done(); }); it('It should be the last point if offset is 1', function(done) { assert.pointEqual(p2, L.GeometryUtil.interpolateOnPointSegment(p1, p2, 1)); done(); }); it('It should return the correct interpolations', function(done) { assert.pointEqual(L.point(0, 4), L.GeometryUtil.interpolateOnPointSegment(p1, p2, 0.5)); assert.pointEqual(L.point(0, 5), L.GeometryUtil.interpolateOnPointSegment(p1, p2, 0.75)); done(); }); }); describe('Interpolate on line', function() { var llA = L.latLng(1, 2), llB = L.latLng(3, 4), llC = L.latLng(5, 6); it('It should be null if the line has less than 2 vertices', function(done) { assert.equal(null, L.GeometryUtil.interpolateOnLine(map, [], 0.5)); assert.equal(null, L.GeometryUtil.interpolateOnLine(map, [llA], 0.5)); done(); }); it('It should be the first vertex if offset is 0', function(done) { var interp = L.GeometryUtil.interpolateOnLine(map, [llA, llB], 0); assert.latLngEqual(interp.latLng, llA); assert.equal(interp.predecessor, -1); done(); }); it('It should be the last vertex if offset is 1', function(done) { var interp = L.GeometryUtil.interpolateOnLine(map, [llA, llB, llC], 1); assert.latLngEqual(interp.latLng, llC); assert.equal(interp.predecessor, 1); done(); }); it('It should not fail if line has no length', function(done) { var interp = L.GeometryUtil.interpolateOnLine(map, [llA, llA, llA], 0.5); assert.latLngEqual(interp.latLng, llA); done(); }); it('It should return the correct interpolations', function(done) { var interp1 = L.GeometryUtil.interpolateOnLine(map, [llA, llB, llC], 0.5); assert.latLngEqual(interp1.latLng, llB); var interp2 = L.GeometryUtil.interpolateOnLine(map, [llA, llB, llC], 0.75); assert.latLngEqual(interp2.latLng, L.latLng([4, 5])); done(); }); it('It should work the same with instances of L.PolyLine and arrays of L.LatLng', function(done) { var lls = [llA, llB, llC]; var withArray = L.GeometryUtil.interpolateOnLine(map, lls, 0.75); var withPolyLine = L.GeometryUtil.interpolateOnLine(map, L.polyline(lls), 0.75); assert.deepEqual(withArray, withPolyLine); done(); }); it('Should always return a LatLng object.', function() { var interp1 = L.GeometryUtil.interpolateOnLine(map, [llA, llB, llC], 0); var interp2 = L.GeometryUtil.interpolateOnLine(map, [llA, llB, llC], 1); assert.isDefined(interp1.latLng.lat); assert.isDefined(interp1.latLng.lng); assert.isDefined(interp2.latLng.lat); assert.isDefined(interp2.latLng.lng); }); }); describe('Locate on line', function() { var line = L.polyline([[0,0], [1, 1], [2, 2]]); it('It should return 0 if start', function(done) { assert.equal(0, L.GeometryUtil.locateOnLine(map, line, L.latLng([0, 0]))); done(); }); it('It should return 1 if end', function(done) { assert.equal(1, L.GeometryUtil.locateOnLine(map, line, L.latLng([2, 2]))); done(); }); it('It should return ratio of point', function(done) { assert.almostequal(0.5, L.GeometryUtil.locateOnLine(map, line, L.latLng([1, 1])), 4); assert.almostequal(0.25, L.GeometryUtil.locateOnLine(map, line, L.latLng([0.5, 0.5])), 4); assert.almostequal(0.85, L.GeometryUtil.locateOnLine(map, line, L.latLng([1.7, 1.7])), 4); done(); }); }); describe('Reverse line', function() { var line = L.polyline([[0,0], [1, 1]]); it('It should invert coordinates', function(done) { assert.latLngEqual(line.getLatLngs()[0], L.GeometryUtil.reverse(line).getLatLngs()[1]); done(); }); it('It should not affect original', function(done) { var start = line.getLatLngs()[0]; L.GeometryUtil.reverse(line); assert.latLngEqual(start, line.getLatLngs()[0]); done(); }); }); describe('Extract line', function() { var line = L.polyline([[0,0], [1, 1], [2, 2], [3, 3]]); it('It should return all coordinates from 0 to 1', function(done) { assert.deepEqual(L.GeometryUtil.extract(map, line, 0, 1), line.getLatLngs()); done(); }); it('It should return inverted coordinates from 1 to 0', function(done) { assert.deepEqual(L.GeometryUtil.extract(map, line, 1, 0), L.GeometryUtil.reverse(line).getLatLngs()); done(); }); it('It should return one coordinate if start equals end', function(done) { assert.latLngEqual(L.latLng(0.7501691078194406, 0.7501524538236026), L.GeometryUtil.extract(map, line, 0.25, 0.25)[0]); done(); }); it('It should return extra coordinate if middle of segment', function(done) { assert.deepEqual(L.GeometryUtil.extract(map, line, 0, 0.2), [L.latLng([0, 0]), L.latLng([0.600141459027052, 0.6001219630588661])]); assert.deepEqual(L.GeometryUtil.extract(map, line, 0, 0.6), [L.latLng([0, 0]), L.latLng([1, 1]), L.latLng([1.800282914111311, 1.8002439493392906])]); assert.deepEqual(L.GeometryUtil.extract(map, line, 0.6, 1.0), [L.latLng([1.800282914111311, 1.8002439493392906]), L.latLng([2, 2]), L.latLng([3, 3])]); assert.deepEqual(L.GeometryUtil.extract(map, line, 0.2, 0.8), [L.latLng([0.600141459027052, 0.6001219630588661]), L.latLng([1, 1]), L.latLng([2, 2]), L.latLng([2.40024267258436, 2.4001524293923637])]); // Should work symetrically assert.deepEqual(L.GeometryUtil.extract(map, line, 1.0, 0.6), [L.latLng([3, 3]), L.latLng([2, 2]), L.latLng([1.800282914111311, 1.8002439493392906])]); done(); }); }); describe('Line order', function() { var lineA = L.polyline([[0, 0], [1, 1]]), lineB = L.polyline([[1, 1], [2, 2]]); it('It should detect if line is before', function(done) { assert.isTrue(L.GeometryUtil.isBefore(lineA, lineB)); assert.isFalse(L.GeometryUtil.isBefore(lineB, lineA)); done(); }); it('It should detect if line is after', function(done) { assert.isTrue(L.GeometryUtil.isAfter(lineB, lineA)); assert.isFalse(L.GeometryUtil.isAfter(lineA, lineB)); done(); }); it('It should detect if line starts at extremity', function(done) { var lineC = L.polyline([[0, 0], [1, 1]]); assert.isTrue(L.GeometryUtil.startsAtExtremity(lineA, lineC)); assert.isTrue(L.GeometryUtil.startsAtExtremity(lineB, lineC)); assert.isFalse(L.GeometryUtil.startsAtExtremity(lineC, lineB)); done(); }); }); describe('Compute angle', function() { it('It should return angle', function(done) { var p1 = L.point(0, 0), p2 = L.point(6, 6); assert.equal(L.GeometryUtil.computeAngle(p1, p2), 45); done(); }); }); describe('Compute slope', function() { it('It should return A and B', function(done) { var p1 = L.point(0, 2), p2 = L.point(5, 7); assert.deepEqual(L.GeometryUtil.computeSlope(p1, p2), {a: 1, b: 2}) done(); }); }); describe('Point rotation', function() { it('It should return the same point if angle is 0', function(done) { var llPoint = L.latLng([3, 3]), llCenter = L.latLng([2, 2]), rotated = L.GeometryUtil.rotatePoint(map, llPoint, 0, llCenter); assert.latLngEqual(llPoint, rotated); done(); }); it('It should return the same point if center and point are the same', function(done) { var llPoint = L.latLng([1, 1]), llCenter = L.latLng([1, 1]), rotated = L.GeometryUtil.rotatePoint(map, llPoint, 90, llCenter); assert.latLngEqual(llPoint, rotated); done(); }); it('It should return a rotated point', function(done) { var llPoint = L.latLng([1, 1]), llCenter = L.latLng([2, 2]), rotated = L.GeometryUtil.rotatePoint(map, llPoint, 90, llCenter); assert.latLngEqual(rotated, L.latLng([3, 1])); done(); }); }); describe('Compute Bearing', function() { it('It should be degrees clockwise from north, 0 degrees.', function(done) { var latlng1 = L.latLng([0.0, 0.0]), latlng2 = L.latLng([90.0, 0.0]); assert.equal(0.0, L.GeometryUtil.bearing(latlng1,latlng2)); done(); }); it('Same point, should be zero.', function(done) { var latlng1 = L.latLng([0.0, 0.0]), latlng2 = L.latLng([0.0, 0.0]); assert.equal(0, L.GeometryUtil.bearing(latlng1,latlng2)); done(); }); it('Crossing Prime Meridian.', function(done) { var latlng1 = L.latLng([10.0, -10.0]), latlng2 = L.latLng([-10.0, 10.0]); assert.equal(134.5614514132577, L.GeometryUtil.bearing(latlng1,latlng2)); done(); }); it('Negative value for bearing greater than / equal to 180', function(done) { var latlng1 = L.latLng([33.0, -120.0]), latlng2 = L.latLng([34.0, -122.0]); assert.equal(-58.503883697887375, L.GeometryUtil.bearing(latlng1,latlng2)); done(); }); }); describe('Destination', function() { it('It should be [90.0,0.0]', function(done) { var latlng1 = L.latLng([0.0, 0.0]), heading = 0.0; dist = 6378137 * Math.PI / 2.0; // 1/4 Earth's circumference. result = L.latLng([90.0,0.0]); assert.latLngEqual(result, L.GeometryUtil.destination(latlng1, heading, dist)); done(); }); it('Crossing the International Date Line', function(done) { var latlng1 = L.latLng([0.0, -175.0]), heading = -90.0; dist = 6378137 * Math.PI / 8.0; result = L.latLng([0.0, 162.5]); assert.latLngEqual(result, L.GeometryUtil.destination(latlng1, heading, dist)); done(); }); it('Crossing the Prime Meridian', function(done) { var latlng1 = L.latLng([10.0, -10.0]), heading = 134.5614514132577; dist = 3140555.3283872544; result = L.latLng([-10, 10.0]); assert.latLngEqual(result, L.GeometryUtil.destination(latlng1, heading, dist)); done(); }); });