Skip to content
View on GitHubSample viewer app

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

screenshot

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

  1. Create a ContinuousField from a raster file.
  2. Create a ContinuousFieldFunction from the continuous field and mask values below sea level.
  3. Round elevation values down to the lowest 10-meter interval with map algebra operators ((continuousFieldFunction / 10).floor() * 10), and then convert the result to a DiscreteFieldFunction with .toDiscreteFieldFunction.
  4. Create BooleanFieldFunctions for each category by defining a range with map algebra operators such as isGreaterThanOrEqualTo, logicalAnd, and isLessThan.
  5. Create a new DiscreteField by chaining replaceIf operations into discrete category values and evaluating the the result with evaluate.
  6. Export the discrete field to files with exportToFiles and create a Raster with the result. Use it to create a RasterLayer.
  7. Apply a ColormapRenderer to 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

ApplyMapAlgebra.cppApplyMapAlgebra.cppApplyMapAlgebra.hApplyMapAlgebra.qml
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
// [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 workflow
void 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 helpers
void 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 helpers
QString 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 state
void 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();
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.