160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
"""
|
|
export a city into Cesium tileset format
|
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
|
Copyright © 2022 Concordia CERC group
|
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
"""
|
|
import json
|
|
import math
|
|
|
|
import pyproj
|
|
from pyproj import Transformer
|
|
|
|
from hub.helpers.geometry_helper import GeometryHelper
|
|
|
|
|
|
class CesiumjsTileset:
|
|
def __init__(self, city, file_name, target_buildings=None, base_uri=None):
|
|
self._city = city
|
|
self._file_name = file_name
|
|
self._target_buildings = target_buildings
|
|
if base_uri is None:
|
|
base_uri = '.'
|
|
self._base_uri = base_uri
|
|
try:
|
|
srs_name = self._city.srs_name
|
|
if self._city.srs_name in GeometryHelper.srs_transformations:
|
|
srs_name = GeometryHelper.srs_transformations[self._city.srs_name]
|
|
input_reference = pyproj.CRS(srs_name) # Projected coordinate system from input data
|
|
except pyproj.exceptions.CRSError as err:
|
|
raise pyproj.exceptions.CRSError from err
|
|
self._to_gps = Transformer.from_crs(input_reference, pyproj.CRS('EPSG:4326'))
|
|
city_upper_corner = [
|
|
self._city.upper_corner[0] - self._city.lower_corner[0],
|
|
self._city.upper_corner[1] - self._city.lower_corner[1],
|
|
self._city.upper_corner[2] - self._city.lower_corner[2]
|
|
]
|
|
city_lower_corner = [0, 0, 0]
|
|
self._tile_set = {
|
|
'asset': {
|
|
'version': '1.1',
|
|
"tilesetVersion": "1.2.3"
|
|
},
|
|
'position': self._to_gps.transform(self._city.lower_corner[0], self._city.lower_corner[1]),
|
|
'schema': {
|
|
'id': "building",
|
|
'classes': {
|
|
'building': {
|
|
"properties": {
|
|
'name': {
|
|
'type': 'STRING'
|
|
},
|
|
'position': {
|
|
'type': 'SCALAR',
|
|
'array': True,
|
|
'componentType': 'FLOAT32'
|
|
},
|
|
'aliases': {
|
|
'type': 'STRING',
|
|
'array': True,
|
|
},
|
|
'volume': {
|
|
'type': 'SCALAR',
|
|
'componentType': 'FLOAT32'
|
|
},
|
|
'floor_area': {
|
|
'type': 'SCALAR',
|
|
'componentType': 'FLOAT32'
|
|
},
|
|
'max_height': {
|
|
'type': 'SCALAR',
|
|
'componentType': 'INT32'
|
|
},
|
|
'year_of_construction': {
|
|
'type': 'SCALAR',
|
|
'componentType': 'INT32'
|
|
},
|
|
'function': {
|
|
'type': 'STRING'
|
|
},
|
|
'usages_percentage': {
|
|
'type': 'STRING'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'geometricError': 500,
|
|
'root': {
|
|
'boundingVolume': {
|
|
'box': CesiumjsTileset._box_values(city_upper_corner, city_lower_corner)
|
|
},
|
|
'geometricError': 70,
|
|
'refine': 'ADD',
|
|
'children': []
|
|
}
|
|
}
|
|
|
|
self._export()
|
|
|
|
@staticmethod
|
|
def _box_values(upper_corner, lower_corner):
|
|
|
|
x = (upper_corner[0] - lower_corner[0]) / 2
|
|
x_center = ((upper_corner[0] - lower_corner[0]) / 2) + lower_corner[0]
|
|
y = (upper_corner[1] - lower_corner[1]) / 2
|
|
y_center = ((upper_corner[1] - lower_corner[1]) / 2) + lower_corner[1]
|
|
z = (upper_corner[2] - lower_corner[2]) / 2
|
|
return [x_center, y_center, z, x, 0, 0, 0, y, 0, 0, 0, z]
|
|
|
|
def _ground_coordinates(self, coordinates):
|
|
ground_coordinates = []
|
|
for coordinate in coordinates:
|
|
ground_coordinates.append(
|
|
(coordinate[0] - self._city.lower_corner[0], coordinate[1] - self._city.lower_corner[1])
|
|
)
|
|
return ground_coordinates
|
|
|
|
def _export(self):
|
|
for building in self._city.buildings:
|
|
upper_corner = [-math.inf, -math.inf, 0]
|
|
lower_corner = [math.inf, math.inf, 0]
|
|
lower_corner_coordinates = lower_corner
|
|
for surface in building.grounds: # todo: maybe we should add the terrain?
|
|
coordinates = self._ground_coordinates(surface.solid_polygon.coordinates)
|
|
lower_corner = [min([c[0] for c in coordinates]), min([c[1] for c in coordinates]), 0]
|
|
lower_corner_coordinates = [
|
|
min([c[0] for c in surface.solid_polygon.coordinates]),
|
|
min([c[1] for c in surface.solid_polygon.coordinates]),
|
|
0
|
|
]
|
|
upper_corner = [max([c[0] for c in coordinates]), max([c[1] for c in coordinates]), building.max_height]
|
|
|
|
tile = {
|
|
'boundingVolume': {
|
|
'box': CesiumjsTileset._box_values(upper_corner, lower_corner)
|
|
},
|
|
'geometricError': 250,
|
|
'metadata': {
|
|
'class': 'building',
|
|
'properties': {
|
|
'name': building.name,
|
|
'position': self._to_gps.transform(lower_corner_coordinates[0], lower_corner_coordinates[1]),
|
|
'aliases': building.aliases,
|
|
'volume': building.volume,
|
|
'floor_area': building.floor_area,
|
|
'max_height': building.max_height,
|
|
'year_of_construction': building.year_of_construction,
|
|
'function': building.function,
|
|
'usages_percentage': building.usages_percentage
|
|
}
|
|
},
|
|
'content': {
|
|
'uri': f'{self._base_uri}/{building.name}.glb'
|
|
}
|
|
}
|
|
self._tile_set['root']['children'].append(tile)
|
|
|
|
with open(self._file_name, 'w') as f:
|
|
json.dump(self._tile_set, f, indent=2)
|