Skip to content

LayerGroup content synchronization #88

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

Merged
merged 11 commits into from
Oct 2, 2014
Merged

LayerGroup content synchronization #88

merged 11 commits into from
Oct 2, 2014

Conversation

petrsloup
Copy link
Member

This PR ensures that changes inside LayerGroups (add/remove layer) are synchronized.

Closes #27.
Closes #38.

@ahocevar
Copy link
Member

Since this is handled in the raster synchronizer, will that also work for vector layers? To me it seems it would be more logical if there was a group synchronizer, which would handle all layer types. Because groups can have vector and raster layers.

@gberaudo
Copy link
Member

@petrsloup, with a hierarchy of layer groups aren't we receive a change event both on the child and on the father?
layergroup1 <- onchange: coming from layergroup2
layergroup2 <- onchange: initial event

@ahocevar, we would loose the flexibility to synchronize only one type of layers.

@petrsloup
Copy link
Member Author

@gberaudo This is true, but only when the layergroup2 is visible: https://github.com/openlayers/ol3/blob/1c7f2ad5ccdc5961f212408c97c7fba8118cd86c/src/ol/layer/layergroup.js#L80. Since we don't synchronize the display properties (including visible), we need to always synchronize the group content and not rely on the event "bubbling" that might not happen.

@ahocevar I don't think the "group synchronizer" is necessary at the moment (and it would require large refactoring) -- I can add the same 3 lines of code to the vectorsynchronizer so the vector layers will stay synchronized as well. It could make sense to implemented some "group synchronizer" as part of the advanced layergroup synchronization (together with the synchronization of the display properties).

@gberaudo
Copy link
Member

@petrsloup, interesting finding.

When layergroup2 is visible and gets changed, the change event will be
received for this layer so a synchronization of layergroup2 will
happen.

But, since the event will be dispatched to the parent layer, it
seems the layergroup1 will also be synchronized.

@ahocevar
Copy link
Member

@petrsloup If vector layers can be kept in sync by adding the same 3 lines of code for vector layers, then I'm fine with it for now. Would you be working on this, or is it something for @gberaudo?

@petrsloup
Copy link
Member Author

@gberaudo Yes, the worst-case scenario is that the synchronization is triggered multiple times (at most once per hierarchy level) if there are nested layergroups and a layer is added to/removed from the "deepest" layergroup.

@ahocevar I added it to this PR.

@ahocevar
Copy link
Member

Looks good to me. Further improvements can be made later - I think this is good to merge.

@gberaudo
Copy link
Member

@ahocevar, we cannot merge this PR knowing the above issue. We must find a real solution.

@ahocevar
Copy link
Member

What about something like this:

var changing = false;
olLayer.on('change', function(e) {
  if (!changing) {
    changing = true;
    this.synchronize();
  }
  if (e.target === /* need reference to map */ map.getLayerGroup()) {
    changing = false;
  }
}, this);

@gberaudo
Copy link
Member

Why not simply listen on add and remove events instead?

@petrsloup
Copy link
Member Author

@ahocevar This would not work, because the event "propagation" can stop at any time and not reach the root.
It's also important to remember, that we currently skip the root layergroup a perform the synchronization on map.getLayers() collection.
One solution would be to add depth parameter to the synchronizeLayer function (which would be increased every time we recursively call synchronizeLayer), then we could do:

olLayer.on('change', function(e) {
  // if this is the last place where the change can be handled
  if (depth == 0 || !olLayer.getVisible()) {
    this.synchronize();
  }
}, this);

@gberaudo 'add' and 'remove' are events of the underlying ol.Collection. It would be possible to listen those, but we also need to handle the case when the whole collection is replaced via .setLayers so we would need to listen to change:layers as well (and create new 'add' and 'remove' listeners when it happens).

@petrsloup
Copy link
Member Author

I just realized the solution drafted above won't work, because we actually need to determine the visibility of the parent group (if the group is hidden, it does not fire the event any more) which is not possible.
Instead of depth we could pass the parent group as a parameter to the synchronizeLayer. Then we could test parentLayer == map.getLayerGroup() || !parentLayer.getVisible().

Alternatively, we could listen add and remove on the collection and change:layers on the group (and re-listen add and remove when this happens).

@gberaudo @ahocevar What do you think -- is this solution acceptable? Any other ideas?

@gberaudo
Copy link
Member

@petrsloup, I strongly prefer your second solution.
It is unfortunate that the ol.layer.Group.prototype.setLayers exist.
Changing the underlying collection of layers is a really strange thing to do.

@klokan
Copy link
Member

klokan commented Sep 30, 2014

@ahocevar how deep hierarchy of LayerGroups is the client using?

@petrsloup
Copy link
Member Author

Since the code duplicity introduced by this PR was getting larger and larger I moved the code shared between raster and vector synchronizer to the new olcs.AbstractSynchronizer -- there are still two independent instances of synchronizers, but the large code duplicity is removed.

This PR now implements the synchronization based on 'add', 'remove' and 'change:layers' events.

@petrsloup
Copy link
Member Author

As a side-effect, I believe this PR now also fixes the issue described in #38 (comment).

this.map = map;

/**
* @type {?ol.View}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ? is not necessary.
The convention in ol3 is to declare nullable object types without ?. It is closure default for objects.

}

this.destroyAll();
this.synchronize();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the synchronize method has not been called yet by the app developer, we should prevent calling synchronize(). See https://github.com/boundlessgeo/ol3-cesium/pull/88/files#r18266082.

});

return;
} else if (!(olLayer instanceof ol.layer.Layer)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about declaring the type of sources handled by the olcs.AbstractSynchronizer and use it here:
@template F,T and !(olLayer instanceof F)
Since the class is about the conversion of an Ol3 type into a Cesium type, it feels more natural to have both from and to types declared. Using F = ol.layer.Layer as the same effect as current code.

@gberaudo
Copy link
Member

gberaudo commented Oct 1, 2014

The introduction of an abstract base class would be a great addition, leading to more robust and readable code. Please see my comments.

@petrsloup
Copy link
Member Author

Thank you for the comments, but most of them are proposing various improvements of the current synchronization mechanism -- this is not the point of this PR.
This PR implements layergroup content synchronization, which turned out to require code restructuring in order to avoid large code duplicity. I agree this will allow us to implement various enhancements more easily, but it should not be part of this PR, which is already getting quite large.

From my point of view, it makes sense to fix the code-style related comment (already done) and unlistening of the 'add' and 'remove' events when layergroup collection changes.

Other comments are out of scope of this PR.

goog.asserts.assert(!goog.isNull(view));
synchronizeLayer(el, view);
});
source.on('updatefeature', function(e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updatefeature has been renamed to changefeature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks

@klokan
Copy link
Member

klokan commented Oct 1, 2014

@gberaudo If fully agree with Petr - we go now for basic possible implementation - there is a ticket for Advanced LayerGroups sync #93 where your comments and more of the listeners can be implemented in future.

@petrsloup
Copy link
Member Author

I added the required changes related to the LayerGroup synchronization.

@petrsloup
Copy link
Member Author

@gberaudo Do you have any other comments regarding the LayerGroup synchronization or can this be merged? Synchronization improvements can be done later in a separate PR.

@ahocevar ahocevar added this to the QA milestone Oct 1, 2014
@klokan
Copy link
Member

klokan commented Oct 1, 2014

One more bug detected by C2C team (mentioned during the Hangout as "never removed listeners") - to be reported here and after it is fixed this can be merged.

}, this);
listenAddRemove();

listenKeyArray.push(olLayer.on('change:layers', function(e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a layer group is removed from the layer tree (using map.removeLayer(aLayerGroup)) what code is going to deregister the change:layers listener from that layer group and the add and remove listeners from that layer group's collection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a layer group is removed, it should fire remove event on some collection. This will result in this.synchronize(); call. The synchronizer keeps track of added event listeners and every time the synchronize is called, it detects which layer groups are no longer used and removes the listeners. (L153)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been tested on our side - the event listeners are removed. Considered to be fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a layer group is removed, it should fire remove event on some collection. This will result in this.synchronize(); call. The synchronizer keeps track of added event listeners and every time the synchronize is called, it detects which layer groups are no longer used and removes the listeners. (L153)

What does look strange to me is that this.map.unByKey is used to deregister listeners that were registered on the collection or on the layer group. I don't get it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gberaudo reminded me that this.map.unByKey is actually just a wrapper to a static function. But it would have been clearer to use ol.Observable.prototype.unByKey rather than this.map.unByKey in the ol3cesium source code. And I hope we'll have ol.Observable.unByKey in the future: openlayers/openlayers#2794.

klokan added a commit that referenced this pull request Oct 2, 2014
@klokan klokan merged commit e3e0249 into openlayers:master Oct 2, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Illegal reference to layerGroup and view LayerGroups basic raster sync
6 participants