# ⬡ ParcelGen — Land Subdivision Tool A full-stack GIS application for automatic land parcel generation from user-drawn site boundaries, road networks, and existing building footprints. --- ## Architecture ``` ┌──────────────────────────────────────────────────────┐ │ Docker Network │ │ │ │ ┌──────────────────┐ ┌────────────────────────┐ │ │ │ Nginx + Front │────▶│ FastAPI Backend v3 │ │ │ │ (port 3000) │ │ (port 8000) │ │ │ │ OpenLayers UI │ │ Shapely / GEOS engine │ │ │ └──────────────────┘ └────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ▲ │ Browser http://localhost:3000 ``` --- ## Quick Start ### Prerequisites - Docker and Docker Compose ### Run ```bash cd parcel-tool docker compose up --build ``` Open **http://localhost:3000** in your browser. ### Stop ```bash docker compose down ``` --- ## Feature Overview ### Drawing Tools | Tool | Description | |---|---| | **◇ Boundary** | Draw the site boundary polygon. Double-click to finish. Only one boundary is kept at a time. | | **⟋ Road** | Draw road centrelines. Each line is buffered to the configured road width. Draw multiple; all are used. | | **▦ Building** | Draw existing building footprints. Carved out before subdivision; containing parcel flagged as "built". | | **📏 Measure** | Click to place measure points, double-click to finish. Shows live distance in the active unit system (m/km or ft/mi). | | **✕ Stop** | Exit any active draw or measure mode. | ### Map Interaction | Action | Result | |---|---| | **Hover** over a parcel | Floating tooltip: area (dual units), frontage, depth, status, road access | | **Click** a parcel | Pins a detail panel bottom-left; highlights parcel in gold | | **Click** empty map | Clears selection | | **Scroll / drag** | Standard map pan and zoom | ### Sidebar Tabs - **Draw** — Drawing tools and Run Subdivision / Export buttons - **Config** — Subdivision parameters with live unit conversion - **Layers** — Visibility, opacity, basemap switcher, and legend - **Results** — Stats dashboard and scrollable parcel list --- ## UI Controls ### Theme Toggle (sun/moon button) Click the ☀️ / 🌙 button in the top-left of the sidebar header to switch between **dark** and **light** themes. The preference is saved in `localStorage` and restored on next visit. Switching theme also switches the default basemap automatically. ### Unit System (m / ft) Click the **m** or **ft** pill to toggle metric/imperial. All config inputs convert in-place. Results display both units. The backend always receives and returns SI (metres, m²). ### Basemap Switcher 12 basemaps in 5 categories, accessible from the **Layers** tab or the floating **Basemap** panel (bottom-right): | Category | Options | |---|---| | Style | Dark (CARTO), Light (CARTO), Night Lights | | Street | OpenStreetMap | | Terrain | OpenTopoMap, ESRI Topo, ESRI Shaded Relief, Ocean | | Imagery | ESRI Satellite, ESRI Satellite + Labels | | Artistic | Stamen Terrain, Stamen Watercolor | ### Layer Switcher Both the floating **Layers** panel and the **Layers** tab expose per-layer controls: - Visibility checkbox - Opacity slider (0–100%, live update) - Colour swatch Layers: Boundary · Buildings (drawn) · Roads (drawn) · Road surfaces · Cul-de-sacs · Blocks · Parcels --- ## Subdivision Engine ### Algorithm ``` 1. Parse boundary polygon (WGS84 / EPSG:4326) 2. Parse user-drawn road centrelines + existing building footprints 3. Build road network: a. Buffer user roads → road polygons b. Auto-generate grid roads to fill blocks > max_block_length (combined with user roads, never either/or) c. Add perimeter access ring inside the boundary edge 4. Carve: buildable = boundary − roads − buildings 5. Extract contiguous buildable blocks 6. Generate cul-de-sacs for oversized blocks (if enabled) 7. Subdivide each block into rectangular parcels: a. Detect dominant orientation via oriented bounding box b. Double-bank parcels back-to-back where depth allows c. Clip each cell to the actual block polygon 8. Quality passes: a. Absorb undersized / badly-shaped parcels into neighbours b. Enforce road access — merge landlocked parcels into accessed neighbour 9. Apply existing buildings → merge spanning parcels, mark status="built" 10. Re-number sequentially, assign addresses 11. Return GeoJSON + stats ``` ### Parcel Access Guarantee Every parcel is checked for road adjacency. Landlocked parcels are merged into their nearest same-block neighbour that has road access. This loop runs until stable. Any remaining inaccessible parcels are flagged in red on the map and counted in the stats panel. ### Metric Scaling All internal geometry is in WGS84 degrees. A scale factor (metres-per-degree) is computed at the site centroid so that metre-based config values work correctly at any latitude: ``` m_per_deg = (111 320 + 111 320 × cos(lat)) / 2 ``` --- ## Default Configuration | Parameter | Default | Description | |---|---|---| | `min_frontage` | 12 m | Minimum parcel road frontage | | `min_depth` | 25 m | Minimum parcel depth | | `road_width` | 9 m | Road right-of-way width | | `max_block_length` | 120 m | Max block length before road/cul-de-sac insertion | | `allow_culdesac` | true | Generate cul-de-sacs for oversized blocks | | `corner_radius` | 3 m | Road corner rounding radius | --- ## Parcel Properties (GeoJSON) ```json { "parcel_id": "P0042", "parcel_num": 42, "block_id": 3, "area_m2": 340.5, "area_ha": 0.034, "frontage_m": 12.3, "depth_m": 27.7, "address": "Block 3, Plot 42", "zone": "Residential", "status": "vacant", "has_access": true, "frontage_ok": true, "area_ok": true, "building_area_m2": 0 } ``` `status` is `"built"` when an existing building footprint overlaps the parcel. --- ## API Reference ### `POST /subdivide` ```json { "boundary": { "type": "Polygon", "coordinates": [[...]] }, "roads": [ { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[...]] }, "properties": {} } ], "existing_features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[...]] }, "properties": {} } ], "config": { "min_frontage": 12, "min_depth": 25, "road_width": 9, "max_block_length": 120, "allow_culdesac": true, "corner_radius": 3 } } ``` Both bare geometry dicts and GeoJSON Feature wrappers are accepted. **Response:** ```json { "parcels": [ ], "roads": [ ], "blocks": [ ], "culdesacs": [ ], "stats": { "total_parcels": 42, "total_blocks": 4, "total_roads": 3, "culdesacs": 1, "parcels_no_access": 0, "parcels_built": 2, "parcels_vacant": 40, "boundary_area_m2": 18400.0, "road_area_m2": 2100.0, "buildable_area_m2": 14200.0, "avg_parcel_area_m2": 338.1, "existing_buildings": 2, "user_roads_drawn": 1 } } ``` ### `GET /health` Returns `{"status": "ok", "version": "3.0.0"}` ### `GET /config/defaults` Returns the default `SubdivisionConfig` as JSON. --- ## Map Colour Legend | Colour | Meaning | |---|---| | Blue outline | Vacant parcel | | Orange outline | Built parcel (existing building) | | Red dashed outline | No road access ⚠ | | Gold outline | Currently selected parcel | | Yellow fill | Road surface | | Purple fill | Cul-de-sac | | Green dashed | Site boundary | | Purple dashed | Drawn building footprint | --- ## Export **Export GeoJSON** in the Draw tab downloads a `.geojson` file with all parcels, roads, and cul-de-sacs plus a `metadata` block (timestamp, display units). All attribute values are always in SI units regardless of the unit switcher. --- ## Development ### Backend ```bash cd backend pip install -r requirements.txt uvicorn main:app --reload --port 8000 ``` ### Frontend Open `frontend/index.html` directly in a browser. `API_URL` auto-detects: port 3000 proxies to port 8000, otherwise uses same origin. --- ## File Structure ``` parcel-tool/ ├── docker-compose.yml ├── README.md ├── backend/ │ ├── Dockerfile │ ├── requirements.txt │ └── main.py ← Subdivision engine (v3) └── frontend/ ├── Dockerfile ├── nginx.conf └── index.html ← OpenLayers UI ``` --- ## Technical Notes - **Coordinate system:** EPSG:4326 for API I/O; EPSG:3857 for map display - **Geometry engine:** Shapely 2.x + GEOS - **Basemaps:** CARTO, OSM, Esri, Stamen — no API key needed - **Theme:** Persisted in `localStorage` under key `parcelgen-theme` - **Imperial mode:** Display only — backend always receives metres - **Performance:** Sites up to ~50 ha typically process in under 2 s