Merge pull request #4518 from ahocevar/osm-vector-tiles

Add support for OSM vector tiles
This commit is contained in:
Andreas Hocevar
2015-12-08 11:01:49 +01:00
10 changed files with 229 additions and 17 deletions

View File

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

View File

@@ -0,0 +1,9 @@
---
layout: example.html
title: OSM Vector Tiles
shortdesc: Using OpenStreetMap vector tiles.
docs: >
A simple vector tiles map with OpenStreetMap vector tiles. **Note**: The tiles used in this example are not optimized for rendering - they clip tiles exactly at the tile boundary instead of adding a buffer, and use geographic coordinates instead of tile relative pixel coordinates in view projection.
tags: "vector, tiles, osm"
---
<div id="map" class="map"></div>

View File

@@ -0,0 +1,142 @@
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.format.TopoJSON');
goog.require('ol.layer.VectorTile');
goog.require('ol.proj');
goog.require('ol.source.VectorTile');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
var format = new ol.format.TopoJSON();
var tileGrid = ol.tilegrid.createXYZ({ maxZoom: 19 });
var roadStyleCache = {};
var roadColor = {
'major_road': '#776',
'minor_road': '#ccb',
'highway': '#f39'
};
var landuseStyleCache = {};
var buildingStyle = [
new ol.style.Style({
fill: new ol.style.Fill({
color: '#666',
opacity: 0.4
}),
stroke: new ol.style.Stroke({
color: '#444',
width: 1
})
})
];
var map = new ol.Map({
layers: [
new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: format,
tileGrid: tileGrid,
url: 'http://{a-c}.tile.openstreetmap.us/' +
'vectiles-water-areas/{z}/{x}/{y}.topojson'
}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: '#9db9e8'
})
})
}),
new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: format,
tileGrid: tileGrid,
url: 'http://{a-c}.tile.openstreetmap.us/' +
'vectiles-highroad/{z}/{x}/{y}.topojson'
}),
style: function(feature, resolution) {
var kind = feature.get('kind');
var railway = feature.get('railway');
var sort_key = feature.get('sort_key');
var styleKey = kind + '/' + railway + '/' + sort_key;
var style = roadStyleCache[styleKey];
if (!style) {
var color, width;
if (railway) {
color = '#7de';
width = 1;
} else {
color = roadColor[kind];
width = kind == 'highway' ? 1.5 : 1;
}
style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: color,
width: width
}),
zIndex: sort_key
});
roadStyleCache[styleKey] = style;
}
return style;
}
}),
new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: format,
tileGrid: tileGrid,
url: 'http://{a-c}.tile.openstreetmap.us/' +
'vectiles-buildings/{z}/{x}/{y}.topojson'
}),
style: function(f, resolution) {
return (resolution < 10) ? buildingStyle : null;
}
}),
new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: format,
tileGrid: tileGrid,
url: 'http://{a-c}.tile.openstreetmap.us/' +
'vectiles-land-usages/{z}/{x}/{y}.topojson'
}),
visible: false,
style: function(feature, resolution) {
var kind = feature.get('kind');
var styleKey = kind;
var style = landuseStyleCache[styleKey];
if (!style) {
var color, width;
color = {
'parking': '#ddd',
'industrial': '#aaa',
'urban area': '#aaa',
'park': '#76C759',
'school': '#DA10E7',
'garden': '#76C759',
'pitch': '#D58F8D',
'scrub': '#3E7D28',
'residential': '#4C9ED9'
}[kind];
width = kind == 'highway' ? 1.5 : 1;
style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: color,
width: width
}),
fill: new ol.style.Fill({
color: color,
opacity: 0.5
})
});
landuseStyleCache[styleKey] = style;
}
return style;
}
})
],
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([-74.0064, 40.7142]),
maxZoom: 19,
zoom: 15
})
});

View File

@@ -9,6 +9,9 @@ goog.require('goog.net.XhrIo');
goog.require('goog.net.XhrIo.ResponseType');
goog.require('ol.TileState');
goog.require('ol.format.FormatType');
goog.require('ol.proj');
goog.require('ol.proj.Projection');
goog.require('ol.proj.Units');
goog.require('ol.xml');
@@ -100,12 +103,21 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) {
goog.asserts.fail('unexpected format type');
}
if (source) {
var features = format.readFeatures(source,
{featureProjection: projection});
if (ol.ENABLE_VECTOR_TILE && success.length == 2) {
success.call(this, features, format.readProjection(source));
var dataProjection = format.readProjection(source);
var units = dataProjection.getUnits();
if (units === ol.proj.Units.TILE_PIXELS) {
projection = new ol.proj.Projection({
code: projection.getCode(),
units: units
});
this.setProjection(projection);
}
success.call(this, format.readFeatures(source,
{featureProjection: projection}), dataProjection);
} else {
success.call(this, features);
success.call(this, format.readFeatures(source,
{featureProjection: projection}));
}
} else {
goog.asserts.fail('undefined or null source');
@@ -142,7 +154,9 @@ ol.featureloader.tile = function(url, format) {
* @this {ol.VectorTile}
*/
function(features, projection) {
this.setProjection(projection);
if (ol.proj.equivalent(projection, this.getProjection())) {
this.setProjection(projection);
}
this.setFeatures(features);
},
/**

View File

@@ -677,12 +677,14 @@ ol.proj.get = function(projectionLike) {
ol.proj.equivalent = function(projection1, projection2) {
if (projection1 === projection2) {
return true;
} else if (projection1.getCode() === projection2.getCode()) {
return projection1.getUnits() === projection2.getUnits();
}
var equalUnits = projection1.getUnits() === projection2.getUnits();
if (projection1.getCode() === projection2.getCode()) {
return equalUnits;
} else {
var transformFn = ol.proj.getTransformFromProjections(
projection1, projection2);
return transformFn === ol.proj.cloneTransform;
return transformFn === ol.proj.cloneTransform && equalUnits;
}
};

View File

@@ -56,7 +56,7 @@ ol.source.VectorTile = function(options) {
/**
* @protected
* @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string,
* ol.format.Feature, ol.TileLoadFunctionType)}
* ol.format.Feature, ol.TileLoadFunctionType, ol.proj.Projection)}
*/
this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;
@@ -83,8 +83,7 @@ ol.source.VectorTile.prototype.getTile =
tileCoord,
tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
tileUrl !== undefined ? tileUrl : '',
this.format_,
this.tileLoadFunction);
this.format_, this.tileLoadFunction, projection);
goog.events.listen(tile, goog.events.EventType.CHANGE,
this.handleTileChange, false, this);

View File

@@ -4,6 +4,7 @@ goog.require('ol.Tile');
goog.require('ol.TileCoord');
goog.require('ol.TileLoadFunctionType');
goog.require('ol.TileState');
goog.require('ol.proj.Projection');
/**
@@ -25,8 +26,10 @@ ol.TileReplayState;
* @param {string} src Data source url.
* @param {ol.format.Feature} format Feature format.
* @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
* @param {ol.proj.Projection} projection Feature projection.
*/
ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
ol.VectorTile =
function(tileCoord, state, src, format, tileLoadFunction, projection) {
goog.base(this, tileCoord, state);
@@ -52,7 +55,7 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {
* @private
* @type {ol.proj.Projection}
*/
this.projection_ = null;
this.projection_ = projection;
/**
* @private
@@ -124,7 +127,7 @@ ol.VectorTile.prototype.getKey = function() {
/**
* @return {ol.proj.Projection} Projection.
* @return {ol.proj.Projection} Feature projection.
*/
ol.VectorTile.prototype.getProjection = function() {
return this.projection_;
@@ -138,7 +141,7 @@ ol.VectorTile.prototype.load = function() {
if (this.state == ol.TileState.IDLE) {
this.setState(ol.TileState.LOADING);
this.tileLoadFunction_(this, this.url_);
this.loader_(null, NaN, null);
this.loader_(null, NaN, this.projection_);
}
};
@@ -153,7 +156,7 @@ ol.VectorTile.prototype.setFeatures = function(features) {
/**
* @param {ol.proj.Projection} projection Projection.
* @param {ol.proj.Projection} projection Feature projection.
*/
ol.VectorTile.prototype.setProjection = function(projection) {
this.projection_ = projection;

View File

@@ -1,6 +1,7 @@
goog.provide('ol.test.featureloader');
describe('ol.featureloader', function() {
describe('ol.featureloader.xhr', function() {
var loader;
var source;
@@ -53,9 +54,48 @@ describe('ol.featureloader', function() {
});
});
describe('ol.featureloader.tile', function() {
var loader;
var tile;
beforeEach(function() {
tile = new ol.VectorTile([0, 0, 0], undefined, undefined, undefined,
undefined, ol.proj.get('EPSG:3857'));
});
it('sets features on the tile', function(done) {
var url = 'spec/ol/data/point.json';
var format = new ol.format.GeoJSON();
loader = ol.featureloader.tile(url, format);
goog.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('sets features on the tile and updates proj units', function(done) {
var url = 'spec/ol/data/14-8938-5680.vector.pbf';
var format = new ol.format.MVT();
loader = ol.featureloader.tile(url, format);
goog.events.listen(tile, 'change', function(e) {
expect(tile.getFeatures().length).to.be.greaterThan(0);
expect(tile.getProjection().getUnits()).to.be('tile-pixels');
done();
});
loader.call(tile, [], 1, ol.proj.get('EPSG:3857'));
});
});
});
goog.require('goog.events');
goog.require('ol.VectorTile');
goog.require('ol.featureloader');
goog.require('ol.format.GeoJSON');
goog.require('ol.format.MVT');
goog.require('ol.proj');
goog.require('ol.source.Vector');
goog.require('ol.source.VectorEventType');

View File

@@ -69,6 +69,7 @@ describe('ol.proj', function() {
});
expect(ol.proj.equivalent(proj1, proj2)).to.not.be.ok();
});
});
describe('identify transform', function() {

View File

@@ -41,7 +41,6 @@ describe('ol.renderer.canvas.VectorTileLayer', 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({