diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js index c23f77270e..ec85963a46 100644 --- a/src/ol/renderer/webgl/TileLayer.js +++ b/src/ol/renderer/webgl/TileLayer.js @@ -294,7 +294,11 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { const tileSource = tileLayer.getSource(); const tileGrid = tileSource.getTileGridForProjection(viewState.projection); const tileTextureCache = this.tileTextureCache_; - const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + const tileRange = tileGrid.getTileRangeForExtentAndZ( + extent, + z, + this.tempTileRange_ + ); const tileSourceKey = getUid(tileSource); if (!(tileSourceKey in frameState.wantedTiles)) { diff --git a/src/ol/tilegrid/TileGrid.js b/src/ol/tilegrid/TileGrid.js index b61d3135a7..019b67a5fe 100644 --- a/src/ol/tilegrid/TileGrid.js +++ b/src/ol/tilegrid/TileGrid.js @@ -6,7 +6,7 @@ import TileRange, { } from '../TileRange.js'; import {DEFAULT_TILE_SIZE} from './common.js'; import {assert} from '../asserts.js'; -import {clamp} from '../math.js'; +import {ceil, clamp, floor} from '../math.js'; import {createOrUpdate, getTopLeft} from '../extent.js'; import {createOrUpdate as createOrUpdateTileCoord} from '../tilecoord.js'; import {isSorted, linearFindNearest} from '../array.js'; @@ -18,6 +18,12 @@ import {toSize} from '../size.js'; */ const tmpTileCoord = [0, 0, 0]; +/** + * Number of decimal digits to consider in integer values when rounding. + * @type {number} + */ +const DECIMALS = 5; + /** * @typedef {Object} Options * @property {import("../extent.js").Extent} [extent] Extent for the tile grid. No tiles outside this @@ -520,19 +526,15 @@ class TileGrid { const origin = this.getOrigin(z); const tileSize = toSize(this.getTileSize(z), this.tmpSize_); - const adjustX = reverseIntersectionPolicy ? 0.5 : 0; - const adjustY = reverseIntersectionPolicy ? 0.5 : 0; - const xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); - const yFromOrigin = Math.floor((origin[1] - y) / resolution + adjustY); - let tileCoordX = (scale * xFromOrigin) / tileSize[0]; - let tileCoordY = (scale * yFromOrigin) / tileSize[1]; + let tileCoordX = (scale * (x - origin[0])) / resolution / tileSize[0]; + let tileCoordY = (scale * (origin[1] - y)) / resolution / tileSize[1]; if (reverseIntersectionPolicy) { - tileCoordX = Math.ceil(tileCoordX) - 1; - tileCoordY = Math.ceil(tileCoordY) - 1; + tileCoordX = ceil(tileCoordX, DECIMALS) - 1; + tileCoordY = ceil(tileCoordY, DECIMALS) - 1; } else { - tileCoordX = Math.floor(tileCoordX); - tileCoordY = Math.floor(tileCoordY); + tileCoordX = floor(tileCoordX, DECIMALS); + tileCoordY = floor(tileCoordY, DECIMALS); } return createOrUpdateTileCoord(z, tileCoordX, tileCoordY, opt_tileCoord); @@ -558,19 +560,15 @@ class TileGrid { const resolution = this.getResolution(z); const tileSize = toSize(this.getTileSize(z), this.tmpSize_); - const adjustX = reverseIntersectionPolicy ? 0.5 : 0; - const adjustY = reverseIntersectionPolicy ? 0.5 : 0; - const xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX); - const yFromOrigin = Math.floor((origin[1] - y) / resolution + adjustY); - let tileCoordX = xFromOrigin / tileSize[0]; - let tileCoordY = yFromOrigin / tileSize[1]; + let tileCoordX = (x - origin[0]) / resolution / tileSize[0]; + let tileCoordY = (origin[1] - y) / resolution / tileSize[1]; if (reverseIntersectionPolicy) { - tileCoordX = Math.ceil(tileCoordX) - 1; - tileCoordY = Math.ceil(tileCoordY) - 1; + tileCoordX = ceil(tileCoordX, DECIMALS) - 1; + tileCoordY = ceil(tileCoordY, DECIMALS) - 1; } else { - tileCoordX = Math.floor(tileCoordX); - tileCoordY = Math.floor(tileCoordY); + tileCoordX = floor(tileCoordX, DECIMALS); + tileCoordY = floor(tileCoordY, DECIMALS); } return createOrUpdateTileCoord(z, tileCoordX, tileCoordY, opt_tileCoord); diff --git a/test/node/ol/tilegrid/TileGrid.test.js b/test/node/ol/tilegrid/TileGrid.test.js index 4f3e9d0d82..438cd8df40 100644 --- a/test/node/ol/tilegrid/TileGrid.test.js +++ b/test/node/ol/tilegrid/TileGrid.test.js @@ -823,14 +823,14 @@ describe('ol/tilegrid/TileGrid.js', function () { expect(tileCoord[2]).to.eql(1); // pixels are top aligned to the origin - coordinate = [1280, -2559.999]; + coordinate = [1280, -2549.999]; tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate, 10); expect(tileCoord[0]).to.eql(0); expect(tileCoord[1]).to.eql(0); expect(tileCoord[2]).to.eql(0); // pixels are left aligned to the origin - coordinate = [2559.999, -1280]; + coordinate = [2549.999, -1280]; tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate, 10); expect(tileCoord[0]).to.eql(0); expect(tileCoord[1]).to.eql(0); @@ -947,6 +947,44 @@ describe('ol/tilegrid/TileGrid.js', function () { }); describe('getTileRangeForExtentAndZ', function () { + it('includes a tile even if a fraction of a pixel is covered', function () { + const tileGrid = new TileGrid({ + resolutions: [1], + origin: [0, 10], + tileSize: 10, + }); + + let tileRange; + + // overlaps to the right + tileRange = tileGrid.getTileRangeForExtentAndZ([0, 0, 10.1, 10], 0); + expect(tileRange.minX).to.be(0); + expect(tileRange.maxX).to.be(1); + expect(tileRange.minY).to.be(0); + expect(tileRange.maxY).to.be(0); + + // overlaps to the bottom + tileRange = tileGrid.getTileRangeForExtentAndZ([0, -0.1, 10, 10], 0); + expect(tileRange.minX).to.be(0); + expect(tileRange.maxX).to.be(0); + expect(tileRange.minY).to.be(0); + expect(tileRange.maxY).to.be(1); + + // overlaps to the left + tileRange = tileGrid.getTileRangeForExtentAndZ([-0.1, 0, 10, 10], 0); + expect(tileRange.minX).to.be(-1); + expect(tileRange.maxX).to.be(0); + expect(tileRange.minY).to.be(0); + expect(tileRange.maxY).to.be(0); + + // overlaps to the top + tileRange = tileGrid.getTileRangeForExtentAndZ([0, 0, 10, 10.1], 0); + expect(tileRange.minX).to.be(0); + expect(tileRange.maxX).to.be(0); + expect(tileRange.minY).to.be(-1); + expect(tileRange.maxY).to.be(0); + }); + it('returns the expected TileRange', function () { const tileGrid = new TileGrid({ resolutions: resolutions, diff --git a/test/rendering/cases/webgl-tile-range/expected.png b/test/rendering/cases/webgl-tile-range/expected.png new file mode 100644 index 0000000000..7cb0e701c2 Binary files /dev/null and b/test/rendering/cases/webgl-tile-range/expected.png differ diff --git a/test/rendering/cases/webgl-tile-range/main.js b/test/rendering/cases/webgl-tile-range/main.js new file mode 100644 index 0000000000..8769080be7 --- /dev/null +++ b/test/rendering/cases/webgl-tile-range/main.js @@ -0,0 +1,67 @@ +import DataTile from '../../../../src/ol/source/DataTile.js'; +import Map from '../../../../src/ol/Map.js'; +import TileLayer from '../../../../src/ol/layer/WebGLTile.js'; +import View from '../../../../src/ol/View.js'; +import {Projection} from '../../../../src/ol/proj.js'; + +const size = 256; +const data = new Uint8Array(size * size * 4); +for (let row = 0; row < size; ++row) { + for (let col = 0; col < size; ++col) { + const index = (row * size + col) * 4; + data[index] = 0; + data[index + 1] = 255; + data[index + 2] = 0; + data[index + 3] = 255; + } +} + +const canvas = document.createElement('canvas'); +canvas.width = size; +canvas.height = size; + +const context = canvas.getContext('2d'); +context.strokeStyle = 'black'; + +function getLabels(z, x, y) { + context.clearRect(0, 0, size, size); + context.strokeRect(0, 0, size, size); + const data = context.getImageData(0, 0, size, size).data; + return new Uint8Array(data.buffer); +} + +document.getElementById('map').style.background = 'red'; + +const projection = new Projection({ + code: 'pixels', + units: 'pixels', + extent: [0, 0, 256, 256], +}); + +new Map({ + target: 'map', + layers: [ + new TileLayer({ + source: new DataTile({ + projection: projection, + maxZoom: 1, + tileSize: size, + loader: () => data, + }), + }), + new TileLayer({ + source: new DataTile({ + projection: projection, + loader: getLabels, + transition: 0, + }), + }), + ], + view: new View({ + projection: projection, + center: [127.7, 128.3], + zoom: 8, + }), +}); + +render();