[Pkg-javascript-commits] [leaflet-geometryutil] 01/06: New upstream version 0.4.0

Dominik George natureshadow-guest at moszumanska.debian.org
Sat Jan 21 18:35:30 UTC 2017


This is an automated email from the git hooks/post-receive script.

natureshadow-guest pushed a commit to branch master
in repository leaflet-geometryutil.

commit b0e2584907f31038a01d3b80a064ad30c9efb3cc
Author: Dominik George <nik at naturalnet.de>
Date:   Sat Jan 21 13:39:39 2017 +0100

    New upstream version 0.4.0
---
 .gitignore                   |   2 +
 .gitmodules                  |   3 +
 .travis.yml                  |   4 +
 LICENSE                      |  27 +++
 Makefile                     |  19 ++
 README.md                    |  94 ++++++++
 dist/leaflet.geometryutil.js | 557 +++++++++++++++++++++++++++++++++++++++++++
 package.json                 |  19 ++
 test/index.html              |  26 ++
 test/test.geometryutil.js    | 542 +++++++++++++++++++++++++++++++++++++++++
 10 files changed, 1293 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..788346b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+.install.stamp
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4a0723a
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "docs"]
+	path = docs
+	url = https://github.com/makinacorpus/Leaflet.GeometryUtil.git
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..18ae2d8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+  - "0.11"
+  - "0.10"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..090a981
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..af6b802
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+INSTALL_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
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..41b1f70
--- /dev/null
+++ b/README.md
@@ -0,0 +1,94 @@
+Leaflet.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)
diff --git a/dist/leaflet.geometryutil.js b/dist/leaflet.geometryutil.js
new file mode 100644
index 0000000..d139188
--- /dev/null
+++ b/dist/leaflet.geometryutil.js
@@ -0,0 +1,557 @@
+// 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<L.Point>|Array<L.LatLng>}
+     * @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<L.Point>|Array<L.LatLng>}
+     * @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.LatLng>|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<L.ILayer>} 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<ILayer>} 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.LatLng>|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<L.LatLng>}
+     */
+    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;
+
+}));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8e6179e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+    "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"
+  }
+}
diff --git a/test/index.html b/test/index.html
new file mode 100644
index 0000000..9f19a01
--- /dev/null
+++ b/test/index.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Mocha Tests</title>
+  <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+</head>
+<body>
+  <div id="map" style="display: none; height: 300px"></div>
+  <div id="mocha"></div>
+  <script src="../node_modules/chai/chai.js"></script>
+  <script src="../node_modules/mocha/mocha.js"></script>
+  <script src="../node_modules/leaflet/build/deps.js"></script>
+  <script src="../node_modules/leaflet/debug/leaflet-include.js"></script>
+  <script src="../dist/leaflet.geometryutil.js"></script>
+
+  <script>
+    var map = L.map('map').fitWorld();
+  </script>
+
+  <script>mocha.setup('bdd')</script>
+  <script src="test.geometryutil.js"></script>
+  <script>
+    (window.mochaPhantomJS || window.mocha).run();
+  </script>
+</body>
+</html>
diff --git a/test/test.geometryutil.js b/test/test.geometryutil.js
new file mode 100644
index 0000000..43dbb14
--- /dev/null
+++ b/test/test.geometryutil.js
@@ -0,0 +1,542 @@
+var 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();
+  });
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/leaflet-geometryutil.git



More information about the Pkg-javascript-commits mailing list