From 63ec149e96054aeedc991446237e67ed83034a0d Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Tue, 16 Mar 2021 16:40:24 +0000 Subject: [PATCH 1/4] improve automatic get_Base_url search --- xarray_leaflet.egg-info/PKG-INFO | 21 + xarray_leaflet.egg-info/SOURCES.txt | 30 ++ xarray_leaflet.egg-info/dependency_links.txt | 1 + xarray_leaflet.egg-info/not-zip-safe | 1 + xarray_leaflet.egg-info/requires.txt | 8 + xarray_leaflet.egg-info/top_level.txt | 1 + .../xarray_leaflet-checkpoint.py | 473 ++++++++++++++++++ .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 538 bytes .../__pycache__/handler.cpython-36.pyc | Bin 0 -> 1434 bytes .../server_extension.cpython-36.pyc | Bin 0 -> 1121 bytes .../__pycache__/transform.cpython-36.pyc | Bin 0 -> 1215 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 2446 bytes .../__pycache__/xarray_leaflet.cpython-36.pyc | Bin 0 -> 13195 bytes xarray_leaflet/xarray_leaflet.py | 30 +- 14 files changed, 553 insertions(+), 12 deletions(-) create mode 100644 xarray_leaflet.egg-info/PKG-INFO create mode 100644 xarray_leaflet.egg-info/SOURCES.txt create mode 100644 xarray_leaflet.egg-info/dependency_links.txt create mode 100644 xarray_leaflet.egg-info/not-zip-safe create mode 100644 xarray_leaflet.egg-info/requires.txt create mode 100644 xarray_leaflet.egg-info/top_level.txt create mode 100644 xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py create mode 100644 xarray_leaflet/__pycache__/__init__.cpython-36.pyc create mode 100644 xarray_leaflet/__pycache__/handler.cpython-36.pyc create mode 100644 xarray_leaflet/__pycache__/server_extension.cpython-36.pyc create mode 100644 xarray_leaflet/__pycache__/transform.cpython-36.pyc create mode 100644 xarray_leaflet/__pycache__/utils.cpython-36.pyc create mode 100644 xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc diff --git a/xarray_leaflet.egg-info/PKG-INFO b/xarray_leaflet.egg-info/PKG-INFO new file mode 100644 index 0000000..b32d840 --- /dev/null +++ b/xarray_leaflet.egg-info/PKG-INFO @@ -0,0 +1,21 @@ +Metadata-Version: 1.2 +Name: xarray-leaflet +Version: 0.1.13 +Summary: An xarray extension for map plotting +Home-page: https://github.com/davidbrochart/xarray_leaflet +Author: David Brochart +Author-email: david.brochart@gmail.com +License: MIT license +Description: An xarray extension for map plotting +Keywords: xarray_leaflet +Platform: UNKNOWN +Classifier: Development Status :: 2 - Pre-Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Requires-Python: >=3.5 diff --git a/xarray_leaflet.egg-info/SOURCES.txt b/xarray_leaflet.egg-info/SOURCES.txt new file mode 100644 index 0000000..7b65003 --- /dev/null +++ b/xarray_leaflet.egg-info/SOURCES.txt @@ -0,0 +1,30 @@ +CONTRIBUTING.rst +HISTORY.rst +LICENSE +MANIFEST.in +README.md +setup.cfg +setup.py +docs/Makefile +docs/conf.py +docs/contributing.rst +docs/getting_started.rst +docs/history.rst +docs/index.rst +docs/installation.rst +docs/make.bat +docs/usage.rst +etc/jupyter/jupyter_notebook_config.d/xarray_leaflet.json +etc/jupyter/jupyter_server_config.d/xarray_leaflet.json +xarray_leaflet/__init__.py +xarray_leaflet/handler.py +xarray_leaflet/server_extension.py +xarray_leaflet/transform.py +xarray_leaflet/utils.py +xarray_leaflet/xarray_leaflet.py +xarray_leaflet.egg-info/PKG-INFO +xarray_leaflet.egg-info/SOURCES.txt +xarray_leaflet.egg-info/dependency_links.txt +xarray_leaflet.egg-info/not-zip-safe +xarray_leaflet.egg-info/requires.txt +xarray_leaflet.egg-info/top_level.txt \ No newline at end of file diff --git a/xarray_leaflet.egg-info/dependency_links.txt b/xarray_leaflet.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/xarray_leaflet.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/xarray_leaflet.egg-info/not-zip-safe b/xarray_leaflet.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/xarray_leaflet.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/xarray_leaflet.egg-info/requires.txt b/xarray_leaflet.egg-info/requires.txt new file mode 100644 index 0000000..a03683c --- /dev/null +++ b/xarray_leaflet.egg-info/requires.txt @@ -0,0 +1,8 @@ +jupyter_server>=0.2.0 +rioxarray>=0.0.30 +ipyleaflet>=0.13.1 +pillow>=7 +matplotlib>=3 +affine>=2 +mercantile>=1 +ipyspin>=0.1.1 diff --git a/xarray_leaflet.egg-info/top_level.txt b/xarray_leaflet.egg-info/top_level.txt new file mode 100644 index 0000000..8e5c7eb --- /dev/null +++ b/xarray_leaflet.egg-info/top_level.txt @@ -0,0 +1 @@ +xarray_leaflet diff --git a/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py b/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py new file mode 100644 index 0000000..9760cdf --- /dev/null +++ b/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py @@ -0,0 +1,473 @@ +import os +import asyncio +import tempfile + +import xarray as xr +from matplotlib import pyplot as plt +import matplotlib as mpl +import numpy as np +import mercantile +from ipyleaflet import LocalTileLayer, WidgetControl, DrawControl +from ipyspin import Spinner +from ipywidgets import Output +from IPython.display import display, Image +from traitlets import HasTraits, Bool, observe +from rasterio.warp import Resampling + +from .transform import passthrough, normalize, coarsen +from .utils import reproject_custom, reproject_not_custom, write_image, get_bbox_tiles, get_transform, wait_for_change + + +@xr.register_dataarray_accessor('leaflet') +class LeafletMap(HasTraits): + """A xarray.DataArray extension for tiled map plotting, based on (ipy)leaflet. + """ + + map_ready = Bool(False) + + @observe('map_ready') + def _map_ready_changed(self, change): + self._start() + + + def __init__(self, da): + self._da = da + self._da_selected = None + + def plot(self, + m, + x_dim='x', + y_dim='y', + fit_bounds=True, + rgb_dim=None, + transform0=None, + transform1=passthrough, + transform2=coarsen(), + transform3=passthrough, + colormap=None, + colorbar_position='topright', + persist=True, + dynamic=False, + tile_dir=None, + tile_height=256, + tile_width=256, + resampling=Resampling.nearest, + get_base_url=None, + debug_output=None): + """Display an array as an interactive map. + + Assumes that the pixels are given on a regular grid + (fixed spacing in x and y). + + Parameters + ---------- + m : ipyleaflet.Map + The map on while to show the layer. + y_dim : str, optional + Name of the y dimension/coordinate + (default: 'y'). + x_dim : str, optional + Name of the x dimension/coordinate + (default: 'x'). + fit_bounds: bool, optional + Set the map to fit the bounds of the array (default True). + rgb_dim : str, optional + Name of the RGB dimension/coordinate + (default: None). + transform0 : function, optional + Transformation over the whole DataArray. + transform1 : function, optional + Transformation over the visible DataArray. + transform2 : function, optional + Transformation over a tile before reprojection. + transform3 : function, optional + Transformation over a tile before saving to PNG. + colormap : function, optional + The colormap function to use for the tile PNG + (default: matplotlib.pyplot.cm.viridis). + colorbar_position : str, optional + Where to show the colorbar (default: "topright"). + persist : bool, optional + Whether to keep the tile files (True) or not (False). + dynamic : bool, optional + Whether the map is dynamic (True) or not (False). If True then the + tiles will refreshed each time the map is dragged or zoomed. + tile_dir : str, optional + The path to the tile directory (must be absolute). + tile_height : int, optional + The heiht of a tile in pixels (default: 256). + tile_width : int, optional + The width of a tile in pixels (default: 256). + resampling : int, optional + The resampling method to use, see rasterio.warp.reproject + (default: Resampling.nearest). + get_base_url: callable, optional + A function taking the window URL and returning the base URL to use. + + Returns + ------- + l : ipyleaflet.LocalTileLayer + A handler to the layer that is added to the map. + """ + + if debug_output is None: + self.debug_output = Output() + else: + self.debug_output = debug_output + + with self.debug_output: + self.get_base_url = get_base_url + + if 'proj4def' in m.crs: + # it's a custom projection + if dynamic: + raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) + self.dst_crs = m.crs['proj4def'] + self.web_mercator = False + self.custom_proj = True + elif m.crs['name'].startswith('EPSG'): + epsg = m.crs['name'][4:] + if dynamic and epsg != '3857': + raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) + self.dst_crs = 'EPSG:' + epsg + self.web_mercator = epsg == '3857' + self.custom_proj = False + else: + raise RuntimeError('Unsupported map projection: {}'.format(m.crs)) + + self.nodata = self._da.rio.nodata + var_dims = self._da.dims + expected_dims = [y_dim, x_dim] + if rgb_dim is not None: + expected_dims.append(rgb_dim) + if set(var_dims) != set(expected_dims): + raise ValueError( + "Invalid dimensions in DataArray: " + "should include only {}, found {}." + .format(tuple(expected_dims), var_dims) + ) + + if rgb_dim is not None and colormap is not None: + raise ValueError( + "Cannot have a RGB dimension and a " + "colormap at the same time." + ) + elif rgb_dim is None: + if colormap is None: + colormap = plt.cm.viridis + if transform0 is None: + transform0 = normalize + else: + # there is a RGB dimension + if transform0 is None: + transform0 = passthrough + + self.resampling = resampling + self.tile_dir = tile_dir + self.persist = persist + self.attrs = self._da.attrs + self.m = m + self.dynamic = dynamic + self.tile_width = tile_width + self.tile_height = tile_height + self.transform0 = transform0 + self.transform1 = transform1 + self.transform2 = transform2 + self.transform3 = transform3 + self.colormap = colormap + self.colorbar = None + self.colorbar_position = colorbar_position + if self.dynamic: + self.persist = False + self.tile_dir = None + + self._da = self._da.rename({y_dim: 'y', x_dim: 'x'}) + if rgb_dim is None: + self.is_rgb = False + else: + self.is_rgb = True + self._da = self._da.rename({rgb_dim: 'rgb'}) + + # ensure latitudes are descending + if np.any(np.diff(self._da.y.values) >= 0): + self._da = self._da.sel(y=slice(None, None, -1)) + + # infer grid specifications (assume a rectangular grid) + y = self._da.y.values + x = self._da.x.values + + self.x_left = float(x.min()) + self.x_right = float(x.max()) + self.y_lower = float(y.min()) + self.y_upper = float(y.max()) + + self.dx = float((self.x_right - self.x_left) / (x.size - 1)) + self.dy = float((self.y_upper - self.y_lower) / (y.size - 1)) + + if fit_bounds: + asyncio.ensure_future(self.async_fit_bounds()) + else: + asyncio.ensure_future(self.async_wait_for_bounds()) + + self.l = LocalTileLayer() + if self._da.name is not None: + self.l.name = self._da.name + + self._da_notransform = self._da + + self.spinner = Spinner() + self.spinner.radius = 5 + self.spinner.length = 3 + self.spinner.width = 5 + self.spinner.lines = 8 + self.spinner.color = '#000000' + self.spinner.layout.height = '30px' + self.spinner.layout.width = '30px' + self.spinner_control = WidgetControl(widget=self.spinner, position='bottomright') + + return self.l + + + def select(self, draw_control=None): + with self.debug_output: + if draw_control is None: + self._draw_control = DrawControl() + self._draw_control.polygon = {} + self._draw_control.polyline = {} + self._draw_control.circlemarker = {} + self._draw_control.rectangle = { + 'shapeOptions': { + 'fillOpacity': 0.5 + } + } + else: + self._draw_control = draw_control + self._draw_control.on_draw(self._get_selection) + self.m.add_control(self._draw_control) + + + def unselect(self): + with self.debug_output: + self.m.remove_control(self._draw_control) + + + def get_selection(self): + return self._da_selected + + + def _get_selection(self, *args, **kwargs): + with self.debug_output: + if self._draw_control.last_draw['geometry'] is not None: + lonlat = self._draw_control.last_draw['geometry']['coordinates'][0] + lats = [ll[1] for ll in lonlat] + lons = [ll[0] for ll in lonlat] + lt0, lt1 = min(lats), max(lats) + ln0, ln1 = min(lons), max(lons) + self._da_selected = self._da_notransform.sel(y=slice(lt1, lt0), x=slice(ln0, ln1)) + + + def _start(self): + with self.debug_output: + self.m.add_control(self.spinner_control) + self._da, self.transform0_args = get_transform(self.transform0(self._da, debug_output=self.debug_output)) + + self.url = self.m.window_url + + # the jupyter key we look for + # keeping the last "/" to avoid bug with a (strange) filename as "jupyter.ipynb" + key = '/jupyter/' + + # A custom function have been set + if self.get_base_url is not None: + self.base_url = self.get_base_url(self.url) + + # Search for the first "jupyter" in url name + elif self.url.find(key) != -1: + i_jupyter = self.url.find(key) + self.base_url = self.url[:i_jupyter + len(key) -1] + + # We are in an "exotic" environment and a get_base_url function must be set + else: + raise ValueError(f''' + xarray-leaflet default use is in a Jupyter environment. + "jupyter" was not part of {self.url}. + Please provide a get_base_url function. + ''') + + if self.tile_dir is None: + self.tile_temp_dir = tempfile.TemporaryDirectory(prefix='xarray_leaflet_') + self.tile_path = self.tile_temp_dir.name + else: + self.tile_path = self.tile_dir + self.url = self.base_url + '/xarray_leaflet' + self.tile_path + '/{z}/{x}/{y}.png' + self.l.path = self.url + + self.m.remove_control(self.spinner_control) + self._get_tiles() + self.m.observe(self._get_tiles, names='pixel_bounds') + if not self.dynamic: + self._show_colorbar(self._da_notransform) + self.m.add_layer(self.l) + + + def _show_colorbar(self, da): + if self.colorbar_position and self.colormap is not None: + vmin = da.min().values + vmax = da.max().values + fig = plt.figure(figsize=(8, 3)) + ax = fig.add_axes([0.05, 0.8, 0.5, 0.07]); + norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) + cbar = mpl.colorbar.ColorbarBase(ax, cmap=self.colormap, norm=norm, orientation='horizontal') + f = tempfile.NamedTemporaryFile(suffix='.png', delete=False) + output = Output() + try: + plt.savefig(f.name, bbox_inches='tight') + with output: + display(Image(filename=f.name)) + finally: + os.unlink(f.name) + f.close() + self.colorbar = WidgetControl(widget=output, position=self.colorbar_position, transparent_bg=True) + self.m.add_control(self.colorbar) + plt.close() + + + def _get_tiles(self, change=None): + with self.debug_output: + self.m.add_control(self.spinner_control) + if self.dynamic: + self.tile_temp_dir.cleanup() + self.tile_temp_dir = tempfile.TemporaryDirectory(prefix='xarray_leaflet_') + new_tile_path = self.tile_temp_dir.name + new_url = self.base_url + '/xarray_leaflet' + new_tile_path + '/{z}/{x}/{y}.png' + if self.l in self.m.layers: + self.m.remove_layer(self.l) + + (left, top), (right, bottom) = self.m.pixel_bounds + (south, west), (north, east) = self.m.bounds + z = int(self.m.zoom) # TODO: support non-integer zoom levels? + if self.custom_proj: + resolution = self.m.crs['resolutions'][z] + + if self.web_mercator: + tiles = list(mercantile.tiles(west, south, east, north, z)) + else: + x0, x1 = int(left) // self.tile_width, int(right) // self.tile_width + 1 + y0, y1 = int(top) // self.tile_height, int(bottom) // self.tile_height + 1 + tiles = [mercantile.Tile(x, y, z) for x in range(x0, x1) for y in range(y0, y1)] + + if self.dynamic: + # dynamic maps are redrawn at each interaction with the map + # so we can take exactly the corresponding slice in the original data + da_visible = self._da.sel(y=slice(north, south), x=slice(west, east)) + elif self.web_mercator: + # for static web mercator maps we can't redraw a tile once it has been (partly) displayed, + # so we must slice the original data on tile boundaries + bbox = get_bbox_tiles(tiles) + # take one more source data point to avoid glitches + da_visible = self._da.sel(y=slice(bbox.north + self.dy, bbox.south - self.dy), x=slice(bbox.west - self.dx, bbox.east + self.dx)) + else: + # it's a custom projection or not web mercator, the visible tiles don't translate easily + # to a slice of the original data, so we keep everything + # TODO: slice the data for EPSG3395, EPSG4326, Earth, Base and Simple + da_visible = self._da + + # check if we have some data to show + if 0 not in da_visible.shape: + da_visible, transform1_args = get_transform(self.transform1(da_visible, *self.transform0_args, debug_output=self.debug_output)) + + if self.dynamic: + self.tile_path = new_tile_path + self.url = new_url + + for tile in tiles: + x, y, z = tile + path = f'{self.tile_path}/{z}/{x}/{y}.png' + # if static map, check if we already have the tile + # if dynamic map, new tiles are always created + if self.dynamic or not os.path.exists(path): + if self.web_mercator: + bbox = mercantile.bounds(tile) + xy_bbox = mercantile.xy_bounds(tile) + x_pix = (xy_bbox.right - xy_bbox.left) / self.tile_width + y_pix = (xy_bbox.top - xy_bbox.bottom) / self.tile_height + # take one more source data point to avoid glitches + da_tile = da_visible.sel(y=slice(bbox.north + self.dy, bbox.south - self.dy), x=slice(bbox.west - self.dx, bbox.east + self.dx)) + else: + da_tile = da_visible + # check if we have data for this tile + if 0 in da_tile.shape: + write_image(path, None, self.persist) + else: + da_tile.attrs = self.attrs + da_tile, transform2_args = get_transform(self.transform2(da_tile, tile_width=self.tile_width, tile_height=self.tile_height, debug_output=self.debug_output), *transform1_args) + # reproject each RGB component if needed + # TODO: must be doable with xarray.apply_ufunc + if self.is_rgb: + das = [da_tile.isel(rgb=i) for i in range(3)] + else: + das = [da_tile] + for i in range(len(das)): + das[i] = das[i].rio.write_nodata(self.nodata) + if self.custom_proj: + das[i] = reproject_custom(das[i], self.dst_crs, x, y, z, resolution, resolution, self.tile_width, self.tile_height, self.resampling) + else: + das[i] = reproject_not_custom(das[i], self.dst_crs, xy_bbox.left, xy_bbox.top, x_pix, y_pix, self.tile_width, self.tile_height, self.resampling) + das[i], transform3_args = get_transform(self.transform3(das[i], *transform2_args, debug_output=self.debug_output)) + if self.is_rgb: + alpha = np.where(das[0]==self._da.rio.nodata, 0, 255) + das.append(alpha) + da_tile = np.stack(das, axis=2) + write_image(path, da_tile, self.persist) + else: + da_tile = self.colormap(das[0]) + write_image(path, da_tile*255, self.persist) + + if self.dynamic: + if self.colorbar in self.m.controls: + self.m.remove_control(self.colorbar) + self._show_colorbar(self._da_notransform.sel(y=slice(north, south), x=slice(west, east))) + self.l.path = self.url + self.m.add_layer(self.l) + self.l.redraw() + + self.m.remove_control(self.spinner_control) + + + async def async_wait_for_bounds(self): + with self.debug_output: + if len(self.m.bounds) == 0: + await wait_for_change(self.m, 'bounds') + self.map_ready = True + + + async def async_fit_bounds(self): + with self.debug_output: + center = self.y_lower + (self.y_upper - self.y_lower) / 2, self.x_left + (self.x_right - self.x_left) / 2 + if center != self.m.center: + self.m.center = center + await wait_for_change(self.m, 'bounds') + zoomed_out = False + # zoom out + while True: + if self.m.zoom <= 1: + break + (south, west), (north, east) = self.m.bounds + if south > self.y_lower or north < self.y_upper or west > self.x_left or east < self.x_right: + self.m.zoom = self.m.zoom - 1 + await wait_for_change(self.m, 'bounds') + zoomed_out = True + else: + break + if not zoomed_out: + # zoom in + while True: + (south, west), (north, east) = self.m.bounds + if south < self.y_lower and north > self.y_upper and west < self.x_left and east > self.x_right: + self.m.zoom = self.m.zoom + 1 + await wait_for_change(self.m, 'bounds') + else: + self.m.zoom = self.m.zoom - 1 + await wait_for_change(self.m, 'bounds') + break + self.map_ready = True diff --git a/xarray_leaflet/__pycache__/__init__.cpython-36.pyc b/xarray_leaflet/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38af4ee063974ec8417039f1735633d8b6faca92 GIT binary patch literal 538 zcmaJ-yH3L}6m?#0nm)w9fRKPJ3>`?Rx-bB$5F;QYm^^TCYqts5b>yT~+dtx$EH);7 zfeD*d2r7XsAD{cM@42_vY(@{Sw~x7l&>Q-(T*Le36ek8K!iYN9p)Pi*hdt`kfcqlA zfrAXM@QSG7st9o?YPjYg7UGCj?@&8j9^T9Rh_e~vaZZNMWX$4`RPmfBMHVJNMx2$& zvUN#j8I3QL9HvB-%N=U%WU#jTV?i>W45e6Bj+1WE?Y$epsO{+JYW2l6$=jYjfTwA` zD4BwSshPRhykwIilM~2EnHG8%9SU0KcNb%ODFy$Gy@+|~5VeemoFG$sGs)diUPAUcK_ zf0Jv2gM0{N4uDA_X+~n4Sw?dfGm5g8c{z`HBh$=}{V$2+(myBCSA65efu!G)Ua$oZ z zhQ^`vF@SsbxzJjyzENV5DSPFWf&&~lqt_!Kb01hmW{{9%8%U#(!5_+!fH(hm-8m`? zs|wqHx|*wXE6v5J8qSN;b@xb&r>c+Jy3E#tqU;-6YW3&=NMqc4^dMgt+t=z$WT_Mu z7sRp;4nF%B5^o!2lc^F?Y0$V)+2l8(L%7r1gWgX9%`djiHc}}Wm z=F1SQUYw9(ztJK*BU`n1LTpg^8~PPkKY=DzpnXc}pbBLBCD%WL4L^ZZc?-0M@>cD? zdiab~T>lO|4L5KC`w7vh>}=Sz_^oU4$t;pxxp#?#uxf26MDl*3nn4aNyg4O}L{VxG zvVZ)R4!2m?wLg$$p&p<9`U{RfzR-v@$6o@>T_6>JQ9a#a9k_sYKAIOB KK@~bQV*dhG6mWe2 literal 0 HcmV?d00001 diff --git a/xarray_leaflet/__pycache__/server_extension.cpython-36.pyc b/xarray_leaflet/__pycache__/server_extension.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2db5825103be0c7ee20431a8d0b7a3f94f4c4092 GIT binary patch literal 1121 zcmaJ=L2uJA6tKxzffLV8S8YAuD$mdHdwt*c-j^r+e)se5cSlnnp+D%#^1y!yk9rD*A%;sd z!()sQ^O#?HGk@%ZAF$33G!9tEBG@}DW;?Kltos2ayB8Qz6KwWBQ7P%!TTUk>*KcUW zN-i(p&R+H#S*CNUi*#CxDz_a;%>C5TOQ+5J42q@OAsr~CVlb!^;sD>ovIl-{UtBJW*p1{CI2u%8)nHZd zGdRZ3^l)V2s@6QK>n|o$nre}oz3b2xcdws^8;nU{BE{XHjIU&F0>+g#k>p<+Avvfe zU?SJ9Cs)iaU@-?9T2;3BLnka{u`1g~guV4o-iFgPW_CJ`nGLkO1a^%4#djx$_gpu! zQiPIHC{5}Ku~0xYR0^)hiO>a^aZMT3lmP5piVQ^HazSd66J3*usF>5-(f=oLv=MBp zL=(dbsDu{W$&y3aK%Z literal 0 HcmV?d00001 diff --git a/xarray_leaflet/__pycache__/transform.cpython-36.pyc b/xarray_leaflet/__pycache__/transform.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e919d8a11643e319f7dfe522d1214e96b02d05d GIT binary patch literal 1215 zcmZ8gOK%%D5GJ|YU20`3ju8~;`%*MmAh3btY8Wk=v(u)B1VRy&7Aa{r`w+;LEd|_@ z4YZde$NYx;gZvhvx1RhLdg=`2I6-zXFLF4X$H#}eyV>)9e*N<~C*)u9$rpkC6Ocuw zh@ur4ZzUbGU`J!Fn2IjRI96Q6AQR2Yv=rr_%w|+2D!ruRp`yQ&TwFmEax$eT;KS&s zKL)aJHE{&I=*c^hM?RLu$c^XH%&kveoTD@hoC5pr_;AtI`fzDveJWS#uqsaN;X0g| zR9em|-F`j)+>J?VHEUywK7P}CAN>{7M! zic#`6cwfN&Q$XNv48Ubx+rx zZoD{cSB;Wpg;fENY>EcZ zIf4Vz#pK4`u|>VC^sJ~lZT#rTs_BYaKi#_L!_Q`KZb424=QS2v{60_sn+?#~v-9_LrUC-hAd*VFATFgqRZ*xyX%R)1sM&bN$vWO$GdpgQ zYWB%R4G)Ol!CxBj##8?SLMq?cEotel=FFMW++M z2X$9=We;^vuE{>?H93&$sQZ%NX2Xp~IGODXxeKmm$)17|em0$^xthc@f^P$*h4KYT zxesB3Dwe$JSRsY=&#-gMwl>kb$*L~;dp7K_YONWw9!4XKh8XP|W@Fp2UE8y3w!aFi zfn_s36PDY7jcjWwY+HK2`PI5zhjIRn<~Z*W!8G(-+vp@Or-ja3Q0^xSY(o00AA@?uUdl_+E4ueFZ55n(6=oc!V8fU z@WQ|j;03So0ss!Mtqw`hNly^hAT`ru#&BH|XLN@|mqd?5pTvL!*{JC<^?H4Vv`rE( zkT^@?MH1&ooG0-T2|~3IuRwE~Pch01c(lfec6bIS!YV|mae5776CwwF{*zL(`Fk`B z7>FWfo@Fa8Ic_dEvk1R~NN*Y6BGgu57OWU&`Aa68wM;Z?TfV|ydDff7w2m{=vCN7U z)=E}$_dCQ=q>fUsr8scnaCg{sq9|RIB?n5TAfmrWK(`k3Jjs;vi-pQvFqs#n(!_b! zp6Vj2nd3yhaKSRo%?HjelY7eb4|QtPSQfb&Pt&;?`ua^;TRZed(IVd)6Jq*3^fx5=KQ;8b7@$%TCmCYI=Uq|vPAHMkMK;uSC;=?p6NLHOsEDA1_ydp&W&qRH5;ZtHPPT5*0Ur^`M8)J?K947IYtT1oQ~H z`1!&ehShD1QZHZmRm--(53%FCG@lM*7fk0xVqBalJxOw80_DQpVwua*d0BGgg1pe6 ziI=4y2XYy7{}5@%`6_{oZ{&M767%_PaWsrvK3iiZuo)FXDz$S z3E(`k0D6xRU96Z2K*4v00@fWPp$BHU0F^x#A}ufH#sv-g{Y3AT&-1uPmY%)|S8!li zzgc9apbVhDLf-Mbf>S*KgDrPJcknuz5}E2fTjOG^Ol13)R6})2w!8 pz3nqRsJUBP6n&f(ayeI*$v-8%AQAFe1Yl6SsEa?2qNo?O{{`*GHtPTY literal 0 HcmV?d00001 diff --git a/xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc b/xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63f1603ca3d661e7669deb7d91abc2ea70d747fd GIT binary patch literal 13195 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 literal 0 HcmV?d00001 diff --git a/xarray_leaflet/xarray_leaflet.py b/xarray_leaflet/xarray_leaflet.py index 87b213b..9760cdf 100644 --- a/xarray_leaflet/xarray_leaflet.py +++ b/xarray_leaflet/xarray_leaflet.py @@ -272,21 +272,27 @@ def _start(self): self._da, self.transform0_args = get_transform(self.transform0(self._da, debug_output=self.debug_output)) self.url = self.m.window_url + + # the jupyter key we look for + # keeping the last "/" to avoid bug with a (strange) filename as "jupyter.ipynb" + key = '/jupyter/' + + # A custom function have been set if self.get_base_url is not None: self.base_url = self.get_base_url(self.url) + + # Search for the first "jupyter" in url name + elif self.url.find(key) != -1: + i_jupyter = self.url.find(key) + self.base_url = self.url[:i_jupyter + len(key) -1] + + # We are in an "exotic" environment and a get_base_url function must be set else: - if self.url.endswith('/lab'): - # we are in JupyterLab - self.base_url = self.url[:-4] - else: - i_notebooks = self.url.find('/notebooks/') - i_voila = self.url.find('/voila/') - if i_notebooks != -1 and (i_voila == -1 or i_voila > i_notebooks): - # we are in classical Notebooks - self.base_url = self.url[:i_notebooks] - elif i_voila != -1 and (i_notebooks == -1 or i_notebooks > i_voila): - # we are in Voila - self.base_url = self.url[:i_voila] + raise ValueError(f''' + xarray-leaflet default use is in a Jupyter environment. + "jupyter" was not part of {self.url}. + Please provide a get_base_url function. + ''') if self.tile_dir is None: self.tile_temp_dir = tempfile.TemporaryDirectory(prefix='xarray_leaflet_') From a3ec6226490ec9e5ebd291bd5fdf05f85a8794cf Mon Sep 17 00:00:00 2001 From: Rambaud Pierrick <12rambau@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:42:27 +0100 Subject: [PATCH 2/4] Create .gitignore --- .gitignore | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ From ee315cc653cb3df197476ef8daa1cef772c01980 Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Tue, 16 Mar 2021 16:45:45 +0000 Subject: [PATCH 3/4] remove cache files --- xarray_leaflet.egg-info/PKG-INFO | 21 - xarray_leaflet.egg-info/SOURCES.txt | 30 -- xarray_leaflet.egg-info/dependency_links.txt | 1 - xarray_leaflet.egg-info/not-zip-safe | 1 - xarray_leaflet.egg-info/requires.txt | 8 - xarray_leaflet.egg-info/top_level.txt | 1 - .../xarray_leaflet-checkpoint.py | 473 ------------------ .../__pycache__/__init__.cpython-36.pyc | Bin 538 -> 0 bytes .../__pycache__/handler.cpython-36.pyc | Bin 1434 -> 0 bytes .../server_extension.cpython-36.pyc | Bin 1121 -> 0 bytes .../__pycache__/transform.cpython-36.pyc | Bin 1215 -> 0 bytes .../__pycache__/utils.cpython-36.pyc | Bin 2446 -> 0 bytes .../__pycache__/xarray_leaflet.cpython-36.pyc | Bin 13195 -> 0 bytes 13 files changed, 535 deletions(-) delete mode 100644 xarray_leaflet.egg-info/PKG-INFO delete mode 100644 xarray_leaflet.egg-info/SOURCES.txt delete mode 100644 xarray_leaflet.egg-info/dependency_links.txt delete mode 100644 xarray_leaflet.egg-info/not-zip-safe delete mode 100644 xarray_leaflet.egg-info/requires.txt delete mode 100644 xarray_leaflet.egg-info/top_level.txt delete mode 100644 xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py delete mode 100644 xarray_leaflet/__pycache__/__init__.cpython-36.pyc delete mode 100644 xarray_leaflet/__pycache__/handler.cpython-36.pyc delete mode 100644 xarray_leaflet/__pycache__/server_extension.cpython-36.pyc delete mode 100644 xarray_leaflet/__pycache__/transform.cpython-36.pyc delete mode 100644 xarray_leaflet/__pycache__/utils.cpython-36.pyc delete mode 100644 xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc diff --git a/xarray_leaflet.egg-info/PKG-INFO b/xarray_leaflet.egg-info/PKG-INFO deleted file mode 100644 index b32d840..0000000 --- a/xarray_leaflet.egg-info/PKG-INFO +++ /dev/null @@ -1,21 +0,0 @@ -Metadata-Version: 1.2 -Name: xarray-leaflet -Version: 0.1.13 -Summary: An xarray extension for map plotting -Home-page: https://github.com/davidbrochart/xarray_leaflet -Author: David Brochart -Author-email: david.brochart@gmail.com -License: MIT license -Description: An xarray extension for map plotting -Keywords: xarray_leaflet -Platform: UNKNOWN -Classifier: Development Status :: 2 - Pre-Alpha -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Natural Language :: English -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Requires-Python: >=3.5 diff --git a/xarray_leaflet.egg-info/SOURCES.txt b/xarray_leaflet.egg-info/SOURCES.txt deleted file mode 100644 index 7b65003..0000000 --- a/xarray_leaflet.egg-info/SOURCES.txt +++ /dev/null @@ -1,30 +0,0 @@ -CONTRIBUTING.rst -HISTORY.rst -LICENSE -MANIFEST.in -README.md -setup.cfg -setup.py -docs/Makefile -docs/conf.py -docs/contributing.rst -docs/getting_started.rst -docs/history.rst -docs/index.rst -docs/installation.rst -docs/make.bat -docs/usage.rst -etc/jupyter/jupyter_notebook_config.d/xarray_leaflet.json -etc/jupyter/jupyter_server_config.d/xarray_leaflet.json -xarray_leaflet/__init__.py -xarray_leaflet/handler.py -xarray_leaflet/server_extension.py -xarray_leaflet/transform.py -xarray_leaflet/utils.py -xarray_leaflet/xarray_leaflet.py -xarray_leaflet.egg-info/PKG-INFO -xarray_leaflet.egg-info/SOURCES.txt -xarray_leaflet.egg-info/dependency_links.txt -xarray_leaflet.egg-info/not-zip-safe -xarray_leaflet.egg-info/requires.txt -xarray_leaflet.egg-info/top_level.txt \ No newline at end of file diff --git a/xarray_leaflet.egg-info/dependency_links.txt b/xarray_leaflet.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/xarray_leaflet.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/xarray_leaflet.egg-info/not-zip-safe b/xarray_leaflet.egg-info/not-zip-safe deleted file mode 100644 index 8b13789..0000000 --- a/xarray_leaflet.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/xarray_leaflet.egg-info/requires.txt b/xarray_leaflet.egg-info/requires.txt deleted file mode 100644 index a03683c..0000000 --- a/xarray_leaflet.egg-info/requires.txt +++ /dev/null @@ -1,8 +0,0 @@ -jupyter_server>=0.2.0 -rioxarray>=0.0.30 -ipyleaflet>=0.13.1 -pillow>=7 -matplotlib>=3 -affine>=2 -mercantile>=1 -ipyspin>=0.1.1 diff --git a/xarray_leaflet.egg-info/top_level.txt b/xarray_leaflet.egg-info/top_level.txt deleted file mode 100644 index 8e5c7eb..0000000 --- a/xarray_leaflet.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -xarray_leaflet diff --git a/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py b/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py deleted file mode 100644 index 9760cdf..0000000 --- a/xarray_leaflet/.ipynb_checkpoints/xarray_leaflet-checkpoint.py +++ /dev/null @@ -1,473 +0,0 @@ -import os -import asyncio -import tempfile - -import xarray as xr -from matplotlib import pyplot as plt -import matplotlib as mpl -import numpy as np -import mercantile -from ipyleaflet import LocalTileLayer, WidgetControl, DrawControl -from ipyspin import Spinner -from ipywidgets import Output -from IPython.display import display, Image -from traitlets import HasTraits, Bool, observe -from rasterio.warp import Resampling - -from .transform import passthrough, normalize, coarsen -from .utils import reproject_custom, reproject_not_custom, write_image, get_bbox_tiles, get_transform, wait_for_change - - -@xr.register_dataarray_accessor('leaflet') -class LeafletMap(HasTraits): - """A xarray.DataArray extension for tiled map plotting, based on (ipy)leaflet. - """ - - map_ready = Bool(False) - - @observe('map_ready') - def _map_ready_changed(self, change): - self._start() - - - def __init__(self, da): - self._da = da - self._da_selected = None - - def plot(self, - m, - x_dim='x', - y_dim='y', - fit_bounds=True, - rgb_dim=None, - transform0=None, - transform1=passthrough, - transform2=coarsen(), - transform3=passthrough, - colormap=None, - colorbar_position='topright', - persist=True, - dynamic=False, - tile_dir=None, - tile_height=256, - tile_width=256, - resampling=Resampling.nearest, - get_base_url=None, - debug_output=None): - """Display an array as an interactive map. - - Assumes that the pixels are given on a regular grid - (fixed spacing in x and y). - - Parameters - ---------- - m : ipyleaflet.Map - The map on while to show the layer. - y_dim : str, optional - Name of the y dimension/coordinate - (default: 'y'). - x_dim : str, optional - Name of the x dimension/coordinate - (default: 'x'). - fit_bounds: bool, optional - Set the map to fit the bounds of the array (default True). - rgb_dim : str, optional - Name of the RGB dimension/coordinate - (default: None). - transform0 : function, optional - Transformation over the whole DataArray. - transform1 : function, optional - Transformation over the visible DataArray. - transform2 : function, optional - Transformation over a tile before reprojection. - transform3 : function, optional - Transformation over a tile before saving to PNG. - colormap : function, optional - The colormap function to use for the tile PNG - (default: matplotlib.pyplot.cm.viridis). - colorbar_position : str, optional - Where to show the colorbar (default: "topright"). - persist : bool, optional - Whether to keep the tile files (True) or not (False). - dynamic : bool, optional - Whether the map is dynamic (True) or not (False). If True then the - tiles will refreshed each time the map is dragged or zoomed. - tile_dir : str, optional - The path to the tile directory (must be absolute). - tile_height : int, optional - The heiht of a tile in pixels (default: 256). - tile_width : int, optional - The width of a tile in pixels (default: 256). - resampling : int, optional - The resampling method to use, see rasterio.warp.reproject - (default: Resampling.nearest). - get_base_url: callable, optional - A function taking the window URL and returning the base URL to use. - - Returns - ------- - l : ipyleaflet.LocalTileLayer - A handler to the layer that is added to the map. - """ - - if debug_output is None: - self.debug_output = Output() - else: - self.debug_output = debug_output - - with self.debug_output: - self.get_base_url = get_base_url - - if 'proj4def' in m.crs: - # it's a custom projection - if dynamic: - raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) - self.dst_crs = m.crs['proj4def'] - self.web_mercator = False - self.custom_proj = True - elif m.crs['name'].startswith('EPSG'): - epsg = m.crs['name'][4:] - if dynamic and epsg != '3857': - raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) - self.dst_crs = 'EPSG:' + epsg - self.web_mercator = epsg == '3857' - self.custom_proj = False - else: - raise RuntimeError('Unsupported map projection: {}'.format(m.crs)) - - self.nodata = self._da.rio.nodata - var_dims = self._da.dims - expected_dims = [y_dim, x_dim] - if rgb_dim is not None: - expected_dims.append(rgb_dim) - if set(var_dims) != set(expected_dims): - raise ValueError( - "Invalid dimensions in DataArray: " - "should include only {}, found {}." - .format(tuple(expected_dims), var_dims) - ) - - if rgb_dim is not None and colormap is not None: - raise ValueError( - "Cannot have a RGB dimension and a " - "colormap at the same time." - ) - elif rgb_dim is None: - if colormap is None: - colormap = plt.cm.viridis - if transform0 is None: - transform0 = normalize - else: - # there is a RGB dimension - if transform0 is None: - transform0 = passthrough - - self.resampling = resampling - self.tile_dir = tile_dir - self.persist = persist - self.attrs = self._da.attrs - self.m = m - self.dynamic = dynamic - self.tile_width = tile_width - self.tile_height = tile_height - self.transform0 = transform0 - self.transform1 = transform1 - self.transform2 = transform2 - self.transform3 = transform3 - self.colormap = colormap - self.colorbar = None - self.colorbar_position = colorbar_position - if self.dynamic: - self.persist = False - self.tile_dir = None - - self._da = self._da.rename({y_dim: 'y', x_dim: 'x'}) - if rgb_dim is None: - self.is_rgb = False - else: - self.is_rgb = True - self._da = self._da.rename({rgb_dim: 'rgb'}) - - # ensure latitudes are descending - if np.any(np.diff(self._da.y.values) >= 0): - self._da = self._da.sel(y=slice(None, None, -1)) - - # infer grid specifications (assume a rectangular grid) - y = self._da.y.values - x = self._da.x.values - - self.x_left = float(x.min()) - self.x_right = float(x.max()) - self.y_lower = float(y.min()) - self.y_upper = float(y.max()) - - self.dx = float((self.x_right - self.x_left) / (x.size - 1)) - self.dy = float((self.y_upper - self.y_lower) / (y.size - 1)) - - if fit_bounds: - asyncio.ensure_future(self.async_fit_bounds()) - else: - asyncio.ensure_future(self.async_wait_for_bounds()) - - self.l = LocalTileLayer() - if self._da.name is not None: - self.l.name = self._da.name - - self._da_notransform = self._da - - self.spinner = Spinner() - self.spinner.radius = 5 - self.spinner.length = 3 - self.spinner.width = 5 - self.spinner.lines = 8 - self.spinner.color = '#000000' - self.spinner.layout.height = '30px' - self.spinner.layout.width = '30px' - self.spinner_control = WidgetControl(widget=self.spinner, position='bottomright') - - return self.l - - - def select(self, draw_control=None): - with self.debug_output: - if draw_control is None: - self._draw_control = DrawControl() - self._draw_control.polygon = {} - self._draw_control.polyline = {} - self._draw_control.circlemarker = {} - self._draw_control.rectangle = { - 'shapeOptions': { - 'fillOpacity': 0.5 - } - } - else: - self._draw_control = draw_control - self._draw_control.on_draw(self._get_selection) - self.m.add_control(self._draw_control) - - - def unselect(self): - with self.debug_output: - self.m.remove_control(self._draw_control) - - - def get_selection(self): - return self._da_selected - - - def _get_selection(self, *args, **kwargs): - with self.debug_output: - if self._draw_control.last_draw['geometry'] is not None: - lonlat = self._draw_control.last_draw['geometry']['coordinates'][0] - lats = [ll[1] for ll in lonlat] - lons = [ll[0] for ll in lonlat] - lt0, lt1 = min(lats), max(lats) - ln0, ln1 = min(lons), max(lons) - self._da_selected = self._da_notransform.sel(y=slice(lt1, lt0), x=slice(ln0, ln1)) - - - def _start(self): - with self.debug_output: - self.m.add_control(self.spinner_control) - self._da, self.transform0_args = get_transform(self.transform0(self._da, debug_output=self.debug_output)) - - self.url = self.m.window_url - - # the jupyter key we look for - # keeping the last "/" to avoid bug with a (strange) filename as "jupyter.ipynb" - key = '/jupyter/' - - # A custom function have been set - if self.get_base_url is not None: - self.base_url = self.get_base_url(self.url) - - # Search for the first "jupyter" in url name - elif self.url.find(key) != -1: - i_jupyter = self.url.find(key) - self.base_url = self.url[:i_jupyter + len(key) -1] - - # We are in an "exotic" environment and a get_base_url function must be set - else: - raise ValueError(f''' - xarray-leaflet default use is in a Jupyter environment. - "jupyter" was not part of {self.url}. - Please provide a get_base_url function. - ''') - - if self.tile_dir is None: - self.tile_temp_dir = tempfile.TemporaryDirectory(prefix='xarray_leaflet_') - self.tile_path = self.tile_temp_dir.name - else: - self.tile_path = self.tile_dir - self.url = self.base_url + '/xarray_leaflet' + self.tile_path + '/{z}/{x}/{y}.png' - self.l.path = self.url - - self.m.remove_control(self.spinner_control) - self._get_tiles() - self.m.observe(self._get_tiles, names='pixel_bounds') - if not self.dynamic: - self._show_colorbar(self._da_notransform) - self.m.add_layer(self.l) - - - def _show_colorbar(self, da): - if self.colorbar_position and self.colormap is not None: - vmin = da.min().values - vmax = da.max().values - fig = plt.figure(figsize=(8, 3)) - ax = fig.add_axes([0.05, 0.8, 0.5, 0.07]); - norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax) - cbar = mpl.colorbar.ColorbarBase(ax, cmap=self.colormap, norm=norm, orientation='horizontal') - f = tempfile.NamedTemporaryFile(suffix='.png', delete=False) - output = Output() - try: - plt.savefig(f.name, bbox_inches='tight') - with output: - display(Image(filename=f.name)) - finally: - os.unlink(f.name) - f.close() - self.colorbar = WidgetControl(widget=output, position=self.colorbar_position, transparent_bg=True) - self.m.add_control(self.colorbar) - plt.close() - - - def _get_tiles(self, change=None): - with self.debug_output: - self.m.add_control(self.spinner_control) - if self.dynamic: - self.tile_temp_dir.cleanup() - self.tile_temp_dir = tempfile.TemporaryDirectory(prefix='xarray_leaflet_') - new_tile_path = self.tile_temp_dir.name - new_url = self.base_url + '/xarray_leaflet' + new_tile_path + '/{z}/{x}/{y}.png' - if self.l in self.m.layers: - self.m.remove_layer(self.l) - - (left, top), (right, bottom) = self.m.pixel_bounds - (south, west), (north, east) = self.m.bounds - z = int(self.m.zoom) # TODO: support non-integer zoom levels? - if self.custom_proj: - resolution = self.m.crs['resolutions'][z] - - if self.web_mercator: - tiles = list(mercantile.tiles(west, south, east, north, z)) - else: - x0, x1 = int(left) // self.tile_width, int(right) // self.tile_width + 1 - y0, y1 = int(top) // self.tile_height, int(bottom) // self.tile_height + 1 - tiles = [mercantile.Tile(x, y, z) for x in range(x0, x1) for y in range(y0, y1)] - - if self.dynamic: - # dynamic maps are redrawn at each interaction with the map - # so we can take exactly the corresponding slice in the original data - da_visible = self._da.sel(y=slice(north, south), x=slice(west, east)) - elif self.web_mercator: - # for static web mercator maps we can't redraw a tile once it has been (partly) displayed, - # so we must slice the original data on tile boundaries - bbox = get_bbox_tiles(tiles) - # take one more source data point to avoid glitches - da_visible = self._da.sel(y=slice(bbox.north + self.dy, bbox.south - self.dy), x=slice(bbox.west - self.dx, bbox.east + self.dx)) - else: - # it's a custom projection or not web mercator, the visible tiles don't translate easily - # to a slice of the original data, so we keep everything - # TODO: slice the data for EPSG3395, EPSG4326, Earth, Base and Simple - da_visible = self._da - - # check if we have some data to show - if 0 not in da_visible.shape: - da_visible, transform1_args = get_transform(self.transform1(da_visible, *self.transform0_args, debug_output=self.debug_output)) - - if self.dynamic: - self.tile_path = new_tile_path - self.url = new_url - - for tile in tiles: - x, y, z = tile - path = f'{self.tile_path}/{z}/{x}/{y}.png' - # if static map, check if we already have the tile - # if dynamic map, new tiles are always created - if self.dynamic or not os.path.exists(path): - if self.web_mercator: - bbox = mercantile.bounds(tile) - xy_bbox = mercantile.xy_bounds(tile) - x_pix = (xy_bbox.right - xy_bbox.left) / self.tile_width - y_pix = (xy_bbox.top - xy_bbox.bottom) / self.tile_height - # take one more source data point to avoid glitches - da_tile = da_visible.sel(y=slice(bbox.north + self.dy, bbox.south - self.dy), x=slice(bbox.west - self.dx, bbox.east + self.dx)) - else: - da_tile = da_visible - # check if we have data for this tile - if 0 in da_tile.shape: - write_image(path, None, self.persist) - else: - da_tile.attrs = self.attrs - da_tile, transform2_args = get_transform(self.transform2(da_tile, tile_width=self.tile_width, tile_height=self.tile_height, debug_output=self.debug_output), *transform1_args) - # reproject each RGB component if needed - # TODO: must be doable with xarray.apply_ufunc - if self.is_rgb: - das = [da_tile.isel(rgb=i) for i in range(3)] - else: - das = [da_tile] - for i in range(len(das)): - das[i] = das[i].rio.write_nodata(self.nodata) - if self.custom_proj: - das[i] = reproject_custom(das[i], self.dst_crs, x, y, z, resolution, resolution, self.tile_width, self.tile_height, self.resampling) - else: - das[i] = reproject_not_custom(das[i], self.dst_crs, xy_bbox.left, xy_bbox.top, x_pix, y_pix, self.tile_width, self.tile_height, self.resampling) - das[i], transform3_args = get_transform(self.transform3(das[i], *transform2_args, debug_output=self.debug_output)) - if self.is_rgb: - alpha = np.where(das[0]==self._da.rio.nodata, 0, 255) - das.append(alpha) - da_tile = np.stack(das, axis=2) - write_image(path, da_tile, self.persist) - else: - da_tile = self.colormap(das[0]) - write_image(path, da_tile*255, self.persist) - - if self.dynamic: - if self.colorbar in self.m.controls: - self.m.remove_control(self.colorbar) - self._show_colorbar(self._da_notransform.sel(y=slice(north, south), x=slice(west, east))) - self.l.path = self.url - self.m.add_layer(self.l) - self.l.redraw() - - self.m.remove_control(self.spinner_control) - - - async def async_wait_for_bounds(self): - with self.debug_output: - if len(self.m.bounds) == 0: - await wait_for_change(self.m, 'bounds') - self.map_ready = True - - - async def async_fit_bounds(self): - with self.debug_output: - center = self.y_lower + (self.y_upper - self.y_lower) / 2, self.x_left + (self.x_right - self.x_left) / 2 - if center != self.m.center: - self.m.center = center - await wait_for_change(self.m, 'bounds') - zoomed_out = False - # zoom out - while True: - if self.m.zoom <= 1: - break - (south, west), (north, east) = self.m.bounds - if south > self.y_lower or north < self.y_upper or west > self.x_left or east < self.x_right: - self.m.zoom = self.m.zoom - 1 - await wait_for_change(self.m, 'bounds') - zoomed_out = True - else: - break - if not zoomed_out: - # zoom in - while True: - (south, west), (north, east) = self.m.bounds - if south < self.y_lower and north > self.y_upper and west < self.x_left and east > self.x_right: - self.m.zoom = self.m.zoom + 1 - await wait_for_change(self.m, 'bounds') - else: - self.m.zoom = self.m.zoom - 1 - await wait_for_change(self.m, 'bounds') - break - self.map_ready = True diff --git a/xarray_leaflet/__pycache__/__init__.cpython-36.pyc b/xarray_leaflet/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 38af4ee063974ec8417039f1735633d8b6faca92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmaJ-yH3L}6m?#0nm)w9fRKPJ3>`?Rx-bB$5F;QYm^^TCYqts5b>yT~+dtx$EH);7 zfeD*d2r7XsAD{cM@42_vY(@{Sw~x7l&>Q-(T*Le36ek8K!iYN9p)Pi*hdt`kfcqlA zfrAXM@QSG7st9o?YPjYg7UGCj?@&8j9^T9Rh_e~vaZZNMWX$4`RPmfBMHVJNMx2$& zvUN#j8I3QL9HvB-%N=U%WU#jTV?i>W45e6Bj+1WE?Y$epsO{+JYW2l6$=jYjfTwA` zD4BwSshPRhykwIilM~2EnHG8%9SU0KcNb%ODFy$Gy@+|~5VeemoFG$sGs)diUPAUcK_ zf0Jv2gM0{N4uDA_X+~n4Sw?dfGm5g8c{z`HBh$=}{V$2+(myBCSA65efu!G)Ua$oZ z zhQ^`vF@SsbxzJjyzENV5DSPFWf&&~lqt_!Kb01hmW{{9%8%U#(!5_+!fH(hm-8m`? zs|wqHx|*wXE6v5J8qSN;b@xb&r>c+Jy3E#tqU;-6YW3&=NMqc4^dMgt+t=z$WT_Mu z7sRp;4nF%B5^o!2lc^F?Y0$V)+2l8(L%7r1gWgX9%`djiHc}}Wm z=F1SQUYw9(ztJK*BU`n1LTpg^8~PPkKY=DzpnXc}pbBLBCD%WL4L^ZZc?-0M@>cD? zdiab~T>lO|4L5KC`w7vh>}=Sz_^oU4$t;pxxp#?#uxf26MDl*3nn4aNyg4O}L{VxG zvVZ)R4!2m?wLg$$p&p<9`U{RfzR-v@$6o@>T_6>JQ9a#a9k_sYKAIOB KK@~bQV*dhG6mWe2 diff --git a/xarray_leaflet/__pycache__/server_extension.cpython-36.pyc b/xarray_leaflet/__pycache__/server_extension.cpython-36.pyc deleted file mode 100644 index 2db5825103be0c7ee20431a8d0b7a3f94f4c4092..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1121 zcmaJ=L2uJA6tKxzffLV8S8YAuD$mdHdwt*c-j^r+e)se5cSlnnp+D%#^1y!yk9rD*A%;sd z!()sQ^O#?HGk@%ZAF$33G!9tEBG@}DW;?Kltos2ayB8Qz6KwWBQ7P%!TTUk>*KcUW zN-i(p&R+H#S*CNUi*#CxDz_a;%>C5TOQ+5J42q@OAsr~CVlb!^;sD>ovIl-{UtBJW*p1{CI2u%8)nHZd zGdRZ3^l)V2s@6QK>n|o$nre}oz3b2xcdws^8;nU{BE{XHjIU&F0>+g#k>p<+Avvfe zU?SJ9Cs)iaU@-?9T2;3BLnka{u`1g~guV4o-iFgPW_CJ`nGLkO1a^%4#djx$_gpu! zQiPIHC{5}Ku~0xYR0^)hiO>a^aZMT3lmP5piVQ^HazSd66J3*usF>5-(f=oLv=MBp zL=(dbsDu{W$&y3aK%Z diff --git a/xarray_leaflet/__pycache__/transform.cpython-36.pyc b/xarray_leaflet/__pycache__/transform.cpython-36.pyc deleted file mode 100644 index 6e919d8a11643e319f7dfe522d1214e96b02d05d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1215 zcmZ8gOK%%D5GJ|YU20`3ju8~;`%*MmAh3btY8Wk=v(u)B1VRy&7Aa{r`w+;LEd|_@ z4YZde$NYx;gZvhvx1RhLdg=`2I6-zXFLF4X$H#}eyV>)9e*N<~C*)u9$rpkC6Ocuw zh@ur4ZzUbGU`J!Fn2IjRI96Q6AQR2Yv=rr_%w|+2D!ruRp`yQ&TwFmEax$eT;KS&s zKL)aJHE{&I=*c^hM?RLu$c^XH%&kveoTD@hoC5pr_;AtI`fzDveJWS#uqsaN;X0g| zR9em|-F`j)+>J?VHEUywK7P}CAN>{7M! zic#`6cwfN&Q$XNv48Ubx+rx zZoD{cSB;Wpg;fENY>EcZ zIf4Vz#pK4`u|>VC^sJ~lZT#rTs_BYaKi#_L!_Q`KZb424=QS2v{60_sn+?#~v-9_LrUC-hAd*VFATFgqRZ*xyX%R)1sM&bN$vWO$GdpgQ zYWB%R4G)Ol!CxBj##8?SLMq?cEotel=FFMW++M z2X$9=We;^vuE{>?H93&$sQZ%NX2Xp~IGODXxeKmm$)17|em0$^xthc@f^P$*h4KYT zxesB3Dwe$JSRsY=&#-gMwl>kb$*L~;dp7K_YONWw9!4XKh8XP|W@Fp2UE8y3w!aFi zfn_s36PDY7jcjWwY+HK2`PI5zhjIRn<~Z*W!8G(-+vp@Or-ja3Q0^xSY(o00AA@?uUdl_+E4ueFZ55n(6=oc!V8fU z@WQ|j;03So0ss!Mtqw`hNly^hAT`ru#&BH|XLN@|mqd?5pTvL!*{JC<^?H4Vv`rE( zkT^@?MH1&ooG0-T2|~3IuRwE~Pch01c(lfec6bIS!YV|mae5776CwwF{*zL(`Fk`B z7>FWfo@Fa8Ic_dEvk1R~NN*Y6BGgu57OWU&`Aa68wM;Z?TfV|ydDff7w2m{=vCN7U z)=E}$_dCQ=q>fUsr8scnaCg{sq9|RIB?n5TAfmrWK(`k3Jjs;vi-pQvFqs#n(!_b! zp6Vj2nd3yhaKSRo%?HjelY7eb4|QtPSQfb&Pt&;?`ua^;TRZed(IVd)6Jq*3^fx5=KQ;8b7@$%TCmCYI=Uq|vPAHMkMK;uSC;=?p6NLHOsEDA1_ydp&W&qRH5;ZtHPPT5*0Ur^`M8)J?K947IYtT1oQ~H z`1!&ehShD1QZHZmRm--(53%FCG@lM*7fk0xVqBalJxOw80_DQpVwua*d0BGgg1pe6 ziI=4y2XYy7{}5@%`6_{oZ{&M767%_PaWsrvK3iiZuo)FXDz$S z3E(`k0D6xRU96Z2K*4v00@fWPp$BHU0F^x#A}ufH#sv-g{Y3AT&-1uPmY%)|S8!li zzgc9apbVhDLf-Mbf>S*KgDrPJcknuz5}E2fTjOG^Ol13)R6})2w!8 pz3nqRsJUBP6n&f(ayeI*$v-8%AQAFe1Yl6SsEa?2qNo?O{{`*GHtPTY diff --git a/xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc b/xarray_leaflet/__pycache__/xarray_leaflet.cpython-36.pyc deleted file mode 100644 index 63f1603ca3d661e7669deb7d91abc2ea70d747fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13195 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 From 3840dbe0cb3e8ecf8eb90ffdaaa666faf28d68eb Mon Sep 17 00:00:00 2001 From: Pierrick Rambaud Date: Tue, 16 Mar 2021 16:49:28 +0000 Subject: [PATCH 4/4] use f strings You were already using this format once (which was preventing any usage for Python version prior to 3.6 I just made consistent through the whole file --- xarray_leaflet/xarray_leaflet.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xarray_leaflet/xarray_leaflet.py b/xarray_leaflet/xarray_leaflet.py index 9760cdf..40085a2 100644 --- a/xarray_leaflet/xarray_leaflet.py +++ b/xarray_leaflet/xarray_leaflet.py @@ -121,19 +121,19 @@ def plot(self, if 'proj4def' in m.crs: # it's a custom projection if dynamic: - raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) + raise RuntimeError(f'Dynamic maps are only supported for Web Mercator (EPSG:3857), not {m.crs}') self.dst_crs = m.crs['proj4def'] self.web_mercator = False self.custom_proj = True elif m.crs['name'].startswith('EPSG'): epsg = m.crs['name'][4:] if dynamic and epsg != '3857': - raise RuntimeError('Dynamic maps are only supported for Web Mercator (EPSG:3857), not {}'.format(m.crs)) + raise RuntimeError(f'Dynamic maps are only supported for Web Mercator (EPSG:3857), not {m.crs}') self.dst_crs = 'EPSG:' + epsg self.web_mercator = epsg == '3857' self.custom_proj = False else: - raise RuntimeError('Unsupported map projection: {}'.format(m.crs)) + raise RuntimeError(f'Unsupported map projection: {m.crs}') self.nodata = self._da.rio.nodata var_dims = self._da.dims @@ -143,8 +143,8 @@ def plot(self, if set(var_dims) != set(expected_dims): raise ValueError( "Invalid dimensions in DataArray: " - "should include only {}, found {}." - .format(tuple(expected_dims), var_dims) + f"should include only {tuple(expected_dims)}, found {var_dims}." + ) if rgb_dim is not None and colormap is not None: