From bbc108c40bc4b4a4d585c332e62c08dc3fe8360f Mon Sep 17 00:00:00 2001 From: tsutterley Date: Tue, 8 Mar 2022 15:09:08 -0800 Subject: [PATCH] start working on imageservicelayer function throttle updates on move try adding initimage --- examples/CustomProjections.ipynb | 3 +- ipyleaflet/leaflet.py | 5 +- js/src/jupyter-leaflet.js | 2 +- js/src/layers/ImageServiceLayer.js | 66 +++-------- js/src/leaflet-imageservice.js | 172 +++++++++++++++++++++++++++++ js/src/leaflet.js | 1 + 6 files changed, 191 insertions(+), 58 deletions(-) create mode 100644 js/src/leaflet-imageservice.js diff --git a/examples/CustomProjections.ipynb b/examples/CustomProjections.ipynb index 04ea5050c..2087a251e 100644 --- a/examples/CustomProjections.ipynb +++ b/examples/CustomProjections.ipynb @@ -84,7 +84,8 @@ " \"\"\",\n", " layers=\"MOA_125_HP1_090_230\",\n", " format='image/png',\n", - " transparent=True,\n", + " transparent=False,\n", + " opacity=0.5,\n", " url='https://nimbus.cr.usgs.gov/arcgis/services/Antarctica/USGS_EROS_Antarctica_Reference/MapServer/WmsServer',\n", " crs=MOA3031\n", ")\n", diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 2d19d5868..ab9544114 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -768,7 +768,7 @@ class ImageServiceLayer(ImageOverlay): Attributes ---------- url: string, default "" - Url to the image service. + Url to the image service f: string, default "image" response format (use `'image'` to stream as bytes) format: string, default "png" @@ -814,8 +814,7 @@ class ImageServiceLayer(ImageOverlay): mosaic_rule = Dict({}).tag(sync=True, o=True) transparent = Bool(False).tag(sync=True, o=True) crs = Dict(default_value=projections.EPSG3857).tag(sync=True) - _url = Unicode().tag(sync=True) - _bounds = Unicode().tag(sync=True) + class Heatmap(RasterLayer): """Heatmap class. diff --git a/js/src/jupyter-leaflet.js b/js/src/jupyter-leaflet.js index 8a9e6e8e3..bf0ceaba2 100644 --- a/js/src/jupyter-leaflet.js +++ b/js/src/jupyter-leaflet.js @@ -12,10 +12,10 @@ export * from './layers/TileLayer.js'; export * from './layers/VectorTileLayer.js'; export * from './layers/LocalTileLayer.js'; export * from './layers/WMSLayer.js'; -export * from './layers/ImageServiceLayer.js'; export * from './layers/MagnifyingGlass.js'; export * from './layers/ImageOverlay.js'; export * from './layers/VideoOverlay.js'; +export * from './layers/ImageServiceLayer.js'; export * from './layers/Velocity.js'; export * from './layers/Heatmap.js'; export * from './layers/VectorLayer.js'; diff --git a/js/src/layers/ImageServiceLayer.js b/js/src/layers/ImageServiceLayer.js index e932cb6a3..b531cbf2e 100644 --- a/js/src/layers/ImageServiceLayer.js +++ b/js/src/layers/ImageServiceLayer.js @@ -3,7 +3,6 @@ const L = require('../leaflet.js'); const imageoverlay = require('./ImageOverlay.js'); -const proj = require('../projections.js'); export class LeafletImageServiceLayerModel extends imageoverlay.LeafletImageOverlayModel { defaults() { @@ -11,6 +10,7 @@ export class LeafletImageServiceLayerModel extends imageoverlay.LeafletImageOver ...super.defaults(), _view_name: 'LeafletImageServiceLayerView', _model_name: 'LeafletImageServiceLayerModel', + // image server url url: '', // response format f: 'image', @@ -19,7 +19,7 @@ export class LeafletImageServiceLayerModel extends imageoverlay.LeafletImageOver // data type of the raster image pixel_type: 'F32', // pixel value or list of pixel values representing no data - no_data: null, + no_data: [], // how to interpret no data values no_data_interpretation: '', // resampling process for interpolating the pixel values @@ -27,75 +27,35 @@ export class LeafletImageServiceLayerModel extends imageoverlay.LeafletImageOver // lossy quality for image compression compression_quality: '', // order of bands to export for multiple band images - band_ids: null, + band_ids: [], // time instance or extent for image - time: null, + time: [], // rules for rendering - rendering_rule: null, + rendering_rule: {}, // rules for mosaicking - mosaic_rule: null, + mosaic_rule: {}, // image transparency transparent: false, // coordinate reference system - crs: null, - _url: '', - _bounds: null, + crs: null }; } } export class LeafletImageServiceLayerView extends imageoverlay.LeafletImageOverlayView { create_obj() { - this.model._url = this.model.get('url') + '/exportImage' + this.buildParams() - this.model._bounds = this._map.getBounds() - this.obj = L.ImageOverlay(this.model._url, this.model._bounds) + this.obj = L.imageServiceLayer( + this.get_options() + ) } model_events() { super.model_events(); for (var option in this.get_options()) { - this._map.on('change:' + option, () => { - this.model._url = this.model.get('url') + '/exportImage' + this.buildParams() - this.model._bounds = this._map.getBounds() - this.refresh(); + this.model.on('change:' + option, () => { + this.obj.updateUrl(); + this.obj.refresh() }); - } - } - - model_epsg() { - // get the EPSG code of the current map - var crs = proj.getProjection(this.model.get('crs')) - var spatial_reference = parseInt(crs.code.split(':')[1], 10); - return spatial_reference - } - - buildParams () { - // parameters for image server query - var params = { - bbox: this._map.getBounds(), - size: this._map.getSize(), - bboxSR: 4326, - imageSR: this.model_epsg(), - ...this.get_options() }; - // merge list parameters - if (params['noData']) { - params['noData'] = params['noData'].join(','); - } - if (params['bandIds']) { - params['bandIds'] = params['bandIds'].join(','); - } - if (params['time']) { - params['time'] = params['time'].join(','); - } - // convert dictionary parameters to JSON - if (params['renderingRule']) { - params['renderingRule'] = JSON.stringify(params['renderingRule']); - } - if (params['mosaicRule']) { - params['mosaicRule'] = JSON.stringify(params['mosaicRule']); - } - // return the formatted query string - return L.Util.getParamString(params); } } diff --git a/js/src/leaflet-imageservice.js b/js/src/leaflet-imageservice.js new file mode 100644 index 000000000..e628f5501 --- /dev/null +++ b/js/src/leaflet-imageservice.js @@ -0,0 +1,172 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. +const proj = require('./projections.js'); + +L.ImageServiceLayer = L.ImageOverlay.extend({ + options: { + // image server url + url: '', + // response format + f: 'image', + // output image format + format: 'png', + // data type of the raster image + pixel_type: 'F32', + // pixel value or list of pixel values representing no data + no_data: [], + // how to interpret no data values + no_data_interpretation: '', + // resampling process for interpolating the pixel values + interpolation: '', + // lossy quality for image compression + compression_quality: '', + // order of bands to export for multiple band images + band_ids: [], + // time instance or extent for image + time: [], + // rules for rendering + rendering_rule: {}, + // rules for mosaicking + mosaic_rule: {}, + // image transparency + transparent: false, + // coordinate reference system + crs: null, + // update interval for panning + updateInterval: 200 + }, + + initialize: function(options) { + this._url = this.get('url') + '/exportImage' + _buildParams(); + this._bounds = this._getBounds(); + L.Util.setOptions(this, options); + L.ImageOverlay.prototype.initialize.call(this._url, this._bounds); + }, + + updateUrl: function() { + this._url = this.get('url') + '/exportImage' + _buildParams(); + this._bounds = this._getBounds(); + L.ImageOverlay.prototype.setUrl.call(this._url); + L.ImageOverlay.prototype.setBounds.call(this._bounds); + }, + + onAdd: function (map) { + this._map = map; + this.updateUrl(); + if (!this._image) { + this._initImage(); + } + this._map.on('moveend', () => { + L.Util.throttle(this.updateUrl(),this.options.updateInterval,this); + L.ImageOverlay.prototype._reset.call(this); + }); + L.ImageOverlay.prototype.onAdd.call(this); + }, + + onRemove: function (map) { + L.ImageOverlay.prototype.onRemove.call(this, map); + }, + + bringToFront: function () { + L.ImageOverlay.prototype.bringToFront.call(this); + return this; + }, + + bringToBack: function () { + L.ImageOverlay.prototype.bringToBack.call(this); + return this; + }, + + setUrl: function (url) { + this._url = url; + L.ImageOverlay.prototype.setUrl.call(this._url); + return this; + }, + + setBounds: function (bounds) { + this._bounds = bounds; + L.ImageOverlay.prototype.setBounds.call(this._bounds); + return this; + }, + + getBounds: function () { + return this._bounds; + }, + + _getBBox: function() { + // get the bounding box of the current map formatted for exportImage + var pixelbounds = this._map.getPixelBounds(); + var sw = map.unproject(pixelbounds.getBottomLeft()); + var ne = map.unproject(pixelbounds.getTopRight()); + return [this._map.options.crs.project(ne),this._map.options.crs.project(sw)]; + }, + + _getBounds: function() { + // get the bounds of the current map for the ImageOverlay + return [[this._map.getBounds().getSouth(),this._map.getBounds().getWest()], + [this._map.getBounds().getNorth(),this._map.getBounds().getEast()]]; + }, + + _getSize: function() { + // get the size of the current map + var size = this._map.getSize(); + return [size.x, size.y]; + }, + + _getEPSG: function() { + // get the EPSG code of the current map + var crs = proj.getProjection(this.model.get('crs')); + var spatial_reference = parseInt(crs.code.split(':')[1], 10); + return spatial_reference; + }, + + _buildParams: function() { + // parameters for image server query + var params = { + bbox: this._getBBox(), + size: this._getSize(), + bboxSR: this._getEPSG(), + imageSR: this._getEPSG(), + ...this.get_options() + }; + // merge list parameters + if (params['noData']) { + params['noData'] = params['noData'].join(','); + } + if (params['bandIds']) { + params['bandIds'] = params['bandIds'].join(','); + } + if (params['time']) { + params['time'] = params['time'].join(','); + } + // convert dictionary parameters to JSON + if (params['renderingRule']) { + params['renderingRule'] = JSON.stringify(params['renderingRule']); + } + if (params['mosaicRule']) { + params['mosaicRule'] = JSON.stringify(params['mosaicRule']); + } + // return the formatted query string + return L.Util.getParamString(params); + }, + + _initImage: function () { + var img = this._image = L.DomUtil.create('img'); + L.DomUtil.addClass(img, 'leaflet-image-layer'); + if (this._zoomAnimated) { L.DomUtil.addClass(img, 'leaflet-zoom-animated'); } + img.onselectstart = L.Util.falseFn; + img.onmousemove = L.Util.falseFn; + img.onload = L.Util.bind(this.fire, this, 'load'); + img.src = this._url; + img.alt = this.options.alt; + }, + + _reset: function () { + L.ImageOverlay.prototype._reset.call(this); + }, + +}); + +L.imageServiceLayer = function (options) { + return new L.ImageServiceLayer(options); +}; diff --git a/js/src/leaflet.js b/js/src/leaflet.js index 65b0027d9..946ed4d9b 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -9,6 +9,7 @@ require('leaflet.markercluster'); require('leaflet-velocity'); require('leaflet-measure'); require('./leaflet-heat.js'); +require('./leaflet-imageservice.js'); require('./leaflet-magnifyingglass.js'); require('leaflet-rotatedmarker'); require('leaflet-fullscreen');