diff --git a/examples/config.json b/examples/config.json
index 318b70e726..6b7648a23a 100644
--- a/examples/config.json
+++ b/examples/config.json
@@ -31,6 +31,7 @@
"Vector tiles": {
"vector_tile_raster_3d": "Raster on 3D map",
"vector_tile_raster_2d": "Raster on 2D map",
+ "vector_tile_mapbox_raster": "Mapbox Vector Tiles to raster",
"vector_tile_3d_mesh": "Vector tile to 3d objects",
"vector_tile_3d_mesh_mapbox": "Mapbox Vector tile to 3d objects",
"vector_tile_dragndrop": "Drag and drop a style"
diff --git a/examples/source_file_kml_raster_usgs.html b/examples/source_file_kml_raster_usgs.html
index db83222b2b..b755f0890e 100644
--- a/examples/source_file_kml_raster_usgs.html
+++ b/examples/source_file_kml_raster_usgs.html
@@ -69,7 +69,6 @@
}
var kmlStyle = {
- zoom: { min: 10, max: 20 },
text: {
field: '{name}',
haloColor: 'black',
diff --git a/examples/vector_tile_mapbox_raster.html b/examples/vector_tile_mapbox_raster.html
new file mode 100644
index 0000000000..01b704bd0f
--- /dev/null
+++ b/examples/vector_tile_mapbox_raster.html
@@ -0,0 +1,91 @@
+
+
+ Itowns - vector-tiles 2d
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Converter/Feature2Texture.js b/src/Converter/Feature2Texture.js
index b197e7fe2b..82c13a50eb 100644
--- a/src/Converter/Feature2Texture.js
+++ b/src/Converter/Feature2Texture.js
@@ -70,6 +70,9 @@ function drawFeature(ctx, feature, extent, invCtxScale) {
for (const geometry of feature.geometries) {
if (Extent.intersectsExtent(geometry.extent, extent)) {
context.setGeometry(geometry);
+ if (style.zoom.min > style.context.zoom || style.zoom.max <= style.context.zoom) {
+ return;
+ }
if (
feature.type === FEATURE_TYPES.POINT && style.point
diff --git a/src/Core/Style.js b/src/Core/Style.js
index 3e716bd26a..7374c7b35c 100644
--- a/src/Core/Style.js
+++ b/src/Core/Style.js
@@ -87,14 +87,18 @@ async function loadImage(source) {
return (await promise).image;
}
-function cropImage(img, cropValues = { width: img.naturalWidth, height: img.naturalHeight }) {
- canvas.width = cropValues.width;
- canvas.height = cropValues.height;
+function cropImage(img, cropValues) {
+ const x = cropValues.x || 0;
+ const y = cropValues.y || 0;
+ const width = cropValues.width || img.naturalWidth;
+ const height = cropValues.height || img.naturalHeight;
+ canvas.width = width;
+ canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(img,
- cropValues.x || 0, cropValues.y || 0, cropValues.width, cropValues.height,
- 0, 0, cropValues.width, cropValues.height);
- return ctx.getImageData(0, 0, cropValues.width, cropValues.height);
+ x, y, width, height,
+ 0, 0, width, height);
+ return ctx.getImageData(0, 0, width, height);
}
function replaceWhitePxl(imgd, color, id) {
@@ -158,7 +162,7 @@ function defineStyleProperty(style, category, parameter, userValue, defaultValue
const dataValue = style.context.featureStyle?.[category]?.[parameter];
if (dataValue != undefined) { return readExpression(dataValue, style.context); }
if (defaultValue instanceof Function) {
- return defaultValue(style.context.properties, style.context);
+ return defaultValue(style.context.properties, style.context) ?? defaultValue;
}
return defaultValue;
},
@@ -298,15 +302,12 @@ function _addIcon(icon, domElement, opt) {
}
/**
- * An object that can contain any properties (order, zoom, fill, stroke, point,
+ * An object that can contain any properties (zoom, fill, stroke, point,
* text or/and icon) and sub properties of a Style.
* Used for the instanciation of a {@link Style}.
*
* @typedef {Object} StyleOptions
*
- * @property {Number} [order] - Order of the features that will be associated to
- * the style. It can helps sorting and prioritizing features if needed.
- *
* @property {Object} [zoom] - Level on which to display the feature
* @property {Number} [zoom.max] - max level
* @property {Number} [zoom.min] - min level
@@ -464,8 +465,6 @@ function _addIcon(icon, domElement, opt) {
* The first parameter of functions used to set `Style` properties is always an object containing
* the properties of the features displayed with the current `Style` instance.
*
- * @property {Number} order - Order of the features that will be associated to
- * the style. It can helps sorting and prioritizing features if needed.
* @property {Object} fill - Polygons and fillings style.
* @property {String|Function|THREE.Color} fill.color - Defines the main color of the filling. Can be
* any [valid color
@@ -615,15 +614,13 @@ function _addIcon(icon, domElement, opt) {
class Style {
/**
* @param {StyleOptions} [params={}] An object that contain any properties
- * (order, zoom, fill, stroke, point, text or/and icon)
+ * (zoom, fill, stroke, point, text or/and icon)
* and sub properties of a Style ({@link StyleOptions}).
*/
constructor(params = {}) {
this.isStyle = true;
this.context = new StyleContext();
- this.order = params.order || 0;
-
params.zoom = params.zoom || {};
params.fill = params.fill || {};
params.stroke = params.stroke || {};
@@ -763,12 +760,12 @@ class Style {
* set Style from vector tile layer properties.
* @param {Object} layer vector tile layer.
* @param {Object} sprites vector tile layer.
- * @param {Number} [order=0]
* @param {Boolean} [symbolToCircle=false]
+ * @param {Set} warn Set storing all warnings encountered by the source.
*
* @returns {StyleOptions} containing all properties for itowns.Style
*/
- static setFromVectorTileLayer(layer, sprites, order = 0, symbolToCircle = false) {
+ static setFromVectorTileLayer(layer, sprites, symbolToCircle = false, warn) {
const style = {
fill: {},
stroke: {},
@@ -780,8 +777,6 @@ class Style {
layer.layout = layer.layout || {};
layer.paint = layer.paint || {};
- style.order = order;
-
if (layer.type === 'fill') {
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['fill-color'] || layer.paint['fill-pattern'], { type: 'color' }));
style.fill.color = color;
@@ -804,7 +799,8 @@ class Style {
style.stroke.color = color;
style.stroke.opacity = opacity;
style.stroke.width = 1.0;
- style.stroke.dasharray = [];
+ } else {
+ style.stroke.width = 0.0;
}
} else if (layer.type === 'line') {
const prepare = readVectorProperty(layer.paint['line-color'], { type: 'color' });
@@ -820,6 +816,8 @@ class Style {
style.point.opacity = opacity;
style.point.radius = readVectorProperty(layer.paint['circle-radius']);
} else if (layer.type === 'symbol') {
+ // if symbol we shouldn't draw stroke but defaut value is 1.
+ style.stroke.width = 0.0;
// overlapping order
style.text.zOrder = readVectorProperty(layer.layout['symbol-z-order']);
if (style.text.zOrder == 'auto') {
@@ -840,7 +838,7 @@ class Style {
// content
style.text.field = readVectorProperty(layer.layout['text-field']);
- style.text.wrap = readVectorProperty(layer.layout['text-max-width']);
+ style.text.wrap = readVectorProperty(layer.layout['text-max-width']);// Units ems
style.text.spacing = readVectorProperty(layer.layout['text-letter-spacing']);
style.text.transform = readVectorProperty(layer.layout['text-transform']);
style.text.justify = readVectorProperty(layer.layout['text-justify']);
@@ -861,6 +859,12 @@ class Style {
// additional icon
const iconImg = readVectorProperty(layer.layout['icon-image']);
if (iconImg) {
+ const cropValueDefault = {
+ x: 0,
+ y: 0,
+ width: 1,
+ height: 1,
+ };
try {
style.icon.id = iconImg;
if (iconImg.stops) {
@@ -871,9 +875,23 @@ class Style {
if (stop[1].includes('{')) {
cropValues = function _(p) {
const id = stop[1].replace(/\{(.+?)\}/g, (a, b) => (p[b] || '')).trim();
- cropValues = sprites[id];
+ if (cropValues === undefined) {
+ const warning = `WARNING: "${id}" not found in sprite file`;
+ if (!warn.has(warning)) {
+ warn.add(warning);
+ console.warn(warning);
+ }
+ sprites[id] = cropValueDefault;// or return cropValueDefault;
+ }
return sprites[id];
};
+ } else if (cropValues === undefined) {
+ const warning = `WARNING: "${stop[1]}" not found in sprite file`;
+ if (!warn.has(warning)) {
+ warn.add(warning);
+ console.warn(warning);
+ }
+ cropValues = cropValueDefault;
}
return [stop[0], cropValues];
}),
@@ -881,19 +899,36 @@ class Style {
style.icon.cropValues = iconCropValue;
} else {
style.icon.cropValues = sprites[iconImg];
- if (iconImg[0].includes('{')) {
+ if (iconImg.includes('{')) {
style.icon.cropValues = function _(p) {
const id = iconImg.replace(/\{(.+?)\}/g, (a, b) => (p[b] || '')).trim();
- style.icon.cropValues = sprites[id];
+ if (sprites[id] === undefined) {
+ const warning = `WARNING: "${id}" not found in sprite file`;
+ if (!warn.has(warning)) {
+ warn.add(warning);
+ console.warn(warning);
+ }
+ sprites[id] = cropValueDefault;// or return cropValueDefault;
+ }
return sprites[id];
};
+ } else if (sprites[iconImg] === undefined) {
+ const warning = `WARNING: "${iconImg}" not found in sprite file`;
+ if (!warn.has(warning)) {
+ warn.add(warning);
+ console.warn(warning);
+ }
+ style.icon.cropValues = cropValueDefault;
}
}
style.icon.source = sprites.source;
- style.icon.size = readVectorProperty(layer.layout['icon-size']) || 1;
+ style.icon.size = readVectorProperty(layer.layout['icon-size']) ?? 1;
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['icon-color'], { type: 'color' }));
- style.icon.color = color;
- style.icon.opacity = readVectorProperty(layer.paint['icon-opacity']) || (opacity !== undefined && opacity);
+ // https://docs.mapbox.com/style-spec/reference/layers/#paint-symbol-icon-color
+ if (iconImg.sdf) {
+ style.icon.color = color;
+ }
+ style.icon.opacity = readVectorProperty(layer.paint['icon-opacity']) ?? (opacity !== undefined && opacity);
} catch (err) {
err.message = `VTlayer '${layer.id}': argument sprites must not be null when using layer.layout['icon-image']`;
throw err;
@@ -919,17 +954,16 @@ class Style {
* @param {Boolean} canBeFilled - true if feature.type == FEATURE_TYPES.POLYGON.
*/
applyToCanvasPolygon(txtrCtx, polygon, invCtxScale, canBeFilled) {
- const context = this.context;
// draw line or edge of polygon
- if (this.stroke) {
+ if (this.stroke.width > 0) {
// TO DO add possibility of using a pattern (https://github.com/iTowns/itowns/issues/2210)
- this._applyStrokeToPolygon(txtrCtx, invCtxScale, polygon, context);
+ this._applyStrokeToPolygon(txtrCtx, invCtxScale, polygon);
}
// fill inside of polygon
- if (canBeFilled && this.fill) {
+ if (canBeFilled && (this.fill.pattern || this.fill.color)) {
// canBeFilled can be move to StyleContext in the later PR
- this._applyFillToPolygon(txtrCtx, invCtxScale, polygon, context);
+ this._applyFillToPolygon(txtrCtx, invCtxScale, polygon);
}
}
@@ -937,11 +971,11 @@ class Style {
if (txtrCtx.strokeStyle !== this.stroke.color) {
txtrCtx.strokeStyle = this.stroke.color;
}
- const width = (this.stroke.width || 2.0) * invCtxScale;
+ const width = this.stroke.width * invCtxScale;
if (txtrCtx.lineWidth !== width) {
txtrCtx.lineWidth = width;
}
- const alpha = this.stroke.opacity == undefined ? 1.0 : this.stroke.opacity;
+ const alpha = this.stroke.opacity;
if (alpha !== txtrCtx.globalAlpha && typeof alpha == 'number') {
txtrCtx.globalAlpha = alpha;
}
@@ -957,7 +991,7 @@ class Style {
// need doc for the txtrCtx.fillStyle.src that seems to always be undefined
if (this.fill.pattern) {
let img = this.fill.pattern;
- const cropValues = this.fill.pattern.cropValues;
+ const cropValues = { ...this.fill.pattern.cropValues };
if (this.fill.pattern.source) {
img = await loadImage(this.fill.pattern.source);
}
@@ -1032,7 +1066,7 @@ class Style {
if (!this.icon.cropValues && !this.icon.color) {
icon.src = this.icon.source;
} else {
- const cropValues = this.icon.cropValues;
+ const cropValues = { ...this.icon.cropValues };
const color = this.icon.color;
const id = this.icon.id || this.icon.source;
const img = await loadImage(this.icon.source);
diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js
index 1dcd649b00..1b2607aa6c 100644
--- a/src/Layer/LabelLayer.js
+++ b/src/Layer/LabelLayer.js
@@ -254,9 +254,18 @@ class LabelLayer extends GeometryLayer {
context.setZoom(extentOrTile.zoom);
data.features.forEach((f) => {
- // TODO: add support for LINE and POLYGON
+ if (f.style.text) {
+ if (Object.keys(f.style.text).length === 0) {
+ return;
+ }
+ }
+
if (f.type !== FEATURE_TYPES.POINT) {
- return;
+ const warn = `Type label ${Object.keys(FEATURE_TYPES).filter(ft => FEATURE_TYPES[ft] === f.type)} not well supported`;
+ if (!this.source.warn.has(warn)) {
+ this.source.warn.add(warn);
+ console.warn(warn);
+ }
}
context.setFeature(f);
@@ -270,19 +279,11 @@ class LabelLayer extends GeometryLayer {
labels.needsAltitude = labels.needsAltitude || this.forceClampToTerrain === true || (isDefaultElevationStyle && !f.hasRawElevationData);
f.geometries.forEach((g) => {
- // NOTE: this only works because only POINT is supported, it
- // needs more work for LINE and POLYGON
- coord.setFromArray(f.vertices, g.size * g.indices[0].offset);
- // Transform coordinate to data.crs projection
- coord.applyMatrix4(data.matrixWorld);
-
- if (!_extent.isPointInside(coord)) { return; }
- const geometryField = g.properties.style && g.properties.style.text && g.properties.style.text.field;
-
context.setGeometry(g);
- let content;
this.style.setContext(context);
const layerField = this.style.text && this.style.text.field;
+ const geometryField = g.properties.style && g.properties.style.text && g.properties.style.text.field;
+ let content;
if (this.labelDomelement) {
content = readExpression(this.labelDomelement, context);
} else if (!geometryField && !featureField && !layerField) {
@@ -294,12 +295,28 @@ class LabelLayer extends GeometryLayer {
}
}
- const label = new Label(content, coord.clone(), this.style);
+ if (this.style.zoom.min > this.style.context.zoom || this.style.zoom.max <= this.style.context.zoom) {
+ return;
+ }
+
+ // NOTE: this only works fine for POINT.
+ // It needs more work for LINE and POLYGON as we currently only use the first point of the entity
- label.layerId = this.id;
- label.padding = this.margin || label.padding;
+ g.indices.forEach((i) => {
+ coord.setFromArray(f.vertices, g.size * i.offset);
+ // Transform coordinate to data.crs projection
+ coord.applyMatrix4(data.matrixWorld);
- labels.push(label);
+ if (!_extent.isPointInside(coord)) { return; }
+
+ const label = new Label(content, coord.clone(), this.style);
+
+ label.layerId = this.id;
+ label.order = f.order;
+ label.padding = this.margin || label.padding;
+
+ labels.push(label);
+ });
});
});
diff --git a/src/Parser/VectorTileParser.js b/src/Parser/VectorTileParser.js
index bff52dc251..ca6a9063f3 100644
--- a/src/Parser/VectorTileParser.js
+++ b/src/Parser/VectorTileParser.js
@@ -116,10 +116,11 @@ function vtFeatureToFeatureGeometry(vtFeature, feature, classify = false) {
function readPBF(file, options) {
options.out = options.out || {};
const vectorTile = new VectorTile(new Protobuf(file));
- const sourceLayers = Object.keys(vectorTile.layers);
+ const vtLayerNames = Object.keys(vectorTile.layers);
- if (sourceLayers.length < 1) {
- return;
+ const collection = new FeatureCollection(options.out);
+ if (vtLayerNames.length < 1) {
+ return Promise.resolve(collection);
}
// x,y,z tile coordinates
@@ -130,41 +131,33 @@ function readPBF(file, options) {
// Only if the layer.origin is top
const y = options.in.isInverted ? options.extent.row : (1 << z) - options.extent.row - 1;
- const collection = new FeatureCollection(options.out);
-
- const vFeature = vectorTile.layers[sourceLayers[0]];
- // TODO: verify if size is correct because is computed with only one feature (vFeature).
- const size = vFeature.extent * 2 ** z;
+ const vFeature0 = vectorTile.layers[vtLayerNames[0]];
+ // TODO: verify if size is correct because is computed with only one feature (vFeature0).
+ const size = vFeature0.extent * 2 ** z;
const center = -0.5 * size;
collection.scale.set(globalExtent.x / size, -globalExtent.y / size, 1);
- collection.position.set(vFeature.extent * x + center, vFeature.extent * y + center, 0).multiply(collection.scale);
+ collection.position.set(vFeature0.extent * x + center, vFeature0.extent * y + center, 0).multiply(collection.scale);
collection.updateMatrixWorld();
- sourceLayers.forEach((layer_id) => {
- if (!options.in.layers[layer_id]) { return; }
+ vtLayerNames.forEach((vtLayerName) => {
+ if (!options.in.layers[vtLayerName]) { return Promise.resolve(collection); }
- const sourceLayer = vectorTile.layers[layer_id];
+ const vectorTileLayer = vectorTile.layers[vtLayerName];
- for (let i = sourceLayer.length - 1; i >= 0; i--) {
- const vtFeature = sourceLayer.feature(i);
+ for (let i = vectorTileLayer.length - 1; i >= 0; i--) {
+ const vtFeature = vectorTileLayer.feature(i);
vtFeature.tileNumbers = { x, y: options.extent.row, z };
- const layers = options.in.layers[layer_id].filter(l => l.filterExpression.filter({ zoom: z }, vtFeature) && z >= l.zoom.min && z < l.zoom.max);
- let feature;
+ // Find layers where this vtFeature is used
+ const layers = options.in.layers[vtLayerName]
+ .filter(l => l.filterExpression.filter({ zoom: z }, vtFeature));
for (const layer of layers) {
- if (!feature) {
- feature = collection.requestFeatureById(layer.id, vtFeature.type - 1);
- feature.id = layer.id;
- feature.order = layer.order;
- feature.style = options.in.styles[feature.id];
- vtFeatureToFeatureGeometry(vtFeature, feature);
- } else if (!collection.features.find(f => f.id === layer.id)) {
- feature = collection.newFeatureByReference(feature);
- feature.id = layer.id;
- feature.order = layer.order;
- feature.style = options.in.styles[feature.id];
- }
+ const feature = collection.requestFeatureById(layer.id, vtFeature.type - 1);
+ feature.id = layer.id;
+ feature.order = layer.order;
+ feature.style = options.in.styles[feature.id];
+ vtFeatureToFeatureGeometry(vtFeature, feature);
}
}
});
diff --git a/src/Renderer/Label2DRenderer.js b/src/Renderer/Label2DRenderer.js
index f9ac17ab60..6e927844df 100644
--- a/src/Renderer/Label2DRenderer.js
+++ b/src/Renderer/Label2DRenderer.js
@@ -186,13 +186,15 @@ class Label2DRenderer {
if (!frustum.containsPoint(worldPosition.applyMatrix4(camera.matrixWorldInverse)) ||
// Check if globe horizon culls the label
// Do some horizon culling (if possible) if the tiles level is small enough.
- label.horizonCullingPoint && GlobeLayer.horizonCulling(label.horizonCullingPoint) ||
- // Check if content isn't present in visible labels
- this.grid.visible.some((l) => {
- // TODO for icon without text filter by position
- const textContent = label.content.textContent;
- return textContent !== '' && l.content.textContent.toLowerCase() == textContent.toLowerCase();
- })) {
+ label.horizonCullingPoint && GlobeLayer.horizonCulling(label.horizonCullingPoint)
+ // Why do we might need this part ?
+ // || // Check if content isn't present in visible labels
+ // this.grid.visible.some((l) => {
+ // // TODO for icon without text filter by position
+ // const textContent = label.content.textContent;
+ // return textContent !== '' && l.content.textContent.toLowerCase() == textContent.toLowerCase();
+ // })
+ ) {
label.visible = false;
} else {
// projecting world position label
diff --git a/src/Source/Source.js b/src/Source/Source.js
index 11c54b5f20..c3590fa15d 100644
--- a/src/Source/Source.js
+++ b/src/Source/Source.js
@@ -107,6 +107,7 @@ class Source extends InformationsData {
constructor(source) {
super(source);
this.isSource = true;
+ this.warn = new Set();
if (!source.url) {
throw new Error('New Source: url is required');
diff --git a/src/Source/VectorTilesSource.js b/src/Source/VectorTilesSource.js
index ed5b9562ed..31e8081f5f 100644
--- a/src/Source/VectorTilesSource.js
+++ b/src/Source/VectorTilesSource.js
@@ -20,6 +20,16 @@ function mergeCollections(collections) {
return collection;
}
+// A deprecated (but still in use) Mapbox spec allows using 'ref' as a propertie to reference an other layer
+// instead of duplicating the following properties: 'type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout'
+function getPropertiesFromRefLayer(layers, layer) {
+ const refProperties = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout'];
+ const refLayer = layers.filter(l => l.id === layer.ref)[0];
+ refProperties.forEach((prop) => {
+ layer[prop] = refLayer[prop];
+ });
+}
+
/**
* VectorTilesSource are object containing informations on how to fetch vector
* tiles resources.
@@ -71,10 +81,11 @@ class VectorTilesSource extends TMSSource {
this.accessToken = source.accessToken;
+ let mvtStyleUrl;
if (source.style) {
if (typeof source.style == 'string') {
- const styleUrl = urlParser.normalizeStyleURL(source.style, this.accessToken);
- promise = Fetcher.json(styleUrl, this.networkOptions);
+ mvtStyleUrl = urlParser.normalizeStyleURL(source.style, this.accessToken);
+ promise = Fetcher.json(mvtStyleUrl, this.networkOptions);
} else {
promise = Promise.resolve(source.style);
}
@@ -82,27 +93,31 @@ class VectorTilesSource extends TMSSource {
throw new Error('New VectorTilesSource: style is required');
}
- this.whenReady = promise.then((style) => {
- this.jsonStyle = style;
- const baseurl = source.sprite || style.sprite;
+ this.whenReady = promise.then((mvtStyle) => {
+ this.jsonStyle = mvtStyle;
+ let baseurl = source.sprite || mvtStyle.sprite;
if (baseurl) {
+ baseurl = new URL(baseurl, mvtStyleUrl).toString();
const spriteUrl = urlParser.normalizeSpriteURL(baseurl, '', '.json', this.accessToken);
return Fetcher.json(spriteUrl, this.networkOptions).then((sprites) => {
this.sprites = sprites;
const imgUrl = urlParser.normalizeSpriteURL(baseurl, '', '.png', this.accessToken);
this.sprites.source = imgUrl;
- return style;
+ return mvtStyle;
});
}
- return style;
- }).then((style) => {
- style.layers.forEach((layer, order) => {
+ return mvtStyle;
+ }).then((mvtStyle) => {
+ mvtStyle.layers.forEach((layer, order) => {
layer.sourceUid = this.uid;
if (layer.type === 'background') {
this.backgroundLayer = layer;
} else if (ffilter(layer)) {
- const style = Style.setFromVectorTileLayer(layer, this.sprites, order, this.symbolToCircle);
+ if (layer['source-layer'] === undefined) {
+ getPropertiesFromRefLayer(mvtStyle.layers, layer);
+ }
+ const style = Style.setFromVectorTileLayer(layer, this.sprites, this.symbolToCircle, this.warn);
this.styles[layer.id] = style;
if (!this.layers[layer['source-layer']]) {
@@ -112,20 +127,18 @@ class VectorTilesSource extends TMSSource {
id: layer.id,
order,
filterExpression: featureFilter(layer.filter),
- zoom: {
- min: layer.minzoom || 0,
- max: layer.maxzoom || 24,
- },
});
}
});
if (this.url == '.') {
- const TMSUrlList = Object.values(style.sources).map((sourceVT) => {
+ const TMSUrlList = Object.values(mvtStyle.sources).map((sourceVT) => {
if (sourceVT.url) {
+ sourceVT.url = new URL(sourceVT.url, mvtStyleUrl).toString();
const urlSource = urlParser.normalizeSourceURL(sourceVT.url, this.accessToken);
return Fetcher.json(urlSource, this.networkOptions).then((tileJSON) => {
if (tileJSON.tiles[0]) {
+ tileJSON.tiles[0] = decodeURIComponent(new URL(tileJSON.tiles[0], urlSource).toString());
return toTMSUrl(tileJSON.tiles[0]);
}
});
@@ -136,7 +149,7 @@ class VectorTilesSource extends TMSSource {
});
return Promise.all(TMSUrlList);
}
- return (Promise.resolve([this.url]));
+ return (Promise.resolve([toTMSUrl(this.url)]));
}).then((TMSUrlList) => {
this.urls = Array.from(new Set(TMSUrlList));
});
diff --git a/test/data/vectortiles/style.json b/test/data/vectortiles/style.json
index 1af58000a3..ffc6a8c707 100644
--- a/test/data/vectortiles/style.json
+++ b/test/data/vectortiles/style.json
@@ -19,7 +19,8 @@
"maxzoom": 13,
"paint": {
"fill-color": "rgb(255, 0, 0)"
- }
+ },
+ "source-layer": "source_layer"
}
]
}
\ No newline at end of file
diff --git a/test/unit/vectortiles.js b/test/unit/vectortiles.js
index fa93cfdca4..3b907531ad 100644
--- a/test/unit/vectortiles.js
+++ b/test/unit/vectortiles.js
@@ -13,7 +13,7 @@ import sprite from '../data/vectortiles/sprite.json';
import mapboxStyle from '../data/mapboxMulti.json';
const resources = {
- 'test/data/vectortiles/style.json': style,
+ 'https://test/data/vectortiles/style.json': style,
'https://test/tilejson.json': tilejson,
'https://test/sprite.json': sprite,
'https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v7.json': mapboxStyle,
@@ -67,15 +67,17 @@ describe('Vector tiles', function () {
}).catch(done);
});
- it('returns nothing', (done) => {
+ it('returns an empty collection', (done) => {
parse(null).then((collection) => {
- assert.equal(collection, undefined);
+ assert.ok(collection.isFeatureCollection);
+ assert.equal(collection.features.length, 0);
done();
}).catch(done);
});
it('filters all features out', (done) => {
parse(multipolygon, {}).then((collection) => {
+ assert.ok(collection.isFeatureCollection);
assert.equal(collection.features.length, 0);
done();
}).catch(done);
@@ -174,6 +176,7 @@ describe('VectorTilesSource', function () {
paint: {
'fill-color': 'rgb(255, 0, 0)',
},
+ 'source-layer': 'source_layer',
}],
},
});
@@ -187,7 +190,7 @@ describe('VectorTilesSource', function () {
it('loads the style from a file', function _it(done) {
const source = new VectorTilesSource({
- style: 'test/data/vectortiles/style.json',
+ style: 'https://test/data/vectortiles/style.json',
});
source.whenReady
.then(() => {
@@ -211,6 +214,7 @@ describe('VectorTilesSource', function () {
paint: {
'fill-color': 'rgb(255, 0, 0)',
},
+ 'source-layer': 'source_layer',
}, {
// minzoom is 5 (specified)
id: 'second',
@@ -219,6 +223,7 @@ describe('VectorTilesSource', function () {
'fill-color': 'rgb(255, 0, 0)',
},
minzoom: 5,
+ 'source-layer': 'source_layer',
}, {
// minzoom is 4 (first stop)
// If a style have `stops` expression, should it be used to determine the min zoom?
@@ -228,6 +233,7 @@ describe('VectorTilesSource', function () {
'fill-color': 'rgb(255, 0, 0)',
'fill-opacity': { stops: [[4, 1], [7, 0.5]] },
},
+ 'source-layer': 'source_layer',
}, {
// minzoom is 1 (first stop and no specified minzoom)
id: 'fourth',
@@ -236,6 +242,7 @@ describe('VectorTilesSource', function () {
'fill-color': 'rgb(255, 0, 0)',
'fill-opacity': { stops: [[1, 1], [7, 0.5]] },
},
+ 'source-layer': 'source_layer',
}, {
// minzoom is 4 (first stop is higher than specified)
id: 'fifth',
@@ -245,6 +252,7 @@ describe('VectorTilesSource', function () {
'fill-opacity': { stops: [[4, 1], [7, 0.5]] },
},
minzoom: 3,
+ 'source-layer': 'source_layer',
}],
},
});