Add location-aware automatic default bounding box
The OpenSky "Default bounding box" now follows where flying happens. A new "Automatic" picker mode (the default) resolves the live-map area from a location cascade — drone telemetry → phone GPS → browser geolocation → the user's Region country → Europe — instead of a fixed box. Manual presets and Custom coordinates still work. - Web App: new shared countries.js dataset (all countries + bbox, offline point→country); the bbox picker gains all European countries and an Automatic option (client pref prefs.autoBbox); the Region setting expands from 6 locale entries to all countries; the live map resolves the cascade each poll and sends it as ?bbox=. - API Server: the states endpoint accepts and validates a ?bbox= override (validBBox); the Web App BFF forwards the query; the hub relays new phoneLatitude/phoneLongitude telemetry to the Web App. - Fly App: reports the phone's own GPS (geolocator) alongside telemetry, used as the "your location" fallback. - API panel: the OpenSky bbox picker lists all European countries. Builds verified across web, panel, both Go modules and the Fly App APK. Region list, Automatic default and the cascade ?bbox= override verified in the browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
150758b0bf
commit
94f6876024
@@ -21,6 +21,10 @@ class FlightModel extends ChangeNotifier {
|
||||
double? altitude;
|
||||
double? latitude;
|
||||
double? longitude;
|
||||
// Phone's own GPS (independent of the drone's fix above); streamed to the
|
||||
// server as a location fallback for the Web App's automatic bounding box.
|
||||
double? phoneLatitude;
|
||||
double? phoneLongitude;
|
||||
int? batteryPercent;
|
||||
|
||||
UploadStatus upload = UploadStatus.disabled;
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
import 'dji_service.dart';
|
||||
import 'flight_model.dart';
|
||||
@@ -46,6 +47,7 @@ class _HomePageState extends State<HomePage> {
|
||||
final FlightModel _model = FlightModel();
|
||||
StreamSubscription<Map<String, dynamic>>? _sub;
|
||||
StreamSubscription<AuthStatus>? _authSub;
|
||||
StreamSubscription<Position>? _phoneLocSub;
|
||||
|
||||
// Streams telemetry to the API Server and receives commands back.
|
||||
late final ServerUploader _uploader;
|
||||
@@ -69,6 +71,43 @@ class _HomePageState extends State<HomePage> {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
_init();
|
||||
_startPhoneLocation();
|
||||
}
|
||||
|
||||
/// Streams the phone's own GPS (coarse, low-frequency) and reports it to the
|
||||
/// server as a telemetry field — a location fallback for the Web App's auto
|
||||
/// bounding box when the drone has no fix. Best-effort: silently gives up if
|
||||
/// location services or permission are unavailable.
|
||||
Future<void> _startPhoneLocation() async {
|
||||
try {
|
||||
if (!await Geolocator.isLocationServiceEnabled()) return;
|
||||
LocationPermission perm = await Geolocator.checkPermission();
|
||||
if (perm == LocationPermission.denied) {
|
||||
perm = await Geolocator.requestPermission();
|
||||
}
|
||||
if (perm == LocationPermission.denied || perm == LocationPermission.deniedForever) {
|
||||
return;
|
||||
}
|
||||
_phoneLocSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.low, // country-level is all the bbox needs
|
||||
distanceFilter: 1000, // metres — infrequent updates
|
||||
),
|
||||
).listen((Position pos) {
|
||||
_model.phoneLatitude = pos.latitude;
|
||||
_model.phoneLongitude = pos.longitude;
|
||||
_model.bump();
|
||||
// Report to the server (rides the existing telemetry channel; only the
|
||||
// phone fields are present, so it never disturbs drone telemetry).
|
||||
_uploader.onEvent(<String, dynamic>{
|
||||
'type': 'telemetry',
|
||||
'phoneLatitude': pos.latitude,
|
||||
'phoneLongitude': pos.longitude,
|
||||
});
|
||||
});
|
||||
} catch (_) {
|
||||
// Location plugin/permission unavailable — non-fatal.
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
@@ -195,6 +234,8 @@ class _HomePageState extends State<HomePage> {
|
||||
if (_model.altitude != null) tel['altitude'] = _model.altitude;
|
||||
if (_model.latitude != null) tel['latitude'] = _model.latitude;
|
||||
if (_model.longitude != null) tel['longitude'] = _model.longitude;
|
||||
if (_model.phoneLatitude != null) tel['phoneLatitude'] = _model.phoneLatitude;
|
||||
if (_model.phoneLongitude != null) tel['phoneLongitude'] = _model.phoneLongitude;
|
||||
if (tel.length > 1) events.add(tel);
|
||||
return events;
|
||||
}
|
||||
@@ -235,6 +276,7 @@ class _HomePageState extends State<HomePage> {
|
||||
void dispose() {
|
||||
_sub?.cancel();
|
||||
_authSub?.cancel();
|
||||
_phoneLocSub?.cancel();
|
||||
_uploadSub?.cancel();
|
||||
_uploader.dispose();
|
||||
_serverHost.dispose();
|
||||
|
||||
@@ -73,6 +73,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -105,6 +113,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -144,6 +160,54 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.4"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.2"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: "853803d6bb1713c094e935b4a5ae5f19c0308acf81da13fa9ff84fb4c70c0b73"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.14"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: cdb082e4f048b69da244117b7914cc60d2a8897546ffaa4f2529c786ded7aee2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.8"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: "19e485a0f8d6a88abcf9c53cba3a4105e14b7435ed8ac1c108c067b938fe8429"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.4"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -437,6 +501,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.3"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -43,6 +43,10 @@ dependencies:
|
||||
# the platform BiometricPrompt). Requires FlutterFragmentActivity on Android.
|
||||
local_auth: ^2.3.0
|
||||
|
||||
# Phone GPS — reported alongside telemetry as a location fallback for the Web
|
||||
# App's automatic bounding box when the drone has no fix.
|
||||
geolocator: ^13.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user