3
3
import time
4
4
from threading import Lock
5
5
6
+ import geopandas as gpd
7
+ import mapbox_vector_tile
8
+ import mercantile
6
9
import numpy as np
10
+ from shapely .geometry import shape
11
+ from shapely .ops import transform
7
12
8
13
from guppy .db .db_session import SessionLocal
9
14
from guppy .db .models import TileStatistics
15
+ from guppy .error import create_error
10
16
11
17
logger = logging .getLogger (__name__ )
18
+ from typing import Optional
19
+ import gzip
20
+ import sqlite3
12
21
13
22
14
23
def tile2lonlat (x , y , z ):
@@ -33,6 +42,14 @@ def tile2lonlat(x, y, z):
33
42
return (lon_left , - lat_bottom , lon_right , - lat_top )
34
43
35
44
45
+ def latlon_to_tilexy (lon , lat , z ):
46
+ lat_rad = math .radians (lat )
47
+ n = 2.0 ** z
48
+ xtile = int ((lon + 180.0 ) / 360.0 * n )
49
+ ytile = int ((1.0 - math .asinh (math .tan (lat_rad )) / math .pi ) / 2.0 * n )
50
+ return z , xtile , ytile
51
+
52
+
36
53
# In-memory request counter, structured by layer_name -> z/x/y -> count
37
54
request_counter = {}
38
55
request_counter_lock = Lock ()
@@ -163,3 +180,70 @@ def get_field_mapping(conn):
163
180
cursor .execute ("PRAGMA table_info(tiles)" )
164
181
columns = cursor .fetchall ()
165
182
return {col [1 ]: col [1 ] for col in columns } # Simple mapping of name to name
183
+
184
+
185
+ def get_tile_data (layer_name : str , mb_file : str , z : int , x : int , y : int ) -> Optional [bytes ]:
186
+ """
187
+ Args:
188
+ layer_name: The name of the layer for which the tile data is being retrieved.
189
+ mb_file: The path to the MBTiles file from which the tile data is being retrieved.
190
+ z: The zoom level of the tile.
191
+ x: The X coordinate of the tile.
192
+ y: The Y coordinate of the tile.
193
+
194
+ Returns:
195
+ Optional[bytes]: The tile data as bytes if found, or None if no tile data exists for the given parameters.
196
+
197
+ Raises:
198
+ HTTPException: If there is an error retrieving the tile data from the MBTiles file.
199
+
200
+ """
201
+ # Flip Y coordinate because MBTiles grid is TMS (bottom-left origin)
202
+ y = (1 << z ) - 1 - y
203
+ logger .info (f"Getting tile for layer { layer_name } at zoom { z } , x { x } , y { y } " )
204
+ try :
205
+ uri = f'file:{ mb_file } ?mode=ro'
206
+ with sqlite3 .connect (uri , uri = True ) as conn :
207
+ cursor = conn .cursor ()
208
+ cursor .execute ("SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?" , (z , x , y ))
209
+ tile = cursor .fetchone ()
210
+ if tile :
211
+ return gzip .decompress (bytes (tile [0 ]))
212
+ else :
213
+ return None
214
+ except Exception as e :
215
+ create_error (code = 404 , message = str (e ))
216
+
217
+
218
+ def pbf_to_geodataframe (pbf_data , x , y , z ):
219
+ """
220
+ Converts PBF data to a GeoDataFrame.
221
+
222
+ :param pbf_data: The PBF data to be decoded.
223
+ :param x: The x-coordinate of the tile.
224
+ :param y: The y-coordinate of the tile.
225
+ :param z: The zoom level of the tile.
226
+ :return: A GeoDataFrame containing the decoded PBF data in GeoJSON format.
227
+ """
228
+ # Decode PBF data
229
+ decoded_data = mapbox_vector_tile .decode (pbf_data )
230
+ tile_bounds = mercantile .bounds (x , y , z )
231
+ # Collect features and convert them to GeoJSON format
232
+ features = []
233
+ for layer_name , layer in decoded_data .items ():
234
+ for feature in layer ['features' ]:
235
+ geom = shape (feature ['geometry' ])
236
+
237
+ def scale_translate (x , y , bounds = tile_bounds , tile_dim = 4096 ):
238
+ # Adjust for the flipped tiles by inverting the y-axis calculation
239
+ lon = (x / tile_dim ) * (bounds .east - bounds .west ) + bounds .west
240
+ lat = (y / tile_dim ) * (bounds .north - bounds .south ) + bounds .south
241
+ return lon , lat
242
+
243
+ geom_transformed = transform (scale_translate , geom )
244
+ properties = feature ['properties' ]
245
+ properties ["featureId" ] = feature ['id' ]
246
+ features .append ({'type' : 'Feature' , 'geometry' : geom_transformed , 'properties' : properties })
247
+
248
+ gdf = gpd .GeoDataFrame .from_features (features , crs = 'EPSG:4326' )
249
+ return gdf
0 commit comments