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

330
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
```
┌──────────────────────────────────────────────────┐
┌──────────────────────────────────────────────────────
│ Docker Network │
│ │
│ ┌─────────────┐ ┌──────────────────────┐
│ │ Nginx │────▶│ FastAPI Backend │
│ │ (port 80) │ │ (port 8000) │
│ │ + Frontend │ │ Shapely/GIS engine │
│ └─────────────┘ └──────────────────────┘
└──────────────────────────────────────────────────┘
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ Nginx + Front │────▶│ FastAPI Backend v3 │
│ │ (port 3000) │ │ (port 8000) │
│ │ OpenLayers UI │ │ Shapely / GEOS engine │
│ └──────────────────┘ └────────────────────────┘ │
└──────────────────────────────────────────────────────
│ :8080
Browser
(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
│ Browser
http://localhost:3000
```
---
@ -54,21 +26,17 @@ corner_radius = 3 m
## Quick Start
### Prerequisites
- Docker & Docker Compose installed
- Docker and Docker Compose
### Run
```bash
# Clone / extract project
cd parcel-tool
# Build and start all services
docker compose up --build
# Access at:
http://localhost:8080
```
Open **http://localhost:3000** in your browser.
### Stop
```bash
@ -77,55 +45,161 @@ docker compose down
---
## Usage Guide
## Feature Overview
### Step 1 — Draw Boundary
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
### Drawing Tools
### Step 2 — (Optional) Draw Roads
1. Click **Draw Road**
2. Click to add road centerline vertices
3. Double-click to finish
4. Repeat for additional roads
| 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. |
> If no roads are drawn, the engine auto-generates an internal road grid based on `max_block_length`.
### Map Interaction
### Step 3 — Configure Parameters
Adjust the configuration panel:
- **Min Frontage** — minimum plot road frontage
- **Min Depth** — minimum plot depth
- **Road Width** — road right-of-way width
- **Max Block Length** — triggers cul-de-sac or cross-road insertion
- **Corner Radius** — road intersection corner rounding
- **Allow Cul-de-sacs** — toggle cul-de-sac generation
| 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 |
### Step 4 — Generate
Click **⚡ Generate Subdivision**
### Sidebar Tabs
Results appear color-coded:
- 🔵 **Blue** — parcels
- ⚫ **Dark grey** — roads
- 🟢 **Green dashed** — block boundaries
- 🟡 **Yellow** — cul-de-sacs
- **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
### Step 5 — Inspect & Export
- Hover over parcels to see attributes
- Click parcels to highlight/select
- Export all data as GeoJSON
---
## 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 `/api/subdivide`
### `POST /subdivide`
```json
{
"boundary": { /* GeoJSON Polygon */ },
"roads": [ /* GeoJSON LineStrings */ ],
"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,
@ -137,57 +211,66 @@ Results appear color-coded:
}
```
Both bare geometry dicts and GeoJSON Feature wrappers are accepted.
**Response:**
```json
{
"parcels": [ /* GeoJSON Features with properties */ ],
"roads": [ /* GeoJSON Features */ ],
"blocks": [ /* GeoJSON Features */ ],
"culdesacs": [ /* GeoJSON Features */ ],
"parcels": [ ],
"roads": [ ],
"blocks": [ ],
"culdesacs": [ ],
"stats": {
"total_parcels": 42,
"total_blocks": 4,
"avg_parcel_area_m2": 340.5,
"culdesacs": 2
"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 `/api/health`
Returns `{"status": "ok"}`
### `GET /health`
Returns `{"status": "ok", "version": "3.0.0"}`
### GET `/api/config/defaults`
Returns default configuration values.
### `GET /config/defaults`
Returns the default `SubdivisionConfig` as JSON.
---
## Subdivision Algorithm
## Map Colour Legend
```
1. Parse boundary polygon (EPSG:4326 → WGS84)
2. Buffer user roads → road polygons
└─ If no roads: auto-generate grid roads at max_block_length intervals
3. Subtract roads from boundary → buildable blocks
4. For each oversized block (> max_block_length):
└─ Insert cul-de-sac if allow_culdesac=true
5. For each block:
a. Detect dominant orientation via OBB
b. Determine double/single frontage layout
c. Calculate parcel columns and rows
d. Clip each parcel cell to block boundary
6. Shape QC:
- Compactness check (reject triangular/complex shapes)
- Minimum area check
- Absorb rejected parcels into neighboring plots
7. Assign addresses: "Plot N, Block B Road"
8. Return GeoJSON FeatureCollection
```
| 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 (Python/FastAPI)
### Backend
```bash
cd backend
@ -196,7 +279,8 @@ uvicorn main:app --reload --port 8000
```
### 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/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── main.py ← Subdivision engine
── frontend/
│ └── index.html ← OpenLayers UI
└── nginx/
└── nginx.conf ← Reverse proxy config
│ └── main.py ← Subdivision engine (v3)
── frontend/
├── Dockerfile
├── nginx.conf
└── index.html ← OpenLayers UI
```
---
## Notes
## Technical Notes
- The map uses CartoDB Dark Matter basemap (no API key needed)
- Coordinate system: EPSG:4326 (WGS84) for I/O, EPSG:3857 for display
- Geometry engine: Shapely 2.x with GEOS backend
- For large sites (>100ha), processing may take a few seconds
- **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

File diff suppressed because it is too large Load Diff