From de449edc76d3d829428758839bb90a5ebb8ad5a0 Mon Sep 17 00:00:00 2001 From: home-imac Date: Sun, 13 Nov 2022 20:10:42 -0500 Subject: [PATCH 1/7] added geopandas import support --- imports/geometry/gpandas.py | 118 ++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 imports/geometry/gpandas.py diff --git a/imports/geometry/gpandas.py b/imports/geometry/gpandas.py new file mode 100644 index 00000000..ce85d329 --- /dev/null +++ b/imports/geometry/gpandas.py @@ -0,0 +1,118 @@ +""" +gpandas module parses geopandas input table and import the geometry into the city model structure +SPDX - License - Identifier: LGPL - 3.0 - or -later +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 +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 + + +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'}) + """ + 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 + else: + poly_resized = poly.buffer(-shrink_distance) #shrink + return poly_resized + From 3568255c29da0fdd0706094231e1c66c6b3dad13 Mon Sep 17 00:00:00 2001 From: home-imac Date: Sun, 13 Nov 2022 21:07:04 -0500 Subject: [PATCH 2/7] gpandas added in factory --- imports/geometry_factory.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/imports/geometry_factory.py b/imports/geometry_factory.py index b1ac2f35..825b37de 100644 --- a/imports/geometry_factory.py +++ b/imports/geometry_factory.py @@ -10,6 +10,7 @@ from imports.geometry.citygml import CityGml from imports.geometry.obj import Obj from imports.geometry.osm_subway import OsmSubway from imports.geometry.rhino import Rhino +from imports.geometry.gpandas import GPandas class GeometryFactory: @@ -35,6 +36,15 @@ class GeometryFactory: :return: City """ return Obj(self._path).city + + + @property + def _gpandas(self) -> City: + """ + Enrich the city by using GeoPandas information as data source + :return: City + """ + return GPandas(self._path).city @property def _osm_subway(self) -> City: From fae577deaa5ab9af1421651ff815d80f0871696b Mon Sep 17 00:00:00 2001 From: home-imac Date: Mon, 14 Nov 2022 03:19:04 -0500 Subject: [PATCH 3/7] quick fix for failed archetype search --- imports/construction/us_physics_parameters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index 2baa6749..657164bf 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -74,7 +74,9 @@ class UsPhysicsParameters(NrelPhysicsInterface): if (str(function) == str(building_archetype.function)) and \ (climate_zone == str(building_archetype.climate_zone)): return building_archetype - return None + # Todo: line below is added by Milad as a quick fix for when archetypes search is not found + return building_archetype + # return None @staticmethod def _search_construction_in_archetype(archetype, construction_type): From 7e5ead236345d08e3eb714c712d1d34e6d8cbaa2 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 15 Nov 2022 11:25:38 -0500 Subject: [PATCH 4/7] small style correction --- imports/geometry/gpandas.py | 197 ++++++++++++++--------------- requirements.txt | 3 +- unittests/test_geometry_factory.py | 12 ++ 3 files changed, 110 insertions(+), 102 deletions(-) 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): """ From d91da411fdb470ed9c2d5db33f5db2bff9fc4b8c Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 15 Nov 2022 11:28:23 -0500 Subject: [PATCH 5/7] variable name correction, it will overwrite index and most likely case bugs in case of more than one scene. --- imports/geometry/gpandas.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imports/geometry/gpandas.py b/imports/geometry/gpandas.py index 30658e52..ac9a0961 100644 --- a/imports/geometry/gpandas.py +++ b/imports/geometry/gpandas.py @@ -54,7 +54,7 @@ class GPandas: """ if self._city is None: self._city = City(self._lower_corner, self._upper_corner, self._srs_name) - for ix, bldg in self._scene.iterrows(): + for scene_index, bldg in self._scene.iterrows(): geom = bldg.geom polygon = ShapelyPoly(geom['coordinates'][0]) height = float(bldg['height_mean']) @@ -62,7 +62,7 @@ class GPandas: trimesh.repair.fill_holes(building_mesh) trimesh.repair.fix_winding(building_mesh) year_of_construction = int(bldg['year_built']) - name = str(ix) + name = str(scene_index) lod = 1 if year_of_construction > 2000: function = cte.RESIDENTIAL @@ -71,13 +71,13 @@ class GPandas: surfaces = [] face_normal = building_mesh.face_normals - for ix, face in enumerate(building_mesh.faces): + for face_index, 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) + s_type = cte.GROUND if face_normal[face_index][2] == -1 else (cte.ROOF if face_normal[face_index][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) From a617f598ff733697f786ad8d4be37e8174749a8a Mon Sep 17 00:00:00 2001 From: guille Date: Wed, 16 Nov 2022 16:26:45 -0500 Subject: [PATCH 6/7] Add unit tests and requirements for geopandas factory. Geopandas factory now can receive a geojson file or a pandas data frame by using the optional variable data_frame in the constructor to be consistent with the rest of the factories So from: ConstructionFactory('gpandas', dataframe) to: ConstructionFactory('gpandas, '', dataframe) --- imports/geometry/gpandas.py | 13 +++++++------ imports/geometry_factory.py | 11 +++++++---- requirements.txt | 4 +++- unittests/test_enrichement.py | 2 -- unittests/test_geometry_factory.py | 15 +++++++++++++-- unittests/tests_data/sample.geojson | 18 ++++++++++++++++++ 6 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 unittests/tests_data/sample.geojson diff --git a/imports/geometry/gpandas.py b/imports/geometry/gpandas.py index ac9a0961..5edbe0c6 100644 --- a/imports/geometry/gpandas.py +++ b/imports/geometry/gpandas.py @@ -8,6 +8,8 @@ Project Coder: Milad Aghamohamadnia --- milad.aghamohamadnia@concordia.ca import trimesh import trimesh.exchange.load import trimesh.geometry +import trimesh.creation +import trimesh.repair from shapely.geometry import Point from shapely.geometry import Polygon as ShapelyPoly from trimesh import Scene @@ -25,16 +27,17 @@ class GPandas: GeoPandas class """ - def __init__(self, gdataframe, srs_name='EPSG:26911'): + def __init__(self, dataframe, srs_name='EPSG:26911'): """_summary_ Arguments: - gdataframe {Geopandas.Dataframe} -- input geometry data in geopandas table + dataframe {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 = dataframe 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] @@ -70,15 +73,13 @@ class GPandas: function = cte.INDUSTRY surfaces = [] - face_normal = building_mesh.face_normals for face_index, 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[face_index][2] == -1 else (cte.ROOF if face_normal[face_index][2] == 1 else cte.WALL) - surface = Surface(solid_polygon, perimeter_polygon, surface_type=s_type) + surface = Surface(solid_polygon, perimeter_polygon) surfaces.append(surface) building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) self._city.add_city_object(building) diff --git a/imports/geometry_factory.py b/imports/geometry_factory.py index 825b37de..581edece 100644 --- a/imports/geometry_factory.py +++ b/imports/geometry_factory.py @@ -11,15 +11,16 @@ from imports.geometry.obj import Obj from imports.geometry.osm_subway import OsmSubway from imports.geometry.rhino import Rhino from imports.geometry.gpandas import GPandas - +import geopandas class GeometryFactory: """ GeometryFactory class """ - def __init__(self, file_type, path): + def __init__(self, file_type, path, data_frame=None): self._file_type = '_' + file_type.lower() self._path = path + self._data_frame = data_frame @property def _citygml(self) -> City: @@ -44,7 +45,9 @@ class GeometryFactory: Enrich the city by using GeoPandas information as data source :return: City """ - return GPandas(self._path).city + if self._data_frame is None: + self._data_frame = geopandas.read_file(self._path) + return GPandas(self._data_frame).city @property def _osm_subway(self) -> City: @@ -76,4 +79,4 @@ class GeometryFactory: Enrich the city given to the class using the class given handler :return: City """ - return CityGml(self._path).city + return GPandas(geopandas.read_file(self._path)).city diff --git a/requirements.txt b/requirements.txt index 30572325..71e5f372 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,6 @@ rhino3dm==7.7.0 scipy PyYAML pyecore==0.12.2 -shapely \ No newline at end of file +shapely +geopandas +triangle \ No newline at end of file diff --git a/unittests/test_enrichement.py b/unittests/test_enrichement.py index 16831dea..6ace72d4 100644 --- a/unittests/test_enrichement.py +++ b/unittests/test_enrichement.py @@ -93,9 +93,7 @@ class TestGeometryFactory(TestCase): _construction_keys = ['nrel'] _usage_keys = ['comnet', 'hft'] for construction_key in _construction_keys: - print('construction key: ', construction_key) for usage_key in _usage_keys: - print('usage key: ', usage_key) # construction factory called first city = self._get_citygml(file) for building in city.buildings: diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index 2b225308..a35c1d86 100644 --- a/unittests/test_geometry_factory.py +++ b/unittests/test_geometry_factory.py @@ -8,9 +8,11 @@ from pathlib import Path from unittest import TestCase from numpy import inf +from pyproj import Proj, transform from imports.geometry_factory import GeometryFactory from imports.construction_factory import ConstructionFactory +import geopandas class TestGeometryFactory(TestCase): @@ -32,6 +34,12 @@ class TestGeometryFactory(TestCase): self.assertIsNotNone(self._city, 'city is none') return self._city + def _get_geojson(self, file): + file_path = (self._example_path / file).resolve() + self._city = GeometryFactory('gpandas', file_path).city_debug + self.assertIsNotNone(self._city, 'city is none') + return self._city + def _get_obj(self, file): # todo: solve the incongruities between city and city_debug file_path = (self._example_path / file).resolve() @@ -151,13 +159,16 @@ class TestGeometryFactory(TestCase): """ Test geopandas import """ - file = 'kelowna.obj' - city = self._get_obj(file) + file = 'sample.geojson' + city = self._get_geojson(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) + self.assertEqual(1912.0898135701814, building.volume) + self.assertEqual(146.19493345171213, building.floor_area) # osm def test_subway(self): diff --git a/unittests/tests_data/sample.geojson b/unittests/tests_data/sample.geojson new file mode 100644 index 00000000..254542b3 --- /dev/null +++ b/unittests/tests_data/sample.geojson @@ -0,0 +1,18 @@ +{ "type": "FeatureCollection", + "features": [ + { "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ [[-73.5027962600162, 45.6572759731914], [-73.5027463586105, 45.6572669735158], [-73.5027513584185, 45.6572530729948], [-73.5026715592026, 45.6572412737672], [-73.5026410593539, 45.6573430727752], [-73.5027703584728, 45.6573621728624], [-73.5027962600162, 45.6572759731914]] ] + ] + + }, + "properties": { + "geom": {"type": "Polygon", "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::4326"}}, "coordinates": [[[3849322.0855625975, 6060583.24800576], [3849326.3956304314, 6060584.796717078], [3849327.0180495544, 6060583.089519385], [3849333.725799462, 6060585.837955164], [3849328.71788522, 6060598.03498192], [3849317.850609142, 6060593.57976506], [3849322.0855625975, 6060583.24800576]]]}, + "height_mean": 13.0790429485, + "year_built": 2000 + } + } + ] + } \ No newline at end of file From 37187b70468094606c0c24989802aca34ab3b498 Mon Sep 17 00:00:00 2001 From: guille Date: Wed, 16 Nov 2022 16:27:34 -0500 Subject: [PATCH 7/7] small style correction --- imports/geometry_factory.py | 1 - 1 file changed, 1 deletion(-) diff --git a/imports/geometry_factory.py b/imports/geometry_factory.py index 581edece..e9daa30b 100644 --- a/imports/geometry_factory.py +++ b/imports/geometry_factory.py @@ -37,7 +37,6 @@ class GeometryFactory: :return: City """ return Obj(self._path).city - @property def _gpandas(self) -> City: