Identify raster cell

View on GitHub
Sample viewer app

Get the cell value of a local raster at the tapped location and display the result in a callout.

Image of identify raster cell

Use case

You may want to identify a raster layer to get its exact cell value in case the approximate value conveyed by its symbology is not sufficient. The information available for the raster cell depends on the type of raster layer being identified. For example, a 3-band satellite or aerial image might provide 8-bit RGB values, whereas a digital elevation model (DEM) would provide floating point z values. By identifying a raster cell of a DEM, you can retrieve the precise elevation of a location.

How to use the sample

Tap or long press drag on an area of the raster to identify it and see the raster cell attributes information displayed in a callout.

How it works

  1. Use various delegate methods in AGSGeoViewTouchDelegate to get the screenPoint where a user tapped or long pressed on the map.
  2. On tap or long press drag:

    • Dismiss the AGSCallout, if one is showing.
    • Call AGSGeoView.identifyLayer(_:screenPoint:tolerance:returnPopupsOnly:completion:) passing in the raster layer, screen point, tolerance, and whether only layers containing popups should be returned.
    • Get the AGSGeoElements from the layer identifying result.
    • Create a callout and populate the callout content with text from the AGSRasterCell attributes and coordinates.
    • Show the callout.

Relevant API

  • AGSGeoView.identifyLayer(_:screenPoint:tolerance:returnPopupsOnly:completion:)
  • AGSIdentifyLayerResult
  • AGSRasterCell
  • AGSRasterCell.attributes
  • AGSRasterLayer

About the data

The data shown is an NDVI classification derived from MODIS imagery between 27 Apr 2020 and 4 May 2020. It comes from the NASA Worldview application. In a normalized difference vegetation index, or NDVI, values range between -1 and +1 with the positive end of the spectrum showing green vegetation.

Tags

band, cell, cell value, continuous, discrete, identify, pixel, pixel value, raster

Sample Code

IdentifyRasterCellViewController.swift
                                                                                                                                         
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
// 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 UIKit
import ArcGIS

class IdentifyRasterCellViewController: UIViewController {
    // MARK: Storyboard views

    /// The custom stack view to show in a callout.
    @IBOutlet weak var calloutStackView: IdentifyRasterCellStackView!

    /// The map view managed by the view controller.
    @IBOutlet weak var mapView: AGSMapView! {
        didSet {
            mapView.map = makeMap()
            mapView.setViewpoint(AGSViewpoint(latitude: -34.1, longitude: 18.6, scale: 1155581.108577))
            mapView.touchDelegate = self
            mapView.callout.customView = calloutStackView
            mapView.callout.isAccessoryButtonHidden = true
        }
    }

    // MARK: Properties

    /// The raster layer created using local raster file.
    let rasterLayer = AGSRasterLayer(raster: AGSRaster(name: "SA_EVI_8Day_03May20", extension: "tif"))

    /// A formatter for coordinates.
    let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.usesGroupingSeparator = false
        formatter.minimumFractionDigits = 3
        formatter.maximumFractionDigits = 3
        return formatter
    }()

    // MARK: Initialize map and identify pixel

    /// Create a map.
    ///
    /// - Returns: An `AGSMap` object.
    func makeMap() -> AGSMap {
        let map = AGSMap(basemapStyle: .arcGISOceans)
        map.operationalLayers.add(rasterLayer)
        return map
    }

    /// Identify a pixel on the raster layer and display the result in a callout.
    ///
    /// - Parameters:
    ///   - screenPoint: The point at which to identify features.
    ///   - offset: An offset to the point where the callout is displayed.
    func identifyPixel(at screenPoint: CGPoint, offset: CGPoint = .zero) {
        mapView.identifyLayer(rasterLayer, screenPoint: screenPoint, tolerance: 1.0, returnPopupsOnly: false) { [weak self] identifyResult in
            guard let self = self else { return }
            if identifyResult.geoElements.isEmpty {
                // If there are no geo elements identified, e.g. not on a raster layer, dismiss the callout.
                self.mapView.callout.dismiss()
                return
            }
            guard let cell = identifyResult.geoElements.first(where: { $0 is AGSRasterCell }) else {
                return
            }
            let calloutText = cell.attributes.map { "\($0.key): \($0.value)" }.joined(separator: "\n")
            let extent = cell.geometry!.extent
            let xyCoordinates = """
            X: \(self.formatter.string(from: extent.xMin as NSNumber)!)
            Y: \(self.formatter.string(from: extent.yMin as NSNumber)!)
            """
            self.calloutStackView.attributesLabel.text = calloutText
            self.calloutStackView.coordinatesLabel.text = xyCoordinates
            self.mapView.callout.show(
                at: self.mapView.screen(toLocation: screenPoint),
                screenOffset: offset,
                rotateOffsetWithMap: false,
                animated: false
            )
        }
    }

    // MARK: UIViewController

    override func viewDidLoad() {
        super.viewDidLoad()
        // Add the source code button item to the right of navigation bar.
        (self.navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["IdentifyRasterCellViewController"]
        // Load the raster layer.
        rasterLayer.load { [weak self] (error) in
            if let error = error {
                self?.presentAlert(error: error)
            }
        }
    }
}

// MARK: - AGSGeoViewTouchDelegate

extension IdentifyRasterCellViewController: AGSGeoViewTouchDelegate {
    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        // Tap to identify a pixel on the raster layer.
        identifyPixel(at: screenPoint)
    }

    func geoView(_ geoView: AGSGeoView, didLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        // Show the callout with offset so that the finger does not cover the callout.
        identifyPixel(at: screenPoint, offset: CGPoint(x: 0, y: -70))
    }

    func geoView(_ geoView: AGSGeoView, didMoveLongPressToScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        identifyPixel(at: screenPoint, offset: CGPoint(x: 0, y: -70))
    }

    func geoView(_ geoView: AGSGeoView, didEndLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        // When tap drag finishes, show the callout without offset.
        identifyPixel(at: screenPoint)
    }
}

// MARK: - Custom stack view to show in a callout

class IdentifyRasterCellStackView: UIStackView {
    @IBOutlet weak var attributesLabel: UILabel!
    @IBOutlet weak var coordinatesLabel: UILabel!
}

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