Yard Map
Static yard mapping site built with Leaflet and plain browser modules.
Status
The project currently has:
- A static Leaflet map with OSM raster tiles.
- Client-side JavaScript-authored yard geometry converted to GeoJSON at load time.
- Typed feature classes with centralized styling.
- 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.gpkgand 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(), andnew 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.jssrc/data/buildings.jssrc/data/pavement.jssrc/data/plants.jssrc/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.
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 planted date for popup rendering.
Feature relationships are exported with each GeoJSON feature as stable id, parentId, and groupIds properties, and the collection also exposes a top-level groups array. 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: Leaflet rendering, legend generation, and popup rendering.
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.gpkgwas used to seed most sample geometry.- The
lot_boundarylayer had mixed or incorrect CRS metadata. - The usable lot polygon was
fid = 2, and it had to be treated asEPSG:26914during one-time extraction before converting to JS coordinates.
Validation
Useful local checks:
node -e "import('./src/data/yard.js').then(({ yardGeoJSON }) => console.log(yardGeoJSON.features.length))"
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.
python3 -m http.server 4173
Then open http://localhost:4173.
Zoom behavior
The yard overlays are vector data, so they stay sharp at any zoom level the map allows. The current basemap is still raster OSM tiles, so above zoom 19 Leaflet reuses and scales those tiles instead of fetching higher-resolution imagery.
If you want a truly crisp basemap at arbitrary close zoom, you need a vector tile or self-hosted tile source rather than the standard public OSM raster tile endpoint.
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 sharper basemap zoom is needed, replace the raster OSM background with a vector tile or self-hosted tile solution.
- 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.