Ortssuche verschob bisher nur die Karte - der HTML-Demonstrator beginnt bei einer Suche immer eine neue, nach dem gefundenen Ort benannte Mission (setMissionFromPlace(), aufgerufen aus doSearch()). Vorherige Aenderungen gehen dabei nicht verloren, da flushPendingAutosave() (hier: CurrentMissionMetaNotifier._flushNow()) zuerst noch ausstehende Autosaves schreibt. Die Nominatim-Suche wurde dafuer aus MapSearchControls in einen eigenstaendigen NominatimService extrahiert (analog WindService): injectable http.Client, damit sich die Suche in Tests ohne echten Netzwerkzugriff ueberschreiben laesst. CurrentMissionMetaNotifier bekam dafuer startNewFromPlace() (startNew() intern darauf umgebaut, um Duplikation zu vermeiden). Kopfleiste von 86%/64% (Plan-/Fly-Modus) auf einheitlich 70% Bildschirmbreite verkleinert - zentriert war sie durch Align(topCenter) + FractionallySizedBox bereits strukturell korrekt, wirkte bei der vollen Breite aber unausgewogen. Verifiziert: flutter analyze (0 issues), flutter test (64/64, davon 4 neue NominatimService-Tests und 1 neuer Widget-Test fuer den Missions-Reset bei Ortssuche), manuell auf Pixel_10a-Emulator - Suche nach "Rotterdam" setzt Fusszeile auf "Mission: Rotterdam" mit 0 Wegpunkten trotz zuvor bestehender Mission mit Wegpunkten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
69 lines
2.2 KiB
Dart
69 lines
2.2 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
/// Ergebnis einer Ortssuche: Koordinaten plus ein kompakter Anzeigename,
|
|
/// der auch als Missionsname dient (siehe MapSearchControls).
|
|
class GeocodingResult {
|
|
const GeocodingResult({
|
|
required this.label,
|
|
required this.lat,
|
|
required this.lon,
|
|
});
|
|
|
|
final String label;
|
|
final double lat;
|
|
final double lon;
|
|
}
|
|
|
|
/// Geocoding ueber Nominatim (OpenStreetMap) - analog doSearch() im HTML-
|
|
/// Demonstrator. Injectable http.Client analog WindService, damit dies in
|
|
/// Tests ohne echten Netzwerkzugriff ueberschrieben werden kann.
|
|
class NominatimService {
|
|
NominatimService({http.Client? client}) : _client = client ?? http.Client();
|
|
|
|
final http.Client _client;
|
|
|
|
Future<GeocodingResult?> search(String query) async {
|
|
final uri = Uri.https('nominatim.openstreetmap.org', '/search', {
|
|
'format': 'json',
|
|
'addressdetails': '1',
|
|
'accept-language': 'en',
|
|
'limit': '1',
|
|
'q': query,
|
|
});
|
|
final response = await _client
|
|
.get(uri, headers: {'User-Agent': 'dmc_app (Drone Mission Control)'});
|
|
if (response.statusCode != 200) return null;
|
|
|
|
final results = jsonDecode(response.body) as List;
|
|
if (results.isEmpty) return null;
|
|
|
|
final first = results.first as Map<String, dynamic>;
|
|
return GeocodingResult(
|
|
label: _extractPlaceLabel(first),
|
|
lat: double.parse(first['lat'] as String),
|
|
lon: double.parse(first['lon'] as String),
|
|
);
|
|
}
|
|
|
|
/// Analog extractPlaceLabel() im HTML-Demonstrator: bevorzugt einen
|
|
/// kompakten Ortsnamen aus den Nominatim-Adressbestandteilen, faellt sonst
|
|
/// auf den ersten Abschnitt von display_name zurueck.
|
|
String _extractPlaceLabel(Map<String, dynamic> result) {
|
|
final address = result['address'] as Map<String, dynamic>?;
|
|
final label = address?['city'] ??
|
|
address?['town'] ??
|
|
address?['village'] ??
|
|
address?['municipality'] ??
|
|
address?['suburb'] ??
|
|
address?['county'];
|
|
if (label is String && label.isNotEmpty) return label;
|
|
final displayName = result['display_name'] as String?;
|
|
if (displayName != null && displayName.isNotEmpty) {
|
|
return displayName.split(',').first;
|
|
}
|
|
return 'Unknown location';
|
|
}
|
|
}
|