better metadata processing
This commit is contained in:
parent
040f592ca2
commit
d58008caef
3 changed files with 327 additions and 11 deletions
287
assets/map.json
Normal file
287
assets/map.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,16 +6,19 @@ import 'package:turf/turf.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'package:flutter/services.dart' show rootBundle;
|
||||||
|
|
||||||
class RugReefer {
|
class RugReefer {
|
||||||
final String apiUrl;
|
final String apiUrl;
|
||||||
final bool enableCacheExpiration;
|
final bool enableCacheExpiration;
|
||||||
final Duration cacheExpirationDuration;
|
final Duration cacheExpirationDuration;
|
||||||
final http.Client httpClient;
|
final http.Client httpClient;
|
||||||
|
final String categoryMap;
|
||||||
Directory? cacheDir;
|
Directory? cacheDir;
|
||||||
|
|
||||||
RugReefer({
|
RugReefer({
|
||||||
required this.apiUrl,
|
required this.apiUrl,
|
||||||
|
required this.categoryMap,
|
||||||
this.enableCacheExpiration = false,
|
this.enableCacheExpiration = false,
|
||||||
this.cacheExpirationDuration = const Duration(days: 14),
|
this.cacheExpirationDuration = const Duration(days: 14),
|
||||||
http.Client? client,
|
http.Client? client,
|
||||||
|
|
@ -34,9 +37,13 @@ class RugReefer {
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> lookup(double latitude, double longitude, {double distanceThreshold = 10.0}) async {
|
Future<List<Map<String, dynamic>>> lookup(double latitude, double longitude, {double distanceThreshold = 10.0}) async {
|
||||||
final String geohash = GeoHasher().encode(latitude, longitude, precision: 4);
|
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 _lookupWeb(geohash, latitude, longitude, distanceThreshold)
|
||||||
: await _lookupFile(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 {
|
Future<List<Map<String, dynamic>>> _lookupFile(String geohash, double latitude, double longitude, double distanceThreshold) async {
|
||||||
|
|
@ -47,7 +54,7 @@ class RugReefer {
|
||||||
await _updateLastAccessTime(geohash);
|
await _updateLastAccessTime(geohash);
|
||||||
}
|
}
|
||||||
final String geoJsonStr = utf8.decode(GZipCodec().decode(cacheFile.readAsBytesSync()));
|
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 {
|
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);
|
final geoJsonStr = utf8.decode(response.bodyBytes);
|
||||||
await prefs.setString('geojson_$geohash', geoJsonStr);
|
await prefs.setString('geojson_$geohash', geoJsonStr);
|
||||||
await _updateLastAccessTime(geohash);
|
await _updateLastAccessTime(geohash);
|
||||||
return _findMetadata(latitude, longitude, jsonDecode(geoJsonStr), distanceThreshold);
|
return jsonDecode(geoJsonStr)['features'];
|
||||||
}
|
}
|
||||||
throw Exception('Failed to download GeoJSON for geohash: $geohash');
|
throw Exception('Failed to download GeoJSON for geohash: $geohash');
|
||||||
}
|
}
|
||||||
await _updateLastAccessTime(geohash);
|
await _updateLastAccessTime(geohash);
|
||||||
return _findMetadata(latitude, longitude, jsonDecode(cachedGeoJson), distanceThreshold);
|
return jsonDecode(cachedGeoJson)['features'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _downloadGeoJson(String geohash, File cacheFile) async {
|
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 Position point = Position(longitude, latitude);
|
||||||
final List<Map<String, dynamic>> results = [];
|
Map<String, Map<String, List<String>>> groupedObjects = {};
|
||||||
for (var feature in geoJson['features']) {
|
|
||||||
|
for (var feature in features) {
|
||||||
final geometry = feature['geometry'];
|
final geometry = feature['geometry'];
|
||||||
final metadata = Map<String, dynamic>.from(feature['properties']['tags'] ?? {})..removeWhere((_, v) => v == null);
|
final metadata = Map<String, dynamic>.from(feature['properties']['tags'] ?? {})..removeWhere((_, v) => v == null);
|
||||||
|
String? mainKey;
|
||||||
|
|
||||||
switch (geometry['type']) {
|
switch (geometry['type']) {
|
||||||
case 'Polygon':
|
case 'Polygon':
|
||||||
final polygon = Polygon(
|
final polygon = Polygon(
|
||||||
|
|
@ -117,7 +132,7 @@ class RugReefer {
|
||||||
.toList())
|
.toList())
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
if (booleanPointInPolygon(point, polygon)) results.add({"type": "polygon", ...metadata});
|
if (booleanPointInPolygon(point, polygon)) mainKey = 'in';
|
||||||
break;
|
break;
|
||||||
case 'LineString':
|
case 'LineString':
|
||||||
final line = LineString(
|
final line = LineString(
|
||||||
|
|
@ -125,11 +140,26 @@ class RugReefer {
|
||||||
.map((p) => Position(p[0].toDouble(), p[1].toDouble()))
|
.map((p) => Position(p[0].toDouble(), p[1].toDouble()))
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
if (_isPointNearLine(Point(coordinates: point), line, distanceThreshold)) results.add({"type": "line", ...metadata});
|
if (_isPointNearLine(Point(coordinates: point), line, distanceThreshold)) mainKey = 'by';
|
||||||
break;
|
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) {
|
bool _isPointNearLine(Point point, LineString line, double distanceThreshold) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
dart_geohash: ^2.1.0
|
dart_geohash: ^2.1.0
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
latlong2: ^0.9.1
|
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
shared_preferences: ^2.5.2
|
shared_preferences: ^2.5.2
|
||||||
turf: ^0.0.10
|
turf: ^0.0.10
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue