New wrapX functions for coordinate and extent

This commit is contained in:
Andreas Hocevar
2020-03-30 19:26:26 +02:00
parent 48b79cf7d1
commit 098885a006
8 changed files with 121 additions and 35 deletions

View File

@@ -3,6 +3,7 @@
*/
import {modulo} from './math.js';
import {padNumber} from './string.js';
import {getWidth} from './extent.js';
/**
@@ -402,3 +403,22 @@ export function toStringHDMS(coordinate, opt_fractionDigits) {
export function toStringXY(coordinate, opt_fractionDigits) {
return format(coordinate, '{x}, {y}', opt_fractionDigits);
}
/**
* Modifies the provided coordinate in-place to be within the real world
* extent.
*
* @param {Coordinate} coordinate Coordinate.
* @param {import("./proj/Projection.js").default} projection Projection
* @return {Coordinate} The coordinate within the real world extent.
*/
export function wrapX(coordinate, projection) {
const projectionExtent = projection.getExtent();
if (projection.canWrapX() && (coordinate[0] < projectionExtent[0] || coordinate[0] > projectionExtent[2])) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
coordinate[0] -= (worldsAway * worldWidth);
}
return coordinate;
}

View File

@@ -813,3 +813,25 @@ export function applyTransform(extent, transformFn, opt_extent, opt_stops) {
}
return _boundingExtentXYs(xs, ys, opt_extent);
}
/**
* Modifies the provided extent in-place to be within the real world
* extent.
*
* @param {Extent} extent Extent.
* @param {import("./proj/Projection.js").default} projection Projection
* @return {Extent} The extent within the real world extent.
*/
export function wrapX(extent, projection) {
const projectionExtent = projection.getExtent();
const center = getCenter(extent);
if (projection.canWrapX() && (center[0] < projectionExtent[0] || center[0] > projectionExtent[2])) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
const offset = (worldsAway * worldWidth);
extent[0] -= offset;
extent[2] -= offset;
}
return extent;
}

View File

@@ -24,7 +24,9 @@ import {
getIntersection,
getWidth,
intersects,
isEmpty
isEmpty,
buffer as bufferExtent,
wrapX
} from '../extent.js';
import {clamp} from '../math.js';
import Style from '../style/Style.js';
@@ -481,19 +483,10 @@ class Graticule extends VectorLayer {
strategyFunction(extent, resolution) {
// extents may be passed in different worlds, to avoid endless loop we use only one
const realExtent = extent.slice();
if (this.projection_) {
const center = getCenter(extent);
const projectionExtent = this.projection_.getExtent();
const worldWidth = getWidth(projectionExtent);
if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
realExtent[0] -= (worldsAway * worldWidth);
realExtent[2] -= (worldsAway * worldWidth);
}
realExtent[0] = Math.round(realExtent[0] * 1e6) / 1e6;
realExtent[2] = Math.round(realExtent[2] * 1e6) / 1e6;
if (this.projection_ && this.getSource().getWrapX()) {
wrapX(realExtent, this.projection_);
}
if (this.loadedExtent_ && !equals(this.loadedExtent_, realExtent)) {
if (this.loadedExtent_ && !containsExtent(bufferExtent(this.loadedExtent_, resolution / 2), realExtent)) {
// we should not keep track of loaded extents
this.getSource().removeLoadedExtent(this.loadedExtent_);
}

View File

@@ -9,6 +9,7 @@ import {inView} from '../layer/Layer.js';
import {shared as iconImageCache} from '../style/IconImageCache.js';
import {compose as composeTransform, makeInverse} from '../transform.js';
import {renderDeclutterItems} from '../render.js';
import {wrapX} from '../coordinate.js';
/**
* @abstract
@@ -102,19 +103,12 @@ class MapRenderer extends Disposable {
const projection = viewState.projection;
let translatedCoordinate = coordinate;
const translatedCoordinate = wrapX(coordinate.slice(), projection);
const offsets = [[0, 0]];
if (projection.canWrapX()) {
if (projection.canWrapX() && checkWrapped) {
const projectionExtent = projection.getExtent();
const worldWidth = getWidth(projectionExtent);
const x = coordinate[0];
if (x < projectionExtent[0] || x > projectionExtent[2]) {
const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
}
if (checkWrapped) {
offsets.push([-worldWidth, 0], [worldWidth, 0]);
}
offsets.push([-worldWidth, 0], [worldWidth, 0]);
}
const layerStates = frameState.layerStatesArray;

View File

@@ -3,7 +3,8 @@
*/
import {getUid} from '../../util.js';
import ViewHint from '../../ViewHint.js';
import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent} from '../../extent.js';
import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent, wrapX as wrapExtentX} from '../../extent.js';
import {wrapX as wrapCoordinateX} from '../../coordinate.js';
import {fromUserExtent, toUserExtent, getUserProjection, getTransformFromProjections} from '../../proj.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
@@ -364,7 +365,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const loadExtents = [extent.slice()];
const projectionExtent = viewState.projection.getExtent();
if (vectorSource.getWrapX() && viewState.projection.canWrapX() &&
if (vectorSource.getWrapX() && projection.canWrapX() &&
!containsExtent(projectionExtent, frameState.extent)) {
// For the replay group, we need an extent that intersects the real world
// (-180° to +180°). To support geometries in a coordinate range from -540°
@@ -375,11 +376,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
const gutter = Math.max(getWidth(extent) / 2, worldWidth);
extent[0] = projectionExtent[0] - gutter;
extent[2] = projectionExtent[2] + gutter;
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
center[0] -= (worldsAway * worldWidth);
const loadExtent = loadExtents[0];
loadExtent[0] -= (worldsAway * worldWidth);
loadExtent[2] -= (worldsAway * worldWidth);
wrapCoordinateX(center, projection);
const loadExtent = wrapExtentX(loadExtents[0], projection);
wrapExtentX(loadExtent, projection);
// If the extent crosses the date line, we load data for both edges of the worlds
if (loadExtent[0] < projectionExtent[0] && loadExtent[2] < projectionExtent[2]) {
loadExtents.push([loadExtent[0] + worldWidth, loadExtent[1], loadExtent[2] + worldWidth, loadExtent[3]]);

View File

@@ -25,6 +25,7 @@ import {
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
import {clear} from '../../obj.js';
import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js';
import {wrapX} from '../../coordinate.js';
/**
@@ -353,9 +354,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
if (tile.getState() === TileState.LOADED && tile.hifi) {
const extent = tileGrid.getTileCoordExtent(tile.tileCoord);
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
const worldWidth = getWidth(projectionExtent);
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
coordinate[0] -= (worldsAway * worldWidth);
wrapX(coordinate, projection);
}
break;
}

View File

@@ -1,5 +1,6 @@
import {add as addCoordinate, scale as scaleCoordinate, rotate as rotateCoordinate, equals as coordinatesEqual, format as formatCoordinate, closestOnCircle, closestOnSegment, createStringXY, squaredDistanceToSegment, toStringXY, toStringHDMS} from '../../../src/ol/coordinate.js';
import {add as addCoordinate, scale as scaleCoordinate, rotate as rotateCoordinate, equals as coordinatesEqual, format as formatCoordinate, closestOnCircle, closestOnSegment, createStringXY, squaredDistanceToSegment, toStringXY, toStringHDMS, wrapX} from '../../../src/ol/coordinate.js';
import Circle from '../../../src/ol/geom/Circle.js';
import {get} from '../../../src/ol/proj.js';
describe('ol.coordinate', function() {
@@ -235,4 +236,29 @@ describe('ol.coordinate', function() {
});
});
describe('wrapX()', function() {
const projection = get('EPSG:4326');
it('leaves real world coordinate untouched', function() {
expect(wrapX([16, 48], projection)).to.eql([16, 48]);
});
it('moves left world coordinate to real world', function() {
expect(wrapX([-344, 48], projection)).to.eql([16, 48]);
});
it('moves right world coordinate to real world', function() {
expect(wrapX([376, 48], projection)).to.eql([16, 48]);
});
it('moves far off left coordinate to real world', function() {
expect(wrapX([-1064, 48], projection)).to.eql([16, 48]);
});
it('moves far off right coordinate to real world', function() {
expect(wrapX([1096, 48], projection)).to.eql([16, 48]);
});
});
});

View File

@@ -1,5 +1,5 @@
import * as _ol_extent_ from '../../../src/ol/extent.js';
import {getTransform} from '../../../src/ol/proj.js';
import {getTransform, get} from '../../../src/ol/proj.js';
import {register} from '../../../src/ol/proj/proj4.js';
@@ -818,4 +818,37 @@ describe('ol.extent', function() {
});
describe('wrapX()', function() {
const projection = get('EPSG:4326');
it('leaves real world extent untouched', function() {
expect(_ol_extent_.wrapX([16, 48, 18, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves left world extent to real world', function() {
expect(_ol_extent_.wrapX([-344, 48, -342, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves right world extent to real world', function() {
expect(_ol_extent_.wrapX([376, 48, 378, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves far off left extent to real world', function() {
expect(_ol_extent_.wrapX([-1064, 48, -1062, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('moves far off right extent to real world', function() {
expect(_ol_extent_.wrapX([1096, 48, 1098, 49], projection)).to.eql([16, 48, 18, 49]);
});
it('leaves -180 crossing extent with real world center untouched', function() {
expect(_ol_extent_.wrapX([-184, 48, 16, 49], projection)).to.eql([-184, 48, 16, 49]);
});
it('moves +180 crossing extent with off-world center to the real world', function() {
expect(_ol_extent_.wrapX([300, 48, 376, 49], projection)).to.eql([-60, 48, 16, 49]);
});
});
});