Skip to content

Commit c3ede82

Browse files
author
Martin Journois
committed
Fix test_image_overlay
1 parent 4c1515d commit c3ede82

5 files changed

Lines changed: 283 additions & 150 deletions

File tree

folium/features.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from jinja2 import Template
99
import json
1010

11-
from .utilities import color_brewer, _parse_size, legend_scaler, _locations_mirror, _locations_tolist
11+
from .utilities import color_brewer, _parse_size, legend_scaler, _locations_mirror, _locations_tolist, write_png,\
12+
mercator_transform
1213

1314
from .element import Element, Figure, JavascriptLink, CssLink, Div, MacroElement
1415
from .map import Map, TileLayer, Icon, Marker, Popup
@@ -535,3 +536,74 @@ def __init__(self, locations, color=None, weight=None, opacity=None, latlon=True
535536
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
536537
{% endmacro %}
537538
""")
539+
540+
class ImageOverlay(MacroElement):
541+
def __init__(self, image, bounds, opacity=1., attribution=None, origin='upper', colormap=None, mercator_project=False):
542+
"""Used to load and display a single image over specific bounds of the map, implements ILayer interface
543+
544+
Parameters
545+
----------
546+
image: string, file or array-like object
547+
The data you want to draw on the map.
548+
* If string, it will be written directly in the output file.
549+
* If file, it's content will be converted as embeded in the output file.
550+
* If array-like, it will be converted to PNG base64 string and embeded in the output.
551+
bounds: list
552+
Image bounds on the map in the form [[lat_min, lon_min], [lat_max, lon_max]]
553+
opacity: float, default Leaflet's default (1.0)
554+
attr: string, default Leaflet's default ("")
555+
origin : ['upper' | 'lower'], optional, default 'upper'
556+
Place the [0,0] index of the array in the upper left or lower left
557+
corner of the axes.
558+
559+
colormap : callable, used only for `mono` image.
560+
Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)]
561+
for transforming a mono image into RGB.
562+
It must output iterables of length 3 or 4, with values between 0. and 1.
563+
Hint : you can use colormaps from `matplotlib.cm`.
564+
565+
mercator_project : bool, default False, used only for array-like image.
566+
Transforms the data to project (longitude,latitude) coordinates to the Mercator projection.
567+
"""
568+
super(ImageOverlay, self).__init__()
569+
self._name = 'ImageOverlay'
570+
571+
if hasattr(image,'read'):
572+
# We got an image file.
573+
if hasattr(image,'name'):
574+
# we try to get the image format from the file name.
575+
fileformat = image.name.lower().split('.')[-1]
576+
else:
577+
fileformat = 'png'
578+
self.url = "data:image/{};base64,{}".format(fileformat,
579+
image.read().encode('base64'))
580+
elif hasattr(image,'__iter__'):
581+
# We got an array-like object
582+
if mercator_project:
583+
data = mercator_transform(image,
584+
[bounds[0][0], bounds[1][0]],
585+
origin=origin)
586+
else:
587+
data = image
588+
self.url = "data:image/png;base64," +write_png(data, origin=origin, colormap=colormap).encode('base64')
589+
else:
590+
# We got an url
591+
self.url = json.loads(json.dumps(image))
592+
593+
self.url = self.url.replace('\n',' ')
594+
self.bounds = json.loads(json.dumps(bounds))
595+
options = {
596+
'opacity': opacity,
597+
'attribution': attribution,
598+
}
599+
self.options = json.dumps({key:val for key,val in options.items() if val},
600+
sort_keys=True)
601+
self._template = Template(u"""
602+
{% macro script(this, kwargs) %}
603+
var {{this.get_name()}} = L.imageOverlay(
604+
'{{ this.url }}',
605+
{{ this.bounds }},
606+
{{ this.options }}
607+
).addTo({{this._parent.get_name()}});
608+
{% endmacro %}
609+
""")

folium/folium.py

Lines changed: 76 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
from .element import Element, Figure, JavascriptLink, CssLink, Div, MacroElement
2727
from .map import Map, TileLayer, Icon, Marker, Popup, FitBounds
2828
from .features import WmsTileLayer, RegularPolygonMarker, Vega, GeoJson, GeoJsonStyle, MarkerCluster, DivIcon,\
29-
CircleMarker, LatLngPopup, ClickForMarker, ColorScale, TopoJson, PolyLine, MultiPolyLine
30-
from .utilities import color_brewer
29+
CircleMarker, LatLngPopup, ClickForMarker, ColorScale, TopoJson, PolyLine, MultiPolyLine, ImageOverlay
30+
from .utilities import color_brewer, write_png
3131
#import sys
3232
#import base64
3333

@@ -907,96 +907,80 @@ def geo_json(self, geo_path=None, geo_str=None, data_out='data.json',
907907
self.add_children(geo_json)
908908
self.add_children(color_scale)
909909

910-
# @iter_obj('image_overlay')
911-
# def image_overlay(self, data, opacity=0.25, min_lat=-90.0, max_lat=90.0,
912-
# min_lon=-180.0, max_lon=180.0, image_name=None,
913-
# filename=None):
914-
# """
915-
# Simple image overlay of raster data from a numpy array. This is a
916-
# lightweight way to overlay geospatial data on top of a map. If your
917-
# data is high res, consider implementing a WMS server and adding a WMS
918-
# layer.
919-
#
920-
# This function works by generating a PNG file from a numpy array. If
921-
# you do not specify a filename, it will embed the image inline.
922-
# Otherwise, it saves the file in the current directory, and then adds
923-
# it as an image overlay layer in leaflet.js. By default, the image is
924-
# placed and stretched using bounds that cover the entire globe.
925-
#
926-
# Parameters
927-
# ----------
928-
# data: numpy array OR url string, required.
929-
# if numpy array, must be a image format,
930-
# i.e., NxM (mono), NxMx3 (rgb), or NxMx4 (rgba)
931-
# if url, must be a valid url to a image (local or external)
932-
# opacity: float, default 0.25
933-
# Image layer opacity in range 0 (transparent) to 1 (opaque)
934-
# min_lat: float, default -90.0
935-
# max_lat: float, default 90.0
936-
# min_lon: float, default -180.0
937-
# max_lon: float, default 180.0
938-
# image_name: string, default None
939-
# The name of the layer object in leaflet.js
940-
# filename: string, default None
941-
# Optional file name of output.png for image overlay.
942-
# Use `None` for inline PNG.
943-
#
944-
# Output
945-
# ------
946-
# Image overlay data layer in obj.template_vars
947-
#
948-
# Examples
949-
# -------
950-
# # assumes a map object `m` has been created
951-
# >>> import numpy as np
952-
# >>> data = np.random.random((100,100))
953-
#
954-
# # to make a rgba from a specific matplotlib colormap:
955-
# >>> import matplotlib.cm as cm
956-
# >>> cmapper = cm.cm.ColorMapper('jet')
957-
# >>> data2 = cmapper.to_rgba(np.random.random((100,100)))
958-
# >>> # Place the data over all of the globe (will be pretty pixelated!)
959-
# >>> m.image_overlay(data)
960-
# >>> # Put it only over a single city (Paris).
961-
# >>> m.image_overlay(data, min_lat=48.80418, max_lat=48.90970,
962-
# ... min_lon=2.25214, max_lon=2.44731)
963-
#
964-
# """
965-
#
966-
# if isinstance(data, str):
967-
# filename = data
968-
# else:
969-
# try:
970-
# png_str = utilities.write_png(data)
971-
# except Exception as e:
972-
# raise e
973-
#
974-
# if filename is not None:
975-
# with open(filename, 'wb') as fd:
976-
# fd.write(png_str)
977-
# else:
978-
# png = "data:image/png;base64,{}".format
979-
# filename = png(base64.b64encode(png_str).decode('utf-8'))
980-
#
981-
# if image_name not in self.added_layers:
982-
# if image_name is None:
983-
# image_name = "Image_Overlay"
984-
# else:
985-
# image_name = image_name.replace(" ", "_")
986-
# image_url = filename
987-
# image_bounds = [[min_lat, min_lon], [max_lat, max_lon]]
988-
# image_opacity = opacity
989-
#
990-
# image_temp = self.env.get_template('image_layer.js')
991-
#
992-
# image = image_temp.render({'image_name': image_name,
993-
# 'image_url': image_url,
994-
# 'image_bounds': image_bounds,
995-
# 'image_opacity': image_opacity})
996-
#
997-
# self.template_vars['image_layers'].append(image)
998-
# self.added_layers.append(image_name)
999-
#
910+
def image_overlay(self, data, opacity=0.25, min_lat=-90.0, max_lat=90.0,
911+
min_lon=-180.0, max_lon=180.0, origin='upper', colormap=None,
912+
image_name=None, filename=None, mercator_project=False):
913+
"""
914+
Simple image overlay of raster data from a numpy array. This is a
915+
lightweight way to overlay geospatial data on top of a map. If your
916+
data is high res, consider implementing a WMS server and adding a WMS
917+
layer.
918+
919+
This function works by generating a PNG file from a numpy array. If
920+
you do not specify a filename, it will embed the image inline.
921+
Otherwise, it saves the file in the current directory, and then adds
922+
it as an image overlay layer in leaflet.js. By default, the image is
923+
placed and stretched using bounds that cover the entire globe.
924+
925+
Parameters
926+
----------
927+
data: numpy array OR url string, required.
928+
if numpy array, must be a image format,
929+
i.e., NxM (mono), NxMx3 (rgb), or NxMx4 (rgba)
930+
if url, must be a valid url to a image (local or external)
931+
opacity: float, default 0.25
932+
Image layer opacity in range 0 (transparent) to 1 (opaque)
933+
min_lat: float, default -90.0
934+
max_lat: float, default 90.0
935+
min_lon: float, default -180.0
936+
max_lon: float, default 180.0
937+
image_name: string, default None
938+
The name of the layer object in leaflet.js
939+
filename: string, default None
940+
Optional file name of output.png for image overlay.
941+
Use `None` for inline PNG.
942+
origin : ['upper' | 'lower'], optional, default 'upper'
943+
Place the [0,0] index of the array in the upper left or lower left
944+
corner of the axes.
945+
946+
colormap : callable, used only for `mono` image.
947+
Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)]
948+
for transforming a mono image into RGB.
949+
It must output iterables of length 3 or 4, with values between 0. and 1.
950+
Hint : you can use colormaps from `matplotlib.cm`.
951+
952+
mercator_project : bool, default False, used only for array-like image.
953+
Transforms the data to project (longitude,latitude) coordinates to the Mercator projection.
954+
Output
955+
------
956+
Image overlay data layer in obj.template_vars
957+
958+
Examples
959+
-------
960+
# assumes a map object `m` has been created
961+
>>> import numpy as np
962+
>>> data = np.random.random((100,100))
963+
964+
# to make a rgba from a specific matplotlib colormap:
965+
>>> import matplotlib.cm as cm
966+
>>> cmapper = cm.cm.ColorMapper('jet')
967+
>>> data2 = cmapper.to_rgba(np.random.random((100,100)))
968+
>>> # Place the data over all of the globe (will be pretty pixelated!)
969+
>>> m.image_overlay(data)
970+
>>> # Put it only over a single city (Paris).
971+
>>> m.image_overlay(data, min_lat=48.80418, max_lat=48.90970,
972+
... min_lon=2.25214, max_lon=2.44731)
973+
974+
"""
975+
if filename:
976+
image = write_png(data, origin=origin, colormap=colormap)
977+
open(filename,'wb').write(image)
978+
data = filename
979+
980+
self.add_children(ImageOverlay(data, [[min_lat, min_lon],[max_lat,max_lon]],
981+
opacity=opacity, origin=origin, colormap=colormap,
982+
mercator_project=mercator_project))
983+
1000984
# def _build_map(self, html_templ=None, templ_type='string'):
1001985
# self._auto_bounds()
1002986
# """Build HTML/JS/CSS from Templates given current map type."""

folium/templates/image_layer.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
var {{ image_name }} = L.imageOverlay('{{ image_url }}', {{ image_bounds }}).addTo(map).setOpacity({{ image_opacity }});
1+
var {{ this.get_name() }} = L.imageOverlay(
2+
'{{ image_url }}',
3+
{{ image_bounds }}
4+
{% if image_opacity %}, {"opacity" : {{ image_opacity }} } {% endif %}
5+
).addTo({{ this._parent.get_name() }});

0 commit comments

Comments
 (0)