Additional tests, documentation and example

This commit is contained in:
Andreas Hocevar
2015-09-27 20:36:07 +02:00
parent 0e8e104a2d
commit 8e9b20db51
14 changed files with 560 additions and 21 deletions

View File

@@ -0,0 +1,3 @@
.map {
background: #f8f4f0;
}

View File

@@ -0,0 +1,15 @@
---
template: example.html
title: Simple Mapbox vector tiles example
shortdesc: Example of a simple Mapbox vector tiles map.
docs: >
A simple vector tiles map. **Note**: Make sure to get your own Mapbox API key when using this example. No map will be visible when the API key has expired.
tags: "simple, mapbox, vector, tiles"
resources:
- resources/mapbox-streets-v6-style.js
---
<div class="row-fluid">
<div class="span12">
<div id="map" class="map"></div>
</div>
</div>

View File

@@ -0,0 +1,44 @@
goog.require('ol.Attribution');
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.format.MVT');
goog.require('ol.layer.VectorTile');
goog.require('ol.source.VectorTile');
goog.require('ol.style.Fill');
goog.require('ol.style.Icon');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
goog.require('ol.style.Text');
// Mapbox access token - request your own at http://mapbox.com
var accessToken =
'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
var map = new ol.Map({
layers: [
new ol.layer.VectorTile({
source: new ol.source.VectorTile({
attributions: [new ol.Attribution({
html: '© <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> ' +
'© <a href="http://www.openstreetmap.org/copyright">' +
'OpenStreetMap contributors</a>'
})],
format: new ol.format.MVT(),
tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}),
tilePixelRatio: 16,
url: 'http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=' + accessToken
}),
style: createMapboxStreetsV6Style()
})
],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
// ol.style.Fill, ol.style.Icon, ol.style.Stroke, ol.style.Style and
// ol.style.Text are required for createMapboxStreetsV6Style()

View File

@@ -3,7 +3,7 @@ template: example.html
title: Mapbox vector tiles example
shortdesc: Example of a Mapbox vector tiles map.
docs: >
A simple vector tiles map.
A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwith on mobile devices. **Note**: Make sure to get your own Mapbox API key when using this example. No map will be visible when the API key has expired.
tags: "simple, mapbox, vector, tiles"
resources:
- resources/mapbox-streets-v6-style.js

View File

@@ -13,21 +13,36 @@ goog.require('ol.style.Text');
goog.require('ol.tilegrid.TileGrid');
// Mapbox access token - request your own at http://mabobox.com
// Mapbox access token - request your own at http://mapbox.com
var accessToken =
'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
// For how many zoom levels do we want to use the same vector tile?
// For how many zoom levels do we want to use the same vector tiles?
// 1 means "use tiles from all zoom levels". 2 means "use the same tiles for 2
// subsequent zoom levels".
var reuseZoomLevels = 2;
// Offset from web mercator zoom level 0
var zOffset = 1;
// 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 = [];
for (var z = zOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) {
for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) {
resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels));
}
function tileUrlFunction(tileCoord) {
return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=' + accessToken)
.replace('{z}', String(tileCoord[0] * reuseZoomLevels + zoomOffset))
.replace('{x}', String(tileCoord[1]))
.replace('{y}', String(-tileCoord[2] - 1))
.replace('{a-d}', 'abcd'.substr(
((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1));
}
var map = new ol.Map({
var map = new ol.Map({
layers: [
new ol.layer.VectorTile({
preload: Infinity,
@@ -37,22 +52,13 @@ var map = new ol.Map({
'© <a href="http://www.openstreetmap.org/copyright">' +
'OpenStreetMap contributors</a>'
})],
rightHandedPolygons: true,
format: new ol.format.MVT(),
tileGrid: new ol.tilegrid.TileGrid({
extent: ol.proj.get('EPSG:3857').getExtent(),
resolutions: resolutions
}),
tilePixelRatio: 16,
tileUrlFunction: function(tileCoord) {
return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
'{z}/{x}/{y}.vector.pbf?access_token=' + accessToken)
.replace('{z}', String(tileCoord[0] * reuseZoomLevels + zOffset))
.replace('{x}', String(tileCoord[1]))
.replace('{y}', String(-tileCoord[2] - 1))
.replace('{a-d}', 'abcd'.substr(
((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1));
}
tileUrlFunction: tileUrlFunction
}),
style: createMapboxStreetsV6Style()
})
@@ -64,3 +70,6 @@ var map = new ol.Map({
zoom: 3
})
});
// ol.style.Fill, ol.style.Icon, ol.style.Stroke, ol.style.Style and
// ol.style.Text are required for createMapboxStreetsV6Style()

View File

@@ -16,7 +16,7 @@ ol.layer.VectorTileProperty = {
/**
* @classdesc
* Vector tile data that is rendered client-side.
* Layer for vector tile data that is rendered client-side.
* Note that any property set in the options is set as a {@link ol.Object}
* property on the layer object; for example, setting `title: 'My Title'` in the
* options means that `title` is observable, and has get/set accessors.

View File

@@ -10,7 +10,7 @@ goog.require('ol.geom.GeometryType');
/**
* Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like
* structure, optimized for rendering and styling. Geometry acces through the
* structure, optimized for rendering and styling. Geometry access through the
* API is limited to getting the type and extent of the geometry.
*
* @constructor

View File

@@ -66,7 +66,9 @@ ol.source.VectorEventType = {
/**
* @classdesc
* Provides a source of features for vector layers.
* Provides a source of features for vector layers. Vector features provided
* by this source are suitable for editing. See {@link ol.source.VectorTile} for
* vector data that is optimized for rendering.
*
* @constructor
* @extends {ol.source.Source}

View File

@@ -12,7 +12,13 @@ goog.require('ol.source.UrlTile');
/**
* @classdesc
* Base class for sources providing images divided into a tile grid.
* Class for layer sources providing vector data divided into a tile grid, to be
* used with {@link ol.layer.VectorTile}. Although this source receives tiles
* with vector features from the server, it is not meant for feature editing.
* Features are optimized for rendering, their geometries are clipped at or near
* tile boundaries and simplified for a view resolution. See
* {@link ol.source.Vector} for vector sources that are suitable for feature
* editing.
*
* @constructor
* @fires ol.source.TileEvent

View File

@@ -0,0 +1,37 @@
goog.provide('ol.test.layer.VectorTile');
describe('ol.layer.VectorTile', function() {
describe('constructor (defaults)', function() {
var layer;
beforeEach(function() {
layer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({})
});
});
afterEach(function() {
goog.dispose(layer);
});
it('creates an instance', function() {
expect(layer).to.be.a(ol.layer.VectorTile);
});
it('provides default preload', function() {
expect(layer.getPreload()).to.be(0);
});
it('provides default useInterimTilesOnError', function() {
expect(layer.getUseInterimTilesOnError()).to.be(true);
});
});
});
goog.require('goog.dispose');
goog.require('ol.layer.VectorTile');
goog.require('ol.source.VectorTile');

View File

@@ -0,0 +1,90 @@
goog.provide('ol.test.render.Feature');
describe('ol.render.Feature', function() {
var renderFeature;
var type = 'Point';
var flatCoordinates = [0, 0];
var ends = null;
var properties = {foo: 'bar'};
describe('Constructor', function() {
it('creates an instance', function() {
renderFeature =
new ol.render.Feature(type, flatCoordinates, ends, properties);
expect(renderFeature).to.be.a(ol.render.Feature);
});
});
describe('#get()', function() {
it('returns a single property', function() {
expect(renderFeature.get('foo')).to.be('bar');
});
});
describe('#getEnds()', function() {
it('returns the ends it was created with', function() {
expect(renderFeature.getEnds()).to.equal(ends);
});
});
describe('#getExtent()', function() {
it('returns the correct extent for a point', function() {
expect(renderFeature.getExtent()).to.eql([0, 0, 0, 0]);
});
it('caches the extent', function() {
expect(renderFeature.getExtent()).to.equal(renderFeature.extent_);
});
it('returns the correct extent for a linestring', function() {
var feature =
new ol.render.Feature('LineString', [-1, -2, 2, 1], null, {});
expect(feature.getExtent()).to.eql([-1, -2, 2, 1]);
});
});
describe('#getFlatCoordinates()', function() {
it('returns the flat coordinates it was created with', function() {
expect(renderFeature.getFlatCoordinates()).to.equal(flatCoordinates);
});
});
describe('#getGeometry()', function() {
it('returns itself as geometry', function() {
expect(renderFeature.getGeometry()).to.equal(renderFeature);
});
});
describe('#getProperties()', function() {
it('returns the properties it was created with', function() {
expect(renderFeature.getProperties()).to.equal(properties);
});
});
describe('#getSimplifiedGeometry()', function() {
it('returns itself as simplified geometry', function() {
expect(renderFeature.getSimplifiedGeometry()).to.equal(renderFeature);
});
});
describe('#getStride()', function() {
it('returns 2', function() {
expect(renderFeature.getStride()).to.be(2);
});
});
describe('#getStyleFunction()', function() {
it('returns undefined', function() {
expect(renderFeature.getStyleFunction()).to.be(undefined);
});
});
describe('#getType()', function() {
it('returns the type it was created with', function() {
expect(renderFeature.getType()).to.equal(type);
});
});
});
goog.require('ol.render.Feature');

View File

@@ -0,0 +1,128 @@
goog.provide('ol.test.renderer.canvas.VectorTileLayer');
describe('ol.renderer.canvas.VectorTileLayer', function() {
describe('constructor', function() {
it('creates a new instance', function() {
var layer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({})
});
var renderer = new ol.renderer.canvas.VectorTileLayer(layer);
expect(renderer).to.be.a(ol.renderer.canvas.VectorTileLayer);
});
it('gives precedence to feature styles over layer styles', function() {
var target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
var map = new ol.Map({
view: new ol.View({
center: [0, 0],
zoom: 0
}),
target: target
});
var layerStyle = [new ol.style.Style({
text: new ol.style.Text({
text: 'layer'
})
})];
var featureStyle = [new ol.style.Style({
text: new ol.style.Text({
text: 'feature'
})
})];
var feature1 = new ol.Feature(new ol.geom.Point([0, 0]));
var feature2 = new ol.Feature(new ol.geom.Point([0, 0]));
feature2.setStyle(featureStyle);
var TileClass = function() {
ol.VectorTile.apply(this, arguments);
this.setState('loaded');
this.setFeatures([feature1, feature2]);
this.setProjection(ol.proj.get('EPSG:3857'));
};
ol.inherits(TileClass, ol.VectorTile);
var source = new ol.source.VectorTile({
format: new ol.format.MVT(),
tileClass: TileClass,
tileGrid: ol.tilegrid.createXYZ()
});
var layer = new ol.layer.VectorTile({
source: source,
style: layerStyle
});
map.addLayer(layer);
var spy = sinon.spy(map.getRenderer().getLayerRenderer(layer),
'renderFeature');
map.renderSync();
expect(spy.getCall(0).args[2]).to.be(layerStyle);
expect(spy.getCall(1).args[2]).to.be(featureStyle);
document.body.removeChild(target);
});
});
describe('#forEachFeatureAtCoordinate', function() {
var layer, renderer, replayGroup;
var TileClass = function() {
ol.VectorTile.apply(this, arguments);
this.setState('loaded');
this.setProjection(ol.proj.get('EPSG:3857'));
this.replayState_.replayGroup = replayGroup;
};
ol.inherits(TileClass, ol.VectorTile);
beforeEach(function() {
replayGroup = {};
layer = new ol.layer.VectorTile({
source: new ol.source.VectorTile({
tileClass: TileClass,
tileGrid: ol.tilegrid.createXYZ()
})
});
renderer = new ol.renderer.canvas.VectorTileLayer(layer);
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
resolution, rotation, skippedFeaturesUids, callback) {
var feature = new ol.Feature();
callback(feature);
callback(feature);
};
});
it('calls callback once per feature with a layer as 2nd arg', function() {
var spy = sinon.spy();
var coordinate = [0, 0];
var frameState = {
layerStates: {},
skippedFeatureUids: {},
viewState: {
resolution: 1,
rotation: 0
}
};
frameState.layerStates[goog.getUid(layer)] = {};
renderer.renderedTiles_ = [new TileClass([0, 0, -1])];
renderer.forEachFeatureAtCoordinate(
coordinate, frameState, spy, undefined);
expect(spy.callCount).to.be(1);
expect(spy.getCall(0).args[1]).to.equal(layer);
});
});
});
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.VectorTile');
goog.require('ol.View');
goog.require('ol.format.MVT');
goog.require('ol.geom.Point');
goog.require('ol.layer.VectorTile');
goog.require('ol.proj');
goog.require('ol.renderer.canvas.VectorTileLayer');
goog.require('ol.source.VectorTile');
goog.require('ol.style.Style');
goog.require('ol.style.Text');

View File

@@ -0,0 +1,162 @@
goog.provide('ol.test.source.UrlTile');
describe('ol.source.UrlTile', function() {
describe('tileUrlFunction', function() {
var tileSource, tileGrid;
beforeEach(function() {
tileSource = new ol.source.UrlTile({
projection: 'EPSG:3857',
tileGrid: ol.tilegrid.createXYZ({maxZoom: 6}),
url: '{z}/{x}/{y}',
wrapX: true
});
tileGrid = tileSource.getTileGrid();
});
it('returns the expected URL', function() {
var coordinate = [829330.2064098881, 5933916.615134273];
var tileUrl;
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 0));
expect(tileUrl).to.eql('0/0/0');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 1));
expect(tileUrl).to.eql('1/1/0');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 2));
expect(tileUrl).to.eql('2/2/1');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 3));
expect(tileUrl).to.eql('3/4/2');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 4));
expect(tileUrl).to.eql('4/8/5');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 5));
expect(tileUrl).to.eql('5/16/11');
tileUrl = tileSource.tileUrlFunction(
tileGrid.getTileCoordForCoordAndZ(coordinate, 6));
expect(tileUrl).to.eql('6/33/22');
});
describe('wrap x', function() {
it('returns the expected URL', function() {
var projection = tileSource.getProjection();
var tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, -31, -23], projection));
expect(tileUrl).to.eql('6/33/22');
tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, 33, -23], projection));
expect(tileUrl).to.eql('6/33/22');
tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, 97, -23], projection));
expect(tileUrl).to.eql('6/33/22');
});
});
describe('crop y', function() {
it('returns the expected URL', function() {
var projection = tileSource.getProjection();
var tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, 33, 0], projection));
expect(tileUrl).to.be(undefined);
tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, 33, -23], projection));
expect(tileUrl).to.eql('6/33/22');
tileUrl = tileSource.tileUrlFunction(
tileSource.getTileCoordForTileUrlFunction(
[6, 33, -65], projection));
expect(tileUrl).to.be(undefined);
});
});
});
describe('#getUrls', function() {
var sourceOptions;
var source;
var url = 'http://geo.nls.uk/maps/towns/glasgow1857/{z}/{x}/{-y}.png';
beforeEach(function() {
sourceOptions = {
tileGrid: ol.tilegrid.createXYZ({
extent: ol.proj.get('EPSG:4326').getExtent()
})
};
});
describe('using a "url" option', function() {
beforeEach(function() {
sourceOptions.url = url;
source = new ol.source.UrlTile(sourceOptions);
});
it('returns the XYZ URL', function() {
var urls = source.getUrls();
expect(urls).to.be.eql([url]);
});
});
describe('using a "urls" option', function() {
beforeEach(function() {
sourceOptions.urls = ['some_xyz_url1', 'some_xyz_url2'];
source = new ol.source.UrlTile(sourceOptions);
});
it('returns the XYZ URLs', function() {
var urls = source.getUrls();
expect(urls).to.be.eql(['some_xyz_url1', 'some_xyz_url2']);
});
});
describe('using a "tileUrlFunction"', function() {
beforeEach(function() {
sourceOptions.tileUrlFunction = function() {
return 'some_xyz_url';
};
source = new ol.source.UrlTile(sourceOptions);
});
it('returns null', function() {
var urls = source.getUrls();
expect(urls).to.be(null);
});
});
});
});
goog.require('ol.TileCoord');
goog.require('ol.proj');
goog.require('ol.source.UrlTile');

View File

@@ -0,0 +1,43 @@
goog.provide('ol.test.source.VectorTile');
describe('ol.source.VectorTile', function() {
var format = new ol.format.MVT();
var source = new ol.source.VectorTile({
format: format,
tileGrid: ol.tilegrid.createXYZ(),
url: '{z}/{x}/{y}.pbf'
});
var tile;
describe('constructor', function() {
it('sets the format on the instance', function() {
expect(source.format_).to.equal(format);
});
it('uses ol.VectorTile as default tileClass', function() {
expect(source.tileClass).to.equal(ol.VectorTile);
});
});
describe('#getTile()', function() {
it('creates a tile with the correct tile class', function() {
tile = source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857'));
expect(tile).to.be.a(ol.VectorTile);
});
it('sets the correct tileCoord on the created tile', function() {
expect(tile.getTileCoord()).to.eql([0, 0, 0]);
});
it('fetches tile from cache when requested again', function() {
expect(source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857')))
.to.equal(tile);
});
});
});
goog.require('ol.VectorTile');
goog.require('ol.format.MVT');
goog.require('ol.proj');
goog.require('ol.source.VectorTile');