4

I want to display feature properties that have been selected in an OpenLayers3 map. I am using AngularJS and try to update the currently selected feature properties using $scope$watch.

I have a MapManager file that is used to handle all necessary OL3 operations:

// DEFAULT variables
var defaultCenter = [-2.15, 52.267];
var defaultZoom = 12;
var epsgWGS84 = 'EPSG:4326';
var epsgDefaultMapOL = 'EPSG:3857';
var defaultTarget = 'map';

// Constructor
function MapManager(target, center, zoom){
    // check if parameters are set - use defaults otherwise
    if(!target){
        target = defaultTarget;
    }
    if(!center){
        center = defaultCenter;
    }
    if(!zoom){
        zoom = defaultZoom;
    }
    this.map = this.initMap(target, center, zoom);
    this.selectedFeatureProperties = {prop1: "initialValue"};
}


// Getter for feature properties
MapManager.prototype.getSelectedFeatureProperties = function(){
    return this.selectedFeatureProperties;
};

// Setter for feature properties
MapManager.prototype.setSelectedFeatureProperties = function(feature){
    var this_ = this;
    console.log("setting selected feature properties...");
    var displayProperties = {};
    if(feature != null){
        $.each(feature.getProperties(), function(key, value) {
            // skip the geometry property
            if(key != "geometry"){
                displayProperties[key] = value;
            }
        });
    }
    console.log(displayProperties);
    this.selectedFeatureProperties = displayProperties;
}

// some geojson with lines
var geojsonObject = {
    'type': 'FeatureCollection',
    'crs': {
        'type': 'name',
        'properties': {
            'name': 'EPSG:4326'
        }
    },
    'features': [
        {
            'type': 'Feature',
            'properties': {
                'prop1': 'feature1',
                'prop2': 'Blub'
            },
            'geometry': {
                'type': 'LineString',
                'coordinates': [
                    [-2.176, 52.266], [-2.166, 52.276]
                ]
            }
        },
        {
            'type': 'Feature',
            'properties': {
                'prop1': 'feature2',
                'prop2': 'Test'
            },
            'geometry': {
                'type': 'LineString',
                'coordinates': [
                    [-2.132, 52.272], [-2.145, 52.258]
                ]
            }
        }
    ]
};

// create vector source with geojson
var vectorSource = new ol.source.Vector({
    features: (new ol.format.GeoJSON()).readFeatures(geojsonObject, {
        dataProjection: 'EPSG:4326',
        featureProjection: 'EPSG:3857'
    })
});


// ### Create layers
// create osm layer
function createLayerWebOSM(){
    return new ol.layer.Tile({
        source: new ol.source.OSM(),
        projection: epsgDefaultMapOL
    });
}

// create vector layer (using geojson vector source)
var vectorLayer = new ol.layer.Vector({
    source: vectorSource
});


// Initialize map
MapManager.prototype.initMap = function(target, center, zoom){
    var this_ = this;
    var map = new ol.Map({
        target: target,
        layers: [createLayerWebOSM(), vectorLayer],
        view: new ol.View({
        center: ol.proj.transform(center, epsgWGS84, epsgDefaultMapOL),
        zoom: zoom
      })
    });

    // make vector layer selectable
    vectorLayer.set('selectable', true);

    map.addInteraction(new ol.interaction.Select({
        layers: function(layer) {
            return layer.get("selectable") == true;
        }
    }));


    // add click interaction
    map.on("click", function(e) {
        map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
            console.log("new feature selected -> ");
            console.log(feature);
            this_.setSelectedFeatureProperties(feature);
            console.log("new feature properties -> ");
            console.log(this_.getSelectedFeatureProperties());
        });
    });
    return map;
}

When a feature is clicked on the map it is stored and can be accessed with an according getter which is watched in the Angular controller:

var app = angular.module('myApp', []);

// Controller
app.controller('appController', function($scope, $q){
    $scope.mapManager = new MapManager();
    $scope.selectedFeatureProperties = {};

    // watch if the selected feature changes
    $scope.$watch(function() {
        return $scope.mapManager.getSelectedFeatureProperties();
        //return $scope.mapManager.getSelectedFeature();
    },
    function(newFeature, oldFeature) {
        console.log("change detected...");
        $scope.selectedFeatureProperties = $scope.mapManager.getSelectedFeatureProperties();
    }, true);

});

I want to display the properties of the currently selected feature in a table:

<div id="main" data-ng-controller="appController">
    <!-- table with selected feature inforamtion -->
    <h4> Selected Feature Properties </h4>
    <table>
        <tr>
            <th> Property </th>
            <th> Value </th>
        </tr>
        <tr ng-repeat="(property, value) in selectedFeatureProperties">
            <td>{{ property }}</td>
            <td>{{ value }}</td>
        </tr>
    </table>

    <div id='map'></div>
</div>

It seems that the change is ignored.

You can see the essential part of the code at this JSFiddle. Some logs indicate that the selectedFeature variable is actually changing...

What am I doing wrong? Is there an easier way to do this (instead of using $scope$watch)?

EDIT: In the MapManager I am now saving the feature properties (selectedFeatureProperties) directly and not the ol.Feature itself. I tried $watch and $watchCollection with and without true/false as last parameter but the listener is not called when the selectedFeatureProperties are changing... If I am understanding the third parameter correctly in my case it should not matter if it is true or false because the reference of the selectedFeatureProperties and the values of the properties are both changing? Moreover I tried to pass the watchExpression as String

"mapManager.getSelectedFeatureProperties()"

or as a function

function(){
  return $scope.mapManager.getSelectedFeatureProperties();
},

but that did not change anything. I adjusted the JSFiddle with some comments so you can see what I tried...

  • 1
    I tried your code and both newFeature and oldFeature returns an empty object, however I still can't find where the problem is. There is another way to make it using angular-openlayers-directive check here tombatossals.github.io/angular-openlayers-directive/#/events/… – Hicham Zouarhi Aug 12 '16 at 13:48
  • Thanks for sharing the angular-openlayers-directive information. I still need to figure out what is wrong with my code. – Lars Aug 13 '16 at 7:40
1

The problem was that the scope was not aware of the changes in the MapManager object. Using $scope.$apply() after setting the new selected feature solved the problem:

// Make the scope available in the MapManager class using a setter:
MapManager.prototype.setScope = function(scope){
    this.scope = scope;
}

// ...

// When defining the click event for the map it is necessary to use scope.$apply() after setting the selected feature properties:
map.on("click", function(e) {
    map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
        this_.setSelectedFeatureProperties(feature);
        this_.scope.$apply();
    });
});

See JSFiddle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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