Swap the pin marker for a rounded-square badge
The National Park Service frames its own map symbols (the icons already vendored in for POI categories) in a rounded square, not a pin — pairing a pin shape with NPS-style pictograms looked like two mismatched icon conventions. Switched the marker to a rounded square to match. This also simplifies the placement code: a pin has to anchor at its tip, so the category glyph on top needed an offset computed from the pin/glyph geometry to land in the pin's head. A square has no tip — both the badge and the glyph layer just center directly on the coordinate with no offset math at all.
This commit is contained in:
1 parent
2a83bb10ca
commit
7de8428b05
3 files changed
+50
-64
No files matched your search
+22
-23
@@ -219,38 +219,37 @@ the same local element name "icon" once REXML drops namespace
|
|||||||
prefixes, so this specifically matches on namespace *URI*
|
prefixes, so this specifically matches on namespace *URI*
|
||||||
(`https://osmand.net`), not local name, to avoid picking whichever
|
(`https://osmand.net`), not local name, to avoid picking whichever
|
||||||
happens to come last in the file). Individual waypoints render as a
|
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
|
rounded-square badge (`public/js/icons.js`'s `MAP_MARKER_ICON`) rather
|
||||||
existing `WAYPOINT_ICON` outline, filled instead of stroked) rather
|
|
||||||
than a plain circle — a flat colored dot turned out to look too much
|
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
|
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
|
instead of reading as "your data". A rounded square rather than a
|
||||||
map uses for its own points. It's colored via
|
pin/teardrop: it matches how the National Park Service itself frames
|
||||||
`["coalesce", ["get", "color"], TRACK_COLOR]` (falling back to the
|
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
|
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
|
GNSS pin) and — unlike a pin, which has to anchor at its tip — centered
|
||||||
standard map-pin convention — the tip, not the shape's center, lands on
|
directly on the coordinate with plain default anchoring, which also
|
||||||
the actual coordinate). A small curated icon set
|
means the category-glyph layer on top needs no offset math: both
|
||||||
(`public/js/icons.js`'s `POI_ICONS`, covering the categories p4n2gpx
|
layers just share the same center anchor at `[0, 0]`. A small curated
|
||||||
actually uses, not an exhaustive OsmAnd icon set) draws a white glyph
|
icon set (`public/js/icons.js`'s `POI_ICONS`, covering the categories
|
||||||
on top of the pin's head, in a symbol layer offset up from the shared
|
p4n2gpx actually uses, not an exhaustive OsmAnd icon set) draws a white
|
||||||
tip anchor by a size computed from the pin and glyph geometry
|
glyph on top of the badge for any waypoint whose `icon` matches a known
|
||||||
(`POI_ICON_OFFSET_Y` — both layers anchor to the same raster canvas
|
key (filtered out entirely for anything else, so an unrecognized icon
|
||||||
edge, so the offset is "how far above that shared anchor is the pin's
|
key just falls back to a plain colored badge rather than a
|
||||||
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
|
|
||||||
broken/missing image warning). Six of the seven glyphs are the
|
broken/missing image warning). Six of the seven glyphs are the
|
||||||
National Park Service's own map symbols
|
National Park Service's own map symbols
|
||||||
(`github.com/nationalparkservice/symbol-library`, BSD-licensed,
|
(`github.com/nationalparkservice/symbol-library`, BSD-licensed,
|
||||||
originally forked from Mapbox's Maki project) — a professional,
|
originally forked from Mapbox's Maki project) — a professional,
|
||||||
purpose-built icon set beats hand-drawing "fuel pump"/"picnic table"
|
purpose-built icon set beats hand-drawing "fuel pump"/"picnic table"
|
||||||
glyphs from scratch; `forest` has no NPS equivalent (that set covers
|
glyphs from scratch; `forest` has no NPS equivalent (that set covers
|
||||||
facilities, not land cover) and stays hand-drawn. Both the pin and the
|
facilities, not land cover) and stays hand-drawn. Both the badge and
|
||||||
category glyphs are rasterized once per style load (`_registerPoiIcons`,
|
the category glyphs are rasterized once per style load
|
||||||
an SVG → `<img>` → `<canvas>` → `ImageData` round trip, since these
|
(`_registerPoiIcons`, an SVG → `<img>` → `<canvas>` → `ImageData` round
|
||||||
need to be real raster images for `map.addImage`) and registered as SDF
|
trip, since these need to be real raster images for `map.addImage`) and
|
||||||
images — a single alpha shape MapLibre tints at render time via
|
registered as SDF images — a single alpha shape MapLibre tints at
|
||||||
`icon-color` rather than a fixed-color image. The points source itself
|
render time via `icon-color` rather than a fixed-color image. The
|
||||||
|
points source itself
|
||||||
is created with
|
is created with
|
||||||
`cluster: true`; nearby points collapse into a stepped-radius,
|
`cluster: true`; nearby points collapse into a stepped-radius,
|
||||||
sequential-blue circle sized by `point_count` below `clusterMaxZoom`,
|
sequential-blue circle sized by `point_count` below `clusterMaxZoom`,
|
||||||
|
|||||||
+17
-33
@@ -6,7 +6,7 @@ import {
|
|||||||
WAYPOINT_ICON,
|
WAYPOINT_ICON,
|
||||||
TRACK_ICON,
|
TRACK_ICON,
|
||||||
ROUTE_ICON,
|
ROUTE_ICON,
|
||||||
MAP_PIN_ICON,
|
MAP_MARKER_ICON,
|
||||||
POI_ICONS,
|
POI_ICONS,
|
||||||
} from "/js/icons.js";
|
} from "/js/icons.js";
|
||||||
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||||
@@ -19,24 +19,9 @@ const MOVE_URL = "/api/gpx/move";
|
|||||||
const TRACK_COLOR = "#e63946";
|
const TRACK_COLOR = "#e63946";
|
||||||
const POI_ICON_KEYS = Object.keys(POI_ICONS);
|
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_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
|
const POI_ICON_SIZE = 0.3; // 48 * 0.3 = ~14px, sized to sit inside the marker badge below
|
||||||
|
const MAP_MARKER_ID = "map-marker";
|
||||||
// The pin glyph (icons.js's MAP_PIN_ICON) is a 24x24 viewBox with its head
|
const MAP_MARKER_SIZE = 0.55;
|
||||||
// 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);
|
|
||||||
|
|
||||||
// Custom drag payload MIME type for moving an already-uploaded file between
|
// 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
|
// 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
|
// color information worth preserving, and SDF lets the same sprite be
|
||||||
// reused if a future category needs a different tint.
|
// reused if a future category needs a different tint.
|
||||||
async _registerPoiIcons() {
|
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(
|
await Promise.all(
|
||||||
Object.keys(sources).map(async (key) => {
|
Object.keys(sources).map(async (key) => {
|
||||||
if (this._map.hasImage(key)) return;
|
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 },
|
layout: { "text-field": ["get", "point_count_abbreviated"], "text-font": ["Noto Sans Regular"], "text-size": 11 },
|
||||||
paint: { "text-color": "#fff" },
|
paint: { "text-color": "#fff" },
|
||||||
});
|
});
|
||||||
// A pin (icons.js's MAP_PIN_ICON), not a plain circle: a flat colored
|
// A rounded-square badge (icons.js's MAP_MARKER_ICON), not a plain
|
||||||
// dot looks too much like a base map style's own small circular POI
|
// circle: a flat colored dot looked too much like a base map style's
|
||||||
// icons, so individual waypoints blended in and were hard to spot as
|
// own small circular POI icons, so individual waypoints blended in
|
||||||
// "your data" versus the map's own points. Anchored at its tip (the
|
// instead of standing out as "your data". A rounded square rather than
|
||||||
// standard map-pin convention), not centered on the coordinate.
|
// 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({
|
this._map.addLayer({
|
||||||
id: `${pointsId}-unclustered`,
|
id: `${pointsId}-unclustered`,
|
||||||
type: "symbol",
|
type: "symbol",
|
||||||
source: pointsId,
|
source: pointsId,
|
||||||
filter: ["!", ["has", "point_count"]],
|
filter: ["!", ["has", "point_count"]],
|
||||||
layout: {
|
layout: {
|
||||||
"icon-image": MAP_PIN_ID,
|
"icon-image": MAP_MARKER_ID,
|
||||||
"icon-size": MAP_PIN_SIZE,
|
"icon-size": MAP_MARKER_SIZE,
|
||||||
"icon-anchor": "bottom",
|
|
||||||
"icon-allow-overlap": true,
|
"icon-allow-overlap": true,
|
||||||
"icon-ignore-placement": true,
|
"icon-ignore-placement": true,
|
||||||
},
|
},
|
||||||
@@ -445,9 +433,7 @@ export class GpxPanelControl {
|
|||||||
});
|
});
|
||||||
// Only for the curated POI_ICON_KEYS (see icons.js) — anything else
|
// Only for the curated POI_ICON_KEYS (see icons.js) — anything else
|
||||||
// (a plain hand-placed waypoint, or a category p4n2gpx/OsmAnd hasn't
|
// (a plain hand-placed waypoint, or a category p4n2gpx/OsmAnd hasn't
|
||||||
// been taught here) just keeps the plain pin above with no glyph.
|
// been taught here) just keeps the plain badge 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.
|
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: `${pointsId}-unclustered-icon`,
|
id: `${pointsId}-unclustered-icon`,
|
||||||
type: "symbol",
|
type: "symbol",
|
||||||
@@ -456,8 +442,6 @@ export class GpxPanelControl {
|
|||||||
layout: {
|
layout: {
|
||||||
"icon-image": ["get", "icon"],
|
"icon-image": ["get", "icon"],
|
||||||
"icon-size": POI_ICON_SIZE,
|
"icon-size": POI_ICON_SIZE,
|
||||||
"icon-anchor": "bottom",
|
|
||||||
"icon-offset": [0, POI_ICON_OFFSET_Y],
|
|
||||||
"icon-allow-overlap": true,
|
"icon-allow-overlap": true,
|
||||||
"icon-ignore-placement": true,
|
"icon-ignore-placement": true,
|
||||||
},
|
},
|
||||||
|
|||||||
+11
-8
@@ -58,14 +58,17 @@ export const ROUTE_ICON = `
|
|||||||
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5" stroke-dasharray="2 2.2"/>
|
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5" stroke-dasharray="2 2.2"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
// Filled version of WAYPOINT_ICON's outline (same path, fill instead of
|
// A rounded-square badge — used as the map marker for individual waypoints,
|
||||||
// stroke) — used as the map marker for individual waypoints, tinted by
|
// tinted by category color via icon-color (see gpx.js). Not a plain dot,
|
||||||
// category color via icon-color (see gpx.js). A pin/teardrop shape rather
|
// which blended in with a base map style's own small circular POI icons,
|
||||||
// than a plain dot specifically so it doesn't blend in with a base map
|
// and not a pin/teardrop either — a rounded square is the National Park
|
||||||
// style's own small circular POI icons; the tip (not the head center) is
|
// Service's own convention for framing these exact category symbols
|
||||||
// the anchor point, the standard map-pin convention.
|
// (POI_ICONS below), so pairing the two looks native rather than like two
|
||||||
export const MAP_PIN_ICON = `
|
// mismatched icon styles. Centered on the coordinate (no tip to anchor at),
|
||||||
<svg viewBox="0 0 24 24"><path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z" fill="currentColor"/></svg>`;
|
// 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 = `
|
||||||
|
<svg viewBox="0 0 24 24"><rect x="1" y="1" width="22" height="22" rx="5" fill="currentColor"/></svg>`;
|
||||||
|
|
||||||
// POI category glyphs (park4night/OsmAnd exports), keyed by the OsmAnd icon
|
// POI category glyphs (park4night/OsmAnd exports), keyed by the OsmAnd icon
|
||||||
// name a file's <osmand:icon> extension carries — a small curated set for
|
// name a file's <osmand:icon> extension carries — a small curated set for
|
||||||
|
|||||||
Reference in new issue
Block a user