Display KML from a URL, portal item, or local KML file.
Use case
Keyhole Markup Language (KML) is a data format used by Google Earth. KML is popular as a transmission format for consumer use and for sharing geographic data between apps. You can use Runtime to display KML files, with full support for a variety of features, including network links, 3D models, screen overlays, and tours.
How to use the sample
Tap the toolbar button to select a source. A KML file from that source will be loaded and displayed in the map.
How it works
- To create a KML layer from a URL, create an
AGSKMLDataset
using the URL to the KML file. Then create anAGSKMLLayer
using the dataset. - To create a KML layer from a portal item, construct an
AGSPortalItem
with anAGSPortal
and the KML portal item ID. Then create anAGSKMLLayer
using theAGSPortalItem
. - To create a KML layer from a local file, create an
AGSKMLDataset
using the absolute file path to the local KML file. Then create anAGSKMLLayer
using the dataset. - Add the layer to the map's
operationalLayers
array.
Relevant API
- AGSKMLDataset
- AGSKMLLayer
Offline data
This sample uses the US State Capitals KML. It is downloaded from ArcGIS Online automatically.
About the data
This sample displays three different KML files:
- From URL - this is a map of the significant weather outlook produced by NOAA/NWS. It uses KML network links to always show the latest data.
- From local file - this is a map of U.S. state capitals. It doesn't define an icon, so the default pushpin is used for the points.
- From portal item - this is a map of U.S. states.
Tags
keyhole, KML, KMZ, OGC
Sample Code
// Copyright 2018 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 ArcGIS
/// A model for our three data sources.
private enum KMLDataSource: Int {
case url, localFile, portalItem
}
class DisplayKMLViewController: UIViewController {
@IBOutlet weak var mapView: AGSMapView!
/// The source currently loaded in the map.
private var displayedSource: KMLDataSource = .url {
didSet {
updateMapForDisplayedSource()
}
}
/// Loads the map contents based on the specified source.
private func updateMapForDisplayedSource() {
switch displayedSource {
case .url:
changeSourceToURL()
case .localFile:
changeSourceToLocalFile()
case .portalItem:
changeSourceToPortalItem()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Instantiate a map with a dark gray basemap.
let map = AGSMap(basemapStyle: .arcGISDarkGray)
// Display the map in the map view.
mapView.map = map
mapView.setViewpoint(AGSViewpoint(latitude: 39, longitude: -98, scale: 3.6978595474472E7))
// Show the initial KML source.
updateMapForDisplayedSource()
// Add the source code button item to the right of navigation bar.
(navigationItem.rightBarButtonItem as! SourceCodeBarButtonItem).filenames = ["DisplayKMLViewController"]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// if we're opening the table of data sources
if let sourceTableViewController = segue.destination as? DisplayKMLDataSourcesTableViewController {
// set the popover height
sourceTableViewController.preferredContentSize.height = 135
// set the popover delgate
sourceTableViewController.popoverPresentationController?.delegate = self
// setup the view controller
sourceTableViewController.initialSelectedIndex = displayedSource.rawValue
sourceTableViewController.selectionHandler = { [weak self] (dataSource) in
guard let self = self else {
return
}
// close the popover
self.presentedViewController?.dismiss(animated: true)
// set the displayed source based on the selection
self.displayedSource = dataSource
}
}
}
// MARK: - KML Loading
private func display(kmlLayer: AGSKMLLayer) {
// Clear the existing layers from the map
mapView.map?.operationalLayers.removeAllObjects()
// Add the KML layer to the map
mapView.map?.operationalLayers.add(kmlLayer)
// Show the progress indicator
UIApplication.shared.showProgressHUD(message: "Loading KML Layer")
// This load call is not required, but it allows for error
// feedback and progress indication
kmlLayer.load { [weak self] (error) in
// Close the progress indicator
UIApplication.shared.hideProgressHUD()
if let error = error {
self?.presentAlert(error: error)
}
}
}
private func changeSourceToURL() {
/// A URL of a remote KML file
let kmlDatasetURL = URL(string: "https://www.wpc.ncep.noaa.gov/kml/noaa_chart/WPC_Day1_SigWx.kml")!
let kmlDataset = AGSKMLDataset(url: kmlDatasetURL)
/// A KML layer created from a remote KML file
let kmlLayer = AGSKMLLayer(kmlDataset: kmlDataset)
display(kmlLayer: kmlLayer)
}
private func changeSourceToLocalFile() {
/// A dataset created by referencing the name of a KML file in the app bundle
let kmlDataset = AGSKMLDataset(name: "US_State_Capitals")
/// A KML layer created from a local KML file
let kmlLayer = AGSKMLLayer(kmlDataset: kmlDataset)
display(kmlLayer: kmlLayer)
}
private func changeSourceToPortalItem() {
let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
/// A remote KML portal item
let portalItem = AGSPortalItem(portal: portal, itemID: "9fe0b1bfdcd64c83bd77ea0452c76253")
/// A KML layer created from an ArcGIS Online portal item
let kmlLayer = AGSKMLLayer(item: portalItem)
display(kmlLayer: kmlLayer)
}
}
extension DisplayKMLViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// prevent the popover from displaying fullscreen in narrow contexts
return .none
}
}
class DisplayKMLDataSourcesTableViewController: UITableViewController {
fileprivate var initialSelectedIndex = 0
fileprivate var selectionHandler: ((KMLDataSource) -> Void)?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if indexPath.row == initialSelectedIndex {
cell.accessoryType = .checkmark
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// get the newly selected cell
guard let selectedCell = tableView.cellForRow(at: indexPath),
let displayedSource = KMLDataSource(rawValue: indexPath.row) else {
return
}
// add a checkmark to the selected cell
selectedCell.accessoryType = .checkmark
// change the displayed KML source based on the selected cell
selectionHandler?(displayedSource)
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
// remove the checkmark
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
}