-
-
Save dasistrobert/b3ac87e3a28885c20d4605d5550d61b8 to your computer and use it in GitHub Desktop.
COVID-19 Inzidenz-Widget für iOS innerhalb Deutschlands 🇩🇪 (Kreis/Stadt + Bundesland + Trend)
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
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: light-gray; icon-glyph: magic; | |
// Licence: Robert Koch-Institut (RKI), dl-de/by-2-0 | |
const outputFields = 'GEN,cases_per_100k,cases7_per_100k,cases7_bl_per_100k,last_update,BL'; | |
const apiUrl = (location) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=${outputFields}&geometry=${location.longitude.toFixed(3)}%2C${location.latitude.toFixed(3)}&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json` | |
const apiUrlStates = 'https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Coronaf%E4lle_in_den_Bundesl%E4ndern/FeatureServer/0/query?where=1%3D1&outFields=LAN_ew_GEN,cases7_bl_per_100k&returnGeometry=false&outSR=4326&f=json' | |
const apiUrlNewCases = 'https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?f=json&where=NeuerFall%20IN(1%2C%20-1)&returnGeometry=false&spatialRel=esriSpatialRelIntersects&outFields=*&outStatistics=%5B%7B%22statisticType%22%3A%22sum%22%2C%22onStatisticField%22%3A%22AnzahlFall%22%2C%22outStatisticFieldName%22%3A%22value%22%7D%5D&resultType=standard&cacheHint=true' | |
const BUNDESLAENDER_SHORT = { | |
'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' | |
}; | |
const widget = await createWidget() | |
if (!config.runsInWidget) { | |
await widget.presentSmall() | |
} | |
Script.setWidget(widget) | |
Script.complete() | |
async function createWidget(items) { | |
const data = await getData() | |
const list = new ListWidget() | |
const bgGradientGreen = new LinearGradient(); | |
const bgGradientOrange = new LinearGradient(); | |
const bgGradientRed = new LinearGradient(); | |
bgGradientGreen.locations = [0, 1]; | |
bgGradientGreen.colors = Device.isUsingDarkAppearance() ? [new Color('061700'), new Color('52c234')] : [new Color('56ab2f'), new Color('a8e063')]; | |
bgGradientOrange.locations = [0, 1]; | |
bgGradientOrange.colors = Device.isUsingDarkAppearance() ? [new Color('f12711'), new Color('f5af19')] : [new Color('FF512F'), new Color('F09819')]; | |
bgGradientRed.locations = [0, 1]; | |
bgGradientRed.colors = Device.isUsingDarkAppearance() ? [new Color('ED213A'), new Color('93291E')] : [new Color('FF416C'), new Color('FF4B2B')]; | |
if (data.incidence >= 50) { | |
list.backgroundGradient = bgGradientRed; | |
} | |
else { | |
if (data.incidence >= 35) { | |
list.backgroundGradient = bgGradientOrange; | |
} | |
else { | |
list.backgroundGradient = bgGradientGreen; | |
} | |
} | |
const header = list.addText("7-Tage-Inzidenz".toUpperCase()) | |
header.font = Font.mediumSystemFont(13) | |
header.textColor = Color.white() | |
if (data) { | |
const weekData = saveLoadData(data, data.areaName) | |
if (!data.shouldCache) { | |
list.addSpacer(6) | |
const loadingIndicator = list.addText("Ort wird ermittelt...".toUpperCase()) | |
loadingIndicator.font = Font.mediumSystemFont(13) | |
loadingIndicator.textOpacity = 0.5 | |
} | |
list.addSpacer() | |
// INCIDENCE | |
const incidenceLabel = list.addStack() | |
incidenceLabel.layoutHorizontally() | |
incidenceLabel.centerAlignContent() | |
incidenceLabel.useDefaultPadding() | |
// 1 | |
data.incidence = (data.incidence >= 100) ? parseInt(data.incidence) : data.incidence | |
const label = incidenceLabel.addText(data.incidence + '' + getIncidenceTrend(data, weekData)) | |
label.font = Font.boldSystemFont(26) | |
label.textColor = Color.white() | |
// 2 | |
const labelBL = incidenceLabel.addText(data.incidenceBL + getIncidenceBLTrend(data, weekData) + '\n' + data.nameBL) | |
labelBL.font = Font.mediumSystemFont(10) | |
labelBL.textColor = Color.white() | |
// CITYNAME | |
const areaLabel = list.addText(data.areaName.toUpperCase()) | |
areaLabel.font = Font.mediumSystemFont(14) | |
areaLabel.textColor = Color.white() | |
list.addSpacer() | |
// UPDATED | |
const formatedCases = new Number(data.cases).toLocaleString('de-DE') | |
const labelUpdated = list.addText(`${data.updated.substr(0, 10)} (+${formatedCases})`) | |
labelUpdated.font = Font.systemFont(10) | |
labelUpdated.textColor = Color.white() | |
if (data.shouldCache) { | |
list.refreshAfterDate = new Date(Date.now() + 60 * 60 * 1000) | |
} | |
} else { | |
list.addSpacer() | |
const errorLabel = list.addText("Daten nicht verfügbar. \nWidget öffnen für reload...") | |
errorLabel.font = Font.mediumSystemFont(12) | |
errorLabel.textColor = Color.gray() | |
} | |
return list | |
} | |
async function getData() { | |
try { | |
let dataCases = await new Request(apiUrlNewCases).loadJSON() | |
const cases = dataCases.features[0].attributes.value | |
let dataStates = await new Request(apiUrlStates).loadJSON() | |
const incidencePerState = dataStates.features.map((f) => { return { BL: BUNDESLAENDER_SHORT[f.attributes.LAN_ew_GEN], incidence: f.attributes.cases7_bl_per_100k } }) | |
const averageIncidence = incidencePerState.reduce((a, b) => a + b.incidence, 0) / incidencePerState.length | |
const location = await getLocation() | |
let data = await new Request(apiUrl(location)).loadJSON() | |
const attr = data.features[0].attributes | |
const res = { | |
incidence: parseFloat(attr.cases7_per_100k.toFixed(1)), | |
incidenceBL: parseFloat(attr.cases7_bl_per_100k.toFixed(1)), | |
areaName: attr.GEN, | |
nameBL: BUNDESLAENDER_SHORT[attr.BL], | |
shouldCache: true, | |
updated: attr.last_update, | |
incidencePerState: incidencePerState, | |
averageIncidence: parseFloat(averageIncidence.toFixed(1)), | |
cases: cases | |
} | |
return res | |
} catch (e) { | |
return null | |
} | |
} | |
async function getLocation() { | |
try { | |
if (args.widgetParameter) { | |
const fixedCoordinates = args.widgetParameter.split(",").map(parseFloat) | |
return { | |
latitude: fixedCoordinates[0], | |
longitude: fixedCoordinates[1] | |
} | |
} else { | |
Location.setAccuracyToThreeKilometers() | |
return await Location.current() | |
} | |
} catch (e) { | |
return null; | |
} | |
} | |
function getIncidenceTrend(data, weekdata) { | |
let incidenceTrend = ' '; | |
if (Object.keys(weekdata).length > 0) { | |
const prevData = getDataForDate(weekdata); | |
if (prevData) { | |
incidenceTrend = (data.incidence < prevData.incidence) ? '↓' : '↑' | |
} | |
} | |
return incidenceTrend | |
} | |
function getIncidenceBLTrend(data, weekdata) { | |
let incidenceBLTrend = ' '; | |
if (Object.keys(weekdata).length > 0) { | |
const prevData = getDataForDate(weekdata); | |
if (prevData) { | |
incidenceBLTrend = (data.incidenceBL < prevData.incidenceBL) ? '↓' : '↑' | |
} | |
} | |
return incidenceBLTrend | |
} | |
function getDataForDate(weekdata, yesterday = true, datestr = '') { | |
let dateKey; | |
let dayOffset = 1 | |
const today = new Date(); | |
const todayDateKey = `${today.getDate()}.${today.getMonth() + 1}.${today.getFullYear()}` | |
if (typeof weekdata[todayDateKey] === 'undefined') { | |
dayOffset = 2 | |
} | |
if (yesterday) { | |
today.setDate(today.getDate() - dayOffset); | |
dateKey = `${today.getDate()}.${today.getMonth() + 1}.${today.getFullYear()}` | |
} else { | |
dateKey = datestr; | |
} | |
if (typeof weekdata[dateKey] !== 'undefined') { | |
return weekdata[dateKey] | |
} | |
return false | |
} | |
// LIMIT TO 7 DAYS | |
function saveLoadData (newData, suffix = '') { | |
const updated = newData.updated.substr(0, 10); | |
const loadedData = loadData(suffix) | |
if (loadedData) { | |
loadedData[updated] = newData | |
const loadedDataKeys = Object.keys(loadedData); | |
const lastDaysKeys = loadedDataKeys.slice(Math.max(Object.keys(loadedData).length - 7, 0)) | |
let loadedDataLimited = {} | |
lastDaysKeys.forEach(key => { | |
loadedDataLimited[key] = loadedData[key] | |
}) | |
try { | |
let fm = FileManager.iCloud() | |
let path = fm.joinPath(fm.documentsDirectory(), 'covid19' + suffix + '.json') | |
fm.writeString(path, JSON.stringify(loadedDataLimited)) | |
console.log('iCloud: save') | |
} catch (e) { | |
let fm = FileManager.local() | |
let path = fm.joinPath(fm.documentsDirectory(), 'covid19' + suffix + '.json') | |
fm.writeString(path, JSON.stringify(loadedDataLimited)) | |
console.log('Local: save') | |
} | |
return loadedData | |
} | |
return {} | |
} | |
function loadData(suffix) { | |
try { | |
let fm = FileManager.iCloud() | |
let path = fm.joinPath(fm.documentsDirectory(), 'covid19' + suffix + '.json') | |
if (fm.fileExists(path)) { | |
let data = fm.readString(path) | |
console.log('iCloud: read') | |
return JSON.parse(data) | |
} | |
} catch (e) { | |
let fm = FileManager.local() | |
let path = fm.joinPath(fm.documentsDirectory(), 'covid19' + suffix + '.json') | |
if (fm.fileExists(path)) { | |
let data = fm.readString(path) | |
console.log('Local: read') | |
return JSON.parse(data) | |
} | |
} | |
return {}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment