aggregate.js 5.44 KB
Newer Older
1
2
3
4
5
"use strict";

import {
  BASE_URL,
  QUERY_PARAMS_COMBINED,
6
  getMetadataPlusObservationsFromSingleOrMultipleDatastreams,
7
8
9
10
11
12
13
14
15
16
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
46
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
} from "./appChart.js";

/**
 * Create 24-hour time strings for a time interval delimited by a start time and an end time. It is assumed that the start time is at "00:00:00" and the end time is at "23:45:00" (when the sampling rate of observations is 15 min) or "23:00:00" (when the sampling rate of observations is 60 min)
 * @param {String} phenomenonSamplingRate The sampling rate of the phenomenon of interest represented as a string, e.g. "15 min", "60 min"
 * @returns {Array} An array of two 24-hour strings representing the start time and end time
 */
const createTimeStringsForInterval = function (phenomenonSamplingRate) {
  const fifteenMinutes = "15 min";
  const sixtyMinutes = "60 min";

  const startTime = "00:00:00";
  const endTimeFifteenMinutes = "23:45:00";
  const endTimeSixtyMinutes = "23:00:00";

  if (
    phenomenonSamplingRate !== fifteenMinutes &&
    phenomenonSamplingRate !== sixtyMinutes
  )
    return;

  // 15 min sampling rate
  if (phenomenonSamplingRate === fifteenMinutes) {
    return [startTime, endTimeFifteenMinutes];
  }

  // 60 min sampling rate
  if (phenomenonSamplingRate === sixtyMinutes) {
    return [startTime, endTimeSixtyMinutes];
  }
};

/**
 * Create an ISO 8601 date and time string
 * @param {String} inputCalendarDate Calendar date string in "YYYY-MM-DD" format
 * @param {String} inputTwentyFourHourTime 24-hour time string in "hh:mm:ss" format
 * @returns {String} An ISO 8601 date and time string
 */
const createIso8601DateTimeString = function (
  inputCalendarDate,
  inputTwentyFourHourTime
) {
  return `${inputCalendarDate}T${inputTwentyFourHourTime}.000Z`;
};

/**
 * Calculate the index of a timestamp in an array of timestamps
 * @param {Array} inputTimestampArr An array of timestamps, extracted from an array of observations
 * @param {String} timestampOfInterest A string representing the timestamp of interest in ISO 8601 format
 * @returns {Number} An integer representing the index of the timestamp of interest in the array of timestamps
 */
const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
  const timestampIndex = inputTimestampArr.findIndex(
    (timestamp) => timestamp === timestampOfInterest
  );

  // If the timestamp does not exist in the timestamp array
  if (timestampIndex === -1)
    throw new Error(
      "A start or end timestamp could not be found in the timestamp array"
    );

  // If the timestamp exists in the timestamp array
  return timestampIndex;
};

/**
 * Aggregate observations within a time interval delimited by a start date and end date. The start date may be the same as the end date.
 * @param {Array} obsArray An array of observations (timestamp + value) that is response from SensorThings API
 * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15 min", "60 min"
 * @param {String} startDate A 24-hour date string representing the start date
 * @param {String} endDate A 24-hour date string representing the end date
 * @returns {Number} A floating-point number representing the aggregated observation value
 */
const aggregateObservationsWithinTimeInterval = function (
  obsArray,
  samplingRate,
  startDate,
  endDate
) {
  // Extract the timestamps and values from the observations
  const obsTimestampArr = obsArray.map((obs) => obs[0]);
  const obsValuesArr = obsArray.map((obs) => obs[1]);

  // Create array of 24-hour strings for the start and end of interval
  const startPlusEndTimeStrings = createTimeStringsForInterval(samplingRate);

  // Extract 24-hour strings for the start and end of interval
  const [startTimeString, endTimeString] = startPlusEndTimeStrings;

  // Create ISO 8601 strings for the start and end of interval
  const startIso8601DateTimeString = createIso8601DateTimeString(
    startDate,
    startTimeString
  );
  const endIso8601DateTimeString = createIso8601DateTimeString(
    endDate,
    endTimeString
  );

  // Calculate the indexes of the timestamps for the start and end of interval
  const indexStartTimestamp = getIndexOfTimestamp(
    obsTimestampArr,
    startIso8601DateTimeString
  );
  const indexEndTimestamp = getIndexOfTimestamp(
    obsTimestampArr,
    endIso8601DateTimeString
  );

  // Extract the observations that fall within our time interval
  const obsValuesForTimeIntervalArr = obsValuesArr.slice(
    indexStartTimestamp,
    indexEndTimestamp + 1
  );

  // Calculate the aggregated observation value
  const aggregatedObsValue = obsValuesForTimeIntervalArr.reduce(
    (accumulator, currentValue) => accumulator + currentValue
  );

  return aggregatedObsValue;
};

/**
 * Test aggregation of observations from a single datastream
 */
const testAggregation = async function () {
135
  const sensorOfInterestNestedArr = [["225", "vl", "60min"]];
136

137
138
139
140
141
142
  const observationsPlusMetadata =
    await getMetadataPlusObservationsFromSingleOrMultipleDatastreams(
      BASE_URL,
      QUERY_PARAMS_COMBINED,
      sensorOfInterestNestedArr
    );
143

144
145
146
  // Extract the observations and metadata for each sensor
  // Array elements in same order as input array
  const [[obsSensorOneArr], [metadataSensorOne]] = observationsPlusMetadata;
147
148
149
150

  // Aggregated observations
  const observationsBau225VLAggregated =
    aggregateObservationsWithinTimeInterval(
151
      obsSensorOneArr,
152
153
154
155
156
157
158
159
160
      "60 min",
      "2020-02-01",
      "2020-03-31"
    );

  // console.log(observationsBau225VLAggregated);
};

// testAggregation();