city_retrofit/hub/helpers/geometry_helper.py

285 lines
11 KiB
Python
Raw Normal View History

2020-06-09 14:07:47 -04:00
"""
Geometry helper
SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
2020-06-09 14:07:47 -04:00
"""
2020-05-19 17:00:15 -04:00
import math
2020-05-19 17:00:15 -04:00
import numpy as np
import requests
from trimesh import Trimesh
from trimesh import intersections
2023-03-10 14:09:41 -05:00
2023-01-24 10:51:50 -05:00
from hub.city_model_structure.attributes.polygon import Polygon
from hub.city_model_structure.attributes.polyhedron import Polyhedron
from hub.helpers.location import Location
2020-05-19 17:00:15 -04:00
2023-02-23 07:25:04 -05:00
2023-03-23 13:24:41 -04:00
class MapPoint:
def __init__(self, x, y):
self._x = int(x)
self._y = int(y)
@property
def x(self):
return self._x
@property
def y(self):
return self._y
def __str__(self):
return f'({self.x}, {self.y})'
2020-05-19 17:00:15 -04:00
def __len__(self):
return 1
def __getitem__(self, index):
if index == 0:
return self._x
elif index == 1:
return self._y
else:
raise IndexError('Index error')
2023-02-23 07:25:04 -05:00
2020-06-11 16:22:58 -04:00
class GeometryHelper:
"""
Geometry helper class
"""
2023-02-23 07:25:04 -05:00
# todo: complete dictionary
srs_transformations = {
'urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH': 'epsg:25832'
}
def __init__(self, delta=0, area_delta=0):
2020-05-19 17:00:15 -04:00
self._delta = delta
self._area_delta = area_delta
2020-05-19 17:00:15 -04:00
@staticmethod
def coordinate_to_map_point(coordinate, city):
2023-02-24 07:10:13 -05:00
return MapPoint(((city.upper_corner[0] - coordinate[0]) * 0.5), ((city.upper_corner[1] - coordinate[1]) * 0.5))
@staticmethod
def city_mapping(city, building_names=None, plot=False):
2023-02-24 07:39:08 -05:00
"""
Returns a shared_information dictionary like
2023-02-24 07:39:08 -05:00
{
"building_name" : [{line: 0 coordinate_1: [x,y,z], coordinate_2:[x, y, z], points: 0}]
}
"""
2023-03-10 14:09:41 -05:00
lines_information = {}
if building_names is None:
building_names = [b.name for b in city.buildings]
2023-02-24 07:10:13 -05:00
x = int((city.upper_corner[0] - city.lower_corner[0]) * 0.5) + 1
y = int((city.upper_corner[1] - city.lower_corner[1]) * 0.5) + 1
2023-03-10 14:09:41 -05:00
city_map = [['' for _ in range(y + 1)] for _ in range(x + 1)]
map_info = [[{} for _ in range(y + 1)] for _ in range(x + 1)]
2023-03-23 13:24:41 -04:00
# img = Image.new('RGB', (x + 1, y + 1), "black") # create a new black image
# city_image = img.load() # create the pixel map
for building_name in building_names:
building = city.city_object(building_name)
2023-02-24 07:39:08 -05:00
line = 0
for ground in building.grounds:
length = len(ground.perimeter_polygon.coordinates) - 1
for i, coordinate in enumerate(ground.perimeter_polygon.coordinates):
j = i + 1
if i == length:
j = 0
next_coordinate = ground.perimeter_polygon.coordinates[j]
point = GeometryHelper.coordinate_to_map_point(coordinate, city)
2023-03-10 14:09:41 -05:00
distance = int(GeometryHelper.distance_between_points(coordinate, next_coordinate))
if distance == 0:
continue
2023-02-24 07:10:13 -05:00
delta_x = (coordinate[0] - next_coordinate[0]) / (distance / 0.5)
delta_y = (coordinate[1] - next_coordinate[1]) / (distance / 0.5)
2023-03-10 14:09:41 -05:00
for k in range(0, distance):
2023-02-24 07:10:13 -05:00
x = MapPoint(point.x + (delta_x * k), point.y + (delta_y * k)).x
y = MapPoint(point.x + (delta_x * k), point.y + (delta_y * k)).y
2023-03-10 14:09:41 -05:00
if city_map[x][y] == '':
city_map[x][y] = building.name
2023-03-10 14:09:41 -05:00
map_info[x][y] = {
'line_start': (coordinate[0], coordinate[1]),
'line_end': (next_coordinate[0], next_coordinate[1]),
}
2023-03-23 13:24:41 -04:00
# city_image[x, y] = (100, 0, 0)
elif city_map[x][y] != building.name:
neighbour = city.city_object(city_map[x][y])
2023-03-10 14:09:41 -05:00
neighbour_info = map_info[x][y]
# prepare the keys
neighbour_start_coordinate = f'{GeometryHelper.coordinate_to_map_point(neighbour_info["line_start"], city)}'
building_start_coordinate = f'{GeometryHelper.coordinate_to_map_point(coordinate, city)}'
2023-03-10 14:09:41 -05:00
neighbour_key = f'{neighbour.name}_{neighbour_start_coordinate}_{building_start_coordinate}'
building_key = f'{building.name}_{building_start_coordinate}_{neighbour_start_coordinate}'
# Add my neighbour info to my shared lines
if building.name in lines_information.keys() and neighbour_key in lines_information[building.name]:
shared_points = int(lines_information[building.name][neighbour_key]['shared_points'])
lines_information[building.name][neighbour_key]['shared_points'] = shared_points + 1
else:
if building.name not in lines_information.keys():
lines_information[building.name] = {}
lines_information[building.name][neighbour_key] = {
'neighbour_name': neighbour.name,
'line_start': (coordinate[0], coordinate[1]),
'line_end': (next_coordinate[0], next_coordinate[1]),
'neighbour_line_start': neighbour_info['line_start'],
'neighbour_line_end': neighbour_info['line_end'],
'coordinate_start': f"{GeometryHelper.coordinate_to_map_point(coordinate, city)}",
'coordinate_end': f"{GeometryHelper.coordinate_to_map_point(next_coordinate, city)}",
'neighbour_start': f"{GeometryHelper.coordinate_to_map_point(neighbour_info['line_start'], city)}",
'neighbour_end': f"{GeometryHelper.coordinate_to_map_point(neighbour_info['line_end'], city)}",
2023-03-10 14:09:41 -05:00
'shared_points': 1
}
# Add my info to my neighbour shared lines
if neighbour.name in lines_information.keys() and building_key in lines_information[neighbour.name]:
shared_points = int(lines_information[neighbour.name][building_key]['shared_points'])
lines_information[neighbour.name][building_key]['shared_points'] = shared_points + 1
else:
if neighbour.name not in lines_information.keys():
lines_information[neighbour.name] = {}
lines_information[neighbour.name][building_key] = {
'neighbour_name': building.name,
'line_start': neighbour_info['line_start'],
'line_end': neighbour_info['line_end'],
'neighbour_line_start': (coordinate[0], coordinate[1]),
'neighbour_line_end': (next_coordinate[0], next_coordinate[1]),
'neighbour_start': f"{GeometryHelper.coordinate_to_map_point(coordinate, city)}",
'neighbour_end': f"{GeometryHelper.coordinate_to_map_point(next_coordinate, city)}",
'coordinate_start': f"{GeometryHelper.coordinate_to_map_point(neighbour_info['line_start'], city)}",
'coordinate_end': f"{GeometryHelper.coordinate_to_map_point(neighbour_info['line_end'], city)}",
2023-03-10 14:09:41 -05:00
'shared_points': 1
}
if building.neighbours is None:
building.neighbours = [neighbour]
elif neighbour not in building.neighbours:
building.neighbours.append(neighbour)
if neighbour.neighbours is None:
neighbour.neighbours = [building]
elif building not in neighbour.neighbours:
neighbour.neighbours.append(building)
2023-02-24 07:39:08 -05:00
line += 1
2023-03-23 13:24:41 -04:00
# if plot:
# img.show()
2023-03-17 13:36:43 -04:00
return lines_information
@staticmethod
2021-03-31 14:17:53 -04:00
def segment_list_to_trimesh(lines) -> Trimesh:
2021-08-26 13:27:43 -04:00
"""
Transform a list of segments into a Trimesh
"""
2023-02-10 05:42:57 -05:00
# todo: trimesh has a method for this
line_points = [lines[0][0], lines[0][1]]
lines.remove(lines[0])
while len(lines) > 1:
i = 0
for line in lines:
i += 1
if GeometryHelper.distance_between_points(line[0], line_points[len(line_points) - 1]) < 1e-8:
line_points.append(line[1])
lines.pop(i - 1)
break
2021-08-26 13:27:43 -04:00
if GeometryHelper.distance_between_points(line[1], line_points[len(line_points) - 1]) < 1e-8:
line_points.append(line[0])
lines.pop(i - 1)
break
2023-02-10 05:42:57 -05:00
polyhedron = Polyhedron(Polygon(line_points).triangles)
2021-03-31 14:17:53 -04:00
trimesh = Trimesh(polyhedron.vertices, polyhedron.faces)
return trimesh
@staticmethod
def _merge_meshes(mesh1, mesh2):
v_1 = mesh1.vertices
f_1 = mesh1.faces
v_2 = mesh2.vertices
f_2 = mesh2.faces
length = len(v_1)
v_merge = np.concatenate((v_1, v_2))
f_merge = np.asarray(f_1)
for item in f_2:
point1 = item.item(0) + length
point2 = item.item(1) + length
point3 = item.item(2) + length
surface = np.asarray([point1, point2, point3])
f_merge = np.concatenate((f_merge, [surface]))
mesh_merge = Trimesh(vertices=v_merge, faces=f_merge)
2021-03-31 14:17:53 -04:00
mesh_merge.fix_normals()
return mesh_merge
@staticmethod
2021-03-31 14:17:53 -04:00
def divide_mesh_by_plane(trimesh, normal_plane, point_plane):
"""
Divide a mesh by a plane
2021-03-31 14:17:53 -04:00
:param trimesh: Trimesh
:param normal_plane: [x, y, z]
:param point_plane: [x, y, z]
:return: [Trimesh]
"""
# The first mesh returns the positive side of the plane and the second the negative side.
# If the plane does not divide the mesh (i.e. it does not touch it or it is coplanar with one or more faces),
# then it returns only the original mesh.
2021-03-31 14:17:53 -04:00
# todo: review split method in https://github.com/mikedh/trimesh/issues/235,
# once triangulate_polygon in Polygon class is solved
normal_plane_opp = [None] * len(normal_plane)
for i in range(0, len(normal_plane)):
normal_plane_opp[i] = - normal_plane[i]
2021-03-31 14:17:53 -04:00
section_1 = intersections.slice_mesh_plane(trimesh, normal_plane, point_plane)
if section_1 is None:
return [trimesh]
lines = list(intersections.mesh_plane(trimesh, normal_plane, point_plane))
cap = GeometryHelper.segment_list_to_trimesh(lines)
trimesh_1 = GeometryHelper._merge_meshes(section_1, cap)
section_2 = intersections.slice_mesh_plane(trimesh, normal_plane_opp, point_plane)
if section_2 is None:
return [trimesh_1]
trimesh_2 = GeometryHelper._merge_meshes(section_2, cap)
return [trimesh_1, trimesh_2]
2020-06-22 13:26:50 -04:00
@staticmethod
2021-08-26 13:27:43 -04:00
def get_location(latitude, longitude) -> Location:
"""
Get Location from latitude and longitude
"""
url = 'https://nominatim.openstreetmap.org/reverse?lat={latitude}&lon={longitude}&format=json'
response = requests.get(url.format(latitude=latitude, longitude=longitude))
if response.status_code != 200:
# This means something went wrong.
raise Exception('GET /tasks/ {}'.format(response.status_code))
2021-08-26 13:27:43 -04:00
response = response.json()
city = 'Unknown'
country = 'ca'
if 'city' in response['address']:
city = response['address']['city']
if 'country_code' in response['address']:
country = response['address']['country_code']
return Location(country, city)
@staticmethod
def distance_between_points(vertex1, vertex2):
2021-03-31 14:17:53 -04:00
"""
distance between points in an n-D Euclidean space
:param vertex1: point or vertex
:param vertex2: point or vertex
:return: float
"""
power = 0
for dimension in range(0, len(vertex1)):
power += math.pow(vertex2[dimension] - vertex1[dimension], 2)
distance = math.sqrt(power)
return distance