Browse Source

add aggregate.html

master
Norwin Roosen 2 weeks ago
parent
commit
46e59006bd
2 changed files with 146 additions and 6 deletions
  1. 145
    0
      app/aggregate.html
  2. 1
    6
      app/assets/scripts/views/app.js

+ 145
- 0
app/aggregate.html View File

@@ -0,0 +1,145 @@
1
+<!doctype html>
2
+  <head>
3
+    <meta charset="utf-8" />
4
+    <title>openSenseMap Dataview</title>
5
+    <style>
6
+      body { font-family: sans; }
7
+      th, td { padding: 10px; text-align: right; }
8
+      tr:nth-child(odd) { background: #eee; }
9
+    </style>
10
+  </head>
11
+
12
+  <body>
13
+    <h1 id="title">loading ...</h1>
14
+    <h4 id="subtitle"></h4>
15
+
16
+    <div>
17
+      <button onclick="loadConfig('&phenomenon=Niederschlag&operation=sum')">Niederschlag</button>
18
+      <button onclick="loadConfig('&phenomenon=Temperatur&operation=arithmeticMean')">ø Temperatur</button>
19
+      <button onclick="loadConfig('&phenomenon=Temperatur&operation=max')">max. Temperatur</button>
20
+      <button onclick="loadConfig('&phenomenon=Temperatur&operation=min')">min. Temperatur</button>
21
+      <button onclick="loadConfig('&phenomenon=PM2.5&operation=max')">max. Feinstaub</button>
22
+    </div>
23
+    <div>
24
+      <button onclick="loadConfig('&windowSize=30 days', reset=false)">Monat</button>
25
+      <button onclick="loadConfig('&windowSize=1 week', reset=false)">Woche</button>
26
+      <button onclick="loadConfig('&windowSize=1 day', reset=false)">Tag</button>
27
+      <button onclick="loadConfig('&windowSize=1 hour', reset=false)">Stunde</button>
28
+    </div>
29
+    <br/>
30
+
31
+    <div id="status"></div>
32
+
33
+    <table id="datatable"></table>
34
+
35
+
36
+    <script>
37
+      const defaultConfig = {
38
+        boxId: '5b26181b1fef04001b69093c',
39
+        phenomenon: 'Niederschlag',
40
+        operation: 'sum',
41
+        start: new Date('2019-04-01').toISOString(),
42
+        end:   new Date().toISOString(),
43
+        windowSize: '30 days',
44
+      }
45
+
46
+      const config = Object.assign(defaultConfig, parseHash())
47
+      main(config).catch(console.error)
48
+
49
+
50
+      async function main (config) {
51
+        try {
52
+          const data = await fetchOsemDataAggregate(config)
53
+
54
+          const table = document.querySelector('#datatable')
55
+          populateTable(table, data, config)
56
+
57
+          const title = `${data['boxName']} - ${data['phenomenon']}`
58
+          document.querySelector('#title').innerText = title
59
+          document.title = title
60
+          const subtitle = `${config.operation} per ${config.windowSize}`
61
+          document.querySelector('#subtitle').innerText = subtitle
62
+        } catch (err) {
63
+          document.querySelector('#status').innerText = err
64
+        }
65
+      }
66
+
67
+      function parseHash () {
68
+        const kvPairs = window.location.search
69
+          // remove leading ?
70
+          .slice(1)
71
+          // create objects from kvpairs
72
+          .split('&')
73
+          .map(kv => {
74
+            [k,v] = kv.split('=')
75
+            return { [k]: decodeURIComponent(v) }
76
+          })
77
+
78
+        return Object.assign.apply(null, kvPairs) // combine objects
79
+      }
80
+
81
+      async function fetchOsemDataAggregate (config) {
82
+        const {
83
+          boxId,
84
+          phenomenon,
85
+          operation,
86
+          start,
87
+          end,
88
+          windowSize,
89
+         } = config
90
+
91
+        const url = `https://api.opensensemap.org/statistics/descriptive?boxid=${boxId}&from-date=${start}&to-date=${end}&phenomenon=${phenomenon}&window=${windowSize}&operation=${operation}&columns=boxName,unit,phenomenon&format=json&download=false`
92
+        const res = await fetch(url)
93
+        return (await res.json())[0]
94
+      }
95
+
96
+      function populateTable (table, data, config) {
97
+        const dateFormat = d => new Date(d).toLocaleDateString('de')
98
+        const valFormat = v => Math.round(v * (10 ** 2)) / (10 ** 2)
99
+        const addRow = (left, right) => {
100
+          const row = document.createElement('tr')
101
+          row.innerHTML = `<td>${left}</td><td>${right}</td>`
102
+          table.appendChild(row)
103
+        }
104
+
105
+        const dates = Object.keys(data).filter(k => k.startsWith('20')).sort()
106
+
107
+        for (let i = 0; i < dates.length; i++) {
108
+          const dateFrom = dates[i]
109
+          const dateTo   = dates[i + 1] || new Date()
110
+          const val      = data[dates[i]]
111
+
112
+          const left  = `${dateFormat(dateFrom)} - ${dateFormat(dateTo)}`
113
+          const right = `${valFormat(val)} ${data['unit']}`
114
+          addRow(left, right)
115
+        }
116
+
117
+        // add aggregate of all values
118
+        let aggregate = null
119
+        switch (config.operation) {
120
+          case 'sum':
121
+            aggregate = dates.map(d => data[d]).reduce((s, v) => s += v, 0)
122
+            break
123
+          case 'arithmeticMean':
124
+            aggregate = dates.map(d => data[d]).reduce((s, v) => s += v, 0) / dates.length
125
+            break
126
+          case 'max':
127
+            aggregate = Math.max.apply(null, dates.map(d => data[d]))
128
+            break
129
+          case 'min':
130
+            aggregate = Math.min.apply(null, dates.map(d => data[d]))
131
+            break
132
+        }
133
+        if (aggregate)
134
+          addRow(config.operation, `${valFormat(aggregate)} ${data['unit']}`)
135
+      }
136
+
137
+      function loadConfig (queryString, reset = true) {
138
+        if (reset)
139
+          window.location.search = queryString
140
+        else
141
+          window.location.search += queryString
142
+      }
143
+    </script>
144
+  </body>
145
+</html>

+ 1
- 6
app/assets/scripts/views/app.js View File

@@ -13,11 +13,6 @@ var App = React.createClass({
13 13
   },
14 14
 
15 15
   render: function () {
16
-
17
-    const monthStart = new Date('2019-01-01').toISOString();
18
-    const monthEnd   = new Date().toISOString();
19
-    const monthRainUrl = `https://api.opensensemap.org/statistics/descriptive?boxid=${config.senseBox.id}&from-date=${monthStart}&to-date=${monthEnd}&phenomenon=Niederschlag&window=30d&operation=sum&columns=boxName,unit&format=json&download=false`
20
-
21 16
     return (
22 17
       <div>
23 18
         <header className='site-header' role='banner'>
@@ -27,7 +22,7 @@ var App = React.createClass({
27 22
               <span>
28 23
                 <a target='_blank' href={'https://opensensemap.org/explore/' + config.senseBox.id}>🗺️ Karte</a>
29 24
                 <br/>
30
-                <a target='_blank' href={monthRainUrl}>Niederschlag / Monat</a>
25
+                <a href='./aggregate.html'>Daten-Aggregation</a>
31 26
               </span>
32 27
             </div>
33 28
           </div>

Loading…
Cancel
Save