diff --git a/src/screens/Map.jsx b/src/screens/Map.jsx index 5057551..ec53741 100644 --- a/src/screens/Map.jsx +++ b/src/screens/Map.jsx @@ -2,11 +2,18 @@ import { useState, useEffect } from 'react' import { WAYPOINTS, ROUTES } from '../constants.js' import { fetchMapCells } from '../api.js' -// Bounding box — Mediterranean basin const BBOX = { minLng: 5, maxLng: 38, minLat: 28, maxLat: 48 } const W = 800 const H = 460 +const H5_IDS = { + ostia: '851e805bfffffff', + capua: '851e8333fffffff', + brundisium: '851e8ba3fffffff', + carthago: '85386e23fffffff', + alexandria: '853f5ba7fffffff', +} + // H3 res-5 cell centre coordinates — hardcoded, h3-js not in browser bundle const CENTRES = { ostia: { lat: 41.73, lng: 12.23 }, @@ -16,21 +23,13 @@ const CENTRES = { alexandria: { lat: 31.20, lng: 29.92 }, } -// H3 res-5 hex string IDs — used for API calls -const H5_IDS = { - ostia: '851e805bfffffff', - capua: '851e8333fffffff', - brundisium: '851e8ba3fffffff', - carthago: '85386e23fffffff', - alexandria: '853f5ba7fffffff', -} - const WAYPOINT_IDS = ['ostia', 'capua', 'brundisium', 'carthago', 'alexandria'] const ROUTE_ORDER = ['olive', 'wine', 'grain', 'linen'] +const EPOCH = 'roman_14bce' -// Active epoch — Roman period for OTIVM-II -// sl_offset_cm = -10 (effectively zero). Coastline matches modern sea level. -const EPOCH = 'roman_14bce' +// H7 cell approximate radius in SVG pixels — H7 inradius ~10km +// At our projection (33° = 800px wide), 1° lng ≈ 24px, 10km ≈ 9px +const H7_RADIUS = 9 function project(lat, lng) { const x = ((lng - BBOX.minLng) / (BBOX.maxLng - BBOX.minLng)) * W @@ -38,40 +37,15 @@ function project(lat, lng) { return { x, y } } -// Approximate H7 cell position within its H5 parent using a sunflower spiral. -// H7 cells are rendered as circles — exact hex geometry requires h3-js (server-side only). -// The spiral distributes 49 cells across the H5 area. Visual approximation only. -// Will be replaced when the API returns per-cell centroids. -function h7ApproxPos(h5Id, cellIndex) { - const cx = CENTRES[h5Id] - if (!cx) return null - const { x: cx_px, y: cy_px } = project(cx.lat, cx.lng) - // Golden angle spiral — distributes points evenly across H5 area (~90km) - // At our projection scale, H5 spans ~120px. Space cells ~16px apart. - const SPACING = 14 - const angle = cellIndex * 2.399963 // golden angle radians - const radius = SPACING * Math.sqrt(cellIndex) - return { - x: cx_px + Math.cos(angle) * radius, - y: cy_px + Math.sin(angle) * radius, - } -} - -const H7_RADIUS = 8 - export default function Map({ state }) { const chapter = state?.chapter ?? 1 const [cellData, setCellData] = useState({}) - // Fetch H7 cells for all revealed waypoints. - // A waypoint is revealed when chapter >= waypoint.chapter. - // Ostia (chapter 1) is revealed from the start. - // Cells are fetched once per waypoint and cached in state. useEffect(() => { WAYPOINT_IDS.forEach((id) => { const wp = WAYPOINTS[id] - if (chapter < wp.chapter) return // not yet revealed - if (cellData[id]) return // already loaded + if (chapter < wp.chapter) return + if (cellData[id]) return fetchMapCells(H5_IDS[id], EPOCH).then(data => { if (data?.cells) { setCellData(prev => ({ ...prev, [id]: data.cells })) @@ -94,24 +68,24 @@ export default function Map({ state }) { style={{ width: '100%', maxWidth: W, display: 'block', margin: '0 auto' }} aria-label="Mediterranean trade map" > - {/* Sea — permanent darkness beyond fog of war */} + {/* Sea — permanent darkness */} - {/* Fog of war — land H7 cells, revealed by chapter */} + {/* Fog of war — land H7 cells at real geographic positions */} {WAYPOINT_IDS.map((id) => { const wp = WAYPOINTS[id] if (chapter < wp.chapter) return null const cells = cellData[id] if (!cells) return null - return cells.map((cell, cellIdx) => { + return cells.map((cell) => { if (!cell.is_land) return null - const pos = h7ApproxPos(id, cellIdx) - if (!pos) return null + // lat/lon now come directly from the API — real H7 centroids + const { x, y } = project(cell.lat, cell.lon) return ( { const route = ROUTES.find((r) => r.id === routeId) if (!route) return null - // Route visible when both endpoints are revealed const fromWp = WAYPOINTS[route.from] const toWp = WAYPOINTS[route.to] if (chapter < fromWp.chapter || chapter < toWp.chapter) return null