Make simplification of polygons more sophisticated

Simplifying polygons means reducing the number of points used to render
them. This commit implements simplification such that, for any given
input polygons, the combined point count of the simplified polygons is
less than 100.

When simplifying the polygons we are trying to get the smallest number
of points while meeting these two rules:
1. No part of the area the user has chosen can be cut off
2. The area of the simplified polygon should be as small as possible

This commit introduces two techniques we weren’t using before:
1. Dilating and eroding the area to fill in concave details of the
   shape, like inlets and harbours[1]
2. Making the simplification threshold proportionate to the perimeter of
   all polygons, so bigger and crinklier polygons get more
   simplification applied

It also shows the estimated bleed as a separate polygon. This lets us
make it bigger (so it’s more closer the the approximate bleed) without
having to send a bigger area to the CBC and compounding the amount of
actual bleed.

1. Inspired by this blog post about ‘removing the crinkley bits’ from
   Vancouver Island:
   http://blog.cleverelephant.ca/2010/11/removing-complexities.html
This commit is contained in:
Chris Hill-Scott
2020-08-24 12:51:23 +01:00
parent 19ce1bd43a
commit 3470c5bb31
3 changed files with 164 additions and 28 deletions

View File

@@ -2,11 +2,10 @@ from datetime import datetime, timedelta
from notifications_utils.template import BroadcastPreviewTemplate
from orderedset import OrderedSet
from shapely.geometry import MultiPolygon, Polygon
from shapely.ops import unary_union
from werkzeug.utils import cached_property
from app.broadcast_areas import broadcast_area_libraries
from app.broadcast_areas.polygons import Polygons
from app.models import JSONModel, ModelList
from app.models.user import User
from app.notify_client.broadcast_message_api_client import (
@@ -72,31 +71,14 @@ class BroadcastMessage(JSONModel):
area.name for area in self.areas
][:10]
@property
@cached_property
def polygons(self):
return broadcast_area_libraries.get_polygons_for_areas_lat_long(
*self._dict['areas']
return Polygons(
broadcast_area_libraries.get_polygons_for_areas_lat_long(
*self._dict['areas']
)
)
@property
def simple_polygons(self):
simple_polygons = broadcast_area_libraries.get_simple_polygons_for_areas_lat_long(
*self._dict['areas']
)
unioned_polygons = unary_union([
Polygon(i) for i in simple_polygons
])
if isinstance(unioned_polygons, MultiPolygon):
return [
[
[x, y] for x, y in p.exterior.coords
]
for p in unioned_polygons
]
return [[
[x, y] for x, y in unioned_polygons.exterior.coords
]]
@property
def template(self):
response = service_api_client.get_service_template(