Giter Site home page Giter Site logo

geopandas / contextily Goto Github PK

View Code? Open in Web Editor NEW
481.0 16.0 80.0 57.68 MB

Context geo-tiles in Python

Home Page: https://contextily.readthedocs.io/en/latest/

License: BSD 3-Clause "New" or "Revised" License

Python 0.20% Jupyter Notebook 99.80% Dockerfile 0.01%
tiles stamen openstreetmap python tile mapping webtiles geography cartography matplotlib osm stamen-maps

contextily's Introduction

contextily: context geo tiles in Python

contextily is a small Python 3 (3.9 and above) package to retrieve tile maps from the internet. It can add those tiles as basemap to matplotlib figures or write tile maps to disk into geospatial raster files. Bounding boxes can be passed in both WGS84 (EPSG:4326) and Spheric Mercator (EPSG:3857). See the notebook contextily_guide.ipynb for usage.

Tests codecov Binder

Tiles

The current tile providers that are available in contextily are the providers defined in the xyzservices package. This includes some popular tile maps, such as:

Dependencies

  • mercantile
  • numpy
  • matplotlib
  • pillow
  • rasterio
  • requests
  • geopy
  • joblib
  • xyzservices

Installation

Python 3 only (3.9 and above)

Latest released version, using pip:

pip3 install contextily

or conda:

conda install contextily

Contributors

contextily is developed by a community of enthusiastic volunteers. You can see a full list here.

If you would like to contribute to the project, have a look at the list of open issues, particularly those labeled as good first contributions.

License

BSD compatible. See LICENSE.txt

contextily's People

Contributors

andy-esch avatar choldgraf avatar chrstnbwnkl avatar darribas avatar datapolitan avatar dependabot[bot] avatar eighteyes avatar ewouth avatar hugovk avatar jacobjeppesen avatar jelson avatar jgaboardi avatar jorisvandenbossche avatar jpn-- avatar kannes avatar ljwolf avatar martinfleis avatar maxdswain avatar mjul avatar ocefpaf avatar qulogic avatar sgillies avatar szmoore avatar tristannew avatar uvchik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

contextily's Issues

[ENH]: backoff parameter?

Sometimes, if you hit APIs for too many tiles, the API will ratelimit.

It would be nice if there were a rudimentary support for a wait time within bounds2* functions, so that you could specify a number of seconds to wait between tile queries & a max_retries amount.

Error in generating base map

I have this code:

import pandas as pd
import numpy as np
from geopandas import GeoDataFrame
import geopandas
from shapely.geometry import LineString, Point
import matplotlib.pyplot as plt
import contextily

''' Do Something'''

df = start_stop_df.drop('track', axis=1)
crs = {'init': 'epsg:4326'}
gdf = GeoDataFrame(df, crs=crs, geometry=geometry)

ax = gdf.plot()
contextily.add_basemap(ax)
ax.set_axis_off()
plt.show()

Basically, this generates a background map that is in Singapore. However, when I run it, I get the following error: HTTPError: Tile URL resulted in a 404 error. Double-check your tile url:http://tile.stamen.com/terrain/29/268436843/268435436.png
However, it still produces this image:
Code output

How can I change the Tile URL? I would still like to have the map of Singapore as the base map.

EDIT:
Also tried including this argument to add_basemap:
url ='https://www.openstreetmap.org/#map=12/1.3332/103.7987'
Which produced this error:
OSError: cannot identify image file <_io.BytesIO object at 0x000001CC3CC4BC50>
I am running on contextily version 1.0rc1

Use standard urls with {x} instead of 'tileX'

As far as I am aware, it's common that urls are given in "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" format, instead the "tileZ/tileX/tileY" pattern we use in contextily. At least that is what I see used in leaflet based ones, and is also closer to the python formatting syntax.

What do you think to switch to that? (and we can certainly still support both in the beginning (to deprecate the current version) or even in the long term)

`rasterio` error when writing raster

If I set up a fresh conda environment:

conda create -n ctx_test -c conda-forge python=3.5 contextily ipython

which installs:

(ctx_test) dh073065:~ dani$ conda list
# packages in environment at /Users/dani/anaconda/envs/ctx_test:
#
affine                    2.1.0                    py35_0    conda-forge
appnope                   0.1.0                    py35_0    conda-forge
asn1crypto                0.22.0                   py35_0    conda-forge
attrs                     17.2.0                   py35_0    conda-forge
boost                     1.64.0                   py35_4    conda-forge
boost-cpp                 1.64.0                        1    conda-forge
boto3                     1.4.7                    py35_0    conda-forge
botocore                  1.5.92                   py35_0    conda-forge
bzip2                     1.0.6                         1    conda-forge
ca-certificates           2017.7.27.1                   0    conda-forge
cairo                     1.14.6                        4    conda-forge
cartopy                   0.15.1                   py35_4    conda-forge
certifi                   2017.7.27.1              py35_0    conda-forge
cffi                      1.10.0                   py35_0    conda-forge
chardet                   3.0.4                    py35_0    conda-forge
click                     6.7                      py35_0    conda-forge
click-plugins             1.0.3                    py35_0    conda-forge
cligj                     0.4.0                    py35_0    conda-forge
contextily                0.9.2                    py35_0    conda-forge
cryptography              2.0.3                    py35_0    conda-forge
curl                      7.54.1                        0    conda-forge
cycler                    0.10.0                   py35_0    conda-forge
decorator                 4.1.2                    py35_0    conda-forge
docutils                  0.14                     py35_0    conda-forge
expat                     2.1.0                         3    conda-forge
fontconfig                2.12.1                        4    conda-forge
freetype                  2.7                           1    conda-forge
freexl                    1.0.2                         2    conda-forge
geos                      3.5.1                         1    conda-forge
gettext                   0.19.8.1                      0    conda-forge
giflib                    5.1.4                         0    conda-forge
glib                      2.51.4                        0    conda-forge
hdf4                      4.2.12                        0    conda-forge
hdf5                      1.8.18                        1    conda-forge
hypothesis                3.23.0                   py35_0    conda-forge
icu                       58.1                          1    conda-forge
idna                      2.5                      py35_0    conda-forge
ipython                   6.1.0                    py35_0    conda-forge
ipython_genutils          0.2.0                    py35_0    conda-forge
jedi                      0.10.2                   py35_0    conda-forge
jmespath                  0.9.3                    py35_0    conda-forge
jpeg                      9b                            0    conda-forge
json-c                    0.12.1                        0    conda-forge
kealib                    1.4.7                         2    conda-forge
krb5                      1.14.2                        0    conda-forge
libdap4                   3.18.3                        2    conda-forge
libffi                    3.2.1                         3    conda-forge
libgdal                   2.1.4                         2    conda-forge
libgfortran               3.0.0                         0    conda-forge
libiconv                  1.14                          4    conda-forge
libkml                    1.3.0                         1    conda-forge
libnetcdf                 4.4.1.1                       6    conda-forge
libpng                    1.6.28                        1    conda-forge
libpq                     9.6.3                         0    conda-forge
libspatialite             4.3.0a                       15    conda-forge
libssh2                   1.8.0                         1    conda-forge
libtiff                   4.0.6                         7    conda-forge
libxml2                   2.9.4                         4    conda-forge
libxslt                   1.1.29                        5    conda-forge
lxml                      3.8.0                    py35_0    conda-forge
matplotlib                2.0.2                    py35_2    conda-forge
mercantile                0.9.0                    py35_0    conda-forge
mkl                       2017.0.3                      0
ncurses                   5.9                          10    conda-forge
numpy                     1.13.1                   py35_0
olefile                   0.44                     py35_0    conda-forge
openjpeg                  2.1.2                         2    conda-forge
openssl                   1.0.2l                        0    conda-forge
owslib                    0.14.0                   py35_1    conda-forge
pandas                    0.20.3                   py35_1    conda-forge
pcre                      8.39                          0    conda-forge
pexpect                   4.2.1                    py35_0    conda-forge
pickleshare               0.7.4                    py35_0    conda-forge
pillow                    4.2.1                    py35_1    conda-forge
pip                       9.0.1                    py35_0    conda-forge
pixman                    0.34.0                        0    conda-forge
poppler                   0.52.0                        2    conda-forge
poppler-data              0.4.7                         0    conda-forge
proj4                     4.9.3                         4    conda-forge
prompt_toolkit            1.0.15                   py35_0    conda-forge
ptyprocess                0.5.2                    py35_0    conda-forge
pycparser                 2.18                     py35_0    conda-forge
pyepsg                    0.3.2                    py35_0    conda-forge
pygments                  2.2.0                    py35_0    conda-forge
pympler                   0.5                      py35_0    conda-forge
pyopenssl                 17.2.0                   py35_0    conda-forge
pyparsing                 2.2.0                    py35_0    conda-forge
pyproj                    1.9.5.1                  py35_0    conda-forge
pyshp                     1.2.12                     py_0    conda-forge
pysocks                   1.6.7                    py35_0    conda-forge
python                    3.5.4                         0    conda-forge
python-dateutil           2.6.1                    py35_0    conda-forge
pytz                      2017.2                   py35_0    conda-forge
rasterio                  0.36.0                   py35_0    conda-forge
readline                  6.2                           0    conda-forge
requests                  2.18.4                   py35_1    conda-forge
s3transfer                0.1.11                   py35_0    conda-forge
scipy                     0.19.1              np113py35_0
setuptools                36.3.0                   py35_0    conda-forge
shapely                   1.6.1                    py35_1    conda-forge
simplegeneric             0.8.1                    py35_0    conda-forge
six                       1.10.0                   py35_1    conda-forge
snuggs                    1.4.1                    py35_0    conda-forge
sqlite                    3.13.0                        1    conda-forge
tk                        8.5.19                        2    conda-forge
tornado                   4.5.2                    py35_0    conda-forge
traitlets                 4.3.2                    py35_0    conda-forge
urllib3                   1.22                     py35_0    conda-forge
wcwidth                   0.1.7                    py35_0    conda-forge
wheel                     0.29.0                   py35_0    conda-forge
xerces-c                  3.1.4                         3    conda-forge
xz                        5.2.3                         0    conda-forge
zlib                      1.2.8                         3    conda-forge
zope                      1.0                      py35_0
zope.interface            4.4.2                    py35_0
(ctx_test) dh073065:~ dani$

Then I try (example taken from here):

In [1]: import contextily as ctx

In [2]: w, s, e, n = (-106.6495132446289, 25.845197677612305, -93.50721740722656, 36.49387741088867)

In [3]: _ = ctx.bounds2raster(w, s, e, n, 6, 'tx.tif', ll=True)

It returns the following error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-7db2ced4b25c> in <module>()
----> 1 _ = ctx.bounds2raster(w, s, e, n, 6, 'tx.tif', ll=True)

~/anaconda/envs/ctx_test/lib/python3.5/site-packages/contextily/__init__.py in bounds2raster(w, s, e, n, zoom, path, url, ll)
     77                       driver='GTiff', height=h, width=w,
     78                       count=b, dtype=Z.dtype,
---> 79                       crs='epsg:3857', transform=transform)
     80     for band in range(b):
     81         raster.write(Z[:, :, band], band+1)

~/anaconda/envs/ctx_test/lib/python3.5/site-packages/rasterio/__init__.py in open(path, mode, driver, width, height, count, crs, transform, dtype, nodata, **kwargs)
    156         raise TypeError("invalid driver: {0!r}".format(driver))
    157     if dtype and not check_dtype(dtype):
--> 158         raise TypeError("invalid dtype: {0!r}".format(dtype))
    159     if transform:
    160         transform = guard_transform(transform)

TypeError: invalid dtype: dtype('uint8')

In [4]:

It is strange, because with a similar set up, the CI seems to pass OK and it involves a call to bounds2raster.

@ocefpaf, @sgillies or @perrygeo, any ideas or suggestions? I'd really appreciate any sort of insight.

Higher level helper function to add background basemap to existing plot

From discussion on GeoPython, would there be interest in adding functionality along this lines?

def add_basemap(ax, zoom, url='http://tile.stamen.com/terrain/tileZ/tileX/tileY.png'):
    xmin, xmax, ymin, ymax = ax.axis()
    basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax, zoom=zoom, url=url)
    ax.imshow(basemap, extent=extent, interpolation='bilinear')
    # restore original x/y limits
    ax.axis((xmin, xmax, ymin, ymax))

I think something like this would make it easier to use contextily in practice to quickly add a map background to a plot.

The above now assumes you plotted on the ax in Web Mercator. In principle we could also accept a crs to also deal with other projections (but then you probably get even less quality basemap by transforming them).

cc @ljwolf

API: specify the basemap / tile source (online tile provider, local file)

https://github.com/darribas/contextily/pull/66 is adding new definitions of online providers (which information is encoded in a dictionary with a 'url' key and additional metadata, similar as what ipyleaflet is doing: https://github.com/jupyter-widgets/ipyleaflet/blob/master/ipyleaflet/basemaps.py)

That PR sparked some discussion about the API how to specify this basemap "source". See https://github.com/darribas/contextily/pull/66#issuecomment-517176827 and subsequent comments.

Extent of a basemap varies once it's been saved to disk

The extent of a basemap is changed

In [80]: pl = ctx.Place('boulder', zoom_adjust=-3, path='temp.tiff')

In [81]: pl.bbox_map
Out[81]: 
(-11740727.544603072,
 -11701591.786121061,
 4852834.0517692715,
 4891969.810251278)

In [82]: rio.open('temp.tiff').bounds
Out[82]: BoundingBox(left=-11740803.981631357, bottom=4852910.488797557, right=-11701668.223149346, top=4892046.247279563)

In [83]: rio.open('temp.tiff').bounds[0] == pl.bbox_map[0]
Out[83]: False

I'm not sure why this is happening but would be nice to at least know why. In principle, I'd expect both basemaps to have the same extent.

Define minimum support versions

We now have a ci/travis/36-minimal.yaml file. I think it would be a good idea to use that one to set some pinned versions (the other yaml file will then always test the latest version). Not necessarily for all dependencies, but for the main ones (eg matplotlib, rasterio, ..)

Just some initial ideas: matplotlib 2.2 (they still had a maintenance release of that branch earlier this year, but if that gives failures we can certainly go to matplotlib 3 as well), rasterio for a release of last year? (also need to check what is available through conda).
Not sure if we need to pin requests or pillow, maybe not.

Also, we can then mention those in the README

`nominating` warning

From the test suite:

tests/test_ctx.py::test_plot_map

  /home/travis/build/darribas/contextily/miniconda/envs/TEST/lib/python3.5/site-packages/geopy/geocoders/osm.py:138: 

UserWarning: Using Nominatim with the default "geopy/1.16.0" `user_agent` is strongly discouraged, as it violates Nominatim's ToS https://operations.osmfoundation.org/policies/nominatim/ and may possibly cause 403 and 429 HTTP errors. 

Please specify a custom `user_agent` with `Nominatim(user_agent="my-application")` or by overriding the default `user_agent`: `geopy.geocoders.options.default_user_agent = "my-application"`. In geopy 2.0 this will become an exception.

    UserWarning

Maybe for @choldgraf to have a look?

Lack of User-Agent header viiolates OSM usage policy

In tile._retryer the request is sent without a User-Agent header.
Previously I'd had no issues, however recently I started getting 403: Forbidden responses from OSM. Reading their usage policy reveals that this header is required:

Valid HTTP User-Agent identifying application. Faking another appโ€™s User-Agent WILL get you blocked.

It's possible this is a recent update, since I don't remember reading that earlier, or maybe they are just a bit lenient until you exceed a usage rate.

I was able to bypass the error by simply adding a User-Agent: contextily header.
However, I think to properly meet the usage requirements, this header needs to be set by the user of contextily to actually identify their app.

ImportError: libkea.so.1.4.7: cannot open shared object file: No such file or directory

Latest master passed on Python 3 four days ago:

https://travis-ci.org/darribas/contextily/builds/407264253

It now fails on Python 3:

==================================== ERRORS ====================================
______________________ ERROR collecting tests/test_ctx.py ______________________
ImportError while importing test module '/home/travis/build/hugovk/contextily/tests/test_ctx.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_ctx.py:3: in <module>
    import contextily as ctx
contextily/__init__.py:6: in <module>
    from .place import Place, plot_map
contextily/place.py:6: in <module>
    from .tile import howmany, bounds2raster, bounds2img, _sm2ll, _calculate_zoom
contextily/tile.py:11: in <module>
    import rasterio as rio
miniconda/envs/TEST/lib/python3.5/site-packages/rasterio/__init__.py:23: in <module>
    from rasterio._base import gdal_version
E   ImportError: libkea.so.1.4.7: cannot open shared object file: No such file or directory
___________________ ERROR collecting tests/test_providers.py ___________________
ImportError while importing test module '/home/travis/build/hugovk/contextily/tests/test_providers.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_providers.py:1: in <module>
    import contextily as ctx
contextily/__init__.py:6: in <module>
    from .place import Place, plot_map
contextily/place.py:6: in <module>
    from .tile import howmany, bounds2raster, bounds2img, _sm2ll, _calculate_zoom
contextily/tile.py:11: in <module>
    import rasterio as rio
miniconda/envs/TEST/lib/python3.5/site-packages/rasterio/__init__.py:23: in <module>
    from rasterio._base import gdal_version
E   ImportError: libkea.so.1.4.7: cannot open shared object file: No such file or directory

https://travis-ci.org/hugovk/contextily/builds/408951523

This also affects PRs, such as #40. See also some extra info at https://github.com/darribas/contextily/pull/40#issuecomment-408434736.

Could this be a problem with a Conda update, similar to conda-forge/fiona-feedstock#86?

ENH: support overlay tiles

The new providers (https://github.com/darribas/contextily/pull/66) contain also a set of sources for "overlay" tiles (like Stamen.TonerLabels or OpenMapSurfer.AdminBounds).

Those are tiles with a transparent background that can be added on top of another layer. We can load them, but the problem is that currently, due to the way we are reading the image and storing it as an array, the transparent background gets converted into a black background (of course defeating the purpose of being able to overlay it).

Example:

df = geopandas.read_file(geopandas.datasets.get_path('nybb')).to_crs(epsg=3857)
ax = df.plot(figsize=(9, 9), alpha=0.5)
ctx.add_basemap(ax, url=ctx.providers.Stamen.TonerLabels)
# or
ctx.add_basemap(ax, url=ctx.providers.OpenMapSurfer.AdminBounds)

gives

image

If we want to support this, we have to investigate if imshow can support transparency (I think yes, by using alpha / RGBA colors, see eg https://matplotlib.org/3.1.1/gallery/images_contours_and_fields/image_transparency_blend.html) and how to read the tiles into an array with those alpha values included.

All providers that are of overlay type can be seen from this query in the leaflet-providers code:

https://github.com/leaflet-extras/leaflet-providers/blob/9eb968f8442ea492626c9c8f0dac8ede484e6905/preview/preview.js#L56-L70

OSError: cannot identify image file

Hi, thanks for the useful package! I'm trying to run the following script to add a basemap, following the example in the geopandas documentation http://geopandas.org/gallery/plotting_basemap_background.html,

import contextily as ctx

def add_basemap(ax, zoom, url='http://a.tile.stamen.com/terrain/{z}/{x}/{y}.png'):
    xmin, xmax, ymin, ymax = ax.axis()
    basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax, zoom=zoom, url=url)
    ax.imshow(basemap, extent=extent, interpolation='bilinear')
    # restore original x/y limits
    ax.axis((xmin, xmax, ymin, ymax))

gdf_longterm_means.crs = {'init': 'epsg:4326'}
base = santa_ynez_ws.to_crs(epsg=3857).plot(color = 'blue', edgecolor='black', figsize=(10, 10), alpha=0.5)
gdf_longterm_means.to_crs(epsg=3857).plot(ax = base, marker = 'o', markersize = 5, column="Long Term Annual Means", cmap="OrRd")
add_basemap(base, zoom=10)

but no matter which url I use in add_basemap, I get this error

----------------------------------------------------------------------
OSError                              Traceback (most recent call last)
<ipython-input-31-469fddd7ac59> in <module>()
      2 base = santa_ynez_ws.to_crs({'init' :'epsg:3857'}).plot(color = 'blue', edgecolor='black', figsize=(10, 10), alpha=0.5)
      3 gdf_longterm_means.to_crs({'init' :'epsg:3857'}).plot(ax = base, marker = 'o', markersize = 5, column="Long Term Annual Means", cmap="OrRd")
----> 4 add_basemap(base, zoom=10)

<ipython-input-25-56aaaf822ec2> in add_basemap(ax, zoom, url)
      3 def add_basemap(ax, zoom, url='http://a.tile.stamen.com/terrain/{z}/{x}/{y}.png'):
      4     xmin, xmax, ymin, ymax = ax.axis()
----> 5     basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax, zoom=zoom, url=url)
      6     ax.imshow(basemap, extent=extent, interpolation='bilinear')
      7     # restore original x/y limits

~/anaconda3/lib/python3.6/site-packages/contextily/tile.py in bounds2img(w, s, e, n, zoom, url, ll, wait, max_retries)
    153         request = _retryer(tile_url, wait, max_retries)
    154         with io.BytesIO(request.content) as image_stream:
--> 155             image = Image.open(image_stream).convert('RGB')
    156             image = np.asarray(image)
    157         # ---

~/anaconda3/lib/python3.6/site-packages/PIL/Image.py in open(fp, mode)
   2620         fp.close()
   2621     raise IOError("cannot identify image file %r"
-> 2622                   % (filename if filename else fp))
   2623 
   2624 #

OSError: cannot identify image file <_io.BytesIO object at 0x7faca3cc79e8>

I think this might just be the version of PIL I'm using but not sure. My PIL version is 5.2 and I'm using python 3.6.

Adding option to restrict `ax` extent to original one when using `add_basemap`

When a user adds a basemap with add_basemap, the extent of the returned figure is automatically reset to fit that of the merged tiles. In some cases, this can render the original data in a too small part of the map. It'd be nice if add_basemap could take an argument reset_extent set to False by default to maintain original behaviour that made sure the outcoming figure had the same extent of the incoming ax object.

Drop support for Python 2.*?

With the new overhaul, we're hitting problems to support seamlessly Python 2. For example, the following fails:

E     File "/Users/dani/code/contextily/contextily/plotting.py", line 78
E       zoom = _calculate_zoom(*min_ll, *max_ll)
E                                       ^
E   SyntaxError: invalid syntax

Or the following:

contextily/__init__.py:5: in <module>
    from . import tile_providers as sources
E   ImportError: cannot import name tile_providers

I imagine there'll be more but those two are picked up by the tests right now. What'd be folks' view on trying a bit to maintain support for Python 2 Vs. just dropping it in favor of a Python 3 only? @ljwolf @choldgraf @jorisvandenbossche @lwasser

Support alternative tile provider

Not sure how common this is but tile.stamen.com seems to be down right now. Is there another well-known tile provider for this kind of thing? If so maybe it'd be worth supporting it as a suggested parameter for url

add city search functionality

Hey there - I'm working to convert some geomapping code from R into Python, and looking to recreate some of the get_map functionality. It seems like this package along with geopy or some other geocoding tool could make it easy to return a map corresponding to a search query. Have you thought about adding in this feature?

It doesn't seem like it'd be too tough and could be quite useful. Imagine just doing ctx.get_map('texas') and it'd try to be smart about the bounding box size, zoom level, etc.

Sub-optimal default attribution

https://github.com/darribas/contextily/pull/43 added a default attribution text (original discussion about it in https://github.com/darribas/contextily/issues/25).

However, I am opening an issue to argue that this default is not really optimal IMO:

  • For many plots, the text is too big and "falls off" the image.

    Eg, just using all defaults and plotting the NYC boroughs gives me:

    import contextily
    import geopandas
    
    df = geopandas.read_file(geopandas.datasets.get_path('nybb'))
    df = df.to_crs(epsg=3857)
    
    ax = df.plot(ax=ax)
    contextily.add_basemap(ax)
    

    gives

    test

    (which doesn't give a good first impression I think)

  • It hardcodes the text, making this only applicable for the default tile provider.
    Which means that once you specify another provider / map style, you also need to specify the attribution text to make your figure not incorrect.

Both are probably fixable (for the first, we might need to somehow detect what the text size can be given the image size, not sure if that is possible with matplotlib. For the second we might need more complex provider definition with additional attributes).
But personally, until those are fixed, I would remove the default addition of the attribution text (in the idea of better no attribution than a sub-optimal one). To be clear, in that proposal, we of course would keep the generic functionality added in the other PR, so you can still manually add an attribution with add_attribution or specifying the text.

Thought on disabling the attribution text by default for now?

Add ability to cache the downloaded web tiles

Since downloading the web tiles takes a bit of time, and typically you generate a figure multiple times while iterating on it, so I think that it would be nice to provide some caching mechanism for the downloaded web tiles.

(+ other possible advantages: easier to use in case of flaky / limited internet connection, potentially more responsive if we could trigger matplotlib to re-draw when resizing)

I explored some options here: http://nbviewer.jupyter.org/gist/jorisvandenbossche/eef4dc7d7770e11e3056e8adb9d08b1c, and it improves plotting the nybb dataframe from 1s to 0.25 s.

The options I explored there:

Thoughts? Other ideas to do this, or preferences?

[ENH] Modify `add_basemap` for local windowed reading

Currently, when the user points to a local file on add_basemap, the file is fully read and then clipped/zoomed on to the extent ax was passed on. For cases where a raster is too large, it'd be nice to provide an option so that only the required window is read from the local raster file. This would make add_basemap a very easy method to plot parts of large rasters (e.g. world night lights) as basemaps.

Pointers:

  • Code should be implemented on L.96-plottint.py to avoid always reading the full file first.
  • Potential code to use for windowed reading from rasterio masking functionality (URL)

To think about:

  • Should we let the user choose to read the whole file and adapt the the extent of ax accordingly? This might fit other use cases.
  • What'd be the best API to enable this functionality? I'm thinking an additional argument on add_basemap such as read_full=False that, when True reads the whole file and adjusts the axis extent, otherwise in only reads a window for the extent of ax?

Error in contextily example on GeoPandas website

When I tried to reproduce the contextily example on the GeoPandas website I got the following error:

df = df.to_crs(epsg=3857)
---------------------------------------------------------------------------
DataDirError                              Traceback (most recent call last)
/anaconda3/envs/aec/lib/python3.7/site-packages/pyproj/datadir.py in get_data_dir()

DataDirError: Valid PROJ data directory not found.Either set the path using the environmental variable PROJ_LIB or with `pyproj.datadir.set_data_dir`.

Exception ignored in: 'pyproj._datadir.get_pyproj_context'
Traceback (most recent call last):
  File "/anaconda3/envs/aec/lib/python3.7/site-packages/pyproj/datadir.py", line 99, in get_data_dir
pyproj.exceptions.DataDirError: Valid PROJ data directory not found.Either set the path using the environmental variable PROJ_LIB or with `pyproj.datadir.set_data_dir`.

---------------------------------------------------------------------------
CRSError                                  Traceback (most recent call last)
<ipython-input-32-de7bc15c702f> in <module>
----> 1 df = df.to_crs(epsg=3857)

/anaconda3/envs/aec/lib/python3.7/site-packages/geopandas/geodataframe.py in to_crs(self, crs, epsg, inplace)
    457         else:
    458             df = self.copy()
--> 459         geom = df.geometry.to_crs(crs=crs, epsg=epsg)
    460         df.geometry = geom
    461         df.crs = geom.crs

/anaconda3/envs/aec/lib/python3.7/site-packages/geopandas/geoseries.py in to_crs(self, crs, epsg)
    302             except TypeError:
    303                 raise TypeError('Must set either crs or epsg for output.')
--> 304         proj_in = pyproj.Proj(self.crs, preserve_units=True)
    305         proj_out = pyproj.Proj(crs, preserve_units=True)
    306         if _PYPROJ2:

/anaconda3/envs/aec/lib/python3.7/site-packages/pyproj/proj.py in __init__(self, projparams, preserve_units, **kwargs)

/anaconda3/envs/aec/lib/python3.7/site-packages/pyproj/crs.py in from_user_input(cls, value)

/anaconda3/envs/aec/lib/python3.7/site-packages/pyproj/crs.py in __init__(self, projparams, **kwargs)

pyproj/_crs.pyx in pyproj._crs._CRS.__init__()

CRSError: Invalid projection: +init=epsg:2263 +type=crs

I'm running the following version of contextily:

$ list conda

contextily                1.0rc1                     py_0    conda-forge

Add option to include license message on `ax` object

This should be the default (albeit an option) in the upcoming add_basemap method and will include a license message. The Stamen message can be seen here:

  • For Toner and Terrain: "Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL".
  • For Watercolor: Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.

Doc updates pre 1.0

The following needs to be added to make the new features exposed to the public:

  • Add use of add_basemap to the guide notebook
  • Update the Place.plot() changes
  • Update README.md

On-the-fly reprojection of tile maps

One that I've discussed with @jorisvandenbossche offline and now have made some impromptu progress. Currently, contextily only supports Web Mercator so, whatever CRS the original data is expressed in, if the user wants to combine them with tile maps, they need to move their data to WM. In some cases, that's OK but, in other contexts, that might be expensive and it might be a lot better to reproject (warp) the tile map.

To get started, here's a tentative function that does this:

from rasterio.io import MemoryFile
from rasterio.vrt import WarpedVRT
from rasterio.enums import Resampling

def warp_tiles(img, ext, 
               t_crs='EPSG:4326',
               resampling=Resampling.bilinear):
    '''
    Reproject (warp) a Web Mercator basemap into any CRS on-the-fly
    ...
    
    Arguments
    ---------
    img         : ndarray
                  Image as a 3D array of RGB values (e.g. as returned from
                  `contextily.bounds2img`)
    ext         : tuple
                  Bounding box [minX, maxX, minY, maxY] of the returned image
    t_crs       : str/CRS
                  [Optional. Default='EPSG:4326'] Target CRS, expressed in any
                  format permitted by rasterio. Defaults to WGS84 (lon/lat)
    resampling  : <enum 'Resampling'>
                  [Optional. Default=Resampling.bilinear] Resampling method for
                  executing warping, expressed as a `rasterio.enums.Resampling
                  method
    
    Returns
    -------
    img         : ndarray
                  Warped Image as a 3D array of RGB values
    ext         : tuple
                  Bounding box [minX, maxX, minY, maxY] of the returned (warped)
                  image
    '''
    h, w, b = img.shape
    # --- https://mapbox.github.io/rasterio/quickstart.html#opening-a-dataset-in-writing-mode
    minX, maxX, minY, maxY = ext
    x = np.linspace(minX, maxX, w)
    y = np.linspace(minY, maxY, h)
    resX = (x[-1] - x[0]) / w
    resY = (y[-1] - y[0]) / h
    transform = from_origin(x[0] - resX / 2,
                            y[-1] + resY / 2, resX, resY)
    # --- Write basemap into memory file
    with MemoryFile() as memfile:
        with memfile.open(driver='GTiff', height=h, width=w, \
                          count=b, dtype=str(img.dtype.name), \
                          crs='epsg:3857', transform=transform) as mraster:
            for band in range(b):
                mraster.write(img[:, :, band], band+1)
            # --- Virtual Warp
            with WarpedVRT(mraster, crs=t_crs,
                           resampling=resampling) as vrt:
                src_wm = vrt.read().transpose(1, 2, 0)
    bb = vrt.bounds
    extent = bb.left, bb.right, bb.bottom, bb.top
    return src_wm, extent

I'm putting it in this precarious format, rather than as a PR so:

  1. Folks quickly say if this API works

  2. We can discuss where it could go (my first thought is on tile.py but haven't thought too much about it

  3. Folks can also chip in on how this could be exposed to end-users. For now, this is all I can think of:

    • As a standalone function warp_tiles that can be piped with bounds2img
    • Through a t_crs argument in add_basemap that defaults to EPSG:3857 and adds a warped basemap to a set of provided matplotlib.Axes
    • Through the Place API, where the user can also specify a t_crs argument when initialising and the response is returned in that CRS (maybe @choldgraf has some input on this?).

Ideas/suggestions?

pixelated images

Oftentimes the images that contextily grabs are quite pixelated. I haven't found a good way to download less-pixelated images other than increasing the zoom level, which generally makes the download take waaaay longer. Do you have any tips for getting the same zoom level, but at a higher resolution?

Release 0.99

Cut release and push to pypi and conda-forge.

ENH: Handle minimum / maximum zoom limit for known providers

using automatic zoom in bounds2img for a small area results in high zoom level values that exceeds the available zoom levels of the provider, and rises HTTPError:

import contextily as ctx
img, ext = ctx.bounds2img(1231934.7608171296, 5785711.490238651, 1232411.1480162584, 5785880.3953617485)

It would be great to clip the zoom level to a max_zoom property for the known providers (18 for stamen, 19 for OSM).

Add auto-zoom

There could be an 'auto' option for the zoom argument. This would use howmany under the hood and pick the greatest zoom that had to download less than X tiles (e.g. 30).

ResourceWaring (unclosed file) in the tests

When running the tests, I see

sys:1: ResourceWarning: unclosed file <_io.TextIOWrapper name=9 mode='w' encoding='UTF-8'>

(not sure if the error is in the tests or in the actual contextily package)

[ENH] Show a little simpler/slightly adjusted example code

Hey,

I really dig the example as it stands, but one thing I found difficult/not intuitive was to map just directly from a tile call.

In the example notebook, you save tiles to a file, then use rio to read in that file, assign its crs attribute to the secondary data's to_crs transform, and plot it.
I think it might help to show that this crs is webmercator, so that for anyone who wants to plot something on top of contextily tiles can cast to_crs(epsg=3857) without sending the tiles to file.

For a dataframe of Brooklyn neighbourhoods kings_county in long-lat polygons:

raster, bounds = ctx.bounds2img(*kings_county.total_bounds, zoom=12, ll=True, 
                                url='http://tile.stamen.com/toner-lite/tileZ/tileX/tileY.png') #cause I want toner tiles
f = plt.figure()
ax = plt.gca()
ax.imshow(raster, extent=bounds) #where bounds is now in webmercator & (w,e,s,n) 
kings_county.to_crs(epsg=3857).plot(ax=ax)
plt.show()

neighbs

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.