Skip to content
Snippets Groups Projects
get_relation.py 6.46 KiB
import geopandas as gpd
from shapely.geometry import Polygon, Point, LineString, MultiPolygon
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import random
import numpy as np


def get_relation(self, body_of_water: str):
    # Load GeoJSON data using geopandas
    geo_data = gpd.read_file("server/map/mjosa.geojson")

    # Filter only polygons, exclude points and other feature types to reduce response size
    polygon_data = geo_data[geo_data['geometry'].geom_type == 'Polygon']

    if polygon_data.empty:
        raise ValueError("Failed to extract polygon data from file")

    # Extract coordinates from polygons and create polygon objects
    polygons = [Polygon(polygon.exterior) for polygon in polygon_data['geometry']]

    if not polygons:
        raise ValueError("Failed to convert to polygons")

    divided_map = []

    # Divide all polygons into sections
    for polygon in polygons:

        cell_size = 0.1
        # Divide the length and with of polygon into a grid of equally sized parts
        grid_lines = create_grid_coords(polygon, cell_size)

        vrt_lines = grid_lines[0]  # Vertical grid coordinates
        hrz_lines = grid_lines[1]  # Horizontal grid coordinates

        # Cut polygon into horizontal sections, bottom to top
        for hrz_line in hrz_lines:

            # Split shape into upper and lower section as hrz_line as divider
            divided_poly = cut_polygon_in_two(polygon, hrz_line, cell_size)
            horizontal_section = divided_poly[0]  # Save upper horizontal section

            polygon = divided_poly[1]  # Set polygon to the remaining, un-split shape for next iteration

            if not horizontal_section or not divided_poly[0]:
                continue

            # Cut each horizontal section into vertical sections, right to left
            for vrt_line in vrt_lines:
                if len(horizontal_section.exterior.coords) < 3:
                    break # Break from loop im remaining section has no coordinates

                # Split the horizontal section into two vertical parts
                vertical_parts = cut_polygon_in_two(horizontal_section, vrt_line, -0.1)

                divided_map.append(vertical_parts[0])  # Append split vertical sections to final list of shapes

                # Set horizontal_section to the remaining, un-split, horizontal section for next iteration
                horizontal_section = vertical_parts[1]

        divided_map.append(polygon)

        break

    tiles = gpd.GeoDataFrame(geometry=divided_map)

    # NB test plot
    fig, ax = plt.subplots()
    ax.set_aspect(1.5)
    ax.xaxis.set_major_locator(ticker.MultipleLocator(0.2))

    for i, polygon in enumerate(tiles['geometry']):
        random_color = "#{:06x}".format(random.randint(0, 0xFFFFFF))

        x, y = polygon.exterior.xy
        ax.plot(x, y, color='blue', alpha=0.5)
        ax.fill(x, y, color=random_color, alpha=1)

    plt.show()

    # Convert GeoDataFrame to GeoJSON
    tiles_json = tiles.to_json()

    # Set headers
    self.send_response(200)
    self.send_header("Content-type", "application/json")
    self.end_headers()

    # Write GeoJSON to response object
    self.wfile.write(tiles_json.encode('utf-8'))


# Takes a polygon and divides its coordinates into two shapes, where divisor is a
# coordinate that defines the point of division
def cut_polygon_in_two(polygon: Polygon, divisor: float, cell_size: float):
    # Extract polygon exterior coordinates
    exterior_coords = list(polygon.exterior.coords)

    # Initialize lists to store coordinates of new shapes after split
    split_shape = []
    remaining_shape = []

    # Loop through points and check which side of the division line they are
    if cell_size > 0:  # Horizontal split
        for point in exterior_coords:
            point = Point(point)  # Convert coordinates to Shapely Point object
            if point.y < divisor:
                split_shape.append(point)
            else:
                remaining_shape.append(point)

        if len(split_shape) > 2:
            # Get last point added to
            last_point = split_shape[len(split_shape)-1]
            # Get length of the newly created edge
            new_edge_len = last_point.x - split_shape[0].x - 0.0001
            print("new_edge_len: ", new_edge_len, "    cell_size: ", cell_size)

            # Add points along the new edge to allow horizontal sections to be split into vertical ones
            while new_edge_len > cell_size:
                print("Hit")
                split_shape.append((new_edge_len-cell_size, last_point.y))
                remaining_shape.append((new_edge_len-cell_size, last_point.y))
                new_edge_len -= cell_size

    else:  # Vertical split
        for point in exterior_coords:
            point = Point(point)  # Convert coordinates to Shapely Point object
            if point.x < divisor:
                split_shape.append(point)
            else:
                remaining_shape.append(point)

    # Check if the split_shape has enough coordinates to create a polygon
    if len(split_shape) < 3:
        # print("Not enough coordinates to create valid polygon: Split shape: ", len(split_shape))
        split_shape = None
    else:
        split_shape.append(split_shape[0])  # Append first coord to create closed loop

    # Check if the remaining_shape has enough coordinates to create a polygon
    if len(remaining_shape) < 3:
        # print("Not enough coordinates to create valid polygon: Remaining shape: ", len(remaining_shape))
        remaining_shape = None
    else:
        remaining_shape.append(remaining_shape[0])  # Append first coord to create closed loop

    # Return split polygons as Shapely Polygon objects
    return Polygon(split_shape), Polygon(remaining_shape)


# Generate grid of equally spaced x and y coordinates where the grid size is determined by cell_size
def create_grid_coords(polygon: Polygon, cell_size: float):
    # Define boundaries of grid
    min_x, min_y, max_x, max_y = polygon.bounds

    # Divide grid into sections of size *cell_size
    x_coords = np.arange(min_x, max_x, cell_size)
    y_coords = np.arange(min_y, max_y, cell_size)

    # Round the coordinates to 4 decimals
    x_coords_rounded = np.around(x_coords, decimals=4)
    y_coords_rounded = np.around(y_coords, decimals=4)

    if len(x_coords_rounded) == 0 or len(y_coords_rounded) == 0:
        raise ValueError("No grid points generated")

    # Return tuple of list of x coordinates and list of y coordinates
    return x_coords_rounded, y_coords_rounded