Add ol.reproj
This commit is contained in:
181
src/ol/reproj/image.js
Normal file
181
src/ol/reproj/image.js
Normal file
@@ -0,0 +1,181 @@
|
||||
goog.provide('ol.reproj.Image');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('ol.ImageBase');
|
||||
goog.require('ol.ImageState');
|
||||
goog.require('ol.dom');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.proj');
|
||||
goog.require('ol.reproj');
|
||||
goog.require('ol.reproj.triangulation');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.ImageBase}
|
||||
* @param {ol.proj.Projection} sourceProj
|
||||
* @param {ol.proj.Projection} targetProj
|
||||
* @param {ol.Extent} targetExtent
|
||||
* @param {number} targetResolution
|
||||
* @param {number} pixelRatio
|
||||
* @param {function(ol.Extent, number, number, ol.proj.Projection) :
|
||||
* ol.ImageBase} getImageFunction
|
||||
*/
|
||||
ol.reproj.Image = function(sourceProj, targetProj,
|
||||
targetExtent, targetResolution, pixelRatio, getImageFunction) {
|
||||
|
||||
var width = ol.extent.getWidth(targetExtent) / targetResolution;
|
||||
var height = ol.extent.getHeight(targetExtent) / targetResolution;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Canvas2DRenderingContext}
|
||||
*/
|
||||
this.context_ = ol.dom.createCanvasContext2D(width, height);
|
||||
this.context_.imageSmoothingEnabled = true;
|
||||
|
||||
if (goog.DEBUG) {
|
||||
this.context_.fillStyle = 'rgba(255,0,0,0.1)';
|
||||
this.context_.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLCanvasElement}
|
||||
*/
|
||||
this.canvas_ = this.context_.canvas;
|
||||
|
||||
var transformInv = ol.proj.getTransform(targetProj, sourceProj);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!ol.reproj.Triangulation}
|
||||
*/
|
||||
this.triangles_ = ol.reproj.triangulation.createForExtent(
|
||||
targetExtent, transformInv);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.targetResolution_ = targetResolution;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!ol.Extent}
|
||||
*/
|
||||
this.targetExtent_ = targetExtent;
|
||||
|
||||
var srcExtent = ol.reproj.triangulation.getSourceExtent(this.triangles_);
|
||||
|
||||
var idealSourceResolution =
|
||||
targetProj.getPointResolution(targetResolution,
|
||||
ol.extent.getCenter(targetExtent)) *
|
||||
targetProj.getMetersPerUnit() / sourceProj.getMetersPerUnit();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.ImageBase}
|
||||
*/
|
||||
this.srcImage_ = getImageFunction(srcExtent, idealSourceResolution,
|
||||
pixelRatio, sourceProj);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {goog.events.Key}
|
||||
*/
|
||||
this.sourceListenerKey_ = null;
|
||||
|
||||
|
||||
var state = ol.ImageState.LOADED;
|
||||
var attributions = [];
|
||||
|
||||
if (!goog.isNull(this.srcImage_)) {
|
||||
state = ol.ImageState.IDLE;
|
||||
attributions = this.srcImage_.getAttributions();
|
||||
}
|
||||
|
||||
goog.base(this, targetExtent, targetResolution, pixelRatio,
|
||||
state, attributions);
|
||||
};
|
||||
goog.inherits(ol.reproj.Image, ol.ImageBase);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Image.prototype.disposeInternal = function() {
|
||||
if (this.state == ol.ImageState.LOADING) {
|
||||
this.unlistenSource_();
|
||||
}
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Image.prototype.getImage = function(opt_context) {
|
||||
return this.canvas_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ol.reproj.Image.prototype.reproject_ = function() {
|
||||
var srcState = this.srcImage_.getState();
|
||||
if (srcState == ol.ImageState.LOADED) {
|
||||
// render the reprojected content
|
||||
ol.reproj.renderTriangles(this.context_, this.srcImage_.getResolution(),
|
||||
this.targetResolution_, this.targetExtent_,
|
||||
this.triangles_, [{
|
||||
extent: this.srcImage_.getExtent(),
|
||||
image: this.srcImage_.getImage()
|
||||
}]);
|
||||
}
|
||||
this.state = srcState;
|
||||
this.changed();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Image.prototype.load = function() {
|
||||
if (this.state == ol.ImageState.IDLE) {
|
||||
this.state = ol.ImageState.LOADING;
|
||||
this.changed();
|
||||
|
||||
var srcState = this.srcImage_.getState();
|
||||
if (srcState == ol.ImageState.LOADED ||
|
||||
srcState == ol.ImageState.ERROR) {
|
||||
this.reproject_();
|
||||
} else {
|
||||
this.sourceListenerKey_ = this.srcImage_.listen(
|
||||
goog.events.EventType.CHANGE, function(e) {
|
||||
var srcState = this.srcImage_.getState();
|
||||
if (srcState == ol.ImageState.LOADED ||
|
||||
srcState == ol.ImageState.ERROR) {
|
||||
this.unlistenSource_();
|
||||
this.reproject_();
|
||||
}
|
||||
}, false, this);
|
||||
this.srcImage_.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ol.reproj.Image.prototype.unlistenSource_ = function() {
|
||||
goog.asserts.assert(!goog.isNull(this.sourceListenerKey_),
|
||||
'this.sourceListenerKey_ should not be null');
|
||||
goog.events.unlistenByKey(this.sourceListenerKey_);
|
||||
this.sourceListenerKey_ = null;
|
||||
};
|
||||
119
src/ol/reproj/reproj.js
Normal file
119
src/ol/reproj/reproj.js
Normal file
@@ -0,0 +1,119 @@
|
||||
goog.provide('ol.reproj');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.math');
|
||||
|
||||
|
||||
/**
|
||||
* Renders the source into the canvas based on the triangulation.
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {number} sourceResolution
|
||||
* @param {number} targetResolution
|
||||
* @param {ol.Extent} targetExtent
|
||||
* @param {ol.reproj.Triangulation} triangulation
|
||||
* @param {Array.<{extent: ol.Extent,
|
||||
* image: (HTMLCanvasElement|Image)}>} sources
|
||||
*/
|
||||
ol.reproj.renderTriangles = function(context,
|
||||
sourceResolution, targetResolution, targetExtent, triangulation, sources) {
|
||||
goog.array.forEach(triangulation, function(tri, i, arr) {
|
||||
context.save();
|
||||
|
||||
var targetTL = ol.extent.getTopLeft(targetExtent);
|
||||
|
||||
/* Calculate affine transform (src -> dst)
|
||||
* Resulting matrix can be used to transform coordinate
|
||||
* from `sourceProjection` to destination pixels.
|
||||
*
|
||||
* To optimize number of context calls and increase numerical stability,
|
||||
* we also do the following operations:
|
||||
* trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
|
||||
* here before solving the linear system.
|
||||
*
|
||||
* Src points: xi, yi
|
||||
* Dst points: ui, vi
|
||||
* Affine coefficients: aij
|
||||
*
|
||||
* | x0 y0 1 0 0 0 | |a00| |u0|
|
||||
* | x1 y1 1 0 0 0 | |a01| |u1|
|
||||
* | x2 y2 1 0 0 0 | x |a02| = |u2|
|
||||
* | 0 0 0 x0 y0 1 | |a10| |v0|
|
||||
* | 0 0 0 x1 y1 1 | |a11| |v1|
|
||||
* | 0 0 0 x2 y2 1 | |a12| |v2|
|
||||
*/
|
||||
var x0 = tri[0][0][0], y0 = tri[0][0][1],
|
||||
x1 = tri[1][0][0], y1 = tri[1][0][1],
|
||||
x2 = tri[2][0][0], y2 = tri[2][0][1];
|
||||
var u0 = tri[0][1][0] - targetTL[0], v0 = -(tri[0][1][1] - targetTL[1]),
|
||||
u1 = tri[1][1][0] - targetTL[0], v1 = -(tri[1][1][1] - targetTL[1]),
|
||||
u2 = tri[2][1][0] - targetTL[0], v2 = -(tri[2][1][1] - targetTL[1]);
|
||||
var augmentedMatrix = [
|
||||
[x0, y0, 1, 0, 0, 0, u0 / targetResolution],
|
||||
[x1, y1, 1, 0, 0, 0, u1 / targetResolution],
|
||||
[x2, y2, 1, 0, 0, 0, u2 / targetResolution],
|
||||
[0, 0, 0, x0, y0, 1, v0 / targetResolution],
|
||||
[0, 0, 0, x1, y1, 1, v1 / targetResolution],
|
||||
[0, 0, 0, x2, y2, 1, v2 / targetResolution]
|
||||
];
|
||||
var coefs = ol.math.solveLinearSystem(augmentedMatrix);
|
||||
if (goog.isNull(coefs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.setTransform(coefs[0], coefs[3], coefs[1],
|
||||
coefs[4], coefs[2], coefs[5]);
|
||||
|
||||
var pixelSize = sourceResolution;
|
||||
var centroid = [(x0 + x1 + x2) / 3, (y0 + y1 + y2) / 3];
|
||||
|
||||
// moves the `point` farther away from the `anchor`
|
||||
var increasePointDistance = function(point, anchor, increment) {
|
||||
var dir = [point[0] - anchor[0], point[1] - anchor[1]];
|
||||
var distance = Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1]);
|
||||
var scaleFactor = (distance + increment) / distance;
|
||||
return [anchor[0] + scaleFactor * dir[0],
|
||||
anchor[1] + scaleFactor * dir[1]];
|
||||
};
|
||||
|
||||
// enlarge the triangle so that the clip paths of individual triangles
|
||||
// slightly (1px) overlap to prevent transparency errors on triangle edges
|
||||
var p0 = increasePointDistance([x0, y0], centroid, pixelSize);
|
||||
var p1 = increasePointDistance([x1, y1], centroid, pixelSize);
|
||||
var p2 = increasePointDistance([x2, y2], centroid, pixelSize);
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(p0[0], p0[1]);
|
||||
context.lineTo(p1[0], p1[1]);
|
||||
context.lineTo(p2[0], p2[1]);
|
||||
context.closePath();
|
||||
context.clip();
|
||||
|
||||
goog.array.forEach(sources, function(src, i, arr) {
|
||||
context.save();
|
||||
var tlSrcFromData = ol.extent.getTopLeft(src.extent);
|
||||
context.translate(tlSrcFromData[0], tlSrcFromData[1]);
|
||||
context.scale(sourceResolution, -sourceResolution);
|
||||
|
||||
// the image has to be scaled by half a pixel in every direction
|
||||
// in order to prevent artifacts between the original tiles
|
||||
// that are introduced by the canvas antialiasing.
|
||||
context.drawImage(src.image, -0.5, -0.5,
|
||||
src.image.width + 1, src.image.height + 1);
|
||||
context.restore();
|
||||
});
|
||||
|
||||
if (goog.DEBUG) {
|
||||
context.strokeStyle = 'black';
|
||||
context.lineWidth = 2 * pixelSize;
|
||||
context.beginPath();
|
||||
context.moveTo(x0, y0);
|
||||
context.lineTo(x1, y1);
|
||||
context.lineTo(x2, y2);
|
||||
context.closePath();
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
context.restore();
|
||||
});
|
||||
};
|
||||
265
src/ol/reproj/tile.js
Normal file
265
src/ol/reproj/tile.js
Normal file
@@ -0,0 +1,265 @@
|
||||
goog.provide('ol.reproj.Tile');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.object');
|
||||
goog.require('ol.Tile');
|
||||
goog.require('ol.TileState');
|
||||
goog.require('ol.dom');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.proj');
|
||||
goog.require('ol.reproj');
|
||||
goog.require('ol.reproj.triangulation');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.Tile}
|
||||
* @param {ol.proj.Projection} sourceProj
|
||||
* @param {ol.tilegrid.TileGrid} sourceTileGrid
|
||||
* @param {ol.proj.Projection} targetProj
|
||||
* @param {ol.tilegrid.TileGrid} targetTileGrid
|
||||
* @param {number} z
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} pixelRatio
|
||||
* @param {function(number, number, number, number) : ol.Tile} getTileFunction
|
||||
*/
|
||||
ol.reproj.Tile = function(sourceProj, sourceTileGrid,
|
||||
targetProj, targetTileGrid, z, x, y, pixelRatio, getTileFunction) {
|
||||
goog.base(this, [z, x, y], ol.TileState.IDLE);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {HTMLCanvasElement}
|
||||
*/
|
||||
this.canvas_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object.<number, HTMLCanvasElement>}
|
||||
*/
|
||||
this.canvasByContext_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.tilegrid.TileGrid}
|
||||
*/
|
||||
this.sourceTileGrid_ = sourceTileGrid;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.tilegrid.TileGrid}
|
||||
*/
|
||||
this.targetTileGrid_ = targetTileGrid;
|
||||
|
||||
var targetExtent = targetTileGrid.getTileCoordExtent(this.getTileCoord());
|
||||
var targetResolution = targetTileGrid.getResolution(z);
|
||||
var transformInv = ol.proj.getTransform(targetProj, sourceProj);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!ol.reproj.Triangulation}
|
||||
*/
|
||||
this.triangles_ = ol.reproj.triangulation.createForExtent(
|
||||
targetExtent, transformInv);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!Array.<ol.Tile>}
|
||||
*/
|
||||
this.srcTiles_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<goog.events.Key>}
|
||||
*/
|
||||
this.sourcesListenerKeys_ = null;
|
||||
|
||||
var idealSourceResolution =
|
||||
targetProj.getPointResolution(targetResolution,
|
||||
ol.extent.getCenter(targetExtent)) *
|
||||
targetProj.getMetersPerUnit() / sourceProj.getMetersPerUnit();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.srcZ_ = sourceTileGrid.getZForResolution(idealSourceResolution);
|
||||
var srcExtent = ol.reproj.triangulation.getSourceExtent(this.triangles_);
|
||||
|
||||
if (!ol.extent.intersects(sourceTileGrid.getExtent(), srcExtent)) {
|
||||
this.state = ol.TileState.EMPTY;
|
||||
} else {
|
||||
var srcRange = sourceTileGrid.getTileRangeForExtentAndZ(
|
||||
srcExtent, this.srcZ_);
|
||||
|
||||
var srcFullRange = sourceTileGrid.getFullTileRange(this.srcZ_);
|
||||
srcRange.minX = Math.max(srcRange.minX, srcFullRange.minX);
|
||||
srcRange.maxX = Math.min(srcRange.maxX, srcFullRange.maxX);
|
||||
srcRange.minY = Math.max(srcRange.minY, srcFullRange.minY);
|
||||
srcRange.maxY = Math.min(srcRange.maxY, srcFullRange.maxY);
|
||||
|
||||
for (var srcX = srcRange.minX; srcX <= srcRange.maxX; srcX++) {
|
||||
for (var srcY = srcRange.minY; srcY <= srcRange.maxY; srcY++) {
|
||||
var tile = getTileFunction(this.srcZ_, srcX, srcY, pixelRatio);
|
||||
if (tile) {
|
||||
this.srcTiles_.push(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.srcTiles_.length === 0) {
|
||||
this.state = ol.TileState.EMPTY;
|
||||
}
|
||||
}
|
||||
};
|
||||
goog.inherits(ol.reproj.Tile, ol.Tile);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Tile.prototype.disposeInternal = function() {
|
||||
if (this.state == ol.TileState.LOADING) {
|
||||
this.unlistenSources_();
|
||||
}
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Tile.prototype.getImage = function(opt_context) {
|
||||
if (goog.isDef(opt_context)) {
|
||||
var image;
|
||||
var key = goog.getUid(opt_context);
|
||||
if (key in this.canvasByContext_) {
|
||||
return this.canvasByContext_[key];
|
||||
} else if (goog.object.isEmpty(this.canvasByContext_)) {
|
||||
image = this.canvas_;
|
||||
} else {
|
||||
image = /** @type {HTMLCanvasElement} */ (this.canvas_.cloneNode(false));
|
||||
}
|
||||
this.canvasByContext_[key] = image;
|
||||
return image;
|
||||
} else {
|
||||
return this.canvas_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ol.reproj.Tile.prototype.reproject_ = function() {
|
||||
var sources = [];
|
||||
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
|
||||
if (tile && tile.getState() == ol.TileState.LOADED) {
|
||||
sources.push({
|
||||
extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
|
||||
image: tile.getImage()
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
|
||||
// create the canvas
|
||||
var tileCoord = this.getTileCoord();
|
||||
var z = tileCoord[0];
|
||||
var size = this.targetTileGrid_.getTileSize(z);
|
||||
var targetResolution = this.targetTileGrid_.getResolution(z);
|
||||
var srcResolution = this.sourceTileGrid_.getResolution(this.srcZ_);
|
||||
|
||||
var width = goog.isNumber(size) ? size : size[0];
|
||||
var height = goog.isNumber(size) ? size : size[1];
|
||||
var context = ol.dom.createCanvasContext2D(width, height);
|
||||
context.imageSmoothingEnabled = true;
|
||||
|
||||
if (goog.DEBUG) {
|
||||
context.fillStyle =
|
||||
sources.length === 0 ? 'rgba(255,0,0,.8)' :
|
||||
(sources.length == 1 ? 'rgba(0,255,0,.3)' : 'rgba(0,0,255,.1)');
|
||||
context.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
if (sources.length > 0) {
|
||||
var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord);
|
||||
ol.reproj.renderTriangles(context, srcResolution, targetResolution,
|
||||
targetExtent, this.triangles_, sources);
|
||||
}
|
||||
|
||||
this.canvas_ = context.canvas;
|
||||
this.state = ol.TileState.LOADED;
|
||||
this.changed();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.reproj.Tile.prototype.load = function() {
|
||||
if (this.state == ol.TileState.IDLE) {
|
||||
this.state = ol.TileState.LOADING;
|
||||
this.changed();
|
||||
|
||||
var leftToLoad = 0;
|
||||
var onSingleSourceLoaded = goog.bind(function() {
|
||||
leftToLoad--;
|
||||
goog.asserts.assert(leftToLoad >= 0, 'leftToLoad should not be negative');
|
||||
if (leftToLoad <= 0) {
|
||||
this.unlistenSources_();
|
||||
this.reproject_();
|
||||
}
|
||||
}, this);
|
||||
|
||||
goog.asserts.assert(goog.isNull(this.sourcesListenerKeys_),
|
||||
'this.sourcesListenerKeys_ should be null');
|
||||
|
||||
this.sourcesListenerKeys_ = [];
|
||||
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
|
||||
var state = tile.getState();
|
||||
if (state == ol.TileState.IDLE || state == ol.TileState.LOADING) {
|
||||
leftToLoad++;
|
||||
|
||||
var sourceListenKey;
|
||||
sourceListenKey = tile.listen(goog.events.EventType.CHANGE,
|
||||
function(e) {
|
||||
var state = tile.getState();
|
||||
if (state == ol.TileState.LOADED ||
|
||||
state == ol.TileState.ERROR ||
|
||||
state == ol.TileState.EMPTY) {
|
||||
onSingleSourceLoaded();
|
||||
goog.events.unlistenByKey(sourceListenKey);
|
||||
}
|
||||
});
|
||||
this.sourcesListenerKeys_.push(sourceListenKey);
|
||||
}
|
||||
}, this);
|
||||
|
||||
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
|
||||
var state = tile.getState();
|
||||
if (state == ol.TileState.IDLE) {
|
||||
tile.load();
|
||||
}
|
||||
});
|
||||
|
||||
if (leftToLoad === 0) {
|
||||
this.reproject_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ol.reproj.Tile.prototype.unlistenSources_ = function() {
|
||||
goog.asserts.assert(!goog.isNull(this.sourcesListenerKeys_),
|
||||
'this.sourcesListenerKeys_ should not be null');
|
||||
goog.array.forEach(this.sourcesListenerKeys_, goog.events.unlistenByKey);
|
||||
this.sourcesListenerKeys_ = null;
|
||||
};
|
||||
104
src/ol/reproj/triangulation.js
Normal file
104
src/ol/reproj/triangulation.js
Normal file
@@ -0,0 +1,104 @@
|
||||
goog.provide('ol.reproj.Triangulation');
|
||||
goog.provide('ol.reproj.triangulation');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math');
|
||||
goog.require('ol.extent');
|
||||
|
||||
|
||||
/**
|
||||
* Array of triangles,
|
||||
* each triangles is Array (length=3) of
|
||||
* projected point pairs (length=2; [src, dst]),
|
||||
* each point is ol.Coordinate.
|
||||
* @typedef {Array.<Array.<Array.<ol.Coordinate>>>}
|
||||
*/
|
||||
ol.reproj.Triangulation;
|
||||
|
||||
|
||||
/**
|
||||
* Triangulates given extent and reprojects vertices.
|
||||
* TODO: improved triangulation, better error handling of some trans fails
|
||||
* @param {ol.Extent} extent
|
||||
* @param {ol.TransformFunction} transformInv Inverse transform (dst -> src).
|
||||
* @param {number=} opt_subdiv Subdivision factor (default 4).
|
||||
* @return {ol.reproj.Triangulation}
|
||||
*/
|
||||
ol.reproj.triangulation.createForExtent = function(extent, transformInv,
|
||||
opt_subdiv) {
|
||||
var triangulation = [];
|
||||
|
||||
var tlDst = ol.extent.getTopLeft(extent);
|
||||
var brDst = ol.extent.getBottomRight(extent);
|
||||
|
||||
var projected = {0: {}}; // cache of already transformed values
|
||||
var subdiv = opt_subdiv || 4;
|
||||
for (var y = 0; y < subdiv; y++) {
|
||||
projected[y + 1] = {}; // prepare cache for the next line
|
||||
for (var x = 0; x < subdiv; x++) {
|
||||
// do 2 triangle: [(x, y), (x + 1, y + 1), (x, y + 1)]
|
||||
// [(x, y), (x + 1, y), (x + 1, y + 1)]
|
||||
|
||||
var x0y0dst = [
|
||||
goog.math.lerp(tlDst[0], brDst[0], x / subdiv),
|
||||
goog.math.lerp(tlDst[1], brDst[1], y / subdiv)
|
||||
];
|
||||
var x1y0dst = [
|
||||
goog.math.lerp(tlDst[0], brDst[0], (x + 1) / subdiv),
|
||||
goog.math.lerp(tlDst[1], brDst[1], y / subdiv)
|
||||
];
|
||||
var x0y1dst = [
|
||||
goog.math.lerp(tlDst[0], brDst[0], x / subdiv),
|
||||
goog.math.lerp(tlDst[1], brDst[1], (y + 1) / subdiv)
|
||||
];
|
||||
var x1y1dst = [
|
||||
goog.math.lerp(tlDst[0], brDst[0], (x + 1) / subdiv),
|
||||
goog.math.lerp(tlDst[1], brDst[1], (y + 1) / subdiv)
|
||||
];
|
||||
|
||||
if (!goog.isDef(projected[y][x])) {
|
||||
projected[y][x] = transformInv(x0y0dst);
|
||||
}
|
||||
if (!goog.isDef(projected[y][x + 1])) {
|
||||
projected[y][x + 1] = transformInv(x1y0dst);
|
||||
}
|
||||
if (!goog.isDef(projected[y + 1][x])) {
|
||||
projected[y + 1][x] = transformInv(x0y1dst);
|
||||
}
|
||||
if (!goog.isDef(projected[y + 1][x + 1])) {
|
||||
projected[y + 1][x + 1] = transformInv(x1y1dst);
|
||||
}
|
||||
|
||||
triangulation.push(
|
||||
[
|
||||
[projected[y][x], x0y0dst],
|
||||
[projected[y + 1][x + 1], x1y1dst],
|
||||
[projected[y + 1][x], x0y1dst]
|
||||
], [
|
||||
[projected[y][x], x0y0dst],
|
||||
[projected[y][x + 1], x1y0dst],
|
||||
[projected[y + 1][x + 1], x1y1dst]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return triangulation;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.reproj.Triangulation} triangulation
|
||||
* @return {ol.Extent}
|
||||
*/
|
||||
ol.reproj.triangulation.getSourceExtent = function(triangulation) {
|
||||
var extent = ol.extent.createEmpty();
|
||||
|
||||
goog.array.forEach(triangulation, function(triangle, i, arr) {
|
||||
ol.extent.extendCoordinate(extent, triangle[0][0]);
|
||||
ol.extent.extendCoordinate(extent, triangle[1][0]);
|
||||
ol.extent.extendCoordinate(extent, triangle[2][0]);
|
||||
});
|
||||
|
||||
return extent;
|
||||
};
|
||||
Reference in New Issue
Block a user