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

update: working division of relation

parent 2a22203f
No related branches found
No related tags found
1 merge request!6Clhp map
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'widgets/map_widget.dart'; import 'widgets/map_widget.dart';
import 'marker_handler/marker_data.dart'; import 'marker_handler/marker_data.dart';
import 'consts.dart'; import 'consts.dart';
import 'marker_handler/get_markers.dart'; import 'marker_handler/get_markers.dart';
import 'marker_handler/get_relation.dart'; import 'marker_handler/get_relation.dart';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:io';
import 'dart:convert';
class DefaultPage extends StatefulWidget { class DefaultPage extends StatefulWidget {
const DefaultPage({Key? key}) : super(key: key); const DefaultPage({Key? key}) : super(key: key);
...@@ -28,6 +25,7 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -28,6 +25,7 @@ class _DefaultPageState extends State<DefaultPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Try to fetch measurement data from server
markerListFuture = fetchMarkerData().then((fetchResult) { markerListFuture = fetchMarkerData().then((fetchResult) {
List<Measurement> measurements = fetchResult.measurements; List<Measurement> measurements = fetchResult.measurements;
serverConnection = fetchResult.connected; serverConnection = fetchResult.connected;
...@@ -39,8 +37,8 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -39,8 +37,8 @@ class _DefaultPageState extends State<DefaultPage> {
}); });
//relationFuture = fetchRelation(); relationFuture = fetchRelation();
relationFuture = Future.value(Uint8List(0)); //relationFuture = Future.value(Uint8List(0));
// Schedule fetchMarkerData to run periodically based on fetchInterval from consts // Schedule fetchMarkerData to run periodically based on fetchInterval from consts
const Duration interval = Duration(minutes: fetchInterval); // NB fetchInterval value to be determined const Duration interval = Duration(minutes: fetchInterval); // NB fetchInterval value to be determined
...@@ -87,7 +85,7 @@ class _DefaultPageState extends State<DefaultPage> { ...@@ -87,7 +85,7 @@ class _DefaultPageState extends State<DefaultPage> {
} else if (snapshot.hasError) { } else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}')); return Center(child: Text('Error: ${snapshot.error}'));
} else { } else {
if (!serverConnection) { if (!serverConnection) { // NB: implement alert
print("Failed to connect to server"); print("Failed to connect to server");
} }
// Display default page once all data is loaded from server // Display default page once all data is loaded from server
......
...@@ -3,8 +3,9 @@ import '../marker_handler/marker_data.dart'; ...@@ -3,8 +3,9 @@ import '../marker_handler/marker_data.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_maps/maps.dart'; import 'package:syncfusion_flutter_maps/maps.dart';
import 'dart:math'; import 'dart:math';
import 'dart:convert';
/// A class containing thickness for each subdivision of the map. /// A class containing thickness data for each subdivision of the map.
class IceThicknessModel { class IceThicknessModel {
IceThicknessModel(this.subDivID, this.thickness); IceThicknessModel(this.subDivID, this.thickness);
...@@ -12,9 +13,9 @@ class IceThicknessModel { ...@@ -12,9 +13,9 @@ class IceThicknessModel {
final int thickness; final int thickness;
} }
/// A stateful widget which contains a choropleth map. /// ChoroplethMap is a stateful widget that contains a choropleth map.
/// The map data is fetched from the server, and the map is rendered /// The map is created using the Syncfusion Flutter Maps library and
/// using the Syncfusion Flutter Maps library. /// coordinates fetched from the server.
class ChoroplethMap extends StatefulWidget { class ChoroplethMap extends StatefulWidget {
const ChoroplethMap({Key? key, const ChoroplethMap({Key? key,
required this.relation, required this.relation,
...@@ -39,55 +40,101 @@ class _ChoroplethMapState extends State<ChoroplethMap> { ...@@ -39,55 +40,101 @@ class _ChoroplethMapState extends State<ChoroplethMap> {
final Random random = Random(); final Random random = Random();
for (int i = 0; i <= 60; i++) { for (int i = 0; i <= 60; i++) {
int randomNumber = random.nextInt(21); // 0 -> 20 int randomNumber = random.nextInt(21); // 0 -> 20, NB: temp test data
iceThicknessList.add(IceThicknessModel(i.toString(), randomNumber)); iceThicknessList.add(IceThicknessModel(i.toString(), randomNumber));
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SfMaps( return Stack(
layers: [ children: [
MapShapeLayer( SfMaps(
source: MapShapeSource.memory( layers: [
widget.relation, MapShapeLayer(
shapeDataField: 'properties.SubDivID', source: MapShapeSource.memory( // Map polygon
dataCount: iceThicknessList.length, widget.relation, // JSON coordinates from server
primaryValueMapper: (int index) => iceThicknessList[index].subDivID, shapeDataField: 'properties.SubDivID',
shapeColorValueMapper: (int index) => iceThicknessList[index].thickness, dataCount: iceThicknessList.length,
shapeColorMappers: const [ primaryValueMapper: (int index) => iceThicknessList[index].subDivID,
MapColorMapper( shapeColorValueMapper: (int index) => iceThicknessList[index].thickness,
from: null, // Default color ),
color: Colors.grey, color: Colors.lightBlueAccent, // Map color
text: 'No value', zoomPanBehavior: _zoomPanBehavior,
), strokeColor: Colors.orange,
MapColorMapper(
from: 0,
to: 3,
color: Color.fromRGBO(223,169,254, 1),
text: '0-3'),
MapColorMapper(
from: 4,
to: 8,
color: Color.fromRGBO(190,78,253, 1),
text: '4-8'),
MapColorMapper(
from: 9,
to: 15,
color: Color.fromRGBO(167,17,252, 1),
text: '9-15'),
MapColorMapper(
from: 16,
to: 20,
color: Color.fromRGBO(170,20,250, 1),
text: '16-20'),
],
), ),
//color: Colors.lightBlueAccent, ],
zoomPanBehavior: _zoomPanBehavior, ),
strokeColor: Colors.orange, CustomPaint( // Render polygons clipped on map
), size: Size.infinite,
], painter: OverlayPainter(mapData: widget.relation),
),
],
); );
} }
} }
/// OverlayPainter is a class extending CustomPainter to
class OverlayPainter extends CustomPainter {
final Uint8List mapData;
const OverlayPainter({
required this.mapData, // Pass map shape
});
@override
void paint(Canvas canvas, Size size) {
// NB: test polygon
Path testPol = Path();
testPol.moveTo(60.8015, 10.5908);
testPol.lineTo(60.5146, 10.8270);
testPol.lineTo(60.7115, 11.3242);
testPol.close();
// Decode map data
var mapShape = json.decode(utf8.decode(mapData));
// Extract polygons and render them
for (var feature in mapShape['features']) {
var geometry = feature['geometry'];
var coordinates = geometry['coordinates'];
for (var ring in coordinates) {
var points = <Offset>[];
for (var point in ring) {
points.add(Offset(point[0], point[1]));
}
var path = Path()..addPolygon(points, true);
// Render each map shape
canvas.drawPath(path, Paint()..color = Colors.lightBlueAccent);
}
}
// Now clip the rendering to the map shape
var mapBoundsPath = Path();
for (var feature in mapShape['features']) {
var geometry = feature['geometry'];
var coordinates = geometry['coordinates'];
for (var ring in coordinates) {
var points = <Offset>[];
for (var point in ring) {
points.add(Offset(point[0], point[1]));
}
mapBoundsPath.addPolygon(points, true);
}
}
canvas.clipPath(mapBoundsPath);
// Render test shape over map, not clipped to map
canvas.drawPath(testPol, Paint()..color = Colors.pink.shade900);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
No preview for this file type
No preview for this file type
import geopandas as gpd import geopandas as gpd
from shapely.geometry import Polygon from shapely.geometry import Polygon, Point, LineString
import matplotlib.pyplot as plt
import numpy as np
def get_relation(self, body_of_water: str): def get_relation(self, body_of_water: str):
print("In get_relation")
# Load GeoJSON data using geopandas # Load GeoJSON data using geopandas
geo_data = gpd.read_file("server/map/mjosa.geojson") geo_data = gpd.read_file("server/map/mjosa.geojson")
...@@ -15,8 +19,13 @@ def get_relation(self, body_of_water: str): ...@@ -15,8 +19,13 @@ def get_relation(self, body_of_water: str):
print("Failed to convert to polygons") print("Failed to convert to polygons")
return return
# Divide relation into tiles print("Performing div call")
tiles = divide_relation(polygons) tiles = divide_relation(polygons, 0.01) # NB: any size under 0.02 will require patience
print("Executed div call")
# NB: temp test plot of map
tiles.plot(color='blue', edgecolor='orange', linewidth=0.5)
plt.show()
# Convert GeoDataFrame to GeoJSON # Convert GeoDataFrame to GeoJSON
tiles_json = tiles.to_json() tiles_json = tiles.to_json()
...@@ -26,40 +35,52 @@ def get_relation(self, body_of_water: str): ...@@ -26,40 +35,52 @@ def get_relation(self, body_of_water: str):
self.send_header("Content-type", "application/json") self.send_header("Content-type", "application/json")
self.end_headers() self.end_headers()
# Write coordinates to response object # Write GeoJSON to response object
self.wfile.write(tiles_json.encode('utf-8')) self.wfile.write(tiles_json.encode('utf-8'))
def divide_relation(polygons): def divide_relation(polygons, cell_size):
# Define tile size # Calculate the combined bounding box of all polygons
tile_size = 0.1 combined_bounds = polygons[0].bounds
subdiv_id = 0 for polygon in polygons[1:]:
combined_bounds = (min(combined_bounds[0], polygon.bounds[0]),
min(combined_bounds[1], polygon.bounds[1]),
max(combined_bounds[2], polygon.bounds[2]),
max(combined_bounds[3], polygon.bounds[3]))
# Create an empty list to store polygons representing tiles
tiles = [] tiles = []
# Iterate over each polygon
for polygon in polygons: for polygon in polygons:
x_min, y_min, x_max, y_max = polygon.bounds # Iterate over each cell
rows = int((y_max - y_min) / tile_size) for i in np.arange(combined_bounds[1], combined_bounds[3], cell_size):
cols = int((x_max - x_min) / tile_size) for j in np.arange(combined_bounds[0], combined_bounds[2], cell_size):
# Define the bounds of the cell
if rows == 0 or cols == 0: # Skip polygons that are smaller than the division size cell_bounds = (j, i, j + cell_size, i + cell_size)
continue
# Create a polygon representing the cell
for row in range(rows): cell_polygon = Polygon([(cell_bounds[0], cell_bounds[1]),
for col in range(cols): (cell_bounds[2], cell_bounds[1]),
tile_bbox = Polygon([ # Calculate coordinate for current place in column and row (cell_bounds[2], cell_bounds[3]),
(x_min + col * tile_size, y_min + row * tile_size), (cell_bounds[0], cell_bounds[3])])
(x_min + (col + 1) * tile_size, y_min + row * tile_size),
(x_min + (col + 1) * tile_size, y_min + (row + 1) * tile_size), # Check if the cell polygon intersects with the current polygon
(x_min + col * tile_size, y_min + (row + 1) * tile_size) if polygon.intersects(cell_polygon):
]) tiles.append(cell_polygon)
tiles.append({"SubDivID": subdiv_id, "geometry": tile_bbox})
subdiv_id += 1 # Create a GeoDataFrame from the list of tiles
tiles_gdf = gpd.GeoDataFrame(geometry=tiles)
if len(tiles) <= 1: # Return empty object if division fails
print("Failed to divide polygons into tiles") return tiles_gdf
return []
# Convert tiles list to a GeoDataFrame def divide_relation_2(polygons, cell_size):
tiles_df = gpd.GeoDataFrame(tiles, geometry='geometry') polygon = Point(0, 0).buffer(2).difference(Point(0, 0).buffer(1))
line1 = LineString([(0, 0), (3, 3)])
return tiles_df line2 = LineString([(0, 0), (3, -3)])
line1_pol = line1.buffer(1e-3)
line2_pol = line2.buffer(1e-3)
new_polygon = polygon.difference(line1_pol).difference(line2_pol)
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