A park4night/OsmAnd POI export (e.g. a p4n2gpx area export) tags every waypoint with a category and a per-category color/icon in its <extensions> block, all of which was previously thrown away — every waypoint rendered as the same plain red dot, unreadable once a file has a few thousand of them. Maptool::Gpx now captures category/color/icon (waypoint_style), matched by namespace URI rather than local element name: a p4n2gpx file carries both <osmand:icon> and <locus:icon> side by side, and REXML strips prefixes from Element#name, so both report as just "icon" — matching on that alone was silently picking the (useless) Locus drawable path instead of the OsmAnd category key. Regression test added for this. On the map: waypoints are colored by category (falling back to the plain track color for anything uncategorized), a small hand-drawn icon set (public/js/icons.js's POI_ICONS, rasterized to SDF sprites at runtime) draws a glyph on top for the categories it covers, the points source is clustered so a dense export collapses into count bubbles instead of a solid blob, and a legend in the side panel lists whichever categories are currently visible. Splitting each file's GeoJSON into a lines source and a points source (rather than one mixed source with a geometry-type filter per layer) was needed for clustering, since MapLibre's cluster support only understands a source made entirely of points.
113 lines
4.9 KiB
JavaScript
113 lines
4.9 KiB
JavaScript
// Small hand-authored monoline SVG icons for the sidebar, so the app
|
|
// doesn't need to vendor an icon font/library for four glyphs.
|
|
|
|
export const LAYERS_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
|
|
<path d="M12 3 2 9l10 6 10-6-10-6z"/>
|
|
<path d="M2 14l10 6 10-6"/>
|
|
</svg>`;
|
|
|
|
export const GPX_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="5" cy="6" r="2"/>
|
|
<circle cx="19" cy="18" r="2"/>
|
|
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5"/>
|
|
</svg>`;
|
|
|
|
export const RADAR_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
|
|
<path d="M7 18a4 4 0 1 1 .7-7.94A5 5 0 0 1 17 9a4 4 0 0 1 0 8H7z"/>
|
|
</svg>`;
|
|
|
|
export const GNSS_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
<circle cx="12" cy="12" r="5.5"/>
|
|
<circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/>
|
|
<path d="M12 2v3M12 19v3M2 12h3M19 12h3"/>
|
|
</svg>`;
|
|
|
|
export const NEW_FOLDER_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>
|
|
<path d="M12 11v5M9.5 13.5h5"/>
|
|
</svg>`;
|
|
|
|
export const UPLOAD_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 15V4M8 8l4-4 4 4"/>
|
|
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/>
|
|
</svg>`;
|
|
|
|
// Shown next to each file in the GPX tree, indicating whether it has
|
|
// waypoints, a track, or both.
|
|
export const WAYPOINT_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z"/>
|
|
<circle cx="12" cy="9" r="2.2"/>
|
|
</svg>`;
|
|
|
|
export const TRACK_ICON = GPX_ICON;
|
|
|
|
// Same shape as TRACK_ICON, dashed — a route is planned rather than
|
|
// recorded, the same visual convention most map apps use for the
|
|
// distinction.
|
|
export const ROUTE_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="5" cy="6" r="2"/>
|
|
<circle cx="19" cy="18" r="2"/>
|
|
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5" stroke-dasharray="2 2.2"/>
|
|
</svg>`;
|
|
|
|
// POI category glyphs (park4night/OsmAnd exports), keyed by the OsmAnd icon
|
|
// name a file's <osmand:icon> extension carries — a small curated set for
|
|
// the categories park4night actually uses (see waypoint_style in
|
|
// lib/maptool/gpx.rb), not an exhaustive OsmAnd icon set. Filled shapes
|
|
// (not stroked outlines like the sidebar icons above) since these render at
|
|
// ~14px on the map, where a hairline stroke disappears; solid silhouettes
|
|
// stay legible that small. Rasterized and used as SDF map icons (see
|
|
// gpx.js), so only the alpha shape matters — the actual `currentColor`
|
|
// value is irrelevant.
|
|
export const POI_ICONS = {
|
|
amenity_parking: `
|
|
<svg viewBox="0 0 24 24"><text x="12" y="18" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700" text-anchor="middle" fill="currentColor">P</text></svg>`,
|
|
|
|
tourism_camp_site: `
|
|
<svg viewBox="0 0 24 24"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 3 22 20 2 20 Z M12 12 15.5 20 8.5 20 Z" fill="currentColor"/></svg>`,
|
|
|
|
leisure_picnic_table: `
|
|
<svg viewBox="0 0 24 24">
|
|
<rect x="3" y="9" width="18" height="3" fill="currentColor"/>
|
|
<path d="M5 12 2 20 5 20 7.5 12 Z M19 12 22 20 19 20 16.5 12 Z" fill="currentColor"/>
|
|
</svg>`,
|
|
|
|
amenity_fuel: `
|
|
<svg viewBox="0 0 24 24"><path d="M12 2C12 2 5 11 5 15.5A7 7 0 0 0 19 15.5C19 11 12 2 12 2Z" fill="currentColor"/></svg>`,
|
|
|
|
tourism_hotel: `
|
|
<svg viewBox="0 0 24 24">
|
|
<rect x="3" y="11" width="18" height="7" rx="1.5" fill="currentColor"/>
|
|
<rect x="4" y="8" width="6" height="4" rx="1" fill="currentColor"/>
|
|
<rect x="3" y="18" width="2" height="3" fill="currentColor"/>
|
|
<rect x="19" y="18" width="2" height="3" fill="currentColor"/>
|
|
</svg>`,
|
|
|
|
forest: `
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12 2 18 12 15 12 19 19 5 19 9 12 6 12Z" fill="currentColor"/>
|
|
<rect x="10.5" y="19" width="3" height="3" fill="currentColor"/>
|
|
</svg>`,
|
|
|
|
sport_cycling: `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round">
|
|
<circle cx="6" cy="17" r="4"/>
|
|
<circle cx="18" cy="17" r="4"/>
|
|
<path d="M6 17 11 8H15M11 8 15 17M18 17 14 10"/>
|
|
</svg>`,
|
|
};
|
|
|
|
export const LIVETRACK_ICON = `
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z"/>
|
|
<path d="M8.5 9a3.5 3.5 0 0 1 7 0" stroke-dasharray="1.5 2.2"/>
|
|
</svg>`;
|