Skip to content

Instantly share code, notes, and snippets.

@IchHabRecht
Created May 3, 2021 18:05
Show Gist options
  • Save IchHabRecht/4c990cfd1ee469579b535f9ebdd906f9 to your computer and use it in GitHub Desktop.
Save IchHabRecht/4c990cfd1ee469579b535f9ebdd906f9 to your computer and use it in GitHub Desktop.
iOS Scriptable Widget für aktuellen 7-Tage-Inzidenzwert + realer 7-Tage-Inzidenzwert der letzten 5 Tage
// Licence: Robert Koch-Institut (RKI), dl-de/by-2-0
// Thanks to @kevinkub (https://github.com/kevinkub), @rphl (https://github.com/rphl) and @tzschies (https://github.com/tzschies) for their inspiring work on this widget.
// See https://gist.github.com/kevinkub/46caebfebc7e26be63403a7f0587f664, https://gist.github.com/rphl/0491c5f9cb345bf831248732374c4ef5 and https://gist.github.com/tzschies/563fab70b37609bc8f2f630d566bcbc9.
class IncidenceWidget {
constructor() {
this.previousDaysToShow = 31
this.apiUrlDistricts = (location) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=RS,GEN,cases7_bl_per_100k,cases7_per_100k,BL,EWZ&geometry=${location.longitude.toFixed(3)},${location.latitude.toFixed(3)}&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json`
this.apiUrlDistrictsHistory = (districtId) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Covid19_RKI_Sums/FeatureServer/0//query?where=IdLandkreis%3D${districtId}&objectIds=&time=&resultType=none&outFields=AnzahlFall,SummeFall,Meldedatum&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields=Meldedatum+DESC&groupByFieldsForStatistics=&outStatistics=&having=&resultOffset=&resultRecordCount=12&sqlFormat=none&f=json`
this.stateToAbbr = {
'Baden-Württemberg': 'BW',
'Bayern': 'BY',
'Berlin': 'BE',
'Brandenburg': 'BB',
'Bremen': 'HB',
'Hamburg': 'HH',
'Hessen': 'HE',
'Mecklenburg-Vorpommern': 'MV',
'Niedersachsen': 'NI',
'Nordrhein-Westfalen': 'NRW',
'Rheinland-Pfalz': 'RP',
'Saarland': 'SL',
'Sachsen': 'SN',
'Sachsen-Anhalt': 'ST',
'Schleswig-Holstein': 'SH',
'Thüringen': 'TH'
}
this.incidenceColors = [
new Color('#dd0085', 1), // > 500
new Color('#671212', 1), // > 250
new Color('#951214', 1), // > 100
new Color('#d43624', 1), // > 50
new Color('#ffb534', 1), // > 25
new Color('#fff380', 1), // > 5
new Color('#fffccd', 1), // < 6
new Color('#adadad', 1), // NaN
];
}
async run() {
let widget = await this.createWidget()
if (!config.runsInWidget) {
await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
}
async createWidget(items) {
let data = await this.getData()
// Basic widget setup
let widget = new ListWidget()
widget.setPadding(0, 0, 0, 0)
let textStack = widget.addStack()
textStack.setPadding(14, 10, 14, 10)
textStack.layoutVertically()
// Header
let header = textStack.addText("🦠 7-Tage-Inzidenz".toUpperCase())
header.font = Font.mediumSystemFont(13)
textStack.addSpacer()
if (data.error) {
// Error handling
let loadingIndicator = textStack.addText(data.error.toUpperCase())
textStack.setPadding(14, 10, 14, 10)
loadingIndicator.font = Font.mediumSystemFont(13)
loadingIndicator.textOpacity = 0.5
let spacer = textStack.addStack()
spacer.addSpacer()
} else {
// Enable caching
widget.refreshAfterDate = new Date(Date.now() + 60601000)
// Main stack for value and area name
let incidenceStack = textStack.addStack()
incidenceStack.layoutVertically()
let valueStack = incidenceStack.addStack()
let incidenceValueLabel = valueStack.addText(data.incidence + data.trend)
incidenceValueLabel.font = Font.boldSystemFont(24)
incidenceValueLabel.textColor = this.getColorByIncidence(data.incidence)
// Chip for displaying state data
valueStack.addSpacer()
let stateStack = valueStack.addStack()
stateStack.backgroundColor = this.incidenceColors[7]
stateStack.cornerRadius = 4
stateStack.setPadding(2, 4, 2, 4)
let stateText = stateStack.addText(data.incidenceBySide + "\n" + data.areaNameBySide)
stateText.font = Font.mediumSystemFont(9)
stateText.textColor = Color.white()
stateText.centerAlignText()
stateText.lineLimit = 2
valueStack.addSpacer()
incidenceStack.addText(data.areaName)
textStack.addSpacer()
let historyStack = textStack.addStack()
historyStack.spacing = 4
for (var i = 0; i < 5; i++) {
var casesSum = data.timeline.features.slice(i + 1, i + 8).reduce((sum, feature) => sum + feature.attributes.AnzahlFall, 0)
var incidence = Math.round(casesSum / data.population * 100000)
var incidenceDate = new Date(data.timeline.features[i].attributes.Meldedatum)
var incidenceDay = incidenceDate.getDate()
var incidenceDayOfWeek = incidenceDate.getDay()
var incidenceRectStack = historyStack.addStack()
incidenceRectStack.backgroundColor = this.getColorByIncidence(incidence)
incidenceRectStack.cornerRadius = 4
incidenceRectStack.setPadding(2, 4, 2, 4)
var incidenceRectText = incidenceRectStack.addText(('00' + incidence).slice(Math.max(3, incidence.toString().length) * -1) + "\n" + ('0' + incidenceDay).slice(Math.max(2, incidenceDay.toString().length) * -1))
incidenceRectText.font = Font.mediumSystemFont(9)
incidenceRectText.textColor = incidenceDayOfWeek == 0 || incidenceDayOfWeek == 6
? Color.lightGray() : incidence > 50 ? Color.white() : Color.black()
incidenceRectText.centerAlignText()
incidenceRectText.lineLimit = 2
}
}
return widget
}
async getData() {
try {
let location = await this.getLocation()
if (location) {
let currentData = await new Request(this.apiUrlDistricts(location)).loadJSON()
let attr = currentData.features[0].attributes
let historicalData = await new Request(this.apiUrlDistrictsHistory(attr.RS)).loadJSON()
let casesToday7 = historicalData.features.slice(0, 7).reduce((sum, feature) => sum + feature.attributes.AnzahlFall, 0)
let casesYesterday7 = historicalData.features.slice(1, 8).reduce((sum, feature) => sum + feature.attributes.AnzahlFall, 0)
let trend = (casesToday7 == casesYesterday7) ? '→' : (casesToday7 > casesYesterday7) ? '↑' : '↓';
return {
incidence: attr.cases7_per_100k.toFixed(0),
areaName: attr.GEN,
trend: trend,
incidenceBySide: attr.cases7_bl_per_100k.toFixed(0),
areaNameBySide: this.stateToAbbr[attr.BL],
population: attr.EWZ,
timeline: historicalData
}
}
return {error: "Standort nicht verfügbar."}
} catch (e) {
return {error: "Fehler bei Datenabruf: " + e.message}
}
}
async getLocation() {
try {
if (args.widgetParameter) {
let fixedCoordinates = args.widgetParameter.split(",").map(parseFloat)
return {
latitude: fixedCoordinates[0],
longitude: fixedCoordinates[1]
}
} else {
Location.setAccuracyToThreeKilometers()
return await Location.current()
}
} catch (e) {
return null
}
}
getColorByIncidence(incidence) {
return incidence > 500
? this.incidenceColors[0]
: incidence > 250
? this.incidenceColors[1]
: incidence > 100
? this.incidenceColors[2]
: incidence > 50
? this.incidenceColors[3]
: incidence > 25
? this.incidenceColors[4]
: incidence > 5
? this.incidenceColors[5]
: incidence >= 0 ? this.incidenceColors[6] : this.incidenceColors[7]
}
getDateString(addDays) {
addDays = addDays || 0
return new Date(Date.now() + addDays * 24 * 60 * 60 * 1000).toISOString().substring(0, 10)
}
}
await new IncidenceWidget().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment