Identify features in WMS layer

View on GitHub

Identify features in a WMS layer and display the associated popup content.

Image of identify features in WMS layer

Use case

Map symbols generally showcase only one or two data points via color or size, but the data can contain many more attributes than what is shown on the map. These additional attributes can be shown in an attribute table or popup for the map viewer to explore interactively. For example, the map might be symbolized to show population density with different shades of a color, but it might contain other interesting attributes to explore in a table, such as median income, educational attainment, and median age.

How to use the sample

Tap a feature to identify it. The HTML content associated with the feature will be displayed in an alert dialog.

How it works

  1. A WMS layer is added via URL and a layer name.
  2. When the map is tapped, ArcGISMapViewController.identifyLayer is used to find matching results within the WMS layer.
  3. If there is a matching feature, the HTML property is taken via a lookup in the feature's attribute dictionary.
  4. This particular server will produce an identify result with an empty table when there is no identified feature. In all other cases, a table with an OBJECTID column is added. This sample checks for the presence of OBJECTID in the HTML, and doesn't display the result if it is missing.
  5. The HTML is displayed in an alert dialog.

Note: the service returns HTML regardless of whether there was an identify result. The sample uses a simple rule to hide empty results.

Relevant API

  • ArcGISMapViewController.identifyLayer
  • IdentifyLayerResult
  • IdentifyLayerResult.geoElements
  • WmsFeature
  • WmsFeature.attributes
  • WmsLayer

About the data

This sample shows a map of surface water sources in each U.S. state. States with more surface and ground water sources appear darker blue. The attribute table includes counts of surface and ground water sources. This map service is provided by the U.S. EPA.

Tags

OGC, web map service, WMS

Sample Code

identify_features_in_wms_layer.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
// Copyright 2024 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 'package:arcgis_maps/arcgis_maps.dart';
import 'package:arcgis_maps_sdk_flutter_samples/utils/sample_state_support.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

  @override
  State<IdentifyFeaturesInWmsLayer> createState() =>
      _IdentifyFeaturesInWmsLayerState();
}

class _IdentifyFeaturesInWmsLayerState extends State<IdentifyFeaturesInWmsLayer>
    with SampleStateSupport {
  // Create a controller for the map view.
  final _mapViewController = ArcGISMapView.createController();
  // Create a WMS Layer.
  late WmsLayer _wmsLayer;
  // Create a web view controller for holding HTML content.
  final _webViewController = WebViewController();
  // A flag for when the map view is ready and controls can be used.
  var _ready = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        top: 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,
                    onTap: onTap,
                  ),
                ),
              ],
            ),
            // Display a banner with instructions.
            SafeArea(
              child: IgnorePointer(
                child: Container(
                  padding: const EdgeInsets.all(10),
                  color: Colors.white.withOpacity(0.7),
                  child: const Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        'Tap on the map to identify features in the WMS layer.',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.black),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            // Display a progress indicator and prevent interaction until state is ready.
            Visibility(
              visible: !_ready,
              child: const SizedBox.expand(
                child: ColoredBox(
                  color: Colors.white30,
                  child: Center(child: CircularProgressIndicator()),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void onMapViewReady() async {
    // Create a map with a basemap and set to the map view controller.
    final map = ArcGISMap.withBasemapStyle(BasemapStyle.arcGISDarkGrayBase);
    _mapViewController.arcGISMap = map;

    // Create a URI to a WMS service showing EPA water info.
    final wmsServiceUri = Uri.parse(
      'https://watersgeo.epa.gov/arcgis/services/OWPROGRAM/SDWIS_WMERC/MapServer/WMSServer?request=GetCapabilities&service=WMS',
    );
    // Create a list of WMS layer names to display.
    final layerNames = ['4'];
    // Create a WMS Layer using the service URI and layer names, and load.
    _wmsLayer = WmsLayer.withUriAndLayerNames(
      uri: wmsServiceUri,
      layerNames: layerNames,
    );
    await _wmsLayer.load();
    // Once loaded get the extent of the layer.
    final layerExtent = _wmsLayer.fullExtent;
    // Set the viewpoint to the extent of the layer.
    if (layerExtent != null) {
      _mapViewController.setViewpoint(Viewpoint.fromTargetExtent(layerExtent));
    }
    // Add the WMS Layer to the map's operational layers.
    map.operationalLayers.add(_wmsLayer);
    // Set the ready state variable to true to enable the sample UI.
    setState(() => _ready = true);
  }

  void onTap(Offset localPosition) async {
    // Prevent addtional taps until the identify operation is complete.
    setState(() => _ready = false);

    // When the map view is tapped, perform an identify operation on the WMS layer.
    final identifyLayerResult = await _mapViewController.identifyLayer(
      _wmsLayer,
      screenPoint: localPosition,
      tolerance: 12.0,
      maximumResults: 1,
    );

    // Check if there are any features identified.
    final features =
        identifyLayerResult.geoElements.whereType<WmsFeature>().toList();
    if (features.isNotEmpty) {
      // Get the identified WMS feature.
      final identifiedWmsFeature = features.first;
      // Retrieve the feature's HTML content.
      final htmlContent = identifiedWmsFeature.attributes['HTML'] as String;
      // This particular server will produce an identify result with an empty table when there is no identified feature.
      // This sample checks for the presence of OBJECTID in the HTML, and doesn't display the result if it is missing.
      if (htmlContent.contains('OBJECTID')) {
        // Zoom into the HTML content to make it more readable.
        final zoomedHtmlContent = updateHtmlInitialScale(htmlContent);
        // Load the HTML content via the web view controller.
        await _webViewController.loadHtmlString(zoomedHtmlContent);
        // Configure a dialog that will display the results.
        showResultsDialog();
      }
    }

    // Allow additional taps.
    setState(() => _ready = true);
  }

  void showResultsDialog() {
    // Show a dialog containing a web view widget.
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(
          'Identify Result',
          style: Theme.of(context).textTheme.titleMedium,
        ),
        content: SizedBox(
          height: 150,
          width: MediaQuery.of(context).size.width * 0.75,
          // Create a web view widget and set the controller to provide the content.
          child: WebViewWidget(
            controller: _webViewController,
          ),
        ),
      ),
    );
  }

  // A helper method to update the initial scale of the provided HTML content to improve readability.
  String updateHtmlInitialScale(String htmlContent) {
    // Define a meta tag to set the scale.
    const metaTag = '<meta name="viewport" content="initial-scale=1"></meta>';
    // Locate the head tag.
    const headTag = '<head>';
    final index = htmlContent.indexOf(headTag);
    if (index != -1) {
      // Insert the meta tag after the head tag.
      return htmlContent.substring(0, index + headTag.length) +
          metaTag +
          htmlContent.substring(index + headTag.length);
    } else {
      // If no valid head tag found, return the original HTML.
      return htmlContent;
    }
  }
}

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