Parameters
URL
A simple primary alert—check it out!
Display a single image on a Map with a StaticImageSource
.
The StaticImageSource
can display an image at an arbitrary extent. You can either pass a URL to the remote image, or provide a texture or an image to display.
import {
AdditiveBlending,
Mesh,
MeshBasicMaterial,
PlaneGeometry,
Vector3,
} from "three";
import { MapControls } from "three/examples/jsm/controls/MapControls.js";
import OSM from "ol/source/OSM.js";
import Extent from "@giro3d/giro3d/core/geographic/Extent.js";
import Instance from "@giro3d/giro3d/core/Instance.js";
import Map from "@giro3d/giro3d/entities/Map.js";
import ColorLayer from "@giro3d/giro3d/core/layer/ColorLayer.js";
import TiledImageSource from "@giro3d/giro3d/sources/TiledImageSource.js";
import Inspector from "@giro3d/giro3d/gui/Inspector.js";
import StaticImageSource from "@giro3d/giro3d/sources/StaticImageSource.js";
function bindTextInput(id, onChange) {
const element = document.getElementById(id);
if (!(element instanceof HTMLInputElement)) {
throw new Error(
"invalid binding element: expected HTMLInputElement, got: " +
element.constructor.name,
);
}
element.onchange = () => {
if (element.checkValidity()) {
onChange(element.value);
}
};
const setValue = (v) => {
element.value = v;
onChange(element.value);
};
const currentValue = element.value;
return [setValue, currentValue, element];
}
function bindButton(id, onClick) {
const element = document.getElementById(id);
if (!(element instanceof HTMLButtonElement)) {
throw new Error(
"invalid binding element: expected HTMLButtonElement, got: " +
element.constructor.name,
);
}
element.onclick = () => {
onClick(element);
};
return element;
}
const extent = new Extent(
"EPSG:3857",
-20037508.342789244,
20037508.342789244,
-20037508.342789244,
20037508.342789244,
);
const instance = new Instance({
target: "view",
crs: extent.crs,
backgroundColor: 0x0a3b59,
});
const map = new Map({ extent, backgroundColor: "white" });
instance.add(map);
// Create the OpenStreetMap color layer using an OpenLayers source.
// See https://openlayers.org/en/latest/apidoc/module-ol_source_OSM-OSM.html
// for more informations.
const osm = new ColorLayer({
name: "osm",
source: new TiledImageSource({ source: new OSM() }),
});
map.addLayer(osm);
instance.view.camera.position.set(0, 0, 80000000);
const controls = new MapControls(instance.view.camera, instance.domElement);
controls.enableRotate = false;
instance.view.setControls(controls);
Inspector.attach("inspector", instance);
let url = null;
const extentPreview = new Mesh(
new PlaneGeometry(1, 1, 1, 1),
new MeshBasicMaterial({
color: "white",
opacity: 0.1,
transparent: true,
blending: AdditiveBlending,
depthTest: false,
}),
);
instance.scene.add(extentPreview);
let topLeftCorner;
function drawExtent() {
return new Promise((resolve) => {
let clickCount = 0;
const onMouseMove = (mouseEvent) => {
if (topLeftCorner) {
const picked = instance.pickObjectsAt(mouseEvent)[0];
if (picked) {
const currentPoint = picked.point;
const width = Math.abs(topLeftCorner.x - currentPoint.x);
const height = Math.abs(topLeftCorner.y - currentPoint.y);
extentPreview.scale.set(width, height, 1);
const center = new Vector3().lerpVectors(
currentPoint,
topLeftCorner,
0.5,
);
extentPreview.position.copy(center);
extentPreview.updateMatrixWorld(true);
instance.notifyChange();
}
}
};
const onClick = (mouseEvent) => {
clickCount++;
const picked = instance.pickObjectsAt(mouseEvent)[0];
if (picked) {
controls.enabled = false;
extentPreview.visible = true;
const point = picked.point;
if (clickCount === 1) {
topLeftCorner = point;
extentPreview.scale.set(0, 0, 1);
} else if (clickCount === 2) {
instance.domElement.removeEventListener("mousedown", onClick);
instance.domElement.removeEventListener("mousemove", onMouseMove);
topLeftCorner = null;
const { x, y } = extentPreview.position;
const scale = extentPreview.scale;
controls.enabled = true;
resolve(
Extent.fromCenterAndSize(
instance.referenceCrs,
{ x, y },
scale.x,
scale.y,
),
);
}
}
};
instance.domElement.addEventListener("mousedown", onClick);
instance.domElement.addEventListener("mousemove", onMouseMove);
});
}
let currentImage;
const showErrorMessage = (show, message) => {
const errorElement = document.getElementById("error");
if (show) {
errorElement.innerText = `Failed to load remote image: ${message}`;
errorElement.style.display = "block";
} else {
errorElement.style.display = "none";
}
};
const startButton = bindButton("draw", (button) => {
button.disabled = true;
showErrorMessage(false);
drawExtent().then((extent) => {
if (currentImage) {
map.removeLayer(currentImage, { disposeLayer: true });
}
const source = new StaticImageSource({
extent,
source: url,
});
currentImage = new ColorLayer({ source });
source.addEventListener("loaded", () => (extentPreview.visible = false));
source.addEventListener("error", ({ error }) => {
extentPreview.visible = false;
showErrorMessage(true, error.message);
});
map.addLayer(currentImage);
instance.notifyChange(map);
button.disabled = false;
});
});
const [setCurrentUrl, currentUrl] = bindTextInput("url", (v) => {
url = v;
startButton.disabled = !url;
});
setCurrentUrl(currentUrl);
<!doctype html>
<html lang="en">
<head>
<title>Static image</title>
<meta charset="UTF-8" />
<meta name="name" content="static_image_source" />
<meta
name="description"
content="Display a single image on a Map with a <code>StaticImageSource</code>."
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="https://giro3d.org/images/favicon.svg" />
<link
href="https://giro3d.org/assets/bootstrap-custom.css"
rel="stylesheet"
/>
<script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
<link
rel="stylesheet"
type="text/css"
href="https://giro3d.org/next/examples/css/example.css"
/>
</head>
<body>
<div id="view" class="m-0 p-0 w-100 h-100"></div>
<div
id="inspector"
class="position-absolute top-0 start-0 mh-100 overflow-auto"
></div>
<div class="side-pane-with-status-bar" style="width: 20rem">
<!--Parameters -->
<div class="card">
<div class="card-header">Parameters</div>
<div class="card-body" id="top-options">
<!-- Image URL -->
<div class="input-group mb-3">
<span class="input-group-text">URL</span>
<input
type="url"
class="form-control"
id="url"
required
value="https://giro3d.org/images/giro3d_logo_big.jpg"
placeholder="Image URL..."
/>
</div>
<!-- Draw extent -->
<button
type="button"
class="btn btn-primary w-100"
disabled
id="draw"
>
<i class="bi bi-pencil-square"></i>
Draw extent and load image
</button>
<!-- Error message -->
<div
class="alert alert-danger m-0 mt-3"
id="error"
style="display: none"
role="alert"
>
A simple primary alert—check it out!
</div>
</div>
</div>
</div>
<script type="module" src="index.js"></script>
<script>
/* activate popovers */
const popoverTriggerList = [].slice.call(
document.querySelectorAll('[data-bs-toggle="popover"]'),
);
popoverTriggerList.map(
// bootstrap is used as script in the template, disable warning about undef
// eslint-disable-next-line no-undef
(popoverTriggerEl) =>
new bootstrap.Popover(popoverTriggerEl, {
trigger: "hover",
placement: "left",
content: document.getElementById(
popoverTriggerEl.getAttribute("data-bs-content"),
).innerHTML,
html: true,
}),
);
</script>
</body>
</html>
{
"name": "static_image_source",
"dependencies": {
"@giro3d/giro3d": "git+https://gitlab.com/giro3d/giro3d.git"
},
"devDependencies": {
"vite": "^3.2.3"
},
"scripts": {
"start": "vite",
"build": "vite build"
}
}