@theseam/ui-common 1.0.2-beta.87 → 2.0.1-beta.101

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.
@@ -1,10 +1,11 @@
1
1
  import { switchMap, startWith, map, distinctUntilChanged, mapTo, filter, take, pairwise, toArray, tap, auditTime, share } from 'rxjs/operators';
2
2
  import { of, merge, interval, from, combineLatest, Observable, isObservable, Subject, BehaviorSubject, EMPTY } from 'rxjs';
3
3
  import area from '@turf/area';
4
- import { convertArea } from '@turf/helpers';
4
+ import { convertArea, polygon } from '@turf/helpers';
5
5
  import kinks from '@turf/kinks';
6
6
  import { fileTypeFromBuffer } from 'file-type';
7
7
  import shp from 'shpjs';
8
+ import booleanContains from '@turf/boolean-contains';
8
9
 
9
10
  /**
10
11
  *
@@ -476,6 +477,18 @@ function notNullOrUndefined(value) {
476
477
  return value !== null && value !== undefined;
477
478
  }
478
479
 
480
+ /**
481
+ * Checks if a single polygon ring's coordinate count violates the given
482
+ * min/max point bounds. `max` is only applied when it is greater than `min`.
483
+ */
484
+ function polygonViolatesMinMax(coordinateLength, min, max) {
485
+ if (coordinateLength < min ||
486
+ (notNullOrUndefined(max) && max > min && coordinateLength > max)) {
487
+ return true;
488
+ }
489
+ return false;
490
+ }
491
+
479
492
  const MIN_MAX_POINTS_VALIDATOR_NAME = 'min-max-points';
480
493
  function minMaxPointsValidator(min = 3, max) {
481
494
  return (control) => {
@@ -522,13 +535,6 @@ function collectionViolatesMinMax(featureCollection, min, max) {
522
535
  }
523
536
  return false;
524
537
  }
525
- function polygonViolatesMinMax(coordinateLength, min, max) {
526
- if (coordinateLength < min ||
527
- (notNullOrUndefined(max) && max > min && coordinateLength > max)) {
528
- return true;
529
- }
530
- return false;
531
- }
532
538
 
533
539
  const NO_EMPTY_FEATURE_COLLECTION_VALIDATOR_NAME = 'no-empty-feature-collection';
534
540
  function noEmptyFeatureCollectionValidator() {
@@ -819,11 +825,11 @@ function splitMultPolygon(multiPolygon) {
819
825
  }
820
826
 
821
827
  /**
822
- * Close all polygons in the GeoJSON.
828
+ * Close all polygons in the GeoJSON so the first and last position of every
829
+ * ring are identical.
823
830
  *
824
- * Google Maps requires that polygons be closed, but not all libraries have that
825
- * requirement. This may add redundent points to the GeoJSON, but it will ensure
826
- * that the GeoJSON is valid for Google Maps.
831
+ * Google Maps requires closed polygon rings, but not all libraries produce
832
+ * them. This is idempotent: rings that are already closed are left unchanged.
827
833
  */
828
834
  function closePolygons(geoJson) {
829
835
  if (geoJson.type === 'FeatureCollection') {
@@ -834,6 +840,12 @@ function closePolygons(geoJson) {
834
840
  else if (geoJson.type === 'Feature') {
835
841
  closePolygonsFeature(geoJson);
836
842
  }
843
+ else if (geoJson.type === 'Polygon') {
844
+ closePolygon(geoJson);
845
+ }
846
+ else if (geoJson.type === 'MultiPolygon') {
847
+ closeMultiPolygon(geoJson);
848
+ }
837
849
  }
838
850
  function closePolygonsFeature(feature) {
839
851
  if (feature.geometry.type === 'Polygon') {
@@ -844,17 +856,96 @@ function closePolygonsFeature(feature) {
844
856
  }
845
857
  }
846
858
  function closePolygon(polygon) {
847
- for (const c of polygon.coordinates) {
848
- c.push(c[0]);
859
+ for (const ring of polygon.coordinates) {
860
+ closeRing(ring);
849
861
  }
850
862
  }
851
863
  function closeMultiPolygon(multiPolygon) {
852
- for (const p of multiPolygon.coordinates) {
853
- for (const c of p) {
854
- c.push(c[0]);
864
+ for (const polygon of multiPolygon.coordinates) {
865
+ for (const ring of polygon) {
866
+ closeRing(ring);
855
867
  }
856
868
  }
857
869
  }
870
+ /**
871
+ * Appends the first position to the end of a ring only when it is not already
872
+ * closed. Exact equality is correct here: the closing point is a copy of the
873
+ * first point, so we compare a value against its own copy.
874
+ */
875
+ function closeRing(ring) {
876
+ if (ring.length < 1) {
877
+ return;
878
+ }
879
+ const first = ring[0];
880
+ const last = ring[ring.length - 1];
881
+ if (first[0] === last[0] && first[1] === last[1]) {
882
+ return;
883
+ }
884
+ ring.push([...first]);
885
+ }
886
+
887
+ /** Count distinct vertices in a ring, ignoring an explicit closing point. */
888
+ function distinctVertexCount(ring) {
889
+ if (ring.length === 0) {
890
+ return 0;
891
+ }
892
+ const first = ring[0];
893
+ const last = ring[ring.length - 1];
894
+ const isClosed = ring.length > 1 && first[0] === last[0] && first[1] === last[1];
895
+ return isClosed ? ring.length - 1 : ring.length;
896
+ }
897
+ /**
898
+ * Whether a polygon's outer ring has at least `min` distinct vertices.
899
+ * Ring closure (a repeated first/last point) does not count toward the total,
900
+ * so this behaves the same for closed and unclosed rings.
901
+ */
902
+ function polygonHasMinDistinctVertices(polygon, min = 3) {
903
+ const outer = polygon.coordinates[0] ?? [];
904
+ return !polygonViolatesMinMax(distinctVertexCount(outer), min, undefined);
905
+ }
906
+
907
+ /**
908
+ * Whether `outer` fully contains `inner`, using turf's boolean-contains on the
909
+ * GeoJSON geometries directly (no Google Maps dependency).
910
+ */
911
+ function polygonContains(outer, inner) {
912
+ return booleanContains(polygon(outer.coordinates), polygon(inner.coordinates));
913
+ }
914
+
915
+ /**
916
+ * Whether a ring is wound clockwise, via the shoelace signed-area sign.
917
+ * Sum of (x2 - x1) * (y2 + y1) is positive for clockwise rings in standard
918
+ * (x = lng, y = lat) orientation.
919
+ */
920
+ function ringIsClockwise(ring) {
921
+ let sum = 0;
922
+ for (let i = 0; i < ring.length - 1; i++) {
923
+ const [x1, y1] = ring[i];
924
+ const [x2, y2] = ring[i + 1];
925
+ sum += (x2 - x1) * (y2 + y1);
926
+ }
927
+ return sum > 0;
928
+ }
929
+ /** Return `ring` wound opposite to `reference`, reversing a copy if needed. */
930
+ function ringWoundOpposite(reference, ring) {
931
+ if (ringIsClockwise(reference) === ringIsClockwise(ring)) {
932
+ return [...ring].reverse();
933
+ }
934
+ return [...ring];
935
+ }
936
+ /**
937
+ * Append the outer ring of `hole` to `exterior` as an interior ring (a
938
+ * cutout), wound opposite the exterior ring so it renders as a hole. Returns a
939
+ * new Polygon; inputs are not mutated.
940
+ */
941
+ function addHoleToPolygon(exterior, hole) {
942
+ const exteriorRing = exterior.coordinates[0];
943
+ const holeRing = ringWoundOpposite(exteriorRing, hole.coordinates[0]);
944
+ return {
945
+ type: 'Polygon',
946
+ coordinates: [...exterior.coordinates, holeRing],
947
+ };
948
+ }
858
949
 
859
950
  // Based on source: https://github.com/sindresorhus/array-move/blob/main/index.js
860
951
  /**
@@ -1403,5 +1494,5 @@ function sanitizeFilename(filename, options) {
1403
1494
  * Generated bundle index. Do not edit.
1404
1495
  */
1405
1496
 
1406
- export { IS_FEATURE_COLLECTION_VALIDATOR_NAME, IS_ONLY_GEOMETRY_TYPES_VALIDATOR_NAME, MIN_MAX_POINTS_VALIDATOR_NAME, NO_EMPTY_FEATURE_COLLECTION_VALIDATOR_NAME, NO_INNER_RINGS_VALIDATOR_NAME, NO_KINKS_VALIDATOR_NAME, PollingTickerOptions, Refreshable, activatedRoutesWithDataProperty, arrayMoveImmutable, arrayMoveMutable, calcPercentage, closePolygons, coerceFeatureCollection, createPadding, deleteProperties, deleteProperty, fileBufferToBlob, fileBufferToObjectUrl, fileDataFromBuffer, fractionalDigitsCount, geoJsonToArea, getAttribute, getClosestWidgetCdkDrag, getControlName, getControlPath, hasAttribute, hasProperty, hasRequiredControl, isAbsoluteUrl, isEmptyInputValue, isEmptyUrlRoute, isFeatureCollectionValidator, isNullOrUndefined, isNullOrUndefinedOrEmpty, isNumeric, isOnlyGeometryTypes, isOnlyGeometryTypesValidator, leafChildRoute, loadStyle, loadStyleSheet, mapEach, mergePolygons, minMaxPointsValidator, noEmptyFeatureCollectionValidator, noInnerRingsValidator, noKinksValidator, notNullOrUndefined, notNullOrUndefinedOrEmpty, observeControlIsDifferent, observeControlStatus, observeControlValid, observeControlValue, observeControlValueChange, observeQueryList, openBlob, padEnd, padStart, phoneNumberMask, pollingTicker, readFileAsDataUrlAsync, readFileAsync, readGeoFile, routeSnapshotPathFull, routeSnapshotPathRelative, sanitizeFilename, splitMultiPolygons, stripOuter, subscriberCount, tapFirst, toggleAttribute, trimRepeated, waitOnConditionAsync, waitOnNonPendingStatus, willHaveDataProp, withoutProperties, withoutProperty, wrapIntoObservable };
1497
+ export { IS_FEATURE_COLLECTION_VALIDATOR_NAME, IS_ONLY_GEOMETRY_TYPES_VALIDATOR_NAME, MIN_MAX_POINTS_VALIDATOR_NAME, NO_EMPTY_FEATURE_COLLECTION_VALIDATOR_NAME, NO_INNER_RINGS_VALIDATOR_NAME, NO_KINKS_VALIDATOR_NAME, PollingTickerOptions, Refreshable, activatedRoutesWithDataProperty, addHoleToPolygon, arrayMoveImmutable, arrayMoveMutable, calcPercentage, closePolygons, coerceFeatureCollection, createPadding, deleteProperties, deleteProperty, fileBufferToBlob, fileBufferToObjectUrl, fileDataFromBuffer, fractionalDigitsCount, geoJsonToArea, getAttribute, getClosestWidgetCdkDrag, getControlName, getControlPath, hasAttribute, hasProperty, hasRequiredControl, isAbsoluteUrl, isEmptyInputValue, isEmptyUrlRoute, isFeatureCollectionValidator, isNullOrUndefined, isNullOrUndefinedOrEmpty, isNumeric, isOnlyGeometryTypes, isOnlyGeometryTypesValidator, leafChildRoute, loadStyle, loadStyleSheet, mapEach, mergePolygons, minMaxPointsValidator, noEmptyFeatureCollectionValidator, noInnerRingsValidator, noKinksValidator, notNullOrUndefined, notNullOrUndefinedOrEmpty, observeControlIsDifferent, observeControlStatus, observeControlValid, observeControlValue, observeControlValueChange, observeQueryList, openBlob, padEnd, padStart, phoneNumberMask, pollingTicker, polygonContains, polygonHasMinDistinctVertices, polygonViolatesMinMax, readFileAsDataUrlAsync, readFileAsync, readGeoFile, ringIsClockwise, routeSnapshotPathFull, routeSnapshotPathRelative, sanitizeFilename, splitMultiPolygons, stripOuter, subscriberCount, tapFirst, toggleAttribute, trimRepeated, waitOnConditionAsync, waitOnNonPendingStatus, willHaveDataProp, withoutProperties, withoutProperty, wrapIntoObservable };
1407
1498
  //# sourceMappingURL=theseam-ui-common-utils.mjs.map