voxcity 0.6.10__tar.gz → 0.6.11__tar.gz
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-0.6.10 → voxcity-0.6.11}/PKG-INFO +1 -1
- {voxcity-0.6.10 → voxcity-0.6.11}/pyproject.toml +1 -1
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/citygml.py +124 -8
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/generator.py +1136 -1136
- {voxcity-0.6.10 → voxcity-0.6.11}/AUTHORS.rst +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/LICENSE +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/README.md +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/oemj.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/osm.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/overture.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/cityles.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/obj.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/grid.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/mesh.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/polygon.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/solar.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/view.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/__init__.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/weather.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "voxcity"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.11"
|
|
4
4
|
description = "voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -398,6 +398,43 @@ def extract_vegetation_info(file_path, namespaces):
|
|
|
398
398
|
try:
|
|
399
399
|
tree = ET.parse(file_path)
|
|
400
400
|
root = tree.getroot()
|
|
401
|
+
|
|
402
|
+
# Build namespaces dynamically from the file and merge with provided ones
|
|
403
|
+
nsmap = root.nsmap or {}
|
|
404
|
+
# Fallbacks in case discovery fails
|
|
405
|
+
fallback_ns = {
|
|
406
|
+
'core': 'http://www.opengis.net/citygml/2.0',
|
|
407
|
+
'bldg': 'http://www.opengis.net/citygml/building/2.0',
|
|
408
|
+
'gml': 'http://www.opengis.net/gml',
|
|
409
|
+
'uro': 'https://www.geospatial.jp/iur/uro/3.0',
|
|
410
|
+
'dem': 'http://www.opengis.net/citygml/relief/2.0',
|
|
411
|
+
'veg': 'http://www.opengis.net/citygml/vegetation/2.0'
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
def pick_ns(prefix, keyword=None, fallback_key=None):
|
|
415
|
+
# Prefer provided namespaces if valid
|
|
416
|
+
if isinstance(namespaces, dict) and prefix in namespaces and namespaces[prefix]:
|
|
417
|
+
return namespaces[prefix]
|
|
418
|
+
# Then try from document nsmap
|
|
419
|
+
uri = nsmap.get(prefix)
|
|
420
|
+
if uri:
|
|
421
|
+
return uri
|
|
422
|
+
# Then try keyword search
|
|
423
|
+
if keyword:
|
|
424
|
+
for v in nsmap.values():
|
|
425
|
+
if isinstance(v, str) and keyword in v:
|
|
426
|
+
return v
|
|
427
|
+
# Finally fallback
|
|
428
|
+
return fallback_ns[fallback_key or prefix]
|
|
429
|
+
|
|
430
|
+
ns = {
|
|
431
|
+
'core': pick_ns('core', keyword='citygml', fallback_key='core'),
|
|
432
|
+
'bldg': pick_ns('bldg', keyword='building', fallback_key='bldg'),
|
|
433
|
+
'gml': pick_ns('gml', keyword='gml', fallback_key='gml'),
|
|
434
|
+
'uro': pick_ns('uro', keyword='iur/uro', fallback_key='uro'),
|
|
435
|
+
'dem': pick_ns('dem', keyword='relief', fallback_key='dem'),
|
|
436
|
+
'veg': pick_ns('veg', keyword='vegetation', fallback_key='veg')
|
|
437
|
+
}
|
|
401
438
|
except Exception as e:
|
|
402
439
|
print(f"Error parsing CityGML file {Path(file_path).name}: {e}")
|
|
403
440
|
return vegetation_elements
|
|
@@ -405,8 +442,8 @@ def extract_vegetation_info(file_path, namespaces):
|
|
|
405
442
|
# Helper: parse polygons in <gml:MultiSurface> or <veg:lodXMultiSurface>
|
|
406
443
|
def parse_lod_multisurface(lod_elem):
|
|
407
444
|
polygons = []
|
|
408
|
-
for poly_node in lod_elem.findall('.//gml:Polygon',
|
|
409
|
-
ring_node = poly_node.find('.//gml:exterior//gml:LinearRing//gml:posList',
|
|
445
|
+
for poly_node in lod_elem.findall('.//gml:Polygon', ns):
|
|
446
|
+
ring_node = poly_node.find('.//gml:exterior//gml:LinearRing//gml:posList', ns)
|
|
410
447
|
if ring_node is None or ring_node.text is None:
|
|
411
448
|
continue
|
|
412
449
|
coords_text = ring_node.text.strip().split()
|
|
@@ -442,25 +479,68 @@ def extract_vegetation_info(file_path, namespaces):
|
|
|
442
479
|
"lod0MultiSurface", "lod1MultiSurface", "lod2MultiSurface", "lod3MultiSurface", "lod4MultiSurface"
|
|
443
480
|
]
|
|
444
481
|
for lod_tag in geometry_lods:
|
|
445
|
-
lod_elem = veg_elem.find(f'.//veg:{lod_tag}',
|
|
482
|
+
lod_elem = veg_elem.find(f'.//veg:{lod_tag}', ns)
|
|
446
483
|
if lod_elem is not None:
|
|
447
484
|
geom = parse_lod_multisurface(lod_elem)
|
|
448
485
|
if geom is not None:
|
|
449
486
|
return geom
|
|
450
487
|
return None
|
|
451
488
|
|
|
489
|
+
def compute_lod_height(veg_elem):
|
|
490
|
+
"""
|
|
491
|
+
Fallback: compute vegetation height from Z values in any gml:posList
|
|
492
|
+
under the available LOD geometry elements. Returns (max_z - min_z)
|
|
493
|
+
if any Z values are found; otherwise None.
|
|
494
|
+
"""
|
|
495
|
+
z_values = []
|
|
496
|
+
geometry_lods = [
|
|
497
|
+
"lod0Geometry", "lod1Geometry", "lod2Geometry", "lod3Geometry", "lod4Geometry",
|
|
498
|
+
"lod0MultiSurface", "lod1MultiSurface", "lod2MultiSurface", "lod3MultiSurface", "lod4MultiSurface"
|
|
499
|
+
]
|
|
500
|
+
try:
|
|
501
|
+
for lod_tag in geometry_lods:
|
|
502
|
+
lod_elem = veg_elem.find(f'.//veg:{lod_tag}', ns)
|
|
503
|
+
if lod_elem is None:
|
|
504
|
+
continue
|
|
505
|
+
for pos_list in lod_elem.findall('.//gml:posList', ns):
|
|
506
|
+
if pos_list.text is None:
|
|
507
|
+
continue
|
|
508
|
+
coords_text = pos_list.text.strip().split()
|
|
509
|
+
# Expect triplets (x,y,z) or (lat,lon,z). Z should be each 3rd value
|
|
510
|
+
for i in range(2, len(coords_text), 3):
|
|
511
|
+
try:
|
|
512
|
+
z = float(coords_text[i])
|
|
513
|
+
if not np.isinf(z) and not np.isnan(z):
|
|
514
|
+
z_values.append(z)
|
|
515
|
+
except Exception:
|
|
516
|
+
continue
|
|
517
|
+
if z_values:
|
|
518
|
+
return float(max(z_values) - min(z_values))
|
|
519
|
+
except Exception:
|
|
520
|
+
pass
|
|
521
|
+
return None
|
|
522
|
+
|
|
452
523
|
# 1) PlantCover
|
|
453
|
-
for plant_cover in root.findall('.//veg:PlantCover',
|
|
524
|
+
for plant_cover in root.findall('.//veg:PlantCover', ns):
|
|
454
525
|
cover_id = plant_cover.get('{http://www.opengis.net/gml}id')
|
|
455
|
-
avg_height_elem = plant_cover.find('.//veg:averageHeight',
|
|
526
|
+
avg_height_elem = plant_cover.find('.//veg:averageHeight', ns)
|
|
456
527
|
if avg_height_elem is not None and avg_height_elem.text:
|
|
457
528
|
try:
|
|
458
529
|
vegetation_height = float(avg_height_elem.text)
|
|
530
|
+
# Treat sentinel values like -9999 as missing
|
|
531
|
+
if vegetation_height <= -9998:
|
|
532
|
+
vegetation_height = None
|
|
459
533
|
except:
|
|
460
534
|
vegetation_height = None
|
|
461
535
|
else:
|
|
462
536
|
vegetation_height = None
|
|
463
537
|
|
|
538
|
+
# Fallback to geometry-derived height if needed
|
|
539
|
+
if vegetation_height is None:
|
|
540
|
+
derived_h = compute_lod_height(plant_cover)
|
|
541
|
+
if derived_h is not None:
|
|
542
|
+
vegetation_height = derived_h
|
|
543
|
+
|
|
464
544
|
geometry = get_veg_geometry(plant_cover)
|
|
465
545
|
if geometry is not None and not geometry.is_empty:
|
|
466
546
|
vegetation_elements.append({
|
|
@@ -472,17 +552,26 @@ def extract_vegetation_info(file_path, namespaces):
|
|
|
472
552
|
})
|
|
473
553
|
|
|
474
554
|
# 2) SolitaryVegetationObject
|
|
475
|
-
for solitary in root.findall('.//veg:SolitaryVegetationObject',
|
|
555
|
+
for solitary in root.findall('.//veg:SolitaryVegetationObject', ns):
|
|
476
556
|
veg_id = solitary.get('{http://www.opengis.net/gml}id')
|
|
477
|
-
height_elem = solitary.find('.//veg:height',
|
|
557
|
+
height_elem = solitary.find('.//veg:height', ns)
|
|
478
558
|
if height_elem is not None and height_elem.text:
|
|
479
559
|
try:
|
|
480
560
|
veg_height = float(height_elem.text)
|
|
561
|
+
# Treat sentinel values like -9999 as missing
|
|
562
|
+
if veg_height <= -9998:
|
|
563
|
+
veg_height = None
|
|
481
564
|
except:
|
|
482
565
|
veg_height = None
|
|
483
566
|
else:
|
|
484
567
|
veg_height = None
|
|
485
568
|
|
|
569
|
+
# Fallback to geometry-derived height if attribute is missing/unparseable
|
|
570
|
+
if veg_height is None:
|
|
571
|
+
derived_h = compute_lod_height(solitary)
|
|
572
|
+
if derived_h is not None:
|
|
573
|
+
veg_height = derived_h
|
|
574
|
+
|
|
486
575
|
geometry = get_veg_geometry(solitary)
|
|
487
576
|
if geometry is not None and not geometry.is_empty:
|
|
488
577
|
vegetation_elements.append({
|
|
@@ -577,7 +666,8 @@ def process_citygml_file(file_path):
|
|
|
577
666
|
terrain_elements = []
|
|
578
667
|
vegetation_elements = []
|
|
579
668
|
|
|
580
|
-
namespaces
|
|
669
|
+
# Default/fallback namespaces (used if not present in the file)
|
|
670
|
+
fallback_namespaces = {
|
|
581
671
|
'core': 'http://www.opengis.net/citygml/2.0',
|
|
582
672
|
'bldg': 'http://www.opengis.net/citygml/building/2.0',
|
|
583
673
|
'gml': 'http://www.opengis.net/gml',
|
|
@@ -590,6 +680,32 @@ def process_citygml_file(file_path):
|
|
|
590
680
|
tree = ET.parse(file_path)
|
|
591
681
|
root = tree.getroot()
|
|
592
682
|
|
|
683
|
+
# Build namespaces dynamically from the file, falling back to defaults.
|
|
684
|
+
nsmap = root.nsmap or {}
|
|
685
|
+
|
|
686
|
+
def pick_ns(prefix, keyword=None, fallback_key=None):
|
|
687
|
+
# Try explicit prefix first
|
|
688
|
+
uri = nsmap.get(prefix)
|
|
689
|
+
if uri:
|
|
690
|
+
return uri
|
|
691
|
+
# Try to discover by keyword in URI (e.g., 'vegetation', 'building', 'relief')
|
|
692
|
+
if keyword:
|
|
693
|
+
for v in nsmap.values():
|
|
694
|
+
if isinstance(v, str) and keyword in v:
|
|
695
|
+
return v
|
|
696
|
+
# Fallback to defaults
|
|
697
|
+
return fallback_namespaces[fallback_key or prefix]
|
|
698
|
+
|
|
699
|
+
namespaces = {
|
|
700
|
+
'core': pick_ns('core', keyword='citygml', fallback_key='core'),
|
|
701
|
+
'bldg': pick_ns('bldg', keyword='building', fallback_key='bldg'),
|
|
702
|
+
'gml': pick_ns('gml', keyword='gml', fallback_key='gml'),
|
|
703
|
+
'uro': pick_ns('uro', keyword='iur/uro', fallback_key='uro'),
|
|
704
|
+
'dem': pick_ns('dem', keyword='relief', fallback_key='dem'),
|
|
705
|
+
# Accept CityGML 2.0 or 3.0 vegetation namespaces
|
|
706
|
+
'veg': pick_ns('veg', keyword='vegetation', fallback_key='veg')
|
|
707
|
+
}
|
|
708
|
+
|
|
593
709
|
# Extract Buildings
|
|
594
710
|
for building in root.findall('.//bldg:Building', namespaces):
|
|
595
711
|
building_id = building.get('{http://www.opengis.net/gml}id')
|