diff --git a/src/ol/projection.exports b/src/ol/projection.exports index 613ac99a3c..e82e78498f 100644 --- a/src/ol/projection.exports +++ b/src/ol/projection.exports @@ -2,6 +2,7 @@ @exportProperty ol.Projection.prototype.getAxisOrientation @exportProperty ol.Projection.prototype.getCode @exportProperty ol.Projection.prototype.getExtent +@exportProperty ol.Projection.prototype.getPointResolution @exportProperty ol.Projection.prototype.getUnits @exportSymbol ol.ProjectionUnits diff --git a/src/ol/projection.js b/src/ol/projection.js index 30cc5deb2c..0969b45882 100644 --- a/src/ol/projection.js +++ b/src/ol/projection.js @@ -8,6 +8,7 @@ goog.require('goog.object'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); goog.require('ol.TransformFunction'); +goog.require('ol.sphere.NORMAL'); /** @@ -85,6 +86,14 @@ ol.Projection.prototype.getExtent = function() { }; +/** + * @param {number} resolution Resolution. + * @param {ol.Coordinate} point Point. + * @return {number} Point resolution. + */ +ol.Projection.prototype.getPointResolution = goog.abstractMethod; + + /** * @return {ol.ProjectionUnits} Units. */ @@ -121,10 +130,49 @@ ol.Proj4jsProjection_ = function(code, proj4jsProj) { */ this.proj4jsProj_ = proj4jsProj; + /** + * @private + * @type {?ol.TransformFunction} + */ + this.toEPSG4326_ = null; + }; goog.inherits(ol.Proj4jsProjection_, ol.Projection); +/** + * @inheritDoc + */ +ol.Proj4jsProjection_.prototype.getPointResolution = + function(resolution, point) { + if (this.getUnits() == ol.ProjectionUnits.DEGREES) { + return resolution; + } else { + // Estimate point resolution by transforming the center pixel to EPSG:4326, + // measuring its width and height on the normal sphere, and taking the + // average of the width and height. + if (goog.isNull(this.toEPSG4326_)) { + this.toEPSG4326_ = ol.projection.getTransform( + this, ol.projection.getProj4jsProjectionFromCode_('EPSG:4326')); + } + var vertices = [ + point.x - resolution / 2, point.y, + point.x + resolution / 2, point.y, + point.x, point.y - resolution / 2, + point.x, point.y + resolution / 2 + ]; + vertices = this.toEPSG4326_(vertices, vertices, 2); + var width = ol.sphere.NORMAL.haversineDistance( + new ol.Coordinate(vertices[0], vertices[1]), + new ol.Coordinate(vertices[2], vertices[3])); + var height = ol.sphere.NORMAL.haversineDistance( + new ol.Coordinate(vertices[4], vertices[5]), + new ol.Coordinate(vertices[6], vertices[7])); + return (width + height) / 2; + } +}; + + /** * @return {Proj4js.Proj} Proj4js projection. */ diff --git a/src/ol/projection/epsg3857.js b/src/ol/projection/epsg3857.js index 0d183c3e66..93a99f8a62 100644 --- a/src/ol/projection/epsg3857.js +++ b/src/ol/projection/epsg3857.js @@ -4,6 +4,7 @@ goog.require('goog.array'); goog.require('ol.Extent'); goog.require('ol.Projection'); goog.require('ol.ProjectionUnits'); +goog.require('ol.math'); goog.require('ol.projection'); @@ -128,3 +129,12 @@ ol.projection.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) { } return output; }; + + +/** + * @inheritDoc + */ +ol.projection.EPSG3857.prototype.getPointResolution = + function(resolution, point) { + return resolution / ol.math.cosh(point.y / ol.projection.EPSG3857.RADIUS); +}; diff --git a/src/ol/projection/epsg4326.js b/src/ol/projection/epsg4326.js index 05b2c760d7..1c5d7215aa 100644 --- a/src/ol/projection/epsg4326.js +++ b/src/ol/projection/epsg4326.js @@ -41,3 +41,12 @@ ol.projection.EPSG4326.PROJECTIONS = [ new ol.projection.EPSG4326('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'), new ol.projection.EPSG4326('urn:ogc:def:crs:OGC:1.3:CRS84') ]; + + +/** + * @inheritDoc + */ +ol.projection.EPSG4326.prototype.getPointResolution = + function(resolution, point) { + return resolution; +}; diff --git a/src/ol/sphere/normal.js b/src/ol/sphere/normal.js new file mode 100644 index 0000000000..e07bf000b4 --- /dev/null +++ b/src/ol/sphere/normal.js @@ -0,0 +1,10 @@ +goog.provide('ol.sphere.NORMAL'); + +goog.require('ol.Sphere'); + + +/** + * The normal sphere. + * @const {ol.Sphere} + */ +ol.sphere.NORMAL = new ol.Sphere(6370997); diff --git a/src/ol/sphere/wgs84.js b/src/ol/sphere/wgs84.js new file mode 100644 index 0000000000..e30aafeb4b --- /dev/null +++ b/src/ol/sphere/wgs84.js @@ -0,0 +1,10 @@ +goog.provide('ol.sphere.WGS84'); + +goog.require('ol.Sphere'); + + +/** + * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid. + * @const {ol.Sphere} + */ +ol.sphere.WGS84 = new ol.Sphere(6378137); diff --git a/test/spec/ol/projection.epsg3857.test.js b/test/spec/ol/projection.epsg3857.test.js new file mode 100644 index 0000000000..51f72444ec --- /dev/null +++ b/test/spec/ol/projection.epsg3857.test.js @@ -0,0 +1,49 @@ +goog.provide('ol.test.projection.EPSG3857'); + + +describe('ol.projection.EPSG3857', function() { + + describe('getPointResolution', function() { + + it('returns the correct point scale at the equator', function() { + // @see http://msdn.microsoft.com/en-us/library/aa940990.aspx + var epsg3857 = ol.projection.getFromCode('EPSG:3857'); + var resolution = 19.11; + var point = new ol.Coordinate(0, 0); + expect(epsg3857.getPointResolution(resolution, point)). + toRoughlyEqual(19.11, 1e-1); + }); + + it('returns the correct point scale at the latitude of Toronto', + function() { + // @see http://msdn.microsoft.com/en-us/library/aa940990.aspx + var epsg3857 = ol.projection.getFromCode('EPSG:3857'); + var epsg4326 = ol.projection.getFromCode('EPSG:4326'); + var resolution = 19.11; + var point = ol.projection.transform( + new ol.Coordinate(0, 43.65), epsg4326, epsg3857); + expect(epsg3857.getPointResolution(resolution, point)). + toRoughlyEqual(19.11 * Math.cos(Math.PI * 43.65 / 180), 1e-9); + }); + + it('returns the correct point scale at various latitudes', function() { + // @see http://msdn.microsoft.com/en-us/library/aa940990.aspx + var epsg3857 = ol.projection.getFromCode('EPSG:3857'); + var epsg4326 = ol.projection.getFromCode('EPSG:4326'); + var resolution = 19.11; + var latitude; + for (latitude = 0; latitude < 90; ++latitude) { + var point = ol.projection.transform( + new ol.Coordinate(0, latitude), epsg4326, epsg3857); + expect(epsg3857.getPointResolution(resolution, point)). + toRoughlyEqual(19.11 * Math.cos(Math.PI * latitude / 180), 1e-9); + } + }); + + }); + +}); + + +goog.require('ol.Coordinate'); +goog.require('ol.projection.EPSG3857'); diff --git a/test/spec/ol/projection.test.js b/test/spec/ol/projection.test.js index 5a217e0f40..c961d41819 100644 --- a/test/spec/ol/projection.test.js +++ b/test/spec/ol/projection.test.js @@ -144,6 +144,36 @@ describe('ol.projection', function() { expect(point.y).toRoughlyEqual(200146.976, 1); }); + it('numerically estimates point scale at the equator', function() { + var googleProjection = ol.projection.getFromCode('GOOGLE'); + expect(googleProjection.getPointResolution(1, new ol.Coordinate(0, 0))). + toRoughlyEqual(1, 1e-1); + }); + + it('numerically estimates point scale at various latitudes', function() { + var epsg3857Projection = ol.projection.getFromCode('EPSG:3857'); + var googleProjection = ol.projection.getFromCode('GOOGLE'); + var point, y; + for (y = -20; y <= 20; ++y) { + point = new ol.Coordinate(0, 1000000 * y); + expect(googleProjection.getPointResolution(1, point)).toRoughlyEqual( + epsg3857Projection.getPointResolution(1, point), 1e-1); + } + }); + + it('numerically estimates point scale at various points', function() { + var epsg3857Projection = ol.projection.getFromCode('EPSG:3857'); + var googleProjection = ol.projection.getFromCode('GOOGLE'); + var point, x, y; + for (x = -20; x <= 20; ++x) { + for (y = -20; y <= 20; ++y) { + point = new ol.Coordinate(1000000 * x, 1000000 * y); + expect(googleProjection.getPointResolution(1, point)).toRoughlyEqual( + epsg3857Projection.getPointResolution(1, point), 1e-1); + } + } + }); + }); describe('ol.projection.getTransform()', function() {