Phase 4: history view with reverse geocoding
3-section layout (15/70/15) for browsing recorded events. Reverse-geocodes coordinates via Photon API with Haversine distance check and address caching. Also adds simulator GPS polling fallback (Position.getInfo) since the simulator does not fire location event callbacks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
025d3007db
commit
902121bd42
7 changed files with 368 additions and 11 deletions
83
source/GeocodingService.mc
Normal file
83
source/GeocodingService.mc
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import Toybox.Communications;
|
||||
import Toybox.Lang;
|
||||
|
||||
// Reverse-geocodes a lat/lon pair via Photon API. Calls back with
|
||||
// a formatted address string or null on failure.
|
||||
class GeocodingService {
|
||||
|
||||
private var _callback as Method(address as String or Null) as Void;
|
||||
private var _lat as Float;
|
||||
private var _lon as Float;
|
||||
|
||||
function initialize(lat as Float, lon as Float,
|
||||
callback as Method(address as String or Null) as Void) {
|
||||
_callback = callback;
|
||||
_lat = lat;
|
||||
_lon = lon;
|
||||
}
|
||||
|
||||
function start() as Void {
|
||||
var url = Config.PHOTON_URL;
|
||||
var params = { "lon" => _lon, "lat" => _lat, "limit" => 1 };
|
||||
var options = {
|
||||
:method => Communications.HTTP_REQUEST_METHOD_GET,
|
||||
:responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
|
||||
:headers => { "Accept" => "application/json" }
|
||||
};
|
||||
Communications.makeWebRequest(url, params, options, method(:_onResponse));
|
||||
}
|
||||
|
||||
function _onResponse(responseCode as Number, data as Dictionary or String or Null) as Void {
|
||||
if (responseCode != 200 || data == null || !(data instanceof Dictionary)) {
|
||||
_callback.invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var features = data["features"];
|
||||
if (features == null || !(features instanceof Array) || features.size() == 0) {
|
||||
_callback.invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var feature = features[0] as Dictionary;
|
||||
var props = feature["properties"] as Dictionary;
|
||||
var geometry = feature["geometry"] as Dictionary;
|
||||
|
||||
// Haversine check: only use address if result is within threshold.
|
||||
if (geometry != null) {
|
||||
var coords = geometry["coordinates"] as Array;
|
||||
if (coords != null && coords.size() >= 2) {
|
||||
var rLon = (coords[0] as Double).toFloat();
|
||||
var rLat = (coords[1] as Double).toFloat();
|
||||
var dist = Haversine.distance(_lat, _lon, rLat, rLon);
|
||||
if (dist > Config.ADDRESS_MAX_DISTANCE_M) {
|
||||
_callback.invoke(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_callback.invoke(_formatAddress(props));
|
||||
}
|
||||
|
||||
private function _formatAddress(props as Dictionary) as String {
|
||||
var street = props["street"];
|
||||
var number = props["housenumber"];
|
||||
var city = props["city"];
|
||||
|
||||
var parts = "";
|
||||
if (street != null) {
|
||||
parts = street as String;
|
||||
if (number != null) {
|
||||
parts = parts + " " + number;
|
||||
}
|
||||
}
|
||||
if (city != null) {
|
||||
if (!parts.equals("")) {
|
||||
parts = parts + ", ";
|
||||
}
|
||||
parts = parts + city;
|
||||
}
|
||||
return parts.equals("") ? "Unbekannt" : parts;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue