Merge pull request #6779 from ahocevar/vector-tile-grid

Decouple source and rendered tile grid of vector tile sources
This commit is contained in:
Andreas Hocevar
2017-05-10 19:07:21 +02:00
committed by GitHub
18 changed files with 708 additions and 288 deletions

View File

@@ -3,7 +3,7 @@ layout: example.html
title: Advanced Mapbox Vector Tiles title: Advanced Mapbox Vector Tiles
shortdesc: Example of a Mapbox vector tiles map with custom tile grid. shortdesc: Example of a Mapbox vector tiles map with custom tile grid.
docs: > docs: >
A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwidth on mobile devices. **Note**: No map will be visible when the access token has expired. A vector tiles map which reuses the same source tiles for subsequent zoom levels to save bandwidth on mobile devices. **Note**: No map will be visible when the access token has expired.
tags: "mapbox, vector, tiles, mobile" tags: "mapbox, vector, tiles, mobile"
resources: resources:
- resources/mapbox-streets-v6-style.js - resources/mapbox-streets-v6-style.js

View File

@@ -15,25 +15,16 @@ goog.require('ol.tilegrid.TileGrid');
var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
// For how many zoom levels do we want to use the same vector tiles? // Calculation of resolutions that match zoom levels 1, 3, 5, 7, 9, 11, 13, 15.
// 1 means "use tiles from all zoom levels". 2 means "use the same tiles for 2
// subsequent zoom levels".
var reuseZoomLevels = 2;
// Offset of loaded tiles from web mercator zoom level 0.
// 0 means "At map zoom level 0, use tiles from zoom level 0". 1 means "At map
// zoom level 0, use tiles from zoom level 1".
var zoomOffset = 1;
// Calculation of tile urls
var resolutions = []; var resolutions = [];
for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) { for (var i = 0; i <= 7; ++i) {
resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels)); resolutions.push(156543.03392804097 / Math.pow(2, i * 2));
} }
// Calculation of tile urls for zoom levels 1, 3, 5, 7, 9, 11, 13, 15.
function tileUrlFunction(tileCoord) { function tileUrlFunction(tileCoord) {
return ('https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + return ('https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=' + key) '{z}/{x}/{y}.vector.pbf?access_token=' + key)
.replace('{z}', String(tileCoord[0] * reuseZoomLevels + zoomOffset)) .replace('{z}', String(tileCoord[0] * 2 - 1))
.replace('{x}', String(tileCoord[1])) .replace('{x}', String(tileCoord[1]))
.replace('{y}', String(-tileCoord[2] - 1)) .replace('{y}', String(-tileCoord[2] - 1))
.replace('{a-d}', 'abcd'.substr( .replace('{a-d}', 'abcd'.substr(
@@ -43,8 +34,6 @@ function tileUrlFunction(tileCoord) {
var map = new ol.Map({ var map = new ol.Map({
layers: [ layers: [
new ol.layer.VectorTile({ new ol.layer.VectorTile({
renderMode: 'vector',
preload: Infinity,
source: new ol.source.VectorTile({ source: new ol.source.VectorTile({
attributions: '© <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> ' + attributions: '© <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> ' +
'© <a href="https://www.openstreetmap.org/copyright">' + '© <a href="https://www.openstreetmap.org/copyright">' +
@@ -52,9 +41,10 @@ var map = new ol.Map({
format: new ol.format.MVT(), format: new ol.format.MVT(),
tileGrid: new ol.tilegrid.TileGrid({ tileGrid: new ol.tilegrid.TileGrid({
extent: ol.proj.get('EPSG:3857').getExtent(), extent: ol.proj.get('EPSG:3857').getExtent(),
resolutions: resolutions resolutions: resolutions,
tileSize: 512
}), }),
tilePixelRatio: 16, tilePixelRatio: 8,
tileUrlFunction: tileUrlFunction tileUrlFunction: tileUrlFunction
}), }),
style: createMapboxStreetsV6Style() style: createMapboxStreetsV6Style()

View File

@@ -4877,7 +4877,7 @@ olx.source.VectorTileOptions.prototype.state;
/** /**
* Class used to instantiate image tiles. Default is {@link ol.VectorTile}. * Class used to instantiate vector tiles. Default is {@link ol.VectorTile}.
* @type {function(new: ol.VectorTile, ol.TileCoord, * @type {function(new: ol.VectorTile, ol.TileCoord,
* ol.TileState, string, ol.format.Feature, * ol.TileState, string, ol.format.Feature,
* ol.TileLoadFunctionType)|undefined} * ol.TileLoadFunctionType)|undefined}

View File

@@ -70,8 +70,8 @@ ol.ImageTile.prototype.disposeInternal = function() {
/** /**
* Get the image element for this tile. * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
* @inheritDoc * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
* @api * @api
*/ */
ol.ImageTile.prototype.getImage = function() { ol.ImageTile.prototype.getImage = function() {

View File

@@ -1,6 +1,7 @@
goog.provide('ol.renderer.canvas.VectorTileLayer'); goog.provide('ol.renderer.canvas.VectorTileLayer');
goog.require('ol'); goog.require('ol');
goog.require('ol.TileState');
goog.require('ol.dom'); goog.require('ol.dom');
goog.require('ol.extent'); goog.require('ol.extent');
goog.require('ol.proj'); goog.require('ol.proj');
@@ -12,7 +13,6 @@ goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.render.replay'); goog.require('ol.render.replay');
goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.vector'); goog.require('ol.renderer.vector');
goog.require('ol.size');
goog.require('ol.transform'); goog.require('ol.transform');
@@ -23,6 +23,9 @@ goog.require('ol.transform');
*/ */
ol.renderer.canvas.VectorTileLayer = function(layer) { ol.renderer.canvas.VectorTileLayer = function(layer) {
/**
* @type {CanvasRenderingContext2D}
*/
this.context = null; this.context = null;
ol.renderer.canvas.TileLayer.call(this, layer); ol.renderer.canvas.TileLayer.call(this, layer);
@@ -95,12 +98,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState,
/** /**
* @param {ol.VectorTile} tile Tile. * @param {ol.VectorImageTile} tile Tile.
* @param {olx.FrameState} frameState Frame state. * @param {olx.FrameState} frameState Frame state.
* @private * @private
*/ */
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile, ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
frameState) { tile, frameState) {
var layer = this.getLayer(); var layer = this.getLayer();
var pixelRatio = frameState.pixelRatio; var pixelRatio = frameState.pixelRatio;
var projection = frameState.viewState.projection; var projection = frameState.viewState.projection;
@@ -113,78 +116,88 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile,
return; return;
} }
replayState.replayGroup = null; for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
replayState.dirty = false; var sourceTile = tile.getTile(tile.tileKeys[t]);
sourceTile.replayGroup = null;
replayState.dirty = false;
var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
var tileGrid = source.getTileGrid(); var sourceTileGrid = source.getTileGrid();
var tileCoord = tile.tileCoord; var sourceTileCoord = sourceTile.tileCoord;
var tileProjection = tile.getProjection(); var tileProjection = sourceTile.getProjection();
var resolution = tileGrid.getResolution(tileCoord[0]); var tileGrid = source.getTileGridForProjection(projection);
var extent, reproject, tileResolution; var resolution = tileGrid.getResolution(tile.tileCoord[0]);
if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { var sourceTileResolution = sourceTileGrid.getResolution(sourceTile.tileCoord[0]);
var tilePixelRatio = tileResolution = source.getTilePixelRatio(); var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio]; var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
} else { var extent, reproject, tileResolution;
tileResolution = resolution; if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
extent = tileGrid.getTileCoordExtent(tileCoord); var tilePixelRatio = tileResolution = source.getTilePixelRatio();
if (!ol.proj.equivalent(projection, tileProjection)) { var transform = ol.transform.compose(this.tmpTransform_,
reproject = true; 0, 0,
tile.setProjection(projection); 1 / sourceTileResolution * tilePixelRatio, -1 / sourceTileResolution * tilePixelRatio,
} 0,
} -sourceTileExtent[0], -sourceTileExtent[3]);
replayState.dirty = false; extent = (ol.transform.apply(transform, [sharedExtent[0], sharedExtent[3]])
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, .concat(ol.transform.apply(transform, [sharedExtent[2], sharedExtent[1]])));
tileResolution, source.getOverlaps(), layer.getRenderBuffer());
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
tileResolution, pixelRatio);
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @this {ol.renderer.canvas.VectorTileLayer}
*/
function renderFeature(feature) {
var styles;
var styleFunction = feature.getStyleFunction();
if (styleFunction) {
styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
} else { } else {
styleFunction = layer.getStyleFunction(); tileResolution = resolution;
extent = sharedExtent;
if (!ol.proj.equivalent(projection, tileProjection)) {
reproject = true;
sourceTile.setProjection(projection);
}
}
replayState.dirty = false;
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
tileResolution, source.getOverlaps(), layer.getRenderBuffer());
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
tileResolution, pixelRatio);
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @this {ol.renderer.canvas.VectorTileLayer}
*/
var renderFeature = function(feature) {
var styles;
var styleFunction = feature.getStyleFunction();
if (styleFunction) { if (styleFunction) {
styles = styleFunction(feature, resolution); styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
} else {
styleFunction = layer.getStyleFunction();
if (styleFunction) {
styles = styleFunction(feature, resolution);
}
} }
} if (styles) {
if (styles) { if (!Array.isArray(styles)) {
if (!Array.isArray(styles)) { styles = [styles];
styles = [styles]; }
var dirty = this.renderFeature(feature, squaredTolerance, styles,
replayGroup);
this.dirty_ = this.dirty_ || dirty;
replayState.dirty = replayState.dirty || dirty;
} }
var dirty = this.renderFeature(feature, squaredTolerance, styles, };
replayGroup);
this.dirty_ = this.dirty_ || dirty; var features = sourceTile.getFeatures();
replayState.dirty = replayState.dirty || dirty; if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
features.sort(renderOrder);
} }
} var feature;
for (var i = 0, ii = features.length; i < ii; ++i) {
var features = tile.getFeatures(); feature = features[i];
if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { if (reproject) {
features.sort(renderOrder); feature.getGeometry().transform(tileProjection, projection);
} }
var feature; renderFeature.call(this, feature);
for (var i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (reproject) {
feature.getGeometry().transform(tileProjection, projection);
} }
renderFeature.call(this, feature); replayGroup.finish();
sourceTile.setReplayGroup(tile.tileCoord.toString(), replayGroup);
} }
replayGroup.finish();
replayState.renderedRevision = revision; replayState.renderedRevision = revision;
replayState.renderedRenderOrder = renderOrder; replayState.renderedRenderOrder = renderOrder;
replayState.replayGroup = replayGroup;
replayState.resolution = NaN;
}; };
@@ -193,10 +206,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile,
*/ */
ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function( ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
tile, frameState, layerState, x, y, w, h, gutter) { tile, frameState, layerState, x, y, w, h, gutter) {
var vectorTile = /** @type {ol.VectorTile} */ (tile); var vectorImageTile = /** @type {ol.VectorImageTile} */ (tile);
this.createReplayGroup_(vectorTile, frameState); this.createReplayGroup_(vectorImageTile, frameState);
if (this.context) { if (this.context) {
this.renderTileImage_(vectorTile, frameState, layerState); this.renderTileImage_(vectorImageTile, frameState, layerState);
ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments); ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments);
} }
}; };
@@ -213,54 +226,61 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
/** @type {Object.<string, boolean>} */ /** @type {Object.<string, boolean>} */
var features = {}; var features = {};
/** @type {Array.<ol.VectorTile>} */ /** @type {Array.<ol.VectorImageTile>} */
var replayables = this.renderedTiles; var renderedTiles = this.renderedTiles;
var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
var tileGrid = source.getTileGrid(); var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var found, tileSpaceCoordinate; var sourceTileGrid = source.getTileGrid();
var bufferedExtent, found, tileSpaceCoordinate;
var i, ii, origin, replayGroup; var i, ii, origin, replayGroup;
var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution; var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
for (i = 0, ii = replayables.length; i < ii; ++i) { for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
tile = replayables[i]; tile = renderedTiles[i];
tileCoord = tile.tileCoord; tileCoord = tile.tileCoord;
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent); tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) { bufferedExtent = ol.extent.buffer(tileExtent, hitTolerance * resolution, bufferedExtent);
if (!ol.extent.containsCoordinate(bufferedExtent, coordinate)) {
continue; continue;
} }
if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
origin = ol.extent.getTopLeft(tileExtent); var sourceTile = tile.getTile(tile.tileKeys[t]);
tilePixelRatio = source.getTilePixelRatio(); if (sourceTile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; var sourceTileCoord = sourceTile.tileCoord;
tileSpaceCoordinate = [ var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
(coordinate[0] - origin[0]) / tileResolution, origin = ol.extent.getTopLeft(sourceTileExtent);
(origin[1] - coordinate[1]) / tileResolution tilePixelRatio = source.getTilePixelRatio();
]; tileResolution = sourceTileGrid.getResolution(sourceTileCoord[0]) / tilePixelRatio;
resolution = tilePixelRatio; tileSpaceCoordinate = [
} else { (coordinate[0] - origin[0]) / tileResolution,
tileSpaceCoordinate = coordinate; (origin[1] - coordinate[1]) / tileResolution
];
resolution = tilePixelRatio;
} else {
tileSpaceCoordinate = coordinate;
}
replayGroup = sourceTile.getReplayGroup(tile.tileCoord);
found = found || replayGroup.forEachFeatureAtCoordinate(
tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @return {?} Callback result.
*/
function(feature) {
var key = ol.getUid(feature).toString();
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
}
});
} }
replayGroup = tile.getReplayState().replayGroup;
found = found || replayGroup.forEachFeatureAtCoordinate(
tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @return {?} Callback result.
*/
function(feature) {
var key = ol.getUid(feature).toString();
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
}
});
} }
return found; return found;
}; };
/** /**
* @param {ol.Tile} tile Tile. * @param {ol.VectorTile} tile Tile.
* @param {olx.FrameState} frameState Frame state. * @param {olx.FrameState} frameState Frame state.
* @return {ol.Transform} transform Transform. * @return {ol.Transform} transform Transform.
* @private * @private
@@ -308,7 +328,9 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(
* @inheritDoc * @inheritDoc
*/ */
ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) { ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
var renderMode = this.getLayer().getRenderMode(); var layer = this.getLayer();
var source = layer.getSource();
var renderMode = layer.getRenderMode();
var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode]; var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
if (replays) { if (replays) {
var pixelRatio = frameState.pixelRatio; var pixelRatio = frameState.pixelRatio;
@@ -317,40 +339,54 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra
var offsetX = Math.round(pixelRatio * size[0] / 2); var offsetX = Math.round(pixelRatio * size[0] / 2);
var offsetY = Math.round(pixelRatio * size[1] / 2); var offsetY = Math.round(pixelRatio * size[1] / 2);
var tiles = this.renderedTiles; var tiles = this.renderedTiles;
var tilePixelRatio = layer.getSource().getTilePixelRatio();
var sourceTileGrid = source.getTileGrid();
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var clips = []; var clips = [];
var zs = []; var zs = [];
for (var i = tiles.length - 1; i >= 0; --i) { for (var i = tiles.length - 1; i >= 0; --i) {
var tile = /** @type {ol.VectorTile} */ (tiles[i]); var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
// Create a clip mask for regions in this low resolution tile that are if (tile.getState() == ol.TileState.ABORT) {
// already filled by a higher resolution tile continue;
var transform = this.getReplayTransform_(tile, frameState); }
var currentClip = tile.getReplayState().replayGroup.getClipCoords(transform); var tileCoord = tile.tileCoord;
var currentZ = tile.tileCoord[0]; var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
context.save(); tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
context.globalAlpha = layerState.opacity; for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY); var sourceTile = tile.getTile(tile.tileKeys[t]);
for (var j = 0, jj = clips.length; j < jj; ++j) { var currentZ = sourceTile.tileCoord[0];
var clip = clips[j]; var sourceResolution = sourceTileGrid.getResolution(currentZ);
if (currentZ < zs[j]) { var transform = this.getReplayTransform_(sourceTile, frameState);
context.beginPath(); ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0);
// counter-clockwise (outer ring) for current tile var replayGroup = sourceTile.getReplayGroup(tileCoord.toString());
context.moveTo(currentClip[0], currentClip[1]); var currentClip = replayGroup.getClipCoords(transform);
context.lineTo(currentClip[2], currentClip[3]); context.save();
context.lineTo(currentClip[4], currentClip[5]); context.globalAlpha = layerState.opacity;
context.lineTo(currentClip[6], currentClip[7]); ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
// clockwise (inner ring) for higher resolution tile // Create a clip mask for regions in this low resolution tile that are
context.moveTo(clip[6], clip[7]); // already filled by a higher resolution tile
context.lineTo(clip[4], clip[5]); for (var j = 0, jj = clips.length; j < jj; ++j) {
context.lineTo(clip[2], clip[3]); var clip = clips[j];
context.lineTo(clip[0], clip[1]); if (currentZ < zs[j]) {
context.clip(); context.beginPath();
} // counter-clockwise (outer ring) for current tile
context.moveTo(currentClip[0], currentClip[1]);
context.lineTo(currentClip[2], currentClip[3]);
context.lineTo(currentClip[4], currentClip[5]);
context.lineTo(currentClip[6], currentClip[7]);
// clockwise (inner ring) for higher resolution tile
context.moveTo(clip[6], clip[7]);
context.lineTo(clip[4], clip[5]);
context.lineTo(clip[2], clip[3]);
context.lineTo(clip[0], clip[1]);
context.clip();
}
}
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
context.restore();
clips.push(currentClip);
zs.push(currentZ);
} }
var replayGroup = tile.getReplayState().replayGroup;
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
context.restore();
clips.push(currentClip);
zs.push(currentZ);
} }
} }
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments); ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
@@ -386,7 +422,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s
/** /**
* @param {ol.VectorTile} tile Tile. * @param {ol.VectorImageTile} tile Tile.
* @param {olx.FrameState} frameState Frame state. * @param {olx.FrameState} frameState Frame state.
* @param {ol.LayerState} layerState Layer state. * @param {ol.LayerState} layerState Layer state.
* @private * @private
@@ -399,28 +435,38 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()]; var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()];
if (replays && replayState.renderedTileRevision !== revision) { if (replays && replayState.renderedTileRevision !== revision) {
replayState.renderedTileRevision = revision; replayState.renderedTileRevision = revision;
var tileCoord = tile.tileCoord; var tileCoord = tile.wrappedTileCoord;
var z = tile.tileCoord[0]; var z = tileCoord[0];
var pixelRatio = frameState.pixelRatio; var pixelRatio = frameState.pixelRatio;
var source = layer.getSource(); var source = layer.getSource();
var tileGrid = source.getTileGrid(); var sourceTileGrid = source.getTileGrid();
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var resolution = tileGrid.getResolution(z);
var tilePixelRatio = source.getTilePixelRatio(); var tilePixelRatio = source.getTilePixelRatio();
var transform = ol.transform.reset(this.tmpTransform_);
if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
var renderPixelRatio = pixelRatio / tilePixelRatio;
ol.transform.scale(transform, renderPixelRatio, renderPixelRatio);
} else {
var resolution = tileGrid.getResolution(z);
var pixelScale = pixelRatio / resolution;
var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
ol.transform.scale(transform, pixelScale, -pixelScale);
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
}
var context = tile.getContext(); var context = tile.getContext();
var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection);
context.canvas.width = size[0]; context.canvas.width = size[0];
context.canvas.height = size[1]; context.canvas.height = size[1];
replayState.replayGroup.replay(context, pixelRatio, transform, 0, {}, replays); var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) {
var sourceTile = tile.getTile(tile.tileKeys[i]);
var sourceTileCoord = sourceTile.tileCoord;
var pixelScale = pixelRatio / resolution;
var transform = ol.transform.reset(this.tmpTransform_);
if (sourceTile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
var sourceResolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
var renderPixelRatio = pixelRatio / tilePixelRatio * sourceResolution / resolution;
ol.transform.scale(transform, renderPixelRatio, renderPixelRatio);
var offsetX = (sourceTileExtent[0] - tileExtent[0]) / sourceResolution * tilePixelRatio;
var offsetY = (tileExtent[3] - sourceTileExtent[3]) / sourceResolution * tilePixelRatio;
ol.transform.translate(transform, Math.round(offsetX), Math.round(offsetY));
} else {
ol.transform.scale(transform, pixelScale, -pixelScale);
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
}
var replayGroup = sourceTile.getReplayGroup(tile.tileCoord.toString());
replayGroup.replay(context, pixelRatio, transform, 0, {}, replays);
}
} }
}; };

View File

@@ -202,7 +202,8 @@ ol.reproj.Tile.prototype.disposeInternal = function() {
/** /**
* @inheritDoc * Get the HTML Canvas element for this tile.
* @return {HTMLCanvasElement} Canvas.
*/ */
ol.reproj.Tile.prototype.getImage = function() { ol.reproj.Tile.prototype.getImage = function() {
return this.canvas_; return this.canvas_;

View File

@@ -91,7 +91,6 @@ ol.inherits(ol.source.TileDebug.Tile_, ol.Tile);
/** /**
* Get the image element for this tile. * Get the image element for this tile.
* @return {HTMLCanvasElement} Image. * @return {HTMLCanvasElement} Image.
* @override
*/ */
ol.source.TileDebug.Tile_.prototype.getImage = function() { ol.source.TileDebug.Tile_.prototype.getImage = function() {
if (this.canvas_) { if (this.canvas_) {

View File

@@ -320,7 +320,6 @@ ol.inherits(ol.source.TileUTFGrid.Tile_, ol.Tile);
/** /**
* Get the image element for this tile. * Get the image element for this tile.
* @return {Image} Image. * @return {Image} Image.
* @override
*/ */
ol.source.TileUTFGrid.Tile_.prototype.getImage = function() { ol.source.TileUTFGrid.Tile_.prototype.getImage = function() {
return null; return null;

View File

@@ -2,10 +2,13 @@ goog.provide('ol.source.VectorTile');
goog.require('ol'); goog.require('ol');
goog.require('ol.TileState'); goog.require('ol.TileState');
goog.require('ol.VectorImageTile');
goog.require('ol.VectorTile'); goog.require('ol.VectorTile');
goog.require('ol.events'); goog.require('ol.events');
goog.require('ol.events.EventType'); goog.require('ol.events.EventType');
goog.require('ol.proj');
goog.require('ol.size'); goog.require('ol.size');
goog.require('ol.tilegrid');
goog.require('ol.source.UrlTile'); goog.require('ol.source.UrlTile');
@@ -37,7 +40,7 @@ ol.source.VectorTile = function(options) {
state: options.state, state: options.state,
tileGrid: options.tileGrid, tileGrid: options.tileGrid,
tileLoadFunction: options.tileLoadFunction ? tileLoadFunction: options.tileLoadFunction ?
options.tileLoadFunction : ol.VectorTile.defaultLoadFunction, options.tileLoadFunction : ol.VectorImageTile.defaultLoadFunction,
tileUrlFunction: options.tileUrlFunction, tileUrlFunction: options.tileUrlFunction,
tilePixelRatio: options.tilePixelRatio, tilePixelRatio: options.tilePixelRatio,
url: options.url, url: options.url,
@@ -51,6 +54,12 @@ ol.source.VectorTile = function(options) {
*/ */
this.format_ = options.format ? options.format : null; this.format_ = options.format ? options.format : null;
/**
* @private
* @type {Object.<string,ol.VectorTile>}
*/
this.sourceTiles_ = {};
/** /**
* @private * @private
* @type {boolean} * @type {boolean}
@@ -64,6 +73,16 @@ ol.source.VectorTile = function(options) {
*/ */
this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile; this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;
/**
* @private
* @type {Object.<string,ol.tilegrid.TileGrid>}
*/
this.tileGrids_ = {};
if (!this.tileGrid) {
this.tileGrid = this.getTileGridForProjection(ol.proj.get(options.projection || 'EPSG:3857'));
}
}; };
ol.inherits(ol.source.VectorTile, ol.source.UrlTile); ol.inherits(ol.source.VectorTile, ol.source.UrlTile);
@@ -89,11 +108,13 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio
tileCoord, projection); tileCoord, projection);
var tileUrl = urlTileCoord ? var tileUrl = urlTileCoord ?
this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
var tile = new this.tileClass( var tile = new ol.VectorImageTile(
tileCoord, tileCoord,
tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY, tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
tileUrl !== undefined ? tileUrl : '', tileUrl !== undefined ? tileUrl : '',
this.format_, this.tileLoadFunction); this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction,
this.tileGrid, this.getTileGridForProjection(projection),
this.sourceTiles_, pixelRatio, projection, this.tileClass);
ol.events.listen(tile, ol.events.EventType.CHANGE, ol.events.listen(tile, ol.events.EventType.CHANGE,
this.handleTileChange, this); this.handleTileChange, this);
@@ -103,6 +124,23 @@ ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projectio
}; };
/**
* @inheritDoc
*/
ol.source.VectorTile.prototype.getTileGridForProjection = function(projection) {
var code = projection.getCode();
var tileGrid = this.tileGrids_[code];
if (!tileGrid) {
// A tile grid that matches the tile size of the source tile grid is more
// likely to have 1:1 relationships between source tiles and rendered tiles.
var sourceTileGrid = this.tileGrid;
tileGrid = this.tileGrids_[code] = ol.tilegrid.createForProjection(projection, undefined,
sourceTileGrid ? sourceTileGrid.getTileSize(sourceTileGrid.getMinZoom()) : undefined);
}
return tileGrid;
};
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -117,6 +155,6 @@ ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
* @inheritDoc * @inheritDoc
*/ */
ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) { ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z)); var tileSize = ol.size.toSize(this.getTileGridForProjection(projection).getTileSize(z));
return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)]; return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
}; };

View File

@@ -59,14 +59,6 @@ ol.Tile.prototype.changed = function() {
}; };
/**
* Get the HTML image element for this tile (may be a Canvas, Image, or Video).
* @abstract
* @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
*/
ol.Tile.prototype.getImage = function() {};
/** /**
* @return {string} Key. * @return {string} Key.
*/ */

View File

@@ -74,7 +74,7 @@ ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_co
/** /**
* Creates a tile grid with a standard XYZ tiling scheme. * Creates a tile grid with a standard XYZ tiling scheme.
* @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options. * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options.
* @return {ol.tilegrid.TileGrid} Tile grid instance. * @return {!ol.tilegrid.TileGrid} Tile grid instance.
* @api * @api
*/ */
ol.tilegrid.createXYZ = function(opt_options) { ol.tilegrid.createXYZ = function(opt_options) {
@@ -126,7 +126,8 @@ ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize)
* @param {ol.ProjectionLike} projection Projection. * @param {ol.ProjectionLike} projection Projection.
* @param {number=} opt_maxZoom Maximum zoom level (default is * @param {number=} opt_maxZoom Maximum zoom level (default is
* ol.DEFAULT_MAX_ZOOM). * ol.DEFAULT_MAX_ZOOM).
* @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE). * @param {number|ol.Size=} opt_tileSize Tile size (default uses
* ol.DEFAULT_TILE_SIZE).
* @param {ol.extent.Corner=} opt_corner Extent corner (default is * @param {ol.extent.Corner=} opt_corner Extent corner (default is
* ol.extent.Corner.BOTTOM_LEFT). * ol.extent.Corner.BOTTOM_LEFT).
* @return {!ol.tilegrid.TileGrid} TileGrid instance. * @return {!ol.tilegrid.TileGrid} TileGrid instance.

View File

@@ -652,8 +652,7 @@ ol.TilePriorityFunction;
* dirty: boolean, * dirty: boolean,
* renderedRenderOrder: (null|ol.RenderOrderFunction), * renderedRenderOrder: (null|ol.RenderOrderFunction),
* renderedTileRevision: number, * renderedTileRevision: number,
* renderedRevision: number, * renderedRevision: number}}
* replayGroup: ol.render.ReplayGroup}}
*/ */
ol.TileReplayState; ol.TileReplayState;

291
src/ol/vectorimagetile.js Normal file
View File

@@ -0,0 +1,291 @@
goog.provide('ol.VectorImageTile');
goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.TileState');
goog.require('ol.array');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.extent');
goog.require('ol.events.EventType');
goog.require('ol.featureloader');
/**
* @constructor
* @extends {ol.Tile}
* @param {ol.TileCoord} tileCoord Tile coordinate.
* @param {ol.TileState} state State.
* @param {string} src Data source url.
* @param {ol.format.Feature} format Feature format.
* @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
* @param {ol.TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
* @param {ol.TileUrlFunctionType} tileUrlFunction Tile url function.
* @param {ol.tilegrid.TileGrid} sourceTileGrid Tile grid of the source.
* @param {ol.tilegrid.TileGrid} tileGrid Tile grid of the renderer.
* @param {Object.<string,ol.VectorTile>} sourceTiles Source tiles.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.proj.Projection} projection Projection.
* @param {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string,
* ol.format.Feature, ol.TileLoadFunctionType)} tileClass Class to
* instantiate for source tiles.
*/
ol.VectorImageTile = function(tileCoord, state, src, format, tileLoadFunction,
urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles,
pixelRatio, projection, tileClass) {
ol.Tile.call(this, tileCoord, state);
/**
* @private
* @type {CanvasRenderingContext2D}
*/
this.context_ = null;
/**
* @private
* @type {ol.format.Feature}
*/
this.format_ = format;
/**
* @private
* @type {ol.FeatureLoader}
*/
this.loader_;
/**
* @private
* @type {ol.TileReplayState}
*/
this.replayState_ = {
dirty: false,
renderedRenderOrder: null,
renderedRevision: -1,
renderedTileRevision: -1
};
/**
* @private
* @type {Object.<string,ol.VectorTile>}
*/
this.sourceTiles_ = sourceTiles;
/**
* Keys of source tiles used by this tile. Use with {@link #getTile}.
* @type {Array.<string>}
*/
this.tileKeys = [];
/**
* @type {string}
*/
this.src_ = src;
/**
* @type {ol.TileCoord}
*/
this.wrappedTileCoord = urlTileCoord;
/**
* @type {Array.<ol.EventsKey>}
*/
this.loadListenerKeys_ = [];
if (urlTileCoord) {
var extent = tileGrid.getTileCoordExtent(urlTileCoord);
var resolution = tileGrid.getResolution(tileCoord[0]);
var sourceZ = sourceTileGrid.getZForResolution(resolution);
sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) {
var sharedExtent = ol.extent.getIntersection(extent,
sourceTileGrid.getTileCoordExtent(sourceTileCoord));
if (ol.extent.getWidth(sharedExtent) / resolution >= 0.5 &&
ol.extent.getHeight(sharedExtent) / resolution >= 0.5) {
// only include source tile if overlap is at least 1 pixel
var sourceTileKey = sourceTileCoord.toString();
var sourceTile = sourceTiles[sourceTileKey];
if (!sourceTile) {
var tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection);
sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord,
tileUrl == undefined ? ol.TileState.EMPTY : ol.TileState.IDLE,
tileUrl == undefined ? '' : tileUrl,
format, tileLoadFunction);
}
sourceTile.consumers++;
this.tileKeys.push(sourceTileKey);
}
}.bind(this));
}
};
ol.inherits(ol.VectorImageTile, ol.Tile);
/**
* @inheritDoc
*/
ol.VectorImageTile.prototype.disposeInternal = function() {
for (var i = 0, ii = this.tileKeys.length; i < ii; ++i) {
var sourceTileKey = this.tileKeys[i];
var sourceTile = this.getTile(sourceTileKey);
sourceTile.consumers--;
if (sourceTile.consumers == 0) {
delete this.sourceTiles_[sourceTileKey];
sourceTile.dispose();
}
}
this.tileKeys.length = 0;
this.sourceTiles_ = null;
if (this.state == ol.TileState.LOADING) {
this.loadListenerKeys_.forEach(ol.events.unlistenByKey);
this.loadListenerKeys_.length = 0;
}
if (this.interimTile) {
this.interimTile.dispose();
}
this.state = ol.TileState.ABORT;
this.changed();
ol.Tile.prototype.disposeInternal.call(this);
};
/**
* @return {CanvasRenderingContext2D} The rendering context.
*/
ol.VectorImageTile.prototype.getContext = function() {
if (!this.context_) {
this.context_ = ol.dom.createCanvasContext2D();
}
return this.context_;
};
/**
* Get the Canvas for this tile.
* @return {HTMLCanvasElement} Canvas.
* @api
*/
ol.VectorImageTile.prototype.getImage = function() {
return this.replayState_.renderedTileRevision == -1 ?
null : this.context_.canvas;
};
/**
* Get the feature format assigned for reading this tile's features.
* @return {ol.format.Feature} Feature format.
* @api
*/
ol.VectorImageTile.prototype.getFormat = function() {
return this.format_;
};
/**
* @return {ol.TileReplayState} The replay state.
*/
ol.VectorImageTile.prototype.getReplayState = function() {
return this.replayState_;
};
/**
* @inheritDoc
*/
ol.VectorImageTile.prototype.getKey = function() {
return this.tileKeys.join('/') + '/' + this.src_;
};
/**
* @param {string} tileKey Key (tileCoord) of the source tile.
* @return {ol.VectorTile} Source tile.
*/
ol.VectorImageTile.prototype.getTile = function(tileKey) {
return this.sourceTiles_[tileKey];
};
/**
* @inheritDoc
*/
ol.VectorImageTile.prototype.load = function() {
var leftToLoad = 0;
var errors = false;
if (this.state == ol.TileState.IDLE) {
this.setState(ol.TileState.LOADING);
}
if (this.state == ol.TileState.LOADING) {
this.tileKeys.forEach(function(sourceTileKey) {
var sourceTile = this.getTile(sourceTileKey);
if (sourceTile.state == ol.TileState.IDLE) {
sourceTile.setLoader(this.loader_);
sourceTile.load();
} else if (sourceTile.state == ol.TileState.ERROR) {
errors = true;
} else if (sourceTile.state == ol.TileState.EMPTY) {
ol.array.remove(this.tileKeys, sourceTileKey);
}
if (sourceTile.state == ol.TileState.LOADING) {
var key = ol.events.listen(sourceTile, ol.events.EventType.CHANGE, function(e) {
var state = sourceTile.getState();
if (state == ol.TileState.LOADED ||
state == ol.TileState.ERROR) {
--leftToLoad;
ol.events.unlistenByKey(key);
ol.array.remove(this.loadListenerKeys_, key);
if (state == ol.TileState.ERROR) {
ol.array.remove(this.tileKeys, sourceTileKey);
errors = true;
}
if (leftToLoad == 0) {
this.setState(this.tileKeys.length > 0 ?
ol.TileState.LOADED : ol.TileState.ERROR);
}
}
}.bind(this));
this.loadListenerKeys_.push(key);
++leftToLoad;
}
}.bind(this));
}
if (leftToLoad == 0) {
setTimeout(function() {
this.setState(this.tileKeys.length > 0 ?
ol.TileState.LOADED :
(errors ? ol.TileState.ERROR : ol.TileState.EMPTY));
}.bind(this), 0);
}
};
/**
* @param {Array.<ol.Feature>} features Features.
* @api
*/
ol.VectorImageTile.prototype.setFeatures = function(features) {
this.features_ = features;
this.setState(ol.TileState.LOADED);
};
/**
* @param {ol.TileState} tileState Tile state.
*/
ol.VectorImageTile.prototype.setState = function(tileState) {
this.state = tileState;
this.changed();
};
/**
* Sets the loader for a tile.
* @param {ol.VectorTile} tile Vector tile.
* @param {string} url URL.
*/
ol.VectorImageTile.defaultLoadFunction = function(tile, url) {
var loader = ol.featureloader.loadFeaturesXhr(
url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile));
tile.setLoader(loader);
};

View File

@@ -3,8 +3,6 @@ goog.provide('ol.VectorTile');
goog.require('ol'); goog.require('ol');
goog.require('ol.Tile'); goog.require('ol.Tile');
goog.require('ol.TileState'); goog.require('ol.TileState');
goog.require('ol.dom');
goog.require('ol.featureloader');
/** /**
@@ -21,10 +19,9 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
ol.Tile.call(this, tileCoord, state); ol.Tile.call(this, tileCoord, state);
/** /**
* @private * @type {number}
* @type {CanvasRenderingContext2D}
*/ */
this.context_ = null; this.consumers = 0;
/** /**
* @private * @private
@@ -51,17 +48,7 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
*/ */
this.projection_; this.projection_;
/** this.replayGroups_ = {};
* @private
* @type {ol.TileReplayState}
*/
this.replayState_ = {
dirty: false,
renderedRenderOrder: null,
renderedRevision: -1,
renderedTileRevision: -1,
replayGroup: null
};
/** /**
* @private * @private
@@ -79,26 +66,6 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
ol.inherits(ol.VectorTile, ol.Tile); ol.inherits(ol.VectorTile, ol.Tile);
/**
* @return {CanvasRenderingContext2D} The rendering context.
*/
ol.VectorTile.prototype.getContext = function() {
if (!this.context_) {
this.context_ = ol.dom.createCanvasContext2D();
}
return this.context_;
};
/**
* @override
*/
ol.VectorTile.prototype.getImage = function() {
return this.replayState_.renderedTileRevision == -1 ?
null : this.context_.canvas;
};
/** /**
* Get the feature format assigned for reading this tile's features. * Get the feature format assigned for reading this tile's features.
* @return {ol.format.Feature} Feature format. * @return {ol.format.Feature} Feature format.
@@ -117,14 +84,6 @@ ol.VectorTile.prototype.getFeatures = function() {
}; };
/**
* @return {ol.TileReplayState} The replay state.
*/
ol.VectorTile.prototype.getReplayState = function() {
return this.replayState_;
};
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -141,6 +100,11 @@ ol.VectorTile.prototype.getProjection = function() {
}; };
ol.VectorTile.prototype.getReplayGroup = function(key) {
return this.replayGroups_[key];
};
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -192,6 +156,11 @@ ol.VectorTile.prototype.setProjection = function(projection) {
}; };
ol.VectorTile.prototype.setReplayGroup = function(key, replayGroup) {
this.replayGroups_[key] = replayGroup;
};
/** /**
* @param {ol.TileState} tileState Tile state. * @param {ol.TileState} tileState Tile state.
*/ */
@@ -209,16 +178,3 @@ ol.VectorTile.prototype.setState = function(tileState) {
ol.VectorTile.prototype.setLoader = function(loader) { ol.VectorTile.prototype.setLoader = function(loader) {
this.loader_ = loader; this.loader_ = loader;
}; };
/**
* Sets the loader for a tile.
* @param {ol.VectorTile} tile Vector tile.
* @param {string} url URL.
*/
ol.VectorTile.defaultLoadFunction = function(tile, url) {
var loader = ol.featureloader.loadFeaturesXhr(
url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile));
tile.setLoader(loader);
};

View File

@@ -62,6 +62,11 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
tileClass: TileClass, tileClass: TileClass,
tileGrid: ol.tilegrid.createXYZ() tileGrid: ol.tilegrid.createXYZ()
}); });
source.getTile = function() {
var tile = ol.source.VectorTile.prototype.getTile.apply(source, arguments);
tile.setState(ol.TileState.LOADED);
return tile;
};
layer = new ol.layer.VectorTile({ layer = new ol.layer.VectorTile({
source: source, source: source,
style: layerStyle style: layerStyle
@@ -156,12 +161,18 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
tileGrid: ol.tilegrid.createXYZ() tileGrid: ol.tilegrid.createXYZ()
}) })
}); });
var tile = new ol.VectorTile([0, 0, 0], 2); var sourceTile = new ol.VectorTile([0, 0, 0], 2);
tile.projection_ = ol.proj.get('EPSG:3857'); sourceTile.setProjection(ol.proj.get('EPSG:3857'));
tile.features_ = []; sourceTile.features_ = [];
tile.getImage = function() { sourceTile.getImage = function() {
return document.createElement('canvas'); return document.createElement('canvas');
}; };
var tile = new ol.VectorImageTile([0, 0, 0]);
tile.wrappedTileCoord = [0, 0, 0];
tile.setState(ol.TileState.LOADED);
tile.getSourceTile = function() {
return sourceTile;
};
layer.getSource().getTile = function() { layer.getSource().getTile = function() {
return tile; return tile;
}; };
@@ -195,12 +206,19 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
describe('#forEachFeatureAtCoordinate', function() { describe('#forEachFeatureAtCoordinate', function() {
var layer, renderer, replayGroup; var layer, renderer, replayGroup;
var TileClass = function() { var TileClass = function() {
ol.VectorTile.apply(this, arguments); ol.VectorImageTile.apply(this, arguments);
this.setState('loaded'); this.setState('loaded');
this.setProjection(ol.proj.get('EPSG:3857')); var sourceTile = new ol.VectorTile([0, 0, 0]);
this.replayState_.replayGroup = replayGroup; sourceTile.setProjection(ol.proj.get('EPSG:3857'));
sourceTile.getReplayGroup = function() {
return replayGroup;
};
var key = sourceTile.tileCoord.toString();
this.tileKeys = [key];
this.sourceTiles_ = {};
this.sourceTiles_[key] = sourceTile;
}; };
ol.inherits(TileClass, ol.VectorTile); ol.inherits(TileClass, ol.VectorImageTile);
beforeEach(function() { beforeEach(function() {
replayGroup = {}; replayGroup = {};
@@ -226,6 +244,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
layerStates: {}, layerStates: {},
skippedFeatureUids: {}, skippedFeatureUids: {},
viewState: { viewState: {
projection: ol.proj.get('EPSG:3857'),
resolution: 1, resolution: 1,
rotation: 0 rotation: 0
} }

View File

@@ -1,6 +1,6 @@
goog.provide('ol.test.source.VectorTile'); goog.provide('ol.test.source.VectorTile');
goog.require('ol.VectorTile'); goog.require('ol.VectorImageTile');
goog.require('ol.format.MVT'); goog.require('ol.format.MVT');
goog.require('ol.proj'); goog.require('ol.proj');
goog.require('ol.source.VectorTile'); goog.require('ol.source.VectorTile');
@@ -12,7 +12,7 @@ describe('ol.source.VectorTile', function() {
var format = new ol.format.MVT(); var format = new ol.format.MVT();
var source = new ol.source.VectorTile({ var source = new ol.source.VectorTile({
format: format, format: format,
tileGrid: ol.tilegrid.createXYZ(), tileGrid: ol.tilegrid.createXYZ({tileSize: 512}),
url: '{z}/{x}/{y}.pbf' url: '{z}/{x}/{y}.pbf'
}); });
var tile; var tile;
@@ -29,7 +29,7 @@ describe('ol.source.VectorTile', function() {
describe('#getTile()', function() { describe('#getTile()', function() {
it('creates a tile with the correct tile class', function() { it('creates a tile with the correct tile class', function() {
tile = source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857')); tile = source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857'));
expect(tile).to.be.a(ol.VectorTile); expect(tile).to.be.a(ol.VectorImageTile);
}); });
it('sets the correct tileCoord on the created tile', function() { it('sets the correct tileCoord on the created tile', function() {
expect(tile.getTileCoord()).to.eql([0, 0, 0]); expect(tile.getTileCoord()).to.eql([0, 0, 0]);
@@ -40,4 +40,11 @@ describe('ol.source.VectorTile', function() {
}); });
}); });
describe('#getTileGridForProjection', function() {
it('creates a tile grid with the source tile grid\'s tile size', function() {
var tileGrid = source.getTileGridForProjection(ol.proj.get('EPSG:3857'));
expect(tileGrid.getTileSize(0)).to.be(512);
});
});
}); });

View File

@@ -0,0 +1,107 @@
goog.provide('ol.test.VectorImageTile');
goog.require('ol.events');
goog.require('ol.VectorImageTile');
goog.require('ol.VectorTile');
goog.require('ol.format.GeoJSON');
goog.require('ol.proj');
describe('ol.VectorImageTile', function() {
it('configures loader that sets features on the source tile', function(done) {
var format = new ol.format.GeoJSON();
var url = 'spec/ol/data/point.json';
var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format,
ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {
return url;
}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {},
1, ol.proj.get('EPSG:3857'), ol.VectorTile);
tile.load();
var sourceTile = tile.getTile(tile.tileKeys[0]);
var loader = sourceTile.loader_;
expect(typeof loader).to.be('function');
ol.events.listen(sourceTile, 'change', function(e) {
expect(sourceTile.getFeatures().length).to.be.greaterThan(0);
done();
});
});
it('sets ERROR state when source tiles fail to load', function(done) {
var format = new ol.format.GeoJSON();
var url = 'spec/ol/data/unavailable.json';
var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format,
ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {
return url;
}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {},
1, ol.proj.get('EPSG:3857'), ol.VectorTile);
tile.load();
ol.events.listen(tile, 'change', function(e) {
expect(tile.getState()).to.be(ol.TileState.ERROR);
done();
});
});
it('sets EMPTY state when tile has only empty source tiles', function(done) {
var format = new ol.format.GeoJSON();
var url = '';
var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format,
ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {},
ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ(), {},
1, ol.proj.get('EPSG:3857'), ol.VectorTile);
tile.load();
ol.events.listen(tile, 'change', function() {
expect(tile.getState()).to.be(ol.TileState.EMPTY);
done();
});
});
it('#dispose() while loading', function() {
var format = new ol.format.GeoJSON();
var url = 'spec/ol/data/point.json';
var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format,
ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {
return url;
}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ({tileSize: 512}), {},
1, ol.proj.get('EPSG:3857'), ol.VectorTile);
tile.load();
expect(tile.loadListenerKeys_.length).to.be(4);
expect(tile.tileKeys.length).to.be(4);
expect(tile.getState()).to.be(ol.TileState.LOADING);
tile.dispose();
expect(tile.loadListenerKeys_.length).to.be(0);
expect(tile.tileKeys.length).to.be(0);
expect(tile.sourceTiles_).to.be(null);
expect(tile.getState()).to.be(ol.TileState.ABORT);
});
it('#dispose() when loaded', function(done) {
var format = new ol.format.GeoJSON();
var url = 'spec/ol/data/point.json';
var tile = new ol.VectorImageTile([0, 0, 0], 0, url, format,
ol.VectorImageTile.defaultLoadFunction, [0, 0, 0], function() {
return url;
}, ol.tilegrid.createXYZ(), ol.tilegrid.createXYZ({tileSize: 512}), {},
1, ol.proj.get('EPSG:3857'), ol.VectorTile);
tile.load();
ol.events.listenOnce(tile, 'change', function() {
expect(tile.getState()).to.be(ol.TileState.LOADED);
expect(tile.loadListenerKeys_.length).to.be(0);
expect(tile.tileKeys.length).to.be(4);
tile.dispose();
expect(tile.tileKeys.length).to.be(0);
expect(tile.sourceTiles_).to.be(null);
expect(tile.getState()).to.be(ol.TileState.ABORT);
done();
});
});
});

View File

@@ -1,39 +1,14 @@
goog.provide('ol.test.VectorTile'); goog.provide('ol.test.VectorTile');
goog.require('ol.events'); goog.require('ol.events');
goog.require('ol.VectorImageTile');
goog.require('ol.VectorTile'); goog.require('ol.VectorTile');
goog.require('ol.Feature'); goog.require('ol.Feature');
goog.require('ol.format.GeoJSON');
goog.require('ol.format.TextFeature'); goog.require('ol.format.TextFeature');
goog.require('ol.proj'); goog.require('ol.proj');
describe('ol.VectorTile.defaultLoadFunction()', function() { describe('ol.VectorTile', function() {
it('sets the loader function on the tile', function() {
var format = new ol.format.GeoJSON();
var tile = new ol.VectorTile([0, 0, 0], null, null, format);
var url = 'https://example.com/';
ol.VectorTile.defaultLoadFunction(tile, url);
var loader = tile.loader_;
expect(typeof loader).to.be('function');
});
it('loader sets features on the tile', function(done) {
var format = new ol.format.GeoJSON();
var tile = new ol.VectorTile([0, 0, 0], null, null, format);
var url = 'spec/ol/data/point.json';
ol.VectorTile.defaultLoadFunction(tile, url);
var loader = tile.loader_;
ol.events.listen(tile, 'change', function(e) {
expect(tile.getFeatures().length).to.be.greaterThan(0);
done();
});
loader.call(tile, [], 1, ol.proj.get('EPSG:3857'));
});
it('loader sets features on the tile and updates proj units', function(done) { it('loader sets features on the tile and updates proj units', function(done) {
// mock format that return a tile-pixels feature // mock format that return a tile-pixels feature
@@ -51,7 +26,7 @@ describe('ol.VectorTile.defaultLoadFunction()', function() {
var tile = new ol.VectorTile([0, 0, 0], null, null, format); var tile = new ol.VectorTile([0, 0, 0], null, null, format);
var url = 'spec/ol/data/point.json'; var url = 'spec/ol/data/point.json';
ol.VectorTile.defaultLoadFunction(tile, url); ol.VectorImageTile.defaultLoadFunction(tile, url);
var loader = tile.loader_; var loader = tile.loader_;
ol.events.listen(tile, 'change', function(e) { ol.events.listen(tile, 'change', function(e) {
expect(tile.getFeatures().length).to.be.greaterThan(0); expect(tile.getFeatures().length).to.be.greaterThan(0);