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.
@@ -75,7 +75,7 @@ services:
75
75
  - PGOPTIONS
76
76
 
77
77
  mapserver:
78
- image: camptocamp/mapserver:8.0-gdal3.6
78
+ image: camptocamp/mapserver:8.2-gdal3.8
79
79
  user: www-data
80
80
  restart: unless-stopped
81
81
  entrypoint: []
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2012-2023, Camptocamp SA
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
- from pyramid.httpexceptions import HTTPNotFound
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
- points: list[dict[str, Any]] = []
120
+ service_layers = [layer for layer in layers if rasters[layer].get("type") == "external_url"]
77
121
 
78
- dist = 0
79
- prev_coord = None
80
- coords = self._create_points(geom.coordinates, int(self.request.params["nbPoints"]))
81
- for coord in coords:
82
- if prev_coord is not None:
83
- dist += self._dist(prev_coord, coord)
84
-
85
- values = {}
86
- for ref in list(rasters.keys()):
87
- value = self._get_raster_value(self.rasters[ref], ref, coord[0], coord[1])
88
- values[ref] = value
89
-
90
- # 10cm accuracy is enough for distances
91
- rounded_dist = Decimal(str(dist)).quantize(Decimal("0.1"))
92
- points.append({"dist": rounded_dist, "values": values, "x": coord[0], "y": coord[1]})
93
- prev_coord = coord
94
-
95
- return layers, points
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
- result[ref] = self._get_raster_value(rasters[ref], ref, lon, lat)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: c2cgeoportal-geoportal
3
- Version: 2.9rc23
3
+ Version: 2.9rc25
4
4
  Summary: c2cgeoportal geoportal
5
5
  Home-page: https://github.com/camptocamp/c2cgeoportal/
6
6
  Author: Camptocamp
@@ -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=VdRZO71CRtmyBzhUXXtwZ1HBpLPT_58BhQUFKKgxkeI,13680
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=lKMBzrt0wVtUyDghgtbiocn4yUajAOOGL9LyuDtM5Ns,5375
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=94rMGBQ7zHTvlKewBl-18LdoYRdLjW4880XlODDvhsE,7535
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.9rc23.dist-info/METADATA,sha256=QIjmnka0rABffGX40rjx-taT2yzOYS4ubeX8q0USir0,1886
189
- c2cgeoportal_geoportal-2.9rc23.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
190
- c2cgeoportal_geoportal-2.9rc23.dist-info/entry_points.txt,sha256=3dnX260FsLX_AubeNMdyeta_z1X4CxcD3steAlfPx2I,1414
191
- c2cgeoportal_geoportal-2.9rc23.dist-info/top_level.txt,sha256=PJIbY7Nx51dDrJ052f5mDA7c6Tehm5aD-Gb32L_CtJA,29
192
- c2cgeoportal_geoportal-2.9rc23.dist-info/RECORD,,
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,,