appChart.js 11 KB
Newer Older
Pithon Kabiro's avatar
Pithon Kabiro committed
1
"use strict";
Pithon Kabiro's avatar
Pithon Kabiro committed
2

3
4
5
// DEBUG:
// Observations WITHOUT data gap - Bau 225 / Datastream ID = 80
// Observations WITH data gap - Bau 112 / Datastream ID = 78
6

7
export const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
8

9
10
11
12
13
14
15
/**
 * Retrieve the datastream ID that corresponds to a particular building
 * @param {*} buildingNumber Integer representing the building ID
 * @param {*} phenomenon String representing the phenomenon of interest
 * @param {*} samplingRate String representing the sampling rate of the observations
 * @returns {Number} Datastream corresponding to the input building
 */
16
export const getDatastreamIdFromBuildingNumber = function (
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  buildingNumber,
  phenomenon,
  samplingRate
) {
  const buildingToDatastreamMapping = {
    101: {
      vl: { "15min": "69", "60min": "75" },
      rl: { "15min": "81", "60min": "87" },
      flow: { "15min": "93", "60min": "99" },
      power: { "15min": "105", "60min": "111" },
      energy: { "15min": "117", "60min": "123" },
      energy_verb: { "15min": "129", "60min": "135" },
    },
    102: {
      vl: { "15min": "70", "60min": "76" },
      rl: { "15min": "82", "60min": "88" },
      flow: { "15min": "94", "60min": "100" },
      power: { "15min": "106", "60min": "112" },
      energy: { "15min": "118", "60min": "124" },
      energy_verb: { "15min": "130", "60min": "136" },
    },
    107: {
      vl: { "15min": "71", "60min": "77" },
      rl: { "15min": "83", "60min": "89" },
      flow: { "15min": "95", "60min": "101" },
      power: { "15min": "107", "60min": "113" },
      energy: { "15min": "119", "60min": "125" },
      energy_verb: { "15min": "131", "60min": "137" },
    },
46
    "112, 118": {
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
      vl: { "15min": "72", "60min": "78" },
      rl: { "15min": "84", "60min": "90" },
      flow: { "15min": "96", "60min": "102" },
      power: { "15min": "108", "60min": "114" },
      energy: { "15min": "120", "60min": "126" },
      energy_verb: { "15min": "132", "60min": "138" },
    },
    125: {
      vl: { "15min": "73", "60min": "79" },
      rl: { "15min": "85", "60min": "91" },
      flow: { "15min": "97", "60min": "103" },
      power: { "15min": "109", "60min": "115" },
      energy: { "15min": "121", "60min": "127" },
      energy_verb: { "15min": "133", "60min": "139" },
    },
    225: {
      vl: { "15min": "74", "60min": "80" },
      rl: { "15min": "86", "60min": "92" },
      flow: { "15min": "98", "60min": "104" },
      power: { "15min": "110", "60min": "116" },
      energy: { "15min": "122", "60min": "128" },
      energy_verb: { "15min": "134", "60min": "140" },
    },
  };

72
73
74
75
76
  if (!buildingNumber) return;

  // check if building is contained in mapping object
  if (!(buildingNumber in buildingToDatastreamMapping)) return;

77
78
79
80
81
82
83
  const datastreamIdMatched = Number(
    buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
  );

  return datastreamIdMatched;
};

84
85
/**
 * Create URL to fetch the Observations corresponding to a provided Datastream
Pithon Kabiro's avatar
Pithon Kabiro committed
86
 * @param {String} baseUrl Base URl of the STA server
87
88
 * @param {Number} datastreamID - Integer representing the Datastream ID
 * @returns {String} URL string for fetching the Observations corresponding to a Datastream
89
 */
90
export const getObservationsUrl = function (baseUrl, datastreamID) {
91
  if (!datastreamID) return;
92
  const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
93
94
95
96
97
  return fullDatastreamURL;
};

/**
 * Create a temporal filter string for the fetched Observations
98
99
100
 * @param {String} dateStart Start date in YYYY-MM-DD format
 * @param {String} dateStop Stop date in YYYY-MM-DD format
 * @returns {String} Temporal filter string
101
 */
102
const createTemporalFilterString = function (dateStart, dateStop) {
103
104
105
106
107
  if (!dateStart || !dateStop) return;
  const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
  return filterString;
};

108
// const BASE_URL_OBSERVATIONS = getObservationsUrl(80);
109
110
111
112
113
114
115
116
117
118
const PARAM_RESULT_FORMAT = "dataArray";
const PARAM_ORDER_BY = "phenomenonTime asc";
const PARAM_FILTER = createTemporalFilterString("2020-01-01", "2021-01-01");
const PARAM_SELECT = "result,phenomenonTime";
export const PARAM_OBJ = {
  "$resultFormat": PARAM_RESULT_FORMAT,
  "$orderBy": PARAM_ORDER_BY,
  "$filter": PARAM_FILTER,
  "$select": PARAM_SELECT,
};
Pithon Kabiro's avatar
Pithon Kabiro committed
119

120
121
122
123
124
125
126
127
128
129
130
/**
 * Perform a GET request using the Axios library
 * @param {String} urlObservations A URL that fetches Observations from STA instance
 * @param {Object} urlParamObj The URL parameters to be sent together with the GET request
 * @returns {Object} Axios request object
 */
export const axiosGetRequest = function (urlObservations, urlParamObj) {
  return axios.get(urlObservations, {
    params: urlParamObj,
  });
};
131
132

/**
133
 * Format the response from SensorThings API to make it suitable for heatmap
134
135
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a heatmap
136
 */
137
export const formatSTAResponseForHeatMap = function (obsArray) {
Pithon Kabiro's avatar
Pithon Kabiro committed
138
  if (!obsArray) return;
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  const dataSTAFormatted = [];
  obsArray.forEach((obs) => {
    // Get the date/time string; first element in input array; remove trailing "Z"
    const obsDateTimeInput = obs[0].slice(0, -1);
    // Get the "date" part of an observation
    const obsDateInput = obs[0].slice(0, 10);
    // Create Date objects
    const obsDateTime = new Date(obsDateTimeInput);
    const obsDate = new Date(obsDateInput);
    // x-axis -> timestamp; will be the same for observations from the same date
    const timestamp = Date.parse(obsDate);
    // y-axis -> hourOfDay
    const hourOfDay = obsDateTime.getHours();
    // value -> the observation's value; second element in input array
    const value = obs[1];
    dataSTAFormatted.push([timestamp, hourOfDay, value]);
  });
  return dataSTAFormatted;
};

/**
 * Draw a heatmap using Highcharts library
161
 * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
162
 * @returns {Object} Highcharts library heatmap object
163
 */
164
export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
  Highcharts.chart("chart-heatmap", {
    chart: {
      type: "heatmap",
      zoomType: "x",
    },

    boost: {
      useGPUTranslations: true,
    },

    title: {
      text: "Inlet flow (Vorlauf)",
      align: "left",
      x: 40,
    },

    subtitle: {
      text: "Temperature variation by day and hour in 2020",
      align: "left",
      x: 40,
    },

    xAxis: {
      type: "datetime",
      // min: Date.UTC(2017, 0, 1),
      // max: Date.UTC(2017, 11, 31, 23, 59, 59),
      labels: {
        align: "left",
        x: 5,
        y: 14,
        format: "{value:%B}", // long month
      },
      showLastLabel: false,
      tickLength: 16,
    },

    yAxis: {
      title: {
        text: null,
      },
      labels: {
        format: "{value}:00",
      },
      minPadding: 0,
      maxPadding: 0,
      startOnTick: false,
      endOnTick: false,
      // tickPositions: [0, 6, 12, 18, 24],
      tickPositions: [0, 3, 6, 9, 12, 15, 18, 21, 24],
      tickWidth: 1,
      min: 0,
      max: 23,
      reversed: true,
    },

    colorAxis: {
      stops: [
        [0, "#3060cf"],
        [0.5, "#fffbbc"],
        [0.9, "#c4463a"],
        [1, "#c4463a"],
      ],
      min: 60,
      max: 85,
      startOnTick: false,
      endOnTick: false,
      labels: {
        format: "{value}℃",
      },
    },

    series: [
      {
238
        data: formattedObsArrayForHeatmap,
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
        boostThreshold: 100,
        borderWidth: 0,
        nullColor: "#525252",
        colsize: 24 * 36e5, // one day
        tooltip: {
          headerFormat: "Temperature<br/>",
          pointFormat:
            "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
        },
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
254
255
/**
 * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch
256
257
 * @param {Array} obsArray Response from SensorThings API as array
 * @returns {Array} Array of formatted observations suitable for use in a line chart
Pithon Kabiro's avatar
Pithon Kabiro committed
258
 */
259
export const formatSTAResponseForLineChart = function (obsArray) {
Pithon Kabiro's avatar
Pithon Kabiro committed
260
  if (!obsArray) return;
Pithon Kabiro's avatar
Pithon Kabiro committed
261
262
263
264
265
266
267
268
269
270
271
  const dataSTAFormatted = [];
  obsArray.forEach((result) => {
    const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp
    const valueObs = result[1];
    dataSTAFormatted.push([timestampObs, valueObs]);
  });
  return dataSTAFormatted;
};

/**
 * Draw a line chart using Highcharts library
272
 * @param {Array} formattedObsArrayForLineChart - Response from SensorThings API formatted for use in a line chart
273
 * @returns {Object} Highcharts library line chart object
Pithon Kabiro's avatar
Pithon Kabiro committed
274
 */
275
export const drawLineChartHC = function (formattedObsArrayForLineChart) {
Pithon Kabiro's avatar
Pithon Kabiro committed
276
277
278
279
280
281
282
283
284
285
286
  // Create the chart
  Highcharts.stockChart("chart-line", {
    chart: {
      zoomType: "x",
    },

    rangeSelector: {
      selected: 1,
    },

    title: {
287
288
289
290
291
292
293
      text: "Inlet flow (Vorlauf)",
      "align": "left",
    },

    subtitle: {
      text: "Temperature variation by hour in 2020",
      align: "left",
Pithon Kabiro's avatar
Pithon Kabiro committed
294
295
296
297
298
    },

    series: [
      {
        name: "AAPL",
299
        data: formattedObsArrayForLineChart,
Pithon Kabiro's avatar
Pithon Kabiro committed
300
301
302
303
304
305
306
307
308
        tooltip: {
          valueDecimals: 2,
        },
        turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
      },
    ],
  });
};

Pithon Kabiro's avatar
Pithon Kabiro committed
309
/**
Pithon Kabiro's avatar
Pithon Kabiro committed
310
 * Follows "@iot.nextLink" links in SensorThingsAPI's response
Pithon Kabiro's avatar
Pithon Kabiro committed
311
 * Appends new results to existing results
Pithon Kabiro's avatar
Pithon Kabiro committed
312
 * @async
313
 * @param {Object} responsePromise Promise object
Pithon Kabiro's avatar
Pithon Kabiro committed
314
 * @returns {Object} - Object containing results from all the "@iot.nextLink" links
Pithon Kabiro's avatar
Pithon Kabiro committed
315
 */
316
export const followNextLink = function (responsePromise) {
Pithon Kabiro's avatar
Pithon Kabiro committed
317
  if (!responsePromise) return;
Pithon Kabiro's avatar
Pithon Kabiro committed
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
  return responsePromise
    .then(function (lastSuccess) {
      if (lastSuccess.data["@iot.nextLink"]) {
        return followNextLink(
          axios.get(lastSuccess.data["@iot.nextLink"])
        ).then(function (nextLinkSuccess) {
          nextLinkSuccess.data.value = lastSuccess.data.value.concat(
            nextLinkSuccess.data.value
          );
          return nextLinkSuccess;
        });
      } else {
        return lastSuccess;
      }
    })
    .catch(function (err) {
      console.log(err);
    });
};

// Get "ALL" the Observations that satisfy our query
339
340
341
// followNextLink(axiosGetRequest)
//   .then((success) => {
//     const successValue = success.data.value;
Pithon Kabiro's avatar
Pithon Kabiro committed
342

343
344
//     // Array that will hold the combined observations
//     const combinedObservations = [];
Pithon Kabiro's avatar
Pithon Kabiro committed
345

346
347
348
//     successValue.forEach((dataObj) => {
//       // Each page of results will have a dataArray that holds the observations
//       const dataArrays = dataObj.dataArray;
Pithon Kabiro's avatar
Pithon Kabiro committed
349

350
351
352
353
354
355
//       combinedObservations.push(...dataArrays);
//     });
//     // DEBUG: Check total number of observations
//     console.log(combinedObservations.length);
//     // DEBUG: Print the array of observations
//     console.log(combinedObservations);
Pithon Kabiro's avatar
Pithon Kabiro committed
356

357
358
359
360
361
362
363
364
365
//     return combinedObservations;
//   })
//   .catch((err) => {
//     console.log(err);
//   })
//   .then((observationArr) => {
//     drawHeatMapHC(observationArr);
//     drawLineChartHC(observationArr);
//   });