Created
May 14, 2025 15:43
-
-
Save darrenwiens/685465ec3467f3b635bb43ed0d7d23e9 to your computer and use it in GitHub Desktop.
A FastAPI that fetches relevant map tiles for given tile coordinates, and returns an image warped to a cube face
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from fastapi import FastAPI | |
from fastapi.responses import StreamingResponse | |
from fastapi.middleware.cors import CORSMiddleware | |
import cv2 | |
import numpy as np | |
import io | |
import os | |
import random | |
import requests | |
MAPTILER_KEY = os.getenv("MAPTILER_KEY") | |
app = FastAPI() | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["http://localhost:3000"], # Adjust this to your frontend URL | |
allow_credentials=True, | |
allow_methods=["*"], | |
allow_headers=["*"], | |
) | |
def combine(imgs, orientation): | |
if orientation == "h": | |
combined = np.concatenate((imgs[0], imgs[1], imgs[2]), axis=1) | |
else: | |
combined = np.concatenate((imgs[0], imgs[1], imgs[2]), axis=0) | |
return combined | |
def warp(image, face): | |
h, w = image.shape[:2] | |
h -= 1 | |
w -= 1 | |
if face == "f": | |
src_points = np.float32( | |
[ | |
[0, 0], # top-left | |
[w, 0], # top-right | |
[int(w / 3 * 2), h], # bottom-right | |
[int(w / 3), h], # bottom-left | |
] | |
) | |
elif face == "l": | |
src_points = np.float32( | |
[ | |
[0, 0], | |
[w, int(h / 3)], | |
[w, int((h / 3) * 2)], | |
[0, h], | |
] | |
) | |
elif face == "b": | |
src_points = np.float32( | |
[ | |
[int(w / 3), 0], | |
[int((w / 3) * 2), 0], | |
[w, h], | |
[0, h], | |
] | |
) | |
elif face == "r": | |
src_points = np.float32( | |
[ | |
[0, int(h / 3)], | |
[w, 0], | |
[w, h], | |
[0, int((h / 3) * 2)], | |
] | |
) | |
dst_size = 1024 | |
dst_points = np.float32( | |
[[0, 0], [dst_size, 0], [dst_size, dst_size], [0, dst_size]] | |
) | |
matrix = cv2.getPerspectiveTransform(src_points, dst_points) | |
return cv2.warpPerspective(image, matrix, (dst_size, dst_size)) | |
def generate_sky(): | |
img = np.zeros((256, 256), dtype=np.uint8) | |
num_stars = 100 | |
for _ in range(num_stars): | |
x = random.randint(0, 255) | |
y = random.randint(0, 255) | |
img[y, x] = random.randint(0, 255) | |
return img | |
def get_map_tile(x, y, z, f): | |
if f == "u": | |
image = generate_sky() | |
else: | |
url = f"https://api.maptiler.com/maps/streets-v2/{z}/{x}/{y}.png?key={MAPTILER_KEY}" | |
response = requests.get(url) | |
image_array = np.frombuffer(response.content, dtype=np.uint8) | |
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) | |
return image | |
@app.get("/tile/{z}/{x}/{y}/{f}") | |
def read_item(x: int, y: int, z: int, f: str): | |
center_tile_coord = [x, y, z] | |
tile_coords = { | |
"f": [ | |
[center_tile_coord[0] - 1, center_tile_coord[1] - 1, center_tile_coord[2]], | |
[center_tile_coord[0], center_tile_coord[1] - 1, center_tile_coord[2]], | |
[center_tile_coord[0] + 1, center_tile_coord[1] - 1, center_tile_coord[2]], | |
], | |
"l": [ | |
[center_tile_coord[0] - 1, center_tile_coord[1] - 1, center_tile_coord[2]], | |
[center_tile_coord[0] - 1, center_tile_coord[1], center_tile_coord[2]], | |
[center_tile_coord[0] - 1, center_tile_coord[1] + 1, center_tile_coord[2]], | |
], | |
"b": [ | |
[center_tile_coord[0] - 1, center_tile_coord[1] + 1, center_tile_coord[2]], | |
[center_tile_coord[0], center_tile_coord[1] + 1, center_tile_coord[2]], | |
[center_tile_coord[0] + 1, center_tile_coord[1] + 1, center_tile_coord[2]], | |
], | |
"r": [ | |
[center_tile_coord[0] + 1, center_tile_coord[1] - 1, center_tile_coord[2]], | |
[center_tile_coord[0] + 1, center_tile_coord[1], center_tile_coord[2]], | |
[center_tile_coord[0] + 1, center_tile_coord[1] + 1, center_tile_coord[2]], | |
], | |
"d": [center_tile_coord], | |
"u": [center_tile_coord], | |
} | |
imgs = [] | |
for i in tile_coords[f]: | |
imgs.append(get_map_tile(i[0], i[1], i[2], f)) | |
actions = { | |
"f": lambda: warp(combine(imgs, "h"), f), | |
"l": lambda: cv2.rotate(warp(combine(imgs, "v"), f), cv2.ROTATE_90_CLOCKWISE), | |
"b": lambda: cv2.rotate(warp(combine(imgs, "h"), f), cv2.ROTATE_180), | |
"r": lambda: cv2.rotate( | |
warp(combine(imgs, "v"), f), cv2.ROTATE_90_COUNTERCLOCKWISE | |
), | |
"u": lambda: imgs[0], | |
"d": lambda: imgs[0], | |
} | |
result_image = actions[f]() | |
success, encoded_image = cv2.imencode(".png", result_image) | |
if not success: | |
return {"error": "Image encoding failed"} | |
image_bytes = encoded_image.tobytes() | |
return StreamingResponse(io.BytesIO(image_bytes), media_type="image/png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment