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.

Files changed (37) hide show
  1. {voxcity-0.6.10 → voxcity-0.6.11}/PKG-INFO +1 -1
  2. {voxcity-0.6.10 → voxcity-0.6.11}/pyproject.toml +1 -1
  3. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/citygml.py +124 -8
  4. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/generator.py +1136 -1136
  5. {voxcity-0.6.10 → voxcity-0.6.11}/AUTHORS.rst +0 -0
  6. {voxcity-0.6.10 → voxcity-0.6.11}/LICENSE +0 -0
  7. {voxcity-0.6.10 → voxcity-0.6.11}/README.md +0 -0
  8. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/__init__.py +0 -0
  9. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/__init__.py +0 -0
  10. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/eubucco.py +0 -0
  11. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/gee.py +0 -0
  12. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/mbfp.py +0 -0
  13. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/oemj.py +0 -0
  14. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/osm.py +0 -0
  15. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/overture.py +0 -0
  16. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/downloader/utils.py +0 -0
  17. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/__init__.py +0 -0
  18. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/cityles.py +0 -0
  19. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/envimet.py +0 -0
  20. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/magicavoxel.py +0 -0
  21. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/exporter/obj.py +0 -0
  22. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/__init__.py +0 -0
  23. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/draw.py +0 -0
  24. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/grid.py +0 -0
  25. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/mesh.py +0 -0
  26. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/network.py +0 -0
  27. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/polygon.py +0 -0
  28. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/geoprocessor/utils.py +0 -0
  29. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/__init__.py +0 -0
  30. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/solar.py +0 -0
  31. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/utils.py +0 -0
  32. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/simulator/view.py +0 -0
  33. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/__init__.py +0 -0
  34. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/lc.py +0 -0
  35. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/material.py +0 -0
  36. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/visualization.py +0 -0
  37. {voxcity-0.6.10 → voxcity-0.6.11}/src/voxcity/utils/weather.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: voxcity
3
- Version: 0.6.10
3
+ Version: 0.6.11
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
  License: MIT
6
6
  Author: Kunihiko Fujiwara
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "voxcity"
3
- version = "0.6.10"
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', namespaces):
409
- ring_node = poly_node.find('.//gml:exterior//gml:LinearRing//gml:posList', namespaces)
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}', namespaces)
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', namespaces):
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', namespaces)
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', namespaces):
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', namespaces)
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')