[GIS] Highlighting clicked feature of TILEWMS

feature-layergeoserveropenlayersstylewms

Using OpenLayers 4.4.1 and GeoServer 2.8.

I am new to the GIS world, and when I say layer/feature on a map, I am thinking table/row on the corresponding database.

I have not been able to find a single working example on how to highlight/color a clicked feature on the map USING ol.layer.Tile and ol.source.TileWMS, every example I have found uses ol.layer.Vector and ol.source.Vector.

I did try to reproduce this example assuming that since it uses an overlay approach, it would not matter what type of layer is beneath it. I am stuck after doing this (I am concentrating on the singleclick event, not the hover event, for now):

//map.js

$(document).ready(function () {
    var descriptionPanel;
    var title;
    var value;
    var parser = new ol.format.WMSCapabilities();
    var tableNames;
    var goalURL = __request_scheme + '://'
            + __http_host
            + __base + '/'
            + (__env === 'prod' ? '' : 'app_dev.php/') + 'caiba/get_table_names/';

    var groups = {};
    $.ajax({
        url: goalURL,
        data: {
        }, beforeSend: function (xhr) {
        }, success: function (data, textStatus, jqXHR) {
            var jsonObject = getJSonObject(data);
            $.each(jsonObject, function (group, arr) {
                groups[group] = arr;
            });

            fetch('http://localhost:8080/geoserver/ows?service=wms&version=1.3.0&request=GetCapabilities', {mode: 'cors'}).then(function (response) {
                return response.text();
            }).then(function (text) {
                var result = parser.read(text);
                var extent = getEX_GeographicBoundingBox(result);
                var extentTransform = ol.proj.transformExtent(extent, 'EPSG:4326', 'EPSG:3857');

                var highlightStyle = new ol.style.Style({
                    stroke: new ol.style.Stroke({
                        color: '#f00',
                        width: 1
                    }),
                    fill: new ol.style.Fill({
                        color: 'rgba(255,0,0,0.1)'
                    }),
                    text: new ol.style.Text({
                        font: '12px Calibri,sans-serif',
                        fill: new ol.style.Fill({
                            color: '#000'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#f00',
                            width: 3
                        })
                    })
                });

                var featureOverlay = new ol.layer.Vector({
                    source: new ol.source.Vector(),
                    map: map,
                    style: function (feature){
                         highlightStyle.getText()
                         .setText(feature.get('name'));
                         return highlightStyle;
                    }
                });    
                var layerObjects = [];
                $.each(groups, function (groupName, layers) {
                    $.each(layers, function (i, layerProps) {
                        var params = {
                            'LAYERS': 'caiba:' + layerProps['name'],
                        };
                        var tile = new ol.layer.Tile({
                            name: layerProps['friendlyName'],
                            source: new ol.source.TileWMS({
                                url: 'http://localhost:8080/geoserver/wms',
                                params: params,
                                serverType: 'geoserver',
                                crossOrigin: 'anonymous'
                            }),
                            zIndex: layerProps['zIndex']
                        });
                        layerObjects.push(tile);

                layerObjects.concat([vectorLayer]);

                var view = new ol.View({
                    center: ol.extent.getCenter(extentTransform),
                    zoom: 11
                });
                var map = new ol.Map({
                    target: 'map',
                    layers: layerObjects,
                    view: view,
                    controls: ol.control.defaults({
                        attributionOptions: {
                            collapsible: false
                        }
                    }).extend([
                        new ol.control.ZoomToExtent({
                            extent: extentTransform
                        }), new ol.control.MousePosition({
                        })
                    ])
                });

                //var currentLayerInfo = undefined;
                map.on('singleclick', function (evt) {
                    map.forEachLayerAtPixel(evt.pixel, function (layer) {
                        //var layerParam = layer.getSource().getParams()['LAYERS'];
                        //if (currentLayerInfo !== layerParam) {
                            //currentLayerInfo = displayLayerInfo(layerParam);
                        //}
                        highlightFeature(map, evt, layer, featureOverlay);
                        //return currentLayerInfo;
                        return true; 
                    });
                });
            });
        }, error: function (jqXHR, textStatus, errorThrown) {
            console.log('error');
        }, complete: function () {
        }
    });    
});    

function highlightFeature(map, evt, layer, featureOverlay) {
    var table = $('#layer-info table');
    var thead = table.children('thead');
    var tbody = table.children('tbody');
    var source = layer.getSource();
    var view = map.getView();
    var viewResolution = view.getResolution();
    var viewProjection = view.getProjection();

  //FOLLOWING IS HOW IT IS DONE IN THE EXAMPLE, BUT IT DOES NOT WORK WITH TILEWMS LAYERS,
  //RIGHT?
  //var feature = map.forEachFeatureAtPixel(pixel, function (feature) {
  //    return feature;
  //});

  //INSTEAD (THIS IS WHERE THE MAJOR CHANGE IS DONE TO GET THIS TO WORK WITH TILEWMS, 
  //ALSO WHERE THE DETAIL WHICH IS MAKING IT NOT WORK MOST LIKELY IS)
  //IN ORDER TO GET THE FEATURE:

    var url = source.getGetFeatureInfoUrl(
            evt.coordinate, viewResolution, viewProjection,
            {'INFO_FORMAT': 'application/vnd.ogc.gml', 'FEATURE_COUNT': 50});

    if (url) {
        var highlight;
        var features;
        var parser = new ol.format.WMSGetFeatureInfo();
        $.ajax({
            url: url
        }).then(function (response) {
            features = parser.readFeatures(response);

            var info = document.getElementById('info');
            if (features[0]) {
                info.innerHTML = features[0].getId() + ': ' + features[0].get('empresa');
            } else {
                info.innerHTML = ' ';
            }
            if (features[0] !== highlight) {
                if (highlight) {
                    featureOverlay.getSource().removeFeature(highlight);
                }
                if (features[0]) {
                    featureOverlay.getSource().addFeature(features[0]);
                }
                highlight = features[0];
            }
        });
    }    
}

I modified it of course, but notice the highlightFeature method which contains all the relevant changes I made to the example, specially when trying to get the clicked feature.

No errors are thrown, it just does not do anything. I am able to get all the properties of the selected feature (and all the features of the layer) and display them but I am stuck trying to highlight/style them somehow.

I have read a while on how GeoServer styles the layers using SLD and I'm thinking maybe that's the way but again nothing to help me couple this with OpenLayers, I'm thinking maybe a way to use GeoServer's services (WMS or WFS) to get the layer's sld url (if such exists) and and another way to change that somehow and submit it back to the server.

I would like to get the featureOverlay approach to work.

Best Answer

When you ask a web map server for a WMS or WMTS layer it sends you a picture of the data that is in that area. As such there is no way for your client to disentangle the individual features from the image for you to highlight. This is why all the examples use vector layers to do highlighting.

The usual solution is to use a Web Feature Service (WFS) request to fetch the feature that is under your click. This returns the actual feature and then your client is free to draw it in any way that it wants.