Create and save KML file

View on GitHubSample viewer app

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

Create and save KML file app

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

Click on the map to add the type of geometry selected in the spinner. Click the "Complete Sketch" button to add the geometry to the KML document as a new KML placemark. When you are finished adding KML nodes, click on the "Save KMZ file" button to save the active KML document as a .kmz file on your system.

How it works

  1. Create a KmlDocument
  2. Create a KmlDataset using the KmlDocument.
  3. Create a KmlLayer using the KmlDataset and add it to the map's operational layers.
  4. Create Geometry using the SketchEditor.
  5. Project that Geometry to WGS84 using GeometryEngine.project(...).
  6. Create a KmlGeometry object using the projected Geometry.
  7. Create a KmlPlacemark using the KmlGeometry.
  8. Add the KmlPlacemark to the KmlDocument.
  9. Set a KmlStyle for the KmlPlacemark.
  10. When finished with adding KmlPlacemark nodes to the KmlDocument, save the KmlDocument to a file using the saveAsAsync method.

Relevant API

  • GeometryEngine.project
  • KmlDataset
  • KmlDocument
  • KmlGeometry
  • KmlLayer
  • KmlNode.saveAsASync
  • KmlPlacemark
  • KmlStyle
  • SketchEditor

Tags

Edit and Manage data Keyhole KML KMZ OGC

Sample Code

MainActivity.ktMainActivity.ktPointSymbolAdapter.kt
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
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
/*
 * Copyright 2019 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.
 *
 */

package com.esri.arcgisruntime.sample.createandsavekmlfile

import android.content.DialogInterface
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment
import com.esri.arcgisruntime.geometry.GeometryEngine
import com.esri.arcgisruntime.geometry.GeometryType
import com.esri.arcgisruntime.geometry.SpatialReferences
import com.esri.arcgisruntime.layers.KmlLayer
import com.esri.arcgisruntime.mapping.ArcGISMap
import com.esri.arcgisruntime.mapping.BasemapStyle
import com.esri.arcgisruntime.mapping.view.MapView
import com.esri.arcgisruntime.mapping.view.SketchCreationMode
import com.esri.arcgisruntime.mapping.view.SketchEditor
import com.esri.arcgisruntime.mapping.view.SketchStyle
import com.esri.arcgisruntime.ogc.kml.*
import com.esri.arcgisruntime.sample.createandsavekmlfile.databinding.ActivityMainBinding
import com.esri.arcgisruntime.sample.createandsavekmlfile.databinding.KmlGeometryControlsLayoutBinding
import java.io.File

class MainActivity : AppCompatActivity() {

    private val activityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    private val mapView: MapView by lazy {
        activityMainBinding.mapView
    }

    private val kmlGeometryControlsLayoutBinding: KmlGeometryControlsLayoutBinding by lazy {
        activityMainBinding.controls
    }

    private val pointSymbolSpinner: Spinner by lazy {
        kmlGeometryControlsLayoutBinding.pointSymbolSpinner
    }

    private val sketchCreationModeSpinner: Spinner by lazy {
        kmlGeometryControlsLayoutBinding.sketchCreationModeSpinner
    }

    private val pointSymbolTextView: TextView by lazy {
        kmlGeometryControlsLayoutBinding.pointSymbolTextView
    }

    private val colorSpinner: Spinner by lazy {
        kmlGeometryControlsLayoutBinding.colorSpinner
    }

    private val colorTextView: TextView by lazy {
        kmlGeometryControlsLayoutBinding.colorTextView
    }

    private val kmlDocument by lazy { KmlDocument() }

    private val pointSymbolUrls by lazy {
        listOf(
            "http://static.arcgis.com/images/Symbols/Shapes/BlueCircleLargeB.png",
            "http://static.arcgis.com/images/Symbols/Shapes/BlueDiamondLargeB.png",
            "http://static.arcgis.com/images/Symbols/Shapes/BluePin1LargeB.png",
            "http://static.arcgis.com/images/Symbols/Shapes/BluePin2LargeB.png",
            "http://static.arcgis.com/images/Symbols/Shapes/BlueSquareLargeB.png",
            "http://static.arcgis.com/images/Symbols/Shapes/BlueStarLargeB.png"
        )
    }

    // set the default color to blue
    var color: Int = Color.parseColor("Blue")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(activityMainBinding.root)

        // authentication with an API key or named user is required to access basemaps and other
        // location services
        ArcGISRuntimeEnvironment.setApiKey(BuildConfig.API_KEY)

        // create a map with a dark gray vector basemap and add a KML layer
        val map = ArcGISMap(BasemapStyle.ARCGIS_DARK_GRAY).apply {
            // create a KML layer from a blank KML document and add it to the map
            operationalLayers.add(KmlLayer(KmlDataset(kmlDocument)))
        }

        mapView.apply {
            // add the map to the map view
            this.map = map

            // create a sketch editor and add it to the map view
            sketchEditor = SketchEditor().apply {
                sketchStyle = SketchStyle()
            }
        }

        // once the map is done loading, create spinners
        map.addDoneLoadingListener {
            createSpinners()
        }
    }

    /**
     * Starts the sketch editor based on the selected sketch creation mode.
     */
    private fun startSketch(sketchEditor: SketchEditor, sketchCreationMode: SketchCreationMode) {
        mapView.run {
            // stop the sketch editor
            sketchEditor.stop()
            // start the sketch editor with the selected creation mode
            sketchEditor.start(sketchCreationMode)
        }
    }

    /**
     * Take the current sketch and use it to create a KML placemark. Add the KML placemark as a child
     * node to the KML document.
     */
    fun addKmlPlaceMark(view: View) {
        if (mapView.sketchEditor.isSketchValid) {
            // project the sketched geometry to WGS84 to comply with the KML standard
            val sketchGeometry = mapView.sketchEditor.geometry
            val projectedGeometry =
                GeometryEngine.project(sketchGeometry, SpatialReferences.getWgs84())

            // stop the current sketch
            mapView.sketchEditor.stop()

            // create a new KML placemark
            val currentKmlPlacemark =
                KmlPlacemark(KmlGeometry(projectedGeometry, KmlAltitudeMode.CLAMP_TO_GROUND))

            // update the style of the current KML placemark
            val kmlStyle = KmlStyle()
            when (sketchGeometry.geometryType) {
                GeometryType.POINT -> {
                    kmlStyle.iconStyle =
                        KmlIconStyle(
                            KmlIcon(pointSymbolUrls[pointSymbolSpinner.selectedItemPosition]),
                            1.0
                        )
                }
                GeometryType.POLYLINE -> {
                    kmlStyle.lineStyle = KmlLineStyle(color, 8.0)
                }
                GeometryType.POLYGON -> {
                    kmlStyle.polygonStyle = KmlPolygonStyle(color).apply {
                        isFilled = true
                        isOutlined = false
                    }
                }
                else -> {
                    Toast.makeText(
                        this,
                        "Geometry type not supported in this sample.",
                        Toast.LENGTH_LONG
                    )
                        .show()
                }
            }
            currentKmlPlacemark.style = kmlStyle

            // add the placemark to the kml document
            kmlDocument.childNodes.add(currentKmlPlacemark)
        } else {
            Toast.makeText(this, "Sketch invalid!", Toast.LENGTH_LONG).show()
        }
        // start a new sketch
        startSketch(
            mapView.sketchEditor,
            SketchCreationMode.valueOf(sketchCreationModeSpinner.selectedItem.toString())
        )
    }


    /**
     * Create a save dialog to get a file name and save the KML Document to a KMZ file.
     */
    fun createSaveDialog(view: View) {
        // create an edit text to choose a file name to save the KML document to
        val fileNameEditText = EditText(applicationContext).apply {
            // set a default file name
            setText(getString(R.string.default_save_name))
        }
        // create an alert dialog
        AlertDialog.Builder(this).apply {
            // set the alert dialog title
            setTitle("Please define a file name:")
            // add the edit text to the view
            setView(fileNameEditText)
            // set positive button to call save async on the KML document
            setPositiveButton("Save") { _: DialogInterface, _: Int ->
                // save the KML document to the device with the file name from the edit text box
                val saveFuture =
                    kmlDocument.saveAsAsync(getExternalFilesDir(null)?.path + File.separator + fileNameEditText.text.toString())
                saveFuture.addDoneListener {
                    try {
                        // call get on the save future to check if it saved correctly
                        saveFuture.get()
                        // notify the file has been saved
                        Toast.makeText(
                            applicationContext,
                            "Your KML document was saved as: " + fileNameEditText.text,
                            Toast.LENGTH_LONG
                        ).show()
                    } catch (e: Exception) {
                        // notify the file was not saved correctly
                        Toast.makeText(
                            applicationContext,
                            "KML document was not saved: " + e.message,
                            Toast.LENGTH_LONG
                        ).show()
                    }
                }
            }
            setCancelable(true)
        }.show()
    }

    /**
     * Create the geometry, point symbol and color spinners.
     */
    private fun createSpinners() {
        // create sketch create mode type spinner
        sketchCreationModeSpinner.apply {
            adapter = ArrayAdapter(
                applicationContext,
                R.layout.spinner_row,
                listOf(
                    SketchCreationMode.POINT.toString(),
                    SketchCreationMode.POLYLINE.toString(),
                    SketchCreationMode.POLYGON.toString()
                )
            )
            onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
                override fun onItemSelected(
                    parent: AdapterView<*>?,
                    view: View?,
                    position: Int,
                    id: Long
                ) {
                    // get the sketch creation mode
                    with(SketchCreationMode.valueOf(selectedItem.toString())) {
                        startSketch(mapView.sketchEditor, this)
                        // show style controls relevant to the selected sketch creation mode
                        when (SketchCreationMode.POINT) {
                            this -> {
                                pointSymbolSpinner.visibility = View.VISIBLE
                                pointSymbolTextView.visibility = View.VISIBLE
                                colorSpinner.visibility = View.GONE
                                colorTextView.visibility = View.GONE
                            }
                            else -> {
                                colorSpinner.visibility = View.VISIBLE
                                colorTextView.visibility = View.VISIBLE
                                pointSymbolSpinner.visibility = View.GONE
                                pointSymbolTextView.visibility = View.GONE
                            }
                        }
                    }
                }

                override fun onNothingSelected(parent: AdapterView<*>?) {}
            }
        }

        // create point symbol spinner
        pointSymbolSpinner.apply {
            val listOfSymbols = listOf(
                R.drawable.blue_circle,
                R.drawable.blue_diamond,
                R.drawable.blue_pin_1,
                R.drawable.blue_pin_2,
                R.drawable.blue_square,
                R.drawable.blue_star
            )
            adapter = PointSymbolAdapter(
                applicationContext,
                listOfSymbols
            )
        }

        colorSpinner.apply {
            adapter = ArrayAdapter(
                applicationContext, R.layout.spinner_row, listOf(
                    "BLUE", "GREEN", "CYAN", "RED", "MAGENTA", "YELLOW"
                )
            )
            onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
                override fun onItemSelected(
                    parent: AdapterView<*>?,
                    view: View?,
                    position: Int,
                    id: Long
                ) {
                    color = Color.parseColor(selectedItem.toString())
                }

                override fun onNothingSelected(parent: AdapterView<*>?) {}
            }
        }
    }

    override fun onPause() {
        mapView.pause()
        super.onPause()
    }

    override fun onResume() {
        super.onResume()
        mapView.resume()
    }

    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}

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