Skip to content

Commit a30c8aa

Browse files
authored
Merge pull request #81 from SheffieldSolar/77-library-size-exceeding-pypi-size-limit-of-100mb
77 library size exceeding pypi size limit of 100mb
2 parents fbb6c86 + bf1bbc1 commit a30c8aa

8 files changed

Lines changed: 104 additions & 38 deletions

File tree

MANIFEST.in

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
include geocode/ons/constituency_centroids.psv
22
include geocode/ons/constituency_centroids_Dec2020.psv
33
include geocode/ons/lad_centroids_May2021.psv
4-
include geocode/ons/nrs_2011.zip
5-
include geocode/ons/nrs_2021.zip
4+
include geocode/ons/nrs_2011.7z
5+
include geocode/ons/nrs_2021.7z
66
include geocode/ons/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip
77
include geocode/code_point_open/codepo_gb.zip
14.4 MB
Binary file not shown.

geocode/ons/nrs_2011.zip

-47.8 MB
Binary file not shown.

geocode/ons/PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip renamed to geocode/ons/nrs_2021.7z

24.5 MB
Binary file not shown.

geocode/ons_nrs.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,16 @@
77
"""
88

99
import os
10-
import sys
11-
import zipfile
12-
import json
13-
import csv
10+
import tempfile
1411
import logging
1512
from pathlib import Path
16-
from typing import Literal, Optional, Iterable, Tuple, Union, List, Dict
13+
from typing import Literal, Iterable, Tuple, Union, List, Dict
1714

1815
import pandas as pd
1916
import geopandas as gpd
20-
import shapefile
2117

2218
try:
23-
from shapely.geometry import shape, Point
24-
from shapely.ops import unary_union
19+
from shapely.geometry import shape
2520
except ImportError:
2621
logging.warning(
2722
"Failed to import Shapely library - you will not be able to reverse-geocode! "
@@ -45,13 +40,12 @@ def __init__(self, cache_manager, proxies=None, ssl_verify=True):
4540
"""
4641
self.cache_manager = cache_manager
4742
self.data_dir = SCRIPT_DIR.joinpath("ons")
48-
self.nrs_zipfile = self.data_dir.joinpath("nrs_2011.zip")
4943
self.constituency_lookup_file = self.data_dir.joinpath(
5044
"constituency_centroids_Dec2020.psv"
5145
)
5246
self.lad_lookup_file = self.data_dir.joinpath("lad_centroids_May2021.psv")
53-
self.pc_llsoa_zipfile = self.data_dir.joinpath(
54-
"PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.zip"
47+
self.pc_llsoa_sevenzipfile = self.data_dir.joinpath(
48+
"PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.7z"
5549
)
5650
self.llsoa_lookup = None
5751
self.llsoa_regions = None
@@ -117,36 +111,40 @@ def _load_llsoa_lookup(self):
117111
]
118112
engwales_lookup.reset_index(names="code", inplace=True)
119113

120-
zip_path_2011 = self.data_dir.joinpath("nrs_2011.zip")
121-
zip_path_2021 = self.data_dir.joinpath("nrs_2021.zip")
114+
sevenzip_path_2011 = self.data_dir.joinpath("nrs_2011.7z")
115+
sevenzip_path_2021 = self.data_dir.joinpath("nrs_2021.7z")
122116

123-
OA_2011_centroids = gpd.read_file(
124-
f"zip://{zip_path_2011}!OutputArea2011_PWC_WGS84.csv",
125-
columns=["code", "easting", "northing"],
117+
OA_2011_centroids = utils.read_csv_from_7z(
118+
sevenzip_path_2011,
119+
"OutputArea2011_PWC_WGS84.csv",
120+
usecols=["code", "easting", "northing"],
126121
)
127122
OA_2011_centroids = utils.add_latlon(OA_2011_centroids, "easting", "northing")
128123
scots_lookup_2011 = OA_2011_centroids[["code", "latitude", "longitude"]]
129-
OA_2021_centroids = gpd.read_file(
130-
f"zip://{zip_path_2021}!OutputArea2022_PWC_WGS84.csv",
131-
columns=["code", "easting", "northing"],
124+
OA_2021_centroids = utils.read_csv_from_7z(
125+
sevenzip_path_2021,
126+
"OutputArea2022_PWC_WGS84.csv",
127+
usecols=["code", "easting", "northing"],
132128
)
133129
OA_2021_centroids = utils.add_latlon(OA_2021_centroids, "easting", "northing")
134130
scots_lookup_2021 = OA_2021_centroids[["code", "latitude", "longitude"]]
135131
scots_lookup = pd.concat([scots_lookup_2011, scots_lookup_2021]).reset_index(
136132
drop=True
137133
)
138134

139-
DZ_2011_centroids = gpd.read_file(
140-
f"zip://{zip_path_2011}!SG_DataZone_Cent_2011.csv",
141-
columns=["DataZone", "Easting", "Northing"],
135+
DZ_2011_centroids = utils.read_csv_from_7z(
136+
sevenzip_path_2011,
137+
"SG_DataZone_Cent_2011.csv",
138+
usecols=["DataZone", "Easting", "Northing"],
142139
)
143140
DZ_2011_centroids = utils.add_latlon(DZ_2011_centroids, "Easting", "Northing")
144141
scots_dz_lookup_2011 = DZ_2011_centroids[
145142
["DataZone", "latitude", "longitude"]
146143
].rename(columns={"DataZone": "code"})
147-
DZ_2021_centroids = gpd.read_file(
148-
f"zip://{zip_path_2021}!SG_DataZone_Cent_2022.csv",
149-
columns=["DataZone", "Easting", "Northing"],
144+
DZ_2021_centroids = utils.read_csv_from_7z(
145+
sevenzip_path_2021,
146+
"SG_DataZone_Cent_2022.csv",
147+
usecols=["DataZone", "Easting", "Northing"],
150148
)
151149
DZ_2021_centroids = utils.add_latlon(DZ_2021_centroids, "Easting", "Northing")
152150
scots_dz_lookup_2021 = DZ_2021_centroids[
@@ -204,19 +202,24 @@ def _load_llsoa_boundaries_engwales_regions(self, version: Literal["2011", "2021
204202

205203
def _load_llsoa_boundaries_scots_regions(self, version: Literal["2011", "2021"]):
206204
"""
207-
Load the LLSOA boundaries for Scotland from the NRS zipfile.
205+
Load the LLSOA boundaries for Scotland from the NRS 7z file.
208206
209207
Parameters
210208
----------
211209
`version` : Literal["2011", "2021"]
212210
The version of the LLSOA boundaries to load.
213211
"""
214-
zip_path = self.data_dir.joinpath(f"nrs_{version}.zip")
212+
sevenzip_path = self.data_dir.joinpath(f"nrs_{version}.7z")
215213
llsoa_filename = {
216-
"2011": "OutputArea2011_EoR_WGS84.shp",
217-
"2021": "OutputArea2022_EoR.shp",
214+
"2011": "OutputArea2011_EoR_WGS84.geojson",
215+
"2021": "OutputArea2022_EoR.geojson",
218216
}
219-
gdf = gpd.read_file(f"zip://{zip_path}!{llsoa_filename[version]}")
217+
target_file = llsoa_filename[version]
218+
219+
with tempfile.TemporaryDirectory() as tmpdir:
220+
utils.extract_from_7z(sevenzip_path, target_file, tmpdir)
221+
extracted_file = Path(tmpdir) / target_file
222+
gdf = gpd.read_file(extracted_file)
220223
if version == "2021":
221224
gdf.set_crs("EPSG:27700", inplace=True)
222225
gdf.to_crs("EPSG:4326", inplace=True)
@@ -273,11 +276,9 @@ def _load_datazone_lookup(self, version: Literal["2011", "2021"]):
273276
f"Loading {version} LLSOA<->Datazone lookup from cache {cache_label}"
274277
)
275278
return datazone_lookup_cache_contents
276-
zip_path = self.data_dir.joinpath(f"nrs_{version}.zip")
279+
sevenzip_path = self.data_dir.joinpath(f"nrs_{version}.7z")
277280
dz_lookup_filename = {"2011": "OA_DZ_IZ_2011.csv", "2021": "OA22_DZ22_IZ22.csv"}
278-
with zipfile.ZipFile(zip_path, "r") as nrs_zip:
279-
with nrs_zip.open(dz_lookup_filename[version], "r") as fid:
280-
dz_lookup = pd.read_csv(fid)
281+
dz_lookup = utils.read_csv_from_7z(sevenzip_path, dz_lookup_filename[version])
281282
if version == "2011":
282283
dz_lookup.set_index("OutputArea2011Code", inplace=True)
283284
dz_lookup.drop(columns=["IntermediateZone2011Code"], inplace=True)
@@ -522,7 +523,11 @@ def _load_postcode_llsoa_lookup(self):
522523
"Loading postcode<->LLSOA lookup from cache ('%s')", "pc_llsoa_lookup"
523524
)
524525
return postcode_llsoa_lookup_cache_contents
525-
pc_llsoa_lookup = pd.read_csv(self.pc_llsoa_zipfile, dtype=str)
526+
pc_llsoa_lookup = utils.read_csv_from_7z(
527+
self.pc_llsoa_sevenzipfile,
528+
"PCD_OA_LSOA_MSOA_LAD_MAY22_UK_LU.csv",
529+
dtype=str,
530+
)
526531
pc_llsoa_lookup["postcode"] = (
527532
pc_llsoa_lookup.pcds.str.strip().str.upper().str.replace(" ", "")
528533
)

geocode/utilities.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
- First Authored: 2022-10-19
77
"""
88

9+
import io
910
import sys
1011
import logging
1112
import requests
1213
import json
14+
from pathlib import Path
1315
from typing import Optional, Iterable, Tuple, Union, List, Dict
1416

1517
import geopandas as gpd
1618
import pandas as pd
1719
import pyproj
20+
import py7zr
1821

1922

2023
class GenericException(Exception):
@@ -423,3 +426,60 @@ def add_latlon(
423426
df[lat_col] = df.geometry.y
424427
df[lon_col] = df.geometry.x
425428
return df
429+
430+
431+
def extract_from_7z(archive_path: Path, file_to_extract: str, tmp_dir: str):
432+
"""
433+
Extract a file from a .7z archive.
434+
435+
Parameters
436+
----------
437+
`archive_path` : Path
438+
Path to the .7z archive.
439+
`file_to_extract` : str
440+
The filename to extract (e.g. "OutputArea2011_EoR_WGS84.geojson").
441+
`tmp_dir` : str
442+
Path to the temporary directory to extract into.
443+
"""
444+
with py7zr.SevenZipFile(str(archive_path), mode="r") as archive:
445+
archive.extract(path=tmp_dir, targets=[file_to_extract])
446+
447+
448+
def read_csv_from_7z(archive_path: Path, file_to_read: str, **kwargs):
449+
"""
450+
Extract a CSV file from a .7z archive directly into memory.
451+
452+
Parameters
453+
----------
454+
`archive_path` : Path
455+
Path to the .7z archive.
456+
`file_to_read` : str
457+
The CSV filename to extract.
458+
`**kwargs`
459+
Additional keyword arguments passed to `pd.read_csv`.
460+
461+
Returns
462+
-------
463+
`pd.DataFrame`
464+
The extracted CSV file as a DataFrame.
465+
"""
466+
467+
class InMemoryFactory:
468+
"""
469+
A factory for py7zr to extract files into memory rather than to disk.
470+
"""
471+
472+
def __init__(self):
473+
self.files = {}
474+
475+
def create(self, filename):
476+
buffer = io.BytesIO()
477+
self.files[filename] = buffer
478+
return buffer
479+
480+
factory = InMemoryFactory()
481+
482+
with py7zr.SevenZipFile(str(archive_path), mode="r") as archive:
483+
archive.extract(targets=[file_to_read], factory=factory)
484+
factory.files[file_to_read].seek(0)
485+
return pd.read_csv(factory.files[file_to_read], **kwargs)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ fiona
99
rtree
1010
shapely>=1.7.0
1111
pyshp
12-
geopandas
12+
geopandas
13+
py7zr

0 commit comments

Comments
 (0)