ParcelToolv4/README.md
2026-03-03 12:38:36 +03:00

312 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ⬡ 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 (0100%, 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