From 37600ce981d8e453302967bd57945fab46b6675f Mon Sep 17 00:00:00 2001 From: sanj <67624670+iodrift@users.noreply.github.com> Date: Fri, 28 Jun 2024 23:26:17 -0700 Subject: [PATCH] Auto-update: Fri Jun 28 23:26:17 PDT 2024 --- sijapi/__init__.py | 2 +- sijapi/classes.py | 114 ++++++++++++++++++++++++++--------------- sijapi/routers/note.py | 7 ++- 3 files changed, 76 insertions(+), 47 deletions(-) diff --git a/sijapi/__init__.py b/sijapi/__init__.py index cfe5388..ba6b82b 100644 --- a/sijapi/__init__.py +++ b/sijapi/__init__.py @@ -12,7 +12,7 @@ from typing import List, Optional import traceback import logging from .logs import Logger -from .classes import AutoResponder, IMAPConfig, SMTPConfig, EmailAccount, EmailContact, IncomingEmail, TimezoneTracker, Database, Geocoder +from .classes import AutoResponder, IMAPConfig, SMTPConfig, EmailAccount, EmailContact, IncomingEmail, Database, Geocoder # from sijapi.config.config import load_config # cfg = load_config() diff --git a/sijapi/classes.py b/sijapi/classes.py index 4b94561..53fa8e9 100644 --- a/sijapi/classes.py +++ b/sijapi/classes.py @@ -3,6 +3,8 @@ from typing import List, Optional, Any, Tuple, Dict, Union, Tuple from datetime import datetime, timedelta, timezone import asyncio import json +import yaml +import math from timezonefinder import TimezoneFinder from pathlib import Path import asyncpg @@ -46,6 +48,7 @@ class Location(BaseModel): } + class Geocoder: def __init__(self, named_locs: Union[str, Path] = None, cache_file: Union[str, Path] = 'timezone_cache.json'): self.tf = TimezoneFinder() @@ -56,10 +59,52 @@ class Geocoder: self.last_update: Optional[datetime] = None self.last_location: Optional[Tuple[float, float]] = None self.executor = ThreadPoolExecutor() + self.override_locations = self.load_override_locations() + + def load_override_locations(self): + if self.named_locs and self.named_locs.exists(): + with open(self.named_locs, 'r') as file: + return yaml.safe_load(file) + return [] + + def haversine(self, lat1, lon1, lat2, lon2): + R = 6371 + + lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2]) + dlat = lat2 - lat1 + dlon = lon2 - lon1 + + a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2 + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + + return R * c + + def find_override_location(self, lat: float, lon: float) -> Optional[str]: + closest_location = None + closest_distance = float('inf') + + for location in self.override_locations: + loc_name = location.get("name") + loc_lat = location.get("latitude") + loc_lon = location.get("longitude") + loc_radius = location.get("radius") + + distance = self.haversine(lat, lon, loc_lat, loc_lon) + + if distance <= loc_radius: + if distance < closest_distance: + closest_distance = distance + closest_location = loc_name + + return closest_location async def location(self, lat: float, lon: float): loop = asyncio.get_running_loop() - return await loop.run_in_executor(self.executor, rg.search, [(lat, lon)]) + result = await loop.run_in_executor(self.executor, rg.search, [(lat, lon)]) + override = self.find_override_location(lat, lon) + if override: + result[0]['override_name'] = override + return result async def elevation(self, latitude: float, longitude: float, unit: str = "m") -> float: loop = asyncio.get_running_loop() @@ -107,12 +152,14 @@ class Geocoder: coordinates = [(location.latitude, location.longitude) for location in processed_locations] - geocode_results = await self.location(*zip(*coordinates)) + geocode_results = await asyncio.gather(*[self.location(lat, lon) for lat, lon in coordinates]) elevations = await asyncio.gather(*[self.elevation(lat, lon) for lat, lon in coordinates]) timezones = await asyncio.gather(*[self.timezone(lat, lon) for lat, lon in coordinates]) geocoded_locations = [] for location, result, elevation, timezone in zip(processed_locations, geocode_results, elevations, timezones): + result = result[0] # Unpack the first result + override_name = result.get('override_name') geocoded_location = Location( latitude=location.latitude, longitude=location.longitude, @@ -123,8 +170,8 @@ class Geocoder: state=result.get("admin1"), country=result.get("cc"), context=location.context or {}, - name=result.get("name"), - display_name=f"{result.get('name')}, {result.get('admin1')}, {result.get('cc')}", + name=override_name or result.get("name"), + display_name=f"{override_name or result.get('name')}, {result.get('admin1')}, {result.get('cc')}", country_code=result.get("cc"), timezone=timezone ) @@ -176,44 +223,12 @@ class Geocoder: timezone=await self.timezone(latitude, longitude) ) - - def load_override_locations(self): - if self.named_locs and self.named_locs.exists(): - with open(self.named_locs, 'r') as file: - return yaml.safe_load(file) - return [] - - def haversine(self, lat1, lon1, lat2, lon2): - R = 6371 # Earth's radius in kilometers + def round_coords(self, lat: float, lon: float, decimal_places: int = 2) -> Tuple[float, float]: + return (round(lat, decimal_places), round(lon, decimal_places)) - lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) - dlat = lat2 - lat1 - dlon = lon2 - lon1 - - a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 - c = 2 * atan2(sqrt(a), sqrt(1-a)) - - return R * c - - async def find_override_location(self, lat: float, lon: float) -> Optional[str]: - closest_location = None - closest_distance = float('inf') - - for location in self.override_locations: - loc_name = location.get("name") - loc_lat = location.get("latitude") - loc_lon = location.get("longitude") - loc_radius = location.get("radius") - - distance = self.haversine(lat, lon, loc_lat, loc_lon) - - if distance <= loc_radius: - if distance < closest_distance: - closest_distance = distance - closest_location = loc_name - - return closest_location + def coords_equal(self, coord1: Tuple[float, float], coord2: Tuple[float, float], tolerance: float = 1e-5) -> bool: + return math.isclose(coord1[0], coord2[0], abs_tol=tolerance) and math.isclose(coord1[1], coord2[1], abs_tol=tolerance) async def refresh_timezone(self, location: Union[Location, Tuple[float, float]], force: bool = False) -> str: if isinstance(location, Location): @@ -221,16 +236,20 @@ class Geocoder: else: lat, lon = location + rounded_location = self.round_coords(lat, lon) current_time = datetime.now() + if (force or not self.last_update or current_time - self.last_update > timedelta(hours=1) or - self.last_location != (lat, lon)): + not self.coords_equal(rounded_location, self.round_coords(*self.last_location) if self.last_location else (None, None))): + new_timezone = await self.timezone(lat, lon) self.last_timezone = new_timezone self.last_update = current_time - self.last_location = (lat, lon) + self.last_location = (lat, lon) # Store the original, non-rounded coordinates await self.tz_save() + return self.last_timezone async def tz_save(self): @@ -260,6 +279,17 @@ class Geocoder: async def tz_last(self) -> Optional[str]: await self.tz_cached() return self.last_timezone + + + async def tz_at(self, lat: float, lon: float) -> str: + """ + Get the timezone at a specific latitude and longitude without affecting the cache. + + :param lat: Latitude + :param lon: Longitude + :return: Timezone string + """ + return await self.timezone(lat, lon) def __del__(self): self.executor.shutdown() diff --git a/sijapi/routers/note.py b/sijapi/routers/note.py index 8aadcec..d0f07db 100644 --- a/sijapi/routers/note.py +++ b/sijapi/routers/note.py @@ -30,11 +30,10 @@ from dateutil.parser import parse as dateutil_parse from fastapi import HTTPException, status from pathlib import Path from fastapi import APIRouter, Query, HTTPException -from sijapi import L, OBSIDIAN_VAULT_DIR, OBSIDIAN_RESOURCES_DIR, ARCHIVE_DIR, BASE_URL, OBSIDIAN_BANNER_SCENE, DEFAULT_11L_VOICE, DEFAULT_VOICE, TZ, DynamicTZ, GEO +from sijapi import L, OBSIDIAN_VAULT_DIR, OBSIDIAN_RESOURCES_DIR, ARCHIVE_DIR, BASE_URL, OBSIDIAN_BANNER_SCENE, DEFAULT_11L_VOICE, DEFAULT_VOICE, GEO from sijapi.routers import cal, loc, tts, llm, time, sd, weather, asr -from sijapi.routers.loc import Location from sijapi.utilities import assemble_journal_path, assemble_archive_path, convert_to_12_hour_format, sanitize_filename, convert_degrees_to_cardinal, HOURLY_COLUMNS_MAPPING - +from sijapi.classes import Location note = APIRouter() @@ -70,7 +69,7 @@ async def build_daily_note_endpoint( date_str = dt_datetime.now().strftime("%Y-%m-%d") if location: lat, lon = map(float, location.split(',')) - tz = ZoneInfo(DynamicTZ.find(lat, lon)) + tz = GEO.tz_at(lat, lon) date_time = dateutil_parse(date_str).replace(tzinfo=tz) else: raise ValueError("Location is not provided or invalid.")