Skip to content

Commit fda3e41

Browse files
Fix multipolygon and point intersections (#5590)
* Fix multipolygon and point intersections multiPolygonContainsPoint was sharing variables between individual polygons inside the multipolygon. This left a situation where an even number of polygons that overlapped a single point, would be said to not contain that point. Now a multipolygon is said to contain a point if any polygon contained in the multipolygon contains that point. * Update changelog * Add multipolygon to style benchmarks * add migrateProjection to bench StubMap --------- Co-authored-by: Harel M <harel.mazor@gmail.com>
1 parent 13170fe commit fda3e41

7 files changed

Lines changed: 220 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
### 🐞 Bug fixes
99

10+
- Fix intersection detection between MultiPolygons and Points ([#5590](https://github.com/maplibre/maplibre-gl-js/pull/5590))
1011
- _...Add new stuff here..._
1112

1213
## 5.2.0

src/util/intersection_tests.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,16 @@ function distToSegmentSquared(p: Point, v: Point, w: Point) {
130130
return p.distSqr(w.sub(v)._mult(t)._add(v));
131131
}
132132

133-
// point in polygon ray casting algorithm
134133
function multiPolygonContainsPoint(rings: Array<Ring>, p: Point) {
135-
let c = false,
136-
ring, p1, p2;
137-
138134
for (let k = 0; k < rings.length; k++) {
139-
ring = rings[k];
140-
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
141-
p1 = ring[i];
142-
p2 = ring[j];
143-
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
144-
c = !c;
145-
}
135+
if (polygonContainsPoint(rings[k], p)) {
136+
return true;
146137
}
147138
}
148-
return c;
139+
return false;
149140
}
150141

142+
// point in polygon ray casting algorithm
151143
function polygonContainsPoint(ring: Ring, p: Point) {
152144
let c = false;
153145
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {

test/bench/data/style-benchmark-locations.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,38 @@
193193
"zoom": 8,
194194
"tags": ["place_label:settlement", "road", "hillshade"]
195195
}
196+
},
197+
{
198+
"type": "Feature",
199+
"geometry": {
200+
"type": "MultiPolygon",
201+
"coordinates": [
202+
[
203+
[
204+
[8.404, 49.702],
205+
[8.885, 48.3139],
206+
[12.006, 49.484],
207+
[9.639, 50.982],
208+
[8.404, 49.702]
209+
]
210+
],
211+
[
212+
[
213+
[10.955, 50.670],
214+
[10.207, 49.660],
215+
[10.893, 48.2862],
216+
[12.594, 48.705],
217+
[12.755, 50.353],
218+
[10.955, 50.670]
219+
]
220+
]
221+
]
222+
},
223+
"properties": {
224+
"place_name": "Query, Germany z8",
225+
"zoom": 8,
226+
"tags": ["query"]
227+
}
196228
}
197229
]
198230
}

test/bench/lib/locations_with_tile_id.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,44 @@ export type LocationsWithTileID = {
88
center: GeoJSON.Position;
99
}
1010

11-
export default function locationsWithTileID(locations: GeoJSON.Feature<GeoJSON.Point>[]): LocationsWithTileID[] {
12-
return locations.map(feature => {
13-
const {coordinates} = feature.geometry;
14-
const {zoom} = feature.properties;
15-
const {x, y} = MercatorCoordinate.fromLngLat({
16-
lng: coordinates[0],
17-
lat: coordinates[1]
18-
});
11+
function createLocation(position: GeoJSON.Position, zoom: number, description: any): LocationsWithTileID {
12+
const {x, y} = MercatorCoordinate.fromLngLat({
13+
lng: position[0],
14+
lat: position[1]
15+
});
1916

20-
const scale = Math.pow(2, zoom);
21-
const tileX = Math.floor(x * scale);
22-
const tileY = Math.floor(y * scale);
17+
const scale = Math.pow(2, zoom);
18+
const tileX = Math.floor(x * scale);
19+
const tileY = Math.floor(y * scale);
2320

24-
return {
25-
description: feature.properties['place_name'],
26-
tileID: [new OverscaledTileID(zoom, 0, zoom, tileX, tileY)],
27-
zoom,
28-
center: coordinates
29-
};
21+
return {
22+
description,
23+
tileID: [new OverscaledTileID(zoom, 0, zoom, tileX, tileY)],
24+
zoom,
25+
center: position
26+
};
27+
}
28+
29+
function createPointLocation(feature: GeoJSON.Feature<GeoJSON.Point>): LocationsWithTileID {
30+
const {coordinates: position} = feature.geometry;
31+
const {zoom} = feature.properties;
32+
return createLocation(position, zoom, feature.properties['place_name']);
33+
}
34+
35+
function createMultiPolygonLocation(feature: GeoJSON.Feature<GeoJSON.MultiPolygon>): LocationsWithTileID {
36+
const {coordinates} = feature.geometry;
37+
const {zoom} = feature.properties;
38+
const position = coordinates[0][0][0];
39+
return createLocation(position, zoom, feature.properties['place_name']);
40+
}
41+
42+
export default function locationsWithTileID(locations: GeoJSON.Feature<GeoJSON.Point | GeoJSON.MultiPolygon>[]): LocationsWithTileID[] {
43+
return locations.map(feature => {
44+
if (feature.geometry.type === 'Point') {
45+
return createPointLocation(feature as GeoJSON.Feature<GeoJSON.Point>);
46+
}
47+
if (feature.geometry.type === 'MultiPolygon') {
48+
return createMultiPolygonLocation(feature as GeoJSON.Feature<GeoJSON.MultiPolygon>);
49+
}
3050
});
3151
}

test/bench/styles/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import QueryBox from '../benchmarks/query_box';
99

1010
import {getGlobalWorkerPool} from '../../../src/util/global_worker_pool';
1111

12-
const locations = locationsWithTileID(styleBenchmarkLocations.features as GeoJSON.Feature<GeoJSON.Point>[]);
12+
const locations = locationsWithTileID(styleBenchmarkLocations.features as GeoJSON.Feature<GeoJSON.Point | GeoJSON.MultiPolygon>[]);
1313

1414
const benchmarks = (window as any).benchmarks = [];
1515

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[
2+
{
3+
"geometry": {
4+
"type": "MultiPolygon",
5+
"coordinates": [
6+
[
7+
[
8+
[
9+
-30.0146484375,
10+
29.993002284551082
11+
],
12+
[
13+
30.0146484375,
14+
29.993002284551082
15+
],
16+
[
17+
30.0146484375,
18+
-29.993002284551068
19+
],
20+
[
21+
-30.0146484375,
22+
-29.993002284551068
23+
],
24+
[
25+
-30.0146484375,
26+
29.993002284551082
27+
]
28+
]
29+
],
30+
[
31+
[
32+
[
33+
-14.9853515625,
34+
14.987239525774243
35+
],
36+
[
37+
14.9853515625,
38+
14.987239525774243
39+
],
40+
[
41+
14.9853515625,
42+
-14.987239525774243
43+
],
44+
[
45+
-14.9853515625,
46+
-14.987239525774243
47+
],
48+
[
49+
-14.9853515625,
50+
14.987239525774243
51+
]
52+
]
53+
]
54+
]
55+
},
56+
"type": "Feature",
57+
"properties": {},
58+
"source": "geojson",
59+
"state": {}
60+
}
61+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"width": 512,
6+
"height": 512,
7+
"debug": true,
8+
"queryGeometry": [
9+
256,
10+
256
11+
]
12+
}
13+
},
14+
"center": [
15+
0,
16+
0
17+
],
18+
"zoom": 0,
19+
"sources": {
20+
"geojson": {
21+
"type": "geojson",
22+
"data": {
23+
"type": "MultiPolygon",
24+
"coordinates": [
25+
[
26+
[
27+
[
28+
-30,
29+
30
30+
],
31+
[
32+
30,
33+
30
34+
],
35+
[
36+
30,
37+
-30
38+
],
39+
[
40+
-30,
41+
-30
42+
],
43+
[
44+
-30,
45+
30
46+
]
47+
]
48+
],
49+
[
50+
[
51+
[
52+
-15,
53+
15
54+
],
55+
[
56+
15,
57+
15
58+
],
59+
[
60+
15,
61+
-15
62+
],
63+
[
64+
-15,
65+
-15
66+
],
67+
[
68+
-15,
69+
15
70+
]
71+
]
72+
]
73+
]
74+
}
75+
}
76+
},
77+
"layers": [
78+
{
79+
"id": "circles",
80+
"type": "fill",
81+
"source": "geojson"
82+
}
83+
]
84+
}

0 commit comments

Comments
 (0)