73 lines
2.5 KiB
Dart
73 lines
2.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../errors/network_exception.dart';
|
|
import '../errors/parse_exception.dart';
|
|
import '../errors/server_exception.dart';
|
|
import 'nominatim_result.dart';
|
|
|
|
/// Tiny wrapper around the public Nominatim geocoder. Only used in the
|
|
/// commute-settings flow to look up a home address; not called from any
|
|
/// hot path. The User-Agent header is **required** by the Nominatim usage
|
|
/// policy — without it the service throttles/blocks the client.
|
|
class NominatimSearch {
|
|
static const _userAgent = 'MarianumMobile/1.0 (contact@elias-mueller.com)';
|
|
static final Uri _base = Uri.parse('https://nominatim.openstreetmap.org/search');
|
|
|
|
/// Returns up to [limit] geocoded matches for the user-typed [query].
|
|
Future<List<NominatimResult>> run(String query, {int limit = 5}) async {
|
|
final uri = _base.replace(
|
|
queryParameters: {
|
|
'q': query,
|
|
'format': 'json',
|
|
'limit': limit.toString(),
|
|
'addressdetails': '0',
|
|
'accept-language': 'de',
|
|
},
|
|
);
|
|
|
|
final http.Response response;
|
|
try {
|
|
response = await http
|
|
.get(uri, headers: {'User-Agent': _userAgent, 'Accept': 'application/json'})
|
|
.timeout(const Duration(seconds: 15));
|
|
} on SocketException catch (e) {
|
|
throw NetworkException(technicalDetails: 'nominatim: ${e.message}');
|
|
} on TimeoutException catch (e) {
|
|
throw NetworkException.timeout(technicalDetails: 'nominatim: $e');
|
|
} on http.ClientException catch (e) {
|
|
throw NetworkException(technicalDetails: 'nominatim: ${e.message}');
|
|
}
|
|
|
|
if (response.statusCode > 299) {
|
|
throw ServerException(
|
|
statusCode: response.statusCode,
|
|
technicalDetails: 'nominatim HTTP ${response.statusCode}',
|
|
);
|
|
}
|
|
|
|
try {
|
|
final raw = jsonDecode(utf8.decode(response.bodyBytes)) as List;
|
|
return raw
|
|
.map((e) => _resultFromRaw(e as Map<String, dynamic>))
|
|
.toList(growable: false);
|
|
} catch (e) {
|
|
throw ParseException(technicalDetails: 'nominatim assemble: $e');
|
|
}
|
|
}
|
|
|
|
static NominatimResult _resultFromRaw(Map<String, dynamic> json) {
|
|
// Nominatim returns lat/lon as strings, not numbers. Normalise here.
|
|
final lat = double.parse(json['lat'].toString());
|
|
final lon = double.parse(json['lon'].toString());
|
|
return NominatimResult(
|
|
displayName: json['display_name'] as String? ?? '?',
|
|
lat: lat,
|
|
lon: lon,
|
|
);
|
|
}
|
|
}
|