@trackunit/shared-utils 0.0.84 → 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 +294 -135
- package/index.esm.js +286 -134
- package/package.json +3 -2
- package/src/GeoJson/GeoJsonSchemas.d.ts +0 -34
- package/src/GeoJson/GeoJsonUtils.d.ts +44 -30
- 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,59 +528,239 @@ 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
|
-
|
|
531
|
+
|
|
532
|
+
const EARTH_RADIUS = 6378137; // Earth’s mean radius in meters
|
|
540
533
|
/**
|
|
541
|
-
*
|
|
542
|
-
* https://tools.ietf.org/html/rfc7946#section-3.1.6
|
|
534
|
+
* @description Creates a polygon (with no holes) from a bounding box.
|
|
543
535
|
*/
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
536
|
+
const getPolygonFromBbox = (bbox) => {
|
|
537
|
+
const [minLon, minLat, maxLon, maxLat] = bbox;
|
|
538
|
+
return {
|
|
539
|
+
type: "Polygon",
|
|
540
|
+
coordinates: [
|
|
541
|
+
[
|
|
542
|
+
[minLon, minLat],
|
|
543
|
+
[maxLon, minLat],
|
|
544
|
+
[maxLon, maxLat],
|
|
545
|
+
[minLon, maxLat],
|
|
546
|
+
[minLon, minLat],
|
|
547
|
+
],
|
|
548
|
+
],
|
|
549
|
+
};
|
|
550
|
+
};
|
|
549
551
|
/**
|
|
550
|
-
*
|
|
552
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
551
553
|
*/
|
|
552
|
-
const
|
|
553
|
-
.
|
|
554
|
-
|
|
555
|
-
|
|
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;
|
|
554
|
+
const getBboxFromGeoJsonPolygon = (polygon) => {
|
|
555
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
556
|
+
if (!polygonParsed.success) {
|
|
557
|
+
return null;
|
|
566
558
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
+
};
|
|
568
|
+
/**
|
|
569
|
+
* @description Creates a round polygon from a point and a radius.
|
|
570
|
+
*/
|
|
571
|
+
const getPolygonFromPointAndRadius = (point, radius) => {
|
|
572
|
+
const [lon, lat] = point.coordinates;
|
|
573
|
+
// Adjust the number of points based on radius (resolution)
|
|
574
|
+
const pointsCount = Math.max(32, Math.floor(radius / 100)); // More points for larger radius
|
|
575
|
+
const angleStep = (2 * Math.PI) / pointsCount;
|
|
576
|
+
const coordinates = [];
|
|
577
|
+
for (let i = 0; i <= pointsCount; i++) {
|
|
578
|
+
const angle = i * angleStep;
|
|
579
|
+
// Calculate offset in latitude and longitude
|
|
580
|
+
const deltaLat = (radius / EARTH_RADIUS) * (180 / Math.PI);
|
|
581
|
+
const deltaLon = deltaLat / Math.cos((lat * Math.PI) / 180);
|
|
582
|
+
// Calculate new coordinates based on angle
|
|
583
|
+
const newLat = lat + deltaLat * Math.sin(angle);
|
|
584
|
+
const newLon = lon + deltaLon * Math.cos(angle);
|
|
585
|
+
coordinates.push([newLon, newLat]);
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
type: "Polygon",
|
|
589
|
+
coordinates: [coordinates],
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
/**
|
|
593
|
+
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
594
|
+
*/
|
|
595
|
+
const getBoundingBoxFromGeoJsonPolygon = (polygon) => {
|
|
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;
|
|
604
|
+
}
|
|
605
|
+
const latitudes = points.map(point => point[1]);
|
|
606
|
+
const longitudes = points.map(point => point[0]);
|
|
607
|
+
return {
|
|
608
|
+
nw: {
|
|
609
|
+
latitude: Math.max(...latitudes),
|
|
610
|
+
longitude: Math.min(...longitudes),
|
|
611
|
+
},
|
|
612
|
+
se: {
|
|
613
|
+
latitude: Math.min(...latitudes),
|
|
614
|
+
longitude: Math.max(...longitudes),
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* @description Creates a GeoJSON Polygon from a TU bounding box.
|
|
620
|
+
*/
|
|
621
|
+
const getGeoJsonPolygonFromBoundingBox = (boundingBox) => {
|
|
622
|
+
const { nw, se } = boundingBox;
|
|
623
|
+
return {
|
|
624
|
+
type: "Polygon",
|
|
625
|
+
coordinates: [
|
|
626
|
+
[
|
|
627
|
+
[nw.longitude, nw.latitude], // Northwest corner
|
|
628
|
+
[se.longitude, nw.latitude], // Northeast corner
|
|
629
|
+
[se.longitude, se.latitude], // Southeast corner
|
|
630
|
+
[nw.longitude, se.latitude], // Southwest corner
|
|
631
|
+
[nw.longitude, nw.latitude], // Close the loop back to Northwest corner
|
|
632
|
+
],
|
|
633
|
+
],
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
638
|
+
*/
|
|
639
|
+
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
640
|
+
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
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];
|
|
571
696
|
if (!point1 || !point2) {
|
|
572
|
-
|
|
573
|
-
code: zod.z.ZodIssueCode.custom,
|
|
574
|
-
message: "Each coordinate must be a defined point.",
|
|
575
|
-
});
|
|
576
|
-
return;
|
|
697
|
+
continue;
|
|
577
698
|
}
|
|
578
|
-
const [
|
|
579
|
-
const [
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
code: zod.z.ZodIssueCode.custom,
|
|
584
|
-
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
585
|
-
});
|
|
586
|
-
return;
|
|
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;
|
|
587
704
|
}
|
|
588
705
|
}
|
|
589
|
-
|
|
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
|
+
};
|
|
590
761
|
|
|
591
|
-
|
|
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
|
|
592
764
|
const isDoubleNestedCoords = (coords) => Array.isArray(coords) &&
|
|
593
765
|
Array.isArray(coords[0]) &&
|
|
594
766
|
Array.isArray(coords[0][0]) &&
|
|
@@ -597,7 +769,7 @@ const isSingleCoords = (coords) => typeof coords[0] === "number";
|
|
|
597
769
|
/**
|
|
598
770
|
* @description Returns coordinates in consistent format
|
|
599
771
|
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
600
|
-
* @returns
|
|
772
|
+
* @returns {GeoJsonPosition[]} Array of standardized coordinates
|
|
601
773
|
*/
|
|
602
774
|
const coordinatesToStandardFormat = (inconsistentCoordinates) => {
|
|
603
775
|
if (!inconsistentCoordinates) {
|
|
@@ -662,93 +834,73 @@ const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
|
662
834
|
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
663
835
|
}
|
|
664
836
|
};
|
|
837
|
+
|
|
838
|
+
//* -------- Trackunit-invented schemas and types to extend the GeoJson spec -------- *//
|
|
665
839
|
/**
|
|
666
|
-
*
|
|
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.
|
|
667
844
|
*/
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
[maxLon, minLat],
|
|
676
|
-
[maxLon, maxLat],
|
|
677
|
-
[minLon, maxLat],
|
|
678
|
-
[minLon, minLat],
|
|
679
|
-
],
|
|
680
|
-
],
|
|
681
|
-
};
|
|
682
|
-
};
|
|
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
|
+
});
|
|
683
852
|
/**
|
|
684
|
-
*
|
|
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
|
|
685
857
|
*/
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const coordinates = [];
|
|
692
|
-
for (let i = 0; i <= pointsCount; i++) {
|
|
693
|
-
const angle = i * angleStep;
|
|
694
|
-
// Calculate offset in latitude and longitude
|
|
695
|
-
const deltaLat = (radius / EARTH_RADIUS) * (180 / Math.PI);
|
|
696
|
-
const deltaLon = deltaLat / Math.cos((lat * Math.PI) / 180);
|
|
697
|
-
// Calculate new coordinates based on angle
|
|
698
|
-
const newLat = lat + deltaLat * Math.sin(angle);
|
|
699
|
-
const newLon = lon + deltaLon * Math.cos(angle);
|
|
700
|
-
coordinates.push([newLon, newLat]);
|
|
701
|
-
}
|
|
702
|
-
return {
|
|
703
|
-
type: "Polygon",
|
|
704
|
-
coordinates: [coordinates],
|
|
705
|
-
};
|
|
706
|
-
};
|
|
858
|
+
const tuGeoJsonPointRadiusSchema = zod.z.strictObject({
|
|
859
|
+
type: zod.z.literal("PointRadius"),
|
|
860
|
+
coordinates: geoJsonPositionSchema,
|
|
861
|
+
radius: zod.z.number().positive(), // in meters
|
|
862
|
+
});
|
|
707
863
|
/**
|
|
708
|
-
*
|
|
864
|
+
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
709
865
|
*/
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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;
|
|
716
880
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
[nw.longitude, se.latitude], // Southwest corner
|
|
741
|
-
[nw.longitude, nw.latitude], // Close the loop back to Northwest corner
|
|
742
|
-
],
|
|
743
|
-
],
|
|
744
|
-
};
|
|
745
|
-
};
|
|
746
|
-
/**
|
|
747
|
-
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
748
|
-
*/
|
|
749
|
-
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
750
|
-
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
751
|
-
};
|
|
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
|
+
});
|
|
752
904
|
|
|
753
905
|
/**
|
|
754
906
|
* Group an array of items by a key.
|
|
@@ -1428,15 +1580,16 @@ exports.geoJsonMultiLineStringSchema = geoJsonMultiLineStringSchema;
|
|
|
1428
1580
|
exports.geoJsonMultiPointSchema = geoJsonMultiPointSchema;
|
|
1429
1581
|
exports.geoJsonMultiPolygonSchema = geoJsonMultiPolygonSchema;
|
|
1430
1582
|
exports.geoJsonPointSchema = geoJsonPointSchema;
|
|
1431
|
-
exports.geoJsonPolygonNoHolesSchema = geoJsonPolygonNoHolesSchema;
|
|
1432
1583
|
exports.geoJsonPolygonSchema = geoJsonPolygonSchema;
|
|
1433
1584
|
exports.geoJsonPositionSchema = geoJsonPositionSchema;
|
|
1434
|
-
exports.
|
|
1585
|
+
exports.getBboxFromGeoJsonPolygon = getBboxFromGeoJsonPolygon;
|
|
1435
1586
|
exports.getBoundingBoxFromGeoJsonPolygon = getBoundingBoxFromGeoJsonPolygon;
|
|
1436
1587
|
exports.getDifferenceBetweenDates = getDifferenceBetweenDates;
|
|
1437
1588
|
exports.getEndOfDay = getEndOfDay;
|
|
1589
|
+
exports.getExtremeGeoJsonPointFromPolygon = getExtremeGeoJsonPointFromPolygon;
|
|
1438
1590
|
exports.getFirstLevelObjectPropertyDifferences = getFirstLevelObjectPropertyDifferences;
|
|
1439
1591
|
exports.getGeoJsonPolygonFromBoundingBox = getGeoJsonPolygonFromBoundingBox;
|
|
1592
|
+
exports.getGeoJsonPolygonIntersection = getGeoJsonPolygonIntersection;
|
|
1440
1593
|
exports.getISOStringFromDate = getISOStringFromDate;
|
|
1441
1594
|
exports.getMultipleCoordinatesFromGeoJsonObject = getMultipleCoordinatesFromGeoJsonObject;
|
|
1442
1595
|
exports.getPointCoordinateFromGeoJsonObject = getPointCoordinateFromGeoJsonObject;
|
|
@@ -1450,6 +1603,9 @@ exports.groupTinyDataToOthers = groupTinyDataToOthers;
|
|
|
1450
1603
|
exports.hourIntervals = hourIntervals;
|
|
1451
1604
|
exports.intersection = intersection;
|
|
1452
1605
|
exports.isArrayEqual = isArrayEqual;
|
|
1606
|
+
exports.isFullyContainedInGeoJsonPolygon = isFullyContainedInGeoJsonPolygon;
|
|
1607
|
+
exports.isGeoJsonPointInPolygon = isGeoJsonPointInPolygon;
|
|
1608
|
+
exports.isGeoJsonPositionInLinearRing = isGeoJsonPositionInLinearRing;
|
|
1453
1609
|
exports.isSorted = isSorted;
|
|
1454
1610
|
exports.isUUID = isUUID;
|
|
1455
1611
|
exports.isValidImage = isValidImage;
|
|
@@ -1477,6 +1633,9 @@ exports.toUUID = toUUID;
|
|
|
1477
1633
|
exports.trimIds = trimIds;
|
|
1478
1634
|
exports.trimPath = trimPath;
|
|
1479
1635
|
exports.truthy = truthy;
|
|
1636
|
+
exports.tuGeoJsonPointRadiusSchema = tuGeoJsonPointRadiusSchema;
|
|
1637
|
+
exports.tuGeoJsonPolygonNoHolesSchema = tuGeoJsonPolygonNoHolesSchema;
|
|
1638
|
+
exports.tuGeoJsonRectangularBoxPolygonSchema = tuGeoJsonRectangularBoxPolygonSchema;
|
|
1480
1639
|
exports.unionArraysByKey = unionArraysByKey;
|
|
1481
1640
|
exports.uuidv3 = uuidv3;
|
|
1482
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,59 +526,239 @@ 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
|
-
|
|
529
|
+
|
|
530
|
+
const EARTH_RADIUS = 6378137; // Earth’s mean radius in meters
|
|
538
531
|
/**
|
|
539
|
-
*
|
|
540
|
-
* https://tools.ietf.org/html/rfc7946#section-3.1.6
|
|
532
|
+
* @description Creates a polygon (with no holes) from a bounding box.
|
|
541
533
|
*/
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
534
|
+
const getPolygonFromBbox = (bbox) => {
|
|
535
|
+
const [minLon, minLat, maxLon, maxLat] = bbox;
|
|
536
|
+
return {
|
|
537
|
+
type: "Polygon",
|
|
538
|
+
coordinates: [
|
|
539
|
+
[
|
|
540
|
+
[minLon, minLat],
|
|
541
|
+
[maxLon, minLat],
|
|
542
|
+
[maxLon, maxLat],
|
|
543
|
+
[minLon, maxLat],
|
|
544
|
+
[minLon, minLat],
|
|
545
|
+
],
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
};
|
|
547
549
|
/**
|
|
548
|
-
*
|
|
550
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
549
551
|
*/
|
|
550
|
-
const
|
|
551
|
-
.
|
|
552
|
-
|
|
553
|
-
|
|
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;
|
|
552
|
+
const getBboxFromGeoJsonPolygon = (polygon) => {
|
|
553
|
+
const polygonParsed = geoJsonPolygonSchema.safeParse(polygon);
|
|
554
|
+
if (!polygonParsed.success) {
|
|
555
|
+
return null;
|
|
564
556
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
};
|
|
566
|
+
/**
|
|
567
|
+
* @description Creates a round polygon from a point and a radius.
|
|
568
|
+
*/
|
|
569
|
+
const getPolygonFromPointAndRadius = (point, radius) => {
|
|
570
|
+
const [lon, lat] = point.coordinates;
|
|
571
|
+
// Adjust the number of points based on radius (resolution)
|
|
572
|
+
const pointsCount = Math.max(32, Math.floor(radius / 100)); // More points for larger radius
|
|
573
|
+
const angleStep = (2 * Math.PI) / pointsCount;
|
|
574
|
+
const coordinates = [];
|
|
575
|
+
for (let i = 0; i <= pointsCount; i++) {
|
|
576
|
+
const angle = i * angleStep;
|
|
577
|
+
// Calculate offset in latitude and longitude
|
|
578
|
+
const deltaLat = (radius / EARTH_RADIUS) * (180 / Math.PI);
|
|
579
|
+
const deltaLon = deltaLat / Math.cos((lat * Math.PI) / 180);
|
|
580
|
+
// Calculate new coordinates based on angle
|
|
581
|
+
const newLat = lat + deltaLat * Math.sin(angle);
|
|
582
|
+
const newLon = lon + deltaLon * Math.cos(angle);
|
|
583
|
+
coordinates.push([newLon, newLat]);
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
type: "Polygon",
|
|
587
|
+
coordinates: [coordinates],
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
592
|
+
*/
|
|
593
|
+
const getBoundingBoxFromGeoJsonPolygon = (polygon) => {
|
|
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;
|
|
602
|
+
}
|
|
603
|
+
const latitudes = points.map(point => point[1]);
|
|
604
|
+
const longitudes = points.map(point => point[0]);
|
|
605
|
+
return {
|
|
606
|
+
nw: {
|
|
607
|
+
latitude: Math.max(...latitudes),
|
|
608
|
+
longitude: Math.min(...longitudes),
|
|
609
|
+
},
|
|
610
|
+
se: {
|
|
611
|
+
latitude: Math.min(...latitudes),
|
|
612
|
+
longitude: Math.max(...longitudes),
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* @description Creates a GeoJSON Polygon from a TU bounding box.
|
|
618
|
+
*/
|
|
619
|
+
const getGeoJsonPolygonFromBoundingBox = (boundingBox) => {
|
|
620
|
+
const { nw, se } = boundingBox;
|
|
621
|
+
return {
|
|
622
|
+
type: "Polygon",
|
|
623
|
+
coordinates: [
|
|
624
|
+
[
|
|
625
|
+
[nw.longitude, nw.latitude], // Northwest corner
|
|
626
|
+
[se.longitude, nw.latitude], // Northeast corner
|
|
627
|
+
[se.longitude, se.latitude], // Southeast corner
|
|
628
|
+
[nw.longitude, se.latitude], // Southwest corner
|
|
629
|
+
[nw.longitude, nw.latitude], // Close the loop back to Northwest corner
|
|
630
|
+
],
|
|
631
|
+
],
|
|
632
|
+
};
|
|
633
|
+
};
|
|
634
|
+
/**
|
|
635
|
+
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
636
|
+
*/
|
|
637
|
+
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
638
|
+
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
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];
|
|
569
694
|
if (!point1 || !point2) {
|
|
570
|
-
|
|
571
|
-
code: z.ZodIssueCode.custom,
|
|
572
|
-
message: "Each coordinate must be a defined point.",
|
|
573
|
-
});
|
|
574
|
-
return;
|
|
695
|
+
continue;
|
|
575
696
|
}
|
|
576
|
-
const [
|
|
577
|
-
const [
|
|
578
|
-
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
code: z.ZodIssueCode.custom,
|
|
582
|
-
message: "Polygon sides must be horizontal or vertical to form a box shape.",
|
|
583
|
-
});
|
|
584
|
-
return;
|
|
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;
|
|
585
702
|
}
|
|
586
703
|
}
|
|
587
|
-
|
|
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
|
+
};
|
|
588
759
|
|
|
589
|
-
|
|
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
|
|
590
762
|
const isDoubleNestedCoords = (coords) => Array.isArray(coords) &&
|
|
591
763
|
Array.isArray(coords[0]) &&
|
|
592
764
|
Array.isArray(coords[0][0]) &&
|
|
@@ -595,7 +767,7 @@ const isSingleCoords = (coords) => typeof coords[0] === "number";
|
|
|
595
767
|
/**
|
|
596
768
|
* @description Returns coordinates in consistent format
|
|
597
769
|
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
598
|
-
* @returns
|
|
770
|
+
* @returns {GeoJsonPosition[]} Array of standardized coordinates
|
|
599
771
|
*/
|
|
600
772
|
const coordinatesToStandardFormat = (inconsistentCoordinates) => {
|
|
601
773
|
if (!inconsistentCoordinates) {
|
|
@@ -660,93 +832,73 @@ const getMultipleCoordinatesFromGeoJsonObject = (geoObject) => {
|
|
|
660
832
|
throw new Error(`Unable to extract point coordinate from ${JSON.stringify(geoObject)}`);
|
|
661
833
|
}
|
|
662
834
|
};
|
|
835
|
+
|
|
836
|
+
//* -------- Trackunit-invented schemas and types to extend the GeoJson spec -------- *//
|
|
663
837
|
/**
|
|
664
|
-
*
|
|
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.
|
|
665
842
|
*/
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
[maxLon, minLat],
|
|
674
|
-
[maxLon, maxLat],
|
|
675
|
-
[minLon, maxLat],
|
|
676
|
-
[minLon, minLat],
|
|
677
|
-
],
|
|
678
|
-
],
|
|
679
|
-
};
|
|
680
|
-
};
|
|
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
|
+
});
|
|
681
850
|
/**
|
|
682
|
-
*
|
|
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
|
|
683
855
|
*/
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const coordinates = [];
|
|
690
|
-
for (let i = 0; i <= pointsCount; i++) {
|
|
691
|
-
const angle = i * angleStep;
|
|
692
|
-
// Calculate offset in latitude and longitude
|
|
693
|
-
const deltaLat = (radius / EARTH_RADIUS) * (180 / Math.PI);
|
|
694
|
-
const deltaLon = deltaLat / Math.cos((lat * Math.PI) / 180);
|
|
695
|
-
// Calculate new coordinates based on angle
|
|
696
|
-
const newLat = lat + deltaLat * Math.sin(angle);
|
|
697
|
-
const newLon = lon + deltaLon * Math.cos(angle);
|
|
698
|
-
coordinates.push([newLon, newLat]);
|
|
699
|
-
}
|
|
700
|
-
return {
|
|
701
|
-
type: "Polygon",
|
|
702
|
-
coordinates: [coordinates],
|
|
703
|
-
};
|
|
704
|
-
};
|
|
856
|
+
const tuGeoJsonPointRadiusSchema = z.strictObject({
|
|
857
|
+
type: z.literal("PointRadius"),
|
|
858
|
+
coordinates: geoJsonPositionSchema,
|
|
859
|
+
radius: z.number().positive(), // in meters
|
|
860
|
+
});
|
|
705
861
|
/**
|
|
706
|
-
*
|
|
862
|
+
* A Polygon with exactly 5 points and 4 horizontal/vertical sides that form a normal rectangular box.
|
|
707
863
|
*/
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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;
|
|
714
878
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
[nw.longitude, se.latitude], // Southwest corner
|
|
739
|
-
[nw.longitude, nw.latitude], // Close the loop back to Northwest corner
|
|
740
|
-
],
|
|
741
|
-
],
|
|
742
|
-
};
|
|
743
|
-
};
|
|
744
|
-
/**
|
|
745
|
-
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
746
|
-
*/
|
|
747
|
-
const getPointCoordinateFromGeoJsonPoint = (point) => {
|
|
748
|
-
return { latitude: point.coordinates[1], longitude: point.coordinates[0] };
|
|
749
|
-
};
|
|
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
|
+
});
|
|
750
902
|
|
|
751
903
|
/**
|
|
752
904
|
* Group an array of items by a key.
|
|
@@ -1394,4 +1546,4 @@ const uuidv4 = () => {
|
|
|
1394
1546
|
*/
|
|
1395
1547
|
const uuidv5 = (name, namespace) => v5(name, namespace);
|
|
1396
1548
|
|
|
1397
|
-
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,
|
|
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,38 +9,14 @@ interface BoundingBox {
|
|
|
8
9
|
nw: PointCoordinate;
|
|
9
10
|
se: PointCoordinate;
|
|
10
11
|
}
|
|
11
|
-
export type GeoPoint = [number, number];
|
|
12
|
-
interface GeoJSONGeometry {
|
|
13
|
-
type?: unknown;
|
|
14
|
-
coordinates?: GeoPoint | GeoPoint[] | GeoPoint[][] | null;
|
|
15
|
-
}
|
|
16
|
-
interface GeoJsonFeature {
|
|
17
|
-
type?: unknown;
|
|
18
|
-
geometry?: GeoJSONGeometry | null;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* @description Returns coordinates in consistent format
|
|
22
|
-
* @param inconsistentCoordinates Single point, array of points or nested array of points
|
|
23
|
-
* @returns GeoPoint[]
|
|
24
|
-
*/
|
|
25
|
-
export declare const coordinatesToStandardFormat: (inconsistentCoordinates: GeoPoint | GeoPoint[] | GeoPoint[][] | null | undefined) => GeoPoint[];
|
|
26
12
|
/**
|
|
27
|
-
* @description
|
|
28
|
-
* @param geoObject A GeoJSON object.
|
|
29
|
-
* @returns {PointCoordinate} A point coordinate.
|
|
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
|
|
34
|
-
* @param geoObject A GeoJSON object.
|
|
35
|
-
* @returns {PointCoordinate[]} An array of point coordinates.
|
|
36
|
-
* @example getMultipleCoordinatesFromGeoJsonObject({ type: "Point", coordinates: [1, 2] }) // [{ longitude: 1, latitude: 2 }]
|
|
17
|
+
* @description Creates a bounding box from a GeoJSON Polygon.
|
|
37
18
|
*/
|
|
38
|
-
export declare const
|
|
39
|
-
/**
|
|
40
|
-
* @description Creates a polygon from a bounding box.
|
|
41
|
-
*/
|
|
42
|
-
export declare const getPolygonFromBbox: (bbox: GeoJsonBbox) => GeoJsonPolygon;
|
|
19
|
+
export declare const getBboxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => GeoJsonBbox | null;
|
|
43
20
|
/**
|
|
44
21
|
* @description Creates a round polygon from a point and a radius.
|
|
45
22
|
*/
|
|
@@ -47,7 +24,7 @@ export declare const getPolygonFromPointAndRadius: (point: GeoJsonPoint, radius:
|
|
|
47
24
|
/**
|
|
48
25
|
* @description Creates a TU bounding box from a GeoJson Polygon.
|
|
49
26
|
*/
|
|
50
|
-
export declare const getBoundingBoxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => BoundingBox |
|
|
27
|
+
export declare const getBoundingBoxFromGeoJsonPolygon: (polygon: GeoJsonPolygon) => BoundingBox | null;
|
|
51
28
|
/**
|
|
52
29
|
* @description Creates a GeoJSON Polygon from a TU bounding box.
|
|
53
30
|
*/
|
|
@@ -56,4 +33,41 @@ export declare const getGeoJsonPolygonFromBoundingBox: (boundingBox: BoundingBox
|
|
|
56
33
|
* @description Creates TU point coordinate from a GeoJSON Point.
|
|
57
34
|
*/
|
|
58
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;
|
|
59
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";
|