68 Commits

Author SHA1 Message Date
KatKatKateryna 9cc56e6e97 typecheck
flake8 / flake8_py3 (push) Has been cancelled
Build / main (3.10) (push) Has been cancelled
Build / admin (3.10) (push) Has been cancelled
Check vulnerabilities / vulnerabilities (push) Has been cancelled
2024-12-22 00:46:21 +00:00
KatKatKateryna 3da41021b1 don't report check pings 2024-12-20 17:42:25 +00:00
KatKatKateryna 36fb66e72e Update loading_screen.html 2024-12-14 01:04:56 +08:00
KatKatKateryna 8325724ab0 Update loading_screen.html 2024-12-14 01:02:13 +08:00
KatKatKateryna 1c749f09c8 Add files via upload 2024-12-13 17:00:52 +00:00
KatKatKateryna 62a37aa7fb remove style dependency 2024-11-26 17:14:24 +00:00
KatKatKateryna 660b37384b solve complex colors 2024-11-26 01:12:58 +00:00
KatKatKateryna d525d58111 add feedback link to loading screen 2024-11-26 00:20:59 +00:00
KatKatKateryna 46aecdac41 feedback form link 2024-11-25 20:58:35 +00:00
KatKatKateryna a4122673ec feedback btn 2024-11-22 19:45:52 +00:00
KatKatKateryna b07ed87d03 add sourceApp 2024-11-22 17:53:40 +00:00
KatKatKateryna 6833aaef21 add project Id 2024-11-22 16:29:37 +00:00
KatKatKateryna eda0268524 hostApp 2024-11-14 10:26:33 +00:00
KatKatKateryna a29b0c3e73 typed displayValue 2024-11-12 10:26:36 +00:00
KatKatKateryna 9f63ef8ced stop patching 2024-11-08 15:25:58 +00:00
KatKatKateryna e94ca610b7 properly patch serializer 2024-11-08 15:20:52 +00:00
KatKatKateryna 46a7b968e1 expand context list with VectorLayers (not only displayable) 2024-11-07 23:33:26 +00:00
KatKatKateryna 82fcfe6661 Revert "fix headers"
This reverts commit 3e4af97056.
2024-11-07 12:33:42 +00:00
KatKatKateryna 3e4af97056 fix headers 2024-11-07 12:17:19 +00:00
KatKatKateryna 99b397b648 slice json 2024-11-07 11:13:51 +00:00
KatKatKateryna 1bcd4a67f5 patch, extra json filter 2024-11-07 11:09:27 +00:00
KatKatKateryna a7de3ee43e check color proxies last 2024-11-06 21:04:05 +00:00
KatKatKateryna 39b81f0a15 Merge pull request #13 from specklesystems/fixing_ifc
Fixing ifc
2024-11-06 19:17:41 +00:00
KatKatKateryna 4bfcd9e6b7 treat 3d Polygons as Meshes 2024-11-06 19:16:04 +00:00
KatKatKateryna cc4a7f6d3a print 2024-11-05 11:27:46 +00:00
KatKatKateryna d0a0d7efae ignore speckleUrl character case 2024-11-05 11:24:08 +00:00
KatKatKateryna 805b148366 support older gis features with no displayValue 2024-10-31 23:03:13 +00:00
KatKatKateryna 7740958608 assign colors better 2024-10-30 18:35:01 +00:00
KatKatKateryna 63d2879709 add 3d extent; check isDisplayable; ignore invalid IFC meshes 2024-10-30 17:35:04 +00:00
KatKatKateryna 396916bb7c fix bug in setting AuthId 2024-10-29 14:14:22 +00:00
KatKatKateryna c7c7ef8b85 unbound error 2024-10-21 18:00:39 +01:00
KatKatKateryna 3389528861 better traverse of GH models 2024-10-14 16:26:10 +01:00
KatKatKateryna 8c691454e6 better warning 2024-10-14 13:18:59 +01:00
KatKatKateryna 5ef776ada0 correct table URL redirect 2024-10-14 13:08:22 +01:00
KatKatKateryna e5077e1677 better dataType warning 2024-10-14 12:34:45 +01:00
KatKatKateryna 672c29671e move validation 2024-10-09 12:16:29 +01:00
KatKatKateryna 0d2de03cd6 handle exception 2024-10-08 09:48:43 +01:00
KatKatKateryna 860bcc1086 cleaned 2024-10-07 20:33:06 +01:00
KatKatKateryna e11a1338a5 rename 2024-10-07 19:29:04 +01:00
KatKatKateryna 525955c058 clean patch 2024-10-07 19:23:57 +01:00
KatKatKateryna 3bcea48c32 validation patch 2024-10-07 19:15:57 +01:00
KatKatKateryna 6e48c4b8b0 index page map error 2024-10-04 17:45:26 +01:00
KatKatKateryna f7f499aa81 fix comment&object redirect 2024-10-03 09:38:32 +01:00
KatKatKateryna fb933e0472 comment position 2024-10-03 01:57:19 +01:00
KatKatKateryna 7f4dfe4272 socket fix 2024-09-30 01:30:42 +01:00
KatKatKateryna 92e016b004 zoom control 2024-09-29 22:45:45 +01:00
KatKatKateryna eb287480c9 logs 2024-09-29 22:20:46 +01:00
KatKatKateryna c84e81451d receive GH objects 2024-09-29 20:20:26 +01:00
KatKatKateryna 6ec6725947 check nonetype 2024-09-27 14:47:16 +01:00
KatKatKateryna f814592ae1 status check 2024-09-27 09:37:58 +01:00
KatKatKateryna 4097aff759 request url
flake8 / flake8_py3 (push) Has been cancelled
Build / main (3.10) (push) Has been cancelled
Build / admin (3.10) (push) Has been cancelled
Check vulnerabilities / vulnerabilities (push) Has been cancelled
2024-09-26 17:00:02 +01:00
KatKatKateryna 8f845c95bb url_params 2024-09-26 16:58:20 +01:00
KatKatKateryna e2bf4ee3b1 don't patch repeatedly 2024-09-26 14:33:10 +01:00
KatKatKateryna 5b37680d73 Merge pull request #12 from specklesystems/exceptions-and-forwarding
Exceptions and forwarding
2024-09-26 15:22:45 +02:00
KatKatKateryna 661d0e9104 header removed 2024-09-26 14:20:07 +01:00
KatKatKateryna 434f712cbf deserialized error fixed 2024-09-26 14:19:59 +01:00
KatKatKateryna ebfb49c4a1 remove html element only if exists 2024-09-25 09:15:46 +02:00
KatKatKateryna 8759fc9e09 remove printing 2024-09-24 01:23:36 +02:00
KatKatKateryna 4e43085ef2 operations in order 2024-09-24 01:17:39 +02:00
KatKatKateryna 079e8e5dda fix header 2024-09-24 00:11:26 +02:00
KatKatKateryna f5e52de572 _ 2024-09-23 13:53:27 +02:00
KatKatKateryna 66d7bd69fb readme sample and time 2024-09-23 12:36:11 +02:00
KatKatKateryna 6b7112ccef logo access 2024-09-23 11:31:05 +02:00
KatKatKateryna 6bf99294db agent crash fix 2024-09-23 11:27:16 +02:00
KatKatKateryna 8bbd50265e return separate response for browser-run apps 2024-09-21 13:55:16 +02:00
KatKatKateryna dd2f3f3bb0 Merge pull request #11 from specklesystems/gergo/server_setup
Gergo/server setup
2024-09-06 18:13:51 +01:00
KatKatKateryna a20daffc4b Extra check (#10)
* colors and materials

* traverse for colors

* fix multipolygons in 3d

* prioritize attributes by default, less geometry divisions

* fix multipolygons

* split polygons in leaflet

* ensure speckleURL argument

* avoid multiple loops

* URL exceptions handling

* add bbox per feature

* leaflet controls to the right

* html syntax

* double-check for display vals
2024-09-05 21:31:22 +08:00
KatKatKateryna 6a8b5df87c Polishing (#9)
flake8 / flake8_py3 (push) Has been cancelled
Build / main (3.10) (push) Has been cancelled
Build / admin (3.10) (push) Has been cancelled
Check vulnerabilities / vulnerabilities (push) Has been cancelled
* colors and materials

* traverse for colors

* fix multipolygons in 3d

* prioritize attributes by default, less geometry divisions

* fix multipolygons

* split polygons in leaflet

* ensure speckleURL argument

* avoid multiple loops

* URL exceptions handling

* add bbox per feature

* leaflet controls to the right

* html syntax
2024-09-05 06:55:19 +08:00
30 changed files with 733 additions and 310 deletions
+1 -1
View File
@@ -530,7 +530,7 @@ paths:
- hello-world
servers:
- description: pygeoapi provides an API to geospatial data
url: http://localhost:5000
url: https://geo.speckle.systems
tags:
- description: pygeoapi provides an API to geospatial data
externalDocs:
+1 -1
View File
@@ -1,5 +1,5 @@
access_log
error_log
error_log*
# Byte-compiled / optimized / DLL files
__pycache__/
+1 -1
View File
@@ -81,7 +81,7 @@ Then you can add it to the base map (e.g. using Leaflet and OpenStreetMap basema
loadSpeckleData();
async function loadSpeckleData() => {
var speckle_model_url = 'https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons';
var speckle_model_url = 'https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons';
const speckle_data = await fetch(speckle_model_url, {
headers: {'Accept': 'application/geo+json'}
}).then(response => response.json());
+1 -1
View File
@@ -75,7 +75,7 @@ LOGGER = logging.getLogger(__name__)
#: Return headers for requests (e.g:X-Powered-By)
HEADERS = {
'Content-Type': 'application/json',
'X-Powered-By': f'pygeoapi {__version__}'
# 'X-Powered-By': f'pygeoapi {__version__}'
}
CHARSET = ['utf-8']
+1 -56
View File
@@ -309,7 +309,7 @@ def get_collection_items(
# clear data if no URL params
load_data = False
for item in request.params:
if item == 'speckleUrl' and len(request.params[item])>40 and ('speckleUrl=' + request.params[item]) in provider_def['data']:
if item.lower() == 'speckleurl' and len(request.params[item])>40 and ('speckleurl=' + request.params[item]) in provider_def['data'].lower():
load_data = True
break
if load_data is False:
@@ -574,61 +574,6 @@ def get_collection_items(
if isinstance(url_saved_as_data, str):
url_props = url_saved_as_data.lower().split("&")
content['missing_url'] = content['missing_url_href'] = ""
content['requested_data_type'] = "polygons (default)"
content['preserve_attributes'] = "false (default)"
content['speckle_url'] = content['speckle_project_url'] = content['crs_authid'] = content['lat'] = content['lon'] = content['north_degrees'] = content['limit'] = "-"
crsauthid = False
for item in url_props:
# if CRS authid is found, rest will be ignored
if "speckleurl=" in item:
content['speckle_url'] = item.split("speckleurl=")[1]
if content['speckle_url'][-1] == "/":
content['speckle_url'] = content['speckle_url'][:-1]
content['speckle_project_url'] = content['speckle_url'].split("/models")[0]
elif "datatype=" in item:
content['requested_data_type'] = item.split("datatype=")[1]
if content['requested_data_type'] not in ["points", "lines", "polygons", "projectcomments"]:
content['requested_data_type'] = "polygons (default)"
elif "preserveattributes=" in item:
content['preserve_attributes'] = item.split("preserveattributes=")[1]
if content['preserve_attributes'] not in ["true", "false"]:
content['preserve_attributes'] = "false (default)"
elif "crsauthid=" in item:
content['crs_authid'] = item.split("crsauthid=")[1]
crsauthid = True
elif "lat=" in item:
try:
content['lat'] = float(item.split("lat=")[1])
except:
content['lat'] = f"Invalid input, must be numeric: {item.split('lat=')[1]}"
elif "lon=" in item:
try:
content['lon'] = float(item.split("lon=")[1])
except:
content['lon'] = f"Invalid input, must be numeric: {item.split('lon=')[1]}"
elif "northdegrees=" in item:
try:
content['north_degrees'] = float(item.split("northdegrees=")[1])
except:
content['north_degrees'] = f"Invalid input, must be numeric: {item.split('northdegrees=')[1]}"
elif "limit=" in item:
try:
content['limit'] = float(item.split("limit=")[1])
except:
content['limit'] = f"Invalid input, must be integer: {item.split('limit=')[1]}"
if content['speckle_url'] == "-":
content['missing_url'] = "true"
if crsauthid:
content['lat'] += " (not applied)"
content['lon'] += " (not applied)"
content['north_degrees'] += " (not applied)"
if content['limit'] == "-":
content['limit'] = f"{api.config['server']['limit']} (default)"
# Set response language to requested provider locale
# (if it supports language) and/or otherwise the requested pygeoapi
# locale (or fallback default locale)
+81 -24
View File
@@ -34,10 +34,14 @@ import os
from typing import Union
import click
from datetime import datetime, timezone
from flask import (Flask, Blueprint, make_response, request,
send_from_directory, Response, Request, stream_with_context)
from http import HTTPStatus
import json
from urllib.request import urlopen
from pygeoapi.api import API, APIRequest, apply_gzip
import pygeoapi.api.coverages as coverages_api
import pygeoapi.api.environmental_data_retrieval as edr_api
@@ -46,6 +50,7 @@ import pygeoapi.api.maps as maps_api
import pygeoapi.api.processes as processes_api
import pygeoapi.api.stac as stac_api
import pygeoapi.api.tiles as tiles_api
from pygeoapi.provider.speckle_utils.legal import COUNTRY_CODES
from pygeoapi.openapi import load_openapi_document
from pygeoapi.config import get_config
from pygeoapi.util import get_mimetype, get_api_rules, render_j2_template
@@ -168,6 +173,62 @@ def execute_from_flask(api_function, request: Request, *args,
return get_response((headers, status, content))
def handle_client(url_route: str):
# if called fromm the browser, Exceptions from this function will result in infinite load
agent = request.headers.get('User-Agent')
if request.environ.get('HTTP_X_FORWARDED_FOR') is None:
ip_address = request.environ['REMOTE_ADDR']
else:
ip_address = request.environ['HTTP_X_FORWARDED_FOR']
if agent is not None and "(https://www.checklyhq.com)" not in agent:
print(f"_______________________{datetime.now().astimezone(timezone.utc)} _URL access")
print(f"_Agent {url_route}: {agent}")
print(f"_IP Address: {ip_address}")
print(f"_Request URL: {request.url}")
request.url += f"&userAgent={agent}"
# by Agent:
if agent is not None and ("YaBrowser/" in agent or "yandex" in agent.lower()):
raise ValueError("Your browser is not supported.")
# by IP:
try:
url = 'https://ipinfo.io/' + ip_address + '/json'
res = urlopen(url)
data = json.load(res)
if isinstance(data, dict) and isinstance(data["country"], str):
if data["country"].lower() in COUNTRY_CODES:
raise PermissionError("Review Speckle Terms and Conditions")
else:
print(f"Error validating client: DATA {data}")
except Exception as e:
print(f"Error validating client from start: {e}")
def generate():
collection_id = "speckle"
yield loading_screen().data
handle_client("/")
CONFIG = get_config(request=request)
api_ = API(CONFIG, OPENAPI)
try:
browser_response = execute_from_flask(itemtypes_api.get_collection_items,
request, collection_id,
skip_valid_check=True)
yield browser_response.data
except PermissionError as ex:
raise ex
except Exception as ex:
yield error_screen(ex).data
@BLUEPRINT.route('/')
def landing_page():
"""
@@ -175,41 +236,24 @@ def landing_page():
:returns: HTTP response
"""
collection_id = "speckle"
agent = request.headers.get('User-Agent')
# Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
# Mozilla/5.0 QGIS/32815/Windows 10 Version 2009
# ArcGIS Pro 3.3.0 (00000000000) - ArcGISPro
browser_agent = False
browser_list = ["Chrome", "Safari", "Firefox", "Edg/", "Trident/"]
print(agent)
if "YaBrowser/" in agent:
raise ValueError("Your browser is not supported.")
for br in browser_list:
if br in agent:
if agent is not None and br in agent:
browser_agent = True
break
# if requested from the browser, return this, otherwise ignore IF statement
if request.method == 'GET' and browser_agent: # list items
def generate():
yield loading_screen().data
try:
browser_response = execute_from_flask(itemtypes_api.get_collection_items,
request, collection_id,
skip_valid_check=True)
yield browser_response.data
except Exception as ex:
yield error_screen(ex).data
return Response(stream_with_context(generate()))
# for non-browsers
handle_client("/")
CONFIG = get_config(request=request)
api_ = API(CONFIG, OPENAPI)
return get_response(api_.landing_page(request))
def error_screen(ex: Exception):
@@ -294,10 +338,20 @@ def collections(collection_id=None):
:returns: HTTP response
"""
# raise NotImplementedError()
handle_client("/collections")
return get_response(api_.describe_collections(request, collection_id))
@BLUEPRINT.route('/speckle')
def speckle_collection():
handle_client("/speckle")
collection_id="speckle"
return collection_items(collection_id=collection_id)
@BLUEPRINT.route('/collections/<path:collection_id>/schema')
def collection_schema(collection_id):
"""
@@ -343,6 +397,9 @@ def collection_items(collection_id, item_id=None):
:returns: HTTP response
"""
handle_client(f"/collections/{collection_id}/items")
collection_id = 'speckle'
if item_id is None:
+57 -12
View File
@@ -119,21 +119,33 @@ class SpeckleProvider(BaseProvider):
self.speckle_url = self.url.lower().split("speckleurl=")[-1].split("&")[0].split("@")[0].split("?")[0]
self.speckle_data = None
self.project_name = ""
self.project_id = ""
self.model_name = ""
self.sourceApp = ""
self.crs = None
self.crs_dict = None
self.requested_data_type: str = "polygons (default)" # points, lines, polygons, projectcomments
self.preserve_attributes: str = "false (default)"
self.commit_gis = False
self.url_params = {"url_data_type":"", "url_preserve_attributes":"", "url_crs_authid":"", "url_lat":"","url_lon":"","url_north_degrees":"","url_limit":""}
self.times = {}
self.country_code = ""
self.requested_data_type: str = "polygons (default)" # points, lines, polygons, projectcomments
self.preserve_attributes: str = "true (default)"
self.lat: float = 48.76755913928929 #51.52486388756923
self.lon: float = 11.408741923664028 #0.1621445437168942
self.north_degrees: float = 0
self.extent = [-180,-90,180,90]
self.crs_authid = ""
self.limit = 10000
self.user_agent = ""
self.missing_url = ""
self.limit_message = ""
self.extent = [-180,-90,180,90]
self.extent3d = [-180,-90,0,180,90,1000]
self.material_color_proxies = {}
@@ -173,7 +185,7 @@ class SpeckleProvider(BaseProvider):
if self.data == "":
return
get_set_url_parameters(self)
get_set_url_parameters(self) # possible ValueError
# check if it's a new request (self.data was updated and doesn't match self.url)
new_request = False
@@ -266,6 +278,18 @@ class SpeckleProvider(BaseProvider):
if data is None:
return {"features":[], "comments":[], "extent": [-180,-90,180,90]}
# add URL parameters
data['speckle_url'] = self.speckle_url
data['requested_data_type'] = self.requested_data_type
data['preserve_attributes'] = self.preserve_attributes
data['crs_authid'] = self.crs_authid
data['lat'] = self.lat
data['lon'] = self.lon
data['north_degrees'] = self.north_degrees
data['limit'] = self.limit
data['missing_url'] = self.missing_url
data["numberMatched"] = len(data["features"])
if resulttype == "hits":
@@ -324,7 +348,8 @@ class SpeckleProvider(BaseProvider):
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, get_comments
from datetime import datetime, timezone
from pygeoapi.provider.speckle_utils.server_utils import get_stream_branch, get_client, get_comments, set_actions
from specklepy.objects.base import Base
from specklepy.logging.exceptions import SpeckleException
@@ -346,6 +371,10 @@ class SpeckleProvider(BaseProvider):
# get stream and branch data
client = get_client(wrapper, url_proj)
stream, branch = get_stream_branch(self, client, wrapper)
if stream is None:
raise ValueError(f"Project from URL '{url_proj}' not found")
if branch is None:
raise ValueError(f"Model '{wrapper.model_id}' of the project '{stream['name']}' not found")
if self.requested_data_type == "projectcomments":
comments = get_comments(client, wrapper.stream_id, wrapper.model_id)
@@ -354,16 +383,20 @@ class SpeckleProvider(BaseProvider):
comments = {}
# set the Model name
self.project_id = wrapper.stream_id
self.project_name = stream['name']
self.model_name = branch['name']
commit = branch["commits"]["items"][0]
objId = commit["referencedObject"]
self.sourceApp = commit["sourceApplication"]
transport = ServerTransport(client=client, account=client.account, stream_id=wrapper.stream_id)
if transport == None:
raise SpeckleException("Transport not found")
# receive commit
set_actions(self, client)
try:
commit_obj = operations.receive(objId, transport, None)
except Exception as ex:
@@ -377,18 +410,21 @@ class SpeckleProvider(BaseProvider):
message="Received commit in pygeoapi",
)
print(f"Rendering model '{branch['name']}' of the project '{stream['name']}'")
print(f"_{datetime.now().astimezone(timezone.utc)} _Rendering model '{branch['name']}' of the project '{stream['name']}'")
speckle_data = self.traverse_data(commit_obj, comments)
set_actions(self, client, "GEO post-receive")
speckle_data["features"].extend(speckle_data["comments"])
speckle_data["comments"] = []
speckle_data["project_id"] = wrapper.stream_id
speckle_data["project"] = stream['name']
speckle_data["model"] = branch['name']
speckle_data["model_last_version_date"] = datetime.strptime(commit['createdAt'].replace("T", " ").replace("Z","").split(".")[0], '%Y-%m-%d %H:%M:%S')
speckle_data["model_id"] = wrapper.model_id
speckle_data["extent"] = self.extent
speckle_data["extent3d"] = self.extent3d
speckle_data["limit_message"] = self.limit_message
return speckle_data
@@ -406,7 +442,7 @@ class SpeckleProvider(BaseProvider):
)
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, get_material_color_proxies
from pygeoapi.provider.speckle_utils.display_utils import isDisplayable, set_default_color, get_material_color_proxies
supported_classes = [GisFeature, GisPolygonElement, Mesh, Brep, Point, Line, Polyline, Curve, Arc, Circle, Ellipse, Polycurve]
supported_types = [y().speckle_type for y in supported_classes]
@@ -428,16 +464,22 @@ class SpeckleProvider(BaseProvider):
"extent": [-180,-90,180,90],
"model_crs": "-",
}
# rule to keep traversing the object's "x" attribute "item" (both conditions need to be fulfilled)
# 1. if the item type is not in supported (convertible) types or is GIS VectorLayer
# 2. if the item's value is a list or a GH object
rule = TraversalRule(
[lambda _: True],
lambda x: [
item
for item in x.get_member_names()
if isinstance(getattr(x, item, None), list)
and (x.speckle_type.split(":")[-1] not in supported_types or isinstance(x, VectorLayer))
if (x.speckle_type.split(":")[-1] not in supported_types or isinstance(x, VectorLayer))
and (isinstance(getattr(x, item, None), list) or (self.sourceApp is not None and "grasshopper" in self.sourceApp.lower() and x.speckle_type == "Base") )
],
)
context_list = [x for x in GraphTraversal([rule]).traverse(commit_obj)]
# for the context list, save the displayable objects and Layers (for getting CRS for now)
context_list = [x for x in GraphTraversal([rule]).traverse(commit_obj) if isDisplayable(x.current) or x.current.speckle_type.endswith("VectorLayer")]
get_set_crs_settings(self, commit_obj, context_list, data)
@@ -459,7 +501,10 @@ class SpeckleProvider(BaseProvider):
sorted_list[i]["properties"]["FID"] = i+1
data['features'] = sorted_list
time2 = datetime.now()
print(f"Sorting time: {(time2-time1).total_seconds()}")
time_operation = (time2-time1).total_seconds()
self.times["time_sort"] = time_operation
# print(f"Sorting time: {time_operation}")
return data
@@ -296,8 +296,24 @@ def assign_geometry(self: "SpeckleProvider", feature: Dict, f_base) -> Tuple[ Li
geometry["type"] = "MultiPolygon"
coord_counts.append(None)
for geom in f_base["geometry"]:
convert_polygon(geom, coords, coord_counts)
polygon_3d = False
for mesh in f_base["displayValue"]:
for i, coord in enumerate(mesh.vertices):
if i>60:
break
if i%3 !=0:
continue
elif coord != 0:
polygon_3d = True
break
if polygon_3d is False:
for geom in f_base["geometry"]:
convert_polygon(geom, coords, coord_counts)
else:
for geom in f_base["displayValue"]:
convert_mesh_or_brep(geom, coords, coord_counts)
elif self.requested_data_type == "points":
@@ -2,6 +2,7 @@
import copy
import math
from typing import List
from pygeoapi.provider.speckle_utils.legal import COUNTRY_CODES, STATES, POSTCODES
def reproject_bulk(self, all_coords: List[List[List[float]]], all_coord_counts: List[List[None| List[int]]], geometries) -> None:
@@ -13,7 +14,12 @@ def reproject_bulk(self, all_coords: List[List[List[float]]], all_coord_counts:
time1 = datetime.now()
flat_coords = reproject_2d_coords_list(self, all_coords)
time2 = datetime.now()
print(f"Reproject time: {(time2-time1).total_seconds()}")
time_operation = (time2-time1).total_seconds()
self.times["time_reproject"] = time_operation
validate_coords(self, flat_coords[0])
if len(flat_coords)>2:
validate_coords(self, flat_coords[len(flat_coords)-1])
# define type of features
feat_coord_group_is_multi = [True if None in x else False for x in all_coord_counts]
@@ -49,7 +55,15 @@ def reproject_bulk(self, all_coords: List[List[List[float]]], all_coord_counts:
if geometry["type"] == "MultiPoint":
poly_part.extend([local_flat_coords[ind] for ind in range_coords_indices])
else:
poly_part.append([local_flat_coords[ind] for ind in range_coords_indices])
new_list = []
for ind in range_coords_indices:
try:
new_list.append(local_flat_coords[ind])
except Exception as e: # corrupted geometry, ignore altogether
new_list = []
break
if len(new_list)>0:
poly_part.append(new_list)
start_index += part_count
@@ -61,7 +75,10 @@ def reproject_bulk(self, all_coords: List[List[List[float]]], all_coord_counts:
geometry["coordinates"].extend(polygon_parts)
time3 = datetime.now()
print(f"Construct back geometry time: {(time3-time2).total_seconds()}")
time_operation = (time3-time2).total_seconds()
self.times["time_reconstruct_geometry"] = time_operation
# print(f"Construct back geometry time: {time_operation}")
def reproject_2d_coords_list(self, coords_in: List[List[float]]) -> List[List[float]]:
"""Return coordinates in a CRS of SpeckleProvider."""
@@ -80,7 +97,9 @@ def reproject_2d_coords_list(self, coords_in: List[List[float]]) -> List[List[fl
all_x = [x[0] for x in transformed]
all_y = [x[1] for x in transformed]
all_z = [x[2] for x in transformed]
self.extent = [min(all_x), min(all_y), max(all_x), max(all_y)]
self.extent3d = [min(all_x), min(all_y), min(all_z), max(all_x), max(all_y), max(all_z)]
return transformed
def offset_rotate(self, coords_in: List[list]) -> List[List[float]]:
@@ -106,3 +125,25 @@ def offset_rotate(self, coords_in: List[list]) -> List[List[float]]:
)
return final_coords
def validate_coords(self, coords):
from geopy.geocoders import Nominatim
country_code = ""
state = ""
postcode = ""
try:
geolocator = Nominatim(user_agent="specklePygeoapi")
coord = f"{coords[1]}, {coords[0]}"
location = geolocator.reverse(coord, exactly_one=True)
if location is not None:
address = location.raw['address']
country_code = address.get('country_code', '')
state = address.get('state', '')
postcode = address.get('postcode', '')
except Exception as e:
print(f"Error validating project location: {e}")
self.country_code = country_code
if country_code in COUNTRY_CODES or state in STATES or postcode in POSTCODES:
print(f"Validating project location: blocked LAT LON {coords[1]}, {coords[0]}, {country_code}, {state}, {postcode}")
raise PermissionError("Review Speckle Terms and Conditions")
+2 -1
View File
@@ -51,7 +51,7 @@ def get_set_crs_settings(self: "SpeckleProvider", commit_obj: "Base", context_li
root_objects = []
try:
root_objects = [commit_obj] + commit_obj.elements
root_objects = [commit_obj] + commit_obj.elements + [c.current for c in context_list]
except AttributeError as ex:
pass # old commit structure
@@ -73,6 +73,7 @@ def get_set_crs_settings(self: "SpeckleProvider", commit_obj: "Base", context_li
offset_y = crs["offset_y"]
self.north_degrees = crs["rotation"]
create_crs_from_wkt(self, crs["wkt"])
self.commit_gis = True
if self.crs.to_authority() is not None:
data["model_crs"] = f"{self.crs.to_authority()}, {self.crs.name} "
+163 -42
View File
@@ -54,6 +54,7 @@ def separate_display_vals(displayValue: List) -> List[Tuple["Base"]]:
count = 0
all_count = len(item.faces)
sub_meshes = []
for _ in item.faces:
if count < all_count:
faces = []
@@ -61,33 +62,67 @@ def separate_display_vals(displayValue: List) -> List[Tuple["Base"]]:
colors = []
vert_num = item.faces[count]
if vert_num == 0:
vert_num = 3
elif vert_num == 1:
vert_num = 4
faces.append(vert_num)
faces.extend([ x for x in list(range(vert_num))])
for ind in range(vert_num):
face_vert_index = count+1+ind
vert_index = item.faces[face_vert_index]
try:
for ind in range(vert_num):
face_vert_index = count+1+ind
#print(face_vert_index)
vert_index = item.faces[face_vert_index]
new_vert = item.vertices[3*vert_index : 3*vert_index + 3]
verts.extend(new_vert)
new_vert = item.vertices[3*vert_index : 3*vert_index + 3]
verts.extend(new_vert)
if isinstance(item.colors, List) and len(item.colors) > vert_index:
color = item.colors[vert_index]
colors.append(color)
if isinstance(item.colors, List) and len(item.colors) > vert_index:
color = item.colors[vert_index]
colors.append(color)
count += vert_num+1
if len(colors)>0:
mesh = Mesh.create(faces= faces, vertices=verts, colors=colors)
else:
mesh = Mesh.create(faces= faces, vertices=verts)
sub_meshes.append((mesh, item))
count += vert_num+1
if len(colors)>0:
mesh = Mesh.create(faces= faces, vertices=verts, colors=colors)
else:
mesh = Mesh.create(faces= faces, vertices=verts)
display_objs.append((mesh, item))
except IndexError: # corrupted mesh, drop altogether
sub_meshes = []
break
display_objs.extend(sub_meshes)
elif item is not None:
display_objs.append((item, item))
return display_objs
def isDisplayable(obj: "Base") -> bool:
if is_primitive(obj):
return True
if obj.speckle_type.endswith("Feature"):
return True
displayValue = None
if hasattr(obj, 'displayValue'):
displayValue = getattr(obj, 'displayValue')
elif hasattr(obj, '@displayValue'):
displayValue = getattr(obj, '@displayValue')
# merge to sigle object, if List
if isinstance(displayValue, List):
return True
return False
def find_display_obj(obj) -> Tuple["Base", "Base"]:
"""Get displayable object."""
@@ -121,11 +156,12 @@ def find_display_obj(obj) -> Tuple["Base", "Base"]:
def is_convertible(obj) -> bool:
"""Check if the object can be converted directly."""
from specklepy.objects.geometry import Base, Point, Line, Arc, Circle, Curve, Polycurve, Mesh, Brep
from specklepy.objects.geometry import Base, Point, Line, Polyline, Arc, Circle, Curve, Polycurve, Mesh, Brep
if ( (isinstance(obj, Base) and obj.speckle_type.endswith("Feature")) or
isinstance(obj, Point) or
isinstance(obj, Line) or
isinstance(obj, Polyline) or
isinstance(obj, Arc) or
isinstance(obj, Circle) or
isinstance(obj, Curve) or
@@ -135,6 +171,23 @@ def is_convertible(obj) -> bool:
return True
return False
def is_primitive(obj) -> bool:
"""Check if the object can be converted directly."""
from specklepy.objects.geometry import Polyline, Point, Line, Arc, Circle, Curve, Polycurve, Mesh, Brep
if (
isinstance(obj, Point) or
isinstance(obj, Line) or
isinstance(obj, Polyline) or
isinstance(obj, Arc) or
isinstance(obj, Circle) or
isinstance(obj, Curve) or
isinstance(obj, Mesh)
):
return True
return False
def get_single_display_object(displayValForColor: List) -> "Base":
"""Get a merged Mesh or a first item from displayValue list."""
@@ -165,12 +218,14 @@ def get_single_display_object(displayValForColor: List) -> "Base":
except IndexError:
break
elif item is not None:
displayValForColor = item
return item
mesh = Mesh.create(faces= faces, vertices=verts, colors=colors)
for prop in displayValForColor[0].get_member_names():
if prop not in ["colors", "vertices", "faces"]:
mesh[prop] = getattr(displayValForColor[0], prop)
if isinstance(displayValForColor, List) and len(displayValForColor)>0:
for prop in displayValForColor[0].get_member_names():
if prop not in ["colors", "vertices", "faces"]:
mesh[prop] = getattr(displayValForColor[0], prop)
displayValForColor = mesh
return displayValForColor
@@ -254,46 +309,94 @@ def set_default_color(context_list: List["TraversalContext"]) -> None:
for item in context_list:
# for GIS-commits, use default blue color
if isinstance(item.current, VectorLayer):
DEFAULT_COLOR = (255 << 24) + (10 << 16) + (132 << 8) + 255
if isinstance(item.current, VectorLayer) or (item.parent is not None and isinstance(item.parent.current, VectorLayer)):
DEFAULT_COLOR = (255 << 24) + (10 << 16) + (132 << 8) + 255 # speckle blue, speckle_blue
break
def assign_color(self: "SpeckleProvider", obj_display, props) -> None:
def getAllParents(tc: "TraversalContext"):
all_tc = [tc]
while True:
try:
parent = tc.parent
if parent:
all_tc.append(parent)
tc = parent
else:
break
except:
break
return all_tc
def assign_color(self: "SpeckleProvider", obj_display_tc: "TraversalContext", props: Dict) -> None:
"""Get and assign color to feature displayProperties."""
from specklepy.objects.geometry import Base, Mesh, Brep
try:
color = self.material_color_proxies[obj_display.applicationId]
props['color'] = color
return
except:
pass
from specklepy.objects.geometry import Mesh, Brep
# initialize Speckle Blue color
color = DEFAULT_COLOR
opacity = None
obj_display = obj_display_tc.current
try:
# first, choose if get color from the parent obj or displayValue
if hasattr(obj_display, 'displayStyle') or hasattr(obj_display, '@displayStyle') or hasattr(obj_display, 'renderMaterial') or hasattr(obj_display, '@renderMaterial'):
obj_display = obj_display_tc.current
else:
# this option will be not very reliable:
# there could be different colors for diff displayValues in the list
if hasattr(obj_display, 'displayValue'):
try:
displayVal = obj_display['displayValue']
except:
displayVal = obj_display.displayValue
if isinstance(displayVal, list) and len(displayVal)>0:
obj_display = displayVal[0]
elif hasattr(obj_display, '@displayValue') and isinstance(obj_display['@displayValue'], list) and len(obj_display['@displayValue'])>0:
obj_display = obj_display['@displayValue'][0]
# prioritize renderMaterials for Meshes & Brep
if isinstance(obj_display, Mesh) or isinstance(obj_display, Brep):
# print(obj_display.get_member_names())
if hasattr(obj_display, 'renderMaterial'):
color = obj_display['renderMaterial']['diffuse']
opacity = obj_display['renderMaterial']['opacity']
try:
renderMaterial = obj_display['renderMaterial']
except:
renderMaterial = obj_display.renderMaterial
color = renderMaterial['diffuse']
opacity = renderMaterial['opacity']
elif hasattr(obj_display, '@renderMaterial'):
color = obj_display['@renderMaterial']['diffuse']
opacity = obj_display['@renderMaterial']['opacity']
elif isinstance(obj_display, Mesh) and isinstance(obj_display.colors, List) and len(obj_display.colors)>1:
sameColors = True
color1 = obj_display.colors[0]
colors_number = 0
all_colors = []
for c in obj_display.colors:
if c != color1:
sameColors = False
break
if sameColors is True:
color = color1
if c not in all_colors:
colors_number += 1
all_colors.append(c)
if colors_number>1:
all_a = 0
all_r = 0
all_g = 0
all_b = 0
for col in all_colors:
a, r, g, b = get_r_g_b(col)
all_a += a
all_r += r
all_g += g
all_b += b
color = (
(int(all_a/len(obj_display.colors)) << 24) + (int(all_r/len(obj_display.colors)) << 16)
+ (int(all_g/len(obj_display.colors)) << 8) + int(all_b/len(obj_display.colors))
)
else:
color = obj_display.colors[0]
elif hasattr(obj_display, 'displayStyle'):
color = obj_display['displayStyle']['color']
@@ -321,6 +424,24 @@ def assign_color(self: "SpeckleProvider", obj_display, props) -> None:
# hex_color = '#%02x%02x%02x' % (r, g, b)
props['color'] = f'rgba({r},{g},{b},{a})'
# if still not found, check proxies:
if color == DEFAULT_COLOR:
for tc in getAllParents(obj_display_tc):
try:
color = self.material_color_proxies[tc.current.applicationId]
props['color'] = color
return
except:
pass
try:
color = self.material_color_proxies[obj_display.applicationId]
props['color'] = color
return
except:
pass
def get_r_g_b(rgb: int) -> Tuple[int, int, int]:
"""Get R, G, B values from int."""
@@ -336,12 +457,12 @@ def get_r_g_b(rgb: int) -> Tuple[int, int, int]:
a = 255
return a, r, g, b
def assign_display_properties(self: "SpeckleProvider", feature: Dict, f_base: "Base", obj_display: "Base") -> None:
def assign_display_properties(self: "SpeckleProvider", feature: Dict, f_base: "Base", obj_display_tc: "TraversalContext") -> None:
"""Assign displayProperties to the feature."""
from specklepy.objects.geometry import Mesh, Brep
assign_color(self, obj_display, feature["displayProperties"])
assign_color(self, obj_display_tc, feature["displayProperties"])
feature["properties"]["color"] = feature["displayProperties"]["color"]
# other properties for rendering
@@ -10,7 +10,10 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
from pygeoapi.provider.speckle_utils.converter_utils import assign_geometry
from pygeoapi.provider.speckle_utils.display_utils import find_display_obj, assign_display_properties, find_list_of_display_obj
print(f"Creating features..")
from specklepy.objects.graph_traversal.traversal import TraversalContext
from specklepy.objects.other import Collection
# print(f"Creating features..")
time1 = datetime.now()
all_props = []
@@ -19,6 +22,9 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
if self.requested_data_type != "projectcomments":
for item in context_list:
if item.current.speckle_type.endswith("Collection") or item.current.speckle_type.endswith("Layer") or item.current.speckle_type.endswith("Proxy"):
continue
if feature_count >= self.limit:
self.limit_message = f" (feature count limited to {self.limit})"
break
@@ -28,9 +34,13 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
f_fid = feature_count + 1
# initialize feature
speckle_type = item.current.speckle_type
if ":" in speckle_type:
speckle_type = speckle_type.split(":")[-1]
feature: Dict = {
"type": "Feature",
# "bbox": [-180.0, -90.0, 180.0, 90.0],
#"bbox": [-180.0, -90.0, 180.0, 90.0], should not be in degrees
"geometry": {},
"displayProperties":{
"object_type": "geometry",
@@ -38,7 +48,7 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
"properties": {
"id": f_id,
"FID": f_fid,
"speckle_type": item.current.speckle_type.split(":")[-1],
"speckle_type": speckle_type,
},
}
@@ -67,13 +77,16 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
if prop not in all_props:
all_props.append(prop)
assign_display_properties(self, feature, f_base, obj_get_color)
obj_get_color_tc = TraversalContext(obj_get_color, "", item)
assign_display_properties(self, feature, f_base, obj_get_color_tc)
feature["max_height"] = max([c[2] for c in coords])
feature["bbox"] = get_feature_bbox(coords)
data["features"].append(feature)
feature_count += 1
else:
list_of_display_obj = find_list_of_display_obj(f_base)
list_of_display_obj = find_list_of_display_obj(f_base) # tuple
for k, vals in enumerate(list_of_display_obj):
obj_display, obj_get_color = vals
@@ -81,7 +94,7 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
f_fid = feature_count + 1
feature_new: Dict = {
"type": "Feature",
# "bbox": [-180.0, -90.0, 180.0, 90.0],
#"bbox": [-180.0, -90.0, 180.0, 90.0], should not be in degrees
"geometry": {},
"displayProperties":{
"object_type": "geometry",
@@ -107,11 +120,14 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
all_coords.extend(coords)
all_coord_counts.append(coord_counts)
assign_display_properties(self, feature_new, f_base, obj_get_color)
obj_get_color_tc = TraversalContext(obj_display, "", item)
assign_display_properties(self, feature_new, f_base, obj_get_color_tc)
feature_new["max_height"] = max([c[2] for c in coords])
feature_new["bbox"] = get_feature_bbox(coords)
data["features"].append(feature_new)
feature_count +=1
assign_missing_props(data["features"], all_props)
else:
####################### create comment features
@@ -153,10 +169,23 @@ def initialize_features(self: "SpeckleProvider", all_coords, all_coord_counts, d
########################
if len(data["features"])==0 and len(data["comments"])==0:
raise ValueError("No supported features found")
raise ValueError(f"No supported features of type '{self.requested_data_type}' found. Make sure correct type is requested by adding a URL parameter (e.g. '&dataType=points').")
time2 = datetime.now()
print(f"Creating features time: {(time2-time1).total_seconds()}")
time_operation = (time2-time1).total_seconds()
self.times["time_creating_features"] = time_operation
# print(f"Creating features time: {time_operation}")
def get_feature_bbox(coords) -> List[float]:
"""Get min max coordinates of the feature."""
x0 = min([c[0] for c in coords])
x1 = max([c[0] for c in coords])
y0 = min([c[1] for c in coords])
y1 = max([c[1] for c in coords])
return [x0, y0, x1, y1]
def assign_comment_data(comments, properties):
"""Create html text to display for the thread."""
+3
View File
@@ -0,0 +1,3 @@
COUNTRY_CODES = ["ru"]
STATES = ['Автономна Республіка Крим', 'Севастополь', 'Донецька область', 'Луганська область']
POSTCODES = [str(i) for i in range(95000,99999)]
@@ -31,10 +31,7 @@ def safe_json_loads(obj: str, obj_id=None) -> Any:
f" int error - falling back to json. \nError: {err}",
SpeckleWarning,
)
try:
return ujson.loads(obj[:-2])
except:
return json.loads(obj)
return json.loads(obj)
class BaseObjectSerializer:
@@ -327,7 +324,6 @@ class BaseObjectSerializer:
# make sure an obj was passed and create dict if string was somehow passed
if not obj:
return
if isinstance(obj, str):
obj = safe_json_loads(obj)
+13 -10
View File
@@ -85,7 +85,7 @@ class ServerTransport(AbstractTransport):
self.url = url
self.session = requests.Session()
if self.account.token is not None:
self._batch_sender = BatchSender(
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
@@ -157,25 +157,28 @@ class ServerTransport(AbstractTransport):
id for id in children_found_map if not children_found_map[id]
]
# save headers and assign them back later
headers = self.session.headers
self.session.headers.update(
{
"Accept": "text/plain",
}
)
# Get the new children
endpoint = f"{self.url}/api/getobjects/{self.stream_id}"
r = self.session.post(
endpoint, data={"objects": json.dumps(new_children_ids)}, stream=True
)
r.encoding = "utf-8"
lines = r.iter_lines(decode_unicode=True, delimiter="},{")
lines = r.iter_lines(decode_unicode=True)
self.session.headers = headers # return previous headers
# iter through returned objects saving them as we go
target_transport.begin_write()
all_lines = [line for _,line in enumerate(lines)]
for i, line in enumerate(all_lines):
for line in lines:
if line:
hash = line.split('"id": "')[1].split('"')[0]
obj = "{" + line + "}"
if i==0:
obj = obj[2:]
elif i==len(all_lines)-1:
obj = obj[:-2]
hash, obj = line.split("\t")
target_transport.save_object(hash, obj)
target_transport.save_object(id, root_obj_serialized)
@@ -169,7 +169,7 @@ def get_info_from_comment(comment: Dict, project_id: str, model_id: str) -> Tupl
res_id = model_id
viewer_state = comment["viewerState"]
if viewer_state is not None: # can be None for Replies
position: List[float] = viewer_state["ui"]["camera"]["target"]
position: List[float] = viewer_state["ui"]["selection"]
try:
res_id = viewer_state["resources"]["request"]["resourceIdString"]
except:
@@ -216,4 +216,19 @@ def get_attachment(project_id: str, attachment_id: str, attachment_name: str) ->
raise Exception(
f"Request not successful: Response code {r.status_code}"
)
def set_actions(self: "SpeckleProvider", client: "SpeckleClient", action: str = "GEO receive"):
from specklepy.logging.metrics import track
try:
full_dict = {**self.url_params, **self.times}
full_dict["GIS commit"] = self.commit_gis
full_dict["project_id"] = f"{self.project_id}"
full_dict["sourceHostApp"] = self.sourceApp
full_dict["model"] = f"{self.project_name}, {self.model_name}"
full_dict["time_TOTAL"] = sum([x[1] for x in self.times.items()])
full_dict["model_url"] = self.speckle_url
full_dict["model_country_code"] = self.country_code
track(action, client.account, full_dict)
except Exception as ex:
print(f"_Cannot set action '{action}': {ex}")
pass
+76 -37
View File
@@ -1,65 +1,104 @@
from typing import Dict
import inspect
def get_set_url_parameters(self: "SpeckleProvider"):
"""Parse and save URL parameters."""
from pygeoapi.provider.speckle_utils.crs_utils import create_crs_from_authid
if (
isinstance(self.data, str)
and "speckleurl=" in self.data.lower()
and "projects" in self.data
and "models" in self.data
):
crs_authid = ""
crsauthid = False
if (isinstance(self.data, str)):
for item in self.data.lower().split("&"):
# if CRS authid is found, rest will be ignored
if "datatype=" in item:
if "speckleurl=" in item:
try:
self.requested_data_type = item.split("datatype=")[1]
if self.requested_data_type not in ["points", "lines", "polygons", "projectcomments"]:
self.requested_data_type = "polygons (default)"
speckle_url = item.split("speckleurl=")[1]
if "/projects/" not in speckle_url or "/models/" not in speckle_url:
raise ValueError(f"Provide valid Speckle Model URL: {item}")
if speckle_url[-1] == "/":
speckle_url = speckle_url[:-1]
self.speckle_project_url = speckle_url.split("/models")[0]
except:
pass
raise ValueError(f"Provide valid Speckle Model URL: {item}")
elif "datatype=" in item:
try:
requested_data_type = item.split("datatype=")[1]
if requested_data_type in ["points", "lines", "polygons", "projectcomments"]:
self.requested_data_type = requested_data_type
self.url_params["url_data_type"] = requested_data_type
except:
raise ValueError(f"Provide valid dataType parameter (points/lines/polygons/projectcomments): {item}")
elif "preserveattributes=" in item:
try:
self.preserve_attributes = item.split("preserveattributes=")[1]
if self.preserve_attributes not in ["true", "false"]:
self.preserve_attributes = "false (default)"
preserve_attributes = item.split("preserveattributes=")[1]
if preserve_attributes in ["true", "false"]:
self.preserve_attributes = preserve_attributes
self.url_params["url_preserve_attributes"] = preserve_attributes
except:
pass
ValueError(f"Provide valid preserverAttributes parameter (true/false): {item}")
elif "limit=" in item:
try:
self.limit = int(item.split("limit=")[1])
except:
pass
elif "crsauthid=" in item:
crs_authid = item.split("crsauthid=")[1]
if isinstance(crs_authid, str) and len(crs_authid)>3:
crsauthid = True
self.crs_authid = crs_authid
self.url_params["url_crs_authid"] = crs_authid
elif "lat=" in item:
try:
self.lat = float(item.split("lat=")[1])
lat = float(item.split("lat=")[1])
self.lat = lat
self.url_params["url_lat"] = lat
except:
pass
# raise ValueError(f"Invalid Lat input, must be numeric: {item.split('lat=')[1]}")
raise ValueError(f"Invalid Lat input, must be numeric: {item}")
elif "lon=" in item:
try:
self.lon = float(item.split("lon=")[1])
lon = float(item.split("lon=")[1])
self.lon = lon
self.url_params["url_lon"] = lon
except:
pass
# raise ValueError(f"Invalid Lon input, must be numeric: {item.split('lon=')[1]}")
raise ValueError(f"Invalid Lon input, must be numeric: {item}")
elif "northdegrees=" in item:
try:
self.north_degrees = float(item.split("northdegrees=")[1])
north_degrees = float(item.split("northdegrees=")[1])
self.north_degrees = north_degrees
self.url_params["url_north_degrees"] = north_degrees
except:
pass
# raise ValueError(f"Invalid NorthDegrees input, must be numeric: {item.split('northdegrees=')[1]}")
raise ValueError(f"Invalid northDegrees input, must be numeric: {item}")
elif "limit=" in item:
try:
limit = int(item.split("limit=")[1])
if limit>0:
self.limit = limit
self.url_params["url_limit"] = limit
except:
ValueError(f"Invalid limit input, must be a positive integer: {item}")
elif "useragent=" in item:
try:
agent = item.split("useragent=")[1]
self.user_agent = agent
self.url_params["user_agent"] = agent
except:
ValueError(f"Invalid limit input, must be a positive integer: {item}")
if self.speckle_url == "-":
self.missing_url = "true"
# if CRS authid is found, rest will be ignored
if crsauthid:
self.lat = str(self.lat) + " (not applied)"
self.lon = str(self.lon) + " (not applied)"
self.north_degrees = 0 # default to 0: rotation ignored when AuthId is used #str(self.north_degrees) + " (not applied)"
# if CRS parameter present, create and assign CRS:
if len(crs_authid)>3:
create_crs_from_authid(self)
if len(self.crs_authid)>3:
create_crs_from_authid(self, self.crs_authid)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+11 -5
View File
@@ -71,7 +71,7 @@
<meta name="language" content="{{ config['server']['language'] }}">
<meta name="description" content="{{ config['metadata']['identification']['title'] }}">
<meta name="keywords" content="{{ config['metadata']['identification']['keywords']|join(',') }}">
<link rel="shortcut icon" href="{{ config['server']['url'] }}/static/img/speckle_geo.png" type="image/x-icon">
<link rel="shortcut icon" href="https://github.com/specklesystems/pygeoapi/blob/dev/pygeoapi/static/img/speckle_geo.png" type="image/x-icon">
<link rel="stylesheet" href="https://unpkg.com/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ config['server']['url'] }}/static/css/default.css">
<!--[if lt IE 9]>
@@ -109,11 +109,9 @@
title="{{ config['metadata']['identification']['title'] }}" style="height:30px;vertical-align: middle;" />
<b style="text-align:left;padding-left: 10px;">Speckle</b>
</a>
<p style="text-align:left;padding-left: 10px;" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<a href="https://geo.speckle.systems/" target="_blank" style="text-align:left;padding-left: 10px;" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<a href="https://geo.speckle.systems/" target="_blank" style="text-align:left;padding-left: 10px;" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
> Geolocating your data
</a>
</p>
</a>
{% if (data["model"] and data["model"]!="") %}
<a href="{{data['speckle_project_url']}}" target="_blank" style="text-align:left;padding-left: 10px;" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
> {{data["project"]}} >
@@ -133,6 +131,14 @@
<ul class="nav nav-pills"> </ul>
</header>
</div>
<div style="max-height:fit-content;margin:0px;padding:0px;background-color: rgb(10,132,255);">
<p style="text-align: center; margin:0px;padding:5px;">
<a href = "https://docs.google.com/forms/d/e/1FAIpQLScKW2pkcWll3deXEwoV_G5ozLtuU06_prw8rf8HFuCk4tmOPQ/viewform?usp=sf_link"
style="color:rgb(255, 255, 255)" target="_blank">We would love to hear your feedback!</a>
</p>
</div>
</div>
<div class="crumbs">
+169 -83
View File
@@ -15,7 +15,7 @@
<div class="row">
{% if not data['missing_url'] %}
{% if data['speckle_url'] %}
<div id="map2d" style="height: 80vh;"></div>
@@ -53,7 +53,7 @@
</p>
</tr>
{% if data['missing_url'] %}
{% if not data['speckle_url'] %}
<tr>
<p>
<div style="height: fit-content;">
@@ -202,7 +202,7 @@
<tr>
{% set title_field = data.title_field %}
<td data-label="{{ title_field }}">
<a title="{{ ft.properties.get(title_field) }}" href="{{ data['speckle_project_url'] }}/models/{{ft.id.split('_')[0]}}" target="_blank">
<a title="{{ ft.properties.get(title_field) }}" href="{{data['speckle_url'].split('/models')[0]}}/models/{{ft.id.split('_')[0]}}" target="_blank">
{{ ft.properties.get(title_field) | string | truncate( 35 ) }}
</a>
</td>
@@ -335,7 +335,11 @@
{% block extrafoot %}
<script>
document.getElementById("loading_screen").remove();
try {
document.getElementById("loading_screen").remove();
document.getElementById("loading_screen_band").remove();
}
catch(err) {}
// attach even to modeSwitch btn
document.getElementById("modeSwitch").onclick = switchMode;
@@ -357,67 +361,123 @@
}
}
function split_polygons(geojson_data){
features = []
geojson_data.forEach((element, index) => {
if (element.geometry.type == "MultiPolygon"){
// mesh faces are stored as parts, so most buildings might have hundreds of parts with just 1 loop or 3-4pts
all_parts = element.geometry.coordinates;
// loops are usually simple (3-4 vertices), and usually only loop
for (polyPart of all_parts){
new_coordinates = [polyPart];
new_element = {"id": element.properties.id, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates": new_coordinates},
"properties": element.properties,
"displayProperties": element.displayProperties };
features.push(new_element)
}
}
else {
features.push(element)
}
});
return features
}
var data = {{ data | to_json | safe }};
var geojson_data = {{ data['features'] | to_json | safe }};
var geojson_data_original = {{ data['features'] | to_json | safe }};
// Leaflet 2d map
function initialize2d() {
var map = L.map('map2d').setView([45, 0], 2);
map.addLayer(new L.TileLayer(
'{{ config['server']['map']['url'] }}', {
maxZoom: 22,
attribution: '{{ config['server']['map']['attribution'] | safe }} &copy; Data: <a href="https://speckle.systems/">Speckle Systems</a>'
}
));
var map = L.map('map2d', {zoomControl: false}).setView([45, 0], 2);
L.control.zoom({
position: 'topright'
}).addTo(map);
var tileLayer = new L.TileLayer(
'{{ config['server']['map']['url'] }}', {
maxZoom: 22,
minZoom: 12,
attribution: '{{ config['server']['map']['attribution'] | safe }} &copy; Data: <a href="https://speckle.systems/">Speckle Systems</a>'
}
);
var items = new L.GeoJSON(geojson_data, {
filter: (feature) => {
return feature.displayProperties["object_type"] == "geometry"
},
pointToLayer: (feature, latlng) => {
return new L.circleMarker(latlng)
},
onEachFeature: function (feature, layer) {
var url = '{{ data['speckle_project_url'] }}/models/' + feature.id.split("_")[0]
var html = '<span><td><p>' + feature['properties']['speckle_type'] + '</p></td><a href="' + url + '" target="_blank">' + feature['properties']['id'].split("_")[0] + '</a></span>';
layer.bindPopup(html);
geojson_data = split_polygons(geojson_data_original);
project_url = ""
try {
project_url = data['speckle_url'].split("/models")[0]
}
catch(err) {}
var myFillColor = feature.displayProperties['color'];
var mylineWeight = feature.displayProperties['lineWidth'];
var myRadius = feature.displayProperties['radius'];
var items = new L.GeoJSON(geojson_data, {
filter: (feature) => {
return feature.displayProperties["object_type"] == "geometry"
},
pointToLayer: (feature, latlng) => {
return new L.circleMarker(latlng)
},
onEachFeature: function (feature, layer) {
var url = project_url + '/models/' + feature.id.split("_")[0]
var html = '<span><td><p>' + feature['properties']['speckle_type'] + '</p></td><a href="' + url + '" target="_blank">' + feature['properties']['id'].split("_")[0] + '</a></span>';
layer.bindPopup(html);
layer.setStyle({
fillColor: myFillColor,
color: myFillColor,
fillOpacity: 0.8,
weight: mylineWeight,
radius: myRadius
});
}
}); //.addTo(map);
var comments = new L.GeoJSON(geojson_data, {
filter: (feature) => {
return feature.displayProperties["object_type"] == "comment"
},
pointToLayer: (feature, latlng) => {
return new L.marker(latlng)
},
onEachFeature: function (feature, layer) {
var url = '{{ data['speckle_project_url'] }}/models/' + feature.properties.resource_id + '#threadId=' + feature.id;
var html = '<span><td><a href="' + url + '" target="_blank">Go to thread</a></td> <td><p>' + feature['properties']['text_html'] + '</p></td> </span>';
layer.bindPopup(html);
}
}); //.addTo(map);
var myFillColor = feature.displayProperties['color'];
var mylineWeight = feature.displayProperties['lineWidth'];
var myRadius = feature.displayProperties['radius'];
var group = L.featureGroup([items, comments])
.addTo(map);
layer.setStyle({
fillColor: myFillColor,
color: myFillColor,
fillOpacity: 0.8,
weight: mylineWeight,
radius: myRadius
});
}
}); //.addTo(map);
var comments = new L.GeoJSON(geojson_data, {
filter: (feature) => {
return feature.displayProperties["object_type"] == "comment"
},
pointToLayer: (feature, latlng) => {
return new L.marker(latlng)
},
onEachFeature: function (feature, layer) {
var url = project_url + '/models/' + feature.properties.resource_id + '#threadId=' + feature.id;
var html = '<span><td><a href="' + url + '" target="_blank">Go to thread</a></td> <td><p>' + feature['properties']['text_html'] + '</p></td> </span>';
layer.bindPopup(html);
}
}); //.addTo(map);
var group = L.featureGroup([items, comments]);
// load proper basemap for Speckle models; but only zoomed-out one for empty data
try
{
bounds = group.getBounds();
map.fitBounds(bounds);
//map.addLayer(lines);
tileLayer.addTo(map);
group.addTo(map);
}
catch (err){
tileLayer = new L.TileLayer(
'{{ config['server']['map']['url'] }}', {
maxZoom: 2,
minZoom: 2,
attribution: '{{ config['server']['map']['attribution'] | safe }} &copy; Data: <a href="https://speckle.systems/">Speckle Systems</a>'
}
);
tileLayer.addTo(map);
}
map.fitBounds(group.getBounds());
// map.setZoom(19); // in order for the tiles to load
//map.addLayer(lines);
// map.setZoom(19); // in order for the tiles to load
};
@@ -504,11 +564,20 @@
coords = feature.geometry.coordinates;
if (feature.geometry.type.includes("Polygon")) {
polygons = []
// check orientation of each PolygonPart, if vertical - shift points slightly
polygon_all_parts = []
// iterate through Polygon Parts
for (let p = 0; p < coords.length; p++) {
// check orientation of each PolygonPart, if vertical - shift points slightly
polygon_part = [];
inner = false;
for (let c = 0; c < coords[p].length; c++) {
polygon_part_loop = [];
if (c>0){
inner = true;
}
sum_orientation = 0;
polygon_pts = coords[p][c]; // usually 3 for Mesh faces
@@ -521,7 +590,9 @@
};
createdPolygon = false;
if (-0.01 < sum_orientation && sum_orientation <0.01){
if (-0.000000001 < sum_orientation && sum_orientation <0.000000001){
coords[p][c][0][0] += 0.0000001;
coords[p][c][0][1] += 0.0000001;
@@ -530,46 +601,60 @@
coords[p][c][2][0] += 0.0000001;
coords[p][c][2][1] += 0.0000001;
if(polygon_pts.length==3) {
createdPolygon = true;
multipolygon_coords = [coords[p][c]];
polygons.push({"id": speckle_features.length, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates":[multipolygon_coords]},
"properties": speckle_data.features[i].properties,
"displayProperties": speckle_data.features[i].displayProperties });
multipolygon_coords = coords[p][c];
polygon_part_loop = multipolygon_coords;
polygon_part = [polygon_part_loop];
polygon_all_parts.push(polygon_part);
}
else if (polygon_pts.length==4) {
createdPolygon = true;
multipolygon_coords = [coords[p][c].slice(0,3)];
polygons.push({"id": speckle_features.length, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates":[multipolygon_coords]},
"properties": speckle_data.features[i].properties,
"displayProperties": speckle_data.features[i].displayProperties });
multipolygon_coords = coords[p][c].slice(0,3);
polygon_part_loop = multipolygon_coords;
multipolygon_coords = [[coords[p][c][2], coords[p][c][3], coords[p][c][0]]];
polygons.push({"id": speckle_features.length, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates":[multipolygon_coords]},
"properties": speckle_data.features[i].properties ,
"displayProperties": speckle_data.features[i].displayProperties });
polygon_part = [polygon_part_loop];
polygon_all_parts.push(polygon_part);
/////////
multipolygon_coords = [coords[p][c][2], coords[p][c][3], coords[p][c][0]];
polygon_part_loop = multipolygon_coords;
polygon_part = [polygon_part_loop];
polygon_all_parts.push(polygon_part);
};
};
if (createdPolygon == false){
multipolygon_coords = [coords[p][c]];
polygons.push({"id": speckle_features.length, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates":[multipolygon_coords]},
"properties": speckle_data.features[i].properties,
"displayProperties": speckle_data.features[i].displayProperties });
if (createdPolygon == false){ // if non-vertical, or vertical with more than 4 vertices
multipolygon_coords = coords[p][c];
polygon_part_loop = multipolygon_coords;
if (inner == false){
polygon_part = [polygon_part_loop];
polygon_all_parts.push(polygon_part);
}
else{
polygon_all_parts[polygon_all_parts.length-1].push(polygon_part_loop);
}
}
};
};
polygons.forEach((element, index, array) => {
element.displayProperties.lineWidth = 0.05
speckle_features.push(element);
});
new_polygon = {"id": speckle_features.length, "type":"Feature",
"geometry": {"type": "MultiPolygon", "coordinates":polygon_all_parts},
"properties": speckle_data.features[i].properties,
"displayProperties": speckle_data.features[i].displayProperties };
new_polygon.displayProperties.lineWidth = 0.05
speckle_features.push(new_polygon);
}
else if (speckle_data.features[i].displayProperties.object_type == "comment")
@@ -620,6 +705,7 @@
longitude: extent[0] + (extent[2]-extent[0])/2,
latitude: extent[1] + (extent[3]-extent[1])/2,
zoom: 22,
minZoom: 12,
pitch: 60,
bearing: 1.469387755102039
},
+4 -1
View File
@@ -9,7 +9,10 @@
</body>
<script>
document.getElementById("loading_screen").remove();
try {
document.getElementById("loading_screen").remove();
}
catch(err) {}
</script>
</html>
+6
View File
@@ -5,4 +5,10 @@
<h2>{% trans %}Exception{% endtrans %}</h2>
<p>{{ data['description'] }}</p>
</section>
<script>
try {
document.getElementById("loading_screen").remove();
}
catch(err) {}
</script>
{% endblock %}
+13 -3
View File
@@ -1,11 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/bootstrap@5.1.3/dist/css/bootstrap.min.css">
</head>
<body>
<div id="loading_screen_band" style="position:absolute;z-index:-1;width:100%;max-height:fit-content;margin:0px;padding:0px;background-color: rgb(10,132,255);">
<p style="text-align: center; margin:0px;padding:15px;">
<a href = "https://docs.google.com/forms/d/e/1FAIpQLScKW2pkcWll3deXEwoV_G5ozLtuU06_prw8rf8HFuCk4tmOPQ/viewform?usp=sf_link"
style="color:rgb(255, 255, 255)" target="_blank">Why not share a feedback while waiting?</a>
</p>
</div>
<div id="loading_screen" style="position: absolute;top:100px;left:0;right:0;width: fit-content;margin-inline: auto;height:200px;">
<img style="height:200px;width:218.6px" src="https://raw.githubusercontent.com/specklesystems/pygeoapi/dev/pygeoapi/static/img/speckle_cube_loading.gif" alt="Loading data.." >
<h3 style="margin-bottom: 0px;font-family: 'Verdana'; text-align: center; color:rgb(40, 127, 209);font-size: x-large;">"I'm on my way!"</h3>
<p style="font-family: 'Verdana'; text-align: right; color:rgb(40, 127, 209)">- your data </p>
<img style="height:200px;width:218.6px" src="https://raw.githubusercontent.com/specklesystems/pygeoapi/dev/pygeoapi/static/img/speckle_cube_loading_winter.gif" alt="Loading data.." >
<h3 style="margin-bottom: 0px;text-align: center; color:rgb(40, 127, 209);font-size: x-large;">"I'm on my way!"</h3>
<p style="text-align: right; color:rgb(40, 127, 209)">- your data </p>
</div>
</body>
+1
View File
@@ -2,6 +2,7 @@ Babel
click
filelock
Flask
geopy==2.4.1
jinja2
jsonschema
pydantic<2.0
+1 -1
View File
@@ -86,7 +86,7 @@
});
// construnt data URL
var link = "https://geo.speckle.systems/?speckleUrl=";
var link = "https://geo.speckle.systems/speckle/?speckleUrl=";
if (document.getElementById("speckle_model").value!=""){
link += document.getElementById("speckle_model").value.replace(" ", "")
}
+1 -1
View File
@@ -34,7 +34,7 @@
));
(async () => {
const speckle_data = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
const speckle_data = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
headers: {
'Accept': 'application/geo+json'
}
+1 -1
View File
@@ -34,7 +34,7 @@
));
(async () => {
const speckle_data = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons', {
const speckle_data = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons', {
headers: {
'Accept': 'application/geo+json'
}
+2 -2
View File
@@ -32,7 +32,7 @@
(async () => {
const speckle_data2 = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/37d93c5d32&preserveAttributes=true', {
const speckle_data2 = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/37d93c5d32&preserveAttributes=true', {
//const speckle_data = await fetch('http://localhost:5000/?speckleUrl=https://app.speckle.systems/projects/64753f52b7/models/338b386787&limit=1000000&lat=-0.031405&lon=109.335828&preserveAttributes=true', {
headers: {
'Accept': 'application/geo+json'
@@ -155,7 +155,7 @@
//map.fitBounds(speckle_layer.getBounds())
const speckle_data = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
const speckle_data = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
//const speckle_data = await fetch('http://localhost:5000/?speckleUrl=https://app.speckle.systems/projects/64753f52b7/models/338b386787&limit=1000000&lat=-0.031405&lon=109.335828&preserveAttributes=true', {
headers: {
'Accept': 'application/geo+json'
+4 -4
View File
@@ -30,12 +30,12 @@
});
(async () => {
//const speckle_data = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
//var speckle_url = 'http://localhost:5000/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/9c43d7569c&limit=1000000&datatype=polygons&preserveattributes=false';
//const speckle_data = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=projectcomments', {
//var speckle_url = 'http://localhost:5000/speckle/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/9c43d7569c&limit=1000000&datatype=polygons&preserveattributes=false';
// https://app.speckle.systems/projects/5feae56049/models/01c4183677
var speckle_url = 'http://localhost:5000/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/01c4183677&limit=1000000&datatype=polygons&preserveattributes=true';
var speckle_url = 'https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/01c4183677&limit=1000000&datatype=polygons&preserveattributes=true';
//var speckle_url = 'https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/9c43d7569c&northDegrees=-30&preserveAttributes=true';
//var speckle_url = 'https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/5feae56049/models/9c43d7569c&northDegrees=-30&preserveAttributes=true';
const speckle_data = await fetch(speckle_url, {
headers: {
'Accept': 'application/geo+json'
@@ -65,7 +65,7 @@
//////// add Speckle layer
(async () => {
const geojson = await fetch('https://geo.speckle.systems/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons', {
const geojson = await fetch('https://geo.speckle.systems/speckle/?speckleUrl=https://app.speckle.systems/projects/344f803f81/models/5582ab673e&datatype=polygons', {
headers: {
'Accept': 'application/geo+json'
}