voxcity 0.5.16__py3-none-any.whl → 0.5.18__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/downloader/__init__.py +0 -1
- voxcity/generator.py +6 -6
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/METADATA +3 -4
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/RECORD +8 -9
- voxcity/downloader/omt.py +0 -294
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/WHEEL +0 -0
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.16.dist-info → voxcity-0.5.18.dist-info}/top_level.txt +0 -0
voxcity/downloader/__init__.py
CHANGED
voxcity/generator.py
CHANGED
|
@@ -24,7 +24,7 @@ import os
|
|
|
24
24
|
from .downloader.mbfp import get_mbfp_gdf
|
|
25
25
|
from .downloader.osm import load_gdf_from_openstreetmap, load_land_cover_gdf_from_osm
|
|
26
26
|
from .downloader.oemj import save_oemj_as_geotiff
|
|
27
|
-
from .downloader.omt import load_gdf_from_openmaptiles
|
|
27
|
+
# from .downloader.omt import load_gdf_from_openmaptiles
|
|
28
28
|
from .downloader.eubucco import load_gdf_from_eubucco
|
|
29
29
|
from .downloader.overture import load_gdf_from_overture
|
|
30
30
|
from .downloader.citygml import load_buid_dem_veg_from_citygml
|
|
@@ -210,9 +210,9 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
210
210
|
elif source == 'EUBUCCO v0.1':
|
|
211
211
|
# European building database with height information
|
|
212
212
|
gdf = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
213
|
-
elif source == "OpenMapTiles":
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
# elif source == "OpenMapTiles":
|
|
214
|
+
# # Vector tiles service for building data
|
|
215
|
+
# gdf = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
216
216
|
elif source == "Overture":
|
|
217
217
|
# Open building dataset from Overture Maps Foundation
|
|
218
218
|
gdf = load_gdf_from_overture(rectangle_vertices)
|
|
@@ -255,8 +255,8 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
255
255
|
gdf_comp = load_gdf_from_openstreetmap(rectangle_vertices)
|
|
256
256
|
elif building_complementary_source == 'EUBUCCO v0.1':
|
|
257
257
|
gdf_comp = load_gdf_from_eubucco(rectangle_vertices, output_dir)
|
|
258
|
-
elif building_complementary_source == "OpenMapTiles":
|
|
259
|
-
|
|
258
|
+
# elif building_complementary_source == "OpenMapTiles":
|
|
259
|
+
# gdf_comp = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
260
260
|
elif building_complementary_source == "Overture":
|
|
261
261
|
gdf_comp = load_gdf_from_overture(rectangle_vertices)
|
|
262
262
|
elif building_complementary_source == "Local file":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.18
|
|
4
4
|
Summary: voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data
|
|
5
5
|
Author-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
|
|
6
6
|
Maintainer-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
|
|
@@ -22,7 +22,7 @@ Requires-Dist: pyproj
|
|
|
22
22
|
Requires-Dist: ipyleaflet
|
|
23
23
|
Requires-Dist: geopandas
|
|
24
24
|
Requires-Dist: rasterio==1.3.11
|
|
25
|
-
Requires-Dist: shapely
|
|
25
|
+
Requires-Dist: shapely
|
|
26
26
|
Requires-Dist: gdown
|
|
27
27
|
Requires-Dist: numpy
|
|
28
28
|
Requires-Dist: matplotlib
|
|
@@ -38,13 +38,12 @@ Requires-Dist: geemap
|
|
|
38
38
|
Requires-Dist: rio-cogeo
|
|
39
39
|
Requires-Dist: geopy
|
|
40
40
|
Requires-Dist: py-vox-io
|
|
41
|
-
Requires-Dist: mapbox_vector_tile
|
|
42
41
|
Requires-Dist: numba
|
|
43
42
|
Requires-Dist: reverse_geocoder
|
|
44
43
|
Requires-Dist: pycountry
|
|
45
44
|
Requires-Dist: seaborn
|
|
46
45
|
Requires-Dist: overturemaps
|
|
47
|
-
Requires-Dist: protobuf
|
|
46
|
+
Requires-Dist: protobuf
|
|
48
47
|
Requires-Dist: timezonefinder
|
|
49
48
|
Requires-Dist: astral
|
|
50
49
|
Requires-Dist: osmnx
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
|
|
2
|
-
voxcity/generator.py,sha256=
|
|
3
|
-
voxcity/downloader/__init__.py,sha256=
|
|
2
|
+
voxcity/generator.py,sha256=h1C2AaLQ1fqdU47UIUZTK1ox8d_c2I1Dql5mdscr7X8,52552
|
|
3
|
+
voxcity/downloader/__init__.py,sha256=o_T_EU7hZLGyXxX9wVWn1x-OAa3ThGYdnpgB1_2v3AE,151
|
|
4
4
|
voxcity/downloader/citygml.py,sha256=jVeHCLlJTf7k55OQGX0lZGQAngz_DD2V5TldSqRFlvc,36024
|
|
5
5
|
voxcity/downloader/eubucco.py,sha256=ln1YNaaOgJfxNfCtVbYaMm775-bUvpAA_LDv60_i22w,17875
|
|
6
6
|
voxcity/downloader/gee.py,sha256=O6HhQnUUumg_tTm4pP_cuyu5YjupDA1uKFxZWxD-i2E,23205
|
|
7
7
|
voxcity/downloader/mbfp.py,sha256=UXDVjsO0fnb0fSal9yqrSFEIBThnRmnutnp08kZTmCA,6595
|
|
8
8
|
voxcity/downloader/oemj.py,sha256=iDacTpiqn7RAXuqyEtHP29m0Cycwta5sMy9-GdvX3Fg,12293
|
|
9
|
-
voxcity/downloader/omt.py,sha256=wxds3u0RBLDuDw1LfOKuIE_zSgWbD4LWmWhnoYRpfmo,12578
|
|
10
9
|
voxcity/downloader/osm.py,sha256=kXiUedT7dwPOQ_4nxXptfeqNb5RKTIsQLG5km7Q8kKk,41645
|
|
11
10
|
voxcity/downloader/overture.py,sha256=4YG2DMwUSSyZKUw_o8cGhMmAkPJon82aPqOFBvrre-Y,11987
|
|
12
11
|
voxcity/downloader/utils.py,sha256=tz6wt4B9BhEOyvoF5OYXlr8rUd5cBEDedWL3j__oT70,3099
|
|
@@ -30,9 +29,9 @@ voxcity/utils/lc.py,sha256=h2yOWLUIrrummkyMyhRK5VbyrsPtslS0MJov_y0WGIQ,18925
|
|
|
30
29
|
voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
|
|
31
30
|
voxcity/utils/visualization.py,sha256=CpekVq4D0Nd69-zY3g1TZvtQ4dbKieMA4SJwah9gW8M,110225
|
|
32
31
|
voxcity/utils/weather.py,sha256=gy1Er0xuGXeSuVvh7VV1BebCzaBfdElUT1UGKAa815g,35619
|
|
33
|
-
voxcity-0.5.
|
|
34
|
-
voxcity-0.5.
|
|
35
|
-
voxcity-0.5.
|
|
36
|
-
voxcity-0.5.
|
|
37
|
-
voxcity-0.5.
|
|
38
|
-
voxcity-0.5.
|
|
32
|
+
voxcity-0.5.18.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
|
|
33
|
+
voxcity-0.5.18.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
|
|
34
|
+
voxcity-0.5.18.dist-info/METADATA,sha256=SNc0bjWU8EvXYXhaB2o5CKsBPOJCbNK2Xf88R6Gx8vA,26342
|
|
35
|
+
voxcity-0.5.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
36
|
+
voxcity-0.5.18.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
|
|
37
|
+
voxcity-0.5.18.dist-info/RECORD,,
|
voxcity/downloader/omt.py
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module for downloading and processing building data from OpenMapTiles vector tiles.
|
|
3
|
-
|
|
4
|
-
This module provides functionality to download and process building footprint data from
|
|
5
|
-
OpenMapTiles vector tile service. It handles downloading PBF tiles, extracting building
|
|
6
|
-
geometries, and converting them to GeoJSON format with standardized properties.
|
|
7
|
-
|
|
8
|
-
Key Features:
|
|
9
|
-
- Downloads vector tiles from OpenMapTiles API
|
|
10
|
-
- Extracts building footprints and properties
|
|
11
|
-
- Converts coordinates from tile-local to WGS84
|
|
12
|
-
- Standardizes building height information
|
|
13
|
-
- Handles both Polygon and MultiPolygon geometries
|
|
14
|
-
- Separates inner and outer rings of building footprints
|
|
15
|
-
|
|
16
|
-
Dependencies:
|
|
17
|
-
- mercantile: For tile calculations and coordinate transformations
|
|
18
|
-
- mapbox_vector_tile: For decoding PBF vector tiles
|
|
19
|
-
- shapely: For geometry operations
|
|
20
|
-
- pyproj: For coordinate system transformations
|
|
21
|
-
- geopandas: For working with geospatial data
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
import mercantile
|
|
25
|
-
import requests
|
|
26
|
-
import mapbox_vector_tile
|
|
27
|
-
from shapely.geometry import shape, mapping
|
|
28
|
-
from shapely.affinity import affine_transform
|
|
29
|
-
import shapely.ops
|
|
30
|
-
import json
|
|
31
|
-
from pyproj import Transformer
|
|
32
|
-
import json
|
|
33
|
-
import geopandas as gpd
|
|
34
|
-
|
|
35
|
-
def load_gdf_from_openmaptiles(rectangle_vertices, API_KEY):
|
|
36
|
-
"""Download and process building footprint data from OpenMapTiles vector tiles.
|
|
37
|
-
|
|
38
|
-
This function downloads vector tiles covering the specified area, extracts building
|
|
39
|
-
footprints, and converts them to a standardized format in a GeoDataFrame.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
rectangle_vertices (list): List of (lon, lat) tuples defining the bounding box corners.
|
|
43
|
-
The coordinates should be in WGS84 (EPSG:4326) format.
|
|
44
|
-
API_KEY (str): OpenMapTiles API key for authentication. Must be valid for the v3 endpoint.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
geopandas.GeoDataFrame: A GeoDataFrame containing building footprints with the following columns:
|
|
48
|
-
- geometry: Building footprint geometry in WGS84 coordinates
|
|
49
|
-
- height: Building height in meters
|
|
50
|
-
- min_height: Minimum height (e.g., for elevated structures) in meters
|
|
51
|
-
- confidence: Confidence score (-1.0 for OpenMapTiles data)
|
|
52
|
-
- is_inner: Boolean indicating if the polygon is an inner ring
|
|
53
|
-
- role: String indicating 'inner' or 'outer' ring
|
|
54
|
-
- id: Unique identifier for each building feature
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
- Uses zoom level 15 for optimal detail vs data size balance
|
|
58
|
-
- Converts coordinates from Web Mercator (EPSG:3857) to WGS84 (EPSG:4326)
|
|
59
|
-
- Handles both Polygon and MultiPolygon geometries
|
|
60
|
-
- Separates complex building footprints into their constituent parts
|
|
61
|
-
"""
|
|
62
|
-
# Extract longitudes and latitudes from vertices to find bounding box
|
|
63
|
-
lons = [coord[0] for coord in rectangle_vertices]
|
|
64
|
-
lats = [coord[1] for coord in rectangle_vertices]
|
|
65
|
-
|
|
66
|
-
min_lon = min(lons)
|
|
67
|
-
max_lon = max(lons)
|
|
68
|
-
min_lat = min(lats)
|
|
69
|
-
max_lat = max(lats)
|
|
70
|
-
|
|
71
|
-
# Use zoom level 15 which provides good detail for buildings while keeping data size manageable
|
|
72
|
-
zoom = 15
|
|
73
|
-
|
|
74
|
-
# Get list of tile coordinates that cover the bounding box at specified zoom level
|
|
75
|
-
tiles = list(mercantile.tiles(min_lon, min_lat, max_lon, max_lat, zoom))
|
|
76
|
-
|
|
77
|
-
building_features = []
|
|
78
|
-
|
|
79
|
-
# Set up coordinate transformer to convert from Web Mercator (EPSG:3857) to WGS84 (EPSG:4326)
|
|
80
|
-
# always_xy=True ensures longitude comes before latitude
|
|
81
|
-
transformer = Transformer.from_crs("EPSG:3857", "EPSG:4326", always_xy=True)
|
|
82
|
-
|
|
83
|
-
for tile in tiles:
|
|
84
|
-
x, y, z = tile.x, tile.y, tile.z
|
|
85
|
-
|
|
86
|
-
# Construct URL for vector tile using MapTiler API
|
|
87
|
-
tile_url = f'https://api.maptiler.com/tiles/v3/{z}/{x}/{y}.pbf?key={API_KEY}'
|
|
88
|
-
|
|
89
|
-
print(f'Downloading tile {z}/{x}/{y}')
|
|
90
|
-
response = requests.get(tile_url)
|
|
91
|
-
|
|
92
|
-
if response.status_code != 200:
|
|
93
|
-
print(f'Failed to download tile {z}/{x}/{y}')
|
|
94
|
-
continue
|
|
95
|
-
|
|
96
|
-
# Decode the Protocol Buffer (PBF) formatted vector tile
|
|
97
|
-
tile_data = mapbox_vector_tile.decode(response.content)
|
|
98
|
-
|
|
99
|
-
# Process building layer if it exists in the tile
|
|
100
|
-
if 'building' in tile_data:
|
|
101
|
-
building_layer = tile_data['building']
|
|
102
|
-
for feature in building_layer['features']:
|
|
103
|
-
# Convert feature geometry to shapely object for manipulation
|
|
104
|
-
geometry = shape(feature['geometry'])
|
|
105
|
-
|
|
106
|
-
# Vector tiles use local coordinates from 0-4096
|
|
107
|
-
# Need to transform these to real world coordinates
|
|
108
|
-
x_min, y_min = 0, 0
|
|
109
|
-
x_max, y_max = 4096, 4096
|
|
110
|
-
|
|
111
|
-
# Get tile bounds in Web Mercator coordinates
|
|
112
|
-
tile_bbox_mercator = mercantile.xy_bounds(x, y, z)
|
|
113
|
-
|
|
114
|
-
# Calculate scale factors to transform local tile coordinates to Web Mercator
|
|
115
|
-
scale_x = (tile_bbox_mercator.right - tile_bbox_mercator.left) / (x_max - x_min)
|
|
116
|
-
scale_y = (tile_bbox_mercator.bottom - tile_bbox_mercator.top) / (y_max - y_min)
|
|
117
|
-
|
|
118
|
-
# Create affine transformation matrix:
|
|
119
|
-
# [a b xoff]
|
|
120
|
-
# [d e yoff]
|
|
121
|
-
# [0 0 1 ]
|
|
122
|
-
a = scale_x # x scale
|
|
123
|
-
b = 0 # rotation
|
|
124
|
-
d = 0 # rotation
|
|
125
|
-
e = -scale_y # y scale (negative because y axis is flipped)
|
|
126
|
-
xoff = tile_bbox_mercator.left # x translation
|
|
127
|
-
yoff = tile_bbox_mercator.bottom # y translation
|
|
128
|
-
|
|
129
|
-
transform_matrix = [a, b, d, e, xoff, yoff]
|
|
130
|
-
|
|
131
|
-
# Transform geometry from tile coordinates to Web Mercator
|
|
132
|
-
transformed_geom = affine_transform(geometry, transform_matrix)
|
|
133
|
-
|
|
134
|
-
# Transform from Web Mercator to WGS84 geographic coordinates
|
|
135
|
-
transformed_geometry = shapely.ops.transform(transformer.transform, transformed_geom)
|
|
136
|
-
|
|
137
|
-
# Create standardized GeoJSON feature
|
|
138
|
-
geojson_feature = {
|
|
139
|
-
'type': 'Feature',
|
|
140
|
-
'geometry': mapping(transformed_geometry),
|
|
141
|
-
'properties': feature['properties']
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
building_features.append(geojson_feature)
|
|
145
|
-
|
|
146
|
-
# Convert features to standardized format with height information
|
|
147
|
-
converted_geojson_data = convert_geojson_format(building_features)
|
|
148
|
-
|
|
149
|
-
gdf = gpd.GeoDataFrame.from_features(converted_geojson_data)
|
|
150
|
-
gdf.set_crs(epsg=4326, inplace=True)
|
|
151
|
-
|
|
152
|
-
# Replace id column with index numbers
|
|
153
|
-
gdf['id'] = gdf.index
|
|
154
|
-
|
|
155
|
-
return gdf
|
|
156
|
-
|
|
157
|
-
def get_height_from_properties(properties):
|
|
158
|
-
"""Extract building height from properties, using levels if height is not available.
|
|
159
|
-
|
|
160
|
-
This function implements a fallback strategy for determining building heights:
|
|
161
|
-
1. First tries to use explicit render_height property
|
|
162
|
-
2. If not available, estimates height from number of building levels
|
|
163
|
-
3. Returns 0 if no valid height information is found
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
properties (dict): Dictionary containing building properties from OpenMapTiles.
|
|
167
|
-
Expected keys:
|
|
168
|
-
- render_height: Direct height specification in meters
|
|
169
|
-
- building:levels: Number of building floors/levels
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
float: Building height in meters. Values can be:
|
|
173
|
-
- Explicit height from render_height property
|
|
174
|
-
- Estimated height (levels * 5.0 meters per level)
|
|
175
|
-
- 0.0 if no valid height information is found
|
|
176
|
-
|
|
177
|
-
Notes:
|
|
178
|
-
- Assumes average floor height of 5 meters when estimating from levels
|
|
179
|
-
- Handles potential invalid values gracefully by returning 0
|
|
180
|
-
"""
|
|
181
|
-
# First try explicit render_height property
|
|
182
|
-
height = properties.get('render_height')
|
|
183
|
-
if height is not None:
|
|
184
|
-
try:
|
|
185
|
-
return float(height)
|
|
186
|
-
except ValueError:
|
|
187
|
-
pass
|
|
188
|
-
|
|
189
|
-
# If no height available, estimate from number of levels
|
|
190
|
-
# OpenMapTiles uses building:levels tag for number of floors
|
|
191
|
-
levels = properties.get('building:levels')
|
|
192
|
-
if levels is not None:
|
|
193
|
-
try:
|
|
194
|
-
return float(levels) * 5.0 # Assume average floor height of 5 meters
|
|
195
|
-
except ValueError:
|
|
196
|
-
pass
|
|
197
|
-
|
|
198
|
-
return 0 # Default height if no valid data found
|
|
199
|
-
|
|
200
|
-
def convert_geojson_format(features):
|
|
201
|
-
"""Convert building features to standardized format with height information.
|
|
202
|
-
|
|
203
|
-
This function processes raw OpenMapTiles building features into a standardized format,
|
|
204
|
-
handling complex geometries and adding consistent property attributes.
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
features (list): List of GeoJSON features containing building footprints.
|
|
208
|
-
Each feature should have:
|
|
209
|
-
- geometry: GeoJSON geometry (Polygon or MultiPolygon)
|
|
210
|
-
- properties: Dictionary of building properties
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
list: List of standardized GeoJSON features where:
|
|
214
|
-
- Complex MultiPolygons are split into individual Polygons
|
|
215
|
-
- Each Polygon ring (outer and inner) becomes a separate feature
|
|
216
|
-
- Properties are standardized to include:
|
|
217
|
-
- height: Building height in meters
|
|
218
|
-
- min_height: Minimum height in meters
|
|
219
|
-
- confidence: Set to -1.0 for OpenMapTiles data
|
|
220
|
-
- is_inner: Boolean flag for inner rings
|
|
221
|
-
- role: String indicating 'inner' or 'outer' ring
|
|
222
|
-
|
|
223
|
-
Notes:
|
|
224
|
-
- Preserves coordinate order as (longitude, latitude)
|
|
225
|
-
- Maintains topological relationships through is_inner and role properties
|
|
226
|
-
- Splits complex geometries for easier processing downstream
|
|
227
|
-
"""
|
|
228
|
-
new_features = []
|
|
229
|
-
|
|
230
|
-
for feature in features:
|
|
231
|
-
geometry = feature['geometry']
|
|
232
|
-
properties = feature['properties']
|
|
233
|
-
|
|
234
|
-
# Extract height information
|
|
235
|
-
height = get_height_from_properties(properties)
|
|
236
|
-
min_height = properties.get('render_min_height', 0)
|
|
237
|
-
try:
|
|
238
|
-
min_height = float(min_height)
|
|
239
|
-
except ValueError:
|
|
240
|
-
min_height = 0
|
|
241
|
-
|
|
242
|
-
# Create standardized properties dictionary
|
|
243
|
-
new_properties = {
|
|
244
|
-
'height': height,
|
|
245
|
-
'min_height': min_height,
|
|
246
|
-
'confidence': -1.0, # No confidence score available from OpenMapTiles
|
|
247
|
-
'is_inner': False # Will be set based on ring position
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
# Handle MultiPolygon geometries by splitting into separate Polygon features
|
|
251
|
-
if geometry['type'] == 'MultiPolygon':
|
|
252
|
-
for i, polygon_coords in enumerate(geometry['coordinates']):
|
|
253
|
-
# Process each ring in the polygon (outer ring + inner holes)
|
|
254
|
-
for j, ring in enumerate(polygon_coords):
|
|
255
|
-
ring_properties = new_properties.copy()
|
|
256
|
-
# First ring (j=0) is outer boundary, others are inner holes
|
|
257
|
-
ring_properties['is_inner'] = j > 0
|
|
258
|
-
ring_properties['role'] = 'inner' if j > 0 else 'outer'
|
|
259
|
-
|
|
260
|
-
# Create new geometry keeping coordinate order as (lon,lat)
|
|
261
|
-
new_geometry = {
|
|
262
|
-
'type': 'Polygon',
|
|
263
|
-
'coordinates': [ring]
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
new_feature = {
|
|
267
|
-
'type': 'Feature',
|
|
268
|
-
'properties': ring_properties,
|
|
269
|
-
'geometry': new_geometry
|
|
270
|
-
}
|
|
271
|
-
new_features.append(new_feature)
|
|
272
|
-
|
|
273
|
-
# Handle single Polygon geometries
|
|
274
|
-
elif geometry['type'] == 'Polygon':
|
|
275
|
-
# Process each ring in the polygon
|
|
276
|
-
for i, ring in enumerate(geometry['coordinates']):
|
|
277
|
-
ring_properties = new_properties.copy()
|
|
278
|
-
ring_properties['is_inner'] = i > 0
|
|
279
|
-
ring_properties['role'] = 'inner' if i > 0 else 'outer'
|
|
280
|
-
|
|
281
|
-
# Create new geometry keeping coordinate order as (lon,lat)
|
|
282
|
-
new_geometry = {
|
|
283
|
-
'type': 'Polygon',
|
|
284
|
-
'coordinates': [ring]
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
new_feature = {
|
|
288
|
-
'type': 'Feature',
|
|
289
|
-
'properties': ring_properties,
|
|
290
|
-
'geometry': new_geometry
|
|
291
|
-
}
|
|
292
|
-
new_features.append(new_feature)
|
|
293
|
-
|
|
294
|
-
return new_features
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|