Skip to content
View on GitHub

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

Apply map algebra sample

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. Tap 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 toggles 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

apply_map_algebra.dart
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
// 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
//
//   https://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 'dart:io';

import 'package:arcgis_maps/arcgis_maps.dart';
import 'package:arcgis_maps_sdk_flutter_samples/common/common.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:path_provider/path_provider.dart';

class ApplyMapAlgebra extends StatefulWidget {
  const ApplyMapAlgebra({super.key});

  @override
  State<ApplyMapAlgebra> createState() => _ApplyMapAlgebraState();
}

class _ApplyMapAlgebraState extends State<ApplyMapAlgebra>
    with SampleStateSupport {
  // Create a controller for the map view.
  final _mapViewController = ArcGISMapView.createController();

  // The elevation file used in the analysis.
  late File _elevationFile;

  // Raster layers to represent the original elevation raster and the results of the analysis.
  RasterLayer? _originalElevationRasterLayer;
  RasterLayer? _resultsRasterLayer;

  // A variable to manage the active raster layer.
  RasterLayer? _selectedRasterLayer;

  // Variables to manage the UI.
  var _isPerformingAnalysis = false;
  var _hasAnalysisResults = false;

  // A flag for when the map view is ready and controls can be used.
  var _ready = false;

  @override
  void initState() {
    // Get the elevation data used in the sample.
    final listPaths = GoRouter.of(context).state.extra! as List<String>;
    _elevationFile = File(listPaths.first);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        top: false,
        left: false,
        right: false,
        child: Stack(
          children: [
            Column(
              children: [
                Expanded(
                  // Add a map view to the widget tree and set a controller.
                  child: ArcGISMapView(
                    controllerProvider: () => _mapViewController,
                    onMapViewReady: onMapViewReady,
                  ),
                ),
                // A button to start performing the analysis.
                Visibility(
                  visible: !_hasAnalysisResults,
                  child: ElevatedButton(
                    onPressed: _isPerformingAnalysis ? null : performAnalysis,
                    child: _isPerformingAnalysis
                        ? const Text('Map algebra computing...')
                        : const Text('Categorize'),
                  ),
                ),
                // Controls for toggling the active raster layer in the map.
                Visibility(
                  visible: _hasAnalysisResults,
                  child: Padding(
                    padding: const EdgeInsets.all(10),
                    child: Column(
                      children: [
                        Row(
                          children: [
                            Expanded(
                              // Sets the original elevation raster layer as active.
                              child: ListTile(
                                leading: Icon(
                                  _selectedRasterLayer ==
                                          _originalElevationRasterLayer
                                      ? Icons.radio_button_checked
                                      : Icons.radio_button_unchecked,
                                  color: Colors.grey,
                                ),
                                title: const Text('Original elevation raster'),
                                dense: true,
                                onTap: () => selectRasterLayer(
                                  _originalElevationRasterLayer!,
                                ),
                              ),
                            ),
                            Expanded(
                              // Sets the results raster layer as active.
                              child: ListTile(
                                leading: Icon(
                                  _selectedRasterLayer == _resultsRasterLayer
                                      ? Icons.radio_button_checked
                                      : Icons.radio_button_unchecked,
                                  color: Colors.grey,
                                ),
                                title: const Text('Map algebra results raster'),
                                dense: true,
                                onTap: () =>
                                    selectRasterLayer(_resultsRasterLayer!),
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 10),
                Text(
                  'Raster data Copyright Scottish Government and SEPA (2014)',
                  style: Theme.of(
                    context,
                  ).textTheme.bodySmall?.copyWith(color: Colors.grey),
                ),
              ],
            ),
            // Display a progress indicator and prevent interaction until state is ready.
            LoadingIndicator(visible: !_ready),
          ],
        ),
      ),
    );
  }

  Future<void> onMapViewReady() async {
    // Create a map with the hillshade dark basemap style.
    final map = ArcGISMap.withBasemapStyle(BasemapStyle.arcGISHillshadeDark);
    // Set an initial viewpoint over the Isle of Arran, Scotland.
    map.initialViewpoint = Viewpoint.withLatLongScale(
      latitude: 55.584612,
      longitude: -5.234218,
      scale: 500000,
    );

    // Create a raster from a raster elevation file.
    final raster = Raster.withFileUri(_elevationFile.uri);
    // Create a raster layer from the raster.
    _originalElevationRasterLayer = RasterLayer.withRaster(raster);

    // Create a stretch renderer to visualize the elevation raster layer using the surface preset color ramp.

    // Set up stretch parameters with min and max values of elevation data.
    final stretchParams = MinMaxStretchParameters(
      minValues: [0, 0],
      maxValues: [874.0],
    );

    // Create a color ramp with the surface type and define a size.
    final colorRamp = ColorRamp(type: PresetColorRampType.surface, size: 256);
    // Define the renderer using the parameters and color ramp.
    final stretchRenderer = StretchRenderer(
      stretchParameters: stretchParams,
      gammas: [1.0],
      estimateStatistics: false,
      colorRamp: colorRamp,
    );

    // Apply the renderer to the raster layer and apply an opacity.
    _originalElevationRasterLayer!.renderer = stretchRenderer;
    _originalElevationRasterLayer!.opacity = 0.5;
    // Add the raster layer to the map.
    map.operationalLayers.add(_originalElevationRasterLayer!);

    // Set the map to the controller.
    _mapViewController.arcGISMap = map;

    setState(() {
      // Set the elevation raster layer as the selected raster layer.
      _selectedRasterLayer = _originalElevationRasterLayer;
      // Set the ready state variable to true to enable the sample UI.
      _ready = true;
    });
  }

  // Applies the map algebra to the elevation raster and creates a new raster layer with the results.
  Future<void> performAnalysis() async {
    setState(() => _isPerformingAnalysis = true);

    // Create a continuous field from the elevation raster file.
    final elevationField = await ContinuousField.createFromFiles(
      filePaths: [_elevationFile.uri],
      band: 0,
    );

    // Create a continuous field function from the elevation field.
    final continuousFieldFunction = ContinuousFieldFunction.create(
      elevationField,
    );

    // Mask out values below sea level to categorize only land.
    final elevationFieldFunction = continuousFieldFunction.mask(
      continuousFieldFunction.isGreaterThanOrEqualToValue(0),
    );

    // Round elevation values down to the lower 10m interval, then convert to a discrete field function.
    final tenMeterBinField = ((elevationFieldFunction / 10).floor() * 10)
        .toDiscreteFieldFunction();

    // Create boolean fields for each geomorphic category based on the nearest 10m interval field.

    // Category: Raised shore line areas.
    final isRaisedShoreline = tenMeterBinField
        .isGreaterThanOrEqualToValue(0)
        .logicalAnd(tenMeterBinField.isLessThanValue(10));

    // Category: Ice covered areas.
    // Note: Operator overloads are available on some operators (e.g. /, *, +, -) to allow for more concise syntax when
    // chaining operations. Instead of using logicalAnd, & can also be used to combine boolean fields, as shown below.
    final isIceCovered =
        tenMeterBinField.isGreaterThanOrEqualToValue(10) &
        tenMeterBinField.isLessThanValue(600);

    // Category: Ice free high ground.
    final isIceFreeHighGround = tenMeterBinField.isGreaterThanOrEqualToValue(
      600,
    );

    // Assign values to the geomorphic categories and evaluate.
    // Raised shoreline=1, ice covered=2, ice-free high ground=3.
    final geomorphicCategoryFieldFunction = tenMeterBinField
        .replaceWithValueIf(selection: isRaisedShoreline, value: 1)
        .replaceWithValueIf(selection: isIceCovered, value: 2)
        .replaceWithValueIf(selection: isIceFreeHighGround, value: 3);
    final geomorphicCategoryField = await geomorphicCategoryFieldFunction
        .evaluate();

    try {
      // Export the disrete field to files in geoTIFF format.
      final tempDir = await getTemporaryDirectory();
      final dataDir = Directory('${tempDir.path}/geomorphic');
      // Clean up if the directory already exists to make the sample reusable.
      if (dataDir.existsSync()) {
        dataDir.deleteSync(recursive: true);
      }
      dataDir.createSync(recursive: true);

      final exportedFiles = await geomorphicCategoryField.exportToFiles(
        outputDirectory: dataDir.uri,
        filenamesPrefix: 'geomorphicCategorization',
      );

      if (exportedFiles.isEmpty) {
        setState(() => _isPerformingAnalysis = false);
        return;
      }

      // Use the exported raster file to create a new raster with the geomorphic categorization.
      final geomorphicCategorizationRaster = Raster.withFileUri(
        exportedFiles.first,
      );
      // Use the raster to create a raster layer for displaying the results on the map.
      _resultsRasterLayer = RasterLayer.withRaster(
        geomorphicCategorizationRaster,
      );

      // Define a renderer for the different geomorphic categories.
      final colors = {
        1: Colors.blue.shade600, // raised shoreline
        2: Colors.teal.shade200, // ice covered
        3: Colors.brown, // ice-free high ground
      };

      // Create a colormap renderer using the defined categories.
      final colormap = Colormap.fromValuesAndColors(colors);
      final colormapRenderer = ColormapRenderer.withColormap(colormap);

      // Set the colormap renderer to the results raster layer and apply an opacity.
      _resultsRasterLayer!.renderer = colormapRenderer;
      _resultsRasterLayer!.opacity = 0.5;
      // Add the results raster layer to the map.
      _mapViewController.arcGISMap!.operationalLayers.add(_resultsRasterLayer!);
      // Set the results raster layer as the active raster layer.
      selectRasterLayer(_resultsRasterLayer!);

      setState(() {
        // Update the sample UI.
        _isPerformingAnalysis = false;
        _hasAnalysisResults = true;
      });
    } on Exception catch (_) {
      setState(() => _isPerformingAnalysis = false);
      if (mounted) {
        await showAlertDialog(context, 'Error configuring raster file.');
      }
    }
  }

  // Sets the provided raster layer as the selected raster.
  void selectRasterLayer(RasterLayer selectedRaster) {
    // Ensure the selected raster is visible and the alternate raster is not visible.
    _originalElevationRasterLayer!.isVisible =
        selectedRaster == _originalElevationRasterLayer;
    _resultsRasterLayer!.isVisible = selectedRaster == _resultsRasterLayer;
    // Update the state with the newly selected raster.
    setState(() => _selectedRasterLayer = selectedRaster);
  }
}

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