- src/lib/search.ts: dependency-free fuzzy search engine that matches POIs and zones by name/address with text normalization (st->street, compass words), substring + Levenshtein fuzzy matching, and field-aware ranking - MapView.svelte: builds a searchable index from loaded layers, adds a search box that shows ranked results, pans/zooms + opens popup on select, and renders dedicated popup rows for address, Facing (compass), and Field of view - custom-layers.mjs: documents optional address / bearing / fov POI fields with examples; osm-data.mjs normalizes bearing (compass point or degrees) into degrees and carries the new properties through to GeoJSON - README: documents POI data fields and the search feature
5.7 KiB
Navigator
A custom web application that serves OpenStreetMap (OSM) data. It has a build process that:
- Pulls OSM data from the Overpass API for configurable geographic areas and feature types.
- Merges hand-authored custom layers — zones (polygons) and points of
interest — from
scripts/custom-layers.mjs. - Converts everything to static GeoJSON files.
- Builds a SvelteKit frontend (with the static adapter) into a plain static site that renders each data source as a toggleable layer on an interactive Leaflet map.
The final output in build/ is a fully self-contained static site — no server
runtime required. You can serve it with any static HTTP server (nginx, Apache,
python -m http.server, S3/Cloudflare Pages, etc.).
Built With
- SvelteKit (Svelte 5, runes mode)
- @sveltejs/adapter-static — outputs a static site
- Leaflet — interactive map on the frontend
- Overpass API — OSM data source
Quick Start
npm install
# Fetch & convert OSM data into static/data/*.geojson
npm run build:data
# Full production build: (fetch data) + (static site) -> build/
npm run build
# Preview the production build locally
npm run preview
Development
npm run dev # Vite dev server (your data in static/data is served too)
npm run check # Type + Svelte checks
Build Process
| Command | What it does |
|---|---|
npm run build:data |
Runs scripts/osm-data.mjs to fetch OSM data → static/data/ |
npm run build |
build:data followed by the SvelteKit static build → build/ |
npm run preview |
Serves the build/ output locally |
Data layout
static/data/
├── _index.json # Manifest of all built layers (drives the UI)
├── <area>.geojson # One FeatureCollection per Overpass area
└── custom/
└── <layer>.geojson # One FeatureCollection per custom zone/POI layer
The frontend loads /data/_index.json, then fetches each layer's GeoJSON and
renders it as a toggleable overlay with a legend (top-right). Every layer can be
shown/hidden independently.
Configuring Areas & Queries
Edit scripts/osm-data.mjs — the AREAS object describes each area:
bbox—[south, west, north, east]in decimal degrees.queries— a map of name → Overpass QL snippet. Use{{bbox}}as the placeholder for the area's bounding box.- The script falls back across multiple public Overpass mirrors if one is overloaded, and is polite to the API (short delay between queries).
Example:
parks: `
way["leisure"="park"]({{bbox}});
out center tags geom;
`
List configured queries with npm run data -- --queries, or build a single area
with npm run data -- --area=tulsa.
Custom Layers (Zones & Points of Interest)
Besides Overpass-fetched data, you can define your own layers by hand in
scripts/custom-layers.mjs. This file ships with a couple of example layers
(my-places POIs and districts zones) that you can extend or replace.
Each layer has:
id— unique slug (becomes the output filename)name— label shown in the UI legendlayerType—'zone'(polygon) or'poi'(point)color— hex color used for markers/fillfeatures— array of feature objects
POI (point) — optional address, bearing, and fov fields:
{
name: 'Gathering Place',
desc: 'Riverside park',
address: '2650 S John Williams Way E, Tulsa, OK 74114',
bearing: 'SE', // compass point (N, NNE, NE, ...) OR numeric degrees (0-360)
fov: 140, // field of view in degrees (optional)
lon: -95.9791,
lat: 36.1098
}
address— street address shown in the popupbearing— compass direction the POI faces; accepts a compass point ('N','NNE','NE', ...) or a numeric bearing in degreesfov— field of view in degrees
The popup renders these as dedicated rows (Facing / Field of view).
Zone (polygon — the ring is closed for you):
{
name: 'Downtown Tulsa',
desc: 'Rough downtown core',
polygon: [
[-96.0000, 36.1300],
[-95.9900, 36.1300],
[-95.9900, 36.1600],
[-96.0000, 36.1600]
]
}
Coordinates are [longitude, latitude] (lon first, like GeoJSON). The file also
includes a copy-paste TEMPLATE block for adding new layers. After editing, run
npm run build:data (or npm run build) to regenerate the static data.
Search
The webapp includes a fuzzy search in the top bar that matches POIs and zones by name or address. Typing at least two characters shows ranked results; selecting one pans/zooms the map to it and opens its popup.
The matcher:
- Normalizes text (case, punctuation, and common address words —
st→street,rd→road,ave→avenue,n→north, etc.), so abbreviated or partial addresses resolve correctly. - Uses substring + Levenshtein edit-distance tolerance, so typos and near-
matches still hit (e.g.
philbrickfindsPhilbrook). - Ranks name matches above address matches above description matches.
Deployment
The build/ directory is entirely static. Serve it directly:
cd build
python3 -m http.server 8080
# or: nginx -s listen 8080; root /path/to/build; (with a fallback to index.html)
Because the site uses a static adapter with prerendering, everything can be hosted on any static file host or CDN.
Repository
Hosted on Gitea: https://gitea.thecookiejar.me/hermes-explorigin/navigator