diff --git a/docs/pages/example/add-image-missing-generated.html b/docs/pages/example/add-image-missing-generated.html new file mode 100644 index 00000000000..30286b5a916 --- /dev/null +++ b/docs/pages/example/add-image-missing-generated.html @@ -0,0 +1,81 @@ +
+ + diff --git a/docs/pages/example/add-image-missing-generated.js b/docs/pages/example/add-image-missing-generated.js new file mode 100644 index 00000000000..df53999905a --- /dev/null +++ b/docs/pages/example/add-image-missing-generated.js @@ -0,0 +1,11 @@ +/*--- +title: Generate and add a missing icon to the map +description: Add a missing icon to the map that was generated at runtime. +tags: + - styles + - layers +pathname: /mapbox-gl-js/example/add-image-missing-generated/ +---*/ +import Example from '../../components/example'; +import html from './add-image-missing-generated.html'; +export default Example(html); diff --git a/src/render/image_manager.js b/src/render/image_manager.js index fee826e4155..a3672895d0a 100644 --- a/src/render/image_manager.js +++ b/src/render/image_manager.js @@ -2,6 +2,7 @@ import potpack from 'potpack'; +import { Event, Evented } from '../util/evented'; import { RGBAImage } from '../util/image'; import { ImagePosition } from './image_atlas'; import Texture from './texture'; @@ -33,7 +34,7 @@ const padding = 1; data-driven support for `*-pattern`, we'll likely use per-bucket pattern atlases, and that would be a good time to refactor this. */ -class ImageManager { +class ImageManager extends Evented { images: {[string]: StyleImage}; loaded: boolean; requestors: Array<{ids: Array, callback: Callback<{[string]: StyleImage}>}>; @@ -44,6 +45,7 @@ class ImageManager { dirty: boolean; constructor() { + super(); this.images = {}; this.loaded = false; this.requestors = []; @@ -115,6 +117,9 @@ class ImageManager { const response = {}; for (const id of ids) { + if (!this.images[id]) { + this.fire(new Event('styleimageneeded', { id })); + } const image = this.images[id]; if (image) { // Clone the image so that our own copy of its ArrayBuffer doesn't get transferred. diff --git a/src/style/style.js b/src/style/style.js index f78420f3518..53f3c8dcafb 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -136,6 +136,7 @@ class Style extends Evented { this.map = map; this.dispatcher = new Dispatcher(getWorkerPool(), this); this.imageManager = new ImageManager(); + this.imageManager.setEventedParent(this); this.glyphManager = new GlyphManager(map._transformRequest, options.localIdeographFontFamily); this.lineAtlas = new LineAtlas(256, 512); this.crossTileSymbolIndex = new CrossTileSymbolIndex(); diff --git a/src/ui/events.js b/src/ui/events.js index 82968ccf8ba..8111f4e184c 100644 --- a/src/ui/events.js +++ b/src/ui/events.js @@ -778,6 +778,20 @@ export type MapEvent = */ | 'sourcedataloading' + /** + * Fired when an icon or pattern needed by the style is missing. The missing image can + * be added with {@link Map#addImage} within this callback to prevent the image from + * being skipped. This event can be used to dynamically generate icons and patterns. + * + * @event styleimageneeded + * @memberof Map + * @instance + * @property {string} id The id of the missing image. + * + * @see [Generate and add a missing icon to the map](https://mapbox.com/mapbox-gl-js/example/add-image-missing-generated/) + */ + | 'styleimageneeded' + /** * @event style.load * @memberof Map diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 55fa4eb7b24..3292e715ede 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -1945,6 +1945,26 @@ test('Map', (t) => { t.end(); }); + t.test('map fires `styleimageneeded` for missing icons', (t) => { + const map = createMap(t); + + const id = "missing-image"; + + let called; + map.on('styleimageneeded', e => { + map.addImage(e.id, {width: 1, height: 1, data: new Uint8Array(4)}); + called = e.id; + }); + + t.notok(map.hasImage(id)); + + map.style.imageManager.getImages([id], () => { + t.equals(called, id); + t.ok(map.hasImage(id)); + t.end(); + }); + }); + t.end(); });