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):