KML_new / app.py
khushidhar1210's picture
Update app.py
b4a6f1f verified
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.")