Building the Flask Application
This is where the magic happens. We are going to create a script that initializes Flask, connects to PostgreSQL, and defines a route that accepts {z}/{x}/{y} parameters to fetch the corresponding tile.
Create a new file named app.py and paste the following code:
import os
import psycopg2
from flask import Flask, Response, abort
app = Flask(__name__)
# Database connection parameters (Ideally, load these from a .env file)
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_NAME = os.getenv("DB_NAME", "your_database")
DB_USER = os.getenv("DB_USER", "postgres")
DB_PASS = os.getenv("DB_PASS", "password")
def get_db_connection():
return psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
@app.route('/tiles/<int:z>/<int:x>/<int:y>.pbf')
def get_tile(z, x, y):
# Here is our PostGIS magic from Lesson 1!
query = """
WITH bounds AS (
SELECT ST_TileEnvelope(%s, %s, %s) AS geom
),
mvtgeom AS (
SELECT
id,
name,
ST_AsMVTGeom(p.geom, bounds.geom) AS geom
FROM places p, bounds
WHERE ST_Intersects(p.geom, bounds.geom)
)
SELECT ST_AsMVT(mvtgeom, 'places') FROM mvtgeom;
"""
conn = None
try:
conn = get_db_connection()
with conn.cursor() as cur:
cur.execute(query, (z, x, y))
tile = cur.fetchone()[0]
# If no data is found for this tile, return a 404 Not Found
if not tile:
abort(404)
# Vector tiles MUST be returned as binary protocol buffers
return Response(bytes(tile), mimetype='application/vnd.mapbox-vector-tile')
except Exception as e:
print(f"Error generating tile: {e}")
abort(500)
finally:
if conn:
conn.close()
if __name__ == '__main__':
# Start the server on port 5000
app.run(debug=True, port=5000)
Read through the SQL query in the code carefully. Notice how it seamlessly strings together ST_TileEnvelope, ST_AsMVTGeom, and ST_AsMVT!