Skip to content
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. Toggle on "Union" toggle 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(around, distances, shouldUnion) to create a Polygon. The parameter around are the points to buffer around, distances are the buffer distances for each point (in meters) and shouldUnion 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
  • 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

CreateBuffersAroundPointsView.swift
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 // Copyright 2023 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 ArcGIS import SwiftUI  struct CreateBuffersAroundPointsView: View {  /// The view model for the sample.  @StateObject private var model = Model()   /// The status of the sample.  @State private var status = Status.addPoints   /// The point where the map was tapped.  @State private var tapPoint: Point?   /// A Boolean value indicating whether union is on.  @State private var shouldUnion = false   /// A Boolean value indicating whether the input box is showing.  @State private var inputBoxIsPresented = false   /// The input obtained from the user for the buffer radius of a point.  @State private var bufferRadius: Double = 100   var body: some View {  // Create a map view to display the map.  MapView(map: model.map, graphicsOverlays: model.graphicsOverlays)  .onSingleTapGesture { _, mapPoint in  // Update tapPoint and bring up input box if point is within bounds.  if model.boundaryContains(mapPoint) {  tapPoint = mapPoint  inputBoxIsPresented = true  } else {  status = .outOfBoundsTap  }  }  .overlay(alignment: .top) {  Text(status.label)  .frame(maxWidth: .infinity, alignment: .center)  .padding(8)  .background(.thinMaterial, ignoresSafeAreaEdges: .horizontal)  }  .toolbar {  ToolbarItemGroup(placement: .bottomBar) {  // Union toggle switch.  Toggle(shouldUnion ? "Union Enabled" : "Union Disabled", isOn: $shouldUnion)  .onChange(of: shouldUnion) {  if !model.bufferPoints.isEmpty {  model.drawBuffers(unioned: shouldUnion)  }  }  Button("Clear") {  model.clearBufferPoints()  status = .addPoints  }  .disabled(model.bufferPoints.isEmpty)  }  }  .alert("Buffer Radius", isPresented: $inputBoxIsPresented, actions: {  TextField("radius in miles", value: $bufferRadius, format: .number)  .keyboardType(.numberPad)  Button("Done") {  guard let tapPoint else {  preconditionFailure("Missing tap point")  }   let newStatus: Status  // Check to ensure the tapPoint is within the boundary.  if model.boundaryContains(tapPoint) {  // Ensure that the input is valid.  if bufferRadius > 0 && bufferRadius < 300 {  model.addBuffer(point: tapPoint, radius: bufferRadius)  model.drawBuffers(unioned: shouldUnion)  newStatus = .bufferCreated  } else {  newStatus = .invalidInput  }  } else {  newStatus = .outOfBoundsTap  }  status = newStatus   // Set the radius to default value.  bufferRadius = 100  }  Button("Cancel", role: .cancel) { bufferRadius = 100 }  // Input alert message.  }, message: {  Text("Please enter a number between 0 and 300 miles.")  })  } }  private extension CreateBuffersAroundPointsView {  /// The view model for this sample.  class Model: ObservableObject {  /// A map centered on Texas with image layers.  let map: Map   /// The graphics overlays used in this sample.  var graphicsOverlays: [GraphicsOverlay] { [boundaryGraphicsOverlay, bufferGraphicsOverlay, tapPointsGraphicsOverlay] }   /// An array of the tapped points and their radii.  private(set) var bufferPoints: [(point: Point, radius: Double)] = []   /// The graphics overlay for the boundary around the valid area of use.  private let boundaryGraphicsOverlay: GraphicsOverlay   /// The graphics overlay for the points' buffers.  private let bufferGraphicsOverlay: GraphicsOverlay   /// The graphics overlay for the points of the tapped locations.  private let tapPointsGraphicsOverlay: GraphicsOverlay   /// A polygon that represents the valid area of use for the spatial reference.  private let boundaryPolygon: ArcGIS.Polygon   init() {  /// The spatial reference for the sample.  let statePlaneNorthCentralTexas = SpatialReference(wkid: WKID(32038)!)!   // Create boundary polygon.  boundaryPolygon = {  let boundaryPoints = [  Point(latitude: 31.720, longitude: -103.070),  Point(latitude: 34.580, longitude: -103.070),  Point(latitude: 34.580, longitude: -94.000),  Point(latitude: 31.720, longitude: -94.000)  ]  let polygon = GeometryEngine.project(Polygon(points: boundaryPoints), into: statePlaneNorthCentralTexas)!  return polygon  }()   // Create map.  map = Self.makeMap(  spatialReference: statePlaneNorthCentralTexas,  viewpointGeometry: boundaryPolygon  )   // Create graphics overlays.  boundaryGraphicsOverlay = Self.makeBoundaryGraphicsOverlay(  boundaryGeometry: boundaryPolygon  )  bufferGraphicsOverlay = Self.makeBufferGraphicsOverlay()  tapPointsGraphicsOverlay = Self.makeTappedPointsGraphicsOverlay()  }   /// Creates a map with image layers from a spatial reference.  /// - Parameters:  /// - spatialReference: The `SpatialReference` the `Map` is derived from.  /// - viewpointGeometry: The `Geometry` to center the map's viewpoint on.  /// - Returns: A new `Map` object with added base layers.  private static func makeMap(spatialReference: SpatialReference, viewpointGeometry: Geometry) -> Map {  // Create a map with the spatial reference.  let map = Map(spatialReference: spatialReference)  map.initialViewpoint = Viewpoint(boundingGeometry: viewpointGeometry)   // Add some base layers (counties, cities, and highways).  let usaLayer = ArcGISMapImageLayer(url: URL(  string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer"  )!)  map.basemap = Basemap(baseLayer: usaLayer)   return map  }   /// Creates a graphics overlay to show the spatial reference's valid area.  /// - Parameter boundaryGeometry: The `Geometry` to create the boundary graphic from.  /// - Returns: A new `GraphicsOverlay` object with a boundary graphic added.  private static func makeBoundaryGraphicsOverlay(boundaryGeometry: Geometry) -> GraphicsOverlay {  let graphicsOverlay = GraphicsOverlay()  let lineSymbol = SimpleLineSymbol(style: .dash, color: .red, width: 5)  let boundaryGraphic = Graphic(geometry: boundaryGeometry, symbol: lineSymbol)  graphicsOverlay.addGraphic(boundaryGraphic)  return graphicsOverlay  }   /// Creates a graphics overlay for the buffer graphics.  /// - Returns: A new `GraphicsOverlay` object to be used for the buffers.  private static func makeBufferGraphicsOverlay() -> GraphicsOverlay {  let graphicsOverlay = GraphicsOverlay()  let bufferPolygonOutlineSymbol = SimpleLineSymbol(style: .solid, color: .systemGreen, width: 3)  let bufferPolygonFillSymbol = SimpleFillSymbol(style: .solid, color: .yellow.withAlphaComponent(0.6), outline: bufferPolygonOutlineSymbol)  graphicsOverlay.renderer = SimpleRenderer(symbol: bufferPolygonFillSymbol)  return graphicsOverlay  }   /// Creates a graphics overlay for the tapped points graphics.  /// - Returns: A new `GraphicsOverlay` object to be used for the tapped points.  private static func makeTappedPointsGraphicsOverlay() -> GraphicsOverlay {  let graphicsOverlay = GraphicsOverlay()  let circleSymbol = SimpleMarkerSymbol(style: .circle, color: .red, size: 10)  graphicsOverlay.renderer = SimpleRenderer(symbol: circleSymbol)  return graphicsOverlay  }   /// Checks if a point is within the valid area of use for this sample.  /// - Parameter point: A `Point` to validate.  /// - Returns: A `Bool` indicating whether it is within bounds.  func boundaryContains(_ point: Point) -> Bool {  guard GeometryEngine.doesGeometry(boundaryPolygon, contain: point) else {  return false  }  return true  }   /// Draws points and their buffers on the map.  /// - Parameter unioned: A Boolean indicating whether the buffers should be unioned.  func drawBuffers(unioned: Bool) {  // Clear existing buffers graphics before drawing.  bufferGraphicsOverlay.removeAllGraphics()  tapPointsGraphicsOverlay.removeAllGraphics()   // Reduce the bufferPoints tuples into points and radii arrays.  let (points, radii) = bufferPoints.reduce(into: ([Point](), [Double]())) { (result, pointAndRadius) in  result.0.append(pointAndRadius.point)  result.1.append(pointAndRadius.radius)  }   // Create the buffers.  // Notice: the radius distances has the same unit of the map's spatial reference's unit.  // In this case, the statePlaneNorthCentralTexas spatial reference uses US feet.  let bufferPolygon = GeometryEngine.buffer(around: points, distances: radii, shouldUnion: unioned)   // Add the tap points to the tapPointsGraphicsOverlay.  points.forEach { point in  tapPointsGraphicsOverlay.addGraphic(Graphic(geometry: point))  }   // Add the buffers to the bufferGraphicsOverlay.  bufferPolygon.forEach { buffer in  bufferGraphicsOverlay.addGraphic(Graphic(geometry: buffer))  }  }   /// Adds a point with its radius to the buffer points array.  /// - Parameters:  /// - point: The center point to create a buffer.  /// - radius: The radius of the buffer.  func addBuffer(point: Point, radius: Double) {  // Update the buffer radius with the text value.  let radiusInMiles = Measurement(value: radius, unit: UnitLength.miles)   // The spatial reference in this sample uses US feet as its unit.  let radiusInFeet = radiusInMiles.converted(to: .feet).value   // Add point with radius to bufferPoints Array.  bufferPoints.append((point: point, radius: radiusInFeet))  }   /// Clears the bufferPoints array and related graphics.  func clearBufferPoints() {  bufferPoints.removeAll()  bufferGraphicsOverlay.removeAllGraphics()  tapPointsGraphicsOverlay.removeAllGraphics()  }  } }  private extension CreateBuffersAroundPointsView {  /// An enumeration for the sample status.  enum Status {  case addPoints, bufferCreated, outOfBoundsTap, invalidInput, noPoints   /// The text message associated with the current status for the overlay.  var label: String {  switch self {  case .addPoints: return "Tap on the map to add buffers."  case .bufferCreated: return "Buffer created."  case .outOfBoundsTap: return "Tap within the boundary to add buffer."  case .invalidInput: return "Enter a value between 0 and 300 to create a buffer."  case .noPoints: return "Add a point to draw the buffers."  }  }  } }  #Preview {  NavigationStack {  CreateBuffersAroundPointsView()  } }

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