initial commit
This commit is contained in:
commit
ec2517a12a
108
Leaflet.MakiMarkers.js
Normal file
108
Leaflet.MakiMarkers.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Leaflet plugin to create map icons using Maki Icons from MapBox.
|
||||||
|
*
|
||||||
|
* References:
|
||||||
|
* Maki Icons: https://www.mapbox.com/maki/
|
||||||
|
* MapBox Marker API: https://www.mapbox.com/developers/api/static/#markers
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* var icon = L.MakiMarkers.icon({icon: "rocket", color: "#b0b", size: "m"});
|
||||||
|
*
|
||||||
|
* License:
|
||||||
|
* MIT: http://jseppi.mit-license.org/
|
||||||
|
*/
|
||||||
|
/*global L:false */
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
L.MakiMarkers = {
|
||||||
|
// Available Maki Icons
|
||||||
|
icons: ["airfield","airport","alcohol-shop","america-football","art-gallery","bakery","bank","bar",
|
||||||
|
"baseball","basketball","beer","bicycle","building","bus","cafe","camera","campsite","car",
|
||||||
|
"cemetery","chemist","cinema","circle-stroked","circle","city","clothing-store","college",
|
||||||
|
"commercial","cricket","cross","dam","danger","disability","dog-park","embassy",
|
||||||
|
"emergency-telephone","entrance","farm","fast-food","ferry","fire-station","fuel","garden",
|
||||||
|
"golf","grocery","hairdresser","harbor","heart","heliport","hospital","industrial",
|
||||||
|
"land-use","laundry","library","lighthouse","lodging","logging","london-underground",
|
||||||
|
"marker-stroked","marker","minefield","mobilephone","monument","museum","music","oil-well",
|
||||||
|
"park2","park","parking-garage","parking","pharmacy","pitch","place-of-worship",
|
||||||
|
"playground","police","polling-place","post","prison","rail-above","rail-light",
|
||||||
|
"rail-metro","rail-underground","rail","religious-christian","religious-jewish",
|
||||||
|
"religious-muslim","restaurant","roadblock","rocket","school","scooter","shop","skiing",
|
||||||
|
"slaughterhouse","soccer","square-stroked","square","star-stroked","star","suitcase",
|
||||||
|
"swimming","telephone","tennis","theatre","toilets","town-hall","town","triangle-stroked",
|
||||||
|
"triangle","village","warehouse","waste-basket","water","wetland","zoo"
|
||||||
|
],
|
||||||
|
defaultColor: "#0a0",
|
||||||
|
defaultIcon: "circle-stroked",
|
||||||
|
defaultSize: "m",
|
||||||
|
apiUrl: "https://api.tiles.mapbox.com/v3/marker/",
|
||||||
|
smallOptions: {
|
||||||
|
iconSize: [20, 50],
|
||||||
|
popupAnchor: [0,-20]
|
||||||
|
},
|
||||||
|
mediumOptions: {
|
||||||
|
iconSize: [30,70],
|
||||||
|
popupAnchor: [0,-30]
|
||||||
|
},
|
||||||
|
largeOptions: {
|
||||||
|
iconSize: [36,90],
|
||||||
|
popupAnchor: [0,-40]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
L.MakiMarkers.Icon = L.Icon.extend({
|
||||||
|
options: {
|
||||||
|
//Maki icon: any from https://www.mapbox.com/maki/ (ref: L.MakiMarkers.icons)
|
||||||
|
icon: L.MakiMarkers.defaultIcon,
|
||||||
|
//Marker color: short or long form hex color code
|
||||||
|
color: L.MakiMarkers.defaultColor,
|
||||||
|
//Marker size: "s" (small), "m" (medium), or "l" (large)
|
||||||
|
size: L.MakiMarkers.defaultSize,
|
||||||
|
shadowAnchor: null,
|
||||||
|
shadowSize: null,
|
||||||
|
shadowUrl: null,
|
||||||
|
className: "maki-marker"
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
var pin;
|
||||||
|
|
||||||
|
options = L.setOptions(this, options);
|
||||||
|
|
||||||
|
switch (options.size) {
|
||||||
|
case "s":
|
||||||
|
L.extend(options, L.MakiMarkers.smallOptions);
|
||||||
|
break;
|
||||||
|
case "l":
|
||||||
|
L.extend(options, L.MakiMarkers.largeOptions);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
options.size = "m";
|
||||||
|
L.extend(options, L.MakiMarkers.mediumOptions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pin = "pin-" + options.size;
|
||||||
|
|
||||||
|
if (options.icon !== null) {
|
||||||
|
pin += "-" + options.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.color !== null) {
|
||||||
|
if (options.color.charAt(0) === "#") {
|
||||||
|
options.color = options.color.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pin += "+" + options.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.iconUrl = "" + L.MakiMarkers.apiUrl + pin + ".png";
|
||||||
|
options.iconRetinaUrl = L.MakiMarkers.apiUrl + pin + "@2x.png";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.MakiMarkers.icon = function(options) {
|
||||||
|
return new L.MakiMarkers.Icon(options);
|
||||||
|
};
|
||||||
|
})();
|
106
history.html
Normal file
106
history.html
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||||
|
<title>Neue und verschwundene Knoten</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: 'Roboto Slab', serif;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hostname {
|
||||||
|
}
|
||||||
|
|
||||||
|
.hostname.online {
|
||||||
|
color: #0A905D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hostname.offline {
|
||||||
|
color: #E42426;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists {
|
||||||
|
max-width: 50em;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0 1em 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists, #map {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists td:nth-child(2) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 80em) {
|
||||||
|
#lists {
|
||||||
|
max-width: 50vw;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 60em) {
|
||||||
|
#container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lists {
|
||||||
|
max-width: none;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Roboto+Slab' rel='stylesheet' type='text/css'>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js"></script>
|
||||||
|
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
|
||||||
|
<script src="Leaflet.MakiMarkers.js"></script>
|
||||||
|
<script src="history.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<div id="map">
|
||||||
|
</div>
|
||||||
|
<div id="lists">
|
||||||
|
<p>
|
||||||
|
Zeigt Knoten an, die in den letzten 14 Tagen dazu gekommen oder verschwunden sind.
|
||||||
|
Funktioniert nur in wirklich modernen Browsern.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Neue Knoten</h2>
|
||||||
|
<table>
|
||||||
|
<tbody id="newnodes">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Verschwundene Knoten</h2>
|
||||||
|
<table>
|
||||||
|
<tbody id="lostnodes">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
163
history.js
Normal file
163
history.js
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', main)
|
||||||
|
|
||||||
|
function get(url) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open('GET', url);
|
||||||
|
|
||||||
|
req.onload = function() {
|
||||||
|
if (req.status == 200) {
|
||||||
|
resolve(req.response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(Error(req.statusText));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.onerror = function() {
|
||||||
|
reject(Error("Network Error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
req.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJSON(url) {
|
||||||
|
return get(url).then(JSON.parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
getJSON('nodes.json').then(handle_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort(key, d) {
|
||||||
|
return d.slice().sort( function (a, b) {
|
||||||
|
return a[key] - b[key]
|
||||||
|
}).reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
function limit(key, m, d) {
|
||||||
|
return d.filter( function (d) {
|
||||||
|
return d[key].isAfter(m)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function offline(d) {
|
||||||
|
return !d.flags.online
|
||||||
|
}
|
||||||
|
|
||||||
|
function online(d) {
|
||||||
|
return d.flags.online
|
||||||
|
}
|
||||||
|
|
||||||
|
function has_location(d) {
|
||||||
|
return "location" in d.nodeinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
function subtract(a, b) {
|
||||||
|
var ids = {}
|
||||||
|
|
||||||
|
b.forEach( function (d) {
|
||||||
|
ids[d.nodeinfo.node_id] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
return a.filter( function (d) {
|
||||||
|
return !(d.nodeinfo.node_id in ids)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_data(data) {
|
||||||
|
var nodes = Object.keys(data.nodes).map(function (key) { return data.nodes[key] })
|
||||||
|
|
||||||
|
nodes = nodes.filter( function (d) {
|
||||||
|
return "firstseen" in d && "lastseen" in d
|
||||||
|
})
|
||||||
|
|
||||||
|
nodes.forEach( function(node) {
|
||||||
|
node.firstseen = moment(node.firstseen)
|
||||||
|
node.lastseen = moment(node.lastseen)
|
||||||
|
})
|
||||||
|
|
||||||
|
var age = moment().subtract(14, 'days')
|
||||||
|
|
||||||
|
var newnodes = limit("firstseen", age, sort("firstseen", nodes).filter(online))
|
||||||
|
var lostnodes = limit("lastseen", age, sort("lastseen", nodes).filter(offline))
|
||||||
|
|
||||||
|
var onlinenodes = subtract(nodes.filter(online).filter(has_location), newnodes)
|
||||||
|
|
||||||
|
addToList(document.getElementById("newnodes"), "firstseen", newnodes)
|
||||||
|
addToList(document.getElementById("lostnodes"), "lastseen", lostnodes)
|
||||||
|
|
||||||
|
mkmap(document.getElementById("map"), newnodes, lostnodes, onlinenodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkmap(el, newnodes, lostnodes, onlinenodes) {
|
||||||
|
var map = L.map(el)
|
||||||
|
|
||||||
|
L.tileLayer("http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg", {
|
||||||
|
subdomains: "1234",
|
||||||
|
type: "osm",
|
||||||
|
attribution: "Map data Tiles © <a href=\"http://www.mapquest.com/\" target=\"_blank\">MapQuest</a> <img src=\"http://developer.mapquest.com/content/osm/mq_logo.png\" />, Map data © OpenStreetMap contributors, CC-BY-SA",
|
||||||
|
maxZoom: 18
|
||||||
|
}).addTo(map)
|
||||||
|
|
||||||
|
var nodes = newnodes.concat(lostnodes).filter( function (d) {
|
||||||
|
return "location" in d.nodeinfo
|
||||||
|
})
|
||||||
|
|
||||||
|
var markers = nodes.map( function (d) {
|
||||||
|
var icon = L.MakiMarkers.icon({ color: d.flags.online ? "#0A905D" : "#E42426" })
|
||||||
|
|
||||||
|
var opt = { icon: icon }
|
||||||
|
|
||||||
|
var m = L.marker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], opt)
|
||||||
|
|
||||||
|
m.bindPopup(d.nodeinfo.hostname)
|
||||||
|
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
|
var onlinemarkers = onlinenodes.map( function (d) {
|
||||||
|
var opt = { color: "#0A905D",
|
||||||
|
fillColor: "#0A905D",
|
||||||
|
radius: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], opt)
|
||||||
|
|
||||||
|
m.bindPopup(d.nodeinfo.hostname)
|
||||||
|
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
|
var group = L.featureGroup(markers).addTo(map)
|
||||||
|
var group_online = L.featureGroup(onlinemarkers).addTo(map)
|
||||||
|
|
||||||
|
map.fitBounds(group.getBounds())
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToList(el, tf, list) {
|
||||||
|
list.forEach( function (d) {
|
||||||
|
var time = moment(d[tf]).fromNow()
|
||||||
|
|
||||||
|
var row = document.createElement("tr")
|
||||||
|
var td1 = document.createElement("td")
|
||||||
|
var span = document.createElement("span")
|
||||||
|
span.classList.add("hostname")
|
||||||
|
span.classList.add(d.flags.online ? "online" : "offline")
|
||||||
|
span.textContent = d.nodeinfo.hostname
|
||||||
|
td1.appendChild(span)
|
||||||
|
|
||||||
|
if ("owner" in d.nodeinfo) {
|
||||||
|
var contact = d.nodeinfo.owner.contact
|
||||||
|
td1.appendChild(document.createTextNode(" – " + contact + ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
var td2 = document.createElement("td")
|
||||||
|
td2.textContent = time
|
||||||
|
|
||||||
|
row.appendChild(td1)
|
||||||
|
row.appendChild(td2)
|
||||||
|
el.appendChild(row)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue