Openlayers3, vector data layer not visible after proceeding select onchange() function - javascript

I input two polygon geojson layers, one is just boundary layer, another is the vectorlayer contains many attributes.
I add map.on('click', function(evt), so users can click on the map, and the corresponding feature will be highlighted (with another style). I did this by add a new overlay layer with only this feature.
I also have a Select with many options and a onchange() function. The onchange() functions let style of vectorlayer change based on the value of select option.
The click and highlight feature thing works well at begining, but when I change the value of the select and the style of vectorlayer changes, the highlight does not work.
I also tried to not using the onchange() function for select, then the highlight works well again.
I'm quite confused about this... Why does this two thing could not work together? Thank you.
Example of select:
<select id="vai_year" onchange="get_vaiSelect(vectorLayer)">
<option value="VAI_2013">2013年</option>
<option value="VAI_2012">2012年</option>
<option value="VAI_2011">2011年</option>
</select>
onchange function:
function get_vaiSelect(layer)
{
featureOverlay.setStyle(highlightStyle);
featureOverlay.setZIndex(50);
map.addLayer(featureOverlay);
features = layer.getSource().getFeatures();
length = features.length;
attributes = layer.getSource().getProperties();
var vaivalue = document.getElementById("vai_year");
for(var f=0;f < length; f++)
{
if (vaivalue.value == 'VAI_2013')
{
level = features[f].getProperties().vai_2013+1;
}
else if (vaivalue.value == 'VAI_2012')
{
level = features[f].getProperties().vai_2012+1;
}
...
else if (vaivalue.value == 'VAI_1992')
{
level = features[f].getProperties().vai_1992+1;
}
styleCache[level] = new ol.style.Style
({
fill: new ol.style.Fill({color: vai_levels[level],}),
});
features[f].setStyle(styleCache[level]);
}
};
highlight style:
highlightStyle = function(feature, resolution)
{
var text = resolution < 5000 ? feature.get('NL_NAME_3') : '';
highlightStyleCache = new ol.style.Style(
{
stroke: new ol.style.Stroke(
{
color: 'rgba(166,18,12,1)',
width: 1.2
}),
fill: new ol.style.Fill(
{
color: 'rgba(255,255,128,0.8)'
}),
text: new ol.style.Text(
{
font: '14px Calibri,sans-serif',
text: text,
color:'rgba(0,0,100,1)'
})
});
return highlightStyleCache;
}
overlay layer:
featureOverlay = new ol.layer.Vector(
{
source: new ol.source.Vector(),
style: highlightStyle,
ZIndes: 60
});
displayFeatureInfo = function(m)
{
featureOverlay.setZIndex(70);
vectorLayer.setZIndex(40);
map.addLayer(featureOverlay);
feature= map.forEachFeatureAtPixel
(
m,
function (feature, vectorLayer)
{
return feature;
}
/* function(layer)
{
return layer === vectorLayer;
}*/
);
if (feature !== highlight)
{
if (highlight)
{
featureOverlay.getSource().removeFeature(highlight);
}
if (feature)
{
featureOverlay.getSource().addFeature(feature);
}
highlight = feature;
}
};
map.onclick:
map.on('click', function(evt)
{
if (evt.dragging)
{
return;
}
pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);

You don't have to add a new layer every time you click the map, it is enough if you just change the source of the desired layer, as you are doing at the end of the displayFeatureInfo function.
Can you please provide us a jsfiddle working example, so that we can debug your code and see more details which are not shown now? (We don't see, for example, vectorLayer definition, or the polygons you mentioned at the beginning of your question)

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.

Changing cursor style on pointermove for specified layer

quick (and I believe for some of you an easy) question regarding cursor styling while hovering above geojson layer/s.
So, I have one clip layer that I'm using to create a mask around wms layers, and another one that represents some administrative areas.
As you can see in picture below
What I would like is to change style of cursor when I'm hovering above administrative areas but it seems that I'm missing something.
I'm trying to isolate to layer only administrative borders layer using this block of code:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
return vectorJLS.get('layer_name') === 'jls';
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
UPDATE
While JGH tweak code a bit it still doesn't work. I've detected that problem lies in layer that I'm using for mask clipping, when removed, code that JGH provided, works.
Here is code that I'm using for mask clipping
var clipLayer = new ol.layer.Image({
source: new ol.source.ImageVector({
source: new ol.source.Vector({
url: 'geojson/clip_wgs.geojson',
format: new ol.format.GeoJSON()
}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'black'
})
})
})
});
clipLayer.on('postcompose', function(e) {
e.context.globalCompositeOperation = 'source-over';
});
clipLayer.on('precompose', function(e) {
e.context.globalCompositeOperation = 'destination-in';
});
clipLayer.setMap(map);
Is it possible to somehow ignore clip layer when changing cursor style or should I take another approach?
UPDATE - 2
I've tweaked code a bit, but still without any success while clipedLayer is on.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return vectorJLS.get('layer_name') === 'jls';
}
});
console.log(hit)
});
Interesting problem if I might add
Finally, with help from fellow JGH I've found appropriate solution for my problem.
Searching release pages and google machine I've stumbled upon some interesting information regarding layer filters and its usage in method hasFeatureAtPixel. This block of code is valid for versions below 3.20.1 but more about that on OpenLayers Git
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
For newer versions you should use layer filter like this (I'm using 4.6.5)
map.hasFeatureAtPixel(pixel, {
layerFilter: layerFilterFn.bind(layerFilterThis)
});
Or for my particular problem like this
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Hope it helps :)
In your function, you are basically looping through all the layers at the mouse location. In that loop, if the layer has the proper name you set the pointer, else if it has a different name, you remove the pointer (or set it to something else).
As it is, it is dependent on the layer order:
ex: layer 1 = target -> set custom pointer. Layer 2 = other layer -> remove pointer. ==> final pointer: removed
ex: Layer 1 = other layer -> remove pointer. Layer 2 = target -> set custom pointer. ==> final pointer: custom pointer
The looping occurs when you set the hit variable, i.e. it corresponds to the last layer only as you are overriding the value for each layer.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = false;
e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if ( vectorJLS.get('layer_name') === 'jls') {
//IF we have found the layer, flag it (but don't return anything!)
hit = true;
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});

OpenLayers 3 show different highlight style based on layer type

I have a map with two layers. One layers display points (circles) and another layer display an icon on the map.
I would like two have to highlight styles so that when then user clicks on a feature it shows the correct highlight style based on the layer type.
Right now I have created two highlight styles. I also have a 'select' event which selects the feature clicked. When I click on a new feature the "old" feature does not remove the highlight style.
So far I have this:
var highlightOne = function () {
var scaleRadius = map.getView().getZoom() >= 7 ? 15 : 5;
return [new ol.style.Style({
image: new ol.style.Circle({
radius: scaleRadius,
fill: new ol.style.Fill({
color: '#fff'
}),
stroke: new ol.style.Stroke({
color: '#658acf',
width: 2
})
}),
zIndex: 1
})]
}
var highlightTwo = function () {
var scaleRadius = map.getView().getZoom() >= 7 ? 15 : 15;
return [new ol.style.Style({
image: new ol.style.RegularShape({
radius: scaleRadius,
points: 4,
rotation: 0.8,
rotateWithView: true,
fill: new ol.style.Fill({
color: '#fff'
}),
stroke: new ol.style.Stroke({
color: '#658acf',
width: 2
})
}),
zIndex: 1
})]
}
var selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
style: highlightStyle
});
selectInteraction.on('select', function (e) {
var selected = e.selected,
deselected = e.deselected;
if (selected.length) // Selected feature
{
selected.forEach(function (feature) {
// Get the layer name to apply custom highlight style
if (feature.getLayer().get('name') == "layerone")
{
feature.setStyle(highlightTwo)
} else {
feature.setStyle(highlightOne)
}
})
} else {
deselected.forEach(function (feature) {
// Remove highlight from the other features
feature.setStyle(null)
})
}
});
In general, you want to avoid setting the styles on the features when setting a temporary display style (as you do with the select interaction). Setting style functions on layers or interactions are not only more memory efficient, it's also takes any responsability of unsetting the style from you.
I would suggest that you look at a solution like
var selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
style: function(feature, resolution){
if (feature.getLayer().get('name') == "layerone") {
return highlightTwo()
} else {
return highlightOne()
}
}
});
As a side note, I would suggest using the resolution parameter to the style function rather than polling the map for the current zoom level. When setting a style on the feature with a radius based on the current zoom level, the style will not update when zooming in or out.

How to fill a select with markers from switched Leaflet layer groups

I have two layer groups on my leaflet map.
var firstMarkerLayer = L.geoJson(firstGroup, {
onEachFeature: function(feature, layer) {
layer.bindPopup(feature.properties.name);
}
});
var secondMarkerLayer = L.geoJson(secondGroup, {
onEachFeature: function(feature, layer) {
layer.bindPopup(feature.properties.name);
}
});
var overlays = {
"first" : firstMarkerLayer,
"second" : secondMarkerLayer
};
Also I have a code, that selects markers on my first layer from drop down list and shows corresponding tooltips.
var selector = L.control({
position: 'topleft'
});
selector.onAdd = function(map) {
var div = L.DomUtil.create('div', 'mySelector');
div.innerHTML = '<select id = "marker_select"><option value = "init">(places)</option></select>';
return div;
};
selector.addTo(map);
firstMarkerLayer.eachLayer(function(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.name;
optionElement.value = layer._leaflet_id;
L.DomUtil.get("marker_select").appendChild(optionElement);
});
var marker_select = L.DomUtil.get("marker_select");
L.DomEvent.addListener(marker_select, 'click', function(e) {
L.DomEvent.stopPropagation(e);
});
L.DomEvent.addListener(marker_select, 'change', changeHandler);
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
firstMarkerLayer._layers[e.target.value].openPopup();
}
}
But it works only with one layer. Can somebody suggest me how can I complete my code to realize all features with any of selected overlap layers?
It is my code: http://jsfiddle.net/anton9ov/y4o7oxbu/
If my understanding is correct, you want to fill your select with the name of all your markers, from overlay layer groups firstMarkerLayer and secondMarkerLayer, and that it opens the marker's popup when the appropriate overlay is selected through the Layers Control?
In that case, you would just need to repeat the step with eachLayer where you fill the select for secondMarkerLayer.
Within your changeHandler listener, you would need to check first to which layer group the selected layer (marker) id belongs to, then open its popup.
You could avoid having to look for the parent layer group by also adding your individual markers into a 3rd layer group (that you do not add to map, of course), just to benefit from the .getLayer(id) method.
Then just check whether the marker is on map or not, and open its popup.
var marker_select = L.DomUtil.get("marker_select");
var allMarkers = L.layerGroup();
function fillSelect(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.name;
optionElement.value = L.stamp(layer);
marker_select.appendChild(optionElement);
allMarkers.addLayer(layer);
}
firstMarkerLayer.eachLayer(fillSelect);
secondMarkerLayer.eachLayer(fillSelect);
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
var layer = allMarkers.getLayer(e.target.value);
if (map.hasLayer(layer)) {
layer.openPopup();
}
}
}
Updated JSFiddle: http://jsfiddle.net/y4o7oxbu/15/
EDIT:
As for changing the content of the select input as well, so that it lists only the markers of the layer groups on map (the one selected through Layers Control), you would simply need to listen to "baselayerchange" event, empty the select input and fill it back with fillSelect.
map.on ('baselayerchange', function (eventLayer) {
var selectedLayer = eventLayer.layer;
marker_select.innerHTML = '<option value = "init">(places)</option>';
selectedLayer.eachLayer(fillSelect);
});
Updated JSFiddle: http://jsfiddle.net/toj5wyt6/24/

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);
}

Categories