Visualize KML placemarks

A KML placemark is a feature with associated geometry. LuciadRIA represents it as KMLPlacemarkFeature. Visualizing KML data with KML placemarks on a map requires two steps:

  1. Create a KMLModel by loading a KML file.

  2. Use a KMLLayer to visualize the model.

//First decode the data into a model
const model = new KMLModel(url);

//Create a layer for the model
//The layer visualizes the KML data using the
//styling information provided in the data
const layer = new KMLLayer(model);

//Add the layer to the map
map.layerTree.addChild(layer);

You can also use a KMLCodec with any kind of Store. For example, you can use a WFSFeatureStore to connect to a WFS service returning KML data, or you can implement a custom Store that decodes KML data from a string.

The example shows how to visualize KML data using a custom Store that reads KML data directly from a string.
//StringStore is a read only store that can handle KML data from a string
class StringStore implements Store {
  private readonly content: string;
  private readonly codec: Codec;

  constructor(kmlString: string) {
    this.content = kmlString;
    this.codec = new KMLCodec();
  }

  query(_query?: object, _options?: object): Cursor | Promise<Cursor> {
    return this.codec.decode({content: this.content});
  }
}

//Instantiate the StringStore and configure a FeatureModel with it.
//Where the kmlString contains a text with KML data
const store = new StringStore(kmlString);
const model = new FeatureModel(store);

//Create a layer for the model
const layer = new KMLLayer(model as KMLModel);

//Add the layer to the map
map.layerTree.addChild(layer);

A KML network link is a feature that references a KML or KMZ file on a local or remote network. LuciadRIA represents a KML network link as KMLNetworkLinkFeature. When the KMLCodec encounters a KML network link while it’s decoding KML data, it emits a "KMLNetworkLink" event with the KMLNetworkLinkFeature instance. For your convenience, the KMLModel propagates this event from the KMLCodec. You can register a handler function that consumes this event to create a new KMLLayer for the emitted KMLNetworkLinkFeature.

You don’t get instances of KMLNetworkLinkFeature when you iterate over a Cursor returned by KMLCodec.decode().

Handle KML ground overlays

A KML ground overlay represents an image overlay draped onto the terrain. Both the KMLCodec and the KMLModel emit the "KMLGroundOverlay" event with a KMLGroundOverlayFeature. You can register a handler function that consumes this event to create a new RasterTileSetLayer for the emitted KMLGroundOverlayFeature.

You don’t get instances of KMLGroundOverlayFeature when you iterate over a Cursor returned by KMLCodec.decode().

Example of how to handle "KMLNetworkLink" and "KMLGroundOverlay" events

The example shows how to handle the "KMLNetworkLink" and "KMLGroundOverlay" events.
//Register handlers for KML events
addListeners(parentLayer);

function addListeners(layer: KMLLayer) {
  (layer.model as KMLModel)?.on("KMLNetworkLink",
      (networkLink: KMLNetworkLinkFeature) => onKMLNetworkLinkEvent(networkLink, layer));
  (layer.model as KMLModel)?.on("KMLGroundOverlay",
      (groundOverlay: KMLGroundOverlayFeature) => onKMLGroundOverlayEvent(groundOverlay, layer));
}

//Handler to consume KMLNetworkLink event
function onKMLNetworkLinkEvent(networkLink: KMLNetworkLinkFeature, parentLayer: KMLLayer) {
  const {link, name, id} = networkLink.properties;
  if (link.href) {
    //Create a new KML layer that points to the network link's KML data
    const kmlLayerForNetworkLink = new KMLLayer(new KMLModel(link.href), {
      id: `${id}`,
      label: `${name}`
    });
    //Register handlers for newly created KML layer
    addListeners(kmlLayerForNetworkLink);
    //Add the layer to the map
    map.layerTree.addChild(kmlLayerForNetworkLink, "above", parentLayer);
  }
}

//Handler to consume KMLGroundOverlay event
function onKMLGroundOverlayEvent(groundOverlay: KMLGroundOverlayFeature, parentLayer: KMLLayer) {
  const {id, properties, shape} = groundOverlay;
  const {icon, name} = properties;

  if (shape && shape.bounds && icon && icon.href) {
    //Create a new raster layer with the referenced image
    const model = new UrlTileSetModel({
      reference: getReference("CRS:84"),
      bounds: shape?.bounds,
      baseURL: icon.href,
      levelCount: 1
    });
    const rasterLayer = new RasterTileSetLayer(model, {
      id: `${id}`,
      label: `${name}`
    });

    //Add the layer to the map
    map.layerTree.addChild(rasterLayer, "below", parentLayer);
  }
}

Handle the KML data structure

KML data holds features nested in KML containers: KML document and KML folder. LuciadRIA represents those as KMLDocumentContainer and KMLFolderContainer. Both KMLDocumentContainer and KMLFolderContainer expose the children property with a value that’s an ordered array of all discovered KML features of the types KMLFolderContainer, KMLPlacemarkFeature, but also the types KMLNetworkLinkFeature and MLGroundOverlayFeature.

When the KMLCodec has fully decoded the KML data, it emits a single "KMLTree" event with the top-most KML container element. Usually, that’s a KMLDocumentContainer. You can register a handler for the "KMLTree" event on the KMLCodec or KMLModel to build a user interface that reflects the hierarchical data structure.

Each KMLFeature has common properties that you can use for setting the initial states of UI components, for example:

  • open - specifies whether to show an expanded or collapsed Document or Folder.

  • visibility - specifies whether to make a feature visible or not when it’s first loaded.

  • snippet - a short description of the feature.

The example shows how to handle the "KMLTree" event to count specific features.
// Count all features in folders that contain the "Buildings" text in the "name" property
(kmlLayer.model as KMLModel)?.on("KMLTree", (treeData: KMLFeature[]) => {
  const count = treeData.reduce((result: number, feature: KMLFeature) => {
    return (feature instanceof KMLFolderContainer &&
            feature.properties.name &&
            feature.properties.name.indexOf("Buildings") > -1) ?
           result + feature.children.length :
           result
  }, 0);
  console.log("Number of features in 'Buildings' folders", count);
});

See the KML sample to find out how to create a UI component which displays the data structure of loaded KML.