Repository navigation
Draw large polygon sets as raster tiles (PolygonTiler) - #33
Merged
Merged
Conversation
pointOnSegment compared the dot product (p-a)·(p-b), a quantity in square degrees, with eps = 1e-6, so any tap within about 0.001° (~100 m) of the middle of a short edge counted as on that edge. With building footprints the first nearby building in the list was returned instead of the tapped one. Now it is the plain distance to the segment, within 1e-6° (~0.1 m).
Holes that neither cross, touch nor contain one another are their own union. The check bins the edges into a grid, so it is linear; the union is quadratic in the hole edges, which a street-network parcel with 80 holes and 8,000 hole vertices turned into seconds. The rings still come back clockwise, as the union returns them. js-sdk-core takes the same shortcut.
SwiftUI re-evaluates the map content on every camera frame and the host syncs the same states back into the collector. Diffing them cost a set, a dictionary and a listener per state: 260 ms a frame for Boston's 98,843 parcels on an iPad Pro, so every pan ran at 5 fps. The same instances in the same order have not changed membership and are already listened to.
The iOS counterpart of js-sdk-core's PolygonTiler / PolygonTileSet and the polygon section of tile-sw.js, drawn the same way: - PolygonTilingOptions: tile from 2,000 polygons or 50,000 vertices. - PolygonTileRenderer (a TileProvider on the LocalTileServer): Web Mercator rings with the outline and holes wound opposite ways and filled nonzero; a grid index of bounding boxes (one z12 tile per cell); per-zoom simplified copies (snap to the pixel grid, drop repeats, Douglas-Peucker at 0.5 px, a dot under 1 px), four zooms kept; outline only, left out under 4 px; tiles cached without the version, and an in-place change drops only the tiles the polygon overlaps. - PolygonTileSet: route, version in the URL, 16 ms coalescing of changes. - PolygonController.tiler: tiled polygons stay in the PolygonManager for taps (original shapes) with no native polygon; the renderer never sees them. PolygonManager.find tests only the polygons whose bounds hold the tap, so a tap among 100,000 tiled parcels no longer reads every state. Driver-facing types are @_spi(MapConductorDriver); PolygonTilingOptions is public. Tests mirror PolygonTiling.test / RasterLayerCoexistence.test.
… state Two costs that every camera frame paid for each overlay, measured on an iPad Pro with Boston's 98,843 parcels: - The same-list check compared states one by one through the generic, 14 ms a frame. The states are class references, so the two buffers are compared with memcmp. - The listener captured each state weakly. A weak reference gives the object a side table, and from then on every retain and release of it takes the slow path; the map content holds each state again on every frame, so that was a third of a pan's main-thread time. The listener now finds the state by id and checks its identity instead.
…lement SwiftUI rebuilds the map content on every camera frame. A single-expression builder went through buildBlock, which merged it into a fresh content, and Markers/Polygons/... built a list and then appended it: two copies of every list, each a retain and a release per overlay. Merging into an empty list now takes the other list's buffer, buildBlock returns a lone component as it is, and the collection items assign into an empty list. With 98,843 parcels in tiles, a pan went from about 30 to about 80 fps on the iPad Pro (117 fps with 605 polygons).
… its state The marker tiles check the server's port on every content update; the polygon tiles now do the same (refreshIfServerMoved), since the server comes back on another port after the app has been in the background. route() runs wherever PolygonController.add runs, off the main thread, while the content update calls refreshIfServerMoved() on the main thread, so the tiled ids, states and the shown base URL are read and written under a lock.
The map hosts set it from their camera callbacks on the main thread, and onCameraChanged, which the overlay registry runs in a task off the main thread, sets it too. Two unguarded writes of a class reference release the old camera twice. Once pans ran at 75 fps with 100,000 tiled parcels on MapLibre, the app crashed a few pans in (EXC_BAD_ACCESS releasing the camera captured by the regionIsChanging task); with the lock, none in four runs of six pans each.
An internal initializer that skipped the fingerprint made the class lose its complete set of public designated initializers, which changes the published interface (@_hasMissingDesignatedInitializers). The fingerprint is computed after the tiles have been handed to the map, so it only delays when taps start resolving, not when the polygons show.
…ge makes it untileable Whether a polygon is tiled was decided only on add. Setting geodesic on a tiled polygon in place, or giving it points that span half the world or fewer than three, kept it in the tiles, drawn with straight edges. update() now checks canTile: a polygon that fails it leaves the tiles (its slot in the renderer is emptied, so only the tiles it was in are drawn again) and is handed to the renderer. It stays on the map, so this is not an add. The reverse is left alone: a native polygon that could now be tiled already looks right. Same as js-sdk-core.
Conflict in PolygonUnion.swift: develop's unionHoleRings files edges in an EdgeGrid and skips rings by their Bounds; feat/polygon-tiling's holesAreDisjoint had its own grid and ringBox. holesAreDisjoint now uses EdgeGrid with meetPad and ringBounds, so there is one grid.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
何をするか
多数のポリゴン(既定: 2,000 個以上または頂点 50,000 以上)を、ネイティブの図形ではなくラスタータイルとして描きます(
PolygonTilingOptions/PolygonTiler)。web・Android・iOS で同じ設計です。このリポジトリで変えたこと
PolygonTilingOptions/PolygonTiler/ タイル描画。PolygonControllerがタイル側のポリゴンを PolygonManager に残し、ネイティブの図形は作りません。タイルにできなくなった変更が入ったらネイティブ描画へ移します。holesAreDisjoint(穴が離れていれば結合を飛ばす近道)を、develop の穴の結合(穴の結合で、近くにある穴の面積を失わないようにし、速くする #32)のEdgeGridとringBounds/meetPadに乗せ換え、独自の格子を消しました(衝突は PolygonUnion.swift の 1 か所)。develop の取り込み
このブランチは push 済みだったので、rebase せず
origin/developを merge commit で取り込みました。xcodebuild test(シミュレータ): 406 件 pass(develop の穴の結合の fixture を含む)。scripts/api-surface.sh check: 変えたモジュールは全部 ok、ベースラインの更新は不要。マージの順番
この機能は複数のリポジトリにまたがります。ios-sdk-core → 各 ios-for- → ios-sdk(親)* の順にマージが要ります。
この PR は大きな機能なので、マージするかはレビューのうえで判断してください。