geojson_exporter #50

Merged
g_gutierrez merged 3 commits from geojson_exporter into main 2023-10-24 02:03:06 -04:00
6 changed files with 148 additions and 4 deletions

View File

@ -9,6 +9,7 @@ from pathlib import Path
from hub.exports.formats.glb import Glb from hub.exports.formats.glb import Glb
from hub.exports.formats.obj import Obj from hub.exports.formats.obj import Obj
from hub.exports.formats.geojson import Geojson
from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm from hub.exports.formats.simplified_radiosity_algorithm import SimplifiedRadiosityAlgorithm
from hub.exports.formats.stl import Stl from hub.exports.formats.stl import Stl
from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset from hub.exports.formats.cesiumjs_tileset import CesiumjsTileset
@ -85,6 +86,10 @@ class ExportsFactory:
def _glb(self): def _glb(self):
return Glb(self._city, self._path, target_buildings=self._target_buildings) return Glb(self._city, self._path, target_buildings=self._target_buildings)
@property
def _geojson(self):
return Geojson(self._city, self._path, target_buildings=self._target_buildings)
def export(self): def export(self):
""" """
Export the city given to the class using the given export type handler Export the city given to the class using the given export type handler

View File

@ -1,3 +1,9 @@
"""
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 json
import math import math

View File

@ -0,0 +1,112 @@
"""
export a city into Geojson format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import json
from pathlib import Path
import numpy as np
import pyproj
from pyproj import Transformer
from hub.helpers.geometry_helper import GeometryHelper
class Geojson:
"""
Export to geojson format
"""
def __init__(self, city, path, target_buildings):
self._city = city
self._file_path = Path(path / f'{self._city.name}.geojson').resolve()
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'))
if target_buildings is None:
target_buildings = [b.name for b in self._city.buildings]
self._geojson_skeleton = {
'type': 'FeatureCollection',
'features': []
}
self._feature_skeleton = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': []
},
'properties': {}
}
self._export()
def _export(self):
for building in self._city.buildings:
if len(building.grounds) == 1:
ground = building.grounds[0]
feature = self._polygon(ground)
else:
feature = self._multipolygon(building.grounds)
feature['id'] = building.name
feature['properties']['height'] = f'{building.max_height - building.lower_corner[2]}'
feature['properties']['function'] = f'{building.function}'
feature['properties']['year_of_construction'] = f'{building.year_of_construction}'
feature['properties']['aliases'] = building.aliases
feature['properties']['elevation'] = f'{building.lower_corner[2]}'
self._geojson_skeleton['features'].append(feature)
with open(self._file_path, 'w', encoding='utf-8') as f:
json.dump(self._geojson_skeleton, f, indent=2)
def _polygon(self, ground):
feature = {
'type': 'Feature',
'geometry': {
'type': 'Polygon',
'coordinates': []
},
'properties': {}
}
ground_coordinates = []
for coordinate in ground.solid_polygon.coordinates:
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
first_gps_coordinate = self._to_gps.transform(
ground.solid_polygon.coordinates[0][0],
ground.solid_polygon.coordinates[0][1]
)
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
feature['geometry']['coordinates'].append(ground_coordinates)
return feature
def _multipolygon(self, grounds):
feature = {
'type': 'Feature',
'geometry': {
'type': 'MultiPolygon',
'coordinates': []
},
'properties': {}
}
polygons = []
for ground in grounds:
ground_coordinates = []
for coordinate in ground.solid_polygon.coordinates:
gps_coordinate = self._to_gps.transform(coordinate[0], coordinate[1])
ground_coordinates.insert(0, [gps_coordinate[1], gps_coordinate[0]])
first_gps_coordinate = self._to_gps.transform(
ground.solid_polygon.coordinates[0][0],
ground.solid_polygon.coordinates[0][1]
)
ground_coordinates.insert(0, [first_gps_coordinate[1], first_gps_coordinate[0]])
polygons.append(ground_coordinates)
feature['geometry']['coordinates'].append(polygons)
return feature

View File

@ -1,3 +1,9 @@
"""
export a city into Glb format
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
"""
import os import os
import shutil import shutil
import subprocess import subprocess
@ -13,6 +19,9 @@ class GltExceptionError(Exception):
class Glb: class Glb:
"""
Glb class
"""
def __init__(self, city, path, target_buildings=None): def __init__(self, city, path, target_buildings=None):
self._city = city self._city = city
self._path = path self._path = path
@ -23,10 +32,6 @@ class Glb:
@property @property
def _obj2gtl(self): def _obj2gtl(self):
"""
Get the SRA installation path
:return: str
"""
return shutil.which('obj2gltf') return shutil.which('obj2gltf')
def _export(self): def _export(self):

View File

@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
""" """
import logging
import numpy as np import numpy as np
import xmltodict import xmltodict
@ -79,6 +80,7 @@ class CityGml:
self._srs_name = envelope['@srsName'] self._srs_name = envelope['@srsName']
else: else:
# If not coordinate system given assuming hub standard # If not coordinate system given assuming hub standard
logging.warning('gml file contains no coordinate system assuming EPSG:26911 (North america with 4m error)')
self._srs_name = "EPSG:26911" self._srs_name = "EPSG:26911"
else: else:
# get the boundary from the city objects instead # get the boundary from the city objects instead

View File

@ -7,6 +7,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord
""" """
import json import json
import logging.handlers import logging.handlers
import os
from pathlib import Path from pathlib import Path
from unittest import TestCase from unittest import TestCase
from hub.imports.geometry_factory import GeometryFactory from hub.imports.geometry_factory import GeometryFactory
@ -98,6 +99,19 @@ class TestExports(TestCase):
glb_file = Path(self._output_path / f'{building.name}.glb') glb_file = Path(self._output_path / f'{building.name}.glb')
self.assertTrue(glb_file.exists(), f'{building.name} Building glb wasn\'t correctly generated') self.assertTrue(glb_file.exists(), f'{building.name} Building glb wasn\'t correctly generated')
def test_geojson_export(self):
self._export('geojson', False)
geojson_file = Path(self._output_path / f'{self._city.name}.geojson')
self.assertTrue(geojson_file.exists(), f'{geojson_file} doesn\'t exists')
with open(geojson_file, 'r') as f:
geojson = json.load(f)
self.assertEqual(1, len(geojson['features']), 'Wrong number of buildings')
geometry = geojson['features'][0]['geometry']
self.assertEqual('Polygon', geometry['type'], 'Wrong geometry type')
self.assertEqual(1, len(geometry['coordinates']), 'Wrong polygon structure')
self.assertEqual(11, len(geometry['coordinates'][0]), 'Wrong number of vertices')
os.unlink(geojson_file) # todo: this test need to cover a multipolygon example too
def test_energy_ade_export(self): def test_energy_ade_export(self):
""" """
export to energy ADE export to energy ADE