# python/01-spatial-data-structures.py"""Spatial data structures and geometric operations"""fromtypingimportList,Dict,Any,Optional,Union,Tuplefromdataclassesimportdataclass,asdictfromenumimportEnumimportgeopandasasgpdimportpandasaspdimportnumpyasnpfromshapely.geometryimportPoint,Polygon,LineString,MultiPolygonfromshapely.opsimportunary_union,cascaded_unionfromshapely.affinityimporttranslate,rotate,scalefromshapely.validationimportmake_validimportpyprojfrompyprojimportCRS,Transformerimportfoliumimportplotly.expressaspximportplotly.graph_objectsasgofromdatetimeimportdatetime,timedeltaimportlogging# Configure logginglogging.basicConfig(level=logging.INFO)logger=logging.getLogger(__name__)classGeometryType(Enum):"""Geometry type enumeration"""POINT="Point"LINESTRING="LineString"POLYGON="Polygon"MULTIPOINT="MultiPoint"MULTILINESTRING="MultiLineString"MULTIPOLYGON="MultiPolygon"classSpatialDataProcessor:"""Spatial data processing utilities"""def__init__(self,crs:str="EPSG:4326"):self.crs=CRS.from_string(crs)self.processor_metrics={}defcreate_point(self,x:float,y:float,crs:str=None)->Point:"""Create a point geometry"""point=Point(x,y)ifcrsandcrs!=self.crs.to_string():# Transform to target CRStransformer=Transformer.from_crs(self.crs,CRS.from_string(crs))x_transformed,y_transformed=transformer.transform(x,y)point=Point(x_transformed,y_transformed)returnpointdefcreate_polygon(self,coordinates:List[Tuple[float,float]],crs:str=None)->Polygon:"""Create a polygon geometry"""polygon=Polygon(coordinates)ifcrsandcrs!=self.crs.to_string():# Transform coordinatestransformer=Transformer.from_crs(self.crs,CRS.from_string(crs))transformed_coords=[transformer.transform(x,y)forx,yincoordinates]polygon=Polygon(transformed_coords)returnpolygondefcreate_linestring(self,coordinates:List[Tuple[float,float]],crs:str=None)->LineString:"""Create a linestring geometry"""linestring=LineString(coordinates)ifcrsandcrs!=self.crs.to_string():# Transform coordinatestransformer=Transformer.from_crs(self.crs,CRS.from_string(crs))transformed_coords=[transformer.transform(x,y)forx,yincoordinates]linestring=LineString(transformed_coords)returnlinestringdeftransform_geometry(self,geometry,from_crs:str,to_crs:str)->Any:"""Transform geometry between coordinate systems"""transformer=Transformer.from_crs(from_crs,to_crs)ifgeometry.geom_type=="Point":x,y=transformer.transform(geometry.x,geometry.y)returnPoint(x,y)elifgeometry.geom_type=="Polygon":coords=list(geometry.exterior.coords)transformed_coords=[transformer.transform(x,y)forx,yincoords]returnPolygon(transformed_coords)else:# Handle other geometry typesreturngeometrydefcalculate_distance(self,geom1:Any,geom2:Any,unit:str="meters")->float:"""Calculate distance between geometries"""# Ensure geometries are in projected CRS for accurate distance calculationifself.crs.is_geographic:# Transform to UTM for distance calculationutm_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=geom1geom2_utm=geom2distance=geom1_utm.distance(geom2_utm)ifunit=="kilometers":returndistance/1000elifunit=="miles":returndistance/1609.34else:returndistancedefcalculate_area(self,geometry:Any,unit:str="square_meters")->float:"""Calculate area of geometry"""ifgeometry.geom_typenotin["Polygon","MultiPolygon"]:return0.0# Ensure geometry is in projected CRS for accurate area calculationifself.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=geometryarea=geometry_projected.areaifunit=="square_kilometers":returnarea/1_000_000elifunit=="acres":returnarea/4046.86else:returnareadefcalculate_bounds(self,geometries:List[Any])->Tuple[float,float,float,float]:"""Calculate bounding box for list of geometries"""ifnotgeometries:return(0,0,0,0)# Get bounds for each geometrybounds_list=[geom.boundsforgeomingeometries]# Calculate overall boundsminx=min(bounds[0]forboundsinbounds_list)miny=min(bounds[1]forboundsinbounds_list)maxx=max(bounds[2]forboundsinbounds_list)maxy=max(bounds[3]forboundsinbounds_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)+1hemisphere="north"iflat>=0else"south"returnf"EPSG:{32600+utm_zoneifhemisphere=='north'else32700+utm_zone}"classSpatialOperations:"""Spatial operations and analysis"""def__init__(self):self.operation_metrics={}defspatial_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}returnresultdefbuffer_analysis(self,geometries:List[Any],buffer_distance:float,crs:str=None)->List[Any]:"""Create buffers around geometries"""ifcrs:# Transform to projected CRS for accurate bufferingtransformer=Transformer.from_crs("EPSG:4326",crs)buffered_geometries=[]forgeomingeometries:ifgeom.geom_type=="Point":x,y=transformer.transform(geom.x,geom.y)geom_projected=Point(x,y)else:# Handle other geometry typesgeom_projected=geombuffered=geom_projected.buffer(buffer_distance)buffered_geometries.append(buffered)returnbuffered_geometrieselse:return[geom.buffer(buffer_distance)forgeomingeometries]defintersection_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.areaifhasattr(intersection,'area')else0,"intersection_type":intersection.geom_typeifhasattr(intersection,'geom_type')elseNone}returnresultdefunion_analysis(self,geometries:List[Any])->Any:"""Perform union of multiple geometries"""ifnotgeometries:returnNoneiflen(geometries)==1:returngeometries[0]# Use unary_union for efficient union operationunion_result=unary_union(geometries)self.operation_metrics["union"]={"input_geometries":len(geometries),"result_geometry_type":union_result.geom_type,"result_area":union_result.areaifhasattr(union_result,'area')else0}returnunion_resultdefclip_analysis(self,target_geom:Any,clip_geom:Any)->Any:"""Clip target geometry with clip geometry"""ifnottarget_geom.intersects(clip_geom):returnNoneclipped=target_geom.intersection(clip_geom)self.operation_metrics["clip"]={"target_area":target_geom.areaifhasattr(target_geom,'area')else0,"clip_area":clip_geom.areaifhasattr(clip_geom,'area')else0,"result_area":clipped.areaifhasattr(clipped,'area')else0}returnclippeddefnearest_neighbor_analysis(self,points:List[Point],target_points:List[Point])->List[Dict[str,Any]]:"""Find nearest neighbors between point sets"""results=[]forpointinpoints:distances=[point.distance(target)fortargetintarget_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})returnresultsclassGeospatialVisualizer:"""Geospatial visualization utilities"""def__init__(self):self.visualization_metrics={}defcreate_interactive_map(self,gdf:gpd.GeoDataFrame,center:Tuple[float,float]=None,zoom_start:int=10)->folium.Map:"""Create interactive Folium map"""ifcenterisNone:# Calculate center from databounds=gdf.total_boundscenter=((bounds[1]+bounds[3])/2,(bounds[0]+bounds[2])/2)m=folium.Map(location=center,zoom_start=zoom_start)# Add geometries to mapforidx,rowingdf.iterrows():ifrow.geometry.geom_type=="Point":folium.Marker(location=[row.geometry.y,row.geometry.x],popup=f"Feature {idx}").add_to(m)elifrow.geometry.geom_typein["Polygon","MultiPolygon"]:folium.GeoJson(row.geometry.__geo_interface__,popup=f"Feature {idx}").add_to(m)returnmdefcreate_plotly_map(self,gdf:gpd.GeoDataFrame,color_column:str=None,size_column:str=None)->go.Figure:"""Create Plotly map visualization"""ifcolor_columnisNone:color_column=gdf.columns[0]iflen(gdf.columns)>1elseNone# Convert geometries to coordinateslats,lons=[],[]colors,sizes=[],[]foridx,rowingdf.iterrows():ifrow.geometry.geom_type=="Point":lats.append(row.geometry.y)lons.append(row.geometry.x)colors.append(row[color_column]ifcolor_columnelse"blue")sizes.append(row[size_column]ifsize_columnelse10)elifrow.geometry.geom_typein["Polygon","MultiPolygon"]:# Extract polygon coordinatesifrow.geometry.geom_type=="Polygon":coords=list(row.geometry.exterior.coords)else:coords=[]forpolyinrow.geometry.geoms:coords.extend(list(poly.exterior.coords))ifcoords:lats.extend([coord[1]forcoordincoords])lons.extend([coord[0]forcoordincoords])colors.extend([row[color_column]ifcolor_columnelse"blue"]*len(coords))sizes.extend([row[size_column]ifsize_columnelse10]*len(coords))fig=go.Figure()ifcolor_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")returnfigdefcreate_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))returnfigclassSpatialDatabase:"""Spatial database operations"""def__init__(self,connection_string:str):self.connection_string=connection_stringself.db_metrics={}defcreate_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 successreturnTruedefinsert_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 successself.db_metrics["inserted_features"]=len(gdf)returnTruedefspatial_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 GeoDataFramereturngpd.GeoDataFrame()defspatial_index_analysis(self,gdf:gpd.GeoDataFrame)->Dict[str,Any]:"""Analyze spatial index performance"""# Calculate spatial index statisticsbounds=gdf.total_boundsarea=(bounds[2]-bounds[0])*(bounds[3]-bounds[1])result={"total_features":len(gdf),"bounds":bounds,"area":area,"density":len(gdf)/areaifarea>0else0,"geometry_types":gdf.geometry.geom_type.value_counts().to_dict()}self.db_metrics["spatial_index"]=resultreturnresult# Usage examplesdefexample_geospatial_development():"""Example geospatial development usage"""# Create spatial data processorprocessor=SpatialDataProcessor(crs="EPSG:4326")# Create sample geometriespoint1=processor.create_point(-74.0,40.7)# New Yorkpoint2=processor.create_point(-73.9,40.8)# Nearby pointpolygon_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 distancedistance=processor.calculate_distance(point1,point2,unit="meters")print(f"Distance between points: {distance:.2f} meters")# Calculate areaarea=processor.calculate_area(polygon,unit="square_kilometers")print(f"Polygon area: {area:.6f} square kilometers")# Create GeoDataFramedata={'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 operationsspatial_ops=SpatialOperations()# Buffer analysisbuffered_points=spatial_ops.buffer_analysis([point1,point2],1000)# 1km bufferprint(f"Created buffers for {len(buffered_points)} points")# Intersection analysisintersection=spatial_ops.intersection_analysis(point1,polygon)print(f"Point intersects polygon: {intersection['intersects']}")# Union analysisunion_result=spatial_ops.union_analysis(buffered_points)print(f"Union result type: {union_result.geom_type}")# Visualizationvisualizer=GeospatialVisualizer()# Create interactive mapfolium_map=visualizer.create_interactive_map(gdf)print("Interactive map created")# Create Plotly mapplotly_fig=visualizer.create_plotly_map(gdf,color_column='value')print("Plotly map created")# Spatial database operationsspatial_db=SpatialDatabase("postgresql://user:pass@localhost/db")# Create spatial tabletable_created=spatial_db.create_spatial_table("spatial_features")print(f"Spatial table created: {table_created}")# Insert datadata_inserted=spatial_db.insert_spatial_data("spatial_features",gdf)print(f"Data inserted: {data_inserted}")# Spatial index analysisindex_analysis=spatial_db.spatial_index_analysis(gdf)print(f"Spatial index analysis: {index_analysis}")