Skip to content

Commit

Permalink
Merge pull request #299 from RENCI/issue-273-timeseries-extract
Browse files Browse the repository at this point in the history
Timeseries extract
  • Loading branch information
lstillwe authored Oct 18, 2024
2 parents 461a2a8 + df41d0d commit 0d379cb
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 28 deletions.
8 changes: 4 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { Sidebar } from '@components/sidebar';
import { ControlPanel } from '@components/control-panel';
import { ComparePanel } from '@components/compare-panel';
import { MapLegend } from '@components/legend';


import { AlertUser } from '@components/alert-user';

/**
* renders the main content
*
* @returns {JSX.Element}
* @returns JSX.Element
* @constructor
*/
const Content = () => {
Expand All @@ -33,6 +32,7 @@ const Content = () => {
return <ObservationDialog key={obs["station_name"]} obs={obs} />;
})
}
<AlertUser />
<Map />
<Sidebar />
<ControlPanel/>
Expand All @@ -45,7 +45,7 @@ const Content = () => {
/**
* renders the application
*
* @returns {JSX.Element}
* @returns JSX.Element
* @constructor
*/
export const App = () => {
Expand Down
23 changes: 23 additions & 0 deletions src/components/alert-user/alert-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { Fragment } from "react";
import { useLayers } from "@context";
import { Dialog, DialogContent, Alert, Tooltip } from '@mui/material';

export const AlertUser = () => {
// get the message alert details from state
const { alertMsg, setAlertMsg } = useLayers();

return(
// render an alert only if there is one
(alertMsg !== null) ? (
<Fragment>
<Dialog open={ true } disableEnforceFocus onClick={ () => setAlertMsg(null) }>
<DialogContent sx={{ p:0, m: .5, fontSize: 10, fontStyle: 'italic'}}>
<Tooltip title="Click to close" placement="top">
<Alert variant="outlined" severity={ alertMsg['severity'] }>{ alertMsg['msg'] }</Alert>
</Tooltip>
</DialogContent>
</Dialog>
</Fragment>
) : ''
);
};
1 change: 1 addition & 0 deletions src/components/alert-user/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './alert-user';
26 changes: 16 additions & 10 deletions src/components/dialog/base-floating-dialog.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import React, { Fragment, useState, useRef, forwardRef } from 'react';
import { ToggleButtonGroup, ToggleButton, Box, Stack, Typography } from '@mui/material';
import { ToggleButtonGroup, ToggleButton, Box, Stack, Typography,
CssBaseline, Dialog, DialogContent, DialogTitle, Paper, Slide, IconButton} from '@mui/material';
import Draggable from "react-draggable";
import PropTypes from 'prop-types';
import { Resizable } from "react-resizable";
import "react-resizable/css/styles.css";

import CssBaseline from '@mui/material/CssBaseline';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogActions';
import DialogTitle from '@mui/material/DialogTitle';

import Paper from '@mui/material/Paper';
import Slide from '@mui/material/Slide';
import IconButton from '@mui/material/IconButton';
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';

import { markUnclicked } from '@utils/map-utils';
Expand Down Expand Up @@ -55,7 +48,6 @@ export default function BaseFloatingDialog({ title, index, dialogObject, dataKey
const minHeight = 175;
const maxHeight = 500;


/**
* the close dialog handler
*/
Expand Down Expand Up @@ -135,6 +127,20 @@ export default function BaseFloatingDialog({ title, index, dialogObject, dataKey
APS Forecast</ToggleButton></Box> : ''
}

{(showLineButtonView("SWAN Nowcast")) ?
<Box><ToggleButton
value="SWAN Nowcast"
sx={{ '&:hover': { color: 'White', backgroundColor: 'CornflowerBlue' }, m: 0, p: "3px", color: 'CornflowerBlue', fontSize: 8 }}>
SWAN Nowcast</ToggleButton></Box> : ''
}

{(showLineButtonView("SWAN Forecast")) ?
<Box><ToggleButton
value="SWAN Forecast"
sx={{ '&:hover': { color: 'White', backgroundColor: 'LimeGreen' }, m: 0, p: "3px", color: 'LimeGreen', fontSize: 8 }}>
SWAN Forecast</ToggleButton></Box> : ''
}

{(showLineButtonView("NOAA Tidal Predictions")) ?
<Box><ToggleButton
value="NOAA Tidal Predictions"
Expand Down
28 changes: 24 additions & 4 deletions src/components/dialog/observation-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function getObsChartData(url, setLineButtonView) {
console.error(error.message);

// make sure we do not render anything
return "";
return error.message;
});

// return the csv data in json format
Expand All @@ -79,7 +79,7 @@ function getObsChartData(url, setLineButtonView) {
*/
function csvToJSON(csvData, setLineButtonView) {
// ensure that there is csv data to convert
if (csvData !== "") {
if (csvData !== "" && csvData.indexOf('Error') < 0 && csvData.indexOf('fail') < 0) {
// split on carriage returns
const lines = csvData.split("\n");

Expand Down Expand Up @@ -151,6 +151,24 @@ function csvToJSON(csvData, setLineButtonView) {
else
e["APS Forecast"] = null;

if (e["SWAN Nowcast"]) {
e["SWAN Nowcast"] = +parseFloat(e["SWAN Nowcast"]).toFixed(3);

// set the line button to be in view
setLineButtonView("SWAN Nowcast");
}
else
e["SWAN Nowcast"] = null;

if (e["SWAN Forecast"]) {
e["SWAN Forecast"] = +parseFloat(e["SWAN Forecast"]).toFixed(3);

// set the line button to be in view
setLineButtonView("SWAN Forecast");
}
else
e["SWAN Forecast"] = null;

if (e["Difference (APS-OBS)"]) {
e["Difference (APS-OBS)"] = +parseFloat(e["Difference (APS-OBS)"]).toFixed(3);

Expand Down Expand Up @@ -205,7 +223,7 @@ function formatX_axis(value) {
*/
function get_yaxis_ticks(data) {
// insure there is something to work with
if (data !== undefined) {
if (data !== undefined && data.length > 0) {
// init the max value found
let maxVal = 0;

Expand Down Expand Up @@ -268,7 +286,7 @@ function CreateObsChart(c) {
<Fragment>
{
status === 'pending' ? ( <div>Gathering chart data...</div> ) :
status === 'error' ? ( <div>There was a problem with observation data for this location.</div> ) :
status === 'error' ? ( <div>There was a problem with collecting data for this location.</div> ) :
<ResponsiveContainer>
<LineChart data={ data } margin={{ left: -25 }} isHide={ c.chartProps.isHideLine }>
<CartesianGrid strokeDasharray="1 1" />
Expand All @@ -284,6 +302,8 @@ function CreateObsChart(c) {
<Line type="monotone" dataKey="NOAA Tidal Predictions" stroke="teal" strokeWidth={1} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["NOAA Tidal Predictions"] }/>
<Line type="monotone" dataKey="APS Nowcast" stroke="CornflowerBlue" strokeWidth={2} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["APS Nowcast"] }/>
<Line type="monotone" dataKey="APS Forecast" stroke="LimeGreen" strokeWidth={2} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["APS Forecast"] }/>
<Line type="monotone" dataKey="SWAN Nowcast" stroke="CornflowerBlue" strokeWidth={2} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["SWAN Nowcast"] }/>
<Line type="monotone" dataKey="SWAN Forecast" stroke="LimeGreen" strokeWidth={2} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["SWAN Forecast"] }/>
<Line type="monotone" dataKey="Difference (APS-OBS)" stroke="red" strokeWidth={1} dot={false} isAnimationActive={false} hide={ c.chartProps.isHideLine["Difference (APS-OBS)"] } />
</LineChart>
</ResponsiveContainer>
Expand Down
4 changes: 4 additions & 0 deletions src/components/dialog/observation-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const ObservationDialog = (obs_data) => {
"NOAA Tidal Predictions": false,
"APS Nowcast": false,
"APS Forecast": false,
"SWAN Nowcast": false,
"SWAN Forecast": false,
"Difference (APS-OBS)": false });

// method to toggle the line view polarity
Expand All @@ -38,6 +40,8 @@ export const ObservationDialog = (obs_data) => {
"NOAA Tidal Predictions": false,
"APS Nowcast": false,
"APS Forecast": false,
"SWAN Nowcast": false,
"SWAN Forecast": false,
"Difference (APS-OBS)": false });

const setLineButtonView = (item) => {
Expand Down
95 changes: 86 additions & 9 deletions src/components/map/adcirc-raster-layer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react';
import { WMSTileLayer } from 'react-leaflet';
import React, { useEffect, useMemo, useState, useCallback } from 'react';
import { WMSTileLayer, useMap, useMapEvent } from 'react-leaflet';
import SldStyleParser from 'geostyler-sld-parser';
import { getNamespacedEnvParam, restoreColorMapType } from '@utils/map-utils';
import { useSettings } from '@context';
import { getNamespacedEnvParam, markClicked, restoreColorMapType } from '@utils/map-utils';
import { useLayers, useSettings } from '@context';

export const AdcircRasterLayer = (layer) => {
const sldParser = new SldStyleParser();
Expand All @@ -15,7 +15,7 @@ export const AdcircRasterLayer = (layer) => {

const [currentStyle, setCurrentStyle] = useState("");

useEffect(() => {
useEffect(() => {
if(layer.layer.properties) {
let style = "";
switch(layer.layer.properties.product_type) {
Expand All @@ -42,12 +42,90 @@ export const AdcircRasterLayer = (layer) => {
});
});
}
}, [mapStyle]);

}, [mapStyle]);

// get the observation points selected, default layers and alert message from state
const {
setSelectedObservations,
defaultModelLayers,
setAlertMsg,
} = useLayers();

// capture the default layers
const layers = defaultModelLayers;

// get a handle to the map
const map = useMap();

// create a list of worthy geo-point layer types
const validLayerTypes = new Set(['Maximum Water Level', 'Maximum Significant Wave Height']);

// create a callback to handle a map click event
const onClick = useCallback((e) => {
// get the visible layer on the map
const layer = layers.find((layer) => layer.properties['product_type'] !== "obs" && layer.state.visible === true);

// if this is a layer we can geo-point on
if (validLayerTypes.has(layer.properties['product_name'])) {
// round the coordinates
const lon = Number(e.latlng.lng).toFixed(5);
const lat = Number(e.latlng.lat).toFixed(5);

// create an id for the point
const id = lon + ', ' + lat;

// create a marker target icon around the observation clicked
markClicked(map, e, id);

// get the FQDN of the UI data server
const data_url = `${getNamespacedEnvParam('REACT_APP_UI_DATA_URL')}`;

// create the correct TDS URL without the hostname
const tds_url = layer.properties['tds_download_url'].replace('catalog', 'dodsC').replace('catalog.html', (layer.id.indexOf('swan') < 0 ?
'fort' : 'swan_HS') + '.63.nc').split('/thredds')[1];

// get the hostname
const tds_svr = layer.properties['tds_download_url'].split('https://')[1].split('/thredds')[0].split('.')[0];

// generate the full url
const fullTDSURL = data_url + "get_geo_point_data?lon=" + e.latlng.lng + "&lat=" + e.latlng.lat + "&ensemble=nowcast&url=" +
tds_url + '&tds_svr=' + tds_svr;

const l_props = layer.properties;

// create a set of properties for this object
const pointProps =
{
"station_name": l_props['product_name'] + " " + id,
"lat": lat,
"lon": lon,
"location_name": l_props['product_name'] + "s over time (lon, lat): " + id,
"model_run_id": layer.group,
"data_source": (l_props['event_type'] + '_' + l_props['grid_type']).toUpperCase(),
"source_name": l_props['model'],
"source_instance": l_props['instance_name'],
"source_archive": l_props['location'],
"forcing_metclass": l_props['met_class'],
"location_type": "GeoPoint",
"grid_name": l_props['grid_type'].toUpperCase(),
"csvurl": fullTDSURL,
"id": id
};

// populate selectedObservations list with the newly selected observation point
setSelectedObservations(previous => [...previous, pointProps]);
}
else
setAlertMsg({'severity': 'warning', 'msg': 'Geo-point selection is not available for the ' + layer.properties['product_name'] + ' product.'});
});

// assign the map click event for geo-point selections
useMapEvent('click', onClick);

// memorizing this params object prevents
// that map flicker on state changes.
const wmsLayerParams = useMemo(() => ({
format:"image/png",
format: "image/png",
transparent: true,
sld_body: currentStyle,
}), [currentStyle]);
Expand All @@ -60,5 +138,4 @@ export const AdcircRasterLayer = (layer) => {
opacity={layer.layer.state.opacity}
/>
);

};
4 changes: 4 additions & 0 deletions src/context/map-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ export const LayersProvider = ({ children }) => {
// used to track the view state of the share comment
const [showShareComment, setShowShareComment] = useState(true);

// used to show alerts
const [alertMsg, setAlertMsg] = useState(null);

return (
<LayersContext.Provider
value={{
Expand All @@ -377,6 +380,7 @@ export const LayersProvider = ({ children }) => {
selectedObservations, setSelectedObservations,
showShareComment, setShowShareComment,
layerTypes,
alertMsg, setAlertMsg,

toggleHurricaneLayerVisibility, toggleLayerVisibility, toggleLayerVisibility2,
getAllLayersInvisible, getAllHurricaneLayersInvisible, getAllRasterLayersInvisible,
Expand Down
3 changes: 2 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ module.exports = {
'@compare-layers': path.resolve(__dirname, 'src/components/trays/compare-layers/'),
'@share': path.resolve(__dirname, 'src/components/trays/share/'),
'@utils': path.resolve(__dirname, 'src/utils/'),
'@side-by-side': path.resolve(__dirname, 'src/components/side-by-side')
'@side-by-side': path.resolve(__dirname, 'src/components/side-by-side/'),
'@alert-user': path.resolve(__dirname, 'src/components/alert-user/')
}
},

Expand Down

0 comments on commit 0d379cb

Please sign in to comment.