Edited readMe file
This commit is contained in:
parent
8a559d89e2
commit
ccb811a459
330
README.md
330
README.md
@ -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 (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
|
## 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
|
||||||
1666
frontend/index.html
1666
frontend/index.html
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user