Spaces:
Sleeping
Sleeping
thedynamicpacif
commited on
Commit
·
c99736d
1
Parent(s):
b96ab7a
Fixes map location errors
Browse filesUpdated the map's default location and added functionality to extract geographic coordinates from image metadata (EXIF, GeoTIFF) in `utils/geospatial.py` and updated map display in `static/js/map.js` to use extracted coordinates. A new GeoJSON file (`processed/4ec3250471454ef1a34bb581171d9c47.geojson`) was also generated.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: c7b687d7-8856-49d8-87a3-9d7f3f6499f6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d7727d0d-3b25-49de-9476-c76c61abfa65/e5387a4e-66a0-44e6-9128-1d268963632f.jpg
- static/js/map.js +21 -6
- utils/geospatial.py +130 -7
static/js/map.js
CHANGED
@@ -14,15 +14,30 @@ function initMap() {
|
|
14 |
map.remove();
|
15 |
}
|
16 |
|
17 |
-
// Create a new map centered on a default location
|
18 |
-
|
19 |
-
map = L.map('map').setView([41.0, -74.5], 10);
|
20 |
|
21 |
-
//
|
22 |
-
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
23 |
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
24 |
maxZoom: 19
|
25 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
// Add a scale control
|
28 |
L.control.scale().addTo(map);
|
|
|
14 |
map.remove();
|
15 |
}
|
16 |
|
17 |
+
// Create a new map centered on a default location (Central US instead of NY)
|
18 |
+
map = L.map('map').setView([33.0, -97.0], 8);
|
|
|
19 |
|
20 |
+
// Define tile layers
|
21 |
+
const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
22 |
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
23 |
maxZoom: 19
|
24 |
+
});
|
25 |
+
|
26 |
+
const satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
27 |
+
attribution: 'Imagery © Esri',
|
28 |
+
maxZoom: 19
|
29 |
+
});
|
30 |
+
|
31 |
+
// Add OpenStreetMap layer by default
|
32 |
+
osmLayer.addTo(map);
|
33 |
+
|
34 |
+
// Add layer control
|
35 |
+
const baseLayers = {
|
36 |
+
"OpenStreetMap": osmLayer,
|
37 |
+
"Satellite": satelliteLayer
|
38 |
+
};
|
39 |
+
|
40 |
+
L.control.layers(baseLayers, null, {position: 'topright'}).addTo(map);
|
41 |
|
42 |
// Add a scale control
|
43 |
L.control.scale().addTo(map);
|
utils/geospatial.py
CHANGED
@@ -9,8 +9,9 @@ import logging
|
|
9 |
import uuid
|
10 |
import numpy as np
|
11 |
import cv2
|
12 |
-
from PIL import Image
|
13 |
import json
|
|
|
14 |
from shapely.geometry import Polygon, MultiPolygon, mapping
|
15 |
from shapely import ops
|
16 |
|
@@ -157,6 +158,99 @@ def merge_nearby_polygons(polygons, distance_threshold=5.0):
|
|
157 |
else:
|
158 |
return []
|
159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
def convert_to_geojson_with_transform(polygons, image_height, image_width,
|
161 |
min_lat=None, min_lon=None, max_lat=None, max_lon=None):
|
162 |
"""
|
@@ -176,9 +270,9 @@ def convert_to_geojson_with_transform(polygons, image_height, image_width,
|
|
176 |
"""
|
177 |
# Set default geographic bounds if not provided
|
178 |
if None in (min_lon, min_lat, max_lon, max_lat):
|
179 |
-
# Default to somewhere neutral (
|
180 |
-
min_lon, min_lat = -
|
181 |
-
max_lon, max_lat = -
|
182 |
|
183 |
# Create a GeoJSON feature collection
|
184 |
geojson = {
|
@@ -255,12 +349,41 @@ def process_image_to_geojson(image_path, feature_type="buildings"):
|
|
255 |
logging.warning("No polygons found in the image after segmentation")
|
256 |
return {"type": "FeatureCollection", "features": []}
|
257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
# Convert to GeoJSON with proper transformation
|
259 |
geojson = convert_to_geojson_with_transform(
|
260 |
polygons, height, width,
|
261 |
-
|
262 |
-
|
263 |
-
max_lat=42.0, max_lon=-73.0
|
264 |
)
|
265 |
|
266 |
return geojson
|
|
|
9 |
import uuid
|
10 |
import numpy as np
|
11 |
import cv2
|
12 |
+
from PIL import Image, TiffTags, TiffImagePlugin
|
13 |
import json
|
14 |
+
import re
|
15 |
from shapely.geometry import Polygon, MultiPolygon, mapping
|
16 |
from shapely import ops
|
17 |
|
|
|
158 |
else:
|
159 |
return []
|
160 |
|
161 |
+
def extract_geo_coordinates_from_image(image_path):
|
162 |
+
"""
|
163 |
+
Extract geographic coordinates from image metadata (EXIF, GeoTIFF).
|
164 |
+
|
165 |
+
Args:
|
166 |
+
image_path (str): Path to the image file
|
167 |
+
|
168 |
+
Returns:
|
169 |
+
tuple: (min_lat, min_lon, max_lat, max_lon) or None if not found
|
170 |
+
"""
|
171 |
+
try:
|
172 |
+
img = Image.open(image_path)
|
173 |
+
|
174 |
+
# Check if it's a TIFF image with geospatial data
|
175 |
+
if img.format == 'TIFF' and hasattr(img, 'tag'):
|
176 |
+
logging.info(f"Detected TIFF image, checking for geospatial metadata")
|
177 |
+
|
178 |
+
# Try to extract ModelPixelScaleTag (33550) and ModelTiepointTag (33922)
|
179 |
+
pixel_scale_tag = None
|
180 |
+
tiepoint_tag = None
|
181 |
+
|
182 |
+
# Check for tags
|
183 |
+
for tag_id, value in img.tag.items():
|
184 |
+
tag_name = TiffTags.TAGS.get(tag_id, str(tag_id))
|
185 |
+
logging.debug(f"TIFF tag: {tag_name} ({tag_id}): {value}")
|
186 |
+
|
187 |
+
if tag_id == 33550: # ModelPixelScaleTag
|
188 |
+
pixel_scale_tag = value
|
189 |
+
elif tag_id == 33922: # ModelTiepointTag
|
190 |
+
tiepoint_tag = value
|
191 |
+
|
192 |
+
if pixel_scale_tag and tiepoint_tag:
|
193 |
+
# Extract pixel scale (x, y)
|
194 |
+
x_scale = float(pixel_scale_tag[0])
|
195 |
+
y_scale = float(pixel_scale_tag[1])
|
196 |
+
|
197 |
+
# Extract model tiepoint (raster origin)
|
198 |
+
i, j, k = float(tiepoint_tag[0]), float(tiepoint_tag[1]), float(tiepoint_tag[2])
|
199 |
+
x, y, z = float(tiepoint_tag[3]), float(tiepoint_tag[4]), float(tiepoint_tag[5])
|
200 |
+
|
201 |
+
# Calculate bounds based on image dimensions
|
202 |
+
width, height = img.size
|
203 |
+
|
204 |
+
# Calculate bounds
|
205 |
+
min_lon = x
|
206 |
+
max_lat = y
|
207 |
+
max_lon = x + width * x_scale
|
208 |
+
min_lat = y - height * y_scale
|
209 |
+
|
210 |
+
logging.info(f"Extracted geo bounds: {min_lon},{min_lat} to {max_lon},{max_lat}")
|
211 |
+
return min_lat, min_lon, max_lat, max_lon
|
212 |
+
|
213 |
+
logging.info("No valid geospatial metadata found in TIFF")
|
214 |
+
|
215 |
+
# Check for EXIF GPS data (typically in JPEG)
|
216 |
+
elif hasattr(img, '_getexif') and img._getexif():
|
217 |
+
exif = img._getexif()
|
218 |
+
if exif and 34853 in exif: # 34853 is the GPS Info tag
|
219 |
+
gps_info = exif[34853]
|
220 |
+
|
221 |
+
# Extract GPS data
|
222 |
+
if 1 in gps_info and 2 in gps_info and 3 in gps_info and 4 in gps_info:
|
223 |
+
# Latitude
|
224 |
+
lat_ref = gps_info[1] # 'N' or 'S'
|
225 |
+
lat = gps_info[2] # ((deg_num, deg_denom), (min_num, min_denom), (sec_num, sec_denom))
|
226 |
+
lat_val = lat[0][0]/lat[0][1] + lat[1][0]/(lat[1][1]*60) + lat[2][0]/(lat[2][1]*3600)
|
227 |
+
if lat_ref == 'S':
|
228 |
+
lat_val = -lat_val
|
229 |
+
|
230 |
+
# Longitude
|
231 |
+
lon_ref = gps_info[3] # 'E' or 'W'
|
232 |
+
lon = gps_info[4]
|
233 |
+
lon_val = lon[0][0]/lon[0][1] + lon[1][0]/(lon[1][1]*60) + lon[2][0]/(lon[2][1]*3600)
|
234 |
+
if lon_ref == 'W':
|
235 |
+
lon_val = -lon_val
|
236 |
+
|
237 |
+
# Create a small region around the point
|
238 |
+
delta = 0.01 # ~1km at the equator
|
239 |
+
min_lat = lat_val - delta
|
240 |
+
min_lon = lon_val - delta
|
241 |
+
max_lat = lat_val + delta
|
242 |
+
max_lon = lon_val + delta
|
243 |
+
|
244 |
+
logging.info(f"Extracted EXIF GPS bounds: {min_lon},{min_lat} to {max_lon},{max_lat}")
|
245 |
+
return min_lat, min_lon, max_lat, max_lon
|
246 |
+
|
247 |
+
logging.info("No valid GPS metadata found in EXIF")
|
248 |
+
|
249 |
+
return None
|
250 |
+
except Exception as e:
|
251 |
+
logging.error(f"Error extracting geo coordinates: {str(e)}")
|
252 |
+
return None
|
253 |
+
|
254 |
def convert_to_geojson_with_transform(polygons, image_height, image_width,
|
255 |
min_lat=None, min_lon=None, max_lat=None, max_lon=None):
|
256 |
"""
|
|
|
270 |
"""
|
271 |
# Set default geographic bounds if not provided
|
272 |
if None in (min_lon, min_lat, max_lon, max_lat):
|
273 |
+
# Default to somewhere neutral (not in New York)
|
274 |
+
min_lon, min_lat = -98.0, 32.0 # Central US
|
275 |
+
max_lon, max_lat = -96.0, 34.0
|
276 |
|
277 |
# Create a GeoJSON feature collection
|
278 |
geojson = {
|
|
|
349 |
logging.warning("No polygons found in the image after segmentation")
|
350 |
return {"type": "FeatureCollection", "features": []}
|
351 |
|
352 |
+
# Try to extract coordinates from the original image
|
353 |
+
original_image_path = None
|
354 |
+
if "_processed" in image_path:
|
355 |
+
original_image_path = image_path.replace("_processed", "")
|
356 |
+
# Try the original image path but replace the extension with common formats
|
357 |
+
if not os.path.exists(original_image_path):
|
358 |
+
base_path = original_image_path.rsplit('.', 1)[0]
|
359 |
+
for ext in ['.tif', '.tiff', '.jpg', '.jpeg', '.png']:
|
360 |
+
if os.path.exists(base_path + ext):
|
361 |
+
original_image_path = base_path + ext
|
362 |
+
break
|
363 |
+
|
364 |
+
# Extract bounds from image if possible
|
365 |
+
coords = None
|
366 |
+
if original_image_path and os.path.exists(original_image_path):
|
367 |
+
logging.info(f"Checking original image for geospatial data: {original_image_path}")
|
368 |
+
coords = extract_geo_coordinates_from_image(original_image_path)
|
369 |
+
|
370 |
+
if not coords:
|
371 |
+
logging.info("Checking processed image for geospatial data")
|
372 |
+
coords = extract_geo_coordinates_from_image(image_path)
|
373 |
+
|
374 |
+
# Use extracted coordinates or defaults
|
375 |
+
if coords:
|
376 |
+
min_lat, min_lon, max_lat, max_lon = coords
|
377 |
+
else:
|
378 |
+
logging.info("No coordinates found in image, using default location in Central US")
|
379 |
+
min_lat, min_lon = 32.0, -98.0 # Central US
|
380 |
+
max_lat, max_lon = 34.0, -96.0
|
381 |
+
|
382 |
# Convert to GeoJSON with proper transformation
|
383 |
geojson = convert_to_geojson_with_transform(
|
384 |
polygons, height, width,
|
385 |
+
min_lat=min_lat, min_lon=min_lon,
|
386 |
+
max_lat=max_lat, max_lon=max_lon
|
|
|
387 |
)
|
388 |
|
389 |
return geojson
|