Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add MagnifyingGlass layer #733

Merged
merged 3 commits into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions examples/MagnifyingGlass.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ipyleaflet import Map, MagnifyingGlass, basemaps, basemap_to_tiles"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"m = Map(center=[0, 0], zoom=2)\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dark_matter_layer = basemap_to_tiles(basemaps.CartoDB.DarkMatter)\n",
"magnifying_glass = MagnifyingGlass(layers=[dark_matter_layer])\n",
"m.add_layer(magnifying_glass)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
31 changes: 31 additions & 0 deletions ipyleaflet/leaflet.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,37 @@ class WMSLayer(TileLayer):
uppercase = Bool(False).tag(sync=True, o=True)


class MagnifyingGlass(RasterLayer):
"""MagnifyingGlass class.

Attributes
----------
layers: list, default []
List of layers to show in the magnifying glass.
Don't re-use layer objects already in use by the main map!
"""

_view_name = Unicode('LeafletMagnifyingGlassView').tag(sync=True)
_model_name = Unicode('LeafletMagnifyingGlassModel').tag(sync=True)

# Options
layers = Tuple().tag(trait=Instance(Layer), sync=True, **widget_serialization)

_layer_ids = List()

@validate('layers')
def _validate_layers(self, proposal):
'''Validate layers list.

Makes sure only one instance of any given layer can exist in the
layers list.
'''
self._layer_ids = [layer.model_id for layer in proposal.value]
if len(set(self._layer_ids)) != len(self._layer_ids):
raise LayerException('duplicate layer detected, only use each layer once')
return proposal.value


class ImageOverlay(RasterLayer):
"""ImageOverlay class.

Expand Down
24 changes: 24 additions & 0 deletions js/src/jupyter-leaflet.css
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
color: var(--jp-ui-font-color1);
font-size: 11px;
}

/* Legend Control */
div.leaflet-control-legend {
color: var(--jp-ui-font-color1);
Expand Down Expand Up @@ -155,3 +156,26 @@ div.leaflet-control-legend > i {
div.leaflet-control-legend > p {
display: inline-block;
}

/* Magnifying Glass */
.leaflet-magnifying-glass {
border-radius: 50%;
border: 1px solid gray;
box-shadow: 0 0 5px gray;
position: absolute;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.leaflet-magnifying-glass > .leaflet-container {
height: 100%;
width: 100%;
}

/* Webkit-only workaround for the border-radius clipping bug,
applied to the map container */
.leaflet-magnifying-glass-webkit {
border-radius: 50%;
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
}
1 change: 1 addition & 0 deletions js/src/jupyter-leaflet.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './layers/TileLayer.js';
export * from './layers/VectorTileLayer.js';
export * from './layers/LocalTileLayer.js';
export * from './layers/WMSLayer.js';
export * from './layers/MagnifyingGlass.js';
export * from './layers/ImageOverlay.js';
export * from './layers/VideoOverlay.js';
export * from './layers/Velocity.js';
Expand Down
47 changes: 47 additions & 0 deletions js/src/layers/MagnifyingGlass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

const widgets = require('@jupyter-widgets/base');
const L = require('../leaflet.js');
const rasterlayer = require('./RasterLayer.js');
const layer = require('./Layer.js');

export class LeafletMagnifyingGlassModel extends rasterlayer.LeafletRasterLayerModel {
defaults() {
return {
...super.defaults(),
_view_name: 'LeafletMagnifyingGlassView',
_model_name: 'LeafletMagnifyingGlassModel',
layers: []
};
}
}

LeafletMagnifyingGlassModel.serializers = {
...widgets.WidgetModel.serializers,
layers: { deserialize: widgets.unpack_models }
};

export class LeafletMagnifyingGlassView extends layer.LeafletLayerView {
add_layer_model(child_model) {
return this.create_child_view(child_model).then(child_view => {
this.layers.push(child_view.obj);
return child_view;
});
}

create_obj() {
this.layers = [];
this.layer_views = new widgets.ViewList(
this.add_layer_model,
this.remove_layer_view,
this
);
this.layer_views.update(this.model.get('layers'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is async, so the line after your list might be empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, that was it, thanks!

this.obj = L.magnifyingGlass({layers: this.layers});
}

model_events() {
super.model_events();
}
}
161 changes: 161 additions & 0 deletions js/src/leaflet-magnifyingglass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// The code below was copied from the following source:
// https://github.com/bbecquet/Leaflet.MagnifyingGlass

L.MagnifyingGlass = L.Layer.extend({
options: {
radius: 100,
zoomOffset: 3,
layers: [ ],
fixedPosition: false,
latLng: [0, 0],
fixedZoom: -1
},

initialize: function(options) {
L.Util.setOptions(this, options);
this._fixedZoom = (this.options.fixedZoom != -1);
this._mainMap = null;
this._glassMap = null;
},

getMap: function(){
return this._glassMap;
},

_createMiniMap: function(elt) {
return new L.Map(elt, {
layers: this.options.layers,
zoom: this._getZoom(),
maxZoom: this._mainMap.getMaxZoom(),
minZoom: this._mainMap.getMinZoom(),
crs: this._mainMap.options.crs,
fadeAnimation: false,
// disable every controls and interaction means
attributionControl: false,
zoomControl: false,
boxZoom: false,
touchZoom: false,
scrollWheelZoom: false,
doubleClickZoom: false,
dragging: false,
keyboard: false,
});
},

_getZoom: function() {
return (this._fixedZoom) ?
this.options.fixedZoom :
this._mainMap.getZoom() + this.options.zoomOffset;
},

_updateZoom: function() {
this._glassMap.setZoom(this._getZoom());
},

setRadius: function(radius) {
this.options.radius = radius;
if(this._wrapperElt) {
this._wrapperElt.style.width = this.options.radius * 2 + 'px';
this._wrapperElt.style.height = this.options.radius * 2 + 'px';
}
},

setLatLng: function(latLng) {
this.options.latLng = latLng;
this._update(latLng);
},

_updateFromMouse: function(evt) {
this._update(evt.latlng, evt.layerPoint);
},

_updateFixed: function() {
this._update(this.options.latLng);
},

_update: function(latLng, layerPoint) {
// update mini map view, forcing no animation
this._glassMap.setView(latLng, this._getZoom(), {
pan : { animate: false }
});

// update the layer element position on the main map,
// using the one provided or reprojecting it
layerPoint = layerPoint || this._mainMap.latLngToLayerPoint(latLng);
this._wrapperElt.style.left = layerPoint.x - this.options.radius + 'px';
this._wrapperElt.style.top = layerPoint.y - this.options.radius + 'px';
},

/**
As defined by ILayer
*/
onAdd: function(map) {
this._mainMap = map;
// create a wrapper element and a container for the map inside it
this._wrapperElt = L.DomUtil.create('div', 'leaflet-magnifying-glass');
var glassMapElt = L.DomUtil.create('div', '', this._wrapperElt);
// Webkit border-radius clipping workaround (see CSS)
if(L.Browser.webkit)
L.DomUtil.addClass(glassMapElt, 'leaflet-magnifying-glass-webkit');
// build the map
this._glassMap = this._createMiniMap(glassMapElt);

// forward some DOM events as Leaflet events
L.DomEvent.addListener(this._wrapperElt, 'click', this._fireClick, this);

var opts = this.options;

this.setRadius(opts.radius);
this.setLatLng(opts.latLng);

this._glassMap.whenReady(function() {
if(opts.fixedPosition) {
this._mainMap.on('zoomend', this._updateFixed, this);
// for now, hide the elements during zoom transitions
L.DomUtil.addClass(this._wrapperElt, ('leaflet-zoom-hide'));
} else {
this._mainMap.on('mousemove', this._updateFromMouse, this);
if(!this._fixedZoom) {
this._mainMap.on('zoomend', this._updateZoom, this);
}
}
}, this);

// add the magnifying glass as a layer to the top-most pane
map.getPanes().popupPane.appendChild(this._wrapperElt);

// needed after the element has been added, otherwise tile loading is messy
this._glassMap.invalidateSize();

return this;
},

_fireClick: function(domMouseEvt) {
this.fire('click', domMouseEvt);
L.DomEvent.stopPropagation(domMouseEvt);
},

/**
As defined by ILayer
*/
onRemove: function(map) {
map.off('viewreset', this._updateFixed, this);
map.off('mousemove', this._updateFromMouse, this);
map.off('zoomend', this._updateZoom, this);
// layers must be explicitely removed before map destruction,
// otherwise they can't be reused if the mg is re-added
for(var i=0, l=this.options.layers.length; i<l; i++) {
this._glassMap.removeLayer(this.options.layers[i]);
}
this._glassMap.remove();
L.DomEvent.removeListener(this._wrapperElt, 'click', this._fireClick);
map.getPanes().popupPane.removeChild(this._wrapperElt);
this._mainMap = null;

return this;
}
});

L.magnifyingGlass = function (options) {
return new L.MagnifyingGlass(options);
};
1 change: 1 addition & 0 deletions js/src/leaflet.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require('leaflet.markercluster');
require('leaflet-velocity');
require('leaflet-measure');
require('./leaflet-heat.js');
require('./leaflet-magnifyingglass.js');
require('leaflet-rotatedmarker');
require('leaflet-fullscreen');
require('leaflet-transform');
Expand Down