From 9d231a8ea9423b00b8b1be6fe5ba239d0bf38a14 Mon Sep 17 00:00:00 2001 From: seanbreckenridge Date: Sat, 4 Mar 2023 10:36:10 -0800 Subject: [PATCH] google_takeout: add semantic location history (#278) * google_takeout: add semantic location history --- my/config.py | 8 +++ my/location/all.py | 12 ++++ my/location/google_takeout_semantic.py | 76 ++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 my/location/google_takeout_semantic.py diff --git a/my/config.py b/my/config.py index 7075d1d..318539c 100644 --- a/my/config.py +++ b/my/config.py @@ -89,6 +89,14 @@ class location: export_path: Paths = '' accuracy: float + class google_takeout_semantic: + # a value between 0 and 100, 100 being the most confident + # set to 0 to include all locations + # https://locationhistoryformat.com/reference/semantic/#/$defs/placeVisit/properties/locationConfidence + require_confidence: float = 40 + # default accuracy for semantic locations + accuracy: float = 100 + from my.core.compat import Literal class time: diff --git a/my/location/all.py b/my/location/all.py index 8d51a82..fd88721 100644 --- a/my/location/all.py +++ b/my/location/all.py @@ -16,6 +16,7 @@ logger = LazyLogger(__name__, level="warning") def locations() -> Iterator[Location]: # can add/comment out sources here to disable them, or use core.disabled_modules yield from _takeout_locations() + yield from _takeout_semantic_locations() yield from _gpslogger_locations() yield from _ip_locations() @@ -26,6 +27,17 @@ def _takeout_locations() -> Iterator[Location]: yield from google_takeout.locations() +@import_source(module_name="my.location.google_takeout_semantic") +def _takeout_semantic_locations() -> Iterator[Location]: + from . import google_takeout_semantic + + for event in google_takeout_semantic.locations(): + if isinstance(event, Exception): + logger.error(f"google_takeout_semantic: {event}") + continue + yield event + + @import_source(module_name="my.location.gpslogger") def _gpslogger_locations() -> Iterator[Location]: from . import gpslogger diff --git a/my/location/google_takeout_semantic.py b/my/location/google_takeout_semantic.py new file mode 100644 index 0000000..4d3514e --- /dev/null +++ b/my/location/google_takeout_semantic.py @@ -0,0 +1,76 @@ +""" +Extracts semantic location history using google_takeout_parser +""" + +# This is a separate module to prevent ImportError and a new config block from breaking +# previously functional my.location.google_takeout locations + +REQUIRES = ["git+https://github.com/seanbreckenridge/google_takeout_parser"] + +from typing import Iterator, List + +from my.google.takeout.parser import events, _cachew_depends_on as _parser_cachew_depends_on +from google_takeout_parser.models import PlaceVisit as SemanticLocation + +from my.core import dataclass, make_config +from my.core.common import mcachew, LazyLogger, Stats +from my.core.error import Res +from .common import Location + +logger = LazyLogger(__name__) + +from my.config import location as user_config + +@dataclass +class semantic_locations_config(user_config.google_takeout_semantic): + # a value between 0 and 100, 100 being the most confident + # set to 0 to include all locations + # https://locationhistoryformat.com/reference/semantic/#/$defs/placeVisit/properties/locationConfidence + require_confidence: int = 40 + # default accuracy for semantic locations + accuracy: float = 100 + + +config = make_config(semantic_locations_config) + + +# add config to cachew dependency so it recomputes on config changes +def _cachew_depends_on() -> List[str]: + dep = _parser_cachew_depends_on() + dep.insert(0, f"require_confidence={config.require_confidence} accuracy={config.accuracy}") + return dep + + + +@mcachew( + depends_on=_cachew_depends_on, + logger=logger, +) +def locations() -> Iterator[Res[Location]]: + require_confidence = config.require_confidence + if require_confidence < 0 or require_confidence > 100: + yield ValueError("location.google_takeout.semantic_require_confidence must be between 0 and 100") + return + + for g in events(): + if isinstance(g, SemanticLocation): + if g.visitConfidence < require_confidence: + logger.debug(f"Skipping {g} due to low confidence ({g.visitConfidence}))") + continue + yield Location( + lon=g.lng, + lat=g.lat, + dt=g.dt, + # can accuracy be inferred from visitConfidence? + # there's no exact distance value in the data, its a 0-100% confidence value... + accuracy=config.accuracy, + elevation=None, + datasource="google_takeout_semantic", + ) + + + +def stats() -> Stats: + from my.core import stat + + return {**stat(locations)}