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__))