1

I'm working on a method to show or hide feature labels (text) based on zoom level. I have a working example that uses setScale on the style object of the feature and is triggered by the "moveend" event of the view:

    map_obj.on('moveend', function (evt) {
    let m = evt.map;
      let v = m.getView();
        if (v.getZoom() > 10) {
            toggleLabels(true);
        } else {
                toggleLabels(false);
        }
    });
    
    function toggleLabels(show) {
        let lyrs = map_obj.getAllLayers(); 
        for (let l in lyrs) {
            source = lyrs[l].getSource();           
            let features = source.getFeaturesCollection();          
            features.forEach( function (feat) {                 
                let s = feat.getStyle()[0];  //works
                if (show) {
                    if (s.getText().getScale()[0] == 0) {       
                        s.getText().setScale([1, 1]);
                    }
                } else {
                    
                    if (s.getText().getScale()[0] == 1) {       
                        s.getText().setScale([0, 0]);
                    }
                }                           
            });         
            
        }           
        map_obj.render();   //appears to do nothing
        //map_obj.renderSync();  //exceeds max call stack size
    }

If the user zooms above level 10, labels show up. However, when the user zooms out below level 10, the labels still show until the map is moved one more time. I'm trying to force the map to re-render but it's not working. Is there a better event to use to trigger this, or a maybe a better strategy for showing/hiding labels?

2
  • Any special reason you are trying to hide labels this way and not in the feature style function?
    – TomazicM
    Commented Sep 13, 2023 at 12:46
  • @TomazicM. No, I'm pretty new to ol. This is just something I worked out. I figured re-setting the scale would be more straight-forward, since I would only set the text values once during the map construction. Commented Sep 13, 2023 at 12:56

2 Answers 2

0

One possible way to control feature text display depending on zoom level is to use layer style option function to add text to style only above desired zoom.

Here is an example of code for random generated features, where marker label is shown only above certain zoom level:

var count = 0;
var features = [];
var e = 4500000;
for (var i = 0; i < 500; ++i) {
  var coordinates = [2 * e * Math.random() - e, 2 * e * Math.random() - e];
  var feature = new ol.Feature(new ol.geom.Point(coordinates));
  feature.set('id', count++);
  features.push(feature);
}

var source = new ol.source.Vector({
  features: features
});

var style = new ol.style.Style({
  image: new ol.style.Circle({
    radius: 10,
    stroke: new ol.style.Stroke({
      color: '#fff',
    }),
    fill: new ol.style.Fill({
      color: '#3399CC',
    }),
  }),
  text: new ol.style.Text({
    text: '',
    fill: new ol.style.Fill({
      color: '#fff',
    }),
  }),
});

var zoomForLabels = 4;

function markerStyle(feature, resolution) {
  var zoom = map.getView().getZoomForResolution(resolution);
  var id = feature.get('id');
  var label = (zoom >= zoomForLabels) ? id.toString() : null;
  style.getText().setText(label);
  
  return style;
}

var markers = new ol.layer.Vector({
  source: source,
  style: markerStyle
});

var raster = new ol.layer.Tile({
  source: new ol.source.OSM(),
});

var map = new ol.Map({
  layers: [raster, markers],
  target: 'map',
  view: new ol.View({
    center: [0, 0],
    zoom: 2,
  }),
});

Here's how it looks at zoom 3:

enter image description here

Here's how it looks at zoom 4:

enter image description here

3
  • Thanks for this. I hadn't thought of creating a function for setting the style property of the layer. How are the feature and resolution arguments in markerStyle() getting set? Is the layer object handling that somehow, or are you using an event listener? You also made me realize I was using the wrong event in my example, so I switched to the "change:resolution" event of the view object. I got my code to work this way. See my answer, below. Commented Sep 14, 2023 at 11:19
  • What you see above is complete working code. There is no extra event handling, everything is handled with the markerStyle function.
    – TomazicM
    Commented Sep 14, 2023 at 13:43
  • You're right. I made an error when copying the code. It works great. Commented Sep 17, 2023 at 13:28
0

Thanks to some parts of @TomazicM 's example. I was able to solve my problem. Here is the updated code with all supporting code added. It still uses setScale, but is called from a more appropriate event -- "change:resolution" on the view object. It also uses a custom property, "layer_id," to bypass the tile layer when iterating over vector layers.

function makePoint(long, lat, the_text) {
    let point = new ol.geom.Point([long, lat]);
   let point_feature = new ol.Feature({
    geometry: point,
   });
    
    let text_style = new ol.style.Style({
        text: new ol.style.Text({
            text: the_text,
            font: 'bold 14px Calibri,sans-serif',
            fill: new ol.style.Fill({
                color: '#3F4047',
            }),
            stroke: new ol.style.Stroke({
                color: 'white',
                width: 1
            }),
            scale: [0, 0],
            offsetY: 15
        }),
    });
    
     let point_style = new ol.style.Style({
        image: new ol.style.Circle({
            radius: 5,
            fill: new ol.style.Fill({
                color: 'black',
            }),
        stroke: new ol.style.Stroke({
            color: 'white',
            width: 1,
            }),
        }),
    });

     point_feature.setStyle([text_style, point_style]);    
    
    return point_feature;   
}    
let p1 = makePoint(-115.176468, 36.188110, "Business A");
let p2 = makePoint(-115.113456, 36.324560, "Business B");
let p3 = makePoint(-115.172228, 36.126678, "Business C");
let p4 = makePoint(-115.127046, 36.284001, "Business D");
    
let vect_source1 = new ol.source.Vector({
    features: [p1, p2],
   useSpatialIndex: false
});

let vect_layer1 = new ol.layer.Vector({
   source: vect_source1,
   'layer_id': 1
   });
        
let vect_source2 = new ol.source.Vector({
    features: [p3, p4],
   useSpatialIndex: false
});

let vect_layer2 = new ol.layer.Vector({
   source: vect_source2,
   'layer_id': 2
   });
    
let osm_source = new ol.source.OSM();
let osm_layer = new ol.layer.Tile({'layer_id': 'tiles', source: osm_source});

map_view = new ol.View({
    projection: 'EPSG:4326',
   center: [-115.176468, 36.188110],
   zoom: 10,    
});

map_obj = new ol.Map({
  view: map_view,
});
        
map_obj.addLayer(osm_layer);    
map_obj.addLayer(vect_layer1);
map_obj.addLayer(vect_layer2);  
map_obj.setTarget('the_map');
    
map_view.on('change:resolution', function (evt) {
    let v = map_obj.getView();      
    if (v.getZoom() > 12) {
        toggleLabels(true);     
    } else {
        toggleLabels(false);        
    }       
});
    
function toggleLabels(show) {
    let lyrs = map_obj.getAllLayers(); 
    for (let l in lyrs) {
        if (lyrs[l].get('layer_id') !== 'tiles') {
            source = lyrs[l].getSource();           
            let features = source.getFeaturesCollection();          
            features.forEach( function (feat) {                 
                let s = feat.getStyle()[0];  //works
                if (show) {
                    s.getText().setScale([1, 1]);
                } else {
                    s.getText().setScale([0, 0]);
                }                           
            });                     
        }
    }       
}
2
  • In your code you create new style for every point, which is not very economic when there is a huge amount of points. If you look at my code, new style is created only once and then modified as necessary in the markerStyle function.
    – TomazicM
    Commented Sep 14, 2023 at 13:47
  • I see what you mean now. Your solution is definitely more efficient. Commented Sep 17, 2023 at 13:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.