diff --git a/.gitignore b/.gitignore index a6f12ec..6c1bb16 100644 --- a/.gitignore +++ b/.gitignore @@ -519,3 +519,7 @@ tags .history # End of https://www.gitignore.io/api/vim,emacs,android,pycharm+all,androidstudio,visualstudiocode,python,java,angular + + +# Custom +requests_cache.sqlite diff --git a/server/nightr/app.py b/server/nightr/app.py index 6d65d1c..cf488fe 100644 --- a/server/nightr/app.py +++ b/server/nightr/app.py @@ -1,4 +1,5 @@ import inspect +import logging import statistics from dataclasses import asdict from datetime import timedelta @@ -7,21 +8,24 @@ from typing import List import requests_cache from flask import Flask, jsonify -from .strategies import dmi, steam, miloStrats +from .strategies import miloStrats, iss, cars_in_traffic, tide_strat, upstairs_neighbour from .util import Context +logger = logging.getLogger(__name__) app = Flask(__name__) -requests_cache.install_cache("requests_cache.sqlite", expire_after=timedelta(minutes=10)) +requests_cache.install_cache("requests_cache", expire_after=timedelta(minutes=10)) strategies = { # name: (weight, probability function) - "dmi": (0.5, dmi.probability), - "steam": (1.0, steam.probability), - "australia": (1.0, miloStrats.australiaStrat), - "camera": (1.0, miloStrats.camImgStrat), - "tv2news": (1.0, miloStrats.tv2newsStrat) + "tv2news": miloStrats.tv2newsStrat, + "australia": miloStrats.australiaStrat, + "camera": miloStrats.camImgStrat, + "iss": iss.night_on_iss, + "cars_in_traffic": cars_in_traffic.cars_in_traffic, + "tide": tide_strat.is_tide, + "upstairs_neighbour": upstairs_neighbour.check_games, } @@ -31,17 +35,18 @@ def probabilities(): context = Context(**phone_data) predictions: List[dict] = [] - for name, (weight, strategy) in strategies.items(): + for name, strategy in strategies.items(): try: prediction = strategy(context) except Exception as e: - print(f"Strategy {name} failed: {e}") + logger.warning("Strategy %s failed: %s", name, e) + logger.exception(e) continue predictions.append({ "name": name, "description": inspect.getdoc(strategy), - "weight": weight, - "weighted_probability": prediction.probability * weight, + "weight": prediction.weight, + "weighted_probability": prediction.probability * prediction.weight, "night": prediction.probability > 0.5, **asdict(prediction), }) diff --git a/server/nightr/strategies/dmi.py b/server/nightr/strategies/dmi.py deleted file mode 100644 index 7b2f431..0000000 --- a/server/nightr/strategies/dmi.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..util import Context, Prediction - - -def probability(context: Context) -> Prediction: - """ - The data from DMI. - """ - p = Prediction() - p.probability = 0.7 - p.reasons.append("It is raining in Tønder") - - return p diff --git a/server/nightr/strategies/iss.py b/server/nightr/strategies/iss.py new file mode 100644 index 0000000..17ed941 --- /dev/null +++ b/server/nightr/strategies/iss.py @@ -0,0 +1,89 @@ +import itertools +import logging +from datetime import datetime +from math import pi, sqrt, sin, cos, atan2 + +import pytz +import requests +from timezonefinder import TimezoneFinder + +from ..util import Context, Prediction + +logger = logging.getLogger(__name__) +tf = TimezoneFinder(in_memory=True) + + +def night_on_iss(context: Context) -> Prediction: + """ + It is night if it is night on the ISS and it is currently orbiting above us. + """ + p = Prediction() + + if not context.flat_earth: + iss_position = requests.get("http://api.open-notify.org/iss-now.json").json()["iss_position"] + the_iss = "The ISS" + iss_position_description = "on board the ISS" + else: + p.reasons.append("The ISS is (obviously) located in Hollywood") + the_iss = "Hollywood" + iss_position = {'latitude': 34.092808, 'longitude': -118.328659} # Hollywood + iss_position_description = "in the Hollywood studio" + + phone_position = context.position + + # Calculate ratio: a number between 0 and 1 saying how close we are to the ISS + distance = haversine(iss_position, phone_position) + max_distance = 40075 / 2 # the furthest you can be from any position is half of the earth's circumference + ratio = distance / max_distance + + # We're in the same "timezone" as the ISS if we're on the same half of the earth + on_iss_time = ratio < 0.5 + + side = "same" if on_iss_time else "other" + p.reasons.append(f"{the_iss} is {int(distance)} km away, so we are on the {side} side of the earth.") + for i in itertools.count(1): + iss_tz = tf.closest_timezone_at(lng=float(iss_position["longitude"]), + lat=float(iss_position["latitude"]), + delta_degree=i) + if iss_tz is not None: + break + iss_time = datetime.now(pytz.timezone(iss_tz)) + + iss_night = 6 < iss_time.hour > 22 + + # iss_night on_iss_time night + # 0 0 1 + # 0 1 0 + # 1 0 0 + # 1 1 1 + night = iss_night == on_iss_time + + iss_time_description = "nighttime" if iss_night else "daytime" + time_description = "nighttime" if night else "daytime" + p.probability = float(night) + p.reasons.append(f"It is {iss_time_description} {iss_position_description}.") + p.reasons.append(f"Therefore, it must be {time_description} where we are.") + + return p + + +def haversine(pos1, pos2): + """ + Distance between two GPS coordinates. + https://stackoverflow.com/a/18144531 + """ + lat1 = float(pos1["latitude"]) + long1 = float(pos1["longitude"]) + lat2 = float(pos2["latitude"]) + long2 = float(pos2["longitude"]) + + degree_to_rad = float(pi / 180.0) + + d_lat = (lat2 - lat1) * degree_to_rad + d_long = (long2 - long1) * degree_to_rad + + a = pow(sin(d_lat / 2), 2) + cos(lat1 * degree_to_rad) * cos(lat2 * degree_to_rad) * pow(sin(d_long / 2), 2) + c = 2 * atan2(sqrt(a), sqrt(1 - a)) + km = 6367 * c + + return km diff --git a/server/nightr/strategies/miloStrats.py b/server/nightr/strategies/miloStrats.py index 49875e6..be105e5 100644 --- a/server/nightr/strategies/miloStrats.py +++ b/server/nightr/strategies/miloStrats.py @@ -1,18 +1,18 @@ from datetime import datetime +from pathlib import Path import requests import cv2 from pytz import timezone from ..util import Context, Prediction -#from server.nightr.util import Context, Prediction def camImgStrat(context : Context) -> Prediction: """ The contents of the camera image """ - img = cv2.imread('night.jpg',0) + img = cv2.imread(str(Path(__file__).parent.joinpath("night.jpg")), 0) average = img.mean(axis=0).mean(axis=0) print(average) p = Prediction() diff --git a/server/nightr/strategies/steam.py b/server/nightr/strategies/steam.py deleted file mode 100644 index 814ae4f..0000000 --- a/server/nightr/strategies/steam.py +++ /dev/null @@ -1,12 +0,0 @@ -from ..util import Context, Prediction - - -def probability(context: Context) -> Prediction: - """ - How many players are currently online on Steam. - """ - p = Prediction() - p.probability = 0.2 - p.reasons.append("CSGO has more than 10.000 online players") - - return p diff --git a/server/nightr/util.py b/server/nightr/util.py index db52a0a..5b2bc3c 100644 --- a/server/nightr/util.py +++ b/server/nightr/util.py @@ -15,4 +15,5 @@ class Context: @dataclass class Prediction: probability: float = 0.5 + weight: float = 1.0 reasons: List[str] = field(default_factory=list) diff --git a/server/requirements.txt b/server/requirements.txt index 776c8a0..a900813 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,7 +1,11 @@ -Flask==1.0.2 -requests==2.21.0 -requests-cache==0.4.13 +Flask +requests +requests-cache pytz beautifulsoup4 pandas opencv-python +timezonefinder +scikit-learn +html5lib +xlrd