1104 lines
32 KiB
HTML
1104 lines
32 KiB
HTML
|
|
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>RLX Power Outage Map</title>
|
|
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
|
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js" integrity="sha256-6XMVI0zB8cRzfZjqKcD01PBsAy3FlDASrlC8SxCpInY=" crossorigin="anonymous"></script>
|
|
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.1/themes/smoothness/jquery-ui.css">
|
|
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.4.2/chroma.min.js" integrity="sha512-zInFF17qBFVvvvFpIfeBzo7Tj7+rQxLeTJDmbxjBz5/zIr89YVbTNelNhdTT+/DCrxoVzBeUPVFJsczKbB7sew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
|
|
<script src="https://unpkg.com/file-saver@2.0.5/dist/FileSaver.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dom-to-image/2.6.0/dom-to-image.js" integrity="sha512-wUa0ktp10dgVVhWdRVfcUO4vHS0ryT42WOEcXjVVF2+2rcYBKTY7Yx7JCEzjWgPV+rj2EDUr8TwsoWF6IoIOPg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
|
|
<script type="text/javascript" src="/tabulator/dist/js/tabulator.min.js"></script>
|
|
<link href="/tabulator/dist/css/tabulator_midnight.css" rel="stylesheet">
|
|
|
|
|
|
|
|
|
|
</head>
|
|
<body>
|
|
|
|
<div class="box">
|
|
|
|
<div id="tabby"></div>
|
|
|
|
<div id="map">
|
|
|
|
</div>
|
|
<div id="toggle">
|
|
<button id="archive" onclick="togglea()">Toggle Archive Mode</button><br>
|
|
<div id='screenshots'>
|
|
Enter Start/End Times in UTC
|
|
<input type="datetime-local" id="start" name="start">
|
|
<input type="datetime-local" id="end" name="end">
|
|
<button id="playarchive" onclick="playarchive()">Load Archive Data</button><br>
|
|
Enable Screenshots: <input type="checkbox" id="llss" name="llss"><br>
|
|
Note: move the map slightly after loading data if you want to capture</div>
|
|
</div>
|
|
|
|
<div id = "slider-2"></div>
|
|
</div>
|
|
<div class="box2">
|
|
|
|
|
|
</div>
|
|
<style type="text/css">
|
|
|
|
|
|
|
|
.tabulator {
|
|
font-size: 26px; // change font size
|
|
padding:4px 4px;
|
|
}
|
|
|
|
|
|
//#mapid { height: 800px; }
|
|
body {
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
html, body {
|
|
height: 100%;
|
|
width: 100%
|
|
|
|
}
|
|
#mapid {
|
|
height: 95%;
|
|
background: none !important;
|
|
|
|
}
|
|
#bottombar {
|
|
// height: 98%;
|
|
}
|
|
|
|
.box{
|
|
position: absolute;
|
|
top: 225px;
|
|
z-index: 9999;
|
|
text-align: center;
|
|
width: 250px;
|
|
left: 10%;
|
|
margin-left: -75px; /* half of the width */
|
|
}
|
|
|
|
|
|
|
|
|
|
.box2{
|
|
position: absolute;
|
|
top: 500px;
|
|
z-index: 9999;
|
|
text-align: center;
|
|
width: 250px;
|
|
left: 10%;
|
|
margin-left: -75px; /* half of the width */
|
|
}
|
|
|
|
|
|
|
|
input[type=number] {
|
|
|
|
|
|
width: 50px;
|
|
}
|
|
|
|
input[type=text] {
|
|
|
|
|
|
width: 150px;
|
|
}
|
|
.legend {
|
|
line-height: 18px;
|
|
color: #555;
|
|
}
|
|
.legend i {
|
|
width: 15px;
|
|
height: 15px;
|
|
float: left;
|
|
margin-right: 8px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.info {
|
|
padding: 6px 8px;
|
|
font: 14px/16px Arial, Helvetica, sans-serif;
|
|
background: white;
|
|
background: rgba(255,255,255,0.8);
|
|
box-shadow: 0 0 15px rgba(0,0,0,0.2);
|
|
border-radius: 5px;
|
|
}
|
|
.info h4 {
|
|
margin: 0 0 5px;
|
|
color: #777;
|
|
}
|
|
|
|
#slider-2 {
|
|
position: absolute;
|
|
left: 20px;
|
|
top: 200px;
|
|
width:300px;
|
|
z-index: 500;
|
|
}
|
|
|
|
#timeforsaveimages {
|
|
position: absolute;
|
|
left: 20px;
|
|
bottom: 20px;
|
|
color: black;
|
|
font-size: 30px;
|
|
}
|
|
|
|
|
|
.slider-container {
|
|
position: absolute;
|
|
bottom: 15px;
|
|
left: 500px;
|
|
background: rgba(255,255,255,0.7);
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
background-color: white;
|
|
z-index: 9999;
|
|
}
|
|
|
|
#tabby {
|
|
left: 0px;
|
|
top: 0px;
|
|
position: fixed;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
|
|
<div id="mapid">
|
|
|
|
<div class="slider-container" id="slider-holder">
|
|
<label for="transparency">County Opacity:</label>
|
|
<input type="range" id="transparency" min="0" max="1" step="0.01" value="1">
|
|
<label for="floodToggle">Toggle Flood Layer</label>
|
|
<input type="checkbox" id="floodToggle" checked onchange="toggleFloodLayer()">
|
|
|
|
</div>
|
|
<div id="timeforsaveimages">
|
|
</div>
|
|
<div id="stats">
|
|
</div>
|
|
|
|
</div>
|
|
<div id="bottombar">
|
|
<a href="cams.html" class="w3-button w3-black">Cam List</a>
|
|
<a href="map.html" class="w3-button w3-black">Cam Map</a>
|
|
<a href="db.html" class="w3-button w3-black">WU obs</a>
|
|
<a href="5min.html" class="w3-button w3-black">5m ASOS obs</a>
|
|
<a href="today.txt" class="w3-button w3-black">CoCoRaHS Remarks</a>
|
|
<a id = "buffer" href="outage.html?buffer=true" class="w3-button w3-black">Outage Map w/ Outside Counties</a>
|
|
<a href="https://docs.google.com/forms/d/1-2rTBkNyyBVe08G1vN1hcSOEOvvLUcS1Vs2SmmaudlU" class="w3-button w3-black" target="_blank">Questions? Comments?</a>
|
|
<a style="font-size:24px">Power Outage Tracker</a>
|
|
</div>
|
|
<script>
|
|
|
|
|
|
|
|
function toggleFloodLayer() {
|
|
var toggle = document.getElementById("floodToggle").checked;
|
|
if (toggle) {
|
|
geoJSONflood.addTo(mymap);
|
|
} else {
|
|
mymap.removeLayer(geoJSONflood);
|
|
}
|
|
}
|
|
var update_position_timeout;
|
|
|
|
let previousData = [];
|
|
|
|
var table = new Tabulator("#tabby", {
|
|
// height:100%, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
|
|
|
|
// data:tabledata, //assign data to table
|
|
// layout:"fitDataTable",
|
|
columns:[ //Define Table Columns
|
|
{title:"County ST", field:"countystate", width: 220, headerSort:false, formatter: function(cell) {
|
|
let data = cell.getData();
|
|
let value = cell.getValue();
|
|
cell.getElement().style.color = getColor(data.perout)
|
|
return value
|
|
}},
|
|
{title:"Out", field:"outage", width: 120, headerSort:false},
|
|
{title:"Served", field:"served", width: 120, headerSort:false},
|
|
{title:"% Out", field:"perout", headerSort:false, formatter: function(cell) {
|
|
let data = cell.getData();
|
|
let value = cell.getValue();
|
|
if (data.changeDirection) {
|
|
//cell.getElement().style.backgroundColor =
|
|
// data.changeDirection === "up" ? "red" : "green";
|
|
switch (data.changeDirection) {
|
|
case "up":
|
|
cell.getElement().style.backgroundColor = "red";
|
|
break;
|
|
case "down":
|
|
cell.getElement().style.backgroundColor = "green";
|
|
break;
|
|
case "upold":
|
|
cell.getElement().style.backgroundColor = "darkred";
|
|
break;
|
|
case "downold":
|
|
cell.getElement().style.backgroundColor = "darkgreen";
|
|
break;
|
|
default:
|
|
cell.getElement().style.backgroundColor = null; // or whatever default color you prefer
|
|
}
|
|
}
|
|
return Math.round(value*10,1)/10; // Display the value
|
|
}},
|
|
],
|
|
placeholder: "No Outages Over 0.25% Detected"
|
|
|
|
});
|
|
|
|
//update_position_timeout = setTimeout(update_position, 10000);
|
|
|
|
|
|
function updateTableData(data) {
|
|
for (i in data) {
|
|
data[i].countystate = data[i].county + ' ' + data[i].state
|
|
}
|
|
const newData = data
|
|
.filter(item => item.perout > 0.2)
|
|
|
|
newData.forEach(function(newRow) {
|
|
let oldRow = previousData.find(function(item) {
|
|
return item.countystate === newRow.countystate;
|
|
});
|
|
if (oldRow) {
|
|
let newValue = parseFloat(newRow.perout);
|
|
let oldValue = parseFloat(oldRow.perout);
|
|
if (!isNaN(newValue) && !isNaN(oldValue)) {
|
|
if (newValue !== oldValue) {
|
|
newRow.changeDirection = newValue > oldValue ? "up" : "down";
|
|
} else {
|
|
// When perout hasn't changed, modify existing direction
|
|
if (oldRow.changeDirection) {
|
|
if (oldRow.changeDirection === "up") {
|
|
newRow.changeDirection = "upold";
|
|
} else if (oldRow.changeDirection === "down") {
|
|
newRow.changeDirection = "downold";
|
|
} else {
|
|
// Preserve any other existing changeDirection values
|
|
newRow.changeDirection = oldRow.changeDirection;
|
|
}
|
|
} else {
|
|
if (previousData.length != 0) {
|
|
newRow.changeDirection = newValue > 0 ? "up" : "down";
|
|
} else {
|
|
newRow.changeDirection = "up";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update the table
|
|
table.setData(newData)
|
|
table.setSort("perout", "desc");
|
|
// Update previousData
|
|
previousData = JSON.parse(JSON.stringify(data));
|
|
}
|
|
|
|
|
|
|
|
|
|
//Need to update test county geojson to move county out of geometry
|
|
|
|
const queryString = window.location.search;
|
|
const urlParams = new URLSearchParams(queryString);
|
|
const sad = urlParams.has('SAD')
|
|
const sad2 = urlParams.has('sad')
|
|
const displayBuffer = urlParams.has('buffer')
|
|
const urlstart = urlParams.get('start')
|
|
const urlend = urlParams.get('end')
|
|
|
|
|
|
if (displayBuffer == true) {
|
|
var countiesjson = 'test.geojson'
|
|
var powerapi = 'powerapitest.php'
|
|
var states = 'states.geojson'
|
|
|
|
var bufferlink = document.getElementById("buffer");
|
|
bufferlink.setAttribute('href', "outage.html");
|
|
bufferlink.innerHTML = "Outages Without Buffer";
|
|
|
|
|
|
|
|
|
|
} else {
|
|
var powerapi = 'powerapi.php'
|
|
var countiesjson = 'rlxtest.json'
|
|
var states = 'states.geojson'
|
|
}
|
|
|
|
|
|
|
|
|
|
var slider = document.getElementById('transparency');
|
|
|
|
function updateOpacity() {
|
|
geoJSONcounties.setStyle({ opacity: slider.value, fillOpacity: slider.value });
|
|
}
|
|
|
|
slider.addEventListener('mousedown', function() {
|
|
mymap.dragging.disable(); // Disable map dragging
|
|
});
|
|
|
|
slider.addEventListener('mouseup', function() {
|
|
mymap.dragging.enable(); // Re-enable map dragging
|
|
});
|
|
|
|
slider.addEventListener('input', updateOpacity);
|
|
|
|
|
|
function vtecget(vtectext){
|
|
vtectext = vtectext.slice(1,-1);
|
|
potato = vtectext.split('.');
|
|
vtecstring = "#20" + potato[6].substring(0,2) + "-";
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
vtecstring = vtecstring.concat(potato[i]);
|
|
if (i < 5) {
|
|
vtecstring = vtecstring.concat("-");
|
|
}}
|
|
return "https://mesonet.agron.iastate.edu/vtec/" + vtecstring
|
|
}
|
|
|
|
|
|
function googleMap(lat,lon){
|
|
return "http://maps.google.com/maps?t=k&q=loc:" + lat + "+" + lon + "&basemap=satellite";
|
|
}
|
|
|
|
|
|
function playarchive() {
|
|
pickerStart = document.getElementById('start').value
|
|
pickerEnd = document.getElementById('end').value
|
|
county_archive_play(pickerStart,pickerEnd)
|
|
clearTimeout(update_position_timeout);
|
|
|
|
|
|
}
|
|
|
|
function togglea() {
|
|
//var hours = document.getElementById("numberofhours").value;
|
|
|
|
pickerStart = document.getElementById('start').value
|
|
pickerEnd = document.getElementById('end').value
|
|
var el = document.getElementById("archive");
|
|
|
|
|
|
if (el.firstChild.data == "Toggle Archive Mode") {
|
|
|
|
mymap.removeLayer(geoJSONflood);
|
|
document.getElementById('start').style.display = 'block'; // or 'inline-block'
|
|
document.getElementById('end').style.display = 'block';
|
|
document.getElementById('screenshots').style.display = 'block';
|
|
document.getElementById("floodToggle").checked = false;
|
|
$("#slider-2" ).slider.display = 'block';
|
|
|
|
el.firstChild.data = "Return to Realtime";
|
|
clearTimeout(update_position_timeout);
|
|
//county_archive_play(pickerStart,pickerEnd)
|
|
//county_archive_play(new Date(new Date().getTime() - (hours * 60 * 60 * 1000)).toISOString(),new Date().toISOString());
|
|
////county_archive_play(new Date(new Date().getTime() - (hours * 60 * 60 * 1000)).toISOString().replace('T',' ').replace('Z',''),new Date().toISOString().replace('T',' ').replace('Z',''));
|
|
// .toISOString().replace('T',' ').replace('Z','')
|
|
|
|
}
|
|
else {
|
|
document.getElementById("floodToggle").checked = true;
|
|
geoJSONflood.addTo(mymap);
|
|
el.firstChild.data = "Toggle Archive Mode";
|
|
$("#slider-2" ).slider.display = 'none';
|
|
document.getElementById('start').style.display = 'none';
|
|
document.getElementById('end').style.display = 'none';
|
|
document.getElementById('screenshots').style.display = 'none';
|
|
|
|
|
|
|
|
update_position();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var mymap = L.map('mapid', {zoomDelta: 0.25, zoomSnap: 0}).setView([38.508, -81.652480], 8.0);
|
|
|
|
|
|
var Esri_WorldStreetMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
|
|
attribution: 'Tiles © Esri'
|
|
});
|
|
|
|
var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
|
|
});
|
|
|
|
var Esri_WorldTopoMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
|
|
attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
|
|
});
|
|
var CartoDB_Positron = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
|
|
subdomains: 'abcd',
|
|
maxZoom: 20
|
|
});
|
|
var USGS_USImageryTopo = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}', {
|
|
maxZoom: 20,
|
|
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
|
|
});
|
|
|
|
|
|
var baselayers = {
|
|
"Black and White": CartoDB_Positron,
|
|
"Esri Street Map": Esri_WorldStreetMap,
|
|
"Esri Satellite": Esri_WorldImagery,
|
|
"Esri Topo": Esri_WorldTopoMap,
|
|
"USGS Sat/Topo": USGS_USImageryTopo,
|
|
}
|
|
var layerControl = L.control.layers(baselayers,null,{collapsed: false}).addTo(mymap);
|
|
|
|
//CartoDB_Positron.addTo(mymap);
|
|
|
|
|
|
|
|
|
|
var geoJSONcounties = L.geoJSON(false, {
|
|
style: function (feature) {
|
|
return {
|
|
weight: 3,
|
|
opacity: 1,
|
|
color: 'black',
|
|
fillColor: 'navy',
|
|
fillOpacity: 1
|
|
};
|
|
},
|
|
}).addTo(mymap);
|
|
|
|
|
|
var geoJSONstates = L.geoJSON(false, {
|
|
style: function (feature) {
|
|
return {
|
|
weight: 1,
|
|
opacity: 1,
|
|
color: 'blue',
|
|
fillColor: null,
|
|
fillOpacity: 0
|
|
};
|
|
},
|
|
}).addTo(mymap);
|
|
|
|
|
|
var geoJSONsvr = L.geoJSON(false, {
|
|
style: function (feature) {
|
|
return {
|
|
weight: 3,
|
|
opacity: 1,
|
|
color: getColorWarning(feature.properties.type),
|
|
fillColor: 'clear',
|
|
fillOpacity: 0
|
|
};
|
|
}
|
|
}).addTo(mymap);
|
|
|
|
var smallIcon = new L.Icon({
|
|
iconSize: [10, 10],
|
|
iconAnchor: [0, 0],
|
|
popupAnchor: [1, -24],
|
|
iconUrl: 'fire.png'
|
|
});
|
|
|
|
var medIcon = new L.Icon({
|
|
iconSize: [15, 15],
|
|
iconAnchor: [0, 0],
|
|
popupAnchor: [1, -24],
|
|
iconUrl: 'fire.png'
|
|
});
|
|
var bigIcon = new L.Icon({
|
|
iconSize: [20, 20],
|
|
iconAnchor: [0, 0],
|
|
popupAnchor: [1, -24],
|
|
iconUrl: 'fire.png'
|
|
});
|
|
|
|
var floodIcon = new L.Icon({
|
|
iconSize: [15, 15],
|
|
iconAnchor: [0, 0],
|
|
popupAnchor: [1, -24],
|
|
iconUrl: 'flood.png'
|
|
});
|
|
|
|
|
|
|
|
var geoJSONfire = L.geoJSON(false, {
|
|
pointToLayer: function(feature,latlng){
|
|
if (feature.properties.dailyacres < 50) {
|
|
return L.marker(latlng,{icon: smallIcon}).bindPopup('Fire Name: ' + feature.properties.incname + '<br> Acres: ' + feature.properties.dailyacres + '<br>Contained: ' + feature.properties.contained + '%<br> Personnel: ' + feature.properties.personnel + '<br>Last Update: ' + feature.properties.modified +'Z');
|
|
} else if (feature.properties.dailyacres < 200) {
|
|
return L.marker(latlng,{icon: medIcon}).bindPopup('Fire Name: ' + feature.properties.incname + '<br> Acres: ' + feature.properties.dailyacres + '<br>Contained: ' + feature.properties.contained + '%<br> Personnel: ' + feature.properties.personnel + '<br>Last Update: ' + feature.properties.modified + 'Z');
|
|
} else {
|
|
return L.marker(latlng,{icon: bigIcon}).bindPopup('Fire Name: ' + feature.properties.incname + '<br> Acres: ' + feature.properties.dailyacres + '<br>Contained: ' + feature.properties.contained + '%<br> Personnel: ' + feature.properties.personnel + '<br>Last Update: ' + feature.properties.modified + 'Z');
|
|
}
|
|
}
|
|
|
|
|
|
}).addTo(mymap);
|
|
|
|
var geoJSONflood = L.geoJSON(false, {
|
|
pointToLayer: function(feature,latlng){
|
|
|
|
return L.marker(latlng,{icon: floodIcon}).bindPopup('Location: ' + feature.properties.description + '<br> Start: ' + feature.properties.start + '<br>Road Status: ' + feature.properties.roadstatus + '<br> Lat: ' + feature.properties.lat + ' Lon: ' + feature.properties.lon);
|
|
|
|
}
|
|
|
|
|
|
|
|
}).addTo(mymap);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var geoJSONLayer = L.geoJSON(false, {
|
|
style: function (feature) {
|
|
return {
|
|
fillColor: getColor((feature.geometry.properties.outages / feature.geometry.properties.served) * 100),
|
|
color: 'navy',
|
|
weight: 3,
|
|
opacity: 1,
|
|
fillOpacity: 1
|
|
};
|
|
}
|
|
}).addTo(mymap);
|
|
|
|
var geoJSONPoint = L.geoJSON(false, {
|
|
pointToLayer: function(feature,latlng) {
|
|
if (/tree|weather/i.test(feature.properties.cause)) {
|
|
return new L.CircleMarker(latlng, {radius: 2, fillOpacity: 1, weight:12, interactive: true, color: getColorpoint(feature.properties.outage)})
|
|
.bindPopup((latlng.lat).toFixed(3) + ", " + (latlng.lng).toFixed(3) + "<br>Outage Start: " + feature.properties.time + "Z<br>Customers Affected: " + feature.properties.outage + "<br>Cause: " + feature.properties.cause + "<br><a href='" + googleMap((latlng.lat).toFixed(3),(latlng.lng).toFixed(3)) + "' target='_blank'>Google Map Link</a>");
|
|
|
|
|
|
}
|
|
else {
|
|
return new L.CircleMarker(latlng, {radius: 2, fillOpacity: 1, interactive: true, color: getColorpoint(feature.properties.outage)})
|
|
.bindPopup((latlng.lat).toFixed(3) + ", " + (latlng.lng).toFixed(3) + "<br>Outage Start: " + feature.properties.time + "Z<br>Customers Affected: " + feature.properties.outage + "<br>Cause: " + feature.properties.cause + "<br><a href='" + googleMap((latlng.lat).toFixed(3),(latlng.lng).toFixed(3)) + "' target='_blank'>Google Map Link</a>");
|
|
}
|
|
}
|
|
}).addTo(mymap);
|
|
|
|
|
|
|
|
// Testing Code here:
|
|
var geoJSONReports = L.geoJSON(false, {
|
|
pointToLayer: function(feature,latlng) {
|
|
if (/tree|weather/i.test(feature.properties.cause)) {
|
|
return new L.CircleMarker(latlng, {radius: 2, fillOpacity: 1, weight:12, interactive: true, color: getColorpoint(feature.properties.outage)})
|
|
.bindPopup((latlng.lat).toFixed(3) + ", " + (latlng.lng).toFixed(3) + "<br>Start Time: " + feature.properties.time + "<br>Issue: " + feature.properties.issue + "<br>Comments: " + feature.properties.comments + "<br><a href='" + googleMap((latlng.lat).toFixed(3),(latlng.lng).toFixed(3)) + "' target='_blank'>Google Map Link</a>");
|
|
|
|
|
|
}
|
|
else {
|
|
return new L.CircleMarker(latlng, {radius: 2, fillOpacity: 1, interactive: true, color: getColorpoint(feature.properties.outage)})
|
|
.bindPopup((latlng.lat).toFixed(3) + ", " + (latlng.lng).toFixed(3) + "<br>Start Time: " + feature.properties.time + "<br>Issue: " + feature.properties.issue + "<br>Comments: " + feature.properties.comments + "<br><a href='" + googleMap((latlng.lat).toFixed(3),(latlng.lng).toFixed(3)) + "' target='_blank'>Google Map Link</a>");
|
|
}
|
|
}
|
|
}).addTo(mymap);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//End test
|
|
|
|
|
|
|
|
|
|
|
|
function getColorWarning(d) {
|
|
if (d == 'SVRRLX'||d == 'SVR') {
|
|
return 'orange'
|
|
}
|
|
if (d == 'TORRLX'|| d == 'TOR') {
|
|
return 'red'
|
|
}
|
|
}
|
|
|
|
|
|
function getColorpoint(d) {
|
|
const scale = chroma.scale(['gray','#0cff0c','#ff9933','red','#fe019a']).domain([4,50,200,500,1000]);
|
|
return scale(d).hex();
|
|
}
|
|
|
|
|
|
function getColor(d) {
|
|
const scale = chroma.scale(['navy','steelblue','yellow','orange','darkorange','crimson','red','magenta']).domain([0,0.25,2,5,10,25,50,100]);
|
|
return scale(d).hex();
|
|
}
|
|
|
|
|
|
|
|
function initial_county_load() {
|
|
geoJSONcounties.clearLayers();
|
|
geoJSONstates.clearLayers();
|
|
|
|
// Load states (can be done in parallel)
|
|
$.getJSON(states, function(data) {
|
|
var geojsonFeature = data;
|
|
geoJSONstates.addData(geojsonFeature);
|
|
geoJSONstates.bringToBack();
|
|
});
|
|
|
|
// Load counties and THEN update their style
|
|
$.getJSON(countiesjson, function(data) {
|
|
var geojsonFeature = data;
|
|
geoJSONcounties.addData(geojsonFeature);
|
|
geoJSONcounties.bringToBack();
|
|
|
|
// **CRITICAL FIX:** Call update_position() here, AFTER the counties are loaded.
|
|
update_position();
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function county_archive_play(start,end) {
|
|
geoJSONPoint.clearLayers();
|
|
geoJSONsvr.clearLayers();
|
|
//
|
|
$.getJSON(powerapi + `?archivepoint=t&start=${start}&end=${end}`, function(data3) {
|
|
var geojsonpoint = data3;
|
|
geoJSONPoint.clearLayers();
|
|
geoJSONPoint.addData(geojsonpoint);
|
|
geoJSONPoint.bringToFront();
|
|
geoJSONPoint.setStyle({color: 'rgba(0,0,0,0)'});
|
|
});
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
$.getJSON(powerapi + `?svr=archive&start=${start}&end=${end}`, function(data3) {
|
|
var geojsonsvr = data3;
|
|
geoJSONsvr.clearLayers();
|
|
geoJSONsvr.addData(geojsonsvr);
|
|
geoJSONsvr.bringToFront();
|
|
geoJSONsvr.removeEventListener('click');
|
|
});
|
|
|
|
|
|
|
|
$.getJSON(powerapi + `?countyarchive=t&start=${start}&end=${end}`, function(data) {
|
|
var uniqueNames = [];
|
|
for(i = 0; i< data.length; i++){
|
|
if(uniqueNames.indexOf(data[i].time) === -1){
|
|
uniqueNames.push(data[i].time);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
$( "#slider-2" ).slider({
|
|
value: 0,
|
|
animate:"slow",
|
|
min: 0,
|
|
max: uniqueNames.length - 1,
|
|
range: false,
|
|
step: 1,
|
|
orientation: "horizontal",
|
|
slide: function(event,ui) {
|
|
$("#slider-2-value").text(uniqueNames[ui.value]);
|
|
archive_county(data,uniqueNames[ui.value]);
|
|
ss_map(uniqueNames[ui.value].replace(/[.]\d+/, 'Z'));
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function archive_county(data,timeindex) {
|
|
$('#map').html(timeindex.replace(/[.]\d+/, 'Z'));
|
|
$('#timeforsaveimages').html(timeindex.replace(/[.]\d+/, 'Z'));
|
|
for (var i in data) {
|
|
geoJSONcounties.eachLayer(function(layer) {
|
|
if (data[i].time == timeindex) {
|
|
|
|
if(layer.feature.properties.county == data[i].county && layer.feature.properties.state == data[i].state) {
|
|
layer.setStyle({fillColor: getColor(data[i].outage / data[i].served*100)});
|
|
layer.bindPopup(data[i].county + " County Outages: " + data[i].outage + " of " + data[i].served + " Served (" + ((data[i].outage / data[i].served)*100).toFixed(2) + "%)");
|
|
}
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
//
|
|
|
|
geoJSONPoint.eachLayer(function(layer) {
|
|
if(Date.parse(layer.feature.properties.time) < Date.parse(timeindex) && Date.parse(layer.feature.properties.lastchange) > Date.parse(timeindex)) {
|
|
|
|
if (/tree|weather/i.test(layer.feature.properties.cause)) {
|
|
layer.setStyle({radius: 2, fillOpacity: 1, weight:12, interactive: true, color: getColorpoint(layer.feature.properties.outage)});
|
|
} else {
|
|
layer.setStyle({radius: 2, weight:3, fillOpacity: 1, interactive: true, color: getColorpoint(layer.feature.properties.outage)});
|
|
}
|
|
|
|
} else {
|
|
layer.setStyle({weight: 0, radius:0, color: 'rgba(0,0,0,0)'});
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
geoJSONsvr.eachLayer(function(layer) {
|
|
if(Date.parse(layer.feature.properties.issue) < Date.parse(timeindex) && Date.parse(layer.feature.properties.end) > Date.parse(timeindex)) {
|
|
layer.setStyle({color: getColorWarning(layer.feature.properties.type)});
|
|
vtecurl = vtecget(layer.feature.properties.vtec)
|
|
|
|
layer.bindPopup('<a href="' + vtecurl + '">' + layer.feature.properties.vtec + '</a>');
|
|
|
|
|
|
//console.log(layer.feature.properties.vtec);
|
|
} else {
|
|
layer.setStyle({color: 'rgba(0,0,0,0)'});
|
|
layer.unbindPopup();
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
function getlsr() {
|
|
$.getJSON(powerapi + '?lsr', function(data) {
|
|
geoJSONcounties.eachLayer(function(layer) {
|
|
console.log(data)
|
|
for(var i in data) {
|
|
|
|
// $('#map').html(data[i].time);
|
|
if(layer.feature.properties.county == data[i].county && layer.feature.properties.state == data[i].state) {
|
|
layer.bindPopup(data[i].county + " County Outages: " + data[i].outage + " of " + data[i].served + " Served (" + ((data[i].outage / data[i].served)*100).toFixed(2) + "%)");
|
|
layer.setStyle({fillColor: getColor(data[i].outage / data[i].served*100)});
|
|
}}});
|
|
// $('#map').html((data[0].time).replace(/[.]\d+/, 'Z'));
|
|
temptime = new Date( Date.now() );
|
|
$('#map').html(temptime.toISOString().replace(/[.]\d+/, '').replace(/T/,' '));
|
|
});
|
|
|
|
}
|
|
|
|
|
|
function countyTable(data) {
|
|
for (i in data) {
|
|
data[i].countystate = data[i].county + ' ' + data[i].state
|
|
|
|
}
|
|
const filtered = data.filter(item => item.perout > 0);
|
|
|
|
updateTableData(filtered)
|
|
//table.setData(filtered)
|
|
|
|
table.setSort("perout", "desc");
|
|
}
|
|
|
|
|
|
function update_position() {
|
|
$.getJSON(powerapi + '?county=r', function(data) {
|
|
console.log(data);
|
|
|
|
const dataMap = {};
|
|
for (const item of data) {
|
|
const key = `${item.state}-${item.county}`;
|
|
dataMap[key] = item;
|
|
}
|
|
|
|
geoJSONcounties.eachLayer(function(layer) {
|
|
const county = layer.feature.properties.county;
|
|
const state = layer.feature.properties.state;
|
|
const key = `${state}-${county}`;
|
|
|
|
if (dataMap[key]) {
|
|
const countyData = dataMap[key];
|
|
|
|
// --- ROBUSTNESS FIX: Manually calculate percentage like in the archive function ---
|
|
let percentageOut = 0;
|
|
if (countyData.served > 0) { // Prevent division by zero
|
|
percentageOut = (countyData.outage / countyData.served) * 100;
|
|
}
|
|
|
|
layer.bindPopup(
|
|
`${countyData.county} County Outages: ${countyData.outage} of ${countyData.served} Served (${percentageOut.toFixed(2)}%)`
|
|
);
|
|
|
|
// Use the newly calculated percentage for styling
|
|
layer.setStyle({ fillColor: getColor(percentageOut) });
|
|
}
|
|
});
|
|
|
|
temptime = new Date(Date.now());
|
|
$('#map').html(temptime.toISOString().replace(/[.]\d+/, '').replace(/T/, ' '));
|
|
console.log(data);
|
|
|
|
updateTableData(data);
|
|
});
|
|
|
|
|
|
$.getJSON(powerapi, function(data1) {
|
|
var geojsonPoint = data1;
|
|
geoJSONPoint.clearLayers();
|
|
geoJSONPoint.addData(geojsonPoint);
|
|
geoJSONPoint.bringToFront();
|
|
});
|
|
|
|
$.getJSON(powerapi + '?svr=current', function(data2) {
|
|
var geojsonsvr = data2;
|
|
geoJSONsvr.clearLayers();
|
|
geoJSONsvr.addData(geojsonsvr);
|
|
geoJSONsvr.bringToFront();
|
|
geoJSONsvr.removeEventListener('click');
|
|
});
|
|
|
|
|
|
$.getJSON('fire.php', function(data2) {
|
|
var fire = data2;
|
|
geoJSONfire.clearLayers();
|
|
geoJSONfire.addData(fire);
|
|
geoJSONfire.bringToFront();
|
|
geoJSONfire.removeEventListener('click');
|
|
});
|
|
|
|
$.getJSON('lsr.php?ohgo=p', function(data3) {
|
|
var flood = data3;
|
|
geoJSONflood.clearLayers();
|
|
geoJSONflood.addData(flood);
|
|
geoJSONflood.bringToFront();
|
|
geoJSONflood.removeEventListener('click');
|
|
});
|
|
|
|
|
|
//Updated code
|
|
//
|
|
/*
|
|
$.getJSON('/911/911.php', function(data3) {
|
|
var geojsonreport = data3;
|
|
geoJSONReports.clearLayers();
|
|
geoJSONReports.addData(geojsonreport);
|
|
geoJSONReports.bringToFront();
|
|
geoJSONReports.removeEventListener('click');
|
|
});
|
|
*/
|
|
//End updating code
|
|
|
|
if (update_position_timeout) {
|
|
clearTimeout(update_position_timeout);
|
|
}
|
|
update_position_timeout = setTimeout(update_position, 120000);
|
|
}
|
|
|
|
initial_county_load();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var legend = L.control({position: 'bottomright'});
|
|
|
|
legend.onAdd = function (map) {
|
|
|
|
var div = L.DomUtil.create('div', 'info legend'),
|
|
grades = [0,0.25,2,5,10,25,50,100],
|
|
points = [4,50,200,500,1000],
|
|
labels = [];
|
|
|
|
// loop through our density intervals and generate a label with a colored square for each interval
|
|
div.innerHTML += '% Customers Served Out' + '<br>' + '(County Shade)' + '<br><br>' + '<i style="background:' + getColor([0]) + '"></i> ' + 'No Outages<br>';
|
|
for (var i = 0; i < grades.length-1; i++) {
|
|
div.innerHTML +=
|
|
'<i style="background:' + getColor(grades[i+1]) + '"></i> ' +
|
|
'Up to ' + grades[i+1] + '%'+'<br>';
|
|
}
|
|
|
|
div.innerHTML += '<br> Point Outages' + '<br>' + '(dots)' + '<br>Big dots - explicit tree or wx cause<br><br><i style="background:' + getColorpoint(points[0]) + '"></i> ' + 'Up to 4<br>';
|
|
|
|
for (var i = 0; i < points.length-1; i++) {
|
|
div.innerHTML +=
|
|
'<i style="background:' + getColorpoint(points[i+1]) + '"></i> ' +
|
|
points[i+1]+ '<br>';
|
|
}
|
|
div.innerHTML += '<br><a href="24hrpower.txt" target=_blank>Click for 120hr tree/wx caused outages</a><br><br>'
|
|
div.innerHTML += 'Fire icons indicate wildfires less than 100%<br> contained and updated within the last 36 hours'
|
|
|
|
|
|
return div;
|
|
};
|
|
|
|
legend.addTo(mymap);
|
|
|
|
|
|
|
|
|
|
//L.simpleMapScreenshoter(pluginOptions).addTo(mymap)
|
|
|
|
|
|
|
|
function ss_map(filen) {
|
|
|
|
if (document.getElementById("llss").checked == true) {
|
|
domtoimage.toPng(document.getElementById('mapid'))
|
|
.then(function (dataUrl) {
|
|
var link = document.createElement('a');
|
|
link.href = dataUrl;
|
|
link.download = filen + '.png';
|
|
link.click();
|
|
|
|
});
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function saddisplay(sad,sad2) {
|
|
|
|
|
|
if (sad == true || sad2 == true) {
|
|
document.getElementById('toggle').style.display = 'none';
|
|
document.getElementById('slider-2').style.display = 'none';
|
|
//document.getElementById('slider-container').style.display = 'none';
|
|
document.getElementById('timeforsaveimages').style.display = 'none';
|
|
document.getElementById('bottombar').remove();
|
|
document.getElementById('slider-holder').remove();
|
|
document.getElementById('map').style.display = 'none';
|
|
layerControl.remove();
|
|
document.getElementById('tabby').style.display = 'block';
|
|
document.getElementById('mapid').style.height = '100%';
|
|
mymap.zoomControl.remove();
|
|
mymap.setView([38.508, -82.152480],8.5)
|
|
//mymap.setZoom(8.5)
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
function populateStartEndTimes() {
|
|
|
|
if (urlstart) {
|
|
// Convert to format expected by datetime-local (YYYY-MM-DDTHH:MM:SS)
|
|
const startDate = new Date(urlstart);
|
|
document.getElementById("start").value = startDate.toISOString().slice(0, 19);
|
|
}
|
|
|
|
if (urlend) {
|
|
const endDate = new Date(urlend);
|
|
document.getElementById("end").value = endDate.toISOString().slice(0, 19);
|
|
}
|
|
|
|
// If we have both parameters, adjust times if needed and automatically fetch the data
|
|
if (urlstart && urlend) {
|
|
let startDate = new Date(urlstart);
|
|
let endDate = new Date(urlend);
|
|
|
|
// Calculate difference in minutes
|
|
const timeDifference = (endDate - startDate) / (1000 * 60); // Convert milliseconds to minutes
|
|
|
|
// If less than 60 minutes apart, adjust the dates
|
|
if (timeDifference < 60) {
|
|
startDate.setMinutes(startDate.getMinutes() - 30);
|
|
endDate.setMinutes(endDate.getMinutes() + 90);
|
|
|
|
// Update the input values with adjusted times
|
|
document.getElementById("start").value = startDate.toISOString().slice(0, 19);
|
|
document.getElementById("end").value = endDate.toISOString().slice(0, 19);
|
|
}
|
|
|
|
togglea();
|
|
playarchive();
|
|
}
|
|
|
|
if (!urlstart && !urlend) {
|
|
//prefill with current time and 24 hours
|
|
const now = new Date();
|
|
const startTime = new Date(now.getTime() - (24 * 60 * 60 * 1000)); // Subtract 24 hours in milliseconds
|
|
|
|
// Format dates to match datetime-local input format (YYYY-MM-DDThh:mm)
|
|
const formatDateTime = (date) => {
|
|
return date.toISOString().slice(0, 16); // Cuts off seconds and timezone
|
|
};
|
|
document.getElementById('start').value = formatDateTime(startTime);
|
|
document.getElementById('end').value = formatDateTime(now);
|
|
document.getElementById('start').style.display = 'none';
|
|
document.getElementById('end').style.display = 'none';
|
|
document.getElementById('screenshots').style.display = 'none';
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
document.getElementById('tabby').style.display = 'none';
|
|
|
|
saddisplay(sad,sad2);
|
|
populateStartEndTimes()
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
|