diff --git a/examples/ImageOverlay.ipynb b/examples/ImageOverlay.ipynb new file mode 100644 index 0000000000..3a4545e30d --- /dev/null +++ b/examples/ImageOverlay.ipynb @@ -0,0 +1,422 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# How to use ImageOverlay" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It may happen that you want to draw an image on you map. Here are example on how to do that." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0,'..')\n", + "import folium\n", + "from folium import plugins" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## In importing a PNG\n", + "\n", + "If you have a static image file on your disk, you can simply draw it on the map in doing:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map([37,0], zoom_start=1, tiles='stamentoner')\n", + "\n", + "plugins.ImageOverlay(open('Mercator_projection_SW.png', 'br'),\n", + " [[-82,-180],[82,180]],\n", + " opacity=0.8,\n", + " ).add_to(m)\n", + "\n", + "folium.LayerControl().add_to(m)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A few remarks:\n", + "\n", + "* Note that your image has to be in Mercator projection format.\n", + "\n", + " The image we've used is based on https://en.wikipedia.org/wiki/File:Mercator_projection_SW.jpg ; that you can find in wikipedia's article on Mercator Projection (https://en.wikipedia.org/wiki/Mercator_projection).\n", + "\n", + "\n", + "* In python 3, be careful to mention explictely the `read mode` of the file (`open(..., 'rb')`). You'll get an error otherwise. \n", + "\n", + "You can also provide simply a file name. In this case, the image will not be embedded in folium's output : you'll need to keep it on your server when your map will be deployed." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map([37,0], zoom_start=1, tiles='stamentoner')\n", + "\n", + "plugins.ImageOverlay('Mercator_projection_SW.png',\n", + " [[-82,-180],[82,180]],\n", + " opacity=0.8,\n", + " ).add_to(m)\n", + "\n", + "folium.LayerControl().add_to(m)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## In importing a JPG" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This works exactly the same way if you want to put a JPG intead of a PNG." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map([37,0], zoom_start=1, tiles='stamentoner')\n", + "\n", + "plugins.ImageOverlay(open('Mercator_projection_SW.jpg', 'br'),\n", + " [[-82,-180],[82,180]],\n", + " opacity=0.8,\n", + " ).add_to(m)\n", + "\n", + "folium.LayerControl().add_to(m)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## In creating an image with numpy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you may wish to create your own image, based on another python computation.\n", + "For this, you'll need to have numpy installed on your machine." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's creater an image to draw a rectangle in the bounds [[0,-60],[60,60]]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "image = np.zeros((61,61))\n", + "image[0,:] = 1.\n", + "image[60,:] = 1.\n", + "image[:,0] = 1.\n", + "image[:,60] = 1." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can draw it on the map in using:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map([37,0], zoom_start=3)\n", + "\n", + "plugins.ImageOverlay(image,\n", + " [[0,-60],[60,60]],\n", + " colormap=lambda x: (1,0,0,x),\n", + " ).add_to(m)\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that you need to provide a colormap of the form `lambda x: (R,G,B,A)` where `R,G,B,A` are floats between 0 and 1." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's try to add a line at latitude 45°, and add a polyline to verify it's well rendered. We'll need to specify `origin='lower` to inform folium that the first lines of the array are to be plotted at the bottom of the image (see `numpy.imshow`, it's the same principle)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image[45,:] = 1.\n", + "\n", + "\n", + "m = folium.Map([37,0], zoom_start=3)\n", + "\n", + "plugins.ImageOverlay(image,\n", + " [[0,-60],[60,60]],\n", + " colormap=lambda x: (1,0,0,x),\n", + " origin='lower',\n", + " ).add_to(m)\n", + "\n", + "folium.PolyLine([[45,-60],[45,60]]).add_to(m)\n", + "m\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But even with `origin='lower'`, the red line is not at the good latitude. This is due to Mercator projection used in Leaflet (and most other map systems).\n", + "\n", + "You can read wikipedia's article on Mercator Projection (https://en.wikipedia.org/wiki/Mercator_projection), or simply let folium do the job, in precising that you want the mercator stuff to be handled." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + " \n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = folium.Map([37,0], zoom_start=3)\n", + "\n", + "folium.PolyLine([[45,-60],[45,60]]).add_to(m)\n", + "\n", + "plugins.ImageOverlay(image,\n", + " [[0,-60],[60,60]],\n", + " origin='lower',\n", + " colormap=lambda x: (1,0,0,x),\n", + " mercator_project=True,\n", + " ).add_to(m)\n", + "\n", + "m" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time, the lines are properly positionned (at the precision of the array)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "That's all folks !\n", + "\n", + "Hope it'll be useful to you. Don't hesitate to provide a feedback on what can be improved, which method do you prefer, etc." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.4.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/Mercator_projection_SW.jpg b/examples/Mercator_projection_SW.jpg new file mode 100644 index 0000000000..532fc69238 Binary files /dev/null and b/examples/Mercator_projection_SW.jpg differ diff --git a/examples/Mercator_projection_SW.png b/examples/Mercator_projection_SW.png new file mode 100644 index 0000000000..0124a1e11c Binary files /dev/null and b/examples/Mercator_projection_SW.png differ diff --git a/folium/__init__.py b/folium/__init__.py index e240e1097b..08cfd66960 100644 --- a/folium/__init__.py +++ b/folium/__init__.py @@ -10,7 +10,7 @@ Popup, TileLayer) from folium.features import (ClickForMarker, ColorScale, CustomIcon, DivIcon, - GeoJson, ImageOverlay, LatLngPopup, + GeoJson, LatLngPopup, MarkerCluster, MultiPolyLine, PolyLine, Vega, RegularPolygonMarker, TopoJson, WmsTileLayer) @@ -30,7 +30,6 @@ 'DivIcon', 'GeoJson', 'GeoJsonStyle', - 'ImageOverlay', 'LatLngPopup', 'MarkerCluster', 'MultiPolyLine', diff --git a/folium/features.py b/folium/features.py index f1e99a64fd..0213beebf4 100644 --- a/folium/features.py +++ b/folium/features.py @@ -13,24 +13,22 @@ text_type, binary_type) from .element import Element, Figure, JavascriptLink, CssLink, MacroElement -from .map import TileLayer, Icon, Marker, Popup +from .map import Layer, Icon, Marker, Popup - -class WmsTileLayer(TileLayer): +class WmsTileLayer(Layer): def __init__(self, url, name=None, format=None, layers=None, transparent=True, - attr=None, overlay=True): + attr=None, overlay=True, control=True): """ TODO docstring here """ - super(TileLayer, self).__init__() + super(WmsTileLayer, self).__init__(overlay=overlay, control=control) self._name = 'WmsTileLayer' self.tile_name = name if name is not None else 'WmsTileLayer_'+self._id self.url = url self.format = format self.layers = layers - self.overlay = overlay self.transparent = transparent self.attr = attr @@ -49,7 +47,6 @@ def __init__(self, url, name=None, {% endmacro %} """) # noqa - class RegularPolygonMarker(Marker): def __init__(self, location, color='black', opacity=1, weight=2, fill_color='blue', fill_opacity=1, @@ -336,16 +333,16 @@ def render(self, **kwargs): name='d3') -class MarkerCluster(MacroElement): +class MarkerCluster(Layer): """Adds a MarkerCluster layer on the map.""" - def __init__(self): + def __init__(self, overlay=True, control=True): """Creates a MarkerCluster element to append into a map with Map.add_children. Parameters ---------- """ - super(MarkerCluster, self).__init__() + super(MarkerCluster, self).__init__(overlay=overlay, control=control) self._name = 'MarkerCluster' self._template = Template(u""" {% macro script(this, kwargs) %} @@ -608,69 +605,6 @@ def __init__(self, locations, color=None, weight=None, {% endmacro %} """) # noqa - -class ImageOverlay(MacroElement): - def __init__(self, image, bounds, opacity=1., attr=None, - origin='upper', colormap=None, mercator_project=False): - """ - Used to load and display a single image over specific bounds of - the map, implements ILayer interface. - - Parameters - ---------- - image: string, file or array-like object - The data you want to draw on the map. - * If string, it will be written directly in the output file. - * If file, it's content will be converted as embedded in the - output file. - * If array-like, it will be converted to PNG base64 string - and embedded in the output. - bounds: list - Image bounds on the map in the form [[lat_min, lon_min], - [lat_max, lon_max]] - opacity: float, default Leaflet's default (1.0) - attr: string, default Leaflet's default ("") - origin : ['upper' | 'lower'], optional, default 'upper' - Place the [0,0] index of the array in the upper left or - lower left corner of the axes. - colormap : callable, used only for `mono` image. - Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)] - for transforming a mono image into RGB. - It must output iterables of length 3 or 4, - with values between 0 and 1. - Hint : you can use colormaps from `matplotlib.cm`. - mercator_project : bool, default False. - Used only for array-like image. Transforms the data to - project (longitude, latitude) coordinates to the - Mercator projection. - - """ - super(ImageOverlay, self).__init__() - self._name = 'ImageOverlay' - - self.url = image_to_url(image, origin=origin, - mercator_project=mercator_project, - bounds=bounds) - - self.bounds = json.loads(json.dumps(bounds)) - options = { - 'opacity': opacity, - 'attribution': attr, - } - self.options = json.dumps({key: val for key, val - in options.items() if val}, - sort_keys=True) - self._template = Template(u""" - {% macro script(this, kwargs) %} - var {{this.get_name()}} = L.imageOverlay( - '{{ this.url }}', - {{ this.bounds }}, - {{ this.options }} - ).addTo({{this._parent.get_name()}}); - {% endmacro %} - """) - - class CustomIcon(Icon): def __init__(self, icon_image, icon_size=None, icon_anchor=None, shadow_image=None, shadow_size=None, shadow_anchor=None, diff --git a/folium/folium.py b/folium/folium.py index 4195510400..536aa312ce 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -18,9 +18,8 @@ from .features import (WmsTileLayer, RegularPolygonMarker, Vega, GeoJson, CircleMarker, LatLngPopup, ClickForMarker, ColorScale, TopoJson, PolyLine, - MultiPolyLine, ImageOverlay) -from .utilities import color_brewer, write_png - + MultiPolyLine) +from .utilities import color_brewer def initialize_notebook(): """Initialize the IPython notebook display elements.""" @@ -686,6 +685,11 @@ def image_overlay(self, data, opacity=0.25, min_lat=-90.0, max_lat=90.0, ... min_lon=2.25214, max_lon=2.44731) """ + warnings.warn('This method is deprecated. Please use `Map.add_children(' + 'folium.plugins.ImageOverlay(...))` instead.') + from .plugins import ImageOverlay + from .utilities import write_png + if filename: image = write_png(data, origin=origin, colormap=colormap) open(filename, 'wb').write(image) diff --git a/folium/map.py b/folium/map.py index f44cd95b03..1df4f5a231 100644 --- a/folium/map.py +++ b/folium/map.py @@ -168,24 +168,43 @@ def add_tile_layer(self, tiles='OpenStreetMap', name=None, detect_retina=detect_retina) self.add_children(tile_layer, name=tile_layer.tile_name) +class Layer(MacroElement): + """An abstract class for everything that is a Layer on the map. + It will be used to define whether an object will be included in LayerControls. + """ + def __init__(self, name=None, overlay=False, control=True): + """Creates a Layer instance. -class TileLayer(MacroElement): + Parameters + ---------- + name : string, default None + The name of the Layer, as it will appear in LayerControls + overlay : bool, default False + Whether the layer is optional (overlay) or compulsory. + control : bool, default True + Whether the Layer will be included in LayerControls + """ + super(Layer, self).__init__() + self.layer_name = name if name is not None else self.get_name() + self.overlay = overlay + self.control = control + +class TileLayer(Layer): def __init__(self, tiles='OpenStreetMap', name=None, min_zoom=1, max_zoom=18, attr=None, API_key=None, - overlay=False, detect_retina=False): + overlay=False, control=True, detect_retina=False): """TODO docstring here Parameters ---------- """ - super(TileLayer, self).__init__() - self._name = 'TileLayer' self.tile_name = (name if name is not None else ''.join(tiles.lower().strip().split())) + super(TileLayer, self).__init__(name=self.tile_name, overlay=overlay, control=control) + self._name = 'TileLayer' self.min_zoom = min_zoom self.max_zoom = max_zoom - self.overlay = overlay self.detect_retina = detect_retina self.tiles = ''.join(tiles.lower().strip().split()) @@ -225,8 +244,8 @@ def __init__(self, tiles='OpenStreetMap', name=None, """) -class FeatureGroup(TileLayer): - def __init__(self, name=None, overlay=True): +class FeatureGroup(Layer): + def __init__(self, name=None, overlay=True, control=True): """ Create a FeatureGroup layer ; you can put things in it and handle them as a single layer. For example, you can add a LayerControl to @@ -242,13 +261,11 @@ def __init__(self, name=None, overlay=True): Whether your layer will be an overlay (ticked with a check box in LayerControls) or a base layer (ticked with a radio button). """ - super(TileLayer, self).__init__() + super(FeatureGroup, self).__init__(overlay=overlay, control=control) self._name = 'FeatureGroup' self.tile_name = name if name is not None else self.get_name() - self.overlay = overlay - self._template = Template(u""" {% macro script(this, kwargs) %} var {{this.get_name()}} = L.featureGroup( @@ -286,18 +303,22 @@ def __init__(self): def render(self, **kwargs): """TODO : docstring here.""" + # We select all Layers for which (control and not overlay). self.base_layers = OrderedDict( - [(val.tile_name, val.get_name()) for key, val in - self._parent._children.items() if - isinstance(val, TileLayer) and not hasattr(val, 'overlay')]) + [(val.layer_name, val.get_name()) for key, val in + self._parent._children.items() if isinstance(val, Layer) + and (not hasattr(val, 'overlay') or not val.overlay) + and (not hasattr(val, 'control') or val.control) + ]) + # We select all Layers for which (control and overlay). self.overlays = OrderedDict( - [(val.tile_name, val.get_name()) for key, val in - self._parent._children.items() if - isinstance(val, TileLayer) and hasattr(val, 'overlay')]) - + [(val.layer_name, val.get_name()) for key, val in + self._parent._children.items() if isinstance(val, Layer) + and (hasattr(val, 'overlay') and val.overlay) + and (not hasattr(val, 'control') or val.control) + ]) super(LayerControl, self).render() - class Icon(MacroElement): def __init__(self, color='blue', icon_color='white', icon='info-sign', angle=0, prefix='glyphicon'): diff --git a/folium/plugins/__init__.py b/folium/plugins/__init__.py index a7487ddf30..f6e877e357 100644 --- a/folium/plugins/__init__.py +++ b/folium/plugins/__init__.py @@ -12,10 +12,13 @@ from .boat_marker import BoatMarker from .timestamped_geo_json import TimestampedGeoJson from .heat_map import HeatMap +from .image_overlay import ImageOverlay __all__ = ['MarkerCluster', 'ScrollZoomToggler', 'Terminator', 'BoatMarker', 'TimestampedGeoJson', - 'HeatMap'] + 'HeatMap', + 'ImageOverlay', + ] diff --git a/folium/plugins/image_overlay.py b/folium/plugins/image_overlay.py new file mode 100644 index 0000000000..ce470959a4 --- /dev/null +++ b/folium/plugins/image_overlay.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +""" +Image Overlay +------------- + +Used to load and display a single image over specific bounds of +the map, implements ILayer interface. + +""" +import json +from jinja2 import Template + +from folium.utilities import image_to_url +from folium.map import Layer + +def mercator_transform(data, lat_bounds, origin='upper', height_out=None): + """Transforms an image computed in (longitude,latitude) coordinates into + the a Mercator projection image. + + Parameters + ---------- + + data: numpy array or equivalent list-like object. + Must be NxM (mono), NxMx3 (RGB) or NxMx4 (RGBA) + + lat_bounds : length 2 tuple + Minimal and maximal value of the latitude of the image. + Bounds must be between -85.051128779806589 and 85.051128779806589 + otherwise they will be clipped to that values. + + origin : ['upper' | 'lower'], optional, default 'upper' + Place the [0,0] index of the array in the upper left or lower left + corner of the axes. + + height_out : int, default None + The expected height of the output. + If None, the height of the input is used. + + See https://en.wikipedia.org/wiki/Web_Mercator for more details. + """ + import numpy as np + + mercator = lambda x: np.arcsinh(np.tan(x*np.pi/180.))*180./np.pi + + array = np.atleast_3d(data).copy() + height, width, nblayers = array.shape + + lat_min = max(lat_bounds[0], -85.051128779806589) + lat_max = min(lat_bounds[1], 85.051128779806589) + if height_out is None: + height_out = height + + # Eventually flip the image + if origin == 'upper': + array = array[::-1, :, :] + + lats = (lat_min + np.linspace(0.5/height, 1.-0.5/height, height) * + (lat_max-lat_min)) + latslats = (mercator(lat_min) + + np.linspace(0.5/height_out, 1.-0.5/height_out, height_out) * + (mercator(lat_max)-mercator(lat_min))) + + out = np.zeros((height_out, width, nblayers)) + for i in range(width): + for j in range(nblayers): + out[:, i, j] = np.interp(latslats, mercator(lats), array[:, i, j]) + + # Eventually flip the image. + if origin == 'upper': + out = out[::-1, :, :] + return out + +class ImageOverlay(Layer): + def __init__(self, image, bounds, opacity=1., attr=None, + origin='upper', colormap=None, mercator_project=False, + overlay=True, control=True): + """ + Used to load and display a single image over specific bounds of + the map, implements ILayer interface. + + Parameters + ---------- + image: string, file or array-like object + The data you want to draw on the map. + * If string, it will be written directly in the output file. + * If file, it's content will be converted as embedded in the + output file. + * If array-like, it will be converted to PNG base64 string + and embedded in the output. + bounds: list + Image bounds on the map in the form [[lat_min, lon_min], + [lat_max, lon_max]] + opacity: float, default Leaflet's default (1.0) + attr: string, default Leaflet's default ("") + origin : ['upper' | 'lower'], optional, default 'upper' + Place the [0,0] index of the array in the upper left or + lower left corner of the axes. + colormap : callable, used only for `mono` image. + Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)] + for transforming a mono image into RGB. + It must output iterables of length 3 or 4, + with values between 0 and 1. + Hint : you can use colormaps from `matplotlib.cm`. + mercator_project : bool, default False. + Used only for array-like image. Transforms the data to + project (longitude, latitude) coordinates to the + Mercator projection. + Beware that this will only work if `image` is an array-like + object. + """ + super(ImageOverlay, self).__init__(overlay=overlay, control=control) + self._name = 'ImageOverlay' + self.overlay = overlay + + if mercator_project: + image = mercator_transform(image, + [bounds[0][0], bounds[1][0]], + origin=origin) + + self.url = image_to_url(image, origin=origin, colormap=colormap) + + self.bounds = json.loads(json.dumps(bounds)) + options = { + 'opacity': opacity, + 'attribution': attr, + } + self.options = json.dumps({key: val for key, val + in options.items() if val}, + sort_keys=True) + self._template = Template(u""" + {% macro script(this, kwargs) %} + var {{this.get_name()}} = L.imageOverlay( + '{{ this.url }}', + {{ this.bounds }}, + {{ this.options }} + ).addTo({{this._parent.get_name()}}); + {% endmacro %} + """) diff --git a/folium/utilities.py b/folium/utilities.py index 1f8807287a..7a33b47364 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -221,64 +221,6 @@ def color_brewer(color_code, n=6): color_scheme = schemes.get(color_code, None) return color_scheme - -def transform_data(data): - """ - Transform Pandas DataFrame into JSON format. - - Parameters - ---------- - data: DataFrame or Series - Pandas DataFrame or Series - - Returns - ------- - JSON compatible dict - - Example - ------- - >>> transform_data(df) - - """ - if pd is None: - raise ImportError("The Pandas package is required" - " for this functionality") - - if np is None: - raise ImportError("The NumPy package is required" - " for this functionality") - - def type_check(value): - """ - Type check values for JSON serialization. Native Python JSON - serialization will not recognize some Numpy data types properly, - so they must be explicitly converted. - - """ - if pd.isnull(value): - return None - elif (isinstance(value, pd.tslib.Timestamp) or - isinstance(value, pd.Period)): - return time.mktime(value.timetuple()) - elif isinstance(value, (int, np.integer)): - return int(value) - elif isinstance(value, (float, np.float_)): - return float(value) - elif isinstance(value, str): - return str(value) - else: - return value - - if isinstance(data, pd.Series): - json_data = [{type_check(x): type_check(y) for - x, y in iteritems(data)}] - elif isinstance(data, pd.DataFrame): - json_data = [{type_check(y): type_check(z) for - x, y, z in data.itertuples()}] - - return json_data - - def split_six(series=None): """ Given a Pandas Series, get a domain of values from zero to the 90% quantile @@ -313,64 +255,7 @@ def base(x): arr = series.values return [base(np.percentile(arr, x)) for x in quants] - -def mercator_transform(data, lat_bounds, origin='upper', height_out=None): - """Transforms an image computed in (longitude,latitude) coordinates into - the a Mercator projection image. - - Parameters - ---------- - - data: numpy array or equivalent list-like object. - Must be NxM (mono), NxMx3 (RGB) or NxMx4 (RGBA) - - lat_bounds : length 2 tuple - Minimal and maximal value of the latitude of the image. - - origin : ['upper' | 'lower'], optional, default 'upper' - Place the [0,0] index of the array in the upper left or lower left - corner of the axes. - - height_out : int, default None - The expected height of the output. - If None, the height of the input is used. - """ - if np is None: - raise ImportError("The NumPy package is required" - " for this functionality") - - mercator = lambda x: np.arcsinh(np.tan(x*np.pi/180.))*180./np.pi - - array = np.atleast_3d(data).copy() - height, width, nblayers = array.shape - - lat_min, lat_max = lat_bounds - if height_out is None: - height_out = height - - # Eventually flip the image - if origin == 'upper': - array = array[::-1, :, :] - - lats = (lat_min + np.linspace(0.5/height, 1.-0.5/height, height) * - (lat_max-lat_min)) - latslats = (mercator(lat_min) + - np.linspace(0.5/height_out, 1.-0.5/height_out, height_out) * - (mercator(lat_max)-mercator(lat_min))) - - out = np.zeros((height_out, width, nblayers)) - for i in range(width): - for j in range(4): - out[:, i, j] = np.interp(latslats, mercator(lats), array[:, i, j]) - - # Eventually flip the image. - if origin == 'upper': - out = out[::-1, :, :] - return out - - -def image_to_url(image, mercator_project=False, colormap=None, - origin='upper', bounds=((-90, -180), (90, 180))): +def image_to_url(image, colormap=None, origin='upper'): """Infers the type of an image argument and transforms it into a URL. Parameters @@ -389,13 +274,6 @@ def image_to_url(image, mercator_project=False, colormap=None, for transforming a mono image into RGB. It must output iterables of length 3 or 4, with values between 0. and 1. Hint : you can use colormaps from `matplotlib.cm`. - mercator_project : bool, default False, used for array-like image. - Transforms the data to project (longitude,latitude) - coordinates to the Mercator projection. - bounds: list-like, default ((-90, -180), (90, 180)) - Image bounds on the map in the form - [[lat_min, lon_min], [lat_max, lon_max]]. - Only used if mercator_project is True. """ if hasattr(image, 'read'): # We got an image file. @@ -409,13 +287,7 @@ def image_to_url(image, mercator_project=False, colormap=None, elif (not (isinstance(image, text_type) or isinstance(image, binary_type))) and hasattr(image, '__iter__'): # We got an array-like object. - if mercator_project: - data = mercator_transform(image, - [bounds[0][0], bounds[1][0]], - origin=origin) - else: - data = image - png = write_png(data, origin=origin, colormap=colormap) + png = write_png(image, origin=origin, colormap=colormap) url = "data:image/png;base64," + base64.b64encode(png).decode('utf-8') else: # We got an URL. @@ -423,7 +295,6 @@ def image_to_url(image, mercator_project=False, colormap=None, return url.replace('\n', ' ') - def write_png(data, origin='upper', colormap=None): """ Transform an array of data into a PNG string. diff --git a/tests/test_folium.py b/tests/test_folium.py index de5262fbc1..925e9eba25 100644 --- a/tests/test_folium.py +++ b/tests/test_folium.py @@ -22,8 +22,8 @@ from folium.six import PY3 from folium.map import Popup, Marker, FitBounds, FeatureGroup from folium.features import (GeoJson, ColorScale, TopoJson, PolyLine, - MultiPolyLine, ImageOverlay) - + MultiPolyLine) +from folium.plugins import ImageOverlay rootpath = os.path.abspath(os.path.dirname(__file__))