Find the union, intersection, or difference of two geometries.
Use case
The different spatial operations (union, difference, symmetric difference, and intersection) can be used for a variety of spatial analyses. For example, government authorities may use the intersect operation to determine whether a proposed road cuts through a restricted piece of land such as a nature reserve or a private property.
When these operations are chained together, they become even more powerful. An analysis of food deserts within an urban area might begin by union-ing service areas of grocery stores, farmers markets, and food co-ops. Taking the difference between this single geometry of all services areas and that of a polygon delineating a neighborhood would reveal the areas within that neighborhood where access to healthy, whole foods may not exist.
How to use the sample
The sample provides an option to select a spatial operation. When an operation is selected, the resulting geometry is shown in red.
How it works
- Create an
AGSGraphicsOverlay
and add it to theAGSMapView
. - Create each polygon
AGSGeometry
usingAGSPolygonBuilder
. - Add the overlapping polygons to the graphics overlay.
- Perform spatial relationships between the polygons by using the appropriate operation:
class AGSGeometryEngine.union(ofGeometry1:geometry2:)
- This method returns the two geometries united together as one geometry.class AGSGeometryEngine.difference(ofGeometry1:geometry2:)
- This method returns the difference of Geometry1 from Geometry2.class AGSGeometryEngine.symmetricDifference(ofGeometry1:geometry2:)
- This method returns any part of Geometry1 or Geometry2 which do not intersect.class AGSGeometryEngine.intersection(ofGeometry1:geometry2:)
- This method returns the intersection of Geometry1 and Geometry2.
- Use the geometry that is returned from the method call to create a new
AGSGraphic
and add it to the graphics overlay for it to be displayed.
Relevant API
- AGSGeometry
- AGSGeometryEngine
- AGSGraphic
- AGSGraphicsOverlay
- class AGSGeometryEngine.difference(ofGeometry1:geometry2:)
- class AGSGeometryEngine.intersection(ofGeometry1:geometry2:)
- class AGSGeometryEngine.symmetricDifference(ofGeometry1:geometry2:)
- class AGSGeometryEngine.union(ofGeometry1:geometry2:)
Tags
analysis, combine, difference, geometry, intersection, merge, polygon, union
Sample Code
// Copyright 2017 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 SpatialOperationsViewController: UIViewController {
// MARK: Storyboard view and properties
/// The map view managed by the view controller.
@IBOutlet var mapView: AGSMapView! {
didSet {
// Initialize map with basemap.
mapView.map = AGSMap(basemapStyle: .arcGISTopographic)
// Add the graphics overlay with two polygon graphics and the result graphic to map view.
mapView.graphicsOverlays.add(makeGraphicsOverlay())
// Set the map view's viewpoint.
let center = AGSPoint(x: -13453, y: 6710127, spatialReference: .webMercator())
mapView.setViewpointCenter(center, scale: 30000, completion: nil)
}
}
/// The resulting graphic for the spatial operation.
private var resultGraphic: AGSGraphic!
private let polygon1: AGSGeometry = {
// Create the polygon 1.
let polygon = AGSPolygonBuilder(spatialReference: .webMercator())
polygon.addPointWith(x: -13960, y: 6709400)
polygon.addPointWith(x: -14660, y: 6710000)
polygon.addPointWith(x: -13760, y: 6710730)
polygon.addPointWith(x: -13300, y: 6710500)
polygon.addPointWith(x: -13160, y: 6710100)
return polygon.toGeometry()
}()
private let polygon2: AGSGeometry = {
// The outer ring of polygon 2.
let outerRing = AGSMutablePart(spatialReference: .webMercator())
outerRing.addPointWith(x: -13060, y: 6711030)
outerRing.addPointWith(x: -12160, y: 6710730)
outerRing.addPointWith(x: -13160, y: 6709700)
outerRing.addPointWith(x: -14560, y: 6710730)
outerRing.addPointWith(x: -13060, y: 6711030)
// The inner ring of polygon 2.
let innerRing = AGSMutablePart(spatialReference: .webMercator())
innerRing.addPointWith(x: -13060, y: 6710910)
innerRing.addPointWith(x: -14160, y: 6710630)
innerRing.addPointWith(x: -13160, y: 6709900)
innerRing.addPointWith(x: -12450, y: 6710660)
innerRing.addPointWith(x: -13060, y: 6710910)
// Create polygon 2.
let polygon = AGSPolygonBuilder(spatialReference: .webMercator())
polygon.parts.add(outerRing)
polygon.parts.add(innerRing)
return polygon.toGeometry()
}()
/// An enum of spatial operations.
private enum SpatialOperation: CaseIterable {
case none, union, difference, symmetricDifference, intersection
/// Human readable label strings for each spatial operation.
var label: String {
switch self {
case .none: return "None"
case .union: return "Union"
case .difference: return "Difference"
case .symmetricDifference: return "Symmetric Difference"
case .intersection: return "Intersection"
}
}
}
/// The selected operation.
private var selectedOperation = SpatialOperation.none
// MARK: Methods
func makeGraphicsOverlay() -> AGSGraphicsOverlay {
// A black line symbol for borders of the graphics.
let lineSymbol = AGSSimpleLineSymbol(style: .solid, color: .black, width: 1)
// The blue fill symbol of polygon 1.
let fillSymbol1 = AGSSimpleFillSymbol(style: .solid, color: .blue, outline: lineSymbol)
// The graphic of polygon 1.
let polygon1Graphic = AGSGraphic(geometry: polygon1, symbol: fillSymbol1)
// The green fill symbol of polygon 2.
let fillSymbol2 = AGSSimpleFillSymbol(style: .solid, color: .green, outline: lineSymbol)
// The graphic of polygon 2.
let polygon2Graphic = AGSGraphic(geometry: polygon2, symbol: fillSymbol2)
// Using red fill symbol with black border for result graphic.
let symbol = AGSSimpleFillSymbol(style: .solid, color: .red, outline: lineSymbol)
let graphic = AGSGraphic(geometry: nil, symbol: symbol)
resultGraphic = graphic
// An overlay to display polygon graphics.
let graphicsOverlay = AGSGraphicsOverlay()
// Add graphics to graphics overlay.
graphicsOverlay.graphics.addObjects(from: [polygon1Graphic, polygon2Graphic, graphic])
return graphicsOverlay
}
private func performOperation(_ operation: SpatialOperation) {
let resultGeometry: AGSGeometry?
switch operation {
case .none:
resultGeometry = nil
case .union:
resultGeometry = AGSGeometryEngine.union(ofGeometry1: polygon1, geometry2: polygon2)!
case .difference:
resultGeometry = AGSGeometryEngine.difference(ofGeometry1: polygon1, geometry2: polygon2)!
case .symmetricDifference:
resultGeometry = AGSGeometryEngine.symmetricDifference(ofGeometry1: polygon1, geometry2: polygon2)!
case .intersection:
resultGeometry = AGSGeometryEngine.intersection(ofGeometry1: polygon1, geometry2: polygon2)!
}
// Update the geometry.
resultGraphic.geometry = resultGeometry
}
@IBAction func chooseOperationBarButtonTapped(_ sender: UIBarButtonItem) {
let selectedIndex = SpatialOperation.allCases.firstIndex(of: selectedOperation)
let controller = OptionsTableViewController(labels: SpatialOperation.allCases.map { $0.label }, selectedIndex: selectedIndex) { [weak self] newIndex in
guard let self = self else { return }
let newOperation = SpatialOperation.allCases[newIndex]
self.selectedOperation = newOperation
// Perform the new spatial operation.
self.performOperation(newOperation)
}
// Configure the options controller as a popover.
controller.modalPresentationStyle = .popover
controller.presentationController?.delegate = self
controller.preferredContentSize = CGSize(width: 300, height: CGFloat(SpatialOperation.allCases.count) * 44)
controller.popoverPresentationController?.barButtonItem = sender
// Show the popover.
present(controller, animated: true)
}
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
// Add the source code button item to the right of navigation bar.
(navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["SpatialOperationsViewController", "OptionsTableViewController"]
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension SpatialOperationsViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// Show presented controller as a popover even on small displays.
return .none
}
}