Skip to content

Commit

Permalink
add hbl
Browse files Browse the repository at this point in the history
  • Loading branch information
iiiii7d committed Jun 7, 2024
1 parent b5d9c46 commit 6c0ee52
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from gatelogue_aggregator.sources.rail.wiki_mrt import WikiMRT
from gatelogue_aggregator.sources.sea.aqualinq import AquaLinQ
from gatelogue_aggregator.sources.sea.aqualinq_warp import AquaLinQWarp
from gatelogue_aggregator.sources.sea.hbl import HBL
from gatelogue_aggregator.sources.sea.hbl_warp import HBLWarp
from gatelogue_aggregator.types.context import Context


Expand Down Expand Up @@ -58,9 +60,11 @@ def run(*, cache_dir: Path, timeout: int, output: Path, fmt: bool, graph: Path |
DynmapMRT,
AquaLinQ,
AquaLinQWarp,
HBL,
HBLWarp,
]
with ThreadPoolExecutor(max_workers=max_workers) as executor:
result = executor.map(lambda s: s(cache_dir, timeout), sources)
result = [s for s in executor.map(lambda s: s(cache_dir, timeout), sources)]
ctx = Context.from_sources(result)
if graph is not None:
ctx.graph(graph)
Expand Down
9 changes: 5 additions & 4 deletions gatelogue-aggregator/src/gatelogue_aggregator/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

import rich.progress

PROGRESS: rich.progress.Progress = rich.progress.Progress(transient=True)
PROGRESS: rich.progress.Progress = rich.progress.Progress()
PROGRESS.start()

INFO1 = "[yellow]"
INFO2 = "[green] "
INFO3 = "[dim green] "
RESULT = "[cyan] "
ERROR = "[red on white]"
ERROR = "[bold red]"


def track[T](it: Iterable[T], *, description: str, nonlinear: bool = False) -> Iterable[T]:
def track[T](it: Iterable[T], *, description: str, nonlinear: bool = False, remove: bool = True) -> Iterable[T]:
total = ((len(it) ** 2) / 2 if nonlinear else len(it)) if isinstance(it, Sized) else None
t = PROGRESS.add_task(description, total=total)
for i, o in enumerate(it):
yield o
PROGRESS.advance(t, i + 1 if nonlinear else 1)
PROGRESS.remove_task(t)
if remove:
PROGRESS.remove_task(t)
44 changes: 44 additions & 0 deletions gatelogue-aggregator/src/gatelogue_aggregator/sources/sea/hbl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import re
from pathlib import Path

import rich

from gatelogue_aggregator.downloader import DEFAULT_CACHE_DIR, DEFAULT_TIMEOUT
from gatelogue_aggregator.logging import RESULT
from gatelogue_aggregator.sources.wiki_base import get_wiki_html
from gatelogue_aggregator.types.base import Source
from gatelogue_aggregator.types.node.sea import SeaSource, SeaContext, SeaLineBuilder


class HBL(SeaSource):
name = "MRT Wiki (Sea, Hummingbird Boat Lines)"
priority = 0

def __init__(self, cache_dir: Path = DEFAULT_CACHE_DIR, timeout: int = DEFAULT_TIMEOUT):
SeaContext.__init__(self)
Source.__init__(self)

company = self.sea_company(name="Hummingbird Boat Lines")

html = get_wiki_html("Hummingbird Boat Lines", cache_dir, timeout)
for td in html.find("table", class_="multicol").find_all("td"):
for p, ul in zip(td.find_all("p"), td.find_all("ul"), strict=False):
line_code = str(p.span.string or p.span.span.string).strip()
line_name = str(p.b.string).strip()
line_colour = re.match(r"background-color:\s*([^;]*)", p.span.attrs["style"]).group(1)
line = self.sea_line(code=line_code, company=company, name=line_name, colour=line_colour)

stops = []
for li in ul.find_all("li"):
if "Planned" in li.strings:
continue
stop_name = "".join(li.strings)
stop = self.sea_stop(codes={stop_name}, name=stop_name, company=company)
stops.append(stop)

if len(stops) == 0:
continue

SeaLineBuilder(self, line).matrix(*stops)

rich.print(RESULT + f"HBL Line {line_code} has {len(stops)} stops")
162 changes: 162 additions & 0 deletions gatelogue-aggregator/src/gatelogue_aggregator/sources/sea/hbl_warp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import itertools
import re
import uuid
from pathlib import Path

import pandas as pd
import rich

from gatelogue_aggregator.downloader import DEFAULT_CACHE_DIR, DEFAULT_TIMEOUT, get_url, warps
from gatelogue_aggregator.logging import ERROR
from gatelogue_aggregator.types.base import Source
from gatelogue_aggregator.types.node.sea import SeaContext, SeaSource

# Adapted from https://docs.google.com/spreadsheets/d/1nIIettVbGwzm7DkmYqqPVoguw2U53R5un4nrC76w-Xg/edit#gid=1423194214
_DICT = {
"ACH": "Achowalogen Takachsin",
"ACR": "New Acreadium",
"AIR": "Airchester",
"ANT": "Anthro Island",
"AQI": "Aquidneck Islands",
"AXB": "Alexandriasburg",
"BAK": "Bakersville",
"BAY": "Bay Point",
"BEA": "Beach City",
"BIW": "Biwabik",
"BVW": "Beachview",
"CEL": "Celina",
"CHG": "Chugsdy Island",
"COV": "Covina",
"CPC": "Cape Cambridge",
"DEA": "Deadbush",
"EDN": "Eden",
"ELC": "Elecna Crescent",
"ELE": "Elecna Bay",
"ELF": "Ellerton Fosby",
"EMS": "East Mesa",
"ENB": "East New Brazil",
"ENS": "Enspington",
"FYX": "Fort Yaxier",
"GAD": "Gorre & Daphetid",
"GEN": "Geneva Bay",
"GIL": "Gillmont",
"GRY": "Gray Cloud",
"HEA": "Heapstead",
"HUM": "Hummingbird Islands",
"ILI": "Ilirea",
"INS": "Insula Montes",
"IOC": "Isle of Chez",
"ITK": "Itokani",
"JIM": "Jimbo",
"KAN": "Kanto",
"KAP": "Kappen",
"KAZ": "Kazeshima",
"KEN": "Kenthurst",
"KLE": "Kleinsburg",
"LAC": "Lacklede",
"LAP": "Lapis Bay",
"LEG": "Lego City",
"LEV": "Levittown",
"LOS": "Los Angeles",
"M10": "Mole Isle",
"MAL": "Malosa",
"MAR": "MRT Marina",
"MAS": "Mason City",
"MCK": "New Mackinaw",
"MNI": "Monte Isola",
"MOA": "Moramoa",
"MOL": "Mole Island",
"MOR": "Morihama",
"MSL": "Marisol",
"MTM": "Metamesa",
"MUR": "Murrville",
"NBK": "New Bakersville City",
"NSG": "New Singapore",
"ONE": "Onemalu",
"OPA": "Oparia",
"OTT": "Ottia Islands",
"PIX": "Pixl",
"PRA": "Praimina",
"PSM": "Port Smith",
"RIS": "Risima",
"RIZ": "Rizalburg",
"RJN": "Richard's Junction",
"ROK": "Roke",
"ROS": "New Rosemont",
"RRI": "Railroad Isle",
"SAN": "Sansmore",
"SCH": "Schillerton",
"SEA": "Seaview",
"SEC": "Secunda",
"SEH": "Seolho",
"SEO": "Seoland",
"SEU": "Seuland",
"SHA": "Shahai",
"SHN": "Shenghua",
"SND": "Sand",
"SOI": "Soiled Solitude",
"SPL": "Spleef Island",
"STA": "St. Anna",
"STO": "Stoneedge",
"SUN": "Sunshine Coast",
"SVK": "Sansvikk",
"SVZ": "San Vincenzo",
"TIT": "Titsensaki",
"TUL": "Tulipsburg",
"TWE": "Tweebuffel",
"VDM": "Verdantium",
"VEN": "Ventura Harbor",
"VER": "Vermilion",
"VIC": "Victoria",
"WAV": "Waverly",
"WEN": "Wenyanga",
"WEZ": "Weezerville",
"WHI": "Whitechapel",
"WHY": "Whiteley",
"ZAQ": "Zaquar",
}


class HBLWarp(SeaSource):
name = "MRT Warp API (Sea, Hummingbird Boat Lines)"
priority = 1

def __init__(self, cache_dir: Path = DEFAULT_CACHE_DIR, timeout: int = DEFAULT_TIMEOUT):
SeaContext.__init__(self)
Source.__init__(self)

company = self.sea_company(name="Hummingbird Boat Lines")

names = list(_DICT.values())
for warp in itertools.chain(
warps(uuid.UUID("c04532bc-45d7-4d89-a13f-1d3bb4b48f2a"), cache_dir, timeout),
warps(uuid.UUID("8a928931-aa14-4a1c-8a39-0a7630922001"), cache_dir, timeout),
):
if (result := re.match(r"HBL_(...)_(.*)", warp["name"])) is None:
continue
if (name := _DICT.get(result.group(1))) is None:
rich.print(ERROR + f"Unknown warp {warp['name']}")
continue
if name not in ("Covnia", "Kenthurst") and name not in names:
continue
if name == "Covina":
if result.group(2) in ("1", "10"):
name += " (marina)"
elif result.group(2) in ("55", "57", "58"):
name += " (canal)"
elif name == "Kenthurst":
if result.group(2) == "9":
name += " (west)"
elif result.group(2) in ("22", "24", "51", "55"):
name += " (north)"

self.sea_stop(codes={name}, company=company, name=name, world="New", coordinates=(warp["x"], warp["z"]))
try:
names.remove(name)
except ValueError:
pass

names.remove("Covina")
names.remove("Kenthurst")
if names:
rich.print(ERROR + f"Not found: {', '.join(names)}")
10 changes: 6 additions & 4 deletions gatelogue-aggregator/src/gatelogue_aggregator/types/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,22 @@ class Context(AirContext, RailContext, SeaContext, ToSerializable):
@classmethod
def from_sources(cls, sources: Iterable[AirSource | RailSource | SeaSource]) -> Self:
self = cls()
for source in track(sources, description=INFO1 + f"Merging sources: {', '.join(s.name for s in sources)}"):
for source in track(
sources, description=INFO1 + f"Merging sources: {', '.join(s.name for s in sources)}", remove=False
):
self.g = nx.compose(self.g, source.g)

processed: dict[type[Node], dict[str, list[Node]]] = {}
to_merge = []
for n in track(self.g.nodes, description=INFO2 + "Finding equivalent nodes", nonlinear=True):
for n in track(self.g.nodes, description=INFO2 + "Finding equivalent nodes", nonlinear=True, remove=False):
key = n.merge_key(self)
ty = type(n)
filtered_processed = processed.get(ty, {}).get(key, [])
if (equiv := next((a for a in filtered_processed if n.equivalent(self, a)), None)) is None:
processed.setdefault(ty, {}).setdefault(key, []).append(n)
continue
to_merge.append((equiv, n))
for equiv, n in track(to_merge, description=INFO2 + "Merging equivalent nodes"):
for equiv, n in track(to_merge, description=INFO2 + "Merging equivalent nodes", remove=False):
equiv.merge(self, n)
self.update()
return self
Expand All @@ -54,7 +56,7 @@ def dist_cmp(a: tuple[int, int], b: tuple[int, int], thres_sq: int) -> bool:
return (x1 - x2) ** 2 + (y1 - y2) ** 2 <= thres_sq

processed = []
for node in track(self.g.nodes, description=INFO1 + "Linking close nodes", nonlinear=True):
for node in track(self.g.nodes, description=INFO1 + "Linking close nodes", nonlinear=True, remove=False):
if not isinstance(node, LocatedNode) or (node_coordinates := node.merged_attr(self, "coordinates")) is None:
continue
node_coordinates = node_coordinates.v
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,32 @@ def circle(
self.connect(
*stations, stations[0], forward_label=forward_label, backward_label=backward_label, one_way=one_way
)

def matrix(self, *stations: S):
if len(stations) == 0:
return
self.line.connect_one(self.ctx, stations[0])
for s1, s2 in itertools.combinations(stations, 2):
forward_label = "towards " + (
a.v
if (a := s2.merged_attr(self.ctx, "name")) is not None
else next(iter(s2.merged_attr(self.ctx, "codes")))
)
backward_label = "towards " + (
a.v
if (a := s1.merged_attr(self.ctx, "name")) is not None
else next(iter(s1.merged_attr(self.ctx, "codes")))
)
s1.connect(
self.ctx,
s2,
value=type(self).CnT(
self.ctx,
line=self.line,
direction=Direction(
forward_towards_code=next(iter(s2.merged_attr(self.ctx, "codes", set))),
forward_direction_label=forward_label,
backward_direction_label=backward_label,
),
),
)

0 comments on commit 6c0ee52

Please sign in to comment.