Last active
March 8, 2023 09:15
-
-
Save rafaelmaeuer/242e39bc29ec99b4bc42aecb7b60ca5d to your computer and use it in GitHub Desktop.
Kombination der Covid-19 Neuinfektionen und der Inzidenz-Zahl für die aktuelle Location. Inspiriert von https://gist.github.com/kevinkub/46caebfebc7e26be63403a7f0587f664
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// COVID-19 iOS-Widget for Incidence and Newcases in Germany and current geo-location | |
// Modified: https://gist.github.com/oliverandrich/0f34c8d4e6de4b6ff32937c584009a65 | |
// Inspired by: https://gist.github.com/kevinkub/46caebfebc7e26be63403a7f0587f664 | |
// Added Data Source: https://api.corona-zahlen.org/docs/ | |
const GEO = { | |
latitude: 49.87, | |
longitude: 8.65 | |
} | |
class IncidenceNewCasesWidget { | |
constructor() { | |
this.coronaApiUrl = `https://api.corona-zahlen.org/germany`; | |
this.incidenceUrl = (location) => | |
`https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=RS,GEN,cases7_per_100k,BL,last_update&geometry=${location.longitude.toFixed(3)}%2C${location.latitude.toFixed(3)}&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json`; | |
this.incidenceUrlStates = | |
"https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Coronaf%E4lle_in_den_Bundesl%E4ndern/FeatureServer/0/query?where=1%3D1&outFields=cases7_bl_per_100k&returnGeometry=false&outSR=4326&f=json"; | |
try { | |
this.fileManager = FileManager.iCloud(); | |
} catch (e) { | |
console.error(e); | |
this.fileManager = FileManager.local(); | |
} | |
this.locationPath = this.fileManager.joinPath( | |
this.fileManager.documentsDirectory(), | |
"incidenceNewCases" | |
); | |
this.locationFileName = this.fileManager.joinPath( | |
this.locationPath, | |
"location.json" | |
); | |
this.fileManager.createDirectory(this.locationPath, true); | |
} | |
async run() { | |
const widget = await this.createWidget(); | |
if (!config.runsInWidget) { | |
await widget.presentSmall(); | |
} | |
Script.setWidget(widget); | |
Script.complete(); | |
} | |
async createWidget() { | |
let header, value, label, trend, data, dataLocal; | |
// Basic widget setup | |
const list = new ListWidget(); | |
list.setPadding(15, 15, 15, 15); | |
// Add background color | |
list.backgroundColor = new Color("ffffff"); | |
list.backgroundColor = Color.dynamic(list.backgroundColor, new Color("1c1c1c")); | |
// New Cases | |
data = await this.getCoronaData(); | |
header = list.addText("🦠 Neu-Infektionen".toUpperCase()); | |
header.font = Font.mediumSystemFont(10); | |
value = list.addText("+" + parseInt(data.cases).toLocaleString()); | |
value.font = Font.mediumSystemFont(24); | |
label = list.addText(data.areaName); | |
label.font = Font.lightSystemFont(12); | |
label.textColor = Color.gray(); | |
list.addSpacer(); | |
// Incidence | |
dataLocal = await this.getIncidenceDataLocal(); | |
header = list.addText("📈 7-Tage Inzidenz".toUpperCase()); | |
header.font = Font.mediumSystemFont(10); | |
const dataValue = data ? parseFloat(data.incidence).toLocaleString() : "NaN"; | |
const localValue = dataLocal ? parseFloat(dataLocal.value).toLocaleString() : "NaN"; | |
const dataString = dataLocal ? `${localValue} (${dataValue})` : dataValue; | |
const dataFontSize = data?.length > 3 ? 2 : 0; | |
const localFontSize = (dataLocal && dataLocal.length) > 3 ? 2 : 0; | |
const fontSize = 22 - dataFontSize - localFontSize; | |
value = list.addText(dataString); | |
value.font = Font.boldSystemFont(fontSize); | |
if (data?.value || dataLocal?.value >= 50) { | |
value.textColor = Color.red(); | |
} else if (data?.value || dataLocal?.value >= 35) { | |
value.textColor = Color.orange(); | |
} else if (data?.value || dataLocal?.value >= 25) { | |
value.textColor = Color.yellow(); | |
} | |
const dataArea = data ? data.areaName.substring(0, 2).toUpperCase() : 'No Data'; | |
const localArea = dataLocal ? dataLocal.areaName : ""; | |
const areaString = dataLocal ? `${localArea} (${dataArea})` : dataArea; | |
const date = dataLocal ? dataLocal.date.split(",")[0] : data.update; | |
label = list.addText(areaString); | |
label.font = Font.lightSystemFont(12); | |
label.textColor = Color.gray(); | |
if (date) { | |
list.addSpacer(4); | |
// Fetch Time | |
const updatedLabel = list.addText(`Update: ${date}`); | |
updatedLabel.font = Font.regularSystemFont(8); | |
updatedLabel.textOpacity = 0.8; | |
updatedLabel.centerAlignText(); | |
} | |
// Enable caching | |
if (data?.shouldCache) { | |
list.refreshAfterDate = new Date(Date.now() + 60 * 60 * 1000); | |
} | |
return list; | |
} | |
getDateString(date) { | |
const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; | |
return date.toLocaleDateString('de-DE', options); | |
} | |
async getCoronaData() { | |
// Load new cases data | |
let data | |
try { | |
data = await new Request(this.coronaApiUrl).loadJSON(); | |
console.log(`getNewCasesData: ${JSON.stringify(data)}`); | |
} catch { | |
console.error('getCoronaData: request failed'); | |
return null; | |
} | |
// Check data results | |
if (!data || !data.casesPerWeek || !data.weekIncidence) { | |
console.error('getCoronaData: no data'); | |
return null; | |
} | |
const date = new Date(data.meta.lastUpdate); | |
// Return data | |
return { | |
cases: data.casesPerWeek, | |
incidence: data.weekIncidence.toFixed(0), | |
update: this.getDateString(date), | |
areaName: "Deutschland", | |
}; | |
} | |
async getIncidenceData() { | |
// Load incidence data | |
let data | |
try { | |
data = await new Request(this.incidenceUrlStates).loadJSON(); | |
//console.log(`getIncidenceData: ${JSON.stringify(data)}`); | |
} catch { | |
console.error('getIncidenceData: request failed'); | |
return null; | |
} | |
// Check data results | |
if (!data || !data.features || data.features.length == 0) { | |
console.error('getIncidenceData: no data'); | |
return null; | |
} | |
// Map data results | |
const incidencePerState = data.features.map( | |
(f) => f.attributes.cases7_bl_per_100k | |
); | |
// Calculate incidence average | |
const averageIncidence = | |
incidencePerState.reduce((a, b) => a + b) / incidencePerState.length; | |
const decimal = averageIncidence.toFixed(0).length > 2 ? 0 : 1; | |
// Return incidence | |
return { | |
value: averageIncidence.toFixed(decimal), | |
length: averageIncidence.toFixed(0).length, | |
areaName: "Deutschland", | |
shouldCache: true, | |
}; | |
} | |
async getIncidenceDataLocal() { | |
// Get location for local data | |
const location = await this.getLocation(); | |
// Load incidence data for location | |
let data | |
try { | |
data = await new Request(this.incidenceUrl(location)).loadJSON(); | |
//console.log(`getIncidenceDataLocal : ${JSON.stringify(data)}`); | |
} catch { | |
console.error('getIncidenceDataLocal: request failed'); | |
return null; | |
} | |
// check data results | |
if (!data || !data.features || data.features.length == 0) { | |
console.error('getIncidenceDataLocal: no data'); | |
return null; | |
} | |
// Get incidence from data | |
const attr = data.features[0].attributes; | |
const incidence = attr.cases7_per_100k; | |
const decimal = incidence.toFixed(0).length > 2 ? 0 : 1; | |
// Return local incidence | |
return { | |
value: incidence.toFixed(decimal), | |
length: incidence.toFixed(0).length, | |
areaName: attr.GEN, | |
date: attr.last_update, | |
shouldCache: true, | |
}; | |
} | |
async getLocation() { | |
try { | |
// Check if widget has parameters | |
if (args.widgetParameter) { | |
const fixedCoordinates = args.widgetParameter | |
.split(",") | |
.map(parseFloat); | |
return { | |
latitude: fixedCoordinates[0], | |
longitude: fixedCoordinates[1], | |
}; | |
} | |
// Get approximate device location | |
else { | |
Location.setAccuracyToThreeKilometers(); | |
let location = await Location.current(); | |
// Fall back to predefined geo data | |
if (!location || !location.latitude) { | |
location = GEO; | |
} | |
// Store location data in storage | |
this.storeLocation(location); | |
return location; | |
} | |
} catch (e) { | |
console.error(e); | |
// Load location data from storage | |
return this.readLocation(); | |
} | |
} | |
storeLocation(location) { | |
this.fileManager.writeString( | |
this.locationFileName, | |
JSON.stringify(location) | |
); | |
} | |
readLocation() { | |
if (this.fileManager.fileExists(this.locationFileName)) { | |
return JSON.parse(this.fileManager.readString(this.locationFileName)); | |
} else { | |
console.error('readLocation failed'); | |
return null; | |
} | |
} | |
} | |
await new IncidenceNewCasesWidget().run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Left side: modified version for Gran Canaria. Right side: this widget (modified with double incidence and update time).