@deck.gl/carto 9.3.2 → 9.3.3

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
@@ -3,7 +3,7 @@
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.3.2",
6
+ "version": "9.3.3",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -75,5 +75,5 @@
75
75
  "@loaders.gl/core": "^4.4.1",
76
76
  "@luma.gl/core": "~9.3.3"
77
77
  },
78
- "gitHead": "09af8de8d18a9cb9a31d064cae8f9e7239df7f53"
78
+ "gitHead": "a277344202566d832eed6bbfa607eef853d171e0"
79
79
  }
@@ -8,6 +8,38 @@ type TileBBox = GeoBoundingBox;
8
8
  type Properties = BinaryPointFeature['properties'];
9
9
  type LineInfo = {index: number; length: number};
10
10
 
11
+ /** Property name for server-provided feature bounding box (comma-separated west,south,east,north in world coordinates) */
12
+ export const FEATURE_BBOX_PROP = '_carto_bbox';
13
+
14
+ function parseFeatureBbox(value: unknown): [number, number, number, number] | null {
15
+ if (typeof value !== 'string') return null;
16
+ const parts = value.split(',');
17
+ if (parts.length !== 4) return null;
18
+ return parts.map(Number) as [number, number, number, number];
19
+ }
20
+
21
+ function latToMercatorY(lat: number): number {
22
+ return Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));
23
+ }
24
+
25
+ /** Convert a WGS84 point to MVT tile-space, where y=0 is north, y=1 is south, linear in Mercator */
26
+ function worldToTile(
27
+ point: [number, number],
28
+ geoBbox: TileBBox,
29
+ tileBbox: TileBBox
30
+ ): [number, number] {
31
+ const xFrac = (point[0] - geoBbox.west) / (geoBbox.east - geoBbox.west);
32
+ const x = tileBbox.west + xFrac * (tileBbox.east - tileBbox.west);
33
+
34
+ const mercY = latToMercatorY(point[1]);
35
+ const mercNorth = latToMercatorY(geoBbox.north);
36
+ const mercSouth = latToMercatorY(geoBbox.south);
37
+ const yFrac = (mercNorth - mercY) / (mercNorth - mercSouth);
38
+ const y = tileBbox.south + yFrac * (tileBbox.north - tileBbox.south);
39
+
40
+ return [x, y];
41
+ }
42
+
11
43
  export function createPointsFromLines(
12
44
  lines: BinaryLineFeature,
13
45
  uniqueIdProperty?: string
@@ -70,9 +102,16 @@ export function createPointsFromLines(
70
102
  export function createPointsFromPolygons(
71
103
  polygons: Required<BinaryPolygonFeature>,
72
104
  tileBbox: TileBBox,
73
- props: any
105
+ props: any,
106
+ geoBbox?: TileBBox
74
107
  ): BinaryPointFeature {
75
- const {west, south, east, north} = tileBbox;
108
+ // When feature bounding boxes are provided, use geoBbox for area filtering
109
+ // as the bbox values are in world coordinates
110
+ const useBbox = Boolean(
111
+ geoBbox && polygons.properties.length > 0 && FEATURE_BBOX_PROP in polygons.properties[0]
112
+ );
113
+ const boundsBbox = useBbox ? geoBbox! : tileBbox;
114
+ const {west, south, east, north} = boundsBbox;
76
115
  const tileArea = (east - west) * (north - south);
77
116
  const minPolygonArea = tileArea * 0.0001; // 0.01% threshold
78
117
 
@@ -85,6 +124,9 @@ export function createPointsFromPolygons(
85
124
  polygons.numericProps
86
125
  );
87
126
 
127
+ // MVT: tile space is Mercator-projected, need worldToTile conversion
128
+ const isMVT = useBbox && geoBbox !== tileBbox;
129
+
88
130
  // Process each polygon
89
131
  let pointIndex = 0;
90
132
  let triangleIndex = 0;
@@ -93,61 +135,80 @@ export function createPointsFromPolygons(
93
135
  const startIndex = polygons.polygonIndices.value[i];
94
136
  const endIndex = polygons.polygonIndices.value[i + 1];
95
137
 
96
- // Skip small polygons
97
- if (getPolygonArea(polygons, i) < minPolygonArea) {
98
- continue;
138
+ let labelPoint: [number, number] | null = null;
139
+
140
+ // Determine preferred label position and area filter
141
+ let preferredPoint: [number, number] | null = null;
142
+ if (useBbox) {
143
+ const featureId = polygons.featureIds.value[startIndex];
144
+ const bbox = parseFeatureBbox(polygons.properties[featureId][FEATURE_BBOX_PROP]);
145
+ if (bbox) {
146
+ const [bboxWest, bboxSouth, bboxEast, bboxNorth] = bbox;
147
+ const bboxArea = (bboxEast - bboxWest) * (bboxNorth - bboxSouth);
148
+ if (bboxArea >= minPolygonArea) {
149
+ const center: [number, number] = [(bboxWest + bboxEast) / 2, (bboxSouth + bboxNorth) / 2];
150
+ if (isPointInBounds(center, geoBbox!)) {
151
+ preferredPoint = isMVT ? worldToTile(center, geoBbox!, tileBbox) : center;
152
+ }
153
+ }
154
+ }
155
+ } else {
156
+ if (getPolygonArea(polygons, i) < minPolygonArea) {
157
+ continue;
158
+ }
159
+ preferredPoint = getPolygonCentroid(polygons, i);
99
160
  }
100
161
 
101
- const centroid = getPolygonCentroid(polygons, i);
102
- let maxArea = -1;
103
- let largestTriangleCenter: [number, number] = [0, 0];
104
- let centroidIsInside = false;
162
+ if (preferredPoint) {
163
+ // Check if preferred point is inside the polygon, tracking largest triangle as fallback
164
+ let maxArea = -1;
165
+ let largestTriangleCenter: [number, number] = [0, 0];
166
+ let isInside = false;
105
167
 
106
- // Scan triangles until we find ones that don't belong to this polygon
107
- while (triangleIndex < polygons.triangles.value.length) {
108
- const i1 = polygons.triangles.value[triangleIndex];
168
+ while (triangleIndex < polygons.triangles.value.length) {
169
+ const i1 = polygons.triangles.value[triangleIndex];
170
+ if (i1 >= endIndex) break;
109
171
 
110
- // If we've moved past the current polygon's triangles, break
111
- if (i1 >= endIndex) {
112
- break;
113
- }
172
+ if (isInside) {
173
+ triangleIndex += 3;
174
+ continue;
175
+ }
176
+
177
+ const i2 = polygons.triangles.value[triangleIndex + 1];
178
+ const i3 = polygons.triangles.value[triangleIndex + 2];
179
+ const v1 = polygons.positions.value.subarray(
180
+ i1 * polygons.positions.size,
181
+ i1 * polygons.positions.size + polygons.positions.size
182
+ );
183
+ const v2 = polygons.positions.value.subarray(
184
+ i2 * polygons.positions.size,
185
+ i2 * polygons.positions.size + polygons.positions.size
186
+ );
187
+ const v3 = polygons.positions.value.subarray(
188
+ i3 * polygons.positions.size,
189
+ i3 * polygons.positions.size + polygons.positions.size
190
+ );
191
+
192
+ if (isPointInTriangle(preferredPoint, v1, v2, v3)) {
193
+ isInside = true;
194
+ } else {
195
+ const area = getTriangleArea(v1, v2, v3);
196
+ if (area > maxArea) {
197
+ maxArea = area;
198
+ largestTriangleCenter = [(v1[0] + v2[0] + v3[0]) / 3, (v1[1] + v2[1] + v3[1]) / 3];
199
+ }
200
+ }
114
201
 
115
- // If we've already found a triangle containing the centroid, skip the rest
116
- if (centroidIsInside) {
117
202
  triangleIndex += 3;
118
- continue;
119
203
  }
120
204
 
121
- const i2 = polygons.triangles.value[triangleIndex + 1];
122
- const i3 = polygons.triangles.value[triangleIndex + 2];
123
- const v1 = polygons.positions.value.subarray(
124
- i1 * polygons.positions.size,
125
- i1 * polygons.positions.size + polygons.positions.size
126
- );
127
- const v2 = polygons.positions.value.subarray(
128
- i2 * polygons.positions.size,
129
- i2 * polygons.positions.size + polygons.positions.size
130
- );
131
- const v3 = polygons.positions.value.subarray(
132
- i3 * polygons.positions.size,
133
- i3 * polygons.positions.size + polygons.positions.size
134
- );
135
-
136
- if (isPointInTriangle(centroid, v1, v2, v3)) {
137
- centroidIsInside = true;
138
- } else {
139
- const area = getTriangleArea(v1, v2, v3);
140
- if (area > maxArea) {
141
- maxArea = area;
142
- largestTriangleCenter = [(v1[0] + v2[0] + v3[0]) / 3, (v1[1] + v2[1] + v3[1]) / 3];
143
- }
205
+ const candidate = isInside ? preferredPoint : largestTriangleCenter;
206
+ if (isPointInBounds(candidate, tileBbox)) {
207
+ labelPoint = candidate;
144
208
  }
145
-
146
- triangleIndex += 3;
147
209
  }
148
210
 
149
- const labelPoint = centroidIsInside ? centroid : largestTriangleCenter;
150
- if (isPointInBounds(labelPoint, tileBbox)) {
211
+ if (labelPoint) {
151
212
  positions.push(...labelPoint);
152
213
  const featureId = polygons.featureIds.value[startIndex];
153
214
  if (extruded) {
@@ -217,7 +217,8 @@ export default class VectorTileLayer<
217
217
  labelData.points = createPointsFromPolygons(
218
218
  props.data.polygons,
219
219
  this.state.mvt ? MVT_BBOX : tileBbox,
220
- props
220
+ props,
221
+ tileBbox
221
222
  );
222
223
  }
223
224