Skip to content

穴の結合で、近くにある穴の面積を失わないようにし、速くする - #32

Merged
wf9a5m75 merged 2 commits into
developfrom
fix/polygon-hole-union
Nov 9, 2026
Merged

wf9a5m75 merged 2 commits into
developfrom
fix/polygon-hole-union

Conversation

@wf9a5m75

@wf9a5m75 wf9a5m75 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

なぜ

ポリゴンの穴が近くにあると、unionHoleRings が穴の面積を失い、穴のはずの所が塗られていました。ボストンの道路区画(穴が 100 個ほどある 1 つのポリゴン)では、穴の面積を最大 21% 失い、街区がまるごと灰色に塗られていました。

結合後の輪郭を決めるとき、各辺の両側の点が穴の外か内かを調べています。その点を、辺から常に 1e-5°(約 1 m)横に取っていました。1 m より近くに別の穴の辺があると、点がその穴の中に入ります。すると必要な辺が捨てられ、作り直した輪郭が穴を横切っていました。穴どうしが接していること自体は原因ではなく、接していると結合が動くので表に出ていただけです。

develop では穴が 2 つ以上あるポリゴンはすべて結合を通るので、穴が離れているデータでも起きます。ボストンの同梱データでは 53 ポリゴンで、最大 20.8% の面積を失っていました。

何を変えたか

  • 判定の点の取り方: 辺の横に取る点の距離を、最寄りの別の辺までの距離の半分以下に抑えました。
  • 速さ: 辺の分割と最寄りの辺の探索が、全部の辺どうしの総当たりでした。辺を一様な格子に登録し、近くの辺だけを調べるようにしました。出力は高速化の前後で座標まで一致します。
  • 公開 API は変えていません。web(js-sdk-core)・Android(android-sdk-core)・iOS(ios-sdk-core)で同じ変更です。

検証

  • テスト: 3 プラットフォームが同じ fixture(polygon-hole-union-fixture.txt、26 ケース)を読みます。正解の面積は shapely の union から取りました。
    • 含むケース:点で接する、辺を共有する、辺の一部を共有する、3 つ以上の連鎖、1 点の周りに 3 つ、重なる、1 単位離れる、外周に接する。それぞれ 1e-5° と 1e-6° の格子で作りました。
    • ボストンの道路区画 2 つも含めています。
    • 直す前は、3 プラットフォームとも同じ 8 ケースで落ちていました。直した後は JS 292 件・Android 330 件・iOS 371 件がすべて通ります。
  • ボストン全域の区画(丸めただけのデータ): 修正前は 10 ポリゴンが穴の面積を 2〜21% 失っていました。修正後に残るずれは 0.011% で、丸めで自己交差した穴だけです。
  • 実機・ブラウザ(MapLibre、Google Maps): web(Playwright)・Android タブレット・iPad の 3 か所とも、z17 で街区が塗られなくなりました。

結合の時間(ボストンの穴が 2 つ以上ある区画 104 件、MapLibre):

修正前 修正後
web next frame 521–527 ms 276–280 ms
Android 結合(debug) 4.5–4.7 s 0.83–0.85 s
Android 結合(debuggable=false) 1.2–1.5 s 0.17 s
iPad 結合(Release) 204–220 ms 66–69 ms

確認できていないもの

  • Android の本物の release ビルド(R8 あり)では測っていません。debuggable=false の debug ビルドで代わりに測りました。
  • 時間は MapLibre でしか測っていません。
  • web の開発時(StrictMode)には、Polygons の effect が 2 回走るため、結合も 2 回走ります。前からあるもので、2 回目は約 50 ms です。
  • feat/polygon-tiling にも同じ不具合があります。develop を取り込めば直ります。

…r edge

The union keeps a sub-edge when the union's interior is on exactly one side,
sampled at a fixed 1e-5 degree (about 1 m) off its mid-point. Where another
hole runs closer than that, the sample lands inside it: the edge looks
interior on both sides, is dropped, and the traced ring cuts across a hole.
Rounded to 1e-5 degree, Boston's street parcels have many holes one unit
apart; their holes lost up to 21% of their area and whole blocks were filled.

The offset is now at most half the distance from the mid-point to the
nearest other sub-edge, which after splitting cannot be crossed. Same change
as js-sdk-core's PolygonUnion.ts and android-sdk-core's PolygonHoleUnion.kt.

Resources/polygon-hole-union-fixture.txt (the same file as in js-sdk-core and
android-sdk-core) holds holes that touch at a point, share an edge or part of
one, chain, overlap, run one unit apart, or touch the outline, at 1e-5 and
1e-6 degree, and two Boston street parcels; the expected areas are shapely's.
Splitting tested every edge against every other, and the clearance added
for the side samples scanned every sub-edge again: both quadratic in the
hole edges. Boston's 104 street parcels with two or more holes (89,589
hole edges) took 2.1 s in node; on the Android tablet (debuggable) the
ten that need a union took 5.2 s before the polygons reached the map.

Edges are now filed in a uniform grid over their bounding boxes, and
splitting and the clearance ask only the edges near a box. The box is
padded by the farthest intersectionPoints can reach past a segment (its
collinear test, 2e-6 degree), so the same edges meet and every output
ring is the same as before, coordinate for coordinate: checked on the
26 fixture cases and the 104 Boston parcels, as rounded and as bundled.
Coverage skips a ring whose latitudes do not straddle the sample point,
which pointInRing would count as outside anyway.

Boston, 104 parcels: node 2.1 s -> 0.26 s, JVM 1.09 s -> 0.14 s.
@wf9a5m75
wf9a5m75 merged commit ca81984 into develop Nov 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant