-
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
Support icon-color for non-SDF icons #3605
Comments
This would be great to support on |
Just in case this helps anyone -- I solved this issue on my map by using a custom icon font (I used http://fontastic.me but FontAwesome or anything else will work just the same). Just upload the font to mapbox studio and use the text-font, text-color, text-size, etc styling options for the geoJson layer and just use no icon. You can do some pretty awesome stuff with that and it has the added benefit of making it much easier to set up an icon baseline size and scale it up or down (vs my previously uploaded SVGs to mapbox studio which all ended up slightly different sizes). |
@rhagigi any chance you can post a full example / screenshot? |
Sure thing, I'll put something together when I get home. |
The way I'm achieving it is fairly simple. As I mentioned before, simply create a font using fontastic or another font generation service/build-tool, or use an existing icon font like fontawesome or material-icons. Then:
map.addSource('MySourceId', {
type: 'geojson',
data: geoJson
});
map.addLayer({
id: 'MyLayerId',
type: 'symbol',
source: 'MySourceId',
layout: {
'text-line-height': 1, // this is to avoid any padding around the "icon"
'text-padding': 0,
'text-anchor': 'bottom', // change if needed, "bottom" is good for marker style icons like in my screenshot,
'text-allow-overlap': true, // assuming you want this, you probably do
'text-field': iconString, // IMPORTANT SEE BELOW: -- this should be the unicode character you're trying to render as a string -- NOT the character code but the actual character,
'icon-optional': true, // since we're not using an icon, only text.
'text-font': ['FontAwesome Regular'], // see step 1 -- whatever the icon font name,
'text-size': 18 // or whatever you want -- dont know if this can be data driven...
},
paint: {
'text-translate-anchor': 'viewport', // up to you to change this -- see the docs
'text-color': '#00FF00' // whatever you want -- can even be data driven using a `{featureProperty}`,
}
}); For more layout/paint properties you can use (like pitch/rotation styling, etc) and more info, visit the Mapbox Style spec page for symbol layers
But, that can be hard when you don't know what the character will be in the font and you want to use the "classname" version for this, like This is kinda-font-awesome specific, as they load the character using CSS into the const getFontAwesomeStringFromClassname = _memoize((className) => {
const element = document.createElement('i');
element.className = 'fa ' + className; // font-awesome specific where `className`==="fa-calendar"
element.style.display = 'none'; // or not
document.body.appendChild(element);
const contentValue = window.getComputedStyle(
element, ':before'
).getPropertyValue('content');
document.body.removeChild(element);
return contentValue;
}); You can then pass that returned contentValue into Unfortunately, I'm not sure if mapbox-gl supports data-driven styling yet for font-size (they might now), so I just used multiple layers with filters on them to do dynamic sizing (e.g.: one layer size 40 and one layer size 20, with filters for the feature type). Hopefully that wasn't too much craziness and I didn't skip anything important. Let me know if you have any questions about all that. |
That's great Royi, thanks! |
We are using a similar strategy as well. I'd be curious if anyone (perhaps the mapbox folks) have any input on potential performance or stability concerns with this strategy. I know there is a lot of logic in the text placement logic to manage collisions, positioning in relation to the icon, etc. Our apps can result in a lot of data on the map, so performance quickly becomes a concern with everything we are doing (part of the appeal of mapbox-gl in the first place). |
We've solved this a different way! We've used circles and icons as separate layers ... see this:
This is what it looks like (if you want the icons inside the circles, change base size to 0.7): |
@rhagigi Thanks for the detailed description. I'm almost there. I'm finding that even though I have added FontAwesome (or Material Icons) as a font to my Mapbox style, and even though some version of that font gets downloaded by the mapbox component, no markers show up. If i use a font that I know exists like I believe the font isn't being properly downloaded, based on the network traffic: 56 bytes seems a bit short. The file is a PBF file that contains:
Any suggestion how to debug this? One hint might be that the font name isn't actually being displayed: (it'd be lovely if mapbox just published these two very common and openly licensed fonts) |
Hmm, reading on the API (https://www.mapbox.com/api-documentation/#fonts) it seems that API call is just getting a single glyph, which should be fine. Maybe it's just my character lookup isn't correct. |
Figured it out. The mapping from icon to a string was where I was failing. For the next people who run into this issue, here are the incantations to map from a font-awesome or Material Design Icons to a string that will fit in with @rhagigi's method above. For Font-Awesome, find the icon like, e.g. http://fontawesome.io/icon/camera/, you'll notice it says it is at Unicode location The string you want is therefore: For Material Icons, you'll notice in the instructions for Web Font under "IE usage" that the entity is That corresponds to using the string It's definitely a manual mapping, but it helped me out. |
I was running into an issue trying to use Glyphicons pro icon packs inside of an Ionic project using Mapbox GL. e.g 0xe592 is not a number so you get compilation errors. See: MDN FromCharCode - Getting it to work with higher values The solution was to actually use |
The biggest issue I have encountered using non-SDF icons is that |
@davidascher @rhagigi Trying your methods to use Google's Material Icons [I've also tried with Font Awesome] and I've not been able to get the icons to show up. I've tested the Material Icons on other parts of the page (ie: ) without problems and used your exact coding. The layer also works with the Maki icons. The icon is also showing up in console after using String.formCharCode(). @rhagigi Does this method only work using Mapbox Studio-hosted fonts? Or can I use the Google Material Icons CDN? |
For reference, here's the manual way to convert font for use as symbol text layer:
Hope this helps! |
@Kilatsat I can't reproduce your comment that |
@everhardt I should have been more clear in my previous response: it appears to respect a=0. I did not test semitransparent images. |
@Kilatsat I'm sorry, I'm still not clear on what you are saying. Do you say that it's possible to change the color of certain non-SDF images or are you saying that it is possible to make an image invisible (completely transparent, alpha = 0) by changing the I fail to achieve either, but I'm interested in the former.. |
@everhardt I can help you modify an existing example to illustrate the point I was originally making:
This modification causes the cat to appear |
@Kilatsat Thanks a lot, that is exactly what I was looking for! |
I have thoroughly investigated the issue. The problem is in the use of |
@rhagigi I opened a pull request, so hopefully this will be easier in the future: openmaptiles/fonts#9 To shown the icon you need to use the hex value as a string. Eg for a nice marker icon of Font Awesome 5 solid, try: "layout": {
'text-line-height': 1, // this is to avoid any padding around the "icon"
'text-padding': 0,
'text-anchor': 'center', // center, so when rotating the map, the "icon" stay on the same location
'text-offset': [0, -0.3], // give it a little offset on y, so when zooming it stay on the right place
'text-allow-overlap': true,
'text-ignore-placement': true,
'text-field': String.fromCharCode("0xF3C5"),
'icon-optional': true, // since we're not using an icon, only text.
'text-font': ['Font Awesome 5 Free Solid'],
'text-size': 35
},
"paint": {
'text-translate-anchor': 'viewport',
'text-color': ['get', 'color'] // get color from the properties geojson file !
} geojson data file should have atleast "color" property, eg: { ..., "properties":{"color": "#86EA66"}, "geometry": ..... |
Piggy-backing on this issue since I recently ran into a need for coloring icons based on data. Currently working around it by using multiple SVGs, though I like the font-awesome workaround suggested above. Coloring icons would be an excellent feature to add for the cost of a single multiply in our shader and 24 bits of color data. |
Why this possiblity is not documented? Me help this information a lot! |
For anyone reading this I have came across the following solution (haven't tried it yet), which is different than using font awsome characters (which I think is problematic if you read the style from a json file without replacing anything like I need to): |
@HarelM Are you asking if it's possible to set the URL to the spritesheet in a style definition? I think that should work, because the spritesheet.json file (example) includes |
@nickpeihl thanks for the info! |
Here is a simple docker image to convert a folder of
Repo : svgToSprite |
This is a very helpful work around, thanks @rhagigi!
For shame mapbox :/ |
I did an even simpler and muuuch more performant version of that:
Here is an Example that integrates nicely with Angular import { Injectable } from '@angular/core';
import { findIconDefinition, library, parse } from '@fortawesome/fontawesome-svg-core';
import { fas } from '@fortawesome/free-solid-svg-icons';
@Injectable({
providedIn: 'root'
})
export class MapService {
constructor() {
library.add(fas);
}
public fontAwesomeCharacterCode(iconName: string): string {
const parseIcon = parse.icon(iconName);
const icon = findIconDefinition(parseIcon)
return String.fromCharCode(parseInt(icon.icon[3], 16));
}
} Use it in a Layer this.mapComponent.mapInstance.addLayer({
id: 'font-awesome-icon',
type: 'symbol',
source: 'pois',
filter: [],
layout: {
'text-font': ['Font Awesome 6 Free Solid'],
'text-field': this.mapService.fontAwesomeCharacterCode('calendar') // or 'fa-calendar'
},
paint: {
'text-color': '#FFFFFF'
}
}); As for the performance. I measured 80_000 iterations with this code and it took only ~50ms. To be fair I measured your code without the usage of public measure() {
const startTimeInMs = new Date().getTime();
const icons = ['fa-heartbeat', 'fa-child', 'fa-medkit', 'fa-h-square', 'fa-crosshairs', 'fa-desktop', 'fa-cutlery', 'fa-adjust'];
for(let i = 0; i < 10_000; i++) {
for(const icon of icons) {
this.fontAwesomeCharacterCode(icon);
}
}
const endTimeInMs = new Date().getTime();
const durationInMs = endTimeInMs - startTimeInMs;
console.log(`myMethod took ${durationInMs} ms`);
} |
We have a use case that requires the ability to use icons for data-driven shapes as well as data-driven coloring of those icons. Currently we can get half of this functionality from the
circle
layer type and half from thesymbol
layer type. We have a hacked solution that merges the basic features of both on a fork of mapbox-gl-js (along with a style-spec and shader fork)...https://github.com/ivelander/mapbox-gl-js
It would be great to be able to get off this fork, something we could do if support was added for
icon-color
for non-SDF icons.The text was updated successfully, but these errors were encountered: