Difference between revisions of "Widget:SpaceAPI"

From Hackerspace ACKspace
Jump to: navigation, search
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=20px
+
|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=20px
+
|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";
+
"use strict";
  
    if ( typeof SpaceAPI === "undefined" )
+
/*
    {
+
TODO:
        window.SpaceAPI = function( _width, _height, _float, _padding, _url, _interval, _features )
+
separate rooms (get latlng)
        {
+
zoom onto toolbox
            this._width = _width;
+
table island not correct (4, not 5 tables)
            this._height = _height;
+
            this._padding = _padding;
+
*/
            this._url = _url;
 
            this._interval = 1000 * _interval;
 
            this._float = _float;
 
            this._features = _features;
 
        }
 
  
        SpaceAPI.prototype.data            = null;
+
if ( typeof SpaceAPI === "undefined" )
        SpaceAPI.prototype._width          = null;
+
{
        SpaceAPI.prototype._height        = null;
+
window.SpaceAPI = function( _width, _height, _float, _padding, _url, _interval, _features )
        SpaceAPI.prototype._float          = null;
+
{
        SpaceAPI.prototype._url            = null;
+
this._width = _width;
        SpaceAPI.prototype._features      = null;
+
this._height = _height;
        SpaceAPI.prototype._interval       = null;
+
this._padding = _padding;
        SpaceAPI.prototype._intervalId    = null;
+
this._url = _url;
        SpaceAPI.prototype._node          = null;
+
this._interval = 1000 * _interval;
        SpaceAPI.prototype._leaflet        = null;
+
this._float = _float;
        SpaceAPI.prototype._msgLoading    = "Loading..";
+
this._features = _features;
        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.start = function( )
+
SpaceAPI.prototype.data = null;
        {
+
SpaceAPI.prototype._width   = null;
            // Use interval timer id as image id
+
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;
+
this._intervalId = 0;
            if ( this._interval > 0 )
+
if ( this._interval > 0 )
                this._intervalId = setInterval( this._fetchState.bind( this ), this._interval );
+
this._intervalId = setInterval( this._fetchState.bind( this ), this._interval );
  
            document.write( '<div id="spaceAPI' + this._intervalId + '"></div>' );
+
document.write( '<div id="spaceAPI' + this._intervalId + '"></div>' );
  
            var node = document.getElementById( "spaceAPI" + this._intervalId );
+
var node = document.getElementById( "spaceAPI" + this._intervalId );
            if ( !node )
+
if ( !node )
            {
+
{
                console && console.log( "node not found" );
+
console && console.log( "node not found" );
                return;
+
return;
            }
+
}
            node.style.width = this._width;
+
node.style.width = this._width;
            node.style.textAlign = "center";
+
node.style.height = this._height;
            node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)";
 
            node.style.position = "relative";
 
  
            if ( this._float )
+
node.style.textAlign = "center";
                node.style.float = this._float;
+
node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)";
 +
node.style.position = "relative";
  
            this._node = node.appendChild( document.createElement( "div" ) );
+
if ( this._float )
            this._node.style.height = this._height + "px";
+
node.style.float = this._float;
            this._node.style.padding = this._padding;
 
            this._node.textContent = this._msgLoading;
 
  
            if ( this._features.split(",").indexOf( "beacon" ) >= 0 )
+
this._node = node.appendChild( document.createElement( "a" ) );
            {
+
this._node.style.display = "block";
                var srcNode;
+
this._node.style.color = "inherit";
                var mapNode = node.appendChild( document.createElement( "div" ) );
+
this._node.style.padding = this._padding;
                mapNode.style.width = "100%";
+
this._node.textContent = this._msgLoading;
                mapNode.style.height = "276px";
+
this._node.href = "/spaceAPI/statechanges.html";
               
 
                srcNode = document.createElement( "link" );
 
                srcNode.href = "/leaflet/leaflet.css";
 
                srcNode.rel = "stylesheet";
 
                srcNode.type = "text/css";
 
  
                // Load the css
+
if ( this._features.split(",").indexOf( "beacon" ) >= 0 )
                ( document.head || document.documentElement ).appendChild( srcNode );
+
{
               
+
var srcNode;
                srcNode = document.createElement( "script" );
+
var mapNode = node.appendChild( document.createElement( "div" ) );
                srcNode.src = "/leaflet/leaflet.js";
+
mapNode.style.width = "100%";
                srcNode.type = "text/javascript";
+
mapNode.style.height = "calc(" + this._height + " + " + this._padding + " - 2em)";
                srcNode.addEventListener( "load", function( _evt )
+
                {
+
srcNode = document.createElement( "link" );
                    this._leaflet = {};
+
srcNode.href = "/leaflet/leaflet.css";
                    this._leaflet.point = L.latLng( 50.8925,5.9713 );
+
srcNode.rel = "stylesheet";
                    this._leaflet.map = L.map( mapNode ).setView( this._leaflet.point, 16);
+
srcNode.type = "text/css";
  
                    L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857,
+
// Load the css
                    {
+
( document.head || document.documentElement ).appendChild( srcNode );
                        scale: function( zoom ) {
+
                            //console.log( zoom );
+
srcNode = document.createElement( "script" );
                            if ( zoom < 24 )
+
srcNode.src = "/leaflet/leaflet.js";
                                return 256 * Math.pow( 2, zoom );
+
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);
  
                            // Freeze the actual zooming above this level to show the different floors
+
L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857,
                            // 256 * pow( 2, 23 )
+
{
                            return 2147483648;
+
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', {
+
L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                        attribution: '&copy; <a href="//openstreetmap.org/copyright">OpenStreetMap</a> contributors',
+
attribution: '&copy; <a href="//openstreetmap.org/copyright">OpenStreetMap</a> contributors',
                        minZoom: 2,
+
minZoom: 2,
                        maxZoom: 28,
+
maxZoom: 28,
                        maxNativeZoom: 19
+
maxNativeZoom: 19
                    }).addTo( this._leaflet.map );
+
}).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',
+
attribution: 'ACKspace',
                        minZoom: 24,
+
minZoom: 24,
                        maxZoom: 28,
+
maxZoom: 28,
                        maxNativeZoom: 23
+
maxNativeZoom: 23
                    }).addTo( this._leaflet.map );
+
}).addTo( this._leaflet.map );
  
 
// special zoom
 
// special zoom
                    var funcLayer = new L.TileLayer.Functional( function( view )
+
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;
+
return url;
                    },
+
},
                    {
+
{
                        attribution: 'ACKspace',
+
attribution: 'ACKspace',
                        minZoom: 21,
+
minZoom: 16,
                        maxZoom: 28
+
maxZoom: 28
                    } ).addTo( this._leaflet.map );
+
} ).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
var o = 1, z = _event.target.getZoom();
 
 
if ( z > 17 )
 
if ( z > 17 )
 
o = Math.max( (23-z) / 5, 0 );
 
o = Math.max( (23-z) / 5, 0 );
this._leaflet.temperatures["28151767050000a0"].setStyle( { fillOpacity: o } );
+
this._leaflet.temperatures["000005667ABD"].setStyle( { fillOpacity: o } );
 
}, this );
 
}, this );
  
Line 294: Line 362:
 
}
 
}
  
                    // "Follow" control
+
// "Follow" control
                    this._leaflet.follow = null;
+
this._leaflet.follow = null;
                    L.Control.Command = L.Control.extend(
+
L.Control.Command = L.Control.extend(
                    {
+
{
                        options:
+
options:
                        {
+
{
                            position: 'topleft',
+
position: 'topleft',
                        },
+
},
               
+
                        onAdd: function( _map )
+
onAdd: function( _map )
                        {
+
{
                            var controlDiv = L.DomUtil.create( "div", "leaflet-bar" );
+
var controlDiv = L.DomUtil.create( "div", "leaflet-bar" );
                            var controlUI = L.DomUtil.create( "a", "leaflet-clickable" + (this._leaflet.follow ? " toggle" : ""), controlDiv );
+
var controlUI = L.DomUtil.create( "a", "leaflet-clickable" + (this._leaflet.follow ? " toggle" : ""), controlDiv );
                            controlUI.innerHTML = "&#8982;";
+
controlUI.innerHTML = "&#8982;";
                            controlUI.style.fontSize = "35px";
+
controlUI.style.fontSize = "35px";
                            controlUI.title = 'Follow beacon';
+
controlUI.title = 'Follow beacon';
               
+
                            L.DomEvent.addListener( controlUI, 'click', function( _evt )
+
L.DomEvent.addListener( controlUI, 'click', function( _evt )
                            {
+
{
                                this._leaflet.follow = !this._leaflet.follow;
+
this._leaflet.follow = !this._leaflet.follow;
                                _evt.currentTarget.className = "leaflet-clickable" + (this._leaflet.follow ? " toggle" : "");
+
_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 map immediately
+
// Update the space state immediately
                                if ( this._leaflet.follow )
+
setTimeout( this._fetchState.bind( this ), 1 );
                                {
+
};
                                    // Determine the bounding box to 'follow
+
SpaceAPI.prototype._determineColor = function( _temperature )
                                    var bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )
+
{
                                    {
+
var tempteratureColors = [
                                        return _beacon.point;
+
[ -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;
  
                                    if ( !this._leaflet.beacons.length )
+
for ( var nTemp = tempteratureColors.length - 1; nTemp; nTemp-- )
                                        bounds.extend( this._leaflet.point );
+
{
 +
if ( _temperature >= tempteratureColors[ nTemp ][0] )
 +
break;
  
                                    this._leaflet.map.fitBounds( bounds );
+
ratio = 0;
                                }
+
if ( index = nTemp )
                            }.bind( this ) );
+
ratio = (_temperature - tempteratureColors[ nTemp - 1 ][0]) / ( tempteratureColors[ nTemp ][0] - tempteratureColors[ nTemp - 1 ][0] );
               
+
}
                            return controlDiv;
 
                        }.bind( this )
 
                    } );
 
  
                    this._leaflet.map.addControl( new L.Control.Command() );
+
var lo = tempteratureColors[ index ? index - 1 : 0 ];
 +
var hi = tempteratureColors[ index ];
  
                    // Icons
+
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))+")";
                    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]
 
                        } )
 
                    };
 
                    this._leaflet.descriptions = {
 
                        "HoaB" : "Hackers on a Bike"
 
                    };
 
  
                    this._leaflet.marker = L.marker( this._leaflet.point ).addTo( this._leaflet.map );
+
SpaceAPI.prototype._nlsTime = function( _time )
                    this._leaflet.beacons = [];
+
{
                    this._leaflet.temperatures = {
+
var postfix;
                        // outside
+
if ( _time < 2 )
                        "28bd7a660500002f" : 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
+
return "moments";
                        "28151767050000a0" : L.polygon( [[50.892458293019,5.971340775608], [50.892410920402,5.9713032246818], [50.892461676775,5.9711557031861], [50.892507357464,5.9711959363214]], {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 )
 
//{lat: 50.89277067395246, lng: 5.971342250704766}
 
                        // hot zone
 
                        /*"288a13670500002a" : null*/
 
                    };
 
                }.bind( this ) );
 
  
                // Load the script
+
if ( _time > 31556952 )
                ( document.head || document.documentElement ).appendChild( srcNode );
+
{
            }
+
_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";
 +
}
  
            // Update the space state immediately
+
_time = Math.round( _time );
            setTimeout( this._fetchState.bind( this ), 1 );
+
return _time + " " + postfix + (_time !== 1 ? "s" : "");
        };
+
};
        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-- )
+
SpaceAPI.prototype._fetchState = function( )
            {
+
{
                if ( _temperature >= tempteratureColors[ nTemp ][0] )
+
this._node.className = "processing";
                    break;
+
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 );
 +
}
  
                ratio = 0;
+
xhr.open( "GET", this._url, true );
                if ( index = nTemp )
 
                    ratio = (_temperature - tempteratureColors[ nTemp - 1 ][0]) / ( tempteratureColors[ nTemp ][0] - tempteratureColors[ nTemp - 1 ][0] );
 
            }
 
  
            var lo = tempteratureColors[ index ? index - 1 : 0 ];
+
// Tells server that this call is made for ajax purposes.
            var hi = tempteratureColors[ index ];
+
// 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
  
            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))+")";
+
temp.bindPopup( "Description: " + _apiTemp.description + "<br/>Location: " + _apiTemp.location + "<br/>Value: " + _apiTemp.value + _apiTemp.unit + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );
        };
+
}
 +
}, this );
 +
}
  
        SpaceAPI.prototype._nlsTime = function( _time )
+
var bounds = null;
        {
+
var maxZoom = 18;
            var postfix;
 
            if ( _time < 2 )
 
            {
 
                return "moments";
 
            }
 
  
            if ( _time > 31556952 )
+
// Handle beacons
            {
+
if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length < 25 )
                _time /= 31556952;
+
{
                postfix = "Year";
+
var bHoaB = false;
            }
+
var beacons = this.data.sensors.beacon.map( function( _apiBeacon )
            else if ( _time > 2629746 )
+
{
            {
+
var beacon = {};
                _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 );
+
beacon.point  = L.latLng( _apiBeacon.location.lat,_apiBeacon.location.lon );
            return _time + " " + postfix + (_time !== 1 ? "s" : "");
+
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 );
  
        SpaceAPI.prototype._fetchState = function( )
+
var delta = (Date.now() - new Date( _apiBeacon.ext_lastchange * 1000 )) / 1000;
        {
 
            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 );
+
// Closure variable
 +
if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" || _apiBeacon.name === "HoT" ) )
 +
bHoaB = true;
  
            // Tells server that this call is made for ajax purposes.
+
if ( this._leaflet.icons[ _apiBeacon.name ] )
            // Most libraries like jQuery/Prototype/Dojo do this
+
this._leaflet.icons[ _apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";
            xhr.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
 
  
            // No data needs to be sent along with the request.
+
var popup = beacon.marker.getPopup();
            xhr.send( null );
+
if ( !popup )
        };
+
popup = beacon.marker.bindPopup().getPopup();
  
        SpaceAPI.prototype._updateState = function( _message, _color, _title )
+
popup.setContent( ( this._leaflet.descriptions[ _apiBeacon.name ] || _apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );
        {
 
            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 )
+
return beacon;
        {
+
}, this );
            var open = null;
 
            var message = null;
 
            var title = null;
 
  
            try
+
// TODO: clean up old beacons!!
            {
+
this._leaflet.beacons.forEach( function( _beacon )
                this.data = JSON.parse( _event.target.responseText );
+
{
                open = this.data.state.open;
+
// Destroy popup
                message = this.data.state.message;
+
_beacon.marker.unbindPopup( );
  
                // Start as epoch timestamp (NOTE: check if timezone doesn't mess things up)
+
// Remove marker and circle
                var d = new Date( 0 );
+
this._leaflet.map.removeLayer( _beacon.marker );
                d.setUTCSeconds( this.data.state.lastchange );
+
this._leaflet.map.removeLayer( _beacon.circle );
                title = this._msgSince + d.toLocaleString( );
+
}, this );
            }
+
/*
            catch( _e )
+
for ( var b = 0; b < Math.min( this._leaflet.beacons.length, beacons, length ); b++ )
            {
+
{
                message = this._msgParserError;
+
// Update position, icon, radius, tooltip
            }
+
this._leaflet.beacons[ b ]
 +
}
 +
*/
 +
this._leaflet.beacons = beacons;
  
            if ( open )
+
// Only follow beacons automatically initially if there is a HoaB among it
                this._updateState( message || this._msgOpen, this._colorOpen, title );
+
if ( bHoaB && this._leaflet.follow === null )
            else if ( open === false )
+
{
                this._updateState( message || this._msgClosed, this._colorClosed, title );
+
this._leaflet.follow = true;
            else
+
document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle";
                this._updateState( message || this._msgUnknown, this._colorUnknown, title );
+
}
 +
}
 +
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;
  
            if ( this._leaflet )
+
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 );
                // Handle temperatures
+
beacon.circle = L.circle( beacon.point, apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map );
                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;
+
var delta = (Date.now() - new Date( apiBeacon.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" );
+
// Closure variable
                        }
+
if ( ( delta < 3600 ) && ( apiBeacon.name === "HoaB" || apiBeacon.name === "HoT" ) )
                    }, this );
+
bHoaB = true;
                }
 
  
                // Handle beacons
+
if ( this._leaflet.icons[ apiBeacon.name ] )
                if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length )
+
this._leaflet.icons[ apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";
                {
 
                    var bHoaB = false;
 
                    var beacons = this.data.sensors.beacon.map( function( _apiBeacon )
 
                    {
 
                        var beacon = {};
 
  
                        beacon.point  = L.latLng( _apiBeacon.location.lat,_apiBeacon.location.lon );
+
var popup = beacon.marker.getPopup();
                        beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ _apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map );
+
if ( !popup )
                        beacon.circle = L.circle( beacon.point, _apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map );
+
popup = beacon.marker.bindPopup().getPopup();
  
                        var delta = (Date.now() - new Date( _apiBeacon.ext_lastchange * 1000 )) / 1000;
+
popup.setContent( ( this._leaflet.descriptions[ apiBeacon.name ] || apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );
  
                        // Closure variable
+
// TODO: clean up old beacons!!
                        if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" ) )
+
this._leaflet.beacons.forEach( function( _beacon )
                            bHoaB = true;
+
{
 +
// Destroy popup
 +
_beacon.marker.unbindPopup( );
  
                        if ( this._leaflet.icons[ _apiBeacon.name ] )
+
// Remove marker and circle
                            this._leaflet.icons[ _apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";
+
this._leaflet.map.removeLayer( _beacon.marker );
 +
this._leaflet.map.removeLayer( _beacon.circle );
 +
}, this );
 +
this._leaflet.beacons = [ beacon ];
  
                        var popup = beacon.marker.getPopup();
+
if ( bHoaB && this._leaflet.follow === null )
                        if ( !popup )
+
{
                            popup = beacon.marker.bindPopup().getPopup();
+
this._leaflet.follow = true;
 +
document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle";
 +
}
  
                        popup.setContent( ( this._leaflet.descriptions[ _apiBeacon.name ] || _apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );
+
this._drawBeaconPolyLine( );
  
                        return beacon;
+
maxZoom = 10;
                    }, this );
+
}
  
                    // TODO: clean up old beacons!!
+
// TODO: Update if coordinate is incorrect
                    this._leaflet.beacons.forEach( function( _beacon )
+
if ( this._leaflet.follow === null )
                    {
+
{
                        // Destroy popup
+
this._leaflet.follow = false;
                        _beacon.marker.unbindPopup( );
 
  
                        // Remove marker and circle
+
// Update location
                        this._leaflet.map.removeLayer( _beacon.marker );
+
this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon );
                        this._leaflet.map.removeLayer( _beacon.circle );
+
this._leaflet.marker.setLatLng( this._leaflet.point );
                    }, this );
+
this._leaflet.map.setZoom( 18 );
                    /*
 
                    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";
 
                    }
 
                }
 
  
 +
// Set popup data and open it
 +
var popup = this._leaflet.marker.getPopup();
 +
if ( !popup )
 +
popup = this._leaflet.marker.bindPopup().getPopup();
  
                // TODO: Update if coordinate is incorrect
+
var info = '<img src="' + this.data.logo + '" style="max-width:'+this._width+'px;width:100%"><br/>';
                if ( this._leaflet.follow === null )
+
var l = this.data.location, s = this.data.spacefed;
                {
+
info += l.address+"<br/>";
                    this._leaflet.follow = false;
 
  
                    // Update location
+
if ( l.ext_floor )
                    this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon );
+
info += "floor " + l.ext_floor;
                    this._leaflet.marker.setLatLng( this._leaflet.point );
+
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;
  
                    // Set popup data and open it
+
popup.setContent( info );
                    var popup = this._leaflet.marker.getPopup();
 
                    if ( !popup )
 
                        popup = this._leaflet.marker.bindPopup().getPopup();
 
  
                    var info = "<img src='" + this.data.logo + "'><br/>";
+
this._leaflet.marker.openPopup();
                    var l = this.data.location, s = this.data.spacefed;
+
//popup.update();
                    info += l.address;
+
}
  
                    if ( l.ext_floor )
+
if ( this._leaflet.follow )
                        info += ", floor " + l.ext_floor;
+
{
                    if ( l.ext_room )
+
// Determine the bounding box to 'follow
                        info += ", room " + l.ext_room;
+
bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )
                    info += "<br/>" + (s.spacenet ? "&#x2714;" : "&#x274C;") + ' <a target="blank" href="Spacenet">spacenet</a>';
+
{
                    info += "<br/>" + (s.ext_spacenet5g ? "&#x2714;" : "&#x274C;") + " spacenet (5GHz)";
+
return _beacon.point;
                    info += "<br/>" + (s.spacesaml ? "&#x2714;" : "&#x274C;") + " spacesaml";
+
} ) );
                    info += "<br/>" + (s.ext_spaceconnect ? "&#x2714;" : "&#x274C;") + " spaceconnect";
 
                    info += "<br/>" + (s.spacephone ? "&#x2714;" : "&#x274C;") + ' <a target="blank" href="Spacephone">spacephone</a>';
 
                    if ( s.ext_spacephone_extension )
 
                        info += ": +" + s.ext_spacephone_extension;
 
  
                    popup.setContent( info );
+
// 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 );
  
                    this._leaflet.marker.openPopup();
+
// Disable zoom (in) if already zoomed beyond set max
                    //popup.update();
+
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._leaflet.follow )
+
if ( this._features.split(",").indexOf( "annex" ) >= 0 )
                {
+
{
                    // Determine the bounding box to 'follow
+
var node = this._node;
                    var bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )
+
[].forEach.call(document.querySelectorAll(".state.annex"),function(_n)
                    {
+
{
                        return _beacon.point;
+
node.removeChild( _n );
                    } ) );
+
});
  
                    // If we don't have Hackers on a Bike, include the home location together with the other beacons
+
(this.data.sensors&&this.data.sensors.service||[]).filter(function(_s)
                    if ( !bHoaB )
+
{
                        bounds.extend( this._leaflet.point );
+
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( )+")";
 +
});
 +
}
 +
};
  
                    this._leaflet.map.fitBounds( bounds );
+
SpaceAPI.prototype._tristate = function( _state )
                }
+
{
            }
+
if ( _state )
        };
+
return "&#x2705;";
 +
else if ( _state === null )
 +
return "&#x2753;";
 +
else
 +
return "&#x274C;";
 +
}
  
        SpaceAPI.prototype._xhr_onerror = function( )
+
SpaceAPI.prototype._xhr_onerror = function( )
        {
+
{
            // Something has failed, show the error
+
// Something has failed, show the error
            this._updateState( this._msgError, this._colorUnknown );
+
this._updateState( this._msgError, this._colorUnknown );
        }
+
}
    }
+
}
    var state;
+
var state;
    //state = new SpaceAPI( "auto", "auto", "none", "8px-->", "//ackspace.nl/spaceAPI/", 15, "beacon" );
+
//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|escape:urlpathinfo}-->", <!--{$interval|validate:int|default:0}-->, "<!--{$features|escape:'quotes'}-->" );
+
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();
+
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.