SciTools / cartopy

Cartopy - a cartographic python library with matplotlib support
https://scitools.org.uk/cartopy/docs/latest
BSD 3-Clause "New" or "Revised" License
1.43k stars 365 forks source link

Poorly looking Cartopy rendered tiles #1048

Closed stefanomattia closed 6 years ago

stefanomattia commented 6 years ago

Description

I posted this on stackoverflow but got no answers so far, hope someone here could shed some light on it. In a nutshell, my tiles look ugly, for example:

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt

extent = [5, 20, 36, 48]

request = cimgt.GoogleTiles()

fig, ax = plt.subplots(figsize=(10, 15))
ax = plt.axes(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request, 6)
plt.show()

Produces:

unknown-3

Look at those pixelated text labels and overall poor resolution. I played with different zoom levels and different styles with no success.

When displaying the same map on google maps, I get the usual nicely rendered look:

screen shot 2018-03-08 at 10 31 44

I can change tile provider but results are similar:

request = cimgt.OSM()

fig, ax = plt.subplots(figsize=(10, 15))
ax = plt.axes(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request, 6)
plt.show()

unknown-4

It doesn't matter if I run the code on a Jupyter notebook or a python console or on a different machine, results are identical.

Any clue?

Traceback

Full environment definition ### Operating system macOS High Sierra 10.13.3 (17D47) ### Cartopy version 0.16.0 ### conda list ``` # packages in environment at /Users/stefano/anaconda3: # _ipyw_jlab_nb_ext_conf 0.1.0 py36h2fc01ae_0 alabaster 0.7.10 py36h174008c_0 altair 1.2.1 py_0 conda-forge anaconda custom py36ha4fed55_0 anaconda-client 1.6.5 py36h04cfe59_0 anaconda-navigator 1.6.9 py36ha31b149_0 anaconda-project 0.8.0 py36h99320b2_0 appnope 0.1.0 py36hf537a9a_0 appscript 1.0.1 py36h9e71e49_1 argparse 1.4.0 asn1crypto 0.22.0 py36hb705621_1 astroid 1.5.3 py36h1333018_0 astropy 2.0.2 py36hf79c81d_4 autopep8 1.3.4 babel 2.5.0 py36h9f161ff_0 backports 1.0 py36ha3c1827_1 backports.shutil_get_terminal_size 1.0.0 py36hd7a2ee4_2 beautifulsoup4 4.6.0 py36h72d3c9f_1 bitarray 0.8.1 py36h20fa61d_0 bkcharts 0.2 py36h073222e_0 blaze 0.11.3 py36h02e7a37_0 bleach 2.0.0 py36h8fcea71_0 bokeh 0.12.10 py36hfd5be35_0 boto 2.48.0 py36hdbc59ac_1 bottleneck 1.2.1 py36hbd380ad_0 branca 0.2.0 py_1 conda-forge bzip2 1.0.6 h92991f9_1 ca-certificates 2018.1.18 0 conda-forge cartopy 0.16.0 py36he7b4726_0 certifi 2018.1.18 py36_0 conda-forge cffi 1.10.0 py36h880867e_1 chardet 3.0.4 py36h96c241c_1 click 6.7 py36hec950be_0 cloudpickle 0.4.0 py36h13b7e56_0 clyent 1.2.2 py36hae3ad88_0 cmocean 1.1 py_0 conda-forge colorama 0.3.9 py36hd29a30c_0 colorspacious 1.1.0 conda 4.3.34 py36_0 conda-forge conda-build 3.0.27 py36hb78d8cd_0 conda-env 2.6.0 h36134e3_0 conda-verify 2.0.0 py36he837df3_0 contextlib2 0.5.5 py36hd66e5e7_0 cryptography 2.0.3 py36h22d4226_1 curl 7.49.0 1 cycler 0.10.0 py36hfc81398_0 cython 0.26.1 py36hd51f8eb_0 cytoolz 0.8.2 py36h290905f_0 dask 0.15.3 py36hc3ad2d6_0 dask-core 0.15.3 py36hc0be6b7_0 datashape 0.5.4 py36hfb22df8_0 dbus 1.10.22 h50d9ad6_0 decorator 4.1.2 py36h69a1b52_0 distributed 1.19.1 py36h4ae75d2_0 docutils 0.14 py36hbfde631_0 entrypoints 0.2.3 py36hd81d71f_2 et_xmlfile 1.0.1 py36h1315bdc_0 expat 2.2.4 h8f26bf8_1 fastcache 1.0.2 py36h8606a76_0 filelock 2.0.12 py36h0d0b4fb_0 flask 0.12.2 py36h5658096_0 flask-cors 3.0.3 py36h7387b97_0 folium 0.5.0 py_0 conda-forge freetype 2.8 h143eb01_0 geos 3.6.2 h5470d99_2 get_terminal_size 1.0.0 h7520d66_0 gettext 0.19.8.1 hb0f4f8b_2 gevent 1.2.2 py36ha70b9d6_0 glib 2.53.6 ha08cb78_1 glob2 0.5 py36h12393a9_0 gmp 6.1.2 h4a9834d_0 gmpy2 2.0.8 py36h7ef02cb_1 greenlet 0.4.12 py36hf09ba7b_0 h5py 2.7.0 np113py36_0 hdf4 4.2.13 h39711bb_2 hdf5 1.8.17 2 heapdict 1.0.0 py36h27a9ac6_0 html5lib 0.999999999 py36h79312fd_0 icu 58.2 hea21ae5_0 idna 2.6 py36h8628d0a_1 imageio 2.2.0 py36h5e01289_0 imagesize 0.7.1 py36h3495948_0 intel-openmp 2018.0.0 h68bdfb3_7 ipykernel 4.6.1 py36h3208c25_0 ipython 6.2.1 py36h3dda519_1 anaconda ipython_genutils 0.2.0 py36h241746c_0 ipywidgets 7.0.0 py36h24d3910_0 isort 4.2.15 py36hceb2a01_0 itsdangerous 0.24 py36h49fbb8d_1 jbig 2.1 h4d881f8_0 jdcal 1.3 py36h1986823_0 jedi 0.10.2 py36h6325097_0 jinja2 2.9.6 py36hde4beb4_1 jpeg 9b haccd157_1 jsonschema 2.6.0 py36hb385e00_0 jupyter 1.0.0 py36h598a6cc_0 jupyter_client 5.1.0 py36hf6c435f_0 jupyter_console 5.2.0 py36hccf5b1c_1 jupyter_core 4.3.0 py36h93810fe_0 jupyterlab 0.27.0 py36hd3092eb_2 jupyterlab_launcher 0.4.0 py36h93e02e9_0 lazy-object-proxy 1.3.1 py36h2fbbe47_0 libcxx 4.0.1 h579ed51_0 libcxxabi 4.0.1 hebd6815_0 libedit 3.1 hb4e282d_0 libffi 3.2.1 hd939716_3 libgfortran 3.0.1 h93005f0_2 libiconv 1.15 h99df5da_5 libnetcdf 4.4.1 1 libpng 1.6.32 hce72d48_2 libsodium 1.0.13 hba5e272_2 libssh2 1.8.0 h1218725_2 libtiff 4.0.8 h8cd0352_9 libxml2 2.9.4 hbd0960b_5 libxslt 1.1.29 h95a2935_5 llvmlite 0.20.0 py36_0 locket 0.2.0 py36hca03003_1 logzero 1.3.1 lxml 4.1.0 py36h8179fc0_0 lzo 2.10 hb6b8854_1 markupsafe 1.0 py36h3a1e703_1 matplotlib 2.1.0 py36h5068139_0 mccabe 0.6.1 py36hdaeb55d_0 mistune 0.7.4 py36hccd6237_0 mkl 2018.0.0 h5ef208c_6 mkl-service 1.1.2 py36h7ea6df4_4 mpc 1.0.3 hc455b36_4 mpfr 3.1.5 h7fa3772_1 mpmath 0.19 py36h9185fea_2 msgpack-python 0.4.8 py36h46767b2_0 multipledispatch 0.4.9 py36hc5f92b5_0 navigator-updater 0.1.0 py36h7aee5fb_0 nbconvert 5.3.1 py36h810822e_0 nbformat 4.4.0 py36h827af21_0 ncurses 6.0 ha932d30_1 netcdf4 1.2.4 np113py36_1 networkx 2.0 py36hefccab9_0 nltk 3.2.4 py36h27d1ea0_0 nose 1.3.7 py36h73fae2b_2 notebook 5.0.0 py36h462289e_2 numba 0.35.0 np113py36_6 numexpr 2.6.2 py36h0f4f1da_1 numpy 1.13.3 py36h2cdce51_0 numpydoc 0.7.0 py36he54d08e_0 odo 0.5.1 py36hc1af34a_0 olefile 0.44 py36ha08bf50_0 openpyxl 2.4.8 py36he899640_1 openssl 1.0.2n 0 conda-forge owslib 0.15.0 py36hf35f654_0 packaging 16.8 py36he5e8135_0 pandas 0.20.3 py36hd6655d8_2 pandoc 1.19.2.1 ha5e8f32_1 pandocfilters 1.4.2 py36h3b0b094_1 partd 0.3.8 py36hf5c4cb8_0 path.py 10.3.1 py36hd33c240_0 pathlib2 2.3.0 py36h877a6d8_0 patsy 0.4.1 py36ha1b3fa5_0 pcre 8.41 h29eefc5_0 pep8 1.7.0 py36hc268eb1_0 pexpect 4.2.1 py36h3eac828_0 pickleshare 0.7.4 py36hf512f8e_0 pillow 4.2.1 py36h0263179_0 pip 9.0.1 py36hbd95645_3 pkginfo 1.4.1 py36h25bf955_0 ply 3.10 py36h10e714e_0 powerline-shell 0.4.7 proj4 4.9.3 h3f1bf9d_7 prompt_toolkit 1.0.15 py36haeda067_0 psutil 5.4.0 py36ha052210_0 ptyprocess 0.5.2 py36he6521c3_0 py 1.4.34 py36hecf431b_1 pycodestyle 2.3.1 py36h83e8646_0 pycosat 0.6.3 py36hee92d8f_0 pycparser 2.18 py36h724b2fc_1 pycrypto 2.6.1 py36h72f2894_1 pycurl 7.43.0 py36_0 pyepsg 0.3.2 py36hba2fa79_0 pyflakes 1.6.0 py36hea45e83_0 pygments 2.2.0 py36h240cd3f_0 pylint 1.7.4 py36hdee9077_0 pyodbc 4.0.17 py36h5478161_0 pyopenssl 17.2.0 py36h5d7bf08_0 pyparsing 2.2.0 py36hb281f35_0 pyproj 1.9.5.1 py36_0 pyqt 5.6.0 py36he5c6137_6 pyshp 1.2.12 py36h78922a7_0 pysocks 1.6.7 py36hfa33cec_1 pytables 3.4.2 np113py36_0 pytest 3.2.1 py36h9963153_1 python 3.6.3 h6804ab2_0 python-dateutil 2.6.1 py36h86d2abb_1 python.app 2 py36h7fe2238_6 pytz 2017.2 py36h2e7dfbc_1 pywavelets 0.5.2 py36h2710a04_0 pyyaml 3.12 py36h2ba1e63_1 pyzmq 16.0.2 py36h087ffad_2 qt 5.6.2 h9975529_14 qtawesome 0.4.4 py36h468c6fb_0 qtconsole 4.3.1 py36hd96c0ff_0 qtpy 1.3.1 py36h16bb863_0 readline 7.0 h81b24a6_3 requests 2.18.4 py36h4516966_1 rope 0.10.5 py36h5764ad1_0 ruamel_yaml 0.11.14 py36h9d7ade0_2 scikit-image 0.13.0 py36h398857d_1 scikit-learn 0.19.1 py36hffbff8c_0 scipy 0.19.1 py36h3e758e1_3 seaborn 0.8.0 py36h74df97e_0 setuptools 36.5.0 py36h2134326_0 shapely 1.6.2 py36hed20685_0 simplegeneric 0.8.1 py36he5b5b09_0 singledispatch 3.4.0.3 py36hf20db9d_0 sip 4.18.1 py36h2824476_2 six 1.11.0 py36h0e22d5e_1 snowballstemmer 1.2.1 py36h6c7b616_0 sortedcollections 0.5.3 py36he9c3ed6_0 sortedcontainers 1.5.7 py36ha982688_0 sphinx 1.6.3 py36hcd1b3e7_0 sphinxcontrib 1.0 py36h9364dc8_1 sphinxcontrib-websupport 1.0.1 py36h92f4a7a_1 spyder 3.2.4 py36h908396f_0 sqlalchemy 1.1.13 py36h156b851_0 sqlite 3.20.1 h900c3b0_1 statsmodels 0.8.0 py36h9c68fc9_0 sympy 1.1.1 py36h7f3cf04_0 tblib 1.3.2 py36hda67792_0 terminado 0.6 py36h656782e_0 testpath 0.3.1 py36h625a49b_0 tk 8.6.7 hcdce994_1 toolz 0.8.2 py36h7b95164_0 tornado 4.5.2 py36h468dda9_0 traitlets 4.3.2 py36h65bd3ce_0 typing 3.6.2 py36haa2d9ef_0 unicodecsv 0.14.1 py36he531d66_0 unixodbc 2.3.4 h4cb4dde_1 urllib3 1.22 py36h68b9469_0 vega 0.4.4 py36_1 conda-forge vincent 0.4.4 py36_0 conda-forge wcwidth 0.1.7 py36h8c6ec74_0 webencodings 0.5.1 py36h3b9701d_1 werkzeug 0.12.2 py36h168efa1_0 wheel 0.29.0 py36h3597b6d_1 widgetsnbextension 3.0.2 py36h91f43ea_1 wrapt 1.10.11 py36hc29e774_0 xarray 0.10.1 py36_0 xlrd 1.1.0 py36h336f4a2_1 xlsxwriter 1.0.2 py36h3736301_0 xlwings 0.11.4 py36hc75f156_0 xlwt 1.2.0 py36h5ad1178_0 xz 5.2.3 ha24016e_1 yaml 0.1.7 hff548bb_1 yapf 0.20.1 zeromq 4.2.2 h131e0f7_1 zict 0.1.3 py36h71da714_0 zlib 1.2.11 h60db283_1 ``` ### pip list ``` alabaster (0.7.10) altair (1.2.1) anaconda-client (1.6.5) anaconda-navigator (1.6.9) anaconda-project (0.8.0) appnope (0.1.0) appscript (1.0.1) asn1crypto (0.22.0) astroid (1.5.3) astropy (2.0.2) autopep8 (1.3.4) Babel (2.5.0) backports.shutil-get-terminal-size (1.0.0) beautifulsoup4 (4.6.0) bitarray (0.8.1) bkcharts (0.2) blaze (0.11.3) bleach (2.0.0) bokeh (0.12.10) boto (2.48.0) Bottleneck (1.2.1) branca (0.2.0) Cartopy (0.16.0) certifi (2018.1.18) cffi (1.10.0) chardet (3.0.4) click (6.7) cloudpickle (0.4.0) clyent (1.2.2) cmocean (1.1) colorama (0.3.9) colorspacious (1.1.0) conda (4.3.34) conda-build (3.0.27) conda-verify (2.0.0) contextlib2 (0.5.5) cryptography (2.0.3) cycler (0.10.0) Cython (0.26.1) cytoolz (0.8.2) dask (0.15.3) datashape (0.5.4) decorator (4.1.2) distributed (1.19.1) docutils (0.14) entrypoints (0.2.3) et-xmlfile (1.0.1) fastcache (1.0.2) filelock (2.0.12) Flask-Cors (3.0.3) folium (0.5.0) gevent (1.2.2) glob2 (0.5) gmpy2 (2.0.8) greenlet (0.4.12) h5py (2.7.0) heapdict (1.0.0) html5lib (0.999999999) idna (2.6) imageio (2.2.0) imagesize (0.7.1) ipykernel (4.6.1) ipython (6.2.1) ipython-genutils (0.2.0) ipywidgets (7.0.0) isort (4.2.15) itsdangerous (0.24) jdcal (1.3) jedi (0.10.2) Jinja2 (2.9.6) jsonschema (2.6.0) jupyter-client (5.1.0) jupyter-console (5.2.0) jupyter-core (4.3.0) jupyterlab (0.27.0) jupyterlab-launcher (0.4.0) lazy-object-proxy (1.3.1) llvmlite (0.20.0) locket (0.2.0) logzero (1.3.1) lxml (4.1.0) MarkupSafe (1.0) matplotlib (2.1.0) mccabe (0.6.1) mistune (0.7.4) mpmath (0.19) msgpack-python (0.4.8) multipledispatch (0.4.9) navigator-updater (0.1.0) nbconvert (5.3.1) nbformat (4.4.0) netCDF4 (1.2.4) networkx (2.0) nltk (3.2.4) nose (1.3.7) notebook (5.0.0) numba (0.35.0+6.gaa35fb1) numexpr (2.6.2) numpy (1.13.3) numpydoc (0.7.0) odo (0.5.1) olefile (0.44) openpyxl (2.4.8) OWSLib (0.15.0) packaging (16.8) pandas (0.20.3) pandocfilters (1.4.2) partd (0.3.8) path.py (10.3.1) pathlib2 (2.3.0) patsy (0.4.1) pep8 (1.7.0) pexpect (4.2.1) pickleshare (0.7.4) Pillow (4.2.1) pip (9.0.1) pkginfo (1.4.1) ply (3.10) powerline-shell (0.4.7) prompt-toolkit (1.0.15) psutil (5.4.0) ptyprocess (0.5.2) py (1.4.34) pycodestyle (2.3.1) pycosat (0.6.3) pycparser (2.18) pycrypto (2.6.1) pycurl (7.43.0) pyepsg (0.3.2) pyflakes (1.6.0) Pygments (2.2.0) pylint (1.7.4) pyodbc (4.0.17) pyOpenSSL (17.2.0) pyparsing (2.2.0) pyproj (1.9.5.1) pyshp (1.2.12) PySocks (1.6.7) pytest (3.2.1) python-dateutil (2.6.1) pytz (2017.2) PyWavelets (0.5.2) PyYAML (3.12) pyzmq (16.0.2) QtAwesome (0.4.4) qtconsole (4.3.1) QtPy (1.3.1) requests (2.18.4) rope (0.10.5) ruamel-yaml (0.11.14) scikit-image (0.13.0) scikit-learn (0.19.1) scipy (0.19.1) seaborn (0.8) setuptools (36.5.0.post20170921) Shapely (1.6.2) simplegeneric (0.8.1) singledispatch (3.4.0.3) six (1.11.0) snowballstemmer (1.2.1) sortedcollections (0.5.3) sortedcontainers (1.5.7) Sphinx (1.6.3) sphinxcontrib-websupport (1.0.1) spyder (3.2.4) SQLAlchemy (1.1.13) statsmodels (0.8.0) sympy (1.1.1) tables (3.4.2) tblib (1.3.2) terminado (0.6) testpath (0.3.1) toolz (0.8.2) tornado (4.5.2) traitlets (4.3.2) typing (3.6.2) unicodecsv (0.14.1) urllib3 (1.22) vega (0.4.4) vincent (0.4.4) wcwidth (0.1.7) webencodings (0.5.1) Werkzeug (0.12.2) wheel (0.29.0) widgetsnbextension (3.0.2) wrapt (1.10.11) xarray (0.10.1) xlrd (1.1.0) XlsxWriter (1.0.2) xlwings (0.11.4) xlwt (1.2.0) yapf (0.20.1) zict (0.1.3) ```
andy-watson commented 6 years ago

Try adding interpolation='none' to the add_image() call, I believe without this matplotlib resamples (possibly not very well as seen) the image to fit the axis.

From the matplotlib documentation (for imshow) this only works for Agg, ps and pdf backends, although I also saw improvements using svg backend when running in Jupyter.

stefanomattia commented 6 years ago

Unfortunately interpolation='none' doesn't seem to change anything, I can't tell any discernible difference in the output image. But it's weird though, am I the only Mac user having this problem?

ajdawson commented 6 years ago

I see the same as you on Mac and Linux. I don't have the solution, but this is not an isolated case.

andy-watson commented 6 years ago

Maybe I've got a slightly different use case in that I was primarily producing PDF files from matplotlib (on a Mac, using Homebrew Python installation) viewing the results with preview or notebook with inline svg.

Your example producing bitmap output (PNG) yes I get noticeable pixelation, there was some improvement using interpolation='bicubic', but still not looking as sharp as original tiles.

I think it is down to add_image() ultimately calling matplotlib imshow() and if the axes pixels are not the same size as the tiles pixels there is going to be a re-sampling of the tile image. In PDF output case I suspect this re-sampling is left to the viewer to perform.

pelson commented 6 years ago

For reference, I've provided a link to the StackOverflow question.

The interpolation keyword is definitely the way to go here. I agree that the bicubic option leads to somewhat fuzzy / less-sharp images. Trying one of the other schemes, such as spline36 has quite a pleasing effect. Take a look at https://matplotlib.org/gallery/images_contours_and_fields/interpolation_methods.html for the options that are available to you.

For future readers who find this and who don't see the benefit from the interpolation keyword, it is probably because you are re-projecting the tiles into the target projection which is happening before matplotlib gets any influence on the scheme. There isn't a lot we can do with this, as cartopy is using a general 3d interpolation scheme in order to support the full diversity of the projections it can handle. Your best option for poor resolution cross projection map tiles is to change the resolution of the re-projection. A question about how to do this came up on StackOverflow not too long ago: [I can't currently find the link :cry: :crystal_ball: ) (there are also others: 1).

To give a concrete example of this, take the following code:

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt

extent = [-25, 25, 10, 55]

request = cimgt.OSM()

fig, ax = plt.subplots(figsize=(6, 6))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent(extent, ccrs.PlateCarree())

ax.add_image(request, 4)

ax.coastlines('50m')
plt.show()

step1

If we simply add the interpolation keyword to the add_image call, we don't get the best possible result:

step2

Instead, we also need to control how coarse/fine the target regridded image should be with the regrid_shape keyword argument. An example add_image might then look like: ax.add_image(request, 4, interpolation='spline36', regrid_shape=2000)

step3

pelson commented 6 years ago

I think this addresses the issue at hand, so will close. Happy to re-open this if the issue isn't resolved for you.

Thanks!

Huite commented 6 years ago

Pardon the nagging: while the interpolation options results in a much better looking result, for me the real question is why cartopy in 2015 didn't require this, while it does now.

The example in the StackOverflow post was posted June 22, 2015. The cartopy version would've been 0.11, 0.12.04, or 0.12.05, judging from the PyPI history.

There's three changes visible:

  1. Different tiles (both OSM and Google updated)
  2. Grid spacing from 0.15 to 0.10 degrees
  3. 2015 version looks "crisp" by default

Could difference 3 be a case of regression?

I had a try at installing an earlier cartopy version to see if I could reproduce the 2015 example, but unfortunately, the earliest version on conda is 0.15.1 (which is also "not-crisp" by default). PyPI does have the earlier versions, but not surprisingly, installing a package from 2015 fails miserably. I'm not nearly familiar enough with the cartopy source to investigate from that direction...

Having good looking tiles is a really cool and convenient feature, so it might be worth another look?

ajdawson commented 6 years ago

Could this issue be because Cartopy is accessing Google tiles through a deprecated interface that is no longer supported/allowed: https://developers.google.com/maps/faq#tos_tiles ?

pelson commented 5 years ago

The simple answer I think is that the default interpolation changed in matplotlib. I have some things lined up to improve the tiles interface further for v0.18 (interactive & auto zoom). Implementing a better interpolation scheme by default is going to be important for that functionality too.

JavierRuano commented 5 years ago

You could change the figsize=(50, 50)