Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev reworking #20

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,184 changes: 617 additions & 567 deletions example.ipynb

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions osmgt/compoments/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from osmgt.apis.nominatim import NominatimApi
from osmgt.apis.overpass import OverpassApi

from osmgt.geometry.network_topology import NetworkFeature

from osmgt.helpers.global_values import network_queries
from osmgt.helpers.global_values import epsg_4326
from osmgt.helpers.global_values import out_geom_query
Expand All @@ -23,7 +25,7 @@

from osmgt.helpers.global_values import osm_url

from osmgt.helpers.misc import chunker
from osmgt.helpers.misc import chunker, df_to_gdf


class ErrorOsmGtCore(Exception):
Expand Down Expand Up @@ -167,17 +169,16 @@ def get_gdf(self, verbose: bool = True) -> gpd.GeoDataFrame:
if not isinstance(self._output_data, gpd.GeoDataFrame):
# more performance comparing .from_features() method
df = pd.DataFrame()
for chunk in chunker(self._output_data, 100000):

if isinstance(self._output_data[0], NetworkFeature):
data = list(map(lambda x: x.to_dict(), self._output_data))
else:
data = self._output_data

for chunk in chunker(data, 100000):
df_tmp = pd.DataFrame(chunk)
df = pd.concat((df, df_tmp), axis=0)
df: pd.DataFrame = pd.DataFrame(self._output_data)

geometry = df[self._GEOMETRY_FIELD]
output_gdf: gpd.GeoDataFrame = gpd.GeoDataFrame(
df.drop([self._GEOMETRY_FIELD], axis=1),
crs=f"EPSG:{epsg_4326}",
geometry=geometry.to_list(),
)
output_gdf: gpd.GeoDataFrame = df_to_gdf(df)

else:
output_gdf: gpd.GeoDataFrame = self._output_data
Expand All @@ -187,7 +188,6 @@ def get_gdf(self, verbose: bool = True) -> gpd.GeoDataFrame:
output_gdf: gpd.GeoDataFrame = self._clean_attributes(output_gdf)

self.logger.info("GeoDataframe Ready")

return output_gdf

def _check_build_input_data(self, output_gdf) -> None:
Expand Down Expand Up @@ -216,6 +216,7 @@ def _location_osm_default_id_computing(self, osm_location_id: int) -> int:
def _build_feature_from_osm(
self, uuid_enum: int, geometry: Union[Point, LineString], properties: Dict
) -> Dict:
# TODO improve it: get()
properties_found: Dict = properties.get(self._PROPERTIES_OSM_FIELD, {})
properties_found[self._ID_OSM_FIELD] = str(properties[self._ID_OSM_FIELD])
properties_found[
Expand Down
48 changes: 14 additions & 34 deletions osmgt/compoments/roads.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import geopandas as gpd
from typing import Tuple
from typing import List
Expand All @@ -12,19 +13,13 @@
from osmgt.compoments.core import EmptyData

from osmgt.geometry.network_topology import NetworkTopology
from osmgt.geometry.network_topology import NetworkFeature

from osmgt.geometry.geom_helpers import compute_wg84_line_length
from osmgt.geometry.geom_helpers import linestring_points_fom_positions

from shapely.geometry import LineString
from shapely.geometry import Point

# to facilitate debugging
try:
from osmgt.network.gt_helper import GraphHelpers
except ModuleNotFoundError:
pass

from osmgt.network.gt_helper import GraphHelpers

from osmgt.helpers.global_values import network_queries

Expand All @@ -45,7 +40,6 @@ class OsmGtRoads(OsmGtCore):
)
_FEATURE_OSM_TYPE: str = "way"


def __init__(self) -> None:
super().__init__()

Expand All @@ -69,9 +63,10 @@ def from_location(
query = self._get_query_from_mode(mode)
request = self._from_location_name_query_builder(self._location_id, query)
raw_data = self._query_on_overpass_api(request)
self._output_data = self.__build_network_topology(
self._output_data: List[NetworkFeature] = self.__build_network_topology(
raw_data, additional_nodes, mode, interpolate_lines
)
return self.get_gdf()

def from_bbox(
self,
Expand All @@ -95,45 +90,30 @@ def from_bbox(
raw_data, additional_nodes, mode, interpolate_lines
)

def get_graph(self) -> GraphHelpers:
def get_graph(self):
self.logger.info("Prepare graph")
self._check_network_output_data()

graph = GraphHelpers(
self.logger, is_directed=network_queries[self._mode]["directed_graph"]
)

# graph.add_edges(self._output_data) # BULK mode
for feature in self._output_data:
graph.add_edge(*self.__compute_edges(feature))

graph.add_edge(
feature.start_coords,
feature.end_coords,
feature.topo_uuid,
feature.length
)
self.logger.info("Graph ok")
return graph

def _check_network_output_data(self):

if len(self._output_data) == 0:
raise EmptyData("Data is empty!")

# here we check the first feature, all feature should have the same structure
first_feature = self._output_data[0]
assert (
self._GEOMETRY_FIELD in first_feature
), f"{self._GEOMETRY_FIELD} key not found!"
assert (
first_feature[self._GEOMETRY_FIELD].geom_type
== self._OUTPUT_EXPECTED_GEOM_TYPE
), f"{self._GEOMETRY_FIELD} key not found!"
assert self._TOPO_FIELD in first_feature, f"{self._TOPO_FIELD} key not found!"

def __compute_edges(self, feature: Dict) -> Tuple[str, str, str, float]:
geometry = feature[self._GEOMETRY_FIELD]
first_coords, *_, last_coords = geometry.coords
return (
Point(first_coords).wkt,
Point(last_coords).wkt,
feature[self._TOPO_FIELD],
compute_wg84_line_length(geometry),
)

def __build_network_topology(
self,
raw_data: List[Dict],
Expand Down
97 changes: 71 additions & 26 deletions osmgt/geometry/network_topology.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from dataclasses import dataclass
from typing import Tuple
from typing import List
from typing import Dict
Expand All @@ -8,9 +9,11 @@

from scipy import spatial

from shapely.geometry import Point
from shapely.geometry import LineString

import rtree
from osmgt.geometry.geom_helpers import compute_wg84_line_length

import numpy as np

Expand All @@ -30,6 +33,51 @@
class NetworkTopologyError(Exception):
pass

class NetworkFeature:
__slots__ = (
"geometry",
"topo_uuid",
"id",
"oneway",
"topology",
)

def __init__(self, geometry: LineString, topo_uuid: str, id_value: str, oneway: str, topology: str):
self.geometry = geometry
self.topo_uuid = topo_uuid
self.id = id_value
self.oneway = oneway
self.topology = topology

@property
def start_coords(self) -> str:
return Point(self.geometry.coords[0]).wkt

@property
def end_coords(self) -> str:
return Point(self.geometry.coords[-1]).wkt

@property
def length(self) -> float:
return compute_wg84_line_length(self.geometry)

def to_dict(self) -> Dict:
return {
"geometry": self.geometry,
"topo_uuid": self.topo_uuid,
"id": self.id,
"oneway": self.oneway,
"topology": self.topology,
}

@property
def to_graph(self):
return (
self.start_coords,
self.end_coords,
self.topo_uuid,
self.length
)

class NetworkTopology:
__slots__ = (
Expand Down Expand Up @@ -117,9 +165,9 @@ def __init__(

self._intersections_found: Optional[Set[Tuple[float, float]]] = None
self.__connections_added: Dict = {}
self._output: List[Dict] = []
self._output: List[NetworkFeature] = []

def run(self) -> List[Dict]:
def run(self) -> List[NetworkFeature]:
self._prepare_data()

# ugly footway processing...
Expand Down Expand Up @@ -232,17 +280,20 @@ def build_lines(self, feature: Dict) -> None:
new_features = self.mode_processing(feature)
self._output.extend(new_features)

def mode_processing(self, input_feature):
def mode_processing(self, input_feature: Dict) -> List[NetworkFeature]:
new_elements = []

if self._mode_post_processing == "vehicle":
# by default
new_forward_feature = self._direction_processing(input_feature, forward_tag)
new_elements.extend(new_forward_feature)
if input_feature.get(self.__JUNCTION_FIELD, None) in self.__JUNCTION_VALUES:

if input_feature[self.__JUNCTION_FIELD] if self.__JUNCTION_FIELD in input_feature else None \
in self.__JUNCTION_VALUES:
return new_elements

if input_feature.get(self.__ONEWAY_FIELD, None) != self.__ONEWAY_VALUE:
if input_feature[self.__ONEWAY_FIELD] if self.__ONEWAY_FIELD in input_feature else None\
!= self.__ONEWAY_VALUE:

new_backward_feature = self._direction_processing(
input_feature, backward_tag
Expand All @@ -259,7 +310,7 @@ def mode_processing(self, input_feature):

def _direction_processing(
self, input_feature: Dict, direction: Optional[str] = None
):
) -> List[NetworkFeature]:
new_features = []
input_feature_copy = dict(input_feature)

Expand Down Expand Up @@ -287,8 +338,7 @@ def _direction_processing(

def __proceed_direction_geom(
self, direction, input_feature, sub_line_coords, idx=None
):
feature = dict(input_feature)
) -> NetworkFeature:

if idx is not None:
idx = f"_{idx}"
Expand All @@ -301,14 +351,20 @@ def __proceed_direction_geom(
new_linestring = LineString(sub_line_coords)
else:
raise NetworkTopologyError(f"Direction issue: value '{direction}' found")
feature[self.__GEOMETRY_FIELD] = new_linestring

if direction is not None:
feature[self.__FIELD_ID] = f"{feature[self.__FIELD_ID]}{idx}_{direction}"
uuid = f"{input_feature[self.__FIELD_ID]}{idx}_{direction}"
else:
feature[self.__FIELD_ID] = f"{feature[self.__FIELD_ID]}{idx}"
uuid = f"{input_feature[self.__FIELD_ID]}{idx}"


return feature
return NetworkFeature(
id_value=input_feature["id"],
geometry=new_linestring,
oneway=input_feature["oneway"] if "oneway" in input_feature else None,
topology=input_feature["topology"],
topo_uuid=uuid,
)

def _split_line(self, feature: Dict, interpolation_level: int) -> List:
new_line_coords = interpolate_curve_based_on_original_points(
Expand All @@ -319,23 +375,12 @@ def _split_line(self, feature: Dict, interpolation_level: int) -> List:
def _prepare_data(self):

self._network_data = {
feature[self.__FIELD_ID]: {
**{self.__COORDINATES_FIELD: feature[self.__GEOMETRY_FIELD].coords[:]},
**feature,
**{self.__CLEANING_FILED_STATUS: self.__TOPOLOGY_TAG_UNCHANGED},
}
feature[self.__FIELD_ID]: {self.__COORDINATES_FIELD: feature[self.__GEOMETRY_FIELD].coords[:]} | feature | {self.__CLEANING_FILED_STATUS: self.__TOPOLOGY_TAG_UNCHANGED}
for feature in self._network_data
}
if self._additional_nodes is not None:
self._additional_nodes = {
feature[self.__FIELD_ID]: {
**{
self.__COORDINATES_FIELD: feature[self.__GEOMETRY_FIELD].coords[
0
]
},
**feature,
}
feature[self.__FIELD_ID]: {self.__COORDINATES_FIELD: feature[self.__GEOMETRY_FIELD].coords[0]} | feature
for feature in self._additional_nodes
}

Expand All @@ -353,7 +398,7 @@ def compute_added_node_connections(self):
with concurrent.futures.ThreadPoolExecutor() as executor:
executor.map(self.split_line, node_keys_by_nearest_lines_filled)

self._network_data: Dict = {**self._network_data, **self.__connections_added}
self._network_data: Dict = self._network_data | self.__connections_added

self.logger.info(
f"Topology lines checker: {', '.join([f'{key}: {value}' for key, value in self.__topology_stats.items()])}"
Expand Down
2 changes: 1 addition & 1 deletion osmgt/helpers/global_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
backward_tag: str = "backward"

# topology
topology_fields: List[str] = ["topo_uuid", "id", "topology", "osm_url", "geometry"]
topology_fields: List[str] = ["topo_uuid", "id", "topology", "geometry"]

# POIs overpass query
poi_query: str = (
Expand Down
16 changes: 14 additions & 2 deletions osmgt/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
from typing import Union
from typing import Any

import geopandas as gpd
import pandas as pd

import time

from functools import wraps


def find_list_dicts_from_key_and_value(
input_dict: List[Dict], key: str, value: Union[Any]
input_dict: List[Dict], key: str, value: Any
) -> int:

for list_idx, values in enumerate(input_dict):
Expand Down Expand Up @@ -63,6 +66,15 @@ def f_retry(*args , **kwargs):
return deco_retry


def chunker(seq, size):
def chunker(seq: List, size: int) -> List:
return (seq[pos:pos + size] for pos in range(0, len(seq), size))


def df_to_gdf(dataframe: pd.DataFrame, geom_colmun: str="geometry", epsg: int=4326) -> gpd.GeoDataFrame:
geometry = dataframe[geom_colmun]
output_gdf: gpd.GeoDataFrame = gpd.GeoDataFrame(
dataframe.drop([geom_colmun], axis=1),
crs=f"EPSG:{epsg}",
geometry=geometry.to_list(),
)
return output_gdf
Loading