Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly overlay features #1145

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion hvplot/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1383,7 +1383,10 @@ def _apply_layers(self, obj):
scale)
else:
feature_obj = feature_obj.opts(scale=scale)
obj = feature_obj * obj
if feature_obj.group in ["Land", "Ocean"]:
obj = feature_obj * obj # Underlay land/ocean
else:
obj = obj * feature_obj # overlay everything else

if self.tiles:
tile_source = 'EsriImagery' if self.tiles == 'ESRI' else self.tiles
Expand Down
6 changes: 6 additions & 0 deletions hvplot/tests/testgeo.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ def test_plot_with_coastline_sets_geo_by_default(self):
coastline = plot.get(1)
self.assertIsInstance(coastline, gv.Feature)

def test_plot_with_features_properly_overlaid_underlaid(self):
# land should be under, borders should be over
plot = self.df.hvplot.points('x', 'y', features=["land", "borders"])
assert plot.get(0).group == "Land"
assert plot.get(2).group == "Borders"

def test_plot_with_coastline_scale(self):
plot = self.df.hvplot.points('x', 'y', geo=True, coastline='10m')
opts = plot.get(1).opts.get('plot')
Expand Down
8 changes: 8 additions & 0 deletions hvplot/tests/testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ def test_proj_to_cartopy(self):

assert isinstance(crs, self.ccrs.CRS)

def test_proj_to_cartopy_wkt_string(self):
from ..util import proj_to_cartopy
crs = proj_to_cartopy('GEOGCRS["unnamed",BASEGEOGCRS["unknown",DATUM["unknown",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],DERIVINGCONVERSION["unknown",METHOD["PROJ ob_tran o_proj=latlon"],PARAMETER["o_lon_p",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],PARAMETER["o_lat_p",37.5,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],PARAMETER["lon_0",357.5,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]]') # noqa: E501

assert isinstance(crs, self.ccrs.RotatedPole)
assert crs.proj4_params["lon_0"] == 357.5
assert crs.proj4_params["o_lat_p"] == 37.5


class TestDynamicArgs(TestCase):

Expand Down
51 changes: 29 additions & 22 deletions hvplot/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,11 @@ class Dummy:
out = pyproj.Proj(crs.to_wkt(), preserve_units=True)
elif isinstance(crs, dict) or isinstance(crs, str):
if isinstance(crs, str):
# quick fix for https://github.com/pyproj4/pyproj/issues/345
crs = crs.replace(' ', '').replace('+', ' +')
try:
crs = pyproj.CRS.from_wkt(crs)
except RuntimeError:
# quick fix for https://github.com/pyproj4/pyproj/issues/345
crs = crs.replace(' ', '').replace('+', ' +')
try:
out = pyproj.Proj(crs, preserve_units=True)
except RuntimeError:
Expand Down Expand Up @@ -117,7 +120,6 @@ def proj_to_cartopy(proj):
a cartopy.crs.Projection object
"""

import cartopy
import cartopy.crs as ccrs
try:
from osgeo import osr
Expand All @@ -126,9 +128,8 @@ def proj_to_cartopy(proj):
has_gdal = False

proj = check_crs(proj)

if proj_is_latlong(proj):
return ccrs.PlateCarree()
if proj is None:
raise ValueError(f"proj must be a valid pyproj.Proj object {proj}")

srs = proj.srs
if has_gdal:
Expand Down Expand Up @@ -168,19 +169,23 @@ def proj_to_cartopy(proj):
except:
pass
if k == 'proj':
if v == 'tmerc':
if v == "longlat":
cl = ccrs.PlateCarree
elif v == 'tmerc':
cl = ccrs.TransverseMercator
kw_proj['approx'] = True
if v == 'lcc':
elif v == 'lcc':
cl = ccrs.LambertConformal
if v == 'merc':
elif v == 'merc':
cl = ccrs.Mercator
if v == 'utm':
elif v == 'utm':
cl = ccrs.UTM
if v == 'stere':
elif v == 'stere':
cl = ccrs.Stereographic
if v == 'ob_tran':
elif v == 'ob_tran':
cl = ccrs.RotatedPole
else:
raise NotImplementedError('Unknown projection {}'.format(v))
if k in km_proj:
if k == 'zone':
v = int(v)
Expand All @@ -200,7 +205,7 @@ def proj_to_cartopy(proj):
if cl.__name__ == 'Mercator':
kw_proj.pop('false_easting', None)
kw_proj.pop('false_northing', None)
if Version(cartopy.__version__) < Version('0.15'):
if "scale_factor" in kw_proj:
kw_proj.pop('latitude_true_scale', None)
elif cl.__name__ == 'Stereographic':
kw_proj.pop('scale_factor', None)
Expand All @@ -214,6 +219,8 @@ def proj_to_cartopy(proj):
else:
kw_proj.pop('latitude_true_scale', None)

print(kw_proj)

try:
return cl(globe=globe, **kw_proj)
except TypeError:
Expand All @@ -229,7 +236,8 @@ def process_crs(crs):
1. EPSG codes: Defined as string of the form "EPSG: {code}" or an integer
2. proj.4 string: Defined as string of the form "{proj.4 string}"
3. cartopy.crs.CRS instance
4. None defaults to crs.PlateCaree
4. WKT string: Defined as string of the form "{WKT string}"
5. None defaults to crs.PlateCaree
"""
missing = []
try:
Expand All @@ -253,25 +261,24 @@ def process_crs(crs):
errors = []
if isinstance(crs, str) and crs.lower().startswith('epsg'):
try:
crs = crs[5:].lstrip().rstrip()
return ccrs.epsg(crs)
crs = pyproj.CRS.from_epsg(crs).to_wkt()
except Exception as e:
errors.append(e)
if isinstance(crs, int):
try:
return ccrs.epsg(crs)
crs = pyproj.CRS.from_epsg(crs).to_wkt()
except Exception as e:
crs = str(crs)
errors.append(e)
if isinstance(crs, (str, pyproj.Proj)):
try:
return proj_to_cartopy(crs)
except Exception as e:
errors.append(e)
return proj_to_cartopy(crs)
if isinstance(crs, ccrs.CRS):
return crs

raise ValueError("Projection must be defined as a EPSG code, proj4 string, cartopy CRS or pyproj.Proj.") from Exception(*errors)
raise ValueError(
"Projection must be defined as a EPSG code, proj4 string, "
"WKT string, cartopy CRS, or pyproj.Proj."
) from Exception(*errors)


def is_list_like(obj):
Expand Down