Advertisement
Guest User

Untitled

a guest
Dec 24th, 2020
191
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Variables used by Scriptable.
  2. // These must be at the very top of the file. Do not edit.
  3. // icon-color: deep-purple; icon-glyph: calendar;
  4. /*
  5.  
  6. ~
  7.  
  8. Welcome to Weather Cal. Run this script to set up your widget.
  9.  
  10. Add or remove items from the widget in the layout section below.
  11.  
  12. You can duplicate this script to create multiple widgets. Make sure to change the name of the script each time.
  13.  
  14. Happy scripting!
  15.  
  16. ~
  17.  
  18. */
  19.  
  20. // Specify the layout of the widget items.
  21. const layout = `
  22.   row
  23.     column
  24.       covid
  25. `
  26.  
  27. /*
  28.  * CODE
  29.  * Be more careful editing this section.
  30.  * =====================================
  31.  */
  32.  
  33. // Names of Weather Cal elements.
  34. const codeFilename = "Weather Cal code"
  35. const gitHubUrl = "https://raw.githubusercontent.com/mzeryck/Weather-Cal/main/weather-cal-code.js"
  36.  
  37. // Determine if the user is using iCloud.
  38. let files = FileManager.local()
  39. const iCloudInUse = files.isFileStoredIniCloud(module.filename)
  40.  
  41. // If so, use an iCloud file manager.
  42. files = iCloudInUse ? FileManager.iCloud() : files
  43.  
  44. // Determine if the Weather Cal code exists and download if needed.
  45. const pathToCode = files.joinPath(files.documentsDirectory(), codeFilename + ".js")
  46. if (!files.fileExists(pathToCode)) {
  47.   const req = new Request(gitHubUrl)
  48.   const codeString = await req.loadString()
  49.   files.writeString(pathToCode, codeString)
  50. }
  51.  
  52. // Import the code.
  53. if (iCloudInUse) { await files.downloadFileFromiCloud(pathToCode) }
  54. const code = importModule(codeFilename)
  55.  
  56. const custom = {
  57.  
  58.   // Custom items and backgrounds can be added here.
  59. async covid(column) {
  60. // Variables used by Scriptable.
  61. // These must be at the very top of the file. Do not edit.
  62. // icon-color: green; icon-glyph: magic;
  63. // Licence: Robert Koch-Institut (RKI), dl-de/by-2-0
  64.  
  65. // Thanks to @rphl (https://github.com/rphl) and @tzschies (https://github.com/tzschies) for their inspiring work on this widget. See https://gist.github.com/rphl/0491c5f9cb345bf831248732374c4ef5 and https://gist.github.com/tzschies/563fab70b37609bc8f2f630d566bcbc9.
  66.  
  67. class IncidenceWidget {
  68.  
  69.   constructor() {
  70.     this.previousDaysToShow = 31;
  71.     this.apiUrlDistricts = (location) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=RS,GEN,cases7_bl_per_100k,cases7_per_100k,BL&geometry=${location.longitude.toFixed(3)}%2C${location.latitude.toFixed(3)}&geometryType=esriGeometryPoint&inSR=4326&spatialRel=esriSpatialRelWithin&returnGeometry=false&outSR=4326&f=json`
  72.     this.apiUrlDistrictsHistory = (districtId) => `https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_COVID19/FeatureServer/0/query?where=IdLandkreis%20%3D%20%27${districtId}%27%20AND%20Meldedatum%20%3E%3D%20TIMESTAMP%20%27${this.getDateString(-this.previousDaysToShow)}%2000%3A00%3A00%27%20AND%20Meldedatum%20%3C%3D%20TIMESTAMP%20%27${this.getDateString(1)}%2000%3A00%3A00%27&outFields=Landkreis,Meldedatum,AnzahlFall&outSR=4326&f=json`
  73.     this.stateToAbbr = {
  74.       'Baden-Württemberg': 'BW',
  75.       'Bayern': 'BY',
  76.       'Berlin': 'BE',
  77.       'Brandenburg': 'BB',
  78.       'Bremen': 'HB',
  79.       'Hamburg': 'HH',
  80.       'Hessen': 'HE',
  81.       'Mecklenburg-Vorpommern': 'MV',
  82.       'Niedersachsen': 'NI',
  83.       'Nordrhein-Westfalen': 'NRW',
  84.       'Rheinland-Pfalz': 'RP',
  85.       'Saarland': 'SL',
  86.       'Sachsen': 'SN',
  87.       'Sachsen-Anhalt': 'ST',
  88.       'Schleswig-Holstein': 'SH',
  89.       'Thüringen': 'TH'
  90.     };
  91.   }
  92.  
  93.   async run() {
  94.       let widget = await this.createWidget()
  95.       if (!config.runsInWidget) {
  96.       }
  97.   }
  98.  
  99.   async createWidget(items) {
  100.     let data = await this.getData()
  101.    
  102.     // Basic widget setup
  103.     let list = column.addStack()
  104. list.layoutVertically()
  105. list.cornerRadius = 20
  106. list.setPadding(10, 10, 10, 10)
  107.     list.setPadding(0, 0, 0, 0)
  108.     let textStack = list.addStack()
  109.     textStack.setPadding(14, 14, 0, 14)
  110.     textStack.layoutVertically()
  111.     textStack.topAlignContent()
  112.        
  113.     // Header
  114.     let header = textStack.addText("🦠 Inzidenz".toUpperCase())
  115.     header.font = Font.mediumSystemFont(13)
  116.     textStack.addSpacer()
  117.    
  118.     if(data.error) {
  119.       // Error handling
  120.       let loadingIndicator = textStack.addText(data.error.toUpperCase())
  121.       textStack.setPadding(14, 14, 14, 14)
  122.       loadingIndicator.font = Font.mediumSystemFont(13)
  123.       loadingIndicator.textOpacity = 0.5
  124.       let spacer = textStack.addStack()
  125.       spacer.addSpacer();
  126.     } else {
  127.       // Enable caching
  128.  
  129.       // Main stack for value and area name
  130.       let incidenceStack = textStack.addStack()
  131.       let valueStack = incidenceStack.addStack()
  132.       incidenceStack.layoutVertically()
  133.       let incidenceValueLabel = valueStack.addText(data.incidence + data.trend)
  134.       incidenceValueLabel.font = Font.boldSystemFont(24)
  135.       incidenceValueLabel.textColor = data.incidence >= 100 ? new Color("9e000a") : data.incidence >= 50 ? Color.red() : data.incidence >= 35 ? Color.yellow() : Color.green();
  136.       incidenceStack.addText(data.areaName)
  137.      
  138.       // Chip for displaying state data      
  139.       valueStack.addSpacer(4)
  140.       let stateStack = valueStack.addStack()
  141.       let stateText = stateStack.addText(data.incidenceBySide + "\n" + data.areaNameBySide)
  142.       stateStack.backgroundColor = new Color('888888', .5)
  143.       stateStack.cornerRadius = 4
  144.       stateStack.setPadding(2, 4, 2, 4)
  145.       stateText.font = Font.mediumSystemFont(9)
  146.       stateText.textColor = Color.white()
  147.       valueStack.addSpacer()
  148.      
  149.       // Chart
  150.       let chart = new LineChart(400, 120, data.timeline).configure((ctx, path) => {
  151.         ctx.opaque = false;
  152.         ctx.setFillColor(new Color("888888", .25));
  153.         ctx.addPath(path);
  154.         ctx.fillPath(path);
  155.       }).getImage();
  156.       let chartStack = list.addStack()
  157.       chartStack.setPadding(0, 0, 0, 0)
  158.       let img = chartStack.addImage(chart)
  159.       img.applyFittingContentMode()
  160.     }
  161.   }
  162.  
  163.   async getData() {
  164.      try {
  165.       let location = await this.getLocation()
  166.       if(location) {
  167.         let currentData = await new Request(this.apiUrlDistricts(location)).loadJSON()
  168.         let attr = currentData.features[0].attributes
  169.         let historicalData = await new Request(this.apiUrlDistrictsHistory(attr.RS)).loadJSON()
  170.         let aggregate = historicalData.features.map(f => f.attributes).reduce((dict, feature) => {
  171.           dict[feature["Meldedatum"]] = (dict[feature["Meldedatum"]]|0) + feature["AnzahlFall"];
  172.           return dict;
  173.         }, {});
  174.         let timeline = Object.keys(aggregate).sort().map(k => aggregate[k]);
  175.         let casesYesterday7 = timeline.slice(-8, -1).reduce(this.sum);
  176.         let casesToday7 = timeline.slice(-7).reduce(this.sum);
  177.         let trend = (casesToday7 == casesYesterday7) ? '→' : (casesToday7 > casesYesterday7) ? '↑' : '↓';
  178.         return {
  179.           incidence: attr.cases7_per_100k.toFixed(0),
  180.           areaName: attr.GEN,
  181.           trend: trend,
  182.           incidenceBySide:
  183.           attr.cases7_bl_per_100k.toFixed(0),
  184.           areaNameBySide:
  185.           this.stateToAbbr[attr.BL],
  186.           timeline: timeline
  187.         };
  188.       }
  189.       return { error: "Standort nicht verfügbar." }
  190.     } catch(e) {
  191.       return { error: "Fehler bei Datenabruf." };
  192.     }
  193.   }
  194.  
  195.   getDateString(addDays) {
  196.     addDays = addDays || 0;
  197.     return new Date(Date.now() + addDays * 24 * 60 * 60 * 1000).toISOString().substring(0, 10)
  198.   }
  199.  
  200.   async getLocation() {
  201.     try {
  202.       if(args.widgetParameter) {
  203.         let fixedCoordinates = args.widgetParameter.split(",").map(parseFloat)
  204.         return { latitude: fixedCoordinates[0], longitude: fixedCoordinates[1] }
  205.       } else {
  206.         Location.setAccuracyToThreeKilometers()
  207.         return await Location.current()
  208.       }
  209.     } catch(e) {
  210.       return null;
  211.     }
  212.   }
  213.  
  214.   sum(a, b) {
  215.     return a + b;
  216.   }
  217.  
  218. }
  219.  
  220. class LineChart {
  221.  
  222.   constructor(width, height, values) {
  223.     this.ctx = new DrawContext()
  224.     this.ctx.size = new Size(width, height)
  225.     this.values = values;
  226.   }
  227.  
  228.   _calculatePath() {
  229.     let maxValue = Math.max(...this.values);
  230.     let minValue = Math.min(...this.values);
  231.     let difference = maxValue - minValue;
  232.     let count = this.values.length;
  233.     let step = this.ctx.size.width / (count - 1);
  234.     let points = this.values.map((current, index, all) => {
  235.         let x = step*index
  236.         let y = this.ctx.size.height - (current - minValue) / difference * this.ctx.size.height;
  237.         return new Point(x, y)
  238.     });
  239.     return this._getSmoothPath(points);
  240.   }
  241.      
  242.   _getSmoothPath(points) {
  243.     let path = new Path()
  244.     path.move(new Point(0, this.ctx.size.height));
  245.     path.addLine(points[0]);
  246.     for(var i = 0; i < points.length-1; i ++) {
  247.       let xAvg = (points[i].x + points[i+1].x) / 2;
  248.       let yAvg = (points[i].y + points[i+1].y) / 2;
  249.       let avg = new Point(xAvg, yAvg);
  250.       let cp1 = new Point((xAvg + points[i].x) / 2, points[i].y);
  251.       let next = new Point(points[i+1].x, points[i+1].y);
  252.       let cp2 = new Point((xAvg + points[i+1].x) / 2, points[i+1].y);
  253.       path.addQuadCurve(avg, cp1);
  254.       path.addQuadCurve(next, cp2);
  255.     }
  256.     path.addLine(new Point(this.ctx.size.width, this.ctx.size.height))
  257.     path.closeSubpath()
  258.     return path;
  259.   }
  260.  
  261.   configure(fn) {
  262.     let path = this._calculatePath()
  263.     if(fn) {
  264.       fn(this.ctx, path);
  265.     } else {
  266.       this.ctx.addPath(path);
  267.       this.ctx.fillPath(path);
  268.     }
  269.     return this.ctx;
  270.   }
  271.  
  272. }
  273.  
  274. await new IncidenceWidget().run();
  275. },
  276. }
  277.  
  278. // Run the initial setup or settings menu.
  279. let preview
  280. if (config.runsInApp) {
  281.   preview = await code.runSetup(Script.name(), iCloudInUse, codeFilename, gitHubUrl)
  282.   if (!preview) return
  283. }
  284.  
  285. // Set up the widget.
  286. const widget = await code.createWidget(layout, Script.name(), iCloudInUse, custom)
  287. Script.setWidget(widget)
  288.  
  289. // If we're in app, display the preview.
  290. if (config.runsInApp) {
  291.   if (preview == "small") { widget.presentSmall() }
  292.   else if (preview == "medium") { widget.presentMedium() }
  293.   else { widget.presentLarge() }
  294. }
  295.  
  296. Script.complete()
  297.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement