From 57a86353cd354b13de056a2ce305d1296c6c0685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Wed, 20 Feb 2013 16:47:22 +0100 Subject: [PATCH 01/28] zoomFactor defaults to 2 --- examples/wms-custom-proj.js | 14 -------------- src/ol/map.js | 4 ++-- src/ol/view2d.js | 6 ++---- test/spec/ol/map.test.js | 2 +- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/examples/wms-custom-proj.js b/examples/wms-custom-proj.js index 19d03ca493..6aae577534 100644 --- a/examples/wms-custom-proj.js +++ b/examples/wms-custom-proj.js @@ -19,19 +19,6 @@ var epsg21781 = new ol.Projection('EPSG:21781', ol.ProjectionUnits.METERS, new ol.Extent(485869.5728, 76443.1884, 837076.5648, 299941.7864)); ol.projection.addProjection(epsg21781); -// We could give the single image source a set of resolutions. This prevents the -// source from requesting images of arbitrary resolutions. To try it, uncomment -// the block below and the resolutions option in the SingleImageWMS config. -/* -var projectionExtent = epsg21781.getExtent(); -var maxResolution = Math.max(projectionExtent.getWidth(), - projectionExtent.getHeight()) / 256; -var resolutions = new Array(10); -for (var i = 0; i < 10; ++i) { - resolutions[i] = maxResolution / Math.pow(2.0, i); -} -*/ - var extent = new ol.Extent(420000, 30000, 900000, 350000); var layers = new ol.Collection([ new ol.layer.TileLayer({ @@ -50,7 +37,6 @@ var layers = new ol.Collection([ }), new ol.layer.ImageLayer({ source: new ol.source.SingleImageWMS({ - //resolutions: resolutions, url: 'http://wms.geo.admin.ch/', attributions: [new ol.Attribution( '© ' + diff --git a/src/ol/map.js b/src/ol/map.js index 9dfcee6d75..771122e531 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -941,7 +941,7 @@ ol.Map.createControls_ = function(mapOptions) { mapOptions.zoomControl : true; if (zoomControl) { var zoomDelta = goog.isDef(mapOptions.zoomDelta) ? - mapOptions.zoomDelta : 4; + mapOptions.zoomDelta : 1; controls.push(new ol.control.Zoom({ delta: zoomDelta })); @@ -971,7 +971,7 @@ ol.Map.createInteractions_ = function(mapOptions) { mapOptions.doubleClickZoom : true; if (doubleClickZoom) { var zoomDelta = goog.isDef(mapOptions.zoomDelta) ? - mapOptions.zoomDelta : 4; + mapOptions.zoomDelta : 1; interactions.push(new ol.interaction.DblClickZoom(zoomDelta)); } diff --git a/src/ol/view2d.js b/src/ol/view2d.js index f80d3cf25c..9f8a82db1c 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -359,10 +359,8 @@ ol.View2D.createConstraints_ = function(view2DOptions) { maxResolution = Math.max( projectionExtent.maxX - projectionExtent.minX, projectionExtent.maxY - projectionExtent.minY) / ol.DEFAULT_TILE_SIZE; - // number of steps we want between two data resolutions - var numSteps = 4; - numZoomLevels = 29 * numSteps; - zoomFactor = Math.exp(Math.log(2) / numSteps); + numZoomLevels = 29; + zoomFactor = 2; } resolutionConstraint = ol.ResolutionConstraint.createSnapToPower( zoomFactor, maxResolution, numZoomLevels - 1); diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 776d268400..c5e0ff2b30 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -121,7 +121,7 @@ describe('ol.Map', function() { var interactions = ol.Map.createInteractions_(options); expect(interactions.getLength()).toEqual(1); expect(interactions.getAt(0)).toBeA(ol.interaction.DblClickZoom); - expect(interactions.getAt(0).delta_).toEqual(4); + expect(interactions.getAt(0).delta_).toEqual(1); }); }); From 3ba9a4afd4779c40d9a38fdb6cdbceb60bc52dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Wed, 20 Feb 2013 16:51:48 +0100 Subject: [PATCH 02/28] View2D zoom may animate pan --- src/ol/view2d.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 9f8a82db1c..2fcc1f9016 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -4,6 +4,7 @@ goog.provide('ol.View2D'); goog.provide('ol.View2DProperty'); +goog.require('goog.fx.easing'); goog.require('ol.Constraints'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); @@ -312,12 +313,22 @@ ol.View2D.prototype.zoom_ = function(map, resolution, opt_anchor) { */ ol.View2D.prototype.zoom = function(map, delta, opt_anchor, opt_duration) { var currentResolution = this.getResolution(); - if (goog.isDef(currentResolution) && goog.isDef(opt_duration)) { + var currentCenter = this.getCenter(); + if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && + goog.isDef(opt_duration)) { map.requestRenderFrame(); map.addPreRenderFunction(ol.animation.zoom({ resolution: currentResolution, - duration: opt_duration + duration: opt_duration, + easing: goog.fx.easing.easeOut })); + if (goog.isDef(opt_anchor)) { + map.addPreRenderFunction(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + } } var resolution = this.constraints_.resolution(currentResolution, delta); this.zoom_(map, resolution, opt_anchor); From 4d486601f98e8dc05b51f0aed72bdf54d6099f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Wed, 20 Feb 2013 16:52:20 +0100 Subject: [PATCH 03/28] MouseWheelZoom interaction animates zoom --- src/ol/interaction/mousewheelzoominteraction.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ol/interaction/mousewheelzoominteraction.js b/src/ol/interaction/mousewheelzoominteraction.js index a0e8f52cc9..9b49e01024 100644 --- a/src/ol/interaction/mousewheelzoominteraction.js +++ b/src/ol/interaction/mousewheelzoominteraction.js @@ -8,6 +8,12 @@ goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); +/** + * @define {number} Zoom duration. + */ +ol.interaction.ZOOM_DURATION = 200; + + /** * @constructor @@ -43,7 +49,7 @@ ol.interaction.MouseWheelZoom.prototype.handleMapBrowserEvent = var view = map.getView(); goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - view.zoom(map, delta, anchor); + view.zoom(map, delta, anchor, ol.interaction.ZOOM_DURATION); mapBrowserEvent.preventDefault(); mouseWheelEvent.preventDefault(); } From faef495cfdf40973d6911e07edc85cb9c0536361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Thu, 21 Feb 2013 14:52:15 +0100 Subject: [PATCH 04/28] Debounce mousewheel zoom events --- src/objectliterals.exports | 1 - .../interaction/mousewheelzoominteraction.js | 91 ++++++++++++++++--- src/ol/map.js | 5 +- test/spec/ol/map.test.js | 27 +----- 4 files changed, 84 insertions(+), 40 deletions(-) diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 3f0a1e984d..36493574b1 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -7,7 +7,6 @@ @exportObjectLiteralProperty ol.MapOptions.keyboardPanOffset number|undefined @exportObjectLiteralProperty ol.MapOptions.layers ol.Collection|undefined @exportObjectLiteralProperty ol.MapOptions.mouseWheelZoom boolean|undefined -@exportObjectLiteralProperty ol.MapOptions.mouseWheelZoomDelta number|undefined @exportObjectLiteralProperty ol.MapOptions.renderer ol.RendererHint|undefined @exportObjectLiteralProperty ol.MapOptions.renderers Array.|undefined @exportObjectLiteralProperty ol.MapOptions.scaleLineControl boolean|undefined diff --git a/src/ol/interaction/mousewheelzoominteraction.js b/src/ol/interaction/mousewheelzoominteraction.js index 9b49e01024..b274dd7e35 100644 --- a/src/ol/interaction/mousewheelzoominteraction.js +++ b/src/ol/interaction/mousewheelzoominteraction.js @@ -4,30 +4,63 @@ goog.provide('ol.interaction.MouseWheelZoom'); goog.require('goog.events.MouseWheelEvent'); goog.require('goog.events.MouseWheelHandler.EventType'); +goog.require('goog.math'); +goog.require('ol.Coordinate'); goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); /** - * @define {number} Zoom duration. + * @define {number} Animation duration. */ -ol.interaction.ZOOM_DURATION = 200; +ol.interaction.MOUSEWHEELZOOM_ANIMATION_DURATION = 250; + + +/** + * @define {number} Maximum delta. + */ +ol.interaction.MOUSEWHEELZOOM_MAXDELTA = 1; + + +/** + * @define {number} Timeout duration. + */ +ol.interaction.MOUSEWHEELZOOM_TIMEOUT_DURATION = 80; /** * @constructor * @extends {ol.interaction.Interaction} - * @param {number} delta The zoom delta applied on each mousewheel. */ -ol.interaction.MouseWheelZoom = function(delta) { +ol.interaction.MouseWheelZoom = function() { + + goog.base(this); + /** * @private * @type {number} */ - this.delta_ = delta; + this.delta_ = 0; + + /** + * @private + * @type {?ol.Coordinate} + */ + this.lastAnchor_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.startTime_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.timeoutId_ = undefined; - goog.base(this); }; goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); @@ -37,20 +70,52 @@ goog.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction); */ ol.interaction.MouseWheelZoom.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { + if (mapBrowserEvent.type == goog.events.MouseWheelHandler.EventType.MOUSEWHEEL) { var map = mapBrowserEvent.map; var mouseWheelEvent = /** @type {goog.events.MouseWheelEvent} */ (mapBrowserEvent.browserEvent); goog.asserts.assert(mouseWheelEvent instanceof goog.events.MouseWheelEvent); - var anchor = mapBrowserEvent.getCoordinate(); - var delta = mouseWheelEvent.deltaY < 0 ? this.delta_ : -this.delta_; - // FIXME works for View2D only - var view = map.getView(); - goog.asserts.assert(view instanceof ol.View2D); - map.requestRenderFrame(); - view.zoom(map, delta, anchor, ol.interaction.ZOOM_DURATION); + + this.lastAnchor_ = mapBrowserEvent.getCoordinate(); + this.delta_ += mouseWheelEvent.deltaY / 3; + + if (!goog.isDef(this.startTime_)) { + this.startTime_ = goog.now(); + } + + var duration = ol.interaction.MOUSEWHEELZOOM_TIMEOUT_DURATION; + var timeLeft = Math.max(duration - (goog.now() - this.startTime_), 0); + + goog.global.clearTimeout(this.timeoutId_); + this.timeoutId_ = goog.global.setTimeout( + goog.bind(this.doZoom_, this, map), timeLeft); + mapBrowserEvent.preventDefault(); mouseWheelEvent.preventDefault(); } }; + + +/** + * @private + * @param {ol.Map} map Map. + */ +ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) { + var maxDelta = ol.interaction.MOUSEWHEELZOOM_MAXDELTA; + var delta = goog.math.clamp(this.delta_, -maxDelta, maxDelta); + + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + + map.requestRenderFrame(); + view.zoom(map, -delta, this.lastAnchor_, + ol.interaction.MOUSEWHEELZOOM_ANIMATION_DURATION); + + this.delta_ = 0; + this.lastAnchor_ = null; + this.startTime_ = undefined; + this.timeoutId_ = undefined; +}; diff --git a/src/ol/map.js b/src/ol/map.js index 771122e531..0650f66a76 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -1014,10 +1014,7 @@ ol.Map.createInteractions_ = function(mapOptions) { var mouseWheelZoom = goog.isDef(mapOptions.mouseWheelZoom) ? mapOptions.mouseWheelZoom : true; if (mouseWheelZoom) { - var mouseWheelZoomDelta = - goog.isDef(mapOptions.mouseWheelZoomDelta) ? - mapOptions.mouseWheelZoomDelta : 1; - interactions.push(new ol.interaction.MouseWheelZoom(mouseWheelZoomDelta)); + interactions.push(new ol.interaction.MouseWheelZoom()); } var shiftDragZoom = goog.isDef(mapOptions.shiftDragZoom) ? diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index c5e0ff2b30..38e5758437 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -85,28 +85,11 @@ describe('ol.Map', function() { }); describe('create mousewheel interaction', function() { - - beforeEach(function() { + it('creates mousewheel interaction', function() { options.mouseWheelZoom = true; - }); - - describe('default mouseWheelZoomDelta', function() { - it('create mousewheel interaction with default delta', function() { - var interactions = ol.Map.createInteractions_(options); - expect(interactions.getLength()).toEqual(1); - expect(interactions.getAt(0)).toBeA(ol.interaction.MouseWheelZoom); - expect(interactions.getAt(0).delta_).toEqual(1); - }); - }); - - describe('set mouseWheelZoomDelta', function() { - it('create mousewheel interaction with set delta', function() { - options.mouseWheelZoomDelta = 7; - var interactions = ol.Map.createInteractions_(options); - expect(interactions.getLength()).toEqual(1); - expect(interactions.getAt(0)).toBeA(ol.interaction.MouseWheelZoom); - expect(interactions.getAt(0).delta_).toEqual(7); - }); + var interactions = ol.Map.createInteractions_(options); + expect(interactions.getLength()).toEqual(1); + expect(interactions.getAt(0)).toBeA(ol.interaction.MouseWheelZoom); }); }); @@ -125,7 +108,7 @@ describe('ol.Map', function() { }); }); - describe('set mouseWheelZoomDelta', function() { + describe('set zoomDelta', function() { it('create double click interaction with set delta', function() { options.zoomDelta = 7; var interactions = ol.Map.createInteractions_(options); From 03ae41a68c466445f5a7ed7875a5f51f3f0806fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 17:59:43 +0100 Subject: [PATCH 05/28] Change View2D function names Also make the TouchZoom interaction no longer use a private View2D function. --- src/ol/control/zoomcontrol.js | 6 ++- src/ol/interaction/dblclickzoominteraction.js | 2 +- .../dragrotateandzoominteraction.js | 2 +- src/ol/interaction/keyboardzoominteraction.js | 3 +- .../interaction/mousewheelzoominteraction.js | 2 +- src/ol/interaction/touchzoominteraction.js | 4 +- src/ol/view2d.js | 42 +++++++++---------- 7 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/ol/control/zoomcontrol.js b/src/ol/control/zoomcontrol.js index ffacacf55f..63e3345cbf 100644 --- a/src/ol/control/zoomcontrol.js +++ b/src/ol/control/zoomcontrol.js @@ -70,7 +70,8 @@ ol.control.Zoom.prototype.handleIn_ = function(browserEvent) { var map = this.getMap(); map.requestRenderFrame(); // FIXME works for View2D only - map.getView().zoom(map, this.delta_, undefined, ol.control.ZOOM_DURATION); + map.getView().zoomByDelta(map, this.delta_, undefined, + ol.control.ZOOM_DURATION); }; @@ -84,5 +85,6 @@ ol.control.Zoom.prototype.handleOut_ = function(browserEvent) { var map = this.getMap(); map.requestRenderFrame(); // FIXME works for View2D only - map.getView().zoom(map, -this.delta_, undefined, ol.control.ZOOM_DURATION); + map.getView().zoomByDelta(map, -this.delta_, undefined, + ol.control.ZOOM_DURATION); }; diff --git a/src/ol/interaction/dblclickzoominteraction.js b/src/ol/interaction/dblclickzoominteraction.js index b5e3e1e589..c8f63a37c7 100644 --- a/src/ol/interaction/dblclickzoominteraction.js +++ b/src/ol/interaction/dblclickzoominteraction.js @@ -41,7 +41,7 @@ ol.interaction.DblClickZoom.prototype.handleMapBrowserEvent = // FIXME works for View2D only var view = map.getView(); goog.asserts.assert(view instanceof ol.View2D); - view.zoom(map, delta, anchor); + view.zoomByDelta(map, delta, anchor); mapBrowserEvent.preventDefault(); browserEvent.preventDefault(); } diff --git a/src/ol/interaction/dragrotateandzoominteraction.js b/src/ol/interaction/dragrotateandzoominteraction.js index 7cf2f7ffa1..cd8425b37a 100644 --- a/src/ol/interaction/dragrotateandzoominteraction.js +++ b/src/ol/interaction/dragrotateandzoominteraction.js @@ -66,7 +66,7 @@ ol.interaction.DragRotateAndZoom.prototype.handleDrag = this.lastAngle_ = theta; if (goog.isDef(this.lastMagnitude_)) { var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude); - view.zoomToResolution(map, resolution); + view.zoom(map, resolution); } this.lastMagnitude_ = magnitude; }; diff --git a/src/ol/interaction/keyboardzoominteraction.js b/src/ol/interaction/keyboardzoominteraction.js index 563827ab45..cbfe06c51e 100644 --- a/src/ol/interaction/keyboardzoominteraction.js +++ b/src/ol/interaction/keyboardzoominteraction.js @@ -40,7 +40,8 @@ ol.interaction.KeyboardZoom.prototype.handleMapBrowserEvent = // FIXME works for View2D only var view = map.getView(); goog.asserts.assert(view instanceof ol.View2D); - view.zoom(map, delta, undefined, ol.interaction.KEYBOARD_ZOOM_DURATION); + view.zoomByDelta(map, delta, undefined, + ol.interaction.KEYBOARD_ZOOM_DURATION); keyEvent.preventDefault(); mapBrowserEvent.preventDefault(); } diff --git a/src/ol/interaction/mousewheelzoominteraction.js b/src/ol/interaction/mousewheelzoominteraction.js index b274dd7e35..125dba9713 100644 --- a/src/ol/interaction/mousewheelzoominteraction.js +++ b/src/ol/interaction/mousewheelzoominteraction.js @@ -111,7 +111,7 @@ ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) { goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - view.zoom(map, -delta, this.lastAnchor_, + view.zoomByDelta(map, -delta, this.lastAnchor_, ol.interaction.MOUSEWHEELZOOM_ANIMATION_DURATION); this.delta_ = 0; diff --git a/src/ol/interaction/touchzoominteraction.js b/src/ol/interaction/touchzoominteraction.js index 9087c5f3b4..ed14b714cf 100644 --- a/src/ol/interaction/touchzoominteraction.js +++ b/src/ol/interaction/touchzoominteraction.js @@ -59,7 +59,7 @@ ol.interaction.TouchZoom.prototype.handleTouchMove = var anchor = map.getCoordinateFromPixel(centroid); // scale, bypass the resolution constraint - view.zoom_(map, view.getResolution() * scaleDelta, anchor); + view.zoomNoConstraint(map, view.getResolution() * scaleDelta, anchor); }; @@ -73,7 +73,7 @@ ol.interaction.TouchZoom.prototype.handleTouchEnd = var map = mapBrowserEvent.map; var view = map.getView(); // take the resolution constraint into account - view.zoomToResolution(map, view.getResolution()); + view.zoom(map, view.getResolution()); view.setHint(ol.ViewHint.INTERACTING, -1); return false; } else { diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 2fcc1f9016..0f3d3474c9 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -282,26 +282,13 @@ ol.View2D.prototype.rotate = function(map, rotation, opt_anchor) { /** - * @private * @param {ol.Map} map Map. * @param {number|undefined} resolution Resolution to go to. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. */ -ol.View2D.prototype.zoom_ = function(map, resolution, opt_anchor) { - if (goog.isDefAndNotNull(resolution) && goog.isDefAndNotNull(opt_anchor)) { - var anchor = opt_anchor; - var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); - var oldResolution = this.getResolution(); - var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; - var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; - var center = new ol.Coordinate(x, y); - map.withFrozenRendering(function() { - this.setCenter(center); - this.setResolution(resolution); - }, this); - } else { - this.setResolution(resolution); - } +ol.View2D.prototype.zoom = function(map, resolution, opt_anchor) { + resolution = this.constraints_.resolution(resolution, 0); + this.zoomNoConstraint(map, resolution, opt_anchor); }; @@ -311,7 +298,8 @@ ol.View2D.prototype.zoom_ = function(map, resolution, opt_anchor) { * @param {ol.Coordinate=} opt_anchor Anchor coordinate. * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.zoom = function(map, delta, opt_anchor, opt_duration) { +ol.View2D.prototype.zoomByDelta = + function(map, delta, opt_anchor, opt_duration) { var currentResolution = this.getResolution(); var currentCenter = this.getCenter(); if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && @@ -331,7 +319,7 @@ ol.View2D.prototype.zoom = function(map, delta, opt_anchor, opt_duration) { } } var resolution = this.constraints_.resolution(currentResolution, delta); - this.zoom_(map, resolution, opt_anchor); + this.zoomNoConstraint(map, resolution, opt_anchor); }; @@ -340,9 +328,21 @@ ol.View2D.prototype.zoom = function(map, delta, opt_anchor, opt_duration) { * @param {number|undefined} resolution Resolution to go to. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. */ -ol.View2D.prototype.zoomToResolution = function(map, resolution, opt_anchor) { - resolution = this.constraints_.resolution(resolution, 0); - this.zoom_(map, resolution, opt_anchor); +ol.View2D.prototype.zoomNoConstraint = function(map, resolution, opt_anchor) { + if (goog.isDefAndNotNull(resolution) && goog.isDefAndNotNull(opt_anchor)) { + var anchor = opt_anchor; + var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); + var oldResolution = this.getResolution(); + var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; + var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; + var center = new ol.Coordinate(x, y); + map.withFrozenRendering(function() { + this.setCenter(center); + this.setResolution(resolution); + }, this); + } else { + this.setResolution(resolution); + } }; From 178c68868b350b63307557518ae17318eda56d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 18:07:45 +0100 Subject: [PATCH 06/28] Add a rotateNoConstraint View2D method --- src/ol/view2d.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 0f3d3474c9..8772f363ca 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -262,6 +262,16 @@ goog.exportProperty( */ ol.View2D.prototype.rotate = function(map, rotation, opt_anchor) { rotation = this.constraints_.rotation(rotation, 0); + this.rotateNoConstraint(map, rotation, opt_anchor); +}; + + +/** + * @param {ol.Map} map Map. + * @param {number|undefined} rotation Rotation. + * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + */ +ol.View2D.prototype.rotateNoConstraint = function(map, rotation, opt_anchor) { if (goog.isDefAndNotNull(opt_anchor)) { var anchor = opt_anchor; var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); From 7d6d82519bb327f1e74a39d4995ce28ac6928fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 18:07:56 +0100 Subject: [PATCH 07/28] Make TouchRotate interaction use rotateNoConstraint --- src/ol/interaction/touchrotateinteraction.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ol/interaction/touchrotateinteraction.js b/src/ol/interaction/touchrotateinteraction.js index 1ce5d02a77..be084f7db0 100644 --- a/src/ol/interaction/touchrotateinteraction.js +++ b/src/ol/interaction/touchrotateinteraction.js @@ -90,7 +90,7 @@ ol.interaction.TouchRotate.prototype.handleTouchMove = // rotate if (this.rotating_) { - view.rotate(map, view.getRotation() + rotationDelta, anchor); + view.rotateNoConstraint(map, view.getRotation() + rotationDelta, anchor); } }; @@ -103,6 +103,9 @@ ol.interaction.TouchRotate.prototype.handleTouchEnd = if (this.targetTouches.length < 2) { var map = mapBrowserEvent.map; var view = map.getView(); + if (this.rotating_) { + view.rotate(map, view.getRotation()); + } view.setHint(ol.ViewHint.INTERACTING, -1); return false; } else { From 6d6188ea791b7d6033f9e0c2aeae9cccf004199d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 5 Mar 2013 00:23:09 +0100 Subject: [PATCH 08/28] Correct Stamen source --- src/objectliterals.exports | 7 +- src/ol/source/stamen.exports | 20 ------ src/ol/source/stamensource.js | 132 ++++++++++++++++++++-------------- 3 files changed, 82 insertions(+), 77 deletions(-) diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 3f0a1e984d..8483e655bf 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -114,8 +114,11 @@ @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.version string|undefined @exportObjectLiteral ol.source.StamenOptions -@exportObjectLiteralProperty ol.source.StamenOptions.flavor string|undefined -@exportObjectLiteralProperty ol.source.StamenOptions.provider string +@exportObjectLiteralProperty ol.source.StamenOptions.layer string +@exportObjectLiteralProperty ol.source.StamenOptions.minZoom number|undefined +@exportObjectLiteralProperty ol.source.StamenOptions.maxZoom number|undefined +@exportObjectLiteralProperty ol.source.StamenOptions.opaque boolean|undefined +@exportObjectLiteralProperty ol.source.StamenOptions.url string|undefined @exportObjectLiteral ol.source.StaticImageOptions @exportObjectLiteralProperty ol.source.StaticImageOptions.attributions Array.|undefined diff --git a/src/ol/source/stamen.exports b/src/ol/source/stamen.exports index 40b2c8b0e7..697330485e 100644 --- a/src/ol/source/stamen.exports +++ b/src/ol/source/stamen.exports @@ -1,21 +1 @@ @exportSymbol ol.source.Stamen - -@exportSymbol ol.source.StamenFlavor -@exportProperty ol.source.StamenFlavor.TERRAIN_BACKGROUND -@exportProperty ol.source.StamenFlavor.TERRAIN_LABELS -@exportProperty ol.source.StamenFlavor.TERRAIN_LINES -@exportProperty ol.source.StamenFlavor.TONER_2010 -@exportProperty ol.source.StamenFlavor.TONER_2011 -@exportProperty ol.source.StamenFlavor.TONER_2011_LABELS -@exportProperty ol.source.StamenFlavor.TONER_2011_LINES -@exportProperty ol.source.StamenFlavor.TONER_2011_LITE -@exportProperty ol.source.StamenFlavor.TONER_BACKGROUND -@exportProperty ol.source.StamenFlavor.TONER_HYBRID -@exportProperty ol.source.StamenFlavor.TONER_LABELS -@exportProperty ol.source.StamenFlavor.TONER_LINES -@exportProperty ol.source.StamenFlavor.TONER_LITE - -@exportSymbol ol.source.StamenProvider -@exportProperty ol.source.StamenProvider.TERRAIN -@exportProperty ol.source.StamenProvider.TONER -@exportProperty ol.source.StamenProvider.WATERCOLOR diff --git a/src/ol/source/stamensource.js b/src/ol/source/stamensource.js index 90355bf1e5..6ea87b6ab4 100644 --- a/src/ol/source/stamensource.js +++ b/src/ol/source/stamensource.js @@ -1,61 +1,76 @@ -// FIXME Configure minZoom when supported by TileGrid - goog.provide('ol.source.Stamen'); -goog.provide('ol.source.StamenFlavor'); -goog.provide('ol.source.StamenProvider'); goog.require('ol.Attribution'); goog.require('ol.source.XYZ'); /** - * @enum {string} + * @type {Object.} */ -ol.source.StamenFlavor = { - TERRAIN_BACKGROUND: 'background', - TERRAIN_LABELS: 'labels', - TERRAIN_LINES: 'lines', - TONER_2010: '2010', - TONER_2011: '2011', - TONER_2011_LABELS: '2011-labels', - TONER_2011_LINES: '2011-lines', - TONER_2011_LITE: '2011-lite', - TONER_BACKGROUND: 'background', - TONER_HYBRID: 'hybrid', - TONER_LABELS: 'labels', - TONER_LINES: 'lines', - TONER_LITE: 'lite' +ol.source.StamenLayerConfig = { + 'terrain': { + extension: 'jpg', + opaque: true + }, + 'terrain-background': { + extension: 'jpg', + opaque: true + }, + 'terrain-labels': { + extension: 'png', + opaque: false + }, + 'terrain-lines': { + extension: 'png', + opaque: false + }, + 'toner-background': { + extension: 'png', + opaque: true + }, + 'toner': { + extension: 'png', + opaque: true + }, + 'toner-hybrid': { + extension: 'png', + opaque: false + }, + 'toner-labels': { + extension: 'png', + opaque: false + }, + 'toner-lines': { + extension: 'png', + opaque: false + }, + 'toner-lite': { + extension: 'png', + opaque: true + }, + 'watercolor': { + extension: 'jpg', + opaque: true + } }; /** - * @enum {string} + * @type {Object.} */ -ol.source.StamenProvider = { - TERRAIN: 'terrain', - TONER: 'toner', - WATERCOLOR: 'watercolor' -}; - - -/** - * @type {Object.} - */ -ol.source.StamenProviderConfig = {}; -ol.source.StamenProviderConfig[ol.source.StamenProvider.TERRAIN] = { - type: 'jpg', - minZoom: 4, - maxZoom: 18 -}; -ol.source.StamenProviderConfig[ol.source.StamenProvider.TONER] = { - type: 'png', - minZoom: 0, - maxZoom: 20 -}; -ol.source.StamenProviderConfig[ol.source.StamenProvider.WATERCOLOR] = { - type: 'jpg', - minZoom: 3, - maxZoom: 16 +ol.source.StamenProviderConfig = { + 'terrain': { + minZoom: 4, + maxZoom: 18 + }, + 'toner': { + minZoom: 0, + maxZoom: 20 + }, + 'watercolor': { + minZoom: 3, + maxZoom: 16 + } }; @@ -63,9 +78,9 @@ ol.source.StamenProviderConfig[ol.source.StamenProvider.WATERCOLOR] = { /** * @constructor * @extends {ol.source.XYZ} - * @param {ol.source.StamenOptions} stamenOptions Stamen options. + * @param {ol.source.StamenOptions} options Options. */ -ol.source.Stamen = function(stamenOptions) { +ol.source.Stamen = function(options) { var attribution = new ol.Attribution( 'Map tiles by Stamen Design, ' + @@ -75,18 +90,25 @@ ol.source.Stamen = function(stamenOptions) { 'under ' + 'CC BY SA.'); - var layer = stamenOptions.provider; - if (goog.isDef(stamenOptions.flavor)) { - layer += '-' + stamenOptions.flavor; - } + var i = options.layer.indexOf('-'); + var provider = i == -1 ? options.layer : options.layer.slice(0, i); + goog.asserts.assert(provider in ol.source.StamenProviderConfig); + var providerConfig = ol.source.StamenProviderConfig[provider]; - var config = ol.source.StamenProviderConfig[stamenOptions.provider]; + goog.asserts.assert(options.layer in ol.source.StamenLayerConfig); + var layerConfig = ol.source.StamenLayerConfig[options.layer]; + + var url = goog.isDef(options.url) ? options.url : + 'http://{a-d}.tile.stamen.com/' + options.layer + '/{z}/{x}/{y}.' + + layerConfig.extension; goog.base(this, { attributions: [attribution], - maxZoom: config.maxZoom, - opaque: false, - url: 'http://{a-d}.tile.stamen.com/' + layer + '/{z}/{x}/{y}.' + config.type + maxZoom: providerConfig.maxZoom, + // FIXME uncomment the following when tilegrid supports minZoom + //minZoom: providerConfig.minZoom, + opaque: layerConfig.opaque, + url: url }); }; From 6f2d15bb5f93da761a4d2379789fad5a9621133a Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 5 Mar 2013 00:41:46 +0100 Subject: [PATCH 09/28] Add Stamen example --- examples/stamen.html | 42 ++++++++++++++++++++++++++++++++++++++++++ examples/stamen.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 examples/stamen.html create mode 100644 examples/stamen.js diff --git a/examples/stamen.html b/examples/stamen.html new file mode 100644 index 0000000000..0b0f9ef71d --- /dev/null +++ b/examples/stamen.html @@ -0,0 +1,42 @@ + + + + + + + + + Stamen example + + +
+
+

Stamen example

+
Example of a Stamen tile source.
+
+

See the + stamen.js source + to see how this is done.

+
+
+
+
fullscreen, stamen, tilelayer
+ + + diff --git a/examples/stamen.js b/examples/stamen.js new file mode 100644 index 0000000000..3c947baf84 --- /dev/null +++ b/examples/stamen.js @@ -0,0 +1,31 @@ +goog.require('ol.Collection'); +goog.require('ol.Coordinate'); +goog.require('ol.Map'); +goog.require('ol.RendererHints'); +goog.require('ol.View2D'); +goog.require('ol.layer.TileLayer'); +goog.require('ol.source.Stamen'); + + +var layers = new ol.Collection([ + new ol.layer.TileLayer({ + source: new ol.source.Stamen({ + layer: 'watercolor' + }) + }), + new ol.layer.TileLayer({ + source: new ol.source.Stamen({ + layer: 'terrain-labels' + }) + }) +]); +var map = new ol.Map({ + layers: layers, + renderers: ol.RendererHints.createFromQueryData(), + scaleLineControl: true, + target: 'map', + view: new ol.View2D({ + center: new ol.Coordinate(0, 0), + zoom: 3 + }) +}); From 1776d80cb961956b6b2203b7b2103bb4a7e10abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 19:21:41 +0100 Subject: [PATCH 10/28] Add a snapToZero rotation constraint --- src/ol/rotationconstraint.js | 20 ++++++++++++++ src/ol/view2d.js | 2 +- test/spec/ol/rotationconstraint.test.js | 36 +++++++++++++++++++++++++ test/spec/ol/view2d.test.js | 10 +++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/spec/ol/rotationconstraint.test.js diff --git a/src/ol/rotationconstraint.js b/src/ol/rotationconstraint.js index 72500f9ec7..c297e13608 100644 --- a/src/ol/rotationconstraint.js +++ b/src/ol/rotationconstraint.js @@ -37,3 +37,23 @@ ol.RotationConstraint.createSnapToN = function(n) { } }; }; + + +/** + * @param {number=} opt_tolerance Tolerance. + * @return {ol.RotationConstraintType} Rotation constraint. + */ +ol.RotationConstraint.createSnapToZero = function(opt_tolerance) { + var tolerance = opt_tolerance || 0.1; + return function(rotation, delta) { + if (goog.isDef(rotation)) { + if (Math.abs(rotation + delta) <= tolerance) { + return 0; + } else { + return rotation + delta; + } + } else { + return undefined; + } + }; +}; diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 8772f363ca..77062aad97 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -387,6 +387,6 @@ ol.View2D.createConstraints_ = function(view2DOptions) { zoomFactor, maxResolution, numZoomLevels - 1); } // FIXME rotation constraint is not configurable at the moment - var rotationConstraint = ol.RotationConstraint.none; + var rotationConstraint = ol.RotationConstraint.createSnapToZero(); return new ol.Constraints(resolutionConstraint, rotationConstraint); }; diff --git a/test/spec/ol/rotationconstraint.test.js b/test/spec/ol/rotationconstraint.test.js new file mode 100644 index 0000000000..950a8892e4 --- /dev/null +++ b/test/spec/ol/rotationconstraint.test.js @@ -0,0 +1,36 @@ +goog.provide('ol.test.RotationConstraint'); + +describe('ol.RotationConstraint', function() { + + describe('SnapToZero', function() { + + it('returns expected rotation value', function() { + var rotationConstraint = ol.RotationConstraint.createSnapToZero(0.3); + + expect(rotationConstraint(0.1, 0)).toEqual(0); + expect(rotationConstraint(0.2, 0)).toEqual(0); + expect(rotationConstraint(0.3, 0)).toEqual(0); + expect(rotationConstraint(0.4, 0)).toEqual(0.4); + + expect(rotationConstraint(-0.1, 0)).toEqual(0); + expect(rotationConstraint(-0.2, 0)).toEqual(0); + expect(rotationConstraint(-0.3, 0)).toEqual(0); + expect(rotationConstraint(-0.4, 0)).toEqual(-0.4); + + expect(rotationConstraint(1, -0.9)).toEqual(0); + expect(rotationConstraint(1, -0.8)).toEqual(0); + // floating-point arithmetic + expect(rotationConstraint(1, -0.7)).not.toEqual(0); + expect(rotationConstraint(1, -0.6)).toEqual(0.4); + + expect(rotationConstraint(-1, 0.9)).toEqual(0); + expect(rotationConstraint(-1, 0.8)).toEqual(0); + // floating-point arithmetic + expect(rotationConstraint(-1, 0.7)).not.toEqual(0); + expect(rotationConstraint(-1, 0.6)).toEqual(-0.4); + }); + + }); +}); + +goog.require('ol.RotationConstraint'); diff --git a/test/spec/ol/view2d.test.js b/test/spec/ol/view2d.test.js index d78e17dad4..efed46163b 100644 --- a/test/spec/ol/view2d.test.js +++ b/test/spec/ol/view2d.test.js @@ -49,6 +49,16 @@ describe('ol.View2D', function() { }); }); + + describe('create rotation constraint', function() { + it('gives a correct rotation constraint function', function() { + var options = {}; + var fn = ol.View2D.createConstraints_(options).rotation; + expect(fn(0.01, 0)).toEqual(0); + expect(fn(0.15, 0)).toEqual(0.15); + }); + }); + }); }); From 2f611529dd04928f7faf256177174b7a88128271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 20:04:51 +0100 Subject: [PATCH 11/28] Do not apply rotation constraint while rotating This is on par to what the TouchRotate interaction does. --- src/ol/interaction/dragrotateinteraction.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ol/interaction/dragrotateinteraction.js b/src/ol/interaction/dragrotateinteraction.js index 276fb15c56..90b92c0be5 100644 --- a/src/ol/interaction/dragrotateinteraction.js +++ b/src/ol/interaction/dragrotateinteraction.js @@ -1,6 +1,7 @@ goog.provide('ol.interaction.DragRotate'); goog.require('ol.View2D'); +goog.require('ol.ViewHint'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); @@ -46,12 +47,26 @@ ol.interaction.DragRotate.prototype.handleDrag = function(mapBrowserEvent) { // FIXME supports View2D only goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - view.rotate(map, view.getRotation() - delta); + view.rotateNoConstraint(map, view.getRotation() - delta); } this.lastAngle_ = theta; }; +/** + * @inheritDoc + */ +ol.interaction.DragRotate.prototype.handleDragEnd = function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent; + var map = mapBrowserEvent.map; + // FIXME supports View2D only + var view = map.getView(); + goog.asserts.assert(view instanceof ol.View2D); + view.rotate(map, view.getRotation()); + view.setHint(ol.ViewHint.INTERACTING, -1); +}; + + /** * @inheritDoc */ @@ -65,6 +80,7 @@ ol.interaction.DragRotate.prototype.handleDragStart = goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); this.lastAngle_ = undefined; + view.setHint(ol.ViewHint.INTERACTING, 1); return true; } else { return false; From c431acacca0dbd01e413730d82548dfcf68cfdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 20:29:18 +0100 Subject: [PATCH 12/28] Make animating rotation possible --- src/ol/view2d.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 77062aad97..4662678d3b 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -259,8 +259,28 @@ goog.exportProperty( * @param {ol.Map} map Map. * @param {number|undefined} rotation Rotation. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.rotate = function(map, rotation, opt_anchor) { +ol.View2D.prototype.rotate = + function(map, rotation, opt_anchor, opt_duration) { + var currentRotation = this.getRotation(); + var currentCenter = this.getCenter(); + if (goog.isDef(currentRotation) && goog.isDef(currentCenter) && + goog.isDef(opt_duration)) { + map.requestRenderFrame(); + map.addPreRenderFunction(ol.animation.rotate({ + rotation: currentRotation, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + if (goog.isDef(opt_anchor)) { + map.addPreRenderFunction(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + } + } rotation = this.constraints_.rotation(rotation, 0); this.rotateNoConstraint(map, rotation, opt_anchor); }; From d42d88c198d24f5569a6ea67f2d35c587b1416c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 20:30:15 +0100 Subject: [PATCH 13/28] Animate rotation when releasing mouse or fingers --- src/ol/interaction/dragrotateinteraction.js | 9 ++++++++- src/ol/interaction/touchrotateinteraction.js | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ol/interaction/dragrotateinteraction.js b/src/ol/interaction/dragrotateinteraction.js index 90b92c0be5..d4036be9c1 100644 --- a/src/ol/interaction/dragrotateinteraction.js +++ b/src/ol/interaction/dragrotateinteraction.js @@ -6,6 +6,12 @@ goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); +/** + * @define {number} Animation duration. + */ +ol.interaction.DRAGROTATE_ANIMATION_DURATION = 250; + + /** * @constructor @@ -62,7 +68,8 @@ ol.interaction.DragRotate.prototype.handleDragEnd = function(mapBrowserEvent) { // FIXME supports View2D only var view = map.getView(); goog.asserts.assert(view instanceof ol.View2D); - view.rotate(map, view.getRotation()); + view.rotate(map, view.getRotation(), undefined, + ol.interaction.DRAGROTATE_ANIMATION_DURATION); view.setHint(ol.ViewHint.INTERACTING, -1); }; diff --git a/src/ol/interaction/touchrotateinteraction.js b/src/ol/interaction/touchrotateinteraction.js index be084f7db0..a3a6e14dfe 100644 --- a/src/ol/interaction/touchrotateinteraction.js +++ b/src/ol/interaction/touchrotateinteraction.js @@ -8,6 +8,14 @@ goog.require('ol.ViewHint'); goog.require('ol.interaction.Touch'); +/** + * @define {number} Animation duration. + */ +ol.interaction.TOUCHROTATE_ANIMATION_DURATION = 250; + + + +/** /** * @constructor @@ -104,7 +112,8 @@ ol.interaction.TouchRotate.prototype.handleTouchEnd = var map = mapBrowserEvent.map; var view = map.getView(); if (this.rotating_) { - view.rotate(map, view.getRotation()); + view.rotate(map, view.getRotation(), undefined, + ol.interaction.TOUCHROTATE_ANIMATION_DURATION); } view.setHint(ol.ViewHint.INTERACTING, -1); return false; From 43b1e11e7af96231b61bbdac17118f0b1ed27d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 20:51:45 +0100 Subject: [PATCH 14/28] 2DView refactoring This refactoring makes animating zoom, as opposed to zoomByDelta, possible. --- src/ol/view2d.js | 145 +++++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/src/ol/view2d.js b/src/ol/view2d.js index 4662678d3b..f022a1bf25 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -263,26 +263,8 @@ goog.exportProperty( */ ol.View2D.prototype.rotate = function(map, rotation, opt_anchor, opt_duration) { - var currentRotation = this.getRotation(); - var currentCenter = this.getCenter(); - if (goog.isDef(currentRotation) && goog.isDef(currentCenter) && - goog.isDef(opt_duration)) { - map.requestRenderFrame(); - map.addPreRenderFunction(ol.animation.rotate({ - rotation: currentRotation, - duration: opt_duration, - easing: goog.fx.easing.easeOut - })); - if (goog.isDef(opt_anchor)) { - map.addPreRenderFunction(ol.animation.pan({ - source: currentCenter, - duration: opt_duration, - easing: goog.fx.easing.easeOut - })); - } - } rotation = this.constraints_.rotation(rotation, 0); - this.rotateNoConstraint(map, rotation, opt_anchor); + this.rotateNoConstraint(map, rotation, opt_anchor, opt_duration); }; @@ -290,23 +272,45 @@ ol.View2D.prototype.rotate = * @param {ol.Map} map Map. * @param {number|undefined} rotation Rotation. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.rotateNoConstraint = function(map, rotation, opt_anchor) { - if (goog.isDefAndNotNull(opt_anchor)) { - var anchor = opt_anchor; - var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); - var center = new ol.Coordinate( - oldCenter.x - anchor.x, - oldCenter.y - anchor.y); - center.rotate(rotation - this.getRotation()); - center.x += anchor.x; - center.y += anchor.y; - map.withFrozenRendering(function() { - this.setCenter(center); +ol.View2D.prototype.rotateNoConstraint = + function(map, rotation, opt_anchor, opt_duration) { + if (goog.isDefAndNotNull(rotation)) { + var currentRotation = this.getRotation(); + var currentCenter = this.getCenter(); + if (goog.isDef(currentRotation) && goog.isDef(currentCenter) && + goog.isDef(opt_duration)) { + map.requestRenderFrame(); + map.addPreRenderFunction(ol.animation.rotate({ + rotation: currentRotation, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + if (goog.isDef(opt_anchor)) { + map.addPreRenderFunction(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + } + } + if (goog.isDefAndNotNull(opt_anchor)) { + var anchor = opt_anchor; + var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); + var center = new ol.Coordinate( + oldCenter.x - anchor.x, + oldCenter.y - anchor.y); + center.rotate(rotation - this.getRotation()); + center.x += anchor.x; + center.y += anchor.y; + map.withFrozenRendering(function() { + this.setCenter(center); + this.setRotation(rotation); + }, this); + } else { this.setRotation(rotation); - }, this); - } else { - this.setRotation(rotation); + } } }; @@ -315,10 +319,12 @@ ol.View2D.prototype.rotateNoConstraint = function(map, rotation, opt_anchor) { * @param {ol.Map} map Map. * @param {number|undefined} resolution Resolution to go to. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.zoom = function(map, resolution, opt_anchor) { +ol.View2D.prototype.zoom = + function(map, resolution, opt_anchor, opt_duration) { resolution = this.constraints_.resolution(resolution, 0); - this.zoomNoConstraint(map, resolution, opt_anchor); + this.zoomNoConstraint(map, resolution, opt_anchor, opt_duration); }; @@ -331,25 +337,8 @@ ol.View2D.prototype.zoom = function(map, resolution, opt_anchor) { ol.View2D.prototype.zoomByDelta = function(map, delta, opt_anchor, opt_duration) { var currentResolution = this.getResolution(); - var currentCenter = this.getCenter(); - if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && - goog.isDef(opt_duration)) { - map.requestRenderFrame(); - map.addPreRenderFunction(ol.animation.zoom({ - resolution: currentResolution, - duration: opt_duration, - easing: goog.fx.easing.easeOut - })); - if (goog.isDef(opt_anchor)) { - map.addPreRenderFunction(ol.animation.pan({ - source: currentCenter, - duration: opt_duration, - easing: goog.fx.easing.easeOut - })); - } - } var resolution = this.constraints_.resolution(currentResolution, delta); - this.zoomNoConstraint(map, resolution, opt_anchor); + this.zoomNoConstraint(map, resolution, opt_anchor, opt_duration); }; @@ -357,21 +346,43 @@ ol.View2D.prototype.zoomByDelta = * @param {ol.Map} map Map. * @param {number|undefined} resolution Resolution to go to. * @param {ol.Coordinate=} opt_anchor Anchor coordinate. + * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.zoomNoConstraint = function(map, resolution, opt_anchor) { - if (goog.isDefAndNotNull(resolution) && goog.isDefAndNotNull(opt_anchor)) { - var anchor = opt_anchor; - var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); - var oldResolution = this.getResolution(); - var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; - var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; - var center = new ol.Coordinate(x, y); - map.withFrozenRendering(function() { - this.setCenter(center); +ol.View2D.prototype.zoomNoConstraint = + function(map, resolution, opt_anchor, opt_duration) { + if (goog.isDefAndNotNull(resolution)) { + var currentResolution = this.getResolution(); + var currentCenter = this.getCenter(); + if (goog.isDef(currentResolution) && goog.isDef(currentCenter) && + goog.isDef(opt_duration)) { + map.requestRenderFrame(); + map.addPreRenderFunction(ol.animation.zoom({ + resolution: currentResolution, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + if (goog.isDef(opt_anchor)) { + map.addPreRenderFunction(ol.animation.pan({ + source: currentCenter, + duration: opt_duration, + easing: goog.fx.easing.easeOut + })); + } + } + if (goog.isDefAndNotNull(opt_anchor)) { + var anchor = opt_anchor; + var oldCenter = /** @type {!ol.Coordinate} */ (this.getCenter()); + var oldResolution = this.getResolution(); + var x = anchor.x - resolution * (anchor.x - oldCenter.x) / oldResolution; + var y = anchor.y - resolution * (anchor.y - oldCenter.y) / oldResolution; + var center = new ol.Coordinate(x, y); + map.withFrozenRendering(function() { + this.setCenter(center); + this.setResolution(resolution); + }, this); + } else { this.setResolution(resolution); - }, this); - } else { - this.setResolution(resolution); + } } }; From e598e4b9b246fed403424548b0d07da8d3b899f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 4 Mar 2013 20:59:33 +0100 Subject: [PATCH 15/28] Animate zoom after pinch --- src/ol/interaction/touchzoominteraction.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ol/interaction/touchzoominteraction.js b/src/ol/interaction/touchzoominteraction.js index ed14b714cf..76d99bafd7 100644 --- a/src/ol/interaction/touchzoominteraction.js +++ b/src/ol/interaction/touchzoominteraction.js @@ -8,6 +8,12 @@ goog.require('ol.ViewHint'); goog.require('ol.interaction.Touch'); +/** + * @define {number} Animation duration. + */ +ol.interaction.TOUCHZOOM_ANIMATION_DURATION = 250; + + /** * @constructor @@ -73,7 +79,8 @@ ol.interaction.TouchZoom.prototype.handleTouchEnd = var map = mapBrowserEvent.map; var view = map.getView(); // take the resolution constraint into account - view.zoom(map, view.getResolution()); + view.zoom(map, view.getResolution(), undefined, + ol.interaction.TOUCHZOOM_ANIMATION_DURATION); view.setHint(ol.ViewHint.INTERACTING, -1); return false; } else { From 29e610b9f3b57455fa4f44eb83a71c791c878ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 5 Mar 2013 10:07:33 +0100 Subject: [PATCH 16/28] Animate double click/tap zoom --- src/ol/interaction/dblclickzoominteraction.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ol/interaction/dblclickzoominteraction.js b/src/ol/interaction/dblclickzoominteraction.js index c8f63a37c7..e758e67d44 100644 --- a/src/ol/interaction/dblclickzoominteraction.js +++ b/src/ol/interaction/dblclickzoominteraction.js @@ -8,6 +8,12 @@ goog.require('ol.View2D'); goog.require('ol.interaction.Interaction'); +/** + * @define {number} Animation duration. + */ +ol.interaction.DBLCLICKZOOM_ANIMATION_DURATION = 250; + + /** * @constructor @@ -41,7 +47,8 @@ ol.interaction.DblClickZoom.prototype.handleMapBrowserEvent = // FIXME works for View2D only var view = map.getView(); goog.asserts.assert(view instanceof ol.View2D); - view.zoomByDelta(map, delta, anchor); + view.zoomByDelta(map, delta, anchor, + ol.interaction.DBLCLICKZOOM_ANIMATION_DURATION); mapBrowserEvent.preventDefault(); browserEvent.preventDefault(); } From 2676050d54e218bef1eb7b486492e4921db27bc7 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 5 Mar 2013 10:01:27 +0100 Subject: [PATCH 17/28] Do not use tiledWMSOptions in closure function See #269. --- src/ol/source/tiledwmssource.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ol/source/tiledwmssource.js b/src/ol/source/tiledwmssource.js index ebc8e7ab8c..935fac2554 100644 --- a/src/ol/source/tiledwmssource.js +++ b/src/ol/source/tiledwmssource.js @@ -49,8 +49,7 @@ ol.source.TiledWMS = function(tiledWMSOptions) { var x = tileCoord.x; var tileExtent = tileGrid.getTileCoordExtent(tileCoord); var projectionExtent = projection.getExtent(); - var extent = goog.isDef(tiledWMSOptions.extent) ? - tiledWMSOptions.extent : projectionExtent; + extent = goog.isDef(extent) ? extent : projectionExtent; // FIXME do we want a wrapDateLine param? The code below will break maps // with projections that do not span the whole world width. if (extent.minX === projectionExtent.minX && From 4bb521db65cde2c0bbd592a9b8ab2ba73539b769 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 5 Mar 2013 10:54:33 +0100 Subject: [PATCH 18/28] WMS GetMap request parameters shall be params To avoid surprises, we configure everything that is a WMS GetMap request parameter in the params object, and not as direct configuration option. This affects the VERSION and TRANSPARENT params. --- src/objectliterals.exports | 3 --- src/ol/imageurlfunction.js | 5 ++--- src/ol/source/singleimagewmssource.js | 3 +-- src/ol/source/tiledwmssource.js | 9 ++++----- src/ol/source/wms.js | 26 ++++++++++++++------------ src/ol/tileurlfunction.js | 5 ++--- test/spec/ol/tileurlfunction.test.js | 16 ++++++++-------- 7 files changed, 31 insertions(+), 36 deletions(-) diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 3f0a1e984d..ce2ad2c57b 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -111,7 +111,6 @@ @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.resolutions Array.|undefined @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.url string|undefined -@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.version string|undefined @exportObjectLiteral ol.source.StamenOptions @exportObjectLiteralProperty ol.source.StamenOptions.flavor string|undefined @@ -129,11 +128,9 @@ @exportObjectLiteral ol.source.TiledWMSOptions @exportObjectLiteralProperty ol.source.TiledWMSOptions.attributions Array.|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.params Object -@exportObjectLiteralProperty ol.source.TiledWMSOptions.version string|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.crossOrigin null|string|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.extent ol.Extent|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.tileGrid ol.tilegrid.TileGrid|undefined -@exportObjectLiteralProperty ol.source.TiledWMSOptions.transparent boolean|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.maxZoom number|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.url string|undefined diff --git a/src/ol/imageurlfunction.js b/src/ol/imageurlfunction.js index 4af4b0a5d2..5bd6ec96a7 100644 --- a/src/ol/imageurlfunction.js +++ b/src/ol/imageurlfunction.js @@ -15,14 +15,13 @@ ol.ImageUrlFunctionType; /** * @param {string} baseUrl Base URL (may have query data). * @param {Object.} params WMS parameters. - * @param {string=} opt_version WMS version. * @return {ol.ImageUrlFunctionType} Image URL function. */ ol.ImageUrlFunction.createWMSParams = - function(baseUrl, params, opt_version) { + function(baseUrl, params) { return function(extent, size, projection) { return ol.source.wms.getUrl( - baseUrl, params, extent, size, projection, opt_version); + baseUrl, params, extent, size, projection); }; }; diff --git a/src/ol/source/singleimagewmssource.js b/src/ol/source/singleimagewmssource.js index 0a8ea8d66c..211abdbd36 100644 --- a/src/ol/source/singleimagewmssource.js +++ b/src/ol/source/singleimagewmssource.js @@ -15,8 +15,7 @@ goog.require('ol.source.ImageSource'); */ ol.source.SingleImageWMS = function(options) { var imageUrlFunction = goog.isDef(options.url) ? - ol.ImageUrlFunction.createWMSParams( - options.url, options.params, options.version) : + ol.ImageUrlFunction.createWMSParams(options.url, options.params) : ol.ImageUrlFunction.nullImageUrlFunction; goog.base(this, { diff --git a/src/ol/source/tiledwmssource.js b/src/ol/source/tiledwmssource.js index 935fac2554..db1315a93e 100644 --- a/src/ol/source/tiledwmssource.js +++ b/src/ol/source/tiledwmssource.js @@ -21,25 +21,24 @@ ol.source.TiledWMS = function(tiledWMSOptions) { if (goog.isDef(tiledWMSOptions.tileGrid)) { tileGrid = tiledWMSOptions.tileGrid; } - var version = tiledWMSOptions.version; var tileUrlFunction; if (tiledWMSOptions.urls) { var tileUrlFunctions = goog.array.map( tiledWMSOptions.urls, function(url) { return ol.TileUrlFunction.createWMSParams( - url, tiledWMSOptions.params, version); + url, tiledWMSOptions.params); }); tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions( tileUrlFunctions); } else if (tiledWMSOptions.url) { tileUrlFunction = ol.TileUrlFunction.createWMSParams( - tiledWMSOptions.url, tiledWMSOptions.params, version); + tiledWMSOptions.url, tiledWMSOptions.params); } else { tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction; } - var transparent = goog.isDef(tiledWMSOptions.transparent) ? - tiledWMSOptions.transparent : true; + var transparent = goog.isDef(tiledWMSOptions.params['TRANSPARENT']) ? + tiledWMSOptions.params['TRANSPARENT'] : true; var extent = tiledWMSOptions.extent; var tileCoordTransform = function(tileCoord, tileGrid, projection) { diff --git a/src/ol/source/wms.js b/src/ol/source/wms.js index 0fff2f8e25..f076c75c59 100644 --- a/src/ol/source/wms.js +++ b/src/ol/source/wms.js @@ -7,31 +7,33 @@ goog.provide('ol.source.wms'); * @param {ol.Extent} extent Extent. * @param {ol.Size} size Size. * @param {ol.Projection} projection Projection. - * @param {string=} opt_version WMS version. Default is '1.3.0'. * @return {string} WMS GetMap request URL. */ ol.source.wms.getUrl = - function(baseUrl, params, extent, size, projection, opt_version) { - var version = goog.isDef(opt_version) ? opt_version : '1.3.0'; - var wms13 = version >= '1.3'; - var axisOrientation = projection.getAxisOrientation(); - var bboxValues = (wms13 && axisOrientation.substr(0, 2) == 'ne') ? - [extent.minY, extent.minX, extent.maxY, extent.maxX] : - [extent.minX, extent.minY, extent.maxX, extent.maxY]; + function(baseUrl, params, extent, size, projection) { var baseParams = { 'SERVICE': 'WMS', - 'VERSION': version, + 'VERSION': '1.3.0', 'REQUEST': 'GetMap', 'FORMAT': 'image/png', 'TRANSPARENT': true, 'WIDTH': size.width, - 'HEIGHT': size.height, - 'BBOX': bboxValues.join(',') + 'HEIGHT': size.height }; goog.object.extend(baseParams, params); - baseParams[wms13 ? 'CRS' : 'SRS'] = projection.getCode(); + //TODO: Provide our own appendParams function to avoid this empty string hack var stylesParam = 'STYLES'; baseParams[stylesParam] = params[stylesParam] || new String(''); + + var wms13 = baseParams['VERSION'] > '1.3'; + baseParams[wms13 ? 'CRS' : 'SRS'] = projection.getCode(); + + var axisOrientation = projection.getAxisOrientation(); + var bboxValues = (wms13 && axisOrientation.substr(0, 2) == 'ne') ? + [extent.minY, extent.minX, extent.maxY, extent.maxX] : + [extent.minX, extent.minY, extent.maxX, extent.maxY]; + baseParams['BBOX'] = bboxValues.join(','); + return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams); }; diff --git a/src/ol/tileurlfunction.js b/src/ol/tileurlfunction.js index 4e09acc976..3c270b80c6 100644 --- a/src/ol/tileurlfunction.js +++ b/src/ol/tileurlfunction.js @@ -74,11 +74,10 @@ ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) { /** * @param {string} baseUrl Base URL (may have query data). * @param {Object.} params WMS parameters. - * @param {string=} opt_version WMS version. * @return {ol.TileUrlFunctionType} Tile URL function. */ ol.TileUrlFunction.createWMSParams = - function(baseUrl, params, opt_version) { + function(baseUrl, params) { return function(tileCoord, tileGrid, projection) { if (goog.isNull(tileCoord)) { return undefined; @@ -86,7 +85,7 @@ ol.TileUrlFunction.createWMSParams = var size = tileGrid.getTileSize(tileCoord.z); var extent = tileGrid.getTileCoordExtent(tileCoord); return ol.source.wms.getUrl( - baseUrl, params, extent, size, projection, opt_version); + baseUrl, params, extent, size, projection); } }; }; diff --git a/test/spec/ol/tileurlfunction.test.js b/test/spec/ol/tileurlfunction.test.js index d411ed4e24..a19f0659fc 100644 --- a/test/spec/ol/tileurlfunction.test.js +++ b/test/spec/ol/tileurlfunction.test.js @@ -74,10 +74,10 @@ describe('ol.TileUrlFunction', function() { 'http://wms?foo=bar', {}); var tileCoord = new ol.TileCoord(1, 0, 0); var tileUrl = tileUrlFunction(tileCoord, tileGrid, epsg3857); - var expected = 'http://wms?foo=bar&SERVICE=WMS&VERSION=1.3.0&' + - 'REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&WIDTH=256&' + - 'HEIGHT=256&BBOX=-20037508.342789244%2C20037508.342789244%2C0%2C' + - '40075016.68557849&CRS=EPSG%3A3857&STYLES='; + var expected = 'http://wms?foo=bar&SERVICE=WMS&VERSION=1.3.0&REQUEST=' + + 'GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&WIDTH=256&HEIGHT=256&' + + 'STYLES=&CRS=EPSG%3A3857&BBOX=-20037508.342789244%2C2' + + '0037508.342789244%2C0%2C40075016.68557849'; expect(tileUrl).toEqual(expected); }); it('creates expected URL respecting axis orientation', function() { @@ -86,10 +86,10 @@ describe('ol.TileUrlFunction', function() { 'http://wms?foo=bar', {}); var tileCoord = new ol.TileCoord(1, 0, 0); var tileUrl = tileUrlFunction(tileCoord, tileGrid, epsg4326); - var expected = 'http://wms?foo=bar&SERVICE=WMS&VERSION=1.3.0&' + - 'REQUEST=GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&WIDTH=256&' + - 'HEIGHT=256&BBOX=20037508.342789244%2C-20037508.342789244%2C' + - '40075016.68557849%2C0&CRS=EPSG%3A4326&STYLES='; + var expected = 'http://wms?foo=bar&SERVICE=WMS&VERSION=1.3.0&REQUEST=' + + 'GetMap&FORMAT=image%2Fpng&TRANSPARENT=true&WIDTH=256&HEIGHT=256&' + + 'STYLES=&CRS=EPSG%3A4326&BBOX=20037508.342789244%2C' + + '-20037508.342789244%2C40075016.68557849%2C0'; expect(tileUrl).toEqual(expected); }); }); From 19f86926724c51f0fb914f87068081ca7024708b Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 5 Mar 2013 11:11:41 +0100 Subject: [PATCH 19/28] Moving VERSION into params --- examples/epsg-4326.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/epsg-4326.js b/examples/epsg-4326.js index c6d48eb4ed..1f8cff65b6 100644 --- a/examples/epsg-4326.js +++ b/examples/epsg-4326.js @@ -16,8 +16,8 @@ var layers = new ol.Collection([ source: new ol.source.TiledWMS({ url: 'http://vmap0.tiles.osgeo.org/wms/vmap0', crossOrigin: null, - version: '1.1.1', params: { + 'VERSION': '1.1.1', 'LAYERS': 'basic', 'FORMAT': 'image/jpeg' }, From aa9275b57f9aa3026c9db778ec0cfe6d35c263bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 5 Mar 2013 11:26:22 +0100 Subject: [PATCH 20/28] Make hostexamples target more robust --- build.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/build.py b/build.py index c0e7e7c1b8..0154f20d94 100755 --- a/build.py +++ b/build.py @@ -363,15 +363,21 @@ def jsdoc_BRANCH_timestamp(t): @target('hostexamples', 'build', 'examples', phony=True) def hostexamples(t): - t.makedirs('build/gh-pages/%(BRANCH)s/examples') - t.makedirs('build/gh-pages/%(BRANCH)s/build') - t.cp(EXAMPLES, (path.replace('.html', '.js') for path in EXAMPLES), 'examples/style.css', 'build/gh-pages/%(BRANCH)s/examples/') - t.rm_rf('build/gh-pages/%(BRANCH)s/examples/data') - t.cp_r('examples/data', 'build/gh-pages/%(BRANCH)s/examples/data') - t.cp('build/loader_hosted_examples.js', 'build/gh-pages/%(BRANCH)s/examples/loader.js') - t.cp('build/ol.js', 'build/ol-simple.js', 'build/ol-whitespace.js', 'build/ol.css', 'build/gh-pages/%(BRANCH)s/build/') - t.cp('examples/example-list.html', 'build/gh-pages/%(BRANCH)s/examples/index.html') - t.cp('examples/example-list.js', 'examples/example-list.xml', 'examples/Jugl.js', 'build/gh-pages/%(BRANCH)s/examples/') + examples_dir = 'build/gh-pages/%(BRANCH)s/examples' + build_dir = 'build/gh-pages/%(BRANCH)s/build' + t.rm_rf(examples_dir) + t.makedirs(examples_dir) + t.rm_rf(build_dir) + t.makedirs(build_dir) + t.cp(EXAMPLES, (path.replace('.html', '.js') for path in EXAMPLES), + 'examples/style.css', examples_dir) + t.cp_r('examples/data', examples_dir + '/data') + t.cp('build/loader_hosted_examples.js', examples_dir + '/loader.js') + t.cp('build/ol.js', 'build/ol-simple.js', 'build/ol-whitespace.js', + 'build/ol.css', build_dir) + t.cp('examples/example-list.html', examples_dir + '/index.html') + t.cp('examples/example-list.js', 'examples/example-list.xml', + 'examples/Jugl.js', examples_dir) @target('check-examples', 'hostexamples', phony=True) From 8b3cfda963fd644b560fe3a3c899dd0c98c8672b Mon Sep 17 00:00:00 2001 From: Bruno Binet Date: Tue, 5 Mar 2013 11:19:46 +0100 Subject: [PATCH 21/28] Add defaultNamespaceURI and errorProperty properties for WMTS. Thanks @bartvde. --- src/ol/parser/ogc/wmtscapabilities_v1_0_0.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js b/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js index 9260038a63..19ad6d481e 100644 --- a/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js +++ b/src/ol/parser/ogc/wmtscapabilities_v1_0_0.js @@ -12,6 +12,8 @@ goog.require('ol.projection'); * @extends {ol.parser.XML} */ ol.parser.ogc.WMTSCapabilities_v1_0_0 = function() { + this.defaultNamespaceURI = 'http://www.opengis.net/wtms/1.0'; + this.errorProperty = 'serviceIdentification'; this.readers = { 'http://www.opengis.net/wmts/1.0': { 'Capabilities': function(node, obj) { From 82c2fc19a19567900e91e587f0de56091426d8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 5 Mar 2013 11:37:30 +0100 Subject: [PATCH 22/28] Check the examples against whitespace build first This is to produce stack traces that are easier to understand when an example fails to load. --- build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 0154f20d94..ce0cad2162 100755 --- a/build.py +++ b/build.py @@ -384,9 +384,9 @@ def hostexamples(t): def check_examples(t): directory = 'build/gh-pages/%(BRANCH)s/' examples = ['build/gh-pages/%(BRANCH)s/' + e for e in EXAMPLES] - all_examples = examples + \ + all_examples = [e + '?mode=whitespace' for e in examples] + \ [e + '?mode=simple' for e in examples] + \ - [e + '?mode=whitespace' for e in examples] + examples for example in all_examples: t.run('%(PHANTOMJS)s', 'bin/check-example.js', example) From 254a96e0361a135f6d3bfcf43baf0abd59a95b69 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 5 Mar 2013 11:51:53 +0100 Subject: [PATCH 23/28] Improve comment parsing when checking for requires --- build.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/build.py b/build.py index c0e7e7c1b8..5b8625d4fe 100755 --- a/build.py +++ b/build.py @@ -243,24 +243,24 @@ def build_lint_src_timestamp(t): def _strip_comments(lines): # FIXME this is a horribe hack, we should use a proper JavaScript parser here - in_comment = False + in_multiline_comment = False + lineno = 0 for line in lines: - if in_comment: + lineno += 1 + if in_multiline_comment: index = line.find('*/') if index != -1: - in_comment = False - yield line[index + 2:] - else: + in_multiline_comment = False + line = line[index + 2:] + if not in_multiline_comment: + line = re.sub(r'//[^\n]*', '', line) + line = re.sub(r'/\*.*?\*/', '', line) index = line.find('/*') if index != -1: - yield line[:index] - in_comment = True + yield lineno, line[:index] + in_multiline_comment = True else: - index = line.find('//') - if index != -1: - yield line[:index] - else: - yield line + yield lineno, line @target('build/check-requires-timestamp', SRC, INTERNAL_SRC, EXTERNAL_SRC, EXAMPLES_SRC, SPEC) @@ -272,10 +272,8 @@ def build_check_requires_timestamp(t): continue require_linenos = {} uses = set() - lineno = 0 lines = open(filename).readlines() - for line in lines: - lineno += 1 + for lineno, line in _strip_comments(lines): m = re.match(r'goog.provide\(\'(.*)\'\);', line) if m: all_provides.add(m.group(1)) @@ -284,7 +282,7 @@ def build_check_requires_timestamp(t): if m: require_linenos[m.group(1)] = lineno continue - for line in lines: + for lineno, line in _strip_comments(lines): for require in require_linenos.iterkeys(): if require in line: uses.add(require) @@ -302,9 +300,7 @@ def build_check_requires_timestamp(t): provides = set() requires = set() uses = set() - lineno = 0 - for line in _strip_comments(open(filename)): - lineno += 1 + for lineno, line in _strip_comments(open(filename)): m = re.match(r'goog.provide\(\'(.*)\'\);', line) if m: provides.add(m.group(1)) From b884f2f25d6b8dd4ddb065a1574286bc13efd5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 5 Mar 2013 12:51:03 +0100 Subject: [PATCH 24/28] View2D NoConstraint methods renamed --- src/ol/interaction/dragrotateinteraction.js | 2 +- src/ol/interaction/touchrotateinteraction.js | 3 ++- src/ol/interaction/touchzoominteraction.js | 2 +- src/ol/view2d.js | 10 +++++----- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ol/interaction/dragrotateinteraction.js b/src/ol/interaction/dragrotateinteraction.js index d4036be9c1..2ad45dfa78 100644 --- a/src/ol/interaction/dragrotateinteraction.js +++ b/src/ol/interaction/dragrotateinteraction.js @@ -53,7 +53,7 @@ ol.interaction.DragRotate.prototype.handleDrag = function(mapBrowserEvent) { // FIXME supports View2D only goog.asserts.assert(view instanceof ol.View2D); map.requestRenderFrame(); - view.rotateNoConstraint(map, view.getRotation() - delta); + view.rotateWithoutConstraints(map, view.getRotation() - delta); } this.lastAngle_ = theta; }; diff --git a/src/ol/interaction/touchrotateinteraction.js b/src/ol/interaction/touchrotateinteraction.js index a3a6e14dfe..3f4abc3ce7 100644 --- a/src/ol/interaction/touchrotateinteraction.js +++ b/src/ol/interaction/touchrotateinteraction.js @@ -98,7 +98,8 @@ ol.interaction.TouchRotate.prototype.handleTouchMove = // rotate if (this.rotating_) { - view.rotateNoConstraint(map, view.getRotation() + rotationDelta, anchor); + view.rotateWithoutConstraints(map, view.getRotation() + rotationDelta, + anchor); } }; diff --git a/src/ol/interaction/touchzoominteraction.js b/src/ol/interaction/touchzoominteraction.js index 76d99bafd7..bbb0bab518 100644 --- a/src/ol/interaction/touchzoominteraction.js +++ b/src/ol/interaction/touchzoominteraction.js @@ -65,7 +65,7 @@ ol.interaction.TouchZoom.prototype.handleTouchMove = var anchor = map.getCoordinateFromPixel(centroid); // scale, bypass the resolution constraint - view.zoomNoConstraint(map, view.getResolution() * scaleDelta, anchor); + view.zoomWithoutConstraints(map, view.getResolution() * scaleDelta, anchor); }; diff --git a/src/ol/view2d.js b/src/ol/view2d.js index f022a1bf25..f472014250 100644 --- a/src/ol/view2d.js +++ b/src/ol/view2d.js @@ -264,7 +264,7 @@ goog.exportProperty( ol.View2D.prototype.rotate = function(map, rotation, opt_anchor, opt_duration) { rotation = this.constraints_.rotation(rotation, 0); - this.rotateNoConstraint(map, rotation, opt_anchor, opt_duration); + this.rotateWithoutConstraints(map, rotation, opt_anchor, opt_duration); }; @@ -274,7 +274,7 @@ ol.View2D.prototype.rotate = * @param {ol.Coordinate=} opt_anchor Anchor coordinate. * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.rotateNoConstraint = +ol.View2D.prototype.rotateWithoutConstraints = function(map, rotation, opt_anchor, opt_duration) { if (goog.isDefAndNotNull(rotation)) { var currentRotation = this.getRotation(); @@ -324,7 +324,7 @@ ol.View2D.prototype.rotateNoConstraint = ol.View2D.prototype.zoom = function(map, resolution, opt_anchor, opt_duration) { resolution = this.constraints_.resolution(resolution, 0); - this.zoomNoConstraint(map, resolution, opt_anchor, opt_duration); + this.zoomWithoutConstraints(map, resolution, opt_anchor, opt_duration); }; @@ -338,7 +338,7 @@ ol.View2D.prototype.zoomByDelta = function(map, delta, opt_anchor, opt_duration) { var currentResolution = this.getResolution(); var resolution = this.constraints_.resolution(currentResolution, delta); - this.zoomNoConstraint(map, resolution, opt_anchor, opt_duration); + this.zoomWithoutConstraints(map, resolution, opt_anchor, opt_duration); }; @@ -348,7 +348,7 @@ ol.View2D.prototype.zoomByDelta = * @param {ol.Coordinate=} opt_anchor Anchor coordinate. * @param {number=} opt_duration Duration. */ -ol.View2D.prototype.zoomNoConstraint = +ol.View2D.prototype.zoomWithoutConstraints = function(map, resolution, opt_anchor, opt_duration) { if (goog.isDefAndNotNull(resolution)) { var currentResolution = this.getResolution(); From df4f2c4c0a5aac00ee800ab1e6b1cdce59bd3dae Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 5 Mar 2013 15:25:47 +0100 Subject: [PATCH 25/28] No projection configuration needed on the layer --- examples/epsg-4326.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/epsg-4326.js b/examples/epsg-4326.js index 1f8cff65b6..303da4c23e 100644 --- a/examples/epsg-4326.js +++ b/examples/epsg-4326.js @@ -9,8 +9,6 @@ goog.require('ol.projection'); goog.require('ol.source.TiledWMS'); -var epsg4326 = ol.projection.getFromCode('EPSG:4326'); - var layers = new ol.Collection([ new ol.layer.TileLayer({ source: new ol.source.TiledWMS({ @@ -20,8 +18,7 @@ var layers = new ol.Collection([ 'VERSION': '1.1.1', 'LAYERS': 'basic', 'FORMAT': 'image/jpeg' - }, - projection: epsg4326 + } }) }) ]); @@ -34,7 +31,7 @@ var map = new ol.Map({ scaleLineUnits: ol.control.ScaleLineUnits.DEGREES, target: 'map', view: new ol.View2D({ - projection: epsg4326, + projection: ol.projection.getFromCode('EPSG:4326'), center: new ol.Coordinate(0, 0), zoom: 2 }) From 9a0e7a96fe78f32273f8e07fe9136b2c97e2575c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 5 Mar 2013 12:41:44 +0100 Subject: [PATCH 26/28] Moving the feature management to the layer The source will be responsible for fetching vector data. --- examples/style-rules.js | 79 ++++--- examples/vector-layer.js | 11 +- src/ol/layer/vectorlayer.js | 192 ++++++++++++++++++ .../canvas/canvasvectorlayerrenderer.js | 5 +- src/ol/source/vectorsource.js | 185 ----------------- test/spec/ol/layer/vectorlayer.test.js | 101 +++++++++ test/spec/ol/source/vectorsource.test.js | 109 +--------- 7 files changed, 344 insertions(+), 338 deletions(-) diff --git a/examples/style-rules.js b/examples/style-rules.js index b2c0e1bd01..aa34ce8a00 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -15,11 +15,46 @@ goog.require('ol.style.Rule'); goog.require('ol.style.Style'); -var source = new ol.source.Vector({ - projection: ol.projection.getFromCode('EPSG:3857') +var style = new ol.style.Style({rules: [ + new ol.style.Rule({ + filter: new ol.filter.Filter(function(feature) { + return feature.get('where') == 'outer'; + }), + symbolizers: [ + new ol.style.Line({ + strokeStyle: new ol.Expression('color'), + strokeWidth: 4, + opacity: 1 + }) + ] + }), + new ol.style.Rule({ + filter: new ol.filter.Filter(function(feature) { + return feature.get('where') == 'inner'; + }), + symbolizers: [ + new ol.style.Line({ + strokeStyle: '#013', + strokeWidth: 4, + opacity: 1 + }), + new ol.style.Line({ + strokeStyle: new ol.Expression('color'), + strokeWidth: 2, + opacity: 1 + }) + ] + }) +]}); + +var vector = new ol.layer.Vector({ + style: style, + source: new ol.source.Vector({ + projection: ol.projection.getFromCode('EPSG:3857') + }) }); -source.addFeatures([ +vector.addFeatures([ new ol.Feature({ g: new ol.geom.LineString([[-10000000, -10000000], [10000000, 10000000]]), 'color': '#BADA55', @@ -52,44 +87,6 @@ source.addFeatures([ }) ]); -var style = new ol.style.Style({ - rules: [ - new ol.style.Rule({ - filter: new ol.filter.Filter(function(feature) { - return feature.get('where') == 'outer'; - }), - symbolizers: [ - new ol.style.Line({ - strokeStyle: new ol.Expression('color'), - strokeWidth: 4, - opacity: 1 - }) - ] - }), - new ol.style.Rule({ - filter: new ol.filter.Filter(function(feature) { - return feature.get('where') == 'inner'; - }), - symbolizers: [ - new ol.style.Line({ - strokeStyle: '#013', - strokeWidth: 4, - opacity: 1 - }), - new ol.style.Line({ - strokeStyle: new ol.Expression('color'), - strokeWidth: 2, - opacity: 1 - }) - ] - }) - ] -}); - -var vector = new ol.layer.Vector({ - source: source, - style: style -}); var map = new ol.Map({ layers: new ol.Collection([vector]), diff --git a/examples/vector-layer.js b/examples/vector-layer.js index 916dc6406b..e28f0d9cd6 100644 --- a/examples/vector-layer.js +++ b/examples/vector-layer.js @@ -17,11 +17,13 @@ var raster = new ol.layer.TileLayer({ source: new ol.source.MapQuestOpenAerial() }); -var source = new ol.source.Vector({ - projection: ol.projection.getFromCode('EPSG:3857') +var vector = new ol.layer.Vector({ + source: new ol.source.Vector({ + projection: ol.projection.getFromCode('EPSG:3857') + }) }); -source.addFeatures([ +vector.addFeatures([ new ol.Feature({ g: new ol.geom.LineString([[-10000000, -10000000], [10000000, 10000000]]) }), @@ -31,9 +33,6 @@ source.addFeatures([ new ol.Feature({g: new ol.geom.Point([-10000000, 5000000])}) ]); -var vector = new ol.layer.Vector({ - source: source -}); var map = new ol.Map({ layers: new ol.Collection([raster, vector]), diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 6703ec3e3c..77fde89ac2 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -3,10 +3,170 @@ goog.provide('ol.layer.Vector'); goog.require('ol.Feature'); goog.require('ol.layer.Layer'); goog.require('ol.source.Vector'); +goog.require('ol.structs.RTree'); goog.require('ol.style.Style'); +/** + * @constructor + */ +ol.layer.FeatureCache = function() { + + /** + * @type {Object.} + * @private + */ + this.idLookup_; + + /** + * @type {Object.} + * @private + */ + this.geometryTypeIndex_; + + /** + * @type {Object.} + * @private + */ + this.boundsByGeometryType_; + + this.clear(); + +}; + + +/** + * Clear the cache. + */ +ol.layer.FeatureCache.prototype.clear = function() { + this.idLookup_ = {}; + + var geometryTypeIndex = {}, + boundsByGeometryType = {}, + geometryType; + for (var key in ol.geom.GeometryType) { + geometryType = ol.geom.GeometryType[key]; + geometryTypeIndex[geometryType] = {}; + boundsByGeometryType[geometryType] = new ol.structs.RTree(); + } + this.geometryTypeIndex_ = geometryTypeIndex; + this.boundsByGeometryType_ = boundsByGeometryType; +}; + + +/** + * Add a feature to the cache. + * @param {ol.Feature} feature Feature to be cached. + */ +ol.layer.FeatureCache.prototype.add = function(feature) { + var id = goog.getUid(feature).toString(), + geometry = feature.getGeometry(); + + this.idLookup_[id] = feature; + + // index by geometry type and bounding box + if (!goog.isNull(geometry)) { + var geometryType = geometry.getType(); + this.geometryTypeIndex_[geometryType][id] = feature; + this.boundsByGeometryType_[geometryType].put(geometry.getBounds(), + feature); + } +}; + + +/** + * @param {ol.filter.Filter=} opt_filter Optional filter. + * @return {Object.} Object of features, keyed by id. + * @private + */ +ol.layer.FeatureCache.prototype.getFeaturesObject_ = function(opt_filter) { + var i, features; + if (!goog.isDef(opt_filter)) { + features = this.idLookup_; + } else { + if (opt_filter instanceof ol.filter.Geometry) { + features = this.geometryTypeIndex_[opt_filter.getType()]; + } else if (opt_filter instanceof ol.filter.Extent) { + var boundsByGeometryType = this.boundsByGeometryType_, + extent = opt_filter.getExtent(); + features = {}; + for (i in boundsByGeometryType) { + goog.object.extend(features, boundsByGeometryType[i].find(extent)); + } + } else if (opt_filter instanceof ol.filter.Logical && + opt_filter.operator === ol.filter.LogicalOperator.AND) { + var filters = opt_filter.getFilters(); + if (filters.length === 2) { + var filter, geometryFilter, extentFilter; + for (i = 0; i <= 1; ++i) { + filter = filters[i]; + if (filter instanceof ol.filter.Geometry) { + geometryFilter = filter; + } else if (filter instanceof ol.filter.Extent) { + extentFilter = filter; + } + } + if (extentFilter && geometryFilter) { + features = this.boundsByGeometryType_[geometryFilter.getType()] + .find(extentFilter.getExtent()); + } + } + } + if (!goog.isDef(features)) { + // TODO: support fast lane for other filter types + var candidates = this.idLookup_, + feature; + features = {}; + for (i in candidates) { + feature = candidates[i]; + if (opt_filter.applies(feature) === true) { + features[i] = feature; + } + } + } + } + return features; +}; + + +/** + * @param {ol.filter.Filter=} opt_filter Optional filter. + * @return {Array.} Array of features. + */ +ol.layer.FeatureCache.prototype.getFeatures = function(opt_filter) { + return goog.object.getValues(this.getFeaturesObject_(opt_filter)); +}; + + +/** + * @param {ol.filter.Geometry} filter Geometry type filter. + * @return {Array.} Array of features. + * @private + */ +ol.layer.FeatureCache.prototype.getFeaturesByGeometryType_ = function(filter) { + return goog.object.getValues(this.geometryTypeIndex_[filter.getType()]); +}; + + +/** + * Get features by ids. + * @param {Array.} ids Array of (internal) identifiers. + * @return {Array.} Array of features. + * @private + */ +ol.layer.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { + var len = ids.length, + features = new Array(len), + i; + for (i = 0; i < len; ++i) { + features[i] = this.idLookup_[ids[i]]; + } + return features; +}; + + + /** * @constructor * @extends {ol.layer.Layer} @@ -21,10 +181,26 @@ ol.layer.Vector = function(layerOptions) { */ this.style_ = goog.isDef(layerOptions.style) ? layerOptions.style : null; + /** + * @type {ol.layer.FeatureCache} + * @private + */ + this.featureCache_ = new ol.layer.FeatureCache(); + }; goog.inherits(ol.layer.Vector, ol.layer.Layer); +/** + * @param {Array.} features Array of features. + */ +ol.layer.Vector.prototype.addFeatures = function(features) { + for (var i = 0, ii = features.length; i < ii; ++i) { + this.featureCache_.add(features[i]); + } +}; + + /** * @return {ol.source.Vector} Source. */ @@ -33,6 +209,15 @@ ol.layer.Vector.prototype.getVectorSource = function() { }; +/** + * @param {ol.filter.Filter=} opt_filter Optional filter. + * @return {Array.} Array of features. + */ +ol.layer.Vector.prototype.getFeatures = function(opt_filter) { + return this.featureCache_.getFeatures(opt_filter); +}; + + /** * @param {Array.} features Features. * @return {Array.} symbolizers for features. @@ -72,3 +257,10 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral = } return featuresBySymbolizer; }; + + +goog.require('ol.filter.Extent'); +goog.require('ol.filter.Geometry'); +goog.require('ol.filter.Logical'); +goog.require('ol.filter.LogicalOperator'); +goog.require('ol.geom.GeometryType'); diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index b4560e2e88..b3a0a191b6 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -167,11 +167,10 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = resolution = view2DState.resolution, extent = frameState.extent, layer = this.getVectorLayer(), - source = layer.getVectorSource(), tileGrid = this.tileGrid_; if (goog.isNull(tileGrid)) { - // lazy tile source creation to match the view projection + // lazy tile grid creation to match the view projection tileGrid = ol.tilegrid.createForProjection( view2DState.projection, 22, // should be no harm in going big here - ideally, it would be ∞ @@ -282,7 +281,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = for (i = 0; i < numFilters; ++i) { geomFilter = filters[i]; type = geomFilter.getType(); - features = source.getFeatures(new ol.filter.Logical( + features = layer.getFeatures(new ol.filter.Logical( [geomFilter, extentFilter], ol.filter.LogicalOperator.AND)); if (features.length) { groups = layer.groupFeaturesBySymbolizerLiteral(features); diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 1dc6c0a0a1..60ff1f1228 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -11,166 +11,6 @@ goog.require('ol.filter.Logical'); goog.require('ol.filter.LogicalOperator'); goog.require('ol.geom.GeometryType'); goog.require('ol.source.Source'); -goog.require('ol.structs.RTree'); - - - -/** - * @constructor - */ -ol.source.FeatureCache = function() { - - /** - * @type {Object.} - * @private - */ - this.idLookup_; - - /** - * @type {Object.} - * @private - */ - this.geometryTypeIndex_; - - /** - * @type {Object.} - * @private - */ - this.boundsByGeometryType_; - - this.clear(); - -}; - - -/** - * Clear the cache. - */ -ol.source.FeatureCache.prototype.clear = function() { - this.idLookup_ = {}; - - var geometryTypeIndex = {}, - boundsByGeometryType = {}, - geometryType; - for (var key in ol.geom.GeometryType) { - geometryType = ol.geom.GeometryType[key]; - geometryTypeIndex[geometryType] = {}; - boundsByGeometryType[geometryType] = new ol.structs.RTree(); - } - this.geometryTypeIndex_ = geometryTypeIndex; - this.boundsByGeometryType_ = boundsByGeometryType; -}; - - -/** - * Add a feature to the cache. - * @param {ol.Feature} feature Feature to be cached. - */ -ol.source.FeatureCache.prototype.add = function(feature) { - var id = goog.getUid(feature).toString(), - geometry = feature.getGeometry(); - - this.idLookup_[id] = feature; - - // index by geometry type and bounding box - if (!goog.isNull(geometry)) { - var geometryType = geometry.getType(); - this.geometryTypeIndex_[geometryType][id] = feature; - this.boundsByGeometryType_[geometryType].put(geometry.getBounds(), - feature); - } -}; - - -/** - * @param {ol.filter.Filter=} opt_filter Optional filter. - * @return {Object.} Object of features, keyed by id. - * @private - */ -ol.source.FeatureCache.prototype.getFeaturesObject_ = function(opt_filter) { - var i, features; - if (!goog.isDef(opt_filter)) { - features = this.idLookup_; - } else { - if (opt_filter instanceof ol.filter.Geometry) { - features = this.geometryTypeIndex_[opt_filter.getType()]; - } else if (opt_filter instanceof ol.filter.Extent) { - var boundsByGeometryType = this.boundsByGeometryType_, - extent = opt_filter.getExtent(); - features = {}; - for (i in boundsByGeometryType) { - goog.object.extend(features, boundsByGeometryType[i].find(extent)); - } - } else if (opt_filter instanceof ol.filter.Logical && - opt_filter.operator === ol.filter.LogicalOperator.AND) { - var filters = opt_filter.getFilters(); - if (filters.length === 2) { - var filter, geometryFilter, extentFilter; - for (i = 0; i <= 1; ++i) { - filter = filters[i]; - if (filter instanceof ol.filter.Geometry) { - geometryFilter = filter; - } else if (filter instanceof ol.filter.Extent) { - extentFilter = filter; - } - } - if (extentFilter && geometryFilter) { - features = this.boundsByGeometryType_[geometryFilter.getType()] - .find(extentFilter.getExtent()); - } - } - } - if (!goog.isDef(features)) { - // TODO: support fast lane for other filter types - var candidates = this.idLookup_, - feature; - features = {}; - for (i in candidates) { - feature = candidates[i]; - if (opt_filter.applies(feature) === true) { - features[i] = feature; - } - } - } - } - return features; -}; - - -/** - * @param {ol.filter.Filter=} opt_filter Optional filter. - * @return {Array.} Array of features. - */ -ol.source.FeatureCache.prototype.getFeatures = function(opt_filter) { - return goog.object.getValues(this.getFeaturesObject_(opt_filter)); -}; - - -/** - * @param {ol.filter.Geometry} filter Geometry type filter. - * @return {Array.} Array of features. - * @private - */ -ol.source.FeatureCache.prototype.getFeaturesByGeometryType_ = function(filter) { - return goog.object.getValues(this.geometryTypeIndex_[filter.getType()]); -}; - - -/** - * Get features by ids. - * @param {Array.} ids Array of (internal) identifiers. - * @return {Array.} Array of features. - * @private - */ -ol.source.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { - var len = ids.length, - features = new Array(len), - i; - for (i = 0; i < len; ++i) { - features[i] = this.idLookup_[ids[i]]; - } - return features; -}; /** @@ -195,30 +35,5 @@ ol.source.Vector = function(options) { projection: options.projection }); - /** - * @type {ol.source.FeatureCache} - * @private - */ - this.featureCache_ = new ol.source.FeatureCache(); - }; goog.inherits(ol.source.Vector, ol.source.Source); - - -/** - * @param {Array.} features Array of features. - */ -ol.source.Vector.prototype.addFeatures = function(features) { - for (var i = 0, ii = features.length; i < ii; ++i) { - this.featureCache_.add(features[i]); - } -}; - - -/** - * @param {ol.filter.Filter=} opt_filter Optional filter. - * @return {Array.} Array of features. - */ -ol.source.Vector.prototype.getFeatures = function(opt_filter) { - return this.featureCache_.getFeatures(opt_filter); -}; diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index c08053419d..b6e5ff89ec 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -2,6 +2,99 @@ goog.provide('ol.test.layer.Vector'); describe('ol.layer.Vector', function() { + describe('#addFeatures()', function() { + + it('allows adding features', function() { + var layer = new ol.layer.Vector({ + new ol.source.Vector({}) + }); + layer.addFeatures([new ol.Feature(), new ol.Feature()]); + expect(layer.getFeatures().length).toEqual(2); + }); + }); + + describe('#getFeatures()', function() { + + var layer, features; + + beforeEach(function() { + features = [ + new ol.Feature({ + g: new ol.geom.Point([16.0, 48.0]) + }), + new ol.Feature({ + g: new ol.geom.Point([16.1, 48.1]) + }), + new ol.Feature({ + g: new ol.geom.Point([16.2, 48.2]) + }), + new ol.Feature({ + g: new ol.geom.Point([16.3, 48.3]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[16.4, 48.4], [16.5, 48.5]]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[16.6, 48.6], [16.7, 48.7]]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[16.8, 48.8], [16.9, 48.9]]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) + }) + ]; + layer = new ol.layer.Vector({ + source: new ol.source.Vector({}) + }); + layer.addFeatures(features); + }); + + var geomFilter = new ol.filter.Geometry(ol.geom.GeometryType.LINESTRING); + var extentFilter = new ol.filter.Extent(new ol.Extent(16, 48, 16.3, 48.3)); + + it('can filter by geometry type using its GeometryType index', function() { + spyOn(geomFilter, 'applies'); + var lineStrings = layer.getFeatures(geomFilter); + expect(geomFilter.applies).not.toHaveBeenCalled(); + expect(lineStrings.length).toEqual(4); + expect(lineStrings).toContain(features[4]); + }); + + it('can filter by extent using its RTree', function() { + spyOn(extentFilter, 'applies'); + var subset = layer.getFeatures(extentFilter); + expect(extentFilter.applies).not.toHaveBeenCalled(); + expect(subset.length).toEqual(4); + expect(subset).not.toContain(features[7]); + }); + + it('can filter by extent and geometry type using its index', function() { + var filter1 = new ol.filter.Logical([geomFilter, extentFilter], + ol.filter.LogicalOperator.AND); + var filter2 = new ol.filter.Logical([extentFilter, geomFilter], + ol.filter.LogicalOperator.AND); + spyOn(filter1, 'applies'); + spyOn(filter2, 'applies'); + var subset1 = layer.getFeatures(filter1); + var subset2 = layer.getFeatures(filter2); + expect(filter1.applies).not.toHaveBeenCalled(); + expect(filter2.applies).not.toHaveBeenCalled(); + expect(subset1.length).toEqual(0); + expect(subset2.length).toEqual(0); + }); + + it('can handle query using the filter\'s applies function', function() { + var filter = new ol.filter.Logical([geomFilter, extentFilter], + ol.filter.LogicalOperator.OR); + spyOn(filter, 'applies').andCallThrough(); + var subset = layer.getFeatures(filter); + expect(filter.applies).toHaveBeenCalled(); + expect(subset.length).toEqual(8); + }); + + }); + describe('#groupFeaturesBySymbolizerLiteral()', function() { var layer = new ol.layer.Vector({ @@ -84,8 +177,16 @@ describe('ol.layer.Vector', function() { }); goog.require('ol.Expression'); +goog.require('ol.Extent'); goog.require('ol.Feature'); +goog.require('ol.Projection'); +goog.require('ol.filter.Extent'); +goog.require('ol.filter.Geometry'); +goog.require('ol.filter.Logical'); +goog.require('ol.filter.LogicalOperator'); +goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); goog.require('ol.projection'); goog.require('ol.layer.Vector'); goog.require('ol.source.Vector'); diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index 37a6b46113..c2cbfdf028 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -3,112 +3,15 @@ goog.provide('ol.test.source.Vector'); describe('ol.source.Vector', function() { - var vectorSource; - - describe('#addFeatures()', function() { - - it('works', function() { - vectorSource = new ol.source.Vector({ - projection: ol.projection.getFromCode('EPSG:4326') - }); - vectorSource.addFeatures([new ol.Feature()]); - expect(vectorSource.getFeatures().length).toEqual(1); + describe('constructor', function() { + it('creates an instance', function() { + var source = new ol.source.Vector({}); + expect(source).toBeA(ol.source.Vector); + expect(source).toBeA(ol.source.Source); }); }); - describe('#getFeatures()', function() { - - var features; - - beforeEach(function() { - features = [ - new ol.Feature({ - g: new ol.geom.Point([16.0, 48.0]) - }), - new ol.Feature({ - g: new ol.geom.Point([16.1, 48.1]) - }), - new ol.Feature({ - g: new ol.geom.Point([16.2, 48.2]) - }), - new ol.Feature({ - g: new ol.geom.Point([16.3, 48.3]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[16.4, 48.4], [16.5, 48.5]]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[16.6, 48.6], [16.7, 48.7]]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[16.8, 48.8], [16.9, 48.9]]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) - }) - ]; - vectorSource = new ol.source.Vector({ - projection: ol.projection.getFromCode('EPSG:4326') - }); - vectorSource.addFeatures(features); - }); - - var geomFilter = new ol.filter.Geometry(ol.geom.GeometryType.LINESTRING); - var extentFilter = new ol.filter.Extent(new ol.Extent(16, 48, 16.3, 48.3)); - - it('can filter by geometry type using its GeometryType index', function() { - spyOn(geomFilter, 'applies'); - var lineStrings = vectorSource.getFeatures(geomFilter); - expect(geomFilter.applies).not.toHaveBeenCalled(); - expect(lineStrings.length).toEqual(4); - expect(lineStrings).toContain(features[4]); - }); - - it('can filter by extent using its RTree', function() { - spyOn(extentFilter, 'applies'); - var subset = vectorSource.getFeatures(extentFilter); - expect(extentFilter.applies).not.toHaveBeenCalled(); - expect(subset.length).toEqual(4); - expect(subset).not.toContain(features[7]); - }); - - it('can filter by extent and geometry type using its index', function() { - var filter1 = new ol.filter.Logical([geomFilter, extentFilter], - ol.filter.LogicalOperator.AND); - var filter2 = new ol.filter.Logical([extentFilter, geomFilter], - ol.filter.LogicalOperator.AND); - spyOn(filter1, 'applies'); - spyOn(filter2, 'applies'); - var subset1 = vectorSource.getFeatures(filter1); - var subset2 = vectorSource.getFeatures(filter2); - expect(filter1.applies).not.toHaveBeenCalled(); - expect(filter2.applies).not.toHaveBeenCalled(); - expect(subset1.length).toEqual(0); - expect(subset2.length).toEqual(0); - }); - - it('can handle query using the filter\'s applies function', function() { - var filter = new ol.filter.Logical([geomFilter, extentFilter], - ol.filter.LogicalOperator.OR); - spyOn(filter, 'applies').andCallThrough(); - var subset = vectorSource.getFeatures(filter); - expect(filter.applies).toHaveBeenCalled(); - expect(subset.length).toEqual(8); - }); - - }); - }); -goog.require('ol.Extent'); -goog.require('ol.Feature'); -goog.require('ol.Projection'); -goog.require('ol.filter.Extent'); -goog.require('ol.filter.Geometry'); -goog.require('ol.filter.Logical'); -goog.require('ol.filter.LogicalOperator'); -goog.require('ol.geom.GeometryType'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.LineString'); +goog.require('ol.source.Source'); goog.require('ol.source.Vector'); -goog.require('ol.projection'); From 4f2523fcf2ce019c524a95a14ead5315baab4461 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 5 Mar 2013 18:13:10 +0100 Subject: [PATCH 27/28] Vector related exports --- src/objectliterals.exports | 31 ++++++++++++++++++++++++++ src/ol/feature.exports | 6 +++++ src/ol/filter/filter.exports | 7 ++++++ src/ol/geom/expression.exports | 1 + src/ol/geom/geometry.exports | 6 +++++ src/ol/layer/vectorlayer.exports | 3 +++ src/ol/source/vectorsource.exports | 1 + src/ol/source/vectorsource.js | 18 ++------------- src/ol/style.exports | 7 ++++++ src/ol/style/line.js | 11 +++------ src/ol/style/polygon.js | 13 ++++------- src/ol/style/rule.js | 7 ------ src/ol/style/shape.js | 19 ++++++---------- src/ol/style/style.js | 6 ----- test/spec/ol/layer/vectorlayer.test.js | 2 +- test/spec/ol/style/line.test.js | 9 +++++--- test/spec/ol/style/polygon.test.js | 12 +++++++--- test/spec/ol/style/shape.test.js | 19 +++++++++++++--- test/spec/ol/style/style.test.js | 8 +++++-- 19 files changed, 116 insertions(+), 70 deletions(-) create mode 100644 src/ol/feature.exports create mode 100644 src/ol/filter/filter.exports create mode 100644 src/ol/geom/expression.exports create mode 100644 src/ol/geom/geometry.exports create mode 100644 src/ol/layer/vectorlayer.exports create mode 100644 src/ol/source/vectorsource.exports create mode 100644 src/ol/style.exports diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 87a20a44c6..8df22dc74e 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -111,6 +111,11 @@ @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.resolutions Array.|undefined @exportObjectLiteralProperty ol.source.SingleImageWMSOptions.url string|undefined +@exportObjectLiteral ol.source.SourceOptions +@exportObjectLiteralProperty ol.source.SourceOptions.attributions Array.|undefined +@exportObjectLiteralProperty ol.source.SourceOptions.extent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.SourceOptions.projection ol.Projection|undefined + @exportObjectLiteral ol.source.StamenOptions @exportObjectLiteralProperty ol.source.StamenOptions.layer string @exportObjectLiteralProperty ol.source.StamenOptions.minZoom number|undefined @@ -138,6 +143,32 @@ @exportObjectLiteralProperty ol.source.TiledWMSOptions.url string|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.urls Array.|undefined +@exportObjectLiteral ol.style.LineOptions +@exportObjectLiteralProperty ol.style.LineOptions.strokeStyle string|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.LineOptions.strokeWidth number|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.LineOptions.opacity number|ol.Expression|undefined + +@exportObjectLiteral ol.style.PolygonOptions +@exportObjectLiteralProperty ol.style.PolygonOptions.fillStyle string|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.PolygonOptions.strokeStyle string|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.PolygonOptions.strokeWidth number|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.PolygonOptions.opacity number|ol.Expression|undefined + +@exportObjectLiteral ol.style.RuleOptions +@exportObjectLiteralProperty ol.style.RuleOptions.filter ol.filter.Filter|undefined +@exportObjectLiteralProperty ol.style.RuleOptions.symbolizers Array.|undefined + +@exportObjectLiteral ol.style.ShapeOptions +@exportObjectLiteralProperty ol.style.ShapeOptions.type ol.style.ShapeType|undefined +@exportObjectLiteralProperty ol.style.ShapeOptions.size number|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.ShapeOptions.fillStyle string|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.ShapeOptions.strokeStyle string|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.ShapeOptions.strokeWidth number|ol.Expression|undefined +@exportObjectLiteralProperty ol.style.ShapeOptions.opacity number|ol.Expression|undefined + +@exportObjectLiteral ol.style.StyleOptions +@exportObjectLiteralProperty ol.style.StyleOptions.rules Array. + @exportObjectLiteral ol.tilegrid.TileGridOptions @exportObjectLiteralProperty ol.tilegrid.TileGridOptions.origin ol.Coordinate|undefined @exportObjectLiteralProperty ol.tilegrid.TileGridOptions.origins Array.|undefined diff --git a/src/ol/feature.exports b/src/ol/feature.exports new file mode 100644 index 0000000000..3192a1fd2d --- /dev/null +++ b/src/ol/feature.exports @@ -0,0 +1,6 @@ +@exportSymbol ol.Feature +@exportProperty ol.Feature.prototype.get +@exportProperty ol.Feature.prototype.getAttributes +@exportProperty ol.Feature.prototype.getGeometry +@exportProperty ol.Feature.prototype.set +@exportProperty ol.Feature.prototype.setGeometry diff --git a/src/ol/filter/filter.exports b/src/ol/filter/filter.exports new file mode 100644 index 0000000000..696edee702 --- /dev/null +++ b/src/ol/filter/filter.exports @@ -0,0 +1,7 @@ +@exportSymbol ol.filter.Filter +@exportSymbol ol.filter.Geometry +@exportSymbol ol.filter.Logical + +@exportSymbol ol.filter.LogicalOperator +@exportProperty ol.filter.LogicalOperator.AND +@exportProperty ol.filter.LogicalOperator.OR diff --git a/src/ol/geom/expression.exports b/src/ol/geom/expression.exports new file mode 100644 index 0000000000..911b8a12c6 --- /dev/null +++ b/src/ol/geom/expression.exports @@ -0,0 +1 @@ +@exportSymbol ol.Expression diff --git a/src/ol/geom/geometry.exports b/src/ol/geom/geometry.exports new file mode 100644 index 0000000000..4170284a9e --- /dev/null +++ b/src/ol/geom/geometry.exports @@ -0,0 +1,6 @@ +@exportSymbol ol.geom.Point +@exportSymbol ol.geom.LineString +@exportSymbol ol.geom.Polygon +@exportSymbol ol.geom.MultiPoint +@exportSymbol ol.geom.MultiLineString +@exportSymbol ol.geom.MultiPolygon diff --git a/src/ol/layer/vectorlayer.exports b/src/ol/layer/vectorlayer.exports new file mode 100644 index 0000000000..6abe609b01 --- /dev/null +++ b/src/ol/layer/vectorlayer.exports @@ -0,0 +1,3 @@ +@exportClass ol.layer.Vector ol.layer.LayerOptions + +@exportProperty ol.layer.Vector.prototype.addFeatures diff --git a/src/ol/source/vectorsource.exports b/src/ol/source/vectorsource.exports new file mode 100644 index 0000000000..27cbc2bea0 --- /dev/null +++ b/src/ol/source/vectorsource.exports @@ -0,0 +1 @@ +@exportClass ol.source.Vector ol.source.SourceOptions diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 60ff1f1228..e45cc25548 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -13,27 +13,13 @@ goog.require('ol.geom.GeometryType'); goog.require('ol.source.Source'); -/** - * @typedef {{attributions: (Array.|undefined), - * extent: (ol.Extent|undefined), - * projection: (ol.Projection|undefined)}} - */ -ol.source.VectorOptions; - - /** * @constructor * @extends {ol.source.Source} - * @param {ol.source.VectorOptions} options Source options. + * @param {ol.source.SourceOptions} options Source options. */ ol.source.Vector = function(options) { - - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - projection: options.projection - }); - + goog.base(this, options); }; goog.inherits(ol.source.Vector, ol.source.Source); diff --git a/src/ol/style.exports b/src/ol/style.exports new file mode 100644 index 0000000000..9b47275dfb --- /dev/null +++ b/src/ol/style.exports @@ -0,0 +1,7 @@ +@exportClass ol.style.Line ol.style.LineOptions +@exportClass ol.style.Polygon ol.style.PolygonOptions +@exportClass ol.style.Rule ol.style.RuleOptions +@exportClass ol.style.Shape ol.style.ShapeOptions +@exportClass ol.style.Style ol.style.StyleOptions +@exportSymbol ol.style.ShapeType +@exportProperty ol.style.ShapeType.CIRCLE diff --git a/src/ol/style/line.js b/src/ol/style/line.js index 1f8d4bea80..59de816f72 100644 --- a/src/ol/style/line.js +++ b/src/ol/style/line.js @@ -24,12 +24,15 @@ ol.style.LineLiteralOptions; ol.style.LineLiteral = function(config) { goog.base(this); + goog.asserts.assertString(config.strokeStyle, 'strokeStyle must be a string'); /** @type {string} */ this.strokeStyle = config.strokeStyle; + goog.asserts.assertNumber(config.strokeWidth, 'strokeWidth must be a number'); /** @type {number} */ this.strokeWidth = config.strokeWidth; + goog.asserts.assertNumber(config.opacity, 'opacity must be a number'); /** @type {number} */ this.opacity = config.opacity; @@ -47,14 +50,6 @@ ol.style.LineLiteral.prototype.equals = function(lineLiteral) { }; -/** - * @typedef {{strokeStyle: (string|ol.Expression|undefined), - * strokeWidth: (number|ol.Expression|undefined), - * opacity: (number|ol.Expression|undefined)}} - */ -ol.style.LineOptions; - - /** * @constructor diff --git a/src/ol/style/polygon.js b/src/ol/style/polygon.js index 7a126abd99..c144ada353 100644 --- a/src/ol/style/polygon.js +++ b/src/ol/style/polygon.js @@ -25,15 +25,19 @@ ol.style.PolygonLiteralOptions; ol.style.PolygonLiteral = function(config) { goog.base(this); + goog.asserts.assertString(config.fillStyle, 'fillStyle must be a string'); /** @type {string} */ this.fillStyle = config.fillStyle; + goog.asserts.assertString(config.strokeStyle, 'strokeStyle must be a string'); /** @type {string} */ this.strokeStyle = config.strokeStyle; + goog.asserts.assertNumber(config.strokeWidth, 'strokeWidth must be a number'); /** @type {number} */ this.strokeWidth = config.strokeWidth; + goog.asserts.assertNumber(config.opacity, 'opacity must be a number'); /** @type {number} */ this.opacity = config.opacity; @@ -52,15 +56,6 @@ ol.style.PolygonLiteral.prototype.equals = function(polygonLiteral) { }; -/** - * @typedef {{fillStyle: (string|ol.Expression|undefined), - * strokeStyle: (string|ol.Expression|undefined), - * strokeWidth: (number|ol.Expression|undefined), - * opacity: (number|ol.Expression|undefined)}} - */ -ol.style.PolygonOptions; - - /** * @constructor diff --git a/src/ol/style/rule.js b/src/ol/style/rule.js index 14b054a3d3..b6f1284fea 100644 --- a/src/ol/style/rule.js +++ b/src/ol/style/rule.js @@ -5,13 +5,6 @@ goog.require('ol.filter.Filter'); goog.require('ol.style.Symbolizer'); -/** - * @typedef {{filter: (ol.filter.Filter), - * symbolizers: (Array.)}} - */ -ol.style.RuleOptions; - - /** * @constructor diff --git a/src/ol/style/shape.js b/src/ol/style/shape.js index 6fe00005e0..83056a6276 100644 --- a/src/ol/style/shape.js +++ b/src/ol/style/shape.js @@ -35,21 +35,27 @@ ol.style.ShapeLiteralOptions; */ ol.style.ShapeLiteral = function(config) { - /** @type {string} */ + goog.asserts.assertString(config.type, 'type must be a string'); + /** @type {ol.style.ShapeType} */ this.type = config.type; + goog.asserts.assertNumber(config.size, 'size must be a number'); /** @type {number} */ this.size = config.size; + goog.asserts.assertString(config.fillStyle, 'fillStyle must be a string'); /** @type {string} */ this.fillStyle = config.fillStyle; + goog.asserts.assertString(config.strokeStyle, 'strokeStyle must be a string'); /** @type {string} */ this.strokeStyle = config.strokeStyle; + goog.asserts.assertNumber(config.strokeWidth, 'strokeWidth must be a number'); /** @type {number} */ this.strokeWidth = config.strokeWidth; + goog.asserts.assertNumber(config.opacity, 'opacity must be a number'); /** @type {number} */ this.opacity = config.opacity; @@ -70,17 +76,6 @@ ol.style.ShapeLiteral.prototype.equals = function(shapeLiteral) { }; -/** - * @typedef {{type: (ol.style.ShapeType|undefined), - * size: (number|ol.Expression|undefined), - * fillStyle: (string|ol.Expression|undefined), - * strokeStyle: (string|ol.Expression|undefined), - * strokeWidth: (number|ol.Expression|undefined), - * opacity: (number|ol.Expression|undefined)}} - */ -ol.style.ShapeOptions; - - /** * @constructor diff --git a/src/ol/style/style.js b/src/ol/style/style.js index e1afc68665..07edb53086 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -6,12 +6,6 @@ goog.require('ol.style.Rule'); goog.require('ol.style.SymbolizerLiteral'); -/** - * @typedef {{rules: (Array.)}} - */ -ol.style.StyleOptions; - - /** * @constructor diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index b6e5ff89ec..f63402c935 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -6,7 +6,7 @@ describe('ol.layer.Vector', function() { it('allows adding features', function() { var layer = new ol.layer.Vector({ - new ol.source.Vector({}) + source: new ol.source.Vector({}) }); layer.addFeatures([new ol.Feature(), new ol.Feature()]); expect(layer.getFeatures().length).toEqual(2); diff --git a/test/spec/ol/style/line.test.js b/test/spec/ol/style/line.test.js index e9957e036e..95784ab15f 100644 --- a/test/spec/ol/style/line.test.js +++ b/test/spec/ol/style/line.test.js @@ -7,15 +7,18 @@ describe('ol.style.LineLiteral', function() { it('identifies equal literals', function() { var literal = new ol.style.LineLiteral({ strokeWidth: 3, - strokeStyle: '#BADA55' + strokeStyle: '#BADA55', + opacity: 1 }); var equalLiteral = new ol.style.LineLiteral({ strokeStyle: '#BADA55', - strokeWidth: 3 + strokeWidth: 3, + opacity: 1 }); var differentLiteral = new ol.style.LineLiteral({ strokeStyle: '#013', - strokeWidth: 3 + strokeWidth: 3, + opacity: 1 }); expect(literal.equals(equalLiteral)).toBe(true); expect(literal.equals(differentLiteral)).toBe(false); diff --git a/test/spec/ol/style/polygon.test.js b/test/spec/ol/style/polygon.test.js index 3e7693bc85..4c8d443db3 100644 --- a/test/spec/ol/style/polygon.test.js +++ b/test/spec/ol/style/polygon.test.js @@ -7,15 +7,21 @@ describe('ol.style.PolygonLiteral', function() { it('identifies equal literals', function() { var literal = new ol.style.PolygonLiteral({ strokeWidth: 3, - fillStyle: '#BADA55' + strokeStyle: '#013', + fillStyle: '#BADA55', + opacity: 1 }); var equalLiteral = new ol.style.PolygonLiteral({ fillStyle: '#BADA55', - strokeWidth: 3 + strokeStyle: '#013', + strokeWidth: 3, + opacity: 1 }); var differentLiteral = new ol.style.PolygonLiteral({ fillStyle: '#013', - strokeWidth: 3 + strokeStyle: '#013', + strokeWidth: 3, + opacity: 1 }); expect(literal.equals(equalLiteral)).toBe(true); expect(literal.equals(differentLiteral)).toBe(false); diff --git a/test/spec/ol/style/shape.test.js b/test/spec/ol/style/shape.test.js index fec06b87d0..4f06a7a661 100644 --- a/test/spec/ol/style/shape.test.js +++ b/test/spec/ol/style/shape.test.js @@ -6,16 +6,28 @@ describe('ol.style.ShapeLiteral', function() { it('identifies equal literals', function() { var literal = new ol.style.ShapeLiteral({ + type: ol.style.ShapeType.CIRCLE, size: 4, - fillStyle: '#BADA55' + fillStyle: '#BADA55', + strokeStyle: '#013', + strokeWidth: 3, + opacity: 1 }); var equalLiteral = new ol.style.ShapeLiteral({ + type: ol.style.ShapeType.CIRCLE, + size: 4, fillStyle: '#BADA55', - size: 4 + strokeStyle: '#013', + strokeWidth: 3, + opacity: 1 }); var differentLiteral = new ol.style.ShapeLiteral({ + type: ol.style.ShapeType.CIRCLE, + size: 4, fillStyle: '#013', - size: 4 + strokeStyle: '#013', + strokeWidth: 3, + opacity: 1 }); expect(literal.equals(equalLiteral)).toBe(true); expect(literal.equals(differentLiteral)).toBe(false); @@ -74,3 +86,4 @@ goog.require('ol.Expression'); goog.require('ol.Feature'); goog.require('ol.style.Shape'); goog.require('ol.style.ShapeLiteral'); +goog.require('ol.style.ShapeType'); diff --git a/test/spec/ol/style/style.test.js b/test/spec/ol/style/style.test.js index f5e7530549..9b76d23b60 100644 --- a/test/spec/ol/style/style.test.js +++ b/test/spec/ol/style/style.test.js @@ -5,6 +5,7 @@ describe('ol.style.Style', function() { describe('#apply()', function() { it('applies a style to a feature', function() { + var style = new ol.style.Style({ rules: [ new ol.style.Rule({ @@ -39,8 +40,10 @@ describe('ol.style.Style', function() { it('returns an array with the Shape default for points', function() { feature.setGeometry(new ol.geom.Point([0, 0])); - expect(ol.style.Style.applyDefaultStyle(feature)[0] - .equals(ol.style.ShapeDefaults)).toBe(true); + var symbolizers = ol.style.Style.applyDefaultStyle(feature); + expect(symbolizers.length).toBe(1); + expect(symbolizers[0]).toBeA(ol.style.ShapeLiteral); + expect(symbolizers[0].equals(ol.style.ShapeDefaults)).toBe(true); }); it('returns an array with the Line default for lines', function() { @@ -66,4 +69,5 @@ goog.require('ol.geom.Polygon'); goog.require('ol.filter.Filter'); goog.require('ol.style.Rule'); goog.require('ol.style.Shape'); +goog.require('ol.style.ShapeLiteral'); goog.require('ol.style.Style'); From ab19d255c74b839488d219a90bb94c89fa37ed8a Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 6 Mar 2013 01:57:08 +0100 Subject: [PATCH 28/28] Rendering icons from external graphic urls --- src/ol/renderer/canvas/canvasrenderer.js | 128 +++++++++++-- .../canvas/canvasvectorlayerrenderer.js | 22 ++- src/ol/style/icon.js | 170 ++++++++++++++++++ test/spec/ol/parser/geojson.test.js | 2 +- 4 files changed, 306 insertions(+), 16 deletions(-) create mode 100644 src/ol/style/icon.js diff --git a/src/ol/renderer/canvas/canvasrenderer.js b/src/ol/renderer/canvas/canvasrenderer.js index 4f0d0b7fb4..3c2197ec85 100644 --- a/src/ol/renderer/canvas/canvasrenderer.js +++ b/src/ol/renderer/canvas/canvasrenderer.js @@ -2,6 +2,7 @@ goog.provide('ol.renderer.canvas.Renderer'); goog.provide('ol.renderer.canvas.SUPPORTED'); goog.require('goog.asserts'); +goog.require('goog.net.ImageLoader'); goog.require('goog.vec.Mat4'); goog.require('ol.Feature'); goog.require('ol.Pixel'); @@ -11,6 +12,7 @@ goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); +goog.require('ol.style.IconLiteral'); goog.require('ol.style.LineLiteral'); goog.require('ol.style.PointLiteral'); goog.require('ol.style.PolygonLiteral'); @@ -32,10 +34,13 @@ ol.renderer.canvas.SUPPORTED = ol.canvas.SUPPORTED; * @param {HTMLCanvasElement} canvas Target canvas. * @param {goog.vec.Mat4.Number} transform Transform. * @param {ol.Pixel=} opt_offset Pixel offset for top-left corner. This is - * provided as an optional argument as a convenience in cases where the - * transform applies to a separate canvas. + * provided as an optional argument as a convenience in cases where the + * transform applies to a separate canvas. + * @param {function()=} opt_iconLoadedCallback Callback for deferred rendering + * when images need to be loaded before rendering. */ -ol.renderer.canvas.Renderer = function(canvas, transform, opt_offset) { +ol.renderer.canvas.Renderer = + function(canvas, transform, opt_offset, opt_iconLoadedCallback) { var context = /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')), @@ -70,6 +75,12 @@ ol.renderer.canvas.Renderer = function(canvas, transform, opt_offset) { */ this.context_ = context; + /** + * @type {function()|undefined} + * @private + */ + this.iconLoadedCallback_ = opt_iconLoadedCallback; + }; @@ -77,13 +88,15 @@ ol.renderer.canvas.Renderer = function(canvas, transform, opt_offset) { * @param {ol.geom.GeometryType} type Geometry type. * @param {Array.} features Array of features. * @param {ol.style.SymbolizerLiteral} symbolizer Symbolizer. + * @return {boolean} true if deferred, false if rendered. */ ol.renderer.canvas.Renderer.prototype.renderFeaturesByGeometryType = function(type, features, symbolizer) { + var deferred = false; switch (type) { case ol.geom.GeometryType.POINT: goog.asserts.assert(symbolizer instanceof ol.style.PointLiteral); - this.renderPointFeatures_( + deferred = this.renderPointFeatures_( features, /** @type {ol.style.PointLiteral} */ (symbolizer)); break; case ol.geom.GeometryType.LINESTRING: @@ -99,6 +112,7 @@ ol.renderer.canvas.Renderer.prototype.renderFeaturesByGeometryType = default: throw new Error('Rendering not implemented for geometry type: ' + type); } + return deferred; }; @@ -138,31 +152,44 @@ ol.renderer.canvas.Renderer.prototype.renderLineStringFeatures_ = /** * @param {Array.} features Array of point features. * @param {ol.style.PointLiteral} symbolizer Point symbolizer. + * @return {boolean} true if deferred, false if rendered. * @private */ ol.renderer.canvas.Renderer.prototype.renderPointFeatures_ = function(features, symbolizer) { var context = this.context_, - canvas, i, ii, point, vec; + content, alpha, i, ii, point, vec; if (symbolizer instanceof ol.style.ShapeLiteral) { - canvas = ol.renderer.canvas.Renderer.renderShape(symbolizer); + content = ol.renderer.canvas.Renderer.renderShape(symbolizer); + alpha = 1; + } else if (symbolizer instanceof ol.style.IconLiteral) { + content = ol.renderer.canvas.Renderer.renderIcon( + symbolizer, this.iconLoadedCallback_); + alpha = symbolizer.opacity; } else { throw new Error('Unsupported symbolizer: ' + symbolizer); } - var mid = canvas.width / 2; + if (goog.isNull(content)) { + return true; + } + + var midWidth = content.width / 2; + var midHeight = content.height / 2; context.save(); - context.setTransform(1, 0, 0, 1, -mid, -mid); - context.globalAlpha = 1; + context.setTransform(1, 0, 0, 1, -midWidth, -midHeight); + context.globalAlpha = alpha; for (i = 0, ii = features.length; i < ii; ++i) { point = /** @type {ol.geom.Point} */ features[i].getGeometry(); vec = goog.vec.Mat4.multVec3( this.transform_, [point.get(0), point.get(1), 0], []); - context.drawImage(canvas, vec[0], vec[1]); + context.drawImage(content, vec[0], vec[1]); } context.restore(); + + return false; }; @@ -293,3 +320,84 @@ ol.renderer.canvas.Renderer.renderShape = function(shape) { } return canvas; }; + + +/** + * @param {ol.style.IconLiteral} icon Icon literal. + * @param {function()=} opt_callback Callback which will be called when + * the icon is loaded and rendering will work without deferring. + * @return {HTMLImageElement} image element of null if deferred. + */ +ol.renderer.canvas.Renderer.renderIcon = function(icon, opt_callback) { + var url = icon.url; + var image = ol.renderer.canvas.Renderer.icons_[url]; + var deferred = false; + if (!goog.isDef(image)) { + deferred = true; + image = /** @type {HTMLImageElement} */ + (goog.dom.createElement(goog.dom.TagName.IMG)); + goog.events.listenOnce(image, goog.events.EventType.ERROR, + goog.bind(ol.renderer.canvas.Renderer.handleIconError_, null, + opt_callback), + false, ol.renderer.canvas.Renderer.renderIcon); + goog.events.listenOnce(image, goog.events.EventType.LOAD, + goog.bind(ol.renderer.canvas.Renderer.handleIconLoad_, null, + opt_callback), + false, ol.renderer.canvas.Renderer.renderIcon); + image.setAttribute('src', url); + ol.renderer.canvas.Renderer.icons_[url] = image; + } else if (!goog.isNull(image)) { + var width = icon.width, + height = icon.height; + if (goog.isDef(width) && goog.isDef(height)) { + image.width = width; + image.height = height; + } else if (goog.isDef(width)) { + image.height = width / image.width * image.height; + } else if (goog.isDef(height)) { + image.width = height / image.height * image.width; + } + } + return deferred ? null : image; +}; + + +/** + * @type {Object.} + * @private + */ +ol.renderer.canvas.Renderer.icons_ = {}; + + +/** + * @param {function()=} opt_callback Callback. + * @param {Event=} opt_event Event. + * @private + */ +ol.renderer.canvas.Renderer.handleIconError_ = + function(opt_callback, opt_event) { + if (goog.isDef(opt_event)) { + var url = opt_event.target.getAttribute('src'); + ol.renderer.canvas.Renderer.icons_[url] = null; + ol.renderer.canvas.Renderer.handleIconLoad_(opt_callback, opt_event); + } +}; + + +/** + * @param {function()=} opt_callback Callback. + * @param {Event=} opt_event Event. + * @private + */ +ol.renderer.canvas.Renderer.handleIconLoad_ = + function(opt_callback, opt_event) { + if (goog.isDef(opt_event)) { + var url = opt_event.target.getAttribute('src'); + ol.renderer.canvas.Renderer.icons_[url] = + /** @type {HTMLImageElement} */ (opt_event.target); + } + if (goog.isDef(opt_callback)) { + opt_callback(); + } +}; + diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index b3a0a191b6..a81f18d951 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -126,6 +126,15 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) { */ this.tileGrid_ = null; + /** + * @private + * @type {function()} + */ + this.requestMapRenderFrame_ = goog.bind(function() { + this.dirty_ = true; + mapRenderer.getMap().requestRenderFrame(); + }, this); + }; goog.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer); @@ -252,7 +261,7 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = sketchCanvas.height = sketchSize.height; var sketchCanvasRenderer = new ol.renderer.canvas.Renderer( - sketchCanvas, sketchTransform); + sketchCanvas, sketchTransform, undefined, this.requestMapRenderFrame_); // clear/resize final canvas var finalCanvas = this.canvas_; @@ -267,15 +276,15 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = var filters = this.geometryFilters_, numFilters = filters.length, i, geomFilter, extentFilter, type, features, - groups, group, j, numGroups; + groups, group, j, numGroups, deferred; for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + deferred = false; tileCoord = new ol.TileCoord(z, x, y); key = tileCoord.toString(); if (this.tileCache_.containsKey(key)) { tilesToRender[key] = tileCoord; } else if (!frameState.viewHints[ol.ViewHint.ANIMATING]) { - tilesToRender[key] = tileCoord; extentFilter = new ol.filter.Extent( tileGrid.getTileCoordExtent(tileCoord)); for (i = 0; i < numFilters; ++i) { @@ -288,11 +297,14 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = numGroups = groups.length; for (j = 0; j < numGroups; ++j) { group = groups[j]; - sketchCanvasRenderer.renderFeaturesByGeometryType(type, - group[0], group[1]); + deferred = sketchCanvasRenderer.renderFeaturesByGeometryType( + type, group[0], group[1]) || deferred; } } } + if (!deferred) { + tilesToRender[key] = tileCoord; + } } } } diff --git a/src/ol/style/icon.js b/src/ol/style/icon.js new file mode 100644 index 0000000000..251981b6d3 --- /dev/null +++ b/src/ol/style/icon.js @@ -0,0 +1,170 @@ +goog.provide('ol.style.Icon'); +goog.provide('ol.style.IconLiteral'); +goog.provide('ol.style.IconType'); + +goog.require('ol.Expression'); +goog.require('ol.ExpressionLiteral'); +goog.require('ol.style.Point'); +goog.require('ol.style.PointLiteral'); + + +/** + * @typedef {{url: (string), + * width: (number|undefined), + * height: (number|undefined), + * opacity: (number), + * rotation: (number)}} + */ +ol.style.IconLiteralOptions; + + + +/** + * @constructor + * @extends {ol.style.PointLiteral} + * @param {ol.style.IconLiteralOptions} config Symbolizer properties. + */ +ol.style.IconLiteral = function(config) { + + /** @type {string} */ + this.url = config.url; + + /** @type {number|undefined} */ + this.width = config.width; + + /** @type {number|undefined} */ + this.height = config.height; + + /** @type {number} */ + this.opacity = config.opacity; + + /** @type {number} */ + this.rotation = config.rotation; + +}; +goog.inherits(ol.style.IconLiteral, ol.style.PointLiteral); + + +/** + * @inheritDoc + */ +ol.style.IconLiteral.prototype.equals = function(iconLiteral) { + return this.url == iconLiteral.type && + this.width == iconLiteral.width && + this.height == iconLiteral.height && + this.opacity == iconLiteral.opacity && + this.rotation == iconLiteral.rotation; +}; + + +/** + * @typedef {{url: (string|ol.Expression), + * width: (number|ol.Expression|undefined), + * height: (number|ol.Expression|undefined), + * opacity: (number|ol.Expression|undefined), + * rotation: (number|ol.Expression|undefined)}} + */ +ol.style.IconOptions; + + + +/** + * @constructor + * @extends {ol.style.Point} + * @param {ol.style.IconOptions} options Symbolizer properties. + */ +ol.style.Icon = function(options) { + + goog.asserts.assert(options.url, 'url must be set'); + + /** + * @type {ol.Expression} + * @private + */ + this.url_ = (options.url instanceof ol.Expression) ? + options.url : new ol.ExpressionLiteral(options.url); + + /** + * @type {ol.Expression} + * @private + */ + this.width_ = !goog.isDef(options.width) ? + null : + (options.width instanceof ol.Expression) ? + options.width : new ol.ExpressionLiteral(options.width); + + /** + * @type {ol.Expression} + * @private + */ + this.height_ = !goog.isDef(options.height) ? + null : + (options.height instanceof ol.Expression) ? + options.height : new ol.ExpressionLiteral(options.height); + + /** + * @type {ol.Expression} + * @private + */ + this.opacity_ = !goog.isDef(options.opacity) ? + new ol.ExpressionLiteral(ol.style.IconDefaults.opacity) : + (options.opacity instanceof ol.Expression) ? + options.opacity : new ol.ExpressionLiteral(options.opacity); + + /** + * @type {ol.Expression} + * @private + */ + this.rotation_ = !goog.isDef(options.rotation) ? + new ol.ExpressionLiteral(ol.style.IconDefaults.rotation) : + (options.rotation instanceof ol.Expression) ? + options.rotation : new ol.ExpressionLiteral(options.rotation); + +}; + + +/** + * @inheritDoc + * @return {ol.style.IconLiteral} Literal shape symbolizer. + */ +ol.style.Icon.prototype.createLiteral = function(feature) { + var attrs = feature.getAttributes(); + + var url = /** @type {string} */ (this.url_.evaluate(feature, attrs)); + goog.asserts.assert(goog.isString(url) && url != '#', 'url must be a string'); + + var width = /** @type {number|undefined} */ (goog.isNull(this.width_) ? + undefined : this.width_.evaluate(feature, attrs)); + goog.asserts.assert(!goog.isDef(width) || goog.isNumber(width), + 'width must be undefined or a number'); + + var height = /** @type {number|undefined} */ (goog.isNull(this.height_) ? + undefined : this.height_.evaluate(feature, attrs)); + goog.asserts.assert(!goog.isDef(height) || goog.isNumber(height), + 'height must be undefined or a number'); + + var opacity = /** {@type {number} */ (this.opacity_.evaluate(feature, attrs)); + goog.asserts.assertNumber(opacity, 'opacity must be a number'); + + var rotation = + /** {@type {number} */ (this.opacity_.evaluate(feature, attrs)); + goog.asserts.assertNumber(rotation, 'rotation must be a number'); + + return new ol.style.IconLiteral({ + url: url, + width: width, + height: height, + opacity: opacity, + rotation: rotation + }); +}; + + +/** + * @type {ol.style.IconLiteral} + */ +ol.style.IconDefaults = new ol.style.IconLiteral({ + url: '#', + opacity: 1, + rotation: 0 +}); diff --git a/test/spec/ol/parser/geojson.test.js b/test/spec/ol/parser/geojson.test.js index f5e750aa4d..0153e4ee2f 100644 --- a/test/spec/ol/parser/geojson.test.js +++ b/test/spec/ol/parser/geojson.test.js @@ -185,7 +185,7 @@ describe('ol.parser.GeoJSON', function() { var callback = function(feature, type) { return lookup[type]; - } + }; var result = parser.readFeaturesFromString(text, {callback: callback}); expect(result.length).toBe(179);