Skip to content

Commit

Permalink
ENH: Use shapefile crs attribute when possible
Browse files Browse the repository at this point in the history
When reading in the shapefile, use the .prj file information
if it is present to set a crs attribute on the reader which
can be used down the line by features to get the correct
Projection.
  • Loading branch information
greglucas committed Dec 24, 2023
1 parent 879f84f commit 74f1a77
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
10 changes: 8 additions & 2 deletions lib/cartopy/feature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ def geometries(self):
path = shapereader.natural_earth(resolution=self.scale,
category=self.category,
name=self.name)
geometries = tuple(shapereader.Reader(path).geometries())
reader = shapereader.Reader(path)
if reader.crs is not None:
self._crs = reader.crs
geometries = tuple(reader.geometries())
_NATURAL_EARTH_GEOM_CACHE[key] = geometries
else:
geometries = _NATURAL_EARTH_GEOM_CACHE[key]
Expand Down Expand Up @@ -418,7 +421,10 @@ def intersecting_geometries(self, extent):
# Load GSHHS geometries from appropriate shape file.
# TODO selective load based on bbox of each geom in file.
path = shapereader.gshhs(scale, level)
geoms = tuple(shapereader.Reader(path).geometries())
reader = shapereader.Reader(path)
if reader.crs is not None:
self._crs = reader.crs
geoms = tuple(reader.geometries())
GSHHSFeature._geometries_cache[(scale, level)] = geoms
for geom in geoms:
if extent is None or extent_geom.intersects(geom):
Expand Down
14 changes: 13 additions & 1 deletion lib/cartopy/io/shapereader.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
from pathlib import Path
from urllib.error import HTTPError

from pyproj import CRS
import shapefile
import shapely.geometry as sgeom

from cartopy import config
import cartopy.crs as ccrs
from cartopy.io import Downloader


Expand Down Expand Up @@ -136,6 +138,13 @@ class BasicReader:
def __init__(self, filename, bbox=None, **kwargs):
# Validate the filename/shapefile
self._reader = reader = shapefile.Reader(filename, **kwargs)
# Try to set the CRS from the .prj file if it exists
self.crs = None
try:
with open(Path(filename).with_suffix('.prj'), 'rt') as fobj:
self.crs = ccrs.CRS(CRS.from_wkt(fobj.read()))
except FileNotFoundError:
pass
self._bbox = bbox
if reader.shp is None or reader.shx is None or reader.dbf is None:
raise ValueError("Incomplete shapefile definition "
Expand Down Expand Up @@ -195,8 +204,11 @@ class FionaReader:

def __init__(self, filename, bbox=None, **kwargs):
self._data = []

self.crs = None
with fiona.open(filename, **kwargs) as f:
# Create a pyproj CRS from the Fiona CRS
if f.crs_wkt:
self.crs = ccrs.CRS(CRS.from_wkt(f.crs_wkt))
if bbox is not None:
assert len(bbox) == 4
features = f.filter(bbox=bbox)
Expand Down
26 changes: 26 additions & 0 deletions lib/cartopy/tests/test_shapereader.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ def test_record(self):
assert actual == expected
assert lake_record.geometry == self.test_lake_geometry

def test_no_included_projection_file(self):
# No .prj file included with the lakes shapefile
assert self.reader.crs is None

def test_bounds(self):
if isinstance(self.reader, shp.BasicReader):
# tests that a file which has a record with a bbox can
Expand Down Expand Up @@ -121,3 +125,25 @@ def test_record(self):
if key in expected_attributes:
assert value == expected_attributes[key]
assert river_record.geometry == self.test_river_geometry

def test_included_projection_file(self):
# This shapefile includes a .prj definition
# NOTE: Fiona and pyproj CRS definitions differ slightly
if shp._HAS_FIONA:
wkt = ('GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",'
'ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],'
'PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],'
'CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],'
'ANGLEUNIT["degree",0.0174532925199433]],'
'AXIS["geodetic longitude (Lon)",east,ORDER[2],'
'ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]]')
else:
wkt = ('GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",'
'ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],'
'ID["EPSG",6326]],PRIMEM["Greenwich",0,'
'ANGLEUNIT["Degree",0.0174532925199433]],CS[ellipsoidal,2],'
'AXIS["longitude",east,ORDER[1],'
'ANGLEUNIT["Degree",0.0174532925199433]],'
'AXIS["latitude",north,ORDER[2],'
'ANGLEUNIT["Degree",0.0174532925199433]]]')
assert self.reader.crs.to_wkt() == wkt

0 comments on commit 74f1a77

Please sign in to comment.