diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 17e80b9a10..22f5e10096 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -35,6 +35,22 @@ view.animate({ }); ``` +#### Use `ol.proj.getPointResolution()` instead of `projection.getPointResolution()` + +The experimental `getPointResolution` method has been removed from `ol.Projection` instances. Since the implementation of this method required an inverse transform (function for transforming projected coordinates to geographic coordinates) and `ol.Projection` instances are not constructed with forward or inverse transforms, it does not make sense that a projection instance can always calculate the point resolution. + +As a substitute for the `projection.getPointResolution()` function, a `ol.proj.getPointResolution()` function has been added. To upgrade, you will need to change things like this: +```js +projection.getPointResolution(resolution, point); +``` + +into this: +```js +ol.proj.getPointResolution(projection, resolution, point); +``` + +Note that if you were previously creating a projection with a `getPointResolution` function in the constructor (or calling `projection.setGetPointResolution()` after construction), this function will be used by `ol.proj.getPointResolution()`. + ### v3.19.1 #### `ol.style.Fill` with `CanvasGradient` or `CanvasPattern` diff --git a/src/ol/control/scaleline.js b/src/ol/control/scaleline.js index 9d3171eb3e..cefaaaf9b2 100644 --- a/src/ol/control/scaleline.js +++ b/src/ol/control/scaleline.js @@ -6,7 +6,7 @@ goog.require('ol.asserts'); goog.require('ol.control.Control'); goog.require('ol.css'); goog.require('ol.events'); -goog.require('ol.proj.METERS_PER_UNIT'); +goog.require('ol.proj'); goog.require('ol.proj.Units'); @@ -169,7 +169,7 @@ ol.control.ScaleLine.prototype.updateElement_ = function() { var projection = viewState.projection; var metersPerUnit = projection.getMetersPerUnit(); var pointResolution = - projection.getPointResolution(viewState.resolution, center) * + ol.proj.getPointResolution(projection, viewState.resolution, center) * metersPerUnit; var nominalCount = this.minWidth_ * pointResolution; diff --git a/src/ol/proj/epsg3857.js b/src/ol/proj/epsg3857.js index 09864be283..105cf64a66 100644 --- a/src/ol/proj/epsg3857.js +++ b/src/ol/proj/epsg3857.js @@ -22,20 +22,15 @@ ol.proj.EPSG3857_ = function(code) { units: ol.proj.Units.METERS, extent: ol.proj.EPSG3857.EXTENT, global: true, - worldExtent: ol.proj.EPSG3857.WORLD_EXTENT + worldExtent: ol.proj.EPSG3857.WORLD_EXTENT, + getPointResolution: function(resolution, point) { + return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); + } }); }; ol.inherits(ol.proj.EPSG3857_, ol.proj.Projection); -/** - * @inheritDoc - */ -ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) { - return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS); -}; - - /** * @const * @type {number} diff --git a/src/ol/proj/epsg4326.js b/src/ol/proj/epsg4326.js index d1fff14687..1ec287e13b 100644 --- a/src/ol/proj/epsg4326.js +++ b/src/ol/proj/epsg4326.js @@ -35,14 +35,6 @@ ol.proj.EPSG4326_ = function(code, opt_axisOrientation) { ol.inherits(ol.proj.EPSG4326_, ol.proj.Projection); -/** - * @inheritDoc - */ -ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) { - return resolution; -}; - - /** * Extent of the EPSG:4326 projection which is the whole world. * diff --git a/src/ol/proj/index.js b/src/ol/proj/index.js index 019398f74e..62968b8d3d 100644 --- a/src/ol/proj/index.js +++ b/src/ol/proj/index.js @@ -1,391 +1,22 @@ goog.provide('ol.proj'); -goog.provide('ol.proj.METERS_PER_UNIT'); -goog.provide('ol.proj.Projection'); -goog.provide('ol.proj.Units'); goog.require('ol'); goog.require('ol.extent'); -goog.require('ol.obj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); +goog.require('ol.proj.projections'); +goog.require('ol.proj.transforms'); goog.require('ol.sphere.NORMAL'); -/** - * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or - * `'us-ft'`. - * @enum {string} - */ -ol.proj.Units = { - DEGREES: 'degrees', - FEET: 'ft', - METERS: 'm', - PIXELS: 'pixels', - TILE_PIXELS: 'tile-pixels', - USFEET: 'us-ft' -}; - - /** * Meters per unit lookup table. * @const * @type {Object.} * @api stable */ -ol.proj.METERS_PER_UNIT = {}; -ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] = - 2 * Math.PI * ol.sphere.NORMAL.radius / 360; -ol.proj.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048; -ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; -ol.proj.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; - - -/** - * @classdesc - * Projection definition class. One of these is created for each projection - * supported in the application and stored in the {@link ol.proj} namespace. - * You can use these in applications, but this is not required, as API params - * and options use {@link ol.ProjectionLike} which means the simple string - * code will suffice. - * - * You can use {@link ol.proj.get} to retrieve the object for a particular - * projection. - * - * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together - * with the following aliases: - * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, - * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84, - * http://www.opengis.net/gml/srs/epsg.xml#4326, - * urn:x-ogc:def:crs:EPSG:4326 - * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913, - * urn:ogc:def:crs:EPSG:6.18:3:3857, - * http://www.opengis.net/gml/srs/epsg.xml#3857 - * - * If you use proj4js, aliases can be added using `proj4.defs()`; see - * [documentation](https://github.com/proj4js/proj4js). To set an alternative - * namespace for proj4, use {@link ol.proj.setProj4}. - * - * @constructor - * @param {olx.ProjectionOptions} options Projection options. - * @struct - * @api stable - */ -ol.proj.Projection = function(options) { - - /** - * @private - * @type {string} - */ - this.code_ = options.code; - - /** - * @private - * @type {ol.proj.Units} - */ - this.units_ = /** @type {ol.proj.Units} */ (options.units); - - /** - * @private - * @type {ol.Extent} - */ - this.extent_ = options.extent !== undefined ? options.extent : null; - - /** - * @private - * @type {ol.Extent} - */ - this.worldExtent_ = options.worldExtent !== undefined ? - options.worldExtent : null; - - /** - * @private - * @type {string} - */ - this.axisOrientation_ = options.axisOrientation !== undefined ? - options.axisOrientation : 'enu'; - - /** - * @private - * @type {boolean} - */ - this.global_ = options.global !== undefined ? options.global : false; - - - /** - * @private - * @type {boolean} - */ - this.canWrapX_ = !!(this.global_ && this.extent_); - - /** - * @private - * @type {function(number, ol.Coordinate):number} - */ - this.getPointResolutionFunc_ = options.getPointResolution !== undefined ? - options.getPointResolution : this.getPointResolution_; - - /** - * @private - * @type {ol.tilegrid.TileGrid} - */ - this.defaultTileGrid_ = null; - - /** - * @private - * @type {number|undefined} - */ - this.metersPerUnit_ = options.metersPerUnit; - - var projections = ol.proj.projections_; - var code = options.code; - ol.DEBUG && console.assert(code !== undefined, - 'Option "code" is required for constructing instance'); - if (ol.ENABLE_PROJ4JS) { - var proj4js = ol.proj.proj4_ || window['proj4']; - if (typeof proj4js == 'function' && projections[code] === undefined) { - var def = proj4js.defs(code); - if (def !== undefined) { - if (def.axis !== undefined && options.axisOrientation === undefined) { - this.axisOrientation_ = def.axis; - } - if (options.metersPerUnit === undefined) { - this.metersPerUnit_ = def.to_meter; - } - if (options.units === undefined) { - this.units_ = def.units; - } - } - } - } - -}; - - -/** - * @return {boolean} The projection is suitable for wrapping the x-axis - */ -ol.proj.Projection.prototype.canWrapX = function() { - return this.canWrapX_; -}; - - -/** - * Get the code for this projection, e.g. 'EPSG:4326'. - * @return {string} Code. - * @api stable - */ -ol.proj.Projection.prototype.getCode = function() { - return this.code_; -}; - - -/** - * Get the validity extent for this projection. - * @return {ol.Extent} Extent. - * @api stable - */ -ol.proj.Projection.prototype.getExtent = function() { - return this.extent_; -}; - - -/** - * Get the units of this projection. - * @return {ol.proj.Units} Units. - * @api stable - */ -ol.proj.Projection.prototype.getUnits = function() { - return this.units_; -}; - - -/** - * Get the amount of meters per unit of this projection. If the projection is - * not configured with `metersPerUnit` or a units identifier, the return is - * `undefined`. - * @return {number|undefined} Meters. - * @api stable - */ -ol.proj.Projection.prototype.getMetersPerUnit = function() { - return this.metersPerUnit_ || ol.proj.METERS_PER_UNIT[this.units_]; -}; - - -/** - * Get the world extent for this projection. - * @return {ol.Extent} Extent. - * @api - */ -ol.proj.Projection.prototype.getWorldExtent = function() { - return this.worldExtent_; -}; - - -/** - * Get the axis orientation of this projection. - * Example values are: - * enu - the default easting, northing, elevation. - * neu - northing, easting, up - useful for "lat/long" geographic coordinates, - * or south orientated transverse mercator. - * wnu - westing, northing, up - some planetary coordinate systems have - * "west positive" coordinate systems - * @return {string} Axis orientation. - */ -ol.proj.Projection.prototype.getAxisOrientation = function() { - return this.axisOrientation_; -}; - - -/** - * Is this projection a global projection which spans the whole world? - * @return {boolean} Whether the projection is global. - * @api stable - */ -ol.proj.Projection.prototype.isGlobal = function() { - return this.global_; -}; - - -/** -* Set if the projection is a global projection which spans the whole world -* @param {boolean} global Whether the projection is global. -* @api stable -*/ -ol.proj.Projection.prototype.setGlobal = function(global) { - this.global_ = global; - this.canWrapX_ = !!(global && this.extent_); -}; - - -/** - * @return {ol.tilegrid.TileGrid} The default tile grid. - */ -ol.proj.Projection.prototype.getDefaultTileGrid = function() { - return this.defaultTileGrid_; -}; - - -/** - * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid. - */ -ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { - this.defaultTileGrid_ = tileGrid; -}; - - -/** - * Set the validity extent for this projection. - * @param {ol.Extent} extent Extent. - * @api stable - */ -ol.proj.Projection.prototype.setExtent = function(extent) { - this.extent_ = extent; - this.canWrapX_ = !!(this.global_ && extent); -}; - - -/** - * Set the world extent for this projection. - * @param {ol.Extent} worldExtent World extent - * [minlon, minlat, maxlon, maxlat]. - * @api - */ -ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) { - this.worldExtent_ = worldExtent; -}; - - -/** -* Set the getPointResolution function for this projection. -* @param {function(number, ol.Coordinate):number} func Function -* @api -*/ -ol.proj.Projection.prototype.setGetPointResolution = function(func) { - this.getPointResolutionFunc_ = func; -}; - - -/** -* Default version. -* Get the resolution of the point in degrees or distance units. -* For projections with degrees as the unit this will simply return the -* provided resolution. For other projections the point resolution is -* estimated by transforming the 'point' pixel to EPSG:4326, -* measuring its width and height on the normal sphere, -* and taking the average of the width and height. -* @param {number} resolution Nominal resolution in projection units. -* @param {ol.Coordinate} point Point to find adjusted resolution at. -* @return {number} Point resolution at point in projection units. -* @private -*/ -ol.proj.Projection.prototype.getPointResolution_ = function(resolution, point) { - var units = this.getUnits(); - if (units == ol.proj.Units.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. - var toEPSG4326 = ol.proj.getTransformFromProjections( - this, ol.proj.get('EPSG:4326')); - var vertices = [ - point[0] - resolution / 2, point[1], - point[0] + resolution / 2, point[1], - point[0], point[1] - resolution / 2, - point[0], point[1] + resolution / 2 - ]; - vertices = toEPSG4326(vertices, vertices, 2); - var width = ol.sphere.NORMAL.haversineDistance( - vertices.slice(0, 2), vertices.slice(2, 4)); - var height = ol.sphere.NORMAL.haversineDistance( - vertices.slice(4, 6), vertices.slice(6, 8)); - var pointResolution = (width + height) / 2; - var metersPerUnit = this.getMetersPerUnit(); - if (metersPerUnit !== undefined) { - pointResolution /= metersPerUnit; - } - return pointResolution; - } -}; - - -/** - * Get the resolution of the point in degrees or distance units. - * For projections with degrees as the unit this will simply return the - * provided resolution. The default for other projections is to estimate - * the point resolution by transforming the 'point' pixel to EPSG:4326, - * measuring its width and height on the normal sphere, - * and taking the average of the width and height. - * An alternative implementation may be given when constructing a - * projection. For many local projections, - * such a custom function will return the resolution unchanged. - * @param {number} resolution Resolution in projection units. - * @param {ol.Coordinate} point Point. - * @return {number} Point resolution in projection units. - * @api - */ -ol.proj.Projection.prototype.getPointResolution = function(resolution, point) { - return this.getPointResolutionFunc_(resolution, point); -}; - - -/** - * @private - * @type {Object.} - */ -ol.proj.projections_ = {}; - - -/** - * @private - * @type {Object.>} - */ -ol.proj.transforms_ = {}; - - -/** - * @private - * @type {proj4} - */ -ol.proj.proj4_ = null; +ol.proj.METERS_PER_UNIT = ol.proj.Units.METERS_PER_UNIT; if (ol.ENABLE_PROJ4JS) { @@ -404,11 +35,60 @@ if (ol.ENABLE_PROJ4JS) { ol.proj.setProj4 = function(proj4) { ol.DEBUG && console.assert(typeof proj4 == 'function', 'proj4 argument should be a function'); - ol.proj.proj4_ = proj4; + ol.proj.proj4.set(proj4); }; } +/** + * Get the resolution of the point in degrees or distance units. + * For projections with degrees as the unit this will simply return the + * provided resolution. For other projections the point resolution is + * estimated by transforming the 'point' pixel to EPSG:4326, + * measuring its width and height on the normal sphere, + * and taking the average of the width and height. + * @param {ol.proj.Projection} projection The projection. + * @param {number} resolution Nominal resolution in projection units. + * @param {ol.Coordinate} point Point to find adjusted resolution at. + * @return {number} Point resolution at point in projection units. + * @api + */ +ol.proj.getPointResolution = function(projection, resolution, point) { + var pointResolution; + var getter = projection.getPointResolutionFunc(); + if (getter) { + pointResolution = getter(resolution, point); + } else { + var units = projection.getUnits(); + if (units == ol.proj.Units.DEGREES) { + pointResolution = 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. + var toEPSG4326 = ol.proj.getTransformFromProjections(projection, ol.proj.get('EPSG:4326')); + var vertices = [ + point[0] - resolution / 2, point[1], + point[0] + resolution / 2, point[1], + point[0], point[1] - resolution / 2, + point[0], point[1] + resolution / 2 + ]; + vertices = toEPSG4326(vertices, vertices, 2); + var width = ol.sphere.NORMAL.haversineDistance( + vertices.slice(0, 2), vertices.slice(2, 4)); + var height = ol.sphere.NORMAL.haversineDistance( + vertices.slice(4, 6), vertices.slice(6, 8)); + pointResolution = (width + height) / 2; + var metersPerUnit = projection.getMetersPerUnit(); + if (metersPerUnit !== undefined) { + pointResolution /= metersPerUnit; + } + } + } + return pointResolution; +}; + + /** * Registers transformation functions that don't alter coordinates. Those allow * to transform between projections with equal meaning. @@ -421,7 +101,7 @@ ol.proj.addEquivalentProjections = function(projections) { projections.forEach(function(source) { projections.forEach(function(destination) { if (source !== destination) { - ol.proj.addTransform(source, destination, ol.proj.cloneTransform); + ol.proj.transforms.add(source, destination, ol.proj.cloneTransform); } }); }); @@ -444,8 +124,8 @@ ol.proj.addEquivalentProjections = function(projections) { ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) { projections1.forEach(function(projection1) { projections2.forEach(function(projection2) { - ol.proj.addTransform(projection1, projection2, forwardTransform); - ol.proj.addTransform(projection2, projection1, inverseTransform); + ol.proj.transforms.add(projection1, projection2, forwardTransform); + ol.proj.transforms.add(projection2, projection1, inverseTransform); }); }); }; @@ -459,8 +139,8 @@ ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTr * @api stable */ ol.proj.addProjection = function(projection) { - ol.proj.projections_[projection.getCode()] = projection; - ol.proj.addTransform(projection, projection, ol.proj.cloneTransform); + ol.proj.projections.add(projection.getCode(), projection); + ol.proj.transforms.add(projection, projection, ol.proj.cloneTransform); }; @@ -476,11 +156,11 @@ ol.proj.addProjections = function(projections) { /** - * FIXME empty description for jsdoc + * Clear all cached projections and transforms. */ ol.proj.clearAllProjections = function() { - ol.proj.projections_ = {}; - ol.proj.transforms_ = {}; + ol.proj.projections.clear(); + ol.proj.transforms.clear(); }; @@ -500,25 +180,6 @@ ol.proj.createProjection = function(projection, defaultCode) { }; -/** - * Registers a conversion function to convert coordinates from the source - * projection to the destination projection. - * - * @param {ol.proj.Projection} source Source. - * @param {ol.proj.Projection} destination Destination. - * @param {ol.TransformFunction} transformFn Transform. - */ -ol.proj.addTransform = function(source, destination, transformFn) { - var sourceCode = source.getCode(); - var destinationCode = destination.getCode(); - var transforms = ol.proj.transforms_; - if (!(sourceCode in transforms)) { - transforms[sourceCode] = {}; - } - transforms[sourceCode][destinationCode] = transformFn; -}; - - /** * Registers coordinate transform functions to convert coordinates between the * source projection and the destination projection. @@ -541,9 +202,9 @@ ol.proj.addTransform = function(source, destination, transformFn) { ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) { var sourceProj = ol.proj.get(source); var destProj = ol.proj.get(destination); - ol.proj.addTransform(sourceProj, destProj, + ol.proj.transforms.add(sourceProj, destProj, ol.proj.createTransformFromCoordinateTransform(forward)); - ol.proj.addTransform(destProj, sourceProj, + ol.proj.transforms.add(destProj, sourceProj, ol.proj.createTransformFromCoordinateTransform(inverse)); }; @@ -581,32 +242,6 @@ ol.proj.createTransformFromCoordinateTransform = function(transform) { }; -/** - * Unregisters the conversion function to convert coordinates from the source - * projection to the destination projection. This method is used to clean up - * cached transforms during testing. - * - * @param {ol.proj.Projection} source Source projection. - * @param {ol.proj.Projection} destination Destination projection. - * @return {ol.TransformFunction} transformFn The unregistered transform. - */ -ol.proj.removeTransform = function(source, destination) { - var sourceCode = source.getCode(); - var destinationCode = destination.getCode(); - var transforms = ol.proj.transforms_; - ol.DEBUG && console.assert(sourceCode in transforms, - 'sourceCode should be in transforms'); - ol.DEBUG && console.assert(destinationCode in transforms[sourceCode], - 'destinationCode should be in transforms of sourceCode'); - var transform = transforms[sourceCode][destinationCode]; - delete transforms[sourceCode][destinationCode]; - if (ol.obj.isEmpty(transforms[sourceCode])) { - delete transforms[sourceCode]; - } - return transform; -}; - - /** * Transforms a coordinate from longitude/latitude to a different projection. * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e. @@ -647,22 +282,22 @@ ol.proj.toLonLat = function(coordinate, opt_projection) { * @api stable */ ol.proj.get = function(projectionLike) { - var projection; + var projection = null; if (projectionLike instanceof ol.proj.Projection) { projection = projectionLike; } else if (typeof projectionLike === 'string') { var code = projectionLike; - projection = ol.proj.projections_[code]; + projection = ol.proj.projections.get(code); if (ol.ENABLE_PROJ4JS) { - var proj4js = ol.proj.proj4_ || window['proj4']; - if (projection === undefined && typeof proj4js == 'function' && + var proj4js = ol.proj.proj4.get(); + if (!projection && typeof proj4js == 'function' && proj4js.defs(code) !== undefined) { projection = new ol.proj.Projection({code: code}); ol.proj.addProjection(projection); } } } - return projection || null; + return projection; }; @@ -719,12 +354,11 @@ ol.proj.getTransform = function(source, destination) { * @return {ol.TransformFunction} Transform function. */ ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) { - var transforms = ol.proj.transforms_; var sourceCode = sourceProjection.getCode(); var destinationCode = destinationProjection.getCode(); - var transform; - if (ol.ENABLE_PROJ4JS && !(sourceCode in transforms && destinationCode in transforms[sourceCode])) { - var proj4js = ol.proj.proj4_ || window['proj4']; + var transform = ol.proj.transforms.get(sourceCode, destinationCode); + if (ol.ENABLE_PROJ4JS && !transform) { + var proj4js = ol.proj.proj4.get(); if (typeof proj4js == 'function') { var sourceDef = proj4js.defs(sourceCode); var destinationDef = proj4js.defs(destinationCode); @@ -737,13 +371,12 @@ ol.proj.getTransformFromProjections = function(sourceProjection, destinationProj ol.proj.addCoordinateTransforms(destinationProjection, sourceProjection, proj4Transform.forward, proj4Transform.inverse); } + transform = ol.proj.transforms.get(sourceCode, destinationCode); } } } - if (sourceCode in transforms && destinationCode in transforms[sourceCode]) { - transform = transforms[sourceCode][destinationCode]; - } else { - ol.DEBUG && console.assert(transform !== undefined, 'transform should be defined'); + if (!transform) { + ol.DEBUG && console.assert(transform, 'transform should be defined'); transform = ol.proj.identityTransform; } return transform; diff --git a/src/ol/proj/proj4.js b/src/ol/proj/proj4.js new file mode 100644 index 0000000000..599673b59a --- /dev/null +++ b/src/ol/proj/proj4.js @@ -0,0 +1,26 @@ +goog.provide('ol.proj.proj4'); + + +/** + * @private + * @type {proj4} + */ +ol.proj.proj4.cache_ = null; + + +/** + * Store the proj4 function. + * @param {proj4} proj4 The proj4 function. + */ +ol.proj.proj4.set = function(proj4) { + ol.proj.proj4.cache_ = proj4; +}; + + +/** + * Get proj4. + * @return {proj4} The proj4 function set above or available globally. + */ +ol.proj.proj4.get = function() { + return ol.proj.proj4.cache_ || window['proj4']; +}; diff --git a/src/ol/proj/projection.js b/src/ol/proj/projection.js new file mode 100644 index 0000000000..82aa693b6e --- /dev/null +++ b/src/ol/proj/projection.js @@ -0,0 +1,277 @@ +goog.provide('ol.proj.Projection'); + +goog.require('ol'); +goog.require('ol.proj.Units'); +goog.require('ol.proj.proj4'); + + +/** + * @classdesc + * Projection definition class. One of these is created for each projection + * supported in the application and stored in the {@link ol.proj} namespace. + * You can use these in applications, but this is not required, as API params + * and options use {@link ol.ProjectionLike} which means the simple string + * code will suffice. + * + * You can use {@link ol.proj.get} to retrieve the object for a particular + * projection. + * + * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together + * with the following aliases: + * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, + * urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84, + * http://www.opengis.net/gml/srs/epsg.xml#4326, + * urn:x-ogc:def:crs:EPSG:4326 + * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913, + * urn:ogc:def:crs:EPSG:6.18:3:3857, + * http://www.opengis.net/gml/srs/epsg.xml#3857 + * + * If you use proj4js, aliases can be added using `proj4.defs()`; see + * [documentation](https://github.com/proj4js/proj4js). To set an alternative + * namespace for proj4, use {@link ol.proj.setProj4}. + * + * @constructor + * @param {olx.ProjectionOptions} options Projection options. + * @struct + * @api stable + */ +ol.proj.Projection = function(options) { + + /** + * @private + * @type {string} + */ + this.code_ = options.code; + + /** + * @private + * @type {ol.proj.Units} + */ + this.units_ = /** @type {ol.proj.Units} */ (options.units); + + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = options.extent !== undefined ? options.extent : null; + + /** + * @private + * @type {ol.Extent} + */ + this.worldExtent_ = options.worldExtent !== undefined ? + options.worldExtent : null; + + /** + * @private + * @type {string} + */ + this.axisOrientation_ = options.axisOrientation !== undefined ? + options.axisOrientation : 'enu'; + + /** + * @private + * @type {boolean} + */ + this.global_ = options.global !== undefined ? options.global : false; + + /** + * @private + * @type {boolean} + */ + this.canWrapX_ = !!(this.global_ && this.extent_); + + /** + * @private + * @type {function(number, ol.Coordinate):number|undefined} + */ + this.getPointResolutionFunc_ = options.getPointResolution; + + /** + * @private + * @type {ol.tilegrid.TileGrid} + */ + this.defaultTileGrid_ = null; + + /** + * @private + * @type {number|undefined} + */ + this.metersPerUnit_ = options.metersPerUnit; + + var code = options.code; + ol.DEBUG && console.assert(code !== undefined, + 'Option "code" is required for constructing instance'); + if (ol.ENABLE_PROJ4JS) { + var proj4js = ol.proj.proj4.get(); + if (typeof proj4js == 'function') { + var def = proj4js.defs(code); + if (def !== undefined) { + if (def.axis !== undefined && options.axisOrientation === undefined) { + this.axisOrientation_ = def.axis; + } + if (options.metersPerUnit === undefined) { + this.metersPerUnit_ = def.to_meter; + } + if (options.units === undefined) { + this.units_ = def.units; + } + } + } + } + +}; + + +/** + * @return {boolean} The projection is suitable for wrapping the x-axis + */ +ol.proj.Projection.prototype.canWrapX = function() { + return this.canWrapX_; +}; + + +/** + * Get the code for this projection, e.g. 'EPSG:4326'. + * @return {string} Code. + * @api stable + */ +ol.proj.Projection.prototype.getCode = function() { + return this.code_; +}; + + +/** + * Get the validity extent for this projection. + * @return {ol.Extent} Extent. + * @api stable + */ +ol.proj.Projection.prototype.getExtent = function() { + return this.extent_; +}; + + +/** + * Get the units of this projection. + * @return {ol.proj.Units} Units. + * @api stable + */ +ol.proj.Projection.prototype.getUnits = function() { + return this.units_; +}; + + +/** + * Get the amount of meters per unit of this projection. If the projection is + * not configured with `metersPerUnit` or a units identifier, the return is + * `undefined`. + * @return {number|undefined} Meters. + * @api stable + */ +ol.proj.Projection.prototype.getMetersPerUnit = function() { + return this.metersPerUnit_ || ol.proj.Units.METERS_PER_UNIT[this.units_]; +}; + + +/** + * Get the world extent for this projection. + * @return {ol.Extent} Extent. + * @api + */ +ol.proj.Projection.prototype.getWorldExtent = function() { + return this.worldExtent_; +}; + + +/** + * Get the axis orientation of this projection. + * Example values are: + * enu - the default easting, northing, elevation. + * neu - northing, easting, up - useful for "lat/long" geographic coordinates, + * or south orientated transverse mercator. + * wnu - westing, northing, up - some planetary coordinate systems have + * "west positive" coordinate systems + * @return {string} Axis orientation. + */ +ol.proj.Projection.prototype.getAxisOrientation = function() { + return this.axisOrientation_; +}; + + +/** + * Is this projection a global projection which spans the whole world? + * @return {boolean} Whether the projection is global. + * @api stable + */ +ol.proj.Projection.prototype.isGlobal = function() { + return this.global_; +}; + + +/** +* Set if the projection is a global projection which spans the whole world +* @param {boolean} global Whether the projection is global. +* @api stable +*/ +ol.proj.Projection.prototype.setGlobal = function(global) { + this.global_ = global; + this.canWrapX_ = !!(global && this.extent_); +}; + + +/** + * @return {ol.tilegrid.TileGrid} The default tile grid. + */ +ol.proj.Projection.prototype.getDefaultTileGrid = function() { + return this.defaultTileGrid_; +}; + + +/** + * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid. + */ +ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { + this.defaultTileGrid_ = tileGrid; +}; + + +/** + * Set the validity extent for this projection. + * @param {ol.Extent} extent Extent. + * @api stable + */ +ol.proj.Projection.prototype.setExtent = function(extent) { + this.extent_ = extent; + this.canWrapX_ = !!(this.global_ && extent); +}; + + +/** + * Set the world extent for this projection. + * @param {ol.Extent} worldExtent World extent + * [minlon, minlat, maxlon, maxlat]. + * @api + */ +ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) { + this.worldExtent_ = worldExtent; +}; + + +/** + * Set the getPointResolution function for this projection. + * @param {function(number, ol.Coordinate):number} func Function + * @api + */ +ol.proj.Projection.prototype.setGetPointResolution = function(func) { + this.getPointResolutionFunc_ = func; +}; + + +/** + * Get the custom point resolution function for this projection (if set). + * @return {function(number, ol.Coordinate):number|undefined} The custom point + * resolution function (if set). + */ +ol.proj.Projection.prototype.getPointResolutionFunc = function() { + return this.getPointResolutionFunc_; +}; diff --git a/src/ol/proj/projections.js b/src/ol/proj/projections.js new file mode 100644 index 0000000000..a81092a5e2 --- /dev/null +++ b/src/ol/proj/projections.js @@ -0,0 +1,38 @@ +goog.provide('ol.proj.projections'); + + +/** + * @private + * @type {Object.} + */ +ol.proj.projections.cache_ = {}; + + +/** + * Clear the projections cache. + */ +ol.proj.projections.clear = function() { + ol.proj.projections.cache_ = {}; +}; + + +/** + * Get a cached projection by code. + * @param {string} code The code for the projection. + * @return {ol.proj.Projection} The projection (if cached). + */ +ol.proj.projections.get = function(code) { + var projections = ol.proj.projections.cache_; + return projections[code] || null; +}; + + +/** + * Add a projection to the cache. + * @param {string} code The projection code. + * @param {ol.proj.Projection} projection The projection to cache. + */ +ol.proj.projections.add = function(code, projection) { + var projections = ol.proj.projections.cache_; + projections[code] = projection; +}; diff --git a/src/ol/proj/transforms.js b/src/ol/proj/transforms.js new file mode 100644 index 0000000000..4bd86d7ce0 --- /dev/null +++ b/src/ol/proj/transforms.js @@ -0,0 +1,80 @@ +goog.provide('ol.proj.transforms'); + +goog.require('ol'); +goog.require('ol.obj'); + + +/** + * @private + * @type {Object.>} + */ +ol.proj.transforms.cache_ = {}; + + +/** + * Clear the transform cache. + */ +ol.proj.transforms.clear = function() { + ol.proj.transforms.cache_ = {}; +}; + + +/** + * Registers a conversion function to convert coordinates from the source + * projection to the destination projection. + * + * @param {ol.proj.Projection} source Source. + * @param {ol.proj.Projection} destination Destination. + * @param {ol.TransformFunction} transformFn Transform. + */ +ol.proj.transforms.add = function(source, destination, transformFn) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + if (!(sourceCode in transforms)) { + transforms[sourceCode] = {}; + } + transforms[sourceCode][destinationCode] = transformFn; +}; + + +/** + * Unregisters the conversion function to convert coordinates from the source + * projection to the destination projection. This method is used to clean up + * cached transforms during testing. + * + * @param {ol.proj.Projection} source Source projection. + * @param {ol.proj.Projection} destination Destination projection. + * @return {ol.TransformFunction} transformFn The unregistered transform. + */ +ol.proj.transforms.remove = function(source, destination) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.proj.transforms.cache_; + ol.DEBUG && console.assert(sourceCode in transforms, + 'sourceCode should be in transforms'); + ol.DEBUG && console.assert(destinationCode in transforms[sourceCode], + 'destinationCode should be in transforms of sourceCode'); + var transform = transforms[sourceCode][destinationCode]; + delete transforms[sourceCode][destinationCode]; + if (ol.obj.isEmpty(transforms[sourceCode])) { + delete transforms[sourceCode]; + } + return transform; +}; + + +/** + * Get a transform given a source code and a destination code. + * @param {string} sourceCode The code for the source projection. + * @param {string} destinationCode The code for the destination projection. + * @return {ol.TransformFunction|undefined} The transform function (if found). + */ +ol.proj.transforms.get = function(sourceCode, destinationCode) { + var transform; + var transforms = ol.proj.transforms.cache_; + if (sourceCode in transforms && destinationCode in transforms[sourceCode]) { + transform = transforms[sourceCode][destinationCode]; + } + return transform; +}; diff --git a/src/ol/proj/units.js b/src/ol/proj/units.js new file mode 100644 index 0000000000..3529a06ad5 --- /dev/null +++ b/src/ol/proj/units.js @@ -0,0 +1,32 @@ +goog.provide('ol.proj.Units'); + +goog.require('ol.sphere.NORMAL'); + + +/** + * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or + * `'us-ft'`. + * @enum {string} + */ +ol.proj.Units = { + DEGREES: 'degrees', + FEET: 'ft', + METERS: 'm', + PIXELS: 'pixels', + TILE_PIXELS: 'tile-pixels', + USFEET: 'us-ft' +}; + + +/** + * Meters per unit lookup table. + * @const + * @type {Object.} + * @api stable + */ +ol.proj.Units.METERS_PER_UNIT = {}; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.DEGREES] = + 2 * Math.PI * ol.sphere.NORMAL.radius / 360; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; +ol.proj.Units.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937; diff --git a/src/ol/reproj/index.js b/src/ol/reproj/index.js index a804c4eec2..482b7d4d78 100644 --- a/src/ol/reproj/index.js +++ b/src/ol/reproj/index.js @@ -50,7 +50,7 @@ ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, // calculate the ideal resolution of the source data var sourceResolution = - targetProj.getPointResolution(targetResolution, targetCenter); + ol.proj.getPointResolution(targetProj, targetResolution, targetCenter); var targetMetersPerUnit = targetProj.getMetersPerUnit(); if (targetMetersPerUnit !== undefined) { @@ -66,7 +66,7 @@ ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, // in order to achieve optimal results. var compensationFactor = - sourceProj.getPointResolution(sourceResolution, sourceCenter) / + ol.proj.getPointResolution(sourceProj, sourceResolution, sourceCenter) / sourceResolution; if (isFinite(compensationFactor) && compensationFactor > 0) { diff --git a/src/ol/tilegrid/index.js b/src/ol/tilegrid/index.js index 74c21dbfa1..2b39e0cec7 100644 --- a/src/ol/tilegrid/index.js +++ b/src/ol/tilegrid/index.js @@ -6,7 +6,6 @@ goog.require('ol.extent'); goog.require('ol.extent.Corner'); goog.require('ol.obj'); goog.require('ol.proj'); -goog.require('ol.proj.METERS_PER_UNIT'); goog.require('ol.proj.Units'); goog.require('ol.tilegrid.TileGrid'); diff --git a/src/ol/view.js b/src/ol/view.js index 2cd8b2deed..9634b2b1c4 100644 --- a/src/ol/view.js +++ b/src/ol/view.js @@ -14,7 +14,6 @@ goog.require('ol.extent'); goog.require('ol.geom.Polygon'); goog.require('ol.geom.SimpleGeometry'); goog.require('ol.proj'); -goog.require('ol.proj.METERS_PER_UNIT'); goog.require('ol.proj.Units'); diff --git a/test/spec/ol/format/kml.test.js b/test/spec/ol/format/kml.test.js index 34437c4eb5..d899f84a68 100644 --- a/test/spec/ol/format/kml.test.js +++ b/test/spec/ol/format/kml.test.js @@ -17,6 +17,7 @@ goog.require('ol.style.Fill'); goog.require('ol.style.Icon'); goog.require('ol.proj'); goog.require('ol.proj.Projection'); +goog.require('ol.proj.transforms'); goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); goog.require('ol.style.Text'); @@ -358,9 +359,9 @@ describe('ol.format.KML', function() { ''; expect(node).to.xmleql(ol.xml.parse(text)); - ol.proj.removeTransform( + ol.proj.transforms.remove( ol.proj.get('EPSG:4326'), ol.proj.get('double')); - ol.proj.removeTransform( + ol.proj.transforms.remove( ol.proj.get('double'), ol.proj.get('EPSG:4326')); }); diff --git a/test/spec/ol/proj/epsg3857.test.js b/test/spec/ol/proj/epsg3857.test.js index 2402b2854e..7dc47dcf9b 100644 --- a/test/spec/ol/proj/epsg3857.test.js +++ b/test/spec/ol/proj/epsg3857.test.js @@ -49,7 +49,7 @@ describe('ol.proj.EPSG3857', function() { var epsg3857 = ol.proj.get('EPSG:3857'); var resolution = 19.11; var point = [0, 0]; - expect(epsg3857.getPointResolution(resolution, point)). + expect(ol.proj.getPointResolution(epsg3857, resolution, point)). to.roughlyEqual(19.11, 1e-1); }); @@ -60,7 +60,7 @@ describe('ol.proj.EPSG3857', function() { var epsg4326 = ol.proj.get('EPSG:4326'); var resolution = 19.11; var point = ol.proj.transform([0, 43.65], epsg4326, epsg3857); - expect(epsg3857.getPointResolution(resolution, point)). + expect(ol.proj.getPointResolution(epsg3857, resolution, point)). to.roughlyEqual(19.11 * Math.cos(Math.PI * 43.65 / 180), 1e-9); }); @@ -72,7 +72,7 @@ describe('ol.proj.EPSG3857', function() { var latitude; for (latitude = 0; latitude <= 85; ++latitude) { var point = ol.proj.transform([0, latitude], epsg4326, epsg3857); - expect(epsg3857.getPointResolution(resolution, point)). + expect(ol.proj.getPointResolution(epsg3857, resolution, point)). to.roughlyEqual(19.11 * Math.cos(Math.PI * latitude / 180), 1e-9); } }); diff --git a/test/spec/ol/proj/index.test.js b/test/spec/ol/proj/index.test.js index 373825cd80..6f2cb44de7 100644 --- a/test/spec/ol/proj/index.test.js +++ b/test/spec/ol/proj/index.test.js @@ -310,7 +310,7 @@ describe('ol.proj', function() { it('numerically estimates point scale at the equator', function() { var googleProjection = ol.proj.get('GOOGLE'); - expect(googleProjection.getPointResolution(1, [0, 0])). + expect(ol.proj.getPointResolution(googleProjection, 1, [0, 0])). to.roughlyEqual(1, 1e-1); }); @@ -320,8 +320,8 @@ describe('ol.proj', function() { var point, y; for (y = -20; y <= 20; ++y) { point = [0, 1000000 * y]; - expect(googleProjection.getPointResolution(1, point)).to.roughlyEqual( - epsg3857Projection.getPointResolution(1, point), 1e-1); + expect(ol.proj.getPointResolution(googleProjection, 1, point)).to.roughlyEqual( + ol.proj.getPointResolution(epsg3857Projection, 1, point), 1e-1); } }); @@ -332,8 +332,8 @@ describe('ol.proj', function() { for (x = -20; x <= 20; x += 2) { for (y = -20; y <= 20; y += 2) { point = [1000000 * x, 1000000 * y]; - expect(googleProjection.getPointResolution(1, point)).to.roughlyEqual( - epsg3857Projection.getPointResolution(1, point), 1e-1); + expect(ol.proj.getPointResolution(googleProjection, 1, point)).to.roughlyEqual( + ol.proj.getPointResolution(epsg3857Projection, 1, point), 1e-1); } } }); @@ -451,37 +451,6 @@ describe('ol.proj', function() { }); }); - describe('ol.proj.removeTransform()', function() { - - var extent = [180, -90, 180, 90]; - var units = 'degrees'; - - it('removes functions cached by addTransform', function() { - var foo = new ol.proj.Projection({ - code: 'foo', - units: units, - extent: extent - }); - var bar = new ol.proj.Projection({ - code: 'bar', - units: units, - extent: extent - }); - var transform = function(input, output, dimension) { - return input; - }; - ol.proj.addTransform(foo, bar, transform); - expect(ol.proj.transforms_).not.to.be(undefined); - expect(ol.proj.transforms_.foo).not.to.be(undefined); - expect(ol.proj.transforms_.foo.bar).to.be(transform); - - var removed = ol.proj.removeTransform(foo, bar); - expect(removed).to.be(transform); - expect(ol.proj.transforms_.foo).to.be(undefined); - }); - - }); - describe('ol.proj.transform()', function() { it('transforms a 2d coordinate', function() { diff --git a/test/spec/ol/proj/transforms.test.js b/test/spec/ol/proj/transforms.test.js new file mode 100644 index 0000000000..3ba0965038 --- /dev/null +++ b/test/spec/ol/proj/transforms.test.js @@ -0,0 +1,36 @@ +goog.provide('ol.test.proj.transforms'); + +goog.require('ol.proj.Projection'); +goog.require('ol.proj.transforms'); + + +describe('ol.proj.transforms.remove()', function() { + + var extent = [180, -90, 180, 90]; + var units = 'degrees'; + + it('removes functions cached by ol.proj.transforms.add()', function() { + var foo = new ol.proj.Projection({ + code: 'foo', + units: units, + extent: extent + }); + var bar = new ol.proj.Projection({ + code: 'bar', + units: units, + extent: extent + }); + var transform = function(input, output, dimension) { + return input; + }; + ol.proj.transforms.add(foo, bar, transform); + expect(ol.proj.transforms.cache_).not.to.be(undefined); + expect(ol.proj.transforms.cache_.foo).not.to.be(undefined); + expect(ol.proj.transforms.cache_.foo.bar).to.be(transform); + + var removed = ol.proj.transforms.remove(foo, bar); + expect(removed).to.be(transform); + expect(ol.proj.transforms.cache_.foo).to.be(undefined); + }); + +}); diff --git a/test/spec/ol/tilegrid/tilegrid.test.js b/test/spec/ol/tilegrid/tilegrid.test.js index 484afa0d63..5e35258bf2 100644 --- a/test/spec/ol/tilegrid/tilegrid.test.js +++ b/test/spec/ol/tilegrid/tilegrid.test.js @@ -5,7 +5,6 @@ goog.require('ol.TileRange'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.proj.EPSG3857'); -goog.require('ol.proj.METERS_PER_UNIT'); goog.require('ol.proj.Projection'); goog.require('ol.tilegrid'); goog.require('ol.tilegrid.TileGrid');