Commit e839a1a9 authored by Klaus Stein's avatar Klaus Stein
Browse files

Add imagemap overlay (and overlay controls) to places

parent 6c3ebda6
L.Control.Appearance = L.Control.extend({
options: {
collapsed: false,
position: 'topright',
label: null,
radioCheckbox: true,
layerName: true,
opacity: false,
color: false,
remove: false,
removeIcon: null,
},
initialize: function (baseLayers, uneditableOverlays, overlays, options) {
L.Util.setOptions(this, options);
this._layerControlInputs = [];
this._layers = [];
this._lastZIndex = 0;
this._handlingClick = false;
for (var i in baseLayers) {
this._addLayer(baseLayers[i], i);
}
for (var i in uneditableOverlays) {
this._addLayer(uneditableOverlays[i], i, true, true);
}
for (var i in overlays) {
this._addLayer(overlays[i], i, true);
}
},
onAdd: function (map) {
this._initLayout();
this._update();
return this._container;
},
// @method addOverlay(layer: Layer, name: String): this
// Adds an overlay (checkbox entry) with the given name to the control.
addOverlay: function (layer, unremovable) {
this._addLayer(layer, layer.options.name, true, unremovable);
return (this._map) ? this._update() : this;
},
_onLayerChange: function (e) {
if (!this._handlingClick) {
this._update();
}
var obj = this._getLayer(Util.stamp(e.target));
// @namespace Map
// @section Layer events
// @event baselayerchange: LayersControlEvent
// Fired when the base layer is changed through the [layer control](#control-layers).
// @event overlayadd: LayersControlEvent
// Fired when an overlay is selected through the [layer control](#control-layers).
// @event overlayremove: LayersControlEvent
// Fired when an overlay is deselected through the [layer control](#control-layers).
// @namespace Control.Layers
var type = obj.overlay ?
(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
(e.type === 'add' ? 'baselayerchange' : null);
if (type) {
this._map.fire(type, obj);
}
},
expand: function () {
L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
this._form.style.height = null;
var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
if (acceptableHeight < this._form.clientHeight) {
L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
this._form.style.height = acceptableHeight + 'px';
} else {
L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
}
return this;
},
collapse: function () {
L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
return this;
},
_initLayout: function () {
var className = 'leaflet-control-layers',
container = this._container = L.DomUtil.create('div', className),
collapsed = this.options.collapsed;
container.setAttribute('aria-haspopup', true);
L.DomEvent.disableClickPropagation(container);
L.DomEvent.disableScrollPropagation(container);
if(this.options.label){
var labelP = L.DomUtil.create('p', className + "-label");
labelP.innerHTML = this.options.label;
container.appendChild(labelP);
}
var form = this._form = L.DomUtil.create('form', className + '-list');
if (collapsed) {
if (!L.Browser.android) {
L.DomEvent.on(container, {
mouseenter: this.expand,
mouseleave: this.collapse
}, this);
}
}
if (!collapsed) {
this.expand();
}
this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
this._separator = L.DomUtil.create('div', className + '-separator', form);
this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
container.appendChild(form);
},
_update: function () {
if (!this._container) { return this; }
L.DomUtil.empty(this._baseLayersList);
L.DomUtil.empty(this._overlaysList);
this._layerControlInputs = [];
var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
for (i = 0; i < this._layers.length; i++) {
obj = this._layers[i];
this._addItem(obj);
overlaysPresent = overlaysPresent || obj.overlay;
baseLayersPresent = baseLayersPresent || !obj.overlay;
baseLayersCount += !obj.overlay ? 1 : 0;
}
this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
return this;
},
_getLayer: function (id) {
for (var i = 0; i < this._layers.length; i++) {
if (this._layers[i] && L.Util.stamp(this._layers[i].layer) === id) {
return this._layers[i];
}
}
},
_addLayer: function (layer, name, overlay, uneditable) {
this._layers.push({
layer: layer,
name: name,
overlay: overlay,
uneditable: uneditable
});
},
_removeLayer: function (id) {
for (var i = 0; i < this._layers.length; i++) {
if (this._layers[i] && L.Util.stamp(this._layers[i].layer) === id) {
this._layers.splice(i,1);
break;
}
}
},
_addItem: function (obj) {
var label = document.createElement('label'),
checked = this._map.hasLayer(obj.layer),
layerName = obj.name
opacity = obj.layer.options.opacity,
color = obj.layer.options.color,
elements = [];
//HTML Elements for OVERLAY
if (obj.overlay) {
if (this.options.radioCheckbox){elements.push(this._createCheckboxElement('leaflet-control-layers-selector', checked))};
if (this.options.layerName){elements.push(this._createNameElement('leaflet-control-layers-name', layerName))};
if (this.options.opacity){elements.push(this._createRangeElement('leaflet-control-layers-range', opacity))};
if (this.options.color && !obj.uneditable){elements.push(this._createColorElement('leaflet-control-layers-color', color))};
if (this.options.remove && !obj.uneditable){elements.push(this._createRemoveElement('leaflet-control-layers-remove'))};
}else{
if (this.options.radioCheckbox){elements.push(this._createRadioElement('leaflet-control-layers-selector', checked))};
if (this.options.layerName){elements.push(this._createNameElement('leaflet-control-layers-name', layerName))};
}
var holder = document.createElement('div');
holder.style = "display: flex; align-items: baseline;";
label.appendChild(holder);
for (var i = 0; i < elements.length; i++) {
holder.appendChild(elements[i]);
if (i == 1){continue}; //layer name don't need UI
this._layerControlInputs.push(elements[i]);
elements[i].layerId = L.Util.stamp(obj.layer);
switch(elements[i].className){
case "leaflet-control-layers-range":
L.DomEvent.on(elements[i], 'change', this._onRangeClick, this);
break;
case "leaflet-control-layers-selector":
L.DomEvent.on(elements[i], 'change', this._onRadioCheckboxClick, this);
break;
case "leaflet-control-layers-color":
L.DomEvent.on(elements[i], 'change', this._onColorClick, this);
break;
case "leaflet-control-layers-remove":
L.DomEvent.on(elements[i], 'change', this._onRemoveClick, this);
break;
}
};
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
return label;
},
_createRadioElement: function (name, checked) {
var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
name + '"' + (checked ? ' checked="checked"' : '') + '/>';
var radioFragment = document.createElement('div');
radioFragment.innerHTML = radioHtml;
return radioFragment.firstChild;
},
_createCheckboxElement: function (name, checked) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = name;
input.defaultChecked = checked;
return input;
},
_createNameElement: function (name, layerName) {
var nameLabel = document.createElement('span');
nameLabel.style.display = "inline-block";
nameLabel.style.width = "100px";
nameLabel.style.margin = "0 5 0 5";
nameLabel.style.overflow = "hidden";
nameLabel.style.verticalAlign = "middle";
nameLabel.innerHTML = ' ' + layerName;
return nameLabel;
},
_createRangeElement: function (name, opacity) {
input = document.createElement('input');
input.type = 'range';
input.style.width = "50px";
input.className = name;
input.min = 0;
input.max = 100;
input.value = opacity * 100;
return input;
},
_createColorElement: function (name, color) {
var colorHtml = '<input type="color" class="leaflet-control-layers-color" value="' +
color + '"' +
'list="data1"/ style="width:50px; margin:0 5 0 5;">';
var colorFragment = document.createElement('div');
colorFragment.innerHTML = colorHtml;
return colorFragment.firstChild;
},
_createRemoveElement: function (name, imgUrl) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = name;
input.defaultChecked = true;
imgUrl = this.options.removeIcon;
if (imgUrl){
input.style = "-webkit-appearance:none; background:url(" + imgUrl + "); width:1rem; height:1rem; background-size: contain;";
}
return input;
},
_onRadioCheckboxClick: function () {
var inputs = this._layerControlInputs,
input, layer;
var addedLayers = [],
removedLayers = [];
this._handlingClick = true;
for (var i = 0; i < inputs.length; i++) {
input = inputs[i];
if (input.className != "leaflet-control-layers-selector"){continue};
layer = this._getLayer(input.layerId).layer;
if (input.checked) {
this._map.addLayer(layer);
} else if (!input.checked) {
this._map.removeLayer(layer);
}
}
for (i = 0; i < removedLayers.length; i++) {
if (this._map.hasLayer(removedLayers[i])) {
this._map.removeLayer(removedLayers[i]);
}
}
for (i = 0; i < addedLayers.length; i++) {
if (!this._map.hasLayer(addedLayers[i])) {
this._map.addLayer(addedLayers[i]);
}
}
this._handlingClick = false;
this._refocusOnMap();
},
_onRangeClick: function () {
var inputs = this._layerControlInputs,
input, layer;
this._handlingClick = true;
for (var i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
if (input.className != "leaflet-control-layers-range"){continue};
layer = this._getLayer(input.layerId).layer;
//undefined = overlay, not undefined = tilemap
if( typeof layer._url === 'undefined'){
var rangeVal = parseFloat(parseInt(input.value / 10)/10);
var style = {"opacity":rangeVal,
"fillOpacity":(rangeVal / 2)};
layer.setStyle(style);
layer.options.opacity = rangeVal;
layer.options.fillOpacity = rangeVal / 2;
}else{
layer.setOpacity(input.value / 100);
}
}
this._handlingClick = false;
this._refocusOnMap();
},
_onColorClick: function () {
var inputs = this._layerControlInputs,
input, layer;
this._handlingClick = true;
for (var i = 0; i < inputs.length; i++) {
input = inputs[i];
if (input.className != "leaflet-control-layers-color"){continue};
layer = this._getLayer(input.layerId).layer;
//not tilemap
if( typeof layer._url === 'undefined'){
var style = {"color":input.value,
"opacity":layer.options.opacity,
"fillOpacity":layer.options.fillOpacity};
layer.setStyle(style);
layer.options.color = input.value;
};
}
this._handlingClick = false;
this._update();
this._refocusOnMap();
},
_onRemoveClick: function () {
var inputs = this._layerControlInputs,
input, layer;
this._handlingClick = true;
for (var i = 0; i < inputs.length; i++) {
input = inputs[i];
if (input.className != "leaflet-control-layers-remove"){continue};
if (!input.checked){
layer = this._getLayer(input.layerId).layer;
this._map.removeLayer(layer);
this._removeLayer(input.layerId);
break;
}
}
this._handlingClick = false;
this._update();
this._refocusOnMap();
},
});
L.control.appearance = function (baseLayers, uneditableOverlays, overlays, options) {
return new L.Control.Appearance(baseLayers, uneditableOverlays, overlays, options);
};
\ No newline at end of file
leaflet-providers: https://raw.githubusercontent.com/leaflet-extras/leaflet-providers/master/leaflet-providers.js
Leaflet.Control.Appearance: https://raw.githubusercontent.com/Kanahiro/Leaflet.Control.Appearance/master/L.Control.Appearance.js
......@@ -18,6 +18,7 @@ import 'leaflet-toolbar/dist/leaflet.toolbar.css'
import 'leaflet-distortableimage/dist/leaflet.distortableimage.css'
import '../leaflet-plugins/leaflet-providers'
import '../leaflet-plugins/L.Control.Appearance.js'
import "../src/overview-map"
import "../src/umm-map"
......
......@@ -5,15 +5,18 @@ import 'leaflet-distortableimage';
function mapWithPlace(mapdiv) {
const range_template = document.getElementById('range_template');
const basecenter = [50.5,10];
const basezoom = 5;
let fullview = true;
const map = L.map(mapdiv,
{
center: [50.5,10],
zoom: 5,
center: basecenter,
zoom: basezoom,
zoomSnap: 0.25,
zoomDelta: 0.25
});
const osm_de = L.tileLayer.provider('OpenStreetMap.DE').addTo(map);
let all_corners = [];
let citybounds = L.latLngBounds();
fetch(mapdiv.dataset.geojsonurl)
.then(r => r.json())
......@@ -27,6 +30,7 @@ function mapWithPlace(mapdiv) {
if(mapdiv.dataset.centerpoint) {
L.geoJSON(JSON.parse(mapdiv.dataset.centerpoint), {
pointToLayer: function(f,latlng) {
citybounds.extend(latlng.toBounds(4000));
return L.circleMarker(latlng, { radius: 1, color: '#c00' });
}
}).addTo(map);
......@@ -43,32 +47,81 @@ function mapWithPlace(mapdiv) {
}
function addGeojson(data) {
L.geoJSON(data,
{
onEachFeature: addTooltip
// pointToLayer: addPoint
}
).addTo(map);
if(data.geometry.type !== "Point") {
const shape = L.geoJSON(data,
{
onEachFeature: addTooltip
// pointToLayer: addPoint
}
).addTo(map);
citybounds.extend(shape.getBounds());
}
}
const image_overlays = {};
function addImagemaps(data) {
data.filter(im => im.anchors).forEach(addImagemap);
if(all_corners.length > 1) {
map.fitBounds(all_corners);
if (!fullview) map.fitBounds(citybounds);
// this for loop with break is the fastest way to figure
// whether image_overlays is empty in stupid JS.
for(let i in image_overlays) {
/*
Due to bugs in leaflet-distortableimage hiding and showing
the layers several times does not work. We therefore want
to hide the radio buttons.
Unfortunately, using the option radioCheckbox: true
breaks the opacity control (another leaflet-distortableimage
bug), I could not figure why.
Therefore wie hide the boxes in CSS (see _place.scss).
*/
const appearanceControl =
L.control.appearance({}, {},
image_overlays,
{opacity:true,
color: false,
collapsed: true
});
appearanceControl.addTo(map);
break;
}
(new (L.Control.extend({
options: {position: 'topleft'},
onAdd: function (map) {
const cdiv = L.DomUtil.create('div','leaflet-bar');
L.DomEvent.disableClickPropagation(cdiv);
const button = L.DomUtil.create('div','toggle-zoom',cdiv);
L.DomUtil.create('div','symbol',button);
button.addEventListener('click',evt => {
if(fullview) {
map.flyToBounds(citybounds);
} else {
map.flyTo(basecenter,basezoom);
}
fullview = !fullview;
});
return cdiv;
}
}))()).addTo(map);
}
function addImagemap(im) {
const corners = im.anchors.coordinates.map(c => L.latLng(c[1],c[0]));
all_corners = all_corners.concat(corners);
fullview = false;
const corners = im.anchors.coordinates.map(c => {
const ll = L.latLng(c[1],c[0]);
citybounds = citybounds.extend(ll);
return ll;
});
const img = L.distortableImageOverlay(im.img_hd,
{ corners: corners,
editable: false,
suppressToolbar: true
});
img.addTo(map);
image_overlays[im.name] = img;
window.img = img;
/*
const li = range_template.cloneNode(true);
li.id = 'li_im_' + im.id;
const slider = li.querySelector('input[type="range"]')
......@@ -84,6 +137,7 @@ function mapWithPlace(mapdiv) {
slider.value});
label.addEventListener('click', evt => img.bringToFront());
*/
}
}
......
......@@ -7,12 +7,35 @@ main.show.place {
margin-right: 0px;
width: 60%;
min-width: 200px;
flex-shrink: 0.1;
.place-map {
height: 80vh;
border: 1px solid grey;
margin-bottom: 1px;
box-sizing: border-box;
.leaflet-control-layers {
min-width: 20px;
min-height: 20px;
input.leaflet-control-layers-selector {
// show/hide is broken with leaflet-distortableimage
// see commentz in place-map.js
display: none;
& + span {
text-overflow: ellipsis;
}
}
}
.toggle-zoom {
width: 30px;
height: 30px;
background: white;
padding: 5px;
box-sizing: border-box;
.symbol { background: #def; height: 100%; }
}
}
}
......@@ -38,7 +61,7 @@ main.show.place {
li {
box-shadow: 0 0 0.8rem #0002;
background: #ff00aa08;
background: #9f000005;
a.map {
display: flex;
align-items: center;
......@@ -55,6 +78,7 @@ main.show.place {
margin-right: 0.5rem;
display: flex;
align-items: center;
flex-shrink: 0;
img {
max-width: 100%;
......
......@@ -34,10 +34,6 @@
<% end %>
</ul>
<% end %>
<hr />
<ul id="ranges">
<li id="range_template"><input type="range" min="0" max="1" step="0.1" value="1" /><label for="range_template"></label></li>
</ul>
</section>
</main>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment