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, 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 >
< title >Layer reprojection</ title >
< meta charset = "UTF-8" />
< meta name = "name" content = "layer_reprojection" />
< meta name = "description" content = "Reproject layers with heterogenous coordinate systems." />
< 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/latest/css/example.css"
/>
</ head >
< body >
< div id = "viewerDiv" class = "m-0 p-0 w-100 h-100" ></ div >
< div id = "panelDiv" class = "position-absolute top-0 start-0 mh-100 overflow-auto" ></ div >
< div class = "side-pane-with-status-bar" style = "width: 22rem" >
< div class = "mh-100 overflow-y-auto" >
< 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 >
</ 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.37.3"
},
"devDependencies" : {
"vite" : "^3.2.3"
},
"scripts" : {
"start" : "vite" ,
"build" : "vite build"
}
}