diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/days-before-shortcuts.js b/assets/src/scripts/monitoring-location/components/hydrograph/days-before-shortcuts.js index a2421fd47dc142239c0808dfc5f638245b5e80d4..d9805eb614e24f5c9cf050c4294f9ed590b26fb3 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/days-before-shortcuts.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/days-before-shortcuts.js @@ -19,7 +19,7 @@ const SHORTCUT_BUTTONS = [ * selectedTimeSpan and fetching the new data. Set up event handler * for a change to the selectedTimeSpan in store to update the state of the radio button. */ -const drawShortcutRadioButton = function(container, store, siteno, {timeSpan, label}) { +const drawShortcutRadioButton = function(container, store, siteno, agencyCode, {timeSpan, label}) { const buttonContainer = container.append('div') .attr('class', 'usa-radio'); buttonContainer.append('input') @@ -33,7 +33,7 @@ const drawShortcutRadioButton = function(container, store, siteno, {timeSpan, la store.dispatch(setSelectedTimeSpan(timeSpan)); store.dispatch(clearGraphBrushOffset()); showDataIndicators(true, store); - store.dispatch(retrieveHydrographData(siteno, getInputsForRetrieval(store.getState()))) + store.dispatch(retrieveHydrographData(siteno, agencyCode, getInputsForRetrieval(store.getState()))) .then(() => { showDataIndicators(false, store); }); @@ -57,8 +57,8 @@ const drawShortcutRadioButton = function(container, store, siteno, {timeSpan, la * @param {Redux store} store * @param {String} siteno */ -export const drawShortcutDaysBeforeButtons = function(container, store, siteno) { +export const drawShortcutDaysBeforeButtons = function(container, store, siteno, agencyCode) { const formContainer = container.append('div') .attr('class', 'usa-form'); - SHORTCUT_BUTTONS.forEach(shortcut => drawShortcutRadioButton(formContainer, store, siteno, shortcut)); + SHORTCUT_BUTTONS.forEach(shortcut => drawShortcutRadioButton(formContainer, store, siteno, agencyCode, shortcut)); }; diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/index.js b/assets/src/scripts/monitoring-location/components/hydrograph/index.js index fb758f8ac8a9d83095b8bc09488ee70cf3ea8701..8c9ac6033b4d7cd183ccec528c8875382333d796 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/index.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/index.js @@ -13,12 +13,12 @@ import {renderTimeSeriesUrlParams} from 'ml/url-params'; import {retrieveHydrographData} from 'ml/store/hydrograph-data'; import {retrieveHydrographParameters} from 'ml/store/hydrograph-parameters'; import {setSelectedParameterCode, setCompareDataVisibility, setSelectedTimeSpan, - setSelectedIVMethodID, setSelectedIVMethodDescription + setSelectedIVMethodID } from 'ml/store/hydrograph-state'; import {Actions as floodDataActions} from 'ml/store/flood-inundation'; -import {getPreferredIVMethodID, getSelectedMethodDescription} from './selectors/time-series-data'; +import {getPreferredIVMethodID} from './selectors/time-series-data'; import {showDataIndicators} from './data-indicator'; import {drawDataTables} from './data-table'; @@ -58,7 +58,7 @@ export const attachToNode = function(store, const nodeElem = select(node); if (!config.ivPeriodOfRecord && !config.gwPeriodOfRecord) { select(node).select('.graph-container').call(drawInfoAlert, {title: 'Hydrograph Alert', body: 'No IV or field visit data is available.'}); - select(node).select('.select-actions-container').call(drawSelectActions, store, siteno); + select(node).select('.select-actions-container').call(drawSelectActions, store, siteno, agencyCd); return; } @@ -131,10 +131,10 @@ export const attachToNode = function(store, .call(drawTimeSeriesLegend, store); if (!thisShowOnlyGraph) { - nodeElem.select('.short-cut-days-before-container').call(drawShortcutDaysBeforeButtons, store, siteno); - nodeElem.select('.select-actions-container').call(drawSelectActions, store, siteno); + nodeElem.select('.short-cut-days-before-container').call(drawShortcutDaysBeforeButtons, store, siteno, agencyCd); + nodeElem.select('.select-actions-container').call(drawSelectActions, store, siteno, agencyCd); - legendControlsContainer.call(drawGraphControls, store, siteno); + legendControlsContainer.call(drawGraphControls, store, siteno, agencyCd); nodeElem.select('#iv-data-table-container') .call(drawDataTables, store); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/select-actions.js b/assets/src/scripts/monitoring-location/components/hydrograph/select-actions.js index 06a54005c86c26155ea5e64a3089f42ea18232c2..59e31880e55f57ee781be2c5f6e39fcf6c6ef9ea 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/select-actions.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/select-actions.js @@ -53,7 +53,7 @@ const appendButton = function(listContainer, {faIcon, buttonLabel, idOfDivToCont * @param {Redux store} store * @param {String} siteno */ -export const drawSelectActions = function(container, store, siteno) { +export const drawSelectActions = function(container, store, siteno, agencyCode) { const listContainer = container.append('ul') .attr('class', 'select-actions-button-group usa-button-group'); if (config.ivPeriodOfRecord || config.gwPeriodOfRecord) { @@ -64,7 +64,7 @@ export const drawSelectActions = function(container, store, siteno) { container.append('div') .attr('id', 'change-time-span-container') .attr('hidden', true) - .call(drawTimeSpanControls, store, siteno); + .call(drawTimeSpanControls, store, siteno, agencyCode); } appendButton(listContainer, { diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js index fa1bf61a6138aae8b70025bf037587cd6b011739..84cf62278f7495e8d4bd3c056e1ff98ee5428502 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/legend-data.js @@ -11,6 +11,9 @@ import {isCompareIVDataVisible, isMedianDataVisible} from 'ml/selectors/hydrogra import {getUniqueGWKinds} from './discrete-data'; import {getFloodLevelData} from './flood-level-data'; import {getIVUniqueDataKinds, HASH_ID} from './iv-data'; +import {getThresholdsInRange} from './thresholds-data'; + + const TS_LABEL = { 'primary': 'Current: ', @@ -29,6 +32,7 @@ const TS_LABEL = { const getLegendDisplay = createSelector( isCompareIVDataVisible, isMedianDataVisible, + getThresholdsInRange, getPrimaryMedianStatisticsData, getIVUniqueDataKinds('primary'), getIVUniqueDataKinds('compare'), @@ -36,8 +40,9 @@ const getLegendDisplay = createSelector( getFloodLevelData, getUniqueGWKinds, getPrimaryParameter, - (showCompare, showMedian, medianSeries, currentClasses, compareClasses, showWaterWatch, floodLevels, gwLevelKinds, + (showCompare, showMedian, thresholds, medianSeries, currentClasses, compareClasses, showWaterWatch, floodLevels, gwLevelKinds, primaryParameter) => { + console.log('in getLegendDisplay thresholds', thresholds) const parameterCode = primaryParameter ? primaryParameter.parameterCode : null; const hasIVData = config.ivPeriodOfRecord && parameterCode ? parameterCode in config.ivPeriodOfRecord : false; const hasGWLevelsData = config.gwPeriodOfRecord && parameterCode ? parameterCode in config.gwPeriodOfRecord : false; @@ -46,7 +51,8 @@ const getLegendDisplay = createSelector( compareIV: hasIVData && showCompare ? compareClasses : undefined, median: showMedian ? medianSeries : undefined, floodLevels: showWaterWatch ? floodLevels : undefined, - groundwaterLevels: hasGWLevelsData ? gwLevelKinds : undefined + groundwaterLevels: hasGWLevelsData ? gwLevelKinds : undefined, + thresholds: thresholds }; } ); @@ -124,6 +130,29 @@ const getFloodLevelMarkers = function(floodLevels) { }); }; +const getThresholdMarkers = function(thresholds) { + const THRESHOLD_LABELS = { + 'Operational limit - low-Public': 'Minimum operating limit', + 'Operational limit - high-Public': 'Maximum operating limit' + }; + + return thresholds.map((threshold) => { + if (threshold.name.includes('Operational')) { + + const labelClass = threshold.name.split('Operational ')[1].toLowerCase().split(' ').join(''); + + return [ + defineTextOnlyMarker(THRESHOLD_LABELS[threshold.name]), + defineLineMarker( + null, + `threshold-data-series ${labelClass}`, + `${threshold.value} ${threshold.unit}`) + ]; + } + }); +}; + + /* * Factory function that returns an array of array of markers to be used for the * time series graph legend @@ -132,13 +161,15 @@ const getFloodLevelMarkers = function(floodLevels) { export const getLegendMarkerRows = createSelector( getLegendDisplay, displayItems => { + console.log('display items ', displayItems) const markerRows = []; const currentTsMarkerRow = displayItems.primaryIV ? getIVMarkers('primary', displayItems.primaryIV) : undefined; const compareTsMarkerRow = displayItems.compareIV ? getIVMarkers('compare', displayItems.compareIV) : undefined; const medianMarkerRows = displayItems.median ? getMedianMarkers(displayItems.median) : []; const floodMarkerRows = displayItems.floodLevels ? getFloodLevelMarkers(displayItems.floodLevels) : []; const gwLevelMarkerRows = displayItems.groundwaterLevels ? getGWMarkers(displayItems.groundwaterLevels) : undefined; - + const thresholdMarkerRows = displayItems.thresholds ? getThresholdMarkers(displayItems.thresholds) : undefined; +console.log('thresholdMarkerRows ', thresholdMarkerRows) if (currentTsMarkerRow) { markerRows.push([defineTextOnlyMarker(TS_LABEL['primary'])].concat(currentTsMarkerRow)); } @@ -148,7 +179,8 @@ export const getLegendMarkerRows = createSelector( if (compareTsMarkerRow) { markerRows.push([defineTextOnlyMarker(TS_LABEL['compare'])].concat(compareTsMarkerRow)); } - markerRows.push(...medianMarkerRows, ...floodMarkerRows); + markerRows.push(...medianMarkerRows, ...floodMarkerRows, ...thresholdMarkerRows); + console.log('markerRows ', markerRows) return markerRows; } ); diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/selectors/thresholds-data.js b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/thresholds-data.js new file mode 100644 index 0000000000000000000000000000000000000000..d091803a880c97fc74a91805dbff8c60807a9516 --- /dev/null +++ b/assets/src/scripts/monitoring-location/components/hydrograph/selectors/thresholds-data.js @@ -0,0 +1,33 @@ +import {createSelector} from 'reselect'; + +import {getMainYScale} from './scales'; +import {getPrimaryParameter, getThresholds} from 'ml/selectors/hydrograph-data-selector'; +import {getSelectedMethodDescription} from './time-series-data'; + +export const getThresholdsInRange = createSelector( + getPrimaryParameter, + getSelectedMethodDescription, + getMainYScale, + getThresholds, + (selectedParameter, selectedMethodDescription, yScale, thresholds) => { + const [yStart, yEnd] = yScale.domain(); + let limits = []; + if (thresholds) { + thresholds.operatingLimits.forEach(limitSet => { + if ( + selectedParameter !== null && + limitSet.parameterCode === selectedParameter.parameterCode && + limitSet.methodDescription === selectedMethodDescription.methodDescription + ) { + limits = limitSet.thresholdDetails.map(detail => { + return { + name: detail.referenceCode, + value: detail.referenceValue, + unit: selectedParameter.unit + }; + }); + } + }); + } + return limits; + }); \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/threshold-lines.js b/assets/src/scripts/monitoring-location/components/hydrograph/threshold-lines.js index d1901c71726ad28b1838669eead4347d46c6cb32..57ec025f140acae4ad2af7ff2cde099e5f871d7b 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/threshold-lines.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/threshold-lines.js @@ -2,28 +2,10 @@ import {line as d3Line} from 'd3-shape'; export const drawThresholdLines = function(svg, {xscale, yscale, parameterInformation, selectedMethodDescription, thresholds}) { - // thresholds = { - // 'operatingLimits': [ - // { - // 'parameterCode': '00065', - // 'methodDescription': '[High-flow backup abv 6.25 ft]', - // 'thresholdDetails': [ - // { - // 'name': 'Operational limit (minimum)', - // 'referenceCode': 'Operational limit - low-Public', - // 'referenceValue': 6.23 - // }, - // { - // 'name': 'Operational limit (maximum)', - // 'referenceCode': 'Operational limit - high-Public', - // 'referenceValue': 6.25 - // } - // ] - // } - // ] - // }; +if (parameterInformation !== undefined && parameterInformation !== null) { console.log('in draw thresholds local values ', `parameter: ${parameterInformation.parameterCode}, method description: ${selectedMethodDescription.methodDescription}`) +} svg.select('#threshold-lines').remove(); const container = svg.append('g') @@ -35,17 +17,17 @@ export const drawThresholdLines = function(svg, {xscale, yscale, parameterInform // } const xRange = xscale.range(); - const [yStart, yEnd] = yscale.domain(); - - thresholds.operatingLimits.forEach(limitSet => { - console.log('limit set from API', limitSet) - if ( - parameterInformation !== null && - limitSet.parameterCode === parameterInformation.parameterCode && - limitSet.methodDescription === selectedMethodDescription.methodDescription - ) { - console.log('got match ') - limitSet.thresholdDetails.forEach(limit => { + + if (thresholds) { + thresholds.operatingLimits.forEach(limitSet => { + + if ( + parameterInformation !== null && + limitSet.parameterCode === parameterInformation.parameterCode && + limitSet.methodDescription === selectedMethodDescription.methodDescription + ) { + + limitSet.thresholdDetails.forEach(limit => { const referenceCodeClass = limit.referenceCode.split('Operational ')[1].toLowerCase().split(' ').join(''); const group = container.append('g'); const yRange = yscale(limit.referenceValue); @@ -55,7 +37,8 @@ export const drawThresholdLines = function(svg, {xscale, yscale, parameterInform .classed(referenceCodeClass, true) .attr('d', thresholdLine); - }); - } - }); + }); + } + }); + } }; \ No newline at end of file diff --git a/assets/src/scripts/monitoring-location/components/hydrograph/time-span-controls.js b/assets/src/scripts/monitoring-location/components/hydrograph/time-span-controls.js index 821fd511682eb840bf4c3d3529e855816f8bc5bb..10e6d6d2ffa3485e673add49bdbd56548a0a6887 100644 --- a/assets/src/scripts/monitoring-location/components/hydrograph/time-span-controls.js +++ b/assets/src/scripts/monitoring-location/components/hydrograph/time-span-controls.js @@ -125,7 +125,7 @@ const drawDaysBeforeTodayForm = function(container, store) { * @param {Redux store} store * @param {String} siteno */ -const updateSelectedTimeSpan = function(container, store, siteno) { +const updateSelectedTimeSpan = function(container, store, siteno, agencyCode) { const startDateStr = select('#start-date').property('value'); const endDateStr = select('#end-date').property('value'); const daysBeforeToday = select('#days-before-today').property('value'); @@ -183,7 +183,7 @@ const updateSelectedTimeSpan = function(container, store, siteno) { showDataIndicators(true, store); store.dispatch(clearGraphBrushOffset()); store.dispatch(setSelectedTimeSpan(newTimeSpan)); - store.dispatch(retrieveHydrographData(siteno, getInputsForRetrieval(store.getState()))) + store.dispatch(retrieveHydrographData(siteno, agencyCode, getInputsForRetrieval(store.getState()))) .then(() => { showDataIndicators(false, store); }); @@ -197,7 +197,7 @@ const updateSelectedTimeSpan = function(container, store, siteno) { * @param {Redux store} store * @param {String} siteno */ -export const drawTimeSpanControls = function(container, store, siteno) { +export const drawTimeSpanControls = function(container, store, siteno, agencyCode) { const inputContainer = container.append('div') .attr('class', 'time-span-input-container'); inputContainer.append('div') @@ -215,6 +215,6 @@ export const drawTimeSpanControls = function(container, store, siteno) { .attr('ga-event-action', 'timeSpanChange') .text('Change time span') .on('click', function() { - container.call(updateSelectedTimeSpan, store, siteno); + container.call(updateSelectedTimeSpan, store, siteno, agencyCode); }); }; diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-data.js b/assets/src/scripts/monitoring-location/store/hydrograph-data.js index ff96631f332f277d1a237d9cb09179f302e8b86a..bc472465181b9aa38a0134c2a9086dd7c4202503 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-data.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-data.js @@ -299,24 +299,26 @@ const cleanThresholdData = function(properties) { export const retrieveHydrographThresholds = function(agencySiteNumberCode) { return function(dispatch) { return fetchDataFromSensorThings(agencySiteNumberCode).then((sensorThingsData) => { - const operatingLimits = JSON.parse(sensorThingsData).value[0].Datastreams.map(observedProperty => { - if (observedProperty.properties && observedProperty.properties.Thresholds) { - return { - parameterCode: observedProperty.properties.ParameterCode, - methodDescription: observedProperty.properties.WebDescription ? observedProperty.properties.WebDescription : - observedProperty.properties.SubLocationIdentifier ? observedProperty.properties.SubLocationIdentifier : '', - thresholdDetails: cleanThresholdData(observedProperty.properties) - }; - } - }).filter(thresholdData => { - if (thresholdData !== undefined && thresholdData.thresholdDetails.length) { - return thresholdData; - } - }); + if (Object.keys(sensorThingsData).length !== 0 ) { + const operatingLimits = JSON.parse(sensorThingsData).value[0].Datastreams.map(observedProperty => { + if (observedProperty.properties && observedProperty.properties.Thresholds) { + return { + parameterCode: observedProperty.properties.ParameterCode, + methodDescription: observedProperty.properties.WebDescription ? observedProperty.properties.WebDescription : + observedProperty.properties.SubLocationIdentifier ? observedProperty.properties.SubLocationIdentifier : '', + thresholdDetails: cleanThresholdData(observedProperty.properties) + }; + } + }).filter(thresholdData => { + if (thresholdData !== undefined && thresholdData.thresholdDetails.length) { + return thresholdData; + } + }); - dispatch(addHydrographThresholds({ - operatingLimits: operatingLimits - })); + dispatch(addHydrographThresholds({ + operatingLimits: operatingLimits + })); + } }); }; }; diff --git a/assets/src/scripts/monitoring-location/store/hydrograph-state.js b/assets/src/scripts/monitoring-location/store/hydrograph-state.js index 9ac1e3f2491979fe69ffb249ff0de23792dce4b9..3273818d8135e9b357daec3b3948fb2b09445a85 100644 --- a/assets/src/scripts/monitoring-location/store/hydrograph-state.js +++ b/assets/src/scripts/monitoring-location/store/hydrograph-state.js @@ -4,7 +4,6 @@ export const INITIAL_STATE = { selectedTimeSpan: 'P7D', selectedParameterCode: null, selectedIVMethodID: null, - selectedIVMethodDescription: null, graphCursorOffset: null, graphBrushOffset: null }; @@ -58,23 +57,6 @@ export const setSelectedIVMethodID = function(methodID) { }; }; -/* - * Synchronous action to set the selected method description (which, in combination with the parameter code, site ID, - * and agency code) can uniquely identify a IV time series). - * Some parameter codes have more than one IV time series. The Sensor Things API uses a combination of the - * parameter code and method description, site ID, and agency code to differentiate between them. - * @param {String} methodDescription - this is a text description. This description is not unique between time series, - * however no parameter code at any monitoring location will have two methods with the same description. - * @return {Object} - Redux action - */ -export const setSelectedIVMethodDescription = function(methodDescription) { - console.log('set ', methodDescription) - return { - type: 'SET_SELECTED_IV_METHOD_DESCRIPTION', - methodDescription - }; -}; - /* * Synchronous action sets the time span of the hydrograph. * @param {String or Object} timeSpan - Can either be a String representing an ISO8601 Duration or an @@ -151,12 +133,6 @@ export const hydrographStateReducer = function(hydrographState=INITIAL_STATE, ac selectedIVMethodID: action.methodID }; - case 'SET_SELECTED_IV_METHOD_DESCRIPTION': - return { - ...hydrographState, - selectedIVMethodDescription: action.methodDescription - }; - case 'SET_SELECTED_TIME_SPAN': if (typeof action.timeSpan === 'string') { return { diff --git a/assets/src/scripts/web-services/sensor-things.js b/assets/src/scripts/web-services/sensor-things.js index 5ed90f87d9f40a892fd35a00673c2c1cdc171e14..a2a6206548204ed07b2bec8263a92991e962df94 100644 --- a/assets/src/scripts/web-services/sensor-things.js +++ b/assets/src/scripts/web-services/sensor-things.js @@ -1,14 +1,18 @@ import {get} from 'ui/ajax'; import config from 'ui/config'; + +import {MOCK_SENSOR_THINGS_DATA} from "../mock-service-data"; + + export const getSensorThingsURL = function(agencySiteNumberCode) { return `${config.SENSOR_THINGS_ENDPOINT}?$filter=name%20eq%20%27${agencySiteNumberCode}%27&$expand=Datastreams($select=name,description,properties,@iot.id)`; }; export const fetchDataFromSensorThings = function(agencySiteNumberCode) { return get(getSensorThingsURL(agencySiteNumberCode)) - .catch(reason => { - console.error(reason); - return {}; + .catch(error => { + console.error('Sensor Things did not return expected data. Reason: ', error.message); + return MOCK_SENSOR_THINGS_DATA; }); }; diff --git a/wdfn-server/config.py b/wdfn-server/config.py index 89257c5a900904bb6dc9afdd6847e3b30d8a763f..bc08283f370fc2566c627cb305cbbd70af6876ae 100644 --- a/wdfn-server/config.py +++ b/wdfn-server/config.py @@ -25,7 +25,9 @@ HISTORICAL_IV_DATA_ENDPOINT = 'https://nwis.waterservices.usgs.gov/nwis/iv' STATISTICS_ENDPOINT = 'https://waterservices.usgs.gov/nwis/stat' GROUNDWATER_LEVELS_ENDPOINT = 'https://waterservices.usgs.gov/nwis/gwlevels/' MONITORING_LOCATIONS_OBSERVATIONS_ENDPOINT = 'https://labs.waterdata.usgs.gov/api/observations/collections/', -SENSOR_THINGS_ENDPOINT = 'https://iow-frost-read-prod-external.wma.chs.usgs.gov/FROST-Server/v1.1/Things' +SENSOR_THINGS_ENDPOINT = 'https://labs.waterdata.usgs.gov/sta/v1.1/Things', +old = 'https://iow-frost-read-prod-external.wma.chs.usgs.gov/FROST-Server/v1.1/Things' + NWISWEB_ENDPOINTS = { 'INVENTORY': 'https://waterdata.usgs.gov/nwis/inventory',