OpenLayers 3 - Get applied style for rendered feature - openlayers-3

Is it possible to get the applied style for a selected feature? My layer uses a style function instead of an ol.style.Style. When I call myFeature.getStyle() it returns the function.
Updating my question with some things that I have tried so far based on responses here and elsewhere.
I ended up creating a select style function for each layer.
This doesn't work, no style is returned:
selectInteraction = new ol.interaction.Select( {
style: function( feature, resolution ) {
if ( feature.selectStyle ) {
return feature.selectStyle;
}
},
layers: mylayers
} );
Also, with the above I get the following error:
Uncaught TypeError: style.getImage is not a function
ol.renderer.vector.renderFeature #ol.js:38455
ol.renderer.canvas.VectorLayer.renderFeature #ol.js:44202
renderFeature #ol.js:44148
ol.renderer.canvas.VectorLayer.prepareFrame #ol.js:44164
ol.renderer.canvas.Map.renderFrame #ol.js:45268
ol.Map.renderFrame_ #ol.js:52034
(anonymous function) #ol.js:50898
This does work, the feature is rendered using my select style. However, I don't want to have to manage re-setting styles, so it really needs to be implemented in the selectInteraction:
evt.selected.forEach( function( evt ) {
evt.setStyle( evt.selectStyle );
}, this );

My workaround was to create the style separately from the feature and then use setStyle() to apply it. I also saved the style elsewhere in the feature so it could be retrieved later.
let myStyle = new ol.style.Style({
image: new ol.style.RegularShape({
fill: new ol.style.Fill({
color: 'cyan'
}),
stroke: new ol.style.Stroke({
color: 'black',
width: 1
}),
points: 4,
radius: 9,
angle: Math.PI / 4
}),
});
myFeature.setStyle(myStyle);
myFeature.set('myStyle', myStyle); // save it for later use
During the 'select' process simply use:
feature.get('myStyle');
to retrieve the style that was applied to the feature.

You should call the function with the feature (and optionally the resolution if your style function depends on it). So something like: myFeature.getStyle().call(this, feature); or myFeature.getStyle().call(this, feature, resolution);

I think you must use myFeature.getStyleFunction() instead of myFeature.getStyle().
Good Luck.

Related

select event for getting coordinate in openlayers3

I would like you use select interaction to add a feature, but the e.coordinate shows undefined.
var select = new ol.interaction.Select({
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#0288D1',
width: 2
})
})
});
map.addInteraction(select);
select.on('select', function(e) {
var feat = new ol.Feature({
geometry: new ol.geom.Point(e.coordinate),
style: style1
});
alert(e.coordinate);
feat.setStyle(style1);
layerVector.getSource().addFeature(feat);
});
If someone know the reason, tell me how to get the coordinate when i click on viewer with this select interaction.
UPDATE
What you ask in comments (about listeners) can be easier if you become a friend of API Docs. In my beginning it was very hard to know them all, but the docs are much, much better so let it be your source.
Each part of OpenLayers has its own listeners, so, for instance, click at ol.Map, scroll to the "Fires:" section and you'll see the several listeners, the same with ol.View and so forth.
To get clicked coordinate inside ol.interaction.Select listener use:
select.on('select', function(evt){
var coord = evt.mapBrowserEvent.coordinate;
console.info(coord);
// ...
});

Cannot set a style for clicked features in vector layer

So in my Openlayers 3 I set a default style , that renders features the first time the layer loads
function styleFunction(feature, resolution) {
var color = feature.get('color');
var name = feature.get('name');
var fill = new ol.style.Fill({
color: color
});
var stroke = new ol.style.Stroke({
color: "black",
lineCap: "butt",
lineJoin: "bevel",
width:1
});
var text= new ol.style.Text({
font: '20px Verdana',
text: name,
fill: new ol.style.Fill({
color: [64, 64, 64, 0.5]
})
})
var cStyle = new ol.style.Style({
fill: fill,
stroke: stroke,
text: text
});
return [cStyle];
}
When a feature is clicked I want that feature to change its style and the rest to keep the default style above.
This is my attempt to do this
//add simple click interaction to the map
var select = new ol.interaction.Select();
map.addInteraction(select);
//for every clicked feature
select.on('select', function(e) {
var name = e.selected[0].get('name');
//set the clicked style
var fillClick= new ol.style.Fill({
color: "black"
});
var strokeClick = new ol.style.Stroke({
color: "white",
lineCap: "butt",
lineJoin: "bevel",
width:3
});
var textClick= new ol.style.Text({
font: '10px Verdana',
text: name,
fill: new ol.style.Fill({
color: [64, 64, 64, 1]
})
})
var styleClick = new ol.style.Style({
stroke: strokeClick,
fill : fillClick,
text : textClick
});
//set all to default
for (i in e.selected){
e.selected[i].setStyle(styleFunction);
}
//reset the first selected to the clicked style
e.selected[0].setStyle(styleClick);
I dont get any errors on the console, but this wont work. The clicked feature wont return to the default style when I click another one. All the clicked features keep the styleClick.
So, how do I fix this and also, is there a simpler way to do this? I think its a lot of code for this kind of functionality
Thanks
EDIT
I replaced
for (i in e.selected){
e.selected[i].setStyle(styleFunction);
}
with
var allfe = sourceVector.getFeatures();
for (i in allfe){
allfe[i].setStyle(styleFunction);
}
So now all the features will get the style defined in the styleFunction function.
This does not work. I get Uncaught TypeError: feature.get is not a function referring to the first line of the function, var color = feature.get('color'); this one.
I cannot just set a style, I need it to be a function so I can check the geomerty type in the future.
So my two problems,
I dont know how to debug and fix this error
If I have like 500 features, redrawing all of them for every click, will slow the rendering. Is there another solution to this?
Thanks
Layer and feature style functions are different
When using OpenLayers 3 style functions, you have to note the difference between an ol.FeatureStyleFunction and a ol.style.StyleFunction. They are very similar, since they both should return an array of ol.Styles based on a feature and a resolution. They are, however, not provided with that information in the same way.
When providing a style function to a feature, using it's setStyle method, the function should be an ol.FeatureStyleFunction. An ol.FeatureStyleFunction is called with this referencing the feature, and with the resolution as the only argument.
When providing a style function to a layer, it should be an ol.style.StyleFunction. They are called with two arguments, the feature and the resolution.
This is the cause of your error. You are using an ol.style.StyleFunction as a feature style. Later, when your style function is called, the first argument is the resolution and not the feature that you are expecting.
Solution 1: setStyle(null)
This solution has already been proposed in #jonatas-walker's answer. It works by setting a style on your selected features and removing the style from the unselected ones (instead of setting your style function to the unselected features).
OpenLayers will use the feature's style if it has one, and otherwise use the layer's style. So re-setting the unselected features' style to null will make them use the layer's style function again.
Solution 2: use the style option of your select interaction
ol.interaction.Select accepts a style option (an ol.style.StyleFunction), which is used to style the selected features.
Pros: Having one style function for the layer (normal view) and a style function for the select interaction makes the code more readable than mixing feature and layer styles. Also, you don't have to keep track of the events or modify the features.
It could be done similar to this:
var selectedStyleFunction = function(feature, resolution) {
return [new ol.style.Style({
stroke: new ol.style.Stroke({
color: "white",
lineCap: "butt",
lineJoin: "bevel",
width:3
}),
fill : new ol.style.Fill({
color: "black"
}),
text : new ol.style.Text({
font: '10px Verdana',
text: feature.get('name'),
fill: new ol.style.Fill({
color: [64, 64, 64, 1]
})
})
})];
};
var select = new ol.interaction.Select({
style: selectedStyleFunction
});
map.addInteraction(select);
UPDATE - fiddle styling with ol.interaction.Select
How about - http://jsfiddle.net/jonataswalker/zz6d4z7d/
var select = new ol.interaction.Select();
map.addInteraction(select);
var styleClick = function() {
// `this` is ol.Feature
return [
new ol.style.Style({
image: new ol.style.Circle({
fill: new ol.style.Fill({ color: [245, 121, 0, 0.8] }),
stroke: new ol.style.Stroke({ color: [0,0,0,1] }),
radius: 7
}),
text: new ol.style.Text({
font: '24px Verdana',
text: this.get('name'),
offsetY: 20,
fill: new ol.style.Fill({
color: [255, 255, 255, 0.8]
})
})
})
];
};
select.on('select', function(evt){
var selected = evt.selected;
var deselected = evt.deselected;
selected.forEach(function(feature){
feature.setStyle(styleClick);
});
deselected.forEach(function(feature){
feature.setStyle(null);
});
});

where can i find a thorough description of implementing custom ol.style.Style in openlayers 3?

I've read through the source, and looked at the examples but haven't found the answer yet.
I need to style the image that appears on the modify overlay beneath the mouse cursor.
i'm using a custom style function to add midpoints and custom endpoints to the layer used by ol.interaction.Modify. ol.interaction.Modify is applying styling to a point near the mouse cursor to indicate that the feature can be modified. This is great except the cursor styling falls beneath the custom endpoints. i can't find a way to alter the z-index.
so, i'm answering my question for myself. i guess that's what makes the internet wonderful. i'm not a dog.
// we'd normally pass feature & resolution parameters to the function, but we're going to
// make this dynamic, so we'll return a style function for later use which will take those params.
DynamicStyleFunction = ( function( /* no feat/res yet!*/ ) {
/**
you really only get style are rendered upon simple geometries, not features. features are made of different geometry types, and styleFunctions are passed a feature that has its geometries rendered. in terms of styling vector geometries, you have only a few options. side note: if there's some feature you expect to see on the the map and it's not showing up, you probably haven't properly styled it. Or, maybe it hasn't been put it in a collection that is included in the source layer... which is a hiccup for a different day.
*/
// for any geometry that you want to be rendered, you'll want a style.
var styles = {};
var s = styles;
/**
an ol.layer.Vector or FeatureOverlay, renders those features in its source by applying Styles made of Strokes, Fills, and Images (made of strokes and fills) on top of the simple geometries which make up the features
Stroke styles get applied to ol.geom.GeometryType.LINE_STRING
MULTI_LINE_STRING can get different styling if you want
*/
var strokeLinesWhite = new ol.style.Stroke({
color: [255, 255, 255, 1], // white
width: 5,
})
var whiteLineStyle new ol.style.Style({
stroke: strokeLinesWhite
})
styles[ol.geom.GeometryType.LINE_STRING] = whiteLineStyle
/**
Polygon styles get applied to ol.geom.GeometryType.POLYGON
Polygons are gonna get filled. They also have Lines... so they can take stroke
*/
var fillPolygonBlue = new ol.style.Style({
fill: new ol.style.Fill({
color: [0, 153, 255, 1], // blue
})
})
var whiteOutlinedBluePolygon = new ol.style.Style({
stroke: strokeLinesWhite,
fill: fillPolygonBlue,
})
styles[ol.geom.GeometryType.POLYGON] = fillPolygonBlue
/**
Circle styles get applied to ol.geom.GeometryType.POINT
They're made with a radius and a fill, and the edge gets stroked...
*/
var smallRedCircleStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({
color: '#FF0000', // red... but i had to look it up
})
})
})
var whiteBigCircleWithBlueBorderStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 10,
fill: new ol.style.Fill({
color: '#FFFFFF' // i guessed it
})
}),
stroke: new.ol.style.Stroke({
color: '#0000FF', // blue
width: 5
})
})
// render all points as small red circles
styles[ol.geom.GeometryType.POINT] = smallRedCircleStyle
// if you pass an array as the style argument, every rendering of the feature will apply every defined style style rendered with the geometry as the argument. that can be a whole lot of rendering in a FeatureOverlay...
smallRedCircleStyle.setZIndex(Infinity)
whiteBigCircleWithBlueBorderStyle.setZIndex(Infinity -1) // that prob wouldn't work, but i hope it's instructive that you can tinker with styles
// so...
var bullseyePointStyle = [ smallRedCircleStyle, whiteBigCircleWithBlueBorderStyle ];
return function dynamicStyleFunction (feature, resolution){
// this is the actual function getting invoked on each function call
// do whatever you want with the feature/resolution.
if (Array.indexOf(feature.getKeys('thisIsOurBullseyeNode') > -1) {
return bullseyePointStyle
} else if (feature.getGeometryName('whiteBlueBox')){
return whiteOutlinedBluePolygon
} else {
return styles[feature.getGeometryName()]
}
}
})()
ol.interaction.Modify, ol.interaction.Select and ol.interaction.Draw take a style argument to change the look of the sketching features.

What's "an array of ol.style.Style" good for?

A style of an ol.layer.Vector can be set as ol.style.Style, a style function or an array of ol.style.Style. What's the array for and what does it do -- compared to just passing an ol.style.Style object?
I cannot find any information on this, neither in the official API docs nor in the tutorials.
If you look at the draw features example, when drawing lines, they are displayed in blue with a white border/outline.
These is achieved by styling the line twice, first with a large white line, then a thin blue line above.
There are 2 styles for the same geometry. It can’t be done with a single ol.style.Style, so to achieve this you need to pass an array of 2 styles: see the source for this.
Because I think this is still relevant and the edit queue is full for the approved answer, I'm posting this with the links updated and with code examples.
The most common way to see the array of styles in action is when drawing features since this is default style in Openlayers. For the line to appear blue with a white border it has to have two styles since it can't be done with a single Style. Openlayers does this by default like this:
styles['LineString'] = [
new Style({
stroke: new Stroke({
color: white,
width: width + 2,
}),
}),
new Style({
stroke: new Stroke({
color: blue,
width: width,
}),
}),
];
source
To expand on how usefull this feature could be you could check the custom polygons example. To have the vertices highlighted, they use two styles, one for the vertices and another for the polygon contour itself. Relevant piece of code:
const styles = [
new Style({
stroke: new Stroke({
color: 'blue',
width: 3,
}),
fill: new Fill({
color: 'rgba(0, 0, 255, 0.1)',
}),
}),
new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({
color: 'orange',
}),
}),
geometry: function (feature) {
// return the coordinates of the first ring of the polygon
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
}),
];

HighCharts Keep Vertical Line on Click Event

Using this example: http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/stock/demo/candlestick-and-volume/
When you hover over points on the chart, you get a nice vertical line showing you which point you're currently on. I want to modify the click event so that the vertical line stays when I hover away after a click. Changing the line color would be ideal on click, but not necessary.
If I click another point I'd want to remove any previous lines. Any ideas on how I could accomplish this?
The above solution like I said, is really cool, but is kind of a hack (getting the path of the crosshair) into the implementation details of highcharts, and may stop working in future releases, may not be totally cross browser (esp since <IE8 do not support SVG, the adding path may still work as it should be handled by highchart's add path method, but getting the crosshair's path may not work, I may be wrong, am an SVG noob). So here I give you the alternate solution of dynamically adding plotLines. PlotLines also allow some additional features like dashStyles, label etc.
get the axis and x value of point clicked (may not exactly overlap the crosshair)
var xValue = evt.xAxis[0].value;
var xAxis = evt.xAxis[0].axis;
Or
EDIT If you want to have the plotLine at the location of the crosshair and not the click position, you can use following formula (No direct API to get this, obtained from source code hence may stop working if code changes)
var chart = this;
var index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index].x;
Add plotline
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
You can cleanup existing plotline
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
OR
try {
xAxis.removePlotLine(myPlotLineId);
} catch (err) {}
Putting the pieces together
var myPlotLineId="myPlotLine";
...
var chart=this;
index = chart.inverted ? chart.plotHeight + chart.plotTop - evt.chartY : evt.chartX - chart.plotLeft;
var xValue = chart.series[0].tooltipPoints[index];
// var xValue = evt.xAxis[0].value; // To use mouse position and not crosshair's position
var xAxis = evt.xAxis[0].axis;
$.each(xAxis.plotLinesAndBands,function(){
if(this.id===myPlotLineId)
{
this.destroy();
}
});
xAxis.addPlotLine({
value: xValue,
width: 1,
color: 'red',
//dashStyle: 'dash',
id: myPlotLineId
});
...
Add plot lines at click position # jsFiddle
Persist crosshair/cursor as plot lines on click # jsFiddle
You can do it in several ways
Highchart has a very cool renderer that allows you to add various graphics to the chart. One of the options is to add a path I will be illustrating the same here.
We shall reuse the path of the crosshair and add the same to the chart with some additional styles like color you mentioned. The path of the crosshair can be optained as this.tooltip.crosshairs[0].d this is in string form and can be easily converted to an array using the Array.split() function
click: function() {
this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
This will accomplish adding the line. You can store the returned object into a global variable and then when you are about to add another such line, you can destroy the existing one by calling Element.destroy()
var line;
...
chart:{
events: {
click: function() {
if (line) {
line.destroy();
}
line = this.renderer.path(this.tooltip.crosshairs[0].d.split(" ")).attr({
'stroke-width': 2,
stroke: 'red'
}).add();
}
}
...
Persist tooltip / crosshair on click # jsFiddle
Assuming you don't have much meta data to be shown along with the line, this is the easiest (or the coolest :) ) approach. You can also attach meta data if you want to using the renderer's text object etc.
An alternate way could be adding vertical plotLines to the xAxis
UPDATE
Refer my other solution to this question, that would work with zoom,scroll,etc

Resources