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 GeoTIFFSource from "@giro3d/giro3d/sources/GeoTIFFSource.js" ;
import { crsToUnit } from "@giro3d/giro3d/core/geographic/Coordinates.js" ;
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;
}
let instance;
let inspector;
let controls;
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" ,
}),
}),
});
map. addLayer (tiledLayer). catch (( e ) => console. error (e));
}
function addCogLayer () {
const cogLayer = new ColorLayer ({
name: "cog" ,
showTileBorders: true ,
source: new GeoTIFFSource ({
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 ({
data: {
url: "https://3d.oslandia.com/giro3d/vectors/countries.geojson" ,
format: new 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);
instance = new Instance ({
target: "view" ,
crs,
backgroundColor: "grey" ,
});
map = new Map ({
extent,
segments: 2 ,
backgroundColor: "black" ,
backgroundOpacity: 0.3 ,
});
instance. add (map);
addMapboxLayer (extent);
addCogLayer ();
addVectorLayer ();
const center = extent. centerAsVector3 ();
instance.view.camera.position. set (
center.x,
center.y - 1 ,
extent. dimensions ().y * 2 ,
);
controls = new MapControls (instance.view.camera, instance.domElement);
controls.target = center;
controls. saveState ();
controls.enableDamping = true ;
controls.dampingFactor = 0.2 ;
controls.maxPolarAngle = Math. PI / 2.3 ;
instance.view. setControls (controls);
inspector = Inspector. attach ( "inspector" , instance);
}
async function fetchCrsBbox ( crs ) {
const code = crs. split ( ":" )[ 1 ];
const link = `https://epsg.io/${ code }` ;
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 ( "srid" ).innerText = json.name;
document. getElementById ( "name" ).innerText = crs;
document. getElementById ( "description" ).innerText = json.area;
document. getElementById ( "link" ).href = link;
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 ) {
const error = document. getElementById ( "message" );
try {
const def = await fetchCrsDefinition (crs);
const extent = await fetchCrsBbox (crs);
const proj = crs;
error.style.display = "none" ;
createScene (proj, def, extent);
} catch (e) {
error.style.display = "block" ;
if (e instanceof Error ) {
error.innerText = e.message;
} else {
error.innerText = `An error occured while fetching CRS definition on epsg.io` ;
}
}
}
bindButton ( "create" , () => {
const epsgCodeElt = document. getElementById ( "code" );
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/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: 22rem" >
< div class = "mh-100 overflow-y-auto" >
< div class = "card" id = "currentCrsSection" >
<!-- CRS code -->
< div class = "card-header" >Parameters</ 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" >
<!-- CRS selector -->
< div class = "input-group" >
< span class = "input-group-text" id = "code-label" >CRS</ span >
< input
type = "text"
class = "form-control"
id = "code"
placeholder = "EPSG code (e.g EPSG:3857)"
value = "EPSG:2154"
aria-label = "EpsgCode"
autocomplete = "off"
aria-describedby = "code-label"
/>
< button class = "input-group-text btn btn-primary" id = "create" >
Update
</ button >
</ div >
< hr />
< h5 class = "card-title" id = "name" >
RGF93 v1 / Lambert-93 -- France
</ h5 >
< h6 class = "card-subtitle text-body-secondary mb-2" id = "srid" >
EPSG:2154
</ h6 >
< p class = "card-text" id = "description" >
France - onshore and offshore, mainland and Corsica (France
métropolitaine including Corsica).
</ p >
< a id = "link" target = "_blank" >See on epsg.io</ a >
</ div >
</ div >
<!-- Error message -->
< div
class = "alert alert-danger mt-3"
id = "message"
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.41.0"
},
"devDependencies" : {
"vite" : "^3.2.3"
},
"scripts" : {
"start" : "vite" ,
"build" : "vite build"
}
}