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/geocoding/src/model.ts
export type Position = [number, number];
export interface Location {
  id: string;
  name: string;
  aliases: string[];
  kind: "place" | "street" | "poi" | "peak" | "address";
  /** Explicit OSM tags; missing properties remain unknown. */
  tags?: Record<string, string>;
  address?: Address;
  category: string;
  coordinates: Position;
  context?: string;
}
export interface Street {
  id: string;
  name: string | null;
  aliases: string[];
  category: string;
  coordinates: Position[];
}
export interface LocationBlock {
  schema: "locations-v1";
  records: Location[];
}
export interface StreetBlock {
  schema: "streets-v1";
  streets: Street[];
}
export const REVERSE_LIMIT_METERS = 1000;
export function validPosition(value: unknown): value is Position {
  return (
    Array.isArray(value) &&
    value.length === 2 &&
    value.every(Number.isFinite) &&
    Math.abs(value[0]) <= 180 &&
    Math.abs(value[1]) <= 85
  );
}
export function normalize(value: string) {
  return value
    .toLowerCase()
    .replaceAll("ß", "ss")
    .normalize("NFKD")
    .replace(/\p{M}/gu, "")
    .replace(/[^\p{L}\p{N}]+/gu, " ")
    .trim()
    .replace(/\s+/g, " ");
}
export function meters(a: Position, b: Position) {
  const rad = Math.PI / 180,
    dlat = (b[1] - a[1]) * rad,
    dlon = (b[0] - a[0]) * rad;
  const h =
    Math.sin(dlat / 2) ** 2 +
    Math.cos(a[1] * rad) * Math.cos(b[1] * rad) * Math.sin(dlon / 2) ** 2;
  return 12742000 * Math.asin(Math.sqrt(Math.min(1, Math.max(0, h))));
}
/** Local segment projection with spherical distance; suitable for short regional street segments. */
export function nearestOnLine(point: Position, line: Position[]) {
  const cos = Math.cos((point[1] * Math.PI) / 180);
  let coordinates: Position = line[0]!,
    distanceMeters = Infinity;
  for (let i = 1; i < line.length; i++) {
    const a = line[i - 1]!,
      b = line[i]!;
    const dx = (b[0] - a[0]) * cos,
      dy = b[1] - a[1],
      length = dx * dx + dy * dy;
    const t =
      length === 0
        ? 0
        : Math.max(
            0,
            Math.min(
              1,
              ((point[0] - a[0]) * cos * dx + (point[1] - a[1]) * dy) / length,
            ),
          );
    const p: Position = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
      d = meters(point, p);
    if (d < distanceMeters) {
      coordinates = p;
      distanceMeters = d;
    }
  }
  return { coordinates, distanceMeters };
}
/** UI uses explicitly labelled latitude, longitude; library APIs always use [longitude, latitude]. */
export function parseCoordinates(text: string): Position {
  const parts = text.trim().split(/\s*,\s*/);
  if (
    parts.length !== 2 ||
    parts.some((s) => !s || !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(s))
  )
    throw new Error(
      "Enter latitude, longitude, for example 47.13929, 9.52280.",
    );
  const position: Position = [Number(parts[1]), Number(parts[0])];
  if (!validPosition(position))
    throw new Error("Use longitude within ±180° and latitude within ±85°.");
  return position;
}

export interface Address {
  houseNumber: string;
  street?: string;
  place?: string;
  city?: string;
  postcode?: string;
  country?: string;
  source: "osm-explicit";
}
export function explicitAddress(
  tags: Record<string, string>,
): Address | undefined {
  const houseNumber = tags["addr:housenumber"]?.trim();
  if (!houseNumber) return;
  return {
    houseNumber,
    source: "osm-explicit",
    ...Object.fromEntries(
      ["street", "place", "city", "postcode", "country"].flatMap((key) =>
        tags[`addr:${key}`]?.trim() ? [[key, tags[`addr:${key}`].trim()]] : [],
      ),
    ),
  };
}
export function addressText(a: Address) {
  return [a.street ?? a.place, a.houseNumber, a.postcode, a.city, a.country]
    .filter(Boolean)
    .join(" ");
}
export function enrichLocation(record: Location): Location {
  const address = record.tags ? explicitAddress(record.tags) : record.address;
  return address
    ? {
        ...record,
        address,
        aliases: [...new Set([...record.aliases, addressText(address)])],
      }
    : record;
}
export const SEMANTIC_RETENTION = {
  version: 1 as const,
  addresses: "osm-explicit" as const,
  poiTags: true,
  administrativeHierarchy: false,
};