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.
This commit is contained in:
Andreas Hocevar
2014-07-08 01:08:00 +02:00
parent dc09b0a27f
commit 8b89f5b689
2 changed files with 150 additions and 232 deletions

View File

@@ -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;
/**

View File

@@ -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 <transform> 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.<string, ol.Proj4jsProjection_>}
*/
ol.proj.proj4jsProjections_ = {};
/**
* @private
* @type {Object.<string, ol.proj.Projection>}
@@ -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.<ol.proj.Projection>} projections Projections.
* @param {Array.<ol.proj.Projection|olx.ProjectionOptions>} projections
* Projections.
* @return {Array.<ol.proj.Projection>} 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.<ol.proj.Projection>} projections Projections.
* @param {Array.<ol.proj.Projection|olx.ProjectionOptions>} projections
* Projections.
* @return {Array.<ol.proj.Projection>} 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.<number>} input Input coordinate values.
* @param {Array.<number>=} opt_output Output array of coordinates.
* @param {number=} opt_dimension Dimension.
* @return {Array.<number>} 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);
};