From 738ac1d81876d35d67dc9885c11545bb6a7495f3 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Fri, 12 Mar 2021 09:17:42 +0000 Subject: [PATCH] Vary bleed amount based on population density MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are basically two kinds of 4G masts: Frequency | Range | Bandwidth ----------|-------------|---------------------------------- 800MHz | Long (500m) | Low (can handle a bit of traffic) 1800Mhz | Short (5km) | High (can handle lots of traffic) The 1800Mhz masts are better in terms of how much traffic they can handle and how fast a connection they provide. But because they have quite short range, it’s only economical to install them in very built up areas†. In more rural areas the 800MHz masts are better because they cover a wider area, and have enough bandwidth for the lower population density. The net effect of this is that cell broadcasts in rural areas are likely to bleed further, because the masts they are being broadcast from are less precise. We can use population density as a proxy for how likely it is to be covered by 1800Mhz masts, and therefore how much bleed we should expect. So this commit varies the amount of bleed shown based on the population density. I came up with the formula based on 3 fixed points: - The most remote areas (for example the Scottish Highlands) should have the highest average bleed, estimated at 5km - An town, like Crewe, should have about the same bleed as we were estimating before (1.5km) – Pete D thinks this is about right based on his knowledge of the area around his office in Crewe - The most built up areas, like London boroughs, could have as little as 500m of bleed Based on these three figures I came up with the following formula, which roughly gives the right bleed distance (`b`) for each of their population densities (`d`): ``` b = 5900 - (log10(d) × 1_250) ``` Plotted on a curve it looks like this: This is based on averages – remember that the UI shows where is _likely_ to receive the alert, based on bleed, not where it’s _possible_ to receive the alert. Here’s what it looks like on the map: --- †There are some additional subtleties which make this not strictly true: - The 800Mhz masts are also used in built up areas to fill in the gaps between the areas covered by the 1800Mhz masts - Switching between masts is inefficient, so if you’re moving fast through a built up area (for example on a train) your phone will only use the 800MHz masts so that you have to handoff from one mast to another less often --- app/broadcast_areas/__init__.py | 27 +++++++ app/models/broadcast_message.py | 13 +++- .../views/broadcast/macros/area-map.html | 2 +- .../partials/area-map-javascripts.html | 2 +- requirements.in | 2 +- requirements.txt | 2 +- .../broadcast_areas/test_broadcast_area.py | 74 +++++++++++++++++++ tests/app/main/views/test_broadcast.py | 17 ++++- 8 files changed, 130 insertions(+), 9 deletions(-) diff --git a/app/broadcast_areas/__init__.py b/app/broadcast_areas/__init__.py index 5e2df3292..c9e40ec89 100644 --- a/app/broadcast_areas/__init__.py +++ b/app/broadcast_areas/__init__.py @@ -1,3 +1,5 @@ +import math + from notifications_utils.formatters import formatted_list from notifications_utils.polygons import Polygons from notifications_utils.serialised_model import SerialisedModelCollection @@ -49,6 +51,10 @@ class BroadcastArea(SortableMixin): BroadcastAreasRepository().get_simple_polygons_for_area(self.id) ) + @cached_property + def simple_polygons_with_bleed(self): + return self.simple_polygons.bleed_by(self.estimated_bleed_in_degrees) + @cached_property def sub_areas(self): return [ @@ -68,6 +74,20 @@ class BroadcastArea(SortableMixin): # https://www.pivotaltracker.com/story/show/174837293 return self._count_of_phones or 0 + @property + def phone_density(self): + return self.count_of_phones / self.polygons.estimated_area + + @property + def estimated_bleed_in_m(self): + if self.id.endswith(CITY_OF_LONDON.WARDS): + return 500 + return 5_900 - (math.log(self.phone_density, 10) * 1_250) + + @property + def estimated_bleed_in_degrees(self): + return self.estimated_bleed_in_m / Polygons.approx_metres_to_degree + @cached_property def parents(self): return list(filter(None, self._parents_iterator)) @@ -109,6 +129,13 @@ class CustomBroadcastArea: simple_polygons = polygons + @cached_property + def simple_polygons_with_bleed(self): + # We don’t yet have a way of working out the population density + # of a custom area, so for now we have to use an average number + # to estimate the amount of bleed + return self.simple_polygons.bleed_by(Polygons.approx_bleed_in_degrees) + class CustomBroadcastAreas(SerialisedModelCollection): model = CustomBroadcastArea diff --git a/app/models/broadcast_message.py b/app/models/broadcast_message.py index 763fc1e3c..e59e5f791 100644 --- a/app/models/broadcast_message.py +++ b/app/models/broadcast_message.py @@ -117,6 +117,17 @@ class BroadcastMessage(JSONModel): def simple_polygons(self): return self.get_simple_polygons(areas=self.areas) + @cached_property + def simple_polygons_with_bleed(self): + polygons = Polygons( + list(itertools.chain(*( + area.simple_polygons_with_bleed for area in self.areas + ))) + ) + # If we’ve added multiple areas then we need to re-simplify the + # combined shapes to keep the point count down + return polygons.smooth.simplify if len(self.areas) > 1 else polygons + @property def reference(self): if self.template_id: @@ -163,7 +174,7 @@ class BroadcastMessage(JSONModel): @property def count_of_phones_likely(self): area_estimate = self.simple_polygons.estimated_area - bleed_area_estimate = self.simple_polygons.bleed.estimated_area - area_estimate + bleed_area_estimate = self.simple_polygons_with_bleed.estimated_area - area_estimate return round_to_significant_figures( self.count_of_phones + (self.count_of_phones * bleed_area_estimate / area_estimate), 1 diff --git a/app/templates/views/broadcast/macros/area-map.html b/app/templates/views/broadcast/macros/area-map.html index 737cd24c0..04645eb2a 100644 --- a/app/templates/views/broadcast/macros/area-map.html +++ b/app/templates/views/broadcast/macros/area-map.html @@ -11,7 +11,7 @@
  • - An extra area of {{ "{:,.1f}".format(broadcast_message.simple_polygons.bleed.estimated_area - broadcast_message.simple_polygons.estimated_area) }} square miles is + An extra area of {{ "{:,.1f}".format(broadcast_message.simple_polygons_with_bleed.estimated_area - broadcast_message.simple_polygons.estimated_area) }} square miles is Likely to get diff --git a/app/templates/views/broadcast/partials/area-map-javascripts.html b/app/templates/views/broadcast/partials/area-map-javascripts.html index 871145332..5a401b8fd 100644 --- a/app/templates/views/broadcast/partials/area-map-javascripts.html +++ b/app/templates/views/broadcast/partials/area-map-javascripts.html @@ -2,7 +2,7 @@