voxcity 0.3.3__py3-none-any.whl → 0.3.5__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,20 +497,19 @@ 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):
@@ -530,8 +520,8 @@ def find_building_containing_point(features, target_point):
530
520
  def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
531
521
  operation='within'):
532
522
  """
533
- Given a list of building footprints (in Lat-Lon) and a set of drawn polygon
534
- vertices (also in Lat-Lon), return the building IDs that fall within or
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
535
525
  intersect the drawn polygon.
536
526
 
537
527
  Args:
@@ -543,7 +533,7 @@ def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
543
533
  "type": "Polygon",
544
534
  "coordinates": [
545
535
  [
546
- [lat1, lon1], [lat2, lon2], ...
536
+ [lon1, lat1], [lon2, lat2], ...
547
537
  ]
548
538
  ]
549
539
  },
@@ -552,10 +542,9 @@ def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
552
542
  ...
553
543
  }
554
544
  }
555
- Note: These coordinates are in (lat, lon) order, not standard (lon, lat).
556
545
 
557
546
  drawn_polygon_vertices (list):
558
- A list of (lat, lon) tuples representing the polygon drawn by the user.
547
+ A list of (lon, lat) tuples representing the polygon drawn by the user.
559
548
 
560
549
  operation (str):
561
550
  Determines how to include buildings.
@@ -566,27 +555,24 @@ def get_buildings_in_drawn_polygon(building_geojson, drawn_polygon_vertices,
566
555
  list:
567
556
  A list of building IDs (strings or ints) that satisfy the condition.
568
557
  """
569
- # 1. Convert the user-drawn polygon vertices (lat, lon) into a Shapely Polygon.
570
- # Shapely expects (x, y) = (longitude, latitude).
571
- # So we'll do (lon, lat) for each vertex.
572
- drawn_polygon_shapely = Polygon([(lon, lat) for (lat, lon) in drawn_polygon_vertices])
558
+ # Create Shapely Polygon from drawn vertices
559
+ drawn_polygon_shapely = Polygon(drawn_polygon_vertices)
573
560
 
574
561
  included_building_ids = []
575
562
 
576
- # 2. Check each building in the GeoJSON
563
+ # Check each building in the GeoJSON
577
564
  for feature in building_geojson:
578
565
  # Skip any feature that is not Polygon
579
566
  if feature['geometry']['type'] != 'Polygon':
580
567
  continue
581
568
 
582
- # Extract coordinates, which are in [ [lat, lon], [lat, lon], ... ]
569
+ # Extract coordinates
583
570
  coords = feature['geometry']['coordinates'][0]
584
571
 
585
572
  # Create a Shapely polygon for the building
586
- # Convert from (lat, lon) to (lon, lat)
587
- building_polygon = Polygon([(lon, lat) for (lat, lon) in coords])
573
+ building_polygon = Polygon(coords)
588
574
 
589
- # 3. Depending on the operation, check the relationship
575
+ # Depending on the operation, check the relationship
590
576
  if operation == 'intersect':
591
577
  if building_polygon.intersects(drawn_polygon_shapely):
592
578
  included_building_ids.append(feature['properties'].get('id', None))
voxcity/geo/__init_.py CHANGED
@@ -1,3 +1,4 @@
1
1
  from .draw import *
2
2
  from .grid import *
3
- from .utils import *
3
+ from .utils import *
4
+ from .network import *
voxcity/geo/draw.py CHANGED
@@ -16,11 +16,11 @@ def rotate_rectangle(m, rectangle_vertices, angle):
16
16
 
17
17
  Args:
18
18
  m (ipyleaflet.Map): Map object to draw the rotated rectangle on
19
- 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
20
20
  angle (float): Rotation angle in degrees
21
21
 
22
22
  Returns:
23
- 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
24
24
  """
25
25
  if not rectangle_vertices:
26
26
  print("Draw a rectangle first!")
@@ -31,7 +31,7 @@ def rotate_rectangle(m, rectangle_vertices, angle):
31
31
  mercator = Proj(init='epsg:3857') # Web Mercator (projection used by most web maps)
32
32
 
33
33
  # Project vertices from WGS84 to Web Mercator for proper distance calculations
34
- 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]
35
35
 
36
36
  # Calculate the centroid to use as rotation center
37
37
  centroid_x = sum(x for x, y in projected_vertices) / len(projected_vertices)
@@ -57,15 +57,12 @@ def rotate_rectangle(m, rectangle_vertices, angle):
57
57
 
58
58
  rotated_vertices.append((new_x, new_y))
59
59
 
60
- # Convert coordinates back to WGS84 (lat/lon)
60
+ # Convert coordinates back to WGS84 (lon/lat)
61
61
  new_vertices = [transform(mercator, wgs84, x, y) for x, y in rotated_vertices]
62
62
 
63
- # Reorder coordinates from (lon,lat) to (lat,lon) format
64
- new_vertices = [(lat, lon) for lon, lat in new_vertices]
65
-
66
63
  # Create and add new polygon layer to map
67
64
  polygon = ipyleaflet.Polygon(
68
- locations=new_vertices,
65
+ locations=[(lat, lon) for lon, lat in new_vertices], # Convert to (lat,lon) for ipyleaflet
69
66
  color="red",
70
67
  fill_color="red"
71
68
  )
@@ -104,8 +101,8 @@ def draw_rectangle_map(center=(40, -100), zoom=4):
104
101
  print("Vertices of the drawn rectangle:")
105
102
  # Store all vertices except last (GeoJSON repeats first vertex at end)
106
103
  for coord in coordinates[:-1]:
107
- # Convert from GeoJSON (lon,lat) to standard (lat,lon) format
108
- rectangle_vertices.append((coord[1], coord[0]))
104
+ # Keep GeoJSON (lon,lat) format
105
+ rectangle_vertices.append((coord[0], coord[1]))
109
106
  print(f"Longitude: {coord[0]}, Latitude: {coord[1]}")
110
107
 
111
108
  # Configure drawing controls - only enable rectangle drawing
@@ -179,9 +176,9 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
179
176
 
180
177
  # Process only if a point was drawn on the map
181
178
  if action == 'created' and geo_json['geometry']['type'] == 'Point':
182
- # Extract point coordinates (converting from GeoJSON lon,lat to lat,lon)
183
- lat, lon = geo_json['geometry']['coordinates'][1], geo_json['geometry']['coordinates'][0]
184
- 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}")
185
182
 
186
183
  # Calculate corner points using geopy's distance calculator
187
184
  # Each point is calculated as a destination from center point using bearing
@@ -190,15 +187,15 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
190
187
  east = distance.distance(meters=east_west_length/2).destination((lat, lon), bearing=90)
191
188
  west = distance.distance(meters=east_west_length/2).destination((lat, lon), bearing=270)
192
189
 
193
- # Create rectangle vertices in counter-clockwise order
190
+ # Create rectangle vertices in counter-clockwise order (lon,lat)
194
191
  rectangle_vertices.extend([
195
- (south.latitude, west.longitude),
196
- (north.latitude, west.longitude),
197
- (north.latitude, east.longitude),
198
- (south.latitude, east.longitude)
192
+ (west.longitude, south.latitude),
193
+ (west.longitude, north.latitude),
194
+ (east.longitude, north.latitude),
195
+ (east.longitude, south.latitude)
199
196
  ])
200
197
 
201
- # Create and add new rectangle to map
198
+ # Create and add new rectangle to map (ipyleaflet expects lat,lon)
202
199
  rectangle = Rectangle(
203
200
  bounds=[(north.latitude, west.longitude), (south.latitude, east.longitude)],
204
201
  color="red",
@@ -209,7 +206,7 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
209
206
 
210
207
  print("Rectangle vertices:")
211
208
  for vertex in rectangle_vertices:
212
- print(f"Latitude: {vertex[0]}, Longitude: {vertex[1]}")
209
+ print(f"Longitude: {vertex[0]}, Latitude: {vertex[1]}")
213
210
 
214
211
  # Configure drawing controls - only enable point drawing
215
212
  draw_control = DrawControl()
@@ -227,44 +224,44 @@ def center_location_map_cityname(cityname, east_west_length, north_south_length,
227
224
 
228
225
  def display_buildings_and_draw_polygon(building_geojson, zoom=17):
229
226
  """
230
- Displays building footprints (in Lat-Lon order) on an ipyleaflet map,
227
+ Displays building footprints (in Lon-Lat order) on an ipyleaflet map,
231
228
  and allows the user to draw a polygon whose vertices are returned
232
- in a Python list (also in Lat-Lon).
229
+ in a Python list (also in Lon-Lat).
233
230
 
234
231
  Args:
235
232
  building_geojson (list): A list of GeoJSON features (Polygons),
236
- BUT with coordinates in [lat, lon] order.
233
+ with coordinates in [lon, lat] order.
237
234
  zoom (int): Initial zoom level for the map. Default=17.
238
235
 
239
236
  Returns:
240
237
  (map_object, drawn_polygon_vertices)
241
238
  - map_object: ipyleaflet Map instance
242
239
  - drawn_polygon_vertices: a Python list that gets updated whenever
243
- a new polygon is created. The list is in (lat, lon) order.
240
+ a new polygon is created. The list is in (lon, lat) order.
244
241
  """
245
242
  # ---------------------------------------------------------
246
243
  # 1. Determine a suitable map center via bounding box logic
247
244
  # ---------------------------------------------------------
248
- all_lats = []
249
245
  all_lons = []
246
+ all_lats = []
250
247
  for feature in building_geojson:
251
248
  # Handle only Polygons here; skip MultiPolygon if present
252
249
  if feature['geometry']['type'] == 'Polygon':
253
- # Coordinates in this data are [ [lat, lon], [lat, lon], ... ]
250
+ # Coordinates in this data are [ [lon, lat], [lon, lat], ... ]
254
251
  coords = feature['geometry']['coordinates'][0] # outer ring
255
- all_lats.extend(pt[0] for pt in coords)
256
- all_lons.extend(pt[1] for pt in coords)
252
+ all_lons.extend(pt[0] for pt in coords)
253
+ all_lats.extend(pt[1] for pt in coords)
257
254
 
258
255
  if not all_lats or not all_lons:
259
256
  # Fallback: If no footprints or invalid data, pick a default
260
- center_lat, center_lon = 40.0, -100.0
257
+ center_lon, center_lat = -100.0, 40.0
261
258
  else:
262
- min_lat, max_lat = min(all_lats), max(all_lats)
263
259
  min_lon, max_lon = min(all_lons), max(all_lons)
264
- center_lat = (min_lat + max_lat) / 2
260
+ min_lat, max_lat = min(all_lats), max(all_lats)
265
261
  center_lon = (min_lon + max_lon) / 2
262
+ center_lat = (min_lat + max_lat) / 2
266
263
 
267
- # Create the ipyleaflet map
264
+ # Create the ipyleaflet map (needs lat,lon)
268
265
  m = Map(center=(center_lat, center_lon), zoom=zoom, scroll_wheel_zoom=True)
269
266
 
270
267
  # -----------------------------------------
@@ -274,8 +271,8 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
274
271
  # Only handle simple Polygons
275
272
  if feature['geometry']['type'] == 'Polygon':
276
273
  coords = feature['geometry']['coordinates'][0]
277
- # Because your data is already lat-lon, we do NOT swap:
278
- lat_lon_coords = [(c[0], c[1]) for c in coords]
274
+ # Convert to (lat,lon) for ipyleaflet
275
+ lat_lon_coords = [(c[1], c[0]) for c in coords]
279
276
 
280
277
  # Create the polygon layer
281
278
  bldg_layer = LeafletPolygon(
@@ -288,15 +285,15 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
288
285
  m.add_layer(bldg_layer)
289
286
 
290
287
  # -----------------------------------------------------------------
291
- # 3. Enable drawing of polygons, capturing the vertices in Lat-Lon
288
+ # 3. Enable drawing of polygons, capturing the vertices in Lon-Lat
292
289
  # -----------------------------------------------------------------
293
- drawn_polygon_vertices = [] # We'll store the newly drawn polygon's vertices here (lat, lon).
290
+ drawn_polygon_vertices = [] # We'll store the newly drawn polygon's vertices here (lon, lat).
294
291
 
295
292
  draw_control = DrawControl(
296
293
  polygon={
297
294
  "shapeOptions": {
298
- "color": "#6bc2e5",
299
- "fillColor": "#6bc2e5",
295
+ "color": "red",
296
+ "fillColor": "red",
300
297
  "fillOpacity": 0.2
301
298
  }
302
299
  },
@@ -311,23 +308,22 @@ def display_buildings_and_draw_polygon(building_geojson, zoom=17):
311
308
  """
312
309
  Callback for whenever a shape is created or edited.
313
310
  ipyleaflet's DrawControl returns standard GeoJSON (lon, lat).
314
- We'll convert them to (lat, lon).
311
+ We'll keep them as (lon, lat).
315
312
  """
316
313
  # Clear any previously stored vertices
317
314
  drawn_polygon_vertices.clear()
318
315
 
319
316
  if action == 'created' and geo_json['geometry']['type'] == 'Polygon':
320
- # The polygons first ring
317
+ # The polygon's first ring
321
318
  coordinates = geo_json['geometry']['coordinates'][0]
322
- print("Vertices of the drawn polygon (Lat-Lon):")
319
+ print("Vertices of the drawn polygon (Lon-Lat):")
323
320
 
324
- # By default, drawn polygon coords are [ [lon, lat], [lon, lat], ... ]
325
- # The last coordinate repeats the first -> skip it with [:-1]
321
+ # Keep GeoJSON (lon,lat) format, skip last repeated coordinate
326
322
  for coord in coordinates[:-1]:
327
323
  lon = coord[0]
328
324
  lat = coord[1]
329
- drawn_polygon_vertices.append((lat, lon))
330
- print(f" - (lat, lon) = ({lat}, {lon})")
325
+ drawn_polygon_vertices.append((lon, lat))
326
+ print(f" - (lon, lat) = ({lon}, {lat})")
331
327
 
332
328
  draw_control.on_draw(handle_draw)
333
329
  m.add_control(draw_control)