Skip to content

Commit efc7db9

Browse files
committedNov 2, 2017
Added OverlaySynchronizer
1 parent ea246a6 commit efc7db9

File tree

5 files changed

+604
-3
lines changed

5 files changed

+604
-3
lines changed
 

‎Cesium.externs.js

+63-1
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,19 @@ Cesium.Cartesian3.distance = function(left, right) {};
886886
Cesium.Cartesian3.angleBetween = function(left, right) {};
887887

888888

889+
/**
890+
* @param {Array.<number>} degrees
891+
* @return {Array.<Cesium.Cartesian3>}
892+
*/
893+
Cesium.Cartesian3.fromDegreesArray = function(degrees) {};
894+
/**
895+
* @param {Array.<number>} degrees
896+
* @return {Array.<Cesium.Cartesian3>}
897+
*/
898+
Cesium.Cartesian3.fromDegreesArrayHeights = function(degrees) {};
899+
900+
901+
889902

890903
/**
891904
* @constructor
@@ -2372,6 +2385,14 @@ Cesium.PerspectiveFrustrum.prototype.near;
23722385
*/
23732386
Cesium.PerspectiveFrustrum.prototype.projectionMatrix;
23742387

2388+
/**
2389+
* @param {Cesium.Cartesian3} position
2390+
* @param {Cesium.Cartesian3} direction
2391+
* @param {Cesium.Cartesian3} up
2392+
* @return {Cesium.CullingVolume}
2393+
*/
2394+
Cesium.PerspectiveFrustrum.prototype.computeCullingVolume = function(position, direction, up) {};
2395+
23752396

23762397
/**
23772398
* @param {!number} drawingBufferWidth
@@ -2575,6 +2596,11 @@ Cesium.Scene.prototype.skyAtmosphere;
25752596
*/
25762597
Cesium.Scene.prototype.maximumAliasedLineWidth;
25772598

2599+
/**
2600+
* @param {Cesium.Cartesian3} value
2601+
* @return {Cesium.Cartesian2}
2602+
*/
2603+
Cesium.Scene.prototype.cartesianToCanvasCoordinates = function(value) {};
25782604

25792605
/**
25802606
* @typedef {{
@@ -3378,9 +3404,11 @@ Cesium.EventHelper.prototype.removeAll = function() {};
33783404

33793405

33803406
/**
3407+
* @param {Cesium.Cartesian3=} center
3408+
* @param {number=} radius
33813409
* @constructor
33823410
*/
3383-
Cesium.BoundingSphere = function() {};
3411+
Cesium.BoundingSphere = function(center, radius) {};
33843412

33853413

33863414
/**
@@ -3422,3 +3450,37 @@ Cesium.EntityView.prototype.update = function(currentTime, bs) {};
34223450
* @constructor
34233451
*/
34243452
Cesium.CallbackProperty = function(cb, constant) {};
3453+
3454+
/**
3455+
* @param {Cesium.BoundingSphere} occluderBoundingSphere
3456+
* @param {Cesium.Cartesian3} cameraPosition
3457+
* @constructor
3458+
*/
3459+
Cesium.Occluder = function(occluderBoundingSphere, cameraPosition) {};
3460+
3461+
/**
3462+
* @param {Cesium.Cartesian3} occludee
3463+
*/
3464+
Cesium.Occluder.prototype.isPointVisible = function(occludee) {};
3465+
3466+
/**
3467+
* @enum {number}
3468+
*/
3469+
Cesium.Intersect = {
3470+
OUTSIDE: -1,
3471+
INTERSECTING: 0,
3472+
INSIDE: 1
3473+
};
3474+
3475+
3476+
/**
3477+
* @param {Array.<Cesium.Cartesian4>} planes
3478+
* @constructor
3479+
*/
3480+
Cesium.CullingVolume = function(planes) {};
3481+
3482+
/**
3483+
* @param {Object} boundingVolume
3484+
* @return {Cesium.Intersect}
3485+
*/
3486+
Cesium.Occluder.prototype.computeVisibility = function(boundingVolume) {};

‎css/olcs.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.olcs-hideoverlay .ol-overlay-container {
2+
visibility:hidden;
3+
}

‎src/olcs/olcesium.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ goog.require('olcs.AutoRenderLoop');
1212
goog.require('olcs.Camera');
1313
goog.require('olcs.RasterSynchronizer');
1414
goog.require('olcs.VectorSynchronizer');
15+
goog.require('olcs.OverlaySynchronizer');
1516

1617

1718

@@ -210,7 +211,8 @@ olcs.OLCesium = function(options) {
210211
const synchronizers = options.createSynchronizers ?
211212
options.createSynchronizers(this.map_, this.scene_, this.dataSourceCollection_) : [
212213
new olcs.RasterSynchronizer(this.map_, this.scene_),
213-
new olcs.VectorSynchronizer(this.map_, this.scene_)
214+
new olcs.VectorSynchronizer(this.map_, this.scene_),
215+
new olcs.OverlaySynchronizer(this.map_, this.scene_)
214216
];
215217

216218
// Assures correct canvas size after initialisation
@@ -532,6 +534,15 @@ olcs.OLCesium.prototype.getEnabled = function() {
532534
return this.enabled_;
533535
};
534536

537+
/**
538+
* @return {Element}
539+
* @api
540+
*/
541+
olcs.OLCesium.prototype.getCanvas = function() {
542+
const viewport = this.map_.getViewport();
543+
return viewport.firstElementChild;
544+
};
545+
535546

536547
/**
537548
* Enables/disables the Cesium.
@@ -563,7 +574,11 @@ olcs.OLCesium.prototype.setEnabled = function(enable) {
563574
this.hiddenRootGroup_ = rootGroup;
564575
this.hiddenRootGroup_.setVisible(false);
565576
}
577+
578+
this.map_.getOverlayContainer().classList.add('olcs-hideoverlay');
579+
this.map_.getOverlayContainerStopEvent().classList.add('olcs-hideoverlay');
566580
}
581+
567582
this.camera_.readFromView();
568583
this.render_();
569584
} else {
@@ -573,7 +588,8 @@ olcs.OLCesium.prototype.setEnabled = function(enable) {
573588
interactions.push(interaction);
574589
});
575590
this.pausedInteractions_.length = 0;
576-
591+
this.map_.getOverlayContainer().classList.remove('olcs-hideoverlay');
592+
this.map_.getOverlayContainerStopEvent().classList.remove('olcs-hideoverlay');
577593
if (this.hiddenRootGroup_) {
578594
this.hiddenRootGroup_.setVisible(true);
579595
this.hiddenRootGroup_ = null;
@@ -709,3 +725,4 @@ olcs.OLCesium.prototype.throwOnUnitializedMap_ = function() {
709725
throw new Error(`The OpenLayers map is not properly initialized: ${center} / ${view.getResolution()}`);
710726
}
711727
};
728+

‎src/olcs/overlay.js

+350
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
goog.provide('olcs.Overlay');
2+
3+
4+
goog.require('ol');
5+
goog.require('ol.proj');
6+
goog.require('ol.dom');
7+
goog.require('ol.Observable');
8+
goog.require('ol.css');
9+
10+
/**
11+
* @constructor
12+
* @param {{
13+
* scene: !Cesium.Scene,
14+
* parent: !ol.Overlay,
15+
* synchronizer: !olcs.OverlaySynchronizer
16+
* }} options Overlay options.
17+
* @api
18+
*/
19+
olcs.Overlay = function(options) {
20+
/**
21+
* @private
22+
* @type {?Function}
23+
*/
24+
this.scenePostRenderListenerRemover_ = null;
25+
26+
/**
27+
* @type {!Cesium.Scene}
28+
*/
29+
this.scene = options.scene;
30+
31+
/** @type {!olcs.OverlaySynchronizer} */
32+
this.synchronizer = options.synchronizer;
33+
34+
/**
35+
* @private
36+
* @type {boolean}
37+
* @todo read from parent
38+
*/
39+
this.insertFirst_ = true;
40+
41+
/**
42+
* @private
43+
* @type {boolean}
44+
* @todo read from parent
45+
*/
46+
this.stopEvent_ = true;
47+
48+
/**
49+
* @private
50+
* @type {Element}
51+
*/
52+
this.element_ = document.createElement('DIV');
53+
this.element_.className = `ol-overlay-container ${ol.css.CLASS_SELECTABLE}`;
54+
this.element_.style.position = 'absolute';
55+
56+
/**
57+
* @private
58+
* @type {!ol.Overlay}
59+
*/
60+
this.parent_ = options['parent'];
61+
62+
/** @type {ol.Coordinate|undefined} */
63+
this.position = this.parent_.getPosition();
64+
65+
/** @type {Element|undefined} */
66+
this.element = this.parent_.getElement();
67+
68+
/**
69+
* @private
70+
* @type {{bottom_: string,
71+
* left_: string,
72+
* right_: string,
73+
* top_: string,
74+
* visible: boolean}}
75+
*/
76+
this.rendered_ = {
77+
bottom_: '',
78+
left_: '',
79+
right_: '',
80+
top_: '',
81+
visible: true
82+
};
83+
84+
/**
85+
* @type {MutationObserver|null}
86+
* @private
87+
*/
88+
this.observer_ = null;
89+
90+
if (this.parent_.getElement()) {
91+
this.addMutationObserver_(this.parent_.getElement().parentNode);
92+
}
93+
94+
/** @type {Array<ol.EventsKey>} */
95+
this.listenerKeys_ = [];
96+
this.listenerKeys_.push(this.parent_.on('change:position', this.changePosition_.bind(this)));
97+
this.listenerKeys_.push(this.parent_.on('change:element', this.changeElement_.bind(this)));
98+
};
99+
100+
/**
101+
* @param {Node} target
102+
* @private
103+
*/
104+
olcs.Overlay.prototype.addMutationObserver_ = function(target) {
105+
if (this.observer_ === null) {
106+
this.observer_ = new MutationObserver(this.changeElement_.bind(this));
107+
this.observer_.observe(target, {
108+
attributes: true,
109+
childList: true,
110+
characterData: false,
111+
subtree: true
112+
});
113+
}
114+
};
115+
116+
/**
117+
* Get the scene associated with this overlay.
118+
* @see ol.Overlay.prototype.getMap
119+
* @return {!Cesium.Scene} The scene that the overlay is part of.
120+
* @observable
121+
* @api
122+
*/
123+
olcs.Overlay.prototype.getScene = function() {
124+
return this.scene;
125+
};
126+
127+
/**
128+
* Modify the visibility of the element.
129+
* @param {boolean} visible Element visibility.
130+
* @protected
131+
*/
132+
olcs.Overlay.prototype.setVisible = function(visible) {
133+
if (this.rendered_.visible !== visible) {
134+
this.element_.style.display = visible ? '' : 'none';
135+
this.rendered_.visible = visible;
136+
}
137+
};
138+
139+
/**
140+
* @api
141+
*/
142+
olcs.Overlay.prototype.handleMapChanged = function() {
143+
if (this.scenePostRenderListenerRemover_) {
144+
this.scenePostRenderListenerRemover_();
145+
}
146+
this.scenePostRenderListenerRemover_ = null;
147+
148+
const scene = this.getScene();
149+
if (scene) {
150+
this.scenePostRenderListenerRemover_ = scene.postRender.addEventListener(this.updatePixelPosition.bind(this));
151+
this.updatePixelPosition();
152+
const container = this.stopEvent_ ?
153+
this.synchronizer.getOverlayContainerStopEvent() : this.synchronizer.getOverlayContainer(); // TODO respect stop-event flag in synchronizer
154+
if (this.insertFirst_) {
155+
container.insertBefore(this.element_, container.childNodes[0] || null);
156+
} else {
157+
container.appendChild(this.element_);
158+
}
159+
}
160+
};
161+
162+
/**
163+
* @protected
164+
*/
165+
olcs.Overlay.prototype.handleElementChanged = function() {
166+
ol.dom.removeChildren(this.element_);
167+
const element = this.getElement();
168+
if (element) {
169+
this.element_.appendChild(element);
170+
}
171+
};
172+
173+
174+
/**
175+
* Sets up a freshly created overlay
176+
* @api
177+
*/
178+
olcs.Overlay.prototype.init = function() {
179+
this.changePosition_();
180+
this.changeElement_();
181+
this.handleMapChanged();
182+
};
183+
184+
/**
185+
* @return {ol.Coordinate|undefined}
186+
* @api
187+
*/
188+
olcs.Overlay.prototype.getPosition = function() {
189+
return this.position;
190+
};
191+
192+
/**
193+
* @return {Array.<number>}
194+
* @api
195+
*/
196+
olcs.Overlay.prototype.getOffset = function() {
197+
return this.parent_.getOffset();
198+
};
199+
200+
/**
201+
* @return {ol.OverlayPositioning}
202+
* @api
203+
*/
204+
olcs.Overlay.prototype.getPositioning = function() {
205+
return this.parent_.getPositioning();
206+
};
207+
208+
/**
209+
* @return {Element|undefined}
210+
*/
211+
olcs.Overlay.prototype.getElement = function() {
212+
return this.element;
213+
};
214+
215+
/**
216+
* @param {ol.Coordinate|undefined} position
217+
*/
218+
olcs.Overlay.prototype.setPosition = function(position) {
219+
this.position = position;
220+
};
221+
222+
/**
223+
* @param {Element} element
224+
*/
225+
olcs.Overlay.prototype.setElement = function(element) {
226+
this.element = element;
227+
this.handleElementChanged();
228+
};
229+
230+
/**
231+
* @private
232+
*/
233+
olcs.Overlay.prototype.changePosition_ = function() {
234+
const position = this.parent_.getPosition();
235+
236+
if (position) {
237+
this.setVisible(true);
238+
const sourceProjection = this.parent_.getMap().getView().getProjection();
239+
const coords = ol.proj.transform(position, sourceProjection, 'EPSG:4326');
240+
this.setPosition(coords);
241+
this.handleMapChanged();
242+
} else {
243+
this.setPosition(undefined);
244+
this.setVisible(false);
245+
}
246+
};
247+
248+
/**
249+
* @private
250+
*/
251+
olcs.Overlay.prototype.changeElement_ = function() {
252+
function cloneNode(node, parent) {
253+
const clone = node.cloneNode();
254+
if (parent) {
255+
parent.appendChild(clone);
256+
}
257+
if (node.nodeType != Node.TEXT_NODE) {
258+
clone.addEventListener('click', (event) => {
259+
node.dispatchEvent(new MouseEvent('click', event));
260+
event.stopPropagation();
261+
});
262+
}
263+
const nodes = node.childNodes;
264+
for (let i = 0; i < nodes.length; i++) {
265+
if (!nodes[i]) {
266+
continue;
267+
}
268+
cloneNode(nodes[i], clone);
269+
}
270+
return clone;
271+
}
272+
273+
if (this.parent_.getElement()) {
274+
const clonedNode = cloneNode(this.parent_.getElement(), null);
275+
276+
this.setElement(clonedNode);
277+
const parentNode = this.getElement().parentNode;
278+
if (parentNode) {
279+
this.addMutationObserver_(parentNode);
280+
while (parentNode.firstChild) {
281+
parentNode.removeChild(parentNode.firstChild);
282+
}
283+
const childNodes = this.parent_.getElement().parentNode.childNodes;
284+
for (let i = 0; i < childNodes.length; i++) {
285+
cloneNode(childNodes[i], parentNode);
286+
}
287+
}
288+
}
289+
};
290+
291+
/**
292+
* Update pixel position.
293+
* @suppress {checkTypes, accessControls}
294+
*/
295+
olcs.Overlay.prototype.updatePixelPosition = function() {
296+
const scene = this.getScene();
297+
const position = this.getPosition();
298+
if (!scene || !position) {
299+
this.setVisible(false);
300+
return;
301+
}
302+
let cartesian;
303+
if (position.length === 2) {
304+
cartesian = Cesium.Cartesian3.fromDegreesArray(position)[0];
305+
} else {
306+
cartesian = Cesium.Cartesian3.fromDegreesArrayHeights(position)[0];
307+
}
308+
309+
const ellipsoidBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(), 6356752);
310+
const occluder = new Cesium.Occluder(ellipsoidBoundingSphere, scene.camera.position);
311+
// check if overlay position is behind the horizon
312+
if (!occluder.isPointVisible(cartesian)) {
313+
this.setVisible(false);
314+
return;
315+
}
316+
const cullingVolume = scene.camera.frustum.computeCullingVolume(scene.camera.position, scene.camera.direction, scene.camera.up);
317+
// check if overlay position is visible from the camera
318+
if (cullingVolume.computeVisibility(new Cesium.BoundingSphere(cartesian)) !== 1) {
319+
this.setVisible(false);
320+
return;
321+
}
322+
this.setVisible(true);
323+
324+
const pixelCartesian = scene.cartesianToCanvasCoordinates(cartesian);
325+
const pixel = [pixelCartesian.x, pixelCartesian.y];
326+
const mapSize = [scene.canvas.width, scene.canvas.height];
327+
const that = /** @type {ol.Overlay} */ (this);
328+
ol.Overlay.prototype.updateRenderedPosition.call(that, pixel, mapSize);
329+
};
330+
331+
/**
332+
* Destroys the overlay, removing all its listeners and elements
333+
* @api
334+
*/
335+
olcs.Overlay.prototype.destroy = function() {
336+
if (this.scenePostRenderListenerRemover_) {
337+
this.scenePostRenderListenerRemover_();
338+
}
339+
if (this.observer_) {
340+
this.observer_.disconnect();
341+
this.observer_ = null;
342+
}
343+
ol.Observable.unByKey(this.listenerKeys_);
344+
this.listenerKeys_.splice(0);
345+
if (this.element_.removeNode) {
346+
this.element_.removeNode(true);
347+
} else {
348+
this.element_.remove();
349+
}
350+
};

‎src/olcs/overlaysynchronizer.js

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
goog.provide('olcs.OverlaySynchronizer');
2+
3+
goog.require('olcs.Overlay');
4+
5+
goog.require('ol');
6+
goog.require('ol.events');
7+
8+
/**
9+
* @param {!ol.Map} map
10+
* @param {!Cesium.Scene} scene
11+
* @constructor
12+
* @template T
13+
* @struct
14+
* @api
15+
*/
16+
olcs.OverlaySynchronizer = function(map, scene) {
17+
/**
18+
* @type {!ol.Map}
19+
* @protected
20+
*/
21+
this.map = map;
22+
23+
/**
24+
* @type {ol.Collection.<ol.Overlay>}
25+
* @private
26+
*/
27+
this.overlays = this.map.getOverlays();
28+
29+
/**
30+
* @type {!Cesium.Scene}
31+
* @protected
32+
*/
33+
this.scene = scene;
34+
35+
/**
36+
* @private
37+
* @type {!Element}
38+
*/
39+
this.overlayContainerStopEvent_ = document.createElement('DIV');
40+
this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
41+
const overlayEvents = [
42+
ol.events.EventType.CLICK,
43+
ol.events.EventType.DBLCLICK,
44+
ol.events.EventType.MOUSEDOWN,
45+
ol.events.EventType.TOUCHSTART,
46+
ol.events.EventType.MSPOINTERDOWN,
47+
ol.MapBrowserEventType.POINTERDOWN,
48+
ol.events.EventType.MOUSEWHEEL,
49+
ol.events.EventType.WHEEL
50+
];
51+
overlayEvents.forEach((event) => {
52+
ol.events.listen(this.overlayContainerStopEvent_, event, ol.events.Event.stopPropagation);
53+
});
54+
this.scene.canvas.parentElement.appendChild(this.overlayContainerStopEvent_);
55+
56+
/**
57+
* @type {!Object<?,olcs.Overlay>}
58+
* @private
59+
*/
60+
this.overlayMap_ = {};
61+
};
62+
63+
64+
65+
/**
66+
* Get the element that serves as a container for overlays that don't allow
67+
* event propagation. Elements added to this container won't let mousedown and
68+
* touchstart events through to the map, so clicks and gestures on an overlay
69+
* don't trigger any {@link ol.MapBrowserEvent}.
70+
* @return {!Element} The map's overlay container that stops events.
71+
*/
72+
olcs.OverlaySynchronizer.prototype.getOverlayContainerStopEvent = function() {
73+
return this.overlayContainerStopEvent_;
74+
};
75+
76+
/**
77+
* Get the element that serves as a container for overlays that don't allow
78+
* event propagation. Elements added to this container won't let mousedown and
79+
* touchstart events through to the map, so clicks and gestures on an overlay
80+
* don't trigger any {@link ol.MapBrowserEvent}.
81+
* @return {!Element} The map's overlay container that stops events.
82+
* @todo return the non-stop equivalent, see the olcs.Overlay todos
83+
*/
84+
olcs.OverlaySynchronizer.prototype.getOverlayContainer = function() {
85+
return this.overlayContainerStopEvent_;
86+
};
87+
88+
/**
89+
* Destroy all and perform complete synchronization of the overlays.
90+
* @api
91+
*/
92+
olcs.OverlaySynchronizer.prototype.synchronize = function() {
93+
this.destroyAll();
94+
this.addOverlays();
95+
this.overlays.on('add', this.addOverlayFromEvent_.bind(this));
96+
this.overlays.on('remove', this.removeOverlayFromEvent_.bind(this));
97+
};
98+
99+
/**
100+
* @param {ol.Collection.Event} event
101+
* @private
102+
*/
103+
olcs.OverlaySynchronizer.prototype.addOverlayFromEvent_ = function(event) {
104+
const overlay = /** @type {ol.Overlay} */ (event.element);
105+
this.addOverlay(overlay);
106+
};
107+
108+
/**
109+
* @api
110+
*/
111+
olcs.OverlaySynchronizer.prototype.addOverlays = function() {
112+
this.overlays.forEach(this.addOverlay, this);
113+
};
114+
115+
/**
116+
* @param {ol.Overlay} overlay
117+
* @api
118+
*/
119+
olcs.OverlaySynchronizer.prototype.addOverlay = function(overlay) {
120+
if (!overlay) {
121+
return;
122+
}
123+
const cesiumOverlay = new olcs.Overlay({
124+
scene: this.scene,
125+
synchronizer: this,
126+
parent: overlay
127+
});
128+
129+
cesiumOverlay.init();
130+
const overlayId = ol.getUid(overlay).toString();
131+
this.overlayMap_[overlayId] = cesiumOverlay;
132+
};
133+
134+
/**
135+
* @param {ol.Collection.Event} event
136+
* @private
137+
*/
138+
olcs.OverlaySynchronizer.prototype.removeOverlayFromEvent_ = function(event) {
139+
const removedOverlay = /** @type {ol.Overlay} */ (event.element);
140+
this.removeOverlay(removedOverlay);
141+
};
142+
143+
/**
144+
* Removes an overlay from the scene
145+
* @param {ol.Overlay} overlay
146+
* @api
147+
*/
148+
olcs.OverlaySynchronizer.prototype.removeOverlay = function(overlay) {
149+
const overlayId = ol.getUid(overlay).toString();
150+
const csOverlay = this.overlayMap_[overlayId];
151+
if (csOverlay) {
152+
csOverlay.destroy();
153+
delete this.overlayMap_[overlayId];
154+
}
155+
};
156+
157+
/**
158+
* Destroys all the created Cesium objects.
159+
* @protected
160+
*/
161+
olcs.OverlaySynchronizer.prototype.destroyAll = function() {
162+
Object.keys(this.overlayMap_).forEach((key) => {
163+
const overlay = this.overlayMap_[key];
164+
overlay.destroy();
165+
delete this.overlayMap_[key];
166+
});
167+
};
168+
169+

0 commit comments

Comments
 (0)
Please sign in to comment.