diff --git a/examples/canvas-tiles-tms.html b/examples/canvas-tiles-tms.html new file mode 100644 index 0000000000..28fc084ab7 --- /dev/null +++ b/examples/canvas-tiles-tms.html @@ -0,0 +1,14 @@ +--- +layout: example.html +title: Custom Canvas Tiles +shortdesc: Renders tiles with TMS coordinates for debugging. +docs: > + The black grid tiles are generated on the client with an HTML5 canvas. + The displayed TMS tile coordinates are produced using a custom template + for TMS, the vector tile source's 512 pixel tile grid and the + zDirection setting for vector tiles. + Notice how the country polygons can be split between tiles and vector + labels may appear in each tile. +tags: "layers, vector tiles, tms, canvas" +--- +
diff --git a/examples/canvas-tiles-tms.js b/examples/canvas-tiles-tms.js new file mode 100644 index 0000000000..e7d37c8a8e --- /dev/null +++ b/examples/canvas-tiles-tms.js @@ -0,0 +1,61 @@ +import MVT from '../src/ol/format/MVT.js'; +import Map from '../src/ol/Map.js'; +import TileDebug from '../src/ol/source/TileDebug.js'; +import TileLayer from '../src/ol/layer/Tile.js'; +import VectorTileLayer from '../src/ol/layer/VectorTile.js'; +import VectorTileSource from '../src/ol/source/VectorTile.js'; +import View from '../src/ol/View.js'; +import {Fill, Stroke, Style, Text} from '../src/ol/style.js'; + +const style = new Style({ + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.6)', + }), + stroke: new Stroke({ + color: '#319FD3', + width: 1, + }), + text: new Text({ + font: '12px Calibri,sans-serif', + fill: new Fill({ + color: '#000', + }), + stroke: new Stroke({ + color: '#fff', + width: 3, + }), + }), +}); + +const vtLayer = new VectorTileLayer({ + declutter: true, + source: new VectorTileSource({ + maxZoom: 15, + format: new MVT(), + url: + 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/' + + 'ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf', + }), + style: function (feature) { + style.getText().setText(feature.get('name')); + return style; + }, +}); + +const debugLayer = new TileLayer({ + source: new TileDebug({ + template: 'z:{z} x:{x} y:{-y}', + projection: vtLayer.getSource().getProjection(), + tileGrid: vtLayer.getSource().getTileGrid(), + zDirection: 1, + }), +}); + +const map = new Map({ + layers: [vtLayer, debugLayer], + target: 'map', + view: new View({ + center: [0, 6000000], + zoom: 4, + }), +}); diff --git a/examples/reprojection-by-code.html b/examples/reprojection-by-code.html index df386d4864..d09702f50f 100644 --- a/examples/reprojection-by-code.html +++ b/examples/reprojection-by-code.html @@ -10,18 +10,24 @@ tags: "reprojection, projection, proj4js, epsg.io, graticule" ---
- + -
- - -
+
+
+ +      + +      +
diff --git a/examples/reprojection-by-code.js b/examples/reprojection-by-code.js index 2926a94486..2e169c1340 100644 --- a/examples/reprojection-by-code.js +++ b/examples/reprojection-by-code.js @@ -2,7 +2,7 @@ import Graticule from '../src/ol/layer/Graticule.js'; import Map from '../src/ol/Map.js'; import OSM from '../src/ol/source/OSM.js'; import Stroke from '../src/ol/style/Stroke.js'; -import TileImage from '../src/ol/source/TileImage.js'; +import TileDebug from '../src/ol/source/TileDebug.js'; import TileLayer from '../src/ol/layer/Tile.js'; import View from '../src/ol/View.js'; import proj4 from 'proj4'; @@ -10,6 +10,16 @@ import {applyTransform} from '../src/ol/extent.js'; import {get as getProjection, getTransform} from '../src/ol/proj.js'; import {register} from '../src/ol/proj/proj4.js'; +const osmSource = new OSM(); + +const debugLayer = new TileLayer({ + source: new TileDebug({ + tileGrid: osmSource.getTileGrid(), + projection: osmSource.getProjection(), + }), + visible: false, +}); + const graticule = new Graticule({ // the style to use for the lines, optional. strokeStyle: new Stroke({ @@ -25,8 +35,9 @@ const graticule = new Graticule({ const map = new Map({ layers: [ new TileLayer({ - source: new OSM(), + source: osmSource, }), + debugLayer, graticule, ], target: 'map', @@ -41,6 +52,7 @@ const queryInput = document.getElementById('epsg-query'); const searchButton = document.getElementById('epsg-search'); const resultSpan = document.getElementById('epsg-result'); const renderEdgesCheckbox = document.getElementById('render-edges'); +const showTilesCheckbox = document.getElementById('show-tiles'); const showGraticuleCheckbox = document.getElementById('show-graticule'); function setProjection(code, name, proj4def, bbox) { @@ -125,22 +137,14 @@ searchButton.onclick = function (event) { }; /** - * Handle checkbox change event. + * Handle checkbox change events. */ renderEdgesCheckbox.onchange = function () { - map.getLayers().forEach(function (layer) { - if (layer instanceof TileLayer) { - const source = layer.getSource(); - if (source instanceof TileImage) { - source.setRenderReprojectionEdges(renderEdgesCheckbox.checked); - } - } - }); + osmSource.setRenderReprojectionEdges(renderEdgesCheckbox.checked); +}; +showTilesCheckbox.onchange = function () { + debugLayer.setVisible(showTilesCheckbox.checked); }; - -/** - * Handle checkbox change event. - */ showGraticuleCheckbox.onchange = function () { graticule.setVisible(showGraticuleCheckbox.checked); }; diff --git a/src/ol/ImageTile.js b/src/ol/ImageTile.js index f6367490ac..a2fce7f973 100644 --- a/src/ol/ImageTile.js +++ b/src/ol/ImageTile.js @@ -72,6 +72,17 @@ class ImageTile extends Tile { return this.image_; } + /** + * Sets an HTML image element for this tile (may be a Canvas or preloaded Image). + * @param {HTMLCanvasElement|HTMLImageElement} element Element. + */ + setImage(element) { + this.image_ = element; + this.state = TileState.LOADED; + this.unlistenImage_(); + this.changed(); + } + /** * Tracks loading or read errors. * diff --git a/src/ol/reproj/Tile.js b/src/ol/reproj/Tile.js index 02fe5c75db..a44fcba169 100644 --- a/src/ol/reproj/Tile.js +++ b/src/ol/reproj/Tile.js @@ -334,15 +334,15 @@ class ReprojTile extends Tile { }.bind(this) ); - this.sourceTiles_.forEach(function (tile, i, arr) { - const state = tile.getState(); - if (state == TileState.IDLE) { - tile.load(); - } - }); - if (leftToLoad === 0) { setTimeout(this.reproject_.bind(this), 0); + } else { + this.sourceTiles_.forEach(function (tile, i, arr) { + const state = tile.getState(); + if (state == TileState.IDLE) { + tile.load(); + } + }); } } } diff --git a/src/ol/source/TileDebug.js b/src/ol/source/TileDebug.js index 36353da531..7f14bcf9a3 100644 --- a/src/ol/source/TileDebug.js +++ b/src/ol/source/TileDebug.js @@ -2,82 +2,10 @@ * @module ol/source/TileDebug */ -import Tile from '../Tile.js'; -import TileState from '../TileState.js'; import XYZ from './XYZ.js'; import {createCanvasContext2D} from '../dom.js'; -import {getKeyZXY} from '../tilecoord.js'; import {toSize} from '../size.js'; -class LabeledTile extends Tile { - /** - * @param {import("../tilecoord.js").TileCoord} tileCoord Tile coordinate. - * @param {import("../size.js").Size} tileSize Tile size. - * @param {string} text Text. - */ - constructor(tileCoord, tileSize, text) { - super(tileCoord, TileState.LOADED); - - /** - * @private - * @type {import("../size.js").Size} - */ - this.tileSize_ = tileSize; - - /** - * @private - * @type {string} - */ - this.text_ = text; - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = null; - } - - /** - * Get the image element for this tile. - * @return {HTMLCanvasElement} Image. - */ - getImage() { - if (this.canvas_) { - return this.canvas_; - } else { - const tileSize = this.tileSize_; - const context = createCanvasContext2D(tileSize[0], tileSize[1]); - - context.strokeStyle = 'grey'; - context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); - - context.fillStyle = 'grey'; - context.strokeStyle = 'white'; - context.textAlign = 'center'; - context.textBaseline = 'middle'; - context.font = '24px sans-serif'; - context.lineWidth = 4; - context.strokeText( - this.text_, - tileSize[0] / 2, - tileSize[1] / 2, - tileSize[0] - ); - context.fillText( - this.text_, - tileSize[0] / 2, - tileSize[1] / 2, - tileSize[0] - ); - - this.canvas_ = context.canvas; - return context.canvas; - } - } - - load() {} -} - /** * @typedef {Object} Options * @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Optional projection. @@ -88,6 +16,8 @@ class LabeledTile extends Tile { * the view resolution does not match any resolution of the tile source. If 0, the nearest * resolution will be used. If 1, the nearest lower resolution will be used. If -1, the * nearest higher resolution will be used. + * @property {string} [template='z:{z} x:{x} y:{y}'] Template for labeling the tiles. + * Should include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders. */ /** @@ -95,8 +25,6 @@ class LabeledTile extends Tile { * A pseudo tile source, which does not fetch tiles from a server, but renders * a grid outline for the tile grid/projection along with the coordinates for * each tile. See examples/canvas-tiles for an example. - * - * Uses Canvas context2d, so requires Canvas support. * @api */ class TileDebug extends XYZ { @@ -115,39 +43,29 @@ class TileDebug extends XYZ { tileGrid: options.tileGrid, wrapX: options.wrapX !== undefined ? options.wrapX : true, zDirection: options.zDirection, - }); - } + url: options.template || 'z:{z} x:{x} y:{y}', + tileLoadFunction: (tile, text) => { + const z = tile.getTileCoord()[0]; + const tileSize = toSize(this.tileGrid.getTileSize(z)); + const context = createCanvasContext2D(tileSize[0], tileSize[1]); - /** - * @param {number} z Tile coordinate z. - * @param {number} x Tile coordinate x. - * @param {number} y Tile coordinate y. - * @return {!LabeledTile} Tile. - */ - getTile(z, x, y) { - const tileCoordKey = getKeyZXY(z, x, y); - if (this.tileCache.containsKey(tileCoordKey)) { - return /** @type {!LabeledTile} */ (this.tileCache.get(tileCoordKey)); - } else { - const tileSize = toSize(this.tileGrid.getTileSize(z)); - const tileCoord = [z, x, y]; - const textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord); - let text; - if (textTileCoord) { - text = - 'z:' + - textTileCoord[0] + - ' x:' + - textTileCoord[1] + - ' y:' + - textTileCoord[2]; - } else { - text = 'none'; - } - const tile = new LabeledTile(tileCoord, tileSize, text); - this.tileCache.set(tileCoordKey, tile); - return tile; - } + context.strokeStyle = 'grey'; + context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5); + + context.fillStyle = 'grey'; + context.strokeStyle = 'white'; + context.textAlign = 'center'; + context.textBaseline = 'middle'; + context.font = '24px sans-serif'; + context.lineWidth = 4; + context.strokeText(text, tileSize[0] / 2, tileSize[1] / 2, tileSize[0]); + context.fillText(text, tileSize[0] / 2, tileSize[1] / 2, tileSize[0]); + + /** @type {import("../ImageTile.js").default} */ (tile).setImage( + context.canvas + ); + }, + }); } } diff --git a/test/rendering/cases/reproj-tile-4326-debug/expected.png b/test/rendering/cases/reproj-tile-4326-debug/expected.png new file mode 100644 index 0000000000..7e5f6784b3 Binary files /dev/null and b/test/rendering/cases/reproj-tile-4326-debug/expected.png differ diff --git a/test/rendering/cases/reproj-tile-4326-debug/main.js b/test/rendering/cases/reproj-tile-4326-debug/main.js new file mode 100644 index 0000000000..d9447b275a --- /dev/null +++ b/test/rendering/cases/reproj-tile-4326-debug/main.js @@ -0,0 +1,49 @@ +import Map from '../../../../src/ol/Map.js'; +import TileLayer from '../../../../src/ol/layer/Tile.js'; +import View from '../../../../src/ol/View.js'; +import {TileDebug, XYZ} from '../../../../src/ol/source.js'; +import {createForProjection, createXYZ} from '../../../../src/ol/tilegrid.js'; +import {get, toLonLat} from '../../../../src/ol/proj.js'; + +const tileGrid = createXYZ(); +const extent = tileGrid.getTileCoordExtent([5, 5, 12]); +const center = [(extent[0] + extent[2]) / 2, extent[1]]; + +const source = new XYZ({ + transition: 0, + minZoom: 5, + maxZoom: 5, + url: '/data/tiles/osm/{z}/{x}/{y}.png', +}); + +const sourceDebug = new TileDebug({tileGrid: source.getTileGrid()}); + +source.setTileGridForProjection( + get('EPSG:4326'), + createForProjection(get('EPSG:4326'), 7, [64, 64]) +); + +sourceDebug.setTileGridForProjection( + get('EPSG:4326'), + createForProjection(get('EPSG:4326'), 7, [64, 64]) +); + +new Map({ + pixelRatio: 1, + target: 'map', + layers: [ + new TileLayer({ + source: source, + }), + new TileLayer({ + source: sourceDebug, + }), + ], + view: new View({ + projection: 'EPSG:4326', + center: toLonLat(center), + zoom: 5, + }), +}); + +render();