import XYZ from 'ol/source/XYZ.js';
import { Stroke, Style } from 'ol/style.js';
import { GeoJSON } from 'ol/format.js';
import { MapControls } from 'three/examples/jsm/controls/MapControls.js';
import Extent from '@giro3d/giro3d/core/geographic/Extent.js';
import Instance from '@giro3d/giro3d/core/Instance.js';
import TiledImageSource from '@giro3d/giro3d/sources/TiledImageSource.js';
import ColorLayer from '@giro3d/giro3d/core/layer/ColorLayer.js';
import Map from '@giro3d/giro3d/entities/Map.js';
import Inspector from '@giro3d/giro3d/gui/Inspector.js';
import VectorSource from '@giro3d/giro3d/sources/VectorSource.js';
import CogSource from '@giro3d/giro3d/sources/CogSource.js';
import { crsToUnit } from '@giro3d/giro3d/core/geographic/Coordinates.js';
/** @type {Instance} */
let instance;
/** @type {Inspector} */
let inspector;
/** @type {MapControls} */
let controls;
/** @type {Map} */
let map;
function addMapboxLayer(extent) {
const apiKey =
'pk.eyJ1IjoidG11Z3VldCIsImEiOiJjbGJ4dTNkOW0wYWx4M25ybWZ5YnpicHV6In0.KhDJ7W5N3d1z3ArrsDjX_A';
// Adds a satellite basemap
const tiledLayer = new ColorLayer({
name: 'basemap',
extent,
showTileBorders: true,
source: new TiledImageSource({
source: new XYZ({
url: `https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.webp?access_token=${apiKey}`,
projection: 'EPSG:3857',
crossOrigin: 'anonymous',
}),
}),
});
map.addLayer(tiledLayer).catch(e => console.error(e));
}
function addCogLayer() {
const cogLayer = new ColorLayer({
name: 'cog',
showTileBorders: true,
source: new CogSource({
url: 'https://3d.oslandia.com/giro3d/rasters/TCI.tif',
crs: 'EPSG:3857',
}),
});
map.addLayer(cogLayer).catch(e => console.error(e));
}
function addVectorLayer() {
const outlineStyle = new Style({
stroke: new Stroke({ color: 'red', width: 2 }),
});
// Display the countries boundaries.
const boundaries = new ColorLayer({
name: 'boundaries',
source: new VectorSource({
format: new GeoJSON(),
data: 'https://3d.oslandia.com/giro3d/vectors/countries.geojson',
style: outlineStyle,
dataProjection: 'EPSG:4326',
}),
});
map.addLayer(boundaries).catch(e => console.error(e));
}
function createScene(crs, crsDef, extent) {
if (instance) {
map.getLayers().forEach(l => l.dispose());
controls.dispose();
inspector.detach();
instance.dispose();
}
Instance.registerCRS(crs, crsDef);
// `viewerDiv` will contain Giro3D' rendering area (the canvas element)
const viewerDiv = document.getElementById('viewerDiv');
// Creates a Giro3D instance
instance = new Instance(viewerDiv, { crs, renderer: { clearColor: 'grey' } });
// Adds the map that will contain the layers.
map = new Map('map', { extent, segments: 128, hillshading: true });
instance.add(map);
// Add a bunch of layers
addMapboxLayer(extent);
addCogLayer();
addVectorLayer();
// Sets the camera position
const center = extent.centerAsVector3();
instance.camera.camera3D.position.set(center.x, center.y - 1, extent.dimensions().y * 2);
// Creates controls
controls = new MapControls(instance.camera.camera3D, viewerDiv);
// Then looks at extent's center
controls.target = center;
controls.saveState();
controls.enableDamping = true;
controls.dampingFactor = 0.2;
controls.maxPolarAngle = Math.PI / 2.3;
instance.useTHREEControls(controls);
inspector = Inspector.attach(document.getElementById('panelDiv'), instance);
}
async function fetchCrsBbox(crs) {
const code = crs.split(':')[1];
const url = `https://epsg.io/${code}.json?download=1`;
const res = await fetch(url, { mode: 'cors' });
const json = await res.json();
const bbox = json.bbox;
const south = Number.parseFloat(bbox.south_latitude);
const north = Number.parseFloat(bbox.north_latitude);
const west = Number.parseFloat(bbox.west_longitude);
const east = Number.parseFloat(bbox.east_longitude);
const wgs84Extent = new Extent('EPSG:4326', {
west,
east,
north,
south,
});
document.getElementById('currentCrsCode').innerText = crs;
document.getElementById('currentCrsName').innerText = json.name;
document.getElementById('currentCrsArea').innerText = json.area;
if (crsToUnit(crs) === undefined) {
// Unsupported projection
throw new Error('unsupported projection (invalid units)');
} else {
return wgs84Extent.as(crs);
}
}
async function fetchCrsDefinition(crs) {
const code = crs.split(':')[1];
const url = `https://epsg.io/${code}.proj4?download=1`;
const res = await fetch(url, { mode: 'cors' });
const def = await res.text();
Instance.registerCRS(crs, def);
return def;
}
async function initialize(crs) {
try {
const def = await fetchCrsDefinition(crs);
const extent = await fetchCrsBbox(crs);
const proj = crs;
const error = document.getElementById('errorMessage');
error.style.display = 'none';
createScene(proj, def, extent);
} catch (e) {
const msg = e.message;
const error = document.getElementById('errorMessage');
error.innerText = msg;
error.style.display = 'block';
}
}
/** @type {HTMLButtonElement} */
const button = document.getElementById('createSceneBtn');
button.onclick = () => {
/** @type {HTMLInputElement} */
const epsgCodeElt = document.getElementById('epsgCode');
const content = epsgCodeElt.value;
if (content) {
initialize(content);
}
};
initialize('EPSG:2154');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Giro3D - Layer reprojection</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<style>
body {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
}
#viewerDiv {
width: 100%;
height: 100%;
}
#panelDiv {
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div id="viewerDiv"></div>
<div id="panelDiv"></div>
<div class="m-3 position-absolute top-0 end-0" style="width: 22rem">
<div class="card" id="currentCrsSection">
<!-- CRS code -->
<div class="card-header" id="currentCrsCode">EPSG:2154</div>
<!-- tooltip -->
<span
class="badge bg-secondary position-absolute top-0 end-0 m-2"
data-bs-toggle="popover"
data-bs-content="help"
>?</span
>
<p class="card-text d-none" id="help">
Type a projected CRS in the field below, then "Update" to create a scene with various
layers. You can search for projection codes on
<a target="_blank" href="https://epsg.io/?q=epsg">epsg.io</a>. <b>Note:</b> not all
projections are supported.
</p>
<!-- CRS name and area -->
<div class="card-body">
<h5 class="card-title" id="currentCrsName">RGF93 v1 / Lambert-93 -- France</h5>
<p class="card-text" id="currentCrsArea">
France - onshore and offshore, mainland and Corsica (France métropolitaine including
Corsica).
</p>
</div>
</div>
<!-- CRS selector -->
<div class="input-group mt-3">
<span class="input-group-text" id="epsgCodeLabel">CRS</span>
<input
type="text"
class="form-control"
id="epsgCode"
placeholder="EPSG code (e.g EPSG:3857)"
value="EPSG:2154"
aria-label="EpsgCode"
aria-describedby="epsgCodeLabel"
/>
<button class="input-group-text btn btn-primary" id="createSceneBtn">Update</button>
</div>
<!-- Error message -->
<div class="alert alert-danger mt-3" id="errorMessage" style="display: none" role="alert">
A simple primary alert—check it out!
</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": "layer_reprojection",
"dependencies": {
"@giro3d/giro3d": "0.35.0"
},
"devDependencies": {
"vite": "^3.2.3"
},
"scripts": {
"start": "vite",
"build": "vite build"
}
}