UNPKG

18.8 kBJavaScriptView Raw
1/**
2 * Wraps a GeoJSON {@link Geometry} in a GeoJSON {@link Feature}.
3 *
4 * @name feature
5 * @param {Geometry} geometry input geometry
6 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
7 * @param {Array<number>} [bbox] BBox [west, south, east, north]
8 * @param {string|number} [id] Identifier
9 * @returns {Feature} a GeoJSON Feature
10 * @example
11 * var geometry = {
12 * "type": "Point",
13 * "coordinates": [110, 50]
14 * };
15 *
16 * var feature = turf.feature(geometry);
17 *
18 * //=feature
19 */
20function feature(geometry, properties, bbox, id) {
21 if (geometry === undefined) throw new Error('geometry is required');
22 if (properties && properties.constructor !== Object) throw new Error('properties must be an Object');
23 if (bbox && bbox.length !== 4) throw new Error('bbox must be an Array of 4 numbers');
24 if (id && ['string', 'number'].indexOf(typeof id) === -1) throw new Error('id must be a number or a string');
25
26 var feat = {type: 'Feature'};
27 if (id) feat.id = id;
28 if (bbox) feat.bbox = bbox;
29 feat.properties = properties || {};
30 feat.geometry = geometry;
31 return feat;
32}
33
34/**
35 * Creates a GeoJSON {@link Geometry} from a Geometry string type & coordinates.
36 * For GeometryCollection type use `helpers.geometryCollection`
37 *
38 * @name geometry
39 * @param {string} type Geometry Type
40 * @param {Array<number>} coordinates Coordinates
41 * @param {Array<number>} [bbox] BBox [west, south, east, north]
42 * @returns {Geometry} a GeoJSON Geometry
43 * @example
44 * var type = 'Point';
45 * var coordinates = [110, 50];
46 *
47 * var geometry = turf.geometry(type, coordinates);
48 *
49 * //=geometry
50 */
51function geometry(type, coordinates, bbox) {
52 // Validation
53 if (!type) throw new Error('type is required');
54 if (!coordinates) throw new Error('coordinates is required');
55 if (!Array.isArray(coordinates)) throw new Error('coordinates must be an Array');
56 if (bbox && bbox.length !== 4) throw new Error('bbox must be an Array of 4 numbers');
57
58 var geom;
59 switch (type) {
60 case 'Point': geom = point(coordinates).geometry; break;
61 case 'LineString': geom = lineString(coordinates).geometry; break;
62 case 'Polygon': geom = polygon(coordinates).geometry; break;
63 case 'MultiPoint': geom = multiPoint(coordinates).geometry; break;
64 case 'MultiLineString': geom = multiLineString(coordinates).geometry; break;
65 case 'MultiPolygon': geom = multiPolygon(coordinates).geometry; break;
66 default: throw new Error(type + ' is invalid');
67 }
68 if (bbox) geom.bbox = bbox;
69 return geom;
70}
71
72/**
73 * Takes coordinates and properties (optional) and returns a new {@link Point} feature.
74 *
75 * @name point
76 * @param {Array<number>} coordinates longitude, latitude position (each in decimal degrees)
77 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
78 * @param {Array<number>} [bbox] BBox [west, south, east, north]
79 * @param {string|number} [id] Identifier
80 * @returns {Feature<Point>} a Point feature
81 * @example
82 * var point = turf.point([-75.343, 39.984]);
83 *
84 * //=point
85 */
86function point(coordinates, properties, bbox, id) {
87 if (!coordinates) throw new Error('No coordinates passed');
88 if (coordinates.length === undefined) throw new Error('Coordinates must be an array');
89 if (coordinates.length < 2) throw new Error('Coordinates must be at least 2 numbers long');
90 if (!isNumber(coordinates[0]) || !isNumber(coordinates[1])) throw new Error('Coordinates must contain numbers');
91
92 return feature({
93 type: 'Point',
94 coordinates: coordinates
95 }, properties, bbox, id);
96}
97
98/**
99 * Takes an array of LinearRings and optionally an {@link Object} with properties and returns a {@link Polygon} feature.
100 *
101 * @name polygon
102 * @param {Array<Array<Array<number>>>} coordinates an array of LinearRings
103 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
104 * @param {Array<number>} [bbox] BBox [west, south, east, north]
105 * @param {string|number} [id] Identifier
106 * @returns {Feature<Polygon>} a Polygon feature
107 * @throws {Error} throw an error if a LinearRing of the polygon has too few positions
108 * or if a LinearRing of the Polygon does not have matching Positions at the beginning & end.
109 * @example
110 * var polygon = turf.polygon([[
111 * [-2.275543, 53.464547],
112 * [-2.275543, 53.489271],
113 * [-2.215118, 53.489271],
114 * [-2.215118, 53.464547],
115 * [-2.275543, 53.464547]
116 * ]], { name: 'poly1', population: 400});
117 *
118 * //=polygon
119 */
120function polygon(coordinates, properties, bbox, id) {
121 if (!coordinates) throw new Error('No coordinates passed');
122
123 for (var i = 0; i < coordinates.length; i++) {
124 var ring = coordinates[i];
125 if (ring.length < 4) {
126 throw new Error('Each LinearRing of a Polygon must have 4 or more Positions.');
127 }
128 for (var j = 0; j < ring[ring.length - 1].length; j++) {
129 // Check if first point of Polygon contains two numbers
130 if (i === 0 && j === 0 && !isNumber(ring[0][0]) || !isNumber(ring[0][1])) throw new Error('Coordinates must contain numbers');
131 if (ring[ring.length - 1][j] !== ring[0][j]) {
132 throw new Error('First and last Position are not equivalent.');
133 }
134 }
135 }
136
137 return feature({
138 type: 'Polygon',
139 coordinates: coordinates
140 }, properties, bbox, id);
141}
142
143/**
144 * Creates a {@link LineString} based on a
145 * coordinate array. Properties can be added optionally.
146 *
147 * @name lineString
148 * @param {Array<Array<number>>} coordinates an array of Positions
149 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
150 * @param {Array<number>} [bbox] BBox [west, south, east, north]
151 * @param {string|number} [id] Identifier
152 * @returns {Feature<LineString>} a LineString feature
153 * @throws {Error} if no coordinates are passed
154 * @example
155 * var linestring1 = turf.lineString([
156 * [-21.964416, 64.148203],
157 * [-21.956176, 64.141316],
158 * [-21.93901, 64.135924],
159 * [-21.927337, 64.136673]
160 * ]);
161 * var linestring2 = turf.lineString([
162 * [-21.929054, 64.127985],
163 * [-21.912918, 64.134726],
164 * [-21.916007, 64.141016],
165 * [-21.930084, 64.14446]
166 * ], {name: 'line 1', distance: 145});
167 *
168 * //=linestring1
169 *
170 * //=linestring2
171 */
172function lineString(coordinates, properties, bbox, id) {
173 if (!coordinates) throw new Error('No coordinates passed');
174 if (coordinates.length < 2) throw new Error('Coordinates must be an array of two or more positions');
175 // Check if first point of LineString contains two numbers
176 if (!isNumber(coordinates[0][1]) || !isNumber(coordinates[0][1])) throw new Error('Coordinates must contain numbers');
177
178 return feature({
179 type: 'LineString',
180 coordinates: coordinates
181 }, properties, bbox, id);
182}
183
184/**
185 * Takes one or more {@link Feature|Features} and creates a {@link FeatureCollection}.
186 *
187 * @name featureCollection
188 * @param {Feature[]} features input features
189 * @param {Array<number>} [bbox] BBox [west, south, east, north]
190 * @param {string|number} [id] Identifier
191 * @returns {FeatureCollection} a FeatureCollection of input features
192 * @example
193 * var features = [
194 * turf.point([-75.343, 39.984], {name: 'Location A'}),
195 * turf.point([-75.833, 39.284], {name: 'Location B'}),
196 * turf.point([-75.534, 39.123], {name: 'Location C'})
197 * ];
198 *
199 * var collection = turf.featureCollection(features);
200 *
201 * //=collection
202 */
203function featureCollection(features, bbox, id) {
204 if (!features) throw new Error('No features passed');
205 if (!Array.isArray(features)) throw new Error('features must be an Array');
206 if (bbox && bbox.length !== 4) throw new Error('bbox must be an Array of 4 numbers');
207 if (id && ['string', 'number'].indexOf(typeof id) === -1) throw new Error('id must be a number or a string');
208
209 var fc = {type: 'FeatureCollection'};
210 if (id) fc.id = id;
211 if (bbox) fc.bbox = bbox;
212 fc.features = features;
213 return fc;
214}
215
216/**
217 * Creates a {@link Feature<MultiLineString>} based on a
218 * coordinate array. Properties can be added optionally.
219 *
220 * @name multiLineString
221 * @param {Array<Array<Array<number>>>} coordinates an array of LineStrings
222 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
223 * @param {Array<number>} [bbox] BBox [west, south, east, north]
224 * @param {string|number} [id] Identifier
225 * @returns {Feature<MultiLineString>} a MultiLineString feature
226 * @throws {Error} if no coordinates are passed
227 * @example
228 * var multiLine = turf.multiLineString([[[0,0],[10,10]]]);
229 *
230 * //=multiLine
231 */
232function multiLineString(coordinates, properties, bbox, id) {
233 if (!coordinates) throw new Error('No coordinates passed');
234
235 return feature({
236 type: 'MultiLineString',
237 coordinates: coordinates
238 }, properties, bbox, id);
239}
240
241/**
242 * Creates a {@link Feature<MultiPoint>} based on a
243 * coordinate array. Properties can be added optionally.
244 *
245 * @name multiPoint
246 * @param {Array<Array<number>>} coordinates an array of Positions
247 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
248 * @param {Array<number>} [bbox] BBox [west, south, east, north]
249 * @param {string|number} [id] Identifier
250 * @returns {Feature<MultiPoint>} a MultiPoint feature
251 * @throws {Error} if no coordinates are passed
252 * @example
253 * var multiPt = turf.multiPoint([[0,0],[10,10]]);
254 *
255 * //=multiPt
256 */
257function multiPoint(coordinates, properties, bbox, id) {
258 if (!coordinates) throw new Error('No coordinates passed');
259
260 return feature({
261 type: 'MultiPoint',
262 coordinates: coordinates
263 }, properties, bbox, id);
264}
265
266/**
267 * Creates a {@link Feature<MultiPolygon>} based on a
268 * coordinate array. Properties can be added optionally.
269 *
270 * @name multiPolygon
271 * @param {Array<Array<Array<Array<number>>>>} coordinates an array of Polygons
272 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
273 * @param {Array<number>} [bbox] BBox [west, south, east, north]
274 * @param {string|number} [id] Identifier
275 * @returns {Feature<MultiPolygon>} a multipolygon feature
276 * @throws {Error} if no coordinates are passed
277 * @example
278 * var multiPoly = turf.multiPolygon([[[[0,0],[0,10],[10,10],[10,0],[0,0]]]]);
279 *
280 * //=multiPoly
281 *
282 */
283function multiPolygon(coordinates, properties, bbox, id) {
284 if (!coordinates) throw new Error('No coordinates passed');
285
286 return feature({
287 type: 'MultiPolygon',
288 coordinates: coordinates
289 }, properties, bbox, id);
290}
291
292/**
293 * Creates a {@link Feature<GeometryCollection>} based on a
294 * coordinate array. Properties can be added optionally.
295 *
296 * @name geometryCollection
297 * @param {Array<Geometry>} geometries an array of GeoJSON Geometries
298 * @param {Object} [properties={}] an Object of key-value pairs to add as properties
299 * @param {Array<number>} [bbox] BBox [west, south, east, north]
300 * @param {string|number} [id] Identifier
301 * @returns {Feature<GeometryCollection>} a GeoJSON GeometryCollection Feature
302 * @example
303 * var pt = {
304 * "type": "Point",
305 * "coordinates": [100, 0]
306 * };
307 * var line = {
308 * "type": "LineString",
309 * "coordinates": [ [101, 0], [102, 1] ]
310 * };
311 * var collection = turf.geometryCollection([pt, line]);
312 *
313 * //=collection
314 */
315function geometryCollection(geometries, properties, bbox, id) {
316 if (!geometries) throw new Error('geometries is required');
317 if (!Array.isArray(geometries)) throw new Error('geometries must be an Array');
318
319 return feature({
320 type: 'GeometryCollection',
321 geometries: geometries
322 }, properties, bbox, id);
323}
324
325// https://en.wikipedia.org/wiki/Great-circle_distance#Radius_for_spherical_Earth
326var factors = {
327 miles: 3960,
328 nauticalmiles: 3441.145,
329 degrees: 57.2957795,
330 radians: 1,
331 inches: 250905600,
332 yards: 6969600,
333 meters: 6373000,
334 metres: 6373000,
335 centimeters: 6.373e+8,
336 centimetres: 6.373e+8,
337 kilometers: 6373,
338 kilometres: 6373,
339 feet: 20908792.65
340};
341
342var areaFactors = {
343 kilometers: 0.000001,
344 kilometres: 0.000001,
345 meters: 1,
346 metres: 1,
347 centimetres: 10000,
348 millimeter: 1000000,
349 acres: 0.000247105,
350 miles: 3.86e-7,
351 yards: 1.195990046,
352 feet: 10.763910417,
353 inches: 1550.003100006
354};
355/**
356 * Round number to precision
357 *
358 * @param {number} num Number
359 * @param {number} [precision=0] Precision
360 * @returns {number} rounded number
361 * @example
362 * turf.round(120.4321)
363 * //=120
364 *
365 * turf.round(120.4321, 2)
366 * //=120.43
367 */
368function round(num, precision) {
369 if (num === undefined || num === null || isNaN(num)) throw new Error('num is required');
370 if (precision && !(precision >= 0)) throw new Error('precision must be a positive number');
371 var multiplier = Math.pow(10, precision || 0);
372 return Math.round(num * multiplier) / multiplier;
373}
374
375/**
376 * Convert a distance measurement (assuming a spherical Earth) from radians to a more friendly unit.
377 * Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
378 *
379 * @name radiansToDistance
380 * @param {number} radians in radians across the sphere
381 * @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
382 * @returns {number} distance
383 */
384function radiansToDistance(radians, units) {
385 if (radians === undefined || radians === null) throw new Error('radians is required');
386
387 var factor = factors[units || 'kilometers'];
388 if (!factor) throw new Error('units is invalid');
389 return radians * factor;
390}
391
392/**
393 * Convert a distance measurement (assuming a spherical Earth) from a real-world unit into radians
394 * Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
395 *
396 * @name distanceToRadians
397 * @param {number} distance in real units
398 * @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
399 * @returns {number} radians
400 */
401function distanceToRadians(distance, units) {
402 if (distance === undefined || distance === null) throw new Error('distance is required');
403
404 var factor = factors[units || 'kilometers'];
405 if (!factor) throw new Error('units is invalid');
406 return distance / factor;
407}
408
409/**
410 * Convert a distance measurement (assuming a spherical Earth) from a real-world unit into degrees
411 * Valid units: miles, nauticalmiles, inches, yards, meters, metres, centimeters, kilometres, feet
412 *
413 * @name distanceToDegrees
414 * @param {number} distance in real units
415 * @param {string} [units=kilometers] can be degrees, radians, miles, or kilometers inches, yards, metres, meters, kilometres, kilometers.
416 * @returns {number} degrees
417 */
418function distanceToDegrees(distance, units) {
419 return radians2degrees(distanceToRadians(distance, units));
420}
421
422/**
423 * Converts any bearing angle from the north line direction (positive clockwise)
424 * and returns an angle between 0-360 degrees (positive clockwise), 0 being the north line
425 *
426 * @name bearingToAngle
427 * @param {number} bearing angle, between -180 and +180 degrees
428 * @returns {number} angle between 0 and 360 degrees
429 */
430function bearingToAngle(bearing) {
431 if (bearing === null || bearing === undefined) throw new Error('bearing is required');
432
433 var angle = bearing % 360;
434 if (angle < 0) angle += 360;
435 return angle;
436}
437
438/**
439 * Converts an angle in radians to degrees
440 *
441 * @name radians2degrees
442 * @param {number} radians angle in radians
443 * @returns {number} degrees between 0 and 360 degrees
444 */
445function radians2degrees(radians) {
446 if (radians === null || radians === undefined) throw new Error('radians is required');
447
448 var degrees = radians % (2 * Math.PI);
449 return degrees * 180 / Math.PI;
450}
451
452/**
453 * Converts an angle in degrees to radians
454 *
455 * @name degrees2radians
456 * @param {number} degrees angle between 0 and 360 degrees
457 * @returns {number} angle in radians
458 */
459function degrees2radians(degrees) {
460 if (degrees === null || degrees === undefined) throw new Error('degrees is required');
461
462 var radians = degrees % 360;
463 return radians * Math.PI / 180;
464}
465
466
467/**
468 * Converts a distance to the requested unit.
469 * Valid units: miles, nauticalmiles, inches, yards, meters, metres, kilometers, centimeters, feet
470 *
471 * @param {number} distance to be converted
472 * @param {string} originalUnit of the distance
473 * @param {string} [finalUnit=kilometers] returned unit
474 * @returns {number} the converted distance
475 */
476function convertDistance(distance, originalUnit, finalUnit) {
477 if (distance === null || distance === undefined) throw new Error('distance is required');
478 if (!(distance >= 0)) throw new Error('distance must be a positive number');
479
480 var convertedDistance = radiansToDistance(distanceToRadians(distance, originalUnit), finalUnit || 'kilometers');
481 return convertedDistance;
482}
483
484/**
485 * Converts a area to the requested unit.
486 * Valid units: kilometers, kilometres, meters, metres, centimetres, millimeter, acre, mile, yard, foot, inch
487 * @param {number} area to be converted
488 * @param {string} [originalUnit=meters] of the distance
489 * @param {string} [finalUnit=kilometers] returned unit
490 * @returns {number} the converted distance
491 */
492function convertArea(area, originalUnit, finalUnit) {
493 if (area === null || area === undefined) throw new Error('area is required');
494 if (!(area >= 0)) throw new Error('area must be a positive number');
495
496 var startFactor = areaFactors[originalUnit || 'meters'];
497 if (!startFactor) throw new Error('invalid original units');
498
499 var finalFactor = areaFactors[finalUnit || 'kilometers'];
500 if (!finalFactor) throw new Error('invalid final units');
501
502 return (area / startFactor) * finalFactor;
503}
504
505/**
506 * isNumber
507 *
508 * @param {*} num Number to validate
509 * @returns {boolean} true/false
510 * @example
511 * turf.isNumber(123)
512 * //=true
513 * turf.isNumber('foo')
514 * //=false
515 */
516function isNumber(num) {
517 return !isNaN(num) && num !== null && !Array.isArray(num);
518}
519
520module.exports = {
521 feature: feature,
522 geometry: geometry,
523 featureCollection: featureCollection,
524 geometryCollection: geometryCollection,
525 point: point,
526 multiPoint: multiPoint,
527 lineString: lineString,
528 multiLineString: multiLineString,
529 polygon: polygon,
530 multiPolygon: multiPolygon,
531 radiansToDistance: radiansToDistance,
532 distanceToRadians: distanceToRadians,
533 distanceToDegrees: distanceToDegrees,
534 radians2degrees: radians2degrees,
535 degrees2radians: degrees2radians,
536 bearingToAngle: bearingToAngle,
537 convertDistance: convertDistance,
538 convertArea: convertArea,
539 round: round,
540 isNumber: isNumber
541};