Skip to content

Instantly share code, notes, and snippets.

@dasistrobert
Forked from rphl/incidence.js
Last active October 24, 2020 15:27
Show Gist options
  • Save dasistrobert/b3ac87e3a28885c20d4605d5550d61b8 to your computer and use it in GitHub Desktop.
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)
// 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