-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Make image operator compatible with styleimagemissing event #8775
Changes from all commits
30ac880
273b146
b46932f
86bcb92
63d2ea4
9ae1733
2f6721c
181fea9
2d48c39
1a5b2ab
13cfbf7
cd63a78
bf9b06e
c936b93
a08d256
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 |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Mapbox GL JS debug page</title> | ||
<meta charset='utf-8'> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | ||
<link rel='stylesheet' href='../dist/mapbox-gl.css' /> | ||
<style> | ||
body { margin: 0; padding: 0; } | ||
html, body, #map { height: 100%; } | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<div id='map'></div> | ||
|
||
<script src='../dist/mapbox-gl-dev.js'></script> | ||
<script src='access_token_generated.js'></script> | ||
<script> | ||
|
||
var map = window.map = new mapboxgl.Map({ | ||
container: 'map', | ||
zoom: 4, | ||
center: [-96, 37.8], | ||
style: 'mapbox://styles/mapbox/light-v10', | ||
hash: true | ||
}); | ||
|
||
map.on('load', function () { | ||
map.addLayer({ | ||
"id": "points", | ||
"type": "symbol", | ||
"source": { | ||
"type": "geojson", | ||
"data": { | ||
"type": "FeatureCollection", | ||
"features": [ | ||
{ | ||
"type": "Feature", | ||
"geometry": { | ||
"type": "Point", | ||
"coordinates": [ | ||
-77.03238901390978, | ||
38.913188059745586 | ||
] | ||
}, | ||
"properties": { | ||
"icon": "monument", | ||
"title": "Mapbox DC" | ||
} | ||
}, { | ||
"type": "Feature", | ||
"geometry": { | ||
"type": "Point", | ||
"coordinates": [ | ||
-122.414, | ||
37.776 | ||
] | ||
}, | ||
"properties": { | ||
"title": "Mapbox SF", | ||
"icon": "harbor" | ||
} | ||
} | ||
] | ||
} | ||
}, | ||
"layout": { | ||
"icon-image": ["coalesce", ["image", "foo"], ["image", "bar"], ["image", ["concat", ["get", "icon"], "-15"]], ["image", "baz"]], | ||
"text-field": "{title}", | ||
"text-font": [ | ||
"Open Sans Semibold", | ||
"Arial Unicode MS Bold" | ||
], | ||
"text-offset": [ | ||
0, | ||
0.6 | ||
], | ||
"text-anchor": "top" | ||
} | ||
}); | ||
}); | ||
</script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -378,7 +378,11 @@ class SymbolBucket implements Bucket { | |
const hasText = | ||
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) && | ||
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0); | ||
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.toString().length > 0; | ||
// we should always resolve the icon-image value if the property was defined in the style | ||
// this allows us to fire the styleimagemissing event if image evaluation returns null | ||
// the only way to distinguish between null returned from a coalesce statement with no valid images | ||
// and null returned because icon-image wasn't defined is to check whether or not iconImage.parameters is an empty object | ||
const hasIcon = iconImage.value.kind !== 'constant' || !!iconImage.value.value || Object.keys(iconImage.parameters).length > 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. Part of this comment explains the high-level reasoning for retuning an object from the This comment seems less of a clarification of what the code does here. Maybe replace it with:
|
||
const symbolSortKey = layout.get('symbol-sort-key'); | ||
|
||
this.features = []; | ||
|
@@ -415,9 +419,13 @@ class SymbolBucket implements Bucket { | |
// but plain string token evaluation skips that pathway so do the | ||
// conversion here. | ||
const resolvedTokens = layer.getValueAndResolveTokens('icon-image', feature, availableImages); | ||
icon = resolvedTokens instanceof ResolvedImage ? | ||
resolvedTokens : | ||
ResolvedImage.fromString(resolvedTokens); | ||
if (resolvedTokens instanceof ResolvedImage) { | ||
icon = resolvedTokens; | ||
} else if (!resolvedTokens || typeof resolvedTokens === 'string') { | ||
icon = ResolvedImage.fromString({name: resolvedTokens, available: false}); | ||
} else { | ||
icon = ResolvedImage.fromString(resolvedTokens); | ||
} | ||
} | ||
|
||
if (!text && !icon) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,8 +53,21 @@ class Coalesce implements Expression { | |
|
||
evaluate(ctx: EvaluationContext) { | ||
let result = null; | ||
let argCount = 0; | ||
let requestedImageName; | ||
for (const arg of this.args) { | ||
argCount++; | ||
result = arg.evaluate(ctx); | ||
// we need to keep track of the first requested image in a coalesce statement | ||
// if coalesce can't find a valid image, we return the first image name so styleimagemissing can fire | ||
if (arg.type.kind === 'image' && !result.available) { | ||
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. @ryanhamley Why type of arg is checked? I think it should it be type of coalesce itself and then check if evaluated result is an image. 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. Changing the check to |
||
if (!requestedImageName) requestedImageName = arg.evaluate(ctx).name; | ||
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. @ryanhamley @asheemmamoowala arg is already evaluated on line 60 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. I think this is an oversight which can be corrected |
||
result = null; | ||
if (argCount === this.args.length) { | ||
result = requestedImageName; | ||
} | ||
} | ||
|
||
if (result !== null) break; | ||
} | ||
return result; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,25 @@ | ||
// @flow | ||
|
||
export type ResolvedImageOptions = { | ||
name: string, | ||
available: boolean | ||
}; | ||
|
||
export default class ResolvedImage { | ||
name: string; | ||
available: boolean; | ||
|
||
constructor(name: string) { | ||
this.name = name; | ||
constructor(options: ResolvedImageOptions) { | ||
this.name = options.name; | ||
this.available = options.available; | ||
} | ||
|
||
toString(): string { | ||
return this.name; | ||
} | ||
|
||
static fromString(imageName: string): ResolvedImage { | ||
return new ResolvedImage(imageName); | ||
static fromString(options: ResolvedImageOptions): ResolvedImage { | ||
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. Nit: Why do we have 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. Hmm, that's a good point. It's used in other places where it might be less straightforward to use the constructor though (https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/expression/definitions/coercion.js#L105). I'm inclined to leave this as is though because it follows the pattern of other expression types and this type and operator will likely be expanded significantly in the future as we add more image manipulation operators. |
||
return new ResolvedImage(options); | ||
} | ||
|
||
serialize(): Array<mixed> { | ||
|
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.
@asheemmamoowala @ryanhamley If min is null => patterns[null] = true?
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.
Wasn't
null
a possible result here even before this change?I can't remember exactly why I changed this now off the top of my head, but I think the ternary operator might have been more of a defensive guard. I'm not actually sure that
min
can ever benull
at this point because any value supplied to*-pattern
properties will be coerced to a ResolvedImage type. If you tried to just doline-pattern: null
or something, you'd get a type error before it got to this point. But maybe there's a pathway I'm not thinking about/a better way to handle this to ensure we don't getpatterns[null]
.