geovisio 2.6.0__py3-none-any.whl → 2.7.0__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.
- geovisio/__init__.py +36 -7
- geovisio/admin_cli/db.py +1 -4
- geovisio/config_app.py +40 -1
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +13 -13
- geovisio/templates/viewer.html +3 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +667 -0
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +730 -0
- geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
- geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
- geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/messages.pot +686 -0
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +594 -0
- geovisio/utils/__init__.py +1 -1
- geovisio/utils/auth.py +50 -11
- geovisio/utils/db.py +65 -0
- geovisio/utils/excluded_areas.py +83 -0
- geovisio/utils/extent.py +30 -0
- geovisio/utils/fields.py +1 -1
- geovisio/utils/filesystems.py +0 -1
- geovisio/utils/link.py +14 -0
- geovisio/utils/params.py +20 -0
- geovisio/utils/pictures.py +92 -68
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +264 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +654 -0
- geovisio/web/auth.py +37 -37
- geovisio/web/collections.py +286 -302
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +241 -14
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +156 -108
- geovisio/web/map.py +20 -20
- geovisio/web/params.py +69 -26
- geovisio/web/pictures.py +14 -31
- geovisio/web/reports.py +399 -0
- geovisio/web/rss.py +13 -7
- geovisio/web/stac.py +129 -134
- geovisio/web/tokens.py +98 -109
- geovisio/web/upload_set.py +768 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +28 -9
- geovisio/workers/runner_pictures.py +252 -204
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/METADATA +16 -13
- geovisio-2.7.0.dist-info/RECORD +66 -0
- geovisio-2.6.0.dist-info/RECORD +0 -41
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/WHEEL +0 -0
geovisio/web/map.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Some parts of code here are heavily inspired from Paul Ramsey's work
|
|
2
2
|
# See for reference : https://github.com/pramsey/minimal-mvt
|
|
3
3
|
|
|
4
|
-
import psycopg
|
|
5
4
|
import io
|
|
6
5
|
from typing import Optional, Dict, Any, Tuple, List, Union
|
|
7
6
|
from uuid import UUID
|
|
8
7
|
from flask import Blueprint, current_app, send_file, request, jsonify, url_for
|
|
9
|
-
from
|
|
8
|
+
from flask_babel import gettext as _
|
|
9
|
+
from geovisio.utils import auth, db
|
|
10
10
|
from geovisio.utils.auth import Account
|
|
11
11
|
from geovisio.web import params
|
|
12
12
|
from geovisio.web.utils import user_dependant_response
|
|
@@ -21,15 +21,15 @@ ZOOM_PICTURES = 15
|
|
|
21
21
|
|
|
22
22
|
def get_style_json(forUser: Optional[Union[UUID, str]] = None):
|
|
23
23
|
# Get correct vector tiles URL
|
|
24
|
-
tilesUrl = url_for("map.getTile", x="
|
|
24
|
+
tilesUrl = url_for("map.getTile", x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
25
25
|
sourceId = "geovisio"
|
|
26
26
|
if forUser == "me":
|
|
27
|
-
tilesUrl = url_for("map.getMyTile", x="
|
|
27
|
+
tilesUrl = url_for("map.getMyTile", x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
28
28
|
sourceId = "geovisio_me"
|
|
29
29
|
elif forUser is not None:
|
|
30
|
-
tilesUrl = url_for("map.getUserTile", userId=forUser, x="
|
|
30
|
+
tilesUrl = url_for("map.getUserTile", userId=forUser, x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
31
31
|
sourceId = f"geovisio_{str(forUser)}"
|
|
32
|
-
tilesUrl = tilesUrl.replace("
|
|
32
|
+
tilesUrl = tilesUrl.replace("11111111", "{x}").replace("22222222", "{y}").replace("33333333", "{z}")
|
|
33
33
|
|
|
34
34
|
# Display sequence on all zooms if user tiles, after grid on general tiles
|
|
35
35
|
sequenceOpacity = ["interpolate", ["linear"], ["zoom"], ZOOM_GRID_SEQUENCES, 0, ZOOM_GRID_SEQUENCES + 1, 1] if forUser is None else 1
|
|
@@ -73,7 +73,7 @@ def get_style_json(forUser: Optional[Union[UUID, str]] = None):
|
|
|
73
73
|
"source": sourceId,
|
|
74
74
|
"source-layer": "grid",
|
|
75
75
|
"paint": {
|
|
76
|
-
"fill-color": ["interpolate
|
|
76
|
+
"fill-color": ["interpolate", ["linear"], ["get", "coef"], 0, "#FFCC80", 0.5, "#E65100", 1, "#BF360C"],
|
|
77
77
|
"fill-opacity": [
|
|
78
78
|
"interpolate",
|
|
79
79
|
["linear"],
|
|
@@ -120,32 +120,31 @@ def checkTileValidity(z, x, y, format):
|
|
|
120
120
|
raises InvalidAPIUsage exceptions if parameters are not OK
|
|
121
121
|
"""
|
|
122
122
|
if z is None or x is None or y is None or format is None:
|
|
123
|
-
raise errors.InvalidAPIUsage("One of required parameter is empty", status_code=404)
|
|
123
|
+
raise errors.InvalidAPIUsage(_("One of required parameter is empty"), status_code=404)
|
|
124
124
|
if format not in ["pbf", "mvt"]:
|
|
125
|
-
raise errors.InvalidAPIUsage("Tile format is invalid, should be either pbf or mvt", status_code=400)
|
|
125
|
+
raise errors.InvalidAPIUsage(_("Tile format is invalid, should be either pbf or mvt"), status_code=400)
|
|
126
126
|
|
|
127
127
|
size = 2**z
|
|
128
128
|
if x >= size or y >= size:
|
|
129
|
-
raise errors.InvalidAPIUsage("X or Y parameter is out of bounds", status_code=404)
|
|
129
|
+
raise errors.InvalidAPIUsage(_("X or Y parameter is out of bounds"), status_code=404)
|
|
130
130
|
if x < 0 or y < 0:
|
|
131
|
-
raise errors.InvalidAPIUsage("X or Y parameter is out of bounds", status_code=404)
|
|
131
|
+
raise errors.InvalidAPIUsage(_("X or Y parameter is out of bounds"), status_code=404)
|
|
132
132
|
if z < 0 or z > 15:
|
|
133
|
-
raise errors.InvalidAPIUsage("Z parameter is out of bounds (should be 0-15)", status_code=404)
|
|
133
|
+
raise errors.InvalidAPIUsage(_("Z parameter is out of bounds (should be 0-15)"), status_code=404)
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
def _getTile(z: int, x: int, y: int, format: str, onlyForUser: Optional[UUID] = None, filter: Optional[sql.SQL] = None):
|
|
137
137
|
checkTileValidity(z, x, y, format)
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
with conn.cursor() as cursor:
|
|
141
|
-
query, params = _get_query(z, x, y, onlyForUser, additional_filter=filter)
|
|
142
|
-
res = cursor.execute(query, params).fetchone()
|
|
139
|
+
query, params = _get_query(z, x, y, onlyForUser, additional_filter=filter)
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
raise errors.InternalError("Impossible to get tile")
|
|
141
|
+
res = db.fetchone(current_app, query, params, timeout=10000)
|
|
146
142
|
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
if not res:
|
|
144
|
+
raise errors.InternalError(_("Impossible to get tile"))
|
|
145
|
+
|
|
146
|
+
res = res[0]
|
|
147
|
+
return send_file(io.BytesIO(res), mimetype="application/vnd.mapbox-vector-tile")
|
|
149
148
|
|
|
150
149
|
|
|
151
150
|
@bp.route("/map/style.json")
|
|
@@ -466,6 +465,7 @@ def getUserStyle(userId: UUID):
|
|
|
466
465
|
|
|
467
466
|
|
|
468
467
|
@bp.route("/users/<uuid:userId>/map/<int:z>/<int:x>/<int:y>.<format>")
|
|
468
|
+
@user_dependant_response(True)
|
|
469
469
|
def getUserTile(userId: UUID, z: int, x: int, y: int, format: str):
|
|
470
470
|
"""Get pictures and sequences as vector tiles for a specific user.
|
|
471
471
|
This tile will contain the same layers as the generic tiles (from `/map/z/x/y.format` route), but with sequences properties on all levels
|
geovisio/web/params.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
|
+
|
|
2
3
|
from geovisio import errors
|
|
3
4
|
import dateutil.parser
|
|
4
5
|
from dateutil import tz
|
|
@@ -9,9 +10,12 @@ from werkzeug.datastructures import MultiDict
|
|
|
9
10
|
from typing import Optional, Tuple, Any, List
|
|
10
11
|
from pygeofilter.backends.sql import to_sql_where
|
|
11
12
|
from pygeofilter.parsers.ecql import parse as ecql_parser
|
|
13
|
+
from pygeofilter import ast
|
|
14
|
+
from pygeofilter.backends.evaluator import Evaluator, handle
|
|
12
15
|
from psycopg import sql
|
|
13
16
|
from geovisio.utils.sequences import STAC_FIELD_MAPPINGS, STAC_FIELD_TO_SQL_FILTER
|
|
14
17
|
from geovisio.utils.fields import SortBy, SQLDirection, SortByField
|
|
18
|
+
from flask_babel import gettext as _
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
RGX_SORTBY = re.compile("[+-]?[A-Za-z_].*(,[+-]?[A-Za-z_].*)*")
|
|
@@ -47,7 +51,7 @@ def parse_datetime(value, error, fallback_as_UTC=False):
|
|
|
47
51
|
d = None
|
|
48
52
|
try:
|
|
49
53
|
d = datetime.datetime.fromisoformat(value)
|
|
50
|
-
except ValueError
|
|
54
|
+
except ValueError:
|
|
51
55
|
pass
|
|
52
56
|
if not d:
|
|
53
57
|
try:
|
|
@@ -81,17 +85,17 @@ def parse_datetime_interval(value: Optional[str]) -> Tuple[Optional[datetime.dat
|
|
|
81
85
|
dates = value.split("/")
|
|
82
86
|
|
|
83
87
|
if len(dates) == 1:
|
|
84
|
-
d = parse_datetime(dates[0], error=
|
|
88
|
+
d = parse_datetime(dates[0], error="Invalid `datetime` argument", fallback_as_UTC=True)
|
|
85
89
|
return (d, d)
|
|
86
90
|
|
|
87
91
|
elif len(dates) == 2:
|
|
88
92
|
# Check if interval is closed or open-ended
|
|
89
93
|
mind, maxd = dates
|
|
90
|
-
mind = None if mind == ".." else parse_datetime(mind, error=
|
|
91
|
-
maxd = None if maxd == ".." else parse_datetime(maxd, error=
|
|
94
|
+
mind = None if mind == ".." else parse_datetime(mind, error="Invalid start date in `datetime` argument", fallback_as_UTC=True)
|
|
95
|
+
maxd = None if maxd == ".." else parse_datetime(maxd, error="Invalid end date in `datetime` argument", fallback_as_UTC=True)
|
|
92
96
|
return (mind, maxd)
|
|
93
97
|
else:
|
|
94
|
-
raise errors.InvalidAPIUsage("Parameter datetime should contain one or two dates", status_code=400)
|
|
98
|
+
raise errors.InvalidAPIUsage(_("Parameter datetime should contain one or two dates"), status_code=400)
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
def parse_bbox(value: Optional[Any], tryFallbacks=True):
|
|
@@ -161,12 +165,12 @@ def parse_bbox(value: Optional[Any], tryFallbacks=True):
|
|
|
161
165
|
or bbox[3] > 90
|
|
162
166
|
):
|
|
163
167
|
raise errors.InvalidAPIUsage(
|
|
164
|
-
"Parameter bbox must contain valid longitude (-180 to 180) and latitude (-90 to 90) values", status_code=400
|
|
168
|
+
_("Parameter bbox must contain valid longitude (-180 to 180) and latitude (-90 to 90) values"), status_code=400
|
|
165
169
|
)
|
|
166
170
|
else:
|
|
167
171
|
return bbox
|
|
168
172
|
except ValueError:
|
|
169
|
-
raise errors.InvalidAPIUsage("Parameter bbox must be in format [minX, minY, maxX, maxY]", status_code=400)
|
|
173
|
+
raise errors.InvalidAPIUsage(_("Parameter bbox must be in format [minX, minY, maxX, maxY]"), status_code=400)
|
|
170
174
|
else:
|
|
171
175
|
return None
|
|
172
176
|
|
|
@@ -203,11 +207,11 @@ def parse_lonlat(values: Optional[Any], paramName: Optional[str] = None) -> Opti
|
|
|
203
207
|
entries = parse_list(values, paramName=paramName)
|
|
204
208
|
|
|
205
209
|
if entries is None or len(entries) != 2:
|
|
206
|
-
raise errors.InvalidAPIUsage(
|
|
210
|
+
raise errors.InvalidAPIUsage(_("Parameter %(p)s must be coordinates in lat,lon format", p=paramName or ""), status_code=400)
|
|
207
211
|
|
|
208
212
|
return [
|
|
209
|
-
as_longitude(entries[0],
|
|
210
|
-
as_latitude(entries[1],
|
|
213
|
+
as_longitude(entries[0], _("Longitude in parameter %(p)s is not valid (should be between -180 and 180)", p=paramName or "")),
|
|
214
|
+
as_latitude(entries[1], _("Latitude in parameter %(p)s is not valid (should be between -90 and 90)", p=paramName or "")),
|
|
211
215
|
]
|
|
212
216
|
|
|
213
217
|
|
|
@@ -230,18 +234,20 @@ def parse_distance_range(values: Optional[str], paramName: Optional[str] = None)
|
|
|
230
234
|
dists = values.split("-")
|
|
231
235
|
if len(dists) != 2:
|
|
232
236
|
raise errors.InvalidAPIUsage(
|
|
233
|
-
|
|
237
|
+
_('Parameter %(p)s is invalid (should be a distance range in meters like "5-15")', p={paramName or ""}), status_code=400
|
|
234
238
|
)
|
|
235
239
|
try:
|
|
236
240
|
dists = [int(d) for d in dists]
|
|
237
241
|
if dists[0] > dists[1]:
|
|
238
|
-
raise errors.InvalidAPIUsage(
|
|
242
|
+
raise errors.InvalidAPIUsage(
|
|
243
|
+
_("Parameter %(p)s has a min value greater than its max value", p=paramName or ""), status_code=400
|
|
244
|
+
)
|
|
239
245
|
else:
|
|
240
246
|
return dists
|
|
241
247
|
|
|
242
248
|
except ValueError:
|
|
243
249
|
raise errors.InvalidAPIUsage(
|
|
244
|
-
|
|
250
|
+
_('Parameter %(p)s is invalid (should be a distance range in meters like "5-15")', p={paramName or ""}), status_code=400
|
|
245
251
|
)
|
|
246
252
|
else:
|
|
247
253
|
return None
|
|
@@ -308,7 +314,7 @@ def parse_list(value: Optional[Any], tryFallbacks: bool = True, paramName: Optio
|
|
|
308
314
|
value = value.replace("[", "").replace("]", "")
|
|
309
315
|
res = [n.strip() for n in value.split(",")]
|
|
310
316
|
else:
|
|
311
|
-
raise errors.InvalidAPIUsage(
|
|
317
|
+
raise errors.InvalidAPIUsage(_("Parameter %(p)s must be a valid list", p=paramName or ""), status_code=400)
|
|
312
318
|
|
|
313
319
|
if len(res) == 0:
|
|
314
320
|
return None
|
|
@@ -328,10 +334,10 @@ def parse_filter(value: Optional[str]) -> Optional[sql.SQL]:
|
|
|
328
334
|
SQL("(s.updated_at >= '2023-12-31')")
|
|
329
335
|
>>> parse_filter("updated >= '2023-12-31' AND created < '2023-10-31'")
|
|
330
336
|
SQL("((s.updated_at >= '2023-12-31') AND (s.inserted_at < '2023-10-31'))")
|
|
331
|
-
>>> parse_filter("status IN ('deleted','ready')")
|
|
332
|
-
SQL("s.status IN ('deleted', 'ready')")
|
|
337
|
+
>>> parse_filter("status IN ('deleted','ready')") # when we ask for deleted, we should also have hidden collections
|
|
338
|
+
SQL("s.status IN ('deleted', 'ready', 'hidden')")
|
|
333
339
|
>>> parse_filter("status = 'deleted' OR status = 'ready'")
|
|
334
|
-
SQL("((s.status = 'deleted') OR (s.status = 'ready'))")
|
|
340
|
+
SQL("(((s.status = 'deleted') OR (s.status = 'hidden')) OR (s.status = 'ready'))")
|
|
335
341
|
>>> parse_filter('invalid = 10') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
336
342
|
Traceback (most recent call last):
|
|
337
343
|
geovisio.errors.InvalidAPIUsage: Unsupported filter parameter
|
|
@@ -342,14 +348,51 @@ def parse_filter(value: Optional[str]) -> Optional[sql.SQL]:
|
|
|
342
348
|
if value is not None and len(value) > 0:
|
|
343
349
|
try:
|
|
344
350
|
filterAst = ecql_parser(value)
|
|
345
|
-
|
|
346
|
-
|
|
351
|
+
altered_ast = _alterFilterAst(filterAst) # type: ignore
|
|
352
|
+
|
|
353
|
+
f = to_sql_where(altered_ast, STAC_FIELD_TO_SQL_FILTER).replace('"', "")
|
|
354
|
+
return sql.SQL(f) # type: ignore
|
|
347
355
|
except:
|
|
348
|
-
raise errors.InvalidAPIUsage(
|
|
356
|
+
raise errors.InvalidAPIUsage(_("Unsupported filter parameter"), status_code=400)
|
|
349
357
|
else:
|
|
350
358
|
return None
|
|
351
359
|
|
|
352
360
|
|
|
361
|
+
class _FilterAstUpdated(Evaluator):
|
|
362
|
+
"""
|
|
363
|
+
We alter the parsed AST in order to always query for 'hidden' pictures when we query for 'deleted' ones
|
|
364
|
+
|
|
365
|
+
The rational here is that for non-owned pictures/sequences, when a pictures/sequence is 'hidden' it should be advertised as 'deleted'.
|
|
366
|
+
|
|
367
|
+
This is especially important for crawler like the meta-catalog, since they should also delete the sequence/picture when it is hidden
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
@handle(ast.Equal)
|
|
371
|
+
def eq(self, node, lhs, rhs):
|
|
372
|
+
if lhs == ast.Attribute("status") and rhs == "deleted":
|
|
373
|
+
return ast.Or(node, ast.Equal(ast.Attribute("status"), "hidden")) # type: ignore
|
|
374
|
+
return node
|
|
375
|
+
|
|
376
|
+
@handle(ast.Or)
|
|
377
|
+
def or_(self, node, lhs, rhs):
|
|
378
|
+
return ast.Or(lhs, rhs)
|
|
379
|
+
|
|
380
|
+
@handle(ast.In)
|
|
381
|
+
def in_(self, node, lhs, *options):
|
|
382
|
+
if "deleted" in node.sub_nodes:
|
|
383
|
+
node.sub_nodes.append("hidden")
|
|
384
|
+
return node
|
|
385
|
+
|
|
386
|
+
def adopt(self, node, *sub_args):
|
|
387
|
+
return node
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _alterFilterAst(ast: ast.Node):
|
|
391
|
+
filtered = _FilterAstUpdated().evaluate(ast)
|
|
392
|
+
|
|
393
|
+
return filtered
|
|
394
|
+
|
|
395
|
+
|
|
353
396
|
def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
354
397
|
"""Reads STAC/OGC sortby parameter, and sends a SQL ORDER BY string.
|
|
355
398
|
|
|
@@ -395,7 +438,7 @@ def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
|
395
438
|
|
|
396
439
|
# Check if in value mapping
|
|
397
440
|
if vOnly not in STAC_FIELD_MAPPINGS:
|
|
398
|
-
raise errors.InvalidAPIUsage(
|
|
441
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: invalid column name"), status_code=400)
|
|
399
442
|
field_mapping = STAC_FIELD_MAPPINGS[vOnly]
|
|
400
443
|
|
|
401
444
|
orders.append(SortByField(field=field_mapping, direction=direction))
|
|
@@ -403,7 +446,7 @@ def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
|
403
446
|
# Create definitive ORDER string
|
|
404
447
|
return SortBy(fields=orders)
|
|
405
448
|
else:
|
|
406
|
-
raise errors.InvalidAPIUsage(
|
|
449
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: syntax isn't correct"), status_code=400)
|
|
407
450
|
else:
|
|
408
451
|
return None
|
|
409
452
|
|
|
@@ -429,10 +472,10 @@ def parse_collections_limit(limit: Optional[str]) -> int:
|
|
|
429
472
|
try:
|
|
430
473
|
int_limit = int(limit)
|
|
431
474
|
except ValueError:
|
|
432
|
-
raise errors.InvalidAPIUsage(
|
|
475
|
+
raise errors.InvalidAPIUsage(_("limit parameter should be a valid, positive integer (between 1 and %(v)s)", v=SEQUENCES_MAX_FETCH))
|
|
433
476
|
|
|
434
477
|
if int_limit < 1 or int_limit > SEQUENCES_MAX_FETCH:
|
|
435
|
-
raise errors.InvalidAPIUsage(
|
|
478
|
+
raise errors.InvalidAPIUsage(_("limit parameter should be an integer between 1 and %(v)s", v=SEQUENCES_MAX_FETCH))
|
|
436
479
|
else:
|
|
437
480
|
return int_limit
|
|
438
481
|
|
|
@@ -443,7 +486,7 @@ def as_longitude(value: str, error):
|
|
|
443
486
|
except ValueError as e:
|
|
444
487
|
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": str(e)}})
|
|
445
488
|
if l < -180 or l > 180:
|
|
446
|
-
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": "longitude needs to be between -180 and 180"}})
|
|
489
|
+
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": _("longitude needs to be between -180 and 180")}})
|
|
447
490
|
return l
|
|
448
491
|
|
|
449
492
|
|
|
@@ -453,7 +496,7 @@ def as_latitude(value: str, error):
|
|
|
453
496
|
except ValueError as e:
|
|
454
497
|
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": str(e)}})
|
|
455
498
|
if l < -90 or l > 90:
|
|
456
|
-
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": "latitude needs to be between -90 and 90"}})
|
|
499
|
+
raise errors.InvalidAPIUsage(message=error, payload={"details": {"error": _("latitude needs to be between -90 and 90")}})
|
|
457
500
|
return l
|
|
458
501
|
|
|
459
502
|
|
geovisio/web/pictures.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
from itertools import repeat
|
|
3
|
-
from flask import Blueprint, current_app, request
|
|
1
|
+
from flask import Blueprint, current_app
|
|
4
2
|
from geovisio import utils, errors
|
|
5
3
|
from flask import redirect
|
|
4
|
+
from flask_babel import gettext as _
|
|
6
5
|
import logging
|
|
7
6
|
|
|
8
7
|
bp = Blueprint("pictures", __name__, url_prefix="/api/pictures")
|
|
@@ -25,7 +24,7 @@ def getPictureHD(pictureId, format):
|
|
|
25
24
|
type: string
|
|
26
25
|
- name: format
|
|
27
26
|
in: path
|
|
28
|
-
description: Wanted format for output image (
|
|
27
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
29
28
|
required: true
|
|
30
29
|
schema:
|
|
31
30
|
type: string
|
|
@@ -37,10 +36,6 @@ def getPictureHD(pictureId, format):
|
|
|
37
36
|
schema:
|
|
38
37
|
type: string
|
|
39
38
|
format: binary
|
|
40
|
-
image/webp:
|
|
41
|
-
schema:
|
|
42
|
-
type: string
|
|
43
|
-
format: binary
|
|
44
39
|
"""
|
|
45
40
|
|
|
46
41
|
utils.pictures.checkFormatParam(format)
|
|
@@ -55,7 +50,7 @@ def getPictureHD(pictureId, format):
|
|
|
55
50
|
try:
|
|
56
51
|
picture = fses.permanent.openbin(utils.pictures.getHDPicturePath(pictureId))
|
|
57
52
|
except:
|
|
58
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
53
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
59
54
|
|
|
60
55
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|
|
61
56
|
|
|
@@ -75,7 +70,7 @@ def getPictureSD(pictureId, format):
|
|
|
75
70
|
type: string
|
|
76
71
|
- name: format
|
|
77
72
|
in: path
|
|
78
|
-
description: Wanted format for output image (
|
|
73
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
79
74
|
required: true
|
|
80
75
|
schema:
|
|
81
76
|
type: string
|
|
@@ -87,10 +82,6 @@ def getPictureSD(pictureId, format):
|
|
|
87
82
|
schema:
|
|
88
83
|
type: string
|
|
89
84
|
format: binary
|
|
90
|
-
image/webp:
|
|
91
|
-
schema:
|
|
92
|
-
type: string
|
|
93
|
-
format: binary
|
|
94
85
|
"""
|
|
95
86
|
utils.pictures.checkFormatParam(format)
|
|
96
87
|
|
|
@@ -104,7 +95,7 @@ def getPictureSD(pictureId, format):
|
|
|
104
95
|
try:
|
|
105
96
|
picture = fses.derivates.openbin(utils.pictures.getPictureFolderPath(pictureId) + "/sd.jpg")
|
|
106
97
|
except:
|
|
107
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
98
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
108
99
|
|
|
109
100
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|
|
110
101
|
|
|
@@ -124,7 +115,7 @@ def getPictureThumb(pictureId, format):
|
|
|
124
115
|
type: string
|
|
125
116
|
- name: format
|
|
126
117
|
in: path
|
|
127
|
-
description: Wanted format for output image (
|
|
118
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
128
119
|
required: true
|
|
129
120
|
schema:
|
|
130
121
|
type: string
|
|
@@ -136,10 +127,6 @@ def getPictureThumb(pictureId, format):
|
|
|
136
127
|
schema:
|
|
137
128
|
type: string
|
|
138
129
|
format: binary
|
|
139
|
-
image/webp:
|
|
140
|
-
schema:
|
|
141
|
-
type: string
|
|
142
|
-
format: binary
|
|
143
130
|
"""
|
|
144
131
|
return utils.pictures.sendThumbnail(pictureId, format)
|
|
145
132
|
|
|
@@ -171,7 +158,7 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
171
158
|
type: number
|
|
172
159
|
- name: format
|
|
173
160
|
in: path
|
|
174
|
-
description: Wanted format for output image (
|
|
161
|
+
description: Wanted format for output image (for the moment only jpg)
|
|
175
162
|
required: true
|
|
176
163
|
schema:
|
|
177
164
|
type: string
|
|
@@ -183,10 +170,6 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
183
170
|
schema:
|
|
184
171
|
type: string
|
|
185
172
|
format: binary
|
|
186
|
-
image/webp:
|
|
187
|
-
schema:
|
|
188
|
-
type: string
|
|
189
|
-
format: binary
|
|
190
173
|
"""
|
|
191
174
|
|
|
192
175
|
utils.pictures.checkFormatParam(format)
|
|
@@ -201,27 +184,27 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
201
184
|
picPath = f"{utils.pictures.getPictureFolderPath(pictureId)}/tiles/{col}_{row}.jpg"
|
|
202
185
|
|
|
203
186
|
if metadata["type"] == "flat":
|
|
204
|
-
raise errors.InvalidAPIUsage("Tiles are not available for flat pictures", status_code=404)
|
|
187
|
+
raise errors.InvalidAPIUsage(_("Tiles are not available for flat pictures"), status_code=404)
|
|
205
188
|
|
|
206
189
|
try:
|
|
207
190
|
col = int(col)
|
|
208
191
|
except:
|
|
209
|
-
raise errors.InvalidAPIUsage("Column parameter is invalid, should be an integer", status_code=404)
|
|
192
|
+
raise errors.InvalidAPIUsage(_("Column parameter is invalid, should be an integer"), status_code=404)
|
|
210
193
|
|
|
211
194
|
if col < 0 or col >= metadata["cols"]:
|
|
212
|
-
raise errors.InvalidAPIUsage("Column parameter is invalid", status_code=404)
|
|
195
|
+
raise errors.InvalidAPIUsage(_("Column parameter is invalid"), status_code=404)
|
|
213
196
|
|
|
214
197
|
try:
|
|
215
198
|
row = int(row)
|
|
216
199
|
except:
|
|
217
|
-
raise errors.InvalidAPIUsage("Row parameter is invalid, should be an integer", status_code=404)
|
|
200
|
+
raise errors.InvalidAPIUsage(_("Row parameter is invalid, should be an integer"), status_code=404)
|
|
218
201
|
|
|
219
202
|
if row < 0 or row >= metadata["rows"]:
|
|
220
|
-
raise errors.InvalidAPIUsage("Row parameter is invalid", status_code=404)
|
|
203
|
+
raise errors.InvalidAPIUsage(_("Row parameter is invalid"), status_code=404)
|
|
221
204
|
|
|
222
205
|
try:
|
|
223
206
|
picture = fses.derivates.openbin(picPath)
|
|
224
207
|
except:
|
|
225
|
-
raise errors.InvalidAPIUsage("Unable to read picture on filesystem", status_code=500)
|
|
208
|
+
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
226
209
|
|
|
227
210
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|