Edited readMe file
This commit is contained in:
parent
8a559d89e2
commit
ccb811a459
336
README.md
336
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
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ Docker Network │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Nginx │────▶│ FastAPI Backend │ │
|
||||
│ │ (port 80) │ │ (port 8000) │ │
|
||||
│ │ + Frontend │ │ Shapely/GIS 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
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ Docker Network │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌────────────────────────┐ │
|
||||
│ │ Nginx + Front │────▶│ FastAPI Backend v3 │ │
|
||||
│ │ (port 3000) │ │ (port 8000) │ │
|
||||
│ │ OpenLayers UI │ │ Shapely / GEOS engine │ │
|
||||
│ └──────────────────┘ └────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
▲
|
||||
│ 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 (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 `/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
|
||||
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