From 6ee1466b8e30c0d54a37e7e13c15c73d1fe66dd3 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 24 Oct 2023 08:00:48 +0200 Subject: [PATCH] Add geojson exporter --- hub/exports/formats/geojson.py | 68 +++++++++++++++++++++++++-------- hub/imports/geometry/citygml.py | 2 + tests/test_exports.py | 10 ++++- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/hub/exports/formats/geojson.py b/hub/exports/formats/geojson.py index 429c6fc3..d9ef7dae 100644 --- a/hub/exports/formats/geojson.py +++ b/hub/exports/formats/geojson.py @@ -47,28 +47,66 @@ class Geojson: def _export(self): for building in self._city.buildings: - feature = self._feature_skeleton.copy() + 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}' + 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]}' - for ground in building.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]]) - feature['geometry']['coordinates'].append(ground_coordinates) 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 + diff --git a/hub/imports/geometry/citygml.py b/hub/imports/geometry/citygml.py index ab36522f..9e5d5e8b 100644 --- a/hub/imports/geometry/citygml.py +++ b/hub/imports/geometry/citygml.py @@ -4,6 +4,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ +import logging import numpy as np import xmltodict @@ -79,6 +80,7 @@ class CityGml: self._srs_name = envelope['@srsName'] else: # 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" else: # get the boundary from the city objects instead diff --git a/tests/test_exports.py b/tests/test_exports.py index c53a717c..61c69a93 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -100,7 +100,15 @@ class TestExports(TestCase): def test_geojson_export(self): self._export('geojson', False) - self.assertTrue(False, "False is not True") + 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') def test_energy_ade_export(self): """