1- import math
21from typing import Any , Dict , List , Optional
32
4- import mapbox_vector_tile # type: ignore
53from asyncpg import Connection
64from fastapi import APIRouter , Depends , HTTPException , Request , Response
7- from shapely .geometry import Point , Polygon # type: ignore
85
96from modules import query , tiles
107from modules .dependencies import commons_params , database
1714class 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
8318def _errors_geojson (
8419 results : List [Dict [str , Any ]],
@@ -179,15 +114,15 @@ async def heat(
179114 sql = (
180115 f"""
181116SELECT
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
192127FROM
193128"""
@@ -198,39 +133,45 @@ async def heat(
198133 + where
199134 + """
200135GROUP 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
236177def _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 (
0 commit comments