voxcity 0.3.2__py3-none-any.whl → 0.3.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of voxcity might be problematic. Click here for more details.

voxcity/file/geojson.py CHANGED
@@ -28,7 +28,7 @@ def filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices):
28
28
 
29
29
  Args:
30
30
  gdf (GeoDataFrame): Input GeoDataFrame containing building data
31
- rectangle_vertices (list): List of (lat, lon) tuples defining the bounding rectangle
31
+ rectangle_vertices (list): List of (lon, lat) tuples defining the bounding rectangle
32
32
 
33
33
  Returns:
34
34
  list: List of GeoJSON features within the bounding rectangle
@@ -43,9 +43,8 @@ def filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices):
43
43
  # Add 'confidence' column with default value
44
44
  gdf['confidence'] = -1.0
45
45
 
46
- # Convert rectangle vertices from (lat,lon) to (lon,lat) format for shapely
47
- rectangle_vertices_lonlat = [(lon, lat) for lat, lon in rectangle_vertices]
48
- rectangle_polygon = Polygon(rectangle_vertices_lonlat)
46
+ # Rectangle vertices already in (lon,lat) format for shapely
47
+ rectangle_polygon = Polygon(rectangle_vertices)
49
48
 
50
49
  # Use spatial index to efficiently filter geometries that intersect with rectangle
51
50
  gdf.sindex # Ensure spatial index is built
@@ -57,13 +56,6 @@ def filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices):
57
56
  # Delete intermediate data to save memory
58
57
  del gdf, possible_matches, precise_matches
59
58
 
60
- # Helper function to swap coordinate ordering from (lon,lat) to (lat,lon)
61
- def swap_coordinates(coords):
62
- if isinstance(coords[0][0], (float, int)):
63
- return [[lat, lon] for lon, lat in coords]
64
- else:
65
- return [swap_coordinates(ring) for ring in coords]
66
-
67
59
  # Create GeoJSON features from filtered geometries
68
60
  features = []
69
61
  for idx, row in filtered_gdf.iterrows():
@@ -78,7 +70,7 @@ def filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices):
78
70
  for polygon_coords in geom['coordinates']:
79
71
  single_geom = {
80
72
  'type': 'Polygon',
81
- 'coordinates': swap_coordinates(polygon_coords)
73
+ 'coordinates': polygon_coords
82
74
  }
83
75
  feature = {
84
76
  'type': 'Feature',
@@ -87,7 +79,6 @@ def filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices):
87
79
  }
88
80
  features.append(feature)
89
81
  elif geom['type'] == 'Polygon':
90
- geom['coordinates'] = swap_coordinates(geom['coordinates'])
91
82
  feature = {
92
83
  'type': 'Feature',
93
84
  'properties': properties,
@@ -114,7 +105,7 @@ def get_geojson_from_gpkg(gpkg_path, rectangle_vertices):
114
105
 
115
106
  Args:
116
107
  gpkg_path (str): Path to the GeoPackage file
117
- rectangle_vertices (list): List of (lat, lon) tuples defining the bounding rectangle
108
+ rectangle_vertices (list): List of (lon, lat) tuples defining the bounding rectangle
118
109
 
119
110
  Returns:
120
111
  list: List of GeoJSON features within the bounding rectangle
@@ -404,9 +395,9 @@ def extract_building_heights_from_geotiff(geotiff_path, geojson_data):
404
395
  for feature in geojson:
405
396
  if (feature['geometry']['type'] == 'Polygon') & (feature['properties']['height']<=0):
406
397
  count_0 += 1
407
- # Transform coordinates from (lat, lon) to raster CRS
398
+ # Transform coordinates from (lon, lat) to raster CRS
408
399
  coords = feature['geometry']['coordinates'][0]
409
- transformed_coords = [transformer.transform(lon, lat) for lat, lon in coords]
400
+ transformed_coords = [transformer.transform(lon, lat) for lon, lat in coords]
410
401
 
411
402
  # Create polygon in raster CRS
412
403
  polygon = shape({"type": "Polygon", "coordinates": [transformed_coords]})
@@ -447,7 +438,7 @@ def get_geojson_from_gpkg(gpkg_path, rectangle_vertices):
447
438
 
448
439
  Args:
449
440
  gpkg_path (str): Path to the GeoPackage file
450
- rectangle_vertices (list): List of (lat, lon) tuples defining the bounding rectangle
441
+ rectangle_vertices (list): List of (lon, lat) tuples defining the bounding rectangle
451
442
 
452
443
  Returns:
453
444
  list: List of GeoJSON features within the bounding rectangle
@@ -460,7 +451,7 @@ def get_geojson_from_gpkg(gpkg_path, rectangle_vertices):
460
451
 
461
452
  def swap_coordinates(features):
462
453
  """
463
- Swap coordinate ordering in GeoJSON features from (lon, lat) to (lat, lon).
454
+ Swap coordinate ordering in GeoJSON features from (lat, lon) to (lon, lat).
464
455
 
465
456
  Args:
466
457
  features (list): List of GeoJSON features to process
@@ -469,11 +460,11 @@ def swap_coordinates(features):
469
460
  for feature in features:
470
461
  if feature['geometry']['type'] == 'Polygon':
471
462
  # Swap coordinates for simple polygons
472
- new_coords = [[[lat, lon] for lon, lat in polygon] for polygon in feature['geometry']['coordinates']]
463
+ new_coords = [[[lon, lat] for lat, lon in polygon] for polygon in feature['geometry']['coordinates']]
473
464
  feature['geometry']['coordinates'] = new_coords
474
465
  elif feature['geometry']['type'] == 'MultiPolygon':
475
466
  # Swap coordinates for multi-polygons (polygons with holes)
476
- new_coords = [[[[lat, lon] for lon, lat in polygon] for polygon in multipolygon] for multipolygon in feature['geometry']['coordinates']]
467
+ new_coords = [[[[lon, lat] for lat, lon in polygon] for polygon in multipolygon] for multipolygon in feature['geometry']['coordinates']]
477
468
  feature['geometry']['coordinates'] = new_coords
478
469
 
479
470
  def save_geojson(features, save_path):
@@ -506,23 +497,89 @@ def find_building_containing_point(features, target_point):
506
497
 
507
498
  Args:
508
499
  features (list): List of GeoJSON feature dictionaries
509
- target_point (tuple): Tuple of (latitude, longitude)
500
+ target_point (tuple): Tuple of (lon, lat)
510
501
 
511
502
  Returns:
512
503
  list: List of building IDs containing the target point
513
504
  """
514
- # Create Shapely point (note coordinate order swap)
515
- point = Point(target_point[1], target_point[0])
505
+ # Create Shapely point
506
+ point = Point(target_point[0], target_point[1])
516
507
 
517
508
  id_list = []
518
509
  for feature in features:
519
510
  # Get polygon coordinates and convert to Shapely polygon
520
511
  coords = feature['geometry']['coordinates'][0]
521
- polygon_coords = [(lon, lat) for lat, lon in coords]
522
- polygon = Polygon(polygon_coords)
512
+ polygon = Polygon(coords)
523
513
 
524
514
  # Check if point is within polygon
525
515
  if polygon.contains(point):
526
516
  id_list.append(feature['properties']['id'])
527
517
 
528
- return id_list
518
+ return id_list
519
+
520
+ def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
521
+ operation='within'):
522
+ """
523
+ Given a list of building footprints and a set of drawn polygon
524
+ vertices (in lon, lat), return the building IDs that fall within or
525
+ intersect the drawn polygon.
526
+
527
+ Args:
528
+ building_geojson (list):
529
+ A list of GeoJSON features, each feature is a dict with:
530
+ {
531
+ "type": "Feature",
532
+ "geometry": {
533
+ "type": "Polygon",
534
+ "coordinates": [
535
+ [
536
+ [lon1, lat1], [lon2, lat2], ...
537
+ ]
538
+ ]
539
+ },
540
+ "properties": {
541
+ "id": ...
542
+ ...
543
+ }
544
+ }
545
+
546
+ drawn_polygon_vertices (list):
547
+ A list of (lon, lat) tuples representing the polygon drawn by the user.
548
+
549
+ operation (str):
550
+ Determines how to include buildings.
551
+ Use "intersect" to include buildings that intersect the drawn polygon.
552
+ Use "within" to include buildings that lie entirely within the drawn polygon.
553
+
554
+ Returns:
555
+ list:
556
+ A list of building IDs (strings or ints) that satisfy the condition.
557
+ """
558
+ # Create Shapely Polygon from drawn vertices
559
+ drawn_polygon_shapely = Polygon(drawn_polygon_vertices)
560
+
561
+ included_building_ids = []
562
+
563
+ # Check each building in the GeoJSON
564
+ for feature in building_geojson:
565
+ # Skip any feature that is not Polygon
566
+ if feature['geometry']['type'] != 'Polygon':
567
+ continue
568
+
569
+ # Extract coordinates
570
+ coords = feature['geometry']['coordinates'][0]
571
+
572
+ # Create a Shapely polygon for the building
573
+ building_polygon = Polygon(coords)
574
+
575
+ # Depending on the operation, check the relationship
576
+ if operation == 'intersect':
577
+ if building_polygon.intersects(drawn_polygon_shapely):
578
+ included_building_ids.append(feature['properties'].get('id', None))
579
+ elif operation == 'within':
580
+ if building_polygon.within(drawn_polygon_shapely):
581
+ included_building_ids.append(feature['properties'].get('id', None))
582
+ else:
583
+ raise ValueError("operation must be 'intersect' or 'within'")
584
+
585
+ return included_building_ids
voxcity/geo/draw.py CHANGED
@@ -4,10 +4,11 @@ This module provides functions for drawing and manipulating rectangles on maps.
4
4
 
5
5
  import math
6
6
  from pyproj import Proj, transform
7
- from ipyleaflet import Map, DrawControl, Rectangle
7
+ from ipyleaflet import Map, DrawControl, Rectangle, Polygon as LeafletPolygon
8
8
  import ipyleaflet
9
9
  from geopy import distance
10
10
  from .utils import get_coordinates_from_cityname
11
+ import shapely.geometry as geom
11
12
 
12
13
  def rotate_rectangle(m, rectangle_vertices, angle):
13
14
  """
@@ -15,11 +16,11 @@ def rotate_rectangle(m, rectangle_vertices, angle):
15
16
 
16
17
  Args:
17
18
  m (ipyleaflet.Map): Map object to draw the rotated rectangle on
18
- rectangle_vertices (list): List of (lat, lon) tuples defining the rectangle vertices
19
+ rectangle_vertices (list): List of (lon, lat) tuples defining the rectangle vertices
19
20
  angle (float): Rotation angle in degrees
20
21
 
21
22
  Returns:
22
- list: List of rotated (lat, lon) tuples defining the new rectangle vertices
23
+ list: List of rotated (lon, lat) tuples defining the new rectangle vertices
23
24
  """
24
25
  if not rectangle_vertices:
25
26
  print("Draw a rectangle first!")
@@ -30,7 +31,7 @@ def rotate_rectangle(m, rectangle_vertices, angle):
30
31
  mercator = Proj(init='epsg:3857') # Web Mercator (projection used by most web maps)
31
32
 
32
33
  # Project vertices from WGS84 to Web Mercator for proper distance calculations
33
- projected_vertices = [transform(wgs84, mercator, lon, lat) for lat, lon in rectangle_vertices]
34
+ projected_vertices = [transform(wgs84, mercator, lon, lat) for lon, lat in rectangle_vertices]
34
35
 
35
36
  # Calculate the centroid to use as rotation center
36
37
  centroid_x = sum(x for x, y in projected_vertices) / len(projected_vertices)
@@ -56,15 +57,12 @@ def rotate_rectangle(m, rectangle_vertices, angle):
56
57
 
57
58
  rotated_vertices.append((new_x, new_y))
58
59
 
59
- # Convert coordinates back to WGS84 (lat/lon)
60
+ # Convert coordinates back to WGS84 (lon/lat)
60
61
  new_vertices = [transform(mercator, wgs84, x, y) for x, y in rotated_vertices]
61
62
 
62
- # Reorder coordinates from (lon,lat) to (lat,lon) format
63
- new_vertices = [(lat, lon) for lon, lat in new_vertices]
64
-
65
63
  # Create and add new polygon layer to map
66
64
  polygon = ipyleaflet.Polygon(
67
- locations=new_vertices,
65
+ locations=[(lat, lon) for lon, lat in new_vertices], # Convert to (lat,lon) for ipyleaflet
68
66
  color="red",
69
67
  fill_color="red"
70
68
  )
@@ -103,8 +101,8 @@ def draw_rectangle_map(center=(40, -100), zoom=4):
103
101
  print("Vertices of the drawn rectangle:")
104
102
  # Store all vertices except last (GeoJSON repeats first vertex at end)
105
103
  for coord in coordinates[:-1]:
106
- # Convert from GeoJSON (lon,lat) to standard (lat,lon) format
107
- rectangle_vertices.append((coord[1], coord[0]))
104
+ # Keep GeoJSON (lon,lat) format
105
+ rectangle_vertices.append((coord[0], coord[1]))
108
106
  print(f"Longitude: {coord[0]}, Latitude: {coord[1]}")
109
107
 
110
108
  # Configure drawing controls - only enable rectangle drawing
@@ -178,9 +176,9 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
178
176
 
179
177
  # Process only if a point was drawn on the map
180
178
  if action == 'created' and geo_json['geometry']['type'] == 'Point':
181
- # Extract point coordinates (converting from GeoJSON lon,lat to lat,lon)
182
- lat, lon = geo_json['geometry']['coordinates'][1], geo_json['geometry']['coordinates'][0]
183
- print(f"Point drawn at Latitude: {lat}, Longitude: {lon}")
179
+ # Extract point coordinates from GeoJSON (lon,lat)
180
+ lon, lat = geo_json['geometry']['coordinates'][0], geo_json['geometry']['coordinates'][1]
181
+ print(f"Point drawn at Longitude: {lon}, Latitude: {lat}")
184
182
 
185
183
  # Calculate corner points using geopy's distance calculator
186
184
  # Each point is calculated as a destination from center point using bearing
@@ -189,15 +187,15 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
189
187
  east = distance.distance(meters=east_west_length/2).destination((lat, lon), bearing=90)
190
188
  west = distance.distance(meters=east_west_length/2).destination((lat, lon), bearing=270)
191
189
 
192
- # Create rectangle vertices in counter-clockwise order
190
+ # Create rectangle vertices in counter-clockwise order (lon,lat)
193
191
  rectangle_vertices.extend([
194
- (south.latitude, west.longitude),
195
- (north.latitude, west.longitude),
196
- (north.latitude, east.longitude),
197
- (south.latitude, east.longitude)
192
+ (west.longitude, south.latitude),
193
+ (west.longitude, north.latitude),
194
+ (east.longitude, north.latitude),
195
+ (east.longitude, south.latitude)
198
196
  ])
199
197
 
200
- # Create and add new rectangle to map
198
+ # Create and add new rectangle to map (ipyleaflet expects lat,lon)
201
199
  rectangle = Rectangle(
202
200
  bounds=[(north.latitude, west.longitude), (south.latitude, east.longitude)],
203
201
  color="red",
@@ -208,7 +206,7 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
208
206
 
209
207
  print("Rectangle vertices:")
210
208
  for vertex in rectangle_vertices:
211
- print(f"Latitude: {vertex[0]}, Longitude: {vertex[1]}")
209
+ print(f"Longitude: {vertex[0]}, Latitude: {vertex[1]}")
212
210
 
213
211
  # Configure drawing controls - only enable point drawing
214
212
  draw_control = DrawControl()
@@ -222,4 +220,112 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
222
220
  # Register event handler for drawing actions
223
221
  draw_control.on_draw(handle_draw)
224
222
 
225
- return m, rectangle_vertices
223
+ return m, rectangle_vertices
224
+
225
+ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
226
+ """
227
+ Displays building footprints (in Lon-Lat order) on an ipyleaflet map,
228
+ and allows the user to draw a polygon whose vertices are returned
229
+ in a Python list (also in Lon-Lat).
230
+
231
+ Args:
232
+ building_geojson (list): A list of GeoJSON features (Polygons),
233
+ with coordinates in [lon, lat] order.
234
+ zoom (int): Initial zoom level for the map. Default=17.
235
+
236
+ Returns:
237
+ (map_object, drawn_polygon_vertices)
238
+ - map_object: ipyleaflet Map instance
239
+ - drawn_polygon_vertices: a Python list that gets updated whenever
240
+ a new polygon is created. The list is in (lon, lat) order.
241
+ """
242
+ # ---------------------------------------------------------
243
+ # 1. Determine a suitable map center via bounding box logic
244
+ # ---------------------------------------------------------
245
+ all_lons = []
246
+ all_lats = []
247
+ for feature in building_geojson:
248
+ # Handle only Polygons here; skip MultiPolygon if present
249
+ if feature['geometry']['type'] == 'Polygon':
250
+ # Coordinates in this data are [ [lon, lat], [lon, lat], ... ]
251
+ coords = feature['geometry']['coordinates'][0] # outer ring
252
+ all_lons.extend(pt[0] for pt in coords)
253
+ all_lats.extend(pt[1] for pt in coords)
254
+
255
+ if not all_lats or not all_lons:
256
+ # Fallback: If no footprints or invalid data, pick a default
257
+ center_lon, center_lat = -100.0, 40.0
258
+ else:
259
+ min_lon, max_lon = min(all_lons), max(all_lons)
260
+ min_lat, max_lat = min(all_lats), max(all_lats)
261
+ center_lon = (min_lon + max_lon) / 2
262
+ center_lat = (min_lat + max_lat) / 2
263
+
264
+ # Create the ipyleaflet map (needs lat,lon)
265
+ m = Map(center=(center_lat, center_lon), zoom=zoom, scroll_wheel_zoom=True)
266
+
267
+ # -----------------------------------------
268
+ # 2. Add each building footprint to the map
269
+ # -----------------------------------------
270
+ for feature in building_geojson:
271
+ # Only handle simple Polygons
272
+ if feature['geometry']['type'] == 'Polygon':
273
+ coords = feature['geometry']['coordinates'][0]
274
+ # Convert to (lat,lon) for ipyleaflet
275
+ lat_lon_coords = [(c[1], c[0]) for c in coords]
276
+
277
+ # Create the polygon layer
278
+ bldg_layer = LeafletPolygon(
279
+ locations=lat_lon_coords,
280
+ color="blue",
281
+ fill_color="blue",
282
+ fill_opacity=0.2,
283
+ weight=2
284
+ )
285
+ m.add_layer(bldg_layer)
286
+
287
+ # -----------------------------------------------------------------
288
+ # 3. Enable drawing of polygons, capturing the vertices in Lon-Lat
289
+ # -----------------------------------------------------------------
290
+ drawn_polygon_vertices = [] # We'll store the newly drawn polygon's vertices here (lon, lat).
291
+
292
+ draw_control = DrawControl(
293
+ polygon={
294
+ "shapeOptions": {
295
+ "color": "red",
296
+ "fillColor": "red",
297
+ "fillOpacity": 0.2
298
+ }
299
+ },
300
+ rectangle={}, # Disable rectangles (or enable if needed)
301
+ circle={}, # Disable circles
302
+ circlemarker={}, # Disable circlemarkers
303
+ polyline={}, # Disable polylines
304
+ marker={} # Disable markers
305
+ )
306
+
307
+ def handle_draw(self, action, geo_json):
308
+ """
309
+ Callback for whenever a shape is created or edited.
310
+ ipyleaflet's DrawControl returns standard GeoJSON (lon, lat).
311
+ We'll keep them as (lon, lat).
312
+ """
313
+ # Clear any previously stored vertices
314
+ drawn_polygon_vertices.clear()
315
+
316
+ if action == 'created' and geo_json['geometry']['type'] == 'Polygon':
317
+ # The polygon's first ring
318
+ coordinates = geo_json['geometry']['coordinates'][0]
319
+ print("Vertices of the drawn polygon (Lon-Lat):")
320
+
321
+ # Keep GeoJSON (lon,lat) format, skip last repeated coordinate
322
+ for coord in coordinates[:-1]:
323
+ lon = coord[0]
324
+ lat = coord[1]
325
+ drawn_polygon_vertices.append((lon, lat))
326
+ print(f" - (lon, lat) = ({lon}, {lat})")
327
+
328
+ draw_control.on_draw(handle_draw)
329
+ m.add_control(draw_control)
330
+
331
+ return m, drawn_polygon_vertices
voxcity/geo/grid.py CHANGED
@@ -235,77 +235,6 @@ def tree_height_grid_from_land_cover(land_cover_grid_ori):
235
235
 
236
236
  return tree_height_grid
237
237
 
238
- def create_land_cover_grid_from_geotiff(tiff_path, mesh_size, land_cover_classes):
239
- """
240
- Create a land cover grid from a GeoTIFF file.
241
-
242
- Args:
243
- tiff_path (str): Path to GeoTIFF file
244
- mesh_size (float): Size of mesh cells
245
- land_cover_classes (dict): Dictionary mapping land cover classes
246
-
247
- Returns:
248
- numpy.ndarray: Grid of land cover classes
249
- """
250
- with rasterio.open(tiff_path) as src:
251
- # Read RGB bands
252
- img = src.read((1,2,3))
253
- left, bottom, right, top = src.bounds
254
- src_crs = src.crs
255
-
256
- # Handle different coordinate reference systems
257
- if src_crs.to_epsg() == 3857: # Web Mercator
258
- # Convert bounds from Web Mercator to WGS84 for accurate distance calculations
259
- wgs84 = CRS.from_epsg(4326)
260
- transformer = Transformer.from_crs(src_crs, wgs84, always_xy=True)
261
- left_wgs84, bottom_wgs84 = transformer.transform(left, bottom)
262
- right_wgs84, top_wgs84 = transformer.transform(right, top)
263
-
264
- # Calculate actual distances using geodesic calculations
265
- geod = Geod(ellps="WGS84")
266
- _, _, width = geod.inv(left_wgs84, bottom_wgs84, right_wgs84, bottom_wgs84)
267
- _, _, height = geod.inv(left_wgs84, bottom_wgs84, left_wgs84, top_wgs84)
268
- else:
269
- # For projections already in meters, use simple subtraction
270
- width = right - left
271
- height = top - bottom
272
-
273
- # Calculate grid dimensions based on mesh size
274
- num_cells_x = int(width / mesh_size + 0.5)
275
- num_cells_y = int(height / mesh_size + 0.5)
276
-
277
- # Adjust mesh size to fit image exactly
278
- adjusted_mesh_size_x = (right - left) / num_cells_x
279
- adjusted_mesh_size_y = (top - bottom) / num_cells_y
280
-
281
- # Create affine transform for new grid
282
- new_affine = Affine(adjusted_mesh_size_x, 0, left, 0, -adjusted_mesh_size_y, top)
283
-
284
- # Create coordinate grids
285
- cols, rows = np.meshgrid(np.arange(num_cells_x), np.arange(num_cells_y))
286
- xs, ys = new_affine * (cols, rows)
287
- xs_flat, ys_flat = xs.flatten(), ys.flatten()
288
-
289
- # Convert coordinates to image indices
290
- row, col = src.index(xs_flat, ys_flat)
291
- row, col = np.array(row), np.array(col)
292
-
293
- # Filter out invalid indices
294
- valid = (row >= 0) & (row < src.height) & (col >= 0) & (col < src.width)
295
- row, col = row[valid], col[valid]
296
-
297
- # Create output grid and fill with land cover classes
298
- grid = np.full((num_cells_y, num_cells_x), 'No Data', dtype=object)
299
-
300
- for i, (r, c) in enumerate(zip(row, col)):
301
- cell_data = img[:, r, c]
302
- dominant_class = get_dominant_class(cell_data, land_cover_classes)
303
- grid_row, grid_col = np.unravel_index(i, (num_cells_y, num_cells_x))
304
- grid[grid_row, grid_col] = dominant_class
305
-
306
- # Flip grid vertically before returning
307
- return np.flipud(grid)
308
-
309
238
  def create_land_cover_grid_from_geotiff_polygon(tiff_path, mesh_size, land_cover_classes, polygon):
310
239
  """
311
240
  Create a land cover grid from a GeoTIFF file within a polygon boundary.
@@ -329,7 +258,7 @@ def create_land_cover_grid_from_geotiff_polygon(tiff_path, mesh_size, land_cover
329
258
  poly = Polygon(polygon)
330
259
 
331
260
  # Get bounds of the polygon in WGS84 coordinates
332
- bottom_wgs84, left_wgs84, top_wgs84, right_wgs84 = poly.bounds
261
+ left_wgs84, bottom_wgs84, right_wgs84, top_wgs84 = poly.bounds
333
262
  # print(left, bottom, right, top)
334
263
 
335
264
  # Calculate width and height using geodesic calculations for accuracy
@@ -381,7 +310,7 @@ def create_land_cover_grid_from_geojson_polygon(geojson_data, meshsize, source,
381
310
  geojson_data (dict): GeoJSON data containing land cover polygons
382
311
  meshsize (float): Size of each grid cell in meters
383
312
  source (str): Source of the land cover data to determine class priorities
384
- rectangle_vertices (list): List of 4 (lat,lon) coordinate pairs defining the rectangle bounds
313
+ rectangle_vertices (list): List of 4 (lon,lat) coordinate pairs defining the rectangle bounds
385
314
 
386
315
  Returns:
387
316
  numpy.ndarray: 2D grid of land cover classes as strings
@@ -411,8 +340,8 @@ def create_land_cover_grid_from_geojson_polygon(geojson_data, meshsize, source,
411
340
  vertex_0, vertex_1, vertex_3 = rectangle_vertices[0], rectangle_vertices[1], rectangle_vertices[3]
412
341
 
413
342
  # Calculate actual distances between vertices using geodesic calculations
414
- dist_side_1 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_1[1], vertex_1[0])
415
- dist_side_2 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_3[1], vertex_3[0])
343
+ dist_side_1 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_1[0], vertex_1[1])
344
+ dist_side_2 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_3[0], vertex_3[1])
416
345
 
417
346
  # Create vectors representing the sides of the rectangle
418
347
  side_1 = np.array(vertex_1) - np.array(vertex_0)
@@ -476,70 +405,6 @@ def create_land_cover_grid_from_geojson_polygon(geojson_data, meshsize, source,
476
405
  continue
477
406
  return grid
478
407
 
479
- def create_canopy_height_grid_from_geotiff(tiff_path, mesh_size):
480
- """
481
- Create a canopy height grid from a GeoTIFF file.
482
-
483
- Args:
484
- tiff_path (str): Path to GeoTIFF file
485
- mesh_size (float): Size of mesh cells
486
-
487
- Returns:
488
- numpy.ndarray: Grid of canopy heights
489
- """
490
- with rasterio.open(tiff_path) as src:
491
- # Read single band height data
492
- img = src.read(1)
493
- left, bottom, right, top = src.bounds
494
- src_crs = src.crs
495
-
496
- # Handle coordinate system conversion and distance calculations
497
- if src_crs.to_epsg() == 3857: # Web Mercator projection
498
- # Convert bounds to WGS84 for accurate distance calculation
499
- wgs84 = CRS.from_epsg(4326)
500
- transformer = Transformer.from_crs(src_crs, wgs84, always_xy=True)
501
- left_wgs84, bottom_wgs84 = transformer.transform(left, bottom)
502
- right_wgs84, top_wgs84 = transformer.transform(right, top)
503
-
504
- # Calculate actual distances using geodesic methods
505
- geod = Geod(ellps="WGS84")
506
- _, _, width = geod.inv(left_wgs84, bottom_wgs84, right_wgs84, bottom_wgs84)
507
- _, _, height = geod.inv(left_wgs84, bottom_wgs84, left_wgs84, top_wgs84)
508
- else:
509
- # For projections already in meters, use simple subtraction
510
- width = right - left
511
- height = top - bottom
512
-
513
- # Calculate grid dimensions and adjust mesh size
514
- num_cells_x = int(width / mesh_size + 0.5)
515
- num_cells_y = int(height / mesh_size + 0.5)
516
-
517
- adjusted_mesh_size_x = (right - left) / num_cells_x
518
- adjusted_mesh_size_y = (top - bottom) / num_cells_y
519
-
520
- # Create affine transform for coordinate mapping
521
- new_affine = Affine(adjusted_mesh_size_x, 0, left, 0, -adjusted_mesh_size_y, top)
522
-
523
- # Generate coordinate grids
524
- cols, rows = np.meshgrid(np.arange(num_cells_x), np.arange(num_cells_y))
525
- xs, ys = new_affine * (cols, rows)
526
- xs_flat, ys_flat = xs.flatten(), ys.flatten()
527
-
528
- # Convert to image coordinates
529
- row, col = src.index(xs_flat, ys_flat)
530
- row, col = np.array(row), np.array(col)
531
-
532
- # Filter valid indices
533
- valid = (row >= 0) & (row < src.height) & (col >= 0) & (col < src.width)
534
- row, col = row[valid], col[valid]
535
-
536
- # Create output grid and fill with height values
537
- grid = np.full((num_cells_y, num_cells_x), np.nan)
538
- flat_indices = np.ravel_multi_index((row, col), img.shape)
539
- np.put(grid, np.ravel_multi_index((rows.flatten()[valid], cols.flatten()[valid]), grid.shape), img.flat[flat_indices])
540
-
541
- return np.flipud(grid)
542
-
543
408
  def create_height_grid_from_geotiff_polygon(tiff_path, mesh_size, polygon):
544
409
  """
545
410
  Create a height grid from a GeoTIFF file within a polygon boundary.
@@ -562,8 +427,9 @@ def create_height_grid_from_geotiff_polygon(tiff_path, mesh_size, polygon):
562
427
  poly = Polygon(polygon)
563
428
 
564
429
  # Get polygon bounds in WGS84
565
- bottom_wgs84, left_wgs84, top_wgs84, right_wgs84 = poly.bounds
566
- print(left, bottom, right, top)
430
+ left_wgs84, bottom_wgs84, right_wgs84, top_wgs84 = poly.bounds
431
+ # print(left, bottom, right, top)
432
+ # print(left_wgs84, bottom_wgs84, right_wgs84, top_wgs84)
567
433
 
568
434
  # Calculate actual distances using geodesic methods
569
435
  geod = Geod(ellps="WGS84")
@@ -624,8 +490,8 @@ def create_building_height_grid_from_geojson_polygon(geojson_data, meshsize, rec
624
490
  vertex_0, vertex_1, vertex_3 = rectangle_vertices[0], rectangle_vertices[1], rectangle_vertices[3]
625
491
 
626
492
  # Calculate distances between vertices
627
- dist_side_1 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_1[1], vertex_1[0])
628
- dist_side_2 = calculate_distance(geod, vertex_0[1], vertex_0[0], vertex_3[1], vertex_3[0])
493
+ dist_side_1 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_1[0], vertex_1[1])
494
+ dist_side_2 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_3[0], vertex_3[1])
629
495
 
630
496
  # Calculate normalized vectors for grid orientation
631
497
  side_1 = np.array(vertex_1) - np.array(vertex_0)