cheap-ruler
Advanced tools
Comparing version 3.0.2 to 4.0.0
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.CheapRuler = factory()); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | ||
typeof define === 'function' && define.amd ? define(factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.CheapRuler = factory()); | ||
})(this, (function () { 'use strict'; | ||
var factors = { | ||
kilometers: 1, | ||
miles: 1000 / 1609.344, | ||
nauticalmiles: 1000 / 1852, | ||
meters: 1000, | ||
metres: 1000, | ||
yards: 1000 / 0.9144, | ||
feet: 1000 / 0.3048, | ||
inches: 1000 / 0.0254 | ||
}; | ||
const factors = { | ||
kilometers: 1, | ||
miles: 1000 / 1609.344, | ||
nauticalmiles: 1000 / 1852, | ||
meters: 1000, | ||
metres: 1000, | ||
yards: 1000 / 0.9144, | ||
feet: 1000 / 0.3048, | ||
inches: 1000 / 0.0254 | ||
}; | ||
// Values that define WGS84 ellipsoid model of the Earth | ||
var RE = 6378.137; // equatorial radius | ||
var FE = 1 / 298.257223563; // flattening | ||
// Values that define WGS84 ellipsoid model of the Earth | ||
const RE = 6378.137; // equatorial radius | ||
const FE = 1 / 298.257223563; // flattening | ||
var E2 = FE * (2 - FE); | ||
var RAD = Math.PI / 180; | ||
const E2 = FE * (2 - FE); | ||
const RAD = Math.PI / 180; | ||
/** | ||
* A collection of very fast approximations to common geodesic measurements. Useful for performance-sensitive code that measures things on a city scale. | ||
* | ||
* @param {number} lat latitude | ||
* @param {string} [units='kilometers'] | ||
* @returns {CheapRuler} | ||
* @example | ||
* const ruler = cheapRuler(35.05, 'miles'); | ||
* //=ruler | ||
*/ | ||
var CheapRuler = function CheapRuler(lat, units) { | ||
if (lat === undefined) { throw new Error('No latitude given.'); } | ||
if (units && !factors[units]) { throw new Error(("Unknown unit " + units + ". Use one of: " + (Object.keys(factors).join(', ')))); } | ||
/** | ||
* A collection of very fast approximations to common geodesic measurements. Useful for performance-sensitive code that measures things on a city scale. | ||
*/ | ||
class CheapRuler { | ||
/** | ||
* Creates a ruler object from tile coordinates (y and z). | ||
* | ||
* @param {number} y | ||
* @param {number} z | ||
* @param {keyof typeof factors} [units='kilometers'] | ||
* @returns {CheapRuler} | ||
* @example | ||
* const ruler = cheapRuler.fromTile(1567, 12); | ||
* //=ruler | ||
*/ | ||
static fromTile(y, z, units) { | ||
const n = Math.PI * (1 - 2 * (y + 0.5) / Math.pow(2, z)); | ||
const lat = Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))) / RAD; | ||
return new CheapRuler(lat, units); | ||
} | ||
// Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional | ||
var m = RAD * RE * (units ? factors[units] : 1); | ||
var coslat = Math.cos(lat * RAD); | ||
var w2 = 1 / (1 - E2 * (1 - coslat * coslat)); | ||
var w = Math.sqrt(w2); | ||
/** | ||
* Multipliers for converting between units. | ||
* | ||
* @example | ||
* // convert 50 meters to yards | ||
* 50 * CheapRuler.units.yards / CheapRuler.units.meters; | ||
*/ | ||
static get units() { | ||
return factors; | ||
} | ||
// multipliers for converting longitude and latitude degrees into distance | ||
this.kx = m * w * coslat; // based on normal radius of curvature | ||
this.ky = m * w * w2 * (1 - E2); // based on meridonal radius of curvature | ||
}; | ||
/** | ||
* Creates a ruler instance for very fast approximations to common geodesic measurements around a certain latitude. | ||
* | ||
* @param {number} lat latitude | ||
* @param {keyof typeof factors} [units='kilometers'] | ||
* @example | ||
* const ruler = cheapRuler(35.05, 'miles'); | ||
* //=ruler | ||
*/ | ||
constructor(lat, units) { | ||
if (lat === undefined) throw new Error('No latitude given.'); | ||
if (units && !factors[units]) throw new Error(`Unknown unit ${ units }. Use one of: ${ Object.keys(factors).join(', ')}`); | ||
var staticAccessors = { units: { configurable: true } }; | ||
// Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional | ||
const m = RAD * RE * (units ? factors[units] : 1); | ||
const coslat = Math.cos(lat * RAD); | ||
const w2 = 1 / (1 - E2 * (1 - coslat * coslat)); | ||
const w = Math.sqrt(w2); | ||
/** | ||
* Given two points of the form [longitude, latitude], returns the distance. | ||
* | ||
* @param {Array<number>} a point [longitude, latitude] | ||
* @param {Array<number>} b point [longitude, latitude] | ||
* @returns {number} distance | ||
* @example | ||
* const distance = ruler.distance([30.5, 50.5], [30.51, 50.49]); | ||
* //=distance | ||
*/ | ||
CheapRuler.fromTile = function fromTile (y, z, units) { | ||
var n = Math.PI * (1 - 2 * (y + 0.5) / Math.pow(2, z)); | ||
var lat = Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))) / RAD; | ||
return new CheapRuler(lat, units); | ||
}; | ||
// multipliers for converting longitude and latitude degrees into distance | ||
this.kx = m * w * coslat; // based on normal radius of curvature | ||
this.ky = m * w * w2 * (1 - E2); // based on meridonal radius of curvature | ||
} | ||
/** | ||
* Multipliers for converting between units. | ||
* | ||
* @example | ||
* // convert 50 meters to yards | ||
* 50 * CheapRuler.units.yards / CheapRuler.units.meters; | ||
*/ | ||
staticAccessors.units.get = function () { | ||
return factors; | ||
}; | ||
/** | ||
* Given two points of the form [longitude, latitude], returns the distance. | ||
* | ||
* @param {[number, number]} a point [longitude, latitude] | ||
* @param {[number, number]} b point [longitude, latitude] | ||
* @returns {number} distance | ||
* @example | ||
* const distance = ruler.distance([30.5, 50.5], [30.51, 50.49]); | ||
* //=distance | ||
*/ | ||
distance(a, b) { | ||
const dx = wrap(a[0] - b[0]) * this.kx; | ||
const dy = (a[1] - b[1]) * this.ky; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
} | ||
CheapRuler.prototype.distance = function distance (a, b) { | ||
var dx = wrap(a[0] - b[0]) * this.kx; | ||
var dy = (a[1] - b[1]) * this.ky; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
}; | ||
/** | ||
* Returns the bearing between two points in angles. | ||
* | ||
* @param {[number, number]} a point [longitude, latitude] | ||
* @param {[number, number]} b point [longitude, latitude] | ||
* @returns {number} bearing | ||
* @example | ||
* const bearing = ruler.bearing([30.5, 50.5], [30.51, 50.49]); | ||
* //=bearing | ||
*/ | ||
bearing(a, b) { | ||
const dx = wrap(b[0] - a[0]) * this.kx; | ||
const dy = (b[1] - a[1]) * this.ky; | ||
return Math.atan2(dx, dy) / RAD; | ||
} | ||
/** | ||
* Returns the bearing between two points in angles. | ||
* | ||
* @param {Array<number>} a point [longitude, latitude] | ||
* @param {Array<number>} b point [longitude, latitude] | ||
* @returns {number} bearing | ||
* @example | ||
* const bearing = ruler.bearing([30.5, 50.5], [30.51, 50.49]); | ||
* //=bearing | ||
*/ | ||
CheapRuler.prototype.bearing = function bearing (a, b) { | ||
var dx = wrap(b[0] - a[0]) * this.kx; | ||
var dy = (b[1] - a[1]) * this.ky; | ||
return Math.atan2(dx, dy) / RAD; | ||
}; | ||
/** | ||
* Returns a new point given distance and bearing from the starting point. | ||
* | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} dist distance | ||
* @param {number} bearing | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.destination([30.5, 50.5], 0.1, 90); | ||
* //=point | ||
*/ | ||
destination(p, dist, bearing) { | ||
const a = bearing * RAD; | ||
return this.offset(p, | ||
Math.sin(a) * dist, | ||
Math.cos(a) * dist); | ||
} | ||
/** | ||
* Returns a new point given distance and bearing from the starting point. | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {number} dist distance | ||
* @param {number} bearing | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.destination([30.5, 50.5], 0.1, 90); | ||
* //=point | ||
*/ | ||
CheapRuler.prototype.destination = function destination (p, dist, bearing) { | ||
var a = bearing * RAD; | ||
return this.offset(p, | ||
Math.sin(a) * dist, | ||
Math.cos(a) * dist); | ||
}; | ||
/** | ||
* Returns a new point given easting and northing offsets (in ruler units) from the starting point. | ||
* | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} dx easting | ||
* @param {number} dy northing | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.offset([30.5, 50.5], 10, 10); | ||
* //=point | ||
*/ | ||
offset(p, dx, dy) { | ||
return [ | ||
p[0] + dx / this.kx, | ||
p[1] + dy / this.ky | ||
]; | ||
} | ||
/** | ||
* Returns a new point given easting and northing offsets (in ruler units) from the starting point. | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {number} dx easting | ||
* @param {number} dy northing | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.offset([30.5, 50.5], 10, 10); | ||
* //=point | ||
*/ | ||
CheapRuler.prototype.offset = function offset (p, dx, dy) { | ||
return [ | ||
p[0] + dx / this.kx, | ||
p[1] + dy / this.ky | ||
]; | ||
}; | ||
/** | ||
* Given a line (an array of points), returns the total line distance. | ||
* | ||
* @param {[number, number][]} points [longitude, latitude] | ||
* @returns {number} total line distance | ||
* @example | ||
* const length = ruler.lineDistance([ | ||
* [-67.031, 50.458], [-67.031, 50.534], | ||
* [-66.929, 50.534], [-66.929, 50.458] | ||
* ]); | ||
* //=length | ||
*/ | ||
lineDistance(points) { | ||
let total = 0; | ||
for (let i = 0; i < points.length - 1; i++) { | ||
total += this.distance(points[i], points[i + 1]); | ||
} | ||
return total; | ||
} | ||
/** | ||
* Given a line (an array of points), returns the total line distance. | ||
* | ||
* @param {Array<Array<number>>} points [longitude, latitude] | ||
* @returns {number} total line distance | ||
* @example | ||
* const length = ruler.lineDistance([ | ||
* [-67.031, 50.458], [-67.031, 50.534], | ||
* [-66.929, 50.534], [-66.929, 50.458] | ||
* ]); | ||
* //=length | ||
*/ | ||
CheapRuler.prototype.lineDistance = function lineDistance (points) { | ||
var total = 0; | ||
for (var i = 0; i < points.length - 1; i++) { | ||
total += this.distance(points[i], points[i + 1]); | ||
} | ||
return total; | ||
}; | ||
/** | ||
* Given a polygon (an array of rings, where each ring is an array of points), returns the area. | ||
* | ||
* @param {[number, number][][]} polygon | ||
* @returns {number} area value in the specified units (square kilometers by default) | ||
* @example | ||
* const area = ruler.area([[ | ||
* [-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534], | ||
* [-66.929, 50.458], [-67.031, 50.458] | ||
* ]]); | ||
* //=area | ||
*/ | ||
area(polygon) { | ||
let sum = 0; | ||
/** | ||
* Given a polygon (an array of rings, where each ring is an array of points), returns the area. | ||
* | ||
* @param {Array<Array<Array<number>>>} polygon | ||
* @returns {number} area value in the specified units (square kilometers by default) | ||
* @example | ||
* const area = ruler.area([[ | ||
* [-67.031, 50.458], [-67.031, 50.534], [-66.929, 50.534], | ||
* [-66.929, 50.458], [-67.031, 50.458] | ||
* ]]); | ||
* //=area | ||
*/ | ||
CheapRuler.prototype.area = function area (polygon) { | ||
var sum = 0; | ||
for (let i = 0; i < polygon.length; i++) { | ||
const ring = polygon[i]; | ||
for (var i = 0; i < polygon.length; i++) { | ||
var ring = polygon[i]; | ||
for (let j = 0, len = ring.length, k = len - 1; j < len; k = j++) { | ||
sum += wrap(ring[j][0] - ring[k][0]) * (ring[j][1] + ring[k][1]) * (i ? -1 : 1); | ||
} | ||
} | ||
for (var j = 0, len = ring.length, k = len - 1; j < len; k = j++) { | ||
sum += wrap(ring[j][0] - ring[k][0]) * (ring[j][1] + ring[k][1]) * (i ? -1 : 1); | ||
return (Math.abs(sum) / 2) * this.kx * this.ky; | ||
} | ||
} | ||
return (Math.abs(sum) / 2) * this.kx * this.ky; | ||
}; | ||
/** | ||
* Returns the point at a specified distance along the line. | ||
* | ||
* @param {[number, number][]} line | ||
* @param {number} dist distance | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.along(line, 2.5); | ||
* //=point | ||
*/ | ||
along(line, dist) { | ||
let sum = 0; | ||
/** | ||
* Returns the point at a specified distance along the line. | ||
* | ||
* @param {Array<Array<number>>} line | ||
* @param {number} dist distance | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @example | ||
* const point = ruler.along(line, 2.5); | ||
* //=point | ||
*/ | ||
CheapRuler.prototype.along = function along (line, dist) { | ||
var sum = 0; | ||
if (dist <= 0) return line[0]; | ||
if (dist <= 0) { return line[0]; } | ||
for (let i = 0; i < line.length - 1; i++) { | ||
const p0 = line[i]; | ||
const p1 = line[i + 1]; | ||
const d = this.distance(p0, p1); | ||
sum += d; | ||
if (sum > dist) return interpolate(p0, p1, (dist - (sum - d)) / d); | ||
} | ||
for (var i = 0; i < line.length - 1; i++) { | ||
var p0 = line[i]; | ||
var p1 = line[i + 1]; | ||
var d = this.distance(p0, p1); | ||
sum += d; | ||
if (sum > dist) { return interpolate(p0, p1, (dist - (sum - d)) / d); } | ||
} | ||
return line[line.length - 1]; | ||
} | ||
return line[line.length - 1]; | ||
}; | ||
/** | ||
* Returns the distance from a point `p` to a line segment `a` to `b`. | ||
* | ||
* @pointToSegmentDistance | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {[number, number]} a segment point 1 [longitude, latitude] | ||
* @param {[number, number]} b segment point 2 [longitude, latitude] | ||
* @returns {number} distance | ||
* @example | ||
* const distance = ruler.pointToSegmentDistance([-67.04, 50.5], [-67.05, 50.57], [-67.03, 50.54]); | ||
* //=distance | ||
*/ | ||
pointToSegmentDistance(p, a, b) { | ||
let [x, y] = a; | ||
let dx = wrap(b[0] - x) * this.kx; | ||
let dy = (b[1] - y) * this.ky; | ||
/** | ||
* Returns the distance from a point `p` to a line segment `a` to `b`. | ||
* | ||
* @pointToSegmentDistance | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {Array<number>} p1 segment point 1 [longitude, latitude] | ||
* @param {Array<number>} p2 segment point 2 [longitude, latitude] | ||
* @returns {number} distance | ||
* @example | ||
* const distance = ruler.pointToSegmentDistance([-67.04, 50.5], [-67.05, 50.57], [-67.03, 50.54]); | ||
* //=distance | ||
*/ | ||
CheapRuler.prototype.pointToSegmentDistance = function pointToSegmentDistance (p, a, b) { | ||
var x = a[0]; | ||
var y = a[1]; | ||
var dx = wrap(b[0] - x) * this.kx; | ||
var dy = (b[1] - y) * this.ky; | ||
var t = 0; | ||
if (dx !== 0 || dy !== 0) { | ||
const t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
if (dx !== 0 || dy !== 0) { | ||
t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
if (t > 1) { | ||
x = b[0]; | ||
y = b[1]; | ||
if (t > 1) { | ||
x = b[0]; | ||
y = b[1]; | ||
} else if (t > 0) { | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
} | ||
} | ||
} else if (t > 0) { | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
dx = wrap(p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
} | ||
} | ||
dx = wrap(p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
/** | ||
* Returns an object of the form {point, index, t}, where point is closest point on the line | ||
* from the given point, index is the start index of the segment with the closest point, | ||
* and t is a parameter from 0 to 1 that indicates where the closest point is on that segment. | ||
* | ||
* @param {[number, number][]} line | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @returns {{point: [number, number], index: number, t: number}} {point, index, t} | ||
* @example | ||
* const point = ruler.pointOnLine(line, [-67.04, 50.5]).point; | ||
* //=point | ||
*/ | ||
pointOnLine(line, p) { | ||
let minDist = Infinity; | ||
let minX = line[0][0]; | ||
let minY = line[0][1]; | ||
let minI = 0; | ||
let minT = 0; | ||
return Math.sqrt(dx * dx + dy * dy); | ||
}; | ||
for (let i = 0; i < line.length - 1; i++) { | ||
/** | ||
* Returns an object of the form {point, index, t}, where point is closest point on the line | ||
* from the given point, index is the start index of the segment with the closest point, | ||
* and t is a parameter from 0 to 1 that indicates where the closest point is on that segment. | ||
* | ||
* @param {Array<Array<number>>} line | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @returns {Object} {point, index, t} | ||
* @example | ||
* const point = ruler.pointOnLine(line, [-67.04, 50.5]).point; | ||
* //=point | ||
*/ | ||
CheapRuler.prototype.pointOnLine = function pointOnLine (line, p) { | ||
var minDist = Infinity; | ||
var minX, minY, minI, minT; | ||
let x = line[i][0]; | ||
let y = line[i][1]; | ||
let dx = wrap(line[i + 1][0] - x) * this.kx; | ||
let dy = (line[i + 1][1] - y) * this.ky; | ||
let t = 0; | ||
for (var i = 0; i < line.length - 1; i++) { | ||
if (dx !== 0 || dy !== 0) { | ||
t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
var x = line[i][0]; | ||
var y = line[i][1]; | ||
var dx = wrap(line[i + 1][0] - x) * this.kx; | ||
var dy = (line[i + 1][1] - y) * this.ky; | ||
var t = 0; | ||
if (t > 1) { | ||
x = line[i + 1][0]; | ||
y = line[i + 1][1]; | ||
if (dx !== 0 || dy !== 0) { | ||
t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
} else if (t > 0) { | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
} | ||
} | ||
if (t > 1) { | ||
x = line[i + 1][0]; | ||
y = line[i + 1][1]; | ||
dx = wrap(p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
} else if (t > 0) { | ||
x += (dx / this.kx) * t; | ||
y += (dy / this.ky) * t; | ||
const sqDist = dx * dx + dy * dy; | ||
if (sqDist < minDist) { | ||
minDist = sqDist; | ||
minX = x; | ||
minY = y; | ||
minI = i; | ||
minT = t; | ||
} | ||
} | ||
return { | ||
point: [minX, minY], | ||
index: minI, | ||
t: Math.max(0, Math.min(1, minT)) | ||
}; | ||
} | ||
dx = wrap(p[0] - x) * this.kx; | ||
dy = (p[1] - y) * this.ky; | ||
/** | ||
* Returns a part of the given line between the start and the stop points (or their closest points on the line). | ||
* | ||
* @param {[number, number]} start point [longitude, latitude] | ||
* @param {[number, number]} stop point [longitude, latitude] | ||
* @param {[number, number][]} line | ||
* @returns {[number, number][]} line part of a line | ||
* @example | ||
* const line2 = ruler.lineSlice([-67.04, 50.5], [-67.05, 50.56], line1); | ||
* //=line2 | ||
*/ | ||
lineSlice(start, stop, line) { | ||
let p1 = this.pointOnLine(line, start); | ||
let p2 = this.pointOnLine(line, stop); | ||
var sqDist = dx * dx + dy * dy; | ||
if (sqDist < minDist) { | ||
minDist = sqDist; | ||
minX = x; | ||
minY = y; | ||
minI = i; | ||
minT = t; | ||
} | ||
} | ||
if (p1.index > p2.index || (p1.index === p2.index && p1.t > p2.t)) { | ||
const tmp = p1; | ||
p1 = p2; | ||
p2 = tmp; | ||
} | ||
return { | ||
point: [minX, minY], | ||
index: minI, | ||
t: Math.max(0, Math.min(1, minT)) | ||
}; | ||
}; | ||
const slice = [p1.point]; | ||
/** | ||
* Returns a part of the given line between the start and the stop points (or their closest points on the line). | ||
* | ||
* @param {Array<number>} start point [longitude, latitude] | ||
* @param {Array<number>} stop point [longitude, latitude] | ||
* @param {Array<Array<number>>} line | ||
* @returns {Array<Array<number>>} line part of a line | ||
* @example | ||
* const line2 = ruler.lineSlice([-67.04, 50.5], [-67.05, 50.56], line1); | ||
* //=line2 | ||
*/ | ||
CheapRuler.prototype.lineSlice = function lineSlice (start, stop, line) { | ||
var p1 = this.pointOnLine(line, start); | ||
var p2 = this.pointOnLine(line, stop); | ||
const l = p1.index + 1; | ||
const r = p2.index; | ||
if (p1.index > p2.index || (p1.index === p2.index && p1.t > p2.t)) { | ||
var tmp = p1; | ||
p1 = p2; | ||
p2 = tmp; | ||
} | ||
if (!equals(line[l], slice[0]) && l <= r) | ||
slice.push(line[l]); | ||
var slice = [p1.point]; | ||
for (let i = l + 1; i <= r; i++) { | ||
slice.push(line[i]); | ||
} | ||
var l = p1.index + 1; | ||
var r = p2.index; | ||
if (!equals(line[r], p2.point)) | ||
slice.push(p2.point); | ||
if (!equals(line[l], slice[0]) && l <= r) | ||
{ slice.push(line[l]); } | ||
return slice; | ||
} | ||
for (var i = l + 1; i <= r; i++) { | ||
slice.push(line[i]); | ||
} | ||
/** | ||
* Returns a part of the given line between the start and the stop points indicated by distance along the line. | ||
* | ||
* @param {number} start start distance | ||
* @param {number} stop stop distance | ||
* @param {[number, number][]} line | ||
* @returns {[number, number][]} part of a line | ||
* @example | ||
* const line2 = ruler.lineSliceAlong(10, 20, line1); | ||
* //=line2 | ||
*/ | ||
lineSliceAlong(start, stop, line) { | ||
let sum = 0; | ||
const slice = []; | ||
if (!equals(line[r], p2.point)) | ||
{ slice.push(p2.point); } | ||
for (let i = 0; i < line.length - 1; i++) { | ||
const p0 = line[i]; | ||
const p1 = line[i + 1]; | ||
const d = this.distance(p0, p1); | ||
return slice; | ||
}; | ||
sum += d; | ||
/** | ||
* Returns a part of the given line between the start and the stop points indicated by distance along the line. | ||
* | ||
* @param {number} start distance | ||
* @param {number} stop distance | ||
* @param {Array<Array<number>>} line | ||
* @returns {Array<Array<number>>} line part of a line | ||
* @example | ||
* const line2 = ruler.lineSliceAlong(10, 20, line1); | ||
* //=line2 | ||
*/ | ||
CheapRuler.prototype.lineSliceAlong = function lineSliceAlong (start, stop, line) { | ||
var sum = 0; | ||
var slice = []; | ||
if (sum > start && slice.length === 0) { | ||
slice.push(interpolate(p0, p1, (start - (sum - d)) / d)); | ||
} | ||
for (var i = 0; i < line.length - 1; i++) { | ||
var p0 = line[i]; | ||
var p1 = line[i + 1]; | ||
var d = this.distance(p0, p1); | ||
if (sum >= stop) { | ||
slice.push(interpolate(p0, p1, (stop - (sum - d)) / d)); | ||
return slice; | ||
} | ||
sum += d; | ||
if (sum > start) slice.push(p1); | ||
} | ||
if (sum > start && slice.length === 0) { | ||
slice.push(interpolate(p0, p1, (start - (sum - d)) / d)); | ||
return slice; | ||
} | ||
if (sum >= stop) { | ||
slice.push(interpolate(p0, p1, (stop - (sum - d)) / d)); | ||
return slice; | ||
/** | ||
* Given a point, returns a bounding box object ([w, s, e, n]) created from the given point buffered by a given distance. | ||
* | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} buffer | ||
* @returns {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @example | ||
* const bbox = ruler.bufferPoint([30.5, 50.5], 0.01); | ||
* //=bbox | ||
*/ | ||
bufferPoint(p, buffer) { | ||
const v = buffer / this.ky; | ||
const h = buffer / this.kx; | ||
return [ | ||
p[0] - h, | ||
p[1] - v, | ||
p[0] + h, | ||
p[1] + v | ||
]; | ||
} | ||
if (sum > start) { slice.push(p1); } | ||
/** | ||
* Given a bounding box, returns the box buffered by a given distance. | ||
* | ||
* @param {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @param {number} buffer | ||
* @returns {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @example | ||
* const bbox = ruler.bufferBBox([30.5, 50.5, 31, 51], 0.2); | ||
* //=bbox | ||
*/ | ||
bufferBBox(bbox, buffer) { | ||
const v = buffer / this.ky; | ||
const h = buffer / this.kx; | ||
return [ | ||
bbox[0] - h, | ||
bbox[1] - v, | ||
bbox[2] + h, | ||
bbox[3] + v | ||
]; | ||
} | ||
/** | ||
* Returns true if the given point is inside in the given bounding box, otherwise false. | ||
* | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @returns {boolean} | ||
* @example | ||
* const inside = ruler.insideBBox([30.5, 50.5], [30, 50, 31, 51]); | ||
* //=inside | ||
*/ | ||
insideBBox(p, bbox) { // eslint-disable-line | ||
return wrap(p[0] - bbox[0]) >= 0 && | ||
wrap(p[0] - bbox[2]) <= 0 && | ||
p[1] >= bbox[1] && | ||
p[1] <= bbox[3]; | ||
} | ||
} | ||
return slice; | ||
}; | ||
/** | ||
* @param {[number, number]} a | ||
* @param {[number, number]} b | ||
*/ | ||
function equals(a, b) { | ||
return a[0] === b[0] && a[1] === b[1]; | ||
} | ||
/** | ||
* Given a point, returns a bounding box object ([w, s, e, n]) created from the given point buffered by a given distance. | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {number} buffer | ||
* @returns {Array<number>} box object ([w, s, e, n]) | ||
* @example | ||
* const bbox = ruler.bufferPoint([30.5, 50.5], 0.01); | ||
* //=bbox | ||
*/ | ||
CheapRuler.prototype.bufferPoint = function bufferPoint (p, buffer) { | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
p[0] - h, | ||
p[1] - v, | ||
p[0] + h, | ||
p[1] + v | ||
]; | ||
}; | ||
/** | ||
* @param {[number, number]} a | ||
* @param {[number, number]} b | ||
* @param {number} t | ||
* @returns {[number, number]} | ||
*/ | ||
function interpolate(a, b, t) { | ||
const dx = wrap(b[0] - a[0]); | ||
const dy = b[1] - a[1]; | ||
return [ | ||
a[0] + dx * t, | ||
a[1] + dy * t | ||
]; | ||
} | ||
/** | ||
* Given a bounding box, returns the box buffered by a given distance. | ||
* | ||
* @param {Array<number>} box object ([w, s, e, n]) | ||
* @param {number} buffer | ||
* @returns {Array<number>} box object ([w, s, e, n]) | ||
* @example | ||
* const bbox = ruler.bufferBBox([30.5, 50.5, 31, 51], 0.2); | ||
* //=bbox | ||
*/ | ||
CheapRuler.prototype.bufferBBox = function bufferBBox (bbox, buffer) { | ||
var v = buffer / this.ky; | ||
var h = buffer / this.kx; | ||
return [ | ||
bbox[0] - h, | ||
bbox[1] - v, | ||
bbox[2] + h, | ||
bbox[3] + v | ||
]; | ||
}; | ||
/** | ||
* normalize a degree value into [-180..180] range | ||
* @param {number} deg | ||
*/ | ||
function wrap(deg) { | ||
while (deg < -180) deg += 360; | ||
while (deg > 180) deg -= 360; | ||
return deg; | ||
} | ||
/** | ||
* Returns true if the given point is inside in the given bounding box, otherwise false. | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {Array<number>} box object ([w, s, e, n]) | ||
* @returns {boolean} | ||
* @example | ||
* const inside = ruler.insideBBox([30.5, 50.5], [30, 50, 31, 51]); | ||
* //=inside | ||
*/ | ||
CheapRuler.prototype.insideBBox = function insideBBox (p, bbox) { | ||
return wrap(p[0] - bbox[0]) >= 0 && | ||
wrap(p[0] - bbox[2]) <= 0 && | ||
p[1] >= bbox[1] && | ||
p[1] <= bbox[3]; | ||
}; | ||
return CheapRuler; | ||
Object.defineProperties( CheapRuler, staticAccessors ); | ||
function equals(a, b) { | ||
return a[0] === b[0] && a[1] === b[1]; | ||
} | ||
function interpolate(a, b, t) { | ||
var dx = wrap(b[0] - a[0]); | ||
var dy = b[1] - a[1]; | ||
return [ | ||
a[0] + dx * t, | ||
a[1] + dy * t | ||
]; | ||
} | ||
// normalize a degree value into [-180..180] range | ||
function wrap(deg) { | ||
while (deg < -180) { deg += 360; } | ||
while (deg > 180) { deg -= 360; } | ||
return deg; | ||
} | ||
return CheapRuler; | ||
})); |
102
index.js
@@ -1,2 +0,1 @@ | ||
'use strict'; /* @flow */ | ||
@@ -23,9 +22,2 @@ const factors = { | ||
* A collection of very fast approximations to common geodesic measurements. Useful for performance-sensitive code that measures things on a city scale. | ||
* | ||
* @param {number} lat latitude | ||
* @param {string} [units='kilometers'] | ||
* @returns {CheapRuler} | ||
* @example | ||
* const ruler = cheapRuler(35.05, 'miles'); | ||
* //=ruler | ||
*/ | ||
@@ -38,3 +30,3 @@ export default class CheapRuler { | ||
* @param {number} z | ||
* @param {string} [units='kilometers'] | ||
* @param {keyof typeof factors} [units='kilometers'] | ||
* @returns {CheapRuler} | ||
@@ -66,4 +58,3 @@ * @example | ||
* @param {number} lat latitude | ||
* @param {string} [units='kilometers'] | ||
* @returns {CheapRuler} | ||
* @param {keyof typeof factors} [units='kilometers'] | ||
* @example | ||
@@ -91,4 +82,4 @@ * const ruler = cheapRuler(35.05, 'miles'); | ||
* | ||
* @param {Array<number>} a point [longitude, latitude] | ||
* @param {Array<number>} b point [longitude, latitude] | ||
* @param {[number, number]} a point [longitude, latitude] | ||
* @param {[number, number]} b point [longitude, latitude] | ||
* @returns {number} distance | ||
@@ -108,4 +99,4 @@ * @example | ||
* | ||
* @param {Array<number>} a point [longitude, latitude] | ||
* @param {Array<number>} b point [longitude, latitude] | ||
* @param {[number, number]} a point [longitude, latitude] | ||
* @param {[number, number]} b point [longitude, latitude] | ||
* @returns {number} bearing | ||
@@ -125,6 +116,6 @@ * @example | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} dist distance | ||
* @param {number} bearing | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
@@ -144,6 +135,6 @@ * const point = ruler.destination([30.5, 50.5], 0.1, 90); | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} dx easting | ||
* @param {number} dy northing | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
@@ -163,3 +154,3 @@ * const point = ruler.offset([30.5, 50.5], 10, 10); | ||
* | ||
* @param {Array<Array<number>>} points [longitude, latitude] | ||
* @param {[number, number][]} points [longitude, latitude] | ||
* @returns {number} total line distance | ||
@@ -184,3 +175,3 @@ * @example | ||
* | ||
* @param {Array<Array<Array<number>>>} polygon | ||
* @param {[number, number][][]} polygon | ||
* @returns {number} area value in the specified units (square kilometers by default) | ||
@@ -211,5 +202,5 @@ * @example | ||
* | ||
* @param {Array<Array<number>>} line | ||
* @param {[number, number][]} line | ||
* @param {number} dist distance | ||
* @returns {Array<number>} point [longitude, latitude] | ||
* @returns {[number, number]} point [longitude, latitude] | ||
* @example | ||
@@ -239,5 +230,5 @@ * const point = ruler.along(line, 2.5); | ||
* @pointToSegmentDistance | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {Array<number>} p1 segment point 1 [longitude, latitude] | ||
* @param {Array<number>} p2 segment point 2 [longitude, latitude] | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {[number, number]} a segment point 1 [longitude, latitude] | ||
* @param {[number, number]} b segment point 2 [longitude, latitude] | ||
* @returns {number} distance | ||
@@ -252,6 +243,5 @@ * @example | ||
let dy = (b[1] - y) * this.ky; | ||
let t = 0; | ||
if (dx !== 0 || dy !== 0) { | ||
t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
const t = (wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); | ||
@@ -279,5 +269,5 @@ if (t > 1) { | ||
* | ||
* @param {Array<Array<number>>} line | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @returns {Object} {point, index, t} | ||
* @param {[number, number][]} line | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @returns {{point: [number, number], index: number, t: number}} {point, index, t} | ||
* @example | ||
@@ -289,3 +279,6 @@ * const point = ruler.pointOnLine(line, [-67.04, 50.5]).point; | ||
let minDist = Infinity; | ||
let minX, minY, minI, minT; | ||
let minX = line[0][0]; | ||
let minY = line[0][1]; | ||
let minI = 0; | ||
let minT = 0; | ||
@@ -336,6 +329,6 @@ for (let i = 0; i < line.length - 1; i++) { | ||
* | ||
* @param {Array<number>} start point [longitude, latitude] | ||
* @param {Array<number>} stop point [longitude, latitude] | ||
* @param {Array<Array<number>>} line | ||
* @returns {Array<Array<number>>} line part of a line | ||
* @param {[number, number]} start point [longitude, latitude] | ||
* @param {[number, number]} stop point [longitude, latitude] | ||
* @param {[number, number][]} line | ||
* @returns {[number, number][]} line part of a line | ||
* @example | ||
@@ -376,6 +369,6 @@ * const line2 = ruler.lineSlice([-67.04, 50.5], [-67.05, 50.56], line1); | ||
* | ||
* @param {number} start distance | ||
* @param {number} stop distance | ||
* @param {Array<Array<number>>} line | ||
* @returns {Array<Array<number>>} line part of a line | ||
* @param {number} start start distance | ||
* @param {number} stop stop distance | ||
* @param {[number, number][]} line | ||
* @returns {[number, number][]} part of a line | ||
* @example | ||
@@ -414,5 +407,5 @@ * const line2 = ruler.lineSliceAlong(10, 20, line1); | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {number} buffer | ||
* @returns {Array<number>} box object ([w, s, e, n]) | ||
* @returns {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @example | ||
@@ -436,5 +429,5 @@ * const bbox = ruler.bufferPoint([30.5, 50.5], 0.01); | ||
* | ||
* @param {Array<number>} box object ([w, s, e, n]) | ||
* @param {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @param {number} buffer | ||
* @returns {Array<number>} box object ([w, s, e, n]) | ||
* @returns {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @example | ||
@@ -458,4 +451,4 @@ * const bbox = ruler.bufferBBox([30.5, 50.5, 31, 51], 0.2); | ||
* | ||
* @param {Array<number>} p point [longitude, latitude] | ||
* @param {Array<number>} box object ([w, s, e, n]) | ||
* @param {[number, number]} p point [longitude, latitude] | ||
* @param {[number, number, number, number]} bbox ([w, s, e, n]) | ||
* @returns {boolean} | ||
@@ -466,3 +459,3 @@ * @example | ||
*/ | ||
insideBBox(p, bbox) { | ||
insideBBox(p, bbox) { // eslint-disable-line | ||
return wrap(p[0] - bbox[0]) >= 0 && | ||
@@ -475,2 +468,6 @@ wrap(p[0] - bbox[2]) <= 0 && | ||
/** | ||
* @param {[number, number]} a | ||
* @param {[number, number]} b | ||
*/ | ||
function equals(a, b) { | ||
@@ -480,2 +477,8 @@ return a[0] === b[0] && a[1] === b[1]; | ||
/** | ||
* @param {[number, number]} a | ||
* @param {[number, number]} b | ||
* @param {number} t | ||
* @returns {[number, number]} | ||
*/ | ||
function interpolate(a, b, t) { | ||
@@ -490,3 +493,6 @@ const dx = wrap(b[0] - a[0]); | ||
// normalize a degree value into [-180..180] range | ||
/** | ||
* normalize a degree value into [-180..180] range | ||
* @param {number} deg | ||
*/ | ||
function wrap(deg) { | ||
@@ -493,0 +499,0 @@ while (deg < -180) deg += 360; |
{ | ||
"name": "cheap-ruler", | ||
"version": "3.0.2", | ||
"version": "4.0.0", | ||
"description": "A collection of fast approximations to common geographic measurements.", | ||
"author": "Vladimir Agafonkin", | ||
"license": "ISC", | ||
"type": "module", | ||
"main": "cheap-ruler.js", | ||
"exports": "./index.js", | ||
"module": "index.js", | ||
"jsdelivr": "cheap-ruler.min.js", | ||
"unpkg": "cheap-ruler.min.js", | ||
"types": "cheap-ruler.d.ts", | ||
"dependencies": {}, | ||
"types": "index.d.ts", | ||
"devDependencies": { | ||
"@rollup/plugin-buble": "^0.21.3", | ||
"@turf/turf": "^6.5.0", | ||
"@turf/turf": "^7.0.0", | ||
"benchmark": "^2.1.4", | ||
"eslint": "^8.2.0", | ||
"eslint-config-mourner": "^3.0.0", | ||
"esm": "^3.2.25", | ||
"eslint": "^9.5.0", | ||
"eslint-config-mourner": "^4.0.0", | ||
"node-vincenty": "0.0.6", | ||
"nyc": "^15.1.0", | ||
"rollup": "^2.60.0", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"tape": "^5.3.2", | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.4.4" | ||
"rollup": "^4.18.0", | ||
"typescript": "^5.5.2" | ||
}, | ||
"scripts": { | ||
"pretest": "eslint index.js bench test/*.js", | ||
"test": "tape -r esm test/test.js", | ||
"posttest": "npm run build && npm run types", | ||
"types": "ts-node test/types.ts", | ||
"build": "rollup -c", | ||
"prepublishOnly": "npm test", | ||
"cov": "nyc tape test/test.js" | ||
"test": "tsc && node --test test/test.js", | ||
"build": "rollup index.js -o cheap-ruler.js -n CheapRuler -f umd", | ||
"prepublishOnly": "npm test && npm run build", | ||
"cov": "node --test --experimental-test-coverage test/test.js" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"cheap-ruler.js", | ||
"cheap-ruler.min.js", | ||
"cheap-ruler.d.ts" | ||
"index.d.ts", | ||
"cheap-ruler.js" | ||
], | ||
"eslintConfig": { | ||
"extends": "mourner" | ||
}, | ||
"repository": { | ||
@@ -53,9 +42,3 @@ "type": "git", | ||
"distance" | ||
], | ||
"author": "Vladimir Agafonkin", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/mapbox/cheap-ruler/issues" | ||
}, | ||
"homepage": "https://github.com/mapbox/cheap-ruler#readme" | ||
] | ||
} |
@@ -1,2 +0,2 @@ | ||
# cheap-ruler [![Build Status](https://travis-ci.org/mapbox/cheap-ruler.svg?branch=master)](https://travis-ci.org/mapbox/cheap-ruler) [![](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) | ||
# cheap-ruler [![Node](https://github.com/mapbox/cheap-ruler/actions/workflows/node.yml/badge.svg)](https://github.com/mapbox/cheap-ruler/actions/workflows/node.yml) [![](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) | ||
@@ -195,4 +195,4 @@ A collection of very fast approximations to common geodesic measurements. | ||
- NPM: `npm install cheap-ruler` | ||
- [Browser build on CDN](https://unpkg.com/cheap-ruler@3.0.1/cheap-ruler.js) | ||
- [Browser build on CDN (minified)](https://unpkg.com/cheap-ruler@3.0.1/cheap-ruler.min.js) | ||
- [Browser build on CDN (ESM)](https://esm.run/cheap-ruler) | ||
- [Browser build on CDN (UMD)](https://cdn.jsdelivr.net/npm/cheap-ruler/cheap-ruler.js) | ||
@@ -199,0 +199,0 @@ ## Precision |
Sorry, the diff of this file is not supported yet
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
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
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
48159
7
1067
Yes
6
1
1