cheap-ruler
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -19,7 +19,13 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.cheapRuler = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
// units per degree on equator | ||
this.d = (units === 'miles' ? 24901.55 : 40075.16) / 360; | ||
var f = lat * Math.PI / 180; | ||
// longitude correction based on latitude | ||
this.e = Math.cos(lat * Math.PI / 180); | ||
// multiplier for unit conversion | ||
var m = units === 'miles' ? 1.609344 : | ||
units === 'meters' ? 1000 : 1; | ||
// longitude correction | ||
this.kx = m * (111.41513 * Math.cos(f) - 0.09455 * Math.cos(3 * f) + 0.00012 * Math.cos(5 * f)); | ||
// latitude correction | ||
this.ky = m * (111.13209 - 0.56605 * Math.cos(2 * f) + 0.0012 * Math.cos(4 * f)); | ||
} | ||
@@ -29,10 +35,10 @@ | ||
distance: function (a, b) { | ||
var dx = (a[0] - b[0]) * this.e; | ||
var dy = a[1] - b[1]; | ||
return Math.sqrt(dx * dx + dy * dy) * this.d; | ||
var dx = (a[0] - b[0]) * this.kx; | ||
var dy = (a[1] - b[1]) * this.ky; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
}, | ||
bearing: function (a, b) { | ||
var dx = (b[0] - a[0]) * this.e; | ||
var dy = b[1] - a[1]; | ||
var dx = (b[0] - a[0]) * this.kx; | ||
var dy = (b[1] - a[1]) * this.ky; | ||
if (!dx && !dy) return 0; | ||
@@ -46,6 +52,5 @@ var bearing = Math.atan2(-dy, dx) * 180 / Math.PI + 90; | ||
var a = (90 - bearing) * Math.PI / 180; | ||
var d = dist / this.d; | ||
return [ | ||
p[0] + d * Math.cos(a) / this.e, | ||
p[1] + d * Math.sin(a) | ||
p[0] + Math.cos(a) * dist / this.kx, | ||
p[1] + Math.sin(a) * dist / this.ky | ||
]; | ||
@@ -73,3 +78,3 @@ }, | ||
return (Math.abs(sum) / 2) * this.e * this.d * this.d; | ||
return (Math.abs(sum) / 2) * this.kx * this.ky; | ||
}, | ||
@@ -101,8 +106,8 @@ | ||
var y = line[i][1]; | ||
var dx = (line[i + 1][0] - x) * this.e; | ||
var dy = line[i + 1][1] - y; | ||
var dx = (line[i + 1][0] - x) * this.kx; | ||
var dy = (line[i + 1][1] - y) * this.ky; | ||
if (dx !== 0 || dy !== 0) { | ||
var t = ((p[0] - x) * this.e * dx + (p[1] - y) * dy) / (dx * dx + dy * dy); | ||
var t = ((p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
@@ -114,9 +119,9 @@ if (t > 1) { | ||
} else if (t > 0) { | ||
x += dx * t / this.e; | ||
y += dy * t; | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
} | ||
} | ||
dx = (p[0] - x) * this.e; | ||
dy = p[1] - y; | ||
dx = (p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
@@ -195,4 +200,4 @@ var sqDist = dx * dx + dy * dy; | ||
bufferPoint: function (p, buffer) { | ||
var v = buffer / this.d; | ||
var h = v / this.e; | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
@@ -207,4 +212,4 @@ p[0] - h, | ||
bufferBBox: function (bbox, buffer) { | ||
var v = buffer / this.d; | ||
var h = v / this.e; | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
@@ -211,0 +216,0 @@ bbox[0] - h, |
21
debug.js
@@ -17,3 +17,3 @@ 'use strict'; | ||
var distances = [1, 100, 1000, 5000]; | ||
var distances = [1, 100, 500, 1000, 5000]; | ||
@@ -27,7 +27,8 @@ for (var i = 0; i < distances.length; i++) { | ||
var p1 = ruler.destination([0, lat], dist / 2, 90); | ||
var p2 = ruler.destination([0, lat], dist / 2, 90 - 180); | ||
var p1 = ruler.destination([0, lat], dist / 2, 45); | ||
var p2 = ruler.destination([0, lat], dist / 2, 45 - 180); | ||
var d = ruler.distance(p1, p2); | ||
var d2 = turf.distance(turf.point(p1), turf.point(p2)); | ||
// var d2 = vincenty.distVincenty(p1[1], p1[0], p2[1], p2[0]).distance / 1000; | ||
// var d = turf.distance(turf.point(p1), turf.point(p2)); | ||
// var d = fccDist(p1, p2, lat); | ||
var d2 = vincenty.distVincenty(p1[1], p1[0], p2[1], p2[0]).distance / 1000; | ||
var err = Math.abs((d - d2) / d2); | ||
@@ -42,1 +43,11 @@ var errStr = (Math.round(100 * 1e2 * err) / 1e2) + '%'; | ||
} | ||
function fccDist(a, b, lat) { | ||
var f = lat * Math.PI / 180; | ||
var k1 = 111.13209 - 0.56605 * Math.cos(2 * f) + 0.0012 * Math.cos(4 * f); | ||
var k2 = 111.41513 * Math.cos(f) - 0.09455 * Math.cos(3 * f) + 0.00012 * Math.cos(5 * f); | ||
var dy = k1 * (a[1] - b[1]); | ||
var dx = k2 * (a[0] - b[0]); | ||
return Math.sqrt(dx * dx + dy * dy); | ||
} |
53
index.js
@@ -18,7 +18,13 @@ 'use strict'; | ||
// units per degree on equator | ||
this.d = (units === 'miles' ? 24901.55 : 40075.16) / 360; | ||
var f = lat * Math.PI / 180; | ||
// longitude correction based on latitude | ||
this.e = Math.cos(lat * Math.PI / 180); | ||
// multiplier for unit conversion | ||
var m = units === 'miles' ? 1.609344 : | ||
units === 'meters' ? 1000 : 1; | ||
// longitude correction | ||
this.kx = m * (111.41513 * Math.cos(f) - 0.09455 * Math.cos(3 * f) + 0.00012 * Math.cos(5 * f)); | ||
// latitude correction | ||
this.ky = m * (111.13209 - 0.56605 * Math.cos(2 * f) + 0.0012 * Math.cos(4 * f)); | ||
} | ||
@@ -28,10 +34,10 @@ | ||
distance: function (a, b) { | ||
var dx = (a[0] - b[0]) * this.e; | ||
var dy = a[1] - b[1]; | ||
return Math.sqrt(dx * dx + dy * dy) * this.d; | ||
var dx = (a[0] - b[0]) * this.kx; | ||
var dy = (a[1] - b[1]) * this.ky; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
}, | ||
bearing: function (a, b) { | ||
var dx = (b[0] - a[0]) * this.e; | ||
var dy = b[1] - a[1]; | ||
var dx = (b[0] - a[0]) * this.kx; | ||
var dy = (b[1] - a[1]) * this.ky; | ||
if (!dx && !dy) return 0; | ||
@@ -45,6 +51,5 @@ var bearing = Math.atan2(-dy, dx) * 180 / Math.PI + 90; | ||
var a = (90 - bearing) * Math.PI / 180; | ||
var d = dist / this.d; | ||
return [ | ||
p[0] + d * Math.cos(a) / this.e, | ||
p[1] + d * Math.sin(a) | ||
p[0] + Math.cos(a) * dist / this.kx, | ||
p[1] + Math.sin(a) * dist / this.ky | ||
]; | ||
@@ -72,3 +77,3 @@ }, | ||
return (Math.abs(sum) / 2) * this.e * this.d * this.d; | ||
return (Math.abs(sum) / 2) * this.kx * this.ky; | ||
}, | ||
@@ -100,8 +105,8 @@ | ||
var y = line[i][1]; | ||
var dx = (line[i + 1][0] - x) * this.e; | ||
var dy = line[i + 1][1] - y; | ||
var dx = (line[i + 1][0] - x) * this.kx; | ||
var dy = (line[i + 1][1] - y) * this.ky; | ||
if (dx !== 0 || dy !== 0) { | ||
var t = ((p[0] - x) * this.e * dx + (p[1] - y) * dy) / (dx * dx + dy * dy); | ||
var t = ((p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
@@ -113,9 +118,9 @@ if (t > 1) { | ||
} else if (t > 0) { | ||
x += dx * t / this.e; | ||
y += dy * t; | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
} | ||
} | ||
dx = (p[0] - x) * this.e; | ||
dy = p[1] - y; | ||
dx = (p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
@@ -194,4 +199,4 @@ var sqDist = dx * dx + dy * dy; | ||
bufferPoint: function (p, buffer) { | ||
var v = buffer / this.d; | ||
var h = v / this.e; | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
@@ -206,4 +211,4 @@ p[0] - h, | ||
bufferBBox: function (bbox, buffer) { | ||
var v = buffer / this.d; | ||
var h = v / this.e; | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
@@ -210,0 +215,0 @@ bbox[0] - h, |
{ | ||
"name": "cheap-ruler", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "A collection of fast approximations to common geographic measurements.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,4 +7,7 @@ # cheap-ruler [![Build Status](https://travis-ci.org/mapbox/cheap-ruler.svg?branch=master)](https://travis-ci.org/mapbox/cheap-ruler) | ||
For distances under a hundred miles and not on the poles, | ||
the results are [typically within 0.1%](#precision) of corresponding Turf functions. | ||
The approximations are based on an [FCC-approved formula of ellipsoidal Earth projection](https://www.gpo.gov/fdsys/pkg/CFR-2005-title47-vol4/pdf/CFR-2005-title47-vol4-sec73-208.pdf). | ||
For distances under 500 kilometers and not on the poles, | ||
the results are very precise — within [0.1% margin of error](#precision) | ||
compared to [Vincenti formulas](https://en.wikipedia.org/wiki/Vincenty%27s_formulae), | ||
and usually much less for shorter distances. | ||
@@ -172,18 +175,12 @@ ## Performance | ||
A table that shows the margin of error for `ruler.distance` compared to `turf.distance`: | ||
A table that shows the margin of error for `ruler.distance` compared to `node-vincenty` | ||
(a state of the art distance formula): | ||
| lat | 0° | 10° | 20° | 30° | 40° | 50° | 60° | 70° | 80° | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1km | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | | ||
| 100km | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.08% | 0.09% | 0.11% | | ||
| 1000km | 0.11% | 0.11% | 0.12% | 0.14% | 0.18% | 0.25% | 0.42% | 0.89% | 3.48% | | ||
| 1km | 0% | 0% | 0% | 0% | 0% | 0% | 0% | 0% | 0% | | ||
| 100km | 0% | 0% | 0% | 0% | 0% | 0% | 0.01% | 0.01% | 0.04% | | ||
| 500km | 0% | 0% | 0% | 0.01% | 0.02% | 0.04% | 0.08% | 0.2% | 0.83% | | ||
| 1000km | 0% | 0% | 0.02% | 0.04% | 0.07% | 0.15% | 0.31% | 0.78% | 3.36% | | ||
The same table for a much more precise Vincenty distance formula (using `node-vincenty` module): | ||
| lat | 0° | 10° | 20° | 30° | 40° | 50° | 60° | 70° | 80° | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1km | 0.34% | 0.32% | 0.26% | 0.17% | 0.06% | 0.06% | 0.17% | 0.26% | 0.31% | | ||
| 100km | 0.34% | 0.32% | 0.26% | 0.17% | 0.06% | 0.06% | 0.16% | 0.25% | 0.28% | | ||
| 1000km | 0.36% | 0.34% | 0.3% | 0.23% | 0.16% | 0.11% | 0.17% | 0.55% | 3.08% | | ||
Errors for all other methods are similar. |
@@ -29,5 +29,5 @@ 'use strict'; | ||
var actual = ruler.distance(points[i], points[i + 1]); | ||
assertErr(t, expected, actual, 0.001, 'distance'); | ||
assertErr(t, expected, actual, 0.003, 'distance'); | ||
} | ||
t.pass('distance within 0.1%'); | ||
t.pass('distance within 0.3%'); | ||
t.end(); | ||
@@ -40,5 +40,5 @@ }); | ||
var actual = ruler.bearing(points[i], points[i + 1]); | ||
assertErr(t, expected, actual, 0.0001, 'bearing'); | ||
assertErr(t, expected, actual, 0.005, 'bearing'); | ||
} | ||
t.pass('bearing within 0.01%'); | ||
t.pass('bearing within 0.05%'); | ||
t.end(); | ||
@@ -52,6 +52,6 @@ }); | ||
var actual = ruler.destination(points[i], 1.0, bearing); | ||
assertErr(t, expected[0], actual[0], 3e-7, 'destination longitude'); | ||
assertErr(t, expected[1], actual[1], 3e-7, 'destination latitude'); | ||
assertErr(t, expected[0], actual[0], 1e-6, 'destination longitude'); | ||
assertErr(t, expected[1], actual[1], 1e-6, 'destination latitude'); | ||
} | ||
t.pass('destination within 3e-7'); | ||
t.pass('destination within 1e-6'); | ||
t.end(); | ||
@@ -64,5 +64,5 @@ }); | ||
var actual = ruler.lineDistance(lines[i]); | ||
assertErr(t, expected, actual, 0.001, 'lineDistance'); | ||
assertErr(t, expected, actual, 0.003, 'lineDistance'); | ||
} | ||
t.pass('lineDistance within 0.1%'); | ||
t.pass('lineDistance within 0.3%'); | ||
t.end(); | ||
@@ -77,5 +77,5 @@ }); | ||
var actual = ruler.area([lines[i]]); | ||
assertErr(t, expected, actual, 0.0002, 'area'); | ||
assertErr(t, expected, actual, 0.003, 'area'); | ||
} | ||
t.pass('area within 0.02%'); | ||
t.pass('area within 0.3%'); | ||
t.end(); | ||
@@ -90,6 +90,6 @@ }); | ||
var actual = ruler.along(lines[i], dist); | ||
assertErr(t, expected[0], actual[0], 2e-7, 'along longitude'); | ||
assertErr(t, expected[1], actual[1], 2e-7, 'along latitude'); | ||
assertErr(t, expected[0], actual[0], 1e-6, 'along longitude'); | ||
assertErr(t, expected[1], actual[1], 1e-6, 'along latitude'); | ||
} | ||
t.pass('lineDistance within 0.1%'); | ||
t.pass('along point within 1e-6'); | ||
t.end(); | ||
@@ -112,3 +112,3 @@ }); | ||
var p = ruler.pointOnLine(line, [-77.034076, 38.882017]).point; | ||
t.same(p, [-77.03051972665213, 38.88046894284234], 'pointOnLine'); | ||
t.same(p, [-77.03052697027461, 38.880457194811896], 'pointOnLine'); | ||
t.end(); | ||
@@ -128,7 +128,8 @@ }); | ||
turf.point(start), turf.point(stop), turf.linestring(line)).geometry.coordinates); | ||
var actual = ruler.lineDistance(ruler.lineSlice(start, stop, line)); | ||
assertErr(t, expected, actual, 0.001, 'lineSlice length'); | ||
assertErr(t, expected, actual, 0, 'lineSlice length'); | ||
} | ||
t.pass('lineSlice length within 0.1%'); | ||
t.pass('lineSlice length the same'); | ||
t.end(); | ||
@@ -150,5 +151,5 @@ }); | ||
assertErr(t, expected, actual, 0.001, 'lineSliceAlong length'); | ||
assertErr(t, expected, actual, 1e-10, 'lineSliceAlong length'); | ||
} | ||
t.pass('lineSliceAlong length within 0.1%'); | ||
t.pass('lineSliceAlong length within 1e-10'); | ||
t.end(); | ||
@@ -163,3 +164,3 @@ }); | ||
var actual = ruler.lineDistance(ruler.lineSlice(start, stop, line)); | ||
t.equal(actual, 0.018665535420681036, 'lineSlice reversed length'); | ||
t.equal(actual, 0.018676802802910702, 'lineSlice reversed length'); | ||
t.end(); | ||
@@ -170,10 +171,10 @@ }); | ||
for (var i = 0; i < points.length; i++) { | ||
var expected = turfPointBuffer(points[i], 0.01); | ||
var actual = milesRuler.bufferPoint(points[i], 0.01); | ||
assertErr(t, expected[0], actual[0], 1e-8, 'bufferPoint west'); | ||
assertErr(t, expected[1], actual[1], 1e-8, 'bufferPoint east'); | ||
assertErr(t, expected[2], actual[2], 1e-8, 'bufferPoint south'); | ||
assertErr(t, expected[3], actual[3], 1e-8, 'bufferPoint north'); | ||
var expected = turfPointBuffer(points[i], 0.1); | ||
var actual = milesRuler.bufferPoint(points[i], 0.1); | ||
assertErr(t, expected[0], actual[0], 3e-5, 'bufferPoint west'); | ||
assertErr(t, expected[1], actual[1], 3e-5, 'bufferPoint east'); | ||
assertErr(t, expected[2], actual[2], 3e-5, 'bufferPoint south'); | ||
assertErr(t, expected[3], actual[3], 3e-5, 'bufferPoint north'); | ||
} | ||
t.pass('point buffer error within 1e-8'); | ||
t.pass('point buffer error within 3e-5'); | ||
t.end(); | ||
@@ -185,3 +186,3 @@ }); | ||
var bbox2 = ruler.bufferBBox(bbox, 1); | ||
t.same(bbox2, [29.989308794440007, 37.991016879283826, 40.01069120555999, 39.008983120716174], 'bufferBBox'); | ||
t.same(bbox2, [29.989319515875376, 37.99098271225711, 40.01068048412462, 39.00901728774289], 'bufferBBox'); | ||
t.end(); | ||
@@ -188,0 +189,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45647
819
185