Add per-hex metadata system for scale-invariant classification
Each hex now gets a meta/<q>_<r>.json with stable ID, pixel center coordinates, pixel bounds, labels, notes, and classification status. The pixelCenter acts as a scale-independent anchor: when switching from 10 Meilen/Hex to 5 Meilen/Hex, pixelToAxial(meta.pixelCenter, newSize) maps coarse hexes to fine hexes without re-running classification. Adds: - pipeline/build-hexmeta.ts: creates/updates metadata + exports data/hexmeta-<map-id>.jsonl (committed, survives git clones) - pipeline/auto-classify-ocean.ts: pixel-based ocean auto-detection - pipeline/create-map.ts: one-off DB map entry creation - extract-submaps.ts: writes meta/<q>_<r>.json during extraction - data/hexmeta-1.jsonl: 8844 hex metadata entries for Aventurien map 1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import { initDb, getDb } from '../server/db.js';
|
||||
import { axialToPixel, hexVertices } from '../core/coords.js';
|
||||
import { gridBoundsForImage } from '../core/hex-grid.js';
|
||||
import { HexEdge, EDGE_DIRECTIONS, ALL_EDGES, type AxialCoord, type PixelCoord } from '../core/types.js';
|
||||
import type { HexMeta } from './build-hexmeta.js';
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
@@ -183,7 +184,7 @@ async function main() {
|
||||
const outDir = join(ROOT, 'pipeline', 'submaps', String(mapId));
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
// Write manifest
|
||||
// Write manifest (with stable IDs, starting at 1)
|
||||
const manifest = {
|
||||
mapId,
|
||||
imageWidth: image_width,
|
||||
@@ -192,17 +193,47 @@ async function main() {
|
||||
originX: origin_x,
|
||||
originY: origin_y,
|
||||
meilenPerHex: hexesPerMeile,
|
||||
hexes: sorted.map(c => ({ q: c.q, r: c.r })),
|
||||
hexes: sorted.map((c, i) => ({ id: i + 1, q: c.q, r: c.r })),
|
||||
};
|
||||
writeFileSync(join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
||||
console.log(`Manifest written: ${sorted.length} hexes`);
|
||||
|
||||
// Create meta directory for per-hex attribute files
|
||||
const metaDir = join(outDir, 'meta');
|
||||
mkdirSync(metaDir, { recursive: true });
|
||||
|
||||
let done = 0;
|
||||
for (const coord of sorted) {
|
||||
for (let i = 0; i < sorted.length; i++) {
|
||||
const coord = sorted[i];
|
||||
const filename = `${coord.q}_${coord.r}.png`;
|
||||
const outPath = join(outDir, filename);
|
||||
const metaPath = join(metaDir, `${coord.q}_${coord.r}.json`);
|
||||
|
||||
// Skip if already extracted (resumable)
|
||||
// Create meta file if not present (never overwrite existing — preserves labels/notes)
|
||||
if (!existsSync(metaPath)) {
|
||||
const px = axialToPixel(coord, hex_size, origin);
|
||||
const r = Math.ceil(hex_size * CROP_RADIUS_FACTOR);
|
||||
const meta: HexMeta = {
|
||||
id: i + 1,
|
||||
q: coord.q, r: coord.r,
|
||||
mapId,
|
||||
hexSizePx: hex_size,
|
||||
meilenPerHex: hexesPerMeile,
|
||||
pixelCenter: { x: Math.round(px.x), y: Math.round(px.y) },
|
||||
pixelBounds: {
|
||||
left: Math.max(0, Math.round(px.x - r)),
|
||||
top: Math.max(0, Math.round(px.y - r)),
|
||||
right: Math.min(image_width, Math.round(px.x + r)),
|
||||
bottom: Math.min(image_height, Math.round(px.y + r)),
|
||||
},
|
||||
labels: [],
|
||||
notes: '',
|
||||
classification: null,
|
||||
};
|
||||
writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
||||
}
|
||||
|
||||
// Skip PNG if already extracted (resumable)
|
||||
if (existsSync(outPath)) {
|
||||
done++;
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user