Buffer list

View inWPFUWPFormsiOSAndroid
View on GitHub

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

Image of Buffer list

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

Click/tap on the map to add points. Tap the "Create Buffer(s)" button to draw buffer(s) around the points (the size of the buffer is determined by the value entered by the user). Check the check box if you want the result to union (combine) the buffers. 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.Buffer(points, distances, union) to create a Polygon. The parameter points are the points to buffer around, distances are the buffer distances for each point (in meters) and union 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

  • Geometry
  • GeometryEngine.Buffer
  • 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 "Buffer" 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

BufferList.cs
                                                                                                                                                                                                                                                                                                                                                                                                
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
// Copyright 2018 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.

using System;
using System.Collections.Generic;
using System.Drawing;
using CoreGraphics;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.Controls;
using Foundation;
using UIKit;

namespace ArcGISRuntime.Samples.BufferList
{
    [Register("BufferList")]
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "Buffer list",
        category: "Geometry",
        description: "Generate multiple individual buffers or a single unioned buffer around multiple points.",
        instructions: "Click/tap on the map to add points. Tap the \"Create Buffer(s)\" button to draw buffer(s) around the points (the size of the buffer is determined by the value entered by the user). Check the check box if you want the result to union (combine) the buffers. 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.",
        tags: new[] { "analysis", "buffer", "geometry", "planar" })]
    public class BufferList : UIViewController
    {
        // Hold references to UI controls.
        private MapView _myMapView;
        private UITextField _bufferDistanceEntry;
        private UIBarButtonItem _helpButton;
        private UIBarButtonItem _bufferButton;
        private UIBarButtonItem _resetButton;

        // A polygon that defines the valid area of the spatial reference used.
        private Polygon _spatialReferenceArea;

        // A Random object to create RGB color values.
        private readonly Random _random = new Random();

        public BufferList()
        {
            Title = "Buffer list";
        }

        private void Initialize()
        {
            // Create a spatial reference that's suitable for creating planar buffers in north central Texas (State Plane).
            SpatialReference statePlaneNorthCentralTexas = new SpatialReference(32038);

            // Define a polygon that represents the valid area of use for the spatial reference.
            // This information is available at https://developers.arcgis.com/net/latest/wpf/guide/pdf/projected_coordinate_systems_rt100_3_0.pdf
            List<MapPoint> spatialReferenceExtentCoords = new List<MapPoint>
            {
                new MapPoint(-103.070, 31.720, SpatialReferences.Wgs84),
                new MapPoint(-103.070, 34.580, SpatialReferences.Wgs84),
                new MapPoint(-94.000, 34.580, SpatialReferences.Wgs84),
                new MapPoint(-94.00, 31.720, SpatialReferences.Wgs84)
            };
            _spatialReferenceArea = new Polygon(spatialReferenceExtentCoords);
            _spatialReferenceArea = GeometryEngine.Project(_spatialReferenceArea, statePlaneNorthCentralTexas) as Polygon;

            // Create a map that uses the North Central Texas state plane spatial reference.
            Map bufferMap = new Map(statePlaneNorthCentralTexas);

            // Add some base layers (counties, cities, and highways).
            Uri usaLayerSource = new Uri("https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer");
            ArcGISMapImageLayer usaLayer = new ArcGISMapImageLayer(usaLayerSource);
            bufferMap.Basemap.BaseLayers.Add(usaLayer);

            // Use a new EnvelopeBuilder to expand the spatial reference extent 120%.
            EnvelopeBuilder envBuilder = new EnvelopeBuilder(_spatialReferenceArea.Extent);
            envBuilder.Expand(1.2);

            // Set the map's initial extent to the expanded envelope.
            Envelope startingEnvelope = envBuilder.ToGeometry();
            bufferMap.InitialViewpoint = new Viewpoint(startingEnvelope);

            // Assign the map to the MapView.
            _myMapView.Map = bufferMap;

            // Create a graphics overlay to show the buffer polygon graphics.
            GraphicsOverlay bufferGraphicsOverlay = new GraphicsOverlay
            {
                // Give the overlay an ID so it can be found later.
                Id = "buffers"
            };

            // Create a graphic to show the spatial reference's valid extent (envelope) with a dashed red line.
            SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Dash, Color.Red, 5);
            Graphic spatialReferenceExtentGraphic = new Graphic(_spatialReferenceArea, lineSymbol);

            // Add the graphic to a new overlay.
            GraphicsOverlay spatialReferenceGraphicsOverlay = new GraphicsOverlay();
            spatialReferenceGraphicsOverlay.Graphics.Add(spatialReferenceExtentGraphic);

            // Add the graphics overlays to the MapView.
            _myMapView.GraphicsOverlays.Add(bufferGraphicsOverlay);
            _myMapView.GraphicsOverlays.Add(spatialReferenceGraphicsOverlay);
        }

        private void MyMapView_GeoViewTapped(object sender, GeoViewInputEventArgs e)
        {
            try
            {
                // Get the input map point (in the map's coordinate system, State Plane for North Central Texas).
                MapPoint tapMapPoint = e.Location;

                // Check if the point coordinates are within the spatial reference envelope.
                bool withInvalidExtent = GeometryEngine.Contains(_spatialReferenceArea, tapMapPoint);

                // If the input point is not within the valid extent for the spatial reference, warn the user and return.
                if (!withInvalidExtent)
                {
                    // Display a message to warn the user.
                    UIAlertController alertController = UIAlertController.Create("Out of bounds", "Location is not valid to buffer using the defined spatial reference.", UIAlertControllerStyle.Alert);
                    alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
                    PresentViewController(alertController, true, null);

                    return;
                }

                // Get the buffer radius (in miles) from the text box.
                double bufferDistanceMiles = Convert.ToDouble(_bufferDistanceEntry.Text);

                // Use a helper method to get the buffer distance in feet (unit that's used by the spatial reference).
                double bufferDistanceFeet = LinearUnits.Miles.ConvertTo(LinearUnits.Feet, bufferDistanceMiles);

                // Create a simple marker symbol (red circle) to display where the user tapped/clicked on the map.
                SimpleMarkerSymbol tapSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle.Circle, Color.Red, 10);

                // Create a new graphic to show the tap location.
                Graphic tapGraphic = new Graphic(tapMapPoint, tapSymbol)
                {
                    // Specify a z-index value on the point graphic to make sure it draws on top of the buffer polygons.
                    ZIndex = 2
                };

                // Store the specified buffer distance as an attribute with the graphic.
                tapGraphic.Attributes["distance"] = bufferDistanceFeet;

                // Add the tap point graphic to the buffer graphics overlay.
                _myMapView.GraphicsOverlays["buffers"].Graphics.Add(tapGraphic);
            }
            catch (Exception ex)
            {
                // Display an error message.
                UIAlertController alertController = UIAlertController.Create("Error creating buffer point", ex.Message, UIAlertControllerStyle.Alert);
                alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
                PresentViewController(alertController, true, null);
            }
        }

        private void PromptForUnionChoice(object sender, EventArgs e)
        {
            var alert = UIAlertController.Create("Union buffers?", "", UIAlertControllerStyle.ActionSheet);
            if (alert.PopoverPresentationController != null)
            {
                alert.PopoverPresentationController.BarButtonItem = _bufferButton;
            }

            alert.AddAction(UIAlertAction.Create("Union", UIAlertActionStyle.Default, action => PerformUnion(true)));
            alert.AddAction(UIAlertAction.Create("Don't union", UIAlertActionStyle.Default, action => PerformUnion(false)));
            PresentViewController(alert, true, null);
        }

        private void PerformUnion(bool unionBuffers)
        {
            try
            {
                // Call a function to delete any existing buffer polygons so they can be recreated.
                ClearBufferPolygons();

                // Iterate all point graphics and create a list of map points and buffer distances for each.
                List<MapPoint> bufferMapPoints = new List<MapPoint>();
                List<double> bufferDistances = new List<double>();
                foreach (Graphic bufferGraphic in _myMapView.GraphicsOverlays["buffers"].Graphics)
                {
                    // Only use point graphics.
                    if (bufferGraphic.Geometry.GeometryType == GeometryType.Point)
                    {
                        // Get the geometry (map point) from the graphic.
                        MapPoint bufferLocation = bufferGraphic.Geometry as MapPoint;

                        // Read the "distance" attribute to get the buffer distance entered when the point was tapped.
                        double bufferDistanceFeet = (double) bufferGraphic.Attributes["distance"];

                        // Add the point and the corresponding distance to the lists.
                        bufferMapPoints.Add(bufferLocation);
                        bufferDistances.Add(bufferDistanceFeet);
                    }
                }

                // Call GeometryEngine.Buffer with a list of map points and a list of buffered distances.
                IEnumerable<Geometry> bufferPolygons = GeometryEngine.Buffer(bufferMapPoints, bufferDistances, unionBuffers);

                // Create the outline for the buffered polygons.
                SimpleLineSymbol bufferPolygonOutlineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.DarkBlue, 3);

                // Loop through all the geometries in the buffer results. There will be one buffered polygon if
                // the result geometries were unioned. Otherwise, there will be one buffer per input geometry.
                foreach (Geometry poly in bufferPolygons)
                {
                    // Create a random color to use for buffer polygon fill.
                    Color bufferPolygonColor = GetRandomColor();

                    // Create simple fill symbol for the buffered polygon using the fill color and outline.
                    SimpleFillSymbol bufferPolygonFillSymbol = new SimpleFillSymbol(SimpleFillSymbolStyle.Solid, bufferPolygonColor, bufferPolygonOutlineSymbol);

                    // Create a new graphic for the buffered polygon using the fill symbol.
                    Graphic bufferPolygonGraphic = new Graphic(poly, bufferPolygonFillSymbol)
                    {
                        // Specify a z-index of 0 to ensure the polygons draw below the tap points.
                        ZIndex = 0
                    };

                    // Add the buffered polygon graphic to the graphics overlay.
                    _myMapView.GraphicsOverlays[0].Graphics.Add(bufferPolygonGraphic);
                }
            }
            catch (Exception ex)
            {
                // Display an error message if there is a problem generating the buffers.
                UIAlertController alertController = UIAlertController.Create("Unable to create buffer polygons", ex.Message, UIAlertControllerStyle.Alert);
                alertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
                PresentViewController(alertController, true, null);
            }
        }

        private Color GetRandomColor()
        {
            // Get a byte array with three random values.
            byte[] colorBytes = new byte[3];
            _random.NextBytes(colorBytes);

            // Use the random bytes to define red, green, and blue values for a new color.
            return Color.FromArgb(155, colorBytes[0], colorBytes[1], colorBytes[2]);
        }

        private void ClearButton_Click(object sender, EventArgs e)
        {
            // Clear all graphics (tap points and buffer polygons).
            _myMapView.GraphicsOverlays["buffers"].Graphics.Clear();
        }

        private void ClearBufferPolygons()
        {
            // Get the collection of graphics in the graphics overlay (points and buffer polygons).
            GraphicCollection bufferGraphics = _myMapView.GraphicsOverlays["buffers"].Graphics;

            // Loop (backwards) through all graphics.
            for (int i = bufferGraphics.Count - 1; i >= 0; i--)
            {
                // If the graphic is a polygon, remove it from the overlay.
                Graphic thisGraphic = bufferGraphics[i];
                if (thisGraphic.Geometry.GeometryType == GeometryType.Polygon)
                {
                    bufferGraphics.RemoveAt(i);
                }
            }
        }

        private void ShowHelpAlert(object sender, EventArgs e)
        {
            const string message = "Tap to add points. Each point will use the buffer distance entered when it was created. " +
                                   "The envelope shows the area where you can expect reasonable results for planar buffer operations with the North Central Texas State Plane spatial reference.";
            new UIAlertView("Instructions", message, (IUIAlertViewDelegate) null, "OK", null).Show();
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            Initialize();
        }

        public override void LoadView()
        {
            // Create the views.
            View = new UIView {BackgroundColor = ApplicationTheme.BackgroundColor};

            _myMapView = new MapView();
            _myMapView.TranslatesAutoresizingMaskIntoConstraints = false;

            _helpButton = new UIBarButtonItem();
            _helpButton.Title = "Help";

            _resetButton = new UIBarButtonItem();
            _resetButton.Title = "Reset";

            _bufferButton = new UIBarButtonItem();
            _bufferButton.Title = "Buffer";

            UIToolbar controlsToolbar = new UIToolbar();
            controlsToolbar.TranslatesAutoresizingMaskIntoConstraints = false;
            controlsToolbar.Items = new[]
            {
                _helpButton,
                // Put a space between the buttons
                new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
                _bufferButton,
                _resetButton
            };

            UIToolbar entryToolbar = new UIToolbar();
            entryToolbar.TranslatesAutoresizingMaskIntoConstraints = false;

            _bufferDistanceEntry = new UITextField();
            _bufferDistanceEntry.TranslatesAutoresizingMaskIntoConstraints = false;
            _bufferDistanceEntry.Text = "10";
            _bufferDistanceEntry.BackgroundColor = UIColor.FromWhiteAlpha(1, .8f);
            _bufferDistanceEntry.Layer.CornerRadius = 4;
            _bufferDistanceEntry.LeftView = new UIView(new CGRect(0, 0, 5, 20));
            _bufferDistanceEntry.LeftViewMode = UITextFieldViewMode.Always;
            _bufferDistanceEntry.KeyboardType = UIKeyboardType.Default;

            UILabel bufferDistanceEntryLabel = new UILabel();
            bufferDistanceEntryLabel.TranslatesAutoresizingMaskIntoConstraints = false;
            bufferDistanceEntryLabel.Text = "Buffer size:";

            // Add the views.
            View.AddSubviews(_myMapView, controlsToolbar, entryToolbar, bufferDistanceEntryLabel, _bufferDistanceEntry);

            // Lay out the views.
            NSLayoutConstraint.ActivateConstraints(new[]
            {
                entryToolbar.TopAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.TopAnchor),
                entryToolbar.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor),
                entryToolbar.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor),

                bufferDistanceEntryLabel.LeadingAnchor.ConstraintEqualTo(entryToolbar.LayoutMarginsGuide.LeadingAnchor, 8),
                bufferDistanceEntryLabel.CenterYAnchor.ConstraintEqualTo(entryToolbar.CenterYAnchor),

                _bufferDistanceEntry.TrailingAnchor.ConstraintEqualTo(entryToolbar.LayoutMarginsGuide.TrailingAnchor),
                _bufferDistanceEntry.CenterYAnchor.ConstraintEqualTo(entryToolbar.CenterYAnchor),
                _bufferDistanceEntry.LeadingAnchor.ConstraintEqualTo(bufferDistanceEntryLabel.TrailingAnchor, 8),

                _myMapView.TopAnchor.ConstraintEqualTo(entryToolbar.BottomAnchor),
                _myMapView.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor),
                _myMapView.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor),
                _myMapView.BottomAnchor.ConstraintEqualTo(controlsToolbar.TopAnchor),

                controlsToolbar.LeadingAnchor.ConstraintEqualTo(View.LeadingAnchor),
                controlsToolbar.TrailingAnchor.ConstraintEqualTo(View.TrailingAnchor),
                controlsToolbar.BottomAnchor.ConstraintEqualTo(View.SafeAreaLayoutGuide.BottomAnchor)
            });
        }

        private bool HandleTextField(UITextField textField)
        {
            // This method allows pressing 'return' to dismiss the software keyboard.
            textField.ResignFirstResponder();
            return true;
        }

        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);

            // Subscribe to events.
            _bufferDistanceEntry.ShouldReturn += HandleTextField;
            _myMapView.GeoViewTapped += MyMapView_GeoViewTapped;
            _helpButton.Clicked += ShowHelpAlert;
            _resetButton.Clicked += ClearButton_Click;
            _bufferButton.Clicked += PromptForUnionChoice;
        }

        public override void ViewDidDisappear(bool animated)
        {
            base.ViewDidDisappear(animated);

            // Unsubscribe from events, per best practice.
            _myMapView.GeoViewTapped -= MyMapView_GeoViewTapped;
            _bufferDistanceEntry.ShouldReturn -= HandleTextField;
            _helpButton.Clicked -= ShowHelpAlert;
            _resetButton.Clicked -= ClearButton_Click;
            _bufferButton.Clicked -= PromptForUnionChoice;
        }
    }
}

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