|
23 | 23 | from folium.six import text_type, binary_type, iteritems
|
24 | 24 |
|
25 | 25 | import sys
|
26 |
| - |
| 26 | +import base64 |
27 | 27 |
|
28 | 28 | ENV = Environment(loader=PackageLoader('folium', 'templates'))
|
29 | 29 |
|
@@ -221,6 +221,7 @@ def __init__(self, location=None, width='100%', height='100%',
|
221 | 221 | self.added_layers = []
|
222 | 222 | self.template_vars.setdefault('wms_layers', [])
|
223 | 223 | self.template_vars.setdefault('tile_layers', [])
|
| 224 | + self.template_vars.setdefault('image_layers', []) |
224 | 225 |
|
225 | 226 | @iter_obj('simple')
|
226 | 227 | def add_tile_layer(self, tile_name=None, tile_url=None, active=False):
|
@@ -281,18 +282,14 @@ def add_layers_to_map(self):
|
281 | 282 | data_string = ''
|
282 | 283 | for i, layer in enumerate(self.added_layers):
|
283 | 284 | name = list(layer.keys())[0]
|
284 |
| - data_string += '\"' |
285 |
| - data_string += name |
286 |
| - data_string += '\"' |
287 |
| - data_string += ': ' |
288 |
| - data_string += name |
289 | 285 | if i < len(self.added_layers)-1:
|
290 |
| - data_string += ",\n" |
| 286 | + term_string = ",\n" |
291 | 287 | else:
|
292 |
| - data_string += "\n" |
| 288 | + term_string += "\n" |
| 289 | + data_string += '\"{}\": {}'.format(name, name, term_string) |
293 | 290 |
|
294 | 291 | data_layers = layers_temp.render({'layers': data_string})
|
295 |
| - self.template_vars.setdefault('data_layers', []).append((data_string)) |
| 292 | + self.template_vars.setdefault('data_layers', []).append((data_layers)) |
296 | 293 |
|
297 | 294 | @iter_obj('simple')
|
298 | 295 | def simple_marker(self, location=None, popup=None,
|
@@ -647,7 +644,8 @@ def fit_bounds(self, bounds, padding_top_left=None,
|
647 | 644 | fit_bounds = self.env.get_template('fit_bounds.js')
|
648 | 645 | fit_bounds_str = fit_bounds.render({
|
649 | 646 | 'bounds': json.dumps(bounds),
|
650 |
| - 'fit_bounds_options': json.dumps(fit_bounds_options), |
| 647 | + 'fit_bounds_options': json.dumps(fit_bounds_options, |
| 648 | + sort_keys=True), |
651 | 649 | })
|
652 | 650 |
|
653 | 651 | self.template_vars.update({'fit_bounds': fit_bounds_str})
|
@@ -948,6 +946,92 @@ def json_style(style_cnt, line_color, line_weight, line_opacity,
|
948 | 946 | self.template_vars.setdefault('geo_styles', []).append(style)
|
949 | 947 | self.template_vars.setdefault('gjson_layers', []).append(layer)
|
950 | 948 |
|
| 949 | + @iter_obj('image_overlay') |
| 950 | + def image_overlay(self, data, opacity=0.25, min_lat=-90.0, max_lat=90.0, |
| 951 | + min_lon=-180.0, max_lon=180.0, image_name=None, filename=None): |
| 952 | + """Simple image overlay of raster data from a numpy array. This is a lightweight |
| 953 | + way to overlay geospatial data on top of a map. If your data is high res, consider |
| 954 | + implementing a WMS server and adding a WMS layer. |
| 955 | +
|
| 956 | + This function works by generating a PNG file from a numpy array. If you do not |
| 957 | + specifiy a filename, it will embed the image inline. Otherwise, it saves the file in the |
| 958 | + current directory, and then adds it as an image overlay layer in leaflet.js. |
| 959 | + By default, the image is placed and stretched using bounds that cover the |
| 960 | + entire globe. |
| 961 | +
|
| 962 | + Parameters |
| 963 | + ---------- |
| 964 | + data: numpy array OR url string, required. |
| 965 | + if numpy array, must be a image format, i.e., NxM (mono), NxMx3 (rgb), or NxMx4 (rgba) |
| 966 | + if url, must be a valid url to a image (local or external) |
| 967 | + opacity: float, default 0.25 |
| 968 | + Image layer opacity in range 0 (completely transparent) to 1 (opaque) |
| 969 | + min_lat: float, default -90.0 |
| 970 | + max_lat: float, default 90.0 |
| 971 | + min_lon: float, default -180.0 |
| 972 | + max_lon: float, default 180.0 |
| 973 | + image_name: string, default None |
| 974 | + The name of the layer object in leaflet.js |
| 975 | + filename: string, default None |
| 976 | + Optional file name of output.png for image overlay. If None, we use a |
| 977 | + inline PNG. |
| 978 | +
|
| 979 | + Output |
| 980 | + ------ |
| 981 | + Image overlay data layer in obj.template_vars |
| 982 | +
|
| 983 | + Examples |
| 984 | + ------- |
| 985 | + # assumes a map object `m` has been created |
| 986 | + >>> import numpy as np |
| 987 | + >>> data = np.random.random((100,100)) |
| 988 | + |
| 989 | + # to make a rgba from a specific matplotlib colormap: |
| 990 | + >>> import matplotlib.cm as cm |
| 991 | + >>> cmapper = cm.cm.ColorMapper('jet') |
| 992 | + >>> data2 = cmapper.to_rgba(np.random.random((100,100))) |
| 993 | +
|
| 994 | + # place the data over all of the globe (will be pretty pixelated!) |
| 995 | + >>> m.image_overlay(data) |
| 996 | +
|
| 997 | + # put it only over a single city (Paris) |
| 998 | + >>> m.image_overlay(data, min_lat=48.80418, max_lat=48.90970, min_lon=2.25214, max_lon=2.44731) |
| 999 | + |
| 1000 | + """ |
| 1001 | + |
| 1002 | + if isinstance(data, str): |
| 1003 | + filename = data |
| 1004 | + else: |
| 1005 | + try: |
| 1006 | + png_str = utilities.write_png(data) |
| 1007 | + except Exception as e: |
| 1008 | + raise e |
| 1009 | + |
| 1010 | + if filename is not None: |
| 1011 | + with open(filename, 'wb') as fd: |
| 1012 | + fd.write(png_str) |
| 1013 | + else: |
| 1014 | + filename = "data:image/png;base64,"+base64.b64encode(png_str).decode('utf-8') |
| 1015 | + |
| 1016 | + if image_name not in self.added_layers: |
| 1017 | + if image_name is None: |
| 1018 | + image_name = "Image_Overlay" |
| 1019 | + else: |
| 1020 | + image_name = image_name.replace(" ", "_") |
| 1021 | + image_url = filename |
| 1022 | + image_bounds = [[min_lat, min_lon], [max_lat, max_lon]] |
| 1023 | + image_opacity = opacity |
| 1024 | + |
| 1025 | + image_temp = self.env.get_template('image_layer.js') |
| 1026 | + |
| 1027 | + image = image_temp.render({'image_name': image_name, |
| 1028 | + 'image_url': image_url, |
| 1029 | + 'image_bounds': image_bounds, |
| 1030 | + 'image_opacity': image_opacity}) |
| 1031 | + |
| 1032 | + self.template_vars['image_layers'].append(image) |
| 1033 | + self.added_layers.append(image_name) |
| 1034 | + |
951 | 1035 | def _build_map(self, html_templ=None, templ_type='string'):
|
952 | 1036 | self._auto_bounds()
|
953 | 1037 | """Build HTML/JS/CSS from Templates given current map type."""
|
|
0 commit comments