feat: complete working navigation functions
This commit is contained in:
		| @ -2,7 +2,6 @@ import 'dart:convert'; | |||||||
|  |  | ||||||
| import 'package:anyhow/anyhow.dart'; | import 'package:anyhow/anyhow.dart'; | ||||||
| import 'package:flutter_map/flutter_map.dart'; | import 'package:flutter_map/flutter_map.dart'; | ||||||
| import 'package:flutter_map/src/gestures/positioned_tap_detector_2.dart'; |  | ||||||
| import 'package:geojson_vi/geojson_vi.dart'; | import 'package:geojson_vi/geojson_vi.dart'; | ||||||
| import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||||
| import 'package:latlong2/latlong.dart'; | import 'package:latlong2/latlong.dart'; | ||||||
| @ -95,8 +94,8 @@ class MyMapController extends GetxController { | |||||||
|         // print(feature?.properties); |         // print(feature?.properties); | ||||||
|         if (feature == null) continue; |         if (feature == null) continue; | ||||||
|         // print(feature.properties); |         // print(feature.properties); | ||||||
|         final parsed = parseFeature( |         final parsed = parseFeature(feature.properties ?? <String, dynamic>{}, | ||||||
|             feature.properties ?? <String, dynamic>{}, feature.geometry); |             feature.geometry, feature.id); | ||||||
|         if (parsed case Ok(:final ok)) { |         if (parsed case Ok(:final ok)) { | ||||||
|           featuresList.add(ok); |           featuresList.add(ok); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ class Feature with _$Feature { | |||||||
|     required GeoJSONGeometry geometry, |     required GeoJSONGeometry geometry, | ||||||
|     int? level, |     int? level, | ||||||
|     String? building, |     String? building, | ||||||
|  |     required String id, | ||||||
|   }) = _Feature; |   }) = _Feature; | ||||||
|  |  | ||||||
|   bool isPolygon() { |   bool isPolygon() { | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ mixin _$Feature { | |||||||
|   GeoJSONGeometry get geometry => throw _privateConstructorUsedError; |   GeoJSONGeometry get geometry => throw _privateConstructorUsedError; | ||||||
|   int? get level => throw _privateConstructorUsedError; |   int? get level => throw _privateConstructorUsedError; | ||||||
|   String? get building => throw _privateConstructorUsedError; |   String? get building => throw _privateConstructorUsedError; | ||||||
|  |   String get id => throw _privateConstructorUsedError; | ||||||
|  |  | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   $FeatureCopyWith<Feature> get copyWith => throw _privateConstructorUsedError; |   $FeatureCopyWith<Feature> get copyWith => throw _privateConstructorUsedError; | ||||||
| @ -38,7 +39,8 @@ abstract class $FeatureCopyWith<$Res> { | |||||||
|       String? description, |       String? description, | ||||||
|       GeoJSONGeometry geometry, |       GeoJSONGeometry geometry, | ||||||
|       int? level, |       int? level, | ||||||
|       String? building}); |       String? building, | ||||||
|  |       String id}); | ||||||
|  |  | ||||||
|   $FeatureTypeCopyWith<$Res> get type; |   $FeatureTypeCopyWith<$Res> get type; | ||||||
| } | } | ||||||
| @ -62,6 +64,7 @@ class _$FeatureCopyWithImpl<$Res, $Val extends Feature> | |||||||
|     Object? geometry = null, |     Object? geometry = null, | ||||||
|     Object? level = freezed, |     Object? level = freezed, | ||||||
|     Object? building = freezed, |     Object? building = freezed, | ||||||
|  |     Object? id = null, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_value.copyWith( |     return _then(_value.copyWith( | ||||||
|       name: null == name |       name: null == name | ||||||
| @ -88,6 +91,10 @@ class _$FeatureCopyWithImpl<$Res, $Val extends Feature> | |||||||
|           ? _value.building |           ? _value.building | ||||||
|           : building // ignore: cast_nullable_to_non_nullable |           : building // ignore: cast_nullable_to_non_nullable | ||||||
|               as String?, |               as String?, | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|     ) as $Val); |     ) as $Val); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -113,7 +120,8 @@ abstract class _$$FeatureImplCopyWith<$Res> implements $FeatureCopyWith<$Res> { | |||||||
|       String? description, |       String? description, | ||||||
|       GeoJSONGeometry geometry, |       GeoJSONGeometry geometry, | ||||||
|       int? level, |       int? level, | ||||||
|       String? building}); |       String? building, | ||||||
|  |       String id}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   $FeatureTypeCopyWith<$Res> get type; |   $FeatureTypeCopyWith<$Res> get type; | ||||||
| @ -136,6 +144,7 @@ class __$$FeatureImplCopyWithImpl<$Res> | |||||||
|     Object? geometry = null, |     Object? geometry = null, | ||||||
|     Object? level = freezed, |     Object? level = freezed, | ||||||
|     Object? building = freezed, |     Object? building = freezed, | ||||||
|  |     Object? id = null, | ||||||
|   }) { |   }) { | ||||||
|     return _then(_$FeatureImpl( |     return _then(_$FeatureImpl( | ||||||
|       name: null == name |       name: null == name | ||||||
| @ -162,6 +171,10 @@ class __$$FeatureImplCopyWithImpl<$Res> | |||||||
|           ? _value.building |           ? _value.building | ||||||
|           : building // ignore: cast_nullable_to_non_nullable |           : building // ignore: cast_nullable_to_non_nullable | ||||||
|               as String?, |               as String?, | ||||||
|  |       id: null == id | ||||||
|  |           ? _value.id | ||||||
|  |           : id // ignore: cast_nullable_to_non_nullable | ||||||
|  |               as String, | ||||||
|     )); |     )); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -175,7 +188,8 @@ class _$FeatureImpl extends _Feature { | |||||||
|       this.description, |       this.description, | ||||||
|       required this.geometry, |       required this.geometry, | ||||||
|       this.level, |       this.level, | ||||||
|       this.building}) |       this.building, | ||||||
|  |       required this.id}) | ||||||
|       : super._(); |       : super._(); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @ -190,10 +204,12 @@ class _$FeatureImpl extends _Feature { | |||||||
|   final int? level; |   final int? level; | ||||||
|   @override |   @override | ||||||
|   final String? building; |   final String? building; | ||||||
|  |   @override | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'Feature(name: $name, type: $type, description: $description, geometry: $geometry, level: $level, building: $building)'; |     return 'Feature(name: $name, type: $type, description: $description, geometry: $geometry, level: $level, building: $building, id: $id)'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @ -209,12 +225,13 @@ class _$FeatureImpl extends _Feature { | |||||||
|                 other.geometry == geometry) && |                 other.geometry == geometry) && | ||||||
|             (identical(other.level, level) || other.level == level) && |             (identical(other.level, level) || other.level == level) && | ||||||
|             (identical(other.building, building) || |             (identical(other.building, building) || | ||||||
|                 other.building == building)); |                 other.building == building) && | ||||||
|  |             (identical(other.id, id) || other.id == id)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   int get hashCode => Object.hash( |   int get hashCode => Object.hash( | ||||||
|       runtimeType, name, type, description, geometry, level, building); |       runtimeType, name, type, description, geometry, level, building, id); | ||||||
|  |  | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   @override |   @override | ||||||
| @ -230,7 +247,8 @@ abstract class _Feature extends Feature { | |||||||
|       final String? description, |       final String? description, | ||||||
|       required final GeoJSONGeometry geometry, |       required final GeoJSONGeometry geometry, | ||||||
|       final int? level, |       final int? level, | ||||||
|       final String? building}) = _$FeatureImpl; |       final String? building, | ||||||
|  |       required final String id}) = _$FeatureImpl; | ||||||
|   const _Feature._() : super._(); |   const _Feature._() : super._(); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @ -246,6 +264,8 @@ abstract class _Feature extends Feature { | |||||||
|   @override |   @override | ||||||
|   String? get building; |   String? get building; | ||||||
|   @override |   @override | ||||||
|  |   String get id; | ||||||
|  |   @override | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   _$$FeatureImplCopyWith<_$FeatureImpl> get copyWith => |   _$$FeatureImplCopyWith<_$FeatureImpl> get copyWith => | ||||||
|       throw _privateConstructorUsedError; |       throw _privateConstructorUsedError; | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import 'package:uninav/data/geo/model.dart'; | |||||||
| import 'package:yaml/yaml.dart'; | import 'package:yaml/yaml.dart'; | ||||||
|  |  | ||||||
| Result<Feature> parseFeature( | Result<Feature> parseFeature( | ||||||
|     Map<String, dynamic> properties, GeoJSONGeometry geometry) { |     Map<String, dynamic> properties, GeoJSONGeometry geometry, String id) { | ||||||
|   final name = properties['name'] as String?; |   final name = properties['name'] as String?; | ||||||
|   final description_yaml = properties['description'] as String? ?? ''; |   final description_yaml = properties['description'] as String? ?? ''; | ||||||
|   final layer = properties['layer'] as String?; |   final layer = properties['layer'] as String?; | ||||||
| @ -110,6 +110,7 @@ Result<Feature> parseFeature( | |||||||
|     geometry: geometry, |     geometry: geometry, | ||||||
|     level: level, |     level: level, | ||||||
|     building: building, |     building: building, | ||||||
|  |     id: id, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| import 'package:anyhow/anyhow.dart'; | import 'package:anyhow/anyhow.dart'; | ||||||
| import 'package:collection/collection.dart'; | import 'package:collection/collection.dart'; | ||||||
| import 'package:directed_graph/directed_graph.dart'; |  | ||||||
| import 'package:fast_immutable_collections/fast_immutable_collections.dart'; | import 'package:fast_immutable_collections/fast_immutable_collections.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
| import 'package:geojson_vi/geojson_vi.dart'; |  | ||||||
| import 'package:get/get.dart'; | import 'package:get/get.dart'; | ||||||
| import 'package:latlong2/latlong.dart'; | import 'package:latlong2/latlong.dart'; | ||||||
|  | import 'package:rust_core/iter.dart'; | ||||||
| import 'package:uninav/data/geo/model.dart'; | import 'package:uninav/data/geo/model.dart'; | ||||||
| import 'package:uninav/util/geojson_util.dart'; | import 'package:uninav/util/geojson_util.dart'; | ||||||
| import 'package:uninav/util/util.dart'; | import 'package:uninav/util/util.dart'; | ||||||
| @ -38,6 +38,12 @@ class GraphFeature with _$GraphFeature { | |||||||
|  |  | ||||||
|   double metersTo(GraphFeature other) => distanceTo(other, "meters"); |   double metersTo(GraphFeature other) => distanceTo(other, "meters"); | ||||||
|  |  | ||||||
|  |   String get id => when( | ||||||
|  |         buildingFloor: (floor, building) => building.id, | ||||||
|  |         portal: (fromFloor, from, toFloor, to, baseFeature) => baseFeature.id, | ||||||
|  |         basicFeature: (floor, building, feature) => feature.id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return when( |     return when( | ||||||
| @ -48,9 +54,102 @@ class GraphFeature with _$GraphFeature { | |||||||
|           'Feature (${formatFeatureTitle(feature)} ($building:$floor))', |           'Feature (${formatFeatureTitle(feature)} ($building:$floor))', | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     return when( | ||||||
|  |       buildingFloor: (floor, building) => Object.hash(floor, building), | ||||||
|  |       portal: (fromFloor, from, toFloor, to, baseFeature) => | ||||||
|  |           Object.hash(fromFloor, from, toFloor, to, baseFeature), | ||||||
|  |       basicFeature: (floor, building, feature) => | ||||||
|  |           Object.hash(floor, building, feature), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| bool eq(String? a, String? b) => a?.toLowerCase() == b?.toLowerCase(); |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is GraphFeature && | ||||||
|  |         other.when( | ||||||
|  |           buildingFloor: (floor, building) => | ||||||
|  |               this is BuildingFloor && | ||||||
|  |               (this as BuildingFloor).floor == floor && | ||||||
|  |               (this as BuildingFloor).building == building, | ||||||
|  |           portal: (fromFloor, from, toFloor, to, baseFeature) => | ||||||
|  |               this is Portal && | ||||||
|  |               (this as Portal).fromFloor == fromFloor && | ||||||
|  |               (this as Portal).from == from && | ||||||
|  |               (this as Portal).toFloor == toFloor && | ||||||
|  |               (this as Portal).to == to && | ||||||
|  |               (this as Portal).baseFeature == baseFeature, | ||||||
|  |           basicFeature: (floor, building, feature) => | ||||||
|  |               this is BasicFeature && | ||||||
|  |               (this as BasicFeature).floor == floor && | ||||||
|  |               (this as BasicFeature).building == building && | ||||||
|  |               (this as BasicFeature).feature == feature, | ||||||
|  |         ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class Graph { | ||||||
|  |   final List<(GraphFeature, double, GraphFeature)> _edges = []; | ||||||
|  |   final HashSet<GraphFeature> _nodes = HashSet(); | ||||||
|  |   final HashSet<(GraphFeature, GraphFeature)> _edgesSet = HashSet(); | ||||||
|  |  | ||||||
|  |   Iterable<GraphFeature> get nodes => _nodes.iter(); | ||||||
|  |  | ||||||
|  |   void addNode(GraphFeature node) { | ||||||
|  |     _nodes.add(node); | ||||||
|  |     if (node is BasicFeature && node.feature.name == 'H22') { | ||||||
|  |       print(node); | ||||||
|  |       print(node.hashCode); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void addEdge(GraphFeature from, GraphFeature to, double weight) { | ||||||
|  |     addNode(from); | ||||||
|  |     addNode(to); | ||||||
|  |     if (!_edgesSet.contains((from, to))) { | ||||||
|  |       _edgesSet.add((from, to)); | ||||||
|  |       _edges.add((from, weight, to)); | ||||||
|  |     } | ||||||
|  |     if (!_edgesSet.contains((to, from))) { | ||||||
|  |       _edgesSet.add((to, from)); | ||||||
|  |       _edges.add((to, weight, from)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   List<(GraphFeature, double, GraphFeature)> getEdges(GraphFeature node) { | ||||||
|  |     return _edges.where((edge) => edge.$1 == node).toList(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool contains(GraphFeature node) { | ||||||
|  |     return _nodes.contains(node); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool containsEdge(GraphFeature from, GraphFeature to) { | ||||||
|  |     return _edgesSet.contains((from, to)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String toString() { | ||||||
|  |     return 'Graph(_edges: $_edges, _nodes: $_nodes, _edgesSet: $_edgesSet)'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     if (identical(this, other)) return true; | ||||||
|  |  | ||||||
|  |     return other is Graph && | ||||||
|  |         listEquals(other._edges, _edges) && | ||||||
|  |         setEquals(other._nodes, _nodes) && | ||||||
|  |         setEquals(other._edgesSet, _edgesSet); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode => _edges.hashCode ^ _nodes.hashCode ^ _edgesSet.hashCode; | ||||||
|  | } | ||||||
|  |  | ||||||
| IList<GraphFeature> wrap(Feature feature, int floor, String buildingFrom) { | IList<GraphFeature> wrap(Feature feature, int floor, String buildingFrom) { | ||||||
|   return feature.type |   return feature.type | ||||||
| @ -60,7 +159,10 @@ IList<GraphFeature> wrap(Feature feature, int floor, String buildingFrom) { | |||||||
|         lift: (floors) => stairPortalGenerator(floors, floor, feature, 99), |         lift: (floors) => stairPortalGenerator(floors, floor, feature, 99), | ||||||
|         door: (connections) => |         door: (connections) => | ||||||
|             doorPortalGenerator(connections, floor, buildingFrom, feature), |             doorPortalGenerator(connections, floor, buildingFrom, feature), | ||||||
|         orElse: () => [GraphFeature.basicFeature(floor, buildingFrom, feature)], |         orElse: () => [ | ||||||
|  |           GraphFeature.basicFeature( | ||||||
|  |               floor, feature.building ?? buildingFrom, feature) | ||||||
|  |         ], | ||||||
|       ) |       ) | ||||||
|       .lock; |       .lock; | ||||||
| } | } | ||||||
| @ -70,8 +172,7 @@ List<GraphFeature> doorPortalGenerator( | |||||||
|   final portals = <GraphFeature>[]; |   final portals = <GraphFeature>[]; | ||||||
|  |  | ||||||
|   for (final connection in connections.where((c) => !eq(c, from))) { |   for (final connection in connections.where((c) => !eq(c, from))) { | ||||||
|     portals.add(GraphFeature.portal( |     portals.add(GraphFeature.portal(floor, from, floor, connection, feature)); | ||||||
|         floor, from, floor, connection.toLowerCase(), feature)); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return portals; |   return portals; | ||||||
| @ -83,12 +184,12 @@ List<GraphFeature> stairPortalGenerator( | |||||||
|   final portals = <GraphFeature>[]; |   final portals = <GraphFeature>[]; | ||||||
|   for (int i = 1; i <= maxDist; i++) { |   for (int i = 1; i <= maxDist; i++) { | ||||||
|     if (floors.contains(floor - i)) { |     if (floors.contains(floor - i)) { | ||||||
|       portals.add(GraphFeature.portal(floor, feature.building!.toLowerCase(), |       portals.add(GraphFeature.portal( | ||||||
|           floor - i, feature.building!.toLowerCase(), feature)); |           floor, feature.building!, floor - i, feature.building!, feature)); | ||||||
|     } |     } | ||||||
|     if (floors.contains(floor + i)) { |     if (floors.contains(floor + i)) { | ||||||
|       portals.add(GraphFeature.portal(floor, feature.building!.toLowerCase(), |       portals.add(GraphFeature.portal( | ||||||
|           floor + i, feature.building!.toLowerCase(), feature)); |           floor, feature.building!, floor + i, feature.building!, feature)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   return portals; |   return portals; | ||||||
| @ -142,117 +243,34 @@ List<GraphFeature> findAdjacent( | |||||||
|   return adjacentFeatures; |   return adjacentFeatures; | ||||||
| } | } | ||||||
|  |  | ||||||
| List<GraphFeature> createGraphList( | Graph makeGraph(GraphFeature origin, List<Feature> allFeatures, | ||||||
|     GraphFeature origin, List<Feature> allFeatures, |     [Graph? graph]) { | ||||||
|     [Set<GraphFeature>? visited]) { |  | ||||||
|   // final usedFeatures = <GraphFeature>[origin]; |   // final usedFeatures = <GraphFeature>[origin]; | ||||||
|  |  | ||||||
|   visited ??= <GraphFeature>{origin}; |   graph ??= Graph(); | ||||||
|  |   graph.addNode(origin); | ||||||
|  |  | ||||||
|   final adjacent = findAdjacent(origin, allFeatures); |   final adjacent = findAdjacent(origin, allFeatures); | ||||||
|   for (final feature in adjacent.asSet()..removeAll(visited)) { |   for (final feature in adjacent.asSet()..removeAll(graph.nodes)) { | ||||||
|     visited.add(feature); |     graph.addEdge(origin, feature, origin.metersTo(feature)); | ||||||
|     final deeper = createGraphList(feature, allFeatures, visited); |     final _ = makeGraph(feature, allFeatures, graph); | ||||||
|     visited.addAll(deeper); |     // graph.addAll(deeper); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return visited.toList(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Map<GraphFeature, Map<GraphFeature, double>> createGraphMap( |  | ||||||
|     GraphFeature origin, List<Feature> allFeatures) { |  | ||||||
|   final graphList = createGraphList(origin, allFeatures); |  | ||||||
|   final graphMap = <GraphFeature, Map<GraphFeature, double>>{}; |  | ||||||
|   for (final node in graphList) { |  | ||||||
|     final adjacents = node.when( |  | ||||||
|       buildingFloor: (floor, building) { |  | ||||||
|         return graphList |  | ||||||
|             .where((f) => |  | ||||||
|                 f is Portal && |  | ||||||
|                     eq(f.from, building.name) && |  | ||||||
|                     f.fromFloor == floor || |  | ||||||
|                 f is BasicFeature && |  | ||||||
|                     eq(f.building, building.name) && |  | ||||||
|                     f.floor == floor) |  | ||||||
|             .map((f) => f.when( |  | ||||||
|                   portal: (fromFloor, from, toFloor, to, baseFeature) => ( |  | ||||||
|                     f, |  | ||||||
|                     f.metersTo(node), |  | ||||||
|                   ), |  | ||||||
|                   basicFeature: (floor, building, feature) => |  | ||||||
|                       (f, f.metersTo(node)), |  | ||||||
|                   buildingFloor: (floor, building) => throw StateError( |  | ||||||
|                       "BUG: createGraphMap(): BuildingFloors shouldn't " |  | ||||||
|                       "be matched by BuildingFloors"), |  | ||||||
|                 )); |  | ||||||
|       }, |  | ||||||
|       portal: (fromFloor, from, toFloor, to, baseFeature) { |  | ||||||
|         return graphList |  | ||||||
|             .where((f) => |  | ||||||
|                 f is BuildingFloor && |  | ||||||
|                 eq(f.building.name, to) && |  | ||||||
|                 f.floor == toFloor) |  | ||||||
|             .map((f) => f.when( |  | ||||||
|                   portal: (fromFloor, from, toFloor, to, baseFeature) => |  | ||||||
|                       throw StateError( |  | ||||||
|                           "BUG: createGraphMap(): Portals shouldn't " |  | ||||||
|                           "be matched by Portals"), |  | ||||||
|                   basicFeature: (floor, building, feature) => throw StateError( |  | ||||||
|                       "BUG: createGraphMap(): BasicFeatures shouldn't " |  | ||||||
|                       "be matched by BasicFeatures"), |  | ||||||
|                   buildingFloor: (floor, building) => ( |  | ||||||
|                     f, |  | ||||||
|                     f.metersTo(node) + |  | ||||||
|                         5 /* 5 extra meters for all portals. TODO: smarter!*/ |  | ||||||
|                   ), |  | ||||||
|                 )); |  | ||||||
|       }, |  | ||||||
|       basicFeature: (floor, building, feature) { |  | ||||||
|         return graphList |  | ||||||
|             .where((f) => |  | ||||||
|                 f is BuildingFloor && |  | ||||||
|                 eq(f.building.name, building) && |  | ||||||
|                 f.floor == floor) |  | ||||||
|             .map((f) => f.when( |  | ||||||
|                   portal: (fromFloor, from, toFloor, to, baseFeature) => |  | ||||||
|                       throw StateError( |  | ||||||
|                           "BUG: createGraphMap(): Portal shouldn't be matched " |  | ||||||
|                           "by BasicFeature"), |  | ||||||
|                   basicFeature: (floor, building, feature) => throw StateError( |  | ||||||
|                       "BUG: createGraphMap(): BasicFeatures shouldn't " |  | ||||||
|                       "be matched by BasicFeatures"), |  | ||||||
|                   buildingFloor: (floor, building) => (f, f.metersTo(node)), |  | ||||||
|                 )); |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     graphMap[node] = |  | ||||||
|         Map.fromEntries(adjacents.map((tup) => MapEntry(tup.$1, tup.$2))); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return graphMap; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| WeightedDirectedGraph<GraphFeature, double> createGraph( |  | ||||||
|     GraphFeature origin, List<Feature> allFeatures) { |  | ||||||
|   final map = createGraphMap(origin, allFeatures); |  | ||||||
|   final graph = WeightedDirectedGraph<GraphFeature, double>( |  | ||||||
|     map, |  | ||||||
|     summation: sum, |  | ||||||
|     zero: 0.0, |  | ||||||
|     comparator: (a, b) => compareGraphFeatures(a, b), |  | ||||||
|   ); |  | ||||||
|   return graph; |   return graph; | ||||||
| } | } | ||||||
|  |  | ||||||
| Result<List<(GraphFeature, double)>> findShortestPath( | Result<List<(GraphFeature, double)>> findShortestPath(GraphFeature origin, | ||||||
|     GraphFeature origin, GraphFeature destination, List<Feature> allFeatures, |     bool Function(GraphFeature) destinationSelector, List<Feature> allFeatures, | ||||||
|     [heuristicVariant = "zero", heuristicMultiplier = 0.2]) { |     {heuristicVariant = "zero", heuristicMultiplier = 0.2}) { | ||||||
|   var graph = createGraphMap(origin, allFeatures); |   Graph graph = makeGraph(origin, allFeatures); | ||||||
|  |  | ||||||
|   if (!(graph.keys.contains(origin) && |   final GraphFeature? destination = | ||||||
|       graph.values.firstWhereOrNull((vals) => vals.containsKey(destination)) != |       graph.nodes.firstWhereOrNull(destinationSelector); | ||||||
|           null)) { |  | ||||||
|  |   if (!(graph.contains(origin) && | ||||||
|  |       destination != null && | ||||||
|  |       graph.contains(destination))) { | ||||||
|     return bail("Origin or destination not in graph"); |     return bail("Origin or destination not in graph"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @ -299,10 +317,10 @@ Result<List<(GraphFeature, double)>> findShortestPath( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // expand node |     // expand node | ||||||
|     final adjacents = graph[node]!; |     final edges = graph.getEdges(node); | ||||||
|     for (final entry in adjacents.entries) { |     for (final entry in edges) { | ||||||
|       final adjNode = entry.key; |       final adjNode = entry.$3; | ||||||
|       final adjCost = entry.value; |       final adjCost = entry.$2; | ||||||
|  |  | ||||||
|       if (closedlist.contains(adjNode)) { |       if (closedlist.contains(adjNode)) { | ||||||
|         continue; |         continue; | ||||||
| @ -349,77 +367,3 @@ Result<List<(GraphFeature, double)>> findShortestPath( | |||||||
|  |  | ||||||
|   return bail("No path found"); |   return bail("No path found"); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Compares two [GraphFeature] instances and determines their relative order. |  | ||||||
| /// |  | ||||||
| /// The comparison is based on the specific subtypes and properties of the |  | ||||||
| /// [GraphFeature] instances. The comparison logic is as follows: |  | ||||||
| /// |  | ||||||
| /// 1. If both instances are [BuildingFloor], they are compared first by the |  | ||||||
| ///    building name and then by the floor number. |  | ||||||
| /// 2. If one instance is a [Portal] and the other is a [BuildingFloor] or |  | ||||||
| ///    [BasicFeature], the [Portal] is considered greater. |  | ||||||
| /// 3. If both instances are [Portal], they are compared first by the `from` |  | ||||||
| ///    property, then by the `to` property, and finally by the `baseFeature` name. |  | ||||||
| /// 4. If one instance is a [BasicFeature] and the other is a [BuildingFloor] or |  | ||||||
| ///    [Portal], the [BasicFeature] is considered greater. |  | ||||||
| /// 5. If both instances are [BasicFeature], they are compared first by the |  | ||||||
| ///    building name, then by the floor number, and finally by the feature name. |  | ||||||
| /// |  | ||||||
| /// Returns a negative value if [a] is considered "less than" [b], a positive |  | ||||||
| /// value if [a] is considered "greater than" [b], and zero if they are considered |  | ||||||
| /// equal. |  | ||||||
| /// |  | ||||||
| /// This function can be used as a comparator for sorting or ordering |  | ||||||
| /// [GraphFeature] instances. |  | ||||||
| int compareGraphFeatures(GraphFeature a, GraphFeature b) { |  | ||||||
|   return a.when( |  | ||||||
|     buildingFloor: (floorA, buildingA) { |  | ||||||
|       return b.when( |  | ||||||
|         buildingFloor: (floorB, buildingB) { |  | ||||||
|           final buildingComparison = buildingA.name.compareTo(buildingB.name); |  | ||||||
|           if (buildingComparison != 0) { |  | ||||||
|             return buildingComparison; |  | ||||||
|           } |  | ||||||
|           return floorA.compareTo(floorB); |  | ||||||
|         }, |  | ||||||
|         portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) => -1, |  | ||||||
|         basicFeature: (floorB, buildingB, featureB) => -1, |  | ||||||
|       ); |  | ||||||
|     }, |  | ||||||
|     portal: (fromFloorA, fromA, toFloorA, toA, baseFeatureA) { |  | ||||||
|       return b.when( |  | ||||||
|         buildingFloor: (floorB, buildingB) => 1, |  | ||||||
|         portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) { |  | ||||||
|           final fromComparison = fromA.compareTo(fromB); |  | ||||||
|           if (fromComparison != 0) { |  | ||||||
|             return fromComparison; |  | ||||||
|           } |  | ||||||
|           final toComparison = toA.compareTo(toB); |  | ||||||
|           if (toComparison != 0) { |  | ||||||
|             return toComparison; |  | ||||||
|           } |  | ||||||
|           return baseFeatureA.name.compareTo(baseFeatureB.name); |  | ||||||
|         }, |  | ||||||
|         basicFeature: (floorB, buildingB, featureB) => -1, |  | ||||||
|       ); |  | ||||||
|     }, |  | ||||||
|     basicFeature: (floorA, buildingA, featureA) { |  | ||||||
|       return b.when( |  | ||||||
|         buildingFloor: (floorB, buildingB) => 1, |  | ||||||
|         portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) => 1, |  | ||||||
|         basicFeature: (floorB, buildingB, featureB) { |  | ||||||
|           final buildingComparison = buildingA.compareTo(buildingB); |  | ||||||
|           if (buildingComparison != 0) { |  | ||||||
|             return buildingComparison; |  | ||||||
|           } |  | ||||||
|           final floorComparison = floorA.compareTo(floorB); |  | ||||||
|           if (floorComparison != 0) { |  | ||||||
|             return floorComparison; |  | ||||||
|           } |  | ||||||
|           return featureA.name.compareTo(featureB.name); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     }, |  | ||||||
|   ); |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -144,24 +144,6 @@ class _$BuildingFloorImpl extends BuildingFloor { | |||||||
|   @override |   @override | ||||||
|   final Feature building; |   final Feature building; | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   String toString() { |  | ||||||
|     return 'GraphFeature.buildingFloor(floor: $floor, building: $building)'; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) { |  | ||||||
|     return identical(this, other) || |  | ||||||
|         (other.runtimeType == runtimeType && |  | ||||||
|             other is _$BuildingFloorImpl && |  | ||||||
|             (identical(other.floor, floor) || other.floor == floor) && |  | ||||||
|             (identical(other.building, building) || |  | ||||||
|                 other.building == building)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => Object.hash(runtimeType, floor, building); |  | ||||||
|  |  | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   @override |   @override | ||||||
|   @pragma('vm:prefer-inline') |   @pragma('vm:prefer-inline') | ||||||
| @ -341,29 +323,6 @@ class _$PortalImpl extends Portal { | |||||||
|   @override |   @override | ||||||
|   final Feature baseFeature; |   final Feature baseFeature; | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   String toString() { |  | ||||||
|     return 'GraphFeature.portal(fromFloor: $fromFloor, from: $from, toFloor: $toFloor, to: $to, baseFeature: $baseFeature)'; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) { |  | ||||||
|     return identical(this, other) || |  | ||||||
|         (other.runtimeType == runtimeType && |  | ||||||
|             other is _$PortalImpl && |  | ||||||
|             (identical(other.fromFloor, fromFloor) || |  | ||||||
|                 other.fromFloor == fromFloor) && |  | ||||||
|             (identical(other.from, from) || other.from == from) && |  | ||||||
|             (identical(other.toFloor, toFloor) || other.toFloor == toFloor) && |  | ||||||
|             (identical(other.to, to) || other.to == to) && |  | ||||||
|             (identical(other.baseFeature, baseFeature) || |  | ||||||
|                 other.baseFeature == baseFeature)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => |  | ||||||
|       Object.hash(runtimeType, fromFloor, from, toFloor, to, baseFeature); |  | ||||||
|  |  | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   @override |   @override | ||||||
|   @pragma('vm:prefer-inline') |   @pragma('vm:prefer-inline') | ||||||
| @ -529,25 +488,6 @@ class _$BasicFeatureImpl extends BasicFeature { | |||||||
|   @override |   @override | ||||||
|   final Feature feature; |   final Feature feature; | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   String toString() { |  | ||||||
|     return 'GraphFeature.basicFeature(floor: $floor, building: $building, feature: $feature)'; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   bool operator ==(Object other) { |  | ||||||
|     return identical(this, other) || |  | ||||||
|         (other.runtimeType == runtimeType && |  | ||||||
|             other is _$BasicFeatureImpl && |  | ||||||
|             (identical(other.floor, floor) || other.floor == floor) && |  | ||||||
|             (identical(other.building, building) || |  | ||||||
|                 other.building == building) && |  | ||||||
|             (identical(other.feature, feature) || other.feature == feature)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   int get hashCode => Object.hash(runtimeType, floor, building, feature); |  | ||||||
|  |  | ||||||
|   @JsonKey(ignore: true) |   @JsonKey(ignore: true) | ||||||
|   @override |   @override | ||||||
|   @pragma('vm:prefer-inline') |   @pragma('vm:prefer-inline') | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| import 'package:flutter/cupertino.dart'; |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:uninav/data/geo/model.dart'; | import 'package:uninav/data/geo/model.dart'; | ||||||
|  |  | ||||||
|  | bool eq(String? a, String? b) => a?.toLowerCase() == b?.toLowerCase(); | ||||||
|  |  | ||||||
| String formatDuration(Duration duration) { | String formatDuration(Duration duration) { | ||||||
|   final days = duration.inDays; |   final days = duration.inDays; | ||||||
|   final hours = duration.inHours.remainder(24); |   final hours = duration.inHours.remainder(24); | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ String formatGraphFeature(GraphFeature feature) { | |||||||
|  |  | ||||||
| void main() { | void main() { | ||||||
|   TestWidgetsFlutterBinding.ensureInitialized(); |   TestWidgetsFlutterBinding.ensureInitialized(); | ||||||
|  |  | ||||||
|   group('findAdjacent', () { |   group('findAdjacent', () { | ||||||
|     late MyMapController mapController; |     late MyMapController mapController; | ||||||
|     late List<Feature> allFeatures; |     late List<Feature> allFeatures; | ||||||
| @ -34,6 +35,69 @@ void main() { | |||||||
|       allFeatures = mapController.features; |       allFeatures = mapController.features; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     test('generates a graph (new)', () { | ||||||
|  |       // Find a building feature | ||||||
|  |       // final buildingFeature = allFeatures.firstWhere((f) => f.type is Building); | ||||||
|  |       final buildingFeature = allFeatures | ||||||
|  |           .firstWhere((f) => f.type is Building && eq(f.name, 'o28')); | ||||||
|  |  | ||||||
|  |       // final endFeature = allFeatures | ||||||
|  |       //     .firstWhere((f) => f.type is Building && eq(f.name, 'o25')); | ||||||
|  |  | ||||||
|  |       // final targetFeature = allFeatures | ||||||
|  |       //     .firstWhere((f) => f.type is LectureHall && eq(f.name, 'H22')); | ||||||
|  |  | ||||||
|  |       // final wrapped = | ||||||
|  |       //     wrap(targetFeature, targetFeature.level!, targetFeature.building!); | ||||||
|  |       // print(wrapped); | ||||||
|  |  | ||||||
|  |       final graph = makeGraph( | ||||||
|  |           wrap(buildingFeature, 2, buildingFeature.name).first, allFeatures); | ||||||
|  |  | ||||||
|  |       final wrapped = [ | ||||||
|  |         graph.nodes.firstWhere((element) => | ||||||
|  |             element is BasicFeature && eq(element.feature.name, "H22")) | ||||||
|  |       ]; | ||||||
|  |  | ||||||
|  |       print(graph.contains(wrapped.first)); // print(graph); | ||||||
|  |       print(wrapped.first.hashCode); // print(graph); | ||||||
|  |       // print(wrap(targetFeature, targetFeature.level!, targetFeature.building!) | ||||||
|  |       //     .first | ||||||
|  |       //     .hashCode); | ||||||
|  |       // print(wrapped.first == | ||||||
|  |       //     wrap(targetFeature, targetFeature.level!, targetFeature.building!) | ||||||
|  |       //         .first); | ||||||
|  |  | ||||||
|  |       // print(graph.toString()); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('tries to find a path through the graph using own method', () async { | ||||||
|  |       // Find a building feature | ||||||
|  |       // final buildingFeature = allFeatures.firstWhere((f) => f.type is Building); | ||||||
|  |       final startFeature = allFeatures | ||||||
|  |           .firstWhere((f) => f.type is Building && eq(f.name, 'o25')); | ||||||
|  |  | ||||||
|  |       // final endFeature = allFeatures | ||||||
|  |       //     .firstWhere((f) => f.type is Building && eq(f.name, 'o25')); | ||||||
|  |  | ||||||
|  |       final endFeature = allFeatures | ||||||
|  |           .firstWhere((f) => f.type is LectureHall && eq(f.name, 'H22')); | ||||||
|  |  | ||||||
|  |       print(endFeature); | ||||||
|  |  | ||||||
|  |       final path = findShortestPath( | ||||||
|  |         wrap(startFeature, 4, startFeature.name).first, | ||||||
|  |         (f) => f is BasicFeature && eq(f.feature.name, 'H1'), | ||||||
|  |         // wrap(endFeature, 2, "o28").first, | ||||||
|  |         allFeatures, | ||||||
|  |       ); | ||||||
|  |       print(path | ||||||
|  |           .unwrap() | ||||||
|  |           .map((e) => "${formatGraphFeature(e.$1)} (${e.$2}m)") | ||||||
|  |           .join(' -> ')); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | /* | ||||||
|     test('generates a graph', () { |     test('generates a graph', () { | ||||||
|       // Find a building feature |       // Find a building feature | ||||||
|       // final buildingFeature = allFeatures.firstWhere((f) => f.type is Building); |       // final buildingFeature = allFeatures.firstWhere((f) => f.type is Building); | ||||||
| @ -172,5 +236,6 @@ void main() { | |||||||
|       print(path.map(formatGraphFeature).join('\n')); |       print(path.map(formatGraphFeature).join('\n')); | ||||||
|     }); |     }); | ||||||
|     */ |     */ | ||||||
|  |     */ | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								test/scratch_1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/scratch_1
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user