Difference between revisions of "Widget:SpaceAPI"
m (added debug mode, shortened list) |
m (Try and fix SpaceAPI popup alignment) |
||
(49 intermediate revisions by the same user not shown) | |||
Line 10: | Line 10: | ||
|url=/spaceAPI/ | |url=/spaceAPI/ | ||
|width=260px | |width=260px | ||
− | |height= | + | |height=300px |
|padding=8px | |padding=8px | ||
|interval=20 | |interval=20 | ||
|float=right | |float=right | ||
− | |features= | + | |features=beacon,annex |
}}</nowiki> | }}</nowiki> | ||
This will give the following result:<br/> | This will give the following result:<br/> | ||
{{#widget:{{PAGENAME}} | {{#widget:{{PAGENAME}} | ||
− | |url=/spaceAPI/ | + | |url=/spaceAPI/?beacon_log=HoT |
|width=260px | |width=260px | ||
− | |height= | + | |height=300px |
|padding=8px | |padding=8px | ||
|interval=20 | |interval=20 | ||
|float=right | |float=right | ||
− | |features= | + | |features=beacon,annex |
}}<br/> | }}<br/> | ||
'''Notes''' | '''Notes''' | ||
Line 38: | Line 38: | ||
(function( ) | (function( ) | ||
{ | { | ||
− | + | "use strict"; | |
− | + | /* | |
− | + | TODO: | |
− | + | separate rooms (get latlng) | |
− | + | zoom onto toolbox | |
− | + | table island not correct (4, not 5 tables) | |
− | + | ||
− | + | */ | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if ( typeof SpaceAPI === "undefined" ) | |
− | + | { | |
− | + | window.SpaceAPI = function( _width, _height, _float, _padding, _url, _interval, _features ) | |
− | + | { | |
− | + | this._width = _width; | |
− | + | this._height = _height; | |
− | + | this._padding = _padding; | |
− | + | this._url = _url; | |
− | + | this._interval = 1000 * _interval; | |
− | + | this._float = _float; | |
− | + | this._features = _features; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | SpaceAPI.prototype.data = null; | |
− | + | SpaceAPI.prototype._width = null; | |
− | + | SpaceAPI.prototype._height = null; | |
+ | SpaceAPI.prototype._float = null; | ||
+ | SpaceAPI.prototype._url = null; | ||
+ | SpaceAPI.prototype._features = null; | ||
+ | SpaceAPI.prototype._interval = null; | ||
+ | SpaceAPI.prototype._intervalId = null; | ||
+ | SpaceAPI.prototype._node = null; | ||
+ | SpaceAPI.prototype._leaflet = null; | ||
+ | SpaceAPI.prototype._msgLoading = "Loading.."; | ||
+ | SpaceAPI.prototype._msgError = "Error"; | ||
+ | SpaceAPI.prototype._msgParserError = "Failed to parse space state information"; | ||
+ | SpaceAPI.prototype._msgOpen = "Open"; | ||
+ | SpaceAPI.prototype._msgClosed = "Closed"; | ||
+ | SpaceAPI.prototype._msgUnknown = "Unknown"; | ||
+ | SpaceAPI.prototype._msgSince = "Since: "; | ||
+ | SpaceAPI.prototype._colorOpen = "#0f0"; | ||
+ | SpaceAPI.prototype._colorClosed = "#f00"; | ||
+ | SpaceAPI.prototype._colorUnknown = "#f70"; | ||
+ | SpaceAPI.prototype._debug = null; | ||
+ | |||
+ | SpaceAPI.prototype._drawBeaconPolyLine = function( ) | ||
+ | { | ||
+ | if ( !this.data.sensors || !this.data.sensors.beacon || this.data.sensors.beacon.length < 50 ) | ||
+ | return; | ||
+ | |||
+ | var points = []; | ||
+ | var bounds = this._leaflet.map.getBounds(); | ||
+ | |||
+ | // Only add points within the bounds | ||
+ | if ( this._leaflet.beaconCount !== this.data.sensors.beacon.length ) | ||
+ | { | ||
+ | this._leaflet.beaconCount = this.data.sensors.beacon.length; | ||
+ | for ( var n = 0, len = this.data.sensors.beacon.length; n < len; ++n ) | ||
+ | { | ||
+ | var point = L.latLng( this.data.sensors.beacon[n].location.lat, this.data.sensors.beacon[n].location.lon ); | ||
+ | points.push( point ); | ||
+ | } | ||
+ | |||
+ | if ( !this._leaflet.beaconline ) | ||
+ | this._leaflet.beaconline = L.polyline(points, {color: 'green'}).addTo( this._leaflet.map ); | ||
+ | else | ||
+ | this._leaflet.beaconline.setLatLngs( points ); | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | SpaceAPI.prototype.start = function( ) | ||
+ | { | ||
+ | // Use interval timer id as image id | ||
this._debug = ( location.hash.split("#").slice(1).indexOf("debug") !== -1 ); | this._debug = ( location.hash.split("#").slice(1).indexOf("debug") !== -1 ); | ||
− | + | this._intervalId = 0; | |
− | + | if ( this._interval > 0 ) | |
− | + | this._intervalId = setInterval( this._fetchState.bind( this ), this._interval ); | |
− | + | document.write( '<div id="spaceAPI' + this._intervalId + '"></div>' ); | |
− | + | var node = document.getElementById( "spaceAPI" + this._intervalId ); | |
− | + | if ( !node ) | |
− | + | { | |
− | + | console && console.log( "node not found" ); | |
− | + | return; | |
− | + | } | |
− | + | node.style.width = this._width; | |
− | + | node.style.height = this._height; | |
− | |||
− | |||
− | + | node.style.textAlign = "center"; | |
− | + | node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)"; | |
+ | node.style.position = "relative"; | ||
− | + | if ( this._float ) | |
− | + | node.style.float = this._float; | |
− | |||
− | |||
− | + | this._node = node.appendChild( document.createElement( "a" ) ); | |
− | + | this._node.style.display = "block"; | |
− | + | this._node.style.color = "inherit"; | |
− | + | this._node.style.padding = this._padding; | |
− | + | this._node.textContent = this._msgLoading; | |
− | + | this._node.href = "/spaceAPI/statechanges.html"; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if ( this._features.split(",").indexOf( "beacon" ) >= 0 ) | |
− | + | { | |
− | + | var srcNode; | |
− | + | var mapNode = node.appendChild( document.createElement( "div" ) ); | |
− | + | mapNode.style.width = "100%"; | |
− | + | mapNode.style.height = "calc(" + this._height + " + " + this._padding + " - 2em)"; | |
− | + | ||
− | + | srcNode = document.createElement( "link" ); | |
− | + | srcNode.href = "/leaflet/leaflet.css"; | |
− | + | srcNode.rel = "stylesheet"; | |
− | + | srcNode.type = "text/css"; | |
− | + | // Load the css | |
− | + | ( document.head || document.documentElement ).appendChild( srcNode ); | |
− | + | ||
− | + | srcNode = document.createElement( "script" ); | |
− | + | srcNode.src = "/leaflet/leaflet.js"; | |
− | + | srcNode.type = "text/javascript"; | |
+ | srcNode.addEventListener( "load", function( _evt ) | ||
+ | { | ||
+ | this._leaflet = {}; | ||
+ | this._leaflet.point = L.latLng( 50.8925,5.9713 ); | ||
+ | this._leaflet.map = L.map( mapNode ).panTo( this._leaflet.point ); | ||
+ | //this._leaflet.map = L.map( mapNode ).setView( this._leaflet.point, 16); | ||
− | + | L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857, | |
− | + | { | |
− | + | scale: function( zoom ) { | |
− | + | if ( zoom < 24 ) | |
− | + | return 256 * Math.pow( 2, zoom ); | |
+ | |||
+ | // Freeze the actual zooming above this level to show the different floors | ||
+ | // 256 * pow( 2, 23 ) | ||
+ | return 2147483648; | ||
+ | } | ||
+ | }); | ||
/**********************************/ | /**********************************/ | ||
Line 212: | Line 249: | ||
/*******************************/ | /*******************************/ | ||
− | + | L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
− | + | attribution: '© <a href="//openstreetmap.org/copyright">OpenStreetMap</a> contributors', | |
− | + | minZoom: 2, | |
− | + | maxZoom: 28, | |
− | + | maxNativeZoom: 19 | |
− | + | }).addTo( this._leaflet.map ); | |
// Generic zoom (note that this will cause 404s | // Generic zoom (note that this will cause 404s | ||
L.tileLayer("/images/ACK_{x}_{y}_{z}.png", { | L.tileLayer("/images/ACK_{x}_{y}_{z}.png", { | ||
− | + | attribution: 'ACKspace', | |
− | + | minZoom: 24, | |
− | + | maxZoom: 28, | |
− | + | maxNativeZoom: 23 | |
− | + | }).addTo( this._leaflet.map ); | |
// special zoom | // special zoom | ||
− | + | var funcLayer = new L.TileLayer.Functional( function( view ) | |
− | + | { | |
var bounds = { | var bounds = { | ||
+ | 16 : { cl: 33855, ch: 33855, rl: 21971, rh: 21971 }, | ||
+ | 17 : { cl: 67710, ch: 67710, rl: 43942, rh: 43942 }, | ||
+ | 18 : { cl: 135420, ch: 135420, rl: 87884, rh: 87884 }, | ||
+ | 19 : { cl: 270840, ch: 270840, rl: 175768, rh: 175768 }, | ||
+ | 20 : { cl: 541680, ch: 541680, rl: 351536, rh: 351537}, | ||
21 : { cl: 1083360, ch: 1083361, rl: 703073, rh: 703074 }, | 21 : { cl: 1083360, ch: 1083361, rl: 703073, rh: 703074 }, | ||
22 : { cl: 2166720, ch: 2166723, rl: 1406146, rh: 1406148 }, | 22 : { cl: 2166720, ch: 2166723, rl: 1406146, rh: 1406148 }, | ||
Line 253: | Line 295: | ||
.replace('{x}', view.tile.column) | .replace('{x}', view.tile.column) | ||
.replace('{s}', view.subdomain); | .replace('{s}', view.subdomain); | ||
− | + | ||
− | + | return url; | |
− | + | }, | |
− | + | { | |
− | + | attribution: 'ACKspace', | |
− | + | minZoom: 16, | |
− | + | maxZoom: 28 | |
− | + | } ).addTo( this._leaflet.map ); | |
+ | |||
+ | this._leaflet.map.on('moveend', function( _event ) | ||
+ | { | ||
+ | //this._drawBeaconPolyLine( _event ); | ||
+ | |||
+ | }, this ); | ||
this._leaflet.map.on('zoomend', function( _event ) | this._leaflet.map.on('zoomend', function( _event ) | ||
{ | { | ||
+ | var o = 1, z = _event.target.getZoom(); | ||
+ | |||
+ | /* | ||
+ | // Infinite zoom | ||
+ | if ( z > 27 ) | ||
+ | { | ||
+ | // boundingbox contains center and zoom > 27 | ||
+ | // then zoom out | ||
+ | // setView/panTo/setZoom/fitBounds/etc. | ||
+ | //_event.target.getCenter() | ||
+ | _event.target.setZoom( 2, true ); | ||
+ | } | ||
+ | */ | ||
+ | |||
+ | |||
+ | //this._drawBeaconPolyLine( _event ); | ||
+ | |||
+ | |||
+ | if ( !this._leaflet.temperatures["000005667ABD"] ) | ||
+ | return; | ||
+ | |||
// zoom 18->23 opacity 1->0 | // zoom 18->23 opacity 1->0 | ||
− | |||
if ( z > 17 ) | if ( z > 17 ) | ||
o = Math.max( (23-z) / 5, 0 ); | o = Math.max( (23-z) / 5, 0 ); | ||
− | this._leaflet.temperatures[" | + | this._leaflet.temperatures["000005667ABD"].setStyle( { fillOpacity: o } ); |
}, this ); | }, this ); | ||
Line 294: | Line 362: | ||
} | } | ||
− | + | // "Follow" control | |
− | + | this._leaflet.follow = null; | |
− | + | L.Control.Command = L.Control.extend( | |
− | + | { | |
− | + | options: | |
− | + | { | |
− | + | position: 'topleft', | |
− | + | }, | |
− | + | ||
− | + | onAdd: function( _map ) | |
− | + | { | |
− | + | var controlDiv = L.DomUtil.create( "div", "leaflet-bar" ); | |
− | + | var controlUI = L.DomUtil.create( "a", "leaflet-clickable" + (this._leaflet.follow ? " toggle" : ""), controlDiv ); | |
− | + | controlUI.innerHTML = "⌖"; | |
− | + | controlUI.style.fontSize = "35px"; | |
− | + | controlUI.title = 'Follow beacon'; | |
− | + | ||
− | + | L.DomEvent.addListener( controlUI, 'click', function( _evt ) | |
− | + | { | |
− | + | this._leaflet.follow = !this._leaflet.follow; | |
− | + | _evt.currentTarget.className = "leaflet-clickable" + (this._leaflet.follow ? " toggle" : ""); | |
+ | |||
+ | // Update the map immediately | ||
+ | if ( this._leaflet.follow ) | ||
+ | { | ||
+ | // Determine the bounding box to 'follow | ||
+ | var bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon ) | ||
+ | { | ||
+ | return _beacon.point; | ||
+ | } ) ); | ||
+ | |||
+ | if ( !this._leaflet.beacons.length ) | ||
+ | bounds.extend( this._leaflet.point ); | ||
+ | |||
+ | // Depending on polyline: don't zoom | ||
+ | //this._leaflet.map.fitBounds( bounds ); | ||
+ | //if ( this._leaflet.map.getZoom() > 18 ) | ||
+ | //this._leaflet.map.setZoom( 18 ); | ||
+ | } | ||
+ | }.bind( this ) ); | ||
+ | |||
+ | return controlDiv; | ||
+ | }.bind( this ) | ||
+ | } ); | ||
+ | |||
+ | this._leaflet.map.addControl( new L.Control.Command() ); | ||
+ | |||
+ | // Icons | ||
+ | this._leaflet.icons = { | ||
+ | "HoaB" : L.icon( { | ||
+ | iconUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.png', | ||
+ | iconSize: [32, 32], | ||
+ | iconAnchor: [16, 26], | ||
+ | popupAnchor: [0, -26], | ||
+ | shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.shadow.png', | ||
+ | shadowSize: [59, 32], | ||
+ | shadowAnchor: [16, 26] | ||
+ | } ), | ||
+ | "HoT" : L.icon( { | ||
+ | iconUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.png', | ||
+ | iconSize: [32, 32], | ||
+ | iconAnchor: [16, 26], | ||
+ | popupAnchor: [0, -26], | ||
+ | shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.shadow.png', | ||
+ | shadowSize: [59, 32], | ||
+ | shadowAnchor: [16, 26] | ||
+ | } ) | ||
+ | }; | ||
+ | this._leaflet.descriptions = { | ||
+ | "HoaB" : "Hackers on a Bike", | ||
+ | "HoT" : "Hackers on Tour" | ||
+ | }; | ||
+ | |||
+ | this._leaflet.marker = L.marker( this._leaflet.point ).addTo( this._leaflet.map ); | ||
+ | this._leaflet.beacons = []; | ||
+ | if ( this._debug ) | ||
+ | this._leaflet.temperatures = {}; | ||
+ | else | ||
+ | this._leaflet.temperatures = { | ||
+ | // outside | ||
+ | "000005671715" : L.polygon([[50.892537811238,5.9711101056338], [50.892517508729,5.9710966945888], [50.892534427487,5.9710564614536], [50.892715457818,5.9712093473674], [50.892696847256,5.9712790848018], [50.892808510518,5.9713836909534], [50.892788208127,5.9714373351337], [50.892774673195,5.9714319707157], [50.892681620427,5.971697509408], [50.892641015525,5.9716653228998], [50.892559805614,5.971893310666], [50.892569956861,5.9719120861291], [50.892559805614,5.9719496370554], [50.892377082796,5.9717940689325], [50.892399077248,5.9717189670801], [50.89233140198,5.9716545940637], [50.892350012689,5.9716009498835], [50.892326326331,5.9715794922114], [50.892414304169,5.9713032246828], [50.892458293026,5.971340775609]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ), | ||
+ | // cold zone | ||
+ | "000005667ABD" : L.polygon( [[50.892458293019,5.971340775608], [50.892410920402,5.9713032246818], [50.8924743319376, 5.9711186739150435], [50.89251872057557, 5.97115655487869]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ), | ||
+ | // barbecue | ||
+ | "DEADBEEF0" : L.circle( [ 50.89277, 5.97134 ], 1, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ), | ||
+ | // hot zone | ||
+ | "00000567138A" : L.circle( [ 50.89250, 5.9711867 ], 2, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ) | ||
+ | }; | ||
+ | |||
+ | var linesRed = [[ | ||
+ | [50.892514028030334, 5.971161649524675], [50.89251319586179, 5.971160894608546]],[ | ||
+ | [50.89251114428003, 5.9711696663976], [50.89251024343285, 5.9711689967662105]],[ | ||
+ | [50.8924980950481, 5.971208224073053], [50.892496785531904, 5.971207094194142]],[ | ||
+ | [50.892495831897186, 5.971215394539513], [50.89249461680935, 5.971214339470522]],[ | ||
+ | [50.892482398287335, 5.971254612622375], [50.89248148152484, 5.971253695897759]],[ | ||
+ | [50.89248006555828, 5.971261391486564], [50.8924792609356, 5.971260443329812]],[ | ||
+ | [50.89246652683481, 5.971301496043679], [50.8924656730421, 5.971300341188908]],[ | ||
+ | [50.892464393586884, 5.971307247454547], [50.89246356324314, 5.971306176293467]] | ||
+ | ]; | ||
+ | |||
+ | var linesBlue = [[ | ||
+ | [50.892505941019564, 5.971185087508389], [50.89250524711004, 5.971184503287078]],[ | ||
+ | [50.892503858003316, 5.971191544085743], [50.892503132264245, 5.9711907897144565]],[ | ||
+ | [50.89249048160161, 5.971230519935489], [50.89248932268764, 5.971229581079457]],[ | ||
+ | [50.89248770274671, 5.971239335350448], [50.89248661219504, 5.97123836979705]],[ | ||
+ | [50.89247468364275, 5.9712776963039005], [50.892473913492424, 5.97127688527138]],[ | ||
+ | [50.892472469662096, 5.971284294786302], [50.89247146830519, 5.971283237998365]],[ | ||
+ | [50.89245897037926, 5.971323475241662], [50.89245824463948, 5.9713221760466695]],[ | ||
+ | [50.89245672874569, 5.971331032450281], [50.89245581256435, 5.971329635940493]] | ||
+ | ]; | ||
+ | |||
+ | if ( this._debug ) | ||
+ | { | ||
+ | L.polyline(linesRed, {color: 'red'}).addTo( this._leaflet.map ); | ||
+ | L.polyline(linesBlue, {color: 'blue'}).addTo( this._leaflet.map ); | ||
+ | } | ||
+ | }.bind( this ) ); | ||
+ | |||
+ | // Load the script | ||
+ | ( document.head || document.documentElement ).appendChild( srcNode ); | ||
+ | } // beacon | ||
− | + | // Update the space state immediately | |
− | + | setTimeout( this._fetchState.bind( this ), 1 ); | |
− | + | }; | |
− | + | SpaceAPI.prototype._determineColor = function( _temperature ) | |
− | + | { | |
− | + | var tempteratureColors = [ | |
− | + | [ -10, 0, 0, 0], // black | |
− | + | [ 0, 0, 0,255], // blue | |
+ | [ 15, 255,255, 0], // yellow | ||
+ | [ 35, 255, 0, 0], // red | ||
+ | [ 45, 255,255,255], // white | ||
+ | [5000, 255,255,255] // white, blink | ||
+ | ]; | ||
+ | var index; | ||
+ | var ratio; | ||
− | + | for ( var nTemp = tempteratureColors.length - 1; nTemp; nTemp-- ) | |
− | + | { | |
+ | if ( _temperature >= tempteratureColors[ nTemp ][0] ) | ||
+ | break; | ||
− | + | ratio = 0; | |
− | + | if ( index = nTemp ) | |
− | + | ratio = (_temperature - tempteratureColors[ nTemp - 1 ][0]) / ( tempteratureColors[ nTemp ][0] - tempteratureColors[ nTemp - 1 ][0] ); | |
− | + | } | |
− | |||
− | |||
− | |||
− | + | var lo = tempteratureColors[ index ? index - 1 : 0 ]; | |
+ | var hi = tempteratureColors[ index ]; | ||
− | + | return "rgb("+Math.round( (lo[1] * (1 - ratio) + hi[1] * ratio) ) +","+Math.round( (lo[2] * (1 - ratio) + hi[2] * ratio))+","+Math.round( (lo[3] * (1 - ratio) + hi[3] * ratio))+")"; | |
− | + | }; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | SpaceAPI.prototype._nlsTime = function( _time ) | |
− | + | { | |
− | + | var postfix; | |
− | + | if ( _time < 2 ) | |
− | + | { | |
− | + | return "moments"; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if ( _time > 31556952 ) | |
− | + | { | |
− | + | _time /= 31556952; | |
+ | postfix = "Year"; | ||
+ | } | ||
+ | else if ( _time > 2629746 ) | ||
+ | { | ||
+ | _time /= 2629746; | ||
+ | postfix = "Month"; | ||
+ | } | ||
+ | else if ( _time > 604800 ) | ||
+ | { | ||
+ | _time /= 604800; | ||
+ | postfix = "Week"; | ||
+ | } | ||
+ | else if ( _time > 86400 ) | ||
+ | { | ||
+ | _time /= 86400; | ||
+ | postfix = "day"; | ||
+ | } | ||
+ | else if ( _time > 3600 ) | ||
+ | { | ||
+ | _time /= 3600; | ||
+ | postfix = "hour"; | ||
+ | } | ||
+ | else if ( _time > 60 ) | ||
+ | { | ||
+ | _time /= 60; | ||
+ | postfix = "minute"; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | postfix = "second"; | ||
+ | } | ||
− | + | _time = Math.round( _time ); | |
− | + | return _time + " " + postfix + (_time !== 1 ? "s" : ""); | |
− | + | }; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | SpaceAPI.prototype._fetchState = function( ) | |
− | + | { | |
− | + | this._node.className = "processing"; | |
− | + | var xhr = new XMLHttpRequest( ); | |
+ | if ( !!( "onload" in xhr ) ) | ||
+ | { | ||
+ | xhr.onreadystatechange = function( _event ) | ||
+ | { | ||
+ | if ( _event.target.readyState !== 4 ) | ||
+ | return; | ||
+ | if ( _event.target.status === 200 ) | ||
+ | this._xhr_onload.apply( this, arguments ); | ||
+ | else | ||
+ | this._xhr_onerror.apply( this, arguments ); | ||
+ | }.bind( this ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | // Modern xhr | ||
+ | xhr.onload = this._xhr_onload.bind( this ); | ||
+ | xhr.onerror = this._xhr_onerror.bind( this ); | ||
+ | } | ||
− | + | xhr.open( "GET", this._url, true ); | |
− | |||
− | |||
− | |||
− | + | // Tells server that this call is made for ajax purposes. | |
− | + | // Most libraries like jQuery/Prototype/Dojo do this | |
+ | xhr.setRequestHeader( "X-Requested-With", "XMLHttpRequest" ); | ||
+ | |||
+ | // No data needs to be sent along with the request. | ||
+ | xhr.send( null ); | ||
+ | }; | ||
+ | |||
+ | SpaceAPI.prototype._updateState = function( _message, _color, _title ) | ||
+ | { | ||
+ | this._node.className = ""; | ||
+ | this._node.textContent = _message; | ||
+ | this._node.style.backgroundColor = _color; | ||
+ | if ( _title ) | ||
+ | this._node.title = _title; | ||
+ | else | ||
+ | this._node.title = ""; | ||
+ | }; | ||
+ | |||
+ | SpaceAPI.prototype._xhr_onload = function( _event ) | ||
+ | { | ||
+ | var open = null; | ||
+ | var message = null; | ||
+ | var title = null; | ||
+ | |||
+ | try | ||
+ | { | ||
+ | this.data = JSON.parse( _event.target.responseText ); | ||
+ | open = this.data.state.open; | ||
+ | message = this.data.state.message; | ||
+ | |||
+ | // Start as epoch timestamp (NOTE: check if timezone doesn't mess things up) | ||
+ | var d = new Date( 0 ); | ||
+ | d.setUTCSeconds( this.data.state.lastchange ); | ||
+ | title = this._msgSince + d.toLocaleString( ); | ||
+ | } | ||
+ | catch( _e ) | ||
+ | { | ||
+ | message = this._msgParserError; | ||
+ | } | ||
+ | |||
+ | if ( open ) | ||
+ | this._updateState( message || this._msgOpen, this._colorOpen, title ); | ||
+ | else if ( open === false ) | ||
+ | this._updateState( message || this._msgClosed, this._colorClosed, title ); | ||
+ | else | ||
+ | this._updateState( message || this._msgUnknown, this._colorUnknown, title ); | ||
+ | |||
+ | if ( this._leaflet ) | ||
+ | { | ||
+ | // Handle temperatures | ||
+ | if ( this.data.sensors && this.data.sensors.temperature && this.data.sensors.temperature.length ) | ||
+ | { | ||
+ | // Iterate the sensors and match a local sensor name | ||
+ | this.data.sensors.temperature.forEach( function( _apiTemp ) | ||
+ | { | ||
+ | var temp = this._leaflet.temperatures[ _apiTemp.name ]; | ||
+ | if ( temp ) | ||
+ | { | ||
+ | temp.setStyle({color: this._determineColor( _apiTemp.value )}); | ||
+ | |||
+ | var delta = (Date.now() - new Date( _apiTemp.ext_lastchange * 1000 )) / 1000; | ||
+ | temp.custom = { | ||
+ | description: _apiTemp.description | _apiTemp.name, | ||
+ | location: _apiTemp.location | null, | ||
+ | value: _apiTemp.value, | ||
+ | lastchange: _apiTemp.ext_lastchange | ||
+ | } | ||
+ | //updatePopup( temp | ||
− | + | temp.bindPopup( "Description: " + _apiTemp.description + "<br/>Location: " + _apiTemp.location + "<br/>Value: " + _apiTemp.value + _apiTemp.unit + "<br/>Last update: " + this._nlsTime( delta ) + " ago" ); | |
− | + | } | |
+ | }, this ); | ||
+ | } | ||
− | + | var bounds = null; | |
− | + | var maxZoom = 18; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // Handle beacons | |
− | + | if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length < 25 ) | |
− | + | { | |
− | + | var bHoaB = false; | |
− | + | var beacons = this.data.sensors.beacon.map( function( _apiBeacon ) | |
− | + | { | |
− | + | var beacon = {}; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | beacon.point = L.latLng( _apiBeacon.location.lat,_apiBeacon.location.lon ); | |
− | + | beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ _apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map ); | |
− | + | beacon.circle = L.circle( beacon.point, _apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map ); | |
− | + | var delta = (Date.now() - new Date( _apiBeacon.ext_lastchange * 1000 )) / 1000; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // Closure variable | |
+ | if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" || _apiBeacon.name === "HoT" ) ) | ||
+ | bHoaB = true; | ||
− | + | if ( this._leaflet.icons[ _apiBeacon.name ] ) | |
− | + | this._leaflet.icons[ _apiBeacon.name ].options.className = delta > 60 ? "disconnected" : ""; | |
− | |||
− | + | var popup = beacon.marker.getPopup(); | |
− | + | if ( !popup ) | |
− | + | popup = beacon.marker.bindPopup().getPopup(); | |
− | + | popup.setContent( ( this._leaflet.descriptions[ _apiBeacon.name ] || _apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" ); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | return beacon; | |
− | + | }, this ); | |
− | |||
− | |||
− | |||
− | + | // TODO: clean up old beacons!! | |
− | + | this._leaflet.beacons.forEach( function( _beacon ) | |
− | + | { | |
− | + | // Destroy popup | |
− | + | _beacon.marker.unbindPopup( ); | |
− | + | // Remove marker and circle | |
− | + | this._leaflet.map.removeLayer( _beacon.marker ); | |
− | + | this._leaflet.map.removeLayer( _beacon.circle ); | |
− | + | }, this ); | |
− | + | /* | |
− | + | for ( var b = 0; b < Math.min( this._leaflet.beacons.length, beacons, length ); b++ ) | |
− | + | { | |
− | + | // Update position, icon, radius, tooltip | |
− | + | this._leaflet.beacons[ b ] | |
+ | } | ||
+ | */ | ||
+ | this._leaflet.beacons = beacons; | ||
− | + | // Only follow beacons automatically initially if there is a HoaB among it | |
− | + | if ( bHoaB && this._leaflet.follow === null ) | |
− | + | { | |
− | + | this._leaflet.follow = true; | |
− | + | document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle"; | |
− | + | } | |
+ | } | ||
+ | else if (this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length) | ||
+ | { | ||
+ | var apiBeacon = this.data.sensors.beacon[ 0 ]; | ||
+ | var beacon = {}; | ||
+ | var bHoaB = false; | ||
− | + | beacon.point = L.latLng( apiBeacon.location.lat,apiBeacon.location.lon ); | |
− | + | beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map ); | |
− | + | beacon.circle = L.circle( beacon.point, apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map ); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | var delta = (Date.now() - new Date( apiBeacon.ext_lastchange * 1000 )) / 1000; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // Closure variable | |
− | + | if ( ( delta < 3600 ) && ( apiBeacon.name === "HoaB" || apiBeacon.name === "HoT" ) ) | |
− | + | bHoaB = true; | |
− | |||
− | + | if ( this._leaflet.icons[ apiBeacon.name ] ) | |
− | + | this._leaflet.icons[ apiBeacon.name ].options.className = delta > 60 ? "disconnected" : ""; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | var popup = beacon.marker.getPopup(); | |
− | + | if ( !popup ) | |
− | + | popup = beacon.marker.bindPopup().getPopup(); | |
− | + | popup.setContent( ( this._leaflet.descriptions[ apiBeacon.name ] || apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" ); | |
− | + | // TODO: clean up old beacons!! | |
− | + | this._leaflet.beacons.forEach( function( _beacon ) | |
− | + | { | |
+ | // Destroy popup | ||
+ | _beacon.marker.unbindPopup( ); | ||
− | + | // Remove marker and circle | |
− | + | this._leaflet.map.removeLayer( _beacon.marker ); | |
+ | this._leaflet.map.removeLayer( _beacon.circle ); | ||
+ | }, this ); | ||
+ | this._leaflet.beacons = [ beacon ]; | ||
− | + | if ( bHoaB && this._leaflet.follow === null ) | |
− | + | { | |
− | + | this._leaflet.follow = true; | |
+ | document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle"; | ||
+ | } | ||
− | + | this._drawBeaconPolyLine( ); | |
− | + | maxZoom = 10; | |
− | + | } | |
− | + | // TODO: Update if coordinate is incorrect | |
− | + | if ( this._leaflet.follow === null ) | |
− | + | { | |
− | + | this._leaflet.follow = false; | |
− | |||
− | + | // Update location | |
− | + | this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon ); | |
− | + | this._leaflet.marker.setLatLng( this._leaflet.point ); | |
− | + | this._leaflet.map.setZoom( 18 ); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | // Set popup data and open it | ||
+ | var popup = this._leaflet.marker.getPopup(); | ||
+ | if ( !popup ) | ||
+ | popup = this._leaflet.marker.bindPopup().getPopup(); | ||
− | + | var info = '<img src="' + this.data.logo + '" style="max-width:'+this._width+'px;width:100%"><br/>'; | |
− | + | var l = this.data.location, s = this.data.spacefed; | |
− | + | info += l.address+"<br/>"; | |
− | |||
− | + | if ( l.ext_floor ) | |
− | + | info += "floor " + l.ext_floor; | |
− | + | if ( l.ext_room ) | |
+ | info += ", room " + l.ext_room; | ||
+ | info += "<br/>☎ " + '<a target="blank" href="tel:+'+this.data.contact.phone+'">+'+this.data.contact.phone+'</a>'; | ||
+ | info += "<br/>" + this._tristate( s.spacenet ) + ' <a target="blank" href="Spacenet">spacenet</a>'; | ||
+ | info += "<br/>" + this._tristate( s.ext_spacenet5g ) + " spacenet (5GHz)"; | ||
+ | info += "<br/>" + this._tristate( s.spacesaml ) + " spacesaml"; | ||
+ | info += "<br/>" + this._tristate( s.ext_spaceconnect ) + " spaceconnect"; | ||
+ | info += "<br/>" + this._tristate( s.spacephone ) + ' <a target="blank" href="Spacephone">spacephone</a>'; | ||
+ | if ( s.ext_spacephone_extension ) | ||
+ | info += ": E" + s.ext_spacephone_extension; | ||
− | + | popup.setContent( info ); | |
− | |||
− | |||
− | |||
− | + | this._leaflet.marker.openPopup(); | |
− | + | //popup.update(); | |
− | + | } | |
− | + | if ( this._leaflet.follow ) | |
− | + | { | |
− | + | // Determine the bounding box to 'follow | |
− | + | bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon ) | |
− | + | { | |
− | + | return _beacon.point; | |
− | + | } ) ); | |
− | |||
− | |||
− | |||
− | |||
− | + | // If we don't have Hackers on a Bike, include the home location together with the other beacons | |
+ | if ( !bHoaB ) | ||
+ | bounds.extend( this._leaflet.point ); | ||
− | + | // Disable zoom (in) if already zoomed beyond set max | |
− | + | if ( this._leaflet.map.getZoom() > maxZoom ) | |
− | + | { | |
+ | this._leaflet.map.panTo( bounds.getCenter() ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | this._leaflet.map.fitBounds( bounds ); | ||
+ | if ( this._leaflet.map.getZoom() > maxZoom ) | ||
+ | this._leaflet.map.setZoom( maxZoom ); | ||
+ | } | ||
+ | } | ||
+ | } // leaflet | ||
− | + | if ( this._features.split(",").indexOf( "annex" ) >= 0 ) | |
− | + | { | |
− | + | var node = this._node; | |
− | + | [].forEach.call(document.querySelectorAll(".state.annex"),function(_n) | |
− | + | { | |
− | + | node.removeChild( _n ); | |
− | + | }); | |
− | + | (this.data.sensors&&this.data.sensors.service||[]).filter(function(_s) | |
− | + | { | |
− | + | return (_s.source==="annex" && _s.name!=="annex"); | |
+ | }).forEach(function(_d,_i) | ||
+ | { | ||
+ | var anode = node.appendChild( document.createElement( "div" ) ); | ||
+ | anode.style.position = "absolute"; | ||
+ | anode.style.width = "0.4em"; | ||
+ | anode.style.height = "0.4em"; | ||
+ | anode.style.top = "calc(2em + 10px)"; | ||
+ | anode.style.right = (0.5 * _i) + "em"; | ||
+ | anode.style.backgroundColor = (_d.status|0)?"green":"red"; | ||
+ | anode.style.zIndex = 500; | ||
+ | anode.className = "state annex"; | ||
+ | var t = new Date( 0 ); | ||
+ | t.setUTCSeconds( _d.ext_lastchange ); | ||
+ | anode.title = _d.name + " (updated " + t.toLocaleString( )+")"; | ||
+ | }); | ||
+ | } | ||
+ | }; | ||
− | + | SpaceAPI.prototype._tristate = function( _state ) | |
− | + | { | |
− | + | if ( _state ) | |
− | + | return "✅"; | |
+ | else if ( _state === null ) | ||
+ | return "❓"; | ||
+ | else | ||
+ | return "❌"; | ||
+ | } | ||
− | + | SpaceAPI.prototype._xhr_onerror = function( ) | |
− | + | { | |
− | + | // Something has failed, show the error | |
− | + | this._updateState( this._msgError, this._colorUnknown ); | |
− | + | } | |
− | + | } | |
− | + | var state; | |
− | + | //state = new SpaceAPI( "auto", "auto", "none", "8px-->", "//ackspace.nl/spaceAPI/", 15, "beacon" ); | |
− | + | state = new SpaceAPI( "<!--{$width|escape:html|default:auto}-->", "<!--{$height|escape:html|default:auto}-->", "<!--{$float|escape:html|default:none}-->", "<!--{$padding|escape:html|default:8px}-->", "<!--{$url}-->", <!--{$interval|validate:int|default:0}-->, "<!--{$features|escape:'quotes'}-->" ); | |
− | + | state.start(); | |
}( )); | }( )); | ||
</script> | </script> | ||
</includeonly> | </includeonly> |
Latest revision as of 16:51, 24 April 2023
This widget allows you to display the Space API data (provided as JSON)
Created by Xopr
Using this widget
To insert this widget, use the following code:
{{#widget:SpaceAPI |url=/spaceAPI/ |width=260px |height=300px |padding=8px |interval=20 |float=right |features=beacon,annex }}
This will give the following result:
Notes
- url is mandatory, the rest is optional (leave out interval to make the data static).
- it also must be written without protocol since colon (:) is not allowed, and may be relative, for example: //ackspace.nl/spaceAPI/ or /spaceAPI/
- You must provide a unit for the sizes (i.e. px, %, etc.)
Copy to your site
To use this widget on your site, just install MediaWiki Widgets extension and copy full source code of this page to your wiki as Widget:SpaceAPI article.