import geopandas as gpd import pandas as pd from shapely.geometry import shape, Polygon, LineString, Point from shapely.ops import unary_union import xml.etree.ElementTree as ET import zipfile import os import matplotlib.pyplot as plt import streamlit as st from transformers import pipeline # For KML access, extracting 3D coordinates (Polygon Z) def p(kml_file): cont = kml_file.decode('utf-8') # Decode bytes to string k = ET.ElementTree(ET.fromstring(cont)) root = k.getroot() ns = {'kml': 'http://www.opengis.net/kml/2.2'} shapes = [] for mark in root.findall('.//kml:Placemark', ns): polygon = mark.find('.//kml:Polygon/kml:coordinates', ns) if polygon is not None: coordinates = polygon.text.strip().split() coords = [(float(lon), float(lat), float(z) if z else 0) for lon, lat, z in [coord.split(',') for coord in coordinates]] shapes.append(Polygon([coords])) # Make it a Polygon with Z line = mark.find('.//kml:LineString/kml:coordinates', ns) if line is not None: coordinates = line.text.strip().split() coords = [(float(lon), float(lat), float(z) if z else 0) for lon, lat, z in [coord.split(',') for coord in coordinates]] shapes.append(LineString(coords)) point = mark.find('.//kml:Point/kml:coordinates', ns) if point is not None: lon, lat, z = point.text.strip().split(',') shapes.append(Point(float(lon), float(lat), float(z) if z else 0)) return shapes if shapes else None # For file extraction if it is in KMZ form def ext(kmz_file): with zipfile.ZipFile(kmz_file, 'r') as zip_ref: zip_ref.extractall('temp_kml') kml_file = [f for f in os.listdir('temp_kml') if f.endswith('.kml')][0] with open(os.path.join('temp_kml', kml_file), 'rb') as f: return p(f.read()) # See if it is a kml or kmz file def choose(upf): file_bytes = upf.read() if upf.name.endswith('.kmz'): return ext(file_bytes) else: return p(file_bytes) # For file uploading st.title("Flood Zone Analysis") upf = st.file_uploader("Upload KML/KMZ file", type=['kml', 'kmz']) # Convert 2D to 3D if needed (add default Z = 0) def convert_to_3d(geom): """Convert 2D geometries to 3D by adding a Z value (default = 0)""" if geom.geom_type == 'Polygon': coords = [(x, y, 0) for x, y in geom.exterior.coords] return Polygon(coords) elif geom.geom_type == 'LineString': coords = [(x, y, 0) for x, y in geom.coords] return LineString(coords) elif geom.geom_type == 'Point': return Point(geom.x, geom.y, 0) return geom # Return unchanged if not 2D # For comparing the boundary between KML and shapefile def bound(f, gdf): if f.empty: # Handle invalid KML shapes return "Invalid KML shape or no valid polygon found.", None overlaps = [] # Save matching boundaries for kml_shape in f: intersection = gdf[gdf.intersects(kml_shape)] if not intersection.empty: overlaps.append(intersection) if not overlaps: return "Boundary doesn't match", None every_int = unary_union([geom for intersect in overlaps for geom in intersect.geometry]) return overlaps, every_int # Find common bound's Acreage and Usable Land def land(overlaps, every_int): all = pd.concat(overlaps) all['area'] = all.geometry.area all['area_acres'] = all['area'] / 4046.86 # Convert to acres fza = {zone: all[all['FLD_ZONE'] == zone]['area_acres'].sum() for zone in all['FLD_ZONE'].unique()} areas = ['A', 'AE', 'AH', 'AO', 'VE'] non = all[~all['FLD_ZONE'].isin(areas)]['area_acres'].sum() merged_area = every_int.area / 4046.86 return fza, non, merged_area # Initial summary was with GPT-2 def summ(fza, non, total_acreage): summarizer = pipeline("summarization", model="gpt2") areas = ['A', 'AE', 'AH', 'AO', 'VE'] flood_zone_summary = "\n".join([f" Zone {zone}: {fza.get(zone, 0):.2f} acres" for zone in areas]) prompt = f""" **Total Land Area**: {total_acreage:.2f} acres **Usable Area**: {non:.2f} acres **Flood-prone Zones**: {flood_zone_summary} Summarize the above given data in 2-3 sentences. """ response = summarizer(prompt, max_length=200, min_length=30, do_sample=False) return response[0]['summary_text'] if upf is not None: # Read shapefiles and convert them to 3D if needed kent = gpd.read_file("K_FLD_HAZ_AR.shp") nc = gpd.read_file("N_FLD_HAZ_AR.shp") sussex = gpd.read_file("S_FLD_HAZ_AR.shp") # Combine them dela = gpd.GeoDataFrame(pd.concat([kent, nc, sussex], ignore_index=True)) # Add Coordinate Reference System dela = dela.set_crs(kent.crs, allow_override=True) # Convert to 3D (add Z = 0 if missing) dela['geometry'] = dela['geometry'].apply(convert_to_3d) dela = dela.to_crs(epsg=3857) # Fix invalid geometries dela['geometry'] = dela['geometry'].apply(lambda x: x.buffer(0) if not x.is_valid else x) # Upload KML/KMZ file f = choose(upf) if f: # Check if KML has valid geometries kml_gdf = gpd.GeoDataFrame(geometry=f, crs="EPSG:4326") kml_gdf = kml_gdf.to_crs(epsg=3857) # Convert KML to 3D (add Z = 0 if missing) kml_gdf['geometry'] = kml_gdf['geometry'].apply(convert_to_3d) # Compare KML and Shapefile intersection, every_int = bound(kml_gdf.geometry, dela) if isinstance(intersection, str): st.write(intersection) else: flood_zone_areas, non, merged_area = land(intersection, every_int) st.write(f"Flood Zone Areas:") for zone, area in flood_zone_areas.items(): st.write(f" Zone {zone}: {area:.2f} acres") st.write(f"\nNon-Flooded Land Area: {non:.2f} acres") st.write(f"\nMerged Area of Intersected Boundary: {merged_area:.2f} acres") summary = summ(flood_zone_areas, non, merged_area) st.write(f"GPT-2 Summary: {summary}") # Show map fig, ax = plt.subplots(figsize=(10, 10)) # shapefile dela.plot(ax=ax, color='blue', alpha=0.5) # Show overlap with KML if intersection: intersection_geom = unary_union([geom for intersect in intersection for geom in intersect.geometry]) gpd.GeoDataFrame(geometry=[intersection_geom], crs=dela.crs).plot(ax=ax, color='red', alpha=0.7) # Plot the KML boundary (green color) kml_gdf.plot(ax=ax, color='green', alpha=0.3) # Display the plot st.pyplot(fig) else: st.write("No valid geometries found in the uploaded KML file.") else: st.write("Please upload a KML/KMZ file to continue.")