// MIT License
//
// Copyright (c) 2024 HowMuchRadiation.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Additional Conditions:
// 1. Attribution: Users are encouraged to credit the original authors when using or redistributing this software.
// 2. Modification Notices: Any modifications made to the original software should be documented.
// 3. Documentation: Clear usage instructions should be provided with any distribution.
// 4. Limitations of Liability: The authors are not liable for any damages resulting from the use of this software.
// 5. Compliance with Laws: Users must ensure compliance with applicable laws when using this software.
// 6. Contribution Guidelines: Contributions are welcome; please follow the provided guidelines.
// 7. Contact Information: For questions or support, please contact HowMuchRadiation.com
//
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Note: This widget is not affiliated with GMCmap and operates independently.
export default {
async fetch(req) {
const url = new URL(req.url);
// Check for "param_id" in URL parameters
if (!url.search || !url.searchParams.has("param_id")) {
return this.helpScreen();
}
const paramId = url.searchParams.get("param_id");
const timezone = parseFloat(url.searchParams.get("timezone")) || 0;
const bgColor = url.searchParams.get("bg_color") || "#f5deb3";
const footer = url.searchParams.get("footer") || "HowMuchRadiation.com";
const showBorder = url.searchParams.get("show_border") !== "false";
const numRecords = parseInt(url.searchParams.get("n")) || 20; // Default n is 20
try {
const jsonData = await this.getGMCData(paramId, numRecords);
return this.radScreen(jsonData, timezone, bgColor, showBorder, footer, numRecords);
} catch (error) {
return this.errorScreen(bgColor, showBorder, footer);
}
},
// Fetch data from GMCmap API
async getGMCData(paramId, numRecords) {
const jsonUrl = `https://www.gmcmap.com/historyData-plain.asp?Param_ID=${paramId}&n=${numRecords}`;
const response = await fetch(jsonUrl);
if (!response.ok) {
throw new Error(`Failed to fetch JSON data: ${response.status} ${response.statusText}`);
}
const jsonData = await response.json();
if (!jsonData || jsonData.length === 0) {
throw new Error("No data available from GMCmap.");
}
return jsonData;
},
// Generate Help Screen
helpScreen() {
const helpText = `
`;
return new Response(helpText, {
headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" },
});
},
// Generate Error Screen
errorScreen(bgColor, showBorder, footer) {
const errorSvg = `
`;
return new Response(errorSvg, {
headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" },
});
},
// Generate Radiation Data Screen
radScreen(jsonData, timezone, bgColor, showBorder, footer, numRecords) {
const { currentUSv, variance, avgInterval, totalTime } = this.calcStats(jsonData, numRecords);
const mostRecent = jsonData[0];
const { CPM, ACPM } = mostRecent;
const formattedTime = this.formatTimestamp(mostRecent.time, timezone);
const varianceText = numRecords > 1
? `
Variance: ${variance.toFixed(2)}% from average
Avg Update Interval: ${avgInterval.toFixed(2)} minutes
Total Time: ${totalTime}
`
: "";
const chartData = jsonData.map(record => parseFloat(record.uSv)).filter(value => !isNaN(value));
const chartSvg = this.asciiChart(chartData, 316, 120); // Adjust width and height as needed
const svgContent = `
`;
return new Response(svgContent, {
headers: { "Content-Type": "image/svg+xml", "Cache-Control": "no-store" },
});
},
calcStats(jsonData, numRecords) {
const uSvValues = jsonData.map(record => parseFloat(record.uSv)).filter(value => !isNaN(value));
const currentUSv = parseFloat(jsonData[0].uSv);
const avgUSv = uSvValues.reduce((a, b) => a + b, 0) / uSvValues.length;
const variance = ((currentUSv - avgUSv) / avgUSv) * 100;
const timestamps = jsonData.map(record => new Date(record.time + " UTC").getTime());
const intervals = timestamps.slice(1).map((time, i) => Math.abs((time - timestamps[i]) / 60000));
const avgInterval = intervals.length > 0 ? intervals.reduce((a, b) => a + b, 0) / intervals.length : 0;
const totalMinutes = Math.abs((timestamps[timestamps.length - 1] - timestamps[0]) / 60000);
const totalTime = totalMinutes <= 90
? `${totalMinutes.toFixed(0)} minutes`
: totalMinutes <= 1440
? `${(totalMinutes / 60).toFixed(1)} hours`
: `${(totalMinutes / 1440).toFixed(1)} days`;
return { currentUSv, variance, avgInterval, totalTime };
},
formatTimestamp(time, timezone) {
const utcDate = new Date(time + " UTC");
const localDate = new Date(utcDate.getTime() + timezone * 60 * 60 * 1000);
return timezone === 0
? `${utcDate.toISOString().replace("T", " ").split(".")[0]} UTC`
: `${localDate.toISOString().replace("T", " ").split(".")[0]} UTC${timezone > 0 ? "+" : ""}${timezone}`;
},
asciiChart(data, width, height) {
const maxValue = Math.max(...data);
const minValue = Math.min(...data);
const midValue = (maxValue + minValue) / 2;
const chartHeight = height; // Adjust for padding
const chartWidth = width - 20; // Adjust for padding
const scaleX = chartWidth / (data.length - 1);
const scaleY = chartHeight / (maxValue - minValue);
let pathData = data
.map((value, index) => {
const x = 22 + index * scaleX; // Move chart left to align with labels
const y = height - 10 - (value - minValue) * scaleY; // Invert y-axis
return `${index === 0 ? "M" : "L"}${x},${y}`;
})
.join(" ");
return `
${minValue.toFixed(2)}
${midValue.toFixed(2)}
${maxValue.toFixed(2)}
`;
},
};