Skip to content

Commit

Permalink
simplify union function
Browse files Browse the repository at this point in the history
  • Loading branch information
lastminutediorama committed Aug 22, 2024
1 parent f8d7677 commit f5ce019
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 42 deletions.
120 changes: 120 additions & 0 deletions src/planscape/planning/fixtures/palmsprings_sanbernardino.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"bbox": [
-116.59000730822105,
33.76094007811721,
-116.46372359000742,
33.86288159982813
],
"type": "Polygon",
"coordinates": [
[
[
-116.5301572048023,
33.86139064872427
],
[
-116.53075570583647,
33.85840866841697
],
[
-116.53195270790485,
33.86288159982813
],
[
-116.52955870376809,
33.86089365923777
],
[
-116.46551909311,
33.83454907768415
],
[
-116.46372359000742,
33.792281376404524
],
[
-116.52058118825529,
33.76094007811721
],
[
-116.58521929994758,
33.776860900708314
],
[
-116.59000730822105,
33.82709158924555
],
[
-116.57624178443476,
33.84449138356989
],
[
-116.5301572048023,
33.86139064872427
]
]
]
},
"properties": {
"id": null
}
},
{
"type": "Feature",
"geometry": {
"bbox": [
-117.36393144521908,
34.03950544279183,
-117.19620895578856,
34.14367901861189
],
"type": "Polygon",
"coordinates": [
[
[
-117.30765613626542,
34.14367901861189
],
[
-117.28117363793427,
34.14093925150775
],
[
-117.22048457925877,
34.13363277157728
],
[
-117.19620895578856,
34.08886680080183
],
[
-117.22158801668924,
34.03950544279183
],
[
-117.34517300890116,
34.07058816903606
],
[
-117.36393144521908,
34.10074579569706
],
[
-117.30765613626542,
34.14367901861189
]
]
]
},
"properties": {
"id": null
}
}
],
"fileName": "palmsprings_sanbernardino/palm_springs_san_bernardino"
}
2 changes: 1 addition & 1 deletion src/planscape/planning/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
User,
UserPrefs,
)
from planning.services import get_acreage
from planscape.exceptions import InvalidGeometry
from stands.models import StandSizeChoices

Expand Down Expand Up @@ -351,7 +352,6 @@ class Meta:
"uuid",
"scenario",
"name",
"origin",
"data",
"geometry",
"created_by",
Expand Down
34 changes: 24 additions & 10 deletions src/planscape/planning/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path
from typing import Any, Dict, Tuple, Type, Union
from django.conf import settings
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon
from django.db import transaction
from django.utils.timezone import now
from fiona.crs import from_epsg
Expand All @@ -26,7 +26,6 @@
ScenarioResultStatus,
ScenarioStatus,
)
from planning.serializers import ProjectAreaSerializer
from planning.tasks import async_forsys_run
from planscape.exceptions import InvalidGeometry
from stands.models import StandSizeChoices, area_from_size
Expand Down Expand Up @@ -116,6 +115,27 @@ def create_scenario(user: UserType, **kwargs) -> Scenario:
return scenario


def union_geojson(uploaded_geojson):
geometries = []
if "features" in uploaded_geojson:
for feature in uploaded_geojson["features"]:
try:
geom = GEOSGeometry(json.dumps(feature["geometry"]), srid=4326)
if isinstance(geom, (Polygon, MultiPolygon)):
geometries.append(geom)
except Exception as e:
print(f"Error processing feature: {e}")
else:
geometries.append(GEOSGeometry(json.dumps(uploaded_geojson), srid=4326))
if not geometries:
raise ValueError("No valid polygon geometries found")
unioned_geometry = geometries[0]
for geom in geometries[1:]:
unioned_geometry = unioned_geometry.union(geom)

return unioned_geometry


def feature_to_project_area(idx: int, user_id: int, scenario, feature):
try:
project_area = {
Expand Down Expand Up @@ -154,31 +174,25 @@ def create_scenario_from_upload(
target=scenario.planning_area,
)

# handle just a polygon
if "type" in uploaded_geom and uploaded_geom["type"] == "Polygon":
new_feature = feature_to_project_area(
1, scenario.user, scenario, json.dumps(uploaded_geom)
)
uploaded_geom.setdefault("properties", {})
uploaded_geom["properties"]["project_id"] = new_feature.pk

# # this handles a format provided by shpjs when a shapefile has multiple features
# if "geometry" in uploaded_geom and "coordinates" in uploaded_geom["geometry"]:
# for idx, f in enumerate(uploaded_geom["geometry"]["coordinates"], 1):
# feature_obj = {"type": "Polygon", "coordinates": [f]}
# feature_to_project_area(idx, scenario.user, scenario, feature_obj)

# this handles a more standard FeatureCollection
if "features" in uploaded_geom:
for idx, f in enumerate(uploaded_geom["features"], 1):
print(f"do we have...mutliple feature? {f}")
new_feature = feature_to_project_area(
idx, scenario.user, scenario, json.dumps(f["geometry"])
)
# TODO: add new feature details back to uploaded_geom
f.setdefault("properties", {})
f["properties"]["project_id"] = new_feature.pk

# Store updated in ScenarioResult.result
# Store geometry with added properties into ScenarioResult.result
ScenarioResult.objects.create(scenario=scenario, result=uploaded_geom)

return scenario
Expand Down
1 change: 0 additions & 1 deletion src/planscape/planning/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import os
from base.region_name import display_name_to_region
from django.conf import settings
from planning.geometry import coerce_geometry
from django.db import transaction
from django.db import IntegrityError
from django.db.models import Count, Max
Expand Down
44 changes: 14 additions & 30 deletions src/planscape/planning/views_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
ScenarioSerializer,
ListCreatorSerializer,
UploadedScenarioSerializer,
UploadedFeatureCollectionSerializer,
)
from planning.services import (
create_planning_area,
Expand All @@ -35,6 +36,7 @@
delete_scenario,
toggle_scenario_status,
create_scenario_from_upload,
union_geojson,
)

User = get_user_model()
Expand Down Expand Up @@ -174,39 +176,21 @@ def toggle_status(self, request, pk=None):
@action(methods=["POST"], detail=False)
def upload_shapefiles(self, request, pk=None, *args, **kwargs):
stand_size = request.data["stand_size"]

# TODO: cleanup dict vs string here
uploaded_geom = request.data["geometry"]
if isinstance(uploaded_geom, str):
uploaded_geojson = json.loads(uploaded_geom)
else:
uploaded_geojson = uploaded_geom
uploaded_geos = None

# TODO: refactor this to a service
if "features" in uploaded_geojson:
geometries = [
GEOSGeometry(json.dumps(feature["geometry"]))
for feature in uploaded_geojson["features"]
]
# Combine all polygons into a single polygon or multipolygon
combined_geometry = geometries[0]
for geom in geometries[1:]:
combined_geometry = combined_geometry.union(geom)
uploaded_geos = GEOSGeometry(combined_geometry, srid=4326)
else:
uploaded_geos = GEOSGeometry(uploaded_geom, srid=4326)

scenario_name = request.data["name"]
planning_area_pk = request.data["planning_area"]
uploaded_geom = request.data["geometry"]

pa = PlanningArea.objects.get(pk=planning_area_pk)

# TODO: validate uploaded geom w serializer

if uploaded_geos.geom_type == "MultiPolygon":
uploaded_geos = uploaded_geos.union(uploaded_geos)
# ensure we have a geojson obj
if isinstance(uploaded_geom, str):
try:
uploaded_geom = json.loads(uploaded_geom)
except json.JSONDecodeError:
raise ValueError("Invalid JSON string")

# Union features and confirm that they're inside the planning area
uploaded_geos = union_geojson(uploaded_geom)
pa = PlanningArea.objects.get(pk=planning_area_pk)
# TODO: check if it's inside pa.geometry
if not pa.geometry.contains(uploaded_geos):
return Response(
{
Expand All @@ -229,7 +213,7 @@ def upload_shapefiles(self, request, pk=None, *args, **kwargs):
# now we create a scenario
new_scenario = create_scenario_from_upload(
scenario_data=scenario_serializer.validated_data,
uploaded_geom=uploaded_geojson,
uploaded_geom=uploaded_geom,
)

out_serializer = ScenarioProjectAreasSerializer(instance=new_scenario)
Expand Down

0 comments on commit f5ce019

Please sign in to comment.