Extract @trails-cool/map-core package from Planner
New renderer-agnostic package with zero dependencies: - Tile configs (base layers, overlay layers) - Color palettes (surface, highway, smoothness, tracktype, cycleway, bikeroute, elevation, maxspeed) — 8 color maps + 3 color functions - POI category definitions (9 categories with Overpass queries, icons, colors, profile mappings) - Z-index layering constants - Snap distance constant Updated 15 consuming files to import from @trails-cool/map-core. Deleted poi-categories.ts and z-index.ts from the Planner (fully moved). packages/map re-exports tile configs from map-core for backwards compat. All 117 tests pass, no user-facing changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cd939ccf07
commit
60b94b5789
33 changed files with 339 additions and 257 deletions
10
packages/map-core/package.json
Normal file
10
packages/map-core/package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@trails-cool/map-core",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
9
packages/map-core/src/colors/bikeroute.ts
Normal file
9
packages/map-core/src/colors/bikeroute.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export const BIKEROUTE_COLORS: Record<string, string> = {
|
||||
icn: "#7c3aed", // purple — international
|
||||
ncn: "#2563eb", // blue — national
|
||||
rcn: "#0891b2", // teal — regional
|
||||
lcn: "#059669", // emerald — local
|
||||
none: "#d4d4d8", // gray — no route
|
||||
};
|
||||
|
||||
export const DEFAULT_BIKEROUTE_COLOR = "#d4d4d8";
|
||||
11
packages/map-core/src/colors/cycleway.ts
Normal file
11
packages/map-core/src/colors/cycleway.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export const CYCLEWAY_COLORS: Record<string, string> = {
|
||||
track: "#22c55e",
|
||||
lane: "#84cc16",
|
||||
shared_lane: "#eab308",
|
||||
share_busway: "#f97316",
|
||||
opposite_lane: "#818cf8",
|
||||
separate: "#16a34a",
|
||||
no: "#ef4444",
|
||||
};
|
||||
|
||||
export const DEFAULT_CYCLEWAY_COLOR = "#9ca3af";
|
||||
18
packages/map-core/src/colors/elevation.ts
Normal file
18
packages/map-core/src/colors/elevation.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export function routeGradeColor(grade: number): string {
|
||||
const absGrade = Math.abs(grade);
|
||||
if (absGrade < 3) return "#22c55e";
|
||||
if (absGrade < 6) return "#eab308";
|
||||
if (absGrade < 10) return "#f97316";
|
||||
if (absGrade < 15) return "#ef4444";
|
||||
return "#991b1b";
|
||||
}
|
||||
|
||||
export function elevationColor(t: number): string {
|
||||
// green (0) → yellow (0.5) → red (1)
|
||||
if (t <= 0.5) {
|
||||
const r = Math.round(255 * (t * 2));
|
||||
return `rgb(${r}, 200, 50)`;
|
||||
}
|
||||
const g = Math.round(200 * (1 - (t - 0.5) * 2));
|
||||
return `rgb(255, ${g}, 50)`;
|
||||
}
|
||||
29
packages/map-core/src/colors/highway.ts
Normal file
29
packages/map-core/src/colors/highway.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export const HIGHWAY_COLORS: Record<string, string> = {
|
||||
// Major roads — warm tones (caution for cyclists)
|
||||
motorway: "#dc2626",
|
||||
motorway_link: "#dc2626",
|
||||
trunk: "#ea580c",
|
||||
trunk_link: "#ea580c",
|
||||
primary: "#f97316",
|
||||
primary_link: "#f97316",
|
||||
// Urban roads — neutral tones
|
||||
secondary: "#6366f1",
|
||||
secondary_link: "#6366f1",
|
||||
tertiary: "#818cf8",
|
||||
tertiary_link: "#818cf8",
|
||||
residential: "#6b7280",
|
||||
unclassified: "#9ca3af",
|
||||
living_street: "#a78bfa",
|
||||
// Paths & cycling infrastructure — green tones
|
||||
cycleway: "#16a34a",
|
||||
path: "#22c55e",
|
||||
footway: "#4ade80",
|
||||
track: "#65a30d",
|
||||
bridleway: "#84cc16",
|
||||
// Service & other — muted tones
|
||||
service: "#d4d4d8",
|
||||
pedestrian: "#c084fc",
|
||||
steps: "#f472b6",
|
||||
};
|
||||
|
||||
export const DEFAULT_HIGHWAY_COLOR = "#9ca3af";
|
||||
8
packages/map-core/src/colors/index.ts
Normal file
8
packages/map-core/src/colors/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { SURFACE_COLORS, DEFAULT_SURFACE_COLOR } from "./surface.ts";
|
||||
export { HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR } from "./highway.ts";
|
||||
export { SMOOTHNESS_COLORS, DEFAULT_SMOOTHNESS_COLOR } from "./smoothness.ts";
|
||||
export { TRACKTYPE_COLORS, DEFAULT_TRACKTYPE_COLOR } from "./tracktype.ts";
|
||||
export { CYCLEWAY_COLORS, DEFAULT_CYCLEWAY_COLOR } from "./cycleway.ts";
|
||||
export { BIKEROUTE_COLORS, DEFAULT_BIKEROUTE_COLOR } from "./bikeroute.ts";
|
||||
export { elevationColor, routeGradeColor } from "./elevation.ts";
|
||||
export { maxspeedColor } from "./maxspeed.ts";
|
||||
12
packages/map-core/src/colors/maxspeed.ts
Normal file
12
packages/map-core/src/colors/maxspeed.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export function maxspeedColor(speed: string): string {
|
||||
if (speed === "walk") return "#22c55e";
|
||||
if (speed === "none") return "#991b1b";
|
||||
const num = parseInt(speed, 10);
|
||||
if (isNaN(num)) return "#9ca3af"; // unknown/gray
|
||||
if (num <= 20) return "#22c55e";
|
||||
if (num <= 30) return "#22c55e";
|
||||
if (num <= 50) return "#eab308";
|
||||
if (num <= 70) return "#f97316";
|
||||
if (num <= 100) return "#ef4444";
|
||||
return "#991b1b"; // >100 dark red
|
||||
}
|
||||
12
packages/map-core/src/colors/smoothness.ts
Normal file
12
packages/map-core/src/colors/smoothness.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export const SMOOTHNESS_COLORS: Record<string, string> = {
|
||||
excellent: "#22c55e",
|
||||
good: "#16a34a",
|
||||
intermediate: "#eab308",
|
||||
bad: "#f97316",
|
||||
very_bad: "#ef4444",
|
||||
horrible: "#991b1b",
|
||||
very_horrible: "#7f1d1d",
|
||||
impassable: "#450a0a",
|
||||
};
|
||||
|
||||
export const DEFAULT_SMOOTHNESS_COLOR = "#9ca3af";
|
||||
21
packages/map-core/src/colors/surface.ts
Normal file
21
packages/map-core/src/colors/surface.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export const SURFACE_COLORS: Record<string, string> = {
|
||||
asphalt: "#6b7280",
|
||||
concrete: "#9ca3af",
|
||||
paved: "#6b7280",
|
||||
paving_stones: "#78716c",
|
||||
cobblestone: "#a8a29e",
|
||||
gravel: "#92400e",
|
||||
compacted: "#b45309",
|
||||
"fine_gravel": "#d97706",
|
||||
ground: "#65a30d",
|
||||
dirt: "#84cc16",
|
||||
grass: "#22c55e",
|
||||
sand: "#fbbf24",
|
||||
mud: "#713f12",
|
||||
wood: "#a16207",
|
||||
unpaved: "#ca8a04",
|
||||
path: "#16a34a",
|
||||
track: "#ea580c",
|
||||
};
|
||||
|
||||
export const DEFAULT_SURFACE_COLOR = "#9ca3af";
|
||||
9
packages/map-core/src/colors/tracktype.ts
Normal file
9
packages/map-core/src/colors/tracktype.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export const TRACKTYPE_COLORS: Record<string, string> = {
|
||||
grade1: "#22c55e",
|
||||
grade2: "#84cc16",
|
||||
grade3: "#eab308",
|
||||
grade4: "#f97316",
|
||||
grade5: "#ef4444",
|
||||
};
|
||||
|
||||
export const DEFAULT_TRACKTYPE_COLOR = "#9ca3af";
|
||||
23
packages/map-core/src/index.ts
Normal file
23
packages/map-core/src/index.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export type { TileLayerConfig, OverlayLayerConfig } from "./tiles.ts";
|
||||
export { baseLayers, overlayLayers } from "./tiles.ts";
|
||||
|
||||
export {
|
||||
SURFACE_COLORS, DEFAULT_SURFACE_COLOR,
|
||||
HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR,
|
||||
SMOOTHNESS_COLORS, DEFAULT_SMOOTHNESS_COLOR,
|
||||
TRACKTYPE_COLORS, DEFAULT_TRACKTYPE_COLOR,
|
||||
CYCLEWAY_COLORS, DEFAULT_CYCLEWAY_COLOR,
|
||||
BIKEROUTE_COLORS, DEFAULT_BIKEROUTE_COLOR,
|
||||
elevationColor, routeGradeColor,
|
||||
maxspeedColor,
|
||||
} from "./colors/index.ts";
|
||||
|
||||
export type { PoiCategory } from "./poi.ts";
|
||||
export { poiCategories, getCategoriesForProfile, profileOverlayDefaults } from "./poi.ts";
|
||||
|
||||
export {
|
||||
Z_CURSOR, Z_GHOST_WAYPOINT, Z_WAYPOINT,
|
||||
Z_POI_MARKER, Z_WAYPOINT_HIGHLIGHTED, Z_HIGHLIGHT,
|
||||
} from "./z-index.ts";
|
||||
|
||||
export { SNAP_DISTANCE_METERS } from "./snap.ts";
|
||||
90
packages/map-core/src/poi.ts
Normal file
90
packages/map-core/src/poi.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
export interface PoiCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
query: string;
|
||||
profiles?: string[];
|
||||
}
|
||||
|
||||
export const poiCategories: PoiCategory[] = [
|
||||
{
|
||||
id: "drinking_water",
|
||||
name: "poi.drinkingWater",
|
||||
icon: "\u{1F4A7}",
|
||||
color: "#2563eb",
|
||||
query: 'nwr["amenity"="drinking_water"];nwr["amenity"="water_point"];',
|
||||
},
|
||||
{
|
||||
id: "shelter",
|
||||
name: "poi.shelter",
|
||||
icon: "\u{1F6D6}",
|
||||
color: "#8B6D3A",
|
||||
query: 'nwr["amenity"="shelter"];nwr["tourism"="wilderness_hut"];',
|
||||
profiles: ["trekking"],
|
||||
},
|
||||
{
|
||||
id: "camping",
|
||||
name: "poi.camping",
|
||||
icon: "\u26FA",
|
||||
color: "#059669",
|
||||
query: 'nwr["tourism"="camp_site"];nwr["tourism"="caravan_site"];',
|
||||
},
|
||||
{
|
||||
id: "food",
|
||||
name: "poi.food",
|
||||
icon: "\u{1F37D}\uFE0F",
|
||||
color: "#dc2626",
|
||||
query: 'nwr["amenity"="restaurant"];nwr["amenity"="cafe"];nwr["amenity"="fast_food"];nwr["amenity"="pub"];nwr["amenity"="biergarten"];',
|
||||
},
|
||||
{
|
||||
id: "groceries",
|
||||
name: "poi.groceries",
|
||||
icon: "\u{1F6D2}",
|
||||
color: "#f97316",
|
||||
query: 'nwr["shop"="supermarket"];nwr["shop"="convenience"];nwr["shop"="bakery"];',
|
||||
},
|
||||
{
|
||||
id: "bike_infra",
|
||||
name: "poi.bikeInfra",
|
||||
icon: "\u{1F527}",
|
||||
color: "#8b5cf6",
|
||||
query: 'nwr["amenity"="bicycle_parking"];nwr["amenity"="bicycle_repair_station"];nwr["amenity"="bicycle_rental"];',
|
||||
profiles: ["fastbike", "safety"],
|
||||
},
|
||||
{
|
||||
id: "accommodation",
|
||||
name: "poi.accommodation",
|
||||
icon: "\u{1F3E8}",
|
||||
color: "#0891b2",
|
||||
query: 'nwr["tourism"="hotel"];nwr["tourism"="hostel"];nwr["tourism"="guest_house"];',
|
||||
},
|
||||
{
|
||||
id: "viewpoints",
|
||||
name: "poi.viewpoints",
|
||||
icon: "\u{1F441}\uFE0F",
|
||||
color: "#9333ea",
|
||||
query: 'nwr["tourism"="viewpoint"];',
|
||||
profiles: ["trekking"],
|
||||
},
|
||||
{
|
||||
id: "toilets",
|
||||
name: "poi.toilets",
|
||||
icon: "\u{1F6BB}",
|
||||
color: "#6b7280",
|
||||
query: 'nwr["amenity"="toilets"];',
|
||||
},
|
||||
];
|
||||
|
||||
export function getCategoriesForProfile(profile: string): string[] {
|
||||
return poiCategories
|
||||
.filter((c) => c.profiles?.includes(profile))
|
||||
.map((c) => c.id);
|
||||
}
|
||||
|
||||
/** Profile -> tile overlay mapping */
|
||||
export const profileOverlayDefaults: Record<string, string[]> = {
|
||||
fastbike: ["waymarked-cycling"],
|
||||
safety: ["waymarked-cycling"],
|
||||
trekking: ["waymarked-hiking"],
|
||||
};
|
||||
1
packages/map-core/src/snap.ts
Normal file
1
packages/map-core/src/snap.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const SNAP_DISTANCE_METERS = 50;
|
||||
66
packages/map-core/src/tiles.ts
Normal file
66
packages/map-core/src/tiles.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
export interface TileLayerConfig {
|
||||
name: string;
|
||||
url: string;
|
||||
attribution: string;
|
||||
maxZoom?: number;
|
||||
}
|
||||
|
||||
export interface OverlayLayerConfig extends TileLayerConfig {
|
||||
id: string;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
export const overlayLayers: OverlayLayerConfig[] = [
|
||||
{
|
||||
id: "hillshading",
|
||||
name: "Hillshading",
|
||||
url: "https://tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png",
|
||||
attribution: "Hillshading: SRTM/Mapzen",
|
||||
maxZoom: 17,
|
||||
opacity: 0.5,
|
||||
},
|
||||
{
|
||||
id: "waymarked-cycling",
|
||||
name: "Cycling Routes",
|
||||
url: "https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
{
|
||||
id: "waymarked-hiking",
|
||||
name: "Hiking Routes",
|
||||
url: "https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
{
|
||||
id: "waymarked-mtb",
|
||||
name: "MTB Routes",
|
||||
url: "https://tile.waymarkedtrails.org/mtb/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
];
|
||||
|
||||
export const baseLayers: TileLayerConfig[] = [
|
||||
{
|
||||
name: "OpenStreetMap",
|
||||
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19,
|
||||
},
|
||||
{
|
||||
name: "OpenTopoMap",
|
||||
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
attribution:
|
||||
'© <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)',
|
||||
maxZoom: 17,
|
||||
},
|
||||
{
|
||||
name: "CyclOSM",
|
||||
url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png",
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases">CyclOSM</a>',
|
||||
maxZoom: 20,
|
||||
},
|
||||
];
|
||||
13
packages/map-core/src/z-index.ts
Normal file
13
packages/map-core/src/z-index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Leaflet marker z-index offsets for consistent layering.
|
||||
* Higher values render on top of lower values.
|
||||
*
|
||||
* Rendering order (bottom to top):
|
||||
* POI markers -> cursor markers -> ghost waypoint -> waypoint markers -> highlight dot
|
||||
*/
|
||||
export const Z_CURSOR = -1000;
|
||||
export const Z_GHOST_WAYPOINT = -100;
|
||||
export const Z_WAYPOINT = 1000;
|
||||
export const Z_POI_MARKER = 900;
|
||||
export const Z_WAYPOINT_HIGHLIGHTED = 1600;
|
||||
export const Z_HIGHLIGHT = 2000;
|
||||
8
packages/map-core/tsconfig.json
Normal file
8
packages/map-core/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
@ -7,6 +7,9 @@
|
|||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@trails-cool/map-core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18",
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ export { MapView } from "./MapView.tsx";
|
|||
export type { MapViewProps } from "./MapView.tsx";
|
||||
export { RouteLayer } from "./RouteLayer.tsx";
|
||||
export type { RouteLayerProps } from "./RouteLayer.tsx";
|
||||
export { baseLayers, overlayLayers } from "./layers.ts";
|
||||
export type { TileLayerConfig, OverlayLayerConfig } from "./layers.ts";
|
||||
export { baseLayers, overlayLayers } from "@trails-cool/map-core";
|
||||
export type { TileLayerConfig, OverlayLayerConfig } from "@trails-cool/map-core";
|
||||
|
|
|
|||
|
|
@ -1,66 +1,2 @@
|
|||
export interface TileLayerConfig {
|
||||
name: string;
|
||||
url: string;
|
||||
attribution: string;
|
||||
maxZoom?: number;
|
||||
}
|
||||
|
||||
export interface OverlayLayerConfig extends TileLayerConfig {
|
||||
id: string;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
export const overlayLayers: OverlayLayerConfig[] = [
|
||||
{
|
||||
id: "hillshading",
|
||||
name: "Hillshading",
|
||||
url: "https://tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png",
|
||||
attribution: "Hillshading: SRTM/Mapzen",
|
||||
maxZoom: 17,
|
||||
opacity: 0.5,
|
||||
},
|
||||
{
|
||||
id: "waymarked-cycling",
|
||||
name: "Cycling Routes",
|
||||
url: "https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
{
|
||||
id: "waymarked-hiking",
|
||||
name: "Hiking Routes",
|
||||
url: "https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
{
|
||||
id: "waymarked-mtb",
|
||||
name: "MTB Routes",
|
||||
url: "https://tile.waymarkedtrails.org/mtb/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://waymarkedtrails.org">Waymarked Trails</a> (CC-BY-SA)',
|
||||
maxZoom: 18,
|
||||
},
|
||||
];
|
||||
|
||||
export const baseLayers: TileLayerConfig[] = [
|
||||
{
|
||||
name: "OpenStreetMap",
|
||||
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19,
|
||||
},
|
||||
{
|
||||
name: "OpenTopoMap",
|
||||
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
attribution:
|
||||
'© <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)',
|
||||
maxZoom: 17,
|
||||
},
|
||||
{
|
||||
name: "CyclOSM",
|
||||
url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png",
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases">CyclOSM</a>',
|
||||
maxZoom: 20,
|
||||
},
|
||||
];
|
||||
export type { TileLayerConfig, OverlayLayerConfig } from "@trails-cool/map-core";
|
||||
export { baseLayers, overlayLayers } from "@trails-cool/map-core";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue