Geoeasy commited on
Commit
398be62
·
verified ·
1 Parent(s): 7729862

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +222 -0
app.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import zipfile
5
+ import uuid
6
+ import rasterio
7
+ import numpy as np
8
+ from rasterio.mask import mask
9
+ import geopandas as gpd
10
+ import matplotlib.pyplot as plt
11
+ from io import BytesIO
12
+ from PIL import Image
13
+ import shutil
14
+
15
+
16
+ def clip_geotiff(tif_file, shapefile_zip):
17
+ """
18
+ Clips a GeoTIFF file using a shapefile
19
+ """
20
+ temp_dir = None
21
+ try:
22
+ # Check if files were provided
23
+ if tif_file is None or shapefile_zip is None:
24
+ return None, None
25
+
26
+ # Create unique temporary directory
27
+ temp_dir = tempfile.mkdtemp(prefix="geotiff_clip_")
28
+ img_dir = os.path.join(temp_dir, "image")
29
+ shp_dir = os.path.join(temp_dir, "shapefile")
30
+ out_dir = os.path.join(temp_dir, "output")
31
+
32
+ # Create subdirectories
33
+ for directory in [img_dir, shp_dir, out_dir]:
34
+ os.makedirs(directory, exist_ok=True)
35
+
36
+ # Handle TIFF file - tif_file is now a file path (string)
37
+ tif_path = os.path.join(img_dir, f"input_{uuid.uuid4().hex}.tif")
38
+ shutil.copy2(tif_file, tif_path)
39
+
40
+ # Handle shapefile ZIP - shapefile_zip is now a file path (string)
41
+ zip_path = os.path.join(shp_dir, f"shapefile_{uuid.uuid4().hex}.zip")
42
+ shutil.copy2(shapefile_zip, zip_path)
43
+
44
+ # Extract ZIP
45
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
46
+ zip_ref.extractall(shp_dir)
47
+
48
+ # Find .shp file
49
+ shp_files = [f for f in os.listdir(shp_dir) if f.endswith(".shp")]
50
+ if not shp_files:
51
+ raise FileNotFoundError(".shp file not found in the provided ZIP.")
52
+
53
+ shp_file = os.path.join(shp_dir, shp_files[0])
54
+
55
+ # Read shapefile using geopandas
56
+ gdf = gpd.read_file(shp_file)
57
+
58
+ # Check if shapefile has valid geometries
59
+ if gdf.empty or gdf.geometry.isna().all():
60
+ raise ValueError("Shapefile does not contain valid geometries.")
61
+
62
+ # Open and process GeoTIFF file
63
+ with rasterio.open(tif_path) as src:
64
+ # Check for overlap between shapefile and raster
65
+ gdf_proj = gdf.to_crs(src.crs)
66
+
67
+ # Perform clipping
68
+ out_image, out_transform = mask(src, gdf_proj.geometry, crop=True, nodata=src.nodata)
69
+
70
+ # Update metadata
71
+ out_meta = src.meta.copy()
72
+ out_meta.update({
73
+ "height": out_image.shape[1],
74
+ "width": out_image.shape[2],
75
+ "transform": out_transform,
76
+ "nodata": src.nodata
77
+ })
78
+
79
+ # Save clipped GeoTIFF
80
+ output_filename = f"clipped_{uuid.uuid4().hex}.tif"
81
+ output_tif_path = os.path.join(out_dir, output_filename)
82
+
83
+ with rasterio.open(output_tif_path, "w", **out_meta) as dest:
84
+ dest.write(out_image)
85
+
86
+ # Create PNG visualization in memory
87
+ # Prepare data for visualization
88
+ if out_image.shape[0] >= 3:
89
+ # If has 3 or more bands, use first 3 (RGB)
90
+ preview_array = out_image[:3]
91
+ else:
92
+ # If has less than 3 bands, repeat first band
93
+ preview_array = np.repeat(out_image[0:1], 3, axis=0)
94
+
95
+ # Rearrange dimensions (bands, height, width) -> (height, width, bands)
96
+ preview_array = np.moveaxis(preview_array, 0, -1)
97
+
98
+ # Normalize values to 0-255 if necessary
99
+ if preview_array.dtype != np.uint8:
100
+ # Normalize to 0-255
101
+ preview_min = np.nanmin(preview_array)
102
+ preview_max = np.nanmax(preview_array)
103
+ if preview_max > preview_min:
104
+ preview_array = ((preview_array - preview_min) / (preview_max - preview_min) * 255).astype(np.uint8)
105
+ else:
106
+ preview_array = np.zeros_like(preview_array, dtype=np.uint8)
107
+
108
+ # Handle nodata values
109
+ if out_meta.get('nodata') is not None:
110
+ nodata_mask = np.any(out_image == out_meta['nodata'], axis=0)
111
+ preview_array[nodata_mask] = [0, 0, 0] # Black for nodata
112
+
113
+ # Create matplotlib figure
114
+ plt.style.use('default') # Ensure default style
115
+ fig, ax = plt.subplots(figsize=(10, 8), dpi=100)
116
+ ax.imshow(preview_array)
117
+ ax.set_title(f"Clipped GeoTIFF - {output_filename}", fontsize=12, pad=20)
118
+ ax.axis('off')
119
+
120
+ # Save to memory buffer
121
+ buf = BytesIO()
122
+ plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, dpi=150)
123
+ plt.close(fig) # Important: close figure to free memory
124
+ buf.seek(0)
125
+
126
+ # Convert to PIL image
127
+ pil_image = Image.open(buf).convert("RGB")
128
+ buf.close()
129
+
130
+ return pil_image, output_tif_path
131
+
132
+ except Exception as e:
133
+ error_msg = f"Error during processing: {str(e)}"
134
+ print(f"[ERROR] {error_msg}")
135
+
136
+ # Create error image
137
+ error_image = Image.new('RGB', (400, 200), color='white')
138
+ from PIL import ImageDraw, ImageFont
139
+ draw = ImageDraw.Draw(error_image)
140
+ try:
141
+ font = ImageFont.truetype("arial.ttf", 16)
142
+ except:
143
+ font = ImageFont.load_default()
144
+
145
+ draw.text((10, 10), "Processing Error:", fill='red', font=font)
146
+ draw.text((10, 40), str(e)[:50] + "..." if len(str(e)) > 50 else str(e), fill='black', font=font)
147
+
148
+ return error_image, None
149
+
150
+ finally:
151
+ # Clean up temporary directory
152
+ if temp_dir and os.path.exists(temp_dir):
153
+ try:
154
+ shutil.rmtree(temp_dir)
155
+ except:
156
+ pass # Ignore cleanup errors
157
+
158
+
159
+ # Gradio Interface
160
+ with gr.Blocks(title="GeoTIFF Clipper", theme=gr.themes.Soft()) as app:
161
+ gr.Markdown("""
162
+ # 🛰️ GeoTIFF Clipping with Shapefile
163
+
164
+ This tool allows you to clip GeoTIFF images using shapefiles as clipping masks.
165
+
166
+ **Instructions:**
167
+ 1. Upload a GeoTIFF file (.tif)
168
+ 2. Upload a ZIP file containing the shapefile (.shp, .dbf, .shx, .prj)
169
+ 3. Click "Execute Clipping"
170
+ 4. View the result and download the clipped file
171
+ """)
172
+
173
+ with gr.Row():
174
+ with gr.Column():
175
+ geotiff_input = gr.File(
176
+ label="📁 GeoTIFF File (.tif)",
177
+ file_types=[".tif", ".tiff"],
178
+ type="filepath" # Changed from "binary" to "filepath"
179
+ )
180
+ shapefile_input = gr.File(
181
+ label="📁 Shapefile ZIP (.zip)",
182
+ file_types=[".zip"],
183
+ type="filepath" # Changed from "binary" to "filepath"
184
+ )
185
+ run_button = gr.Button("🚀 Execute Clipping", variant="primary", size="lg")
186
+
187
+ with gr.Row():
188
+ with gr.Column():
189
+ preview_output = gr.Image(
190
+ label="🖼️ Result Preview",
191
+ type="pil",
192
+ height=400
193
+ )
194
+ with gr.Column():
195
+ tif_output = gr.File(
196
+ label="💾 Download Clipped GeoTIFF",
197
+ type="filepath"
198
+ )
199
+
200
+ # Connect function to button
201
+ run_button.click(
202
+ fn=clip_geotiff,
203
+ inputs=[geotiff_input, shapefile_input],
204
+ outputs=[preview_output, tif_output]
205
+ )
206
+
207
+ gr.Markdown("""
208
+ ---
209
+ **Notes:**
210
+ - The shapefile ZIP must contain at least .shp, .dbf and .shx files
211
+ - Coordinate systems will be automatically adjusted if necessary
212
+ - Preview is automatically generated for visualization
213
+ """)
214
+
215
+ # Configure for Hugging Face Spaces
216
+ if __name__ == "__main__":
217
+ app.launch(
218
+ server_name="0.0.0.0", # Required for Hugging Face Spaces
219
+ server_port=7860, # Default Hugging Face Spaces port
220
+ share=False # Don't create additional public link
221
+ )
222
+