You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1420 lines
44 KiB
1420 lines
44 KiB
(function (root, factory) { |
|
|
|
// Node. |
|
if (typeof module === 'object' && typeof module.exports === 'object') { |
|
exports = module.exports = factory(); |
|
} |
|
|
|
// Browser Global. |
|
if (typeof window === "object") { |
|
root.Terraformer = factory(); |
|
} |
|
|
|
}(this, function () { |
|
"use strict"; |
|
|
|
var exports = {}, |
|
EarthRadius = 6378137, |
|
DegreesPerRadian = 57.295779513082320, |
|
RadiansPerDegree = 0.017453292519943, |
|
MercatorCRS = { |
|
"type": "link", |
|
"properties": { |
|
"href": "http://spatialreference.org/ref/sr-org/6928/ogcwkt/", |
|
"type": "ogcwkt" |
|
} |
|
}, |
|
GeographicCRS = { |
|
"type": "link", |
|
"properties": { |
|
"href": "http://spatialreference.org/ref/epsg/4326/ogcwkt/", |
|
"type": "ogcwkt" |
|
} |
|
}; |
|
|
|
/* |
|
Internal: isArray function |
|
*/ |
|
function isArray(obj) { |
|
return Object.prototype.toString.call(obj) === "[object Array]"; |
|
} |
|
|
|
/* |
|
Internal: safe warning |
|
*/ |
|
function warn() { |
|
var args = Array.prototype.slice.apply(arguments); |
|
|
|
if (typeof console !== undefined && console.warn) { |
|
console.warn.apply(console, args); |
|
} |
|
} |
|
|
|
/* |
|
Internal: Extend one object with another. |
|
*/ |
|
function extend(destination, source) { |
|
for (var k in source) { |
|
if (source.hasOwnProperty(k)) { |
|
destination[k] = source[k]; |
|
} |
|
} |
|
return destination; |
|
} |
|
|
|
/* |
|
Public: Calculate an bounding box for a geojson object |
|
*/ |
|
function calculateBounds(geojson) { |
|
if (geojson.type) { |
|
switch (geojson.type) { |
|
case 'Point': |
|
return [geojson.coordinates[0], geojson.coordinates[1], geojson.coordinates[0], geojson.coordinates[1]]; |
|
|
|
case 'MultiPoint': |
|
return calculateBoundsFromArray(geojson.coordinates); |
|
|
|
case 'LineString': |
|
return calculateBoundsFromArray(geojson.coordinates); |
|
|
|
case 'MultiLineString': |
|
return calculateBoundsFromNestedArrays(geojson.coordinates); |
|
|
|
case 'Polygon': |
|
return calculateBoundsFromNestedArrays(geojson.coordinates); |
|
|
|
case 'MultiPolygon': |
|
return calculateBoundsFromNestedArrayOfArrays(geojson.coordinates); |
|
|
|
case 'Feature': |
|
return geojson.geometry ? calculateBounds(geojson.geometry) : null; |
|
|
|
case 'FeatureCollection': |
|
return calculateBoundsForFeatureCollection(geojson); |
|
|
|
case 'GeometryCollection': |
|
return calculateBoundsForGeometryCollection(geojson); |
|
|
|
default: |
|
throw new Error("Unknown type: " + geojson.type); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/* |
|
Internal: Calculate an bounding box from an nested array of positions |
|
[ |
|
[ |
|
[ [lng, lat],[lng, lat],[lng, lat] ] |
|
] |
|
[ |
|
[lng, lat],[lng, lat],[lng, lat] |
|
] |
|
[ |
|
[lng, lat],[lng, lat],[lng, lat] |
|
] |
|
] |
|
*/ |
|
function calculateBoundsFromNestedArrays(array) { |
|
var x1 = null, x2 = null, y1 = null, y2 = null; |
|
|
|
for (var i = 0; i < array.length; i++) { |
|
var inner = array[i]; |
|
|
|
for (var j = 0; j < inner.length; j++) { |
|
var lonlat = inner[j]; |
|
|
|
var lon = lonlat[0]; |
|
var lat = lonlat[1]; |
|
|
|
if (x1 === null) { |
|
x1 = lon; |
|
} else if (lon < x1) { |
|
x1 = lon; |
|
} |
|
|
|
if (x2 === null) { |
|
x2 = lon; |
|
} else if (lon > x2) { |
|
x2 = lon; |
|
} |
|
|
|
if (y1 === null) { |
|
y1 = lat; |
|
} else if (lat < y1) { |
|
y1 = lat; |
|
} |
|
|
|
if (y2 === null) { |
|
y2 = lat; |
|
} else if (lat > y2) { |
|
y2 = lat; |
|
} |
|
} |
|
} |
|
|
|
return [x1, y1, x2, y2]; |
|
} |
|
|
|
/* |
|
Internal: Calculate a bounding box from an array of arrays of arrays |
|
[ |
|
[ [lng, lat],[lng, lat],[lng, lat] ] |
|
[ [lng, lat],[lng, lat],[lng, lat] ] |
|
[ [lng, lat],[lng, lat],[lng, lat] ] |
|
] |
|
*/ |
|
function calculateBoundsFromNestedArrayOfArrays(array) { |
|
var x1 = null, x2 = null, y1 = null, y2 = null; |
|
|
|
for (var i = 0; i < array.length; i++) { |
|
var inner = array[i]; |
|
|
|
for (var j = 0; j < inner.length; j++) { |
|
var innerinner = inner[j]; |
|
for (var k = 0; k < innerinner.length; k++) { |
|
var lonlat = innerinner[k]; |
|
|
|
var lon = lonlat[0]; |
|
var lat = lonlat[1]; |
|
|
|
if (x1 === null) { |
|
x1 = lon; |
|
} else if (lon < x1) { |
|
x1 = lon; |
|
} |
|
|
|
if (x2 === null) { |
|
x2 = lon; |
|
} else if (lon > x2) { |
|
x2 = lon; |
|
} |
|
|
|
if (y1 === null) { |
|
y1 = lat; |
|
} else if (lat < y1) { |
|
y1 = lat; |
|
} |
|
|
|
if (y2 === null) { |
|
y2 = lat; |
|
} else if (lat > y2) { |
|
y2 = lat; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return [x1, y1, x2, y2]; |
|
} |
|
|
|
/* |
|
Internal: Calculate a bounding box from an array of positions |
|
[ |
|
[lng, lat],[lng, lat],[lng, lat] |
|
] |
|
*/ |
|
function calculateBoundsFromArray(array) { |
|
var x1 = null, x2 = null, y1 = null, y2 = null; |
|
|
|
for (var i = 0; i < array.length; i++) { |
|
var lonlat = array[i]; |
|
var lon = lonlat[0]; |
|
var lat = lonlat[1]; |
|
|
|
if (x1 === null) { |
|
x1 = lon; |
|
} else if (lon < x1) { |
|
x1 = lon; |
|
} |
|
|
|
if (x2 === null) { |
|
x2 = lon; |
|
} else if (lon > x2) { |
|
x2 = lon; |
|
} |
|
|
|
if (y1 === null) { |
|
y1 = lat; |
|
} else if (lat < y1) { |
|
y1 = lat; |
|
} |
|
|
|
if (y2 === null) { |
|
y2 = lat; |
|
} else if (lat > y2) { |
|
y2 = lat; |
|
} |
|
} |
|
|
|
return [x1, y1, x2, y2]; |
|
} |
|
|
|
/* |
|
Internal: Calculate an bounding box for a feature collection |
|
*/ |
|
function calculateBoundsForFeatureCollection(featureCollection) { |
|
var extents = [], extent; |
|
for (var i = featureCollection.features.length - 1; i >= 0; i--) { |
|
extent = calculateBounds(featureCollection.features[i].geometry); |
|
extents.push([extent[0], extent[1]]); |
|
extents.push([extent[2], extent[3]]); |
|
} |
|
|
|
return calculateBoundsFromArray(extents); |
|
} |
|
|
|
/* |
|
Internal: Calculate an bounding box for a geometry collection |
|
*/ |
|
function calculateBoundsForGeometryCollection(geometryCollection) { |
|
var extents = [], extent; |
|
|
|
for (var i = geometryCollection.geometries.length - 1; i >= 0; i--) { |
|
extent = calculateBounds(geometryCollection.geometries[i]); |
|
extents.push([extent[0], extent[1]]); |
|
extents.push([extent[2], extent[3]]); |
|
} |
|
|
|
return calculateBoundsFromArray(extents); |
|
} |
|
|
|
function calculateEnvelope(geojson) { |
|
var bounds = calculateBounds(geojson); |
|
return { |
|
x: bounds[0], |
|
y: bounds[1], |
|
w: Math.abs(bounds[0] - bounds[2]), |
|
h: Math.abs(bounds[1] - bounds[3]) |
|
}; |
|
} |
|
|
|
/* |
|
Internal: Convert radians to degrees. Used by spatial reference converters. |
|
*/ |
|
function radToDeg(rad) { |
|
return rad * DegreesPerRadian; |
|
} |
|
|
|
/* |
|
Internal: Convert degrees to radians. Used by spatial reference converters. |
|
*/ |
|
function degToRad(deg) { |
|
return deg * RadiansPerDegree; |
|
} |
|
|
|
/* |
|
Internal: Loop over each array in a geojson object and apply a function to it. Used by spatial reference converters. |
|
*/ |
|
function eachPosition(coordinates, func) { |
|
for (var i = 0; i < coordinates.length; i++) { |
|
// we found a number so lets convert this pair |
|
if (typeof coordinates[i][0] === "number") { |
|
coordinates[i] = func(coordinates[i]); |
|
} |
|
// we found an coordinates array it again and run THIS function against it |
|
if (typeof coordinates[i] === "object") { |
|
coordinates[i] = eachPosition(coordinates[i], func); |
|
} |
|
} |
|
return coordinates; |
|
} |
|
|
|
/* |
|
Public: Convert a GeoJSON Position object to Geographic (4326) |
|
*/ |
|
function positionToGeographic(position) { |
|
var x = position[0]; |
|
var y = position[1]; |
|
return [radToDeg(x / EarthRadius) - (Math.floor((radToDeg(x / EarthRadius) + 180) / 360) * 360), radToDeg((Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / EarthRadius))))]; |
|
} |
|
|
|
/* |
|
Public: Convert a GeoJSON Position object to Web Mercator (102100) |
|
*/ |
|
function positionToMercator(position) { |
|
var lng = position[0]; |
|
var lat = Math.max(Math.min(position[1], 89.99999), -89.99999); |
|
return [degToRad(lng) * EarthRadius, EarthRadius / 2.0 * Math.log((1.0 + Math.sin(degToRad(lat))) / (1.0 - Math.sin(degToRad(lat))))]; |
|
} |
|
|
|
/* |
|
Public: Apply a function agaist all positions in a geojson object. Used by spatial reference converters. |
|
*/ |
|
function applyConverter(geojson, converter, noCrs) { |
|
if (geojson.type === "Point") { |
|
geojson.coordinates = converter(geojson.coordinates); |
|
} else if (geojson.type === "Feature") { |
|
geojson.geometry = applyConverter(geojson.geometry, converter, true); |
|
} else if (geojson.type === "FeatureCollection") { |
|
for (var f = 0; f < geojson.features.length; f++) { |
|
geojson.features[f] = applyConverter(geojson.features[f], converter, true); |
|
} |
|
} else if (geojson.type === "GeometryCollection") { |
|
for (var g = 0; g < geojson.geometries.length; g++) { |
|
geojson.geometries[g] = applyConverter(geojson.geometries[g], converter, true); |
|
} |
|
} else { |
|
geojson.coordinates = eachPosition(geojson.coordinates, converter); |
|
} |
|
|
|
if (!noCrs) { |
|
if (converter === positionToMercator) { |
|
geojson.crs = MercatorCRS; |
|
} |
|
} |
|
|
|
if (converter === positionToGeographic) { |
|
delete geojson.crs; |
|
} |
|
|
|
return geojson; |
|
} |
|
|
|
/* |
|
Public: Convert a GeoJSON object to ESRI Web Mercator (102100) |
|
*/ |
|
function toMercator(geojson) { |
|
return applyConverter(geojson, positionToMercator); |
|
} |
|
|
|
/* |
|
Convert a GeoJSON object to Geographic coordinates (WSG84, 4326) |
|
*/ |
|
function toGeographic(geojson) { |
|
return applyConverter(geojson, positionToGeographic); |
|
} |
|
|
|
|
|
/* |
|
Internal: -1,0,1 comparison function |
|
*/ |
|
function cmp(a, b) { |
|
if (a < b) { |
|
return -1; |
|
} else if (a > b) { |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
/* |
|
Internal: used for sorting |
|
*/ |
|
function compSort(p1, p2) { |
|
if (p1[0] > p2[0]) { |
|
return -1; |
|
} else if (p1[0] < p2[0]) { |
|
return 1; |
|
} else if (p1[1] > p2[1]) { |
|
return -1; |
|
} else if (p1[1] < p2[1]) { |
|
return 1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
/* |
|
Internal: used to determine turn |
|
*/ |
|
function turn(p, q, r) { |
|
// Returns -1, 0, 1 if p,q,r forms a right, straight, or left turn. |
|
return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0); |
|
} |
|
|
|
/* |
|
Internal: used to determine euclidean distance between two points |
|
*/ |
|
function euclideanDistance(p, q) { |
|
// Returns the squared Euclidean distance between p and q. |
|
var dx = q[0] - p[0]; |
|
var dy = q[1] - p[1]; |
|
|
|
return dx * dx + dy * dy; |
|
} |
|
|
|
function nextHullPoint(points, p) { |
|
// Returns the next point on the convex hull in CCW from p. |
|
var q = p; |
|
for (var r in points) { |
|
var t = turn(p, q, points[r]); |
|
if (t === -1 || t === 0 && euclideanDistance(p, points[r]) > euclideanDistance(p, q)) { |
|
q = points[r]; |
|
} |
|
} |
|
return q; |
|
} |
|
|
|
function convexHull(points) { |
|
// implementation of the Jarvis March algorithm |
|
// adapted from http://tixxit.wordpress.com/2009/12/09/jarvis-march/ |
|
|
|
if (points.length === 0) { |
|
return []; |
|
} else if (points.length === 1) { |
|
return points; |
|
} |
|
|
|
// Returns the points on the convex hull of points in CCW order. |
|
var hull = [points.sort(compSort)[0]]; |
|
|
|
for (var p = 0; p < hull.length; p++) { |
|
var q = nextHullPoint(points, hull[p]); |
|
|
|
if (q !== hull[0]) { |
|
hull.push(q); |
|
} |
|
} |
|
|
|
return hull; |
|
} |
|
|
|
function isConvex(points) { |
|
var ltz; |
|
|
|
for (var i = 0; i < points.length - 3; i++) { |
|
var p1 = points[i]; |
|
var p2 = points[i + 1]; |
|
var p3 = points[i + 2]; |
|
var v = [p2[0] - p1[0], p2[1] - p1[1]]; |
|
|
|
// p3.x * v.y - p3.y * v.x + v.x * p1.y - v.y * p1.x |
|
var res = p3[0] * v[1] - p3[1] * v[0] + v[0] * p1[1] - v[1] * p1[0]; |
|
|
|
if (i === 0) { |
|
if (res < 0) { |
|
ltz = true; |
|
} else { |
|
ltz = false; |
|
} |
|
} else { |
|
if (ltz && (res > 0) || !ltz && (res < 0)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function coordinatesContainPoint(coordinates, point) { |
|
var contains = false; |
|
for (var i = -1, l = coordinates.length, j = l - 1; ++i < l; j = i) { |
|
if (((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1]) || |
|
(coordinates[j][1] <= point[1] && point[1] < coordinates[i][1])) && |
|
(point[0] < (coordinates[j][0] - coordinates[i][0]) * (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0])) { |
|
contains = !contains; |
|
} |
|
} |
|
return contains; |
|
} |
|
|
|
function polygonContainsPoint(polygon, point) { |
|
if (polygon && polygon.length) { |
|
if (polygon.length === 1) { // polygon with no holes |
|
return coordinatesContainPoint(polygon[0], point); |
|
} else { // polygon with holes |
|
if (coordinatesContainPoint(polygon[0], point)) { |
|
for (var i = 1; i < polygon.length; i++) { |
|
if (coordinatesContainPoint(polygon[i], point)) { |
|
return false; // found in hole |
|
} |
|
} |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function edgeIntersectsEdge(a1, a2, b1, b2) { |
|
var ua_t = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0]); |
|
var ub_t = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0]); |
|
var u_b = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1]); |
|
|
|
if (u_b !== 0) { |
|
var ua = ua_t / u_b; |
|
var ub = ub_t / u_b; |
|
|
|
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function isNumber(n) { |
|
return !isNaN(parseFloat(n)) && isFinite(n); |
|
} |
|
|
|
function arraysIntersectArrays(a, b) { |
|
if (isNumber(a[0][0])) { |
|
if (isNumber(b[0][0])) { |
|
for (var i = 0; i < a.length - 1; i++) { |
|
for (var j = 0; j < b.length - 1; j++) { |
|
if (edgeIntersectsEdge(a[i], a[i + 1], b[j], b[j + 1])) { |
|
return true; |
|
} |
|
} |
|
} |
|
} else { |
|
for (var k = 0; k < b.length; k++) { |
|
if (arraysIntersectArrays(a, b[k])) { |
|
return true; |
|
} |
|
} |
|
} |
|
} else { |
|
for (var l = 0; l < a.length; l++) { |
|
if (arraysIntersectArrays(a[l], b)) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/* |
|
Internal: Returns a copy of coordinates for s closed polygon |
|
*/ |
|
function closedPolygon(coordinates) { |
|
var outer = []; |
|
|
|
for (var i = 0; i < coordinates.length; i++) { |
|
var inner = coordinates[i].slice(); |
|
if (pointsEqual(inner[0], inner[inner.length - 1]) === false) { |
|
inner.push(inner[0]); |
|
} |
|
|
|
outer.push(inner); |
|
} |
|
|
|
return outer; |
|
} |
|
|
|
function pointsEqual(a, b) { |
|
for (var i = 0; i < a.length; i++) { |
|
|
|
if (a[i] !== b[i]) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function coordinatesEqual(a, b) { |
|
if (a.length !== b.length) { |
|
return false; |
|
} |
|
|
|
var na = a.slice().sort(compSort); |
|
var nb = b.slice().sort(compSort); |
|
|
|
for (var i = 0; i < na.length; i++) { |
|
if (na[i].length !== nb[i].length) { |
|
return false; |
|
} |
|
for (var j = 0; j < na.length; j++) { |
|
if (na[i][j] !== nb[i][j]) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
Internal: An array of variables that will be excluded form JSON objects. |
|
*/ |
|
var excludeFromJSON = ["length"]; |
|
|
|
/* |
|
Internal: Base GeoJSON Primitive |
|
*/ |
|
function Primitive(geojson) { |
|
if (geojson) { |
|
switch (geojson.type) { |
|
case 'Point': |
|
return new Point(geojson); |
|
|
|
case 'MultiPoint': |
|
return new MultiPoint(geojson); |
|
|
|
case 'LineString': |
|
return new LineString(geojson); |
|
|
|
case 'MultiLineString': |
|
return new MultiLineString(geojson); |
|
|
|
case 'Polygon': |
|
return new Polygon(geojson); |
|
|
|
case 'MultiPolygon': |
|
return new MultiPolygon(geojson); |
|
|
|
case 'Feature': |
|
return new Feature(geojson); |
|
|
|
case 'FeatureCollection': |
|
return new FeatureCollection(geojson); |
|
|
|
case 'GeometryCollection': |
|
return new GeometryCollection(geojson); |
|
|
|
default: |
|
throw new Error("Unknown type: " + geojson.type); |
|
} |
|
} |
|
} |
|
|
|
Primitive.prototype.toMercator = function () { |
|
return toMercator(this); |
|
}; |
|
|
|
Primitive.prototype.toGeographic = function () { |
|
return toGeographic(this); |
|
}; |
|
|
|
Primitive.prototype.envelope = function () { |
|
return calculateEnvelope(this); |
|
}; |
|
|
|
Primitive.prototype.bbox = function () { |
|
return calculateBounds(this); |
|
}; |
|
|
|
Primitive.prototype.convexHull = function () { |
|
var coordinates = [], i, j; |
|
if (this.type === 'Point') { |
|
return null; |
|
} else if (this.type === 'LineString' || this.type === 'MultiPoint') { |
|
if (this.coordinates && this.coordinates.length >= 3) { |
|
coordinates = this.coordinates; |
|
} else { |
|
return null; |
|
} |
|
} else if (this.type === 'Polygon' || this.type === 'MultiLineString') { |
|
if (this.coordinates && this.coordinates.length > 0) { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
coordinates = coordinates.concat(this.coordinates[i]); |
|
} |
|
if (coordinates.length < 3) { |
|
return null; |
|
} |
|
} else { |
|
return null; |
|
} |
|
} else if (this.type === 'MultiPolygon') { |
|
if (this.coordinates && this.coordinates.length > 0) { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
for (j = 0; j < this.coordinates[i].length; j++) { |
|
coordinates = coordinates.concat(this.coordinates[i][j]); |
|
} |
|
} |
|
if (coordinates.length < 3) { |
|
return null; |
|
} |
|
} else { |
|
return null; |
|
} |
|
} else if (this.type === "Feature") { |
|
var primitive = new Primitive(this.geometry); |
|
return primitive.convexHull(); |
|
} |
|
|
|
return new Polygon({ |
|
type: 'Polygon', |
|
coordinates: closedPolygon([convexHull(coordinates)]) |
|
}); |
|
}; |
|
|
|
Primitive.prototype.toJSON = function () { |
|
var obj = {}; |
|
for (var key in this) { |
|
if (this.hasOwnProperty(key) && excludeFromJSON.indexOf(key) === -1) { |
|
obj[key] = this[key]; |
|
} |
|
} |
|
obj.bbox = calculateBounds(this); |
|
return obj; |
|
}; |
|
|
|
Primitive.prototype.contains = function (primitive) { |
|
return new Primitive(primitive).within(this); |
|
}; |
|
|
|
Primitive.prototype.within = function (primitive) { |
|
var coordinates, i, j, contains; |
|
|
|
// if we are passed a feature, use the polygon inside instead |
|
if (primitive.type === 'Feature') { |
|
primitive = primitive.geometry; |
|
} |
|
|
|
// point.within(point) :: equality |
|
if (primitive.type === "Point") { |
|
if (this.type === "Point") { |
|
return pointsEqual(this.coordinates, primitive.coordinates); |
|
|
|
} |
|
} |
|
|
|
// point.within(multilinestring) |
|
if (primitive.type === "MultiLineString") { |
|
if (this.type === "Point") { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
var linestring = {type: "LineString", coordinates: primitive.coordinates[i]}; |
|
|
|
if (this.within(linestring)) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// point.within(linestring), point.within(multipoint) |
|
if (primitive.type === "LineString" || primitive.type === "MultiPoint") { |
|
if (this.type === "Point") { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
if (this.coordinates.length !== primitive.coordinates[i].length) { |
|
return false; |
|
} |
|
|
|
if (pointsEqual(this.coordinates, primitive.coordinates[i])) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (primitive.type === "Polygon") { |
|
// polygon.within(polygon) |
|
if (this.type === "Polygon") { |
|
// check for equal polygons |
|
if (primitive.coordinates.length === this.coordinates.length) { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
if (coordinatesEqual(this.coordinates[i], primitive.coordinates[i])) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
if (this.coordinates.length && polygonContainsPoint(primitive.coordinates, this.coordinates[0][0])) { |
|
return !arraysIntersectArrays(closedPolygon(this.coordinates), closedPolygon(primitive.coordinates)); |
|
} else { |
|
return false; |
|
} |
|
|
|
// point.within(polygon) |
|
} else if (this.type === "Point") { |
|
return polygonContainsPoint(primitive.coordinates, this.coordinates); |
|
|
|
// linestring/multipoint withing polygon |
|
} else if (this.type === "LineString" || this.type === "MultiPoint") { |
|
if (!this.coordinates || this.coordinates.length === 0) { |
|
return false; |
|
} |
|
|
|
for (i = 0; i < this.coordinates.length; i++) { |
|
if (polygonContainsPoint(primitive.coordinates, this.coordinates[i]) === false) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
|
|
// multilinestring.within(polygon) |
|
} else if (this.type === "MultiLineString") { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
var ls = new LineString(this.coordinates[i]); |
|
|
|
if (ls.within(primitive) === false) { |
|
contains++; |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
|
|
// multipolygon.within(polygon) |
|
} else if (this.type === "MultiPolygon") { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
var p1 = new Primitive({type: "Polygon", coordinates: this.coordinates[i]}); |
|
|
|
if (p1.within(primitive) === false) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
} |
|
|
|
if (primitive.type === "MultiPolygon") { |
|
// point.within(multipolygon) |
|
if (this.type === "Point") { |
|
if (primitive.coordinates.length) { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
coordinates = primitive.coordinates[i]; |
|
if (polygonContainsPoint(coordinates, this.coordinates) && arraysIntersectArrays([this.coordinates], primitive.coordinates) === false) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
// polygon.within(multipolygon) |
|
} else if (this.type === "Polygon") { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
if (primitive.coordinates[i].length === this.coordinates.length) { |
|
for (j = 0; j < this.coordinates.length; j++) { |
|
if (coordinatesEqual(this.coordinates[j], primitive.coordinates[i][j])) { |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (arraysIntersectArrays(this.coordinates, primitive.coordinates) === false) { |
|
if (primitive.coordinates.length) { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
coordinates = primitive.coordinates[i]; |
|
if (polygonContainsPoint(coordinates, this.coordinates[0][0]) === false) { |
|
contains = false; |
|
} else { |
|
contains = true; |
|
} |
|
} |
|
|
|
return contains; |
|
} |
|
} |
|
|
|
// linestring.within(multipolygon), multipoint.within(multipolygon) |
|
} else if (this.type === "LineString" || this.type === "MultiPoint") { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
var p = {type: "Polygon", coordinates: primitive.coordinates[i]}; |
|
|
|
if (this.within(p)) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// multilinestring.within(multipolygon) |
|
} else if (this.type === "MultiLineString") { |
|
for (i = 0; i < this.coordinates.length; i++) { |
|
var lines = new LineString(this.coordinates[i]); |
|
|
|
if (lines.within(primitive) === false) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
|
|
// multipolygon.within(multipolygon) |
|
} else if (this.type === "MultiPolygon") { |
|
for (i = 0; i < primitive.coordinates.length; i++) { |
|
var mpoly = {type: "Polygon", coordinates: primitive.coordinates[i]}; |
|
|
|
if (this.within(mpoly) === false) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
} |
|
|
|
// default to false |
|
return false; |
|
}; |
|
|
|
Primitive.prototype.intersects = function (primitive) { |
|
// if we are passed a feature, use the polygon inside instead |
|
if (primitive.type === 'Feature') { |
|
primitive = primitive.geometry; |
|
} |
|
|
|
var p = new Primitive(primitive); |
|
if (this.within(primitive) || p.within(this)) { |
|
return true; |
|
} |
|
|
|
|
|
if (this.type !== 'Point' && this.type !== 'MultiPoint' && |
|
primitive.type !== 'Point' && primitive.type !== 'MultiPoint') { |
|
return arraysIntersectArrays(this.coordinates, primitive.coordinates); |
|
} else if (this.type === 'Feature') { |
|
// in the case of a Feature, use the internal primitive for intersection |
|
var inner = new Primitive(this.geometry); |
|
return inner.intersects(primitive); |
|
} |
|
|
|
warn("Type " + this.type + " to " + primitive.type + " intersection is not supported by intersects"); |
|
return false; |
|
}; |
|
|
|
|
|
/* |
|
GeoJSON Point Class |
|
new Point(); |
|
new Point(x,y,z,wtf); |
|
new Point([x,y,z,wtf]); |
|
new Point([x,y]); |
|
new Point({ |
|
type: "Point", |
|
coordinates: [x,y] |
|
}); |
|
*/ |
|
function Point(input) { |
|
var args = Array.prototype.slice.call(arguments); |
|
|
|
if (input && input.type === "Point" && input.coordinates) { |
|
extend(this, input); |
|
} else if (input && isArray(input)) { |
|
this.coordinates = input; |
|
} else if (args.length >= 2) { |
|
this.coordinates = args; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.Point"; |
|
} |
|
|
|
this.type = "Point"; |
|
} |
|
|
|
Point.prototype = new Primitive(); |
|
Point.prototype.constructor = Point; |
|
|
|
/* |
|
GeoJSON MultiPoint Class |
|
new MultiPoint(); |
|
new MultiPoint([[x,y], [x1,y1]]); |
|
new MultiPoint({ |
|
type: "MultiPoint", |
|
coordinates: [x,y] |
|
}); |
|
*/ |
|
function MultiPoint(input) { |
|
if (input && input.type === "MultiPoint" && input.coordinates) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.coordinates = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.MultiPoint"; |
|
} |
|
|
|
this.type = "MultiPoint"; |
|
} |
|
|
|
MultiPoint.prototype = new Primitive(); |
|
MultiPoint.prototype.constructor = MultiPoint; |
|
MultiPoint.prototype.forEach = function (func) { |
|
for (var i = 0; i < this.coordinates.length; i++) { |
|
func.apply(this, [this.coordinates[i], i, this.coordinates]); |
|
} |
|
return this; |
|
}; |
|
MultiPoint.prototype.addPoint = function (point) { |
|
this.coordinates.push(point); |
|
return this; |
|
}; |
|
MultiPoint.prototype.insertPoint = function (point, index) { |
|
this.coordinates.splice(index, 0, point); |
|
return this; |
|
}; |
|
MultiPoint.prototype.removePoint = function (remove) { |
|
if (typeof remove === "number") { |
|
this.coordinates.splice(remove, 1); |
|
} else { |
|
this.coordinates.splice(this.coordinates.indexOf(remove), 1); |
|
} |
|
return this; |
|
}; |
|
MultiPoint.prototype.get = function (i) { |
|
return new Point(this.coordinates[i]); |
|
}; |
|
|
|
/* |
|
GeoJSON LineString Class |
|
new LineString(); |
|
new LineString([[x,y], [x1,y1]]); |
|
new LineString({ |
|
type: "LineString", |
|
coordinates: [x,y] |
|
}); |
|
*/ |
|
function LineString(input) { |
|
if (input && input.type === "LineString" && input.coordinates) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.coordinates = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.LineString"; |
|
} |
|
|
|
this.type = "LineString"; |
|
} |
|
|
|
LineString.prototype = new Primitive(); |
|
LineString.prototype.constructor = LineString; |
|
LineString.prototype.addVertex = function (point) { |
|
this.coordinates.push(point); |
|
return this; |
|
}; |
|
LineString.prototype.insertVertex = function (point, index) { |
|
this.coordinates.splice(index, 0, point); |
|
return this; |
|
}; |
|
LineString.prototype.removeVertex = function (remove) { |
|
this.coordinates.splice(remove, 1); |
|
return this; |
|
}; |
|
|
|
/* |
|
GeoJSON MultiLineString Class |
|
new MultiLineString(); |
|
new MultiLineString([ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ]); |
|
new MultiLineString({ |
|
type: "MultiLineString", |
|
coordinates: [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] |
|
}); |
|
*/ |
|
function MultiLineString(input) { |
|
if (input && input.type === "MultiLineString" && input.coordinates) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.coordinates = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.MultiLineString"; |
|
} |
|
|
|
this.type = "MultiLineString"; |
|
} |
|
|
|
MultiLineString.prototype = new Primitive(); |
|
MultiLineString.prototype.constructor = MultiLineString; |
|
MultiLineString.prototype.forEach = function (func) { |
|
for (var i = 0; i < this.coordinates.length; i++) { |
|
func.apply(this, [this.coordinates[i], i, this.coordinates]); |
|
} |
|
}; |
|
MultiLineString.prototype.get = function (i) { |
|
return new LineString(this.coordinates[i]); |
|
}; |
|
|
|
/* |
|
GeoJSON Polygon Class |
|
new Polygon(); |
|
new Polygon([ [[x,y], [x1,y1], [x2,y2]] ]); |
|
new Polygon({ |
|
type: "Polygon", |
|
coordinates: [ [[x,y], [x1,y1], [x2,y2]] ] |
|
}); |
|
*/ |
|
function Polygon(input) { |
|
if (input && input.type === "Polygon" && input.coordinates) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.coordinates = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.Polygon"; |
|
} |
|
|
|
this.type = "Polygon"; |
|
} |
|
|
|
Polygon.prototype = new Primitive(); |
|
Polygon.prototype.constructor = Polygon; |
|
Polygon.prototype.addVertex = function (point) { |
|
this.insertVertex(point, this.coordinates[0].length - 1); |
|
return this; |
|
}; |
|
Polygon.prototype.insertVertex = function (point, index) { |
|
this.coordinates[0].splice(index, 0, point); |
|
return this; |
|
}; |
|
Polygon.prototype.removeVertex = function (remove) { |
|
this.coordinates[0].splice(remove, 1); |
|
return this; |
|
}; |
|
Polygon.prototype.close = function () { |
|
this.coordinates = closedPolygon(this.coordinates); |
|
}; |
|
Polygon.prototype.hasHoles = function () { |
|
return this.coordinates.length > 1; |
|
}; |
|
Polygon.prototype.holes = function () { |
|
var holes = []; |
|
if (this.hasHoles()) { |
|
for (var i = 1; i < this.coordinates.length; i++) { |
|
holes.push(new Polygon([this.coordinates[i]])); |
|
} |
|
} |
|
return holes; |
|
}; |
|
|
|
/* |
|
GeoJSON MultiPolygon Class |
|
new MultiPolygon(); |
|
new MultiPolygon([ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ]); |
|
new MultiPolygon({ |
|
type: "MultiPolygon", |
|
coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ] |
|
}); |
|
*/ |
|
function MultiPolygon(input) { |
|
if (input && input.type === "MultiPolygon" && input.coordinates) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.coordinates = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.MultiPolygon"; |
|
} |
|
|
|
this.type = "MultiPolygon"; |
|
} |
|
|
|
MultiPolygon.prototype = new Primitive(); |
|
MultiPolygon.prototype.constructor = MultiPolygon; |
|
MultiPolygon.prototype.forEach = function (func) { |
|
for (var i = 0; i < this.coordinates.length; i++) { |
|
func.apply(this, [this.coordinates[i], i, this.coordinates]); |
|
} |
|
}; |
|
MultiPolygon.prototype.get = function (i) { |
|
return new Polygon(this.coordinates[i]); |
|
}; |
|
MultiPolygon.prototype.close = function () { |
|
var outer = []; |
|
this.forEach(function (polygon) { |
|
outer.push(closedPolygon(polygon)); |
|
}); |
|
this.coordinates = outer; |
|
return this; |
|
}; |
|
|
|
/* |
|
GeoJSON Feature Class |
|
new Feature(); |
|
new Feature({ |
|
type: "Feature", |
|
geometry: { |
|
type: "Polygon", |
|
coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ] |
|
} |
|
}); |
|
new Feature({ |
|
type: "Polygon", |
|
coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ] |
|
}); |
|
*/ |
|
function Feature(input) { |
|
if (input && input.type === "Feature") { |
|
extend(this, input); |
|
} else if (input && input.type && input.coordinates) { |
|
this.geometry = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.Feature"; |
|
} |
|
|
|
this.type = "Feature"; |
|
} |
|
|
|
Feature.prototype = new Primitive(); |
|
Feature.prototype.constructor = Feature; |
|
|
|
/* |
|
GeoJSON FeatureCollection Class |
|
new FeatureCollection(); |
|
new FeatureCollection([feature, feature1]); |
|
new FeatureCollection({ |
|
type: "FeatureCollection", |
|
coordinates: [feature, feature1] |
|
}); |
|
*/ |
|
function FeatureCollection(input) { |
|
if (input && input.type === "FeatureCollection" && input.features) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.features = input; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.FeatureCollection"; |
|
} |
|
|
|
this.type = "FeatureCollection"; |
|
} |
|
|
|
FeatureCollection.prototype = new Primitive(); |
|
FeatureCollection.prototype.constructor = FeatureCollection; |
|
FeatureCollection.prototype.forEach = function (func) { |
|
for (var i = 0; i < this.features.length; i++) { |
|
func.apply(this, [this.features[i], i, this.features]); |
|
} |
|
}; |
|
FeatureCollection.prototype.get = function (id) { |
|
var found; |
|
this.forEach(function (feature) { |
|
if (feature.id === id) { |
|
found = feature; |
|
} |
|
}); |
|
return new Feature(found); |
|
}; |
|
|
|
/* |
|
GeoJSON GeometryCollection Class |
|
new GeometryCollection(); |
|
new GeometryCollection([geometry, geometry1]); |
|
new GeometryCollection({ |
|
type: "GeometryCollection", |
|
coordinates: [geometry, geometry1] |
|
}); |
|
*/ |
|
function GeometryCollection(input) { |
|
if (input && input.type === "GeometryCollection" && input.geometries) { |
|
extend(this, input); |
|
} else if (isArray(input)) { |
|
this.geometries = input; |
|
} else if (input.coordinates && input.type) { |
|
this.type = "GeometryCollection"; |
|
this.geometries = [input]; |
|
} else { |
|
throw "Terraformer: invalid input for Terraformer.GeometryCollection"; |
|
} |
|
|
|
this.type = "GeometryCollection"; |
|
} |
|
|
|
GeometryCollection.prototype = new Primitive(); |
|
GeometryCollection.prototype.constructor = GeometryCollection; |
|
GeometryCollection.prototype.forEach = function (func) { |
|
for (var i = 0; i < this.geometries.length; i++) { |
|
func.apply(this, [this.geometries[i], i, this.geometries]); |
|
} |
|
}; |
|
GeometryCollection.prototype.get = function (i) { |
|
return new Primitive(this.geometries[i]); |
|
}; |
|
|
|
function createCircle(center, radius, interpolate) { |
|
var mercatorPosition = positionToMercator(center); |
|
var steps = interpolate || 64; |
|
var polygon = { |
|
type: "Polygon", |
|
coordinates: [[]] |
|
}; |
|
for (var i = 1; i <= steps; i++) { |
|
var radians = i * (360 / steps) * Math.PI / 180; |
|
polygon.coordinates[0].push([mercatorPosition[0] + radius * Math.cos(radians), mercatorPosition[1] + radius * Math.sin(radians)]); |
|
} |
|
polygon.coordinates = closedPolygon(polygon.coordinates); |
|
|
|
return toGeographic(polygon); |
|
} |
|
|
|
function Circle(center, radius, interpolate) { |
|
var steps = interpolate || 64; |
|
var rad = radius || 250; |
|
|
|
if (!center || center.length < 2 || !rad || !steps) { |
|
throw new Error("Terraformer: missing parameter for Terraformer.Circle"); |
|
} |
|
|
|
extend(this, new Feature({ |
|
type: "Feature", |
|
geometry: createCircle(center, rad, steps), |
|
properties: { |
|
radius: rad, |
|
center: center, |
|
steps: steps |
|
} |
|
})); |
|
} |
|
|
|
Circle.prototype = new Primitive(); |
|
Circle.prototype.constructor = Circle; |
|
Circle.prototype.recalculate = function () { |
|
this.geometry = createCircle(this.properties.center, this.properties.radius, this.properties.steps); |
|
return this; |
|
}; |
|
Circle.prototype.center = function (coordinates) { |
|
if (coordinates) { |
|
this.properties.center = coordinates; |
|
this.recalculate(); |
|
} |
|
return this.properties.center; |
|
}; |
|
Circle.prototype.radius = function (radius) { |
|
if (radius) { |
|
this.properties.radius = radius; |
|
this.recalculate(); |
|
} |
|
return this.properties.radius; |
|
}; |
|
Circle.prototype.steps = function (steps) { |
|
if (steps) { |
|
this.properties.steps = steps; |
|
this.recalculate(); |
|
} |
|
return this.properties.steps; |
|
}; |
|
|
|
Circle.prototype.toJSON = function () { |
|
var output = Primitive.prototype.toJSON.call(this); |
|
return output; |
|
}; |
|
|
|
exports.Primitive = Primitive; |
|
exports.Point = Point; |
|
exports.MultiPoint = MultiPoint; |
|
exports.LineString = LineString; |
|
exports.MultiLineString = MultiLineString; |
|
exports.Polygon = Polygon; |
|
exports.MultiPolygon = MultiPolygon; |
|
exports.Feature = Feature; |
|
exports.FeatureCollection = FeatureCollection; |
|
exports.GeometryCollection = GeometryCollection; |
|
exports.Circle = Circle; |
|
|
|
exports.toMercator = toMercator; |
|
exports.toGeographic = toGeographic; |
|
|
|
exports.Tools = {}; |
|
exports.Tools.positionToMercator = positionToMercator; |
|
exports.Tools.positionToGeographic = positionToGeographic; |
|
exports.Tools.applyConverter = applyConverter; |
|
exports.Tools.toMercator = toMercator; |
|
exports.Tools.toGeographic = toGeographic; |
|
exports.Tools.createCircle = createCircle; |
|
|
|
exports.Tools.calculateBounds = calculateBounds; |
|
exports.Tools.calculateEnvelope = calculateEnvelope; |
|
|
|
exports.Tools.coordinatesContainPoint = coordinatesContainPoint; |
|
exports.Tools.polygonContainsPoint = polygonContainsPoint; |
|
exports.Tools.arraysIntersectArrays = arraysIntersectArrays; |
|
exports.Tools.coordinatesContainPoint = coordinatesContainPoint; |
|
exports.Tools.coordinatesEqual = coordinatesEqual; |
|
exports.Tools.convexHull = convexHull; |
|
exports.Tools.isConvex = isConvex; |
|
|
|
exports.MercatorCRS = MercatorCRS; |
|
exports.GeographicCRS = GeographicCRS; |
|
|
|
return exports; |
|
}));
|
|
|