Skip to content

Commit 41e8726

Browse files
committed
Merge pull request #191 from ocefpaf/image_overlay_defaults
docstring and defaults
2 parents 195d61b + a7fd814 commit 41e8726

File tree

3 files changed

+69
-60
lines changed

3 files changed

+69
-60
lines changed

folium/folium.py

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -936,71 +936,82 @@ def json_style(style_cnt, line_color, line_weight, line_opacity,
936936
self.template_vars.setdefault('gjson_layers', []).append(layer)
937937

938938
@iter_obj('image_overlay')
939-
def image_overlay(self, data, opacity=0.75, min_lat=-90.0, max_lat=90.0,
939+
def image_overlay(self, data, image_opacity=1, min_lat=-90.0, max_lat=90.0,
940940
min_lon=-180.0, max_lon=180.0, image_name=None,
941-
filename=None, data_projection='mercator'):
942-
"""Simple image overlay of raster data from a numpy array. This is a
943-
lightweight way to overlay geospatial data on top of a map.
944-
If your data is high res, consider implementing a WMS server
945-
and adding a WMS layer.
946-
947-
This function works by generating a PNG file from a numpy
948-
array. If you do not specify a filename, it will embed the
949-
image inline. Otherwise, it saves the file in the current
950-
directory, and then adds it as an image overlay layer in
951-
leaflet.js. By default, the image is placed and stretched
952-
using bounds that cover the entire globe. By default, we
953-
assume that your data is in geodetic projection and thus
954-
project it to web mercator for display purposes. If you are
955-
overlaying a non-georeferenced image, set data_projection to
956-
None.
941+
filename=None):
942+
"""
943+
Simple image overlay of raster `data` from an image or a NumPy array.
944+
This is a lightweight way to overlay geospatial data on top of a map.
945+
The default lon, lat corners are the whole globe. Adjust it to your
946+
image.
947+
948+
If `data` is is a NumPy array a PNG file will be created for you.
949+
If you do not specify a filename the image will be embedded inline.
950+
951+
NOTE: The image must be projected as Web Mercator
952+
(https://en.wikipedia.org/wiki/Web_Mercator). See the examples below
953+
for re-projection.
957954
958955
Parameters
959956
----------
960-
data: numpy array OR url string, required.
961-
if numpy array, must be a image format,
962-
i.e., NxM (mono), NxMx3 (RGB), or NxMx4 (RGBA)
963-
if url, must be a valid url to a image (local or external)
964-
opacity: float, default 0.75
965-
Image layer opacity in range 0 (transparent) to 1 (opaque)
966-
min_lat: float, default -90.0
967-
max_lat: float, default 90.0
968-
min_lon: float, default -180.0
969-
max_lon: float, default 180.0
957+
data: NumPy array OR image.
958+
The NumPy array can NxM (mono), NxMx3 (RGB), or NxMx4 (RGBA)
959+
image_opacity: float, default 1
960+
Image layer image_opacity in range 0 (transparent) to 1 (opaque)
961+
min_lat: float (default: -90)
962+
max_lat: float (default: 90)
963+
min_lon: float (default: -180)
964+
max_lon: float (default" 180)
970965
image_name: string, default None
971966
The name of the layer object in leaflet.js
972967
filename: string or None, default None
973968
Optional file name of output.png for image overlay.
974969
If None, we use a inline PNG.
975-
data_projection: string or None, default 'mercator'
976-
Used to specify projection of image. If None, do no projection
977970
978971
Output
979972
------
980973
Image overlay data layer in obj.template_vars
981974
982975
Examples
983976
-------
984-
# Assumes a map object `m` has been created.
985977
>>> import numpy as np
986-
>>> data = np.random.random((180,360))
987-
988-
# Place the data over all of the globe (will be pretty pixelated!)
989-
>>> m.image_overlay(data)
990-
991-
# Put it only over a single city (Paris)
992-
>>> m.image_overlay(data, min_lat=48.80418, max_lat=48.90970,
993-
... min_lon=2.25214, max_lon=2.44731)
978+
>>> import matplotlib
979+
>>> def sample_data(shape=(73, 145)):
980+
... nlats, nlons = shape
981+
... lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
982+
... lons = np.linspace(0, 2 * np.pi, nlons)
983+
... lons, lats = np.meshgrid(lons, lats)
984+
... wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
985+
... mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
986+
... lats = np.rad2deg(lats)
987+
... lons = np.rad2deg(lons)
988+
... data = wave + mean
989+
... return lons, lats, data
990+
>>> # Lets create some data,
991+
>>> lon, lat, data = sample_data(shape=(73, 145))
992+
>>> lon -= 180
993+
>>> # and color it.
994+
>>> cm = matplotlib.cm.get_cmap('cubehelix')
995+
>>> normed_data = (data - data.min()) / (data.max() - data.min())
996+
>>> colored_data = cm(normed_data)
997+
>>> # First no projection (wrong).
998+
>>> map = folium.Map(location=[lat.mean(), lon.mean()], zoom_start=1)
999+
>>> map.image_overlay(colored_data,
1000+
... min_lat=lat.min(), max_lat=lat.max(),
1001+
... min_lon=lon.min(), max_lon=lon.max(),
1002+
... image_opacity=0.25)
1003+
>>> # Now lets project the data as to Web Mercator (correct).
1004+
>>> map = folium.Map(location=[lat.mean(), lon.mean()], zoom_start=1)
1005+
>>> project = geodetic_to_mercator(colored_data)
1006+
>>> map.image_overlay(projected,
1007+
... min_lat=lat.min(), max_lat=lat.max(),
1008+
... min_lon=lon.min(), max_lon=lon.max(),
1009+
... image_opacity=0.25)
9941010
9951011
"""
9961012
if isinstance(data, str):
9971013
filename = data
9981014
else:
999-
assert data_projection in [None, 'mercator']
1000-
# This assumes a lat x long array.
1001-
# with 2x as many points in long as lat dims.
1002-
if data_projection is 'mercator':
1003-
data = utilities.geodetic_to_mercator(data)
10041015
try:
10051016
png_str = utilities.write_png(data)
10061017
except Exception as e:
@@ -1015,19 +1026,20 @@ def image_overlay(self, data, opacity=0.75, min_lat=-90.0, max_lat=90.0,
10151026

10161027
if image_name not in self.added_layers:
10171028
if image_name is None:
1029+
# FIXME: This will fails with multiple overlays!
10181030
image_name = "Image_Overlay"
10191031
else:
1032+
# FIXME: We should write a more robust `name_normalizer()`.
10201033
image_name = image_name.replace(" ", "_")
10211034
image_url = filename
10221035
image_bounds = [[min_lat, min_lon], [max_lat, max_lon]]
1023-
image_opacity = opacity
10241036

1025-
image_temp = self.env.get_template('image_layer.js')
1037+
image_templ = self.env.get_template('image_layer.js')
10261038

1027-
image = image_temp.render({'image_name': image_name,
1028-
'image_url': image_url,
1029-
'image_bounds': image_bounds,
1030-
'image_opacity': image_opacity})
1039+
image = image_templ.render({'image_name': image_name,
1040+
'image_url': image_url,
1041+
'image_bounds': image_bounds,
1042+
'image_opacity': image_opacity})
10311043

10321044
self.template_vars['image_layers'].append(image)
10331045
self.added_layers.append(image_name)

folium/utilities.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def write_png(array):
366366
array_full = array_full.astype('uint8')
367367
height, width = array_full.shape[:2]
368368
array_full = np.flipud(array_full)
369-
369+
370370
array_full = array_full.tobytes()
371371

372372
# Reverse the vertical line order and add null bytes at the start.
@@ -408,12 +408,12 @@ def geodetic_to_mercator(geodetic):
408408
409409
Parameters
410410
----------
411-
geodetic: numpy image array
411+
geodetic: NumPy image array
412412
Latitude x Longitude array, in mono (NxM), RGB (NxMx3) or RGBA (NxMx4)
413413
414414
Returns
415415
-------
416-
mercator: projected numpy image array
416+
mercator: projected NumPy image array
417417
418418
"""
419419
geo = np.repeat(np.atleast_3d(geodetic), 2, axis=0)

tests/folium_tests.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def test_fit_bounds(self):
485485
def test_image_overlay(self):
486486
"""Test image overlay."""
487487
from numpy.random import random
488-
from folium.utilities import write_png, geodetic_to_mercator
488+
from folium.utilities import write_png
489489
import base64
490490

491491
data = random((100, 100))
@@ -495,18 +495,16 @@ def test_image_overlay(self):
495495

496496
image_url = 'data.png'
497497
inline_image_url = ("data:image/png;base64," +
498-
base64.b64encode(write_png(
499-
geodetic_to_mercator(data))).decode('utf-8'))
498+
base64.b64encode(write_png(data)).decode('utf-8'))
500499

501500
image_tpl = self.env.get_template('image_layer.js')
502501
image_name = 'Image_Overlay'
503-
image_opacity = 0.75
502+
image_opacity = 1
504503

505504
min_lon, max_lon, min_lat, max_lat = -90.0, 90.0, -180.0, 180.0
506505
image_bounds = [[min_lon, min_lat], [max_lon, max_lat]]
507506

508-
# Test the external png.
509-
507+
# Test the external PNG.
510508
image_rendered = image_tpl.render({'image_name': image_name,
511509
'image_url': image_url,
512510
'image_bounds': image_bounds,
@@ -515,8 +513,7 @@ def test_image_overlay(self):
515513
self.map.image_overlay(data, filename=image_url)
516514
assert image_rendered in self.map.template_vars['image_layers']
517515

518-
# Test the inline png.
519-
516+
# Test the inline PNG.
520517
image_rendered = image_tpl.render({'image_name': image_name,
521518
'image_url': inline_image_url,
522519
'image_bounds': image_bounds,

0 commit comments

Comments
 (0)