From ccb811a4599723a335513e0fdc9a1f5412abd46a Mon Sep 17 00:00:00 2001 From: kanyarimwangi Date: Tue, 3 Mar 2026 12:38:36 +0300 Subject: [PATCH] Edited readMe file --- README.md | 336 +++++---- frontend/index.html | 1666 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1875 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index a61e24c..4962ac0 100644 --- a/README.md +++ b/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 \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 566549b..4767463 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,10 +1,1672 @@ - - Title + + +ParcelGen β€” Land Subdivision Tool + + + + +
+ + + + +
+
+ + +
+
β—‡ Boundary
+
βŸ‹ Road
+
β–¦ Building
+
πŸ“ Measure
+
βœ• Stop
+
+ + +
+ + +
+
β€”βœ•
+
+
+ + +
+ Units: + METRIC (m) +
+ + +
+ +
+
+
+ + Basemap +
+ β–² +
+
+
+
+
+
+
+ +
+
+
+ + Layers +
+ β–² +
+
+
+
+
+
+
+
+ + +
+
+ Draw a boundary polygon to begin. + β€” +
+ + +
+
β€”
+
+
+ + +
+
+
Subdividing parcels…
+
+ + +
+
+
+ + + \ No newline at end of file