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 ColorLayer from "@giro3d/giro3d/core/layer/ColorLayer.js" ;
import ElevationLayer from "@giro3d/giro3d/core/layer/ElevationLayer.js" ;
import BilFormat from "@giro3d/giro3d/formats/BilFormat.js" ;
import Map from "@giro3d/giro3d/entities/Map.js" ;
import Inspector from "@giro3d/giro3d/gui/Inspector.js" ;
import WmtsSource from "@giro3d/giro3d/sources/WmtsSource.js" ;
function bindToggle ( id , onChange ) {
const element = document. getElementById (id);
if ( ! (element instanceof HTMLInputElement )) {
throw new Error (
"invalid binding element: expected HTMLButtonElement, got: " +
element. constructor .name,
);
}
element. oninput = function oninput () {
onChange (element.checked);
};
const callback = ( v ) => {
element.checked = v;
onChange (element.checked);
};
return [callback, element.checked, element];
}
function bindSlider ( id , onChange ) {
const element = document. getElementById (id);
if ( ! (element instanceof HTMLInputElement )) {
throw new Error (
"invalid binding element: expected HTMLInputElement, got: " +
element. constructor .name,
);
}
element. oninput = function oninput () {
onChange (element.valueAsNumber);
};
const setValue = ( v , min , max , step ) => {
if (min != null && max != null ) {
element.min = min. toString ();
element.max = max. toString ();
if (step != null ) {
element.step = step;
}
}
element.valueAsNumber = v;
onChange (element.valueAsNumber);
};
const initialValue = element.valueAsNumber;
return [setValue, initialValue, element];
}
import { MapLightingMode } from "@giro3d/giro3d/entities/MapLightingOptions.js" ;
Instance. registerCRS (
"EPSG:3946" ,
"+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" ,
);
Instance. registerCRS (
"IGNF:WGS84G" ,
'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]' ,
);
const extent = new Extent (
"EPSG:3946" ,
1837816.94334 ,
1847692.32501 ,
5170036.4587 ,
5178412.82698 ,
);
const instance = new Instance ({
target: "view" ,
crs: extent.crs,
});
const map = new Map ({
extent,
// Enables hillshading on this map
lighting: {
enabled: true ,
mode: MapLightingMode.Hillshade,
},
backgroundColor: "white" ,
});
instance. add (map);
const url =
"https://data.geopf.fr/wmts?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities" ;
const noDataValue = - 1000 ;
let colorLayer;
// Let's build the elevation layer from the WMTS capabilities
WmtsSource. fromCapabilities (url, {
layer: "ELEVATION.ELEVATIONGRIDCOVERAGE.HIGHRES" ,
format: new BilFormat (),
noDataValue,
})
. then (( elevationWmts ) => {
map. addLayer (
new ElevationLayer ({
name: "elevation" ,
extent: map.extent,
minmax: { min: 100 , max: 300 },
source: elevationWmts,
}),
);
})
. catch (console.error);
// Let's build the color layer from the WMTS capabilities
WmtsSource. fromCapabilities (url, {
layer: "HR.ORTHOIMAGERY.ORTHOPHOTOS" ,
})
. then (( orthophotoWmts ) => {
colorLayer = new ColorLayer ({
name: "color" ,
extent: map.extent. split ( 2 , 1 )[ 0 ],
source: orthophotoWmts,
});
map. addLayer (colorLayer);
})
. catch (console.error);
const mapCenter = extent. centerAsVector3 ();
instance.view.camera.position. set (mapCenter.x, mapCenter.y - 1 , 10000 );
const controls = new MapControls (instance.view.camera, instance.domElement);
controls.target = mapCenter;
controls. saveState ();
controls.enableDamping = true ;
controls.dampingFactor = 0.2 ;
controls.maxPolarAngle = Math. PI / 2.3 ;
instance.view. setControls (controls);
Inspector. attach ( "inspector" , instance);
// Example GUI
const [, , colorLayersToggle ] = bindToggle ( "colorLayers" , ( state ) => {
map.lighting.elevationLayersOnly = ! state;
instance. notifyChange (map);
});
const [, , azimuthSlider ] = bindSlider ( "azimuth" , ( azimuth ) => {
map.lighting.hillshadeAzimuth = azimuth;
instance. notifyChange (map);
});
const [, , zenithSlider ] = bindSlider ( "zenith" , ( zenith ) => {
map.lighting.hillshadeZenith = zenith;
instance. notifyChange (map);
});
bindToggle ( "enabled" , ( state ) => {
map.lighting.enabled = state;
instance. notifyChange (map);
colorLayersToggle.disabled = ! state;
azimuthSlider.disabled = ! state;
zenithSlider.disabled = ! state;
});
const [, , opacitySlider ] = bindSlider ( "opacity" , ( percentage ) => {
const opacity = percentage / 100.0 ;
colorLayer.opacity = opacity;
instance. notifyChange (map);
opacitySlider.innerHTML = `${ percentage }%` ;
});
bindSlider ( "intensity" , ( intensity ) => {
map.lighting.hillshadeIntensity = intensity;
instance. notifyChange (map);
});
bindSlider ( "zFactor" , ( zFactor ) => {
map.lighting.zFactor = zFactor;
instance. notifyChange (map);
});
const [, , stitchingToggle ] = bindToggle ( "stitching" , ( enabled ) => {
map.terrain.stitching = enabled;
instance. notifyChange (map);
});
bindToggle ( "terrainDeformation" , ( enabled ) => {
map.terrain.enabled = enabled;
instance. notifyChange (map);
stitchingToggle.disabled = ! enabled;
});
<! doctype html >
< html lang = "en" >
< head >
< title >Hillshading & terrain</ title >
< meta charset = "UTF-8" />
< meta name = "name" content = "hillshade" />
< meta
name = "description"
content = "Illustrate the use of hillshading on maps with terrain."
/>
< 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" >
< div class = "card" >
< div class = "card-header" >Terrain rendering</ div >
< div class = "card-body" >
< div class = "form-check form-switch" >
< input
class = "form-check-input"
type = "checkbox"
checked = "true"
role = "switch"
id = "terrainDeformation"
autocomplete = "off"
/>
< label class = "form-check-label" for = "terrainDeformation"
>Terrain deformation</ label
>
</ div >
< div class = "form-check form-switch" >
< input
class = "form-check-input"
type = "checkbox"
checked = "true"
role = "switch"
id = "stitching"
autocomplete = "off"
/>
< label class = "form-check-label" for = "stitching"
>Terrain stitching</ label
>
</ div >
< div class = "form-check form-switch" >
< input
class = "form-check-input"
type = "checkbox"
checked = "true"
role = "switch"
id = "enabled"
autocomplete = "off"
/>
< label class = "form-check-label" for = "enabled" >Hillshading</ label >
</ div >
< div class = "form-check form-switch" >
< input
class = "form-check-input"
type = "checkbox"
checked = "true"
role = "switch"
id = "colorLayers"
autocomplete = "off"
/>
< label class = "form-check-label" for = "colorLayers"
>Shade color layers</ label
>
</ div >
< label for = "azimuth" class = "form-label" >Azimuth (0 - 360)</ label >
< div class = "input-group" >
< input
type = "range"
min = "0"
max = "360"
value = "315"
class = "form-range"
id = "azimuth"
autocomplete = "off"
/>
</ div >
< div class = "my-2" ></ div >
< label for = "zenith" class = "form-label" >Zenith (0 - 90)</ label >
< div class = "input-group" >
< input
type = "range"
min = "0"
max = "90"
value = "45"
class = "form-range"
id = "zenith"
autocomplete = "off"
/>
</ div >
< label for = "intensity" class = "form-label" >Intensity</ label >
< div class = "input-group" >
< input
type = "range"
min = "0"
max = "1"
value = "1"
step = "0.1"
class = "form-range"
id = "intensity"
autocomplete = "off"
/>
</ div >
< label for = "zFactor" class = "form-label" >Z-factor</ label >
< div class = "input-group" >
< input
type = "range"
min = "0"
max = "10"
value = "1"
step = "0.1"
class = "form-range"
id = "zFactor"
autocomplete = "off"
/>
</ div >
< label for = "opacity" class = "form-label" >Color layer opacity</ label >
< div class = "input-group" >
< input
type = "range"
min = "0"
max = "100"
value = "100"
class = "form-range"
id = "opacity"
autocomplete = "off"
/>
</ 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 >