Skip to content

Instantly share code, notes, and snippets.

@barclay-reg
Forked from rphl/incidence.js
Last active November 3, 2020 14:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save barclay-reg/ff883a5f8a8fc99c14e19c841bcac2e0 to your computer and use it in GitHub Desktop.
Save barclay-reg/ff883a5f8a8fc99c14e19c841bcac2e0 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: deep-green; icon-glyph: dove;
// 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) {
let locations
if(args.widgetParameter) {
locations = args.widgetParameter.split(";").map(parseInput)
} else {
Location.setAccuracyToThreeKilometers()
locations = [await Location.current()]
}
const list = new ListWidget()
const header = list.addText("🦠 Inzidenz".toUpperCase())
header.font = Font.mediumSystemFont(13)
let lastData
const spaceFor3 = locations.length == 3
list.addSpacer()
for (location of locations) {
const data = await getDataForLocation(location)
const weekData = saveLoadData(data, data.areaName)
if (data) {
lastData = data
if (!data.shouldCache) {
list.addSpacer(6)
const loadingIndicator = list.addText("Ort wird ermittelt...".toUpperCase())
loadingIndicator.font = Font.mediumSystemFont(13)
loadingIndicator.textOpacity = 0.5
}
// INCIDENCE
const locationStack = list.addStack()
locationStack.layoutHorizontally()
locationStack.centerAlignContent()
locationStack.useDefaultPadding()
// 1
data.incidence = (data.incidence >= 100) ? parseInt(data.incidence) : data.incidence
const label = locationStack.addText(data.incidence + '' + getIncidenceTrend(data, weekData))
label.font = Font.boldSystemFont( spaceFor3 ? 20: 28)
label.textColor = data.incidence >= 50 ? Color.red() : data.incidence >= 35 ? Color.orange() : Color.green()
// 2
blStack = locationStack.addStack()
blStack.layoutVertically()
const labelBL = blStack.addText(data.incidenceBL + getIncidenceBLTrend(data, weekData))
labelBL.font = Font.mediumSystemFont(spaceFor3 ? 8 : 10)
labelBL.textColor = Color.gray()
labelBlName = blStack.addText(data.nameBL)
labelBlName.font = Font.mediumSystemFont(spaceFor3 ? 8 : 10)
labelBlName.textColor = Color.gray()
// CITYNAME
const areaLabel = list.addText(data.areaName.toUpperCase())
areaLabel.font = Font.mediumSystemFont(spaceFor3 ? 8 : 12)
areaLabel.leftAlignText()
}
}
if (lastData) {
list.addSpacer()
// UPDATED
const labelUpdated = list.addText(`${lastData.updated.substr(0, 10)} ( +${lastData.cases} )`)
labelUpdated.font = Font.systemFont(10)
labelUpdated.textColor = Color.gray()
if (lastData.shouldCache) {
list.refreshAfterDate = new Date(Date.now() + 60 * 60 * 1000)
}
} else {
list.addSpacer()
list.addText("Daten nicht verfügbar")
}
return list
}
async function getDataForLocation(location) {
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
let data = await new Request(apiUrl(location)).loadJSON()
const attr = data.features[0].attributes
log("Data: " + JSON.stringify(data))
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 = ' ';
const prevData = getDataForDate(weekdata);
if (prevData) {
incidenceTrend = (data.incidence < prevData.incidence) ? '↓' : '↑'
}
return incidenceTrend
}
function getIncidenceBLTrend(data, weekdata) {
let incidenceBLTrend = ' ';
const prevData = getDataForDate(weekdata);
if (prevData) {
incidenceBLTrend = (data.incidenceBL < prevData.incidenceBL) ? '↓' : '↑'
}
return incidenceBLTrend
}
function getDataForDate(weekdata, yesterday = true, datestr = '') {
let dateKey;
if (yesterday) {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
dateKey = `${yesterday.getDate()}.${yesterday.getMonth() + 1}.${yesterday.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)
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
}
function parseInput (input) {
const fixedCoordinates = input.split(",")
return {
latitude: parseFloat(fixedCoordinates[0]),
longitude: parseFloat(fixedCoordinates[1]),
name: fixedCoordinates.length >= 2 ? fixedCoordinates[2] : null,
}
}
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 {};
}
@barclay-reg
Copy link
Author

forked my most liked fork from @rphl

  • added support for more locations

  • you now can chose to either

    • use current location
    • or use up to three fixed locations
  • fixed locations must be given as input string, while configuring the widget on your screen

    • format is latitude,longitude;latitude,longitude;latitude,longitude

Examples:

97085381-60deaa00-161d-11eb-9ac0-c1ce7081e8f2
97085382-620fd700-161d-11eb-91bb-9a57483d7e28

@chimcen
Copy link

chimcen commented Oct 31, 2020

Wie oft sind denn die Updateintervalle? Morgens wenn ich das Handy aus dem Flugzeugmodus hole dauerts immer ewig bis das widget updatet. Das von rphl updatet immer gleich...

@barclay-reg
Copy link
Author

barclay-reg commented Nov 1, 2020

@chimcen das ist jetzt gefixed. Ich hatte den Refresh-Intervall etwas zu groß :)

@chimcen
Copy link

chimcen commented Nov 1, 2020

Klasse, funzt. Danke.
Wenn Fu jetzt die Trendpfeile wie im Script von rphl übernehmen könntest (↑,↓,→ und mit rot/grün) dann wärs perfekt 🤩

@barclay-reg
Copy link
Author

@chimcen das mache ich mal, aber momentan zeigt ja eh alles nach oben und rot :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment