Leaflet: using mouseover and opacity get a conflict - javascript

I created a function to mouse over a colorless layer and everything. So far so good, and also created a function to go back as it was before the mouse. But when I use the opacity, the reset function does not return to normal and leaves the opacity in the default state (0.7) and not in the state it is in at the moment.
Function to mouseouver:
function highlightFeature_stComerciais(e) {
layerStComerciais = e.target;
layerStComerciais.setStyle({
weight: 5,
color: '#666',
dashArray: ''
});
info.update(layerStComerciais.feature.properties);
}
function to mouseout
function resetHighlight_stComerciais(e) {
setoresComerciaisOverlay.resetStyle(e.target);
info.update();
}
opacity:
$('#sldOpacity').on('change', function(){
$('#image-opacity').html(this.value);
setoresComerciaisOverlay.setStyle({ fillOpacity: this.value })
});
The default opacity is 0.7, assuming I put 0 of opacity, when I do mouse over the Layer all right, but when I mouse out it returns to 0.7 and I do not want this. Why my reset doesnt work how I want? Thanks!

I guess you build your vector layers using GeoJSON data and L.geoJSON factory, that returns a Leaflet GeoJSON Layer Group.
The resetStyle method on that group re-applies the style that was applied at the time of creation of that group, or the default one if you did not provide style:
Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
If you later change the style of one or all of the vector layers in that group, it will be therefore overridden when you use resetStyle, and the initial style will be applied.
An easy workaround is simply to modify the GeoJSON Layer Group's style option as well. However, it will affect all its child layers.
group.options.style = newStyle;
Another solution would be to record the new style of each vector layer, and use that recorded value when you want to restore the previous state, instead of using the group's resetStyle method.
var map = L.map("map").setView([48.85, 2.35], 12);
var geojson = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [2.35, 48.85]
}
};
var point;
var startStyle = {
color: "red"
};
var newStyle = {
color: 'green'
};
var group = L.geoJSON(geojson, {
style: startStyle,
pointToLayer: function(feature, latlng) {
point = L.circleMarker(latlng);
assignStyle(point, startStyle);
return point;
}
}).addTo(map);
// Record the style to the individual vector layer if necessary.
function assignStyle(leafletLayer, newStyle) {
leafletLayer.setStyle(newStyle);
leafletLayer._recordedStyle = newStyle;
}
// When desired, apply the style that has been previously recorded.
function reassignStyle(leafletLayer) {
leafletLayer.setStyle(leafletLayer._recordedStyle);
}
document.getElementById('button').addEventListener('click', function(event) {
event.preventDefault();
// Either use the wrapper function that records the new style…
//assignStyle(point, newStyle);
// Or simply modify the group's style option, if it is acceptable affecting all child layers.
point.setStyle(newStyle);
group.options.style = newStyle;
});
group.on({
mouseover: function(e) {
e.target.setStyle({
color: 'blue'
});
},
mouseout: function(e) {
//reassignStyle(e.layer);
group.resetStyle(e.layer);
}
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.0.3/dist/leaflet-src.js"></script>
<div id="map" style="height: 100px"></div>
<button id="button">Change color to Green…</button>
<p>…then mouseover and mouseout</p>

Related

Conditionally style Leaflet circleMarkers to be rectangles

I am creating a Leaflet map from GeoJSON data. I want to make some Leaflet markers to be a different shape based on if the geoJSON has images (this is indicated under feature.properties, along with some other HTML for a pop-up). The default shape is an L.circleMarker and I want the relevant markers to turn into rectangles. Unfortunately L.circleMarker does not have a default option to change the shape.
I tried this plugin https://github.com/masajid390/BeautifyMarker but it was really buggy for my purposes. The code just seemed to be applying CSS to markers anyway. See this issue: https://github.com/masajid390/BeautifyMarker/issues/20 where it was suggested to style markers into rectangles with iconStyle : 'border-radius:0!important'. Returning to ordinary Leaflet, if I could get at the CSS for an individual circle I could easily change it by setting the CSS:
.rectangle_marker {
border-radius: 0
}
One approach would be to add a class to the relevant markers, and then style them.
I am already applying some conditional styling as follows, directly setting the L.circleMarker's options:
function style(feature) {
options = drawOptions(feature.properties);
// mark items that have images
if (feature.properties.First) {
options.radius = 11
options.width = 3
}
// mark filter options with different colors
if (feature.properties.Filter) {
if (feature.properties.Filter.toUpperCase() === "BEFORE") {
options.fillColor = "#29A7B3"
} else if (feature.properties.Filter.toUpperCase() === "DURING") {
options.fillColor = "#DC4A35"
} else if (feature.properties.Filter.toUpperCase() === "AFTER") {
options.fillColor = "#215E57"
}
}
return options
}
(in turn, drawOptions looks like this:)
function drawOptions(props) {
return {
radius: 7,
color: "black",
width: 1,
opacity: 1,
fillColor: "#784889",
fillOpacity: 1
}
}
I tried this way: How to use the addClass method in leaflet maker?, but it doesn't work because L.circleMarker doesn't have a built in option like className to set a class. How to add a marker with a specific class to a layer or layergroup? would not work for the same reason.
Tried the following code to indirectly add a class based on How do you add a class to a Leaflet marker?:
function pointToLayer(feature, latlng) {
const marker = L.circleMarker(latlng, drawOptions(feature.properties));
if (check_images(feature.properties)) {
marker._path.classList.add("rectangle_marker")
}
return marker
}
But it doesn't work, because the marker has no attribute _path before being added to the map.
Since I am adding the entire layer to the map, I could conceivably pull out the relevant markers in between creating the geoJSON and adding them to the map, but I'm not sure how:
window.geojson = L.geoJSON(json, {
onEachFeature: onEachFeature,
pointToLayer: pointToLayer,
style: style
});
//What would go here?
window.geojson.addTo(window.map);
Is there any other approach I could take to get some of my L.circleMarkers to appear as squares?
I gave up and used divIcons instead. You can add classNames and add css instead.
function markerOptions(props) {
//default options
const options = {
iconSize: [15, 15],
//mark the center of the icon
iconAnchor: [7, 7],
}
// if location has images, make marker a rectangle
if (check_images(props)) {
options.className = "marker-images"
}
// mark filter options with different colors
if (props.Filter) {
if (props.Filter.toUpperCase() === "BEFORE") {
options.className += " options-before"
} else if (props.Filter.toUpperCase() === "DURING") {
options.className += " options-during"
} else if (props.Filter.toUpperCase() === "AFTER") {
options.className += " options-after"
}
}
//enlarge first location
if (props.First) {
options.className += " first"
options.iconSize = [21, 21]
options.iconAnchor = [10, 10]
}
return options
}
function pointToLayer(feature, latlng) {
return L.marker(latlng, { icon: L.divIcon(markerOptions(feature.properties)) });
}
Then in your css file you can change the border, border-radius, background-color, and other options that are not built in to L.divIcon.

How to change color of first vertex while drawing polygon using Leaflet.Draw?

I am using Leaflet and Leaflet.Draw, and I am letting the user from my code to draw polygon (NOT using the Leaflet Draw Controls).
While the user is drawing the polygon I need to change the color of its first vertex, for example: green, so that user knows that he needs to click on the first point in order to close the polygon and finish drawing.
How can I change color of first vertex while drawing polygon using Leaflet.Draw?
The following image for elaboration, meaning it's fixed with a Paint Software.
P.S. Here is my code
var map = L.map('mapid',
{
minZoom: -1,
maxZoom: 4,
center: [0, 0],
zoom: 1,
crs: L.CRS.Simple
});
var polygonDrawer = new L.Draw.Polygon(map);
map.on('draw:created', function (e) {
var type = e.layerType, layer = e.layer;
layer.editing.enable();
layer.addTo(map);
});
$(document)ready(function(){
polygonDrawer.enable();
});
While I was hacking with the Leaflet.Draw and on the creation of polygon I have come up with the following code:
map.on('draw:drawvertex',
function (e) {
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-editing-icon.leaflet-touch-icon.leaflet-zoom-animated.leaflet-interactive:first").css({ 'background-color': 'green' });
});
So, there is a listener you can insert it in your code, draw:drawvertex which means whenever a vertex created I need to do something.
Then, using jQuery you're selecting the first element from this long selector, and set its background color to green or any other color.
This is a way to do it with CSS only:
#root
> main
> div
> div.col-sm-8.m-auto.p-0.flex-column.float-right
> div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom
> div.leaflet-pane.leaflet-map-pane
> div.leaflet-pane.leaflet-marker-pane
> div:nth-child(2) {
background: green;
}
For me, worked this way (classes are a little bit different. leaflet 1.3.1 and draw 0.4.3)
map.on('draw:drawvertex', function (e) {
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-editing-icon.leaflet-zoom-animated.leaflet-interactive:first").css({ 'background-color': 'green' });
});
This is worked for me:
map.on("editable:vertex:dragend", function (e) {
// Set GREEN color for Vertex START (First) Point
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-vertex-icon.leaflet-zoom-animated.leaflet-interactive.leaflet-marker-draggable:nth-child(1)").css({ 'background-color': 'green' });
// Set RED color for Vertex END (Last) Point
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-vertex-icon.leaflet-zoom-animated.leaflet-interactive.leaflet-marker-draggable:nth-child(2)").css({ 'background-color': 'red' });
});

Leaflet clicking on features

So, I'm trying to map bus routes using leaflet w/geojson for the coordinates. I'm having a difficult time with one aspect where, on a click, the bus line is boldened, and, ideally, the last clicked on feature returns to the default style.
What I have so far
function $onEachFeature(feature, layer) {
layer.on({
click: function(e) {
//calls up the feature clicked on
var $layer = e.target;
var highlightStyle = {
opacity: 1,
weight: 5
};
$layer.bringToFront();
$layer.setStyle(highlightStyle);
}
});
}
//imagine all the leaflet map tile code here
//this is where the features get added in and the $oneachfeature function
var busFeature = L.geoJson(busRoutes, {
style: defaultBusRouteColor,
onEachFeature : $onEachFeature
});
busFeature.addTo(map);
Above, what I have now successfully changes the style of the feature to what's in highlightStyle. However, when another feature is clicked, the style remains. How do I remove the previously clicked on feature's style so that only one feature at a time has the style highlightStyle?
Things I've already tried: using addClass/removeClass to jQuery methods, layer.resetStyle() with leaflet, and a bunch of other things that still didn't work. Note: this would ideally be used in a mobile version, as the desktop version uses a hover function that emphasizes the features, with no problem. this:
function $oneachfeature(feature, layer){
layer.on({
mouseover: function (e){makes feature bold}
});
layer.on({
mouseout: function (e){makes feature normal again}
});
}
Any suggestions?
Store a reference to the highlighted layer so you can later call resetStyle on it:
// Variable to store selected
var selected
// Create new geojson layer
new L.GeoJSON(collection, {
// Set default style
'style': function () {
return {
'color': 'yellow',
}
}
}).on('click', function (e) {
// Check for selected
if (selected) {
// Reset selected to default style
e.target.resetStyle(selected)
}
// Assign new selected
selected = e.layer
// Bring selected to front
selected.bringToFront()
// Style selected
selected.setStyle({
'color': 'red'
})
}).addTo(map)
Example: http://embed.plnkr.co/RnQO1s/preview
Reference: http://leafletjs.com/reference.html#geojson-resetstyle
using resetStyle() would seem to be an easier solution...simply reset the style of the layer before applying the new style to the feature. This requires only a sinlge line of code adding to your original function:
function $onEachFeature(feature, layer) {
layer.on({
click: function(e) {
//calls up the feature clicked on
var $layer = e.target;
var highlightStyle = {
opacity: 1,
weight: 5
};
busFeature.resetStyle();
$layer.bringToFront();
$layer.setStyle(highlightStyle);
}
});
}
Remove previous Highlight before adding the next:
.removeLayer() works to remove the previously set geoJSON selection using .addTo()
theMap = yourMap.Map
geoJson = yourMap.geoJSON();
onclick() {
const highlightedFeature = {
'color': '#12FF38',
'fillColor': '#30D8E0',
'fillOpacity': 0.3
};
this.theMap.removeLayer(this.geoJson);
this.geoJson = yourMap.geoJSON( Feature, {
style: highlightedFeature
});
this.geoJson.addTo(this.theMap);
}

Can't get featureLayer.setStyle to remove highlight on mouseout (mapbox/leaflet)

So, I'm building a map of water taking permits in Toronto. Work in progress available here: http://pennybeames.net/maps/PermitsTO.html
I'm using L.mapbox.featureLayer to add my markers:
var permitsLayer = L.mapbox.featureLayer(permits, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, permitsStyle(feature));}
}).addTo(map);
I've got the custom info control from the Leaflet choropleth example working (http://leafletjs.com/examples/choropleth.html).
I change the style and update the info in the custom control as follows:
featureLayer.eachLayer(function (layer) {
layer.on('mousemove', function (e) {
// highlight feature
layer.setStyle({
weight: 2,
opacity: 1,
fillOpacity: 0.9
});
//update properties in DomUtil
info.update(layer.feature.properties);
});
});
All is working smoothly until we get to mouseout. The info control updates fine (either switching to the properties of the new target or reverting to its empty state), but the style won't change. The highlight stays on.
featureLayer.eachLayer(function (layer) {
layer.on('mouseout', function (e) {
//reset style
featureLayer.setStyle(permitsStyle);
//empty properties in DomUtil
info.update();
});
});
I don't get any errors, but I also don't see any change. I've tried
featureLayer.resetStyle(e.target)
but then all I get is Uncaught TypeError: featureLayer.resetStyle is not a function.
There is probably a really simple question, but it's eluding me. Insight, hivemind?
Don't bind to your events inside the eachLayer loop, just do featureLayer.on('myevent',function(e) {e.layer.setStyle(MY_NEW_STYLE)})

Basemap Reference Layers (text) ignore Opacity on Selection-Change?

I am trying to keep my basemap layer opacity at a constant between different selections (and can be controlled by the user with a slider). Any basemap layers that don't have a related 'reference' layer behave as expected (i.e. if topo is at 25% before changing to imagery, it will update to 25% on change). If a user selects a basemap that also has a reference layer (imagery with labels; light gray canvas, etc), the reference layer ignores the opacity setting when loaded and will only change AFTER the user tries to move the slider. Thoughts?
Fun tidbit... Basemap layer 'Terrain with Labels' ignores this completely on both the imagery and the text when swapping. It almost looks like it refreshes after it loads.
Here is the working example in JSFiddle (http://jsfiddle.net/disuse/ez6mN/) and the dojo code that I am using to replicate my issue. Using the latest Esri ArcGIS Javascript 3.7.
Codeblock
var baseMap_Opacity;
var baseOpacity = 0.25;
require([
"esri/map",
"esri/dijit/BasemapGallery",
"dijit/form/HorizontalSlider",
"dijit/form/HorizontalRule",
"dijit/form/HorizontalRuleLabels",
"dojo/parser",
"dojo/dom",
"dojo/on",
"dojo/ready",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HorizontalSlider,
HorizontalRule,
HorizontalRuleLabels,
parser,
dom,
on,
ready
) {
ready(function() {
map = new Map("map", {
center: [-121.569, 39.00],
zoom: 7,
optimizePanAnimation: true,
basemap: "topo"
});
var basemapGallery = new BasemapGallery({
showArcGISBasemaps: true,
map: map
}, "basemaps");
basemapGallery.startup();
basemap = map.getLayer("layer0");
basemap.setOpacity(baseOpacity);
on(basemapGallery, "selection-change", function() {
changeBasemapOpacity(baseOpacity);
});
createHorzSlider();
});
function createHorzSlider() {
baseMap_Opacity = dom.byId("baseMap_Opacity");
baseMap_Opacity.innerHTML = Math.round(baseOpacity*100) + "%";
var horzSlider = new HorizontalSlider({
minimum: 0,
maximum: 1,
value: baseOpacity,
intermediateChanges: true,
showButtons: true,
discreteValues: 101,
style: "width: 300px; margin-left: 25px;",
onChange: function(value) {
changeBasemapOpacity(value);
}
}, "horzSlider");
horzSlider.startup();
var horzSliderRule = new HorizontalRule({
container: "bottomDecoration",
count: 2 ,
style: "height: 5px; width: 288px; margin-top: 5px; margin-left: 32px;"
}, "horzSliderRule");
horzSliderRule.startup();
var horzSliderLabels = new HorizontalRuleLabels({
container: "bottomDecoration",
labels: ["0", "100"],
style: "width: 288px; margin-left: 32px;",
labelStyle: "font-style: normal; font-weight: bold; font-size: 14px;"
}, "horzSliderLabels");
horzSliderLabels.startup();
}
function changeBasemapOpacity(value) {
baseOpacity = value;
baseMap_Opacity.innerHTML = Math.round(baseOpacity*100) + "%";
var esriURL = "http://services.arcgisonline.com";
var layers = map.getLayersVisibleAtScale();
for (var i = 0; i < layers.length; i++) {
var lyr = map.getLayer(layers[i].id);
if ((lyr._basemapGalleryLayerType) || (lyr.id == "layer0") || ((lyr.url) && (lyr.url.indexOf(esriURL) == 0))) {
lyr.setOpacity(baseOpacity);
}
}
}
});
The basemap gallery's selection-change event fires after the newly selected basemap is in the map. This fires before reference layers are added and is the intended design, the idea being that you wouldn't want to manipulate reference layers. In your case, that's not what you want so using selection-change is out.
To accomplish what you want, use the map's layer-add-result event and check if layer._basemapGalleryLayerType is truthy. If it is, you know a layer used by the basemap gallery was added to the map and you should update its opacity. Here's a code snippet:
map.on("layer-add-result", function(e) {
if ( e.layer._basemapGalleryLayerType ) {
e.layer.setOpacity(baseOpacity);
}
});
Regarding the issue with the Terrain with Labels basemap, things are working as expected. Because that basemap's reference layer includes labels as well as political boundaries and major roads, it looks like opacity isn't being applied when in fact it is. Using the code above will set opacity on both the layer that represents the terrain basemap as well as the reference layer.
Here's a modified version of your page that I think accomplishes what you want: http://jsbin.com/IyixAPa/1/edit

Categories