Skip to content

Commit e960173

Browse files
authored
Merge pull request #452 from Esri/destiny/Update-Create-and-edit-geometries-sample
[Update] Create and edit geometries sample
2 parents 4321b05 + 0ecb0dd commit e960173

File tree

4 files changed

+215
-20
lines changed

4 files changed

+215
-20
lines changed

Shared/Samples/Create and edit geometries/CreateAndEditGeometriesView.swift

Lines changed: 209 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,43 @@ import SwiftUI
1717

1818
/// A view that shows how to interact with the geometry editor.
1919
struct CreateAndEditGeometriesView: View {
20-
/// The map to display in the view.
21-
@State private var map = Map(basemapStyle: .arcGISTopographic)
20+
/// A map with an imagery basemap.
21+
@State private var map: Map = {
22+
let map = Map(basemapStyle: .arcGISImagery)
23+
// A viewpoint centered at the island of Inis Meáin (Aran Islands) in Ireland.
24+
map.initialViewpoint = Viewpoint(
25+
center: Point(latitude: 53.08230, longitude: -9.5920),
26+
scale: 5_000
27+
)
28+
return map
29+
}()
2230

2331
/// The view model for this sample.
2432
@StateObject private var model = GeometryEditorModel()
2533

34+
/// The screen point to perform an identify operation.
35+
@State private var identifyScreenPoint: CGPoint?
36+
2637
var body: some View {
2738
VStack {
28-
MapView(map: map, graphicsOverlays: [model.geometryOverlay])
29-
.geometryEditor(model.geometryEditor)
39+
MapViewReader { proxy in
40+
MapView(map: map, graphicsOverlays: [model.geometryOverlay])
41+
.geometryEditor(model.geometryEditor)
42+
.onSingleTapGesture { screenPoint, _ in
43+
identifyScreenPoint = screenPoint
44+
}
45+
.task(id: identifyScreenPoint) {
46+
guard let identifyScreenPoint,
47+
let identifyResult = try? await proxy.identify(
48+
on: model.geometryOverlay,
49+
screenPoint: identifyScreenPoint,
50+
tolerance: 5
51+
),
52+
let graphic = identifyResult.graphics.first,
53+
!model.isStarted else { return }
54+
model.startEditing(with: graphic)
55+
}
56+
}
3057
}
3158
.toolbar {
3259
ToolbarItem(placement: .primaryAction) {
@@ -197,11 +224,11 @@ private extension GeometryEditorMenu {
197224
Divider()
198225

199226
Button(role: .destructive) {
200-
model.clearSavedSketches()
227+
model.deleteAllGeometries()
201228
} label: {
202-
Label("Clear Saved Sketches", systemImage: "trash")
229+
Label("Delete All Geometries", systemImage: "trash")
203230
}
204-
.disabled(!model.canClearSavedSketches)
231+
.disabled(!model.canClearGraphics)
205232
}
206233
}
207234

@@ -296,8 +323,8 @@ private class GeometryEditorModel: ObservableObject {
296323
/// The graphics overlay used to save geometries to.
297324
let geometryOverlay = GraphicsOverlay(renderingMode: .dynamic)
298325

299-
/// A Boolean value indicating if the saved sketches can be cleared.
300-
@Published private(set) var canClearSavedSketches = false
326+
/// A Boolean value indicating if the initial graphics and saved sketches can be cleared.
327+
@Published private(set) var canClearGraphics = false
301328

302329
/// A Boolean value indicating if the geometry editor has started.
303330
@Published private(set) var isStarted = false
@@ -314,43 +341,89 @@ private class GeometryEditorModel: ObservableObject {
314341
isUniformScale ? .uniform : .stretch
315342
}
316343

344+
/// The selected graphic to edit.
345+
private var selectedGraphic: Graphic?
346+
347+
init() {
348+
let boundaryGraphic = Graphic(geometry: .boundary(), symbol: .polygon)
349+
350+
let road1Graphic = Graphic(geometry: .road1(), symbol: .polyline)
351+
352+
let road2Graphic = Graphic(geometry: .road2(), symbol: .polyline)
353+
354+
let outbuildingsGraphic = Graphic(geometry: .outbuildings(), symbol: .multipoint)
355+
356+
let houseGraphic = Graphic(geometry: .house(), symbol: .point)
357+
358+
geometryOverlay.addGraphics([
359+
boundaryGraphic,
360+
road1Graphic,
361+
road2Graphic,
362+
outbuildingsGraphic,
363+
houseGraphic
364+
])
365+
366+
canClearGraphics = true
367+
}
368+
317369
/// Saves the current geometry to the graphics overlay and stops editing.
318370
/// - Precondition: Geometry's sketch must be valid.
319371
func save() {
320372
precondition(geometryEditor.geometry?.sketchIsValid ?? false)
373+
374+
if selectedGraphic != nil {
375+
// Update geometry for edited graphic.
376+
updateGraphic()
377+
} else {
378+
// Add new graphic.
379+
addGraphic()
380+
}
381+
}
382+
383+
/// Updates the selected graphic with the current geometry.
384+
private func updateGraphic() {
385+
guard let selectedGraphic else { return }
386+
selectedGraphic.geometry = geometryEditor.stop()
387+
isStarted = false
388+
selectedGraphic.isVisible = true
389+
self.selectedGraphic = nil
390+
}
391+
392+
/// Adds a new graphic for the current geometry to the graphics overlay.
393+
private func addGraphic() {
321394
let geometry = geometryEditor.geometry!
322395
let graphic = Graphic(geometry: geometry, symbol: symbol(for: geometry))
323396
geometryOverlay.addGraphic(graphic)
324397
stop()
325-
canClearSavedSketches = true
398+
canClearGraphics = true
326399
}
327400

328-
/// Clears all the saved sketches on the graphics overlay.
329-
func clearSavedSketches() {
401+
/// Removes the initial graphics and saved sketches on the graphics overlay.
402+
func deleteAllGeometries() {
330403
geometryOverlay.removeAllGraphics()
331-
canClearSavedSketches = false
404+
canClearGraphics = false
332405
}
333406

334407
/// Stops editing with the geometry editor.
335408
func stop() {
336409
geometryEditor.stop()
337410
isStarted = false
411+
selectedGraphic?.isVisible = true
338412
}
339413

340414
/// Returns the symbology for graphics saved to the graphics overlay.
341415
/// - Parameter geometry: The geometry of the graphic to be saved.
342416
/// - Returns: Either a marker or fill symbol depending on the type of provided geometry.
343417
private func symbol(for geometry: Geometry) -> Symbol {
344418
switch geometry {
345-
case is Point, is Multipoint:
346-
return SimpleMarkerSymbol(style: .circle, color: .blue, size: 20)
419+
case is Point:
420+
return .point
421+
case is Multipoint:
422+
return .multipoint
347423
case is Polyline:
348-
return SimpleLineSymbol(color: .blue, width: 2)
424+
return .polyline
349425
case is ArcGIS.Polygon:
350-
return SimpleFillSymbol(
351-
color: .gray.withAlphaComponent(0.5),
352-
outline: SimpleLineSymbol(color: .blue, width: 2)
353-
)
426+
return .polygon
354427
default:
355428
fatalError("Unexpected geometry type")
356429
}
@@ -385,6 +458,122 @@ private class GeometryEditorModel: ObservableObject {
385458
geometryEditor.start(withType: geometryType)
386459
isStarted = true
387460
}
461+
462+
/// Starts editing a given graphic with the geometry editor.
463+
/// - Parameter graphic: The graphic to edit.
464+
func startEditing(with graphic: Graphic) {
465+
selectedGraphic = graphic
466+
graphic.isVisible = false
467+
let geometry = graphic.geometry!
468+
geometryEditor.start(withInitial: geometry)
469+
isStarted = true
470+
}
471+
}
472+
473+
private extension Geometry {
474+
// swiftlint:disable force_try
475+
static func house() -> Point {
476+
let jsonStr = """
477+
{"x":-1067898.59,
478+
"y":6998366.62,
479+
"spatialReference":{"latestWkid":3857,"wkid":102100}}
480+
"""
481+
return try! Point.fromJSON(jsonStr)
482+
}
483+
484+
static func road1() -> Polyline {
485+
let jsonStr = """
486+
{"paths":[[[-1068095.40,6998123.52],[-1068086.16,6998134.60],
487+
[-1068083.20,6998160.44],[-1068104.27,6998205.37],
488+
[-1068070.63,6998255.22],[-1068014.44,6998291.54],
489+
[-1067952.33,6998351.85],[-1067927.93,6998386.93],
490+
[-1067907.97,6998396.78],[-1067889.86,6998406.63],
491+
[-1067848.08,6998495.26],[-1067832.92,6998521.11]]],
492+
"spatialReference":{"latestWkid":3857,"wkid":102100}}
493+
"""
494+
return try! Polyline.fromJSON(jsonStr)
495+
}
496+
497+
static func road2() -> Polyline {
498+
let jsonStr = """
499+
{"paths":[[[-1067999.28,6998061.97],[-1067994.48,6998086.59],
500+
[-1067964.53,6998125.37],[-1067952.70,6998215.84],
501+
[-1067923.13,6998347.54],[-1067903.90,6998391.86],
502+
[-1067895.40,6998422.02],[-1067891.70,6998460.18],
503+
[-1067889.49,6998483.56],[-1067880.98,6998527.26]]],
504+
"spatialReference":{"latestWkid":3857,"wkid":102100}}
505+
"""
506+
return try! Polyline.fromJSON(jsonStr)
507+
}
508+
509+
static func outbuildings() -> Multipoint {
510+
let jsonStr = """
511+
{"points":[[-1067984.26,6998346.28],[-1067966.80,6998244.84],
512+
[-1067921.88,6998284.65],[-1067934.36,6998340.74],
513+
[-1067917.93,6998373.97],[-1067828.30,6998355.28],
514+
[-1067832.25,6998339.70],[-1067823.10,6998336.93],
515+
[-1067873.22,6998386.78],[-1067896.72,6998244.49]],
516+
"spatialReference":{"latestWkid":3857,"wkid":102100}}
517+
"""
518+
return try! Multipoint.fromJSON(jsonStr)
519+
}
520+
521+
static func boundary() -> Polygon {
522+
let jsonStr = """
523+
{"rings":[[[-1067943.67,6998403.86],[-1067938.17,6998427.60],
524+
[-1067898.77,6998415.86],[-1067888.26,6998398.80],
525+
[-1067800.85,6998372.93],[-1067799.61,6998342.81],
526+
[-1067809.38,6998330.00],[-1067817.07,6998307.85],
527+
[-1067838.07,6998285.34],[-1067849.10,6998250.38],
528+
[-1067874.02,6998256.00],[-1067879.87,6998235.95],
529+
[-1067913.41,6998245.03],[-1067934.84,6998291.34],
530+
[-1067948.41,6998251.90],[-1067961.18,6998186.68],
531+
[-1068008.59,6998199.49],[-1068052.89,6998225.45],
532+
[-1068039.37,6998261.11],[-1068064.12,6998265.26],
533+
[-1068043.32,6998299.88],[-1068036.25,6998327.93],
534+
[-1068004.43,6998409.28],[-1067943.67,6998403.86]]],
535+
"spatialReference":{"latestWkid":3857,"wkid":102100}}
536+
"""
537+
return try! Polygon.fromJSON(jsonStr)
538+
}
539+
// swiftlint:enable force_try
540+
}
541+
542+
private extension Symbol {
543+
static var point: SimpleMarkerSymbol {
544+
SimpleMarkerSymbol(
545+
style: .square,
546+
color: .red,
547+
size: 10
548+
)
549+
}
550+
551+
static var multipoint: SimpleMarkerSymbol {
552+
SimpleMarkerSymbol(
553+
style: .circle,
554+
color: .yellow,
555+
size: 5
556+
)
557+
}
558+
559+
static var polyline: SimpleLineSymbol {
560+
SimpleLineSymbol(
561+
color: .blue,
562+
width: 2
563+
)
564+
}
565+
566+
static var polygon: SimpleFillSymbol {
567+
SimpleFillSymbol(
568+
style: .solid,
569+
color: .red.withAlphaComponent(0.3),
570+
outline: SimpleLineSymbol(
571+
style: .dash,
572+
color: .black,
573+
width: 1
574+
)
575+
)
576+
}
388577
}
389578

390579
#Preview {

Shared/Samples/Create and edit geometries/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ A field worker can mark features of interest on a map using an appropriate geome
1313

1414
Tap the pencil button to choose a geometry editor tool. Begin interactively sketching on the map view. Tap the pencil button again for editing options.
1515

16+
When using the reticle vertex tool, you can move the map position of the reticle by dragging and zooming the map. Insert a vertex under the reticle by tapping on the map. Move a vertex by tapping when the reticle is located over a vertex, drag the map to move the position of the reticle, then tap a second time to place the vertex.
17+
1618
## How it works
1719

1820
1. Create a `GeometryEditor` and assign it to a map view with the `geometryEditor` view modifier.
@@ -31,6 +33,10 @@ Tap the pencil button to choose a geometry editor tool. Begin interactively sket
3133
* GraphicsOverlay
3234
* MapView
3335

36+
## Additional information
37+
38+
The sample opens with the ArcGIS Imagery basemap centered on the island of Inis Meáin (Aran Islands) in Ireland. Inis Meáin comprises a landscape of interlinked stone walls, roads, buildings, archaeological sites, and geological features, producing complex geometrical relationships.
39+
3440
## Tags
3541

3642
draw, edit, freehand, geometry editor, sketch, vertex
151 KB
Loading
140 KB
Loading

0 commit comments

Comments
 (0)