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/geo/utils.py CHANGED
@@ -177,16 +177,15 @@ def transform_coords(transformer, lon, lat):
177
177
  def create_polygon(vertices):
178
178
  """
179
179
  Create a Shapely polygon from vertices.
180
- Converts from (lat,lon) format to (lon,lat) format required by Shapely.
180
+ Input vertices are already in (lon,lat) format required by Shapely.
181
181
 
182
182
  Args:
183
- vertices (list): List of (lat, lon) coordinate pairs
183
+ vertices (list): List of (lon, lat) coordinate pairs
184
184
 
185
185
  Returns:
186
186
  Polygon: Shapely polygon object
187
187
  """
188
- flipped_vertices = [(lon, lat) for lat, lon in vertices]
189
- return Polygon(flipped_vertices)
188
+ return Polygon(vertices)
190
189
 
191
190
  def create_geodataframe(polygon, crs=4326):
192
191
  """
@@ -202,14 +201,14 @@ def create_geodataframe(polygon, crs=4326):
202
201
  """
203
202
  return gpd.GeoDataFrame({'geometry': [polygon]}, crs=from_epsg(crs))
204
203
 
205
- def haversine_distance(lat1, lon1, lat2, lon2):
204
+ def haversine_distance(lon1, lat1, lon2, lat2):
206
205
  """
207
206
  Calculate great-circle distance between two points using Haversine formula.
208
207
  This is an approximation that treats the Earth as a perfect sphere.
209
208
 
210
209
  Args:
211
- lat1, lon1 (float): Coordinates of first point
212
- lat2, lon2 (float): Coordinates of second point
210
+ lon1, lat1 (float): Coordinates of first point
211
+ lon2, lat2 (float): Coordinates of second point
213
212
 
214
213
  Returns:
215
214
  float: Distance in kilometers
@@ -323,15 +322,16 @@ def merge_geotiffs(geotiff_files, output_dir):
323
322
  def convert_format_lat_lon(input_coords):
324
323
  """
325
324
  Convert coordinate format and close polygon.
325
+ Input coordinates are already in [lon, lat] format.
326
326
 
327
327
  Args:
328
328
  input_coords (list): List of [lon, lat] coordinates
329
329
 
330
330
  Returns:
331
- list: List of [lat, lon] coordinates with first point repeated at end
331
+ list: List of [lon, lat] coordinates with first point repeated at end
332
332
  """
333
- # Swap lon/lat to lat/lon format and create list
334
- output_coords = [[coord[1], coord[0]] for coord in input_coords]
333
+ # Create list with coordinates in same order
334
+ output_coords = input_coords.copy()
335
335
  # Close polygon by repeating first point at end
336
336
  output_coords.append(output_coords[0])
337
337
  return output_coords
@@ -344,7 +344,7 @@ def get_coordinates_from_cityname(place_name):
344
344
  place_name (str): Name of city to geocode
345
345
 
346
346
  Returns:
347
- tuple: (latitude, longitude) or None if geocoding fails
347
+ tuple: (longitude, latitude) or None if geocoding fails
348
348
  """
349
349
  # Initialize geocoder with user agent
350
350
  geolocator = Nominatim(user_agent="my_geocoding_script")
@@ -366,16 +366,16 @@ def get_city_country_name_from_rectangle(coordinates):
366
366
  Get city and country name from rectangle coordinates.
367
367
 
368
368
  Args:
369
- coordinates (list): List of (lat, lon) coordinates defining rectangle
369
+ coordinates (list): List of (lon, lat) coordinates defining rectangle
370
370
 
371
371
  Returns:
372
372
  str: String in format "city/ country" or None if lookup fails
373
373
  """
374
374
  # Calculate center point of rectangle
375
- latitudes = [coord[0] for coord in coordinates]
376
- longitudes = [coord[1] for coord in coordinates]
377
- center_lat = sum(latitudes) / len(latitudes)
375
+ longitudes = [coord[0] for coord in coordinates]
376
+ latitudes = [coord[1] for coord in coordinates]
378
377
  center_lon = sum(longitudes) / len(longitudes)
378
+ center_lat = sum(latitudes) / len(latitudes)
379
379
  center_coord = (center_lat, center_lon)
380
380
 
381
381
  # Initialize geocoder with rate limiting to avoid hitting API limits
@@ -401,17 +401,16 @@ def get_timezone_info(rectangle_coords):
401
401
  Get timezone and central meridian info for a location.
402
402
 
403
403
  Args:
404
- rectangle_coords (list): List of (lat, lon) coordinates defining rectangle
404
+ rectangle_coords (list): List of (lon, lat) coordinates defining rectangle
405
405
 
406
406
  Returns:
407
407
  tuple: (timezone string, central meridian longitude string)
408
408
  """
409
409
  # Calculate center point of rectangle
410
- latitudes = [coord[0] for coord in rectangle_coords]
411
- longitudes = [coord[1] for coord in rectangle_coords]
412
- center_lat = sum(latitudes) / len(latitudes)
410
+ longitudes = [coord[0] for coord in rectangle_coords]
411
+ latitudes = [coord[1] for coord in rectangle_coords]
413
412
  center_lon = sum(longitudes) / len(longitudes)
414
- center_coord = (center_lat, center_lon)
413
+ center_lat = sum(latitudes) / len(latitudes)
415
414
 
416
415
  # Find timezone at center coordinates
417
416
  tf = TimezoneFinder()
@@ -475,6 +474,7 @@ def create_building_polygons(filtered_buildings):
475
474
  """
476
475
  building_polygons = []
477
476
  idx = index.Index()
477
+ valid_count = 0
478
478
  count = 0
479
479
 
480
480
  # Find highest existing ID to avoid duplicates
@@ -487,63 +487,76 @@ def create_building_polygons(filtered_buildings):
487
487
  else:
488
488
  id_count = 1
489
489
 
490
- for i, building in enumerate(filtered_buildings):
491
- # Create polygon from coordinates
492
- polygon = Polygon(building['geometry']['coordinates'][0])
493
-
494
- # Extract height information from various possible property fields
495
- height = building['properties'].get('height')
496
- levels = building['properties'].get('levels')
497
- floors = building['properties'].get('num_floors')
498
- min_height = building['properties'].get('min_height')
499
- min_level = building['properties'].get('min_level')
500
- min_floor = building['properties'].get('min_floor')
501
-
502
- # Calculate height if not directly specified
503
- if (height is None) or (height<=0):
504
- if levels is not None:
505
- height = floor_height * levels
506
- elif floors is not None:
507
- height = floor_height * floors
508
- else:
509
- count += 1
510
- height = np.nan
511
-
512
- # Calculate minimum height if not directly specified
513
- if (min_height is None) or (min_height<=0):
514
- if min_level is not None:
515
- min_height = floor_height * float(min_level)
516
- elif min_floor is not None:
517
- min_height = floor_height * float(min_floor)
490
+ for building in filtered_buildings:
491
+ try:
492
+ # Handle potential nested coordinate tuples
493
+ coords = building['geometry']['coordinates'][0]
494
+ # Flatten coordinates if they're nested tuples
495
+ if isinstance(coords[0], tuple):
496
+ coords = [list(c) for c in coords]
497
+ elif isinstance(coords[0][0], tuple):
498
+ coords = [list(c[0]) for c in coords]
499
+
500
+ # Create polygon from coordinates
501
+ polygon = Polygon(coords)
502
+
503
+ # Skip invalid geometries
504
+ if not polygon.is_valid:
505
+ print(f"Warning: Skipping invalid polygon geometry")
506
+ continue
507
+
508
+ height = building['properties'].get('height')
509
+ levels = building['properties'].get('levels')
510
+ floors = building['properties'].get('num_floors')
511
+ min_height = building['properties'].get('min_height')
512
+ min_level = building['properties'].get('min_level')
513
+ min_floor = building['properties'].get('min_floor')
514
+
515
+ if (height is None) or (height<=0):
516
+ if levels is not None:
517
+ height = floor_height * levels
518
+ elif floors is not None:
519
+ height = floor_height * floors
520
+ else:
521
+ count += 1
522
+ height = np.nan
523
+
524
+ if (min_height is None) or (min_height<=0):
525
+ if min_level is not None:
526
+ min_height = floor_height * float(min_level)
527
+ elif min_floor is not None:
528
+ min_height = floor_height * float(min_floor)
529
+ else:
530
+ min_height = 0
531
+
532
+ if building['properties'].get('id') is not None:
533
+ feature_id = building['properties']['id']
518
534
  else:
519
- min_height = 0
535
+ feature_id = id_count
536
+ id_count += 1
520
537
 
521
- # Get or assign building ID
522
- if building['properties'].get('id') is not None:
523
- feature_id = building['properties']['id']
524
- else:
525
- feature_id = id_count
526
- id_count += 1
527
-
528
- # Check if building is inner part of another building
529
- if building['properties'].get('is_inner') is not None:
530
- is_inner = building['properties']['is_inner']
531
- else:
532
- is_inner = False
538
+ if building['properties'].get('is_inner') is not None:
539
+ is_inner = building['properties']['is_inner']
540
+ else:
541
+ is_inner = False
533
542
 
534
- # Store polygon with all properties and add to spatial index
535
- building_polygons.append((polygon, height, min_height, is_inner, feature_id))
536
- idx.insert(i, polygon.bounds)
543
+ building_polygons.append((polygon, height, min_height, is_inner, feature_id))
544
+ idx.insert(valid_count, polygon.bounds)
545
+ valid_count += 1
546
+
547
+ except Exception as e:
548
+ print(f"Warning: Skipping invalid building geometry: {e}")
549
+ continue
537
550
 
538
551
  return building_polygons, idx
539
552
 
540
- def get_country_name(lat, lon):
553
+ def get_country_name(lon, lat):
541
554
  """
542
555
  Get country name from coordinates using reverse geocoding.
543
556
 
544
557
  Args:
545
- lat (float): Latitude
546
558
  lon (float): Longitude
559
+ lat (float): Latitude
547
560
 
548
561
  Returns:
549
562
  str: Country name or None if lookup fails