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

checkbox in the layerswitcher for layer symbologies #1124

Open
andreaordonselli opened this issue Jan 27, 2025 · 14 comments
Open

checkbox in the layerswitcher for layer symbologies #1124

andreaordonselli opened this issue Jan 27, 2025 · 14 comments
Labels

Comments

@andreaordonselli
Copy link

Good morning,
I would like the layer switcher to allow me to turn on/off the individual layer symbologies configured in the style function.
This way I could have a filter on the data in the map.

I would like this result in the layer switcher:
Image

For example, this is the style of my "Edifici" layer

var size = 0;
var placement = 'point';
function categories_Edifici(feature, value, size, resolution, labelText,
                       labelFont, labelFill, bufferColor, bufferWidth,
                       placement) {
                switch(value.toString()) {case "Edificio civile":
                    return [ new ol.style.Style({
        stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0.988}),fill: new ol.style.Fill({color: 'rgba(225,187,111,0.5019607843137255)'}),
        text: createTextStyle(feature, resolution, labelText, labelFont,
                              labelFill, placement, bufferColor,
                              bufferWidth)
    })];
                    break;
case "Edificio industriale":
                    return [ new ol.style.Style({
        stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0.988}),fill: new ol.style.Fill({color: 'rgba(76,212,55,0.5019607843137255)'}),
        text: createTextStyle(feature, resolution, labelText, labelFont,
                              labelFill, placement, bufferColor,
                              bufferWidth)
    })];
                    break;
case "Edificio civile in costruzione":
                    return [ new ol.style.Style({
        stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0.988}),fill: new ol.style.Fill({color: 'rgba(166,206,227,0.5019607843137255)'}),
        text: createTextStyle(feature, resolution, labelText, labelFont,
                              labelFill, placement, bufferColor,
                              bufferWidth)
    })];
                    break;
case "Chiesa":
                    return [ new ol.style.Style({
        stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0.988}),fill: new ol.style.Fill({color: 'rgba(223,81,159,0.5019607843137255)'}),
        text: createTextStyle(feature, resolution, labelText, labelFont,
                              labelFill, placement, bufferColor,
                              bufferWidth)
    })];
                    break;}};

var style_Edifici = function(feature, resolution){
    var context = {
        feature: feature,
        variables: {}
    };
    var value = feature.get("descrizion");
    var labelText = "";
    size = 0;
    var labelFont = "10px, sans-serif";
    var labelFill = "#000000";
    var bufferColor = "";
    var bufferWidth = 0;
    var textAlign = "left";
    var offsetX = 8;
    var offsetY = 3;
    var placement = 'point';
    if ("" !== null) {
        labelText = String("");
    }
    
var style = categories_Edifici(feature, value, size, resolution, labelText,
                          labelFont, labelFill, bufferColor,
                          bufferWidth, placement);

    return style;
};

Is it possible?
Thanks

@Viglino
Copy link
Owner

Viglino commented Jan 27, 2025

The layerswitcher only handles layers, but you can add your own component using the drawlist event to handle what you want.

switcher.on('drawlist', function(e) {
  // add your own component 
  // for the layer e.layer 
  // in the list e.li
}

See example here : https://codepen.io/viglino/pen/GgKPEPB

@andreaordonselli
Copy link
Author

Wow,
I configured the drawlist to create checkboxes for each style category. Then, when the checkbox state changes, I check those that match the categories and update the layer's style function. The result is a bit slow; do you have any tips for improving it?

var cat_Edifici = [
    { name: 'Edificio civile', checked: true },
    { name: 'Edificio industriale', checked: true },
    { name: 'Edificio civile in costruzione', checked: true },
    { name: 'Chiesa', checked: true }
];
var val_Edifici = 'descrizion';

function updateStyle(layer, categories, filterValue, styleFunction) {
    var selectedCategories = categories.filter(function(category) {
        return category.checked;
    }).map(function(category) {
        return category.name;
    });
    layer.setStyle(function(feature) {
        var value = feature.get(filterValue);
        if (selectedCategories.includes(value)) {
            return styleFunction(feature);	
        }
        return null;
    });
}

function createLayerDrawList(layer, categories, filterValue, styleFunction) {
    layerSwitcher.on('drawlist', function(e) {
        if (e.layer === layer) {
            var d = document.createElement('UL');
            d.classList.add('categories');
            e.li.appendChild(d);

            categories.forEach(function(category) {
                var li = document.createElement('LI');
                var input = document.createElement('INPUT');
                input.name = category.name;
                input.type = 'checkbox';
                input.checked = true; // Imposta lo stato iniziale
                input.addEventListener('change', function() {
                    category.checked = input.checked; 
                    updateStyle(layer, categories, filterValue, styleFunction)
                });

                li.appendChild(input);
                li.appendChild(document.createTextNode(category.name));
                d.appendChild(li);
            });
        }
    });
}

document.addEventListener('DOMContentLoaded', function() {
    createLayerDrawList(lyr_Edifici, cat_Edifici, val_Edifici, lyr_Edifici.getStyle() );	
	});

@Viglino
Copy link
Owner

Viglino commented Jan 29, 2025

The style function may be slow if you create a new style for each feature, maybe you have to use a cache to speed up the drawing.

@andreaordonselli
Copy link
Author

Thank you for your response.
I have modified the layer style update function this way: I read the original style, adjust it to include only the active categories/symbols, and apply it to the layer. Does this work better?

function updateLayerStyle(layer, categories, filterValue, styleFunction) {
    var activeCategories = categories.filter(c => c.checked).map(c => c.name);
    var newStyleFunction = function(feature, resolution) {
        var value = feature.get(filterValue);
        if (activeCategories.includes(value)) {
            return styleFunction(feature, resolution);
        }
        return null; 
    };
    layer.setStyle(newStyleFunction);
}

@Viglino
Copy link
Owner

Viglino commented Jan 29, 2025

You can also use renderMode: 'image' option on the layer to speed up map moves.

The issue may be in the style fuction :

function styleFunction(feature, resolution) {
  //   creating hundreds of objects may be slow
  return new ol.style.Style({
     ...
  }
}

Use a cache

// Style cache
var cache = {}

function styleFunction(feature, resolution) {
  // get a key depending on feature properties that impact the style
  var key = getkey(feature)
  var style = cache[key]
  // No style => create and cache it
  if (!style) {
    style = new ol.style.Style({
       ...
    }
    // cache it
    cache[key] = style
  }
  return style
}

See openlayers/openlayers#8514

@andreaordonselli
Copy link
Author

I've make this codepen:
https://codepen.io/andreaordonselli/pen/PwYvKWJ

Image

Can you help me manage the clicks?

When I click on the name of the layer, I want to lock the activation/deactivation of its checkbox, so I wrote this:

  	// Remove click on the label to turn on/off layer
    switcher.on('drawlist', function(e) {
      e.li.querySelector('.li-content > label > span').addEventListener('click', function(e) {
        e.stopPropagation()
        e.preventDefault()
      })
    })

At the same time, when I click on the name of the layer, I would like to expand the menu to display the symbols, so I wrote this:

// Expand collapse layerlegend 
	switcher.on('drawlist', function (e) {
	   var layer = e.layer;
	   // Toggle on click
	   $(".li-content #layertitle").click(function(e) {
		   $(this).toggleClass("active"); 
			e.preventDefault()				   
		});
	});

But something strange happens with the click: the first checkbox of the symbology is always clicked, as if the click is active on the entire legend element. Can you understand what's happening?
Thank you.

@Viglino
Copy link
Owner

Viglino commented Feb 4, 2025

Why don't you just do the trick in the first code?

    // Change click action
    switcher.on('drawlist', function(e) {
      e.li.querySelector('.li-content > label > span').addEventListener('click', function(e2) {
        // expend/collapse
        e.li.toggleClass('active');
        // Prevent default action
        e2.stopPropagation()
        e2.preventDefault()
      })
    })

@andreaordonselli
Copy link
Author

Scusa, ma non funziona, puoi guardare il codepen e capire come risolvere?

@Viglino
Copy link
Owner

Viglino commented Feb 4, 2025

@andreaordonselli
Copy link
Author

Thanks, it worked wonderfully well, as always.

Do you also have an idea on how to select/deselect all layer symbols with a single command?
In case of many symbols it can be essential to deactivate them all and then turn on only one. Or, on the contrary, activate them all and deactivate one.

Greetings.

@Viglino
Copy link
Owner

Viglino commented Feb 6, 2025

just add a button and change the states of list the on click.

@andreaordonselli
Copy link
Author

I've update a codepen https://codepen.io/andreaordonselli/pen/PwYvKWJ

Image

It works well, but I notice a delay when I turn the first symbol (with many geometry) on and off; do you think I’m managing the style cache properly?

        var styleCache = {}; 

	function getCachedStyle(layer, value, styleFunction) {
		var key = `${layer.get('permalink')}_${value}`; 
		if (!styleCache[key]) {
			var fakeFeature = { get: () => value }; 
			styleCache[key] = styleFunction(fakeFeature, 0); 
		}
		return styleCache[key];
	}

	function updateLayerStyle(layer, categories, filterValue, style_Function) {
		var activeCategories = categories.filter(c => c.checked).map(c => c.name);
		layer.setStyle(function(feature, resolution) {
			var value = feature.get(filterValue);
			return activeCategories.includes(value) ? getCachedStyle(layer, value, style_Function) : null;
		});
	}


 switcher.on('drawlist', function(e) {
    if (e.layer===lyr_Edifici) {
        var lyr = e.layer
        var d = document.createElement('ul');
        d.classList.add('layerlegend');
        e.li.appendChild(d)

        var categories = lyr.get('cat')
        categories.forEach(function(category) {
          var li = document.createElement('li');
          li.classList.add('simbology');
          var input = document.createElement('input');
          input.name = category.name;
          input.type = 'checkbox';
          input.checked = category.checked;
          input.addEventListener('change', function() {
            category.checked = input.checked; 
            updateLayerStyle(lyr, lyr.get('cat'), lyr.get('val'), lyr.getStyle());
          });

          var img = document.createElement('img');
          var cat = lyr.get('cat').find(a => a.name === category.name);
          if (cat) img.src = cat.img;

          var textSimbol = document.createElement('a');
          textSimbol.textContent = category.name;

          li.appendChild(input);
          li.appendChild(img);
          li.appendChild(textSimbol);
          d.appendChild(li);		
      });
    }
})

I’ll program the button for toggling all symbols later, thanks for the advice.
Thanks

@andreaordonselli
Copy link
Author

What do you think of the way I cache styles? Do you think it is correct? Thanks

@Viglino
Copy link
Owner

Viglino commented Feb 24, 2025

What do you think of the way I cache styles? Do you think it is correct? Thanks

that should be fine

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants