geovisio 2.5.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 +38 -8
- geovisio/admin_cli/__init__.py +2 -2
- geovisio/admin_cli/db.py +8 -0
- geovisio/config_app.py +64 -0
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +14 -14
- 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 +94 -69
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +288 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +654 -0
- geovisio/web/auth.py +50 -37
- geovisio/web/collections.py +305 -319
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +288 -12
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +203 -151
- geovisio/web/map.py +322 -106
- 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 -121
- geovisio/web/tokens.py +105 -112
- geovisio/web/upload_set.py +768 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +38 -9
- geovisio/workers/runner_pictures.py +278 -183
- geovisio-2.7.0.dist-info/METADATA +95 -0
- geovisio-2.7.0.dist-info/RECORD +66 -0
- geovisio-2.5.0.dist-info/METADATA +0 -115
- geovisio-2.5.0.dist-info/RECORD +0 -41
- {geovisio-2.5.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
- {geovisio-2.5.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
|
-
from typing import Optional, Dict, Any, Tuple, List
|
|
5
|
+
from typing import Optional, Dict, Any, Tuple, List, Union
|
|
7
6
|
from uuid import UUID
|
|
8
|
-
from flask import Blueprint, current_app, send_file, request
|
|
9
|
-
from
|
|
7
|
+
from flask import Blueprint, current_app, send_file, request, jsonify, url_for
|
|
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
|
|
@@ -15,6 +15,91 @@ from psycopg import sql
|
|
|
15
15
|
|
|
16
16
|
bp = Blueprint("map", __name__, url_prefix="/api")
|
|
17
17
|
|
|
18
|
+
ZOOM_GRID_SEQUENCES = 6
|
|
19
|
+
ZOOM_PICTURES = 15
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_style_json(forUser: Optional[Union[UUID, str]] = None):
|
|
23
|
+
# Get correct vector tiles URL
|
|
24
|
+
tilesUrl = url_for("map.getTile", x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
25
|
+
sourceId = "geovisio"
|
|
26
|
+
if forUser == "me":
|
|
27
|
+
tilesUrl = url_for("map.getMyTile", x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
28
|
+
sourceId = "geovisio_me"
|
|
29
|
+
elif forUser is not None:
|
|
30
|
+
tilesUrl = url_for("map.getUserTile", userId=forUser, x="11111111", y="22222222", z="33333333", format="mvt", _external=True)
|
|
31
|
+
sourceId = f"geovisio_{str(forUser)}"
|
|
32
|
+
tilesUrl = tilesUrl.replace("11111111", "{x}").replace("22222222", "{y}").replace("33333333", "{z}")
|
|
33
|
+
|
|
34
|
+
# Display sequence on all zooms if user tiles, after grid on general tiles
|
|
35
|
+
sequenceOpacity = ["interpolate", ["linear"], ["zoom"], ZOOM_GRID_SEQUENCES, 0, ZOOM_GRID_SEQUENCES + 1, 1] if forUser is None else 1
|
|
36
|
+
|
|
37
|
+
layers = [
|
|
38
|
+
{
|
|
39
|
+
"id": f"{sourceId}_sequences",
|
|
40
|
+
"type": "line",
|
|
41
|
+
"source": sourceId,
|
|
42
|
+
"source-layer": "sequences",
|
|
43
|
+
"paint": {
|
|
44
|
+
"line-color": "#FF6F00",
|
|
45
|
+
"line-width": ["interpolate", ["linear"], ["zoom"], 0, 0.5, 10, 2, 14, 4, 16, 5, 22, 3],
|
|
46
|
+
"line-opacity": sequenceOpacity,
|
|
47
|
+
},
|
|
48
|
+
"layout": {
|
|
49
|
+
"line-cap": "square",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": f"{sourceId}_pictures",
|
|
54
|
+
"type": "circle",
|
|
55
|
+
"source": sourceId,
|
|
56
|
+
"source-layer": "pictures",
|
|
57
|
+
"paint": {
|
|
58
|
+
"circle-color": "#FF6F00",
|
|
59
|
+
"circle-radius": ["interpolate", ["linear"], ["zoom"], ZOOM_PICTURES, 4.5, 17, 8, 22, 12],
|
|
60
|
+
"circle-opacity": ["interpolate", ["linear"], ["zoom"], ZOOM_PICTURES, 0, ZOOM_PICTURES + 1, 1],
|
|
61
|
+
"circle-stroke-color": "#ffffff",
|
|
62
|
+
"circle-stroke-width": ["interpolate", ["linear"], ["zoom"], 17, 0, 20, 2],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Grid layer of general tiles
|
|
68
|
+
if forUser is None:
|
|
69
|
+
layers.append(
|
|
70
|
+
{
|
|
71
|
+
"id": f"{sourceId}_grid",
|
|
72
|
+
"type": "fill",
|
|
73
|
+
"source": sourceId,
|
|
74
|
+
"source-layer": "grid",
|
|
75
|
+
"paint": {
|
|
76
|
+
"fill-color": ["interpolate", ["linear"], ["get", "coef"], 0, "#FFCC80", 0.5, "#E65100", 1, "#BF360C"],
|
|
77
|
+
"fill-opacity": [
|
|
78
|
+
"interpolate",
|
|
79
|
+
["linear"],
|
|
80
|
+
["zoom"],
|
|
81
|
+
0,
|
|
82
|
+
1,
|
|
83
|
+
ZOOM_GRID_SEQUENCES - 2,
|
|
84
|
+
1,
|
|
85
|
+
ZOOM_GRID_SEQUENCES,
|
|
86
|
+
0.8,
|
|
87
|
+
ZOOM_GRID_SEQUENCES + 0.5,
|
|
88
|
+
0,
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
style = {
|
|
95
|
+
"version": 8,
|
|
96
|
+
"name": "GeoVisio Vector Tiles",
|
|
97
|
+
"sources": {sourceId: {"type": "vector", "tiles": [tilesUrl], "minzoom": 0, "maxzoom": ZOOM_PICTURES}},
|
|
98
|
+
"layers": layers,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return jsonify(style)
|
|
102
|
+
|
|
18
103
|
|
|
19
104
|
def checkTileValidity(z, x, y, format):
|
|
20
105
|
"""Check if tile parameters are valid
|
|
@@ -35,32 +120,52 @@ def checkTileValidity(z, x, y, format):
|
|
|
35
120
|
raises InvalidAPIUsage exceptions if parameters are not OK
|
|
36
121
|
"""
|
|
37
122
|
if z is None or x is None or y is None or format is None:
|
|
38
|
-
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)
|
|
39
124
|
if format not in ["pbf", "mvt"]:
|
|
40
|
-
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)
|
|
41
126
|
|
|
42
127
|
size = 2**z
|
|
43
128
|
if x >= size or y >= size:
|
|
44
|
-
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)
|
|
45
130
|
if x < 0 or y < 0:
|
|
46
|
-
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)
|
|
47
132
|
if z < 0 or z > 15:
|
|
48
|
-
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)
|
|
49
134
|
|
|
50
135
|
|
|
51
136
|
def _getTile(z: int, x: int, y: int, format: str, onlyForUser: Optional[UUID] = None, filter: Optional[sql.SQL] = None):
|
|
52
137
|
checkTileValidity(z, x, y, format)
|
|
53
138
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
res = cursor.execute(query, params).fetchone()
|
|
139
|
+
query, params = _get_query(z, x, y, onlyForUser, additional_filter=filter)
|
|
140
|
+
|
|
141
|
+
res = db.fetchone(current_app, query, params, timeout=10000)
|
|
58
142
|
|
|
59
|
-
|
|
60
|
-
|
|
143
|
+
if not res:
|
|
144
|
+
raise errors.InternalError(_("Impossible to get tile"))
|
|
61
145
|
|
|
62
|
-
|
|
63
|
-
|
|
146
|
+
res = res[0]
|
|
147
|
+
return send_file(io.BytesIO(res), mimetype="application/vnd.mapbox-vector-tile")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@bp.route("/map/style.json")
|
|
151
|
+
@user_dependant_response(False)
|
|
152
|
+
def getStyle():
|
|
153
|
+
"""Get vector tiles style.
|
|
154
|
+
|
|
155
|
+
This style file follows MapLibre Style Spec : https://maplibre.org/maplibre-style-spec/
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
tags:
|
|
159
|
+
- Map
|
|
160
|
+
responses:
|
|
161
|
+
200:
|
|
162
|
+
description: Vector tiles style JSON
|
|
163
|
+
content:
|
|
164
|
+
application/json:
|
|
165
|
+
schema:
|
|
166
|
+
$ref: '#/components/schemas/MapLibreStyleJSON'
|
|
167
|
+
"""
|
|
168
|
+
return get_style_json()
|
|
64
169
|
|
|
65
170
|
|
|
66
171
|
@bp.route("/map/<int:z>/<int:x>/<int:y>.<format>")
|
|
@@ -68,13 +173,12 @@ def _getTile(z: int, x: int, y: int, format: str, onlyForUser: Optional[UUID] =
|
|
|
68
173
|
def getTile(z: int, x: int, y: int, format: str):
|
|
69
174
|
"""Get pictures and sequences as vector tiles
|
|
70
175
|
|
|
71
|
-
Vector tiles contains
|
|
176
|
+
Vector tiles contains different layers based on zoom level : sequences, pictures or grid.
|
|
72
177
|
|
|
73
178
|
Layer "sequences":
|
|
74
|
-
- Available on
|
|
75
|
-
- Available properties
|
|
179
|
+
- Available on zoom levels >= 6
|
|
180
|
+
- Available properties:
|
|
76
181
|
- id (sequence ID)
|
|
77
|
-
- Other properties (available on zoom levels >= 13)
|
|
78
182
|
- account_id
|
|
79
183
|
- model (camera make and model)
|
|
80
184
|
- type (flat or equirectangular)
|
|
@@ -90,6 +194,14 @@ def getTile(z: int, x: int, y: int, format: str):
|
|
|
90
194
|
- sequences (list of sequences ID this pictures belongs to)
|
|
91
195
|
- type (flat or equirectangular)
|
|
92
196
|
- model (camera make and model)
|
|
197
|
+
|
|
198
|
+
Layer "grid":
|
|
199
|
+
- Available on zoom levels 0 to 5 (included)
|
|
200
|
+
- Available properties:
|
|
201
|
+
- id
|
|
202
|
+
- nb_pictures
|
|
203
|
+
- coef (value from 0 to 1, relative quantity of available pictures)
|
|
204
|
+
|
|
93
205
|
---
|
|
94
206
|
tags:
|
|
95
207
|
- Map
|
|
@@ -135,27 +247,24 @@ def getTile(z: int, x: int, y: int, format: str):
|
|
|
135
247
|
def _get_query(z: int, x: int, y: int, onlyForUser: Optional[UUID], additional_filter: Optional[sql.SQL]) -> Tuple[sql.Composed, Dict]:
|
|
136
248
|
"""Returns appropriate SQL query according to given zoom"""
|
|
137
249
|
|
|
138
|
-
sequences_filter: List[sql.Composable] = [sql.SQL("s.geom && ST_Transform(ST_TileEnvelope(%(z)s, %(x)s, %(y)s), 4326)")]
|
|
139
|
-
pictures_filter: List[sql.Composable] = [sql.SQL("p.geom && ST_Transform(ST_TileEnvelope(%(z)s, %(x)s, %(y)s), 4326)")]
|
|
140
250
|
params: Dict[str, Any] = {"x": x, "y": y, "z": z}
|
|
141
251
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
#
|
|
145
|
-
sequences_filter.append(sql.SQL("s.status != 'deleted'"))
|
|
146
|
-
pictures_filter.append(sql.SQL("p.status != 'waiting-for-delete'"))
|
|
252
|
+
#############################################################
|
|
253
|
+
# SQL Filters
|
|
254
|
+
#
|
|
147
255
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
256
|
+
# Basic filters
|
|
257
|
+
grid_filter: List[sql.Composable] = [sql.SQL("g.geom && ST_Transform(ST_TileEnvelope(%(z)s, %(x)s, %(y)s), 4326)")]
|
|
258
|
+
sequences_filter: List[sql.Composable] = [
|
|
259
|
+
sql.SQL("s.status != 'deleted'"), # we never want to display deleted sequences on the map
|
|
260
|
+
sql.SQL("s.geom && ST_Transform(ST_TileEnvelope(%(z)s, %(x)s, %(y)s), 4326)"),
|
|
261
|
+
]
|
|
262
|
+
pictures_filter: List[sql.Composable] = [
|
|
263
|
+
sql.SQL("p.status != 'waiting-for-delete'"), # we never want to display deleted pictures on the map
|
|
264
|
+
sql.SQL("p.geom && ST_Transform(ST_TileEnvelope(%(z)s, %(x)s, %(y)s), 4326)"),
|
|
265
|
+
]
|
|
158
266
|
|
|
267
|
+
# Supplementary filters
|
|
159
268
|
if additional_filter:
|
|
160
269
|
sequences_filter.append(additional_filter)
|
|
161
270
|
filter_str = additional_filter.as_string(None)
|
|
@@ -166,16 +275,46 @@ def _get_query(z: int, x: int, y: int, onlyForUser: Optional[UUID], additional_f
|
|
|
166
275
|
pic_additional_filter = sql.SQL(pic_additional_filter_str) # type: ignore
|
|
167
276
|
pictures_filter.append(sql.SQL("(") + sql.SQL(" OR ").join([pic_additional_filter, additional_filter]) + sql.SQL(")"))
|
|
168
277
|
|
|
278
|
+
# Per-user filters
|
|
279
|
+
if onlyForUser:
|
|
280
|
+
sequences_filter.append(sql.SQL("s.account_id = %(account)s"))
|
|
281
|
+
pictures_filter.append(sql.SQL("p.account_id = %(account)s"))
|
|
282
|
+
params["account"] = onlyForUser
|
|
283
|
+
|
|
284
|
+
# Not logged-in requests -> only show "ready" pics/sequences
|
|
285
|
+
account = auth.get_current_account()
|
|
286
|
+
accountId = account.id if account is not None else None
|
|
287
|
+
if not onlyForUser or accountId != str(onlyForUser):
|
|
288
|
+
sequences_filter.append(sql.SQL("s.status = 'ready'"))
|
|
289
|
+
pictures_filter.append(sql.SQL("p.status = 'ready'"))
|
|
290
|
+
pictures_filter.append(sql.SQL("s.status = 'ready'"))
|
|
291
|
+
|
|
292
|
+
#############################################################
|
|
293
|
+
# SQL Result columns/fields
|
|
294
|
+
#
|
|
295
|
+
|
|
296
|
+
grid_fields = [
|
|
297
|
+
sql.SQL("ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(%(z)s, %(x)s, %(y)s)) AS geom"),
|
|
298
|
+
sql.SQL("id"),
|
|
299
|
+
sql.SQL("nb_pictures"),
|
|
300
|
+
sql.SQL(
|
|
301
|
+
"""
|
|
302
|
+
((CASE WHEN nb_pictures <= (select PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_pictures) from pictures_grid)
|
|
303
|
+
THEN nb_pictures::float / (select PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_pictures) from pictures_grid) * 0.5
|
|
304
|
+
ELSE 0.5 + nb_pictures::float / (SELECT MAX(nb_pictures) FROM pictures_grid) * 0.5
|
|
305
|
+
END) * 10)::int / 10::float AS coef
|
|
306
|
+
"""
|
|
307
|
+
),
|
|
308
|
+
]
|
|
169
309
|
sequences_fields = [
|
|
170
310
|
sql.SQL("ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(%(z)s, %(x)s, %(y)s)) AS geom"),
|
|
171
311
|
sql.SQL("id"),
|
|
172
312
|
]
|
|
173
313
|
simplified_sequence_fields = [
|
|
174
|
-
sql.SQL("ST_Simplify(geom, 0.01) AS geom"),
|
|
175
|
-
sql.SQL("id"),
|
|
176
|
-
sql.SQL("status"),
|
|
314
|
+
sql.SQL("ST_AsMVTGeom(ST_Transform(ST_Simplify(geom, 0.01), 3857), ST_TileEnvelope(%(z)s, %(x)s, %(y)s)) AS geom"),
|
|
177
315
|
]
|
|
178
|
-
|
|
316
|
+
|
|
317
|
+
if z >= ZOOM_GRID_SEQUENCES or onlyForUser:
|
|
179
318
|
sequences_fields.extend(
|
|
180
319
|
[
|
|
181
320
|
sql.SQL("account_id"),
|
|
@@ -185,92 +324,148 @@ def _get_query(z: int, x: int, y: int, onlyForUser: Optional[UUID], additional_f
|
|
|
185
324
|
sql.SQL("computed_capture_date AS date"),
|
|
186
325
|
]
|
|
187
326
|
)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
)
|
|
196
|
-
if z >= 15:
|
|
327
|
+
|
|
328
|
+
#############################################################
|
|
329
|
+
# SQL Full requests
|
|
330
|
+
#
|
|
331
|
+
|
|
332
|
+
# Full pictures + sequences (z15+)
|
|
333
|
+
if z >= ZOOM_PICTURES:
|
|
197
334
|
query = sql.SQL(
|
|
198
335
|
"""
|
|
199
|
-
SELECT mvtsequences.mvt || mvtpictures.mvt
|
|
200
|
-
FROM (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
) mvtsequences,
|
|
210
|
-
(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
) mvtpictures
|
|
228
|
-
"""
|
|
336
|
+
SELECT mvtsequences.mvt || mvtpictures.mvt
|
|
337
|
+
FROM (
|
|
338
|
+
SELECT ST_AsMVT(mvtgeomseqs.*, 'sequences') AS mvt
|
|
339
|
+
FROM (
|
|
340
|
+
SELECT
|
|
341
|
+
{sequences_fields}
|
|
342
|
+
FROM sequences s
|
|
343
|
+
WHERE
|
|
344
|
+
{sequences_filter}
|
|
345
|
+
) mvtgeomseqs
|
|
346
|
+
) mvtsequences,
|
|
347
|
+
(
|
|
348
|
+
SELECT ST_AsMVT(mvtgeompics.*, 'pictures') AS mvt
|
|
349
|
+
FROM (
|
|
350
|
+
SELECT
|
|
351
|
+
ST_AsMVTGeom(ST_Transform(p.geom, 3857), ST_TileEnvelope(%(z)s, %(x)s, %(y)s)) AS geom,
|
|
352
|
+
p.id, p.ts, p.heading, p.account_id,
|
|
353
|
+
NULLIF(p.status != 'ready' OR s.status != 'ready', FALSE) AS hidden,
|
|
354
|
+
array_to_json(ARRAY_AGG(sp.seq_id)) AS sequences,
|
|
355
|
+
p.metadata->>'type' AS type,
|
|
356
|
+
TRIM(CONCAT(p.metadata->>'make', ' ', p.metadata->>'model')) AS model
|
|
357
|
+
FROM pictures p
|
|
358
|
+
LEFT JOIN sequences_pictures sp ON p.id = sp.pic_id
|
|
359
|
+
LEFT JOIN sequences s ON s.id = sp.seq_id
|
|
360
|
+
WHERE
|
|
361
|
+
{pictures_filter}
|
|
362
|
+
GROUP BY 1, 2, 3, 4, 5, 6
|
|
363
|
+
) mvtgeompics
|
|
364
|
+
) mvtpictures
|
|
365
|
+
"""
|
|
229
366
|
).format(
|
|
230
367
|
sequences_filter=sql.SQL(" AND ").join(sequences_filter),
|
|
231
368
|
pictures_filter=sql.SQL(" AND ").join(pictures_filter),
|
|
232
369
|
sequences_fields=sql.SQL(", ").join(sequences_fields),
|
|
233
370
|
)
|
|
234
371
|
|
|
235
|
-
|
|
372
|
+
# Full sequences (z7-14.9 and z0-14.9 for specific users)
|
|
373
|
+
elif z >= ZOOM_GRID_SEQUENCES + 1 or onlyForUser:
|
|
236
374
|
query = sql.SQL(
|
|
237
375
|
"""
|
|
238
|
-
SELECT ST_AsMVT(mvtsequences.*, 'sequences') AS mvt
|
|
239
|
-
FROM (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
) mvtsequences
|
|
246
|
-
"""
|
|
376
|
+
SELECT ST_AsMVT(mvtsequences.*, 'sequences') AS mvt
|
|
377
|
+
FROM (
|
|
378
|
+
SELECT
|
|
379
|
+
{sequences_fields}
|
|
380
|
+
FROM sequences s
|
|
381
|
+
WHERE
|
|
382
|
+
{sequences_filter}
|
|
383
|
+
) mvtsequences
|
|
384
|
+
"""
|
|
247
385
|
).format(sequences_filter=sql.SQL(" AND ").join(sequences_filter), sequences_fields=sql.SQL(", ").join(sequences_fields))
|
|
248
|
-
|
|
386
|
+
|
|
387
|
+
# Sequences + grid (z6-6.9)
|
|
388
|
+
elif z >= ZOOM_GRID_SEQUENCES:
|
|
249
389
|
query = sql.SQL(
|
|
250
390
|
"""
|
|
251
|
-
SELECT
|
|
252
|
-
FROM (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
391
|
+
SELECT mvtsequences.mvt || mvtgrid.mvt
|
|
392
|
+
FROM (
|
|
393
|
+
SELECT ST_AsMVT(mvtgeomseqs.*, 'sequences') AS mvt
|
|
394
|
+
FROM (
|
|
395
|
+
SELECT
|
|
396
|
+
{simplified_sequence_fields}
|
|
397
|
+
FROM sequences s
|
|
398
|
+
WHERE
|
|
399
|
+
{sequences_filter}
|
|
400
|
+
) mvtgeomseqs
|
|
401
|
+
) mvtsequences,
|
|
402
|
+
(
|
|
403
|
+
SELECT ST_AsMVT(mvtgeomgrid.*, 'grid') AS mvt
|
|
404
|
+
FROM (
|
|
405
|
+
SELECT
|
|
406
|
+
{grid_fields}
|
|
407
|
+
FROM pictures_grid g
|
|
408
|
+
WHERE {grid_filter}
|
|
409
|
+
) mvtgeomgrid
|
|
410
|
+
) mvtgrid
|
|
411
|
+
"""
|
|
264
412
|
).format(
|
|
265
413
|
sequences_filter=sql.SQL(" AND ").join(sequences_filter),
|
|
266
|
-
sequences_fields=sql.SQL(", ").join(sequences_fields),
|
|
267
414
|
simplified_sequence_fields=sql.SQL(", ").join(simplified_sequence_fields),
|
|
415
|
+
grid_filter=sql.SQL(" AND ").join(grid_filter),
|
|
416
|
+
grid_fields=sql.SQL(", ").join(grid_fields),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Grid overview (all users + z0-5.9)
|
|
420
|
+
else:
|
|
421
|
+
query = sql.SQL(
|
|
422
|
+
"""
|
|
423
|
+
SELECT ST_AsMVT(mvtgrid.*, 'grid') AS mvt
|
|
424
|
+
FROM (
|
|
425
|
+
SELECT
|
|
426
|
+
{grid_fields}
|
|
427
|
+
FROM pictures_grid g
|
|
428
|
+
WHERE {grid_filter}
|
|
429
|
+
) mvtgrid
|
|
430
|
+
"""
|
|
431
|
+
).format(
|
|
432
|
+
grid_filter=sql.SQL(" AND ").join(grid_filter),
|
|
433
|
+
grid_fields=sql.SQL(", ").join(grid_fields),
|
|
268
434
|
)
|
|
269
435
|
|
|
270
436
|
return query, params
|
|
271
437
|
|
|
272
438
|
|
|
439
|
+
@bp.route("/users/<uuid:userId>/map/style.json")
|
|
440
|
+
@user_dependant_response(False)
|
|
441
|
+
def getUserStyle(userId: UUID):
|
|
442
|
+
"""Get vector tiles style for a single user.
|
|
443
|
+
|
|
444
|
+
This style file follows MapLibre Style Spec : https://maplibre.org/maplibre-style-spec/
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
tags:
|
|
448
|
+
- Map
|
|
449
|
+
parameters:
|
|
450
|
+
- name: userId
|
|
451
|
+
in: path
|
|
452
|
+
description: User ID
|
|
453
|
+
required: true
|
|
454
|
+
schema:
|
|
455
|
+
type: string
|
|
456
|
+
responses:
|
|
457
|
+
200:
|
|
458
|
+
description: Vector tiles style JSON
|
|
459
|
+
content:
|
|
460
|
+
application/json:
|
|
461
|
+
schema:
|
|
462
|
+
$ref: '#/components/schemas/MapLibreStyleJSON'
|
|
463
|
+
"""
|
|
464
|
+
return get_style_json(forUser=userId)
|
|
465
|
+
|
|
466
|
+
|
|
273
467
|
@bp.route("/users/<uuid:userId>/map/<int:z>/<int:x>/<int:y>.<format>")
|
|
468
|
+
@user_dependant_response(True)
|
|
274
469
|
def getUserTile(userId: UUID, z: int, x: int, y: int, format: str):
|
|
275
470
|
"""Get pictures and sequences as vector tiles for a specific user.
|
|
276
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
|
|
@@ -327,6 +522,27 @@ def getUserTile(userId: UUID, z: int, x: int, y: int, format: str):
|
|
|
327
522
|
return _getTile(z, x, y, format, onlyForUser=userId, filter=filter)
|
|
328
523
|
|
|
329
524
|
|
|
525
|
+
@bp.route("/users/me/map/style.json")
|
|
526
|
+
@auth.login_required_with_redirect()
|
|
527
|
+
def getMyStyle(account: Account):
|
|
528
|
+
"""Get vector tiles style.
|
|
529
|
+
|
|
530
|
+
This style file follows MapLibre Style Spec : https://maplibre.org/maplibre-style-spec/
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
tags:
|
|
534
|
+
- Map
|
|
535
|
+
responses:
|
|
536
|
+
200:
|
|
537
|
+
description: Vector tiles style JSON
|
|
538
|
+
content:
|
|
539
|
+
application/json:
|
|
540
|
+
schema:
|
|
541
|
+
$ref: '#/components/schemas/MapLibreStyleJSON'
|
|
542
|
+
"""
|
|
543
|
+
return get_style_json(forUser="me")
|
|
544
|
+
|
|
545
|
+
|
|
330
546
|
@bp.route("/users/me/map/<int:z>/<int:x>/<int:y>.<format>")
|
|
331
547
|
@auth.login_required_with_redirect()
|
|
332
548
|
def getMyTile(account: Account, z: int, x: int, y: int, format: str):
|