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:
Ullrich Schäfer 2026-04-12 22:28:14 +02:00
parent cd939ccf07
commit 60b94b5789
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
33 changed files with 339 additions and 257 deletions

View file

@ -1,6 +1,15 @@
import { useMemo } from "react";
import { Polyline } from "react-leaflet";
import type L from "leaflet";
import {
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 "@trails-cool/map-core";
export type ColorMode = "plain" | "elevation" | "surface" | "grade" | "highway" | "maxspeed" | "smoothness" | "tracktype" | "cycleway" | "bikeroute";
@ -16,135 +25,6 @@ interface ColoredRouteProps {
bikeroutes?: string[];
}
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",
};
const DEFAULT_SURFACE_COLOR = "#9ca3af";
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",
};
const DEFAULT_HIGHWAY_COLOR = "#9ca3af";
const SMOOTHNESS_COLORS: Record<string, string> = {
excellent: "#22c55e",
good: "#16a34a",
intermediate: "#eab308",
bad: "#f97316",
very_bad: "#ef4444",
horrible: "#991b1b",
very_horrible: "#7f1d1d",
impassable: "#450a0a",
};
const DEFAULT_SMOOTHNESS_COLOR = "#9ca3af";
const TRACKTYPE_COLORS: Record<string, string> = {
grade1: "#22c55e",
grade2: "#84cc16",
grade3: "#eab308",
grade4: "#f97316",
grade5: "#ef4444",
};
const DEFAULT_TRACKTYPE_COLOR = "#9ca3af";
const CYCLEWAY_COLORS: Record<string, string> = {
track: "#22c55e",
lane: "#84cc16",
shared_lane: "#eab308",
share_busway: "#f97316",
opposite_lane: "#818cf8",
separate: "#16a34a",
no: "#ef4444",
};
const DEFAULT_CYCLEWAY_COLOR = "#9ca3af";
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
};
const DEFAULT_BIKEROUTE_COLOR = "#d4d4d8";
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)`;
}
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
}
export function ColoredRoute({ coordinates, colorMode, surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes }: ColoredRouteProps) {
const segments = useMemo(() => {
if (colorMode === "plain" || coordinates.length < 2) {
@ -356,11 +236,3 @@ export function findSegmentForPoint(
return 0;
}
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,
};

View file

@ -10,8 +10,8 @@ import {
TRACKTYPE_COLORS, DEFAULT_TRACKTYPE_COLOR,
CYCLEWAY_COLORS, DEFAULT_CYCLEWAY_COLOR,
BIKEROUTE_COLORS, DEFAULT_BIKEROUTE_COLOR,
type ColorMode,
} from "~/components/ColoredRoute";
} from "@trails-cool/map-core";
import type { ColorMode } from "~/components/ColoredRoute";
function gradeColor(grade: number): string {
const absGrade = Math.abs(grade);

View file

@ -13,7 +13,7 @@ import { usePois } from "~/lib/use-pois";
import { useProfileDefaults } from "~/lib/use-profile-defaults";
import { useYjsPoiSync } from "~/lib/use-yjs-poi-sync";
import { snapToPoi } from "~/lib/poi-snap";
import { Z_CURSOR, Z_WAYPOINT, Z_WAYPOINT_HIGHLIGHTED, Z_HIGHLIGHT } from "~/lib/z-index";
import { Z_CURSOR, Z_WAYPOINT, Z_WAYPOINT_HIGHLIGHTED, Z_HIGHLIGHT } from "@trails-cool/map-core";
import { NoGoAreaLayer } from "./NoGoAreaLayer";
import { ColoredRoute, findSegmentForPoint, type ColorMode } from "./ColoredRoute";
import { RouteInteraction } from "./RouteInteraction";

View file

@ -2,9 +2,8 @@ import { useState, useEffect, useRef } from "react";
import L from "leaflet";
import { useTranslation } from "react-i18next";
import { useMap } from "react-leaflet";
import { poiCategories } from "~/lib/poi-categories";
import { poiCategories, Z_POI_MARKER } from "@trails-cool/map-core";
import type { PoiState } from "~/lib/use-pois";
import { Z_POI_MARKER } from "~/lib/z-index";
import "leaflet.markercluster/dist/MarkerCluster.css";
import "leaflet.markercluster/dist/MarkerCluster.Default.css";

View file

@ -1,7 +1,7 @@
import { useEffect, useRef, useCallback } from "react";
import { useMap } from "react-leaflet";
import L from "leaflet";
import { Z_GHOST_WAYPOINT } from "~/lib/z-index";
import { Z_GHOST_WAYPOINT } from "@trails-cool/map-core";
interface RouteInteractionProps {
coordinates: [number, number, number][]; // [lon, lat, ele]

View file

@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { buildQuery, parseResponse, deduplicateById, type Poi } from "./overpass.ts";
import { poiCategories } from "./poi-categories.ts";
import { poiCategories } from "@trails-cool/map-core";
describe("buildQuery", () => {
it("builds Overpass QL with bbox and category union", () => {

View file

@ -1,4 +1,4 @@
import type { PoiCategory } from "./poi-categories.ts";
import type { PoiCategory } from "@trails-cool/map-core";
const OVERPASS_ENDPOINTS = [
"https://overpass.kumi.systems/api/interpreter",

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { getCategoriesForProfile, profileOverlayDefaults, poiCategories } from "./poi-categories.ts";
import { getCategoriesForProfile, profileOverlayDefaults, poiCategories } from "@trails-cool/map-core";
describe("getCategoriesForProfile", () => {
it("returns bike infra for cycling profiles", () => {

View file

@ -1,6 +1,5 @@
import type { Poi } from "./overpass.ts";
const SNAP_DISTANCE_METERS = 50;
import { SNAP_DISTANCE_METERS } from "@trails-cool/map-core";
export interface SnapResult {
lat: number;

View file

@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { queryPois, OverpassRateLimitError, type Poi, type BBox } from "./overpass.ts";
import { getCached, setCached } from "./poi-cache.ts";
import { poiCategories } from "./poi-categories.ts";
import { poiCategories } from "@trails-cool/map-core";
const MIN_ZOOM = 10;
const DEBOUNCE_MS = 800;

View file

@ -1,7 +1,7 @@
import { useEffect, useRef } from "react";
import type { YjsState } from "./use-yjs.ts";
import type { PoiState } from "./use-pois.ts";
import { getCategoriesForProfile } from "./poi-categories.ts";
import { getCategoriesForProfile } from "@trails-cool/map-core";
/**
* Auto-enable relevant POI categories when the routing profile changes.

View file

@ -24,6 +24,7 @@
"@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*",
"@trails-cool/map": "workspace:*",
"@trails-cool/map-core": "workspace:*",
"@trails-cool/types": "workspace:*",
"@trails-cool/ui": "workspace:*",
"codemirror": "^6.0.2",

View file

@ -1,45 +1,45 @@
## 1. Package Setup
- [ ] 1.1 Create `packages/map-core/` with `package.json`, `tsconfig.json`, add to pnpm workspace and Turborepo pipeline
- [ ] 1.2 Create `src/index.ts` with re-exports for all modules
- [x] 1.1 Create `packages/map-core/` with `package.json`, `tsconfig.json`, add to pnpm workspace and Turborepo pipeline
- [x] 1.2 Create `src/index.ts` with re-exports for all modules
## 2. Tile Definitions
- [ ] 2.1 Move `baseLayers` and `TileLayerConfig` from `packages/map/src/layers.ts` to `packages/map-core/src/tiles.ts`
- [ ] 2.2 Move `overlayLayers` and `OverlayLayerConfig` to `packages/map-core/src/tiles.ts`
- [ ] 2.3 Update `packages/map/src/layers.ts` to re-export from `@trails-cool/map-core`
- [ ] 2.4 Update `packages/map/src/index.ts` exports
- [x] 2.1 Move `baseLayers` and `TileLayerConfig` from `packages/map/src/layers.ts` to `packages/map-core/src/tiles.ts`
- [x] 2.2 Move `overlayLayers` and `OverlayLayerConfig` to `packages/map-core/src/tiles.ts`
- [x] 2.3 Update `packages/map/src/layers.ts` to re-export from `@trails-cool/map-core`
- [x] 2.4 Update `packages/map/src/index.ts` exports
## 3. Color Palettes
- [ ] 3.1 Extract `SURFACE_COLORS`, `DEFAULT_SURFACE_COLOR` from `ColoredRoute.tsx` to `map-core/src/colors/surface.ts`
- [ ] 3.2 Extract `HIGHWAY_COLORS`, `DEFAULT_HIGHWAY_COLOR` to `map-core/src/colors/highway.ts`
- [ ] 3.3 Extract `SMOOTHNESS_COLORS`, `DEFAULT_SMOOTHNESS_COLOR` to `map-core/src/colors/smoothness.ts`
- [ ] 3.4 Extract `TRACKTYPE_COLORS`, `DEFAULT_TRACKTYPE_COLOR` to `map-core/src/colors/tracktype.ts`
- [ ] 3.5 Extract `CYCLEWAY_COLORS`, `DEFAULT_CYCLEWAY_COLOR` to `map-core/src/colors/cycleway.ts`
- [ ] 3.6 Extract `BIKEROUTE_COLORS`, `DEFAULT_BIKEROUTE_COLOR` to `map-core/src/colors/bikeroute.ts`
- [ ] 3.7 Extract `elevationColor()`, `routeGradeColor()` to `map-core/src/colors/elevation.ts`
- [ ] 3.8 Extract `maxspeedColor()` to `map-core/src/colors/maxspeed.ts`
- [ ] 3.9 Create `map-core/src/colors/index.ts` re-exporting all color maps and functions
- [ ] 3.10 Update `ColoredRoute.tsx` to import from `@trails-cool/map-core`
- [ ] 3.11 Update `ElevationChart.tsx` to import from `@trails-cool/map-core`
- [x] 3.1 Extract `SURFACE_COLORS`, `DEFAULT_SURFACE_COLOR` from `ColoredRoute.tsx` to `map-core/src/colors/surface.ts`
- [x] 3.2 Extract `HIGHWAY_COLORS`, `DEFAULT_HIGHWAY_COLOR` to `map-core/src/colors/highway.ts`
- [x] 3.3 Extract `SMOOTHNESS_COLORS`, `DEFAULT_SMOOTHNESS_COLOR` to `map-core/src/colors/smoothness.ts`
- [x] 3.4 Extract `TRACKTYPE_COLORS`, `DEFAULT_TRACKTYPE_COLOR` to `map-core/src/colors/tracktype.ts`
- [x] 3.5 Extract `CYCLEWAY_COLORS`, `DEFAULT_CYCLEWAY_COLOR` to `map-core/src/colors/cycleway.ts`
- [x] 3.6 Extract `BIKEROUTE_COLORS`, `DEFAULT_BIKEROUTE_COLOR` to `map-core/src/colors/bikeroute.ts`
- [x] 3.7 Extract `elevationColor()`, `routeGradeColor()` to `map-core/src/colors/elevation.ts`
- [x] 3.8 Extract `maxspeedColor()` to `map-core/src/colors/maxspeed.ts`
- [x] 3.9 Create `map-core/src/colors/index.ts` re-exporting all color maps and functions
- [x] 3.10 Update `ColoredRoute.tsx` to import from `@trails-cool/map-core`
- [x] 3.11 Update `ElevationChart.tsx` to import from `@trails-cool/map-core`
## 4. POI Categories
- [ ] 4.1 Move `PoiCategory` type and `poiCategories` array from `poi-categories.ts` to `map-core/src/poi.ts`
- [ ] 4.2 Move `getCategoriesForProfile()` and `profileOverlayDefaults` to `map-core/src/poi.ts`
- [ ] 4.3 Update Planner imports: `PoiPanel.tsx`, `use-pois.ts`, `use-profile-defaults.ts`, `poi-categories.test.ts`
- [ ] 4.4 Move POI snap distance constant to `map-core/src/snap.ts`
- [x] 4.1 Move `PoiCategory` type and `poiCategories` array from `poi-categories.ts` to `map-core/src/poi.ts`
- [x] 4.2 Move `getCategoriesForProfile()` and `profileOverlayDefaults` to `map-core/src/poi.ts`
- [x] 4.3 Update Planner imports: `PoiPanel.tsx`, `use-pois.ts`, `use-profile-defaults.ts`, `poi-categories.test.ts`
- [x] 4.4 Move POI snap distance constant to `map-core/src/snap.ts`
## 5. Z-Index Constants
- [ ] 5.1 Move all z-index constants from `z-index.ts` to `map-core/src/z-index.ts`
- [ ] 5.2 Update Planner imports: `PlannerMap.tsx`, `PoiPanel.tsx`, `RouteInteraction.tsx`
- [x] 5.1 Move all z-index constants from `z-index.ts` to `map-core/src/z-index.ts`
- [x] 5.2 Update Planner imports: `PlannerMap.tsx`, `PoiPanel.tsx`, `RouteInteraction.tsx`
## 6. Verification
- [ ] 6.1 Run `pnpm typecheck` — all packages compile
- [ ] 6.2 Run `pnpm lint` — no import errors
- [ ] 6.3 Run `pnpm test` — all existing tests pass (update import paths in test files)
- [ ] 6.4 Run `pnpm test:e2e` — no regressions
- [ ] 6.5 Verify `map-core` has zero dependencies in its `package.json`
- [x] 6.1 Run `pnpm typecheck` — all packages compile
- [x] 6.2 Run `pnpm lint` — no import errors
- [x] 6.3 Run `pnpm test` — all existing tests pass (update import paths in test files)
- [x] 6.4 Run `pnpm test:e2e` — no regressions
- [x] 6.5 Verify `map-core` has zero dependencies in its `package.json`

View 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"
}

View 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";

View 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";

View 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)`;
}

View 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";

View 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";

View 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
}

View 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";

View 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";

View 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";

View 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";

View file

@ -11,14 +11,14 @@ export const poiCategories: PoiCategory[] = [
{
id: "drinking_water",
name: "poi.drinkingWater",
icon: "💧",
icon: "\u{1F4A7}",
color: "#2563eb",
query: 'nwr["amenity"="drinking_water"];nwr["amenity"="water_point"];',
},
{
id: "shelter",
name: "poi.shelter",
icon: "🛖",
icon: "\u{1F6D6}",
color: "#8B6D3A",
query: 'nwr["amenity"="shelter"];nwr["tourism"="wilderness_hut"];',
profiles: ["trekking"],
@ -26,28 +26,28 @@ export const poiCategories: PoiCategory[] = [
{
id: "camping",
name: "poi.camping",
icon: "",
icon: "\u26FA",
color: "#059669",
query: 'nwr["tourism"="camp_site"];nwr["tourism"="caravan_site"];',
},
{
id: "food",
name: "poi.food",
icon: "🍽️",
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: "🛒",
icon: "\u{1F6D2}",
color: "#f97316",
query: 'nwr["shop"="supermarket"];nwr["shop"="convenience"];nwr["shop"="bakery"];',
},
{
id: "bike_infra",
name: "poi.bikeInfra",
icon: "🔧",
icon: "\u{1F527}",
color: "#8b5cf6",
query: 'nwr["amenity"="bicycle_parking"];nwr["amenity"="bicycle_repair_station"];nwr["amenity"="bicycle_rental"];',
profiles: ["fastbike", "safety"],
@ -55,14 +55,14 @@ export const poiCategories: PoiCategory[] = [
{
id: "accommodation",
name: "poi.accommodation",
icon: "🏨",
icon: "\u{1F3E8}",
color: "#0891b2",
query: 'nwr["tourism"="hotel"];nwr["tourism"="hostel"];nwr["tourism"="guest_house"];',
},
{
id: "viewpoints",
name: "poi.viewpoints",
icon: "👁️",
icon: "\u{1F441}\uFE0F",
color: "#9333ea",
query: 'nwr["tourism"="viewpoint"];',
profiles: ["trekking"],
@ -70,7 +70,7 @@ export const poiCategories: PoiCategory[] = [
{
id: "toilets",
name: "poi.toilets",
icon: "🚻",
icon: "\u{1F6BB}",
color: "#6b7280",
query: 'nwr["amenity"="toilets"];',
},
@ -82,7 +82,7 @@ export function getCategoriesForProfile(profile: string): string[] {
.map((c) => c.id);
}
/** Profile tile overlay mapping */
/** Profile -> tile overlay mapping */
export const profileOverlayDefaults: Record<string, string[]> = {
fastbike: ["waymarked-cycling"],
safety: ["waymarked-cycling"],

View file

@ -0,0 +1 @@
export const SNAP_DISTANCE_METERS = 50;

View 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: '&copy; <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: '&copy; <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: '&copy; <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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19,
},
{
name: "OpenTopoMap",
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
attribution:
'&copy; <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:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases">CyclOSM</a>',
maxZoom: 20,
},
];

View file

@ -3,7 +3,7 @@
* Higher values render on top of lower values.
*
* Rendering order (bottom to top):
* POI markers cursor markers ghost waypoint waypoint markers highlight dot
* POI markers -> cursor markers -> ghost waypoint -> waypoint markers -> highlight dot
*/
export const Z_CURSOR = -1000;
export const Z_GHOST_WAYPOINT = -100;

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -7,6 +7,9 @@
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"@trails-cool/map-core": "workspace:*"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18",

View file

@ -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";

View file

@ -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: '&copy; <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: '&copy; <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: '&copy; <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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom: 19,
},
{
name: "OpenTopoMap",
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
attribution:
'&copy; <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:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <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";

35
pnpm-lock.yaml generated
View file

@ -324,6 +324,9 @@ importers:
'@trails-cool/map':
specifier: workspace:*
version: link:../../packages/map
'@trails-cool/map-core':
specifier: workspace:*
version: link:../../packages/map-core
'@trails-cool/types':
specifier: workspace:*
version: link:../../packages/types
@ -436,6 +439,9 @@ importers:
packages/map:
dependencies:
'@trails-cool/map-core':
specifier: workspace:*
version: link:../map-core
leaflet:
specifier: '>=1.9'
version: 1.9.4
@ -449,6 +455,8 @@ importers:
specifier: '>=5'
version: 5.0.0(leaflet@1.9.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
packages/map-core: {}
packages/types: {}
packages/ui: {}
@ -1593,36 +1601,42 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.15':
resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15':
resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15':
resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.15':
resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-rc.15':
resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-rc.15':
resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==}
@ -1684,66 +1698,79 @@ packages:
resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.0':
resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.0':
resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.0':
resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.0':
resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.0':
resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.0':
resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.0':
resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.0':
resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.0':
resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.0':
resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.0':
resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.0':
resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.0':
resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
@ -1974,24 +2001,28 @@ packages:
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
@ -3110,24 +3141,28 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}