From f36a162ffe3b080f0b06a4d4cc662d2406032f03 Mon Sep 17 00:00:00 2001 From: Sara <sarasdj@stud.ntnu.no> Date: Wed, 14 Feb 2024 12:04:44 +0100 Subject: [PATCH] update: marker_data classes --- .gitignore | 5 +- app/lib/pages/default_page.dart | 51 ++---- app/lib/pages/marker_handler/get_markers.dart | 33 ++++ app/lib/pages/marker_handler/marker_data.dart | 157 ++++++------------ app/lib/pages/widgets/map_widget.dart | 97 ++--------- .../Flutter/GeneratedPluginRegistrant.swift | 2 + .../__pycache__/get_markers.cpython-311.pyc | Bin 2576 -> 3243 bytes server/map/get_markers.py | 63 ++++--- 8 files changed, 161 insertions(+), 247 deletions(-) diff --git a/.gitignore b/.gitignore index 6668ffa9..5bd67ec6 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 6bd8f225..f483d54b 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 e69de29b..14d3da49 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 19ddbc8c..902f9f01 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 98bda421..30e0754d 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 cccf817a..e777c67d 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 GIT binary patch delta 1643 zcmZuwOKcNI7@qY$yz9q$lWdIRK(cvYN+1cKAOVH&umeO%s4CK`Q47)H8H&NL?yd<% zYurN*E<&PoFDZ4UZ57c}^Z>`+I8e1xd$5rutd)@515%^*;0h`sPMvWaLL`38d^6w7 z_s{q5jP^_UZkPCj-|qo9{`%$*bl-DdJVJQ|OljPo*|arBeXuzJ^0euSfit)AjLAS( z7tuE(&sx7zvqMlQae2;UP5Ls>`c2wo*Co)iKDS-gC$wyh((U6n&R{tMz3ZEp`8F{B zIy2mBjsRetXdQBOZ4H!Yllz#0Qi&m!2eIVFR|YA*6(()7*s$L^;SvjZx8Yygz=yc* zPypP;eKnx#l8emrjtwgJn^l@@1X_uBfNdL96oBn{9(Lq~bO_3M-e|+3nU?<ItNKhg zWPa+rgKh6Nt%(4q3=aT9Zi(?Q2s9<{*<cqmJ=UMD&aEwzIRHW4bIp_HSH}fv0bHTy zzyf6g+e<FoW%AZ;y2JXO?hL_AFtpK=0$AtAx@}%@&DRc)JjN7ksd)hGvMTfd6F#NJ ztrv964@ag|BVptq+NnTmlIb0VBa?A7o5^BMX2&vykx3B}Rg>{tQZ*2qy^x4sN+Vq- zIoFuatzVhI-k55r3RK=XrKCn?kgDf2ltO7^GNv5D$s1NYN-H$W&kj(Wm1ct{xm8<7 zcxCEzyus4pkH+IUO^tu*FvN0&y!;Be_Z9LWD;{enJ4i9syKIlhoknRrqiN*Xt`1v2 zvwacc0#YDOJE!U>@f_z~b$xhd<u@$YPB~m64ROg=M~PH?U3x&V{$bymZFAh{3X10p z<nZT_5x<}&lMbh5vnUN6=3FL|beLp9HyrM=n#>{Hp_3@>uvsmUHXP=np1}m>Qd!** zCa%O$)<|U1)_dHMg4bbmlsxZn_|tI34x?ojbVo|9-{U-~+M)5fBfh?zfR2}J&1cSC zL~-oW)Cn5Wv-9x`M2<J5YL}2U-*l&t?#17_cK>*Mxi5O)BdfukD7=j~e7Nfoe4C$v zhHD?&UFXG{;&rhBxRCE(zzP261Te)Q0V#O1aJ^6oDAj;cWa`~p%6zEav8e&Ng!U#B zqqQxH-Bx<Pc&fg+*QQFxTf7$DvZ$6KYi!%l^6tXY<=U#I;dggqOAN)Sh76>ha^LQ9 zfV7&V*gaKg*S%m>+V@yG{75?dz*~`~tI~A2*%~~EjMWCWEmKR!>#IX8vVFO4>HL%a zh^;U0Sz7pRcxkxdW_p7SK)?ffwlB^!+JwRwu2~psFB6Z7w5u%b`Y!||xhxMpmUldo zcU0uzsyti?j8p?7MdqnjEE-?zzp;Ojx;|bU|Ms*ke?3>4TkgAkcJb^JUt78TSj88u z`l4lDv=-_vPS(Ob#i`;{oy?zxdrCw0?7hBnc&rj0tA@vlQ#)R~c<PlKfM&bb;R)|A zi_r%Z2HYxqtjfpAe9VgRw}P=fvAy8oUe81?{qS&bB251hW+!^BzxlaQ1wW$}AwVAW wNAL_I&<<dD$sbH*U@nQqwUd~VpGUuK>B3H`ZrQ?Y-%*O%(*SQq?F3f;4?MWRpa1{> delta 1067 zcmZWnU1$_n6h8Amv-7jrBpppQ#?@>@B}tPOeCSVNe(FP`f*8e66sCK}WYgW5?aVAm zWv8johb1kf_O@W@md?ZC4;HF#LLUnDrBDh9<YBoW<fRXJ5LWcbC+}ptih8*Bn{&Q% z=A4hiz0>nuw{cU~6F|)^{$u|s-ZPFfs!l_W-Y3^tJkMO{U4W3QurqLRG2|;eVteBH zUBmw}bN!T3UQ&V3P6E(O0{_nT=xU8aT1^Ut3KwzL9(S!mMYzEL+@ZH+41AkMdME}; zn*msQ(W=5B@i*fJOaGv^WufE@4?v)`V}LApnNSXrFBz1q$Unm`?CY}*p^i{QsZg2+ z1fl$;?8vR}GP4XT+&nBZ6^LZ26klSyc<L+E?*Y__x?WYm08J+1!iY(B#L<%4o*!wH z7oZ|VI&~C4-7OvX9&4%$oqQIKymi`=z87Zn;>bxW$OmO)4`qt@jIiG<j-0YBzwFs) zZZV(x%&~nxPW>u;t$OhicH-Ub;0192KN2&Hgbk_xW4YahC?{QKVYeYkz0ruslet{k zvvOY$@vQCmuDA0y?RJDAV%|Zv6I?lpuSy3xPthLk%+!0C{8KvZ3j4`Ld{;U=X%NX? zv2*3XCh}!F$Sqoh0^vV(-2xG<Qpt7@;g@{ZAp$BFOFmIPTFKd^ARjX&FYg3+NIpKS z5Z<>7mx)M~Pz8kd+-0Be$O<gNQJ<*A)&h!D5dK5%<Kwr1AIj%e$EXvp+5d*h`U;wC zR!BERP?GD$n$ZMdul5)OMSmIt4JjDVVXm*Pt!^eWTggn7-%h5ge4}em6AWc=7pl|S z2eQ$by4lDKM3Z%~(YHThY9|`|GEu5_zA<?CR;qr!MWPei|JNKGjI8>hhy4emccahi z$9^i*3QdVmcQzr0q?V>?C{$;gy`ZPoXV+%0tD9POOUrKTp6%{ERpmdk_lKpZ`<As~ zj&7QxTjpq0t*ZELVsGd8{jm*WoSsd2VoRRbkS8!nTr;OerpDmGSYkTOJ$T=oHo3n{ zVLFYks`GF6(Y1K{Vu+V?8=g=EZ<N9_`Gca1$_0De8>eCXeEc7=q75+(d`z3`8)n{V L!kgfINDH3>)4U-1 diff --git a/server/map/get_markers.py b/server/map/get_markers.py index 17baa767..ce82e44d 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 = '[]' -- GitLab