@trackunit/react-map 0.1.4 → 0.1.6

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/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@trackunit/react-map",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
7
7
  "node": ">=24.x"
8
8
  },
9
9
  "dependencies": {
10
- "@trackunit/react-components": "2.1.23",
11
- "@trackunit/css-class-variance-utilities": "1.13.29",
12
- "@trackunit/react-form-components": "2.1.24",
13
- "@trackunit/react-core-hooks": "1.17.37",
14
- "@trackunit/geo-json-utils": "1.14.31",
15
- "@trackunit/i18n-library-translation": "2.0.25",
16
- "@trackunit/react-modal": "2.1.25",
10
+ "@trackunit/react-components": "2.1.24",
11
+ "@trackunit/css-class-variance-utilities": "1.13.30",
12
+ "@trackunit/react-form-components": "2.1.25",
13
+ "@trackunit/react-core-hooks": "1.17.38",
14
+ "@trackunit/geo-json-utils": "1.14.32",
15
+ "@trackunit/i18n-library-translation": "2.0.26",
16
+ "@trackunit/react-modal": "2.1.26",
17
17
  "react-minimal-pie-chart": "^8.4.0",
18
- "@trackunit/react-map-adapter-shared": "0.0.10",
19
- "@trackunit/ui-design-tokens": "1.13.29",
18
+ "@trackunit/react-map-adapter-shared": "0.0.12",
19
+ "@trackunit/ui-design-tokens": "1.13.30",
20
20
  "@floating-ui/react": "^0.26.25",
21
21
  "es-toolkit": "^1.39.10",
22
22
  "tailwind-merge": "^2.0.0",
@@ -41,6 +41,13 @@ export type EdgeAutoPlacementOptions = Readonly<{
41
41
  labelPlacementResolver?: EdgeLabelPlacementResolver;
42
42
  /** Placement mode. Defaults to `"auto"`. */
43
43
  mode?: EdgeAutoPlacementMode;
44
+ /**
45
+ * When `true`, the label anchor and rotation angle are computed via great-circle
46
+ * arc math rather than Mercator straight-line interpolation. Should match
47
+ * `ShapeStyle.geodesic` so the label tracks the visible curved edge.
48
+ * Defaults to `false`.
49
+ */
50
+ geodesic?: boolean;
44
51
  }>;
45
52
  export type EdgeAutoPlacementOutcome = Readonly<{
46
53
  type: "placed";
@@ -17,5 +17,9 @@ export type EdgeComputedProperties = Readonly<{
17
17
  * For Polygons, `outwardSide` uses the cross product with the geometry centroid
18
18
  * to determine which side faces away from the interior. For LineStrings (no
19
19
  * interior), `outwardSide` defaults to `"above"`.
20
+ *
21
+ * When `geodesic` is `true`, `angleDeg` is derived from the arc tangent at
22
+ * the edge midpoint (t = 0.5) via finite difference, so decorations are
23
+ * rotated correctly relative to the visible curved edge.
20
24
  */
21
- export declare const computeEdgeProperties: (start: GeoJsonPosition, end: GeoJsonPosition, geometry: GeoJsonGeometry, zoom: number, tileSize: number) => EdgeComputedProperties;
25
+ export declare const computeEdgeProperties: (start: GeoJsonPosition, end: GeoJsonPosition, geometry: GeoJsonGeometry, zoom: number, tileSize: number, geodesic?: boolean) => EdgeComputedProperties;
@@ -85,6 +85,7 @@ export { isPositionInsideRing as isPointInsideRing } from "@trackunit/geo-json-u
85
85
  * the rotated label rectangle when placed on the outward and inward side respectively.
86
86
  */
87
87
  export type EdgeCandidateInfo = Readonly<{
88
+ candidateId: number;
88
89
  edgeIdx: number;
89
90
  edge: EdgeIdentity;
90
91
  pxLen: number;
@@ -140,6 +141,15 @@ export type EdgePlacementContext = Readonly<{
140
141
  * left than it was on the previous frame ("don't slide back" behaviour).
141
142
  */
142
143
  previousAnchorClippedT?: number;
144
+ /**
145
+ * The `candidateId` of the candidate that `previousAnchorClippedT` was measured
146
+ * against. With geodesic segmentation a single edge yields multiple candidates
147
+ * (one per arc segment) that all share the same `edge` identity; `previousAnchorClippedT`
148
+ * is a fraction along *this specific* candidate's reading frame. The resolver must
149
+ * pin the label to this same candidate, otherwise the hold fraction would be applied
150
+ * in a different segment's reading frame and the label would jump.
151
+ */
152
+ previousAnchorClippedCandidateId?: number;
143
153
  /**
144
154
  * The `outwardSide` ("above" | "below") of the label on the previous frame.
145
155
  * The default resolver uses this to avoid flipping the label to the other
@@ -161,6 +171,7 @@ export type EdgePlacementContext = Readonly<{
161
171
  */
162
172
  export type EdgePlacementDecision = Readonly<{
163
173
  edgeIdx: number;
174
+ candidateId?: number;
164
175
  side: "outward" | "inward";
165
176
  anchorT?: number;
166
177
  anchor?: EdgeLabelAnchor;
@@ -229,6 +240,14 @@ export type FindBestEdgePositionConfig = Readonly<{
229
240
  labelPlacementResolver?: EdgeLabelPlacementResolver;
230
241
  /** True when this is the forced retry pass (minPixelWidth was relaxed to 0). Passed to EdgePlacementContext. */
231
242
  isForced?: boolean;
243
+ /**
244
+ * When `true`, the anchor geo-position is computed via great-circle interpolation
245
+ * and `directionPx` uses the arc tangent at that point rather than the straight
246
+ * Mercator chord direction. Defaults to `false` (Mercator straight-line).
247
+ *
248
+ * Set this to match `ShapeStyle.geodesic` so the label tracks the visible arc.
249
+ */
250
+ geodesic?: boolean;
232
251
  }>;
233
252
  /**
234
253
  * Default edge-label placement strategy.
@@ -244,4 +263,4 @@ export type FindBestEdgePositionConfig = Readonly<{
244
263
  * to the inward side of the edge, which naturally avoids the overflow.
245
264
  */
246
265
  export declare const defaultEdgeLabelPlacementResolver: (context: EdgePlacementContext) => EdgePlacementDecision | null;
247
- export declare const findBestEdgePosition: ({ features, viewportBounds, zoom, minPixelWidth, labelPixelWidth, tileSize, maxReadableAngleDeg, previousEdgeIdentity, previousAnchorGeo, previousLayoutSide, edgeInsets, labelAnchor, labelPlacementResolver, isForced, }: FindBestEdgePositionConfig) => EdgePlacement | null;
266
+ export declare const findBestEdgePosition: ({ features, viewportBounds, zoom, minPixelWidth, labelPixelWidth, tileSize, maxReadableAngleDeg, previousEdgeIdentity, previousAnchorGeo, previousLayoutSide, edgeInsets, labelAnchor, labelPlacementResolver, isForced, geodesic, }: FindBestEdgePositionConfig) => EdgePlacement | null;
@@ -1,5 +1,5 @@
1
1
  import { type GeoJsonBbox, type GeoJsonFeature, type GeoJsonGeometry, type GeoJsonMultiPolygon, type GeoJsonPosition } from "@trackunit/geo-json-utils";
2
- import type { ShapeStyle } from "@trackunit/react-map-adapter-shared";
2
+ import { type ShapeStyle } from "@trackunit/react-map-adapter-shared";
3
3
  import type { ResolveShapeStackOrder } from "./shapeStackOrder";
4
4
  /**
5
5
  * Pure fill-tiling core for ADR-0021. Given a set of features, the current
@@ -42,6 +42,17 @@ export type FillTilingInput = Readonly<{
42
42
  * viewport. They get no `featureToGroupKey` entry so promotion is a no-op.
43
43
  */
44
44
  suppressedFeatureIds?: ReadonlySet<string>;
45
+ /**
46
+ * Layer-level geodesic flag. When `true` (or `undefined`), polygon/line edges
47
+ * follow great-circle arcs and coordinates are densified before any clip math.
48
+ * Mirror of `ShapeStyle.geodesic` at the layer level.
49
+ */
50
+ layerGeodesic: boolean | undefined;
51
+ /**
52
+ * Per-feature style overrides. `geodesic` is read from each entry to determine
53
+ * whether that feature's edges should be densified (overrides `layerGeodesic`).
54
+ */
55
+ featureStyles: ReadonlyMap<string, ShapeStyle> | undefined;
45
56
  }>;
46
57
  export type FillTilingResult = Readonly<{
47
58
  /** Feature id → clipped fill geometry. Only clipped/covered features appear. */
@@ -102,6 +113,17 @@ export declare const shapesUnderCursor: (position: GeoJsonPosition, features: Re
102
113
  * = full geometry; null / empty MultiPolygon = no visible fill.
103
114
  */
104
115
  visibleFillFor?: VisibleFillLookup;
116
+ /**
117
+ * Layer-level geodesic flag. When `true` (or `undefined`), edges are
118
+ * densified before boundary-distance and point-in-polygon checks so stroke
119
+ * and fill hits follow great-circle arcs.
120
+ */
121
+ layerGeodesic: boolean | undefined;
122
+ /**
123
+ * Per-feature style overrides; `geodesic` is read per-feature to determine
124
+ * whether that feature's edges should be densified.
125
+ */
126
+ featureStyles: ReadonlyMap<string, ShapeStyle> | undefined;
105
127
  }>) => Array<ShapesUnderCursorHit>;
106
128
  /**
107
129
  * Hover-promotion overlay for one overlap group: the winner renders at full
@@ -110,5 +132,5 @@ export declare const shapesUnderCursor: (position: GeoJsonPosition, features: Re
110
132
  * its full geometry — so peer-to-peer overlap ownership from resting tiling is
111
133
  * preserved and translucent fills do not compound under the winner.
112
134
  */
113
- export declare const computePromotionGroupFills: (members: ReadonlyArray<GeoJsonFeature>, winnerId: string, restingPeerFills?: ReadonlyMap<string, GeoJsonGeometry>) => ReadonlyMap<string, GeoJsonGeometry>;
135
+ export declare const computePromotionGroupFills: (members: ReadonlyArray<GeoJsonFeature>, winnerId: string, restingPeerFills?: ReadonlyMap<string, GeoJsonGeometry>, layerGeodesic?: boolean | undefined, featureStyles?: ReadonlyMap<string, ShapeStyle> | undefined) => ReadonlyMap<string, GeoJsonGeometry>;
114
136
  export declare const computeFillTiling: (input: FillTilingInput) => FillTilingResult;