Files
a-plot-of-plants/README.md
T
aaron.axvig c8c26c1706
Check and deploy / deploy (push) Successful in 23s
Deploy site with Gitea Actions
2026-07-25 22:14:40 -05:00

6.7 KiB

A Plot of Plants

Static garden and 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 checkbox-tree visibility controls.
  • Example yard data mostly copied from buildings.gpkg and then preserved as JS modules.

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 top-level category, 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, featureCategories, featureTypes, and groups at the top level. Categories use stable IDs such as plants, structures, infrastructure, and reference; feature types and groups refer to them with categoryId. 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 Junipers.

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, visibility controls, 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:

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.

Automatic deployment

Gitea Actions validates and publishes the website after every push to main. The workflow in .gitea/workflows/deploy.yml assembles only the runtime files and syncs them to a-plot-of-plants-deploy@<DEPLOY_HOST>:/var/www/html/a-plot-of-plants/. It can also be run manually from the Actions page.

The workflow requires these repository Actions secrets:

  • DEPLOY_HOST: the web server hostname or IP address as reached by the runner.
  • DEPLOY_SSH_KEY: the complete private deployment key.
  • DEPLOY_KNOWN_HOSTS: the web server's verified SSH host-key line.

The deployment deliberately excludes Git metadata, authoring documentation, the source GeoPackage, and other development-only files.

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

  • Decide whether parent-child feature relationships should further affect the visibility hierarchy.
  • 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.