Python Geospatial Development Best Practices¶
Objective: Master senior-level Python geospatial development patterns for production systems. When you need to build spatial data applications, when you want to implement geospatial analysis workflows, when you need enterprise-grade geospatial strategiesโthese best practices become your weapon of choice.
Core Principles¶
- Spatial Accuracy: Ensure precise spatial calculations and transformations
- Performance: Optimize for large geospatial datasets
- Standards: Follow OGC and industry standards
- Visualization: Create effective geospatial visualizations
- Integration: Seamlessly integrate with spatial databases
Geospatial Data Processing¶
Spatial Data Structures¶
# python/01-spatial-data-structures.py
"""
Spatial data structures and geometric operations
"""
from typing import List, Dict, Any, Optional, Union, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import Point, Polygon, LineString, MultiPolygon
from shapely.ops import unary_union, cascaded_union
from shapely.affinity import translate, rotate, scale
from shapely.validation import make_valid
import pyproj
from pyproj import CRS, Transformer
import folium
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class GeometryType(Enum):
"""Geometry type enumeration"""
POINT = "Point"
LINESTRING = "LineString"
POLYGON = "Polygon"
MULTIPOINT = "MultiPoint"
MULTILINESTRING = "MultiLineString"
MULTIPOLYGON = "MultiPolygon"
class SpatialDataProcessor:
"""Spatial data processing utilities"""
def __init__(self, crs: str = "EPSG:4326"):
self.crs = CRS.from_string(crs)
self.processor_metrics = {}
def create_point(self, x: float, y: float, crs: str = None) -> Point:
"""Create a point geometry"""
point = Point(x, y)
if crs and crs != self.crs.to_string():
# Transform to target CRS
transformer = Transformer.from_crs(self.crs, CRS.from_string(crs))
x_transformed, y_transformed = transformer.transform(x, y)
point = Point(x_transformed, y_transformed)
return point
def create_polygon(self, coordinates: List[Tuple[float, float]],
crs: str = None) -> Polygon:
"""Create a polygon geometry"""
polygon = Polygon(coordinates)
if crs and crs != self.crs.to_string():
# Transform coordinates
transformer = Transformer.from_crs(self.crs, CRS.from_string(crs))
transformed_coords = [transformer.transform(x, y) for x, y in coordinates]
polygon = Polygon(transformed_coords)
return polygon
def create_linestring(self, coordinates: List[Tuple[float, float]],
crs: str = None) -> LineString:
"""Create a linestring geometry"""
linestring = LineString(coordinates)
if crs and crs != self.crs.to_string():
# Transform coordinates
transformer = Transformer.from_crs(self.crs, CRS.from_string(crs))
transformed_coords = [transformer.transform(x, y) for x, y in coordinates]
linestring = LineString(transformed_coords)
return linestring
def transform_geometry(self, geometry, from_crs: str, to_crs: str) -> Any:
"""Transform geometry between coordinate systems"""
transformer = Transformer.from_crs(from_crs, to_crs)
if geometry.geom_type == "Point":
x, y = transformer.transform(geometry.x, geometry.y)
return Point(x, y)
elif geometry.geom_type == "Polygon":
coords = list(geometry.exterior.coords)
transformed_coords = [transformer.transform(x, y) for x, y in coords]
return Polygon(transformed_coords)
else:
# Handle other geometry types
return geometry
def calculate_distance(self, geom1: Any, geom2: Any,
unit: str = "meters") -> float:
"""Calculate distance between geometries"""
# Ensure geometries are in projected CRS for accurate distance calculation
if self.crs.is_geographic:
# Transform to UTM for distance calculation
utm_crs = self._get_utm_crs(geom1.centroid.x, geom1.centroid.y)
geom1_utm = self.transform_geometry(geom1, self.crs.to_string(), utm_crs)
geom2_utm = self.transform_geometry(geom2, self.crs.to_string(), utm_crs)
else:
geom1_utm = geom1
geom2_utm = geom2
distance = geom1_utm.distance(geom2_utm)
if unit == "kilometers":
return distance / 1000
elif unit == "miles":
return distance / 1609.34
else:
return distance
def calculate_area(self, geometry: Any, unit: str = "square_meters") -> float:
"""Calculate area of geometry"""
if geometry.geom_type not in ["Polygon", "MultiPolygon"]:
return 0.0
# Ensure geometry is in projected CRS for accurate area calculation
if self.crs.is_geographic:
utm_crs = self._get_utm_crs(geometry.centroid.x, geometry.centroid.y)
geometry_projected = self.transform_geometry(geometry, self.crs.to_string(), utm_crs)
else:
geometry_projected = geometry
area = geometry_projected.area
if unit == "square_kilometers":
return area / 1_000_000
elif unit == "acres":
return area / 4046.86
else:
return area
def calculate_bounds(self, geometries: List[Any]) -> Tuple[float, float, float, float]:
"""Calculate bounding box for list of geometries"""
if not geometries:
return (0, 0, 0, 0)
# Get bounds for each geometry
bounds_list = [geom.bounds for geom in geometries]
# Calculate overall bounds
minx = min(bounds[0] for bounds in bounds_list)
miny = min(bounds[1] for bounds in bounds_list)
maxx = max(bounds[2] for bounds in bounds_list)
maxy = max(bounds[3] for bounds in bounds_list)
return (minx, miny, maxx, maxy)
def _get_utm_crs(self, lon: float, lat: float) -> str:
"""Get UTM CRS for given coordinates"""
utm_zone = int((lon + 180) / 6) + 1
hemisphere = "north" if lat >= 0 else "south"
return f"EPSG:{32600 + utm_zone if hemisphere == 'north' else 32700 + utm_zone}"
class SpatialOperations:
"""Spatial operations and analysis"""
def __init__(self):
self.operation_metrics = {}
def spatial_join(self, left_gdf: gpd.GeoDataFrame, right_gdf: gpd.GeoDataFrame,
how: str = "inner", predicate: str = "intersects") -> gpd.GeoDataFrame:
"""Perform spatial join between GeoDataFrames"""
result = gpd.sjoin(left_gdf, right_gdf, how=how, predicate=predicate)
self.operation_metrics["spatial_join"] = {
"left_features": len(left_gdf),
"right_features": len(right_gdf),
"result_features": len(result),
"join_type": how,
"predicate": predicate
}
return result
def buffer_analysis(self, geometries: List[Any], buffer_distance: float,
crs: str = None) -> List[Any]:
"""Create buffers around geometries"""
if crs:
# Transform to projected CRS for accurate buffering
transformer = Transformer.from_crs("EPSG:4326", crs)
buffered_geometries = []
for geom in geometries:
if geom.geom_type == "Point":
x, y = transformer.transform(geom.x, geom.y)
geom_projected = Point(x, y)
else:
# Handle other geometry types
geom_projected = geom
buffered = geom_projected.buffer(buffer_distance)
buffered_geometries.append(buffered)
return buffered_geometries
else:
return [geom.buffer(buffer_distance) for geom in geometries]
def intersection_analysis(self, geom1: Any, geom2: Any) -> Dict[str, Any]:
"""Analyze intersection between geometries"""
intersection = geom1.intersection(geom2)
result = {
"intersects": geom1.intersects(geom2),
"intersection_geometry": intersection,
"intersection_area": intersection.area if hasattr(intersection, 'area') else 0,
"intersection_type": intersection.geom_type if hasattr(intersection, 'geom_type') else None
}
return result
def union_analysis(self, geometries: List[Any]) -> Any:
"""Perform union of multiple geometries"""
if not geometries:
return None
if len(geometries) == 1:
return geometries[0]
# Use unary_union for efficient union operation
union_result = unary_union(geometries)
self.operation_metrics["union"] = {
"input_geometries": len(geometries),
"result_geometry_type": union_result.geom_type,
"result_area": union_result.area if hasattr(union_result, 'area') else 0
}
return union_result
def clip_analysis(self, target_geom: Any, clip_geom: Any) -> Any:
"""Clip target geometry with clip geometry"""
if not target_geom.intersects(clip_geom):
return None
clipped = target_geom.intersection(clip_geom)
self.operation_metrics["clip"] = {
"target_area": target_geom.area if hasattr(target_geom, 'area') else 0,
"clip_area": clip_geom.area if hasattr(clip_geom, 'area') else 0,
"result_area": clipped.area if hasattr(clipped, 'area') else 0
}
return clipped
def nearest_neighbor_analysis(self, points: List[Point],
target_points: List[Point]) -> List[Dict[str, Any]]:
"""Find nearest neighbors between point sets"""
results = []
for point in points:
distances = [point.distance(target) for target in target_points]
nearest_idx = np.argmin(distances)
nearest_point = target_points[nearest_idx]
distance = distances[nearest_idx]
results.append({
"source_point": point,
"nearest_target": nearest_point,
"distance": distance,
"target_index": nearest_idx
})
return results
class GeospatialVisualizer:
"""Geospatial visualization utilities"""
def __init__(self):
self.visualization_metrics = {}
def create_interactive_map(self, gdf: gpd.GeoDataFrame,
center: Tuple[float, float] = None,
zoom_start: int = 10) -> folium.Map:
"""Create interactive Folium map"""
if center is None:
# Calculate center from data
bounds = gdf.total_bounds
center = ((bounds[1] + bounds[3]) / 2, (bounds[0] + bounds[2]) / 2)
m = folium.Map(location=center, zoom_start=zoom_start)
# Add geometries to map
for idx, row in gdf.iterrows():
if row.geometry.geom_type == "Point":
folium.Marker(
location=[row.geometry.y, row.geometry.x],
popup=f"Feature {idx}"
).add_to(m)
elif row.geometry.geom_type in ["Polygon", "MultiPolygon"]:
folium.GeoJson(
row.geometry.__geo_interface__,
popup=f"Feature {idx}"
).add_to(m)
return m
def create_plotly_map(self, gdf: gpd.GeoDataFrame,
color_column: str = None,
size_column: str = None) -> go.Figure:
"""Create Plotly map visualization"""
if color_column is None:
color_column = gdf.columns[0] if len(gdf.columns) > 1 else None
# Convert geometries to coordinates
lats, lons = [], []
colors, sizes = [], []
for idx, row in gdf.iterrows():
if row.geometry.geom_type == "Point":
lats.append(row.geometry.y)
lons.append(row.geometry.x)
colors.append(row[color_column] if color_column else "blue")
sizes.append(row[size_column] if size_column else 10)
elif row.geometry.geom_type in ["Polygon", "MultiPolygon"]:
# Extract polygon coordinates
if row.geometry.geom_type == "Polygon":
coords = list(row.geometry.exterior.coords)
else:
coords = []
for poly in row.geometry.geoms:
coords.extend(list(poly.exterior.coords))
if coords:
lats.extend([coord[1] for coord in coords])
lons.extend([coord[0] for coord in coords])
colors.extend([row[color_column] if color_column else "blue"] * len(coords))
sizes.extend([row[size_column] if size_column else 10] * len(coords))
fig = go.Figure()
if color_column:
fig.add_trace(go.Scattermapbox(
lat=lats,
lon=lons,
mode='markers',
marker=dict(
size=sizes,
color=colors,
colorscale='Viridis'
),
text=gdf[color_column].tolist(),
hovertemplate=f"{color_column}: %{{text}}<br>Lat: %{{lat}}<br>Lon: %{{lon}}<extra></extra>"
))
else:
fig.add_trace(go.Scattermapbox(
lat=lats,
lon=lons,
mode='markers',
marker=dict(size=sizes)
))
fig.update_layout(
mapbox=dict(
style="open-street-map",
center=dict(lat=np.mean(lats), lon=np.mean(lons)),
zoom=10
),
title="Geospatial Data Visualization"
)
return fig
def create_choropleth_map(self, gdf: gpd.GeoDataFrame,
value_column: str,
color_scale: str = "Viridis") -> go.Figure:
"""Create choropleth map"""
fig = px.choropleth_mapbox(
gdf,
geojson=gdf.geometry,
locations=gdf.index,
color=value_column,
color_continuous_scale=color_scale,
mapbox_style="open-street-map",
center=dict(lat=gdf.geometry.centroid.y.mean(),
lon=gdf.geometry.centroid.x.mean()),
zoom=10
)
fig.update_layout(
title=f"Choropleth Map - {value_column}",
margin=dict(r=0, t=0, l=0, b=0)
)
return fig
class SpatialDatabase:
"""Spatial database operations"""
def __init__(self, connection_string: str):
self.connection_string = connection_string
self.db_metrics = {}
def create_spatial_table(self, table_name: str,
geometry_column: str = "geometry",
crs: str = "EPSG:4326") -> bool:
"""Create spatial table with PostGIS"""
# This would be implemented with actual database connection
# For now, return success
return True
def insert_spatial_data(self, table_name: str, gdf: gpd.GeoDataFrame) -> bool:
"""Insert spatial data into database"""
# This would be implemented with actual database connection
# For now, return success
self.db_metrics["inserted_features"] = len(gdf)
return True
def spatial_query(self, table_name: str,
geometry: Any,
operation: str = "intersects") -> gpd.GeoDataFrame:
"""Perform spatial query"""
# This would be implemented with actual database connection
# For now, return empty GeoDataFrame
return gpd.GeoDataFrame()
def spatial_index_analysis(self, gdf: gpd.GeoDataFrame) -> Dict[str, Any]:
"""Analyze spatial index performance"""
# Calculate spatial index statistics
bounds = gdf.total_bounds
area = (bounds[2] - bounds[0]) * (bounds[3] - bounds[1])
result = {
"total_features": len(gdf),
"bounds": bounds,
"area": area,
"density": len(gdf) / area if area > 0 else 0,
"geometry_types": gdf.geometry.geom_type.value_counts().to_dict()
}
self.db_metrics["spatial_index"] = result
return result
# Usage examples
def example_geospatial_development():
"""Example geospatial development usage"""
# Create spatial data processor
processor = SpatialDataProcessor(crs="EPSG:4326")
# Create sample geometries
point1 = processor.create_point(-74.0, 40.7) # New York
point2 = processor.create_point(-73.9, 40.8) # Nearby point
polygon_coords = [(-74.1, 40.6), (-73.9, 40.6), (-73.9, 40.8), (-74.1, 40.8), (-74.1, 40.6)]
polygon = processor.create_polygon(polygon_coords)
# Calculate distance
distance = processor.calculate_distance(point1, point2, unit="meters")
print(f"Distance between points: {distance:.2f} meters")
# Calculate area
area = processor.calculate_area(polygon, unit="square_kilometers")
print(f"Polygon area: {area:.6f} square kilometers")
# Create GeoDataFrame
data = {
'name': ['Point 1', 'Point 2', 'Polygon 1'],
'value': [10, 20, 30]
}
geometries = [point1, point2, polygon]
gdf = gpd.GeoDataFrame(data, geometry=geometries, crs="EPSG:4326")
print(f"GeoDataFrame created with {len(gdf)} features")
# Spatial operations
spatial_ops = SpatialOperations()
# Buffer analysis
buffered_points = spatial_ops.buffer_analysis([point1, point2], 1000) # 1km buffer
print(f"Created buffers for {len(buffered_points)} points")
# Intersection analysis
intersection = spatial_ops.intersection_analysis(point1, polygon)
print(f"Point intersects polygon: {intersection['intersects']}")
# Union analysis
union_result = spatial_ops.union_analysis(buffered_points)
print(f"Union result type: {union_result.geom_type}")
# Visualization
visualizer = GeospatialVisualizer()
# Create interactive map
folium_map = visualizer.create_interactive_map(gdf)
print("Interactive map created")
# Create Plotly map
plotly_fig = visualizer.create_plotly_map(gdf, color_column='value')
print("Plotly map created")
# Spatial database operations
spatial_db = SpatialDatabase("postgresql://user:pass@localhost/db")
# Create spatial table
table_created = spatial_db.create_spatial_table("spatial_features")
print(f"Spatial table created: {table_created}")
# Insert data
data_inserted = spatial_db.insert_spatial_data("spatial_features", gdf)
print(f"Data inserted: {data_inserted}")
# Spatial index analysis
index_analysis = spatial_db.spatial_index_analysis(gdf)
print(f"Spatial index analysis: {index_analysis}")
Geospatial Analysis¶
# python/02-geospatial-analysis.py
"""
Advanced geospatial analysis patterns and workflows
"""
from typing import List, Dict, Any, Optional, Union, Tuple
import geopandas as gpd
import pandas as pd
import numpy as np
from shapely.geometry import Point, Polygon, LineString
from shapely.ops import nearest_points
from scipy.spatial import cKDTree
from sklearn.cluster import DBSCAN
from sklearn.neighbors import NearestNeighbors
import folium
from folium import plugins
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import logging
logger = logging.getLogger(__name__)
class SpatialAnalysis:
"""Advanced spatial analysis utilities"""
def __init__(self):
self.analysis_metrics = {}
def point_in_polygon_analysis(self, points: gpd.GeoDataFrame,
polygons: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
"""Analyze which points are inside which polygons"""
# Perform spatial join
result = gpd.sjoin(points, polygons, how='left', predicate='within')
self.analysis_metrics["point_in_polygon"] = {
"total_points": len(points),
"total_polygons": len(polygons),
"points_in_polygons": len(result.dropna(subset=['index_right'])),
"coverage_percentage": (len(result.dropna(subset=['index_right'])) / len(points)) * 100
}
return result
def spatial_clustering(self, points: gpd.GeoDataFrame,
algorithm: str = "dbscan",
**kwargs) -> gpd.GeoDataFrame:
"""Perform spatial clustering on points"""
# Extract coordinates
coords = np.array([[point.x, point.y] for point in points.geometry])
if algorithm == "dbscan":
eps = kwargs.get('eps', 0.01)
min_samples = kwargs.get('min_samples', 5)
clustering = DBSCAN(eps=eps, min_samples=min_samples)
cluster_labels = clustering.fit_predict(coords)
else:
raise ValueError(f"Unsupported clustering algorithm: {algorithm}")
# Add cluster labels to GeoDataFrame
points_with_clusters = points.copy()
points_with_clusters['cluster'] = cluster_labels
# Calculate cluster statistics
n_clusters = len(set(cluster_labels)) - (1 if -1 in cluster_labels else 0)
n_noise = list(cluster_labels).count(-1)
self.analysis_metrics["spatial_clustering"] = {
"algorithm": algorithm,
"n_clusters": n_clusters,
"n_noise": n_noise,
"total_points": len(points),
"clustered_points": len(points) - n_noise
}
return points_with_clusters
def spatial_density_analysis(self, points: gpd.GeoDataFrame,
cell_size: float = 0.01) -> gpd.GeoDataFrame:
"""Analyze spatial density using grid cells"""
# Create grid
bounds = points.total_bounds
x_min, y_min, x_max, y_max = bounds
# Create grid cells
grid_cells = []
x_coords = np.arange(x_min, x_max, cell_size)
y_coords = np.arange(y_min, y_max, cell_size)
for x in x_coords:
for y in y_coords:
cell = Polygon([
(x, y), (x + cell_size, y),
(x + cell_size, y + cell_size), (x, y + cell_size)
])
grid_cells.append(cell)
# Create grid GeoDataFrame
grid_gdf = gpd.GeoDataFrame(geometry=grid_cells, crs=points.crs)
# Count points in each cell
spatial_join = gpd.sjoin(grid_gdf, points, how='left', predicate='contains')
point_counts = spatial_join.groupby(spatial_join.index).size()
# Add density information
grid_gdf['point_count'] = point_counts.reindex(grid_gdf.index, fill_value=0)
grid_gdf['density'] = grid_gdf['point_count'] / (cell_size ** 2)
self.analysis_metrics["density_analysis"] = {
"grid_cells": len(grid_gdf),
"cell_size": cell_size,
"max_density": grid_gdf['density'].max(),
"mean_density": grid_gdf['density'].mean(),
"high_density_cells": len(grid_gdf[grid_gdf['density'] > grid_gdf['density'].quantile(0.9)])
}
return grid_gdf
def spatial_autocorrelation(self, gdf: gpd.GeoDataFrame,
value_column: str) -> Dict[str, Any]:
"""Calculate spatial autocorrelation (Moran's I)"""
from libpysal.weights import Queen
from esda.moran import Moran
# Create spatial weights
w = Queen.from_dataframe(gdf)
# Calculate Moran's I
moran = Moran(gdf[value_column], w)
result = {
"morans_i": moran.I,
"p_value": moran.p_norm,
"z_score": moran.z_norm,
"significant": moran.p_norm < 0.05,
"interpretation": "positive" if moran.I > 0 else "negative"
}
self.analysis_metrics["spatial_autocorrelation"] = result
return result
def spatial_interpolation(self, points: gpd.GeoDataFrame,
value_column: str,
method: str = "idw",
grid_resolution: int = 100) -> gpd.GeoDataFrame:
"""Perform spatial interpolation"""
from scipy.interpolate import griddata
# Extract coordinates and values
coords = np.array([[point.x, point.y] for point in points.geometry])
values = points[value_column].values
# Create interpolation grid
bounds = points.total_bounds
x_min, y_min, x_max, y_max = bounds
x_grid = np.linspace(x_min, x_max, grid_resolution)
y_grid = np.linspace(y_min, y_max, grid_resolution)
X_grid, Y_grid = np.meshgrid(x_grid, y_grid)
# Perform interpolation
if method == "idw":
# Inverse Distance Weighting
grid_points = np.column_stack([X_grid.ravel(), Y_grid.ravel()])
interpolated_values = griddata(coords, values, grid_points, method='cubic')
interpolated_values = interpolated_values.reshape(X_grid.shape)
# Create result GeoDataFrame
result_data = []
for i in range(grid_resolution):
for j in range(grid_resolution):
if not np.isnan(interpolated_values[i, j]):
point = Point(x_grid[j], y_grid[i])
result_data.append({
'geometry': point,
'interpolated_value': interpolated_values[i, j]
})
result_gdf = gpd.GeoDataFrame(result_data, crs=points.crs)
self.analysis_metrics["spatial_interpolation"] = {
"method": method,
"grid_resolution": grid_resolution,
"interpolated_points": len(result_gdf),
"min_value": result_gdf['interpolated_value'].min(),
"max_value": result_gdf['interpolated_value'].max()
}
return result_gdf
class NetworkAnalysis:
"""Network analysis utilities"""
def __init__(self):
self.network_metrics = {}
def create_network_graph(self, edges: gpd.GeoDataFrame,
nodes: gpd.GeoDataFrame = None) -> Dict[str, Any]:
"""Create network graph from edges and nodes"""
import networkx as nx
# Create graph
G = nx.Graph()
# Add nodes
if nodes is not None:
for idx, node in nodes.iterrows():
G.add_node(idx, **node.drop('geometry').to_dict())
else:
# Extract nodes from edges
all_nodes = set()
for edge in edges.geometry:
if edge.geom_type == "LineString":
all_nodes.update(edge.coords)
for i, node_coords in enumerate(all_nodes):
G.add_node(i, x=node_coords[0], y=node_coords[1])
# Add edges
for idx, edge in edges.iterrows():
if edge.geometry.geom_type == "LineString":
coords = list(edge.geometry.coords)
for i in range(len(coords) - 1):
start_node = coords[i]
end_node = coords[i + 1]
# Find closest nodes in graph
start_idx = self._find_closest_node(G, start_node)
end_idx = self._find_closest_node(G, end_node)
if start_idx != end_idx:
G.add_edge(start_idx, end_idx, **edge.drop('geometry').to_dict())
self.network_metrics["network_graph"] = {
"nodes": G.number_of_nodes(),
"edges": G.number_of_edges(),
"density": nx.density(G),
"connected_components": nx.number_connected_components(G)
}
return {"graph": G, "metrics": self.network_metrics["network_graph"]}
def _find_closest_node(self, G, coords: Tuple[float, float],
tolerance: float = 0.001) -> int:
"""Find closest node to given coordinates"""
min_distance = float('inf')
closest_node = None
for node_id, node_data in G.nodes(data=True):
if 'x' in node_data and 'y' in node_data:
distance = np.sqrt((node_data['x'] - coords[0])**2 + (node_data['y'] - coords[1])**2)
if distance < min_distance and distance < tolerance:
min_distance = distance
closest_node = node_id
if closest_node is None:
# Create new node
new_node_id = G.number_of_nodes()
G.add_node(new_node_id, x=coords[0], y=coords[1])
return new_node_id
return closest_node
def shortest_path_analysis(self, G, start_node: int, end_node: int) -> Dict[str, Any]:
"""Find shortest path between nodes"""
import networkx as nx
try:
path = nx.shortest_path(G, start_node, end_node)
path_length = nx.shortest_path_length(G, start_node, end_node)
result = {
"path": path,
"path_length": path_length,
"path_exists": True
}
except nx.NetworkXNoPath:
result = {
"path": [],
"path_length": float('inf'),
"path_exists": False
}
return result
def centrality_analysis(self, G) -> Dict[str, Any]:
"""Calculate network centrality measures"""
import networkx as nx
# Calculate centrality measures
degree_centrality = nx.degree_centrality(G)
betweenness_centrality = nx.betweenness_centrality(G)
closeness_centrality = nx.closeness_centrality(G)
result = {
"degree_centrality": degree_centrality,
"betweenness_centrality": betweenness_centrality,
"closeness_centrality": closeness_centrality,
"most_central_node": max(degree_centrality, key=degree_centrality.get),
"network_diameter": nx.diameter(G) if nx.is_connected(G) else None
}
return result
class GeospatialWorkflow:
"""Complete geospatial workflow"""
def __init__(self):
self.workflow_metrics = {}
self.spatial_processor = SpatialDataProcessor()
self.spatial_ops = SpatialOperations()
self.spatial_analysis = SpatialAnalysis()
self.network_analysis = NetworkAnalysis()
def complete_analysis(self, points: gpd.GeoDataFrame,
polygons: gpd.GeoDataFrame = None,
edges: gpd.GeoDataFrame = None) -> Dict[str, Any]:
"""Perform complete geospatial analysis"""
results = {}
# Basic spatial analysis
if polygons is not None:
pip_result = self.spatial_analysis.point_in_polygon_analysis(points, polygons)
results["point_in_polygon"] = pip_result
# Spatial clustering
clustered_points = self.spatial_analysis.spatial_clustering(points)
results["spatial_clustering"] = clustered_points
# Density analysis
density_grid = self.spatial_analysis.spatial_density_analysis(points)
results["density_analysis"] = density_grid
# Spatial interpolation
if len(points) > 1:
value_column = points.select_dtypes(include=[np.number]).columns[0]
interpolated = self.spatial_analysis.spatial_interpolation(points, value_column)
results["spatial_interpolation"] = interpolated
# Network analysis
if edges is not None:
network_result = self.network_analysis.create_network_graph(edges)
results["network_analysis"] = network_result
# Compile metrics
self.workflow_metrics = {
"input_points": len(points),
"input_polygons": len(polygons) if polygons is not None else 0,
"input_edges": len(edges) if edges is not None else 0,
"analysis_completed": list(results.keys())
}
return results
def export_results(self, results: Dict[str, Any], output_dir: str) -> bool:
"""Export analysis results"""
import os
try:
os.makedirs(output_dir, exist_ok=True)
for analysis_name, result in results.items():
if isinstance(result, gpd.GeoDataFrame):
result.to_file(f"{output_dir}/{analysis_name}.geojson", driver="GeoJSON")
elif isinstance(result, dict) and "graph" in result:
# Export network graph
import networkx as nx
nx.write_gml(result["graph"], f"{output_dir}/{analysis_name}_network.gml")
return True
except Exception as e:
logger.error(f"Failed to export results: {e}")
return False
# Usage examples
def example_geospatial_analysis():
"""Example geospatial analysis usage"""
# Create sample data
np.random.seed(42)
# Create random points
n_points = 100
points_data = {
'id': range(n_points),
'value': np.random.normal(50, 15, n_points),
'category': np.random.choice(['A', 'B', 'C'], n_points)
}
# Create random point geometries
points_geometry = [Point(np.random.uniform(-74.1, -73.9),
np.random.uniform(40.6, 40.8)) for _ in range(n_points)]
points_gdf = gpd.GeoDataFrame(points_data, geometry=points_geometry, crs="EPSG:4326")
# Create sample polygons
polygon_coords = [
[(-74.05, 40.65), (-73.95, 40.65), (-73.95, 40.75), (-74.05, 40.75), (-74.05, 40.65)],
[(-73.9, 40.7), (-73.8, 40.7), (-73.8, 40.8), (-73.9, 40.8), (-73.9, 40.7)]
]
polygons_data = {'id': [1, 2], 'name': ['Area 1', 'Area 2']}
polygons_geometry = [Polygon(coords) for coords in polygon_coords]
polygons_gdf = gpd.GeoDataFrame(polygons_data, geometry=polygons_geometry, crs="EPSG:4326")
# Perform spatial analysis
spatial_analysis = SpatialAnalysis()
# Point in polygon analysis
pip_result = spatial_analysis.point_in_polygon_analysis(points_gdf, polygons_gdf)
print(f"Points in polygons: {len(pip_result.dropna(subset=['index_right']))}")
# Spatial clustering
clustered_points = spatial_analysis.spatial_clustering(points_gdf, algorithm="dbscan", eps=0.01)
print(f"Clusters found: {clustered_points['cluster'].nunique()}")
# Density analysis
density_grid = spatial_analysis.spatial_density_analysis(points_gdf, cell_size=0.01)
print(f"Density grid created with {len(density_grid)} cells")
# Spatial interpolation
interpolated = spatial_analysis.spatial_interpolation(points_gdf, 'value')
print(f"Interpolated surface created with {len(interpolated)} points")
# Network analysis
network_analysis = NetworkAnalysis()
# Create sample edges
edge_coords = [
LineString([(-74.0, 40.7), (-73.9, 40.7)]),
LineString([(-73.9, 40.7), (-73.8, 40.8)]),
LineString([(-74.0, 40.7), (-74.0, 40.8)])
]
edges_data = {'id': [1, 2, 3], 'weight': [1, 2, 1]}
edges_gdf = gpd.GeoDataFrame(edges_data, geometry=edge_coords, crs="EPSG:4326")
# Create network graph
network_result = network_analysis.create_network_graph(edges_gdf)
print(f"Network created with {network_result['metrics']['nodes']} nodes and {network_result['metrics']['edges']} edges")
# Complete workflow
workflow = GeospatialWorkflow()
complete_results = workflow.complete_analysis(points_gdf, polygons_gdf, edges_gdf)
print(f"Complete analysis completed: {list(complete_results.keys())}")
# Export results
export_success = workflow.export_results(complete_results, "geospatial_output")
print(f"Results exported: {export_success}")
TL;DR Runbook¶
Quick Start¶
# 1. Spatial data processing
processor = SpatialDataProcessor(crs="EPSG:4326")
point = processor.create_point(-74.0, 40.7)
polygon = processor.create_polygon([(-74.1, 40.6), (-73.9, 40.6), (-73.9, 40.8), (-74.1, 40.8)])
# 2. Spatial operations
spatial_ops = SpatialOperations()
buffered = spatial_ops.buffer_analysis([point], 1000)
intersection = spatial_ops.intersection_analysis(point, polygon)
# 3. Spatial analysis
spatial_analysis = SpatialAnalysis()
clustered = spatial_analysis.spatial_clustering(points_gdf)
density_grid = spatial_analysis.spatial_density_analysis(points_gdf)
# 4. Visualization
visualizer = GeospatialVisualizer()
folium_map = visualizer.create_interactive_map(gdf)
plotly_fig = visualizer.create_plotly_map(gdf, color_column='value')
# 5. Complete workflow
workflow = GeospatialWorkflow()
results = workflow.complete_analysis(points_gdf, polygons_gdf, edges_gdf)
Essential Patterns¶
# Complete geospatial setup
def setup_geospatial_development():
"""Setup complete geospatial development environment"""
# Spatial data processor
processor = SpatialDataProcessor()
# Spatial operations
spatial_ops = SpatialOperations()
# Spatial analysis
spatial_analysis = SpatialAnalysis()
# Network analysis
network_analysis = NetworkAnalysis()
# Geospatial visualizer
visualizer = GeospatialVisualizer()
# Spatial database
spatial_db = SpatialDatabase("postgresql://user:pass@localhost/db")
# Complete workflow
workflow = GeospatialWorkflow()
print("Geospatial development setup complete!")
This guide provides the complete machinery for Python geospatial development. Each pattern includes implementation examples, spatial analysis strategies, and real-world usage patterns for enterprise geospatial management.