Apply map algebra to an elevation raster to floor, mask, and categorize the elevation values into discrete integer-based categories.

Use case
Categorizing raster data, such as elevation values, into distinct categories is a common spatial analysis workflow. This often involves applying threshold‑based logic or algebraic expressions to transform continuous numeric fields into discrete, integer‑based categories suitable for downstream analytical or computational operations. These operations can be specified and applied using map algebra.
How to use the sample
When the sample opens, it displays the source elevation raster. Select the Categorize button to generate a raster with three distinct ice age related geomorphological categories (raised shore line areas in blue, ice free high ground in brown and areas covered by ice in teal). After processing completes, use the radio buttons to switch between the map algebra results raster and the original elevation raster.
How it works
- Create a
ContinuousFieldfrom a raster file. - Create a
ContinuousFieldFunctionfrom the continuous field and mask values below sea level. - Round elevation values down to the lowest 10-meter interval with map algebra operators
((continuousFieldFunction / 10).floor() * 10), and then convert the result to aDiscreteFieldFunctionwith.toDiscreteFieldFunction. - Create
BooleanFieldFunctions for each category by defining a range with map algebra operators such asisGreaterThanOrEqualTo,logicalAnd, andisLessThan. - Create a new
DiscreteFieldby chainingreplaceIfoperations into discrete category values and evaluating the the result withevaluate. - Export the discrete field to files with
exportToFilesand create aRasterwith the result. Use it to create aRasterLayer. - Apply a
ColormapRendererto the raster and display it in the map view.
Relevant API
- BooleanFieldFunction
- Colormap
- ColormapRenderer
- ColorRamp
- ContinuousField
- ContinuousFieldFunction
- DiscreteField
- DiscreteFieldFunction
- Raster
- RasterLayer
- StretchRenderer
About the data
The sample uses a 10m resolution digital terrain elevation raster of the Isle of Arran, Scotland (Data Copyright Scottish Government and SEPA (2014)).
Tags
elevation, map algebra, raster, spatial analysis, terrain
Sample Code
// [WriteFile Name=ApplyMapAlgebra, Category=Layers]// [Legal]// Copyright 2026 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 "ApplyMapAlgebra.h"
// ArcGIS Maps SDK headers#include "BooleanFieldFunction.h"#include "ColorRamp.h"#include "Colormap.h"#include "ColormapRenderer.h"#include "ContinuousField.h"#include "ContinuousFieldFunction.h"#include "DiscreteField.h"#include "DiscreteFieldFunction.h"#include "LayerListModel.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "MinMaxStretchParameters.h"#include "Raster.h"#include "RasterLayer.h"#include "SpatialReference.h"#include "StretchRenderer.h"#include "Viewpoint.h"
// Qt headers#include <QDir>#include <QStandardPaths>
using namespace Esri::ArcGISRuntime;
namespace{ QString defaultDataPath() { QString dataPath;
#ifdef Q_OS_IOS dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);#else dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);#endif
return dataPath + "/ArcGIS/Runtime/Data/raster"; }} // namespace
ApplyMapAlgebra::ApplyMapAlgebra(QQuickItem* parent /* = nullptr */) : QQuickItem(parent), m_dataPath(defaultDataPath()){ connect(&m_createElevationFieldWatcher, &QFutureWatcher<ContinuousField*>::finished, this, [this]() { m_elevationField = m_createElevationFieldWatcher.result(); if (!m_elevationField) { setBusy(false); setErrorMessage(QStringLiteral("Failed to create a continuous field from the elevation raster.")); return; }
setDataAvailable(true); setErrorMessage(QString()); setBusy(false); });
connect(&m_createGeomorphicFieldWatcher, &QFutureWatcher<DiscreteField*>::finished, this, [this]() { m_geomorphicField = m_createGeomorphicFieldWatcher.result(); if (!m_geomorphicField) { setBusy(false); setErrorMessage(QStringLiteral("Failed to evaluate the geomorphic map algebra field.")); return; }
exportGeomorphicField(); });
connect(&m_exportGeomorphicFieldWatcher, &QFutureWatcher<QStringList>::finished, this, [this]() { const QStringList exportedFiles = m_exportGeomorphicFieldWatcher.result(); if (exportedFiles.isEmpty()) { setBusy(false); setErrorMessage(QStringLiteral("The geomorphic results could not be exported to a raster file.")); return; }
const QString& rasterFilePath = exportedFiles.constFirst(); if (!QFileInfo::exists(rasterFilePath)) { setBusy(false); setErrorMessage(QStringLiteral("The exported geomorphic raster file was not found.")); return; }
displayGeomorphicRaster(rasterFilePath); setResultsAvailable(true); setShowGeomorphicResults(true); setErrorMessage(QString()); setBusy(false); });}
ApplyMapAlgebra::~ApplyMapAlgebra() = default;
void ApplyMapAlgebra::init(){ // Register the map view for QML qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<ApplyMapAlgebra>("Esri.Samples", 1, 0, "ApplyMapAlgebraSample");}
void ApplyMapAlgebra::componentComplete(){ QQuickItem::componentComplete();
m_mapView = findChild<MapQuickView*>("mapView"); if (!m_mapView) { setErrorMessage(QStringLiteral("MapView was not found in the QML component tree.")); return; }
setupMap();}
void ApplyMapAlgebra::categorize(){ if (m_busy || !m_elevationField) { return; }
setBusy(true); setErrorMessage(QString()); createGeomorphicField();}
bool ApplyMapAlgebra::busy() const{ return m_busy;}
bool ApplyMapAlgebra::dataAvailable() const{ return m_dataAvailable;}
bool ApplyMapAlgebra::resultsAvailable() const{ return m_resultsAvailable;}
bool ApplyMapAlgebra::showGeomorphicResults() const{ return m_showGeomorphicResults;}
QString ApplyMapAlgebra::errorMessage() const{ return m_errorMessage;}
// Sample workflowvoid ApplyMapAlgebra::setupMap(){ m_map = new Map(BasemapStyle::ArcGISHillshadeDark, this); m_map->setInitialViewpoint(Viewpoint(55.584612, -5.234218, 300000)); m_mapView->setMap(m_map);
displayElevationRaster(); createElevationField();}
void ApplyMapAlgebra::createElevationField(){ const QString rasterPath = elevationRasterPath(); if (!QFileInfo::exists(rasterPath)) { setErrorMessage(QStringLiteral("The sample data was not found at %1.").arg(rasterPath)); return; }
setBusy(true); setErrorMessage(QString()); m_createElevationFieldWatcher.setFuture(ContinuousField::createFromFilesAsync({rasterPath}, 0, SpatialReference::wgs84(), this));}
void ApplyMapAlgebra::createGeomorphicField(){ ContinuousFieldFunction* continuousFieldFunction = ContinuousFieldFunction::create(m_elevationField, this); ContinuousFieldFunction* elevationFieldFunction = continuousFieldFunction->mask(continuousFieldFunction->isGreaterThanOrEqualTo(0.0));
DiscreteFieldFunction* tenMeterBinField = elevationFieldFunction->divide(10)->floor()->multiply(10)->toDiscreteFieldFunction();
BooleanFieldFunction* isRaisedShoreline = tenMeterBinField->isGreaterThanOrEqualTo(0)->logicalAnd(tenMeterBinField->isLessThan(10));
BooleanFieldFunction* isIceCovered = tenMeterBinField->isGreaterThanOrEqualTo(10)->logicalAnd(tenMeterBinField->isLessThan(600));
BooleanFieldFunction* isIceFreeHighGround = tenMeterBinField->isGreaterThanOrEqualTo(600);
DiscreteFieldFunction* geomorphicFieldFunction = tenMeterBinField->replaceIf(isRaisedShoreline, 1)->replaceIf(isIceCovered, 2)->replaceIf(isIceFreeHighGround, 3);
m_createGeomorphicFieldWatcher.setFuture(geomorphicFieldFunction->evaluateAsync(this));}
void ApplyMapAlgebra::exportGeomorphicField(){ // Raster layers display files, so the evaluated field is written to a temporary raster first. const QString outputDirectory = tempOutputDirectory(); clearPreviousExportedFiles(outputDirectory); m_exportGeomorphicFieldWatcher.setFuture(m_geomorphicField->exportToFilesAsync(outputDirectory, QStringLiteral("geomorphicCategorization")));}
// Display helpersvoid ApplyMapAlgebra::displayElevationRaster(){ const QString rasterPath = elevationRasterPath(); if (!QFileInfo::exists(rasterPath)) { setErrorMessage(QStringLiteral("The sample data was not found at %1.").arg(rasterPath)); return; }
Raster* raster = new Raster(rasterPath, this); m_elevationRasterLayer = new RasterLayer(raster, this);
const MinMaxStretchParameters stretchParameters({0.0}, {874.0}); StretchRenderer* stretchRenderer = new StretchRenderer(stretchParameters, {1.0}, false, ColorRamp::create(PresetColorRampType::Surface, 256, this), this);
m_elevationRasterLayer->setRenderer(stretchRenderer); m_elevationRasterLayer->setOpacity(0.5); m_map->operationalLayers()->append(m_elevationRasterLayer);}
void ApplyMapAlgebra::displayGeomorphicRaster(const QString& rasterFilePath){ if (m_geomorphicRasterLayer) { m_map->operationalLayers()->removeOne(m_geomorphicRasterLayer); m_geomorphicRasterLayer->deleteLater(); m_geomorphicRasterLayer = nullptr; }
Raster* raster = new Raster(rasterFilePath, this); m_geomorphicRasterLayer = new RasterLayer(raster, this);
const QMap<int, QColor> colors{ {1, QColor(QStringLiteral("#2f6db3"))}, {2, QColor(QStringLiteral("#73c7c7"))}, {3, QColor(QStringLiteral("#7d4a2d"))}, };
ColormapRenderer* colormapRenderer = new ColormapRenderer(Colormap::create(colors, this), this); m_geomorphicRasterLayer->setRenderer(colormapRenderer); m_geomorphicRasterLayer->setOpacity(0.5); m_map->operationalLayers()->append(m_geomorphicRasterLayer); updateVisibleRasterLayer();}
void ApplyMapAlgebra::updateVisibleRasterLayer(){ if (m_elevationRasterLayer) { m_elevationRasterLayer->setVisible(!m_showGeomorphicResults); }
if (m_geomorphicRasterLayer) { m_geomorphicRasterLayer->setVisible(m_showGeomorphicResults); }}
// File system helpersQString ApplyMapAlgebra::elevationRasterPath() const{ return m_dataPath + QStringLiteral("/arran.tif");}
QString ApplyMapAlgebra::tempOutputDirectory() const{ const QString basePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); QDir outputDirectory(basePath + QStringLiteral("/ApplyMapAlgebra")); outputDirectory.mkpath(QStringLiteral(".")); return outputDirectory.path();}
void ApplyMapAlgebra::clearPreviousExportedFiles(const QString& outputDirectory) const{ QDir directory(outputDirectory); const QStringList existingFiles = directory.entryList({QStringLiteral("geomorphicCategorization*")}, QDir::Files); for (const QString& fileName : existingFiles) { directory.remove(fileName); }}
// Internal statevoid ApplyMapAlgebra::setBusy(bool busy){ if (m_busy == busy) { return; }
m_busy = busy; emit busyChanged();}
void ApplyMapAlgebra::setDataAvailable(bool dataAvailable){ if (m_dataAvailable == dataAvailable) { return; }
m_dataAvailable = dataAvailable; emit dataAvailableChanged();}
void ApplyMapAlgebra::setResultsAvailable(bool resultsAvailable){ if (m_resultsAvailable == resultsAvailable) { return; }
m_resultsAvailable = resultsAvailable; emit resultsAvailableChanged();}
void ApplyMapAlgebra::setShowGeomorphicResults(bool showGeomorphicResults){ if (m_showGeomorphicResults == showGeomorphicResults) { return; }
m_showGeomorphicResults = showGeomorphicResults; updateVisibleRasterLayer(); emit showGeomorphicResultsChanged();}
void ApplyMapAlgebra::setErrorMessage(const QString& errorMessage){ if (m_errorMessage == errorMessage) { return; }
m_errorMessage = errorMessage; emit errorMessageChanged();}// [WriteFile Name=ApplyMapAlgebra, Category=Layers]// [Legal]// Copyright 2026 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 APPLYMAPALGEBRA_H#define APPLYMAPALGEBRA_H
// Qt headers#include <QFutureWatcher>#include <QQuickItem>
namespace Esri::ArcGISRuntime{ class ColormapRenderer; class ContinuousField; class DiscreteField; class Map; class MapQuickView; class RasterLayer;} // namespace Esri::ArcGISRuntime
class ApplyMapAlgebra : public QQuickItem{ Q_OBJECT
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(bool dataAvailable READ dataAvailable NOTIFY dataAvailableChanged) Q_PROPERTY(bool resultsAvailable READ resultsAvailable NOTIFY resultsAvailableChanged) Q_PROPERTY(bool showGeomorphicResults READ showGeomorphicResults WRITE setShowGeomorphicResults NOTIFY showGeomorphicResultsChanged) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
public: explicit ApplyMapAlgebra(QQuickItem* parent = nullptr); ~ApplyMapAlgebra() override;
static void init(); void componentComplete() override;
Q_INVOKABLE void categorize();
bool busy() const; bool dataAvailable() const; bool resultsAvailable() const; bool showGeomorphicResults() const; QString errorMessage() const;
signals: void busyChanged(); void dataAvailableChanged(); void resultsAvailableChanged(); void showGeomorphicResultsChanged(); void errorMessageChanged();
private: // Sample workflow void setupMap(); void createElevationField(); void createGeomorphicField(); void exportGeomorphicField();
// Display helpers void displayElevationRaster(); void displayGeomorphicRaster(const QString& rasterFilePath); void updateVisibleRasterLayer();
// Internal state void setBusy(bool busy); void setDataAvailable(bool dataAvailable); void setResultsAvailable(bool resultsAvailable); void setShowGeomorphicResults(bool showGeomorphicResults); void setErrorMessage(const QString& errorMessage);
// File system helpers QString elevationRasterPath() const; QString tempOutputDirectory() const; void clearPreviousExportedFiles(const QString& outputDirectory) const;
// Map and raster state Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::RasterLayer* m_elevationRasterLayer = nullptr; Esri::ArcGISRuntime::RasterLayer* m_geomorphicRasterLayer = nullptr; Esri::ArcGISRuntime::ContinuousField* m_elevationField = nullptr; Esri::ArcGISRuntime::DiscreteField* m_geomorphicField = nullptr;
// Async workflow QFutureWatcher<Esri::ArcGISRuntime::ContinuousField*> m_createElevationFieldWatcher; QFutureWatcher<Esri::ArcGISRuntime::DiscreteField*> m_createGeomorphicFieldWatcher; QFutureWatcher<QStringList> m_exportGeomorphicFieldWatcher;
// Local paths QString m_dataPath;
// UI state bool m_busy = false; bool m_dataAvailable = false; bool m_resultsAvailable = false; bool m_showGeomorphicResults = true; QString m_errorMessage;};
#endif // APPLYMAPALGEBRA_H// [WriteFile Name=ApplyMapAlgebra, Category=Layers]// [Legal]// Copyright 2026 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
ApplyMapAlgebraSample { id: root
// add a mapView component MapView { id: view anchors.fill: parent objectName: "mapView"
Component.onCompleted: { // Set and keep the focus on MapView to enable keyboard navigation forceActiveFocus(); } }
Rectangle { visible: !resultsAvailable anchors { top: parent.top right: parent.right margins: 16 } radius: 8 color: palette.base border.color: palette.dark
width: Math.min(280, parent.width - 32) implicitHeight: actionColumn.implicitHeight + 24
MouseArea { anchors.fill: parent onClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true }
ColumnLayout { id: actionColumn anchors.fill: parent anchors.margins: 12 spacing: 8
Label { text: qsTr("Create geomorphic categories from the Arran elevation raster.") wrapMode: Text.WordWrap Layout.fillWidth: true color: palette.text }
Button { text: busy ? qsTr("Map algebra computing...") : qsTr("Categorize") enabled: dataAvailable && !busy onClicked: categorize() } } }
Rectangle { visible: resultsAvailable anchors { top: parent.top right: parent.right margins: 16 } radius: 8 color: palette.base border.color: palette.dark
width: Math.min(280, parent.width - 32) height: resultsColumn.implicitHeight + 24
MouseArea { anchors.fill: parent onClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true }
ColumnLayout { id: resultsColumn anchors.fill: parent anchors.margins: 12 spacing: 8
Label { text: qsTr("Display") font.bold: true color: palette.text }
RadioButton { text: qsTr("Map algebra results") checked: showGeomorphicResults onToggled: if (checked) showGeomorphicResults = true }
RadioButton { text: qsTr("Original elevation raster") checked: !showGeomorphicResults onToggled: if (checked) showGeomorphicResults = false } } }
Rectangle { visible: errorMessage.length > 0 anchors { left: parent.left right: parent.right bottom: attributionLabel.top margins: 16 bottomMargin: 12 } radius: 8 color: Qt.rgba(0.35, 0.12, 0.12, 0.92) border.color: Qt.rgba(1, 1, 1, 0.15) implicitHeight: errorLabel.implicitHeight + 20
Label { id: errorLabel anchors.fill: parent anchors.margins: 10 text: errorMessage wrapMode: Text.WordWrap color: "white" }
MouseArea { anchors.fill: parent onClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true } }
Label { id: attributionLabel anchors { right: parent.right left: parent.left bottom: parent.bottom leftMargin: 16 rightMargin: 4 bottomMargin: 32 } horizontalAlignment: Text.AlignRight wrapMode: Text.WordWrap text: qsTr("Raster data Copyright Scottish Government and SEPA (2014)") color: Qt.rgba(1, 1, 1, 0.78) font.pixelSize: 11 font.italic: true }}// [WriteFile Name=ApplyMapAlgebra, Category=Layers]// [Legal]// Copyright 2026 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 "ApplyMapAlgebra.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlApplicationEngine>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
int main(int argc, char* argv[]){ QGuiApplication app(argc, argv); app.setApplicationName(QString("ApplyMapAlgebra"));
// 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 ApplyMapAlgebra::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/Layers/ApplyMapAlgebra/main.qml"));
return app.exec();}// Copyright 2026 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
ApplyMapAlgebra { anchors.fill: parent }}