Update utils to do linear transformation of polygons

Brings in https://github.com/alphagov/notifications-utils/pull/889/files

At the moment, we are not doing any transformation of features before
applying geometric algorithms to them. This is, in effect, assuming that
the earth is flat.

This new version of utils implements the transformation of our polygons
to a Cartesian plane. In other words, it converts them from being
defined in spherical degrees to metres.

For the admin app this means we need to convert places where the code
expects things to be measured in degrees to work in metres instead.
This commit is contained in:
Chris Hill-Scott
2021-08-03 18:46:30 +01:00
parent 6b52735dac
commit 6cb326f153
16 changed files with 99 additions and 80 deletions

View File

@@ -7,6 +7,8 @@ from notifications_utils.serialised_model import SerialisedModelCollection
from rtreelib import Rect
from werkzeug.utils import cached_property
from app.formatters import square_metres_to_square_miles
from .populations import CITY_OF_LONDON
from .repo import BroadcastAreasRepository, rtree_index
@@ -55,13 +57,13 @@ class BaseBroadcastArea(ABC):
@cached_property
def simple_polygons_with_bleed(self):
return self.simple_polygons.bleed_by(self.estimated_bleed_in_degrees)
return self.simple_polygons.bleed_by(self.estimated_bleed_in_m)
@cached_property
def phone_density(self):
if not self.polygons.estimated_area:
return 0
return self.count_of_phones / self.polygons.estimated_area
return self.count_of_phones / square_metres_to_square_miles(self.polygons.estimated_area)
@property
def estimated_bleed_in_m(self):
@@ -72,14 +74,10 @@ class BaseBroadcastArea(ABC):
range masts, so the typical bleed will be high (up to 5,000m).
'''
if self.phone_density < 1:
return Polygons.approx_bleed_in_degrees * Polygons.approx_metres_to_degree
return Polygons.approx_bleed_in_m
estimated_bleed = 5_900 - (math.log(self.phone_density, 10) * 1_250)
return max(500, min(estimated_bleed, 5000))
@property
def estimated_bleed_in_degrees(self):
return self.estimated_bleed_in_m / Polygons.approx_metres_to_degree
class BroadcastArea(BaseBroadcastArea, SortableMixin):
@@ -123,7 +121,7 @@ class BroadcastArea(BaseBroadcastArea, SortableMixin):
def count_of_phones(self):
if self.id.endswith(CITY_OF_LONDON.WARDS):
return CITY_OF_LONDON.DAYTIME_POPULATION * (
self.polygons.estimated_area / CITY_OF_LONDON.AREA_SQUARE_MILES
self.polygons.estimated_area / CITY_OF_LONDON.AREA_SQUARE_METRES
)
if self.sub_areas:
return sum(area.count_of_phones for area in self.sub_areas)
@@ -169,7 +167,10 @@ class CustomBroadcastArea(BaseBroadcastArea):
return Polygons(
# Polygons in the DB are stored with the coordinate pair
# order flipped this flips them back again
Polygons(self._polygons).as_coordinate_pairs_lat_long
[
[[lat, long] for long, lat in polygon]
for polygon in self._polygons
]
)
simple_polygons = polygons