uninav/lib/controllers/map_controller.dart
2024-04-21 16:27:48 +02:00

195 lines
5.6 KiB
Dart

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<Feature> features = <Feature>[].obs;
final currentLevel = 1.obs;
final levels = <int>[1].obs;
final Rx<Position?> position = null.obs;
bool _locationEnsured = false;
@override
onInit() async {
print("init");
ever(features, refreshLevels);
super.onInit();
}
void refreshLevels(List<Feature> curFeatures) {
print("refreshLevels");
final newLevels = <int>[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<void> 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<Feature> 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<void> loadGeoJson(String geoJsonString) async {
try {
// print(geoJsonString);
final featuresList = <Feature>[];
// 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 ?? <String, dynamic>{},
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<MyMapController>().computeHits(point,
filter: (feature) =>
feature.isOnLevel(null) /* is not on a level */ ||
feature.isOnLevel(Get.find<MyMapController>().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<Feature>? 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<Result<Position>> getCurrentPosition() async {
if (!_locationEnsured) {
final ensureRes = await ensureLocationPermission();
if (ensureRes is Err) {
// TODO: check that it works
return ensureRes as Err<Position>;
}
}
_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<Result<()>> 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(());
}
}