# Yard Map

Static yard mapping site built with MapLibre, PMTiles, and plain browser modules.

## Status

The project currently has:

- A static MapLibre map backed by a local Mandan-area PMTiles vector archive.
- Client-side JavaScript-authored yard geometry converted to GeoJSON at load time.
- Typed feature classes with centralized styling.
- Schema-versioned yard data with a top-level feature type catalog.
- Per-anchor local coordinate rotation for offset-based authoring.
- Feature relationship metadata (`id`, `parentId`, `groupIds`) and a top-level group catalog for future visibility controls.
- Example yard data mostly copied from `buildings.gpkg` and then preserved as JS modules.

The next likely product direction is map-layer visibility controls driven by the existing feature groups and parent-child relationships.

## Authoring model

Geometry stays in JavaScript, then gets converted to GeoJSON client-side at load time.

- Use absolute coordinates with `point(lat, lon)`.
- Use absolute GeoJSON-style coordinates with `lngLat(lon, lat)` or `.trace([[lon, lat], ...])`.
- Use local offsets with `offset(origin)` inside `.add(...)`, for example `.add(offset(lotCorner), south(ft(15)), east(ft(10)))`.
- Build typed features with classes like `new House()`, `new Garage()`, `new Tree()`, and `new Sprinkler()` so styles live in one place.
- Add relationships with `.childOf(...)` and `.inGroup(...)` so future UI can show or hide related features together.

The example yard is now split by concern:

- `src/data/boundaries.js`
- `src/data/buildings.js`
- `src/data/pavement.js`
- `src/data/plants.js`
- `src/data/irrigation.js`

The typed feature registry lives in `src/lib/feature-types.js`, feature classes live in `src/lib/yard-features.js`, and shared coordinate helpers live in `src/lib/geometry.js`. Feature type definitions include the label, required geometry type, known detail fields, and default map style.

Local `north`, `south`, `east`, and `west` offsets are rotated per anchor. In this example, `topLeftLotCorner` in `src/data/anchors.js` carries `assumedNorthClockwiseDegrees` from `src/data/settings.js`. A negative value means that anchor's local north is counterclockwise from true north, so local east drifts a bit toward true north.

Most example geometry was copied from `buildings.gpkg` and preserved as JS-authored coordinates. Tree records also carry custom attributes like canopy diameter, height, and lifecycle dates for popup rendering.

Feature relationships are exported with each GeoJSON feature as stable `id`, `parentId`, and `groupIds` properties. Each feature also exports universal `plantedDate` and `removedDate` lifecycle fields for timeline filtering. The collection exposes `schemaVersion`, `lifecycleFields`, `featureTypes`, and `groups` at the top level. Style is resolved from the feature type registry at render time rather than stored on each exported feature. The current sample marks the porch and deck as part of the house and groups the Medora junipers under `Juniper Trees`.

## Project structure

- `src/lib/geometry.js`: low-level coordinate and offset helpers.
- `src/lib/feature-types.js`: centralized default styling by feature kind.
- `src/lib/yard-features.js`: typed feature classes, feature/group metadata, and GeoJSON export.
- `src/data/settings.js`: local authoring settings such as lot rotation.
- `src/data/anchors.js`: named anchor points for offset-based authoring.
- `src/data/boundaries.js`: lot boundary and fence features.
- `src/data/buildings.js`: house, garage, porch, deck, and building-related groups.
- `src/data/pavement.js`: driveway and walkway geometry.
- `src/data/plants.js`: flower beds, trees, and tree groups.
- `src/data/irrigation.js`: sprinkler features.
- `src/data/yard.js`: top-level yard collection assembly.
- `src/main.js`: MapLibre rendering, legend generation, and popup rendering.
- `tiles/mandan.pmtiles`: locally served vector basemap for the Mandan area.
- `scripts/extract-mandan-pmtiles.sh`: reproducible Mandan tile extraction.
- `docs/vector-tiles.md`: tile update and hosting notes.

## Key decisions

- Geometry is authored in JavaScript, not GeoJSON, so yard data can mix exact coordinates with local offset expressions.
- Styling is defined by feature type rather than repeated inline for each feature.
- Local offsets rotate per anchor, not globally, so future anchors can carry different orientations.
- Grouping is stored in the data model now so later show/hide behavior can be implemented without restructuring features.
- The example GPKG data was used as a one-time source to seed JS-authored geometry, not as a runtime dependency.

## GeoPackage notes

- `buildings.gpkg` was used to seed most sample geometry.
- The `lot_boundary` layer had mixed or incorrect CRS metadata.
- The usable lot polygon was `fid = 2`, and it had to be treated as `EPSG:26914` during one-time extraction before converting to JS coordinates.

## Validation

Useful local checks:

```bash
node -e "import('./src/data/yard.js').then(({ yardGeoJSON }) => console.log(yardGeoJSON.features.length))"
```

```bash
python3 -m http.server 4173
```

The first check validates that the authored yard data still imports and exports cleanly. The second runs the static site locally.

## Run locally

Because the app uses ES modules, serve it over HTTP instead of opening `index.html` directly.

```bash
python3 -m http.server 4173
```

Then open <http://localhost:4173>.

## Zoom behavior

Both the yard overlays and the local basemap are vector data, so linework stays
sharp as the map zooms in. The Protomaps archive tops out at its source maximum
zoom and MapLibre overzooms those vector tiles to the yard map's maximum zoom
of 24.

## Future direction

- Add UI controls for toggling groups and possibly parent-linked feature clusters.
- Decide whether toggles should operate on feature groups, feature kinds, parent-child trees, or all three.
- If more data import is needed from GIS sources, prefer one-time extraction into JS-authored modules rather than making the app depend on runtime GIS tooling.
