Source code for simplify_aoi

import json
import os

import fiona
from shapely import concave_hull, unary_union
from shapely.geometry import Polygon, mapping, shape


[docs] def get_coordinates(file_path): """ Helper Method - Gets coordinates from a GeoJSON file Parameters ---------- file_path: str Path location of geojson file Returns ------- coordinates_list: List[float] List of vertices from GeoJSON polygon """ with open(file_path) as f: data = json.load(f) coordinates_list = [] for feature in data["features"]: geometry = feature["geometry"] geometry_type = geometry["type"] coordinates = geometry["coordinates"] if geometry_type in ["Point", "LineString"]: coordinates_list.append(coordinates) elif geometry_type == "Polygon": for polygon in coordinates: coordinates_list.extend(polygon) elif geometry_type == "MultiPolygon": for multipolygon in coordinates: for polygon in multipolygon: coordinates_list.extend(polygon) return coordinates_list
[docs] def vertex_count(file_path): """ Counts vertices from a GeoJSON file. Parameters ---------- file_path: str Path location of geojson file Returns ------- vertex_num: int Number of vertices in geojson file """ vertex_num = len(get_coordinates(file_path)) - 1 return vertex_num
[docs] def reduce_vertex(file_path, ratio): """ Reduces the number of vertices in a polygon Parameters ---------- file_path: str Path location of geojson file ratio: float Sets the concave_hull ratio """ with fiona.open(file_path) as collection: hulls = [concave_hull(shape(feat["geometry"]), ratio) for feat in collection] dissolved_hulls = mapping(unary_union(hulls)) with open("reduced_vertex.geojson", "w") as f: json.dump(dissolved_hulls, f)
[docs] def check_hole(file_path): """ Checks if a polygon contains a hole. Parameters ---------- file_path: str Path location of geojson file Returns ------- boolean: bool True if there is a hole in the polygon, false if there is not """ with open(file_path) as f: geojson = json.load(f) for feature in geojson["features"]: if feature["geometry"]["type"] == "Polygon": coordinates = feature["geometry"]["coordinates"] if len(coordinates) > 1: return True return False
[docs] def fill_holes(file_path: str): """ Fills holes of GeoJSON by deleting interior ring coordinates and creating a new GeoJSON with new coordinates Parameters ---------- file_path: str Path location of geojson file """ coordinates_list = get_coordinates(file_path) new_coordinates_list = [] first_entry = coordinates_list[0] new_coordinates_list.append(first_entry) for coordinate in coordinates_list[1:]: new_coordinates_list.append(coordinate) if coordinate == first_entry: break geojson = { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": {"type": "Polygon", "coordinates": [new_coordinates_list]}, "properties": {}, } ], } with open("filled_holes.geojson", "w") as f: json.dump(geojson, f)
[docs] def check_overlap(file_path: str) -> bool: """ Checks if two polygons are overlapping from a GeoJSON file Parameters ---------- file_path: str Path location of geojson file Returns ------- boolean: bool True if there is overlap and false if there is no overlap """ coordinates_list = get_coordinates(file_path) polygon1 = [] polygon2 = [] first_entry = coordinates_list[0] polygon1.append(first_entry) divider = 0 for coordinate in coordinates_list[1:]: polygon1.append(coordinate) if coordinate == first_entry: divider = coordinates_list[1:].index(coordinate) + 1 break first_entry = coordinates_list[divider] for coordinate in coordinates_list[divider + 1 :]: polygon2.append(coordinate) if coordinate == first_entry: divider = coordinates_list[1:].index(coordinate) + 1 break return Polygon(polygon1).intersects(Polygon(polygon2))
[docs] def fix_overlap(file_path: str): """ Fixes the overlap of two polygons by deleting the overlapped area and merging the two polygons into one polygon Parameters ---------- file_path: str Path location of geojson file """ coordinates_list = get_coordinates(file_path) polygon1 = [] polygon2 = [] first_entry = coordinates_list[0] polygon1.append(first_entry) divider = 0 for coordinate in coordinates_list[1:]: polygon1.append(coordinate) if coordinate == first_entry: divider = coordinates_list[1:].index(coordinate) + 1 break first_entry = coordinates_list[divider] for coordinate in coordinates_list[divider + 1 :]: polygon2.append(coordinate) if coordinate == first_entry: divider = coordinates_list[1:].index(coordinate) + 1 break combined_polygon = unary_union([Polygon(polygon1), Polygon(polygon2)]) geojson = { "type": "FeatureCollection", "features": [ {"type": "Feature", "geometry": mapping(combined_polygon), "properties": {}} ], } with open("corrected_overlap.geojson", "w") as f: json.dump(geojson, f)
[docs] def clipping_check(file_path: str, AOI_Coordinates: list) -> bool: """ Checks if a given polygon clips outside the contracted AOI coordinates Parameters ---------- file_path: str Path location of geojson file AOI_Coordinates: List[float] List of coordinates representing the AOI bounds Returns ------- boolean: bool True if it clips outside and false if it does not """ given_polygon = Polygon(get_coordinates(file_path)) first_array = AOI_Coordinates[0] last_array = AOI_Coordinates[-1] if first_array != last_array: AOI_Coordinates.append(first_array.copy()) AOI_Bounds = Polygon(AOI_Coordinates) return not given_polygon.within(AOI_Bounds)
[docs] def fix_clipping(file_path: str, AOI_Coordinates: list): """ Deletes all parts of a Polygon that exceed the AOI coordinate bounds Parameters ---------- file_path: str Path location of geojson file AOI_Coordinates: List[float] List of coordinates representing the AOI bounds """ first_array = AOI_Coordinates[0] last_array = AOI_Coordinates[-1] if first_array != last_array: AOI_Coordinates.append(first_array.copy()) combined_polygon = Polygon(AOI_Coordinates).intersection( Polygon(get_coordinates(file_path)) ) geojson = { "type": "FeatureCollection", "features": [ {"type": "Feature", "geometry": mapping(combined_polygon), "properties": {}} ], } with open("corrected_clipping.geojson", "w") as f: json.dump(geojson, f)
[docs] def simplify(file_path: str, ratio: int, AOI_Coordinates: list): """ Runs all checks and fixes to adhere to Planet's AOI Geometry Limits Parameters ---------- file_path: str Path location of geojson file AOI_Coordinates: List[float] List of coordinates representing the AOI bounds """ update_file_path = file_path # Vertices Check if vertex_count(file_path) > 500: print("Too many vertices, reducing number") reduce_vertex(update_file_path) update_file_path = "reduced_vertex.geojson" # Holes and Exterior Rings Check if check_hole(update_file_path): print("Polygon contains hole, filling hole") fill_holes(update_file_path) update_file_path = "filled_holes.geojson" # Overlapping and Intersections Check if check_overlap(update_file_path): print("Polygons are overlapping, combining polygons to eliminate overlap") fix_overlap(update_file_path) update_file_path = "corrected_overlap.geojson" # Clipping outside AOI Check if clipping_check(update_file_path, AOI_Coordinates): print("Polygon clips outside of AOI, cutting outside areas") fix_clipping(update_file_path, AOI_Coordinates) update_file_path = "corrected_clipping.geojson" try: os.rename(update_file_path, "Simplified.geojson") print( "Your new simplified file is called Simplified.geojson. For more information on Planet's AOI Geometry Limits, check out this link: https://developers.planet.com/docs/subscriptions/tools/" ) except PermissionError: print("Permission denied.") if os.path.exists("reduced_vertex.geojson"): os.remove("reduced_vertex.geojson") if os.path.exists("filled_holes.geojson"): os.remove("filled_holes.geojson") if os.path.exists("corrected_overlap.geojson"): os.remove("corrected_overlap.geojson")