initial
This commit is contained in:
343
lsrtool.html
Normal file
343
lsrtool.html
Normal file
@@ -0,0 +1,343 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user