diff --git a/.gitignore b/.gitignore index 6668ffa9626fc3fb8f6c6a8bbb8b1ec1c07ac1f6..5bd67ec656a148677ed4235edf0b1531818c028b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ .vs/ # Auto-generated by Android Studio -.idea/ \ No newline at end of file +.idea/ + +# Python interpereter +venv/ \ No newline at end of file diff --git a/app/lib/pages/default_page.dart b/app/lib/pages/default_page.dart index 6bd8f225067d7cfaff04917d7f0add0bf2bd0585..f483d54bc4792637aa6765976b2f9709393cfa21 100644 --- a/app/lib/pages/default_page.dart +++ b/app/lib/pages/default_page.dart @@ -1,10 +1,9 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'widgets/map_widget.dart'; -import 'marker_data.dart'; +import 'marker_handler/marker_data.dart'; import 'consts.dart'; +import 'marker_handler/get_markers.dart'; class DefaultPage extends StatefulWidget { const DefaultPage({super.key}); @@ -16,45 +15,27 @@ class DefaultPage extends StatefulWidget { class _DefaultPageState extends State<DefaultPage> { late Timer _timer; - List<MarkerTemplate> markerList = []; + List<Measurement> markerList = []; - // fetchMarkerTemplate requests data from the update_map endpoint - Future<void> fetchMarkerTemplate() async { + // Call fetchMarkerTemplate and await its result before setting the state + Future<void> loadMarkerList() async { try { - // Custom HTTP client - HttpClient client = HttpClient() - ..badCertificateCallback = // NB: temporary disable SSL certificate validation - (X509Certificate cert, String host, int port) => true; + List<Measurement> fetchedMarkers = await fetchMarkerData(); - // Request makers from API and wait for response - var request = await client.getUrl(Uri.parse(serverURI + mapEndpoint)); - var response = await request.close(); - - // Attempt to parse json if request is ok - if (response.statusCode == 200) { - var responseBody = await response.transform(utf8.decoder).join(); - setState(() { - // Parse JSON string from response body - List<dynamic> jsonData = json.decode(responseBody); - - // Convert response from type List<dynamic> to List<MarkerTemplate> - markerList = - jsonData.map((data) => MarkerTemplate.fromJson(data)).toList(); - }); - } else { - print('Request failed with status: ${response.statusCode}'); - } + setState(() { + markerList = fetchedMarkers; + }); } catch (e) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("Error"), - content: Text("Failed to connect to the server. Please check your network connection"), + content: Text(e.toString()), actions: [ TextButton( onPressed: () { - Navigator.of(context).pop(); // Close the dialog + Navigator.of(context).pop(); }, child: Text("OK"), ), @@ -69,13 +50,13 @@ class _DefaultPageState extends State<DefaultPage> { @override void initState() { super.initState(); - // Call fetchMarkerTemplate when the widget is first created - fetchMarkerTemplate(); + // Load marker data from server + loadMarkerList(); // Schedule fetchMarkerTemplate to run periodically based on fetchInterval const - const Duration fiveMinutes = Duration(minutes: fetchInterval); - _timer = Timer.periodic(fiveMinutes, (timer) { - fetchMarkerTemplate(); + const Duration interval = Duration(minutes: fetchInterval); + _timer = Timer.periodic(interval, (timer) { + fetchMarkerData(); }); } diff --git a/app/lib/pages/marker_handler/get_markers.dart b/app/lib/pages/marker_handler/get_markers.dart index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..14d3da4963697ea96e9ee00bb8be68ba35b4ca0e 100644 --- a/app/lib/pages/marker_handler/get_markers.dart +++ b/app/lib/pages/marker_handler/get_markers.dart @@ -0,0 +1,33 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import '../consts.dart'; +import 'marker_data.dart'; + +// fetchMarkerTemplate requests all marker data from the server +Future<List<Measurement>> fetchMarkerData() async { + try { + HttpClient client = HttpClient() + ..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + + var request = await client.getUrl(Uri.parse(serverURI + mapEndpoint)); + var response = await request.close(); + + if (response.statusCode == 200) { + var responseBody = await response.transform(utf8.decoder).join(); + + List<dynamic> jsonData = json.decode(responseBody); + + return jsonData.map((data) => Measurement.Measurement(data)).toList(); + + } else { + print('Request failed with status: ${response.statusCode}'); + // Throw an exception or return null if the request fails + throw Exception('Failed to fetch marker template'); + } + } catch (e) { + print('Error: $e'); + // Throw an exception or return null if there's an error + throw Exception('Failed to connect to the server. Please check your network connection'); + } +} diff --git a/app/lib/pages/marker_handler/marker_data.dart b/app/lib/pages/marker_handler/marker_data.dart index 19ddbc8c036fe16dfc1b57ca96a6d46a6a8bfc0e..902f9f01d33a5dd64f7d2c66992abf9bb4400087 100644 --- a/app/lib/pages/marker_handler/marker_data.dart +++ b/app/lib/pages/marker_handler/marker_data.dart @@ -1,128 +1,73 @@ -import 'package:flutter/material.dart'; -import 'package:latlong2/latlong.dart'; - -// Sensor holds data about a single sensor -class Sensor { - int id; - String type; - bool active; +class Measurement { + int measurementID; + int timeMeasured; + Sensor sensor; + List<Data> dataList; - Sensor({required this.id, required this.type, required this.active}); + Measurement({ + required this.measurementID, + required this.timeMeasured, + required this.sensor, + required this.dataList, + }); - factory Sensor.fromJson(Map<String, dynamic> json) { - return Sensor( - id: json['ID'], - type: json['type'], - active: json['active'], + factory Measurement.Measurement(Map<String, dynamic> json) { + return Measurement( + measurementID: json['MeasurementID'], + timeMeasured: json['TimeMeasured'], + sensor: Sensor.fromJson(json['Sensor']), + dataList: (json['Data'] as List<dynamic>) + .map((data) => Data.fromJson(data)) + .toList(), ); } } -// DateAndTime holds the date and time for a single measurement -class DateAndTime { - int year; - int month; - int day; - int hour; - int minute; +class Sensor { + int sensorID; + String sensorType; + bool active; - DateAndTime({ - required this.year, - required this.month, - required this.day, - required this.hour, - required this.minute, + Sensor({ + required this.sensorID, + required this.sensorType, + required this.active, }); - factory DateAndTime.fromJson(Map<String, dynamic> json) { - return DateAndTime( - year: json['year'], - month: json['month'], - day: json['day'], - hour: json['hour'], - minute: json['minute'], + factory Sensor.fromJson(Map<String, dynamic> json) { + return Sensor( + sensorID: json['SensorID'], + sensorType: json['SensorType'], + active: json['Active'], ); } } -// Measurement holds data related to a singular measurement taken -// at a given time -class Measurement { - double longitude; +class Data { double latitude; - DateAndTime datetime; - Sensor sensor; - double precipitation; - double thickness; - double maxWeight; - double safetyLevel; + double longitude; + double iceTop; + double iceBottom; + double calculatedThickness; double accuracy; - Measurement({ - required this.longitude, + Data({ required this.latitude, - required this.datetime, - required this.sensor, - required this.precipitation, - required this.thickness, - required this.maxWeight, - required this.safetyLevel, + required this.longitude, + required this.iceTop, + required this.iceBottom, + required this.calculatedThickness, required this.accuracy, }); - factory Measurement.fromJson(Map<String, dynamic> json) { - return Measurement( - longitude: json['longitude'], - latitude: json['latitude'], - datetime: DateAndTime.fromJson(json['datetime']), - sensor: Sensor.fromJson(json['sensor']), - precipitation: json['precipitation'], - thickness: json['thickness'], - maxWeight: json['max_weight'], - safetyLevel: json['safety_level'], - accuracy: json['accuracy'], + factory Data.fromJson(Map<String, dynamic> json) { + return Data( + latitude: json['Latitude'], + longitude: json['Longitude'], + iceTop: json['IceTop'], + iceBottom: json['IceBottom'], + calculatedThickness: json['CalculatedThickness'], + accuracy: json['Accuracy'], ); } -} - -// MarkerTemplate holds all data required for rendering a marker -class MarkerTemplate { - Measurement geoData; - LatLng location; - double size; - Color color; - - MarkerTemplate({ - required this.geoData, - required this.location, - required this.size, - required this.color - }); - - factory MarkerTemplate.fromJson(Map<String, dynamic> json) { - // Parse from JSON string to type Color - Color parsedColor = parseColor(json['color']); - - return MarkerTemplate( - geoData: Measurement.fromJson(json['geo_data']), - location: LatLng(json['latitude'], json['longitude']), - size: json['size'], - color: parsedColor, - ); - } - - // parseColor parses the color strings into Colors types - static Color parseColor(String colorString) { - colorString = colorString.toLowerCase(); - switch (colorString) { - case 'yellow': - return Colors.yellow; - case 'red': - return Colors.red; - case 'green': - return Colors.green; - default: - return Colors.black; // Default color if unrecognized - } - } -} +} \ No newline at end of file diff --git a/app/lib/pages/widgets/map_widget.dart b/app/lib/pages/widgets/map_widget.dart index 98bda421c34be3ff9ee6dbdb64242bcf089e7456..30e0754d1ababd81f861ab1cdf5664fb7b3bcccd 100644 --- a/app/lib/pages/widgets/map_widget.dart +++ b/app/lib/pages/widgets/map_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../marker_data.dart'; +import '../marker_handler/marker_data.dart'; import '../consts.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; @@ -8,7 +8,7 @@ import 'quick_view_chart.dart'; import 'stat_charts.dart'; class MapContainerWidget extends StatefulWidget { - final List<MarkerTemplate> markerList; + final List<Measurement> markerList; const MapContainerWidget({Key? key, required this.markerList}) : super(key: key); @@ -18,7 +18,7 @@ class MapContainerWidget extends StatefulWidget { class _MapContainerWidgetState extends State<MapContainerWidget> { - MarkerTemplate? selectedMarker; + Measurement? selectedMarker; bool isMinimized = true; // Quick view box state tacker @override @@ -31,83 +31,12 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { double screenWidth = constraints.maxWidth; double boxWidth = 0.86; double boxHeight = 1.4; - -<<<<<<< HEAD - return Column( - children: [ - Container( - width: screenWidth * boxWidth, - height: screenWidth * boxHeight, - color: Colors.blue, - child: FlutterMap( - options: MapOptions( - center: LatLng(60.7666, 10.8471), - zoom: 9.0, - ), - children: [ - TileLayer( - urlTemplate: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", - subdomains: const ['a', 'b', 'c'], - ), - MarkerLayer( - markers: widget.markerList.map((MarkerTemplate markerTemplate) { - return Marker( - width: markerTemplate.size, - height: markerTemplate.size, - point: markerTemplate.location, - builder: (ctx) => GestureDetector( - onTap: () { - setState(() { - selectedMarker = markerTemplate; - }); - }, - child: Image.asset( - 'assets/icons/circle-red.png', - // Path to your custom icon asset - color: markerTemplate.color, - width: markerTemplate.size, - height: markerTemplate.size, - ), - ), - ); - }).toList(), - ), - /*PolygonLayer( - polygons: [ - Polygon( - points: [ - LatLng(60.7600, 10.8000), - LatLng(60.7600, 11.0000), - LatLng(60.7000, 11.0000), - LatLng(60.7000, 10.8000), - ], - color: Colors.blue, - isFilled: true, - ), - ], - ),*/ - ], - ), - ), - const SizedBox(height: contPadding), // Padding between containers - Container( - //width: screenWidth * boxWidth, - height: screenWidth * boxHeight*1.5, // NB: make dynamic - color: const Color(0xFF64B5F6), - child: Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.only(top: 20, left: 20), // Edge padding, text - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, -======= return Column( children: [ const SizedBox(height: contPadding), ClipRRect( borderRadius: BorderRadius.circular(20), child: Stack( ->>>>>>> faa9a56f985acdab1c81feefa6cee6b055f14b19 children: [ SizedBox( width: screenWidth * boxWidth, @@ -123,22 +52,22 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { subdomains: const ['a', 'b', 'c'], ), MarkerLayer( - markers: widget.markerList.map((MarkerTemplate markerTemplate) { + markers: widget.markerList.map((Measurement Measurement) { return Marker( - width: markerTemplate.size, - height: markerTemplate.size, - point: markerTemplate.location, + width: 50, + height: 50, + point: LatLng(Measurement.dataList[0].latitude, Measurement.dataList[0].longitude), builder: (ctx) => GestureDetector( onTap: () { setState(() { - selectedMarker = markerTemplate; + selectedMarker = Measurement; }); }, child: Image.asset( 'assets/icons/circle-red.png', - color: markerTemplate.color, - width: markerTemplate.size, - height: markerTemplate.size, + color: Colors.red, + width: 50, + height: 50, ), ), ); @@ -209,11 +138,11 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { style: titleStyle, ), Text( - 'Safety level: ${selectedMarker?.geoData.safetyLevel}', + 'Time of measurement: ${selectedMarker?.timeMeasured}', style: regTextStyle, ), Text( - 'Location: (${selectedMarker?.geoData.latitude},${selectedMarker?.geoData.longitude})', + 'Location: (placeholder, placeholder)', style: regTextStyle, ), const SizedBox(height: contPadding), diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a52206e8a8eef501faed292993ff21a31..e777c67df2219fce2c33861bf98710f86bfc79b3 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/server/map/__pycache__/get_markers.cpython-311.pyc b/server/map/__pycache__/get_markers.cpython-311.pyc index 8159e27776dc6f9fa6d2b93036b8051095d2a074..078d2229e68ac680f9151b17632da553101f0b6c 100644 Binary files a/server/map/__pycache__/get_markers.cpython-311.pyc and b/server/map/__pycache__/get_markers.cpython-311.pyc differ diff --git a/server/map/get_markers.py b/server/map/get_markers.py index 17baa767d4d4aae2ac3cb187842f407971b7a582..ce82e44d3869d7f4a98fd1f91bf1574eb42c38de 100644 --- a/server/map/get_markers.py +++ b/server/map/get_markers.py @@ -1,6 +1,7 @@ import os import sys from bson import json_util + current_dir = os.path.dirname(__file__) parent_dir = os.path.abspath(os.path.join(current_dir, '..')) sys.path.append(parent_dir) @@ -10,40 +11,60 @@ import json # get_markers requests all marker data from mongoDB def get_all_markers(self, cursor): try: - # Fetch all data cursor.execute(''' - SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, m.Latitude, m.Longitude, - m.MeasuredThickness, m.Accuracy, s.SensorType, s.Active + SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, d.Latitude, d.Longitude, + d.IceTop, d.IceBottom, d.CalculatedThickness, d.Accuracy, s.SensorType, s.Active FROM Measurement m INNER JOIN Sensor s ON m.SensorID = s.SensorID + INNER JOIN Data d ON m.MeasurementID = d.MeasurementID ''') rows = cursor.fetchall() - data = [] + measurement_data = {} + for row in rows: - measurement = { - 'MeasurementID': row[0], - 'TimeMeasured': row[1], - 'Latitude': row[2], - 'Longitude': row[3], - 'MeasuredThickness': row[4], - 'Accuracy': row[5], - 'Sensor': { - 'SensorID': row[6], - 'SensorType': row[7], - 'Active': bool(row[8]) - } + measurement_id = row[0] + data_object = { + 'Latitude': row[3], + 'Longitude': row[4], + 'IceTop': row[5], + 'IceBottom': row[6], + 'CalculatedThickness': row[7], + 'Accuracy': row[8] } - data.append(measurement) - resp_code = 200 - # Convert the list of dictionaries to JSON - marker_json = json.dumps(data, indent=4) + # Append data with measurement ID x to measurement x object + if measurement_id in measurement_data: + measurement_data[measurement_id]['Data'].append(data_object) + else: + # Create a new measurement entry if measurement ID doesn't exist already + measurement_data[measurement_id] = { + 'MeasurementID': measurement_id, + 'TimeMeasured': row[1], + 'Sensor': { + 'SensorID': row[2], + 'SensorType': row[9], + 'Active': bool(row[10]) + }, + 'Data': [data_object] + } + + # Convert dictionary values to list of measurements + data = list(measurement_data.values()) + + if len(rows) == 0 or len(data) == 0: # Return 500 and empty list if no data is found + print(f"An error occurred while querying the database") + resp_code = 500 + marker_json = '[]' + else: + resp_code = 200 + # Convert list of dictionaries to JSON + marker_json = json.dumps(data, indent=4) except Exception as e: - print(f"An error occurred while querying MongoDB: {e}") + print(f"An error occurred while querying the database: {e}") resp_code = 500 marker_json = '[]'