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 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'widgets/map_widget.dart';
import 'marker_handler/marker_data.dart';
import 'consts.dart';
import 'marker_handler/get_markers.dart';
import 'marker_handler/get_relation.dart';
import 'dart:typed_data';
import 'dart:io';
import 'dart:convert';
class DefaultPage extends StatefulWidget {
const DefaultPage({Key? key}) : super(key: key);
......@@ -28,6 +25,7 @@ class _DefaultPageState extends State<DefaultPage> {
@override
void initState() {
super.initState();
// Try to fetch measurement data from server
markerListFuture = fetchMarkerData().then((fetchResult) {
List<Measurement> measurements = fetchResult.measurements;
serverConnection = fetchResult.connected;
......@@ -39,8 +37,8 @@ class _DefaultPageState extends State<DefaultPage> {
});
//relationFuture = fetchRelation();
relationFuture = Future.value(Uint8List(0));
relationFuture = fetchRelation();
//relationFuture = Future.value(Uint8List(0));
// Schedule fetchMarkerData to run periodically based on fetchInterval from consts
const Duration interval = Duration(minutes: fetchInterval); // NB fetchInterval value to be determined
......@@ -87,7 +85,7 @@ class _DefaultPageState extends State<DefaultPage> {
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
if (!serverConnection) {
if (!serverConnection) { // NB: implement alert
print("Failed to connect to server");
}
// Display default page once all data is loaded from server
......
......@@ -3,8 +3,9 @@ import '../marker_handler/marker_data.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_maps/maps.dart';
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 {
IceThicknessModel(this.subDivID, this.thickness);
......@@ -12,9 +13,9 @@ class IceThicknessModel {
final int thickness;
}
/// A stateful widget which contains a choropleth map.
/// The map data is fetched from the server, and the map is rendered
/// using the Syncfusion Flutter Maps library.
/// ChoroplethMap is a stateful widget that contains a choropleth map.
/// The map is created using the Syncfusion Flutter Maps library and
/// coordinates fetched from the server.
class ChoroplethMap extends StatefulWidget {
const ChoroplethMap({Key? key,
required this.relation,
......@@ -39,55 +40,101 @@ class _ChoroplethMapState extends State<ChoroplethMap> {
final Random random = Random();
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));
}
}
@override
Widget build(BuildContext context) {
return SfMaps(
layers: [
MapShapeLayer(
source: MapShapeSource.memory(
widget.relation,
shapeDataField: 'properties.SubDivID',
dataCount: iceThicknessList.length,
primaryValueMapper: (int index) => iceThicknessList[index].subDivID,
shapeColorValueMapper: (int index) => iceThicknessList[index].thickness,
shapeColorMappers: const [
MapColorMapper(
from: null, // Default color
color: Colors.grey,
text: 'No value',
),
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'),
],
return Stack(
children: [
SfMaps(
layers: [
MapShapeLayer(
source: MapShapeSource.memory( // Map polygon
widget.relation, // JSON coordinates from server
shapeDataField: 'properties.SubDivID',
dataCount: iceThicknessList.length,
primaryValueMapper: (int index) => iceThicknessList[index].subDivID,
shapeColorValueMapper: (int index) => iceThicknessList[index].thickness,
),
color: Colors.lightBlueAccent, // Map color
zoomPanBehavior: _zoomPanBehavior,
strokeColor: Colors.orange,
),
//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
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):
print("In get_relation")
# Load GeoJSON data using geopandas
geo_data = gpd.read_file("server/map/mjosa.geojson")
......@@ -15,8 +19,13 @@ def get_relation(self, body_of_water: str):
print("Failed to convert to polygons")
return
# Divide relation into tiles
tiles = divide_relation(polygons)
print("Performing div call")
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
tiles_json = tiles.to_json()
......@@ -26,40 +35,52 @@ def get_relation(self, body_of_water: str):
self.send_header("Content-type", "application/json")
self.end_headers()
# Write coordinates to response object
# Write GeoJSON to response object
self.wfile.write(tiles_json.encode('utf-8'))
def divide_relation(polygons):
# Define tile size
tile_size = 0.1
subdiv_id = 0
def divide_relation(polygons, cell_size):
# Calculate the combined bounding box of all polygons
combined_bounds = polygons[0].bounds
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 = []
# Iterate over each polygon
for polygon in polygons:
x_min, y_min, x_max, y_max = polygon.bounds
rows = int((y_max - y_min) / tile_size)
cols = int((x_max - x_min) / tile_size)
if rows == 0 or cols == 0: # Skip polygons that are smaller than the division size
continue
for row in range(rows):
for col in range(cols):
tile_bbox = Polygon([ # Calculate coordinate for current place in column and row
(x_min + col * tile_size, y_min + row * tile_size),
(x_min + (col + 1) * tile_size, y_min + row * tile_size),
(x_min + (col + 1) * tile_size, y_min + (row + 1) * tile_size),
(x_min + col * tile_size, y_min + (row + 1) * tile_size)
])
tiles.append({"SubDivID": subdiv_id, "geometry": tile_bbox})
subdiv_id += 1
if len(tiles) <= 1: # Return empty object if division fails
print("Failed to divide polygons into tiles")
return []
# Convert tiles list to a GeoDataFrame
tiles_df = gpd.GeoDataFrame(tiles, geometry='geometry')
return tiles_df
# Iterate over each cell
for i in np.arange(combined_bounds[1], combined_bounds[3], cell_size):
for j in np.arange(combined_bounds[0], combined_bounds[2], cell_size):
# Define the bounds of the cell
cell_bounds = (j, i, j + cell_size, i + cell_size)
# Create a polygon representing the cell
cell_polygon = Polygon([(cell_bounds[0], cell_bounds[1]),
(cell_bounds[2], cell_bounds[1]),
(cell_bounds[2], cell_bounds[3]),
(cell_bounds[0], cell_bounds[3])])
# Check if the cell polygon intersects with the current polygon
if polygon.intersects(cell_polygon):
tiles.append(cell_polygon)
# Create a GeoDataFrame from the list of tiles
tiles_gdf = gpd.GeoDataFrame(geometry=tiles)
return tiles_gdf
def divide_relation_2(polygons, cell_size):
polygon = Point(0, 0).buffer(2).difference(Point(0, 0).buffer(1))
line1 = LineString([(0, 0), (3, 3)])
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