Skip to content

Commit

Permalink
Merge pull request #33 from flightaware/proxy-map-request
Browse files Browse the repository at this point in the history
Add endpoint that returns flight map image
  • Loading branch information
NasaGeek authored Jan 25, 2022
2 parents 23dc95b + 1df7989 commit c4a9f55
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 24 deletions.
3 changes: 2 additions & 1 deletion fids/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM python:3.8-slim-bullseye
LABEL org.opencontainers.image.source=https://github.com/flightaware/firestarter

RUN apt-get update && \
apt-get install -y libpq-dev gcc npm make
apt-get install -y libpq-dev make
RUN id -u firestarter || useradd -u 8081 firestarter -c "FIRESTARTER User" -m -s /bin/sh
USER firestarter
WORKDIR /home/firestarter
Expand All @@ -25,5 +25,6 @@ ENV VIRTUAL_ENV=./venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY --chown=firestarter fids/app.py .
COPY --chown=firestarter fids/trig.py .

CMD ["python3", "app.py"]
53 changes: 43 additions & 10 deletions fids/app.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"""Read flight information from database and display it on a webpage"""

from base64 import b64encode
from datetime import datetime, timezone
import os
import time
from typing import Optional
from typing import Optional, Iterable
from flask import Flask, request, jsonify, abort, Response
import requests
import sqlalchemy as sa # type: ignore
from sqlalchemy.sql import union, select, func, and_, or_ # type: ignore
from sqlalchemy.sql.expression import text # type: ignore

import trig

# pylint: disable=invalid-name
flights_engine = sa.create_engine(os.environ["FLIGHTS_DB_URL"], echo=True)
flights_meta = sa.MetaData()
Expand Down Expand Up @@ -44,13 +48,20 @@
UTC = timezone.utc


def _get_positions(flight_id: str) -> Iterable:
return (
positions_engine.execute(
positions.select().where(positions.c.id == flight_id).order_by(positions.c.time.desc())
)
or []
)


@app.route("/positions/<flight_id>")
def get_positions(flight_id: str) -> dict:
def get_positions(flight_id: str) -> Response:
"""Get positions for a specific flight_id"""
result = positions_engine.execute(
positions.select().where(positions.c.id == flight_id).order_by(positions.c.time.desc())
)
if result is None:
result = _get_positions(flight_id)
if not result:
abort(404)
return jsonify([dict(e) for e in result])

Expand Down Expand Up @@ -222,9 +233,31 @@ def airport_scheduled(airport: str) -> Response:
return jsonify([dict(e) for e in result])


@app.route("/mapskey")
def get_map_api_key() -> Response:
"""Get the google maps api key"""
return google_maps_api_key
@app.route("/map/<flight_id>")
def get_map(flight_id: str) -> bytes:
"""Get a static map image of the specified flight. Returned as a
base64-encoded image"""
positions = list(_get_positions(flight_id))
if not positions:
abort(404)
bearing = 0
if len(positions) > 1:
coord1 = (float(positions[1].latitude), float(positions[1].longitude))
coord2 = (float(positions[0].latitude), float(positions[0].longitude))
bearing = trig.get_cardinal_for_angle(trig.get_bearing_degrees(coord1, coord2))
coords = "|".join(f"{pos.latitude},{pos.longitude}" for pos in positions)

google_maps_url = "https://maps.googleapis.com/maps/api/staticmap"
google_maps_params = {
"size": "640x400",
"markers": f"anchor:center|icon:https://github.com/flightaware/fids_frontend/raw/master/images/aircraft_{bearing}.png|{positions[0].latitude},{positions[0].longitude}",
"path": f"color:0x0000ff|weight:5|{coords}",
"key": google_maps_api_key,
}
response = requests.get(google_maps_url, google_maps_params)
response.raise_for_status()
image = b64encode(response.content)
return image


app.run(host="0.0.0.0", port=5000)
1 change: 1 addition & 0 deletions fids/requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ sqlalchemy==1.3.16
Flask==1.1.2
flask-cors==3.0.9
psycopg2-binary==2.8.5
requests
44 changes: 31 additions & 13 deletions fids/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,37 @@
#
# pip-compile --generate-hashes --output-file=base.txt base.in
#
certifi==2021.10.8 \
--hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
--hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569 \
# via requests
charset-normalizer==2.0.10 \
--hash=sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd \
--hash=sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455 \
# via requests
click==7.1.2 \
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
# via flask
flask-cors==3.0.9 \
--hash=sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8 \
--hash=sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324
--hash=sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324 \
# via -r base.in
flask==1.1.2 \
--hash=sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060 \
--hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557
# via
# -r base.in
# flask-cors
--hash=sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557 \
# via -r base.in, flask-cors
idna==3.3 \
--hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
--hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d \
# via requests
itsdangerous==1.1.0 \
--hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 \
--hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749
--hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \
# via flask
jinja2==2.11.3 \
--hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \
--hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6
--hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6 \
# via flask
markupsafe==1.1.1 \
--hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
Expand Down Expand Up @@ -78,7 +88,7 @@ markupsafe==1.1.1 \
--hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \
--hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
--hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \
--hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621
--hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \
# via jinja2
psycopg2-binary==2.8.5 \
--hash=sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac \
Expand Down Expand Up @@ -110,11 +120,15 @@ psycopg2-binary==2.8.5 \
--hash=sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d \
--hash=sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162 \
--hash=sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e \
--hash=sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd
--hash=sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd \
# via -r base.in
requests==2.27.1 \
--hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \
--hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d \
# via -r base.in
six==1.15.0 \
--hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \
# via flask-cors
sqlalchemy==1.3.16 \
--hash=sha256:083e383a1dca8384d0ea6378bd182d83c600ed4ff4ec8247d3b2442cf70db1ad \
Expand All @@ -135,9 +149,13 @@ sqlalchemy==1.3.16 \
--hash=sha256:8d8c21e9d4efef01351bf28513648ceb988031be4159745a7ad1b3e28c8ff68a \
--hash=sha256:bbb545da054e6297242a1bb1ba88e7a8ffb679f518258d66798ec712b82e4e07 \
--hash=sha256:d00b393f05dbd4ecd65c989b7f5a81110eae4baea7a6a4cdd94c20a908d1456e \
--hash=sha256:e18752cecaef61031252ca72031d4d6247b3212ebb84748fc5d1a0d2029c23ea
--hash=sha256:e18752cecaef61031252ca72031d4d6247b3212ebb84748fc5d1a0d2029c23ea \
# via -r base.in
urllib3==1.26.8 \
--hash=sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed \
--hash=sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c \
# via requests
werkzeug==1.0.1 \
--hash=sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43 \
--hash=sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c
--hash=sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c \
# via flask
28 changes: 28 additions & 0 deletions fids/trig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from math import degrees, radians, sin, cos, atan2
from typing import Tuple


def get_bearing_degrees(coord1: Tuple[float, float], coord2: Tuple[float, float]) -> float:
"""Get bearing in degrees between 2 sets of coordinates"""
r_lat1 = radians(coord1[0])
r_lon1 = radians(coord1[1])
r_lat2 = radians(coord2[0])
r_lon2 = radians(coord2[1])
d_lon = r_lon2 - r_lon1
y = sin(d_lon) * cos(r_lat2)
x = cos(r_lat1) * sin(r_lat2) - sin(r_lat1) * cos(r_lat2) * cos(d_lon)
bearing = degrees(atan2(y, x))
bearing = (bearing + 360) % 360
return bearing


def get_cardinal_for_angle(angle_degrees: float) -> int:
"""Approximate an angle in degrees to 1 of 4 cardinal directions"""
if 45 <= angle_degrees < 135:
return 90
elif 135 <= angle_degrees < 225:
return 180
elif 225 <= angle_degrees < 315:
return 270
else:
return 0

0 comments on commit c4a9f55

Please sign in to comment.