Skip to content

Bring feature/bearing-clockwise into develop - #24

Merged
wf9a5m75 merged 42 commits into
developfrom
feature/bearing-clockwise
Oct 25, 2026
Merged

wf9a5m75 merged 42 commits into
developfrom
feature/bearing-clockwise

Conversation

@wf9a5m75

Copy link
Copy Markdown
Contributor

Brings the rest of feature/bearing-clockwise into develop: the 39 commits after #9 (react-vectortile and react-vectorstyle, vector tile rendering across web providers, the deck.gl sample, the test workflow, API gate coverage, and the provider bumps that go with them).

Merging develop

develop was merged into the branch first. The only conflicts were submodule pointers, resolved as follows:

  • js-sdk-core and react-for-*: each submodule's develop tip, which now holds the bearing work (react-for-* 0.1.1 #5–build(deps-dev): bump undici from 8.10.0 to 8.11.2 #14, js-sdk-core fix(ci): npm-publish の可視性検証を正しく動かす #8).
  • js-sdk-react: develop tip 4fa1db2, which adds labelPosition through the RN bridge.
  • reactnative-for-* (7 repos): the tip of checkpoint/vector-style-20261010, after develop was merged into it. These are the heads of the open Pass vectorStyle to the native map PRs.
  • react-icons-{jp,us,weather}: already pointed at fix/register-in-api-gate, the head of each repo's open PR 0.1.0 #1.
  • react-for-deckgl: left as the merge produced it (d339617). Moving it to develop's deck.gl 9.4.0 also needs a lockfile update, so that is left to separate work.

Merge order: merge the reactnative-for-* and react-icons-* PRs before this one, so every pointer is on develop. #23 also touches package-lock.json, so whichever of the two lands second may need a re-merge.

Checked locally

The merged tree, with every submodule checked out at its pointer:

  • npm run build:packages: ok
  • npm test: 227 pass, 1 fail
  • npm run lint: ok
  • node scripts/api-surface.mjs check: every package passes except the ones below.

Failures on this branch that this merge did not cause

Expect these in CI until they are fixed:

  1. react-vectorstyle / react-vectortile do not build from a clean checkout.
    • vectorstyle/dist and vectortile/dist are gitignored and produced by scripts/sync-sdk.sh in mapconductor-vectortile.
    • The local run above passed only after copying them in from a dev checkout.
  2. api-surface cannot build react-icons-{jp,us,weather}.
    • These three are in the gate's PACKAGES, but they are not npm workspaces, so npm run build --workspace fails.
    • With the packages pre-built, all three pass against their new baselines.
  3. react-for-template's test fails.
    • The test imports dist/index.mjs, but the package has no pretest script and is not in build:packages.
    • test.yml is new on this branch, so CI never ran this test before.
  4. The js-sdk-core baseline is out of date on develop.

masashi and others added 30 commits September 1, 2026 11:43
bearing を「値を増やすと地図が時計回りに回る」向きに統一し、ネイティブ SDK との変換をコアの CameraBearing に集約する。
An independent workspace package, not a core change — it wraps the
shared Rust renderer as a TileProvider and serves through the existing
LocalTileServer, the same way react-heatmap does.

Ships ESM only: the renderer's wasm bindings are ESM, so a CJS build
could not load them. The vendored renderer is kept external from the
bundle, because inlining it would move the wasm URL out of vendor/pkg
and break loading at runtime — verified in a browser rather than
assumed.
The repo-wide dist/ ignore rule swallowed vendor/dist, so a clean
checkout had a package that could not build: nothing in react-vectortile
regenerates it, only the sync script in the mapconductor-vectortile
repo. Tracking it matches how android-vectortile carries its .so and
ios-vectortile its XCFramework.

Also carries in the byte-budgeted tile cache.
wasm-pack writes a .gitignore containing '*' into its output directory,
and the sync copied it verbatim - so the wasm the package needs at
runtime was silently untracked. The sync script now drops it.
Depends on @mapconductor/vectortile as a package rather than a vendored copy,
so react-vectortile is now what it always claimed to be: one 120-line component
over a renderer that knows nothing about React.

The sample's colour pickers blend each layer's own colour toward the picked one
instead of replacing it. A basemap tells forest from grass from farmland with a
dozen greens, and flattening a category to a single colour throws that away —
the map stops looking like a map. Categories come from the Shortbread source
layer a layer draws, not from this style's layer ids, so the same code works on
any Shortbread style.

Restyling now works end to end, which needed two fixes:

- attachTo() threads a cache key, bumped per restyle. Without it the map keeps
  serving raster tiles it already holds and nothing appears to change.
- Changing `style` no longer tears down and rebuilds the renderer. It calls
  setStyle, which keeps the fetched vector tiles — the geometry did not change,
  only the paint. Measured on the sample: recolouring refetches 0 vector tiles
  and re-renders 6 raster tiles.

`style` is compared by content rather than object identity, because a colour
picker necessarily builds a new style object on every render.

vite.config sets worker.format to 'es'. The renderer runs in a module worker
and Vite's default iife format cannot code-split, which fails the production
build only — dev serves module workers natively.
…dian

The cells were 0.02 degrees. On 144k markers a tile-sized query costs 2.31 ms
at that size and 0.06 ms at 0.005, because a 0.02 cell hands back about five
times the markers a tile actually needs. The Android and iOS indexes were
measured onto 0.005 this week; this is the same number for the same reason.

Both callers pad their query by a few pixels — a tile by its border, a click by
the icon's half extent. Near 180 degrees that padding walks off the end of the
coordinate range, and the result was one of two silent failures: corners that
come in reversed iterated no cells at all and returned nothing, and a west or
east past ±180 compared as a plain number, so a click at 179.99 could not reach
a marker at -179.99. Columns now fold back onto the globe, at insert and at
query, and the containment test measures the angle east of `west` rather than
comparing raw degrees.

Neither threw. Markers just went missing, which is why the test compares
against a brute-force scan on every query instead of asserting that something
came back.
Seven test files were on disk and invisible to git. The ignore list carries
`*.mjs`, `*.js`, `*.cjs` and `*.mts` unanchored to catch build output, so they
matched everything under test/ as well — `!src/tileserver/tile-sw.js` sitting a
few lines above is the same problem, patched narrowly the last time someone hit
it.

Nothing here is new: these are the tests that already run under `npm test`, all
69 of them passing, only now they are in the repository rather than on one
machine. Fresh clone, `npm test`, seven files short and nobody the wiser.

`!test/**` is the narrow fix, matching the exception already in the file.
Anchoring the patterns to `dist/` would be the broad one, but that changes what
every other pattern matches and belongs in its own change.
Keeping one marker per cell of a few pixels is what makes 144,183 street trees
readable below street level: they are planted metres apart along a road, so
most of them sit on top of one another, and thinning them halves the tile bytes
while showing the same map. android-sdk grew the option when that sample was
built; porting the sample to the other two turned up its absence.

Zero, the default, keeps the behaviour these renderers already had — drop only
markers that agree exactly, same rectangle and same icon, which cannot change
the tile. Above zero the group is a cell and the icon stops being part of it:
the caller has said markers that close together are interchangeable, so one of
them stands for the rest whatever they draw.

The value is tile pixels on all three, not points or dp. It is compared against
destination rectangles the renderer has already rounded into the tile's own
pixel grid, and passing it through unscaled is what makes 14 mean the same
density on a 3x iPhone as on Android.
Tokyo's 144,183 street trees, the sample android-sdk has had since the marker
tiling work. A harder shape than the post office set: six times as many, inside
one metropolitan area rather than spread over a country, and each species draws
its own colour, so the renderer cannot collapse two trees of different species
that land on the same pixel.

All three read the same 2.8 MB asset — a magic, three string tables, then a
fixed 20-byte record per tree, little-endian. The source is a 12 MB Shift-JIS
CSV; parsing that on a phone or in a browser would measure CSV parsing rather
than the map. The web decode is 10 ms in node for the whole file.

Species colours come out of the same golden-angle hue rotation on each
platform, with the last index — the bucket holding every species under a
hundred trees — grey rather than competing with a named species for a hue. A
species is the same colour on all three.

The basemap is the stripped-down style the Android sample uses: land, water,
the major roads and the place names, with the finer streets from zoom 13. It is
MapLibre's own design type, so it applies there and the page runs on whatever
basemap the other providers bring.

Verified by rendering, not by compiling. iOS has a screenshot test that opens
the page and waits for the trees, because the failures here — asset missing
from the bundle, style URL unresolved — all look like "the map draws and the
trees do not" and none of them throw. It caught the first cut passing
declutterPx scaled by the content scale, which thinned a retina screen to a
third of the density Android shows.
The renderer gained a split ground/label layer, glyph handover and the
style's attributions; this component was written before any of it and
kept using the single combined route. So a map drew no labels, and a
basemap drawing OpenStreetMap credited nobody — which is a licence
problem, not a cosmetic one.

Follows android-vectortile's VectorTileLayer:

- groundTiles and labelTiles on two routes, ground mounted first. zIndex
  is not enough on its own: maplibre and mapkit stack raster layers in
  mount order and ignore it, so the element order carries the stacking
  there and zIndex carries it on google-maps, leaflet, openlayers and
  cesium.
- onGlyphsLoaded replaces the label half alone. Mounted alongside its
  predecessor and the old one dropped 900 ms later, because replacing a
  raster layer's URL is remove-then-add and the layer is gone until the
  new source fetches its first tiles. Same window as android's
  HANDOVER_MS.
- attributions attached to both halves. The overlay deduplicates, so
  hiding either half cannot silence the credit.

Restyling still recolours in place; both halves re-key on the style
version so the map refetches what it already holds.

Registers the package in the API gate, which never covered it. The
surface is unchanged — the same seven props.

Verified on 17 providers with npm run smoke: 14 OK and all 17 renders
distinct. The three that are not (mapkit, mappls, google-maps-3d) fail
the same way on an unrelated page — an invalid MapKit token, a blocked
Mappls CDN — and did so before this.
PACKAGES is hand-maintained, so adding a package and forgetting to list
it means the gate skips it — silently, which is the worst way for a gate
to break: it stays green. CLAUDE.md records this happening once with
reactnative-for-longdo. It had happened four more times since, to
vectortile, react-vectortile and react-icons-{jp,us,weather}.

check now walks the workspace and fails on any published package that is
in neither PACKAGES nor UNGATED. dump only warns — dump is how you accept
a change, so stopping there would block the way out.

UNGATED carries a reason rather than a list of names. The two templates
are there because they exist to be copied and rewritten, and freezing
that fights their purpose. private workspaces — examples/* — are out of
scope: nothing unpublished has an app developer's frozen API.

Registers the four that were missing, with baselines. vectortile is the
one that matters: it is the renderer react-vectortile is built on, and
its 562-line surface was carrying no gate at all.
"*" accepted any version of @mapconductor/vectortile, and the two have
to move together: the binding calls groundTiles, labelTiles and
onGlyphsLoaded, none of which existed a version ago. A range that cannot
be wrong is a range that cannot warn.

Caret, matching js-sdk-core and js-sdk-react alongside it.
CI here was api-surface and deploy only. Nothing ran the tests, so a
change that kept every signature and broke what was behind it went
through green.

`npm test` walks the workspaces; each package's `pretest` builds its own
dist, so there is nothing to sequence. Lint runs alongside.

`npm run smoke` stays out. It drives real Chrome across every provider
and needs API keys and minutes; if it belongs in CI it belongs on its
own trigger.

Also moves js-sdk-core to the Web Mercator all three platforms now agree
on.
The tile is a 256 px canvas shown at 256 CSS px, so nothing here
multiplies by devicePixelRatio and the icon is density independent
already. `sizePx` did not say which pixels it meant, which is how
android-sdk ended up building the same icon at raw device pixels and
shrinking it on denser screens.

Also drops 14 to 10, matching android-sdk and ios-sdk.
…ty one

A transparent tile is the right answer for a spot with nothing on it
and the wrong one for a render that failed: the library keeps it, and
the hole stays with the neighbouring tiles' overhang cut off at its
edge. js-sdk-core now distinguishes the two, so this path reports a
failure as a failed tile and lets the library ask again.
Bigger tiles are cheaper for the same screen, so 512 stays the default,
but ArcGIS's 3D SceneView picks the level as though every tile were 256
pixels and fetches one level deeper with four times the tiles. The
layer now asks the map's service registry first; an explicit tileSize
still wins.
Style dimensions are screen units, so the rasteriser needs two numbers
to turn them into pixels: the pixel count asked for, and the width the
tile will be shown at. It only had the first and assumed the second was
512, which held everywhere until ArcGIS's 3D SceneView, where a tile
lands in a 256 px slot whatever pixels it carries -- and every label
and every road came out at half size, with the style read one zoom too
deep.

The renderer now takes the display size and derives both rules from it;
512 reproduces the old numbers exactly, so nothing else moves.
The page gets an "As basemap" checkbox covering MapLibre, Google (2D), ArcGIS, Mapbox, MapTiler, TomTom, Longdo, HERE, Leaflet, OpenLayers, deck.gl, Cesium and Azure; MapKit, Mappls and Google 3D have no design that draws nothing. Bumps the provider packages for their None designs and syncs the vector tile wasm (output version 17).
persistSourceTiles / persistRenderedTiles put them in Cache Storage; cacheBytes bounds the in-memory source cache.
…m and azuremaps

deck.gl gets a singleton view and design options; the camera sync registry, heatmap, design and post office pages follow; the vector tile page turns the tile caches on. Bumps maplibre (tests), tomtom and azuremaps (zoom clamp).
…ectly

On a map that registers VectorStyleSupport no raster layer is mounted; the
style becomes the basemap. Other backends keep the raster path. The sample's
As-basemap toggle drives it.
Submodule bumps for the in-place mapDesignType path on every web provider,
Google 2D None, and Longdo's deck.gl global isolation.

examples: hide inactive singleton map layers with opacity as well as
visibility, which Longdo's built-in MapLibre attribution was overriding and
staying on screen under every other provider.

smoke: DPR env (deviceScaleFactor) and gmp-map-3d as a map surface.
A style can now be adjusted on the vector-tile path, not just handed over
whole: rules are applied to the document before anything is drawn, so a
rule that hides a role leaves those layers out of what the renderer is
given and they cost nothing to not draw. The compiler's wasm is loaded
only when there are rules; a URL style is fetched first, because the
compiler takes text.

examples: the knobs are rules now. Each category hides several roles,
because a knob is a thing on the map and a role is a thing in the style —
'land' on screen is the background plus the landuse patches plus the
sites, and hiding only the middle one leaves the ground as it was. Labels
join them, switchable but not tinted: pulling every symbol layer towards
one colour would flatten the legend by merely opening the page.

The colour knobs go through the same rules (mix blends towards the picked
colour rather than replacing it), so recolour.ts — a hand-rolled colour
parser matching on Shortbread source layers — is gone. Same map: 0.3% of
pixels differ at the default palette.
# Conflicts:
#	js-sdk-core
#	react-for-arcgis
#	react-for-azuremaps
#	react-for-cesium
#	react-for-googlemaps
#	react-for-here
#	react-for-leaflet
#	react-for-longdo
#	react-for-mapbox
#	react-for-mapkit
#	react-for-maplibre
#	react-for-mappls
#	react-for-maptiler
#	react-for-openlayers
#	react-for-tomtom
#	reactnative-for-arcgis
#	reactnative-for-googlemaps
#	reactnative-for-longdo
#	reactnative-for-mapbox
#	reactnative-for-maplibre
#	reactnative-for-maptiler
#	reactnative-for-tomtom
The packages' `node --test "test/**/*.test.mjs"` relies on node --test
expanding the glob, which it does only from Node 21. On 20 every package
with tests fails without running any.
@wf9a5m75

Copy link
Copy Markdown
Contributor Author

Added 065c404: test.yml now runs on Node 22. The packages' node --test "test/**/*.test.mjs" needs Node 21+ to expand the glob; on 20 every package with tests would fail before running anything. The same failure showed up in MapConductor/vectortile's CI.

@wf9a5m75
wf9a5m75 merged commit af74790 into develop Oct 25, 2026
1 of 2 checks passed
@wf9a5m75
wf9a5m75 deleted the feature/bearing-clockwise branch October 25, 2026 07:11
wf9a5m75 added a commit that referenced this pull request Oct 27, 2026
51511d2 added the react-for-deckgl gitlink without a .gitmodules entry, so
every checkout with submodules fails ("No url found for submodule path
'react-for-deckgl' in .gitmodules"): the Dependency Submission workflow on
develop since #24, and the PR checks into main.
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