From efb91b65fe75b06fc5c6df974a04298365e130d1 Mon Sep 17 00:00:00 2001 From: Dan Cervelli Date: Tue, 2 Oct 2018 14:20:48 -0700 Subject: [PATCH] Add ImageSource#updateImage (#7342) --- debug/update_image.html | 69 ++++++++++++++++++++++++++ docs/pages/example/animate-images.html | 56 ++++++++++----------- src/source/image_source.js | 52 ++++++++++++++++--- test/unit/source/image_source.test.js | 47 ++++++++++++++++++ 4 files changed, 189 insertions(+), 35 deletions(-) create mode 100644 debug/update_image.html diff --git a/debug/update_image.html b/debug/update_image.html new file mode 100644 index 00000000000..9db8b8b54c6 --- /dev/null +++ b/debug/update_image.html @@ -0,0 +1,69 @@ + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + + + diff --git a/docs/pages/example/animate-images.html b/docs/pages/example/animate-images.html index 995499b7055..e035015cdc6 100644 --- a/docs/pages/example/animate-images.html +++ b/docs/pages/example/animate-images.html @@ -9,39 +9,37 @@ center: [-75.789, 41.874] }); -map.on('load', function() { +var frameCount = 5; +var currentImage = 0; + +function getPath() { + return "https://www.mapbox.com/mapbox-gl-js/assets/radar" + currentImage + ".gif"; +} - var frameCount = 5; - for (var i = 0; i < frameCount; i++) { +map.on('load', function() { - map.addLayer({ - id: 'radar' + i, - source: { - type: 'image', - url: 'https://www.mapbox.com/mapbox-gl-js/assets/radar' + i + '.gif', - coordinates: [ - [-80.425, 46.437], - [-71.516, 46.437], - [-71.516, 37.936], - [-80.425, 37.936] - ] - }, - type: 'raster', - paint: { - 'raster-opacity': 0, - 'raster-opacity-transition': { - duration: 0 - } - } - }); - } + map.addSource("radar", { + type: "image", + url: getPath(), + coordinates: [ + [-80.425, 46.437], + [-71.516, 46.437], + [-71.516, 37.936], + [-80.425, 37.936] + ] + }); + map.addLayer({ + id: "radar-layer", + "type": "raster", + "source": "radar", + "paint": { + "raster-fade-duration": 0 + } + }); - var frame = frameCount - 1; setInterval(function() { - map.setPaintProperty('radar' + frame, 'raster-opacity', 0); - frame = (frame + 1) % frameCount; - map.setPaintProperty('radar' + frame, 'raster-opacity', 1); + currentImage = (currentImage + 1) % frameCount; + map.getSource("radar").updateImage({ url: getPath() }); }, 200); - }); diff --git a/src/source/image_source.js b/src/source/image_source.js index 0306b98ba01..8b7d7ddc323 100644 --- a/src/source/image_source.js +++ b/src/source/image_source.js @@ -27,6 +27,8 @@ import type { VideoSourceSpecification } from '../style-spec/types'; +type Coordinates = [[number, number], [number, number], [number, number], [number, number]]; + /** * A data source containing an image. * (See the [Style Specification](https://www.mapbox.com/mapbox-gl-style-spec/#sources-image) for detailed documentation of options.) @@ -44,7 +46,7 @@ import type { * ] * }); * - * // update + * // update coordinates * var mySource = map.getSource('some id'); * mySource.setCoordinates([ * [-76.54335737228394, 39.18579907229748], @@ -52,7 +54,18 @@ import type { * [-76.5295386314392, 39.17683392507606], * [-76.54520273208618, 39.17876344106642] * ]); - * + * + * // update url and coordinates simultaneously + * mySource.updateImage({ + * url: 'https://www.mapbox.com/images/bar.png', + * coordinates: [ + * [-76.54335737228394, 39.18579907229748], + * [-76.52803659439087, 39.1838364847587], + * [-76.5295386314392, 39.17683392507606], + * [-76.54520273208618, 39.17876344106642] + * ] + * }) + * * map.removeSource('some id'); // remove * @see [Add an image](https://www.mapbox.com/mapbox-gl-js/example/image-on-a-map/) */ @@ -64,12 +77,12 @@ class ImageSource extends Evented implements Source { tileSize: number; url: string; - coordinates: [[number, number], [number, number], [number, number], [number, number]]; + coordinates: Coordinates; tiles: {[string]: Tile}; options: any; dispatcher: Dispatcher; map: Map; - texture: Texture; + texture: Texture | null; image: ImageData; centerCoord: Coordinate; tileID: CanonicalTileID; @@ -97,7 +110,7 @@ class ImageSource extends Evented implements Source { this.options = options; } - load() { + load(newCoordinates?: Coordinates, successCallback?: () => void) { this.fire(new Event('dataloading', {dataType: 'source'})); this.url = this.options.url; @@ -107,11 +120,38 @@ class ImageSource extends Evented implements Source { this.fire(new ErrorEvent(err)); } else if (image) { this.image = browser.getImageData(image); + if (newCoordinates) { + this.coordinates = newCoordinates; + } + if (successCallback) { + successCallback(); + } this._finishLoading(); } }); } + /** + * Updates the image URL and, optionally, the coordinates. To avoid having the image flash after changing, + * set the `raster-fade-duration` paint property on the raster layer to 0. + * + * @param {Object} options + * @param {string} [options.url] Required image URL. + * @param {Array>} [options.coordinates] Four geographical coordinates, + * represented as arrays of longitude and latitude numbers, which define the corners of the image. + * The coordinates start at the top left corner of the image and proceed in clockwise order. + * They do not have to represent a rectangle. + * @returns {ImageSource} this + */ + updateImage(options: {url: string, coordinates?: Coordinates}) { + if (!this.image || !options.url) { + return this; + } + this.options.url = options.url; + this.load(options.coordinates, () => { this.texture = null; }); + return this; + } + _finishLoading() { if (this.map) { this.setCoordinates(this.coordinates); @@ -133,7 +173,7 @@ class ImageSource extends Evented implements Source { * They do not have to represent a rectangle. * @returns {ImageSource} this */ - setCoordinates(coordinates: [[number, number], [number, number], [number, number], [number, number]]) { + setCoordinates(coordinates: Coordinates) { this.coordinates = coordinates; // Calculate which mercator tile is suitable for rendering the video in diff --git a/test/unit/source/image_source.test.js b/test/unit/source/image_source.test.js index a2b01313371..27cbaf780d4 100644 --- a/test/unit/source/image_source.test.js +++ b/test/unit/source/image_source.test.js @@ -81,6 +81,53 @@ test('ImageSource', (t) => { t.end(); }); + t.test('updates url from updateImage', (t) => { + const source = createSource({ url : '/image.png' }); + const map = new StubMap(); + const spy = t.spy(map, '_transformRequest'); + source.onAdd(map); + respond(); + t.ok(spy.calledOnce); + t.equal(spy.getCall(0).args[0], '/image.png'); + t.equal(spy.getCall(0).args[1], 'Image'); + source.updateImage({ url: '/image2.png' }); + respond(); + t.ok(spy.calledTwice); + t.equal(spy.getCall(1).args[0], '/image2.png'); + t.equal(spy.getCall(1).args[1], 'Image'); + t.end(); + }); + + t.test('sets coordinates', (t) => { + const source = createSource({ url : '/image.png' }); + const map = new StubMap(); + source.onAdd(map); + respond(); + const beforeSerialized = source.serialize(); + t.deepEqual(beforeSerialized.coordinates, [[0, 0], [1, 0], [1, 1], [0, 1]]); + source.setCoordinates([[0, 0], [-1, 0], [-1, -1], [0, -1]]); + const afterSerialized = source.serialize(); + t.deepEqual(afterSerialized.coordinates, [[0, 0], [-1, 0], [-1, -1], [0, -1]]); + t.end(); + }); + + t.test('sets coordinates via updateImage', (t) => { + const source = createSource({ url : '/image.png' }); + const map = new StubMap(); + source.onAdd(map); + respond(); + const beforeSerialized = source.serialize(); + t.deepEqual(beforeSerialized.coordinates, [[0, 0], [1, 0], [1, 1], [0, 1]]); + source.updateImage({ + url: '/image2.png', + coordinates: [[0, 0], [-1, 0], [-1, -1], [0, -1]] + }); + respond(); + const afterSerialized = source.serialize(); + t.deepEqual(afterSerialized.coordinates, [[0, 0], [-1, 0], [-1, -1], [0, -1]]); + t.end(); + }); + t.test('fires data event when content is loaded', (t) => { const source = createSource({ url : '/image.png' }); source.on('data', (e) => {