Create buffers around points

View on GitHub

Generate multiple individual buffers or a single unioned buffer around multiple points.

Image of create buffers around points

Use case

Creating buffers is a core concept in GIS proximity analysis that allows you to visualize and locate geographic features contained within a set distance of a feature. For example, consider an area where wind turbines are proposed. It has been determined that each turbine should be located at least 2 km away from residential premises due to noise pollution regulations, and a proximity analysis is therefore required. The first step would be to generate 2 km buffer polygons around all proposed turbines. As the buffer polygons may overlap for each turbine, unioning the result would produce a single graphic result with a neater visual output. If any premises are located within 2 km of a turbine, that turbine would be in breach of planning regulations.

How to use the sample

Tap on the map to add points. Tap the "Settings" button to adjust the buffer radius and union settings. The buffer radius can be adjusted using the slider, and the union option can be toggled using the switch. Tap the "Clear" button to start over. The red dashed envelope shows the area where you can expect reasonable results for planar buffer operations with the North Central Texas State Plane spatial reference.

How it works

  1. Use GeometryEngine.bufferCollection(geometries, distances, unionResult) to create buffer polygons. The parameter geometries are the points to buffer around, distances are the buffer distances for each point (in feet), and unionResult is a boolean for whether the results should be unioned.
  2. Add the resulting polygons (if not unioned) or single polygon (if unioned) to the map's GraphicsOverlay as a Graphic.

Relevant API

  • ArcGISMap
  • ArcGISMapImageLayer
  • ArcGISMapView
  • GeometryEngine.bufferCollection
  • GeometryEngine.project
  • GraphicsOverlay
  • SpatialReference

Additional information

The properties of the underlying projection determine the accuracy of buffer polygons in a given area. Planar buffers work well when analyzing distances around features that are concentrated in a relatively small area in a projected coordinate system. Inaccurate buffers could still be created by buffering points inside the spatial reference's envelope with distances that move it outside the envelope. On the other hand, geodesic buffers consider the curved shape of the Earth's surface and provide more accurate buffer offsets for features that are more dispersed (i.e., cover multiple UTM zones, large regions, or even the whole globe). See the "Create Planar and Geodetic Buffers" sample for an example of a geodesic buffer.

For more information about using buffer analysis, see the topic How Buffer (Analysis) works in the ArcGIS Pro documentation.

Tags

analysis, buffer, geometry, planar

Sample Code

create_buffers_around_points.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
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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//
// 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 'dart:math';

import 'package:arcgis_maps/arcgis_maps.dart';
import 'package:arcgis_maps_sdk_flutter_samples/utils/sample_state_support.dart';
import 'package:flutter/material.dart';

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

  @override
  State<CreateBuffersAroundPoints> createState() =>
      _CreateBuffersAroundPointsState();
}

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

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

  // A polygon that represents the valid area of use for the spatial reference.
  late Polygon _boundaryPolygon;

  // List of tap points.
  final _bufferPoints = <ArcGISPoint>[];

  // List of buffer radii.
  final _bufferRadii = <double>[];

  // Current status of the buffer.
  var _status = Status.addPoints;

  // Buffer radius.
  var _bufferRadius = 100.0;

  // Union status.
  var _shouldUnion = false;

  // A flag for when the settings bottom sheet is visible.
  var _showSettings = false;

  // Define the graphics overlays.
  final _bufferGraphicsOverlay = GraphicsOverlay();
  final _tapPointGraphicsOverlay = GraphicsOverlay();

  // Fill symbols for buffer and tap points.
  final _bufferFillSymbol = SimpleFillSymbol();
  final _tapPointSymbol = SimpleMarkerSymbol();

  // Define the spatial reference required by the sample.
  final _statePlaneNorthCentralTexasSpatialReference =
      SpatialReference(wkid: 32038);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        top: true,
        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,
                  ),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    // Conditionally display the settings.
                    ElevatedButton(
                      onPressed: () {
                        setState(() => _showSettings = !_showSettings);
                      },
                      child: const Text('Settings'),
                    ),
                    // A button to clear the buffers.
                    ElevatedButton(
                      onPressed: _bufferPoints.isEmpty
                          ? null
                          : () {
                              clearBufferPoints();
                              setState(() => _status = Status.addPoints);
                            },
                      child: const Text('Clear'),
                    ),
                  ],
                ),
              ],
            ),
            // 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()),
                ),
              ),
            ),
            // Display a banner with instructions at the top.
            SafeArea(
              child: IgnorePointer(
                child: Container(
                  padding: const EdgeInsets.all(10),
                  color: Colors.white.withOpacity(0.7),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        _status.label,
                        textAlign: TextAlign.center,
                        style: const TextStyle(color: Colors.black),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
      bottomSheet: _showSettings ? buildSettings(context, setState) : null,
    );
  }

  void onMapViewReady() {
    // Initialize the boundary polygon and the symbols.
    _boundaryPolygon = _makeBoundaryPolygon();
    _initializeSymbols();

    // Create a map with the defined spatial reference and add it to our map controller.
    final map = ArcGISMap(
      spatialReference: _statePlaneNorthCentralTexasSpatialReference,
    );

    // Add some base layers (counties, cities, highways).
    final mapServiceUri = Uri.parse(
      'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer',
    );

    // Create a map image layer from the uri.
    final usaLayer = ArcGISMapImageLayer.withUri(mapServiceUri);

    // Add the map image layer to the map.
    map.operationalLayers.add(usaLayer);

    // Set the map on the map view controller.
    _mapViewController.arcGISMap = map;

    // Set the viewpoint of the map view to our boundary polygon extent.
    _mapViewController
        .setViewpoint(Viewpoint.fromTargetExtent(_boundaryPolygon.extent));

    _configureGraphicsOverlays();

    // Set the ready state variable to true to enable the sample UI.
    setState(() => _ready = true);
  }

  void onTap(Offset offset) {
    // Convert the screen point to a map point.
    final mapPoint = _mapViewController.screenToLocation(screen: offset);

    // Ensure the map point is within the boundary.
    if (!GeometryEngine.contains(
      geometry1: _boundaryPolygon,
      geometry2: mapPoint!,
    )) {
      setState(() => _status = Status.outOfBoundsTap);
      return;
    }

    // Use the current buffer radius value from the slider.
    addBuffer(point: mapPoint, radius: _bufferRadius);
    drawBuffers(unionized: _shouldUnion);

    setState(() => _status = Status.bufferCreated);
  }

  void _initializeSymbols() {
    // Initialize the fill symbol for the buffer.
    _bufferFillSymbol
      ..color = Colors.yellow.withOpacity(0.5)
      ..outline = SimpleLineSymbol(
        style: SimpleLineSymbolStyle.solid,
        color: Colors.green,
        width: 3,
      );

    // Initialize the tap point symbol.
    _tapPointSymbol
      ..style = SimpleMarkerSymbolStyle.circle
      ..color = Colors.red
      ..size = 10;
  }

  // The build method for the settings.
  Widget buildSettings(BuildContext context, StateSetter setState) {
    return Container(
      padding: EdgeInsets.fromLTRB(
        20.0,
        0.0,
        20.0,
        max(
          20.0,
          View.of(context).viewPadding.bottom /
              View.of(context).devicePixelRatio,
        ),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            children: [
              Text(
                'Settings',
                style: Theme.of(context).textTheme.titleLarge,
              ),
              const Spacer(),
              IconButton(
                onPressed: () {
                  setState(() => _showSettings = false);
                },
                icon: const Icon(Icons.close),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Buffer Radius (miles)'),
              const Spacer(),
              Text(
                _bufferRadius.round().toString(),
                textAlign: TextAlign.right,
              ),
            ],
          ),
          Row(
            children: [
              Expanded(
                // A slider to adjust the buffer radius.
                child: Slider(
                  value: _bufferRadius,
                  min: 10,
                  max: 300,
                  onChanged: (value) => setState(() => _bufferRadius = value),
                ),
              ),
            ],
          ),
          Row(
            children: [
              Text(_shouldUnion ? 'Union Enabled' : 'Union Disabled'),
              const Spacer(),
              Switch(
                value: _shouldUnion,
                onChanged: (value) {
                  setState(() => _shouldUnion = value);
                  if (_bufferPoints.isNotEmpty) {
                    drawBuffers(unionized: _shouldUnion);
                  }
                },
              ),
            ],
          ),
        ],
      ),
    );
  }

  void _configureGraphicsOverlays() {
    // Create a graphics overlay to show the spatial reference's valid area.
    final boundaryGraphicsOverlay = GraphicsOverlay();

    // Add the graphics overlay to the map view.
    _mapViewController.graphicsOverlays.add(boundaryGraphicsOverlay);

    // Create a symbol for the graphics.
    final lineSymbol = SimpleLineSymbol(
      style: SimpleLineSymbolStyle.dash,
      color: const Color(0xFFFF0000),
      width: 5,
    );

    // Create the graphics.
    final boundaryGraphic =
        Graphic(geometry: _boundaryPolygon, symbol: lineSymbol);

    // Add the graphics to the graphics overlay.
    boundaryGraphicsOverlay.graphics.add(boundaryGraphic);

    // Add the buffer and tap points graphics overlays to the map view.
    _mapViewController.graphicsOverlays.add(_bufferGraphicsOverlay);
    _mapViewController.graphicsOverlays.add(_tapPointGraphicsOverlay);
  }

  Polygon _makeBoundaryPolygon() {
    // Create a boundary polygon.
    final polygonBuilder =
        PolygonBuilder(spatialReference: SpatialReference.wgs84);

    // Add points to define the boundary where the spatial reference is valid for planar buffers.
    polygonBuilder.addPointXY(x: -103.070, y: 31.720);
    polygonBuilder.addPointXY(x: -103.070, y: 34.580);
    polygonBuilder.addPointXY(x: -94.000, y: 34.580);
    polygonBuilder.addPointXY(x: -94.000, y: 31.720);

    // Use the polygon builder to define a boundary geometry.
    final boundaryGeometry = polygonBuilder.toGeometry();

    // Project the boundary geometry to the spatial reference used by the sample.
    final boundaryPolygon = GeometryEngine.project(
      boundaryGeometry,
      outputSpatialReference: _statePlaneNorthCentralTexasSpatialReference,
    ) as Polygon;

    return boundaryPolygon;
  }

  void drawBuffers({required bool unionized}) {
    // Clear existing buffers before drawing.
    _bufferGraphicsOverlay.graphics.clear();
    _tapPointGraphicsOverlay.graphics.clear();

    // Create buffers.
    final bufferPolygons = GeometryEngine.bufferCollection(
      geometries: _bufferPoints,
      distances: _bufferRadii,
      unionResult: unionized,
    );

    // Add the tap points to the tapPointsGraphicsOverlay.
    for (final point in _bufferPoints) {
      _tapPointGraphicsOverlay.graphics.add(
        Graphic(geometry: point, symbol: _tapPointSymbol),
      );
    }

    // Add the buffers to the bufferGraphicsOverlay.
    for (final bufferPolygon in bufferPolygons) {
      _bufferGraphicsOverlay.graphics.add(
        Graphic(geometry: bufferPolygon, symbol: _bufferFillSymbol),
      );
    }
  }

  // Clears the buffer points.
  void clearBufferPoints() {
    _bufferPoints.clear();
    _bufferRadii.clear();
    _bufferGraphicsOverlay.graphics.clear();
    _tapPointGraphicsOverlay.graphics.clear();
  }

  void addBuffer({required ArcGISPoint point, required double radius}) {
    // Ensure the radius is within a valid range before adding the buffer.
    if (radius <= 0 || radius > 300) {
      setState(() => _status = Status.invalidInput);
      return;
    }
    // Convert the radius from miles to feet directly.
    final radiusInFeet = radius * 5280;

    // Add point with radius to bufferPoints  and bufferRadii lists.
    _bufferPoints.add(point);
    _bufferRadii.add(radiusInFeet);
  }
}

enum Status {
  addPoints('Tap on the map to add buffers.'),
  bufferCreated('Buffer created.'),
  outOfBoundsTap('Tap within the boundary to add buffer.'),
  invalidInput('Enter a value between 0 and 300 to create a buffer.'),
  noPoints('Add a point to draw the buffers.');

  final String label;

  const Status(this.label);
}

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