Source code for classrooms

import json
import time
from typing import Dict, Iterable, Union

import pandas as pd
from geopy.geocoders import Nominatim

import backend.resources as rsrc


[docs]class Address: """ Address object made from informations retrieved via ADE API. :param kwargs: dict with minimal entries (can be None): - address1 - zipCode - city - country :type kwargs: str :Example: >>> informations = dict(address1='Rue Rose 42', zipCode='1300', city='Wavre', country='Belgique') >>> address = Address(**informations) """ def __init__(self, **kwargs: str): self.address = kwargs def __repr__(self) -> str: return str(self) def __str__(self) -> str: location = "\n".join( filter( None, [ self.address[rsrc.INDEX.ADDRESS], self.address[rsrc.INDEX.ZIP_CODE], self.address[rsrc.INDEX.CITY], self.address[rsrc.INDEX.COUNTRY], ], ) ) return location
[docs]def get_geo_locations() -> Dict: """ Returns the dictionary mapping each address to its geo-location. :return: the geo-locations :rtype: Dict """ with open("static/json/geo_locations.json", "r") as f: return json.load(f)
[docs]def save_geo_locations(geo_locations: Dict): """ Saves the dictionary of geo-locations into a json file with pretty indent. This file can be manually edited without causing any problem. :param geo_locations: the geo-locations :type geo_locations: Dict """ with open("static/json/geo_locations.json", "w") as f: json.dump(geo_locations, f, sort_keys=True, indent=4) f.write("\n")
[docs]def prettify_classrooms(classrooms: pd.DataFrame, sleep: float = 0) -> pd.DataFrame: """ Returns the classrooms dataframe in a pretty format, useful when need to display. The function will request, for every different address, a geo-localisation, so it can take some times. If too many requests are done, Nominatim will not like it so prefer to put a time.sleep between each request. :param classrooms: the classrooms with fields defined in backend.resources.py :type classrooms: pd.DataFrame :param sleep: the sleep duration between each address request :type sleep: float :return: the classrooms in a prettier format and with geo-location information :rtype: pd.DataFrame """ geolocator = Nominatim(user_agent="ADE_SCHEDULER") geo_locations = get_geo_locations() def __pretty__(classroom: pd.Series): address = Address(**classroom.to_dict()) location = str(address).replace("\n", ", ") name = classroom[rsrc.INDEX.NAME] code = classroom[rsrc.INDEX.CODE] return pd.Series([name, code, location], index=["name", "code", "address"]) def __geoloc__(classroom: pd.Series): name = classroom["name"] code = classroom["code"] address = classroom["address"] geo_location = geo_locations[address] if geo_location is None: latitude = None longitude = None else: latitude = geo_location["lat"] longitude = geo_location["lon"] return pd.Series( [name, code, address, latitude, longitude], index=["name", "code", "address", "latitude", "longitude"], dtype=object, ) classrooms = classrooms.apply(__pretty__, axis=1, result_type="expand") for address in classrooms["address"].unique(): if address not in geo_locations: response = geolocator.geocode(address, exactly_one=True) time.sleep(sleep) if response is not None: geo_locations[address] = response.raw else: geo_locations[address] = None save_geo_locations(geo_locations) return classrooms.apply(__geoloc__, axis=1, result_type="expand")
[docs]class Classroom: """ Classroom object containing the address (as string or Address object), the name of the classroom and its id. :param kwargs: dict with minimal entries: - address - name - id :type kwargs: Union[str, Address] Its main purpose is be used with :func:`location`. """ def __init__(self, **kwargs: Union[str, Address]): self.infos = kwargs def __str__(self) -> str: return str(self.infos["name"]) + "\n" + str(self.infos["address"]) def __hash__(self): return hash(self.infos) def __eq__(self, other): return self.infos == other.infos def __repr__(self) -> str: id = self.infos["id"] name = self.infos["name"] return f"{id}: {name}"
[docs] def location(self) -> str: """ Returns the location (address) of this classroom. :return: the location :rtype: str """ return "\n".join(filter(None, str(self).split("\n"))) # Removes blank lines
[docs]def merge_classrooms(classrooms: Iterable[Classroom]) -> Classroom: """ Merges multiple classrooms into one. :param classrooms: multiple classrooms :type classrooms: Iterable[Classroom] :return: the new classroom :rtype: Classroom :Example: >>> c1 = Classroom(address1, 'classA', 1) >>> c2 = Classroom(address2, 'classB', 2) >>> c3 = merge_classrooms((c1, c2)) """ names = " | ".join( classroom.infos["name"] for classroom in classrooms if classroom.infos["name"] ) addresses = "\n".join(str(classroom.infos["address"]) for classroom in classrooms) id = "|".join( classroom.infos["id"] for classroom in classrooms if classroom.infos["id"] ) return Classroom(name=names, address=addresses, id=id)