-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Select control #897
Select control #897
Conversation
Cool to see this in action @ahocevar. We've discussed this, but just to be clear here, this doesn't do anything with features on the existing layer (it doesn't remove them or suggest to the renderer that they be hidden). It just adds the same features to a new layer. I still think it makes sense to clone the features before adding them to a new layer. The idea behind the render intent would be that it would be a styling hook. So we'd tell the original layer that the rendering intent for the selected features is "hidden" (or similar). And then after cloning the layer, we'd set the render intent to "select" and add them to the temp layer. This would allow the user to decide how selected features should be styled with filter syntax like this: new ol.style.Style({rules: [
new ol.style.Rule({
filter: 'this.renderIntent == "selected"'
symbolizers: [ /** symbolizers for selected style here */ ]
})
]}); I can also imagine we might want to have one temp layer per original layer. This would make it easier to handle applications that let people do things like remove a layer or choose new layers for editing during an editing session. It would also allow the control to clone the original layer's style for the temp layer. This would mean people could just configure their styles in one place and add rules for styling selected or temporary features. The feature cloning and one temp per original layer can change later - we just want to make sure they are considered before adding a way for people to style their selected features. I'll take a closer look tomorrow, but I'm looking forward to having this functionality in. |
Tim, did you mean "after cloning the feature"? |
Don't we want that the feature selection/deselection logic be implemented in an interaction rather in the control? With this the select control would just add the button, and add/remove the interaction when the button is clicked. This can probably be changed later, but I just wanted to know if it makes sense to people. |
@@ -337,6 +346,7 @@ | |||
* @property {number|undefined} opacity Opacity. 0-1. Default is 1. | |||
* @property {ol.source.Source} source Source for this layer. | |||
* @property {ol.style.Style|undefined} style Style. | |||
* @property {boolean|undefined} temp True for temporary layers. For internal use only. |
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.
Do we want to export that property if it's for internal use only? How about adding a private setTemporary method to ol.layer.Vector instead?
@elemoine If we wanted to split this into control and interaction, the control would have to be responsible for adding the interaction to the map, and would have to activate and deactivate the interaction. Is this what you're envisioning? I also think this can be changed later if we want to have this split. |
@tschaub Thanks for your great suggestions. I see a way now how the mapping between features on the source layer and the target sketch layer can work efficiently. I'll add a commit. I also like the idea of having a separate sketch layer for each source layer and cloning styles of the original layer. I'll add a commit for that as well. |
Yes, this is what I envisioned. Adding a select control/toggle button should not be necessary to activate interactive feature selection. Also programmatically selecting features should be possible, eventually. |
As it is, you can activate the control programmatically. The "interaction" is just a click. What else were you envisioning that the interaction would do? |
ol2's select control doesn't add any DOM element. It just enables feature selection on click or drag-box. So I tend to think that his functionality should be implemented in an interaction in ol3 (for example, ol.interaction.ClickSelect and ol.interaction.DragSelect). The ol.control.Select control would add/remove the interaction when the toggle button is pressed/depressed. I haven't thought too much about it, so please tell me if that doesn't make sense to you. |
@tschaub I implemented your suggestions, and also the renderIntent handling. I think this is in good shape now. You may want to continue/finish your review. Thanks! |
@elemoine I spent a bit more time thinking about the control - interaction split. As far as I can see, interactions are designed to change the state of the map's view. The select control does not do that. It manages its own state by keeping track of selected/unselected features, and manages temporary layers to display them. A BoxSelect interaction would have to share the state with a ClickSelect interaction, which suggests that a third component is needed for the state and the selection layer management. And a fourth for providing the button if people want to use it as control. All this makes me think that for now we should stick with the control, and if we come up with something smart we can change it later. Having the events dispatched on the control is not a problem for now, because even if we decide to change that later, we can still relay the events on the control. |
To me interactions just receive map browser events. They may perform any action. The interactions we currently have in master update the view, but I think we could have interactions that call Also, interactions are organized in a prioritized array. This means that a "select interaction" could steal the map browser event, and prevent lower-priority interactions from getting that event.
In ol2 the arrays of selected features are on the layers. This makes me wonder: with the current ol.control.Select code, what do we do when a vector layer is removed? Should the control listen to "remove layer" events and clean up its selected features array?
Yes, the control is a high-level component. If we agree on, and restrict, the control API we can later decompose the code into separate modules. |
Thanks for your valuable comment @elemoine. You are right - managing addition and removal of layers is still a TODO. If we decide to handle feature selection on the layer, it needs to be done on |
Some comments on the control API: Currently an array of layers can be passed to the ol.control.Select constructor. The control keeps a reference to that array. As layers are added to/removed from the map, and made visible/invisible, the control and/or the control user need to update that array. The bookkeeping of this array can be tedious and error-prone. What about working with a layer filter function instead? That function would take a layer as its argument and return a boolean value. For example, if you don't want invisible layers to participate in the layer selection you would do:
And with this, layers that are not in the map would automatically be excluded from the layer selection. Thoughts? |
Your work is very cool, @ahocevar ! I tried this at my environment, and would like to add some comments.
|
@schedul-xor Thanks for your feedback. Point selection is already implemented, and it is even smarter than nearest neighbor search, because it takes the actual symbolizer into account. And it also uses the R-Tree. You can see this outside the context of the Select control in http://ol3js.org/en/r3.0.0-alpha.4/examples/gpx.html - just hover over the points. Regarding the click selection, there is a trade-off between a pure selection mode where map dragging is disabled and the convenience of being able to drag the map. But you can always remove the dragpaninteraction from the map when you want pure selection mode. |
This is what I'm thinking about: https://github.com/elemoine/ol3/commit/cf96b102f2c95b76e43a9fbe5f23c9827b6c6bdb. |
Passing filter functions to functions/constructors is a typical JavaScript pattern.
Couldn't we lazily create selection layers? A feature is clicked, if we don't have a selection layer with the proper style for that feature then we create a new selection layer, otherwise we reuse the selection layer. In this way, we don't have a 1:1 mapping between original layers and selection layers, which means no bookkeeping. Now, what to do when we have selected features and the original layer gets removed from the map, or is made invisible, is application-specific. There are certainly use-cases where you want the selection to remain visible, and others where you don't.
We have ol.Map#removeControl, which does control.setMap(null). I think your code re-registers a listener when removeControl is called. |
Thanks @elemoine, I'll give it a try. There will still be some bookkeeping to see if we have a selection layer already. |
I like the idea of filter functions, but they are more of a hassle if you have multiple layers without clearly distinguishable properties. Say you have four vector layers and want one editable/selectable, it means you have to give the layer some id and then create a function that returns true if the id matches. It is more straightforward to pass a reference to the layer itself. A flexible API would take a filter function or a list of layers. |
@elemoine I have converted the control to an interaction. Would be great if you could take a look. Thanks. |
Thanks @ahocevar. Will have a look this afternoon. |
<div id="docs"> | ||
<p>See the <a href="select-features.js" target="_blank">select-features.js source</a> to see how this is done.</p> | ||
</div> | ||
<div id="tags">SelectFeature, vector</div> |
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.
"select" sounds like a better tag than "SelectFeature" to me. This is minor.
The select-features example does not work in advanced mode (select-features.html?mode=advanced). Is it because the feature.renderIntent property gets renamed, preventing the rule filter from doing its job? |
ol.Feature.prototype.clone = function() { | ||
var clone = new ol.Feature(this.getAttributes()); | ||
clone.setGeometry(this.getGeometry().clone()); | ||
clone.featureId_ = this.featureId_; |
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.
The ol.feature#getFeatureId API doc says: "This identifier is usually the unique id in the source store", so I'm wondering if the clone should have the same featureId as the original feature.
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.
The source store is the server. Think of it as GML fid. If the use case is to clone a feature because it will be persisted on the server with changes later, then the current behavior makes sense. If the use case is to create a copy and duplicate the feature on the server too, then it would be better to not use the same fid for the clone. So how should we proceed?
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.
To me this suggests that the decision should be made at the application level.
What we need here is a mix of deep and shallow cloning, and we do not want to do this in a generic ol.Feature#clone() method.
renderer: ol.RendererHint.CANVAS, | ||
target: 'map', | ||
view: new ol.View2D({ | ||
center: [-10997171.194994785, 5206335.565590534], |
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.
Minor (and I know this is a problem in the example from which it was copied as well), but it would be nice to center the view on the visible data. Something like [-11000000, 4600000]
makes a nice starting point.
Really nice work @ahocevar. I'm in favor of merging this in - the comments I added above can be addressed now or later. I'll admit I'm not totally sold on "select" as an interaction (wondering if instead a more basic "feature" interaction makes sense). I do think we should get this in and move on to editing to see how the different parts work best together. |
I was in the process of reviewing :) No problem. I'll make comments anyway, if I have some. |
Sorry for that @elemoine - additional comments welcome of course. |
}; | ||
goog.inherits(ol.interaction.Interaction, goog.Disposable); |
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.
When the map calls ol.interaction.defaults() to create the default collection of interactions, it should also call registerDisposable on each interaction of the collection.
With the map not disposing of the interactions it creates, and with no exports of our disposable objects' dispose
methods, it is useless to have ol.interaction.Interaction inherit from goog.Disposable.
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.
@elemoine So effectively this means I can also remove ol.interaction.Select#disposeInternal()
?
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 so.
And in any way isn't it when the interaction is removed from the map that the selection layers should be removed, etc.?
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.
Maybe, but I don't see a way to do that because there is no hook that is called when an interaction is removed from the map. For now I'm adding some code to remove selection layers when they do not contain any selection.
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.
Until the select interaction the interactions were all stateless. We need to think this through I guess.
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.
As @tschaub said, it may not be the best thing to have such a high level interaction. But let's see how we get along with it when we work on editing, and make changes as we need them.
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.
Yes.
I added a few comments. And I'm done. |
* Interaction is no goog.Disposable any more. * Permanent cleanup during selection instead of disposeInternal. * Moved selectionLayers creation outside feature loop. * Maintain selectedFeatures and unselectedFeatures only for layers that have a setRenderIntent method.
@elemoine Please take a quick look at #945, which addresses most of your post-merge comments. The only one I did not address was the |
Follow-up improvements for #897
This pull request implements the Select control as described in https://github.com/openlayers/ol3/wiki/Vector-Editing#olcontrolselect. Button css is added in the example only for now, in anticipation with the ongoing control button refactoring that should simplify button css handling.