-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MapBox VectorTile: bug fixes using an official MapBox stream #2469
base: master
Are you sure you want to change the base?
Changes from all commits
06804f1
5c62908
f903a1a
328cc2c
a96f21b
d756acb
a881fac
b77965b
7f27ef8
c7a1c32
6d568fa
5f63ee2
af2e0f1
049dd6a
efa1777
36bcd33
151e9af
bb8f57d
18b1d2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,7 +69,6 @@ | |
} | ||
|
||
var kmlStyle = { | ||
zoom: { min: 10, max: 20 }, | ||
text: { | ||
field: '{name}', | ||
haloColor: 'black', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<html> | ||
<head> | ||
<title>Itowns - Globe</title> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update the title please? |
||
|
||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
|
||
<link | ||
rel="stylesheet" | ||
type="text/css" | ||
href="css/example.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
type="text/css" | ||
href="css/LoadingScreen.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
type="text/css" | ||
href="https://www.itowns-project.org/itowns/examples/css/widgets.css" | ||
/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't need widgets in this example |
||
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script> | ||
</head> | ||
<body> | ||
<div id="viewerDiv"></div> | ||
|
||
<!-- Import iTowns source code --> | ||
<script src="../dist/itowns.js"></script> | ||
<script src="../dist/debug.js"></script> | ||
<!-- Import iTowns Widgets plugin --> | ||
<script src="../dist/itowns_widgets.js"></script> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't need widgets in this example |
||
<!-- Import iTowns LoadingScreen and GuiTools plugins --> | ||
<script src="js/GUI/GuiTools.js"></script> | ||
<script src="js/GUI/LoadingScreen.js"></script> | ||
|
||
<script> | ||
const typeView = 'Planar'; | ||
// const typeView = 'Globe'; | ||
|
||
// `viewerDiv` will contain iTowns' rendering area (`<canvas>`) | ||
var viewerDiv = document.getElementById('viewerDiv'); | ||
let view; | ||
|
||
if (typeView === 'Globe') { | ||
const coord = new itowns.Coordinates('EPSG:4326', 2.351323, 48.856712); // Paris | ||
// const coord = new itowns.Coordinates("EPSG:4326", 60.599525, 56.834341); // Yekaterinburg | ||
const placement = { | ||
coord, | ||
range: 950000, | ||
}; | ||
view = new itowns.GlobeView(viewerDiv, placement); | ||
} else if (typeView === 'Planar') { | ||
// Define geographic extent: CRS, min/max X, min/max Y | ||
var extent = new itowns.Extent( | ||
'EPSG:3857', | ||
-20037508.342789244, 20037508.342789244, | ||
-20037508.342789255, 20037508.342789244); | ||
|
||
// Instanciate PlanarView | ||
view = new itowns.PlanarView(viewerDiv, extent, { | ||
maxSubdivisionLevel: 20, | ||
controls: { | ||
// We do not want the user to zoom out too much | ||
maxAltitude: 40000000, | ||
// We want to keep the rotation disabled, to only have a view from the top | ||
enableRotation: false, | ||
// Don't zoom too much on smart zoom | ||
smartTravelHeightMax: 100000, | ||
}, | ||
}); | ||
} | ||
|
||
const debugMenu = new GuiTools("menuDiv", view); | ||
setupLoadingScreen(viewerDiv, view); | ||
|
||
const source = new itowns.VectorTilesSource({ | ||
style: "mapbox://styles/mapbox/streets-v8", | ||
accessToken: | ||
"pk.eyJ1IjoiZ2VvdXJmbyIsImEiOiJjbHdyZWVrd2owOW1rMmlxdmx2Z3Fwb2JhIn0.sg5en26yZ6aSEg6CsfUByA", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is that your personal access token? |
||
}); | ||
|
||
const layer = new itowns.ColorLayer("vector-map", { | ||
source, | ||
noTextureParentOutsideLimit: true, | ||
addLabelLayer: true, | ||
}); | ||
|
||
view.addLayer(layer).then(() => { | ||
debugMenu.addLayerGUI.bind(debugMenu); | ||
itowns.ColorLayersOrdering.moveLayerToIndex(view, "vector-map", 15); | ||
}); | ||
|
||
debug.createTileDebugUI(debugMenu.gui, view); | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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.<br/> | ||
* 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you provide more details about why you remove it please? |
||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need for this comment I think, the code is clear enough |
||
// 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,29 +875,60 @@ 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]; | ||
}), | ||
}; | ||
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,29 +954,28 @@ 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we check for stroke properties too, like for fill ? |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we remove canBeFilled ? Its value is feature.type == FEATURE_TYPES.POLYGON . We are in a method named |
||
this._applyFillToPolygon(txtrCtx, invCtxScale, polygon, context); | ||
this._applyFillToPolygon(txtrCtx, invCtxScale, polygon); | ||
} | ||
} | ||
|
||
_applyStrokeToPolygon(txtrCtx, invCtxScale, polygon) { | ||
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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add it to the examples list please?