|
| 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) |
0 commit comments