thedynamicpacif commited on
Commit
c99736d
·
1 Parent(s): b96ab7a

Fixes map location errors

Browse files

Updated 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

Files changed (2) hide show
  1. static/js/map.js +21 -6
  2. 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
- // Use a location outside of Manhattan
19
- map = L.map('map').setView([41.0, -74.5], 10);
20
 
21
- // Add the base tile layer (OpenStreetMap)
22
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
23
  attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
24
  maxZoom: 19
25
- }).addTo(map);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: '&copy; <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 &copy; 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 (center of Atlantic Ocean)
180
- min_lon, min_lat = -30.0, 0.0
181
- max_lon, max_lat = -20.0, 10.0
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
- # Use generic bounds away from Manhattan
262
- min_lat=40.0, min_lon=-75.0,
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