Skip to content
Snippets Groups Projects
Commit 99f6d4c9 authored by Hoa Ben The Nguyen's avatar Hoa Ben The Nguyen
Browse files

change: more addaptive torward the map

parents 720ed4d0 c410f6cb
No related branches found
No related tags found
No related merge requests found
Showing with 191 additions and 62 deletions
......@@ -13,6 +13,28 @@ Precompiled binaries can be found on https://www.sqlite.org/download.html. Extra
binary in a folder and note its path. Add the path to your system environment variables. Now you can
manage the SQLite database.
## Adding new maps
The current server only contains the data for a single lake, Mjøsa. To add more lakes
go to https://overpass-turbo.eu/. Once you have navigated to Overpass API, enter
the Overpass query below in the left field, but swap 'lakeName' out
with the name of the lake you want to add. Once the query has been adjusted,
press the 'Run' button.
```
[out:json];
(
way["natural"="water"]["name"="lakeName"];
relation["natural"="water"]["name"="lakeName"];
);
(._;>;);
out body;
```
If a text box saying "This query returned quite a lot of data (approx. x MB). Your browser may have a hard time trying to render this. Do you really want to continue?
" appears, press 'continue anyway'. Double check that you have
the correct lake, then press 'Export'. In the 'Export' menu, download the shape data as
GeoJson. Once downloaded, name the file the *lakeName.json, and move the file into
IceMap/server/lake_relations. Once you have added the file, run map division...
## Endpoints
## Bugs
......
......@@ -10,7 +10,7 @@ const int fetchInterval = 60; // Fetch marker data every n minutes
// Map variables
LatLng mapCenter = LatLng(60.7666, 10.8471);
DateTime ?lastUpdate; // Last time marker data was fetched from server
DateTime ?lastUpdate; // Last time data was fetched from server
// Font variables
const textColor = Colors.white;
......
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import '../consts.dart';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import '../consts.dart';
/// Fetch relation data from server
Future<Uint8List> fetchRelation() async {
......@@ -21,16 +23,38 @@ Future<Uint8List> fetchRelation() async {
var responseBody = await response.transform(utf8.decoder).join();
if (responseBody.isNotEmpty) {
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String filePath = '${appDocumentsDirectory.path}/last_relation.json';
try { // Write most recent time of update to file
await File(filePath).writeAsString(responseBody, mode: FileMode.write);
print('Relation written to file');
} catch (error) { print('Error in writing to file: $error');}
// Return relation data from the response body
return Uint8List.fromList(utf8.encode(responseBody));
} else {
throw Exception('Response body is empty');
}
} else {
throw Exception('Failed to fetch relation data: Status code ${response.statusCode}');
}
return loadSavedRelation();
} catch (e) {
throw Exception('Failed to fetch relation data: ${e.toString()}');
return loadSavedRelation();
}
}
Future<Uint8List> loadSavedRelation() async {
// Get latest saved relation from file if the server does not respond
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String filePath = '${appDocumentsDirectory.path}/last_relation.json';
// Read file contents
File file = File(filePath);
if (await file.exists()) {
String contents = await file.readAsString();
List<dynamic> jsonData = json.decode(contents); // Parse JSON string from file
Uint8List relation = Uint8List.fromList(utf8.encode(jsonData.toString()));
return relation;
} else {
throw Exception('File does not exist');
}
}
......@@ -7,10 +7,12 @@ import 'dart:convert';
/// A class containing thickness data for each subdivision of the map.
class IceThicknessModel {
IceThicknessModel(this.subDivID, this.thickness);
IceThicknessModel(this.sub_div_id, this.thickness, this.color, this.savedColor);
final String subDivID;
final String sub_div_id;
final int thickness;
Color color;
final Color savedColor;
}
/// ChoroplethMap is a stateful widget that contains a choropleth map.
......@@ -19,29 +21,42 @@ class IceThicknessModel {
class ChoroplethMap extends StatefulWidget {
const ChoroplethMap({Key? key,
required this.relation,
required this.measurements
required this.measurements,
required this.onSelectionChanged,
}) : super(key: key);
final Uint8List relation;
final List<Measurement> measurements;
final void Function(int selectedIndex) onSelectionChanged; // Callback function
@override
_ChoroplethMapState createState() => _ChoroplethMapState();
}
class _ChoroplethMapState extends State<ChoroplethMap> {
int selectedIndex = -1;
late MapShapeSource mapShapeSource;
late final MapZoomPanBehavior _zoomPanBehavior = MapZoomPanBehavior();
List<IceThicknessModel> iceThicknessList = <IceThicknessModel>[];
List<Color> testColors = [ // NB test color
const Color(0xff8a003b),
const Color(0xff8a4300),
const Color(0xff8a7a00),
const Color(0xff538a00),
const Color(0xff007b8a),
];
@override
void initState() {
super.initState();
final Random random = Random();
for (int i = 0; i <= 60; i++) {
for (int i = 0; i <= 120; i++) {
int ran = random.nextInt(5); // NB test color
Color randomColor = testColors[ran];
int randomNumber = random.nextInt(21); // 0 -> 20, NB: temp test data
iceThicknessList.add(IceThicknessModel(i.toString(), randomNumber));
iceThicknessList.add(IceThicknessModel(i.toString(), randomNumber, randomColor, randomColor));
}
}
......@@ -54,14 +69,30 @@ class _ChoroplethMapState extends State<ChoroplethMap> {
MapShapeLayer(
source: MapShapeSource.memory( // Map polygon
widget.relation, // JSON coordinates from server
shapeDataField: 'properties.SubDivID',
shapeDataField: 'sub_div_id',
dataCount: iceThicknessList.length,
primaryValueMapper: (int index) => iceThicknessList[index].subDivID,
shapeColorValueMapper: (int index) => iceThicknessList[index].thickness,
primaryValueMapper: (int index) => iceThicknessList[index].sub_div_id,
shapeColorValueMapper: (int index) => iceThicknessList[index].color,
),
color: Colors.blue.shade400, // Map color
//color: Colors.blue.shade400, // Map color
zoomPanBehavior: _zoomPanBehavior,
strokeColor: Colors.black,
// Shape selection
selectedIndex: selectedIndex,
onSelectionChanged: (int index) {
setState(() {
selectedIndex = index;
for (int i = 0; i < iceThicknessList.length; i++) {
iceThicknessList[i].color = i == index ? Colors.red : iceThicknessList[i].savedColor;
}
});
widget.onSelectionChanged(selectedIndex);
},
selectionSettings: MapSelectionSettings(
color: Colors.orange,
strokeColor: Colors.red[900],
strokeWidth: 3,
),
),
],
),
......
......@@ -28,6 +28,7 @@ class MapContainerWidget extends StatefulWidget {
class _MapContainerWidgetState extends State<MapContainerWidget> {
Measurement? selectedMarker; // Containing data for selected marker
int selectedMarkerIndex = 0;
bool isMinimized = true; // Quick view box state tacker
bool satLayer = false; // Satellite layer visibility tracker
bool isTapped = false; // Button tap state tracker
......@@ -53,10 +54,17 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
}
}
// Tile selection handler
void handleSelection(int index) {
setState(() {
selectedMarkerIndex = index;
});
}
@override
Widget build(BuildContext context) {
// Initialise selectedMarker to first element in markerList
selectedMarker ??= widget.markerList[0];
selectedMarker ??= widget.markerList[selectedMarkerIndex];
checkAndSetLastUpdate();
......@@ -154,7 +162,10 @@ class _MapContainerWidgetState extends State<MapContainerWidget> {
height: screenWidth * boxHeight,
child: Padding(
padding: const EdgeInsets.all(15.0), // Padding around map
child: ChoroplethMap(relation: widget.relation, measurements: widget.markerList,),
child: ChoroplethMap(
relation: widget.relation,
measurements: widget.markerList,
onSelectionChanged: handleSelection,),
),
),
Positioned( // Quick view box layered over map
......
No preview for this file type
......@@ -10,4 +10,4 @@ SSL_KEY_PATH = CERT_DIR + "testKey.key"
SSL_CERT_PATH = CERT_DIR + "testCert.crt"
# Measurement specs
AREA_SIZE = 20
\ No newline at end of file
AREA_SIZE = 20
File added
import numpy as np
import laspy
import json
import math
import utm # src: https://github.com/Turbo87/utm
from itertools import groupby
from server.data_processing.area_processing import calculate_corners, define_gridareas
from shapely.geometry import Polygon
# hard coded files for test data
lazData_path = ["server/example_lidar_data/ot_N_000005_1.laz", "server/example_lidar_data/ot_N_000033_1.laz"]
......@@ -37,6 +38,24 @@ def inArea(position, areaRange):
else:
return False
# find distance between two points
def distance(point1, point2):
x1, y1 = point1
x2, y2 = point2
return math.sqrt(abs(x2 - x1)**2 + abs(y2 - y1)**2)
# find the closest point in json list
def closest_points(point, list):
closest_point = None
closest_dist = float('inf')
for current_point in list:
lng, lat = current_point['properties']['sub_div_center'][0]
dist = distance(point, (lat, lng))
if dist < closest_dist:
closest_dist = dist
closest_point = current_point
return closest_point
# Calculation of height in an area from las files
def find_height(points):
height_differences = [] # final container for height data
......@@ -115,12 +134,17 @@ def calculate_area_data(center, cell_size, body_of_water):
((((min_point[0] - max_point[0]) * ((end[0]-center[0])/(area_limit[1][0]-area_limit[3][0]))) + (min_point[0] - max_point[0])/2 + max_point[0],
(((min_point[1] - max_point[1]) * ((end[1]-center[1])/(area_limit[1][1]-area_limit[3][1])))) + (min_point[1] - max_point[1])/2 + min_point[1]))]
# calculate map zones height
xs, ys = start
xe, ye = end
sub_center = ((xs + xe)/2, (ys + ye)/2)
current_map_zone = closest_points(sub_center, map_data)
current_zone_id = current_map_zone['properties']['sub_div_id']
# filter data within sub-area zones
points_in_area = list(filter(lambda point_position: inArea(point_position, areazone), ice_points))
area_heights.append((sub_area[0],sub_area[1],find_height(points_in_area)))
area_heights.append((current_zone_id,sub_area[1], sub_center, find_height(points_in_area)))
return area_heights
print(calculate_area_data((61,11), 0.04,'mjosa'))
print(abs(-69.9))
# print(calculate_area_data((61,11), 0.04,'mjosa'))
\ No newline at end of file
The following query is used to retrieve the relation(shape) of a lake.
Go to https://overpass-turbo.eu/#, enter the query in the left field, add
the name of the lake you want to add, and press 'run'.
If a text box saying "" appears, press 'continue anyways'. Double check that you have
the correct lake, then press 'Export'. In the 'Export' menu, download the shape data as
GeoJson. Once downloaded, name the file the *lakeName.json, and move the file into
IceMap/server/lake_relations. Once you have added the file, run map division...
[out:json];
(
way["natural"="water"]["name"="lakeName"];
......
......@@ -6,7 +6,6 @@ from map.get_relation import get_relation
from APIs.get_weather import get_weather
from map.input_new_data import input_new_Lidar_data
import ssl
import keyboard
import sqlite3
app = Flask(__name__)
......
No preview for this file type
import geopandas as gpd
from shapely.geometry import Polygon, LineString
from shapely.geometry import Polygon, LineString, MultiLineString
from shapely.ops import linemerge, unary_union, polygonize
import matplotlib.pyplot as plt
import random
import json
import os
polygon_min_x = None # The left most point of the entire polygon
# Read a json file with relation data and send to response object
def get_relation(self, body_of_water: str): # NB: implement body_of_water
# Read relation from GeoJson file and extract all polygons
......@@ -17,8 +14,7 @@ def get_relation(self, body_of_water: str): # NB: implement body_of_water
polygons = [Polygon(polygon.exterior) for polygon in polygon_data['geometry']]
if len(polygons) <= 1:
print("Failed to convert to polygons")
return
raise Exception("Failed to convert JSON object to Shapely Polygons")
divided_map = []
......@@ -32,32 +28,60 @@ def get_relation(self, body_of_water: str): # NB: implement body_of_water
divided_map.extend(combine_grid_with_poly(polygon, lines))
'''
####################### PLOTTING ############################
tiles = [gpd.GeoDataFrame(geometry=[tile]) for tile in divided_map]
sub_div_id = 0
for tile in tiles:
tile['sub_div_id'] = sub_div_id
# Warning: finner ikke center av grid celler, men center av punkt 0 eller annet? til (x,y)
tile['sub_div_center'] = tile['geometry'].centroid.apply(lambda x: [x.x, x.y])
sub_div_id += 1
print("Plotting... This may take some time...")
# NB test plot
fig, ax = plt.subplots()
ax.set_aspect(1.5)
# Plot each tile
for tile in tiles: # NB temporarily limited to 5 tiles
random_color = "#{:06x}".format(random.randint(0, 0xFFFFFF))
gpd.GeoSeries(tile.geometry).plot(ax=ax, facecolor=random_color, edgecolor='none')
tiles_json = {'type': 'FeatureCollection', 'features': []}
for tile in tiles:
feature = {
plt.show()
##################### PLOTTIND END ###########################
'''
features = []
sub_div_id = 0
for tile in divided_map: # NB temporarily limited to 5 tiles
# Round coordinates to 4 decimals
center = round(tile.centroid.coords[0][0], 4), round(tile.centroid.coords[0][1], 4)
rounded_coordinates = []
if isinstance(tile, Polygon):
for coords in tile.exterior.coords:
rounded_coords = (round(coords[0], 4), round(coords[1], 4))
rounded_coordinates.append(rounded_coords)
rounded_tile = Polygon(rounded_coordinates)
tile_feature = {
'type': 'Feature',
'geometry': tile.geometry.__geo_interface__,
'properties': {
'sub_div_id': int(tile['sub_div_id'].iloc[0]),
'sub_div_center': tile['sub_div_center'].tolist()
}
'sub_div_id': str(sub_div_id),
'sub_div_center': center
},
'geometry': rounded_tile.__geo_interface__
}
tiles_json['features'].append(feature)
features.append(tile_feature)
sub_div_id += 1
feature_collection = {
'type': 'FeatureCollection',
'features': features
}
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(tiles_json).encode('utf-8'))
self.wfile.write(json.dumps(feature_collection).encode('utf-8'))
def create_grid(poly: Polygon, cell_size):
......@@ -85,17 +109,17 @@ def create_grid(poly: Polygon, cell_size):
def combine_grid_with_poly(polygon, grid):
# Create an empty list to store tiles intersecting the polygon
intersecting_tiles = []
# Iterate through each grid line
for line in grid:
# Check if the line intersects with the polygon
if line.intersects(polygon):
# If the line intersects, find the intersection points
intersection = line.intersection(polygon)
# Add each line to the list
intersecting_tiles.append(intersection)
# Check if intersection is a MultiLineString
if isinstance(intersection, MultiLineString):
# Extend the intersecting tiles with the polygonized results
intersecting_tiles.extend(list(polygonize(intersection)))
else:
intersecting_tiles.append(intersection)
return intersecting_tiles
......
......@@ -30,10 +30,12 @@ def input_new_Lidar_data(self, cursor, bodyOfWater):
# calculate the area of to be calculated based on the coordinates given to the calculation model
areas_data = calculate_area_data((latitude, longitude), 0.04, bodyOfWater)
if(areas_data):
# calculate data for each zone within the area
for area in areas_data:
# lng and lat relative to map
map_lng, map_lat = area
if(len(area[2]) != 0):
average = sum(area[2])/len(area[2])
minimum_thickness = min(area[2])
......@@ -47,7 +49,7 @@ def input_new_Lidar_data(self, cursor, bodyOfWater):
cursor.execute('''
INSERT INTO SubDivision(MeasurementID, SubDivisionID, GroupID, MinimumThickness, AverageThickness, CenterLatitude, CenterLongitude, Accuracy) VALUES
(?,?,?,?,?,?,?,?);
''',(measurement_id, area[0], area[1], float(minimum_thickness), float(average), float(latitude), float(longitude), float(1)))
''',(measurement_id, area[0], area[1], float(minimum_thickness), float(average), float(map_lat), float(map_lng), float(1)))
total_measurement_average = total_measurement_average / len(areas_data)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment