< 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 >