PageSourceSearch

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

js hypercities.com collected 2026-10-03 21:39:02 UTC 3,524 bytes, 73 lines download raw bytes

1export const mapLibraryCsvUrl = "https://raw.githubusercontent.com/yohman/maplibrary/main/data/maps.csv";
2
3export function parseCsv(text) {
4  const rows = [];
5  let row = [];
6  let value = "";
7  let quoted = false;
8  for (let index = 0; index < text.length; index += 1) {
9    const char = text[index];
10    if (quoted) {
11      if (char === '"' && text[index + 1] === '"') { value += '"'; index += 1; }
12      else if (char === '"') quoted = false;
13      else value += char;
14    } else if (char === '"' && value === "") quoted = true;
15    else if (char === ",") { row.push(value); value = ""; }
16    else if (char === "\n" || char === "\r") {
17      if (char === "\r" && text[index + 1] === "\n") index += 1;
18      row.push(value); value = "";
19      if (row.some((cell) => cell !== "")) rows.push(row);
20      row = [];
21    } else value += char;
22  }
23  if (value || row.length) { row.push(value); if (row.some((cell) => cell !== "")) rows.push(row); }
24  if (!rows.length) return [];
25  const [headers, ...records] = rows;
26  return records.map((cells) => Object.fromEntries(headers.map((header, index) => [header, cells[index] ?? ""])));
27}
28
29export async function loadMapLibraryRows() {
30  const url = new URL(mapLibraryCsvUrl);
31  url.searchParams.set("v", Date.now().toString());
32  const response = await fetch(url, { cache: "no-store" });
33  if (!response.ok) throw new Error(`Map Library CSV unavailable (${response.status})`);
34  const records = parseCsv(await response.text());
35  if (!records.length || !records[0].id) throw new Error("Map Library CSV has no map records.");
36  return records;
37}
38
39function yearFrom(value, fallback = 0) {
40  const match = String(value ?? "").match(/(?:^|\D)(\d{3,4})(?:\D|$)/);
41  return match ? Number(match[1]) : fallback;
42}
43
44export function nodeFromMapRecord(record, existing = {}) {
45  const sourceRecordId = String(record.id || existing.source?.sourceRecordId || "");
46  const south = Number(record["mapping.swLat"]);
47  const west = Number(record["mapping.swLon"]);
48  const north = Number(record["mapping.neLat"]);
49  const east = Number(record["mapping.neLon"]);
50  const validBounds = [west, south, east, north].every(Number.isFinite) && west < east && south < north;
51  const startDate = record["mapping.dateFrom.date"] || "";
52  const endDate = record["mapping.dateTo.date"] || startDate;
53  const startYear = yearFrom(startDate, yearFrom(record.publicationDate, existing.temporal?.startYear || 0));
54  const endYear = yearFrom(endDate, startYear);
55  return {
56    ...existing,
57    id: existing.id || `map:${sourceRecordId}`,
58    kind: "historical-map",
59    title: record.titleEn || record.title || existing.title || "Untitled historical map",
60    alternativeTitle: record.title || existing.alternativeTitle || null,
61    source: { ...(existing.source || {}), dataset: "map-library", sourceRecordId },
62    place: { ...(existing.place || {}), label: record.city || existing.place?.label || "Unplaced" },
63    temporal: { ...(existing.temporal || {}), start: startDate, end: endDate, startYear, endYear },
64    geometry: validBounds ? { ...(existing.geometry || {}), bbox: [west, south, east, north] } : existing.geometry,
65    rendering: {
66      ...(existing.rendering || {}), tileType: record.tileType || null, tileUrl: record.tileUrl || null,
67      thumbnailUrl: record.thumbnailUrl || null, minZoom: Number(record.minZoom || 0), maxZoom: Number(record.maxZoom || 22),
68      projection: record.projection || null
69    },
70    sourceRecord: record,
71    eligibleForCoring: validBounds && startYear > 0
72  };
73}

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.