diff --git a/imports/geometry/gpandas.py b/imports/geometry/gpandas.py index ce85d329..30658e52 100644 --- a/imports/geometry/gpandas.py +++ b/imports/geometry/gpandas.py @@ -5,114 +5,109 @@ Copyright © 2022 Concordia CERC group Project Coder: Milad Aghamohamadnia --- milad.aghamohamadnia@concordia.ca """ -import trimesh.exchange.load -from trimesh import Scene -import trimesh.geometry -from shapely.geometry import Polygon as ShapelyPoly -from shapely.geometry import Point import trimesh -from pyproj import CRS -from city_model_structure.city import City +import trimesh.exchange.load +import trimesh.geometry +from shapely.geometry import Point +from shapely.geometry import Polygon as ShapelyPoly +from trimesh import Scene + +from city_model_structure.attributes.polygon import Polygon from city_model_structure.building import Building from city_model_structure.building_demand.surface import Surface -from city_model_structure.attributes.polygon import Polygon -import numpy as np +from city_model_structure.city import City + +import helpers.constants as cte class GPandas: + """ + GeoPandas class + """ + + def __init__(self, gdataframe, srs_name='EPSG:26911'): + """_summary_ + Arguments: + gdataframe {Geopandas.Dataframe} -- input geometry data in geopandas table + Keyword Arguments: + srs_name {str} -- coordinate system of coordinate system (default: {'EPSG:26911'}) """ - GeoPandas class + self._srs_name = srs_name + self._city = None + self._scene = gdataframe + self._scene = self._scene.to_crs(self._srs_name) + min_x, min_y, max_x, max_y = self._scene.total_bounds + self._lower_corner = [min_x, min_y, 0] + self._upper_corner = [max_x, max_y, 0] + + @property + def scene(self) -> Scene: """ - def __init__(self, gdataframe, srs_name='EPSG:26911'): - """_summary_ + Get GeoPandas scene + """ + return self._scene - Arguments: - gdataframe {Geopandas.Dataframe} -- input geometry data in geopandas table - - Keyword Arguments: - srs_name {str} -- coordinate system of coordinate system (default: {'EPSG:26911'}) - """ - self._srs_name = srs_name - self._city = None - self._scene = gdataframe - self._scene = self._scene.to_crs(self._srs_name) - minx, miny, maxx, maxy = self._scene.total_bounds - self._lower_corner = [minx, miny, 0] - self._upper_corner = [maxx, maxy, 0] - - @property - def scene(self) -> Scene: - """ - Get GeoPandas scene - """ - return self._scene - - @property - def city(self) -> City: - """ - Get city out of a GeoPandas Table - """ - if self._city is None: - self._city = City(self._lower_corner, self._upper_corner, self._srs_name) - for ix, bldg in self._scene.iterrows(): - polygon = bldg['geometry'] - geom = bldg.geom - polygon = ShapelyPoly(geom['coordinates'][0]) - height = float(bldg['height_mean']) - building_mesh = trimesh.creation.extrude_polygon(polygon, height) - trimesh.repair.fill_holes(building_mesh) - trimesh.repair.fix_winding(building_mesh) - year_of_construction = int(bldg['year_built']) - name = str(ix) - lod = 1 - if year_of_construction>2000: - function = 'residential' - else: - function = 'industry' - average_storey_height = 3 - - surfaces = [] - face_normal = building_mesh.face_normals - for ix, face in enumerate(building_mesh.faces): - points = [] - for vertex_index in face: - points.append(building_mesh.vertices[vertex_index]) - solid_polygon = Polygon(points) - perimeter_polygon = solid_polygon - s_type = 'Ground' if face_normal[ix][2]==-1 else ('Roof' if face_normal[ix][2]==1 else 'Wall') - surface = Surface(solid_polygon, perimeter_polygon, surface_type=s_type) - surfaces.append(surface) - building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) - self._city.add_city_object(building) - return self._city - - @staticmethod - def resize_polygon(poly, factor=0.10, expand=False): - """ - returns the shapely polygon which is smaller or bigger by passed factor. - - Arguments: - poly {shapely.geometry.Polygon} -- an input geometry in shapely polygon format - - Keyword Arguments: - factor {float} -- factor of expansion (default: {0.10}) - expand {bool} -- If expand = True , then it returns bigger polygon, else smaller (default: {False}) - - Returns: - {shapely.geometry.Polygon} -- output geometry in shapely polygon format - """ - xs = list(poly.exterior.coords.xy[0]) - ys = list(poly.exterior.coords.xy[1]) - x_center = 0.5 * min(xs) + 0.5 * max(xs) - y_center = 0.5 * min(ys) + 0.5 * max(ys) - min_corner = Point(min(xs), min(ys)) - max_corner = Point(max(xs), max(ys)) - center = Point(x_center, y_center) - shrink_distance = center.distance(min_corner)*factor - - if expand: - poly_resized = poly.buffer(shrink_distance) #expand + @property + def city(self) -> City: + """ + Get city out of a GeoPandas Table + """ + if self._city is None: + self._city = City(self._lower_corner, self._upper_corner, self._srs_name) + for ix, bldg in self._scene.iterrows(): + geom = bldg.geom + polygon = ShapelyPoly(geom['coordinates'][0]) + height = float(bldg['height_mean']) + building_mesh = trimesh.creation.extrude_polygon(polygon, height) + trimesh.repair.fill_holes(building_mesh) + trimesh.repair.fix_winding(building_mesh) + year_of_construction = int(bldg['year_built']) + name = str(ix) + lod = 1 + if year_of_construction > 2000: + function = cte.RESIDENTIAL else: - poly_resized = poly.buffer(-shrink_distance) #shrink - return poly_resized - + function = cte.INDUSTRY + + surfaces = [] + face_normal = building_mesh.face_normals + for ix, face in enumerate(building_mesh.faces): + points = [] + for vertex_index in face: + points.append(building_mesh.vertices[vertex_index]) + solid_polygon = Polygon(points) + perimeter_polygon = solid_polygon + s_type = cte.GROUND if face_normal[ix][2] == -1 else (cte.ROOF if face_normal[ix][2] == 1 else cte.WALL) + surface = Surface(solid_polygon, perimeter_polygon, surface_type=s_type) + surfaces.append(surface) + building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) + self._city.add_city_object(building) + return self._city + + @staticmethod + def resize_polygon(poly, factor=0.10, expand=False) -> ShapelyPoly: + """ + returns the shapely polygon which is smaller or bigger by passed factor. + Arguments: + poly {shapely.geometry.Polygon} -- an input geometry in shapely polygon format + + Keyword Arguments: + factor {float} -- factor of expansion (default: {0.10}) + expand {bool} -- If expand = True , then it returns bigger polygon, else smaller (default: {False}) + + Returns: + {shapely.geometry.Polygon} -- output geometry in shapely polygon format + """ + xs = list(poly.exterior.coords.xy[0]) + ys = list(poly.exterior.coords.xy[1]) + x_center = 0.5 * min(xs) + 0.5 * max(xs) + y_center = 0.5 * min(ys) + 0.5 * max(ys) + min_corner = Point(min(xs), min(ys)) + center = Point(x_center, y_center) + shrink_distance = center.distance(min_corner) * factor + + if expand: + poly_resized = poly.buffer(shrink_distance) # expand + else: + poly_resized = poly.buffer(-shrink_distance) # shrink + return poly_resized diff --git a/requirements.txt b/requirements.txt index 5813e91f..30572325 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,5 @@ ply rhino3dm==7.7.0 scipy PyYAML -pyecore==0.12.2 \ No newline at end of file +pyecore==0.12.2 +shapely \ No newline at end of file diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index 5551403c..2b225308 100644 --- a/unittests/test_geometry_factory.py +++ b/unittests/test_geometry_factory.py @@ -147,6 +147,18 @@ class TestGeometryFactory(TestCase): for building in city.buildings: self._check_surfaces(building) + def test_import_geopandas(self): + """ + Test geopandas import + """ + file = 'kelowna.obj' + city = self._get_obj(file) + self.assertIsNotNone(city, 'city is none') + self.assertTrue(len(city.buildings) == 1) + self._check_buildings(city) + for building in city.buildings: + self._check_surfaces(building) + # osm def test_subway(self): """