UNPKG

geotoolbox

Version:

geotoolbox is GIS javascript library. It is based on d3geo, topojson and geos-wasm.

152 lines (139 loc) 4.62 kB
import { geoContains, geoArea, geoStream, geoTransform } from "d3-geo"; import { check } from "./helpers/check.js"; /** * @function rewind * @summary Rewind a geoJSON (fil recipe). The function allows to rewind the winding order of a GeoJSON object. The winding order of a polygon is the order in which the vertices are visited by the path that defines the polygon. The winding order of a polygon is significant because it determines the interior of the polygon. The winding order of a polygon is typically either clockwise or counterclockwise. * @description Based on https://observablehq.com/@fil/rewind * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry. * @param {object} options - Optional parameters * @param {number} [options.simple = true] - Rewind simple polygons larger than a hemisphere * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`) * @example * geotoolbox.rewind(*a geojson*) */ export function rewind(data, { simple = true } = {}) { const handle = check(data); let x = handle.import(data); let result = x?.stream ? geoRewindProjection(x, simple) : x?.type ? geoRewindFeature(x, simple) : Array.isArray(x) ? Array.from(x, (d) => rewind(d, simple)) : x; return handle.export(result); } const geoRewindFeature = (feature, simple) => geoProjectSimple(feature, geoRewindStream(simple)); function geoRewindStream(simple = true) { let ring, polygon; return geoTransform({ polygonStart() { this.stream.polygonStart(); polygon = []; }, lineStart() { if (polygon) polygon.push((ring = [])); else this.stream.lineStart(); }, lineEnd() { if (!polygon) this.stream.lineEnd(); }, point(x, y) { if (polygon) ring.push([x, y]); else this.stream.point(x, y); }, polygonEnd() { for (let [i, ring] of polygon.entries()) { ring.push(ring[0].slice()); if ( i ? // a hole must contain the first point of the polygon !geoContains( { type: "Polygon", coordinates: [ring] }, polygon[0][0] ) : polygon[1] ? // the outer ring must contain the first point of its first hole (if any) !geoContains( { type: "Polygon", coordinates: [ring] }, polygon[1][0] ) : // a single ring polygon must be smaller than a hemisphere (optional) simple && geoArea({ type: "Polygon", coordinates: [ring] }) > 2 * Math.PI ) { ring.reverse(); } this.stream.lineStart(); ring.pop(); for (const [x, y] of ring) this.stream.point(x, y); this.stream.lineEnd(); } this.stream.polygonEnd(); polygon = null; }, }); } const geoProjectSimple = function (object, projection) { const stream = projection.stream; let project; if (!stream) throw new Error("invalid projection"); switch (object && object.type) { case "Feature": project = projectFeature; break; case "FeatureCollection": project = projectFeatureCollection; break; default: project = projectGeometry; break; } return project(object, stream); }; function projectFeatureCollection(o, stream) { return { ...o, features: o.features.map((f) => projectFeature(f, stream)) }; } function projectFeature(o, stream) { return { ...o, geometry: projectGeometry(o.geometry, stream) }; } function projectGeometryCollection(o, stream) { return { ...o, geometries: o.geometries.map((o) => projectGeometry(o, stream)), }; } function projectGeometry(o, stream) { return !o ? null : o.type === "GeometryCollection" ? projectGeometryCollection(o, stream) : o.type === "Polygon" || o.type === "MultiPolygon" ? projectPolygons(o, stream) : o; } function projectPolygons(o, stream) { let coordinates = []; let polygon, line; geoStream( o, stream({ polygonStart() { coordinates.push((polygon = [])); }, polygonEnd() {}, lineStart() { polygon.push((line = [])); }, lineEnd() { line.push(line[0].slice()); }, point(x, y) { line.push([x, y]); }, }) ); if (o.type === "Polygon") coordinates = coordinates[0]; return { ...o, coordinates }; }