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

ENH: Tile warping (#48) #49

Merged
merged 17 commits into from
Aug 1, 2019
Merged

ENH: Tile warping (#48) #49

merged 17 commits into from
Aug 1, 2019

Conversation

darribas
Copy link
Collaborator

@darribas darribas commented Jan 6, 2019

Initial commit to add tile warping functionality

@coveralls
Copy link

coveralls commented Jan 6, 2019

Coverage Status

Coverage increased (+1.4%) to 92.652% when pulling 8877dd4 on tile_warping#48 into 09db67e on master.

@darribas
Copy link
Collaborator Author

darribas commented Jan 6, 2019

Anybody recognises the errors popping up? Never seen them and they seem to do with pytest, not contextily? @jorisvandenbossche @ljwolf

@ljwolf
Copy link
Member

ljwolf commented Jan 6, 2019

Yeah, that's really odd, and it's only affecting the 3.6 runs? never seen this before, but it's probably not anything you've changed.

@jorisvandenbossche
Copy link
Member

The error you're seeing was a temporary problem with pytest-cov (the latest pytest release yesterday broke that package, but they already released an update), so it you restart the travis build (or push again), it should work again.

@jorisvandenbossche
Copy link
Member

Quick feedback: I tried this branch and I think there is still some error in the tile url determination, as it is downloading the wrong tiles:

df = geopandas.read_file(geopandas.datasets.get_path('nybb'))
ax = df.plot()
contextily.add_basemap(ax, crs=df.crs)

gives me

contextily_warping

(instead of New York boroughs)

@jorisvandenbossche
Copy link
Member

Something like

--- a/contextily/plotting.py
+++ b/contextily/plotting.py
@@ -84,6 +84,12 @@ def add_basemap(ax, zoom=ZOOM, url=sources.ST_TERRAIN,
         # Extent
         left, right = ax.get_xlim()
         bottom, top = ax.get_ylim()
+        if crs is not None:
+            import pyproj
+            proj_in = pyproj.Proj(crs, preserve_units=True)
+            proj_out = pyproj.Proj({'init': 'epsg:3857'}, preserve_units=True)
+            left, bottom = pyproj.transform(proj_in, proj_out, left, bottom)
+            right, top = pyproj.transform(proj_in, proj_out, right, top)
         # Zoom
         if isinstance(zoom, str) and (zoom.lower() == 'auto'):
             min_ll = _sm2ll(left, bottom)

fixes it. But the question is maybe a bit which library we want to use here exactly.

@darribas
Copy link
Collaborator Author

OK I've worked on this a bit more and I think it actually now fully works as one would expect it. Things I've included:

  • User methods in tile.py to warp tiles (warp_tiles) and rasterio-like rasters (warp_img_transform)
  • Internal method for the warping (_warper) in tile.py
  • Added functionality to add_basemap to warp both tiles pulled from the web and local files
  • A demonstration notebook (warping_guide.ipynb) to show you how this works

One thing I wanted to check with you. As @jorisvandenbossche mentions, for this to work, we need reprojection capabilities for points. Currently, I'm doing this in _reproj_bb and I use geopandas. This is probably overkill to reproject two points but I wanted a quick prototype. Ideally, we'd use whatever rasterio uses and we don't need to add any extra dependencies. Do you know what this would be? It should be an easy swap as both the import and the functionality are encapsulated inside _reproj_bb.

If you OK this (@jorisvandenbossche and/or @ljwolf ), I'll add tests and make sure they pass, it should be pretty straightforward.

@ljwolf
Copy link
Member

ljwolf commented Apr 26, 2019

Personally, I think it's fine to use geopandas's crs stuff for this. It's not a particularly onerous dependency given we will already have rasterio as one for the resampling.

@jorisvandenbossche
Copy link
Member

Some user feedback:

import geopandas
import contextily

countries = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres'))
countries = countries[countries['name'] != 'Antarctica'].copy()

Using WGS84 lon/lat, I need to limit the axis extent manually, otherwise pyproj cannot project the image bounds to WebMercator.

ax = countries.plot(facecolor='none', edgecolor='k')
ax.axis((-180, 180, -60, 84))
contextily.add_basemap(ax, attribution=None)

Should we try to automatically limit the bounds? (we know the bounds of web mercator)
Or, we should actually check if the providers also provide tiles outside of those bounds. I actually suppose they do, which means we should do the conversion of lon/lat to web mercator ourselves (or specify to pyproj to allow out of bounds conversion)

Converting the countries first to WebMercator gives the following bug:

countries2 = countries.to_crs(epsg=3857)

ax = countries2.plot(facecolor='none', edgecolor='k')
contextily.add_basemap(ax, attribution=None, crs=countries2.crs)

index

(which doesn't happen on master)

In general, I am also not sure we should by default warp the web mercator images to WGS84 / plate carree coordinates. This will give quite distorted images.
The better options would be do automatically convert the axis data to web mercator coordinates under the hood, and keeping the images in their original state (I think this is what all web tile + lon/lat (geojson) based plotting apps do). But, that might not be that easy to do .. (as you probably need to set up such transform when creating the ax ..)

@darribas
Copy link
Collaborator Author

New commits pass locally and implement warping with a default to no warping.

@darribas
Copy link
Collaborator Author

@jorisvandenbossche / @ljwolf, any idea of the error that's returning now? I've not seen it before and it's not the rasterio install from the weekend. Maybe something related to #44?

@jorisvandenbossche
Copy link
Member

@darribas seems something went wrong with your rebase / merge of master, as the diff now shows many things of other PRs. Maybe try to squash it into a single commit to clean-up?

@jorisvandenbossche
Copy link
Member

any idea of the error that's returning now?

The first one not directly, the second one if from a missing geopandas

@darribas
Copy link
Collaborator Author

How can I squash? I think it was my merge with master, which was effective but not elegant? :-)

@darribas
Copy link
Collaborator Author

hey @jorisvandenbossche this is passing after the last commit to add geopandas as dependency. How should I do the squashing? Do you think it's compulsory? 😄

@darribas
Copy link
Collaborator Author

Re-up of this one for @jorisvandenbossche and @ljwolf to have a look. if It works we can merge in and we're closer to the release by the end of July :)

Adding CRS option to add_basemap

Adding warping imports on tile.py

Adding _warper, warp_tiles and warp_image_transform to tile.py to be able to reproject tiles and other rasters on-the-fly

Adding warping functionality to add_basemap

Adding notebook guide for warping functionality (we might remove before merging into master but useful for tests and guidance

setting default to no warping on add_basemap

Adding warp_tiles and warp_img_transform to user land

Adding tests for warping functionality in tile.py

Adding tests for warping functionality in add_basemap

adding geopandas to test suite so warping tests pass
@jorisvandenbossche jorisvandenbossche changed the title [WIP] Tile warping#48 ENH: Tile warping (#48) Jul 31, 2019
@jorisvandenbossche
Copy link
Member

@darribas I rebased / squashed everything to fix the conflicts, and forced pushed. The diff looks better now (and I hope I fixed everything correctly, if you can quickly check the diff?). Best be careful how to pull it again locally for you.

@jorisvandenbossche
Copy link
Member

jorisvandenbossche commented Jul 31, 2019

Some feedback:

  • the notebook is not working for me (might be that it needs to be updated for the latest changes, eg that 4326 is no longer the default crs?).
    In addition, I would personally also not show it with EPSG:4326, but rather with a projected CRS (I think that is a better use case to show)
  • I still see the bug with the black background I showed above (https://github.com/darribas/contextily/pull/49#issuecomment-490089359). Now it less urgent, as the default is again Mercator, so you don't need to specify the Mercator CRS. But if you do, you get thus the above bug, and it might be pointing to a problem in the reprojection, I don't know.

out_bb = in_bb.to_crs(t_crs)
left, top = list(out_bb['lt'].coords)[0]
right, bottom = list(out_bb['rb'].coords)[0]
return left, right, bottom, top
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can rather simply implement this with just pyproj (or even simpler, also no need for creating points of it), and so not need to add geopandas as a dependency (only pyproj). I think the following should be a drop in replacement:

def _reproj_bb(left, right, bottom, top, s_crs, t_crs):
    proj_source = pyproj.Proj(s_crs, preserve_units=True)
    proj_target = pyproj.Proj(t_crs, preserve_units=True)
    x, y = pyproj.transform(proj_source, proj_target, [left, right], [bottom, top])
    left, right = x
    bottom, top = y
    return left, right, bottom, top

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, rasterio also has a rasterio.warp.transform_bounds(src_crs, dst_crs, left, bottom, right, top) which I think does exactly what we need here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I don't think that was there last time I checked on rasterio! Addressed in b0354e6

@@ -205,6 +209,125 @@ def _fetch_tile(tile_url, wait, max_retries):
return image


def warp_tiles(img, ext,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ext -> extent ?

(I find that more readable, and it's also called like that in the return section of the bounds2img docstring)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed in 8877dd4

contextily/tile.py Outdated Show resolved Hide resolved
contextily/tile.py Show resolved Hide resolved
@jorisvandenbossche jorisvandenbossche merged commit dcb3854 into master Aug 1, 2019
@jorisvandenbossche
Copy link
Member

Merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants