Skip to content

Commit 9125193

Browse files
committed
[UK] May 2025 Boundary-Line import.
And NIE update script from 2024 general election.
1 parent e1a11b6 commit 9125193

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

mapit_gb/controls/2025-05.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# A control file for importing Boundary-Line.
2+
# CEDs (county council electoral divisions) don't have ONS codes, so we have to
3+
# have something manual as this is a year of county council boundary changes.
4+
#
5+
# OS release notes are at https://docs.os.uk/os-downloads/addressing-and-location/boundary-line/release-notes/may-2025
6+
#
7+
# The following English counties have had full boundary changes:
8+
# Derbyshire - https://www.legislation.gov.uk/uksi/2024/1189/contents/made
9+
# Gloucestershire - https://www.legislation.gov.uk/uksi/2025/34/contents/made
10+
# Oxfordshire - https://www.legislation.gov.uk/uksi/2025/33/contents/made
11+
# Staffordshire - https://www.legislation.gov.uk/uksi/2024/1184/contents/made
12+
# Worcestershire - https://www.legislation.gov.uk/uksi/2024/1176/contents/made
13+
#
14+
# Unitaries with boundary changes are the following, but we spot them
15+
# automatically by GSS code: Buckinghamshire, Durham, North Northamptonshire,
16+
# Northumberland, Shropshire, West Northamptonshire
17+
18+
from mapit.models import Area, Generation, CodeType
19+
20+
COUNTIES_CHANGED = ["%s County Council" % c for c in [
21+
'Derbyshire', 'Gloucestershire', 'Oxfordshire', 'Staffordshire', 'Worcestershire'
22+
]]
23+
COUNTIES_NOT_CHANGED = ["%s County Council" % c for c in [
24+
'Cambridgeshire', 'Devon', 'Dorset', 'East Sussex', 'Essex', 'Hampshire',
25+
'Hertfordshire', 'Kent', 'Lancashire', 'Leicestershire', 'Lincolnshire',
26+
"Norfolk", "Northamptonshire", "North Yorkshire", 'Nottinghamshire',
27+
"Somerset", "Suffolk", "Surrey", 'Warwickshire', 'West Sussex'
28+
]]
29+
30+
31+
def code_version():
32+
return 'gss'
33+
34+
35+
def check(name, type, country, geometry, ons_code, commit, **args):
36+
"""Should return True if this area is NEW, False if we should match against
37+
an ONS/unit_id code, or an Area to be used as an override instead."""
38+
39+
current = Generation.objects.current()
40+
if not current: # Fresh import
41+
return False
42+
43+
# The boundary between Maidstone Rural West and Maidstone Central is
44+
# incorrect (the release notes say they did not get the fixed amendment in
45+
# in time). There was a change, but a much smaller one than what is
46+
# incorrectly included, so better to keep the old boundaries, as less wrong
47+
if type == 'CED' and name in ('Maidstone Rural West ED', 'Maidstone Central ED'):
48+
return 'SKIP'
49+
50+
# Another repeat of 2012-05/2013-10/2018-05/2019-05 comments; still nothing
51+
# has changed, I'm doing the dance the same as last time.
52+
53+
# There is a problem, in that users of this service, including ourselves,
54+
# have assumed a mapit ID is an identifier for a concept, rather than a
55+
# particular boundary. Until now, this has not really been an issue, but
56+
# this edition of Boundary-Line changes the boundary of two councils,
57+
# assigning them new ONS IDs in the process. This would cause some amount
58+
# of pain for code that assumes e.g. ID 2579 is the concept of Glasgow City
59+
# Council, rather than the boundary of Glasgow City Council from
60+
# generations 1 to 16 (but then not 17).
61+
#
62+
# Therefore, for the time being, we have decided to match up the new
63+
# boundaries with the current mapit IDs for these four councils.
64+
#
65+
# In the future, perhaps the current IDs should host the concept, for
66+
# continuity, and the current and historical boundaries are then available
67+
# under it in some way. Tricky.
68+
69+
if type == 'MTD' and name in ('Barnsley District (B)', 'Sheffield District (B)'):
70+
# 1. Create a deep copy of the existing area with a new ID
71+
area = Area.objects.get(
72+
names__name=name, names__type__code='O', generation_low__lte=current, generation_high__gte=current)
73+
area_id = area.id
74+
area_polygons = list(area.polygons.all())
75+
area_codes = list(area.codes.all())
76+
area_names = list(area.names.all())
77+
area.pk = None
78+
if commit:
79+
area.save()
80+
for data in [area_polygons, area_codes, area_names]:
81+
for thing in data:
82+
thing.pk = None
83+
thing.area = area
84+
thing.save()
85+
86+
# 2. Update the existing area with the new boundary/generation IDs
87+
area = Area.objects.get(id=area_id)
88+
new_generation = Generation.objects.new()
89+
area.generation_low = new_generation
90+
if commit:
91+
area.save()
92+
# Delete the old ONS code because that only applies to the old boundary
93+
area.codes.filter(type=CodeType.objects.get(code='ons')).delete()
94+
# Update the GSS code because it's what the main script will then use, oddly.
95+
area.codes.update_or_create(type=CodeType.objects.get(code='gss'), defaults={'code': ons_code})
96+
97+
return area
98+
99+
# Some UTAs have had boundary changes, but have ONS codes and so can be
100+
# ignored/ detected that way.
101+
if type != 'CED':
102+
return False
103+
104+
# Make sure CEDs are loaded *after* CTY
105+
area_within = Area.objects.filter(type__code='CTY', polygons__polygon__contains=geometry.geos.point_on_surface)[0]
106+
if area_within.name in COUNTIES_CHANGED:
107+
return True
108+
elif area_within.name in COUNTIES_NOT_CHANGED:
109+
return False
110+
raise Exception("Bad county name given: %s" % area_within.name)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This script is to be run as a one-off after the 2024 UK General Election
2+
# to update the Northern Ireland Assembly boundaries to match the UK
3+
# constituencies, as per section 33 of the Northern Ireland Act 1998.
4+
5+
from django.core.management.base import BaseCommand
6+
from mapit.models import Area, Generation, Type
7+
8+
9+
def save(obj, options, space=''):
10+
obj.pk = None
11+
if options['commit']:
12+
obj.save()
13+
print(space, obj)
14+
15+
16+
class Command(BaseCommand):
17+
help = 'Update the NI Assembly constituencies after the 2024 election'
18+
19+
def add_arguments(self, parser):
20+
parser.add_argument('--commit', action='store_true', dest='commit', help='Actually update the database')
21+
22+
def handle(self, **options):
23+
areas = Area.objects.filter(type__code='WMC', country__code='N', generation__high=Generation.objects.current())
24+
nie_type = Type.objects.get(code='NIE')
25+
generation = Generation.objects.new()
26+
for area in areas:
27+
polygons = list(area.polygons.all())
28+
names = list(area.names.all())
29+
codes = list(area.codes.all())
30+
area.type = nie_type
31+
area.generation_low_id = generation
32+
area.generation_high_id = generation
33+
save(area, options)
34+
for data in [polygons, names, codes]:
35+
for obj in data:
36+
obj.area = area
37+
save(obj, options, ' ')

mapit_gb/management/commands/mapit_UK_import_boundary_line.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ def handle_label(self, filename, **options):
6363
ons_code = patch['ons-code']
6464
elif 'unit-id' in patch:
6565
unit_id = patch['unit-id']
66+
elif 'name' in patch:
67+
name = patch['name']
6668

6769
if area_code == 'NCP':
6870
continue # Ignore Non Parished Areas
@@ -220,4 +222,8 @@ def patch_boundary_line(self, name, ons_code, unit_id, area_code):
220222
if area_code == 'DIW' and name == 'Loughton Fairmead Ward' and ons_code == 'E05015731':
221223
return {'ons-code': 'E05015730'}
222224

225+
# Tewkesbury is *not* renaming, May 2025 has renamed it
226+
if name == 'North Gloucestershire District (B)' and ons_code == 'E07000083':
227+
return {'name': 'Tewkesbury District (B)'}
228+
223229
return {}

0 commit comments

Comments
 (0)