Open layer 3 click on layer best practice - openlayers-3

I am new to OL.
I need to implement a logic that should happen if I pressed on a specific item in a specific layer.
I have to state that I'm not writing a project from scratch, and I actually inherited a very basic but complex system.
The system gets the layers from MapGuide 2.5.
This is how the map is initiated:
var map = new ol.Map({
loadTilesWhileInteracting:true,
layers: this.layers,
target: this._element[0],
controls: controls,
interactions: interactions,
view: view
});
view.fit(that.extent, map.getSize());
I tried adding a select interaction - it didn't work (my promise was never called).
var select_interaction = new ol.interaction.Select();
select_interaction.getFeatures().on("add", function (e) {
var feature = e.element; //the feature selected
});
map.addInteraction(select_interaction);
I tried:
map.on('click', function (evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function (feature, layer) {
debugger;
this.log("fff")
});
});
In this case, the promise works but I get no features.
EDIT:
I also tried:
var feature = map.forEachLayerAtPixel(evt.pixel,
function (feature, layer) {..}
but I get the exception:
uncaught security error: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data. ol.js:341
How can I do this?
Thanks,
Ido

I was getting the same error while I was trying to loop through layers at pixel via forEachLayerAtPixel method.
Thanks to #kagelos comment, I managed to solve the problem simply by filtering the layers and looping through only Vector layers.
Layer types are roughly separated in two categories in OL. Image layers (tiles etc.) and vector layers. You have to inspect the contents of this.layers that you pass as an argument in the constructor of the map and see what type of layers it contains. You can only interact directly with vector type layers.
Here is the final code:
Map.forEachLayerAtPixel( cursorPosition, function ( _layer ) {
// you will get the vector layer here
}, this, function ( _layer ) {
if ( _layer instanceof ol.layer.Vector ) {
return true;
}
});

Related

OL3: Clustering Vector Features when can't determine Feature type

I have to load some Features in a Vector layer and have a style function.
var features = new ol.format.GeoJSON().readFeatures( geojsonStr, {
featureProjection: 'EPSG:3857'
});
var vectorSource = new ol.source.Vector({
features: features,
});
/*
var clusterSource = new ol.source.Cluster({
distance: 15,
source: vectorSource
});
*/
var customStyleFunction = function( feature, resolution ) {
....
}
var vectorLayer = new ol.layer.Vector({
//source: clusterSource,
source: vectorSource,
style : customStyleFunction
});
map.addLayer( vectorLayer );
I don't know what kind of geometry I will get in geojsonStr. The problem is: When my collection is of type "Point" I can cluster it, but with any other types I can't see the Layer... How can I cluster Points and ignore Polygons and Lines? or let OL3 be clever enough to decide?
EDIT: I've read https://github.com/openlayers/openlayers/pull/4917
I would recommend you to create 2 different layers: One for clustering and a another one for a common vector layer.
To solve your problem, you can loop through the features and check the geometry type of each, and add it to an already existing source with the addFeature method:
for (var i = 0; i < geojsonFeatures.length; i++) {
if (geojsonFeatures[i].getGeometry().getType() === 'Point') {
clusterSource.addFeature(geojsonFeatures[i]);
} else {
vectorSource.addFeature(geojsonFeatures[i]);
}
}
I have created a jsfiddle which gets a couple of features from a GeoJSON object and add them to different sources depending on the geometry type. If you want to see more points in the cluster souce to make sure that it is working properly, you can use the commented lines as well.

How can I change ol.source.OSM tile url based on level (z value)

We supply tiles for levels 0-9. So when the user goes to a zoom level 10 or higher I want the URL to change back to the default values of Open Street Map.
I've tried this and it almost works. When level 10 or higher is selected I change the URL using the ol.source.OSM.setURLs() function. But in some cases - not all - the image is still set to our local URL. I'm assuming this is some kind of caching issue but not sure.
$scope.tilesource = new ol.source.OSM({
url : '/'+$scope.tileRoot+'/tiles/{z}/{x}/{y}.png',
wrapX : false
});
var raster = new ol.layer.Tile({
source : $scope.tilesource
});
$scope.tilesource.on('tileloadstart', function(arg) {
//console.log(arg.tile.src_);
if ($scope.tileLevelsSupported.search(arg.tile.tileCoord[0]) == -1) {
$scope.tilesource.setUrls(["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png", "https://b.tile.openstreetmap.org/{z}/{x}/{y}.png", "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png"]);
} else {
$scope.tilesource.setUrl('/'+$scope.tileRoot+'/tiles/{z}/{x}/{y}.png');
}
});
I've tried several methods on OSM and Tile but have had no luck. On those instances when the Tile URL is wrong I get the File Not Found 404 error (expected), but then it corrects itself and the tile gets loaded.
Thanks in advance.
Instead of changing the URL, you could use two different layers with the minResolution and maxResolution options:
var raster = new ol.layer.Tile({
source : $scope.tilesource,
minResolution: 200,
maxResolution: 10000000
});
var osmLayer = new ol.layer.Tile({
source: new ol.source.OSM(),
minResolution: 0,
maxResolution: 200
});
When you zoom in from level 9 to 10, the raster layer will become invisible and the osm layer will appear.

How to dectect every layer in the View OpenLayers3

I'm trying to detect all the layers seen in the view of the map (OpenLayers 3).
I've tried this method but it works just for a pixel.
map.forEachLayerAtPixel(evt.pixel, function(layer){
// And I edit the layer...
});
Is there any function that allow me to do it?
thanks.
You should be able to loop through the layers and check if the extent intersects with the view extent. That will at least get you the layers that have some pixels within the current view.
var viewExtent = map.getView().calculateExtent(map.getSize());
var layersInView = [];
map.getLayers().forEach(function (layer) {
var layerExtent = layer.getExtent();
if (ol.extent.intersects(layerExtent, viewExtent)) {
layersInView.push(layer);
}
});
I'm not sure how you'd tell if a layer is actually visible to the user, but this might get you closer.

OpenLayers 3 selecting multiple features from same layer

I have a layer with overlapping features (e.g. bounding boxes). In OL2 the select control seemed to select the expected feature (e.g. the feature with less surface area). In OL3 this doesn't seem to be the case. While I could get all features at a specific pixel, I would prefer for the select control to return all features that intersect with the click. Any way to do this?
You can set the multi member of ol.interaction.Select to true (it allows to choose all features at the coordinate you have clicked) and add an event to choose what feature do you want to select among all the overlapping features:
var select = new ol.interaction.Select({
multi: true
});
var fnHandler = function (e) {
e.selected; // array of selected features
e.target; // select interaction
var feature = e.selected.filter(function (feature) {
// do some filtering to choose what feature do you want
})[0];
e.target.getFeatures().clear(); // unselect all features
e.target.getFeatures().push(feature); // select the feature you filtered
};
select.on('select', fnHandler);
To get all of the features in a layer that intersect with the mouse click I do something like this:
map.on("click", function(event) {
var coordinate = event.coordinate;
var features = myVectorLayer.getSource().getFeaturesAtCoordinate(coordinate);
// Do something with the features that were clicked here...
});

Use of 'drawPolygonGeometry()' on postCompose event with vectorContext

I'm trying to draw a Circle around every kind of geometry (could be every ol.geom type: point,polygon etc.) in an event called on 'postcompose'. The purpose of this is to create an animation when a certain feature is selected.
listenerKeys.push(map.on('postcompose',
goog.bind(this.draw_, this, data)));
this.draw_ = function(data, postComposeRender){
var extent = feature.getGeometry().getExtent();
var flashGeom = new ol.geom.Polygon.fromExtent(extent);
var vectorContext = postComposeRender.vectorContext;
...//ANIMATION CODE TO GET THE RADIUS WITH THE ELAPSED TIME
var imageStyle = this.getStyleSquare_(radius, opacity);
vectorContext.setImageStyle(imageStyle);
vectorContext.drawPolygonGeometry(flashGeom, null);
}
The method
drawPolygonGeometry( {ol.geom.Polygon} , {ol.feature} )
is not working. However, it works when I use the method
drawPointGeometry({ol.geom.Point}, {ol.feature} )
Even if the type of flashGeom is
ol.geom.Polygon that I just built from an extent. I don't want to use this method because extents from polygons could be received and it animates for every point of the polygon...
Finally, after analyzing the way drawPolygonGeometry in OL3 works in the source code, I realized that I need to to apply the style with this method before :
vectorContext.setFillStrokeStyle(imageStyle.getFill(),
imageStyle.getStroke());
DrawPointGeometry and drawPolygonGeometry are not using the same style instance.

Resources