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:
-
Register an event handler on
KMLModel
orKMLCodec
for the"KMLScreenOverlay"
event. -
In the handler, create the
HTMLElement
that holds the image referred to by the KML screen overlay. -
Position and resize the
HTMLElement
with the image, based on the properties of theKMLScreenOverlayFeature
.
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.
KMLScreenOverlay
event.
kmlModel.on("KMLScreenOverlay", (screenOverlay: KMLScreenOverlayFeature) => {
//For an example implementation see ScreenOverlay.tsx in the KML sample.
createOverlay(screenOverlay);
}
)
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
};
}
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";
}
}
}
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 + ")";
}