Create and save KML file

View on GitHub

Construct a KML document and save it as a KMZ file.

KML style settings Sketching a KML

Use case

If you need to create and save data on the fly, you can use KML to create points, lines, and polygons by sketching on the map, customizing the style, and serializing them as KML nodes in a KML Document. Once complete, you can share the KML data with others that are using a KML reading application, such as ArcGIS Earth.

How to use the sample

Tap on the sketch button in the toolbar to add a new KML. Select a type of feature and choose its color or icon. Tap on the map to sketch the KML. Tap the sketch button and tap "Save Sketch" to complete the sketch. Tap the file export button in the toolbar to export the file. Tap the sketch button and the "Clear Saved Sketches" button to clear the current KML document.

How it works

  1. Create an KMLDocument.
  2. Create an KMLDataset using the KMLDocument.
  3. Create an KMLLayer using the KMLDataset and add it to the map's operationalLayers array.
  4. Create Geometry using GeometryEditor.
  5. Project that Geometry to WGS84 using GeometryEngine.project(_:into:).
  6. Create an KMLGeometry object using that projected Geometry.
  7. Create an KMLPlacemark using the KMLGeometry.
  8. Add the KMLPlacemark to the KMLDocument.
  9. Set the KMLStyle for the KMLPlacemark.
  10. When finished with adding KMLPlacemark nodes to the KMLDocument, save the KMLDocument to a file using the KMLNode.save(to:) method.

Relevant API

  • GeometryEditor
  • GeometryEngine
  • KMLDataset
  • KMLDocument
  • KMLGeometry
  • KMLLayer
  • KMLNode
  • KMLPlacemark
  • KMLStyle

Tags

Keyhole, KML, KMZ, OGC

Sample Code

CreateAndSaveKMLView.swiftCreateAndSaveKMLView.swiftCreateAndSaveKMLView.Model.swiftCreateAndSaveKMLView.Views.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
// 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
import UniformTypeIdentifiers

struct CreateAndSaveKMLView: View {
    /// The view model for this sample.
    @StateObject var model = Model()

    var body: some View {
        MapView(map: model.map)
            .geometryEditor(model.geometryEditor)
            .errorAlert(presentingError: $model.error)
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Menu {
                        if !model.isStarted {
                            // If the geometry editor is not started, show the main menu.
                            mainMenuContent
                        } else {
                            // If the geometry editor is started, show the edit menu.
                            editMenuContent
                        }
                    }  label: {
                        Label("Geometry Editor", systemImage: "pencil.tip.crop.circle")
                    }

                    Spacer()

                    Button {
                        model.showingFileExporter = true
                    } label: {
                        Label("Export File", systemImage: "square.and.arrow.up")
                    }
                    .disabled(model.fileExporterButtonIsDisabled)
                }
            }
            .task {
                for await geometry in model.geometryEditor.$geometry {
                    model.geometry = geometry
                }
            }
            .fileExporter(isPresented: $model.showingFileExporter, document: model.kmzFile, contentType: .kmz) { result in
                switch result {
                case .success:
                    // We no longer need the file locally.
                    model.kmzFile.deleteFile()

                    // We no longer have a local file so disable file exporter button.
                    model.fileExporterButtonIsDisabled = true
                case .failure(let error):
                    model.error = error
                }
            }
    }
}

extension CreateAndSaveKMLView {
    /// A KMZ file that can be used with the native file exporter.
    final class KMZFile: FileDocument {
        /// The KML document that is used to create the KMZ file.
        private let document: KMLDocument

        /// The temporary directory where the KMZ file will be stored.
        private let temporaryDirectory = FileManager.createTemporaryDirectory()

        /// The temporary URL to the KMZ file.
        private var temporaryDocumentURL: URL? {
            temporaryDirectory
                .appendingPathComponent("\(document.name).kmz")
        }

        static var readableContentTypes: [UTType] { [.kmz] }

        /// Creates a KMZ file with a KML document.
        /// - Parameter document: The KML document that is used when creating the KMZ file.
        init(document: KMLDocument) {
            self.document = document
        }

        // This initializer loads data that has been saved previously.
        init(configuration: ReadConfiguration) throws {
            fatalError("Loading KML files is not supported by this sample")
        }

        // This will be called when the system wants to write our data to disk
        func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
            guard let temporaryDocumentURL else { return FileWrapper() }
            return try FileWrapper(url: temporaryDocumentURL)
        }

        /// Deletes the temporarily stored KMZ file.
        func deleteFile() {
            try? FileManager.default.removeItem(at: temporaryDirectory)
        }

        /// Saves the KML document as a KMZ file to a temporary location.
        func saveFile() async throws {
            if document.name.isEmpty {
                document.name = "Untitled"
            }

            try await document.save(to: temporaryDocumentURL!)
        }
    }
}

private extension FileManager {
    /// Creates a temporary directory and returns the URL of the created directory.
    static func createTemporaryDirectory() -> URL {
        // swiftlint:disable:next force_try
        try! FileManager.default.url(
            for: .itemReplacementDirectory,
            in: .userDomainMask,
            appropriateFor: FileManager.default.temporaryDirectory,
            create: true
        )
    }
}

private extension UTType {
    /// A type that represents a KMZ file.
    static let kmz = UTType(filenameExtension: "kmz")!
}

#Preview {
    NavigationStack {
        CreateAndSaveKMLView()
    }
}

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