import 'dart:async'; import 'dart:convert'; import 'package:anyhow/anyhow.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:geojson_vi/geojson_vi.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; import 'package:uninav/components/feature_bottom_sheet.dart'; import 'package:uninav/data/geo/model.dart'; import 'package:uninav/data/geo/parser.dart'; import 'package:uninav/util/geojson_util.dart'; import 'package:uninav/util/geolocator.dart'; import 'package:uninav/util/geomath.dart'; class MyMapController extends GetxController { final MapController mapController = MapController(); final RxList features = [].obs; final currentLevel = 1.obs; final levels = [1].obs; final Rx position = null.obs; bool _locationEnsured = false; @override onInit() async { print("init"); ever(features, refreshLevels); super.onInit(); } void refreshLevels(List curFeatures) { print("refreshLevels"); final newLevels = [1]; for (final feature in curFeatures) { if (feature.level != null && !newLevels.contains(feature.level)) { newLevels.add(feature.level!); } } newLevels.sort(); levels.value = newLevels; update(); } Result setLevel(int level) { // check that level is in features if (!levels.contains(level)) { return bail('Level $level is not in features'); } currentLevel.value = level; update(); return const Ok(()); } List computeHits(LatLng position, {bool Function(Feature)? filter}) { final hits = <(Feature, double)>[]; for (final feature in features) { if (filter != null && !filter(feature)) { continue; } if (feature.isPolygon()) { if ((feature.geometry as GeoJSONPolygon) .isPointInside(latLonToGeoJSON(position))) { // compute distance to center of polygon final distance = distanceBetweenLatLng( polygonCenterMinmax((feature.geometry as GeoJSONPolygon) .coordinates[0] .map(geoJSONToLatLon) .toList()), position, 'meters'); hits.add((feature, distance)); } } else if (feature.isPoint()) { final distance = distanceBetweenLatLng( geoJSONToLatLon((feature.geometry as GeoJSONPoint).coordinates), position, 'meters'); if (distance <= 5) { hits.add((feature, distance)); } } } hits.sort((a, b) => a.$2.compareTo(b.$2)); return hits.map((e) => e.$1).toList(); } Future loadGeoJson(String geoJsonString) async { try { // print(geoJsonString); final featuresList = []; // print('doing'); final geojson = GeoJSONFeatureCollection.fromJSON(geoJsonString); // print('done'); for (final feature in geojson.features) { // print(feature); // print(feature?.properties); if (feature == null) continue; // print(feature.properties); final parsed = parseFeature(feature.properties ?? {}, feature.geometry, feature.id); if (parsed case Ok(:final ok)) { featuresList.add(ok); } else { print('Error parsing feature: $parsed'); } } features.value = featuresList; update(); } catch (e) { print('Error parsing GeoJSON: $e'); } } void handleTap(TapPosition tapPosition, LatLng point) { final hits = Get.find().computeHits(point, filter: (feature) => feature.isOnLevel(null) /* is not on a level */ || feature.isOnLevel(Get.find().currentLevel.value)); print('Hits: ${hits.map((e) => e.name)}'); if (hits.isNotEmpty) { focusOnFeature(hits[0], move: false, closestFeatures: hits.length > 1 ? hits.skip(1).toList() : null); // closest match } } void focusOnFeature(Feature feature, {bool move = true, List? closestFeatures}) { try { if (move) { mapController.move( feature .getCenterPoint() .expect("Couldn't find Center Point of target geometry"), mapController.camera.zoom, ); } } catch (e) { print("Error moving map controller: $e"); } showFeatureBottomSheet(feature, closestFeatures); } Future> getCurrentPosition() async { if (!_locationEnsured) { final ensureRes = await ensureLocationPermission(); if (ensureRes is Err) { // TODO: check that it works return ensureRes as Err; } } _locationEnsured = true; try { final pos = await Geolocator.getCurrentPosition( // desiredAccuracy: LocationAccuracy.high, timeLimit: Duration(minutes: 1), ); position.value = pos; return Ok(pos); } on TimeoutException catch (e) { return bail("Timeout while waiting for location lock: $e"); } } Future> subscribePosition() async { if (!_locationEnsured) { final ensureRes = await ensureLocationPermission(); if (ensureRes is Err) { // TODO: check that it works return ensureRes; } } _locationEnsured = true; Geolocator.getPositionStream( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 100, //timeLimit: Duration(minutes: 10) ), ).listen((pos) { position.value = pos; }); return const Ok(()); } }