Commit 1255a80e authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Fix bug: calculate temperature difference

We were using the original VL observations array in the 'map' array
method. Updated to use the final version of VL observations array
parent e02c4e2a
Showing with 229 additions and 217 deletions
+229 -217
......@@ -163,10 +163,9 @@ const calculateMinimumObservationValuesWithinInterval = function (
)
);
}
// Calculate minimum values of observations - monthly
// Note the use of the two nested `map` methods
if (aggregationInterval === "monthly") {
else if (aggregationInterval === "monthly") {
return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
calculateMinimumObservationValuesWithinMonthInterval(
......@@ -211,10 +210,9 @@ const calculateMaximumObservationValuesWithinInterval = function (
)
);
}
// Calculate maximum values of observations - monthly
// Note the use of the two nested `map` methods
if (aggregationInterval === "monthly") {
else if (aggregationInterval === "monthly") {
return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
calculateMaximumObservationValuesWithinMonthInterval(
......@@ -259,10 +257,9 @@ const calculateSumOfObservationValuesWithinInterval = function (
)
);
}
// Calculate sum of values of observations - monthly
// Note the use of the two nested `map` methods
if (aggregationInterval === "monthly") {
else if (aggregationInterval === "monthly") {
return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
calculateSumOfObservationValuesWithinMonthInterval(
......@@ -307,10 +304,9 @@ const calculateAverageOfObservationValuesWithinInterval = function (
)
);
}
// Calculate average of values of observations - monthly
// Note the use of the two nested `map` methods
if (aggregationInterval === "monthly") {
else if (aggregationInterval === "monthly") {
return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
calculateAverageOfObservationValuesWithinMonthInterval(
......
......@@ -16,18 +16,17 @@ const createTimeStringsForInterval = function (phenomenonSamplingRate) {
if (
phenomenonSamplingRate !== fifteenMinutes &&
phenomenonSamplingRate !== sixtyMinutes
)
) {
throw new Error(
`Check that the provided phenomenon sampling rate string is in this format: "15min" or "60min"`
);
}
// 15 min sampling rate
if (phenomenonSamplingRate === fifteenMinutes) {
else if (phenomenonSamplingRate === fifteenMinutes) {
return [startTime, endTimeFifteenMinutes];
}
// 60 min sampling rate
if (phenomenonSamplingRate === sixtyMinutes) {
else if (phenomenonSamplingRate === sixtyMinutes) {
return [startTime, endTimeSixtyMinutes];
}
};
......@@ -66,13 +65,15 @@ const getIndexOfTimestamp = function (inputTimestampArr, timestampOfInterest) {
);
// If the timestamp does not exist in the timestamp array
if (timestampIndex === -1)
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
else {
return timestampIndex;
}
};
/**
......@@ -210,23 +211,25 @@ const extractObservationValuesWithinMonthInterval = function (
const [yearNum, monthNum] =
extractMonthYearDigitsFromCalendarMonthString(calendarMonthStr);
if (monthNum < 1 || monthNum > 12) return;
// All the months start on the first
const startDateStr = `${calendarMonthStr}-01`;
if (monthNum < 1 || monthNum > 12) {
throw new Error("The specified digit for the month of the year is invalid");
}
// February
if (monthNum === 2) {
else if (monthNum === 2) {
// Leap year
if (checkIfLeapYear(yearNum))
if (checkIfLeapYear(yearNum)) {
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-29`
);
}
// Non-leap year
else {
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
......@@ -234,23 +237,30 @@ const extractObservationValuesWithinMonthInterval = function (
`${calendarMonthStr}-28`
);
}
}
// Months with 30 days
if (monthNum === 4 || monthNum === 6 || monthNum === 9 || monthNum === 11)
else if (
monthNum === 4 ||
monthNum === 6 ||
monthNum === 9 ||
monthNum === 11
) {
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-30`
);
}
// Months with 31 days
else {
return extractObservationValuesWithinDatesInterval(
obsArray,
samplingRate,
startDateStr,
`${calendarMonthStr}-31`
);
}
};
/**
......
......@@ -75,30 +75,25 @@ export const calculateVorlaufMinusRuecklaufTemperature = async function (
(rlTempObs) => rlTempObs[1]
);
// The arrays have equal length, we need only use one of them for looping
// The final observation arrays now have equal length, we need only use one of them for looping
// Resulting array contains the following pairs (timestamp + dT)
const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
(vlTempObs, i) => {
const vorlaufMinusRuecklaufTemperatureObs =
vorlaufTemperatureObsFinalArr.map((vlTempObsFinal, i) => {
// Use timestamp from VL, since is equal to that of RL
const timestamp = vlTempObs[0];
const timestamp = vlTempObsFinal[0];
// Case 1: One of the observation values is `null`,
// no need to calculate temperature difference
if (
vorlaufTemperatureValues[i] === null ||
ruecklaufTemperatureValues[i] === null
) {
return [timestamp, null];
}
// Case 2: Neither of the observation values is `null`,
// calculate temperature difference
return [
return vorlaufTemperatureValues[i] === null ||
ruecklaufTemperatureValues[i] === null
? [timestamp, null]
: [
timestamp,
vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
];
}
);
});
// From Vorlauf metadata, extract `name` and `unitOfMeasurement`
const {
......
......@@ -48,11 +48,11 @@ const createSeriesOptionsForColumnChart = function (
if (
formattedAggregatedResultForColumnChart.length !==
buildingIdsPhenomenonNamesArr.length
)
) {
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
} else {
return formattedAggregatedResultForColumnChart.map(
(formattedAggResArray, i) => {
return {
......@@ -62,6 +62,7 @@ const createSeriesOptionsForColumnChart = function (
};
}
);
}
};
/**
......@@ -83,16 +84,17 @@ const drawColumnChartHighcharts = function (
aggregationType;
// Check whether the datastream properties are for aggregated observations
if (extractedFormattedDatastreamProperties?.aggregationType === undefined) {
// Case 1: No aggregation
if (extractedFormattedDatastreamProperties?.aggregationType === undefined) {
({
datastreamNamesArr,
phenomenonNamesArr,
buildingIdsPhenomenonNamesArr,
unitOfMeasurementSymbolsArr,
} = extractedFormattedDatastreamProperties);
} else {
}
// Case 2: Aggregation
else {
({
datastreamNamesArr,
phenomenonNamesArr,
......
......@@ -53,18 +53,18 @@ const removeUniqueObservationsFromLargerArray = function (
largerObsArr
) {
// Create a reversed copy of the indexes array, so that the larger index is removed first
const reversedUniqueIndexesArr = uniqueIndexesArr.reverse();
const uniqueIndexesReversedCopyArr = [...uniqueIndexesArr].reverse();
// Create a copy the larger observation array, will be modified in place
const processedLargerObsArr = largerObsArr;
// Create a copy of the larger observation array, will be modified in place
const largerObsCopyArr = [...largerObsArr];
reversedUniqueIndexesArr.forEach((index) => {
if (index > -1) {
processedLargerObsArr.splice(index, 1);
uniqueIndexesReversedCopyArr.forEach((uniqueIndex) => {
if (uniqueIndex > -1) {
largerObsCopyArr.splice(uniqueIndex, 1);
}
});
return processedLargerObsArr;
return largerObsCopyArr;
};
/**
......@@ -75,10 +75,11 @@ const removeUniqueObservationsFromLargerArray = function (
*/
const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
if (firstArr.length === secondArr.length) return;
if (firstArr.length > secondArr.length) return firstArr;
if (firstArr.length < secondArr.length) return secondArr;
else if (firstArr.length > secondArr.length) {
return firstArr;
} else if (firstArr.length < secondArr.length) {
return secondArr;
}
};
/**
......@@ -89,10 +90,11 @@ const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
*/
const getSmallerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
if (firstArr.length === secondArr.length) return;
if (firstArr.length < secondArr.length) return firstArr;
if (firstArr.length > secondArr.length) return secondArr;
else if (firstArr.length < secondArr.length) {
return firstArr;
} else if (firstArr.length > secondArr.length) {
return secondArr;
}
};
/**
......@@ -162,17 +164,17 @@ const checkForAndDeleteUniqueObservationsFromLargerArray = function (
obsArrayTwo
) {
if (obsArrayOne.length === obsArrayTwo.length) return;
// Case 1: obsArrayOne.length < obsArrayTwo.length
if (obsArrayOne.length < obsArrayTwo.length) {
// Case 1: obsArrayTwo larger than obsArrayOne
else if (obsArrayOne.length < obsArrayTwo.length) {
const [biggerObsArr, smallerObsArr] =
deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
return [smallerObsArr, biggerObsArr];
}
// Case 2: obsArrayOne.length > obsArrayTwo.length
// Case 2: obsArrayOne larger than obsArrayTwo
else if (obsArrayOne.length > obsArrayTwo.length) {
return deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
}
};
/**
......@@ -194,14 +196,16 @@ const convertHexColorToRGBColor = function (hexCode) {
"#91e8e1": "rgb(145, 232, 225)",
};
if (hexToRGBMapping?.[hexCode] === undefined)
if (hexToRGBMapping?.[hexCode] === undefined) {
throw new Error(
"The provided hex code is not valid or is not supported by this function"
);
}
// Extract the RGB color elements as a single string
// The individual color elements are separated by commas
else {
return (hexToRGBMapping?.[hexCode]).slice(4, -1);
}
};
/**
......@@ -264,11 +268,13 @@ const createPartialTitleForLineOrColumnChart = function (
) {
// Case 1: No aggregation; return empty string
if (!aggregationInterval && !aggregationType) return ``;
// Case 2: Aggregation; capitalize the first characters
else {
return `${
aggregationInterval.slice(0, 1).toUpperCase() + aggregationInterval.slice(1)
aggregationInterval.slice(0, 1).toUpperCase() +
aggregationInterval.slice(1)
} ${aggregationType.slice(0, 1).toUpperCase() + aggregationType.slice(1)}`;
}
};
/**
......@@ -289,12 +295,13 @@ const createFullTitleForLineOrColumnChart = function (
aggregationInterval,
aggregationType
)}${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
// Case 2: Aggregation
else {
return `${createPartialTitleForLineOrColumnChart(
aggregationInterval,
aggregationType
)}: ${createCombinedTextDelimitedByComma(phenomenonNamesArr)}`;
}
};
/**
......
......@@ -53,11 +53,11 @@ const createSeriesOptionsForLineChart = function (
if (
formattedObsArraysForLineChart.length !==
buildingIdsPhenomenonNamesArr.length
)
) {
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
} else {
return formattedObsArraysForLineChart.map((formattedObsArray, i) => {
return {
name: `${buildingIdsPhenomenonNamesArr[i]}`,
......@@ -66,6 +66,7 @@ const createSeriesOptionsForLineChart = function (
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
};
});
}
};
/**
......
......@@ -45,9 +45,10 @@ const formatSensorThingsApiResponseForScatterPlot = function (
return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal);
}
// When our observation arrays already have SAME lengths
else {
return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
}
};
/**
......@@ -105,21 +106,22 @@ const createYAxisTitleTextScatterPlot = function (
// y-axis phenomenon symbols start at array index 1
const unitOfMeasurementSymbolsYAxisArr = unitOfMeasurementSymbolsArr.slice(1);
const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
(phenomenonNameYAxis, i) =>
`${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
);
// The phenomenon names and unit of measurement arrays should have equal lengths
// Use one of the arrays for looping
if (
phenomenonNamesYAxisArr.length !== unitOfMeasurementSymbolsYAxisArr.length
)
) {
throw new Error(
"The phenomenon names array and unit of measurement symbols array have different lengths"
);
const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
(phenomenonNameYAxis, i) =>
`${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
);
} else {
return createCombinedTextDelimitedByComma(combinedNameSymbolArr);
}
};
/**
......@@ -159,11 +161,11 @@ const createSeriesOptionsForScatterPlot = function (
// Use one of the arrays for looping
if (
formattedObsArraysForScatterPlot.length !== phenomenonNamesYAxisArr.length
)
) {
throw new Error(
"The observations array and phenomenon names array have different lengths"
);
} else {
return formattedObsArraysForScatterPlot.map((formattedObsArray, i) => {
return {
name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`,
......@@ -171,6 +173,7 @@ const createSeriesOptionsForScatterPlot = function (
color: seriesColors[i],
};
});
}
};
/**
......
......@@ -111,24 +111,22 @@ const getSelectedOptionsFromAllDropDownLists = function () {
);
// Ensure that all the options have at least one selection
if (selectedChartTypeArr.length === 0)
if (selectedChartTypeArr.length === 0) {
throw new Error("Please ensure that the chart type is selected");
if (selectedBuildingDataPointOptionsSplitArr.length === 0)
} else if (selectedBuildingDataPointOptionsSplitArr.length === 0) {
throw new Error("Please ensure that at least one data point is selected");
if (selectedSamplingRateArr.length === 0)
} else if (selectedSamplingRateArr.length === 0) {
throw new Error("Please ensure that the aggregation type is selected");
if (selectedSamplingRateArr.length === 0)
} else if (selectedSamplingRateArr.length === 0) {
throw new Error("Please ensure that the sampling rate is selected");
} else {
return [
selectedChartTypeArr,
selectedBuildingDataPointOptionsSplitArr,
selectedAggregationOptionsArr,
selectedSamplingRateArr,
];
}
};
/**
......@@ -231,14 +229,6 @@ const extractTemperatureDifferenceOptions = function (
const bldgDataPntSamplingRateAbbrvArr =
buildingDataPointSamplingRateAbbrevArr[foundIndex];
// Extract the building and sampling rate strings
// Note: we have ignored the second element
// const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr;
// Create a new array that contains two elements,
// the building and sampling rate abbreviated strings
// const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv];
temperatureDifferenceOptionsAbbrevArr.push(bldgDataPntSamplingRateAbbrvArr);
});
......@@ -326,9 +316,8 @@ const checkIfSelectedBuildingDataPointsOptionsAreValid = function (
throw new Error("A heatmap can only display one data point at a time");
}
}
// A scatter plot requires at least two data points
if (selectedChartType === "Scatter Plot") {
else if (selectedChartType === "Scatter Plot") {
if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length >= 2)
return true;
else if (
......@@ -418,29 +407,6 @@ const getBuildingSensorSamplingRateAbbreviation = function (
},
};
if (
fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm] === undefined
)
throw new Error(
"The provided building ID is not valid or is not supported by this function"
);
if (
fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm] ===
undefined
)
throw new Error(
"The provided data point is not valid or is not supported by this function"
);
if (
fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm] ===
undefined
)
throw new Error(
"The provided sampling rate is not valid or is not supported by this function"
);
const buildingAbbrev =
fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm];
......@@ -450,7 +416,21 @@ const getBuildingSensorSamplingRateAbbreviation = function (
const samplingRateAbbrev =
fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm];
if (buildingAbbrev === undefined) {
throw new Error(
"The provided building ID is not valid or is not supported by this function"
);
} else if (phenomenonAbbrev === undefined) {
throw new Error(
"The provided data point is not valid or is not supported by this function"
);
} else if (samplingRateAbbrev === undefined) {
throw new Error(
"The provided sampling rate is not valid or is not supported by this function"
);
} else {
return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
}
};
/**
......@@ -483,11 +463,11 @@ const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
// Assume that the buildings and data points arrays have equal length
// use one of the arrays for looping
if (selectedBuildingsArr.length !== selectedDataPointsArr.length)
if (selectedBuildingsArr.length !== selectedDataPointsArr.length) {
throw new Error(
"The buildings array and data points array have different lengths"
);
} else {
return selectedBuildingsArr.map((selectedBuilding, i) =>
getBuildingSensorSamplingRateAbbreviation(
selectedBuilding,
......@@ -495,6 +475,7 @@ const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
...selectedSamplingRateArr
)
);
}
};
export {
......
......@@ -11,14 +11,18 @@ const matchUnitOfMeasurementSymbolStringToSymbol = function (
const unicodeCodePointDegreeSymbol = "\u00B0";
const unicodeCodePointSuperscriptThree = "\u00B3";
if (unitOfMeasurementSymbolString === "degC")
// Symbol - temperature
if (unitOfMeasurementSymbolString === "degC") {
return `${unicodeCodePointDegreeSymbol}C`;
if (unitOfMeasurementSymbolString === "m3/h")
}
// Symbol - flow rate
else if (unitOfMeasurementSymbolString === "m3/h") {
return `m${unicodeCodePointSuperscriptThree}/h`;
}
// If no symbol exists
else {
return unitOfMeasurementSymbolString;
}
};
/**
......@@ -94,39 +98,37 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
if (
formattedDatastreamsMetadataArr === undefined ||
isMetadataForAggregation === undefined
)
) {
throw new Error(
"This function expects two arguments, ensure that both have been supplied"
);
if (
} else if (
formattedDatastreamsMetadataArr &&
isMetadataForAggregation &&
(aggregationInterval === undefined || aggregationType === undefined)
)
) {
throw new Error(
"This function expects four arguments, ensure that all of them have been supplied"
);
if (
} else if (
isMetadataForAggregation &&
aggregationInterval !== "daily" &&
aggregationInterval !== "monthly"
)
) {
throw new Error(
`The supported aggegation interval strings are "daily" or "monthly"`
);
if (
} else if (
isMetadataForAggregation &&
aggregationType !== "minimum" &&
aggregationType !== "maximum" &&
aggregationType !== "sum" &&
aggregationType !== "average"
)
) {
throw new Error(
`The supported aggegation type strings are "minimum", "maximum", "sum" or "average"`
);
}
// Create arrays from the properties of the formatted datastream metadata
const datastreamDescriptionsArr = formattedDatastreamsMetadataArr.map(
......@@ -150,7 +152,7 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
);
// Case 1: Metadata NOT USED for aggregation; "isMetadataForAggregation" = false
if (!isMetadataForAggregation)
if (!isMetadataForAggregation) {
return {
datastreamDescriptionsArr,
datastreamNamesArr,
......@@ -158,8 +160,9 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
buildingIdsPhenomenonNamesArr,
unitOfMeasurementSymbolsArr,
};
}
// Case 2: Metadata USED for aggregation; "isMetadataForAggregation" = true
else {
return {
datastreamDescriptionsArr,
datastreamNamesArr,
......@@ -169,6 +172,7 @@ const extractPropertiesFromFormattedDatastreamMetadata = function (
aggregationInterval,
aggregationType,
};
}
};
export {
......
......@@ -82,14 +82,27 @@ export const getDatastreamIdFromBuildingNumber = function (
},
};
if (
if (buildingToDatastreamMapping?.[buildingNumber] === undefined) {
throw new Error(
"The supplied building number is not valid or is not supported by this function"
);
} else if (
buildingToDatastreamMapping?.[buildingNumber]?.[phenomenon] === undefined
) {
throw new Error(
"The supplied phenomenon name is not valid or is not supported by this function"
);
} else if (
buildingToDatastreamMapping?.[buildingNumber]?.[phenomenon]?.[
samplingRate
] === undefined
)
return;
) {
throw new Error(
"The supplied sampling rate is not valid or is not supported by this function"
);
} else {
return Number(
buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate]
);
}
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment