Experience results from numerical simulations by the CEREMA in 2 locations in France. Build on OpenStreetMap, IGN data and AFrame for WebXR support.
https://bric-vr.com/cerema/portail/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
7.8 KiB
224 lines
7.8 KiB
<html>
|
|
<head>
|
|
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<script>
|
|
var currentGeoPose = null; // https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition
|
|
navigator.geolocation.getCurrentPosition( geolocalized ) // should handle error, including being ignored
|
|
// otherwise have 2 fallbacks via menu
|
|
var positionsCurated = [
|
|
[50.8440239,4.3952614], // brussels for tests
|
|
[1,1]
|
|
]
|
|
|
|
// should get in turn nwr(50.844,4.395,50.845,4.396); as example of scale
|
|
|
|
// default position fallback to faciliate tests
|
|
var position = positionsCurated[0]
|
|
|
|
// initially done as a different page at first with query parameter
|
|
var forcePosition = AFRAME.utils.getUrlParameter('position');
|
|
if ( forcePosition ) position = positionsCurated[forcePosition]
|
|
|
|
var precision = 4
|
|
var bbox_limit = 10/10**precision
|
|
|
|
var elevation_res = 10
|
|
|
|
// generate 3D model from the target location from OSM data
|
|
var bbox = "" + position[0]
|
|
+ "," + position[1]
|
|
+ "," + (position[0]+bbox_limit)
|
|
+ "," + (position[1]+bbox_limit)
|
|
var queryOSM = `https://overpass-api.de/api/interpreter?data=[out:json];nwr(${bbox});out;`
|
|
// format https://overpass-api.de/output_formats.html#json
|
|
// bounding box https://dev.overpass-api.de/overpass-doc/en/full_data/bbox.html
|
|
// testing https://overpass-turbo.eu
|
|
fetch(queryOSM).then(response => response.json()).then(data => fromOSMTo3DElements(data) )
|
|
|
|
// fetch flood data of target location (should be only 2 available) as timeseries
|
|
var queryCEREMA = "https://cerema.fr/api/"+position
|
|
//fetch(queryCEREMA).then(response => response.json()).then(data => console.log(data) )
|
|
|
|
// animate water based on flood data
|
|
|
|
// allow viewer to control the animation e.g restart, pause, change time
|
|
|
|
// modify environment to reflect time of day
|
|
|
|
AFRAME.registerComponent('osm', { // currently only 1 hand, the right one
|
|
init: function () {
|
|
}
|
|
});
|
|
|
|
var globaljosm
|
|
|
|
function rescale( value ){
|
|
return (((value*10**precision)-(Math.round(value*10**precision)))*10).toFixed(precision)
|
|
}
|
|
|
|
function elevationTo3DElements( locations ){
|
|
console.log( locations )
|
|
locations.map( h => {
|
|
var el = document.createElement("a-sphere")
|
|
el.setAttribute("color", "blue")
|
|
el.setAttribute("radius", "0.1")
|
|
// simplification, for a proper polygon see make3DFloor() but here shouldn't assume a centroid
|
|
// could instead rely on a grid
|
|
el.setAttribute("position", h.latitude + " "+ h.elevation +" " + h.longitude )
|
|
// rescale() does not work here, probably different format than in addHouses()
|
|
// could find the minimum altitude then remove it from every other values
|
|
AFRAME.scenes[0].appendChild( el )
|
|
})
|
|
}
|
|
|
|
function getElevation(){
|
|
var locations = []
|
|
for (var i=0;i<elevation_res;i++)
|
|
for (var j=0;j<elevation_res;j++)
|
|
locations.push({
|
|
latitude:Number( (position[0]+(bbox_limit/elevation_res)*i).toFixed(precision) ),
|
|
longitude:Number( (position[1]+(bbox_limit/elevation_res)*j).toFixed(precision) ),
|
|
})
|
|
fetch('https://api.open-elevation.com/api/v1/lookup', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({locations:locations})
|
|
}).then(response => response.json()).then(data => elevationTo3DElements(data.results) )
|
|
}
|
|
|
|
|
|
function fromOSMTo3DElements( josm ){
|
|
globaljosm = josm
|
|
var ways = josm.elements.filter( e => (e.type == "way") )
|
|
var houses = josm.elements.filter( e => (e.type == "node" && e.tags && e.tags["addr:housenumber"]))
|
|
// does not give a polygon, might need further requests. Might also not be the right way.
|
|
addHouses( houses )
|
|
}
|
|
|
|
function addHouses( houses ){
|
|
houses.map( h => {
|
|
var el = document.createElement("a-box")
|
|
// simplification, for a proper polygon see addWall() and make3DFloor()
|
|
el.setAttribute("position", rescale( h.lat ) + " 0 " + rescale( h.lon ))
|
|
AFRAME.scenes[0].appendChild( el )
|
|
})
|
|
}
|
|
|
|
function geolocalized( pos ){
|
|
console.log( pos.coords )
|
|
}
|
|
|
|
function addWall(a, b, h, el){
|
|
var geometry = new THREE.BufferGeometry();
|
|
// create a simple rectangle shape. We duplicate the top left and bottom right
|
|
// vertices because each vertex needs to appear once per triangle.
|
|
var vertices = new Float32Array( [
|
|
a.x, a.y, a.z,
|
|
a.x, a.y+h, a.z,
|
|
b.x, b.y, b.z,
|
|
|
|
a.x, a.y+h, a.z,
|
|
b.x, b.y+h, b.z,
|
|
b.x, b.y, b.z,
|
|
] );
|
|
|
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
|
|
var material = new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true, side: THREE.DoubleSide } );
|
|
var mesh = new THREE.Mesh( geometry, material );
|
|
var entity = document.createElement("a-entity");
|
|
entity.object3D.add( mesh );
|
|
el.appendChild( entity );
|
|
|
|
}
|
|
|
|
function addFloorFan(a, b, c, el){
|
|
var geometry = new THREE.BufferGeometry();
|
|
var vertices = new Float32Array( [
|
|
a.x, a.y, a.z,
|
|
b.x, b.y, b.z,
|
|
c.x, c.y, c.z,
|
|
] );
|
|
|
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
|
|
var material = new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true, side: THREE.DoubleSide } );
|
|
var mesh = new THREE.Mesh( geometry, material );
|
|
var entity = document.createElement("a-entity");
|
|
entity.object3D.add( mesh );
|
|
el.appendChild( entity );
|
|
}
|
|
|
|
function make3DFloor(points, el){ // assumes an array of threejs Vector3, not AFRAME elements anymore
|
|
var entity = document.createElement("a-entity")
|
|
var centroid = getCentroid( points )
|
|
entity.id = "roomcentroid"
|
|
entity.setAttribute("position", centroid)
|
|
el.appendChild( entity )
|
|
|
|
points.map( (e,i,arr) => {
|
|
(i==arr.length-1) ? addFloorFan(arr[0], e, centroid, el) : addFloorFan(e, arr[i+1], centroid, el)
|
|
})
|
|
|
|
}
|
|
|
|
function getCentroid(points){
|
|
var centroid = new THREE.Vector3(0,0,0);
|
|
// can probably be simplied now that we rely on threejs vectors rather than AFrame elements
|
|
for (var e of points) {
|
|
centroid.add( e );
|
|
}
|
|
centroid.divideScalar(points.length);
|
|
return centroid;
|
|
}
|
|
|
|
AFRAME.registerComponent('elevation', {
|
|
init: function () {
|
|
var el = this.el
|
|
getElevation()
|
|
}
|
|
})
|
|
AFRAME.registerComponent('procgen', {
|
|
init: function () {
|
|
var el = this.el
|
|
var a = new THREE.Vector3(1,0,1)
|
|
var b = new THREE.Vector3(-1,0,1)
|
|
var c = new THREE.Vector3(-1,0,-1)
|
|
var d = new THREE.Vector3(0,0,-2)
|
|
var e = new THREE.Vector3(1,0,-1)
|
|
var poly = [a,b,c,d,e]
|
|
var h = 2
|
|
poly.map( (e,i,arr) => { (i==arr.length-1) ? addWall(arr[0], e, h, el) : addWall(e, arr[i+1], h, el) })
|
|
make3DFloor( poly, el )
|
|
}
|
|
});
|
|
|
|
</script>
|
|
<a-scene osm elevation>
|
|
<!-- Quest available only during event, otherwise cardboard -->
|
|
<a-entity id="leftHand" hand-tracking-controls="hand: left;"></a-entity>
|
|
<a-entity id="rightHand" hand-tracking-controls="hand: right;"></a-entity>
|
|
<!-- for cardboard consider instead https://aframe.io/docs/1.3.0/components/cursor.html -->
|
|
|
|
<a-entity procgen position="-4 0 -4"></a-entity>
|
|
|
|
<!-- Default lighting injected by A-Frame. -->
|
|
<a-entity light="type: ambient; color: #BBB"></a-entity>
|
|
<a-entity animation="property: position; to: 5 1 1; dur: 20000; easing: linear; loop: true"
|
|
light="type: directional; color: #FFF; intensity: 0.6" position="-5 1 1"></a-entity>
|
|
<!-- to animate but also consider the background color -->
|
|
<a-sky color="#ECECEC"></a-sky>
|
|
|
|
<a-sound src="src: url(birds.mp3)" autoplay="true" position="0 2 5"></a-sound>
|
|
<a-sound src="src: url(river.mp3)" autoplay="true" position="0 2 5"></a-sound>
|
|
|
|
<a-plane animation="property: position; to: 0 0.4 0; dur: 20000; easing: linear; loop: true"
|
|
position="0 -0.5 0" rotation="-90 0 0" width="100" height="100" color="lightblue"></a-plane>
|
|
</a-scene>
|
|
</body>
|
|
</html>
|
|
|