From 8b83345bd2c52034d65ec7b1657d6e392334bfb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 22 Jan 2013 17:03:01 +0100 Subject: [PATCH 01/29] Add equals to Rectangle --- src/ol/rectangle.js | 10 ++++++++++ src/ol/tilerange.js | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ol/rectangle.js b/src/ol/rectangle.js index d15f1d7a18..a459d04603 100644 --- a/src/ol/rectangle.js +++ b/src/ol/rectangle.js @@ -41,6 +41,16 @@ ol.Rectangle = function(minX, minY, maxX, maxY) { }; +/** + * @param {ol.Rectangle} rectangle Rectangle. + * @return {boolean} Equals. + */ +ol.Rectangle.prototype.equals = function(rectangle) { + return this.minX == rectangle.minX && this.minY == rectangle.minY && + this.maxX == rectangle.maxX && this.maxY == rectangle.maxY; +}; + + /** * @param {ol.Rectangle} rectangle Rectangle. */ diff --git a/src/ol/tilerange.js b/src/ol/tilerange.js index 7c402f00cb..e1baa169f8 100644 --- a/src/ol/tilerange.js +++ b/src/ol/tilerange.js @@ -61,16 +61,6 @@ ol.TileRange.prototype.containsTileRange = function(tileRange) { }; -/** - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} Equals. - */ -ol.TileRange.prototype.equals = function(tileRange) { - return this.minX == tileRange.minX && tileRange.maxX == this.maxX && - this.minY == tileRange.minY && tileRange.minY == this.minY; -}; - - /** * @inheritDoc * @return {number} Height. From 49696390fceecbce5d15e4960fd3c49a64a7367e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 22 Jan 2013 17:17:08 +0100 Subject: [PATCH 02/29] Add ol.ImageUrlFunction --- src/imageurlfunction.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/imageurlfunction.js diff --git a/src/imageurlfunction.js b/src/imageurlfunction.js new file mode 100644 index 0000000000..bb328819eb --- /dev/null +++ b/src/imageurlfunction.js @@ -0,0 +1,40 @@ +goog.provide('ol.ImageUrlFunction'); +goog.provide('ol.ImageUrlFunctionType'); + +goog.require('ol.Extent'); +goog.require('ol.Size'); + + +/** + * @typedef {function(ol.Extent, ol.Size): (string|undefined)} + */ +ol.ImageUrlFunctionType; + + +/** + * @param {string} baseUrl Base URL (may have query data). + * @return {ol.ImageUrlFunctionType} Image URL function. + */ +ol.ImageUrlFunction.createBboxParam = function(baseUrl) { + return function(extent, size) { + // FIXME Projection dependant axis order. + var bboxValue = [ + extent.minX, extent.minY, extent.maxX, extent.maxY + ].join(','); + return goog.uri.utils.appendParams(baseUrl, + 'BBOX', bboxValue, + 'HEIGHT', size.height, + 'WIDTH', size.width); + }; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {ol.Size} size Size. + * @return {string|undefined} Image URL. + */ +ol.ImageUrlFunction.nullImageUrlFunction = + function(extent, size) { + return undefined; +}; From cf206a103d43e78d03072e1269428304664531c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 22 Jan 2013 17:19:01 +0100 Subject: [PATCH 03/29] Add ol.Image class --- src/ol/image.js | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/ol/image.js diff --git a/src/ol/image.js b/src/ol/image.js new file mode 100644 index 0000000000..ee81baa348 --- /dev/null +++ b/src/ol/image.js @@ -0,0 +1,173 @@ +goog.provide('ol.Image'); +goog.provide('ol.ImageState'); + +goog.require('goog.array'); +goog.require('goog.events'); +goog.require('ol.Extent'); + + +/** + * @enum {number} + */ +ol.ImageState = { + IDLE: 0, + LOADING: 1, + LOADED: 2, + ERROR: 3 +}; + + + +/** + * @constructor + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + */ +ol.Image = function(extent, resolution, src, crossOrigin) { + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = extent; + + /** + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {number} + */ + this.resolution_ = resolution; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.} + */ + this.imageListenerKeys_ = null; + + /** + * @protected + * @type {ol.ImageState} + */ + this.state = ol.ImageState.IDLE; +}; + + +/** + * @return {ol.Extent} Extent. + */ +ol.Image.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * @param {Object=} opt_context Object. + * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image. + */ +ol.Image.prototype.getImageElement = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * @return {number} Resolution. + */ +ol.Image.prototype.getResolution = function() { + return this.resolution_; +}; + + +/** + * @return {ol.ImageState} State. + */ +ol.Image.prototype.getState = function() { + return this.state; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.Image.prototype.handleImageError_ = function() { + this.state = ol.ImageState.ERROR; + this.unlistenImage_(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.Image.prototype.handleImageLoad_ = function() { + this.state = ol.ImageState.LOADED; + this.unlistenImage_(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.Image.prototype.load = function() { + if (this.state == ol.ImageState.IDLE) { + this.state = ol.ImageState.LOADING; + goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.image_.src = this.src_; + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.Image.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; From 27125640ef21ccc377e642f79ca81e3956bcdbb1 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 21 Jan 2013 17:44:21 +0100 Subject: [PATCH 04/29] Add ol.layer.ImageLayer --- src/ol/layer/imagelayer.exports | 1 + src/ol/layer/imagelayer.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/ol/layer/imagelayer.exports create mode 100644 src/ol/layer/imagelayer.js diff --git a/src/ol/layer/imagelayer.exports b/src/ol/layer/imagelayer.exports new file mode 100644 index 0000000000..f00c7ec5e5 --- /dev/null +++ b/src/ol/layer/imagelayer.exports @@ -0,0 +1 @@ +@exportClass ol.layer.ImageLayer ol.layer.LayerOptions diff --git a/src/ol/layer/imagelayer.js b/src/ol/layer/imagelayer.js new file mode 100644 index 0000000000..8b61efce0e --- /dev/null +++ b/src/ol/layer/imagelayer.js @@ -0,0 +1,24 @@ +goog.provide('ol.layer.ImageLayer'); + +goog.require('ol.layer.Layer'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.layer.Layer} + * @param {ol.layer.LayerOptions} layerOptions Layer options. + */ +ol.layer.ImageLayer = function(layerOptions) { + goog.base(this, layerOptions); +}; +goog.inherits(ol.layer.ImageLayer, ol.layer.Layer); + + +/** + * @return {ol.source.ImageSource} Single image source. + */ +ol.layer.ImageLayer.prototype.getImageSource = function() { + return /** @type {ol.source.ImageSource} */ (this.getSource()); +}; From 5a0adf2345d8fec1361028eeb217b431d80f6e6d Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 21 Jan 2013 17:44:06 +0100 Subject: [PATCH 05/29] Add ol.source.ImageSource --- src/ol/source/imagesource.js | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/ol/source/imagesource.js diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js new file mode 100644 index 0000000000..cd99fc40b1 --- /dev/null +++ b/src/ol/source/imagesource.js @@ -0,0 +1,111 @@ +goog.provide('ol.source.ImageSource'); + +goog.require('goog.array'); +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunction'); +goog.require('ol.ImageUrlFunctionType'); +goog.require('ol.Projection'); +goog.require('ol.Size'); +goog.require('ol.array'); +goog.require('ol.source.Source'); + + +/** + * @typedef {{attributions: (Array.|undefined), + * crossOrigin: (null|string|undefined), + * extent: (null|ol.Extent|undefined), + * projection: (ol.Projection|undefined), + * resolutions: (Array.|undefined), + * imageUrlFunction: (ol.ImageUrlFunctionType| + * undefined)}} + */ +ol.source.ImageSourceOptions; + + + +/** + * @constructor + * @extends {ol.source.Source} + * @param {ol.source.ImageSourceOptions} options Single + * image source options. + */ +ol.source.ImageSource = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + projection: options.projection + }); + + /** + * @protected + * @type {ol.ImageUrlFunctionType} + */ + this.imageUrlFunction = + goog.isDef(options.imageUrlFunction) ? + options.imageUrlFunction : + ol.ImageUrlFunction.nullImageUrlFunction; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : 'anonymous'; + + /** + * @private + * @type {Array.} + */ + this.resolutions_ = goog.isDef(options.resolutions) ? + options.resolutions : null; + if (!goog.isNull(this.resolutions_)) { + goog.array.sort(this.resolutions_, function(a, b) {return b - a; }); + } + +}; +goog.inherits(ol.source.ImageSource, ol.source.Source); + + +/** + * @protected + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @param {ol.Size} size Size. + * @return {ol.Image} Single image. + */ +ol.source.ImageSource.prototype.createImage = + function(extent, resolution, size) { + var image = null; + var imageUrl = this.imageUrlFunction(extent, size); + if (goog.isDef(imageUrl)) { + image = new ol.Image( + extent, resolution, imageUrl, this.crossOrigin_); + } + return image; +}; + + +/** + * @protected + * @param {number} resolution Resolution. + * @return {number} Resolution. + */ +ol.source.ImageSource.prototype.findNearestResolution = + function(resolution) { + if (!goog.isNull(this.resolutions_)) { + var idx = ol.array.linearFindNearest(this.resolutions_, resolution); + resolution = this.resolutions_[idx]; + } + return resolution; +}; + + +/** + * @param {ol.Extent} extent Extent. + * @param {number} resolution Resolution. + * @return {ol.Image} Single image. + */ +ol.source.ImageSource.prototype.getImage = goog.abstractMethod; From f581040b0f40a5c0920cc59f6dfbf7521a6a91a3 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 21 Jan 2013 17:44:52 +0100 Subject: [PATCH 06/29] Add ol.renderer.canvas.ImageLayer --- .../canvas/canvasimagelayerrenderer.js | 121 ++++++++++++++++++ src/ol/renderer/canvas/canvasmaprenderer.js | 6 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/ol/renderer/canvas/canvasimagelayerrenderer.js diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js new file mode 100644 index 0000000000..1b7857b9e2 --- /dev/null +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -0,0 +1,121 @@ +goog.provide('ol.renderer.canvas.ImageLayer'); + +goog.require('goog.vec.Mat4'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.canvas.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Single image layer. + */ +ol.renderer.canvas.ImageLayer = function(mapRenderer, imageLayer) { + + goog.base(this, mapRenderer, imageLayer); + + /** + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.transform_ = goog.vec.Mat4.createNumber(); + +}; +goog.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer); + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getImage = function() { + return goog.isNull(this.image_) ? + null : this.image_.getImageElement(this); +}; + + +/** + * @return {ol.layer.ImageLayer} Single image layer. + */ +ol.renderer.canvas.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.getTransform = function() { + return this.transform_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.canvas.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + image = imageSource.getImage( + frameState.extent, viewResolution); + var imageState = image.getState(); + var animate = false; + if (imageState == ol.ImageState.ERROR) { + // pass + } else if (imageState == ol.ImageState.IDLE) { + animate = true; + image.load(); + } else if (imageState == ol.ImageState.LOADING) { + animate = true; + } else if (imageState == ol.ImageState.LOADED) { + this.image_ = image; + } + if (animate) { + frameState.animate = true; + } + } + + if (!goog.isNull(this.image_)) { + image = this.image_; + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var transform = this.transform_; + goog.vec.Mat4.makeIdentity(transform); + goog.vec.Mat4.translate(transform, + frameState.size.width / 2, frameState.size.height / 2, 0); + goog.vec.Mat4.rotateZ(transform, viewRotation); + goog.vec.Mat4.scale( + transform, + imageResolution / viewResolution, + imageResolution / viewResolution, + 1); + goog.vec.Mat4.translate( + transform, + (imageExtent.minX - viewCenter.x) / imageResolution, + (viewCenter.y - imageExtent.maxY) / imageResolution, + 0); + } +}; diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js index bd0b3dbfe3..24ee38307f 100644 --- a/src/ol/renderer/canvas/canvasmaprenderer.js +++ b/src/ol/renderer/canvas/canvasmaprenderer.js @@ -7,8 +7,10 @@ goog.require('goog.dom'); goog.require('goog.style'); goog.require('goog.vec.Mat4'); goog.require('ol.Size'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); +goog.require('ol.renderer.canvas.ImageLayer'); goog.require('ol.renderer.canvas.TileLayer'); @@ -59,7 +61,9 @@ goog.inherits(ol.renderer.canvas.Map, ol.renderer.Map); * @inheritDoc */ ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) { - if (layer instanceof ol.layer.TileLayer) { + if (layer instanceof ol.layer.ImageLayer) { + return new ol.renderer.canvas.ImageLayer(this, layer); + } else if (layer instanceof ol.layer.TileLayer) { return new ol.renderer.canvas.TileLayer(this, layer); } else { goog.asserts.assert(false); From 35a6cac37f9486c3500576b3e0a197458285a6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 28 Jan 2013 12:25:04 +0100 Subject: [PATCH 07/29] Add containsExtent to ol.Extent --- src/ol/extent.js | 18 ++++++++- test/spec/ol/extent.test.js | 77 ++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index 3d0ab215d6..3606dfc63c 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -46,17 +46,31 @@ ol.Extent.boundingExtent = function(var_args) { /** - * Checks if the given coordinate is contained or on the edge of the extent. + * Checks if the passed coordinate is contained or on the edge + * of the extent. * * @param {ol.Coordinate} coordinate Coordinate. * @return {boolean} Contains. */ -ol.Extent.prototype.contains = function(coordinate) { +ol.Extent.prototype.containsCoordinate = function(coordinate) { return this.minX <= coordinate.x && coordinate.x <= this.maxX && this.minY <= coordinate.y && coordinate.y <= this.maxY; }; +/** + * Checks if the passed extent is contained or on the edge of the + * extent. + * + * @param {ol.Extent} extent Extent. + * @return {boolean} Contains. + */ +ol.Extent.prototype.containsExtent = function(extent) { + return this.minX <= extent.minX && extent.maxX <= this.maxX && + this.minY <= extent.minY && extent.maxY <= this.maxY; +}; + + /** * @return {ol.Coordinate} Bottom left coordinate. */ diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index 2a2d98e9f3..de00b63e18 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -2,42 +2,67 @@ goog.provide('ol.test.Extent'); describe('ol.Extent', function() { - describe('contains', function() { + describe('containsCoordinate', function() { describe('positive', function() { it('returns true', function() { var extent = new ol.Extent(1, 2, 3, 4); - expect(extent.contains(new ol.Coordinate(1, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(1, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(1, 4))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(2, 4))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 2))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 3))).toBeTruthy(); - expect(extent.contains(new ol.Coordinate(3, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 4))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 2))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 3))).toBeTruthy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 4))).toBeTruthy(); }); }); describe('negative', function() { it('returns false', function() { var extent = new ol.Extent(1, 2, 3, 4); - expect(extent.contains(new ol.Coordinate(0, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 2))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 3))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 4))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(0, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(1, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(1, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(2, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(2, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(3, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(3, 5))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 1))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 2))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 3))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 4))).toBeFalsy(); - expect(extent.contains(new ol.Coordinate(4, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 2))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 3))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 4))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(0, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(1, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(2, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(3, 5))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 1))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 2))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 3))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 4))).toBeFalsy(); + expect(extent.containsCoordinate( + new ol.Coordinate(4, 5))).toBeFalsy(); }); }); }); From aa9f820723f696f1b8868d5f1e24884b15b77357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 29 Jan 2013 15:41:27 +0100 Subject: [PATCH 08/29] Add scale to ol.Rectangle --- src/ol/rectangle.js | 13 +++++++++++++ test/spec/ol/rectangle.test.js | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/ol/rectangle.js b/src/ol/rectangle.js index a459d04603..c7d9cab121 100644 --- a/src/ol/rectangle.js +++ b/src/ol/rectangle.js @@ -124,3 +124,16 @@ ol.Rectangle.prototype.normalize = function(coordinate) { ol.Rectangle.prototype.toString = function() { return '(' + [this.minX, this.minY, this.maxX, this.maxY].join(', ') + ')'; }; + + +/** + * @param {number} value Value. + */ +ol.Rectangle.prototype.scale = function(value) { + var deltaX = (this.getWidth() / 2.0) * (value - 1); + var deltaY = (this.getHeight() / 2.0) * (value - 1); + this.minX -= deltaX; + this.minY -= deltaY; + this.maxX += deltaX; + this.maxY += deltaY; +}; diff --git a/test/spec/ol/rectangle.test.js b/test/spec/ol/rectangle.test.js index 55d8882e54..5f882621ca 100644 --- a/test/spec/ol/rectangle.test.js +++ b/test/spec/ol/rectangle.test.js @@ -98,6 +98,17 @@ describe('ol.Rectangle', function() { }); }); + describe('scale', function() { + it('scales the extent', function() { + var rectangle = new ol.Rectangle(1, 1, 3, 3); + rectangle.scale(2); + expect(rectangle.minX).toEqual(0); + expect(rectangle.minY).toEqual(0); + expect(rectangle.maxX).toEqual(4); + expect(rectangle.maxY).toEqual(4); + }); + }); + }); goog.require('ol.Coordinate'); From 62b10cf87880bc58f50ce0ceab59558104fedefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Tue, 22 Jan 2013 17:38:50 +0100 Subject: [PATCH 09/29] Add ol.source.SingleImageWMS --- src/objectliterals.exports | 10 +++ src/ol/source/singleimagewms.exports | 1 + src/ol/source/singleimagewmssource.js | 99 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/ol/source/singleimagewms.exports create mode 100644 src/ol/source/singleimagewmssource.js diff --git a/src/objectliterals.exports b/src/objectliterals.exports index cf5e830f3f..a6ce462790 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -92,6 +92,16 @@ @exportObjectLiteralProperty ol.source.DebugTileSourceOptions.projection ol.Projection|undefined @exportObjectLiteralProperty ol.source.DebugTileSourceOptions.tileGrid ol.tilegrid.TileGrid|undefined +@exportObjectLiteral ol.source.SingleImageWMSOptions +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.attributions Array.|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.crossOrigin null|string|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.extent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.SingleImageWMSOptions.params Object. +@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 @exportObjectLiteralProperty ol.source.StamenOptions.provider string diff --git a/src/ol/source/singleimagewms.exports b/src/ol/source/singleimagewms.exports new file mode 100644 index 0000000000..4d79ecdabc --- /dev/null +++ b/src/ol/source/singleimagewms.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.SingleImageWMS diff --git a/src/ol/source/singleimagewmssource.js b/src/ol/source/singleimagewmssource.js new file mode 100644 index 0000000000..ec8ce0c329 --- /dev/null +++ b/src/ol/source/singleimagewmssource.js @@ -0,0 +1,99 @@ +goog.provide('ol.source.SingleImageWMS'); + +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunction'); +goog.require('ol.Projection'); +goog.require('ol.Size'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.source.ImageSource} + * @param {ol.source.SingleImageWMSOptions} options Options. + */ +ol.source.SingleImageWMS = function(options) { + + var projection = ol.Projection.createProjection( + options.projection, 'EPSG:3857'); + var projectionExtent = projection.getExtent(); + + var extent = goog.isDef(options.extent) ? + options.extent : projectionExtent; + + var version = goog.isDef(options.version) ? + options.version : '1.3'; + + var baseParams = { + 'SERVICE': 'WMS', + 'VERSION': version, + 'REQUEST': 'GetMap', + 'STYLES': '', + 'FORMAT': 'image/png', + 'TRANSPARENT': true + }; + baseParams[version >= '1.3' ? 'CRS' : 'SRS'] = projection.getCode(); + goog.object.extend(baseParams, options.params); + + var imageUrlFunction; + if (options.url) { + var url = goog.uri.utils.appendParamsFromMap( + options.url, baseParams); + imageUrlFunction = ol.ImageUrlFunction.createBboxParam(url); + } else { + imageUrlFunction = + ol.ImageUrlFunction.nullImageUrlFunction; + } + + goog.base(this, { + attributions: options.attributions, + crossOrigin: options.crossOrigin, + extent: extent, + projection: projection, + resolutions: options.resolutions, + imageUrlFunction: imageUrlFunction + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = null; + + /** + * FIXME configurable? + * @private + * @type {number} + */ + this.ratio_ = 1.5; + +}; +goog.inherits(ol.source.SingleImageWMS, ol.source.ImageSource); + + +/** + * @inheritDoc + */ +ol.source.SingleImageWMS.prototype.getImage = + function(extent, resolution) { + resolution = this.findNearestResolution(resolution); + + var image = this.image_; + if (!goog.isNull(image) && + image.getResolution() == resolution && + image.getExtent().containsExtent(extent)) { + return image; + } + + extent = new ol.Extent(extent.minX, extent.minY, + extent.maxX, extent.maxY); + extent.scale(this.ratio_); + var width = extent.getWidth() / resolution; + var height = extent.getHeight() / resolution; + var size = new ol.Size(width, height); + + this.image_ = this.createImage(extent, resolution, size); + return this.image_; +}; From 8ae25684f9a74cb89c7e65d1950e1da04bce4419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 28 Jan 2013 13:03:56 +0100 Subject: [PATCH 10/29] Add ol.source.StaticImage --- src/objectliterals.exports | 9 +++++ src/ol/source/staticimage.exports | 1 + src/ol/source/staticimagesource.js | 61 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/ol/source/staticimage.exports create mode 100644 src/ol/source/staticimagesource.js diff --git a/src/objectliterals.exports b/src/objectliterals.exports index a6ce462790..0e24207a11 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -106,6 +106,15 @@ @exportObjectLiteralProperty ol.source.StamenOptions.flavor string|undefined @exportObjectLiteralProperty ol.source.StamenOptions.provider string +@exportObjectLiteral ol.source.StaticImageOptions +@exportObjectLiteralProperty ol.source.StaticImageOptions.attributions Array.|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.crossOrigin null|string|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.extent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.imageExtent ol.Extent|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.imageSize ol.Size|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.projection ol.Projection|undefined +@exportObjectLiteralProperty ol.source.StaticImageOptions.url string|undefined + @exportObjectLiteral ol.source.TiledWMSOptions @exportObjectLiteralProperty ol.source.TiledWMSOptions.attributions Array.|undefined @exportObjectLiteralProperty ol.source.TiledWMSOptions.params Object diff --git a/src/ol/source/staticimage.exports b/src/ol/source/staticimage.exports new file mode 100644 index 0000000000..3f7b2c98a5 --- /dev/null +++ b/src/ol/source/staticimage.exports @@ -0,0 +1 @@ +@exportSymbol ol.source.StaticImage diff --git a/src/ol/source/staticimagesource.js b/src/ol/source/staticimagesource.js new file mode 100644 index 0000000000..eafa205eb1 --- /dev/null +++ b/src/ol/source/staticimagesource.js @@ -0,0 +1,61 @@ +goog.provide('ol.source.StaticImage'); + +goog.require('ol.Image'); +goog.require('ol.ImageUrlFunctionType'); +goog.require('ol.source.ImageSource'); + + + +/** + * @constructor + * @extends {ol.source.ImageSource} + * @param {ol.source.StaticImageOptions} options Options. + */ +ol.source.StaticImage = function(options) { + + var imageFunction = ol.source.StaticImage.createImageFunction( + options.url); + + var imageExtent = options.imageExtent; + var imageSize = options.imageSize; + var imageResolution = imageExtent.getHeight() / imageSize.height; + + goog.base(this, { + attributions: options.attributions, + crossOrigin: options.crossOrigin, + extent: options.extent, + projection: options.projection, + imageUrlFunction: imageFunction, + resolutions: [imageResolution] + }); + + /** + * @private + * @type {ol.Image} + */ + this.image_ = this.createImage(imageExtent, imageResolution, imageSize); + +}; +goog.inherits(ol.source.StaticImage, ol.source.ImageSource); + + +/** + * @inheritDoc + */ +ol.source.StaticImage.prototype.getImage = function(extent, resolution) { + if (extent.intersects(this.image_.getExtent())) { + return this.image_; + } + return null; +}; + + +/** + * @param {string|undefined} url URL. + * @return {ol.ImageUrlFunctionType} Function. + */ +ol.source.StaticImage.createImageFunction = function(url) { + return function(extent, size) { + return url; + }; +}; From 238c6952650ff0913e83c1cb42d2808c1aa251a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 28 Jan 2013 13:04:35 +0100 Subject: [PATCH 11/29] Add FIXME to ol.control.Attribution --- src/ol/control/attributioncontrol.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/control/attributioncontrol.js b/src/ol/control/attributioncontrol.js index d6f707ea56..731e2b9435 100644 --- a/src/ol/control/attributioncontrol.js +++ b/src/ol/control/attributioncontrol.js @@ -1,4 +1,5 @@ // FIXME handle date line wrap +// FIXME does not handle image sources goog.provide('ol.control.Attribution'); From 17a17b01b66d441044f9ce53cb6400c483bee208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Mon, 28 Jan 2013 16:24:45 +0100 Subject: [PATCH 12/29] Add ol.renderer.dom.ImageLayer --- src/ol/renderer/dom/domimagelayerrenderer.js | 127 +++++++++++++++++++ src/ol/renderer/dom/dommaprenderer.js | 15 ++- 2 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 src/ol/renderer/dom/domimagelayerrenderer.js diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js new file mode 100644 index 0000000000..eda01022e1 --- /dev/null +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -0,0 +1,127 @@ +goog.provide('ol.renderer.dom.ImageLayer'); + +goog.require('goog.dom'); +goog.require('goog.vec.Mat4'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.dom'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.dom.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.dom.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Image layer. + */ +ol.renderer.dom.ImageLayer = function(mapRenderer, imageLayer) { + var target = goog.dom.createElement(goog.dom.TagName.DIV); + target.className = 'ol-layer-image'; + target.style.position = 'absolute'; + + goog.base(this, mapRenderer, imageLayer, target); + + /** + * The last rendered image. + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * @private + * @type {goog.vec.Mat4.AnyType} + */ + this.transform_ = goog.vec.Mat4.createNumberIdentity(); + +}; +goog.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer); + + +/** + * @return {ol.layer.ImageLayer} Image layer. + */ +ol.renderer.dom.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.dom.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image = this.image_; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + var image_ = imageSource.getImage(frameState.extent, viewResolution); + var imageState = image_.getState(); + var animate = false; + if (imageState == ol.ImageState.ERROR) { + // pass + } else if (imageState == ol.ImageState.IDLE) { + animate = true; + image_.load(); + } else if (imageState == ol.ImageState.LOADING) { + animate = true; + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + } + if (animate) { + frameState.animate = true; + } + } + + if (!goog.isNull(image)) { + var imageExtent = image.getExtent(); + var imageResolution = image.getResolution(); + var transform = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeIdentity(transform); + goog.vec.Mat4.translate(transform, + frameState.size.width / 2, frameState.size.height / 2, 0); + goog.vec.Mat4.rotateZ(transform, viewRotation); + goog.vec.Mat4.scale( + transform, + imageResolution / viewResolution, + imageResolution / viewResolution, + 1); + goog.vec.Mat4.translate( + transform, + (imageExtent.minX - viewCenter.x) / imageResolution, + (viewCenter.y - imageExtent.maxY) / imageResolution, + 0); + if (image != this.image_) { + var imageElement = image.getImageElement(this); + imageElement.style.position = 'absolute'; + goog.dom.removeChildren(this.target); + goog.dom.appendChild(this.target, imageElement); + this.image_ = image; + } + this.setTransform(transform); + } + +}; + + +/** + * @param {goog.vec.Mat4.AnyType} transform Transform. + */ +ol.renderer.dom.ImageLayer.prototype.setTransform = function(transform) { + if (!goog.vec.Mat4.equals(transform, this.transform_)) { + ol.dom.transformElement2D(this.target, transform, 6); + goog.vec.Mat4.setFromArray(this.transform_, transform); + } +}; diff --git a/src/ol/renderer/dom/dommaprenderer.js b/src/ol/renderer/dom/dommaprenderer.js index 18700d9b25..a833d5fd91 100644 --- a/src/ol/renderer/dom/dommaprenderer.js +++ b/src/ol/renderer/dom/dommaprenderer.js @@ -5,8 +5,10 @@ goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.style'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); +goog.require('ol.renderer.dom.ImageLayer'); goog.require('ol.renderer.dom.TileLayer'); @@ -57,14 +59,15 @@ ol.renderer.dom.Map.prototype.addLayer = function(layer) { * @inheritDoc */ ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) { + var layerRenderer; if (layer instanceof ol.layer.TileLayer) { - var layerRenderer = new ol.renderer.dom.TileLayer(this, layer); - goog.dom.appendChild(this.layersPane_, layerRenderer.getTarget()); - return layerRenderer; - } else { - goog.asserts.assert(false); - return null; + layerRenderer = new ol.renderer.dom.TileLayer(this, layer); + } else if (layer instanceof ol.layer.ImageLayer) { + layerRenderer = new ol.renderer.dom.ImageLayer(this, layer); } + goog.asserts.assert(goog.isDef(layerRenderer)); + goog.dom.appendChild(this.layersPane_, layerRenderer.getTarget()); + return layerRenderer; }; From 02843939e353cee321d100b0163375a2f3a15058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Wed, 6 Feb 2013 08:13:04 +0100 Subject: [PATCH 13/29] Add ol.renderer.webgl.ImageLayer --- .../renderer/webgl/webglimagelayerrenderer.js | 220 ++++++++++++++++++ src/ol/renderer/webgl/webgllayerrenderer.js | 6 + src/ol/renderer/webgl/webglmaprenderer.js | 19 +- .../renderer/webgl/webgltilelayerrenderer.js | 14 ++ 4 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 src/ol/renderer/webgl/webglimagelayerrenderer.js diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js new file mode 100644 index 0000000000..c259b02b99 --- /dev/null +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -0,0 +1,220 @@ +goog.provide('ol.renderer.webgl.ImageLayer'); + +goog.require('goog.vec.Mat4'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); +goog.require('ol.ViewHint'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.renderer.webgl.Layer'); + + + +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.Map} mapRenderer Map renderer. + * @param {ol.layer.ImageLayer} imageLayer Tile layer. + */ +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + goog.base(this, mapRenderer, imageLayer); + + /** + * The last rendered image. + * @private + * @type {?ol.Image} + */ + this.image_ = null; + + /** + * The last rendered texture. + * @private + * @type {WebGLTexture} + */ + this.texture_ = null; + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.texCoordMatrix_ = goog.vec.Mat4.createNumberIdentity(); + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.vertexCoordMatrix_ = goog.vec.Mat4.createNumber(); + +}; +goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + + +/** + * @private + * @param {ol.Image} image Image. + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImageElement(this); + var gl = this.getMapRenderer().getGL(); + + var texture = gl.createTexture(); + + gl.bindTexture(goog.webgl.TEXTURE_2D, texture); + gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, + goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement); + + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, + goog.webgl.CLAMP_TO_EDGE); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, + goog.webgl.CLAMP_TO_EDGE); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); + + return texture; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.disposeInternal = function() { + var mapRenderer = this.getMapRenderer(); + var gl = mapRenderer.getGL(); + if (!gl.isContextLost()) { + gl.deleteTexture(this.texture_); + } + goog.base(this, 'disposeInternal'); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getTexture = function() { + return this.texture_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.getVertexCoordMatrix = function() { + return this.vertexCoordMatrix_; +}; + + +/** + * @return {ol.layer.ImageLayer} Tile layer. + */ +ol.renderer.webgl.ImageLayer.prototype.getImageLayer = function() { + return /** @type {ol.layer.ImageLayer} */ (this.getLayer()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.handleWebGLContextLost = function() { + this.texture_ = null; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.renderFrame = + function(frameState, layerState) { + + var gl = this.getMapRenderer().getGL(); + + var view2DState = frameState.view2DState; + var viewCenter = view2DState.center; + var viewResolution = view2DState.resolution; + var viewRotation = view2DState.rotation; + + var image = this.image_; + var texture = this.texture_; + var imageLayer = this.getImageLayer(); + var imageSource = imageLayer.getImageSource(); + + var hints = frameState.viewHints; + + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { + var image_ = imageSource.getImage(frameState.extent, viewResolution); + var imageState = image_.getState(); + var animate = false; + if (imageState == ol.ImageState.ERROR) { + // pass + } else if (imageState == ol.ImageState.IDLE) { + animate = true; + image_.load(); + } else if (imageState == ol.ImageState.LOADING) { + animate = true; + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + texture = this.createTexture_(image_); + if (!goog.isNull(this.texture_)) { + frameState.postRenderFunctions.push( + goog.partial(function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }, gl, this.texture_)); + } + } + if (animate) { + frameState.animate = true; + } + } + + if (!goog.isNull(image)) { + goog.asserts.assert(!goog.isNull(texture)); + + var canvas = this.getMapRenderer().getCanvas(); + + var canvasExtentWidth = canvas.width * viewResolution; + var canvasExtentHeight = canvas.height * viewResolution; + + var imageExtent = image.getExtent(); + + var vertexCoordMatrix = this.vertexCoordMatrix_; + goog.vec.Mat4.makeIdentity(vertexCoordMatrix); + goog.vec.Mat4.scale(vertexCoordMatrix, + 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); + goog.vec.Mat4.rotateZ(vertexCoordMatrix, -viewRotation); + goog.vec.Mat4.translate(vertexCoordMatrix, + imageExtent.minX - viewCenter.x, + imageExtent.minY - viewCenter.y, + 0); + goog.vec.Mat4.scale(vertexCoordMatrix, + imageExtent.getWidth() / 2, imageExtent.getHeight() / 2, 1); + goog.vec.Mat4.translate(vertexCoordMatrix, 1, 1, 0); + + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix_; + goog.vec.Mat4.makeIdentity(texCoordMatrix); + goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1); + goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0); + + this.image_ = image; + this.texture_ = texture; + } +}; diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 238462410f..067a4e054c 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -97,6 +97,12 @@ ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = goog.abstractMethod; ol.renderer.webgl.Layer.prototype.getTexture = goog.abstractMethod; +/** + * @return {!goog.vec.Mat4.Number} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getVertexCoordMatrix = goog.abstractMethod; + + /** * @inheritDoc */ diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index f7a553f3f2..62b03fc489 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -14,9 +14,11 @@ goog.require('goog.webgl'); goog.require('ol.FrameState'); goog.require('ol.Size'); goog.require('ol.Tile'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.Map'); goog.require('ol.renderer.webgl.FragmentShader'); +goog.require('ol.renderer.webgl.ImageLayer'); goog.require('ol.renderer.webgl.TileLayer'); goog.require('ol.renderer.webgl.VertexShader'); goog.require('ol.structs.LRUCache'); @@ -79,11 +81,12 @@ ol.renderer.webgl.map.shader.Vertex = function() { 'attribute vec2 aTexCoord;', '', 'uniform mat4 uTexCoordMatrix;', + 'uniform mat4 uVertexCoordMatrix;', '', 'varying vec2 vTexCoord;', '', 'void main(void) {', - ' gl_Position = vec4(aPosition, 0., 1.);', + ' gl_Position = uVertexCoordMatrix * vec4(aPosition, 0., 1.);', ' vTexCoord = (uTexCoordMatrix * vec4(aTexCoord, 0., 1.)).st;', '}' ].join('\n')); @@ -159,7 +162,8 @@ ol.renderer.webgl.Map = function(container, map) { * uColorMatrix: WebGLUniformLocation, * uOpacity: WebGLUniformLocation, * uTexture: WebGLUniformLocation, - * uTexCoordMatrix: WebGLUniformLocation}|null} + * uTexCoordMatrix: WebGLUniformLocation, + * uVertexCoordMatrix: WebGLUniformLocation}|null} */ this.locations_ = null; @@ -270,12 +274,15 @@ ol.renderer.webgl.Map.prototype.bindTileTexture = * @inheritDoc */ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) { + var layerRenderer = null; if (layer instanceof ol.layer.TileLayer) { - return new ol.renderer.webgl.TileLayer(this, layer); + layerRenderer = new ol.renderer.webgl.TileLayer(this, layer); + } else if (layer instanceof ol.layer.ImageLayer) { + layerRenderer = new ol.renderer.webgl.ImageLayer(this, layer); } else { goog.asserts.assert(false); - return null; } + return layerRenderer; }; @@ -519,6 +526,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { aTexCoord: gl.getAttribLocation(program, 'aTexCoord'), uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), uTexCoordMatrix: gl.getUniformLocation(program, 'uTexCoordMatrix'), + uVertexCoordMatrix: gl.getUniformLocation(program, 'uVertexCoordMatrix'), uOpacity: gl.getUniformLocation(program, 'uOpacity'), uTexture: gl.getUniformLocation(program, 'uTexture') }; @@ -555,6 +563,9 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { gl.uniformMatrix4fv( this.locations_.uTexCoordMatrix, false, layerRenderer.getTexCoordMatrix()); + gl.uniformMatrix4fv( + this.locations_.uVertexCoordMatrix, false, + layerRenderer.getVertexCoordMatrix()); gl.uniformMatrix4fv( this.locations_.uColorMatrix, false, layerRenderer.getColorMatrix()); gl.uniform1f(this.locations_.uOpacity, layer.getOpacity()); diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 8f1dab302a..360f8f8d82 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -141,6 +141,12 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { */ this.texCoordMatrix_ = goog.vec.Mat4.createNumber(); + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.vertexCoordMatrix_ = goog.vec.Mat4.createNumberIdentity(); + /** * @private * @type {ol.TileRange} @@ -237,6 +243,14 @@ ol.renderer.webgl.TileLayer.prototype.getTexture = function() { }; +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.getVertexCoordMatrix = function() { + return this.vertexCoordMatrix_; +}; + + /** * @return {ol.layer.TileLayer} Tile layer. */ From 3d0682a4d49f4ab9f1be8f7cd95bedb77ef82c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Thu, 14 Feb 2013 23:26:03 +0100 Subject: [PATCH 14/29] Use an ImageLayer in wms-custom-proj example --- examples/wms-custom-proj.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/wms-custom-proj.js b/examples/wms-custom-proj.js index d389451935..fea7b563dd 100644 --- a/examples/wms-custom-proj.js +++ b/examples/wms-custom-proj.js @@ -10,7 +10,9 @@ goog.require('ol.Projection'); goog.require('ol.ProjectionUnits'); goog.require('ol.RendererHints'); goog.require('ol.View2D'); +goog.require('ol.layer.ImageLayer'); goog.require('ol.layer.TileLayer'); +goog.require('ol.source.SingleImageWMS'); goog.require('ol.source.TiledWMS'); @@ -24,6 +26,17 @@ 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 give the single image source a set of resolutions. This prevents the +// source from requesting images of arbitrary resolutions. +var projectionExtent = epsg21781.getExtent(); +var maxResolution = Math.max( + projectionExtent.maxX - projectionExtent.minX, + projectionExtent.maxY - projectionExtent.minY) / 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({ @@ -41,8 +54,8 @@ var layers = new ol.Collection([ extent: extent }) }), - new ol.layer.TileLayer({ - source: new ol.source.TiledWMS({ + new ol.layer.ImageLayer({ + source: new ol.source.SingleImageWMS({ url: 'http://wms.geo.admin.ch/', attributions: [new ol.Attribution( '© ' + @@ -50,7 +63,7 @@ var layers = new ol.Collection([ 'National parks / geo.admin.ch')], params: {'LAYERS': 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung'}, projection: epsg21781, - extent: extent + resolutions: resolutions }) }) ]); From e6ed1c1a8976aa4452c3e6a4090c2e713b8ed13b Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Fri, 15 Feb 2013 00:45:13 +0100 Subject: [PATCH 15/29] Temporarily work around pake's eager evaluation of variables in target names --- build.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/build.py b/build.py index 7a5f049e36..6c09e4928b 100755 --- a/build.py +++ b/build.py @@ -22,14 +22,17 @@ if sys.platform == 'win32': variables.JSDOC = 'jsdoc' # FIXME variables.PYTHON = os.path.join(Python27, 'python.exe') PHANTOMJS_WINDOWS_ZIP = 'build/phantomjs-1.8.1-windows.zip' - PHANTOMJS = 'build/phantomjs-1.8.1-windows/phantomjs.exe' + # FIXME we should not need both a pake variable and a Python constant here + # FIXME this requires pake to be modified to lazily evaluate variables in target names + variables.PHANTOMJS = 'build/phantomjs-1.8.1-windows/phantomjs.exe' + PHANTOMJS = variables.PHANTOMJS else: variables.GIT = 'git' variables.GJSLINT = 'gjslint' variables.JAVA = 'java' variables.JSDOC = 'jsdoc' variables.PYTHON = 'python' - PHANTOMJS = 'phantomjs' + variables.PHANTOMJS = 'phantomjs' variables.BRANCH = output('%(GIT)s', 'rev-parse', '--abbrev-ref', 'HEAD').strip() @@ -335,12 +338,12 @@ def hostexamples(t): t.cp('examples/example-list.js', 'examples/example-list.xml', 'examples/Jugl.js', 'build/gh-pages/%(BRANCH)s/examples/') -@target('test', PHANTOMJS, INTERNAL_SRC, 'test/requireall.js', phony=True) -def test(t): - t.run(PHANTOMJS, 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') - - if sys.platform == 'win32': + @target('test', '%(PHANTOMJS)s', INTERNAL_SRC, 'test/requireall.js', phony=True) + def test(t): + t.run(PHANTOMJS, 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') + + # FIXME the PHANTOMJS should be a pake variable, not a constant @target(PHANTOMJS, PHANTOMJS_WINDOWS_ZIP, clean=False) def phantom_js(t): from zipfile import ZipFile @@ -351,7 +354,9 @@ if sys.platform == 'win32': t.download('http://phantomjs.googlecode.com/files/' + os.path.basename(t.name)) else: - virtual(PHANTOMJS) + @target('test', INTERNAL_SRC, 'test/requireall.js', phony=True) + def test(t): + t.run('%(PHANTOMJS)s', 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') @target('fixme', phony=True) From e1505abe0db6c8253d318e9fcc675859fcabb24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Fri, 15 Feb 2013 17:43:27 +0100 Subject: [PATCH 16/29] Rename ol.Rectangle scale to scaleFromCenter --- src/ol/rectangle.js | 2 +- src/ol/source/singleimagewmssource.js | 2 +- test/spec/ol/rectangle.test.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ol/rectangle.js b/src/ol/rectangle.js index c7d9cab121..9d7a27af5e 100644 --- a/src/ol/rectangle.js +++ b/src/ol/rectangle.js @@ -129,7 +129,7 @@ ol.Rectangle.prototype.toString = function() { /** * @param {number} value Value. */ -ol.Rectangle.prototype.scale = function(value) { +ol.Rectangle.prototype.scaleFromCenter = function(value) { var deltaX = (this.getWidth() / 2.0) * (value - 1); var deltaY = (this.getHeight() / 2.0) * (value - 1); this.minX -= deltaX; diff --git a/src/ol/source/singleimagewmssource.js b/src/ol/source/singleimagewmssource.js index ec8ce0c329..3529678443 100644 --- a/src/ol/source/singleimagewmssource.js +++ b/src/ol/source/singleimagewmssource.js @@ -89,7 +89,7 @@ ol.source.SingleImageWMS.prototype.getImage = extent = new ol.Extent(extent.minX, extent.minY, extent.maxX, extent.maxY); - extent.scale(this.ratio_); + extent.scaleFromCenter(this.ratio_); var width = extent.getWidth() / resolution; var height = extent.getHeight() / resolution; var size = new ol.Size(width, height); diff --git a/test/spec/ol/rectangle.test.js b/test/spec/ol/rectangle.test.js index 5f882621ca..fe7ee9a1c3 100644 --- a/test/spec/ol/rectangle.test.js +++ b/test/spec/ol/rectangle.test.js @@ -98,10 +98,10 @@ describe('ol.Rectangle', function() { }); }); - describe('scale', function() { - it('scales the extent', function() { + describe('scaleFromCenter', function() { + it('scales the extent from its center', function() { var rectangle = new ol.Rectangle(1, 1, 3, 3); - rectangle.scale(2); + rectangle.scaleFromCenter(2); expect(rectangle.minX).toEqual(0); expect(rectangle.minY).toEqual(0); expect(rectangle.maxX).toEqual(4); From 1e4229497d17f04585a8b4792c998bfc6daf1e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Fri, 15 Feb 2013 17:52:06 +0100 Subject: [PATCH 17/29] Do not sort resolutions array We assert that the resolutions array is sorted instead of sorting it ourselves. This is to consistent with ol.TileGrid. --- src/ol/source/imagesource.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js index cd99fc40b1..caad04b5e9 100644 --- a/src/ol/source/imagesource.js +++ b/src/ol/source/imagesource.js @@ -61,9 +61,11 @@ ol.source.ImageSource = function(options) { */ this.resolutions_ = goog.isDef(options.resolutions) ? options.resolutions : null; - if (!goog.isNull(this.resolutions_)) { - goog.array.sort(this.resolutions_, function(a, b) {return b - a; }); - } + goog.asserts.assert(goog.isNull(this.resolutions_) || + goog.array.isSorted(this.resolutions_, + function(a, b) { + return b - a; + }, true)); }; goog.inherits(ol.source.ImageSource, ol.source.Source); From a3c65978d1a319121fab33c7dd95cb4ec8607d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Fri, 15 Feb 2013 17:57:05 +0100 Subject: [PATCH 18/29] Make ol.Image emit change events --- src/ol/image.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ol/image.js b/src/ol/image.js index ee81baa348..9f7cbe0c47 100644 --- a/src/ol/image.js +++ b/src/ol/image.js @@ -3,6 +3,8 @@ goog.provide('ol.ImageState'); goog.require('goog.array'); goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); goog.require('ol.Extent'); @@ -20,6 +22,7 @@ ol.ImageState = { /** * @constructor + * @extends {goog.events.EventTarget} * @param {ol.Extent} extent Extent. * @param {number} resolution Resolution. * @param {string} src Image source URI. @@ -72,6 +75,15 @@ ol.Image = function(extent, resolution, src, crossOrigin) { */ this.state = ol.ImageState.IDLE; }; +goog.inherits(ol.Image, goog.events.EventTarget); + + +/** + * @protected + */ +ol.Image.prototype.dispatchChangeEvent = function() { + this.dispatchEvent(goog.events.EventType.CHANGE); +}; /** @@ -129,6 +141,7 @@ ol.Image.prototype.getState = function() { ol.Image.prototype.handleImageError_ = function() { this.state = ol.ImageState.ERROR; this.unlistenImage_(); + this.dispatchChangeEvent(); }; @@ -140,6 +153,7 @@ ol.Image.prototype.handleImageError_ = function() { ol.Image.prototype.handleImageLoad_ = function() { this.state = ol.ImageState.LOADED; this.unlistenImage_(); + this.dispatchChangeEvent(); }; From 544f399e04d1716e95210240ade2d06d7bbd8eb2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Fri, 15 Feb 2013 17:55:29 +0100 Subject: [PATCH 19/29] Add spherical geometry functions --- src/ol/sphere.js | 189 +++++++++++++ test/spec/ol/sphere.test.js | 551 ++++++++++++++++++++++++++++++++++++ 2 files changed, 740 insertions(+) create mode 100644 src/ol/sphere.js create mode 100644 test/spec/ol/sphere.test.js diff --git a/src/ol/sphere.js b/src/ol/sphere.js new file mode 100644 index 0000000000..70c154ca64 --- /dev/null +++ b/src/ol/sphere.js @@ -0,0 +1,189 @@ +/** + * @license + * Latitude/longitude spherical geodesy formulae taken from + * http://www.movable-type.co.uk/scripts/latlong.html + * Licenced under CC-BY-3.0. + */ + +// FIXME add intersection of two paths given start points and bearings +// FIXME add rhumb lines + +goog.provide('ol.Sphere'); + +goog.require('goog.math'); +goog.require('ol.Coordinate'); + + + +/** + * @constructor + * @param {number} radius Radius. + */ +ol.Sphere = function(radius) { + + /** + * @type {number} + */ + this.radius = radius; + +}; + + +/** + * Returns the distance from c1 to c2 using the spherical law of cosines. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Spherical law of cosines distance. + */ +ol.Sphere.prototype.cosineDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var deltaLon = goog.math.toRadians(c2.x - c1.x); + return this.radius * Math.acos( + Math.sin(lat1) * Math.sin(lat2) + + Math.cos(lat1) * Math.cos(lat2) * Math.cos(deltaLon)); +}; + + +/** + * Returns the distance of c3 from the great circle path defined by c1 and c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @param {ol.Coordinate} c3 Coordinate 3. + * @return {number} Cross-track distance. + */ +ol.Sphere.prototype.crossTrackDistance = function(c1, c2, c3) { + var d12 = this.cosineDistance(c1, c2); + var d13 = this.cosineDistance(c1, c2); + var theta12 = goog.math.toRadians(this.initialBearing(c1, c2)); + var theta13 = goog.math.toRadians(this.initialBearing(c1, c3)); + return this.radius * + Math.asin(Math.sin(d13 / this.radius) * Math.sin(theta13 - theta12)); +}; + + +/** + * Returns the distance from c1 to c2 using Pythagoras's theorem on an + * equirectangular projection. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Equirectangular distance. + */ +ol.Sphere.prototype.equirectangularDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var deltaLon = goog.math.toRadians(c2.x - c1.x); + var x = deltaLon * Math.cos((lat1 + lat2) / 2); + var y = lat2 - lat1; + return this.radius * Math.sqrt(x * x + y * y); +}; + + +/** + * Returns the final bearing from c1 to c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Initial bearing. + */ +ol.Sphere.prototype.finalBearing = function(c1, c2) { + return (this.initialBearing(c2, c1) + 180) % 360; +}; + + +/** + * Returns the distance from c1 to c2 using the haversine formula. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Haversine distance. + */ +ol.Sphere.prototype.haversineDistance = function(c1, c2) { + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var deltaLatBy2 = (lat2 - lat1) / 2; + var deltaLonBy2 = goog.math.toRadians(c2.x - c1.x) / 2; + var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * + Math.cos(lat1) * Math.cos(lat2); + return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); +}; + + +/** + * Returns the initial bearing from c1 to c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {number} Initial bearing. + */ +ol.Sphere.prototype.initialBearing = function(c1, c2) { + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var deltaLon = goog.math.toRadians(c2.x - c1.x); + var y = Math.sin(deltaLon) * Math.cos(lat2); + var x = Math.cos(lat1) * Math.sin(lat2) - + Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon); + return goog.math.toDegrees(Math.atan2(y, x)); +}; + + +/** + * Returns the maximum latitude of the great circle defined by bearing and + * latitude. + * + * @param {number} bearing Bearing. + * @param {number} latitude Latitude. + * @return {number} Maximum latitude. + */ +ol.Sphere.prototype.maximumLatitude = function(bearing, latitude) { + return Math.cos(Math.abs(Math.sin(goog.math.toRadians(bearing)) * + Math.cos(goog.math.toRadians(latitude)))); +}; + + +/** + * Returns the midpoint between c1 and c2. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 2. + * @return {ol.Coordinate} Midpoint. + */ +ol.Sphere.prototype.midpoint = function(c1, c2) { + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var lon1 = goog.math.toRadians(c1.x); + var deltaLon = goog.math.toRadians(c2.x - c1.x); + var Bx = Math.cos(lat2) * Math.cos(deltaLon); + var By = Math.cos(lat2) * Math.sin(deltaLon); + var cosLat1PlusBx = Math.cos(lat1) + Bx; + var lat = Math.atan2(Math.sin(lat1) + Math.sin(lat2), + Math.sqrt(cosLat1PlusBx * cosLat1PlusBx + By * By)); + var lon = lon1 + Math.atan2(By, cosLat1PlusBx); + return new ol.Coordinate(goog.math.toDegrees(lon), goog.math.toDegrees(lat)); +}; + + +/** + * Returns the coordinate at the given distance and bearing from c. + * + * @param {ol.Coordinate} c1 Coordinate. + * @param {number} distance Distance. + * @param {number} bearing Bearing. + * @return {ol.Coordinate} Coordinate. + */ +ol.Sphere.prototype.offset = function(c1, distance, bearing) { + var lat1 = goog.math.toRadians(c1.y); + var lon1 = goog.math.toRadians(c1.x); + var dByR = distance / this.radius; + var lat = Math.asin( + Math.sin(lat1) * Math.cos(dByR) + + Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing)); + var lon = lon1 + Math.atan2( + Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1), + Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat)); + return new ol.Coordinate(goog.math.toDegrees(lon), goog.math.toDegrees(lat)); +}; diff --git a/test/spec/ol/sphere.test.js b/test/spec/ol/sphere.test.js new file mode 100644 index 0000000000..ee5b9b6dd5 --- /dev/null +++ b/test/spec/ol/sphere.test.js @@ -0,0 +1,551 @@ +// See http://www.movable-type.co.uk/scripts/latlong.html +// FIXME add tests for crossTrackDistance +// FIXME add tests for maximumLatitude +// FIXME add tests for offset + +goog.provide('ol.test.Sphere'); + + +describe('ol.Sphere', function() { + + var sphere = new ol.Sphere(6371); + var expected = [ + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(0, 0), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(0, 0) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(45, 45), + cosineDistance: 6671.695598673524, + equirectangularDistance: 6812.398372654371, + finalBearing: 54.735610317245346, + haversineDistance: 6671.695598673525, + initialBearing: 35.264389682754654, + midpoint: new ol.Coordinate(18.434948822922006, 24.0948425521107) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-45, 45), + cosineDistance: 6671.695598673524, + equirectangularDistance: 6812.398372654371, + finalBearing: 305.26438968275465, + haversineDistance: 6671.695598673525, + initialBearing: -35.264389682754654, + midpoint: new ol.Coordinate(-18.434948822922006, 24.0948425521107) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-45, -45), + cosineDistance: 6671.695598673524, + equirectangularDistance: 6812.398372654371, + finalBearing: 234.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: -144.73561031724535, + midpoint: new ol.Coordinate(-18.434948822922006, -24.0948425521107) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(45, -45), + cosineDistance: 6671.695598673524, + equirectangularDistance: 6812.398372654371, + finalBearing: 125.26438968275465, + haversineDistance: 6671.695598673525, + initialBearing: 144.73561031724535, + midpoint: new ol.Coordinate(18.434948822922006, -24.0948425521107) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(90, 180), + cosineDistance: 10007.543398010286, + equirectangularDistance: 20015.086796020572, + finalBearing: 90, + haversineDistance: 10007.543398010288, + initialBearing: -90, + midpoint: new ol.Coordinate(-45.00000000000005, 4.961398865471767e-15) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 10007.543398010286, + equirectangularDistance: 20015.086796020572, + finalBearing: 270, + haversineDistance: 10007.543398010288, + initialBearing: 90, + midpoint: new ol.Coordinate(45.00000000000005, 4.961398865471767e-15) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(90, -180), + cosineDistance: 10007.543398010286, + equirectangularDistance: 20015.086796020572, + finalBearing: 90, + haversineDistance: 10007.543398010288, + initialBearing: -90.00000000000001, + midpoint: new ol.Coordinate(-45.00000000000005, -4.961398865471767e-15) + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(90, -180), + cosineDistance: 10007.543398010286, + equirectangularDistance: 20015.086796020572, + finalBearing: 90, + haversineDistance: 10007.543398010288, + initialBearing: -90.00000000000001, + midpoint: new ol.Coordinate(-45.00000000000005, -4.961398865471767e-15) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(45, 45), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(45.00000000000005, 45) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-45, 45), + cosineDistance: 6671.695598673525, + equirectangularDistance: 7076.401799751738, + finalBearing: 234.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: -54.73561031724535, + midpoint: new ol.Coordinate(0, 54.735610317245346) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-45, -45), + cosineDistance: 13343.391197347048, + equirectangularDistance: 14152.803599503475, + finalBearing: 234.73561031724535, + haversineDistance: 13343.391197347048, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(0, 0) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(45, -45), + cosineDistance: 10007.543398010284, + equirectangularDistance: 10007.543398010286, + finalBearing: 180, + haversineDistance: 10007.543398010286, + initialBearing: 180, + midpoint: new ol.Coordinate(45.00000000000005, 0) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(90, 180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 15132.953174634127, + finalBearing: 35.264389682754654, + haversineDistance: 13343.391197347048, + initialBearing: -54.735610317245346, + midpoint: new ol.Coordinate(-45.00000000000005, 45.00000000000001) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 6671.695598673524, + equirectangularDistance: 16072.9523901477, + finalBearing: 324.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: 125.26438968275465, + midpoint: new ol.Coordinate(71.56505117707799, 24.094842552110702) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 25092.03003421417, + finalBearing: 35.264389682754654, + haversineDistance: 13343.391197347048, + initialBearing: -54.735610317245346, + midpoint: new ol.Coordinate(-45.00000000000005, 45) + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 25092.03003421417, + finalBearing: 35.264389682754654, + haversineDistance: 13343.391197347048, + initialBearing: -54.735610317245346, + midpoint: new ol.Coordinate(-45.00000000000005, 45) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-45, 45), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-45.00000000000005, 45) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-45, -45), + cosineDistance: 10007.543398010284, + equirectangularDistance: 10007.543398010286, + finalBearing: 180, + haversineDistance: 10007.543398010286, + initialBearing: 180, + midpoint: new ol.Coordinate(-45.00000000000005, 0) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(45, -45), + cosineDistance: 13343.391197347048, + equirectangularDistance: 14152.803599503475, + finalBearing: 125.26438968275465, + haversineDistance: 13343.391197347048, + initialBearing: 125.26438968275465, + midpoint: new ol.Coordinate(0, 0) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(90, 180), + cosineDistance: 6671.695598673524, + equirectangularDistance: 16072.9523901477, + finalBearing: 35.264389682754654, + haversineDistance: 6671.695598673525, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-71.56505117707799, 24.094842552110702) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 15132.953174634127, + finalBearing: 324.73561031724535, + haversineDistance: 13343.391197347048, + initialBearing: 54.735610317245346, + midpoint: new ol.Coordinate(45.00000000000005, 45.00000000000001) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 6671.695598673525, + equirectangularDistance: 25669.894779453065, + finalBearing: 35.264389682754654, + haversineDistance: 6671.695598673525, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-71.56505117707799, 24.0948425521107) + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 6671.695598673525, + equirectangularDistance: 25669.894779453065, + finalBearing: 35.264389682754654, + haversineDistance: 6671.695598673525, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-71.56505117707799, 24.0948425521107) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-45, -45), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-45.00000000000005, -45) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(45, -45), + cosineDistance: 6671.695598673525, + equirectangularDistance: 7076.401799751738, + finalBearing: 54.735610317245346, + haversineDistance: 6671.695598673525, + initialBearing: 125.26438968275465, + midpoint: new ol.Coordinate(0, -54.735610317245346) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(90, 180), + cosineDistance: 6671.695598673525, + equirectangularDistance: 25669.894779453065, + finalBearing: 144.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: -54.735610317245346, + midpoint: new ol.Coordinate(-71.56505117707799, -24.0948425521107) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 25092.03003421417, + finalBearing: 215.26438968275465, + haversineDistance: 13343.391197347048, + initialBearing: 125.26438968275465, + midpoint: new ol.Coordinate(45.00000000000005, -45) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 6671.695598673524, + equirectangularDistance: 16072.9523901477, + finalBearing: 144.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: -54.73561031724536, + midpoint: new ol.Coordinate(-71.56505117707799, -24.094842552110702) + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 6671.695598673524, + equirectangularDistance: 16072.9523901477, + finalBearing: 144.73561031724535, + haversineDistance: 6671.695598673525, + initialBearing: -54.73561031724536, + midpoint: new ol.Coordinate(-71.56505117707799, -24.094842552110702) + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(45, -45), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(45.00000000000005, -45) + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(90, 180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 25092.03003421417, + finalBearing: 144.73561031724535, + haversineDistance: 13343.391197347048, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-45.00000000000005, -45) + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 6671.695598673525, + equirectangularDistance: 25669.894779453065, + finalBearing: 215.26438968275465, + haversineDistance: 6671.695598673525, + initialBearing: 54.735610317245346, + midpoint: new ol.Coordinate(71.56505117707799, -24.0948425521107) + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 15132.953174634127, + finalBearing: 144.73561031724535, + haversineDistance: 13343.391197347048, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-45.00000000000005, -45.00000000000001) + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(90, -180), + cosineDistance: 13343.39119734705, + equirectangularDistance: 15132.953174634127, + finalBearing: 144.73561031724535, + haversineDistance: 13343.391197347048, + initialBearing: -125.26438968275465, + midpoint: new ol.Coordinate(-45.00000000000005, -45.00000000000001) + }, + { + c1: new ol.Coordinate(90, 180), + c2: new ol.Coordinate(90, 180), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, 7.0164775638926606e-15) + }, + { + c1: new ol.Coordinate(90, 180), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 20015.086796020572, + equirectangularDistance: 20015.086796020572, + finalBearing: 26.565051177077976, + haversineDistance: 20015.086796020572, + initialBearing: 153.43494882292202, + midpoint: new ol.Coordinate(-180, 63.43494882292201) + }, + { + c1: new ol.Coordinate(90, 180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 0, + equirectangularDistance: 40030.173592041145, + finalBearing: 0, + haversineDistance: 1.5603934160404731e-12, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, 0) + }, + { + c1: new ol.Coordinate(90, 180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 0, + equirectangularDistance: 40030.173592041145, + finalBearing: 0, + haversineDistance: 1.5603934160404731e-12, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, 0) + }, + { + c1: new ol.Coordinate(-90, 180), + c2: new ol.Coordinate(-90, 180), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(90, 7.0164775638926606e-15) + }, + { + c1: new ol.Coordinate(-90, 180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 20015.086796020572, + equirectangularDistance: 44755.09465146047, + finalBearing: 270, + haversineDistance: 20015.086796020572, + initialBearing: -90, + midpoint: new ol.Coordinate(-180, 0) + }, + { + c1: new ol.Coordinate(-90, 180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 20015.086796020572, + equirectangularDistance: 44755.09465146047, + finalBearing: 270, + haversineDistance: 20015.086796020572, + initialBearing: -90, + midpoint: new ol.Coordinate(-180, 0) + }, + { + c1: new ol.Coordinate(90, -180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, -7.0164775638926606e-15) + }, + { + c1: new ol.Coordinate(90, -180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, -7.0164775638926606e-15) + }, + { + c1: new ol.Coordinate(90, -180), + c2: new ol.Coordinate(90, -180), + cosineDistance: 0, + equirectangularDistance: 0, + finalBearing: 180, + haversineDistance: 0, + initialBearing: 0, + midpoint: new ol.Coordinate(-90, -7.0164775638926606e-15) + } + ]; + + describe('cosineDistance', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + expect(sphere.cosineDistance(e.c1, e.c2)).toRoughlyEqual( + e.cosineDistance, 1e-9); + } + }); + + }); + + describe('equirectangularDistance', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + expect(sphere.equirectangularDistance(e.c1, e.c2)).toRoughlyEqual( + e.equirectangularDistance, 1e-9); + } + }); + + }); + + describe('finalBearing', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + expect(sphere.finalBearing(e.c1, e.c2)).toRoughlyEqual( + e.finalBearing, 1e-9); + } + }); + + }); + + describe('haversineDistance', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + expect(sphere.haversineDistance(e.c1, e.c2)).toRoughlyEqual( + e.haversineDistance, 1e-9); + } + }); + + }); + + describe('initialBearing', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + expect(sphere.initialBearing(e.c1, e.c2)).toRoughlyEqual( + e.initialBearing, 1e-9); + } + }); + + }); + + describe('midpoint', function() { + + it('results match Chris Veness\'s reference implementation', function() { + var e, i; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + midpoint = sphere.midpoint(e.c1, e.c2); + // Test modulo 360 to avoid unnecessary expensive modulo operations + // in our implementation. + expect(goog.math.modulo(midpoint.x, 360)).toRoughlyEqual( + goog.math.modulo(e.midpoint.x, 360), 1e-9); + expect(midpoint.y).toRoughlyEqual(e.midpoint.y, 1e-9); + } + }); + + }); + +}); + + +goog.require('ol.Coordinate'); +goog.require('ol.Sphere'); From 7de6cb4fd414056443e3d1d0299da4e6e70fc5a3 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 16 Feb 2013 20:19:38 +0100 Subject: [PATCH 20/29] Add remaining geolocation properties --- src/ol/geolocation.js | 97 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/src/ol/geolocation.js b/src/ol/geolocation.js index b75aaaf38c..e9271bebde 100644 --- a/src/ol/geolocation.js +++ b/src/ol/geolocation.js @@ -15,8 +15,12 @@ goog.require('ol.Projection'); */ ol.GeolocationProperty = { ACCURACY: 'accuracy', + ALTITUDE: 'altitude', + ALTITUDE_ACCURACY: 'altitudeAccuracy', + HEADING: 'heading', POSITION: 'position', - PROJECTION: 'projection' + PROJECTION: 'projection', + SPEED: 'speed' }; @@ -92,10 +96,19 @@ ol.Geolocation.isSupported = 'geolocation' in navigator; */ ol.Geolocation.prototype.positionChange_ = function(position) { var coords = position.coords; + this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy); + this.set(ol.GeolocationProperty.ALTITUDE, + goog.isNull(coords.altitude) ? undefined : coords.altitude); + this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY, + goog.isNull(coords.altitudeAccuracy) ? + undefined : coords.altitudeAccuracy); + this.set(ol.GeolocationProperty.HEADING, + goog.isNull(coords.heading) ? undefined : coords.heading); this.position_ = new ol.Coordinate(coords.longitude, coords.latitude); this.set(ol.GeolocationProperty.POSITION, this.transformCoords_(this.position_)); - this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy); + this.set(ol.GeolocationProperty.SPEED, + goog.isNull(coords.speed) ? undefined : coords.speed); }; @@ -107,20 +120,6 @@ ol.Geolocation.prototype.positionError_ = function(error) { }; -/** - * The position of the device. - * @return {ol.Coordinate|undefined} position. - */ -ol.Geolocation.prototype.getPosition = function() { - return /** @type {ol.Coordinate} */ ( - this.get(ol.GeolocationProperty.POSITION)); -}; -goog.exportProperty( - ol.Geolocation.prototype, - 'getPosition', - ol.Geolocation.prototype.getPosition); - - /** * The accuracy of the position in meters. * @return {number|undefined} accuracy. @@ -135,6 +134,59 @@ goog.exportProperty( ol.Geolocation.prototype.getAccuracy); +/** + * @return {number|undefined} Altitude. + */ +ol.Geolocation.prototype.getAltitude = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.ALTITUDE)); +}; +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitude', + ol.Geolocation.prototype.getAltitude); + + +/** + * @return {number|undefined} Altitude accuracy. + */ +ol.Geolocation.prototype.getAltitudeAccuracy = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY)); +}; +goog.exportProperty( + ol.Geolocation.prototype, + 'getAltitudeAccuracy', + ol.Geolocation.prototype.getAltitudeAccuracy); + + +/** + * @return {number|undefined} Heading. + */ +ol.Geolocation.prototype.getHeading = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.HEADING)); +}; +goog.exportProperty( + ol.Geolocation.prototype, + 'getHeading', + ol.Geolocation.prototype.getHeading); + + +/** + * The position of the device. + * @return {ol.Coordinate|undefined} position. + */ +ol.Geolocation.prototype.getPosition = function() { + return /** @type {ol.Coordinate} */ ( + this.get(ol.GeolocationProperty.POSITION)); +}; +goog.exportProperty( + ol.Geolocation.prototype, + 'getPosition', + ol.Geolocation.prototype.getPosition); + + /** * @return {ol.Projection|undefined} projection. */ @@ -148,6 +200,19 @@ goog.exportProperty( ol.Geolocation.prototype.getProjection); +/** + * @return {number|undefined} Speed. + */ +ol.Geolocation.prototype.getSpeed = function() { + return /** @type {number|undefined} */ ( + this.get(ol.GeolocationProperty.SPEED)); +}; +goog.exportProperty( + ol.Geolocation.prototype, + 'getSpeed', + ol.Geolocation.prototype.getSpeed); + + /** * @param {ol.Projection} projection Projection. */ From 85eec4e9b85512b8f096ee8617491d932f48baa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Fri, 15 Feb 2013 18:02:15 +0100 Subject: [PATCH 21/29] Avoid busy-waiting while images load This is to be in conformance with the work done with #184. --- .../renderer/canvas/canvasimagelayerrenderer.js | 13 +++---------- src/ol/renderer/dom/domimagelayerrenderer.js | 13 +++---------- src/ol/renderer/layerrenderer.js | 15 +++++++++++++++ src/ol/renderer/webgl/webglimagelayerrenderer.js | 13 +++---------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js index 1b7857b9e2..33558a9c51 100644 --- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -82,20 +82,13 @@ ol.renderer.canvas.ImageLayer.prototype.renderFrame = image = imageSource.getImage( frameState.extent, viewResolution); var imageState = image.getState(); - var animate = false; - if (imageState == ol.ImageState.ERROR) { - // pass - } else if (imageState == ol.ImageState.IDLE) { - animate = true; + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); image.load(); - } else if (imageState == ol.ImageState.LOADING) { - animate = true; } else if (imageState == ol.ImageState.LOADED) { this.image_ = image; } - if (animate) { - frameState.animate = true; - } } if (!goog.isNull(this.image_)) { diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js index eda01022e1..1e32a94775 100644 --- a/src/ol/renderer/dom/domimagelayerrenderer.js +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -69,20 +69,13 @@ ol.renderer.dom.ImageLayer.prototype.renderFrame = if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { var image_ = imageSource.getImage(frameState.extent, viewResolution); var imageState = image_.getState(); - var animate = false; - if (imageState == ol.ImageState.ERROR) { - // pass - } else if (imageState == ol.ImageState.IDLE) { - animate = true; + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); image_.load(); - } else if (imageState == ol.ImageState.LOADING) { - animate = true; } else if (imageState == ol.ImageState.LOADED) { image = image_; } - if (animate) { - frameState.animate = true; - } } if (!goog.isNull(image)) { diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index fab64889fc..bfe98600c4 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -3,6 +3,8 @@ goog.provide('ol.renderer.Layer'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('ol.FrameState'); +goog.require('ol.Image'); +goog.require('ol.ImageState'); goog.require('ol.Object'); goog.require('ol.Tile'); goog.require('ol.TileCoord'); @@ -119,6 +121,19 @@ ol.renderer.Layer.prototype.handleLayerContrastChange = goog.nullFunction; ol.renderer.Layer.prototype.handleLayerHueChange = goog.nullFunction; +/** + * Handle changes in image state. + * @param {goog.events.Event} event Image change event. + * @protected + */ +ol.renderer.Layer.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + if (image.getState() === ol.ImageState.LOADED) { + this.getMap().requestRenderFrame(); + } +}; + + /** * @protected */ diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index c259b02b99..f7c7e3dc9c 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -160,14 +160,10 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { var image_ = imageSource.getImage(frameState.extent, viewResolution); var imageState = image_.getState(); - var animate = false; - if (imageState == ol.ImageState.ERROR) { - // pass - } else if (imageState == ol.ImageState.IDLE) { - animate = true; + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); image_.load(); - } else if (imageState == ol.ImageState.LOADING) { - animate = true; } else if (imageState == ol.ImageState.LOADED) { image = image_; texture = this.createTexture_(image_); @@ -180,9 +176,6 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = }, gl, this.texture_)); } } - if (animate) { - frameState.animate = true; - } } if (!goog.isNull(image)) { From be6c1a1a278ce56624d1d444b89a267156cae321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Fri, 15 Feb 2013 23:17:30 +0100 Subject: [PATCH 22/29] Test the image layer renderer's transform matrix --- .../renderer/webgl/webglimagelayerrenderer.js | 53 +++++++---- .../spec/ol/renderer/webgl/imagelayer.test.js | 87 +++++++++++++++++++ 2 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 test/spec/ol/renderer/webgl/imagelayer.test.js diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index f7c7e3dc9c..7d11ff53e5 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -1,6 +1,8 @@ goog.provide('ol.renderer.webgl.ImageLayer'); goog.require('goog.vec.Mat4'); +goog.require('ol.Coordinate'); +goog.require('ol.Extent'); goog.require('ol.Image'); goog.require('ol.ImageState'); goog.require('ol.ViewHint'); @@ -183,23 +185,8 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = var canvas = this.getMapRenderer().getCanvas(); - var canvasExtentWidth = canvas.width * viewResolution; - var canvasExtentHeight = canvas.height * viewResolution; - - var imageExtent = image.getExtent(); - - var vertexCoordMatrix = this.vertexCoordMatrix_; - goog.vec.Mat4.makeIdentity(vertexCoordMatrix); - goog.vec.Mat4.scale(vertexCoordMatrix, - 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); - goog.vec.Mat4.rotateZ(vertexCoordMatrix, -viewRotation); - goog.vec.Mat4.translate(vertexCoordMatrix, - imageExtent.minX - viewCenter.x, - imageExtent.minY - viewCenter.y, - 0); - goog.vec.Mat4.scale(vertexCoordMatrix, - imageExtent.getWidth() / 2, imageExtent.getHeight() / 2, 1); - goog.vec.Mat4.translate(vertexCoordMatrix, 1, 1, 0); + this.updateVertexCoordMatrix_(canvas.width, canvas.height, + viewCenter, viewResolution, viewRotation, image.getExtent()); // Translate and scale to flip the Y coord. var texCoordMatrix = this.texCoordMatrix_; @@ -211,3 +198,35 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = this.texture_ = texture; } }; + + +/** + * @private + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + */ +ol.renderer.webgl.ImageLayer.prototype.updateVertexCoordMatrix_ = + function(canvasWidth, canvasHeight, viewCenter, + viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + + var vertexCoordMatrix = this.vertexCoordMatrix_; + goog.vec.Mat4.makeIdentity(vertexCoordMatrix); + goog.vec.Mat4.scale(vertexCoordMatrix, + 2 / canvasExtentWidth, 2 / canvasExtentHeight, 1); + goog.vec.Mat4.rotateZ(vertexCoordMatrix, -viewRotation); + goog.vec.Mat4.translate(vertexCoordMatrix, + imageExtent.minX - viewCenter.x, + imageExtent.minY - viewCenter.y, + 0); + goog.vec.Mat4.scale(vertexCoordMatrix, + imageExtent.getWidth() / 2, imageExtent.getHeight() / 2, 1); + goog.vec.Mat4.translate(vertexCoordMatrix, 1, 1, 0); + +}; diff --git a/test/spec/ol/renderer/webgl/imagelayer.test.js b/test/spec/ol/renderer/webgl/imagelayer.test.js new file mode 100644 index 0000000000..62f3038827 --- /dev/null +++ b/test/spec/ol/renderer/webgl/imagelayer.test.js @@ -0,0 +1,87 @@ +goog.provide('ol.test.renderer.webgl.ImageLayer'); + +describe('ol.renderer.webgl.ImageLayer', function() { + describe('updateVertexCoordMatrix_', function() { + var map; + var renderer; + var canvasWidth; + var canvasHeight; + var viewExtent; + var viewResolution; + var viewRotation; + var imageExtent; + + beforeEach(function() { + map = new ol.Map({ + target: 'map' + }); + var layer = new ol.layer.ImageLayer({ + source: new ol.source.ImageSource({ + extent: new ol.Extent(0, 0, 1, 1) + }) + }); + renderer = new ol.renderer.webgl.ImageLayer(map.getRenderer(), layer); + + // input params + canvasWidth = 512; + canvasHeight = 256; + viewResolution = 10; + viewRotation = 0; + viewCenter = new ol.Coordinate(7680, 3840); + // view extent is 512O, 2560, 10240, 5120 + + // image size is 1024, 768 + // image resolution is 10 + imageExtent = new ol.Extent(0, 0, 10240, 7680); + }); + + afterEach(function() { + map.dispose(); + }); + + it('produces a correct matrix', function() { + + renderer.updateVertexCoordMatrix_(canvasWidth, canvasHeight, + viewCenter, viewResolution, viewRotation, imageExtent); + var matrix = renderer.getVertexCoordMatrix(); + + var input; + var output = goog.vec.Vec4.createNumber(); + + input = goog.vec.Vec4.createFromValues(-1, -1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-3); + expect(output[1]).toEqual(-3); + + input = goog.vec.Vec4.createFromValues(1, -1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(1); + expect(output[1]).toEqual(-3); + + input = goog.vec.Vec4.createFromValues(-1, 1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-3); + expect(output[1]).toEqual(3); + + input = goog.vec.Vec4.createFromValues(1, 1, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(1); + expect(output[1]).toEqual(3); + + input = goog.vec.Vec4.createFromValues(0, 0, 0, 1); + goog.vec.Mat4.multVec4(matrix, input, output); + expect(output[0]).toEqual(-1); + expect(output[1]).toEqual(0); + }); + }); +}); + +goog.require('goog.vec.Mat4'); +goog.require('goog.vec.Vec4'); +goog.require('ol.Extent'); +goog.require('ol.Image'); +goog.require('ol.Map'); +goog.require('ol.layer.ImageLayer'); +goog.require('ol.source.ImageSource'); +goog.require('ol.renderer.Map'); +goog.require('ol.renderer.webgl.ImageLayer'); From d713c9b8ed1356302c6ef9a88710fd7815dcc481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Lemoine?= Date: Sun, 17 Feb 2013 23:07:58 +0100 Subject: [PATCH 23/29] Deal with null images --- .../canvas/canvasimagelayerrenderer.js | 19 +++++------ src/ol/renderer/dom/domimagelayerrenderer.js | 16 ++++++---- .../renderer/webgl/webglimagelayerrenderer.js | 32 ++++++++++--------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js index 33558a9c51..273f22d49f 100644 --- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -79,15 +79,16 @@ ol.renderer.canvas.ImageLayer.prototype.renderFrame = var hints = frameState.viewHints; if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { - image = imageSource.getImage( - frameState.extent, viewResolution); - var imageState = image.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image.load(); - } else if (imageState == ol.ImageState.LOADED) { - this.image_ = image; + image = imageSource.getImage(frameState.extent, viewResolution); + if (!goog.isNull(image)) { + var imageState = image.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image.load(); + } else if (imageState == ol.ImageState.LOADED) { + this.image_ = image; + } } } diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js index 1e32a94775..b3f5f9f224 100644 --- a/src/ol/renderer/dom/domimagelayerrenderer.js +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -68,13 +68,15 @@ ol.renderer.dom.ImageLayer.prototype.renderFrame = if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { var image_ = imageSource.getImage(frameState.extent, viewResolution); - var imageState = image_.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image_, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image_.load(); - } else if (imageState == ol.ImageState.LOADED) { - image = image_; + if (!goog.isNull(image_)) { + var imageState = image_.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image_.load(); + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + } } } diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 7d11ff53e5..6449a7c178 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -161,21 +161,23 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame = if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.PANNING]) { var image_ = imageSource.getImage(frameState.extent, viewResolution); - var imageState = image_.getState(); - if (imageState == ol.ImageState.IDLE) { - goog.events.listenOnce(image_, goog.events.EventType.CHANGE, - this.handleImageChange, false, this); - image_.load(); - } else if (imageState == ol.ImageState.LOADED) { - image = image_; - texture = this.createTexture_(image_); - if (!goog.isNull(this.texture_)) { - frameState.postRenderFunctions.push( - goog.partial(function(gl, texture) { - if (!gl.isContextLost()) { - gl.deleteTexture(texture); - } - }, gl, this.texture_)); + if (!goog.isNull(image_)) { + var imageState = image_.getState(); + if (imageState == ol.ImageState.IDLE) { + goog.events.listenOnce(image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + image_.load(); + } else if (imageState == ol.ImageState.LOADED) { + image = image_; + texture = this.createTexture_(image_); + if (!goog.isNull(this.texture_)) { + frameState.postRenderFunctions.push( + goog.partial(function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }, gl, this.texture_)); + } } } } From fc9f323f835ff65b916e0c55978f9fb9ae50b0fe Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sun, 17 Feb 2013 23:53:37 +0100 Subject: [PATCH 24/29] Store heading in radians, thanks @fredj --- src/ol/geolocation.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ol/geolocation.js b/src/ol/geolocation.js index e9271bebde..99a0535382 100644 --- a/src/ol/geolocation.js +++ b/src/ol/geolocation.js @@ -5,6 +5,7 @@ goog.provide('ol.Geolocation'); goog.provide('ol.GeolocationProperty'); goog.require('goog.functions'); +goog.require('goog.math'); goog.require('ol.Coordinate'); goog.require('ol.Object'); goog.require('ol.Projection'); @@ -102,8 +103,8 @@ ol.Geolocation.prototype.positionChange_ = function(position) { this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY, goog.isNull(coords.altitudeAccuracy) ? undefined : coords.altitudeAccuracy); - this.set(ol.GeolocationProperty.HEADING, - goog.isNull(coords.heading) ? undefined : coords.heading); + this.set(ol.GeolocationProperty.HEADING, goog.isNull(coords.heading) ? + undefined : goog.math.toRadians(coords.heading)); this.position_ = new ol.Coordinate(coords.longitude, coords.latitude); this.set(ol.GeolocationProperty.POSITION, this.transformCoords_(this.position_)); From 36a3d02816aa8805350712a1edb73356c653be02 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 18 Feb 2013 12:26:10 +0100 Subject: [PATCH 25/29] Add ellipsoid geometry functions --- src/ol/ellipsoid.js | 170 ++++++++++++++ src/ol/ellipsoid/wgs84.js | 10 + test/spec/ol/ellipsoid.test.js | 391 +++++++++++++++++++++++++++++++++ 3 files changed, 571 insertions(+) create mode 100644 src/ol/ellipsoid.js create mode 100644 src/ol/ellipsoid/wgs84.js create mode 100644 test/spec/ol/ellipsoid.test.js diff --git a/src/ol/ellipsoid.js b/src/ol/ellipsoid.js new file mode 100644 index 0000000000..1669c7de5f --- /dev/null +++ b/src/ol/ellipsoid.js @@ -0,0 +1,170 @@ +goog.provide('ol.Ellipsoid'); + +goog.require('goog.math'); +goog.require('ol.Coordinate'); + + + +/** + * @constructor + * @param {number} a Major radius. + * @param {number} flattening Flattening. + */ +ol.Ellipsoid = function(a, flattening) { + + /** + * @type {number} + */ + this.a = a; + + /** + * @type {number} + */ + this.flattening = flattening; + + /** + * @type {number} + */ + this.b = this.a * (1 - this.flattening); + +}; + + +/** + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {{distance: number, initialBearing: number, finalBearing: number}} + * Vincenty. + */ +ol.Ellipsoid.prototype.vincenty = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var minDeltaLambda = goog.isDef(opt_minDeltaLambda) ? + opt_minDeltaLambda : 1e-12; + var maxIterations = goog.isDef(opt_maxIterations) ? + opt_maxIterations : 100; + var f = this.flattening; + var lat1 = goog.math.toRadians(c1.y); + var lat2 = goog.math.toRadians(c2.y); + var deltaLon = goog.math.toRadians(c2.x - c1.x); + var U1 = Math.atan((1 - f) * Math.tan(lat1)); + var cosU1 = Math.cos(U1); + var sinU1 = Math.sin(U1); + var U2 = Math.atan((1 - f) * Math.tan(lat2)); + var cosU2 = Math.cos(U2); + var sinU2 = Math.sin(U2); + var lambda = deltaLon; + var cosSquaredAlpha, sinAlpha; + var cosLambda, deltaLambda = Infinity, sinLambda; + var cos2SigmaM, cosSigma, sigma, sinSigma; + var i; + for (i = maxIterations; i > 0; --i) { + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + var x = cosU2 * sinLambda; + var y = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; + sinSigma = Math.sqrt(x * x + y * y); + if (sinSigma === 0) { + return { + distance: 0, + initialBearing: 0, + finalBearing: 0 + }; + } + cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; + sigma = Math.atan2(sinSigma, cosSigma); + sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; + cosSquaredAlpha = 1 - sinAlpha * sinAlpha; + cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSquaredAlpha; + if (isNaN(cos2SigmaM)) { + cos2SigmaM = 0; + } + var C = f / 16 * cosSquaredAlpha * (4 + f * (4 - 3 * cosSquaredAlpha)); + var lambdaPrime = deltaLon + (1 - C) * f * sinAlpha * (sigma + + C * sinSigma * (cos2SigmaM + + C * cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1))); + deltaLambda = Math.abs(lambdaPrime - lambda); + lambda = lambdaPrime; + if (deltaLambda < minDeltaLambda) { + break; + } + } + if (i === 0) { + return { + distance: NaN, + finalBearing: NaN, + initialBearing: NaN + }; + } + var aSquared = this.a * this.a; + var bSquared = this.b * this.b; + var uSquared = cosSquaredAlpha * (aSquared - bSquared) / bSquared; + var A = 1 + uSquared / 16384 * + (4096 + uSquared * (uSquared * (320 - 175 * uSquared) - 768)); + var B = uSquared / 1024 * + (256 + uSquared * (uSquared * (74 - 47 * uSquared) - 128)); + var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * + (cosSigma * (2 * cos2SigmaM * cos2SigmaM - 1) - + B / 6 * cos2SigmaM * (4 * sinSigma * sinSigma - 3) * + (4 * cos2SigmaM * cos2SigmaM - 3))); + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + var alpha1 = Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + var alpha2 = Math.atan2(cosU1 * sinLambda, + cosU1 * sinU2 * cosLambda - sinU1 * cosU2); + return { + distance: this.b * A * (sigma - deltaSigma), + initialBearing: goog.math.toDegrees(alpha1), + finalBearing: goog.math.toDegrees(alpha2) + }; +}; + + +/** + * Returns the distance from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Vincenty distance. + */ +ol.Ellipsoid.prototype.vincentyDistance = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.distance; +}; + + +/** + * Returns the final bearing from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Initial bearing. + */ +ol.Ellipsoid.prototype.vincentyFinalBearing = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.finalBearing; +}; + + +/** + * Returns the initial bearing from c1 to c2 using Vincenty. + * + * @param {ol.Coordinate} c1 Coordinate 1. + * @param {ol.Coordinate} c2 Coordinate 1. + * @param {number=} opt_minDeltaLambda Minimum delta lambda for convergence. + * @param {number=} opt_maxIterations Maximum iterations. + * @return {number} Initial bearing. + */ +ol.Ellipsoid.prototype.vincentyInitialBearing = + function(c1, c2, opt_minDeltaLambda, opt_maxIterations) { + var vincenty = this.vincenty(c1, c2, opt_minDeltaLambda, opt_maxIterations); + return vincenty.initialBearing; +}; diff --git a/src/ol/ellipsoid/wgs84.js b/src/ol/ellipsoid/wgs84.js new file mode 100644 index 0000000000..e5244b80d7 --- /dev/null +++ b/src/ol/ellipsoid/wgs84.js @@ -0,0 +1,10 @@ +goog.provide('ol.ellipsoid.WGS84'); + +goog.require('ol.Ellipsoid'); + + +/** + * @const + * @type {ol.Ellipsoid} + */ +ol.ellipsoid.WGS84 = new ol.Ellipsoid(6378137, 1 / 298.257223563); diff --git a/test/spec/ol/ellipsoid.test.js b/test/spec/ol/ellipsoid.test.js new file mode 100644 index 0000000000..cf65f8d23f --- /dev/null +++ b/test/spec/ol/ellipsoid.test.js @@ -0,0 +1,391 @@ +goog.provide('ol.test.Ellipsoid'); + + +describe('ol.Ellipsoid', function() { + + var expected = [ + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(0, 0), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(45, 45), + vincentyFinalBearing: 54.890773827979565, + vincentyInitialBearing: 35.41005890511814, + vincentyDistance: 6662472.718217184 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(45, -45), + vincentyFinalBearing: 125.10922617202044, + vincentyInitialBearing: 144.58994109488185, + vincentyDistance: 6662472.718217184 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-45, -45), + vincentyFinalBearing: -125.10922617202044, + vincentyInitialBearing: -144.58994109488185, + vincentyDistance: 6662472.718217184 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-45, 45), + vincentyFinalBearing: -54.890773827979565, + vincentyInitialBearing: -35.41005890511814, + vincentyDistance: 6662472.718217184 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: 180, + vincentyInitialBearing: 4.296211503097554e-31, + vincentyDistance: 10001965.729311794 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: 7.0164775638926606e-15, + vincentyInitialBearing: 180, + vincentyDistance: 10001965.729311794 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -180, + vincentyInitialBearing: -4.296211503097554e-31, + vincentyDistance: 10001965.729311794 + }, + { + c1: new ol.Coordinate(0, 0), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -180, + vincentyInitialBearing: -4.296211503097554e-31, + vincentyDistance: 10001965.729311794 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(45, 45), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(45, -45), + vincentyFinalBearing: 180, + vincentyInitialBearing: 180, + vincentyDistance: 9969888.755957305 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-45, -45), + vincentyFinalBearing: -125.10922617202044, + vincentyInitialBearing: -125.10922617202044, + vincentyDistance: 13324945.436434371 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-45, 45), + vincentyFinalBearing: -125.27390277185786, + vincentyInitialBearing: -54.726097228142166, + vincentyDistance: 6690232.932559058 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: 135, + vincentyInitialBearing: 3.5023624896823797e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: 45.00000000000001, + vincentyInitialBearing: 180, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 135.00000000000003, + vincentyInitialBearing: 3.5023624896823793e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(45, 45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 135.00000000000003, + vincentyInitialBearing: 3.5023624896823793e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(45, -45), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(-45, -45), + vincentyFinalBearing: -54.726097228142166, + vincentyInitialBearing: -125.27390277185786, + vincentyDistance: 6690232.932559058 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(-45, 45), + vincentyFinalBearing: -54.890773827979565, + vincentyInitialBearing: -54.890773827979565, + vincentyDistance: 13324945.436434371 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: 135, + vincentyInitialBearing: 3.5023624896823797e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: 45.00000000000001, + vincentyInitialBearing: 180, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 135.00000000000003, + vincentyInitialBearing: 3.5023624896823793e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(45, -45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 135.00000000000003, + vincentyInitialBearing: 3.5023624896823793e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-45, -45), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-45, 45), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 9969888.755957305 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: -135.00000000000003, + vincentyInitialBearing: -3.5023624896823793e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: -44.999999999999986, + vincentyInitialBearing: -180, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -135, + vincentyInitialBearing: -3.5023624896823797e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(-45, -45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -135, + vincentyInitialBearing: -3.5023624896823797e-15, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-45, 45), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: -135.00000000000003, + vincentyInitialBearing: -3.5023624896823793e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: -44.999999999999986, + vincentyInitialBearing: -180, + vincentyDistance: 14986910.107290443 + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -135, + vincentyInitialBearing: -3.5023624896823797e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(-45, 45), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: -135, + vincentyInitialBearing: -3.5023624896823797e-15, + vincentyDistance: 5017021.35133314 + }, + { + c1: new ol.Coordinate(180, 90), + c2: new ol.Coordinate(180, 90), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(180, 90), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: 180, + vincentyInitialBearing: 180, + vincentyDistance: 20003931.458623584 + }, + { + c1: new ol.Coordinate(180, 90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 90, + vincentyInitialBearing: 90, + vincentyDistance: 9.565041537306137e-26 + }, + { + c1: new ol.Coordinate(180, 90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 90, + vincentyInitialBearing: 90, + vincentyDistance: 9.565041537306137e-26 + }, + { + c1: new ol.Coordinate(180, -90), + c2: new ol.Coordinate(180, -90), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(180, -90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 7.0164775638926606e-15, + vincentyInitialBearing: 7.0164775638926606e-15, + vincentyDistance: 20003931.458623584 + }, + { + c1: new ol.Coordinate(180, -90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 7.0164775638926606e-15, + vincentyInitialBearing: 7.0164775638926606e-15, + vincentyDistance: 20003931.458623584 + }, + { + c1: new ol.Coordinate(-180, 90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(-180, 90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + }, + { + c1: new ol.Coordinate(-180, 90), + c2: new ol.Coordinate(-180, 90), + vincentyFinalBearing: 0, + vincentyInitialBearing: 0, + vincentyDistance: 0 + } + ]; + + describe('vincenty', function() { + + it('returns the same distances as Chris Veness\'s reference implementation', + function() { + var e, i, v; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + v = ol.ellipsoid.WGS84.vincenty(e.c1, e.c2, 1e-12, 100); + expect(v.distance).toRoughlyEqual(e.vincentyDistance, 1e-8); + expect(v.finalBearing).toRoughlyEqual(e.vincentyFinalBearing, 1e-9); + expect(v.initialBearing).toRoughlyEqual(e.vincentyInitialBearing, 1e-9); + } + }); + + }); + + describe('vincentyDistance', function() { + + it('returns the same distances as Chris Veness\'s reference implementation', + function() { + var e, i, vincentyDistance; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + vincentyDistance = + ol.ellipsoid.WGS84.vincentyDistance(e.c1, e.c2, 1e-12, 100); + expect(vincentyDistance).toRoughlyEqual(e.vincentyDistance, 1e-8); + } + }); + + }); + + describe('vincentyFinalBearing', function() { + + it('returns the same distances as Chris Veness\'s reference implementation', + function() { + var e, i, vincentyFinalBearing; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + vincentyFinalBearing = + ol.ellipsoid.WGS84.vincentyFinalBearing(e.c1, e.c2, 1e-12, 100); + expect(vincentyFinalBearing).toRoughlyEqual( + e.vincentyFinalBearing, 1e-9); + } + }); + + }); + + describe('vincentyInitialBearing', function() { + + it('returns the same distances as Chris Veness\'s reference implementation', + function() { + var e, i, vincentyInitialBearing; + for (i = 0; i < expected.length; ++i) { + e = expected[i]; + vincentyInitialBearing = + ol.ellipsoid.WGS84.vincentyInitialBearing(e.c1, e.c2, 1e-12, 100); + expect(vincentyInitialBearing).toRoughlyEqual( + e.vincentyInitialBearing, 1e-9); + } + }); + + }); + +}); + + +goog.require('ol.Coordinate'); +goog.require('ol.ellipsoid.WGS84'); From be48a30d6acc38c18a35ed0722d988f55b1b7088 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 18 Feb 2013 14:44:14 +0100 Subject: [PATCH 26/29] Add target to download and install Proj4js --- .gitignore | 2 ++ build.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.gitignore b/.gitignore index 24c0e4318d..78d2adfe1c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ /build/src /build/phantomjs-*-windows /build/phantomjs-*-windows.zip +/build/proj4js-*.zip +/build/proj4js /examples/*.json /examples/*.combined.js /examples/example-list.js diff --git a/build.py b/build.py index 6c09e4928b..85f4246b36 100755 --- a/build.py +++ b/build.py @@ -77,6 +77,10 @@ SRC = [path PLOVR_JAR = 'bin/plovr-eba786b34df9.jar' PLOVR_JAR_MD5 = '20eac8ccc4578676511cf7ccbfc65100' +PROJ4JS = 'build/proj4js/lib/proj4js-combined.js' +PROJ4JS_ZIP = 'build/proj4js-1.1.0.zip' +PROJ4JS_ZIP_MD5 = '17caad64cf6ebc6e6fe62f292b134897' + def report_sizes(t): t.info('uncompressed: %d bytes', os.stat(t.name).st_size) @@ -338,6 +342,17 @@ def hostexamples(t): t.cp('examples/example-list.js', 'examples/example-list.xml', 'examples/Jugl.js', 'build/gh-pages/%(BRANCH)s/examples/') +@target(PROJ4JS, PROJ4JS_ZIP) +def proj4js(t): + from zipfile import ZipFile + ZipFile(PROJ4JS_ZIP).extractall('build') + + +@target(PROJ4JS_ZIP, clean=False) +def proj4js_zip(t): + t.download('http://download.osgeo.org/proj4js/' + os.path.basename(t.name), md5=PROJ4JS_ZIP_MD5) + + if sys.platform == 'win32': @target('test', '%(PHANTOMJS)s', INTERNAL_SRC, 'test/requireall.js', phony=True) def test(t): From cb9fbc3dec8f3d815c2a034caa6612668738e584 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 18 Feb 2013 14:44:35 +0100 Subject: [PATCH 27/29] Add Proj4js to test framework --- build.py | 4 ++-- test/ol.html | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 85f4246b36..e2c5264019 100755 --- a/build.py +++ b/build.py @@ -354,7 +354,7 @@ def proj4js_zip(t): if sys.platform == 'win32': - @target('test', '%(PHANTOMJS)s', INTERNAL_SRC, 'test/requireall.js', phony=True) + @target('test', '%(PHANTOMJS)s', INTERNAL_SRC, PROJ4JS, 'test/requireall.js', phony=True) def test(t): t.run(PHANTOMJS, 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') @@ -369,7 +369,7 @@ if sys.platform == 'win32': t.download('http://phantomjs.googlecode.com/files/' + os.path.basename(t.name)) else: - @target('test', INTERNAL_SRC, 'test/requireall.js', phony=True) + @target('test', INTERNAL_SRC, PROJ4JS, 'test/requireall.js', phony=True) def test(t): t.run('%(PHANTOMJS)s', 'test/phantom-jasmine/run_jasmine_test.coffee', 'test/ol.html') diff --git a/test/ol.html b/test/ol.html index d4e3191ac9..979397f83e 100644 --- a/test/ol.html +++ b/test/ol.html @@ -25,6 +25,7 @@ +