PageSourceSearch

https://hypercities.com/js/data.js?v=live-map-csv-1

js hypercities.com collected 2026-10-03 21:38:51 UTC 5,570 bytes, 125 lines download raw bytes

1import { loadMapLibraryRows, nodeFromMapRecord } from "./map-library.js?v=live-map-csv-1";
2
3const sourcePaths = {
4  book: "./data/hyperbook/hyperbook.json",
5  entities: "./data/hyperbook/entities.json",
6  edges: "./data/hyperbook/edges.json",
7  encounters: "./data/hyperbook/encounters.json",
8  affordances: "./data/hyperbook/experience-affordances.json",
9  bookPages: "./data/book-page-locations.json",
10  mapGraph: "./data/hyperbook-graph.json"
11};
12
13async function loadJson(path) {
14  const response = await fetch(path);
15  if (!response.ok) throw new Error(`Could not load ${path} (${response.status})`);
16  return response.json();
17}
18
19function normaliseMap(node) {
20  const bbox = node.geometry?.bbox;
21  if (!Array.isArray(bbox) || bbox.length !== 4 || !node.temporal?.startYear) return null;
22  const [west, south, east, north] = bbox.map(Number);
23  if (![west, south, east, north].every(Number.isFinite) || west >= east || south >= north) return null;
24  return {
25    id: node.id,
26    title: node.title || node.alternativeTitle || "Untitled historical map",
27    alternativeTitle: node.alternativeTitle || null,
28    city: node.place?.label || node.sourceRecord?.city || "Unplaced",
29    year: Number(node.temporal.startYear),
30    endYear: Number(node.temporal.endYear || node.temporal.startYear),
31    bbox: [west, south, east, north],
32    tileBase: node.rendering?.tileUrl || null,
33    tileType: node.rendering?.tileType || null,
34    minZoom: Number(node.rendering?.minZoom ?? 0),
35    maxZoom: Number(node.rendering?.maxZoom ?? 22),
36    sourceId: node.source?.sourceRecordId || node.id,
37    original: node
38  };
39}
40
41export function containsCoordinate(map, lngLat) {
42  const [west, south, east, north] = map.bbox;
43  return lngLat.lng >= west && lngLat.lng <= east && lngLat.lat >= south && lngLat.lat <= north;
44}
45
46export function asPolygon(map, elevation = 0) {
47  const [west, south, east, north] = map.bbox;
48  return [
49    [west, south, elevation], [east, south, elevation], [east, north, elevation],
50    [west, north, elevation], [west, south, elevation]
51  ];
52}
53
54export function tileTemplate(map) {
55  if (!map.tileBase) return null;
56  const base = map.tileBase.endsWith("/") ? map.tileBase : `${map.tileBase}/`;
57  return `${base.replace(/^http:/, "https:")}{z}/{x}/{y}.png`;
58}
59
60export function tileDiagnostic(map) {
61  if (!map.tileBase) return { state: "missing", message: "No raster endpoint is recorded for this map." };
62  const originalProtocol = new URL(map.tileBase).protocol;
63  const secure = tileTemplate(map);
64  if (originalProtocol === "http:") {
65    return {
66      state: "upgraded",
67      message: "Stored as HTTP; HyperCities upgrades the same host to HTTPS before loading tiles.",
68      originalUrl: map.tileBase,
69      url: secure
70    };
71  }
72  return { state: "recorded", message: "Secure raster template recorded.", url: secure };
73}
74
75export async function loadData() {
76  const [book, entityDoc, edgeDoc, encounterDoc, affordanceDoc, bookPageDoc, mapGraph, liveMapRows] = await Promise.all([
77    loadJson(sourcePaths.book), loadJson(sourcePaths.entities), loadJson(sourcePaths.edges),
78    loadJson(sourcePaths.encounters), loadJson(sourcePaths.affordances), loadJson(sourcePaths.bookPages), loadJson(sourcePaths.mapGraph),
79    loadMapLibraryRows().catch(() => null)
80  ]);
81  const liveById = new Map((liveMapRows || []).map((record) => [String(record.id), record]));
82  const currentMapNodes = mapGraph.nodes.map((node) => {
83    if (node.kind !== "historical-map") return node;
84    const row = liveById.get(String(node.source?.sourceRecordId || ""));
85    return row ? nodeFromMapRecord(row, node) : node;
86  });
87  const maps = currentMapNodes
88    .filter((node) => node.kind === "historical-map" && node.eligibleForCoring !== false)
89    .map(normaliseMap).filter(Boolean)
90    .sort((a, b) => a.year - b.year || a.title.localeCompare(b.title));
91  const affordancesByEncounter = new Map(encounterDoc.encounters.map((encounter) => [encounter.id, []]));
92  for (const affordance of affordanceDoc.affordances) {
93    if (!affordancesByEncounter.has(affordance.encounterId)) continue;
94    affordancesByEncounter.get(affordance.encounterId).push(affordance);
95  }
96  return {
97    book,
98    entities: new Map(entityDoc.entities.map((entity) => [entity.id, entity])),
99    objects: new Map(book.objects.map((object) => [object.id, object])),
100    edges: edgeDoc.edges,
101    encounters: new Map(encounterDoc.encounters.map((encounter) => [encounter.id, encounter])),
102    encounterList: encounterDoc.encounters,
103    affordances: affordanceDoc.affordances,
104    affordancesByEncounter,
105    bookPageLocations: bookPageDoc.highlights || {},
106    maps
107  };
108}
109
110// The Take a Map iframe should remain a lightweight, independently usable
111// example. It needs one map record, not the full Hyperbook and encounter set.
112export async function loadMapById(id) {
113  const graph = await loadJson(sourcePaths.mapGraph);
114  const node = graph.nodes.find((candidate) =>
114 candidate.id === id && candidate.kind === "historical-map");
115  if (!node) return null;
116  const sourceRecordId = String(node.source?.sourceRecordId || "");
117  const rows = await loadMapLibraryRows().catch(() => null);
118  const row = rows?.find((record) => String(record.id) === sourceRecordId);
119  return normaliseMap(row ? nodeFromMapRecord(row, node) : node);
120}
121
122export { loadMapLibraryRows };
123
124export function titleFor(data, id) { return data.entities.get(id)?.preferredLabel || data.objects.get(id)?.title || id; }
125export function objectKind(data, id) { return data.entities.get(id)?.type || data.objects.get(id)?.kind || "object"; }

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.