Create and save KML file

View on GitHub
Sample 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.ktPointSymbolAdapter.kt
                                                                                                                                                                                                                                                                                                                                                  
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<String>(
        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.INVISIBLE
                colorTextView.visibility = View.INVISIBLE
              }
              else -> {
                colorSpinner.visibility = View.VISIBLE
                colorTextView.visibility = View.VISIBLE
                pointSymbolSpinner.visibility = View.INVISIBLE
                pointSymbolTextView.visibility = View.INVISIBLE
              }
            }
          }
        }

        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<String>(
        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.