From 77824fd1cbccd19e2fac9edcda08aed2b3e14c19 Mon Sep 17 00:00:00 2001 From: Peter Yefi Date: Tue, 4 Apr 2023 22:31:54 -0400 Subject: [PATCH] Logging to remove sub polygons that are holes --- hub/imports/geometry/geojson.py | 114 +++++++++++++++---------- hub/unittests/test_geometry_factory.py | 14 --- requirements.txt | 3 +- 3 files changed, 70 insertions(+), 61 deletions(-) diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 79f5eee5..4c3637f1 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -18,8 +18,8 @@ from hub.city_model_structure.attributes.polygon import Polygon from hub.city_model_structure.building import Building from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.city import City -from typing import Dict, Tuple -from shapely.geometry import Polygon +from typing import List +from shapely.geometry import Polygon as Poly class Geojson: @@ -47,6 +47,7 @@ class Geojson: self._year_of_construction_field = year_of_construction_field self._function_field = function_field self._function_to_hub = function_to_hub + self._building_coordinates = [] with open(path) as json_file: self._geojson = json.loads(json_file.read()) @@ -80,6 +81,7 @@ class Geojson: lod0_buildings = Geojson._create_buildings_lod0(name, year_of_construction, function, surface_coordinates) surfaces = [] buildings = [] + for zone, lod0_building in enumerate(lod0_buildings): for surface in lod0_building.grounds: @@ -176,18 +178,40 @@ class Geojson: percentage += percentage_ground * percentage_height wall.percentage_shared = percentage - '''def _remove_sub_polygons(self, building_coordinates: Dict, new_coordinate: Tuple) -> Dict: - new_polygon = Polygon(new_coordinate[1]) - is_sub_polygon = False - for coordinates in building_coordinates['coords']: - if Polygon(coordinates).contains(new_polygon): - is_sub_polygon = True + def _unwind_coordinates(self, polygons, coordinates): + """ + Makes list of coordinates from complex coordinate list + :param polygons: complex list of coordinates e.g. [[[3.5, 2.7], [10.7, 11.19]], [[13.5, 22.7], [18.7, 11.9]]] + :param coordinates: and empty list to hold list of coordinates + :return: list of coordinates, e.g. [[3.5, 2.7], [10.7, 11.19], [13.5, 22.7], [18.7, 11.9]] + """ + for polygon in polygons: + if len(polygon) != 2: + return self._unwind_coordinates(polygons[0], coordinates) + else: + coordinates.append(polygon) + return coordinates - if not is_sub_polygon: - building_coordinates['coords'].append(new_coordinate[1]) - building_coordinates['parts'].append(new_coordinate[0]) + def _remove_sub_polygons(self, new_building_coordinate: List): + """ + building a pool of building cooordinates (GeoJSON polygons) while + ignoring polygons that are inside other polygons + :param new_building_coordinate: a new coordinate to be added, this is checked + to make sure it is not inside a polygon or a polygon is not inside it + :return: + """ + processed_coordinates = self._unwind_coordinates(new_building_coordinate[0], []) + is_sub_coordinate = False + for coordinates in self._building_coordinates[:]: + if Poly(processed_coordinates).contains(Poly(coordinates[0])): + self._building_coordinates.remove(coordinates) + elif Poly(coordinates[0]).contains(Poly(processed_coordinates)): + is_sub_coordinate = True + break - return building_coordinates''' + if is_sub_coordinate is False: + new_building_coordinate[0] = processed_coordinates + self._building_coordinates.append(new_building_coordinate) @property def city(self) -> City: @@ -223,39 +247,37 @@ class Geojson: else: building_name = f'building_{building_id}' building_id += 1 - polygons = [] - '''building_coordinates = {'coords': [], 'parts': []} - for part, coordinates in enumerate(geometry['coordinates']): - building_coordinates = self._remove_sub_polygons(building_coordinates, (part, coordinates)) - # remove polygon inside other polygons - print(building_coordinates)''' - for part, coordinates in enumerate(geometry['coordinates']): - polygons = self._get_polygons(polygons, coordinates) - for zone, polygon in enumerate(polygons): - if extrusion_height == 0: - buildings = buildings + Geojson._create_buildings_lod0(f'{building_name}_part_{part}', - year_of_construction, - function, - [polygon]) - lod = 0 - else: - if self._max_z < extrusion_height: - self._max_z = extrusion_height - buildings = buildings + Geojson._create_buildings_lod1(f'{building_name}_part_{part}', - year_of_construction, - function, - extrusion_height, - [polygon]) + for coordinates in geometry['coordinates']: + self._remove_sub_polygons([coordinates, building_name, year_of_construction, + function, extrusion_height]) + + for part, coordinates in enumerate(self._building_coordinates): + polygons = [] + polygons = self._get_polygons(polygons, coordinates[0]) + for zone, polygon in enumerate(polygons): + if coordinates[4] == 0: + buildings = buildings + Geojson._create_buildings_lod0(f'{coordinates[1]}_part_{part}', + coordinates[2], + coordinates[3], + [polygon]) + lod = 0 + else: + if self._max_z < coordinates[4]: + self._max_z = coordinates[4] + buildings = buildings + Geojson._create_buildings_lod1(f'{coordinates[1]}_part_{part}', + coordinates[2], + coordinates[3], + coordinates[4], + [polygon]) + self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') + for building in buildings: + if building.floor_area >= 25: + self._city.add_city_object(building) + self._city.level_of_detail.geometry = lod + if lod == 1: + lines_information = GeometryHelper.city_mapping(self._city, plot=False) + self._store_shared_percentage_to_walls(self._city, lines_information) + if len(missing_functions) > 0: + print(f'There are unknown functions {missing_functions}') - self._city = City([self._min_x, self._min_y, 0.0], [self._max_x, self._max_y, self._max_z], 'epsg:26911') - for building in buildings: - # Do not include "small building-like structures" to buildings - if building.floor_area >= 25: - self._city.add_city_object(building) - self._city.level_of_detail.geometry = lod - if lod == 1: - lines_information = GeometryHelper.city_mapping(self._city, plot=False) - self._store_shared_percentage_to_walls(self._city, lines_information) - if len(missing_functions) > 0: - print(f'There are unknown functions {missing_functions}') return self._city diff --git a/hub/unittests/test_geometry_factory.py b/hub/unittests/test_geometry_factory.py index eb0624fe..ebcc1b59 100644 --- a/hub/unittests/test_geometry_factory.py +++ b/hub/unittests/test_geometry_factory.py @@ -144,20 +144,6 @@ class TestGeometryFactory(TestCase): # including 25 square meter condition for a building reduces buildings number from 2289 to 2057 self.assertEqual(2057, len(city.buildings), 'wrong number of buildings') - def test_import_geojson_2(self): - """ - Test geojson import - """ - file = 'test.geojson' - city = GeometryFactory('geojson', - path=(self._example_path / file).resolve(), - height_field='citygml_me', - year_of_construction_field='ANNEE_CONS', - function_field='CODE_UTILI', - function_to_hub=MontrealFunctionToHubFunction().dictionary).city - for building in city.buildings: - print(building.floor_area) - def test_map_neighbours(self): """ Test neighbours map creation diff --git a/requirements.txt b/requirements.txt index 1d53e0cb..5fdf683e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,4 +23,5 @@ shapely geopandas triangle psycopg2-binary -Pillow \ No newline at end of file +Pillow +shapely==2.0.1 \ No newline at end of file