Skip to content

Densify the closing edge of polygon rings, geodesic or not - #28

Merged
wf9a5m75 merged 2 commits into
developfrom
fix/geodesic-polygon-fill
Nov 8, 2026
Merged

wf9a5m75 merged 2 commits into
developfrom
fix/geodesic-polygon-fill

Conversation

@wf9a5m75

@wf9a5m75 wf9a5m75 commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

なぜ

ポリゴンの点は開いたリング(末尾 ≠ 先頭)で渡されます。WGS84Geodesic.createInterpolatePoints も Planar.createInterpolatePoints も隣り合う点の間しか補間しないので、そのまま渡すと閉じる辺(末尾 → 先頭)だけが補間されず、描画側で閉じたときに 1 本の直線(弦)になっていました。一方、輪郭はリングを閉じてからポリラインとして密度化するので、閉じる辺も他の辺と同じ形になります。

Boston (42.36N, 71.06W) → London (51.5N, 0.12W) → Iceland (64.1N, 21.9W) の三角形(閉じる辺は Iceland → Boston)での数値:

  • geodesic=true: 大圏の中点は 55.6N / 53.2W。同じ経度で Web メルカトルの弦は 51.6N を通ります(約 4° のずれ)。MapLibre などでは「輪郭は曲線、塗りの一辺だけ直線」、Google のように塗りと輪郭を 1 つのネイティブポリゴンで描くプロバイダでは輪郭まで直線でした。
  • geodesic=false: 緯度経度の線形補間の中点は 53.2N / 46.5W、同じ経度で弦は 54.7N(約 1.4° のずれ)。ポリラインの geodesic=false は緯度経度の線形補間なので、輪郭はメルカトル上でわずかに曲がり、塗りの閉じる辺だけ直線でした。

どの補間を正とするか(geodesic=false)

プロバイダ 輪郭(false) 塗りの閉じる辺(false、修正前)
GL 系(MapLibre / Mapbox / Mappls / MapTiler / Longdo)・OMM ポリラインとして線形補間 補間なし(弦)
Google / MapKit / HERE iOS / Longdo iOS / web HERE 塗りと同じネイティブポリゴン 補間なし(弦)。他の辺は線形補間
HERE Android / TomTom(Android・iOS) 塗りと同じネイティブポリゴン 全辺が補間なし(生の頂点)
ArcGIS(web) 塗りと同じ 閉じてから線形補間(済)
Cesium GEODESIC のポリライン RHUMB の 1 本

ポリラインの geodesic=false(全プロバイダで緯度経度の線形補間)と、既に閉じてから密度化している web の ArcGIS に合わせ、閉じる辺も含めて緯度経度で線形補間する方に統一しました。「密度化しない(メルカトルの直線)」に寄せると、ポリラインの false と形が変わり、ArcGIS・Cesium のように辺を測地線でつなぐ SDK では直線を表せないためです。

何を変えたか

  • densifyPolygonRing(ring, geodesic, maxSegmentLength = 10000) を追加しました。リングを閉じてから大圏(geodesic)または緯度経度の線形補間(false)で密度化し、入力が開いていれば開いたまま返します(閉じた入力は閉じたまま)。
  • buildPolygonRings / buildUnwrappedPolygonRings の外周・穴をすべてこれで密度化します(densifyAndNormalize と同じく、線形補間は 10 km 刻みのまま)。共通パイプラインを使う MapLibre / Mapbox / Mappls / MapTiler / TomTom / Longdo / Leaflet / OpenLayers / Azure Maps / deck.gl / MapKit JS はこの PR だけで直ります(npm 公開後)。
  • タップ判定(PolygonManager.find)も geodesic は閉じる辺を大圏で補間します。geodesic=false は従来どおり生の頂点を緯度経度平面で判定します(緯度経度の直線=線形補間と同じ線なので、点列を増やす必要がありません)。テストで固定しています。
  • 公開 API は densifyPolygonRing の追加のみです。

関連 PR(同じ修正の一式)

fix/geodesic-polygon-fill は次の 16 リポジトリにまたがります。

1. core(3 本は互いに独立。先にマージ・公開してください)

core だけで直るプロバイダ(共通パイプライン buildPolygonRings / buildUnwrappedPolygonRings を使うもの)には PR がありません: Android の MapLibre / Mapbox / Mappls / MapTiler / Longdo / OpenMobileMaps、iOS の MapLibre / Mapbox / Mappls / MapTiler / OpenMobileMaps、web の MapLibre / Mapbox / Mappls / MapTiler / TomTom / Longdo / Leaflet / OpenLayers / Azure Maps / deck.gl / MapKit JS / ArcGIS。

2. Android プロバイダ(android-sdk-core の公開後。densifyPolygonRing を使うのでそれまでビルドできません。ArcGIS だけは core に依存しません)

3. iOS プロバイダ(ios-sdk-core の公開後。ArcGIS だけは core に依存しません)

4. web プロバイダ(js-sdk-core の新しい版を npm に公開し、依存(今は ^0.3.1)を引き上げてから。npm の公開はまだ行いません)

マージの順番: core 3 本 → core の公開 → Android / iOS プロバイダ → js-sdk-core の npm 公開と依存の引き上げ → web プロバイダ。

見た目が変わるもの: ArcGIS(Android / iOS)の geodesic=false の閉じる辺は、以前は補間されず ArcGIS が測地線として描いていましたが、他の辺と同じ緯度経度の線形補間になります(web の react-for-arcgis と同じ形で、3 プラットフォームがそろう方向。承認済み)。ほかのプロバイダも geodesic=false の閉じる辺がわずかに曲がるようになります。

検証

  • npm test(243 本すべて通過)。test/GeodesicPolygonRing.test.mjs を追加: 閉じる辺の補間(true / false)、塗りが輪郭と同じ点列になること(unwrap 版・分割版・穴)、タップ判定(true は大圏、false は緯度経度の直線)、大圏の中点 55.6N と弦 51.6N。false の塗りの 2 本は false の修正前に落ちることを確かめました(点数 717 ≠ 1109)。true 側は前のコミットの修正前に落ちることを確かめています。
  • node scripts/api-surface.mjs dump js-sdk-core(react-sdk): densifyPolygonRing の追加のみ。
  • ブラウザ(Playwright、examples/basic の polygon-geodesic): 三角形を geodesic=true / false で 13 プロバイダ表示し、修正前後を比べました。false でも閉じる辺が他の辺と同じく曲がり、Cesium の輪郭と塗りが重なるようになりました。Google Maps は手元の環境で地図が読み込めず、単体テストのみです。true は前のコミットの結果のままです。

確認できていないもの

  • 世界マスク級の大きなポリゴンでの描画(特に TomTom の geodesic=false)は実機で確かめていません。
  • npm 公開前なので、web プロバイダは公開版の js-sdk-core では動きません(集約リポジトリ内でソースを差し替えて確認しました)。

他の PR との関係

createInterpolatePoints only interpolates between neighbouring points, and
polygon rings arrive open (last != first). The closing edge (last -> first)
of a geodesic polygon was therefore never densified and was drawn as a
straight chord, while the outline - which closes the ring and densifies it
as a polyline - followed the great circle. Fill and outline disagreed on
that edge (e.g. Boston -> London -> Iceland: the Iceland -> Boston edge's
great circle passes 55.6N at 53.2W, the chord passes 51.6N there).

Add densifyGeodesicRing, which closes the ring before densifying and keeps
the input's open/closed shape, and use it for outer rings and holes in
buildPolygonRings / buildUnwrappedPolygonRings and for the PolygonManager
hit test. geodesic=false is unchanged. The public API only gains the new
function.
The closing edge (last vertex -> first) of a geodesic=false polygon had the
same gap: Planar.createInterpolatePoints also only interpolates between
neighbouring points, so the fill's closing edge stayed a single straight
Web Mercator chord, while the outline - which closes the ring and densifies
it as a polyline - interpolated latitude/longitude linearly and bent
slightly on screen. (Boston -> London -> Iceland: halfway along the Iceland
-> Boston edge, the linear edge passes 53.2N, the chord 54.7N.)

Generalise densifyGeodesicRing (unreleased) into densifyPolygonRing(ring,
geodesic, maxSegmentLength), which closes the ring before densifying with
WGS84Geodesic or Planar, and use it for every ring in buildPolygonRings /
buildUnwrappedPolygonRings (like densifyAndNormalize, the linear case keeps
its 10 km segments). The hit test keeps testing geodesic=false against the
raw vertices: in the lat/lng plane that is the same line as the linear
interpolation. Compared with develop the public API only gains
densifyPolygonRing. Same change as android-sdk-core and ios-sdk-core.
@wf9a5m75
wf9a5m75 merged commit 18f216c into develop Nov 8, 2026
1 check 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