Files
Client/lib/api/geocoding/nominatim_search.dart
T

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,
);
}
}