Files
notifications-admin/app/broadcast_areas/repo.py
Chris Hill-Scott 6a3aba3bc5 Store CRS to avoid costly lookup
Looking up the coordinate reference system for a given polygon’s bounds
is surprisingly expensive.

We can make things run faster by precomputing it at the time of
generating the simplified polygons, then storing it in the SQLite
database alongside the polygon data.
2021-12-01 14:10:55 +00:00

254 lines
7.9 KiB
Python

import json
import os
import pickle
import sqlite3
from pathlib import Path
rtree_index_path = Path(__file__).parent / 'rtree.pickle'
rtree_index = pickle.loads(rtree_index_path.read_bytes())
class BroadcastAreasRepository(object):
def __init__(self):
self.database = Path(__file__).resolve().parent / 'broadcast-areas.sqlite3'
def conn(self):
return sqlite3.connect(str(self.database))
def delete_db(self):
os.remove(str(self.database))
def create_tables(self):
with self.conn() as conn:
conn.execute("""
CREATE TABLE broadcast_area_libraries (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
name_singular TEXT NOT NULL,
is_group BOOLEAN NOT NULL
)""")
conn.execute("""
CREATE TABLE broadcast_area_library_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
broadcast_area_library_id TEXT NOT NULL
)""")
conn.execute("""
CREATE TABLE broadcast_areas (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
broadcast_area_library_id TEXT NOT NULL,
broadcast_area_library_group_id TEXT,
count_of_phones INTEGER,
FOREIGN KEY (broadcast_area_library_id)
REFERENCES broadcast_area_libraries(id),
FOREIGN KEY (broadcast_area_library_group_id)
REFERENCES broadcast_area_library_groups(id)
)""")
conn.execute("""
CREATE TABLE broadcast_area_polygons (
id TEXT PRIMARY KEY,
polygons TEXT NOT NULL,
simple_polygons TEXT NOT NULL,
utm_crs TEXT NOT NULL
)""")
conn.execute("""
CREATE INDEX broadcast_areas_broadcast_area_library_id
ON broadcast_areas (broadcast_area_library_id);
""")
conn.execute("""
CREATE INDEX broadcast_areas_broadcast_area_library_group_id
ON broadcast_areas (broadcast_area_library_group_id);
""")
def delete_library_data(self):
# delete everything except broadcast_area_polygons
with self.conn() as conn:
conn.execute('DELETE FROM broadcast_area_libraries;')
conn.execute('DELETE FROM broadcast_area_library_groups;')
conn.execute('DELETE FROM broadcast_areas;')
def insert_broadcast_area_library(self, id, *, name, name_singular, is_group):
q = """
INSERT INTO broadcast_area_libraries (id, name, name_singular, is_group)
VALUES (?, ?, ?, ?)
"""
with self.conn() as conn:
conn.execute(q, (id, name, name_singular, is_group))
def insert_broadcast_areas(self, areas, keep_old_features):
areas_q = """
INSERT INTO broadcast_areas (
id, name,
broadcast_area_library_id, broadcast_area_library_group_id,
count_of_phones
)
VALUES (?, ?, ?, ?, ?)
"""
features_q = """
INSERT INTO broadcast_area_polygons (
id,
polygons, simple_polygons, utm_crs
)
VALUES (?, ?, ?, ?)
"""
with self.conn() as conn:
for id, name, area_id, group, polygons, simple_polygons, utm_crs, count_of_phones in areas:
conn.execute(areas_q, (
id, name, area_id, group, count_of_phones
))
if not keep_old_features:
conn.execute(features_q, (
id, json.dumps(polygons), json.dumps(simple_polygons), utm_crs
))
def query(self, sql, *args):
with self.conn() as conn:
cursor = conn.cursor()
cursor.execute(sql, (*args,))
return cursor.fetchall()
def get_libraries(self):
q = "SELECT id, name, name_singular, is_group FROM broadcast_area_libraries"
results = self.query(q)
libraries = [(row[0], row[1], row[2], row[3]) for row in results]
return sorted(libraries)
def get_areas(self, area_ids):
q = """
SELECT id, name, count_of_phones, broadcast_area_library_id
FROM broadcast_areas
WHERE id IN ({})
""".format(",".join("?" * len(area_ids)))
results = self.query(q, *area_ids)
areas = [
(row[0], row[1], row[2], row[3])
for row in results
]
return areas
def get_areas_with_simple_polygons(self, area_ids):
q = """
SELECT broadcast_areas.id, name, count_of_phones, broadcast_area_library_id, simple_polygons, utm_crs
FROM broadcast_areas
JOIN broadcast_area_polygons on broadcast_area_polygons.id = broadcast_areas.id
WHERE broadcast_areas.id IN ({})
""".format(",".join("?" * len(area_ids)))
results = self.query(q, *area_ids)
areas = [
(row[0], row[1], row[2], row[3], json.loads(row[4]), row[5])
for row in results
]
return areas
def get_all_areas_for_library(self, library_id):
is_multi_tier_library = self.query("""
SELECT exists(
SELECT 1
FROM broadcast_areas
WHERE broadcast_area_library_id = ? AND
broadcast_area_library_group_id IS NOT NULL
)
""", library_id)[0][0]
if is_multi_tier_library:
# only interested in areas with children - eg local authorities, counties, unitary authorities. not wards.
q = """
SELECT id, name, count_of_phones, broadcast_area_library_id
FROM broadcast_areas
JOIN (
SELECT DISTINCT broadcast_area_library_group_id
FROM broadcast_areas
WHERE broadcast_area_library_group_id IS NOT NULL
) AS parent_broadcast_areas ON parent_broadcast_areas.broadcast_area_library_group_id = broadcast_areas.id
WHERE broadcast_area_library_id = ?
"""
else:
# Countries don't have any children, so the above query wouldn't return anything.
q = """
SELECT id, name, count_of_phones, broadcast_area_library_id
FROM broadcast_areas
WHERE broadcast_area_library_id = ?
"""
results = self.query(q, library_id)
return [
(row[0], row[1], row[2], row[3])
for row in results
]
def get_all_areas_for_group(self, group_id):
q = """
SELECT id, name, count_of_phones, broadcast_area_library_id
FROM broadcast_areas
WHERE broadcast_area_library_group_id = ?
"""
results = self.query(q, group_id)
areas = [
(row[0], row[1], row[2], row[3])
for row in results
]
return areas
def get_parent_for_area(self, area_id):
q = """
SELECT id, name, count_of_phones, broadcast_area_library_id
FROM broadcast_areas
WHERE id IN (
SELECT broadcast_area_library_group_id
FROM broadcast_areas
WHERE id = ?
)
"""
results = self.query(q, area_id)
if not results:
return None
return (results[0][0], results[0][1], results[0][2], results[0][3])
def get_polygons_for_area(self, area_id):
q = """
SELECT polygons, utm_crs
FROM broadcast_area_polygons
WHERE id = ?
"""
results = self.query(q, area_id)
return json.loads(results[0][0]), results[0][1]
def get_simple_polygons_for_area(self, area_id):
q = """
SELECT simple_polygons, utm_crs
FROM broadcast_area_polygons
WHERE id = ?
"""
results = self.query(q, area_id)
return json.loads(results[0][0]), results[0][1]