-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Allow styles to override feature geometries #3010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
5c48aa1
to
a419463
Compare
What about using a function instead of a concrete geometry (or both)? Something like:
The function would receive the original geometry and can return a new geometry that will be used for the rendering. This would make it even more powerful. |
I would be interested in how you would do that with this approach. From the original linestring you would somehow calculate the positions of the arrows. So instead of a linestring you now have a MultiPoint. But to render the arrows, you would also need the rotation (for each arrow). How would you do that? |
I'm working on a different approach now. Similar to what @tsauerwein suggested, but the function will not be a property of the style, but of ol.layer.Vector and ol.FeatureOverlay, just like the styleFunction. It will be called with feature, resolution and style. |
@tsauerwein To get an arrow for each segment of a linestring, you could configure your layer with a styleFunction like this: function(feature, resolution) {
var styles = [];
var pointsArray = feature.getGeometry().getCoordinates();
for (var i = pointsArray.length - 1; i >= 1; --i) {
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(/* calculate coordinates from segment */ ),
image: new ol.style.Icon({
rotation: /* calculate rotation from segment's gradient */
// other style properties here
});
}));
}
return styles;
} |
If the styles are always the same for a given feature it would be a shame to recalculate them each time. One option is to cache the styles in the feature: function(feature, resolution) {
var styles = [];
var pointsArray = feature.getGeometry().getCoordinates();
for (var i = pointsArray.length - 1; i >= 1; --i) {
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(/* calculate coordinates from segment */ ),
image: new ol.style.Icon({
rotation: /* calculate rotation from segment's gradient */
// other style properties here
});
}));
}
// cache the styles in the feature
feature.setStyle(styles);
return styles;
}
|
There is already the method |
@gberaudo |
Another option would be to configure a I think I'll consider @tsauerwein's suggestion to use a function instead or in addition to an Ok, and now on to the examples. |
For describing arrows positions along a multiline or each segments, this approach will require computations from the developer and storing the world coordinates (eg lonlat coordinates) and derivatives for each point. Though this approach is straightforward and flexible, it is also very inefficient for long/many multilines, creating tens of thousands of styles, one for each arrow point. On the contrary, a declarative approach would be compact, and use a single static style. {
position: ('50%',
image: new ol.style.Circle(),
orientation: ' derivative'
} A declarative approach would also make interaction with external libraries or services like ol3-cesium and mapfish print much easier. |
@gberaudo Your suggestion above would definitely make sense for arrows along lines. The arrow use case is just something that came to my mind, I'm not going to create an example for it. There are other use cases that are perfectly covered by the changes this pull request introduces. |
See also how this is handled with CartoCSS:
|
823bea7
to
5541dc8
Compare
This is now ready for review, with example and unit tests. @tschaub, I think you wanted to see an example, so it would be great if you could take a look at it. |
8a86cf7
to
803e0ed
Compare
Any final review would be much appreciated. Thanks! |
803e0ed
to
2193536
Compare
Thanks for the thorough review @elemoine. I have addressed all your comments. Couple good catches among them. |
With this change, application developers are able to define styles that render a different geometry than the feature geometry. This can e.g. be used to render an interior point of a polygon instead of the polygon, or to render symbols like arrows along lines.
2193536
to
6478454
Compare
I think this adds a lot of flexibility in a way that fits in pretty naturally with the current Alternative 1 Make var func = style.getGeometryFunction();
var geom = func(feature); Alternative 2 Add a var geom = style.getGeometry(feature); The first would be a minor change but gives us yet another function named *Function (a personal peeve). The second is (subjectively) more natural to use (I mean who really is going to do anything with the return from My preference would be to add |
} else if (goog.isString(geometry)) { | ||
this.geometryFunction_ = function(feature) { | ||
return feature.get(geometry); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you've lulled the compiler to sleep here. I mean how can it possibly know that this function returns a geometry?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see your point @tschaub. The function's body should be
var result = feature.get(geometry);
return goog.isDef(result) ? result : null;
Really strange that the compiler does not catch this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or I should accept that the function may return undefined and change the types accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I was meaning that feature.get(geometry)
could return any type (assuming geometry
is a user provided property name and the value could be anything. If the compiler were more strict about this, we'd want an assertion in there that the return was ol.geom.Geometry
(instead of anything else that is possible to store in a feature).
Sorry for the confusion. I know you've already pushed ahocevar@af30b88 - but I think it isn't necessary.
This is what I was talking about:
var feature = new ol.Feature({foo: 'bar'});
var style = new ol.style.Style({
geometry: 'foo',
...
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, I can remove that commit again - but now I need some sleep 😄
Ok @elemoine, but don't you agree that there is not much use in maintaining a Both @tschaub and I agree that having a get/set* pair which is not a real getter/setter is not elegant because it lacks symmetry. So would everyone be at least somewhat happy if I just rename |
Another way to make everyone happy would be to just rename |
I'm happy now. |
I am sorry but I still don't understand why we would not have |
I think what application developers do with an Regarding the question whether |
If possible, it would be great to see some code examples of what people think makes for a nice API. Here is mine: // create a style with a point geometry
var style = new ol.style.Style({
geometry: new ol.geom.Point([1, 2])
});
var point = style.getGeometry();
// create a style with a function that creates a geometry given a feature
var style = new ol.style.Style({
geometry: function(feature) {
// return a geometry based on the feature
}
});
var geometry = style.getGeometry(feature); |
I am not convinced :-) If I set a geometry into a style I want to be able to call This is just opinion @ahocevar and I certainly don't want to block this PR if I'm the only one with this opinion. |
var style = new ol.style.Style({
geometry: new ol.geom.Point([1, 2]);
});
// change the geometry
style.getGeometry().setCoordinates([3, 4]);
// The geometry you configured with your style now has the coordinates [3, 4] Note that the above is with the pull request as it is now. |
@elemoine can you provide some example code that you think would make for a good API? |
Not really. I can't do My API: // create a style with a point geometry
var style = new ol.style.Style({
geometry: new ol.geom.Point([1, 2])
});
var point = style.getGeometry(); // create a style with a point geometry
var style = new ol.style.Style({
geometry: new ol.geom.Point([1, 2])
});
var geometry = style.getGeometryFunction()(feature); // create a style with a function that creates a geometry given a feature
var style = new ol.style.Style({
geometry: function(feature) {
// return a geometry based on the feature
}
});
var geometry = style.getGeometry()(feature); // create a style with a function that creates a geometry given a feature
var style = new ol.style.Style({
geometry: function(feature) {
// return a geometry based on the feature
}
});
var geometry = style.getGeometryFunction()(feature); I think this is consistent with |
I just updated my code snippets. There were mistakes. |
FWIW, if you configured your style with an function() {
return geometry;
}; |
The thing I don't like about this sort of thing is that it is awkward for anybody except the This forces anybody who wants something more predictable to use the even more awkward style.getGeometryFunction()(feature) or even weirder // if the geometry function expects `this` to have special meaning
style.getGeometryFunction().call(style, feature); |
I think this is apparent, but what I'm hoping is that we can reserve the "nice" or "sensible" method names for the most common/useful/flexible purposes. Having sat with this for a while (and revisiting #1690), I'm also compelled by the consistency argument. In the end, I think both So would be fine by me to remove a few commits here and make it so:
@ahocevar whaddya think? |
How about not calling For example: var style = new ol.style.Style({
// ...
});
style.setGeometry(function(feature) {
// "this" is a reference to the style object
// ...
}.bind(style)); This would also make the application code more explicit. |
Ok, I'm going to make these changes and merge. |
Are you going to remove the call to |
Yes, I'm going to remove all that. |
c8bbc47
to
7847016
Compare
Allow styles to override feature geometries
With this change, application developers are able to define styles that
render a different geometry than the feature geometry. This can e.g. be
used to render an interior point of a polygon instead of the polygon, or
to render symbols like arrows along lines.