Skip to content
Snippets Groups Projects
Commit 9659058d authored by Sara Savanovic Djordjevic's avatar Sara Savanovic Djordjevic
Browse files

update: db structure and api req

parent 97f6e99a
No related branches found
No related tags found
1 merge request!3Choropleth map implementation
...@@ -12,7 +12,7 @@ Future<List<Measurement>> fetchMarkerData() async { ...@@ -12,7 +12,7 @@ Future<List<Measurement>> fetchMarkerData() async {
..badCertificateCallback = // NB: temporary disable SSL certificate validation ..badCertificateCallback = // NB: temporary disable SSL certificate validation
(X509Certificate cert, String host, int port) => true; (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 request = await client.getUrl(Uri.parse(serverURI + mapEndpoint));
var response = await request.close(); // Close response body at end of function var response = await request.close(); // Close response body at end of function
...@@ -23,8 +23,11 @@ Future<List<Measurement>> fetchMarkerData() async { ...@@ -23,8 +23,11 @@ Future<List<Measurement>> fetchMarkerData() async {
if (responseBody.isNotEmpty) { if (responseBody.isNotEmpty) {
var jsonData = json.decode(responseBody); var jsonData = json.decode(responseBody);
if (jsonData != null && jsonData is List) { // Check if jsonData is not null and is a List // Attempt to parse response to Measurement object only if the body
return jsonData.map((data) => Measurement.Measurement(data)).toList(); // 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 { } else {
throw Exception('Failed to parse marker data: Unexpected response format'); throw Exception('Failed to parse marker data: Unexpected response format');
} }
...@@ -35,7 +38,6 @@ Future<List<Measurement>> fetchMarkerData() async { ...@@ -35,7 +38,6 @@ Future<List<Measurement>> fetchMarkerData() async {
throw Exception('Failed to fetch marker data: Status code ${response.statusCode}'); throw Exception('Failed to fetch marker data: Status code ${response.statusCode}');
} }
} catch (e) { } catch (e) {
print('Error fetching marker data: $e');
throw Exception('Failed to fetch marker data: ${e.toString()}'); throw Exception('Failed to fetch marker data: ${e.toString()}');
} }
} }
......
import 'dart:core'; import 'dart:core';
import 'package:latlong2/latlong.dart';
class Measurement { class Measurement {
int measurementID; int measurementID;
DateTime timeMeasured; DateTime timeMeasured;
Sensor sensor; Sensor sensor;
List<Data> dataList;
List<Corner> cornerList;
String bodyOfWater; String bodyOfWater;
LatLng center;
List <SubDiv> subDiv;
Measurement({ Measurement({
required this.measurementID, required this.measurementID,
required this.timeMeasured, required this.timeMeasured,
required this.sensor, required this.sensor,
required this.dataList,
required this.cornerList,
required this.bodyOfWater, 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( return Measurement(
measurementID: json['MeasurementID'], measurementID: json['MeasurementID'],
timeMeasured: DateTime.parse(json['TimeMeasured']), timeMeasured: DateTime.parse(json['TimeMeasured']),
sensor: Sensor.fromJson(json['Sensor']), sensor: Sensor.fromJson(json['Sensor']),
dataList: (json['Data'] as List<dynamic>) bodyOfWater: json['BodyOfWater'] ?? 'nil',
.map((data) => Data.fromJson(data)) center: LatLng(json['CenterLat'], json['CenterLon']),
.toList(), subDiv: (json['Subdivisions'] as List<dynamic>).map((data) => SubDiv.fromJson(data)).toList(),
cornerList: (json['Corners'] as List<dynamic>) );
.map((data) => Corner.fromJson(data)) }
.toList(), }
bodyOfWater: json['BodyOfWater'],
/* class SubDiv {
dataList: (json['Data'] != null && json['Data'] is List) int subDivID;
? (json['Data'] as List<dynamic>).map((data) => Data.fromJson(data)).toList() int groupID;
: [], double minThickness;
cornerList: (json['Corner'] != null && json['Corner'] is List) double avgThickness;
? (json['Corner'] as List<dynamic>).map((data) => Corner.fromJson(data)).toList() LatLng center;
: [], double accuracy;
bodyOfWater: json['WaterBodyName'] ?? '',
*/ 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 { ...@@ -56,57 +73,8 @@ class Sensor {
factory Sensor.fromJson(Map<String, dynamic> json) { factory Sensor.fromJson(Map<String, dynamic> json) {
return Sensor( return Sensor(
sensorID: json['SensorID'], sensorID: json['SensorID'],
sensorType: json['SensorType'], sensorType: json['SensorType'] ?? 'nil',
active: json['Active'], 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
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
...@@ -69,12 +69,8 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ...@@ -69,12 +69,8 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'], subdomains: const ['a', 'b', 'c'],
), ),
PolygonLayer( /*PolygonLayer(
polygons: widget.markerList.map((Measurement measurement) { 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( return Polygon(
points: points, // Use list of corner coordinates to render polygon points: points, // Use list of corner coordinates to render polygon
...@@ -82,35 +78,14 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ...@@ -82,35 +78,14 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
isFilled: true, isFilled: true,
); );
}).toList(), }).toList(),
), ),*/
MarkerLayer( MarkerLayer(
markers: widget.markerList.map((Measurement measurement) { 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( return Marker(
width: 50, width: 50,
height: 50, height: 50,
point: point(corners), point: measurement.center, // Set markers at center of measurement
builder: (ctx) => GestureDetector( builder: (ctx) => GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
...@@ -126,7 +101,6 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ...@@ -126,7 +101,6 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
); );
}).toList(), }).toList(),
), ),
], ],
), ),
), ),
...@@ -268,7 +242,7 @@ class _MapContainerWidgetState extends State<MapContainerWidget> { ...@@ -268,7 +242,7 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
), ),
const SizedBox(height: contPadding), const SizedBox(height: contPadding),
Text( Text(
'Measuring point: (${selectedMarker?.dataList[0].latitude}, ${selectedMarker?.dataList[0].latitude})', 'Measuring point: (${selectedMarker?.measurementID}, ${selectedMarker?.measurementID})',
style: regTextStyle, style: regTextStyle,
), ),
const SizedBox(height: contPadding), const SizedBox(height: contPadding),
......
No preview for this file type
...@@ -4,14 +4,10 @@ ...@@ -4,14 +4,10 @@
HOST = "0.0.0.0" HOST = "0.0.0.0"
PORT = 8443 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"
# Certificate paths # Certificate paths
CERT_DIR = "server/certificates/" CERT_DIR = "server/certificates/"
MONGO_CERT_PATH = CERT_DIR + "MongoCert.pem"
SSL_KEY_PATH = CERT_DIR + "testKey.key" SSL_KEY_PATH = CERT_DIR + "testKey.key"
SSL_CERT_PATH = CERT_DIR + "testCert.crt" SSL_CERT_PATH = CERT_DIR + "testCert.crt"
# Measurement specs
AREA_SIZE = 20
\ No newline at end of file
No preview for this file type
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():
...@@ -6,7 +6,7 @@ import json ...@@ -6,7 +6,7 @@ import json
def get_all_markers(self, cursor, valid: bool, waterBodyName): def get_all_markers(self, cursor, valid: bool, waterBodyName):
try: try:
sql_query = ''' sql_query = '''
SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, SELECT m.MeasurementID, m.SensorID, m.TimeMeasured, m.CenterLat, m.CenterLon,
s.SensorType, s.Active, s.SensorType, s.Active,
b.Name, b.Name,
d.SubDivisionID, d.GroupID, d.MinimumThickness, d.SubDivisionID, d.GroupID, d.MinimumThickness,
...@@ -35,13 +35,13 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName): ...@@ -35,13 +35,13 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName):
# Create subdivision new object # Create subdivision new object
sub_division = { sub_division = {
'SubdivID': row[6], 'SubdivID': row[8],
'GroupID': row[7], 'GroupID': row[9],
'MinThickness': row[8], 'MinThickness': row[10],
'AvgThickness': row[9], 'AvgThickness': row[11],
'CenLatitude': row[10], 'CenLatitude': row[12],
'CenLongitude': row[11], 'CenLongitude': row[13],
'Accuracy': row[12] 'Accuracy': row[14]
} }
# Check if measurement ID already exists in measurement_data # Check if measurement ID already exists in measurement_data
...@@ -55,10 +55,12 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName): ...@@ -55,10 +55,12 @@ def get_all_markers(self, cursor, valid: bool, waterBodyName):
measurement_data[measurement_id] = { measurement_data[measurement_id] = {
'MeasurementID': measurement_id, 'MeasurementID': measurement_id,
'TimeMeasured': row[2], 'TimeMeasured': row[2],
'CenterLat': row[3],
'CenterLon': row[4],
'Sensor': { # Each measurement only has one related sensor 'Sensor': { # Each measurement only has one related sensor
'SensorID': row[1], 'SensorID': row[1],
'SensorType': row[3], 'SensorType': row[5],
'Active': bool(row[4]) 'Active': bool(row[6])
}, },
'Subdivisions': [sub_division], # Array of sub_division objects 'Subdivisions': [sub_division], # Array of sub_division objects
} }
......
No preview for this file type
...@@ -16,6 +16,8 @@ CREATE TABLE Measurement ( ...@@ -16,6 +16,8 @@ CREATE TABLE Measurement (
SensorID INT NOT NULL, SensorID INT NOT NULL,
TimeMeasured DATETIME NOT NULL, TimeMeasured DATETIME NOT NULL,
WaterBodyName TEXT NOT NULL, WaterBodyName TEXT NOT NULL,
CenterLat FLOAT NOT NULL,
CenterLon FLOAT NOT NULL,
WholeAverageThickness FLOAT NOT NULL, WholeAverageThickness FLOAT NOT NULL,
FOREIGN KEY (SensorID) REFERENCES Sensor(SensorID), FOREIGN KEY (SensorID) REFERENCES Sensor(SensorID),
FOREIGN KEY (WaterBodyName) REFERENCES BodyOfWater(Name) FOREIGN KEY (WaterBodyName) REFERENCES BodyOfWater(Name)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment