1// Test-only adapter for the committed v3 JSON corpus. Production code has no2// legacy presentation reader; these old extractions remain useful as expected3// pictures while the corpus is rewritten as semantic fixtures.45import type { LocationTable } from "../data/atlasloc.ts";6import type { Collection, Geometry as LegacyGeometry, WorldPayload, TileGrid } from "../data/payload.ts";7import type { OpenWorld } from "../data/plane.ts";8import type {9 Feature, Geometry, GeometryPart, Property, RasterCoverage, RasterPyramid,10} from "../data/semantic.ts";11import { project } from "../world/model.ts";1213export function legacyOpenWorld(14 slug: string,15 payload: WorldPayload,16 grid: TileGrid,17 locations: LocationTable | null,18): OpenWorld {19 const sets = payload.collections.map((collection, owner) => ({20 id: `legacy/set/${collection.id}`,21 title: collection.title,22 semanticType: `geometry.${collection.kind}`,23 properties: [],24 claims: properties(collection.attrs),25 features: features(collection, owner, grid, locations),26 }));27 const styles = payload.collections.map((collection) => ({28 id: `legacy/style/${collection.id}`, symbol: "", icon: collection.icon ?? "",29 iconAsset: "", iconPicture: Boolean(collection.iconPicture),30 renderAs: collection.renderAs ?? collection.attrs?.["atlas.render.as"] ?? "",31 stroke: collection.kind === "path" ? collection.color ?? collection.iconColor ?? "" : "",32 fill: collection.color ?? collection.iconColor ?? "",33 }));34 const layers = payload.collections.map((collection, order) => ({35 id: String(collection.id), presentation: "legacy/presentation",36 featureSet: `legacy/set/${collection.id}`, style: `legacy/style/${collection.id}`,37 group: collection.group ?? "", labelPolicy: collection.labelPolicy ?? collection.attrs?.["atlas.label.policy"] ?? "",38 visible: collection.visible, order, minZoom: 0, maxZoom: 32,39 }));40 const rasters = payload.lenses.map((lens): RasterPyramid => ({41 id: lens.id ?? lens.tiles, name: lens.name, codec: "image/fixture", tileSize: grid.tileSize,42 minZoom: lens.minZoom, maxZoom: lens.maxZoom, fullZoom: lens.fullZoom,43 sourceZoom: lens.sourceZoom, template: lens.template ?? `tiles/${lens.tiles}/{z}/{x}/{y}.{format}`,44 formats: lens.formats, bounds: lens.bounds ?? null, surface: lens.surface ?? null,45 interpolate: lens.interpolate, background: lens.background ?? "", shard: lens.shard ?? 0,46 coverage: Object.entries(lens.coverage ?? {}).map(([zoom, level]): RasterCoverage => ({47 zoom: Number(zoom), x: level.x, y: level.y, w: level.w, h: level.h, bits: unbase64(level.bits),48 })),49 }));50 return {51 volume: { id: "legacy", title: "Legacy fixture", worlds: [], assets: new Map() },52 world: {53 id: slug, title: slug,54 coordinateSpace: {55 id: `${slug}/space`, kind: "projected", unit: "pixel", definition: "atlas:tile-plane",56 extent: [0, 0, grid.size, grid.size], sourceZoom: grid.sourceZoom,57 originX: grid.originX ?? grid.firstTile, originY: grid.originY ?? grid.firstTile,58 tileSize: grid.tileSize, size: grid.size,59 },60 claims: properties(payload.attrs), featureSets: sets, rasterPyramids: rasters,61 presentation: {62 id: "legacy/presentation", title: slug, styles, layers,63 legend: layers.map((layer, order) => ({ layer: layer.id, label: sets[order]?.title ?? "", order })),64 },65 },66 };67}6869function features(70 collection: Collection,71 owner: number,72 grid: TileGrid,73 locations: LocationTable | null,74): Feature[] {75 const out: Feature[] = [];76 if (collection.kind === "point" && locations) {77 for (let row = 0; row < locations.count; row++) {78 if (locations.owner[row] !== owner) continue;79 const shown = project(grid, locations.lat[row] ?? 0, locations.lng[row] ?? 0);80 const id = String(locations.id[row] ?? 0);81 const position = [shown[0], -shown[1]] as const;82 out.push(feature(id, locations.title(row), {83 kind: 1, parts: [{ rings: [[position]] }],84 }, locations.shard[row] ?? 0, [], locations.member[row] ? [{ predicate: "within", target: String(locations.member[row]) }] : []));85 }86 }87 for (const shape of collection.features ?? []) {88 const relationships = shape.parent === undefined ? [] : [{ predicate: "within", target: String(shape.parent) }];89 const held = feature(String(shape.id), shape.title, legacyGeometry(shape.geometry, collection.kind, grid), shape.shard ?? 0,90 properties(shape.attrs), relationships);91 (held as { subtitle: string }).subtitle = shape.subtitle ?? "";92 if (shape.center) {93 const shown = project(grid, shape.center.lat, shape.center.lng);94 (held as { center: readonly [number, number] }).center = [shown[0], -shown[1]];95 }96 out.push(held);97 }98 return out;99}100101function feature(102 id: string,103 title: string,104 geometry: Geometry,105 shard: number,106 attrs: readonly Property[],107 relationships: readonly { readonly predicate: string; readonly target: string }[],108): Feature {109 return {110 id, title, subtitle: "", description: "", center: null, shard, geometry,111 properties: attrs, relationships, provenance: [],112 };113}114115function legacyGeometry(items: readonly LegacyGeometry[], kind: Collection["kind"], grid: TileGrid): Geometry {116 const parts: GeometryPart[] = [];117 for (const item of items) {118 switch (item.type) {119 case "Polygon": parts.push({ rings: rings(item.coordinates, grid) }); break;120 case "MultiPolygon": for (const polygon of item.coordinates as unknown[]) parts.push({ rings: rings(polygon, grid) }); break;121 case "LineString": parts.push({ rings: [line(item.coordinates, grid)] }); break;122 case "MultiLineString": for (const raw of item.coordinates as unknown[]) parts.push({ rings: [line(raw, grid)] }); break;123 }124 }125 return { kind: kind === "path" ? 2 : 3, parts };126}127128function rings(raw: unknown, grid: TileGrid): readonly (readonly (readonly [number, number])[])[] {129 return (raw as unknown[]).map((held) => line(held, grid));130}131132function line(raw: unknown, grid: TileGrid): readonly (readonly [number, number])[] {133 return (raw as [number, number][]).map(([lng, lat]) => {134 const shown = project(grid, lat, lng);135 return [shown[0], -shown[1]] as const;136 });137}138139function properties(attrs?: Readonly<Record<string, string>>): Property[] {140 return Object.entries(attrs ?? {}).map(([name, value]) => ({ id: `legacy:${name}`, name, kind: 4, value }));141}142143function unbase64(value: string): Uint8Array {144 const binary = atob(value);145 return Uint8Array.from(binary, (character) => character.charCodeAt(0));146}