navigator/README.md
hermes-explorigin 63f1b75456 Add fuzzy search + richer POI data (address, bearing, fov)
- 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
2026-08-08 17:01:10 +00:00

175 lines
5.7 KiB
Markdown

# Navigator
A custom web application that serves **OpenStreetMap (OSM) data**. It has a build
process that:
1. **Pulls** OSM data from the [Overpass API](https://overpass-api.de/) for
configurable geographic areas and feature types.
2. **Merges** hand-authored custom layers — **zones** (polygons) and **points of
interest** — from `scripts/custom-layers.mjs`.
3. **Converts** everything to static **GeoJSON** files.
4. **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](https://svelte.dev/kit) (Svelte 5, runes mode)
- [@sveltejs/adapter-static](https://kit.svelte.dev/docs/adapter-static) — outputs a static site
- [Leaflet](https://leafletjs.com/) — interactive map on the frontend
- [Overpass API](https://overpass-api.de/) — OSM data source
## Quick Start
```bash
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
```bash
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:
```js
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 legend
- `layerType``'zone'` (polygon) or `'poi'` (point)
- `color` — hex color used for markers/fill
- `features` — array of feature objects
POI (point) — optional `address`, `bearing`, and `fov` fields:
```js
{
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 popup
- `bearing` — compass direction the POI faces; accepts a compass point
(`'N'`, `'NNE'`, `'NE'`, ...) or a numeric bearing in degrees
- `fov` — field of view in degrees
The popup renders these as dedicated rows (Facing / Field of view).
Zone (polygon — the ring is closed for you):
```js
{
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. `philbrick` finds `Philbrook`).
- Ranks name matches above address matches above description matches.
## Deployment
The `build/` directory is entirely static. Serve it directly:
```bash
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