Animate a series of images with an image overlay.

Use case
An image overlay is useful for displaying fast and dynamic images; for example, rendering real-time sensor data captured from a drone. Each frame from the drone becomes a static image which is updated on the fly as the data is made available.
How to use the sample
The application loads a map of the Southwestern United States. Click the “Start” or “Stop” buttons to start or stop the radar animation. Use the drop down menu to select how quickly the animation plays. Move the slider to change the opacity of the image overlay.
How it works
- Create an
ImageOverlayand add it to theSceneView. - Set up a timer with an initial interval time of 68ms, which will display approximately 15
ImageFrames per second. - Connect to the timeout signal from the timer.
- Create a new image frame every timeout and set it on the image overlay.
Relevant API
- ImageFrame
- ImageOverlay
- SceneView
Offline data
| Link | Local Location |
|---|---|
| Pacific South West Images | <userhome>/ArcGIS/Runtime/Data/3D/ImageOverlay/PacificSouthWest.zip |
About the data
These radar images were captured by the US National Weather Service (NWS). They highlight the Pacific Southwest sector which is made up of part the western United States and Mexico. For more information visit the National Weather Service website.
Additional information
The supported image formats are GeoTIFF, TIFF, JPEG, and PNG. ImageOverlay does not support the rich processing and rendering capabilities of a RasterLayer. Use Raster and RasterLayer for static image rendering, analysis, and persistence.
Tags
3d, animation, drone, dynamic, image frame, image overlay, real time, rendering
Sample Code
// [WriteFile Name=AnimateImagesWithImageOverlay, Category=Scenes]// [Legal]// Copyright 2020 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifdef PCH_BUILD#include "pch.hpp"#endif // PCH_BUILD
// sample headers#include "AnimateImagesWithImageOverlay.h"
// ArcGIS Maps SDK headers#include "ArcGISTiledElevationSource.h"#include "ArcGISTiledLayer.h"#include "Camera.h"#include "ElevationSourceListModel.h"#include "Envelope.h"#include "ImageFrame.h"#include "ImageOverlay.h"#include "ImageOverlayListModel.h"#include "LayerListModel.h"#include "MapTypes.h"#include "Point.h"#include "Scene.h"#include "SceneQuickView.h"#include "SceneView.h"#include "SpatialReference.h"#include "Surface.h"#include "Viewpoint.h"
// Qt headers#include <QDir>#include <QFile>#include <QImage>#include <QStandardPaths>#include <QString>#include <QTimer>#include <QtCore/qglobal.h>
// STL headers#include <memory>
using namespace Esri::ArcGISRuntime;
// helper method to get cross platform data pathnamespace{QString defaultDataPath(){ QString dataPath;
#ifdef Q_OS_IOS dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);#else dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);#endif
return dataPath;}} // namespace
using namespace Esri::ArcGISRuntime;
AnimateImagesWithImageOverlay::AnimateImagesWithImageOverlay(QObject* parent /* = nullptr */): QObject(parent), // Create a scene using the dark gray basemap m_scene(new Scene(BasemapStyle::ArcGISDarkGray, this)), m_dataPath(defaultDataPath() + "/ArcGIS/Runtime/Data/3D/ImageOverlay/PacificSouthWest"), m_dir(m_dataPath), m_images(m_dir.entryList(QStringList() << "*.png", QDir::Files)), m_imagesSize(m_images.size()){ // create a new elevation source from Terrain3D REST service ArcGISTiledElevationSource* elevationSource = new ArcGISTiledElevationSource( QUrl("https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"), this);
// add the elevation source to the scene to display elevation m_scene->baseSurface()->elevationSources()->append(elevationSource);
}
AnimateImagesWithImageOverlay::~AnimateImagesWithImageOverlay() = default;
void AnimateImagesWithImageOverlay::init(){ // Register classes for QML qmlRegisterType<SceneQuickView>("Esri.Samples", 1, 0, "SceneView"); qmlRegisterType<AnimateImagesWithImageOverlay>("Esri.Samples", 1, 0, "AnimateImagesWithImageOverlaySample");}
SceneQuickView* AnimateImagesWithImageOverlay::sceneView() const{ return m_sceneView;}
// Set the view (created in QML)void AnimateImagesWithImageOverlay::setSceneView(SceneQuickView* sceneView){ if (!sceneView || sceneView == m_sceneView) return;
m_sceneView = sceneView; m_sceneView->setArcGISScene(m_scene);
emit sceneViewChanged();
setupImageOverlay();}
void AnimateImagesWithImageOverlay::setupImageOverlay(){ // create an envelope of the pacific southwest sector for displaying the image frame const Point pointForImageFrame(-120.0724273439448, 35.131016955536694, SpatialReference(4326)); m_pacificSouthwestEnvelope = Envelope(pointForImageFrame, 15.09589635986124, -14.3770441522488);
// create a camera, looking at the pacific southwest sector const Point observationPoint(-116.621, 24.7773, 856977); const Camera camera(observationPoint, 353.994, 48.5495, 0); const Viewpoint pacificSouthwestViewpoint(observationPoint, camera);
m_scene->setInitialViewpoint(pacificSouthwestViewpoint);
// create an image overlay m_imageOverlay = new ImageOverlay(this);
// append the image overlay to the scene view m_sceneView->imageOverlays()->append(m_imageOverlay);
// Create new Timer and set the timeout interval to 68ms m_timer = new QTimer(this); m_timer->setInterval(68);
// connect to the timeout signal to load and display a new image frame each time connect(m_timer, &QTimer::timeout, this, &AnimateImagesWithImageOverlay::animateImageFrames);}
void AnimateImagesWithImageOverlay::animateImageFrames(){ // check if string list of images is empty before trying to load and animate them if (m_imagesSize == 0) return;
// create an image with the given path and use it to create an image frame const QImage image(m_dataPath + "/" + m_images[m_index]);
// use std::unqiue_ptr to handle the lifetime of the image frame std::unique_ptr<ImageFrame> imageFrame = std::make_unique<ImageFrame>(image, m_pacificSouthwestEnvelope);
// set image frame to image overlay m_imageOverlay->setImageFrame(imageFrame.get());
// increment the index to keep track of which image to load next m_index++;
// reset index once all files have been loaded if (m_index == m_imagesSize) m_index = 0;}
void AnimateImagesWithImageOverlay::setTimerInterval(int value){ if (!m_timer) return;
m_timer->setInterval(value);}void AnimateImagesWithImageOverlay::setOpacity(float value){ if (!m_imageOverlay) return;
m_imageOverlay->setOpacity(value);}
void AnimateImagesWithImageOverlay::startTimer(){ if (!m_timer) return;
m_isStopped = false; emit isStoppedChanged(); m_timer->start();}
void AnimateImagesWithImageOverlay::stopTimer(){ if (!m_timer) return;
m_isStopped = true; emit isStoppedChanged(); m_timer->stop();}// [WriteFile Name=AnimateImagesWithImageOverlay, Category=Scenes]// [Legal]// Copyright 2020 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
#ifndef ANIMATEIMAGESWITHIMAGEOVERLAY_H#define ANIMATEIMAGESWITHIMAGEOVERLAY_H
// ArcGIS Maps SDK headers#include "Envelope.h"
// Qt headers#include <QDir>#include <QObject>
namespace Esri::ArcGISRuntime{class Scene;class SceneQuickView;class ImageFrame;class ImageOverlay;}
class QTimer;
Q_MOC_INCLUDE("SceneQuickView.h")
class AnimateImagesWithImageOverlay : public QObject{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::SceneQuickView* sceneView READ sceneView WRITE setSceneView NOTIFY sceneViewChanged) Q_PROPERTY(bool isStopped MEMBER m_isStopped NOTIFY isStoppedChanged)
public: explicit AnimateImagesWithImageOverlay(QObject* parent = nullptr); ~AnimateImagesWithImageOverlay();
static void init();
Q_INVOKABLE void setTimerInterval(int value); Q_INVOKABLE void setOpacity(float value); Q_INVOKABLE void startTimer(); Q_INVOKABLE void stopTimer();
signals: void sceneViewChanged(); void isStoppedChanged();
private slots: void animateImageFrames();
private: Esri::ArcGISRuntime::SceneQuickView* sceneView() const; void setSceneView(Esri::ArcGISRuntime::SceneQuickView* sceneView); void setupImageOverlay();
Esri::ArcGISRuntime::Envelope m_pacificSouthwestEnvelope; Esri::ArcGISRuntime::ImageOverlay* m_imageOverlay = nullptr; Esri::ArcGISRuntime::Scene* m_scene = nullptr; Esri::ArcGISRuntime::SceneQuickView* m_sceneView = nullptr;
int m_index = 0; bool m_isStopped = true; QTimer* m_timer = nullptr; const QString m_dataPath = ""; const QDir m_dir; const QStringList m_images; const int m_imagesSize = 0;};
#endif // ANIMATEIMAGESWITHIMAGEOVERLAY_H// [WriteFile Name=AnimateImagesWithImageOverlay, Category=Scenes]// [Legal]// Copyright 2020 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
import QtQuickimport QtQuick.Controlsimport QtQuick.Layoutsimport Esri.Samples
Item { property var timerIntervals: [17,34,68] property var imageFrameRefreshRate: ["Fast","Medium","Slow"]
SceneView { id: view anchors.fill: parent
Component.onCompleted: { // Set the focus on SceneView to initially enable keyboard navigation forceActiveFocus(); }
Rectangle { id: controlsRect anchors { bottom: view.attributionTop horizontalCenter: parent.horizontalCenter } width: childrenRect.width height: childrenRect.height color: "lightgrey" opacity: 0.8 radius: 5 ColumnLayout { id: controlsLayout Layout.alignment: Qt.AlignBottom spacing: 0
Slider { id: opacitySlider from: 0 to: 1 value: 1 Layout.preferredHeight: bottomRowControls.height Layout.alignment: Qt.AlignHCenter
onMoved: model.setOpacity(value);
// Custom slider handle that displays the current value handle: Item { x: parent.leftPadding + parent.visualPosition * (parent.availableWidth - headingHandleNub.width) y: parent.topPadding + parent.availableHeight / 2 - headingHandleNub.height / 2
Rectangle { id: headingHandleNub color: headingHandleRect.color radius: width * 0.5 width: 20 height: width } Rectangle { id: headingHandleRect height: childrenRect.height width: childrenRect.width radius: 3 x: headingHandleNub.x - width / 2 + headingHandleNub.width / 2 y: headingHandleNub.y - height
Text { id: headingValue font.pixelSize: 14 padding: 3 horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter text: "opacity: " + (opacitySlider.value).toFixed(2) } } } }
RowLayout { id: bottomRowControls spacing: 2 Button { id: startStopBtn text: model.isStopped ? qsTr("Start") : qsTr("Stop") Layout.minimumWidth: timerIntervalComboBox.width onClicked: { if (model.isStopped) { model.startTimer(); } else { model.stopTimer(); } } }
ComboBox { id: timerIntervalComboBox currentIndex: 2 model: imageFrameRefreshRate Layout.minimumWidth: startStopBtn.width
onActivated: { model.setTimerInterval(timerIntervals[currentIndex]); } } } } } }
// Declare the C++ instance which creates the scene etc. and supply the view AnimateImagesWithImageOverlaySample { id: model sceneView: view }}// [Legal]// Copyright 2020 Esri.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.// [Legal]
// sample headers#include "AnimateImagesWithImageOverlay.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QDir>#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QSurfaceFormat>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
int main(int argc, char *argv[]){ Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setUseLegacyAuthentication(false);#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) // Linux requires 3.2 OpenGL Context // in order to instance 3D symbols QSurfaceFormat fmt = QSurfaceFormat::defaultFormat(); fmt.setVersion(3, 2); QSurfaceFormat::setDefaultFormat(fmt);#endif
QGuiApplication app(argc, argv); app.setApplicationName(QString("AnimateImagesWithImageOverlay"));
// Use of ArcGIS location services, such as basemap styles, geocoding, and routing services, // requires an access token. For more information see // https://links.esri.com/arcgis-runtime-security-auth.
// The following methods grant an access token:
// 1. User authentication: Grants a temporary access token associated with a user's ArcGIS account. // To generate a token, a user logs in to the app with an ArcGIS account that is part of an // organization in ArcGIS Online or ArcGIS Enterprise.
// 2. API key authentication: Get a long-lived access token that gives your application access to // ArcGIS location services. Go to the tutorial at https://links.esri.com/create-an-api-key. // Copy the API Key access token.
const QString accessToken = QString("");
if (accessToken.isEmpty()) { qWarning() << "Use of ArcGIS location services, such as the basemap styles service, requires" << "you to authenticate with an ArcGIS account or set the API Key property."; } else { Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setApiKey(accessToken); }
// Initialize the sample AnimateImagesWithImageOverlay::init();
// Initialize application view QQmlApplicationEngine engine; // Add the import Path engine.addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml"));
#ifdef ARCGIS_RUNTIME_IMPORT_PATH_2 engine.addImportPath(ARCGIS_RUNTIME_IMPORT_PATH_2);#endif
// Set the source engine.load(QUrl("qrc:/Samples/Scenes/AnimateImagesWithImageOverlay/main.qml"));
return app.exec();}// Copyright 2020 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import QtQuick.Controlsimport Esri.Samples
ApplicationWindow { visible: true width: 800 height: 600
AnimateImagesWithImageOverlay { anchors.fill: parent }}