├── .editorconfig ├── .gitignore ├── CNAME ├── app.js ├── compass.html ├── config.dist.js ├── index.html ├── marker.svg └── rules.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = LF 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config.js 2 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | locat.me 2 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | // 5 | // Utilities 6 | // 7 | function guid() { 8 | function s4() { 9 | return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 10 | } 11 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 12 | } 13 | 14 | function now() { 15 | return Math.floor(Date.now() / 1000); 16 | } 17 | 18 | // 19 | // Environment 20 | // 21 | var myUuid; 22 | var mapId; 23 | 24 | mapId = location.hash.replace(/^#/, ''); 25 | if (!mapId) { 26 | mapId = (Math.random() + 1).toString(36).substring(2, 12); 27 | location.hash = mapId; 28 | } 29 | 30 | if (typeof localStorage == 'undefined') { 31 | document.getElementById('map').innerHTML = 'Sorry but your browser is not supported' 32 | return 33 | } 34 | 35 | myUuid = localStorage.getItem('myUuid'); 36 | if (!myUuid) { 37 | myUuid = guid(); 38 | localStorage.setItem('myUuid', myUuid); 39 | } 40 | 41 | 42 | // 43 | // Mapbox 44 | // 45 | var map; 46 | var markers = {}; 47 | var mapZooming = false; 48 | 49 | L.mapbox.accessToken = config.mapbox.accessToken; 50 | 51 | map = L.mapbox.map('map', config.mapbox.mapId, { 52 | zoomControl: false, 53 | attributionControl: false, 54 | tileLayer: { 55 | maxNativeZoom: 19 56 | } 57 | }).setView([48.861920, 2.341755], 18) 58 | 59 | // map.on('ready', function() { console.log('map.ready') }); 60 | 61 | // https://github.com/bbecquet/Leaflet.PolylineDecorator 62 | L.RotatedMarker = L.Marker.extend({ 63 | options: { angle: 0 }, 64 | _setPos: function(pos) { 65 | L.Marker.prototype._setPos.call(this, pos); 66 | if (L.DomUtil.TRANSFORM) { 67 | // use the CSS transform rule if available 68 | this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)'; 69 | } else if (L.Browser.ie) { 70 | // fallback for IE6, IE7, IE8 71 | var rad = this.options.angle * L.LatLng.DEG_TO_RAD, 72 | costheta = Math.cos(rad), 73 | sintheta = Math.sin(rad); 74 | this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + 75 | costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')'; 76 | } 77 | } 78 | }); 79 | L.rotatedMarker = function(pos, options) { 80 | return new L.RotatedMarker(pos, options); 81 | }; 82 | 83 | map.on('zoomstart', function() { 84 | mapZooming = true 85 | }) 86 | map.on('zoomend', function() { 87 | mapZooming = false 88 | }) 89 | 90 | function createIcon(uuid, point) { 91 | var color 92 | var svg; 93 | 94 | if (uuid === myUuid) { 95 | // Own marker 96 | color = '#2196f3' 97 | } else if (point.timestamp < now() - 60) { 98 | // Inactive marker 99 | color = '#bdbdbd' 100 | } else { 101 | // Others marker 102 | color = '#ff9800' 103 | } 104 | 105 | var svg = '' 110 | 111 | return L.icon({ 112 | iconUrl: 'data:image/svg+xml;base64,' + btoa(svg), 113 | iconSize: [40, 40], 114 | }) 115 | } 116 | 117 | function addPoint(uuid, point) { 118 | var marker = L.rotatedMarker([point.coords.latitude, point.coords.longitude], { 119 | //zIndexOffset: (uuid === myUuid ? 1000 : 0), 120 | icon: createIcon(uuid, point) 121 | }) 122 | 123 | markers[uuid] = marker; 124 | 125 | marker.options.angle = point.orientation; 126 | marker.addTo(map) 127 | 128 | map.fitBounds(Object.keys(markers).map(function(uuid) { 129 | return markers[uuid].getLatLng() 130 | })) 131 | } 132 | 133 | function removePoint(uuid) { 134 | if (markers[uuid]) { 135 | map.removeLayer(markers[uuid]) 136 | //markers[uuid] = null 137 | } 138 | } 139 | 140 | function updatePoint(uuid, point) { 141 | // Avoid clipping effect when zooming map + updating point at the same time. 142 | if (mapZooming) { 143 | map.once('zoomend', function() { 144 | updatePoint(uuid, point) 145 | }) 146 | return 147 | } 148 | 149 | var marker = markers[uuid] 150 | 151 | marker.setIcon(createIcon(uuid, point)); 152 | 153 | marker.options.angle = point.orientation 154 | marker.setLatLng([point.coords.latitude, point.coords.longitude]) 155 | } 156 | 157 | function putPoint(uuid, point) { 158 | if (markers[uuid]) { 159 | updatePoint(uuid, point) 160 | } else { 161 | addPoint(uuid, point) 162 | } 163 | } 164 | 165 | 166 | // 167 | // Firebase 168 | // 169 | var endpoint; 170 | 171 | endpoint = new Firebase('https://' + config.firebase + '.firebaseio.com/maps/' + mapId); 172 | 173 | endpoint.on('child_added', function(childSnapshot) { 174 | var uuid = childSnapshot.key() 175 | var point = childSnapshot.val() 176 | 177 | if (uuid === myUuid) return 178 | 179 | addPoint(uuid, point) 180 | }) 181 | 182 | endpoint.on('child_changed', function(childSnapshot) { 183 | var uuid = childSnapshot.key() 184 | var point = childSnapshot.val() 185 | 186 | if (uuid === myUuid) return 187 | 188 | putPoint(uuid, point) 189 | }) 190 | 191 | endpoint.on('child_removed', function(oldChildSnapshot) { 192 | var uuid = oldChildSnapshot.key() 193 | 194 | removePoint(uuid) 195 | }) 196 | 197 | // 198 | // Tracking 199 | // 200 | var watchPositionId; 201 | var currentCoords = null; 202 | var currentOrientation = null; 203 | 204 | function pushCurrentStatus() { 205 | if (!currentCoords) return 206 | 207 | endpoint.child(myUuid).set({ 208 | coords: { 209 | latitude: currentCoords.latitude, 210 | longitude: currentCoords.longitude, 211 | }, 212 | orientation: currentOrientation, 213 | timestamp: now() 214 | }) 215 | } 216 | pushCurrentStatus = _.throttle(pushCurrentStatus, 50) 217 | 218 | if (navigator.geolocation) { 219 | setTimeout(function() { 220 | watchPositionId = navigator.geolocation.watchPosition( 221 | successWatchPosition, 222 | failWatchPosition, 223 | {enableHighAccuracy: false} 224 | ) 225 | }, 0) 226 | 227 | setTimeout(function() { 228 | navigator.geolocation.clearWatch(watchPositionId) 229 | 230 | watchPositionId = navigator.geolocation.watchPosition( 231 | successWatchPosition, 232 | failWatchPosition, 233 | {enableHighAccuracy: true} 234 | ) 235 | }, 5000) 236 | } 237 | 238 | function successWatchPosition(position) { 239 | if (!position.coords) return 240 | 241 | currentCoords = position.coords 242 | 243 | pushCurrentStatus() 244 | putPoint(myUuid, {coords: currentCoords, orientation: currentOrientation}) 245 | } 246 | 247 | function failWatchPosition() { 248 | alert('Fail to get your location') 249 | } 250 | 251 | 252 | if (window.DeviceOrientationEvent) { 253 | window.addEventListener('deviceorientation', deviceOrientationHandler, true) 254 | } 255 | 256 | function deviceOrientationHandler(event) { 257 | var alpha; 258 | 259 | if (event.webkitCompassHeading) { 260 | alpha = event.webkitCompassHeading; 261 | } else { 262 | alpha = event.alpha; 263 | } 264 | 265 | if (!alpha) return 266 | 267 | currentOrientation = 360 - alpha 268 | 269 | pushCurrentStatus() 270 | putPoint(myUuid, {coords: currentCoords, orientation: currentOrientation}) 271 | } 272 | 273 | 274 | // 275 | // Remove old markers 276 | // 277 | setInterval(function() { 278 | endpoint.limitToFirst(100).once('value', function(snap) { 279 | var now = Math.floor(Date.now() / 1000) 280 | 281 | snap.forEach(function(childSnapshot) { 282 | var uuid = childSnapshot.key() 283 | var point = childSnapshot.val() 284 | 285 | if (uuid === myUuid) return 286 | 287 | if (childSnapshot.val().timestamp < now - 60 * 30) { 288 | endpoint.child(uuid).set(null) 289 | } else { 290 | updatePoint(uuid, point) 291 | } 292 | }) 293 | }) 294 | }, 5000); 295 | 296 | })(); 297 | -------------------------------------------------------------------------------- /compass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |12 | Value = 13 |
14 | 15 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /config.dist.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | firebase: '', 3 | mapbox: { 4 | accessToken: '', 5 | mapId: '', 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |