Skip to content

Commit 7f8a5a5

Browse files
committed
Replace mapbox_vector_tile by Postgis ST_AsMVT
1 parent 626e3b9 commit 7f8a5a5

File tree

4 files changed

+83
-107
lines changed

4 files changed

+83
-107
lines changed

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This guide is compatible with Ubuntu 18.04 and Ubuntu 20.04
66
### Dependencies
77

88
```
9-
apt install python3 python3-dev virtualenv gcc pkg-config libpng-dev libjpeg-dev libfreetype6-dev postgresql-server-dev-all libgeos-dev g++ python3-shapely nodejs npm
9+
apt install python3 python3-dev virtualenv gcc pkg-config libpng-dev libjpeg-dev libfreetype6-dev postgresql-server-dev-all libgeos-dev g++ nodejs npm
1010
```
1111

1212
#### Database

api/issues_tiles.py

Lines changed: 44 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import math
21
from typing import Any, Dict, List, Optional
32

4-
import mapbox_vector_tile # type: ignore
53
from asyncpg import Connection
64
from fastapi import APIRouter, Depends, HTTPException, Request, Response
7-
from shapely.geometry import Point, Polygon # type: ignore
85

96
from modules import query, tiles
107
from modules.dependencies import commons_params, database
@@ -17,68 +14,6 @@
1714
class MVTResponse(Response):
1815
media_type = "application/vnd.mapbox-vector-tile"
1916

20-
def render(self, content: Any) -> bytes:
21-
return mapbox_vector_tile.encode(
22-
content["content"],
23-
extents=content.get("extents", 2048),
24-
quantize_bounds=content.get("quantize_bounds"),
25-
)
26-
27-
28-
def mvtResponse(content) -> Response:
29-
if not content or not content["content"]:
30-
return Response(status_code=204)
31-
else:
32-
return MVTResponse(content, media_type="application/vnd.mapbox-vector-tile")
33-
34-
35-
def _errors_mvt(
36-
results: List[Dict[str, Any]],
37-
z: int,
38-
min_lon: float,
39-
min_lat: float,
40-
max_lon: float,
41-
max_lat: float,
42-
limit: int,
43-
) -> Optional[Dict[str, Any]]:
44-
if not results or len(results) == 0:
45-
return None
46-
else:
47-
limit_feature = []
48-
if len(results) == limit and z < 18:
49-
limit_feature = [
50-
{
51-
"name": "limit",
52-
"features": [
53-
{
54-
"geometry": Point(
55-
(min_lon + max_lon) / 2, (min_lat + max_lat) / 2
56-
)
57-
}
58-
],
59-
}
60-
]
61-
62-
issues_features = []
63-
for res in sorted(results, key=lambda res: -res["lat"]):
64-
issues_features.append(
65-
{
66-
"id": res["id"],
67-
"geometry": Point(res["lon"], res["lat"]),
68-
"properties": {
69-
"uuid": str(res["uuid"]),
70-
"item": res["item"] or 0,
71-
"class": res["class"] or 0,
72-
},
73-
}
74-
)
75-
76-
return {
77-
"content": [{"name": "issues", "features": issues_features}]
78-
+ limit_feature,
79-
"quantize_bounds": (min_lon, min_lat, max_lon, max_lat),
80-
}
81-
8217

8318
def _errors_geojson(
8419
results: List[Dict[str, Any]],
@@ -179,15 +114,15 @@ async def heat(
179114
sql = (
180115
f"""
181116
SELECT
182-
COUNT(*),
117+
COUNT(*) AS count,
183118
(
184119
(lon-${len(sql_params)-4}) * ${len(sql_params)} /
185-
(${len(sql_params)-2}-${len(sql_params)-4}) + 0.5
186-
)::int AS latn,
120+
(${len(sql_params)-2}-${len(sql_params)-4}) - 0.5
121+
)::int AS x,
187122
(
188123
(lat-${len(sql_params)-3}) * ${len(sql_params)} /
189124
(${len(sql_params)-1}-${len(sql_params)-3}) + 0.5
190-
)::int AS lonn,
125+
)::int AS y,
191126
mode() WITHIN GROUP (ORDER BY items.marker_color) AS color
192127
FROM
193128
"""
@@ -198,39 +133,45 @@ async def heat(
198133
+ where
199134
+ """
200135
GROUP BY
201-
latn,
202-
lonn
136+
x, y
203137
"""
204138
)
205139

206-
features = []
207-
for row in await db.fetch(sql, *sql_params):
208-
count, x, y, color = row
209-
count = max(
210-
int(
211-
math.log(count)
212-
/ math.log(limit / ((z - 4 + 1 + math.sqrt(COUNT)) ** 2))
213-
* 255
214-
),
215-
1 if count > 0 else 0,
216-
)
217-
if count > 0:
218-
count = 255 if count > 255 else count
219-
features.append(
220-
{
221-
"geometry": Polygon(
222-
[(x, y), (x - 1, y), (x - 1, y - 1), (x, y - 1)]
223-
),
224-
"properties": {"color": int(color[1:], 16), "count": count},
225-
}
226-
)
227-
228-
return mvtResponse(
229-
{
230-
"content": [{"name": "issues", "features": features}],
231-
"extents": COUNT,
232-
}
140+
sql_params += [params.limit, params.zoom]
141+
sql = f"""
142+
WITH
143+
grid AS ({sql}),
144+
grid_count AS (
145+
SELECT
146+
greatest(
147+
(
148+
log(count)
149+
/ log(${len(sql_params)-1} / ((${len(sql_params)} - 4 + 1 + sqrt(${len(sql_params)-2})) ^ 2))
150+
* 255
151+
)::int,
152+
CASE WHEN count > 0 THEN 1 ELSE 0 END
153+
) AS count,
154+
x AS x, ${len(sql_params)-2} - y AS y, color
155+
FROM
156+
grid
157+
),
158+
a AS (
159+
SELECT
160+
least(count, 255) AS count,
161+
('0x' || substring(color, 2))::int AS color,
162+
ST_MakeEnvelope(x, y, x+1, y+1) AS geom
163+
FROM
164+
grid_count
165+
WHERE
166+
count > 0
233167
)
168+
SELECT ST_AsMVT(a, 'issues', ${len(sql_params)-2}::int, 'geom') FROM a
169+
"""
170+
results = await db.fetchval(sql, *sql_params)
171+
if results is None or len(results) == 0:
172+
return Response(status_code=204)
173+
else:
174+
return MVTResponse(results)
234175

235176

236177
def _issues_params(
@@ -277,10 +218,11 @@ async def issues_mvt(
277218
if params.zoom > 18 or params.zoom < 7:
278219
return Response(status_code=204)
279220

280-
results = await query._gets(db, params)
281-
lon1, lat2 = tiles.tile2lonlat(x, y, z)
282-
lon2, lat1 = tiles.tile2lonlat(x + 1, y + 1, z)
283-
return mvtResponse(_errors_mvt(results, z, lon1, lat1, lon2, lat2, params.limit))
221+
results = await query._gets(db, params, mvt=True)
222+
if results is None or len(results) == 0:
223+
return Response(status_code=204)
224+
else:
225+
return MVTResponse(results)
284226

285227

286228
@router.get(

modules/query.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Any, Dict, Iterable, List, Optional, Tuple
2+
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
33

44
from asyncpg import Connection
55

@@ -260,7 +260,9 @@ def fixes_default(fixes: List[List[Dict[str, Any]]]) -> List[List[Dict[str, Any]
260260
)
261261

262262

263-
async def _gets(db: Connection, params: Params) -> List[Dict[str, Any]]:
263+
async def _gets(
264+
db: Connection, params: Params, mvt: bool = False
265+
) -> Union[List[Dict[str, Any]], bytes]:
264266
sqlbase = """
265267
SELECT
266268
uuid_to_bigint(uuid) as id,
@@ -335,6 +337,40 @@ async def _gets(db: Connection, params: Params) -> List[Dict[str, Any]]:
335337
${len(sql_params)}"""
336338

337339
sql = sqlbase % (join, where)
340+
341+
if mvt:
342+
sql_params.extend([params.limit, params.zoom, params.tilex, params.tiley])
343+
sql = f"""
344+
WITH
345+
query AS ({sql}),
346+
issues AS (
347+
SELECT
348+
(id >> 32)::integer AS id, uuid, coalesce(item, 0) AS item, coalesce(class, 0) AS class,
349+
ST_AsMVTGeom(
350+
ST_Transform(ST_SetSRID(ST_MakePoint(lon, lat), 4326), 3857),
351+
ST_TileEnvelope(${len(sql_params)-2}, ${len(sql_params)-1}, ${len(sql_params)}),
352+
4096, 0, false
353+
) AS geom
354+
FROM query
355+
),
356+
limit_ AS (
357+
SELECT
358+
ST_AsMVTGeom(
359+
ST_Centroid(ST_TileEnvelope(${len(sql_params)-2}, ${len(sql_params)-1}, ${len(sql_params)})),
360+
ST_TileEnvelope(${len(sql_params)-2}, ${len(sql_params)-1}, ${len(sql_params)}),
361+
4096, 0, false
362+
) AS geom
363+
WHERE (SELECT COUNT(*) FROM query) >= ${len(sql_params)-3}
364+
),
365+
layers AS (
366+
SELECT ST_AsMVT(issues, 'issues', 4096, 'geom', 'id') AS layer FROM issues
367+
UNION ALL
368+
SELECT ST_AsMVT(limit_, 'limit', 4096, 'geom') AS layer FROM limit_
369+
)
370+
SELECT string_agg(layer, ''::bytea) FROM layers
371+
"""
372+
return await db.fetchval(sql, *sql_params)
373+
338374
results = list(await db.fetch(sql, *sql_params))
339375
return list(
340376
map(

requirements.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ asyncpg
22
matplotlib >= 1.1
33
requests >= 2.0
44
polib
5-
protobuf < 4 # 4.x binary not yet compatible with system package, deps of mapbox-vector-tile
6-
mapbox-vector-tile
75
pyclipper
86
fastapi
97
fastapi-sessions

0 commit comments

Comments
 (0)