better metadata processing

This commit is contained in:
randogoth 2025-02-28 13:53:15 +02:00
parent 040f592ca2
commit d58008caef
3 changed files with 327 additions and 11 deletions

287
assets/map.json Normal file
View file

@ -0,0 +1,287 @@
{
"abandoned": {
"historic": "ruins",
"landuse": "abandoned",
"railway": [
"abandoned",
"disused"
]
},
"airfield": {
"aeroway": true
},
"archeologic": {
"historic": "archaeological_site"
},
"beach": {
"natural": [
"beach",
"coastline",
"shingle",
"shoal"
]
},
"border": {
"boundary": [
"border_zone",
"disputed"
]
},
"bush": {
"natural": [
"scrub",
"shrubbery"
]
},
"cemetery": {
"landuse": [
"cemetery",
"grave_yard",
"graveyard"
]
},
"commercial": {
"landuse": [
"commercial",
"retail"
]
},
"construction": {
"landuse": [
"brownfield",
"construction"
],
"railway": "construction"
},
"farmland": {
"landuse": [
"agricultural",
"agriculture",
"animal_keeping",
"farmland",
"farmyard",
"greenhouse_horticulture",
"orchard",
"paddy",
"plant_nursery",
"vineyard"
],
"place": [
"farm",
"plot"
]
},
"forest": {
"boundary": "forest",
"landuse": [
"forest",
"forestry",
"logging"
],
"natural": [
"tree_row",
"wood"
],
"place": "woodland"
},
"fort": {
"historic": [
"castle_crush",
"castle_wall",
"castle",
"fort"
]
},
"grassland": {
"landuse": [
"clearing",
"grass",
"grassland",
"green",
"greenfield",
"meadow",
"pasture"
],
"natural": [
"grassland",
"heath",
"tundra"
]
},
"greenery": {
"landuse": "greenery"
},
"hazard": {
"boundary": "hazard",
"geological": "sinkhole"
},
"history": {
"historic": [
"aqueduct",
"battlefield",
"citywalls",
"yes"
]
},
"industrial": {
"landuse": "industrial",
"power": "plant"
},
"island": {
"place": [
"island",
"islet"
]
},
"landfill": {
"landuse": "landfill"
},
"lithic": {
"geological": [
"columnar_jointing",
"dyke",
"glacial_erratic",
"hoodoo",
"inselberg",
"limestone_pavement",
"monocline",
"moraine",
"outcrop",
"pingo",
"tor"
],
"landuse": "quarry",
"natural": [
"bare_rock",
"scree",
"stone"
]
},
"meteor": {
"geological": "meteor_crater"
},
"military": {
"landuse": "military",
"military": [
"airfield",
"barracks",
"base",
"bunker",
"danger_area",
"nuclear_explosion_site",
"range",
"training_area"
]
},
"park": {
"boundary": "national_park",
"landuse": [
"park",
"village_green"
],
"leisure": [
"dog_park",
"garden",
"park"
]
},
"protected": {
"boundary": [
"aboriginal_lands",
"protected_area"
],
"landuse": "conservation",
"leisure": "nature_reserve"
},
"railway": {
"landuse": "railway",
"railway": "rail"
},
"recreation": {
"landuse": [
"playground",
"recreation_ground",
"recreation"
],
"leisure": [
"miniature_gold",
"playground",
"water_park"
]
},
"residential": {
"landuse": [
"allotments",
"residential"
]
},
"spiritual": {
"historic": [
"church",
"mosque",
"temple"
],
"landuse": [
"churchyard",
"religious"
]
},
"sports": {
"landuse": "winter_sports",
"sport": [
"bmx",
"golf",
"paintball",
"skateboard",
"skiing",
"speedway",
"tennis"
]
},
"tourism": {
"tourism": [
"attraction",
"yes"
]
},
"volcano": {
"geological": [
"volcanic_caldera_rim",
"volcanic_lava_field",
"volcanic_vent"
],
"natural": "volcano"
},
"wasteland": {
"landuse": "wasteland",
"natural": [
"desert",
"mud",
"sand"
]
},
"water": {
"landuse": [
"basin",
"reservoir",
"salt_pond"
],
"natural": [
"strait",
"water",
"wetland"
],
"place": [
"ocean",
"sea"
],
"water": true,
"waterway": [
"river",
"stream",
"tidal_channel",
"canal",
"fairway"
]
}
}

View file

@ -6,16 +6,19 @@ import 'package:turf/turf.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/services.dart' show rootBundle;
class RugReefer {
final String apiUrl;
final bool enableCacheExpiration;
final Duration cacheExpirationDuration;
final http.Client httpClient;
final String categoryMap;
Directory? cacheDir;
RugReefer({
required this.apiUrl,
required this.categoryMap,
this.enableCacheExpiration = false,
this.cacheExpirationDuration = const Duration(days: 14),
http.Client? client,
@ -34,9 +37,13 @@ class RugReefer {
Future<List<Map<String, dynamic>>> lookup(double latitude, double longitude, {double distanceThreshold = 10.0}) async {
final String geohash = GeoHasher().encode(latitude, longitude, precision: 4);
return kIsWeb
final jsonMap = await loadJsonFromAssets(categoryMap);
List<Map<String, dynamic>> rawData = kIsWeb
? await _lookupWeb(geohash, latitude, longitude, distanceThreshold)
: await _lookupFile(geohash, latitude, longitude, distanceThreshold);
return _processMetadata(rawData, jsonMap, latitude, longitude, distanceThreshold);
}
Future<List<Map<String, dynamic>>> _lookupFile(String geohash, double latitude, double longitude, double distanceThreshold) async {
@ -47,7 +54,7 @@ class RugReefer {
await _updateLastAccessTime(geohash);
}
final String geoJsonStr = utf8.decode(GZipCodec().decode(cacheFile.readAsBytesSync()));
return _findMetadata(latitude, longitude, jsonDecode(geoJsonStr), distanceThreshold);
return jsonDecode(geoJsonStr)['features'];
}
Future<List<Map<String, dynamic>>> _lookupWeb(String geohash, double latitude, double longitude, double distanceThreshold) async {
@ -59,12 +66,12 @@ class RugReefer {
final geoJsonStr = utf8.decode(response.bodyBytes);
await prefs.setString('geojson_$geohash', geoJsonStr);
await _updateLastAccessTime(geohash);
return _findMetadata(latitude, longitude, jsonDecode(geoJsonStr), distanceThreshold);
return jsonDecode(geoJsonStr)['features'];
}
throw Exception('Failed to download GeoJSON for geohash: $geohash');
}
await _updateLastAccessTime(geohash);
return _findMetadata(latitude, longitude, jsonDecode(cachedGeoJson), distanceThreshold);
return jsonDecode(cachedGeoJson)['features'];
}
Future<void> _downloadGeoJson(String geohash, File cacheFile) async {
@ -102,12 +109,20 @@ class RugReefer {
}
}
List<Map<String, dynamic>> _findMetadata(double latitude, double longitude, Map<String, dynamic> geoJson, double distanceThreshold) {
Future<Map<String, dynamic>> loadJsonFromAssets(String assetPath) async {
final jsonString = await rootBundle.loadString(assetPath);
return jsonDecode(jsonString);
}
List<Map<String, dynamic>> _processMetadata(List<Map<String, dynamic>> features, Map<String, dynamic> jsonMap, double latitude, double longitude, double distanceThreshold) {
final Position point = Position(longitude, latitude);
final List<Map<String, dynamic>> results = [];
for (var feature in geoJson['features']) {
Map<String, Map<String, List<String>>> groupedObjects = {};
for (var feature in features) {
final geometry = feature['geometry'];
final metadata = Map<String, dynamic>.from(feature['properties']['tags'] ?? {})..removeWhere((_, v) => v == null);
String? mainKey;
switch (geometry['type']) {
case 'Polygon':
final polygon = Polygon(
@ -117,7 +132,7 @@ class RugReefer {
.toList())
.toList(),
);
if (booleanPointInPolygon(point, polygon)) results.add({"type": "polygon", ...metadata});
if (booleanPointInPolygon(point, polygon)) mainKey = 'in';
break;
case 'LineString':
final line = LineString(
@ -125,11 +140,26 @@ class RugReefer {
.map((p) => Position(p[0].toDouble(), p[1].toDouble()))
.toList(),
);
if (_isPointNearLine(Point(coordinates: point), line, distanceThreshold)) results.add({"type": "line", ...metadata});
if (_isPointNearLine(Point(coordinates: point), line, distanceThreshold)) mainKey = 'by';
break;
}
if (mainKey == null) continue;
groupedObjects.putIfAbsent(mainKey, () => {});
for (var key in metadata.keys) {
for (var category in jsonMap.keys) {
var categoryMap = jsonMap[category];
if (categoryMap is Map && categoryMap.containsKey(key)) {
var rule = categoryMap[key];
if (rule is String || (rule is List && rule.contains(metadata[key])) || rule == true) {
groupedObjects[mainKey]!.putIfAbsent(category, () => []);
groupedObjects[mainKey]![category]!.add(metadata[key]);
}
}
}
}
}
return results;
return groupedObjects.entries.map((entry) => {entry.key: entry.value}).toList();
}
bool _isPointNearLine(Point point, LineString line, double distanceThreshold) {

View file

@ -10,7 +10,6 @@ environment:
dependencies:
dart_geohash: ^2.1.0
http: ^1.3.0
latlong2: ^0.9.1
path_provider: ^2.1.5
shared_preferences: ^2.5.2
turf: ^0.0.10