diff --git a/examples/css/example.css b/examples/css/example.css
index 4bdc1c8553..384c6af523 100644
--- a/examples/css/example.css
+++ b/examples/css/example.css
@@ -253,7 +253,7 @@ h3 {
.tooltip {
display: none;
- background-image: linear-gradient(rgba(80, 80, 80,0.95), rgba(60, 60, 60,0.95));
+ background-image: linear-gradient(rgba(167, 164, 164, 0.95), rgba(60, 60, 60,0.95));
box-shadow: -1px 2px 5px 1px rgba(0, 0, 0, 0.5);
margin-top: 20px;
margin-left: 20px;
diff --git a/examples/js/plugins/FeatureToolTip.js b/examples/js/plugins/FeatureToolTip.js
index 0dc0fcad2b..f19446ebca 100644
--- a/examples/js/plugins/FeatureToolTip.js
+++ b/examples/js/plugins/FeatureToolTip.js
@@ -64,42 +64,38 @@ const FeatureToolTip = (function _() {
}
}
- function getGeometryProperties(geometry) {
- return function properties() { return geometry.properties; };
- }
-
function fillToolTip(features, layer, options) {
let content = '';
let feature;
let geometry;
- let style;
+ const style = layer.style;
let fill;
let stroke;
let symb = '';
let prop;
+ const context = style.context;
+
for (let p = 0; p < features.length; p++) {
feature = features[p];
geometry = feature.geometry;
- style = (geometry.properties && geometry.properties.style) || feature.style || layer.style;
- const context = { globals: {}, properties: getGeometryProperties(geometry) };
- style = style.applyContext(context);
+
+ context.setFeature(feature);
+ context.setGeometry(geometry);
if (feature.type === itowns.FEATURE_TYPES.POLYGON) {
symb = '◼';
- if (style) {
- fill = style.fill && style.fill.color;
- stroke = style.stroke && ('1.25px ' + style.stroke.color);
- }
+ fill = style.fill && style.fill.color;
+ stroke = style.stroke && ('1.25px ' + style.stroke.color);
} else if (feature.type === itowns.FEATURE_TYPES.LINE) {
symb = '━';
- fill = style && style.stroke && style.stroke.color;
+ fill = style.stroke && style.stroke.color;
stroke = '0px';
} else if (feature.type === itowns.FEATURE_TYPES.POINT) {
symb = '●';
- if (style && style.point) { // Style and style.point can be undefined if no style options were passed
- fill = style.point.color;
- stroke = '1.25px ' + style.point.line;
+ if (style.point || style.icon) { // Style and style.point can be undefined if no style options were passed
+ fill = (style.point && style.point.color) || (style.icon && style.icon.color);
+ stroke = '1.25px ' + ((style.point && style.point.line) || 'black');
}
}
@@ -109,10 +105,10 @@ const FeatureToolTip = (function _() {
content += '';
if (geometry.properties) {
- content += (geometry.properties.description || geometry.properties.name || geometry.properties.nom || layer.name || '');
+ content += (geometry.properties.description || geometry.properties.name || geometry.properties.nom || geometry.properties.title || layer.name || '');
}
- if (feature.type === itowns.FEATURE_TYPES.POINT) {
+ if (feature.type === itowns.FEATURE_TYPES.POINT && options.writeLatLong) {
content += '
long ' + feature.coordinates[0].toFixed(4) + '';
content += '
lat ' + feature.coordinates[1].toFixed(4) + '';
}
@@ -231,8 +227,8 @@ const FeatureToolTip = (function _() {
}
const opts = options || { filterAllProperties: true };
- opts.filterProperties = opts.filterProperties == undefined ? [] : opts.filterProperties;
- opts.filterProperties.concat(['name', 'nom', 'style', 'description']);
+ opts.filterProperties = opts.filterProperties === undefined ? [] : opts.filterProperties;
+ opts.writeLatLong = opts.writeLatLong || false;
layers.push({ layer: layer, options: opts });
layersId.push(layer.id);
diff --git a/examples/source_file_gpx_3d.html b/examples/source_file_gpx_3d.html
index 86016cac62..c968daea5d 100644
--- a/examples/source_file_gpx_3d.html
+++ b/examples/source_file_gpx_3d.html
@@ -61,6 +61,15 @@
var waypointGeometry = new itowns.THREE.BoxGeometry(1, 1, 80);
var waypointMaterial = new itowns.THREE.MeshBasicMaterial({ color: 0xffffff });
+ const style = {
+ stroke: {
+ color: 'red',
+ width: 2,
+ },
+ point: {
+ color: 'white',
+ }
+ };
// Listen for globe full initialisation event
view.addEventListener(itowns.GLOBE_VIEW_EVENTS.GLOBE_INITIALIZED, function () {
console.info('Globe initialized');
@@ -72,18 +81,9 @@
out: {
crs: view.referenceCrs,
structure: '3d',
- style: new itowns.Style({
- stroke: {
- color: 'red',
- width: 2,
- },
- point: {
- color: 'white',
- }
- }),
}
}))
- .then(itowns.Feature2Mesh.convert())
+ .then(itowns.Feature2Mesh.convert({style}))
.then(function (mesh) {
if (mesh) {
mesh.updateMatrixWorld();
diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js
index 8a66e700bd..e057ad6687 100644
--- a/src/Converter/Feature2Mesh.js
+++ b/src/Converter/Feature2Mesh.js
@@ -7,10 +7,12 @@ import Extent from 'Core/Geographic/Extent';
import Crs from 'Core/Geographic/Crs';
import OrientationUtils from 'Utils/OrientationUtils';
import Coordinates from 'Core/Geographic/Coordinates';
-import { StyleContext } from 'Core/Style';
+import Style, { StyleContext } from 'Core/Style';
const coord = new Coordinates('EPSG:4326', 0, 0, 0);
const context = new StyleContext();
+const defaultStyle = new Style();
+let style;
const dim_ref = new THREE.Vector2();
const dim = new THREE.Vector2();
@@ -191,7 +193,9 @@ function featureToPoint(feature, options) {
const vertices = new Float32Array(ptsIn);
inverseScale.setFromMatrixScale(context.collection.matrixWorldInverse);
normal.set(0, 0, 1).multiply(inverseScale);
- context.globals = { point: true };
+
+ const pointMaterialSize = [];
+ context.setFeature(feature);
for (const geometry of feature.geometries) {
const start = geometry.indices[0].offset;
@@ -206,10 +210,14 @@ function featureToPoint(feature, options) {
}
coord.copy(context.setLocalCoordinatesFromArray(feature.vertices, v));
- const style = feature.style.applyContext(context);
- const { base_altitude, color } = style.point;
+ style.setContext(context);
+ const { base_altitude, color, radius } = style.point;
coord.z = 0;
+ if (!pointMaterialSize.includes(radius)) {
+ pointMaterialSize.push(radius);
+ }
+
// populate vertices
base.copy(normal).multiplyScalar(base_altitude).add(coord).toArray(vertices, v);
toColor(color).multiplyScalar(255).toArray(colors, v);
@@ -223,7 +231,11 @@ function featureToPoint(feature, options) {
geom.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
geom.setAttribute('batchId', new THREE.BufferAttribute(batchIds, 1));
- options.pointMaterial.size = feature.style.point.radius;
+ options.pointMaterial.size = pointMaterialSize[0];
+ if (pointMaterialSize.length > 1) {
+ // TODO CREATE material for each feature
+ console.warn('Too many differents point.radius, only the first one will be used');
+ }
return new THREE.Points(geom, options.pointMaterial);
}
@@ -241,9 +253,8 @@ function featureToLine(feature, options) {
const geom = new THREE.BufferGeometry();
geom.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
- // TODO CREATE material for each feature
- options.lineMaterial.linewidth = feature.style.stroke.width;
- context.globals = { stroke: true };
+ const lineMaterialWidth = [];
+ context.setFeature(feature);
const countIndices = (count - feature.geometries.length) * 2;
const indices = getIntArrayFromSize(countIndices, count);
@@ -279,18 +290,26 @@ function featureToLine(feature, options) {
}
coord.copy(context.setLocalCoordinatesFromArray(feature.vertices, v));
- const style = feature.style.applyContext(context);
- const { base_altitude, color } = style.stroke;
+ style.setContext(context);
+ const { base_altitude, color, width } = style.stroke;
coord.z = 0;
+ if (!lineMaterialWidth.includes(width)) {
+ lineMaterialWidth.push(width);
+ }
+
// populate geometry buffers
base.copy(normal).multiplyScalar(base_altitude).add(coord).toArray(vertices, v);
toColor(color).multiplyScalar(255).toArray(colors, v);
batchIds[j] = id;
}
-
featureId++;
}
+ options.lineMaterial.linewidth = lineMaterialWidth[0];
+ if (lineMaterialWidth.length > 1) {
+ // TODO CREATE material for each feature
+ console.warn('Too many differents stroke.width, only the first one will be used');
+ }
geom.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
geom.setAttribute('batchId', new THREE.BufferAttribute(batchIds, 1));
geom.setIndex(new THREE.BufferAttribute(indices, 1));
@@ -304,7 +323,7 @@ function featureToPolygon(feature, options) {
const batchIds = new Uint32Array(vertices.length / 3);
const batchId = options.batchId || ((p, id) => id);
- context.globals = { fill: true };
+ context.setFeature(feature);
inverseScale.setFromMatrixScale(context.collection.matrixWorldInverse);
normal.set(0, 0, 1).multiply(inverseScale);
@@ -332,7 +351,7 @@ function featureToPolygon(feature, options) {
}
coord.copy(context.setLocalCoordinatesFromArray(feature.vertices, i));
- const style = feature.style.applyContext(context);
+ style.setContext(context);
const { base_altitude, color } = style.fill;
coord.z = 0;
@@ -392,7 +411,7 @@ function featureToExtrudedPolygon(feature, options) {
let featureId = 0;
- context.globals = { fill: true };
+ context.setFeature(feature);
inverseScale.setFromMatrixScale(context.collection.matrixWorldInverse);
normal.set(0, 0, 1).multiply(inverseScale);
coord.setCrs(context.collection.crs);
@@ -418,7 +437,7 @@ function featureToExtrudedPolygon(feature, options) {
coord.copy(context.setLocalCoordinatesFromArray(ptsIn, i));
- const style = feature.style.applyContext(context);
+ style.setContext(context);
const { base_altitude, extrusion_height, color } = style.fill;
coord.z = 0;
@@ -513,7 +532,7 @@ function createInstancedMesh(mesh, count, ptsIn) {
function pointsToInstancedMeshes(feature) {
const ptsIn = feature.vertices;
const count = feature.geometries.length;
- const modelObject = feature.style.point.model.object;
+ const modelObject = style.point.model.object;
if (modelObject instanceof THREE.Mesh) {
return createInstancedMesh(modelObject, count, ptsIn);
@@ -524,15 +543,15 @@ function pointsToInstancedMeshes(feature) {
meshes.forEach(mesh => group.add(createInstancedMesh(mesh, count, ptsIn)));
return group;
} else {
- throw new Error('The format of the model object provided in the feature style (feature.style.point.model.object) is not supported. Only THREE.Mesh or THREE.Object3D are supported.');
+ throw new Error('The format of the model object provided in the style (layer.style.point.model.object) is not supported. Only THREE.Mesh or THREE.Object3D are supported.');
}
}
/**
* Convert a [Feature]{@link Feature} to a Mesh
- *
* @param {Feature} feature - the feature to convert
* @param {Object} options - options controlling the conversion
+ *
* @return {THREE.Mesh} mesh or GROUP of THREE.InstancedMesh
*/
function featureToMesh(feature, options) {
@@ -543,7 +562,7 @@ function featureToMesh(feature, options) {
let mesh;
switch (feature.type) {
case FEATURE_TYPES.POINT:
- if (feature.style.point?.model?.object) {
+ if (style.point?.model?.object) {
try {
mesh = pointsToInstancedMeshes(feature);
mesh.isInstancedMesh = true;
@@ -558,7 +577,7 @@ function featureToMesh(feature, options) {
mesh = featureToLine(feature, options);
break;
case FEATURE_TYPES.POLYGON:
- if (feature.style.fill.extrusion_height) {
+ if (style.fill && Object.keys(style.fill).includes('extrusion_height')) {
mesh = featureToExtrudedPolygon(feature, options);
} else {
mesh = featureToPolygon(feature, options);
@@ -573,10 +592,6 @@ function featureToMesh(feature, options) {
}
mesh.feature = feature;
- if (options.layer) {
- mesh.layer = options.layer;
- }
-
return mesh;
}
@@ -592,6 +607,8 @@ export default {
* @param {function} [options.batchId] - optional function to create batchId attribute.
* It is passed the feature property and the feature index. As the batchId is using an unsigned int structure on 32 bits,
* the batchId could be between 0 and 4,294,967,295.
+ * @param {StyleOptions} [options.style] - optional style properties. Only needed if the convert is used without instancing
+ * a layer beforehand.
* @return {function}
* @example
Example usage of batchId with featureId.
* view.addLayer({
@@ -624,20 +641,28 @@ export default {
if (!options.pointMaterial) {
// Opacity and wireframe refered with layer properties
- // TODO :next step is move these properties to Style
+ // TODO: next step is move these properties to Style
options.pointMaterial = ReferLayerProperties(new THREE.PointsMaterial(), this);
options.lineMaterial = ReferLayerProperties(new THREE.LineBasicMaterial(), this);
options.polygonMaterial = ReferLayerProperties(new THREE.MeshBasicMaterial(), this);
- options.layer = this;
}
+ // In the case we didn't instanciate the layer (this) before the convert, we can pass
+ // style properties (@link StyleOptions) using options.style.
+ // This is usually done in some tests and if you want to use Feature2Mesh.convert()
+ // as in examples/source_file_gpx_3d.html.
+ style = this?.style || (options.style ? new Style(options.style) : defaultStyle);
+
context.setCollection(collection);
const features = collection.features;
-
if (!features || features.length == 0) { return; }
- const meshes = features.map(feature => featureToMesh(feature, options));
+ const meshes = features.map((feature) => {
+ const mesh = featureToMesh(feature, options);
+ mesh.layer = this;
+ return mesh;
+ });
const featureNode = new FeatureMesh(meshes, collection);
return featureNode;
diff --git a/src/Converter/Feature2Texture.js b/src/Converter/Feature2Texture.js
index 77dcaa0d65..4d08acd8d3 100644
--- a/src/Converter/Feature2Texture.js
+++ b/src/Converter/Feature2Texture.js
@@ -4,7 +4,9 @@ import Extent from 'Core/Geographic/Extent';
import Coordinates from 'Core/Geographic/Coordinates';
import Style, { StyleContext } from 'Core/Style';
+const defaultStyle = new Style();
const context = new StyleContext();
+let style;
/**
* Draw polygon (contour, line edge and fill) based on feature vertices into canvas
@@ -15,27 +17,15 @@ const context = new StyleContext();
* @param {Object[]} indices - Contains the indices that define the geometry.
* Objects stored in this array have two properties, an `offset` and a `count`.
* The offset is related to the overall number of vertices in the Feature.
- * @param {Object} style - object defining the style of the polygon.
* @param {Number} size - The size of the feature.
* @param {Number} extent - The extent.
* @param {Number} invCtxScale - The ration to scale line width and radius circle.
* @param {Boolean} canBeFilled - true if feature.type == FEATURE_TYPES.POLYGON
*/
-function drawPolygon(ctx, vertices, indices = [{ offset: 0, count: 1 }], style = {}, size, extent, invCtxScale, canBeFilled) {
+function drawPolygon(ctx, vertices, indices = [{ offset: 0, count: 1 }], size, extent, invCtxScale, canBeFilled) {
if (vertices.length === 0) {
return;
}
-
- if (style.length) {
- for (const s of style) {
- _drawPolygon(ctx, vertices, indices, s, size, extent, invCtxScale, canBeFilled);
- }
- } else {
- _drawPolygon(ctx, vertices, indices, style, size, extent, invCtxScale, canBeFilled);
- }
-}
-
-function _drawPolygon(ctx, vertices, indices, style, size, extent, invCtxScale, canBeFilled) {
// build contour
const path = new Path2D();
@@ -49,10 +39,10 @@ function _drawPolygon(ctx, vertices, indices, style, size, extent, invCtxScale,
}
}
}
- Style.prototype.applyToCanvasPolygon.call(style, ctx, path, invCtxScale, canBeFilled);
+ style.applyToCanvasPolygon(ctx, path, invCtxScale, canBeFilled);
}
-function drawPoint(ctx, x, y, style = {}, invCtxScale) {
+function drawPoint(ctx, x, y, invCtxScale) {
ctx.beginPath();
const opacity = style.point.opacity == undefined ? 1.0 : style.point.opacity;
if (opacity !== ctx.globalAlpha) {
@@ -73,36 +63,32 @@ function drawPoint(ctx, x, y, style = {}, invCtxScale) {
const coord = new Coordinates('EPSG:4326', 0, 0, 0);
-function drawFeature(ctx, feature, extent, style, invCtxScale) {
+function drawFeature(ctx, feature, extent, invCtxScale) {
const extentDim = extent.planarDimensions();
const scaleRadius = extentDim.x / ctx.canvas.width;
for (const geometry of feature.geometries) {
if (Extent.intersectsExtent(geometry.extent, extent)) {
context.setGeometry(geometry);
- const contextStyle = (geometry.properties.style || style).applyContext(context);
-
- if (contextStyle) {
- if (
- feature.type === FEATURE_TYPES.POINT
- && contextStyle.point
- ) {
- // cross multiplication to know in the extent system the real size of
- // the point
- const px = (Math.round(contextStyle.point.radius * invCtxScale) || 3 * invCtxScale) * scaleRadius;
- for (const indice of geometry.indices) {
- const offset = indice.offset * feature.size;
- const count = offset + indice.count * feature.size;
- for (let j = offset; j < count; j += feature.size) {
- coord.setFromArray(feature.vertices, j);
- if (extent.isPointInside(coord, px)) {
- drawPoint(ctx, feature.vertices[j], feature.vertices[j + 1], contextStyle, invCtxScale);
- }
+
+ if (
+ feature.type === FEATURE_TYPES.POINT && style.point
+ ) {
+ // cross multiplication to know in the extent system the real size of
+ // the point
+ const px = (Math.round(style.point.radius * invCtxScale) || 3 * invCtxScale) * scaleRadius;
+ for (const indice of geometry.indices) {
+ const offset = indice.offset * feature.size;
+ const count = offset + indice.count * feature.size;
+ for (let j = offset; j < count; j += feature.size) {
+ coord.setFromArray(feature.vertices, j);
+ if (extent.isPointInside(coord, px)) {
+ drawPoint(ctx, feature.vertices[j], feature.vertices[j + 1], invCtxScale);
}
}
- } else {
- drawPolygon(ctx, feature.vertices, geometry.indices, contextStyle, feature.size, extent, invCtxScale, (feature.type == FEATURE_TYPES.POLYGON));
}
+ } else {
+ drawPolygon(ctx, feature.vertices, geometry.indices, feature.size, extent, invCtxScale, (feature.type == FEATURE_TYPES.POLYGON));
}
}
}
@@ -121,7 +107,9 @@ const featureExtent = new Extent('EPSG:4326', 0, 0, 0, 0);
export default {
// backgroundColor is a THREE.Color to specify a color to fill the texture
// with, given there is no feature passed in parameter
- createTextureFromFeature(collection, extent, sizeTexture, style = {}, backgroundColor) {
+ createTextureFromFeature(collection, extent, sizeTexture, layerStyle, backgroundColor) {
+ style = layerStyle || defaultStyle;
+ style.setContext(context);
let texture;
if (collection) {
@@ -139,7 +127,9 @@ export default {
ctx.fillStyle = backgroundColor.getStyle();
ctx.fillRect(0, 0, sizeTexture, sizeTexture);
}
- ctx.globalCompositeOperation = style.globalCompositeOperation || 'source-over';
+
+ // Documentation needed !!
+ ctx.globalCompositeOperation = layerStyle.globalCompositeOperation || 'source-over';
ctx.imageSmoothingEnabled = false;
ctx.lineJoin = 'round';
@@ -168,16 +158,12 @@ export default {
// to scale line width and radius circle
const invCtxScale = Math.abs(1 / scale.x);
- context.globals = {
- fill: true,
- stroke: true,
- point: true,
- zoom: extent.zoom,
- };
+ context.setZoom(extent.zoom);
// Draw the canvas
for (const feature of collection.features) {
- drawFeature(ctx, feature, featureExtent, feature.style || style, invCtxScale);
+ context.setFeature(feature);
+ drawFeature(ctx, feature, featureExtent, invCtxScale);
}
texture = new THREE.CanvasTexture(c);
diff --git a/src/Core/Feature.js b/src/Core/Feature.js
index 26bf4a69cd..197572b859 100644
--- a/src/Core/Feature.js
+++ b/src/Core/Feature.js
@@ -251,7 +251,7 @@ class Feature {
}
this._pos = 0;
this._pushValues = (this.size === 3 ? push3DValues : push2DValues).bind(this);
- this.style = new Style({}, collection.style);
+ this.style = Style.setFromProperties;
}
/**
* Instance a new {@link FeatureGeometry} and push in {@link Feature}.
@@ -424,7 +424,7 @@ export class FeatureCollection extends THREE.Object3D {
/**
* Updates the global transform of the object and its descendants.
*
- * @param {booolean} force The force
+ * @param {boolean} force The force
*/
updateMatrixWorld(force) {
super.updateMatrixWorld(force);
@@ -498,12 +498,4 @@ export class FeatureCollection extends THREE.Object3D {
this.features.push(ref);
return ref;
}
-
- setParentStyle(style) {
- if (style) {
- this.features.forEach((f) => {
- f.style.parent = style;
- });
- }
- }
}
diff --git a/src/Core/Label.js b/src/Core/Label.js
index d1818b0518..019185443f 100644
--- a/src/Core/Label.js
+++ b/src/Core/Label.js
@@ -83,7 +83,7 @@ class Label extends THREE.Object3D {
if (typeof content === 'string') {
this.content = document.createElement('div');
- this.content.textContent = content;
+ this.content.textContent = style.text.field;
} else {
this.content = content.cloneNode(true);
}
diff --git a/src/Core/Style.js b/src/Core/Style.js
index 8856e58ede..002c0268c0 100644
--- a/src/Core/Style.js
+++ b/src/Core/Style.js
@@ -15,22 +15,11 @@ const matrix = svg.createSVGMatrix();
const inv255 = 1 / 255;
const canvas = (typeof document !== 'undefined') ? document.createElement('canvas') : {};
-const style_properties = {};
-function base_altitudeDefault(properties, ctx) {
+function baseAltitudeDefault(properties, ctx) {
return ctx?.coordinates?.z || ctx?.collection?.center?.z || 0;
}
-function mapPropertiesFromContext(mainKey, from, to, context) {
- to[mainKey] = to[mainKey] || {};
- for (const key of style_properties[mainKey]) {
- const value = readExpression(from[mainKey][key], context);
- if (value !== undefined) {
- to[mainKey][key] = value;
- }
- }
-}
-
export function readExpression(property, ctx) {
if (property != undefined) {
if (property.expression) {
@@ -39,19 +28,22 @@ export function readExpression(property, ctx) {
for (let i = property.stops.length - 1; i >= 0; i--) {
const stop = property.stops[i];
- if (ctx.globals.zoom >= stop[0]) {
+ if (ctx.zoom >= stop[0]) {
return stop[1];
}
}
return property.stops[0][1];
- } else if (property instanceof Function) {
+ }
+ if (typeof property === 'string' || property instanceof String) {
+ property = property.replace(/\{(.+?)\}/g, (a, b) => (ctx.properties[b] || '')).trim();
+ }
+ if (property instanceof Function) {
// TOBREAK: Pass the current `context` as a unique parameter.
// In this proposal, metadata will be accessed in the callee by the
// `context.properties` property.
return property(ctx.properties, ctx);
- } else {
- return property;
}
+ return property;
}
}
@@ -138,27 +130,42 @@ const textAnchorPosition = {
'top-left': [0, 0],
};
-function defineStyleProperty(style, category, name, value, defaultValue) {
+/**
+ * Defines a property for the given Style for a specific parameter in a given category (one of fill, stroke, point, text, icon or zoom),
+ * by generating its getter and setter.
+ * The getter is in charge of returning the right style value from the following ones if they are defined (in that specific order):
+ * the value set by the user (`userValue`)
+ * the value read from the data source (`dataValue`)
+ * the default fallback value (`defaultValue`).
+ * The setter can be called to change dynamically the value.
+ * @param {Style} style - The Style instance to set.
+ * @param {string} category - The category (fill, stroke, point, test, icon or zoom) to set.
+ * @param {string} parameter - The parameter of the category to set.
+ * @param {All} userValue - The value given by the user (if any). Can be undefined.
+ * @param {All} [defaultValue] - The default value to return (if needed).
+ */
+function defineStyleProperty(style, category, parameter, userValue, defaultValue) {
let property;
-
Object.defineProperty(
style[category],
- name,
+ parameter,
{
enumerable: true,
get: () => {
- if (property === undefined) {
- return style.parent[category][name] || defaultValue;
- } else {
- return property;
+ // != to check for 'undefined' and 'null' value)
+ if (property != undefined) { return property; }
+ if (userValue != undefined) { return readExpression(userValue, style.context); }
+ 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;
},
set: (v) => {
property = v;
},
});
-
- style[category][name] = value;
}
/**
@@ -167,28 +174,35 @@ function defineStyleProperty(style, category, name, value, defaultValue) {
* type of feature and what is needed (fill, stroke or draw a point, etc.) as well as where to get its
* properties and its coordinates (for base_altitude).
*
- * @property {Object} globals Style type (fill, stroke, point, text and or icon) to consider, it also
- * contains the current zoom.
- * @property {Object} collection The FeatureCollection to which the FeatureGeometry is attached
- * @property {Coordinates} coordinates The coordinates (in world space) of the last vertex (x, y, z) set with
+ * @property {number} zoom Current zoom to display the FeatureGeometry.
+ * @property {Object} collection The FeatureCollection to which the FeatureGeometry is attached.
+ * @property {Object} properties Properties of the FeatureGeometry.
+ * @property {string} type Geometry type of the feature. Can be `point`, `line`, or `polygon`.
+ * @property {StyleOptions|Function}featureStyle StyleOptions object (or a function returning one) to get style
+ * information at feature and FeatureGeometry level from the data parsed.
+ * @property {Coordinates} coordinates The coordinates (in world space) of the last vertex (x, y, z) set with
* setLocalCoordinatesFromArray().
* private properties:
- * @property {Coordinates} worldCoord @private Coordinates object to store coordinates in world space.
- * @property {Coordinates} localCoordinates @private Coordinates object to store coordinates in local space.
- * @property {boolean} worldCoordsComputed @private Have the world coordinates already been computed
- * from the local coordinates?
- * @property {FeatureGeometry} geometry @private The FeatureGeometry to compute the style.
+ * @property {Coordinates} worldCoord @private Coordinates object to store coordinates in world space.
+ * @property {Coordinates} localCoordinates @private Coordinates object to store coordinates in local space.
+ * @property {boolean} worldCoordsComputed @private Have the world coordinates already been computed
+ * from the local coordinates?
+ * @property {Feature} feature @private The itowns feature of interest.
+ * @property {FeatureGeometry} geometry @private The FeatureGeometry to compute the style.
*/
export class StyleContext {
#worldCoord = new Coordinates('EPSG:4326', 0, 0, 0);
#localCoordinates = new Coordinates('EPSG:4326', 0, 0, 0);
#worldCoordsComputed = true;
+ #feature = {};
#geometry = {};
- /**
- * @constructor
- */
- constructor() {
- this.globals = {};
+
+ setZoom(zoom) {
+ this.zoom = zoom;
+ }
+
+ setFeature(f) {
+ this.#feature = f;
}
setGeometry(g) {
@@ -209,6 +223,17 @@ export class StyleContext {
return this.#geometry.properties;
}
+ get type() {
+ return this.#feature.type;
+ }
+ get featureStyle() {
+ let featureStyle = this.#feature.style;
+ if (featureStyle instanceof Function) {
+ featureStyle = featureStyle(this.properties, this);
+ }
+ return featureStyle;
+ }
+
get coordinates() {
if (!this.#worldCoordsComputed) {
this.#worldCoordsComputed = true;
@@ -221,6 +246,58 @@ export class StyleContext {
}
}
+function _addIcon(icon, domElement, opt) {
+ const cIcon = icon.cloneNode();
+
+ cIcon.setAttribute('class', 'itowns-icon');
+
+ cIcon.width = icon.width * opt.size;
+ cIcon.height = icon.height * opt.size;
+ cIcon.style.color = opt.color;
+ cIcon.style.opacity = opt.opacity;
+ cIcon.style.position = 'absolute';
+ cIcon.style.top = '0';
+ cIcon.style.left = '0';
+
+ switch (opt.anchor) { // center by default
+ case 'left':
+ cIcon.style.top = `${-0.5 * cIcon.height}px`;
+ break;
+ case 'right':
+ cIcon.style.top = `${-0.5 * cIcon.height}px`;
+ cIcon.style.left = `${-cIcon.width}px`;
+ break;
+ case 'top':
+ cIcon.style.left = `${-0.5 * cIcon.width}px`;
+ break;
+ case 'bottom':
+ cIcon.style.top = `${-cIcon.height}px`;
+ cIcon.style.left = `${-0.5 * cIcon.width}px`;
+ break;
+ case 'bottom-left':
+ cIcon.style.top = `${-cIcon.height}px`;
+ break;
+ case 'bottom-right':
+ cIcon.style.top = `${-cIcon.height}px`;
+ cIcon.style.left = `${-cIcon.width}px`;
+ break;
+ case 'top-left':
+ break;
+ case 'top-right':
+ cIcon.style.left = `${-cIcon.width}px`;
+ break;
+ case 'center':
+ default:
+ cIcon.style.top = `${-0.5 * cIcon.height}px`;
+ cIcon.style.left = `${-0.5 * cIcon.width}px`;
+ break;
+ }
+
+ cIcon.style['z-index'] = -1;
+ domElement.appendChild(cIcon);
+ return cIcon;
+}
+
/**
* @typedef {Object} StyleOptions
* @memberof StyleOptions
@@ -418,8 +495,8 @@ export class StyleOptions {}
* for each coordinate.
* If `base_altitude` is `undefined`, the original altitude is kept, and if it doesn't exist
* then the altitude value is set to 0.
- * @property {Number|Function} fill.extrusion_height - Only for {@link GeometryLayer}, if defined,
- * polygons will be extruded by the specified amount
+ * @property {Number|Function} [fill.extrusion_height] - Only for {@link GeometryLayer} and if user sets it.
+ * If defined, polygons will be extruded by the specified amount.
* @property {Object} stroke - Lines and polygons edges.
* @property {String|Function|THREE.Color} stroke.color The color of the line. Can be any [valid
* color string](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).
@@ -548,24 +625,14 @@ class Style {
* @param {StyleOptions} [params={}] An object that contain any properties
* (order, zoom, fill, stroke, point, text or/and icon)
* and sub properties of a Style (@see {@link StyleOptions}).
- * @param {Style} [parent] The parent style, that is looked onto if a value
- * is missing.
* @constructor
*/
- constructor(params = {}, parent) {
+ constructor(params = {}) {
this.isStyle = true;
+ this.context = new StyleContext();
this.order = params.order || 0;
- this.parent = parent || {
- zoom: {},
- fill: {},
- stroke: {},
- point: {},
- text: {},
- icon: {},
- };
-
params.zoom = params.zoom || {};
params.fill = params.fill || {};
params.stroke = params.stroke || {};
@@ -581,15 +648,17 @@ class Style {
defineStyleProperty(this, 'fill', 'color', params.fill.color);
defineStyleProperty(this, 'fill', 'opacity', params.fill.opacity, 1.0);
defineStyleProperty(this, 'fill', 'pattern', params.fill.pattern);
- defineStyleProperty(this, 'fill', 'base_altitude', params.fill.base_altitude, base_altitudeDefault);
- defineStyleProperty(this, 'fill', 'extrusion_height', params.fill.extrusion_height);
+ defineStyleProperty(this, 'fill', 'base_altitude', params.fill.base_altitude, baseAltitudeDefault);
+ if (params.fill.extrusion_height) {
+ defineStyleProperty(this, 'fill', 'extrusion_height', params.fill.extrusion_height);
+ }
this.stroke = {};
defineStyleProperty(this, 'stroke', 'color', params.stroke.color);
defineStyleProperty(this, 'stroke', 'opacity', params.stroke.opacity, 1.0);
defineStyleProperty(this, 'stroke', 'width', params.stroke.width, 1.0);
defineStyleProperty(this, 'stroke', 'dasharray', params.stroke.dasharray, []);
- defineStyleProperty(this, 'stroke', 'base_altitude', params.stroke.base_altitude, base_altitudeDefault);
+ defineStyleProperty(this, 'stroke', 'base_altitude', params.stroke.base_altitude, baseAltitudeDefault);
this.point = {};
defineStyleProperty(this, 'point', 'color', params.point.color);
@@ -597,8 +666,10 @@ class Style {
defineStyleProperty(this, 'point', 'opacity', params.point.opacity, 1.0);
defineStyleProperty(this, 'point', 'radius', params.point.radius, 2.0);
defineStyleProperty(this, 'point', 'width', params.point.width, 0.0);
- defineStyleProperty(this, 'point', 'base_altitude', params.point.base_altitude, base_altitudeDefault);
- defineStyleProperty(this, 'point', 'model', params.point.model);
+ defineStyleProperty(this, 'point', 'base_altitude', params.point.base_altitude, baseAltitudeDefault);
+ if (params.point.model) {
+ defineStyleProperty(this, 'point', 'model', params.point.model);
+ }
this.text = {};
defineStyleProperty(this, 'text', 'field', params.text.field);
@@ -650,62 +721,47 @@ class Style {
}
/**
- * Clones this style.
- *
- * @return {Style} The new style, cloned from this one.
- */
+ * Clones this style.
+ *
+ * @return {Style} The new style, cloned from this one.
+ */
clone() {
const clone = new Style();
return clone.copy(this);
}
- /**
- * Map style object properties (fill, stroke, point, text and icon) from context to Style.
- * Only the necessary properties are mapped to object.
- * if a property is expression, the mapped value will be the expression result depending on context.
- * @param {Object} context The context of the FeatureGeometry that we want to get the Style.
- *
- * @return {Style} mapped style depending on context.
- */
- applyContext(context) {
- const style = {};
- if (this.fill.color || this.fill.pattern || context.globals.fill) {
- mapPropertiesFromContext('fill', this, style, context);
- }
- if (this.stroke.color || context.globals.stroke) {
- mapPropertiesFromContext('stroke', this, style, context);
- }
- if (this.point.color || this.point.model || context.globals.point) {
- mapPropertiesFromContext('point', this, style, context);
- }
- if (this.text || context.globals.text) {
- mapPropertiesFromContext('text', this, style, context);
- }
- if (this.icon || context.globals.icon) {
- mapPropertiesFromContext('icon', this, style, context);
- }
- style.order = this.order;
- return new Style(style);
+ setContext(ctx) {
+ this.context = ctx;
}
/**
* set Style from (geojson-like) properties.
* @param {Object} properties (geojson-like) properties.
- * @param {Number} type
+ * @param {FeatureContext} featCtx the context of the feature
*
* @returns {StyleOptions} containing all properties for itowns.Style
*/
- setFromGeojsonProperties(properties, type) {
+ static setFromProperties(properties, featCtx) {
+ const type = featCtx.type;
+ const style = {};
if (type === FEATURE_TYPES.POINT) {
- this.point.color = properties.fill;
- this.point.opacity = properties['fill-opacity'];
- this.point.line = properties.stroke;
- this.point.radius = properties.radius;
-
- this.text.color = properties['label-color'];
- this.text.opacity = properties['label-opacity'];
- this.text.size = properties['label-size'];
-
+ const point = {
+ ...(properties.fill !== undefined && { color: properties.fill }),
+ ...(properties['fill-opacity'] !== undefined && { opacity: properties['fill-opacity'] }),
+ ...(properties.stroke !== undefined && { line: properties.stroke }),
+ ...(properties.radius !== undefined && { radius: properties.radius }),
+ };
+ if (Object.keys(point).length) {
+ style.point = point;
+ }
+ const text = {
+ ...(properties['label-color'] !== undefined && { color: properties['label-color'] }),
+ ...(properties['label-opacity'] !== undefined && { opacity: properties['label-opacity'] }),
+ ...(properties['label-size'] !== undefined && { size: properties['label-size'] }),
+ };
+ if (Object.keys(point).length) {
+ style.text = text;
+ }
const icon = {
...(properties.icon !== undefined && { source: properties.icon }),
...(properties['icon-scale'] !== undefined && { size: properties['icon-scale'] }),
@@ -713,19 +769,28 @@ class Style {
...(properties['icon-color'] !== undefined && { color: properties['icon-color'] }),
};
if (Object.keys(icon).length) {
- this.icon = icon;
+ style.icon = icon;
}
} else {
- this.stroke.color = properties.stroke;
- this.stroke.width = properties['stroke-width'];
- this.stroke.opacity = properties['stroke-opacity'];
-
+ const stroke = {
+ ...(properties.stroke !== undefined && { color: properties.stroke }),
+ ...(properties['stroke-width'] !== undefined && { width: properties['stroke-width'] }),
+ ...(properties['stroke-opacity'] !== undefined && { opacity: properties['stroke-opacity'] }),
+ };
+ if (Object.keys(stroke).length) {
+ style.stroke = stroke;
+ }
if (type !== FEATURE_TYPES.LINE) {
- this.fill.color = properties.fill;
- this.fill.opacity = properties['fill-opacity'];
+ const fill = {
+ ...(properties.fill !== undefined && { color: properties.fill }),
+ ...(properties['fill-opacity'] !== undefined && { opacity: properties['fill-opacity'] }),
+ };
+ if (Object.keys(fill).length) {
+ style.fill = fill;
+ }
}
}
- return this;
+ return style;
}
/**
@@ -737,19 +802,27 @@ class Style {
*
* @returns {StyleOptions} containing all properties for itowns.Style
*/
- setFromVectorTileLayer(layer, sprites, order = 0, symbolToCircle = false) {
+ static setFromVectorTileLayer(layer, sprites, order = 0, symbolToCircle = false) {
+ const style = {
+ fill: {},
+ stroke: {},
+ point: {},
+ text: {},
+ icon: {},
+ };
+
layer.layout = layer.layout || {};
layer.paint = layer.paint || {};
- this.order = order;
+ style.order = order;
- if (layer.type === 'fill' && !this.fill.color) {
+ if (layer.type === 'fill') {
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['fill-color'] || layer.paint['fill-pattern'], { type: 'color' }));
- this.fill.color = color;
- this.fill.opacity = readVectorProperty(layer.paint['fill-opacity']) || opacity;
+ style.fill.color = color;
+ style.fill.opacity = readVectorProperty(layer.paint['fill-opacity']) || opacity;
if (layer.paint['fill-pattern']) {
try {
- this.fill.pattern = {
+ style.fill.pattern = {
id: layer.paint['fill-pattern'],
source: sprites.source,
cropValues: sprites[layer.paint['fill-pattern']],
@@ -762,82 +835,90 @@ class Style {
if (layer.paint['fill-outline-color']) {
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['fill-outline-color'], { type: 'color' }));
- this.stroke.color = color;
- this.stroke.opacity = opacity;
- this.stroke.width = 1.0;
- this.stroke.dasharray = [];
+ style.stroke.color = color;
+ style.stroke.opacity = opacity;
+ style.stroke.width = 1.0;
+ style.stroke.dasharray = [];
}
- } else if (layer.type === 'line' && !this.stroke.color) {
+ } else if (layer.type === 'line') {
const prepare = readVectorProperty(layer.paint['line-color'], { type: 'color' });
const { color, opacity } = rgba2rgb(prepare);
- this.stroke.dasharray = readVectorProperty(layer.paint['line-dasharray']);
- this.stroke.color = color;
- this.stroke.lineCap = layer.layout['line-cap'];
- this.stroke.width = readVectorProperty(layer.paint['line-width']);
- this.stroke.opacity = readVectorProperty(layer.paint['line-opacity']) || opacity;
+ style.stroke.dasharray = readVectorProperty(layer.paint['line-dasharray']);
+ style.stroke.color = color;
+ style.stroke.lineCap = layer.layout['line-cap'];
+ style.stroke.width = readVectorProperty(layer.paint['line-width']);
+ style.stroke.opacity = readVectorProperty(layer.paint['line-opacity']) || opacity;
} else if (layer.type === 'circle' || symbolToCircle) {
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['circle-color'], { type: 'color' }));
- this.point.color = color;
- this.point.opacity = opacity;
- this.point.radius = readVectorProperty(layer.paint['circle-radius']);
+ style.point.color = color;
+ style.point.opacity = opacity;
+ style.point.radius = readVectorProperty(layer.paint['circle-radius']);
} else if (layer.type === 'symbol') {
// overlapping order
- this.text.zOrder = readVectorProperty(layer.layout['symbol-z-order']);
- if (this.text.zOrder == 'auto') {
- this.text.zOrder = readVectorProperty(layer.layout['symbol-sort-key']) || 'Y';
- } else if (this.text.zOrder == 'viewport-y') {
- this.text.zOrder = 'Y';
- } else if (this.text.zOrder == 'source') {
- this.text.zOrder = 0;
+ style.text.zOrder = readVectorProperty(layer.layout['symbol-z-order']);
+ if (style.text.zOrder == 'auto') {
+ style.text.zOrder = readVectorProperty(layer.layout['symbol-sort-key']) || 'Y';
+ } else if (style.text.zOrder == 'viewport-y') {
+ style.text.zOrder = 'Y';
+ } else if (style.text.zOrder == 'source') {
+ style.text.zOrder = 0;
}
// position
- this.text.anchor = readVectorProperty(layer.layout['text-anchor']);
- this.text.offset = readVectorProperty(layer.layout['text-offset']);
- this.text.padding = readVectorProperty(layer.layout['text-padding']);
- this.text.size = readVectorProperty(layer.layout['text-size']);
- this.text.placement = readVectorProperty(layer.layout['symbol-placement']);
- this.text.rotation = readVectorProperty(layer.layout['text-rotation-alignment']);
+ style.text.anchor = readVectorProperty(layer.layout['text-anchor']);
+ style.text.offset = readVectorProperty(layer.layout['text-offset']);
+ style.text.padding = readVectorProperty(layer.layout['text-padding']);
+ style.text.size = readVectorProperty(layer.layout['text-size']);
+ style.text.placement = readVectorProperty(layer.layout['symbol-placement']);
+ style.text.rotation = readVectorProperty(layer.layout['text-rotation-alignment']);
// content
- this.text.field = readVectorProperty(layer.layout['text-field']);
- this.text.wrap = readVectorProperty(layer.layout['text-max-width']);
- this.text.spacing = readVectorProperty(layer.layout['text-letter-spacing']);
- this.text.transform = readVectorProperty(layer.layout['text-transform']);
- this.text.justify = readVectorProperty(layer.layout['text-justify']);
+ style.text.field = readVectorProperty(layer.layout['text-field']);
+ style.text.wrap = readVectorProperty(layer.layout['text-max-width']);
+ 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']);
// appearance
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['text-color'], { type: 'color' }));
- this.text.color = color;
- this.text.opacity = readVectorProperty(layer.paint['text-opacity']) || (opacity !== undefined && opacity);
+ style.text.color = color;
+ style.text.opacity = readVectorProperty(layer.paint['text-opacity']) || (opacity !== undefined && opacity);
- this.text.font = readVectorProperty(layer.layout['text-font']);
+ style.text.font = readVectorProperty(layer.layout['text-font']);
const haloColor = readVectorProperty(layer.paint['text-halo-color'], { type: 'color' });
if (haloColor) {
- this.text.haloColor = haloColor.color || haloColor;
- this.text.haloWidth = readVectorProperty(layer.paint['text-halo-width']);
- this.text.haloBlur = readVectorProperty(layer.paint['text-halo-blur']);
+ style.text.haloColor = haloColor.color || haloColor;
+ style.text.haloWidth = readVectorProperty(layer.paint['text-halo-width']);
+ style.text.haloBlur = readVectorProperty(layer.paint['text-halo-blur']);
}
// additional icon
const iconImg = readVectorProperty(layer.layout['icon-image']);
if (iconImg) {
try {
- this.icon.id = iconImg;
- this.icon.source = sprites.source;
- this.icon.cropValues = sprites[iconImg];
+ style.icon.id = iconImg;
+ style.icon.source = sprites.source;
+ style.icon.cropValues = sprites[iconImg];
- this.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' }));
- this.icon.color = color;
- this.icon.opacity = readVectorProperty(layer.paint['icon-opacity']) || (opacity !== undefined && opacity);
+ 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;
}
}
}
- return this;
+ // VectorTileSet: by default minZoom = 0 and maxZoom = 24
+ // https://docs.mapbox.com/style-spec/reference/layers/#maxzoom and #minzoom
+ // Should be move to layer properties, when (if) one mapBox layer will be considered as several itowns layers.
+ // issue https://github.com/iTowns/itowns/issues/2153 (last point)
+ style.zoom = {
+ min: layer.minzoom || 0,
+ max: layer.maxzoom || 24,
+ };
+ return style;
}
/**
@@ -848,16 +929,17 @@ 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) {
// TO DO add possibility of using a pattern (https://github.com/iTowns/itowns/issues/2210)
- Style.prototype._applyStrokeToPolygon.call(this, txtrCtx, invCtxScale, polygon);
+ this._applyStrokeToPolygon(txtrCtx, invCtxScale, polygon, context);
}
// fill inside of polygon
if (canBeFilled && this.fill) {
// canBeFilled can be move to StyleContext in the later PR
- Style.prototype._applyFillToPolygon.call(this, txtrCtx, invCtxScale, polygon);
+ this._applyFillToPolygon(txtrCtx, invCtxScale, polygon, context);
}
}
@@ -885,10 +967,11 @@ 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;
if (this.fill.pattern.source) {
img = await loadImage(this.fill.pattern.source);
}
- cropImage(img, this.fill.pattern.cropValues);
+ cropImage(img, cropValues);
txtrCtx.fillStyle = txtrCtx.createPattern(canvas, 'repeat');
if (txtrCtx.fillStyle.setTransform) {
@@ -926,7 +1009,6 @@ class Style {
if (this.text.size > 0) {
domElement.style.fontSize = `${this.text.size}px`;
}
-
domElement.style.fontFamily = this.text.font.join(',');
domElement.style.textTransform = this.text.transform;
domElement.style.letterSpacing = `${this.text.spacing}em`;
@@ -947,74 +1029,31 @@ class Style {
const icon = document.createElement('img');
const iconPromise = new Promise((resolve, reject) => {
- icon.onload = () => resolve(this._addIcon(icon, domElement));
+ const opt = {
+ size: this.icon.size,
+ color: this.icon.color,
+ opacity: this.icon.opacity,
+ anchor: this.icon.anchor,
+ };
+ icon.onload = () => resolve(_addIcon(icon, domElement, opt));
icon.onerror = err => reject(err);
});
if (!this.icon.cropValues && !this.icon.color) {
icon.src = this.icon.source;
} else {
+ 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);
- const imgd = cropImage(img, this.icon.cropValues);
- const imgdColored = replaceWhitePxl(imgd, this.icon.color, this.icon.id || this.icon.source);
+ const imgd = cropImage(img, cropValues);
+ const imgdColored = replaceWhitePxl(imgd, color, id);
canvas.getContext('2d').putImageData(imgdColored, 0, 0);
icon.src = canvas.toDataURL('image/png');
}
return iconPromise;
}
- _addIcon(icon, domElement) {
- const cIcon = icon.cloneNode();
-
- cIcon.setAttribute('class', 'itowns-icon');
-
- cIcon.width = icon.width * this.icon.size;
- cIcon.height = icon.height * this.icon.size;
- cIcon.style.color = this.icon.color;
- cIcon.style.opacity = this.icon.opacity;
- cIcon.style.position = 'absolute';
- cIcon.style.top = '0';
- cIcon.style.left = '0';
-
- switch (this.icon.anchor) { // center by default
- case 'left':
- cIcon.style.top = `${-0.5 * cIcon.height}px`;
- break;
- case 'right':
- cIcon.style.top = `${-0.5 * cIcon.height}px`;
- cIcon.style.left = `${-cIcon.width}px`;
- break;
- case 'top':
- cIcon.style.left = `${-0.5 * cIcon.width}px`;
- break;
- case 'bottom':
- cIcon.style.top = `${-cIcon.height}px`;
- cIcon.style.left = `${-0.5 * cIcon.width}px`;
- break;
- case 'bottom-left':
- cIcon.style.top = `${-cIcon.height}px`;
- break;
- case 'bottom-right':
- cIcon.style.top = `${-cIcon.height}px`;
- cIcon.style.left = `${-cIcon.width}px`;
- break;
- case 'top-left':
- break;
- case 'top-right':
- cIcon.style.left = `${-cIcon.width}px`;
- break;
- case 'center':
- default:
- cIcon.style.top = `${-0.5 * cIcon.height}px`;
- cIcon.style.left = `${-0.5 * cIcon.width}px`;
- break;
- }
-
- cIcon.style['z-index'] = -1;
- domElement.appendChild(cIcon);
- return cIcon;
- }
-
/**
* Gets the values corresponding to the anchor of the text. It is
* proportions, to use with a `translate()` and a `transform` property.
@@ -1033,24 +1072,6 @@ class Style {
return this.text.anchor;
}
}
-
- /**
- * Returns a string, associating `style.text.field` and properties to use to
- * replace the keys in `style.text.field`.
- *
- * @param {Object} ctx - An object containing the feature context.
- *
- * @return {String|undefined} The formatted string if `style.text.field` is defined, nothing otherwise.
- */
- getTextFromProperties(ctx) {
- if (!this.text.field) { return; }
-
- if (this.text.field.expression) {
- return readExpression(this.text.field, ctx);
- } else {
- return this.text.field.replace(/\{(.+?)\}/g, (a, b) => (ctx.properties[b] || '')).trim();
- }
- }
}
// Add custom style sheet with iTowns specifics
@@ -1069,12 +1090,4 @@ if (typeof document !== 'undefined') {
document.getElementsByTagName('head')[0].appendChild(customStyleSheet);
}
-const style = new Style();
-
-style_properties.fill = Object.keys(style.fill);
-style_properties.stroke = Object.keys(style.stroke);
-style_properties.point = Object.keys(style.point);
-style_properties.text = Object.keys(style.text);
-style_properties.icon = Object.keys(style.icon);
-
export default Style;
diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js
index 40bdea859b..86fc46ed67 100644
--- a/src/Layer/C3DTilesLayer.js
+++ b/src/Layer/C3DTilesLayer.js
@@ -373,6 +373,9 @@ class C3DTilesLayer extends GeometryLayer {
if (!this._style) {
return false;
}
+ if (!this.object3d) {
+ return false;
+ }
const currentMaterials = [];// list materials used for this update
diff --git a/src/Layer/ColorLayer.js b/src/Layer/ColorLayer.js
index a0b437a48a..fecf467561 100644
--- a/src/Layer/ColorLayer.js
+++ b/src/Layer/ColorLayer.js
@@ -84,7 +84,6 @@ class ColorLayer extends RasterLayer {
deprecatedColorLayerOptions(config);
super(id, config);
this.isColorLayer = true;
- this.style = config.style;
this.defineLayerProperty('visible', true);
this.defineLayerProperty('opacity', 1.0);
this.defineLayerProperty('sequence', 0);
diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js
index d2c55ef813..bd3a7bb2b4 100644
--- a/src/Layer/LabelLayer.js
+++ b/src/Layer/LabelLayer.js
@@ -238,28 +238,24 @@ class LabelLayer extends GeometryLayer {
convert(data, extent) {
const labels = [];
- const layerField = this.style && this.style.text && this.style.text.field;
-
// Converting the extent now is faster for further operation
extent.as(data.crs, _extent);
coord.crs = data.crs;
- context.globals = {
- icon: true,
- text: true,
- zoom: extent.zoom,
- };
+
+ context.setZoom(extent.zoom);
data.features.forEach((f) => {
// TODO: add support for LINE and POLYGON
if (f.type !== FEATURE_TYPES.POINT) {
return;
}
+ context.setFeature(f);
- const featureField = f.style.text.field;
+ const featureField = f.style?.text?.field;
// determine if altitude style is specified by the user
- const altitudeStyle = f.style.point.base_altitude;
- const isDefaultElevationStyle = altitudeStyle instanceof Function && altitudeStyle.name == 'base_altitudeDefault';
+ const altitudeStyle = f.style?.point?.base_altitude;
+ const isDefaultElevationStyle = altitudeStyle instanceof Function && altitudeStyle.name == 'baseAltitudeDefault';
// determine if the altitude needs update with ElevationLayer
labels.needsAltitude = labels.needsAltitude || this.forceClampToTerrain === true || (isDefaultElevationStyle && !f.hasRawElevationData);
@@ -272,31 +268,25 @@ class LabelLayer extends GeometryLayer {
coord.applyMatrix4(data.matrixWorld);
if (!_extent.isPointInside(coord)) { return; }
-
- const geometryField = g.properties.style && g.properties.style.text.field;
+ 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;
if (this.labelDomelement) {
content = readExpression(this.labelDomelement, context);
} else if (!geometryField && !featureField && !layerField) {
// Check if there is an icon, with no text
- if (!(g.properties.style && (g.properties.style.icon.source || g.properties.style.icon.id))
- && !(f.style && (f.style.icon.source || f.style.icon.id))
- && !(this.style && (this.style.icon.source || this.style.icon.id))) {
+ if (!(g.properties.style && (g.properties.style.icon.source || g.properties.style.icon.key))
+ && !(f.style && f.style.icon && (f.style.icon.source || f.style.icon.key))
+ && !(this.style.icon && (this.style.icon.source || this.style.icon.key))) {
return;
}
- } else if (geometryField) {
- content = g.properties.style.getTextFromProperties(context);
- } else if (featureField) {
- content = f.style.getTextFromProperties(context);
- } else if (layerField) {
- content = this.style.getTextFromProperties(context);
}
- const style = (g.properties.style || f.style || this.style).applyContext(context);
+ const label = new Label(content, coord.clone(), this.style);
- const label = new Label(content, coord.clone(), style);
label.layerId = this.id;
label.padding = this.margin || label.padding;
diff --git a/src/Layer/Layer.js b/src/Layer/Layer.js
index 55651d1f96..fb6a0eb642 100644
--- a/src/Layer/Layer.js
+++ b/src/Layer/Layer.js
@@ -98,6 +98,8 @@ class Layer extends THREE.EventDispatcher {
throw new Error(`Layer ${id} needs Source`);
}
super();
+ this.isLayer = true;
+
if (config.style && !(config.style instanceof Style)) {
if (typeof config.style.fill?.pattern === 'string') {
console.warn('Using style.fill.pattern = { source: Img|url } is adviced');
@@ -105,8 +107,7 @@ class Layer extends THREE.EventDispatcher {
}
config.style = new Style(config.style);
}
- this.isLayer = true;
-
+ this.style = config.style || new Style();
Object.assign(this, config);
Object.defineProperty(this, 'id', {
diff --git a/src/Parser/GeoJsonParser.js b/src/Parser/GeoJsonParser.js
index 535978a18c..41905f4dc2 100644
--- a/src/Parser/GeoJsonParser.js
+++ b/src/Parser/GeoJsonParser.js
@@ -1,6 +1,5 @@
import Coordinates from 'Core/Geographic/Coordinates';
import { FeatureCollection, FEATURE_TYPES } from 'Core/Feature';
-import Style from 'Core/Style';
import { deprecatedParsingOptionsToNewOne } from 'Core/Deprecated/Undeprecator';
function readCRS(json) {
@@ -74,7 +73,7 @@ const toFeature = {
const geometry = feature.bindNewGeometry();
geometry.properties = properties;
- geometry.properties.style = new Style({}, feature.style).setFromGeojsonProperties(properties, feature.type);
+
this.populateGeometry(crsIn, coordsIn, geometry, feature);
feature.updateExtent(geometry);
},
@@ -85,7 +84,6 @@ const toFeature = {
}
const geometry = feature.bindNewGeometry();
geometry.properties = properties;
- geometry.properties.style = new Style({}, feature.style).setFromGeojsonProperties(properties, feature.type);
// Then read contour and holes
for (let i = 0; i < coordsIn.length; i++) {
diff --git a/src/Source/FileSource.js b/src/Source/FileSource.js
index 28dea00c8c..0701b21a77 100644
--- a/src/Source/FileSource.js
+++ b/src/Source/FileSource.js
@@ -175,10 +175,6 @@ class FileSource extends Source {
this.extent.applyMatrix4(data.matrixWorld);
}
}
-
- if (data.isFeatureCollection) {
- data.setParentStyle(options.out.style);
- }
});
}
diff --git a/src/Source/VectorTilesSource.js b/src/Source/VectorTilesSource.js
index b2095f50c6..9d2f69192d 100644
--- a/src/Source/VectorTilesSource.js
+++ b/src/Source/VectorTilesSource.js
@@ -94,9 +94,7 @@ class VectorTilesSource extends TMSSource {
if (layer.type === 'background') {
this.backgroundLayer = layer;
} else if (ffilter(layer)) {
- const style = new Style().setFromVectorTileLayer(layer, this.sprites, order, this.symbolToCircle);
- style.zoom.min = layer.minzoom || 0;
- style.zoom.max = layer.maxzoom || 24;
+ const style = Style.setFromVectorTileLayer(layer, this.sprites, order, this.symbolToCircle);
this.styles[layer.id] = style;
if (!this.layers[layer['source-layer']]) {
@@ -136,9 +134,6 @@ class VectorTilesSource extends TMSSource {
console.warn('With VectorTilesSource and FeatureGeometryLayer, the accurate option is always false');
options.out.accurate = false;
}
- const keys = Object.keys(this.styles);
-
- keys.forEach((k) => { this.styles[k].parent = options.out.style; });
}
}
}
diff --git a/test/unit/3dtileslayerstyle.js b/test/unit/3dtileslayerstyle.js
index b9b9c9dd85..327b62a382 100644
--- a/test/unit/3dtileslayerstyle.js
+++ b/test/unit/3dtileslayerstyle.js
@@ -4,7 +4,6 @@ import * as THREE from 'three';
import { HttpsProxyAgent } from 'https-proxy-agent';
import Extent from 'Core/Geographic/Extent';
import PlanarView from 'Core/Prefab/PlanarView';
-import Style from 'Core/Style';
import C3DTBatchTable from 'Core/3DTiles/C3DTBatchTable';
import C3DTilesSource from 'Source/C3DTilesSource';
import C3DTilesLayer from 'Layer/C3DTilesLayer';
@@ -35,6 +34,27 @@ describe('3DTilesLayer Style', () => {
view,
);
+ $3dTilesLayer.style = {
+ fill: {
+ color: (c3DTileFeature) => {
+ if (c3DTileFeature.batchId > 1) {
+ return 'red';
+ } else {
+ return 'blue';
+ }
+ },
+ opacity: (c3DTileFeature) => {
+ if (c3DTileFeature.getInfo().something) {
+ return 0.1;
+ } else if (c3DTileFeature.userData.something === 'random') {
+ return 1;
+ } else {
+ return 0.5;
+ }
+ },
+ },
+ };
+
// Create a 'fake' tile content for this test purpose
const createTileContent = (tileId) => {
const geometry = new THREE.SphereGeometry(15, 32, 16);
@@ -61,28 +81,6 @@ describe('3DTilesLayer Style', () => {
return result;
};
- $3dTilesLayer.style = new Style({
- fill: {
- color: (c3DTileFeature) => {
- if (c3DTileFeature.batchId > 1) {
- return 'red';
- } else {
- return 'blue';
- }
- },
- opacity: (c3DTileFeature) => {
- if (c3DTileFeature.getInfo().something) {
- return 0.1;
- } else if (c3DTileFeature.userData.something === 'random') {
- return 1;
- } else {
- return 0.5;
- }
- },
- },
- });
-
-
it('Load tile content', function () {
for (let index = 0; index < 10; index++) {
const tileContent = createTileContent(index);
diff --git a/test/unit/feature2mesh.js b/test/unit/feature2mesh.js
index 60867dce2b..cb25096e31 100644
--- a/test/unit/feature2mesh.js
+++ b/test/unit/feature2mesh.js
@@ -3,7 +3,6 @@ import proj4 from 'proj4';
import assert from 'assert';
import GeoJsonParser from 'Parser/GeoJsonParser';
import Feature2Mesh from 'Converter/Feature2Mesh';
-import Style from 'Core/Style';
const geojson = require('../data/geojson/holes.geojson.json');
const geojson2 = require('../data/geojson/simple.geojson.json');
@@ -58,63 +57,66 @@ describe('Feature2Mesh', function () {
const parsed3 = GeoJsonParser.parse(geojson3, { in: { crs: 'EPSG:3946' }, out: { crs: 'EPSG:3946', buildExtent: true, mergeFeatures: false, structure: '3d' } });
it('rect mesh area should match geometry extent', function (done) {
- parsed.then((collection) => {
- const mesh = Feature2Mesh.convert()(collection).meshes;
- const extentSize = collection.extent.planarDimensions();
+ parsed
+ .then((collection) => {
+ const mesh = Feature2Mesh.convert()(collection).meshes;
+ const extentSize = collection.extent.planarDimensions();
- assert.equal(
- extentSize.x * extentSize.y,
- computeAreaOfMesh(mesh.children[0]));
- done();
- }).catch(done);
+ assert.equal(
+ extentSize.x * extentSize.y,
+ computeAreaOfMesh(mesh.children[0]));
+ done();
+ }).catch(done);
});
it('square mesh area should match geometry extent minus holes', function (done) {
- parsed.then((collection) => {
- const mesh = Feature2Mesh.convert()(collection).meshes;
+ parsed
+ .then((collection) => {
+ const mesh = Feature2Mesh.convert()(collection).meshes;
- const noHoleArea = computeAreaOfMesh(mesh.children[0]);
- const holeArea = computeAreaOfMesh(mesh.children[1]);
- const meshWithHoleArea = computeAreaOfMesh(mesh.children[2]);
+ const noHoleArea = computeAreaOfMesh(mesh.children[0]);
+ const holeArea = computeAreaOfMesh(mesh.children[1]);
+ const meshWithHoleArea = computeAreaOfMesh(mesh.children[2]);
- assert.equal(
- noHoleArea - holeArea,
- meshWithHoleArea);
- done();
- }).catch(done);
+ assert.equal(
+ noHoleArea - holeArea, meshWithHoleArea,
+ );
+ done();
+ }).catch(done);
});
it('convert points, lines and mesh', function (done) {
- parsed2.then((collection) => {
- const mesh = Feature2Mesh.convert()(collection).meshes;
- assert.equal(mesh.children[0].type, 'Points');
- assert.equal(mesh.children[1].type, 'LineSegments');
- assert.equal(mesh.children[2].type, 'Mesh');
- done();
- }).catch(done);
+ parsed2
+ .then((collection) => {
+ const mesh = Feature2Mesh.convert()(collection).meshes;
+ assert.equal(mesh.children[0].type, 'Points');
+ assert.equal(mesh.children[1].type, 'LineSegments');
+ assert.equal(mesh.children[2].type, 'Mesh');
+ done();
+ }).catch(done);
});
it('convert to instanced meshes', function (done) {
- const styleModel3D = new Style({
+ const styleModel3D = {
point: {
model: { object: makeTree() },
},
- });
- parsed3.then((collection) => {
- for (const feat of collection.features) { feat.style = styleModel3D; }
- const mesh = Feature2Mesh.convert()(collection).meshes;
+ };
+ parsed3
+ .then((collection) => {
+ const mesh = Feature2Mesh.convert({ style: styleModel3D })(collection).meshes;
- let isInstancedMesh = false;
- mesh.traverse((obj) => {
- if (obj.isInstancedMesh) {
- isInstancedMesh = true;
- return null;
- }
- },
- );
- assert.ok(isInstancedMesh);
- assert.equal(mesh.children.length, 3);
- done();
- }).catch(done);
+ let isInstancedMesh = false;
+ mesh.traverse((obj) => {
+ if (obj.isInstancedMesh) {
+ isInstancedMesh = true;
+ return null;
+ }
+ },
+ );
+ assert.ok(isInstancedMesh);
+ assert.equal(mesh.children.length, 3);
+ done();
+ }).catch(done);
});
});
diff --git a/test/unit/label.js b/test/unit/label.js
index 38e1571ec2..a0c5c7db6d 100644
--- a/test/unit/label.js
+++ b/test/unit/label.js
@@ -50,21 +50,22 @@ describe('Label', function () {
let label;
let style;
const c = new Coordinates('EPSG:4326');
+ const layerVT = {
+ type: 'symbol',
+ paint: {},
+ layout: {
+ 'icon-image': 'icon',
+ 'icon-size': 1,
+ 'text-field': 'label',
+ },
+ };
const sprites = {
img: '',
icon: { x: 0, y: 0, width: 10, height: 10 },
};
before('init style', function () {
- style = new Style();
- style.setFromVectorTileLayer({
- type: 'symbol',
- paint: {},
- layout: {
- 'icon-image': 'icon',
- 'icon-size': 1,
- },
- }, sprites);
+ style = new Style(Style.setFromVectorTileLayer(layerVT, sprites));
});
it('should throw errors for bad Label construction', function () {
@@ -72,9 +73,14 @@ describe('Label', function () {
assert.throws(() => { label = new Label('content'); });
});
- it('should correctly create Labels', function () {
- assert.doesNotThrow(() => { label = new Label('', c); });
- assert.doesNotThrow(() => { label = new Label(document.createElement('div'), c); });
+ describe('should correctly create Labels', function () {
+ it('with label from style', function () {
+ assert.doesNotThrow(() => { label = new Label('', c, style); });
+ assert.equal(label.content.textContent, layerVT.layout['text-field']);
+ });
+ it('from a DomElement', function () {
+ assert.doesNotThrow(() => { label = new Label(document.createElement('div'), c); });
+ });
});
it('should hide the DOM', function () {
diff --git a/test/unit/style.js b/test/unit/style.js
index 8193d17881..5a0dd312cc 100644
--- a/test/unit/style.js
+++ b/test/unit/style.js
@@ -1,4 +1,5 @@
import Style from 'Core/Style';
+import { FEATURE_TYPES } from 'Core/Feature';
import assert from 'assert';
import Fetcher from 'Provider/Fetcher';
import { TextureLoader } from 'three';
@@ -278,4 +279,84 @@ describe('Style', function () {
});
});
});
+
+ describe('setFromProperties', () => {
+ it('FEATURE_TYPES.POINT', () => {
+ const properties = {
+ radius: 2,
+ 'label-color': '#eba55f',
+ 'icon-color': '#eba55f',
+ };
+ const style = Style.setFromProperties(properties, { type: FEATURE_TYPES.POINT });
+ assert.equal(style.point.radius, 2);
+ assert.equal(style.text.color, '#eba55f');
+ assert.equal(style.icon.color, '#eba55f');
+ });
+ it('FEATURE_TYPES.POLYGON', () => {
+ const properties = {
+ fill: '#eba55f',
+ stroke: '#eba55f',
+ };
+ const style = Style.setFromProperties(properties, { type: FEATURE_TYPES.POLYGON });
+ assert.equal(style.stroke.color, '#eba55f');
+ assert.equal(style.fill.color, '#eba55f');
+ });
+ });
+
+ describe('setFromVectorTileLayer', () => {
+ describe("layer.type==='fill'", () => {
+ const imgId = 'filler';
+ const vectorTileLayer = {
+ type: 'fill',
+ paint: { 'fill-outline-color': '#eba55f' },
+ };
+ it('without fill-pattern (or sprites)', () => {
+ const style = Style.setFromVectorTileLayer(vectorTileLayer);
+ // fill-outline-color
+ assert.equal(style.stroke.color, '#eba55f');
+ });
+
+ it('with fill-pattern (and sprites)', () => {
+ vectorTileLayer.paint['fill-pattern'] = imgId;
+ const sprites = {
+ filler: { x: 0, y: 0, width: 0, height: 0, pixelRatio: 1 },
+ source: 'ImgUrl',
+ };
+ const style = Style.setFromVectorTileLayer(vectorTileLayer, sprites);
+ // fill-pattern
+ assert.equal(style.fill.pattern.id, imgId);
+ assert.equal(style.fill.pattern.cropValues, sprites[imgId]);
+ });
+ });
+ it("layer.type==='line'", () => {
+ const vectorTileLayer = {
+ type: 'line',
+ paint: {
+ 'line-color': '#eba55f',
+ },
+ };
+ const style = Style.setFromVectorTileLayer(vectorTileLayer);
+ assert.equal(style.stroke.color, '#eba55f');
+ });
+ it("layer.type==='circle'", () => {
+ const vectorTileLayer = {
+ type: 'circle',
+ paint: {
+ 'circle-color': '#eba55f',
+ },
+ };
+ const style = Style.setFromVectorTileLayer(vectorTileLayer);
+ assert.equal(style.point.color, '#eba55f');
+ });
+ it("layer.type==='symbol'", () => {
+ const vectorTileLayer = {
+ type: 'symbol',
+ layout: {
+ 'symbol-z-order': 'auto',
+ },
+ };
+ const style = Style.setFromVectorTileLayer(vectorTileLayer);
+ assert.equal(style.text.zOrder, 'Y');
+ });
+ });
});
diff --git a/test/unit/vectortiles.js b/test/unit/vectortiles.js
index 916296d80d..80d9366395 100644
--- a/test/unit/vectortiles.js
+++ b/test/unit/vectortiles.js
@@ -50,21 +50,21 @@ describe('Vector tiles', function () {
assert.equal(square2[1], square2[4 * size + 1]);
done();
- });
+ }).catch(done);
});
it('returns nothing', (done) => {
parse(null).then((collection) => {
assert.equal(collection, undefined);
done();
- });
+ }).catch(done);
});
it('filters all features out', (done) => {
parse(multipolygon, {}).then((collection) => {
assert.equal(collection.features.length, 0);
done();
- });
+ }).catch(done);
});
describe('VectorTilesSource', function () {
@@ -86,7 +86,7 @@ describe('Vector tiles', function () {
// eslint-disable-next-line no-template-curly-in-string
assert.equal(source.url, 'http://server.geo/${z}/${x}/${y}.pbf');
done();
- });
+ }).catch(done);
});
it('reads the background layer', (done) => {
@@ -100,7 +100,7 @@ describe('Vector tiles', function () {
source.whenReady.then(() => {
assert.ok(source.backgroundLayer);
done();
- });
+ }).catch(done);
});
it('creates styles and assigns filters', (done) => {
@@ -121,33 +121,7 @@ describe('Vector tiles', function () {
assert.ok(source.styles.land);
assert.equal(source.styles.land.fill.color, 'rgb(255,0,0)');
done();
- });
- });
-
- it('get style from context', (done) => {
- const source = new VectorTilesSource({
- url: 'fakeurl',
- style: {
- sources: { geojson: {} },
- layers: [{
- id: 'land',
- type: 'fill',
- paint: {
- 'fill-color': 'rgb(255, 0, 0)',
- 'fill-opacity': { stops: [[2, 1], [5, 0.5]] },
- },
- }],
- },
- });
- source.whenReady.then(() => {
- const styleLand_zoom_3 = source.styles.land.applyContext({ globals: { zoom: 3 }, properties: () => {} });
- const styleLand_zoom_5 = source.styles.land.applyContext({ globals: { zoom: 5 }, properties: () => {} });
- assert.equal(styleLand_zoom_3.fill.color, 'rgb(255,0,0)');
- assert.equal(styleLand_zoom_3.fill.opacity, 1);
- assert.equal(styleLand_zoom_5.fill.color, 'rgb(255,0,0)');
- assert.equal(styleLand_zoom_5.fill.opacity, 0.5);
- done();
- });
+ }).catch(done);
});
it('loads the style from a file', (done) => {
@@ -161,7 +135,7 @@ describe('Vector tiles', function () {
assert.equal(source.styles.land.zoom.min, 5);
assert.equal(source.styles.land.zoom.max, 13);
done();
- });
+ }).catch(done);
});
it('sets the correct Style#zoom.min', (done) => {
@@ -221,7 +195,7 @@ describe('Vector tiles', function () {
assert.equal(source.styles.fourth.zoom.min, 0);
assert.equal(source.styles.fifth.zoom.min, 3);
done();
- });
+ }).catch(done);
});
it('Vector tile source mapbox url', () => {