Edited readMe file

This commit is contained in:
kanyarimwangi 2026-03-03 12:38:36 +03:00
parent 8a559d89e2
commit ccb811a459
2 changed files with 1875 additions and 127 deletions

336
README.md
View File

@ -1,52 +1,24 @@
# 🗺 Parcel Subdivision Tool # ⬡ ParcelGen — Land Subdivision Tool
A full-stack GIS application for automatic land parcel generation from user-drawn site boundaries and road networks. A full-stack GIS application for automatic land parcel generation from user-drawn site boundaries, road networks, and existing building footprints.
--- ---
## Architecture ## Architecture
``` ```
┌──────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────┐
│ Docker Network │ │ Docker Network │
│ │ │ │
│ ┌─────────────┐ ┌──────────────────────┐ │ │ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ Nginx │────▶│ FastAPI Backend │ │ │ │ Nginx + Front │────▶│ FastAPI Backend v3 │ │
│ │ (port 80) │ │ (port 8000) │ │ │ │ (port 3000) │ │ (port 8000) │ │
│ │ + Frontend │ │ Shapely/GIS engine │ │ │ │ OpenLayers UI │ │ Shapely / GEOS engine │ │
│ └─────────────┘ └──────────────────────┘ │ │ └──────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────┘
│ :8080 │ Browser
Browser http://localhost:3000
(OpenLayers UI)
```
## Features
| Feature | Description |
|---|---|
| **Draw Boundary** | Freehand polygon drawing on map |
| **Draw Roads** | Road centerline drawing (buffered to configured width) |
| **Auto Road Grid** | Automatic internal road generation if no roads drawn |
| **Block Detection** | Remaining buildable areas become blocks |
| **Cul-de-sacs** | Auto-generated for blocks exceeding max length |
| **Parcel Subdivision** | Rectangular parcel generation respecting min frontage/depth |
| **Shape Optimization** | Bad/small parcels absorbed by neighbors |
| **Addressing** | Auto address assignment per block/plot |
| **GeoJSON Export** | Download all results |
| **Layer Controls** | Toggle parcels, roads, blocks, cul-de-sacs |
| **Hover Tooltips** | Area, frontage, depth, address per parcel |
## Default Parameters
```
min_frontage = 12 m
min_depth = 25 m
road_width = 9 m
max_block_length = 120 m
allow_culdesac = true
corner_radius = 3 m
``` ```
--- ---
@ -54,21 +26,17 @@ corner_radius = 3 m
## Quick Start ## Quick Start
### Prerequisites ### Prerequisites
- Docker & Docker Compose installed - Docker and Docker Compose
### Run ### Run
```bash ```bash
# Clone / extract project
cd parcel-tool cd parcel-tool
# Build and start all services
docker compose up --build docker compose up --build
# Access at:
http://localhost:8080
``` ```
Open **http://localhost:3000** in your browser.
### Stop ### Stop
```bash ```bash
@ -77,55 +45,161 @@ docker compose down
--- ---
## Usage Guide ## Feature Overview
### Step 1 — Draw Boundary ### Drawing Tools
1. Click **Draw Boundary** in the toolbar
2. Click on the map to add polygon vertices
3. **Double-click** to finish the polygon
4. Press **Escape** to cancel
### Step 2 — (Optional) Draw Roads | Tool | Description |
1. Click **Draw Road** |---|---|
2. Click to add road centerline vertices | **◇ Boundary** | Draw the site boundary polygon. Double-click to finish. Only one boundary is kept at a time. |
3. Double-click to finish | ** Road** | Draw road centrelines. Each line is buffered to the configured road width. Draw multiple; all are used. |
4. Repeat for additional roads | **▦ 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. |
> If no roads are drawn, the engine auto-generates an internal road grid based on `max_block_length`. ### Map Interaction
### Step 3 — Configure Parameters | Action | Result |
Adjust the configuration panel: |---|---|
- **Min Frontage** — minimum plot road frontage | **Hover** over a parcel | Floating tooltip: area (dual units), frontage, depth, status, road access |
- **Min Depth** — minimum plot depth | **Click** a parcel | Pins a detail panel bottom-left; highlights parcel in gold |
- **Road Width** — road right-of-way width | **Click** empty map | Clears selection |
- **Max Block Length** — triggers cul-de-sac or cross-road insertion | **Scroll / drag** | Standard map pan and zoom |
- **Corner Radius** — road intersection corner rounding
- **Allow Cul-de-sacs** — toggle cul-de-sac generation
### Step 4 — Generate ### Sidebar Tabs
Click **⚡ Generate Subdivision**
Results appear color-coded: - **Draw** — Drawing tools and Run Subdivision / Export buttons
- 🔵 **Blue** — parcels - **Config** — Subdivision parameters with live unit conversion
- ⚫ **Dark grey** — roads - **Layers** — Visibility, opacity, basemap switcher, and legend
- 🟢 **Green dashed** — block boundaries - **Results** — Stats dashboard and scrollable parcel list
- 🟡 **Yellow** — cul-de-sacs
### Step 5 — Inspect & Export ---
- Hover over parcels to see attributes
- Click parcels to highlight/select ## UI Controls
- Export all data as GeoJSON
### 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 ## API Reference
### POST `/api/subdivide` ### `POST /subdivide`
```json ```json
{ {
"boundary": { /* GeoJSON Polygon */ }, "boundary": { "type": "Polygon", "coordinates": [[...]] },
"roads": [ /* GeoJSON LineStrings */ ], "roads": [
{ "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[...]] }, "properties": {} }
],
"existing_features": [
{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[...]] }, "properties": {} }
],
"config": { "config": {
"min_frontage": 12, "min_frontage": 12,
"min_depth": 25, "min_depth": 25,
@ -137,57 +211,66 @@ Results appear color-coded:
} }
``` ```
Both bare geometry dicts and GeoJSON Feature wrappers are accepted.
**Response:** **Response:**
```json ```json
{ {
"parcels": [ /* GeoJSON Features with properties */ ], "parcels": [ ],
"roads": [ /* GeoJSON Features */ ], "roads": [ ],
"blocks": [ /* GeoJSON Features */ ], "blocks": [ ],
"culdesacs": [ /* GeoJSON Features */ ], "culdesacs": [ ],
"stats": { "stats": {
"total_parcels": 42, "total_parcels": 42,
"total_blocks": 4, "total_blocks": 4,
"avg_parcel_area_m2": 340.5, "total_roads": 3,
"culdesacs": 2 "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 `/api/health` ### `GET /health`
Returns `{"status": "ok"}` Returns `{"status": "ok", "version": "3.0.0"}`
### GET `/api/config/defaults` ### `GET /config/defaults`
Returns default configuration values. Returns the default `SubdivisionConfig` as JSON.
--- ---
## Subdivision Algorithm ## Map Colour Legend
``` | Colour | Meaning |
1. Parse boundary polygon (EPSG:4326 → WGS84) |---|---|
2. Buffer user roads → road polygons | Blue outline | Vacant parcel |
└─ If no roads: auto-generate grid roads at max_block_length intervals | Orange outline | Built parcel (existing building) |
3. Subtract roads from boundary → buildable blocks | Red dashed outline | No road access ⚠ |
4. For each oversized block (> max_block_length): | Gold outline | Currently selected parcel |
└─ Insert cul-de-sac if allow_culdesac=true | Yellow fill | Road surface |
5. For each block: | Purple fill | Cul-de-sac |
a. Detect dominant orientation via OBB | Green dashed | Site boundary |
b. Determine double/single frontage layout | Purple dashed | Drawn building footprint |
c. Calculate parcel columns and rows
d. Clip each parcel cell to block boundary ---
6. Shape QC:
- Compactness check (reject triangular/complex shapes) ## Export
- Minimum area check
- Absorb rejected parcels into neighboring plots **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.
7. Assign addresses: "Plot N, Block B Road"
8. Return GeoJSON FeatureCollection
```
--- ---
## Development ## Development
### Backend (Python/FastAPI) ### Backend
```bash ```bash
cd backend cd backend
@ -196,7 +279,8 @@ uvicorn main:app --reload --port 8000
``` ```
### Frontend ### Frontend
Pure HTML/JS — just open `frontend/index.html` in a browser (update API_BASE to `http://localhost:8000`).
Open `frontend/index.html` directly in a browser. `API_URL` auto-detects: port 3000 proxies to port 8000, otherwise uses same origin.
--- ---
@ -209,18 +293,20 @@ parcel-tool/
├── backend/ ├── backend/
│ ├── Dockerfile │ ├── Dockerfile
│ ├── requirements.txt │ ├── requirements.txt
│ └── main.py ← Subdivision engine │ └── main.py ← Subdivision engine (v3)
── frontend/ ── frontend/
│ └── index.html ← OpenLayers UI ├── Dockerfile
└── nginx/ ├── nginx.conf
└── nginx.conf ← Reverse proxy config └── index.html ← OpenLayers UI
``` ```
--- ---
## Notes ## Technical Notes
- The map uses CartoDB Dark Matter basemap (no API key needed) - **Coordinate system:** EPSG:4326 for API I/O; EPSG:3857 for map display
- Coordinate system: EPSG:4326 (WGS84) for I/O, EPSG:3857 for display - **Geometry engine:** Shapely 2.x + GEOS
- Geometry engine: Shapely 2.x with GEOS backend - **Basemaps:** CARTO, OSM, Esri, Stamen — no API key needed
- For large sites (>100ha), processing may take a few seconds - **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

File diff suppressed because it is too large Load Diff