jwass / mplleaflet

Easily convert matplotlib plots from Python into interactive Leaflet web maps.
BSD 3-Clause "New" or "Revised" License
521 stars 76 forks source link

contourf error #40

Open quai20 opened 7 years ago

quai20 commented 7 years ago

Hi, Really great work it's an amazing tool. I managed to make the contour example work and one of my contour plot also. But when I switch the plot to contourf (wich is ok with my plt.show()), mplleaflet crashes with this error :

Traceback (most recent call last):
  File "readnc_kb.py", line 37, in <module>
    mplleaflet.show(path='test.html')
  File "/usr/lib/python2.7/site-packages/mplleaflet/_display.py", line 180, in show
    save_html(fig, fileobj=f, **kwargs)
  File "/usr/lib/python2.7/site-packages/mplleaflet/_display.py", line 131, in save_html
    html = fig_to_html(fig, **kwargs)
  File "/usr/lib/python2.7/site-packages/mplleaflet/_display.py", line 84, in fig_to_html
    exporter.run(fig)
  File "/usr/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.py", line 51, in run
    self.crawl_fig(fig)
  File "/usr/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.py", line 118, in crawl_fig
    self.crawl_ax(ax)
  File "/usr/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.py", line 140, in crawl_ax
    self.draw_collection(ax, collection)
  File "/usr/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.py", line 272, in draw_collection
    mplobj=collection)
  File "/usr/lib/python2.7/site-packages/mplleaflet/mplexporter/renderers/base.py", line 272, in draw_path_collection
    mplobj=mplobj)
  File "/usr/lib/python2.7/site-packages/mplleaflet/leaflet_renderer.py", line 125, in draw_path
    rings = list(iter_rings(data, pathcodes))
  File "/usr/lib/python2.7/site-packages/mplleaflet/utils.py", line 14, in iter_rings
    raise ValueError('Unrecognized code: {}'.format(code))
ValueError: Unrecognized code: Z

Any ideas ?

jwass commented 7 years ago

Hi @quai20 - can you provide a short example demonstrating this?

quai20 commented 7 years ago

of course, here's my example :

`#READING from netCDF4 import Dataset import numpy as np

my_nc_file = 'NRTOAGL01_20150315_fld_TEMP.nc' fh = Dataset(my_nc_file, mode='r')

LON=fh.variables['longitude'][:] LAT=fh.variables['latitude'][:] TEMP=fh.variables['TEMP'][:] TEMP=TEMP[0,0,:,:] fh.close()

PLOTTING

import matplotlib.pyplot as plt import mplleaflet

contouring

xx, yy = np.meshgrid(LON, LAT)

THIS WORKS WITH plt.show() AND MPLLEAFLET

aa = plt.contour(xx, yy, TEMP,50)

THIS WORKS WITH plt.show() BUT CRASHES WITH MPLLEAFLET (error above)

aa = plt.contourf(xx, yy, TEMP,50)

plt.show()

mplleaflet.show(path='test.html') `

The data file is here if needed : https://cloud.ifremer.fr/index.php/s/moxThfGrVPQaHWi

eazytracer commented 7 years ago

I am running into the same issue now. I can plot the contour just fine, contourf works with pyplot, but gives the following error with mplleaflet.

ValueErrorTraceback (most recent call last)
<ipython-input-149-87023e0e107a> in <module>()
----> 1 mplleaflet.show()

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/_display.pyc in show(fig, path, **kwargs)
    178     fullpath = os.path.abspath(path)
    179     with open(fullpath, 'w') as f:
--> 180         save_html(fig, fileobj=f, **kwargs)
    181     webbrowser.open('file://' + fullpath)

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/_display.pyc in save_html(fig, fileobj, **kwargs)
    129     if not hasattr(fileobj, 'write'):
    130         raise ValueError("fileobj should be a filename or a writable file")
--> 131     html = fig_to_html(fig, **kwargs)
    132     fileobj.write(html)
    133     fileobj.close()

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/_display.pyc in fig_to_html(fig, template, tiles, crs, epsg, embed_links)
     82     renderer = LeafletRenderer(crs=crs, epsg=epsg)
     83     exporter = Exporter(renderer)
---> 84     exporter.run(fig)
     85 
     86     attribution = _attribution + ' | ' + tiles[1]

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.pyc in run(self, fig)
     49             import matplotlib.pyplot as plt
     50             plt.close(fig)
---> 51         self.crawl_fig(fig)
     52 
     53     @staticmethod

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.pyc in crawl_fig(self, fig)
    116                                        props=utils.get_figure_properties(fig)):
    117             for ax in fig.axes:
--> 118                 self.crawl_ax(ax)
    119 
    120     def crawl_ax(self, ax):

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.pyc in crawl_ax(self, ax)
    138                 self.draw_patch(ax, patch)
    139             for collection in ax.collections:
--> 140                 self.draw_collection(ax, collection)
    141             for image in ax.images:
    142                 self.draw_image(ax, image)

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/mplexporter/exporter.pyc in draw_collection(self, ax, collection, force_pathtrans, force_offsettrans)
    270                                            offset_order=offset_order,
    271                                            styles=styles,
--> 272                                            mplobj=collection)
    273 
    274     def draw_image(self, ax, image):

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/mplexporter/renderers/base.pyc in draw_path_collection(self, paths, path_coordinates, path_transforms, offsets, offset_coordinates, offset_order, styles, mplobj)
    270                            pathcodes=pathcodes, style=style, offset=offset,
    271                            offset_coordinates=offset_coordinates,
--> 272                            mplobj=mplobj)
    273 
    274     def draw_markers(self, data, coordinates, style, label, mplobj=None):

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/leaflet_renderer.pyc in draw_path(self, data, coordinates, pathcodes, style, offset, offset_coordinates, mplobj)
    123             else:
    124                 data = [c.tolist() for c in data]
--> 125             rings = list(iter_rings(data, pathcodes))
    126 
    127             if style['facecolor'] != 'none':

/Users/midgetracer/miniconda3/envs/iris_lab/lib/python2.7/site-packages/mplleaflet/utils.pyc in iter_rings(data, pathcodes)
     12             ring.append(point)
     13         else:
---> 14             raise ValueError('Unrecognized code: {}'.format(code))
     15 
     16     if len(ring):

ValueError: Unrecognized code: Z

Code:

import mplleaflet
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.pyplot as plt
import pygrib
import single_point

files = ["gfs.t12z.pgrb2.0p25.f006",
         "hrrr.t23z.wrfsfcf02.grib2",
         "hrrr.t01z.wrfsfcf06.grib2"]

hrrr = pygrib.open(files[2])

hrrr.seek(0)
ref = hrrr.select(name="Maximum/Composite radar reflectivity")[0]
radar = ref.values
lats, lons = ref.latlons()

clevs = [20,30,40,50,60,70]
cmap = LinearSegmentedColormap.from_list("my_colormap",                    
                                        [[0.063, 0.306, 0.545],
                                        [0.000, 0.804, 0.000],
                                        [0.933, 0.933, 0.000],
                                        [0.804, 0.000, 0.000],
                                        [0.000, 1.000, 1.000]],
                                        N=5,
                                        gamma=1.0)

cs = plt.contourf(lons, lats, radar, clevs, cmap=cmap)

#plt.show()

mplleaflet.show()

Seems as if a ring isn't completed and is throwing an error perhaps?

contourf plotting with pyplot

screen shot 2017-07-02 at 10 35 46 am

contour plotting with mplleaflet

screen shot 2017-07-02 at 10 17 33 am
disarticulate commented 7 years ago

Got the same error. Added this to the code to bypass it.

elif code == 'L' or code == 'Z' or code == 'S':
anderl80 commented 6 years ago

The same issue here for me..

YaSergo commented 6 years ago

@anderl80 check link above: https://github.com/jwass/mplleaflet/issues/45 There you can found solution of problem. If you still have problems just ask.

@jwass Maybe it's time to make changes in the code? =)

techkuz commented 6 years ago

@jwass , please make changes in the code

Agnpalm commented 6 years ago

Is this being worked on? I have almost the exact same problem. I'm trying to plot rings, and I get the error Unrecognized code: C

For reference, this is what it looks like in matplotlib: image

And this is the error:

ValueError                                Traceback (most recent call last)
<ipython-input-65-bae575a61e87> in <module>()
     19 ax.set_ylim([27.5, 50])
     20 
---> 21 mplleaflet.show(fig = ax.figure)
     22 # plt.show()

c:\python36\lib\site-packages\mplleaflet\_display.py in show(fig, path, **kwargs)
    178     fullpath = os.path.abspath(path)
    179     with open(fullpath, 'w') as f:
--> 180         save_html(fig, fileobj=f, **kwargs)
    181     webbrowser.open('file://' + fullpath)

c:\python36\lib\site-packages\mplleaflet\_display.py in save_html(fig, fileobj, **kwargs)
    129     if not hasattr(fileobj, 'write'):
    130         raise ValueError("fileobj should be a filename or a writable file")
--> 131     html = fig_to_html(fig, **kwargs)
    132     fileobj.write(html)
    133     fileobj.close()

c:\python36\lib\site-packages\mplleaflet\_display.py in fig_to_html(fig, template, tiles, crs, epsg, embed_links)
     82     renderer = LeafletRenderer(crs=crs, epsg=epsg)
     83     exporter = Exporter(renderer)
---> 84     exporter.run(fig)
     85 
     86     attribution = _attribution + ' | ' + tiles[1]

c:\python36\lib\site-packages\mplleaflet\mplexporter\exporter.py in run(self, fig)
     49             import matplotlib.pyplot as plt
     50             plt.close(fig)
---> 51         self.crawl_fig(fig)
     52 
     53     @staticmethod

c:\python36\lib\site-packages\mplleaflet\mplexporter\exporter.py in crawl_fig(self, fig)
    116                                        props=utils.get_figure_properties(fig)):
    117             for ax in fig.axes:
--> 118                 self.crawl_ax(ax)
    119 
    120     def crawl_ax(self, ax):

c:\python36\lib\site-packages\mplleaflet\mplexporter\exporter.py in crawl_ax(self, ax)
    138                 self.draw_patch(ax, patch)
    139             for collection in ax.collections:
--> 140                 self.draw_collection(ax, collection)
    141             for image in ax.images:
    142                 self.draw_image(ax, image)

c:\python36\lib\site-packages\mplleaflet\mplexporter\exporter.py in draw_collection(self, ax, collection, force_pathtrans, force_offsettrans)
    270                                            offset_order=offset_order,
    271                                            styles=styles,
--> 272                                            mplobj=collection)
    273 
    274     def draw_image(self, ax, image):

c:\python36\lib\site-packages\mplleaflet\mplexporter\renderers\base.py in draw_path_collection(self, paths, path_coordinates, path_transforms, offsets, offset_coordinates, offset_order, styles, mplobj)
    270                            pathcodes=pathcodes, style=style, offset=offset,
    271                            offset_coordinates=offset_coordinates,
--> 272                            mplobj=mplobj)
    273 
    274     def draw_markers(self, data, coordinates, style, label, mplobj=None):

c:\python36\lib\site-packages\mplleaflet\leaflet_renderer.py in draw_path(self, data, coordinates, pathcodes, style, offset, offset_coordinates, mplobj)
    123             else:
    124                 data = [c.tolist() for c in data]
--> 125             rings = list(iter_rings(data, pathcodes))
    126 
    127             if style['facecolor'] != 'none':

c:\python36\lib\site-packages\mplleaflet\utils.py in iter_rings(data, pathcodes)
     12             ring.append(point)
     13         else:
---> 14             raise ValueError('Unrecognized code: {}'.format(code))
     15 
     16     if len(ring):

ValueError: Unrecognized code: C

And this is the code I'm using to generate it:

import matplotlib.pyplot as plt
from matplotlib.patches import Wedge
from matplotlib.collections import PatchCollection
import mplleaflet

lon = -77.036451
lat = 38.897503

patches = []
ring = Wedge((lon, lat), 10, 0, 360, width=4)
patches.append(ring)

p = PatchCollection(patches, alpha=0.6)

fig, ax = plt.subplots()
ax.add_collection(p)
ax.set_xlim([-87.5, -65])
ax.set_ylim([27.5, 50])

# mplleaflet.show(fig = ax.figure)
plt.show()

The solution given above does not work, since the C code is a Bezier Curve, and treating it like the L command gives a weird shape: image

If Matplotlib is using Bezier curves it's probably a good idea for mplleaflet to support them as well.

Edit: I did some light debugging. The pathcodes that gets passed to iter_rings in my case is:

'M', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'M', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'M', 'Z'

and the data is this (abbreviated):

[[-67.036451, 38.897503], [-67.036451, 40.210690968073855], [-67.29512039592409, 41.51110983760738], [-67.79765567488712, 42.7243373236509], [-68.30019095385018, 43.93756480969442], [-69.03681907093691, 45.040006694667866]

The main thing to notice is that the first command is the M command (Move to), which takes two parameters. The C command however, takes three sets of pairs as parameters, yet the data (point in the for loop in iter_rings) only contains two parameters.

You can also note that the Z command doesn't take any arguments at all, but the workaround above seems to work anyway since the data is probably an empty list, although I haven't checked. Similarly, the workaround above includes the S command, but that's also a type of Bezier curve and it will most probably produce the wrong result.

I tried changing iter_rings to add a different number of parameters depending on what command it was processing, but that didn't work. I suspect that either there's something wrong with my code, or whatever code is consuming the output of iter_rings doesn't expect data in anything else than pairs, or both. Anyway, this is my non functioning code:

def iter_rings(data, pathcodes):
    ring = []
    i = 0
    # TODO: Do this smartly by finding when pathcodes changes value and do
    # smart indexing on data, instead of iterating over each coordinate
    for code in pathcodes:
        if code == 'M':
            # Emit the path and start a new one
            if len(ring):
                yield ring
            ring = [data[i:i+2]]
            i += 2
        elif code == 'L':
            ring.append(point)
        elif code == 'Z':
            ring.append([])
        elif code == 'C':
            params = []
            params.extend(data[i:i+2])
            params.extend(',')
            i += 2
            params.extend(data[i:i+2])
            params.extend(',')
            i += 2
            params.extend(data[i:i+2])
            i += 2
            ring.append(params)
        else:
            raise ValueError('Unrecognized code: {}'.format(code))

    if len(ring):
        yield ring

When I look at the HTML output it seems as if the data gets divided into pairs somewhere anyway:

{"type": "Polygon", "coordinates": [[[[-67.036451, 38.897503], [-67.036451, 40.210690968073855]], [[-67.29512039592409, 41.51110983760738], [-67.79765567488712, 42.7243373236509], ",", [-68.30019095385018, 43.93756480969442], [-69.03681907093691, 45.040006694667866], ",", [-69.96538318813452, 45.968570811865476], [-70.89394730533213, 46.897134929063085]], [[-71.99638919030558, 47.633763046149824], [-73.2096166763491, 48.13629832511287], ",", [-74.42284416239262, 48.63883360407591], [-75.72326303192614, 48.897503], ",",
 ... and so on
jwass commented 6 years ago

@disarticulate Can you post a pull request with the change to fix this?

disarticulate commented 6 years ago

ask and you shall receive https://github.com/jwass/mplleaflet/pull/51

jwass commented 6 years ago

@disarticulate You rock! Thanks. I'll try to push a new release next few days.

Agnpalm commented 6 years ago

I hope I don't offend anyone, but https://github.com/jwass/mplleaflet/pull/51 is not a fix to this issue. I left a comment there as well, but I thought my lengthy comment above in this issue explained pretty well what was wrong. The main issue that should be addressed is that SVG contain codes that take a different amount of parameters, not only 2 like L. With https://github.com/jwass/mplleaflet/pull/51 you will still have issues with cirlces and other shapes that use Bezier paths. In my example above the SVG that was emitted contains C and Z codes, neither of which will work correctly.