SOURCE / PINNED RELEASE
Made of little things.
Atlas map viewer
- Release
- 1d2807e80b44…
- Author-recorded commit
- 43810ed2b368…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-4757dbabc86
Archive hash verified: 8db6876d96c460ac…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import type { Geometry } from "geojson";
import booleanIntersects from "@turf/boolean-intersects";
import type { AtlasReader } from "../../format/src/reader";
import {
boxValid,
tagTerm,
PACK_BYTES,
CHUNK_BYTES,
objectId,
overlap,
splitBounds,
validKey,
type Box,
type ObjectCatalog,
type ObjectRecord,
type Page,
} from "./model";
export * from "./model";
export const hasObjects = (r: AtlasReader) =>
r.index.manifest.capabilities.includes("source-objects");
function checkedPage(p: Page) {
if (
!p ||
!validKey(p.key) ||
!/^objects\/(records|spatial|lookup|ids|tag-values|tag-index|tag-pages)\//.test(
p.key,
) ||
typeof p.unlocated !== "boolean" ||
!Array.isArray(p.bounds) ||
!p.bounds.every(boxValid) ||
!Number.isSafeInteger(p.count) ||
p.count < 0 ||
!Array.isArray(p.keys) ||
!p.keys.every((x) => typeof x === "string") ||
!Array.isArray(p.facets) ||
!p.facets.every((x) => typeof x === "string")
)
throw new Error("Invalid object index page");
return p;
}
export async function objectCatalog(r: AtlasReader): Promise<ObjectCatalog> {
if (!hasObjects(r))
throw new Error(
"This archive has no general source-object data; its display layers cannot recover omitted objects.",
);
const c = JSON.parse(
new TextDecoder().decode(await r.block("objects/index")),
);
if (
!["source-objects-v1", "source-objects-v2"].includes(c?.schema) ||
!Number.isSafeInteger(c.count) ||
c.count < 0 ||
c.report?.schema !== "retention-v1"
)
throw new Error("Invalid object catalog");
checkedPage(c.spatial);
checkedPage(c.lookup);
if (c.schema === "source-objects-v2") checkedPage(c.tags);
if (c.spatial.count !== c.count || c.lookup.count !== c.count)
throw new Error("Object catalog count mismatch");
return c;
}
async function page(r: AtlasReader, p: Page, signal?: AbortSignal) {
signal?.throwIfAborted();
checkedPage(p);
const b = await r.block(p.key);
signal?.throwIfAborted();
if (b.length > 4 * 1024 ** 2)
throw new Error("Object index page exceeds 4 MiB");
const v = JSON.parse(new TextDecoder().decode(b));
if (
!Array.isArray(v.rows) ||
v.schema !== `objects-${p.key.split("/")[1]}-v1` ||
v.rows.length > 256
)
throw new Error("Invalid object page");
return v;
}
function validateRecord(v: ObjectRecord): ObjectRecord {
const blob = (b: any, kind: string) =>
!b ||
(Array.isArray(b.blocks) &&
(b.offset === undefined
? b.blocks.length === Math.ceil(b.bytes / CHUNK_BYTES)
: b.blocks.length === 1 &&
Number.isSafeInteger(b.offset) &&
b.offset >= 0 &&
b.offset + b.bytes <= PACK_BYTES) &&
b.blocks.every(
(k: any) => validKey(k) && k.startsWith(`objects/${kind}/`),
) &&
Number.isSafeInteger(b.bytes) &&
b.bytes > 0 &&
b.bytes <= 768 * 1024 ** 2);
if (
!v ||
!objectId(v.id) ||
typeof v.name !== "string" ||
!v.tags ||
Array.isArray(v.tags) ||
Object.values(v.tags).some((x) => typeof x !== "string") ||
!Array.isArray(v.bounds) ||
!v.bounds.every(boxValid) ||
!Array.isArray(v.facets) ||
!v.facets.every((x) => typeof x === "string") ||
![
"complete",
"missing-nodes",
"incomplete-area",
"relation-members",
].includes(v.geometryStatus) ||
typeof v.dependencyOnly !== "boolean" ||
!blob(v.geometry, "geometry") ||
!blob(v.topology, "topology")
)
throw new Error("Invalid object metadata");
return v;
}
export interface ObjectQuery {
bounds?: Box;
facet?: string;
tag?: { key: string; value?: string };
text?: string;
includeDependencies?: boolean;
includeUnlocated?: boolean;
}
export async function* objectRecords(
r: AtlasReader,
q: ObjectQuery = {},
signal?: AbortSignal,
progress: (s: string) => void = () => {},
onPage: (key: string) => Promise<void> = async () => {},
) {
await onPage("objects/index");
const root = await objectCatalog(r),
windows = q.bounds ? splitBounds(q.bounds) : undefined;
let pages = 0;
const seen = new Set<string>();
const visit = async function* (p: Page): AsyncGenerator<ObjectRecord> {
signal?.throwIfAborted();
checkedPage(p);
if (seen.has(p.key)) throw new Error("Cyclic object index");
seen.add(p.key);
if (
windows &&
!(q.includeUnlocated && p.unlocated) &&
!p.bounds.some((b) => windows.some((w) => overlap(w, b)))
)
return;
if (q.tag && !p.keys.includes(q.tag.key)) return;
if (q.facet && !p.facets.includes(q.facet)) return;
await onPage(p.key);
const v = await page(r, p, signal);
progress(`Reading source objects · ${++pages} index/attribute pages`);
if (["objects-spatial-v1", "objects-tag-pages-v1"].includes(v.schema)) {
for (const child of v.rows) yield* visit(child);
return;
}
if (v.schema !== "objects-records-v1")
throw new Error("Unexpected object spatial page");
for (const input of v.rows) {
const row = validateRecord(input);
if (!q.includeDependencies && row.dependencyOnly) continue;
if (
windows &&
!(q.includeUnlocated && !row.bounds.length) &&
!row.bounds.some((b) => windows.some((w) => overlap(w, b)))
)
continue;
if (q.facet && !row.facets.includes(q.facet)) continue;
if (
q.tag &&
(!Object.hasOwn(row.tags, q.tag.key) ||
(q.tag.value !== undefined && row.tags[q.tag.key] !== q.tag.value))
)
continue;
if (q.text) {
const text = Object.entries(row.tags)
.filter(
([k]) =>
k.includes("name") ||
["ref", "wikidata", "ISO3166-1", "ISO3166-1:alpha2"].includes(k),
)
.map(([, v]) => v)
.join(" ")
.normalize("NFKD")
.toLowerCase();
if (
!q.text
.normalize("NFKD")
.toLowerCase()
.split(/\s+/)
.every((t) => text.includes(t))
)
continue;
}
yield row;
}
};
let selected: Page | undefined = root.spatial;
const compact: Page[] = [];
if (q.tag?.value !== undefined && root.schema === "source-objects-v2") {
const term = tagTerm(q.tag.key, q.tag.value),
visited = new Set<string>();
const lookup = async (p: Page): Promise<Page | undefined> => {
signal?.throwIfAborted();
checkedPage(p);
if (p.min !== undefined && (term < p.min || term > p.max!)) return;
if (visited.has(p.key)) throw new Error("Cyclic tag-value index");
visited.add(p.key);
await onPage(p.key);
const v = await page(r, p, signal);
progress(`Finding tag value · ${++pages} index pages`);
if (v.schema === "objects-tag-index-v1") {
for (const child of v.rows) {
const hit = await lookup(child);
if (hit) return hit;
}
return;
}
if (v.schema !== "objects-tag-values-v1")
throw new Error("Unexpected tag-value index page");
const found = v.rows.find((row: any) => row.term === term);
if (!found) return;
if (found.root) return checkedPage(found.root);
if (
!Array.isArray(found.pages) ||
found.pages.length > 8 ||
!found.pages.every(
(key: unknown) =>
validKey(key) && String(key).startsWith("objects/records/"),
)
)
throw new Error("Invalid tag-value postings");
// Short lists avoid repeating attribute-page summaries for every unique name/address.
for (const key of found.pages) {
// Records still apply exact attribute and geographic filters; the synthetic
// envelope only lets this compact posting reach the normal record visitor.
compact.push({
key,
bounds: [[-180, -90, 180, 90]],
unlocated: true,
keys: [q.tag!.key],
facets: q.facet ? [q.facet] : [],
count: 0,
});
}
return;
};
selected = await lookup(root.tags!);
}
if (selected) yield* visit(selected);
for (const p of compact) yield* visit(p);
}
export async function findObject(
r: AtlasReader,
id: string,
signal?: AbortSignal,
): Promise<ObjectRecord | undefined> {
if (!objectId(id)) throw new Error("Use an exact OSM object identity");
const c = await objectCatalog(r);
const seen = new Set<string>();
const visit = async (p: Page): Promise<ObjectRecord | undefined> => {
if ((p.min && id < p.min) || (p.max && id > p.max)) return;
if (seen.has(p.key)) throw new Error("Cyclic object ID index");
seen.add(p.key);
const v = await page(r, p, signal);
if (v.schema === "objects-lookup-v1") {
for (const child of v.rows) {
const found = await visit(child);
if (found) return found;
}
return;
}
if (v.schema !== "objects-ids-v1")
throw new Error("Unexpected object ID index");
const hit = v.rows.find((x: any) => x.id === id);
if (!hit) return;
if (!validKey(hit.page) || !hit.page.startsWith("objects/records/"))
throw new Error("Invalid object record pointer");
const records = JSON.parse(
new TextDecoder().decode(await r.block(hit.page)),
);
if (records.schema !== "objects-records-v1" || !Array.isArray(records.rows))
throw new Error("Invalid object record page");
const found = records.rows.find((x: any) => x.id === id);
if (!found) throw new Error("Object index identity mismatch");
return validateRecord(found);
};
return visit(c.lookup);
}
async function blob(
r: AtlasReader,
ref: NonNullable<ObjectRecord["geometry"]>,
signal?: AbortSignal,
) {
if (ref.offset !== undefined) {
signal?.throwIfAborted();
const packed = await r.block(ref.blocks[0]!);
signal?.throwIfAborted();
if (packed.length > PACK_BYTES || ref.offset + ref.bytes > packed.length)
throw new Error("Invalid packed object slice");
return JSON.parse(
new TextDecoder().decode(
packed.subarray(ref.offset, ref.offset + ref.bytes),
),
);
}
const output = new Uint8Array(ref.bytes);
let at = 0;
for (const key of ref.blocks) {
signal?.throwIfAborted();
const b = await r.block(key);
if (b.length > 1024 ** 2 || at + b.length > output.length)
throw new Error("Invalid object payload size");
output.set(b, at);
at += b.length;
}
if (at !== output.length) throw new Error("Incomplete object payload");
return JSON.parse(new TextDecoder().decode(output));
}
export async function objectGeometry(
r: AtlasReader,
row: ObjectRecord,
signal?: AbortSignal,
): Promise<Geometry | undefined> {
validateRecord(row);
if (!row.geometry) return;
const g: Geometry = await blob(r, row.geometry, signal);
const { geometryBounds } = await import("./model");
if (JSON.stringify(geometryBounds(g)) !== JSON.stringify(row.bounds))
throw new Error("Object geometry extent mismatch");
return g;
}
export async function objectTopology(
r: AtlasReader,
row: ObjectRecord,
signal?: AbortSignal,
) {
validateRecord(row);
return row.topology ? blob(r, row.topology, signal) : {};
}
/** Dateline-aware local planar intersection. Storage supports poles; polar cap polygon interpretation is not inferred. */
export function intersectsGlobal(g: Geometry, bounds: Box) {
const unwrap = (v: Geometry): Geometry => {
const line = (ps: number[][], anchor?: number) => {
let prev = anchor ?? ps[0]![0]!;
return ps.map((p) => {
let x = p[0]!;
while (x - prev > 180) x -= 360;
while (x - prev < -180) x += 360;
prev = x;
return [x, p[1]!];
});
};
switch (v.type) {
case "Point":
case "MultiPoint":
return v;
case "LineString":
return { ...v, coordinates: line(v.coordinates) };
case "MultiLineString":
return { ...v, coordinates: v.coordinates.map((p) => line(p)) };
case "Polygon":
return {
...v,
coordinates: v.coordinates.map((p) =>
line(p, v.coordinates[0]![0]![0]),
),
};
case "MultiPolygon":
return {
...v,
coordinates: v.coordinates.map((poly) =>
poly.map((p) => line(p, poly[0]![0]![0])),
),
};
case "GeometryCollection":
return { ...v, geometries: v.geometries.map(unwrap) };
}
};
const u = unwrap(g);
return splitBounds(bounds).some((b) =>
[-360, 0, 360].some((d) =>
booleanIntersects(u, {
type: "Polygon",
coordinates: [
[
[b[0] + d, b[1]],
[b[2] + d, b[1]],
[b[2] + d, b[3]],
[b[0] + d, b[3]],
[b[0] + d, b[1]],
],
],
}),
),
);
}
export async function* readObjects(
r: AtlasReader,
bounds: Box,
geometry = true,
signal?: AbortSignal,
predicate: (f: any) => boolean = () => true,
progress?: (s: string) => void,
q: ObjectQuery = {},
) {
for await (const row of objectRecords(
r,
{ ...q, bounds },
signal,
progress,
)) {
const f = {
type: "Feature" as const,
id: row.id,
bbox: row.bounds[0],
properties: {
id: row.id,
name: row.name,
kind: "objects" as const,
tags: row.tags,
facets: row.facets,
geometryStatus: row.geometryStatus,
},
};
if (!predicate(f) || !row.geometry) continue;
const contained = row.bounds.every((b) =>
splitBounds(bounds).some(
(w) => w[0] <= b[0] && w[1] <= b[1] && w[2] >= b[2] && w[3] >= b[3],
),
);
const g =
geometry || !contained ? await objectGeometry(r, row, signal) : undefined;
if (g && !intersectsGlobal(g, bounds)) continue;
yield { ...f, ...(geometry ? { geometry: g! } : {}) };
}
}
