343 lines
14 KiB
HTML
343 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>RLX Report Query</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-timedimension/1.1.0/leaflet.timedimension.control.min.css" />
|
|
<link rel="stylesheet" href="/js/leaflet-radar.css">
|
|
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
|
|
|
|
<link rel="preload" href="https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}" as="image">
|
|
|
|
<style>
|
|
body, html {
|
|
height: 100%;
|
|
width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
}
|
|
#mapid {
|
|
height: 90%;
|
|
width: 100%;
|
|
position: absolute;
|
|
top: 0;
|
|
z-index: 1;
|
|
}
|
|
#slider-2 {
|
|
position: absolute;
|
|
left: 20px;
|
|
top: 200px;
|
|
width: 300px;
|
|
z-index: 1000;
|
|
}
|
|
#controls {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 80%;
|
|
z-index: 1000;
|
|
background: rgba(255, 255, 255, 0.8);
|
|
padding: 10px;
|
|
}
|
|
#time-display, #local-time-display {
|
|
margin: 10px 0;
|
|
font-size: 14px;
|
|
}
|
|
.box, .box2 {
|
|
position: absolute;
|
|
z-index: 1000;
|
|
text-align: center;
|
|
width: 250px;
|
|
left: 10%;
|
|
margin-left: -125px;
|
|
}
|
|
.box { top: 225px; }
|
|
.box2 { top: 500px; }
|
|
.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: 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;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="mapid"></div>
|
|
<div id="slider-2"></div>
|
|
<div class="box"></div>
|
|
<div class="box2"></div>
|
|
<div id="controls">
|
|
<input type="range" id="slider" min="0" max="12" step="1" value="0">
|
|
<div id="time-display">Time: </div>
|
|
<div id="local-time-display">Local Time (ET): </div>
|
|
</div>
|
|
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js" defer></script>
|
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin="" defer></script>
|
|
<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" defer></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" defer></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.43/moment-timezone-with-data.min.js" defer></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-timedimension/1.1.0/leaflet.timedimension.min.js" defer></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-timedimension/1.1.0/leaflet.timedimension.control.min.js" defer></script>
|
|
<script src="/js/leaflet-radar.js" defer></script>
|
|
<script src="https://unpkg.com/file-saver@2.0.5/dist/FileSaver.js" defer></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" defer></script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (typeof L === 'undefined') {
|
|
console.error('Leaflet is not loaded yet');
|
|
return;
|
|
}
|
|
|
|
if (typeof moment === 'undefined') {
|
|
console.error('Moment.js is not loaded yet');
|
|
return;
|
|
}
|
|
|
|
const mymap = L.map('mapid', {
|
|
zoomDelta: 0.25,
|
|
zoomSnap: 0,
|
|
fadeAnimation: false,
|
|
zoomAnimation: true,
|
|
zoomAnimationThreshold: 4,
|
|
preferCanvas: true
|
|
}).setView([38.508, -81.652480], 8.0);
|
|
|
|
const Esri_WorldStreetMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
|
|
attribution: 'Tiles © Esri',
|
|
maxZoom: 19,
|
|
tileSize: 256,
|
|
updateWhenIdle: true,
|
|
updateInterval: 200,
|
|
reuseTiles: true
|
|
}).addTo(mymap);
|
|
|
|
const baselayers = {
|
|
"Esri Street Map": Esri_WorldStreetMap,
|
|
"Esri Satellite": 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',
|
|
maxZoom: 19,
|
|
updateWhenIdle: true,
|
|
reuseTiles: true
|
|
}),
|
|
"Esri Topo": 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',
|
|
maxZoom: 19,
|
|
updateWhenIdle: true,
|
|
reuseTiles: true
|
|
}),
|
|
"USGS Sat/Topo": 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>',
|
|
updateWhenIdle: true,
|
|
reuseTiles: true
|
|
})
|
|
};
|
|
|
|
L.control.layers(baselayers, null, {collapsed: false}).addTo(mymap);
|
|
|
|
mymap.createPane('polygonPane');
|
|
mymap.getPane('polygonPane').style.zIndex = 300;
|
|
mymap.createPane('radarPane');
|
|
mymap.getPane('radarPane').style.zIndex = 350;
|
|
mymap.getPane('markerPane').style.zIndex = 400;
|
|
|
|
const geoJSONsvr = L.geoJSON(null, {
|
|
style: function(feature) {
|
|
return {
|
|
weight: 3,
|
|
opacity: 1,
|
|
color: getColorWarning(feature.properties.warntype),
|
|
fillOpacity: 0,
|
|
interactive: false
|
|
};
|
|
},
|
|
onEachFeature: function(feature, layer) {
|
|
const vtecurl = vtecget(feature.properties.vtec);
|
|
layer.bindPopup(`<a href="${vtecurl}">${feature.properties.vtec}</a>`);
|
|
},
|
|
pane: 'polygonPane'
|
|
}).addTo(mymap);
|
|
|
|
const geoJSONPoint = L.geoJSON(null, {
|
|
pointToLayer: function(feature, latlng) {
|
|
const isWeatherRelated = /tree|weather/i.test(feature.properties.cause);
|
|
return L.circleMarker(latlng, {
|
|
radius: isWeatherRelated ? 6 : 4,
|
|
fillOpacity: 1,
|
|
weight: isWeatherRelated ? 12 : 1,
|
|
color: getColorpoint(feature.properties.outage),
|
|
interactive: true,
|
|
pane: 'markerPane'
|
|
}).bindPopup(`
|
|
${latlng.lat.toFixed(3)}, ${latlng.lng.toFixed(3)}<br>
|
|
Outage Start: ${feature.properties.time}<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);
|
|
|
|
function googleMap(lat, lon) {
|
|
return `http://maps.google.com/maps?t=k&q=loc:${lat}+${lon}&basemap=satellite`;
|
|
}
|
|
|
|
function vtecget(vtectext) {
|
|
vtectext = vtectext.slice(1, -1);
|
|
const parts = vtectext.split('.');
|
|
let vtecstring = `#20${parts[6].substring(0, 2)}-`;
|
|
for (let i = 0; i < 6; i++) {
|
|
vtecstring += parts[i] + (i < 5 ? '-' : '');
|
|
}
|
|
return `https://mesonet.agron.iastate.edu/vtec/${vtecstring}`;
|
|
}
|
|
|
|
function getColorWarning(d) {
|
|
return d === 'SVRRLX' || d === 'SVR' ? 'orange' :
|
|
d === 'TORRLX' || d === 'TOR' ? 'red' : 'gray';
|
|
}
|
|
|
|
function getColorpoint(d) {
|
|
return chroma.scale(['gray', '#0cff0c', '#ff9933', 'red', '#fe019a'])
|
|
.domain([4, 50, 200, 500, 1000])(d).hex();
|
|
}
|
|
|
|
fetch('counties.json')
|
|
.then(res => res.json())
|
|
.then(data => L.geoJSON(data, {
|
|
style: {color: "#000000", weight: 1, fillOpacity: 0},
|
|
pane: 'polygonPane'
|
|
}).addTo(mymap))
|
|
.catch(err => console.error('Failed to load counties:', err));
|
|
|
|
async function saddisplay() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const vtec = urlParams.get('vtec');
|
|
const ids = urlParams.get('id');
|
|
|
|
try {
|
|
const svrResponse = await fetch(`lsr.php?vtec=${vtec}`);
|
|
const geojsonsvr = await svrResponse.json();
|
|
geoJSONsvr.clearLayers().addData(geojsonsvr);
|
|
|
|
if (geojsonsvr.features.length > 0) {
|
|
const firstFeature = geojsonsvr.features[0].properties;
|
|
mymap.setView([firstFeature.lat, firstFeature.lon], 10);
|
|
createRadarMap(firstFeature.issue + "Z", firstFeature.endtime + "Z");
|
|
}
|
|
|
|
if (ids) {
|
|
const powerResponse = await fetch(`powerapi.php?poweridsgeojson=${ids}`);
|
|
const geojsonPoint = await powerResponse.json();
|
|
geoJSONPoint.clearLayers().addData(geojsonPoint);
|
|
}
|
|
|
|
geoJSONPoint.bringToFront();
|
|
} catch (error) {
|
|
console.error('Error in saddisplay:', error);
|
|
}
|
|
}
|
|
|
|
function createRadarMap(startTime, endTime) {
|
|
const coeff = 1000 * 60 * 5;
|
|
const start = new Date(Math.round(new Date(startTime).getTime() / coeff) * coeff);
|
|
const end = new Date(endTime);
|
|
const times = [];
|
|
let current = new Date(start);
|
|
|
|
while (current <= end) {
|
|
times.push(current.toISOString());
|
|
current.setMinutes(current.getMinutes() + 5);
|
|
}
|
|
|
|
const radarLayers = new Map();
|
|
const slider = document.getElementById("slider");
|
|
const timeDisplay = document.getElementById("time-display");
|
|
const localTimeDisplay = document.getElementById("local-time-display");
|
|
|
|
function createRadarLayer(time) {
|
|
return L.tileLayer.wms("https://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0q-t.cgi", {
|
|
layers: 'nexrad-n0q-wmst',
|
|
format: 'image/png',
|
|
transparent: true,
|
|
attribution: "Weather data © 2025 IEM Nexrad",
|
|
time: time,
|
|
opacity: 0,
|
|
pane: 'radarPane',
|
|
updateWhenIdle: true,
|
|
reuseTiles: true
|
|
});
|
|
}
|
|
|
|
function updateLocalTimeDisplay(utcTime) {
|
|
const easternTime = moment.utc(utcTime).tz('America/New_York').format('YYYY-MM-DD HH:mm:ss');
|
|
localTimeDisplay.textContent = `Local Time (ET): ${easternTime}`;
|
|
}
|
|
|
|
slider.max = times.length - 1;
|
|
slider.value = 0;
|
|
|
|
slider.addEventListener("input", function() {
|
|
const index = parseInt(this.value);
|
|
const time = times[index];
|
|
|
|
if (!radarLayers.has(time)) {
|
|
const layer = createRadarLayer(time);
|
|
radarLayers.set(time, layer);
|
|
layer.addTo(mymap);
|
|
}
|
|
|
|
radarLayers.forEach((layer, layerTime) => {
|
|
layer.setOpacity(layerTime === time ? 0.8 : 0);
|
|
});
|
|
|
|
timeDisplay.textContent = `Time: ${time}`;
|
|
updateLocalTimeDisplay(time);
|
|
geoJSONPoint.bringToFront();
|
|
});
|
|
|
|
const firstLayer = createRadarLayer(times[0]);
|
|
radarLayers.set(times[0], firstLayer);
|
|
firstLayer.setOpacity(0.8).addTo(mymap);
|
|
timeDisplay.textContent = `Time: ${times[0]}`;
|
|
updateLocalTimeDisplay(times[0]);
|
|
geoJSONPoint.bringToFront();
|
|
|
|
setInterval(() => {
|
|
const currentIndex = parseInt(slider.value);
|
|
updateLocalTimeDisplay(times[currentIndex]);
|
|
}, 1000);
|
|
}
|
|
|
|
saddisplay();
|
|
|
|
mymap.on('overlayadd overlayremove zoomend', () => {
|
|
geoJSONPoint.bringToFront();
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |