feat: major progress

This commit is contained in:
2024-04-20 16:32:01 +02:00
parent ba71707514
commit be359980bb
19 changed files with 5328 additions and 50 deletions

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/get_navigation.dart';
import 'package:uninav/map.dart';
class MyDrawer extends StatelessWidget {
@override
@ -20,9 +23,11 @@ class MyDrawer extends StatelessWidget {
),
),
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
leading: Icon(Icons.map),
title: Text('Map Page'),
onTap: () {
Get.back(); // close drawer
Get.toNamed('/map');
// Handle menu item tap
},
),
@ -30,6 +35,8 @@ class MyDrawer extends StatelessWidget {
leading: Icon(Icons.settings),
title: Text('Settings'),
onTap: () {
Get.back(); // close drawer
Get.toNamed('/settings');
// Handle menu item tap
},
),

View File

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
class HamburgerMenu extends StatelessWidget {
const HamburgerMenu({
super.key,
});
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
);
}
}

View File

@ -0,0 +1,256 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/map.dart';
import 'package:uninav/util/geomath.dart';
List<Widget> renderLevel(int level) {
return <Widget>[
LevelLayer(
filter: (feature) =>
feature.level == level && feature.type is LectureHall,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.orange.withOpacity(0.2),
borderColor: Colors.orange,
isFilled: true,
borderStrokeWidth: 1,
),
)
.unwrap(),
markerConstructor: (feature) => Marker(
width: 150,
height: 60,
point: feature.getPoint().unwrap(),
builder: (cx) => Center(
child: Column(
children: [
Icon(
Icons.class_,
color: Colors.black,
),
Text('${feature.name}'),
],
),
),
)),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Room,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.green.withOpacity(0.2),
borderColor: Colors.green,
isFilled: true,
borderStrokeWidth: 1,
),
)
.unwrap(),
),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Door,
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => const Icon(
Icons.door_front_door,
color: Colors.brown,
),
);
},
),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Toilet,
markerConstructor: (feature) {
final type = (feature.type as Toilet).toilet_type;
IconData icon;
switch (type.toLowerCase()) {
case 'male':
icon = Icons.male;
break;
case 'female':
icon = Icons.female;
break;
case 'handicap':
icon = Icons.wheelchair_pickup;
break;
default:
print("WARN: Toilet didn't have recognizable type! "
"(Level ${feature.level}, Name ${feature.name}, "
"Location: ${feature.getPoint().unwrap()})");
icon = Icons.wc;
break;
}
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => Icon(
icon,
color: Colors.purple,
),
rotateAlignment: Alignment.center,
);
},
),
LevelLayer(
filter: (feature) =>
feature.type is Stairs &&
(feature.type as Stairs).connects_levels.contains(level),
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => Icon(
Icons.stairs_outlined,
color: Colors.deepPurple.shade300,
),
);
},
),
LevelLayer(
filter: (feature) =>
feature.type is Lift &&
(feature.type as Lift).connects_levels.contains(level),
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => const Icon(
Icons.elevator_outlined,
color: Colors.deepPurple,
),
);
},
),
];
}
class LevelLayer extends StatelessWidget {
final bool Function(Feature)? filter;
final Polygon Function(Feature)? polyConstructor;
final Marker Function(LatLng, String)? polyCenterMarkerConstructor;
final Marker Function(Feature)? markerConstructor;
final int? level;
const LevelLayer({
this.level,
this.filter,
this.polyConstructor,
this.polyCenterMarkerConstructor,
this.markerConstructor,
super.key,
});
@override
Widget build(BuildContext context) {
final myMapController = Get.find<MyMapController>();
return Obx(() {
final List<Polygon> filteredPolygons = [];
final List<Marker> polygonCenterMarkers = [];
final List<Marker> filteredMarkers = [];
for (final feature in myMapController.features) {
if (filter == null || filter!(feature)) {
if (feature.isPolygon()) {
if (polyConstructor != null) {
filteredPolygons.add(polyConstructor!(feature));
} else {
filteredPolygons.add(feature.getPolygon().unwrap());
}
// calculate polygon center
final center =
polygonCenterMinmax(feature.getPolygon().unwrap().points);
if (polyCenterMarkerConstructor != null) {
polygonCenterMarkers
.add(polyCenterMarkerConstructor!(center, feature.name));
} else {
polygonCenterMarkers.add(Marker(
width: 100,
height: 100,
point: center,
builder: (cx) => Center(
child: Text(
feature.name,
style: const TextStyle(
color: Colors.black54,
// backgroundColor: Colors.white,
),
),
),
));
}
} else if (feature.isPoint()) {
if (markerConstructor != null) {
filteredMarkers.add(markerConstructor!(feature));
} else {
final point = feature.getPoint().unwrap();
filteredMarkers.add(Marker(
width: 100,
height: 100,
point: point,
builder: (cx) => Center(
child: Text(
feature.name,
style: const TextStyle(
color: Colors.black54,
// backgroundColor: Colors.white,
),
),
),
));
}
}
}
}
// print(filteredPolygons.length);
// print(filteredPolygons);
// print(filteredPolygons[0].points[0]);
// print(myMapController.features.length);
final List<Widget> widgets = [];
if (filteredPolygons.isNotEmpty) {
if (polyConstructor != null) {
widgets.add(PolygonLayer(polygons: filteredPolygons));
} else {
widgets.add(PolygonLayer(
polygons: filteredPolygons
.map((poly) => Polygon(
points: poly.points,
borderColor: Colors.black26,
borderStrokeWidth: 2.0,
))
.toList()));
}
widgets.add(MarkerLayer(
markers: polygonCenterMarkers,
rotate: true,
));
}
if (filteredMarkers.isNotEmpty) {
widgets.add(MarkerLayer(markers: filteredMarkers, rotate: true));
}
return Stack(children: widgets);
});
}
}

View File

@ -0,0 +1,67 @@
import 'dart:convert';
import 'package:anyhow/anyhow.dart';
import 'package:geojson/geojson.dart';
import 'package:get/get.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/data/geo/parser.dart';
class MyMapController extends GetxController {
// constructor that calls loadgeojson with a default geojson string
final RxList<Feature> features = <Feature>[].obs;
final currentLevel = 1.obs;
final levels = <int>[1].obs;
@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;
}
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;
return const Ok(());
}
Future<void> loadGeoJson(String geoJsonString) async {
try {
// print(geoJsonString);
final featuresList = <Feature>[];
final geojson = GeoJson();
await geojson.parse(geoJsonString);
for (final feature in geojson.features) {
print(feature.properties);
final parsed =
parseFeature(feature.properties ?? <String, dynamic>{}, feature);
if (parsed case Ok(:final ok)) {
featuresList.add(ok);
}
}
features.value = featuresList;
} catch (e) {
print('Error parsing GeoJSON: $e');
}
}
}

70
lib/data/geo/model.dart Normal file
View File

@ -0,0 +1,70 @@
import 'package:anyhow/anyhow.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:geojson/geojson.dart';
import 'package:latlong2/latlong.dart';
part 'model.freezed.dart';
@freezed
class Feature with _$Feature {
const Feature._();
const factory Feature({
required String name,
required FeatureType type,
String? description,
required dynamic geometry,
int? level,
}) = _Feature;
bool isPolygon() {
return geometry is GeoJsonFeature<GeoJsonPolygon>;
}
bool isPoint() {
return geometry is GeoJsonFeature<GeoJsonPoint>;
}
Result<Polygon> getPolygon({Polygon Function(List<LatLng>)? constructor}) {
if (isPolygon()) {
constructor ??= (pts) => Polygon(
points: pts, borderColor: Colors.black26, borderStrokeWidth: 2.0);
final polygon = geometry as GeoJsonFeature<GeoJsonPolygon>;
// print(polygon.geometry!.geoSeries[0].geoPoints);
final points = polygon.geometry!.geoSeries[0].geoPoints
.map((e) => LatLng(e.latitude, e.longitude))
.toList();
// print(points);
return Ok(constructor(points));
} else {
return bail("Feature Geometry is not a Polygon");
}
}
Result<LatLng> getPoint() {
if (isPoint()) {
final point = geometry as GeoJsonFeature<GeoJsonPoint>;
return Ok(LatLng(point.geometry!.geoPoint.latitude,
point.geometry!.geoPoint.longitude));
} else {
return bail("Feature Geometry is not a Point");
}
}
}
@freezed
class FeatureType with _$FeatureType {
// multiple feature types like lecture hall, toliet, ...
const factory FeatureType.building() = Building;
const factory FeatureType.lectureHall() = LectureHall;
const factory FeatureType.room() = Room;
const factory FeatureType.door(List<String> connects) = Door;
const factory FeatureType.toilet(String toilet_type) = Toilet;
const factory FeatureType.stairs(List<int> connects_levels) = Stairs;
const factory FeatureType.lift(List<int> connects_levels) = Lift;
const factory FeatureType.publicTransport(
List<String> bus_lines, List<String> tram_lines) = PublicTransport;
}

File diff suppressed because it is too large Load Diff

142
lib/data/geo/parser.dart Normal file
View File

@ -0,0 +1,142 @@
import 'package:anyhow/anyhow.dart';
import 'package:geojson/geojson.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:yaml/yaml.dart';
Result<Feature> parseFeature(
Map<String, dynamic> properties, dynamic geometry) {
final name = properties['name'] as String?;
final description_yaml = properties['description'] as String? ?? '';
final layer = properties['layer'] as String?;
int? level;
// check if layer key contains a digit using \d+ regex
final layerNum = RegExp(r'\d+').firstMatch(layer ?? '');
if (layerNum != null) {
level = int.parse(layerNum.group(0)!);
}
// try parse yaml
if (description_yaml == null) {
print("warn: Description key is missing for feature $name");
}
if (layer == null) {
return bail("Layer key \'layer\' is missing for feature $name");
}
dynamic yaml;
try {
yaml = loadYaml(description_yaml);
} on YamlException catch (e) {
return bail("Couldn't parse YAML in description for feature $name: $e");
}
yaml = yaml as YamlMap? ?? {};
final description = yaml['desription'] as String?;
print("yaml: $yaml");
var raw_type = yaml['type'] as String?;
if (raw_type == null && layer?.toLowerCase() == 'buildings') {
raw_type = 'building';
}
if (raw_type == null) {
return bail("Type key \'type\' is missing for feature $name");
}
FeatureType type;
try {
switch (raw_type) {
case 'building':
type = FeatureType.building();
case 'lift':
final list = getYamlList<int>(yaml, 'connects_levels')
.expect("Couldn't parse 'connects_levels' for feature $name");
type = FeatureType.lift(list);
break;
case 'stairs':
final list = getYamlList<int>(yaml, 'connects_levels')
.expect("Couldn't parse 'connects_levels' for feature $name");
type = FeatureType.stairs(list);
break;
case 'lecture_hall':
type = FeatureType.lectureHall();
break;
case 'room':
type = FeatureType.room();
break;
case 'door':
final list = getYamlList<String>(yaml, 'connects')
.expect("Couldn't parse 'connects' for feature $name");
type = FeatureType.door(list);
break;
case 'toilet':
final toiletType = getYamlKey<String>(yaml, 'toilet_type')
.expect("Couldn't parse 'toilet_type' for feature $name");
type = FeatureType.toilet(toiletType);
break;
case 'public_transport':
final busLines = getYamlList<dynamic>(yaml, 'bus_lines')
.expect("Couldn't parse 'bus_lines' for feature $name");
final tramLines = getYamlList<dynamic>(yaml, 'tram_lines')
.expect("Couldn't parse 'tram_lines' for feature $name");
type = FeatureType.publicTransport(
stringifyList(busLines)
.expect('Couldn\'t stringify \'bus_lines\' for feature $name'),
stringifyList(tramLines)
.expect('Couldn\'t stringify \'tram_lines\' for feature $name'),
);
break;
default:
return bail("Unknown feature type $raw_type for feature $name");
}
} catch (e) {
return bail("Failed to parse feature type for feature $name: $e");
}
return Ok(Feature(
name: name ?? 'Unknown',
type: type,
description: description,
geometry: geometry,
level: level,
));
}
Result<List<String>> stringifyList(List<dynamic> tramLines) {
try {
return Ok(tramLines.map((e) => e.toString()).toList());
} catch (e) {
return bail("Couldn't stringify list: $e");
}
}
Result<List<T>> getYamlList<T>(YamlMap yaml, String key) {
try {
print('yaml is ${yaml[key]}');
final val = (yaml[key] as YamlList?);
if (val == null) {
return bail("Key $key is missing in yaml");
}
return Ok(val.cast<T>());
} catch (e) {
return bail("Failed to parse yaml key $key as ${T.toString()}: $e");
}
}
Result<T> getYamlKey<T>(YamlMap yaml, String key) {
try {
final val = yaml[key] as T?;
if (val == null) {
return bail("Key $key is missing in yaml");
}
return Ok(val);
} catch (e) {
return bail("Failed to parse yaml key $key as ${T.toString()}: $e");
}
}

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:get/get.dart';
import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/map.dart';
import 'package:uninav/settings.dart';
void main() {
Get.put(MyMapController());
runApp(const MyApp());
}
@ -33,7 +36,11 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: MapPage(),
initialRoute: '/map',
getPages: [
GetPage(name: '/map', page: () => const MapPage()),
GetPage(name: '/settings', page: () => const SettingsPage()),
],
);
}
}

View File

@ -1,9 +1,16 @@
import 'package:anim_search_bar/anim_search_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:uninav/components/drawer..dart';
import 'package:rust_core/slice.dart';
import 'package:uninav/components/drawer.dart';
import 'package:uninav/components/hamburger_menu.dart';
import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/util/geomath.dart';
import 'package:url_launcher/url_launcher.dart';
class MapPage extends StatelessWidget {
@ -12,18 +19,10 @@ class MapPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
appBar: AppBar(
title: const Text('Map'),
leading: Builder(
builder: (context) {
return IconButton(
icon: Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openDrawer();
},
);
}
),
leading: HamburgerMenu(),
// right side expanding search bar widget
actions: [
@ -39,29 +38,365 @@ class MapPage extends StatelessWidget {
},
),
]),
drawer: MyDrawer(),
body: FlutterMap(
mapController: MapController(),
options: MapOptions(
center: LatLng(51.5, -0.09),
zoom: 13.0,
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Add onPressed logic here
await Get.find<MyMapController>().loadGeoJson(
await rootBundle.loadString('assets/geo/uulm_beta.geojson'));
},
child: const Icon(Icons.add),
),
body: Stack(
children: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'],
FlutterMap(
mapController: MapController(),
options: MapOptions(
center: LatLng(48.422766, 9.9564),
zoom: 16.0,
// camera constraints
maxZoom: 19,
),
children: [
TileLayer(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
maxZoom: 19,
),
// buildings
LevelLayer(
filter: (feature) => feature.type is Building,
),
// public transport
LevelLayer(
filter: (feature) =>
feature.level == null && feature.type is PublicTransport,
polyCenterMarkerConstructor: (center, name) => Marker(
width: 100,
height: 100,
point: center,
builder: (cx) => const Center(
child: Icon(
Icons.train,
color: Colors.black,
),
),
),
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.green.withOpacity(0.2),
borderColor: Colors.green,
isFilled: true,
borderStrokeWidth: 1,
))
.unwrap(),
),
// current level
Obx(
() => Stack(
children: renderLevel(
Get.find<MyMapController>().currentLevel.value),
),
)
// RichAttributionWidget(attributions: [
// TextSourceAttribution(
// 'OpenStreetMap contributors',
// onTap: () =>
// launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
// )
// ]),
],
),
RichAttributionWidget(attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
onTap: () =>
launchUrl(Uri.parse('https://openstreetmap.org/copyright')),
)
])
// PolygonLayer(polygons: myGeoJson.polygons),
// PolylineLayer(polylines: myGeoJson.polylines),
// MarkerLayer(markers: myGeoJson.markers)
Positioned(
left: 16,
bottom: 16,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.grey,
width: 1,
),
),
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Obx(
() => DropdownButton<int>(
value: Get.find<MyMapController>().currentLevel.value,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface),
dropdownColor: Theme.of(context).colorScheme.surface,
onChanged: (int? newValue) {
if (newValue != null) {
Get.find<MyMapController>().setLevel(newValue);
}
// Handle dropdown value change
},
items: Get.find<MyMapController>()
.levels
.map<DropdownMenuItem<int>>((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text("Level $value"),
);
}).toList(),
),
),
)),
],
));
}
}
List<Widget> renderLevel(int level) {
return <Widget>[
LevelLayer(
filter: (feature) =>
feature.level == level && feature.type is LectureHall,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.orange.withOpacity(0.2),
borderColor: Colors.orange,
isFilled: true,
borderStrokeWidth: 1,
),
)
.unwrap(),
markerConstructor: (feature) => Marker(
width: 150,
height: 60,
point: feature.getPoint().unwrap(),
builder: (cx) => Center(
child: Column(
children: [
Icon(
Icons.class_,
color: Colors.black,
),
Text('${feature.name}'),
],
),
),
)),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Room,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.green.withOpacity(0.2),
borderColor: Colors.green,
isFilled: true,
borderStrokeWidth: 1,
),
)
.unwrap(),
),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Door,
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => const Icon(
Icons.door_front_door,
color: Colors.brown,
),
);
},
),
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Toilet,
markerConstructor: (feature) {
final type = (feature.type as Toilet).toilet_type;
IconData icon;
switch (type.toLowerCase()) {
case 'male':
icon = Icons.male;
break;
case 'female':
icon = Icons.female;
break;
case 'handicap':
icon = Icons.wheelchair_pickup;
break;
default:
print("WARN: Toilet didn't have recognizable type! "
"(Level ${feature.level}, Name ${feature.name}, "
"Location: ${feature.getPoint().unwrap()})");
icon = Icons.wc;
break;
}
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => Icon(
icon,
color: Colors.purple,
),
rotateAlignment: Alignment.center,
);
},
),
LevelLayer(
filter: (feature) =>
feature.type is Stairs &&
(feature.type as Stairs).connects_levels.contains(level),
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => Icon(
Icons.stairs_outlined,
color: Colors.deepPurple.shade300,
),
);
},
),
LevelLayer(
filter: (feature) =>
feature.type is Lift &&
(feature.type as Lift).connects_levels.contains(level),
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 20,
height: 20,
point: point,
builder: (ctx) => const Icon(
Icons.elevator_outlined,
color: Colors.deepPurple,
),
);
},
),
];
}
class LevelLayer extends StatelessWidget {
final bool Function(Feature)? filter;
final Polygon Function(Feature)? polyConstructor;
final Marker Function(LatLng, String)? polyCenterMarkerConstructor;
final Marker Function(Feature)? markerConstructor;
final int? level;
const LevelLayer({
this.level,
this.filter,
this.polyConstructor,
this.polyCenterMarkerConstructor,
this.markerConstructor,
super.key,
});
@override
Widget build(BuildContext context) {
final myMapController = Get.find<MyMapController>();
return Obx(() {
final List<Polygon> filteredPolygons = [];
final List<Marker> polygonCenterMarkers = [];
final List<Marker> filteredMarkers = [];
for (final feature in myMapController.features) {
if (filter == null || filter!(feature)) {
if (feature.isPolygon()) {
if (polyConstructor != null) {
filteredPolygons.add(polyConstructor!(feature));
} else {
filteredPolygons.add(feature.getPolygon().unwrap());
}
// calculate polygon center
final center =
polygonCenterMinmax(feature.getPolygon().unwrap().points);
if (polyCenterMarkerConstructor != null) {
polygonCenterMarkers
.add(polyCenterMarkerConstructor!(center, feature.name));
} else {
polygonCenterMarkers.add(Marker(
width: 100,
height: 100,
point: center,
builder: (cx) => Center(
child: Text(
feature.name,
style: const TextStyle(
color: Colors.black54,
// backgroundColor: Colors.white,
),
),
),
));
}
} else if (feature.isPoint()) {
if (markerConstructor != null) {
filteredMarkers.add(markerConstructor!(feature));
} else {
final point = feature.getPoint().unwrap();
filteredMarkers.add(Marker(
width: 100,
height: 100,
point: point,
builder: (cx) => Center(
child: Text(
feature.name,
style: const TextStyle(
color: Colors.black54,
// backgroundColor: Colors.white,
),
),
),
));
}
}
}
}
// print(filteredPolygons.length);
// print(filteredPolygons);
// print(filteredPolygons[0].points[0]);
// print(myMapController.features.length);
final List<Widget> widgets = [];
if (filteredPolygons.isNotEmpty) {
if (polyConstructor != null) {
widgets.add(PolygonLayer(polygons: filteredPolygons));
} else {
widgets.add(PolygonLayer(
polygons: filteredPolygons
.map((poly) => Polygon(
points: poly.points,
borderColor: Colors.black26,
borderStrokeWidth: 2.0,
))
.toList()));
}
widgets.add(MarkerLayer(
markers: polygonCenterMarkers,
rotate: true,
));
}
if (filteredMarkers.isNotEmpty) {
widgets.add(MarkerLayer(markers: filteredMarkers, rotate: true));
}
return Stack(children: widgets);
});
}
}

21
lib/settings copy.dart Normal file
View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:uninav/components/drawer.dart';
import 'package:uninav/components/hamburger_menu.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
leading: HamburgerMenu(),
),
drawer: MyDrawer(),
body: const Center(
child: Text('TODO'),
),
);
}
}

21
lib/settings.dart Normal file
View File

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:uninav/components/drawer.dart';
import 'package:uninav/components/hamburger_menu.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
leading: HamburgerMenu(),
),
drawer: MyDrawer(),
body: const Center(
child: Text('TODO'),
),
);
}
}

37
lib/util/geomath.dart Normal file
View File

@ -0,0 +1,37 @@
import 'dart:math';
import 'package:latlong2/latlong.dart';
LatLng polygonCenterMinmax(List<LatLng> polygon) {
double minLat = double.infinity;
double maxLat = double.negativeInfinity;
double minLng = double.infinity;
double maxLng = double.negativeInfinity;
for (LatLng point in polygon) {
minLat = min(minLat, point.latitude);
maxLat = max(maxLat, point.latitude);
minLng = min(minLng, point.longitude);
maxLng = max(maxLng, point.longitude);
}
double centerLat = (minLat + maxLat) / 2;
double centerLng = (minLng + maxLng) / 2;
return LatLng(centerLat, centerLng);
}
LatLng polygonCenterAvg(List<LatLng> polygon) {
double sumLat = 0;
double sumLng = 0;
for (LatLng point in polygon) {
sumLat += point.latitude;
sumLng += point.longitude;
}
double centerLat = sumLat / polygon.length;
double centerLng = sumLng / polygon.length;
return LatLng(centerLat, centerLng);
}