"use strict"; const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1"; /** * Retrieve the datastream ID that corresponds to a particular building * @param {Number | String} buildingNumber Integer representing the building ID * @param {String} phenomenon String representing the phenomenon of interest * @param {String} samplingRate String representing the sampling rate of the observations * @returns {Number} Datastream corresponding to the input building */ const getDatastreamIdFromBuildingNumber = function ( buildingNumber, phenomenon, samplingRate ) { const buildingToDatastreamMapping = { 101: { vl: { "15min": "69", "60min": "75" }, rl: { "15min": "81", "60min": "87" }, // These Datastreams do not yet have Observations // 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" }, // These Datastreams do not yet have Observations // 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" }, // These Datastreams do not yet have Observations // flow: { "15min": "95", "60min": "101" }, // power: { "15min": "107", "60min": "113" }, // energy: { "15min": "119", "60min": "125" }, // energy_verb: { "15min": "131", "60min": "137" }, }, "112, 118": { vl: { "15min": "72", "60min": "78" }, rl: { "15min": "84", "60min": "90" }, // These Datastreams do not yet have Observations // 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" }, // These Datastreams do not yet have Observations // 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" }, }, }; if ( buildingToDatastreamMapping?.[buildingNumber]?.[phenomenon]?.[ samplingRate ] === undefined ) return; const datastreamIdMatched = Number( buildingToDatastreamMapping[buildingNumber][phenomenon][samplingRate] ); return datastreamIdMatched; }; /** * Create URL to fetch the details of single Datastream * @param {String} baseUrl Base URL of the STA server * @param {Number} datastreamID Integer representing the Datastream ID * @returns {String} URL string for fetching a single Datastream */ const getDatastreamUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})`; return fullDatastreamURL; }; /** * Create URL to fetch Observations * @param {String} baseUrl Base URL of the STA server * @param {Number} datastreamID Integer representing the Datastream ID * @returns {String} URL string for fetching Observations */ const getObservationsUrl = function (baseUrl, datastreamID) { if (!datastreamID) return; const fullObservationsURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`; return fullObservationsURL; }; /** * Create a temporal filter string for the fetched Observations * @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 */ const createTemporalFilterString = function (dateStart, dateStop) { if (!dateStart || !dateStop) return; const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`; return filterString; }; // const BASE_URL_OBSERVATIONS = getObservationsUrl(80); const QUERY_PARAM_RESULT_FORMAT = "dataArray"; const QUERY_PARAM_ORDER_BY = "phenomenonTime asc"; const QUERY_PARAM_FILTER = createTemporalFilterString( "2020-01-01", "2021-01-01" ); const QUERY_PARAM_SELECT = "result,phenomenonTime"; const QUERY_PARAMS_COMBINED = { "$resultFormat": QUERY_PARAM_RESULT_FORMAT, "$orderBy": QUERY_PARAM_ORDER_BY, "$filter": QUERY_PARAM_FILTER, "$select": QUERY_PARAM_SELECT, }; /** * Perform a GET request using the Axios library * @param {String} urlObservations A URL that fetches Observations from an STA instance * @param {Object} urlParamObj The URL parameters to be sent together with the GET request * @returns {Promise} A promise that contains the first page of results when fulfilled */ const axiosGetRequest = function (urlObservations, urlParamObj) { return axios.get(urlObservations, { params: urlParamObj, }); }; /** * Retrieve the metadata for a single datastream * @async * @param {String} urlDatastream A URL that fetches a Datastream from an STA instance * @returns {Promise} A promise that contains a metadata object for a Datastream when fulfilled */ const getDatastreamMetadata = async function (urlDatastream) { try { // Extract properties of interest const { data: { description, name, unitOfMeasurement }, } = await axiosGetRequest(urlDatastream); return { description, name, unitOfMeasurement }; } catch (err) { console.error(err); } }; /** * Format the response containing a Datastream's metadata from Sensorthings API * @param {Object} datastreamMetadata An object containing a Datastream's metadata * @returns {Object} An object containing the formatted metadata that is suitable for use in a line chart or heatmap */ const formatDatastreamMetadataForChart = function (datastreamMetadata) { const { description: datastreamDescription, name: datastreamName, unitOfMeasurement, } = datastreamMetadata; // Extract phenomenon name from Datastream name const regex = /\/ (.*) DS/; const phenomenonName = datastreamName.match(regex)[1]; // use second element in array // Match the unitOfMeasurement's string representation of a symbol // to an actual symbol, where necessary const unitOfMeasurementSymbol = (() => { if (unitOfMeasurement.symbol === "degC") { return "℃"; } else if (unitOfMeasurement.symbol === "m3/h") { return "m3/h"; } else { return unitOfMeasurement.symbol; } })(); return { datastreamDescription, datastreamName, phenomenonName, unitOfMeasurementSymbol, }; }; /** * Format the response from SensorThings API to make it suitable for heatmap * @param {Array} obsArray Response from SensorThings API as array * @returns {Array} Array of formatted observations suitable for use in a heatmap */ const formatSTAResponseForHeatMap = function (obsArray) { if (!obsArray) return; const dataSTAFormatted = obsArray.map((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]; return [timestamp, hourOfDay, value]; }); return dataSTAFormatted; }; /** * Draw a heatmap using Highcharts library * @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata * @returns {undefined} undefined */ const drawHeatMapHC = function ( formattedObsArrayForHeatmap, formattedDatastreamMetadata ) { const { datastreamDescription: DATASTREAM_DESCRIPTION, datastreamName: DATASTREAM_NAME, phenomenonName: PHENOMENON_NAME, unitOfMeasurementSymbol: PHENOMENON_SYMBOL, } = formattedDatastreamMetadata; // Function returns the min and max observation values const { minObsValue: MINIMUM_VALUE_COLOR_AXIS, maxObsValue: MAXIMUM_VALUE_COLOR_AXIS, } = (() => { // The observation value is the third element in array const obsValueArr = formattedObsArrayForHeatmap.map((obs) => obs[2]); // Extract integer part const minValue = Math.trunc(Math.min(...obsValueArr)); const maxValue = Math.trunc(Math.max(...obsValueArr)); // Calculate the closest multiple of 5 const minObsValue = minValue - (minValue % 5); const maxObsValue = maxValue + (5 - (maxValue % 5)); return { minObsValue, maxObsValue }; })(); Highcharts.chart("chart-heatmap", { chart: { type: "heatmap", zoomType: "x", }, boost: { useGPUTranslations: true, }, title: { text: DATASTREAM_DESCRIPTION, align: "left", x: 40, }, subtitle: { text: DATASTREAM_NAME, 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, 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: MINIMUM_VALUE_COLOR_AXIS, max: MAXIMUM_VALUE_COLOR_AXIS, startOnTick: false, endOnTick: false, labels: { // format: "{value}℃", format: `{value}${PHENOMENON_SYMBOL}`, }, }, series: [ { data: formattedObsArrayForHeatmap, boostThreshold: 100, borderWidth: 0, nullColor: "#525252", colsize: 24 * 36e5, // one day tooltip: { headerFormat: `${PHENOMENON_NAME}
`, valueDecimals: 2, pointFormat: // "{point.x:%e %b, %Y} {point.y}:00: {point.value} ℃", `{point.x:%e %b, %Y} {point.y}:00: {point.value} ${PHENOMENON_SYMBOL}`, nullFormat: `{point.x:%e %b, %Y} {point.y}:00: null`, }, turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }, ], }); }; /** * Convert the observations' phenomenonTime from an ISO 8601 string to Unix epoch * @param {Array} obsArray Response from SensorThings API as array * @returns {Array} Array of formatted observations suitable for use in a line chart */ const formatSTAResponseForLineChart = function (obsArray) { if (!obsArray) return; const dataSTAFormatted = obsArray.map((result) => { const timestampObs = new Date(result[0].slice(0, -1)).getTime(); // slice() removes trailing "Z" character in timestamp const valueObs = result[1]; return [timestampObs, valueObs]; }); return dataSTAFormatted; }; /** * Draw a line chart using Highcharts library * @param {Array} formattedObsArrayForLineChart Response from SensorThings API formatted for use in a line chart * @param {Object} formattedDatastreamMetadata Object containing Datastream metadata * @returns {undefined} undefined */ const drawLineChartHC = function ( formattedObsArrayForLineChart, formattedDatastreamMetadata ) { const { datastreamDescription: DATASTREAM_DESCRIPTION, datastreamName: DATASTREAM_NAME, phenomenonName: PHENOMENON_NAME, unitOfMeasurementSymbol: PHENOMENON_SYMBOL, } = formattedDatastreamMetadata; Highcharts.stockChart("chart-line", { chart: { zoomType: "x", }, rangeSelector: { selected: 5, }, title: { text: DATASTREAM_DESCRIPTION, "align": "left", }, subtitle: { text: DATASTREAM_NAME, align: "left", }, series: [ { name: `${PHENOMENON_NAME} (${PHENOMENON_SYMBOL})`, data: formattedObsArrayForLineChart, tooltip: { valueDecimals: 2, }, turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release }, ], }); }; /** * Follows "@iot.nextLink" links in SensorThingsAPI's response * Appends new results to existing results * @async * @param {Promise} responsePromise Promise object resulting from an Axios GET request * @returns {Object} Object containing results from all the "@iot.nextLink" links */ const followNextLink = function (responsePromise) { if (!responsePromise) return; return responsePromise .then((lastSuccess) => { if (lastSuccess.data["@iot.nextLink"]) { return followNextLink( axios.get(lastSuccess.data["@iot.nextLink"]) ).then((nextLinkSuccess) => { nextLinkSuccess.data.value = lastSuccess.data.value.concat( nextLinkSuccess.data.value ); return nextLinkSuccess; }); } else { return lastSuccess; } }) .catch((err) => { console.error(err); }); }; /** * Retrieve all the Observations from a Datastream after traversing all the "@iot.nextLink" links * @async * @param {Promise} httpGetRequestPromise Promise object resulting from an Axios GET request * @returns {Promise} A promise that contains an array of Observations from a single Datastream when fulfilled */ const getCombinedObservationsFromAllNextLinks = function ( httpGetRequestPromise ) { return followNextLink(httpGetRequestPromise) .then((success) => { const successValue = success.data.value; // Array that will hold the combined observations const combinedObservations = []; successValue.forEach((dataObj) => { // Each page of results will have a dataArray that holds the observations const dataArrays = dataObj.dataArray; combinedObservations.push(...dataArrays); }); return new Promise((resolve, reject) => { resolve(combinedObservations); }); }) .catch((err) => { console.error(err); }); }; /** * Retrieve the metadata for a Datastream as well as the Observations corresponding to it * @async * @param {Promise} metadataPlusObsPromiseArray An array that contains two promises, one for datastream metadata, the other for observations * @returns {Promise} A promise that contains two arrays when fulfilled, one for datastream metadata and the other for observations */ const getMetadataPlusObservationsForChart = async function ( metadataPlusObsPromiseArray ) { // Array to store our final result const combinedResolvedPromises = []; // Use for/of loop - we need to maintain the order of execution of the async operations for (const promise of metadataPlusObsPromiseArray) { try { // Resolved value of a single promise const resolvedPromise = await promise; combinedResolvedPromises.push(resolvedPromise); } catch (err) { console.error(err); } } return combinedResolvedPromises; }; /** * Retrieve all the Observations from an array of Observations promises * @async * @param {Promise} observationPromiseArray An array that contains N observation promises * @returns {Promise} A promise that contains an array of Observations from multiple Datastreams when fulfilled */ const getObservationsFromMultipleDatastreams = async function ( observationPromiseArray ) { // Array to store our final result const observationsAllDatastreamsArr = []; // Use for/of loop - we need to maintain the order of execution of the async operations for (const observationPromise of observationPromiseArray) { try { // Observations from a single Datastream const observations = await observationPromise; observationsAllDatastreamsArr.push(observations); } catch (err) { console.error(err); } } return observationsAllDatastreamsArr; }; // Building + phenomenon + sampling rate const buildingsSensorSamplingRateRLArr = [ ["101", "rl", "60min"], ["102", "rl", "60min"], ["107", "rl", "60min"], ["112, 118", "rl", "60min"], ["125", "rl", "60min"], ["225", "rl", "60min"], ]; // Datastreams IDs const datastreamsRLArr = buildingsSensorSamplingRateRLArr.map((bldg) => getDatastreamIdFromBuildingNumber(...bldg) ); // Datastreams URLs const datastreamsUrlRLArr = datastreamsRLArr.map((datastreamId) => getObservationsUrl(BASE_URL, datastreamId) ); // Promise objects - Observations / RL const observationsPromisesRLArr = datastreamsUrlRLArr.map((obsUrl) => getCombinedObservationsFromAllNextLinks( axiosGetRequest(obsUrl, QUERY_PARAMS_COMBINED) ) ); // getObservationsFromMultipleDatastreams(observationsPromisesRLArr).then((x) => // console.log(x) // ); const drawScatterPlotHC = function () { Highcharts.chart("chart-scatter-plot", { chart: { type: "scatter", zoomType: "xy", }, title: { text: "Height Versus Weight of 507 Individuals by Gender", }, subtitle: { text: "Source: Heinz 2003", }, xAxis: { title: { enabled: true, text: "Height (cm)", }, startOnTick: true, endOnTick: true, showLastLabel: true, }, yAxis: { title: { text: "Weight (kg)", }, }, legend: { layout: "vertical", align: "left", verticalAlign: "top", x: 100, y: 70, floating: true, backgroundColor: Highcharts.defaultOptions.chart.backgroundColor, borderWidth: 1, }, plotOptions: { scatter: { marker: { radius: 5, states: { hover: { enabled: true, lineColor: "rgb(100,100,100)", }, }, }, states: { hover: { marker: { enabled: false, }, }, }, tooltip: { headerFormat: "{series.name}
", pointFormat: "{point.x} cm, {point.y} kg", }, }, }, series: [ { name: "Female", color: "rgba(223, 83, 83, .5)", data: [ [161.2, 51.6], [167.5, 59.0], [159.5, 49.2], [157.0, 63.0], [155.8, 53.6], [170.0, 59.0], [159.1, 47.6], [166.0, 69.8], [176.2, 66.8], [160.2, 75.2], [172.5, 55.2], [170.9, 54.2], [172.9, 62.5], [153.4, 42.0], [160.0, 50.0], [147.2, 49.8], [168.2, 49.2], [175.0, 73.2], [157.0, 47.8], [167.6, 68.8], [159.5, 50.6], [175.0, 82.5], [166.8, 57.2], [176.5, 87.8], [170.2, 72.8], [174.0, 54.5], [173.0, 59.8], [179.9, 67.3], [170.5, 67.8], [160.0, 47.0], [154.4, 46.2], [162.0, 55.0], [176.5, 83.0], [160.0, 54.4], [152.0, 45.8], [162.1, 53.6], [170.0, 73.2], [160.2, 52.1], [161.3, 67.9], [166.4, 56.6], [168.9, 62.3], [163.8, 58.5], [167.6, 54.5], [160.0, 50.2], [161.3, 60.3], [167.6, 58.3], [165.1, 56.2], [160.0, 50.2], [170.0, 72.9], [157.5, 59.8], [167.6, 61.0], [160.7, 69.1], [163.2, 55.9], [152.4, 46.5], [157.5, 54.3], [168.3, 54.8], [180.3, 60.7], [165.5, 60.0], [165.0, 62.0], [164.5, 60.3], [156.0, 52.7], [160.0, 74.3], [163.0, 62.0], [165.7, 73.1], [161.0, 80.0], [162.0, 54.7], [166.0, 53.2], [174.0, 75.7], [172.7, 61.1], [167.6, 55.7], [151.1, 48.7], [164.5, 52.3], [163.5, 50.0], [152.0, 59.3], [169.0, 62.5], [164.0, 55.7], [161.2, 54.8], [155.0, 45.9], [170.0, 70.6], [176.2, 67.2], [170.0, 69.4], [162.5, 58.2], [170.3, 64.8], [164.1, 71.6], [169.5, 52.8], [163.2, 59.8], [154.5, 49.0], [159.8, 50.0], [173.2, 69.2], [170.0, 55.9], [161.4, 63.4], [169.0, 58.2], [166.2, 58.6], [159.4, 45.7], [162.5, 52.2], [159.0, 48.6], [162.8, 57.8], [159.0, 55.6], [179.8, 66.8], [162.9, 59.4], [161.0, 53.6], [151.1, 73.2], [168.2, 53.4], [168.9, 69.0], [173.2, 58.4], [171.8, 56.2], [178.0, 70.6], [164.3, 59.8], [163.0, 72.0], [168.5, 65.2], [166.8, 56.6], [172.7, 105.2], [163.5, 51.8], [169.4, 63.4], [167.8, 59.0], [159.5, 47.6], [167.6, 63.0], [161.2, 55.2], [160.0, 45.0], [163.2, 54.0], [162.2, 50.2], [161.3, 60.2], [149.5, 44.8], [157.5, 58.8], [163.2, 56.4], [172.7, 62.0], [155.0, 49.2], [156.5, 67.2], [164.0, 53.8], [160.9, 54.4], [162.8, 58.0], [167.0, 59.8], [160.0, 54.8], [160.0, 43.2], [168.9, 60.5], [158.2, 46.4], [156.0, 64.4], [160.0, 48.8], [167.1, 62.2], [158.0, 55.5], [167.6, 57.8], [156.0, 54.6], [162.1, 59.2], [173.4, 52.7], [159.8, 53.2], [170.5, 64.5], [159.2, 51.8], [157.5, 56.0], [161.3, 63.6], [162.6, 63.2], [160.0, 59.5], [168.9, 56.8], [165.1, 64.1], [162.6, 50.0], [165.1, 72.3], [166.4, 55.0], [160.0, 55.9], [152.4, 60.4], [170.2, 69.1], [162.6, 84.5], [170.2, 55.9], [158.8, 55.5], [172.7, 69.5], [167.6, 76.4], [162.6, 61.4], [167.6, 65.9], [156.2, 58.6], [175.2, 66.8], [172.1, 56.6], [162.6, 58.6], [160.0, 55.9], [165.1, 59.1], [182.9, 81.8], [166.4, 70.7], [165.1, 56.8], [177.8, 60.0], [165.1, 58.2], [175.3, 72.7], [154.9, 54.1], [158.8, 49.1], [172.7, 75.9], [168.9, 55.0], [161.3, 57.3], [167.6, 55.0], [165.1, 65.5], [175.3, 65.5], [157.5, 48.6], [163.8, 58.6], [167.6, 63.6], [165.1, 55.2], [165.1, 62.7], [168.9, 56.6], [162.6, 53.9], [164.5, 63.2], [176.5, 73.6], [168.9, 62.0], [175.3, 63.6], [159.4, 53.2], [160.0, 53.4], [170.2, 55.0], [162.6, 70.5], [167.6, 54.5], [162.6, 54.5], [160.7, 55.9], [160.0, 59.0], [157.5, 63.6], [162.6, 54.5], [152.4, 47.3], [170.2, 67.7], [165.1, 80.9], [172.7, 70.5], [165.1, 60.9], [170.2, 63.6], [170.2, 54.5], [170.2, 59.1], [161.3, 70.5], [167.6, 52.7], [167.6, 62.7], [165.1, 86.3], [162.6, 66.4], [152.4, 67.3], [168.9, 63.0], [170.2, 73.6], [175.2, 62.3], [175.2, 57.7], [160.0, 55.4], [165.1, 104.1], [174.0, 55.5], [170.2, 77.3], [160.0, 80.5], [167.6, 64.5], [167.6, 72.3], [167.6, 61.4], [154.9, 58.2], [162.6, 81.8], [175.3, 63.6], [171.4, 53.4], [157.5, 54.5], [165.1, 53.6], [160.0, 60.0], [174.0, 73.6], [162.6, 61.4], [174.0, 55.5], [162.6, 63.6], [161.3, 60.9], [156.2, 60.0], [149.9, 46.8], [169.5, 57.3], [160.0, 64.1], [175.3, 63.6], [169.5, 67.3], [160.0, 75.5], [172.7, 68.2], [162.6, 61.4], [157.5, 76.8], [176.5, 71.8], [164.4, 55.5], [160.7, 48.6], [174.0, 66.4], [163.8, 67.3], ], }, { name: "Male", color: "rgba(119, 152, 191, .5)", data: [ [174.0, 65.6], [175.3, 71.8], [193.5, 80.7], [186.5, 72.6], [187.2, 78.8], [181.5, 74.8], [184.0, 86.4], [184.5, 78.4], [175.0, 62.0], [184.0, 81.6], [180.0, 76.6], [177.8, 83.6], [192.0, 90.0], [176.0, 74.6], [174.0, 71.0], [184.0, 79.6], [192.7, 93.8], [171.5, 70.0], [173.0, 72.4], [176.0, 85.9], [176.0, 78.8], [180.5, 77.8], [172.7, 66.2], [176.0, 86.4], [173.5, 81.8], [178.0, 89.6], [180.3, 82.8], [180.3, 76.4], [164.5, 63.2], [173.0, 60.9], [183.5, 74.8], [175.5, 70.0], [188.0, 72.4], [189.2, 84.1], [172.8, 69.1], [170.0, 59.5], [182.0, 67.2], [170.0, 61.3], [177.8, 68.6], [184.2, 80.1], [186.7, 87.8], [171.4, 84.7], [172.7, 73.4], [175.3, 72.1], [180.3, 82.6], [182.9, 88.7], [188.0, 84.1], [177.2, 94.1], [172.1, 74.9], [167.0, 59.1], [169.5, 75.6], [174.0, 86.2], [172.7, 75.3], [182.2, 87.1], [164.1, 55.2], [163.0, 57.0], [171.5, 61.4], [184.2, 76.8], [174.0, 86.8], [174.0, 72.2], [177.0, 71.6], [186.0, 84.8], [167.0, 68.2], [171.8, 66.1], [182.0, 72.0], [167.0, 64.6], [177.8, 74.8], [164.5, 70.0], [192.0, 101.6], [175.5, 63.2], [171.2, 79.1], [181.6, 78.9], [167.4, 67.7], [181.1, 66.0], [177.0, 68.2], [174.5, 63.9], [177.5, 72.0], [170.5, 56.8], [182.4, 74.5], [197.1, 90.9], [180.1, 93.0], [175.5, 80.9], [180.6, 72.7], [184.4, 68.0], [175.5, 70.9], [180.6, 72.5], [177.0, 72.5], [177.1, 83.4], [181.6, 75.5], [176.5, 73.0], [175.0, 70.2], [174.0, 73.4], [165.1, 70.5], [177.0, 68.9], [192.0, 102.3], [176.5, 68.4], [169.4, 65.9], [182.1, 75.7], [179.8, 84.5], [175.3, 87.7], [184.9, 86.4], [177.3, 73.2], [167.4, 53.9], [178.1, 72.0], [168.9, 55.5], [157.2, 58.4], [180.3, 83.2], [170.2, 72.7], [177.8, 64.1], [172.7, 72.3], [165.1, 65.0], [186.7, 86.4], [165.1, 65.0], [174.0, 88.6], [175.3, 84.1], [185.4, 66.8], [177.8, 75.5], [180.3, 93.2], [180.3, 82.7], [177.8, 58.0], [177.8, 79.5], [177.8, 78.6], [177.8, 71.8], [177.8, 116.4], [163.8, 72.2], [188.0, 83.6], [198.1, 85.5], [175.3, 90.9], [166.4, 85.9], [190.5, 89.1], [166.4, 75.0], [177.8, 77.7], [179.7, 86.4], [172.7, 90.9], [190.5, 73.6], [185.4, 76.4], [168.9, 69.1], [167.6, 84.5], [175.3, 64.5], [170.2, 69.1], [190.5, 108.6], [177.8, 86.4], [190.5, 80.9], [177.8, 87.7], [184.2, 94.5], [176.5, 80.2], [177.8, 72.0], [180.3, 71.4], [171.4, 72.7], [172.7, 84.1], [172.7, 76.8], [177.8, 63.6], [177.8, 80.9], [182.9, 80.9], [170.2, 85.5], [167.6, 68.6], [175.3, 67.7], [165.1, 66.4], [185.4, 102.3], [181.6, 70.5], [172.7, 95.9], [190.5, 84.1], [179.1, 87.3], [175.3, 71.8], [170.2, 65.9], [193.0, 95.9], [171.4, 91.4], [177.8, 81.8], [177.8, 96.8], [167.6, 69.1], [167.6, 82.7], [180.3, 75.5], [182.9, 79.5], [176.5, 73.6], [186.7, 91.8], [188.0, 84.1], [188.0, 85.9], [177.8, 81.8], [174.0, 82.5], [177.8, 80.5], [171.4, 70.0], [185.4, 81.8], [185.4, 84.1], [188.0, 90.5], [188.0, 91.4], [182.9, 89.1], [176.5, 85.0], [175.3, 69.1], [175.3, 73.6], [188.0, 80.5], [188.0, 82.7], [175.3, 86.4], [170.5, 67.7], [179.1, 92.7], [177.8, 93.6], [175.3, 70.9], [182.9, 75.0], [170.8, 93.2], [188.0, 93.2], [180.3, 77.7], [177.8, 61.4], [185.4, 94.1], [168.9, 75.0], [185.4, 83.6], [180.3, 85.5], [174.0, 73.9], [167.6, 66.8], [182.9, 87.3], [160.0, 72.3], [180.3, 88.6], [167.6, 75.5], [186.7, 101.4], [175.3, 91.1], [175.3, 67.3], [175.9, 77.7], [175.3, 81.8], [179.1, 75.5], [181.6, 84.5], [177.8, 76.6], [182.9, 85.0], [177.8, 102.5], [184.2, 77.3], [179.1, 71.8], [176.5, 87.9], [188.0, 94.3], [174.0, 70.9], [167.6, 64.5], [170.2, 77.3], [167.6, 72.3], [188.0, 87.3], [174.0, 80.0], [176.5, 82.3], [180.3, 73.6], [167.6, 74.1], [188.0, 85.9], [180.3, 73.2], [167.6, 76.3], [183.0, 65.9], [183.0, 90.9], [179.1, 89.1], [170.2, 62.3], [177.8, 82.7], [179.1, 79.1], [190.5, 98.2], [177.8, 84.1], [180.3, 83.2], [180.3, 83.2], ], }, ], }); }; drawScatterPlotHC(); export { BASE_URL, QUERY_PARAMS_COMBINED, getDatastreamIdFromBuildingNumber, getDatastreamUrl, getObservationsUrl, createTemporalFilterString, axiosGetRequest, getDatastreamMetadata, formatDatastreamMetadataForChart, formatSTAResponseForHeatMap, drawHeatMapHC, formatSTAResponseForLineChart, drawLineChartHC, getCombinedObservationsFromAllNextLinks, getMetadataPlusObservationsForChart, };