Merge branch 'add/geopandas/importer' into 'master'

variable name correction, it will overwrite index and most likely case bugs in...

See merge request Guille/hub!32
This commit is contained in:
Guillermo Gutierrez Morote 2022-11-16 22:04:32 +00:00
commit e3b9c84d63
7 changed files with 177 additions and 7 deletions

View File

@ -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):

114
imports/geometry/gpandas.py Normal file
View File

@ -0,0 +1,114 @@
"""
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
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
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.city import City
import helpers.constants as cte
class GPandas:
"""
GeoPandas class
"""
def __init__(self, dataframe, srs_name='EPSG:26911'):
"""_summary_
Arguments:
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 = 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]
self._upper_corner = [max_x, max_y, 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 scene_index, 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(scene_index)
lod = 1
if year_of_construction > 2000:
function = cte.RESIDENTIAL
else:
function = cte.INDUSTRY
surfaces = []
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
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)
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

View File

@ -10,15 +10,17 @@ 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
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:
@ -36,6 +38,16 @@ class GeometryFactory:
"""
return Obj(self._path).city
@property
def _gpandas(self) -> City:
"""
Enrich the city by using GeoPandas information as data source
:return: 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:
"""
@ -66,4 +78,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

View File

@ -16,3 +16,6 @@ rhino3dm==7.7.0
scipy
PyYAML
pyecore==0.12.2
shapely
geopandas
triangle

View File

@ -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:

View File

@ -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()
@ -147,6 +155,21 @@ class TestGeometryFactory(TestCase):
for building in city.buildings:
self._check_surfaces(building)
def test_import_geopandas(self):
"""
Test geopandas import
"""
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):
"""

View File

@ -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
}
}
]
}