A screen overlay represents an image that’s visualized on top of the map and always remains in the same position. A typical use case for a screen overlay is a map legend or an attribution icon.

To add a KML screen overlay to your view, you must:

  1. Register an event handler on KMLModel or KMLCodec for the "KMLScreenOverlay" event.

  2. In the handler, create the HTMLElement that holds the image referred to by the KML screen overlay.

  3. Position and resize the HTMLElement with the image, based on the properties of the KMLScreenOverlayFeature.

The following code snippets give an overview of how you can handle screen overlays. For a full implementation, see the ScreenOverlay of the KML sample.

Program: Register handler for KMLScreenOverlay event.
kmlModel.on("KMLScreenOverlay", (screenOverlay: KMLScreenOverlayFeature) => {
      //For an example implementation see ScreenOverlay.tsx in the KML sample.
      createOverlay(screenOverlay);
    }
)
Program: Add HTMLElement to view for KMLScreenOverlayFeature. (from samples/kml/ui/KMLUtility.ts)
export interface ScreenOverlayHandle extends Handle {
  element: HTMLDivElement;
  isVisible: () => boolean;
  setVisible: (visible: boolean) => void;
}

export function createScreenOverlay(map: Map, screenOverlayFeature: KMLScreenOverlayFeature): ScreenOverlayHandle {
  const props = screenOverlayFeature.properties;
  const screenDiv = createDiv(screenOverlayFeature, "ScreenDiv", "kml-screenoverlay-screen");

  const overlayDiv = createDiv(screenOverlayFeature, "OverlayDiv", "kml-screenoverlay-overlay");
  const setVisible = (visible: boolean) => {
    screenDiv.style.display = visible ? "block" : "none";
  };
  setVisible(screenOverlayFeature.properties.visibility);
  const isVisible = () => {
    return screenDiv.style.display === "block";
  };

  let kmlOverlayDiv = map.domNode.getElementsByClassName("kml-screenoverlay-container")[0];
  if (!kmlOverlayDiv) {
    kmlOverlayDiv = document.createElement("div");
    kmlOverlayDiv.className = "kml-screenoverlay-container";
    map.domNode.appendChild(kmlOverlayDiv);
  }

  kmlOverlayDiv.appendChild(screenDiv);

  const imgNode = document.createElement("img");
  imgNode.id = screenOverlayFeature.id + "ImageNode";
  imgNode.className = "kml-screenoverlay-image";
  imgNode.src = screenOverlayFeature.properties.icon.href as string;

  screenDiv.appendChild(overlayDiv);
  overlayDiv.appendChild(imgNode);

  const handleResize = () => resizeScreenOverlayElement(map, screenOverlayFeature, imgNode, screenDiv);
  imgNode.onload = ((): void => {
    //Set size and position per KML spec
    resizeScreenOverlayElement(map, screenOverlayFeature, imgNode, screenDiv)
    positionScreenOverlay(props, screenDiv, overlayDiv);
    //Maintaining the aspect ratio in some cases requires dynamic sizing
    window.addEventListener("resize", handleResize);

    //Set the pivot point for the rotation. Only supported when it's defined with relative coordinates.
    if (props.rotationXY.xUnits === KMLUnits.FRACTION && props.rotationXY.yUnits === KMLUnits.FRACTION) {
      const x = `${getCSSValue(props.rotationXY.x, props.rotationXY.xUnits)}%`;
      const y = `${100 - getCSSValue(props.rotationXY.y, props.rotationXY.yUnits)}%`
      imgNode.style.transformOrigin = `${x} ${y}`;
    }

    imgNode.style.transform += `rotate(${-(props.rotation || 0)}deg`;
  });
  return {
    element: screenDiv,
    remove: () => {
      window.removeEventListener("resize", handleResize);
      const kmlOverlayDiv = map.domNode.getElementsByClassName("kml-screenoverlay-container")[0];
      if (kmlOverlayDiv) {
        kmlOverlayDiv.removeChild(screenDiv);
        if (!kmlOverlayDiv.hasChildNodes()) {
          map.domNode.removeChild(kmlOverlayDiv);
        }
      }
    },
    isVisible,
    setVisible
  };
}
Program: Apply correct size for KML screen overlay. (from samples/kml/ui/KMLUtility.ts)
function resizeScreenOverlayElement(map: Map, feature: KMLScreenOverlayFeature, imgNode: HTMLImageElement,
                                    screenDivSFCT: HTMLElement): void {
  const imgAspectRatio = imgNode.naturalWidth / imgNode.naturalHeight;
  const mapAspectRatio = map.domNode.clientWidth / map.domNode.clientHeight;
  const props = feature.properties;
  const xScalingMode = getScalingMode(props.size.x);
  const yScalingMode = getScalingMode(props.size.y);
  const xValue = getCSSValue(props.size.x, props.size.xUnits);
  const xUnit = getCSSUnit(props.size.xUnits);
  const yValue = getCSSValue(props.size.y, props.size.yUnits);
  const yUnit = getCSSUnit(props.size.yUnits);

  if (xScalingMode === ScalingMode.DEFINED_VALUE) {
    screenDivSFCT.style.width = xValue + xUnit;
  } else if (xScalingMode === ScalingMode.NATIVE) {
    screenDivSFCT.style.width = imgNode.naturalWidth + "px";
  }

  if (yScalingMode === ScalingMode.DEFINED_VALUE) {
    screenDivSFCT.style.height = yValue + yUnit;
    if (xScalingMode === ScalingMode.MAINTAIN_ASPECT) {
      if (props.size.yUnits === KMLUnits.FRACTION) {
        screenDivSFCT.style.width = (yValue * imgAspectRatio * (1 / mapAspectRatio)) + yUnit;
      } else {
        screenDivSFCT.style.width = (yValue * imgAspectRatio) + yUnit;
      }
    }
  } else if (yScalingMode === ScalingMode.NATIVE) {
    screenDivSFCT.style.height = imgNode.naturalHeight + "px";
    if (xScalingMode === ScalingMode.MAINTAIN_ASPECT) {
      screenDivSFCT.style.width = imgNode.naturalWidth + "px";
    }
  } else {
    if (xScalingMode === ScalingMode.DEFINED_VALUE) {
      if (props.size.yUnits === KMLUnits.FRACTION) {
        screenDivSFCT.style.height = (xValue * (1 / imgAspectRatio) * mapAspectRatio) + xUnit;
      } else {
        screenDivSFCT.style.height = (xValue * (1 / imgAspectRatio)) + xUnit;
      }
    } else if (xScalingMode === ScalingMode.NATIVE) {
      screenDivSFCT.style.height = imgNode.naturalHeight + "px";
    } else {
      screenDivSFCT.style.height = imgNode.naturalHeight + "px";
      screenDivSFCT.style.width = imgNode.naturalWidth + "px";
    }
  }
}
Program: Positioning of KML screen overlay. (from samples/kml/ui/KMLUtility.ts)
function positionScreenOverlay(props: KMLScreenOverlayFeatureProperties, screenDiv: HTMLElement,
                               overlayDiv: HTMLElement): void {
  // Move screen div to screenXY point on the map
  const xTranslate = getCSSValue(props.screenXY.x, props.screenXY.xUnits);
  const yTranslate = getCSSValue(props.screenXY.y, props.screenXY.yUnits);
  const xUnit = getCSSUnit(props.screenXY.xUnits);

  if (props.screenXY.xUnits === KMLUnits.INSET_PIXELS) {
    screenDiv.style.right = -xTranslate + xUnit;
    screenDiv.style.transform += "translateX(100%)";
  } else {
    screenDiv.style.left = xTranslate + xUnit;
  }

  const yUnit = getCSSUnit(props.screenXY.yUnits);
  if (props.screenXY.yUnits === KMLUnits.INSET_PIXELS) {
    screenDiv.style.top = -yTranslate + yUnit;
    screenDiv.style.transform += "translateY(-100%)";
  } else {
    screenDiv.style.bottom = yTranslate + yUnit;
  }

  // To apply the overlay anchor point (overlayXY), offset the anchoring div.
  let xAnchorTranslate = -getCSSValue(props.overlayXY.x, props.overlayXY.xUnits);
  if (props.overlayXY.xUnits === KMLUnits.INSET_PIXELS) {
    overlayDiv.style.transform += "translateX(-100%)";
    xAnchorTranslate = -xAnchorTranslate;
  }

  let yAnchorTranslate = getCSSValue(props.overlayXY.y, props.overlayXY.yUnits);
  if (props.overlayXY.yUnits === KMLUnits.INSET_PIXELS) {
    overlayDiv.style.transform += "translateY(100%)";
    yAnchorTranslate = -yAnchorTranslate;
  }

  const xAnchorTranslateString = xAnchorTranslate + getCSSUnit(props.overlayXY.xUnits);
  const yAnchorTranslateString = yAnchorTranslate + getCSSUnit(props.overlayXY.yUnits);
  overlayDiv.style.transform += "translate(" + xAnchorTranslateString + ", " + yAnchorTranslateString + ")";
}