@trackunit/shared-utils 0.0.83 → 0.0.85
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 +301 -114
- package/index.esm.js +292 -113
- package/package.json +3 -2
- package/src/GeoJson/GeoJsonSchemas.d.ts +0 -34
- package/src/GeoJson/GeoJsonUtils.d.ts +44 -23
- package/src/GeoJson/TUGeoJsonObjectBridgeUtils.d.ts +33 -0
- package/src/GeoJson/TuGeoJsonSchemas.d.ts +58 -0
- package/src/index.d.ts +2 -0
package/index.cjs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var zod = require('zod');
|
|
4
|
+
var polygonClipping = require('polygon-clipping');
|
|
4
5
|
var uuid = require('uuid');
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -480,15 +481,6 @@ const geoJsonLinearRingSchema = zod.z
|
|
|
480
481
|
message: "First and last coordinate positions must be identical (to close the linear ring aka polygon).",
|
|
481
482
|
});
|
|
482
483
|
}
|
|
483
|
-
// Check if any coordinate is (0, 0)
|
|
484
|
-
coords.forEach((coord, index) => {
|
|
485
|
-
if (coord[0] === 0 && coord[1] === 0) {
|
|
486
|
-
ctx.addIssue({
|
|
487
|
-
code: zod.z.ZodIssueCode.custom,
|
|
488
|
-
message: `Coordinate at index ${index} should not be (0, 0). This is likely not a valid coordinate.`,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
484
|
// Check if consecutive points are identical (excluding first and last)
|
|
493
485
|
for (let i = 1; i < coords.length - 1; i++) {
|
|
494
486
|
if (JSON.stringify(coords[i]) === JSON.stringify(coords[i - 1])) {
|
|
@@ -536,107 +528,10 @@ const geoJsonBboxSchema = zod.z
|
|
|
536
528
|
.refine(([minLng, minLat, maxLng, maxLat]) => maxLng > minLng && maxLat > minLat, {
|
|
537
529
|
message: "Invalid bounding box: maxLng should be greater than minLng, and maxLat should be greater than minLat.",
|
|
538
530
|
});
|
|
539
|
-
//* -------- Extras -------- *//
|
|
540
|
-
/**
|
|
541
|
-
* Polygon geometry object that explicitly disallows holes.
|
|
542
|
-
* https://tools.ietf.org/html/rfc7946#section-3.1.6
|
|
543
|
-
*/
|
|
544
|
-
const geoJsonPolygonNoHolesSchema = zod.z.strictObject({
|
|
545
|
-
type: zod.z.literal("Polygon"),
|
|
546
|
-
//uses tuple instead of array to enforce only 1 linear ring aka the polygon itself
|
|
547
|
-
coordinates: zod.z.tuple([geoJsonLinearRingSchema]),
|
|
548
|
-
});
|
|
549
|
-
/**
|
|
550
|
-
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
551
|
-
*/
|
|
552
|
-
const geoJsonRectangularBoxPolygonSchema = zod.z
|
|
553
|
-
.strictObject({
|
|
554
|
-
type: zod.z.literal("Polygon"),
|
|
555
|
-
coordinates: zod.z.array(geoJsonLinearRingSchema),
|
|
556
|
-
})
|
|
557
|
-
.superRefine((data, ctx) => {
|
|
558
|
-
const coordinates = data.coordinates[0];
|
|
559
|
-
// Validate polygon has exactly 5 points
|
|
560
|
-
if ((coordinates === null || coordinates === void 0 ? void 0 : coordinates.length) !== 5) {
|
|
561
|
-
ctx.addIssue({
|
|
562
|
-
code: zod.z.ZodIssueCode.custom,
|
|
563
|
-
message: "Polygon must have exactly 5 coordinates to form a closed box.",
|
|
564
|
-
});
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
// Check each side is either horizontal or vertical
|
|
568
|
-
for (let i = 0; i < 4; i++) {
|
|
569
|
-
const point1 = coordinates[i];
|
|
570
|
-
const point2 = coordinates[i + 1];
|
|
571
|
-
if (!point1 || !point2) {
|
|
572
|
-
ctx.addIssue({
|
|
573
|
-
code: zod.z.ZodIssueCode.custom,
|
|
574
|
-
message: "Each coordinate must be a defined point.",
|
|
575
|
-
});
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
const [x1, y1] = point1;
|
|
579
|
-
const [x2, y2] = point2;
|
|
580
|
-
// Ensure each line segment is either horizontal or vertical
|
|
581
|
-
if (x1 !== x2 && y1 !== y2) {
|
|
582
|
-
ctx.addIssue({
|
|
583
|
-
code: zod.z.ZodIssueCode.custom,
|
|
584
|
-
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
585
|
-
});
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
531
|
|
|
591
532
|
const EARTH_RADIUS = 6378137; // Earth’s mean radius in meters
|
|
592
533
|
/**
|
|
593
|
-
* @description
|
|
594
|
-
* @param geoObject A GeoJSON object.
|
|
595
|
-
* @returns {PointCoordinate} A point coordinate.
|
|
596
|
-
*/
|
|
597
|
-
const getPointCoordinateFromGeoJsonObject = (geoObject) => {
|
|
598
|
-
if (!geoObject) {
|
|
599
|
-
return undefined;
|
|
600
|
-
}
|
|
601
|
-
else if ("geometry" in geoObject) {
|
|
602
|
-
return getPointCoordinateFromGeoJsonObject(geoObject.geometry);
|
|
603
|
-
}
|
|
604
|
-
else if ("coordinates" in geoObject &&
|
|
605
|
-
Array.isArray(geoObject.coordinates) &&
|
|
606
|
-
typeof geoObject.coordinates[0] === "number" &&
|
|
607
|
-
typeof geoObject.coordinates[1] === "number") {
|
|
608
|
-
return { longitude: geoObject.coordinates[0], latitude: geoObject.coordinates[1] };
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
612
|
-
}
|
|
613
|
-
};
|
|
614
|
-
/**
|
|
615
|
-
* @description Extracts multiple point coordinates from a GeoJSON object.
|
|
616
|
-
* @param geoObject A GeoJSON object.
|
|
617
|
-
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
618
|
-
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
619
|
-
*/
|
|
620
|
-
const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
621
|
-
if (!geoObject) {
|
|
622
|
-
return undefined;
|
|
623
|
-
}
|
|
624
|
-
else if ("geometry" in geoObject) {
|
|
625
|
-
return getMultipleCoordinatesFromGeoJsonObject(geoObject.geometry);
|
|
626
|
-
}
|
|
627
|
-
else if ("coordinates" in geoObject &&
|
|
628
|
-
Array.isArray(geoObject.coordinates) &&
|
|
629
|
-
Array.isArray(geoObject.coordinates[0])) {
|
|
630
|
-
// @ts-ignore - suppressImplicitAnyIndexErrors
|
|
631
|
-
// We would not be here if element was not an array.
|
|
632
|
-
return geoObject.coordinates.map(element => ({ longitude: element[0], latitude: element[1] }));
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
/**
|
|
639
|
-
* @description Creates a polygon from a bounding box.
|
|
534
|
+
* @description Creates a polygon (with no holes) from a bounding box.
|
|
640
535
|
*/
|
|
641
536
|
const getPolygonFromBbox = (bbox) => {
|
|
642
537
|
const [minLon, minLat, maxLon, maxLat] = bbox;
|
|
@@ -653,6 +548,23 @@ const getPolygonFromBbox = (bbox) => {
|
|
|
653
548
|
],
|
|
654
549
|
};
|
|
655
550
|
};
|
|
551
|
+
/**
|
|
552
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
553
|
+
*/
|
|
554
|
+
const getBboxFromGeoJsonPolygon = (polygon) => {
|
|
555
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
556
|
+
if (!polygonParsed.success) {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
const points = polygonParsed.data.coordinates[0];
|
|
560
|
+
if (!points) {
|
|
561
|
+
// Should never happen since the schema checks for it
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
const latitudes = points.map(point => point[1]);
|
|
565
|
+
const longitudes = points.map(point => point[0]);
|
|
566
|
+
return [Math.min(...longitudes), Math.min(...latitudes), Math.max(...longitudes), Math.max(...latitudes)];
|
|
567
|
+
};
|
|
656
568
|
/**
|
|
657
569
|
* @description Creates a round polygon from a point and a radius.
|
|
658
570
|
*/
|
|
@@ -681,12 +593,17 @@ const getPolygonFromPointAndRadius = (point, radius) => {
|
|
|
681
593
|
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
682
594
|
*/
|
|
683
595
|
const getBoundingBoxFromGeoJsonPolygon = (polygon) => {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
596
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
597
|
+
if (!polygonParsed.success) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
const points = polygonParsed.data.coordinates[0];
|
|
601
|
+
if (!points) {
|
|
602
|
+
// Should never happen since the schema checks for it
|
|
603
|
+
return null;
|
|
689
604
|
}
|
|
605
|
+
const latitudes = points.map(point => point[1]);
|
|
606
|
+
const longitudes = points.map(point => point[0]);
|
|
690
607
|
return {
|
|
691
608
|
nw: {
|
|
692
609
|
latitude: Math.max(...latitudes),
|
|
@@ -722,6 +639,268 @@ const getGeoJsonPolygonFromBoundingBox = (boundingBox) => {
|
|
|
722
639
|
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
723
640
|
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
724
641
|
};
|
|
642
|
+
/**
|
|
643
|
+
* @description Gets the extreme point of a polygon in a given direction.
|
|
644
|
+
* @param {object} params - The parameters object
|
|
645
|
+
* @param {GeoJsonPolygon} params.polygon - The polygon to get the extreme point from
|
|
646
|
+
* @param {("top" | "right" | "bottom" | "left")} params.direction - The direction to get the extreme point in
|
|
647
|
+
* @returns {GeoJsonPoint} The extreme point in the given direction
|
|
648
|
+
*/
|
|
649
|
+
const getExtremeGeoJsonPointFromPolygon = ({ polygon, direction, }) => {
|
|
650
|
+
var _a, _b, _c;
|
|
651
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
652
|
+
if (!polygonParsed.success) {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
const firstPoint = (_a = polygonParsed.data.coordinates[0]) === null || _a === void 0 ? void 0 : _a[0];
|
|
656
|
+
if (!firstPoint) {
|
|
657
|
+
// Should never happen since the schema checks for it
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
const extremePosition = (_b = polygonParsed.data.coordinates[0]) === null || _b === void 0 ? void 0 : _b.reduce((extremePoint, currentPoint) => {
|
|
661
|
+
var _a, _b, _c, _d;
|
|
662
|
+
switch (direction) {
|
|
663
|
+
case "top":
|
|
664
|
+
return currentPoint[1] > ((_a = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[1]) !== null && _a !== void 0 ? _a : -Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
665
|
+
case "right":
|
|
666
|
+
return currentPoint[0] > ((_b = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[0]) !== null && _b !== void 0 ? _b : -Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
667
|
+
case "bottom":
|
|
668
|
+
return currentPoint[1] < ((_c = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[1]) !== null && _c !== void 0 ? _c : Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
669
|
+
case "left":
|
|
670
|
+
return currentPoint[0] < ((_d = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[0]) !== null && _d !== void 0 ? _d : Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
671
|
+
default: {
|
|
672
|
+
throw new Error(`${direction} is not known`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}, (_c = polygonParsed.data.coordinates[0]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
676
|
+
return extremePosition
|
|
677
|
+
? {
|
|
678
|
+
type: "Point",
|
|
679
|
+
coordinates: extremePosition,
|
|
680
|
+
}
|
|
681
|
+
: null; // Should never happen since the schema checks for it
|
|
682
|
+
};
|
|
683
|
+
/**
|
|
684
|
+
* Checks if a position is inside a linear ring. On edge is considered inside.
|
|
685
|
+
*/
|
|
686
|
+
const isGeoJsonPositionInLinearRing = ({ position, linearRing, }) => {
|
|
687
|
+
const linearRingParsed = geoJsonLinearRingSchema.safeParse(linearRing);
|
|
688
|
+
if (!linearRingParsed.success) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
let inside = false;
|
|
692
|
+
const [x, y] = position;
|
|
693
|
+
for (let i = 0, j = linearRingParsed.data.length - 1; i < linearRingParsed.data.length; j = i++) {
|
|
694
|
+
const point1 = linearRingParsed.data[i];
|
|
695
|
+
const point2 = linearRingParsed.data[j];
|
|
696
|
+
if (!point1 || !point2) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const [xi, yi] = point1;
|
|
700
|
+
const [xj, yj] = point2;
|
|
701
|
+
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
702
|
+
if (intersect) {
|
|
703
|
+
inside = !inside;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return inside;
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* @description Checks if a point is inside a polygon.
|
|
710
|
+
*/
|
|
711
|
+
const isGeoJsonPointInPolygon = ({ point, polygon, }) => {
|
|
712
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
713
|
+
if (!polygonParsed.success) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
return polygonParsed.data.coordinates.some(linearRing => isGeoJsonPositionInLinearRing({ position: point.coordinates, linearRing }));
|
|
717
|
+
};
|
|
718
|
+
/**
|
|
719
|
+
* Checks if polygon1 is fully contained within polygon2
|
|
720
|
+
*/
|
|
721
|
+
const isFullyContainedInGeoJsonPolygon = (polygon1, polygon2) => {
|
|
722
|
+
const polygon1Parsed = geoJsonPolygonSchema.safeParse(polygon1);
|
|
723
|
+
const polygon2Parsed = geoJsonPolygonSchema.safeParse(polygon2);
|
|
724
|
+
// The schema checks more than a TypeScript type can represent
|
|
725
|
+
if (!polygon1Parsed.success || !polygon2Parsed.success) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
return polygon1Parsed.data.coordinates.every(linearRing => polygon2Parsed.data.coordinates.some(lr => linearRing.every(position => isGeoJsonPositionInLinearRing({ position, linearRing: lr }))));
|
|
729
|
+
};
|
|
730
|
+
/**
|
|
731
|
+
* @description Gets the intersection between two GeoJSON polygons. If one polygon is fully contained within the other,
|
|
732
|
+
* returns the contained polygon. Otherwise returns a MultiPolygon representing the intersection.
|
|
733
|
+
* @param polygon1 The first polygon to check intersection
|
|
734
|
+
* @param polygon2 The second polygon to check intersection
|
|
735
|
+
* @returns {(GeoJsonMultiPolygon | GeoJsonPolygon)} The intersection as either a Polygon (if one contains the other) or MultiPolygon
|
|
736
|
+
*/
|
|
737
|
+
const getGeoJsonPolygonIntersection = (polygon1, polygon2) => {
|
|
738
|
+
const polygon1Parsed = geoJsonPolygonSchema.safeParse(polygon1);
|
|
739
|
+
const polygon2Parsed = geoJsonPolygonSchema.safeParse(polygon2);
|
|
740
|
+
if (!polygon1Parsed.success || !polygon2Parsed.success) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
if (isFullyContainedInGeoJsonPolygon(polygon1, polygon2)) {
|
|
744
|
+
return polygon1;
|
|
745
|
+
}
|
|
746
|
+
if (isFullyContainedInGeoJsonPolygon(polygon2, polygon1)) {
|
|
747
|
+
return polygon2;
|
|
748
|
+
}
|
|
749
|
+
const intersectionResult = polygonClipping.intersection(polygon1.coordinates, polygon2.coordinates);
|
|
750
|
+
if (intersectionResult.length === 1 && intersectionResult[0]) {
|
|
751
|
+
return {
|
|
752
|
+
type: "Polygon",
|
|
753
|
+
coordinates: intersectionResult[0],
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
type: "MultiPolygon",
|
|
758
|
+
coordinates: polygonClipping.intersection(polygon1.coordinates, polygon2.coordinates),
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
//! These tools are used to bridge the gap with out poorly typed graphql types
|
|
763
|
+
// Should be ideally be avoided but are needed until we fix the graphql types
|
|
764
|
+
const isDoubleNestedCoords = (coords) => Array.isArray(coords) &&
|
|
765
|
+
Array.isArray(coords[0]) &&
|
|
766
|
+
Array.isArray(coords[0][0]) &&
|
|
767
|
+
typeof coords[0][0][0] === "number";
|
|
768
|
+
const isSingleCoords = (coords) => typeof coords[0] === "number";
|
|
769
|
+
/**
|
|
770
|
+
* @description Returns coordinates in consistent format
|
|
771
|
+
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
772
|
+
* @returns {GeoJsonPosition[]} Array of standardized coordinates
|
|
773
|
+
*/
|
|
774
|
+
const coordinatesToStandardFormat = (inconsistentCoordinates) => {
|
|
775
|
+
if (!inconsistentCoordinates) {
|
|
776
|
+
return [];
|
|
777
|
+
}
|
|
778
|
+
if (isSingleCoords(inconsistentCoordinates)) {
|
|
779
|
+
return [inconsistentCoordinates];
|
|
780
|
+
}
|
|
781
|
+
if (isDoubleNestedCoords(inconsistentCoordinates)) {
|
|
782
|
+
return inconsistentCoordinates[0] || [];
|
|
783
|
+
}
|
|
784
|
+
if (inconsistentCoordinates[0] && typeof inconsistentCoordinates[0][0] === "number") {
|
|
785
|
+
return inconsistentCoordinates;
|
|
786
|
+
}
|
|
787
|
+
return [];
|
|
788
|
+
};
|
|
789
|
+
/**
|
|
790
|
+
* @description Extracts a point coordinate from a GeoJSON object.
|
|
791
|
+
* @param geoObject A GeoJSON object.
|
|
792
|
+
* @returns {PointCoordinate} A point coordinate.
|
|
793
|
+
*/
|
|
794
|
+
const getPointCoordinateFromGeoJsonObject = (geoObject) => {
|
|
795
|
+
if (!geoObject) {
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
else if ("geometry" in geoObject) {
|
|
799
|
+
return getPointCoordinateFromGeoJsonObject(geoObject.geometry);
|
|
800
|
+
}
|
|
801
|
+
else if ("coordinates" in geoObject &&
|
|
802
|
+
Array.isArray(geoObject.coordinates) &&
|
|
803
|
+
typeof geoObject.coordinates[0] === "number" &&
|
|
804
|
+
typeof geoObject.coordinates[1] === "number") {
|
|
805
|
+
const [point] = coordinatesToStandardFormat(geoObject.coordinates);
|
|
806
|
+
if (point) {
|
|
807
|
+
return { latitude: point[1], longitude: point[0] };
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
/**
|
|
818
|
+
* @description Extracts multiple point coordinates from a GeoJSON object.
|
|
819
|
+
* @param geoObject A GeoJSON object.
|
|
820
|
+
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
821
|
+
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
822
|
+
*/
|
|
823
|
+
const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
824
|
+
if (!geoObject) {
|
|
825
|
+
return undefined;
|
|
826
|
+
}
|
|
827
|
+
else if ("geometry" in geoObject) {
|
|
828
|
+
return getMultipleCoordinatesFromGeoJsonObject(geoObject.geometry);
|
|
829
|
+
}
|
|
830
|
+
else if ("coordinates" in geoObject) {
|
|
831
|
+
return coordinatesToStandardFormat(geoObject.coordinates).map(([longitude, latitude]) => ({ longitude, latitude }));
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
//* -------- Trackunit-invented schemas and types to extend the GeoJson spec -------- *//
|
|
839
|
+
/**
|
|
840
|
+
* Polygon geometry object that explicitly disallows holes.
|
|
841
|
+
*
|
|
842
|
+
* Same as geoJsonPolygonSchema but type disallows holes by
|
|
843
|
+
* using tuple of one single linear ring instead of an array.
|
|
844
|
+
*/
|
|
845
|
+
const tuGeoJsonPolygonNoHolesSchema = zod.z.strictObject({
|
|
846
|
+
//The type is still "Polygon" (not PolygonNoHoles or similar) since it's always
|
|
847
|
+
//compliant with Polygon, just not the other way around
|
|
848
|
+
type: zod.z.literal("Polygon"),
|
|
849
|
+
//uses tuple instead of array to enforce only 1 linear ring aka the polygon itself
|
|
850
|
+
coordinates: zod.z.tuple([geoJsonLinearRingSchema]),
|
|
851
|
+
});
|
|
852
|
+
/**
|
|
853
|
+
* Point radius object.
|
|
854
|
+
* For when you wish to define an area by a point and a radius.
|
|
855
|
+
*
|
|
856
|
+
* radius is in meters
|
|
857
|
+
*/
|
|
858
|
+
const tuGeoJsonPointRadiusSchema = zod.z.strictObject({
|
|
859
|
+
type: zod.z.literal("PointRadius"),
|
|
860
|
+
coordinates: geoJsonPositionSchema,
|
|
861
|
+
radius: zod.z.number().positive(), // in meters
|
|
862
|
+
});
|
|
863
|
+
/**
|
|
864
|
+
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
865
|
+
*/
|
|
866
|
+
const tuGeoJsonRectangularBoxPolygonSchema = zod.z
|
|
867
|
+
.strictObject({
|
|
868
|
+
type: zod.z.literal("Polygon"),
|
|
869
|
+
coordinates: zod.z.array(geoJsonLinearRingSchema),
|
|
870
|
+
})
|
|
871
|
+
.superRefine((data, ctx) => {
|
|
872
|
+
const coordinates = data.coordinates[0];
|
|
873
|
+
// Validate polygon has exactly 5 points
|
|
874
|
+
if ((coordinates === null || coordinates === void 0 ? void 0 : coordinates.length) !== 5) {
|
|
875
|
+
ctx.addIssue({
|
|
876
|
+
code: zod.z.ZodIssueCode.custom,
|
|
877
|
+
message: "Polygon must have exactly 5 coordinates to form a closed box.",
|
|
878
|
+
});
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
// Check each side is either horizontal or vertical
|
|
882
|
+
for (let i = 0; i < 4; i++) {
|
|
883
|
+
const point1 = coordinates[i];
|
|
884
|
+
const point2 = coordinates[i + 1];
|
|
885
|
+
if (point1 === undefined || point2 === undefined) {
|
|
886
|
+
ctx.addIssue({
|
|
887
|
+
code: zod.z.ZodIssueCode.custom,
|
|
888
|
+
message: "Each coordinate must be a defined point.",
|
|
889
|
+
});
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const [x1, y1] = point1;
|
|
893
|
+
const [x2, y2] = point2;
|
|
894
|
+
// Ensure each line segment is either horizontal or vertical
|
|
895
|
+
if (x1 !== x2 && y1 !== y2) {
|
|
896
|
+
ctx.addIssue({
|
|
897
|
+
code: zod.z.ZodIssueCode.custom,
|
|
898
|
+
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
899
|
+
});
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
725
904
|
|
|
726
905
|
/**
|
|
727
906
|
* Group an array of items by a key.
|
|
@@ -1380,6 +1559,7 @@ exports.capitalize = capitalize;
|
|
|
1380
1559
|
exports.convertBlobToBase64 = convertBlobToBase64;
|
|
1381
1560
|
exports.convertMetersToYards = convertMetersToYards;
|
|
1382
1561
|
exports.convertYardsToMeters = convertYardsToMeters;
|
|
1562
|
+
exports.coordinatesToStandardFormat = coordinatesToStandardFormat;
|
|
1383
1563
|
exports.dateCompare = dateCompare;
|
|
1384
1564
|
exports.deleteUndefinedKeys = deleteUndefinedKeys;
|
|
1385
1565
|
exports.difference = difference;
|
|
@@ -1400,15 +1580,16 @@ exports.geoJsonMultiLineStringSchema = geoJsonMultiLineStringSchema;
|
|
|
1400
1580
|
exports.geoJsonMultiPointSchema = geoJsonMultiPointSchema;
|
|
1401
1581
|
exports.geoJsonMultiPolygonSchema = geoJsonMultiPolygonSchema;
|
|
1402
1582
|
exports.geoJsonPointSchema = geoJsonPointSchema;
|
|
1403
|
-
exports.geoJsonPolygonNoHolesSchema = geoJsonPolygonNoHolesSchema;
|
|
1404
1583
|
exports.geoJsonPolygonSchema = geoJsonPolygonSchema;
|
|
1405
1584
|
exports.geoJsonPositionSchema = geoJsonPositionSchema;
|
|
1406
|
-
exports.
|
|
1585
|
+
exports.getBboxFromGeoJsonPolygon = getBboxFromGeoJsonPolygon;
|
|
1407
1586
|
exports.getBoundingBoxFromGeoJsonPolygon = getBoundingBoxFromGeoJsonPolygon;
|
|
1408
1587
|
exports.getDifferenceBetweenDates = getDifferenceBetweenDates;
|
|
1409
1588
|
exports.getEndOfDay = getEndOfDay;
|
|
1589
|
+
exports.getExtremeGeoJsonPointFromPolygon = getExtremeGeoJsonPointFromPolygon;
|
|
1410
1590
|
exports.getFirstLevelObjectPropertyDifferences = getFirstLevelObjectPropertyDifferences;
|
|
1411
1591
|
exports.getGeoJsonPolygonFromBoundingBox = getGeoJsonPolygonFromBoundingBox;
|
|
1592
|
+
exports.getGeoJsonPolygonIntersection = getGeoJsonPolygonIntersection;
|
|
1412
1593
|
exports.getISOStringFromDate = getISOStringFromDate;
|
|
1413
1594
|
exports.getMultipleCoordinatesFromGeoJsonObject = getMultipleCoordinatesFromGeoJsonObject;
|
|
1414
1595
|
exports.getPointCoordinateFromGeoJsonObject = getPointCoordinateFromGeoJsonObject;
|
|
@@ -1422,6 +1603,9 @@ exports.groupTinyDataToOthers = groupTinyDataToOthers;
|
|
|
1422
1603
|
exports.hourIntervals = hourIntervals;
|
|
1423
1604
|
exports.intersection = intersection;
|
|
1424
1605
|
exports.isArrayEqual = isArrayEqual;
|
|
1606
|
+
exports.isFullyContainedInGeoJsonPolygon = isFullyContainedInGeoJsonPolygon;
|
|
1607
|
+
exports.isGeoJsonPointInPolygon = isGeoJsonPointInPolygon;
|
|
1608
|
+
exports.isGeoJsonPositionInLinearRing = isGeoJsonPositionInLinearRing;
|
|
1425
1609
|
exports.isSorted = isSorted;
|
|
1426
1610
|
exports.isUUID = isUUID;
|
|
1427
1611
|
exports.isValidImage = isValidImage;
|
|
@@ -1449,6 +1633,9 @@ exports.toUUID = toUUID;
|
|
|
1449
1633
|
exports.trimIds = trimIds;
|
|
1450
1634
|
exports.trimPath = trimPath;
|
|
1451
1635
|
exports.truthy = truthy;
|
|
1636
|
+
exports.tuGeoJsonPointRadiusSchema = tuGeoJsonPointRadiusSchema;
|
|
1637
|
+
exports.tuGeoJsonPolygonNoHolesSchema = tuGeoJsonPolygonNoHolesSchema;
|
|
1638
|
+
exports.tuGeoJsonRectangularBoxPolygonSchema = tuGeoJsonRectangularBoxPolygonSchema;
|
|
1452
1639
|
exports.unionArraysByKey = unionArraysByKey;
|
|
1453
1640
|
exports.uuidv3 = uuidv3;
|
|
1454
1641
|
exports.uuidv4 = uuidv4;
|
package/index.esm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { intersection as intersection$1 } from 'polygon-clipping';
|
|
2
3
|
import { v3, v4, v5 } from 'uuid';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -478,15 +479,6 @@ const geoJsonLinearRingSchema = z
|
|
|
478
479
|
message: "First and last coordinate positions must be identical (to close the linear ring aka polygon).",
|
|
479
480
|
});
|
|
480
481
|
}
|
|
481
|
-
// Check if any coordinate is (0, 0)
|
|
482
|
-
coords.forEach((coord, index) => {
|
|
483
|
-
if (coord[0] === 0 && coord[1] === 0) {
|
|
484
|
-
ctx.addIssue({
|
|
485
|
-
code: z.ZodIssueCode.custom,
|
|
486
|
-
message: `Coordinate at index ${index} should not be (0, 0). This is likely not a valid coordinate.`,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
482
|
// Check if consecutive points are identical (excluding first and last)
|
|
491
483
|
for (let i = 1; i < coords.length - 1; i++) {
|
|
492
484
|
if (JSON.stringify(coords[i]) === JSON.stringify(coords[i - 1])) {
|
|
@@ -534,107 +526,10 @@ const geoJsonBboxSchema = z
|
|
|
534
526
|
.refine(([minLng, minLat, maxLng, maxLat]) => maxLng > minLng && maxLat > minLat, {
|
|
535
527
|
message: "Invalid bounding box: maxLng should be greater than minLng, and maxLat should be greater than minLat.",
|
|
536
528
|
});
|
|
537
|
-
//* -------- Extras -------- *//
|
|
538
|
-
/**
|
|
539
|
-
* Polygon geometry object that explicitly disallows holes.
|
|
540
|
-
* https://tools.ietf.org/html/rfc7946#section-3.1.6
|
|
541
|
-
*/
|
|
542
|
-
const geoJsonPolygonNoHolesSchema = z.strictObject({
|
|
543
|
-
type: z.literal("Polygon"),
|
|
544
|
-
//uses tuple instead of array to enforce only 1 linear ring aka the polygon itself
|
|
545
|
-
coordinates: z.tuple([geoJsonLinearRingSchema]),
|
|
546
|
-
});
|
|
547
|
-
/**
|
|
548
|
-
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
549
|
-
*/
|
|
550
|
-
const geoJsonRectangularBoxPolygonSchema = z
|
|
551
|
-
.strictObject({
|
|
552
|
-
type: z.literal("Polygon"),
|
|
553
|
-
coordinates: z.array(geoJsonLinearRingSchema),
|
|
554
|
-
})
|
|
555
|
-
.superRefine((data, ctx) => {
|
|
556
|
-
const coordinates = data.coordinates[0];
|
|
557
|
-
// Validate polygon has exactly 5 points
|
|
558
|
-
if ((coordinates === null || coordinates === void 0 ? void 0 : coordinates.length) !== 5) {
|
|
559
|
-
ctx.addIssue({
|
|
560
|
-
code: z.ZodIssueCode.custom,
|
|
561
|
-
message: "Polygon must have exactly 5 coordinates to form a closed box.",
|
|
562
|
-
});
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
// Check each side is either horizontal or vertical
|
|
566
|
-
for (let i = 0; i < 4; i++) {
|
|
567
|
-
const point1 = coordinates[i];
|
|
568
|
-
const point2 = coordinates[i + 1];
|
|
569
|
-
if (!point1 || !point2) {
|
|
570
|
-
ctx.addIssue({
|
|
571
|
-
code: z.ZodIssueCode.custom,
|
|
572
|
-
message: "Each coordinate must be a defined point.",
|
|
573
|
-
});
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
const [x1, y1] = point1;
|
|
577
|
-
const [x2, y2] = point2;
|
|
578
|
-
// Ensure each line segment is either horizontal or vertical
|
|
579
|
-
if (x1 !== x2 && y1 !== y2) {
|
|
580
|
-
ctx.addIssue({
|
|
581
|
-
code: z.ZodIssueCode.custom,
|
|
582
|
-
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
583
|
-
});
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
529
|
|
|
589
530
|
const EARTH_RADIUS = 6378137; // Earth’s mean radius in meters
|
|
590
531
|
/**
|
|
591
|
-
* @description
|
|
592
|
-
* @param geoObject A GeoJSON object.
|
|
593
|
-
* @returns {PointCoordinate} A point coordinate.
|
|
594
|
-
*/
|
|
595
|
-
const getPointCoordinateFromGeoJsonObject = (geoObject) => {
|
|
596
|
-
if (!geoObject) {
|
|
597
|
-
return undefined;
|
|
598
|
-
}
|
|
599
|
-
else if ("geometry" in geoObject) {
|
|
600
|
-
return getPointCoordinateFromGeoJsonObject(geoObject.geometry);
|
|
601
|
-
}
|
|
602
|
-
else if ("coordinates" in geoObject &&
|
|
603
|
-
Array.isArray(geoObject.coordinates) &&
|
|
604
|
-
typeof geoObject.coordinates[0] === "number" &&
|
|
605
|
-
typeof geoObject.coordinates[1] === "number") {
|
|
606
|
-
return { longitude: geoObject.coordinates[0], latitude: geoObject.coordinates[1] };
|
|
607
|
-
}
|
|
608
|
-
else {
|
|
609
|
-
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
/**
|
|
613
|
-
* @description Extracts multiple point coordinates from a GeoJSON object.
|
|
614
|
-
* @param geoObject A GeoJSON object.
|
|
615
|
-
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
616
|
-
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
617
|
-
*/
|
|
618
|
-
const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
619
|
-
if (!geoObject) {
|
|
620
|
-
return undefined;
|
|
621
|
-
}
|
|
622
|
-
else if ("geometry" in geoObject) {
|
|
623
|
-
return getMultipleCoordinatesFromGeoJsonObject(geoObject.geometry);
|
|
624
|
-
}
|
|
625
|
-
else if ("coordinates" in geoObject &&
|
|
626
|
-
Array.isArray(geoObject.coordinates) &&
|
|
627
|
-
Array.isArray(geoObject.coordinates[0])) {
|
|
628
|
-
// @ts-ignore - suppressImplicitAnyIndexErrors
|
|
629
|
-
// We would not be here if element was not an array.
|
|
630
|
-
return geoObject.coordinates.map(element => ({ longitude: element[0], latitude: element[1] }));
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
/**
|
|
637
|
-
* @description Creates a polygon from a bounding box.
|
|
532
|
+
* @description Creates a polygon (with no holes) from a bounding box.
|
|
638
533
|
*/
|
|
639
534
|
const getPolygonFromBbox = (bbox) => {
|
|
640
535
|
const [minLon, minLat, maxLon, maxLat] = bbox;
|
|
@@ -651,6 +546,23 @@ const getPolygonFromBbox = (bbox) => {
|
|
|
651
546
|
],
|
|
652
547
|
};
|
|
653
548
|
};
|
|
549
|
+
/**
|
|
550
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
551
|
+
*/
|
|
552
|
+
const getBboxFromGeoJsonPolygon = (polygon) => {
|
|
553
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
554
|
+
if (!polygonParsed.success) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
const points = polygonParsed.data.coordinates[0];
|
|
558
|
+
if (!points) {
|
|
559
|
+
// Should never happen since the schema checks for it
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const latitudes = points.map(point => point[1]);
|
|
563
|
+
const longitudes = points.map(point => point[0]);
|
|
564
|
+
return [Math.min(...longitudes), Math.min(...latitudes), Math.max(...longitudes), Math.max(...latitudes)];
|
|
565
|
+
};
|
|
654
566
|
/**
|
|
655
567
|
* @description Creates a round polygon from a point and a radius.
|
|
656
568
|
*/
|
|
@@ -679,12 +591,17 @@ const getPolygonFromPointAndRadius = (point, radius) => {
|
|
|
679
591
|
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
680
592
|
*/
|
|
681
593
|
const getBoundingBoxFromGeoJsonPolygon = (polygon) => {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
594
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
595
|
+
if (!polygonParsed.success) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
const points = polygonParsed.data.coordinates[0];
|
|
599
|
+
if (!points) {
|
|
600
|
+
// Should never happen since the schema checks for it
|
|
601
|
+
return null;
|
|
687
602
|
}
|
|
603
|
+
const latitudes = points.map(point => point[1]);
|
|
604
|
+
const longitudes = points.map(point => point[0]);
|
|
688
605
|
return {
|
|
689
606
|
nw: {
|
|
690
607
|
latitude: Math.max(...latitudes),
|
|
@@ -720,6 +637,268 @@ const getGeoJsonPolygonFromBoundingBox = (boundingBox) => {
|
|
|
720
637
|
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
721
638
|
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
722
639
|
};
|
|
640
|
+
/**
|
|
641
|
+
* @description Gets the extreme point of a polygon in a given direction.
|
|
642
|
+
* @param {object} params - The parameters object
|
|
643
|
+
* @param {GeoJsonPolygon} params.polygon - The polygon to get the extreme point from
|
|
644
|
+
* @param {("top" | "right" | "bottom" | "left")} params.direction - The direction to get the extreme point in
|
|
645
|
+
* @returns {GeoJsonPoint} The extreme point in the given direction
|
|
646
|
+
*/
|
|
647
|
+
const getExtremeGeoJsonPointFromPolygon = ({ polygon, direction, }) => {
|
|
648
|
+
var _a, _b, _c;
|
|
649
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
650
|
+
if (!polygonParsed.success) {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
const firstPoint = (_a = polygonParsed.data.coordinates[0]) === null || _a === void 0 ? void 0 : _a[0];
|
|
654
|
+
if (!firstPoint) {
|
|
655
|
+
// Should never happen since the schema checks for it
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
const extremePosition = (_b = polygonParsed.data.coordinates[0]) === null || _b === void 0 ? void 0 : _b.reduce((extremePoint, currentPoint) => {
|
|
659
|
+
var _a, _b, _c, _d;
|
|
660
|
+
switch (direction) {
|
|
661
|
+
case "top":
|
|
662
|
+
return currentPoint[1] > ((_a = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[1]) !== null && _a !== void 0 ? _a : -Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
663
|
+
case "right":
|
|
664
|
+
return currentPoint[0] > ((_b = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[0]) !== null && _b !== void 0 ? _b : -Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
665
|
+
case "bottom":
|
|
666
|
+
return currentPoint[1] < ((_c = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[1]) !== null && _c !== void 0 ? _c : Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
667
|
+
case "left":
|
|
668
|
+
return currentPoint[0] < ((_d = extremePoint === null || extremePoint === void 0 ? void 0 : extremePoint[0]) !== null && _d !== void 0 ? _d : Infinity) ? currentPoint : extremePoint !== null && extremePoint !== void 0 ? extremePoint : currentPoint;
|
|
669
|
+
default: {
|
|
670
|
+
throw new Error(`${direction} is not known`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}, (_c = polygonParsed.data.coordinates[0]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
674
|
+
return extremePosition
|
|
675
|
+
? {
|
|
676
|
+
type: "Point",
|
|
677
|
+
coordinates: extremePosition,
|
|
678
|
+
}
|
|
679
|
+
: null; // Should never happen since the schema checks for it
|
|
680
|
+
};
|
|
681
|
+
/**
|
|
682
|
+
* Checks if a position is inside a linear ring. On edge is considered inside.
|
|
683
|
+
*/
|
|
684
|
+
const isGeoJsonPositionInLinearRing = ({ position, linearRing, }) => {
|
|
685
|
+
const linearRingParsed = geoJsonLinearRingSchema.safeParse(linearRing);
|
|
686
|
+
if (!linearRingParsed.success) {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
let inside = false;
|
|
690
|
+
const [x, y] = position;
|
|
691
|
+
for (let i = 0, j = linearRingParsed.data.length - 1; i < linearRingParsed.data.length; j = i++) {
|
|
692
|
+
const point1 = linearRingParsed.data[i];
|
|
693
|
+
const point2 = linearRingParsed.data[j];
|
|
694
|
+
if (!point1 || !point2) {
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const [xi, yi] = point1;
|
|
698
|
+
const [xj, yj] = point2;
|
|
699
|
+
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
700
|
+
if (intersect) {
|
|
701
|
+
inside = !inside;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return inside;
|
|
705
|
+
};
|
|
706
|
+
/**
|
|
707
|
+
* @description Checks if a point is inside a polygon.
|
|
708
|
+
*/
|
|
709
|
+
const isGeoJsonPointInPolygon = ({ point, polygon, }) => {
|
|
710
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
711
|
+
if (!polygonParsed.success) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
return polygonParsed.data.coordinates.some(linearRing => isGeoJsonPositionInLinearRing({ position: point.coordinates, linearRing }));
|
|
715
|
+
};
|
|
716
|
+
/**
|
|
717
|
+
* Checks if polygon1 is fully contained within polygon2
|
|
718
|
+
*/
|
|
719
|
+
const isFullyContainedInGeoJsonPolygon = (polygon1, polygon2) => {
|
|
720
|
+
const polygon1Parsed = geoJsonPolygonSchema.safeParse(polygon1);
|
|
721
|
+
const polygon2Parsed = geoJsonPolygonSchema.safeParse(polygon2);
|
|
722
|
+
// The schema checks more than a TypeScript type can represent
|
|
723
|
+
if (!polygon1Parsed.success || !polygon2Parsed.success) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
return polygon1Parsed.data.coordinates.every(linearRing => polygon2Parsed.data.coordinates.some(lr => linearRing.every(position => isGeoJsonPositionInLinearRing({ position, linearRing: lr }))));
|
|
727
|
+
};
|
|
728
|
+
/**
|
|
729
|
+
* @description Gets the intersection between two GeoJSON polygons. If one polygon is fully contained within the other,
|
|
730
|
+
* returns the contained polygon. Otherwise returns a MultiPolygon representing the intersection.
|
|
731
|
+
* @param polygon1 The first polygon to check intersection
|
|
732
|
+
* @param polygon2 The second polygon to check intersection
|
|
733
|
+
* @returns {(GeoJsonMultiPolygon | GeoJsonPolygon)} The intersection as either a Polygon (if one contains the other) or MultiPolygon
|
|
734
|
+
*/
|
|
735
|
+
const getGeoJsonPolygonIntersection = (polygon1, polygon2) => {
|
|
736
|
+
const polygon1Parsed = geoJsonPolygonSchema.safeParse(polygon1);
|
|
737
|
+
const polygon2Parsed = geoJsonPolygonSchema.safeParse(polygon2);
|
|
738
|
+
if (!polygon1Parsed.success || !polygon2Parsed.success) {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
if (isFullyContainedInGeoJsonPolygon(polygon1, polygon2)) {
|
|
742
|
+
return polygon1;
|
|
743
|
+
}
|
|
744
|
+
if (isFullyContainedInGeoJsonPolygon(polygon2, polygon1)) {
|
|
745
|
+
return polygon2;
|
|
746
|
+
}
|
|
747
|
+
const intersectionResult = intersection$1(polygon1.coordinates, polygon2.coordinates);
|
|
748
|
+
if (intersectionResult.length === 1 && intersectionResult[0]) {
|
|
749
|
+
return {
|
|
750
|
+
type: "Polygon",
|
|
751
|
+
coordinates: intersectionResult[0],
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
type: "MultiPolygon",
|
|
756
|
+
coordinates: intersection$1(polygon1.coordinates, polygon2.coordinates),
|
|
757
|
+
};
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
//! These tools are used to bridge the gap with out poorly typed graphql types
|
|
761
|
+
// Should be ideally be avoided but are needed until we fix the graphql types
|
|
762
|
+
const isDoubleNestedCoords = (coords) => Array.isArray(coords) &&
|
|
763
|
+
Array.isArray(coords[0]) &&
|
|
764
|
+
Array.isArray(coords[0][0]) &&
|
|
765
|
+
typeof coords[0][0][0] === "number";
|
|
766
|
+
const isSingleCoords = (coords) => typeof coords[0] === "number";
|
|
767
|
+
/**
|
|
768
|
+
* @description Returns coordinates in consistent format
|
|
769
|
+
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
770
|
+
* @returns {GeoJsonPosition[]} Array of standardized coordinates
|
|
771
|
+
*/
|
|
772
|
+
const coordinatesToStandardFormat = (inconsistentCoordinates) => {
|
|
773
|
+
if (!inconsistentCoordinates) {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
if (isSingleCoords(inconsistentCoordinates)) {
|
|
777
|
+
return [inconsistentCoordinates];
|
|
778
|
+
}
|
|
779
|
+
if (isDoubleNestedCoords(inconsistentCoordinates)) {
|
|
780
|
+
return inconsistentCoordinates[0] || [];
|
|
781
|
+
}
|
|
782
|
+
if (inconsistentCoordinates[0] && typeof inconsistentCoordinates[0][0] === "number") {
|
|
783
|
+
return inconsistentCoordinates;
|
|
784
|
+
}
|
|
785
|
+
return [];
|
|
786
|
+
};
|
|
787
|
+
/**
|
|
788
|
+
* @description Extracts a point coordinate from a GeoJSON object.
|
|
789
|
+
* @param geoObject A GeoJSON object.
|
|
790
|
+
* @returns {PointCoordinate} A point coordinate.
|
|
791
|
+
*/
|
|
792
|
+
const getPointCoordinateFromGeoJsonObject = (geoObject) => {
|
|
793
|
+
if (!geoObject) {
|
|
794
|
+
return undefined;
|
|
795
|
+
}
|
|
796
|
+
else if ("geometry" in geoObject) {
|
|
797
|
+
return getPointCoordinateFromGeoJsonObject(geoObject.geometry);
|
|
798
|
+
}
|
|
799
|
+
else if ("coordinates" in geoObject &&
|
|
800
|
+
Array.isArray(geoObject.coordinates) &&
|
|
801
|
+
typeof geoObject.coordinates[0] === "number" &&
|
|
802
|
+
typeof geoObject.coordinates[1] === "number") {
|
|
803
|
+
const [point] = coordinatesToStandardFormat(geoObject.coordinates);
|
|
804
|
+
if (point) {
|
|
805
|
+
return { latitude: point[1], longitude: point[0] };
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
/**
|
|
816
|
+
* @description Extracts multiple point coordinates from a GeoJSON object.
|
|
817
|
+
* @param geoObject A GeoJSON object.
|
|
818
|
+
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
819
|
+
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
820
|
+
*/
|
|
821
|
+
const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
822
|
+
if (!geoObject) {
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
else if ("geometry" in geoObject) {
|
|
826
|
+
return getMultipleCoordinatesFromGeoJsonObject(geoObject.geometry);
|
|
827
|
+
}
|
|
828
|
+
else if ("coordinates" in geoObject) {
|
|
829
|
+
return coordinatesToStandardFormat(geoObject.coordinates).map(([longitude, latitude]) => ({ longitude, latitude }));
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
//* -------- Trackunit-invented schemas and types to extend the GeoJson spec -------- *//
|
|
837
|
+
/**
|
|
838
|
+
* Polygon geometry object that explicitly disallows holes.
|
|
839
|
+
*
|
|
840
|
+
* Same as geoJsonPolygonSchema but type disallows holes by
|
|
841
|
+
* using tuple of one single linear ring instead of an array.
|
|
842
|
+
*/
|
|
843
|
+
const tuGeoJsonPolygonNoHolesSchema = z.strictObject({
|
|
844
|
+
//The type is still "Polygon" (not PolygonNoHoles or similar) since it's always
|
|
845
|
+
//compliant with Polygon, just not the other way around
|
|
846
|
+
type: z.literal("Polygon"),
|
|
847
|
+
//uses tuple instead of array to enforce only 1 linear ring aka the polygon itself
|
|
848
|
+
coordinates: z.tuple([geoJsonLinearRingSchema]),
|
|
849
|
+
});
|
|
850
|
+
/**
|
|
851
|
+
* Point radius object.
|
|
852
|
+
* For when you wish to define an area by a point and a radius.
|
|
853
|
+
*
|
|
854
|
+
* radius is in meters
|
|
855
|
+
*/
|
|
856
|
+
const tuGeoJsonPointRadiusSchema = z.strictObject({
|
|
857
|
+
type: z.literal("PointRadius"),
|
|
858
|
+
coordinates: geoJsonPositionSchema,
|
|
859
|
+
radius: z.number().positive(), // in meters
|
|
860
|
+
});
|
|
861
|
+
/**
|
|
862
|
+
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
863
|
+
*/
|
|
864
|
+
const tuGeoJsonRectangularBoxPolygonSchema = z
|
|
865
|
+
.strictObject({
|
|
866
|
+
type: z.literal("Polygon"),
|
|
867
|
+
coordinates: z.array(geoJsonLinearRingSchema),
|
|
868
|
+
})
|
|
869
|
+
.superRefine((data, ctx) => {
|
|
870
|
+
const coordinates = data.coordinates[0];
|
|
871
|
+
// Validate polygon has exactly 5 points
|
|
872
|
+
if ((coordinates === null || coordinates === void 0 ? void 0 : coordinates.length) !== 5) {
|
|
873
|
+
ctx.addIssue({
|
|
874
|
+
code: z.ZodIssueCode.custom,
|
|
875
|
+
message: "Polygon must have exactly 5 coordinates to form a closed box.",
|
|
876
|
+
});
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
// Check each side is either horizontal or vertical
|
|
880
|
+
for (let i = 0; i < 4; i++) {
|
|
881
|
+
const point1 = coordinates[i];
|
|
882
|
+
const point2 = coordinates[i + 1];
|
|
883
|
+
if (point1 === undefined || point2 === undefined) {
|
|
884
|
+
ctx.addIssue({
|
|
885
|
+
code: z.ZodIssueCode.custom,
|
|
886
|
+
message: "Each coordinate must be a defined point.",
|
|
887
|
+
});
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const [x1, y1] = point1;
|
|
891
|
+
const [x2, y2] = point2;
|
|
892
|
+
// Ensure each line segment is either horizontal or vertical
|
|
893
|
+
if (x1 !== x2 && y1 !== y2) {
|
|
894
|
+
ctx.addIssue({
|
|
895
|
+
code: z.ZodIssueCode.custom,
|
|
896
|
+
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
897
|
+
});
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
});
|
|
723
902
|
|
|
724
903
|
/**
|
|
725
904
|
* Group an array of items by a key.
|
|
@@ -1367,4 +1546,4 @@ const uuidv4 = () => {
|
|
|
1367
1546
|
*/
|
|
1368
1547
|
const uuidv5 = (name, namespace) => v5(name, namespace);
|
|
1369
1548
|
|
|
1370
|
-
export { DateTimeFormat, EARTH_RADIUS, HoursAndMinutesFormat, align, alphabeticallySort, arrayLengthCompare, arrayNotEmpty, booleanCompare, capitalize, convertBlobToBase64, convertMetersToYards, convertYardsToMeters, dateCompare, deleteUndefinedKeys, difference, doNothing, enumFromValue, enumFromValueTypesafe, enumOrUndefinedFromValue, exhaustiveCheck, filterByMultiple, formatAddress, formatCoordinates, fuzzySearch, geoJsonBboxSchema, geoJsonGeometrySchema, geoJsonLineStringSchema, geoJsonLinearRingSchema, geoJsonMultiLineStringSchema, geoJsonMultiPointSchema, geoJsonMultiPolygonSchema, geoJsonPointSchema,
|
|
1549
|
+
export { DateTimeFormat, EARTH_RADIUS, HoursAndMinutesFormat, align, alphabeticallySort, arrayLengthCompare, arrayNotEmpty, booleanCompare, capitalize, convertBlobToBase64, convertMetersToYards, convertYardsToMeters, coordinatesToStandardFormat, dateCompare, deleteUndefinedKeys, difference, doNothing, enumFromValue, enumFromValueTypesafe, enumOrUndefinedFromValue, exhaustiveCheck, filterByMultiple, formatAddress, formatCoordinates, fuzzySearch, geoJsonBboxSchema, geoJsonGeometrySchema, geoJsonLineStringSchema, geoJsonLinearRingSchema, geoJsonMultiLineStringSchema, geoJsonMultiPointSchema, geoJsonMultiPolygonSchema, geoJsonPointSchema, geoJsonPolygonSchema, geoJsonPositionSchema, getBboxFromGeoJsonPolygon, getBoundingBoxFromGeoJsonPolygon, getDifferenceBetweenDates, getEndOfDay, getExtremeGeoJsonPointFromPolygon, getFirstLevelObjectPropertyDifferences, getGeoJsonPolygonFromBoundingBox, getGeoJsonPolygonIntersection, getISOStringFromDate, getMultipleCoordinatesFromGeoJsonObject, getPointCoordinateFromGeoJsonObject, getPointCoordinateFromGeoJsonPoint, getPolygonFromBbox, getPolygonFromPointAndRadius, getResizedDimensions, getStartOfDay, groupBy, groupTinyDataToOthers, hourIntervals, intersection, isArrayEqual, isFullyContainedInGeoJsonPolygon, isGeoJsonPointInPolygon, isGeoJsonPositionInLinearRing, isSorted, isUUID, isValidImage, nonNullable, numberCompare, numberCompareUnknownAfterHighest, objNotEmpty, objectEntries, objectFromEntries, objectKeys, objectValues, pick, removeLeftPadding, resizeBlob, resizeImage, size, stringCompare, stringCompareFromKey, stringNaturalCompare, stripHiddenCharacters, titleCase, toID, toIDs, toUUID, trimIds, trimPath, truthy, tuGeoJsonPointRadiusSchema, tuGeoJsonPolygonNoHolesSchema, tuGeoJsonRectangularBoxPolygonSchema, unionArraysByKey, uuidv3, uuidv4, uuidv5 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/shared-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.85",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"uuid": "^10.0.0",
|
|
11
|
-
"zod": "3.22.4"
|
|
11
|
+
"zod": "3.22.4",
|
|
12
|
+
"polygon-clipping": "^0.15.7"
|
|
12
13
|
},
|
|
13
14
|
"module": "./index.esm.js",
|
|
14
15
|
"main": "./index.cjs.js",
|
|
@@ -174,37 +174,3 @@ export type GeoJsonGeometry = z.infer<typeof geoJsonGeometrySchema>;
|
|
|
174
174
|
*/
|
|
175
175
|
export declare const geoJsonBboxSchema: z.ZodEffects<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>, [number, number, number, number], [number, number, number, number]>;
|
|
176
176
|
export type GeoJsonBbox = z.infer<typeof geoJsonBboxSchema>;
|
|
177
|
-
/**
|
|
178
|
-
* Polygon geometry object that explicitly disallows holes.
|
|
179
|
-
* https://tools.ietf.org/html/rfc7946#section-3.1.6
|
|
180
|
-
*/
|
|
181
|
-
export declare const geoJsonPolygonNoHolesSchema: z.ZodObject<{
|
|
182
|
-
type: z.ZodLiteral<"Polygon">;
|
|
183
|
-
coordinates: z.ZodTuple<[z.ZodEffects<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, "many">, [number, number][], [number, number][]>], null>;
|
|
184
|
-
}, "strict", z.ZodTypeAny, {
|
|
185
|
-
type: "Polygon";
|
|
186
|
-
coordinates: [[number, number][]];
|
|
187
|
-
}, {
|
|
188
|
-
type: "Polygon";
|
|
189
|
-
coordinates: [[number, number][]];
|
|
190
|
-
}>;
|
|
191
|
-
export type GeoJsonPolygonNoHoles = z.infer<typeof geoJsonPolygonNoHolesSchema>;
|
|
192
|
-
/**
|
|
193
|
-
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
194
|
-
*/
|
|
195
|
-
export declare const geoJsonRectangularBoxPolygonSchema: z.ZodEffects<z.ZodObject<{
|
|
196
|
-
type: z.ZodLiteral<"Polygon">;
|
|
197
|
-
coordinates: z.ZodArray<z.ZodEffects<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, "many">, [number, number][], [number, number][]>, "many">;
|
|
198
|
-
}, "strict", z.ZodTypeAny, {
|
|
199
|
-
type: "Polygon";
|
|
200
|
-
coordinates: [number, number][][];
|
|
201
|
-
}, {
|
|
202
|
-
type: "Polygon";
|
|
203
|
-
coordinates: [number, number][][];
|
|
204
|
-
}>, {
|
|
205
|
-
type: "Polygon";
|
|
206
|
-
coordinates: [number, number][][];
|
|
207
|
-
}, {
|
|
208
|
-
type: "Polygon";
|
|
209
|
-
coordinates: [number, number][][];
|
|
210
|
-
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { GeoJsonBbox, GeoJsonPoint, GeoJsonPolygon } from "./GeoJsonSchemas";
|
|
1
|
+
import { GeoJsonBbox, GeoJsonLinearRing, GeoJsonMultiPolygon, GeoJsonPoint, GeoJsonPolygon, GeoJsonPosition } from "./GeoJsonSchemas";
|
|
2
|
+
import { TuGeoJsonPolygonNoHoles } from "./TuGeoJsonSchemas";
|
|
2
3
|
export declare const EARTH_RADIUS = 6378137;
|
|
3
4
|
interface PointCoordinate {
|
|
4
5
|
longitude: number;
|
|
@@ -8,31 +9,14 @@ interface BoundingBox {
|
|
|
8
9
|
nw: PointCoordinate;
|
|
9
10
|
se: PointCoordinate;
|
|
10
11
|
}
|
|
11
|
-
interface GeoJSONGeometry {
|
|
12
|
-
type?: unknown;
|
|
13
|
-
coordinates?: number[] | number[][] | number[][][] | null;
|
|
14
|
-
}
|
|
15
|
-
interface GeoJsonFeature {
|
|
16
|
-
type?: unknown;
|
|
17
|
-
geometry?: GeoJSONGeometry | null;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* @description Extracts a point coordinate from a GeoJSON object.
|
|
21
|
-
* @param geoObject A GeoJSON object.
|
|
22
|
-
* @returns {PointCoordinate} A point coordinate.
|
|
23
|
-
*/
|
|
24
|
-
export declare const getPointCoordinateFromGeoJsonObject: (geoObject: GeoJsonFeature | GeoJSONGeometry | undefined | null) => PointCoordinate | undefined;
|
|
25
12
|
/**
|
|
26
|
-
* @description
|
|
27
|
-
* @param geoObject A GeoJSON object.
|
|
28
|
-
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
29
|
-
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
13
|
+
* @description Creates a polygon (with no holes) from a bounding box.
|
|
30
14
|
*/
|
|
31
|
-
export declare const
|
|
15
|
+
export declare const getPolygonFromBbox: (bbox: GeoJsonBbox) => TuGeoJsonPolygonNoHoles;
|
|
32
16
|
/**
|
|
33
|
-
* @description Creates a
|
|
17
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
34
18
|
*/
|
|
35
|
-
export declare const
|
|
19
|
+
export declare const getBboxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => GeoJsonBbox | null;
|
|
36
20
|
/**
|
|
37
21
|
* @description Creates a round polygon from a point and a radius.
|
|
38
22
|
*/
|
|
@@ -40,7 +24,7 @@ export declare const getPolygonFromPointAndRadius: (point: GeoJsonPoint, radius:
|
|
|
40
24
|
/**
|
|
41
25
|
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
42
26
|
*/
|
|
43
|
-
export declare const getBoundingBoxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => BoundingBox |
|
|
27
|
+
export declare const getBoundingBoxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => BoundingBox | null;
|
|
44
28
|
/**
|
|
45
29
|
* @description Creates a GeoJSON Polygon from a TU bounding box.
|
|
46
30
|
*/
|
|
@@ -49,4 +33,41 @@ export declare const getGeoJsonPolygonFromBoundingBox: (boundingBox: BoundingBox
|
|
|
49
33
|
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
50
34
|
*/
|
|
51
35
|
export declare const getPointCoordinateFromGeoJsonPoint: (point: GeoJsonPoint) => PointCoordinate;
|
|
36
|
+
/**
|
|
37
|
+
* @description Gets the extreme point of a polygon in a given direction.
|
|
38
|
+
* @param {object} params - The parameters object
|
|
39
|
+
* @param {GeoJsonPolygon} params.polygon - The polygon to get the extreme point from
|
|
40
|
+
* @param {("top" | "right" | "bottom" | "left")} params.direction - The direction to get the extreme point in
|
|
41
|
+
* @returns {GeoJsonPoint} The extreme point in the given direction
|
|
42
|
+
*/
|
|
43
|
+
export declare const getExtremeGeoJsonPointFromPolygon: ({ polygon, direction, }: {
|
|
44
|
+
polygon: GeoJsonPolygon;
|
|
45
|
+
direction: "top" | "right" | "bottom" | "left";
|
|
46
|
+
}) => GeoJsonPoint | null;
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a position is inside a linear ring. On edge is considered inside.
|
|
49
|
+
*/
|
|
50
|
+
export declare const isGeoJsonPositionInLinearRing: ({ position, linearRing, }: {
|
|
51
|
+
position: GeoJsonPosition;
|
|
52
|
+
linearRing: GeoJsonLinearRing;
|
|
53
|
+
}) => boolean | null;
|
|
54
|
+
/**
|
|
55
|
+
* @description Checks if a point is inside a polygon.
|
|
56
|
+
*/
|
|
57
|
+
export declare const isGeoJsonPointInPolygon: ({ point, polygon, }: {
|
|
58
|
+
point: GeoJsonPoint;
|
|
59
|
+
polygon: GeoJsonPolygon;
|
|
60
|
+
}) => boolean | null;
|
|
61
|
+
/**
|
|
62
|
+
* Checks if polygon1 is fully contained within polygon2
|
|
63
|
+
*/
|
|
64
|
+
export declare const isFullyContainedInGeoJsonPolygon: (polygon1: GeoJsonPolygon, polygon2: GeoJsonPolygon) => boolean | null;
|
|
65
|
+
/**
|
|
66
|
+
* @description Gets the intersection between two GeoJSON polygons. If one polygon is fully contained within the other,
|
|
67
|
+
* returns the contained polygon. Otherwise returns a MultiPolygon representing the intersection.
|
|
68
|
+
* @param polygon1 The first polygon to check intersection
|
|
69
|
+
* @param polygon2 The second polygon to check intersection
|
|
70
|
+
* @returns {(GeoJsonMultiPolygon | GeoJsonPolygon)} The intersection as either a Polygon (if one contains the other) or MultiPolygon
|
|
71
|
+
*/
|
|
72
|
+
export declare const getGeoJsonPolygonIntersection: (polygon1: GeoJsonPolygon, polygon2: GeoJsonPolygon) => GeoJsonMultiPolygon | GeoJsonPolygon | null;
|
|
52
73
|
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GeoJsonPosition } from "./GeoJsonSchemas";
|
|
2
|
+
interface PointCoordinate {
|
|
3
|
+
longitude: number;
|
|
4
|
+
latitude: number;
|
|
5
|
+
}
|
|
6
|
+
interface GeoJSONGeometry {
|
|
7
|
+
type?: unknown;
|
|
8
|
+
coordinates?: GeoJsonPosition | GeoJsonPosition[] | GeoJsonPosition[][] | null;
|
|
9
|
+
}
|
|
10
|
+
interface GeoJsonFeature {
|
|
11
|
+
type?: unknown;
|
|
12
|
+
geometry?: GeoJSONGeometry | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @description Returns coordinates in consistent format
|
|
16
|
+
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
17
|
+
* @returns {GeoJsonPosition[]} Array of standardized coordinates
|
|
18
|
+
*/
|
|
19
|
+
export declare const coordinatesToStandardFormat: (inconsistentCoordinates: GeoJsonPosition | GeoJsonPosition[] | GeoJsonPosition[][] | null | undefined) => [number, number][];
|
|
20
|
+
/**
|
|
21
|
+
* @description Extracts a point coordinate from a GeoJSON object.
|
|
22
|
+
* @param geoObject A GeoJSON object.
|
|
23
|
+
* @returns {PointCoordinate} A point coordinate.
|
|
24
|
+
*/
|
|
25
|
+
export declare const getPointCoordinateFromGeoJsonObject: (geoObject: GeoJsonFeature | GeoJSONGeometry | undefined | null) => PointCoordinate | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* @description Extracts multiple point coordinates from a GeoJSON object.
|
|
28
|
+
* @param geoObject A GeoJSON object.
|
|
29
|
+
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
30
|
+
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
31
|
+
*/
|
|
32
|
+
export declare const getMultipleCoordinatesFromGeoJsonObject: (geoObject: GeoJsonFeature | GeoJSONGeometry | undefined | null) => PointCoordinate[] | undefined;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Polygon geometry object that explicitly disallows holes.
|
|
4
|
+
*
|
|
5
|
+
* Same as geoJsonPolygonSchema but type disallows holes by
|
|
6
|
+
* using tuple of one single linear ring instead of an array.
|
|
7
|
+
*/
|
|
8
|
+
export declare const tuGeoJsonPolygonNoHolesSchema: z.ZodObject<{
|
|
9
|
+
type: z.ZodLiteral<"Polygon">;
|
|
10
|
+
coordinates: z.ZodTuple<[z.ZodEffects<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, "many">, [number, number][], [number, number][]>], null>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
12
|
+
type: "Polygon";
|
|
13
|
+
coordinates: [[number, number][]];
|
|
14
|
+
}, {
|
|
15
|
+
type: "Polygon";
|
|
16
|
+
coordinates: [[number, number][]];
|
|
17
|
+
}>;
|
|
18
|
+
export type TuGeoJsonPolygonNoHoles = z.infer<typeof tuGeoJsonPolygonNoHolesSchema>;
|
|
19
|
+
/**
|
|
20
|
+
* Point radius object.
|
|
21
|
+
* For when you wish to define an area by a point and a radius.
|
|
22
|
+
*
|
|
23
|
+
* radius is in meters
|
|
24
|
+
*/
|
|
25
|
+
export declare const tuGeoJsonPointRadiusSchema: z.ZodObject<{
|
|
26
|
+
type: z.ZodLiteral<"PointRadius">;
|
|
27
|
+
coordinates: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
|
|
28
|
+
radius: z.ZodNumber;
|
|
29
|
+
}, "strict", z.ZodTypeAny, {
|
|
30
|
+
type: "PointRadius";
|
|
31
|
+
coordinates: [number, number];
|
|
32
|
+
radius: number;
|
|
33
|
+
}, {
|
|
34
|
+
type: "PointRadius";
|
|
35
|
+
coordinates: [number, number];
|
|
36
|
+
radius: number;
|
|
37
|
+
}>;
|
|
38
|
+
export type TuGeoJsonPointRadius = z.infer<typeof tuGeoJsonPointRadiusSchema>;
|
|
39
|
+
/**
|
|
40
|
+
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
41
|
+
*/
|
|
42
|
+
export declare const tuGeoJsonRectangularBoxPolygonSchema: z.ZodEffects<z.ZodObject<{
|
|
43
|
+
type: z.ZodLiteral<"Polygon">;
|
|
44
|
+
coordinates: z.ZodArray<z.ZodEffects<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>, "many">, [number, number][], [number, number][]>, "many">;
|
|
45
|
+
}, "strict", z.ZodTypeAny, {
|
|
46
|
+
type: "Polygon";
|
|
47
|
+
coordinates: [number, number][][];
|
|
48
|
+
}, {
|
|
49
|
+
type: "Polygon";
|
|
50
|
+
coordinates: [number, number][][];
|
|
51
|
+
}>, {
|
|
52
|
+
type: "Polygon";
|
|
53
|
+
coordinates: [number, number][][];
|
|
54
|
+
}, {
|
|
55
|
+
type: "Polygon";
|
|
56
|
+
coordinates: [number, number][][];
|
|
57
|
+
}>;
|
|
58
|
+
export type TuGeoJsonRectangularBoxPolygon = z.infer<typeof tuGeoJsonRectangularBoxPolygonSchema>;
|
package/src/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export * from "./fastArrayOperations";
|
|
|
11
11
|
export * from "./filter";
|
|
12
12
|
export * from "./GeoJson/GeoJsonSchemas";
|
|
13
13
|
export * from "./GeoJson/GeoJsonUtils";
|
|
14
|
+
export * from "./GeoJson/TUGeoJsonObjectBridgeUtils";
|
|
15
|
+
export * from "./GeoJson/TuGeoJsonSchemas";
|
|
14
16
|
export * from "./groupBy/groupBy";
|
|
15
17
|
export * from "./GroupingUtility";
|
|
16
18
|
export * from "./idUtils";
|