Skip to content

Commit

Permalink
Replace geojson-equality with a fresh implementation that fixes preci…
Browse files Browse the repository at this point in the history
…sion handling.
  • Loading branch information
solarissmoke committed Nov 10, 2023
1 parent ef8b904 commit e3860f4
Show file tree
Hide file tree
Showing 8 changed files with 1,265 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/turf-boolean-equal/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Feature, Geometry } from "geojson";
import GeojsonEquality from "geojson-equality";
import { GeojsonEquality } from "@turf/helpers";
import cleanCoords from "@turf/clean-coords";
import { getGeom } from "@turf/invariant";

Expand Down
2 changes: 0 additions & 2 deletions packages/turf-boolean-equal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"test:tape": "tsx test.js"
},
"devDependencies": {
"@types/geojson-equality": "^0.2.0",
"@types/tape": "*",
"benchmark": "*",
"boolean-shapely": "*",
Expand All @@ -69,7 +68,6 @@
"@turf/clean-coords": "^7.0.0-alpha.2",
"@turf/helpers": "^7.0.0-alpha.2",
"@turf/invariant": "^7.0.0-alpha.2",
"geojson-equality": "0.1.6",
"tslib": "^2.3.0"
}
}
2 changes: 1 addition & 1 deletion packages/turf-boolean-overlap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { segmentEach } from "@turf/meta";
import { getGeom } from "@turf/invariant";
import lineOverlap from "@turf/line-overlap";
import lineIntersect from "@turf/line-intersect";
import GeojsonEquality from "geojson-equality";
import { GeojsonEquality } from "@turf/helpers";

/**
* Compares two geometries of the same dimension and returns true if their intersection set results in a geometry
Expand Down
2 changes: 0 additions & 2 deletions packages/turf-boolean-overlap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
"test:tape": "tsx test.js"
},
"devDependencies": {
"@types/geojson-equality": "^0.2.0",
"@types/tape": "*",
"benchmark": "*",
"boolean-shapely": "*",
Expand All @@ -70,7 +69,6 @@
"@turf/line-intersect": "^7.0.0-alpha.2",
"@turf/line-overlap": "^7.0.0-alpha.2",
"@turf/meta": "^7.0.0-alpha.2",
"geojson-equality": "0.1.6",
"tslib": "^2.3.0"
}
}
1 change: 1 addition & 0 deletions packages/turf-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {

import { Id } from "./lib/geojson";
export * from "./lib/geojson";
export * from "./lib/geojson-equality";

// TurfJS Combined Types
export type Coord = Feature<Point> | Point | Position;
Expand Down
192 changes: 192 additions & 0 deletions packages/turf-helpers/lib/geojson-equality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import {
Feature,
LineString,
Position,
GeoJSON,
Point,
Polygon,
GeometryCollection,
FeatureCollection,
MultiLineString,
MultiPoint,
MultiPolygon,
} from "geojson";
import equal from "deep-equal";

/**
* GeoJSON equality checking utility.
* Adapted from https://github.com/geosquare/geojson-equality
*
* @memberof helpers
* @type {Class}
*/
export class GeojsonEquality {
private precision: number;
private direction = false;

constructor(opts?: { precision?: number; direction?: boolean }) {
this.precision = 10 ** -(opts?.precision ?? 17);
this.direction = opts?.direction ?? false;
}

compare(g1: GeoJSON, g2: GeoJSON): boolean {
if (g1.type !== g2.type) {
return false;
}

if (!sameLength(g1, g2)) {
return false;
}

switch (g1.type) {
case "Point":
return this.compareCoord(g1.coordinates, (g2 as Point).coordinates);
case "LineString":
return this.compareLine(g1.coordinates, (g2 as LineString).coordinates);
case "Polygon":
return this.comparePolygon(g1, g2 as Polygon);
case "GeometryCollection":
return this.compareGeometryCollection(g1, g2 as GeometryCollection);
case "Feature":
return this.compareFeature(g1, g2 as Feature);
case "FeatureCollection":
return this.compareFeatureCollection(g1, g2 as FeatureCollection);
default:
if (g1.type.startsWith("Multi")) {
const g1s = explode(g1);
const g2s = explode(
g2 as MultiLineString | MultiPoint | MultiPolygon
);
return g1s.every((g1part) =>
g2s.some((g2part) => this.compare(g1part as any, g2part as any))
);
}
}
return false;
}

private compareCoord(c1: Position, c2: Position) {
return (
c1.length === c2.length &&
c1.every((c, i) => Math.abs(c - c2[i]) < this.precision)
);
}

private compareLine(
path1: Position[],
path2: Position[],
ind = 0,
isPoly = false
): boolean {
if (!sameLength(path1, path2)) {
return false;
}
const p1 = path1;
let p2 = path2;
if (isPoly && !this.compareCoord(p1[0], p2[0])) {
// fix start index of both to same point
const startIndex = this.fixStartIndex(p2, p1);
if (!startIndex) {
return false;
} else {
p2 = startIndex;
}
}
// for linestring ind =0 and for polygon ind =1
const sameDirection = this.compareCoord(p1[ind], p2[ind]);
if (this.direction || sameDirection) {
return this.comparePath(p1, p2);
} else {
if (this.compareCoord(p1[ind], p2[p2.length - (1 + ind)])) {
return this.comparePath(p1.slice().reverse(), p2);
}
return false;
}
}

private fixStartIndex(sourcePath: Position[], targetPath: Position[]) {
//make sourcePath first point same as of targetPath
let correctPath,
ind = -1;
for (let i = 0; i < sourcePath.length; i++) {
if (this.compareCoord(sourcePath[i], targetPath[0])) {
ind = i;
break;
}
}
if (ind >= 0) {
correctPath = ([] as Position[]).concat(
sourcePath.slice(ind, sourcePath.length),
sourcePath.slice(1, ind + 1)
);
}
return correctPath;
}

private comparePath(p1: Position[], p2: Position[]) {
return p1.every((c, i) => this.compareCoord(c, p2[i]));
}

private comparePolygon(g1: Polygon, g2: Polygon) {
if (this.compareLine(g1.coordinates[0], g2.coordinates[0], 1, true)) {
const holes1 = g1.coordinates.slice(1, g1.coordinates.length);
const holes2 = g2.coordinates.slice(1, g2.coordinates.length);
return holes1.every((h1) =>
holes2.some((h2) => this.compareLine(h1, h2, 1, true))
);
}
return false;
}

private compareGeometryCollection(
g1: GeometryCollection,
g2: GeometryCollection
) {
return (
sameLength(g1.geometries, g2.geometries) &&
this.compareBBox(g1, g2) &&
g1.geometries.every((g, i) => this.compare(g, g2.geometries[i]))
);
}

private compareFeature(g1: Feature, g2: Feature) {
return (
g1.id === g2.id &&
equal(g1.properties, g2.properties) &&
this.compareBBox(g1, g2) &&
this.compare(g1.geometry, g2.geometry)
);
}

private compareFeatureCollection(
g1: FeatureCollection,
g2: FeatureCollection
) {
return (
sameLength(g1.features, g2.features) &&
this.compareBBox(g1, g2) &&
g1.features.every((f, i) => this.compare(f, g2.features[i]))
);
}

private compareBBox(g1: GeoJSON, g2: GeoJSON): boolean {
return (
Boolean(!g1.bbox && !g2.bbox) ||
(g1.bbox && g2.bbox ? this.compareCoord(g1.bbox, g2.bbox) : false)
);
}
}

function sameLength(g1: any, g2: any) {
return g1.coordinates
? g1.coordinates.length === g2.coordinates.length
: g1.length === g2.length;
}

function explode(g: MultiLineString | MultiPoint | MultiPolygon) {
return g.coordinates.map((part) => ({
type: g.type.replace("Multi", ""),
coordinates: part,
}));
}
Loading

0 comments on commit e3860f4

Please sign in to comment.