Use real H7 centroids from API — remove spiral approximation
This commit is contained in:
@@ -2,11 +2,18 @@ import { useState, useEffect } from 'react'
|
|||||||
import { WAYPOINTS, ROUTES } from '../constants.js'
|
import { WAYPOINTS, ROUTES } from '../constants.js'
|
||||||
import { fetchMapCells } from '../api.js'
|
import { fetchMapCells } from '../api.js'
|
||||||
|
|
||||||
// Bounding box — Mediterranean basin
|
|
||||||
const BBOX = { minLng: 5, maxLng: 38, minLat: 28, maxLat: 48 }
|
const BBOX = { minLng: 5, maxLng: 38, minLat: 28, maxLat: 48 }
|
||||||
const W = 800
|
const W = 800
|
||||||
const H = 460
|
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
|
// H3 res-5 cell centre coordinates — hardcoded, h3-js not in browser bundle
|
||||||
const CENTRES = {
|
const CENTRES = {
|
||||||
ostia: { lat: 41.73, lng: 12.23 },
|
ostia: { lat: 41.73, lng: 12.23 },
|
||||||
@@ -16,21 +23,13 @@ const CENTRES = {
|
|||||||
alexandria: { lat: 31.20, lng: 29.92 },
|
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 WAYPOINT_IDS = ['ostia', 'capua', 'brundisium', 'carthago', 'alexandria']
|
||||||
const ROUTE_ORDER = ['olive', 'wine', 'grain', 'linen']
|
const ROUTE_ORDER = ['olive', 'wine', 'grain', 'linen']
|
||||||
|
const EPOCH = 'roman_14bce'
|
||||||
|
|
||||||
// Active epoch — Roman period for OTIVM-II
|
// H7 cell approximate radius in SVG pixels — H7 inradius ~10km
|
||||||
// sl_offset_cm = -10 (effectively zero). Coastline matches modern sea level.
|
// At our projection (33° = 800px wide), 1° lng ≈ 24px, 10km ≈ 9px
|
||||||
const EPOCH = 'roman_14bce'
|
const H7_RADIUS = 9
|
||||||
|
|
||||||
function project(lat, lng) {
|
function project(lat, lng) {
|
||||||
const x = ((lng - BBOX.minLng) / (BBOX.maxLng - BBOX.minLng)) * W
|
const x = ((lng - BBOX.minLng) / (BBOX.maxLng - BBOX.minLng)) * W
|
||||||
@@ -38,40 +37,15 @@ function project(lat, lng) {
|
|||||||
return { x, y }
|
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 }) {
|
export default function Map({ state }) {
|
||||||
const chapter = state?.chapter ?? 1
|
const chapter = state?.chapter ?? 1
|
||||||
const [cellData, setCellData] = useState({})
|
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(() => {
|
useEffect(() => {
|
||||||
WAYPOINT_IDS.forEach((id) => {
|
WAYPOINT_IDS.forEach((id) => {
|
||||||
const wp = WAYPOINTS[id]
|
const wp = WAYPOINTS[id]
|
||||||
if (chapter < wp.chapter) return // not yet revealed
|
if (chapter < wp.chapter) return
|
||||||
if (cellData[id]) return // already loaded
|
if (cellData[id]) return
|
||||||
fetchMapCells(H5_IDS[id], EPOCH).then(data => {
|
fetchMapCells(H5_IDS[id], EPOCH).then(data => {
|
||||||
if (data?.cells) {
|
if (data?.cells) {
|
||||||
setCellData(prev => ({ ...prev, [id]: 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' }}
|
style={{ width: '100%', maxWidth: W, display: 'block', margin: '0 auto' }}
|
||||||
aria-label="Mediterranean trade map"
|
aria-label="Mediterranean trade map"
|
||||||
>
|
>
|
||||||
{/* Sea — permanent darkness beyond fog of war */}
|
{/* Sea — permanent darkness */}
|
||||||
<rect width={W} height={H} fill="#0d1a26" />
|
<rect width={W} height={H} fill="#0d1a26" />
|
||||||
|
|
||||||
{/* Fog of war — land H7 cells, revealed by chapter */}
|
{/* Fog of war — land H7 cells at real geographic positions */}
|
||||||
{WAYPOINT_IDS.map((id) => {
|
{WAYPOINT_IDS.map((id) => {
|
||||||
const wp = WAYPOINTS[id]
|
const wp = WAYPOINTS[id]
|
||||||
if (chapter < wp.chapter) return null
|
if (chapter < wp.chapter) return null
|
||||||
const cells = cellData[id]
|
const cells = cellData[id]
|
||||||
if (!cells) return null
|
if (!cells) return null
|
||||||
return cells.map((cell, cellIdx) => {
|
return cells.map((cell) => {
|
||||||
if (!cell.is_land) return null
|
if (!cell.is_land) return null
|
||||||
const pos = h7ApproxPos(id, cellIdx)
|
// lat/lon now come directly from the API — real H7 centroids
|
||||||
if (!pos) return null
|
const { x, y } = project(cell.lat, cell.lon)
|
||||||
return (
|
return (
|
||||||
<circle
|
<circle
|
||||||
key={`${id}-${cell.h7}`}
|
key={`${id}-${cell.h7}`}
|
||||||
cx={pos.x}
|
cx={x}
|
||||||
cy={pos.y}
|
cy={y}
|
||||||
r={H7_RADIUS}
|
r={H7_RADIUS}
|
||||||
fill="#2d3b2a"
|
fill="#2d3b2a"
|
||||||
stroke="#3a4a30"
|
stroke="#3a4a30"
|
||||||
@@ -126,7 +100,6 @@ export default function Map({ state }) {
|
|||||||
{ROUTE_ORDER.map((routeId) => {
|
{ROUTE_ORDER.map((routeId) => {
|
||||||
const route = ROUTES.find((r) => r.id === routeId)
|
const route = ROUTES.find((r) => r.id === routeId)
|
||||||
if (!route) return null
|
if (!route) return null
|
||||||
// Route visible when both endpoints are revealed
|
|
||||||
const fromWp = WAYPOINTS[route.from]
|
const fromWp = WAYPOINTS[route.from]
|
||||||
const toWp = WAYPOINTS[route.to]
|
const toWp = WAYPOINTS[route.to]
|
||||||
if (chapter < fromWp.chapter || chapter < toWp.chapter) return null
|
if (chapter < fromWp.chapter || chapter < toWp.chapter) return null
|
||||||
|
|||||||
Reference in New Issue
Block a user