Skip to content

Commit

Permalink
Implement SCT RWY/AIRPORT parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
a3li committed Jul 11, 2024
1 parent 07671d1 commit b394bd6
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 5 deletions.
16 changes: 14 additions & 2 deletions mapbuilder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .cache import Cache
from .data.aixm2 import parse_aixm
from .data.kml import KMLParser
from .data.rwy import parse_runway
from .data.sidstar import parse_sidstar
from .dfs import aixm
from .handlers.jinja import JinjaHandler
Expand Down Expand Up @@ -34,7 +35,9 @@ def __init__(self, source_dir, target_dir, cache_dir, config):
data_root = None
if "root" in config["data"][data_source]:
data_root = config["data"][data_source]["root"]
parser = KMLParser(source_dir / config["data"][data_source]["source"], data_root)
parser = KMLParser(
source_dir / config["data"][data_source]["source"], data_root
)
self.data[data_source] = parser.parse()
elif data_source_type == "raw":
logging.debug(f"Loading raw source {data_source}...")
Expand All @@ -45,7 +48,16 @@ def __init__(self, source_dir, target_dir, cache_dir, config):
elif data_source_type == "ese":
logging.debug(f"Loading ESE source {data_source}...")
self.data[data_source] = {
"SIDSTAR": parse_sidstar(source_dir / config["data"][data_source]["source"]),
"SIDSTAR": parse_sidstar(
source_dir / config["data"][data_source]["source"]
),
}
elif data_source_type == "sct":
logging.debug(f"Loading SCT source {data_source}...")
self.data[data_source] = {
"RUNWAY": parse_runway(
source_dir / config["data"][data_source]["source"]
),
}
else:
logging.error(f"Unknown data source type for data source {data_source}")
Expand Down
90 changes: 90 additions & 0 deletions mapbuilder/data/rwy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import re
from pathlib import Path

from mapbuilder.utils import geo
from mapbuilder.utils.legacy import parse_es_coords


def parse_airport(file_path: Path) -> dict:
"""Parses Runway information from the SCT"""
pattern = r"^(\w{4})\s+(\d{3}\.\d{3})\s+(\S+)\s+(\S+)\s+(\w)$"

ads = {}
in_section = False

with file_path.open("r", encoding="iso-8859-1") as f:
for raw_line in f:
line = raw_line.strip()
if line.startswith(";"):
continue

if line == "[AIRPORT]":
in_section = True
continue

if not in_section:
continue

# Next section
if line.startswith("["):
break

match = re.search(pattern, line)

if match:
ads[match.group(1)] = geo.Fix(
parse_es_coords(match.group(3), match.group(4))
)

return ads


def parse_runway(file_path: Path) -> dict:
"""Parses Runway information from the SCT"""
pattern = r"^(\d{2}[LRC]?)\s+(\d{2}[LRC]?)\s+(\d{3})\s+(\d{3})\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\w{4})$"

ads = parse_airport(file_path)
runways = {}
in_section = False

with file_path.open("r", encoding="iso-8859-1") as f:
for raw_line in f:
line = raw_line.strip()
if line.startswith(";"):
continue

if line == "[RUNWAY]":
in_section = True
continue

if not in_section:
continue

# Next section
if line.startswith("["):
break

match = re.search(pattern, line)

if match:
rwy1 = match.group(1)
rwy2 = match.group(2)
rwystr = f"{rwy1}-{rwy2}"
icao = match.group(9)
thr1 = geo.Fix(parse_es_coords(match.group(5), match.group(6)))
thr2 = geo.Fix(parse_es_coords(match.group(7), match.group(8)))

if icao not in runways:
runways[icao] = {}

runways[icao][rwystr] = {
"rwy1": rwy1,
"rwy2": rwy2,
"brg1": int(match.group(3)),
"brg2": int(match.group(4)),
"thr1": thr1,
"thr2": thr2,
"center": ads[icao],
}

return runways
12 changes: 9 additions & 3 deletions mapbuilder/handlers/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from shapely import Geometry, Polygon

from mapbuilder.data.aixm2 import AIXMFeature
from mapbuilder.utils.ad import render_cl, render_runways
from mapbuilder.utils.ecl import draw_ecl_dashes, draw_loc_tick, draw_marker_ticks
from mapbuilder.utils.geo import brg, fix
from mapbuilder.utils.sidstar import render_sid
Expand All @@ -32,6 +33,8 @@ def handle(self, item: Path) -> str:
render_sid=render_sid,
fix=fix,
brg=brg,
render_runways=render_runways,
render_cl=render_cl,
)
jinja_env.filters.update(
geoms=geoms,
Expand Down Expand Up @@ -170,7 +173,7 @@ def envelope(geometries):
return shapely.envelope(geometries)


def to_poly(geometries, designator: str, color: str, coordpoly=False):
def to_poly(geometries, designator: str, color: str | None = None, coordpoly=False):
lines = [f"// {designator}"] if designator else []

_render_polygon(lines, _get_geoms(geometries), color)
Expand All @@ -181,9 +184,12 @@ def to_poly(geometries, designator: str, color: str, coordpoly=False):
return "\n".join(lines)


def _render_polygon(lines, polygons, color):
def _render_polygon(lines, polygons, color=None):
for polygon in polygons:
lines.append(f"COLOR:{color}")
if color is not None:
lines.append(f"COLOR:{color}")
else:
lines.append("")

for point in polygon.coords:
lines.append(f"COORD:{coord2es((point[0], point[1]))}")
Expand Down
24 changes: 24 additions & 0 deletions mapbuilder/utils/ad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
def render_runways(ad: dict, length: float = 1.5, exclude: list | None = None) -> str:
if exclude is None:
exclude = []
lines = []

for rwy_id, data in ad.items():
if rwy_id in exclude:
continue

if data["brg1"] == 0 and data["brg2"] == 0:
continue

lines.append(str(data["center"].move_to(length / 2, data["brg2"]).line_to(length, data["brg1"])))

return "\n".join(lines)


def render_cl(ad: dict) -> str:
lines = []

for _, data in ad.items():
lines.append(str(data["thr1"].line_to_fix(data["thr2"])))

return "\n".join(lines)
21 changes: 21 additions & 0 deletions mapbuilder/utils/legacy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
def parse_es_coords(lat_str, lon_str):
def convert_to_decimal(degree, minute, second, direction):
decimal = degree + minute / 60 + second / 3600
if direction in ["S", "W"]:
decimal = -decimal
return decimal

lat_deg = int(lat_str[1:4])
lat_min = int(lat_str[5:7])
lat_sec = float(lat_str[8:13])
lat_dir = lat_str[0]

lon_deg = int(lon_str[1:4])
lon_min = int(lon_str[5:7])
lon_sec = float(lon_str[8:13])
lon_dir = lon_str[0]

latitude = convert_to_decimal(lat_deg, lat_min, lat_sec, lat_dir)
longitude = convert_to_decimal(lon_deg, lon_min, lon_sec, lon_dir)

return latitude, longitude

0 comments on commit b394bd6

Please sign in to comment.