Skip to content

Instantly share code, notes, and snippets.

@nielspetersen
Created June 10, 2021 21:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nielspetersen/82a3f252150fee2029928c5d0718f4c7 to your computer and use it in GitHub Desktop.
Save nielspetersen/82a3f252150fee2029928c5d0718f4c7 to your computer and use it in GitHub Desktop.
Corona widget
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: red; icon-glyph: briefcase-medical;
/**
* Licence: Robert Koch-Institut (RKI), dl-de/by-2-0
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* AUTHOR: https://github.com/rphl - https://github.com/rphl/corona-widget/
* ISSUES: https://github.com/rphl/corona-widget/issues
*
*/
// ============= ============= ============= ============= =================
// ÄNDERUNGEN HIER, WERDEN BEI AKTIVEN AUTOUPDATE ÜBERSCHRIEBEN
// ZUR KONFIGURATION SIEHE README!
// https://github.com/rphl/corona-widget#erweiterte-konfiguration
//
// ============= ============= ============= ============= =================
let CFG = {
theme: '', // '' = Automatic Ligh/Darkmode based on iOS. light = only lightmode is used, dark = only lightmode is used
showVaccineInMedium: true, // "show vaccine status based on RKI reports. MEDIUMWIDGET IS REQUIRED!
openUrl: false, //"https://experience.arcgis.com/experience/478220a4c454480e823b17327b2bf1d4", // open RKI dashboard on tap, set false to disable
graphShowValues: 'i', // 'i' = incidence OR 'c' = cases
graphShowDays: 21, // show days in graph
csvRvalueFields: ['Schätzer_7_Tage_R_Wert', 'Punktschätzer des 7-Tage-R Wertes', 'Schไtzer_7_Tage_R_Wert', 'Punktschไtzer des 7-Tage-R Wertes'], // try to find possible field (column) with rvalue, because rki is changing columnsnames and encoding randomly on each update
scriptRefreshInterval: 5400, // refresh after 1,5 hours (in seconds)
scriptSelfUpdate: false, // script updates itself,
disableLiveIncidence: false, // show old, static incidance. update ONLY ONCE A DAY on intial RKI import
debugIncidenceCalc: false // show all calculated incidencevalues on console
}
// ============= ============= ============= ============= =================
// HALT, STOP !!!
// NACHFOLGENDE ZEILEN NUR AUF EIGENE GEFAHR ÄNDERN !!!
// ============= ============= ============= ============= =================
const ENV = {
themes: {
light: {
mainBackgroundImageURL: '',
mainBackgroundColor: '#f0f0f0',
stackBackgroundColor: '#99999920',
stackBackgroundColorSmall: '#99999915',
stackBackgroundColorSmallTop: '#99999900',
areaIconBackgroundColor: '#99999930',
titleTextColor: '#222222',
titleRowTextColor: '#222222',
titleRowTextColor2: '#222222',
smallNameTextColor: '#777777',
dateTextColor: '#777777',
dateTextColor2: '#777777',
graphTextColor: '#888888',
incidenceColorsDarkdarkred: '#941100',
incidenceColorsDarkred: '#c01a00',
incidenceColorsRed: '#f92206',
incidenceColorsOrange: '#faa31b',
incidenceColorsYellow: '#ffff64',
incidenceColorsGreen: '#00cc00',
incidenceColorsGray: '#d0d0d0'
},
dark: {
mainBackgroundImageURL: '',
mainBackgroundColor: '#9999999',
stackBackgroundColor: '#99999920',
stackBackgroundColorSmall: '#99999910',
stackBackgroundColorSmallTop: '#99999900',
areaIconBackgroundColor: '#99999930',
titleTextColor: '#f0f0f0',
titleRowTextColor: '#f0f0f0',
titleRowTextColor2: '#f0f0f0',
smallNameTextColor: '#888888',
dateTextColor: '#777777',
dateTextColor2: '#777777',
graphTextColor: '#888888',
incidenceColorsDarkdarkred: '#941100',
incidenceColorsDarkred: '#c01a00',
incidenceColorsRed: '#f92206',
incidenceColorsOrange: '#faa31b',
incidenceColorsYellow: '#ffff64',
incidenceColorsGreen: '#00cc00',
incidenceColorsGray: '#d0d0d0'
}
},
incidenceColors: {
darkdarkred: { limit: 250, color: 'incidenceColorsDarkdarkred' },
darkred: { limit: 100, color: 'incidenceColorsDarkred' },
red: { limit: 50, color: 'incidenceColorsRed' },
orange: { limit: 35, color: 'incidenceColorsOrange' },
yellow: { limit: 25, color: 'incidenceColorsYellow' },
green: { limit: 1, color: 'incidenceColorsGreen' },
gray: { limit: 0, color: 'incidenceColorsGray' }
},
statesAbbr: {
'8': 'BW',
'9': 'BY',
'11': 'BE',
'12': 'BB',
'4': 'HB',
'2': 'HH',
'6': 'HE',
'13': 'MV',
'3': 'NI',
'5': 'NRW',
'7': 'RP',
'10': 'SL',
'14': 'SN',
'15': 'ST',
'1': 'SH',
'16': 'TH'
},
vaccineSatesAbbr: {
'8' : 'Baden-Württemberg',
'9' : 'Bayern',
'11' : 'Berlin',
'12' : 'Brandenburg',
'4' : 'Bremen',
'2' : 'Hamburg',
'6' : 'Hessen',
'13' : 'Mecklenburg-Vorpommern',
'3' : 'Niedersachsen',
'5' : 'Nordrhein-Westfalen',
'7' : 'Rheinland-Pfalz',
'10' : 'Saarland',
'14' : 'Sachsen',
'15' : 'Sachsen-Anhalt',
'1' : 'Schleswig-Holstein',
'16' : 'Thüringen'
},
areaIBZ: {
'40': 'KS',// Kreisfreie Stadt
'41': 'SK', // Stadtkreis
'42': 'K', // Kreis
'46': 'K', // Sonderverband offiziel Kreis
'43': 'LK', // Landkreis
'45': 'LK', // Sonderverband offiziel Landkreis
null: 'BZ',
'': 'BZ'
},
fonts: {
xlarge: Font.boldSystemFont(26),
large: Font.mediumSystemFont(20),
medium: Font.mediumSystemFont(14),
normal: Font.mediumSystemFont(12),
small: Font.boldSystemFont(11),
small2: Font.boldSystemFont(10),
xsmall: Font.boldSystemFont(9)
},
status: {
nogps: 555,
offline: 418,
notfound: 404,
error: 500,
ok: 200,
fromcache: 418
},
isMediumWidget: config.widgetFamily === 'medium',
isSameState: false,
cache: {},
staticCoordinates: [],
script: {
selfUpdate: CFG.scriptSelfUpdate,
filename: this.module.filename.replace(/^.*[\\\/]/, ''),
updateStatus: ''
}
}
class Theme {
static getCurrentTheme () {
let theme = 'auto';
if (CFG.theme === 'light' || CFG.theme === 'dark') {
theme = CFG.theme
}
return theme
}
static getColor(colorName, useDefault = false) {
let theme = Theme.getCurrentTheme();
if (theme === 'auto' && useDefault) {
theme = 'light';
}
if (theme === 'light' || theme === 'dark') {
return new Color(ENV.themes[theme][colorName])
}
return false // no color preferred
}
static setColor(object, propertyName, colorName, useDefault = false) {
if (CFG.theme === 'light' || CFG.theme === 'dark' || useDefault) {
let theme = Theme.getCurrentTheme();
object[propertyName] = new Color(ENV.themes[theme][colorName])
}
}
}
class IncidenceWidget {
constructor(coordinates = []) {
this.loadConfig();
if (args.widgetParameter) ENV.staticCoordinates = Parse.input(args.widgetParameter)
ENV.staticCoordinates = [...ENV.staticCoordinates, ...coordinates]
if (typeof ENV.staticCoordinates[1] !== 'undefined' && Object.keys(ENV.staticCoordinates[1]).length >= 3) ENV.isMediumWidget = true
Helper.log("Current Theme:", Theme.getCurrentTheme())
this.selfUpdate()
}
async init() {
this.widget = await this.createWidget()
this.widget.setPadding(0, 0, 0, 0)
if (Theme.getCurrentTheme() === 'light' && Theme.getCurrentTheme() === 'dark') {
const backgroundImageUrl = ENV.themes[Theme.getCurrentTheme()]['mainBackgroundImageURL']
if (backgroundImageUrl !== '') {
const i = await new Request(backgroundImageUrl);
const img = await i.loadImage();
this.widget.backgroundImage = img
}
}
Theme.setColor(this.widget, 'backgroundColor', 'mainBackgroundColor')
if (!config.runsInWidget) {
(ENV.isMediumWidget) ? await this.widget.presentMedium() : await this.widget.presentSmall()
}
Script.setWidget(this.widget)
Script.complete()
}
async createWidget() {
const list = new ListWidget()
const statusPos0 = await Data.load(0)
const statusPos1 = (ENV.isMediumWidget && typeof ENV.staticCoordinates[1] !== 'undefined') ? await Data.load(1) : false
// UI ===============
let topBar = new UI(list).stack('h', [4, 8, 4, 4])
topBar.text("🦠", Font.mediumSystemFont(22))
topBar.space(3)
if (statusPos0 === ENV.status.error || statusPos1 === ENV.status.error) {
topBar.space()
list.addSpacer()
let statusError = new UI(list).stack('v', [4, 6, 4, 6])
statusError.text('⚡️', ENV.fonts.medium)
statusError.text('Standortdaten konnten nicht geladen werden. \nKein Cache verfügbar. \n\nBitte später nochmal versuchen.', ENV.fonts.small, Theme.getColor('titleTextColor'))
list.addSpacer(4)
list.refreshAfterDate = new Date(Date.now() + ((CFG.scriptRefreshInterval / 2) * 1000))
return list
}
Helper.calcIncidence('s0')
Helper.calcIncidence(ENV.cache['s0'].meta.BL_ID)
Helper.calcIncidence('d')
ENV.isSameState = false;
if (statusPos0 === statusPos1) {
ENV.isSameState = (ENV.cache['s0'].meta.BL_ID === ENV.cache['s1'].meta.BL_ID)
}
if (statusPos1) Helper.calcIncidence('s1')
if (statusPos1 && !ENV.isSameState) Helper.calcIncidence(ENV.cache['s1'].meta.BL_ID)
let topRStack = new UI(topBar).stack('v', [0,0,0,0])
topRStack.text(Format.number(ENV.cache.d.meta.r, 2, 'n/v') + 'ᴿ', ENV.fonts.medium, Theme.getColor('titleTextColor'))
let updatedDate = Format.dateStr(ENV.cache.d.getDay().date);
let updatedTime = ('' + new Date().getHours()).padStart(2, '0') + ':' + ('' + new Date().getMinutes()).padStart(2, '0')
topRStack.text(updatedDate + ' ' +updatedTime, ENV.fonts.xsmall, Theme.getColor('dateTextColor', true))
topBar.space()
UIComp.statusBlock(topBar, statusPos0)
topBar.space(4)
if (ENV.isMediumWidget && !ENV.isSameState && statusPos1) {
topBar.space()
UIComp.smallIncidenceRow(topBar, 'd', 'stackBackgroundColorSmallTop')
}
UIComp.incidenceVaccineRows(list)
list.addSpacer(3)
let stateBar = new UI(list).stack('h', [0, 0, 0, 0])
stateBar.space(6)
let leftCacheID = ENV.cache['s0'].meta.BL_ID
if (ENV.isMediumWidget) { UIComp.smallIncidenceRow(stateBar, leftCacheID) } else { UIComp.smallIncidenceBlock(stateBar, leftCacheID) }
stateBar.space(4)
// DEFAULT IS GER... else STATE
let rightCacheID = (ENV.isMediumWidget && !ENV.isSameState && statusPos1) ? ENV.cache['s1'].meta.BL_ID : 'd'
if (ENV.isMediumWidget) { UIComp.smallIncidenceRow(stateBar, rightCacheID) } else { UIComp.smallIncidenceBlock(stateBar, rightCacheID) }
stateBar.space(6)
list.addSpacer(5)
// UI ===============
if (CFG.openUrl) list.url = CFG.openUrl
list.refreshAfterDate = new Date(Date.now() + (CFG.scriptRefreshInterval * 1000))
return list
}
async selfUpdate() {
if (!ENV.script.selfUpdate) return
Helper.log('script selfUpdate', 'running')
let url = 'https://raw.githubusercontent.com/rphl/corona-widget/master/incidence.js';
let request = new Request(url)
let filenameBak = ENV.script.filename.replace('.js', '.bak.js')
try {
let script = await request.loadString()
if (script !== '') {
if (cfm.fm.fileExists(filenameBak)) await cfm.fm.remove(filenameBak)
cfm.copy(ENV.script.filename, filenameBak)
script = script.replace("scriptSelfUpdate: false", "scriptSelfUpdate: true")
cfm.save(script, ENV.script.filename)
ENV.script.updateStatus = 'updated'
Helper.log('script selfUpdate', ENV.script.updateStatus);
}
} catch (e) {
console.warn(e)
if (cfm.fm.fileExists(filenameBak)) {
// await cfm.fm.copy(filenameBak, ENV.script.filename)
// await cfm.fm.remove(filenameBak)
ENV.script.updateStatus = 'loading failed, rollback?'
Helper.log('script selfUpdate', ENV.script.updateStatus);
}
}
}
async loadConfig () {
let path = cfm.fm.joinPath(cfm.configPath, 'config.json');
if (cfm.fm.fileExists(path)) {
Helper.log('Loading config.json (defaults will be overwritten)')
const cfg = await cfm.read('config')
if (typeof cfg.data.themes !== 'undefined' && typeof cfg.data.themes.dark !== 'undefined') {
ENV.themes.dark = Object.assign(ENV.themes.dark, cfg.data.themes.dark)
}
Object.keys(ENV.themes).forEach(theme => {
if (typeof cfg.data.themes !== 'undefined' && typeof cfg.data.themes[theme] !== 'undefined') {
Helper.log('Loading custom theme from config.json: ' + theme)
ENV.themes[theme] = Object.assign(ENV.themes[theme], cfg.data.themes[theme])
}
})
if (cfg.status === ENV.status.ok) CFG = Object.assign(CFG, cfg.data)
}
}
}
class UIComp {
static incidenceVaccineRows(view) {
let b = new UI(view).stack('v', [4, 6, 4, 6])
let bb = new UI(b).stack('v', false, Theme.getColor('stackBackgroundColor', true), 10)
let padding = [4, 6, 4, 4]
if (ENV.isMediumWidget) {
padding = [2, 8, 2, 8]
}
let bb2 = new UI(bb).stack('h', padding, Theme.getColor('stackBackgroundColor', true), 10)
UIComp.incidenceRow(bb2, 's0')
let bb3 = new UI(bb).stack('h', padding)
if (ENV.isMediumWidget && CFG.showVaccineInMedium && typeof ENV.cache.s1 === 'undefined' && typeof ENV.cache.vaccine !== 'undefined') {
UIComp.vaccineRow(bb3, 's0')
} else if (ENV.isMediumWidget && typeof ENV.cache.s1 !== 'undefined') {
UIComp.incidenceRow(bb3, 's1')
} else if (!ENV.isMediumWidget) {
bb3.space()
UIComp.areaIcon(bb3, ENV.cache['s0'].meta.IBZ)
bb3.space(3)
let areaName = ENV.cache['s0'].meta.GEN
if (typeof ENV.staticCoordinates[0] !== 'undefined' && ENV.staticCoordinates[0].name !== false) {
areaName = ENV.staticCoordinates[0].name
}
bb3.text(areaName.toUpperCase(), ENV.fonts.medium, Theme.getColor('titleRowTextColor'), 1, 0.9)
bb3.space(8) // center title if small widget
bb3.space()
}
}
static incidenceRow(view, cacheID) {
let b = new UI(view).stack('h', [2,0,0,0])
let ib = new UI(b).stack('h', [0,0,0,0], false, false, false, [72, 26])
ib.elem.centerAlignContent()
let incidence = ENV.cache[cacheID].getDay().incidence
let incidenceFormatted = Format.number(incidence, 1, 'n/v', 100)
let incidenceParts = incidenceFormatted.split(",")
ib.text(incidenceParts[0], Font.boldMonospacedSystemFont(26), UI.getIncidenceColor(incidence), 1, 1)
if (typeof incidenceParts[1] !== "undefined") {
ib.text(',' + incidenceParts[1], Font.boldMonospacedSystemFont(18), UI.getIncidenceColor(incidence), 1, 1)
}
let trendArrow = UI.getTrendArrow(ENV.cache[cacheID].getAvg(0), ENV.cache[cacheID].getAvg(1))
let trendColor = (trendArrow === '↑') ? Theme.getColor(ENV.incidenceColors.red.color, true) : (trendArrow === '↓') ? Theme.getColor(ENV.incidenceColors.green.color, true) : Theme.getColor(ENV.incidenceColors.gray.color, true)
ib.text(trendArrow, Font.boldRoundedSystemFont(18), trendColor, 1, 0.9)
if (ENV.isMediumWidget) {
b.space(5)
UIComp.areaIcon(b, ENV.cache[cacheID].meta.IBZ)
b.space(3)
let areaName = ENV.cache[cacheID].meta.GEN
let cacheIndex = parseInt(cacheID.replace('s', ''))
if (typeof ENV.staticCoordinates[cacheIndex] !== 'undefined' && ENV.staticCoordinates[cacheIndex].name !== false) {
areaName = ENV.staticCoordinates[cacheIndex].name
}
b.text(areaName.toUpperCase(), ENV.fonts.medium, Theme.getColor('titleRowTextColor'), 1, 1)
}
b.space()
let b2 = new UI(b).stack('v', [2, 0, 0, 0], false, false, false, [58, 30])
let graphImg
if (CFG.graphShowValues === 'i') {
graphImg = UI.generateIcidenceGraph(ENV.cache[cacheID], 58, 16, false).getImage()
} else {
graphImg = UI.generateGraph(ENV.cache[cacheID], 58, 16, false).getImage()
}
b2.image(graphImg)
let bb2 = new UI(b2).stack('h')
bb2.space()
bb2.text('+' + Format.number(ENV.cache[cacheID].getDay().cases), ENV.fonts.xsmall, Theme.getColor('graphTextColor', true), 1, 1)
bb2.space(0)
}
static vaccineRow (view, cacheID) {
let vaccineStateName = ENV.vaccineSatesAbbr[ENV.cache[cacheID].meta.BL_ID]
let b = new UI(view).stack('h', [4,0,4,0],)
b.elem.centerAlignContent()
b.space()
b.text("🧬 ", ENV.fonts.medium, false, 1, 0.9)
let name = (typeof ENV.cache[cacheID].meta.BL_ID !== 'undefined') ? ENV.statesAbbr[ENV.cache[cacheID].meta.BL_ID] : cacheID
let vaccinatedState = ENV.cache.vaccine.data.states[vaccineStateName].vaccinated / 1000000;
b.text(name + ": " + Format.number(vaccinatedState, 3) + '', ENV.fonts.medium, Theme.getColor('titleRowTextColor2'), 1, 0.9)
b.space(4)
let vaccinated = ENV.cache.vaccine.data.vaccinated / 1000000;
b.text("/ D: " + Format.number(vaccinated, 3) + '', ENV.fonts.medium, Theme.getColor('titleRowTextColor2'), 1, 0.9)
b.space(4)
let dateTS = new Date(ENV.cache.vaccine.meta.lastUpdate).getTime()
let date = Format.dateStr(dateTS)
date = date.replace('.2021', '');
b.text('(in Mio. / '+ date +')', ENV.fonts.xsmall, Theme.getColor('dateTextColor2', true), 1, 0.9)
b.space()
view.space()
}
static smallIncidenceBlock(view, cacheID, options = {}) {
let b = new UI(view).stack('v', false, Theme.getColor('stackBackgroundColorSmall', true), 12)
let b2 = new UI(b).stack('h', [4, 0, 0, 5])
b2.space()
let incidence = ENV.cache[cacheID].getDay().incidence
b2.text(Format.number(incidence, 1, 'n/v', 100), ENV.fonts.small2, UI.getIncidenceColor(incidence), 1, 1)
let trendArrow = UI.getTrendArrow(ENV.cache[cacheID].getAvg(0), ENV.cache[cacheID].getAvg(1))
let trendColor = (trendArrow === '↑') ? Theme.getColor(ENV.incidenceColors.red.color, true) : (trendArrow === '↓') ? Theme.getColor(ENV.incidenceColors.green.color, true) : Theme.getColor(ENV.incidenceColors.gray.color, true)
b2.text(trendArrow, ENV.fonts.small2, trendColor, 1, 1)
let name = (typeof ENV.cache[cacheID].meta.BL_ID !== 'undefined') ? ENV.statesAbbr[ENV.cache[cacheID].meta.BL_ID] : cacheID
b2.text(name.toUpperCase(), ENV.fonts.small2, Theme.getColor('smallNameTextColor', true), 1, 1)
let b3 = new UI(b).stack('h', [0, 0, 0, 5])
b3.space()
//let chartdata = [{ incidence: 0, value: 0 }, { incidence: 10, value: 10 }, { incidence: 20, value: 20 }, { incidence: 30, value: 30 }, { incidence: 40, value: 40 }, { incidence: 50, value: 50 }, { incidence: 70, value: 70 }, { incidence: 100, value: 100 }, { incidence: 60, value: 60 }, { incidence: 70, value: 70 }, { incidence: 39, value: 39 }, { incidence: 20, value: 25 }, { incidence: 10, value: 20 }, { incidence: 30, value: 30 }, { incidence: 0, value: 0 }, { incidence: 10, value: 10 }, { incidence: 20, value: 20 }, { incidence: 30, value: 30 }, { incidence: 60, value: 60 }, { incidence: 70, value: 70 }, { incidence: 39, value: 39 }, { incidence: 40, value: 40 }, { incidence: 50, value: 50 }, { incidence: 70, value: 70 }, { incidence: 100, value: 100 }, { incidence: 60, value: 60 }, { incidence: 70, value: 70 }, { incidence: 40, value: 40 }]
let graphImg
if (CFG.graphShowValues === 'i') {
graphImg = UI.generateIcidenceGraph(ENV.cache[cacheID], 58, 8, false).getImage()
} else {
graphImg = UI.generateGraph(ENV.cache[cacheID], 58, 8, false).getImage()
}
b3.image(graphImg, 0.9)
let b4 = new UI(b).stack('h', [0, 0, 1, 5])
b4.space()
b4.text('+' + Format.number(ENV.cache[cacheID].getDay().cases), ENV.fonts.xsmall, Theme.getColor('graphTextColor', true), 1, 0.9)
b.space(2)
}
static smallIncidenceRow(view, cacheID, bgColor = 'stackBackgroundColorSmall') {
let r = new UI(view).stack('h', false, Theme.getColor(bgColor, true), 12)
let b = new UI(r).stack('v')
let bb2 = new UI(b).stack('h', [2, 0, 0, 6])
bb2.space()
let incidence = ENV.cache[cacheID].getDay().incidence
bb2.text(Format.number(incidence, 1, 'n/v', 100), ENV.fonts.normal, UI.getIncidenceColor(incidence), 1 ,1)
let trendArrow = UI.getTrendArrow(ENV.cache[cacheID].getAvg(0), ENV.cache[cacheID].getAvg(1))
let trendColor = (trendArrow === '↑') ? Theme.getColor(ENV.incidenceColors.red.color, true) : (trendArrow === '↓') ? Theme.getColor(ENV.incidenceColors.green.color, true) : Theme.getColor(ENV.incidenceColors.gray.color, true)
bb2.text(trendArrow, ENV.fonts.normal, trendColor)
bb2.space(2)
let name = (typeof ENV.cache[cacheID].meta.BL_ID !== 'undefined') ? ENV.statesAbbr[ENV.cache[cacheID].meta.BL_ID] : cacheID
bb2.text(name.toUpperCase(), ENV.fonts.normal, Theme.getColor('smallNameTextColor', true))
let b3 = new UI(b).stack('h', [0, 0, 2, 6])
b3.space()
let b3Text = ' ';
if (CFG.showVaccineInMedium && ENV.cache.vaccine) {
let vaccineStateName = ENV.vaccineSatesAbbr[ENV.cache[cacheID].meta.BL_ID]
let vaccineQuote
if (typeof ENV.cache.vaccine.data.states[vaccineStateName] !== 'undefined') {
vaccineQuote = ENV.cache.vaccine.data.states[vaccineStateName].quote
} else {
vaccineQuote = ENV.cache.vaccine.data.quote
}
b3Text = '🧬 ' + Format.number(vaccineQuote, 2, 'n/v') +'%'
}
b3.text(b3Text, ENV.fonts.xsmall, Theme.getColor('graphTextColor', true), 1, 0.9)
let b2 = new UI(r).stack('v', false, false, false, false, [60, 30])
let b2b2 = new UI(b2).stack('h', [0, 0, 0, 6])
b2b2.space()
let graphImg
if (CFG.graphShowValues == 'i') {
graphImg = UI.generateIcidenceGraph(ENV.cache[cacheID], 58, 10, false).getImage()
} else {
graphImg = UI.generateGraph(ENV.cache[cacheID], 58, 10, false).getImage()
}
b2b2.image(graphImg, 0.9)
let b2b3 = new UI(b2).stack('h', [0, 0, 0, 0])
b2b3.space()
b2b3.text('+' + Format.number(ENV.cache[cacheID].getDay().cases), ENV.fonts.xsmall, Theme.getColor('graphTextColor', true), 1, 0.9)
r.space(6)
}
static areaIcon(view, ibzID) {
let b = new UI(view).stack('h', [1, 3, 1, 3], Theme.getColor('areaIconBackgroundColor', true), 2, 2)
b.text(ENV.areaIBZ[ibzID], ENV.fonts.xsmall, Theme.getColor('titleRowTextColor'), 1, 1)
}
static statusBlock(view, status) {
let icon
let iconText
switch (status) {
case ENV.status.offline:
icon = '⚡️'
iconText = 'Offline'
break;
case ENV.status.nogps:
icon = '📡'
iconText = 'GPS?'
break;
}
if (icon && iconText) {
let topStatusStack = new UI(view).stack('v')
topStatusStack.text(icon, ENV.fonts.small)
}
}
}
class UI {
constructor(view) {
if (view instanceof UI) {
this.view = this.elem = view.elem
} else {
this.view = this.elem = view
}
}
static generateGraph(data, width, height, alignLeft = true) {
let graphData = data.data.slice(Math.max(data.data.length - CFG.graphShowDays, 1));
let context = new DrawContext()
context.size = new Size(width, height)
context.opaque = false
context.respectScreenScale = true
let max = Math.max.apply(Math, graphData.map(function (o) { return o.cases; }))
max = (max <= 0) ? 10 : max;
let w = Math.max(2, Math.round((width - (graphData.length * 2)) / graphData.length))
let xOffset = (!alignLeft) ? (width - (graphData.length * (w + 1))) : 0
for (let i = 0; i < CFG.graphShowDays; i++) {
let item = graphData[i]
let value = parseFloat(item.cases)
if (value === -1 && i == 0) value = 10;
let h = Math.max(2, (Math.abs(value) / max) * height)
let x = xOffset + (w + 1) * i
let rect = new Rect(x, height - h, w, h)
context.setFillColor(UI.getIncidenceColor((item.cases >= 1) ? item.incidence : 0))
context.fillRect(rect)
}
return context
}
static generateIcidenceGraph(data, width, height, alignLeft = true) {
let graphData = data.data.slice(Math.max(data.data.length - CFG.graphShowDays, 1));
let context = new DrawContext()
context.size = new Size(width, height)
context.opaque = false
context.respectScreenScale = true
let max = Math.max.apply(Math, graphData.map(function (o) { return o.incidence; }))
let min = Math.min.apply(Math, graphData.map(function (o) { return o.incidence; })) / 1.2
max = (max <= 0) ? 10 : max - min;
let w = Math.max(2, Math.round((width - (graphData.length * 2)) / graphData.length))
let xOffset = (!alignLeft) ? (width - (graphData.length * (w + 1))) : 0
for (let i = 0; i < CFG.graphShowDays; i++) {
let item = graphData[i]
let value = parseFloat(item.incidence) - min
if (value === -1 && i == 0) value = 10;
let h = Math.max(2,(Math.abs(value) / max) * (height - 1))
let x = xOffset + (w + 1) * i
let rect = new Rect(x, height - h - 1, w, h)
context.setFillColor(UI.getIncidenceColor((item.cases >= 0) ? item.incidence : 0))
context.fillRect(rect)
}
return context
}
stack(type = 'h', padding = false, borderBgColor = false, radius = false, borderWidth = false, size = false) {
this.elem = this.view.addStack()
if (radius) this.elem.cornerRadius = radius
if (borderWidth !== false) {
this.elem.borderWidth = borderWidth
this.elem.borderColor = borderBgColor
} else if (borderBgColor) {
this.elem.backgroundColor = borderBgColor
}
if (padding) this.elem.setPadding(...padding)
if (size) this.elem.size = new Size(size[0], size[1])
if (type === 'h') { this.elem.layoutHorizontally() } else { this.elem.layoutVertically() }
this.elem.centerAlignContent()
return this
}
text(text, font = false, color = false, maxLines = 0, minScale = 0.9) {
let t = this.elem.addText(text)
if (color) t.textColor = (typeof color === 'string') ? new Color(color) : color
t.font = (font) ? font : ENV.fonts.normal
t.lineLimit = (maxLines > 0 && minScale < 1) ? maxLines + 1 : maxLines
t.minimumScaleFactor = minScale
return this
}
image(image, imageOpacity = 1.0) {
let i = this.elem.addImage(image)
i.resizable = false
i.imageOpacity = imageOpacity
}
space(size) {
this.elem.addSpacer(size)
return this
}
static getTrendUpArrow(now, prev) {
if (now < 0 && prev < 0) {
now = Math.abs(now)
prev = Math.abs(prev)
}
return (now < prev) ? '↗' : (now > prev) ? '↑' : '→'
}
static getTrendArrow(value1, value2) {
return (value1 < value2) ? '↓' : (value1 > value2) ? '↑' : '→'
}
static getTrendColor(value1, value2, altColorUp = null, altColorDown = null) {
let colorUp = (altColorUp) ? new Color(altColorUp) : Theme.getColor(ENV.incidenceColors.red.color, true)
let colorDown = (altColorDown) ? new Color(altColorDown) : Theme.getColor(ENV.incidenceColors.green.color, true)
return (value1 < value2) ? colorDown : (value1 > value2) ? colorUp : Theme.getColor(ENV.incidenceColors.gray.color, true)
}
static getIncidenceColor(incidence) {
let color = Theme.getColor(ENV.incidenceColors.green.color, true)
if (incidence > ENV.incidenceColors.darkdarkred.limit) {
color = Theme.getColor(ENV.incidenceColors.darkdarkred.color, true)
} else if (incidence >= ENV.incidenceColors.darkred.limit) {
color = Theme.getColor(ENV.incidenceColors.darkred.color, true)
} else if (incidence >= ENV.incidenceColors.red.limit) {
color = Theme.getColor(ENV.incidenceColors.red.color, true)
} else if (incidence >= ENV.incidenceColors.orange.limit) {
color = Theme.getColor(ENV.incidenceColors.orange.color, true)
} else if (incidence >= ENV.incidenceColors.yellow.limit) {
color = Theme.getColor(ENV.incidenceColors.yellow.color, true)
} else if (incidence === 0) {
color = Theme.getColor(ENV.incidenceColors.gray.color, true)
}
return color
}
}
class DataResponse {
constructor(data, status = ENV.status.ok) {
this.data = data
this.status = status
}
}
class CustomFilemanager {
constructor() {
try {
this.fm = FileManager.iCloud()
this.fm.documentsDirectory()
} catch (e) {
this.fm = FileManager.local()
}
this.configDirectory = 'coronaWidgetNext'
this.configPath = this.fm.joinPath(this.fm.documentsDirectory(), '/' + this.configDirectory)
if (!this.fm.isDirectory(this.configPath)) this.fm.createDirectory(this.configPath)
}
async copy(oldFilename, newFilename) {
let oldPath = this.fm.joinPath(this.configPath, oldFilename);
let newPath = this.fm.joinPath(this.configPath, newFilename);
this.fm.copy(oldPath, newPath)
}
async save(data, filename = '') {
let path
let dataStr
if (filename === '') {
path = this.fm.joinPath(this.configPath, 'coronaWidget_' + data.dataId + '.json');
dataStr = JSON.stringify(data);
} else {
path = this.fm.joinPath(this.fm.documentsDirectory(), filename);
dataStr = data;
}
this.fm.writeString(path, dataStr);
}
async read(filename) {
let path = this.fm.joinPath(this.configPath, filename + '.json');
let type = 'json'
if (filename.includes('.')) {
path = this.fm.joinPath(this.fm.documentsDirectory(), filename);
type = 'string'
}
if (this.fm.isFileStoredIniCloud(path) && !this.fm.isFileDownloaded(path)) await this.fm.downloadFileFromiCloud(path);
if (this.fm.fileExists(path)) {
try {
let resStr = await this.fm.readString(path)
let res = (type === 'json') ? JSON.parse(resStr) : resStr
return new DataResponse(res);
} catch (e) {
console.error(e)
return new DataResponse('', ENV.status.error);
}
}
return new DataResponse('', ENV.status.notfound);
}
}
class Data {
constructor(dataId, data = {}, meta = {}) {
this.dataId = dataId
this.data = data
this.meta = meta
}
getDay (dayOffset = 0) {
return (typeof this.data[this.data.length - 1 - dayOffset] !== 'undefined') ? this.data[this.data.length - 1 - dayOffset] : false;
}
getAvg (weekOffset = 0, ignoreToday = false) {
let casesData = [...this.data].reverse()
let skipToday = (ignoreToday) ? 1 : 0;
const offsetDays = 7
const weekData = casesData.slice((offsetDays * weekOffset) + skipToday, (offsetDays * weekOffset) + 7 + skipToday)
const avg = weekData.reduce((a, b) => a + b.incidence, 0) / offsetDays
// Helper.log(weekOffset, avg)
return avg
}
static completeHistory (data) {
const completeDataObj = {}
for(let i = 0; i <= CFG.graphShowDays + 8; i++) {
let lastDate = new Date()
let prevDate = lastDate.setDate(lastDate.getDate() - i);
completeDataObj[Format.dateStr(prevDate)] = { cases: 0, date: prevDate }
}
data.map((value) => {
let curDate = Format.dateStr(value.date)
completeDataObj[curDate].cases = value.cases
})
let completeData = Object.values(completeDataObj)
completeData.sort((a, b) => { return a.date - b.date; })
return completeData.reverse();
}
static async tryLoadFromCache(cacheID, useStaticCoordsIndex) {
const dataResponse = await cfm.read(cfm.configDirectory + '/coronaWidget_config.json')
if (dataResponse.status !== ENV.status.ok) return ENV.status.error
const cacheIDs = JSON.parse(dataResponse.data)
if (typeof cacheIDs[cacheID] === 'undefined') return ENV.status.error
const dataIds = cacheIDs[cacheID]
if (typeof dataIds['dataIndex' + useStaticCoordsIndex] !== 'undefined') {
const areaData = await cfm.read('coronaWidget_' + dataIds['dataIndex' + useStaticCoordsIndex])
if (!areaData.data.data) return ENV.status.error
const area = new Data(dataIds['dataIndex' + useStaticCoordsIndex], areaData.data.data, areaData.data.meta)
ENV.cache['s' + useStaticCoordsIndex] = area
const stateData = await cfm.read('coronaWidget_' + areaData.data.meta.BL_ID)
if (!stateData.data.data) return ENV.status.error
const state = new Data(areaData.data.meta.BL_ID, stateData.data.data, stateData.data.meta)
ENV.cache[areaData.data.meta.BL_ID] = state
const dData = await cfm.read('coronaWidget_d')
if (!dData.data.data) return ENV.status.error
const d = new Data('d', dData.data.data, dData.data.meta)
ENV.cache.d = d
const vaccineData = await cfm.read('coronaWidget_vaccine')
if (!vaccineData.data.data) return ENV.status.error
const vaccine = new Data('vaccine', vaccineData.data.data, vaccineData.data.meta)
ENV.cache.vaccine = vaccine
return ENV.status.ok
}
return ENV.status.error
}
static async load(useStaticCoordsIndex = false) {
if (typeof ENV.cache['s' + useStaticCoordsIndex] !== 'undefined') return true
let configId = btoa('cID' + JSON.stringify(ENV.staticCoordinates).replace(/[^a-zA-Z ]/g, ""))
const location = await Helper.getLocation(useStaticCoordsIndex)
if (!location) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.nogps : ENV.status.error
}
const locationData = await rkiRequest.locationData(location)
if (!locationData) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.fromcache : ENV.status.error
}
let areaCases = await rkiRequest.areaCases(locationData.RS)
if (!areaCases) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.fromcache : ENV.status.error
}
await Data.geoCache(configId, useStaticCoordsIndex, locationData.RS)
let areaData = new Data(locationData.RS)
areaData.data = areaCases
areaData.meta = locationData
await cfm.save(areaData)
ENV.cache['s' + useStaticCoordsIndex] = areaData
// STATE DATA
if (typeof ENV.cache[locationData.BL_ID] === 'undefined') {
let stateCases = await rkiRequest.stateCases(locationData.BL_ID)
if (!stateCases) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.fromcache : ENV.status.error
}
let stateData = new Data(locationData.BL_ID)
stateData.data = stateCases
stateData.meta = {
BL_ID: locationData.BL_ID,
BL: locationData.BL,
EWZ: locationData.EWZ_BL
}
await cfm.save(stateData)
ENV.cache[locationData.BL_ID] = stateData
}
// GER DATA
if (typeof ENV.cache.d === 'undefined') {
let dCases = await rkiRequest.dCases()
if (!dCases) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.fromcache : ENV.status.error
}
let dData = new Data('d')
dData.data = dCases
dData.meta = {
r: await rkiRequest.rvalue(),
EWZ: 83.02 * 1000000 // @TODO real number?
}
await cfm.save(dData)
ENV.cache.d = dData
}
if (typeof ENV.cache.vaccine === 'undefined') {
let vaccineValues = await rkiRequest.vaccinevalues()
if (!vaccineValues) {
const status = await Data.tryLoadFromCache(configId, useStaticCoordsIndex)
return (status === ENV.status.ok) ? ENV.status.fromcache : ENV.status.error
}
let vaccineData = new Data('vaccine')
vaccineData.data = vaccineValues
vaccineData.meta.lastUpdate = vaccineValues.lastUpdate
await cfm.save(vaccineData)
ENV.cache.vaccine = vaccineData
}
if (typeof ENV.cache['s' + useStaticCoordsIndex] !== 'undefined' && typeof ENV.cache[locationData.BL_ID] !== 'undefined' && typeof ENV.cache.d !== 'undefined') {
return ENV.status.ok
}
return ENV.status.error
}
static async geoCache(configId, dataIndex, rsid) {
let data = {}
let dataResponse = await cfm.read(cfm.configDirectory + '/coronaWidget_config.json')
if (dataResponse.status === ENV.status.ok) data = JSON.parse(dataResponse.data)
if (typeof data[configId] === 'undefined') data[configId] = {}
data[configId]['dataIndex' + dataIndex] = rsid
await cfm.save(JSON.stringify(data), cfm.configDirectory + '/coronaWidget_config.json')
}
}
class Format {
static dateStr(timestamp) {
let date = new Date(timestamp)
return `${('' + date.getDate()).padStart(2, '0')}.${('' + (date.getMonth() + 1)).padStart(2, '0')}.${date.getFullYear()}`
}
static number(number, fractionDigits = 0, placeholder = null, limit = false) {
if (!!placeholder && number === 0) return placeholder
if (limit !== false && Math.round(number) >= limit) fractionDigits = 0
return Number(number).toLocaleString('de-DE', { maximumFractionDigits: fractionDigits, minimumFractionDigits: fractionDigits })
}
static timestamp(dateStr) {
const regex = /([\d]+)\.([\d]+)\.([\d]+),\ ([0-2]?[0-9]):([0-5][0-9])/g;
let m = regex.exec(dateStr)
return new Date(m[3], m[2] - 1, m[1], m[4], m[5]).getTime()
}
static rValue(data) {
const parsedData = Parse.rCSV(data)
let r = 0
if (parsedData.length === 0) return r
let availeRvalueField
Object.keys(parsedData[0]).forEach(key => {
CFG.csvRvalueFields.forEach(possibleRKey => {
if (key === possibleRKey) availeRvalueField = possibleRKey;
})
});
let firstDatefield = Object.keys(parsedData[0])[0];
if (availeRvalueField) {
parsedData.forEach(item => {
if (item[firstDatefield].includes('.') && typeof item[availeRvalueField] !== 'undefined' && parseFloat(item[availeRvalueField].replace(',', '.')) > 0) {
r = item;
}
})
}
return (r) ? parseFloat(r[availeRvalueField].replace(',', '.')) : r
}
}
class RkiRequest {
async locationData(location) {
const outputFields = 'GEN,RS,EWZ,EWZ_BL,BL_ID,cases,cases_per_100k,cases7_per_100k,cases7_bl_per_100k,last_update,BL,IBZ';
const url = `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 response = await this.exec(url)
return (response.status === ENV.status.ok) ? response.data.features[0].attributes : false
}
async areaCases(areaID) {
const apiStartDate = Helper.getDateBefore(CFG.graphShowDays + 7)
const newCasesTodayUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?f=json&where=NeuerFall%20IN(1,-1)%20AND%20IdLandkreis%3D${areaID}&objectIds&time&resultType=standard&outFields&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields&groupByFieldsForStatistics&outStatistics=%5B%7B%22statisticType%22:%22sum%22,%22onStatisticField%22:%22AnzahlFall%22,%22outStatisticFieldName%22:%22cases%22%7D,%20%7B%22statisticType%22:%22max%22,%22onStatisticField%22:%22MeldeDatum%22,%22outStatisticFieldName%22:%22date%22%7D%5D&having&resultOffset&resultRecordCount&sqlFormat=none&token`
const newCasesHistoryUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=NeuerFall+IN%281%2C0%29+AND+IdLandkreis=${areaID}+AND+MeldeDatum+%3E%3D+TIMESTAMP+%27${apiStartDate}%27&objectIds=&time=&resultType=standard&outFields=AnzahlFall%2CMeldeDatum&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields=MeldeDatum&groupByFieldsForStatistics=MeldeDatum&outStatistics=%5B%7B%22statisticType%22%3A%22sum%22%2C%22onStatisticField%22%3A%22AnzahlFall%22%2C%22outStatisticFieldName%22%3A%22cases%22%7D%5D%0D%0A&having=&resultOffset=&resultRecordCount=&sqlFormat=none&f=pjson&token=`
return await this.getCases(newCasesTodayUrl, newCasesHistoryUrl)
}
async stateCases(blID) {
const apiStartDate = Helper.getDateBefore(CFG.graphShowDays + 7)
const newCasesTodayUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?f=json&where=NeuerFall%20IN(1,%20-1)%20AND%20IdBundesland%3D${blID}&objectIds&time&resultType=standard&outFields&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields&groupByFieldsForStatistics&outStatistics=%5B%7B%22statisticType%22:%22sum%22,%22onStatisticField%22:%22AnzahlFall%22,%22outStatisticFieldName%22:%22cases%22%7D,%20%7B%22statisticType%22:%22max%22,%22onStatisticField%22:%22MeldeDatum%22,%22outStatisticFieldName%22:%22date%22%7D%5D&having&resultOffset&resultRecordCount&sqlFormat=none&token`
const newCasesHistoryUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=NeuerFall+IN%281%2C0%29+AND+IdBundesland=${blID}+AND+MeldeDatum+%3E%3D+TIMESTAMP+%27${apiStartDate}%27&objectIds=&time=&resultType=standard&outFields=AnzahlFall%2CMeldeDatum&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields=MeldeDatum&groupByFieldsForStatistics=MeldeDatum&outStatistics=%5B%7B%22statisticType%22%3A%22sum%22%2C%22onStatisticField%22%3A%22AnzahlFall%22%2C%22outStatisticFieldName%22%3A%22cases%22%7D%5D%0D%0A&having=&resultOffset=&resultRecordCount=&sqlFormat=none&f=pjson&token=`
return await this.getCases(newCasesTodayUrl, newCasesHistoryUrl)
}
async dCases() {
const apiStartDate = Helper.getDateBefore(CFG.graphShowDays + 7)
const newCasesTodayUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?f=json&where=NeuerFall%20IN(1,%20-1)&returnGeometry=false&geometry=42.000,12.000&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&outFields=*&outStatistics=%5B%7B%22statisticType%22:%22sum%22,%22onStatisticField%22:%22AnzahlFall%22,%22outStatisticFieldName%22:%22cases%22%7D,%20%7B%22statisticType%22:%22max%22,%22onStatisticField%22:%22MeldeDatum%22,%22outStatisticFieldName%22:%22date%22%7D%5D&resultType=standard&cacheHint=true`
const newCasesHistoryUrl = `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=NeuerFall+IN%281%2C0%29+AND+MeldeDatum+%3E%3D+TIMESTAMP+%27${apiStartDate}%27&objectIds=&time=&resultType=standard&outFields=AnzahlFall%2CMeldeDatum&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnDistinctValues=false&cacheHint=false&orderByFields=MeldeDatum&groupByFieldsForStatistics=MeldeDatum&outStatistics=%5B%7B%22statisticType%22%3A%22sum%22%2C%22onStatisticField%22%3A%22AnzahlFall%22%2C%22outStatisticFieldName%22%3A%22cases%22%7D%5D%0D%0A&having=&resultOffset=&resultRecordCount=&sqlFormat=none&f=pjson&token=`
return await this.getCases(newCasesTodayUrl, newCasesHistoryUrl)
}
async rvalue() {
const url = `https://www.rki.de/DE/Content/InfAZ/N/Neuartiges_Coronavirus/Projekte_RKI/Nowcasting_Zahlen_csv.csv?__blob=publicationFile`
const response = await this.exec(url, false)
return (response.status === ENV.status.ok) ? Format.rValue(response.data) : false
}
async vaccinevalues () {
const url = `https://rki-vaccination-data.vercel.app/api`
const response = await this.exec(url)
return (response.status === ENV.status.ok) ? response.data : false
}
async getCases(urlToday, urlHistory) {
const responseToday = await this.exec(urlToday)
const responseHistory = await this.exec(urlHistory)
if (responseToday.status === ENV.status.ok && responseHistory.status === ENV.status.ok) {
let data = responseHistory.data.features.map(day => { return { cases: day.attributes.cases, date: day.attributes.MeldeDatum } })
let todayCases = responseToday.data.features.reduce((a, b) => a + b.attributes.cases, 0)
let lastDateHistory = Math.max(...responseHistory.data.features.map(a => a.attributes.MeldeDatum))
let lastDateToday = Math.max(...responseToday.data.features.map(a => a.attributes.date))
let lastDate = lastDateHistory;
if (!!lastDateToday || new Date(lastDateToday).setHours(0, 0, 0, 0) <= new Date(lastDateHistory).setHours(0, 0, 0, 0)) {
let lastReportDate = new Date(lastDateHistory)
lastDate = lastReportDate.setDate(lastReportDate.getDate() + 1);
}
data.push({ cases: todayCases, date: lastDate })
data = Data.completeHistory(data)
return data;
}
return false;
}
async exec(url, isJson = true) {
try {
const resData = new Request(url)
resData.timeoutInterval = 60
let data = {}
let status = ENV.status.ok
if (isJson) {
data = await resData.loadJSON()
} else {
data = await resData.loadString()
}
status = this.checkStatus(data, isJson)
return new DataResponse(data, status)
} catch (e) {
console.warn(e)
return new DataResponse({}, ENV.status.notfound)
}
}
checkStatus (data, isJson) {
if (typeof data.length === '') return ENV.status.notfound
if (isJson && typeof data.error !== 'undefined') return ENV.status.notfound
return ENV.status.ok
}
}
class Parse {
static input(input) {
const _coords = []
const _staticCoordinates = input.split(";").map(coords => {
return coords.split(',')
})
_staticCoordinates.forEach(coords => {
_coords[parseInt(coords[0])] = {
index: parseInt(coords[0]),
latitude: parseFloat(coords[1]),
longitude: parseFloat(coords[2]),
name: (coords[3]) ? coords[3] : false
}
})
return _coords
}
static rCSV(rDataStr) {
let lines = rDataStr.split(/(?:\r\n|\n)+/).filter(function (el) { return el.length != 0 })
let headers = lines.splice(0, 1)[0].split(";");
let elements = []
for (let i = 0; i < lines.length; i++) {
let element = {};
let j = 0;
let values = lines[i].split(';')
element = values.reduce(function (result, field, index) {
result[headers[index]] = field;
return result;
}, {})
elements.push(element)
}
return elements
}
}
class Helper {
static getIncidenceLimits(incidence) {
if (incidence >= ENV.incidenceColors.green.limit && incidence < ENV.incidenceColors.yellow.limit) {
return { min: ENV.incidenceColors.green.limit, max: ENV.incidenceColors.yellow.limit }
} else if (incidence >= ENV.incidenceColors.yellow.limit && incidence < ENV.incidenceColors.orange.limit) {
return { min: ENV.incidenceColors.red.limit, max: ENV.incidenceColors.darkred.limit }
} else if (incidence >= ENV.incidenceColors.orange.limit && incidence < ENV.incidenceColors.red.limit) {
return { min: ENV.incidenceColors.red.limit, max: ENV.incidenceColors.darkred.limit }
} else if (incidence >= ENV.incidenceColors.red.limit && incidence < ENV.incidenceColors.darkred.limit) {
return { min: ENV.incidenceColors.red.limit, max: ENV.incidenceColors.darkred.limit }
} else if (incidence >= ENV.incidenceColors.darkred.limit && incidence < ENV.incidenceColors.darkdarkred.limit) {
return { min: ENV.incidenceColors.darkred.limit, max: ENV.incidenceColors.darkdarkred.limit }
} else if (incidence > ENV.incidenceColors.darkdarkred.limit) {
return { min: ENV.incidenceColors.darkdarkred.limit, max: 500 }
}
return { min: 0, max: 0 }
}
static calcIncidence(cacheID) {
const casesData = [...ENV.cache[cacheID].data]
if (CFG.debugIncidenceCalc) Helper.log('calcIncidence', cacheID)
for(let i = 0; i < CFG.graphShowDays; i++) {
let theDays = casesData.slice(i + 1, i + 1 + 7) // without today
let sumCasesLast7Days = theDays.reduce((a, b) => a + b.cases, 0)
casesData[i].incidence = (sumCasesLast7Days / ENV.cache[cacheID].meta.EWZ) * 100000
if (CFG.debugIncidenceCalc) Helper.log(Format.dateStr(casesData[i].date), casesData[i].cases, casesData[i].incidence)
}
// @TODO Workaround use incidence from api
if (CFG.disableLiveIncidence && typeof ENV.cache[cacheID].meta.cases7_per_100k !== 'undefined') {
casesData[0].incidence = ENV.cache[cacheID].meta.cases7_per_100k
}
ENV.cache[cacheID].data = casesData.reverse()
}
static getDateBefore(days) {
let offsetDate = new Date()
offsetDate.setDate(new Date().getDate() - days)
return offsetDate.toISOString().split('T').shift()
}
static async getLocation(staticCoordinateIndex = false) {
if (typeof ENV.staticCoordinates[staticCoordinateIndex] !== 'undefined' && Object.keys(ENV.staticCoordinates[staticCoordinateIndex]).length >= 3) {
return ENV.staticCoordinates[staticCoordinateIndex]
}
try {
Location.setAccuracyToThreeKilometers()
return await Location.current()
} catch (e) {
console.warn(e)
}
return null;
}
static log(...data) {
console.log(data.map(JSON.stringify).join(' | '))
}
}
const cfm = new CustomFilemanager()
const rkiRequest = new RkiRequest()
await new IncidenceWidget().init()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment