diff --git a/.github/workflows/build_and_push_dev_image.yml b/.github/workflows/build_and_push_dev_image.yml
index 97483a7..3d5c97f 100644
--- a/.github/workflows/build_and_push_dev_image.yml
+++ b/.github/workflows/build_and_push_dev_image.yml
@@ -23,7 +23,7 @@ jobs:
platform:
- linux/amd64
- linux/arm64
- - linux/arm64/v8
+ # - linux/arm64/v8
steps:
- name: Prepare
run: |
diff --git a/Dockerfile b/Dockerfile
index bb54a03..fce0699 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,17 +9,19 @@ ARG MICRO_TETHYS=true \
MAMBA_DOCKERFILE_ACTIVATE=1
-
#########################
# ADD APPLICATION FILES #
#########################
COPY . ${TETHYS_HOME}/apps/ngiab
+COPY run.sh ${TETHYS_HOME}/run.sh
+
###############
# ENVIRONMENT #
###############
ENV TETHYS_DB_ENGINE=django.db.backends.sqlite3
+ENV SKIP_DB_SETUP=True
ENV TETHYS_DB_NAME=
ENV TETHYS_DB_USERNAME=
ENV TETHYS_DB_PASSWORD=
@@ -30,14 +32,21 @@ ENV PORTAL_SUPERUSER_NAME=admin
ENV PORTAL_SUPERUSER_PASSWORD=pass
ENV PROJ_LIB=/opt/conda/envs/tethys/share/proj
+# fix error for numpy not being imported. libquadmath not found
+RUN apt-get update \
+ && apt-get -y install gfortran \
+ && rm -rf /var/lib/apt/lists/*
+
#######################
# INSTALL APPLICATION #
#######################
+
RUN cd ${TETHYS_HOME}/apps/ngiab && \
micromamba install --yes -c conda-forge --file requirements.txt && \
+ micromamba remove pyarrow && micromamba install --yes -c conda-forge pyarrow && \
micromamba clean --all --yes && \
npm install && npm run build && \
- tethys install -d -N
+ tethys install -d -N -w
#########
# PORTS #
@@ -48,4 +57,7 @@ EXPOSE 80
# RUN #
#######
-CMD bash run.sh
\ No newline at end of file
+CMD bash run.sh
+
+HEALTHCHECK --start-period=1s \
+ CMD ./liveness-probe.sh
\ No newline at end of file
diff --git a/cli/__init__.py b/cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cli/convert_geom.py b/cli/convert_geom.py
index 3c4f1ef..246348e 100644
--- a/cli/convert_geom.py
+++ b/cli/convert_geom.py
@@ -1,20 +1,21 @@
-import geopandas as gpd
import argparse
from geo.Geoserver import Geoserver
-import os
+import geopandas as gpd
import zipfile
+import os
-def read_and_transform(gpkg_path, layer_name):
+def _read_and_transform(gpkg_path, layer_name):
gdf = gpd.read_file(gpkg_path, layer=layer_name)
if gdf.crs.to_string() != "EPSG:5070":
gdf = gdf.to_crs(epsg=5070)
return gdf
-def create_and_publish_shp(
+def _create_and_publish_shp(
gdf,
shp_path,
+ store_name,
geoserver_host,
geoserver_port,
geoserver_username,
@@ -36,20 +37,36 @@ def create_and_publish_shp(
username=geoserver_username,
password=geoserver_password,
)
- geo.create_workspace(workspace="nextgen")
geo.create_shp_datastore(
path=f"{shp_path}.zip",
- store_name="hydrofabrics",
+ store_name=f"{store_name}",
workspace="nextgen",
)
# remove zip file
os.remove(f"{shp_path}.zip")
+def create_workspace(
+ geoserver_host, geoserver_port, geoserver_username, geoserver_password
+):
+ try:
+ geo = Geoserver(
+ f"http://{geoserver_host}:{geoserver_port}/geoserver",
+ username=geoserver_username,
+ password=geoserver_password,
+ )
+ geo.create_workspace(workspace="nextgen")
+
+ except Exception as e:
+ print(f"Error in create_workspace: {e}")
+ return 1
+ return 0
+
+
def convert_gpkg_to_geojson(gpkg_path, layer_name, output_path):
try:
- gdf = read_and_transform(gpkg_path, layer_name)
+ gdf = _read_and_transform(gpkg_path, layer_name)
gdf.to_file(output_path, driver="GeoJSON")
return 0
except Exception as e:
@@ -61,16 +78,18 @@ def publish_gpkg_layer_to_geoserver(
gpkg_path,
layer_name,
shp_path,
+ store_name,
geoserver_host,
geoserver_port,
geoserver_username,
geoserver_password,
):
try:
- gdf = read_and_transform(gpkg_path, layer_name)
- create_and_publish_shp(
+ gdf = _read_and_transform(gpkg_path, layer_name)
+ _create_and_publish_shp(
gdf,
shp_path,
+ store_name,
geoserver_host,
geoserver_port,
geoserver_username,
@@ -85,16 +104,18 @@ def publish_gpkg_layer_to_geoserver(
def publish_geojson_layer_to_geoserver(
geojson_path,
shp_path,
+ store_name,
geoserver_host,
geoserver_port,
geoserver_username,
geoserver_password,
):
try:
- gdf = read_and_transform(geojson_path, None)
- create_and_publish_shp(
+ gdf = _read_and_transform(geojson_path, None)
+ _create_and_publish_shp(
gdf,
shp_path,
+ store_name,
geoserver_host,
geoserver_port,
geoserver_username,
@@ -123,6 +144,11 @@ def main():
action="store_true",
help="Publish the GeoJSON layer to GeoServer",
)
+ parser.add_argument(
+ "--create_workspace",
+ action="store_true",
+ help="Create a workspace in GeoServer",
+ )
# Optional Arguments
parser.add_argument(
@@ -142,6 +168,7 @@ def main():
parser.add_argument(
"--shp_path", help="Path to save the shapefile for GeoServer", required=False
)
+ parser.add_argument("--store_name", help="Shapefile store name", required=False)
parser.add_argument("--geoserver_host", help="GeoServer host", required=False)
parser.add_argument("--geoserver_port", help="GeoServer port", required=False)
parser.add_argument(
@@ -172,6 +199,7 @@ def main():
args.gpkg_path,
args.layer_name,
args.shp_path,
+ args.store_name,
args.geoserver_host,
args.geoserver_port,
args.geoserver_username,
@@ -185,6 +213,25 @@ def main():
result = publish_geojson_layer_to_geoserver(
args.geojson_path,
args.shp_path,
+ args.store_name,
+ args.geoserver_host,
+ args.geoserver_port,
+ args.geoserver_username,
+ args.geoserver_password,
+ )
+ exit(result)
+
+ if args.create_workspace:
+ if (
+ not args.geoserver_host
+ or not args.geoserver_port
+ or not args.geoserver_username
+ or not args.geoserver_password
+ ):
+ parser.error(
+ "--create_workspace requires --geoserver_host, --geoserver_port, --geoserver_username, and --geoserver_password."
+ )
+ result = create_workspace(
args.geoserver_host,
args.geoserver_port,
args.geoserver_username,
diff --git a/reactapp/features/Map/providers/MapProvider.js b/reactapp/features/Map/providers/MapProvider.js
index 5c18715..44bdff2 100644
--- a/reactapp/features/Map/providers/MapProvider.js
+++ b/reactapp/features/Map/providers/MapProvider.js
@@ -96,7 +96,6 @@ export const MapProvider = ({ children, layers= [] }) => {
useEffect(() => {
if (!state.state.extent) return;
- console.log(state.state.extent)
state.state.mapObject.getView().fit(state.state.extent, {duration: 1300, padding: [50, 50, 50, 50]});
}, [state.state.extent]);
diff --git a/reactapp/features/hydroFabric/components/catchmentsSelect.js b/reactapp/features/hydroFabric/components/catchmentsSelect.js
new file mode 100644
index 0000000..ab0f514
--- /dev/null
+++ b/reactapp/features/hydroFabric/components/catchmentsSelect.js
@@ -0,0 +1,102 @@
+
+
+import {useEffect, Suspense, Fragment,lazy} from 'react';
+import { useHydroFabricContext } from 'features/hydroFabric/hooks/useHydroFabricContext';
+import appAPI from 'services/api/app';
+
+import SelectComponent from './selectComponent';
+
+// const SelectComponent = lazy(() => import('selectComponent'));
+
+const CatchmentSelect = (props) => {
+ const {state,actions} = useHydroFabricContext();
+
+ useEffect(() => {
+ if (!state.catchment.id) return;
+ actions.reset_nexus();
+ props.setIsLoading(true);
+ var params = {
+ catchment_id: state.catchment.id
+ }
+ appAPI.getCatchmentTimeSeries(params).then((response) => {
+ actions.set_catchment_series(response.data);
+ actions.set_catchment_variable_list(response.variables);
+ actions.set_catchment_variable(null);
+ actions.set_catchment_list(response.catchment_ids);
+ actions.set_troute_id(state.catchment.id);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching catchment time series", error);
+ })
+ return () => {
+ if (state.catchment.id) return;
+ actions.reset_catchment();
+ }
+
+ }, [state.catchment.id]);
+
+
+ useEffect(() => {
+ if (!state.catchment.variable) return;
+ props.setIsLoading(true);
+
+ var params = {
+ catchment_id: state.catchment.id,
+ variable_column: state.catchment.variable
+ }
+ appAPI.getCatchmentTimeSeries(params).then((response) => {
+ actions.set_catchment_series(response.data);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching nexus time series", error);
+ })
+ return () => {
+
+ }
+
+ }, [state.catchment.variable]);
+
+
+ return (
+
+ {state.catchment.id &&
+
+ Catchment Metadata
+ ID: {state.catchment.id}
+
+
+
+
+
+ }
+
+
+
+
+ );
+};
+
+export default CatchmentSelect;
\ No newline at end of file
diff --git a/reactapp/features/hydroFabric/components/hydroFabricLinePlot.js b/reactapp/features/hydroFabric/components/hydroFabricLinePlot.js
index 851e69f..43b5947 100644
--- a/reactapp/features/hydroFabric/components/hydroFabricLinePlot.js
+++ b/reactapp/features/hydroFabric/components/hydroFabricLinePlot.js
@@ -11,7 +11,7 @@ import '../css/chart.css';
const chartOptions = {
axisX: {
type: FixedScaleAxis,
- divisor: 5,
+ divisor: 10,
labelInterpolationFnc: function(value) {
return new Date(value).toLocaleDateString();
}
@@ -29,6 +29,8 @@ const chartOptions = {
};
+
+
const HydroFabricLinePlot = (props) => {
// Reference to the chart container
const chartRef = useRef(null);
@@ -39,6 +41,7 @@ const HydroFabricLinePlot = (props) => {
useEffect(() => {
if (!state.nexus.series) return;
const nexusSeries = state.nexus.series.map(point => ({x: new Date(point.x), y: point.y}));
+
const chartData = {
series: [
{ name: 'Nexus', data: nexusSeries },
@@ -100,6 +103,44 @@ const HydroFabricLinePlot = (props) => {
};
}, [state.catchment.series]); // Re-run effect if series data changes
+
+
+ useEffect(() => {
+ if (!state.troute.series) return;
+ if (chartRef.current) {
+ const trouteSeries = state.troute.series.map(point => ({x: new Date(point.x), y: point.y}));
+ const chartData = {
+ series: [
+ { name: 'Troute', data: trouteSeries },
+ ]
+ };
+
+ chartInstance.current = new LineChart(chartRef.current, chartData, chartOptions);
+
+ addAnimationToLineChart(chartInstance.current, easings)
+ makeAxis(
+ chartRef.current,
+ 'Time (Date)',
+ `${state.troute.variable ? state.troute.variable.toLowerCase() : state.troute.variable_list ? state.troute.variable_list[0].label : null}`
+ )
+
+ makeTitle(
+ chartRef.current,
+ `${state.troute.variable ? state.troute.variable.toLowerCase() : state.troute.variable_list ? state.troute.variable_list[0].label : null}: ${state.troute.id} `)
+ }
+
+ return () => {
+ if(props.singleRowOn){
+ actions.reset_troute();
+ chartRef.current.detach();
+ document.getElementById('x-axis-title')?.remove();
+ document.getElementById('y-axis-title')?.remove();
+ }
+
+ };
+ }, [state.troute.series]); // Re-run effect if series data changes
+
+
return (
);
diff --git a/reactapp/features/hydroFabric/components/nexusSelect.js b/reactapp/features/hydroFabric/components/nexusSelect.js
new file mode 100644
index 0000000..cfaba04
--- /dev/null
+++ b/reactapp/features/hydroFabric/components/nexusSelect.js
@@ -0,0 +1,57 @@
+
+
+import {useEffect, Fragment,lazy} from 'react';
+import { useHydroFabricContext } from 'features/hydroFabric/hooks/useHydroFabricContext';
+import appAPI from 'services/api/app';
+import SelectComponent from './selectComponent';
+// const SelectComponent = lazy(() => import('selectComponent'));
+
+const NexusSelect = (props) => {
+ const {state,actions} = useHydroFabricContext();
+
+ useEffect(() => {
+ if (!state.nexus.id) return;
+ actions.reset_catchment();
+ var params = {
+ nexus_id: state.nexus.id
+ }
+ appAPI.getNexusTimeSeries(params).then((response) => {
+ actions.set_nexus_series(response.data);
+ actions.set_nexus_list(response.nexus_ids);
+ actions.set_troute_id(state.nexus.id);
+ props.toggleSingleRow(false);
+ }).catch((error) => {
+ console.log("Error fetching nexus time series", error);
+ })
+ return () => {
+ if(state.nexus.id) return
+ actions.reset_nexus();
+ }
+
+ }, [state.nexus.id]);
+
+
+ return (
+
+ {state.nexus.id &&
+
+ Nexus Metadata
+ ID: {state.nexus.id}
+
+
+
+ }
+
+ );
+};
+
+export default NexusSelect;
\ No newline at end of file
diff --git a/reactapp/features/hydroFabric/components/trouteSelect.js b/reactapp/features/hydroFabric/components/trouteSelect.js
new file mode 100644
index 0000000..d591b8d
--- /dev/null
+++ b/reactapp/features/hydroFabric/components/trouteSelect.js
@@ -0,0 +1,74 @@
+
+
+import {useEffect,Fragment,lazy} from 'react';
+import { useHydroFabricContext } from 'features/hydroFabric/hooks/useHydroFabricContext';
+import appAPI from 'services/api/app';
+import SelectComponent from './selectComponent';
+
+// const SelectComponent = lazy(() => import('../../features/hydroFabric/components/selectComponent'));
+
+const TRouteSelect = (props) => {
+ const {state,actions} = useHydroFabricContext();
+
+
+ useEffect(() => {
+ if (!state.troute.id) return;
+ props.setIsLoading(true);
+ var params = {
+ troute_id: state.troute.id
+ }
+ appAPI.getTrouteVariables(params).then((response) => {
+ actions.set_troute_variable_list(response.troute_variables);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching troute variables", error);
+ })
+ return () => {
+ if (state.troute.id) return;
+ actions.reset_troute();
+ }
+ },[state.troute.id]);
+
+
+ useEffect(() => {
+ if (!state.troute.variable || !state.troute.id) return;
+ props.setIsLoading(true);
+ var params = {
+ troute_id: state.troute.id,
+ troute_variable: state.troute.variable
+ }
+ appAPI.getTrouteTimeSeries(params).then((response) => {
+ actions.set_troute_series(response.data);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching troute time series", error);
+ })
+ return () => {
+ if (state.troute.id) return;
+ actions.reset_troute();
+ }
+ },[state.troute.variable]);
+
+
+ return (
+
+ {
+ state.troute.id &&
+
+ Troute
+
+
+
+ }
+
+ );
+};
+
+export default TRouteSelect;
\ No newline at end of file
diff --git a/reactapp/features/hydroFabric/hooks/useHydroFabric.js b/reactapp/features/hydroFabric/hooks/useHydroFabric.js
index aa945db..53bcd21 100644
--- a/reactapp/features/hydroFabric/hooks/useHydroFabric.js
+++ b/reactapp/features/hydroFabric/hooks/useHydroFabric.js
@@ -16,6 +16,11 @@ const useHydroFabric = () => {
set_nexus_list: (list) => dispatch({ type: hydroFabricActionsTypes.set_nexus_list, payload: list}),
set_catchment_list: (list) => dispatch({ type: hydroFabricActionsTypes.set_catchment_list, payload: list}),
set_catchment_variable_list: (list) => dispatch({ type: hydroFabricActionsTypes.set_catchment_variable_list, payload: list}),
+ set_troute_series: (series) => dispatch({ type: hydroFabricActionsTypes.set_troute_series, payload: series }),
+ set_troute_id: (id) => dispatch({ type: hydroFabricActionsTypes.set_troute_id, payload: id }),
+ set_troute_variable: (variable) => dispatch({ type: hydroFabricActionsTypes.set_troute_variable, payload: variable }),
+ set_troute_variable_list: (list) => dispatch({ type: hydroFabricActionsTypes.set_troute_variable_list, payload: list }),
+ reset_troute: () => dispatch({ type: hydroFabricActionsTypes.reset_troute }),
reset_nexus: () => dispatch({ type: hydroFabricActionsTypes.reset_nexus }),
reset_catchment: () => dispatch({ type: hydroFabricActionsTypes.reset_catchment }),
reset: () => dispatch({ type: hydroFabricActionsTypes.reset }),
diff --git a/reactapp/features/hydroFabric/store/actions/actionsTypes.js b/reactapp/features/hydroFabric/store/actions/actionsTypes.js
index 57228f0..6425a3c 100644
--- a/reactapp/features/hydroFabric/store/actions/actionsTypes.js
+++ b/reactapp/features/hydroFabric/store/actions/actionsTypes.js
@@ -7,8 +7,13 @@ const hydroFabricActionsTypes = {
set_catchment_id: 'SET_CATCHMENT_ID',
set_catchment_variable: 'SET_CATCHMENT_VARIABLE',
set_catchment_variable_list: 'SET_CATCHMENT_VARIABLE_LIST',
+ set_troute_series: 'SET_TROUTE_SERIES',
+ set_troute_id: 'SET_TROUTE_ID',
+ set_troute_variable: 'SET_TROUTE_VARIABLE',
+ set_troute_variable_list: 'SET_TROUTE_VARIABLE_LIST',
reset_nexus: 'RESET_NEXUS',
reset_catchment: 'RESET_CATCHMENT',
+ reset_troute: 'RESET_TROUTE',
reset: 'RESET',
};
diff --git a/reactapp/features/hydroFabric/store/reducers/hydroFabricReducer.js b/reactapp/features/hydroFabric/store/reducers/hydroFabricReducer.js
index 63de112..288b0a7 100644
--- a/reactapp/features/hydroFabric/store/reducers/hydroFabricReducer.js
+++ b/reactapp/features/hydroFabric/store/reducers/hydroFabricReducer.js
@@ -15,6 +15,13 @@ const hydroFabricInitialStore = {
list:null,
variable_list:null
},
+ troute:{
+ series:null,
+ variable:null,
+ id:null,
+ list:null,
+ variable_list:null
+ }
},
actions:{}
@@ -124,6 +131,64 @@ const hydroFabricReducer = (state, action) => {
}
}
};
+ case hydroFabricActionsTypes.set_troute_series:
+ return {
+ ...state,
+ state: {
+ ...state.state,
+ troute: {
+ ...state.state.troute,
+ series: action.payload
+ }
+ }
+ };
+ case hydroFabricActionsTypes.set_troute_id:
+ return {
+ ...state,
+ state: {
+ ...state.state,
+ troute: {
+ ...state.state.troute,
+ id: action.payload
+ }
+ }
+ };
+ case hydroFabricActionsTypes.set_troute_variable:
+ return {
+ ...state,
+ state: {
+ ...state.state,
+ troute: {
+ ...state.state.troute,
+ variable: action.payload
+ }
+ }
+ };
+ case hydroFabricActionsTypes.set_troute_variable_list:
+ return {
+ ...state,
+ state: {
+ ...state.state,
+ troute: {
+ ...state.state.troute,
+ variable_list: action.payload
+ }
+ }
+ };
+ case hydroFabricActionsTypes.reset_troute:
+ return {
+ ...state,
+ state: {
+ ...state.state,
+ troute: {
+ series:null,
+ variable:null,
+ id:null,
+ list:null,
+ variable_list:null
+ }
+ }
+ };
case hydroFabricActionsTypes.reset_nexus:
return {
...state,
diff --git a/reactapp/lib/layersData.js b/reactapp/lib/layersData.js
index b5926ff..f94b102 100644
--- a/reactapp/lib/layersData.js
+++ b/reactapp/lib/layersData.js
@@ -48,5 +48,30 @@ const sldCatchmentStyle = `
`
-export { initialLayersArray,sldCatchmentStyle }
+const sldFlowPathsStyle = `
+
+
+ nextgen:flowpaths
+
+
+
+
+
+ #87CEEB
+ 2
+
+
+
+
+
+
+`
+
+
+export { initialLayersArray,sldCatchmentStyle,sldFlowPathsStyle }
diff --git a/reactapp/lib/mapUtils.js b/reactapp/lib/mapUtils.js
index 608059c..24d15e6 100644
--- a/reactapp/lib/mapUtils.js
+++ b/reactapp/lib/mapUtils.js
@@ -8,7 +8,7 @@ import {
Text,
} from 'ol/style';
import { VectorSourceLayer } from 'features/Map/lib/source/sources';
-import { sldCatchmentStyle } from './layersData';
+import { sldCatchmentStyle,sldFlowPathsStyle } from './layersData';
const image = new CircleStyle({
radius: 5,
@@ -202,6 +202,34 @@ let makeCatchmentLayer = (catchmentLayersURL) =>{
}
}
+let makeFlowPathsLayer = (flowPathsLayersURL) =>{
+ return {
+ layerType: 'OlTileLayer',
+ options:
+ {
+ sourceType: 'WMSTile',
+ url: flowPathsLayersURL,
+ // all the params for the source goes here
+ params: {
+ LAYERS: 'nextgen:flowpaths',
+ Tiled: true,
+ SLD_BODY: sldFlowPathsStyle
+ },
+ // the rest of the attributes are for the definition of the layer
+ name: "Flowpaths Layer",
+ zIndex: 2,
+ source:{
+ serverType: 'geoserver',
+ crossOrigin: 'anonymous'
+ }
+
+ },
+
+ }
+}
+
+
+
const customForEachLayerAtPixelLayerFilter = (layer) =>{
return layer.get('name') !== 'baseMapLayer'
// return layer.get('events') && layer.get('events').length > 0 && layer.get('events').findIndex(event => event.type === 'click') > -1
@@ -313,6 +341,7 @@ const displayFeatureInfo = (event,layer,hydroFabricActions) => {
export {
makeNexusLayerParams,
makeCatchmentLayer,
+ makeFlowPathsLayer,
displayFeatureInfo,
displayFeatureInfoWMS,
customForEachLayerAtPixelLayerFilter,
diff --git a/reactapp/services/api/app.js b/reactapp/services/api/app.js
index d418436..fd68e99 100644
--- a/reactapp/services/api/app.js
+++ b/reactapp/services/api/app.js
@@ -12,6 +12,12 @@ const appAPI = {
getCatchmentTimeSeries: (params) => {
return apiClient.get(`${APP_ROOT_URL}getCatchmentTimeSeries/`, { params });
},
-};
+ getTrouteVariables: (params) => {
+ return apiClient.get(`${APP_ROOT_URL}getTrouteVariables/`, { params });
+ },
+ getTrouteTimeSeries: (params) => {
+ return apiClient.get(`${APP_ROOT_URL}getTrouteTimeSeries/`, { params });
+ }
+}
export default appAPI;
\ No newline at end of file
diff --git a/reactapp/views/ngiab/hydroFabricView.js b/reactapp/views/ngiab/hydroFabricView.js
index bdafe7c..fb55239 100644
--- a/reactapp/views/ngiab/hydroFabricView.js
+++ b/reactapp/views/ngiab/hydroFabricView.js
@@ -22,6 +22,7 @@ const HydroFabricView = (props) => {
appAPI.getNexusTimeSeries(params).then((response) => {
actions.set_nexus_series(response.data);
actions.set_nexus_list(response.nexus_ids);
+ actions.set_troute_id(state.nexus.id);
props.toggleSingleRow(false);
}).catch((error) => {
console.log("Error fetching nexus time series", error);
@@ -33,6 +34,49 @@ const HydroFabricView = (props) => {
}, [state.nexus.id]);
+ useEffect(() => {
+ console.log("here")
+ if (!state.troute.id) return;
+ props.setIsLoading(true);
+ var params = {
+ troute_id: state.troute.id
+ }
+ appAPI.getTrouteVariables(params).then((response) => {
+ actions.set_troute_variable_list(response.troute_variables);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching troute variables", error);
+ })
+ return () => {
+ if (state.troute.id) return;
+ actions.reset_troute();
+ }
+ },[state.troute.id]);
+
+
+ useEffect(() => {
+ if (!state.troute.variable || !state.troute.id) return;
+ props.setIsLoading(true);
+ var params = {
+ troute_id: state.troute.id,
+ troute_variable: state.troute.variable
+ }
+ appAPI.getTrouteTimeSeries(params).then((response) => {
+ actions.set_troute_series(response.data);
+ props.toggleSingleRow(false);
+ props.setIsLoading(false);
+ }).catch((error) => {
+ props.setIsLoading(false);
+ console.log("Error fetching troute time series", error);
+ })
+ return () => {
+ if (state.troute.id) return;
+ actions.reset_troute();
+ }
+ },[state.troute.variable]);
+
useEffect(() => {
if (!state.catchment.id) return;
@@ -46,6 +90,7 @@ const HydroFabricView = (props) => {
actions.set_catchment_variable_list(response.variables);
actions.set_catchment_variable(null);
actions.set_catchment_list(response.catchment_ids);
+ actions.set_troute_id(state.catchment.id);
props.toggleSingleRow(false);
props.setIsLoading(false);
}).catch((error) => {
@@ -132,8 +177,22 @@ const HydroFabricView = (props) => {
}
/>
+
+ }
+ {
+ state.troute.id &&
+
+ Troute
+
+
+
}
+
+
}>
diff --git a/reactapp/views/ngiab/hydroFabricView2.js b/reactapp/views/ngiab/hydroFabricView2.js
new file mode 100644
index 0000000..1cea049
--- /dev/null
+++ b/reactapp/views/ngiab/hydroFabricView2.js
@@ -0,0 +1,60 @@
+
+
+import {useEffect, Suspense, Fragment,lazy} from 'react';
+import { useHydroFabricContext } from 'features/hydroFabric/hooks/useHydroFabricContext';
+import appAPI from 'services/api/app';
+import { SelectContainer,HydroFabricPlotContainer } from './containers';
+import LoadingAnimation from 'components/loader/LoadingAnimation';
+
+
+const HydroFabricLinePlot = lazy(() => import('../../features/hydroFabric/components/hydroFabricLinePlot'));
+const CatchmentSelectComponent = lazy(() => import('../../features/hydroFabric/components/catchmentsSelect'));
+const NexusSelectComponent = lazy(() => import('../../features/hydroFabric/components/nexusSelect'));
+const TrouteSelectComponent = lazy(() => import('../../features/hydroFabric/components/trouteSelect'));
+
+const HydroFabricView = (props) => {
+ const {state,actions} = useHydroFabricContext();
+
+ return (
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+
+
+ }>
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default HydroFabricView;
\ No newline at end of file
diff --git a/reactapp/views/ngiab/map_view.js b/reactapp/views/ngiab/map_view.js
index 5c1df5d..cdd74d3 100644
--- a/reactapp/views/ngiab/map_view.js
+++ b/reactapp/views/ngiab/map_view.js
@@ -13,6 +13,7 @@ import { initialLayersArray } from 'lib/layersData';
import {
makeNexusLayerParams,
makeCatchmentLayer,
+ makeFlowPathsLayer,
createClusterVectorLayer
} from 'lib/mapUtils';
import LoadingAnimation from 'components/loader/LoadingAnimation';
@@ -30,6 +31,12 @@ const MapView = (props) => {
const catchmentLayersURL = 'http://localhost:8181/geoserver/wms';
return makeCatchmentLayer(catchmentLayersURL);
},[])
+
+ const flowPathsLayerCallBack = useCallback(() => {
+ const flowPathsLayersURL = 'http://localhost:8181/geoserver/wms';
+ return makeFlowPathsLayer(flowPathsLayersURL);
+ },[])
+
const onPointerMoveLayersEventCallback = useCallback((event) => {
return onPointerMoveLayersEvent(event)
},[]);
@@ -61,16 +68,19 @@ const MapView = (props) => {
// Define the parameters for the layer
var nexusLayerParams = nexusLayerParamsCallBack();
var catchmentLayer = catchmentLayerCallBack();
+ var flowPathsLayer = flowPathsLayerCallBack();
nexusLayerParams['geojsonLayer']=response.geojson;
const nexusClusterLayer = createClusterVectorLayer(nexusLayerParams);
+
const NexusExtent = nexusClusterLayer.options.params.source.getExtent();
mapActions.addLayer(nexusClusterLayer);
mapActions.set_extent(NexusExtent);
mapActions.addLayer(catchmentLayer);
+ mapActions.addLayer(flowPathsLayer);
}).catch(error => {
console.log(error)
diff --git a/reactapp/views/ngiab/ngiab_view.js b/reactapp/views/ngiab/ngiab_view.js
index 12c836a..087a7c4 100644
--- a/reactapp/views/ngiab/ngiab_view.js
+++ b/reactapp/views/ngiab/ngiab_view.js
@@ -3,7 +3,7 @@ import { HydroFabricProvider } from 'features/hydroFabric/providers/hydroFabricP
import { MapProvider } from 'features/Map/providers/MapProvider';
import { HydroFabricContainer, MapContainer } from './containers';
import LoadingAnimation from 'components/loader/LoadingAnimation';
-const HydroFabricView = lazy(() => import('./hydroFabricView.js'));
+const HydroFabricView = lazy(() => import('./hydroFabricView2.js'));
const MapView = lazy(() => import('./map_view.js'));
const NGIABView = () => {
diff --git a/run.sh b/run.sh
new file mode 100644
index 0000000..79849ef
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+tail_file() {
+ echo "tailing file $1"
+ ALIGN=27
+ LENGTH=`echo $1 | wc -c`
+ PADDING=`expr ${ALIGN} - ${LENGTH}`
+ PREFIX=$1`perl -e "print ' ' x $PADDING;"`
+ file="/var/log/$1"
+ # each tail runs in the background but prints to stdout
+ # sed outputs each line from tail prepended with the filename+padding
+ tail -qF $file | sed --unbuffered "s|^|${PREFIX}:|g" &
+}
+
+echo_status() {
+ local args="${@}"
+ tput setaf 4
+ tput bold
+ echo -e "- $args"
+ tput sgr0
+}
+
+
+echo_status "Starting up..."
+
+
+echo_status "Enforcing start state... (This might take a bit)"
+salt-call --local state.apply
+
+
+echo_status "Fixing permissions"
+chown -R www: /usr/lib/tethys
+chown -R www: /var/lib/tethys_persist
+chown -R www: /var/log/tethys
+
+echo_status "Starting supervisor"
+
+# Start Supervisor
+/usr/bin/supervisord
+
+echo_status "Done!"
+
+# Watch Logs
+echo_status "Watching logs. You can ignore errors from either apache (httpd) or nginx depending on which one you are using."
+
+log_files=("httpd/access_log"
+"httpd/error_log"
+"nginx/access.log"
+"nginx/error.log"
+"supervisor/supervisord.log"
+"tethys/tethys.log")
+
+# When this exits, exit all background tail processes
+trap 'kill $(jobs -p)' EXIT
+for log_file in "${log_files[@]}"; do
+tail_file "${log_file}"
+done
+
+# Read output from tail; wait for kill or stop command (docker waits here)
+wait
diff --git a/tethysapp/ngiab/controllers.py b/tethysapp/ngiab/controllers.py
index 80eb0a0..ff550ad 100644
--- a/tethysapp/ngiab/controllers.py
+++ b/tethysapp/ngiab/controllers.py
@@ -4,7 +4,15 @@
import json
import geopandas as gpd
from tethys_sdk.routing import controller
-from .utils import get_base_output, getCatchmentsIds, getNexusIDs
+from .utils import (
+ get_base_output,
+ getCatchmentsIds,
+ getNexusIDs,
+ check_troute_id,
+ get_troute_vars,
+ get_troute_df,
+)
+
from .app import App
@@ -95,4 +103,45 @@ def getNexusTimeSeries(request, app_workspace):
for time, streamflow in zip(time_col.tolist(), streamflow_cms_col.tolist())
]
- return JsonResponse({"data": data, "nexus_ids": getNexusIDs(app_workspace)})
+ return JsonResponse(
+ {
+ "data": data,
+ "nexus_ids": getNexusIDs(app_workspace),
+ }
+ )
+
+
+@controller(app_workspace=True)
+def getTrouteVariables(request, app_workspace):
+ troute_id = request.GET.get("troute_id")
+ clean_troute_id = troute_id.split("-")[1]
+ df = get_troute_df(app_workspace)
+ try:
+ if check_troute_id(df, clean_troute_id):
+ vars = get_troute_vars(df)
+ else:
+ vars = []
+ except Exception:
+ vars = []
+
+ return JsonResponse({"troute_variables": vars})
+
+
+@controller(app_workspace=True)
+def getTrouteTimeSeries(request, app_workspace):
+ troute_id = request.GET.get("troute_id")
+ clean_troute_id = troute_id.split("-")[1]
+ variable_column = request.GET.get("troute_variable")
+ df = get_troute_df(app_workspace)
+ df_sliced_by_id = df[df["featureID"] == int(clean_troute_id)]
+ try:
+ time_col = df_sliced_by_id["current_time"]
+ var_col = df_sliced_by_id[variable_column]
+ data = [
+ {"x": time, "y": val}
+ for time, val in zip(time_col.tolist(), var_col.tolist())
+ ]
+ except Exception:
+ data = []
+
+ return JsonResponse({"data": data})
diff --git a/tethysapp/ngiab/utils.py b/tethysapp/ngiab/utils.py
index f2570c0..893edb0 100644
--- a/tethysapp/ngiab/utils.py
+++ b/tethysapp/ngiab/utils.py
@@ -1,5 +1,36 @@
import os
import json
+import pandas as pd
+import glob
+
+
+def _get_base_troute_output(app_workspace):
+ base_output_path = os.path.join(
+ app_workspace.path, "ngen-data", "outputs", "troute"
+ )
+ return base_output_path
+
+
+def get_troute_df(app_workspace):
+ base_output_path = _get_base_troute_output(app_workspace)
+ troute_output_files = glob.glob(base_output_path + "/*.csv")
+ df = pd.read_csv(troute_output_files[0])
+ return df
+
+
+def check_troute_id(df, id):
+ if int(id) in df["featureID"].values:
+ return True
+ return False
+
+
+def get_troute_vars(df):
+ list_variables = df.columns.tolist()[3:] # remove feature, time and t0
+
+ variables = [
+ {"value": variable, "label": variable.lower()} for variable in list_variables
+ ]
+ return variables
def get_base_output(app_workspace):