diff --git a/services/tileserver/macrostrat/tileserver/__init__.py b/services/tileserver/macrostrat/tileserver/__init__.py index 60c90a0ab..a151ba7bd 100644 --- a/services/tileserver/macrostrat/tileserver/__init__.py +++ b/services/tileserver/macrostrat/tileserver/__init__.py @@ -200,6 +200,10 @@ async def shutdown_event(): app.include_router(fossils_router, tags=["PBDB"], prefix="/pbdb") +from .measurements import router as measurements_router + +app.include_router(measurements_router, tags=["Measurements"], prefix="/measurements") + from .integrations import router as integrations_router app.include_router(integrations_router, tags=["Integrations"], prefix="/integrations") diff --git a/services/tileserver/macrostrat/tileserver/measurements/__init__.py b/services/tileserver/macrostrat/tileserver/measurements/__init__.py new file mode 100644 index 000000000..7a8e42f7c --- /dev/null +++ b/services/tileserver/macrostrat/tileserver/measurements/__init__.py @@ -0,0 +1,116 @@ +from pathlib import Path + +from buildpg import render +from fastapi import APIRouter, Request, Response +from timvt.resources.enums import MimeTypes + +router = APIRouter() + +__here__ = Path(__file__).parent + + +@router.get("/tile/{z}/{x}/{y}") +async def tile_query( + request: Request, + z: int, + x: int, + y: int, +): + """Get a tile from the tileserver.""" + pool = request.app.state.pool + + where = "" + + params = { + "z": z, + "x": x, + "y": y, + } + + if "type" in request.query_params: + type_vals = request.query_params["type"].split(",") + type_vals = [v.strip() for v in type_vals if v.strip()] + where += " AND type = ANY(:type_vals)" + params["type_vals"] = type_vals + + if "cluster" in request.query_params: + cluster_val = request.query_params["cluster"] + cluster = cluster_val.lower() not in ("false", "0", "no") + + else: + cluster = True + + clusterSQL = """ + , + + mvt_features AS ( + SELECT id, + ST_SnapToGrid(geom, 256, 256) AS cluster_geom, + geom + FROM points + ), + grouped_features AS ( + SELECT + tile_utils.cluster_expansion_zoom(ST_Collect(geom), :z) AS expansion_zoom, + count(*) AS n, + st_centroid(ST_Collect(geom)) AS geom, + CASE + WHEN count(*) < 2 THEN string_agg(f.id::text, ',') + ELSE null + END AS id + FROM mvt_features f + GROUP BY cluster_geom + ) + SELECT ST_AsMVT(row) AS mvt + FROM (SELECT * FROM grouped_features) AS row; + """ + + unclusteredSQL = """ + SELECT ST_AsMVT( + points.*, + 'default', + 4096, + 'geom' + ) AS mvt + FROM points + """ + + if cluster: + ending = clusterSQL + else: + ending = unclusteredSQL + + query = f""" + WITH + tile AS ( + SELECT ST_TileEnvelope(:z, :x, :y) AS envelope, + tile_layers.geographic_envelope(:x, :y, :z, 0.01) AS envelope_4326 + ), + points AS ( + SELECT + id, + type, + tile_layers.tile_geom( + ST_Intersection(geometry, envelope_4326), + envelope + ) AS geom + FROM macrostrat_api.measurements_with_type + JOIN tile ON true + WHERE + lat IS NOT NULL AND lng IS NOT NULL + AND ST_Intersects( + ST_SetSRID(ST_MakePoint(lng, lat), 4326), + envelope_4326 + ) + {where} + ) + {ending} + """ + + q, p = render(query, **params) + q = q.replace("textarray", "text[]") + + async with pool.acquire() as con: + data = await con.fetchval(q, *p) + + return Response(data, media_type=MimeTypes.pbf.value)