@trackunit/geo-json-utils 1.14.30 → 1.14.32

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/index.cjs.js CHANGED
@@ -1256,6 +1256,202 @@ const getGeoJsonPolygonIntersection = (polygon1, polygon2) => {
1256
1256
  coordinates: intersectionResult,
1257
1257
  };
1258
1258
  };
1259
+ /**
1260
+ * Computes the difference of subject minus the union of all clips.
1261
+ * Returns null when the subject is fully covered (zero remaining area).
1262
+ * Returns Polygon for a single-ring result, MultiPolygon otherwise.
1263
+ * When clips is empty the subject is returned unchanged.
1264
+ */
1265
+ const geoJsonPolygonDifference = (subject, clips) => {
1266
+ if (clips.length === 0) {
1267
+ return subject;
1268
+ }
1269
+ const subjectClip = subject.type === "MultiPolygon" ? toClipMultiPolygon(subject.coordinates) : [toClipPolygon(subject.coordinates)];
1270
+ const clipsConverted = clips.map(clip => clip.type === "MultiPolygon" ? toClipMultiPolygon(clip.coordinates) : [toClipPolygon(clip.coordinates)]);
1271
+ let differenceResult;
1272
+ try {
1273
+ differenceResult = polygonClipping.difference(subjectClip, ...clipsConverted);
1274
+ }
1275
+ catch {
1276
+ // polygon-clipping can throw "Unable to complete output ring" for degenerate or
1277
+ // near-self-intersecting polygons. Fall back to returning the subject unclipped
1278
+ // so the shape still renders rather than crashing the caller.
1279
+ return subject;
1280
+ }
1281
+ if (differenceResult.length === 0) {
1282
+ return null;
1283
+ }
1284
+ if (differenceResult.length === 1 && differenceResult[0]) {
1285
+ return {
1286
+ type: "Polygon",
1287
+ coordinates: differenceResult[0],
1288
+ };
1289
+ }
1290
+ return {
1291
+ type: "MultiPolygon",
1292
+ coordinates: differenceResult,
1293
+ };
1294
+ };
1295
+ // Web Mercator (EPSG:3857) latitude limit. Beyond this the projection diverges to
1296
+ // infinity; clamping keeps the round-trip finite for shapes near the poles.
1297
+ const WEB_MERCATOR_MAX_LAT = 85.05112878;
1298
+ const DEG_TO_RAD$1 = Math.PI / 180;
1299
+ const RAD_TO_DEG = 180 / Math.PI;
1300
+ /**
1301
+ * Project a `[lng, lat]` position to Web Mercator metres (EPSG:3857).
1302
+ *
1303
+ * Web map renderers (Google Maps, Mapbox, …) draw straight polygon edges in this
1304
+ * projected space, not in planar lng/lat. Boolean geometry operations whose result
1305
+ * must visually align with rendered edges therefore have to run in Web Mercator —
1306
+ * see `unprojectWebMercatorToLngLat` for the inverse.
1307
+ */
1308
+ const projectLngLatToWebMercator = (position) => {
1309
+ const [lng, lat] = position;
1310
+ const clampedLat = Math.max(-WEB_MERCATOR_MAX_LAT, Math.min(WEB_MERCATOR_MAX_LAT, lat));
1311
+ const x = lng * DEG_TO_RAD$1 * EARTH_RADIUS;
1312
+ const y = Math.log(Math.tan(Math.PI / 4 + (clampedLat * DEG_TO_RAD$1) / 2)) * EARTH_RADIUS;
1313
+ return [x, y];
1314
+ };
1315
+ /** Inverse of `projectLngLatToWebMercator`: Web Mercator metres back to `[lng, lat]`. */
1316
+ const unprojectWebMercatorToLngLat = (position) => {
1317
+ const [x, y] = position;
1318
+ const lng = (x / EARTH_RADIUS) * RAD_TO_DEG;
1319
+ const lat = (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) * RAD_TO_DEG;
1320
+ return [lng, lat];
1321
+ };
1322
+ const mapPolygonalCoordinates = (geometry, project) => {
1323
+ const mapRing = (ring) => ring.map(project);
1324
+ if (geometry.type === "Polygon") {
1325
+ return { type: "Polygon", coordinates: geometry.coordinates.map(mapRing) };
1326
+ }
1327
+ return { type: "MultiPolygon", coordinates: geometry.coordinates.map(rings => rings.map(mapRing)) };
1328
+ };
1329
+ /**
1330
+ * Project a polygonal geometry's coordinates to Web Mercator metres, preserving its
1331
+ * ring/part structure. The output is no longer valid lng/lat — feed it to operations
1332
+ * such as `geoJsonPolygonDifference` and unproject the result with
1333
+ * `unprojectPolygonalFromWebMercator`.
1334
+ */
1335
+ const projectPolygonalToWebMercator = (geometry) => mapPolygonalCoordinates(geometry, projectLngLatToWebMercator);
1336
+ /** Inverse of `projectPolygonalToWebMercator`. */
1337
+ const unprojectPolygonalFromWebMercator = (geometry) => mapPolygonalCoordinates(geometry, unprojectWebMercatorToLngLat);
1338
+ /**
1339
+ * Generalisation of isFullyContainedInGeoJsonPolygon to Polygon|MultiPolygon arguments.
1340
+ * Returns true when every outer-ring vertex of geom1 lies inside geom2 (holes respected).
1341
+ * For a MultiPolygon geom2, "inside" means inside any one of its polygon members.
1342
+ * Returns null if either geometry fails validation.
1343
+ */
1344
+ const isFullyContainedInGeoJsonGeometry = (geom1, geom2) => {
1345
+ const outerRings1 = [];
1346
+ if (geom1.type === "Polygon") {
1347
+ const parsed = geoJsonPolygonSchema.safeParse(geom1);
1348
+ if (!parsed.success)
1349
+ return null;
1350
+ const outerRing = parsed.data.coordinates[0];
1351
+ if (!outerRing)
1352
+ return null;
1353
+ outerRings1.push(outerRing);
1354
+ }
1355
+ else {
1356
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geom1);
1357
+ if (!parsed.success)
1358
+ return null;
1359
+ for (const poly of parsed.data.coordinates) {
1360
+ const outerRing = poly[0];
1361
+ if (!outerRing)
1362
+ return null;
1363
+ outerRings1.push(outerRing);
1364
+ }
1365
+ }
1366
+ const polygonParts2 = [];
1367
+ if (geom2.type === "Polygon") {
1368
+ const parsed = geoJsonPolygonSchema.safeParse(geom2);
1369
+ if (!parsed.success)
1370
+ return null;
1371
+ polygonParts2.push(parsed.data);
1372
+ }
1373
+ else {
1374
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geom2);
1375
+ if (!parsed.success)
1376
+ return null;
1377
+ for (const poly of parsed.data.coordinates) {
1378
+ polygonParts2.push({ type: "Polygon", coordinates: poly });
1379
+ }
1380
+ }
1381
+ for (const outerRing of outerRings1) {
1382
+ for (const position of outerRing) {
1383
+ const point = { coordinates: position };
1384
+ let insideAny = false;
1385
+ for (const part of polygonParts2) {
1386
+ const result = isGeoJsonPointInPolygon({ point, polygon: part });
1387
+ if (result === null)
1388
+ return null;
1389
+ if (result) {
1390
+ insideAny = true;
1391
+ break;
1392
+ }
1393
+ }
1394
+ if (!insideAny)
1395
+ return false;
1396
+ }
1397
+ }
1398
+ return true;
1399
+ };
1400
+ /**
1401
+ * Returns the minimum distance (in coordinate units) from the given position to the
1402
+ * nearest edge segment of any ring of the polygon/multipolygon, including hole rings.
1403
+ * Returns null if the geometry fails validation.
1404
+ *
1405
+ * Distance is in geographic-coordinate units (degrees), appropriate for relative
1406
+ * Penetration Depth comparisons where only ordering matters, not absolute metric values.
1407
+ */
1408
+ const distanceToGeoJsonPolygonBoundary = (position, geometry) => {
1409
+ const rings = [];
1410
+ if (geometry.type === "Polygon") {
1411
+ const parsed = geoJsonPolygonSchema.safeParse(geometry);
1412
+ if (!parsed.success)
1413
+ return null;
1414
+ rings.push(...parsed.data.coordinates);
1415
+ }
1416
+ else {
1417
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geometry);
1418
+ if (!parsed.success)
1419
+ return null;
1420
+ for (const poly of parsed.data.coordinates) {
1421
+ rings.push(...poly);
1422
+ }
1423
+ }
1424
+ if (rings.length === 0)
1425
+ return null;
1426
+ const [px, py] = position;
1427
+ let minDist = Infinity;
1428
+ for (const ring of rings) {
1429
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
1430
+ const a = ring[j];
1431
+ const b = ring[i];
1432
+ if (!a || !b)
1433
+ continue;
1434
+ const [ax, ay] = a;
1435
+ const [bx, by] = b;
1436
+ const dx = bx - ax;
1437
+ const dy = by - ay;
1438
+ let dist;
1439
+ if (dx === 0 && dy === 0) {
1440
+ dist = Math.sqrt((px - ax) ** 2 + (py - ay) ** 2);
1441
+ }
1442
+ else {
1443
+ const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
1444
+ const nearx = ax + t * dx;
1445
+ const neary = ay + t * dy;
1446
+ dist = Math.sqrt((px - nearx) ** 2 + (py - neary) ** 2);
1447
+ }
1448
+ if (dist < minDist) {
1449
+ minDist = dist;
1450
+ }
1451
+ }
1452
+ }
1453
+ return minDist === Infinity ? null : minDist;
1454
+ };
1259
1455
 
1260
1456
  //! These tools are used to bridge the gap with out poorly typed graphql types
1261
1457
  // Should be ideally be avoided but are needed until we fix the graphql types
@@ -1553,6 +1749,7 @@ exports.checkCrossesMeridian = checkCrossesMeridian;
1553
1749
  exports.computeGeometryCentroid = computeGeometryCentroid;
1554
1750
  exports.coordinatesToStandardFormat = coordinatesToStandardFormat;
1555
1751
  exports.denormalizeLongitude = denormalizeLongitude;
1752
+ exports.distanceToGeoJsonPolygonBoundary = distanceToGeoJsonPolygonBoundary;
1556
1753
  exports.edgePixelLength = edgePixelLength;
1557
1754
  exports.extractEdges = extractEdges;
1558
1755
  exports.extractFirstPointCoordinate = extractFirstPointCoordinate;
@@ -1568,6 +1765,7 @@ exports.geoJsonMultiLineStringSchema = geoJsonMultiLineStringSchema;
1568
1765
  exports.geoJsonMultiPointSchema = geoJsonMultiPointSchema;
1569
1766
  exports.geoJsonMultiPolygonSchema = geoJsonMultiPolygonSchema;
1570
1767
  exports.geoJsonPointSchema = geoJsonPointSchema;
1768
+ exports.geoJsonPolygonDifference = geoJsonPolygonDifference;
1571
1769
  exports.geoJsonPolygonSchema = geoJsonPolygonSchema;
1572
1770
  exports.geoJsonPosition2dSchema = geoJsonPosition2dSchema;
1573
1771
  exports.geoJsonPositionSchema = geoJsonPositionSchema;
@@ -1584,11 +1782,14 @@ exports.getPointCoordinateFromGeoJsonPoint = getPointCoordinateFromGeoJsonPoint;
1584
1782
  exports.getPolygonFromBbox = getPolygonFromBbox;
1585
1783
  exports.getPolygonFromPointAndRadius = getPolygonFromPointAndRadius;
1586
1784
  exports.isBboxInsideFeatureCollection = isBboxInsideFeatureCollection;
1785
+ exports.isFullyContainedInGeoJsonGeometry = isFullyContainedInGeoJsonGeometry;
1587
1786
  exports.isFullyContainedInGeoJsonPolygon = isFullyContainedInGeoJsonPolygon;
1588
1787
  exports.isGeoJsonPointInPolygon = isGeoJsonPointInPolygon;
1589
1788
  exports.isGeoJsonPositionInLinearRing = isGeoJsonPositionInLinearRing;
1590
1789
  exports.isPositionInsideRing = isPositionInsideRing;
1591
1790
  exports.normalizeLongitudes = normalizeLongitudes;
1791
+ exports.projectLngLatToWebMercator = projectLngLatToWebMercator;
1792
+ exports.projectPolygonalToWebMercator = projectPolygonalToWebMercator;
1592
1793
  exports.splitPolygonAtAntimeridian = splitPolygonAtAntimeridian;
1593
1794
  exports.splitPolygonWithHolesAtAntimeridian = splitPolygonWithHolesAtAntimeridian;
1594
1795
  exports.toFeatureCollection = toFeatureCollection;
@@ -1596,6 +1797,8 @@ exports.toPosition2d = toPosition2d;
1596
1797
  exports.tuGeoJsonPointRadiusSchema = tuGeoJsonPointRadiusSchema;
1597
1798
  exports.tuGeoJsonPolygonNoHolesSchema = tuGeoJsonPolygonNoHolesSchema;
1598
1799
  exports.tuGeoJsonRectangularBoxPolygonSchema = tuGeoJsonRectangularBoxPolygonSchema;
1800
+ exports.unprojectPolygonalFromWebMercator = unprojectPolygonalFromWebMercator;
1801
+ exports.unprojectWebMercatorToLngLat = unprojectWebMercatorToLngLat;
1599
1802
  exports.validateBbox = validateBbox;
1600
1803
  exports.validateBboxWithFallback = validateBboxWithFallback;
1601
1804
  exports.validateFeatureCollection = validateFeatureCollection;
package/index.esm.js CHANGED
@@ -1254,6 +1254,202 @@ const getGeoJsonPolygonIntersection = (polygon1, polygon2) => {
1254
1254
  coordinates: intersectionResult,
1255
1255
  };
1256
1256
  };
1257
+ /**
1258
+ * Computes the difference of subject minus the union of all clips.
1259
+ * Returns null when the subject is fully covered (zero remaining area).
1260
+ * Returns Polygon for a single-ring result, MultiPolygon otherwise.
1261
+ * When clips is empty the subject is returned unchanged.
1262
+ */
1263
+ const geoJsonPolygonDifference = (subject, clips) => {
1264
+ if (clips.length === 0) {
1265
+ return subject;
1266
+ }
1267
+ const subjectClip = subject.type === "MultiPolygon" ? toClipMultiPolygon(subject.coordinates) : [toClipPolygon(subject.coordinates)];
1268
+ const clipsConverted = clips.map(clip => clip.type === "MultiPolygon" ? toClipMultiPolygon(clip.coordinates) : [toClipPolygon(clip.coordinates)]);
1269
+ let differenceResult;
1270
+ try {
1271
+ differenceResult = polygonClipping.difference(subjectClip, ...clipsConverted);
1272
+ }
1273
+ catch {
1274
+ // polygon-clipping can throw "Unable to complete output ring" for degenerate or
1275
+ // near-self-intersecting polygons. Fall back to returning the subject unclipped
1276
+ // so the shape still renders rather than crashing the caller.
1277
+ return subject;
1278
+ }
1279
+ if (differenceResult.length === 0) {
1280
+ return null;
1281
+ }
1282
+ if (differenceResult.length === 1 && differenceResult[0]) {
1283
+ return {
1284
+ type: "Polygon",
1285
+ coordinates: differenceResult[0],
1286
+ };
1287
+ }
1288
+ return {
1289
+ type: "MultiPolygon",
1290
+ coordinates: differenceResult,
1291
+ };
1292
+ };
1293
+ // Web Mercator (EPSG:3857) latitude limit. Beyond this the projection diverges to
1294
+ // infinity; clamping keeps the round-trip finite for shapes near the poles.
1295
+ const WEB_MERCATOR_MAX_LAT = 85.05112878;
1296
+ const DEG_TO_RAD$1 = Math.PI / 180;
1297
+ const RAD_TO_DEG = 180 / Math.PI;
1298
+ /**
1299
+ * Project a `[lng, lat]` position to Web Mercator metres (EPSG:3857).
1300
+ *
1301
+ * Web map renderers (Google Maps, Mapbox, …) draw straight polygon edges in this
1302
+ * projected space, not in planar lng/lat. Boolean geometry operations whose result
1303
+ * must visually align with rendered edges therefore have to run in Web Mercator —
1304
+ * see `unprojectWebMercatorToLngLat` for the inverse.
1305
+ */
1306
+ const projectLngLatToWebMercator = (position) => {
1307
+ const [lng, lat] = position;
1308
+ const clampedLat = Math.max(-WEB_MERCATOR_MAX_LAT, Math.min(WEB_MERCATOR_MAX_LAT, lat));
1309
+ const x = lng * DEG_TO_RAD$1 * EARTH_RADIUS;
1310
+ const y = Math.log(Math.tan(Math.PI / 4 + (clampedLat * DEG_TO_RAD$1) / 2)) * EARTH_RADIUS;
1311
+ return [x, y];
1312
+ };
1313
+ /** Inverse of `projectLngLatToWebMercator`: Web Mercator metres back to `[lng, lat]`. */
1314
+ const unprojectWebMercatorToLngLat = (position) => {
1315
+ const [x, y] = position;
1316
+ const lng = (x / EARTH_RADIUS) * RAD_TO_DEG;
1317
+ const lat = (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) * RAD_TO_DEG;
1318
+ return [lng, lat];
1319
+ };
1320
+ const mapPolygonalCoordinates = (geometry, project) => {
1321
+ const mapRing = (ring) => ring.map(project);
1322
+ if (geometry.type === "Polygon") {
1323
+ return { type: "Polygon", coordinates: geometry.coordinates.map(mapRing) };
1324
+ }
1325
+ return { type: "MultiPolygon", coordinates: geometry.coordinates.map(rings => rings.map(mapRing)) };
1326
+ };
1327
+ /**
1328
+ * Project a polygonal geometry's coordinates to Web Mercator metres, preserving its
1329
+ * ring/part structure. The output is no longer valid lng/lat — feed it to operations
1330
+ * such as `geoJsonPolygonDifference` and unproject the result with
1331
+ * `unprojectPolygonalFromWebMercator`.
1332
+ */
1333
+ const projectPolygonalToWebMercator = (geometry) => mapPolygonalCoordinates(geometry, projectLngLatToWebMercator);
1334
+ /** Inverse of `projectPolygonalToWebMercator`. */
1335
+ const unprojectPolygonalFromWebMercator = (geometry) => mapPolygonalCoordinates(geometry, unprojectWebMercatorToLngLat);
1336
+ /**
1337
+ * Generalisation of isFullyContainedInGeoJsonPolygon to Polygon|MultiPolygon arguments.
1338
+ * Returns true when every outer-ring vertex of geom1 lies inside geom2 (holes respected).
1339
+ * For a MultiPolygon geom2, "inside" means inside any one of its polygon members.
1340
+ * Returns null if either geometry fails validation.
1341
+ */
1342
+ const isFullyContainedInGeoJsonGeometry = (geom1, geom2) => {
1343
+ const outerRings1 = [];
1344
+ if (geom1.type === "Polygon") {
1345
+ const parsed = geoJsonPolygonSchema.safeParse(geom1);
1346
+ if (!parsed.success)
1347
+ return null;
1348
+ const outerRing = parsed.data.coordinates[0];
1349
+ if (!outerRing)
1350
+ return null;
1351
+ outerRings1.push(outerRing);
1352
+ }
1353
+ else {
1354
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geom1);
1355
+ if (!parsed.success)
1356
+ return null;
1357
+ for (const poly of parsed.data.coordinates) {
1358
+ const outerRing = poly[0];
1359
+ if (!outerRing)
1360
+ return null;
1361
+ outerRings1.push(outerRing);
1362
+ }
1363
+ }
1364
+ const polygonParts2 = [];
1365
+ if (geom2.type === "Polygon") {
1366
+ const parsed = geoJsonPolygonSchema.safeParse(geom2);
1367
+ if (!parsed.success)
1368
+ return null;
1369
+ polygonParts2.push(parsed.data);
1370
+ }
1371
+ else {
1372
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geom2);
1373
+ if (!parsed.success)
1374
+ return null;
1375
+ for (const poly of parsed.data.coordinates) {
1376
+ polygonParts2.push({ type: "Polygon", coordinates: poly });
1377
+ }
1378
+ }
1379
+ for (const outerRing of outerRings1) {
1380
+ for (const position of outerRing) {
1381
+ const point = { coordinates: position };
1382
+ let insideAny = false;
1383
+ for (const part of polygonParts2) {
1384
+ const result = isGeoJsonPointInPolygon({ point, polygon: part });
1385
+ if (result === null)
1386
+ return null;
1387
+ if (result) {
1388
+ insideAny = true;
1389
+ break;
1390
+ }
1391
+ }
1392
+ if (!insideAny)
1393
+ return false;
1394
+ }
1395
+ }
1396
+ return true;
1397
+ };
1398
+ /**
1399
+ * Returns the minimum distance (in coordinate units) from the given position to the
1400
+ * nearest edge segment of any ring of the polygon/multipolygon, including hole rings.
1401
+ * Returns null if the geometry fails validation.
1402
+ *
1403
+ * Distance is in geographic-coordinate units (degrees), appropriate for relative
1404
+ * Penetration Depth comparisons where only ordering matters, not absolute metric values.
1405
+ */
1406
+ const distanceToGeoJsonPolygonBoundary = (position, geometry) => {
1407
+ const rings = [];
1408
+ if (geometry.type === "Polygon") {
1409
+ const parsed = geoJsonPolygonSchema.safeParse(geometry);
1410
+ if (!parsed.success)
1411
+ return null;
1412
+ rings.push(...parsed.data.coordinates);
1413
+ }
1414
+ else {
1415
+ const parsed = geoJsonMultiPolygonSchema.safeParse(geometry);
1416
+ if (!parsed.success)
1417
+ return null;
1418
+ for (const poly of parsed.data.coordinates) {
1419
+ rings.push(...poly);
1420
+ }
1421
+ }
1422
+ if (rings.length === 0)
1423
+ return null;
1424
+ const [px, py] = position;
1425
+ let minDist = Infinity;
1426
+ for (const ring of rings) {
1427
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
1428
+ const a = ring[j];
1429
+ const b = ring[i];
1430
+ if (!a || !b)
1431
+ continue;
1432
+ const [ax, ay] = a;
1433
+ const [bx, by] = b;
1434
+ const dx = bx - ax;
1435
+ const dy = by - ay;
1436
+ let dist;
1437
+ if (dx === 0 && dy === 0) {
1438
+ dist = Math.sqrt((px - ax) ** 2 + (py - ay) ** 2);
1439
+ }
1440
+ else {
1441
+ const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)));
1442
+ const nearx = ax + t * dx;
1443
+ const neary = ay + t * dy;
1444
+ dist = Math.sqrt((px - nearx) ** 2 + (py - neary) ** 2);
1445
+ }
1446
+ if (dist < minDist) {
1447
+ minDist = dist;
1448
+ }
1449
+ }
1450
+ }
1451
+ return minDist === Infinity ? null : minDist;
1452
+ };
1257
1453
 
1258
1454
  //! These tools are used to bridge the gap with out poorly typed graphql types
1259
1455
  // Should be ideally be avoided but are needed until we fix the graphql types
@@ -1544,4 +1740,4 @@ const edgePixelLength = (x0, y0, x1, y1, zoom, midLatDeg, tileSize = 256) => {
1544
1740
  return Math.sqrt(dxPx * dxPx + dyPx * dyPx);
1545
1741
  };
1546
1742
 
1547
- export { EARTH_RADIUS, EMPTY_FEATURE_COLLECTION, boundingBoxCrossesMeridian, checkCrossesMeridian, computeGeometryCentroid, coordinatesToStandardFormat, denormalizeLongitude, edgePixelLength, extractEdges, extractFirstPointCoordinate, extractPositionsFromGeometry, geoJsonBboxSchema, geoJsonFeatureCollectionSchema, geoJsonFeatureSchema, geoJsonGeometryCollectionSchema, geoJsonGeometrySchema, geoJsonLineStringSchema, geoJsonLinearRingSchema, geoJsonMultiLineStringSchema, geoJsonMultiPointSchema, geoJsonMultiPolygonSchema, geoJsonPointSchema, geoJsonPolygonSchema, geoJsonPosition2dSchema, geoJsonPositionSchema, getBboxFromGeoJsonPolygon, getBoundingBoxFromGeoJsonBbox, getBoundingBoxFromGeoJsonPolygon, getExtremeGeoJsonPointFromPolygon, getGeoJsonPolygonFromBoundingBox, getGeoJsonPolygonIntersection, getMinMaxLongitudes, getMultipleCoordinatesFromGeoJsonObject, getPointCoordinateFromGeoJsonObject, getPointCoordinateFromGeoJsonPoint, getPolygonFromBbox, getPolygonFromPointAndRadius, isBboxInsideFeatureCollection, isFullyContainedInGeoJsonPolygon, isGeoJsonPointInPolygon, isGeoJsonPositionInLinearRing, isPositionInsideRing, normalizeLongitudes, splitPolygonAtAntimeridian, splitPolygonWithHolesAtAntimeridian, toFeatureCollection, toPosition2d, tuGeoJsonPointRadiusSchema, tuGeoJsonPolygonNoHolesSchema, tuGeoJsonRectangularBoxPolygonSchema, validateBbox, validateBboxWithFallback, validateFeatureCollection, validatePosition };
1743
+ export { EARTH_RADIUS, EMPTY_FEATURE_COLLECTION, boundingBoxCrossesMeridian, checkCrossesMeridian, computeGeometryCentroid, coordinatesToStandardFormat, denormalizeLongitude, distanceToGeoJsonPolygonBoundary, edgePixelLength, extractEdges, extractFirstPointCoordinate, extractPositionsFromGeometry, geoJsonBboxSchema, geoJsonFeatureCollectionSchema, geoJsonFeatureSchema, geoJsonGeometryCollectionSchema, geoJsonGeometrySchema, geoJsonLineStringSchema, geoJsonLinearRingSchema, geoJsonMultiLineStringSchema, geoJsonMultiPointSchema, geoJsonMultiPolygonSchema, geoJsonPointSchema, geoJsonPolygonDifference, geoJsonPolygonSchema, geoJsonPosition2dSchema, geoJsonPositionSchema, getBboxFromGeoJsonPolygon, getBoundingBoxFromGeoJsonBbox, getBoundingBoxFromGeoJsonPolygon, getExtremeGeoJsonPointFromPolygon, getGeoJsonPolygonFromBoundingBox, getGeoJsonPolygonIntersection, getMinMaxLongitudes, getMultipleCoordinatesFromGeoJsonObject, getPointCoordinateFromGeoJsonObject, getPointCoordinateFromGeoJsonPoint, getPolygonFromBbox, getPolygonFromPointAndRadius, isBboxInsideFeatureCollection, isFullyContainedInGeoJsonGeometry, isFullyContainedInGeoJsonPolygon, isGeoJsonPointInPolygon, isGeoJsonPositionInLinearRing, isPositionInsideRing, normalizeLongitudes, projectLngLatToWebMercator, projectPolygonalToWebMercator, splitPolygonAtAntimeridian, splitPolygonWithHolesAtAntimeridian, toFeatureCollection, toPosition2d, tuGeoJsonPointRadiusSchema, tuGeoJsonPolygonNoHolesSchema, tuGeoJsonRectangularBoxPolygonSchema, unprojectPolygonalFromWebMercator, unprojectWebMercatorToLngLat, validateBbox, validateBboxWithFallback, validateFeatureCollection, validatePosition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/geo-json-utils",
3
- "version": "1.14.30",
3
+ "version": "1.14.32",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -101,3 +101,46 @@ export declare const isFullyContainedInGeoJsonPolygon: (polygon1: GeoJsonPolygon
101
101
  * Returns a MultiPolygon representing the intersection, or null if there is no intersection.
102
102
  */
103
103
  export declare const getGeoJsonPolygonIntersection: (polygon1: GeoJsonPolygon | GeoJsonMultiPolygon, polygon2: GeoJsonPolygon | GeoJsonMultiPolygon) => GeoJsonMultiPolygon | GeoJsonPolygon | null;
104
+ /**
105
+ * Computes the difference of subject minus the union of all clips.
106
+ * Returns null when the subject is fully covered (zero remaining area).
107
+ * Returns Polygon for a single-ring result, MultiPolygon otherwise.
108
+ * When clips is empty the subject is returned unchanged.
109
+ */
110
+ export declare const geoJsonPolygonDifference: (subject: GeoJsonPolygon | GeoJsonMultiPolygon, clips: ReadonlyArray<GeoJsonPolygon | GeoJsonMultiPolygon>) => GeoJsonPolygon | GeoJsonMultiPolygon | null;
111
+ /**
112
+ * Project a `[lng, lat]` position to Web Mercator metres (EPSG:3857).
113
+ *
114
+ * Web map renderers (Google Maps, Mapbox, …) draw straight polygon edges in this
115
+ * projected space, not in planar lng/lat. Boolean geometry operations whose result
116
+ * must visually align with rendered edges therefore have to run in Web Mercator —
117
+ * see `unprojectWebMercatorToLngLat` for the inverse.
118
+ */
119
+ export declare const projectLngLatToWebMercator: (position: GeoJsonPosition) => GeoJsonPosition;
120
+ /** Inverse of `projectLngLatToWebMercator`: Web Mercator metres back to `[lng, lat]`. */
121
+ export declare const unprojectWebMercatorToLngLat: (position: GeoJsonPosition) => GeoJsonPosition;
122
+ /**
123
+ * Project a polygonal geometry's coordinates to Web Mercator metres, preserving its
124
+ * ring/part structure. The output is no longer valid lng/lat — feed it to operations
125
+ * such as `geoJsonPolygonDifference` and unproject the result with
126
+ * `unprojectPolygonalFromWebMercator`.
127
+ */
128
+ export declare const projectPolygonalToWebMercator: (geometry: GeoJsonPolygon | GeoJsonMultiPolygon) => GeoJsonPolygon | GeoJsonMultiPolygon;
129
+ /** Inverse of `projectPolygonalToWebMercator`. */
130
+ export declare const unprojectPolygonalFromWebMercator: (geometry: GeoJsonPolygon | GeoJsonMultiPolygon) => GeoJsonPolygon | GeoJsonMultiPolygon;
131
+ /**
132
+ * Generalisation of isFullyContainedInGeoJsonPolygon to Polygon|MultiPolygon arguments.
133
+ * Returns true when every outer-ring vertex of geom1 lies inside geom2 (holes respected).
134
+ * For a MultiPolygon geom2, "inside" means inside any one of its polygon members.
135
+ * Returns null if either geometry fails validation.
136
+ */
137
+ export declare const isFullyContainedInGeoJsonGeometry: (geom1: GeoJsonPolygon | GeoJsonMultiPolygon, geom2: GeoJsonPolygon | GeoJsonMultiPolygon) => boolean | null;
138
+ /**
139
+ * Returns the minimum distance (in coordinate units) from the given position to the
140
+ * nearest edge segment of any ring of the polygon/multipolygon, including hole rings.
141
+ * Returns null if the geometry fails validation.
142
+ *
143
+ * Distance is in geographic-coordinate units (degrees), appropriate for relative
144
+ * Penetration Depth comparisons where only ordering matters, not absolute metric values.
145
+ */
146
+ export declare const distanceToGeoJsonPolygonBoundary: (position: GeoJsonPosition, geometry: GeoJsonPolygon | GeoJsonMultiPolygon) => number | null;