c2cgeoportal-geoportal 2.9rc23__py3-none-any.whl → 2.9rc25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +1 -1
- c2cgeoportal_geoportal/views/profile.py +87 -22
- c2cgeoportal_geoportal/views/raster.py +43 -3
- {c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/METADATA +1 -1
- {c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/RECORD +8 -8
- {c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/WHEEL +0 -0
- {c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/entry_points.txt +0 -0
- {c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2012-
|
1
|
+
# Copyright (c) 2012-2024, Camptocamp SA
|
2
2
|
# All rights reserved.
|
3
3
|
|
4
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -25,20 +25,26 @@
|
|
25
25
|
# of the authors and should not be interpreted as representing official policies,
|
26
26
|
# either expressed or implied, of the FreeBSD Project.
|
27
27
|
|
28
|
-
|
28
|
+
import json
|
29
|
+
import logging
|
29
30
|
import math
|
31
|
+
import urllib.parse
|
30
32
|
from decimal import Decimal
|
33
|
+
from json.decoder import JSONDecodeError
|
31
34
|
from typing import Any
|
32
35
|
|
33
36
|
import geojson
|
34
37
|
import pyramid.request
|
35
|
-
|
38
|
+
import requests
|
39
|
+
from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
|
36
40
|
from pyramid.i18n import TranslationStringFactory
|
37
41
|
from pyramid.view import view_config
|
38
42
|
|
39
43
|
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
40
44
|
from c2cgeoportal_geoportal.views.raster import Raster
|
41
45
|
|
46
|
+
_LOG = logging.getLogger(__name__)
|
47
|
+
|
42
48
|
_ = TranslationStringFactory("c2cgeoportal")
|
43
49
|
|
44
50
|
|
@@ -48,6 +54,41 @@ class Profile(Raster):
|
|
48
54
|
def __init__(self, request: pyramid.request.Request):
|
49
55
|
Raster.__init__(self, request)
|
50
56
|
|
57
|
+
@staticmethod
|
58
|
+
def _to_filtered(points: list[dict[str, Any]], layers: list[str]) -> list[dict[str, Any]]:
|
59
|
+
profile = []
|
60
|
+
for point in points:
|
61
|
+
filtered_alts = {key: value for key, value in point["alts"].items() if key in layers}
|
62
|
+
profile.append(
|
63
|
+
{
|
64
|
+
"dist": point["dist"],
|
65
|
+
"values": filtered_alts,
|
66
|
+
"x": point["easting"],
|
67
|
+
"y": point["northing"],
|
68
|
+
}
|
69
|
+
)
|
70
|
+
return profile
|
71
|
+
|
72
|
+
def _get_profile_service_data(
|
73
|
+
self, layers: list[str], geom: dict[str, Any], rasters: dict[str, Any], nb_points: int
|
74
|
+
) -> list[dict[str, Any]]:
|
75
|
+
request = f"{rasters[layers[0]]['url']}/profile.json?{urllib.parse.urlencode({'geom': geom, 'nbPoints': nb_points, 'distinct_points': 'true'})}"
|
76
|
+
response = requests.get(request, timeout=10)
|
77
|
+
if not response.ok:
|
78
|
+
_LOG.error("profile request %s failed with status code %s", request, response.status_code)
|
79
|
+
raise HTTPInternalServerError(
|
80
|
+
f"Failed to fetch profile data from internal request: \
|
81
|
+
{response.status_code} {response.reason}"
|
82
|
+
)
|
83
|
+
|
84
|
+
try:
|
85
|
+
points = json.loads(response.content)
|
86
|
+
except (TypeError, JSONDecodeError) as exc:
|
87
|
+
_LOG.exception("profile request %s failed", request)
|
88
|
+
raise HTTPInternalServerError("Failed to decode JSON response from internal request") from exc
|
89
|
+
|
90
|
+
return self._to_filtered(points, layers)
|
91
|
+
|
51
92
|
@view_config(route_name="profile.json", renderer="fast_json") # type: ignore
|
52
93
|
def json(self) -> dict[str, Any]:
|
53
94
|
"""Answer to /profile.json."""
|
@@ -58,6 +99,9 @@ class Profile(Raster):
|
|
58
99
|
def _compute_points(self) -> tuple[list[str], list[dict[str, Any]]]:
|
59
100
|
"""Compute the alt=fct(dist) array."""
|
60
101
|
geom = geojson.loads(self.request.params["geom"], object_hook=geojson.GeoJSON.to_instance)
|
102
|
+
nb_points = int(self.request.params["nbPoints"])
|
103
|
+
coords = []
|
104
|
+
service_results: list[dict[str, Any]] = []
|
61
105
|
|
62
106
|
layers: list[str]
|
63
107
|
if "layers" in self.request.params:
|
@@ -73,26 +117,47 @@ class Profile(Raster):
|
|
73
117
|
layers = list(rasters.keys())
|
74
118
|
layers.sort()
|
75
119
|
|
76
|
-
|
120
|
+
service_layers = [layer for layer in layers if rasters[layer].get("type") == "external_url"]
|
77
121
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
122
|
+
if len(service_layers) > 0:
|
123
|
+
urls = [rasters[layer]["url"] for layer in service_layers]
|
124
|
+
if len(set(urls)) != 1:
|
125
|
+
raise HTTPBadRequest("All service layers must have the same URL.")
|
126
|
+
service_results = self._get_profile_service_data(service_layers, geom, rasters, nb_points)
|
127
|
+
if len(service_layers) < len(layers):
|
128
|
+
coords = [(point["x"], point["y"]) for point in service_results]
|
129
|
+
else:
|
130
|
+
return layers, service_results
|
131
|
+
|
132
|
+
if len(service_results) == 0:
|
133
|
+
points: list[dict[str, Any]] = []
|
134
|
+
|
135
|
+
dist = 0
|
136
|
+
prev_coord = None
|
137
|
+
coords = self._create_points(geom.coordinates, nb_points)
|
138
|
+
for coord in coords:
|
139
|
+
if prev_coord is not None:
|
140
|
+
dist += self._dist(prev_coord, coord)
|
141
|
+
_LOG.info("new dist %s", dist)
|
142
|
+
|
143
|
+
values = {}
|
144
|
+
for ref in list(rasters.keys()):
|
145
|
+
value = self._get_raster_value(self.rasters[ref], ref, coord[0], coord[1])
|
146
|
+
values[ref] = value
|
147
|
+
_LOG.info("values %s", values)
|
148
|
+
|
149
|
+
# 10cm accuracy is enough for distances
|
150
|
+
rounded_dist = Decimal(str(dist)).quantize(Decimal("0.1"))
|
151
|
+
points.append({"dist": rounded_dist, "values": values, "x": coord[0], "y": coord[1]})
|
152
|
+
prev_coord = coord
|
153
|
+
return layers, points
|
154
|
+
else:
|
155
|
+
additional_layers = [layer for layer in layers if layer not in service_layers]
|
156
|
+
for point in service_results:
|
157
|
+
for ref in additional_layers:
|
158
|
+
value = self._get_raster_value(self.rasters[ref], ref, point["x"], point["y"])
|
159
|
+
point["values"][ref] = value
|
160
|
+
return layers, service_results
|
96
161
|
|
97
162
|
@staticmethod
|
98
163
|
def _dist(coord1: tuple[float, float], coord2: tuple[float, float]) -> float:
|
@@ -27,16 +27,20 @@
|
|
27
27
|
|
28
28
|
|
29
29
|
import decimal
|
30
|
+
import json
|
30
31
|
import logging
|
31
32
|
import math
|
32
33
|
import os
|
33
34
|
import traceback
|
35
|
+
import urllib.parse
|
36
|
+
from json.decoder import JSONDecodeError
|
34
37
|
from typing import TYPE_CHECKING, Any
|
35
38
|
|
36
39
|
import numpy
|
37
40
|
import pyramid.request
|
41
|
+
import requests
|
38
42
|
import zope.event.classhandler
|
39
|
-
from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound
|
43
|
+
from pyramid.httpexceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNotFound
|
40
44
|
from pyramid.view import view_config
|
41
45
|
from rasterio.io import DatasetReader
|
42
46
|
|
@@ -95,10 +99,21 @@ class Raster:
|
|
95
99
|
raise HTTPNotFound(f"Layer {layer} not found")
|
96
100
|
else:
|
97
101
|
rasters = self.rasters
|
102
|
+
layers = list(rasters.keys())
|
103
|
+
layers.sort()
|
104
|
+
|
105
|
+
result: dict[str, Any] = {}
|
106
|
+
|
107
|
+
service_layers = [layer for layer in layers if rasters[layer].get("type") == "external_url"]
|
108
|
+
|
109
|
+
if len(service_layers) > 0:
|
110
|
+
for layer in service_layers:
|
111
|
+
service_result: dict[str, Any] = self._get_service_data(layer, lat, lon, rasters)
|
112
|
+
result.update(service_result)
|
98
113
|
|
99
|
-
result = {}
|
100
114
|
for ref in list(rasters.keys()):
|
101
|
-
|
115
|
+
if ref not in service_layers:
|
116
|
+
result[ref] = self._get_raster_value(rasters[ref], ref, lon, lat)
|
102
117
|
|
103
118
|
set_common_headers(self.request, "raster", Cache.PUBLIC_NO)
|
104
119
|
return result
|
@@ -181,6 +196,31 @@ class Raster:
|
|
181
196
|
|
182
197
|
return result
|
183
198
|
|
199
|
+
def _get_service_data(
|
200
|
+
self, layer: str, lat: float, lon: float, rasters: dict[str, Any]
|
201
|
+
) -> dict[str, Any]:
|
202
|
+
request = (
|
203
|
+
f"{rasters[layer]['url']}/height?{urllib.parse.urlencode({'easting': lon, 'northing': lat})}"
|
204
|
+
)
|
205
|
+
_LOG.info("Doing height request to %s", request)
|
206
|
+
response = requests.get(request, timeout=10)
|
207
|
+
if not response.ok:
|
208
|
+
_LOG.error("Elevation request %s failed with status code %s", request, response.status_code)
|
209
|
+
raise HTTPInternalServerError(
|
210
|
+
f"Failed to fetch elevation data from the internal request: \
|
211
|
+
{response.status_code} {response.reason}"
|
212
|
+
)
|
213
|
+
|
214
|
+
try:
|
215
|
+
result = json.loads(response.content).get(rasters[layer]["elevation_name"])
|
216
|
+
except (TypeError, JSONDecodeError) as exc:
|
217
|
+
_LOG.exception("Height request to %s failed", request)
|
218
|
+
raise HTTPInternalServerError("Failed to decode JSON response from the internal request") from exc
|
219
|
+
|
220
|
+
set_common_headers(self.request, "raster", Cache.PUBLIC_NO)
|
221
|
+
|
222
|
+
return {layer: result}
|
223
|
+
|
184
224
|
@staticmethod
|
185
225
|
def _round(value: numpy.float32, round_to: float) -> decimal.Decimal | None:
|
186
226
|
if value is not None:
|
{c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/RECORD
RENAMED
@@ -68,7 +68,7 @@ c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile,sha256
|
|
68
68
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst,sha256=QJcaUtT05atb5x8SJYflSt0YIJkbAz0Zh39_FYtyFks,423
|
69
69
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build,sha256=CjYe1cIL-TsKPbdxy1zStTUEGguILXpyUC63X2_Cy8c,7010
|
70
70
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml,sha256=sNzJxDQX54WfKgJhhVhDm4a05z3Rz1j6Oocl1QHzeyQ,518
|
71
|
-
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml,sha256=
|
71
|
+
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml,sha256=nNwk7HpCQduiQNBRqIInMZe5weLNDelc5ItiRBk8WJY,13680
|
72
72
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml,sha256=YE44s31kI79j5ypud4RS69sSDGFH1ROHNNAkyWxZ4jk,576
|
73
73
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml,sha256=X5vtxQ0pvLy6FSrooVmt90j4C4YNxtkSKzbPDOb4uB8,2615
|
74
74
|
c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml,sha256=ZpQ33JhdBxkoUNBV9kB5f07p8DpqgvJ3-Mp0SzLnJ6w,2380
|
@@ -164,9 +164,9 @@ c2cgeoportal_geoportal/views/memory.py,sha256=ttkX7DVcnlA1KsTtQEGZv66qGbjVFbeJGb
|
|
164
164
|
c2cgeoportal_geoportal/views/ogcproxy.py,sha256=2i3cuZ2cRybEH-DvAQbrLnUxd9y3B0iKnwkpaJvhwSc,5215
|
165
165
|
c2cgeoportal_geoportal/views/pdfreport.py,sha256=nJ_V4Z74YHtYFw1ZIAJer8iz5KMedHPPbgBUy2qkpCU,9616
|
166
166
|
c2cgeoportal_geoportal/views/printproxy.py,sha256=b-fNcRPNFYXouTmLNnDeja45APFQoA6i9fU3h5kcE6g,5928
|
167
|
-
c2cgeoportal_geoportal/views/profile.py,sha256=
|
167
|
+
c2cgeoportal_geoportal/views/profile.py,sha256=dELL7AGEBIW2h705-7wlZDZvQmUF9TkzRFQkPDc_NK8,8425
|
168
168
|
c2cgeoportal_geoportal/views/proxy.py,sha256=BCjFtnsrIQ2U30wk08YFLyt3Cq0yX3Rtwv5y7hCNpJ0,10390
|
169
|
-
c2cgeoportal_geoportal/views/raster.py,sha256=
|
169
|
+
c2cgeoportal_geoportal/views/raster.py,sha256=0PbYbdud1nHYH5zq_sbQYmsCjwlWVFVQjDo0xkfc2fs,9236
|
170
170
|
c2cgeoportal_geoportal/views/resourceproxy.py,sha256=S8PEqBpeJKsoLJlzA5lsqQoVDvh8pu5FEPh4OhJQU0Y,3183
|
171
171
|
c2cgeoportal_geoportal/views/shortener.py,sha256=54HNEDpueIdsKIpbWuk1MDpFC2gyo01MoAXc0uMVcl8,6167
|
172
172
|
c2cgeoportal_geoportal/views/theme.py,sha256=aX2v3p9R9be7u4YL_avHQ7ixQKO7FRMDinxiNzY4MSA,55884
|
@@ -185,8 +185,8 @@ tests/test_mapserverproxy_route_predicate.py,sha256=SzILSSzIuilzIkUYVPZiVzLwW1du
|
|
185
185
|
tests/test_raster.py,sha256=82NJ2MXgZlMqs0ytN-VgNw376iURdk4PkAg__dyh5ns,11948
|
186
186
|
tests/test_wmstparsing.py,sha256=xjA8nJuXFl3H5Bfs4sJw_8qX8E8qvAALK7Hs2-DTP2A,9054
|
187
187
|
tests/xmlstr.py,sha256=rkTKSU4FGjupBKLx75H8o-goB0KbQrxDvdpc6xVX_uQ,5985
|
188
|
-
c2cgeoportal_geoportal-2.
|
189
|
-
c2cgeoportal_geoportal-2.
|
190
|
-
c2cgeoportal_geoportal-2.
|
191
|
-
c2cgeoportal_geoportal-2.
|
192
|
-
c2cgeoportal_geoportal-2.
|
188
|
+
c2cgeoportal_geoportal-2.9rc25.dist-info/METADATA,sha256=cwecgRAPmB-M7kAR7suaW8YbzJoy2SOjnWyZyMKEa3k,1886
|
189
|
+
c2cgeoportal_geoportal-2.9rc25.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
190
|
+
c2cgeoportal_geoportal-2.9rc25.dist-info/entry_points.txt,sha256=3dnX260FsLX_AubeNMdyeta_z1X4CxcD3steAlfPx2I,1414
|
191
|
+
c2cgeoportal_geoportal-2.9rc25.dist-info/top_level.txt,sha256=PJIbY7Nx51dDrJ052f5mDA7c6Tehm5aD-Gb32L_CtJA,29
|
192
|
+
c2cgeoportal_geoportal-2.9rc25.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{c2cgeoportal_geoportal-2.9rc23.dist-info → c2cgeoportal_geoportal-2.9rc25.dist-info}/top_level.txt
RENAMED
File without changes
|