@totemsdk/spatial-proof 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +3 -3
- package/dist/canonical.js +14 -23
- package/dist/cjs/canonical.d.ts +53 -0
- package/dist/cjs/canonical.js +97 -0
- package/dist/cjs/claim.d.ts +34 -0
- package/dist/cjs/claim.js +75 -0
- package/dist/cjs/geometry.d.ts +91 -0
- package/dist/cjs/geometry.js +334 -0
- package/dist/cjs/index.d.ts +31 -0
- package/dist/cjs/index.js +68 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/proof.d.ts +54 -0
- package/dist/cjs/proof.js +189 -0
- package/dist/cjs/proofgraph.d.ts +44 -0
- package/dist/cjs/proofgraph.js +98 -0
- package/dist/cjs/relations.d.ts +21 -0
- package/dist/cjs/relations.js +270 -0
- package/dist/cjs/types.d.ts +126 -0
- package/dist/cjs/types.js +13 -0
- package/dist/claim.js +4 -8
- package/dist/geometry.js +15 -32
- package/dist/index.js +7 -45
- package/dist/proof.js +18 -26
- package/dist/proofgraph.js +15 -21
- package/dist/relations.js +22 -25
- package/dist/types.js +1 -2
- package/package.json +23 -16
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure TypeScript geometry helpers for @totemsdk/spatial-proof.
|
|
3
|
+
*
|
|
4
|
+
* COORDINATE ORDER: [lon, lat] in decimal degrees (GeoJSON / EPSG:4326).
|
|
5
|
+
* All functions are deterministic and dependency-free. Where a calculation is
|
|
6
|
+
* approximate (e.g. planar distance scaling near the poles), the result is
|
|
7
|
+
* clearly labelled in the calling relation's uncertainty notes.
|
|
8
|
+
*
|
|
9
|
+
* No network, no storage, no GDAL, no PostGIS, no map rendering.
|
|
10
|
+
*/
|
|
11
|
+
import type { BoundingBox, Coordinate, GeoGeometry, GeoLineStringGeometry, GeoMultiPolygonGeometry, GeoPolygonGeometry, SpatialObject, SpatialValidationResult } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Validate a single [lon, lat] coordinate.
|
|
14
|
+
* Returns an error if the coordinate is out of range or not finite.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateCoordinate(coord: Coordinate): SpatialValidationResult;
|
|
17
|
+
/**
|
|
18
|
+
* Ring closure: the first and last points must be equal. When a ring is not
|
|
19
|
+
* closed, it is rejected (see normalizePolygonRing for the deterministic
|
|
20
|
+
* normalizer).
|
|
21
|
+
*/
|
|
22
|
+
export declare function isRingClosed(ring: Coordinate[]): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Deterministic normalizer: if the ring is not closed, append a copy of the
|
|
25
|
+
* first point. Idempotent for already-closed rings. The returned array is a
|
|
26
|
+
* fresh copy — the input is never mutated.
|
|
27
|
+
*/
|
|
28
|
+
export declare function normalizePolygonRing(ring: Coordinate[]): Coordinate[];
|
|
29
|
+
/**
|
|
30
|
+
* Deterministic normalizer for a whole polygon: closes every ring.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizePolygon(polygon: GeoPolygonGeometry): GeoPolygonGeometry;
|
|
33
|
+
/**
|
|
34
|
+
* Validate a single geometry.
|
|
35
|
+
*
|
|
36
|
+
* Rules:
|
|
37
|
+
* - Point: 1 valid coordinate
|
|
38
|
+
* - LineString: at least 2 valid coordinates
|
|
39
|
+
* - Polygon: at least 4 points per ring; rings must be closed
|
|
40
|
+
* - MultiPolygon: at least 1 polygon, each valid
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateGeometry(geometry: GeoGeometry): SpatialValidationResult;
|
|
43
|
+
/**
|
|
44
|
+
* Validate a spatial object. CRS defaults to EPSG:4326; any other CRS
|
|
45
|
+
* produces a warning (all geometry math here assumes WGS84 lon/lat).
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateSpatialObject(obj: SpatialObject): SpatialValidationResult;
|
|
48
|
+
/**
|
|
49
|
+
* Compute the axis-aligned bounding box of any geometry. Handles Point,
|
|
50
|
+
* LineString, Polygon, and MultiPolygon (multi-dimensional flattening).
|
|
51
|
+
*/
|
|
52
|
+
export declare function getBoundingBox(geometry: GeoGeometry): BoundingBox;
|
|
53
|
+
/**
|
|
54
|
+
* True when two bounding boxes share any area or edge.
|
|
55
|
+
*/
|
|
56
|
+
export declare function bboxIntersects(a: BoundingBox, b: BoundingBox): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when box `a` fully covers box `b`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function bboxCovers(a: BoundingBox, b: BoundingBox): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Great-circle distance between two [lon, lat] points using the Haversine
|
|
63
|
+
* formula. Approximate (spherical Earth, R = 6371 km).
|
|
64
|
+
*/
|
|
65
|
+
export declare function distanceMeters(a: Coordinate, b: Coordinate): number;
|
|
66
|
+
/**
|
|
67
|
+
* Perpendicular distance from a point to a line segment defined by [a, b].
|
|
68
|
+
* Uses an equirectangular local approximation scaled to meters — accurate for
|
|
69
|
+
* short segments, approximate over large distances or near the poles.
|
|
70
|
+
*/
|
|
71
|
+
export declare function distancePointToSegmentMeters(point: Coordinate, a: Coordinate, b: Coordinate): number;
|
|
72
|
+
/**
|
|
73
|
+
* Minimum distance from a point to a LineString.
|
|
74
|
+
* Approximate: equirectangular scaling applied per segment.
|
|
75
|
+
*/
|
|
76
|
+
export declare function distancePointToLineStringMeters(point: Coordinate, line: GeoLineStringGeometry): number;
|
|
77
|
+
/**
|
|
78
|
+
* Ray-casting point-in-polygon test over the outer ring of a Polygon.
|
|
79
|
+
* Uses a normalized (closed) copy of the ring. Approximate on the lon/lat
|
|
80
|
+
* plane — fine for typical geofences; not geodesic.
|
|
81
|
+
*/
|
|
82
|
+
export declare function pointInPolygon(point: Coordinate, polygon: GeoPolygonGeometry): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Point-in-MultiPolygon: true when the point is inside any of the polygons.
|
|
85
|
+
*/
|
|
86
|
+
export declare function pointInMultiPolygon(point: Coordinate, multi: GeoMultiPolygonGeometry): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* True when a point is within `thresholdM` meters of any boundary ring of a
|
|
89
|
+
* Polygon (outer ring and holes). Uses the equirectangular approximation.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isPointNearBoundary(point: Coordinate, polygon: GeoPolygonGeometry, thresholdM: number): boolean;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure TypeScript geometry helpers for @totemsdk/spatial-proof.
|
|
4
|
+
*
|
|
5
|
+
* COORDINATE ORDER: [lon, lat] in decimal degrees (GeoJSON / EPSG:4326).
|
|
6
|
+
* All functions are deterministic and dependency-free. Where a calculation is
|
|
7
|
+
* approximate (e.g. planar distance scaling near the poles), the result is
|
|
8
|
+
* clearly labelled in the calling relation's uncertainty notes.
|
|
9
|
+
*
|
|
10
|
+
* No network, no storage, no GDAL, no PostGIS, no map rendering.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.validateCoordinate = validateCoordinate;
|
|
14
|
+
exports.isRingClosed = isRingClosed;
|
|
15
|
+
exports.normalizePolygonRing = normalizePolygonRing;
|
|
16
|
+
exports.normalizePolygon = normalizePolygon;
|
|
17
|
+
exports.validateGeometry = validateGeometry;
|
|
18
|
+
exports.validateSpatialObject = validateSpatialObject;
|
|
19
|
+
exports.getBoundingBox = getBoundingBox;
|
|
20
|
+
exports.bboxIntersects = bboxIntersects;
|
|
21
|
+
exports.bboxCovers = bboxCovers;
|
|
22
|
+
exports.distanceMeters = distanceMeters;
|
|
23
|
+
exports.distancePointToSegmentMeters = distancePointToSegmentMeters;
|
|
24
|
+
exports.distancePointToLineStringMeters = distancePointToLineStringMeters;
|
|
25
|
+
exports.pointInPolygon = pointInPolygon;
|
|
26
|
+
exports.pointInMultiPolygon = pointInMultiPolygon;
|
|
27
|
+
exports.isPointNearBoundary = isPointNearBoundary;
|
|
28
|
+
const EARTH_RADIUS_M = 6371000;
|
|
29
|
+
function toRadians(deg) {
|
|
30
|
+
return (deg * Math.PI) / 180;
|
|
31
|
+
}
|
|
32
|
+
/** Meters per degree of latitude (equirectangular approximation). */
|
|
33
|
+
function metersPerDegreeLat() {
|
|
34
|
+
return (Math.PI / 180) * EARTH_RADIUS_M;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Meters per degree of longitude at a given latitude (equirectangular
|
|
38
|
+
* approximation). Approximate and only valid for small distances.
|
|
39
|
+
*/
|
|
40
|
+
function metersPerDegreeLon(latDeg) {
|
|
41
|
+
return Math.cos(toRadians(latDeg)) * metersPerDegreeLat();
|
|
42
|
+
}
|
|
43
|
+
// ── Validation ───────────────────────────────────────────────────────────────
|
|
44
|
+
function ok() {
|
|
45
|
+
return { valid: true, errors: [], warnings: [] };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate a single [lon, lat] coordinate.
|
|
49
|
+
* Returns an error if the coordinate is out of range or not finite.
|
|
50
|
+
*/
|
|
51
|
+
function validateCoordinate(coord) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
const warnings = [];
|
|
54
|
+
if (!Array.isArray(coord) || coord.length !== 2) {
|
|
55
|
+
errors.push('coordinate must be a [lon, lat] pair');
|
|
56
|
+
return { valid: false, errors, warnings };
|
|
57
|
+
}
|
|
58
|
+
const [lon, lat] = coord;
|
|
59
|
+
if (!Number.isFinite(lon)) {
|
|
60
|
+
errors.push('longitude must be finite');
|
|
61
|
+
}
|
|
62
|
+
else if (lon < -180 || lon > 180) {
|
|
63
|
+
errors.push('longitude must be between -180 and 180');
|
|
64
|
+
}
|
|
65
|
+
if (!Number.isFinite(lat)) {
|
|
66
|
+
errors.push('latitude must be finite');
|
|
67
|
+
}
|
|
68
|
+
else if (lat < -90 || lat > 90) {
|
|
69
|
+
errors.push('latitude must be between -90 and 90');
|
|
70
|
+
}
|
|
71
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Ring closure: the first and last points must be equal. When a ring is not
|
|
75
|
+
* closed, it is rejected (see normalizePolygonRing for the deterministic
|
|
76
|
+
* normalizer).
|
|
77
|
+
*/
|
|
78
|
+
function isRingClosed(ring) {
|
|
79
|
+
if (ring.length < 2)
|
|
80
|
+
return false;
|
|
81
|
+
const first = ring[0];
|
|
82
|
+
const last = ring[ring.length - 1];
|
|
83
|
+
return first[0] === last[0] && first[1] === last[1];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Deterministic normalizer: if the ring is not closed, append a copy of the
|
|
87
|
+
* first point. Idempotent for already-closed rings. The returned array is a
|
|
88
|
+
* fresh copy — the input is never mutated.
|
|
89
|
+
*/
|
|
90
|
+
function normalizePolygonRing(ring) {
|
|
91
|
+
if (isRingClosed(ring))
|
|
92
|
+
return [...ring];
|
|
93
|
+
return [...ring.map((c) => [c[0], c[1]]), [ring[0][0], ring[0][1]]];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Deterministic normalizer for a whole polygon: closes every ring.
|
|
97
|
+
*/
|
|
98
|
+
function normalizePolygon(polygon) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'Polygon',
|
|
101
|
+
coordinates: polygon.coordinates.map(normalizePolygonRing),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Validate a single geometry.
|
|
106
|
+
*
|
|
107
|
+
* Rules:
|
|
108
|
+
* - Point: 1 valid coordinate
|
|
109
|
+
* - LineString: at least 2 valid coordinates
|
|
110
|
+
* - Polygon: at least 4 points per ring; rings must be closed
|
|
111
|
+
* - MultiPolygon: at least 1 polygon, each valid
|
|
112
|
+
*/
|
|
113
|
+
function validateGeometry(geometry) {
|
|
114
|
+
const errors = [];
|
|
115
|
+
const warnings = [];
|
|
116
|
+
switch (geometry.type) {
|
|
117
|
+
case 'Point': {
|
|
118
|
+
const point = validateCoordinate(geometry.coordinates);
|
|
119
|
+
errors.push(...point.errors);
|
|
120
|
+
warnings.push(...point.warnings);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'LineString': {
|
|
124
|
+
const coords = geometry.coordinates;
|
|
125
|
+
if (coords.length < 2) {
|
|
126
|
+
errors.push('LineString must have at least 2 points');
|
|
127
|
+
}
|
|
128
|
+
for (const c of coords) {
|
|
129
|
+
const point = validateCoordinate(c);
|
|
130
|
+
errors.push(...point.errors);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'Polygon': {
|
|
135
|
+
const rings = geometry.coordinates;
|
|
136
|
+
if (rings.length === 0) {
|
|
137
|
+
errors.push('Polygon must have at least 1 ring');
|
|
138
|
+
}
|
|
139
|
+
for (const ring of rings) {
|
|
140
|
+
if (ring.length < 4) {
|
|
141
|
+
errors.push('Polygon ring must have at least 4 points including closing point');
|
|
142
|
+
}
|
|
143
|
+
if (!isRingClosed(ring)) {
|
|
144
|
+
errors.push('Polygon ring must be closed (first point must equal last point)');
|
|
145
|
+
}
|
|
146
|
+
for (const c of ring) {
|
|
147
|
+
const point = validateCoordinate(c);
|
|
148
|
+
errors.push(...point.errors);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'MultiPolygon': {
|
|
154
|
+
const polys = geometry.coordinates;
|
|
155
|
+
if (polys.length === 0) {
|
|
156
|
+
errors.push('MultiPolygon must contain at least 1 polygon');
|
|
157
|
+
}
|
|
158
|
+
for (const poly of polys) {
|
|
159
|
+
const sub = validateGeometry({ type: 'Polygon', coordinates: poly });
|
|
160
|
+
errors.push(...sub.errors);
|
|
161
|
+
warnings.push(...sub.warnings);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
errors.push('unknown geometry type: ' + geometry.type);
|
|
167
|
+
}
|
|
168
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate a spatial object. CRS defaults to EPSG:4326; any other CRS
|
|
172
|
+
* produces a warning (all geometry math here assumes WGS84 lon/lat).
|
|
173
|
+
*/
|
|
174
|
+
function validateSpatialObject(obj) {
|
|
175
|
+
const errors = [];
|
|
176
|
+
const warnings = [];
|
|
177
|
+
if (typeof obj.spatialId !== 'string' || obj.spatialId.length === 0) {
|
|
178
|
+
errors.push('spatialId is required');
|
|
179
|
+
}
|
|
180
|
+
if (typeof obj.kind !== 'string' || obj.kind.length === 0) {
|
|
181
|
+
errors.push('kind is required');
|
|
182
|
+
}
|
|
183
|
+
const geo = validateGeometry(obj.geometry);
|
|
184
|
+
errors.push(...geo.errors);
|
|
185
|
+
warnings.push(...geo.warnings);
|
|
186
|
+
const crs = obj.crs ?? 'EPSG:4326';
|
|
187
|
+
if (crs !== 'EPSG:4326') {
|
|
188
|
+
warnings.push(`CRS is ${crs}; geometry math assumes WGS84 (EPSG:4326) [lon, lat]`);
|
|
189
|
+
}
|
|
190
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
191
|
+
}
|
|
192
|
+
// ── Bounding boxes ───────────────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Compute the axis-aligned bounding box of any geometry. Handles Point,
|
|
195
|
+
* LineString, Polygon, and MultiPolygon (multi-dimensional flattening).
|
|
196
|
+
*/
|
|
197
|
+
function getBoundingBox(geometry) {
|
|
198
|
+
const coords = [];
|
|
199
|
+
const collect = (c) => { coords.push(c); };
|
|
200
|
+
switch (geometry.type) {
|
|
201
|
+
case 'Point':
|
|
202
|
+
collect(geometry.coordinates);
|
|
203
|
+
break;
|
|
204
|
+
case 'LineString':
|
|
205
|
+
geometry.coordinates.forEach(collect);
|
|
206
|
+
break;
|
|
207
|
+
case 'Polygon':
|
|
208
|
+
geometry.coordinates.forEach((ring) => ring.forEach(collect));
|
|
209
|
+
break;
|
|
210
|
+
case 'MultiPolygon':
|
|
211
|
+
geometry.coordinates.forEach((poly) => poly.forEach((ring) => ring.forEach(collect)));
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
let minLon = Infinity;
|
|
215
|
+
let minLat = Infinity;
|
|
216
|
+
let maxLon = -Infinity;
|
|
217
|
+
let maxLat = -Infinity;
|
|
218
|
+
for (const [lon, lat] of coords) {
|
|
219
|
+
if (lon < minLon)
|
|
220
|
+
minLon = lon;
|
|
221
|
+
if (lon > maxLon)
|
|
222
|
+
maxLon = lon;
|
|
223
|
+
if (lat < minLat)
|
|
224
|
+
minLat = lat;
|
|
225
|
+
if (lat > maxLat)
|
|
226
|
+
maxLat = lat;
|
|
227
|
+
}
|
|
228
|
+
return { minLon, minLat, maxLon, maxLat };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* True when two bounding boxes share any area or edge.
|
|
232
|
+
*/
|
|
233
|
+
function bboxIntersects(a, b) {
|
|
234
|
+
return !(a.maxLon < b.minLon || b.maxLon < a.minLon || a.maxLat < b.minLat || b.maxLat < a.minLat);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* True when box `a` fully covers box `b`.
|
|
238
|
+
*/
|
|
239
|
+
function bboxCovers(a, b) {
|
|
240
|
+
return a.minLon <= b.minLon && a.maxLon >= b.maxLon && a.minLat <= b.minLat && a.maxLat >= b.maxLat;
|
|
241
|
+
}
|
|
242
|
+
// ── Distance ─────────────────────────────────────────────────────────────────
|
|
243
|
+
/**
|
|
244
|
+
* Great-circle distance between two [lon, lat] points using the Haversine
|
|
245
|
+
* formula. Approximate (spherical Earth, R = 6371 km).
|
|
246
|
+
*/
|
|
247
|
+
function distanceMeters(a, b) {
|
|
248
|
+
const dLat = toRadians(b[1] - a[1]);
|
|
249
|
+
const dLon = toRadians(b[0] - a[0]);
|
|
250
|
+
const lat1 = toRadians(a[1]);
|
|
251
|
+
const lat2 = toRadians(b[1]);
|
|
252
|
+
const h = Math.sin(dLat / 2) ** 2 +
|
|
253
|
+
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
|
254
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Perpendicular distance from a point to a line segment defined by [a, b].
|
|
258
|
+
* Uses an equirectangular local approximation scaled to meters — accurate for
|
|
259
|
+
* short segments, approximate over large distances or near the poles.
|
|
260
|
+
*/
|
|
261
|
+
function distancePointToSegmentMeters(point, a, b) {
|
|
262
|
+
const refLat = (point[1] + a[1] + b[1]) / 3;
|
|
263
|
+
const mPerLon = metersPerDegreeLon(refLat);
|
|
264
|
+
const mPerLat = metersPerDegreeLat();
|
|
265
|
+
const px = point[0] * mPerLon;
|
|
266
|
+
const py = point[1] * mPerLat;
|
|
267
|
+
const ax = a[0] * mPerLon;
|
|
268
|
+
const ay = a[1] * mPerLat;
|
|
269
|
+
const bx = b[0] * mPerLon;
|
|
270
|
+
const by = b[1] * mPerLat;
|
|
271
|
+
const dx = bx - ax;
|
|
272
|
+
const dy = by - ay;
|
|
273
|
+
if (dx === 0 && dy === 0) {
|
|
274
|
+
return Math.hypot(px - ax, py - ay);
|
|
275
|
+
}
|
|
276
|
+
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
|
|
277
|
+
const cx = ax + t * dx;
|
|
278
|
+
const cy = ay + t * dy;
|
|
279
|
+
return Math.hypot(px - cx, py - cy);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Minimum distance from a point to a LineString.
|
|
283
|
+
* Approximate: equirectangular scaling applied per segment.
|
|
284
|
+
*/
|
|
285
|
+
function distancePointToLineStringMeters(point, line) {
|
|
286
|
+
let min = Infinity;
|
|
287
|
+
for (let i = 1; i < line.coordinates.length; i++) {
|
|
288
|
+
const d = distancePointToSegmentMeters(point, line.coordinates[i - 1], line.coordinates[i]);
|
|
289
|
+
if (d < min)
|
|
290
|
+
min = d;
|
|
291
|
+
}
|
|
292
|
+
return min === Infinity ? 0 : min;
|
|
293
|
+
}
|
|
294
|
+
// ── Point-in-polygon ─────────────────────────────────────────────────────────
|
|
295
|
+
/**
|
|
296
|
+
* Ray-casting point-in-polygon test over the outer ring of a Polygon.
|
|
297
|
+
* Uses a normalized (closed) copy of the ring. Approximate on the lon/lat
|
|
298
|
+
* plane — fine for typical geofences; not geodesic.
|
|
299
|
+
*/
|
|
300
|
+
function pointInPolygon(point, polygon) {
|
|
301
|
+
const ring = normalizePolygonRing(polygon.coordinates[0]);
|
|
302
|
+
let inside = false;
|
|
303
|
+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
304
|
+
const [xi, yi] = ring[i];
|
|
305
|
+
const [xj, yj] = ring[j];
|
|
306
|
+
const intersect = yi > point[1] !== yj > point[1] &&
|
|
307
|
+
point[0] < ((xj - xi) * (point[1] - yi)) / (yj - yi) + xi;
|
|
308
|
+
if (intersect)
|
|
309
|
+
inside = !inside;
|
|
310
|
+
}
|
|
311
|
+
return inside;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Point-in-MultiPolygon: true when the point is inside any of the polygons.
|
|
315
|
+
*/
|
|
316
|
+
function pointInMultiPolygon(point, multi) {
|
|
317
|
+
return multi.coordinates.some((poly) => pointInPolygon(point, { type: 'Polygon', coordinates: poly }));
|
|
318
|
+
}
|
|
319
|
+
// ── Boundary distance ────────────────────────────────────────────────────────
|
|
320
|
+
/**
|
|
321
|
+
* True when a point is within `thresholdM` meters of any boundary ring of a
|
|
322
|
+
* Polygon (outer ring and holes). Uses the equirectangular approximation.
|
|
323
|
+
*/
|
|
324
|
+
function isPointNearBoundary(point, polygon, thresholdM) {
|
|
325
|
+
for (const rawRing of polygon.coordinates) {
|
|
326
|
+
const ring = normalizePolygonRing(rawRing);
|
|
327
|
+
for (let i = 1; i < ring.length; i++) {
|
|
328
|
+
const d = distancePointToSegmentMeters(point, ring[i - 1], ring[i]);
|
|
329
|
+
if (d <= thresholdM)
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/spatial-proof
|
|
3
|
+
*
|
|
4
|
+
* Generic geospatial relationship proof primitives for Totem Edge.
|
|
5
|
+
*
|
|
6
|
+
* Answers "how does this proof, device, asset, observation, raster, image,
|
|
7
|
+
* route, or event relate to a known spatial object?" — inside a site boundary,
|
|
8
|
+
* covering a polygon, near a boundary, on a route, overlapping a zone, etc.
|
|
9
|
+
*
|
|
10
|
+
* This is a pure schema/calculation/proof package:
|
|
11
|
+
*
|
|
12
|
+
* geometry + subject + relation + computation metadata
|
|
13
|
+
* → deterministic spatial relation claim
|
|
14
|
+
* → evidence hash
|
|
15
|
+
* → Totem proof envelope
|
|
16
|
+
* → proofgraph linkage
|
|
17
|
+
*
|
|
18
|
+
* No network, no storage, no map rendering, no GDAL, no PostGIS, no external
|
|
19
|
+
* API. All geometry math is deterministic; where a calculation is approximate
|
|
20
|
+
* (e.g. bounding-box-only relations) the uncertainty is explicit in the output.
|
|
21
|
+
*
|
|
22
|
+
* COORDINATE ORDER: [lon, lat] (GeoJSON / EPSG:4326).
|
|
23
|
+
*/
|
|
24
|
+
export type { Coordinate, GeoGeometry, GeoPointGeometry, GeoLineStringGeometry, GeoPolygonGeometry, GeoMultiPolygonGeometry, BoundingBox, SpatialObjectKind, SpatialObject, SpatialRelationType, EngineInfo, SpatialRelationClaimInputs, SpatialRelationClaimResult, SpatialRelationClaim, SpatialValidationResult, EvaluateSpatialRelationParams, CreateSpatialProofParams, SpatialProofVerifyResult, } from './types.js';
|
|
25
|
+
export { canonicalJson, toHex } from './canonical.js';
|
|
26
|
+
export { computeGeometryHash, computeSpatialObjectId, computeSpatialRelationId, hashSpatialObject, hashSpatialRelationClaim, } from './canonical.js';
|
|
27
|
+
export { validateCoordinate, validateGeometry, validateSpatialObject, getBoundingBox, bboxIntersects, bboxCovers, distanceMeters, distancePointToSegmentMeters, distancePointToLineStringMeters, pointInPolygon, pointInMultiPolygon, isPointNearBoundary, isRingClosed, normalizePolygonRing, normalizePolygon, } from './geometry.js';
|
|
28
|
+
export { evaluateSpatialRelation } from './relations.js';
|
|
29
|
+
export { spatialRelationFromLocationClaim, validateSpatialRelationClaim, } from './claim.js';
|
|
30
|
+
export { spatialObjectToEvidenceRef, spatialRelationToEvidenceRef, spatialClaimEvidenceRefs, createUnsignedSpatialProof, signSpatialProof, verifySpatialProof, } from './proof.js';
|
|
31
|
+
export { spatialObjectToProofGraphNode, spatialRelationToProofGraphNode, spatialRelationToGraphEdges, addSpatialRelationToGraph, } from './proofgraph.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module @totemsdk/spatial-proof
|
|
4
|
+
*
|
|
5
|
+
* Generic geospatial relationship proof primitives for Totem Edge.
|
|
6
|
+
*
|
|
7
|
+
* Answers "how does this proof, device, asset, observation, raster, image,
|
|
8
|
+
* route, or event relate to a known spatial object?" — inside a site boundary,
|
|
9
|
+
* covering a polygon, near a boundary, on a route, overlapping a zone, etc.
|
|
10
|
+
*
|
|
11
|
+
* This is a pure schema/calculation/proof package:
|
|
12
|
+
*
|
|
13
|
+
* geometry + subject + relation + computation metadata
|
|
14
|
+
* → deterministic spatial relation claim
|
|
15
|
+
* → evidence hash
|
|
16
|
+
* → Totem proof envelope
|
|
17
|
+
* → proofgraph linkage
|
|
18
|
+
*
|
|
19
|
+
* No network, no storage, no map rendering, no GDAL, no PostGIS, no external
|
|
20
|
+
* API. All geometry math is deterministic; where a calculation is approximate
|
|
21
|
+
* (e.g. bounding-box-only relations) the uncertainty is explicit in the output.
|
|
22
|
+
*
|
|
23
|
+
* COORDINATE ORDER: [lon, lat] (GeoJSON / EPSG:4326).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.addSpatialRelationToGraph = exports.spatialRelationToGraphEdges = exports.spatialRelationToProofGraphNode = exports.spatialObjectToProofGraphNode = exports.verifySpatialProof = exports.signSpatialProof = exports.createUnsignedSpatialProof = exports.spatialClaimEvidenceRefs = exports.spatialRelationToEvidenceRef = exports.spatialObjectToEvidenceRef = exports.validateSpatialRelationClaim = exports.spatialRelationFromLocationClaim = exports.evaluateSpatialRelation = exports.normalizePolygon = exports.normalizePolygonRing = exports.isRingClosed = exports.isPointNearBoundary = exports.pointInMultiPolygon = exports.pointInPolygon = exports.distancePointToLineStringMeters = exports.distancePointToSegmentMeters = exports.distanceMeters = exports.bboxCovers = exports.bboxIntersects = exports.getBoundingBox = exports.validateSpatialObject = exports.validateGeometry = exports.validateCoordinate = exports.hashSpatialRelationClaim = exports.hashSpatialObject = exports.computeSpatialRelationId = exports.computeSpatialObjectId = exports.computeGeometryHash = exports.toHex = exports.canonicalJson = void 0;
|
|
27
|
+
var canonical_js_1 = require("./canonical.js");
|
|
28
|
+
Object.defineProperty(exports, "canonicalJson", { enumerable: true, get: function () { return canonical_js_1.canonicalJson; } });
|
|
29
|
+
Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return canonical_js_1.toHex; } });
|
|
30
|
+
var canonical_js_2 = require("./canonical.js");
|
|
31
|
+
Object.defineProperty(exports, "computeGeometryHash", { enumerable: true, get: function () { return canonical_js_2.computeGeometryHash; } });
|
|
32
|
+
Object.defineProperty(exports, "computeSpatialObjectId", { enumerable: true, get: function () { return canonical_js_2.computeSpatialObjectId; } });
|
|
33
|
+
Object.defineProperty(exports, "computeSpatialRelationId", { enumerable: true, get: function () { return canonical_js_2.computeSpatialRelationId; } });
|
|
34
|
+
Object.defineProperty(exports, "hashSpatialObject", { enumerable: true, get: function () { return canonical_js_2.hashSpatialObject; } });
|
|
35
|
+
Object.defineProperty(exports, "hashSpatialRelationClaim", { enumerable: true, get: function () { return canonical_js_2.hashSpatialRelationClaim; } });
|
|
36
|
+
var geometry_js_1 = require("./geometry.js");
|
|
37
|
+
Object.defineProperty(exports, "validateCoordinate", { enumerable: true, get: function () { return geometry_js_1.validateCoordinate; } });
|
|
38
|
+
Object.defineProperty(exports, "validateGeometry", { enumerable: true, get: function () { return geometry_js_1.validateGeometry; } });
|
|
39
|
+
Object.defineProperty(exports, "validateSpatialObject", { enumerable: true, get: function () { return geometry_js_1.validateSpatialObject; } });
|
|
40
|
+
Object.defineProperty(exports, "getBoundingBox", { enumerable: true, get: function () { return geometry_js_1.getBoundingBox; } });
|
|
41
|
+
Object.defineProperty(exports, "bboxIntersects", { enumerable: true, get: function () { return geometry_js_1.bboxIntersects; } });
|
|
42
|
+
Object.defineProperty(exports, "bboxCovers", { enumerable: true, get: function () { return geometry_js_1.bboxCovers; } });
|
|
43
|
+
Object.defineProperty(exports, "distanceMeters", { enumerable: true, get: function () { return geometry_js_1.distanceMeters; } });
|
|
44
|
+
Object.defineProperty(exports, "distancePointToSegmentMeters", { enumerable: true, get: function () { return geometry_js_1.distancePointToSegmentMeters; } });
|
|
45
|
+
Object.defineProperty(exports, "distancePointToLineStringMeters", { enumerable: true, get: function () { return geometry_js_1.distancePointToLineStringMeters; } });
|
|
46
|
+
Object.defineProperty(exports, "pointInPolygon", { enumerable: true, get: function () { return geometry_js_1.pointInPolygon; } });
|
|
47
|
+
Object.defineProperty(exports, "pointInMultiPolygon", { enumerable: true, get: function () { return geometry_js_1.pointInMultiPolygon; } });
|
|
48
|
+
Object.defineProperty(exports, "isPointNearBoundary", { enumerable: true, get: function () { return geometry_js_1.isPointNearBoundary; } });
|
|
49
|
+
Object.defineProperty(exports, "isRingClosed", { enumerable: true, get: function () { return geometry_js_1.isRingClosed; } });
|
|
50
|
+
Object.defineProperty(exports, "normalizePolygonRing", { enumerable: true, get: function () { return geometry_js_1.normalizePolygonRing; } });
|
|
51
|
+
Object.defineProperty(exports, "normalizePolygon", { enumerable: true, get: function () { return geometry_js_1.normalizePolygon; } });
|
|
52
|
+
var relations_js_1 = require("./relations.js");
|
|
53
|
+
Object.defineProperty(exports, "evaluateSpatialRelation", { enumerable: true, get: function () { return relations_js_1.evaluateSpatialRelation; } });
|
|
54
|
+
var claim_js_1 = require("./claim.js");
|
|
55
|
+
Object.defineProperty(exports, "spatialRelationFromLocationClaim", { enumerable: true, get: function () { return claim_js_1.spatialRelationFromLocationClaim; } });
|
|
56
|
+
Object.defineProperty(exports, "validateSpatialRelationClaim", { enumerable: true, get: function () { return claim_js_1.validateSpatialRelationClaim; } });
|
|
57
|
+
var proof_js_1 = require("./proof.js");
|
|
58
|
+
Object.defineProperty(exports, "spatialObjectToEvidenceRef", { enumerable: true, get: function () { return proof_js_1.spatialObjectToEvidenceRef; } });
|
|
59
|
+
Object.defineProperty(exports, "spatialRelationToEvidenceRef", { enumerable: true, get: function () { return proof_js_1.spatialRelationToEvidenceRef; } });
|
|
60
|
+
Object.defineProperty(exports, "spatialClaimEvidenceRefs", { enumerable: true, get: function () { return proof_js_1.spatialClaimEvidenceRefs; } });
|
|
61
|
+
Object.defineProperty(exports, "createUnsignedSpatialProof", { enumerable: true, get: function () { return proof_js_1.createUnsignedSpatialProof; } });
|
|
62
|
+
Object.defineProperty(exports, "signSpatialProof", { enumerable: true, get: function () { return proof_js_1.signSpatialProof; } });
|
|
63
|
+
Object.defineProperty(exports, "verifySpatialProof", { enumerable: true, get: function () { return proof_js_1.verifySpatialProof; } });
|
|
64
|
+
var proofgraph_js_1 = require("./proofgraph.js");
|
|
65
|
+
Object.defineProperty(exports, "spatialObjectToProofGraphNode", { enumerable: true, get: function () { return proofgraph_js_1.spatialObjectToProofGraphNode; } });
|
|
66
|
+
Object.defineProperty(exports, "spatialRelationToProofGraphNode", { enumerable: true, get: function () { return proofgraph_js_1.spatialRelationToProofGraphNode; } });
|
|
67
|
+
Object.defineProperty(exports, "spatialRelationToGraphEdges", { enumerable: true, get: function () { return proofgraph_js_1.spatialRelationToGraphEdges; } });
|
|
68
|
+
Object.defineProperty(exports, "addSpatialRelationToGraph", { enumerable: true, get: function () { return proofgraph_js_1.addSpatialRelationToGraph; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof envelope integration for @totemsdk/spatial-proof.
|
|
3
|
+
*
|
|
4
|
+
* Bridges spatial objects and spatial relation claims into @totemsdk/proof
|
|
5
|
+
* evidence refs and signed proof envelopes. Signing/verification delegate to
|
|
6
|
+
* @totemsdk/proof. Anchoring is deliberately NOT required for verification.
|
|
7
|
+
*/
|
|
8
|
+
import type { EvidenceRef, UnsignedProof, SignedProof } from '@totemsdk/proof';
|
|
9
|
+
import type { CreateSpatialProofParams, GeoGeometry, SpatialObject, SpatialProofVerifyResult, SpatialRelationClaim } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Convert a SpatialObject into an EvidenceRef for inclusion in a proof.
|
|
12
|
+
*/
|
|
13
|
+
export declare function spatialObjectToEvidenceRef(obj: SpatialObject): EvidenceRef;
|
|
14
|
+
/**
|
|
15
|
+
* Convert a SpatialRelationClaim into an EvidenceRef for inclusion in a proof.
|
|
16
|
+
*/
|
|
17
|
+
export declare function spatialRelationToEvidenceRef(claim: SpatialRelationClaim): EvidenceRef;
|
|
18
|
+
/**
|
|
19
|
+
* Build the evidence ref list for a spatial proof.
|
|
20
|
+
*
|
|
21
|
+
* Always includes the relation claim hash and spatial object hash. Optionally
|
|
22
|
+
* adds the subject geometry hash, subject proof ID, location claim ID, and
|
|
23
|
+
* raster manifest ID when present on the claim.
|
|
24
|
+
*/
|
|
25
|
+
export declare function spatialClaimEvidenceRefs(claim: SpatialRelationClaim, obj: SpatialObject, subjectGeometry?: GeoGeometry): EvidenceRef[];
|
|
26
|
+
/**
|
|
27
|
+
* Create an unsigned attestation proof for a spatial relation claim.
|
|
28
|
+
*
|
|
29
|
+
* The proof claims: "this subject relates to this spatial object in this way
|
|
30
|
+
* at this time, computed by this engine." It does NOT claim the relation is
|
|
31
|
+
* geodetically exact — approximation notes travel inside the claim result.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createUnsignedSpatialProof(params: CreateSpatialProofParams): UnsignedProof;
|
|
34
|
+
/**
|
|
35
|
+
* Sign an unsigned spatial proof with a WOTS key.
|
|
36
|
+
*
|
|
37
|
+
* The caller is responsible for reserving the WOTS key index (see
|
|
38
|
+
* @totemsdk/wots-lease) before calling — one-time key warning applies.
|
|
39
|
+
*/
|
|
40
|
+
export declare function signSpatialProof(unsigned: UnsignedProof, seed: Uint8Array, keyIndex: number): SignedProof;
|
|
41
|
+
/**
|
|
42
|
+
* Verify a signed spatial proof end to end.
|
|
43
|
+
*
|
|
44
|
+
* Checks:
|
|
45
|
+
* 1. the underlying @totemsdk/proof verification (signature, proofId, expiry)
|
|
46
|
+
* 2. payload contains a structurally valid SpatialRelationClaim
|
|
47
|
+
* 3. the relation's claimId matches a recomputation from its fields
|
|
48
|
+
* 4. the relation evidence hash matches the payload claim
|
|
49
|
+
* 5. the spatial-object evidence hash matches the payload claim inputs
|
|
50
|
+
* 6. any subject geometry evidence hash is present when claimed
|
|
51
|
+
*
|
|
52
|
+
* Anchoring is not required.
|
|
53
|
+
*/
|
|
54
|
+
export declare function verifySpatialProof(signed: SignedProof): SpatialProofVerifyResult;
|