Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeseries extract #299

Merged
merged 17 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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