From 9659058de9da606a12d006585d73a2bfeecdf7a6 Mon Sep 17 00:00:00 2001 From: Sara <sarasdj@stud.ntnu.no> Date: Mon, 26 Feb 2024 12:51:45 +0100 Subject: [PATCH] update: db structure and api req --- app/lib/pages/marker_handler/get_markers.dart | 10 +- app/lib/pages/marker_handler/marker_data.dart | 110 +++++++----------- .../pages/widgets/interactive_polygon.dart | 71 +++++++++++ app/lib/pages/widgets/map_widget.dart | 34 +----- server/__pycache__/consts.cpython-311.pyc | Bin 602 -> 359 bytes server/consts.py | 14 +-- .../__pycache__/get_markers.cpython-311.pyc | Bin 3265 -> 3345 bytes server/map/divide_measurement.py | 24 ++++ server/map/get_markers.py | 22 ++-- server/sql_db/icedb | Bin 45056 -> 49152 bytes server/sql_db/schema.sql | 2 + 11 files changed, 163 insertions(+), 124 deletions(-) create mode 100644 app/lib/pages/widgets/interactive_polygon.dart create mode 100644 server/map/divide_measurement.py diff --git a/app/lib/pages/marker_handler/get_markers.dart b/app/lib/pages/marker_handler/get_markers.dart index 702d4145..156de62c 100644 --- a/app/lib/pages/marker_handler/get_markers.dart +++ b/app/lib/pages/marker_handler/get_markers.dart @@ -12,7 +12,7 @@ Future<List<Measurement>> fetchMarkerData() async { ..badCertificateCallback = // NB: temporary disable SSL certificate validation (X509Certificate cert, String host, int port) => true; - // Request markers from API + // Request markers from server var request = await client.getUrl(Uri.parse(serverURI + mapEndpoint)); var response = await request.close(); // Close response body at end of function @@ -23,8 +23,11 @@ Future<List<Measurement>> fetchMarkerData() async { if (responseBody.isNotEmpty) { var jsonData = json.decode(responseBody); - if (jsonData != null && jsonData is List) { // Check if jsonData is not null and is a List - return jsonData.map((data) => Measurement.Measurement(data)).toList(); + // Attempt to parse response to Measurement object only if the body + // contains correctly formatted data + if (jsonData != null && jsonData is List) { + print(jsonData.map((data) => Measurement.fromJson(data)).toList()); + return jsonData.map((data) => Measurement.fromJson(data)).toList(); } else { throw Exception('Failed to parse marker data: Unexpected response format'); } @@ -35,7 +38,6 @@ Future<List<Measurement>> fetchMarkerData() async { throw Exception('Failed to fetch marker data: Status code ${response.statusCode}'); } } catch (e) { - print('Error fetching marker data: $e'); throw Exception('Failed to fetch marker data: ${e.toString()}'); } } diff --git a/app/lib/pages/marker_handler/marker_data.dart b/app/lib/pages/marker_handler/marker_data.dart index fd466ed8..e84c1416 100644 --- a/app/lib/pages/marker_handler/marker_data.dart +++ b/app/lib/pages/marker_handler/marker_data.dart @@ -1,43 +1,60 @@ import 'dart:core'; +import 'package:latlong2/latlong.dart'; class Measurement { int measurementID; DateTime timeMeasured; Sensor sensor; - List<Data> dataList; - List<Corner> cornerList; String bodyOfWater; + LatLng center; + List <SubDiv> subDiv; Measurement({ required this.measurementID, required this.timeMeasured, required this.sensor, - required this.dataList, - required this.cornerList, required this.bodyOfWater, + required this.center, + required this.subDiv, }); - factory Measurement.Measurement(Map<String, dynamic> json) { + factory Measurement.fromJson(Map<String, dynamic> json) { return Measurement( measurementID: json['MeasurementID'], timeMeasured: DateTime.parse(json['TimeMeasured']), sensor: Sensor.fromJson(json['Sensor']), - dataList: (json['Data'] as List<dynamic>) - .map((data) => Data.fromJson(data)) - .toList(), - cornerList: (json['Corners'] as List<dynamic>) - .map((data) => Corner.fromJson(data)) - .toList(), - bodyOfWater: json['BodyOfWater'], - /* - dataList: (json['Data'] != null && json['Data'] is List) - ? (json['Data'] as List<dynamic>).map((data) => Data.fromJson(data)).toList() - : [], - cornerList: (json['Corner'] != null && json['Corner'] is List) - ? (json['Corner'] as List<dynamic>).map((data) => Corner.fromJson(data)).toList() - : [], - bodyOfWater: json['WaterBodyName'] ?? '', - */ + bodyOfWater: json['BodyOfWater'] ?? 'nil', + center: LatLng(json['CenterLat'], json['CenterLon']), + subDiv: (json['Subdivisions'] as List<dynamic>).map((data) => SubDiv.fromJson(data)).toList(), + ); + } +} + +class SubDiv { + int subDivID; + int groupID; + double minThickness; + double avgThickness; + LatLng center; + double accuracy; + + SubDiv({ + required this.subDivID, + required this.groupID, + required this.minThickness, + required this.avgThickness, + required this.center, + required this.accuracy, + }); + + factory SubDiv.fromJson(Map<String, dynamic> json) { + return SubDiv( + subDivID: json['SubdivID'], + groupID: json['GroupID'], + minThickness: json['MinThickness'], + avgThickness: json['AvgThickness'], + center: LatLng(json['CenLatitude'], json['CenLongitude']), + accuracy: json['Accuracy'], ); } } @@ -56,57 +73,8 @@ class Sensor { factory Sensor.fromJson(Map<String, dynamic> json) { return Sensor( sensorID: json['SensorID'], - sensorType: json['SensorType'], + sensorType: json['SensorType'] ?? 'nil', active: json['Active'], ); } } - -class Data { - double latitude; - double longitude; - double iceTop; - double iceBottom; - double calculatedThickness; - double accuracy; - - Data({ - required this.latitude, - required this.longitude, - required this.iceTop, - required this.iceBottom, - required this.calculatedThickness, - required this.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'], - ); - } -} - -class Corner { - int cornerID; - double latitude; - double longitude; - - Corner({ - required this.cornerID, - required this.latitude, - required this.longitude, - }); - - factory Corner.fromJson(Map<String, dynamic> json) { - return Corner( - cornerID: json['CornerID'], - latitude: json['Latitude'], - longitude: json['Longitude'], - ); - } -} \ No newline at end of file diff --git a/app/lib/pages/widgets/interactive_polygon.dart b/app/lib/pages/widgets/interactive_polygon.dart new file mode 100644 index 00000000..52968654 --- /dev/null +++ b/app/lib/pages/widgets/interactive_polygon.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +// InteractivePolygon returns a Polygon object wrapped in a GestureDetector +// in order to make the Polygon clickable +class InteractivePolygon extends StatelessWidget { + final List<Offset> points; + final VoidCallback onTap; + final Color color; + final double strokeWidth; + + InteractivePolygon({ + required this.points, + required this.onTap, + this.color = Colors.blue, + this.strokeWidth = 1.0, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: CustomPaint( + painter: PolygonPainter( + points: points, + color: color, + strokeWidth: strokeWidth, + ), + ), + ); + } +} + +// PolygonPainter takes the points, color, and stroke width from a +// object of type InteractivePolygon, and renders it +class PolygonPainter extends CustomPainter { + final List<Offset> points; + final Color color; + final double strokeWidth; + + PolygonPainter({ + required this.points, + required this.color, + required this.strokeWidth, + }); + + // paint() iterates through all the vertices of the polygon and connects + // each sequential pair with a line + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth; + + final path = Path(); + path.moveTo(points[0].dx, points[0].dy); + for (var i = 1; i < points.length; i++) { + path.lineTo(points[i].dx, points[i].dy); // Render line between vertices + } + path.close(); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(PolygonPainter oldDelegate) { + return oldDelegate.points != points || + oldDelegate.color != color || + oldDelegate.strokeWidth != strokeWidth; + } +} \ 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 ebb7521c..ea3ec581 100644 --- a/app/lib/pages/widgets/map_widget.dart +++ b/app/lib/pages/widgets/map_widget.dart @@ -69,12 +69,8 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", subdomains: const ['a', 'b', 'c'], ), - PolygonLayer( + /*PolygonLayer( polygons: widget.markerList.map((Measurement measurement) { - // Map corners to a list of LatLng objects - List<LatLng> points = measurement.cornerList.map((Corner corner) { - return LatLng(corner.latitude, corner.longitude); - }).toList(); return Polygon( points: points, // Use list of corner coordinates to render polygon @@ -82,35 +78,14 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { isFilled: true, ); }).toList(), - ), + ),*/ MarkerLayer( markers: widget.markerList.map((Measurement measurement) { - List<LatLng> corners = measurement.cornerList.map((Corner corner) { - return LatLng(corner.latitude, corner.longitude); - }).toList(); - - // point calculates the middle point between corners - LatLng point(List<LatLng> coordinates) { - double averageLatitude = 0.0; - double averageLongitude = 0.0; - - for (LatLng point in coordinates) { - averageLatitude += point.latitude; - averageLongitude += point.longitude; - } - - // Calculate average latitude and longitude - averageLatitude /= coordinates.length; - averageLongitude /= coordinates.length; - - return LatLng(averageLatitude, averageLongitude); // Return the middle point - } - return Marker( width: 50, height: 50, - point: point(corners), + point: measurement.center, // Set markers at center of measurement builder: (ctx) => GestureDetector( onTap: () { setState(() { @@ -126,7 +101,6 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ); }).toList(), ), - ], ), ), @@ -268,7 +242,7 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ), const SizedBox(height: contPadding), Text( - 'Measuring point: (${selectedMarker?.dataList[0].latitude}, ${selectedMarker?.dataList[0].latitude})', + 'Measuring point: (${selectedMarker?.measurementID}, ${selectedMarker?.measurementID})', style: regTextStyle, ), const SizedBox(height: contPadding), diff --git a/server/__pycache__/consts.cpython-311.pyc b/server/__pycache__/consts.cpython-311.pyc index 5c3188f8a9d95c5a2766585e72bbb8895f196618..3c60371f12da04a05b4df70c07a52110b373b16c 100644 GIT binary patch delta 240 zcmcb`@|=lpIWI340}!~HT~GbWFp*EfP7BDJ&XB?o#gM`n#hAhr#gxjF!dwLujba9} zSimfn6xJx#6t-XnP4+5w13fUz{H?&iFj;_6+=9C#wYbDPwNfuTwX%u_#Bok7D$z?W zDtRdaG{;Yq?G}rNe{jexmH_{tkXsziu0bL3E}lV?XEDlh-{N!(a&?Rk_Kb3!yq(e6 zlLx4S5r~V;fy4)9Mn=XD91I+S4J<d<xf@tMurcr`HE`VE7oVYfkzc)m=Z1j947-a0 Snhm@_DPH~tP7o{t`4IrNZ#+%_ delta 503 zcmX|-!EVz)6h+656FX^~q!ow?goKa?ilBB>!2(1;jT6y`*sc;Iz#G@KUt3qP9gQc2 zdO`gQS&={BYgl9#4I9LUZT4)K7{oly=-x9|?~UdcPlei7S*{?qPJey_iJ#YYuU|0t z1^LKEK4)`2Z}Wb^F8G2i_(i)2MZc7?gk54v%$3NLlq<<Ek5Q|ViftvY;P*X*s5J0E zcc;BBtyz!|EuKukBS9RcpTjshi~W;F3BG);s(}Zgdp-3gvxERvg7!X4;}hjyNQr=` zuiP0qAICH7LFd8#6Zph}kvnN_unyk28wE+&(M{vE+3W7Tf7*W5#6a-XI~)*5It0(4 z`LPqa7cm1@>5T;NC1BM9OoG$Ea|vuGYAWlScWDeNQwZnjoin3Vroy2)wy1Dqjw~wn zx{h(6_i05lhr_;R4NQZU^SRF3(SS-?e`Gnm!HCxX!rUJnSckMa9uJ*2{SQv=<c<4( zIsdL5H`woTY$pag)&-8^))JDoGhroeWx~3Iwr*zP5BY9ZS@THN`K-)X^?oL;c5W}6 g<<8@*zN+6^>@DjLv+Am`yLh>5v@&^9xW-!k0WF7_wg3PC diff --git a/server/consts.py b/server/consts.py index 295463e8..5653feea 100644 --- a/server/consts.py +++ b/server/consts.py @@ -2,16 +2,12 @@ # Server variables HOST = "0.0.0.0" -PORT = 8443 - -# Database paths -DB_NAME = 'IceMapDB' -#COLLECTION = 'IceData' -COLLECTION = 'TestCollection' # NB: temporary collection -MONGO_URI = "mongodb+srv://icemapcluster.i02epob.mongodb.net/?authSource=%24external&authMechanism=MONGODB-X509&retryWrites=true&w=majority" +PORT = 8443 # Certificate paths CERT_DIR = "server/certificates/" -MONGO_CERT_PATH = CERT_DIR + "MongoCert.pem" SSL_KEY_PATH = CERT_DIR + "testKey.key" -SSL_CERT_PATH = CERT_DIR + "testCert.crt" \ No newline at end of file +SSL_CERT_PATH = CERT_DIR + "testCert.crt" + +# Measurement specs +AREA_SIZE = 20 \ No newline at end of file diff --git a/server/map/__pycache__/get_markers.cpython-311.pyc b/server/map/__pycache__/get_markers.cpython-311.pyc index 7dbac2b7ec9e41b54e8502402b73e37c0dd244fc..0936525a134986cdf0765898d85dcce201203bbb 100644 GIT binary patch delta 655 zcmX>oIZ=vlIWI340}vEv-bq#Do5&Z$#SP>!1M%lS6SGPfw{5&H#Hdrlo+2O#QU(S< zo*;r(!VVK>U;xSpO=e`06cwJsT*H~pm?AQVt%j|J!v?6NXZvJ*rrFk_%NQ9LRs%5v zFfycw)pDnZ*Ye~^q)60qmk7dS7#OlRVeA^N6#g}mlUbRKWu<C(XEV%Y!fwjsQ091M z=@gmC+nI|QWhZO1D6`1<B_>P`VsVko)pJhGD@iT#Ni5M(fOGQmbS5jZifmrRqR%w> zF`ESYOJ1O*FZm|_V9ON+1;a}g5CL-UOI8rUHo1UZC4}u3uWxE%acNO%F3?a<ms>m` znYpP@p_E&kaI4_V{JdLi!Kr!0`9(#-Ky#|3T#JhGixe{R6begIiz+ko(l<Y4f5Ir_ z43uO8;^GV@28ITP4@^FjZ*vB)DRK&ZU{IWF$fd~Tw1oYJg7N|&H93Z>fbWK)%7VZp zVo+-G87^HhQEn5UY9MhzKw*L0MFE2g0tR1YCi8RKaOq#<F}T8GaDm5QawzwsdIeWy zhNH?{ZoJG#b;aB`n2&L=0@<3(MLIwy6sdy<kb_n-d<K#XMNUBC7l%!5eoARhs$G#A rkP8Z#Vk;o=ftit!@dg8T0~mhb3T0$8S|Rd*0Yu{>xgioou0Z_&gy*3n delta 585 zcmbOzbx@LTIWI340}yx`UQfNqJCQGlixbFW2I9{TCT5i|uGn~Ah*6+~9i#>VYS>c* zCd)EOiVDtQuHj5)Oc9#HQo~lmVFQ%zSvlF8X|}cSGDZf5)j$jZj0`CvwcIJ9wLEze zDPpzUC4w*+28Jw<bzo4#l_Ic4e6k|5v8+T5?`(#-OxR7CT*w^HESVxT`7(1cyEH`2 zWO*hrZlE;`!3>%*eu?gr^H^LaFJ=|le4j<1iH!|t{!8}BT<kKF4cN1Q>VRC34_~rO zUeB%)$a0I<H#M=iv?w(<HLt|e<rYs!W^O7}DCHJgaB5z0eo>JyP)C)nW1d24QBi)8 zLVj{`X;D#Xib8otW=^U?VQFenWoBNwLP<vIW_6Ayj6(K6^&p#zqnH>N8W=t>c}<q( z3Sg7x6#Bp*Ke?Dok;`!j+YJTf1wd-@Jg$PtGTgdif}AEmX&`YyKw*L0MFE2g0tR0t zCdYHza5-J%alXRie1XS#@>1?c^+K+q3`a$|T<w{U%8R*LF(0*J1+q1ni!^|C7Ab=W zkPB8ad<K#XMRq{q7l%!5eoARhs$G#2kP8ZjVk;o=ftit!@dg8T0~mhb3T9+9S|Rd* P0Yu{>xgioojzIkYXzPt! diff --git a/server/map/divide_measurement.py b/server/map/divide_measurement.py new file mode 100644 index 00000000..02081900 --- /dev/null +++ b/server/map/divide_measurement.py @@ -0,0 +1,24 @@ +from math import pi, cos + +EARTH = 6378.137 # Radius of the earth in kilometer +METER = (1 / ((2 * pi / 360) * EARTH)) / 1000 # 1 meter in degree +OFFSET = 20 # Offset in meters + + +def calculate_corners(lat, lng): + # From https://stackoverflow.com/questions/7477003/calculating-new-longitude-latitude-from-old-n-meters + # Formulas: + lat_pos = lat + (OFFSET * METER) + lng_pos = lng + (OFFSET * METER) / cos(lat * (pi / 180)) + + lat_neg = lat - (OFFSET * METER) + lng_neg = lng - (OFFSET * METER) / cos(lat * (pi / 180)) + + return [ + (lat_neg, lng_pos), + (lat_pos, lng_pos), + (lat_pos, lng_neg), + (lat_neg, lng_neg) + ] + +# def div_measurement(): diff --git a/server/map/get_markers.py b/server/map/get_markers.py index 48ce0673..2f5c6733 100644 --- a/server/map/get_markers.py +++ b/server/map/get_markers.py @@ -6,7 +6,7 @@ import json def get_all_markers(self, cursor, valid: bool, waterBodyName): try: sql_query = ''' - SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, + SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, m.CenterLat, m.CenterLon, s.SensorType, s.Active, b.Name, d.SubDivisionID, d.GroupID, d.MinimumThickness, @@ -35,13 +35,13 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName): # Create subdivision new object sub_division = { - 'SubdivID': row[6], - 'GroupID': row[7], - 'MinThickness': row[8], - 'AvgThickness': row[9], - 'CenLatitude': row[10], - 'CenLongitude': row[11], - 'Accuracy': row[12] + 'SubdivID': row[8], + 'GroupID': row[9], + 'MinThickness': row[10], + 'AvgThickness': row[11], + 'CenLatitude': row[12], + 'CenLongitude': row[13], + 'Accuracy': row[14] } # Check if measurement ID already exists in measurement_data @@ -55,10 +55,12 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName): measurement_data[measurement_id] = { 'MeasurementID': measurement_id, 'TimeMeasured': row[2], + 'CenterLat': row[3], + 'CenterLon': row[4], 'Sensor': { # Each measurement only has one related sensor 'SensorID': row[1], - 'SensorType': row[3], - 'Active': bool(row[4]) + 'SensorType': row[5], + 'Active': bool(row[6]) }, 'Subdivisions': [sub_division], # Array of sub_division objects } diff --git a/server/sql_db/icedb b/server/sql_db/icedb index b19e3e7766c83a08ad8f1b6aa7c08743c90ab02d..2ba5aeca796f334edd9df517a92df6a2d401852f 100644 GIT binary patch delta 657 zcmZp8z|_#dJV9EJkAZ=K2Z*_Wm=TD1C+Zlh@-gV8z2fDPX5ePmW8hEcKgie1Z^bRf zvzvV#&u^~lTv6<LY<D+KjAV-fs<|j`ZOzzZT#}fSlUiJulUb5lTvD2nnIB)0np+T$ zPngF!$kj2#RUyRD$;VXzqPI?fUEE!nu~B~V0xn%fp2>k+vXftP$p|QDgeR7y7CGgo zRQe_6rfP0p$DYf?!p+BEKUtp3nzNdpUECF@O&X|8baEaS507tZVsU9vYHn&?2^Y{5 z3qb{qkj&gvsBns=Dc|J1JTj9@_#`=aJ~40rz0R;%jQb9wF#it*PH|;r#@1$jUUo1e zb@Cq`v&rVXa^ecXsd>fuMV>ASo_-+;e*Qol>f@s`xrA3?@(f<t$vb(~ycC>)R;CvD zB$g<+`S?2`lqtBlx;cjWgeYhj=ox70z|`gE!3{!FG5ImCCXa#!%uY?6$<lnr910rX z0MOjr%U9ya%b&);%&*PBug!mizjL#ofGj_!B_jtXqp>(U`{aA^N|FwK$!AShL{vJc zmNv4f+|>vAQ<)Pe%Q3k-UW(I_nS&9;V+Zm;YBLj`d{6!7pqkX=u)V>=iiv{}V$zm) zWdR4jSP89}2KyXT52-cG^$gmSz-F+SZNV>o5m4Yk0VpkjFdNV<OZg}7T<^YVLJR-o bmiTyCPG)gN->m%NL}oaPiGx|36D$S*8-%l9 delta 345 zcmZo@U~YK8G(lRBhk=2C8;D_meWH%BG7p1Z+ACiE9}L{=>lpac`495-@>}tI;tS&z z<JrwMolBa1-DW`n9rn%Z*mId!s<_z2C(CnLPoBx8FRl@inVagHnpj*~l$xSh%gHY8 zuFTjdJK3CFcXB_M++-hi-pT#!{QMf>i6yB;PWdU7eu=rMnw!PA?=W&TC9<-MD=Ra$ za!zLDHDl#c0D{TZymFK8@NtWHx+r-1g(&#>197O2j}Az7asjUb3z#v1R~Eup%d0k7 zpNDm_9G?ciMsR9gaek4fizY;M5T7xZ2G~9>O;fhXdwFCgf8~<g+{;(u$O{YrX8sli z{_Ff}`CEX#E90NsvOj*ZWxVXB1U7@sY#ZwMCoYJW<zyCT^v%jIPGp9&m^hflIl*F! HCI|okyRBEq diff --git a/server/sql_db/schema.sql b/server/sql_db/schema.sql index 14138216..ebc8dbae 100644 --- a/server/sql_db/schema.sql +++ b/server/sql_db/schema.sql @@ -16,6 +16,8 @@ CREATE TABLE Measurement ( SensorID INT NOT NULL, TimeMeasured DATETIME NOT NULL, WaterBodyName TEXT NOT NULL, + CenterLat FLOAT NOT NULL, + CenterLon FLOAT NOT NULL, WholeAverageThickness FLOAT NOT NULL, FOREIGN KEY (SensorID) REFERENCES Sensor(SensorID), FOREIGN KEY (WaterBodyName) REFERENCES BodyOfWater(Name) -- GitLab