gcoord
Version:
geographic coordinate library
289 lines (272 loc) • 8.42 kB
text/typescript
import {
GeoJSON,
Feature,
FeatureCollection,
GeometryCollection,
Geometry,
Position,
Point,
LineString,
MultiPoint,
Polygon,
MultiLineString,
MultiPolygon,
} from './geojson';
export function assert(condition: boolean, msg?: string): asserts condition {
if (!condition) throw new Error(msg);
}
/**
* isObject
*
* @param {*} input variable to validate
* @returns {boolean} true/false
* @example
* isObject({elevation: 10})
* //=true
* isObject('foo')
* //=false
*/
export function isObject(input: any): input is object {
return !!input && input.constructor === Object;
}
/**
* isArray
*
* @param {*} input variable to validate
* @returns {boolean} true/false
*/
export function isArray(input: any): input is any[] {
return !!input && Object.prototype.toString.call(input) === '[object Array]';
}
/**
* isNumber
*
* @param {*} num Number to validate
* @returns {boolean} true/false
* @example
* isNumber(123)
* //=true
* isNumber('foo')
* //=false
*/
export function isNumber(input: any): input is number {
return !isNaN(Number(input)) && input !== null && !isArray(input);
}
/**
* isString
*
* @param {*} input variable to validate
* @returns {boolean} true/false
*/
export function isString(input: any): input is string {
return typeof input === 'string';
}
/**
* compose
*/
export function compose(...funcs: Function[]): Function {
const start = funcs.length - 1;
/* eslint-disable func-names */
return function (...args: any[]) {
let i = start;
let result = funcs[start].apply(null, args);
while (i--) result = funcs[i].call(null, result);
return result;
};
}
/**
* Iterate over coordinates in any GeoJSON object, similar to Array.forEach()
* https://github.com/Turfjs/turf/blob/master/packages/turf-meta/index.mjs
*
* @name coordEach
* @param {FeatureCollection|Feature|Geometry} geojson any GeoJSON object
* @param {Function} callback a method that takes (currentCoord, coordIndex, featureIndex, multiFeatureIndex)
* @param {boolean} [excludeWrapCoord=false] whether or not to include the final coordinate of LinearRings that wraps the ring in its iteration.
* @returns {void}
* @example
* let features = featureCollection([
* point([26, 37], {"foo": "bar"}),
* point([36, 53], {"hello": "world"})
* ]);
*
* coordEach(features, function (currentCoord, coordIndex, featureIndex, multiFeatureIndex, geometryIndex) {
* //=currentCoord
* //=coordIndex
* //=featureIndex
* //=multiFeatureIndex
* //=geometryIndex
* });
*/
/* eslint-disable no-param-reassign */
export function coordEach(
geojson: GeoJSON,
callback: (
currentCoord: Position,
coordIndex: number,
featureIndex: number,
multiFeatureIndex: number,
geometryIndex: number
) => any,
excludeWrapCoord = false
): boolean | void | never {
// Handles null Geometry -- Skips this GeoJSON
if (geojson === null) return;
/* eslint-disable-next-line */
let j: number;
let k: number;
let l: number;
let geometry: Geometry | GeometryCollection | null;
let coords: Position | Position[] | Position[][] | Position[][][];
let stopG: number;
let wrapShrink = 0;
let coordIndex = 0;
let geometryMaybeCollection: any;
let isGeometryCollection: boolean;
const { type } = geojson;
const isFeatureCollection = type === 'FeatureCollection';
const isFeature = type === 'Feature';
const stop = isFeatureCollection
? (<FeatureCollection>geojson).features.length
: 1;
// This logic may look a little weird. The reason why it is that way
// is because it's trying to be fast. GeoJSON supports multiple kinds
// of objects at its root: FeatureCollection, Features, Geometries.
// This function has the responsibility of handling all of them, and that
// means that some of the `for` loops you see below actually just don't apply
// to certain inputs. For instance, if you give this just a
// Point geometry, then both loops are short-circuited and all we do
// is gradually rename the input until it's called 'geometry'.
//
// This also aims to allocate as few resources as possible: just a
// few numbers and booleans, rather than any temporary arrays as would
// be required with the normalization approach.
for (let featureIndex = 0; featureIndex < stop; featureIndex++) {
geometryMaybeCollection = isFeatureCollection
? (<FeatureCollection>geojson).features[featureIndex].geometry
: isFeature
? (<Feature>geojson).geometry
: geojson;
isGeometryCollection = geometryMaybeCollection
? geometryMaybeCollection.type === 'GeometryCollection'
: false;
stopG = isGeometryCollection
? (<GeometryCollection>geometryMaybeCollection).geometries.length
: 1;
for (let geomIndex = 0; geomIndex < stopG; geomIndex++) {
let multiFeatureIndex = 0;
let geometryIndex = 0;
geometry = isGeometryCollection
? (<GeometryCollection>geometryMaybeCollection).geometries[geomIndex]
: geometryMaybeCollection;
// Handles null Geometry -- Skips this geometry
if (geometry === null) continue;
const geomType = geometry.type;
wrapShrink =
excludeWrapCoord &&
(geomType === 'Polygon' || geomType === 'MultiPolygon')
? 1
: 0;
switch (geomType) {
case null:
break;
case 'Point':
coords = (<Point>geometry).coordinates;
if (
callback(
coords,
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false
)
return false;
coordIndex++;
multiFeatureIndex++;
break;
case 'LineString':
case 'MultiPoint':
coords = (<LineString | MultiPoint>geometry).coordinates;
for (j = 0; j < coords.length; j++) {
if (
callback(
coords[j],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false
)
return false;
coordIndex++;
if (geomType === 'MultiPoint') multiFeatureIndex++;
}
if (geomType === 'LineString') multiFeatureIndex++;
break;
case 'Polygon':
case 'MultiLineString':
coords = (<Polygon | MultiLineString>geometry).coordinates;
for (j = 0; j < coords.length; j++) {
for (k = 0; k < coords[j].length - wrapShrink; k++) {
if (
callback(
coords[j][k],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false
)
return false;
coordIndex++;
}
if (geomType === 'MultiLineString') multiFeatureIndex++;
if (geomType === 'Polygon') geometryIndex++;
}
if (geomType === 'Polygon') multiFeatureIndex++;
break;
case 'MultiPolygon':
coords = (<MultiPolygon>geometry).coordinates;
for (j = 0; j < coords.length; j++) {
geometryIndex = 0;
for (k = 0; k < coords[j].length; k++) {
for (l = 0; l < coords[j][k].length - wrapShrink; l++) {
if (
callback(
coords[j][k][l],
coordIndex,
featureIndex,
multiFeatureIndex,
geometryIndex
) === false
)
return false;
coordIndex++;
}
geometryIndex++;
}
multiFeatureIndex++;
}
break;
case 'GeometryCollection':
for (
j = 0;
j < (<GeometryCollection>geometry).geometries.length;
j++
) {
if (
coordEach(
(<GeometryCollection>geometry).geometries[j],
callback,
excludeWrapCoord
) === false
)
return false;
}
break;
default:
throw new Error('Unknown Geometry Type');
}
}
}
}