voxcity 0.5.13__py3-none-any.whl → 0.5.15__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.

@@ -3,12 +3,33 @@ Utility functions for geographic operations and coordinate transformations.
3
3
 
4
4
  This module provides various utility functions for working with geographic data,
5
5
  including coordinate transformations, distance calculations, geocoding, and building
6
- polygon processing.
6
+ polygon processing. It supports operations such as:
7
+
8
+ - Tile coordinate calculations and quadkey conversions
9
+ - Geographic distance calculations (Haversine and geodetic)
10
+ - Coordinate system transformations
11
+ - Polygon and GeoDataFrame operations
12
+ - Raster file processing and merging
13
+ - Geocoding and reverse geocoding
14
+ - Timezone and location information retrieval
15
+ - Building polygon validation and processing
16
+
17
+ The module uses several external libraries for geographic operations:
18
+ - pyproj: For coordinate transformations and geodetic calculations
19
+ - geopandas: For handling geographic data frames
20
+ - rasterio: For raster file operations
21
+ - shapely: For geometric operations
22
+ - geopy: For geocoding services
23
+ - timezonefinder: For timezone lookups
7
24
  """
8
25
 
26
+ # Standard library imports
9
27
  import os
10
28
  import math
11
29
  from math import radians, sin, cos, sqrt, atan2
30
+ from datetime import datetime
31
+
32
+ # Third-party geographic processing libraries
12
33
  import numpy as np
13
34
  from pyproj import Geod, Transformer
14
35
  import geopandas as gpd
@@ -19,32 +40,40 @@ from rasterio.mask import mask
19
40
  from shapely.geometry import Polygon, box
20
41
  from fiona.crs import from_epsg
21
42
  from rtree import index
43
+
44
+ # Geocoding and location services
22
45
  from geopy.geocoders import Nominatim
23
46
  from geopy.exc import GeocoderTimedOut, GeocoderServiceError
24
47
  from geopy.extra.rate_limiter import RateLimiter
25
- import warnings
26
48
  import reverse_geocoder as rg
27
49
  import pycountry
28
50
 
51
+ # Timezone handling
29
52
  from timezonefinder import TimezoneFinder
30
53
  import pytz
31
- from datetime import datetime
32
54
 
55
+ # Suppress rasterio warnings for non-georeferenced files
56
+ import warnings
33
57
  warnings.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning)
34
58
 
35
- floor_height = 2.5
59
+ # Global constants
60
+ floor_height = 2.5 # Standard floor height in meters used for building height calculations
36
61
 
37
62
  def tile_from_lat_lon(lat, lon, level_of_detail):
38
63
  """
39
64
  Convert latitude/longitude coordinates to tile coordinates at a given zoom level.
65
+ Uses the Web Mercator projection (EPSG:3857) commonly used in web mapping.
40
66
 
41
67
  Args:
42
- lat (float): Latitude in degrees
43
- lon (float): Longitude in degrees
44
- level_of_detail (int): Zoom level
68
+ lat (float): Latitude in degrees (-90 to 90)
69
+ lon (float): Longitude in degrees (-180 to 180)
70
+ level_of_detail (int): Zoom level (0-23, where 0 is the entire world)
45
71
 
46
72
  Returns:
47
- tuple: (tile_x, tile_y) tile coordinates
73
+ tuple: (tile_x, tile_y) tile coordinates in the global tile grid
74
+
75
+ Example:
76
+ >>> tile_x, tile_y = tile_from_lat_lon(35.6762, 139.6503, 12) # Tokyo at zoom 12
48
77
  """
49
78
  # Convert latitude to radians and calculate sine
50
79
  sin_lat = math.sin(lat * math.pi / 180)
@@ -70,11 +99,20 @@ def quadkey_to_tile(quadkey):
70
99
  Each digit in the quadkey represents a tile at a zoom level, with each subsequent digit
71
100
  representing a more detailed zoom level.
72
101
 
102
+ The quadkey numbering scheme:
103
+ - 0: Top-left quadrant
104
+ - 1: Top-right quadrant
105
+ - 2: Bottom-left quadrant
106
+ - 3: Bottom-right quadrant
107
+
73
108
  Args:
74
- quadkey (str): Quadkey string
109
+ quadkey (str): Quadkey string (e.g., "120" for zoom level 3)
75
110
 
76
111
  Returns:
77
112
  tuple: (tile_x, tile_y, level_of_detail) tile coordinates and zoom level
113
+
114
+ Example:
115
+ >>> x, y, zoom = quadkey_to_tile("120") # Returns coordinates at zoom level 3
78
116
  """
79
117
  tile_x = tile_y = 0
80
118
  level_of_detail = len(quadkey)
@@ -101,25 +139,42 @@ def quadkey_to_tile(quadkey):
101
139
  def initialize_geod():
102
140
  """
103
141
  Initialize a Geod object for geodetic calculations using WGS84 ellipsoid.
104
- The WGS84 ellipsoid is the standard reference system used by GPS.
142
+ The WGS84 ellipsoid (EPSG:4326) is the standard reference system used by GPS
143
+ and most modern mapping applications.
144
+
145
+ The Geod object provides methods for:
146
+ - Forward geodetic calculations (direct)
147
+ - Inverse geodetic calculations (inverse)
148
+ - Area calculations
149
+ - Line length calculations
105
150
 
106
151
  Returns:
107
- Geod: Initialized Geod object
152
+ Geod: Initialized Geod object for WGS84 calculations
153
+
154
+ Example:
155
+ >>> geod = initialize_geod()
156
+ >>> fwd_az, back_az, dist = geod.inv(lon1, lat1, lon2, lat2)
108
157
  """
109
158
  return Geod(ellps='WGS84')
110
159
 
111
160
  def calculate_distance(geod, lon1, lat1, lon2, lat2):
112
161
  """
113
- Calculate geodetic distance between two points.
114
- Uses inverse geodetic computation to find the shortest distance along the ellipsoid.
162
+ Calculate geodetic distance between two points on the Earth's surface.
163
+ Uses inverse geodetic computation to find the shortest distance along the ellipsoid,
164
+ which is more accurate than great circle (spherical) calculations.
115
165
 
116
166
  Args:
117
- geod (Geod): Geod object for calculations
118
- lon1, lat1 (float): Coordinates of first point
119
- lon2, lat2 (float): Coordinates of second point
167
+ geod (Geod): Geod object for calculations, initialized with WGS84
168
+ lon1, lat1 (float): Coordinates of first point in decimal degrees
169
+ lon2, lat2 (float): Coordinates of second point in decimal degrees
120
170
 
121
171
  Returns:
122
- float: Distance in meters
172
+ float: Distance in meters between the two points along the ellipsoid
173
+
174
+ Example:
175
+ >>> geod = initialize_geod()
176
+ >>> distance = calculate_distance(geod, 139.6503, 35.6762,
177
+ ... -74.0060, 40.7128) # Tokyo to NYC
123
178
  """
124
179
  # inv() returns forward azimuth, back azimuth, and distance
125
180
  _, _, dist = geod.inv(lon1, lat1, lon2, lat2)
@@ -127,43 +182,65 @@ def calculate_distance(geod, lon1, lat1, lon2, lat2):
127
182
 
128
183
  def normalize_to_one_meter(vector, distance_in_meters):
129
184
  """
130
- Normalize a vector to represent one meter.
131
- Useful for creating unit vectors in geographic calculations.
185
+ Normalize a vector to represent one meter in geographic space.
186
+ Useful for creating unit vectors in geographic calculations, particularly
187
+ when working with distance-based operations or scaling geographic features.
132
188
 
133
189
  Args:
134
- vector (numpy.ndarray): Vector to normalize
135
- distance_in_meters (float): Current distance in meters
190
+ vector (numpy.ndarray): Vector to normalize, typically a direction vector
191
+ distance_in_meters (float): Current distance in meters that the vector represents
136
192
 
137
193
  Returns:
138
- numpy.ndarray: Normalized vector
194
+ numpy.ndarray: Normalized vector where magnitude represents 1 meter
195
+
196
+ Example:
197
+ >>> direction = np.array([3.0, 4.0]) # Vector of length 5
198
+ >>> unit_meter = normalize_to_one_meter(direction, 5.0)
139
199
  """
140
200
  return vector * (1 / distance_in_meters)
141
201
 
142
202
  def setup_transformer(from_crs, to_crs):
143
203
  """
144
- Set up a coordinate transformer between two CRS.
145
- The always_xy=True parameter ensures consistent handling of coordinate order.
204
+ Set up a coordinate transformer between two Coordinate Reference Systems (CRS).
205
+ The always_xy=True parameter ensures consistent handling of coordinate order
206
+ by always using (x,y) or (longitude,latitude) order regardless of CRS definition.
207
+
208
+ Common CRS codes:
209
+ - EPSG:4326 - WGS84 (latitude/longitude)
210
+ - EPSG:3857 - Web Mercator
211
+ - EPSG:2263 - NY State Plane
146
212
 
147
213
  Args:
148
- from_crs: Source coordinate reference system
149
- to_crs: Target coordinate reference system
214
+ from_crs: Source coordinate reference system (EPSG code, proj4 string, or CRS dict)
215
+ to_crs: Target coordinate reference system (EPSG code, proj4 string, or CRS dict)
150
216
 
151
217
  Returns:
152
- Transformer: Initialized transformer object
218
+ Transformer: Initialized transformer object for coordinate conversion
219
+
220
+ Example:
221
+ >>> transformer = setup_transformer("EPSG:4326", "EPSG:3857")
222
+ >>> x, y = transformer.transform(longitude, latitude)
153
223
  """
154
224
  return Transformer.from_crs(from_crs, to_crs, always_xy=True)
155
225
 
156
226
  def transform_coords(transformer, lon, lat):
157
227
  """
158
- Transform coordinates using provided transformer.
159
- Includes error handling for invalid transformations and infinite values.
228
+ Transform coordinates using provided transformer with error handling.
229
+ Includes validation for infinite values that may result from invalid transformations
230
+ or coordinates outside the valid range for the target CRS.
160
231
 
161
232
  Args:
162
- transformer (Transformer): Coordinate transformer
163
- lon, lat (float): Input coordinates
233
+ transformer (Transformer): Coordinate transformer from setup_transformer()
234
+ lon, lat (float): Input coordinates in the source CRS
164
235
 
165
236
  Returns:
166
- tuple: (x, y) transformed coordinates or (None, None) if transformation fails
237
+ tuple: (x, y) transformed coordinates in the target CRS, or (None, None) if transformation fails
238
+
239
+ Example:
240
+ >>> transformer = setup_transformer("EPSG:4326", "EPSG:3857")
241
+ >>> x, y = transform_coords(transformer, -74.0060, 40.7128) # NYC coordinates
242
+ >>> if x is not None:
243
+ ... print(f"Transformed coordinates: ({x}, {y})")
167
244
  """
168
245
  try:
169
246
  x, y = transformer.transform(lon, lat)
@@ -176,28 +253,44 @@ def transform_coords(transformer, lon, lat):
176
253
 
177
254
  def create_polygon(vertices):
178
255
  """
179
- Create a Shapely polygon from vertices.
180
- Input vertices are already in (lon,lat) format required by Shapely.
256
+ Create a Shapely polygon from a list of vertices.
257
+ Input vertices must be in (longitude, latitude) format as required by Shapely.
258
+ The polygon will be automatically closed if the first and last vertices don't match.
181
259
 
182
260
  Args:
183
- vertices (list): List of (lon, lat) coordinate pairs
261
+ vertices (list): List of (longitude, latitude) coordinate pairs forming the polygon.
262
+ The coordinates should be in counter-clockwise order for exterior rings
263
+ and clockwise order for interior rings (holes).
184
264
 
185
265
  Returns:
186
- Polygon: Shapely polygon object
266
+ Polygon: Shapely polygon object that can be used for spatial operations
267
+
268
+ Example:
269
+ >>> vertices = [(0, 0), (1, 0), (1, 1), (0, 1)] # Square
270
+ >>> polygon = create_polygon(vertices)
271
+ >>> print(f"Polygon area: {polygon.area}")
187
272
  """
188
273
  return Polygon(vertices)
189
274
 
190
275
  def create_geodataframe(polygon, crs=4326):
191
276
  """
192
- Create a GeoDataFrame from a polygon.
193
- Default CRS is WGS84 (EPSG:4326).
277
+ Create a GeoDataFrame from a Shapely polygon.
278
+ Default CRS is WGS84 (EPSG:4326) for geographic coordinates.
279
+ The GeoDataFrame provides additional functionality for spatial operations,
280
+ data analysis, and export to various geographic formats.
194
281
 
195
282
  Args:
196
- polygon (Polygon): Shapely polygon object
197
- crs (int): Coordinate reference system EPSG code
283
+ polygon (Polygon): Shapely polygon object to convert
284
+ crs (int): Coordinate reference system EPSG code (default: 4326 for WGS84)
198
285
 
199
286
  Returns:
200
- GeoDataFrame: GeoDataFrame containing the polygon
287
+ GeoDataFrame: GeoDataFrame containing the polygon with specified CRS
288
+
289
+ Example:
290
+ >>> vertices = [(0, 0), (1, 0), (1, 1), (0, 1)]
291
+ >>> polygon = create_polygon(vertices)
292
+ >>> gdf = create_geodataframe(polygon)
293
+ >>> gdf.to_file("polygon.geojson", driver="GeoJSON")
201
294
  """
202
295
  return gpd.GeoDataFrame({'geometry': [polygon]}, crs=from_epsg(crs))
203
296
 
@@ -229,14 +322,19 @@ def haversine_distance(lon1, lat1, lon2, lat2):
229
322
 
230
323
  def get_raster_bbox(raster_path):
231
324
  """
232
- Get bounding box of a raster file.
233
- Returns a rectangular polygon representing the spatial extent of the raster.
325
+ Get the bounding box of a raster file in its native coordinate system.
326
+ Returns a rectangular polygon representing the spatial extent of the raster,
327
+ which can be used for spatial queries and intersection tests.
234
328
 
235
329
  Args:
236
- raster_path (str): Path to raster file
330
+ raster_path (str): Path to the raster file (GeoTIFF, IMG, etc.)
237
331
 
238
332
  Returns:
239
- box: Shapely box representing the raster bounds
333
+ box: Shapely box representing the raster bounds in the raster's CRS
334
+
335
+ Example:
336
+ >>> bbox = get_raster_bbox("elevation.tif")
337
+ >>> print(f"Raster extent: {bbox.bounds}") # (minx, miny, maxx, maxy)
240
338
  """
241
339
  with rasterio.open(raster_path) as src:
242
340
  bounds = src.bounds
@@ -244,15 +342,21 @@ def get_raster_bbox(raster_path):
244
342
 
245
343
  def raster_intersects_polygon(raster_path, polygon):
246
344
  """
247
- Check if a raster intersects with a polygon.
248
- Transforms coordinates to WGS84 if needed before checking intersection.
345
+ Check if a raster file's extent intersects with a given polygon.
346
+ Automatically handles coordinate system transformations by converting
347
+ the raster bounds to WGS84 (EPSG:4326) if needed before the intersection test.
249
348
 
250
349
  Args:
251
- raster_path (str): Path to raster file
252
- polygon (Polygon): Shapely polygon to check intersection with
350
+ raster_path (str): Path to the raster file to check
351
+ polygon (Polygon): Shapely polygon to test intersection with (in WGS84)
253
352
 
254
353
  Returns:
255
- bool: True if raster intersects polygon, False otherwise
354
+ bool: True if raster intersects or contains the polygon, False otherwise
355
+
356
+ Example:
357
+ >>> aoi = create_polygon([(lon1, lat1), (lon2, lat2), ...]) # Area of interest
358
+ >>> if raster_intersects_polygon("dem.tif", aoi):
359
+ ... print("Raster covers the area of interest")
256
360
  """
257
361
  with rasterio.open(raster_path) as src:
258
362
  bounds = src.bounds
@@ -265,12 +369,17 @@ def raster_intersects_polygon(raster_path, polygon):
265
369
 
266
370
  def save_raster(input_path, output_path):
267
371
  """
268
- Save a copy of a raster file.
269
- Creates a direct copy without any transformation or modification.
372
+ Create a copy of a raster file at a new location.
373
+ Performs a direct file copy without any transformation or modification,
374
+ preserving all metadata, georeferencing, and pixel values.
270
375
 
271
376
  Args:
272
377
  input_path (str): Source raster file path
273
- output_path (str): Destination raster file path
378
+ output_path (str): Destination path for the copied raster
379
+
380
+ Example:
381
+ >>> save_raster("original.tif", "backup/copy.tif")
382
+ >>> print("Copied original file to: backup/copy.tif")
274
383
  """
275
384
  import shutil
276
385
  shutil.copy(input_path, output_path)
@@ -278,11 +387,23 @@ def save_raster(input_path, output_path):
278
387
 
279
388
  def merge_geotiffs(geotiff_files, output_dir):
280
389
  """
281
- Merge multiple GeoTIFF files into a single file.
390
+ Merge multiple GeoTIFF files into a single mosaic.
391
+ Handles edge matching and overlapping areas between adjacent rasters.
392
+ The output will have the same coordinate system and data type as the input files.
393
+
394
+ Important considerations:
395
+ - All input files should have the same coordinate system
396
+ - All input files should have the same data type
397
+ - Overlapping areas are handled by taking the first value encountered
282
398
 
283
399
  Args:
284
- geotiff_files (list): List of GeoTIFF file paths to merge
285
- output_dir (str): Directory to save merged output
400
+ geotiff_files (list): List of paths to GeoTIFF files to merge
401
+ output_dir (str): Directory where the merged output will be saved
402
+
403
+ Example:
404
+ >>> files = ["tile1.tif", "tile2.tif", "tile3.tif"]
405
+ >>> merge_geotiffs(files, "output_directory")
406
+ >>> print("Merged output saved to: output_directory/lulc.tif")
286
407
  """
287
408
  if not geotiff_files:
288
409
  return
@@ -338,13 +459,25 @@ def convert_format_lat_lon(input_coords):
338
459
 
339
460
  def get_coordinates_from_cityname(place_name):
340
461
  """
341
- Get coordinates for a city name using geocoding.
462
+ Geocode a city name to get its coordinates using OpenStreetMap's Nominatim service.
463
+ Includes rate limiting and error handling to comply with Nominatim's usage policy.
464
+
465
+ Note:
466
+ - Results may vary based on the specificity of the place name
467
+ - For better results, include country or state information
468
+ - Service has usage limits and may timeout
342
469
 
343
470
  Args:
344
- place_name (str): Name of city to geocode
471
+ place_name (str): Name of the city to geocode (e.g., "Tokyo, Japan")
345
472
 
346
473
  Returns:
347
- tuple: (longitude, latitude) or None if geocoding fails
474
+ tuple: (latitude, longitude) coordinates or None if geocoding fails
475
+
476
+ Example:
477
+ >>> coords = get_coordinates_from_cityname("Paris, France")
478
+ >>> if coords:
479
+ ... lat, lon = coords
480
+ ... print(f"Paris coordinates: {lat}, {lon}")
348
481
  """
349
482
  # Initialize geocoder with user agent
350
483
  geolocator = Nominatim(user_agent="my_geocoding_script")
@@ -363,13 +496,25 @@ def get_coordinates_from_cityname(place_name):
363
496
 
364
497
  def get_city_country_name_from_rectangle(coordinates):
365
498
  """
366
- Get city and country name from rectangle coordinates.
499
+ Get the city and country name for a location defined by a rectangle.
500
+ Uses reverse geocoding to find the nearest named place to the rectangle's center.
501
+
502
+ The function:
503
+ 1. Calculates the center point of the rectangle
504
+ 2. Performs reverse geocoding with rate limiting
505
+ 3. Extracts city and country information from the result
367
506
 
368
507
  Args:
369
- coordinates (list): List of (lon, lat) coordinates defining rectangle
508
+ coordinates (list): List of (longitude, latitude) coordinates defining the rectangle
370
509
 
371
510
  Returns:
372
511
  str: String in format "city/ country" or None if lookup fails
512
+
513
+ Example:
514
+ >>> coords = [(139.65, 35.67), (139.66, 35.67),
515
+ ... (139.66, 35.68), (139.65, 35.68)]
516
+ >>> location = get_city_country_name_from_rectangle(coords)
517
+ >>> print(f"Location: {location}") # e.g., "Shibuya/ Japan"
373
518
  """
374
519
  # Calculate center point of rectangle
375
520
  longitudes = [coord[0] for coord in coordinates]
@@ -398,13 +543,29 @@ def get_city_country_name_from_rectangle(coordinates):
398
543
 
399
544
  def get_timezone_info(rectangle_coords):
400
545
  """
401
- Get timezone and central meridian info for a location.
546
+ Get timezone and central meridian information for a location.
547
+ Uses the rectangle's center point to determine the local timezone and
548
+ calculates the central meridian based on the UTC offset.
549
+
550
+ The function provides:
551
+ 1. Local timezone identifier (e.g., "America/New_York")
552
+ 2. UTC offset (e.g., "UTC-04:00")
553
+ 3. Central meridian longitude for the timezone
402
554
 
403
555
  Args:
404
- rectangle_coords (list): List of (lon, lat) coordinates defining rectangle
556
+ rectangle_coords (list): List of (longitude, latitude) coordinates defining the area
405
557
 
406
558
  Returns:
407
- tuple: (timezone string, central meridian longitude string)
559
+ tuple: (timezone string with UTC offset, central meridian longitude string)
560
+
561
+ Raises:
562
+ ValueError: If timezone cannot be determined for the given location
563
+
564
+ Example:
565
+ >>> coords = [(139.65, 35.67), (139.66, 35.67),
566
+ ... (139.66, 35.68), (139.65, 35.68)]
567
+ >>> tz, meridian = get_timezone_info(coords)
568
+ >>> print(f"Timezone: {tz}, Meridian: {meridian}") # e.g., "UTC+09:00, 135.00000"
408
569
  """
409
570
  # Calculate center point of rectangle
410
571
  longitudes = [coord[0] for coord in rectangle_coords]
@@ -434,13 +595,29 @@ def get_timezone_info(rectangle_coords):
434
595
 
435
596
  def validate_polygon_coordinates(geometry):
436
597
  """
437
- Validate and close polygon coordinate rings.
598
+ Validate and ensure proper closure of polygon coordinate rings.
599
+ Performs validation and correction of GeoJSON polygon geometries according to
600
+ the GeoJSON specification requirements.
601
+
602
+ Validation checks:
603
+ 1. Geometry type (Polygon or MultiPolygon)
604
+ 2. Ring closure (first point equals last point)
605
+ 3. Minimum number of points (4, including closure)
438
606
 
439
607
  Args:
440
- geometry (dict): GeoJSON geometry object
608
+ geometry (dict): GeoJSON geometry object with 'type' and 'coordinates' properties
441
609
 
442
610
  Returns:
443
- bool: True if valid polygon coordinates, False otherwise
611
+ bool: True if polygon coordinates are valid or were successfully corrected,
612
+ False if validation failed
613
+
614
+ Example:
615
+ >>> geom = {
616
+ ... "type": "Polygon",
617
+ ... "coordinates": [[[0,0], [1,0], [1,1], [0,1]]] # Not closed
618
+ ... }
619
+ >>> if validate_polygon_coordinates(geom):
620
+ ... print("Polygon is valid") # Will close the ring automatically
444
621
  """
445
622
  if geometry['type'] == 'Polygon':
446
623
  for ring in geometry['coordinates']:
@@ -465,12 +642,40 @@ def validate_polygon_coordinates(geometry):
465
642
  def create_building_polygons(filtered_buildings):
466
643
  """
467
644
  Create building polygons with properties from filtered GeoJSON features.
645
+ Processes a list of GeoJSON building features to create Shapely polygons
646
+ with associated height and other properties, while also building a spatial index.
647
+
648
+ Processing steps:
649
+ 1. Extract and validate coordinates
650
+ 2. Create Shapely polygons
651
+ 3. Process building properties (height, levels, etc.)
652
+ 4. Build spatial index for efficient querying
653
+
654
+ Height calculation rules:
655
+ - Use explicit height if available
656
+ - Calculate from levels * floor_height if height not available
657
+ - Calculate from floors * floor_height if levels not available
658
+ - Use NaN if no height information available
468
659
 
469
660
  Args:
470
- filtered_buildings (list): List of GeoJSON building features
661
+ filtered_buildings (list): List of GeoJSON building features with properties
471
662
 
472
663
  Returns:
473
- tuple: (list of building polygons with properties, spatial index)
664
+ tuple: (
665
+ list of tuples (polygon, height, min_height, is_inner, feature_id),
666
+ rtree spatial index for the polygons
667
+ )
668
+
669
+ Example:
670
+ >>> buildings = [
671
+ ... {
672
+ ... "type": "Feature",
673
+ ... "geometry": {"type": "Polygon", "coordinates": [...]},
674
+ ... "properties": {"height": 30, "levels": 10}
675
+ ... },
676
+ ... # ... more buildings ...
677
+ ... ]
678
+ >>> polygons, spatial_idx = create_building_polygons(buildings)
474
679
  """
475
680
  building_polygons = []
476
681
  idx = index.Index()
@@ -553,13 +758,19 @@ def create_building_polygons(filtered_buildings):
553
758
  def get_country_name(lon, lat):
554
759
  """
555
760
  Get country name from coordinates using reverse geocoding.
761
+ Uses a local database for fast reverse geocoding to country level,
762
+ then converts the country code to full name using pycountry.
556
763
 
557
764
  Args:
558
- lon (float): Longitude
559
- lat (float): Latitude
765
+ lon (float): Longitude in decimal degrees
766
+ lat (float): Latitude in decimal degrees
560
767
 
561
768
  Returns:
562
- str: Country name or None if lookup fails
769
+ str: Full country name or None if lookup fails
770
+
771
+ Example:
772
+ >>> country = get_country_name(139.6503, 35.6762)
773
+ >>> print(f"Country: {country}") # "Japan"
563
774
  """
564
775
  # Use reverse geocoder to get country code
565
776
  results = rg.search((lat, lon))