import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:dart_geohash/dart_geohash.dart'; import 'package:latlong2/latlong.dart'; import 'package:turf/turf.dart'; class GeoMetadataLookup { final String apiUrl; final Directory cacheDir; final Distance distance = const Distance(); GeoMetadataLookup({ required this.apiUrl, required this.cacheDir, }) { if (!cacheDir.existsSync()) { cacheDir.createSync(recursive: true); } } Future>> lookup(double latitude, double longitude, {double distance = 10.0}) async { final String geohash = GeoHasher().encode(longitude, latitude); final File cacheFile = File('${cacheDir.path}/$geohash.geojson.gz'); if (!cacheFile.existsSync()) { await _downloadGeoJson(geohash, cacheFile); } final String geoJsonStr = utf8.decode(GZipCodec().decode(cacheFile.readAsBytesSync())); final Map geoJson = jsonDecode(geoJsonStr); return _findMetadata(latitude, longitude, distance, geoJson); } Future _downloadGeoJson(String geohash, File cacheFile) async { final response = await http.get(Uri.parse('$apiUrl$geohash')); if (response.statusCode == 200) { cacheFile.writeAsBytesSync(GZipCodec().encode(response.bodyBytes)); } else { throw Exception('Failed to download GeoJSON for geohash: $geohash'); } } List> _findMetadata(double latitude, double longitude, double distanceThreshold, Map geoJson) { final Position point = Position(longitude, latitude); List> results = []; for (var feature in geoJson['features']) { final geometry = feature['geometry']; final properties = feature['properties'] ?? {}; if (geometry['type'] == 'Polygon') { final polygon = Polygon( coordinates: (geometry['coordinates'] as List) .map((ring) => (ring as List) .map((p) => Position(p[0] as double, p[1] as double)) .toList()) .toList(), ); if (booleanPointInPolygon(point, polygon)) { results.add({'type': 'polygon', ...properties}); } } else if (geometry['type'] == 'LineString') { final line = LineString( coordinates: (geometry['coordinates'] as List) .map((p) => Position(p[0] as double, p[1] as double)) .toList(), ); if (_isPointNearLine(point, line, distanceThreshold)) { results.add({'type': 'line', ...properties}); } } else if (geometry['type'] == 'MultiLineString') { for (var lineCoords in geometry['coordinates']) { final line = LineString( coordinates: (lineCoords as List) .map((p) => Position(p[0] as double, p[1] as double)) .toList(), ); if (_isPointNearLine(point, line, distanceThreshold)) { results.add({'type': 'multi_line', ...properties}); break; } } } } return results; } bool _isPointNearLine(Position point, LineString line, double distanceThreshold) { for (var i = 0; i < line.coordinates.length - 1; i++) { final p1 = line.coordinates[i]; final double dist = distance.distance( LatLng(point.lat.toDouble(), point.lng.toDouble()), LatLng(p1.lat.toDouble(), p1.lng.toDouble()), ); if (dist <= distanceThreshold) return true; } return false; } }