@math.gl/polygon 4.0.0 → 4.0.1

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,145 +1,145 @@
1
1
  import { cutPolylineByGrid, cutPolygonByGrid } from "./cut-by-grid.js";
2
2
  import { getPointAtIndex, push } from "./utils.js";
3
+ // https://en.wikipedia.org/wiki/Web_Mercator_projection
3
4
  const DEFAULT_MAX_LATITUDE = 85.051129;
5
+ /** https://user-images.githubusercontent.com/2059298/78465769-938b7a00-76ae-11ea-9b95-1f4c26425ab9.png */
4
6
  export function cutPolylineByMercatorBounds(positions, options) {
5
- const {
6
- size = 2,
7
- startIndex = 0,
8
- endIndex = positions.length,
9
- normalize = true
10
- } = options || {};
11
- const newPositions = positions.slice(startIndex, endIndex);
12
- wrapLongitudesForShortestPath(newPositions, size, 0, endIndex - startIndex);
13
- const parts = cutPolylineByGrid(newPositions, {
14
- size,
15
- broken: true,
16
- gridResolution: 360,
17
- gridOffset: [-180, -180]
18
- });
19
-
20
- if (normalize) {
21
- for (const part of parts) {
22
- shiftLongitudesIntoRange(part, size);
7
+ const { size = 2, startIndex = 0, endIndex = positions.length, normalize = true } = options || {};
8
+ // Remap longitudes so that each segment takes the shorter path
9
+ const newPositions = positions.slice(startIndex, endIndex);
10
+ wrapLongitudesForShortestPath(newPositions, size, 0, endIndex - startIndex);
11
+ const parts = cutPolylineByGrid(newPositions, {
12
+ size,
13
+ broken: true,
14
+ gridResolution: 360,
15
+ gridOffset: [-180, -180]
16
+ });
17
+ if (normalize) {
18
+ // Each part is guaranteed to be in a single copy of the world
19
+ // Map longitudes back to [-180, 180]
20
+ for (const part of parts) {
21
+ shiftLongitudesIntoRange(part, size);
22
+ }
23
23
  }
24
- }
25
-
26
- return parts;
24
+ return parts;
27
25
  }
26
+ /** https://user-images.githubusercontent.com/2059298/78465770-94241080-76ae-11ea-809a-6a8534dac1d9.png */
28
27
  export function cutPolygonByMercatorBounds(positions, holeIndices = null, options) {
29
- const {
30
- size = 2,
31
- normalize = true,
32
- edgeTypes = false
33
- } = options || {};
34
- holeIndices = holeIndices || [];
35
- const newPositions = [];
36
- const newHoleIndices = [];
37
- let srcStartIndex = 0;
38
- let targetIndex = 0;
39
-
40
- for (let ringIndex = 0; ringIndex <= holeIndices.length; ringIndex++) {
41
- const srcEndIndex = holeIndices[ringIndex] || positions.length;
42
- const targetStartIndex = targetIndex;
43
- const splitIndex = findSplitIndex(positions, size, srcStartIndex, srcEndIndex);
44
-
45
- for (let i = splitIndex; i < srcEndIndex; i++) {
46
- newPositions[targetIndex++] = positions[i];
28
+ const { size = 2, normalize = true, edgeTypes = false } = options || {};
29
+ holeIndices = holeIndices || [];
30
+ const newPositions = [];
31
+ const newHoleIndices = [];
32
+ let srcStartIndex = 0;
33
+ let targetIndex = 0;
34
+ for (let ringIndex = 0; ringIndex <= holeIndices.length; ringIndex++) {
35
+ // srcStartIndex/srcEndIndex define the ring in the original positions
36
+ const srcEndIndex = holeIndices[ringIndex] || positions.length;
37
+ // targetStartIndex/targetIndex define the ring in newPositions
38
+ const targetStartIndex = targetIndex;
39
+ // In case the ring contains a pole (e.g. Antarctica), we'll have to insert vertices
40
+ // The insertion point is defined by the vertex closest to the pole
41
+ // Split the the ring by the insertion point when copying to newPositions
42
+ const splitIndex = findSplitIndex(positions, size, srcStartIndex, srcEndIndex);
43
+ for (let i = splitIndex; i < srcEndIndex; i++) {
44
+ newPositions[targetIndex++] = positions[i];
45
+ }
46
+ for (let i = srcStartIndex; i < splitIndex; i++) {
47
+ newPositions[targetIndex++] = positions[i];
48
+ }
49
+ // Remap longitudes so that each segment takes the shorter path
50
+ wrapLongitudesForShortestPath(newPositions, size, targetStartIndex, targetIndex);
51
+ // Handle the case when the ring contains a pole
52
+ insertPoleVertices(newPositions, size, targetStartIndex, targetIndex, options?.maxLatitude);
53
+ srcStartIndex = srcEndIndex;
54
+ newHoleIndices[ringIndex] = targetIndex;
47
55
  }
48
-
49
- for (let i = srcStartIndex; i < splitIndex; i++) {
50
- newPositions[targetIndex++] = positions[i];
56
+ newHoleIndices.pop();
57
+ const parts = cutPolygonByGrid(newPositions, newHoleIndices, {
58
+ size,
59
+ gridResolution: 360,
60
+ gridOffset: [-180, -180],
61
+ edgeTypes
62
+ });
63
+ if (normalize) {
64
+ // Each part is guaranteed to be in a single copy of the world
65
+ // Map longitudes back to [-180, 180]
66
+ for (const part of parts) {
67
+ // @ts-expect-error (mutates readonly array) May mutate newPositions, which is created by us
68
+ shiftLongitudesIntoRange(part.positions, size);
69
+ }
51
70
  }
52
-
53
- wrapLongitudesForShortestPath(newPositions, size, targetStartIndex, targetIndex);
54
- insertPoleVertices(newPositions, size, targetStartIndex, targetIndex, options === null || options === void 0 ? void 0 : options.maxLatitude);
55
- srcStartIndex = srcEndIndex;
56
- newHoleIndices[ringIndex] = targetIndex;
57
- }
58
-
59
- newHoleIndices.pop();
60
- const parts = cutPolygonByGrid(newPositions, newHoleIndices, {
61
- size,
62
- gridResolution: 360,
63
- gridOffset: [-180, -180],
64
- edgeTypes
65
- });
66
-
67
- if (normalize) {
68
- for (const part of parts) {
69
- shiftLongitudesIntoRange(part.positions, size);
70
- }
71
- }
72
-
73
- return parts;
71
+ return parts;
74
72
  }
75
-
73
+ /* Helpers */
74
+ // See comments for insertPoleVertices
76
75
  function findSplitIndex(positions, size, startIndex, endIndex) {
77
- let maxLat = -1;
78
- let pointIndex = -1;
79
-
80
- for (let i = startIndex + 1; i < endIndex; i += size) {
81
- const lat = Math.abs(positions[i]);
82
-
83
- if (lat > maxLat) {
84
- maxLat = lat;
85
- pointIndex = i - 1;
76
+ let maxLat = -1;
77
+ let pointIndex = -1;
78
+ for (let i = startIndex + 1; i < endIndex; i += size) {
79
+ const lat = Math.abs(positions[i]);
80
+ if (lat > maxLat) {
81
+ maxLat = lat;
82
+ pointIndex = i - 1;
83
+ }
86
84
  }
87
- }
88
-
89
- return pointIndex;
85
+ return pointIndex;
90
86
  }
91
-
87
+ // https://user-images.githubusercontent.com/2059298/78857483-5987e400-79de-11ea-98fc-0631287a8431.png
88
+ //
89
+ // If the polygon contains a pole, to tesselate it correctly, we need to insert the edge
90
+ // of map into the polygon. This requires adding two vertices that represent the pole, by
91
+ // drawing a perpendicular line to the Mercator map edge from a selected vertex on the ring.
92
+ //
93
+ // We select the insertion position carefully so that the inserted line segments do not
94
+ // intersect with the ring itself. This is ensured by findSplitIndex, which returns the
95
+ // vertex closest to the pole.
92
96
  function insertPoleVertices(positions, size, startIndex, endIndex, maxLatitude = DEFAULT_MAX_LATITUDE) {
93
- const firstLng = positions[startIndex];
94
- const lastLng = positions[endIndex - size];
95
-
96
- if (Math.abs(firstLng - lastLng) > 180) {
97
- const p = getPointAtIndex(positions, 0, size, startIndex);
98
- p[0] += Math.round((lastLng - firstLng) / 360) * 360;
99
- push(positions, p);
100
- p[1] = Math.sign(p[1]) * maxLatitude;
101
- push(positions, p);
102
- p[0] = firstLng;
103
- push(positions, p);
104
- }
97
+ // Check if the ring contains a pole
98
+ const firstLng = positions[startIndex];
99
+ const lastLng = positions[endIndex - size];
100
+ if (Math.abs(firstLng - lastLng) > 180) {
101
+ // The ring does not make a round trip
102
+ // Add the nearest pole to the vertices so that the polygon tesselates correctly
103
+ const p = getPointAtIndex(positions, 0, size, startIndex);
104
+ // Copy the first vertex to the world of the last vertex
105
+ p[0] += Math.round((lastLng - firstLng) / 360) * 360;
106
+ push(positions, p);
107
+ // Project the copied vertex to the edge of the map
108
+ p[1] = Math.sign(p[1]) * maxLatitude;
109
+ push(positions, p);
110
+ // Project the first vertex to the edge of the map
111
+ p[0] = firstLng;
112
+ push(positions, p);
113
+ }
105
114
  }
106
-
107
115
  function wrapLongitudesForShortestPath(positions, size, startIndex, endIndex) {
108
- let prevLng = positions[0];
109
- let lng;
110
-
111
- for (let i = startIndex; i < endIndex; i += size) {
112
- lng = positions[i];
113
- const delta = lng - prevLng;
114
-
115
- if (delta > 180 || delta < -180) {
116
- lng -= Math.round(delta / 360) * 360;
116
+ let prevLng = positions[0];
117
+ let lng;
118
+ for (let i = startIndex; i < endIndex; i += size) {
119
+ lng = positions[i];
120
+ const delta = lng - prevLng;
121
+ if (delta > 180 || delta < -180) {
122
+ lng -= Math.round(delta / 360) * 360;
123
+ }
124
+ positions[i] = prevLng = lng;
117
125
  }
118
-
119
- positions[i] = prevLng = lng;
120
- }
121
126
  }
122
-
123
127
  function shiftLongitudesIntoRange(positions, size) {
124
- let refLng;
125
- const pointCount = positions.length / size;
126
-
127
- for (let i = 0; i < pointCount; i++) {
128
- refLng = positions[i * size];
129
-
130
- if ((refLng + 180) % 360 !== 0) {
131
- break;
128
+ let refLng;
129
+ const pointCount = positions.length / size;
130
+ // Find a longitude that is not on the edge of a world
131
+ // Which we will use to determine which world copy it is
132
+ for (let i = 0; i < pointCount; i++) {
133
+ refLng = positions[i * size];
134
+ if ((refLng + 180) % 360 !== 0) {
135
+ break;
136
+ }
137
+ }
138
+ const delta = -Math.round(refLng / 360) * 360;
139
+ if (delta === 0) {
140
+ return;
141
+ }
142
+ for (let i = 0; i < pointCount; i++) {
143
+ positions[i * size] += delta;
132
144
  }
133
- }
134
-
135
- const delta = -Math.round(refLng / 360) * 360;
136
-
137
- if (delta === 0) {
138
- return;
139
- }
140
-
141
- for (let i = 0; i < pointCount; i++) {
142
- positions[i * size] += delta;
143
- }
144
145
  }
145
- //# sourceMappingURL=cut-by-mercator-bounds.js.map
package/dist/earcut.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { NumericArray } from '@math.gl/core';
2
- import { Plane2D } from './polygon-utils';
2
+ import { Plane2D } from "./polygon-utils.js";
3
3
  /**
4
4
  * Computes a triangulation of a polygon
5
5
  * @param positions a flat array of the vertex positions that define the polygon.
@@ -10,4 +10,3 @@ import { Plane2D } from './polygon-utils';
10
10
  * Adapted from https://github.com/mapbox/earcut
11
11
  */
12
12
  export declare function earcut(positions: NumericArray, holeIndices?: NumericArray, dim?: number, areas?: NumericArray, plane?: Plane2D): number[];
13
- //# sourceMappingURL=earcut.d.ts.map