diff --git a/index.html b/index.html index f8540a2..f931180 100644 --- a/index.html +++ b/index.html @@ -4,75 +4,23 @@ Speedtest.net map + - -

SpeedTest.net file mapper


- - + +

Status:

-
+
+
- + \ No newline at end of file diff --git a/main.css b/main.css new file mode 100644 index 0000000..82a4b80 --- /dev/null +++ b/main.css @@ -0,0 +1,56 @@ +#statusbox p { margin: 0; } +.error { + color: red; +} +.success { + color: darkgreen; +} +.map { + height: 800px; + width: 100%; +} + +/* popup related */ +.ol-popup { + position: absolute; + background-color: white; + box-shadow: 0 1px 4px rgba(0,0,0,0.2); + padding: 15px; + border-radius: 10px; + border: 1px solid #cccccc; + bottom: 12px; + left: -50px; + min-width: 280px; + white-space: nowrap; +} +.ol-popup:after, .ol-popup:before { + top: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} +.ol-popup:after { + border-top-color: white; + border-width: 10px; + left: 48px; + margin-left: -10px; +} +.ol-popup:before { + border-top-color: #cccccc; + border-width: 11px; + left: 48px; + margin-left: -11px; +} +.ol-popup-closer { + text-decoration: none; + position: absolute; + top: 2px; + right: 8px; +} + +button.off { + background-color: red; /* placeholder */ +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..c745f02 --- /dev/null +++ b/main.js @@ -0,0 +1,251 @@ +//class defs +class Entry { + constructor(data) { + this.date = data[0].replaceAll("/", ".").replace(",", ""); + this.conntype = data[1].toUpperCase(); + this.loc = [parseFloat(data[3]), parseFloat(data[2])]; // long, lat + this.speed = [parseFloat(data[4]), parseFloat(data[6])]; // dl, up + this.size = [parseInt(data[5]), parseInt(data[7])]; // dl, up + this.ping = parseInt(data[8]); + this.server = data[9]; + this.intIP = data[10]; + this.extIP = data[11]; + this.url = data[12]; + } + + toHTML() { + return this.conntype + " [" + this.date + "] " + "
\n" + this.speedStr + "
\nPing: " + this.ping + "ms
\nresult Page🔗
\n"; + } + + get speedStr() { + return this.speed[0].toFixed(2) + " Mbps ↓ / " + this.speed[1].toFixed(2) + " Mbps ↑"; + } +}; + +/** @type {HTMLDivElement} */ +var statusBox = document.getElementById("statusbox"); +var layerlist = document.getElementById("layerlist"); + +var popupcontainer = document.getElementById('popup'); +var popupcontent = document.getElementById('popup-content'); +var popupcloser = document.getElementById('popup-closer'); + +var zoomstart = 15; +var dotradius = 5; + +var isInit = false; +var entryList = []; +var layers = {}; //mapping of layers: name -> layerobj + +//init map +var overlay = new ol.Overlay({ + element: popupcontainer, + autoPan: true, + autoPanAnimation: { + duration: 250, + }, +}); + +popupcloser.onclick = function () { + popupcontent.innerHTML = ""; + + overlay.setPosition(undefined); + popupcloser.blur(); + return false; +}; + +var map = new ol.Map({ + target: 'map', + layers: [ + new ol.layer.Tile({ + source: new ol.source.OSM() + }) + ], + view: new ol.View({ + center: ol.proj.fromLonLat([0, 0]), + zoom: zoomstart + }), + overlays: [overlay] +}); + +var highlightStyle = new ol.style.Style({ + image: new ol.style.Circle({ + radius: dotradius, + fill: new ol.style.Fill({color: 'green'}) + }) + }); + +var defaultStyle = new ol.style.Style({ + image: new ol.style.Circle({ + radius: dotradius, + fill: new ol.style.Fill({color: 'red'}) + }) + }); + +//liste of features, that have the highlighted style +var highlighted = []; + +// event listener +map.on("pointermove", e => { + var newhighlighted = []; + + //gather overlay information + var fids = []; //list of currently hovered feature ids + map.forEachFeatureAtPixel(e.pixel, feature => { + var fid = feature.getProperties().id; + fids.push(fid); + feature.setStyle(highlightStyle); + newhighlighted.push(feature); + }) + + if(newhighlighted.length > 0) { + //remove highlight style from non hovered elements + highlighted.filter(h => newhighlighted.indexOf(h) == -1).forEach(h => h.setStyle(defaultStyle)); + highlighted = newhighlighted; + } + + // display information + if(fids.length > 0) { + popupcontent.innerHTML = ""; + overlay.setPosition(e.coordinate); + } + for(var i = 0; i < fids.length; ++i) { + var entry = entryList[fids[i]]; + + //console.log(entry); + + //print entry + popupcontent.innerHTML += entry.toHTML(); + if(i+1 < fids.length) + popupcontent.innerHTML += "
\n"; + } +}); + +function showError(error) { + var span = document.createElement('p'); + span.classList.add('error'); + span.innerHTML = `Error: ${error}`; + statusBox.appendChild(span); +} + +function showSuccess(success) { + var span = document.createElement('p'); + span.classList.add('success'); + span.innerHTML = `Success: ${success}`; + statusBox.appendChild(span); +} + +function clearStatus() { + statusBox.innerHTML = ""; +} + +function addElement(elem, features) { + var id = entryList.length; + entryList.push(elem); + var dot = ol.proj.fromLonLat(elem.loc); + if(!isInit) { + isInit = true; + map.setView(new ol.View({center: dot, zoom: zoomstart})); + console.log(dot); + } + + var feat = new ol.Feature({ + geometry: new ol.geom.Point(dot), + type: 'point', + id: id + }); + features.push(feat); +} + +function loadedFile(text, filename) { + var lines = text.split("\n"); + var featuresLTE = []; + var features = []; + for(var i = 1; i < lines.length; ++i) { + var line = lines[i]; + if(line.length < 10) continue; + + var date = line.substr(1, 17); + line = line.substr(20); + var lineSplit = line.split(","); + lineSplit = [date ,... lineSplit]; + + var elem = new Entry(lineSplit); + if(elem.conntype == "LTE") { + addElement(elem, featuresLTE); + } else { + addElement(elem, features); + } + } + + addLayer(featuresLTE, filename + " LTE"); + addLayer(features, filename + " nonLTE"); +} + +function addLayer(features, name) { + var sourceLayer = new ol.source.Vector({ + features + }); + + var vectorLayer = new ol.layer.Vector({ + source: sourceLayer, + style: defaultStyle + }); + + layers[name] = vectorLayer; + map.addLayer(vectorLayer); + + //add button to list + layerlist.innerHTML += `${name}:
\n` +} + +function loadFile(file) { + console.log("loadFile: " + file.name); + console.log(file); + + var text = file.text(); + text.then(t => loadedFile(t, file.name)); +} + +function triggerImport(event) { + clearStatus(); + isInit = false; + + //try to read the files + var filelist = document.getElementById("files").files; + + if(filelist.length == 0) { + showError("No file selected"); + return false; + } + + var successC = 0; + for(var i = 0; i < filelist.length; ++i) { + var f = filelist[i]; + try { + loadFile(f); + ++successC; + } catch(e) { + showError(`The file ${f.name} is not in the csv format`); + } + } + + if(successC > 0) + showSuccess("Loaded " + successC + " files"); + + return false; +} + +function triggerLayer(btnevent, layername) { + var btn = btnevent.target; + var btnname = btn.innerText; + var mode = btn.classList.contains("off"); // true = layer is off, false = layer is on + if(mode) btn.classList.remove("off"); + else btn.classList.add("off"); + + var layer = layers[layername]; + if(btnname == "30%") + layer.setOpacity(mode ? 1 : 0.3); + else + layer.setVisible(mode); +} \ No newline at end of file