From c56ace42ed4ea48e3d840a24f38edee4c4a3b498 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Wed, 21 Aug 2024 00:08:31 +0100 Subject: [PATCH] more refactor --- pygeoapi/provider/speckle.py | 148 +++--------------- pygeoapi/provider/speckle_utils/crs_utils.py | 64 +++++++- .../provider/speckle_utils/display_utils.py | 1 - .../provider/speckle_utils/feature_utils.py | 68 ++++++++ .../provider/speckle_utils/server_utils.py | 31 ++++ 5 files changed, 182 insertions(+), 130 deletions(-) create mode 100644 pygeoapi/provider/speckle_utils/feature_utils.py create mode 100644 pygeoapi/provider/speckle_utils/server_utils.py diff --git a/pygeoapi/provider/speckle.py b/pygeoapi/provider/speckle.py index c5f2725..d976d75 100644 --- a/pygeoapi/provider/speckle.py +++ b/pygeoapi/provider/speckle.py @@ -346,8 +346,11 @@ class SpeckleProvider(BaseProvider): def __repr__(self): return f" {self.data}" - def load_speckle_data(self: str): - + def load_speckle_data(self: str) -> Dict: + """Receive and process Speckle data, return geojson.""" + + from pygeoapi.provider.speckle_utils.server_utils import get_stream_branch, get_client + from specklepy.logging.exceptions import SpeckleException from specklepy.api import operations from specklepy.core.api.wrapper import StreamWrapper @@ -364,22 +367,9 @@ class SpeckleProvider(BaseProvider): # set actual branch wrapper.model_id = self.speckle_url.split("models/")[1].split("/")[0].split("&")[0] - # get client by URL, no authentication - client = SpeckleClient(host=wrapper.host, use_ssl=wrapper.host.startswith("https")) - client.account.serverInfo.url = url_proj.split("/projects")[0] - - # get branch data - stream = client.stream.get( - id = wrapper.stream_id, branch_limit=100 - ) - - if isinstance(stream, Exception): - raise SpeckleException(stream.message+ ", "+ self.speckle_url) - - for br in stream['branches']['items']: - if br['id'] == wrapper.model_id: - branch = br - break + # get stream and branch data + client = get_client(wrapper, url_proj) + stream, branch = get_stream_branch(self, client, wrapper) # set the Model name self.model_name = branch['name'] @@ -391,7 +381,7 @@ class SpeckleProvider(BaseProvider): if transport == None: raise SpeckleException("Transport not found") - # data transfer + # receive commit try: commit_obj = operations.receive(objId, transport, None) except Exception as ex: @@ -404,18 +394,19 @@ class SpeckleProvider(BaseProvider): source_application="pygeoapi", message="Received commit in pygeoapi", ) - print(f"Rendering model '{branch['name']}' of the project '{stream['name']}'") + print(f"Rendering model '{branch['name']}' of the project '{stream['name']}'") speckle_data = self.traverse_data(commit_obj) + speckle_data["project"] = stream['name'] speckle_data["model"] = branch['name'] return speckle_data - def traverse_data(self, commit_obj): + def traverse_data(self, commit_obj) -> Dict: + """Traverse Speckle commit and return geojson with features.""" from specklepy.objects.geometry import Point, Line, Polyline, Curve, Mesh, Brep - from specklepy.objects.GIS.CRS import CRS from specklepy.objects.GIS.layers import VectorLayer from specklepy.objects.GIS.geometry import GisPolygonElement from specklepy.objects.GIS.GisFeature import GisFeature @@ -423,11 +414,9 @@ class SpeckleProvider(BaseProvider): GraphTraversal, TraversalRule, ) - from pygeoapi.provider.speckle_utils.crs_utils import create_crs_from_wkt, create_crs_default, create_crs_dict - from pygeoapi.provider.speckle_utils.coords_utils import reproject_bulk - from pygeoapi.provider.speckle_utils.props_utils import assign_props, assign_missing_props - from pygeoapi.provider.speckle_utils.converter_utils import assign_geometry - from pygeoapi.provider.speckle_utils.display_utils import find_display_obj, set_default_color, assign_display_properties, get_display_units + from pygeoapi.provider.speckle_utils.crs_utils import get_set_crs_settings + from pygeoapi.provider.speckle_utils.feature_utils import create_features + from pygeoapi.provider.speckle_utils.display_utils import set_default_color supported_classes = [GisFeature, GisPolygonElement, Mesh, Brep, Point, Line, Polyline, Curve] supported_types = [y().speckle_type for y in supported_classes] @@ -447,7 +436,6 @@ class SpeckleProvider(BaseProvider): "features": [], "model_crs": "-", } - self.assign_coordinate_system_to_geojson(data) rule = TraversalRule( [lambda _: True], lambda x: [ @@ -459,111 +447,15 @@ class SpeckleProvider(BaseProvider): ) context_list = [x for x in GraphTraversal([rule]).traverse(commit_obj)] - # iterate Speckle objects to get CRS, DisplayUnits, offsets, rotation - crs = None - displayUnits = None - offset_x = 0 - offset_y = 0 - try: - for item in [commit_obj] + commit_obj.elements: - if ( - crs is None - and hasattr(item, "crs") - and isinstance(item["crs"], CRS) - ): - crs = item["crs"] - displayUnits = crs["units_native"] - offset_x = crs["offset_x"] - offset_y = crs["offset_y"] - self.north_degrees = crs["rotation"] - create_crs_from_wkt(self, crs["wkt"]) - - if self.crs.to_authority() is not None: - data["model_crs"] = f"{self.crs.to_authority()}, {self.crs.name} " - else: - data["model_crs"] = f"{self.crs.to_proj4()}" - - break - except AttributeError as ex: - pass # old commit structure - - # if CRS not found, create default one and get model units for scaling - if self.crs is None: - create_crs_default(self) - # if displayUnits not found, get from displayable object - if displayUnits is None: - displayUnits = get_display_units(context_list) - - create_crs_dict(self, offset_x, offset_y, displayUnits) - - # get coordinates in bulk - all_coords = [] - all_coord_counts = [] - - - all_props = [] + get_set_crs_settings(self, commit_obj, context_list, data) set_default_color(context_list) - - print(f"Loading features..") - time1 = datetime.now() - for item in context_list: - - f_base = item.current - f_id = item.current.id - f_fid = len(data["features"]) + 1 - - # initialize feature - feature: Dict = { - "type": "Feature", - # "bbox": [-180.0, -90.0, 180.0, 90.0], - "geometry": {}, - "properties": { - "id": f_id, - "FID": f_fid, - "speckle_type": item.current.speckle_type.split(":")[-1], - }, - } - - # feature geometry, props and displayProps - obj_display, obj_get_color = find_display_obj(f_base) - coords, coord_counts = assign_geometry(feature, obj_display) - if len(coords)!=0: - all_coords.extend(coords) - all_coord_counts.append(coord_counts) - - assign_props(f_base, feature["properties"]) - # update list of all properties - for prop in feature["properties"]: - if prop not in all_props: - all_props.append(prop) - - assign_display_properties(feature, f_base, obj_get_color) - data["features"].append(feature) - - assign_missing_props(data["features"], all_props) - - if len(data["features"])==0: - raise ValueError("No supported features found") - - time2 = datetime.now() - print(f"Loading features before reprojecting time: {(time2-time1).total_seconds()}") - - reproject_bulk(self, all_coords, all_coord_counts, [f["geometry"] for f in data["features"]]) + create_features(self, context_list, data) return data - def assign_coordinate_system_to_geojson(self, data: Dict): + def get_python_path(self) -> str: + """Get current Python executable path.""" - crs = { - "crs": { - "type": "name", - "properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}, - } - } - - data["crs"] = crs - - def get_python_path(self): if sys.platform.startswith("linux"): return sys.executable pythonExec = os.path.dirname(sys.executable) diff --git a/pygeoapi/provider/speckle_utils/crs_utils.py b/pygeoapi/provider/speckle_utils/crs_utils.py index 07e9336..3a37f72 100644 --- a/pygeoapi/provider/speckle_utils/crs_utils.py +++ b/pygeoapi/provider/speckle_utils/crs_utils.py @@ -1,4 +1,7 @@ +from typing import Dict, List + + def create_crs_from_wkt(self: "SpeckleProvider", wkt: str | None) -> None: """Create and assign CRS object from WKT string.""" @@ -25,7 +28,7 @@ def create_crs_default(self: "SpeckleProvider") -> None: self.crs = crs_obj def create_crs_dict(self: "SpeckleProvider", offset_x, offset_y, displayUnits: str | None) -> None: - """Create and assign CRS object from WKT string.""" + """Create and assign CRS_dict of SpeckleProvider.""" if self.crs is not None: self.crs_dict = { @@ -37,3 +40,62 @@ def create_crs_dict(self: "SpeckleProvider", offset_x, offset_y, displayUnits: s "obj": self.crs, } + +def get_set_crs_settings(self: "SpeckleProvider", commit_obj: "Base", context_list: List["TraversalContext"], data: Dict) -> None: + """Assign CRS object and Dict to SpeckleProvider.""" + + from pygeoapi.provider.speckle_utils.display_utils import get_display_units + from specklepy.objects.GIS.CRS import CRS + + assign_coordinate_system_to_geojson(data) + + root_objects = [] + try: + root_objects = [commit_obj] + commit_obj.elements + except AttributeError as ex: + pass # old commit structure + + # iterate Speckle objects to get CRS, DisplayUnits, offsets, rotation + crs = None + displayUnits = None + offset_x = 0 + offset_y = 0 + + for item in root_objects: + if ( + crs is None + and hasattr(item, "crs") + and isinstance(item["crs"], CRS) + ): + crs = item["crs"] + displayUnits = crs["units_native"] + offset_x = crs["offset_x"] + offset_y = crs["offset_y"] + self.north_degrees = crs["rotation"] + create_crs_from_wkt(self, crs["wkt"]) + + if self.crs.to_authority() is not None: + data["model_crs"] = f"{self.crs.to_authority()}, {self.crs.name} " + else: + data["model_crs"] = f"{self.crs.to_proj4()}" + break + + # if CRS not found, create default one and get model units for scaling + if self.crs is None: + create_crs_default(self) + if displayUnits is None: + displayUnits = get_display_units(context_list) + + create_crs_dict(self, offset_x, offset_y, displayUnits) + + + +def assign_coordinate_system_to_geojson(data: Dict): + + crs = { + "crs": { + "type": "name", + "properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"}, + } + } + data["crs"] = crs diff --git a/pygeoapi/provider/speckle_utils/display_utils.py b/pygeoapi/provider/speckle_utils/display_utils.py index d9a1da9..b253fb2 100644 --- a/pygeoapi/provider/speckle_utils/display_utils.py +++ b/pygeoapi/provider/speckle_utils/display_utils.py @@ -51,7 +51,6 @@ def find_display_obj(obj) -> Tuple["Base", "Base"]: displayValForColor = mesh displayVal = displayValForColor - print(displayValForColor) # if not searching for colored object, return GisFeatures as is if obj.speckle_type.endswith(".GisFeature"): diff --git a/pygeoapi/provider/speckle_utils/feature_utils.py b/pygeoapi/provider/speckle_utils/feature_utils.py new file mode 100644 index 0000000..6d61ab2 --- /dev/null +++ b/pygeoapi/provider/speckle_utils/feature_utils.py @@ -0,0 +1,68 @@ + +from datetime import datetime +from typing import Dict, List + + +def initialize_features(all_coords, all_coord_counts, data, context_list) -> None: + """Create features with props and displayProps, and assign flat list of coordinates.""" + + from pygeoapi.provider.speckle_utils.props_utils import assign_props, assign_missing_props + from pygeoapi.provider.speckle_utils.converter_utils import assign_geometry + from pygeoapi.provider.speckle_utils.display_utils import find_display_obj, assign_display_properties + + print(f"Creating features..") + time1 = datetime.now() + + all_props = [] + + for item in context_list: + + f_base = item.current + f_id = item.current.id + f_fid = len(data["features"]) + 1 + + # initialize feature + feature: Dict = { + "type": "Feature", + # "bbox": [-180.0, -90.0, 180.0, 90.0], + "geometry": {}, + "properties": { + "id": f_id, + "FID": f_fid, + "speckle_type": item.current.speckle_type.split(":")[-1], + }, + } + + # feature geometry, props and displayProps + obj_display, obj_get_color = find_display_obj(f_base) + coords, coord_counts = assign_geometry(feature, obj_display) + if len(coords)!=0: + all_coords.extend(coords) + all_coord_counts.append(coord_counts) + + assign_props(f_base, feature["properties"]) + # update list of all properties + for prop in feature["properties"]: + if prop not in all_props: + all_props.append(prop) + + assign_display_properties(feature, f_base, obj_get_color) + data["features"].append(feature) + + assign_missing_props(data["features"], all_props) + + if len(data["features"])==0: + raise ValueError("No supported features found") + + time2 = datetime.now() + print(f"Creating features time: {(time2-time1).total_seconds()}") + +def create_features(self: "SpeckleProvider", context_list: List["TraversalContext"], data: Dict) -> None: + """Create features from the list of traversal context.""" + + from pygeoapi.provider.speckle_utils.coords_utils import reproject_bulk + + all_coords = [] + all_coord_counts = [] + initialize_features(all_coords, all_coord_counts, data, context_list) + reproject_bulk(self, all_coords, all_coord_counts, [f["geometry"] for f in data["features"]]) diff --git a/pygeoapi/provider/speckle_utils/server_utils.py b/pygeoapi/provider/speckle_utils/server_utils.py new file mode 100644 index 0000000..9e199d0 --- /dev/null +++ b/pygeoapi/provider/speckle_utils/server_utils.py @@ -0,0 +1,31 @@ +from typing import Tuple + + +def get_stream_branch(self: "SpeckleProvider", client: "SpeckleClient", wrapper: "StreamWrapper") -> Tuple: + """Get stream and branch from the server.""" + + from specklepy.logging.exceptions import SpeckleException + + branch = None + stream = client.stream.get( + id = wrapper.stream_id, branch_limit=100 + ) + + if isinstance(stream, Exception): + raise SpeckleException(stream.message+ ", "+ self.speckle_url) + + for br in stream['branches']['items']: + if br['id'] == wrapper.model_id: + branch = br + break + return stream, branch + +def get_client(wrapper: "StreamWrapper", url_proj: str) -> "SpeckleClient": + """Get unauthenticated SpeckleClient.""" + + from specklepy.core.api.client import SpeckleClient + + # get client by URL, no authentication + client = SpeckleClient(host=wrapper.host, use_ssl=wrapper.host.startswith("https")) + client.account.serverInfo.url = url_proj.split("/projects")[0] + return client