Skip to content

Instantly share code, notes, and snippets.

@rflrkn
Created October 28, 2020 09:03
Show Gist options
  • Save rflrkn/f8ec055954865f02be1212ab58366e08 to your computer and use it in GitHub Desktop.
Save rflrkn/f8ec055954865f02be1212ab58366e08 to your computer and use it in GitHub Desktop.
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: purple; icon-glyph: magic;
// Licence: Robert Koch-Institut (RKI), dl-de/by-2-0
/**
* MEDIUMWIDGET SUPPORT - Set Widgetparemeter to: NAME,51.123,6.7373
*/
const outputFields = 'GEN,cases,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 outputFieldsStates = 'Fallzahl,LAN_ew_GEN,cases7_bl_per_100k';
const apiUrlStates = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Coronaf%E4lle_in_den_Bundesl%E4ndern/FeatureServer/0/query?where=1%3D1&outFields=${outputFieldsStates}&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 LIMIT_RED = 50
const LIMIT_ORANGE = 35
const LIMIT_RED_COLOR = new Color('FF1A2A')
const LIMIT_ORANGE_COLOR = new Color('FFD800')
const LIMIT_GREEN_COLOR = new Color('00B32C')
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': 'NW',
'Rheinland-Pfalz': 'RP',
'Saarland': 'SL',
'Sachsen': 'SN',
'Sachsen-Anhalt': 'ST',
'Schleswig-Holstein': 'SH',
'Thüringen': 'TH'
};
let MEDIUMWIDGET = false
let fixedCoordinates = []
if (args.widgetParameter) {
fixedCoordinates = args.widgetParameter.split(",")
if (fixedCoordinates.length === 3) {
MEDIUMWIDGET = true
}
} else { // DEBUG MEDIUM WIDGET
// fixedCoordinates = ['DUS',51.2277,6.7734]
// MEDIUMWIDGET = true
}
let data = {}
let weekData = {}
const widget = await createWidget()
if (!config.runsInWidget) {
if (MEDIUMWIDGET) {
await widget.presentMedium()
} else {
await widget.presentSmall()
}
}
Script.setWidget(widget)
Script.complete()
async function createWidget(items) {
const _data = await getData()
const areaName = _data.areaName;
data[areaName] = _data
const list = new ListWidget()
const headerLabel = list.addStack()
headerLabel.useDefaultPadding()
headerLabel.centerAlignContent()
if (MEDIUMWIDGET) {
headerLabel.layoutHorizontally()
} else {
list.setPadding(10,15,10,10)
headerLabel.layoutVertically()
}
const header = headerLabel.addText("Corona-Info".toUpperCase())
header.font = Font.mediumSystemFont(12)
if (data && typeof data[areaName] !== 'undefined') {
weekData[areaName] = saveLoadData(data[areaName], areaName)
if (!data[areaName].shouldCache) {
list.addSpacer(6)
const loadingIndicator = list.addText("Ort wird ermittelt...".toUpperCase())
loadingIndicator.font = Font.mediumSystemFont(13)
loadingIndicator.textOpacity = 0.5
}
if (MEDIUMWIDGET && typeof data[areaName] !== 'undefined') {
headerLabel.addSpacer()
createGerDailyCasesLabel(headerLabel, data[areaName], -1)
}
list.addSpacer(16)
// INCIDENCE
const incidenceLabel = list.addStack()
if (MEDIUMWIDGET) {
incidenceLabel.size = new Size(300, 90)
}
incidenceLabel.layoutHorizontally()
incidenceLabel.useDefaultPadding()
incidenceLabel.topAlignContent()
createIncidenceLabelBlock(incidenceLabel, data[areaName], weekData[areaName])
const _dataF = await getData(true)
const areaNameF = _dataF.areaName;
data[areaNameF] = _dataF
if (MEDIUMWIDGET && typeof data[areaNameF] !== 'undefined') {
weekData[areaNameF] = saveLoadData(data[areaNameF], areaNameF)
incidenceLabel.addSpacer(10)
createIncidenceLabelBlock(incidenceLabel, data[areaNameF], weekData[areaNameF])
} else if (typeof data[areaName] !== 'undefined') {
list.addSpacer()
createGerDailyCasesLabel(list, data[areaName])
}
if (data[areaName].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(useFixedCoords = false) {
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,
cases: f.attributes.Fallzahl // ???
}})
const averageIncidence = incidencePerState.reduce((a, b) => a + b.incidence, 0) / incidencePerState.length
const location = await getLocation(useFixedCoords)
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,
areaCases: parseFloat(attr.cases.toFixed(1)),
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(useFixedCoords = false) {
try {
if (fixedCoordinates.length === 2) {
return {
latitude: parseFloat(fixedCoordinates[0]),
longitude: parseFloat(fixedCoordinates[1])
}
} else if (useFixedCoords && fixedCoordinates.length === 3) {
return {
latitude: parseFloat(fixedCoordinates[1]),
longitude: parseFloat(fixedCoordinates[2])
}
} else {
Location.setAccuracyToThreeKilometers()
return await Location.current()
}
} catch (e) {
return null;
}
}
// ABSOLUTE DAILY CASES GER LABEL
function createGerDailyCasesLabel(label, data, align = 0) {
const formatedCases = new Number(data.cases).toLocaleString('de-DE')
const labelUpdated = label.addText(`DE gesamt: +${formatedCases}↑\nAktualisiert: ${data.updated.substr(0, 10)}`)
if (align === -1) labelUpdated.rightAlignText()
labelUpdated.font = Font.systemFont(10)
labelUpdated.textColor = Color.gray()
}
function createIncidenceLabelBlock(labelBlock, data, weekData) {
const stack = labelBlock.addStack()
stack.layoutVertically()
stack.useDefaultPadding()
stack.topAlignContent()
// AREA CASES
const areaCasesLabel = stack.addText(getNewAreaCasesTrend(data, weekData))
areaCasesLabel.font = Font.systemFont(10)
areaCasesLabel.leftAlignText()
areaCasesLabel.textColor = Color.white()
// MAIN ROW WITH INCIDENCE
const stackMainRow = stack.addStack()
stackMainRow.useDefaultPadding()
stackMainRow.centerAlignContent()
stackMainRow.size = (MEDIUMWIDGET) ? new Size(145, 30) : new Size(135, 30)
// MAIN INCIDENCE
let incidence = data.incidence >= 1000 ? Math.floor(data.incidence) : data.incidence;
const incidenceLabel = stackMainRow.addText('' + incidence)
incidenceLabel.font = Font.boldSystemFont(27)
incidenceLabel.leftAlignText();
incidenceLabel.textColor = data.incidence >= LIMIT_RED ? LIMIT_RED_COLOR : data.incidence >= LIMIT_ORANGE ? LIMIT_ORANGE_COLOR : LIMIT_GREEN_COLOR
const incidenceTrend = getIncidenceTrend(data, weekData)
const incidenceLabelTrend = stackMainRow.addText(incidenceTrend)
incidenceLabelTrend.font = Font.boldSystemFont(27)
incidenceLabelTrend.leftAlignText();
incidenceLabelTrend.textColor = (incidenceTrend === '↑') ? LIMIT_RED_COLOR : LIMIT_GREEN_COLOR
stackMainRow.addSpacer(5)
// BL INCIDENCE
const incidenceBLStack = stackMainRow.addStack();
incidenceBLStack.addSpacer(1)
//incidenceBLStack.size = new Size(10,25)
incidenceBLStack.bottomAlignContent()
const incidenceBLLabel = incidenceBLStack.addText(data.incidenceBL + getIncidenceBLTrend(data, weekData) + "\n" + data.nameBL)
incidenceBLLabel.font = Font.mediumSystemFont(10)
incidenceBLLabel.textColor = new Color('eeeeee', 0.5)
stackMainRow.addSpacer()
const areanameLabel = stack.addText(data.areaName.toUpperCase())
areanameLabel.font = Font.mediumSystemFont(14)
areanameLabel.lineLimit = 2
}
function getIncidenceTrend(data, weekdata) {
let incidenceTrend = ' ';
if (typeof weekdata !== 'undefined' && Object.keys(weekdata).length > 0) {
const prevData = getDataForDate(weekdata);
if (prevData) {
incidenceTrend = (data.incidence < prevData.incidence) ? '↓' : '↑'
}
}
return incidenceTrend
}
function getNewAreaCasesTrend(data, weekdata) {
let newAreaCases = '';
if (typeof weekdata !== 'undefined' && Object.keys(weekdata).length > 0) {
const prevData = getDataForDate(weekdata);
if (prevData && typeof prevData.areaCases !== 'undefined') {
newAreaCases += (data.areaCases < prevData.areaCases) ?'-' : '+'
newAreaCases += Math.abs(data.areaCases - prevData.areaCases)
newAreaCases += (data.areaCases > prevData.areaCases) ?'↑' : '↓'
}
}
return newAreaCases
}
function getIncidenceBLTrend(data, weekdata) {
let incidenceBLTrend = ' ';
if (typeof weekdata !== 'undefined' && 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