diff --git a/docs/architecture.adoc b/docs/architecture.adoc index 066f808..548633c 100644 --- a/docs/architecture.adoc +++ b/docs/architecture.adoc @@ -219,38 +219,37 @@ the same local element name "icon" once REXML drops namespace prefixes, so this specifically matches on namespace *URI* (`https://osmand.net`), not local name, to avoid picking whichever happens to come last in the file). Individual waypoints render as a -pin (`public/js/icons.js`'s `MAP_PIN_ICON`, the same path as the -existing `WAYPOINT_ICON` outline, filled instead of stroked) rather +rounded-square badge (`public/js/icons.js`'s `MAP_MARKER_ICON`) rather than a plain circle — a flat colored dot turned out to look too much like a base map style's own small circular POI icons, blending in -instead of reading as "your data"; a pin is a shape convention no base -map uses for its own points. It's colored via -`["coalesce", ["get", "color"], TRACK_COLOR]` (falling back to the +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 anchored at its tip (`icon-anchor: "bottom"`, the -standard map-pin convention — the tip, not the shape's center, lands on -the actual coordinate). 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 pin's head, in a symbol layer offset up from the shared -tip anchor by a size computed from the pin and glyph geometry -(`POI_ICON_OFFSET_Y` — both layers anchor to the same raster canvas -edge, so the offset is "how far above that shared anchor is the pin's -head-center, minus how far above it the glyph's own visual center -already sits at its own icon-size"), filtered to just the known keys -(anything else keeps the plain pin with no glyph, rather than a +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 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 pin and the -category glyphs are rasterized once per style load (`_registerPoiIcons`, -an SVG → `` → `` → `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 +facilities, not land cover) and stays hand-drawn. Both the badge and +the category glyphs are rasterized once per style load +(`_registerPoiIcons`, an SVG → `` → `` → `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 `cluster: true`; nearby points collapse into a stepped-radius, sequential-blue circle sized by `point_count` below `clusterMaxZoom`, diff --git a/public/js/gpx.js b/public/js/gpx.js index f9738aa..7a38fb8 100644 --- a/public/js/gpx.js +++ b/public/js/gpx.js @@ -6,7 +6,7 @@ import { WAYPOINT_ICON, TRACK_ICON, ROUTE_ICON, - MAP_PIN_ICON, + MAP_MARKER_ICON, POI_ICONS, } from "/js/icons.js"; import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js"; @@ -19,24 +19,9 @@ const MOVE_URL = "/api/gpx/move"; const TRACK_COLOR = "#e63946"; const POI_ICON_KEYS = Object.keys(POI_ICONS); const POI_ICON_IMAGE_SIZE = 48; // rasterized once at this size, rendered much smaller by icon-size below -const POI_ICON_SIZE = 0.3; // 48 * 0.3 = ~14px, matching the pin head's diameter below - -// The pin glyph (icons.js's MAP_PIN_ICON) is a 24x24 viewBox with its head -// circle centered at y=9, rasterized onto the same POI_ICON_IMAGE_SIZE -// canvas as every other icon. icon-anchor: "bottom" anchors both this layer -// and the category-glyph layer below to the *raster canvas's* bottom edge -// (not the pin's visual tip, which sits a little above that edge) — so both -// layers share one screen anchor point, and placing the glyph in the pin's -// head is just "how far above that shared anchor is each icon's own visual -// center, and what's the gap between them": -const MAP_PIN_ID = "map-pin"; -const MAP_PIN_SIZE = 0.55; -const MAP_PIN_HEAD_CENTER_SVG_Y = 9; -const MAP_PIN_VIEWBOX = 24; -const pinHeadCenterAboveAnchorPx = - (MAP_PIN_VIEWBOX - MAP_PIN_HEAD_CENTER_SVG_Y) * (POI_ICON_IMAGE_SIZE / MAP_PIN_VIEWBOX) * MAP_PIN_SIZE; -const glyphOwnCenterAboveAnchorPx = (POI_ICON_IMAGE_SIZE * POI_ICON_SIZE) / 2; // glyph content roughly fills its own canvas -const POI_ICON_OFFSET_Y = -((pinHeadCenterAboveAnchorPx - glyphOwnCenterAboveAnchorPx) / POI_ICON_SIZE); +const POI_ICON_SIZE = 0.3; // 48 * 0.3 = ~14px, sized to sit inside the marker badge below +const MAP_MARKER_ID = "map-marker"; +const MAP_MARKER_SIZE = 0.55; // Custom drag payload MIME type for moving an already-uploaded file between // folders, as opposed to an OS file drag (type "Files") for uploading a new @@ -71,7 +56,7 @@ export class GpxPanelControl { // color information worth preserving, and SDF lets the same sprite be // reused if a future category needs a different tint. async _registerPoiIcons() { - const sources = { ...POI_ICONS, [MAP_PIN_ID]: MAP_PIN_ICON }; + const sources = { ...POI_ICONS, [MAP_MARKER_ID]: MAP_MARKER_ICON }; await Promise.all( Object.keys(sources).map(async (key) => { if (this._map.hasImage(key)) return; @@ -424,20 +409,23 @@ export class GpxPanelControl { layout: { "text-field": ["get", "point_count_abbreviated"], "text-font": ["Noto Sans Regular"], "text-size": 11 }, paint: { "text-color": "#fff" }, }); - // A pin (icons.js's MAP_PIN_ICON), not a plain circle: a flat colored - // dot looks too much like a base map style's own small circular POI - // icons, so individual waypoints blended in and were hard to spot as - // "your data" versus the map's own points. Anchored at its tip (the - // standard map-pin convention), not centered on the coordinate. + // A rounded-square badge (icons.js's MAP_MARKER_ICON), not a plain + // circle: a flat colored dot looked too much like a base map style's + // own small circular POI icons, so individual waypoints blended in + // instead of standing out as "your data". A rounded square rather than + // a pin/teardrop specifically to match how the NPS itself frames these + // exact category symbols (POI_ICONS below) on its own maps. Centered on + // the coordinate — a square has no natural "tip" the way a pin does, so + // default center anchoring is simplest and also means the glyph layer + // below needs no offset, just the same center anchor. this._map.addLayer({ id: `${pointsId}-unclustered`, type: "symbol", source: pointsId, filter: ["!", ["has", "point_count"]], layout: { - "icon-image": MAP_PIN_ID, - "icon-size": MAP_PIN_SIZE, - "icon-anchor": "bottom", + "icon-image": MAP_MARKER_ID, + "icon-size": MAP_MARKER_SIZE, "icon-allow-overlap": true, "icon-ignore-placement": true, }, @@ -445,9 +433,7 @@ export class GpxPanelControl { }); // Only for the curated POI_ICON_KEYS (see icons.js) — anything else // (a plain hand-placed waypoint, or a category p4n2gpx/OsmAnd hasn't - // been taught here) just keeps the plain pin above with no glyph. - // icon-offset moves it up from the anchor (the pin's tip) into the - // pin's round head — see POI_ICON_OFFSET_Y's comment above. + // been taught here) just keeps the plain badge above with no glyph. this._map.addLayer({ id: `${pointsId}-unclustered-icon`, type: "symbol", @@ -456,8 +442,6 @@ export class GpxPanelControl { layout: { "icon-image": ["get", "icon"], "icon-size": POI_ICON_SIZE, - "icon-anchor": "bottom", - "icon-offset": [0, POI_ICON_OFFSET_Y], "icon-allow-overlap": true, "icon-ignore-placement": true, }, diff --git a/public/js/icons.js b/public/js/icons.js index e47cba3..2c2f5e5 100644 --- a/public/js/icons.js +++ b/public/js/icons.js @@ -58,14 +58,17 @@ export const ROUTE_ICON = ` `; -// Filled version of WAYPOINT_ICON's outline (same path, fill instead of -// stroke) — used as the map marker for individual waypoints, tinted by -// category color via icon-color (see gpx.js). A pin/teardrop shape rather -// than a plain dot specifically so it doesn't blend in with a base map -// style's own small circular POI icons; the tip (not the head center) is -// the anchor point, the standard map-pin convention. -export const MAP_PIN_ICON = ` -`; +// A rounded-square badge — used as the map marker for individual waypoints, +// tinted by category color via icon-color (see gpx.js). Not a plain dot, +// which blended in with a base map style's own small circular POI icons, +// and not a pin/teardrop either — a rounded square is the National Park +// Service's own convention for framing these exact category symbols +// (POI_ICONS below), so pairing the two looks native rather than like two +// mismatched icon styles. Centered on the coordinate (no tip to anchor at), +// which also means the category glyph on top needs no offset — both layers +// just share the same center anchor at [0, 0]. +export const MAP_MARKER_ICON = ` +`; // POI category glyphs (park4night/OsmAnd exports), keyed by the OsmAnd icon // name a file's extension carries — a small curated set for