├── README.md ├── TODO.md ├── examples ├── basic.html ├── sun-light-estimation │ ├── README.md │ ├── google-map-geocoder.html │ ├── sun-position.html │ └── vendor │ │ └── suncalc.js └── vendor │ └── ar.js │ ├── aframe │ └── build │ │ └── aframe-ar.js │ └── data │ └── data │ └── camera_para.dat ├── src ├── gps-camera-debug.js ├── gps-camera-position.js ├── gps-camera-rotation.js └── gps-entity-place.js └── tmp ├── obsolete-gps-ar.js ├── original-old-buggy.html └── phills-sphere.html /README.md: -------------------------------------------------------------------------------- 1 | # Experimentation with GPS for AR 2 | 3 | This is an experimentation of using phone gps + gyroscope to display Augmented reality. 4 | 5 | Note: GPS accuracy is significantly less than normal AR tracking. So be sure 6 | it matches your use-case. 7 | 8 | Note2: this is very experimental and not finished. 9 | 10 | # Credit 11 | It is based on the discussion from this [github issue](https://github.com/jeromeetienne/AR.js/issues/190). 12 | It has been originated by [1d10t](https://github.com/1d10t) in this [file](https://1d10t.github.io/test/phills-sphere.html). 13 | Thanks a LOT for your contribution! 14 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - gps-place: only one parameters. location 2 | - a lot of components options are uselessly complex 3 | 4 | - camera initialisation isnt compatible with marker projection matrix 5 | - see how to make it so 6 | 7 | - make a good example 8 | - how to handle the specific of the user latitude/longitude 9 | 10 | 11 | - display stuff independantly of the Z 12 | - thus the tracking seems less bad 13 | 14 | - make components name more uniforms - more ar.js like 15 | - gps-place - for an object in real world 16 | - gps-debug - for camera - to display debug 17 | - compass-rotation - for camera - to set the compass of the camera 18 | - gps-position - for camera - to set the position from the gps 19 | - put arjs-gps-* instead of gps- 20 | - new name => arjs-gps-location and arjs-gps-camera 21 | 22 | - improve object location to be searched by google maps or similar 23 | - thus no need to push unreadable coordinates 24 | 25 | 26 | - DONE put the aframe components in their own file 27 | - DONE make a debug layer 28 | - component gps-debug on camera 29 | 30 | 31 | # Possible Demo 32 | - put several spot in the real world 33 | - well known cities 34 | - search local interest: e.g. closest restaurants, museums 35 | - display them with a a-text 36 | - name of the place + distance from the camera 37 | - display them independantly from the distance to the camera 38 | -------------------------------------------------------------------------------- /examples/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/sun-light-estimation/README.md: -------------------------------------------------------------------------------- 1 | to use the position of the sun as a way to do light estimation 2 | could even use the weather prediction for that. 3 | 4 | - if cloudy, make the light dimmer 5 | 6 | # Demo idea 7 | "Show be the sun behind the cloud" 8 | 9 | 10 | # Useful links 11 | - https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition 12 | - https://github.com/mourner/suncalc 13 | - http://curious.astro.cornell.edu/about-us/112-observational-astronomy/stargazing/technical-questions/698-what-are-altitude-and-azimuth-intermediate 14 | -------------------------------------------------------------------------------- /examples/sun-light-estimation/google-map-geocoder.html: -------------------------------------------------------------------------------- 1 | 2 | 23 | -------------------------------------------------------------------------------- /examples/sun-light-estimation/sun-position.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/sun-light-estimation/vendor/suncalc.js: -------------------------------------------------------------------------------- 1 | /* 2 | (c) 2011-2015, Vladimir Agafonkin 3 | SunCalc is a JavaScript library for calculating sun/moon position and light phases. 4 | https://github.com/mourner/suncalc 5 | */ 6 | 7 | (function () { 'use strict'; 8 | 9 | // shortcuts for easier to read formulas 10 | 11 | var PI = Math.PI, 12 | sin = Math.sin, 13 | cos = Math.cos, 14 | tan = Math.tan, 15 | asin = Math.asin, 16 | atan = Math.atan2, 17 | acos = Math.acos, 18 | rad = PI / 180; 19 | 20 | // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas 21 | 22 | 23 | // date/time constants and conversions 24 | 25 | var dayMs = 1000 * 60 * 60 * 24, 26 | J1970 = 2440588, 27 | J2000 = 2451545; 28 | 29 | function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } 30 | function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } 31 | function toDays(date) { return toJulian(date) - J2000; } 32 | 33 | 34 | // general calculations for position 35 | 36 | var e = rad * 23.4397; // obliquity of the Earth 37 | 38 | function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } 39 | function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } 40 | 41 | function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } 42 | function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } 43 | 44 | function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } 45 | 46 | function astroRefraction(h) { 47 | if (h < 0) // the following formula works for positive altitudes only. 48 | h = 0; // if h = -0.08901179 a div/0 would occur. 49 | 50 | // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 51 | // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: 52 | return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); 53 | } 54 | 55 | // general sun calculations 56 | 57 | function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } 58 | 59 | function eclipticLongitude(M) { 60 | 61 | var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center 62 | P = rad * 102.9372; // perihelion of the Earth 63 | 64 | return M + C + P + PI; 65 | } 66 | 67 | function sunCoords(d) { 68 | 69 | var M = solarMeanAnomaly(d), 70 | L = eclipticLongitude(M); 71 | 72 | return { 73 | dec: declination(L, 0), 74 | ra: rightAscension(L, 0) 75 | }; 76 | } 77 | 78 | 79 | var SunCalc = {}; 80 | 81 | 82 | // calculates sun position for a given date and latitude/longitude 83 | 84 | SunCalc.getPosition = function (date, lat, lng) { 85 | 86 | var lw = rad * -lng, 87 | phi = rad * lat, 88 | d = toDays(date), 89 | 90 | c = sunCoords(d), 91 | H = siderealTime(d, lw) - c.ra; 92 | 93 | return { 94 | azimuth: azimuth(H, phi, c.dec), 95 | altitude: altitude(H, phi, c.dec) 96 | }; 97 | }; 98 | 99 | 100 | // sun times configuration (angle, morning name, evening name) 101 | 102 | var times = SunCalc.times = [ 103 | [-0.833, 'sunrise', 'sunset' ], 104 | [ -0.3, 'sunriseEnd', 'sunsetStart' ], 105 | [ -6, 'dawn', 'dusk' ], 106 | [ -12, 'nauticalDawn', 'nauticalDusk'], 107 | [ -18, 'nightEnd', 'night' ], 108 | [ 6, 'goldenHourEnd', 'goldenHour' ] 109 | ]; 110 | 111 | // adds a custom time to the times config 112 | 113 | SunCalc.addTime = function (angle, riseName, setName) { 114 | times.push([angle, riseName, setName]); 115 | }; 116 | 117 | 118 | // calculations for sun times 119 | 120 | var J0 = 0.0009; 121 | 122 | function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } 123 | 124 | function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } 125 | function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } 126 | 127 | function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } 128 | 129 | // returns set time for the given sun altitude 130 | function getSetJ(h, lw, phi, dec, n, M, L) { 131 | 132 | var w = hourAngle(h, phi, dec), 133 | a = approxTransit(w, lw, n); 134 | return solarTransitJ(a, M, L); 135 | } 136 | 137 | 138 | // calculates sun times for a given date and latitude/longitude 139 | 140 | SunCalc.getTimes = function (date, lat, lng) { 141 | 142 | var lw = rad * -lng, 143 | phi = rad * lat, 144 | 145 | d = toDays(date), 146 | n = julianCycle(d, lw), 147 | ds = approxTransit(0, lw, n), 148 | 149 | M = solarMeanAnomaly(ds), 150 | L = eclipticLongitude(M), 151 | dec = declination(L, 0), 152 | 153 | Jnoon = solarTransitJ(ds, M, L), 154 | 155 | i, len, time, Jset, Jrise; 156 | 157 | 158 | var result = { 159 | solarNoon: fromJulian(Jnoon), 160 | nadir: fromJulian(Jnoon - 0.5) 161 | }; 162 | 163 | for (i = 0, len = times.length; i < len; i += 1) { 164 | time = times[i]; 165 | 166 | Jset = getSetJ(time[0] * rad, lw, phi, dec, n, M, L); 167 | Jrise = Jnoon - (Jset - Jnoon); 168 | 169 | result[time[1]] = fromJulian(Jrise); 170 | result[time[2]] = fromJulian(Jset); 171 | } 172 | 173 | return result; 174 | }; 175 | 176 | 177 | // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas 178 | 179 | function moonCoords(d) { // geocentric ecliptic coordinates of the moon 180 | 181 | var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude 182 | M = rad * (134.963 + 13.064993 * d), // mean anomaly 183 | F = rad * (93.272 + 13.229350 * d), // mean distance 184 | 185 | l = L + rad * 6.289 * sin(M), // longitude 186 | b = rad * 5.128 * sin(F), // latitude 187 | dt = 385001 - 20905 * cos(M); // distance to the moon in km 188 | 189 | return { 190 | ra: rightAscension(l, b), 191 | dec: declination(l, b), 192 | dist: dt 193 | }; 194 | } 195 | 196 | SunCalc.getMoonPosition = function (date, lat, lng) { 197 | 198 | var lw = rad * -lng, 199 | phi = rad * lat, 200 | d = toDays(date), 201 | 202 | c = moonCoords(d), 203 | H = siderealTime(d, lw) - c.ra, 204 | h = altitude(H, phi, c.dec), 205 | // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 206 | pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); 207 | 208 | h = h + astroRefraction(h); // altitude correction for refraction 209 | 210 | return { 211 | azimuth: azimuth(H, phi, c.dec), 212 | altitude: h, 213 | distance: c.dist, 214 | parallacticAngle: pa 215 | }; 216 | }; 217 | 218 | 219 | // calculations for illumination parameters of the moon, 220 | // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and 221 | // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 222 | 223 | SunCalc.getMoonIllumination = function (date) { 224 | 225 | var d = toDays(date || new Date()), 226 | s = sunCoords(d), 227 | m = moonCoords(d), 228 | 229 | sdist = 149598000, // distance from Earth to Sun in km 230 | 231 | phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), 232 | inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), 233 | angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - 234 | cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); 235 | 236 | return { 237 | fraction: (1 + cos(inc)) / 2, 238 | phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, 239 | angle: angle 240 | }; 241 | }; 242 | 243 | 244 | function hoursLater(date, h) { 245 | return new Date(date.valueOf() + h * dayMs / 24); 246 | } 247 | 248 | // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article 249 | 250 | SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { 251 | var t = new Date(date); 252 | if (inUTC) t.setUTCHours(0, 0, 0, 0); 253 | else t.setHours(0, 0, 0, 0); 254 | 255 | var hc = 0.133 * rad, 256 | h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, 257 | h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; 258 | 259 | // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) 260 | for (var i = 1; i <= 24; i += 2) { 261 | h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; 262 | h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; 263 | 264 | a = (h0 + h2) / 2 - h1; 265 | b = (h2 - h0) / 2; 266 | xe = -b / (2 * a); 267 | ye = (a * xe + b) * xe + h1; 268 | d = b * b - 4 * a * h1; 269 | roots = 0; 270 | 271 | if (d >= 0) { 272 | dx = Math.sqrt(d) / (Math.abs(a) * 2); 273 | x1 = xe - dx; 274 | x2 = xe + dx; 275 | if (Math.abs(x1) <= 1) roots++; 276 | if (Math.abs(x2) <= 1) roots++; 277 | if (x1 < -1) x1 = x2; 278 | } 279 | 280 | if (roots === 1) { 281 | if (h0 < 0) rise = i + x1; 282 | else set = i + x1; 283 | 284 | } else if (roots === 2) { 285 | rise = i + (ye < 0 ? x2 : x1); 286 | set = i + (ye < 0 ? x1 : x2); 287 | } 288 | 289 | if (rise && set) break; 290 | 291 | h0 = h2; 292 | } 293 | 294 | var result = {}; 295 | 296 | if (rise) result.rise = hoursLater(t, rise); 297 | if (set) result.set = hoursLater(t, set); 298 | 299 | if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; 300 | 301 | return result; 302 | }; 303 | 304 | 305 | // export as Node module / AMD module / browser variable 306 | if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; 307 | else if (typeof define === 'function' && define.amd) define(SunCalc); 308 | else window.SunCalc = SunCalc; 309 | 310 | }()); 311 | -------------------------------------------------------------------------------- /examples/vendor/ar.js/data/data/camera_para.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARjs-org/location-based-ar/e855f48c3de6164c014c3efe8727d4f103c9b728/examples/vendor/ar.js/data/data/camera_para.dat -------------------------------------------------------------------------------- /src/gps-camera-debug.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('gps-camera-debug', { 2 | init : function(){ 3 | var camera = this.el 4 | 5 | ////////////////////////////////////////////////////////////////////////////// 6 | // Create html 7 | ////////////////////////////////////////////////////////////////////////////// 8 | var domElement = document.createElement('div') 9 | domElement.innerHTML = ` 10 | 11 |
12 |
13 | current lng/lat coords: , 14 |
15 |
16 | origin lng/lat coords: , 17 |
18 |
19 | camera 3d position: , 20 |
21 | compass heading: , 22 | camera angle: , 23 | yaw angle: 24 |
25 |
26 | ` 27 | document.body.appendChild(domElement.children[0]) 28 | 29 | // TODO cleanup this code 30 | // TODO build the html element in there 31 | 32 | camera.addEventListener('componentchanged', function (event) { 33 | switch(event.detail.name){ 34 | case 'rotation': 35 | //console.log('camera rotation changed', event.detail.newData); 36 | var compassRotation = camera.components['gps-camera-rotation'] 37 | var lookControls = camera.components['look-controls'] 38 | 39 | camera_angle.innerText = event.detail.newData.y.toFixed(2) 40 | 41 | if( lookControls ){ 42 | yaw_angle.innerText = THREE.Math.radToDeg(lookControls.yawObject.rotation.y).toFixed(2) 43 | } 44 | if( compassRotation && compassRotation.heading !== null ){ 45 | compass_heading.innerText = compassRotation.heading.toFixed(2) 46 | } 47 | break 48 | case 'position': 49 | //console.log('camera position changed', event.detail.newData) 50 | camera_p_x.innerText = event.detail.newData.x 51 | camera_p_z.innerText = event.detail.newData.z 52 | 53 | var gpsPosition = camera.components['gps-camera-position'] 54 | if( gpsPosition ){ 55 | if(gpsPosition.currentCoords){ 56 | current_coords_longitude.innerText = gpsPosition.currentCoords.longitude 57 | current_coords_latitude.innerText = gpsPosition.currentCoords.latitude 58 | } 59 | if(gpsPosition.originCoords){ 60 | origin_coords_longitude.innerText = gpsPosition.originCoords.longitude 61 | origin_coords_latitude.innerText = gpsPosition.originCoords.latitude 62 | } 63 | } 64 | 65 | break 66 | } 67 | }) 68 | 69 | } 70 | }) 71 | -------------------------------------------------------------------------------- /src/gps-camera-position.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('gps-camera-position', { 2 | 3 | _watchPositionId: null, 4 | 5 | originCoords: null, 6 | currentCoords: null, 7 | 8 | schema: { 9 | minAccuracy: { 10 | type: 'int', 11 | default: 100 12 | }, 13 | }, 14 | 15 | init: function () { 16 | 17 | this._watchPositionId = this._initWatchGPS(function(position){ 18 | // https://developer.mozilla.org/en-US/docs/Web/API/Coordinates 19 | this.currentCoords = position.coords 20 | this._updatePosition() 21 | }.bind(this)) 22 | 23 | }, 24 | remove: function() { 25 | if(this._watchPositionId) navigator.geolocation.clearWatch(this._watchPositionId) 26 | this._watchPositionId = null 27 | }, 28 | 29 | _initWatchGPS: function( onSuccess, onError ){ 30 | // TODO put that in .init directly 31 | 32 | if( onError === undefined ){ 33 | onError = function(err) { console.warn('ERROR('+err.code+'): '+err.message) } 34 | } 35 | 36 | if( "geolocation" in navigator === false ){ 37 | onError({code: 0, message: 'Geolocation is not supported by your browser'}) 38 | return 39 | } 40 | 41 | // https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition 42 | return navigator.geolocation.watchPosition(onSuccess, onError, { 43 | enableHighAccuracy: true, 44 | maximumAge: 0, 45 | timeout: 27000 46 | }) 47 | }, 48 | 49 | _updatePosition: function () { 50 | // dont update if accuracy isnt good enough 51 | if( this.currentCoords.accuracy > this.data.minAccuracy ) return 52 | 53 | // init originCoords if needed 54 | if( this.originCoords === null ) this.originCoords = this.currentCoords 55 | 56 | var position = this.el.getAttribute('position') 57 | 58 | // compute position.x 59 | var dstCoords = { 60 | longitude: this.currentCoords.longitude, 61 | latitude: this.originCoords.latitude 62 | } 63 | position.x = this.computeDistanceMeters(this.originCoords, dstCoords) 64 | position.x *= this.currentCoords.longitude > this.originCoords.longitude ? 1 : -1 65 | 66 | // compute position.z 67 | var dstCoords = { 68 | longitude: this.originCoords.longitude, 69 | latitude: this.currentCoords.latitude 70 | } 71 | position.z = this.computeDistanceMeters(this.originCoords, dstCoords) 72 | position.z *= this.currentCoords.latitude > this.originCoords.latitude ? -1 : 1 73 | 74 | // update element position 75 | this.el.setAttribute('position', position) 76 | }, 77 | 78 | computeDistanceMeters: function(src, dest) { 79 | // 'Calculate distance, bearing and more between Latitude/Longitude points' 80 | // https://www.movable-type.co.uk/scripts/latlong.html 81 | var dlon = THREE.Math.degToRad(dest.longitude - src.longitude) 82 | var dlat = THREE.Math.degToRad(dest.latitude - src.latitude) 83 | 84 | var a = (Math.sin(dlat / 2) * Math.sin(dlat / 2)) + Math.cos(THREE.Math.degToRad(src.latitude)) * Math.cos(THREE.Math.degToRad(dest.latitude)) * (Math.sin(dlon / 2) * Math.sin(dlon / 2)) 85 | var angle = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) 86 | 87 | return angle * 6378160 88 | }, 89 | 90 | 91 | }) 92 | -------------------------------------------------------------------------------- /src/gps-camera-rotation.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('gps-camera-rotation', { 2 | 3 | lookControls: null, 4 | heading: null, 5 | 6 | 7 | schema: { 8 | }, 9 | 10 | init: function () { 11 | 12 | if( this.el.components['look-controls'] === undefined ) return 13 | 14 | this.lookControls = this.el.components['look-controls'] 15 | 16 | // listen to deviceorientation event 17 | var eventName = this._getDeviceOrientationEventName() 18 | this._$onDeviceOrientation = this._onDeviceOrientation.bind(this) 19 | window.addEventListener( eventName, this._$onDeviceOrientation, false) 20 | 21 | window.addEventListener('compassneedscalibration', function(event) { 22 | alert('Your compass needs calibrating! Wave your device in a figure-eight motion') 23 | event.preventDefault() 24 | }, true) 25 | 26 | }, 27 | 28 | tick: function( time, timeDelta ){ 29 | 30 | if( this.heading === null ) return 31 | 32 | this._updateRotation() 33 | 34 | }, 35 | 36 | remove: function () { 37 | var eventName = this._getDeviceOrientationEventName() 38 | window.removeEventListener(eventName, this._$onDeviceOrientation, false) 39 | }, 40 | 41 | _getDeviceOrientationEventName: function(){ 42 | if('ondeviceorientationabsolute' in window){ 43 | var eventName = 'deviceorientationabsolute' 44 | }else if('ondeviceorientation' in window){ 45 | var eventName = 'deviceorientation' 46 | }else{ 47 | var eventName = '' 48 | console.error('Compass not supported') 49 | } 50 | return eventName 51 | }, 52 | 53 | _computeCompassHeading: function (alpha, beta, gamma) { 54 | 55 | // Convert degrees to radians 56 | var alphaRad = alpha * (Math.PI / 180) 57 | var betaRad = beta * (Math.PI / 180) 58 | var gammaRad = gamma * (Math.PI / 180) 59 | 60 | // Calculate equation components 61 | var cA = Math.cos(alphaRad) 62 | var sA = Math.sin(alphaRad) 63 | var cB = Math.cos(betaRad) 64 | var sB = Math.sin(betaRad) 65 | var cG = Math.cos(gammaRad) 66 | var sG = Math.sin(gammaRad) 67 | 68 | // Calculate A, B, C rotation components 69 | var rA = - cA * sG - sA * sB * cG 70 | var rB = - sA * sG + cA * sB * cG 71 | var rC = - cB * cG 72 | 73 | // Calculate compass heading 74 | var compassHeading = Math.atan(rA / rB) 75 | 76 | // Convert from half unit circle to whole unit circle 77 | if(rB < 0) { 78 | compassHeading += Math.PI 79 | }else if(rA < 0) { 80 | compassHeading += 2 * Math.PI 81 | } 82 | 83 | // Convert radians to degrees 84 | compassHeading *= 180 / Math.PI 85 | 86 | return compassHeading 87 | }, 88 | 89 | _onDeviceOrientation: function( event ){ 90 | 91 | // compute heading 92 | if( event.webkitCompassHeading !== undefined ){ 93 | if(event.webkitCompassAccuracy < 50){ 94 | this.heading = event.webkitCompassHeading 95 | }else{ 96 | console.warn('webkitCompassAccuracy is event.webkitCompassAccuracy') 97 | } 98 | }else if( event.alpha !== null ){ 99 | if(event.absolute === true || event.absolute === undefined ) { 100 | this.heading = this._computeCompassHeading(event.alpha, event.beta, event.gamma) 101 | }else{ 102 | console.warn('event.absolute === false') 103 | } 104 | }else{ 105 | console.warn('event.alpha === null') 106 | } 107 | }, 108 | 109 | _updateRotation: function() { 110 | 111 | var heading = 360 - this.heading 112 | var cameraRotation = this.el.getAttribute('rotation').y 113 | var yawRotation = THREE.Math.radToDeg(this.lookControls.yawObject.rotation.y) 114 | 115 | var offset = ( heading - ( cameraRotation - yawRotation ) ) % 360 116 | 117 | this.lookControls.yawObject.rotation.y = THREE.Math.degToRad(offset) 118 | 119 | }, 120 | 121 | }) 122 | -------------------------------------------------------------------------------- /src/gps-entity-place.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('gps-entity-place', { 2 | 3 | _cameraGpsPosition: null, 4 | _deferredInitInterval: 0, 5 | 6 | schema: { 7 | latitude: { 8 | type: 'number', 9 | default: 0 10 | }, 11 | longitude: { 12 | type: 'number', 13 | default: 0 14 | }, 15 | }, 16 | 17 | init: function () { 18 | // TODO use a ._initialized = true instead 19 | if( this._deferredInit() ) return 20 | 21 | this._deferredInitInterval = setInterval(this._deferredInit.bind(this), 100) 22 | }, 23 | 24 | _deferredInit: function () { 25 | 26 | if( this._cameraGpsPosition === null ){ 27 | var camera = document.querySelector('a-camera, [camera]') 28 | if( camera.components['gps-camera-position'] === undefined) return 29 | this._cameraGpsPosition = camera.components['gps-camera-position'] 30 | } 31 | 32 | console.log(this._cameraGpsPosition.originCoords) 33 | if( this._cameraGpsPosition.originCoords === null ) return 34 | 35 | clearInterval(this._deferredInitInterval) 36 | this._deferredInitInterval = 0 37 | 38 | this._updatePosition() 39 | 40 | 41 | return true 42 | }, 43 | 44 | _updatePosition: function() { 45 | 46 | var position = {x: 0, y: 0, z: 0} 47 | 48 | // update position.x 49 | var dstCoords = { 50 | longitude: this.data.longitude, 51 | latitude: this._cameraGpsPosition.originCoords.latitude 52 | } 53 | position.x = this._cameraGpsPosition.computeDistanceMeters( this._cameraGpsPosition.originCoords, dstCoords ) 54 | position.x *= this.data.longitude > this._cameraGpsPosition.originCoords.longitude ? 1 : -1 55 | 56 | // update position.z 57 | var dstCoords = { 58 | longitude: this._cameraGpsPosition.originCoords.longitude, 59 | latitude: this.data.latitude 60 | } 61 | position.z = this._cameraGpsPosition.computeDistanceMeters(this._cameraGpsPosition.originCoords, dstCoords) 62 | position.z *= this.data.latitude > this._cameraGpsPosition.originCoords.latitude ? -1 : 1 63 | 64 | // update element's position in 3d world 65 | this.el.setAttribute('position', position) 66 | } 67 | }) 68 | -------------------------------------------------------------------------------- /tmp/obsolete-gps-ar.js: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////// 2 | // component gps-position 3 | ////////////////////////////////////////////////////////////////////////////// 4 | 5 | AFRAME.registerComponent('gps-position', { 6 | 7 | _watchPositionId: null, 8 | 9 | originCoords: null, 10 | currentCoords: null, 11 | 12 | schema: { 13 | accuracy: { 14 | type: 'int', 15 | default: 100 16 | }, 17 | 'origin-coords-latitude': { 18 | type: 'number', 19 | default: NaN 20 | }, 21 | 'origin-coords-longitude': { 22 | type: 'number', 23 | default: NaN 24 | } 25 | }, 26 | 27 | init: function () { 28 | 29 | if( !isNaN(this.data['origin-coords-latitude']) && !isNaN(this.data['origin-coords-longitude']) ){ 30 | this.originCoords = {latitude: this.data['origin-coords-latitude'], longitude: this.data['origin-coords-longitude']} 31 | } 32 | 33 | this._watchPositionId = this._initWatchGPS(function(position){ 34 | // https://developer.mozilla.org/en-US/docs/Web/API/Coordinates 35 | this.currentCoords = position.coords 36 | this._updatePosition() 37 | }.bind(this)) 38 | 39 | }, 40 | remove: function() { 41 | if(this._watchPositionId) navigator.geolocation.clearWatch(this._watchPositionId) 42 | this._watchPositionId = null 43 | }, 44 | 45 | _initWatchGPS: function( onSuccess, onError ){ 46 | // TODO put that in .init directly 47 | 48 | if( onError === undefined ){ 49 | onError = function(err) { console.warn('ERROR('+err.code+'): '+err.message) } 50 | } 51 | 52 | if( "geolocation" in navigator === false ){ 53 | onError({code: 0, message: 'Geolocation is not supported by your browser'}) 54 | return 55 | } 56 | 57 | // https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition 58 | return navigator.geolocation.watchPosition(onSuccess, onError, { 59 | enableHighAccuracy: true, 60 | maximumAge: 0, 61 | timeout: 27000 62 | }) 63 | }, 64 | 65 | _updatePosition: function () { 66 | // dont update if accuracy isnt good enough 67 | if( this.currentCoords.accuracy > this.data.accuracy ) return 68 | 69 | // init originCoords if needed 70 | if( this.originCoords === null ) this.originCoords = this.currentCoords 71 | 72 | var position = this.el.getAttribute('position') 73 | 74 | // compute position.x 75 | var dstCoords = { 76 | longitude: this.currentCoords.longitude, 77 | latitude: this.originCoords.latitude 78 | } 79 | position.x = this.computeDistanceMeters(this.originCoords, dstCoords) 80 | position.x *= this.currentCoords.longitude > this.originCoords.longitude ? 1 : -1 81 | 82 | // compute position.z 83 | var dstCoords = { 84 | longitude: this.originCoords.longitude, 85 | latitude: this.currentCoords.latitude 86 | } 87 | position.z = this.computeDistanceMeters(this.originCoords, dstCoords) 88 | position.z *= this.currentCoords.latitude > this.originCoords.latitude ? -1 : 1 89 | 90 | // update element position 91 | this.el.setAttribute('position', position) 92 | }, 93 | 94 | computeDistanceMeters: function(src, dest) { 95 | // 'Calculate distance, bearing and more between Latitude/Longitude points' 96 | // https://www.movable-type.co.uk/scripts/latlong.html 97 | var dlon = THREE.Math.degToRad(dest.longitude - src.longitude) 98 | var dlat = THREE.Math.degToRad(dest.latitude - src.latitude) 99 | 100 | var a = (Math.sin(dlat / 2) * Math.sin(dlat / 2)) + Math.cos(THREE.Math.degToRad(src.latitude)) * Math.cos(THREE.Math.degToRad(dest.latitude)) * (Math.sin(dlon / 2) * Math.sin(dlon / 2)) 101 | var angle = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) 102 | 103 | return angle * 6378160 104 | }, 105 | 106 | 107 | }) 108 | 109 | 110 | ////////////////////////////////////////////////////////////////////////////// 111 | // component compass-rotation 112 | ////////////////////////////////////////////////////////////////////////////// 113 | 114 | AFRAME.registerComponent('compass-rotation', { 115 | 116 | lookControls: null, 117 | lastTimestamp: 0, 118 | heading: null, 119 | 120 | 121 | schema: { 122 | fixTime: { 123 | type: 'int', 124 | default: 2000 125 | }, 126 | orientationEvent: { 127 | type: 'string', 128 | default: 'auto' 129 | } 130 | }, 131 | 132 | init: function () { 133 | 134 | if( this.el.components['look-controls'] === undefined ) return 135 | 136 | this.lookControls = this.el.components['look-controls'] 137 | 138 | this.handlerOrientation = this.handlerOrientation.bind(this) 139 | 140 | if( this.data.orientationEvent === 'auto' ){ 141 | if('ondeviceorientationabsolute' in window){ 142 | this.data.orientationEvent = 'deviceorientationabsolute' 143 | }else if('ondeviceorientation' in window){ 144 | this.data.orientationEvent = 'deviceorientation' 145 | }else{ 146 | this.data.orientationEvent = '' 147 | alert('Compass not supported') 148 | return 149 | } 150 | } 151 | 152 | window.addEventListener( this.data.orientationEvent, this.handlerOrientation, false) 153 | 154 | window.addEventListener('compassneedscalibration', function(event) { 155 | alert('Your compass needs calibrating! Wave your device in a figure-eight motion') 156 | event.preventDefault() 157 | }, true) 158 | 159 | }, 160 | 161 | tick: function( time, timeDelta ){ 162 | 163 | if(this.heading === null || this.lastTimestamp > (time - this.data.fixTime)) return 164 | 165 | this.lastTimestamp = time 166 | this._updateRotation() 167 | 168 | }, 169 | 170 | _computeCompassHeading: function (alpha, beta, gamma) { 171 | 172 | // Convert degrees to radians 173 | var alphaRad = alpha * (Math.PI / 180) 174 | var betaRad = beta * (Math.PI / 180) 175 | var gammaRad = gamma * (Math.PI / 180) 176 | 177 | // Calculate equation components 178 | var cA = Math.cos(alphaRad) 179 | var sA = Math.sin(alphaRad) 180 | var cB = Math.cos(betaRad) 181 | var sB = Math.sin(betaRad) 182 | var cG = Math.cos(gammaRad) 183 | var sG = Math.sin(gammaRad) 184 | 185 | // Calculate A, B, C rotation components 186 | var rA = - cA * sG - sA * sB * cG 187 | var rB = - sA * sG + cA * sB * cG 188 | var rC = - cB * cG 189 | 190 | // Calculate compass heading 191 | var compassHeading = Math.atan(rA / rB) 192 | 193 | // Convert from half unit circle to whole unit circle 194 | if(rB < 0) { 195 | compassHeading += Math.PI 196 | }else if(rA < 0) { 197 | compassHeading += 2 * Math.PI 198 | } 199 | 200 | // Convert radians to degrees 201 | compassHeading *= 180 / Math.PI 202 | 203 | return compassHeading 204 | }, 205 | 206 | handlerOrientation: function( event ){ 207 | 208 | var heading = null 209 | 210 | //console.log('device orientation event', event) 211 | 212 | if( event.webkitCompassHeading !== undefined ){ 213 | 214 | if(event.webkitCompassAccuracy < 50){ 215 | heading = event.webkitCompassHeading 216 | }else{ 217 | console.warn('webkitCompassAccuracy is event.webkitCompassAccuracy') 218 | } 219 | 220 | }else if( event.alpha !== null ){ 221 | if(event.absolute === true || event.absolute === undefined ) { 222 | heading = this._computeCompassHeading(event.alpha, event.beta, event.gamma) 223 | }else{ 224 | console.warn('event.absolute === false') 225 | } 226 | }else{ 227 | console.warn('event.alpha === null') 228 | } 229 | 230 | this.heading = heading 231 | }, 232 | 233 | _updateRotation: function() { 234 | 235 | /* 236 | camera.components["look-controls"].yawObject.rotation.y = THREE.Math.degToRad( 237 | ( 238 | 360 239 | - camera.components["compass-rotation"].heading 240 | - ( 241 | camera.getAttribute('rotation').y 242 | - THREE.Math.radToDeg(camera.components["look-controls"].yawObject.rotation.y) 243 | ) 244 | ) 245 | % 360 246 | ) 247 | */ 248 | 249 | 250 | var heading = 360 - this.heading 251 | var camera_rotation = this.el.getAttribute('rotation').y 252 | var yaw_rotation = THREE.Math.radToDeg(this.lookControls.yawObject.rotation.y) 253 | 254 | var offset = ( heading - ( camera_rotation - yaw_rotation ) ) % 360 255 | 256 | this.lookControls.yawObject.rotation.y = THREE.Math.degToRad(offset) 257 | 258 | }, 259 | 260 | remove: function () { 261 | if(this.data.orientationEvent){ 262 | window.removeEventListener(this.data.orientationEvent, this.handlerOrientation, false) 263 | } 264 | } 265 | 266 | }) 267 | 268 | 269 | ////////////////////////////////////////////////////////////////////////////// 270 | // Component gps-debug 271 | ////////////////////////////////////////////////////////////////////////////// 272 | 273 | AFRAME.registerComponent('gps-debug', { 274 | init : function(){ 275 | var camera = this.el; 276 | 277 | ////////////////////////////////////////////////////////////////////////////// 278 | // Create html 279 | ////////////////////////////////////////////////////////////////////////////// 280 | var domElement = document.createElement('div') 281 | domElement.innerHTML = ` 282 | 283 |
284 |
285 | current coords: , 286 | (origin coords: , ) 287 |
288 |
289 | camera coords: , 290 |
291 |
292 | compass heading: , 293 | camera angle: , 294 | yaw angle: 295 |
296 |
297 | ` 298 | document.body.appendChild(domElement.children[0]) 299 | 300 | // TODO cleanup this code 301 | // TODO build the html element in there 302 | 303 | camera.addEventListener('componentchanged', function (event) { 304 | switch(event.detail.name){ 305 | case 'rotation': 306 | //console.log('camera rotation changed', event.detail.newData); 307 | var compassRotation = camera.components['compass-rotation'] 308 | var lookControls = camera.components['look-controls'] 309 | 310 | camera_angle.innerText = event.detail.newData.y; 311 | 312 | if( lookControls ){ 313 | yaw_angle.innerText = THREE.Math.radToDeg(lookControls.yawObject.rotation.y); 314 | } 315 | if( compassRotation ){ 316 | compass_heading.innerText = compassRotation.heading; 317 | } 318 | break; 319 | case 'position': 320 | //console.log('camera position changed', event.detail.newData); 321 | camera_p_x.innerText = event.detail.newData.x; 322 | camera_p_z.innerText = event.detail.newData.z; 323 | 324 | var gpsPosition = camera.components['gps-position']; 325 | if( gpsPosition ){ 326 | if(gpsPosition.currentCoords){ 327 | current_coords_longitude.innerText = gpsPosition.currentCoords.longitude; 328 | current_coords_latitude.innerText = gpsPosition.currentCoords.latitude; 329 | } 330 | if(gpsPosition.originCoords){ 331 | origin_coords_longitude.innerText = gpsPosition.originCoords.longitude; 332 | origin_coords_latitude.innerText = gpsPosition.originCoords.latitude; 333 | } 334 | } 335 | 336 | break; 337 | } 338 | }); 339 | 340 | } 341 | }) 342 | 343 | 344 | ////////////////////////////////////////////////////////////////////////////// 345 | // Component gps-place 346 | ////////////////////////////////////////////////////////////////////////////// 347 | 348 | AFRAME.registerComponent('gps-place', { 349 | 350 | _cameraGpsPosition: null, 351 | _deferredInitInterval: 0, 352 | 353 | schema: { 354 | latitude: { 355 | type: 'number', 356 | default: 0 357 | }, 358 | longitude: { 359 | type: 'number', 360 | default: 0 361 | }, 362 | cameraSelector: { // TODO do i need this ? 363 | type: 'string', 364 | default: 'a-camera, [camera]' 365 | } 366 | }, 367 | 368 | init: function () { 369 | if( this._deferredInit() ) return 370 | this._deferredInitInterval = setInterval(this._deferredInit.bind(this), 100) 371 | }, 372 | 373 | _deferredInit: function () { 374 | 375 | if( this._cameraGpsPosition === null ){ 376 | var camera = document.querySelector(this.data.cameraSelector) 377 | if(typeof(camera.components['gps-position']) == 'undefined') return 378 | this._cameraGpsPosition = camera.components['gps-position'] 379 | } 380 | 381 | if( this._cameraGpsPosition.originCoords === null ) return 382 | 383 | this._updatePosition() 384 | 385 | clearInterval(this._deferredInitInterval) 386 | this._deferredInitInterval = 0 387 | 388 | return true 389 | }, 390 | 391 | _updatePosition: function() { 392 | 393 | var position = {x: 0, y: 0, z: 0} 394 | 395 | // update position.x 396 | var dstCoords = { 397 | longitude: this.data.longitude, 398 | latitude: this._cameraGpsPosition.originCoords.latitude 399 | } 400 | position.x = this._cameraGpsPosition.computeDistanceMeters( this._cameraGpsPosition.originCoords, dstCoords ) 401 | position.x *= this.data.longitude > this._cameraGpsPosition.originCoords.longitude ? 1 : -1 402 | 403 | // update position.z 404 | var dstCoords = { 405 | longitude: this._cameraGpsPosition.originCoords.longitude, 406 | latitude: this.data.latitude 407 | } 408 | position.z = this._cameraGpsPosition.computeDistanceMeters(this._cameraGpsPosition.originCoords, dstCoords) 409 | position.z *= this.data.latitude > this._cameraGpsPosition.originCoords.latitude ? -1 : 1 410 | 411 | // update element's position 412 | this.el.setAttribute('position', position) 413 | } 414 | }) 415 | -------------------------------------------------------------------------------- /tmp/original-old-buggy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 452 | 453 | 454 | 455 |
456 |
457 | coords: , 458 | (zero coords: , ) 459 |
460 |
461 | camera coords: , 462 |
463 |
464 | compass heading: , 465 | camera angle: , 466 | yaw angle: 467 |
468 |
geohash_7chars:
469 |
470 | 471 | 472 | 473 | 474 | 475 | 476 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 495 | 496 | 497 | 498 | 499 | 500 | -------------------------------------------------------------------------------- /tmp/phills-sphere.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 384 | 385 | 386 | 387 |
388 |
389 | coords: , 390 | (zero coords: , ) 391 |
392 |
393 | camera coords: , 394 |
395 |
396 | compass heading: , 397 | camera angle: , 398 | yaw angle: 399 |
400 |
401 | 402 | 403 | 404 | 405 | 406 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | --------------------------------------------------------------------------------