einsatzprotokoll/source/GpsService.mc
EiSiMo 216d40c2c5 Custom icons, improved GPS, history layout, address fixes
- Replace placeholder SVG icons with custom PNG icons (80x80 padded)
- AffineTransform for pixel-perfect icon centering in menu ring
- Colored circles behind icons with icon drawn at 60% size
- GPS: always wait full timeout (30s), keep best quality fix
- History: show address, PLZ+city, coordinates on separate lines
- Geocoding: fall back to "name" field for street-level results,
  include PLZ+city in cached address data
- Address distance threshold raised to 70m

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:05:17 +02:00

75 lines
2.4 KiB
MonkeyC

import Toybox.Lang;
import Toybox.Position;
import Toybox.Timer;
// Single-shot GPS acquisition with a hard timeout. Uses both
// enableLocationEvents (fires on real hardware) and a polling
// fallback via Position.getInfo() (works in the simulator where
// "Set Position" doesn't trigger location events).
class GpsService {
private var _callback as Method(result as Dictionary or Null) as Void;
private var _timer as Timer.Timer;
private var _pollTimer as Timer.Timer;
private var _finished as Boolean = false;
private var _bestFix as Dictionary or Null = null;
function initialize(callback as Method(result as Dictionary or Null) as Void) {
_callback = callback;
_timer = new Timer.Timer();
_pollTimer = new Timer.Timer();
}
function start() as Void {
Position.enableLocationEvents(
Position.LOCATION_CONTINUOUS,
method(:_onPosition)
);
// Poll Position.getInfo() every 500ms as fallback for simulator.
_pollTimer.start(method(:_poll), 500, true);
_timer.start(method(:_onTimeout), Config.GPS_TIMEOUT_MS, false);
}
function _poll() as Void {
if (_finished) { return; }
var info = Position.getInfo();
if (info != null && info.position != null) {
_processInfo(info);
}
}
function _onPosition(info as Position.Info) as Void {
if (_finished) { return; }
_processInfo(info);
}
private function _processInfo(info as Position.Info) as Void {
if (info == null || info.position == null) { return; }
var degrees = info.position.toDegrees();
var quality = (info.accuracy != null) ? info.accuracy : 0;
// Always keep the best quality fix.
if (_bestFix == null || quality >= (_bestFix["q"] as Number)) {
_bestFix = {
"lat" => degrees[0].toFloat(),
"lon" => degrees[1].toFloat(),
"acc" => quality.toFloat(),
"q" => quality
};
}
}
function _onTimeout() as Void {
_finish(_bestFix);
}
function _finish(result as Dictionary or Null) as Void {
if (_finished) { return; }
_finished = true;
_timer.stop();
_pollTimer.stop();
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:_onPosition));
_callback.invoke(result);
}
}