Impervious Surface Mapping¶
This notebook demonstrates how to map impervious surfaces (roads, buildings, parking lots, sidewalks) using NAIP aerial imagery and DC Open Data. We train a semantic segmentation model to classify each pixel as impervious or pervious, then vectorize and analyze the results.
Install packages¶
# %pip install geoai-py segmentation-models-pytorch
Import libraries¶
import geoai
Download sample data¶
We download NAIP aerial imagery (RGB) and impervious surface polygons for Washington DC. The vector labels come from the DC Impervious Surface 2023 dataset published by DC GIS Open Data.
raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/dc_naip.tif"
)
vector_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/dc_impervious_surfaces.geojson"
raster_path = geoai.download_file(raster_url)
vector_path = geoai.download_file(vector_url)
Inspect raster data¶
geoai.get_raster_info(raster_path)
Visualize input data¶
View the NAIP aerial imagery and the impervious surface labels.
geoai.view_raster(raster_path)
geoai.view_vector_interactive(vector_path, tiles=raster_path)
Create training data¶
Generate image tiles and corresponding label masks from the raster and vector data. The export_geotiff_tiles function creates images/ and labels/ subdirectories inside the output folder.
out_folder = "impervious"
tiles = geoai.export_geotiff_tiles(
in_raster=raster_path,
out_folder=out_folder,
in_class_data=vector_path,
tile_size=512,
stride=256,
buffer_radius=0,
skip_empty_tiles=True,
)
Train segmentation model¶
Train a U-Net model with a ResNet34 encoder for binary segmentation (background vs. impervious surface). For more architectures and encoders, see the segmentation-models-pytorch documentation.
geoai.train_segmentation_model(
images_dir=f"{out_folder}/images",
labels_dir=f"{out_folder}/labels",
output_dir=f"{out_folder}/models",
architecture="unet",
encoder_name="resnet34",
encoder_weights="imagenet",
num_channels=3,
num_classes=2,
batch_size=8,
num_epochs=20,
learning_rate=0.001,
val_split=0.2,
verbose=True,
)
Evaluate model performance¶
Plot the training and validation loss curves along with IoU and other metrics.
geoai.plot_performance_metrics(
history_path=f"{out_folder}/models/training_history.pth",
figsize=(15, 5),
verbose=True,
)
Run inference¶
Use the trained model to predict impervious surfaces on the input raster.
masks_path = "impervious_prediction.tif"
model_path = f"{out_folder}/models/best_model.pth"
geoai.semantic_segmentation(
input_path=raster_path,
output_path=masks_path,
model_path=model_path,
architecture="unet",
encoder_name="resnet34",
num_channels=3,
num_classes=2,
window_size=512,
overlap=256,
batch_size=4,
)
Visualize raster mask¶
View the predicted impervious surface mask overlaid on the original imagery.
geoai.view_raster(masks_path, nodata=0, basemap=raster_path, backend="ipyleaflet")
Vectorize results¶
Convert the predicted raster mask to vector polygons representing impervious surfaces.
output_vector_path = "impervious_polygons.geojson"
gdf = geoai.raster_to_vector(
raster_path=masks_path,
output_path=output_vector_path,
min_area=50,
simplify_tolerance=None,
)
Add geometric properties¶
Calculate area, perimeter, and other geometric properties for each detected polygon.
gdf_props = geoai.add_geometric_properties(gdf, area_unit="m2", length_unit="m")
gdf_props.head()
Filter small artifacts¶
Remove small detected regions that are unlikely to be actual impervious surfaces.
gdf_filtered = gdf_props[gdf_props["area_m2"] > 50]
print(f"Impervious surface polygons: {len(gdf_filtered)}")
print(f"Removed {len(gdf_props) - len(gdf_filtered)} small artifacts")
Visualize detected impervious surfaces¶
Display the detected impervious surface polygons on an interactive map, colored by area.
geoai.view_vector_interactive(gdf_filtered, column="area_m2", tiles=raster_path)
Split map comparison¶
Create a side-by-side comparison between the detected impervious surfaces and the original imagery.
geoai.create_split_map(
left_layer=gdf_filtered,
right_layer=raster_path,
left_args={"style": {"color": "red", "fillOpacity": 0.3}},
basemap=raster_path,
)
Impervious surface area statistics¶
Analyze the distribution of impervious surface sizes.
print(gdf_filtered["area_m2"].describe())
gdf_filtered["area_m2"].hist(bins=50)
import matplotlib.pyplot as plt
plt.xlabel("Area (m\u00b2)")
plt.ylabel("Count")
plt.title("Distribution of Impervious Surface Areas")
plt.show()
Save results¶
Save the final impervious surface polygons to a GeoJSON file.
gdf_filtered.to_file("impervious_surfaces_final.geojson", driver="GeoJSON")
print(
f"Saved {len(gdf_filtered)} impervious surface polygons to impervious_surfaces_final.geojson"
)
Summary¶
This notebook demonstrated:
- Downloading data from HuggingFace using
geoai.download_file() - Creating training tiles from NAIP imagery and impervious surface polygons
- Training a U-Net model for binary semantic segmentation
- Running inference on the input raster with sliding-window prediction
- Vectorizing results into impervious surface polygons
- Analyzing surfaces with geometric properties and area statistics
- Visualizing results with interactive maps and split-map comparisons
Model Details¶
| Property | Value |
|---|---|
| Architecture | U-Net |
| Encoder | ResNet34 (ImageNet pretrained) |
| Input | 3-band NAIP (RGB) |
| Classes | Background (0), Impervious (1) |
| Tile Size | 512 x 512 pixels |
| Training Epochs | 20 |
References¶
- DC Impervious Surface 2023: https://opendata.dc.gov/datasets/DCGIS::impervious-surface-2023
- NAIP Imagery: https://naip-usdaonline.hub.arcgis.com/
- Segmentation Models PyTorch: https://smp.readthedocs.io/