From 8b89f5b6892b4af422477ef2e5b32d80aa0bd323 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 8 Jul 2014 01:08:00 +0200 Subject: [PATCH] Update API to work with custom transforms, including proj4js All transparent proj4js handling is now in ol.proj.get, and a new addCoordinateTransforms function makes it easy to configure custom transform functions. ol.Proj4jsProjection is no longer needed. --- externs/olx.js | 30 ++-- src/ol/proj/proj.js | 352 +++++++++++++++++--------------------------- 2 files changed, 150 insertions(+), 232 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 4dc7e75a87..af32ea0812 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -275,34 +275,28 @@ olx.OverlayOptions.prototype.insertFirst; /** - * Object literal with config options for the Proj4js projection. - * @typedef {{code: string, - * extent: (ol.Extent|undefined), - * global: (boolean|undefined)}} + * Object literal with forward and inverse coordinate transforms. + * @typedef {{forward: function(ol.Coordinate): ol.Coordinate, + * inverse: function(ol.Coordinate): ol.Coordinate}} * @api */ -olx.Proj4jsProjectionOptions; +olx.CoordinateTransforms; /** - * The SRS identifier code, e.g. `EPSG:31256`. - * @type {string} + * The forward transform function that takes a {@link ol.Coordinate} as argument + * and returns the transformed {@link ol.Coordinate}. + * @type {function(ol.Coordinate): ol.Coordinate} */ -olx.Proj4jsProjectionOptions.prototype.code; +olx.CoordinateTransforms.prototype.forward; /** - * The validity extent for the SRS. - * @type {ol.Extent|undefined} + * The inverse transform function that takes a {@link ol.Coordinate} as argument + * and returns the transformed {@link ol.Coordinate}. + * @type {function(ol.Coordinate): ol.Coordinate} */ -olx.Proj4jsProjectionOptions.prototype.extent; - - -/** - * Whether the projection is valid for the whole globe. Default is `false`. - * @type {boolean|undefined} - */ -olx.Proj4jsProjectionOptions.prototype.global; +olx.CoordinateTransforms.prototype.inverse; /** diff --git a/src/ol/proj/proj.js b/src/ol/proj/proj.js index c205ffffe8..20d2397c50 100644 --- a/src/ol/proj/proj.js +++ b/src/ol/proj/proj.js @@ -64,10 +64,10 @@ ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1; * geographic (EPSG:4326) and web or spherical mercator (EPSG:3857) * coordinate reference systems. * - * Additional transforms may be added by using the - * {@link http://proj4js.org/|proj4js} library. If the proj4js library is - * included, the method will work between any two coordinate - * reference systems with proj4js definitions. + * Additional transforms may be added by using the {@link http://proj4js.org/} + * library. If the proj4js library is loaded, transforms will work between any + * coordinate reference systems with proj4js definitions. These definitions can + * be obtained from {@link http://epsg.io/}. * * @constructor * @param {olx.ProjectionOptions} options Projection options. @@ -136,19 +136,6 @@ ol.proj.Projection.prototype.getExtent = function() { }; -/** - * Get the resolution of the point in degrees. 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 center - * 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 Resolution. - * @param {ol.Coordinate} point Point. - * @return {number} Point resolution. - */ -ol.proj.Projection.prototype.getPointResolution = goog.abstractMethod; - - /** * Get the units of this projection. * @return {ol.proj.Units} Units. @@ -209,80 +196,42 @@ ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) { }; - /** - * @constructor - * @extends {ol.proj.Projection} - * @param {Proj4js.Proj} proj4jsProj Proj4js projection. - * @param {olx.Proj4jsProjectionOptions} options Proj4js projection options. - * @private - * @struct + * Set the validity extent for this projection. + * @param {ol.Extent} extent Extent. + * @api */ -ol.Proj4jsProjection_ = function(proj4jsProj, options) { - - var units = /** @type {ol.proj.Units} */ (proj4jsProj.units); - - var config = /** @type {olx.ProjectionOptions} */ ({ - units: units, - axisOrientation: proj4jsProj.axis - }); - goog.object.extend(config, options); - - goog.base(this, config); - - /** - * @private - * @type {Proj4js.Proj} - */ - this.proj4jsProj_ = proj4jsProj; - - /** - * @private - * @type {?ol.TransformFunction} - */ - this.toEPSG4326_ = null; - -}; -goog.inherits(ol.Proj4jsProjection_, ol.proj.Projection); - - -/** - * @inheritDoc - */ -ol.Proj4jsProjection_.prototype.getMetersPerUnit = function() { - var metersPerUnit = this.proj4jsProj_.to_meter; - if (!goog.isDef(metersPerUnit)) { - metersPerUnit = ol.proj.METERS_PER_UNIT[this.units_]; - } - return metersPerUnit; +ol.proj.Projection.prototype.setExtent = function(extent) { + this.extent_ = extent; }; /** - * @inheritDoc + * Get the resolution of the point in degrees. 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 center + * 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 Resolution. + * @param {ol.Coordinate} point Point. + * @return {number} Point resolution. */ -ol.Proj4jsProjection_.prototype.getPointResolution = - function(resolution, point) { +ol.proj.Projection.prototype.getPointResolution = function(resolution, point) { if (this.getUnits() == 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. - if (goog.isNull(this.toEPSG4326_)) { - this.toEPSG4326_ = ol.proj.getTransformFromProjections( - this, ol.proj.getProj4jsProjectionFromCode_({ - code: 'EPSG:4326', - extent: null - })); - } + 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 = this.toEPSG4326_(vertices, vertices, 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( @@ -298,21 +247,6 @@ ol.Proj4jsProjection_.prototype.getPointResolution = }; -/** - * @return {Proj4js.Proj} Proj4js projection. - */ -ol.Proj4jsProjection_.prototype.getProj4jsProj = function() { - return this.proj4jsProj_; -}; - - -/** - * @private - * @type {Object.} - */ -ol.proj.proj4jsProjections_ = {}; - - /** * @private * @type {Object.} @@ -331,17 +265,21 @@ ol.proj.transforms_ = {}; * Registers transformation functions that don't alter coordinates. Those allow * to transform between projections with equal meaning. * - * @param {Array.} projections Projections. + * @param {Array.} projections + * Projections. + * @return {Array.} The added equivalent projections. + * @api */ ol.proj.addEquivalentProjections = function(projections) { - ol.proj.addProjections(projections); - goog.array.forEach(projections, function(source) { - goog.array.forEach(projections, function(destination) { + var addedProjections = ol.proj.addProjections(projections); + goog.array.forEach(addedProjections, function(source) { + goog.array.forEach(addedProjections, function(destination) { if (source !== destination) { ol.proj.addTransform(source, destination, ol.proj.cloneTransform); } }); }); + return addedProjections; }; @@ -369,39 +307,37 @@ ol.proj.addEquivalentTransforms = }; -/** - * @param {ol.Proj4jsProjection_} proj4jsProjection Proj4js projection. - * @private - */ -ol.proj.addProj4jsProjection_ = function(proj4jsProjection) { - var proj4jsProjections = ol.proj.proj4jsProjections_; - var code = proj4jsProjection.getCode(); - goog.asserts.assert(!goog.object.containsKey(proj4jsProjections, code)); - proj4jsProjections[code] = proj4jsProjection; -}; - - /** * Add a Projection object to the list of supported projections. * - * @param {ol.proj.Projection} projection Projection object. + * @param {ol.proj.Projection|olx.ProjectionOptions} projection Projection + * instance or configuration. + * @return {ol.proj.Projection} The added projection. * @api */ ol.proj.addProjection = function(projection) { var projections = ol.proj.projections_; - var code = projection.getCode(); - projections[code] = projection; - ol.proj.addTransform(projection, projection, ol.proj.cloneTransform); + var proj = projection instanceof ol.proj.Projection ? + projection : + new ol.proj.Projection(/** @type {olx.ProjectionOptions} */ (projection)); + var code = proj.getCode(); + projections[code] = proj; + ol.proj.addTransform(proj, proj, ol.proj.cloneTransform); + return proj; }; /** - * @param {Array.} projections Projections. + * @param {Array.} projections + * Projections. + * @return {Array.} The added projections. */ ol.proj.addProjections = function(projections) { + var addedProjections = []; goog.array.forEach(projections, function(projection) { - ol.proj.addProjection(projection); + addedProjections.push(ol.proj.addProjection(projection)); }); + return addedProjections; }; @@ -409,9 +345,6 @@ ol.proj.addProjections = function(projections) { * FIXME empty description for jsdoc */ ol.proj.clearAllProjections = function() { - if (ol.ENABLE_PROJ4JS) { - ol.proj.proj4jsProjections_ = {}; - } ol.proj.projections_ = {}; ol.proj.transforms_ = {}; }; @@ -453,6 +386,62 @@ ol.proj.addTransform = function(source, destination, transformFn) { }; +/** + * Registers coordinate transform functions to convert coordinates between the + * source projection and the destination projection. + * + * @param {ol.proj.ProjectionLike} source Source projection. + * @param {ol.proj.ProjectionLike} destination Destination projection. + * @param {olx.CoordinateTransforms} transforms Forward and inverse transform + * functions. + * @api + */ +ol.proj.addCoordinateTransforms = function(source, destination, transforms) { + var sourceProj = ol.proj.get(source); + var destProj = ol.proj.get(destination); + var forward, inverse; + if (sourceProj === destProj) { + forward = ol.proj.cloneTransform; + inverse = ol.proj.cloneTransform; + } else { + forward = + ol.proj.createTransformFromCoordinateTransform(transforms.forward); + inverse = + ol.proj.createTransformFromCoordinateTransform(transforms.inverse); + } + ol.proj.addTransform(sourceProj, destProj, forward); + ol.proj.addTransform(destProj, sourceProj, inverse); +}; + + +/** + * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform + * function. + * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate + * transform. + * @return {ol.TransformFunction} Transform function. + */ +ol.proj.createTransformFromCoordinateTransform = function(transform) { + return /** @type {ol.TransformFunction} */ ( + function(input, opt_output, opt_dimension) { + var length = input.length; + var dimension = goog.isDef(opt_dimension) ? opt_dimension : 2; + var output = goog.isDef(opt_output) ? opt_output : new Array(length); + var point, i, j; + for (i = 0; i < length; i += dimension) { + point = transform([input[i], input[i + 1]]); + output[i] = point[0]; + output[i + 1] = point[1]; + for (j = dimension - 1; j >= 2; --j) { + output[i + j] = input[i + j]; + } + } + return output; + } + ); +}; + + /** * Unregisters the conversion function to convert coordinates from the source * projection to the destination projection. This method is used to clean up @@ -493,16 +482,44 @@ ol.proj.get = function(projectionLike) { projection = projectionLike; } else if (goog.isString(projectionLike)) { var code = projectionLike; - projection = ol.proj.projections_[code]; + var projections = ol.proj.projections_; + projection = projections[code]; if (ol.HAVE_PROJ4JS && !goog.isDef(projection)) { - projection = ol.proj.getProj4jsProjectionFromCode_({ - code: code, - extent: null - }); - } - if (!goog.isDef(projection)) { - goog.asserts.assert(goog.isDef(projection)); - projection = null; + var proj4jsProj; + var def = proj4.defs[code]; + if (goog.isDef(def)) { + proj4jsProj = new proj4.Proj(code); + var units = proj4jsProj.units; + if (!goog.isDef(units)) { + if (goog.isDef(proj4jsProj.to_meter)) { + units = proj4jsProj.to_meter.toString(); + ol.proj.METERS_PER_UNIT[units] = proj4jsProj.to_meter; + } + } + projection = new ol.proj.Projection({ + code: code, + units: units, + axisOrientation: proj4jsProj.axis + }); + ol.proj.addProjection(projection); + var currentCode, currentDef, currentProj, currentProj4jsProj; + for (currentCode in projections) { + currentDef = proj4.defs[currentCode]; + if (goog.isDef(currentDef)) { + currentProj4jsProj = new proj4.Proj(currentCode); + currentProj = ol.proj.get(currentCode); + if (currentDef === def) { + ol.proj.addEquivalentProjections([currentProj, projection]); + } else { + ol.proj.addCoordinateTransforms(currentProj, projection, + proj4(currentProj4jsProj, proj4jsProj)); + } + } + } + } else { + goog.asserts.assert(goog.isDef(projection)); + projection = null; + } } } else { projection = null; @@ -511,32 +528,6 @@ ol.proj.get = function(projectionLike) { }; -/** - * @param {olx.Proj4jsProjectionOptions} options Proj4js projection options. - * @private - * @return {ol.Proj4jsProjection_} Proj4js projection. - */ -ol.proj.getProj4jsProjectionFromCode_ = function(options) { - var code = options.code; - var proj4jsProjections = ol.proj.proj4jsProjections_; - var proj4jsProjection = proj4jsProjections[code]; - if (!goog.isDef(proj4jsProjection)) { - var proj4jsProj = new Proj4js.Proj(code); - var srsCode = proj4jsProj.srsCode; - proj4jsProjection = proj4jsProjections[srsCode]; - if (!goog.isDef(proj4jsProjection)) { - var config = /** @type {olx.Proj4jsProjectionOptions} */ - (goog.object.clone(options)); - config.code = srsCode; - proj4jsProjection = new ol.Proj4jsProjection_(proj4jsProj, config); - proj4jsProjections[srsCode] = proj4jsProjection; - } - proj4jsProjections[code] = proj4jsProjection; - } - return proj4jsProjection; -}; - - /** * Checks if two projections are the same, that is every coordinate in one * projection does represent the same geographic point as the same coordinate in @@ -596,61 +587,6 @@ ol.proj.getTransformFromProjections = goog.object.containsKey(transforms[sourceCode], destinationCode)) { transform = transforms[sourceCode][destinationCode]; } - if (ol.HAVE_PROJ4JS && !goog.isDef(transform)) { - var proj4jsSource; - if (sourceProjection instanceof ol.Proj4jsProjection_) { - proj4jsSource = sourceProjection; - } else { - proj4jsSource = - ol.proj.getProj4jsProjectionFromCode_({ - code: sourceCode, - extent: null - }); - } - var sourceProj4jsProj = proj4jsSource.getProj4jsProj(); - var proj4jsDestination; - if (destinationProjection instanceof ol.Proj4jsProjection_) { - proj4jsDestination = destinationProjection; - } else { - proj4jsDestination = - ol.proj.getProj4jsProjectionFromCode_({ - code: destinationCode, - extent: null - }); - } - var destinationProj4jsProj = proj4jsDestination.getProj4jsProj(); - transform = - /** - * @param {Array.} input Input coordinate values. - * @param {Array.=} opt_output Output array of coordinates. - * @param {number=} opt_dimension Dimension. - * @return {Array.} Output coordinate values. - */ - function(input, opt_output, opt_dimension) { - var length = input.length, - dimension = opt_dimension > 1 ? opt_dimension : 2, - output = opt_output; - if (!goog.isDef(output)) { - if (dimension > 2) { - // preserve values beyond second dimension - output = input.slice(); - } else { - output = new Array(length); - } - } - goog.asserts.assert(output.length % dimension === 0); - var proj4jsPoint; - for (var i = 0; i < length; i += dimension) { - proj4jsPoint = new Proj4js.Point(input[i], input[i + 1]); - proj4jsPoint = Proj4js.transform( - sourceProj4jsProj, destinationProj4jsProj, proj4jsPoint); - output[i] = proj4jsPoint.x; - output[i + 1] = proj4jsPoint.y; - } - return output; - }; - ol.proj.addTransform(sourceProjection, destinationProjection, transform); - } if (!goog.isDef(transform)) { goog.asserts.assert(goog.isDef(transform)); transform = ol.proj.identityTransform; @@ -749,15 +685,3 @@ ol.proj.transformWithProjections = sourceProjection, destinationProjection); return transformFn(point); }; - - -/** - * @param {olx.Proj4jsProjectionOptions} options Proj4js projection options. - * @return {ol.proj.Projection} Proj4js projection. - * @api - */ -ol.proj.configureProj4jsProjection = function(options) { - goog.asserts.assert(!goog.object.containsKey( - ol.proj.proj4jsProjections_, options.code)); - return ol.proj.getProj4jsProjectionFromCode_(options); -};