File size: 7,163 Bytes
3894000
 
780fa43
a767f1e
b3c40c7
3894000
6f842ea
de3d1f9
3894000
de3d1f9
 
 
 
 
 
 
 
a767f1e
 
de3d1f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a767f1e
de3d1f9
a767f1e
de3d1f9
 
a767f1e
de3d1f9
ec0024a
 
 
 
a767f1e
 
de3d1f9
b3c40c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a767f1e
3894000
de3d1f9
 
 
064f2e2
de3d1f9
 
 
 
 
 
 
 
 
a767f1e
3894000
de3d1f9
 
 
 
3894000
de3d1f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3c40c7
 
 
 
 
09cddb2
de3d1f9
3894000
ec0024a
de3d1f9
3894000
 
de3d1f9
3894000
de3d1f9
 
86258b2
 
3894000
de3d1f9
ad6c4be
89a1ce6
 
de3d1f9
c3c16bf
de3d1f9
 
 
3894000
86258b2
3894000
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import gradio as gr
import geopandas as gpd
import pandas as pd
import os
import matplotlib.pyplot as plt
from shapely.geometry import shape
from shapely.ops import unary_union
#from datasets import load_dataset

#ds = load_dataset('psalama/NYC_sensitive_sites', data_files=data_files)

def process_buildings(input_gdf, sensitive_sites_gdf, default_building_height_m, multiplier_factor):
    # List to store all intersected sensitive sites
    intersected_sites = []
    
    # List to store all buffers
    buffers = []

    intersection_desc = ""
    
    # Iterate over each building in the input file
    for idx, building in input_gdf.iterrows():
        building_name = building.get('building_name', 'Unnamed building')
        
        # If the 'building_height' field exists and its value is not null or zero for this building,
        # use it as the building height. Otherwise, use the default building height provided by the user.
        if 'building_height' in building and pd.notnull(building['building_height']) and building['building_height'] != 0:
            building_height_m = building['building_height'] * 0.3048
        else:
            building_height_m = default_building_height_m

        buffer_distance_m = building_height_m * multiplier_factor

        # Convert building's geometry to EPSG:3857 for accurate meter-based distance measurement
        building_geometry = gpd.GeoSeries([building['geometry']], crs="EPSG:4326")
        building_geometry_m = building_geometry.to_crs("EPSG:3857")

        # Create a buffer around the building and convert it to a GeoDataFrame
        building_buffer = building_geometry_m.buffer(buffer_distance_m)
        building_buffer_gdf = gpd.GeoDataFrame(geometry=building_buffer, crs="EPSG:3857")
        building_buffer_gdf = building_buffer_gdf.to_crs("EPSG:4326")

        # Convert back to feet for storing and printing, rounding to the nearest foot
        building_height_ft = round(building_height_m / 0.3048)
        buffer_distance_ft = round(buffer_distance_m / 0.3048)

        # Assign additional attributes
        building_buffer_gdf['building_name'] = building_name
        building_buffer_gdf['building_height'] = building_height_ft
        building_buffer_gdf['buffer_distance'] = buffer_distance_ft

        buffers.append(building_buffer_gdf)

        # Check if the buffer intersects with any sensitive sites
        intersects = gpd.overlay(building_buffer_gdf, sensitive_sites_gdf, how='intersection')
        
        if not intersects.empty:
            building_intersect_desc = f"Building {idx} ({building_name}), height: {building_height_ft}, buffer distance: {buffer_distance_ft} is in the vicinity of a sensitive site."
            intersected_sites.append(intersects)
        else:
            building_intersect_desc = f"Building {idx} ({building_name}), height: {building_height_ft}, buffer distance: {buffer_distance_ft} is not in the vicinity of any sensitive sites."

        if intersection_desc == "":
            intersection_desc = building_intersect_desc
        else:
            intersection_desc += "\n" + building_intersect_desc
    
    return buffers, intersected_sites, intersection_desc

def get_max_extent(*gdfs):
    minx = min(gdf.total_bounds[0] for gdf in gdfs)
    miny = min(gdf.total_bounds[1] for gdf in gdfs)
    maxx = max(gdf.total_bounds[2] for gdf in gdfs)
    maxy = max(gdf.total_bounds[3] for gdf in gdfs)
    
    return minx, miny, maxx, maxy

def create_plot(filename, extent, *gdfs):
    fig, ax = plt.subplots(1, 1)
    
    colors = ['blue', 'red', 'green', 'purple', 'orange', 'yellow'] # Extend this list as needed

    for idx, gdf in enumerate(gdfs):
        gdf.plot(ax=ax, color=colors[idx % len(colors)]) # Cycle through colors

    ax.set_xlim(extent[0], extent[2])
    ax.set_ylim(extent[1], extent[3])

    plt.savefig(filename)

def ss_intersect(geojson1, ss_geoselect, multiplier_factor, default_building_height): 
    # Read the GeoJSON files
    input_gdf = gpd.read_file(geojson1.name)

    # Check that CRS is EPSG:4326
    if input_gdf.crs.to_epsg() != 4326:
        raise ValueError("Input GeoJSON files must be in CRS EPSG:4326")
    
    if ss_geoselect==0:
        sensitive_sites_gdf = gpd.read_file("sensitive_sites/NYC_Parks_Properties.geojson")
    else:
        sensitive_sites_gdf = gpd.read_file("sensitive_sites/NYC_Parks_Zones.geojson")

    default_building_height_m = default_building_height * 0.3048

    buffers, intersected_sites, intersection_desc = process_buildings(input_gdf, sensitive_sites_gdf, default_building_height_m, multiplier_factor)

    # Concatenate all buffer GeoDataFrames and save as a GeoJSON file
    buffers_gdf = pd.concat(buffers, ignore_index=True)
    buffers_gdf = buffers_gdf.to_crs("EPSG:4326")
    buffers_gdf.to_file("building_buffers.geojson", driver='GeoJSON')

    # Concatenate all intersected sensitive sites and save as a GeoJSON file
    if intersected_sites:
        intersected_sites_gdf = pd.concat(intersected_sites, ignore_index=True)
        intersected_sites_gdf = intersected_sites_gdf.to_crs("EPSG:4326")
        intersected_sites_gdf.to_file("intersected_sensitive_sites.geojson", driver='GeoJSON')
    else:
        print("No buildings are in the vicinity of any sensitive sites.")

    # Perform the union operation if there is more than one buffer
    if len(buffers) > 1:
        # Perform a unary union on the geometry column of the GeoDataFrame
        buffer_union = unary_union(buffers_gdf['geometry'])

        # Create a new GeoDataFrame from the union result
        buffer_union_gdf = gpd.GeoDataFrame(geometry=[buffer_union], crs="EPSG:4326")

        # Save the union GeoDataFrame as a GeoJSON file
        buffer_union_gdf.to_file("buffer_union.geojson", driver='GeoJSON')


    # Calculate the maximum extent
    extent = get_max_extent(input_gdf, buffers_gdf)

    # Create and save the plot
    create_plot('output_image.png', extent, buffer_union_gdf, intersected_sites_gdf, input_gdf)
            
    # Return the image
    return 'output_image.png', "building_buffers.geojson", "buffer_union.geojson", intersection_desc


iface = gr.Interface(
    fn=ss_intersect, 
    inputs=[
        gr.inputs.File(label="Building Footprints GeoJSON"),
        gr.Radio(["Parks Properties", "Park Zones"], label="Which Sensitive Sites?", info="From NYC DPR", type="index"),
        gr.inputs.Slider(minimum=0.0, maximum=10.0, default=4.3, label="Building Height Multiplier"),
        gr.inputs.Number(default=200, label="Default Building Height"), #Can I make this optional?
    ], 
    outputs=[
        gr.outputs.Image(type="pil", label="Result Image"),
        gr.outputs.File(label="Building Buffers"),
        gr.outputs.File(label="Union of Building Buffers"),
        gr.outputs.Textbox(label="Building and Sensitive Site Vicinities"),
    ],
    examples=[
        ["files/building4test.geojson", "Parks Properties", 4.3, 200],
    ],
    title="Shadow Proximity",
    description="Upload proposed building footprints in a GeoJSON file and select a numeric value to get the building proximity prediction.",
)

iface.launch()