aboutsummaryrefslogtreecommitdiffstats
path: root/helpers.py
blob: 64a3b0a8f5696a790a9ca0aae3d6f91b0d90f39c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import argparse
import math
from io import StringIO
from typing import Any, Dict, List, Optional, Tuple

import numpy as np
import pandas as pd
import requests
from geopy.geocoders import Nominatim
from geopy.location import Location
from tabulate import tabulate

from constants import ENDPOINT, HEADERS, SORT_KV


def get_location(address: str) -> tuple[float, float]:
    geolocator = Nominatim(user_agent="FuelNearMe")
    result = geolocator.geocode(address)
    if not isinstance(result, Location):
        raise ValueError(f"Failed to get location from address: '{address}'")
    return (result.latitude, result.longitude)


def get_latest_data() -> tuple[pd.DataFrame, Optional[str]]:
    response = requests.get(ENDPOINT, headers=HEADERS, timeout=10)
    response.raise_for_status()
    return pd.read_csv(StringIO(response.text)), response.headers.get("Last-Modified")


def filter_df(
    dframe: pd.DataFrame, arguments: argparse.Namespace, loc: Tuple[float, float]
) -> List[Dict[str, Any]]:

    def bounding_box() -> pd.DataFrame:
        lat, lon = loc
        deg_lat = arguments.radius / 69.0
        deg_lon = arguments.radius / (69.0 * math.cos(math.radians(lat)))
        return dframe[
            dframe["forecourts.location.latitude"].between(lat - deg_lat, lat + deg_lat)
            & dframe["forecourts.location.longitude"].between(
                lon - deg_lon, lon + deg_lon
            )
        ]

    def haversine_miles(lat2: np.ndarray, lon2: np.ndarray) -> np.ndarray:
        R = 3958.8
        lat1, lon1 = np.radians(loc[0]), np.radians(loc[1])
        lat2, lon2 = np.radians(lat2), np.radians(lon2)
        dlat = lat2 - lat1
        dlon = lon2 - lon1
        a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
        return R * 2 * np.arcsin(np.sqrt(a))

    def pence_to_pounds(col: pd.Series) -> pd.Series:
        return (col / 100).round(2).where(col.notna(), other="N/A")

    df = bounding_box().copy()

    df["distance"] = haversine_miles(
        df["forecourts.location.latitude"].to_numpy(),
        df["forecourts.location.longitude"].to_numpy(),
    ).round(1)

    df = df[df["distance"] < arguments.radius]

    df = df.assign(
        e5_price=pence_to_pounds(df["forecourts.fuel_price.E5"]),
        e10_price=pence_to_pounds(df["forecourts.fuel_price.E10"]),
        diesel_price=pence_to_pounds(df["forecourts.fuel_price.B7S"]),
    )

    return df.rename(columns={"forecourts.trading_name": "station_name"})[
        ["station_name", "distance", "e5_price", "e10_price", "diesel_price"]
    ].to_dict(orient="records")


def sort_stations(stations: list[dict], sort: str) -> list[dict]:
    sort_key = SORT_KV[sort]
    return sorted(stations, key=lambda d: d[sort_key] if d[sort_key] != "N/A" else 999)


def output_stations(stations: List[Dict[str, Any]]) -> None:
    if not stations:
        print("[*] No stations found.")
        return
    print(
        tabulate(
            stations,
            headers={
                "station_name": "Station Name",
                "distance": "Distance (miles)",
                "e5_price": "E5 (£/L)",
                "e10_price": "E10 (£/L)",
                "diesel_price": "B7S (£/L)",
            },
            floatfmt=".2f",
        )
    )
git.ajschof.me — hosted by ajschofield — powered by cgit