Skip to content

Commit 7af2686

Browse files
committed
adding sewer corridor check
1 parent cb7d7ff commit 7af2686

2 files changed

Lines changed: 124 additions & 0 deletions

File tree

.DS_Store

0 Bytes
Binary file not shown.

scripts/verify_sewer_corridor.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
"""
3+
verify_sewer_corridor.py
4+
------------------------
5+
Verify the Estimated Sewer Service Corridor area calculation.
6+
7+
This script:
8+
1. Loads all wastewater and combined sewer features
9+
2. Projects to UTM Zone 18 for accurate calculations
10+
3. Buffers them by 300 feet (91.4 meters) on both sides
11+
4. Unions (dissolves) overlapping buffers
12+
5. Clips to Vermont town boundaries
13+
6. Calculates total area in square miles
14+
15+
Expected: ~111.34 square miles
16+
17+
Run from repo root:
18+
python scripts/verify_sewer_corridor.py
19+
"""
20+
21+
import json
22+
from pathlib import Path
23+
24+
from shapely.geometry import shape
25+
from shapely.ops import unary_union
26+
import geopandas as gpd
27+
from geopandas import GeoSeries, GeoDataFrame
28+
29+
REPO = Path(__file__).resolve().parent.parent
30+
LINEAR_DIR = REPO / "data" / "linear_by_rpc"
31+
TOWNS_FILE = REPO / "data" / "Vermont_Town_GEOID_RPC_County.geojson"
32+
33+
34+
def load_linear_features():
35+
"""Load all linear features from RPC split files."""
36+
paths = sorted(LINEAR_DIR.glob("Vermont_Linear_*.geojson"))
37+
features = []
38+
for path in paths:
39+
with path.open() as f:
40+
gj = json.load(f)
41+
features.extend(gj.get("features", []))
42+
return features
43+
44+
45+
def load_vermont_boundary():
46+
"""Load Vermont town boundaries and union them into one polygon."""
47+
with TOWNS_FILE.open() as f:
48+
towns_gj = json.load(f)
49+
50+
polygons = []
51+
for feature in towns_gj.get("features", []):
52+
geom = feature.get("geometry")
53+
if geom:
54+
polygons.append(shape(geom))
55+
56+
if not polygons:
57+
raise ValueError("No Vermont town polygons loaded")
58+
59+
vermont_boundary = unary_union(polygons)
60+
return vermont_boundary
61+
62+
63+
def verify_corridor():
64+
"""Calculate the sewer service corridor area."""
65+
features = load_linear_features()
66+
vermont_boundary_wgs84 = load_vermont_boundary()
67+
68+
# Filter to wastewater and combined only
69+
ww_features = [
70+
f for f in features
71+
if (f.get("properties") or {}).get("SystemType") in ("Wastewater", "Combined")
72+
]
73+
74+
print(f"Total linear features: {len(features):,}")
75+
print(f"Wastewater + Combined features: {len(ww_features):,}")
76+
77+
if not ww_features:
78+
print("No wastewater/combined features found")
79+
return
80+
81+
# Create GeoDataFrame with wastewater/combined features
82+
geos = [{"geometry": shape(f.get("geometry"))} for f in ww_features]
83+
gdf = GeoDataFrame(geos, crs="EPSG:4326")
84+
85+
# Project to UTM Zone 18 for accurate buffering and area calculation
86+
print("Projecting to UTM Zone 18...")
87+
gdf_utm = gdf.to_crs("EPSG:32618") # UTM Zone 18N (covers Vermont)
88+
89+
# 300 feet = 91.4432 meters
90+
BUFFER_DISTANCE_M = 91.4432
91+
print(f"Buffering by {BUFFER_DISTANCE_M} meters ({BUFFER_DISTANCE_M / 0.3048:.1f} feet)...")
92+
gdf_buffered = gdf_utm.copy()
93+
gdf_buffered["geometry"] = gdf_utm.geometry.buffer(BUFFER_DISTANCE_M)
94+
95+
# Dissolve (union) all buffers using shapely for efficiency
96+
print("Unioning overlapping buffers (this may take a minute)...")
97+
buffered_geoms = [geom for geom in gdf_buffered.geometry]
98+
corridor_utm = unary_union(buffered_geoms)
99+
100+
# Load and project Vermont boundary
101+
print("Projecting Vermont boundary to UTM...")
102+
vt_boundary_gdf = GeoDataFrame({"geometry": [vermont_boundary_wgs84]}, crs="EPSG:4326")
103+
vt_boundary_utm = vt_boundary_gdf.to_crs("EPSG:32618").iloc[0].geometry
104+
105+
# Clip corridor to Vermont boundary
106+
print("Clipping corridor to Vermont boundary...")
107+
clipped_corridor_utm = corridor_utm.intersection(vt_boundary_utm)
108+
109+
# Calculate area in square meters
110+
area_sq_meters = clipped_corridor_utm.area
111+
112+
# Convert to square miles: 1 mile = 1609.34 meters
113+
sq_miles_per_sq_meter = 1 / (1609.34 ** 2)
114+
area_sq_miles = area_sq_meters * sq_miles_per_sq_meter
115+
116+
print(f"\n{'='*60}")
117+
print(f"Sewer Service Corridor Area: {area_sq_miles:.2f} square miles")
118+
print(f"{'='*60}")
119+
print(f"\nExpected (from index.html): 111.34 square miles")
120+
print(f"Difference: {abs(area_sq_miles - 111.34):.2f} square miles ({abs(area_sq_miles - 111.34)/111.34*100:.1f}%)")
121+
122+
123+
if __name__ == "__main__":
124+
verify_corridor()

0 commit comments

Comments
 (0)