Back to Atlas map viewer
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.

packages/views/src/index.ts
import { MAP_LAYERS, type MapLayer } from "../../format/src/index";
import { mapReference } from "../../format/src/urls";
import {
  PAPER,
  FLAT_SCENE,
  parseStyle,
  parseScene,
  type MapStyle,
  type MapScene,
} from "../../renderer/src/style";
import type { RasterLayerViews } from "../../renderer/src/raster";

export const TOOL_PURPOSES = [
  "routing",
  "search",
  "queries",
  "features",
] as const;
export type ToolPurpose = (typeof TOOL_PURPOSES)[number];
export interface Presentation {
  style: MapStyle;
  scene: MapScene;
  layers: MapLayer[];
  labels: { places: boolean; streets: boolean; pois: boolean };
  /** Permission to read on demand, never a request to preload. */
  tools: ToolPurpose[];
}
export interface ViewSource {
  url: string;
  root: string;
  enabled: boolean;
  layers: MapLayer[];
  labels: boolean;
  rasterLayers?: RasterLayerViews;
}
export interface ViewCamera {
  center: [number, number];
  zoom: number;
  pitch: number;
  bearing: number;
}
export interface ViewRecipe {
  version: 1;
  id: string;
  name: string;
  sources: ViewSource[];
  presentation: Presentation;
  camera?: ViewCamera;
  coverage: boolean;
}
export function defaultPresentation(): Presentation {
  return {
    style: parseStyle(PAPER),
    scene: { ...FLAT_SCENE },
    layers: [...MAP_LAYERS],
    labels: { places: true, streets: true, pois: true },
    tools: [...TOOL_PURPOSES],
  };
}
function record(v: unknown, label: string): Record<string, any> {
  if (!v || typeof v !== "object" || Array.isArray(v))
    throw new Error(`Invalid ${label}`);
  return v as Record<string, any>;
}
function bool(v: unknown): boolean {
  if (typeof v !== "boolean") throw new Error("Expected a boolean");
  return v;
}
function choices<T extends string>(v: unknown, allowed: readonly T[]): T[] {
  if (
    !Array.isArray(v) ||
    v.length > allowed.length ||
    new Set(v).size !== v.length ||
    v.some((x) => !allowed.includes(x))
  )
    throw new Error("Invalid view data selection");
  // Canonical order keeps equality and signed revisions stable.
  return allowed.filter((x) => v.includes(x));
}
export function parsePresentation(v: unknown): Presentation {
  const p = record(v, "presentation"),
    l = record(p.labels, "label selection");
  return {
    style: parseStyle(p.style),
    scene: parseScene(p.scene),
    layers: choices(p.layers, MAP_LAYERS),
    labels: {
      places: bool(l.places),
      streets: bool(l.streets),
      pois: bool(l.pois),
    },
    tools: choices(p.tools, TOOL_PURPOSES),
  };
}
export function parseCamera(v: unknown): ViewCamera {
  const c = record(v, "camera");
  if (
    !Array.isArray(c.center) ||
    c.center.length !== 2 ||
    c.center.some(
      (n: unknown) =>
        typeof n !== "number" || !Number.isFinite(n) || n < 0 || n > 1,
    ) ||
    ![c.zoom, c.pitch, c.bearing].every(Number.isFinite) ||
    c.zoom < 0 ||
    c.zoom > 20 ||
    c.pitch < 0 ||
    c.pitch > 60 ||
    Math.abs(c.bearing) > 360
  )
    throw new Error("Invalid view camera");
  return {
    center: [...c.center] as [number, number],
    zoom: c.zoom,
    pitch: c.pitch,
    bearing: c.bearing,
  };
}
export function parseViewRecipe(value: unknown): ViewRecipe {
  if (JSON.stringify(value).length > 128 * 1024)
    throw new Error("View recipe is too large");
  const v = record(value, "view recipe");
  if (
    v.version !== 1 ||
    typeof v.id !== "string" ||
    !/^[a-z0-9][a-z0-9-]{0,63}$/.test(v.id) ||
    typeof v.name !== "string" ||
    !v.name.trim() ||
    v.name.length > 120 ||
    !Array.isArray(v.sources) ||
    !v.sources.length ||
    v.sources.length > 8
  )
    throw new Error("Invalid view recipe");
  const sources = v.sources.map((x: unknown): ViewSource => {
    const s = record(x, "view source");
    if (typeof s.root !== "string" || !/^[a-f0-9]{64}$/.test(s.root))
      throw new Error("View sources require pinned Hashtree roots");
    const rasters: RasterLayerViews = {};
    if (s.rasterLayers !== undefined) {
      const r = record(s.rasterLayers, "imagery selection");
      if (Object.keys(r).length > 64)
        throw new Error("Too many imagery layers");
      for (const [id, state] of Object.entries(r)) {
        if (
          !/^[a-zA-Z0-9:_-]{1,120}$/.test(id) ||
          ["__proto__", "constructor", "prototype"].includes(id)
        )
          throw new Error("Invalid imagery id");
        const t = record(state, "imagery state");
        if (!Number.isFinite(t.opacity) || t.opacity < 0 || t.opacity > 1)
          throw new Error("Invalid imagery opacity");
        rasters[id] = { enabled: bool(t.enabled), opacity: t.opacity };
      }
    }
    return {
      url: mapReference(s.url, s.url, s.root),
      root: s.root,
      enabled: bool(s.enabled),
      layers: choices(s.layers, MAP_LAYERS),
      labels: bool(s.labels),
      rasterLayers: rasters,
    };
  });
  if (
    new Set(sources.map((s: ViewSource) => s.root)).size !== sources.length ||
    !sources.some((s: ViewSource) => s.enabled)
  )
    throw new Error("Choose distinct sources and enable at least one");
  return {
    version: 1,
    id: v.id,
    name: v.name.trim(),
    sources,
    presentation: parsePresentation(v.presentation),
    ...(v.camera ? { camera: parseCamera(v.camera) } : {}),
    coverage: bool(v.coverage),
  };
}