Stop faking SDF for POI icons, fix the resulting blur
CI / check (push) Successful in 40s
CI / deploy (push) Successful in 14s

The badge and category glyphs were registered as SDF images (a single alpha
shape MapLibre tints at render time via icon-color) purely so one shared
sprite could be recolored per category. But SDF rendering assumes the
source actually encodes a distance field — a smooth multi-pixel gradient at
every edge — and these were just normally antialiased rasters (a sharp ~1px
transition). MapLibre's SDF antialiasing pass softened every edge as a
result, visibly blurrier than the base map's own icons, worst on the small
category glyph.

The glyph color never varies (always white), so it never needed runtime
tinting at all: _registerCategoryGlyphs now bakes #fff directly into the
raster and registers one plain image per category. The badge color does
vary (arbitrary per-file hex), but a real POI export only has a handful of
distinct category colors, so _registerBadgeColors rasterizes one sharp
image per distinct color actually present in the points being shown,
cached by color, instead of one shared image tinted at render time.
This commit is contained in:
randy committed 2026-09-07 16:55:23 +02:00
1 parent 7de8428b05
commit 6f7d332993
2 files changed
+94 -40

No files matched your search

+39 -21
View File
@@ -225,32 +225,50 @@ like a base map style's own small circular POI icons, blending in
instead of reading as "your data". A rounded square rather than a
pin/teardrop: it matches how the National Park Service itself frames
these exact category symbols (below) on its own maps, so the two look
native together instead of like mismatched icon styles. It's colored
via `["coalesce", ["get", "color"], TRACK_COLOR]` (falling back to the
plain track color for a waypoint with no category, e.g. a hand-placed
GNSS pin) and — unlike a pin, which has to anchor at its tip — centered
directly on the coordinate with plain default anchoring, which also
means the category-glyph layer on top needs no offset math: both
layers just share the same center anchor at `[0, 0]`. A small curated
icon set (`public/js/icons.js`'s `POI_ICONS`, covering the categories
p4n2gpx actually uses, not an exhaustive OsmAnd icon set) draws a white
glyph on top of the badge for any waypoint whose `icon` matches a known
key (filtered out entirely for anything else, so an unrecognized icon
key just falls back to a plain colored badge rather than a
broken/missing image warning). Six of the seven glyphs are the
native together instead of like mismatched icon styles. `_addLayers`
defaults a point's `color` to the plain track color right on the
feature (`feature.properties.color ||= TRACK_COLOR`) before it ever
reaches a layer, rather than a `coalesce` in a style expression — see
below for why. Unlike a pin, which has to anchor at its tip, the badge
is centered directly on the coordinate with plain default anchoring,
which also means the category-glyph layer on top needs no offset math:
both layers just share the same center anchor at `[0, 0]`. A small
curated icon set (`public/js/icons.js`'s `POI_ICONS`, covering the
categories p4n2gpx actually uses, not an exhaustive OsmAnd icon set)
draws a white glyph on top of the badge for any waypoint whose `icon`
matches a known key (filtered out entirely for anything else, so an
unrecognized icon key just falls back to a plain colored badge rather
than a broken/missing image warning). Six of the seven glyphs are the
National Park Service's own map symbols
(`github.com/nationalparkservice/symbol-library`, BSD-licensed,
originally forked from Mapbox's Maki project) — a professional,
purpose-built icon set beats hand-drawing "fuel pump"/"picnic table"
glyphs from scratch; `forest` has no NPS equivalent (that set covers
facilities, not land cover) and stays hand-drawn. Both the badge and
the category glyphs are rasterized once per style load
(`_registerPoiIcons`, an SVG → `<img>` → `<canvas>` → `ImageData` round
trip, since these need to be real raster images for `map.addImage`) and
registered as SDF images — a single alpha shape MapLibre tints at
render time via `icon-color` rather than a fixed-color image. The
points source itself
is created with
facilities, not land cover) and stays hand-drawn.
+
Both the badge and the glyphs are rasterized from their source SVGs
(an `<img>` → `<canvas>` → `ImageData` round trip, since `map.addImage`
needs a real raster image) as plain, non-SDF images with the actual
target color already baked into the raster — not SDF images (a single
alpha shape MapLibre tints at render time via `icon-color`) as this
first shipped. SDF assumes the source encodes a genuine distance field
(a smooth multi-pixel gradient at every edge); handed a normally
antialiased raster (a sharp ~1px transition) instead, MapLibre's SDF
antialiasing pass made every glyph and badge visibly softer than the
base map's own icons, worst at the glyph's small icon-size. The glyphs
are always white, so `_registerCategoryGlyphs` just bakes `#fff` into
each one and registers a single fixed image per category, once per
style load — no per-render tinting needed at all. The badge's color is
per-file data though (an arbitrary hex value from the GPX), so it can't
be one fixed image; `_registerBadgeColors` instead rasterizes one sharp
image per *distinct* color actually present in the points being shown
(`badge-<color>` as the image id, `["concat", "badge-", ["get",
"color"]]` as the layer's `icon-image`) — cheap in practice, since a
real POI export only ever has a handful of distinct category colors,
and results are cached by color so a second file reusing a color
already seen registers nothing new.
+
The points source itself is created with
`cluster: true`; nearby points collapse into a stepped-radius,
sequential-blue circle sized by `point_count` below `clusterMaxZoom`,
clicking one zooms to its `getClusterExpansionZoom()`, and this is