diff --git a/README.md b/README.md index 01a409a5a..4f80c28d5 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,19 @@ If you see error messages about missing software (for example, telling you that Run make install `make install`. This will create an executable file called cme in the CoastalME folder. -Edit cme.ini to tell CoastalME which input file to read (for example, in/test_suite/minimal_wave_angle_230/minimal.dat). You'll need to make sure that the "Path for output" listed in cme.ini (for example, out/test_suite/minimal_wave_angle_230) exists. If it doesn't exist, then create it: `mkdir -p out/test_suite/minimal_wave_angle_230/`. +CoastalME supports two configuration file formats: `.ini` (legacy) and `.yaml` (modern). + +**Using YAML format (recommended)**: +Edit `cme.yaml` to specify input and output paths. Example: +```yaml +input_data_file: in/test_suite/minimal_wave_angle_230/minimal.yaml +output_path: out/test_suite/minimal_wave_angle_230/ +``` + +**Using legacy .ini format**: +Edit `cme.ini` to tell CoastalME which input file to read (for example, in/test_suite/minimal_wave_angle_230/minimal.dat). You'll need to make sure that the "Path for output" listed in cme.ini (for example, out/test_suite/minimal_wave_angle_230) exists. If it doesn't exist, then create it: `mkdir -p out/test_suite/minimal_wave_angle_230/`. + +**Note**: CoastalME will automatically detect which format to use. If both `cme.yaml` and `cme.ini` exist, `cme.yaml` takes priority. Run cme `./cme`. Output will appear in the "Path for output" folder. diff --git a/cme.yaml b/cme.yaml new file mode 100644 index 000000000..ec4e85278 --- /dev/null +++ b/cme.yaml @@ -0,0 +1,32 @@ +#====================================================================================================================== +# +# CoastalME initialization file (YAML format) +# +# Copyright (C) 2024 David Favis-Mortlock and Andres Payo +# +#===================================================================================================================== +# +# This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with this program; if not, write to the Free +# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +#====================================================================================================================== + +# Path to the main input configuration file (.dat or .yaml) +# Can be absolute or relative to the directory containing this file +input_data_file: /home/wilfchun/CoastalME/CoastalME_data_local/Typology/Cliff/in/cliff.yaml + +# Path for output files +# Can be absolute or relative to the directory containing this file +# IMPORTANT: A trailing slash is required +output_path: /home/wilfchun/CoastalME/CoastalME_data_local/Typology/Cliff/out/ + +# Email address for notification messages (optional, Linux only) +# Uncomment to enable email notifications +# email_address: user@example.com diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5edd51138..5e90fb057 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,11 +178,22 @@ elseif(CMAKE_BUILD_TYPE_LC STREQUAL "relwithdebinfo") add_compile_options( -march=native -O3 + -g3 + -gdwarf-3 + -fopenmp + # -foffload=nvidia-ptx + -ffast-math + -fno-omit-frame-pointer # -Wall -fno-delete-null-pointer-checks -fno-strict-overflow -fno-strict-aliasing -ftrivial-auto-var-init=zero + -flto=auto + ) + add_link_options( + -flto=auto + -rdynamic ) if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|LCC|QCC)$") # Production build using gcc @@ -239,6 +250,7 @@ elseif(CMAKE_BUILD_TYPE_LC STREQUAL "release") -fno-strict-overflow -fno-strict-aliasing -ftrivial-auto-var-init=zero + -flto ) if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|LCC|QCC)$") # Production build using gcc @@ -251,9 +263,9 @@ endif() # Finally set some gcc-specific linker flags, for all build types if(UNIX) - add_link_options(-Wl) + # add_link_options(-Wl) if(LINUX) - add_link_options(--as-needed -Wl,--no-undefined) + # add_link_options(--as-needed -Wl,--no-undefined) elseif(APPLE) endif() endif() diff --git a/src/assign_landforms.cpp b/src/assign_landforms.cpp index c7a08884f..c8f797dac 100644 --- a/src/assign_landforms.cpp +++ b/src/assign_landforms.cpp @@ -48,23 +48,23 @@ int CSimulation::nAssignLandformsForAllCoasts(void) int const nY = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(j)->nGetY(); // Store the coastline number and the number of the coastline point in the cell so we can get these quickly later - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCoast(nCoast); - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetPointOnCoast(j); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCoast(nCoast); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetPointOnCoast(j); // OK, start assigning coastal landforms. First, is there an intervention here? - if (bIsInterventionCell(nX, nY) || m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight() > 0) + if (bIsInterventionCell(nX, nY) || m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight() > 0) { // There is, so create an intervention object on the vector coastline with these attributes CACoastLandform* pIntervention = new CRWIntervention(&m_VCoast[nCoast], nCoast, j); m_VCoast[nCoast].AppendCoastLandform(pIntervention); - // LogStream << j << " [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} " << m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory() << " " << m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight() << endl; + // LogStream << j << " [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} " << m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory() << " " << m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight() << endl; continue; } // OK this landform is something other than an intervention. So check what we have at SWL on this cell: is it unconsolidated or consolidated sediment? Note that layer 0 is the first layer above basement - int const nLayer = m_pRasterGrid->m_Cell[nX][nY].nGetLayerAtElev(m_dThisIterSWL); + int const nLayer = m_pRasterGrid->Cell(nX, nY).nGetLayerAtElev(m_dThisIterSWL); if (nLayer == ELEV_IN_BASEMENT) { @@ -76,12 +76,12 @@ int CSimulation::nAssignLandformsForAllCoasts(void) else if (nLayer == ELEV_ABOVE_SEDIMENT_TOP) { // Again, should never happen - LogStream << m_ulIter << ": SWL (" << m_dThisIterSWL << ") is above sediment-top elevation (" << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << ") on cell [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, cannot assign coastal landform for coastline " << nCoast << endl; + LogStream << m_ulIter << ": SWL (" << m_dThisIterSWL << ") is above sediment-top elevation (" << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << ") on cell [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, cannot assign coastal landform for coastline " << nCoast << endl; return RTN_ERR_CANNOT_ASSIGN_COASTAL_LANDFORM; } - double const dConsSedTop = m_pRasterGrid->m_Cell[nX][nY].dGetConsSedTopElevForLayerAboveBasement(nLayer); + double const dConsSedTop = m_pRasterGrid->Cell(nX, nY).dGetConsSedTopElevForLayerAboveBasement(nLayer); bool bConsSedAtSWL = false; if (dConsSedTop >= m_dThisIterSWL) @@ -98,9 +98,9 @@ int CSimulation::nAssignLandformsForAllCoasts(void) // First timestep: we have consolidated sediment at SWL, so this is a cliff cell. Set some initial values for the cliff object's attributes // LogStream << m_ulIter << ": initialisation, cliff created at [" << nX << "][" << nY << "] m_dThisIterNotchBaseElev = " << m_dThisIterNotchBaseElev << " m_dThisIterSWL = " << m_dThisIterSWL << endl; - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchBaseElev(m_dThisIterNotchBaseElev); - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchBaseElev(m_dThisIterNotchBaseElev); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchDepth(0); // Create a cliff object on the vector coastline with these attributes CACoastLandform* pCliff = new CRWCliff(&m_VCoast[nCoast], nCoast, j, m_dCellSide, 0, m_dThisIterNotchBaseElev, 0); @@ -117,8 +117,8 @@ int CSimulation::nAssignLandformsForAllCoasts(void) m_VCoast[nCoast].AppendCoastLandform(pDrift); // Safety check - if (m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory() != LF_CAT_DRIFT) - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_DRIFT_MIXED); + if (m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory() != LF_CAT_DRIFT) + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_DRIFT_MIXED); // LogStream << m_ulIter << ": DRIFT CREATED [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; } @@ -134,25 +134,25 @@ int CSimulation::nAssignLandformsForAllCoasts(void) double dNotchElev; // Get the existing landform category of this cell - if (m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory() == LF_CAT_CLIFF) + if (m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory() == LF_CAT_CLIFF) { // This cell was a cliff in a previous timestep, so get the data stored in the cell, this will be stored in the cliff object on the vector coastline - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); - dAccumWaveEnergy = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->dGetAccumWaveEnergy(); - dNotchDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->dGetCliffNotchDepth(); - dNotchElev = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->dGetCliffNotchBaseElev(); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); + dAccumWaveEnergy = m_pRasterGrid->Cell(nX, nY).pGetLandform()->dGetAccumWaveEnergy(); + dNotchDepth = m_pRasterGrid->Cell(nX, nY).pGetLandform()->dGetCliffNotchDepth(); + dNotchElev = m_pRasterGrid->Cell(nX, nY).pGetLandform()->dGetCliffNotchBaseElev(); // LogStream << m_ulIter << ": continues to be a cliff at [" << nX << "][" << nY << "] notch base elevation = " << dNotchElev << " notch depth = " << dNotchDepth << " this-iteration SWL = " << m_dThisIterSWL << endl; } else { // This cell was not a cliff object in a previous timestep. Mark it as one now and set the cell with the default values for the cliff object's attributes - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); - dAccumWaveEnergy = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->dGetAccumWaveEnergy(); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); + dAccumWaveEnergy = m_pRasterGrid->Cell(nX, nY).pGetLandform()->dGetAccumWaveEnergy(); dNotchDepth = 0; - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchDepth(dNotchDepth); // Note that this is permanent i.e. stored for the remainder of the simulation + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchDepth(dNotchDepth); // Note that this is permanent i.e. stored for the remainder of the simulation dNotchElev = m_dThisIterNotchBaseElev; - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchBaseElev(dNotchElev); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchBaseElev(dNotchElev); // LogStream << m_ulIter << ": cliff created at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << " notch base elevation = " << dNotchElev << " this-iteration SWL = " << m_dThisIterSWL << endl; } @@ -168,8 +168,8 @@ int CSimulation::nAssignLandformsForAllCoasts(void) m_VCoast[nCoast].AppendCoastLandform(pDrift); // Safety check - if (m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory() != LF_CAT_DRIFT) - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_DRIFT_MIXED); + if (m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory() != LF_CAT_DRIFT) + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_DRIFT_MIXED); // LogStream << m_ulIter << ": drift created at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; } @@ -189,8 +189,8 @@ int CSimulation::nAssignLandformsForAllCoasts(void) // LogStream << m_ulIter << ": coast cell " << j << " at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} has landform category = "; // // int - // nCat = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory(), - // nSubCat = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFSubCategory(); + // nCat = m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory(), + // nSubCat = m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFSubCategory(); // // switch (nCat) // { @@ -298,14 +298,14 @@ int CSimulation::nLandformToGrid(int const nCoast, int const nPoint) // double const dNotchDepth = pCliff->dGetNotchDepth(); // // // And store some attribute values in the cliff cell - // m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); - // m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchBaseElev(dNotchBaseElev); - // m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchDepth(dNotchDepth); + // m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFSubCategory(LF_SUBCAT_CLIFF_ON_COASTLINE); + // m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchBaseElev(dNotchBaseElev); + // m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchDepth(dNotchDepth); // } // else // { // // // The cliff has collapsed: all sediment above the base of the erosional notch is gone from this cliff object via cliff collapse, so this cell is no longer a cliff - // // m_pRasterGrid->m_Cell[nX][nY].SetInContiguousSea(); + // // m_pRasterGrid->Cell(nX, nY).SetInContiguousSea(); // // // // // Check the x-y extremities of the contiguous sea for the bounding box (used later in wave propagation) // // if (nX < m_nXMinBoundingBox) @@ -320,21 +320,21 @@ int CSimulation::nLandformToGrid(int const nCoast, int const nPoint) // // if (nY > m_nYMaxBoundingBox) // // m_nYMaxBoundingBox = nY; // - // int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + // int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); // // // Safety check // if (nTopLayer == INT_NODATA) // return RTN_ERR_NO_TOP_LAYER; // // // Update the cell's layer elevations - // m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + // m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // // // And update the cell's sea depth - // m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + // m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); // } // Always accumulate wave energy - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetAccumWaveEnergy(pCliff->dGetTotAccumWaveEnergy()); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetAccumWaveEnergy(pCliff->dGetTotAccumWaveEnergy()); } else if (nCategory == LF_CAT_DRIFT) { @@ -354,7 +354,7 @@ int CSimulation::nAssignLandformsForAllCells(void) // Read-only phase: determine what changes need to be made #ifdef _OPENMP -#pragma omp parallel for collapse(2) +#pragma omp parallel for collapse(2) schedule(static) #endif for (int nX = 0; nX < m_nXGridSize; nX++) @@ -362,19 +362,19 @@ int CSimulation::nAssignLandformsForAllCells(void) for (int nY = 0; nY < m_nYGridSize; nY++) { // Get this cell's landform category - CRWCellLandform const* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform const* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); // Store what action to take (to avoid writing during read phase) int nAction = -1; // -1 = no change, others defined below - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { nAction = 0; // Set to unknown landform } else if ((nCat == LF_CAT_SEDIMENT_INPUT) || (nCat == LF_CAT_SEDIMENT_INPUT_SUBMERGED) || (nCat == LF_CAT_SEDIMENT_INPUT_NOT_SUBMERGED)) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) { nAction = 1; // Set to submerged sediment input } @@ -390,13 +390,13 @@ int CSimulation::nAssignLandformsForAllCells(void) { nAction = 3; // Set to beach } - else if (m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() > m_dThisIterSWL) + else if (m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() > m_dThisIterSWL) { nAction = 4; // Set to island } // else keep as sea (no action needed) } - else if (! m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + else if (! m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { // Is not coastline if (nCat == LF_CAT_CLIFF) @@ -423,7 +423,7 @@ int CSimulation::nAssignLandformsForAllCells(void) if (nAction == -1) continue; // No change - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); switch (nAction) { @@ -433,7 +433,7 @@ int CSimulation::nAssignLandformsForAllCells(void) case 1: // Set to submerged sediment input pLandform->SetLFCategory(LF_CAT_SEDIMENT_INPUT_SUBMERGED); - m_pRasterGrid->m_Cell[nX][nY].SetInContiguousSea(); + m_pRasterGrid->Cell(nX, nY).SetInContiguousSea(); break; case 2: // Set to not submerged sediment input @@ -476,7 +476,7 @@ bool CSimulation::bSurroundedByDriftCells(int const nX, int const nY) if (bIsWithinValidGrid(nXTmp, nYTmp)) { - CRWCellLandform const* pLandform = m_pRasterGrid->m_Cell[nXTmp][nYTmp].pGetLandform(); + CRWCellLandform const* pLandform = m_pRasterGrid->Cell(nXTmp, nYTmp).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat == LF_CAT_DRIFT) || (nCat == LF_CAT_CLIFF)) @@ -489,7 +489,7 @@ bool CSimulation::bSurroundedByDriftCells(int const nX, int const nY) if (bIsWithinValidGrid(nXTmp, nYTmp)) { - CRWCellLandform const* pLandform = m_pRasterGrid->m_Cell[nXTmp][nYTmp].pGetLandform(); + CRWCellLandform const* pLandform = m_pRasterGrid->Cell(nXTmp, nYTmp).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat == LF_CAT_DRIFT) || (nCat == LF_CAT_CLIFF)) @@ -502,7 +502,7 @@ bool CSimulation::bSurroundedByDriftCells(int const nX, int const nY) if (bIsWithinValidGrid(nXTmp, nYTmp)) { - CRWCellLandform const* pLandform = m_pRasterGrid->m_Cell[nXTmp][nYTmp].pGetLandform(); + CRWCellLandform const* pLandform = m_pRasterGrid->Cell(nXTmp, nYTmp).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat == LF_CAT_DRIFT) || (nCat == LF_CAT_CLIFF)) @@ -515,7 +515,7 @@ bool CSimulation::bSurroundedByDriftCells(int const nX, int const nY) if (bIsWithinValidGrid(nXTmp, nYTmp)) { - CRWCellLandform const* pLandform = m_pRasterGrid->m_Cell[nXTmp][nYTmp].pGetLandform(); + CRWCellLandform const* pLandform = m_pRasterGrid->Cell(nXTmp, nYTmp).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat == LF_CAT_DRIFT) || (nCat == LF_CAT_CLIFF)) diff --git a/src/calc_shadow_zones.cpp b/src/calc_shadow_zones.cpp index 3f8837c45..36b7bcb29 100644 --- a/src/calc_shadow_zones.cpp +++ b/src/calc_shadow_zones.cpp @@ -348,10 +348,10 @@ int CSimulation::nDoAllShadowZones(void) int const nXPrev = PtiPrev.nGetX(); int const nYPrev = PtiPrev.nGetY(); - if (! m_pRasterGrid->m_Cell[nXPrev][nYPrev].bIsInActiveZone()) + if (! m_pRasterGrid->Cell(nXPrev, nYPrev).bIsInActiveZone()) { // The previous cell was outside the active zone, so use its wave orientation value - dPrevWaveAngle = m_pRasterGrid->m_Cell[nXPrev][nYPrev].dGetWaveAngle(); + dPrevWaveAngle = m_pRasterGrid->Cell(nXPrev, nYPrev).dGetWaveAngle(); } else { @@ -366,7 +366,7 @@ int CSimulation::nDoAllShadowZones(void) else { // This shadow boundary has not already hit sea, just use the wave orientation from the previous cell - dPrevWaveAngle = m_pRasterGrid->m_Cell[nXPrev][nYPrev].dGetWaveAngle(); + dPrevWaveAngle = m_pRasterGrid->Cell(nXPrev, nYPrev).dGetWaveAngle(); // LogStream << m_ulIter << ": not already hit sea, using previous cell's wave orientation for cell [" << nXPrev << "][" << nYPrev << "] = {" << dGridCentroidXToExtCRSX(nXPrev) << ", " << dGridCentroidYToExtCRSY(nYPrev) << "}" << endl; } @@ -376,10 +376,10 @@ int CSimulation::nDoAllShadowZones(void) { // LogStream << m_ulIter << ": dPrevWaveAngle == DBL_NODATA for cell [" << nXPrev << "][" << nYPrev << "] = {" << dGridCentroidXToExtCRSX(nXPrev) << ", " << dGridCentroidYToExtCRSY(nYPrev) << "}" << endl; - if (! m_pRasterGrid->m_Cell[nXPrev][nYPrev].bIsInContiguousSea()) + if (! m_pRasterGrid->Cell(nXPrev, nYPrev).bIsInContiguousSea()) { // The previous cell was an inland cell, so use the deep water wave orientation - dPrevWaveAngle = m_pRasterGrid->m_Cell[nXPrev][nYPrev].dGetCellDeepWaterWaveAngle(); + dPrevWaveAngle = m_pRasterGrid->Cell(nXPrev, nYPrev).dGetCellDeepWaterWaveAngle(); } else { @@ -403,7 +403,7 @@ int CSimulation::nDoAllShadowZones(void) int const nY = PtiNew.nGetY(); // Have we hit the edge of the valid part of the grid? - if ((! bIsWithinValidGrid(&PtiNew)) || (m_pRasterGrid->m_Cell[nX][nY].bIsBoundingBoxEdge())) + if ((! bIsWithinValidGrid(&PtiNew)) || (m_pRasterGrid->Cell(nX, nY).bIsBoundingBoxEdge())) { // Yes we have bHitEdge = true; @@ -424,7 +424,7 @@ int CSimulation::nDoAllShadowZones(void) // OK so far. Have we hit a sea cell yet? if ((nDist > MAX_LAND_LENGTH_OF_SHADOW_ZONE_LINE) && (! bHitSea)) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) bHitSea = true; else { @@ -445,7 +445,7 @@ int CSimulation::nDoAllShadowZones(void) // Having hit sea, have we now hit we hit a coast point? Note that two diagonal(ish) raster lines can cross each other without any intersection, so must also test an adjacent cell for intersection (does not matter which adjacent cell) if (bHitSea) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsCoastline() || (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->m_Cell[nX][nY + 1].bIsCoastline())) + if (m_pRasterGrid->Cell(nX, nY).bIsCoastline() || (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->Cell(nX, nY + 1).bIsCoastline())) { bHitCoast = true; @@ -556,6 +556,7 @@ int CSimulation::nDoAllShadowZones(void) // ========================================================================================================================= // The third stage: store the shadow zone boundary, cell-by-cell fill the shadow zone, then change wave properties by sweeping the shadow zone and the area downdrift from the shadow zone + // First pass: flood fill all shadow zones (must be serial due to shared m_VCoast writes) for (unsigned int nZone = 0; nZone < VILShadowBoundary.size(); nZone++) { // if (m_nLogFileDetail >= LOG_FILE_HIGH_DETAIL) @@ -575,11 +576,11 @@ int CSimulation::nDoAllShadowZones(void) int const nTmpY = VILShadowBoundary[nZone][nn].nGetY(); // Mark the cells as shadow zone boundary - m_pRasterGrid->m_Cell[nTmpX][nTmpY].SetShadowZoneBoundary(); + m_pRasterGrid->Cell(nTmpX, nTmpY).SetShadowZoneBoundary(); // If this is a sea cell, mark the shadow zone boundary cell as being in the shadow zone, but not yet processed (a -ve number) - if (m_pRasterGrid->m_Cell[nTmpX][nTmpY].bIsInContiguousSea()) - m_pRasterGrid->m_Cell[nTmpX][nTmpY].SetShadowZoneNumber(-(nZone + 1)); + if (m_pRasterGrid->Cell(nTmpX, nTmpY).bIsInContiguousSea()) + m_pRasterGrid->Cell(nTmpX, nTmpY).SetShadowZoneNumber(-(nZone + 1)); // If not already there, append this values to the two shadow boundary vectors LBoundary.AppendIfNotAlready(dGridCentroidXToExtCRSX(nTmpX), dGridCentroidYToExtCRSY(nTmpY)); @@ -667,10 +668,17 @@ int CSimulation::nDoAllShadowZones(void) } } - // Sweep the shadow zone, changing wave orientation and height - DoShadowZoneAndDownDriftZone(nCoast, nZone, VnShadowBoundaryStartCoastPoint[nZone], VnShadowBoundaryEndCoastPoint[nZone]); } } + + // Second pass: sweep shadow zones and modify wave properties in parallel + // Each shadow zone has already been marked, so parallel processing is safe with critical sections + #pragma omp parallel for schedule(static) if(VILShadowBoundary.size() > 1) + for (unsigned int nZone = 0; nZone < VILShadowBoundary.size(); nZone++) + { + // Sweep the shadow zone, changing wave orientation and height + DoShadowZoneAndDownDriftZone(nCoast, nZone, VnShadowBoundaryStartCoastPoint[nZone], VnShadowBoundaryEndCoastPoint[nZone]); + } } return RTN_OK; @@ -686,7 +694,7 @@ int CSimulation::nFloodFillShadowZone(int const nZone, CGeom2DIPoint const* pPti bool bAllPointNotSea = true; CGeom2DIPoint PtiFloodFillStart = *pPtiCentroid; - if (! m_pRasterGrid->m_Cell[PtiFloodFillStart.nGetX()][PtiFloodFillStart.nGetY()].bIsInContiguousSea()) + if (! m_pRasterGrid->Cell(PtiFloodFillStart.nGetX(), PtiFloodFillStart.nGetY()).bIsInContiguousSea()) { // No it isn't: so try to find a cell that is bStartPointOK = false; @@ -713,7 +721,7 @@ int CSimulation::nFloodFillShadowZone(int const nZone, CGeom2DIPoint const* pPti return RTN_ERR_SHADOW_ZONE_FLOOD_FILL_NOGRID; } - if (m_pRasterGrid->m_Cell[PtiFloodFillStart.nGetX()][PtiFloodFillStart.nGetY()].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(PtiFloodFillStart.nGetX(), PtiFloodFillStart.nGetY()).bIsInContiguousSea()) { // Start point is a sea cell, all OK bStartPointOK = true; @@ -758,7 +766,7 @@ int CSimulation::nFloodFillShadowZone(int const nZone, CGeom2DIPoint const* pPti nX = Pti.nGetX(), nY = Pti.nGetY(); - while ((nX >= 0) && m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea() && (!m_pRasterGrid->m_Cell[nX][nY].bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->m_Cell[nX][nY].bIsShadowZoneBoundary()) && (!m_pRasterGrid->m_Cell[nX][nY].bIsCoastline())) + while ((nX >= 0) && m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea() && (!m_pRasterGrid->Cell(nX, nY).bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->Cell(nX, nY).bIsShadowZoneBoundary()) && (!m_pRasterGrid->Cell(nX, nY).bIsCoastline())) nX--; nX++; @@ -767,31 +775,31 @@ int CSimulation::nFloodFillShadowZone(int const nZone, CGeom2DIPoint const* pPti bSpanAbove = false, bSpanBelow = false; - while ((nX < m_nXGridSize) && m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea() && (!m_pRasterGrid->m_Cell[nX][nY].bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->m_Cell[nX][nY].bIsShadowZoneBoundary()) && (!m_pRasterGrid->m_Cell[nX][nY].bIsCoastline())) + while ((nX < m_nXGridSize) && m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea() && (!m_pRasterGrid->Cell(nX, nY).bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->Cell(nX, nY).bIsShadowZoneBoundary()) && (!m_pRasterGrid->Cell(nX, nY).bIsCoastline())) { // Mark the cell as being in the shadow zone but not yet processed (a -ve number, with -1 being zone 1) - m_pRasterGrid->m_Cell[nX][nY].SetShadowZoneNumber(-nZone - 1); + m_pRasterGrid->Cell(nX, nY).SetShadowZoneNumber(-nZone - 1); // LogStream << m_ulIter << ": [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} marked as shadow zone" << endl; - if ((! bSpanAbove) && (nY > 0) && m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea() && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsShadowZoneBoundary()) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsCoastline())) + if ((! bSpanAbove) && (nY > 0) && m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea() && (!m_pRasterGrid->Cell(nX, nY - 1).bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->Cell(nX, nY - 1).bIsShadowZoneBoundary()) && (!m_pRasterGrid->Cell(nX, nY - 1).bIsCoastline())) { PtiStack.push(CGeom2DIPoint(nX, nY - 1)); bSpanAbove = true; } - else if (bSpanAbove && (nY > 0) && ((!m_pRasterGrid->m_Cell[nX][nY - 1].bIsInContiguousSea()) || m_pRasterGrid->m_Cell[nX][nY - 1].bIsinThisShadowZone(-nZone - 1) || m_pRasterGrid->m_Cell[nX][nY - 1].bIsShadowZoneBoundary() || m_pRasterGrid->m_Cell[nX][nY - 1].bIsCoastline())) + else if (bSpanAbove && (nY > 0) && ((!m_pRasterGrid->Cell(nX, nY - 1).bIsInContiguousSea()) || m_pRasterGrid->Cell(nX, nY - 1).bIsinThisShadowZone(-nZone - 1) || m_pRasterGrid->Cell(nX, nY - 1).bIsShadowZoneBoundary() || m_pRasterGrid->Cell(nX, nY - 1).bIsCoastline())) { bSpanAbove = false; } - if ((! bSpanBelow) && m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea() && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsShadowZoneBoundary()) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsCoastline())) + if ((! bSpanBelow) && m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea() && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->Cell(nX, nY + 1).bIsinThisShadowZone(-nZone - 1)) && (!m_pRasterGrid->Cell(nX, nY + 1).bIsShadowZoneBoundary()) && (!m_pRasterGrid->Cell(nX, nY + 1).bIsCoastline())) { PtiStack.push(CGeom2DIPoint(nX, nY + 1)); bSpanBelow = true; } - else if (bSpanBelow && (nY < m_nYGridSize - 1) && ((!m_pRasterGrid->m_Cell[nX][nY + 1].bIsInContiguousSea()) || m_pRasterGrid->m_Cell[nX][nY + 1].bIsinThisShadowZone(-nZone - 1) || m_pRasterGrid->m_Cell[nX][nY + 1].bIsShadowZoneBoundary() || m_pRasterGrid->m_Cell[nX][nY + 1].bIsCoastline())) + else if (bSpanBelow && (nY < m_nYGridSize - 1) && ((!m_pRasterGrid->Cell(nX, nY + 1).bIsInContiguousSea()) || m_pRasterGrid->Cell(nX, nY + 1).bIsinThisShadowZone(-nZone - 1) || m_pRasterGrid->Cell(nX, nY + 1).bIsShadowZoneBoundary() || m_pRasterGrid->Cell(nX, nY + 1).bIsCoastline())) { bSpanBelow = false; } @@ -959,7 +967,7 @@ void CSimulation::DoShadowZoneAndDownDriftZone(int const nCoast, int const nZone LDownDriftBoundary.Append(&PtThis); // Mark the cell (a +ve number, same as the associated shadow zone number i.e. starting from 1) - m_pRasterGrid->m_Cell[nX][nY].SetDownDriftZoneNumber(nZone + 1); + m_pRasterGrid->Cell(nX, nY).SetDownDriftZoneNumber(nZone + 1); // Increment the boundary length nTotDownDriftBoundaryDistance++; @@ -982,7 +990,11 @@ void CSimulation::DoShadowZoneAndDownDriftZone(int const nCoast, int const nZone } // Store the downdrift boundary (external CRS), with the start point first - m_VCoast[nCoast].AppendShadowDowndriftBoundary(&LDownDriftBoundary); + // Protected with critical section since this modifies shared m_VCoast vector + #pragma omp critical(append_downdrift_boundary) + { + m_VCoast[nCoast].AppendShadowDowndriftBoundary(&LDownDriftBoundary); + } // Compare the lengths of the along-coast and the along-downdrift boundaries. The increment will be 1 for the smaller of the two, will be > 1 for the larger of the two int nMaxDistance; @@ -1125,7 +1137,7 @@ void CSimulation::DoShadowZoneAndDownDriftZone(int const nCoast, int const nZone } // Not a sea cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) { // Not a sea cell // LogStream << m_ulIter << ": n = " << n << ", m = " << m << ", dLinkingLineLength = " << dLinkingLineLength << ", dCoastDistSoFar = " << dCoastDistSoFar << " (nTotAlongCoastDistanceToDownDriftEndpoint = " << nTotAlongCoastDistanceToDownDriftEndpoint << "), dDownDriftBoundaryDistSoFar = " << dDownDriftBoundaryDistSoFar << " (nTotDownDriftBoundaryDistance = " << nTotDownDriftBoundaryDistance << ") not a sea cell at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; @@ -1145,7 +1157,7 @@ void CSimulation::DoShadowZoneAndDownDriftZone(int const nCoast, int const nZone // We have not, so the linking line has two parts: one between the coast and the shadow boundary, one between the shadow boundary and the downdrift boundary bool bInShadowZone = true; - if (! m_pRasterGrid->m_Cell[nX][nY].bIsinAnyShadowZone()) + if (! m_pRasterGrid->Cell(nX, nY).bIsinAnyShadowZone()) { // We have left the shadow zone // LogStream << "[" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} LEFT SHADOW ZONE" << endl; @@ -1241,7 +1253,7 @@ void CSimulation::DoShadowZoneAndDownDriftZone(int const nCoast, int const nZone void CSimulation::ProcessDownDriftCell(int const nX, int const nY, int const nTraversed, double const dTotalToTraverse, int const nZone) { // Get the pre-existing (i.e. shore-parallel) wave height - double const dWaveHeight = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + double const dWaveHeight = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); if (bFPIsEqual(dWaveHeight, DBL_NODATA, TOLERANCE)) { @@ -1251,7 +1263,7 @@ void CSimulation::ProcessDownDriftCell(int const nX, int const nY, int const nTr return; } - int nZoneCode = m_pRasterGrid->m_Cell[nX][nY].nGetShadowZoneNumber(); + int nZoneCode = m_pRasterGrid->Cell(nX, nY).nGetShadowZoneNumber(); if (nZoneCode == (nZone + 1)) { @@ -1269,7 +1281,7 @@ void CSimulation::ProcessDownDriftCell(int const nX, int const nY, int const nTr return; } - nZoneCode = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); + nZoneCode = m_pRasterGrid->Cell(nX, nY).nGetDownDriftZoneNumber(); if (nZoneCode == (nZone + 1)) { @@ -1280,16 +1292,16 @@ void CSimulation::ProcessDownDriftCell(int const nX, int const nY, int const nTr } // OK, we are downdrift of the shadow zone area and have not yet processed this cell for this zone, so mark it - m_pRasterGrid->m_Cell[nX][nY].SetDownDriftZoneNumber(nZone + 1); + m_pRasterGrid->Cell(nX, nY).SetDownDriftZoneNumber(nZone + 1); // Equation 14 from Hurst et al. TODO 056 Check this! Could not get this to work (typo in paper?), so used the equation below instead // double dKp = 0.5 * (1.0 - sin((PI * 90.0 * nSweep) / (180.0 * nSweepLength))); double const dKp = 0.5 + (0.5 * sin((PI * nTraversed) / (2.0 * dTotalToTraverse))); // Set the modified wave height - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dKp * dWaveHeight); + m_pRasterGrid->Cell(nX, nY).SetWaveHeight(dKp * dWaveHeight); - // LogStream << m_ulIter << ": [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, nTraversed = " << nTraversed << " dTotalToTraverse = " << dTotalToTraverse << " fraction traversed = " << nTraversed / dTotalToTraverse << endl << "m_pRasterGrid->m_Cell[" << nX << "][" << nY << "].dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight() << " m, original dWaveHeight = " << dWaveHeight << " m, dKp = " << dKp << ", modified wave height = " << dKp * dWaveHeight << " m" << endl << endl; + // LogStream << m_ulIter << ": [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, nTraversed = " << nTraversed << " dTotalToTraverse = " << dTotalToTraverse << " fraction traversed = " << nTraversed / dTotalToTraverse << endl << "m_pRasterGrid->Cell(" << nX << ", " << nY << ").dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight() << " m, original dWaveHeight = " << dWaveHeight << " m, dKp = " << dKp << ", modified wave height = " << dKp * dWaveHeight << " m" << endl << endl; } //=============================================================================================================================== @@ -1297,12 +1309,12 @@ void CSimulation::ProcessDownDriftCell(int const nX, int const nY, int const nTr //=============================================================================================================================== void CSimulation::ProcessShadowZoneCell(int const nX, int const nY, int const nShadowZoneCoastToCapeSeaHand, CGeom2DIPoint const* pPtiCoast, int const nShadowEndX, int const nShadowEndY, int const nZone) { - int const nZoneCode = m_pRasterGrid->m_Cell[nX][nY].nGetShadowZoneNumber(); + int const nZoneCode = m_pRasterGrid->Cell(nX, nY).nGetShadowZoneNumber(); if (nZoneCode == (-nZone - 1)) { // OK, we are in the shadow zone and have not already processed this cell, so mark it (a +ve number, starting from 1) - m_pRasterGrid->m_Cell[nX][nY].SetShadowZoneNumber(nZone + 1); + m_pRasterGrid->Cell(nX, nY).SetShadowZoneNumber(nZone + 1); // Next calculate wave angle here: first calculate dOmega, the signed angle subtended between this end point and the start point, and this end point and the end of the shadow boundary CGeom2DIPoint const PtiThis(nX, nY); @@ -1312,10 +1324,10 @@ void CSimulation::ProcessShadowZoneCell(int const nX, int const nY, int const nS // If dOmega is 90 degrees or more in either direction, set both wave angle and wave height to zero if (tAbs(dOmega) >= 90) { - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(0); - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(0); + m_pRasterGrid->Cell(nX, nY).SetWaveAngle(0); + m_pRasterGrid->Cell(nX, nY).SetWaveHeight(0); - // LogStream << m_ulIter << ": on shadow linking line with coast end [" << pPtiCoast->nGetX() << "][" << pPtiCoast->nGetY() << "] = {" << dGridCentroidXToExtCRSX(pPtiCoast->nGetX()) << ", " << dGridCentroidYToExtCRSY(pPtiCoast->nGetY()) << "} and shadow boundary end [" << PtiShadowBoundary.nGetX() << "][" << PtiShadowBoundary.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiShadowBoundary.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiShadowBoundary.nGetY()) << "}, this point [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl << "angle subtended = " << dOmega << " degrees, m_pRasterGrid->m_Cell[" << nX << "][" << nY << "].dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight() << " degrees, wave orientation = 0 degrees, wave height = 0 m" << endl; + // LogStream << m_ulIter << ": on shadow linking line with coast end [" << pPtiCoast->nGetX() << "][" << pPtiCoast->nGetY() << "] = {" << dGridCentroidXToExtCRSX(pPtiCoast->nGetX()) << ", " << dGridCentroidYToExtCRSY(pPtiCoast->nGetY()) << "} and shadow boundary end [" << PtiShadowBoundary.nGetX() << "][" << PtiShadowBoundary.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiShadowBoundary.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiShadowBoundary.nGetY()) << "}, this point [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl << "angle subtended = " << dOmega << " degrees, m_pRasterGrid->Cell(" << nX << ", " << nY << ").dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight() << " degrees, wave orientation = 0 degrees, wave height = 0 m" << endl; } else @@ -1324,7 +1336,7 @@ void CSimulation::ProcessShadowZoneCell(int const nX, int const nY, int const nS double const dDeltaShadowWaveAngle = 1.5 * dOmega; // Get the pre-existing (i.e. shore-parallel) wave orientation - double const dWaveAngle = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + double const dWaveAngle = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); double dShadowWaveAngle; @@ -1335,18 +1347,18 @@ void CSimulation::ProcessShadowZoneCell(int const nX, int const nY, int const nS dShadowWaveAngle = dWaveAngle - dDeltaShadowWaveAngle; // Set the shadow zone wave orientation - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(dKeepWithin360(dShadowWaveAngle)); + m_pRasterGrid->Cell(nX, nY).SetWaveAngle(dKeepWithin360(dShadowWaveAngle)); // Now calculate wave height within the shadow zone, use equation 13 from Hurst et al. double const dKp = 0.5 * cos(dOmega * PI / 180); // Get the pre-existing (i.e. shore-parallel) wave height - double const dWaveHeight = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + double const dWaveHeight = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // Set the shadow zone wave height - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dKp * dWaveHeight); + m_pRasterGrid->Cell(nX, nY).SetWaveHeight(dKp * dWaveHeight); - // LogStream << m_ulIter << ": on shadow linking line with coast end [" << pPtiCoast->nGetX() << "][" << pPtiCoast->nGetY() << "] = {" << dGridCentroidXToExtCRSX(pPtiCoast->nGetX()) << ", " << dGridCentroidYToExtCRSY(pPtiCoast->nGetY()) << "} and shadow boundary end [" << PtiShadowBoundary.nGetX() << "][" << PtiShadowBoundary.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiShadowBoundary.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiShadowBoundary.nGetY()) << "}, this point [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, angle subtended = " << dOmega << " degrees, m_pRasterGrid->m_Cell[" << nX << "][" << nY << "].dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight() << " m, dDeltaShadowWaveAngle = " << dDeltaShadowWaveAngle << " degrees, dWaveAngle = " << dWaveAngle << " degrees, dShadowWaveAngle = " << dShadowWaveAngle << " degrees, dWaveHeight = " << dWaveHeight << " m, dKp = " << dKp << ", shadow zone wave height = " << dKp * dWaveHeight << " m" << endl; + // LogStream << m_ulIter << ": on shadow linking line with coast end [" << pPtiCoast->nGetX() << "][" << pPtiCoast->nGetY() << "] = {" << dGridCentroidXToExtCRSX(pPtiCoast->nGetX()) << ", " << dGridCentroidYToExtCRSY(pPtiCoast->nGetY()) << "} and shadow boundary end [" << PtiShadowBoundary.nGetX() << "][" << PtiShadowBoundary.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiShadowBoundary.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiShadowBoundary.nGetY()) << "}, this point [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, angle subtended = " << dOmega << " degrees, m_pRasterGrid->Cell(" << nX << ", " << nY << ").dGetCellDeepWaterWaveHeight() = " << m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight() << " m, dDeltaShadowWaveAngle = " << dDeltaShadowWaveAngle << " degrees, dWaveAngle = " << dWaveAngle << " degrees, dShadowWaveAngle = " << dShadowWaveAngle << " degrees, dWaveHeight = " << dWaveHeight << " m, dKp = " << dKp << ", shadow zone wave height = " << dKp * dWaveHeight << " m" << endl; } } } diff --git a/src/calc_waves.cpp b/src/calc_waves.cpp index 59abf6c69..e77b47eba 100644 --- a/src/calc_waves.cpp +++ b/src/calc_waves.cpp @@ -22,10 +22,10 @@ #include #include -using std::sin; using std::cos; -using std::pow; using std::fmod; +using std::pow; +using std::sin; #include using std::to_string; @@ -45,13 +45,15 @@ using std::reverse_copy; #include "simulation.h" #include "cshore/cshore.h" #include "2d_point.h" +#include "cell.h" //=============================================================================================================================== //! Give every coast point a value for deep water wave height and direction TODO 005 This may not be realistic, maybe better to use end-of-profile value instead (how?) //=============================================================================================================================== int CSimulation::nSetAllCoastpointDeepWaterWaveValues(void) { - LogStream << endl << m_ulIter << ": Calculating waves" << endl; + LogStream << endl + << m_ulIter << ": Calculating waves" << endl; // For each coastline, put a value for deep water wave height and direction at each coastline point for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) @@ -73,7 +75,7 @@ int CSimulation::nSetAllCoastpointDeepWaterWaveValues(void) if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nPoint)) { // OK, a coastline-normal profile begins at this coastline point, so set the deep water wave values at this coastline point to be the values at the seaward end of the coastline normal - CGeomProfile const* pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nPoint); + CGeomProfile const *pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nPoint); // int nProfile = pProfile->nGetProfileID(); double const dThisDeepWaterWaveHeight = pProfile->dGetProfileDeepWaterWaveHeight(); @@ -91,7 +93,7 @@ int CSimulation::nSetAllCoastpointDeepWaterWaveValues(void) dPrevProfileDeepWaterWavePeriod = dThisDeepWaterWavePeriod; // Find the next profile - CGeomProfile const* pNextProfile = pProfile->pGetDownCoastAdjacentProfile(); + CGeomProfile const *pNextProfile = pProfile->pGetDownCoastAdjacentProfile(); if (pNextProfile == NULL) { @@ -140,7 +142,7 @@ int CSimulation::nSetAllCoastpointDeepWaterWaveValues(void) //! Uses OpenMP parallelization to process multiple synthetic transects concurrently //! OPTIMIZED: Pre-computes external CRS positions to avoid redundant coordinate conversions //=============================================================================================================================== -void CSimulation::GenerateSyntheticTransects(vector const* pVRealTransects, vector* pVAllTransects) +void CSimulation::GenerateSyntheticTransects(vector const *pVRealTransects, vector *pVAllTransects) { // Start with all the real transects *pVAllTransects = *pVRealTransects; @@ -162,8 +164,8 @@ void CSimulation::GenerateSyntheticTransects(vector const* pVR for (int i = 0; i < nNumRealTransects; i++) { - TransectWaveData const& transect = (*pVRealTransects)[i]; - if (!transect.VdX.empty()) + TransectWaveData const &transect = (*pVRealTransects)[i]; + if (! transect.VdX.empty()) { VdTransectExtX[i] = dGridCentroidXToExtCRSX(transect.VdX[0]); VdTransectExtY[i] = dGridCentroidYToExtCRSY(transect.VdY[0]); @@ -181,8 +183,8 @@ void CSimulation::GenerateSyntheticTransects(vector const* pVR for (int nPair = 0; nPair < nNumRealTransects - 1; nPair++) { - TransectWaveData const& transect1 = (*pVRealTransects)[nPair]; - TransectWaveData const& transect2 = (*pVRealTransects)[nPair + 1]; + TransectWaveData const &transect1 = (*pVRealTransects)[nPair]; + TransectWaveData const &transect2 = (*pVRealTransects)[nPair + 1]; // Only interpolate between transects on the same coast if (transect1.nCoastID != transect2.nCoastID) @@ -230,15 +232,15 @@ void CSimulation::GenerateSyntheticTransects(vector const* pVR // Using OpenMP to parallelize - each thread handles one pair int nCurrentIndex = 0; - #pragma omp parallel for schedule(dynamic) +#pragma omp parallel for schedule(static) for (int nPair = 0; nPair < nNumRealTransects - 1; nPair++) { int const nNumSynthetics = VnNumSyntheticsPerPair[nPair]; if (nNumSynthetics == 0) continue; - TransectWaveData const& transect1 = (*pVRealTransects)[nPair]; - TransectWaveData const& transect2 = (*pVRealTransects)[nPair + 1]; + TransectWaveData const &transect1 = (*pVRealTransects)[nPair]; + TransectWaveData const &transect2 = (*pVRealTransects)[nPair + 1]; // Calculate the starting index for this pair's synthetic transects int nPairStartIndex = 0; @@ -249,7 +251,7 @@ void CSimulation::GenerateSyntheticTransects(vector const* pVR for (int nSynth = 1; nSynth <= nNumSynthetics; nSynth++) { int const nSynthIndex = nPairStartIndex + (nSynth - 1); - TransectWaveData& synthTransect = VSyntheticTransects[nSynthIndex]; + TransectWaveData &synthTransect = VSyntheticTransects[nSynthIndex]; // Calculate interpolation weight (0 < alpha < 1) double const dAlpha = static_cast(nSynth) / (nNumSynthetics + 1); @@ -257,7 +259,7 @@ void CSimulation::GenerateSyntheticTransects(vector const* pVR // Set metadata for synthetic transect synthTransect.nCoastID = transect1.nCoastID; - synthTransect.nProfileID = -1; // Mark as synthetic + synthTransect.nProfileID = -1; // Mark as synthetic synthTransect.bIsGridEdge = false; // Interpolate points - use the minimum length to avoid extrapolation @@ -392,9 +394,9 @@ int CSimulation::nDoAllPropagateWaves(void) for (int nX = 0; nX < m_nXGridSize; nX++) { - if (m_pRasterGrid->m_Cell[nX][0].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nX, 0).bIsInContiguousSea()) { - int const nPolyID = m_pRasterGrid->m_Cell[nX][0].nGetPolygonID(); + int const nPolyID = m_pRasterGrid->Cell(nX, 0).nGetPolygonID(); if (nPolyID == INT_NODATA) { @@ -405,8 +407,8 @@ int CSimulation::nDoAllPropagateWaves(void) if (! m_bSingleDeepWaterWaveValues) { // Not using the same value of deep water height and angle for all cells, so get this cell's deep water height and angle values - dDeepWaterWaveX = m_pRasterGrid->m_Cell[nX][0].dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->m_Cell[nX][0].dGetCellDeepWaterWaveAngle() * PI / 180); - dDeepWaterWaveY = m_pRasterGrid->m_Cell[nX][0].dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->m_Cell[nX][0].dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveX = m_pRasterGrid->Cell(nX, 0).dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->Cell(nX, 0).dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveY = m_pRasterGrid->Cell(nX, 0).dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->Cell(nX, 0).dGetCellDeepWaterWaveAngle() * PI / 180); } VdDeepWaterHeightX.push_back(dDeepWaterWaveX); @@ -414,9 +416,9 @@ int CSimulation::nDoAllPropagateWaves(void) } } - if (m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nX, m_nYGridSize - 1).bIsInContiguousSea()) { - int const nPolyID = m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].nGetPolygonID(); + int const nPolyID = m_pRasterGrid->Cell(nX, m_nYGridSize - 1).nGetPolygonID(); if (nPolyID == INT_NODATA) { @@ -427,8 +429,8 @@ int CSimulation::nDoAllPropagateWaves(void) if (! m_bSingleDeepWaterWaveValues) { // Not using the same value of deep water height and angle for all cells, so get this cell's deep water height and angle values - dDeepWaterWaveX = m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].dGetCellDeepWaterWaveAngle() * PI / 180); - dDeepWaterWaveY = m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->m_Cell[nX][m_nYGridSize - 1].dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveX = m_pRasterGrid->Cell(nX, m_nYGridSize - 1).dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->Cell(nX, m_nYGridSize - 1).dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveY = m_pRasterGrid->Cell(nX, m_nYGridSize - 1).dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->Cell(nX, m_nYGridSize - 1).dGetCellDeepWaterWaveAngle() * PI / 180); } VdDeepWaterHeightX.push_back(dDeepWaterWaveX); @@ -439,9 +441,9 @@ int CSimulation::nDoAllPropagateWaves(void) for (int nY = 0; nY < m_nYGridSize; nY++) { - if (m_pRasterGrid->m_Cell[0][nY].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(0, nY).bIsInContiguousSea()) { - int const nPolyID = m_pRasterGrid->m_Cell[0][nY].nGetPolygonID(); + int const nPolyID = m_pRasterGrid->Cell(0, nY).nGetPolygonID(); if (nPolyID == INT_NODATA) { @@ -452,8 +454,8 @@ int CSimulation::nDoAllPropagateWaves(void) if (! m_bSingleDeepWaterWaveValues) { // Not using the same value of deep water height and angle for all cells, so get this cell's deep water height and angle values - dDeepWaterWaveX = m_pRasterGrid->m_Cell[0][nY].dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->m_Cell[0][nY].dGetCellDeepWaterWaveAngle() * PI / 180); - dDeepWaterWaveY = m_pRasterGrid->m_Cell[0][nY].dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->m_Cell[0][nY].dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveX = m_pRasterGrid->Cell(0, nY).dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->Cell(0, nY).dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveY = m_pRasterGrid->Cell(0, nY).dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->Cell(0, nY).dGetCellDeepWaterWaveAngle() * PI / 180); } VdDeepWaterHeightX.push_back(dDeepWaterWaveX); @@ -461,9 +463,9 @@ int CSimulation::nDoAllPropagateWaves(void) } } - if (m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(m_nXGridSize - 1, nY).bIsInContiguousSea()) { - int const nPolyID = m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].nGetPolygonID(); + int const nPolyID = m_pRasterGrid->Cell(m_nXGridSize - 1, nY).nGetPolygonID(); if (nPolyID == INT_NODATA) { @@ -474,8 +476,8 @@ int CSimulation::nDoAllPropagateWaves(void) if (! m_bSingleDeepWaterWaveValues) { // Not using the same value of deep water height and angle for all cells, so get this cell's deep water height and angle values - dDeepWaterWaveX = m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].dGetCellDeepWaterWaveAngle() * PI / 180); - dDeepWaterWaveY = m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->m_Cell[m_nXGridSize - 1][nY].dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveX = m_pRasterGrid->Cell(m_nXGridSize - 1, nY).dGetCellDeepWaterWaveHeight() * sin(m_pRasterGrid->Cell(m_nXGridSize - 1, nY).dGetCellDeepWaterWaveAngle() * PI / 180); + dDeepWaterWaveY = m_pRasterGrid->Cell(m_nXGridSize - 1, nY).dGetCellDeepWaterWaveHeight() * cos(m_pRasterGrid->Cell(m_nXGridSize - 1, nY).dGetCellDeepWaterWaveAngle() * PI / 180); } VdDeepWaterHeightX.push_back(dDeepWaterWaveX); @@ -487,16 +489,16 @@ int CSimulation::nDoAllPropagateWaves(void) // Are the waves off-shore for every profile? If so, do nothing more // Check if any transect has breaking waves bool bHasBreakingWaves = false; - for (const auto& transect : VAllTransects) + for (auto const &transect : VAllTransects) { - if (!transect.VbBreaking.empty()) + if (! transect.VbBreaking.empty()) { bHasBreakingWaves = true; break; } } - if (!bHasBreakingWaves) + if (! bHasBreakingWaves) { LogStream << m_ulIter << ": waves off-shore for all profiles" << endl; return RTN_OK; @@ -532,7 +534,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // } // } // @@ -564,7 +566,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); // } // } // @@ -604,7 +606,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // } // } // @@ -636,7 +638,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); // } // } // @@ -677,7 +679,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // } // } // @@ -709,7 +711,7 @@ int CSimulation::nDoAllPropagateWaves(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); // } // } // @@ -802,8 +804,8 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast // Calculate some wave properties based on the wave period following Airy wave theory double const dDeepWaterWavePeriod = pProfile->dGetProfileDeepWaterWavePeriod(); - m_dC_0 = (m_dG * dDeepWaterWavePeriod) / (2 * PI); // Deep water (offshore) wave celerity (m/s) - m_dL_0 = m_dC_0 * dDeepWaterWavePeriod; // Deep water (offshore) wave length (m) + m_dC_0 = (m_dG * dDeepWaterWavePeriod) / (2 * PI); // Deep water (offshore) wave celerity (m/s) + m_dL_0 = m_dC_0 * dDeepWaterWavePeriod; // Deep water (offshore) wave length (m) int const nSeaHand = m_VCoast[nCoast].nGetSeaHandedness(); int const nCoastPoint = pProfile->nGetCoastPoint(); @@ -968,13 +970,13 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast if (m_nWavePropagationModel == WAVE_MODEL_CSHORE) { // We are using CShore to propagate the waves - double const dCShoreTimeStep = 3600; // In seconds, not important because we are not using CShore to erode the profile, just to get the hydrodynamics + double const dCShoreTimeStep = 3600; // In seconds, not important because we are not using CShore to erode the profile, just to get the hydrodynamics double const dSurgeLevel = CSHORE_SURGE_LEVEL; // Set up vectors for the coastline-normal profile elevations. The length of this vector line is given by the number of cells 'under' the profile. Thus each point on the vector relates to a single cell in the grid. This assumes that all points on the profile vector are equally spaced (not quite true, depends on the orientation of the line segments which comprise the profile) - vector VdProfileZ; // Initial (pre-erosion) elevation of both consolidated and unconsolidated sediment for cells 'under' the profile, in CShore units - vector VdProfileDistXY; // Along-profile distance measured from the seaward limit, in CShore units - vector VdProfileFrictionFactor; // Along-profile friction factor from seaward limit + vector VdProfileZ; // Initial (pre-erosion) elevation of both consolidated and unconsolidated sediment for cells 'under' the profile, in CShore units + vector VdProfileDistXY; // Along-profile distance measured from the seaward limit, in CShore units + vector VdProfileFrictionFactor; // Along-profile friction factor from seaward limit // The elevation of each of these profile points is the elevation of the centroid of the cell that is 'under' the point. However we cannot always be confident that this is the 'true' elevation of the point on the vector since (unless the profile runs planview N-S or W-E) the vector does not always run exactly through the centroid of the cell int nRet = nGetThisProfileElevationsForCShore(nCoast, pProfile, nProfileSize, &VdProfileDistXY, &VdProfileZ, &VdProfileFrictionFactor); @@ -1004,25 +1006,25 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast dWaveToNormalAngle = tMin(dWaveToNormalAngle, 80.0); int const nProfileDistXYSize = static_cast(VdProfileDistXY.size()); - vector VdFreeSurfaceStd(nProfileDistXYSize, 0); // This is converted to Hrms by Hrms = sqr(8)*FreeSurfaceStd - vector VdSinWaveAngleRadians(nProfileDistXYSize, 0); // This is converted to deg by asin(VdSinWaveAngleRadians)*(180/pi) - vector VdFractionBreakingWaves(nProfileDistXYSize, 0); // Is 0 if no wave breaking, and 1 if all waves breaking + vector VdFreeSurfaceStd(nProfileDistXYSize, 0); // This is converted to Hrms by Hrms = sqr(8)*FreeSurfaceStd + vector VdSinWaveAngleRadians(nProfileDistXYSize, 0); // This is converted to deg by asin(VdSinWaveAngleRadians)*(180/pi) + vector VdFractionBreakingWaves(nProfileDistXYSize, 0); // Is 0 if no wave breaking, and 1 if all waves breaking // Now define the other values that CShore requires - int const nILine = 1; // This is the number of cross-shore lines i.e. the number of CoastalME profiles. Only one at a time, at present - int const nIProfl = 0; // 0 for fixed bottom profile, 1 for profile evolution computation - int const nIPerm = 0; // 0 for impermeable bottom, 1 for permeable bottom of stone structure - int const nIOver = 0; // 0 for no wave overtopping and overflow on crest, 1 for wave overtopping and overflow - int const nIWCInt = 0; // 0 for no wave/current interaction, 1 for wave/current interaction in frequency dispersion, momentum and wave action equations - int const nIRoll = 0; // 0 for no roller effects, 1 for roller effects in governing equations - int const nIWind = 0; // 0 for no wind effects, 1 for wind shear stresses on momentum equations - int const nITide = 0; // 0 for no tidal effect on currents, 1 for longshore and cross-shore tidal currents - int const nILab = 0; // 0 for field data set, 1 for laboratory data set - int const nNWave = 1; // Number of waves at x = 0 starting from time = 0 - int const nNSurge = 1; // Number of water levels at x = 0 from time = 0 + int const nILine = 1; // This is the number of cross-shore lines i.e. the number of CoastalME profiles. Only one at a time, at present + int const nIProfl = 0; // 0 for fixed bottom profile, 1 for profile evolution computation + int const nIPerm = 0; // 0 for impermeable bottom, 1 for permeable bottom of stone structure + int const nIOver = 0; // 0 for no wave overtopping and overflow on crest, 1 for wave overtopping and overflow + int const nIWCInt = 0; // 0 for no wave/current interaction, 1 for wave/current interaction in frequency dispersion, momentum and wave action equations + int const nIRoll = 0; // 0 for no roller effects, 1 for roller effects in governing equations + int const nIWind = 0; // 0 for no wind effects, 1 for wind shear stresses on momentum equations + int const nITide = 0; // 0 for no tidal effect on currents, 1 for longshore and cross-shore tidal currents + int const nILab = 0; // 0 for field data set, 1 for laboratory data set + int const nNWave = 1; // Number of waves at x = 0 starting from time = 0 + int const nNSurge = 1; // Number of water levels at x = 0 from time = 0 double const dDX = VdProfileDistXY.back() / (static_cast(VdProfileDistXY.size() - 1)); - double const dWaveInitTime = 0; // CShore wave start time - double const dSurgeInitTime = 0; // CShore surge start time + double const dWaveInitTime = 0; // CShore wave start time + double const dSurgeInitTime = 0; // CShore surge start time #if defined CSHORE_FILE_INOUT || CSHORE_BOTH // Move to the CShore folder @@ -1054,29 +1056,29 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast switch (nRet) { - case -1: - strErr = to_string(m_ulIter) + ": CShore WARNING 1: negative depth at the first node "; - break; + case -1: + strErr = to_string(m_ulIter) + ": CShore WARNING 1: negative depth at the first node "; + break; - case 2: - strErr = to_string(m_ulIter) + ": CShore WARNING 2: negative value at end of landward marching computation "; - break; + case 2: + strErr = to_string(m_ulIter) + ": CShore WARNING 2: negative value at end of landward marching computation "; + break; - case 3: - strErr = to_string(m_ulIter) + ": CShore WARNING 3: large energy gradients at the first node: small waves with short period at sea boundary "; - break; + case 3: + strErr = to_string(m_ulIter) + ": CShore WARNING 3: large energy gradients at the first node: small waves with short period at sea boundary "; + break; - case 4: - strErr = to_string(m_ulIter) + ": CShore WARNING 4: zero energy at the first node "; - break; + case 4: + strErr = to_string(m_ulIter) + ": CShore WARNING 4: zero energy at the first node "; + break; - case 5: - strErr = to_string(m_ulIter) + ": CShore WARNING 5: at end of landward marching computation, insufficient water depth "; - break; + case 5: + strErr = to_string(m_ulIter) + ": CShore WARNING 5: at end of landward marching computation, insufficient water depth "; + break; - case 7: - strErr = to_string(m_ulIter) + ": CShore WARNING 7: did not reach convergence "; - break; + case 7: + strErr = to_string(m_ulIter) + ": CShore WARNING 7: did not reach convergence "; + break; } strErr += "(coast " + to_string(nCoast) + " profile " + to_string(pProfile->nGetProfileID()) + " profile length " + to_string(nOutSize) + ")\n"; @@ -1112,9 +1114,9 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast // VdSWLin = {dSurgeLevel, dSurgeLevel}, // Ditto // Clean up the CShore outputs -#ifdef _WIN32 + #ifdef _WIN32 nRet = system("./clean.bat"); -#else + #else if (SAVE_CSHORE_OUTPUT) { @@ -1132,7 +1134,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast } nRet = system("./clean.sh"); -#endif + #endif if (nRet != RTN_OK) return nRet; @@ -1147,25 +1149,25 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast #if defined CSHORE_ARG_INOUT || CSHORE_BOTH // We are communicating with CShore by passing arguments - int nOutSize = 0; // CShore will return the size of the output vectors + int nOutSize = 0; // CShore will return the size of the output vectors // Set the error flag: this will be changed within CShore if there is a problem nRet = 0; - vector VdInitTime = {dWaveInitTime, dCShoreTimeStep}; // Size is nNwave+1, value 1 is for the start of the CShore run, value 2 for end of CShore run - vector VdTPIn = {dDeepWaterWavePeriod, dDeepWaterWavePeriod}; // Ditto - vector VdHrmsIn = {dProfileDeepWaterWaveHeight, dProfileDeepWaterWaveHeight}; // Ditto - vector VdWangIn = {dWaveToNormalAngle, dWaveToNormalAngle}; // Ditto - vector VdTSurg = {dSurgeInitTime, dCShoreTimeStep}; // Ditto - vector VdSWLin = {dSurgeLevel, dSurgeLevel}; // Ditto - vector VdFPInp = VdProfileFrictionFactor; // Set the value for wave friction at every point of the normal profile - vector VdXYDistFromCShoreOut(CSHOREARRAYOUTSIZE, 0); // Output from CShore - vector VdFreeSurfaceStdOut(CSHOREARRAYOUTSIZE, 0); // Ditto - vector VdWaveSetupSurgeOut(CSHOREARRAYOUTSIZE, 0); // Ditto + vector VdInitTime = {dWaveInitTime, dCShoreTimeStep}; // Size is nNwave+1, value 1 is for the start of the CShore run, value 2 for end of CShore run + vector VdTPIn = {dDeepWaterWavePeriod, dDeepWaterWavePeriod}; // Ditto + vector VdHrmsIn = {dProfileDeepWaterWaveHeight, dProfileDeepWaterWaveHeight}; // Ditto + vector VdWangIn = {dWaveToNormalAngle, dWaveToNormalAngle}; // Ditto + vector VdTSurg = {dSurgeInitTime, dCShoreTimeStep}; // Ditto + vector VdSWLin = {dSurgeLevel, dSurgeLevel}; // Ditto + vector VdFPInp = VdProfileFrictionFactor; // Set the value for wave friction at every point of the normal profile + vector VdXYDistFromCShoreOut(CSHOREARRAYOUTSIZE, 0); // Output from CShore + vector VdFreeSurfaceStdOut(CSHOREARRAYOUTSIZE, 0); // Ditto + vector VdWaveSetupSurgeOut(CSHOREARRAYOUTSIZE, 0); // Ditto // vector VdStormSurgeOut(CSHOREARRAYOUTSIZE, 0); // Ditto - vector const VdWaveSetupRunUpOut(CSHOREARRAYOUTSIZE, 0); // Ditto - vector VdSinWaveAngleRadiansOut(CSHOREARRAYOUTSIZE, 0); // Ditto - vector VdFractionBreakingWavesOut(CSHOREARRAYOUTSIZE, 0); // Ditto + vector const VdWaveSetupRunUpOut(CSHOREARRAYOUTSIZE, 0); // Ditto + vector VdSinWaveAngleRadiansOut(CSHOREARRAYOUTSIZE, 0); // Ditto + vector VdFractionBreakingWavesOut(CSHOREARRAYOUTSIZE, 0); // Ditto // Call CShore using the argument-passing wrapper // long lIter = static_cast(m_ulIter); // Bodge to get round compiler 'invalid conversion' error @@ -1229,29 +1231,29 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast switch (nRet) { - case -1: - strErr = to_string(m_ulIter) + ": CShore WARNING 1: negative depth at the first node "; - break; + case -1: + strErr = to_string(m_ulIter) + ": CShore WARNING 1: negative depth at the first node "; + break; - case 2: - strErr = to_string(m_ulIter) + ": CShore WARNING 2: negative value at end of landward marching computation "; - break; + case 2: + strErr = to_string(m_ulIter) + ": CShore WARNING 2: negative value at end of landward marching computation "; + break; - case 3: - strErr = to_string(m_ulIter) + ": CShore WARNING 3: large energy gradients at the first node: small waves with short period at sea boundary "; - break; + case 3: + strErr = to_string(m_ulIter) + ": CShore WARNING 3: large energy gradients at the first node: small waves with short period at sea boundary "; + break; - case 4: - strErr = to_string(m_ulIter) + ": CShore WARNING 4: zero energy at the first node "; - break; + case 4: + strErr = to_string(m_ulIter) + ": CShore WARNING 4: zero energy at the first node "; + break; - case 5: - strErr = to_string(m_ulIter) + ": CShore WARNING 5: at end of landward marching computation, insufficient water depth "; - break; + case 5: + strErr = to_string(m_ulIter) + ": CShore WARNING 5: at end of landward marching computation, insufficient water depth "; + break; - case 7: - strErr = to_string(m_ulIter) + ": CShore WARNING 7: did not reach convergence "; - break; + case 7: + strErr = to_string(m_ulIter) + ": CShore WARNING 7: did not reach convergence "; + break; } strErr += "(coast " + to_string(nCoast) + " profile " + to_string(pProfile->nGetProfileID()) + " profile length " + to_string(nOutSize) + ")\n"; @@ -1268,7 +1270,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast #endif #if defined CSHORE_BOTH -#if !defined _WIN32 + #if ! defined _WIN32 if (SAVE_CSHORE_OUTPUT) { @@ -1285,7 +1287,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast return nRet; } -#endif + #endif // Return to the CoastalME folder nRet = chdir(m_strCMEDir.c_str()); @@ -1341,14 +1343,14 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast } // if ((VdFractionBreakingWaves[nProfilePoint] >= 0.10) && (! bBreaking)) // Sometimes is possible that waves break again - if ((VdFractionBreakingWaves[nProfilePoint] >= 0.10) && (m_dDepthOfClosure >= m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth()) && (! bBreaking)) + if ((VdFractionBreakingWaves[nProfilePoint] >= 0.10) && (m_dDepthOfClosure >= m_pRasterGrid->Cell(nX, nY).dGetSeaDepth()) && (! bBreaking)) { bBreaking = true; // assert(VdWaveHeight[nProfilePoint] >= 0); dProfileBreakingWaveHeight = VdWaveHeight[nProfilePoint]; dProfileBreakingWaveAngle = VdWaveDirection[nProfilePoint]; - dProfileBreakingDepth = m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); // Water depth for the cell 'under' this point in the profile - nProfileBreakingDist = nProfilePoint + 1; // At the nearest point nProfilePoint = 0, so, plus one + dProfileBreakingDepth = m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(); // Water depth for the cell 'under' this point in the profile + nProfileBreakingDist = nProfilePoint + 1; // At the nearest point nProfilePoint = 0, so, plus one // LogStream << m_ulIter << ": CShore breaking at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} nProfile = " << nProfile << ", nProfilePoint = " << nProfilePoint << ", dBreakingWaveHeight = " << dBreakingWaveHeight << ", dBreakingWaveAngle = " << dBreakingWaveAngle << ", dProfileBreakingDepth = " << dProfileBreakingDepth << ", nProfileBreakingDist = " << nProfileBreakingDist << endl; } @@ -1358,7 +1360,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast if (dProfileBreakingWaveHeight >= dProfileDeepWaterWaveHeight) { - dProfileBreakingWaveHeight = DBL_NODATA; // checking poorly conditions profiles problems for cshore + dProfileBreakingWaveHeight = DBL_NODATA; // checking poorly conditions profiles problems for cshore } } @@ -1374,10 +1376,10 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast int const nY = pProfile->pPtiGetCellInProfile(nProfilePoint)->nGetY(); // Safety check - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) continue; - double const dSeaDepth = m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); // Water depth for the cell 'under' this point in the profile + double const dSeaDepth = m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(); // Water depth for the cell 'under' this point in the profile if (dSeaDepth > dDepthLookupMax) { @@ -1391,14 +1393,14 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast if (! bBreaking) { // Start calculating wave properties using linear wave theory - double const dL = m_dL_0 * sqrt(tanh((2 * PI * dSeaDepth) / m_dL_0)); // Wavelength (m) in intermediate-shallow waters - double const dC = m_dC_0 * tanh((2 * PI * dSeaDepth) / dL); // Wave speed (m/s) set by dSeaDepth, dL and m_dC_0 - double const dk = 2 * PI / dL; // Wave number (1/m) - double const dn = ((2 * dSeaDepth * dk) / (sinh(2 * dSeaDepth * dk)) + 1) / 2; // Shoaling factor - double const dKs = sqrt(m_dC_0 / (dn * dC * 2)); // Shoaling coefficient - double const dAlpha = (180 / PI) * asin((dC / m_dC_0) * sin((PI / 180) * dWaveToNormalAngle)); // Calculate angle between wave direction and the normal to the coast tangent - double const dKr = sqrt(cos((PI / 180) * dWaveToNormalAngle) / cos((PI / 180) * dAlpha)); // Refraction coefficient - dProfileWaveHeight = dProfileDeepWaterWaveHeight * dKs * dKr; // Calculate wave height, based on the previous (more seaward) wave height + double const dL = m_dL_0 * sqrt(tanh((2 * PI * dSeaDepth) / m_dL_0)); // Wavelength (m) in intermediate-shallow waters + double const dC = m_dC_0 * tanh((2 * PI * dSeaDepth) / dL); // Wave speed (m/s) set by dSeaDepth, dL and m_dC_0 + double const dk = 2 * PI / dL; // Wave number (1/m) + double const dn = ((2 * dSeaDepth * dk) / (sinh(2 * dSeaDepth * dk)) + 1) / 2; // Shoaling factor + double const dKs = sqrt(m_dC_0 / (dn * dC * 2)); // Shoaling coefficient + double const dAlpha = (180 / PI) * asin((dC / m_dC_0) * sin((PI / 180) * dWaveToNormalAngle)); // Calculate angle between wave direction and the normal to the coast tangent + double const dKr = sqrt(cos((PI / 180) * dWaveToNormalAngle) / cos((PI / 180) * dAlpha)); // Refraction coefficient + dProfileWaveHeight = dProfileDeepWaterWaveHeight * dKs * dKr; // Calculate wave height, based on the previous (more seaward) wave height if (nSeaHand == LEFT_HANDED) dProfileWaveAngle = dKeepWithin360(dAlpha + 90 + dFluxOrientationThis); @@ -1420,9 +1422,9 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast bBreaking = true; // Wave has already broken - dProfileWaveAngle = dProfileBreakingWaveAngle; // Wave orientation remains equal to wave orientation at breaking + dProfileWaveAngle = dProfileBreakingWaveAngle; // Wave orientation remains equal to wave orientation at breaking - dProfileWaveHeight = dProfileBreakingWaveHeight * (nProfilePoint / nProfileBreakingDist); // Wave height decreases linearly to zero at shoreline + dProfileWaveHeight = dProfileBreakingWaveHeight * (nProfilePoint / nProfileBreakingDist); // Wave height decreases linearly to zero at shoreline // dProfileWaveHeight = dSeaDepth * m_dBreakingWaveHeightDepthRatio; // Wave height is limited by depth dProfileBreakingDepth = dSeaDepth; nProfileBreakingDist = nProfilePoint; @@ -1443,7 +1445,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast int nY = pProfile->pPtiGetCellInProfile(nProfilePoint)->nGetY(); // Safety check - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) continue; // Get the wave attributes calculated for this profile: wave height, wave angle, and whether is in the active zone @@ -1481,7 +1483,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast double const dDiffProfileDistXY = hypot(dXDist, dYDist); // Compute the beach slope - double const dtanBeta = tan(tAbs(m_pRasterGrid->m_Cell[nX1][nY1].dGetSeaDepth() - m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth()) / dDiffProfileDistXY); + double const dtanBeta = tan(tAbs(m_pRasterGrid->Cell(nX1, nY1).dGetSeaDepth() - m_pRasterGrid->Cell(nX, nY).dGetSeaDepth()) / dDiffProfileDistXY); // Compute the wave run-up using NIELSEN & HANSLOW (1991) & DHI (2004) int nValidPointsWaveHeight = 0; @@ -1504,7 +1506,7 @@ int CSimulation::nCalcWavePropertiesOnProfile(int const nCoast, int const nCoast for (int nPoint = 0; nPoint < static_cast(VdWaveSetupSurge.size()); nPoint++) { - if (tAbs(VdWaveSetupSurge[nPoint]) < 1) // limiting the absolute value of setup + surge if cshore run fails + if (tAbs(VdWaveSetupSurge[nPoint]) < 1) // limiting the absolute value of setup + surge if cshore run fails { nValidPointsWaveSetup += 1; } @@ -1620,7 +1622,7 @@ int CSimulation::nCreateCShoreInfile(int const nCoast, int const nProfile, int c } // And write to the file - CShoreOutStream << 3 << endl; // Number of comment lines + CShoreOutStream << 3 << endl; // Number of comment lines CShoreOutStream << "------------------------------------------------------------" << endl; CShoreOutStream << "CShore input file created by CoastalME for iteration " << m_ulIter << ", coast " << nCoast << ", profile " << nProfile << endl; CShoreOutStream << "------------------------------------------------------------" << endl; @@ -1640,24 +1642,24 @@ int CSimulation::nCreateCShoreInfile(int const nCoast, int const nProfile, int c CShoreOutStream << setw(11) << nSurge << " -> NSURGE" << endl; // Line 18 of infile - CShoreOutStream << setw(11) << setprecision(2) << dWaveInitTime; // TWAVE(1) in CShore - CShoreOutStream << setw(11) << setprecision(4) << dWavePeriod; // TPIN(1) in CShore - CShoreOutStream << setw(11) << dHrms; // HRMS(1) in CShore - CShoreOutStream << setw(11) << dWaveAngle << endl; // WANGIN(1) in CShore + CShoreOutStream << setw(11) << setprecision(2) << dWaveInitTime; // TWAVE(1) in CShore + CShoreOutStream << setw(11) << setprecision(4) << dWavePeriod; // TPIN(1) in CShore + CShoreOutStream << setw(11) << dHrms; // HRMS(1) in CShore + CShoreOutStream << setw(11) << dWaveAngle << endl; // WANGIN(1) in CShore // Line 19 of infile - CShoreOutStream << setw(11) << setprecision(2) << dTimestep; // TWAVE(2) in CShore - CShoreOutStream << setw(11) << setprecision(4) << dWavePeriod; // TPIN(2) in CShore - CShoreOutStream << setw(11) << dHrms; // HRMS(2) in CShore - CShoreOutStream << setw(11) << dWaveAngle << endl; // WANGIN(2) in CShore + CShoreOutStream << setw(11) << setprecision(2) << dTimestep; // TWAVE(2) in CShore + CShoreOutStream << setw(11) << setprecision(4) << dWavePeriod; // TPIN(2) in CShore + CShoreOutStream << setw(11) << dHrms; // HRMS(2) in CShore + CShoreOutStream << setw(11) << dWaveAngle << endl; // WANGIN(2) in CShore // Line 20 of infile - CShoreOutStream << setw(11) << setprecision(2) << dSurgeInitTime; // TSURG(1) in CShore - CShoreOutStream << setw(11) << setprecision(4) << dSurgeLevel << endl; // SWLIN(1) in CShore + CShoreOutStream << setw(11) << setprecision(2) << dSurgeInitTime; // TSURG(1) in CShore + CShoreOutStream << setw(11) << setprecision(4) << dSurgeLevel << endl; // SWLIN(1) in CShore // Line 21 of infile - CShoreOutStream << setw(11) << setprecision(2) << dTimestep; // TSURG(2) in CShore - CShoreOutStream << setw(11) << setprecision(4) << dSurgeLevel << endl; // SWLIN(2) in CShore + CShoreOutStream << setw(11) << setprecision(2) << dTimestep; // TSURG(2) in CShore + CShoreOutStream << setw(11) << setprecision(4) << dSurgeLevel << endl; // SWLIN(2) in CShore // Line 22 of infile CShoreOutStream << setw(8) << pVdXdist->size() << " -> NBINP" << endl; @@ -1711,14 +1713,14 @@ int CSimulation::nGetThisProfileElevationsForCShore(int const nCoast, CGeomProfi // Before we store the X-Y distance, must check that it is not the same as the previously-stored distance (if it is, we get zero-divide errors in CShore). If they are the same, add on a small distance if (bFPIsEqual(dProfileDistXY, dPrevDist, TOLERANCE)) - dProfileDistXY += 0.1; // TODO 084 Improve this + dProfileDistXY += 0.1; // TODO 084 Improve this // Update the cell indexes, the initial cell is now the previous one nX1 = nX; nY1 = nY; // Get the number of the highest layer with non-zero thickness - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety checks if (nTopLayer == INT_NODATA) @@ -1729,7 +1731,7 @@ int CSimulation::nGetThisProfileElevationsForCShore(int const nCoast, CGeomProfi return RTN_OK; // Get the elevation for both consolidated and unconsolidated sediment on this cell - double const dTopElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() + m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight(); + double const dTopElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() + m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight(); double const VdProfileZ = dTopElev - m_dThisIterSWL; // Check that landward elevation is greater than SWL @@ -1737,7 +1739,7 @@ int CSimulation::nGetThisProfileElevationsForCShore(int const nCoast, CGeomProfi { if (VdProfileZ < 0) { - VdVZ->push_back(0.1); // TODO 053 Set it to a small +ve elevation (compared with SWL). However there must be a better way of doing this + VdVZ->push_back(0.1); // TODO 053 Set it to a small +ve elevation (compared with SWL). However there must be a better way of doing this // Could not create the profile elevation vectors LogStream << m_ulIter << ": " << WARN << "for coast " << nCoast << ", profile " << pProfile->nGetProfileID() << ", elevation at the landward end is " << dTopElev << " m. This is lower than this-iteration SWL (" << m_dThisIterSWL << " m). For CShore, changing the landward elevation for profile " << pProfile->nGetProfileID() << " to " << m_dThisIterSWL + 0.1 << "m" << endl; @@ -1758,7 +1760,7 @@ int CSimulation::nGetThisProfileElevationsForCShore(int const nCoast, CGeomProfi VdDistXY->push_back(dProfileDistXY); // Get the landform type at each point along the profile - double const dInterventionHeight = m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight(); + double const dInterventionHeight = m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight(); // Modify default friction factor if a structural intervention is found, otherwise use the default if (dInterventionHeight > 0 || bIsBehindIntervention) @@ -1792,7 +1794,7 @@ int CSimulation::nReadCShoreOutput(int const nProfile, string const *strCShoreFi InStream.open(strCShoreFilename->c_str(), ios::in); // Did it open OK? - if (!InStream.is_open()) + if (! InStream.is_open()) { // Error: cannot open CShore file for input LogStream << m_ulIter << ": " << ERR << "for profile " << nProfile << ", cannot open " << *strCShoreFilename << " for input" << endl; @@ -1992,7 +1994,7 @@ void CSimulation::InterpolateCShoreOutput(vector const *pVdProfileDistXY // // DEBUG CODE ======================================================================================================== // Finally we linearly interpolate the CShore output vectors to each point on the CoastalME profile. Input parameters x_old, y_old, x_new and the routine returns y_new - *pVdFreeSurfaceStdCME = VdInterpolateCShoreProfileOutput(&VdXYDistCShoreTmp, pVdFreeSurfaceStdCShore, pVdProfileDistXYCME); // was &VdDistXYCopy); + *pVdFreeSurfaceStdCME = VdInterpolateCShoreProfileOutput(&VdXYDistCShoreTmp, pVdFreeSurfaceStdCShore, pVdProfileDistXYCME); // was &VdDistXYCopy); *pVdWaveSetupSurgeCME = VdInterpolateCShoreProfileOutput(&VdXYDistCShoreTmp, pVdWaveSetupSurgeCShore, pVdProfileDistXYCME); // *pVdStormSurgeCME = VdInterpolateCShoreProfileOutput(&VdXYDistCShoreTmp, pVdStormSurgeCShore, pVdProfileDistXYCME); // *pVdWaveSetupRunUpCME = VdInterpolateCShoreProfileOutput(&VdXYDistCShoreTmp, pVdWaveSetupRunUpCShore, pVdProfileDistXYCME); @@ -2023,7 +2025,7 @@ void CSimulation::ModifyBreakingWavePropertiesWithinShadowZoneToCoastline(int co int const nThisCoastPoint = pProfile->nGetCoastPoint(); int const nProfileSize = pProfile->nGetNumCellsInProfile(); int nThisBreakingDist = m_VCoast[nCoast].nGetBreakingDistance(nThisCoastPoint); - double dThisBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nThisCoastPoint); // This could be DBL_NODATA + double dThisBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nThisCoastPoint); // This could be DBL_NODATA double dThisBreakingWaveAngle = m_VCoast[nCoast].dGetBreakingWaveAngle(nThisCoastPoint); double dThisBreakingDepth = m_VCoast[nCoast].dGetDepthOfBreaking(nThisCoastPoint); @@ -2034,12 +2036,12 @@ void CSimulation::ModifyBreakingWavePropertiesWithinShadowZoneToCoastline(int co int const nY = pProfile->pPtiGetCellInProfile(nProfilePoint)->nGetY(); // If there is any cell profile within the shadow zone and waves are breaking then modify wave breaking properties otherwise continue - if (m_pRasterGrid->m_Cell[nX][nY].bIsinAnyShadowZone()) + if (m_pRasterGrid->Cell(nX, nY).bIsinAnyShadowZone()) { bProfileIsinShadowZone = true; // Check if the new wave height is breaking - double const dWaveHeight = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + double const dWaveHeight = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // Check that wave height at the given point is lower than maximum real wave height. If breaking wave height is expected that no good wave height are obtained, so, do not take it if (dWaveHeight > (m_dDepthOfClosure * m_dBreakingWaveHeightDepthRatio) && (! bModfiedWaveHeightisBreaking) && (! bFPIsEqual(dThisBreakingWaveHeight, DBL_NODATA, TOLERANCE))) @@ -2048,7 +2050,7 @@ void CSimulation::ModifyBreakingWavePropertiesWithinShadowZoneToCoastline(int co bModfiedWaveHeightisBreaking = true; dThisBreakingWaveHeight = m_dDepthOfClosure * m_dBreakingWaveHeightDepthRatio; - dThisBreakingWaveAngle = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + dThisBreakingWaveAngle = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); dThisBreakingDepth = m_dDepthOfClosure; nThisBreakingDist = nProfilePoint; } @@ -2056,12 +2058,12 @@ void CSimulation::ModifyBreakingWavePropertiesWithinShadowZoneToCoastline(int co } // Update breaking wave properties along coastal line object (Wave height, dir, distance). TODO 010 Update the active zone cells - if (bProfileIsinShadowZone && bModfiedWaveHeightisBreaking) // Modified wave height is still breaking + if (bProfileIsinShadowZone && bModfiedWaveHeightisBreaking) // Modified wave height is still breaking { // This coast point is in the active zone, so set breaking wave height, breaking wave angle, and depth of breaking for the coast point TODO 007 Where does the 0.78 come from? TODO 011 Should it be an input variable or a named constant? if (dThisBreakingWaveHeight > dThisBreakingDepth * 0.78) { - dThisBreakingWaveHeight = dThisBreakingDepth * 0.78; // Likely CShore output wave height is not adequately reproduced due to input profile and wave properties. TODO 007 Finish surge and runup stuff. Does something need to be changed then? + dThisBreakingWaveHeight = dThisBreakingDepth * 0.78; // Likely CShore output wave height is not adequately reproduced due to input profile and wave properties. TODO 007 Finish surge and runup stuff. Does something need to be changed then? } // assert(dThisBreakingWaveHeight >= 0); @@ -2102,7 +2104,7 @@ void CSimulation::InterpolateWavePropertiesBetweenProfiles(int const nCoast, int // For the breaking wave stuff, to go into the in-between coastline points int const nThisBreakingDist = m_VCoast[nCoast].nGetBreakingDistance(nThisCoastPoint); - double const dThisBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nThisCoastPoint); // This could be DBL_NODATA + double const dThisBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nThisCoastPoint); // This could be DBL_NODATA double const dThisBreakingWaveAngle = m_VCoast[nCoast].dGetBreakingWaveAngle(nThisCoastPoint); double const dThisBreakingDepth = m_VCoast[nCoast].dGetDepthOfBreaking(nThisCoastPoint); double const dThisWaveSetupSurge = m_VCoast[nCoast].dGetWaveSetupSurge(nThisCoastPoint); @@ -2143,7 +2145,7 @@ void CSimulation::InterpolateWavePropertiesBetweenProfiles(int const nCoast, int return; int const nNextBreakingDist = m_VCoast[nCoast].nGetBreakingDistance(nNextCoastPoint); - double const dNextBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nNextCoastPoint); // This could be DBL_NODATA + double const dNextBreakingWaveHeight = m_VCoast[nCoast].dGetBreakingWaveHeight(nNextCoastPoint); // This could be DBL_NODATA double const dNextBreakingWaveAngle = m_VCoast[nCoast].dGetBreakingWaveAngle(nNextCoastPoint); double const dNextBreakingDepth = m_VCoast[nCoast].dGetDepthOfBreaking(nNextCoastPoint); double const dNextWaveSetupSurge = m_VCoast[nCoast].dGetWaveSetupSurge(nNextCoastPoint); @@ -2322,8 +2324,8 @@ void CSimulation::CalcCoastTangents(int const nCoast) if (nCoastPoint == 0) { // For the point at the start of the coastline: use the straight line from 'this' point to the next point - CGeom2DPoint const PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint); // In external CRS - CGeom2DPoint const PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint + 1); // In external CRS + CGeom2DPoint const PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint); // In external CRS + CGeom2DPoint const PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint + 1); // In external CRS dXDiff = PtAfter.dGetX() - PtThis.dGetX(); dYDiff = PtAfter.dGetY() - PtThis.dGetY(); @@ -2332,8 +2334,8 @@ void CSimulation::CalcCoastTangents(int const nCoast) else if (nCoastPoint == nCoastSize - 1) { // For the point at the end of the coastline: use the straight line from the point before to 'this' point - CGeom2DPoint const PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint - 1); // In external CRS - CGeom2DPoint const PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint); // In external CRS + CGeom2DPoint const PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint - 1); // In external CRS + CGeom2DPoint const PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint); // In external CRS dXDiff = PtThis.dGetX() - PtBefore.dGetX(); dYDiff = PtThis.dGetY() - PtBefore.dGetY(); @@ -2342,8 +2344,8 @@ void CSimulation::CalcCoastTangents(int const nCoast) else { // For coastline points not at the start or end of the coast: start with a straight line which links the coastline points before and after 'this' coastline point - CGeom2DPoint const PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint - 1); // In external CRS - CGeom2DPoint const PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint + 1); // In external CRS + CGeom2DPoint const PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint - 1); // In external CRS + CGeom2DPoint const PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nCoastPoint + 1); // In external CRS dXDiff = PtAfter.dGetX() - PtBefore.dGetX(); dYDiff = PtAfter.dGetY() - PtBefore.dGetY(); @@ -2393,63 +2395,147 @@ void CSimulation::CalcCoastTangents(int const nCoast) } //=============================================================================================================================== -//! Calculates an average d50 for each polygon. Also fills in 'holes' in active zone and wave calcs i.e. orphan cells which should have been included in the active zone but which have been omitted because of rounding problems +//! Helper function to process a single neighbor cell for wave calculations +//=============================================================================================================================== +void CSimulation::ProcessNeighborForWaveCalc( + int const nXNeighbor, + int const nYNeighbor, + int &nRead, + int &nActive, + int &nShadow, + int &nShadowNum, + int &nDownDrift, + int &nDownDriftNum, + int &nCoast, + double &dWaveHeight, + double &dWaveAngle) +{ + if (! bIsWithinValidGrid(nXNeighbor, nYNeighbor)) + return; + + auto &rNeighbor = m_pRasterGrid->Cell(nXNeighbor, nYNeighbor); + + if (rNeighbor.bIsInContiguousSea()) + { + nRead++; + dWaveHeight += rNeighbor.dGetWaveHeight(); + dWaveAngle += rNeighbor.dGetWaveAngle(); + + if (rNeighbor.bIsInActiveZone()) + nActive++; + + int nTmp = rNeighbor.nGetShadowZoneNumber(); + if (nTmp != 0) + { + nShadow++; + nShadowNum = nTmp; + } + + nTmp = rNeighbor.nGetDownDriftZoneNumber(); + if (nTmp > 0) + { + nDownDrift++; + nDownDriftNum = nTmp; + } + } + else + { + nCoast++; + } +} + +//=============================================================================================================================== +//! Calculates an average d50 for each polygon. +//Also fills in 'holes' in active zone and wave calcs i.e. orphan cells which +//should have been included in the active zone but which have been omitted because of rounding problems //=============================================================================================================================== void CSimulation::CalcD50AndFillWaveCalcHoles(void) +{ + // Calculate D50 for all polygons + CalcD50(); + + // Fill wave calculation holes + FillWaveCalcHoles(); +} + +void CSimulation::CalcD50(void) { // Get the total number of polygons, all coasts int nTotPolygonAllCoasts = 0; for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) - nTotPolygonAllCoasts += m_VCoast[nCoast].nGetNumPolygons(); + nTotPolygonAllCoasts += m_VCoast[nCoast].nGetNumPolygons(); // Vectors for D50 stuff vector VnPolygonD50Count(nTotPolygonAllCoasts, 0); vector VdPolygonD50(nTotPolygonAllCoasts, 0); +#pragma omp parallel for collapse(2) schedule(static) for (int nX = 0; nX < m_nXGridSize; nX++) { for (int nY = 0; nY < m_nYGridSize; nY++) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + // Cache cell reference to avoid repeated array lookups + auto &rCell = m_pRasterGrid->Cell(nX, nY); + + if (rCell.bIsInContiguousSea()) { // This is a sea cell, first get polygon ID - int const nPolyID = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); + int const nPolyID = rCell.nGetPolygonID(); // Is it in the active zone? - bool const bActive = m_pRasterGrid->m_Cell[nX][nY].bIsInActiveZone(); + bool const bActive = rCell.bIsInActiveZone(); if (bActive) { // It is in the active zone. Does it have unconsolidated sediment on it? Test this using the UnconD50 value: if dGetUnconsD50() returns DBL_NODATA, there is no unconsolidated sediment - double const dTmpd50 = m_pRasterGrid->m_Cell[nX][nY].dGetUnconsD50(); + double const dTmpd50 = rCell.dGetUnconsD50(); if (! bFPIsEqual(dTmpd50, DBL_NODATA, TOLERANCE)) { // It does have unconsolidated sediment, so which polygon is this cell in? if (nPolyID != INT_NODATA) { +#pragma omp atomic VnPolygonD50Count[nPolyID]++; +#pragma omp atomic VdPolygonD50[nPolyID] += dTmpd50; } } } + } + } + } + +// Calculate the average d50 for every polygon +#pragma omp parallel for schedule(static) + for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) + { + for (int nPoly = 0; nPoly < m_VCoast[nCoast].nGetNumPolygons(); nPoly++) + { + CGeomCoastPolygon *pPolygon = m_VCoast[nCoast].pGetPolygon(nPoly); + int const nPolyID = pPolygon->nGetPolygonCoastID(); + + if (VnPolygonD50Count[nPolyID] > 0) + VdPolygonD50[nPolyID] /= VnPolygonD50Count[nPolyID]; - // // Now fill in wave calc holes - // if (m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight() == 0) - // { - // if (nID == INT_NODATA) - // m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(m_dAllCellsDeepWaterWaveHeight); - // } - // - // if (m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle() == 0) - // { - // if (nID == INT_NODATA) - // m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(m_dAllCellsDeepWaterWaveAngle); - // } + pPolygon->SetAvgUnconsD50(VdPolygonD50[nPolyID]); + } + } +} + +void CSimulation::FillWaveCalcHoles(void) +{ +#pragma omp parallel for collapse(2) schedule(static) + for (int nX = 0; nX < m_nXGridSize; nX++) + { + for (int nY = 0; nY < m_nYGridSize; nY++) + { + // Cache cell reference to avoid repeated array lookups + auto &rCell = m_pRasterGrid->Cell(nX, nY); + if (rCell.bIsInContiguousSea()) + { // Next look at the cell's N-S and W-E neighbours - int nXTmp; - int nYTmp; int nActive = 0; int nShadow = 0; int nShadowNum = 0; @@ -2460,149 +2546,11 @@ void CSimulation::CalcD50AndFillWaveCalcHoles(void) double dWaveHeight = 0; double dWaveAngle = 0; - // North - nXTmp = nX; - nYTmp = nY - 1; - - if (bIsWithinValidGrid(nXTmp, nYTmp)) - { - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInContiguousSea()) - { - nRead++; - dWaveHeight += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveHeight(); - dWaveAngle += (m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveAngle()); - - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInActiveZone()) - nActive++; - - int nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetShadowZoneNumber(); - - if (nTmp != 0) - { - nShadow++; - nShadowNum = nTmp; - } - - nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetDownDriftZoneNumber(); - - if (nTmp > 0) - { - nDownDrift++; - nDownDriftNum = nTmp; - } - } - - else - nCoast++; - } - - // East - nXTmp = nX + 1; - nYTmp = nY; - - if (bIsWithinValidGrid(nXTmp, nYTmp)) - { - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInContiguousSea()) - { - nRead++; - dWaveHeight += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveHeight(); - dWaveAngle += (m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveAngle()); - - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInActiveZone()) - nActive++; - - int nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetShadowZoneNumber(); - - if (nTmp != 0) - { - nShadow++; - nShadowNum = nTmp; - } - - nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetDownDriftZoneNumber(); - - if (nTmp > 0) - { - nDownDrift++; - nDownDriftNum = nTmp; - } - } - - else - nCoast++; - } - - // South - nXTmp = nX; - nYTmp = nY + 1; - - if (bIsWithinValidGrid(nXTmp, nYTmp)) - { - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInContiguousSea()) - { - nRead++; - dWaveHeight += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveHeight(); - dWaveAngle += (m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveAngle()); - - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInActiveZone()) - nActive++; - - int nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetShadowZoneNumber(); - - if (nTmp != 0) - { - nShadow++; - nShadowNum = nTmp; - } - - nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetDownDriftZoneNumber(); - - if (nTmp > 0) - { - nDownDrift++; - nDownDriftNum = nTmp; - } - } - - else - nCoast++; - } - - // West - nXTmp = nX - 1; - nYTmp = nY; - - if (bIsWithinValidGrid(nXTmp, nYTmp)) - { - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInContiguousSea()) - { - nRead++; - dWaveHeight += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveHeight(); - dWaveAngle += (m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetWaveAngle()); - - if (m_pRasterGrid->m_Cell[nXTmp][nYTmp].bIsInActiveZone()) - nActive++; - - int nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetShadowZoneNumber(); - - if (nTmp != 0) - { - nShadow++; - nShadowNum = nTmp; - } - - nTmp = m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetDownDriftZoneNumber(); - - if (nTmp > 0) - { - nDownDrift++; - nDownDriftNum = nTmp; - } - } - - else - nCoast++; - } + // Process all four neighbors (N, E, S, W) + ProcessNeighborForWaveCalc(nX, nY - 1, nRead, nActive, nShadow, nShadowNum, nDownDrift, nDownDriftNum, nCoast, dWaveHeight, dWaveAngle); // North + ProcessNeighborForWaveCalc(nX + 1, nY, nRead, nActive, nShadow, nShadowNum, nDownDrift, nDownDriftNum, nCoast, dWaveHeight, dWaveAngle); // East + ProcessNeighborForWaveCalc(nX, nY + 1, nRead, nActive, nShadow, nShadowNum, nDownDrift, nDownDriftNum, nCoast, dWaveHeight, dWaveAngle); // South + ProcessNeighborForWaveCalc(nX - 1, nY, nRead, nActive, nShadow, nShadowNum, nDownDrift, nDownDriftNum, nCoast, dWaveHeight, dWaveAngle); // West if (nRead > 0) { @@ -2614,67 +2562,52 @@ void CSimulation::CalcD50AndFillWaveCalcHoles(void) // If this sea cell has four active-zone neighbours, then it must also be in the active zone: give it wave height and orientation which is the average of its neighbours if (nActive == 4) { - m_pRasterGrid->m_Cell[nX][nY].SetInActiveZone(true); - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dWaveHeight); - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(dWaveAngle); + rCell.SetInActiveZone(true); + rCell.SetWaveHeight(dWaveHeight); + rCell.SetWaveAngle(dWaveAngle); } // If this sea cell has a wave height which is the same as its deep-water wave height, but its neighbours have a different average wave height, then give it the average of its neighbours - double const dDeepWaterWaveHeight = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight(); + double const dDeepWaterWaveHeight = rCell.dGetCellDeepWaterWaveHeight(); - if ((bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(), dDeepWaterWaveHeight, TOLERANCE)) && (! bFPIsEqual(dDeepWaterWaveHeight, dWaveHeight, TOLERANCE))) + if ((bFPIsEqual(rCell.dGetWaveHeight(), dDeepWaterWaveHeight, TOLERANCE)) && (! bFPIsEqual(dDeepWaterWaveHeight, dWaveHeight, TOLERANCE))) { - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dWaveHeight); + rCell.SetWaveHeight(dWaveHeight); } // If this sea cell has a wave orientation which is the same as its deep-water wave orientation, but its neighbours have a different average wave orientation, then give it the average of its neighbours - double const dDeepWaterWaveAngle = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle(); + double const dDeepWaterWaveAngle = rCell.dGetCellDeepWaterWaveAngle(); - if ((bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(), dDeepWaterWaveAngle, TOLERANCE)) && (! bFPIsEqual(dDeepWaterWaveAngle, dWaveAngle, TOLERANCE))) + if ((bFPIsEqual(rCell.dGetWaveAngle(), dDeepWaterWaveAngle, TOLERANCE)) && (! bFPIsEqual(dDeepWaterWaveAngle, dWaveAngle, TOLERANCE))) { - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(dWaveAngle); + rCell.SetWaveAngle(dWaveAngle); } // Is this sea cell is not already marked as in a shadow zone (note could be marked as in a shadow zone but not yet processed: a -ve number)? - int const nShadowZoneCode = m_pRasterGrid->m_Cell[nX][nY].nGetShadowZoneNumber(); + int const nShadowZoneCode = rCell.nGetShadowZoneNumber(); if (nShadowZoneCode <= 0) { // If the cell has four neighbours which are all in a shadow zone, or four neighbours some of which are shadow zone and the remainder downdrift zone, or four neighbours some of which are shadow zone and the remainder coast; then it should also be in the shadow zone: give it the average of its neighbours if ((nShadow == 4) || (nShadow + nDownDrift == 4) || (nShadow + nCoast == 4)) { - m_pRasterGrid->m_Cell[nX][nY].SetShadowZoneNumber(nShadowNum); - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dWaveHeight); - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(dWaveAngle); + rCell.SetShadowZoneNumber(nShadowNum); + rCell.SetWaveHeight(dWaveHeight); + rCell.SetWaveAngle(dWaveAngle); } } // If this sea cell is not marked as in a downdrift zone but has four neighbours which are in a downdrift zone, then it should also be in the downdrift zone: give it the average of its neighbours - int const nDownDriftZoneCode = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); + int const nDownDriftZoneCode = rCell.nGetDownDriftZoneNumber(); if ((nDownDriftZoneCode == 0) && (nDownDrift == 4)) { - m_pRasterGrid->m_Cell[nX][nY].SetDownDriftZoneNumber(nDownDriftNum); - m_pRasterGrid->m_Cell[nX][nY].SetWaveHeight(dWaveHeight); - m_pRasterGrid->m_Cell[nX][nY].SetWaveAngle(dWaveAngle); + rCell.SetDownDriftZoneNumber(nDownDriftNum); + rCell.SetWaveHeight(dWaveHeight); + rCell.SetWaveAngle(dWaveAngle); } } } } } - - // Calculate the average d50 for every polygon - for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) - { - for (int nPoly = 0; nPoly < m_VCoast[nCoast].nGetNumPolygons(); nPoly++) - { - CGeomCoastPolygon* pPolygon = m_VCoast[nCoast].pGetPolygon(nPoly); - int const nPolyID = pPolygon->nGetPolygonCoastID(); - - if (VnPolygonD50Count[nPolyID] > 0) - VdPolygonD50[nPolyID] /= VnPolygonD50Count[nPolyID]; - - pPolygon->SetAvgUnconsD50(VdPolygonD50[nPolyID]); - } - } } diff --git a/src/cell.h b/src/cell.h index 222c91467..02b30d47e 100644 --- a/src/cell.h +++ b/src/cell.h @@ -420,4 +420,9 @@ class CGeomCell void SetDownDriftZoneNumber(int const); int nGetDownDriftZoneNumber(void) const; }; + +// Include inline implementations for CGeomRasterGrid::Cell() methods +// Must be included here after CGeomCell is fully defined to avoid circular dependency +#include "raster_grid_inline.h" + #endif // CELL_H diff --git a/src/cme.h b/src/cme.h index e083e4c74..423138c0e 100644 --- a/src/cme.h +++ b/src/cme.h @@ -227,32 +227,34 @@ using std::ostringstream; #include using std::ostream; +#include + //===================================================== platform-specific stuff ================================================= #ifdef _WIN32 #define access _access - #define F_OK 0 // Test for file existence + #define F_OK 0 // Test for file existence #endif #ifdef _MSC_VER // MS Visual C++ compiler, byte order is IEEE little-endian #ifdef _DEBUG - #include // useful + #include // useful #endif - // clock_t is a signed long: see - long const CLOCK_T_MIN = LONG_MIN; - double const CLOCK_T_RANGE = static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); +// clock_t is a signed long: see +long const CLOCK_T_MIN = LONG_MIN; +double const CLOCK_T_RANGE = static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); #ifdef _M_ALPHA - string const PLATFORM = "MS Visual C++ for Alpha"; +string const PLATFORM = "MS Visual C++ for Alpha"; #elif defined _M_IX86 - string const PLATFORM = "MS Visual C++ for Intel x86"; +string const PLATFORM = "MS Visual C++ for Intel x86"; #elif defined _M_MPPC - string const PLATFORM = "MS Visual C++ for Power PC"; +string const PLATFORM = "MS Visual C++ for Power PC"; #elif defined _M_MRX000 - string const PLATFORM = "MS Visual C++ for MIPS"; +string const PLATFORM = "MS Visual C++ for MIPS"; #else - string const PLATFORM = "MS Visual C++ for unknown CPU"; +string const PLATFORM = "MS Visual C++ for unknown CPU"; #endif #elif defined __GNUG__ @@ -261,32 +263,32 @@ using std::ostream; #error "CPU not defined" #else #ifdef x86 - // Intel x86, byte order is little-endian - string const PLATFORM = "GNU Compiler for Intel x86"; - // clock_t is an unsigned long: see - unsigned long const CLOCK_T_MIN = 0; - double const CLOCK_T_RANGE = static_cast(ULONG_MAX); +// Intel x86, byte order is little-endian +string const PLATFORM = "GNU Compiler for Intel x86"; +// clock_t is an unsigned long: see +unsigned long const CLOCK_T_MIN = 0; +double const CLOCK_T_RANGE = static_cast(ULONG_MAX); #elif defined rs6000 - // IBM RS-6000, byte order is big-endian - string const PLATFORM = "GNU complier for IBM RS-6000"; - // clock_t is a signed long: see NEED TO CHECK - long const CLOCK_T_MIN = LONG_MIN; - double const CLOCK_T_RANGE = - static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); +// IBM RS-6000, byte order is big-endian +string const PLATFORM = "GNU complier for IBM RS-6000"; +// clock_t is a signed long: see NEED TO CHECK +long const CLOCK_T_MIN = LONG_MIN; +double const CLOCK_T_RANGE = + static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); #elif defined ultrasparc - // Sun UltraSparc, byte order is big-endian - string const PLATFORM = "GNU compiler for Sun UltraSPARC"; - // clock_t is a signed long: see - long const CLOCK_T_MIN = LONG_MIN; - double const CLOCK_T_RANGE = - static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); +// Sun UltraSparc, byte order is big-endian +string const PLATFORM = "GNU compiler for Sun UltraSPARC"; +// clock_t is a signed long: see +long const CLOCK_T_MIN = LONG_MIN; +double const CLOCK_T_RANGE = + static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); #else - // Something else - string const PLATFORM = "GNU compiler for unknown CPU"; - // clock_t is a signed long: NEED TO CHECK - long const CLOCK_T_MIN = LONG_MIN; - double const CLOCK_T_RANGE = - static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); +// Something else +string const PLATFORM = "GNU compiler for unknown CPU"; +// clock_t is a signed long: NEED TO CHECK +long const CLOCK_T_MIN = LONG_MIN; +double const CLOCK_T_RANGE = + static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); #endif #endif @@ -296,18 +298,18 @@ using std::ostream; #error "CPU not defined" #else #ifdef x86 - // Intel x86, byte order is little-endian - string const PLATFORM = "Clang compiler for Intel x86"; - // clock_t is an unsigned long: see - unsigned long const CLOCK_T_MIN = 0; - double const CLOCK_T_RANGE = static_cast(ULONG_MAX); +// Intel x86, byte order is little-endian +string const PLATFORM = "Clang compiler for Intel x86"; +// clock_t is an unsigned long: see +unsigned long const CLOCK_T_MIN = 0; +double const CLOCK_T_RANGE = static_cast(ULONG_MAX); #else - // Something else - string const PLATFORM = "Clang compiler for unknown CPU"; - // clock_t is a signed long: NEED TO CHECK - long const CLOCK_T_MIN = LONG_MIN; - double const CLOCK_T_RANGE = - static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); +// Something else +string const PLATFORM = "Clang compiler for unknown CPU"; +// clock_t is a signed long: NEED TO CHECK +long const CLOCK_T_MIN = LONG_MIN; +double const CLOCK_T_RANGE = + static_cast(LONG_MAX) - static_cast(CLOCK_T_MIN); #endif #endif @@ -318,15 +320,15 @@ using std::ostream; #define WEXITSTATUS(x) ((x) & 0xff) #elif defined __HP_aCC - // HP-UX aCC, byte order is big-endian, can be either 32-bit or 64-bit - string const PLATFORM = "HP-UX aC++"; - // clock_t is an unsigned long: see - unsigned long const CLOCK_T_MIN = 0; +// HP-UX aCC, byte order is big-endian, can be either 32-bit or 64-bit +string const PLATFORM = "HP-UX aC++"; +// clock_t is an unsigned long: see +unsigned long const CLOCK_T_MIN = 0; #ifdef __ia64 - // However, clock_t is a 32-bit unsigned long and we are using 64-bit unsigned longs here - double const CLOCK_T_RANGE = 4294967295UL; // crude, improve +// However, clock_t is a 32-bit unsigned long and we are using 64-bit unsigned longs here +double const CLOCK_T_RANGE = 4294967295UL; // crude, improve #else - double const CLOCK_T_RANGE = static_cast(ULONG_MAX); +double const CLOCK_T_RANGE = static_cast(ULONG_MAX); #endif #endif @@ -350,7 +352,7 @@ using std::ostream; char const COLON = ':'; char const COMMA = ','; char const DASH = '-'; -char const PATH_SEPARATOR = '/'; // Works for Windows too! +char const PATH_SEPARATOR = '/'; // Works for Windows too! char const QUOTE1 = ';'; char const QUOTE2 = '#'; char const SLASH = '/'; @@ -359,35 +361,35 @@ char const TILDE = '~'; // TESTING options bool const ACCEPT_TRUNCATED_PROFILES = true; -bool const CREATE_SHADOW_ZONE_IF_HITS_GRID_EDGE = true; // If shadow line tracing hits grid edge, create shadow zone? -bool const SAVE_CSHORE_OUTPUT = true; // #ifdef CSHORE_FILE_INOUT || CSHORE_BOTH, append all CShore output files to a whole-run master -bool const USE_DEEP_WATER_FOR_SHADOW_LINE = true; // Use deep water wave orientation in determining shadow line orientation? +bool const CREATE_SHADOW_ZONE_IF_HITS_GRID_EDGE = true; // If shadow line tracing hits grid edge, create shadow zone? +bool const SAVE_CSHORE_OUTPUT = true; // #ifdef CSHORE_FILE_INOUT || CSHORE_BOTH, append all CShore output files to a whole-run master +bool const USE_DEEP_WATER_FOR_SHADOW_LINE = true; // Use deep water wave orientation in determining shadow line orientation? // Not likely that user will need to change these -int const NUMBER_OF_RNGS = 2; // Number of random number generators -int const SAVEMAX = 100000; // Maximum number of saves of spatial output -int const BUF_SIZE = 2048; // Max length (inc. terminating NULL) of any C-type string -int const CAPE_POINT_MIN_SPACING = 10; // In cells: for shadow zone stuff, cape points must not be closer than this -int const CLOCK_CHECK_ITERATION = 5000; // If have done this many timesteps then reset the CPU time running total -int const COAST_LENGTH_MAX = 10; // For safety check when tracing coast -int const COAST_LENGTH_MIN_X_PROF_SPACE = 20; // Ignore very short coasts less than this x profile spacing +int const NUMBER_OF_RNGS = 2; // Number of random number generators +int const SAVEMAX = 100000; // Maximum number of saves of spatial output +int const BUF_SIZE = 2048; // Max length (inc. terminating NULL) of any C-type string +int const CAPE_POINT_MIN_SPACING = 10; // In cells: for shadow zone stuff, cape points must not be closer than this +int const CLOCK_CHECK_ITERATION = 5000; // If have done this many timesteps then reset the CPU time running total +int const COAST_LENGTH_MAX = 10; // For safety check when tracing coast +int const COAST_LENGTH_MIN_X_PROF_SPACE = 20; // Ignore very short coasts less than this x profile spacing //! The size of the arrays output by CShore. If this is changed, then must also set the same value on line 12 of cshore_wrapper.f03 (integer, parameter :: NN = 1000, NL = 1) and recompile CShore. Eventually we should move to dynamically allocated arrays TODO 070 int const CSHOREARRAYOUTSIZE = 1000; -int const FLOOD_FILL_START_OFFSET = 2; // In cells: cell-by-cell fill starts this distance inside polygon -int const GRID_MARGIN = 10; // Ignore this many along-coast grid-edge points re. shadow zone calcs -int const INT_NODATA = -9999; // CME's internal NODATA value for ints -int const MAX_CLIFF_TALUS_LENGTH = 100; // In cells: maximum length of the Dean profile for cliff collapse talus -int const MAX_SEAWARD_OFFSET_FOR_CLIFF_TALUS = 30; // In cells: maximum distance that the Dean profile for cliff collapse talus can be offset from the coast -int const MAX_LEN_SHADOW_LINE_TO_IGNORE = 200; // In cells: if can't find cell-by-cell fill start point, continue if short shadow line -int const MAX_NUM_PREV_ORIENTATION_VALUES = 10; // Max length of deque used in tracing shadow boundary -int const MAX_NUM_SHADOW_ZONES = 10; // Consider at most this number of shadow zones -int const MIN_INLAND_OFFSET_UNCONS_EROSION = 5; // Used in estimation of beach erosion -int const MIN_PARALLEL_PROFILE_SIZE = 3; // In cells: min size for valid unconsolidated sediment parallel profile -int const MIN_PROFILE_SIZE = 3; // In cells: min size for valid unconsolidated sediment profile -int const DEFAULT_PROFILE_SPACING = 15; // In cells: profile creation does not work well if profiles are too closely spaced -int const SAVGOL_POLYNOMIAL_MAX_ORDER = 6; // Maximum order of Savitzky-Golay smoothing polynomial +int const FLOOD_FILL_START_OFFSET = 2; // In cells: cell-by-cell fill starts this distance inside polygon +int const GRID_MARGIN = 10; // Ignore this many along-coast grid-edge points re. shadow zone calcs +int const INT_NODATA = -9999; // CME's internal NODATA value for ints +int const MAX_CLIFF_TALUS_LENGTH = 100; // In cells: maximum length of the Dean profile for cliff collapse talus +int const MAX_SEAWARD_OFFSET_FOR_CLIFF_TALUS = 30; // In cells: maximum distance that the Dean profile for cliff collapse talus can be offset from the coast +int const MAX_LEN_SHADOW_LINE_TO_IGNORE = 200; // In cells: if can't find cell-by-cell fill start point, continue if short shadow line +int const MAX_NUM_PREV_ORIENTATION_VALUES = 10; // Max length of deque used in tracing shadow boundary +int const MAX_NUM_SHADOW_ZONES = 10; // Consider at most this number of shadow zones +int const MIN_INLAND_OFFSET_UNCONS_EROSION = 5; // Used in estimation of beach erosion +int const MIN_PARALLEL_PROFILE_SIZE = 3; // In cells: min size for valid unconsolidated sediment parallel profile +int const MIN_PROFILE_SIZE = 3; // In cells: min size for valid unconsolidated sediment profile +int const DEFAULT_PROFILE_SPACING = 15; // In cells: profile creation does not work well if profiles are too closely spaced +int const SAVGOL_POLYNOMIAL_MAX_ORDER = 6; // Maximum order of Savitzky-Golay smoothing polynomial // Log file detail level int const NO_LOG_FILE = 0; @@ -407,8 +409,8 @@ int const SOUTH_WEST = 6; int const WEST = 7; int const NORTH_WEST = 8; -int const DIRECTION_DOWNCOAST = 0; // Down-coast, i.e. along the coast so that the index of coastline points INCREASES -int const DIRECTION_UPCOAST = 1; // Up-coast, i.e. along the coast so that the index of coastline points DECREASES +int const DIRECTION_DOWNCOAST = 0; // Down-coast, i.e. along the coast so that the index of coastline points INCREASES +int const DIRECTION_UPCOAST = 1; // Up-coast, i.e. along the coast so that the index of coastline points DECREASES // Handedness codes, these show which side the sea is on when travelling down-coast (i.e. in the direction in which coastline point numbers INCREASE) int const NULL_HANDED = -1; @@ -444,8 +446,8 @@ int const LF_CAT_SEDIMENT_INPUT_SUBMERGED = 16; // TODO 091 These shoul int const LF_CAT_SEDIMENT_INPUT_NOT_SUBMERGED = 17; // TODO 091 These should not be LF categories // Landform category codes for cells and coast landform objects -int const LF_CAT_CLIFF = 3; // Raster output of LF_CAT_CLIFF shows LF_CAT_CLIFF subcategories, rather than just LF_CAT_CLIFF -int const LF_CAT_DRIFT = 4; // Raster output of LF_CAT_DRIFT shows LF_CAT_DRIFT subcategories, rather than just LF_CAT_DRIFT +int const LF_CAT_CLIFF = 3; // Raster output of LF_CAT_CLIFF shows LF_CAT_CLIFF subcategories, rather than just LF_CAT_CLIFF +int const LF_CAT_DRIFT = 4; // Raster output of LF_CAT_DRIFT shows LF_CAT_DRIFT subcategories, rather than just LF_CAT_DRIFT int const LF_CAT_INTERVENTION = 5; // Landform sub-category codes for cells, LF_CAT_CLIFF @@ -707,9 +709,9 @@ int const WAVE_MODEL_CSHORE = 1; int const UNCONS_SEDIMENT_EQUATION_CERC = 0; int const UNCONS_SEDIMENT_EQUATION_KAMPHUIS = 1; -int const CLIFF_COLLAPSE_LENGTH_INCREMENT = 10; // Increment the planview length of the cliff talus Dean profile, if we have not been able to deposit enough -int const PROFILE_CHECK_DIST_FROM_COAST = 3; // Used in checking shoreline-normal profiles for intersection -int const GAP_BETWEEN_DIFFERENT_COAST_PROFILES = 30; // In cells, is the gap between profile ends belonging to different coasts +int const CLIFF_COLLAPSE_LENGTH_INCREMENT = 10; // Increment the planview length of the cliff talus Dean profile, if we have not been able to deposit enough +int const PROFILE_CHECK_DIST_FROM_COAST = 3; // Used in checking shoreline-normal profiles for intersection +int const GAP_BETWEEN_DIFFERENT_COAST_PROFILES = 30; // In cells, is the gap between profile ends belonging to different coasts unsigned long const MASK = 0xfffffffful; unsigned long const SEDIMENT_INPUT_EVENT_ERROR = -1; @@ -717,37 +719,38 @@ unsigned long const UNSIGNED_LONG_NODATA = 9999; double const PI = 3.141592653589793238462643; -double const D50_FINE_DEFAULT = 0.0625; // In mm -double const D50_SAND_DEFAULT = 0.42; // In mm -double const D50_COARSE_DEFAULT = 19.0; // In mm +double const D50_FINE_DEFAULT = 0.0625; // In mm +double const D50_SAND_DEFAULT = 0.42; // In mm +double const D50_COARSE_DEFAULT = 19.0; // In mm -double const BEACH_PROTECTION_HB_RATIO = 0.23; // The beach protection factor is this times breaking depth -double const WALKDEN_HALL_PARAM_1 = 3.25; // First parameter in Equation 4 from Walkden & Hall, 2005 -double const WALKDEN_HALL_PARAM_2 = 1.50; // Second parameter in Equation 4 from Walkden & Hall, 2005 +double const BEACH_PROTECTION_HB_RATIO = 0.23; // The beach protection factor is this times breaking depth +double const WALKDEN_HALL_PARAM_1 = 3.25; // First parameter in Equation 4 from Walkden & Hall, 2005 +double const WALKDEN_HALL_PARAM_2 = 1.50; // Second parameter in Equation 4 from Walkden & Hall, 2005 -double const DEPTH_OVER_DB_INCREMENT = 0.001; // Depth over DB increment for erosion potential look-up function -double const INVERSE_DEPTH_OVER_DB_INCREMENT = 1000; // Inverse of the above -double const DEAN_POWER = 2.0 / 3.0; // Dean profile exponent +double const DEPTH_OVER_DB_INCREMENT = 0.001; // Depth over DB increment for erosion potential look-up function +double const INVERSE_DEPTH_OVER_DB_INCREMENT = 1000; // Inverse of the above +double const DEAN_POWER = 2.0 / 3.0; // Dean profile exponent // TODO 011 Let the user define these CShore input parameters -double const CSHORE_FRICTION_FACTOR = 0.015; // Friction factor for CShore model -double const CSHORE_SURGE_LEVEL = 0.0; // TODO 007 +double const CSHORE_FRICTION_FACTOR = 0.015; // Friction factor for CShore model +double const CSHORE_SURGE_LEVEL = 0.0; // TODO 007 -double const TOLERANCE = 1e-7; // For bFPIsEqual, if too small (e.g. 1e-10), get spurious "rounding" errors -double const SEDIMENT_ELEV_TOLERANCE = 1e-10; // For bFPIsEqual, used to compare depth-equivalent sediment amounts -double const MASS_BALANCE_TOLERANCE = 1e-5; // For bFPIsEqual, used to compare for mass balance checks +double const TOLERANCE = 1e-7; // For bFPIsEqual, if too small (e.g. 1e-10), get spurious "rounding" errors +double const SEDIMENT_ELEV_TOLERANCE = 1e-10; // For bFPIsEqual, used to compare depth-equivalent sediment amounts +double const MASS_BALANCE_TOLERANCE = 1e-5; // For bFPIsEqual, used to compare for mass balance checks double const STRAIGHT_COAST_MAX_DETAILED_CURVATURE = -5; double const STRAIGHT_COAST_MAX_SMOOTH_CURVATURE = -1; -double const MIN_LENGTH_OF_SHADOW_ZONE_LINE = 10; // Used in shadow line tracing -double const MAX_LAND_LENGTH_OF_SHADOW_ZONE_LINE = 5; // Used in shadow line tracing -double const CLIFF_COLLAPSE_HEIGHT_INCREMENT = 0.1; // Increment the fractional height of the cliff talus Dean profile, if we have not been able to deposit enough -double const INTERVENTION_PROFILE_SPACING_FACTOR = 0.5; // Profile spacing on interventions works better if it is smaller than profile spacing on coastline +double const MIN_LENGTH_OF_SHADOW_ZONE_LINE = 10; // Used in shadow line tracing +double const MAX_LAND_LENGTH_OF_SHADOW_ZONE_LINE = 5; // Used in shadow line tracing +double const CLIFF_COLLAPSE_HEIGHT_INCREMENT = 0.1; // Increment the fractional height of the cliff talus Dean profile, if we have not been able to deposit enough +double const INTERVENTION_PROFILE_SPACING_FACTOR = 0.5; // Profile spacing on interventions works better if it is smaller than profile spacing on coastline double const DBL_NODATA = -9999; string const PROGRAM_NAME = "Coastal Modelling Environment (CoastalME) version 1.4.0 (28 Aug 2025)"; string const PROGRAM_NAME_SHORT = "CME"; string const CME_INI = "cme.ini"; +string const CME_YAML = "cme.yaml"; string const COPYRIGHT = "(C) 2025 Andres Payo and David Favis-Mortlock"; string const LINE = "-------------------------------------------------------------------------------"; @@ -882,7 +885,7 @@ string const RASTER_CLIFF_COLLAPSE_TIMESTEP_CODE = "cliff_collapse_timestep"; string const RASTER_CLIFF_COLLAPSE_TIMESTEP_NAME = "cliff_collapse_timestep_all"; string const RASTER_CLIFF_NOTCH_ALL_CODE = "cliff_notch_all"; string const RASTER_CLIFF_NOTCH_ALL_NAME = "cliff_notch_all"; -string const RASTER_CLIFF_TOE_NAME = "cliff_toe"; // Note no code for this, because is chosen by m_bCliffToeLocate in input file +string const RASTER_CLIFF_TOE_NAME = "cliff_toe"; // Note no code for this, because is chosen by m_bCliffToeLocate in input file string const RASTER_COARSE_CONS_CODE = "cons_sed_coarse"; string const RASTER_COARSE_CONS_NAME = "cons_sed_coarse"; string const RASTER_COARSE_UNCONS_CODE = "uncons_sed_coarse"; @@ -942,7 +945,7 @@ string const RASTER_SHADOW_ZONE_CODE = "shadow_zones"; string const RASTER_SHADOW_ZONE_NAME = "shadow_zones"; string const RASTER_SLICE_CODE = "slice"; string const RASTER_SLICE_NAME = "slice"; -string const RASTER_SLOPE_FOR_CLIFF_TOE_NAME = "toe_slope"; // Note no code for this, because is chosen by m_bCliffToeLocate in input file +string const RASTER_SLOPE_FOR_CLIFF_TOE_NAME = "toe_slope"; // Note no code for this, because is chosen by m_bCliffToeLocate in input file string const RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_CODE = "cons_sediment_slope"; string const RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_NAME = "cons_sediment_slope"; string const RASTER_SUSP_SED_CODE = "susp_sed"; @@ -1163,32 +1166,32 @@ string const WAVE_HEIGHT_Y_FILENAME = "wave_height_y.csv"; string const ACTIVE_ZONE_FILENAME = "activezone.csv"; //================================================ Globally-available functions ================================================= -template +template T tMax(T a, T b) { return ((a > b) ? a : b); } -template +template T tMax(T a, T b, T c) { T max = (a < b) ? b : a; return ((max < c) ? c : max); } -template +template T tMin(T a, T b) { return ((a < b) ? a : b); } -template +template T tMin(T a, T b, T c) { return (a < b ? (a < c ? a : c) : (b < c ? b : c)); } -template +template T tAbs(T a) { // From a posting dated 18 Nov 93 by rmartin@rcmcon.com (Robert Martin), archived in cpp_tips @@ -1202,8 +1205,8 @@ T tAbs(T a) // return ((a >= b) && (a <= c)); // } -template -string strDblToStr(const T &t) +template +string strDblToStr(T const &t) { // From http://stackoverflow.com/questions/2125880/convert-float-to-stdstring-in-c ostringstream os; @@ -1214,8 +1217,8 @@ string strDblToStr(const T &t) // ============================================================================================================================== // For comparison of two floating-point numbers, with a specified accuracy // ============================================================================================================================== -template -bool bFPIsEqual(const T d1, const T d2, const T dEpsilon) +template +bool bFPIsEqual(T const d1, T const d2, T const dEpsilon) { // Since the accuracy of floating-point numbers varies with their magnitude, we must compare them by using an accuracy threshold which is relative to the magnitude of the two numbers being compared. This is a blend of an example from Knuth's 'The Art of Computer Programming. Volume 1. Fundamental Algorithms' and a posting dated 18 Nov 93 by rmartin@rcmcon.com (Robert Martin), archived in cpp_tips @@ -1242,7 +1245,9 @@ bool bIsStringValidInt(string &); struct FillToWidth { - FillToWidth(char f, int w) : chFill(f), nWidth(w) {} + FillToWidth(char f, int w) : chFill(f), nWidth(w) + { + } char chFill; int nWidth; }; @@ -1260,17 +1265,17 @@ struct TransectWaveData bool bIsGridEdge; }; -ostream &operator<<(ostream &, const FillToWidth &); +ostream &operator<<(ostream &, FillToWidth const &); // string strDbl(double const, int const); string strDblRight(double const, int const, int const, bool const = true); string strIntRight(int const, int const); -string strCentre(const char *, int const); -string strCentre(const string &, int const); -string strRight(const string &, int const); -string strRight(const char *, int const); -string strLeft(const string &, int const); -string strLeft(const char *, int const); +string strCentre(char const *, int const); +string strCentre(string const &, int const); +string strRight(string const &, int const); +string strRight(char const *, int const); +string strLeft(string const &, int const); +string strLeft(char const *, int const); string strRightPerCent(double const, double const, int const, int const, bool const = true); #endif @@ -1278,4 +1283,4 @@ string strRightPerCent(double const, double const, int const, int const, //================================================= debugging stuff ============================================================= // #define CLOCKCHECK // Uncomment to check CPU clock rollover settings -#endif // CME_H +#endif // CME_H diff --git a/src/configuration.cpp b/src/configuration.cpp index 219f5c76c..fe452230d 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -106,6 +106,7 @@ void CConfiguration::InitializeDefaults() // m_dFinalWaterLevel = 0.0; m_bHasFinalWaterLevel = false; + m_strWaveInputMode = "fixed"; m_strWaveHeightTimeSeries = ""; m_strWaveStationDataFile = ""; m_dDeepWaterWaveHeight = 1.0; diff --git a/src/configuration.h b/src/configuration.h index e63ad2fed..0823eb6b5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -100,6 +100,7 @@ class CConfiguration bool m_bHasFinalWaterLevel; // Waves + string m_strWaveInputMode; string m_strWaveHeightTimeSeries; string m_strWaveStationDataFile; double m_dDeepWaterWaveHeight; @@ -110,6 +111,9 @@ class CConfiguration string m_strTideDataFile; double m_dBreakingWaveRatio; + // Sea flood fill seed points + string m_strSeaFloodSeedPointShapefile; + // Sediment and Erosion bool m_bCoastPlatformErosion; double m_dPlatformErosionResistance; @@ -354,6 +358,10 @@ class CConfiguration } // Wave height Data + void SetWaveInputMode(string const &str) + { + m_strWaveInputMode = str; + } void SetWaveHeightTimeSeries(string const &str) { m_strWaveHeightTimeSeries = str; @@ -384,6 +392,11 @@ class CConfiguration m_dBreakingWaveRatio = d; } + void SetSeaFloodSeedPointShapefile(string const &str) + { + m_strSeaFloodSeedPointShapefile = str; + } + // Additional setters for comprehensive YAML support void SetCoastPlatformErosion(bool b) { @@ -766,6 +779,10 @@ class CConfiguration } // Wave data configuration getters (Cases 37-40) + string GetWaveInputMode() const + { + return m_strWaveInputMode; + } string GetWaveHeightTimeSeries() const { return m_strWaveHeightTimeSeries; @@ -796,6 +813,11 @@ class CConfiguration return m_dBreakingWaveRatio; } + string GetSeaFloodSeedPointShapefile() const + { + return m_strSeaFloodSeedPointShapefile; + } + // Sediment and Erosion parameters bool GetCoastPlatformErosion() const { diff --git a/src/create_polygons.cpp b/src/create_polygons.cpp index 1dcb4cca4..5fa4a66fc 100644 --- a/src/create_polygons.cpp +++ b/src/create_polygons.cpp @@ -233,7 +233,7 @@ int CSimulation::nCreateAllPolygons(void) for (int i = nCoastPoint; i <= nNextProfileCoastPoint; i++) { CGeom2DIPoint const PtiToMark = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(i); - m_pRasterGrid->m_Cell[PtiToMark.nGetX()][PtiToMark.nGetY()].SetCoastAndPolygonID(nCoast, nPolygon); + m_pRasterGrid->Cell(PtiToMark.nGetX(), PtiToMark.nGetY()).SetCoastAndPolygonID(nCoast, nPolygon); } // Do the down-coast normal profile (does the whole length, including any shared line segments. So some cells are marked twice, however this is not a problem) @@ -242,7 +242,7 @@ int CSimulation::nCreateAllPolygons(void) for (int i = 0; i < nCellsInProfile; i++) { CGeom2DIPoint const PtiToMark = *pNextProfile->pPtiGetCellInProfile(i); - m_pRasterGrid->m_Cell[PtiToMark.nGetX()][PtiToMark.nGetY()].SetCoastAndPolygonID(nCoast, nPolygon); + m_pRasterGrid->Cell(PtiToMark.nGetX(), PtiToMark.nGetY()).SetCoastAndPolygonID(nCoast, nPolygon); } // Do this normal profile (again does the whole length, including any shared line segments) @@ -251,7 +251,7 @@ int CSimulation::nCreateAllPolygons(void) for (int i = 0; i < nCellsInProfile; i++) { CGeom2DIPoint const PtiToMark = *pThisProfile->pPtiGetCellInProfile(i); - m_pRasterGrid->m_Cell[PtiToMark.nGetX()][PtiToMark.nGetY()].SetCoastAndPolygonID(nCoast, nPolygon); + m_pRasterGrid->Cell(PtiToMark.nGetX(), PtiToMark.nGetY()).SetCoastAndPolygonID(nCoast, nPolygon); } // If the polygon doesn't meet at a point at its seaward end, also need to rasterize the 'joining line' @@ -320,7 +320,7 @@ void CSimulation::RasterizePolygonJoiningLine(int nCoast, CGeom2DIPoint const* p // assert(nPoly < m_VCoast[0].nGetNumPolygons()); // Mark this point on the raster grid - m_pRasterGrid->m_Cell[nX][nY].SetCoastAndPolygonID(nCoast, nPoly); + m_pRasterGrid->Cell(nX, nY).SetCoastAndPolygonID(nCoast, nPoly); // And increment for next time dX += dXInc; @@ -489,7 +489,7 @@ void CSimulation::MarkPolygonCells(void) int nX = Pti.nGetX(); int const nY = Pti.nGetY(); - while ((nX >= 0) && (m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID() == INT_NODATA)) + while ((nX >= 0) && (m_pRasterGrid->Cell(nX, nY).nGetPolygonID() == INT_NODATA)) nX--; nX++; @@ -497,67 +497,67 @@ void CSimulation::MarkPolygonCells(void) bool bSpanAbove = false; bool bSpanBelow = false; - while ((nX < m_nXGridSize) && (m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID() == INT_NODATA)) + while ((nX < m_nXGridSize) && (m_pRasterGrid->Cell(nX, nY).nGetPolygonID() == INT_NODATA)) { // assert(nPolyID < m_VCoast[nCoast].nGetNumPolygons()); // Mark the cell as being in this polygon and this coast - m_pRasterGrid->m_Cell[nX][nY].SetCoastAndPolygonID(nCoast, nPolyID); + m_pRasterGrid->Cell(nX, nY).SetCoastAndPolygonID(nCoast, nPolyID); // LogStream << "Marked [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; // Increment the running totals for this polygon - // dCliffCollapseErosionFine += m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionFine(); - // dCliffCollapseErosionSand += m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionSand(); - // dCliffCollapseErosionCoarse += m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionCoarse(); - // dCliffCollapseTalusSand += m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseSandTalusDeposition(); - // dCliffCollapseTalusCoarse += m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseCoarseTalusDeposition(); + // dCliffCollapseErosionFine += m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionFine(); + // dCliffCollapseErosionSand += m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionSand(); + // dCliffCollapseErosionCoarse += m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionCoarse(); + // dCliffCollapseTalusSand += m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseSandTalusDeposition(); + // dCliffCollapseTalusCoarse += m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseCoarseTalusDeposition(); // Get the number of the highest layer with non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if ((nThisLayer != NO_NONZERO_THICKNESS_LAYERS) && (nThisLayer != INT_NODATA)) { // Increment some more running totals for this polygon TODO 066 should this be for ALL layers above the basement? - dStoredUnconsFine += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); - dStoredUnconsSand += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - dStoredUnconsCoarse += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + dStoredUnconsFine += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); + dStoredUnconsSand += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + dStoredUnconsCoarse += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - dStoredConsFine += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetFineDepth(); - dStoredConsSand += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetSandDepth(); - dStoredConsCoarse += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); + dStoredConsFine += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetFineDepth(); + dStoredConsSand += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetSandDepth(); + dStoredConsCoarse += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); // Add to the start-iteration total of suspended fine sediment within polygons - m_dStartIterSuspFineInPolygons += m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); + m_dStartIterSuspFineInPolygons += m_pRasterGrid->Cell(nX, nY).dGetSuspendedSediment(); // Add to the total of sediment derived from sediment input events - dSedimentInputFine += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineSedimentInputDepth(); - dSedimentInputSand += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandSedimentInputDepth(); - dSedimentInputCoarse += m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseSedimentInputDepth(); + dSedimentInputFine += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineSedimentInputDepth(); + dSedimentInputSand += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandSedimentInputDepth(); + dSedimentInputCoarse += m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseSedimentInputDepth(); } nCellsInPolygon++; - dTotDepth += m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); + dTotDepth += m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(); - if ((! bSpanAbove) && (nY > 0) && (m_pRasterGrid->m_Cell[nX][nY - 1].nGetPolygonID() == INT_NODATA)) + if ((! bSpanAbove) && (nY > 0) && (m_pRasterGrid->Cell(nX, nY - 1).nGetPolygonID() == INT_NODATA)) { PtiStack.push(CGeom2DIPoint(nX, nY - 1)); bSpanAbove = true; } - else if (bSpanAbove && (nY > 0) && (m_pRasterGrid->m_Cell[nX][nY - 1].nGetPolygonID() != INT_NODATA)) + else if (bSpanAbove && (nY > 0) && (m_pRasterGrid->Cell(nX, nY - 1).nGetPolygonID() != INT_NODATA)) { bSpanAbove = false; } - if ((! bSpanBelow) && (nY < m_nYGridSize - 1) && (m_pRasterGrid->m_Cell[nX][nY + 1].nGetPolygonID() == INT_NODATA)) + if ((! bSpanBelow) && (nY < m_nYGridSize - 1) && (m_pRasterGrid->Cell(nX, nY + 1).nGetPolygonID() == INT_NODATA)) { PtiStack.push(CGeom2DIPoint(nX, nY + 1)); bSpanBelow = true; } - else if (bSpanBelow && (nY < m_nYGridSize - 1) && (m_pRasterGrid->m_Cell[nX][nY + 1].nGetPolygonID() != INT_NODATA)) + else if (bSpanBelow && (nY < m_nYGridSize - 1) && (m_pRasterGrid->Cell(nX, nY + 1).nGetPolygonID() != INT_NODATA)) { bSpanBelow = false; } @@ -613,7 +613,7 @@ void CSimulation::MarkPolygonCells(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // int nID = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); + // int nID = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); // if (nID == INT_NODATA) // nNotInPoly++; // else diff --git a/src/create_profiles.cpp b/src/create_profiles.cpp index 87d93abb3..31d32e8e2 100644 --- a/src/create_profiles.cpp +++ b/src/create_profiles.cpp @@ -1,2289 +1,2289 @@ -/*! - - \file create_profiles.cpp - \brief Creates profiles which are approximately normal to the coastline, these will become inter-polygon boundaries - \details TODO 001 A more detailed description of these routines. - \author David Favis-Mortlock - \author Andres Payo - \date 2025 - \copyright GNU General Public License - -*/ - -/* ============================================================================================================================== - - This file is part of CoastalME, the Coastal Modelling Environment. - - CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -==============================================================================================================================*/ -#include - -#include -#include -#include - -#include -using std::cerr; -using std::endl; -using std::ios; - -#include -using std::find; -using std::sort; - -#include -using std::make_pair; -using std::pair; - -#include -using std::normal_distribution; - -#include "cme.h" -#include "simulation.h" -#include "coast.h" -#include "2d_point.h" -#include "2di_point.h" - -namespace -{ -//=============================================================================================================================== -//! Function used to sort coastline curvature values when locating start points of normal profiles -//=============================================================================================================================== -bool bCurvaturePairCompareDescending(const pair& prLeft, const pair& prRight) -{ - // Sort in descending order (i.e. most concave first) - return prLeft.second > prRight.second; -} -} // namespace - -//=============================================================================================================================== -//! Create coastline-normal profiles for all coastlines. The first profiles are created 'around' the most concave bits of coast. Also create 'special' profiles at the start and end of the coast, and put these onto the raster grid -//=============================================================================================================================== -int CSimulation::nCreateAllProfiles(void) -{ - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << endl << m_ulIter << ": Creating profiles" << endl; - - for (unsigned int nCoast = 0; nCoast < m_VCoast.size(); nCoast++) - { - int nProfile = 0; - int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); - - // Create a bool vector to mark coast points which have been searched - vector bVCoastPointDone(nCoastSize, false); - - // Now create a vector of pairs: the first value of the pair is the coastline point, the second is the coastline's curvature at that point - vector> prVCurvature; - - for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) - { - double dCurvature; - - if (m_VCoast[nCoast].pGetCoastLandform(nCoastPoint)->nGetLandFormCategory() != LF_CAT_INTERVENTION) - { - // Not an intervention coast point, so store the smoothed curvature - dCurvature = m_VCoast[nCoast].dGetSmoothCurvature(nCoastPoint); - } - - else - { - // This is an intervention coast point, which is likely to have some sharp angles. So store the detailed curvature - dCurvature = m_VCoast[nCoast].dGetDetailedCurvature(nCoastPoint); - } - - prVCurvature.push_back(make_pair(nCoastPoint, dCurvature)); - } - - // Sort this pair vector in descending order, so that the most convex curvature points are first - sort(prVCurvature.begin(), prVCurvature.end(), bCurvaturePairCompareDescending); - - // // DEBUG CODE ======================================================================================================================= - // for (int n = 0; n < prVCurvature.size(); n++) - // { - // LogStream << prVCurvature[n].first << "\t" << prVCurvature[n].second << endl; - // } - // LogStream << endl << endl; - // // DEBUG CODE ======================================================================================================================= - - // And mark points at and near the start and end of the coastline so that they don't get searched (will be creating 'special' start- and end-of-coast profiles at these end points later) - for (int n = 0; n < m_nCoastNormalSpacing; n++) - { - if (n < nCoastSize) - bVCoastPointDone[n] = true; - - int const m = nCoastSize - n - 1; - - if (m >= 0) - bVCoastPointDone[m] = true; - } - - // Now locate the start points for all coastline-normal profiles (except the grid-edge ones), at points of maximum convexity. Then create the profiles - LocateAndCreateProfiles(nCoast, nProfile, &bVCoastPointDone, &prVCurvature); - - // Did we fail to create any normal profiles? If so, quit - if (nProfile < 0) - { - string strErr = ERR + "timestep " + strDblToStr(m_ulIter) + ": could not create profiles for coastline " + strDblToStr(nCoast); - - if (m_ulIter == 1) - strErr += ". Check the SWL"; - - strErr += "\n"; - - cerr << strErr; - LogStream << strErr; - - return RTN_ERR_NO_PROFILES_1; - } - - // Locate and create a 'special' profile at the grid edge, first at the beginning of the coastline. Then put this onto the raster grid - int nRet = nLocateAndCreateGridEdgeProfile(true, nCoast, nProfile); - - if (nRet != RTN_OK) - return nRet; - - // Locate a second 'special' profile at the grid edge, this time at end of the coastline. Then put this onto the raster grid - nRet = nLocateAndCreateGridEdgeProfile(false, nCoast, ++nProfile); - - if (nRet != RTN_OK) - return nRet; - - // Insert pointers to profiles at coastline points in the profile-all-coastpoint index - m_VCoast[nCoast].InsertProfilesInProfileCoastPointIndex(); - - // // DEBUG CODE =================================================================================================== - // LogStream << endl << "===========================================================================================" << endl; - // LogStream << "PROFILES BEFORE ADDING BEFORE- AND AFTER-PROFILE NUMBERS" << endl; - // int nNumProfiles = m_VCoast[nCoast].nGetNumProfiles(); - // for (int nn = 0; nn < nNumProfiles; nn++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nn); - // - // LogStream << nn << " nCoastID = " << pProfile->nGetProfileID() << " nGlobalID = " << pProfile->nGetProfileID() << " nGetCoastPoint = " << pProfile->nGetCoastPoint() << " pGetUpCoastAdjacentProfile = " << pProfile->pGetUpCoastAdjacentProfile() << " pGetDownCoastAdjacentProfile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; - // } - // LogStream << "===================================================================================================" << endl << endl; - // // DEBUG CODE =================================================================================================== - - CGeomProfile* pLastProfile; - CGeomProfile* pThisProfile; - - // Go along the coastline and give each profile the number of the adjacent up-coast profile and the adjacent down-coast profile - for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) - { - if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nCoastPoint)) - { - pThisProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nCoastPoint); - // nThisProfile = pThisProfile->nGetProfileID(); - - if (nCoastPoint == 0) - { - pThisProfile->SetUpCoastAdjacentProfile(NULL); - - // LogStream << "nCoastPoint = " << nCoastPoint << " ThisProfile = " << nThisProfile << " ThisProfile UpCoast = " << pThisProfile->pGetUpCoastAdjacentProfile() << " ThisProfile DownCoast = " << pThisProfile->pGetDownCoastAdjacentProfile() << endl; - - pLastProfile = pThisProfile; - // nLastProfile = nThisProfile; - continue; - } - - pLastProfile->SetDownCoastAdjacentProfile(pThisProfile); - pThisProfile->SetUpCoastAdjacentProfile(pLastProfile); - - if (nCoastPoint == nCoastSize - 1) - pThisProfile->SetDownCoastAdjacentProfile(NULL); - - pLastProfile = pThisProfile; - } - } - - // And create an index to this coast's profiles in along-coastline sequence - m_VCoast[nCoast].CreateProfileDownCoastIndex(); - - // // DEBUG CODE ======================================================================================================================= - // for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); - // CGeomProfile* pUpCoastProfile = pProfile->pGetUpCoastAdjacentProfile(); - // CGeomProfile* pDownCoastProfile = pProfile->pGetDownCoastAdjacentProfile(); - // int nUpCoastProfile = INT_NODATA; - // int nDownCoastProfile = INT_NODATA; - // if (pUpCoastProfile != 0) - // nUpCoastProfile = pUpCoastProfile->nGetProfileID(); - // if (pDownCoastProfile != 0) - // nDownCoastProfile = pDownCoastProfile->nGetProfileID(); - // LogStream << "nCoastID = " << pProfile->nGetProfileID() << "\t up-coast profile = " << nUpCoastProfile << "\t down-coast profile = " << nDownCoastProfile << endl; - // } - // LogStream << endl; - // // DEBUG CODE ======================================================================================================================= - - // // DEBUG CODE ======================================================================================================================= - // int nProf = 0; - // for (int n = 0; n < nCoastSize; n++) - // { - // // LogStream << n << "\t"; - // - // // LogStream << m_VCoast[nCoast].dGetDetailedCurvature(n) << "\t"; - // // - // // LogStream << m_VCoast[nCoast].dGetSmoothCurvature(n) << "\t"; - // // - // // if (m_VCoast[nCoast].pGetCoastLandform(n)->nGetLandFormCategory() == LF_CAT_INTERVENTION) - // // LogStream << "I\t"; - // - // if (m_VCoast[nCoast].bIsProfileAtCoastPoint(n)) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(n); - // - // LogStream << "profile " << pProfile->nGetProfileID() << " at coast point " << n << " adjacent up-coast profile = " << pProfile->pGetUpCoastAdjacentProfile() << " adjacent down-coast profile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; - // - // nProf++; - // } - // } - // LogStream << endl; - // LogStream << "nProf = " << nProf << endl; - // // DEBUG CODE ======================================================================================================================= - - // // DEBUG CODE ======================================================================================================================= - // LogStream << "=====================" << endl; - // for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); - // int nStartPoint = pProfile->nGetCoastPoint(); - // - // LogStream << n << "\t nCoastID = " << pProfile->nGetProfileID() << "\tnStartPoint = " << nStartPoint << endl; - // } - // LogStream << endl; - // LogStream << "=====================" << endl; - // // DEBUG CODE ======================================================================================================================= - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! For a single coastline, locate the start points for all coastline-normal profiles (except the grid-edge profiles). Then create the profiles -//=============================================================================================================================== -void CSimulation::LocateAndCreateProfiles(int const nCoast, int& nProfile, vector* pbVCoastPointDone, vector> const* prVCurvature) -{ - int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); - - // Work along the vector of curvature pairs starting at the convex end - for (int n = nCoastSize - 1; n >= 0; - n--) - { - // Have we searched all the coastline points? - int nStillToSearch = 0; - - for (int m = 0; m < nCoastSize; m++) - if (! pbVCoastPointDone->at(m)) - nStillToSearch++; - - if (nStillToSearch == 0) - // OK we are done here - return; - - // This convex point on the coastline is a potential location for a normal - int const nNormalPoint = prVCurvature->at(n).first; - - // Ignore each end of the coastline - if ((nNormalPoint == 0) || (nNormalPoint == nCoastSize - 1)) - continue; - - // TODO 089 When choosing locations for profiles, do coast first then interventions - - if (! pbVCoastPointDone->at(nNormalPoint)) - { - // We have not already searched this coast point. Is it an intervention coast point? - bool bIntervention = false; - - if (m_VCoast[nCoast].pGetCoastLandform(nNormalPoint)->nGetLandFormCategory() == LF_CAT_INTERVENTION) - { - // It is an intervention - bIntervention = true; - } - - CGeom2DIPoint const PtiThis = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nNormalPoint); - - // Create a profile here - int const nRet = nCreateProfile(nCoast, nCoastSize, nNormalPoint, nProfile, bIntervention, &PtiThis); - - // // DEBUG CODE ================= - // LogStream << "After nCreateProfile() ===========" << endl; - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); - // LogStream << pProfile->nGetProfileID() << "\t"; - // - // int nPointsInProfile = pProfile->nGetProfileSize(); - // - // for (int nPoint = 0; nPoint < nPointsInProfile; nPoint++) - // { - // CGeom2DPoint Pt = *pProfile->pPtGetPointInProfile(nPoint); - // LogStream << " {" << Pt.dGetX() << ", " << Pt.dGetY() << "}"; - // } - // LogStream << endl << "===========" << endl; - // // DEBUG CODE ================= - - // Mark this coast point as searched - pbVCoastPointDone->at(nNormalPoint) = true; - - if (nRet != RTN_OK) - { - // This potential profile is no good (has hit coast, or hit dry land, etc.) so forget about it - // LogStream << "Profile is no good" << endl; - continue; - } - - // // DEBUG CODE =================================================================================================== - // LogStream << endl << "===========================================================================================" << endl; - // LogStream << "PROFILES JUST AFTER CREATION" << endl; - // int nNumProfiles = m_VCoast[nCoast].nGetNumProfiles(); - // for (int nn = 0; nn < nNumProfiles; nn++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nn); - // - // LogStream << nn << " nCoastID = " << pProfile->nGetProfileID() << " nGlobalID = " << pProfile->nGetProfileID() << " nGetCoastPoint = " << pProfile->nGetCoastPoint() << " pGetUpCoastAdjacentProfile = " << pProfile->pGetUpCoastAdjacentProfile() << " pGetDownCoastAdjacentProfile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; - // } - // LogStream << "===================================================================================================" << endl << endl; - // // DEBUG CODE =================================================================================================== - // - // // DEBUG CODE =================================================================================================== - // LogStream << "++++++++++++++++++++++" << endl; - // LogStream << endl << "Just created profile " << nProfile << endl; - // int nProf = 0; - // for (int nnn = 0; nnn < nCoastSize; nnn++) - // { - // if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nnn)) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nnn); - // - // LogStream << "profile " << pProfile->nGetProfileID() << " at coast point " << nnn << " adjacent up-coast profile = " << pProfile->pGetUpCoastAdjacentProfile() << " adjacent down-coast profile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; - // - // nProf++; - // } - // } - // LogStream << endl; - // LogStream << "nProf = " << nProf << endl; - // LogStream << "++++++++++++++++++++++" << endl; - // // DEBUG CODE =================================================================================================== - - // CGeom2DPoint PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nNormalPoint); - // if (m_nLogFileDetail >= LOG_FILE_ALL) - // LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " created at coast point " << nNormalPoint << " [" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] = {" << PtThis.dGetX() << ", " << PtThis.dGetY() << "} (smoothed curvature = " << m_VCoast[nCoast].dGetSmoothCurvature(nNormalPoint) << ", detailed curvature = " << m_VCoast[nCoast].dGetDetailedCurvature(nNormalPoint) << ")" << endl; - - // // DEBUG CODE ================================================================================= - // if (m_pRasterGrid->m_Cell[PtiThis.nGetX()][PtiThis.nGetY()].bIsCoastline()) - // LogStream << m_ulIter << ": cell[" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] IS coastline, coast number = " << m_pRasterGrid->m_Cell[PtiThis.nGetX()][PtiThis.nGetY()].nGetCoastline() << endl; - // else - // LogStream << m_ulIter << ": ******* cell[" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] IS NOT coastline" << endl; - // // DEBUG CODE ================================================================================= - - // This profile is fine - nProfile++; - - // We need to mark points on either side of this profile so that we don't get profiles which are too close together. However best-placed profiles on narrow intervention structures may need to be quite closes - double dNumToMark = m_nCoastNormalSpacing; - - if (bIntervention) - dNumToMark = m_nCoastNormalInterventionSpacing; - - // If we have a random factor for profile spacing, then modify the profile spacing - if (m_dCoastNormalRandSpacingFactor > 0) - { - // Draw a sample from the unit normal distribution using random number generator 0 - double const dRand = m_dGetFromUnitNormalDist(m_Rand[0]); - - double const dTmp = dRand * m_dCoastNormalRandSpacingFactor * dNumToMark; - dNumToMark += dTmp; - - // Make sure number to mark is not too small or too big TODO 011 - if (bIntervention) - { - dNumToMark = tMin(dNumToMark, m_nCoastNormalInterventionSpacing * 0.75); - dNumToMark = tMax(dNumToMark, m_nCoastNormalInterventionSpacing * 1.25); - } - - else - { - dNumToMark = tMin(dNumToMark, m_nCoastNormalSpacing * 0.75); - dNumToMark = tMax(dNumToMark, m_nCoastNormalSpacing * 1.25); - } - - // TODO 014 Assume that the above is the profile spacing on straight bits of coast. Try gradually increasing the profile spacing with increasing concavity, and decreasing the profile spacing with increasing convexity. Could use a Michaelis-Menten S-curve relationship for this i.e. - // double fReN = pow(NowCell[nX][nY].dGetReynolds(m_dNu), m_dDepN); - // double fC1 = m_dC1Laminar - ((m_dC1Diff * fReN) / (fReN + m_dReMidN)); - } - - // Mark points on either side of the profile - for (int m = 1; m < dNumToMark; m++) - { - int nTmpPoint = nNormalPoint + m; - - if (nTmpPoint < nCoastSize) - pbVCoastPointDone->at(nTmpPoint) = true; - - nTmpPoint = nNormalPoint - m; - - if (nTmpPoint >= 0) - pbVCoastPointDone->at(nTmpPoint) = true; - } - } - } -} - -//=============================================================================================================================== -//! Creates a single coastline-normal profile (which may be an intervention profile) -//=============================================================================================================================== -int CSimulation::nCreateProfile(int const nCoast, int const nCoastSize, int const nProfileStartPoint, int const nProfile, bool const bIntervention, CGeom2DIPoint const* pPtiStart) -{ - // OK, we have flagged the start point of this new coastline-normal profile, so create it. Make the start of the profile the centroid of the actual cell that is marked as coast (not the cell under the smoothed vector coast, they may well be different) - CGeom2DPoint PtStart; // In external CRS - PtStart.SetX(dGridCentroidXToExtCRSX(pPtiStart->nGetX())); - PtStart.SetY(dGridCentroidYToExtCRSY(pPtiStart->nGetY())); - - CGeom2DPoint PtEnd; // In external CRS - CGeom2DIPoint PtiEnd; // In grid CRS - int const nRet = nGetCoastNormalEndPoint(nCoast, nProfileStartPoint, nCoastSize, &PtStart, m_dCoastNormalLength, &PtEnd, &PtiEnd, bIntervention); - if (nRet == RTN_ERR_NO_SOLUTION_FOR_ENDPOINT) - { - // Could not solve end-point equation, so forget about this profile - return nRet; - } - - int const nXEnd = PtiEnd.nGetX(); - int const nYEnd = PtiEnd.nGetY(); - - // Safety check: is the end point in the contiguous sea? - if (! m_pRasterGrid->m_Cell[nXEnd][nYEnd].bIsInContiguousSea()) - { - // if (m_nLogFileDetail >= LOG_FILE_ALL) - // LogStream << m_ulIter << ": coast " << nCoast << ", possible profile with start point " << nProfileStartPoint << " has inland end point at [" << nXEnd << "][" << nYEnd << "] = {" << dGridCentroidXToExtCRSX(nXEnd) << ", " << dGridCentroidYToExtCRSY(nYEnd) << "}, ignoring" << endl; - - return RTN_ERR_PROFILE_ENDPOINT_IS_INLAND; - } - - // Safety check: is the water depth at the end point less than the depth of closure? - if (m_pRasterGrid->m_Cell[nXEnd][nYEnd].dGetSeaDepth() < m_dDepthOfClosure) - { - // if (m_nLogFileDetail >= LOG_FILE_ALL) - // LogStream << m_ulIter << ": coast " << nCoast << ", possible profile with start point " << nProfileStartPoint << " is too short for depth of closure " << m_dDepthOfClosure << " at end point [" << nXEnd << "][" << nYEnd << "] = {" << dGridCentroidXToExtCRSX(nXEnd) << ", " << dGridCentroidYToExtCRSY(nYEnd) << "}, ignoring" << endl; - - return RTN_ERR_PROFILE_END_INSUFFICIENT_DEPTH; - } - - // No problems, so create the new profile - CGeomProfile* pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, bIntervention); - - // And create the profile's coastline-normal vector. Only two points (start and end points, both external CRS) are stored - vector VNormal; - VNormal.push_back(PtStart); - VNormal.push_back(PtEnd); - - // Set the start and end points (external CRS) of the profile - pProfile->SetPointsInProfile(&VNormal); - - // Create the profile's CGeomMultiLine then set nProfile as the only co-incident profile of the only line segment - pProfile->AppendLineSegment(); - pProfile->AppendCoincidentProfileToLineSegments(make_pair(nProfile, 0)); - - // Save the profile, note that several fields in the profile are still blank - m_VCoast[nCoast].AppendProfile(pProfile); - - // // DEBUG CODE ================= - // LogStream << "in nCreateProfile() ===========" << endl; - // // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); - // LogStream << pProfile->nGetProfileID() << "\t"; - // - // int nPointsInProfile = pProfile->nGetProfileSize(); - // - // for (int nPoint = 0; nPoint < nPointsInProfile; nPoint++) - // { - // CGeom2DPoint Pt = *pProfile->pPtGetPointInProfile(nPoint); - // LogStream << " {" << Pt.dGetX() << ", " << Pt.dGetY() << "}"; - // } - // LogStream << endl << "===========" << endl; - // // DEBUG CODE ================= - - // assert(pProfile->nGetProfileSize() > 0); - - LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " created at coast point " << nProfileStartPoint << " from [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "] = {" << PtStart.dGetX() << ", " << PtStart.dGetY() << "} to [" << PtiEnd.nGetX() << "][" << PtiEnd.nGetY() << "] = {" << PtEnd.dGetX() << ", " << PtEnd.dGetY() << "}" << (pProfile->bIsIntervention() ? ", from intervention" : "") << endl; - - return RTN_OK; -} - -//=============================================================================================================================== -//! Creates a 'special' profile at each end of a coastline, at the edge of the raster grid. This profile is not necessarily normal to the coastline since it goes along the grid's edge -//=============================================================================================================================== -int CSimulation::nLocateAndCreateGridEdgeProfile(bool const bCoastStart, int const nCoast, int& nProfile) -{ - int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); - int const nHandedness = m_VCoast[nCoast].nGetSeaHandedness(); - int const nProfileLen = nRound(m_dCoastNormalLength / m_dCellSide); // Profile length in grid CRS - int nProfileStartEdge; - - CGeom2DIPoint PtiProfileStart; // In grid CRS - vector VPtiNormalPoints; // In grid CRS - - if (bCoastStart) - { - // At start of coast - PtiProfileStart = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(0); // Grid CRS - nProfileStartEdge = m_VCoast[nCoast].nGetStartEdge(); - } - else - { - // At end of coast - PtiProfileStart = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastSize - 1); // Grid CRS - nProfileStartEdge = m_VCoast[nCoast].nGetEndEdge(); - } - - VPtiNormalPoints.push_back(PtiProfileStart); - - // Find the start cell in the list of edge cells - auto it = find(m_VEdgeCell.begin(), m_VEdgeCell.end(), PtiProfileStart); - - if (it == m_VEdgeCell.end()) - { - // Not found. This can happen because of rounding problems, i.e. the cell which was stored as the first cell of the raster coastline - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter << ": " << ERR << " when constructing start-of-coast profile, [" << PtiProfileStart.nGetX() << "][" << PtiProfileStart.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiProfileStart.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiProfileStart.nGetY()) << "} not found in list of edge cells" << endl; - - return RTN_ERR_COAST_CANT_FIND_EDGE_CELL; - } - - // Found - int nPos = static_cast(it - m_VEdgeCell.begin()); - - // Now construct the edge profile, searching for edge cells - for (int n = 0; n < nProfileLen; n++) - { - if (bCoastStart) - { - // At start of coast - if (nHandedness == LEFT_HANDED) - { - // The list of edge cells is in clockwise sequence, go in this direction - nPos++; - - if (nPos >= static_cast(m_VEdgeCell.size())) - { - // We've reached the end of the list of edge cells before the profile is long enough. OK, we can live with this - break; - } - } - else // Right-handed - { - // The list of edge cells is in clockwise sequence, go in the opposite direction - nPos--; - - if (nPos < 0) - { - // We've reached the beginning of the list of edge cells before the profile is long enough. OK, we can live with this - break; - } - } - } - else - { - // At end of coast - if (nHandedness == LEFT_HANDED) - { - // The list of edge cells is in clockwise sequence, go in the opposite direction - nPos--; - - if (nPos < 0) - { - // We've reached the beginning of the list of edge cells before the profile is long enough. OK, we can live with this - break; - } - } - else // Right-handed - { - // The list of edge cells is in clockwise sequence, go in this direction - nPos++; - - if (nPos >= static_cast(m_VEdgeCell.size())) - { - // We've reached the end of the list of edge cells before the profile is long enough. OK, we can live with this - break; - } - } - } - - if (m_VEdgeCellEdge[nPos] != nProfileStartEdge) - { - // We've reached the end of a grid side before the profile is long enough. OK, we can live with this - break; - } - - // All OK, so append this grid-edge cell, making sure that there is no gap between this and the previously-appended cell (if there is, will get problems with cell-by-cell fill) - AppendEnsureNoGap(&VPtiNormalPoints, &m_VEdgeCell[nPos]); - } - - int nProfileStartPoint; - CGeomProfile* pProfile; - CGeom2DIPoint const PtiDummy(INT_NODATA, INT_NODATA); - - if (bCoastStart) - { - nProfileStartPoint = 0; - - // Create the new start-of-coast profile - pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, false); - - // Mark this as a start-of-coast profile - pProfile->SetStartOfCoast(true); - } - else - { - nProfileStartPoint = nCoastSize - 1; - - // Create the new end-of-coast profile - pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, false); - - // Mark this as an end-of-coast profile - pProfile->SetEndOfCoast(true); - } - - // Create the list of cells 'under' this grid-edge profile. Note that more than two cells are stored - for (unsigned int n = 0; n < VPtiNormalPoints.size(); n++) - { - int const nX = VPtiNormalPoints[n].nGetX(); - int const nY = VPtiNormalPoints[n].nGetY(); - - // Mark each cell in the raster grid - m_pRasterGrid->m_Cell[nX][nY].SetCoastAndProfileID(nCoast, nProfile); - - // Store the raster grid coordinates in the profile object - pProfile->AppendCellInProfile(nX, nY); - - CGeom2DPoint const Pt(dGridCentroidXToExtCRSX(nX), dGridCentroidYToExtCRSY(nY)); // In external CRS - - // Store the external coordinates in the profile object. Note that for this grid-edge profile, the coordinates of the cells and the coordinates of points on the profile itself are identical, this is not the case for ordinary profiles - pProfile->AppendPointInProfile(&Pt); - } - - int const nEndX = VPtiNormalPoints.back().nGetX(); - int const nEndY = VPtiNormalPoints.back().nGetY(); - - // Get the deep water wave height and orientation values at the end of the profile - double const dDeepWaterWaveHeight = m_pRasterGrid->m_Cell[nEndX][nEndY].dGetCellDeepWaterWaveHeight(); - double const dDeepWaterWaveAngle = m_pRasterGrid->m_Cell[nEndX][nEndY].dGetCellDeepWaterWaveAngle(); - double const dDeepWaterWavePeriod = m_pRasterGrid->m_Cell[nEndX][nEndY].dGetCellDeepWaterWavePeriod(); - - // And store them in this profile - pProfile->SetProfileDeepWaterWaveHeight(dDeepWaterWaveHeight); - pProfile->SetProfileDeepWaterWaveAngle(dDeepWaterWaveAngle); - pProfile->SetProfileDeepWaterWavePeriod(dDeepWaterWavePeriod); - - // Create the profile's CGeomMultiLine then set nProfile as the only co-incident profile of the only line segment - pProfile->AppendLineSegment(); - pProfile->AppendCoincidentProfileToLineSegments(make_pair(nProfile, 0)); - - // Store the grid-edge profile - m_VCoast[nCoast].AppendProfile(pProfile); - m_VCoast[nCoast].SetProfileAtCoastPoint(nProfileStartPoint, pProfile); - - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": coast " << nCoast << " grid-edge profile " << nProfile << " created at coast " << (bCoastStart ? "start" : "end") << " point " << (bCoastStart ? 0 : nCoastSize - 1) << ", from [" << PtiProfileStart.nGetX() << "][" << PtiProfileStart.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiProfileStart.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiProfileStart.nGetY()) << "} to [" << VPtiNormalPoints.back().nGetX() << "][" << VPtiNormalPoints.back().nGetY() << "] = {" << dGridCentroidXToExtCRSX(VPtiNormalPoints.back().nGetX()) << ", " << dGridCentroidYToExtCRSY(VPtiNormalPoints.back().nGetY()) << "}" << endl; - - // assert(pProfile->nGetProfileSize() > 0); - - return RTN_OK; -} - -//=============================================================================================================================== -//! Finds the end point of a coastline-normal line, given the start point on the vector coastline. If however the start point is on the grid edge (only applicable to cliff collapse [rofiles), then the end point is also on the grid edge, and the line joining the start and end points is not necessarily normal to the vector coast. All input coordinates are in the external CRS -//=============================================================================================================================== -int CSimulation::nGetCoastNormalEndPoint(int const nCoast, int const nStartCoastPoint, int const nCoastSize, CGeom2DPoint const* pPtStart, double const dLineLength, CGeom2DPoint* pPtEnd, CGeom2DIPoint* pPtiEnd, bool const bIntervention) -{ - int const AVGSIZE = 21; // TODO 011 This should be a user input - - double dXEnd1 = 0; - double dXEnd2 = 0; - double dYEnd1 = 0; - double dYEnd2 = 0; - - CGeom2DPoint PtBefore; - CGeom2DPoint PtAfter; - - if (bIntervention) - { - // This is an intervention profile, so just use one point on either side (coordinates in external CRS). TODO Note this this assumes that this intervention profile is not at the start or end of the coastline - PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nStartCoastPoint - 1); - PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nStartCoastPoint + 1); - } - else - { - // This is not an intervention profile. It could be a cliff collapse profile, which could be a grid-edge profile - double const dXStart = pPtStart->dGetX(); - double const dYStart = pPtStart->dGetY(); - - int const nXStart = nRound(dExtCRSXToGridX(dXStart)); - int const nYStart = nRound(dExtCRSYToGridY(dYStart)); - int const nLineLength = nConvertMetresToNumCells(dLineLength); - - // LogStream << nXStart << ", " << nYStart << endl; - if ((nXStart == 0) || (nXStart == m_nXGridSize-1)) - { - // Yes it is a grid-edge profile - dXEnd1 = dGridXToExtCRSX(nXStart); - dXEnd2 = dXEnd1; - - dYEnd1 = dGridYToExtCRSY(nYStart + nLineLength); - dYEnd2 = dGridYToExtCRSY(nYStart - nLineLength); - } - else if ((nYStart == 0) || (nYStart == m_nYGridSize-1)) - { - // Yes it is a grid-edge profile - dYEnd1 = dGridYToExtCRSY(nYStart); - dYEnd2 = dYEnd1; - - dXEnd1 = dGridXToExtCRSX(nXStart + nLineLength); - dXEnd2 = dGridXToExtCRSX(nXStart - nLineLength); - } - else - { - // This is not a grid-edge profile, so put a maximum of AVGSIZE points before the start point into a vector - vector VPtBeforeToAverage; - - for (int n = 1; n <= AVGSIZE; n++) - { - int const nPoint = nStartCoastPoint - n; - if (nPoint < 0) - break; - - VPtBeforeToAverage.push_back(*m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nPoint)); - } - - // Put a maximum of AVGSIZE points after the start point into a vector - vector VPtAfterToAverage; - - for (int n = 1; n <= AVGSIZE; n++) - { - int const nPoint = nStartCoastPoint + n; - if (nPoint > nCoastSize - 1) - break; - - VPtAfterToAverage.push_back(*m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nPoint)); - } - - // Now average each of these vectors of points: results are in PtBefore and PtAfter (coordinates in external CRS) - PtBefore = PtAverage(&VPtBeforeToAverage); - PtAfter = PtAverage(&VPtAfterToAverage); - - // Get the y = a * x + b equation of the straight line linking the coastline points before and after 'this' coastline point. For this linking line, slope a = (y2 - y1) / (x2 - x1) - double const dYDiff = PtAfter.dGetY() - PtBefore.dGetY(); - double const dXDiff = PtAfter.dGetX() - PtBefore.dGetX(); - - if (bFPIsEqual(dYDiff, 0.0, TOLERANCE)) - { - // The linking line runs W-E or E-W, so a straight line at right angles to this runs N-S or S-N. Calculate the two possible end points for this coastline-normal profile - dXEnd1 = dXEnd2 = pPtStart->dGetX(); - dYEnd1 = pPtStart->dGetY() + dLineLength; - dYEnd2 = pPtStart->dGetY() - dLineLength; - } - else if (bFPIsEqual(dXDiff, 0.0, TOLERANCE)) - { - // The linking line runs N-S or S-N, so a straight line at right angles to this runs W-E or E-W. Calculate the two possible end points for this coastline-normal profile - dYEnd1 = dYEnd2 = pPtStart->dGetY(); - dXEnd1 = pPtStart->dGetX() + dLineLength; - dXEnd2 = pPtStart->dGetX() - dLineLength; - } - else - { - // The linking line runs neither W-E nor N-S so we have to work a bit harder to find the end-point of the coastline-normal profile - double const dA = dYDiff / dXDiff; - - // Now calculate the equation of the straight line which is perpendicular to this linking line - double const dAPerp = -1 / dA; - double const dBPerp = pPtStart->dGetY() - (dAPerp * pPtStart->dGetX()); - - // Calculate the end point of the profile: first do some substitution then rearrange as a quadratic equation i.e. in the form Ax^2 + Bx + C = 0 (see http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle) - double const dQuadA = 1 + (dAPerp * dAPerp); - double const dQuadB = 2 * ((dBPerp * dAPerp) - (dAPerp * pPtStart->dGetY()) - pPtStart->dGetX()); - double const dQuadC = ((pPtStart->dGetX() * pPtStart->dGetX()) + (pPtStart->dGetY() * pPtStart->dGetY()) + (dBPerp * dBPerp) - (2 * pPtStart->dGetY() * dBPerp) - (dLineLength * dLineLength)); - - // Solve for x and y using the quadratic formula x = (−B ± sqrt(B^2 − 4AC)) / 2A - double const dDiscriminant = (dQuadB * dQuadB) - (4 * dQuadA * dQuadC); - - if (dDiscriminant < 0) - { - LogStream << ERR << "timestep " << m_ulIter << ": discriminant < 0 when finding profile end point on coastline " << nCoast << ", from coastline point " << nStartCoastPoint << "), ignored" << endl; - return RTN_ERR_NO_SOLUTION_FOR_ENDPOINT; - } - - dXEnd1 = (-dQuadB + sqrt(dDiscriminant)) / (2 * dQuadA); - dYEnd1 = (dAPerp * dXEnd1) + dBPerp; - dXEnd2 = (-dQuadB - sqrt(dDiscriminant)) / (2 * dQuadA); - dYEnd2 = (dAPerp * dXEnd2) + dBPerp; - } - } - } - - // We have two possible solutions, so decide which of the two endpoints to use then create the profile end-point (coordinates in external CRS) - int const nSeaHand = m_VCoast[nCoast].nGetSeaHandedness(); // Assumes handedness is either 0 or 1 (i.e. not -1) - *pPtEnd = PtChooseEndPoint(nSeaHand, &PtBefore, &PtAfter, dXEnd1, dYEnd1, dXEnd2, dYEnd2); - - // Check that pPtiEnd is not off the grid. Note that pPtiEnd is not necessarily a cell centroid - pPtiEnd->SetXY(nRound(dExtCRSXToGridX(pPtEnd->dGetX())), nRound(dExtCRSYToGridY(pPtEnd->dGetY()))); - - if (! bIsWithinValidGrid(pPtiEnd)) - { - // LogStream << m_ulIter << ": profile endpoint is outside grid [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}. The profile starts at coastline point " << nStartCoastPoint << " = {" << pPtStart->dGetX() << ", " << pPtStart->dGetY() << "}" << endl; - - // The end point is off the grid, so constrain it to be within the valid grid - CGeom2DIPoint const PtiStart(nRound(dExtCRSXToGridX(pPtStart->dGetX())), nRound(dExtCRSYToGridY(pPtStart->dGetY()))); - KeepWithinValidGrid(&PtiStart, pPtiEnd); - - pPtEnd->SetX(dGridCentroidXToExtCRSX(pPtiEnd->nGetX())); - pPtEnd->SetY(dGridCentroidYToExtCRSY(pPtiEnd->nGetY())); - - LogStream << m_ulIter << ": profile endpoint constrained to be within grid, is now [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}. The profile starts at coastline point " << nStartCoastPoint << " = {" << pPtStart->dGetX() << ", " << pPtStart->dGetY() << "}" << endl; - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! Choose which end point to use for the coastline-normal profile -//=============================================================================================================================== -CGeom2DPoint CSimulation::PtChooseEndPoint(int const nHand, CGeom2DPoint const* PtBefore, CGeom2DPoint const* PtAfter, double const dXEnd1, double const dYEnd1, double const dXEnd2, double const dYEnd2) -{ - CGeom2DPoint PtChosen; - - // All coordinates here are in the external CRS, so the origin of the grid is the bottom left - if (nHand == RIGHT_HANDED) - { - // The sea is to the right of the linking line. So which way is the linking line oriented? First check the N-S component - if (PtAfter->dGetY() > PtBefore->dGetY()) - { - // We are going S to N and the sea is to the right: the normal endpoint is to the E. We want the larger of the two x values - if (dXEnd1 > dXEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else if (PtAfter->dGetY() < PtBefore->dGetY()) - { - // We are going N to S and the sea is to the right: the normal endpoint is to the W. We want the smaller of the two x values - if (dXEnd1 < dXEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else - { - // No N-S component i.e. the linking line is exactly W-E. So check the W-E component - if (PtAfter->dGetX() > PtBefore->dGetX()) - { - // We are going W to E and the sea is to the right: the normal endpoint is to the s. We want the smaller of the two y values - if (dYEnd1 < dYEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else // Do not check for (PtAfter->dGetX() == PtBefore->dGetX()), since this would mean the two points are co-incident - { - // We are going E to W and the sea is to the right: the normal endpoint is to the N. We want the larger of the two y values - if (dYEnd1 > dYEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - } - } - else // nHand == LEFT_HANDED - { - // The sea is to the left of the linking line. So which way is the linking line oriented? First check the N-S component - if (PtAfter->dGetY() > PtBefore->dGetY()) - { - // We are going S to N and the sea is to the left: the normal endpoint is to the W. We want the smaller of the two x values - if (dXEnd1 < dXEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else if (PtAfter->dGetY() < PtBefore->dGetY()) - { - // We are going N to S and the sea is to the left: the normal endpoint is to the E. We want the larger of the two x values - if (dXEnd1 > dXEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else - { - // No N-S component i.e. the linking line is exactly W-E. So check the W-E component - if (PtAfter->dGetX() > PtBefore->dGetX()) - { - // We are going W to E and the sea is to the left: the normal endpoint is to the N. We want the larger of the two y values - if (dYEnd1 > dYEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - else // Do not check for (PtAfter->dGetX() == PtBefore->dGetX()), since this would mean the two points are co-incident - { - // We are going E to W and the sea is to the left: the normal endpoint is to the S. We want the smaller of the two y values - if (dYEnd1 < dYEnd2) - { - PtChosen.SetX(dXEnd1); - PtChosen.SetY(dYEnd1); - } - else - { - PtChosen.SetX(dXEnd2); - PtChosen.SetY(dYEnd2); - } - } - } - } - - return PtChosen; -} - -//=============================================================================================================================== -//! Checks all coastline-normal profiles for intersection, and modifies those that intersect -//=============================================================================================================================== -void CSimulation::CheckForIntersectingProfiles(void) -{ - LogStream << endl << m_ulIter << ": Checking for profile intersection" << endl; - - // Do once for every coastline object - int const nCoastLines = static_cast(m_VCoast.size()); - - for (int nCoast = 0; nCoast < nCoastLines; nCoast++) - { - int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); - - // Do once for every profile, in along-coast sequence - for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) - { - if (! m_VCoast[nCoast].bIsProfileAtCoastPoint(nCoastPoint)) - continue; - - // There is a profile at this coast point - CGeomProfile* pFirstProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nCoastPoint); - int const nFirstProfile = pFirstProfile->nGetProfileID(); - - // Only check this profile if it is problem free, and is not a start- or end-of-coast profile. Continue checking if it has been truncated, however - if (! pFirstProfile->bProfileOKIncTruncated()) - { - // LogStream << m_ulIter << ": nCoastPoint = " << nCoastPoint << " pFirstProfile = " << pFirstProfile->nGetProfileID() << " is not OK (could be a start- or end-of-coast profile), abandoning" << endl; - continue; - } - - // OK we have found a first profile. Now go along the coast in alternate directions: first down-coast (in the direction of increasing coast point numbers) then up-coast - for (int nDirection = DIRECTION_DOWNCOAST; nDirection <= DIRECTION_UPCOAST; nDirection++) - { - int nStartPoint; - - if (nDirection == DIRECTION_DOWNCOAST) - nStartPoint = nCoastPoint + 1; - else - nStartPoint = nCoastPoint - 1; - - for (int nSecondCoastPoint = nStartPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint < nCoastSize : nSecondCoastPoint >= 0; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint++ : nSecondCoastPoint--) - // // In this direction, look at profiles which are increasingly close to the first profile - // int nStartPoint; - // if (nDirection == DIRECTION_DOWNCOAST) - // nStartPoint = 0; - // else - // nStartPoint = nCoastSize - 1; - // - // for (int nSecondCoastPoint = nStartPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint < nCoastPoint : nSecondCoastPoint > nCoastPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint++ : nSecondCoastPoint--) - { - if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nSecondCoastPoint)) - { - // There is a profile at the second coast point, so get a pointer to it - CGeomProfile* pSecondProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nSecondCoastPoint); - int const nSecondProfile = pSecondProfile->nGetProfileID(); - - // LogStream << m_ulIter << ": " << (nDirection == DIRECTION_DOWNCOAST ? "down" : "up") << "-coast search, nCoastPoint = " << nCoastPoint << " nSecondCoastPoint = " << nSecondCoastPoint << " (profiles " << pFirstProfile->nGetProfileID() << " and " << pSecondProfile->nGetProfileID() << ")" << endl; - - // Only check this profile if it is problem free, and is not a start- or end-of-coast profile. Continue checking if it has been truncated, however - if (! pSecondProfile->bProfileOKIncTruncated()) - { - // LogStream << m_ulIter << ": second profile = " << pSecondProfile->nGetProfileID() << " is not OK (could be a start- or end-of-coast profile), abandoning" << endl; - continue; - } - - // Only check these two profiles for intersection if they are are not co-incident in the final line segment of both profiles (i.e. the profiles have not already intersected) - if ((pFirstProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nSecondProfile)) || (pSecondProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nFirstProfile))) - { - // LogStream << m_ulIter << ": profiles " << pFirstProfile->nGetProfileID() << " and " << pSecondProfile->nGetProfileID() << " are are not co-incident in the final line segment of both profiles (i.e. the profiles have not already intersected), abandoning" << endl; - continue; - } - - // OK go for it - int nProf1LineSeg = 0; - int nProf2LineSeg = 0; - double dIntersectX = 0; - double dIntersectY = 0; - double dAvgEndX = 0; - double dAvgEndY = 0; - - if (bCheckForIntersection(pFirstProfile, pSecondProfile, nProf1LineSeg, nProf2LineSeg, dIntersectX, dIntersectY, dAvgEndX, dAvgEndY)) - { - // The profiles intersect. Decide which profile to truncate, and which to retain - int nPoint = -1; - - if (pFirstProfile->bIsIntervention()) - { - LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, truncate " << nFirstProfile << " since it is an intervention profile" << endl; - - // Truncate the first profile, since it is an intervention profile - TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); - } - else if (pSecondProfile->bIsIntervention()) - { - LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, truncate " << nSecondProfile << " since it is an intervention profile" << endl; - - // Truncate the second profile, since it is an intervention profile - TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); - } - // Is the point of intersection already present in the first profile (i.e. because there has already been an intersection at this point between the first profile and some other profile)? - else if (pFirstProfile->bIsPointInProfile(dIntersectX, dIntersectY, nPoint)) - { - LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, but point {" << dIntersectX << ", " << dIntersectY << "} is already present in profile " << nFirstProfile << " as point " << nPoint << endl; - - // Truncate the second profile and merge it with the first profile - TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, true); - } - // Is the point of intersection already present in the second profile? - else if (pSecondProfile->bIsPointInProfile(dIntersectX, dIntersectY, nPoint)) - { - LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, but point {" << dIntersectX << ", " << dIntersectY << "} is already present in profile " << nSecondProfile << " as point " << nPoint << endl; - - // Truncate the first profile and merge it with the second profile - TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, true); - } - else - { - // The point of intersection is not already present in either profile, so get the number of line segments of each profile - int const nFirstProfileLineSegments = pFirstProfile->nGetNumLineSegments(); - int const nSecondProfileLineSegments = pSecondProfile->nGetNumLineSegments(); - - // assert(nProf1LineSeg < nFirstProfileLineSegments); - // assert(nProf2LineSeg < nSecondProfileLineSegments); - - // Next check whether the point of intersection is on the final line segment of both profiles - if ((nProf1LineSeg == (nFirstProfileLineSegments - 1)) && (nProf2LineSeg == (nSecondProfileLineSegments - 1))) - { - // Yes, the point of intersection is on the final line segment of both profiles, so merge the profiles seaward of the point of intersection - MergeProfilesAtFinalLineSegments(nCoast, pFirstProfile, pSecondProfile, nFirstProfileLineSegments, nSecondProfileLineSegments, dIntersectX, dIntersectY, dAvgEndX, dAvgEndY); - - // LogStream << m_ulIter << ": " << ((nDirection == DIRECTION_DOWNCOAST) ? "down" : "up") << "-coast search, end-segment intersection between profiles " << nFirstProfile << " and " << nSecondProfile << " at [" << dIntersectX << ", " << dIntersectY << "] in line segment [" << nProf1LineSeg << "] of " << nFirstProfileLineSegments << " segments, and line segment [" << nProf2LineSeg << "] of " << nSecondProfileLineSegments << " segments, respectively" << endl; - - // // DEBUG CODE ============================================================================================= - // int nSizeTmp = pFirstProfile->nGetProfileSize(); - // CGeom2DPoint PtEndTmp = *pFirstProfile->pPtGetPointInProfile(nSizeTmp-1); - // - // LogStream << m_ulIter << ": end of first profile (" << nFirstProfile << ") is point " << nSizeTmp-1 << " at [" << dExtCRSXToGridX(PtEndTmp.dGetX()) << "][" << dExtCRSYToGridY(PtEndTmp.dGetY()) << "} = {" << PtEndTmp.dGetX() << ", " << PtEndTmp.dGetY() << "}" << endl; - // - // nSizeTmp = pSecondProfile->nGetProfileSize(); - // PtEndTmp = *pSecondProfile->pPtGetPointInProfile(nSizeTmp-1); - // - // LogStream << m_ulIter << ": end of second profile (" << nSecondProfile << ") is point " << nSizeTmp-1 << " at [" << dExtCRSXToGridX(PtEndTmp.dGetX()) << "][" << dExtCRSYToGridY(PtEndTmp.dGetY()) << "} = {" << PtEndTmp.dGetX() << ", " << PtEndTmp.dGetY() << "}" << endl; - // // DEBUG CODE ============================================================================================= - } - else - { - // The profiles intersect, but the point of intersection is not on the final line segment of both profiles. One of the profiles will be truncated, the other profile will be retained - // LogStream << m_ulIter << ": " << ((nDirection == DIRECTION_DOWNCOAST) ? "down" : "up") << "-coast search, intersection (NOT both end segments) between profiles " << nFirstProfile << " and " << nSecondProfile << " at [" << dIntersectX << ", " << dIntersectY << "] in line segment [" << nProf1LineSeg << "] of " << nFirstProfileLineSegments << ", and line segment [" << nProf2LineSeg << "] of " << nSecondProfileLineSegments << ", respectively" << endl; - - // Decide which profile to truncate, and which to retain - if (pFirstProfile->bIsIntervention()) - { - // Truncate the first profile, since it is an intervention profile - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pFirstProfile is an intervention profile, so truncate pFirstProfile (" << pFirstProfile->nGetProfileID() << ")" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); - } - else if (pSecondProfile->bIsIntervention()) - { - // Truncate the second profile, since it is an intervention profile - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pSecondProfile is an intervention profile, so truncate pSecondProfile (" << pSecondProfile->nGetProfileID() << ")" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); - } - else if (nFirstProfileLineSegments < nSecondProfileLineSegments) - { - // Truncate the first profile, since it has a smaller number of line segments - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pFirstProfile has a smaller number of line segments, so truncate pFirstProfile (" << pFirstProfile->nGetProfileID() << ")" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); - } - else if (nFirstProfileLineSegments > nSecondProfileLineSegments) - { - // Truncate the second profile, since it has a smaller number of line segments - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pSecondProfile has a smaller number of line segments, so truncate pSecondProfile (" << pSecondProfile->nGetProfileID() << ")" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); - } - else - { - // Both profiles have the same number of line segments, so choose randomly. Draw a sample from the unit normal distribution using random number generator 1 - double const dRand = m_dGetFromUnitNormalDist(m_Rand[0]); - - if (dRand >= 0.0) - { - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", same number of line segment, randomly truncate pFirstProfile" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); - } - else - { - // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", same number of line segment, randomly truncate pSecondProfile" << endl; - - TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); - } - } - } - } - } - } - } - } - } - } -} - -//=============================================================================================================================== -//! Check all coastline-normal profiles and modify the profiles if they intersect, then mark valid profiles on the raster grid -//=============================================================================================================================== -int CSimulation::nCheckAndMarkAllProfiles(void) -{ - // Check to see which coastline-normal profiles intersect. Then modify intersecting profiles so that the sections of each profile seaward of the point of intersection are 'shared' i.e. are multi-lines. This creates the boundaries of the triangular polygons - CheckForIntersectingProfiles(); - - // Again check the normal profiles for insufficient length: is the water depth at the end point less than the depth of closure? We do this again because some profiles may have been shortened as a result of intersection. Do once for every coastline object - for (unsigned int nCoast = 0; nCoast < m_VCoast.size(); nCoast++) - { - for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) - { - CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); - int const nProfile = pProfile->nGetProfileID(); - - if (pProfile->bProfileOK()) - { - int const nSize = pProfile->nGetProfileSize(); - - // Safety check - if (nSize == 0) - { - // pProfile->SetTooShort(true); - m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); - LogStream << "Profile " << nProfile << " is too short, size = " << nSize << endl; - continue; - } - - CGeom2DPoint const* pPtEnd = pProfile->pPtGetPointInProfile(nSize - 1); - CGeom2DIPoint const PtiEnd = PtiExtCRSToGridRound(pPtEnd); - int nXEnd = PtiEnd.nGetX(); - int nYEnd = PtiEnd.nGetY(); - - // Safety checks: the point may be outside the grid, so keep it within the grid - nXEnd = tMin(nXEnd, m_nXGridSize - 1); - nYEnd = tMin(nYEnd, m_nYGridSize - 1); - nXEnd = tMax(nXEnd, 0); - nYEnd = tMax(nYEnd, 0); - - if (m_pRasterGrid->m_Cell[nXEnd][nYEnd].dGetSeaDepth() < m_dDepthOfClosure) - { - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter << ": coast " << nCoast << ", profile " << nProfile << " is invalid, is too short for depth of closure " << m_dDepthOfClosure << " at end point [" << nXEnd << "][" << nYEnd << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}, flagging as too short" << endl; - - // pProfile->SetTooShort(true); - m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); - } - } - } - - // For this coast, put all valid coastline-normal profiles (apart from the profiles at the start and end of the coast, since they have already been done) onto the raster grid. But if the profile is not long enough, crosses a coastline, hits dry land, or hits another profile, then mark the profile as invalid - int nValidProfiles = 0; - MarkProfilesOnGrid(nCoast, nValidProfiles); - - if (nValidProfiles == 0) - { - // Problem! No valid profiles, so quit - cerr << m_ulIter << ": " << ERR << "no coastline-normal profiles created" << endl; - return RTN_ERR_NO_PROFILES_2; - } - - // // DEBUG CODE =========================================================================================================== - // if (m_ulIter == 109) - // { - // string strOutFile = m_strOutPath; - // strOutFile += "00_profile_raster_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // int nn = 0; - // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // if (m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) - // pdRaster[nn] = -1; - // else - // { - // - // // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].nGetProfileID(); - // } - // - // nn++; - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // int nRet1 = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet1 == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // } - // // DEBUG CODE =========================================================================================================== - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! Checks all line segments of a pair of coastline-normal profiles for intersection. If the lines intersect, returns true with the numbers of the line segments at which intersection occurs in nProfile1LineSegment and nProfile1LineSegment, the intersection point in dXIntersect and dYIntersect, and the 'average' seaward endpoint of the two intersecting profiles at dXAvgEnd and dYAvgEnd -//=============================================================================================================================== -bool CSimulation::bCheckForIntersection(CGeomProfile* const pVProfile1, CGeomProfile* const pVProfile2, int& nProfile1LineSegment, int& nProfile2LineSegment, double& dXIntersect, double& dYIntersect, double& dXAvgEnd, double& dYAvgEnd) -{ - // For both profiles, look at all line segments - int const nProfile1NumSegments = pVProfile1->nGetNumLineSegments(); - int const nProfile2NumSegments = pVProfile2->nGetNumLineSegments(); - // nProfile1Size = pVProfile1->nGetProfileSize(), - // nProfile2Size = pVProfile2->nGetProfileSize(); - - // assert(nProfile1Size == nProfile1NumSegments+1); - // assert(nProfile2Size == nProfile2NumSegments+1); - - for (int i = 0; i < nProfile1NumSegments; i++) - { - for (int j = 0; j < nProfile2NumSegments; j++) - { - // In external coordinates - double const dX1 = pVProfile1->pPtVGetPoints()->at(i).dGetX(); - double const dY1 = pVProfile1->pPtVGetPoints()->at(i).dGetY(); - double const dX2 = pVProfile1->pPtVGetPoints()->at(i + 1).dGetX(); - double const dY2 = pVProfile1->pPtVGetPoints()->at(i + 1).dGetY(); - - double const dX3 = pVProfile2->pPtVGetPoints()->at(j).dGetX(); - double const dY3 = pVProfile2->pPtVGetPoints()->at(j).dGetY(); - double const dX4 = pVProfile2->pPtVGetPoints()->at(j + 1).dGetX(); - double const dY4 = pVProfile2->pPtVGetPoints()->at(j + 1).dGetY(); - - // Uses Cramer's Rule to solve the equations. Modified from code at http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect (in turn based on Andre LeMothe's "Tricks of the Windows Game Programming Gurus") - double const dDiffX1 = dX2 - dX1; - double const dDiffY1 = dY2 - dY1; - double const dDiffX2 = dX4 - dX3; - double const dDiffY2 = dY4 - dY3; - - double dS = -999; - double dT = -999; - double dTmp = 0; - - dTmp = -dDiffX2 * dDiffY1 + dDiffX1 * dDiffY2; - - if (! bFPIsEqual(dTmp, 0.0, TOLERANCE)) - dS = (-dDiffY1 * (dX1 - dX3) + dDiffX1 * (dY1 - dY3)) / dTmp; - - dTmp = -dDiffX2 * dDiffY1 + dDiffX1 * dDiffY2; - - if (! bFPIsEqual(dTmp, 0.0, TOLERANCE)) - dT = (dDiffX2 * (dY1 - dY3) - dDiffY2 * (dX1 - dX3)) / dTmp; - - if (dS >= 0 && dS <= 1 && dT >= 0 && dT <= 1) - { - // Collision detected, calculate intersection coordinates - dXIntersect = dX1 + (dT * dDiffX1); - dYIntersect = dY1 + (dT * dDiffY1); - - // And calc the average end-point coordinates - dXAvgEnd = (dX2 + dX4) / 2; - dYAvgEnd = (dY2 + dY4) / 2; - - // Get the line segments at which intersection occurred - nProfile1LineSegment = i; - nProfile2LineSegment = j; - - // LogStream << "\t" << "INTERSECTION dX2 = " << dX2 << " dX4 = " << dX4 << " dY2 = " << dY2 << " dY4 = " << dY4 << endl; - return true; - } - } - } - - // No intersection - return false; -} - -//=============================================================================================================================== -//! For this coastline, marks all coastline-normal profiles (apart from the two 'special' ones at the start and end of the coast) onto the raster grid, i.e. rasterizes multi-line vector objects onto the raster grid. Note that this doesn't work if the vector has already been interpolated to fit on the grid i.e. if distances between vector points are just one cell apart -//=============================================================================================================================== -void CSimulation::MarkProfilesOnGrid(int const nCoast, int& nValidProfiles) -{ - // How many profiles on this coast? - int const nProfiles = m_VCoast[nCoast].nGetNumProfiles(); - - if (nProfiles == 0) - { - // This can happen if the coastline is very short, so just give a warning and carry on with the next coastline - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << WARN << m_ulIter << ": coast " << nCoast << " has no profiles" << endl; - - return; - } - - static bool bDownCoast = true; - - // Now do this for every profile, alternate between up-coast and down-coast directions - for (int n = 0; n < nProfiles; n++) - { - CGeomProfile* pProfile; - - if (bDownCoast) - pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); - else - pProfile = m_VCoast[nCoast].pGetProfileWithUpCoastSeq(n); - - // Don't do this for the first and last profiles (i.e. the profiles at the start and end of the coast) since these are put onto the grid elsewhere - if (pProfile->bIsGridEdge()) - continue; - - int const nProfile = pProfile->nGetProfileID(); - - // If this profile has a problem, then forget about it - // if (! pProfile->bProfileOK()) - // { - // LogStream << m_ulIter << ": in MarkProfilesOnGrid() profile " << nProfile << " is not OK" << endl; - // continue; - // } - - int const nPoints = pProfile->nGetProfileSize(); - - if (nPoints < 2) - { - // Need at least two points in the profile, so this profile is invalid: mark it - m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); - - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter << ": coast " << nCoast << ", profile " << nProfile << " is invalid, has only " << nPoints << " points" << endl; - - continue; - } - - // OK, go for it: set up temporary vectors to hold the x-y coords (in grid CRS) of the cells which we will mark - vector VCellsToMark; - vector bVShared; - bool bTooShort = false; - bool bTruncatedSameCoast = false; - bool bHitCoast = false; - bool bHitLand = false; - bool bHitIntervention = false; - bool bHitAnotherProfile = false; - - CreateRasterizedProfile(nCoast, pProfile, &VCellsToMark, &bVShared, bTooShort, bTruncatedSameCoast, bHitCoast, bHitLand, bHitIntervention, bHitAnotherProfile); - - if ((bTruncatedSameCoast && (! ACCEPT_TRUNCATED_PROFILES)) || bTooShort || bHitCoast || bHitLand || bHitIntervention || bHitAnotherProfile || VCellsToMark.size() == 0) - continue; - - // This profile is fine - nValidProfiles++; - - for (unsigned int k = 0; k < VCellsToMark.size(); k++) - { - // Ignore duplicate points - if ((k > 0) && (VCellsToMark[k] == m_VCoast[nCoast].pGetProfile(nProfile)->pPtiGetLastCellInProfile())) - continue; - - // Mark each cell in the raster grid - int const nXTmp = VCellsToMark[k].nGetX(); - int const nYTmp = VCellsToMark[k].nGetY(); - m_pRasterGrid->m_Cell[nXTmp][nYTmp].SetCoastAndProfileID(nCoast, nProfile); - - // Store the raster grid coordinates in the profile object - m_VCoast[nCoast].pGetProfile(nProfile)->AppendCellInProfile(nXTmp, nYTmp); - - // Mark the shared (i.e. multi-line) parts of the profile (if any) - // if (bVShared[k]) - // { - // m_VCoast[nCoast].pGetProfile(nProfile)->AppendPointShared(true); - // // LogStream << m_ulIter << ": profile " << j << " point " << k << " marked as shared" << endl; - // } - // else - // { - // m_VCoast[nCoast].pGetProfile(nProfile)->AppendPointShared(false); - // // LogStream << m_ulIter << ": profile " << nProfile << " point " << k << " marked as NOT shared" << endl; - // } - } - - // Get the deep water wave height and orientation values at the end of the profile - double const dDeepWaterWaveHeight = m_pRasterGrid->m_Cell[VCellsToMark.back().nGetX()][VCellsToMark.back().nGetY()].dGetCellDeepWaterWaveHeight(); - double const dDeepWaterWaveAngle = m_pRasterGrid->m_Cell[VCellsToMark.back().nGetX()][VCellsToMark.back().nGetY()].dGetCellDeepWaterWaveAngle(); - double const dDeepWaterWavePeriod = m_pRasterGrid->m_Cell[VCellsToMark.back().nGetX()][VCellsToMark.back().nGetY()].dGetCellDeepWaterWavePeriod(); - - // And store them for this profile - m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWaveHeight(dDeepWaterWaveHeight); - m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWaveAngle(dDeepWaterWaveAngle); - m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWavePeriod(dDeepWaterWavePeriod); - } - - bDownCoast = ! bDownCoast; -} - -//=============================================================================================================================== -//! Given a pointer to a coastline-normal profile, returns an output vector of cells which are 'under' every line segment of the profile. If there is a problem with the profile (e.g. a rasterized cell is dry land or coast, or the profile has to be truncated) then we pass this back as an error code -//=============================================================================================================================== -void CSimulation::CreateRasterizedProfile(int const nCoast, CGeomProfile* pProfile, vector* pVIPointsOut, vector* pbVShared, bool& bTooShort, bool& bTruncatedSameCoast, bool& bHitCoast, bool& bHitLand, bool& bHitIntervention, bool& bHitAnotherProfile) -{ - int const nProfile = pProfile->nGetProfileID(); - int nSeg = 0; - int const nNumSegments = pProfile->nGetNumLineSegments(); - - pVIPointsOut->clear(); - - // LogStream << m_ulIter << ": in CreateRasterizedProfile() *pPtiStart for profile " << nProfile << " is [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "]" << endl; - int nXStartLast = INT_NODATA; - int nYStartLast = INT_NODATA; - int nXEndLast = INT_NODATA; - int nYEndLast = INT_NODATA; - - // Do for every segment of this profile - for (nSeg = 0; nSeg < nNumSegments; nSeg++) - { - // Do once for every line segment - CGeom2DIPoint PtiSegStart; - - if (nSeg == 0) - { - // If this is the first segment, use the coastline start point to prevent external CRS to grid CRS rounding errors - int const nCoastPoint = pProfile->nGetCoastPoint(); - PtiSegStart = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastPoint); - } - else - { - CGeom2DPoint const* pPtSegStart = pProfile->pPtGetPointInProfile(nSeg); - - // Convert from the external CRS to grid CRS - PtiSegStart = PtiExtCRSToGridRound(pPtSegStart); - } - - CGeom2DPoint const* pPtSegEnd = pProfile->pPtGetPointInProfile(nSeg + 1); // This is OK - - // Convert from the external CRS to grid CRS - CGeom2DIPoint const PtiSegEnd = PtiExtCRSToGridRound(pPtSegEnd); - - // Safety check - if (PtiSegStart == PtiSegEnd) - continue; - - int const nXStart = PtiSegStart.nGetX(); - int const nYStart = PtiSegStart.nGetY(); - int const nXEnd = PtiSegEnd.nGetX(); - int const nYEnd = PtiSegEnd.nGetY(); - - bool bShared = false; - - if (pProfile->nGetNumCoincidentProfilesInLineSegment(nSeg) > 1) - { - bShared = true; - - // If this is the second or more of several coincident line segments (i.e. it has the same start and end points as the previous line segment) then ignore it - if ((nXStart == nXStartLast) && (nYStart == nYStartLast) && (nXEnd == nXEndLast) && (nYEnd == nYEndLast)) - continue; - } - - // Interpolate between cells by a simple DDA line algorithm, see http://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) Note that Bresenham's algorithm gave occasional gaps - double dXInc = nXEnd - nXStart; - double dYInc = nYEnd - nYStart; - double const dLength = tMax(tAbs(dXInc), tAbs(dYInc)); - - dXInc /= dLength; - dYInc /= dLength; - - double dX = nXStart; - double dY = nYStart; - - // Process each interpolated point - for (int m = 0; m <= nRound(dLength); m++) - { - int const nX = nRound(dX); - int const nY = nRound(dY); - - // Do some checking of this interpolated point, but only if this is not a grid-edge profile (these profiles are always valid) - if (! pProfile->bIsGridEdge()) - { - // Is the interpolated point within the valid raster grid? - if (! bIsWithinValidGrid(nX, nY)) - { - // It is outside the valid grid, so mark this profile and quit the loop - bTruncatedSameCoast = true; - - if (! ACCEPT_TRUNCATED_PROFILES) - pProfile->SetTruncatedSameCoast(true); - - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter << ": profile " << nProfile << " is invalid, truncated at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; - - break; - } - - // Check again: is this cell (or an adjacent cell: does not matter which) already marked as 'under' a profile? - int nYTmp = nY+1; - if (nY+1 >= m_nYGridSize) - nYTmp = nY-1; - - if (m_pRasterGrid->m_Cell[nX][nY].bIsProfile() || m_pRasterGrid->m_Cell[nX][nYTmp].bIsProfile()) - { - // This cell or an adjacent cell, is 'under' a profile, so now check if the profile belongs to another coast - int const nHitProfileCoast1 = m_pRasterGrid->m_Cell[nX][nY].nGetProfileCoastID(); - int const nHitProfileCoast2 = m_pRasterGrid->m_Cell[nX][nYTmp].nGetProfileCoastID(); - - if ((nHitProfileCoast1 == nCoast) || (nHitProfileCoast2 == nCoast)) - { - // The profile belongs to the same coast, mark this profile as invalid - bHitAnotherProfile = true; - pProfile->SetHitAnotherProfile(true); - return; - } - } - - // If this is the first line segment of the profile, then once we are clear of the coastline (say, when m > 3), check if this profile hits land at this interpolated point. NOTE Get problems here since if the coastline vector has been heavily smoothed, this can result is 'false positives' profiles marked as invalid which are not actually invalid, because the profile hits land when m = 0 or m = 1. This results in some cells being flagged as profile cells which are actually inland - if (m > PROFILE_CHECK_DIST_FROM_COAST) - { - // Check this cell. Two diagonal(ish) raster lines can cross each other without any intersection, so must also test an adjacent cell for intersection (does not matter which adjacent cell) - if ((m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) || (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->m_Cell[nX][nY + 1].bIsCoastline())) - { - // We've hit a coastline so set a switch and mark the profile, then quit - bHitCoast = true; - pProfile->SetHitCoast(true); - int const nHitCoast = m_pRasterGrid->m_Cell[nX][nY].nGetCoastline(); - - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " is invalid, hit coast " << nHitCoast << " at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; - - return; - } - - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) - { - // We've hit dry land, so set a switch and mark the profile - bHitLand = true; - pProfile->SetHitLand(true); - - LogStream << m_ulIter << ": profile " << nProfile << " HIT LAND at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, elevation = " << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << ", SWL = " << m_dThisIterSWL << endl; - - return; - } - - if (m_pRasterGrid->m_Cell[nX][nY].nGetInterventionClass() != INT_NODATA) - { - // We've hit an intervention, so set a switch and mark the profile - bHitIntervention = true; - pProfile->SetHitIntervention(true); - - LogStream << m_ulIter << ": profile " << nProfile << " HIT INTERVENTION at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, elevation = " << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << ", SWL = " << m_dThisIterSWL << endl; - - return; - } - } - - // Now check to see if we hit another profile which is not a coincident normal to this normal - if (m_pRasterGrid->m_Cell[nX][nY].bIsProfile()) - { - // We've hit a raster cell which is already marked as 'under' a normal profile. Get the number of the profile which marked this cell, and the coast to hich this profile belongs - int const nHitProfile = m_pRasterGrid->m_Cell[nX][nY].nGetProfileID(); - int const nHitProfileCoast = m_pRasterGrid->m_Cell[nX][nY].nGetProfileCoastID(); - - // Do both profiles belong to the same coast? - if (nCoast == nHitProfileCoast) - { - // Both profiles belong to the same coast. Is this the number of a coincident profile of this profile? - if (! pProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nHitProfile)) - { - // It isn't a coincident profile, so we have just hit an unrelated profile. Mark this profile as invalid and move on - pProfile->SetHitAnotherProfile(true); - bHitAnotherProfile = true; - - return; - } - } - } - } - - // Append this point to the output vector - pVIPointsOut->push_back(CGeom2DIPoint(nX, nY)); // Is in raster grid coordinates - pbVShared->push_back(bShared); - - // And increment for next time - dX += dXInc; - dY += dYInc; - } - - nXStartLast = nXStart; - nYStartLast = nYStart; - nXEndLast = nXEnd; - nYEndLast = nYEnd; - - if (bTruncatedSameCoast) - break; - } - - if (bTruncatedSameCoast) - { - if (nSeg < (nNumSegments - 1)) - // We are truncating the profile, so remove any line segments after this one - pProfile->TruncateLineSegments(nSeg); - - // Shorten the vector input. Ignore CPPCheck errors here, since we know that pVIPointsOut is not empty - int const nLastX = pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX(); - int const nLastY = pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY(); - - pProfile->pPtGetPointInProfile(nSeg + 1)->SetX(dGridCentroidXToExtCRSX(nLastX)); - pProfile->pPtGetPointInProfile(nSeg + 1)->SetY(dGridCentroidYToExtCRSY(nLastY)); - } - - // // DEBUG CODE ===================================================================================== - // LogStream << "====================" << endl; - // LogStream << m_ulIter << ": for profile " << nProfile << " pPtiStart = [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "] pPtiEnd = [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] pVIPointsOut->size() = " << pVIPointsOut->size() << endl; - // // for (int n = 0; n < static_cast(pVIPointsOut->size()); n++) - // // LogStream << "\t[" << pVIPointsOut->at(n).nGetX() << "][" << pVIPointsOut->at(n).nGetY() << "]" << endl; - // LogStream << "====================" << endl; - // // DEBUG CODE ===================================================================================== - - if (pVIPointsOut->size() < 3) - { - // Coastline-normal profiles cannot be very short (e.g. with less than 3 cells), since we cannot calculate along-profile slope properly for such short profiles - bTooShort = true; - pProfile->SetTooShort(true); - - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - { - // Ignore CPPCheck errors here, since we know that pVIPointsOut is not empty - LogStream << m_ulIter << ": profile " << nProfile << " is invalid, is too short, only " << pVIPointsOut->size() << " points, HitLand?" << bHitLand << ". From [" << pVIPointsOut->at(0).nGetX() << "][" << pVIPointsOut->at(0).nGetY() << "] = {" << dGridCentroidXToExtCRSX(pVIPointsOut->at(0).nGetX()) << ", " << dGridCentroidYToExtCRSY(pVIPointsOut->at(0).nGetY()) << "} to [" << pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX() << "][" << pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY() << "] = {" << dGridCentroidXToExtCRSX(pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX()) << ", " << dGridCentroidYToExtCRSY(pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY()) << "}" << endl; - } - } -} - -//=============================================================================================================================== -//! Merges two profiles which intersect at their final (most seaward) line segments, seaward of their point of intersection -//=============================================================================================================================== -void CSimulation::MergeProfilesAtFinalLineSegments(int const nCoast, CGeomProfile* pFirstProfile, CGeomProfile* pSecondProfile, int const nFirstProfileLineSegments, int const nSecondProfileLineSegments, double const dIntersectX, double const dIntersectY, double const dAvgEndX, double const dAvgEndY) -{ - // The point of intersection is on the final (most seaward) line segment of both profiles. Put together a vector of coincident profile numbers (with no duplicates) for both profiles - int nCombinedLastSeg = 0; - vector> prVCombinedProfilesCoincidentProfilesLastSeg; - - for (unsigned int n = 0; n < pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->size(); n++) - { - pair prTmp; - prTmp.first = pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->at(n).first; - prTmp.second = pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->at(n).second; - - bool bFound = false; - - for (unsigned int m = 0; m < prVCombinedProfilesCoincidentProfilesLastSeg.size(); m++) - { - if (prVCombinedProfilesCoincidentProfilesLastSeg[m].first == prTmp.first) - { - bFound = true; - break; - } - } - - if (! bFound) - { - prVCombinedProfilesCoincidentProfilesLastSeg.push_back(prTmp); - nCombinedLastSeg++; - } - } - - for (unsigned int n = 0; n < pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->size(); n++) - { - pair prTmp; - prTmp.first = pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->at(n).first; - prTmp.second = pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->at(n).second; - - bool bFound = false; - - for (unsigned int m = 0; m < prVCombinedProfilesCoincidentProfilesLastSeg.size(); m++) - { - if (prVCombinedProfilesCoincidentProfilesLastSeg[m].first == prTmp.first) - { - bFound = true; - break; - } - } - - if (! bFound) - { - prVCombinedProfilesCoincidentProfilesLastSeg.push_back(prTmp); - nCombinedLastSeg++; - } - } - - // Increment the number of each line segment - for (int m = 0; m < nCombinedLastSeg; m++) - prVCombinedProfilesCoincidentProfilesLastSeg[m].second++; - - vector> prVFirstProfileCoincidentProfilesLastSeg = *pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1); - vector> prVSecondProfileCoincidentProfilesLastSeg = *pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1); - int const nNumFirstProfileCoincidentProfilesLastSeg = static_cast(prVFirstProfileCoincidentProfilesLastSeg.size()); - int const nNumSecondProfileCoincidentProfilesLastSeg = static_cast(prVSecondProfileCoincidentProfilesLastSeg.size()); - - // LogStream << m_ulIter << ": END-SEGMENT INTERSECTION between profiles " << nFirstProfile << " and " << nSecondProfile << " at line segment " << nFirstProfileLineSegments-1 << "/" << nFirstProfileLineSegments-1 << ", and line segment " << nSecondProfileLineSegments-1 << "/" << nSecondProfileLineSegments-1 << ", respectively. Both truncated at [" << dIntersectX << ", " << dIntersectY << "] then profiles {" << nFirstProfile << "} and {" << nSecondProfile << "} extended to [" << dAvgEndX << ", " << dAvgEndY << "]" << endl; - - // Truncate the first profile, and all co-incident profiles, at the point of intersection - for (int n = 0; n < nNumFirstProfileCoincidentProfilesLastSeg; n++) - { - int const nThisProfile = prVFirstProfileCoincidentProfilesLastSeg[n].first; - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - int const nProfileLength = pThisProfile->nGetProfileSize(); - - // This is the final line segment of the first 'main' profile. We are assuming that it is also the final line segment of all co-incident profiles. This is fine, altho' each profile may well have a different number of line segments landwards i.e. the number of the line segment may be different for each co-incident profile - pThisProfile->SetPointInProfile(nProfileLength - 1, dIntersectX, dIntersectY); - } - - // Truncate the second profile, and all co-incident profiles, at the point of intersection - for (int n = 0; n < nNumSecondProfileCoincidentProfilesLastSeg; n++) - { - int const nThisProfile = prVSecondProfileCoincidentProfilesLastSeg[n].first; - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - int const nProfileLength = pThisProfile->nGetProfileSize(); - - // This is the final line segment of the second 'main' profile. We are assuming that it is also the final line segment of all co-incident profiles. This is fine, altho' each profile may well have a different number of line segments landwards i.e. the number of the line segment may be different for each co-incident profile - pThisProfile->SetPointInProfile(nProfileLength - 1, dIntersectX, dIntersectY); - } - - // Append a new straight line segment to the existing line segment(s) of the first profile, and to all co-incident profiles - for (int nThisLineSeg = 0; nThisLineSeg < nNumFirstProfileCoincidentProfilesLastSeg; nThisLineSeg++) - { - int const nThisProfile = prVFirstProfileCoincidentProfilesLastSeg[nThisLineSeg].first; - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - - // Update this profile - pThisProfile->AppendPointInProfile(dAvgEndX, dAvgEndY); - - // Append details of the combined profiles - pThisProfile->AppendLineSegment(); - - for (int m = 0; m < nCombinedLastSeg; m++) - pThisProfile->AppendCoincidentProfileToLineSegments(prVCombinedProfilesCoincidentProfilesLastSeg[m]); - } - - // Append a new straight line segment to the existing line segment(s) of the second profile, and to all co-incident profiles - for (int nThisLineSeg = 0; nThisLineSeg < nNumSecondProfileCoincidentProfilesLastSeg; nThisLineSeg++) - { - int const nThisProfile = prVSecondProfileCoincidentProfilesLastSeg[nThisLineSeg].first; - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - - // Update this profile - pThisProfile->AppendPointInProfile(dAvgEndX, dAvgEndY); - - // Append details of the combined profiles - pThisProfile->AppendLineSegment(); - - for (int m = 0; m < nCombinedLastSeg; m++) - pThisProfile->AppendCoincidentProfileToLineSegments(prVCombinedProfilesCoincidentProfilesLastSeg[m]); - } - - // // DEBUG CODE **************************************************************** - // int nFirstProfileLineSeg= pFirstProfile->nGetNumLineSegments(); - // int nSecondProfileLineSeg = pSecondProfile->nGetNumLineSegments(); - // - // LogStream << "\tProfile {" << nFirstProfile << "} now has " << nFirstProfileLineSeg << " line segments" << endl; - // for (int m = 0; m < nFirstProfileLineSeg; m++) - // { - // vector > prVCoincidentProfiles = *pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(m); - // LogStream << "\tCo-incident profiles and line segments for line segment " << m << " of profile {" << nFirstProfile << "} are {"; - // for (int nn = 0; nn < prVCoincidentProfiles.size(); nn++) - // LogStream << " " << prVCoincidentProfiles[nn].first << "[" << prVCoincidentProfiles[nn].second << "] "; - // LogStream << " }" << endl; - // } - // LogStream << "\tProfile {" << nSecondProfile << "} now has " << nSecondProfileLineSeg << " line segments" << endl; - // for (int m = 0; m < nSecondProfileLineSeg; m++) - // { - // vector > prVCoincidentProfiles = *pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(m); - // LogStream << "\tCo-incident profiles and line segments for line segment " << m << " of profile {" << nSecondProfile << "} are {"; - // for (int nn = 0; nn < prVCoincidentProfiles.size(); nn++) - // LogStream << " " << prVCoincidentProfiles[nn].first << "[" << prVCoincidentProfiles[nn].second << "] "; - // LogStream << " }" << endl; - // } - // // DEBUG CODE ****************************************************************** -} - -//=============================================================================================================================== -//! Truncates one intersecting profile at the point of intersection, and retains the other profile -//=============================================================================================================================== -void CSimulation::TruncateOneProfileRetainOtherProfile(int const nCoast, CGeomProfile* pProfileToTruncate, CGeomProfile* pProfileToRetain, double dIntersectX, double dIntersectY, int nProfileToTruncateIntersectLineSeg, int nProfileToRetainIntersectLineSeg, bool const bAlreadyPresent) -{ - // // Occasionally, profiles cross each other, with the crossing not detected. So check for intersection between pProfileToTruncate and all profiles (starting from the last i.e. in reverse order) in all segments (starting from the last i.e. in reverse order) of pProfileToRetain's CGeomMultiLine - // bool bFound = false; - // int nNumSegProfRetain = pProfileToRetain->nGetNumLineSegments(); - // for (int nSeg = nNumSegProfRetain-1; nSeg >= 0; nSeg--) - // { - // if (bFound) - // break; - // - // int nNumProfInSeg = pProfileToRetain->nGetNumCoincidentProfilesInLineSegment(nSeg); - // for (int nProf = nNumProfInSeg-1; nProf >= 0; nProf--) - // { - // int nThisProf = pProfileToRetain->nGetCoincidentProfileForLineSegment(nSeg, nProf); - // CGeomProfile* pThisProf = m_VCoast[nCoast].pGetProfile(nThisProf); - // - // int nProfToTruncLineSeg = 0; - // int nThisProfLineSeg = 0; - // double dTmpIntersectX = 0; - // double dTmpIntersectY = 0; - // double dAvgEndX = 0; - // double dAvgEndY = 0; - // - // if (bCheckForIntersection(pProfileToTruncate, pThisProf, nProfToTruncLineSeg, nThisProfLineSeg, dTmpIntersectX, dTmpIntersectY, dAvgEndX, dAvgEndY)) - // { - // // An intersection was found: so the profile with which pProfileToTruncate intersects becomes the new pProfileToRetain, and dIntersectX, dIntersectY, nProfileToTruncateIntersectLineSeg, and nProfileToRetainIntersectLineSeg are also changed - // pProfileToRetain = pThisProf; - // dIntersectX = dTmpIntersectX; - // dIntersectY = dTmpIntersectY; - // nProfileToRetainIntersectLineSeg = nThisProfLineSeg; - // nProfileToTruncateIntersectLineSeg = nProfToTruncLineSeg; - // - // bFound = true; - // break; - // } - // } - // } - - // Insert the intersection point into the main retain-profile if it is not already in the profile, and do the same for all co-incident profiles of the main retain-profile. Also add details of the to-truncate profile (and all its coincident profiles) to every line segment of the main to-retain profile which is seaward of the point of intersection - int const nRet = nInsertPointIntoProfilesIfNeededThenUpdate(nCoast, pProfileToRetain, dIntersectX, dIntersectY, nProfileToRetainIntersectLineSeg, pProfileToTruncate, nProfileToTruncateIntersectLineSeg, bAlreadyPresent); - - if (nRet != RTN_OK) - { - // LogStream << m_ulIter << ": error in nInsertPointIntoProfilesIfNeededThenUpdate()" << endl; - return; - } - - // Get all profile points of the main retain-profile seawards from the intersection point, and do the same for the corresponding line segments (including coincident profiles). This also includes details of the main to-truncate profile (and all its coincident profiles) - vector PtVProfileLastPart; - vector>> prVLineSegLastPart; - - if (bAlreadyPresent) - { - PtVProfileLastPart = pProfileToRetain->PtVGetThisPointAndAllAfter(nProfileToRetainIntersectLineSeg); - prVLineSegLastPart = pProfileToRetain->prVVGetAllLineSegAfter(nProfileToRetainIntersectLineSeg); - } - - else - { - PtVProfileLastPart = pProfileToRetain->PtVGetThisPointAndAllAfter(nProfileToRetainIntersectLineSeg + 1); - prVLineSegLastPart = pProfileToRetain->prVVGetAllLineSegAfter(nProfileToRetainIntersectLineSeg + 1); - } - - // assert(PtVProfileLastPart.size() > 1); - // assert(prVLineSegLastPart.size() > 0); - - // Truncate the truncate-profile at the point of intersection, and do the same for all its co-incident profiles. Then append the profile points of the main to-retain profile seaward from the intersection point, and do the same for the corresponding line segments (including coincident profiles) - TruncateProfileAndAppendNew(nCoast, pProfileToTruncate, nProfileToTruncateIntersectLineSeg, &PtVProfileLastPart, &prVLineSegLastPart); - - // assert(m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->nGetProfileSize() > 1); - // assert(pProfileToRetain->nGetNumLineSegments() > 0); - // assert(m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->nGetNumLineSegments() > 0); -} - -//=============================================================================================================================== -//! Inserts an intersection point into the profile that is to be retained, if that point is not already present in the profile, then does the same for all co-incident profiles. Finally adds the numbers of the to-truncate profile (and all its coincident profiles) to the seaward line segments of the to-retain profile and all its coincident profiles -//=============================================================================================================================== -int CSimulation::nInsertPointIntoProfilesIfNeededThenUpdate(int const nCoast, CGeomProfile* pProfileToRetain, double const dIntersectX, double const dIntersectY, int const nProfileToRetainIntersectLineSeg, CGeomProfile* pProfileToTruncate, int const nProfileToTruncateIntersectLineSeg, bool const bAlreadyPresent) -{ - // // DEBUG CODE **************************************************************** - // // Get the index numbers of all coincident profiles for the 'main' to-retain profile for the line segment in which intersection occurred - // vector > prVRetainCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); - // int nNumRetainCoincidentCHECK1 = prVRetainCoincidentProfilesCHECK1.size(); - // for (int nn = 0; nn < nNumRetainCoincidentCHECK1; nn++) - // { - // int nThisProfile = prVRetainCoincidentProfilesCHECK1[nn].first; - // LogStream << "\tBEFORE nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-retain profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) - // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; - // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; - // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) - // { - // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); - // LogStream << "{ "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // - // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) - // { - // CGeom2DPoint - // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), - // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); - // - // if (Pt1 == Pt2) - // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; - // } - // } - // - // // Get the index numbers of all coincident profiles for the 'main' to-truncate profile for the line segment in which intersection occurred - // vector > prVTruncateCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToTruncateIntersectLineSeg); - // int nNumTruncateCoincidentCHECK1 = prVTruncateCoincidentProfilesCHECK1.size(); - // for (int nn = 0; nn < nNumTruncateCoincidentCHECK1; nn++) - // { - // int nThisProfile = prVTruncateCoincidentProfilesCHECK1[nn].first; - // LogStream << "\tBEFORE nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nProfileToTruncate ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) - // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; - // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; - // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) - // { - // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); - // LogStream << "{ "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // - // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) - // { - // CGeom2DPoint - // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), - // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); - // - // if (Pt1 == Pt2) - // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; - // } - // } - // // DEBUG CODE ****************************************************************** - - int const nProfileToRetain = pProfileToRetain->nGetProfileID(); - - // Get the index numbers of all coincident profiles for the 'main' to-retain profile for the line segment in which intersection occurs - vector> prVCoincidentProfiles = *pProfileToRetain->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); - int const nNumCoincident = static_cast(prVCoincidentProfiles.size()); - vector nLineSegAfterIntersect(nNumCoincident, -1); // The line segment after the point of intersection, for each co-incident profile - - // Do this for the main profile and all profiles which are co-incident for this line segment - for (int nn = 0; nn < nNumCoincident; nn++) - { - int const nThisProfile = prVCoincidentProfiles[nn].first; // The number of this profile - int const nThisLineSeg = prVCoincidentProfiles[nn].second; // The line segment of this profile - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - - // Is the intersection point already present in the to-retain profile? - if (! bAlreadyPresent) - { - // It is not already present, so insert it and also update the associated multi-line - if (! pThisProfile->bInsertIntersection(dIntersectX, dIntersectY, nThisLineSeg)) - { - // Error - LogStream << WARN << m_ulIter << ": cannot insert a line segment after the final line segment (" << nThisLineSeg << ") for " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " profile (" << nThisProfile << "), abandoning" << endl; - - return RTN_ERR_CANNOT_INSERT_POINT; - } - - // LogStream << "\tIntersection point NOT already in " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " profile {" << nThisProfile << "}, inserted it as point " << nThisLineSeg+1 << endl; - } - - // Get the line segment after intersection - nLineSegAfterIntersect[nn] = nThisLineSeg + 1; - } - - // for (int nn = 0; nn < nNumCoincident; nn++) - // LogStream << "\tFor profile " << prVCoincidentProfiles[nn].first << " line segment [" << nLineSegAfterIntersect[nn] << "] is immediately after the intersection point" << endl; - - // Get the coincident profiles for the to-truncate profile, at the line segment where intersection occurs - vector> prVToTruncateCoincidentProfiles = *pProfileToTruncate->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToTruncateIntersectLineSeg); - int const nNumToTruncateCoincident = static_cast(prVToTruncateCoincidentProfiles.size()); - - // Now add the number of the to-truncate profile, and all its coincident profiles, to all line segments which are seaward of the point of intersection. Do this for the main profile and all profiles which are co-incident for this line segment - for (int nn = 0; nn < nNumCoincident; nn++) - { - int const nThisProfile = prVCoincidentProfiles[nn].first; // The number of this profile - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - - // Get the number of line segments for this to-retain profile (will have just increased, if we just inserted a point) - int const nNumLineSegs = pThisProfile->nGetNumLineSegments(); - - // Do for all line segments seaward of the point of intersection - for (int nLineSeg = nLineSegAfterIntersect[nn], nIncr = 0; nLineSeg < nNumLineSegs; nLineSeg++, nIncr++) - { - // // This can happen occasionally - // if (nThisProfile == nProfileToTruncateIntersectLineSeg) - // { - // LogStream << "\t*** ERROR nThisProfile = " << nThisProfile << " nProfileToTruncateIntersectLineSeg = " << nProfileToTruncateIntersectLineSeg << ", ignoring" << endl; - // pThisProfile->SetHitAnotherProfile(true); - // continue; - // } - - // Add the number of the to-truncate profile, and all its coincident profiles, to this line segment - for (int m = 0; m < nNumToTruncateCoincident; m++) - { - int const nProfileToAdd = prVToTruncateCoincidentProfiles[m].first; - int const nProfileToAddLineSeg = prVToTruncateCoincidentProfiles[m].second; - - // LogStream << "\tAdding " << (nProfileToAdd == nProfileToTruncateIntersectLineSeg ? "main" : "co-incident") << " truncate-profile " << nProfileToAdd << ", line segment [" << nProfileToAddLineSeg + nIncr << "] to line segment " << nLineSeg << " of " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " to-retain profile " << nThisProfile << endl; - - pThisProfile->AddCoincidentProfileToExistingLineSegment(nLineSeg, nProfileToAdd, nProfileToAddLineSeg + nIncr); - } - } - } - - // // DEBUG CODE **************************************************************** - // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurred - // vector > prVCoincidentProfilesCHECK2 = *m_VCoast[nCoast].pGetProfile(nProfileToRetain)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); - // int nNumCoincidentCHECK2 = prVCoincidentProfilesCHECK2.size(); - // for (int nn = 0; nn < nNumCoincidentCHECK2; nn++) - // { - // int nThisProfile = prVCoincidentProfilesCHECK2[nn].first; - // LogStream << "\tAFTER nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nProfileToRetain ? "MAIN" : "COINCIDENT") << " to-retain profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) - // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; - // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; - // for (int nLineSeg = 0; nLineSeg < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); nLineSeg++) - // { - // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nLineSeg); - // LogStream << "{ "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(nLineSeg); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // - // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) - // { - // CGeom2DPoint - // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), - // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); - // - // if (Pt1 == Pt2) - // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; - // } - // } - // // DEBUG CODE ****************************************************************** - - return RTN_OK; -} - -//=============================================================================================================================== -//! Truncate a profile at the point of intersection, and do the same for all its co-incident profiles -//=============================================================================================================================== -void CSimulation::TruncateProfileAndAppendNew(int const nCoast, CGeomProfile* pProfileToRetain, int const nMainProfileIntersectLineSeg, vector const* pPtVProfileLastPart, vector>> const* pprVLineSegLastPart) -{ - // // DEBUG CODE **************************************************************** - // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurred - // vector > prVCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); - // int nNumCoincidentCHECK1 = prVCoincidentProfilesCHECK1.size(); - // - // LogStream << "\tTruncating profile {" << nMainProfile << "}, intersection is at [" << dIntersectX << ", " << dIntersectY << "] in line segment " << nMainProfileIntersectLineSeg << endl; - // for (int nn = 0; nn < nNumCoincidentCHECK1; nn++) - // { - // int nThisProfile = prVCoincidentProfilesCHECK1[nn].first; - // LogStream << "\tBEFORE TruncateProfileAndAppendNew(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points ("; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) - // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; - // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles are "; - // for (int nLineSeg = 0; nLineSeg < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); nLineSeg++) - // { - // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nLineSeg); - // LogStream << "{ "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(nLineSeg); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // - // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) - // { - // CGeom2DPoint - // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), - // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); - // - // if (Pt1 == Pt2) - // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; - // } - // } - // LogStream << "\tPart-profile to append is "; - // for (int mm = 0; mm < pPtVProfileLastPart->size(); mm++) - // LogStream << "[" << pPtVProfileLastPart->at(mm).dGetX() << ", " << pPtVProfileLastPart->at(mm).dGetY() << "] "; - // LogStream << endl; - // LogStream << "\tPart line-segment to append is "; - // for (int mm = 0; mm < pprVLineSegLastPart->size(); mm++) - // { - // vector > prVTmp = pprVLineSegLastPart->at(mm); - // LogStream << "{ "; - // for (int nn = 0; nn < prVTmp.size(); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // // DEBUG CODE ****************************************************************** - - // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurs - vector> prVCoincidentProfiles = *pProfileToRetain->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); - int const nNumCoincident = static_cast(prVCoincidentProfiles.size()); - - for (int nn = 0; nn < nNumCoincident; nn++) - { - // Do this for the main to-truncate profile, and do the same for all its co-incident profiles - int const nThisProfile = prVCoincidentProfiles[nn].first; - int const nThisProfileLineSeg = prVCoincidentProfiles[nn].second; - CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); - - // if (nThisProfile == nMainProfile) - // assert(nThisProfileLineSeg == nMainProfileIntersectLineSeg); - - // Truncate the profile - // LogStream << "\tTruncating " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} at line segment " << nThisProfileLineSeg+1 << endl; - pThisProfile->TruncateProfile(nThisProfileLineSeg + 1); - - // Reduce the number of line segments for this profile - pThisProfile->TruncateLineSegments(nThisProfileLineSeg + 1); - - // Append the profile points from the last part of the retain-profile - for (unsigned int mm = 0; mm < pPtVProfileLastPart->size(); mm++) - { - CGeom2DPoint const Pt = pPtVProfileLastPart->at(mm); - pThisProfile->AppendPointInProfile(&Pt); - } - - // Append the line segments, and their co-incident profile numbers, from the last part of the retain-profile - for (unsigned int mm = 0; mm < pprVLineSegLastPart->size(); mm++) - { - vector> prVTmp = pprVLineSegLastPart->at(mm); - - pThisProfile->AppendLineSegment(&prVTmp); - } - - // Fix the line seg numbers for this profile - vector nVProf; - vector nVProfsLineSeg; - - for (int nSeg = 0; nSeg < pThisProfile->nGetNumLineSegments(); nSeg++) - { - for (int nCoinc = 0; nCoinc < pThisProfile->nGetNumCoincidentProfilesInLineSegment(nSeg); nCoinc++) - { - int const nProf = pThisProfile->nGetProf(nSeg, nCoinc); - int const nProfsLineSeg = pThisProfile->nGetProfsLineSeg(nSeg, nCoinc); - - auto it = find(nVProf.begin(), nVProf.end(), nProf); - - if (it == nVProf.end()) - { - // Not found - nVProf.push_back(nProf); - nVProfsLineSeg.push_back(nProfsLineSeg); - } - - else - { - // Found - int const nPos = static_cast(it - nVProf.begin()); - int nNewProfsLineSeg = nVProfsLineSeg[nPos]; - nNewProfsLineSeg++; - - nVProfsLineSeg[nPos] = nNewProfsLineSeg; - pThisProfile->SetProfsLineSeg(nSeg, nCoinc, nNewProfsLineSeg); - } - } - } - - // assert(pThisProfile->nGetProfileSize() > 1); - } - - // // DEBUG CODE **************************************************************** - // Get the index numbers of all coincident profiles for the 'main' to-truncate profile for the line segment in which intersection occurred - // vector > prVToTruncateCoincidentProfilesCHECK2 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); - // int nNumToTruncateCoincidentCHECK2 = prVToTruncateCoincidentProfilesCHECK2.size(); - // for (int nn = 0; nn < nNumToTruncateCoincidentCHECK2; nn++) - // { - // int nThisProfile = prVToTruncateCoincidentProfilesCHECK2[nn].first; - // LogStream << "\tAFTER TruncateProfileAndAppendNew(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points ("; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) - // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; - // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles are "; - // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) - // { - // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); - // LogStream << "{ "; - // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) - // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; - // LogStream << "} "; - // } - // LogStream << endl; - // - // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) - // { - // CGeom2DPoint - // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), - // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); - // - // if (Pt1 == Pt2) - // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; - // } - // } - // // DEBUG CODE ****************************************************************** -} - +/*! + + \file create_profiles.cpp + \brief Creates profiles which are approximately normal to the coastline, these will become inter-polygon boundaries + \details TODO 001 A more detailed description of these routines. + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License + +*/ + +/* ============================================================================================================================== + + This file is part of CoastalME, the Coastal Modelling Environment. + + CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +==============================================================================================================================*/ +#include + +#include +#include +#include + +#include +using std::cerr; +using std::endl; +using std::ios; + +#include +using std::find; +using std::sort; + +#include +using std::make_pair; +using std::pair; + +#include +using std::normal_distribution; + +#include "cme.h" +#include "simulation.h" +#include "coast.h" +#include "2d_point.h" +#include "2di_point.h" + +namespace +{ +//=============================================================================================================================== +//! Function used to sort coastline curvature values when locating start points of normal profiles +//=============================================================================================================================== +bool bCurvaturePairCompareDescending(const pair& prLeft, const pair& prRight) +{ + // Sort in descending order (i.e. most concave first) + return prLeft.second > prRight.second; +} +} // namespace + +//=============================================================================================================================== +//! Create coastline-normal profiles for all coastlines. The first profiles are created 'around' the most concave bits of coast. Also create 'special' profiles at the start and end of the coast, and put these onto the raster grid +//=============================================================================================================================== +int CSimulation::nCreateAllProfiles(void) +{ + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << endl << m_ulIter << ": Creating profiles" << endl; + + for (unsigned int nCoast = 0; nCoast < m_VCoast.size(); nCoast++) + { + int nProfile = 0; + int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); + + // Create a bool vector to mark coast points which have been searched + vector bVCoastPointDone(nCoastSize, false); + + // Now create a vector of pairs: the first value of the pair is the coastline point, the second is the coastline's curvature at that point + vector> prVCurvature; + + for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) + { + double dCurvature; + + if (m_VCoast[nCoast].pGetCoastLandform(nCoastPoint)->nGetLandFormCategory() != LF_CAT_INTERVENTION) + { + // Not an intervention coast point, so store the smoothed curvature + dCurvature = m_VCoast[nCoast].dGetSmoothCurvature(nCoastPoint); + } + + else + { + // This is an intervention coast point, which is likely to have some sharp angles. So store the detailed curvature + dCurvature = m_VCoast[nCoast].dGetDetailedCurvature(nCoastPoint); + } + + prVCurvature.push_back(make_pair(nCoastPoint, dCurvature)); + } + + // Sort this pair vector in descending order, so that the most convex curvature points are first + sort(prVCurvature.begin(), prVCurvature.end(), bCurvaturePairCompareDescending); + + // // DEBUG CODE ======================================================================================================================= + // for (int n = 0; n < prVCurvature.size(); n++) + // { + // LogStream << prVCurvature[n].first << "\t" << prVCurvature[n].second << endl; + // } + // LogStream << endl << endl; + // // DEBUG CODE ======================================================================================================================= + + // And mark points at and near the start and end of the coastline so that they don't get searched (will be creating 'special' start- and end-of-coast profiles at these end points later) + for (int n = 0; n < m_nCoastNormalSpacing; n++) + { + if (n < nCoastSize) + bVCoastPointDone[n] = true; + + int const m = nCoastSize - n - 1; + + if (m >= 0) + bVCoastPointDone[m] = true; + } + + // Now locate the start points for all coastline-normal profiles (except the grid-edge ones), at points of maximum convexity. Then create the profiles + LocateAndCreateProfiles(nCoast, nProfile, &bVCoastPointDone, &prVCurvature); + + // Did we fail to create any normal profiles? If so, quit + if (nProfile < 0) + { + string strErr = ERR + "timestep " + strDblToStr(m_ulIter) + ": could not create profiles for coastline " + strDblToStr(nCoast); + + if (m_ulIter == 1) + strErr += ". Check the SWL"; + + strErr += "\n"; + + cerr << strErr; + LogStream << strErr; + + return RTN_ERR_NO_PROFILES_1; + } + + // Locate and create a 'special' profile at the grid edge, first at the beginning of the coastline. Then put this onto the raster grid + int nRet = nLocateAndCreateGridEdgeProfile(true, nCoast, nProfile); + + if (nRet != RTN_OK) + return nRet; + + // Locate a second 'special' profile at the grid edge, this time at end of the coastline. Then put this onto the raster grid + nRet = nLocateAndCreateGridEdgeProfile(false, nCoast, ++nProfile); + + if (nRet != RTN_OK) + return nRet; + + // Insert pointers to profiles at coastline points in the profile-all-coastpoint index + m_VCoast[nCoast].InsertProfilesInProfileCoastPointIndex(); + + // // DEBUG CODE =================================================================================================== + // LogStream << endl << "===========================================================================================" << endl; + // LogStream << "PROFILES BEFORE ADDING BEFORE- AND AFTER-PROFILE NUMBERS" << endl; + // int nNumProfiles = m_VCoast[nCoast].nGetNumProfiles(); + // for (int nn = 0; nn < nNumProfiles; nn++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nn); + // + // LogStream << nn << " nCoastID = " << pProfile->nGetProfileID() << " nGlobalID = " << pProfile->nGetProfileID() << " nGetCoastPoint = " << pProfile->nGetCoastPoint() << " pGetUpCoastAdjacentProfile = " << pProfile->pGetUpCoastAdjacentProfile() << " pGetDownCoastAdjacentProfile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; + // } + // LogStream << "===================================================================================================" << endl << endl; + // // DEBUG CODE =================================================================================================== + + CGeomProfile* pLastProfile; + CGeomProfile* pThisProfile; + + // Go along the coastline and give each profile the number of the adjacent up-coast profile and the adjacent down-coast profile + for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) + { + if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nCoastPoint)) + { + pThisProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nCoastPoint); + // nThisProfile = pThisProfile->nGetProfileID(); + + if (nCoastPoint == 0) + { + pThisProfile->SetUpCoastAdjacentProfile(NULL); + + // LogStream << "nCoastPoint = " << nCoastPoint << " ThisProfile = " << nThisProfile << " ThisProfile UpCoast = " << pThisProfile->pGetUpCoastAdjacentProfile() << " ThisProfile DownCoast = " << pThisProfile->pGetDownCoastAdjacentProfile() << endl; + + pLastProfile = pThisProfile; + // nLastProfile = nThisProfile; + continue; + } + + pLastProfile->SetDownCoastAdjacentProfile(pThisProfile); + pThisProfile->SetUpCoastAdjacentProfile(pLastProfile); + + if (nCoastPoint == nCoastSize - 1) + pThisProfile->SetDownCoastAdjacentProfile(NULL); + + pLastProfile = pThisProfile; + } + } + + // And create an index to this coast's profiles in along-coastline sequence + m_VCoast[nCoast].CreateProfileDownCoastIndex(); + + // // DEBUG CODE ======================================================================================================================= + // for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); + // CGeomProfile* pUpCoastProfile = pProfile->pGetUpCoastAdjacentProfile(); + // CGeomProfile* pDownCoastProfile = pProfile->pGetDownCoastAdjacentProfile(); + // int nUpCoastProfile = INT_NODATA; + // int nDownCoastProfile = INT_NODATA; + // if (pUpCoastProfile != 0) + // nUpCoastProfile = pUpCoastProfile->nGetProfileID(); + // if (pDownCoastProfile != 0) + // nDownCoastProfile = pDownCoastProfile->nGetProfileID(); + // LogStream << "nCoastID = " << pProfile->nGetProfileID() << "\t up-coast profile = " << nUpCoastProfile << "\t down-coast profile = " << nDownCoastProfile << endl; + // } + // LogStream << endl; + // // DEBUG CODE ======================================================================================================================= + + // // DEBUG CODE ======================================================================================================================= + // int nProf = 0; + // for (int n = 0; n < nCoastSize; n++) + // { + // // LogStream << n << "\t"; + // + // // LogStream << m_VCoast[nCoast].dGetDetailedCurvature(n) << "\t"; + // // + // // LogStream << m_VCoast[nCoast].dGetSmoothCurvature(n) << "\t"; + // // + // // if (m_VCoast[nCoast].pGetCoastLandform(n)->nGetLandFormCategory() == LF_CAT_INTERVENTION) + // // LogStream << "I\t"; + // + // if (m_VCoast[nCoast].bIsProfileAtCoastPoint(n)) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(n); + // + // LogStream << "profile " << pProfile->nGetProfileID() << " at coast point " << n << " adjacent up-coast profile = " << pProfile->pGetUpCoastAdjacentProfile() << " adjacent down-coast profile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; + // + // nProf++; + // } + // } + // LogStream << endl; + // LogStream << "nProf = " << nProf << endl; + // // DEBUG CODE ======================================================================================================================= + + // // DEBUG CODE ======================================================================================================================= + // LogStream << "=====================" << endl; + // for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); + // int nStartPoint = pProfile->nGetCoastPoint(); + // + // LogStream << n << "\t nCoastID = " << pProfile->nGetProfileID() << "\tnStartPoint = " << nStartPoint << endl; + // } + // LogStream << endl; + // LogStream << "=====================" << endl; + // // DEBUG CODE ======================================================================================================================= + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! For a single coastline, locate the start points for all coastline-normal profiles (except the grid-edge profiles). Then create the profiles +//=============================================================================================================================== +void CSimulation::LocateAndCreateProfiles(int const nCoast, int& nProfile, vector* pbVCoastPointDone, vector> const* prVCurvature) +{ + int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); + + // Work along the vector of curvature pairs starting at the convex end + for (int n = nCoastSize - 1; n >= 0; + n--) + { + // Have we searched all the coastline points? + int nStillToSearch = 0; + + for (int m = 0; m < nCoastSize; m++) + if (! pbVCoastPointDone->at(m)) + nStillToSearch++; + + if (nStillToSearch == 0) + // OK we are done here + return; + + // This convex point on the coastline is a potential location for a normal + int const nNormalPoint = prVCurvature->at(n).first; + + // Ignore each end of the coastline + if ((nNormalPoint == 0) || (nNormalPoint == nCoastSize - 1)) + continue; + + // TODO 089 When choosing locations for profiles, do coast first then interventions + + if (! pbVCoastPointDone->at(nNormalPoint)) + { + // We have not already searched this coast point. Is it an intervention coast point? + bool bIntervention = false; + + if (m_VCoast[nCoast].pGetCoastLandform(nNormalPoint)->nGetLandFormCategory() == LF_CAT_INTERVENTION) + { + // It is an intervention + bIntervention = true; + } + + CGeom2DIPoint const PtiThis = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nNormalPoint); + + // Create a profile here + int const nRet = nCreateProfile(nCoast, nCoastSize, nNormalPoint, nProfile, bIntervention, &PtiThis); + + // // DEBUG CODE ================= + // LogStream << "After nCreateProfile() ===========" << endl; + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); + // LogStream << pProfile->nGetProfileID() << "\t"; + // + // int nPointsInProfile = pProfile->nGetProfileSize(); + // + // for (int nPoint = 0; nPoint < nPointsInProfile; nPoint++) + // { + // CGeom2DPoint Pt = *pProfile->pPtGetPointInProfile(nPoint); + // LogStream << " {" << Pt.dGetX() << ", " << Pt.dGetY() << "}"; + // } + // LogStream << endl << "===========" << endl; + // // DEBUG CODE ================= + + // Mark this coast point as searched + pbVCoastPointDone->at(nNormalPoint) = true; + + if (nRet != RTN_OK) + { + // This potential profile is no good (has hit coast, or hit dry land, etc.) so forget about it + // LogStream << "Profile is no good" << endl; + continue; + } + + // // DEBUG CODE =================================================================================================== + // LogStream << endl << "===========================================================================================" << endl; + // LogStream << "PROFILES JUST AFTER CREATION" << endl; + // int nNumProfiles = m_VCoast[nCoast].nGetNumProfiles(); + // for (int nn = 0; nn < nNumProfiles; nn++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nn); + // + // LogStream << nn << " nCoastID = " << pProfile->nGetProfileID() << " nGlobalID = " << pProfile->nGetProfileID() << " nGetCoastPoint = " << pProfile->nGetCoastPoint() << " pGetUpCoastAdjacentProfile = " << pProfile->pGetUpCoastAdjacentProfile() << " pGetDownCoastAdjacentProfile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; + // } + // LogStream << "===================================================================================================" << endl << endl; + // // DEBUG CODE =================================================================================================== + // + // // DEBUG CODE =================================================================================================== + // LogStream << "++++++++++++++++++++++" << endl; + // LogStream << endl << "Just created profile " << nProfile << endl; + // int nProf = 0; + // for (int nnn = 0; nnn < nCoastSize; nnn++) + // { + // if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nnn)) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nnn); + // + // LogStream << "profile " << pProfile->nGetProfileID() << " at coast point " << nnn << " adjacent up-coast profile = " << pProfile->pGetUpCoastAdjacentProfile() << " adjacent down-coast profile = " << pProfile->pGetDownCoastAdjacentProfile() << endl; + // + // nProf++; + // } + // } + // LogStream << endl; + // LogStream << "nProf = " << nProf << endl; + // LogStream << "++++++++++++++++++++++" << endl; + // // DEBUG CODE =================================================================================================== + + // CGeom2DPoint PtThis = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nNormalPoint); + // if (m_nLogFileDetail >= LOG_FILE_ALL) + // LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " created at coast point " << nNormalPoint << " [" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] = {" << PtThis.dGetX() << ", " << PtThis.dGetY() << "} (smoothed curvature = " << m_VCoast[nCoast].dGetSmoothCurvature(nNormalPoint) << ", detailed curvature = " << m_VCoast[nCoast].dGetDetailedCurvature(nNormalPoint) << ")" << endl; + + // // DEBUG CODE ================================================================================= + // if (m_pRasterGrid->Cell(PtiThis.nGetX(), PtiThis.nGetY()).bIsCoastline()) + // LogStream << m_ulIter << ": cell[" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] IS coastline, coast number = " << m_pRasterGrid->Cell(PtiThis.nGetX(), PtiThis.nGetY()).nGetCoastline() << endl; + // else + // LogStream << m_ulIter << ": ******* cell[" << PtiThis.nGetX() << "][" << PtiThis.nGetY() << "] IS NOT coastline" << endl; + // // DEBUG CODE ================================================================================= + + // This profile is fine + nProfile++; + + // We need to mark points on either side of this profile so that we don't get profiles which are too close together. However best-placed profiles on narrow intervention structures may need to be quite closes + double dNumToMark = m_nCoastNormalSpacing; + + if (bIntervention) + dNumToMark = m_nCoastNormalInterventionSpacing; + + // If we have a random factor for profile spacing, then modify the profile spacing + if (m_dCoastNormalRandSpacingFactor > 0) + { + // Draw a sample from the unit normal distribution using random number generator 0 + double const dRand = m_dGetFromUnitNormalDist(m_Rand[0]); + + double const dTmp = dRand * m_dCoastNormalRandSpacingFactor * dNumToMark; + dNumToMark += dTmp; + + // Make sure number to mark is not too small or too big TODO 011 + if (bIntervention) + { + dNumToMark = tMin(dNumToMark, m_nCoastNormalInterventionSpacing * 0.75); + dNumToMark = tMax(dNumToMark, m_nCoastNormalInterventionSpacing * 1.25); + } + + else + { + dNumToMark = tMin(dNumToMark, m_nCoastNormalSpacing * 0.75); + dNumToMark = tMax(dNumToMark, m_nCoastNormalSpacing * 1.25); + } + + // TODO 014 Assume that the above is the profile spacing on straight bits of coast. Try gradually increasing the profile spacing with increasing concavity, and decreasing the profile spacing with increasing convexity. Could use a Michaelis-Menten S-curve relationship for this i.e. + // double fReN = pow(NowCell[nX][nY].dGetReynolds(m_dNu), m_dDepN); + // double fC1 = m_dC1Laminar - ((m_dC1Diff * fReN) / (fReN + m_dReMidN)); + } + + // Mark points on either side of the profile + for (int m = 1; m < dNumToMark; m++) + { + int nTmpPoint = nNormalPoint + m; + + if (nTmpPoint < nCoastSize) + pbVCoastPointDone->at(nTmpPoint) = true; + + nTmpPoint = nNormalPoint - m; + + if (nTmpPoint >= 0) + pbVCoastPointDone->at(nTmpPoint) = true; + } + } + } +} + +//=============================================================================================================================== +//! Creates a single coastline-normal profile (which may be an intervention profile) +//=============================================================================================================================== +int CSimulation::nCreateProfile(int const nCoast, int const nCoastSize, int const nProfileStartPoint, int const nProfile, bool const bIntervention, CGeom2DIPoint const* pPtiStart) +{ + // OK, we have flagged the start point of this new coastline-normal profile, so create it. Make the start of the profile the centroid of the actual cell that is marked as coast (not the cell under the smoothed vector coast, they may well be different) + CGeom2DPoint PtStart; // In external CRS + PtStart.SetX(dGridCentroidXToExtCRSX(pPtiStart->nGetX())); + PtStart.SetY(dGridCentroidYToExtCRSY(pPtiStart->nGetY())); + + CGeom2DPoint PtEnd; // In external CRS + CGeom2DIPoint PtiEnd; // In grid CRS + int const nRet = nGetCoastNormalEndPoint(nCoast, nProfileStartPoint, nCoastSize, &PtStart, m_dCoastNormalLength, &PtEnd, &PtiEnd, bIntervention); + if (nRet == RTN_ERR_NO_SOLUTION_FOR_ENDPOINT) + { + // Could not solve end-point equation, so forget about this profile + return nRet; + } + + int const nXEnd = PtiEnd.nGetX(); + int const nYEnd = PtiEnd.nGetY(); + + // Safety check: is the end point in the contiguous sea? + if (! m_pRasterGrid->Cell(nXEnd, nYEnd).bIsInContiguousSea()) + { + // if (m_nLogFileDetail >= LOG_FILE_ALL) + // LogStream << m_ulIter << ": coast " << nCoast << ", possible profile with start point " << nProfileStartPoint << " has inland end point at [" << nXEnd << "][" << nYEnd << "] = {" << dGridCentroidXToExtCRSX(nXEnd) << ", " << dGridCentroidYToExtCRSY(nYEnd) << "}, ignoring" << endl; + + return RTN_ERR_PROFILE_ENDPOINT_IS_INLAND; + } + + // Safety check: is the water depth at the end point less than the depth of closure? + if (m_pRasterGrid->Cell(nXEnd, nYEnd).dGetSeaDepth() < m_dDepthOfClosure) + { + // if (m_nLogFileDetail >= LOG_FILE_ALL) + // LogStream << m_ulIter << ": coast " << nCoast << ", possible profile with start point " << nProfileStartPoint << " is too short for depth of closure " << m_dDepthOfClosure << " at end point [" << nXEnd << "][" << nYEnd << "] = {" << dGridCentroidXToExtCRSX(nXEnd) << ", " << dGridCentroidYToExtCRSY(nYEnd) << "}, ignoring" << endl; + + return RTN_ERR_PROFILE_END_INSUFFICIENT_DEPTH; + } + + // No problems, so create the new profile + CGeomProfile* pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, bIntervention); + + // And create the profile's coastline-normal vector. Only two points (start and end points, both external CRS) are stored + vector VNormal; + VNormal.push_back(PtStart); + VNormal.push_back(PtEnd); + + // Set the start and end points (external CRS) of the profile + pProfile->SetPointsInProfile(&VNormal); + + // Create the profile's CGeomMultiLine then set nProfile as the only co-incident profile of the only line segment + pProfile->AppendLineSegment(); + pProfile->AppendCoincidentProfileToLineSegments(make_pair(nProfile, 0)); + + // Save the profile, note that several fields in the profile are still blank + m_VCoast[nCoast].AppendProfile(pProfile); + + // // DEBUG CODE ================= + // LogStream << "in nCreateProfile() ===========" << endl; + // // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); + // LogStream << pProfile->nGetProfileID() << "\t"; + // + // int nPointsInProfile = pProfile->nGetProfileSize(); + // + // for (int nPoint = 0; nPoint < nPointsInProfile; nPoint++) + // { + // CGeom2DPoint Pt = *pProfile->pPtGetPointInProfile(nPoint); + // LogStream << " {" << Pt.dGetX() << ", " << Pt.dGetY() << "}"; + // } + // LogStream << endl << "===========" << endl; + // // DEBUG CODE ================= + + // assert(pProfile->nGetProfileSize() > 0); + + LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " created at coast point " << nProfileStartPoint << " from [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "] = {" << PtStart.dGetX() << ", " << PtStart.dGetY() << "} to [" << PtiEnd.nGetX() << "][" << PtiEnd.nGetY() << "] = {" << PtEnd.dGetX() << ", " << PtEnd.dGetY() << "}" << (pProfile->bIsIntervention() ? ", from intervention" : "") << endl; + + return RTN_OK; +} + +//=============================================================================================================================== +//! Creates a 'special' profile at each end of a coastline, at the edge of the raster grid. This profile is not necessarily normal to the coastline since it goes along the grid's edge +//=============================================================================================================================== +int CSimulation::nLocateAndCreateGridEdgeProfile(bool const bCoastStart, int const nCoast, int& nProfile) +{ + int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); + int const nHandedness = m_VCoast[nCoast].nGetSeaHandedness(); + int const nProfileLen = nRound(m_dCoastNormalLength / m_dCellSide); // Profile length in grid CRS + int nProfileStartEdge; + + CGeom2DIPoint PtiProfileStart; // In grid CRS + vector VPtiNormalPoints; // In grid CRS + + if (bCoastStart) + { + // At start of coast + PtiProfileStart = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(0); // Grid CRS + nProfileStartEdge = m_VCoast[nCoast].nGetStartEdge(); + } + else + { + // At end of coast + PtiProfileStart = *m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastSize - 1); // Grid CRS + nProfileStartEdge = m_VCoast[nCoast].nGetEndEdge(); + } + + VPtiNormalPoints.push_back(PtiProfileStart); + + // Find the start cell in the list of edge cells + auto it = find(m_VEdgeCell.begin(), m_VEdgeCell.end(), PtiProfileStart); + + if (it == m_VEdgeCell.end()) + { + // Not found. This can happen because of rounding problems, i.e. the cell which was stored as the first cell of the raster coastline + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter << ": " << ERR << " when constructing start-of-coast profile, [" << PtiProfileStart.nGetX() << "][" << PtiProfileStart.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiProfileStart.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiProfileStart.nGetY()) << "} not found in list of edge cells" << endl; + + return RTN_ERR_COAST_CANT_FIND_EDGE_CELL; + } + + // Found + int nPos = static_cast(it - m_VEdgeCell.begin()); + + // Now construct the edge profile, searching for edge cells + for (int n = 0; n < nProfileLen; n++) + { + if (bCoastStart) + { + // At start of coast + if (nHandedness == LEFT_HANDED) + { + // The list of edge cells is in clockwise sequence, go in this direction + nPos++; + + if (nPos >= static_cast(m_VEdgeCell.size())) + { + // We've reached the end of the list of edge cells before the profile is long enough. OK, we can live with this + break; + } + } + else // Right-handed + { + // The list of edge cells is in clockwise sequence, go in the opposite direction + nPos--; + + if (nPos < 0) + { + // We've reached the beginning of the list of edge cells before the profile is long enough. OK, we can live with this + break; + } + } + } + else + { + // At end of coast + if (nHandedness == LEFT_HANDED) + { + // The list of edge cells is in clockwise sequence, go in the opposite direction + nPos--; + + if (nPos < 0) + { + // We've reached the beginning of the list of edge cells before the profile is long enough. OK, we can live with this + break; + } + } + else // Right-handed + { + // The list of edge cells is in clockwise sequence, go in this direction + nPos++; + + if (nPos >= static_cast(m_VEdgeCell.size())) + { + // We've reached the end of the list of edge cells before the profile is long enough. OK, we can live with this + break; + } + } + } + + if (m_VEdgeCellEdge[nPos] != nProfileStartEdge) + { + // We've reached the end of a grid side before the profile is long enough. OK, we can live with this + break; + } + + // All OK, so append this grid-edge cell, making sure that there is no gap between this and the previously-appended cell (if there is, will get problems with cell-by-cell fill) + AppendEnsureNoGap(&VPtiNormalPoints, &m_VEdgeCell[nPos]); + } + + int nProfileStartPoint; + CGeomProfile* pProfile; + CGeom2DIPoint const PtiDummy(INT_NODATA, INT_NODATA); + + if (bCoastStart) + { + nProfileStartPoint = 0; + + // Create the new start-of-coast profile + pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, false); + + // Mark this as a start-of-coast profile + pProfile->SetStartOfCoast(true); + } + else + { + nProfileStartPoint = nCoastSize - 1; + + // Create the new end-of-coast profile + pProfile = new CGeomProfile(nCoast, nProfileStartPoint, nProfile, false); + + // Mark this as an end-of-coast profile + pProfile->SetEndOfCoast(true); + } + + // Create the list of cells 'under' this grid-edge profile. Note that more than two cells are stored + for (unsigned int n = 0; n < VPtiNormalPoints.size(); n++) + { + int const nX = VPtiNormalPoints[n].nGetX(); + int const nY = VPtiNormalPoints[n].nGetY(); + + // Mark each cell in the raster grid + m_pRasterGrid->Cell(nX, nY).SetCoastAndProfileID(nCoast, nProfile); + + // Store the raster grid coordinates in the profile object + pProfile->AppendCellInProfile(nX, nY); + + CGeom2DPoint const Pt(dGridCentroidXToExtCRSX(nX), dGridCentroidYToExtCRSY(nY)); // In external CRS + + // Store the external coordinates in the profile object. Note that for this grid-edge profile, the coordinates of the cells and the coordinates of points on the profile itself are identical, this is not the case for ordinary profiles + pProfile->AppendPointInProfile(&Pt); + } + + int const nEndX = VPtiNormalPoints.back().nGetX(); + int const nEndY = VPtiNormalPoints.back().nGetY(); + + // Get the deep water wave height and orientation values at the end of the profile + double const dDeepWaterWaveHeight = m_pRasterGrid->Cell(nEndX, nEndY).dGetCellDeepWaterWaveHeight(); + double const dDeepWaterWaveAngle = m_pRasterGrid->Cell(nEndX, nEndY).dGetCellDeepWaterWaveAngle(); + double const dDeepWaterWavePeriod = m_pRasterGrid->Cell(nEndX, nEndY).dGetCellDeepWaterWavePeriod(); + + // And store them in this profile + pProfile->SetProfileDeepWaterWaveHeight(dDeepWaterWaveHeight); + pProfile->SetProfileDeepWaterWaveAngle(dDeepWaterWaveAngle); + pProfile->SetProfileDeepWaterWavePeriod(dDeepWaterWavePeriod); + + // Create the profile's CGeomMultiLine then set nProfile as the only co-incident profile of the only line segment + pProfile->AppendLineSegment(); + pProfile->AppendCoincidentProfileToLineSegments(make_pair(nProfile, 0)); + + // Store the grid-edge profile + m_VCoast[nCoast].AppendProfile(pProfile); + m_VCoast[nCoast].SetProfileAtCoastPoint(nProfileStartPoint, pProfile); + + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": coast " << nCoast << " grid-edge profile " << nProfile << " created at coast " << (bCoastStart ? "start" : "end") << " point " << (bCoastStart ? 0 : nCoastSize - 1) << ", from [" << PtiProfileStart.nGetX() << "][" << PtiProfileStart.nGetY() << "] = {" << dGridCentroidXToExtCRSX(PtiProfileStart.nGetX()) << ", " << dGridCentroidYToExtCRSY(PtiProfileStart.nGetY()) << "} to [" << VPtiNormalPoints.back().nGetX() << "][" << VPtiNormalPoints.back().nGetY() << "] = {" << dGridCentroidXToExtCRSX(VPtiNormalPoints.back().nGetX()) << ", " << dGridCentroidYToExtCRSY(VPtiNormalPoints.back().nGetY()) << "}" << endl; + + // assert(pProfile->nGetProfileSize() > 0); + + return RTN_OK; +} + +//=============================================================================================================================== +//! Finds the end point of a coastline-normal line, given the start point on the vector coastline. If however the start point is on the grid edge (only applicable to cliff collapse [rofiles), then the end point is also on the grid edge, and the line joining the start and end points is not necessarily normal to the vector coast. All input coordinates are in the external CRS +//=============================================================================================================================== +int CSimulation::nGetCoastNormalEndPoint(int const nCoast, int const nStartCoastPoint, int const nCoastSize, CGeom2DPoint const* pPtStart, double const dLineLength, CGeom2DPoint* pPtEnd, CGeom2DIPoint* pPtiEnd, bool const bIntervention) +{ + int const AVGSIZE = 21; // TODO 011 This should be a user input + + double dXEnd1 = 0; + double dXEnd2 = 0; + double dYEnd1 = 0; + double dYEnd2 = 0; + + CGeom2DPoint PtBefore; + CGeom2DPoint PtAfter; + + if (bIntervention) + { + // This is an intervention profile, so just use one point on either side (coordinates in external CRS). TODO Note this this assumes that this intervention profile is not at the start or end of the coastline + PtBefore = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nStartCoastPoint - 1); + PtAfter = *m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nStartCoastPoint + 1); + } + else + { + // This is not an intervention profile. It could be a cliff collapse profile, which could be a grid-edge profile + double const dXStart = pPtStart->dGetX(); + double const dYStart = pPtStart->dGetY(); + + int const nXStart = nRound(dExtCRSXToGridX(dXStart)); + int const nYStart = nRound(dExtCRSYToGridY(dYStart)); + int const nLineLength = nConvertMetresToNumCells(dLineLength); + + // LogStream << nXStart << ", " << nYStart << endl; + if ((nXStart == 0) || (nXStart == m_nXGridSize-1)) + { + // Yes it is a grid-edge profile + dXEnd1 = dGridXToExtCRSX(nXStart); + dXEnd2 = dXEnd1; + + dYEnd1 = dGridYToExtCRSY(nYStart + nLineLength); + dYEnd2 = dGridYToExtCRSY(nYStart - nLineLength); + } + else if ((nYStart == 0) || (nYStart == m_nYGridSize-1)) + { + // Yes it is a grid-edge profile + dYEnd1 = dGridYToExtCRSY(nYStart); + dYEnd2 = dYEnd1; + + dXEnd1 = dGridXToExtCRSX(nXStart + nLineLength); + dXEnd2 = dGridXToExtCRSX(nXStart - nLineLength); + } + else + { + // This is not a grid-edge profile, so put a maximum of AVGSIZE points before the start point into a vector + vector VPtBeforeToAverage; + + for (int n = 1; n <= AVGSIZE; n++) + { + int const nPoint = nStartCoastPoint - n; + if (nPoint < 0) + break; + + VPtBeforeToAverage.push_back(*m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nPoint)); + } + + // Put a maximum of AVGSIZE points after the start point into a vector + vector VPtAfterToAverage; + + for (int n = 1; n <= AVGSIZE; n++) + { + int const nPoint = nStartCoastPoint + n; + if (nPoint > nCoastSize - 1) + break; + + VPtAfterToAverage.push_back(*m_VCoast[nCoast].pPtGetCoastlinePointExtCRS(nPoint)); + } + + // Now average each of these vectors of points: results are in PtBefore and PtAfter (coordinates in external CRS) + PtBefore = PtAverage(&VPtBeforeToAverage); + PtAfter = PtAverage(&VPtAfterToAverage); + + // Get the y = a * x + b equation of the straight line linking the coastline points before and after 'this' coastline point. For this linking line, slope a = (y2 - y1) / (x2 - x1) + double const dYDiff = PtAfter.dGetY() - PtBefore.dGetY(); + double const dXDiff = PtAfter.dGetX() - PtBefore.dGetX(); + + if (bFPIsEqual(dYDiff, 0.0, TOLERANCE)) + { + // The linking line runs W-E or E-W, so a straight line at right angles to this runs N-S or S-N. Calculate the two possible end points for this coastline-normal profile + dXEnd1 = dXEnd2 = pPtStart->dGetX(); + dYEnd1 = pPtStart->dGetY() + dLineLength; + dYEnd2 = pPtStart->dGetY() - dLineLength; + } + else if (bFPIsEqual(dXDiff, 0.0, TOLERANCE)) + { + // The linking line runs N-S or S-N, so a straight line at right angles to this runs W-E or E-W. Calculate the two possible end points for this coastline-normal profile + dYEnd1 = dYEnd2 = pPtStart->dGetY(); + dXEnd1 = pPtStart->dGetX() + dLineLength; + dXEnd2 = pPtStart->dGetX() - dLineLength; + } + else + { + // The linking line runs neither W-E nor N-S so we have to work a bit harder to find the end-point of the coastline-normal profile + double const dA = dYDiff / dXDiff; + + // Now calculate the equation of the straight line which is perpendicular to this linking line + double const dAPerp = -1 / dA; + double const dBPerp = pPtStart->dGetY() - (dAPerp * pPtStart->dGetX()); + + // Calculate the end point of the profile: first do some substitution then rearrange as a quadratic equation i.e. in the form Ax^2 + Bx + C = 0 (see http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle) + double const dQuadA = 1 + (dAPerp * dAPerp); + double const dQuadB = 2 * ((dBPerp * dAPerp) - (dAPerp * pPtStart->dGetY()) - pPtStart->dGetX()); + double const dQuadC = ((pPtStart->dGetX() * pPtStart->dGetX()) + (pPtStart->dGetY() * pPtStart->dGetY()) + (dBPerp * dBPerp) - (2 * pPtStart->dGetY() * dBPerp) - (dLineLength * dLineLength)); + + // Solve for x and y using the quadratic formula x = (−B ± sqrt(B^2 − 4AC)) / 2A + double const dDiscriminant = (dQuadB * dQuadB) - (4 * dQuadA * dQuadC); + + if (dDiscriminant < 0) + { + LogStream << ERR << "timestep " << m_ulIter << ": discriminant < 0 when finding profile end point on coastline " << nCoast << ", from coastline point " << nStartCoastPoint << "), ignored" << endl; + return RTN_ERR_NO_SOLUTION_FOR_ENDPOINT; + } + + dXEnd1 = (-dQuadB + sqrt(dDiscriminant)) / (2 * dQuadA); + dYEnd1 = (dAPerp * dXEnd1) + dBPerp; + dXEnd2 = (-dQuadB - sqrt(dDiscriminant)) / (2 * dQuadA); + dYEnd2 = (dAPerp * dXEnd2) + dBPerp; + } + } + } + + // We have two possible solutions, so decide which of the two endpoints to use then create the profile end-point (coordinates in external CRS) + int const nSeaHand = m_VCoast[nCoast].nGetSeaHandedness(); // Assumes handedness is either 0 or 1 (i.e. not -1) + *pPtEnd = PtChooseEndPoint(nSeaHand, &PtBefore, &PtAfter, dXEnd1, dYEnd1, dXEnd2, dYEnd2); + + // Check that pPtiEnd is not off the grid. Note that pPtiEnd is not necessarily a cell centroid + pPtiEnd->SetXY(nRound(dExtCRSXToGridX(pPtEnd->dGetX())), nRound(dExtCRSYToGridY(pPtEnd->dGetY()))); + + if (! bIsWithinValidGrid(pPtiEnd)) + { + // LogStream << m_ulIter << ": profile endpoint is outside grid [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}. The profile starts at coastline point " << nStartCoastPoint << " = {" << pPtStart->dGetX() << ", " << pPtStart->dGetY() << "}" << endl; + + // The end point is off the grid, so constrain it to be within the valid grid + CGeom2DIPoint const PtiStart(nRound(dExtCRSXToGridX(pPtStart->dGetX())), nRound(dExtCRSYToGridY(pPtStart->dGetY()))); + KeepWithinValidGrid(&PtiStart, pPtiEnd); + + pPtEnd->SetX(dGridCentroidXToExtCRSX(pPtiEnd->nGetX())); + pPtEnd->SetY(dGridCentroidYToExtCRSY(pPtiEnd->nGetY())); + + LogStream << m_ulIter << ": profile endpoint constrained to be within grid, is now [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}. The profile starts at coastline point " << nStartCoastPoint << " = {" << pPtStart->dGetX() << ", " << pPtStart->dGetY() << "}" << endl; + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! Choose which end point to use for the coastline-normal profile +//=============================================================================================================================== +CGeom2DPoint CSimulation::PtChooseEndPoint(int const nHand, CGeom2DPoint const* PtBefore, CGeom2DPoint const* PtAfter, double const dXEnd1, double const dYEnd1, double const dXEnd2, double const dYEnd2) +{ + CGeom2DPoint PtChosen; + + // All coordinates here are in the external CRS, so the origin of the grid is the bottom left + if (nHand == RIGHT_HANDED) + { + // The sea is to the right of the linking line. So which way is the linking line oriented? First check the N-S component + if (PtAfter->dGetY() > PtBefore->dGetY()) + { + // We are going S to N and the sea is to the right: the normal endpoint is to the E. We want the larger of the two x values + if (dXEnd1 > dXEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else if (PtAfter->dGetY() < PtBefore->dGetY()) + { + // We are going N to S and the sea is to the right: the normal endpoint is to the W. We want the smaller of the two x values + if (dXEnd1 < dXEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else + { + // No N-S component i.e. the linking line is exactly W-E. So check the W-E component + if (PtAfter->dGetX() > PtBefore->dGetX()) + { + // We are going W to E and the sea is to the right: the normal endpoint is to the s. We want the smaller of the two y values + if (dYEnd1 < dYEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else // Do not check for (PtAfter->dGetX() == PtBefore->dGetX()), since this would mean the two points are co-incident + { + // We are going E to W and the sea is to the right: the normal endpoint is to the N. We want the larger of the two y values + if (dYEnd1 > dYEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + } + } + else // nHand == LEFT_HANDED + { + // The sea is to the left of the linking line. So which way is the linking line oriented? First check the N-S component + if (PtAfter->dGetY() > PtBefore->dGetY()) + { + // We are going S to N and the sea is to the left: the normal endpoint is to the W. We want the smaller of the two x values + if (dXEnd1 < dXEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else if (PtAfter->dGetY() < PtBefore->dGetY()) + { + // We are going N to S and the sea is to the left: the normal endpoint is to the E. We want the larger of the two x values + if (dXEnd1 > dXEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else + { + // No N-S component i.e. the linking line is exactly W-E. So check the W-E component + if (PtAfter->dGetX() > PtBefore->dGetX()) + { + // We are going W to E and the sea is to the left: the normal endpoint is to the N. We want the larger of the two y values + if (dYEnd1 > dYEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + else // Do not check for (PtAfter->dGetX() == PtBefore->dGetX()), since this would mean the two points are co-incident + { + // We are going E to W and the sea is to the left: the normal endpoint is to the S. We want the smaller of the two y values + if (dYEnd1 < dYEnd2) + { + PtChosen.SetX(dXEnd1); + PtChosen.SetY(dYEnd1); + } + else + { + PtChosen.SetX(dXEnd2); + PtChosen.SetY(dYEnd2); + } + } + } + } + + return PtChosen; +} + +//=============================================================================================================================== +//! Checks all coastline-normal profiles for intersection, and modifies those that intersect +//=============================================================================================================================== +void CSimulation::CheckForIntersectingProfiles(void) +{ + LogStream << endl << m_ulIter << ": Checking for profile intersection" << endl; + + // Do once for every coastline object + int const nCoastLines = static_cast(m_VCoast.size()); + + for (int nCoast = 0; nCoast < nCoastLines; nCoast++) + { + int const nCoastSize = m_VCoast[nCoast].nGetCoastlineSize(); + + // Do once for every profile, in along-coast sequence + for (int nCoastPoint = 0; nCoastPoint < nCoastSize; nCoastPoint++) + { + if (! m_VCoast[nCoast].bIsProfileAtCoastPoint(nCoastPoint)) + continue; + + // There is a profile at this coast point + CGeomProfile* pFirstProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nCoastPoint); + int const nFirstProfile = pFirstProfile->nGetProfileID(); + + // Only check this profile if it is problem free, and is not a start- or end-of-coast profile. Continue checking if it has been truncated, however + if (! pFirstProfile->bProfileOKIncTruncated()) + { + // LogStream << m_ulIter << ": nCoastPoint = " << nCoastPoint << " pFirstProfile = " << pFirstProfile->nGetProfileID() << " is not OK (could be a start- or end-of-coast profile), abandoning" << endl; + continue; + } + + // OK we have found a first profile. Now go along the coast in alternate directions: first down-coast (in the direction of increasing coast point numbers) then up-coast + for (int nDirection = DIRECTION_DOWNCOAST; nDirection <= DIRECTION_UPCOAST; nDirection++) + { + int nStartPoint; + + if (nDirection == DIRECTION_DOWNCOAST) + nStartPoint = nCoastPoint + 1; + else + nStartPoint = nCoastPoint - 1; + + for (int nSecondCoastPoint = nStartPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint < nCoastSize : nSecondCoastPoint >= 0; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint++ : nSecondCoastPoint--) + // // In this direction, look at profiles which are increasingly close to the first profile + // int nStartPoint; + // if (nDirection == DIRECTION_DOWNCOAST) + // nStartPoint = 0; + // else + // nStartPoint = nCoastSize - 1; + // + // for (int nSecondCoastPoint = nStartPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint < nCoastPoint : nSecondCoastPoint > nCoastPoint; (nDirection == DIRECTION_DOWNCOAST) ? nSecondCoastPoint++ : nSecondCoastPoint--) + { + if (m_VCoast[nCoast].bIsProfileAtCoastPoint(nSecondCoastPoint)) + { + // There is a profile at the second coast point, so get a pointer to it + CGeomProfile* pSecondProfile = m_VCoast[nCoast].pGetProfileAtCoastPoint(nSecondCoastPoint); + int const nSecondProfile = pSecondProfile->nGetProfileID(); + + // LogStream << m_ulIter << ": " << (nDirection == DIRECTION_DOWNCOAST ? "down" : "up") << "-coast search, nCoastPoint = " << nCoastPoint << " nSecondCoastPoint = " << nSecondCoastPoint << " (profiles " << pFirstProfile->nGetProfileID() << " and " << pSecondProfile->nGetProfileID() << ")" << endl; + + // Only check this profile if it is problem free, and is not a start- or end-of-coast profile. Continue checking if it has been truncated, however + if (! pSecondProfile->bProfileOKIncTruncated()) + { + // LogStream << m_ulIter << ": second profile = " << pSecondProfile->nGetProfileID() << " is not OK (could be a start- or end-of-coast profile), abandoning" << endl; + continue; + } + + // Only check these two profiles for intersection if they are are not co-incident in the final line segment of both profiles (i.e. the profiles have not already intersected) + if ((pFirstProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nSecondProfile)) || (pSecondProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nFirstProfile))) + { + // LogStream << m_ulIter << ": profiles " << pFirstProfile->nGetProfileID() << " and " << pSecondProfile->nGetProfileID() << " are are not co-incident in the final line segment of both profiles (i.e. the profiles have not already intersected), abandoning" << endl; + continue; + } + + // OK go for it + int nProf1LineSeg = 0; + int nProf2LineSeg = 0; + double dIntersectX = 0; + double dIntersectY = 0; + double dAvgEndX = 0; + double dAvgEndY = 0; + + if (bCheckForIntersection(pFirstProfile, pSecondProfile, nProf1LineSeg, nProf2LineSeg, dIntersectX, dIntersectY, dAvgEndX, dAvgEndY)) + { + // The profiles intersect. Decide which profile to truncate, and which to retain + int nPoint = -1; + + if (pFirstProfile->bIsIntervention()) + { + LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, truncate " << nFirstProfile << " since it is an intervention profile" << endl; + + // Truncate the first profile, since it is an intervention profile + TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); + } + else if (pSecondProfile->bIsIntervention()) + { + LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, truncate " << nSecondProfile << " since it is an intervention profile" << endl; + + // Truncate the second profile, since it is an intervention profile + TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); + } + // Is the point of intersection already present in the first profile (i.e. because there has already been an intersection at this point between the first profile and some other profile)? + else if (pFirstProfile->bIsPointInProfile(dIntersectX, dIntersectY, nPoint)) + { + LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, but point {" << dIntersectX << ", " << dIntersectY << "} is already present in profile " << nFirstProfile << " as point " << nPoint << endl; + + // Truncate the second profile and merge it with the first profile + TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, true); + } + // Is the point of intersection already present in the second profile? + else if (pSecondProfile->bIsPointInProfile(dIntersectX, dIntersectY, nPoint)) + { + LogStream << m_ulIter << ": profiles " << nFirstProfile << " and " << nSecondProfile << " intersect, but point {" << dIntersectX << ", " << dIntersectY << "} is already present in profile " << nSecondProfile << " as point " << nPoint << endl; + + // Truncate the first profile and merge it with the second profile + TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, true); + } + else + { + // The point of intersection is not already present in either profile, so get the number of line segments of each profile + int const nFirstProfileLineSegments = pFirstProfile->nGetNumLineSegments(); + int const nSecondProfileLineSegments = pSecondProfile->nGetNumLineSegments(); + + // assert(nProf1LineSeg < nFirstProfileLineSegments); + // assert(nProf2LineSeg < nSecondProfileLineSegments); + + // Next check whether the point of intersection is on the final line segment of both profiles + if ((nProf1LineSeg == (nFirstProfileLineSegments - 1)) && (nProf2LineSeg == (nSecondProfileLineSegments - 1))) + { + // Yes, the point of intersection is on the final line segment of both profiles, so merge the profiles seaward of the point of intersection + MergeProfilesAtFinalLineSegments(nCoast, pFirstProfile, pSecondProfile, nFirstProfileLineSegments, nSecondProfileLineSegments, dIntersectX, dIntersectY, dAvgEndX, dAvgEndY); + + // LogStream << m_ulIter << ": " << ((nDirection == DIRECTION_DOWNCOAST) ? "down" : "up") << "-coast search, end-segment intersection between profiles " << nFirstProfile << " and " << nSecondProfile << " at [" << dIntersectX << ", " << dIntersectY << "] in line segment [" << nProf1LineSeg << "] of " << nFirstProfileLineSegments << " segments, and line segment [" << nProf2LineSeg << "] of " << nSecondProfileLineSegments << " segments, respectively" << endl; + + // // DEBUG CODE ============================================================================================= + // int nSizeTmp = pFirstProfile->nGetProfileSize(); + // CGeom2DPoint PtEndTmp = *pFirstProfile->pPtGetPointInProfile(nSizeTmp-1); + // + // LogStream << m_ulIter << ": end of first profile (" << nFirstProfile << ") is point " << nSizeTmp-1 << " at [" << dExtCRSXToGridX(PtEndTmp.dGetX()) << "][" << dExtCRSYToGridY(PtEndTmp.dGetY()) << "} = {" << PtEndTmp.dGetX() << ", " << PtEndTmp.dGetY() << "}" << endl; + // + // nSizeTmp = pSecondProfile->nGetProfileSize(); + // PtEndTmp = *pSecondProfile->pPtGetPointInProfile(nSizeTmp-1); + // + // LogStream << m_ulIter << ": end of second profile (" << nSecondProfile << ") is point " << nSizeTmp-1 << " at [" << dExtCRSXToGridX(PtEndTmp.dGetX()) << "][" << dExtCRSYToGridY(PtEndTmp.dGetY()) << "} = {" << PtEndTmp.dGetX() << ", " << PtEndTmp.dGetY() << "}" << endl; + // // DEBUG CODE ============================================================================================= + } + else + { + // The profiles intersect, but the point of intersection is not on the final line segment of both profiles. One of the profiles will be truncated, the other profile will be retained + // LogStream << m_ulIter << ": " << ((nDirection == DIRECTION_DOWNCOAST) ? "down" : "up") << "-coast search, intersection (NOT both end segments) between profiles " << nFirstProfile << " and " << nSecondProfile << " at [" << dIntersectX << ", " << dIntersectY << "] in line segment [" << nProf1LineSeg << "] of " << nFirstProfileLineSegments << ", and line segment [" << nProf2LineSeg << "] of " << nSecondProfileLineSegments << ", respectively" << endl; + + // Decide which profile to truncate, and which to retain + if (pFirstProfile->bIsIntervention()) + { + // Truncate the first profile, since it is an intervention profile + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pFirstProfile is an intervention profile, so truncate pFirstProfile (" << pFirstProfile->nGetProfileID() << ")" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); + } + else if (pSecondProfile->bIsIntervention()) + { + // Truncate the second profile, since it is an intervention profile + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pSecondProfile is an intervention profile, so truncate pSecondProfile (" << pSecondProfile->nGetProfileID() << ")" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); + } + else if (nFirstProfileLineSegments < nSecondProfileLineSegments) + { + // Truncate the first profile, since it has a smaller number of line segments + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pFirstProfile has a smaller number of line segments, so truncate pFirstProfile (" << pFirstProfile->nGetProfileID() << ")" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); + } + else if (nFirstProfileLineSegments > nSecondProfileLineSegments) + { + // Truncate the second profile, since it has a smaller number of line segments + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", pSecondProfile has a smaller number of line segments, so truncate pSecondProfile (" << pSecondProfile->nGetProfileID() << ")" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); + } + else + { + // Both profiles have the same number of line segments, so choose randomly. Draw a sample from the unit normal distribution using random number generator 1 + double const dRand = m_dGetFromUnitNormalDist(m_Rand[0]); + + if (dRand >= 0.0) + { + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", same number of line segment, randomly truncate pFirstProfile" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pFirstProfile, pSecondProfile, dIntersectX, dIntersectY, nProf1LineSeg, nProf2LineSeg, false); + } + else + { + // LogStream << m_ulIter << ": pFirstProfile = " << pFirstProfile->nGetProfileID() << " pSecondProfile = " << pSecondProfile->nGetProfileID() << ", same number of line segment, randomly truncate pSecondProfile" << endl; + + TruncateOneProfileRetainOtherProfile(nCoast, pSecondProfile, pFirstProfile, dIntersectX, dIntersectY, nProf2LineSeg, nProf1LineSeg, false); + } + } + } + } + } + } + } + } + } + } +} + +//=============================================================================================================================== +//! Check all coastline-normal profiles and modify the profiles if they intersect, then mark valid profiles on the raster grid +//=============================================================================================================================== +int CSimulation::nCheckAndMarkAllProfiles(void) +{ + // Check to see which coastline-normal profiles intersect. Then modify intersecting profiles so that the sections of each profile seaward of the point of intersection are 'shared' i.e. are multi-lines. This creates the boundaries of the triangular polygons + CheckForIntersectingProfiles(); + + // Again check the normal profiles for insufficient length: is the water depth at the end point less than the depth of closure? We do this again because some profiles may have been shortened as a result of intersection. Do once for every coastline object + for (unsigned int nCoast = 0; nCoast < m_VCoast.size(); nCoast++) + { + for (int n = 0; n < m_VCoast[nCoast].nGetNumProfiles(); n++) + { + CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); + int const nProfile = pProfile->nGetProfileID(); + + if (pProfile->bProfileOK()) + { + int const nSize = pProfile->nGetProfileSize(); + + // Safety check + if (nSize == 0) + { + // pProfile->SetTooShort(true); + m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); + LogStream << "Profile " << nProfile << " is too short, size = " << nSize << endl; + continue; + } + + CGeom2DPoint const* pPtEnd = pProfile->pPtGetPointInProfile(nSize - 1); + CGeom2DIPoint const PtiEnd = PtiExtCRSToGridRound(pPtEnd); + int nXEnd = PtiEnd.nGetX(); + int nYEnd = PtiEnd.nGetY(); + + // Safety checks: the point may be outside the grid, so keep it within the grid + nXEnd = tMin(nXEnd, m_nXGridSize - 1); + nYEnd = tMin(nYEnd, m_nYGridSize - 1); + nXEnd = tMax(nXEnd, 0); + nYEnd = tMax(nYEnd, 0); + + if (m_pRasterGrid->Cell(nXEnd, nYEnd).dGetSeaDepth() < m_dDepthOfClosure) + { + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter << ": coast " << nCoast << ", profile " << nProfile << " is invalid, is too short for depth of closure " << m_dDepthOfClosure << " at end point [" << nXEnd << "][" << nYEnd << "] = {" << pPtEnd->dGetX() << ", " << pPtEnd->dGetY() << "}, flagging as too short" << endl; + + // pProfile->SetTooShort(true); + m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); + } + } + } + + // For this coast, put all valid coastline-normal profiles (apart from the profiles at the start and end of the coast, since they have already been done) onto the raster grid. But if the profile is not long enough, crosses a coastline, hits dry land, or hits another profile, then mark the profile as invalid + int nValidProfiles = 0; + MarkProfilesOnGrid(nCoast, nValidProfiles); + + if (nValidProfiles == 0) + { + // Problem! No valid profiles, so quit + cerr << m_ulIter << ": " << ERR << "no coastline-normal profiles created" << endl; + return RTN_ERR_NO_PROFILES_2; + } + + // // DEBUG CODE =========================================================================================================== + // if (m_ulIter == 109) + // { + // string strOutFile = m_strOutPath; + // strOutFile += "00_profile_raster_"; + // strOutFile += to_string(m_ulIter); + // strOutFile += ".tif"; + // + // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); + // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); + // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); + // pDataSet->SetGeoTransform(m_dGeoTransform); + // + // int nn = 0; + // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // if (m_pRasterGrid->Cell(nX, nY).bIsCoastline()) + // pdRaster[nn] = -1; + // else + // { + // + // // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).nGetProfileID(); + // } + // + // nn++; + // } + // } + // + // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); + // pBand->SetNoDataValue(m_dMissingValue); + // int nRet1 = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); + // + // if (nRet1 == CE_Failure) + // return RTN_ERR_GRIDCREATE; + // + // GDALClose(pDataSet); + // delete[] pdRaster; + // } + // // DEBUG CODE =========================================================================================================== + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! Checks all line segments of a pair of coastline-normal profiles for intersection. If the lines intersect, returns true with the numbers of the line segments at which intersection occurs in nProfile1LineSegment and nProfile1LineSegment, the intersection point in dXIntersect and dYIntersect, and the 'average' seaward endpoint of the two intersecting profiles at dXAvgEnd and dYAvgEnd +//=============================================================================================================================== +bool CSimulation::bCheckForIntersection(CGeomProfile* const pVProfile1, CGeomProfile* const pVProfile2, int& nProfile1LineSegment, int& nProfile2LineSegment, double& dXIntersect, double& dYIntersect, double& dXAvgEnd, double& dYAvgEnd) +{ + // For both profiles, look at all line segments + int const nProfile1NumSegments = pVProfile1->nGetNumLineSegments(); + int const nProfile2NumSegments = pVProfile2->nGetNumLineSegments(); + // nProfile1Size = pVProfile1->nGetProfileSize(), + // nProfile2Size = pVProfile2->nGetProfileSize(); + + // assert(nProfile1Size == nProfile1NumSegments+1); + // assert(nProfile2Size == nProfile2NumSegments+1); + + for (int i = 0; i < nProfile1NumSegments; i++) + { + for (int j = 0; j < nProfile2NumSegments; j++) + { + // In external coordinates + double const dX1 = pVProfile1->pPtVGetPoints()->at(i).dGetX(); + double const dY1 = pVProfile1->pPtVGetPoints()->at(i).dGetY(); + double const dX2 = pVProfile1->pPtVGetPoints()->at(i + 1).dGetX(); + double const dY2 = pVProfile1->pPtVGetPoints()->at(i + 1).dGetY(); + + double const dX3 = pVProfile2->pPtVGetPoints()->at(j).dGetX(); + double const dY3 = pVProfile2->pPtVGetPoints()->at(j).dGetY(); + double const dX4 = pVProfile2->pPtVGetPoints()->at(j + 1).dGetX(); + double const dY4 = pVProfile2->pPtVGetPoints()->at(j + 1).dGetY(); + + // Uses Cramer's Rule to solve the equations. Modified from code at http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect (in turn based on Andre LeMothe's "Tricks of the Windows Game Programming Gurus") + double const dDiffX1 = dX2 - dX1; + double const dDiffY1 = dY2 - dY1; + double const dDiffX2 = dX4 - dX3; + double const dDiffY2 = dY4 - dY3; + + double dS = -999; + double dT = -999; + double dTmp = 0; + + dTmp = -dDiffX2 * dDiffY1 + dDiffX1 * dDiffY2; + + if (! bFPIsEqual(dTmp, 0.0, TOLERANCE)) + dS = (-dDiffY1 * (dX1 - dX3) + dDiffX1 * (dY1 - dY3)) / dTmp; + + dTmp = -dDiffX2 * dDiffY1 + dDiffX1 * dDiffY2; + + if (! bFPIsEqual(dTmp, 0.0, TOLERANCE)) + dT = (dDiffX2 * (dY1 - dY3) - dDiffY2 * (dX1 - dX3)) / dTmp; + + if (dS >= 0 && dS <= 1 && dT >= 0 && dT <= 1) + { + // Collision detected, calculate intersection coordinates + dXIntersect = dX1 + (dT * dDiffX1); + dYIntersect = dY1 + (dT * dDiffY1); + + // And calc the average end-point coordinates + dXAvgEnd = (dX2 + dX4) / 2; + dYAvgEnd = (dY2 + dY4) / 2; + + // Get the line segments at which intersection occurred + nProfile1LineSegment = i; + nProfile2LineSegment = j; + + // LogStream << "\t" << "INTERSECTION dX2 = " << dX2 << " dX4 = " << dX4 << " dY2 = " << dY2 << " dY4 = " << dY4 << endl; + return true; + } + } + } + + // No intersection + return false; +} + +//=============================================================================================================================== +//! For this coastline, marks all coastline-normal profiles (apart from the two 'special' ones at the start and end of the coast) onto the raster grid, i.e. rasterizes multi-line vector objects onto the raster grid. Note that this doesn't work if the vector has already been interpolated to fit on the grid i.e. if distances between vector points are just one cell apart +//=============================================================================================================================== +void CSimulation::MarkProfilesOnGrid(int const nCoast, int& nValidProfiles) +{ + // How many profiles on this coast? + int const nProfiles = m_VCoast[nCoast].nGetNumProfiles(); + + if (nProfiles == 0) + { + // This can happen if the coastline is very short, so just give a warning and carry on with the next coastline + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << WARN << m_ulIter << ": coast " << nCoast << " has no profiles" << endl; + + return; + } + + static bool bDownCoast = true; + + // Now do this for every profile, alternate between up-coast and down-coast directions + for (int n = 0; n < nProfiles; n++) + { + CGeomProfile* pProfile; + + if (bDownCoast) + pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(n); + else + pProfile = m_VCoast[nCoast].pGetProfileWithUpCoastSeq(n); + + // Don't do this for the first and last profiles (i.e. the profiles at the start and end of the coast) since these are put onto the grid elsewhere + if (pProfile->bIsGridEdge()) + continue; + + int const nProfile = pProfile->nGetProfileID(); + + // If this profile has a problem, then forget about it + // if (! pProfile->bProfileOK()) + // { + // LogStream << m_ulIter << ": in MarkProfilesOnGrid() profile " << nProfile << " is not OK" << endl; + // continue; + // } + + int const nPoints = pProfile->nGetProfileSize(); + + if (nPoints < 2) + { + // Need at least two points in the profile, so this profile is invalid: mark it + m_VCoast[nCoast].pGetProfile(nProfile)->SetTooShort(true); + + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter << ": coast " << nCoast << ", profile " << nProfile << " is invalid, has only " << nPoints << " points" << endl; + + continue; + } + + // OK, go for it: set up temporary vectors to hold the x-y coords (in grid CRS) of the cells which we will mark + vector VCellsToMark; + vector bVShared; + bool bTooShort = false; + bool bTruncatedSameCoast = false; + bool bHitCoast = false; + bool bHitLand = false; + bool bHitIntervention = false; + bool bHitAnotherProfile = false; + + CreateRasterizedProfile(nCoast, pProfile, &VCellsToMark, &bVShared, bTooShort, bTruncatedSameCoast, bHitCoast, bHitLand, bHitIntervention, bHitAnotherProfile); + + if ((bTruncatedSameCoast && (! ACCEPT_TRUNCATED_PROFILES)) || bTooShort || bHitCoast || bHitLand || bHitIntervention || bHitAnotherProfile || VCellsToMark.size() == 0) + continue; + + // This profile is fine + nValidProfiles++; + + for (unsigned int k = 0; k < VCellsToMark.size(); k++) + { + // Ignore duplicate points + if ((k > 0) && (VCellsToMark[k] == m_VCoast[nCoast].pGetProfile(nProfile)->pPtiGetLastCellInProfile())) + continue; + + // Mark each cell in the raster grid + int const nXTmp = VCellsToMark[k].nGetX(); + int const nYTmp = VCellsToMark[k].nGetY(); + m_pRasterGrid->Cell(nXTmp, nYTmp).SetCoastAndProfileID(nCoast, nProfile); + + // Store the raster grid coordinates in the profile object + m_VCoast[nCoast].pGetProfile(nProfile)->AppendCellInProfile(nXTmp, nYTmp); + + // Mark the shared (i.e. multi-line) parts of the profile (if any) + // if (bVShared[k]) + // { + // m_VCoast[nCoast].pGetProfile(nProfile)->AppendPointShared(true); + // // LogStream << m_ulIter << ": profile " << j << " point " << k << " marked as shared" << endl; + // } + // else + // { + // m_VCoast[nCoast].pGetProfile(nProfile)->AppendPointShared(false); + // // LogStream << m_ulIter << ": profile " << nProfile << " point " << k << " marked as NOT shared" << endl; + // } + } + + // Get the deep water wave height and orientation values at the end of the profile + double const dDeepWaterWaveHeight = m_pRasterGrid->Cell(VCellsToMark.back().nGetX(), VCellsToMark.back().nGetY()).dGetCellDeepWaterWaveHeight(); + double const dDeepWaterWaveAngle = m_pRasterGrid->Cell(VCellsToMark.back().nGetX(), VCellsToMark.back().nGetY()).dGetCellDeepWaterWaveAngle(); + double const dDeepWaterWavePeriod = m_pRasterGrid->Cell(VCellsToMark.back().nGetX(), VCellsToMark.back().nGetY()).dGetCellDeepWaterWavePeriod(); + + // And store them for this profile + m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWaveHeight(dDeepWaterWaveHeight); + m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWaveAngle(dDeepWaterWaveAngle); + m_VCoast[nCoast].pGetProfile(nProfile)->SetProfileDeepWaterWavePeriod(dDeepWaterWavePeriod); + } + + bDownCoast = ! bDownCoast; +} + +//=============================================================================================================================== +//! Given a pointer to a coastline-normal profile, returns an output vector of cells which are 'under' every line segment of the profile. If there is a problem with the profile (e.g. a rasterized cell is dry land or coast, or the profile has to be truncated) then we pass this back as an error code +//=============================================================================================================================== +void CSimulation::CreateRasterizedProfile(int const nCoast, CGeomProfile* pProfile, vector* pVIPointsOut, vector* pbVShared, bool& bTooShort, bool& bTruncatedSameCoast, bool& bHitCoast, bool& bHitLand, bool& bHitIntervention, bool& bHitAnotherProfile) +{ + int const nProfile = pProfile->nGetProfileID(); + int nSeg = 0; + int const nNumSegments = pProfile->nGetNumLineSegments(); + + pVIPointsOut->clear(); + + // LogStream << m_ulIter << ": in CreateRasterizedProfile() *pPtiStart for profile " << nProfile << " is [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "]" << endl; + int nXStartLast = INT_NODATA; + int nYStartLast = INT_NODATA; + int nXEndLast = INT_NODATA; + int nYEndLast = INT_NODATA; + + // Do for every segment of this profile + for (nSeg = 0; nSeg < nNumSegments; nSeg++) + { + // Do once for every line segment + CGeom2DIPoint PtiSegStart; + + if (nSeg == 0) + { + // If this is the first segment, use the coastline start point to prevent external CRS to grid CRS rounding errors + int const nCoastPoint = pProfile->nGetCoastPoint(); + PtiSegStart = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastPoint); + } + else + { + CGeom2DPoint const* pPtSegStart = pProfile->pPtGetPointInProfile(nSeg); + + // Convert from the external CRS to grid CRS + PtiSegStart = PtiExtCRSToGridRound(pPtSegStart); + } + + CGeom2DPoint const* pPtSegEnd = pProfile->pPtGetPointInProfile(nSeg + 1); // This is OK + + // Convert from the external CRS to grid CRS + CGeom2DIPoint const PtiSegEnd = PtiExtCRSToGridRound(pPtSegEnd); + + // Safety check + if (PtiSegStart == PtiSegEnd) + continue; + + int const nXStart = PtiSegStart.nGetX(); + int const nYStart = PtiSegStart.nGetY(); + int const nXEnd = PtiSegEnd.nGetX(); + int const nYEnd = PtiSegEnd.nGetY(); + + bool bShared = false; + + if (pProfile->nGetNumCoincidentProfilesInLineSegment(nSeg) > 1) + { + bShared = true; + + // If this is the second or more of several coincident line segments (i.e. it has the same start and end points as the previous line segment) then ignore it + if ((nXStart == nXStartLast) && (nYStart == nYStartLast) && (nXEnd == nXEndLast) && (nYEnd == nYEndLast)) + continue; + } + + // Interpolate between cells by a simple DDA line algorithm, see http://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) Note that Bresenham's algorithm gave occasional gaps + double dXInc = nXEnd - nXStart; + double dYInc = nYEnd - nYStart; + double const dLength = tMax(tAbs(dXInc), tAbs(dYInc)); + + dXInc /= dLength; + dYInc /= dLength; + + double dX = nXStart; + double dY = nYStart; + + // Process each interpolated point + for (int m = 0; m <= nRound(dLength); m++) + { + int const nX = nRound(dX); + int const nY = nRound(dY); + + // Do some checking of this interpolated point, but only if this is not a grid-edge profile (these profiles are always valid) + if (! pProfile->bIsGridEdge()) + { + // Is the interpolated point within the valid raster grid? + if (! bIsWithinValidGrid(nX, nY)) + { + // It is outside the valid grid, so mark this profile and quit the loop + bTruncatedSameCoast = true; + + if (! ACCEPT_TRUNCATED_PROFILES) + pProfile->SetTruncatedSameCoast(true); + + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter << ": profile " << nProfile << " is invalid, truncated at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; + + break; + } + + // Check again: is this cell (or an adjacent cell: does not matter which) already marked as 'under' a profile? + int nYTmp = nY+1; + if (nY+1 >= m_nYGridSize) + nYTmp = nY-1; + + if (m_pRasterGrid->Cell(nX, nY).bIsProfile() || m_pRasterGrid->Cell(nX, nYTmp).bIsProfile()) + { + // This cell or an adjacent cell, is 'under' a profile, so now check if the profile belongs to another coast + int const nHitProfileCoast1 = m_pRasterGrid->Cell(nX, nY).nGetProfileCoastID(); + int const nHitProfileCoast2 = m_pRasterGrid->Cell(nX, nYTmp).nGetProfileCoastID(); + + if ((nHitProfileCoast1 == nCoast) || (nHitProfileCoast2 == nCoast)) + { + // The profile belongs to the same coast, mark this profile as invalid + bHitAnotherProfile = true; + pProfile->SetHitAnotherProfile(true); + return; + } + } + + // If this is the first line segment of the profile, then once we are clear of the coastline (say, when m > 3), check if this profile hits land at this interpolated point. NOTE Get problems here since if the coastline vector has been heavily smoothed, this can result is 'false positives' profiles marked as invalid which are not actually invalid, because the profile hits land when m = 0 or m = 1. This results in some cells being flagged as profile cells which are actually inland + if (m > PROFILE_CHECK_DIST_FROM_COAST) + { + // Check this cell. Two diagonal(ish) raster lines can cross each other without any intersection, so must also test an adjacent cell for intersection (does not matter which adjacent cell) + if ((m_pRasterGrid->Cell(nX, nY).bIsCoastline()) || (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->Cell(nX, nY + 1).bIsCoastline())) + { + // We've hit a coastline so set a switch and mark the profile, then quit + bHitCoast = true; + pProfile->SetHitCoast(true); + int const nHitCoast = m_pRasterGrid->Cell(nX, nY).nGetCoastline(); + + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": coast " << nCoast << " profile " << nProfile << " is invalid, hit coast " << nHitCoast << " at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; + + return; + } + + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) + { + // We've hit dry land, so set a switch and mark the profile + bHitLand = true; + pProfile->SetHitLand(true); + + LogStream << m_ulIter << ": profile " << nProfile << " HIT LAND at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, elevation = " << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << ", SWL = " << m_dThisIterSWL << endl; + + return; + } + + if (m_pRasterGrid->Cell(nX, nY).nGetInterventionClass() != INT_NODATA) + { + // We've hit an intervention, so set a switch and mark the profile + bHitIntervention = true; + pProfile->SetHitIntervention(true); + + LogStream << m_ulIter << ": profile " << nProfile << " HIT INTERVENTION at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}, elevation = " << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << ", SWL = " << m_dThisIterSWL << endl; + + return; + } + } + + // Now check to see if we hit another profile which is not a coincident normal to this normal + if (m_pRasterGrid->Cell(nX, nY).bIsProfile()) + { + // We've hit a raster cell which is already marked as 'under' a normal profile. Get the number of the profile which marked this cell, and the coast to hich this profile belongs + int const nHitProfile = m_pRasterGrid->Cell(nX, nY).nGetProfileID(); + int const nHitProfileCoast = m_pRasterGrid->Cell(nX, nY).nGetProfileCoastID(); + + // Do both profiles belong to the same coast? + if (nCoast == nHitProfileCoast) + { + // Both profiles belong to the same coast. Is this the number of a coincident profile of this profile? + if (! pProfile->bFindProfileInCoincidentProfilesOfLastLineSegment(nHitProfile)) + { + // It isn't a coincident profile, so we have just hit an unrelated profile. Mark this profile as invalid and move on + pProfile->SetHitAnotherProfile(true); + bHitAnotherProfile = true; + + return; + } + } + } + } + + // Append this point to the output vector + pVIPointsOut->push_back(CGeom2DIPoint(nX, nY)); // Is in raster grid coordinates + pbVShared->push_back(bShared); + + // And increment for next time + dX += dXInc; + dY += dYInc; + } + + nXStartLast = nXStart; + nYStartLast = nYStart; + nXEndLast = nXEnd; + nYEndLast = nYEnd; + + if (bTruncatedSameCoast) + break; + } + + if (bTruncatedSameCoast) + { + if (nSeg < (nNumSegments - 1)) + // We are truncating the profile, so remove any line segments after this one + pProfile->TruncateLineSegments(nSeg); + + // Shorten the vector input. Ignore CPPCheck errors here, since we know that pVIPointsOut is not empty + int const nLastX = pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX(); + int const nLastY = pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY(); + + pProfile->pPtGetPointInProfile(nSeg + 1)->SetX(dGridCentroidXToExtCRSX(nLastX)); + pProfile->pPtGetPointInProfile(nSeg + 1)->SetY(dGridCentroidYToExtCRSY(nLastY)); + } + + // // DEBUG CODE ===================================================================================== + // LogStream << "====================" << endl; + // LogStream << m_ulIter << ": for profile " << nProfile << " pPtiStart = [" << pPtiStart->nGetX() << "][" << pPtiStart->nGetY() << "] pPtiEnd = [" << pPtiEnd->nGetX() << "][" << pPtiEnd->nGetY() << "] pVIPointsOut->size() = " << pVIPointsOut->size() << endl; + // // for (int n = 0; n < static_cast(pVIPointsOut->size()); n++) + // // LogStream << "\t[" << pVIPointsOut->at(n).nGetX() << "][" << pVIPointsOut->at(n).nGetY() << "]" << endl; + // LogStream << "====================" << endl; + // // DEBUG CODE ===================================================================================== + + if (pVIPointsOut->size() < 3) + { + // Coastline-normal profiles cannot be very short (e.g. with less than 3 cells), since we cannot calculate along-profile slope properly for such short profiles + bTooShort = true; + pProfile->SetTooShort(true); + + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + { + // Ignore CPPCheck errors here, since we know that pVIPointsOut is not empty + LogStream << m_ulIter << ": profile " << nProfile << " is invalid, is too short, only " << pVIPointsOut->size() << " points, HitLand?" << bHitLand << ". From [" << pVIPointsOut->at(0).nGetX() << "][" << pVIPointsOut->at(0).nGetY() << "] = {" << dGridCentroidXToExtCRSX(pVIPointsOut->at(0).nGetX()) << ", " << dGridCentroidYToExtCRSY(pVIPointsOut->at(0).nGetY()) << "} to [" << pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX() << "][" << pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY() << "] = {" << dGridCentroidXToExtCRSX(pVIPointsOut->at(pVIPointsOut->size() - 1).nGetX()) << ", " << dGridCentroidYToExtCRSY(pVIPointsOut->at(pVIPointsOut->size() - 1).nGetY()) << "}" << endl; + } + } +} + +//=============================================================================================================================== +//! Merges two profiles which intersect at their final (most seaward) line segments, seaward of their point of intersection +//=============================================================================================================================== +void CSimulation::MergeProfilesAtFinalLineSegments(int const nCoast, CGeomProfile* pFirstProfile, CGeomProfile* pSecondProfile, int const nFirstProfileLineSegments, int const nSecondProfileLineSegments, double const dIntersectX, double const dIntersectY, double const dAvgEndX, double const dAvgEndY) +{ + // The point of intersection is on the final (most seaward) line segment of both profiles. Put together a vector of coincident profile numbers (with no duplicates) for both profiles + int nCombinedLastSeg = 0; + vector> prVCombinedProfilesCoincidentProfilesLastSeg; + + for (unsigned int n = 0; n < pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->size(); n++) + { + pair prTmp; + prTmp.first = pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->at(n).first; + prTmp.second = pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1)->at(n).second; + + bool bFound = false; + + for (unsigned int m = 0; m < prVCombinedProfilesCoincidentProfilesLastSeg.size(); m++) + { + if (prVCombinedProfilesCoincidentProfilesLastSeg[m].first == prTmp.first) + { + bFound = true; + break; + } + } + + if (! bFound) + { + prVCombinedProfilesCoincidentProfilesLastSeg.push_back(prTmp); + nCombinedLastSeg++; + } + } + + for (unsigned int n = 0; n < pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->size(); n++) + { + pair prTmp; + prTmp.first = pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->at(n).first; + prTmp.second = pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1)->at(n).second; + + bool bFound = false; + + for (unsigned int m = 0; m < prVCombinedProfilesCoincidentProfilesLastSeg.size(); m++) + { + if (prVCombinedProfilesCoincidentProfilesLastSeg[m].first == prTmp.first) + { + bFound = true; + break; + } + } + + if (! bFound) + { + prVCombinedProfilesCoincidentProfilesLastSeg.push_back(prTmp); + nCombinedLastSeg++; + } + } + + // Increment the number of each line segment + for (int m = 0; m < nCombinedLastSeg; m++) + prVCombinedProfilesCoincidentProfilesLastSeg[m].second++; + + vector> prVFirstProfileCoincidentProfilesLastSeg = *pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(nFirstProfileLineSegments - 1); + vector> prVSecondProfileCoincidentProfilesLastSeg = *pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(nSecondProfileLineSegments - 1); + int const nNumFirstProfileCoincidentProfilesLastSeg = static_cast(prVFirstProfileCoincidentProfilesLastSeg.size()); + int const nNumSecondProfileCoincidentProfilesLastSeg = static_cast(prVSecondProfileCoincidentProfilesLastSeg.size()); + + // LogStream << m_ulIter << ": END-SEGMENT INTERSECTION between profiles " << nFirstProfile << " and " << nSecondProfile << " at line segment " << nFirstProfileLineSegments-1 << "/" << nFirstProfileLineSegments-1 << ", and line segment " << nSecondProfileLineSegments-1 << "/" << nSecondProfileLineSegments-1 << ", respectively. Both truncated at [" << dIntersectX << ", " << dIntersectY << "] then profiles {" << nFirstProfile << "} and {" << nSecondProfile << "} extended to [" << dAvgEndX << ", " << dAvgEndY << "]" << endl; + + // Truncate the first profile, and all co-incident profiles, at the point of intersection + for (int n = 0; n < nNumFirstProfileCoincidentProfilesLastSeg; n++) + { + int const nThisProfile = prVFirstProfileCoincidentProfilesLastSeg[n].first; + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + int const nProfileLength = pThisProfile->nGetProfileSize(); + + // This is the final line segment of the first 'main' profile. We are assuming that it is also the final line segment of all co-incident profiles. This is fine, altho' each profile may well have a different number of line segments landwards i.e. the number of the line segment may be different for each co-incident profile + pThisProfile->SetPointInProfile(nProfileLength - 1, dIntersectX, dIntersectY); + } + + // Truncate the second profile, and all co-incident profiles, at the point of intersection + for (int n = 0; n < nNumSecondProfileCoincidentProfilesLastSeg; n++) + { + int const nThisProfile = prVSecondProfileCoincidentProfilesLastSeg[n].first; + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + int const nProfileLength = pThisProfile->nGetProfileSize(); + + // This is the final line segment of the second 'main' profile. We are assuming that it is also the final line segment of all co-incident profiles. This is fine, altho' each profile may well have a different number of line segments landwards i.e. the number of the line segment may be different for each co-incident profile + pThisProfile->SetPointInProfile(nProfileLength - 1, dIntersectX, dIntersectY); + } + + // Append a new straight line segment to the existing line segment(s) of the first profile, and to all co-incident profiles + for (int nThisLineSeg = 0; nThisLineSeg < nNumFirstProfileCoincidentProfilesLastSeg; nThisLineSeg++) + { + int const nThisProfile = prVFirstProfileCoincidentProfilesLastSeg[nThisLineSeg].first; + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + + // Update this profile + pThisProfile->AppendPointInProfile(dAvgEndX, dAvgEndY); + + // Append details of the combined profiles + pThisProfile->AppendLineSegment(); + + for (int m = 0; m < nCombinedLastSeg; m++) + pThisProfile->AppendCoincidentProfileToLineSegments(prVCombinedProfilesCoincidentProfilesLastSeg[m]); + } + + // Append a new straight line segment to the existing line segment(s) of the second profile, and to all co-incident profiles + for (int nThisLineSeg = 0; nThisLineSeg < nNumSecondProfileCoincidentProfilesLastSeg; nThisLineSeg++) + { + int const nThisProfile = prVSecondProfileCoincidentProfilesLastSeg[nThisLineSeg].first; + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + + // Update this profile + pThisProfile->AppendPointInProfile(dAvgEndX, dAvgEndY); + + // Append details of the combined profiles + pThisProfile->AppendLineSegment(); + + for (int m = 0; m < nCombinedLastSeg; m++) + pThisProfile->AppendCoincidentProfileToLineSegments(prVCombinedProfilesCoincidentProfilesLastSeg[m]); + } + + // // DEBUG CODE **************************************************************** + // int nFirstProfileLineSeg= pFirstProfile->nGetNumLineSegments(); + // int nSecondProfileLineSeg = pSecondProfile->nGetNumLineSegments(); + // + // LogStream << "\tProfile {" << nFirstProfile << "} now has " << nFirstProfileLineSeg << " line segments" << endl; + // for (int m = 0; m < nFirstProfileLineSeg; m++) + // { + // vector > prVCoincidentProfiles = *pFirstProfile->pprVGetPairedCoincidentProfilesForLineSegment(m); + // LogStream << "\tCo-incident profiles and line segments for line segment " << m << " of profile {" << nFirstProfile << "} are {"; + // for (int nn = 0; nn < prVCoincidentProfiles.size(); nn++) + // LogStream << " " << prVCoincidentProfiles[nn].first << "[" << prVCoincidentProfiles[nn].second << "] "; + // LogStream << " }" << endl; + // } + // LogStream << "\tProfile {" << nSecondProfile << "} now has " << nSecondProfileLineSeg << " line segments" << endl; + // for (int m = 0; m < nSecondProfileLineSeg; m++) + // { + // vector > prVCoincidentProfiles = *pSecondProfile->pprVGetPairedCoincidentProfilesForLineSegment(m); + // LogStream << "\tCo-incident profiles and line segments for line segment " << m << " of profile {" << nSecondProfile << "} are {"; + // for (int nn = 0; nn < prVCoincidentProfiles.size(); nn++) + // LogStream << " " << prVCoincidentProfiles[nn].first << "[" << prVCoincidentProfiles[nn].second << "] "; + // LogStream << " }" << endl; + // } + // // DEBUG CODE ****************************************************************** +} + +//=============================================================================================================================== +//! Truncates one intersecting profile at the point of intersection, and retains the other profile +//=============================================================================================================================== +void CSimulation::TruncateOneProfileRetainOtherProfile(int const nCoast, CGeomProfile* pProfileToTruncate, CGeomProfile* pProfileToRetain, double dIntersectX, double dIntersectY, int nProfileToTruncateIntersectLineSeg, int nProfileToRetainIntersectLineSeg, bool const bAlreadyPresent) +{ + // // Occasionally, profiles cross each other, with the crossing not detected. So check for intersection between pProfileToTruncate and all profiles (starting from the last i.e. in reverse order) in all segments (starting from the last i.e. in reverse order) of pProfileToRetain's CGeomMultiLine + // bool bFound = false; + // int nNumSegProfRetain = pProfileToRetain->nGetNumLineSegments(); + // for (int nSeg = nNumSegProfRetain-1; nSeg >= 0; nSeg--) + // { + // if (bFound) + // break; + // + // int nNumProfInSeg = pProfileToRetain->nGetNumCoincidentProfilesInLineSegment(nSeg); + // for (int nProf = nNumProfInSeg-1; nProf >= 0; nProf--) + // { + // int nThisProf = pProfileToRetain->nGetCoincidentProfileForLineSegment(nSeg, nProf); + // CGeomProfile* pThisProf = m_VCoast[nCoast].pGetProfile(nThisProf); + // + // int nProfToTruncLineSeg = 0; + // int nThisProfLineSeg = 0; + // double dTmpIntersectX = 0; + // double dTmpIntersectY = 0; + // double dAvgEndX = 0; + // double dAvgEndY = 0; + // + // if (bCheckForIntersection(pProfileToTruncate, pThisProf, nProfToTruncLineSeg, nThisProfLineSeg, dTmpIntersectX, dTmpIntersectY, dAvgEndX, dAvgEndY)) + // { + // // An intersection was found: so the profile with which pProfileToTruncate intersects becomes the new pProfileToRetain, and dIntersectX, dIntersectY, nProfileToTruncateIntersectLineSeg, and nProfileToRetainIntersectLineSeg are also changed + // pProfileToRetain = pThisProf; + // dIntersectX = dTmpIntersectX; + // dIntersectY = dTmpIntersectY; + // nProfileToRetainIntersectLineSeg = nThisProfLineSeg; + // nProfileToTruncateIntersectLineSeg = nProfToTruncLineSeg; + // + // bFound = true; + // break; + // } + // } + // } + + // Insert the intersection point into the main retain-profile if it is not already in the profile, and do the same for all co-incident profiles of the main retain-profile. Also add details of the to-truncate profile (and all its coincident profiles) to every line segment of the main to-retain profile which is seaward of the point of intersection + int const nRet = nInsertPointIntoProfilesIfNeededThenUpdate(nCoast, pProfileToRetain, dIntersectX, dIntersectY, nProfileToRetainIntersectLineSeg, pProfileToTruncate, nProfileToTruncateIntersectLineSeg, bAlreadyPresent); + + if (nRet != RTN_OK) + { + // LogStream << m_ulIter << ": error in nInsertPointIntoProfilesIfNeededThenUpdate()" << endl; + return; + } + + // Get all profile points of the main retain-profile seawards from the intersection point, and do the same for the corresponding line segments (including coincident profiles). This also includes details of the main to-truncate profile (and all its coincident profiles) + vector PtVProfileLastPart; + vector>> prVLineSegLastPart; + + if (bAlreadyPresent) + { + PtVProfileLastPart = pProfileToRetain->PtVGetThisPointAndAllAfter(nProfileToRetainIntersectLineSeg); + prVLineSegLastPart = pProfileToRetain->prVVGetAllLineSegAfter(nProfileToRetainIntersectLineSeg); + } + + else + { + PtVProfileLastPart = pProfileToRetain->PtVGetThisPointAndAllAfter(nProfileToRetainIntersectLineSeg + 1); + prVLineSegLastPart = pProfileToRetain->prVVGetAllLineSegAfter(nProfileToRetainIntersectLineSeg + 1); + } + + // assert(PtVProfileLastPart.size() > 1); + // assert(prVLineSegLastPart.size() > 0); + + // Truncate the truncate-profile at the point of intersection, and do the same for all its co-incident profiles. Then append the profile points of the main to-retain profile seaward from the intersection point, and do the same for the corresponding line segments (including coincident profiles) + TruncateProfileAndAppendNew(nCoast, pProfileToTruncate, nProfileToTruncateIntersectLineSeg, &PtVProfileLastPart, &prVLineSegLastPart); + + // assert(m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->nGetProfileSize() > 1); + // assert(pProfileToRetain->nGetNumLineSegments() > 0); + // assert(m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->nGetNumLineSegments() > 0); +} + +//=============================================================================================================================== +//! Inserts an intersection point into the profile that is to be retained, if that point is not already present in the profile, then does the same for all co-incident profiles. Finally adds the numbers of the to-truncate profile (and all its coincident profiles) to the seaward line segments of the to-retain profile and all its coincident profiles +//=============================================================================================================================== +int CSimulation::nInsertPointIntoProfilesIfNeededThenUpdate(int const nCoast, CGeomProfile* pProfileToRetain, double const dIntersectX, double const dIntersectY, int const nProfileToRetainIntersectLineSeg, CGeomProfile* pProfileToTruncate, int const nProfileToTruncateIntersectLineSeg, bool const bAlreadyPresent) +{ + // // DEBUG CODE **************************************************************** + // // Get the index numbers of all coincident profiles for the 'main' to-retain profile for the line segment in which intersection occurred + // vector > prVRetainCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); + // int nNumRetainCoincidentCHECK1 = prVRetainCoincidentProfilesCHECK1.size(); + // for (int nn = 0; nn < nNumRetainCoincidentCHECK1; nn++) + // { + // int nThisProfile = prVRetainCoincidentProfilesCHECK1[nn].first; + // LogStream << "\tBEFORE nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-retain profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) + // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; + // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; + // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) + // { + // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); + // LogStream << "{ "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // + // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) + // { + // CGeom2DPoint + // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), + // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); + // + // if (Pt1 == Pt2) + // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; + // } + // } + // + // // Get the index numbers of all coincident profiles for the 'main' to-truncate profile for the line segment in which intersection occurred + // vector > prVTruncateCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nProfileToTruncate)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToTruncateIntersectLineSeg); + // int nNumTruncateCoincidentCHECK1 = prVTruncateCoincidentProfilesCHECK1.size(); + // for (int nn = 0; nn < nNumTruncateCoincidentCHECK1; nn++) + // { + // int nThisProfile = prVTruncateCoincidentProfilesCHECK1[nn].first; + // LogStream << "\tBEFORE nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nProfileToTruncate ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) + // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; + // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; + // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) + // { + // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); + // LogStream << "{ "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // + // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) + // { + // CGeom2DPoint + // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), + // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); + // + // if (Pt1 == Pt2) + // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; + // } + // } + // // DEBUG CODE ****************************************************************** + + int const nProfileToRetain = pProfileToRetain->nGetProfileID(); + + // Get the index numbers of all coincident profiles for the 'main' to-retain profile for the line segment in which intersection occurs + vector> prVCoincidentProfiles = *pProfileToRetain->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); + int const nNumCoincident = static_cast(prVCoincidentProfiles.size()); + vector nLineSegAfterIntersect(nNumCoincident, -1); // The line segment after the point of intersection, for each co-incident profile + + // Do this for the main profile and all profiles which are co-incident for this line segment + for (int nn = 0; nn < nNumCoincident; nn++) + { + int const nThisProfile = prVCoincidentProfiles[nn].first; // The number of this profile + int const nThisLineSeg = prVCoincidentProfiles[nn].second; // The line segment of this profile + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + + // Is the intersection point already present in the to-retain profile? + if (! bAlreadyPresent) + { + // It is not already present, so insert it and also update the associated multi-line + if (! pThisProfile->bInsertIntersection(dIntersectX, dIntersectY, nThisLineSeg)) + { + // Error + LogStream << WARN << m_ulIter << ": cannot insert a line segment after the final line segment (" << nThisLineSeg << ") for " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " profile (" << nThisProfile << "), abandoning" << endl; + + return RTN_ERR_CANNOT_INSERT_POINT; + } + + // LogStream << "\tIntersection point NOT already in " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " profile {" << nThisProfile << "}, inserted it as point " << nThisLineSeg+1 << endl; + } + + // Get the line segment after intersection + nLineSegAfterIntersect[nn] = nThisLineSeg + 1; + } + + // for (int nn = 0; nn < nNumCoincident; nn++) + // LogStream << "\tFor profile " << prVCoincidentProfiles[nn].first << " line segment [" << nLineSegAfterIntersect[nn] << "] is immediately after the intersection point" << endl; + + // Get the coincident profiles for the to-truncate profile, at the line segment where intersection occurs + vector> prVToTruncateCoincidentProfiles = *pProfileToTruncate->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToTruncateIntersectLineSeg); + int const nNumToTruncateCoincident = static_cast(prVToTruncateCoincidentProfiles.size()); + + // Now add the number of the to-truncate profile, and all its coincident profiles, to all line segments which are seaward of the point of intersection. Do this for the main profile and all profiles which are co-incident for this line segment + for (int nn = 0; nn < nNumCoincident; nn++) + { + int const nThisProfile = prVCoincidentProfiles[nn].first; // The number of this profile + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + + // Get the number of line segments for this to-retain profile (will have just increased, if we just inserted a point) + int const nNumLineSegs = pThisProfile->nGetNumLineSegments(); + + // Do for all line segments seaward of the point of intersection + for (int nLineSeg = nLineSegAfterIntersect[nn], nIncr = 0; nLineSeg < nNumLineSegs; nLineSeg++, nIncr++) + { + // // This can happen occasionally + // if (nThisProfile == nProfileToTruncateIntersectLineSeg) + // { + // LogStream << "\t*** ERROR nThisProfile = " << nThisProfile << " nProfileToTruncateIntersectLineSeg = " << nProfileToTruncateIntersectLineSeg << ", ignoring" << endl; + // pThisProfile->SetHitAnotherProfile(true); + // continue; + // } + + // Add the number of the to-truncate profile, and all its coincident profiles, to this line segment + for (int m = 0; m < nNumToTruncateCoincident; m++) + { + int const nProfileToAdd = prVToTruncateCoincidentProfiles[m].first; + int const nProfileToAddLineSeg = prVToTruncateCoincidentProfiles[m].second; + + // LogStream << "\tAdding " << (nProfileToAdd == nProfileToTruncateIntersectLineSeg ? "main" : "co-incident") << " truncate-profile " << nProfileToAdd << ", line segment [" << nProfileToAddLineSeg + nIncr << "] to line segment " << nLineSeg << " of " << (nThisProfile == nProfileToRetain ? "main" : "co-incident") << " to-retain profile " << nThisProfile << endl; + + pThisProfile->AddCoincidentProfileToExistingLineSegment(nLineSeg, nProfileToAdd, nProfileToAddLineSeg + nIncr); + } + } + } + + // // DEBUG CODE **************************************************************** + // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurred + // vector > prVCoincidentProfilesCHECK2 = *m_VCoast[nCoast].pGetProfile(nProfileToRetain)->pprVGetPairedCoincidentProfilesForLineSegment(nProfileToRetainIntersectLineSeg); + // int nNumCoincidentCHECK2 = prVCoincidentProfilesCHECK2.size(); + // for (int nn = 0; nn < nNumCoincidentCHECK2; nn++) + // { + // int nThisProfile = prVCoincidentProfilesCHECK2[nn].first; + // LogStream << "\tAFTER nInsertPointIntoProfilesIfNeededThenUpdate(): " << (nThisProfile == nProfileToRetain ? "MAIN" : "COINCIDENT") << " to-retain profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) + // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; + // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles and their line segments are "; + // for (int nLineSeg = 0; nLineSeg < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); nLineSeg++) + // { + // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nLineSeg); + // LogStream << "{ "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(nLineSeg); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // + // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) + // { + // CGeom2DPoint + // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), + // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); + // + // if (Pt1 == Pt2) + // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; + // } + // } + // // DEBUG CODE ****************************************************************** + + return RTN_OK; +} + +//=============================================================================================================================== +//! Truncate a profile at the point of intersection, and do the same for all its co-incident profiles +//=============================================================================================================================== +void CSimulation::TruncateProfileAndAppendNew(int const nCoast, CGeomProfile* pProfileToRetain, int const nMainProfileIntersectLineSeg, vector const* pPtVProfileLastPart, vector>> const* pprVLineSegLastPart) +{ + // // DEBUG CODE **************************************************************** + // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurred + // vector > prVCoincidentProfilesCHECK1 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); + // int nNumCoincidentCHECK1 = prVCoincidentProfilesCHECK1.size(); + // + // LogStream << "\tTruncating profile {" << nMainProfile << "}, intersection is at [" << dIntersectX << ", " << dIntersectY << "] in line segment " << nMainProfileIntersectLineSeg << endl; + // for (int nn = 0; nn < nNumCoincidentCHECK1; nn++) + // { + // int nThisProfile = prVCoincidentProfilesCHECK1[nn].first; + // LogStream << "\tBEFORE TruncateProfileAndAppendNew(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points ("; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) + // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; + // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles are "; + // for (int nLineSeg = 0; nLineSeg < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); nLineSeg++) + // { + // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nLineSeg); + // LogStream << "{ "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(nLineSeg); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // + // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) + // { + // CGeom2DPoint + // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), + // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); + // + // if (Pt1 == Pt2) + // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; + // } + // } + // LogStream << "\tPart-profile to append is "; + // for (int mm = 0; mm < pPtVProfileLastPart->size(); mm++) + // LogStream << "[" << pPtVProfileLastPart->at(mm).dGetX() << ", " << pPtVProfileLastPart->at(mm).dGetY() << "] "; + // LogStream << endl; + // LogStream << "\tPart line-segment to append is "; + // for (int mm = 0; mm < pprVLineSegLastPart->size(); mm++) + // { + // vector > prVTmp = pprVLineSegLastPart->at(mm); + // LogStream << "{ "; + // for (int nn = 0; nn < prVTmp.size(); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // // DEBUG CODE ****************************************************************** + + // Get the index numbers of all coincident profiles for the 'main' profile for the line segment in which intersection occurs + vector> prVCoincidentProfiles = *pProfileToRetain->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); + int const nNumCoincident = static_cast(prVCoincidentProfiles.size()); + + for (int nn = 0; nn < nNumCoincident; nn++) + { + // Do this for the main to-truncate profile, and do the same for all its co-incident profiles + int const nThisProfile = prVCoincidentProfiles[nn].first; + int const nThisProfileLineSeg = prVCoincidentProfiles[nn].second; + CGeomProfile* pThisProfile = m_VCoast[nCoast].pGetProfile(nThisProfile); + + // if (nThisProfile == nMainProfile) + // assert(nThisProfileLineSeg == nMainProfileIntersectLineSeg); + + // Truncate the profile + // LogStream << "\tTruncating " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} at line segment " << nThisProfileLineSeg+1 << endl; + pThisProfile->TruncateProfile(nThisProfileLineSeg + 1); + + // Reduce the number of line segments for this profile + pThisProfile->TruncateLineSegments(nThisProfileLineSeg + 1); + + // Append the profile points from the last part of the retain-profile + for (unsigned int mm = 0; mm < pPtVProfileLastPart->size(); mm++) + { + CGeom2DPoint const Pt = pPtVProfileLastPart->at(mm); + pThisProfile->AppendPointInProfile(&Pt); + } + + // Append the line segments, and their co-incident profile numbers, from the last part of the retain-profile + for (unsigned int mm = 0; mm < pprVLineSegLastPart->size(); mm++) + { + vector> prVTmp = pprVLineSegLastPart->at(mm); + + pThisProfile->AppendLineSegment(&prVTmp); + } + + // Fix the line seg numbers for this profile + vector nVProf; + vector nVProfsLineSeg; + + for (int nSeg = 0; nSeg < pThisProfile->nGetNumLineSegments(); nSeg++) + { + for (int nCoinc = 0; nCoinc < pThisProfile->nGetNumCoincidentProfilesInLineSegment(nSeg); nCoinc++) + { + int const nProf = pThisProfile->nGetProf(nSeg, nCoinc); + int const nProfsLineSeg = pThisProfile->nGetProfsLineSeg(nSeg, nCoinc); + + auto it = find(nVProf.begin(), nVProf.end(), nProf); + + if (it == nVProf.end()) + { + // Not found + nVProf.push_back(nProf); + nVProfsLineSeg.push_back(nProfsLineSeg); + } + + else + { + // Found + int const nPos = static_cast(it - nVProf.begin()); + int nNewProfsLineSeg = nVProfsLineSeg[nPos]; + nNewProfsLineSeg++; + + nVProfsLineSeg[nPos] = nNewProfsLineSeg; + pThisProfile->SetProfsLineSeg(nSeg, nCoinc, nNewProfsLineSeg); + } + } + } + + // assert(pThisProfile->nGetProfileSize() > 1); + } + + // // DEBUG CODE **************************************************************** + // Get the index numbers of all coincident profiles for the 'main' to-truncate profile for the line segment in which intersection occurred + // vector > prVToTruncateCoincidentProfilesCHECK2 = *m_VCoast[nCoast].pGetProfile(nMainProfile)->pprVGetPairedCoincidentProfilesForLineSegment(nMainProfileIntersectLineSeg); + // int nNumToTruncateCoincidentCHECK2 = prVToTruncateCoincidentProfilesCHECK2.size(); + // for (int nn = 0; nn < nNumToTruncateCoincidentCHECK2; nn++) + // { + // int nThisProfile = prVToTruncateCoincidentProfilesCHECK2[nn].first; + // LogStream << "\tAFTER TruncateProfileAndAppendNew(): " << (nThisProfile == nMainProfile ? "MAIN" : "COINCIDENT") << " to-truncate profile {" << nThisProfile << "} has " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize() << " points ("; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize(); nn++) + // LogStream << "[" << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetX() << ", " << m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nn)->dGetY() << "] "; + // LogStream << "), and " << m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments() << " line segments, co-incident profiles are "; + // for (int mm = 0; mm < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumLineSegments(); mm++) + // { + // vector > prVTmp = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pprVGetPairedCoincidentProfilesForLineSegment(mm); + // LogStream << "{ "; + // for (int nn = 0; nn < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetNumCoincidentProfilesInLineSegment(mm); nn++) + // LogStream << prVTmp[nn].first << "[" << prVTmp[nn].second << "] "; + // LogStream << "} "; + // } + // LogStream << endl; + // + // for (int nPoint = 0; nPoint < m_VCoast[nCoast].pGetProfile(nThisProfile)->nGetProfileSize()-1; nPoint++) + // { + // CGeom2DPoint + // Pt1 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint), + // Pt2 = *m_VCoast[nCoast].pGetProfile(nThisProfile)->pPtGetPointInProfile(nPoint+1); + // + // if (Pt1 == Pt2) + // LogStream << m_ulIter << ": IDENTICAL POINTS before changes, in profile {" << nThisProfile << "} points = " << nPoint << " and " << nPoint+1 << endl; + // } + // } + // // DEBUG CODE ****************************************************************** +} + diff --git a/src/do_beach_sediment_movement.cpp b/src/do_beach_sediment_movement.cpp index 8362c8a18..451a4192d 100644 --- a/src/do_beach_sediment_movement.cpp +++ b/src/do_beach_sediment_movement.cpp @@ -380,7 +380,7 @@ int CSimulation::nDoAllActualBeachErosionAndDeposition(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -467,7 +467,7 @@ int CSimulation::nDoAllActualBeachErosionAndDeposition(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -549,7 +549,7 @@ int CSimulation::nDoAllActualBeachErosionAndDeposition(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -790,7 +790,7 @@ int CSimulation::nDoAllActualBeachErosionAndDeposition(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // diff --git a/src/do_beach_within_polygon.cpp b/src/do_beach_within_polygon.cpp index f82b792e3..454d02e9e 100644 --- a/src/do_beach_within_polygon.cpp +++ b/src/do_beach_within_polygon.cpp @@ -183,8 +183,8 @@ int CSimulation::nDoUnconsErosionOnPolygon(int const nCoast, CGeomCoastPolygon* int nParProfLen; int nInlandOffset = -1; - double const dParProfCoastElev = m_pRasterGrid->m_Cell[nCoastX][nCoastY].dGetSedimentTopElev(); - double const dParProfEndElev = m_pRasterGrid->m_Cell[nParProfEndX][nParProfEndY].dGetSedimentTopElev(); + double const dParProfCoastElev = m_pRasterGrid->Cell(nCoastX, nCoastY).dGetSedimentTopElev(); + double const dParProfEndElev = m_pRasterGrid->Cell(nParProfEndX, nParProfEndY).dGetSedimentTopElev(); vector VdParProfileDeanElev; @@ -332,7 +332,7 @@ int CSimulation::nDoUnconsErosionOnPolygon(int const nCoast, CGeomCoastPolygon* if (bIsInterventionCell(nX, nY)) bVProfileValid[m] = false; - dVParProfileNow[m] = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + dVParProfileNow[m] = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); } // Get the total difference in elevation (present profile - Dean profile) @@ -530,23 +530,23 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in continue; // Don't do cells twice - if (! m_pRasterGrid->m_Cell[nX][nY].bBeachErosionOrDepositionThisIter()) + if (! m_pRasterGrid->Cell(nX, nY).bBeachErosionOrDepositionThisIter()) { // Get this cell's current elevation - double const dThisElevNow = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dThisElevNow = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // LogStream << "\tnPoly = " << nPoly << ", [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} nCoastPoint = " << nCoastPoint << " nDistSeawardFromNewCoast = " << nDistSeawardFromNewCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << VdParProfileDeanElev[nDistSeawardFromNewCoast] << endl; // Subtract the two elevations double const dElevDiff = dThisElevNow - pVdParProfileDeanElev->at(nDistSeawardFromNewCoast); - if ((dElevDiff > 0) && (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea())) + if ((dElevDiff > 0) && (m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea())) { // The current elevation is higher than the Dean elevation, so we have possible beach erosion (i.e. if not constrained by availability of unconsolidated sediment) here // LogStream << "\tnPoly = " << nPoly << " doing DOWN-COAST, possible beach erosion = " << dElevDiff << " at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "} nCoastPoint = " << nCoastPoint << " nDistSeawardFromNewCoast = " << nDistSeawardFromNewCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << pVdParProfileDeanElev->at(nDistSeawardFromNewCoast) << endl; // Now get the number of the highest layer with non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nThisLayer == INT_NODATA) @@ -596,7 +596,7 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in } } - else if ((dElevDiff < 0) && (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea())) + else if ((dElevDiff < 0) && (m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea())) { if ((nTexture == TEXTURE_SAND) || (nTexture == TEXTURE_COARSE)) { @@ -605,7 +605,7 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in { double dTotToDeposit = tMin(-dElevDiff, dTotEroded); - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -617,8 +617,8 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in if (nTexture == TEXTURE_SAND) { - double const dSandNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dTotToDeposit); + double const dSandNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dTotToDeposit); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -635,8 +635,8 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in if (nTexture == TEXTURE_COARSE) { - double const dCoarseNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dTotToDeposit); + double const dCoarseNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dTotToDeposit); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -653,16 +653,16 @@ int CSimulation::nDoParallelProfileUnconsErosion(CGeomCoastPolygon* pPolygon, in } // Now update the cell's layer elevations - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // Update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); // Update the cell's beach deposition, and total beach deposition, values - m_pRasterGrid->m_Cell[nX][nY].IncrBeachDeposition(dTotToDeposit); + m_pRasterGrid->Cell(nX, nY).IncrBeachDeposition(dTotToDeposit); // And set the landform category - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat != LF_CAT_SEDIMENT_INPUT) && (nCat != LF_CAT_SEDIMENT_INPUT_SUBMERGED) && (nCat != LF_CAT_SEDIMENT_INPUT_NOT_SUBMERGED)) @@ -689,19 +689,19 @@ void CSimulation::ErodeCellBeachSedimentSupplyLimited(int const nX, int const nY if (nTexture == TEXTURE_FINE) { - dExistingAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); + dExistingAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); dErodibility = m_dFineErodibilityNormalized; } else if (nTexture == TEXTURE_SAND) { - dExistingAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + dExistingAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); dErodibility = m_dSandErodibilityNormalized; } else if (nTexture == TEXTURE_COARSE) { - dExistingAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + dExistingAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); dErodibility = m_dCoarseErodibilityNormalized; } @@ -721,34 +721,34 @@ void CSimulation::ErodeCellBeachSedimentSupplyLimited(int const nX, int const nY if (nTexture == TEXTURE_FINE) { // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dRemaining); } else if (nTexture == TEXTURE_SAND) { // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dRemaining); } else if (nTexture == TEXTURE_COARSE) { // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dRemaining); } // And set the changed-this-timestep switch m_bUnconsChangedThisIter[nThisLayer] = true; // Set the actual erosion value for this cell - m_pRasterGrid->m_Cell[nX][nY].SetActualBeachErosion(dRemoved); + m_pRasterGrid->Cell(nX, nY).SetActualBeachErosion(dRemoved); if (dRemoved > 0) { // Recalculate the elevation of every layer - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // And update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); } } @@ -940,7 +940,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo PtiVParProfile.back().SetY(nSeaEndY); } - double const dParProfEndElev = m_pRasterGrid->m_Cell[nSeaEndX][nSeaEndY].dGetSedimentTopElev(); + double const dParProfEndElev = m_pRasterGrid->Cell(nSeaEndX, nSeaEndY).dGetSedimentTopElev(); // Set the start elevation for the Dean profile just a bit above mean SWL for this timestep (i.e. so that it is a Bruun profile) double const dParProfStartElev = m_dThisIterMeanSWL + m_dDeanProfileStartAboveSWL; @@ -968,7 +968,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo double const dInc = dParProfDeanLen / (nParProfLen - nSeawardOffset - 2); // The elevation of the coast point in the Dean profile is the same as the elevation of the current coast point TODO 020 Is this correct? Should it be dParProfStartElev? - double const dCoastElev = m_pRasterGrid->m_Cell[nCoastX][nCoastY].dGetSedimentTopElev(); + double const dCoastElev = m_pRasterGrid->Cell(nCoastX, nCoastY).dGetSedimentTopElev(); // For this depositing parallel profile, calculate the Dean equilibrium profile of the unconsolidated sediment h(y) = A * y^(2/3) where h(y) is the distance below the highest point in the profile at a distance y from the landward start of the profile CalcDeanProfile(&VdParProfileDeanElev, dInc, dParProfStartElev, dParProfA, true, nSeawardOffset, dCoastElev); @@ -996,9 +996,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo continue; // Don't do cells twice - if (! m_pRasterGrid->m_Cell[nX][nY].bBeachErosionOrDepositionThisIter()) + if (! m_pRasterGrid->Cell(nX, nY).bBeachErosionOrDepositionThisIter()) { - double const dTmpElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dTmpElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); double const dDiff = VdParProfileDeanElev[m] - dTmpElev; dParProfTotDiff += dDiff; @@ -1020,7 +1020,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // if (! bIsWithinValidGrid(nX, nY)) // KeepWithinValidGrid(nX, nY); // - // LogStream << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << " "; + // LogStream << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << " "; // } // LogStream << endl; // LogStream << "\tParallel Dean equilibrium profile for deposition = "; @@ -1043,7 +1043,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // KeepWithinValidGrid(nX, nY); // // double - // dTmpElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(), + // dTmpElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(), // dDiff = VdParProfileDeanElev[n] - dTmpElev; // // LogStream << dDiff << " "; @@ -1107,16 +1107,16 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo continue; // Don't do cells twice - if (! m_pRasterGrid->m_Cell[nX][nY].bBeachErosionOrDepositionThisIter()) + if (! m_pRasterGrid->Cell(nX, nY).bBeachErosionOrDepositionThisIter()) { // Get this cell's current elevation - double const dThisElevNow = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dThisElevNow = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // LogStream << "\tnPoly = " << nPoly << ", [" << nX << "][" << nY << "] nCoastPoint = " << nCoastPoint << " nSeawardFromCoast = " << nSeawardFromCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << VdParProfileDeanElev[nSeawardFromCoast] << endl; // Subtract the two elevations double const dElevDiff = VdParProfileDeanElev[nSeawardFromCoast] - dThisElevNow; - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); if (dElevDiff > SEDIMENT_ELEV_TOLERANCE) { @@ -1130,7 +1130,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << " DOWN-COAST nPoly = " << nPoly << " nCoastPoint = " << nCoastPoint << " texture = " << strTexture << " dToDepositHere = " << dToDepositHere << endl; - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -1142,9 +1142,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo if (nTexture == TEXTURE_SAND) { - double const dSandNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + double const dSandNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dToDepositHere); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dToDepositHere); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1155,7 +1155,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << "XXXXXXX texture = " << strTexture << " dDepositedOnProfile = " << dDepositedOnProfile << " dDepositedOnPoly = " << dDepositedOnPoly << endl; // Update the cell's beach deposition, and total beach deposition, values - m_pRasterGrid->m_Cell[nX][nY].IncrBeachDeposition(dToDepositHere); + m_pRasterGrid->Cell(nX, nY).IncrBeachDeposition(dToDepositHere); dStillToDepositOnPoly -= dToDepositHere; dStillToDepositOnProfile -= dToDepositHere; @@ -1170,9 +1170,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo else if (nTexture == TEXTURE_COARSE) { - double const dCoarseNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + double const dCoarseNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dToDepositHere); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dToDepositHere); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1188,7 +1188,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << "XXXXXXX texture = " << strTexture << " dDepositedOnProfile = " << dDepositedOnProfile << " dDepositedOnPoly = " << dDepositedOnPoly << endl; // Update the cell's beach deposition, and total beach deposition, values - m_pRasterGrid->m_Cell[nX][nY].IncrBeachDeposition(dToDepositHere); + m_pRasterGrid->Cell(nX, nY).IncrBeachDeposition(dToDepositHere); dStillToDepositOnPoly -= dToDepositHere; dStillToDepositOnProfile -= dToDepositHere; @@ -1206,10 +1206,10 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo if (bDeposited) { // Update the cell's layer elevations - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // Update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); // And set the landform category int const nCat = pLandform->nGetLFCategory(); @@ -1233,12 +1233,12 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // The current elevation is higher than the Dean elevation, so we have potential beach erosion (i.e. not constrained by availability of unconsolidated sediment) here m_ulThisIterNumPotentialBeachErosionCells++; - m_pRasterGrid->m_Cell[nX][nY].SetPotentialBeachErosion(-dElevDiff); + m_pRasterGrid->Cell(nX, nY).SetPotentialBeachErosion(-dElevDiff); // LogStream << m_ulIter << ": nPoly = " << nPoly << " texture = " << strTexture << " going DOWN-COAST, potential beach erosion = " << -dElevDiff << " at [" << nX << "][" << nY << "] nCoastPoint = " << nCoastPoint << " nSeawardFromCoast = " << nSeawardFromCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << VdParProfileDeanElev[nSeawardFromCoast] << endl; // Now get the number of the highest layer with non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nThisLayer == INT_NODATA) @@ -1441,7 +1441,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo PtiVParProfile.back().SetY(nSeaEndY); } - double const dParProfEndElev = m_pRasterGrid->m_Cell[nSeaEndX][nSeaEndY].dGetSedimentTopElev(); + double const dParProfEndElev = m_pRasterGrid->Cell(nSeaEndX, nSeaEndY).dGetSedimentTopElev(); // Set the start elevation for the Dean profile just a bit above mean SWL for this timestep (i.e. so that it is a Bruun profile) double const dParProfStartElev = m_dThisIterMeanSWL + m_dDeanProfileStartAboveSWL; @@ -1465,7 +1465,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo double const dInc = dParProfDeanLen / (nParProfLen - nSeawardOffset - 2); // The elevation of the coast point in the Dean profile is the same as the elevation of the current coast point TODO 020 Is this correct? Should it be dParProfStartElev? - double const dCoastElev = m_pRasterGrid->m_Cell[nCoastX][nCoastY].dGetSedimentTopElev(); + double const dCoastElev = m_pRasterGrid->Cell(nCoastX, nCoastY).dGetSedimentTopElev(); // For this depositing parallel profile, calculate the Dean equilibrium profile of the unconsolidated sediment h(y) = A * y^(2/3) where h(y) is the distance below the highest point in the profile at a distance y from the landward start of the profile CalcDeanProfile(&VdParProfileDeanElev, dInc, dParProfStartElev, dParProfA, true, nSeawardOffset, dCoastElev); @@ -1493,9 +1493,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo continue; // Don't do cells twice - if (! m_pRasterGrid->m_Cell[nX][nY].bBeachErosionOrDepositionThisIter()) + if (! m_pRasterGrid->Cell(nX, nY).bBeachErosionOrDepositionThisIter()) { - double const dTmpElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dTmpElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); double const dDiff = VdParProfileDeanElev[m] - dTmpElev; dParProfTotDiff += dDiff; @@ -1517,7 +1517,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // if (! bIsWithinValidGrid(nX, nY)) // KeepWithinValidGrid(nX, nY); // - // LogStream << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << " "; + // LogStream << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << " "; // } // LogStream << endl; // LogStream << "\tParallel Dean equilibrium profile for deposition = "; @@ -1540,7 +1540,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // KeepWithinValidGrid(nX, nY); // // double - // dTmpElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(), + // dTmpElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(), // dDiff = VdParProfileDeanElev[n] - dTmpElev; // // LogStream << dDiff << " "; @@ -1604,16 +1604,16 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo continue; // Don't do cells twice - if (! m_pRasterGrid->m_Cell[nX][nY].bBeachErosionOrDepositionThisIter()) + if (! m_pRasterGrid->Cell(nX, nY).bBeachErosionOrDepositionThisIter()) { // Get this cell's current elevation - double const dThisElevNow = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dThisElevNow = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // LogStream << "\tnPoly = " << nPoly << " going UP-COAST, [" << nX << "][" << nY << "] nCoastPoint = " << nCoastPoint << " nSeawardFromCoast = " << nSeawardFromCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << VdParProfileDeanElev[nSeawardFromCoast] << endl; // Subtract the two elevations double const dElevDiff = VdParProfileDeanElev[nSeawardFromCoast] - dThisElevNow; - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); if (dElevDiff > SEDIMENT_ELEV_TOLERANCE) { @@ -1627,7 +1627,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << " UP-COAST nPoly = " << nPoly << " nCoastPoint = " << nCoastPoint << " texture = " << strTexture << " dToDepositHere = " << dToDepositHere << endl; - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -1639,9 +1639,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo if (nTexture == TEXTURE_SAND) { - double const dSandNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + double const dSandNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dToDepositHere); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandNow + dToDepositHere); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1652,7 +1652,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << "XXXXXXX texture = " << strTexture << " dDepositedOnProfile = " << dDepositedOnProfile << " dDepositedOnPoly = " << dDepositedOnPoly << endl; // Update the cell's beach deposition, and total beach deposition, values - m_pRasterGrid->m_Cell[nX][nY].IncrBeachDeposition(dToDepositHere); + m_pRasterGrid->Cell(nX, nY).IncrBeachDeposition(dToDepositHere); dStillToDepositOnPoly -= dToDepositHere; dStillToDepositOnProfile -= dToDepositHere; @@ -1667,9 +1667,9 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo else if (nTexture == TEXTURE_COARSE) { - double const dCoarseNow = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + double const dCoarseNow = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dToDepositHere); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseNow + dToDepositHere); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1685,7 +1685,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // LogStream << "XXXXXXX texture = " << strTexture << " dDepositedOnProfile = " << dDepositedOnProfile << " dDepositedOnPoly = " << dDepositedOnPoly << endl; // Update the cell's beach deposition, and total beach deposition, values - m_pRasterGrid->m_Cell[nX][nY].IncrBeachDeposition(dToDepositHere); + m_pRasterGrid->Cell(nX, nY).IncrBeachDeposition(dToDepositHere); dStillToDepositOnPoly -= dToDepositHere; dStillToDepositOnProfile -= dToDepositHere; @@ -1698,10 +1698,10 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo if (bDeposited) { // Now update the cell's layer elevations - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // Update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); int const nCat = pLandform->nGetLFCategory(); @@ -1724,12 +1724,12 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // The current elevation is higher than the Dean elevation, so we could have beach erosion here m_ulThisIterNumPotentialBeachErosionCells++; - m_pRasterGrid->m_Cell[nX][nY].SetPotentialBeachErosion(-dElevDiff); + m_pRasterGrid->Cell(nX, nY).SetPotentialBeachErosion(-dElevDiff); // LogStream << m_ulIter << ": nPoly = " << nPoly << " texture = " << strTexture << " going UP-COAST, potential beach erosion = " << -dElevDiff << " at [" << nX << "][" << nY << "] nCoastPoint = " << nCoastPoint << " nSeawardFromCoast = " << nSeawardFromCoast << " dThisElevNow = " << dThisElevNow << " Dean Elev = " << VdParProfileDeanElev[nSeawardFromCoast] << endl; // Now get the number of the highest layer with non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nThisLayer == INT_NODATA) @@ -1837,7 +1837,7 @@ int CSimulation::nDoUnconsDepositionOnPolygon(int const nCoast, CGeomCoastPolygo // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -1856,7 +1856,7 @@ bool CSimulation::bElevAboveDeanElev(int const nX, int const nY, double const dE // TODO 075 What if it is bedrock that sticks above Dean profile? if (dElevDiff <= 0) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) return true; int const nCat = pLandform->nGetLFCategory(); diff --git a/src/do_cliff_collapse.cpp b/src/do_cliff_collapse.cpp index 9bc654e7b..104105181 100644 --- a/src/do_cliff_collapse.cpp +++ b/src/do_cliff_collapse.cpp @@ -69,7 +69,7 @@ int CSimulation::nDoAllWaveEnergyToCoastLandforms(void) int const nX = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastPoint)->nGetX(); int const nY = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(nCoastPoint)->nGetY(); int const nCat = pCoastLandform->nGetLandFormCategory(); - double const dTopElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dTopElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); if ((nCat == LF_CAT_CLIFF) || (nCat == LF_SUBCAT_CLIFF_ON_COASTLINE) || (nCat == LF_SUBCAT_CLIFF_INLAND)) { @@ -144,9 +144,9 @@ int CSimulation::nDoAllWaveEnergyToCoastLandforms(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandCons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotConsSandThickConsiderNotch(); + // dTmpSandCons += m_pRasterGrid->Cell(nX1, nY1).dGetTotConsSandThickConsiderNotch(); // - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -155,7 +155,7 @@ int CSimulation::nDoAllWaveEnergyToCoastLandforms(void) // int nYCliff = pCliff->pPtiGetCellMarkedAsCliff()->nGetY(); // // // Get this cell's polygon - // int nPoly = m_pRasterGrid->m_Cell[nXCliff][nYCliff].nGetPolygonID(); + // int nPoly = m_pRasterGrid->Cell(nXCliff, nYCliff).nGetPolygonID(); // // LogStream << endl; // LogStream << "*****************************" << endl; @@ -194,9 +194,9 @@ int CSimulation::nDoAllWaveEnergyToCoastLandforms(void) // { // for (int nY1 = 0; nY1 < m_nYGridSize; nY1++) // { - // dTmpSandCons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotConsSandThickConsiderNotch(); + // dTmpSandCons += m_pRasterGrid->Cell(nX1, nY1).dGetTotConsSandThickConsiderNotch(); // - // dTmpSandUncons += m_pRasterGrid->m_Cell[nX1][nY1].dGetTotUnconsSand(); + // dTmpSandUncons += m_pRasterGrid->Cell(nX1, nY1).dGetTotUnconsSand(); // } // } // @@ -205,7 +205,7 @@ int CSimulation::nDoAllWaveEnergyToCoastLandforms(void) // int nYCliff = pCliff->pPtiGetCellMarkedAsCliff()->nGetY(); // // // Get this cell's polygon - // int nPoly = m_pRasterGrid->m_Cell[nXCliff][nYCliff].nGetPolygonID(); + // int nPoly = m_pRasterGrid->Cell(nXCliff, nYCliff).nGetPolygonID(); // // LogStream << endl; // LogStream << "*****************************" << endl; @@ -252,7 +252,7 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF int const nY = pCliff->pPtiGetCellMarkedAsCliff()->nGetY(); // Get this cell's polygon - int const nPoly = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); + int const nPoly = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); if (nPoly == INT_NODATA) { @@ -264,13 +264,13 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF CGeomCoastPolygon* pPolygon = m_VCoast[nCoast].pGetPolygon(nPoly); - double const dOrigCliffTopElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + double const dOrigCliffTopElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // Get the elevation of the base of the notch from the cliff object double const dNotchElev = pCliff->dGetNotchBaseElev(); // Get the index of the layer containing the notch (layer 0 being just above basement) - int const nNotchLayer = m_pRasterGrid->m_Cell[nX][nY].nGetLayerAtElev(dNotchElev); + int const nNotchLayer = m_pRasterGrid->Cell(nX, nY).nGetLayerAtElev(dNotchElev); // Safety checks if (nNotchLayer == ELEV_IN_BASEMENT) @@ -292,7 +292,7 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF // Notch layer is OK, so flag the coastline cliff object as having collapsed pCliff->SetCliffCollapsed(); - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -310,7 +310,7 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF m_bUnconsChangedThisIter[nTopLayer] = true; // Get the pre-collapse cliff elevation - dPreCollapseCliffElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + dPreCollapseCliffElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // Now calculate the vertical depth of sediment lost in this cliff collapse. In CoastalME, all depth equivalents are assumed to be a depth upon the whole of a cell i.e. upon the area of a whole cell. So to keep the depth of cliff collapse consistent with all other depth equivalents, weight it by the fraction of the cell's area which is being removed double dAvailable = 0; @@ -325,76 +325,76 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF for (int n = nTopLayer; n > nNotchLayer; n--) { // Start with the unconsolidated sediment - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetFineDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchFineLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetFineDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchFineLost(); if (dAvailable > 0) { dFineCollapse += dAvailable; dFineUnconsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetFineDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchFineLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetFineDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchFineLost(0); } - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetSandDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchSandLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetSandDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchSandLost(); if (dAvailable > 0) { dSandCollapse += dAvailable; dSandUnconsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetSandDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchSandLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetSandDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchSandLost(0); } - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetCoarseDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchCoarseLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetCoarseDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->dGetNotchCoarseLost(); if (dAvailable > 0) { dCoarseCollapse += dAvailable; dCoarseUnconsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetCoarseDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchCoarseLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetCoarseDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetUnconsolidatedSediment()->SetNotchCoarseLost(0); } // Now get the consolidated sediment - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetFineDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchFineLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetFineDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchFineLost(); if (dAvailable > 0) { dFineCollapse += dAvailable; dFineConsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetFineDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchFineLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetFineDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchFineLost(0); } - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetSandDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchSandLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetSandDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchSandLost(); if (dAvailable > 0) { dSandCollapse += dAvailable; dSandConsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetSandDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchSandLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetSandDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchSandLost(0); } - dAvailable = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetCoarseDepth() - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchCoarseLost(); + dAvailable = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetCoarseDepth() - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->dGetNotchCoarseLost(); if (dAvailable > 0) { dCoarseCollapse += dAvailable; dCoarseConsLost += dAvailable; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetCoarseDepth(0); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchCoarseLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetCoarseDepth(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(n)->pGetConsolidatedSediment()->SetNotchCoarseLost(0); } } // Now sort out the sediment lost from the consolidated layer into which the erosional notch was incised - double const dNotchLayerTop = m_pRasterGrid->m_Cell[nX][nY].dCalcLayerElev(nNotchLayer); - double const dNotchLayerThickness = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->dGetTotalThickness(); + double const dNotchLayerTop = m_pRasterGrid->Cell(nX, nY).dCalcLayerElev(nNotchLayer); + double const dNotchLayerThickness = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->dGetTotalThickness(); double const dNotchLayerFracRemoved = (dNotchLayerTop - dNotchElev) / dNotchLayerThickness; // Sort out the notched layer's sediment, both consolidated and unconsolidated, for this cell. First the unconsolidated sediment - double dFineDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); - dAvailable = dFineDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchFineLost(); + double dFineDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); + dAvailable = dFineDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchFineLost(); if (dAvailable > 0) { @@ -402,12 +402,12 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dFineCollapse += dLost; dFineUnconsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dFineDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchFineLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dFineDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchFineLost(0); } - double dSandDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - dAvailable = dSandDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchSandLost(); + double dSandDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + dAvailable = dSandDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchSandLost(); if (dAvailable > 0) { @@ -415,12 +415,12 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dSandCollapse += dLost; dSandUnconsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchSandLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dSandDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchSandLost(0); } - double dCoarseDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - dAvailable = dCoarseDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchCoarseLost(); + double dCoarseDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + dAvailable = dCoarseDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->dGetNotchCoarseLost(); if (dAvailable > 0) { @@ -428,13 +428,13 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dCoarseCollapse += dLost; dCoarseUnconsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchCoarseLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dCoarseDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetUnconsolidatedSediment()->SetNotchCoarseLost(0); } // Now do the consolidated sediment - dFineDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetFineDepth(); - dAvailable = dFineDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchFineLost(); + dFineDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetFineDepth(); + dAvailable = dFineDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchFineLost(); if (dAvailable > 0) { @@ -442,12 +442,12 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dFineCollapse += dLost; dFineConsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetFineDepth(dFineDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchFineLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetFineDepth(dFineDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchFineLost(0); } - dSandDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetSandDepth(); - dAvailable = dSandDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchSandLost(); + dSandDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetSandDepth(); + dAvailable = dSandDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchSandLost(); if (dAvailable > 0) { @@ -455,12 +455,12 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dSandCollapse += dLost; dSandConsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetSandDepth(dSandDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchSandLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetSandDepth(dSandDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchSandLost(0); } - dCoarseDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); - dAvailable = dCoarseDepth - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchCoarseLost(); + dCoarseDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); + dAvailable = dCoarseDepth - m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->dGetNotchCoarseLost(); if (dAvailable > 0) { @@ -468,26 +468,26 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF double const dLost = dAvailable * dNotchLayerFracRemoved; dCoarseCollapse += dLost; dCoarseConsLost += dLost; - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetCoarseDepth(dCoarseDepth - dLost); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchCoarseLost(0); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetCoarseDepth(dCoarseDepth - dLost); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nNotchLayer)->pGetConsolidatedSediment()->SetNotchCoarseLost(0); } // Update the cell's totals for cliff collapse erosion - m_pRasterGrid->m_Cell[nX][nY].IncrCliffCollapseErosion(dFineCollapse, dSandCollapse, dCoarseCollapse); + m_pRasterGrid->Cell(nX, nY).IncrCliffCollapseErosion(dFineCollapse, dSandCollapse, dCoarseCollapse); // Update the cell's layer elevations and d50 - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // Get the post-collapse cliff elevation - dPostCollapseCliffElev = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + dPostCollapseCliffElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) LogStream << m_ulIter << ": cliff collapse at [" << nX << "][" << nY << "], original cliff elevation = " << dOrigCliffTopElev << ", new cliff elevation = " << dPostCollapseCliffElev << ", change in elevation = " << dOrigCliffTopElev - dPostCollapseCliffElev << endl; // And update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); - // LogStream << m_ulIter << ": cell [" << nX << "][" << nY << "] after removing sediment, dGetVolEquivSedTopElev() = " << m_pRasterGrid->m_Cell[nX][nY].dGetVolEquivSedTopElev() << ", dGetSedimentTopElev() = " << m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() << endl << endl; + // LogStream << m_ulIter << ": cell [" << nX << "][" << nY << "] after removing sediment, dGetVolEquivSedTopElev() = " << m_pRasterGrid->Cell(nX, nY).dGetVolEquivSedTopElev() << ", dGetSedimentTopElev() = " << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << endl << endl; // Update this-polygon totals: add to the depths of cliff collapse erosion for this polygon pPolygon->AddCliffCollapseErosionFine(dFineCollapse); @@ -512,16 +512,16 @@ int CSimulation::nDoCliffCollapse(int const nCoast, CRWCliff* pCliff, double& dF m_dThisIterCliffCollapseErosionCoarseCons += dCoarseConsLost; // Save the timestep at which cliff collapse occurred - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffCollapseTimestep(m_ulIter); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffCollapseTimestep(m_ulIter); // Reset cell cliff info - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetCliffNotchDepth(m_dCellSide); + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetCliffNotchDepth(m_dCellSide); // And update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); // Final safety check - int const nNewTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopLayerAboveBasement(); + int const nNewTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopLayerAboveBasement(); if (nNewTopLayer == INT_NODATA) return RTN_ERR_NO_TOP_LAYER; @@ -552,7 +552,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC // double const dYCliff = dGridYToExtCRSY(nYCliff); // Get this cell's polygon - int const nPoly = m_pRasterGrid->m_Cell[nXCliff][nYCliff].nGetPolygonID(); + int const nPoly = m_pRasterGrid->Cell(nXCliff, nYCliff).nGetPolygonID(); if (nPoly == INT_NODATA) { // This cell isn't in a polygon @@ -589,7 +589,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC // int const nYEnd = PtiEnd.nGetY(); // // // Safety check: is the end point in the contiguous sea? - // if (! m_pRasterGrid->m_Cell[nXEnd][nYEnd].bIsInContiguousSea()) + // if (! m_pRasterGrid->Cell(nXEnd, nYEnd).bIsInContiguousSea()) // { // // if (m_nLogFileDetail >= LOG_FILE_ALL) // // LogStream << m_ulIter << ": coast " << nCoast << ", possible profile with start point " << nProfileStartPoint << " has inland end point at [" << nXEnd << "][" << nYEnd << "] = {" << dGridCentroidXToExtCRSX(nXEnd) << ", " << dGridCentroidYToExtCRSY(nYEnd) << "}, ignoring" << endl; @@ -866,7 +866,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC int const nX = VCellsUnderProfile[n].nGetX(); int const nY = VCellsUnderProfile[n].nGetY(); - dVProfileNow[n] = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + dVProfileNow[n] = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // Don't allow cliff collapse talus onto intervention cells TODO 078 Is this realistic? Should it change with different types on intervention? if (bIsInterventionCell(nX, nY)) @@ -913,7 +913,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC // int // nX = VCellsUnderProfile[n].nGetX(), // nY = VCellsUnderProfile[n].nGetY(); - // dVProfileNow[n] = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + // dVProfileNow[n] = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // LogStream << dVProfileNow[n] << " "; // } // LogStream << endl; @@ -977,7 +977,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC if (bIsInterventionCell(nX, nY)) continue; - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -1001,7 +1001,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC dSandToDeposit = (dVDeanProfile[n] - dVProfileNow[n]) * dSandProp; dSandToDeposit = tMin(dSandToDeposit, (dTargetSandToDepositOnThisProfile - dSandDepositedOnThisProfile), (dTotSandToDepositAllProfiles - dTotSandDepositedAllProfiles)); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandDepth(dSandToDeposit); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandDepth(dSandToDeposit); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1017,7 +1017,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC dCoarseToDeposit = (dVDeanProfile[n] - dVProfileNow[n]) * dCoarseProp; dCoarseToDeposit = tMin(dCoarseToDeposit, (dTargetCoarseToDepositOnThisProfile - dCoarseDepositedOnThisProfile), (dTotCoarseToDepositAllProfiles - dTotCoarseDepositedAllProfiles)); - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseDepth(dCoarseToDeposit); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseDepth(dCoarseToDeposit); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1027,17 +1027,17 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC } // Now update the cell's layer elevations - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // Update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); // Update the cell's talus deposition, and total talus deposition, values - m_pRasterGrid->m_Cell[nX][nY].AddSandTalusDeposition(dSandToDeposit); - m_pRasterGrid->m_Cell[nX][nY].AddCoarseTalusDeposition(dCoarseToDeposit); + m_pRasterGrid->Cell(nX, nY).AddSandTalusDeposition(dSandToDeposit); + m_pRasterGrid->Cell(nX, nY).AddCoarseTalusDeposition(dCoarseToDeposit); // And set the landform category - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); if ((nCat != LF_CAT_SEDIMENT_INPUT) && (nCat != LF_CAT_SEDIMENT_INPUT_SUBMERGED) && (nCat != LF_CAT_SEDIMENT_INPUT_NOT_SUBMERGED)) @@ -1049,9 +1049,9 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC double const dThisLowering = dVProfileNow[n] - dVDeanProfile[n]; // Find out how much sediment we have available on this cell - double const dExistingAvailableFine = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); - double const dExistingAvailableSand = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - double const dExistingAvailableCoarse = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + double const dExistingAvailableFine = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); + double const dExistingAvailableSand = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + double const dExistingAvailableCoarse = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); // Now partition the total lowering for this cell between the three size fractions: do this by relative erodibility int const nFineWeight = (dExistingAvailableFine > 0 ? 1 : 0); @@ -1070,7 +1070,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC double const dRemaining = dExistingAvailableFine - dFine; // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetFineDepth(dRemaining); // And set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1094,7 +1094,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC double const dRemaining = dExistingAvailableSand - dSandToErode; // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetSandDepth(dRemaining); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1122,7 +1122,7 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC double const dRemaining = dExistingAvailableCoarse - dCoarseToErode; // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dRemaining); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->SetCoarseDepth(dRemaining); // Set the changed-this-timestep switch m_bUnconsChangedThisIter[nTopLayer] = true; @@ -1141,10 +1141,10 @@ int CSimulation::nDoCliffCollapseDeposition(int const nCoast, CRWCliff const* pC } // for this cell, recalculate the elevation of every layer - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nX, nY).CalcAllLayerElevsAndD50(); // And update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); } } // All cells in this profile diff --git a/src/do_sediment_input_event.cpp b/src/do_sediment_input_event.cpp index 35033e9bc..3c7b8138e 100644 --- a/src/do_sediment_input_event.cpp +++ b/src/do_sediment_input_event.cpp @@ -101,21 +101,21 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) return RTN_ERR_SEDIMENT_INPUT_EVENT; // All OK, so get landform - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nPointGridX, nPointGridY).pGetLandform(); // Is this sediment input event at a pre-specified fixed point, or in a block on a coast, or where a line intersects with a coast? if (m_bSedimentInputAtPoint) { // Sediment input is at a user-specified point - int const nTopLayer = m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nPointGridX, nPointGridY).nGetTopLayerAboveBasement(); // Is this user-specified point in a polygon? - int const nThisPoly = m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].nGetPolygonID(); + int const nThisPoly = m_pRasterGrid->Cell(nPointGridX, nPointGridY).nGetPolygonID(); int nThisPolyCoast = INT_NODATA; if (nThisPoly != INT_NODATA) { // Yes we are in a polygon, so get the coast ID of the polygon for this cell - nThisPolyCoast = m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].nGetPolygonCoastID(); + nThisPolyCoast = m_pRasterGrid->Cell(nPointGridX, nPointGridY).nGetPolygonCoastID(); // Safety check if (nThisPolyCoast == INT_NODATA) @@ -137,10 +137,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dFineDepth > 0) { // Yes, so add to this cell's fine unconsolidated sediment - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepth); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).CalcAllLayerElevsAndD50(); // If we are in a polygon, then add to this polygon's sand sediment input total if (nThisPoly != INT_NODATA) @@ -159,10 +159,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dSandDepth > 0) { // Yes, so add to this cell's sand unconsolidated sediment - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepth); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).CalcAllLayerElevsAndD50(); // If we are in a polygon, then add to this polygon's sand sediment input total if (nThisPoly != INT_NODATA) @@ -181,10 +181,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dCoarseDepth > 0) { // Yes, so add to this cell's coarse unconsolidated sediment - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepth); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nPointGridX, nPointGridY).CalcAllLayerElevsAndD50(); // If we are in a polygon, then add to this polygon's coarse sediment input total if (nThisPoly != INT_NODATA) @@ -216,8 +216,8 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) LogStream << m_ulIter << ": Closest coast point is at [" << nCoastX << "][" << nCoastY << "] = {" << dGridXToExtCRSX(nCoastX) << ", " << dGridYToExtCRSY(nCoastY) << "}, along-coast width of sediment block = " << dWidth << " m, coast-normal length of sediment block = " << dLen << " m" << endl; - int const nCoast = m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLandform()->nGetCoast(); - int const nCoastPoint = m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLandform()->nGetPointOnCoast(); + int const nCoast = m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLandform()->nGetCoast(); + int const nCoastPoint = m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLandform()->nGetPointOnCoast(); int const nHalfWidth = nRound(dWidth / m_dCellSide); int const nLength = nRound(dLen / m_dCellSide); int const nCoastLen = m_VCoast[nCoast].nGetCoastlineSize(); @@ -362,7 +362,7 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) double const dCoarseDepthPerCell = dCoarseDepth / dArea; // OK, so finally: put some sediment onto each cell in the sediment block - int const nTopLayer = m_pRasterGrid->m_Cell[nPointGridX][nPointGridY].nGetTopLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nPointGridX, nPointGridY).nGetTopLayerAboveBasement(); for (unsigned int n = 0; n < nArea; n++) { @@ -370,13 +370,13 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) int const nY = VPoints[n].nGetY(); // Add to this cell's fine unconsolidated sediment - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepthPerCell); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepthPerCell); // Add to this cell's sand unconsolidated sediment - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepthPerCell); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepthPerCell); // Add to this cell's coarse unconsolidated sediment - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepthPerCell); + m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepthPerCell); } // Add to the this-iteration totals @@ -479,7 +479,7 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) int const nY = nRound(dY); // Have we hit a coastline cell? - if (bIsWithinValidGrid(nX, nY) && m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + if (bIsWithinValidGrid(nX, nY) && m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { nCoastX = nX; nCoastY = nY; @@ -487,7 +487,7 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) } // Two diagonal(ish) raster lines can cross each other without any intersection, so must also test an adjacent cell for intersection (does not matter which adjacent cell) - if (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->m_Cell[nX][nY + 1].bIsCoastline()) + if (bIsWithinValidGrid(nX, nY + 1) && m_pRasterGrid->Cell(nX, nY + 1).bIsCoastline()) { nCoastX = nX; nCoastY = nY + 1; @@ -515,16 +515,16 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) } // OK we have an intersection of the line and coast. We will input the sediment here. Get landform and top layer - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLandform(); - int const nTopLayer = m_pRasterGrid->m_Cell[nCoastX][nCoastY].nGetTopLayerAboveBasement(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLandform(); + int const nTopLayer = m_pRasterGrid->Cell(nCoastX, nCoastY).nGetTopLayerAboveBasement(); // Is this intersection point in a polygon? - int const nThisPoly = m_pRasterGrid->m_Cell[nCoastX][nCoastY].nGetPolygonID(); + int const nThisPoly = m_pRasterGrid->Cell(nCoastX, nCoastY).nGetPolygonID(); int nThisPolyCoast = INT_NODATA; if (nThisPoly != INT_NODATA) { // Yes we are in a polygon, so get the coast ID of the polygon for this cell - nThisPolyCoast = m_pRasterGrid->m_Cell[nCoastX][nCoastY].nGetPolygonCoastID(); + nThisPolyCoast = m_pRasterGrid->Cell(nCoastX, nCoastY).nGetPolygonCoastID(); // Safety check if (nThisPolyCoast == INT_NODATA) @@ -546,10 +546,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dFineDepth > 0) { // Yes, so add to this cell's fine unconsolidated sediment - m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepth); + m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddFineSedimentInputDepth(dFineDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nCoastX][nCoastY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nCoastX, nCoastY).CalcAllLayerElevsAndD50(); if (nThisPoly != INT_NODATA) // Add to this polygon's fine sediment input total @@ -567,10 +567,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dSandDepth > 0) { // Yes, so add to this cell's sand unconsolidated sediment - m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepth); + m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddSandSedimentInputDepth(dSandDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nCoastX][nCoastY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nCoastX, nCoastY).CalcAllLayerElevsAndD50(); if (nThisPoly != INT_NODATA) // Add to this polygon's sand sediment input total @@ -588,10 +588,10 @@ int CSimulation::nDoSedimentInputEvent(int const nEvent) if (dCoarseDepth > 0) { // Yes, so add to this cell's coarse unconsolidated sediment - m_pRasterGrid->m_Cell[nCoastX][nCoastY].pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepth); + m_pRasterGrid->Cell(nCoastX, nCoastY).pGetLayerAboveBasement(nTopLayer)->pGetUnconsolidatedSediment()->AddCoarseSedimentInputDepth(dCoarseDepth); // And update the sediment top elevation value - m_pRasterGrid->m_Cell[nCoastX][nCoastY].CalcAllLayerElevsAndD50(); + m_pRasterGrid->Cell(nCoastX, nCoastY).CalcAllLayerElevsAndD50(); if (nThisPoly != INT_NODATA) // Add to this polygon's coarse sediment input total diff --git a/src/do_shore_platform_erosion.cpp b/src/do_shore_platform_erosion.cpp index 586bd4bda..76fba1f7b 100644 --- a/src/do_shore_platform_erosion.cpp +++ b/src/do_shore_platform_erosion.cpp @@ -37,6 +37,7 @@ using std::array; using std::shuffle; #include "cme.h" +#include "cell.h" #include "hermite_cubic.h" #include "simulation.h" #include "coast.h" @@ -68,7 +69,7 @@ int CSimulation::nDoAllShorePlatFormErosion(void) // { // for (int nX = 0; nX < m_nXGridSize; nX++) // { - // int nID = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); + // int nID = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); // if (nID == INT_NODATA) // nNotInPoly++; // else @@ -146,13 +147,20 @@ int CSimulation::nDoAllShorePlatFormErosion(void) FillInBeachProtectionHoles(); // Finally calculate actual platform erosion on all sea cells (both on profiles, and between profiles) + #pragma omp parallel for collapse(2) schedule(static) \ + reduction(+:m_dThisIterActualPlatformErosionFineCons, m_dThisIterFineSedimentToSuspension, \ + m_dThisIterActualPlatformErosionSandCons, m_dThisIterActualPlatformErosionCoarseCons, \ + m_ulThisIterNumActualPlatformErosionCells, m_dDepositionSandDiff, m_dDepositionCoarseDiff) for (int nX = 0; nX < m_nXGridSize; nX++) { for (int nY = 0; nY < m_nYGridSize; nY++) { - if (m_pRasterGrid->m_Cell[nX][nY].bPotentialPlatformErosion()) + // Cache cell reference to avoid repeated array lookups + auto& rCell = m_pRasterGrid->Cell(nX, nY); + + if (rCell.bPotentialPlatformErosion()) // Calculate actual (supply-limited) shore platform erosion on each cell that has potential platform erosion, also add the eroded sand/coarse sediment to that cell's polygon, ready to be redistributed within the polygon during beach erosion/deposition - DoActualPlatformErosionOnCell(nX, nY); + DoActualPlatformErosionOnCell(nX, nY, rCell); } } @@ -232,7 +240,7 @@ int CSimulation::nCalcPotentialPlatformErosionOnProfile(int const nCoast, CGeomP int const nY = pProfile->pPtiVGetCellsInProfile()->at(i).nGetY(); // Get the number of the highest layer with non-zero thickness - int const nTopLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -243,10 +251,10 @@ int CSimulation::nCalcPotentialPlatformErosionOnProfile(int const nCoast, CGeomP return RTN_OK; // Get the elevation for consolidated sediment only on this cell - dVConsProfileZ[i] = m_pRasterGrid->m_Cell[nX][nY].dGetConsSedTopElevForLayerAboveBasement(nTopLayer); + dVConsProfileZ[i] = m_pRasterGrid->Cell(nX, nY).dGetConsSedTopElevForLayerAboveBasement(nTopLayer); // Get the elevation for both consolidated and unconsolidated sediment on this cell - VdProfileZ[i] = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); + VdProfileZ[i] = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); // And store the X-Y plane distance from the start of the profile VdProfileDistXY[i] = i * dSpacingXY; @@ -349,13 +357,13 @@ int CSimulation::nCalcPotentialPlatformErosionOnProfile(int const nCoast, CGeomP int const nY = pProfile->pPtiVGetCellsInProfile()->at(i).nGetY(); // Store the local slope of the consolidated sediment, this is just for output display purposes - m_pRasterGrid->m_Cell[nX][nY].SetLocalConsSlope(dVConsSlope[i]); + m_pRasterGrid->Cell(nX, nY).SetLocalConsSlope(dVConsSlope[i]); // dDeltaZ is zero or -ve: if dDeltaZ is zero then do nothing, if -ve then remove some sediment from this cell if (dDeltaZ < 0) { // If there has already been potential erosion on this cell, then it must be a shared line segment (i.e. has co-incident profiles) - double const dPrevPotentialErosion = -m_pRasterGrid->m_Cell[nX][nY].dGetPotentialPlatformErosion(); + double const dPrevPotentialErosion = -m_pRasterGrid->Cell(nX, nY).dGetPotentialPlatformErosion(); if (dPrevPotentialErosion < 0) { @@ -370,7 +378,7 @@ int CSimulation::nCalcPotentialPlatformErosionOnProfile(int const nCoast, CGeomP dVChangeElevZ[i] = dDeltaZ; // Set the potential (unconstrained) erosion for this cell, is a +ve value - m_pRasterGrid->m_Cell[nX][nY].SetPotentialPlatformErosion(-dDeltaZ); + m_pRasterGrid->Cell(nX, nY).SetPotentialPlatformErosion(-dDeltaZ); // Update this-timestep totals m_ulThisIterNumPotentialPlatformErosionCells++; @@ -385,7 +393,7 @@ int CSimulation::nCalcPotentialPlatformErosionOnProfile(int const nCoast, CGeomP // Finally, calculate the beach protection factor, this will be used in estimating actual (supply-limited) erosion double const dBeachProtectionFactor = dCalcBeachProtectionFactor(nX, nY, dBreakingWaveHeight); - m_pRasterGrid->m_Cell[nX][nY].SetBeachProtectionFactor(dBeachProtectionFactor); + m_pRasterGrid->Cell(nX, nY).SetBeachProtectionFactor(dBeachProtectionFactor); } // If desired, save this coastline-normal consolidated-only profile data for checking purposes @@ -479,7 +487,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, } // Is this coastline start point the start point of an adjacent coastline-normal vector? - if (m_pRasterGrid->m_Cell[nParCoastX][nParCoastY].bIsProfile()) + if (m_pRasterGrid->Cell(nParCoastX, nParCoastY).bIsProfile()) { // LogStream << m_ulIter << ": coast " << nCoast << ", LEAVING LOOP since hit another profile at nThisPointOnCoast = " << nThisPointOnCoast << " while doing potential platform erosion " << (nDirection == DIRECTION_DOWNCOAST ? "down" : "up") << "-coast from profile = " << nProfile << ", dist from profile = " << nDistFromProfile << endl; break; @@ -530,7 +538,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, int const nYPar = PtiVGridParProfile[i].nGetY(); // Is this a sea cell? - if (! m_pRasterGrid->m_Cell[nXPar][nYPar].bIsInundated()) + if (! m_pRasterGrid->Cell(nXPar, nYPar).bIsInundated()) { // It isn't so move along, nothing to do here // LogStream << m_ulIter << " : [" << nXPar << "][" << nYPar << "] is not inundated" << endl; @@ -538,7 +546,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, } // Is this cell in a polygon? - int const nPolyID = m_pRasterGrid->m_Cell[nXPar][nYPar].nGetPolygonID(); + int const nPolyID = m_pRasterGrid->Cell(nXPar, nYPar).nGetPolygonID(); if (nPolyID == INT_NODATA) { @@ -548,7 +556,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, } // Get the number of the highest layer with non-zero thickness - int const nTopLayer = m_pRasterGrid->m_Cell[nXPar][nYPar].nGetTopNonZeroLayerAboveBasement(); + int const nTopLayer = m_pRasterGrid->Cell(nXPar, nYPar).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nTopLayer == INT_NODATA) @@ -559,10 +567,10 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, return RTN_OK; // Get the elevation for consolidated sediment only on this cell - dVParConsProfileZ[i] = m_pRasterGrid->m_Cell[nXPar][nYPar].dGetConsSedTopElevForLayerAboveBasement(nTopLayer); + dVParConsProfileZ[i] = m_pRasterGrid->Cell(nXPar, nYPar).dGetConsSedTopElevForLayerAboveBasement(nTopLayer); // Get the elevation for both consolidated and unconsolidated sediment on this cell - dVParProfileZ[i] = m_pRasterGrid->m_Cell[nXPar][nYPar].dGetSedimentTopElev(); + dVParProfileZ[i] = m_pRasterGrid->Cell(nXPar, nYPar).dGetSedimentTopElev(); // And store the X-Y plane distance from the start of the profile dVParProfileDistXY[i] = i * dParSpacingXY; @@ -679,16 +687,16 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, int const nYPar = PtiVGridParProfile[i].nGetY(); // Store the local slope of the consolidated sediment, this is just for output display purposes - m_pRasterGrid->m_Cell[nXPar][nYPar].SetLocalConsSlope(dVParConsSlope[i]); + m_pRasterGrid->Cell(nXPar, nYPar).SetLocalConsSlope(dVParConsSlope[i]); // dDeltaZ is zero or -ve: if dDeltaZ is zero then do nothing, if -ve then remove some sediment from this cell if (dDeltaZ < 0) { // Has this cell already been eroded during this timestep? - if (m_pRasterGrid->m_Cell[nXPar][nYPar].bPotentialPlatformErosion()) + if (m_pRasterGrid->Cell(nXPar, nYPar).bPotentialPlatformErosion()) { // It has - double const dPrevPotentialErosion = -m_pRasterGrid->m_Cell[nXPar][nYPar].dGetPotentialPlatformErosion(); + double const dPrevPotentialErosion = -m_pRasterGrid->Cell(nXPar, nYPar).dGetPotentialPlatformErosion(); // LogStream << m_ulIter << ": [" << nXPar << "][" << nYPar << "] parallel profile " << nDistFromProfile << " coast points " << (nDirection == DIRECTION_DOWNCOAST ? "down" : "up") << "-coast from profile " << nProfile << " has previous potential platform erosion = " << dPrevPotentialErosion << ", current potential platform erosion = " << dDeltaZ << ", max value = " << tMin(dPrevPotentialErosion, dDeltaZ) << endl; @@ -712,7 +720,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, dVParDeltaZ[i] = dDeltaZ; // Set the potential (unconstrained) erosion for this cell, it is a +ve value - m_pRasterGrid->m_Cell[nXPar][nYPar].SetPotentialPlatformErosion(-dDeltaZ); + m_pRasterGrid->Cell(nXPar, nYPar).SetPotentialPlatformErosion(-dDeltaZ); // LogStream << "[" << nXPar << "][" << nYPar << "] = {" << dGridCentroidXToExtCRSX(nXPar) << ", " << dGridCentroidYToExtCRSY(nYPar) << "} has potential platform erosion = " << -dDeltaZ << endl; // Update this-timestep totals @@ -728,7 +736,7 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, // Finally, calculate the beach protection factor, this will be used in estimating actual (supply-limited) erosion double const dBeachProtectionFactor = dCalcBeachProtectionFactor(nXPar, nYPar, dBreakingWaveHeight); - m_pRasterGrid->m_Cell[nXPar][nYPar].SetBeachProtectionFactor(dBeachProtectionFactor); + m_pRasterGrid->Cell(nXPar, nYPar).SetBeachProtectionFactor(dBeachProtectionFactor); } // If desired, save this parallel coastline-normal profile for checking purposes @@ -751,22 +759,22 @@ int CSimulation::nCalcPotentialPlatformErosionBetweenProfiles(int const nCoast, //=============================================================================================================================== //! Calculates actual (constrained by available sediment) erosion of the consolidated shore platform on a single sea cell //=============================================================================================================================== -void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) +void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY, CGeomCell& rCell) { // LogStream << m_ulIter << ": doing platform erosion on cell [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; // Get the beach protection factor, which quantifies the extent to which unconsolidated sediment on the shore platform (beach) protects the shore platform - double const dBeachProtectionFactor = m_pRasterGrid->m_Cell[nX][nY].dGetBeachProtectionFactor(); + double const dBeachProtectionFactor = rCell.dGetBeachProtectionFactor(); if (bFPIsEqual(dBeachProtectionFactor, 0.0, TOLERANCE)) // The beach is sufficiently thick to prevent any platform erosion (or we are down to basement) return; // Get the potential depth of potential erosion, considering beach protection - double const dThisPotentialErosion = m_pRasterGrid->m_Cell[nX][nY].dGetPotentialPlatformErosion() * dBeachProtectionFactor; + double const dThisPotentialErosion = rCell.dGetPotentialPlatformErosion() * dBeachProtectionFactor; // We will be eroding the topmost layer that has non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = rCell.nGetTopNonZeroLayerAboveBasement(); // Safety check if (nThisLayer == INT_NODATA) @@ -780,9 +788,9 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) return; // OK, we have a layer that can be eroded so find out how much consolidated sediment we have available on this cell - double const dExistingAvailableFine = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetFineDepth(); - double const dExistingAvailableSand = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetSandDepth(); - double const dExistingAvailableCoarse = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); + double const dExistingAvailableFine = rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetFineDepth(); + double const dExistingAvailableSand = rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetSandDepth(); + double const dExistingAvailableCoarse = rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); // Now partition the total lowering for this cell between the three size fractions: do this by relative erodibility int const nFineWeight = (dExistingAvailableFine > 0 ? 1 : 0); @@ -806,7 +814,7 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) dTotActualErosion += dFineEroded; // Set the value for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetFineDepth(dRemaining); + rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetFineDepth(dRemaining); // And set the changed-this-timestep switch m_bConsChangedThisIter[nThisLayer] = true; @@ -828,10 +836,10 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) dTotActualErosion += dSandEroded; // Set the new value of sand consolidated sediment depth for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetSandDepth(dRemaining); + rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetSandDepth(dRemaining); // And add this to the depth of sand unconsolidated sediment for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->AddSandDepth(dSandEroded); + rCell.pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->AddSandDepth(dSandEroded); // Set the changed-this-timestep switch m_bConsChangedThisIter[nThisLayer] = true; @@ -852,10 +860,10 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) dTotActualErosion += dCoarseEroded; // Set the new value of coarse consolidated sediment depth for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetCoarseDepth(dRemaining); + rCell.pGetLayerAboveBasement(nThisLayer)->pGetConsolidatedSediment()->SetCoarseDepth(dRemaining); // And add this to the depth of coarse unconsolidated sediment for this layer - m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->AddCoarseDepth(dCoarseEroded); + rCell.pGetLayerAboveBasement(nThisLayer)->pGetUnconsolidatedSediment()->AddCoarseDepth(dCoarseEroded); // Set the changed-this-timestep switch m_bConsChangedThisIter[nThisLayer] = true; @@ -868,26 +876,31 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if (dTotActualErosion > 0) { // We did, so set the actual erosion value for this cell - m_pRasterGrid->m_Cell[nX][nY].SetActualPlatformErosion(dTotActualErosion); + rCell.SetActualPlatformErosion(dTotActualErosion); // Recalculate the elevation of every layer - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + rCell.CalcAllLayerElevsAndD50(); // And update the cell's sea depth - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + rCell.SetSeaDepth(); // Update per-timestep totals m_ulThisIterNumActualPlatformErosionCells++; // Add eroded sand/coarse sediment for this cell to the polygon that contains the cell, ready for redistribution during beach erosion/deposition (fine sediment has already been dealt with) - int nPolyID = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - int nPolyCoastID = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonCoastID(); + int nPolyID = rCell.nGetPolygonID(); + int nPolyCoastID = rCell.nGetPolygonCoastID(); if (nPolyID == INT_NODATA) { // Can get occasional problems with polygon rasterization near the coastline, so also search the eight adjacent cells array nDirection = {NORTH, NORTH_EAST, EAST, SOUTH_EAST, SOUTH, SOUTH_WEST, WEST, NORTH_WEST}; - shuffle(nDirection.begin(), nDirection.end(), m_Rand[0]); + + // Thread-safe: protect RNG access in parallel region + #pragma omp critical(rng_shuffle) + { + shuffle(nDirection.begin(), nDirection.end(), m_Rand[0]); + } for (int n = 0; n < 8; n++) { @@ -901,11 +914,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if (nYAdj >= 0) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -918,11 +931,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if ((nXAdj < m_nXGridSize) && (nYAdj >= 0)) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -935,11 +948,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if (nXAdj < m_nXGridSize) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -952,11 +965,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if ((nXAdj < m_nXGridSize) && (nYAdj < m_nYGridSize)) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -969,11 +982,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if (nYAdj < m_nYGridSize) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -986,11 +999,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if ((nXAdj >= 0) && (nXAdj < m_nXGridSize)) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -1003,11 +1016,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if (nXAdj >= 0) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -1020,11 +1033,11 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) if ((nXAdj >= 0) && (nYAdj >= 0)) { - nPolyID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonID(); + nPolyID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonID(); if (nPolyID != INT_NODATA) { - nPolyCoastID = m_pRasterGrid->m_Cell[nXAdj][nYAdj].nGetPolygonCoastID(); + nPolyCoastID = m_pRasterGrid->Cell(nXAdj, nYAdj).nGetPolygonCoastID(); break; } } @@ -1049,8 +1062,12 @@ void CSimulation::DoActualPlatformErosionOnCell(int const nX, int const nY) } // All OK, so add this to the polygon's total of unconsolidated sand/coarse sediment, to be deposited or moved later. These values are +ve (deposition) - m_VCoast[nPolyCoastID].pGetPolygon(nPolyID)->AddPlatformErosionUnconsSand(dSandEroded); - m_VCoast[nPolyCoastID].pGetPolygon(nPolyID)->AddPlatformErosionUnconsCoarse(dCoarseEroded); + // Thread-safe: protect polygon updates in parallel region + #pragma omp critical(polygon_updates) + { + m_VCoast[nPolyCoastID].pGetPolygon(nPolyID)->AddPlatformErosionUnconsSand(dSandEroded); + m_VCoast[nPolyCoastID].pGetPolygon(nPolyID)->AddPlatformErosionUnconsCoarse(dCoarseEroded); + } } } @@ -1130,7 +1147,7 @@ double CSimulation::dCalcBeachProtectionFactor(int const nX, int const nY, doubl return 0; // We are considering the unconsolidated sediment (beach) of the topmost layer that has non-zero thickness - int const nThisLayer = m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); + int const nThisLayer = m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); // Safety check if (nThisLayer == INT_NODATA) @@ -1144,7 +1161,7 @@ double CSimulation::dCalcBeachProtectionFactor(int const nX, int const nY, doubl return 0; // In SCAPE, 0.23 * the significant breaking wave height is assumed to be the maximum depth of beach that waves can penetrate to erode a platform. For depths less than this, the beach protective ability is assumed to vary linearly - double const dBeachDepth = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nThisLayer)->dGetUnconsolidatedThickness(); + double const dBeachDepth = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nThisLayer)->dGetUnconsolidatedThickness(); double const dMaxPenetrationDepth = BEACH_PROTECTION_HB_RATIO * dBreakingWaveHeight; double dFactor = 0; @@ -1165,7 +1182,7 @@ void CSimulation::FillInBeachProtectionHoles(void) { for (int nY = 0; nY < m_nYGridSize; nY++) { - if ((m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) && (bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) + if ((m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) && (bFPIsEqual(m_pRasterGrid->Cell(nX, nY).dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) { // This is a sea cell, and it has an initialised beach protection value. So look at its N-S and W-E neighbours int nXTmp; @@ -1177,46 +1194,46 @@ void CSimulation::FillInBeachProtectionHoles(void) nXTmp = nX; nYTmp = nY - 1; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) { nAdjacent++; - dBeachProtection += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(); + dBeachProtection += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(); } // East nXTmp = nX + 1; nYTmp = nY; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) { nAdjacent++; - dBeachProtection += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(); + dBeachProtection += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(); } // South nXTmp = nX; nYTmp = nY + 1; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) { nAdjacent++; - dBeachProtection += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(); + dBeachProtection += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(); } // West nXTmp = nX - 1; nYTmp = nY; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(), DBL_NODATA, TOLERANCE))) { nAdjacent++; - dBeachProtection += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetBeachProtectionFactor(); + dBeachProtection += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetBeachProtectionFactor(); } // If this sea cell has four neighbours with initialised beach protection values, then assume that it should not have an uninitialised beach protection value. Set it to the average of its neighbours if (nAdjacent == 4) { - m_pRasterGrid->m_Cell[nX][nY].SetBeachProtectionFactor(dBeachProtection / 4); + m_pRasterGrid->Cell(nX, nY).SetBeachProtectionFactor(dBeachProtection / 4); } } } @@ -1232,7 +1249,7 @@ void CSimulation::FillPotentialPlatformErosionHoles(void) { for (int nY = 0; nY < m_nYGridSize; nY++) { - if ((m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) && (bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) + if ((m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) && (bFPIsEqual(m_pRasterGrid->Cell(nX, nY).dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) { // This is a sea cell, it has a zero potential platform erosion value. So look at its N-S and W-E neighbours int nXTmp; @@ -1244,40 +1261,40 @@ void CSimulation::FillPotentialPlatformErosionHoles(void) nXTmp = nX; nYTmp = nY - 1; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) { nAdjacent++; - dPotentialPlatformErosion += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(); + dPotentialPlatformErosion += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(); } // East nXTmp = nX + 1; nYTmp = nY; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) { nAdjacent++; - dPotentialPlatformErosion += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(); + dPotentialPlatformErosion += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(); } // South nXTmp = nX; nYTmp = nY + 1; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) { nAdjacent++; - dPotentialPlatformErosion += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(); + dPotentialPlatformErosion += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(); } // West nXTmp = nX - 1; nYTmp = nY; - if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) + if ((bIsWithinValidGrid(nXTmp, nYTmp)) && (! bFPIsEqual(m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(), 0.0, TOLERANCE))) { nAdjacent++; - dPotentialPlatformErosion += m_pRasterGrid->m_Cell[nXTmp][nYTmp].dGetPotentialPlatformErosion(); + dPotentialPlatformErosion += m_pRasterGrid->Cell(nXTmp, nYTmp).dGetPotentialPlatformErosion(); } // If this sea cell has four neighbours with non-zero potential platform erosion values, then assume that it should not have a zero potential platform erosion value. Set it to the average of its neighbours @@ -1285,7 +1302,7 @@ void CSimulation::FillPotentialPlatformErosionHoles(void) { double const dThisPotentialPlatformErosion = dPotentialPlatformErosion / 4; - m_pRasterGrid->m_Cell[nX][nY].SetPotentialPlatformErosion(dThisPotentialPlatformErosion); + m_pRasterGrid->Cell(nX, nY).SetPotentialPlatformErosion(dThisPotentialPlatformErosion); // Update this-timestep totals m_ulThisIterNumPotentialPlatformErosionCells++; @@ -1329,9 +1346,9 @@ void CSimulation::ConstructParallelProfile(int const nProfileStartX, int const n } // Have we hit an adjacent coastline-normal profile? If so, cut short - if (m_pRasterGrid->m_Cell[nXPar][nYPar].bIsProfile()) + if (m_pRasterGrid->Cell(nXPar, nYPar).bIsProfile()) { - // LogStream << "HIT PROFILE " << m_pRasterGrid->m_Cell[nXPar][nYPar].nGetProfileID() << " at [" << nXPar << "][" << nYPar << "] = {" << dGridCentroidXToExtCRSX(nXPar) << ", " << dGridCentroidYToExtCRSY(nYPar) << "}" << endl; + // LogStream << "HIT PROFILE " << m_pRasterGrid->Cell(nXPar, nYPar).nGetProfileID() << " at [" << nXPar << "][" << nYPar << "] = {" << dGridCentroidXToExtCRSX(nXPar) << ", " << dGridCentroidYToExtCRSY(nYPar) << "}" << endl; return; } diff --git a/src/do_surge_flood.cpp b/src/do_surge_flood.cpp index 0114cdc35..8adc20115 100644 --- a/src/do_surge_flood.cpp +++ b/src/do_surge_flood.cpp @@ -154,10 +154,10 @@ void CSimulation::FloodFillLand(int const nXStart, int const nYStart) while (nX >= 0) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsCellFloodCheck()) + if (m_pRasterGrid->Cell(nX, nY).bIsCellFloodCheck()) break; - if (! m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel()) + if (! m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel()) break; nX--; @@ -170,45 +170,45 @@ void CSimulation::FloodFillLand(int const nXStart, int const nYStart) while (nX < m_nXGridSize) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsCellFloodCheck()) + if (m_pRasterGrid->Cell(nX, nY).bIsCellFloodCheck()) break; - if (! m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel()) + if (! m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel()) break; // Flood this cell - m_pRasterGrid->m_Cell[nX][nY].SetCheckFloodCell(); - m_pRasterGrid->m_Cell[nX][nY].SetInContiguousFlood(); + m_pRasterGrid->Cell(nX, nY).SetCheckFloodCell(); + m_pRasterGrid->Cell(nX, nY).SetInContiguousFlood(); switch (m_nLevel) { case 0: // WAVESETUP + STORMSURGE: - m_pRasterGrid->m_Cell[nX][nY].SetFloodBySetupSurge(); + m_pRasterGrid->Cell(nX, nY).SetFloodBySetupSurge(); break; case 1: // WAVESETUP + STORMSURGE + RUNUP: - m_pRasterGrid->m_Cell[nX][nY].SetFloodBySetupSurgeRunup(); + m_pRasterGrid->Cell(nX, nY).SetFloodBySetupSurgeRunup(); break; } - if ((! bSpanAbove) && (nY > 0) && (m_pRasterGrid->m_Cell[nX][nY - 1].bIsElevLessThanWaterLevel()) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsCellFloodCheck())) + if ((! bSpanAbove) && (nY > 0) && (m_pRasterGrid->Cell(nX, nY - 1).bIsElevLessThanWaterLevel()) && (!m_pRasterGrid->Cell(nX, nY - 1).bIsCellFloodCheck())) { PtiStackFlood.push(CGeom2DIPoint(nX, nY - 1)); bSpanAbove = true; } - else if (bSpanAbove && (nY > 0) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsElevLessThanWaterLevel())) + else if (bSpanAbove && (nY > 0) && (!m_pRasterGrid->Cell(nX, nY - 1).bIsElevLessThanWaterLevel())) { bSpanAbove = false; } - if ((! bSpanBelow) && (nY < m_nYGridSize - 1) && (m_pRasterGrid->m_Cell[nX][nY + 1].bIsElevLessThanWaterLevel()) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsCellFloodCheck())) + if ((! bSpanBelow) && (nY < m_nYGridSize - 1) && (m_pRasterGrid->Cell(nX, nY + 1).bIsElevLessThanWaterLevel()) && (!m_pRasterGrid->Cell(nX, nY + 1).bIsCellFloodCheck())) { PtiStackFlood.push(CGeom2DIPoint(nX, nY + 1)); bSpanBelow = true; } - else if (bSpanBelow && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsElevLessThanWaterLevel())) + else if (bSpanBelow && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->Cell(nX, nY + 1).bIsElevLessThanWaterLevel())) { bSpanBelow = false; } @@ -249,17 +249,17 @@ int CSimulation::nTraceAllFloodCoasts(void) int const nYNext = m_VEdgeCell[n + 1].nGetY(); // Get "Is it sea?" information for 'this' and 'next' cells - bool const bThisCellIsSea = m_pRasterGrid->m_Cell[nXThis][nYThis].bIsInContiguousSeaArea(); - bool const bNextCellIsSea = m_pRasterGrid->m_Cell[nXNext][nYNext].bIsInContiguousSeaArea(); + bool const bThisCellIsSea = m_pRasterGrid->Cell(nXThis, nYThis).bIsInContiguousSeaArea(); + bool const bNextCellIsSea = m_pRasterGrid->Cell(nXNext, nYNext).bIsInContiguousSeaArea(); // Are we at a coast? if ((! bThisCellIsSea) && bNextCellIsSea) { // 'This' cell is just inland, has it already been flagged as a possible start for a coastline (even if this subsequently 'failed' as a coastline)? - // if (! m_pRasterGrid->m_Cell[nXThis][nYThis].bIsPossibleCoastStartCell()) + // if (! m_pRasterGrid->Cell(nXThis, nYThis).bIsPossibleCoastStartCell()) { // It has not, so flag it - m_pRasterGrid->m_Cell[nXThis][nYThis].SetPossibleFloodStartCell(); + m_pRasterGrid->Cell(nXThis, nYThis).SetPossibleFloodStartCell(); // And save it V2DIPossibleStartCell.push_back(CGeom2DIPoint(nXThis, nYThis)); @@ -272,10 +272,10 @@ int CSimulation::nTraceAllFloodCoasts(void) else if (bThisCellIsSea && (! bNextCellIsSea)) { // The 'next' cell is just inland, has it already been flagged as a possible start for a coastline (even if this subsequently 'failed' as a coastline)? - // if (! m_pRasterGrid->m_Cell[nXNext][nYNext].bIsPossibleCoastStartCell()) + // if (! m_pRasterGrid->Cell(nXNext, nYNext).bIsPossibleCoastStartCell()) { // It has not, so flag it - m_pRasterGrid->m_Cell[nXNext][nYNext].SetPossibleFloodStartCell(); + m_pRasterGrid->Cell(nXNext, nYNext).SetPossibleFloodStartCell(); // And save it V2DIPossibleStartCell.push_back(CGeom2DIPoint(nXNext, nYNext)); @@ -345,7 +345,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde CGeomILine ILTempGridCRS; // Mark the start cell as coast and add it to the vector object - m_pRasterGrid->m_Cell[nStartX][nStartY].SetAsFloodline(true); + m_pRasterGrid->Cell(nStartX, nStartY).SetAsFloodline(true); CGeom2DIPoint const PtiStart(nStartX, nStartY); ILTempGridCRS.Append(&PtiStart); @@ -392,7 +392,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde bHasLeftStartEdge = true; // Flag this cell to ensure that it is not chosen as a coastline start cell later - m_pRasterGrid->m_Cell[nX][nY].SetPossibleFloodStartCell(); + m_pRasterGrid->Cell(nX, nY).SetPossibleFloodStartCell(); // LogStream << "Flagging [" << nX << "][" << nY << "] as possible coast start cell NOT YET LEFT EDGE" << endl; } @@ -638,26 +638,26 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde if (bIsWithinValidGrid(nXSeaward, nYSeaward)) { // It is, so check if the cell in the seaward direction is a sea cell - if (m_pRasterGrid->m_Cell[nXSeaward][nYSeaward].bIsInContiguousSeaArea()) + if (m_pRasterGrid->Cell(nXSeaward, nYSeaward).bIsInContiguousSeaArea()) { // There is sea in this seaward direction, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a coast cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsFloodline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsFloodline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel())) + if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel())) { // It is, so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } - else if (! m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel()) + else if (! m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel()) { // The sediment top is above SWL so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } } @@ -679,26 +679,26 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde if (bIsWithinValidGrid(nXStraightOn, nYStraightOn)) { // It is, so check if there is sea immediately in front - if (m_pRasterGrid->m_Cell[nXStraightOn][nYStraightOn].bIsInContiguousSeaArea()) + if (m_pRasterGrid->Cell(nXStraightOn, nYStraightOn).bIsInContiguousSeaArea()) { // Sea is in front, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a floodline cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsFloodline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsFloodline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel())) + if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel())) { // It is, so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } - else if (! m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel()) + else if (! m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel()) { // The sediment top is above SWL so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } } @@ -719,26 +719,26 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde if (bIsWithinValidGrid(nXAntiSeaward, nYAntiSeaward)) { // It is, so check if there is sea in this anti-seaward cell - if (m_pRasterGrid->m_Cell[nXAntiSeaward][nYAntiSeaward].bIsInContiguousSeaArea()) + if (m_pRasterGrid->Cell(nXAntiSeaward, nYAntiSeaward).bIsInContiguousSeaArea()) { // There is sea on the anti-seaward side, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a floodline cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsFloodline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsFloodline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel())) + if ((bIsInterventionCell(nX, nY)) && (!m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel())) { // It is, so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } - else if (! m_pRasterGrid->m_Cell[nX][nY].bIsElevLessThanWaterLevel()) + else if (! m_pRasterGrid->Cell(nX, nY).bIsElevLessThanWaterLevel()) { // The sediment top is above SWL so mark as coast and add it to the vector object - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(true); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(true); ILTempGridCRS.Append(&Pti); } } @@ -784,7 +784,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde // Unmark these cells as coast cells for (int n = 0; n < nCoastSize; n++) - m_pRasterGrid->m_Cell[ILTempGridCRS[n].nGetX()][ILTempGridCRS[n].nGetY()].SetAsFloodline(false); + m_pRasterGrid->Cell(ILTempGridCRS[n].nGetX(), ILTempGridCRS[n].nGetY()).SetAsFloodline(false); return RTN_ERR_TRACING_FLOOD; } @@ -804,7 +804,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde // Unmark these cells as coast cells for (int n = 0; n < nCoastSize; n++) - m_pRasterGrid->m_Cell[ILTempGridCRS[n].nGetX()][ILTempGridCRS[n].nGetY()].SetAsFloodline(false); + m_pRasterGrid->Cell(ILTempGridCRS[n].nGetX(), ILTempGridCRS[n].nGetY()).SetAsFloodline(false); return RTN_ERR_TRACING_FLOOD; } @@ -823,7 +823,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde // Unmark these cells as coast cells for (int n = 0; n < nCoastSize; n++) - m_pRasterGrid->m_Cell[ILTempGridCRS[n].nGetX()][ILTempGridCRS[n].nGetY()].SetAsFloodline(false); + m_pRasterGrid->Cell(ILTempGridCRS[n].nGetX(), ILTempGridCRS[n].nGetY()).SetAsFloodline(false); return RTN_ERR_TRACING_FLOOD; } @@ -845,7 +845,7 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde // Unmark these cells as coast cells for (int n = 0; n < nCoastSize; n++) - m_pRasterGrid->m_Cell[ILTempGridCRS[n].nGetX()][ILTempGridCRS[n].nGetY()].SetAsFloodline(false); + m_pRasterGrid->Cell(ILTempGridCRS[n].nGetX(), ILTempGridCRS[n].nGetY()).SetAsFloodline(false); return RTN_ERR_TRACING_FLOOD; } @@ -859,20 +859,20 @@ int CSimulation::nTraceFloodCoastLine(unsigned int const nTraceFromStartCellInde if ((nCoastEndX != nEndX) || (nCoastEndY != nEndY)) { // The grid-edge cell at nEndX, nEndY is not already at end of ILTempGridCRS. But is the final cell in ILTempGridCRS already at the edge of the grid? - if (! m_pRasterGrid->m_Cell[nCoastEndX][nCoastEndY].bIsBoundingBoxEdge()) + if (! m_pRasterGrid->Cell(nCoastEndX, nCoastEndY).bIsBoundingBoxEdge()) { // The final cell in ILTempGridCRS is not a grid-edge cell, so add the grid-edge cell and mark the cell as coastline ILTempGridCRS.Append(nEndX, nEndY); nCoastSize++; - m_pRasterGrid->m_Cell[nEndX][nEndY].SetAsFloodline(true); + m_pRasterGrid->Cell(nEndX, nEndY).SetAsFloodline(true); } } // Need to specify start edge and end edge for smoothing routines // int - // nStartEdge = m_pRasterGrid->m_Cell[nStartX][nStartY].nGetBoundingBoxEdge(), - // nEndEdge = m_pRasterGrid->m_Cell[nEndX][nEndY].nGetBoundingBoxEdge(); + // nStartEdge = m_pRasterGrid->Cell(nStartX, nStartY).nGetBoundingBoxEdge(), + // nEndEdge = m_pRasterGrid->Cell(nEndX, nEndY).nGetBoundingBoxEdge(); // Next, convert the grid coordinates in ILTempGridCRS (integer values stored as doubles) to external CRS coordinates (which will probably be non-integer, again stored as doubles). This is done now, so that smoothing is more effective CGeomLine LTempExtCRS; diff --git a/src/gis_raster.cpp b/src/gis_raster.cpp index 3ef5641bf..3608bf443 100644 --- a/src/gis_raster.cpp +++ b/src/gis_raster.cpp @@ -1,2772 +1,2569 @@ -/*! - \file gis_raster.cpp - \brief These functions use GDAL (at least version 2) to read and write raster - GIS files in several formats - \details TODO 001 A more detailed description of these routines. - \author David Favis-Mortlock - \author Andres Payo - \date 2025 - \copyright GNU General Public License -*/ - -/* =============================================================================================================================== - This file is part of CoastalME, the Coastal Modelling Environment. - - CoastalME is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 3 of the License, or (at your option) any later -version. - - This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, -Cambridge, MA 02139, USA. -===============================================================================================================================*/ -#include - -#include - -#include -using std::atan2; -using std::hypot; -using std::isfinite; -using std::isnan; -using std::sqrt; - -#include -using std::vector; - -#include -using std::cerr; -using std::endl; -using std::ios; - -#include -using std::ifstream; - -#include -using std::stringstream; - -#include -using std::to_string; - -#include -#include -#include -#include -#include -#include - -#include "2di_point.h" -#include "cme.h" -#include "coast.h" -#include "simulation.h" -#include "spatial_interpolation.h" - -//=============================================================================================================================== -//! Initialize GDAL with performance optimizations -//=============================================================================================================================== -void CSimulation::InitializeGDALPerformance(void) { - // Configure GDAL for optimal performance - // Enable GDAL threading - use all available CPU cores -#ifdef _OPENMP - CPLSetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"); -#else - CPLSetConfigOption("GDAL_NUM_THREADS", "4"); // Fallback for non-OpenMP builds -#endif - - // Optimize GDAL memory usage and caching - CPLSetConfigOption("GDAL_CACHEMAX", - "1.5GB"); // 2GB cache for large grids (was 1GB) - CPLSetConfigOption("GDAL_DISABLE_READDIR_ON_OPEN", - "EMPTY_DIR"); // Faster file access - CPLSetConfigOption("VSI_CACHE", "TRUE"); // Enable virtual file system cache - CPLSetConfigOption("VSI_CACHE_SIZE", "512MB"); // 256MB VSI cache - - // Block and chunk optimizations for raster operations - CPLSetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"); - CPLSetConfigOption("GDAL_RASTERIO_RESAMPLING", - "CUBIC"); // Better for coastal DEM data - - // Grid creation optimizations (for GDALGridCreate performance) - CPLSetConfigOption("GDAL_GRID_MAX_POINTS_PER_QUADTREE_LEAF", "1024"); - // Increased from 512 - CPLSetConfigOption("GDAL_GRID_POINT_COUNT_THRESHOLD", - "100"); // New 2024 option - - // Thread-safe dataset access (GDAL 3.10+) - CPLSetConfigOption("GDAL_DATASET_CACHE_SIZE", "64"); // Cache more datasets - - // Compression optimizations for output - CPLSetConfigOption("GDAL_TIFF_OVR_BLOCKSIZE", - "512"); // Optimal for coastal data - - // Memory allocator optimization for multi-threading - CPLSetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "YES"); - - // Disable GDAL warnings for cleaner output (optional) - // CPLSetConfigOption("CPL_LOG", "/dev/null"); - // Debugging (remove in production) - // CPLSetConfigOption("CPL_DEBUG", "ON"); - // CPLSetConfigOption("GDAL_DEBUG", "ON"); - - m_bGDALOptimisations = true; -} - -//=============================================================================================================================== -//! Reads a raster DEM of basement elevation data to the Cell array -//=============================================================================================================================== -int CSimulation::nReadRasterBasementDEM(void) { - // Initialize GDAL performance settings (only needs to be done once) - static bool bGDALInitialized = false; - - if (!bGDALInitialized) { - InitializeGDALPerformance(); - bGDALInitialized = true; - } - - // Use GDAL to create a dataset object, which then opens the DEM file - GDALDataset *pGDALDataset = static_cast( - GDALOpen(m_strInitialBasementDEMFile.c_str(), GA_ReadOnly)); - - if (NULL == pGDALDataset) { - // Can't open file (note will already have sent GDAL error message to - // stdout) - cerr << ERR << "cannot open " << m_strInitialBasementDEMFile - << " for input: " << CPLGetLastErrorMsg() << endl; - return RTN_ERR_DEMFILE; - } - - // Opened OK, so get GDAL basement DEM dataset information - m_strGDALBasementDEMDriverCode = pGDALDataset->GetDriver()->GetDescription(); - m_strGDALBasementDEMDriverDesc = - pGDALDataset->GetDriver()->GetMetadataItem(GDAL_DMD_LONGNAME); - m_strGDALBasementDEMProjection = pGDALDataset->GetProjectionRef(); - - // If we have reference units, then check that they are in metres (note US - // spelling) - if (!m_strGDALBasementDEMProjection.empty()) { - string const strTmp = strToLower(&m_strGDALBasementDEMProjection); - - if ((strTmp.find("meter") == string::npos) && - (strTmp.find("metre") == string::npos)) { - // error: x-y values must be in metres - cerr << ERR << "GIS file x-y values (" << m_strGDALBasementDEMProjection - << ") in " << m_strInitialBasementDEMFile << " must be in metres" - << endl; - return RTN_ERR_DEMFILE; - } - } - - // Now get dataset size, and do some rudimentary checks - m_nXGridSize = pGDALDataset->GetRasterXSize(); - - if (m_nXGridSize == 0) { - // Error: silly number of columns specified - cerr << ERR << "invalid number of columns (" << m_nXGridSize << ") in " - << m_strInitialBasementDEMFile << endl; - return RTN_ERR_DEMFILE; - } - - m_nYGridSize = pGDALDataset->GetRasterYSize(); - - if (m_nYGridSize == 0) { - // Error: silly number of rows specified - cerr << ERR << "invalid number of rows (" << m_nYGridSize << ") in " - << m_strInitialBasementDEMFile << endl; - return RTN_ERR_DEMFILE; - } - - // Get geotransformation info (see http://www.gdal.org/classGDALDataset.html) - if (CE_Failure == pGDALDataset->GetGeoTransform(m_dGeoTransform)) { - // Can't get geotransformation (note will already have sent GDAL error - // message to stdout) - cerr << ERR << CPLGetLastErrorMsg() << " in " << m_strInitialBasementDEMFile - << endl; - return RTN_ERR_DEMFILE; - } - - // CoastalME can only handle rasters that are oriented N-S and W-E. (If you - // need to work with a raster that is oriented differently, then you must - // rotate it before running CoastalME). So here we check whether row rotation - // (m_dGeoTransform[2]) and column rotation (m_dGeoTransform[4]) are both - // zero. See https://gdal.org/tutorials/geotransforms_tut.html - if ((!bFPIsEqual(m_dGeoTransform[2], 0.0, TOLERANCE)) || - (!bFPIsEqual(m_dGeoTransform[4], 0.0, TOLERANCE))) { - // Error: not oriented NS and W-E - cerr << ERR << m_strInitialBasementDEMFile - << " is not oriented N-S and W-E. Row rotation = " - << m_dGeoTransform[2] - << " and column rotation = " << m_dGeoTransform[4] << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // Get the X and Y cell sizes, in external CRS units. Note that while the cell - // is supposed to be square, it may not be exactly so due to oddities with - // some GIS calculations - double const dCellSideX = tAbs(m_dGeoTransform[1]); - double const dCellSideY = tAbs(m_dGeoTransform[5]); - - // Check that the cell is more or less square - if (!bFPIsEqual(dCellSideX, dCellSideY, 1e-2)) { - // Error: cell is not square enough - cerr << ERR << "cell is not square in " << m_strInitialBasementDEMFile - << ", is " << dCellSideX << " x " << dCellSideY << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // Calculate the average length of cell side, the cell's diagonal, and the - // area of a cell (in external CRS units) - m_dCellSide = (dCellSideX + dCellSideY) / 2.0; - m_dCellArea = m_dCellSide * m_dCellSide; - m_dCellDiagonal = hypot(m_dCellSide, m_dCellSide); - - // And calculate the inverse values - m_dInvCellSide = 1 / m_dCellSide; - m_dInvCellDiagonal = 1 / m_dCellDiagonal; - - // Save some values in external CRS - m_dNorthWestXExtCRS = m_dGeoTransform[0] - (m_dGeoTransform[1] / 2); - m_dNorthWestYExtCRS = m_dGeoTransform[3] - (m_dGeoTransform[5] / 2); - m_dSouthEastXExtCRS = m_dGeoTransform[0] + - (m_nXGridSize * m_dGeoTransform[1]) + - (m_dGeoTransform[1] / 2); - m_dSouthEastYExtCRS = m_dGeoTransform[3] + - (m_nYGridSize * m_dGeoTransform[5]) + - (m_dGeoTransform[5] / 2); - - // And calc the grid area in external CRS units - m_dExtCRSGridArea = tAbs(m_dNorthWestXExtCRS - m_dSouthEastXExtCRS) * - tAbs(m_dNorthWestYExtCRS * m_dSouthEastYExtCRS); - - // Now get GDAL raster band information - GDALRasterBand *pGDALBand = pGDALDataset->GetRasterBand(1); - int nBlockXSize = 0, nBlockYSize = 0; - pGDALBand->GetBlockSize(&nBlockXSize, &nBlockYSize); - m_strGDALBasementDEMDataType = - GDALGetDataTypeName(pGDALBand->GetRasterDataType()); - - // If we have value units, then check them - string const strUnits = pGDALBand->GetUnitType(); - - if ((!strUnits.empty()) && (strUnits.find('m') == string::npos)) { - // Error: value units must be m - cerr << ERR << "DEM vertical units are (" << strUnits << " ) in " - << m_strInitialBasementDEMFile << ", should be 'm'" << endl; - return RTN_ERR_DEMFILE; - } - - // If present, get the missing value (NODATA) setting - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - double const dMissingValue = - pGDALBand->GetNoDataValue(); // Will fail for some formats - CPLPopErrorHandler(); - - if (!bFPIsEqual(dMissingValue, m_dMissingValue, TOLERANCE)) { - cerr << " " << NOTE << "NODATA value in " << m_strInitialBasementDEMFile - << " is " << dMissingValue - << "\n instead using CoastalME's default floating-point " - "NODATA value " - << m_dMissingValue << endl; - } - - // Next allocate memory for a 2D array of raster cell objects: tell the user - // what is happening - AnnounceAllocateMemory(); - int const nRet = m_pRasterGrid->nCreateGrid(); - - if (nRet != RTN_OK) - return nRet; - - // Allocate memory for a 1D floating-point array, to hold the scan line for - // GDAL - double *pdScanline = new double[m_nXGridSize]; - - if (NULL == pdScanline) { - // Error, can't allocate memory - cerr << ERR << "cannot allocate memory for " << m_nXGridSize - << " x 1D array" << endl; - return (RTN_ERR_MEMALLOC); - } - - // Now read in the data - for (int j = 0; j < m_nYGridSize; j++) { - // Read scanline - if (CE_Failure == pGDALBand->RasterIO(GF_Read, 0, j, m_nXGridSize, 1, - pdScanline, m_nXGridSize, 1, - GDT_Float64, 0, 0, NULL)) { - // Error while reading scanline - cerr << ERR << CPLGetLastErrorMsg() << " in " - << m_strInitialBasementDEMFile << endl; - return RTN_ERR_DEMFILE; - } - - // All OK, so read scanline into cell elevations (including any missing - // values) - for (int i = 0; i < m_nXGridSize; i++) { - double dTmp = pdScanline[i]; - - if ((isnan(dTmp)) || (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) - dTmp = m_dMissingValue; - - m_pRasterGrid->m_Cell[i][j].SetBasementElev(dTmp); - } - } - - // Finished, so get rid of dataset object - GDALClose(pGDALDataset); - - // Get rid of memory allocated to this array - delete[] pdScanline; - - return RTN_OK; -} - -//=============================================================================================================================== -//! Mark cells which are at the edge of a bounding box which represents the -//! valid part of the grid, as defined by the basement layer. The valid part of -//! the grid may be the whole grid, or only part of the whole grid. The bounding -//! box may be an irregular shape (but may not have re-entrant edges): simple -//! shapes are more likely to work correctly -//=============================================================================================================================== -int CSimulation::nMarkBoundingBoxEdgeCells(void) { - // The bounding box must touch the edge of the grid at least once on each side - // of the grid, so store these points. Search in a clockwise direction around - // the edge of the grid - vector VPtiBoundingBoxCorner; - - // Start with the top (north) edge - bool bFound = false; - - for (int nX = 0; nX < m_nXGridSize; nX++) { - if (bFound) - break; - - for (int nY = 0; nY < m_nYGridSize; nY++) { - if (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - CGeom2DIPoint const PtiTmp(nX, nY); - VPtiBoundingBoxCorner.push_back(PtiTmp); - bFound = true; - break; - } - } - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": north (top) edge of bounding box not found" - << endl; - - return RTN_ERR_BOUNDING_BOX; - } - - // Do the same for the right (east) edge - bFound = false; - - for (int nY = 0; nY < m_nYGridSize; nY++) { - if (bFound) - break; - - for (int nX = m_nXGridSize - 1; nX >= 0; nX--) { - if (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - CGeom2DIPoint const PtiTmp(nX, nY); - VPtiBoundingBoxCorner.push_back(PtiTmp); - bFound = true; - break; - } - } - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": east (right) edge of bounding box not found" - << endl; - - return RTN_ERR_BOUNDING_BOX; - } - - // Do the same for the south (bottom) edge - bFound = false; - - for (int nX = m_nXGridSize - 1; nX >= 0; nX--) { - if (bFound) - break; - - for (int nY = m_nYGridSize - 1; nY >= 0; nY--) { - if (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - CGeom2DIPoint const PtiTmp(nX, nY); - VPtiBoundingBoxCorner.push_back(PtiTmp); - bFound = true; - break; - } - } - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": south (bottom) edge of bounding box not found" - << endl; - - return RTN_ERR_BOUNDING_BOX; - } - - // And finally repeat for the west (left) edge - bFound = false; - - for (int nY = m_nYGridSize - 1; nY >= 0; nY--) { - if (bFound) - break; - - for (int nX = 0; nX < m_nXGridSize; nX++) { - if (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - CGeom2DIPoint const PtiTmp(nX, nY); - VPtiBoundingBoxCorner.push_back(PtiTmp); - bFound = true; - break; - } - } - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_ALL) - LogStream << m_ulIter << ": west (left) edge of bounding box not found" - << endl; - - return RTN_ERR_BOUNDING_BOX; - } - - // OK, so we have a point on each side of the grid, so start at this point and - // find the edges of the bounding box. Go round in a clockwise direction: top - // (north) edge first - for (int nX = VPtiBoundingBoxCorner[0].nGetX(); - nX <= VPtiBoundingBoxCorner[1].nGetX(); nX++) { - bFound = false; - - for (int nY = VPtiBoundingBoxCorner[0].nGetY(); nY < m_nYGridSize; nY++) { - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - m_ulMissingValueBasementCells++; - continue; - } - - // Found a bounding box edge cell - m_pRasterGrid->m_Cell[nX][nY].SetBoundingBoxEdge(NORTH); - - m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); - m_VEdgeCellEdge.push_back(NORTH); - - bFound = true; - break; - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream - << m_ulIter - << ": could not find a bounding box edge cell for grid column " - << nX << endl; - - return RTN_ERR_BOUNDING_BOX; - } - } - - // Right (east) edge - for (int nY = VPtiBoundingBoxCorner[1].nGetY(); - nY <= VPtiBoundingBoxCorner[2].nGetY(); nY++) { - bFound = false; - - for (int nX = VPtiBoundingBoxCorner[1].nGetX(); nX >= 0; nX--) { - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - m_ulMissingValueBasementCells++; - continue; - } - - // Found a bounding box edge cell - m_pRasterGrid->m_Cell[nX][nY].SetBoundingBoxEdge(EAST); - - m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); - m_VEdgeCellEdge.push_back(EAST); - - bFound = true; - break; - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter - << ": could not find a bounding box edge cell for grid row " - << nY << endl; - - return RTN_ERR_BOUNDING_BOX; - } - } - - // Bottom (south) edge - for (int nX = VPtiBoundingBoxCorner[2].nGetX(); - nX >= VPtiBoundingBoxCorner[3].nGetX(); nX--) { - bFound = false; - - for (int nY = VPtiBoundingBoxCorner[2].nGetY(); nY >= 0; nY--) { - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - m_ulMissingValueBasementCells++; - continue; - } - - // Found a bounding box edge cell - m_pRasterGrid->m_Cell[nX][nY].SetBoundingBoxEdge(SOUTH); - - m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); - m_VEdgeCellEdge.push_back(SOUTH); - - bFound = true; - break; - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream - << m_ulIter - << ": could not find a bounding box edge cell for grid column " - << nX << endl; - - return RTN_ERR_BOUNDING_BOX; - } - } - - // Left (west) edge - for (int nY = VPtiBoundingBoxCorner[3].nGetY(); - nY >= VPtiBoundingBoxCorner[0].nGetY(); nY--) { - for (int nX = VPtiBoundingBoxCorner[3].nGetX(); nX < m_nXGridSize - 1; nX++) - // for (int nX = VPtiBoundingBoxCorner[3].nGetX(); nX < - // VPtiBoundingBoxCorner[3].nGetX(); nX++) - { - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) { - m_ulMissingValueBasementCells++; - continue; - } - - // Found a bounding box edge cell - m_pRasterGrid->m_Cell[nX][nY].SetBoundingBoxEdge(WEST); - - m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); - m_VEdgeCellEdge.push_back(WEST); - - bFound = true; - break; - } - - if (!bFound) { - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << m_ulIter - << ": could not find a bounding box edge cell for grid row " - << nY << endl; - - return RTN_ERR_BOUNDING_BOX; - } - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! Reads raster GIS datafiles into the RasterGrid array -//=============================================================================================================================== -int CSimulation::nReadRasterGISFile(int const nDataItem, int const nLayer) { - string strGISFile; - string strDriverCode; - string strDriverDesc; - string strProjection; - string strDataType; - - switch (nDataItem) { - case (LANDFORM_RASTER): - // Initial Landform Class GIS data - strGISFile = m_strInitialLandformFile; - break; - - case (INTERVENTION_CLASS_RASTER): - // Intervention class - strGISFile = m_strInterventionClassFile; - break; - - case (INTERVENTION_HEIGHT_RASTER): - // Intervention height - strGISFile = m_strInterventionHeightFile; - break; - - case (SUSP_SED_RASTER): - // Initial Suspended Sediment GIS data - strGISFile = m_strInitialSuspSedimentFile; - break; - - case (FINE_UNCONS_RASTER): - // Initial Unconsolidated Fine Sediment GIS data - strGISFile = m_VstrInitialFineUnconsSedimentFile[nLayer]; - break; - - case (SAND_UNCONS_RASTER): - // Initial Unconsolidated Sand Sediment GIS data - strGISFile = m_VstrInitialSandUnconsSedimentFile[nLayer]; - break; - - case (COARSE_UNCONS_RASTER): - // Initial Unconsolidated Coarse Sediment GIS data - strGISFile = m_VstrInitialCoarseUnconsSedimentFile[nLayer]; - break; - - case (FINE_CONS_RASTER): - // Initial Consolidated Fine Sediment GIS data - strGISFile = m_VstrInitialFineConsSedimentFile[nLayer]; - break; - - case (SAND_CONS_RASTER): - // Initial Consolidated Sand Sediment GIS data - strGISFile = m_VstrInitialSandConsSedimentFile[nLayer]; - break; - - case (COARSE_CONS_RASTER): - // Initial Consolidated Coarse Sediment GIS data - strGISFile = m_VstrInitialCoarseConsSedimentFile[nLayer]; - break; - } - - // Use GDAL to create a dataset object, which then opens the GIS file - GDALDataset *pGDALDataset = - static_cast(GDALOpen(strGISFile.c_str(), GA_ReadOnly)); - - if (NULL == pGDALDataset) { - // Can't open file (note will already have sent GDAL error message to - // stdout) - cerr << ERR << "cannot open " << strGISFile - << " for input: " << CPLGetLastErrorMsg() << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // Opened OK, so get dataset information - strDriverCode = pGDALDataset->GetDriver()->GetDescription(); - strDriverDesc = pGDALDataset->GetDriver()->GetMetadataItem(GDAL_DMD_LONGNAME); - strProjection = pGDALDataset->GetProjectionRef(); - - // Get geotransformation info - double dGeoTransform[6]; - if (CE_Failure == pGDALDataset->GetGeoTransform(dGeoTransform)) { - // Can't get geotransformation (note will already have sent GDAL error - // message to stdout) - cerr << ERR << CPLGetLastErrorMsg() << " in " << strGISFile << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // Now get dataset size, and do some checks - int const nTmpXSize = pGDALDataset->GetRasterXSize(); - if (nTmpXSize != m_nXGridSize) { - // Error: incorrect number of columns specified - cerr << ERR << "different number of columns in " << strGISFile << " (" - << nTmpXSize << ") and " << m_strInitialBasementDEMFile << "(" - << m_nXGridSize << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - int const nTmpYSize = pGDALDataset->GetRasterYSize(); - if (nTmpYSize != m_nYGridSize) { - // Error: incorrect number of rows specified - cerr << ERR << "different number of rows in " << strGISFile << " (" - << nTmpYSize << ") and " << m_strInitialBasementDEMFile << " (" - << m_nYGridSize << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - double dTmp = m_dGeoTransform[0] - (m_dGeoTransform[1] / 2); - if (!bFPIsEqual(dTmp, m_dNorthWestXExtCRS, TOLERANCE)) { - // Error: different min x from DEM file - cerr << ERR << "different min x values in " << strGISFile << " (" << dTmp - << ") and " << m_strInitialBasementDEMFile << " (" - << m_dNorthWestXExtCRS << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - dTmp = m_dGeoTransform[3] - (m_dGeoTransform[5] / 2); - if (!bFPIsEqual(dTmp, m_dNorthWestYExtCRS, TOLERANCE)) { - // Error: different min x from DEM file - cerr << ERR << "different min y values in " << strGISFile << " (" << dTmp - << ") and " << m_strInitialBasementDEMFile << " (" - << m_dNorthWestYExtCRS << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - double const dTmpResX = tAbs(dGeoTransform[1]); - if (!bFPIsEqual(dTmpResX, m_dCellSide, 1e-2)) { - // Error: different cell size in X direction: note that due to rounding - // errors in some GIS packages, must expect some discrepancies - cerr << ERR << "cell size in X direction (" << dTmpResX << ") in " - << strGISFile << " differs from cell size in of basement DEM (" - << m_dCellSide << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - double const dTmpResY = tAbs(dGeoTransform[5]); - if (!bFPIsEqual(dTmpResY, m_dCellSide, 1e-2)) { - // Error: different cell size in Y direction: note that due to rounding - // errors in some GIS packages, must expect some discrepancies - cerr << ERR << "cell size in Y direction (" << dTmpResY << ") in " - << strGISFile << " differs from cell size of basement DEM (" - << m_dCellSide << ")" << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // Now get GDAL raster band information - GDALRasterBand *pGDALBand = pGDALDataset->GetRasterBand( - 1); // TODO 028 Give a message if there are several bands - int nBlockXSize = 0, nBlockYSize = 0; - pGDALBand->GetBlockSize(&nBlockXSize, &nBlockYSize); - strDataType = GDALGetDataTypeName(pGDALBand->GetRasterDataType()); - - switch (nDataItem) { - case (LANDFORM_RASTER): - // Initial Landform Class GIS data - m_strGDALLDriverCode = strDriverCode; - m_strGDALLDriverDesc = strDriverDesc; - m_strGDALLProjection = strProjection; - m_strGDALLDataType = strDataType; - break; - - case (INTERVENTION_CLASS_RASTER): - // Intervention class - m_strGDALICDriverCode = strDriverCode; - m_strGDALICDriverDesc = strDriverDesc; - m_strGDALICProjection = strProjection; - m_strGDALICDataType = strDataType; - break; - - case (INTERVENTION_HEIGHT_RASTER): - // Intervention height - m_strGDALIHDriverCode = strDriverCode; - m_strGDALIHDriverDesc = strDriverDesc; - m_strGDALIHProjection = strProjection; - m_strGDALIHDataType = strDataType; - break; - - case (SUSP_SED_RASTER): - // Initial Suspended Sediment GIS data - m_strGDALISSDriverCode = strDriverCode; - m_strGDALISSDriverDesc = strDriverDesc; - m_strGDALISSProjection = strProjection; - m_strGDALISSDataType = strDataType; - break; - - case (FINE_UNCONS_RASTER): - // Initial Unconsolidated Fine Sediment GIS data - m_VstrGDALIUFDriverCode[nLayer] = strDriverCode; - m_VstrGDALIUFDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALIUFProjection[nLayer] = strProjection; - m_VstrGDALIUFDataType[nLayer] = strDataType; - break; - - case (SAND_UNCONS_RASTER): - // Initial Unconsolidated Sand Sediment GIS data - m_VstrGDALIUSDriverCode[nLayer] = strDriverCode; - m_VstrGDALIUSDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALIUSProjection[nLayer] = strProjection; - m_VstrGDALIUSDataType[nLayer] = strDataType; - break; - - case (COARSE_UNCONS_RASTER): - // Initial Unconsolidated Coarse Sediment GIS data - m_VstrGDALIUCDriverCode[nLayer] = strDriverCode; - m_VstrGDALIUCDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALIUCProjection[nLayer] = strProjection; - m_VstrGDALIUCDataType[nLayer] = strDataType; - break; - - case (FINE_CONS_RASTER): - // Initial Consolidated Fine Sediment GIS data - m_VstrGDALICFDriverCode[nLayer] = strDriverCode; - m_VstrGDALICFDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALICFProjection[nLayer] = strProjection; - m_VstrGDALICFDataType[nLayer] = strDataType; - break; - - case (SAND_CONS_RASTER): - // Initial Consolidated Sand Sediment GIS data - m_VstrGDALICSDriverCode[nLayer] = strDriverCode; - m_VstrGDALICSDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALICSProjection[nLayer] = strProjection; - m_VstrGDALICSDataType[nLayer] = strDataType; - break; - - case (COARSE_CONS_RASTER): - // Initial Consolidated Coarse Sediment GIS data - m_VstrGDALICCDriverCode[nLayer] = strDriverCode; - m_VstrGDALICCDriverDesc[nLayer] = strDriverDesc; - m_VstrGDALICCProjection[nLayer] = strProjection; - m_VstrGDALICCDataType[nLayer] = strDataType; - break; - } - - // If present, get the missing value setting - string const strTmp = strToLower(&strDataType); - if (strTmp.find("int") != string::npos) { - // This is an integer layer - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to - // fail silently, if it fails - m_nGISMissingValue = static_cast( - pGDALBand->GetNoDataValue()); // Note will fail for some formats - CPLPopErrorHandler(); - - if (m_nGISMissingValue != m_nMissingValue) { - cerr - << " " << NOTE << "NODATA value in " << strGISFile << " is " - << m_nGISMissingValue - << "\n instead using CoatalME's default integer NODATA value " - << m_nMissingValue << endl; - } - } else { - // This is a floating point layer - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to - // fail silently, if it fails - m_dGISMissingValue = - pGDALBand->GetNoDataValue(); // Note will fail for some formats - CPLPopErrorHandler(); - - if (!bFPIsEqual(m_dGISMissingValue, m_dMissingValue, TOLERANCE)) { - cerr << " " << NOTE << "NODATA value in " << strGISFile << " is " - << m_dGISMissingValue - << "\n instead using CoastalME's default floating-point " - "NODATA value " - << m_dMissingValue << endl; - } - } - - // Allocate memory for a 1D array, to hold the scan line for GDAL - double *pdScanline = new double[m_nXGridSize]; - if (NULL == pdScanline) { - // Error, can't allocate memory - cerr << ERR << "cannot allocate memory for " << m_nXGridSize - << " x 1D array" << endl; - return (RTN_ERR_MEMALLOC); - } - - // Now read in the data - int nMissing = 0; - - for (int nY = 0; nY < m_nYGridSize; nY++) { - // Read scanline - if (CE_Failure == pGDALBand->RasterIO(GF_Read, 0, nY, m_nXGridSize, 1, - pdScanline, m_nXGridSize, 1, - GDT_Float64, 0, 0, NULL)) { - // Error while reading scanline - cerr << ERR << CPLGetLastErrorMsg() << " in " << strGISFile << endl; - return (RTN_ERR_RASTER_FILE_READ); - } - - // All OK, so read scanline into cells (including any missing values) - for (int nX = 0; nX < m_nXGridSize; nX++) { - int nTmp; - - switch (nDataItem) { - case (LANDFORM_RASTER): - // Initial Landform Class GIS data, is integer TODO 030 Do we also need - // a landform sub-category input? - nTmp = static_cast(pdScanline[nX]); - - if ((isnan(nTmp)) || (nTmp == m_nGISMissingValue)) { - nTmp = m_nMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->SetLFCategory(nTmp); - break; - - case (INTERVENTION_CLASS_RASTER): - // Intervention class, is integer - nTmp = static_cast(pdScanline[nX]); - - if ((isnan(nTmp)) || (nTmp == m_nGISMissingValue)) { - nTmp = m_nMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY].SetInterventionClass(nTmp); - break; - - case (INTERVENTION_HEIGHT_RASTER): - // Intervention height - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY].SetInterventionHeight(dTmp); - break; - - case (SUSP_SED_RASTER): - // Initial Suspended Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY].SetSuspendedSediment(dTmp); - break; - - case (FINE_UNCONS_RASTER): - // Initial Unconsolidated Fine Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->SetFineDepth(dTmp); - break; - - case (SAND_UNCONS_RASTER): - // Initial Unconsolidated Sand Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->SetSandDepth(dTmp); - break; - - case (COARSE_UNCONS_RASTER): - // Initial Unconsolidated Coarse Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->SetCoarseDepth(dTmp); - break; - - case (FINE_CONS_RASTER): - // Initial Consolidated Fine Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->SetFineDepth(dTmp); - break; - - case (SAND_CONS_RASTER): - // Initial Consolidated Sand Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->SetSandDepth(dTmp); - break; - - case (COARSE_CONS_RASTER): - // Initial Consolidated Coarse Sediment GIS data - dTmp = pdScanline[nX]; - - if ((isnan(dTmp)) || - (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { - dTmp = m_dMissingValue; - nMissing++; - } - - m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->SetCoarseDepth(dTmp); - break; - } - } - } - - // Finished, so get rid of dataset object - GDALClose(pGDALDataset); - - // Get rid of memory allocated to this array - delete[] pdScanline; - - if (nMissing > 0) { - cerr << WARN << nMissing << " missing values in " << strGISFile << endl; - LogStream << WARN << nMissing << " missing values in " << strGISFile - << endl; - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! Writes GIS raster files using GDAL, using data from the RasterGrid array -//=============================================================================================================================== -bool CSimulation::bWriteRasterGISFile(int const nDataItem, - string const *strPlotTitle, - int const nLayer, double const dElev) { - bool bIsInteger = false; - bool bIsUnsignedLong = false; - - // Begin constructing the file name for this save - string strFilePathName(m_strOutPath); - string strLayer = "_layer_"; - - stringstream ststrTmp; - - strLayer.append(to_string(nLayer + 1)); - - switch (nDataItem) { - case (RASTER_PLOT_BASEMENT_ELEVATION): - strFilePathName.append(RASTER_BASEMENT_ELEVATION_NAME); - break; - - case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): - strFilePathName.append(RASTER_SEDIMENT_TOP_ELEVATION_NAME); - break; - - case (RASTER_PLOT_OVERALL_TOP_ELEVATION): - strFilePathName.append(RASTER_OVERALL_TOP_ELEVATION_NAME); - break; - - case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_NAME); - break; - - case (RASTER_PLOT_SLOPE_FOR_CLIFF_TOE): - strFilePathName.append(RASTER_SLOPE_FOR_CLIFF_TOE_NAME); - break; - - case (RASTER_PLOT_CLIFF_TOE): - strFilePathName.append(RASTER_CLIFF_TOE_NAME); - break; - - case (RASTER_PLOT_SEA_DEPTH): - strFilePathName.append(RASTER_SEA_DEPTH_NAME); - break; - - case (RASTER_PLOT_AVG_SEA_DEPTH): - strFilePathName.append(RASTER_AVG_SEA_DEPTH_NAME); - break; - - case (RASTER_PLOT_WAVE_HEIGHT): - strFilePathName.append(RASTER_WAVE_HEIGHT_NAME); - break; - - case (RASTER_PLOT_AVG_WAVE_HEIGHT): - strFilePathName.append(RASTER_AVG_WAVE_HEIGHT_NAME); - break; - - case (RASTER_PLOT_WAVE_ORIENTATION): - strFilePathName.append(RASTER_WAVE_ORIENTATION_NAME); - break; - - case (RASTER_PLOT_AVG_WAVE_ORIENTATION): - strFilePathName.append(RASTER_AVG_WAVE_ORIENTATION_NAME); - break; - - case (RASTER_PLOT_BEACH_PROTECTION): - strFilePathName.append(RASTER_BEACH_PROTECTION_NAME); - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): - strFilePathName.append(RASTER_POTENTIAL_PLATFORM_EROSION_NAME); - break; - - case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): - strFilePathName.append(RASTER_ACTUAL_PLATFORM_EROSION_NAME); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): - strFilePathName.append(RASTER_TOTAL_POTENTIAL_PLATFORM_EROSION_NAME); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): - strFilePathName.append(RASTER_TOTAL_ACTUAL_PLATFORM_EROSION_NAME); - break; - - case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): - strFilePathName.append(RASTER_POTENTIAL_BEACH_EROSION_NAME); - break; - - case (RASTER_PLOT_ACTUAL_BEACH_EROSION): - strFilePathName.append(RASTER_ACTUAL_BEACH_EROSION_NAME); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): - strFilePathName.append(RASTER_TOTAL_POTENTIAL_BEACH_EROSION_NAME); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): - strFilePathName.append(RASTER_TOTAL_ACTUAL_BEACH_EROSION_NAME); - break; - - case (RASTER_PLOT_BEACH_DEPOSITION): - strFilePathName.append(RASTER_BEACH_DEPOSITION_NAME); - break; - - case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): - strFilePathName.append(RASTER_TOTAL_BEACH_DEPOSITION_NAME); - break; - - case (RASTER_PLOT_SUSPENDED_SEDIMENT): - strFilePathName.append(RASTER_SUSP_SED_NAME); - break; - - case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): - strFilePathName.append(RASTER_AVG_SUSP_SED_NAME); - break; - - case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_FINE_UNCONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_SAND_UNCONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_COARSE_UNCONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_FINE_CONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_SAND_CONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): - strFilePathName.append(RASTER_COARSE_CONS_NAME); - strFilePathName.append(strLayer); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_FINE_NAME); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_SAND_NAME); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_COARSE_NAME); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): - strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_FINE_NAME); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): - strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_SAND_NAME); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): - strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE_NAME); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_DEPOSITION_SAND_NAME); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_DEPOSITION_COARSE_NAME); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): - strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND_NAME); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): - strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE_NAME); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): - strFilePathName.append(RASTER_CLIFF_COLLAPSE_TIMESTEP_NAME); - break; - - case (RASTER_PLOT_CLIFF_NOTCH_ALL): - strFilePathName.append(RASTER_CLIFF_NOTCH_ALL_NAME); - break; - - case (RASTER_PLOT_INTERVENTION_HEIGHT): - strFilePathName.append(RASTER_INTERVENTION_HEIGHT_NAME); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): - strFilePathName.append(RASTER_DEEP_WATER_WAVE_ORIENTATION_NAME); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): - strFilePathName.append(RASTER_DEEP_WATER_WAVE_HEIGHT_NAME); - break; - - case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): - strFilePathName.append(RASTER_POLYGON_GAIN_OR_LOSS_NAME); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): - strFilePathName.append(RASTER_WAVE_PERIOD_NAME); - break; - - case (RASTER_PLOT_SEDIMENT_INPUT): - strFilePathName.append(RASTER_SEDIMENT_INPUT_EVENT_NAME); - break; - - case (RASTER_PLOT_BEACH_MASK): - bIsInteger = true; - strFilePathName.append(RASTER_BEACH_MASK_NAME); - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): - bIsInteger = true; - strFilePathName.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_NAME); - break; - - case (RASTER_PLOT_INUNDATION_MASK): - bIsInteger = true; - strFilePathName.append(RASTER_INUNDATION_MASK_NAME); - break; - - case (RASTER_PLOT_SLICE): - bIsInteger = true; - ststrTmp.str(""); - ststrTmp.clear(); - - // TODO 031 Get working for multiple slices - strFilePathName.append(RASTER_SLICE_NAME); - ststrTmp << "_" << dElev << "_"; - strFilePathName.append(ststrTmp.str()); - break; - - case (RASTER_PLOT_LANDFORM): - bIsInteger = true; - strFilePathName.append(RASTER_LANDFORM_NAME); - break; - - case (RASTER_PLOT_INTERVENTION_CLASS): - bIsInteger = true; - strFilePathName.append(RASTER_INTERVENTION_CLASS_NAME); - break; - - case (RASTER_PLOT_COAST): - bIsInteger = true; - strFilePathName.append(RASTER_COAST_NAME); - break; - - case (RASTER_PLOT_NORMAL_PROFILE): - bIsInteger = true; - strFilePathName.append(RASTER_COAST_NORMAL_NAME); - break; - - case (RASTER_PLOT_ACTIVE_ZONE): - bIsInteger = true; - strFilePathName.append(RASTER_ACTIVE_ZONE_NAME); - break; - - case (RASTER_PLOT_POLYGON): - bIsInteger = true; - strFilePathName.append(RASTER_POLYGON_NAME); - break; - - case (RASTER_PLOT_SHADOW_ZONE): - bIsInteger = true; - strFilePathName.append(RASTER_SHADOW_ZONE_NAME); - break; - - case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): - bIsInteger = true; - strFilePathName.append(RASTER_SHADOW_DOWNDRIFT_ZONE_NAME); - break; - - case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): - bIsInteger = true; - strFilePathName.append(RASTER_POLYGON_UPDRIFT_OR_DOWNDRIFT_NAME); - break; - - case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): - bIsInteger = true; - strFilePathName.append(RASTER_SETUP_SURGE_FLOOD_MASK_NAME); - break; - - case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): - bIsInteger = true; - strFilePathName.append(RASTER_SETUP_SURGE_RUNUP_FLOOD_MASK_NAME); - break; - - case (RASTER_PLOT_WAVE_FLOOD_LINE): - bIsInteger = true; - strFilePathName.append(RASTER_WAVE_FLOOD_LINE_NAME); - break; - } - - // Append the 'save number' to the filename, and prepend zeros to the save - // number - ststrTmp.str(""); - ststrTmp.clear(); - - strFilePathName.append("_"); - - if (m_bGISSaveDigitsSequential) { - // Save number is m_bGISSaveDigitsSequential - ststrTmp << FillToWidth('0', m_nGISMaxSaveDigits) << m_nGISSave; - } else { - // Save number is iteration - ststrTmp << FillToWidth('0', m_nGISMaxSaveDigits) << m_ulIter; - } - - strFilePathName.append(ststrTmp.str()); - - // Finally, maybe append the extension - if (!m_strGDALRasterOutputDriverExtension.empty()) { - strFilePathName.append("."); - strFilePathName.append(m_strGDALRasterOutputDriverExtension); - } - - // TODO 065 Used to try to debug floating point exception in pDriver->Create() - // below CPLSetConfigOption("CPL_DEBUG", "ON"); - // CPLSetConfigOption("GDAL_NUM_THREADS", "1"); - - GDALDriver *pDriver; - GDALDataset *pDataSet; - - if (m_bGDALCanCreate) { - // The user-requested raster driver supports the Create() method - pDriver = GetGDALDriverManager()->GetDriverByName( - m_strRasterGISOutFormat.c_str()); - - if (bIsInteger) { - pDataSet = - pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, - 1, GDT_Int16, m_papszGDALRasterOptions); - } else if (bIsUnsignedLong) { - pDataSet = - pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, - 1, GDT_UInt32, m_papszGDALRasterOptions); - - } else if (m_strRasterGISOutFormat == "gpkg") { - // TODO 065 Floating point exception here - pDataSet = - pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, - 1, GDT_Byte, m_papszGDALRasterOptions); - } else { - pDataSet = pDriver->Create(strFilePathName.c_str(), m_nXGridSize, - m_nYGridSize, 1, m_GDALWriteFloatDataType, - m_papszGDALRasterOptions); - } - - if (NULL == pDataSet) { - // Error, couldn't create file - cerr << ERR << "cannot create " << m_strRasterGISOutFormat - << " file named " << strFilePathName << endl - << CPLGetLastErrorMsg() << endl; - return false; - } - } else { - // The user-requested raster driver does not support the Create() method, so - // we must first create a memory-file dataset - pDriver = GetGDALDriverManager()->GetDriverByName("MEM"); - pDataSet = pDriver->Create("", m_nXGridSize, m_nYGridSize, 1, - m_GDALWriteFloatDataType, NULL); - - if (NULL == pDataSet) { - // Couldn't create in-memory file dataset - cerr << ERR << "cannot create in-memory file for " - << m_strRasterGISOutFormat << " file named " << strFilePathName - << endl - << CPLGetLastErrorMsg() << endl; - return false; - } - } - - // Set projection info for output dataset (will be same as was read in from - // basement DEM) - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - pDataSet->SetProjection( - m_strGDALBasementDEMProjection.c_str()); // Will fail for some formats - CPLPopErrorHandler(); - - // Set geotransformation info for output dataset (will be same as was read in - // from DEM) - if (CE_Failure == pDataSet->SetGeoTransform(m_dGeoTransform)) - LogStream << WARN << "cannot write geotransformation information to " - << m_strRasterGISOutFormat << " file named " << strFilePathName - << endl - << CPLGetLastErrorMsg() << endl; - - // Allocate memory for a 1D array, to hold the floating point raster band data - // for GDAL - double *pdRaster = new double[m_ulNumCells]; - if (NULL == pdRaster) { - // Error, can't allocate memory - cerr << ERR << "cannot allocate memory for " << m_ulNumCells - << " x 1D floating-point array for " << m_strRasterGISOutFormat - << " file named " << strFilePathName << endl; - return (RTN_ERR_MEMALLOC); - } - - bool bScaleOutput = false; - double dRangeScale = 0; - double dDataMin = 0; - - if (!m_bGDALCanWriteFloat) { - double dDataMax = 0; - - // The output file format cannot handle floating-point numbers, so we may - // need to scale the output - GetRasterOutputMinMax(nDataItem, dDataMin, dDataMax, nLayer, 0); - - double const dDataRange = dDataMax - dDataMin; - double const dWriteRange = - static_cast(m_lGDALMaxCanWrite - m_lGDALMinCanWrite); - - if (dDataRange > 0) - dRangeScale = dWriteRange / dDataRange; - - // If we are attempting to write values which are outside this format's - // allowable range, and the user has set the option, then scale the output - if (((dDataMin < static_cast(m_lGDALMinCanWrite)) || - (dDataMax > static_cast(m_lGDALMaxCanWrite))) && - m_bScaleRasterOutput) - bScaleOutput = true; - } - - // Fill the array - int n = 0; - int nPoly = 0; - int nPolyCoast = 0; - int nTopLayer = 0; - double dTmp = 0; - - for (int nY = 0; nY < m_nYGridSize; nY++) { - for (int nX = 0; nX < m_nXGridSize; nX++) { - switch (nDataItem) { - case (RASTER_PLOT_BASEMENT_ELEVATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBasementElev(); - break; - - case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); - break; - - case (RASTER_PLOT_OVERALL_TOP_ELEVATION): - dTmp = - m_pRasterGrid->m_Cell[nX][nY].dGetSedimentPlusInterventionTopElev(); - break; - - case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetConsSedSlope(); - break; - - case (RASTER_PLOT_SLOPE_FOR_CLIFF_TOE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSlopeForCliffToe(); - break; - - case (RASTER_PLOT_CLIFF_TOE): - dTmp = static_cast(m_pRasterGrid->m_Cell[nX][nY].bIsCliffToe()); - break; - - case (RASTER_PLOT_SEA_DEPTH): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); - break; - - case (RASTER_PLOT_AVG_SEA_DEPTH): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSeaDepth() / - static_cast(m_ulIter); - break; - - case (RASTER_PLOT_WAVE_HEIGHT): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); - else - dTmp = 0; - break; - - case (RASTER_PLOT_AVG_WAVE_HEIGHT): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotWaveHeight() / - static_cast(m_ulIter); - else - dTmp = 0; - break; - - case (RASTER_PLOT_WAVE_ORIENTATION): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); - else - dTmp = 0; - break; - - case (RASTER_PLOT_AVG_WAVE_ORIENTATION): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotWaveAngle() / - static_cast(m_ulIter); - else - dTmp = 0; - break; - - case (RASTER_PLOT_BEACH_PROTECTION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBeachProtectionFactor(); - - if (bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) - dTmp = m_dMissingValue; - else - dTmp = 1 - dTmp; // Output the inverse, seems more intuitive - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetPotentialPlatformErosion(); - break; - - case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetActualPlatformErosion(); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotPotentialPlatformErosion(); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotActualPlatformErosion(); - break; - - case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetPotentialBeachErosion(); - break; - - case (RASTER_PLOT_ACTUAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetActualBeachErosion(); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotPotentialBeachErosion(); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotActualBeachErosion(); - break; - - case (RASTER_PLOT_BEACH_DEPOSITION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBeachDeposition(); - break; - - case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotBeachDeposition(); - break; - - case (RASTER_PLOT_SUSPENDED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); - break; - - case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSuspendedSediment() / - static_cast(m_ulIter); - break; - - case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->dGetFineDepth(); - break; - - case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->dGetSandDepth(); - break; - - case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetUnconsolidatedSediment() - ->dGetCoarseDepth(); - break; - - case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->dGetFineDepth(); - break; - - case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->dGetSandDepth(); - break; - - case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nLayer) - ->pGetConsolidatedSediment() - ->dGetCoarseDepth(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .dGetThisIterCliffCollapseErosionFine(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .dGetThisIterCliffCollapseErosionSand(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .dGetThisIterCliffCollapseErosionCoarse(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseFine(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseSand(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseCoarse(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .dGetThisIterCliffCollapseSandTalusDeposition(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .dGetThisIterCliffCollapseCoarseTalusDeposition(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSandTalusDeposition(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCoarseTalusDeposition(); - break; - - case (RASTER_PLOT_CLIFF_NOTCH_ALL): - dTmp = - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->dGetCliffNotchDepth(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): - dTmp = static_cast(m_pRasterGrid->m_Cell[nX][nY] - .pGetLandform() - ->ulGetCliffCollapseTimestep()); - bIsUnsignedLong = true; - break; - - case (RASTER_PLOT_INTERVENTION_HEIGHT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight(); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle(); - else - dTmp = 0; - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight(); - else - dTmp = 0; - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWavePeriod(); - else - dTmp = 0; - break; - - case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): - nPoly = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - nPolyCoast = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonCoastID(); - - if (nPoly == INT_NODATA) - dTmp = m_dMissingValue; - else { - // Get total volume (all sediment size classes) of change in sediment - // for this polygon for this timestep (-ve erosion, +ve deposition) - dTmp = m_VCoast[nPolyCoast] - .pGetPolygon(nPoly) - ->dGetBeachDepositionAndSuspensionAllUncons() * - m_dCellArea; - - // Calculate the rate in m^3 / sec - dTmp /= (m_dTimeStep * 3600); - } - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): - // cppcheck-suppress assignBoolToFloat - dTmp = m_pRasterGrid->m_Cell[nX][nY].bPotentialPlatformErosion(); - break; - - case (RASTER_PLOT_INUNDATION_MASK): - // cppcheck-suppress assignBoolToFloat - dTmp = m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea(); - break; - - case (RASTER_PLOT_BEACH_MASK): - dTmp = 0; - nTopLayer = - m_pRasterGrid->m_Cell[nX][nY].nGetTopNonZeroLayerAboveBasement(); - - if ((nTopLayer == INT_NODATA) || - (nTopLayer == NO_NONZERO_THICKNESS_LAYERS)) - break; - - if ((m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nTopLayer) - ->dGetUnconsolidatedThickness() > 0) && - (m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() > - m_dThisIterSWL)) - dTmp = 1; - - break; - - case (RASTER_PLOT_SLICE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetLayerAtElev(dElev); - break; - - case (RASTER_PLOT_LANDFORM): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory(); - bIsInteger = true; - - if ((static_cast(dTmp) == LF_CAT_DRIFT) || - (static_cast(dTmp) == LF_CAT_CLIFF)) - dTmp = - m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFSubCategory(); - - break; - - case (RASTER_PLOT_INTERVENTION_CLASS): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetInterventionClass(); - bIsInteger = true; - break; - - case (RASTER_PLOT_COAST): - dTmp = (m_pRasterGrid->m_Cell[nX][nY].bIsCoastline() ? 1 : 0); - break; - - case (RASTER_PLOT_NORMAL_PROFILE): - // dTmp = (m_pRasterGrid->m_Cell[nX][nY].bIsProfile() ? 1 : 0); - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetProfileID(); - bIsInteger = true; - break; - - case (RASTER_PLOT_ACTIVE_ZONE): - dTmp = (m_pRasterGrid->m_Cell[nX][nY].bIsInActiveZone() ? 1 : 0); - break; - - case (RASTER_PLOT_POLYGON): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - bIsInteger = true; - break; - - case (RASTER_PLOT_SHADOW_ZONE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetShadowZoneNumber(); - bIsInteger = true; - break; - - case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); - bIsInteger = true; - break; - - case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): - nPoly = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - nPolyCoast = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonCoastID(); - bIsInteger = true; - - if (nPoly == INT_NODATA) - dTmp = m_nMissingValue; - else { - if (m_VCoast[nPolyCoast].pGetPolygon(nPoly)->bDownCoastThisIter()) - dTmp = 1; - else - dTmp = 0; - } - break; - - case (RASTER_PLOT_SEDIMENT_INPUT): - dTmp = m_pRasterGrid->m_Cell[nX][nY] - .pGetLayerAboveBasement(nTopLayer) - ->pGetUnconsolidatedSediment() - ->dGetTotAllSedimentInputDepth(); - break; - - case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): - dTmp = (m_pRasterGrid->m_Cell[nX][nY].bIsFloodBySetupSurge() ? 1 : 0); - break; - - case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): - dTmp = - (m_pRasterGrid->m_Cell[nX][nY].bIsFloodBySetupSurgeRunup() ? 1 : 0); - break; - - case (RASTER_PLOT_WAVE_FLOOD_LINE): - dTmp = (m_pRasterGrid->m_Cell[nX][nY].bIsFloodline() ? 1 : 0); - break; - } - - // If necessary, scale this value - if (bScaleOutput) { - if (bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) - dTmp = 0; // TODO 032 Improve this - else - dTmp = dRound(static_cast(m_lGDALMinCanWrite) + - (dRangeScale * (dTmp - dDataMin))); - } - - // Write this value to the array - pdRaster[n++] = dTmp; - } - } - - // Create a single raster band - GDALRasterBand *pBand = pDataSet->GetRasterBand(1); - - // And fill it with the NODATA value - if (bIsInteger) - pBand->Fill(m_nMissingValue); - else if (bIsUnsignedLong) - pBand->Fill(static_cast(m_ulMissingValue)); - else - pBand->Fill(m_dMissingValue); - - // Set value units for this band - string strUnits; - - switch (nDataItem) { - case (RASTER_PLOT_BASEMENT_ELEVATION): - case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): - case (RASTER_PLOT_OVERALL_TOP_ELEVATION): - case (RASTER_PLOT_SEA_DEPTH): - case (RASTER_PLOT_AVG_SEA_DEPTH): - case (RASTER_PLOT_WAVE_HEIGHT): - case (RASTER_PLOT_AVG_WAVE_HEIGHT): - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): - case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): - case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): - case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): - case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): - case (RASTER_PLOT_ACTUAL_BEACH_EROSION): - case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): - case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): - case (RASTER_PLOT_BEACH_DEPOSITION): - case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): - case (RASTER_PLOT_SUSPENDED_SEDIMENT): - case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): - case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): - case (RASTER_PLOT_INTERVENTION_HEIGHT): - case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): - case (RASTER_PLOT_SEDIMENT_INPUT): - case (RASTER_PLOT_CLIFF_NOTCH_ALL): - strUnits = "m"; - break; - - case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): - strUnits = "m/m"; - break; - - case (RASTER_PLOT_WAVE_ORIENTATION): - case (RASTER_PLOT_AVG_WAVE_ORIENTATION): - strUnits = "degrees"; - break; - - case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): - strUnits = "cumecs"; - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): - strUnits = "secs"; - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): - case (RASTER_PLOT_INUNDATION_MASK): - case (RASTER_PLOT_BEACH_MASK): - case (RASTER_PLOT_SLICE): - case (RASTER_PLOT_LANDFORM): - case (RASTER_PLOT_INTERVENTION_CLASS): - case (RASTER_PLOT_COAST): - case (RASTER_PLOT_NORMAL_PROFILE): - case (RASTER_PLOT_ACTIVE_ZONE): - case (RASTER_PLOT_POLYGON): - case (RASTER_PLOT_SHADOW_ZONE): - case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): - case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): - case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): - case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): - case (RASTER_PLOT_WAVE_FLOOD_LINE): - case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): - strUnits = "none"; - break; - } - - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - pBand->SetUnitType(strUnits.c_str()); // Not supported for some GIS formats - CPLPopErrorHandler(); - - // Tell the output dataset about NODATA (missing values) - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - - if (bIsInteger) - pBand->SetNoDataValue(m_nMissingValue); // Will fail for some formats - if (bIsUnsignedLong) - pBand->SetNoDataValueAsUInt64( - m_ulMissingValue); // Will fail for some formats - else - pBand->SetNoDataValue(m_dMissingValue); // Will fail for some formats - - CPLPopErrorHandler(); - - // Construct the description - string strDesc(*strPlotTitle); - - if (nDataItem == RASTER_PLOT_SLICE) { - ststrTmp.clear(); - ststrTmp << dElev << "m, "; - strDesc.append(ststrTmp.str()); - } - - strDesc.append(" at "); - strDesc.append(strDispTime(m_dSimElapsed, false, false)); - - // Set the GDAL description - pBand->SetDescription(strDesc.c_str()); - - // Set raster category names - char **papszCategoryNames = NULL; - - switch (nDataItem) { - case (RASTER_PLOT_SLICE): - papszCategoryNames = CSLAddString(papszCategoryNames, "Basement"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 0"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 1"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 2"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 3"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 4"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 5"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 6"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 7"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 8"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 9"); - break; - - case (RASTER_PLOT_LANDFORM): - papszCategoryNames = CSLAddString(papszCategoryNames, "None"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Hinterland"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Sea"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Cliff"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Drift"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Intervention"); - - papszCategoryNames = CSLAddString(papszCategoryNames, "Cliff on Coastline"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Inland Cliff"); - - papszCategoryNames = CSLAddString(papszCategoryNames, "Mixed Drift"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Talus"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Beach"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Dunes"); - break; - - case (RASTER_PLOT_INTERVENTION_CLASS): - papszCategoryNames = CSLAddString(papszCategoryNames, "None"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Structural"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Non-Structural"); - break; - - case (RASTER_PLOT_COAST): - papszCategoryNames = CSLAddString(papszCategoryNames, "Not coastline"); - papszCategoryNames = CSLAddString(papszCategoryNames, "Coastline"); - break; - - case (RASTER_PLOT_NORMAL_PROFILE): - papszCategoryNames = - CSLAddString(papszCategoryNames, "Not coastline-normal profile"); - papszCategoryNames = - CSLAddString(papszCategoryNames, "Coastline-normal profile"); - break; - - case (RASTER_PLOT_ACTIVE_ZONE): - papszCategoryNames = CSLAddString(papszCategoryNames, "Not in active zone"); - papszCategoryNames = CSLAddString(papszCategoryNames, "In active zone"); - break; - - case (RASTER_PLOT_POLYGON): - papszCategoryNames = CSLAddString(papszCategoryNames, "Not polygon"); - papszCategoryNames = CSLAddString(papszCategoryNames, "In polygon"); - break; - - case (RASTER_PLOT_SHADOW_ZONE): - papszCategoryNames = CSLAddString(papszCategoryNames, "Not in shadow zone"); - papszCategoryNames = CSLAddString(papszCategoryNames, "In shadow zone"); - break; - - case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): - papszCategoryNames = - CSLAddString(papszCategoryNames, "Not in shadow downdrift zone"); - papszCategoryNames = - CSLAddString(papszCategoryNames, "In shadow downdrift zone"); - break; - - case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): - papszCategoryNames = CSLAddString( - papszCategoryNames, "Updrift movement of unconsolidated sediment "); - papszCategoryNames = CSLAddString( - papszCategoryNames, "Downdrift movement of unconsolidated sediment"); - break; - - case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): - papszCategoryNames = - CSLAddString(papszCategoryNames, "Inundated by swl setup and surge "); - papszCategoryNames = CSLAddString(papszCategoryNames, - "Not inundated by swl setup and surge"); - break; - - case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): - papszCategoryNames = CSLAddString( - papszCategoryNames, "Inundated by swl setup, surge and runup "); - papszCategoryNames = CSLAddString( - papszCategoryNames, "Not inundated by swl setup, surge and runup"); - break; - - case (RASTER_PLOT_WAVE_FLOOD_LINE): - papszCategoryNames = - CSLAddString(papszCategoryNames, "Intersection line of inundation "); - papszCategoryNames = CSLAddString(papszCategoryNames, - "Not inundated by swl waves and runup"); - break; - } - - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - pBand->SetCategoryNames( - papszCategoryNames); // Not supported for some GIS formats - CPLPopErrorHandler(); - - // Now write the data with optimized I/O - // Enable multi-threaded compression for faster writing - CPLSetThreadLocalConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"); - - if (CE_Failure == pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, - pdRaster, m_nXGridSize, m_nYGridSize, - GDT_Float64, 0, 0, NULL)) { - // Write error, better error message - cerr << ERR << "cannot write data for " << m_strRasterGISOutFormat - << " file named " << strFilePathName << endl - << CPLGetLastErrorMsg() << endl; - delete[] pdRaster; - return false; - } - - // Calculate statistics for this band - double dMin, dMax, dMean, dStdDev; - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - pBand->ComputeStatistics(false, &dMin, &dMax, &dMean, &dStdDev, NULL, NULL); - CPLPopErrorHandler(); - - // And then write the statistics - CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail - // silently, if it fails - pBand->SetStatistics(dMin, dMax, dMean, dStdDev); - CPLPopErrorHandler(); - - if (!m_bGDALCanCreate) { - // Since the user-selected raster driver cannot use the Create() method, we - // have been writing to a dataset created by the in-memory driver. So now we - // need to use CreateCopy() to copy this in-memory dataset to a file in the - // user-specified raster driver format - GDALDriver *pOutDriver = GetGDALDriverManager()->GetDriverByName( - m_strRasterGISOutFormat.c_str()); - GDALDataset *pOutDataSet = - pOutDriver->CreateCopy(strFilePathName.c_str(), pDataSet, false, - m_papszGDALRasterOptions, NULL, NULL); - - if (NULL == pOutDataSet) { - // Couldn't create file - cerr << ERR << "cannot create " << m_strRasterGISOutFormat - << " file named " << strFilePathName << endl - << CPLGetLastErrorMsg() << endl; - return false; - } - - // Get rid of this user-selected dataset object - GDALClose(pOutDataSet); - } - - // Get rid of dataset object - GDALClose(pDataSet); - - // Also get rid of memory allocated to this array - delete[] pdRaster; - - return true; -} - -//=============================================================================================================================== -//! Interpolates wave properties from profile points to all cells within polygons -//! -//! ALGORITHM: k-Nearest Neighbor Inverse Distance Weighting (k-NN IDW) -//! -//! This function takes wave height components (X and Y) measured at discrete profile points -//! and interpolates them to a regular grid using a spatial interpolation method. -//! -//! METHOD OVERVIEW: -//! 1. Builds a k-d tree spatial index from input profile point coordinates (X, Y) -//! 2. For each grid cell, finds the k nearest profile points (typically k=12) -//! 3. Calculates interpolated value using inverse distance weighting (IDW) with power=2 -//! 4. Converts X/Y wave components back to magnitude and direction -//! 5. Updates grid cells with interpolated wave properties -//! -//! KEY PARAMETERS (defined in spatial_interpolation.cpp DualSpatialInterpolator): -//! - k_neighbors = 12 Number of nearest points to use for interpolation -//! - power = 2.0 Exponent for inverse distance weighting -//! (higher = more weight to closer points) -//! -//! TUNING GUIDANCE: -//! - Increase k_neighbors (e.g., 15-20) for smoother results with more averaging -//! - Decrease k_neighbors (e.g., 8-10) for results that follow local variations more closely -//! - Increase power (e.g., 3.0-4.0) to emphasize nearby points (sharper transitions) -//! - Decrease power (e.g., 1.0-1.5) for smoother, more gradual transitions -//! -//! IMPORTANT NOTES: -//! - This method does NOT respect transect structure (point 1 vs point 2, etc.) -//! - Treats all input points equally regardless of which transect they belong to -//! - Works well for scattered points but may not preserve transect-aligned features -//! - For transect-aware interpolation, consider bilinear methods instead -//! -//! COASTLINE ORIENTATION: -//! - The coastlines are available in m_VCoast[] vector -//! - Each coast has flux orientation: m_VCoast[i].dGetFluxOrientation(coastpoint) -//! - Each coast has breaking wave angle: m_VCoast[i].dGetBreakingWaveAngle(coastpoint) -//! - These could be used to implement coast-aware interpolation algorithms -//! -//! @param pVTransects Vector of TransectWaveData containing wave data per transect -//! @param pVdDeepWaterX X coordinates of deep water grid edge points -//! @param pVdDeepWaterY Y coordinates of deep water grid edge points -//! @param pVdDeepWaterHeightX X component of wave height at deep water points -//! @param pVdDeepWaterHeightY Y component of wave height at deep water points -//! @return RTN_OK on success, error code otherwise -//=============================================================================================================================== -int CSimulation::nInterpolateWavesToPolygonCells( - vector const *pVTransects, - vector const *pVdDeepWaterX, - vector const *pVdDeepWaterY, - vector const *pVdDeepWaterHeightX, - vector const *pVdDeepWaterHeightY) { - - // ============================================================================ - // STEP 1: Calculate grid dimensions and initialize variables - // ============================================================================ - - int nXSize = 0; - int nYSize = 0; - - // Average values used as fallback when interpolation fails or returns NaN - double dXAvg = 0; - double dYAvg = 0; - - // Calculate bounding box size - nXSize = m_nXMaxBoundingBox - m_nXMinBoundingBox + 1; - nYSize = m_nYMaxBoundingBox - m_nYMinBoundingBox + 1; - int const nGridSize = nXSize * nYSize; - - // Count total points across all transects plus deep water points - unsigned int nPoints = 0; - for (const auto& transect : *pVTransects) { - nPoints += static_cast(transect.VdX.size()); - } - nPoints += static_cast(pVdDeepWaterX->size()); - - // Initialize output arrays (will hold interpolated X and Y wave components) - vector VdOutX(nGridSize, 0); - vector VdOutY(nGridSize, 0); - - // ============================================================================ - // STEP 2: Prepare input data for spatial interpolation - // ============================================================================ - - // Flatten transect data and deep water data into contiguous arrays for the interpolator - std::vector points; - std::vector VdHeightX; - std::vector VdHeightY; - - points.reserve(nPoints); - VdHeightX.reserve(nPoints); - VdHeightY.reserve(nPoints); - - // Add profile/transect points - for (const auto& transect : *pVTransects) { - for (size_t i = 0; i < transect.VdX.size(); i++) { - points.emplace_back(transect.VdX[i], transect.VdY[i]); - VdHeightX.push_back(transect.VdHeightX[i]); - VdHeightY.push_back(transect.VdHeightY[i]); - } - } - - // Add deep water grid edge points - for (size_t i = 0; i < pVdDeepWaterX->size(); i++) { - points.emplace_back((*pVdDeepWaterX)[i], (*pVdDeepWaterY)[i]); - VdHeightX.push_back((*pVdDeepWaterHeightX)[i]); - VdHeightY.push_back((*pVdDeepWaterHeightY)[i]); - } - - // ============================================================================ - // STEP 3: Create spatial interpolator - // ============================================================================ - // - // DualSpatialInterpolator parameters: - // - points: Input point coordinates (from profiles/transects) - // - VdHeightX, VdHeightY: Wave height X and Y components at those points - // - k_neighbors = 12: Use 12 nearest neighbors for interpolation - // ** ADJUST THIS to change smoothness vs local detail ** - // - power = 2.0: Inverse distance weighting power - // ** ADJUST THIS to change influence of nearby vs distant points ** - // - // The interpolator builds a k-d tree for fast nearest neighbor search - // and shares it between X and Y interpolation for efficiency - DualSpatialInterpolator interp(points, VdHeightX, VdHeightY, 6, 2.0); - - // ============================================================================ - // STEP 4: Build query points (grid cells where we want interpolated values) - // ============================================================================ - - std::vector query_points; - query_points.reserve(nGridSize); - for (int nY = m_nYMinBoundingBox; nY <= m_nYMaxBoundingBox; nY++) { - for (int nX = m_nXMinBoundingBox; nX <= m_nXMaxBoundingBox; nX++) { - query_points.emplace_back(static_cast(nX), - static_cast(nY)); - } - } - - // ============================================================================ - // STEP 5: Perform batch interpolation - // ============================================================================ - // - // This does the actual interpolation for all grid points at once - // Uses OpenMP parallelization if available (see spatial_interpolation.cpp) - // Interpolates both X and Y components simultaneously using shared k-d tree - interp.Interpolate(query_points, VdOutX, VdOutY); - - // ============================================================================ - // STEP 6: Validate results and calculate average values for fallback - // ============================================================================ - // - // Check for NaN or unreasonably large values and replace with missing value marker - // Also calculate average of valid values to use as fallback - - int nXValid = 0; - int nYValid = 0; - - // Validate X component - for (unsigned int n = 0; n < VdOutX.size(); n++) { - if (isnan(VdOutX[n])) - VdOutX[n] = m_dMissingValue; - else if (tAbs(VdOutX[n]) > 1e10) // Sanity check for unreasonably large values - VdOutX[n] = m_dMissingValue; - else { - dXAvg += VdOutX[n]; - nXValid++; - } - } - - // Validate Y component - for (unsigned int n = 0; n < VdOutY.size(); n++) { - if (isnan(VdOutY[n])) - VdOutY[n] = m_dMissingValue; - else if (tAbs(VdOutY[n]) > 1e10) // Sanity check for unreasonably large values - VdOutY[n] = m_dMissingValue; - else { - dYAvg += VdOutY[n]; - nYValid++; - } - } - - // Calculate averages (for use as fallback when individual cells have missing values) - if (nXValid > 0) - dXAvg /= nXValid; - if (nYValid > 0) - dYAvg /= nYValid; - - // ============================================================================ - // STEP 7: Update grid cells with interpolated wave properties - // ============================================================================ - // - // Convert X and Y components back to magnitude and direction, - // then update each cell's wave attributes - - int n = 0; - - for (int nY = 0; nY < nYSize; nY++) { - for (int nX = 0; nX < nXSize; nX++) { - int const nActualX = nX + m_nXMinBoundingBox; - int const nActualY = nY + m_nYMinBoundingBox; - - if (m_pRasterGrid->m_Cell[nActualX][nActualY] - .bIsInContiguousSea()) { - // Only update sea cells - - if (m_pRasterGrid->m_Cell[nActualX][nActualY].nGetPolygonID() == - INT_NODATA) { - // -------------------------------------------------------------------- - // Deep water cell (NOT in a polygon) - // -------------------------------------------------------------------- - // Use the cell's pre-assigned deep water wave values - // (these cells are beyond the coastal zone, so don't need interpolation) - - double const dDeepWaterWaveHeight = - m_pRasterGrid->m_Cell[nActualX][nActualY] - .dGetCellDeepWaterWaveHeight(); - m_pRasterGrid->m_Cell[nActualX][nActualY].SetWaveHeight( - dDeepWaterWaveHeight); - - double const dDeepWaterWaveAngle = - m_pRasterGrid->m_Cell[nActualX][nActualY] - .dGetCellDeepWaterWaveAngle(); - m_pRasterGrid->m_Cell[nActualX][nActualY].SetWaveAngle( - dDeepWaterWaveAngle); - } else { - // -------------------------------------------------------------------- - // Coastal zone cell (IN a polygon) - // -------------------------------------------------------------------- - // Use the interpolated wave values calculated above - - double dWaveHeightX; - double dWaveHeightY; - - // Get interpolated X component (use average as fallback if missing/invalid) - if ((isnan(VdOutX[n])) || - (bFPIsEqual(VdOutX[n], m_dMissingValue, TOLERANCE))) - dWaveHeightX = dXAvg; - else - dWaveHeightX = VdOutX[n]; - - // Get interpolated Y component (use average as fallback if missing/invalid) - if ((isnan(VdOutY[n])) || - (bFPIsEqual(VdOutY[n], m_dMissingValue, TOLERANCE))) - dWaveHeightY = dYAvg; - else - dWaveHeightY = VdOutY[n]; - - // Convert X/Y components to magnitude and direction - double const dWaveHeight = sqrt((dWaveHeightX * dWaveHeightX) + - (dWaveHeightY * dWaveHeightY)); - double const dWaveDir = - atan2(dWaveHeightX, dWaveHeightY) * (180 / PI); - - // Update the cell's wave attributes - m_pRasterGrid->m_Cell[nActualX][nActualY].SetWaveHeight( - dWaveHeight); - m_pRasterGrid->m_Cell[nActualX][nActualY].SetWaveAngle( - dKeepWithin360(dWaveDir)); - - // Calculate wave height-to-depth ratio and update active zone status - // (active zone = where waves are breaking or near-breaking) - double const dSeaDepth = - m_pRasterGrid->m_Cell[nActualX][nActualY].dGetSeaDepth(); - - if ((dWaveHeight / dSeaDepth) >= - m_dBreakingWaveHeightDepthRatio) - m_pRasterGrid->m_Cell[nActualX][nActualY].SetInActiveZone( - true); - - // LogStream << " nX = " << nX << " nY = " << nY << " [" << - // nActualX - // << "][" << nActualY << "] waveheight = " << dWaveHeight << " - // dWaveDir = " << dWaveDir << " dKeepWithin360(dWaveDir) = " << - // dKeepWithin360(dWaveDir) << endl; - } - } - - // Increment with safety check - n++; - n = tMin(n, static_cast(VdOutX.size() - 1)); - } - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! If the user supplies multiple deep water wave height and angle values, -//! this routine interplates these to all cells (including dry land cells) -//=============================================================================================================================== -int CSimulation::nInterpolateAllDeepWaterWaveValues(void) { - // Interpolate deep water height and orientation from multiple - // user-supplied values - unsigned int const nUserPoints = - static_cast(m_VdDeepWaterWaveStationX.size()); - - // Performance optimization: Enable GDAL threading for interpolation - CPLSetThreadLocalConfigOption("GDAL_NUM_THREADS", "ALL_CPUS"); - - // Call GDALGridCreate() with the GGA_InverseDistanceToAPower - // interpolation algorithm. It has following parameters: radius1 is the - // first radius (X axis if rotation angle is 0) of the search ellipse, - // set this to zero (the default) to use the whole point array; radius2 - // is the second radius (Y axis if rotation angle is 0) of the search - // ellipse, again set this parameter to zero (the default) to use the - // whole point array; angle is the angle of the search ellipse rotation - // in degrees (counter clockwise, default 0.0); nodata is the NODATA - // marker to fill empty points (default 0.0) TODO 086 - GDALGridInverseDistanceToAPowerOptions *pOptions = - new GDALGridInverseDistanceToAPowerOptions(); - pOptions->dfAngle = 0; - pOptions->dfAnisotropyAngle = 0; - pOptions->dfAnisotropyRatio = 0; - pOptions->dfPower = 2; // Reduced from 3 to 2 for faster computation - pOptions->dfSmoothing = - 50; // Reduced from 100 to 50 for faster computation - pOptions->dfRadius1 = 0; - pOptions->dfRadius2 = 0; - pOptions->nMaxPoints = - 12; // Limit points for faster computation (was 0 = unlimited) - pOptions->nMinPoints = 3; // Minimum points needed for interpolation - pOptions->dfNoDataValue = m_nMissingValue; - - // CPLSetConfigOption("CPL_DEBUG", "ON"); - // CPLSetConfigOption("GDAL_NUM_THREADS", "1"); - - // OK, now create a gridded version of wave height: first create the - // GDAL context TODO 086 GDALGridContext* pContext = - // GDALGridContextCreate(GGA_InverseDistanceToAPower, pOptions, - // nUserPoints, &m_VdDeepWaterWaveStationX[0], - // &m_VdDeepWaterWaveStationY[0], - // &m_VdThisIterDeepWaterWaveStationHeight[0], true); - GDALGridContext *pContext = GDALGridContextCreate( - GGA_InverseDistanceToAPower, pOptions, nUserPoints, - m_VdDeepWaterWaveStationX.data(), m_VdDeepWaterWaveStationY.data(), - m_VdThisIterDeepWaterWaveStationHeight.data(), true); - - if (pContext == NULL) { - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Now process the context - double *dHeightOut = new double[m_ulNumCells]; - int nRet = GDALGridContextProcess( - pContext, 0, m_nXGridSize - 1, 0, m_nYGridSize - 1, m_nXGridSize, - m_nYGridSize, GDT_Float64, dHeightOut, NULL, NULL); - - if (nRet == CE_Failure) { - delete[] dHeightOut; - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Get rid of the context - GDALGridContextFree(pContext); - - // Next create a gridded version of wave orientation: first create the - // GDAL context pContext = - // GDALGridContextCreate(GGA_InverseDistanceToAPower, pOptions, - // nUserPoints, &(m_VdDeepWaterWaveStationX[0]), - // &(m_VdDeepWaterWaveStationY[0]), - // (&m_VdThisIterDeepWaterWaveStationAngle[0]), true); - pContext = GDALGridContextCreate( - GGA_InverseDistanceToAPower, pOptions, nUserPoints, - m_VdDeepWaterWaveStationX.data(), m_VdDeepWaterWaveStationY.data(), - m_VdThisIterDeepWaterWaveStationAngle.data(), true); - - if (pContext == NULL) { - delete[] dHeightOut; - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Now process the context TODO 086 - double *dAngleOut = new double[m_ulNumCells]; - nRet = GDALGridContextProcess( - pContext, 0, m_nXGridSize - 1, 0, m_nYGridSize - 1, m_nXGridSize, - m_nYGridSize, GDT_Float64, dAngleOut, NULL, NULL); - - if (nRet == CE_Failure) { - delete[] dHeightOut; - delete[] dAngleOut; - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Get rid of the context - GDALGridContextFree(pContext); - - // OK, now create a gridded version of wave period: first create the - // GDAL context pContext = - // GDALGridContextCreate(GGA_InverseDistanceToAPower, pOptions, - // nUserPoints, &m_VdDeepWaterWaveStationX[0], - // &m_VdDeepWaterWaveStationY[0], - // &m_VdThisIterDeepWaterWaveStationPeriod[0], true); - pContext = GDALGridContextCreate( - GGA_InverseDistanceToAPower, pOptions, nUserPoints, - m_VdDeepWaterWaveStationX.data(), m_VdDeepWaterWaveStationY.data(), - m_VdThisIterDeepWaterWaveStationPeriod.data(), true); - - if (pContext == NULL) { - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Now process the context TODO 086 - double *dPeriopdOut = new double[m_ulNumCells]; - nRet = GDALGridContextProcess( - pContext, 0, m_nXGridSize - 1, 0, m_nYGridSize - 1, m_nXGridSize, - m_nYGridSize, GDT_Float64, dPeriopdOut, NULL, NULL); - - if (nRet == CE_Failure) { - delete[] dPeriopdOut; - delete pOptions; - return RTN_ERR_GRIDCREATE; - } - - // Get rid of the context - GDALGridContextFree(pContext); - - // The output from GDALGridCreate() is in dHeightOut, dAngleOut and - // dPeriopdOut but must be reversed - vector VdHeight; - vector VdAngle; - vector VdPeriod; - - int n = 0; - int nValidHeight = 0; - int nValidAngle = 0; - int nValidPeriod = 0; - - double dAvgHeight = 0; - double dAvgAngle = 0; - double dAvgPeriod = 0; - - for (int nY = m_nYGridSize - 1; nY >= 0; nY--) { - for (int nX = 0; nX < m_nXGridSize; nX++) { - if (isfinite(dHeightOut[n])) { - VdHeight.push_back(dHeightOut[n]); - - dAvgHeight += dHeightOut[n]; - nValidHeight++; - } - - else { - VdHeight.push_back(m_dMissingValue); - } - - if (isfinite(dAngleOut[n])) { - VdAngle.push_back(dAngleOut[n]); - - dAvgAngle += dAngleOut[n]; - nValidAngle++; - } - - else { - VdAngle.push_back(m_dMissingValue); - } - - if (isfinite(dPeriopdOut[n])) { - VdPeriod.push_back(dPeriopdOut[n]); - - dAvgPeriod += dPeriopdOut[n]; - nValidPeriod++; - } - - else { - VdPeriod.push_back(m_dMissingValue); - } - - // LogStream << " nX = " << nX << " nY = " << nY << " n = " << n << - // " dHeightOut[n] = " << dHeightOut[n] << " dAngleOut[n] = " << - // dAngleOut[n] << endl; - n++; - } - } - - // Calculate averages - dAvgHeight /= nValidHeight; - dAvgAngle /= nValidAngle; - dAvgPeriod /= nValidPeriod; - - // Tidy - delete pOptions; - delete[] dHeightOut; - delete[] dAngleOut; - delete[] dPeriopdOut; - - // Now update all raster cells - n = 0; - - for (int nY = 0; nY < m_nYGridSize; nY++) { - for (int nX = 0; nX < m_nXGridSize; nX++) { - if (bFPIsEqual(VdHeight[n], m_dMissingValue, TOLERANCE)) - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveHeight( - dAvgHeight); - - else - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveHeight( - VdHeight[n]); - - if (bFPIsEqual(VdAngle[n], m_dMissingValue, TOLERANCE)) - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveAngle( - dAvgAngle); - - else - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveAngle( - VdAngle[n]); - - if (bFPIsEqual(VdPeriod[n], m_dMissingValue, TOLERANCE)) - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWavePeriod( - dAvgPeriod); - - else - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWavePeriod( - VdPeriod[n]); - - // LogStream << " [" << nX << "][" << nY << "] deep water wave - // height = " - // << m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight() << - // " deep water wave angle = " << - // m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle() << - // endl; - n++; - } - } - - // // DEBUG CODE - // =========================================================================================================== - // string strOutFile = m_strOutPath; - // strOutFile += "init_deep_water_wave_height_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // GDALDriver* pDriver = - // GetGDALDriverManager()->GetDriverByName("gtiff"); GDALDataset* - // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, - // m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // double* pdRaster = new double[m_ulNumCells]; - // int nn = 0; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // // Write this value to the array - // pdRaster[nn] = - // m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight(); nn++; - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_nMissingValue); - // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, - // pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // // DEBUG CODE - // =========================================================================================================== - - // // DEBUG CODE - // =========================================================================================================== - // strOutFile = m_strOutPath; - // strOutFile += "init_deep_water_wave_angle_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, - // m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // nn = 0; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // // Write this value to the array - // pdRaster[nn] = - // m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle(); nn++; - // } - // } - // - // pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_nMissingValue); - // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, - // pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE - // =========================================================================================================== - - return RTN_OK; - } +/*! + \file gis_raster.cpp + \brief These functions use GDAL (at least version 2) to read and write raster + GIS files in several formats + \details TODO 001 A more detailed description of these routines. + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License +*/ + +/* =============================================================================================================================== + This file is part of CoastalME, the Coastal Modelling Environment. + + CoastalME is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + + This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. +===============================================================================================================================*/ +#include + +#include + +#include +using std::atan2; +using std::hypot; +using std::isfinite; +using std::isnan; +using std::sqrt; + +#include +using std::vector; + +#include +using std::cerr; +using std::endl; +using std::ios; + +#include +using std::ifstream; + +#include +using std::stringstream; + +#include +using std::to_string; + +#include +#include +#include +#include +#include +#include + +#include "2di_point.h" +#include "cme.h" +#include "coast.h" +#include "simulation.h" +#include "spatial_interpolation.h" + +//=============================================================================================================================== +//! Initialize GDAL with performance optimizations +//=============================================================================================================================== +void CSimulation::InitializeGDALPerformance(void) { + // Configure GDAL for optimal performance + // Enable GDAL threading - use all available CPU cores +#ifdef _OPENMP + CPLSetConfigOption("GDAL_NUM_THREADS", "1"); +#else + CPLSetConfigOption("GDAL_NUM_THREADS", "4"); // Fallback for non-OpenMP builds +#endif + + // Optimize GDAL memory usage and caching + CPLSetConfigOption("GDAL_CACHEMAX", + "1.5GB"); // 2GB cache for large grids (was 1GB) + CPLSetConfigOption("GDAL_DISABLE_READDIR_ON_OPEN", + "EMPTY_DIR"); // Faster file access + CPLSetConfigOption("VSI_CACHE", "TRUE"); // Enable virtual file system cache + CPLSetConfigOption("VSI_CACHE_SIZE", "512MB"); // 256MB VSI cache + + // Block and chunk optimizations for raster operations + CPLSetConfigOption("GDAL_TIFF_INTERNAL_MASK_TO_8BIT", "YES"); + CPLSetConfigOption("GDAL_RASTERIO_RESAMPLING", + "CUBIC"); // Better for coastal DEM data + + // Grid creation optimizations (for GDALGridCreate performance) + CPLSetConfigOption("GDAL_GRID_MAX_POINTS_PER_QUADTREE_LEAF", "1024"); + // Increased from 512 + CPLSetConfigOption("GDAL_GRID_POINT_COUNT_THRESHOLD", + "100"); // New 2024 option + + // Thread-safe dataset access (GDAL 3.10+) + CPLSetConfigOption("GDAL_DATASET_CACHE_SIZE", "64"); // Cache more datasets + + // Compression optimizations for output + CPLSetConfigOption("GDAL_TIFF_OVR_BLOCKSIZE", + "512"); // Optimal for coastal data + + // Memory allocator optimization for multi-threading + CPLSetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "YES"); + + // Disable GDAL warnings for cleaner output (optional) + // CPLSetConfigOption("CPL_LOG", "/dev/null"); + // Debugging (remove in production) + // CPLSetConfigOption("CPL_DEBUG", "ON"); + // CPLSetConfigOption("GDAL_DEBUG", "ON"); + + m_bGDALOptimisations = true; +} + +//=============================================================================================================================== +//! Reads a raster DEM of basement elevation data to the Cell array +//=============================================================================================================================== +int CSimulation::nReadRasterBasementDEM(void) { + // Initialize GDAL performance settings (only needs to be done once) + static bool bGDALInitialized = false; + + if (!bGDALInitialized) { + InitializeGDALPerformance(); + bGDALInitialized = true; + } + + // Use GDAL to create a dataset object, which then opens the DEM file + GDALDataset *pGDALDataset = static_cast( + GDALOpen(m_strInitialBasementDEMFile.c_str(), GA_ReadOnly)); + + if (NULL == pGDALDataset) { + // Can't open file (note will already have sent GDAL error message to + // stdout) + cerr << ERR << "cannot open " << m_strInitialBasementDEMFile + << " for input: " << CPLGetLastErrorMsg() << endl; + return RTN_ERR_DEMFILE; + } + + // Opened OK, so get GDAL basement DEM dataset information + m_strGDALBasementDEMDriverCode = pGDALDataset->GetDriver()->GetDescription(); + m_strGDALBasementDEMDriverDesc = + pGDALDataset->GetDriver()->GetMetadataItem(GDAL_DMD_LONGNAME); + m_strGDALBasementDEMProjection = pGDALDataset->GetProjectionRef(); + + // If we have reference units, then check that they are in metres (note US + // spelling) + if (!m_strGDALBasementDEMProjection.empty()) { + string const strTmp = strToLower(&m_strGDALBasementDEMProjection); + + if ((strTmp.find("meter") == string::npos) && + (strTmp.find("metre") == string::npos)) { + // error: x-y values must be in metres + cerr << ERR << "GIS file x-y values (" << m_strGDALBasementDEMProjection + << ") in " << m_strInitialBasementDEMFile << " must be in metres" + << endl; + return RTN_ERR_DEMFILE; + } + } + + // Now get dataset size, and do some rudimentary checks + m_nXGridSize = pGDALDataset->GetRasterXSize(); + + if (m_nXGridSize == 0) { + // Error: silly number of columns specified + cerr << ERR << "invalid number of columns (" << m_nXGridSize << ") in " + << m_strInitialBasementDEMFile << endl; + return RTN_ERR_DEMFILE; + } + + m_nYGridSize = pGDALDataset->GetRasterYSize(); + + if (m_nYGridSize == 0) { + // Error: silly number of rows specified + cerr << ERR << "invalid number of rows (" << m_nYGridSize << ") in " + << m_strInitialBasementDEMFile << endl; + return RTN_ERR_DEMFILE; + } + + // Get geotransformation info (see http://www.gdal.org/classGDALDataset.html) + if (CE_Failure == pGDALDataset->GetGeoTransform(m_dGeoTransform)) { + // Can't get geotransformation (note will already have sent GDAL error + // message to stdout) + cerr << ERR << CPLGetLastErrorMsg() << " in " << m_strInitialBasementDEMFile + << endl; + return RTN_ERR_DEMFILE; + } + + // CoastalME can only handle rasters that are oriented N-S and W-E. (If you + // need to work with a raster that is oriented differently, then you must + // rotate it before running CoastalME). So here we check whether row rotation + // (m_dGeoTransform[2]) and column rotation (m_dGeoTransform[4]) are both + // zero. See https://gdal.org/tutorials/geotransforms_tut.html + if ((!bFPIsEqual(m_dGeoTransform[2], 0.0, TOLERANCE)) || + (!bFPIsEqual(m_dGeoTransform[4], 0.0, TOLERANCE))) { + // Error: not oriented NS and W-E + cerr << ERR << m_strInitialBasementDEMFile + << " is not oriented N-S and W-E. Row rotation = " + << m_dGeoTransform[2] + << " and column rotation = " << m_dGeoTransform[4] << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // Get the X and Y cell sizes, in external CRS units. Note that while the cell + // is supposed to be square, it may not be exactly so due to oddities with + // some GIS calculations + double const dCellSideX = tAbs(m_dGeoTransform[1]); + double const dCellSideY = tAbs(m_dGeoTransform[5]); + + // Check that the cell is more or less square + if (!bFPIsEqual(dCellSideX, dCellSideY, 1e-2)) { + // Error: cell is not square enough + cerr << ERR << "cell is not square in " << m_strInitialBasementDEMFile + << ", is " << dCellSideX << " x " << dCellSideY << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // Calculate the average length of cell side, the cell's diagonal, and the + // area of a cell (in external CRS units) + m_dCellSide = (dCellSideX + dCellSideY) / 2.0; + m_dCellArea = m_dCellSide * m_dCellSide; + m_dCellDiagonal = hypot(m_dCellSide, m_dCellSide); + + // And calculate the inverse values + m_dInvCellSide = 1 / m_dCellSide; + m_dInvCellDiagonal = 1 / m_dCellDiagonal; + + // Save some values in external CRS + m_dNorthWestXExtCRS = m_dGeoTransform[0] - (m_dGeoTransform[1] / 2); + m_dNorthWestYExtCRS = m_dGeoTransform[3] - (m_dGeoTransform[5] / 2); + m_dSouthEastXExtCRS = m_dGeoTransform[0] + + (m_nXGridSize * m_dGeoTransform[1]) + + (m_dGeoTransform[1] / 2); + m_dSouthEastYExtCRS = m_dGeoTransform[3] + + (m_nYGridSize * m_dGeoTransform[5]) + + (m_dGeoTransform[5] / 2); + + // And calc the grid area in external CRS units + m_dExtCRSGridArea = tAbs(m_dNorthWestXExtCRS - m_dSouthEastXExtCRS) * + tAbs(m_dNorthWestYExtCRS * m_dSouthEastYExtCRS); + + // Now get GDAL raster band information + GDALRasterBand *pGDALBand = pGDALDataset->GetRasterBand(1); + int nBlockXSize = 0, nBlockYSize = 0; + pGDALBand->GetBlockSize(&nBlockXSize, &nBlockYSize); + m_strGDALBasementDEMDataType = + GDALGetDataTypeName(pGDALBand->GetRasterDataType()); + + // If we have value units, then check them + string const strUnits = pGDALBand->GetUnitType(); + + if ((!strUnits.empty()) && (strUnits.find('m') == string::npos)) { + // Error: value units must be m + cerr << ERR << "DEM vertical units are (" << strUnits << " ) in " + << m_strInitialBasementDEMFile << ", should be 'm'" << endl; + return RTN_ERR_DEMFILE; + } + + // If present, get the missing value (NODATA) setting + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + double const dMissingValue = + pGDALBand->GetNoDataValue(); // Will fail for some formats + CPLPopErrorHandler(); + + if (!bFPIsEqual(dMissingValue, m_dMissingValue, TOLERANCE)) { + cerr << " " << NOTE << "NODATA value in " << m_strInitialBasementDEMFile + << " is " << dMissingValue + << "\n instead using CoastalME's default floating-point " + "NODATA value " + << m_dMissingValue << endl; + } + + // Next allocate memory for a 2D array of raster cell objects: tell the user + // what is happening + AnnounceAllocateMemory(); + int const nRet = m_pRasterGrid->nCreateGrid(); + + if (nRet != RTN_OK) + return nRet; + + // Allocate memory for a 1D floating-point array, to hold the scan line for + // GDAL + double *pdScanline = new double[m_nXGridSize]; + + if (NULL == pdScanline) { + // Error, can't allocate memory + cerr << ERR << "cannot allocate memory for " << m_nXGridSize + << " x 1D array" << endl; + return (RTN_ERR_MEMALLOC); + } + + // Now read in the data + for (int j = 0; j < m_nYGridSize; j++) { + // Read scanline + if (CE_Failure == pGDALBand->RasterIO(GF_Read, 0, j, m_nXGridSize, 1, + pdScanline, m_nXGridSize, 1, + GDT_Float64, 0, 0, NULL)) { + // Error while reading scanline + cerr << ERR << CPLGetLastErrorMsg() << " in " + << m_strInitialBasementDEMFile << endl; + return RTN_ERR_DEMFILE; + } + + // All OK, so read scanline into cell elevations (including any missing + // values) + for (int i = 0; i < m_nXGridSize; i++) { + double dTmp = pdScanline[i]; + + if ((isnan(dTmp)) || (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) + dTmp = m_dMissingValue; + + m_pRasterGrid->Cell(i, j).SetBasementElev(dTmp); + } + } + + // Finished, so get rid of dataset object + GDALClose(pGDALDataset); + + // Get rid of memory allocated to this array + delete[] pdScanline; + + return RTN_OK; +} + +//=============================================================================================================================== +//! Mark cells which are at the edge of a bounding box which represents the +//! valid part of the grid, as defined by the basement layer. The valid part of +//! the grid may be the whole grid, or only part of the whole grid. The bounding +//! box may be an irregular shape (but may not have re-entrant edges): simple +//! shapes are more likely to work correctly +//=============================================================================================================================== +int CSimulation::nMarkBoundingBoxEdgeCells(void) { + // The bounding box must touch the edge of the grid at least once on each side + // of the grid, so store these points. Search in a clockwise direction around + // the edge of the grid + vector VPtiBoundingBoxCorner; + + // Start with the top (north) edge + bool bFound = false; + + for (int nX = 0; nX < m_nXGridSize; nX++) { + if (bFound) + break; + + for (int nY = 0; nY < m_nYGridSize; nY++) { + if (!m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + CGeom2DIPoint const PtiTmp(nX, nY); + VPtiBoundingBoxCorner.push_back(PtiTmp); + bFound = true; + break; + } + } + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": north (top) edge of bounding box not found" + << endl; + + return RTN_ERR_BOUNDING_BOX; + } + + // Do the same for the right (east) edge + bFound = false; + + for (int nY = 0; nY < m_nYGridSize; nY++) { + if (bFound) + break; + + for (int nX = m_nXGridSize - 1; nX >= 0; nX--) { + if (!m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + CGeom2DIPoint const PtiTmp(nX, nY); + VPtiBoundingBoxCorner.push_back(PtiTmp); + bFound = true; + break; + } + } + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": east (right) edge of bounding box not found" + << endl; + + return RTN_ERR_BOUNDING_BOX; + } + + // Do the same for the south (bottom) edge + bFound = false; + + for (int nX = m_nXGridSize - 1; nX >= 0; nX--) { + if (bFound) + break; + + for (int nY = m_nYGridSize - 1; nY >= 0; nY--) { + if (!m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + CGeom2DIPoint const PtiTmp(nX, nY); + VPtiBoundingBoxCorner.push_back(PtiTmp); + bFound = true; + break; + } + } + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": south (bottom) edge of bounding box not found" + << endl; + + return RTN_ERR_BOUNDING_BOX; + } + + // And finally repeat for the west (left) edge + bFound = false; + + for (int nY = m_nYGridSize - 1; nY >= 0; nY--) { + if (bFound) + break; + + for (int nX = 0; nX < m_nXGridSize; nX++) { + if (!m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + CGeom2DIPoint const PtiTmp(nX, nY); + VPtiBoundingBoxCorner.push_back(PtiTmp); + bFound = true; + break; + } + } + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_ALL) + LogStream << m_ulIter << ": west (left) edge of bounding box not found" + << endl; + + return RTN_ERR_BOUNDING_BOX; + } + + // OK, so we have a point on each side of the grid, so start at this point and + // find the edges of the bounding box. Go round in a clockwise direction: top + // (north) edge first + for (int nX = VPtiBoundingBoxCorner[0].nGetX(); + nX <= VPtiBoundingBoxCorner[1].nGetX(); nX++) { + bFound = false; + + for (int nY = VPtiBoundingBoxCorner[0].nGetY(); nY < m_nYGridSize; nY++) { + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + m_ulMissingValueBasementCells++; + continue; + } + + // Found a bounding box edge cell + m_pRasterGrid->Cell(nX, nY).SetBoundingBoxEdge(NORTH); + + m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); + m_VEdgeCellEdge.push_back(NORTH); + + bFound = true; + break; + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream + << m_ulIter + << ": could not find a bounding box edge cell for grid column " + << nX << endl; + + return RTN_ERR_BOUNDING_BOX; + } + } + + // Right (east) edge + for (int nY = VPtiBoundingBoxCorner[1].nGetY(); + nY <= VPtiBoundingBoxCorner[2].nGetY(); nY++) { + bFound = false; + + for (int nX = VPtiBoundingBoxCorner[1].nGetX(); nX >= 0; nX--) { + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + m_ulMissingValueBasementCells++; + continue; + } + + // Found a bounding box edge cell + m_pRasterGrid->Cell(nX, nY).SetBoundingBoxEdge(EAST); + + m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); + m_VEdgeCellEdge.push_back(EAST); + + bFound = true; + break; + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter + << ": could not find a bounding box edge cell for grid row " + << nY << endl; + + return RTN_ERR_BOUNDING_BOX; + } + } + + // Bottom (south) edge + for (int nX = VPtiBoundingBoxCorner[2].nGetX(); + nX >= VPtiBoundingBoxCorner[3].nGetX(); nX--) { + bFound = false; + + for (int nY = VPtiBoundingBoxCorner[2].nGetY(); nY >= 0; nY--) { + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + m_ulMissingValueBasementCells++; + continue; + } + + // Found a bounding box edge cell + m_pRasterGrid->Cell(nX, nY).SetBoundingBoxEdge(SOUTH); + + m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); + m_VEdgeCellEdge.push_back(SOUTH); + + bFound = true; + break; + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream + << m_ulIter + << ": could not find a bounding box edge cell for grid column " + << nX << endl; + + return RTN_ERR_BOUNDING_BOX; + } + } + + // Left (west) edge + for (int nY = VPtiBoundingBoxCorner[3].nGetY(); + nY >= VPtiBoundingBoxCorner[0].nGetY(); nY--) { + for (int nX = VPtiBoundingBoxCorner[3].nGetX(); nX < m_nXGridSize - 1; nX++) + // for (int nX = VPtiBoundingBoxCorner[3].nGetX(); nX < + // VPtiBoundingBoxCorner[3].nGetX(); nX++) + { + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) { + m_ulMissingValueBasementCells++; + continue; + } + + // Found a bounding box edge cell + m_pRasterGrid->Cell(nX, nY).SetBoundingBoxEdge(WEST); + + m_VEdgeCell.push_back(CGeom2DIPoint(nX, nY)); + m_VEdgeCellEdge.push_back(WEST); + + bFound = true; + break; + } + + if (!bFound) { + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << m_ulIter + << ": could not find a bounding box edge cell for grid row " + << nY << endl; + + return RTN_ERR_BOUNDING_BOX; + } + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! Reads raster GIS datafiles into the RasterGrid array +//=============================================================================================================================== +int CSimulation::nReadRasterGISFile(int const nDataItem, int const nLayer) { + string strGISFile; + string strDriverCode; + string strDriverDesc; + string strProjection; + string strDataType; + + switch (nDataItem) { + case (LANDFORM_RASTER): + // Initial Landform Class GIS data + strGISFile = m_strInitialLandformFile; + break; + + case (INTERVENTION_CLASS_RASTER): + // Intervention class + strGISFile = m_strInterventionClassFile; + break; + + case (INTERVENTION_HEIGHT_RASTER): + // Intervention height + strGISFile = m_strInterventionHeightFile; + break; + + case (SUSP_SED_RASTER): + // Initial Suspended Sediment GIS data + strGISFile = m_strInitialSuspSedimentFile; + break; + + case (FINE_UNCONS_RASTER): + // Initial Unconsolidated Fine Sediment GIS data + strGISFile = m_VstrInitialFineUnconsSedimentFile[nLayer]; + break; + + case (SAND_UNCONS_RASTER): + // Initial Unconsolidated Sand Sediment GIS data + strGISFile = m_VstrInitialSandUnconsSedimentFile[nLayer]; + break; + + case (COARSE_UNCONS_RASTER): + // Initial Unconsolidated Coarse Sediment GIS data + strGISFile = m_VstrInitialCoarseUnconsSedimentFile[nLayer]; + break; + + case (FINE_CONS_RASTER): + // Initial Consolidated Fine Sediment GIS data + strGISFile = m_VstrInitialFineConsSedimentFile[nLayer]; + break; + + case (SAND_CONS_RASTER): + // Initial Consolidated Sand Sediment GIS data + strGISFile = m_VstrInitialSandConsSedimentFile[nLayer]; + break; + + case (COARSE_CONS_RASTER): + // Initial Consolidated Coarse Sediment GIS data + strGISFile = m_VstrInitialCoarseConsSedimentFile[nLayer]; + break; + } + + // Use GDAL to create a dataset object, which then opens the GIS file + GDALDataset *pGDALDataset = + static_cast(GDALOpen(strGISFile.c_str(), GA_ReadOnly)); + + if (NULL == pGDALDataset) { + // Can't open file (note will already have sent GDAL error message to + // stdout) + cerr << ERR << "cannot open " << strGISFile + << " for input: " << CPLGetLastErrorMsg() << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // Opened OK, so get dataset information + strDriverCode = pGDALDataset->GetDriver()->GetDescription(); + strDriverDesc = pGDALDataset->GetDriver()->GetMetadataItem(GDAL_DMD_LONGNAME); + strProjection = pGDALDataset->GetProjectionRef(); + + // Get geotransformation info + double dGeoTransform[6]; + if (CE_Failure == pGDALDataset->GetGeoTransform(dGeoTransform)) { + // Can't get geotransformation (note will already have sent GDAL error + // message to stdout) + cerr << ERR << CPLGetLastErrorMsg() << " in " << strGISFile << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // Now get dataset size, and do some checks + int const nTmpXSize = pGDALDataset->GetRasterXSize(); + if (nTmpXSize != m_nXGridSize) { + // Error: incorrect number of columns specified + cerr << ERR << "different number of columns in " << strGISFile << " (" + << nTmpXSize << ") and " << m_strInitialBasementDEMFile << "(" + << m_nXGridSize << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + int const nTmpYSize = pGDALDataset->GetRasterYSize(); + if (nTmpYSize != m_nYGridSize) { + // Error: incorrect number of rows specified + cerr << ERR << "different number of rows in " << strGISFile << " (" + << nTmpYSize << ") and " << m_strInitialBasementDEMFile << " (" + << m_nYGridSize << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + double dTmp = m_dGeoTransform[0] - (m_dGeoTransform[1] / 2); + if (!bFPIsEqual(dTmp, m_dNorthWestXExtCRS, TOLERANCE)) { + // Error: different min x from DEM file + cerr << ERR << "different min x values in " << strGISFile << " (" << dTmp + << ") and " << m_strInitialBasementDEMFile << " (" + << m_dNorthWestXExtCRS << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + dTmp = m_dGeoTransform[3] - (m_dGeoTransform[5] / 2); + if (!bFPIsEqual(dTmp, m_dNorthWestYExtCRS, TOLERANCE)) { + // Error: different min x from DEM file + cerr << ERR << "different min y values in " << strGISFile << " (" << dTmp + << ") and " << m_strInitialBasementDEMFile << " (" + << m_dNorthWestYExtCRS << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + double const dTmpResX = tAbs(dGeoTransform[1]); + if (!bFPIsEqual(dTmpResX, m_dCellSide, 1e-2)) { + // Error: different cell size in X direction: note that due to rounding + // errors in some GIS packages, must expect some discrepancies + cerr << ERR << "cell size in X direction (" << dTmpResX << ") in " + << strGISFile << " differs from cell size in of basement DEM (" + << m_dCellSide << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + double const dTmpResY = tAbs(dGeoTransform[5]); + if (!bFPIsEqual(dTmpResY, m_dCellSide, 1e-2)) { + // Error: different cell size in Y direction: note that due to rounding + // errors in some GIS packages, must expect some discrepancies + cerr << ERR << "cell size in Y direction (" << dTmpResY << ") in " + << strGISFile << " differs from cell size of basement DEM (" + << m_dCellSide << ")" << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // Now get GDAL raster band information + GDALRasterBand *pGDALBand = pGDALDataset->GetRasterBand( + 1); // TODO 028 Give a message if there are several bands + int nBlockXSize = 0, nBlockYSize = 0; + pGDALBand->GetBlockSize(&nBlockXSize, &nBlockYSize); + strDataType = GDALGetDataTypeName(pGDALBand->GetRasterDataType()); + + switch (nDataItem) { + case (LANDFORM_RASTER): + // Initial Landform Class GIS data + m_strGDALLDriverCode = strDriverCode; + m_strGDALLDriverDesc = strDriverDesc; + m_strGDALLProjection = strProjection; + m_strGDALLDataType = strDataType; + break; + + case (INTERVENTION_CLASS_RASTER): + // Intervention class + m_strGDALICDriverCode = strDriverCode; + m_strGDALICDriverDesc = strDriverDesc; + m_strGDALICProjection = strProjection; + m_strGDALICDataType = strDataType; + break; + + case (INTERVENTION_HEIGHT_RASTER): + // Intervention height + m_strGDALIHDriverCode = strDriverCode; + m_strGDALIHDriverDesc = strDriverDesc; + m_strGDALIHProjection = strProjection; + m_strGDALIHDataType = strDataType; + break; + + case (SUSP_SED_RASTER): + // Initial Suspended Sediment GIS data + m_strGDALISSDriverCode = strDriverCode; + m_strGDALISSDriverDesc = strDriverDesc; + m_strGDALISSProjection = strProjection; + m_strGDALISSDataType = strDataType; + break; + + case (FINE_UNCONS_RASTER): + // Initial Unconsolidated Fine Sediment GIS data + m_VstrGDALIUFDriverCode[nLayer] = strDriverCode; + m_VstrGDALIUFDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALIUFProjection[nLayer] = strProjection; + m_VstrGDALIUFDataType[nLayer] = strDataType; + break; + + case (SAND_UNCONS_RASTER): + // Initial Unconsolidated Sand Sediment GIS data + m_VstrGDALIUSDriverCode[nLayer] = strDriverCode; + m_VstrGDALIUSDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALIUSProjection[nLayer] = strProjection; + m_VstrGDALIUSDataType[nLayer] = strDataType; + break; + + case (COARSE_UNCONS_RASTER): + // Initial Unconsolidated Coarse Sediment GIS data + m_VstrGDALIUCDriverCode[nLayer] = strDriverCode; + m_VstrGDALIUCDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALIUCProjection[nLayer] = strProjection; + m_VstrGDALIUCDataType[nLayer] = strDataType; + break; + + case (FINE_CONS_RASTER): + // Initial Consolidated Fine Sediment GIS data + m_VstrGDALICFDriverCode[nLayer] = strDriverCode; + m_VstrGDALICFDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALICFProjection[nLayer] = strProjection; + m_VstrGDALICFDataType[nLayer] = strDataType; + break; + + case (SAND_CONS_RASTER): + // Initial Consolidated Sand Sediment GIS data + m_VstrGDALICSDriverCode[nLayer] = strDriverCode; + m_VstrGDALICSDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALICSProjection[nLayer] = strProjection; + m_VstrGDALICSDataType[nLayer] = strDataType; + break; + + case (COARSE_CONS_RASTER): + // Initial Consolidated Coarse Sediment GIS data + m_VstrGDALICCDriverCode[nLayer] = strDriverCode; + m_VstrGDALICCDriverDesc[nLayer] = strDriverDesc; + m_VstrGDALICCProjection[nLayer] = strProjection; + m_VstrGDALICCDataType[nLayer] = strDataType; + break; + } + + // If present, get the missing value setting + string const strTmp = strToLower(&strDataType); + if (strTmp.find("int") != string::npos) { + // This is an integer layer + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to + // fail silently, if it fails + m_nGISMissingValue = static_cast( + pGDALBand->GetNoDataValue()); // Note will fail for some formats + CPLPopErrorHandler(); + + if (m_nGISMissingValue != m_nMissingValue) { + cerr + << " " << NOTE << "NODATA value in " << strGISFile << " is " + << m_nGISMissingValue + << "\n instead using CoatalME's default integer NODATA value " + << m_nMissingValue << endl; + } + } else { + // This is a floating point layer + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to + // fail silently, if it fails + m_dGISMissingValue = + pGDALBand->GetNoDataValue(); // Note will fail for some formats + CPLPopErrorHandler(); + + if (!bFPIsEqual(m_dGISMissingValue, m_dMissingValue, TOLERANCE)) { + cerr << " " << NOTE << "NODATA value in " << strGISFile << " is " + << m_dGISMissingValue + << "\n instead using CoastalME's default floating-point " + "NODATA value " + << m_dMissingValue << endl; + } + } + + // Allocate memory for a 1D array, to hold the scan line for GDAL + double *pdScanline = new double[m_nXGridSize]; + if (NULL == pdScanline) { + // Error, can't allocate memory + cerr << ERR << "cannot allocate memory for " << m_nXGridSize + << " x 1D array" << endl; + return (RTN_ERR_MEMALLOC); + } + + // Now read in the data + int nMissing = 0; + + for (int nY = 0; nY < m_nYGridSize; nY++) { + // Read scanline + if (CE_Failure == pGDALBand->RasterIO(GF_Read, 0, nY, m_nXGridSize, 1, + pdScanline, m_nXGridSize, 1, + GDT_Float64, 0, 0, NULL)) { + // Error while reading scanline + cerr << ERR << CPLGetLastErrorMsg() << " in " << strGISFile << endl; + return (RTN_ERR_RASTER_FILE_READ); + } + + // All OK, so read scanline into cells (including any missing values) + for (int nX = 0; nX < m_nXGridSize; nX++) { + int nTmp; + + switch (nDataItem) { + case (LANDFORM_RASTER): + // Initial Landform Class GIS data, is integer TODO 030 Do we also need + // a landform sub-category input? + nTmp = static_cast(pdScanline[nX]); + + if ((isnan(nTmp)) || (nTmp == m_nGISMissingValue)) { + nTmp = m_nMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY).pGetLandform()->SetLFCategory(nTmp); + break; + + case (INTERVENTION_CLASS_RASTER): + // Intervention class, is integer + nTmp = static_cast(pdScanline[nX]); + + if ((isnan(nTmp)) || (nTmp == m_nGISMissingValue)) { + nTmp = m_nMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY).SetInterventionClass(nTmp); + break; + + case (INTERVENTION_HEIGHT_RASTER): + // Intervention height + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY).SetInterventionHeight(dTmp); + break; + + case (SUSP_SED_RASTER): + // Initial Suspended Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY).SetSuspendedSediment(dTmp); + break; + + case (FINE_UNCONS_RASTER): + // Initial Unconsolidated Fine Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->SetFineDepth(dTmp); + break; + + case (SAND_UNCONS_RASTER): + // Initial Unconsolidated Sand Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->SetSandDepth(dTmp); + break; + + case (COARSE_UNCONS_RASTER): + // Initial Unconsolidated Coarse Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->SetCoarseDepth(dTmp); + break; + + case (FINE_CONS_RASTER): + // Initial Consolidated Fine Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->SetFineDepth(dTmp); + break; + + case (SAND_CONS_RASTER): + // Initial Consolidated Sand Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->SetSandDepth(dTmp); + break; + + case (COARSE_CONS_RASTER): + // Initial Consolidated Coarse Sediment GIS data + dTmp = pdScanline[nX]; + + if ((isnan(dTmp)) || + (bFPIsEqual(dTmp, m_dGISMissingValue, TOLERANCE))) { + dTmp = m_dMissingValue; + nMissing++; + } + + m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->SetCoarseDepth(dTmp); + break; + } + } + } + + // Finished, so get rid of dataset object + GDALClose(pGDALDataset); + + // Get rid of memory allocated to this array + delete[] pdScanline; + + if (nMissing > 0) { + cerr << WARN << nMissing << " missing values in " << strGISFile << endl; + LogStream << WARN << nMissing << " missing values in " << strGISFile + << endl; + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! Writes GIS raster files using GDAL, using data from the RasterGrid array +//=============================================================================================================================== +bool CSimulation::bWriteRasterGISFile(int const nDataItem, + string const *strPlotTitle, + int const nLayer, double const dElev) { + bool bIsInteger = false; + bool bIsUnsignedLong = false; + + // Begin constructing the file name for this save + string strFilePathName(m_strOutPath); + string strLayer = "_layer_"; + + stringstream ststrTmp; + + strLayer.append(to_string(nLayer + 1)); + + switch (nDataItem) { + case (RASTER_PLOT_BASEMENT_ELEVATION): + strFilePathName.append(RASTER_BASEMENT_ELEVATION_NAME); + break; + + case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): + strFilePathName.append(RASTER_SEDIMENT_TOP_ELEVATION_NAME); + break; + + case (RASTER_PLOT_OVERALL_TOP_ELEVATION): + strFilePathName.append(RASTER_OVERALL_TOP_ELEVATION_NAME); + break; + + case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_NAME); + break; + + case (RASTER_PLOT_SLOPE_FOR_CLIFF_TOE): + strFilePathName.append(RASTER_SLOPE_FOR_CLIFF_TOE_NAME); + break; + + case (RASTER_PLOT_CLIFF_TOE): + strFilePathName.append(RASTER_CLIFF_TOE_NAME); + break; + + case (RASTER_PLOT_SEA_DEPTH): + strFilePathName.append(RASTER_SEA_DEPTH_NAME); + break; + + case (RASTER_PLOT_AVG_SEA_DEPTH): + strFilePathName.append(RASTER_AVG_SEA_DEPTH_NAME); + break; + + case (RASTER_PLOT_WAVE_HEIGHT): + strFilePathName.append(RASTER_WAVE_HEIGHT_NAME); + break; + + case (RASTER_PLOT_AVG_WAVE_HEIGHT): + strFilePathName.append(RASTER_AVG_WAVE_HEIGHT_NAME); + break; + + case (RASTER_PLOT_WAVE_ORIENTATION): + strFilePathName.append(RASTER_WAVE_ORIENTATION_NAME); + break; + + case (RASTER_PLOT_AVG_WAVE_ORIENTATION): + strFilePathName.append(RASTER_AVG_WAVE_ORIENTATION_NAME); + break; + + case (RASTER_PLOT_BEACH_PROTECTION): + strFilePathName.append(RASTER_BEACH_PROTECTION_NAME); + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): + strFilePathName.append(RASTER_POTENTIAL_PLATFORM_EROSION_NAME); + break; + + case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): + strFilePathName.append(RASTER_ACTUAL_PLATFORM_EROSION_NAME); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): + strFilePathName.append(RASTER_TOTAL_POTENTIAL_PLATFORM_EROSION_NAME); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): + strFilePathName.append(RASTER_TOTAL_ACTUAL_PLATFORM_EROSION_NAME); + break; + + case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): + strFilePathName.append(RASTER_POTENTIAL_BEACH_EROSION_NAME); + break; + + case (RASTER_PLOT_ACTUAL_BEACH_EROSION): + strFilePathName.append(RASTER_ACTUAL_BEACH_EROSION_NAME); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): + strFilePathName.append(RASTER_TOTAL_POTENTIAL_BEACH_EROSION_NAME); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): + strFilePathName.append(RASTER_TOTAL_ACTUAL_BEACH_EROSION_NAME); + break; + + case (RASTER_PLOT_BEACH_DEPOSITION): + strFilePathName.append(RASTER_BEACH_DEPOSITION_NAME); + break; + + case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): + strFilePathName.append(RASTER_TOTAL_BEACH_DEPOSITION_NAME); + break; + + case (RASTER_PLOT_SUSPENDED_SEDIMENT): + strFilePathName.append(RASTER_SUSP_SED_NAME); + break; + + case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): + strFilePathName.append(RASTER_AVG_SUSP_SED_NAME); + break; + + case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_FINE_UNCONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_SAND_UNCONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_COARSE_UNCONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_FINE_CONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_SAND_CONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): + strFilePathName.append(RASTER_COARSE_CONS_NAME); + strFilePathName.append(strLayer); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_FINE_NAME); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_SAND_NAME); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_EROSION_COARSE_NAME); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): + strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_FINE_NAME); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): + strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_SAND_NAME); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): + strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE_NAME); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_DEPOSITION_SAND_NAME); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_DEPOSITION_COARSE_NAME); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): + strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND_NAME); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): + strFilePathName.append(RASTER_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE_NAME); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): + strFilePathName.append(RASTER_CLIFF_COLLAPSE_TIMESTEP_NAME); + break; + + case (RASTER_PLOT_CLIFF_NOTCH_ALL): + strFilePathName.append(RASTER_CLIFF_NOTCH_ALL_NAME); + break; + + case (RASTER_PLOT_INTERVENTION_HEIGHT): + strFilePathName.append(RASTER_INTERVENTION_HEIGHT_NAME); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): + strFilePathName.append(RASTER_DEEP_WATER_WAVE_ORIENTATION_NAME); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): + strFilePathName.append(RASTER_DEEP_WATER_WAVE_HEIGHT_NAME); + break; + + case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): + strFilePathName.append(RASTER_POLYGON_GAIN_OR_LOSS_NAME); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): + strFilePathName.append(RASTER_WAVE_PERIOD_NAME); + break; + + case (RASTER_PLOT_SEDIMENT_INPUT): + strFilePathName.append(RASTER_SEDIMENT_INPUT_EVENT_NAME); + break; + + case (RASTER_PLOT_BEACH_MASK): + bIsInteger = true; + strFilePathName.append(RASTER_BEACH_MASK_NAME); + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): + bIsInteger = true; + strFilePathName.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_NAME); + break; + + case (RASTER_PLOT_INUNDATION_MASK): + bIsInteger = true; + strFilePathName.append(RASTER_INUNDATION_MASK_NAME); + break; + + case (RASTER_PLOT_SLICE): + bIsInteger = true; + ststrTmp.str(""); + ststrTmp.clear(); + + // TODO 031 Get working for multiple slices + strFilePathName.append(RASTER_SLICE_NAME); + ststrTmp << "_" << dElev << "_"; + strFilePathName.append(ststrTmp.str()); + break; + + case (RASTER_PLOT_LANDFORM): + bIsInteger = true; + strFilePathName.append(RASTER_LANDFORM_NAME); + break; + + case (RASTER_PLOT_INTERVENTION_CLASS): + bIsInteger = true; + strFilePathName.append(RASTER_INTERVENTION_CLASS_NAME); + break; + + case (RASTER_PLOT_COAST): + bIsInteger = true; + strFilePathName.append(RASTER_COAST_NAME); + break; + + case (RASTER_PLOT_NORMAL_PROFILE): + bIsInteger = true; + strFilePathName.append(RASTER_COAST_NORMAL_NAME); + break; + + case (RASTER_PLOT_ACTIVE_ZONE): + bIsInteger = true; + strFilePathName.append(RASTER_ACTIVE_ZONE_NAME); + break; + + case (RASTER_PLOT_POLYGON): + bIsInteger = true; + strFilePathName.append(RASTER_POLYGON_NAME); + break; + + case (RASTER_PLOT_SHADOW_ZONE): + bIsInteger = true; + strFilePathName.append(RASTER_SHADOW_ZONE_NAME); + break; + + case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): + bIsInteger = true; + strFilePathName.append(RASTER_SHADOW_DOWNDRIFT_ZONE_NAME); + break; + + case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): + bIsInteger = true; + strFilePathName.append(RASTER_POLYGON_UPDRIFT_OR_DOWNDRIFT_NAME); + break; + + case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): + bIsInteger = true; + strFilePathName.append(RASTER_SETUP_SURGE_FLOOD_MASK_NAME); + break; + + case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): + bIsInteger = true; + strFilePathName.append(RASTER_SETUP_SURGE_RUNUP_FLOOD_MASK_NAME); + break; + + case (RASTER_PLOT_WAVE_FLOOD_LINE): + bIsInteger = true; + strFilePathName.append(RASTER_WAVE_FLOOD_LINE_NAME); + break; + } + + // Append the 'save number' to the filename, and prepend zeros to the save + // number + ststrTmp.str(""); + ststrTmp.clear(); + + strFilePathName.append("_"); + + if (m_bGISSaveDigitsSequential) { + // Save number is m_bGISSaveDigitsSequential + ststrTmp << FillToWidth('0', m_nGISMaxSaveDigits) << m_nGISSave; + } else { + // Save number is iteration + ststrTmp << FillToWidth('0', m_nGISMaxSaveDigits) << m_ulIter; + } + + strFilePathName.append(ststrTmp.str()); + + // Finally, maybe append the extension + if (!m_strGDALRasterOutputDriverExtension.empty()) { + strFilePathName.append("."); + strFilePathName.append(m_strGDALRasterOutputDriverExtension); + } + + // TODO 065 Used to try to debug floating point exception in pDriver->Create() + // below CPLSetConfigOption("CPL_DEBUG", "ON"); + // CPLSetConfigOption("GDAL_NUM_THREADS", "1"); + + GDALDriver *pDriver; + GDALDataset *pDataSet; + + if (m_bGDALCanCreate) { + // The user-requested raster driver supports the Create() method + pDriver = GetGDALDriverManager()->GetDriverByName( + m_strRasterGISOutFormat.c_str()); + + if (bIsInteger) { + pDataSet = + pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, + 1, GDT_Int16, m_papszGDALRasterOptions); + } else if (bIsUnsignedLong) { + pDataSet = + pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, + 1, GDT_UInt32, m_papszGDALRasterOptions); + + } else if (m_strRasterGISOutFormat == "gpkg") { + // TODO 065 Floating point exception here + pDataSet = + pDriver->Create(strFilePathName.c_str(), m_nXGridSize, m_nYGridSize, + 1, GDT_Byte, m_papszGDALRasterOptions); + } else { + pDataSet = pDriver->Create(strFilePathName.c_str(), m_nXGridSize, + m_nYGridSize, 1, m_GDALWriteFloatDataType, + m_papszGDALRasterOptions); + } + + if (NULL == pDataSet) { + // Error, couldn't create file + cerr << ERR << "cannot create " << m_strRasterGISOutFormat + << " file named " << strFilePathName << endl + << CPLGetLastErrorMsg() << endl; + return false; + } + } else { + // The user-requested raster driver does not support the Create() method, so + // we must first create a memory-file dataset + pDriver = GetGDALDriverManager()->GetDriverByName("MEM"); + pDataSet = pDriver->Create("", m_nXGridSize, m_nYGridSize, 1, + m_GDALWriteFloatDataType, NULL); + + if (NULL == pDataSet) { + // Couldn't create in-memory file dataset + cerr << ERR << "cannot create in-memory file for " + << m_strRasterGISOutFormat << " file named " << strFilePathName + << endl + << CPLGetLastErrorMsg() << endl; + return false; + } + } + + // Set projection info for output dataset (will be same as was read in from + // basement DEM) + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + pDataSet->SetProjection( + m_strGDALBasementDEMProjection.c_str()); // Will fail for some formats + CPLPopErrorHandler(); + + // Set geotransformation info for output dataset (will be same as was read in + // from DEM) + if (CE_Failure == pDataSet->SetGeoTransform(m_dGeoTransform)) + LogStream << WARN << "cannot write geotransformation information to " + << m_strRasterGISOutFormat << " file named " << strFilePathName + << endl + << CPLGetLastErrorMsg() << endl; + + // Allocate memory for a 1D array, to hold the floating point raster band data + // for GDAL + double *pdRaster = new double[m_ulNumCells]; + if (NULL == pdRaster) { + // Error, can't allocate memory + cerr << ERR << "cannot allocate memory for " << m_ulNumCells + << " x 1D floating-point array for " << m_strRasterGISOutFormat + << " file named " << strFilePathName << endl; + return (RTN_ERR_MEMALLOC); + } + + bool bScaleOutput = false; + double dRangeScale = 0; + double dDataMin = 0; + + if (!m_bGDALCanWriteFloat) { + double dDataMax = 0; + + // The output file format cannot handle floating-point numbers, so we may + // need to scale the output + GetRasterOutputMinMax(nDataItem, dDataMin, dDataMax, nLayer, 0); + + double const dDataRange = dDataMax - dDataMin; + double const dWriteRange = + static_cast(m_lGDALMaxCanWrite - m_lGDALMinCanWrite); + + if (dDataRange > 0) + dRangeScale = dWriteRange / dDataRange; + + // If we are attempting to write values which are outside this format's + // allowable range, and the user has set the option, then scale the output + if (((dDataMin < static_cast(m_lGDALMinCanWrite)) || + (dDataMax > static_cast(m_lGDALMaxCanWrite))) && + m_bScaleRasterOutput) + bScaleOutput = true; + } + + // Fill the array + int n = 0; + int nPoly = 0; + int nPolyCoast = 0; + int nTopLayer = 0; + double dTmp = 0; + + for (int nY = 0; nY < m_nYGridSize; nY++) { + for (int nX = 0; nX < m_nXGridSize; nX++) { + switch (nDataItem) { + case (RASTER_PLOT_BASEMENT_ELEVATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBasementElev(); + break; + + case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); + break; + + case (RASTER_PLOT_OVERALL_TOP_ELEVATION): + dTmp = + m_pRasterGrid->Cell(nX, nY).dGetSedimentPlusInterventionTopElev(); + break; + + case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetConsSedSlope(); + break; + + case (RASTER_PLOT_SLOPE_FOR_CLIFF_TOE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSlopeForCliffToe(); + break; + + case (RASTER_PLOT_CLIFF_TOE): + dTmp = static_cast(m_pRasterGrid->Cell(nX, nY).bIsCliffToe()); + break; + + case (RASTER_PLOT_SEA_DEPTH): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(); + break; + + case (RASTER_PLOT_AVG_SEA_DEPTH): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSeaDepth() / + static_cast(m_ulIter); + break; + + case (RASTER_PLOT_WAVE_HEIGHT): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); + else + dTmp = 0; + break; + + case (RASTER_PLOT_AVG_WAVE_HEIGHT): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotWaveHeight() / + static_cast(m_ulIter); + else + dTmp = 0; + break; + + case (RASTER_PLOT_WAVE_ORIENTATION): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); + else + dTmp = 0; + break; + + case (RASTER_PLOT_AVG_WAVE_ORIENTATION): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotWaveAngle() / + static_cast(m_ulIter); + else + dTmp = 0; + break; + + case (RASTER_PLOT_BEACH_PROTECTION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBeachProtectionFactor(); + + if (bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) + dTmp = m_dMissingValue; + else + dTmp = 1 - dTmp; // Output the inverse, seems more intuitive + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetPotentialPlatformErosion(); + break; + + case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetActualPlatformErosion(); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotPotentialPlatformErosion(); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotActualPlatformErosion(); + break; + + case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetPotentialBeachErosion(); + break; + + case (RASTER_PLOT_ACTUAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetActualBeachErosion(); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotPotentialBeachErosion(); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotActualBeachErosion(); + break; + + case (RASTER_PLOT_BEACH_DEPOSITION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBeachDeposition(); + break; + + case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotBeachDeposition(); + break; + + case (RASTER_PLOT_SUSPENDED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSuspendedSediment(); + break; + + case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSuspendedSediment() / + static_cast(m_ulIter); + break; + + case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->dGetFineDepth(); + break; + + case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->dGetSandDepth(); + break; + + case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetUnconsolidatedSediment() + ->dGetCoarseDepth(); + break; + + case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->dGetFineDepth(); + break; + + case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->dGetSandDepth(); + break; + + case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nLayer) + ->pGetConsolidatedSediment() + ->dGetCoarseDepth(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): + dTmp = m_pRasterGrid->Cell(nX, nY) + .dGetThisIterCliffCollapseErosionFine(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY) + .dGetThisIterCliffCollapseErosionSand(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY) + .dGetThisIterCliffCollapseErosionCoarse(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseFine(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseSand(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseCoarse(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY) + .dGetThisIterCliffCollapseSandTalusDeposition(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY) + .dGetThisIterCliffCollapseCoarseTalusDeposition(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSandTalusDeposition(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCoarseTalusDeposition(); + break; + + case (RASTER_PLOT_CLIFF_NOTCH_ALL): + dTmp = + m_pRasterGrid->Cell(nX, nY).pGetLandform()->dGetCliffNotchDepth(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): + dTmp = static_cast(m_pRasterGrid->Cell(nX, nY) + .pGetLandform() + ->ulGetCliffCollapseTimestep()); + bIsUnsignedLong = true; + break; + + case (RASTER_PLOT_INTERVENTION_HEIGHT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight(); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveAngle(); + else + dTmp = 0; + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight(); + else + dTmp = 0; + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) + dTmp = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWavePeriod(); + else + dTmp = 0; + break; + + case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): + nPoly = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + nPolyCoast = m_pRasterGrid->Cell(nX, nY).nGetPolygonCoastID(); + + if (nPoly == INT_NODATA) + dTmp = m_dMissingValue; + else { + // Get total volume (all sediment size classes) of change in sediment + // for this polygon for this timestep (-ve erosion, +ve deposition) + dTmp = m_VCoast[nPolyCoast] + .pGetPolygon(nPoly) + ->dGetBeachDepositionAndSuspensionAllUncons() * + m_dCellArea; + + // Calculate the rate in m^3 / sec + dTmp /= (m_dTimeStep * 3600); + } + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): + // cppcheck-suppress assignBoolToFloat + dTmp = m_pRasterGrid->Cell(nX, nY).bPotentialPlatformErosion(); + break; + + case (RASTER_PLOT_INUNDATION_MASK): + // cppcheck-suppress assignBoolToFloat + dTmp = m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea(); + break; + + case (RASTER_PLOT_BEACH_MASK): + dTmp = 0; + nTopLayer = + m_pRasterGrid->Cell(nX, nY).nGetTopNonZeroLayerAboveBasement(); + + if ((nTopLayer == INT_NODATA) || + (nTopLayer == NO_NONZERO_THICKNESS_LAYERS)) + break; + + if ((m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nTopLayer) + ->dGetUnconsolidatedThickness() > 0) && + (m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() > + m_dThisIterSWL)) + dTmp = 1; + + break; + + case (RASTER_PLOT_SLICE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetLayerAtElev(dElev); + break; + + case (RASTER_PLOT_LANDFORM): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory(); + bIsInteger = true; + + if ((static_cast(dTmp) == LF_CAT_DRIFT) || + (static_cast(dTmp) == LF_CAT_CLIFF)) + dTmp = + m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFSubCategory(); + + break; + + case (RASTER_PLOT_INTERVENTION_CLASS): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetInterventionClass(); + bIsInteger = true; + break; + + case (RASTER_PLOT_COAST): + dTmp = (m_pRasterGrid->Cell(nX, nY).bIsCoastline() ? 1 : 0); + break; + + case (RASTER_PLOT_NORMAL_PROFILE): + // dTmp = (m_pRasterGrid->Cell(nX, nY).bIsProfile() ? 1 : 0); + dTmp = m_pRasterGrid->Cell(nX, nY).nGetProfileID(); + bIsInteger = true; + break; + + case (RASTER_PLOT_ACTIVE_ZONE): + dTmp = (m_pRasterGrid->Cell(nX, nY).bIsInActiveZone() ? 1 : 0); + break; + + case (RASTER_PLOT_POLYGON): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + bIsInteger = true; + break; + + case (RASTER_PLOT_SHADOW_ZONE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetShadowZoneNumber(); + bIsInteger = true; + break; + + case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetDownDriftZoneNumber(); + bIsInteger = true; + break; + + case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): + nPoly = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + nPolyCoast = m_pRasterGrid->Cell(nX, nY).nGetPolygonCoastID(); + bIsInteger = true; + + if (nPoly == INT_NODATA) + dTmp = m_nMissingValue; + else { + if (m_VCoast[nPolyCoast].pGetPolygon(nPoly)->bDownCoastThisIter()) + dTmp = 1; + else + dTmp = 0; + } + break; + + case (RASTER_PLOT_SEDIMENT_INPUT): + dTmp = m_pRasterGrid->Cell(nX, nY) + .pGetLayerAboveBasement(nTopLayer) + ->pGetUnconsolidatedSediment() + ->dGetTotAllSedimentInputDepth(); + break; + + case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): + dTmp = (m_pRasterGrid->Cell(nX, nY).bIsFloodBySetupSurge() ? 1 : 0); + break; + + case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): + dTmp = + (m_pRasterGrid->Cell(nX, nY).bIsFloodBySetupSurgeRunup() ? 1 : 0); + break; + + case (RASTER_PLOT_WAVE_FLOOD_LINE): + dTmp = (m_pRasterGrid->Cell(nX, nY).bIsFloodline() ? 1 : 0); + break; + } + + // If necessary, scale this value + if (bScaleOutput) { + if (bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) + dTmp = 0; // TODO 032 Improve this + else + dTmp = dRound(static_cast(m_lGDALMinCanWrite) + + (dRangeScale * (dTmp - dDataMin))); + } + + // Write this value to the array + pdRaster[n++] = dTmp; + } + } + + // Create a single raster band + GDALRasterBand *pBand = pDataSet->GetRasterBand(1); + + // And fill it with the NODATA value + if (bIsInteger) + pBand->Fill(m_nMissingValue); + else if (bIsUnsignedLong) + pBand->Fill(static_cast(m_ulMissingValue)); + else + pBand->Fill(m_dMissingValue); + + // Set value units for this band + string strUnits; + + switch (nDataItem) { + case (RASTER_PLOT_BASEMENT_ELEVATION): + case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): + case (RASTER_PLOT_OVERALL_TOP_ELEVATION): + case (RASTER_PLOT_SEA_DEPTH): + case (RASTER_PLOT_AVG_SEA_DEPTH): + case (RASTER_PLOT_WAVE_HEIGHT): + case (RASTER_PLOT_AVG_WAVE_HEIGHT): + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): + case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): + case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): + case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): + case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): + case (RASTER_PLOT_ACTUAL_BEACH_EROSION): + case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): + case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): + case (RASTER_PLOT_BEACH_DEPOSITION): + case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): + case (RASTER_PLOT_SUSPENDED_SEDIMENT): + case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): + case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): + case (RASTER_PLOT_INTERVENTION_HEIGHT): + case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): + case (RASTER_PLOT_SEDIMENT_INPUT): + case (RASTER_PLOT_CLIFF_NOTCH_ALL): + strUnits = "m"; + break; + + case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): + strUnits = "m/m"; + break; + + case (RASTER_PLOT_WAVE_ORIENTATION): + case (RASTER_PLOT_AVG_WAVE_ORIENTATION): + strUnits = "degrees"; + break; + + case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): + strUnits = "cumecs"; + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_PERIOD): + strUnits = "secs"; + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK): + case (RASTER_PLOT_INUNDATION_MASK): + case (RASTER_PLOT_BEACH_MASK): + case (RASTER_PLOT_SLICE): + case (RASTER_PLOT_LANDFORM): + case (RASTER_PLOT_INTERVENTION_CLASS): + case (RASTER_PLOT_COAST): + case (RASTER_PLOT_NORMAL_PROFILE): + case (RASTER_PLOT_ACTIVE_ZONE): + case (RASTER_PLOT_POLYGON): + case (RASTER_PLOT_SHADOW_ZONE): + case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): + case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): + case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): + case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): + case (RASTER_PLOT_WAVE_FLOOD_LINE): + case (RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP): + strUnits = "none"; + break; + } + + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + pBand->SetUnitType(strUnits.c_str()); // Not supported for some GIS formats + CPLPopErrorHandler(); + + // Tell the output dataset about NODATA (missing values) + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + + if (bIsInteger) + pBand->SetNoDataValue(m_nMissingValue); // Will fail for some formats + if (bIsUnsignedLong) + pBand->SetNoDataValueAsUInt64( + m_ulMissingValue); // Will fail for some formats + else + pBand->SetNoDataValue(m_dMissingValue); // Will fail for some formats + + CPLPopErrorHandler(); + + // Construct the description + string strDesc(*strPlotTitle); + + if (nDataItem == RASTER_PLOT_SLICE) { + ststrTmp.clear(); + ststrTmp << dElev << "m, "; + strDesc.append(ststrTmp.str()); + } + + strDesc.append(" at "); + strDesc.append(strDispTime(m_dSimElapsed, false, false)); + + // Set the GDAL description + pBand->SetDescription(strDesc.c_str()); + + // Set raster category names + char **papszCategoryNames = NULL; + + switch (nDataItem) { + case (RASTER_PLOT_SLICE): + papszCategoryNames = CSLAddString(papszCategoryNames, "Basement"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 0"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 1"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 2"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 3"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 4"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 5"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 6"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 7"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 8"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Layer 9"); + break; + + case (RASTER_PLOT_LANDFORM): + papszCategoryNames = CSLAddString(papszCategoryNames, "None"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Hinterland"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Sea"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Cliff"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Drift"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Intervention"); + + papszCategoryNames = CSLAddString(papszCategoryNames, "Cliff on Coastline"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Inland Cliff"); + + papszCategoryNames = CSLAddString(papszCategoryNames, "Mixed Drift"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Talus"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Beach"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Dunes"); + break; + + case (RASTER_PLOT_INTERVENTION_CLASS): + papszCategoryNames = CSLAddString(papszCategoryNames, "None"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Structural"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Non-Structural"); + break; + + case (RASTER_PLOT_COAST): + papszCategoryNames = CSLAddString(papszCategoryNames, "Not coastline"); + papszCategoryNames = CSLAddString(papszCategoryNames, "Coastline"); + break; + + case (RASTER_PLOT_NORMAL_PROFILE): + papszCategoryNames = + CSLAddString(papszCategoryNames, "Not coastline-normal profile"); + papszCategoryNames = + CSLAddString(papszCategoryNames, "Coastline-normal profile"); + break; + + case (RASTER_PLOT_ACTIVE_ZONE): + papszCategoryNames = CSLAddString(papszCategoryNames, "Not in active zone"); + papszCategoryNames = CSLAddString(papszCategoryNames, "In active zone"); + break; + + case (RASTER_PLOT_POLYGON): + papszCategoryNames = CSLAddString(papszCategoryNames, "Not polygon"); + papszCategoryNames = CSLAddString(papszCategoryNames, "In polygon"); + break; + + case (RASTER_PLOT_SHADOW_ZONE): + papszCategoryNames = CSLAddString(papszCategoryNames, "Not in shadow zone"); + papszCategoryNames = CSLAddString(papszCategoryNames, "In shadow zone"); + break; + + case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): + papszCategoryNames = + CSLAddString(papszCategoryNames, "Not in shadow downdrift zone"); + papszCategoryNames = + CSLAddString(papszCategoryNames, "In shadow downdrift zone"); + break; + + case (RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT): + papszCategoryNames = CSLAddString( + papszCategoryNames, "Updrift movement of unconsolidated sediment "); + papszCategoryNames = CSLAddString( + papszCategoryNames, "Downdrift movement of unconsolidated sediment"); + break; + + case (RASTER_PLOT_SETUP_SURGE_FLOOD_MASK): + papszCategoryNames = + CSLAddString(papszCategoryNames, "Inundated by swl setup and surge "); + papszCategoryNames = CSLAddString(papszCategoryNames, + "Not inundated by swl setup and surge"); + break; + + case (RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK): + papszCategoryNames = CSLAddString( + papszCategoryNames, "Inundated by swl setup, surge and runup "); + papszCategoryNames = CSLAddString( + papszCategoryNames, "Not inundated by swl setup, surge and runup"); + break; + + case (RASTER_PLOT_WAVE_FLOOD_LINE): + papszCategoryNames = + CSLAddString(papszCategoryNames, "Intersection line of inundation "); + papszCategoryNames = CSLAddString(papszCategoryNames, + "Not inundated by swl waves and runup"); + break; + } + + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + pBand->SetCategoryNames( + papszCategoryNames); // Not supported for some GIS formats + CPLPopErrorHandler(); + + if (CE_Failure == pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, + pdRaster, m_nXGridSize, m_nYGridSize, + GDT_Float64, 0, 0, NULL)) { + // Write error, better error message + cerr << ERR << "cannot write data for " << m_strRasterGISOutFormat + << " file named " << strFilePathName << endl + << CPLGetLastErrorMsg() << endl; + delete[] pdRaster; + return false; + } + + // Calculate statistics for this band + double dMin, dMax, dMean, dStdDev; + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + pBand->ComputeStatistics(false, &dMin, &dMax, &dMean, &dStdDev, NULL, NULL); + CPLPopErrorHandler(); + + // And then write the statistics + CPLPushErrorHandler(CPLQuietErrorHandler); // Needed to get next line to fail + // silently, if it fails + pBand->SetStatistics(dMin, dMax, dMean, dStdDev); + CPLPopErrorHandler(); + + if (!m_bGDALCanCreate) { + // Since the user-selected raster driver cannot use the Create() method, we + // have been writing to a dataset created by the in-memory driver. So now we + // need to use CreateCopy() to copy this in-memory dataset to a file in the + // user-specified raster driver format + GDALDriver *pOutDriver = GetGDALDriverManager()->GetDriverByName( + m_strRasterGISOutFormat.c_str()); + GDALDataset *pOutDataSet = + pOutDriver->CreateCopy(strFilePathName.c_str(), pDataSet, false, + m_papszGDALRasterOptions, NULL, NULL); + + if (NULL == pOutDataSet) { + // Couldn't create file + cerr << ERR << "cannot create " << m_strRasterGISOutFormat + << " file named " << strFilePathName << endl + << CPLGetLastErrorMsg() << endl; + return false; + } + + // Get rid of this user-selected dataset object + GDALClose(pOutDataSet); + } + + // Get rid of dataset object + GDALClose(pDataSet); + + // Also get rid of memory allocated to this array + delete[] pdRaster; + + return true; +} + +//=============================================================================================================================== +//! Interpolates wave properties from profile points to all cells within polygons +//! +//! ALGORITHM: k-Nearest Neighbor Inverse Distance Weighting (k-NN IDW) +//! +//! This function takes wave height components (X and Y) measured at discrete profile points +//! and interpolates them to a regular grid using a spatial interpolation method. +//! +//! METHOD OVERVIEW: +//! 1. Builds a k-d tree spatial index from input profile point coordinates (X, Y) +//! 2. For each grid cell, finds the k nearest profile points (typically k=12) +//! 3. Calculates interpolated value using inverse distance weighting (IDW) with power=2 +//! 4. Converts X/Y wave components back to magnitude and direction +//! 5. Updates grid cells with interpolated wave properties +//! +//! KEY PARAMETERS (defined in spatial_interpolation.cpp DualSpatialInterpolator): +//! - k_neighbors = 12 Number of nearest points to use for interpolation +//! - power = 2.0 Exponent for inverse distance weighting +//! (higher = more weight to closer points) +//! +//! TUNING GUIDANCE: +//! - Increase k_neighbors (e.g., 15-20) for smoother results with more averaging +//! - Decrease k_neighbors (e.g., 8-10) for results that follow local variations more closely +//! - Increase power (e.g., 3.0-4.0) to emphasize nearby points (sharper transitions) +//! - Decrease power (e.g., 1.0-1.5) for smoother, more gradual transitions +//! +//! IMPORTANT NOTES: +//! - This method does NOT respect transect structure (point 1 vs point 2, etc.) +//! - Treats all input points equally regardless of which transect they belong to +//! - Works well for scattered points but may not preserve transect-aligned features +//! - For transect-aware interpolation, consider bilinear methods instead +//! +//! COASTLINE ORIENTATION: +//! - The coastlines are available in m_VCoast[] vector +//! - Each coast has flux orientation: m_VCoast[i].dGetFluxOrientation(coastpoint) +//! - Each coast has breaking wave angle: m_VCoast[i].dGetBreakingWaveAngle(coastpoint) +//! - These could be used to implement coast-aware interpolation algorithms +//! +//! @param pVTransects Vector of TransectWaveData containing wave data per transect +//! @param pVdDeepWaterX X coordinates of deep water grid edge points +//! @param pVdDeepWaterY Y coordinates of deep water grid edge points +//! @param pVdDeepWaterHeightX X component of wave height at deep water points +//! @param pVdDeepWaterHeightY Y component of wave height at deep water points +//! @return RTN_OK on success, error code otherwise +//=============================================================================================================================== +int CSimulation::nInterpolateWavesToPolygonCells( + vector const *pVTransects, + vector const *pVdDeepWaterX, + vector const *pVdDeepWaterY, + vector const *pVdDeepWaterHeightX, + vector const *pVdDeepWaterHeightY) { + + // ============================================================================ + // STEP 1: Calculate grid dimensions and initialize variables + // ============================================================================ + + int nXSize = 0; + int nYSize = 0; + + // Average values used as fallback when interpolation fails or returns NaN + double dXAvg = 0; + double dYAvg = 0; + + // Calculate bounding box size + nXSize = m_nXMaxBoundingBox - m_nXMinBoundingBox + 1; + nYSize = m_nYMaxBoundingBox - m_nYMinBoundingBox + 1; + int const nGridSize = nXSize * nYSize; + + // Count total points across all transects plus deep water points + unsigned int nPoints = 0; + for (const auto& transect : *pVTransects) { + nPoints += static_cast(transect.VdX.size()); + } + nPoints += static_cast(pVdDeepWaterX->size()); + + // Initialize output arrays (will hold interpolated X and Y wave components) + vector VdOutX(nGridSize, 0); + vector VdOutY(nGridSize, 0); + + // ============================================================================ + // STEP 2: Prepare input data for spatial interpolation + // ============================================================================ + + // Flatten transect data and deep water data into contiguous arrays for the interpolator + std::vector points; + std::vector VdHeightX; + std::vector VdHeightY; + + points.reserve(nPoints); + VdHeightX.reserve(nPoints); + VdHeightY.reserve(nPoints); + + // Add profile/transect points + for (const auto& transect : *pVTransects) { + for (size_t i = 0; i < transect.VdX.size(); i++) { + points.emplace_back(transect.VdX[i], transect.VdY[i]); + VdHeightX.push_back(transect.VdHeightX[i]); + VdHeightY.push_back(transect.VdHeightY[i]); + } + } + + // Add deep water grid edge points + for (size_t i = 0; i < pVdDeepWaterX->size(); i++) { + points.emplace_back((*pVdDeepWaterX)[i], (*pVdDeepWaterY)[i]); + VdHeightX.push_back((*pVdDeepWaterHeightX)[i]); + VdHeightY.push_back((*pVdDeepWaterHeightY)[i]); + } + + // ============================================================================ + // STEP 3: Create spatial interpolator + // ============================================================================ + // + // DualSpatialInterpolator parameters: + // - points: Input point coordinates (from profiles/transects) + // - VdHeightX, VdHeightY: Wave height X and Y components at those points + // - k_neighbors = 12: Use 12 nearest neighbors for interpolation + // ** ADJUST THIS to change smoothness vs local detail ** + // - power = 2.0: Inverse distance weighting power + // ** ADJUST THIS to change influence of nearby vs distant points ** + // + // The interpolator builds a k-d tree for fast nearest neighbor search + // and shares it between X and Y interpolation for efficiency + DualSpatialInterpolator interp(points, VdHeightX, VdHeightY, 6, 2.0); + + // ============================================================================ + // STEP 4: Build query points (grid cells where we want interpolated values) + // ============================================================================ + + std::vector query_points; + query_points.reserve(nGridSize); + for (int nY = m_nYMinBoundingBox; nY <= m_nYMaxBoundingBox; nY++) { + for (int nX = m_nXMinBoundingBox; nX <= m_nXMaxBoundingBox; nX++) { + query_points.emplace_back(static_cast(nX), + static_cast(nY)); + } + } + + // ============================================================================ + // STEP 5: Perform batch interpolation + // ============================================================================ + // + // This does the actual interpolation for all grid points at once + // Uses OpenMP parallelization if available (see spatial_interpolation.cpp) + // Interpolates both X and Y components simultaneously using shared k-d tree + interp.Interpolate(query_points, VdOutX, VdOutY); + + // ============================================================================ + // STEP 6: Validate results and calculate average values for fallback + // ============================================================================ + // + // Check for NaN or unreasonably large values and replace with missing value marker + // Also calculate average of valid values to use as fallback + + int nXValid = 0; + int nYValid = 0; + + // Validate X component + for (unsigned int n = 0; n < VdOutX.size(); n++) { + if (isnan(VdOutX[n])) + VdOutX[n] = m_dMissingValue; + else if (tAbs(VdOutX[n]) > 1e10) // Sanity check for unreasonably large values + VdOutX[n] = m_dMissingValue; + else { + dXAvg += VdOutX[n]; + nXValid++; + } + } + + // Validate Y component + for (unsigned int n = 0; n < VdOutY.size(); n++) { + if (isnan(VdOutY[n])) + VdOutY[n] = m_dMissingValue; + else if (tAbs(VdOutY[n]) > 1e10) // Sanity check for unreasonably large values + VdOutY[n] = m_dMissingValue; + else { + dYAvg += VdOutY[n]; + nYValid++; + } + } + + // Calculate averages (for use as fallback when individual cells have missing values) + if (nXValid > 0) + dXAvg /= nXValid; + if (nYValid > 0) + dYAvg /= nYValid; + + // ============================================================================ + // STEP 7: Update grid cells with interpolated wave properties + // ============================================================================ + // + // Convert X and Y components back to magnitude and direction, + // then update each cell's wave attributes + + int n = 0; + + for (int nY = 0; nY < nYSize; nY++) { + for (int nX = 0; nX < nXSize; nX++) { + int const nActualX = nX + m_nXMinBoundingBox; + int const nActualY = nY + m_nYMinBoundingBox; + + if (m_pRasterGrid->Cell(nActualX, nActualY) + .bIsInContiguousSea()) { + // Only update sea cells + + if (m_pRasterGrid->Cell(nActualX, nActualY).nGetPolygonID() == + INT_NODATA) { + // -------------------------------------------------------------------- + // Deep water cell (NOT in a polygon) + // -------------------------------------------------------------------- + // Use the cell's pre-assigned deep water wave values + // (these cells are beyond the coastal zone, so don't need interpolation) + + double const dDeepWaterWaveHeight = + m_pRasterGrid->Cell(nActualX, nActualY) + .dGetCellDeepWaterWaveHeight(); + m_pRasterGrid->Cell(nActualX, nActualY).SetWaveHeight( + dDeepWaterWaveHeight); + + double const dDeepWaterWaveAngle = + m_pRasterGrid->Cell(nActualX, nActualY) + .dGetCellDeepWaterWaveAngle(); + m_pRasterGrid->Cell(nActualX, nActualY).SetWaveAngle( + dDeepWaterWaveAngle); + } else { + // -------------------------------------------------------------------- + // Coastal zone cell (IN a polygon) + // -------------------------------------------------------------------- + // Use the interpolated wave values calculated above + + double dWaveHeightX; + double dWaveHeightY; + + // Get interpolated X component (use average as fallback if missing/invalid) + if ((isnan(VdOutX[n])) || + (bFPIsEqual(VdOutX[n], m_dMissingValue, TOLERANCE))) + dWaveHeightX = dXAvg; + else + dWaveHeightX = VdOutX[n]; + + // Get interpolated Y component (use average as fallback if missing/invalid) + if ((isnan(VdOutY[n])) || + (bFPIsEqual(VdOutY[n], m_dMissingValue, TOLERANCE))) + dWaveHeightY = dYAvg; + else + dWaveHeightY = VdOutY[n]; + + // Convert X/Y components to magnitude and direction + double const dWaveHeight = sqrt((dWaveHeightX * dWaveHeightX) + + (dWaveHeightY * dWaveHeightY)); + double const dWaveDir = + atan2(dWaveHeightX, dWaveHeightY) * (180 / PI); + + // Update the cell's wave attributes + m_pRasterGrid->Cell(nActualX, nActualY).SetWaveHeight( + dWaveHeight); + m_pRasterGrid->Cell(nActualX, nActualY).SetWaveAngle( + dKeepWithin360(dWaveDir)); + + // Calculate wave height-to-depth ratio and update active zone status + // (active zone = where waves are breaking or near-breaking) + double const dSeaDepth = + m_pRasterGrid->Cell(nActualX, nActualY).dGetSeaDepth(); + + if ((dWaveHeight / dSeaDepth) >= + m_dBreakingWaveHeightDepthRatio) + m_pRasterGrid->Cell(nActualX, nActualY).SetInActiveZone( + true); + + // LogStream << " nX = " << nX << " nY = " << nY << " [" << + // nActualX + // << "][" << nActualY << "] waveheight = " << dWaveHeight << " + // dWaveDir = " << dWaveDir << " dKeepWithin360(dWaveDir) = " << + // dKeepWithin360(dWaveDir) << endl; + } + } + + // Increment with safety check + n++; + n = tMin(n, static_cast(VdOutX.size() - 1)); + } + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! If the user supplies multiple deep water wave height and angle values, +//! this routine interplates these to all cells (including dry land cells) +//=============================================================================================================================== +int CSimulation::nInterpolateAllDeepWaterWaveValues(void) { + + unsigned int const nUserPoints = static_cast(m_VdDeepWaterWaveStationX.size()); + + if (nUserPoints < 3) { + // Need minimum 3 points for interpolation + LogStream << "ERROR: Insufficient wave station data points (" << nUserPoints << "), need at least 3" << endl; + return RTN_ERR_GRIDCREATE; + } + + // ============================================================================ + // STEP 1: Build input point cloud + // ============================================================================ + std::vector station_points; + station_points.reserve(nUserPoints); + + for (unsigned int i = 0; i < nUserPoints; i++) { + station_points.emplace_back(m_VdDeepWaterWaveStationX[i], + m_VdDeepWaterWaveStationY[i]); + } + + // ============================================================================ + // STEP 2: Create shared spatial interpolators + // ============================================================================ + // Use same parameters as existing nanoflann implementation + // k_neighbors=12 matches GDAL's nMaxPoints=12 setting + // power=2.0 matches GDAL's dfPower=2 setting + + SpatialInterpolator height_interp(station_points, m_VdThisIterDeepWaterWaveStationHeight, 12, 2.0); + + // Create angle and period interpolators sharing the same k-d tree for efficiency + SpatialInterpolator angle_interp(height_interp.GetPointCloud(), height_interp.GetKDTree(), + m_VdThisIterDeepWaterWaveStationAngle, 12, 2.0); + + SpatialInterpolator period_interp(height_interp.GetPointCloud(), height_interp.GetKDTree(), + m_VdThisIterDeepWaterWaveStationPeriod, 12, 2.0); + + // ============================================================================ + // STEP 3: Build query points (all grid cells) + // ============================================================================ + std::vector query_points; + query_points.reserve(m_ulNumCells); + + for (int nY = 0; nY < m_nYGridSize; nY++) { + for (int nX = 0; nX < m_nXGridSize; nX++) { + // Convert grid coordinates to CRS coordinates + double dX = dGridCentroidXToExtCRSX(nX); + double dY = dGridCentroidYToExtCRSY(nY); + query_points.emplace_back(dX, dY); + } + } + + // ============================================================================ + // STEP 4: Perform batch interpolation (OpenMP parallelized) + // ============================================================================ + std::vector height_results, angle_results, period_results; + + // All three interpolations use the same k-d tree - very efficient! + height_interp.Interpolate(query_points, height_results); + angle_interp.Interpolate(query_points, angle_results); + period_interp.Interpolate(query_points, period_results); + + // ============================================================================ + // STEP 5: Apply results to grid (single pass, optimal cache usage) + // ============================================================================ + + // Calculate averages for fallback (matches GDAL behavior) + double dAvgHeight = 0, dAvgAngle = 0, dAvgPeriod = 0; + int nValidHeight = 0, nValidAngle = 0, nValidPeriod = 0; + + for (size_t i = 0; i < height_results.size(); i++) { + if (std::isfinite(height_results[i])) { + dAvgHeight += height_results[i]; + nValidHeight++; + } + if (std::isfinite(angle_results[i])) { + dAvgAngle += angle_results[i]; + nValidAngle++; + } + if (std::isfinite(period_results[i])) { + dAvgPeriod += period_results[i]; + nValidPeriod++; + } + } + + if (nValidHeight > 0) dAvgHeight /= nValidHeight; + if (nValidAngle > 0) dAvgAngle /= nValidAngle; + if (nValidPeriod > 0) dAvgPeriod /= nValidPeriod; + + // Apply to grid cells in single OpenMP parallel loop + #pragma omp parallel for collapse(2) schedule(static) + for (int nY = 0; nY < m_nYGridSize; nY++) { + for (int nX = 0; nX < m_nXGridSize; nX++) { + int const idx = nY * m_nXGridSize + nX; + + // Apply height (with fallback to average) + double height = std::isfinite(height_results[idx]) ? height_results[idx] : dAvgHeight; + m_pRasterGrid->Cell(nX, nY).SetCellDeepWaterWaveHeight(height); + + // Apply angle (with fallback to average) + double angle = std::isfinite(angle_results[idx]) ? angle_results[idx] : dAvgAngle; + m_pRasterGrid->Cell(nX, nY).SetCellDeepWaterWaveAngle(angle); + + // Apply period (with fallback to average) + double period = std::isfinite(period_results[idx]) ? period_results[idx] : dAvgPeriod; + m_pRasterGrid->Cell(nX, nY).SetCellDeepWaterWavePeriod(period); + } + } + + return RTN_OK; +} diff --git a/src/gis_utils.cpp b/src/gis_utils.cpp index bedaf88f5..a04e2d232 100644 --- a/src/gis_utils.cpp +++ b/src/gis_utils.cpp @@ -1,1921 +1,1921 @@ -/*! - \file gis_utils.cpp - \brief Various GIS-related functions, requires GDAL - \details Note re. coordinate systems used - - 1. In the raster CRS, cell[0][0] is at the top left (NW) corner of the grid. Raster grid co-oordinate [0][0] is actually the top left (NW) corner of this cell. - - 2. We assume that the grid CRS and external CRS have parallel axes. If they have not, see http://www.gdal.org/classGDALDataset.html which says that: - - To convert between pixel/line (P,L) raster space, and projection coordinates (Xp,Yp) space Xp = padfTransform[0] + padfTransform[1] + padfTransform[2]; Yp - = padfTransform[3] + padfTransform[4] + padfTransform[5]; - - In a north-up image, padfTransform[1] is the pixel width, and padfTransform[5] is the pixel height. The upper left corner of the upper left pixel is at position (padfTransform[0], padfTransform[3]). - - 3. Usually, raster grid CRS values are integer, i.e. they refer to a point which is at the centroid of a cell. They may also be -ve or greater than m_nXGridSize-1 i.e. may refer to a point which lies outside any cell of the raster grid. - - \author David Favis-Mortlock - \author Andres Payo - \date 2025 - \copyright GNU General Public License -*/ - -/* =============================================================================================================================== - - This file is part of CoastalME, the Coastal Modelling Environment. CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -===============================================================================================================================*/ -#include - -#include - -#include -using std::vector; - -#include -using std::cerr; -using std::endl; -using std::ios; - -#include -using std::atan2; - -#include -#include -#include - -#include -using std::strstr; - -#include -#include -#include - -#include "cme.h" -#include "coast.h" -#include "raster_grid.h" -#include "2d_point.h" -#include "2di_point.h" - -//=============================================================================================================================== -//! Given the integer X-axis ordinate of a cell in the raster grid CRS, returns the external CRS X-axis ordinate of the cell's centroid -//=============================================================================================================================== -double CSimulation::dGridCentroidXToExtCRSX(int const nGridX) const -{ - // TODO 064 - return (m_dGeoTransform[0] + (nGridX * m_dGeoTransform[1]) + (m_dGeoTransform[1] / 2)); -} - -//=============================================================================================================================== -//! Given the integer Y-axis ordinate of a cell in the raster grid CRS, returns the external CRS Y-axis ordinate of the cell's centroid -//=============================================================================================================================== -double CSimulation::dGridCentroidYToExtCRSY(int const nGridY) const -{ - // TODO 064 - return (m_dGeoTransform[3] + (nGridY * m_dGeoTransform[5]) + (m_dGeoTransform[5] / 2)); -} - -//=============================================================================================================================== -//! Transforms a pointer to a CGeom2DIPoint in the raster grid CRS (assumed to be the centroid of a cell) to the equivalent CGeom2DPoint in the external CRS -//=============================================================================================================================== -CGeom2DPoint CSimulation::PtGridCentroidToExt(CGeom2DIPoint const *pPtiIn) const -{ - // TODO 064 - double const dX = m_dGeoTransform[0] + (pPtiIn->nGetX() * m_dGeoTransform[1]) + (m_dGeoTransform[1] / 2); - double const dY = m_dGeoTransform[3] + (pPtiIn->nGetY() * m_dGeoTransform[5]) + (m_dGeoTransform[5] / 2); - - return CGeom2DPoint(dX, dY); -} - -//=============================================================================================================================== -//! Given a real-valued X-axis ordinate in the raster grid CRS (i.e. not the centroid of a cell), returns the external CRS X-axis ordinate -//=============================================================================================================================== -double CSimulation::dGridXToExtCRSX(double const dGridX) const -{ - // TODO 064 Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2) - return m_dGeoTransform[0] + (dGridX * m_dGeoTransform[1]) - 1; -} - -//=============================================================================================================================== -//! Given a real-valued Y-axis ordinate in the raster grid CRS (i.e. not the centroid of a cell), returns the external CRS Y-axis ordinate -//=============================================================================================================================== -double CSimulation::dGridYToExtCRSY(double const dGridY) const -{ - // TODO 064 Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5) - return m_dGeoTransform[3] + (dGridY * m_dGeoTransform[5]) - 1; -} - -//=============================================================================================================================== -//! Transforms an X-axis ordinate in the external CRS to the equivalent X-axis ordinate in the raster grid CRS (the result is not rounded, and so may not be integer, and may be outside the grid) -//=============================================================================================================================== -double CSimulation::dExtCRSXToGridX(double const dExtCRSX) const -{ - // TODO 064 - return ((dExtCRSX - m_dGeoTransform[0]) / m_dGeoTransform[1]) - 1; -} - -//=============================================================================================================================== -//! Transforms a Y-axis ordinate in the external CRS to the equivalent Y-axis ordinate in the raster grid CRS (the result is not rounded, and so may not be integer, and may be outside the grid) -//=============================================================================================================================== -double CSimulation::dExtCRSYToGridY(double const dExtCRSY) const -{ - // TODO 064 - return ((dExtCRSY - m_dGeoTransform[3]) / m_dGeoTransform[5]) - 1; -} - -//=============================================================================================================================== -//! Transforms a pointer to a CGeom2DPoint in the external CRS to the equivalent CGeom2DIPoint in the raster grid CRS (both values rounded). The result may be outside the grid -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiExtCRSToGridRound(CGeom2DPoint const* pPtIn) const -{ - // TODO 064 - int const nX = nRound(((pPtIn->dGetX() - m_dGeoTransform[0]) / m_dGeoTransform[1]) - 1); - int const nY = nRound(((pPtIn->dGetY() - m_dGeoTransform[3]) / m_dGeoTransform[5]) - 1); - - return CGeom2DIPoint(nX, nY); -} - -//=============================================================================================================================== -//! Returns the distance (in external CRS) between two points -//=============================================================================================================================== -double CSimulation::dGetDistanceBetween(CGeom2DPoint const* Pt1, CGeom2DPoint const* Pt2) -{ - double const dXDist = Pt1->dGetX() - Pt2->dGetX(); - double const dYDist = Pt1->dGetY() - Pt2->dGetY(); - - return hypot(dXDist, dYDist); -} - -//=============================================================================================================================== -//! Returns the distance (in external CRS) between two points -//=============================================================================================================================== -double CSimulation::dGetDistanceBetween(CGeom2DIPoint const* Pti1, CGeom2DIPoint const* Pti2) -{ - double const dXDist = Pti1->nGetX() - Pti2->nGetX(); - double const dYDist = Pti1->nGetY() - Pti2->nGetY(); - - return hypot(dXDist, dYDist); -} - -//=============================================================================================================================== -//! Returns the distance (in external CRS) between two points -//=============================================================================================================================== -double CSimulation::dGetDistanceBetween(double const dX1, double const dY1, double const dX2, double const dY2) -{ - double const dXDist = dX1 - dX2; - double const dYDist = dY1 - dY2; - - return hypot(dXDist, dYDist); -} - -//=============================================================================================================================== -//! Returns twice the signed area of a triangle, defined by three points -//=============================================================================================================================== -double CSimulation::dTriangleAreax2(CGeom2DPoint const* pPtA, CGeom2DPoint const* pPtB, CGeom2DPoint const* pPtC) -{ - return (pPtB->dGetX() - pPtA->dGetX()) * (pPtC->dGetY() - pPtA->dGetY()) - (pPtB->dGetY() - pPtA->dGetY()) * (pPtC->dGetX() - pPtA->dGetX()); -} - -//=============================================================================================================================== -//! Checks whether the supplied point (an x-y pair, in the grid CRS) is within the raster grid, and is a valid cell (i.e. the basement DEM is not NODATA) -//=============================================================================================================================== -bool CSimulation::bIsWithinValidGrid(int const nX, int const nY) const -{ - if ((nX < 0) || (nX >= m_nXGridSize)) - return false; - - if ((nY < 0) || (nY >= m_nYGridSize)) - return false; - - if (m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) - return false; - - return true; -} - -//=============================================================================================================================== -//! Checks whether the supplied point (a reference to a CGeom2DIPoint, in the grid CRS) is within the raster grid, and is a valid cell (i.e. the basement DEM is not NODATA) -//=============================================================================================================================== -bool CSimulation::bIsWithinValidGrid(CGeom2DIPoint const* Pti) const -{ - int const nX = Pti->nGetX(); - int const nY = Pti->nGetY(); - - return this->bIsWithinValidGrid(nX, nY); -} - -//=============================================================================================================================== -//! Constrains the supplied point (in the grid CRS) to be a valid cell within the raster grid -//=============================================================================================================================== -void CSimulation::KeepWithinValidGrid(int& nX, int& nY) const -{ - nX = tMax(nX, 0); - nX = tMin(nX, m_nXGridSize - 1); - - nY = tMax(nY, 0); - nY = tMin(nY, m_nYGridSize - 1); -} - -//=============================================================================================================================== -//! Constrains the second supplied point (both are CGeom2DIPoints, in the grid CRS) to be a valid cell within the raster grid -//=============================================================================================================================== -void CSimulation::KeepWithinValidGrid(CGeom2DIPoint const* Pti0, CGeom2DIPoint* Pti1) const -{ - KeepWithinValidGrid(Pti0->nGetX(), Pti0->nGetY(), *Pti1->pnGetX(), *Pti1->pnGetY()); -} - -//=============================================================================================================================== -//! Given two points in the grid CRS (the points assumed not to be coincident), this routine modifies the value of the second point so that it is on a line joining the original two points and is a valid cell within the raster grid. However in some cases (e.g. if the first point is at the edge of the valid part of the raster grid) then the second cell will be coincident with the -//! first cell, and the line joining them is thus of zero length. The calling routine has to be able to handle this -//=============================================================================================================================== -void CSimulation::KeepWithinValidGrid(int nX0, int nY0, int& nX1, int& nY1) const -{ - // Safety check: make sure that the first point is within the valid grid - if (nX0 >= m_nXGridSize) - nX0 = m_nXGridSize - 1; - - else if (nX0 < 0) - nX0 = 0; - - if (nY0 >= m_nYGridSize) - nY0 = m_nYGridSize - 1; - - else if (nY0 < 0) - nY0 = 0; - - // OK let's go - int const nDiffX = nX0 - nX1; - int const nDiffY = nY0 - nY1; - - if (nDiffX == 0) - { - // The two points have the same x coordinates, so we just need to constrain the y co-ord - if (nY1 < nY0) - { - nY1 = -1; - - do - { - nY1++; - } while (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue()); - - return; - } - else - { - nY1 = m_nYGridSize; - - do - { - nY1--; - } while (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue()); - - return; - } - } - else if (nDiffY == 0) - { - // The two points have the same y coordinates, so we just need to constrain the x co-ord - if (nX1 < nX0) - { - nX1 = -1; - - do - { - nX1++; - } while (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue()); - - return; - } - else - { - nX1 = m_nXGridSize; - - do - { - nX1--; - } while (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue()); - - return; - } - } - else - { - // The two points have different x coordinates and different y coordinates, so we have to work harder. First find which of the coordinates is the greatest distance outside the grid, and constrain that co-ord for efficiency (since this will reduce the number of times round the loop). Note that both may be inside the grid, if the incorrect co-ord is in the invalid margin, in which case arbitrarily contrain the x co-ord - int nXDistanceOutside = 0; - int nYDistanceOutside = 0; - - if (nX1 < 0) - nXDistanceOutside = -nX1; - else if (nX1 >= m_nXGridSize) - nXDistanceOutside = nX1 - m_nXGridSize + 1; - - if (nY1 < 0) - nYDistanceOutside = -nY1; - else if (nY1 >= m_nYGridSize) - nXDistanceOutside = nY1 - m_nYGridSize + 1; - - if (nXDistanceOutside >= nYDistanceOutside) - { - // Constrain the x co-ord - if (nX1 < nX0) - { - // The incorrect x co-ord is less than the correct x co-ord: constrain it and find the y co-ord - nX1 = -1; - - do - { - nX1++; - - nY1 = nY0 + nRound(((nX1 - nX0) * nDiffY) / static_cast(nDiffX)); - } while ((nY1 < 0) || (nY1 >= m_nYGridSize) || (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue())); - - return; - } - else - { - // The incorrect x co-ord is greater than the correct x-co-ord: constrain it and find the y co-ord - nX1 = m_nXGridSize; - - do - { - nX1--; - - nY1 = nY0 + nRound(((nX1 - nX0) * nDiffY) / static_cast(nDiffX)); - } while ((nY1 < 0) || (nY1 >= m_nYGridSize) || (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue())); - - return; - } - } - else - { - // Constrain the y co-ord - if (nY1 < nY0) - { - // The incorrect y co-ord is less than the correct y-co-ord: constrain it and find the x co-ord - nY1 = -1; - - do - { - nY1++; - - nX1 = nX0 + nRound(((nY1 - nY0) * nDiffX) / static_cast(nDiffY)); - } while ((nX1 < 0) || (nX1 >= m_nXGridSize) || (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue())); - - return; - } - else - { - // The incorrect y co-ord is greater than the correct y co-ord: constrain it and find the x co-ord - nY1 = m_nYGridSize; - - do - { - nY1--; - - nX1 = nX0 + - nRound(((nY1 - nY0) * nDiffX) / static_cast(nDiffY)); - } while ((nX1 < 0) || (nX1 >= m_nXGridSize) || (m_pRasterGrid->m_Cell[nX1][nY1].bBasementElevIsMissingValue())); - - return; - } - } - } -} - -//=============================================================================================================================== -//! Constrains the supplied angle to be within 0 and 360 degrees -//=============================================================================================================================== -double CSimulation::dKeepWithin360(double const dAngle) -{ - double dNewAngle = dAngle; - - // Sort out -ve angles - while (dNewAngle < 0) - dNewAngle += 360; - - // Sort out angles > 360 - while (dNewAngle > 360) - dNewAngle -= 360; - - return dNewAngle; -} - -//=============================================================================================================================== -//! Returns a point (external CRS) which is the average of (i.e. is midway between) two other external CRS points -//=============================================================================================================================== -CGeom2DPoint CSimulation::PtAverage(CGeom2DPoint const* pPt1, CGeom2DPoint const* pPt2) -{ - double const dPt1X = pPt1->dGetX(); - double const dPt1Y = pPt1->dGetY(); - double const dPt2X = pPt2->dGetX(); - double const dPt2Y = pPt2->dGetY(); - double const dPtAvgX = (dPt1X + dPt2X) / 2; - double const dPtAvgY = (dPt1Y + dPt2Y) / 2; - - return CGeom2DPoint(dPtAvgX, dPtAvgY); -} - -// //=============================================================================================================================== -// //! Returns an integer point (grid CRS) which is the approximate average of (i.e. is midway between) two other grid CRS integer points -// //=============================================================================================================================== -// CGeom2DIPoint CSimulation::PtiAverage(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2) -// { -// int const nPti1X = pPti1->nGetX(); -// int const nPti1Y = pPti1->nGetY(); -// int const nPti2X = pPti2->nGetX(); -// int const nPti2Y = pPti2->nGetY(); -// int const nPtiAvgX = (nPti1X + nPti2X) / 2; -// int const nPtiAvgY = (nPti1Y + nPti2Y) / 2; -// -// return CGeom2DIPoint(nPtiAvgX, nPtiAvgY); -// } - -//=============================================================================================================================== -//! Returns an integer point (grid CRS) which is the weighted average of two other grid CRS integer points. The weight must be <= 1, if the weight is < 0.5 then the output point is closer to the first point, if the weight is > 0.5 then the output point is closer to the second point -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiWeightedAverage(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2, double const dWeight) -{ - int const nPti1X = pPti1->nGetX(); - int const nPti1Y = pPti1->nGetY(); - int const nPti2X = pPti2->nGetX(); - int const nPti2Y = pPti2->nGetY(); - double const dOtherWeight = 1.0 - dWeight; - - int const nPtiWeightAvgX = nRound((dWeight * nPti2X) + (dOtherWeight * nPti1X)); - int const nPtiWeightAvgY = nRound((dWeight * nPti2Y) + (dOtherWeight * nPti1Y)); - - return CGeom2DIPoint(nPtiWeightAvgX, nPtiWeightAvgY); -} - -//=============================================================================================================================== -//! Returns a point (external CRS) which is the average of a vector of external CRS points -//=============================================================================================================================== -CGeom2DPoint CSimulation::PtAverage(vector* pVIn) -{ - int const nSize = static_cast(pVIn->size()); - - if (nSize == 0) - return CGeom2DPoint(DBL_NODATA, DBL_NODATA); - - double dAvgX = 0; - double dAvgY = 0; - - for (int n = 0; n < nSize; n++) - { - dAvgX += pVIn->at(n).dGetX(); - dAvgY += pVIn->at(n).dGetY(); - } - - dAvgX /= nSize; - dAvgY /= nSize; - - return CGeom2DPoint(dAvgX, dAvgY); -} - -// //=============================================================================================================================== -// //! Returns a point (grid CRS) which is the average of a vector of grid CRS points -// //=============================================================================================================================== -// CGeom2DIPoint CSimulation::PtiAverage(vector* pVIn) -// { -// int nSize = static_cast(pVIn->size()); -// if (nSize == 0) -// return CGeom2DIPoint(INT_NODATA, INT_NODATA); -// -// double dAvgX = 0; -// double dAvgY = 0; -// -// for (int n = 0; n < nSize; n++) -// { -// dAvgX += pVIn->at(n).nGetX(); -// dAvgY += pVIn->at(n).nGetY(); -// } -// -// dAvgX /= nSize; -// dAvgY /= nSize; -// -// return CGeom2DIPoint(nRound(dAvgX), nRound(dAvgY)); -// } - -//=============================================================================================================================== -//! Returns an integer point (grid CRS) which is the centroid of a polygon, given by a vector of grid CRS points. From https://stackoverflow.com/questions/2792443/finding-the-centroid-of-a-polygon -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiPolygonCentroid(vector* pVIn) -{ - CGeom2DIPoint PtiCentroid(0, 0); - int const nSize = static_cast(pVIn->size()); - int nX0 = 0; // Current vertex X - int nY0 = 0; // Current vertex Y - int nX1 = 0; // Next vertex X - int nY1 = 0; // Next vertex Y - - double dA = 0; // Partial signed area - double dSignedArea = 0.0; - - // For all vertices except last - for (int i = 0; i < nSize - 1; ++i) - { - nX0 = pVIn->at(i).nGetX(); - nY0 = pVIn->at(i).nGetY(); - nX1 = pVIn->at(i + 1).nGetX(); - nY1 = pVIn->at(i + 1).nGetY(); - - dA = (nX0 * nY1) - (nX1 * nY0); - dSignedArea += dA; - PtiCentroid.AddXAddY((nX0 + nX1) * dA, (nY0 + nY1) * dA); - } - - // Do last vertex separately to avoid performing an expensive modulus operation in each iteration - nX0 = pVIn->at(nSize - 1).nGetX(); - nY0 = pVIn->at(nSize - 1).nGetY(); - nX1 = pVIn->at(0).nGetX(); - nY1 = pVIn->at(0).nGetY(); - - dA = (nX0 * nY1) - (nX1 * nY0); - dSignedArea += dA; - PtiCentroid.AddXAddY((nX0 + nX1) * dA, (nY0 + nY1) * dA); - - dSignedArea *= 0.5; - PtiCentroid.DivXDivY(6.0 * dSignedArea, 6.0 * dSignedArea); - - return PtiCentroid; -} - -//=============================================================================================================================== -// Returns a vector which is perpendicular to an existing vector -//=============================================================================================================================== -// vector CSimulation::VGetPerpendicular(CGeom2DPoint const* -// PtStart, CGeom2DPoint const* PtNext, double const dDesiredLength, int const -// nHandedness) -// { -// // Returns a two-point vector which passes through PtStart with a scaled -// length -// double dXLen = PtNext->dGetX() - PtStart->dGetX(); -// double dYLen = PtNext->dGetY() - PtStart->dGetY(); -// -// double dLength = hypot(dXLen, dYLen); -// double dScaleFactor = dDesiredLength / dLength; -// -// // The difference vector is (dXLen, dYLen), so the perpendicular -// difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) -// CGeom2DPoint EndPt; -// if (nHandedness == RIGHT_HANDED) -// { -// EndPt.SetX(PtStart->dGetX() + (dScaleFactor * dYLen)); -// EndPt.SetY(PtStart->dGetY() - (dScaleFactor * dXLen)); -// } -// else -// { -// EndPt.SetX(PtStart->dGetX() - (dScaleFactor * dYLen)); -// EndPt.SetY(PtStart->dGetY() + (dScaleFactor * dXLen)); -// } -// -// vector VNew; -// VNew.push_back(*PtStart); -// VNew.push_back(EndPt); -// return VNew; -// } - -// //=============================================================================================================================== -// //! Returns a CGeom2DPoint which is the 'other' point of a two-point vector passing through PtStart, and which is perpendicular to the two-point vector from PtStart to PtNext -// //=============================================================================================================================== -// CGeom2DPoint CSimulation::PtGetPerpendicular(CGeom2DPoint const* PtStart, CGeom2DPoint const* PtNext, double const dDesiredLength, int const nHandedness) -// { -// double const dXLen = PtNext->dGetX() - PtStart->dGetX(); -// double const dYLen = PtNext->dGetY() - PtStart->dGetY(); -// double dLength; -// -// if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) -// dLength = dYLen; -// else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) -// dLength = dXLen; -// else -// dLength = hypot(dXLen, dYLen); -// -// double const dScaleFactor = dDesiredLength / dLength; -// -// // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) -// CGeom2DPoint EndPt; -// -// if (nHandedness == RIGHT_HANDED) -// { -// EndPt.SetX(PtStart->dGetX() + (dScaleFactor * dYLen)); -// EndPt.SetY(PtStart->dGetY() - (dScaleFactor * dXLen)); -// } -// else -// { -// EndPt.SetX(PtStart->dGetX() - (dScaleFactor * dYLen)); -// EndPt.SetY(PtStart->dGetY() + (dScaleFactor * dXLen)); -// } -// -// return EndPt; -// } - -//=============================================================================================================================== -//! Returns a CGeom2DIPoint (grid CRS) which is the 'other' point of a two-point vector passing through PtiStart, and which is perpendicular to the two-point vector from PtiStart to PtiNext -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiGetPerpendicular(CGeom2DIPoint const* PtiStart, CGeom2DIPoint const* PtiNext, double const dDesiredLength, int const nHandedness) -{ - double const dXLen = PtiNext->nGetX() - PtiStart->nGetX(); - double const dYLen = PtiNext->nGetY() - PtiStart->nGetY(); - double dLength; - - if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) - dLength = dYLen; - - else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) - dLength = dXLen; - - else - dLength = hypot(dXLen, dYLen); - - double const dScaleFactor = dDesiredLength / dLength; - - // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) - CGeom2DIPoint EndPti; - - if (nHandedness == RIGHT_HANDED) - { - EndPti.SetX(PtiStart->nGetX() + nRound(dScaleFactor * dYLen)); - EndPti.SetY(PtiStart->nGetY() - nRound(dScaleFactor * dXLen)); - } - - else - { - EndPti.SetX(PtiStart->nGetX() - nRound(dScaleFactor * dYLen)); - EndPti.SetY(PtiStart->nGetY() + nRound(dScaleFactor * dXLen)); - } - - return EndPti; -} - -//=============================================================================================================================== -//! Returns a CGeom2DIPoint (grid CRS) which is the 'other' point of a two-point vector passing through [nStartX][nStartY], and which is perpendicular to the two-point vector from [nStartX][nStartY] to [nNextX][nNextY] -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiGetPerpendicular(int const nStartX, int const nStartY, int const nNextX, int const nNextY, double const dDesiredLength, int const nHandedness) -{ - double const dXLen = nNextX - nStartX; - double const dYLen = nNextY - nStartY; - double dLength; - - if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) - dLength = dYLen; - - else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) - dLength = dXLen; - - else - dLength = hypot(dXLen, dYLen); - - double const dScaleFactor = dDesiredLength / dLength; - - // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) - CGeom2DIPoint EndPti; - - if (nHandedness == RIGHT_HANDED) - { - EndPti.SetX(nStartX + nRound(dScaleFactor * dYLen)); - EndPti.SetY(nStartY - nRound(dScaleFactor * dXLen)); - } - - else - { - EndPti.SetX(nStartX - nRound(dScaleFactor * dYLen)); - EndPti.SetY(nStartY + nRound(dScaleFactor * dXLen)); - } - - return EndPti; -} - -//=============================================================================================================================== -//! Returns the signed angle BAC (in radians) subtended between three CGeom2DIPoints B A C. From http://stackoverflow.com/questions/3057448/angle-between-3-vertices -//=============================================================================================================================== -double CSimulation::dAngleSubtended(CGeom2DIPoint const* pPtiA, CGeom2DIPoint const* pPtiB, CGeom2DIPoint const* pPtiC) -{ - double const dXDistBtoA = pPtiB->nGetX() - pPtiA->nGetX(); - double const dYDistBtoA = pPtiB->nGetY() - pPtiA->nGetY(); - double const dXDistCtoA = pPtiC->nGetX() - pPtiA->nGetX(); - double const dYDistCtoA = pPtiC->nGetY() - pPtiA->nGetY(); - double const dDotProduct = dXDistBtoA * dXDistCtoA + dYDistBtoA * dYDistCtoA; - double const dPseudoCrossProduct = dXDistBtoA * dYDistCtoA - dYDistBtoA * dXDistCtoA; - double const dAngle = atan2(dPseudoCrossProduct, dDotProduct); - - return dAngle; -} - -//=============================================================================================================================== -//! Checks whether the selected raster GDAL driver supports file creation, 32-bit doubles, etc. -//=============================================================================================================================== -bool CSimulation::bCheckRasterGISOutputFormat(void) -{ - // Register all available GDAL raster and vector drivers (GDAL 2) - GDALAllRegister(); - - // If the user hasn't specified a GIS output format, assume that we will use the same GIS format as the input basement DEM - if (m_strRasterGISOutFormat.empty()) - m_strRasterGISOutFormat = m_strGDALBasementDEMDriverCode; - - // Load the raster GDAL driver - GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName(m_strRasterGISOutFormat.c_str()); - - if (NULL == pDriver) - { - // Can't load raster GDAL driver. Incorrectly specified? - cerr << ERR << "Unknown raster GIS output format '" << m_strRasterGISOutFormat << "'." << endl; - return false; - } - - // Get the metadata for this raster driver - char **papszMetadata = pDriver->GetMetadata(); - - // for (int i = 0; papszMetadata[i] != NULL; i++) - // cout << papszMetadata[i] << endl; - // cout << endl; - - // Need to test if this is a raster driver - if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_RASTER, false)) - { - // This is not a raster driver - cerr << ERR << "GDAL driver '" << m_strRasterGISOutFormat << "' is not a raster driver. Choose another format." << endl; - return false; - } - - // This driver is OK, so store its longname and the default file extension - string strTmp = CSLFetchNameValue(papszMetadata, "DMD_LONGNAME"); - m_strGDALRasterOutputDriverLongname = strTrim(&strTmp); - strTmp = CSLFetchNameValue(papszMetadata, "DMD_EXTENSIONS"); // Note DMD_EXTENSION (no S, is a single value) appears not to be implemented for newer drivers - strTmp = strTrim(&strTmp); - - // We have a space-separated list of one or more file extensions: use the first extension in the list - long unsigned int const nPos = strTmp.find(SPACE); - - if (nPos == string::npos) - { - // No space i.e. just one extension - m_strGDALRasterOutputDriverExtension = strTmp; - } - - else - { - // There's a space, so we must have more than one extension - m_strGDALRasterOutputDriverExtension = strTmp.substr(0, nPos); - } - - // Set up any defaults for raster files that are created using this driver - SetRasterFileCreationDefaults(); - - // Now do various tests of the driver's capabilities - if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, false)) - { - // This raster driver does not support the Create() method, does it support CreateCopy()? - if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATECOPY, false)) - { - cerr << ERR << "Cannot write using raster GDAL driver '" << m_strRasterGISOutFormat << " since neither Create() or CreateCopy() are supported'. Choose another GDAL raster format." << endl; - return false; - } - - // Can't use Create() but can use CreateCopy() - m_bGDALCanCreate = false; - } - - // Next, test to see what data types the driver can write and from this, work out the largest int and float we can write - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Float")) - { - m_bGDALCanWriteFloat = true; - m_GDALWriteFloatDataType = GDT_Float32; - } - - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "UInt32")) - { - m_bGDALCanWriteInt32 = true; - - m_GDALWriteIntDataType = GDT_UInt32; - m_lGDALMaxCanWrite = UINT32_MAX; - m_lGDALMinCanWrite = 0; - - if (! m_bGDALCanWriteFloat) - m_GDALWriteFloatDataType = GDT_UInt32; - - return true; - } - - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Int32")) - { - m_bGDALCanWriteInt32 = true; - - m_GDALWriteIntDataType = GDT_Int32; - m_lGDALMaxCanWrite = INT32_MAX; - m_lGDALMinCanWrite = INT32_MIN; - - if (! m_bGDALCanWriteFloat) - m_GDALWriteFloatDataType = GDT_Int32; - - return true; - } - - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "UInt16")) - { - m_bGDALCanWriteInt32 = false; - - m_GDALWriteIntDataType = GDT_UInt16; - m_lGDALMaxCanWrite = UINT16_MAX; - m_lGDALMinCanWrite = 0; - - if (! m_bGDALCanWriteFloat) - m_GDALWriteFloatDataType = GDT_UInt16; - - return true; - } - - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Int16")) - { - m_bGDALCanWriteInt32 = false; - - m_GDALWriteIntDataType = GDT_Int16; - m_lGDALMaxCanWrite = INT16_MAX; - m_lGDALMinCanWrite = INT16_MIN; - - if (! m_bGDALCanWriteFloat) - m_GDALWriteFloatDataType = GDT_Int16; - - return true; - } - - if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Byte")) - { - m_bGDALCanWriteInt32 = false; - - m_GDALWriteIntDataType = GDT_Byte; - m_lGDALMaxCanWrite = UINT8_MAX; - m_lGDALMinCanWrite = 0; - - if (! m_bGDALCanWriteFloat) - m_GDALWriteFloatDataType = GDT_Byte; - - return true; - } - - // This driver does not even support byte output - cerr << ERR << "Cannot write using raster GDAL driver '" << m_strRasterGISOutFormat << ", not even byte output is supported'. Choose another GIS raster format." << endl; - return false; -} - -//=============================================================================================================================== -//! Checks whether the selected vector GDAL/OGR driver supports file creation etc. -//=============================================================================================================================== -bool CSimulation::bCheckVectorGISOutputFormat(void) -{ - // Load the vector GDAL driver (this assumes that GDALAllRegister() has already been called) - GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName(m_strVectorGISOutFormat.c_str()); - - if (NULL == pDriver) - { - // Can't load vector GDAL driver. Incorrectly specified? - cerr << ERR << "Unknown vector GIS output format '" << m_strVectorGISOutFormat << "'." << endl; - return false; - } - - // Get the metadata for this vector driver - char **papszMetadata = pDriver->GetMetadata(); - - // For GDAL2, need to test if this is a vector driver - if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_VECTOR, false)) - { - // This is not a vector driver - cerr << ERR << "GDAL driver '" << m_strVectorGISOutFormat << "' is not a vector driver. Choose another format." << endl; - return false; - } - - if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, false)) - { - // Driver does not support create() method - cerr << ERR << "Cannot write vector GIS files using GDAL driver '" << m_strRasterGISOutFormat << "'. Choose another format." << endl; - return false; - } - - // Driver is OK, now set some options for individual drivers - if (m_strVectorGISOutFormat == "ESRI Shapefile") - { - // Set this, so that just a single dataset-with-one-layer shapefile is created, rather than a directory (see http://www.gdal.org/ogr/drv_shapefile.html) - m_strOGRVectorOutputExtension = ".shp"; - } - - else if (m_strVectorGISOutFormat == "geojson") - { - m_strOGRVectorOutputExtension = ".geojson"; - } - - else if (m_strVectorGISOutFormat == "gpkg") - { - m_strOGRVectorOutputExtension = ".gpkg"; - } - - // TODO 033 Others - - return true; -} - -//=============================================================================================================================== -//! The bSaveAllRasterGISFiles member function saves the raster GIS files using values from the RasterGrid array -//=============================================================================================================================== -bool CSimulation::bSaveAllRasterGISFiles(void) -{ - // Increment file number - m_nGISSave++; - - // Set for next save - if (m_bSaveRegular) - { - m_dRegularSaveTime += m_dRegularSaveInterval; - } - else - { - if (m_nThisSave < m_nUSave - 1) - { - // Still have user-defined save times remaining - m_nThisSave++; - } - else - { - // Finished user-defined times, switch to regular interval using last value as interval - double dLastInterval; - - if (m_nUSave > 1) - dLastInterval = m_dUSaveTime[m_nUSave - 1] - m_dUSaveTime[m_nUSave - 2]; - else - dLastInterval = m_dUSaveTime[m_nUSave - 1]; - - m_dRegularSaveTime = m_dSimElapsed + dLastInterval; - m_dRegularSaveInterval = dLastInterval; - m_bSaveRegular = true; - } - } - - if (m_bSedimentTopSurfSave) - if (! bWriteRasterGISFile(RASTER_PLOT_SEDIMENT_TOP_ELEVATION, &RASTER_PLOT_SEDIMENT_TOP_ELEVATION_TITLE)) - return false; - - if (m_bTopSurfSave) - if (! bWriteRasterGISFile(RASTER_PLOT_OVERALL_TOP_ELEVATION, &RASTER_PLOT_OVERALL_TOP_ELEVATION_TITLE)) - return false; - - if (m_bSlopeConsSedSave) - if (! bWriteRasterGISFile(RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT_TITLE)) - return false; - - if (m_bSlopeSaveForCliffToe) - if (! bWriteRasterGISFile(RASTER_PLOT_SLOPE_FOR_CLIFF_TOE, &RASTER_PLOT_SLOPE_FOR_CLIFF_TOE_TITLE)) - return false; - - if (m_bCliffToeSave) - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_TOE, &RASTER_PLOT_CLIFF_TOE_TITLE)) - return false; - - if (m_bSeaDepthSave) - if (! bWriteRasterGISFile(RASTER_PLOT_SEA_DEPTH, &RASTER_PLOT_SEA_DEPTH_TITLE)) - return false; - - if (m_bWaveHeightSave) - if (! bWriteRasterGISFile(RASTER_PLOT_WAVE_HEIGHT, &RASTER_PLOT_WAVE_HEIGHT_TITLE)) - return false; - - if (m_bWaveAngleSave) - if (! bWriteRasterGISFile(RASTER_PLOT_WAVE_ORIENTATION, &RASTER_PLOT_WAVE_ORIENTATION_TITLE)) - return false; - - // Don't write platform erosion files if there is no platform erosion - if (m_bDoShorePlatformErosion) - { - if (m_bPotentialPlatformErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_PLATFORM_EROSION, &RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_TITLE)) - return false; - - if (m_bActualPlatformErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_ACTUAL_PLATFORM_EROSION, &RASTER_PLOT_ACTUAL_PLATFORM_EROSION_TITLE)) - return false; - - if (m_bTotalPotentialPlatformErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION, &RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION_TITLE)) - return false; - - if (m_bTotalActualPlatformErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION, &RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION_TITLE)) - return false; - - if (m_bPotentialPlatformErosionMaskSave) - if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK, &RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK_TITLE)) - return false; - - if (m_bBeachProtectionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_PROTECTION, &RASTER_PLOT_BEACH_PROTECTION_TITLE)) - return false; - - if (m_bPotentialBeachErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_BEACH_EROSION, &RASTER_PLOT_POTENTIAL_BEACH_EROSION_TITLE)) - return false; - - if (m_bActualBeachErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_ACTUAL_BEACH_EROSION, &RASTER_PLOT_ACTUAL_BEACH_EROSION_TITLE)) - return false; - - if (m_bTotalPotentialBeachErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION, &RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION_TITLE)) - return false; - - if (m_bTotalActualBeachErosionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION, &RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION_TITLE)) - return false; - - if (m_bBeachDepositionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_DEPOSITION, &RASTER_PLOT_BEACH_DEPOSITION_TITLE)) - return false; - - if (m_bTotalBeachDepositionSave) - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_BEACH_DEPOSITION, &RASTER_PLOT_TOTAL_BEACH_DEPOSITION_TITLE)) - return false; - } - - if (m_bLandformSave) - if (! bWriteRasterGISFile(RASTER_PLOT_LANDFORM, &RASTER_PLOT_LANDFORM_TITLE)) - return false; - - if (m_bAvgWaveHeightSave) - if (! bWriteRasterGISFile(RASTER_PLOT_AVG_WAVE_HEIGHT, &RASTER_PLOT_AVG_WAVE_HEIGHT_TITLE)) - return false; - - if (m_bAvgWaveAngleSave) - if (! bWriteRasterGISFile(RASTER_PLOT_AVG_WAVE_ORIENTATION, &RASTER_PLOT_AVG_WAVE_ORIENTATION_TITLE)) - return false; - - if (m_bAvgSeaDepthSave) - if (! bWriteRasterGISFile(RASTER_PLOT_AVG_SEA_DEPTH, &RASTER_PLOT_AVG_SEA_DEPTH_TITLE)) - return false; - - if (m_bSedimentInput && m_bSedimentInputEventSave) - if (! bWriteRasterGISFile(RASTER_PLOT_SEDIMENT_INPUT, &RASTER_PLOT_SEDIMENT_INPUT_EVENT_TITLE)) - return false; - - // Don't write suspended sediment files if there is no fine sediment - if (m_bHaveFineSediment) - { - if (m_bSuspSedSave) - if (! bWriteRasterGISFile(RASTER_PLOT_SUSPENDED_SEDIMENT, &RASTER_PLOT_SUSPENDED_SEDIMENT_TITLE)) - return false; - - if (m_bAvgSuspSedSave) - if (! bWriteRasterGISFile(RASTER_PLOT_AVG_SUSPENDED_SEDIMENT, &RASTER_PLOT_AVG_SUSPENDED_SEDIMENT_TITLE)) - return false; - } - - if (m_bBasementElevSave) - if (! bWriteRasterGISFile(RASTER_PLOT_BASEMENT_ELEVATION, &RASTER_PLOT_BASEMENT_ELEVATION_TITLE)) - return false; - - for (int nLayer = 0; nLayer < m_nLayers; nLayer++) - { - if (m_bHaveFineSediment && m_bFineUnconsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - - if (m_bHaveSandSediment && m_bSandUnconsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - - if (m_bHaveCoarseSediment && m_bCoarseUnconsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - - if (m_bHaveFineSediment && m_bFineConsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - - if (m_bHaveSandSediment && m_bSandConsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - - if (m_bHaveCoarseSediment && m_bCoarseConsSedSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) - return false; - } - } - - if (m_bSliceSave) - { - for (int i = 0; i < static_cast(m_VdSliceElev.size()); i++) - { - if (! bWriteRasterGISFile(RASTER_PLOT_SLICE, &RASTER_PLOT_SLICE_TITLE, 0, m_VdSliceElev[i])) - return false; - } - } - - if (m_bRasterCoastlineSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_COAST, &RASTER_PLOT_COAST_TITLE)) - return false; - } - - if (m_bRasterNormalProfileSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_NORMAL_PROFILE, &RASTER_PLOT_NORMAL_PROFILE_TITLE)) - return false; - } - - if (m_bActiveZoneSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_ACTIVE_ZONE, &RASTER_PLOT_ACTIVE_ZONE_TITLE)) - return false; - } - - // Don't write cliff collapse files if we aren't considering cliff collapse - if (m_bDoCliffCollapse) - { - if (m_bCliffCollapseSave) - { - if (m_bHaveFineSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE_TITLE)) - return false; - } - - if (m_bHaveSandSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND_TITLE)) - return false; - } - - if (m_bHaveCoarseSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE_TITLE)) - return false; - } - - if (m_bCliffNotchAllSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_NOTCH_ALL, &RASTER_PLOT_CLIFF_NOTCH_ALL_TITLE)) - return false; - } - - if (m_bCliffCollapseTimestepSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP, &RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP_TITLE)) - return false; - } - } - - if (m_bTotCliffCollapseSave) - { - if (m_bHaveFineSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE_TITLE)) - return false; - } - - if (m_bHaveSandSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND_TITLE)) - return false; - } - - if (m_bHaveCoarseSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE_TITLE)) - return false; - } - } - - if (m_bCliffCollapseDepositionSave) - { - if (m_bHaveSandSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND, &RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND_TITLE)) - return false; - } - - if (m_bHaveCoarseSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE, &RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE_TITLE)) - return false; - } - } - - if (m_bTotCliffCollapseDepositionSave) - { - if (m_bHaveSandSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND_TITLE)) - return false; - } - - if (m_bHaveCoarseSediment) - { - if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE_TITLE)) - return false; - } - } - } - - if (m_bRasterPolygonSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON, &RASTER_PLOT_POLYGON_TITLE)) - return false; - } - - if (m_bSeaMaskSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_INUNDATION_MASK, &RASTER_PLOT_INUNDATION_MASK_TITLE)) - return false; - } - - if (m_bBeachMaskSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_MASK, &RASTER_PLOT_BEACH_MASK_TITLE)) - return false; - } - - if (m_bInterventionClassSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_INTERVENTION_CLASS, &RASTER_PLOT_INTERVENTION_CLASS_TITLE)) - return false; - } - - if (m_bInterventionHeightSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_INTERVENTION_HEIGHT, &RASTER_PLOT_INTERVENTION_HEIGHT_TITLE)) - return false; - } - - if (m_bShadowZoneCodesSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_SHADOW_ZONE, &RASTER_PLOT_SHADOW_ZONE_TITLE)) - return false; - - if (! bWriteRasterGISFile(RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE, &RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE_TITLE)) - return false; - } - - if (m_bDeepWaterWaveAngleSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION, &RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION_TITLE)) - return false; - } - - if (m_bDeepWaterWaveHeightSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT, &RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT_TITLE)) - return false; - } - - if (m_bDeepWaterWavePeriodSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_PERIOD, &RASTER_PLOT_DEEP_WATER_WAVE_PERIOD_TITLE)) - return false; - } - - if (m_bPolygonUnconsSedUpOrDownDriftSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT, &RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT_TITLE)) - return false; - } - - if (m_bPolygonUnconsSedGainOrLossSave) - { - if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON_GAIN_OR_LOSS, &RASTER_PLOT_POLYGON_GAIN_OR_LOSS_TITLE)) - return false; - } - - // if (m_bSetupSurgeFloodMaskSave) - // { - // if (! bWriteRasterGISFile(RASTER_PLOT_SETUP_SURGE_FLOOD_MASK, &RASTER_PLOT_SETUP_SURGE_FLOOD_MASK_TITLE)) - // return false; - // } - - // if (m_bSetupSurgeRunupFloodMaskSave) - // { - // if (! bWriteRasterGISFile(RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK, &RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK_TITLE)) - // return false; - // } - // - return true; -} - -//=============================================================================================================================== -//! The bSaveAllvectorGISFiles member function saves the vector GIS files TODO 081 Choose more files to omit from "usual" vector output -//=============================================================================================================================== -bool CSimulation::bSaveAllVectorGISFiles(void) -{ - // Always written - if (m_bCoastSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_COAST, &VECTOR_PLOT_COAST_TITLE)) - return false; - - if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_SWL_HIGHEST, &VECTOR_PLOT_COAST_SWL_HIGHEST_TITLE)) - return false; - - if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_SWL_LOWEST, &VECTOR_PLOT_COAST_SWL_LOWEST_TITLE)) - return false; - } - - if (m_bCliffEdgeSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_CLIFF_EDGE, &VECTOR_PLOT_CLIFF_EDGE_TITLE)) - return false; - } - - if (m_bNormalsSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_NORMALS, &VECTOR_PLOT_NORMALS_TITLE)) - return false; - } - - if (m_bInvalidNormalsSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_INVALID_NORMALS, &VECTOR_PLOT_INVALID_NORMALS_TITLE)) - return false; - } - - if (m_bCoastCurvatureSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_CURVATURE, &VECTOR_PLOT_COAST_CURVATURE_TITLE)) - return false; - } - - if (m_bWaveAngleAndHeightSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_WAVE_ANGLE_AND_HEIGHT_TITLE)) - return false; - } - - if (m_bAvgWaveAngleAndHeightSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_AVG_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_AVG_WAVE_ANGLE_AND_HEIGHT_TITLE)) - return false; - } - - if (m_bWaveEnergySinceCollapseSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_ENERGY_SINCE_COLLAPSE, &VECTOR_PLOT_WAVE_ENERGY_SINCE_COLLAPSE_TITLE)) - return false; - } - - if (m_bMeanWaveEnergySave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_MEAN_WAVE_ENERGY, &VECTOR_PLOT_MEAN_WAVE_ENERGY_TITLE)) - return false; - } - - if (m_bBreakingWaveHeightSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_BREAKING_WAVE_HEIGHT, &VECTOR_PLOT_BREAKING_WAVE_HEIGHT_TITLE)) - return false; - } - - if (m_bPolygonNodeSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_NODES, &VECTOR_PLOT_POLYGON_NODES_TITLE)) - return false; - } - - if (m_bPolygonBoundarySave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_BOUNDARY, &VECTOR_PLOT_POLYGON_BOUNDARY_TITLE)) - return false; - } - - if (m_bCliffNotchSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_CLIFF_NOTCH_ACTIVE, &VECTOR_PLOT_CLIFF_NOTCH_ACTIVE_TITLE)) - return false; - } - - if (m_bWaveTransectPointsSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_TRANSECT_POINTS, &VECTOR_PLOT_WAVE_TRANSECT_POINTS_TITLE)) - return false; - } - - if (m_bShadowBoundarySave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_SHADOW_ZONE_BOUNDARY, &VECTOR_PLOT_SHADOW_ZONE_BOUNDARY_TITLE)) - return false; - } - - if (m_bShadowDowndriftBoundarySave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_DOWNDRIFT_ZONE_BOUNDARY, &VECTOR_PLOT_DOWNDRIFT_ZONE_BOUNDARY_TITLE)) - return false; - } - - if (m_bDeepWaterWaveAngleAndHeightSave) - { - if (! bWriteVectorGISFile(VECTOR_PLOT_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT_TITLE)) - return false; - } - - // if (m_bWaveSetupSave) - // { - // if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_SETUP, &VECTOR_PLOT_WAVE_SETUP_TITLE)) - // return false; - // } - // - // if (m_bStormSurgeSave) - // { - // if (! bWriteVectorGISFile(VECTOR_PLOT_STORM_SURGE, &VECTOR_PLOT_STORM_SURGE_TITLE)) - // return false; - // } - // - // if (m_bRunUpSave) - // { - // if (! bWriteVectorGISFile(VECTOR_PLOT_RUN_UP, &VECTOR_PLOT_RUN_UP_TITLE)) - // return false; - // } - // - // if (m_bRiverineFlooding && m_bVectorWaveFloodLineSave) - // { - // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_LINE, &VECTOR_PLOT_FLOOD_SWL_SETUP_LINE_TITLE)) - // return false; - // - // // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_LINE, - // // &VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_LINE_TITLE)) return false; - // - // // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_RUNUP_LINE, - // // &VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_RUNUP_LINE_TITLE)) return false; - // } - - return true; -} - -//=============================================================================================================================== -//! Finds the max and min values in order to scale raster output if we cannot write doubles -//=============================================================================================================================== -void CSimulation::GetRasterOutputMinMax(int const nDataItem, double& dMin, double& dMax, int const nLayer, double const dElev) -{ - // If this is a binary mask layer, we already know the max and min values - if ((nDataItem == RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK) || - (nDataItem == RASTER_PLOT_INUNDATION_MASK) || - (nDataItem == RASTER_PLOT_BEACH_MASK) || - (nDataItem == RASTER_PLOT_COAST) || - (nDataItem == RASTER_PLOT_NORMAL_PROFILE) || - (nDataItem == RASTER_PLOT_ACTIVE_ZONE) || - (nDataItem == RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT) || - (nDataItem == RASTER_PLOT_SETUP_SURGE_FLOOD_MASK) || - (nDataItem == RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK) || - (nDataItem == RASTER_PLOT_WAVE_FLOOD_LINE)) - { - dMin = 0; - dMax = 1; - - return; - } - - // Not a binary mask layer, so we must find the max and min values - dMin = DBL_MAX; - dMax = DBL_MIN; - - double dTmp = 0; - - for (int nY = 0; nY < m_nYGridSize; nY++) - { - for (int nX = 0; nX < m_nXGridSize; nX++) - { - switch (nDataItem) - { - case (RASTER_PLOT_SLICE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetLayerAtElev(dElev); - break; - - case (RASTER_PLOT_LANDFORM): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory(); - break; - - case (RASTER_PLOT_INTERVENTION_CLASS): - dTmp = INT_NODATA; - - if (bIsInterventionCell(nX, nY)) - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFSubCategory(); - - break; - - case (RASTER_PLOT_INTERVENTION_HEIGHT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetInterventionHeight(); - break; - - case (RASTER_PLOT_POLYGON): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - break; - - case (RASTER_PLOT_BASEMENT_ELEVATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBasementElev(); - break; - - case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev(); - break; - - case (RASTER_PLOT_OVERALL_TOP_ELEVATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetOverallTopElev(); - break; - - case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetConsSedSlope(); - break; - - case (RASTER_PLOT_SEA_DEPTH): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); - break; - - case (RASTER_PLOT_AVG_SEA_DEPTH): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSeaDepth() / static_cast(m_ulIter); - break; - - case (RASTER_PLOT_WAVE_HEIGHT): - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) - dTmp = m_dMissingValue; - else - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); - - break; - - case (RASTER_PLOT_AVG_WAVE_HEIGHT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotWaveHeight() / static_cast(m_ulIter); - break; - - case (RASTER_PLOT_WAVE_ORIENTATION): - if (! m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) - dTmp = m_dMissingValue; - - else - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); - - break; - - case (RASTER_PLOT_AVG_WAVE_ORIENTATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotWaveAngle() / static_cast(m_ulIter); - break; - - case (RASTER_PLOT_BEACH_PROTECTION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBeachProtectionFactor(); - - if (! bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) - dTmp = 1 - dTmp; // Output the inverse, seems more intuitive - - break; - - case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetPotentialPlatformErosion(); - break; - - case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetActualPlatformErosion(); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotPotentialPlatformErosion(); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotActualPlatformErosion(); - break; - - case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetPotentialBeachErosion(); - break; - - case (RASTER_PLOT_ACTUAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetActualBeachErosion(); - break; - - case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotPotentialBeachErosion(); - break; - - case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotActualBeachErosion(); - break; - - case (RASTER_PLOT_BEACH_DEPOSITION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetBeachDeposition(); - break; - - case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotBeachDeposition(); - break; - - case (RASTER_PLOT_SUSPENDED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); - break; - - case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSuspendedSediment() / - static_cast(m_ulIter); - break; - - case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); - break; - - case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); - break; - - case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); - break; - - case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetFineDepth(); - break; - - case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetSandDepth(); - break; - - case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionFine(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionSand(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseErosionCoarse(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseFine(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseSand(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCliffCollapseCoarse(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseSandTalusDeposition(); - break; - - case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetThisIterCliffCollapseCoarseTalusDeposition(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotSandTalusDeposition(); - break; - - case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].dGetTotCoarseTalusDeposition(); - break; - - case (RASTER_PLOT_SHADOW_ZONE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetShadowZoneNumber(); - break; - - case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); - break; - - case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): - dTmp = m_pRasterGrid->m_Cell[nX][nY].nGetDownDriftZoneNumber(); - break; - - case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): - int const nPoly = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - int const nPolyCoast = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonCoastID(); - - if (nPoly == INT_NODATA) - dTmp = m_dMissingValue; - else - dTmp = m_VCoast[nPolyCoast].pGetPolygon(nPoly)->dGetBeachDepositionAndSuspensionAllUncons(); - - break; - } - - if (! bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) - { - if (dTmp > dMax) - dMax = dTmp; - - if (dTmp < dMin) - dMin = dTmp; - } - } - } -} - -//=============================================================================================================================== -//! Sets per-driver defaults for raster files created using GDAL -//=============================================================================================================================== -void CSimulation::SetRasterFileCreationDefaults(void) -{ - string const strDriver = strToLower(&m_strRasterGISOutFormat); - string const strComment = "Created by " + PROGRAM_NAME + " for " + PLATFORM + " " + strGetBuild() + " running on " + strGetComputerName(); - - // TODO 034 Do these for all commonly-used file types - if (strDriver == "aaigrid") - { - } - else if (strDriver == "bmp") - { - } - else if (strDriver == "gtiff") - { - if (m_bWorldFile) - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "TFW", "YES"); - - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NUM_THREADS", "ALL_CPUS"); - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMPRESS", "LZW"); - } - else if (strDriver == "hfa") - { - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NBITS", "4"); - } - else if (strDriver == "jpeg") - { - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "QUALITY", "95"); - } - else if (strDriver == "png") - { - if (m_bWorldFile) - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "WORLDFILE", "YES"); - - // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "TITLE", "This is the title"); m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "DESCRIPTION", "This is a description"); - // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COPYRIGHT", "This is some copyright statement"); - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NBITS", "4"); - } - else if (strDriver == "rst") - { - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); - } - else if (strDriver == "geojson") - { - // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); - } - else if (strDriver == "gpkg") - { - // TODO 065 Does GDAL support overwriting raster gpkg files yet? - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "OVERWRITE", "YES"); - m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "USE_TILE_EXTENT", "YES"); - } - else if (strDriver == "netcdf") - { - } -} - -//=============================================================================================================================== -//! Returns the opposite direction -//=============================================================================================================================== -int CSimulation::nGetOppositeDirection(int const nDirection) -{ - switch (nDirection) - { - case NORTH: - return SOUTH; - - case NORTH_EAST: - return SOUTH - WEST; - - case EAST: - return WEST; - - case SOUTH_EAST: - return NORTH - WEST; - - case SOUTH: - return NORTH; - - case SOUTH_WEST: - return NORTH - EAST; - - case WEST: - return EAST; - - case NORTH_WEST: - return SOUTH - EAST; - } - - // Should never get here - return NO_DIRECTION; -} - -// //=============================================================================================================================== -// //! Given two integer points, calculates the slope and intercept of the line passing through the points -// //=============================================================================================================================== -// void CSimulation::GetSlopeAndInterceptFromPoints(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2, double& dSlope, double& dIntercept) -// { -// int nX1 = pPti1->nGetX(); -// int nY1 = pPti1->nGetY(); -// int nX2 = pPti2->nGetX(); -// int nY2 = pPti2->nGetY(); -// -// double dXDiff = nX1 - nX2; -// double dYDiff = nY1 - nY2; -// -// if (bFPIsEqual(dXDiff, 0.0, TOLERANCE)) -// dSlope = 0; -// else -// dSlope = dYDiff / dXDiff; -// -// dIntercept = nY1 - (dSlope * nX1); -// } - -//=============================================================================================================================== -//! Finds the closest point on any coastline to a given point -//=============================================================================================================================== -CGeom2DIPoint CSimulation::PtiFindClosestCoastPoint(int const nX, int const nY) -{ - unsigned int nMinSqDist = UINT_MAX; - CGeom2DIPoint PtiCoastPoint; - - // Do for every coast - for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) - { - for (int j = 0; j < m_VCoast[nCoast].nGetCoastlineSize(); j++) - { - // Get the coords of the grid cell marked as coastline for the coastal landform object - int const nXCoast = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(j)->nGetX(); - int const nYCoast = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(j)->nGetY(); - - // Calculate the squared distance between this point and the given point - int const nXDist = nX - nXCoast; - int const nYDist = nY - nYCoast; - - unsigned int const nSqDist = (nXDist * nXDist) + (nYDist * nYDist); - - // Is this the closest so dar? - if (nSqDist < nMinSqDist) - { - nMinSqDist = nSqDist; - PtiCoastPoint.SetXY(nXCoast, nYCoast); - } - } - } - - return PtiCoastPoint; -} - -//=============================================================================================================================== -//! Given a length in m, this returns the rounded equivalent number of cells -//=============================================================================================================================== -int CSimulation::nConvertMetresToNumCells(double const dLen) const -{ - return nRound(dLen / m_dCellSide); -} - -//=============================================================================================================================== -//! Given a line between two points and another point, this finds the closest point on the line to the other point. From https://cboard.cprogramming.com/c-programming/155809-find-closest-point-line.html -//=============================================================================================================================== -void CSimulation::GetClosestPoint(double const dAx, double const dAy, double const dBx, double const dBy, double const dPx, double const dPy, double& dXRet, double& dYRet) -{ - double const dAPx = dPx - dAx; - double const dAPy = dPy - dAy; - double const dABx = dBx - dAx; - double const dABy = dBy - dAy; - double const dMagAB2 = (dABx * dABx) + (dABy * dABy); - double const dABdotAP = (dABx * dAPx) + (dABy * dAPy); - double const dT = dABdotAP / dMagAB2; - - if ( dT < 0) - { - dXRet = dAx; - dYRet = dAy; - } - else if (dT > 1) - { - dXRet = dBx; - dYRet = dBy; - } - else - { - dXRet = dAx + (dABx * dT); - dYRet = dAy + (dABy * dT); - } -} +/*! + \file gis_utils.cpp + \brief Various GIS-related functions, requires GDAL + \details Note re. coordinate systems used + + 1. In the raster CRS, cell[0][0] is at the top left (NW) corner of the grid. Raster grid co-oordinate [0][0] is actually the top left (NW) corner of this cell. + + 2. We assume that the grid CRS and external CRS have parallel axes. If they have not, see http://www.gdal.org/classGDALDataset.html which says that: + + To convert between pixel/line (P,L) raster space, and projection coordinates (Xp,Yp) space Xp = padfTransform[0] + padfTransform[1] + padfTransform[2]; Yp + = padfTransform[3] + padfTransform[4] + padfTransform[5]; + + In a north-up image, padfTransform[1] is the pixel width, and padfTransform[5] is the pixel height. The upper left corner of the upper left pixel is at position (padfTransform[0], padfTransform[3]). + + 3. Usually, raster grid CRS values are integer, i.e. they refer to a point which is at the centroid of a cell. They may also be -ve or greater than m_nXGridSize-1 i.e. may refer to a point which lies outside any cell of the raster grid. + + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License +*/ + +/* =============================================================================================================================== + + This file is part of CoastalME, the Coastal Modelling Environment. CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +===============================================================================================================================*/ +#include + +#include + +#include +using std::vector; + +#include +using std::cerr; +using std::endl; +using std::ios; + +#include +using std::atan2; + +#include +#include +#include + +#include +using std::strstr; + +#include +#include +#include + +#include "cme.h" +#include "coast.h" +#include "raster_grid.h" +#include "2d_point.h" +#include "2di_point.h" + +//=============================================================================================================================== +//! Given the integer X-axis ordinate of a cell in the raster grid CRS, returns the external CRS X-axis ordinate of the cell's centroid +//=============================================================================================================================== +double CSimulation::dGridCentroidXToExtCRSX(int const nGridX) const +{ + // TODO 064 + return (m_dGeoTransform[0] + (nGridX * m_dGeoTransform[1]) + (m_dGeoTransform[1] / 2)); +} + +//=============================================================================================================================== +//! Given the integer Y-axis ordinate of a cell in the raster grid CRS, returns the external CRS Y-axis ordinate of the cell's centroid +//=============================================================================================================================== +double CSimulation::dGridCentroidYToExtCRSY(int const nGridY) const +{ + // TODO 064 + return (m_dGeoTransform[3] + (nGridY * m_dGeoTransform[5]) + (m_dGeoTransform[5] / 2)); +} + +//=============================================================================================================================== +//! Transforms a pointer to a CGeom2DIPoint in the raster grid CRS (assumed to be the centroid of a cell) to the equivalent CGeom2DPoint in the external CRS +//=============================================================================================================================== +CGeom2DPoint CSimulation::PtGridCentroidToExt(CGeom2DIPoint const *pPtiIn) const +{ + // TODO 064 + double const dX = m_dGeoTransform[0] + (pPtiIn->nGetX() * m_dGeoTransform[1]) + (m_dGeoTransform[1] / 2); + double const dY = m_dGeoTransform[3] + (pPtiIn->nGetY() * m_dGeoTransform[5]) + (m_dGeoTransform[5] / 2); + + return CGeom2DPoint(dX, dY); +} + +//=============================================================================================================================== +//! Given a real-valued X-axis ordinate in the raster grid CRS (i.e. not the centroid of a cell), returns the external CRS X-axis ordinate +//=============================================================================================================================== +double CSimulation::dGridXToExtCRSX(double const dGridX) const +{ + // TODO 064 Xgeo = GT(0) + Xpixel*GT(1) + Yline*GT(2) + return m_dGeoTransform[0] + (dGridX * m_dGeoTransform[1]) - 1; +} + +//=============================================================================================================================== +//! Given a real-valued Y-axis ordinate in the raster grid CRS (i.e. not the centroid of a cell), returns the external CRS Y-axis ordinate +//=============================================================================================================================== +double CSimulation::dGridYToExtCRSY(double const dGridY) const +{ + // TODO 064 Ygeo = GT(3) + Xpixel*GT(4) + Yline*GT(5) + return m_dGeoTransform[3] + (dGridY * m_dGeoTransform[5]) - 1; +} + +//=============================================================================================================================== +//! Transforms an X-axis ordinate in the external CRS to the equivalent X-axis ordinate in the raster grid CRS (the result is not rounded, and so may not be integer, and may be outside the grid) +//=============================================================================================================================== +double CSimulation::dExtCRSXToGridX(double const dExtCRSX) const +{ + // TODO 064 + return ((dExtCRSX - m_dGeoTransform[0]) / m_dGeoTransform[1]) - 1; +} + +//=============================================================================================================================== +//! Transforms a Y-axis ordinate in the external CRS to the equivalent Y-axis ordinate in the raster grid CRS (the result is not rounded, and so may not be integer, and may be outside the grid) +//=============================================================================================================================== +double CSimulation::dExtCRSYToGridY(double const dExtCRSY) const +{ + // TODO 064 + return ((dExtCRSY - m_dGeoTransform[3]) / m_dGeoTransform[5]) - 1; +} + +//=============================================================================================================================== +//! Transforms a pointer to a CGeom2DPoint in the external CRS to the equivalent CGeom2DIPoint in the raster grid CRS (both values rounded). The result may be outside the grid +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiExtCRSToGridRound(CGeom2DPoint const* pPtIn) const +{ + // TODO 064 + int const nX = nRound(((pPtIn->dGetX() - m_dGeoTransform[0]) / m_dGeoTransform[1]) - 1); + int const nY = nRound(((pPtIn->dGetY() - m_dGeoTransform[3]) / m_dGeoTransform[5]) - 1); + + return CGeom2DIPoint(nX, nY); +} + +//=============================================================================================================================== +//! Returns the distance (in external CRS) between two points +//=============================================================================================================================== +double CSimulation::dGetDistanceBetween(CGeom2DPoint const* Pt1, CGeom2DPoint const* Pt2) +{ + double const dXDist = Pt1->dGetX() - Pt2->dGetX(); + double const dYDist = Pt1->dGetY() - Pt2->dGetY(); + + return hypot(dXDist, dYDist); +} + +//=============================================================================================================================== +//! Returns the distance (in external CRS) between two points +//=============================================================================================================================== +double CSimulation::dGetDistanceBetween(CGeom2DIPoint const* Pti1, CGeom2DIPoint const* Pti2) +{ + double const dXDist = Pti1->nGetX() - Pti2->nGetX(); + double const dYDist = Pti1->nGetY() - Pti2->nGetY(); + + return hypot(dXDist, dYDist); +} + +//=============================================================================================================================== +//! Returns the distance (in external CRS) between two points +//=============================================================================================================================== +double CSimulation::dGetDistanceBetween(double const dX1, double const dY1, double const dX2, double const dY2) +{ + double const dXDist = dX1 - dX2; + double const dYDist = dY1 - dY2; + + return hypot(dXDist, dYDist); +} + +//=============================================================================================================================== +//! Returns twice the signed area of a triangle, defined by three points +//=============================================================================================================================== +double CSimulation::dTriangleAreax2(CGeom2DPoint const* pPtA, CGeom2DPoint const* pPtB, CGeom2DPoint const* pPtC) +{ + return (pPtB->dGetX() - pPtA->dGetX()) * (pPtC->dGetY() - pPtA->dGetY()) - (pPtB->dGetY() - pPtA->dGetY()) * (pPtC->dGetX() - pPtA->dGetX()); +} + +//=============================================================================================================================== +//! Checks whether the supplied point (an x-y pair, in the grid CRS) is within the raster grid, and is a valid cell (i.e. the basement DEM is not NODATA) +//=============================================================================================================================== +bool CSimulation::bIsWithinValidGrid(int const nX, int const nY) const +{ + if ((nX < 0) || (nX >= m_nXGridSize)) + return false; + + if ((nY < 0) || (nY >= m_nYGridSize)) + return false; + + if (m_pRasterGrid->Cell(nX, nY).bBasementElevIsMissingValue()) + return false; + + return true; +} + +//=============================================================================================================================== +//! Checks whether the supplied point (a reference to a CGeom2DIPoint, in the grid CRS) is within the raster grid, and is a valid cell (i.e. the basement DEM is not NODATA) +//=============================================================================================================================== +bool CSimulation::bIsWithinValidGrid(CGeom2DIPoint const* Pti) const +{ + int const nX = Pti->nGetX(); + int const nY = Pti->nGetY(); + + return this->bIsWithinValidGrid(nX, nY); +} + +//=============================================================================================================================== +//! Constrains the supplied point (in the grid CRS) to be a valid cell within the raster grid +//=============================================================================================================================== +void CSimulation::KeepWithinValidGrid(int& nX, int& nY) const +{ + nX = tMax(nX, 0); + nX = tMin(nX, m_nXGridSize - 1); + + nY = tMax(nY, 0); + nY = tMin(nY, m_nYGridSize - 1); +} + +//=============================================================================================================================== +//! Constrains the second supplied point (both are CGeom2DIPoints, in the grid CRS) to be a valid cell within the raster grid +//=============================================================================================================================== +void CSimulation::KeepWithinValidGrid(CGeom2DIPoint const* Pti0, CGeom2DIPoint* Pti1) const +{ + KeepWithinValidGrid(Pti0->nGetX(), Pti0->nGetY(), *Pti1->pnGetX(), *Pti1->pnGetY()); +} + +//=============================================================================================================================== +//! Given two points in the grid CRS (the points assumed not to be coincident), this routine modifies the value of the second point so that it is on a line joining the original two points and is a valid cell within the raster grid. However in some cases (e.g. if the first point is at the edge of the valid part of the raster grid) then the second cell will be coincident with the +//! first cell, and the line joining them is thus of zero length. The calling routine has to be able to handle this +//=============================================================================================================================== +void CSimulation::KeepWithinValidGrid(int nX0, int nY0, int& nX1, int& nY1) const +{ + // Safety check: make sure that the first point is within the valid grid + if (nX0 >= m_nXGridSize) + nX0 = m_nXGridSize - 1; + + else if (nX0 < 0) + nX0 = 0; + + if (nY0 >= m_nYGridSize) + nY0 = m_nYGridSize - 1; + + else if (nY0 < 0) + nY0 = 0; + + // OK let's go + int const nDiffX = nX0 - nX1; + int const nDiffY = nY0 - nY1; + + if (nDiffX == 0) + { + // The two points have the same x coordinates, so we just need to constrain the y co-ord + if (nY1 < nY0) + { + nY1 = -1; + + do + { + nY1++; + } while (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue()); + + return; + } + else + { + nY1 = m_nYGridSize; + + do + { + nY1--; + } while (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue()); + + return; + } + } + else if (nDiffY == 0) + { + // The two points have the same y coordinates, so we just need to constrain the x co-ord + if (nX1 < nX0) + { + nX1 = -1; + + do + { + nX1++; + } while (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue()); + + return; + } + else + { + nX1 = m_nXGridSize; + + do + { + nX1--; + } while (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue()); + + return; + } + } + else + { + // The two points have different x coordinates and different y coordinates, so we have to work harder. First find which of the coordinates is the greatest distance outside the grid, and constrain that co-ord for efficiency (since this will reduce the number of times round the loop). Note that both may be inside the grid, if the incorrect co-ord is in the invalid margin, in which case arbitrarily contrain the x co-ord + int nXDistanceOutside = 0; + int nYDistanceOutside = 0; + + if (nX1 < 0) + nXDistanceOutside = -nX1; + else if (nX1 >= m_nXGridSize) + nXDistanceOutside = nX1 - m_nXGridSize + 1; + + if (nY1 < 0) + nYDistanceOutside = -nY1; + else if (nY1 >= m_nYGridSize) + nXDistanceOutside = nY1 - m_nYGridSize + 1; + + if (nXDistanceOutside >= nYDistanceOutside) + { + // Constrain the x co-ord + if (nX1 < nX0) + { + // The incorrect x co-ord is less than the correct x co-ord: constrain it and find the y co-ord + nX1 = -1; + + do + { + nX1++; + + nY1 = nY0 + nRound(((nX1 - nX0) * nDiffY) / static_cast(nDiffX)); + } while ((nY1 < 0) || (nY1 >= m_nYGridSize) || (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue())); + + return; + } + else + { + // The incorrect x co-ord is greater than the correct x-co-ord: constrain it and find the y co-ord + nX1 = m_nXGridSize; + + do + { + nX1--; + + nY1 = nY0 + nRound(((nX1 - nX0) * nDiffY) / static_cast(nDiffX)); + } while ((nY1 < 0) || (nY1 >= m_nYGridSize) || (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue())); + + return; + } + } + else + { + // Constrain the y co-ord + if (nY1 < nY0) + { + // The incorrect y co-ord is less than the correct y-co-ord: constrain it and find the x co-ord + nY1 = -1; + + do + { + nY1++; + + nX1 = nX0 + nRound(((nY1 - nY0) * nDiffX) / static_cast(nDiffY)); + } while ((nX1 < 0) || (nX1 >= m_nXGridSize) || (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue())); + + return; + } + else + { + // The incorrect y co-ord is greater than the correct y co-ord: constrain it and find the x co-ord + nY1 = m_nYGridSize; + + do + { + nY1--; + + nX1 = nX0 + + nRound(((nY1 - nY0) * nDiffX) / static_cast(nDiffY)); + } while ((nX1 < 0) || (nX1 >= m_nXGridSize) || (m_pRasterGrid->Cell(nX1, nY1).bBasementElevIsMissingValue())); + + return; + } + } + } +} + +//=============================================================================================================================== +//! Constrains the supplied angle to be within 0 and 360 degrees +//=============================================================================================================================== +double CSimulation::dKeepWithin360(double const dAngle) +{ + double dNewAngle = dAngle; + + // Sort out -ve angles + while (dNewAngle < 0) + dNewAngle += 360; + + // Sort out angles > 360 + while (dNewAngle > 360) + dNewAngle -= 360; + + return dNewAngle; +} + +//=============================================================================================================================== +//! Returns a point (external CRS) which is the average of (i.e. is midway between) two other external CRS points +//=============================================================================================================================== +CGeom2DPoint CSimulation::PtAverage(CGeom2DPoint const* pPt1, CGeom2DPoint const* pPt2) +{ + double const dPt1X = pPt1->dGetX(); + double const dPt1Y = pPt1->dGetY(); + double const dPt2X = pPt2->dGetX(); + double const dPt2Y = pPt2->dGetY(); + double const dPtAvgX = (dPt1X + dPt2X) / 2; + double const dPtAvgY = (dPt1Y + dPt2Y) / 2; + + return CGeom2DPoint(dPtAvgX, dPtAvgY); +} + +// //=============================================================================================================================== +// //! Returns an integer point (grid CRS) which is the approximate average of (i.e. is midway between) two other grid CRS integer points +// //=============================================================================================================================== +// CGeom2DIPoint CSimulation::PtiAverage(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2) +// { +// int const nPti1X = pPti1->nGetX(); +// int const nPti1Y = pPti1->nGetY(); +// int const nPti2X = pPti2->nGetX(); +// int const nPti2Y = pPti2->nGetY(); +// int const nPtiAvgX = (nPti1X + nPti2X) / 2; +// int const nPtiAvgY = (nPti1Y + nPti2Y) / 2; +// +// return CGeom2DIPoint(nPtiAvgX, nPtiAvgY); +// } + +//=============================================================================================================================== +//! Returns an integer point (grid CRS) which is the weighted average of two other grid CRS integer points. The weight must be <= 1, if the weight is < 0.5 then the output point is closer to the first point, if the weight is > 0.5 then the output point is closer to the second point +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiWeightedAverage(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2, double const dWeight) +{ + int const nPti1X = pPti1->nGetX(); + int const nPti1Y = pPti1->nGetY(); + int const nPti2X = pPti2->nGetX(); + int const nPti2Y = pPti2->nGetY(); + double const dOtherWeight = 1.0 - dWeight; + + int const nPtiWeightAvgX = nRound((dWeight * nPti2X) + (dOtherWeight * nPti1X)); + int const nPtiWeightAvgY = nRound((dWeight * nPti2Y) + (dOtherWeight * nPti1Y)); + + return CGeom2DIPoint(nPtiWeightAvgX, nPtiWeightAvgY); +} + +//=============================================================================================================================== +//! Returns a point (external CRS) which is the average of a vector of external CRS points +//=============================================================================================================================== +CGeom2DPoint CSimulation::PtAverage(vector* pVIn) +{ + int const nSize = static_cast(pVIn->size()); + + if (nSize == 0) + return CGeom2DPoint(DBL_NODATA, DBL_NODATA); + + double dAvgX = 0; + double dAvgY = 0; + + for (int n = 0; n < nSize; n++) + { + dAvgX += pVIn->at(n).dGetX(); + dAvgY += pVIn->at(n).dGetY(); + } + + dAvgX /= nSize; + dAvgY /= nSize; + + return CGeom2DPoint(dAvgX, dAvgY); +} + +// //=============================================================================================================================== +// //! Returns a point (grid CRS) which is the average of a vector of grid CRS points +// //=============================================================================================================================== +// CGeom2DIPoint CSimulation::PtiAverage(vector* pVIn) +// { +// int nSize = static_cast(pVIn->size()); +// if (nSize == 0) +// return CGeom2DIPoint(INT_NODATA, INT_NODATA); +// +// double dAvgX = 0; +// double dAvgY = 0; +// +// for (int n = 0; n < nSize; n++) +// { +// dAvgX += pVIn->at(n).nGetX(); +// dAvgY += pVIn->at(n).nGetY(); +// } +// +// dAvgX /= nSize; +// dAvgY /= nSize; +// +// return CGeom2DIPoint(nRound(dAvgX), nRound(dAvgY)); +// } + +//=============================================================================================================================== +//! Returns an integer point (grid CRS) which is the centroid of a polygon, given by a vector of grid CRS points. From https://stackoverflow.com/questions/2792443/finding-the-centroid-of-a-polygon +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiPolygonCentroid(vector* pVIn) +{ + CGeom2DIPoint PtiCentroid(0, 0); + int const nSize = static_cast(pVIn->size()); + int nX0 = 0; // Current vertex X + int nY0 = 0; // Current vertex Y + int nX1 = 0; // Next vertex X + int nY1 = 0; // Next vertex Y + + double dA = 0; // Partial signed area + double dSignedArea = 0.0; + + // For all vertices except last + for (int i = 0; i < nSize - 1; ++i) + { + nX0 = pVIn->at(i).nGetX(); + nY0 = pVIn->at(i).nGetY(); + nX1 = pVIn->at(i + 1).nGetX(); + nY1 = pVIn->at(i + 1).nGetY(); + + dA = (nX0 * nY1) - (nX1 * nY0); + dSignedArea += dA; + PtiCentroid.AddXAddY((nX0 + nX1) * dA, (nY0 + nY1) * dA); + } + + // Do last vertex separately to avoid performing an expensive modulus operation in each iteration + nX0 = pVIn->at(nSize - 1).nGetX(); + nY0 = pVIn->at(nSize - 1).nGetY(); + nX1 = pVIn->at(0).nGetX(); + nY1 = pVIn->at(0).nGetY(); + + dA = (nX0 * nY1) - (nX1 * nY0); + dSignedArea += dA; + PtiCentroid.AddXAddY((nX0 + nX1) * dA, (nY0 + nY1) * dA); + + dSignedArea *= 0.5; + PtiCentroid.DivXDivY(6.0 * dSignedArea, 6.0 * dSignedArea); + + return PtiCentroid; +} + +//=============================================================================================================================== +// Returns a vector which is perpendicular to an existing vector +//=============================================================================================================================== +// vector CSimulation::VGetPerpendicular(CGeom2DPoint const* +// PtStart, CGeom2DPoint const* PtNext, double const dDesiredLength, int const +// nHandedness) +// { +// // Returns a two-point vector which passes through PtStart with a scaled +// length +// double dXLen = PtNext->dGetX() - PtStart->dGetX(); +// double dYLen = PtNext->dGetY() - PtStart->dGetY(); +// +// double dLength = hypot(dXLen, dYLen); +// double dScaleFactor = dDesiredLength / dLength; +// +// // The difference vector is (dXLen, dYLen), so the perpendicular +// difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) +// CGeom2DPoint EndPt; +// if (nHandedness == RIGHT_HANDED) +// { +// EndPt.SetX(PtStart->dGetX() + (dScaleFactor * dYLen)); +// EndPt.SetY(PtStart->dGetY() - (dScaleFactor * dXLen)); +// } +// else +// { +// EndPt.SetX(PtStart->dGetX() - (dScaleFactor * dYLen)); +// EndPt.SetY(PtStart->dGetY() + (dScaleFactor * dXLen)); +// } +// +// vector VNew; +// VNew.push_back(*PtStart); +// VNew.push_back(EndPt); +// return VNew; +// } + +// //=============================================================================================================================== +// //! Returns a CGeom2DPoint which is the 'other' point of a two-point vector passing through PtStart, and which is perpendicular to the two-point vector from PtStart to PtNext +// //=============================================================================================================================== +// CGeom2DPoint CSimulation::PtGetPerpendicular(CGeom2DPoint const* PtStart, CGeom2DPoint const* PtNext, double const dDesiredLength, int const nHandedness) +// { +// double const dXLen = PtNext->dGetX() - PtStart->dGetX(); +// double const dYLen = PtNext->dGetY() - PtStart->dGetY(); +// double dLength; +// +// if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) +// dLength = dYLen; +// else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) +// dLength = dXLen; +// else +// dLength = hypot(dXLen, dYLen); +// +// double const dScaleFactor = dDesiredLength / dLength; +// +// // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) +// CGeom2DPoint EndPt; +// +// if (nHandedness == RIGHT_HANDED) +// { +// EndPt.SetX(PtStart->dGetX() + (dScaleFactor * dYLen)); +// EndPt.SetY(PtStart->dGetY() - (dScaleFactor * dXLen)); +// } +// else +// { +// EndPt.SetX(PtStart->dGetX() - (dScaleFactor * dYLen)); +// EndPt.SetY(PtStart->dGetY() + (dScaleFactor * dXLen)); +// } +// +// return EndPt; +// } + +//=============================================================================================================================== +//! Returns a CGeom2DIPoint (grid CRS) which is the 'other' point of a two-point vector passing through PtiStart, and which is perpendicular to the two-point vector from PtiStart to PtiNext +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiGetPerpendicular(CGeom2DIPoint const* PtiStart, CGeom2DIPoint const* PtiNext, double const dDesiredLength, int const nHandedness) +{ + double const dXLen = PtiNext->nGetX() - PtiStart->nGetX(); + double const dYLen = PtiNext->nGetY() - PtiStart->nGetY(); + double dLength; + + if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) + dLength = dYLen; + + else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) + dLength = dXLen; + + else + dLength = hypot(dXLen, dYLen); + + double const dScaleFactor = dDesiredLength / dLength; + + // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) + CGeom2DIPoint EndPti; + + if (nHandedness == RIGHT_HANDED) + { + EndPti.SetX(PtiStart->nGetX() + nRound(dScaleFactor * dYLen)); + EndPti.SetY(PtiStart->nGetY() - nRound(dScaleFactor * dXLen)); + } + + else + { + EndPti.SetX(PtiStart->nGetX() - nRound(dScaleFactor * dYLen)); + EndPti.SetY(PtiStart->nGetY() + nRound(dScaleFactor * dXLen)); + } + + return EndPti; +} + +//=============================================================================================================================== +//! Returns a CGeom2DIPoint (grid CRS) which is the 'other' point of a two-point vector passing through [nStartX][nStartY], and which is perpendicular to the two-point vector from [nStartX][nStartY] to [nNextX][nNextY] +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiGetPerpendicular(int const nStartX, int const nStartY, int const nNextX, int const nNextY, double const dDesiredLength, int const nHandedness) +{ + double const dXLen = nNextX - nStartX; + double const dYLen = nNextY - nStartY; + double dLength; + + if (bFPIsEqual(dXLen, 0.0, TOLERANCE)) + dLength = dYLen; + + else if (bFPIsEqual(dYLen, 0.0, TOLERANCE)) + dLength = dXLen; + + else + dLength = hypot(dXLen, dYLen); + + double const dScaleFactor = dDesiredLength / dLength; + + // The difference vector is (dXLen, dYLen), so the perpendicular difference vector is (-dYLen, dXLen) or (dYLen, -dXLen) + CGeom2DIPoint EndPti; + + if (nHandedness == RIGHT_HANDED) + { + EndPti.SetX(nStartX + nRound(dScaleFactor * dYLen)); + EndPti.SetY(nStartY - nRound(dScaleFactor * dXLen)); + } + + else + { + EndPti.SetX(nStartX - nRound(dScaleFactor * dYLen)); + EndPti.SetY(nStartY + nRound(dScaleFactor * dXLen)); + } + + return EndPti; +} + +//=============================================================================================================================== +//! Returns the signed angle BAC (in radians) subtended between three CGeom2DIPoints B A C. From http://stackoverflow.com/questions/3057448/angle-between-3-vertices +//=============================================================================================================================== +double CSimulation::dAngleSubtended(CGeom2DIPoint const* pPtiA, CGeom2DIPoint const* pPtiB, CGeom2DIPoint const* pPtiC) +{ + double const dXDistBtoA = pPtiB->nGetX() - pPtiA->nGetX(); + double const dYDistBtoA = pPtiB->nGetY() - pPtiA->nGetY(); + double const dXDistCtoA = pPtiC->nGetX() - pPtiA->nGetX(); + double const dYDistCtoA = pPtiC->nGetY() - pPtiA->nGetY(); + double const dDotProduct = dXDistBtoA * dXDistCtoA + dYDistBtoA * dYDistCtoA; + double const dPseudoCrossProduct = dXDistBtoA * dYDistCtoA - dYDistBtoA * dXDistCtoA; + double const dAngle = atan2(dPseudoCrossProduct, dDotProduct); + + return dAngle; +} + +//=============================================================================================================================== +//! Checks whether the selected raster GDAL driver supports file creation, 32-bit doubles, etc. +//=============================================================================================================================== +bool CSimulation::bCheckRasterGISOutputFormat(void) +{ + // Register all available GDAL raster and vector drivers (GDAL 2) + GDALAllRegister(); + + // If the user hasn't specified a GIS output format, assume that we will use the same GIS format as the input basement DEM + if (m_strRasterGISOutFormat.empty()) + m_strRasterGISOutFormat = m_strGDALBasementDEMDriverCode; + + // Load the raster GDAL driver + GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName(m_strRasterGISOutFormat.c_str()); + + if (NULL == pDriver) + { + // Can't load raster GDAL driver. Incorrectly specified? + cerr << ERR << "Unknown raster GIS output format '" << m_strRasterGISOutFormat << "'." << endl; + return false; + } + + // Get the metadata for this raster driver + char **papszMetadata = pDriver->GetMetadata(); + + // for (int i = 0; papszMetadata[i] != NULL; i++) + // cout << papszMetadata[i] << endl; + // cout << endl; + + // Need to test if this is a raster driver + if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_RASTER, false)) + { + // This is not a raster driver + cerr << ERR << "GDAL driver '" << m_strRasterGISOutFormat << "' is not a raster driver. Choose another format." << endl; + return false; + } + + // This driver is OK, so store its longname and the default file extension + string strTmp = CSLFetchNameValue(papszMetadata, "DMD_LONGNAME"); + m_strGDALRasterOutputDriverLongname = strTrim(&strTmp); + strTmp = CSLFetchNameValue(papszMetadata, "DMD_EXTENSIONS"); // Note DMD_EXTENSION (no S, is a single value) appears not to be implemented for newer drivers + strTmp = strTrim(&strTmp); + + // We have a space-separated list of one or more file extensions: use the first extension in the list + long unsigned int const nPos = strTmp.find(SPACE); + + if (nPos == string::npos) + { + // No space i.e. just one extension + m_strGDALRasterOutputDriverExtension = strTmp; + } + + else + { + // There's a space, so we must have more than one extension + m_strGDALRasterOutputDriverExtension = strTmp.substr(0, nPos); + } + + // Set up any defaults for raster files that are created using this driver + SetRasterFileCreationDefaults(); + + // Now do various tests of the driver's capabilities + if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, false)) + { + // This raster driver does not support the Create() method, does it support CreateCopy()? + if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATECOPY, false)) + { + cerr << ERR << "Cannot write using raster GDAL driver '" << m_strRasterGISOutFormat << " since neither Create() or CreateCopy() are supported'. Choose another GDAL raster format." << endl; + return false; + } + + // Can't use Create() but can use CreateCopy() + m_bGDALCanCreate = false; + } + + // Next, test to see what data types the driver can write and from this, work out the largest int and float we can write + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Float")) + { + m_bGDALCanWriteFloat = true; + m_GDALWriteFloatDataType = GDT_Float32; + } + + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "UInt32")) + { + m_bGDALCanWriteInt32 = true; + + m_GDALWriteIntDataType = GDT_UInt32; + m_lGDALMaxCanWrite = UINT32_MAX; + m_lGDALMinCanWrite = 0; + + if (! m_bGDALCanWriteFloat) + m_GDALWriteFloatDataType = GDT_UInt32; + + return true; + } + + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Int32")) + { + m_bGDALCanWriteInt32 = true; + + m_GDALWriteIntDataType = GDT_Int32; + m_lGDALMaxCanWrite = INT32_MAX; + m_lGDALMinCanWrite = INT32_MIN; + + if (! m_bGDALCanWriteFloat) + m_GDALWriteFloatDataType = GDT_Int32; + + return true; + } + + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "UInt16")) + { + m_bGDALCanWriteInt32 = false; + + m_GDALWriteIntDataType = GDT_UInt16; + m_lGDALMaxCanWrite = UINT16_MAX; + m_lGDALMinCanWrite = 0; + + if (! m_bGDALCanWriteFloat) + m_GDALWriteFloatDataType = GDT_UInt16; + + return true; + } + + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Int16")) + { + m_bGDALCanWriteInt32 = false; + + m_GDALWriteIntDataType = GDT_Int16; + m_lGDALMaxCanWrite = INT16_MAX; + m_lGDALMinCanWrite = INT16_MIN; + + if (! m_bGDALCanWriteFloat) + m_GDALWriteFloatDataType = GDT_Int16; + + return true; + } + + if (strstr(CSLFetchNameValue(papszMetadata, "DMD_CREATIONDATATYPES"), "Byte")) + { + m_bGDALCanWriteInt32 = false; + + m_GDALWriteIntDataType = GDT_Byte; + m_lGDALMaxCanWrite = UINT8_MAX; + m_lGDALMinCanWrite = 0; + + if (! m_bGDALCanWriteFloat) + m_GDALWriteFloatDataType = GDT_Byte; + + return true; + } + + // This driver does not even support byte output + cerr << ERR << "Cannot write using raster GDAL driver '" << m_strRasterGISOutFormat << ", not even byte output is supported'. Choose another GIS raster format." << endl; + return false; +} + +//=============================================================================================================================== +//! Checks whether the selected vector GDAL/OGR driver supports file creation etc. +//=============================================================================================================================== +bool CSimulation::bCheckVectorGISOutputFormat(void) +{ + // Load the vector GDAL driver (this assumes that GDALAllRegister() has already been called) + GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName(m_strVectorGISOutFormat.c_str()); + + if (NULL == pDriver) + { + // Can't load vector GDAL driver. Incorrectly specified? + cerr << ERR << "Unknown vector GIS output format '" << m_strVectorGISOutFormat << "'." << endl; + return false; + } + + // Get the metadata for this vector driver + char **papszMetadata = pDriver->GetMetadata(); + + // For GDAL2, need to test if this is a vector driver + if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_VECTOR, false)) + { + // This is not a vector driver + cerr << ERR << "GDAL driver '" << m_strVectorGISOutFormat << "' is not a vector driver. Choose another format." << endl; + return false; + } + + if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, false)) + { + // Driver does not support create() method + cerr << ERR << "Cannot write vector GIS files using GDAL driver '" << m_strRasterGISOutFormat << "'. Choose another format." << endl; + return false; + } + + // Driver is OK, now set some options for individual drivers + if (m_strVectorGISOutFormat == "ESRI Shapefile") + { + // Set this, so that just a single dataset-with-one-layer shapefile is created, rather than a directory (see http://www.gdal.org/ogr/drv_shapefile.html) + m_strOGRVectorOutputExtension = ".shp"; + } + + else if (m_strVectorGISOutFormat == "geojson") + { + m_strOGRVectorOutputExtension = ".geojson"; + } + + else if (m_strVectorGISOutFormat == "gpkg") + { + m_strOGRVectorOutputExtension = ".gpkg"; + } + + // TODO 033 Others + + return true; +} + +//=============================================================================================================================== +//! The bSaveAllRasterGISFiles member function saves the raster GIS files using values from the RasterGrid array +//=============================================================================================================================== +bool CSimulation::bSaveAllRasterGISFiles(void) +{ + // Increment file number + m_nGISSave++; + + // Set for next save + if (m_bSaveRegular) + { + m_dRegularSaveTime += m_dRegularSaveInterval; + } + else + { + if (m_nThisSave < m_nUSave - 1) + { + // Still have user-defined save times remaining + m_nThisSave++; + } + else + { + // Finished user-defined times, switch to regular interval using last value as interval + double dLastInterval; + + if (m_nUSave > 1) + dLastInterval = m_dUSaveTime[m_nUSave - 1] - m_dUSaveTime[m_nUSave - 2]; + else + dLastInterval = m_dUSaveTime[m_nUSave - 1]; + + m_dRegularSaveTime = m_dSimElapsed + dLastInterval; + m_dRegularSaveInterval = dLastInterval; + m_bSaveRegular = true; + } + } + + if (m_bSedimentTopSurfSave) + if (! bWriteRasterGISFile(RASTER_PLOT_SEDIMENT_TOP_ELEVATION, &RASTER_PLOT_SEDIMENT_TOP_ELEVATION_TITLE)) + return false; + + if (m_bTopSurfSave) + if (! bWriteRasterGISFile(RASTER_PLOT_OVERALL_TOP_ELEVATION, &RASTER_PLOT_OVERALL_TOP_ELEVATION_TITLE)) + return false; + + if (m_bSlopeConsSedSave) + if (! bWriteRasterGISFile(RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT_TITLE)) + return false; + + if (m_bSlopeSaveForCliffToe) + if (! bWriteRasterGISFile(RASTER_PLOT_SLOPE_FOR_CLIFF_TOE, &RASTER_PLOT_SLOPE_FOR_CLIFF_TOE_TITLE)) + return false; + + if (m_bCliffToeSave) + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_TOE, &RASTER_PLOT_CLIFF_TOE_TITLE)) + return false; + + if (m_bSeaDepthSave) + if (! bWriteRasterGISFile(RASTER_PLOT_SEA_DEPTH, &RASTER_PLOT_SEA_DEPTH_TITLE)) + return false; + + if (m_bWaveHeightSave) + if (! bWriteRasterGISFile(RASTER_PLOT_WAVE_HEIGHT, &RASTER_PLOT_WAVE_HEIGHT_TITLE)) + return false; + + if (m_bWaveAngleSave) + if (! bWriteRasterGISFile(RASTER_PLOT_WAVE_ORIENTATION, &RASTER_PLOT_WAVE_ORIENTATION_TITLE)) + return false; + + // Don't write platform erosion files if there is no platform erosion + if (m_bDoShorePlatformErosion) + { + if (m_bPotentialPlatformErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_PLATFORM_EROSION, &RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_TITLE)) + return false; + + if (m_bActualPlatformErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_ACTUAL_PLATFORM_EROSION, &RASTER_PLOT_ACTUAL_PLATFORM_EROSION_TITLE)) + return false; + + if (m_bTotalPotentialPlatformErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION, &RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION_TITLE)) + return false; + + if (m_bTotalActualPlatformErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION, &RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION_TITLE)) + return false; + + if (m_bPotentialPlatformErosionMaskSave) + if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK, &RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK_TITLE)) + return false; + + if (m_bBeachProtectionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_PROTECTION, &RASTER_PLOT_BEACH_PROTECTION_TITLE)) + return false; + + if (m_bPotentialBeachErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_POTENTIAL_BEACH_EROSION, &RASTER_PLOT_POTENTIAL_BEACH_EROSION_TITLE)) + return false; + + if (m_bActualBeachErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_ACTUAL_BEACH_EROSION, &RASTER_PLOT_ACTUAL_BEACH_EROSION_TITLE)) + return false; + + if (m_bTotalPotentialBeachErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION, &RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION_TITLE)) + return false; + + if (m_bTotalActualBeachErosionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION, &RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION_TITLE)) + return false; + + if (m_bBeachDepositionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_DEPOSITION, &RASTER_PLOT_BEACH_DEPOSITION_TITLE)) + return false; + + if (m_bTotalBeachDepositionSave) + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_BEACH_DEPOSITION, &RASTER_PLOT_TOTAL_BEACH_DEPOSITION_TITLE)) + return false; + } + + if (m_bLandformSave) + if (! bWriteRasterGISFile(RASTER_PLOT_LANDFORM, &RASTER_PLOT_LANDFORM_TITLE)) + return false; + + if (m_bAvgWaveHeightSave) + if (! bWriteRasterGISFile(RASTER_PLOT_AVG_WAVE_HEIGHT, &RASTER_PLOT_AVG_WAVE_HEIGHT_TITLE)) + return false; + + if (m_bAvgWaveAngleSave) + if (! bWriteRasterGISFile(RASTER_PLOT_AVG_WAVE_ORIENTATION, &RASTER_PLOT_AVG_WAVE_ORIENTATION_TITLE)) + return false; + + if (m_bAvgSeaDepthSave) + if (! bWriteRasterGISFile(RASTER_PLOT_AVG_SEA_DEPTH, &RASTER_PLOT_AVG_SEA_DEPTH_TITLE)) + return false; + + if (m_bSedimentInput && m_bSedimentInputEventSave) + if (! bWriteRasterGISFile(RASTER_PLOT_SEDIMENT_INPUT, &RASTER_PLOT_SEDIMENT_INPUT_EVENT_TITLE)) + return false; + + // Don't write suspended sediment files if there is no fine sediment + if (m_bHaveFineSediment) + { + if (m_bSuspSedSave) + if (! bWriteRasterGISFile(RASTER_PLOT_SUSPENDED_SEDIMENT, &RASTER_PLOT_SUSPENDED_SEDIMENT_TITLE)) + return false; + + if (m_bAvgSuspSedSave) + if (! bWriteRasterGISFile(RASTER_PLOT_AVG_SUSPENDED_SEDIMENT, &RASTER_PLOT_AVG_SUSPENDED_SEDIMENT_TITLE)) + return false; + } + + if (m_bBasementElevSave) + if (! bWriteRasterGISFile(RASTER_PLOT_BASEMENT_ELEVATION, &RASTER_PLOT_BASEMENT_ELEVATION_TITLE)) + return false; + + for (int nLayer = 0; nLayer < m_nLayers; nLayer++) + { + if (m_bHaveFineSediment && m_bFineUnconsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + + if (m_bHaveSandSediment && m_bSandUnconsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + + if (m_bHaveCoarseSediment && m_bCoarseUnconsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT, &RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + + if (m_bHaveFineSediment && m_bFineConsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + + if (m_bHaveSandSediment && m_bSandConsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + + if (m_bHaveCoarseSediment && m_bCoarseConsSedSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT, &RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT_TITLE, nLayer)) + return false; + } + } + + if (m_bSliceSave) + { + for (int i = 0; i < static_cast(m_VdSliceElev.size()); i++) + { + if (! bWriteRasterGISFile(RASTER_PLOT_SLICE, &RASTER_PLOT_SLICE_TITLE, 0, m_VdSliceElev[i])) + return false; + } + } + + if (m_bRasterCoastlineSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_COAST, &RASTER_PLOT_COAST_TITLE)) + return false; + } + + if (m_bRasterNormalProfileSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_NORMAL_PROFILE, &RASTER_PLOT_NORMAL_PROFILE_TITLE)) + return false; + } + + if (m_bActiveZoneSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_ACTIVE_ZONE, &RASTER_PLOT_ACTIVE_ZONE_TITLE)) + return false; + } + + // Don't write cliff collapse files if we aren't considering cliff collapse + if (m_bDoCliffCollapse) + { + if (m_bCliffCollapseSave) + { + if (m_bHaveFineSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE_TITLE)) + return false; + } + + if (m_bHaveSandSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND_TITLE)) + return false; + } + + if (m_bHaveCoarseSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE, &RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE_TITLE)) + return false; + } + + if (m_bCliffNotchAllSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_NOTCH_ALL, &RASTER_PLOT_CLIFF_NOTCH_ALL_TITLE)) + return false; + } + + if (m_bCliffCollapseTimestepSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP, &RASTER_PLOT_CLIFF_COLLAPSE_TIMESTEP_TITLE)) + return false; + } + } + + if (m_bTotCliffCollapseSave) + { + if (m_bHaveFineSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE_TITLE)) + return false; + } + + if (m_bHaveSandSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND_TITLE)) + return false; + } + + if (m_bHaveCoarseSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE_TITLE)) + return false; + } + } + + if (m_bCliffCollapseDepositionSave) + { + if (m_bHaveSandSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND, &RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND_TITLE)) + return false; + } + + if (m_bHaveCoarseSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE, &RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE_TITLE)) + return false; + } + } + + if (m_bTotCliffCollapseDepositionSave) + { + if (m_bHaveSandSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND_TITLE)) + return false; + } + + if (m_bHaveCoarseSediment) + { + if (! bWriteRasterGISFile(RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE, &RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE_TITLE)) + return false; + } + } + } + + if (m_bRasterPolygonSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON, &RASTER_PLOT_POLYGON_TITLE)) + return false; + } + + if (m_bSeaMaskSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_INUNDATION_MASK, &RASTER_PLOT_INUNDATION_MASK_TITLE)) + return false; + } + + if (m_bBeachMaskSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_BEACH_MASK, &RASTER_PLOT_BEACH_MASK_TITLE)) + return false; + } + + if (m_bInterventionClassSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_INTERVENTION_CLASS, &RASTER_PLOT_INTERVENTION_CLASS_TITLE)) + return false; + } + + if (m_bInterventionHeightSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_INTERVENTION_HEIGHT, &RASTER_PLOT_INTERVENTION_HEIGHT_TITLE)) + return false; + } + + if (m_bShadowZoneCodesSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_SHADOW_ZONE, &RASTER_PLOT_SHADOW_ZONE_TITLE)) + return false; + + if (! bWriteRasterGISFile(RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE, &RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE_TITLE)) + return false; + } + + if (m_bDeepWaterWaveAngleSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION, &RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION_TITLE)) + return false; + } + + if (m_bDeepWaterWaveHeightSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT, &RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT_TITLE)) + return false; + } + + if (m_bDeepWaterWavePeriodSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_DEEP_WATER_WAVE_PERIOD, &RASTER_PLOT_DEEP_WATER_WAVE_PERIOD_TITLE)) + return false; + } + + if (m_bPolygonUnconsSedUpOrDownDriftSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT, &RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT_TITLE)) + return false; + } + + if (m_bPolygonUnconsSedGainOrLossSave) + { + if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON_GAIN_OR_LOSS, &RASTER_PLOT_POLYGON_GAIN_OR_LOSS_TITLE)) + return false; + } + + // if (m_bSetupSurgeFloodMaskSave) + // { + // if (! bWriteRasterGISFile(RASTER_PLOT_SETUP_SURGE_FLOOD_MASK, &RASTER_PLOT_SETUP_SURGE_FLOOD_MASK_TITLE)) + // return false; + // } + + // if (m_bSetupSurgeRunupFloodMaskSave) + // { + // if (! bWriteRasterGISFile(RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK, &RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK_TITLE)) + // return false; + // } + // + return true; +} + +//=============================================================================================================================== +//! The bSaveAllvectorGISFiles member function saves the vector GIS files TODO 081 Choose more files to omit from "usual" vector output +//=============================================================================================================================== +bool CSimulation::bSaveAllVectorGISFiles(void) +{ + // Always written + if (m_bCoastSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_COAST, &VECTOR_PLOT_COAST_TITLE)) + return false; + + if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_SWL_HIGHEST, &VECTOR_PLOT_COAST_SWL_HIGHEST_TITLE)) + return false; + + if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_SWL_LOWEST, &VECTOR_PLOT_COAST_SWL_LOWEST_TITLE)) + return false; + } + + if (m_bCliffEdgeSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_CLIFF_EDGE, &VECTOR_PLOT_CLIFF_EDGE_TITLE)) + return false; + } + + if (m_bNormalsSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_NORMALS, &VECTOR_PLOT_NORMALS_TITLE)) + return false; + } + + if (m_bInvalidNormalsSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_INVALID_NORMALS, &VECTOR_PLOT_INVALID_NORMALS_TITLE)) + return false; + } + + if (m_bCoastCurvatureSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_COAST_CURVATURE, &VECTOR_PLOT_COAST_CURVATURE_TITLE)) + return false; + } + + if (m_bWaveAngleAndHeightSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_WAVE_ANGLE_AND_HEIGHT_TITLE)) + return false; + } + + if (m_bAvgWaveAngleAndHeightSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_AVG_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_AVG_WAVE_ANGLE_AND_HEIGHT_TITLE)) + return false; + } + + if (m_bWaveEnergySinceCollapseSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_ENERGY_SINCE_COLLAPSE, &VECTOR_PLOT_WAVE_ENERGY_SINCE_COLLAPSE_TITLE)) + return false; + } + + if (m_bMeanWaveEnergySave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_MEAN_WAVE_ENERGY, &VECTOR_PLOT_MEAN_WAVE_ENERGY_TITLE)) + return false; + } + + if (m_bBreakingWaveHeightSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_BREAKING_WAVE_HEIGHT, &VECTOR_PLOT_BREAKING_WAVE_HEIGHT_TITLE)) + return false; + } + + if (m_bPolygonNodeSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_NODES, &VECTOR_PLOT_POLYGON_NODES_TITLE)) + return false; + } + + if (m_bPolygonBoundarySave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_BOUNDARY, &VECTOR_PLOT_POLYGON_BOUNDARY_TITLE)) + return false; + } + + if (m_bCliffNotchSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_CLIFF_NOTCH_ACTIVE, &VECTOR_PLOT_CLIFF_NOTCH_ACTIVE_TITLE)) + return false; + } + + if (m_bWaveTransectPointsSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_TRANSECT_POINTS, &VECTOR_PLOT_WAVE_TRANSECT_POINTS_TITLE)) + return false; + } + + if (m_bShadowBoundarySave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_SHADOW_ZONE_BOUNDARY, &VECTOR_PLOT_SHADOW_ZONE_BOUNDARY_TITLE)) + return false; + } + + if (m_bShadowDowndriftBoundarySave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_DOWNDRIFT_ZONE_BOUNDARY, &VECTOR_PLOT_DOWNDRIFT_ZONE_BOUNDARY_TITLE)) + return false; + } + + if (m_bDeepWaterWaveAngleAndHeightSave) + { + if (! bWriteVectorGISFile(VECTOR_PLOT_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT, &VECTOR_PLOT_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT_TITLE)) + return false; + } + + // if (m_bWaveSetupSave) + // { + // if (! bWriteVectorGISFile(VECTOR_PLOT_WAVE_SETUP, &VECTOR_PLOT_WAVE_SETUP_TITLE)) + // return false; + // } + // + // if (m_bStormSurgeSave) + // { + // if (! bWriteVectorGISFile(VECTOR_PLOT_STORM_SURGE, &VECTOR_PLOT_STORM_SURGE_TITLE)) + // return false; + // } + // + // if (m_bRunUpSave) + // { + // if (! bWriteVectorGISFile(VECTOR_PLOT_RUN_UP, &VECTOR_PLOT_RUN_UP_TITLE)) + // return false; + // } + // + // if (m_bRiverineFlooding && m_bVectorWaveFloodLineSave) + // { + // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_LINE, &VECTOR_PLOT_FLOOD_SWL_SETUP_LINE_TITLE)) + // return false; + // + // // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_LINE, + // // &VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_LINE_TITLE)) return false; + // + // // if (! bWriteVectorGISFile(VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_RUNUP_LINE, + // // &VECTOR_PLOT_FLOOD_SWL_SETUP_SURGE_RUNUP_LINE_TITLE)) return false; + // } + + return true; +} + +//=============================================================================================================================== +//! Finds the max and min values in order to scale raster output if we cannot write doubles +//=============================================================================================================================== +void CSimulation::GetRasterOutputMinMax(int const nDataItem, double& dMin, double& dMax, int const nLayer, double const dElev) +{ + // If this is a binary mask layer, we already know the max and min values + if ((nDataItem == RASTER_PLOT_POTENTIAL_PLATFORM_EROSION_MASK) || + (nDataItem == RASTER_PLOT_INUNDATION_MASK) || + (nDataItem == RASTER_PLOT_BEACH_MASK) || + (nDataItem == RASTER_PLOT_COAST) || + (nDataItem == RASTER_PLOT_NORMAL_PROFILE) || + (nDataItem == RASTER_PLOT_ACTIVE_ZONE) || + (nDataItem == RASTER_PLOT_POLYGON_UPDRIFT_OR_DOWNDRIFT) || + (nDataItem == RASTER_PLOT_SETUP_SURGE_FLOOD_MASK) || + (nDataItem == RASTER_PLOT_SETUP_SURGE_RUNUP_FLOOD_MASK) || + (nDataItem == RASTER_PLOT_WAVE_FLOOD_LINE)) + { + dMin = 0; + dMax = 1; + + return; + } + + // Not a binary mask layer, so we must find the max and min values + dMin = DBL_MAX; + dMax = DBL_MIN; + + double dTmp = 0; + + for (int nY = 0; nY < m_nYGridSize; nY++) + { + for (int nX = 0; nX < m_nXGridSize; nX++) + { + switch (nDataItem) + { + case (RASTER_PLOT_SLICE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetLayerAtElev(dElev); + break; + + case (RASTER_PLOT_LANDFORM): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory(); + break; + + case (RASTER_PLOT_INTERVENTION_CLASS): + dTmp = INT_NODATA; + + if (bIsInterventionCell(nX, nY)) + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFSubCategory(); + + break; + + case (RASTER_PLOT_INTERVENTION_HEIGHT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetInterventionHeight(); + break; + + case (RASTER_PLOT_POLYGON): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + break; + + case (RASTER_PLOT_BASEMENT_ELEVATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBasementElev(); + break; + + case (RASTER_PLOT_SEDIMENT_TOP_ELEVATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); + break; + + case (RASTER_PLOT_OVERALL_TOP_ELEVATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetOverallTopElev(); + break; + + case (RASTER_PLOT_SLOPE_OF_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetConsSedSlope(); + break; + + case (RASTER_PLOT_SEA_DEPTH): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(); + break; + + case (RASTER_PLOT_AVG_SEA_DEPTH): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSeaDepth() / static_cast(m_ulIter); + break; + + case (RASTER_PLOT_WAVE_HEIGHT): + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) + dTmp = m_dMissingValue; + else + dTmp = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); + + break; + + case (RASTER_PLOT_AVG_WAVE_HEIGHT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotWaveHeight() / static_cast(m_ulIter); + break; + + case (RASTER_PLOT_WAVE_ORIENTATION): + if (! m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) + dTmp = m_dMissingValue; + + else + dTmp = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); + + break; + + case (RASTER_PLOT_AVG_WAVE_ORIENTATION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotWaveAngle() / static_cast(m_ulIter); + break; + + case (RASTER_PLOT_BEACH_PROTECTION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBeachProtectionFactor(); + + if (! bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) + dTmp = 1 - dTmp; // Output the inverse, seems more intuitive + + break; + + case (RASTER_PLOT_POTENTIAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetPotentialPlatformErosion(); + break; + + case (RASTER_PLOT_ACTUAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetActualPlatformErosion(); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotPotentialPlatformErosion(); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_PLATFORM_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotActualPlatformErosion(); + break; + + case (RASTER_PLOT_POTENTIAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetPotentialBeachErosion(); + break; + + case (RASTER_PLOT_ACTUAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetActualBeachErosion(); + break; + + case (RASTER_PLOT_TOTAL_POTENTIAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotPotentialBeachErosion(); + break; + + case (RASTER_PLOT_TOTAL_ACTUAL_BEACH_EROSION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotActualBeachErosion(); + break; + + case (RASTER_PLOT_BEACH_DEPOSITION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetBeachDeposition(); + break; + + case (RASTER_PLOT_TOTAL_BEACH_DEPOSITION): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotBeachDeposition(); + break; + + case (RASTER_PLOT_SUSPENDED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetSuspendedSediment(); + break; + + case (RASTER_PLOT_AVG_SUSPENDED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSuspendedSediment() / + static_cast(m_ulIter); + break; + + case (RASTER_PLOT_FINE_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetFineDepth(); + break; + + case (RASTER_PLOT_SAND_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetSandDepth(); + break; + + case (RASTER_PLOT_COARSE_UNCONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetUnconsolidatedSediment()->dGetCoarseDepth(); + break; + + case (RASTER_PLOT_FINE_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetFineDepth(); + break; + + case (RASTER_PLOT_SAND_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetSandDepth(); + break; + + case (RASTER_PLOT_COARSE_CONSOLIDATED_SEDIMENT): + dTmp = m_pRasterGrid->Cell(nX, nY).pGetLayerAboveBasement(nLayer)->pGetConsolidatedSediment()->dGetCoarseDepth(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_FINE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionFine(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionSand(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_EROSION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseErosionCoarse(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_FINE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseFine(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseSand(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_EROSION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCliffCollapseCoarse(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseSandTalusDeposition(); + break; + + case (RASTER_PLOT_CLIFF_COLLAPSE_DEPOSITION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetThisIterCliffCollapseCoarseTalusDeposition(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_SAND): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotSandTalusDeposition(); + break; + + case (RASTER_PLOT_TOTAL_CLIFF_COLLAPSE_DEPOSITION_COARSE): + dTmp = m_pRasterGrid->Cell(nX, nY).dGetTotCoarseTalusDeposition(); + break; + + case (RASTER_PLOT_SHADOW_ZONE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetShadowZoneNumber(); + break; + + case (RASTER_PLOT_SHADOW_DOWNDRIFT_ZONE): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetDownDriftZoneNumber(); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_ORIENTATION): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetDownDriftZoneNumber(); + break; + + case (RASTER_PLOT_DEEP_WATER_WAVE_HEIGHT): + dTmp = m_pRasterGrid->Cell(nX, nY).nGetDownDriftZoneNumber(); + break; + + case (RASTER_PLOT_POLYGON_GAIN_OR_LOSS): + int const nPoly = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + int const nPolyCoast = m_pRasterGrid->Cell(nX, nY).nGetPolygonCoastID(); + + if (nPoly == INT_NODATA) + dTmp = m_dMissingValue; + else + dTmp = m_VCoast[nPolyCoast].pGetPolygon(nPoly)->dGetBeachDepositionAndSuspensionAllUncons(); + + break; + } + + if (! bFPIsEqual(dTmp, DBL_NODATA, TOLERANCE)) + { + if (dTmp > dMax) + dMax = dTmp; + + if (dTmp < dMin) + dMin = dTmp; + } + } + } +} + +//=============================================================================================================================== +//! Sets per-driver defaults for raster files created using GDAL +//=============================================================================================================================== +void CSimulation::SetRasterFileCreationDefaults(void) +{ + string const strDriver = strToLower(&m_strRasterGISOutFormat); + string const strComment = "Created by " + PROGRAM_NAME + " for " + PLATFORM + " " + strGetBuild() + " running on " + strGetComputerName(); + + // TODO 034 Do these for all commonly-used file types + if (strDriver == "aaigrid") + { + } + else if (strDriver == "bmp") + { + } + else if (strDriver == "gtiff") + { + if (m_bWorldFile) + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "TFW", "YES"); + + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NUM_THREADS", "ALL_CPUS"); + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMPRESS", "LZW"); + } + else if (strDriver == "hfa") + { + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NBITS", "4"); + } + else if (strDriver == "jpeg") + { + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "QUALITY", "95"); + } + else if (strDriver == "png") + { + if (m_bWorldFile) + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "WORLDFILE", "YES"); + + // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "TITLE", "This is the title"); m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "DESCRIPTION", "This is a description"); + // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COPYRIGHT", "This is some copyright statement"); + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "NBITS", "4"); + } + else if (strDriver == "rst") + { + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); + } + else if (strDriver == "geojson") + { + // m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "COMMENT", strComment.c_str()); + } + else if (strDriver == "gpkg") + { + // TODO 065 Does GDAL support overwriting raster gpkg files yet? + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "OVERWRITE", "YES"); + m_papszGDALRasterOptions = CSLSetNameValue(m_papszGDALRasterOptions, "USE_TILE_EXTENT", "YES"); + } + else if (strDriver == "netcdf") + { + } +} + +//=============================================================================================================================== +//! Returns the opposite direction +//=============================================================================================================================== +int CSimulation::nGetOppositeDirection(int const nDirection) +{ + switch (nDirection) + { + case NORTH: + return SOUTH; + + case NORTH_EAST: + return SOUTH - WEST; + + case EAST: + return WEST; + + case SOUTH_EAST: + return NORTH - WEST; + + case SOUTH: + return NORTH; + + case SOUTH_WEST: + return NORTH - EAST; + + case WEST: + return EAST; + + case NORTH_WEST: + return SOUTH - EAST; + } + + // Should never get here + return NO_DIRECTION; +} + +// //=============================================================================================================================== +// //! Given two integer points, calculates the slope and intercept of the line passing through the points +// //=============================================================================================================================== +// void CSimulation::GetSlopeAndInterceptFromPoints(CGeom2DIPoint const* pPti1, CGeom2DIPoint const* pPti2, double& dSlope, double& dIntercept) +// { +// int nX1 = pPti1->nGetX(); +// int nY1 = pPti1->nGetY(); +// int nX2 = pPti2->nGetX(); +// int nY2 = pPti2->nGetY(); +// +// double dXDiff = nX1 - nX2; +// double dYDiff = nY1 - nY2; +// +// if (bFPIsEqual(dXDiff, 0.0, TOLERANCE)) +// dSlope = 0; +// else +// dSlope = dYDiff / dXDiff; +// +// dIntercept = nY1 - (dSlope * nX1); +// } + +//=============================================================================================================================== +//! Finds the closest point on any coastline to a given point +//=============================================================================================================================== +CGeom2DIPoint CSimulation::PtiFindClosestCoastPoint(int const nX, int const nY) +{ + unsigned int nMinSqDist = UINT_MAX; + CGeom2DIPoint PtiCoastPoint; + + // Do for every coast + for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) + { + for (int j = 0; j < m_VCoast[nCoast].nGetCoastlineSize(); j++) + { + // Get the coords of the grid cell marked as coastline for the coastal landform object + int const nXCoast = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(j)->nGetX(); + int const nYCoast = m_VCoast[nCoast].pPtiGetCellMarkedAsCoastline(j)->nGetY(); + + // Calculate the squared distance between this point and the given point + int const nXDist = nX - nXCoast; + int const nYDist = nY - nYCoast; + + unsigned int const nSqDist = (nXDist * nXDist) + (nYDist * nYDist); + + // Is this the closest so dar? + if (nSqDist < nMinSqDist) + { + nMinSqDist = nSqDist; + PtiCoastPoint.SetXY(nXCoast, nYCoast); + } + } + } + + return PtiCoastPoint; +} + +//=============================================================================================================================== +//! Given a length in m, this returns the rounded equivalent number of cells +//=============================================================================================================================== +int CSimulation::nConvertMetresToNumCells(double const dLen) const +{ + return nRound(dLen / m_dCellSide); +} + +//=============================================================================================================================== +//! Given a line between two points and another point, this finds the closest point on the line to the other point. From https://cboard.cprogramming.com/c-programming/155809-find-closest-point-line.html +//=============================================================================================================================== +void CSimulation::GetClosestPoint(double const dAx, double const dAy, double const dBx, double const dBy, double const dPx, double const dPy, double& dXRet, double& dYRet) +{ + double const dAPx = dPx - dAx; + double const dAPy = dPy - dAy; + double const dABx = dBx - dAx; + double const dABy = dBy - dAy; + double const dMagAB2 = (dABx * dABx) + (dABy * dABy); + double const dABdotAP = (dABx * dAPx) + (dABy * dAPy); + double const dT = dABdotAP / dMagAB2; + + if ( dT < 0) + { + dXRet = dAx; + dYRet = dAy; + } + else if (dT > 1) + { + dXRet = dBx; + dYRet = dBy; + } + else + { + dXRet = dAx + (dABx * dT); + dYRet = dAy + (dABy * dT); + } +} diff --git a/src/gis_vector.cpp b/src/gis_vector.cpp index 9208341a0..910e94119 100644 --- a/src/gis_vector.cpp +++ b/src/gis_vector.cpp @@ -400,6 +400,107 @@ int CSimulation::nReadVectorGISFile(int const nDataItem) return RTN_OK; } +//=============================================================================================================================== +//! Reads sea flood fill seed points from a point shapefile +//=============================================================================================================================== +int CSimulation::nReadSeaFloodSeedPointShapefile(void) +{ + // Open the GDAL/OGR datasource + GDALDataset* pOGRDataSource = static_cast(GDALOpenEx(m_strSeaFloodSeedPointShapefile.c_str(), GDAL_OF_VECTOR, NULL, NULL, NULL)); + + if (pOGRDataSource == NULL) + { + // Can't open file + cerr << ERR << "cannot open " << m_strSeaFloodSeedPointShapefile << " for input: " << CPLGetLastErrorMsg() << endl; + return RTN_ERR_VECTOR_FILE_READ; + } + + // Get the first layer (assume seed points are in first layer) + OGRLayer* pOGRLayer = pOGRDataSource->GetLayer(0); + + if (pOGRLayer == NULL) + { + cerr << ERR << "cannot get layer 0 from " << m_strSeaFloodSeedPointShapefile << endl; + GDALClose(pOGRDataSource); + return RTN_ERR_VECTOR_FILE_READ; + } + + // Make sure we are at the beginning of the layer + pOGRLayer->ResetReading(); + + // Clear any existing seed points + m_VSeaFloodSeedPoint.clear(); + + // Iterate through all features in the layer + OGRFeature* pOGRFeature; + int nSeedCount = 0; + + while ((pOGRFeature = pOGRLayer->GetNextFeature()) != NULL) + { + // Get the geometry for this feature + OGRGeometry* pOGRGeometry = pOGRFeature->GetGeometryRef(); + + if (pOGRGeometry == NULL) + { + cerr << WARN << "null geometry in " << m_strSeaFloodSeedPointShapefile << ", skipping feature" << endl; + OGRFeature::DestroyFeature(pOGRFeature); + continue; + } + + // Check geometry type + int const nGeometry = wkbFlatten(pOGRGeometry->getGeometryType()); + + if (nGeometry != wkbPoint) + { + cerr << WARN << "non-point geometry in " << m_strSeaFloodSeedPointShapefile << ", skipping feature (expected point, got type " << nGeometry << ")" << endl; + OGRFeature::DestroyFeature(pOGRFeature); + continue; + } + + // Extract point coordinates + OGRPoint* pOGRPoint = static_cast(pOGRGeometry); + double const dExtX = pOGRPoint->getX(); + double const dExtY = pOGRPoint->getY(); + + // Convert from external CRS to grid CRS + double const dGridX = dExtCRSXToGridX(dExtX); + double const dGridY = dExtCRSYToGridY(dExtY); + + // Convert to integer grid coordinates + int const nGridX = static_cast(dGridX); + int const nGridY = static_cast(dGridY); + + // Check if point is within grid bounds + if (nGridX < 0 || nGridX >= m_nXGridSize || nGridY < 0 || nGridY >= m_nYGridSize) + { + cerr << WARN << "seed point at (" << dExtX << ", " << dExtY << ") is outside grid bounds, skipping" << endl; + OGRFeature::DestroyFeature(pOGRFeature); + continue; + } + + // Add to seed point vector + m_VSeaFloodSeedPoint.push_back(CGeom2DIPoint(nGridX, nGridY)); + nSeedCount++; + + // Clean up feature + OGRFeature::DestroyFeature(pOGRFeature); + } + + // Clean up data source + GDALClose(pOGRDataSource); + + if (nSeedCount == 0) + { + cerr << WARN << "no valid seed points found in " << m_strSeaFloodSeedPointShapefile << ", will use grid edge cells instead" << endl; + } + else + { + LogStream << "Read " << nSeedCount << " sea flood fill seed point" << (nSeedCount > 1 ? "s" : "") << " from " << m_strSeaFloodSeedPointShapefile << endl; + } + + return RTN_OK; +} + //=============================================================================================================================== //! Writes vector GIS files using GDAL/OGR //=============================================================================================================================== @@ -1453,7 +1554,7 @@ bool CSimulation::bWriteVectorGISFile(int const nDataItem, string const* strPlot for (int nY = 0; nY < m_nYGridSize; nY++) { // Only output a value if the cell is a sea cell which is not in the active zone (wave height and angle values are meaningless if in the active zone) - if ((m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) && (!m_pRasterGrid->m_Cell[nX][nY].bIsInActiveZone())) + if ((m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea()) && (!m_pRasterGrid->Cell(nX, nY).bIsInActiveZone())) { // Create a feature object, one per sea cell OGRFeature* pOGRFeature = OGRFeature::CreateFeature(pOGRLayer->GetLayerDefn()); @@ -1463,8 +1564,8 @@ bool CSimulation::bWriteVectorGISFile(int const nDataItem, string const* strPlot OGRPt.setY(dGridCentroidYToExtCRSY(nY)); pOGRFeature->SetGeometry(&OGRPt); - double const dOrientation = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); - double const dHeight = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + double const dOrientation = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); + double const dHeight = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); if (bFPIsEqual(dHeight, DBL_NODATA, TOLERANCE) || bFPIsEqual(dOrientation, DBL_NODATA, TOLERANCE)) continue; @@ -1649,8 +1750,8 @@ bool CSimulation::bWriteVectorGISFile(int const nDataItem, string const* strPlot OGRPt.setY(dGridCentroidYToExtCRSY(nY)); pOGRFeature->SetGeometry(&OGRPt); - double const dOrientation = m_pRasterGrid->m_Cell[nX][nY].dGetTotWaveAngle() / static_cast(m_ulIter); - double const dHeight = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight() / static_cast(m_ulIter); + double const dOrientation = m_pRasterGrid->Cell(nX, nY).dGetTotWaveAngle() / static_cast(m_ulIter); + double const dHeight = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight() / static_cast(m_ulIter); if (bFPIsEqual(dHeight, DBL_NODATA, TOLERANCE) || bFPIsEqual(dOrientation, DBL_NODATA, TOLERANCE)) continue; @@ -1917,10 +2018,10 @@ bool CSimulation::bWriteVectorGISFile(int const nDataItem, string const* strPlot OGRPt.setY(dGridCentroidYToExtCRSY(nY)); pOGRFeature->SetGeometry(&OGRPt); - double const dOrientation = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle(); - double const dHeight = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight(); + double const dOrientation = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveAngle(); + double const dHeight = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight(); - if (bFPIsEqual(dHeight, DBL_NODATA, TOLERANCE) || bFPIsEqual(dOrientation, DBL_NODATA, TOLERANCE) || (!m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea())) + if (bFPIsEqual(dHeight, DBL_NODATA, TOLERANCE) || bFPIsEqual(dOrientation, DBL_NODATA, TOLERANCE) || (!m_pRasterGrid->Cell(nX, nY).bIsInContiguousSea())) continue; // Set the feature's attributes diff --git a/src/init_grid.cpp b/src/init_grid.cpp index 7ededde19..8f5ee70b7 100644 --- a/src/init_grid.cpp +++ b/src/init_grid.cpp @@ -58,41 +58,41 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) m_nYMinBoundingBox = INT_MAX; m_nYMaxBoundingBox = INT_MIN; - m_ulThisIterNumSeaCells = - m_ulThisIterNumCoastCells = - m_ulThisIterNumPotentialPlatformErosionCells = + m_ulThisIterNumSeaCells = 0; + m_ulThisIterNumCoastCells = 0; + m_ulThisIterNumPotentialPlatformErosionCells = 0; m_ulThisIterNumActualPlatformErosionCells = 0; - m_ulThisIterNumPotentialBeachErosionCells = - m_ulThisIterNumActualBeachErosionCells = + m_ulThisIterNumPotentialBeachErosionCells = 0; + m_ulThisIterNumActualBeachErosionCells = 0; m_ulThisIterNumBeachDepositionCells = 0; - m_dThisIterTotSeaDepth = - m_dThisIterPotentialPlatformErosion = - m_dThisIterPotentialBeachErosion = - m_dThisIterBeachErosionFine = - m_dThisIterBeachErosionSand = - m_dThisIterBeachErosionCoarse = - m_dThisIterBeachDepositionSand = - m_dThisIterBeachDepositionCoarse = - m_dThisIterPotentialSedLostBeachErosion = - m_dThisIterFineSedimentToSuspension = - m_dThisIterCliffCollapseErosionFineUncons = - m_dThisIterCliffCollapseErosionSandUncons = - m_dThisIterCliffCollapseErosionCoarseUncons = - m_dThisIterUnconsSandCliffDeposition = - m_dThisIterUnconsCoarseCliffDeposition = - m_dThisIterCliffCollapseErosionFineCons = - m_dThisIterCliffCollapseErosionSandCons = - m_dThisIterCliffCollapseErosionCoarseCons = - m_dThisIterActualPlatformErosionFineCons = - m_dThisIterActualPlatformErosionSandCons = - m_dThisIterActualPlatformErosionCoarseCons = - m_dThisIterLeftGridUnconsFine = // TODO 067 Suspended fine sediment never decreases i.e. no suspended fine sediment ever leaves the grid. Is this OK? - m_dThisIterLeftGridUnconsSand = - m_dThisIterLeftGridUnconsCoarse = - m_dThisiterUnconsFineInput = - m_dThisiterUnconsSandInput = + m_dThisIterTotSeaDepth = 0; + m_dThisIterPotentialPlatformErosion = 0; + m_dThisIterPotentialBeachErosion = 0; + m_dThisIterBeachErosionFine = 0; + m_dThisIterBeachErosionSand = 0; + m_dThisIterBeachErosionCoarse = 0; + m_dThisIterBeachDepositionSand = 0; + m_dThisIterBeachDepositionCoarse = 0; + m_dThisIterPotentialSedLostBeachErosion = 0; + m_dThisIterFineSedimentToSuspension = 0; + m_dThisIterCliffCollapseErosionFineUncons = 0; + m_dThisIterCliffCollapseErosionSandUncons = 0; + m_dThisIterCliffCollapseErosionCoarseUncons = 0; + m_dThisIterUnconsSandCliffDeposition = 0; + m_dThisIterUnconsCoarseCliffDeposition = 0; + m_dThisIterCliffCollapseErosionFineCons = 0; + m_dThisIterCliffCollapseErosionSandCons = 0; + m_dThisIterCliffCollapseErosionCoarseCons = 0; + m_dThisIterActualPlatformErosionFineCons = 0; + m_dThisIterActualPlatformErosionSandCons = 0; + m_dThisIterActualPlatformErosionCoarseCons = 0; + m_dThisIterLeftGridUnconsFine = 0; // TODO 067 Suspended fine sediment never decreases i.e. no suspended fine sediment ever leaves the grid. Is this OK? + m_dThisIterLeftGridUnconsSand = 0; + m_dThisIterLeftGridUnconsCoarse = 0; + m_dThisiterUnconsFineInput = 0; + m_dThisiterUnconsSandInput = 0; m_dThisiterUnconsCoarseInput = 0; for (int n = 0; n < m_nLayers; n++) @@ -106,74 +106,128 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) int nZeroThickness = 0; - m_dStartIterSuspFineAllCells = - m_dStartIterSuspFineInPolygons = - m_dStartIterUnconsFineAllCells = - m_dStartIterUnconsSandAllCells = - m_dStartIterUnconsCoarseAllCells = - m_dStartIterConsFineAllCells = - m_dStartIterConsSandAllCells = + m_dStartIterSuspFineAllCells = 0; + m_dStartIterSuspFineInPolygons = 0; + m_dStartIterUnconsFineAllCells = 0; + m_dStartIterUnconsSandAllCells = 0; + m_dStartIterUnconsCoarseAllCells = 0; + m_dStartIterConsFineAllCells = 0; + m_dStartIterConsSandAllCells = 0; m_dStartIterConsCoarseAllCells = 0; - // And go through all cells in the RasterGrid array - // Use OpenMP parallel loop with reduction clauses for thread-safe accumulation + // Cache-aligned accumulator structure to prevent false sharing between threads + // Each structure is padded to 64 bytes (typical cache line size) to ensure + // different threads' accumulators reside in separate cache lines + struct alignas(64) ThreadLocalAccumulators + { + int nZeroThickness; + double dConsFine; + double dConsSand; + double dConsCoarse; + double dSuspFine; + double dUnconsFine; + double dUnconsSand; + double dUnconsCoarse; + + ThreadLocalAccumulators() : + nZeroThickness(0), dConsFine(0), dConsSand(0), dConsCoarse(0), + dSuspFine(0), dUnconsFine(0), dUnconsSand(0), dUnconsCoarse(0) {} + }; + + // Determine maximum number of threads for pre-allocation + // This scales efficiently from dev machine (8 cores) to production (32+ cores) #ifdef _OPENMP -#pragma omp parallel for collapse(2) reduction(+ : nZeroThickness) \ - reduction(+ : m_dStartIterConsFineAllCells, m_dStartIterConsSandAllCells, m_dStartIterConsCoarseAllCells) \ - reduction(+ : m_dStartIterSuspFineAllCells, m_dStartIterUnconsFineAllCells, m_dStartIterUnconsSandAllCells, m_dStartIterUnconsCoarseAllCells) + int const nMaxThreads = omp_get_max_threads(); +#else + int const nMaxThreads = 1; #endif + // Pre-allocate cache-aligned accumulators for each thread + // This eliminates false sharing and allows lock-free parallel accumulation + std::vector threadAccum(nMaxThreads); + + // Parallel loop without reduction clauses - each thread accumulates into its own cache line + // This approach eliminates the OpenMP reduction overhead that caused thread synchronization bottlenecks +#ifdef _OPENMP +#pragma omp parallel for collapse(2) schedule(static, m_ulNumCells/nMaxThreads) +#endif for (int nX = 0; nX < m_nXGridSize; nX++) { for (int nY = 0; nY < m_nYGridSize; nY++) { + // Get thread ID to access this thread's private accumulator +#ifdef _OPENMP + int const tid = omp_get_thread_num(); +#else + int const tid = 0; +#endif + ThreadLocalAccumulators& acc = threadAccum[tid]; + + // Get cell + auto& this_cell = m_pRasterGrid->Cell(nX, nY); + // Re-initialise values for this cell - m_pRasterGrid->m_Cell[nX][nY].InitCell(); + this_cell.InitCell(); if (m_ulIter == 1) { // For the first timestep only, check to see that all cells have some sediment on them - double const dSedThickness = m_pRasterGrid->m_Cell[nX][nY].dGetTotAllSedThickness(); + double const dSedThickness = this_cell.dGetTotAllSedThickness(); if (dSedThickness <= 0) { - nZeroThickness++; + acc.nZeroThickness++; // Note: Logging from parallel regions can cause race conditions, but this is for debugging only // In production, consider collecting problematic cells and logging after the parallel region if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) { -#ifdef _OPENMP -#pragma omp critical(logging) -#endif +// #ifdef _OPENMP +// #pragma omp critical(logging) +// #endif LogStream << m_ulIter << ": " << WARN << "total sediment thickness is " << dSedThickness << " at [" << nX << "][" << nY << "] = {" << dGridCentroidXToExtCRSX(nX) << ", " << dGridCentroidYToExtCRSY(nY) << "}" << endl; } } // For the first timestep only, calculate the elevation of all this cell's layers. During the rest of the simulation, each cell's elevation is re-calculated just after any change occurs on that cell - m_pRasterGrid->m_Cell[nX][nY].CalcAllLayerElevsAndD50(); + this_cell.CalcAllLayerElevsAndD50(); } + // Accumulate into thread-local variables (no synchronization required!) // Note that these totals include sediment which is both within and outside the polygons (because we have not yet defined polygons for this iteration, duh!) - m_dStartIterConsFineAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsFineThickConsiderNotch(); - m_dStartIterConsSandAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsSandThickConsiderNotch(); - m_dStartIterConsCoarseAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsCoarseThickConsiderNotch(); - - m_dStartIterSuspFineAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); - m_dStartIterUnconsFineAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsFine(); - m_dStartIterUnconsSandAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsSand(); - m_dStartIterUnconsCoarseAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsCoarse(); + acc.dConsFine += this_cell.dGetTotConsFineThickConsiderNotch(); + acc.dConsSand += this_cell.dGetTotConsSandThickConsiderNotch(); + acc.dConsCoarse += this_cell.dGetTotConsCoarseThickConsiderNotch(); + acc.dSuspFine += this_cell.dGetSuspendedSediment(); + acc.dUnconsFine += this_cell.dGetTotUnconsFine(); + acc.dUnconsSand += this_cell.dGetTotUnconsSand(); + acc.dUnconsCoarse+= this_cell.dGetTotUnconsCoarse(); if (m_bSingleDeepWaterWaveValues) { // If we have just a single measurement for deep water waves (either given by the user, or from a single wave station) then set all cells, even dry land cells, to the same value for deep water wave height, deep water wave orientation, and deep water period - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveHeight(m_dAllCellsDeepWaterWaveHeight); - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWaveAngle(m_dAllCellsDeepWaterWaveAngle); - m_pRasterGrid->m_Cell[nX][nY].SetCellDeepWaterWavePeriod(m_dAllCellsDeepWaterWavePeriod); + this_cell.SetCellDeepWaterWaveHeight(m_dAllCellsDeepWaterWaveHeight); + this_cell.SetCellDeepWaterWaveAngle(m_dAllCellsDeepWaterWaveAngle); + this_cell.SetCellDeepWaterWavePeriod(m_dAllCellsDeepWaterWavePeriod); } } } + // After parallel region: combine thread-local results sequentially + // This is fast (O(nThreads), typically 8-32 operations) compared to the parallel work (O(nCells)) + // and eliminates all the false sharing and synchronization overhead from the parallel loop + for (int t = 0; t < nMaxThreads; t++) + { + nZeroThickness += threadAccum[t].nZeroThickness; + m_dStartIterConsFineAllCells += threadAccum[t].dConsFine; + m_dStartIterConsSandAllCells += threadAccum[t].dConsSand; + m_dStartIterConsCoarseAllCells += threadAccum[t].dConsCoarse; + m_dStartIterSuspFineAllCells += threadAccum[t].dSuspFine; + m_dStartIterUnconsFineAllCells += threadAccum[t].dUnconsFine; + m_dStartIterUnconsSandAllCells += threadAccum[t].dUnconsSand; + m_dStartIterUnconsCoarseAllCells+= threadAccum[t].dUnconsCoarse; + } + if (m_bHaveWaveStationData && (! m_bSingleDeepWaterWaveValues)) { // Each cell's value for deep water wave height and deep water wave orientation is interpolated from multiple user-supplied values @@ -222,7 +276,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) // for (int nX = 0; nX < m_nXGridSize; nX++) // { // // Write this value to the array - // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveHeight(); + // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveHeight(); // nn++; // } // } @@ -251,7 +305,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) // for (int nX = 0; nX < m_nXGridSize; nX++) // { // // Write this value to the array - // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].dGetCellDeepWaterWaveAngle(); + // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).dGetCellDeepWaterWaveAngle(); // nn++; // } // } @@ -280,7 +334,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) // for (int nX = 0; nX < m_nXGridSize; nX++) // { // // Write this value to the array - // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); + // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); // nn++; // } // } @@ -309,7 +363,7 @@ int CSimulation::nInitGridAndCalcStillWaterLevel(void) // for (int nX = 0; nX < m_nXGridSize; nX++) // { // // Write this value to the array - // pdRaster[nn] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); + // pdRaster[nn] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); // nn++; // } // } diff --git a/src/interpolate.cpp b/src/interpolate.cpp index 08c5b8a9f..2af199638 100644 --- a/src/interpolate.cpp +++ b/src/interpolate.cpp @@ -112,25 +112,47 @@ double CSimulation::dGetInterpolatedValue(vector const* pVnXdata, vector const* pVdX, double const dValueIn) { - double dLastValue = DBL_MAX; - int nIndexFound = 0; + int const nSize = static_cast(pVdX->size()); - for (unsigned int i = 0; i < pVdX->size(); ++i) + if (nSize == 0) + return 0; + + if (nSize == 1) + return 0; + + // Check if beyond bounds + if (dValueIn <= pVdX->at(0)) + return 0; + + if (dValueIn >= pVdX->at(nSize - 1)) + return nSize - 1; + + // Binary search to find the two points that bracket dValueIn + int nLeft = 0; + int nRight = nSize - 1; + + while (nRight - nLeft > 1) { - double const dThisValue = tAbs(dValueIn - pVdX->at(i)); + int const nMid = (nLeft + nRight) / 2; - if (dThisValue <= dLastValue) - { - dLastValue = dThisValue; - nIndexFound = i; - } + if (pVdX->at(nMid) <= dValueIn) + nLeft = nMid; + else + nRight = nMid; } - return nIndexFound; + // Now nLeft and nRight bracket dValueIn (or nLeft == nRight) + // Return the index of whichever is closer + double const dDistLeft = tAbs(dValueIn - pVdX->at(nLeft)); + double const dDistRight = tAbs(dValueIn - pVdX->at(nRight)); + + return (dDistLeft <= dDistRight) ? nLeft : nRight; } //=============================================================================================================================== diff --git a/src/locate_cliff_toe.cpp b/src/locate_cliff_toe.cpp index 90bee4282..7231a9068 100644 --- a/src/locate_cliff_toe.cpp +++ b/src/locate_cliff_toe.cpp @@ -78,16 +78,16 @@ void CSimulation::nCalcSlopeAtAllCells(void) // Look at the four surounding cells if ((nX > 0) && (nX < m_nXGridSize - 1) && (nY > 0) && (nY < m_nYGridSize - 1)) { - double const dElevLeft = m_pRasterGrid->m_Cell[nX - 1][nY].dGetSedimentTopElev(); - double const dElevRight = m_pRasterGrid->m_Cell[nX + 1][nY].dGetSedimentTopElev(); - double const dElevUp = m_pRasterGrid->m_Cell[nX][nY - 1].dGetSedimentTopElev(); - double const dElevDown = m_pRasterGrid->m_Cell[nX][nY + 1].dGetSedimentTopElev(); + double const dElevLeft = m_pRasterGrid->Cell(nX - 1, nY).dGetSedimentTopElev(); + double const dElevRight = m_pRasterGrid->Cell(nX + 1, nY).dGetSedimentTopElev(); + double const dElevUp = m_pRasterGrid->Cell(nX, nY - 1).dGetSedimentTopElev(); + double const dElevDown = m_pRasterGrid->Cell(nX, nY + 1).dGetSedimentTopElev(); // Calculate slope using finite difference method double const dSlopeX = (dElevRight - dElevLeft) / (2.0 * m_dCellSide); double const dSlopeY = (dElevDown - dElevUp) / (2.0 * m_dCellSide); double const dSlope = sqrt(dSlopeX * dSlopeX + dSlopeY * dSlopeY); - m_pRasterGrid->m_Cell[nX][nY].SetSlopeForCliffToe(dSlope); + m_pRasterGrid->Cell(nX, nY).SetSlopeForCliffToe(dSlope); } } } @@ -102,10 +102,10 @@ void CSimulation::nLocateCliffCell(void) { for (int nY = 0; nY < m_nYGridSize; nY++) { - double const dSlope = m_pRasterGrid->m_Cell[nX][nY].dGetSlopeForCliffToe(); + double const dSlope = m_pRasterGrid->Cell(nX, nY).dGetSlopeForCliffToe(); if (dSlope >= m_dSlopeThresholdForCliffToe) { - m_pRasterGrid->m_Cell[nX][nY].SetAsCliffToe(true); + m_pRasterGrid->Cell(nX, nY).SetAsCliffToe(true); } } } @@ -127,7 +127,7 @@ void CSimulation::nRemoveSmallCliffIslands(int const dMinCliffCellThreshold) for (unsigned int nY = 0; nY < static_cast(m_nYGridSize); nY++) { // Check if this is an unvisited cliff cell - if ((! bVisited[nX][nY]) && m_pRasterGrid->m_Cell[nX][nY].bIsCliffToe()) + if ((! bVisited[nX][nY]) && m_pRasterGrid->Cell(nX, nY).bIsCliffToe()) { // Found the start of a new cliff region - use flood fill to find all connected cliff cells vector> VCurrentCliffRegion; @@ -148,7 +148,7 @@ void CSimulation::nRemoveSmallCliffIslands(int const dMinCliffCellThreshold) // Skip if already visited or out of bounds if (nCurX >= static_cast(m_nXGridSize) || nCurY >= static_cast(m_nYGridSize) || bVisited[nCurX][nCurY] || - (! m_pRasterGrid->m_Cell[nCurX][nCurY].bIsCliffToe())) + (! m_pRasterGrid->Cell(nCurX, nCurY).bIsCliffToe())) { continue; } @@ -183,7 +183,7 @@ void CSimulation::nRemoveSmallCliffIslands(int const dMinCliffCellThreshold) // Remove cliff designation from all small island cells for (const auto &cell : VSmallIslandCells) { - m_pRasterGrid->m_Cell[cell.first][cell.second].SetAsCliffToe(false); + m_pRasterGrid->Cell(cell.first, cell.second).SetAsCliffToe(false); } } @@ -205,10 +205,10 @@ void CSimulation::nTraceSeawardCliffEdge(void) { for (int nY = 2; nY < m_nYGridSize - 2; nY++) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsCliffToe()) + if (m_pRasterGrid->Cell(nX, nY).bIsCliffToe()) { // East direction (check if this is a seaward-facing cliff toe) - if (! m_pRasterGrid->m_Cell[nX][nY + 1].bIsCliffToe()) + if (! m_pRasterGrid->Cell(nX, nY + 1).bIsCliffToe()) { V2DIPossibleStartCell.push_back(CGeom2DIPoint(nX, nY)); VbPossibleStartCellHandedness.push_back(true); @@ -216,7 +216,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // South direction - if (! m_pRasterGrid->m_Cell[nX + 1][nY].bIsCliffToe()) + if (! m_pRasterGrid->Cell(nX + 1, nY).bIsCliffToe()) { V2DIPossibleStartCell.push_back(CGeom2DIPoint(nX, nY)); VbPossibleStartCellHandedness.push_back(true); @@ -224,7 +224,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // West direction - if (! m_pRasterGrid->m_Cell[nX][nY - 1].bIsCliffToe()) + if (! m_pRasterGrid->Cell(nX, nY - 1).bIsCliffToe()) { V2DIPossibleStartCell.push_back(CGeom2DIPoint(nX, nY)); VbPossibleStartCellHandedness.push_back(true); @@ -232,7 +232,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // North direction - if (! m_pRasterGrid->m_Cell[nX - 1][nY].bIsCliffToe()) + if (! m_pRasterGrid->Cell(nX - 1, nY).bIsCliffToe()) { V2DIPossibleStartCell.push_back(CGeom2DIPoint(nX, nY)); VbPossibleStartCellHandedness.push_back(true); @@ -344,7 +344,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) bool bFoundNextCell = false; // 1. Try seaward (right turn) - if (bIsWithinValidGrid(nXSeaward, nYSeaward) && m_pRasterGrid->m_Cell[nXSeaward][nYSeaward].bIsCliffToe()) + if (bIsWithinValidGrid(nXSeaward, nYSeaward) && m_pRasterGrid->Cell(nXSeaward, nYSeaward).bIsCliffToe()) { nX = nXSeaward; nY = nYSeaward; @@ -373,7 +373,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // 2. Try straight ahead - else if (bIsWithinValidGrid(nXStraightOn, nYStraightOn) && m_pRasterGrid->m_Cell[nXStraightOn][nYStraightOn].bIsCliffToe()) + else if (bIsWithinValidGrid(nXStraightOn, nYStraightOn) && m_pRasterGrid->Cell(nXStraightOn, nYStraightOn).bIsCliffToe()) { nX = nXStraightOn; nY = nYStraightOn; @@ -383,7 +383,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // 3. Try anti-seaward (left turn) - else if (bIsWithinValidGrid(nXAntiSeaward, nYAntiSeaward) && m_pRasterGrid->m_Cell[nXAntiSeaward][nYAntiSeaward].bIsCliffToe()) + else if (bIsWithinValidGrid(nXAntiSeaward, nYAntiSeaward) && m_pRasterGrid->Cell(nXAntiSeaward, nYAntiSeaward).bIsCliffToe()) { nX = nXAntiSeaward; nY = nYAntiSeaward; @@ -412,7 +412,7 @@ void CSimulation::nTraceSeawardCliffEdge(void) } // 4. Try going back (U-turn) - else if (bIsWithinValidGrid(nXGoBack, nYGoBack) && m_pRasterGrid->m_Cell[nXGoBack][nYGoBack].bIsCliffToe()) + else if (bIsWithinValidGrid(nXGoBack, nYGoBack) && m_pRasterGrid->Cell(nXGoBack, nYGoBack).bIsCliffToe()) { nX = nXGoBack; nY = nYGoBack; @@ -550,15 +550,15 @@ CGeomLine CSimulation::nValidateCliffToeDirection(CGeomLine& CliffEdge, bool bRe // Check if perpendicular cells are within bounds if (bIsWithinValidGrid(nLeftX, nLeftY) && bIsWithinValidGrid(nRightX, nRightY)) { - bool const bLeftIsCliff = m_pRasterGrid->m_Cell[nLeftX][nLeftY].bIsCliffToe(); - bool const bRightIsCliff = m_pRasterGrid->m_Cell[nRightX][nRightY].bIsCliffToe(); + bool const bLeftIsCliff = m_pRasterGrid->Cell(nLeftX, nLeftY).bIsCliffToe(); + bool const bRightIsCliff = m_pRasterGrid->Cell(nRightX, nRightY).bIsCliffToe(); // One should be cliff and one should be not cliff for a valid cliff edge if (bLeftIsCliff != bRightIsCliff) { // Get the elevation of these two adjacent cells - double const dLeftElev = m_pRasterGrid->m_Cell[nLeftX][nLeftY].dGetSedimentTopElev(); - double const dRightElev = m_pRasterGrid->m_Cell[nRightX][nRightY].dGetSedimentTopElev(); + double const dLeftElev = m_pRasterGrid->Cell(nLeftX, nLeftY).dGetSedimentTopElev(); + double const dRightElev = m_pRasterGrid->Cell(nRightX, nRightY).dGetSedimentTopElev(); // Determine which is the cliff side and which is the non-cliff side double dCliffElev; diff --git a/src/locate_coast.cpp b/src/locate_coast.cpp index c068c784d..23d9babb0 100644 --- a/src/locate_coast.cpp +++ b/src/locate_coast.cpp @@ -1,8 +1,11 @@ /*! \file locate_coast.cpp - \brief Finds the coastline on the raster grid - \details TODO 001 A more detailed description of these routines. + \brief Finds the coastline on the raster grid using constrained flood-fill + \details Implements a seed-based constrained flood-fill algorithm that identifies sea cells + by expanding from known sea locations (grid edges) through cells below still water level. + This prevents incorrect flooding of isolated depressions behind elevation barriers. + See DEPRESSION_FLOODING_BUG_FIX.md for detailed algorithm documentation. \author David Favis-Mortlock \author Andres Payo \date 2025 @@ -93,45 +96,100 @@ int CSimulation::nLocateSeaAndCoasts(void) //=============================================================================================================================== void CSimulation::FindAllSeaCells(void) { - // Go along the list of edge cells - for (unsigned int n = 0; n < m_VEdgeCell.size(); n++) + // Seed-based constrained flood-fill algorithm + // Seeds can come from either: (1) shapefile points, or (2) grid edge cells + // Phase 1: Identify valid seed points (parallelized if using edge cells) + // Phase 2: Sequential flood-fill from each seed with elevation constraints + + vector vSeeds; // Will hold all valid seed points + + // Check if we have seed points from shapefile + if (! m_VSeaFloodSeedPoint.empty()) { - if (m_bOmitSearchNorthEdge && m_VEdgeCellEdge[n] == NORTH) - continue; + // Use seed points from shapefile + // Validate each seed point: must be below SWL and not yet processed + for (const auto& seedPoint : m_VSeaFloodSeedPoint) + { + int const nX = seedPoint.nGetX(); + int const nY = seedPoint.nGetY(); - if (m_bOmitSearchSouthEdge && m_VEdgeCellEdge[n] == SOUTH) - continue; + // Verify point is inundated and hasn't been processed yet + if ((m_pRasterGrid->Cell(nX, nY).bIsInundated()) && + (bFPIsEqual(m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(), 0.0, TOLERANCE))) + { + vSeeds.push_back(seedPoint); + } + else + { + // Seed point is not valid - log warning + if (! m_pRasterGrid->Cell(nX, nY).bIsInundated()) + { + LogStream << WARN << "seed point at grid [" << nX << "][" << nY << "] is above SWL (elev = " + << m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() << "m, SWL = " + << m_dThisIterSWL << "m), skipping" << endl; + } + } + } - if (m_bOmitSearchWestEdge && m_VEdgeCellEdge[n] == WEST) - continue; + LogStream << "Using " << vSeeds.size() << " seed point" << (vSeeds.size() != 1 ? "s" : "") + << " from shapefile (out of " << m_VSeaFloodSeedPoint.size() << " total)" << endl; + } + else + { + // No shapefile seeds - use grid edge cells (original behavior) + for (unsigned int n = 0; n < m_VEdgeCell.size(); n++) + { + // Check edge omission flags + if (m_bOmitSearchNorthEdge && m_VEdgeCellEdge[n] == NORTH) + continue; - if (m_bOmitSearchEastEdge && m_VEdgeCellEdge[n] == EAST) - continue; + if (m_bOmitSearchSouthEdge && m_VEdgeCellEdge[n] == SOUTH) + continue; - int const nX = m_VEdgeCell[n].nGetX(); - int const nY = m_VEdgeCell[n].nGetY(); + if (m_bOmitSearchWestEdge && m_VEdgeCellEdge[n] == WEST) + continue; + + if (m_bOmitSearchEastEdge && m_VEdgeCellEdge[n] == EAST) + continue; + + int const nX = m_VEdgeCell[n].nGetX(); + int const nY = m_VEdgeCell[n].nGetY(); + + // Check if this is a valid seed: below SWL, not yet processed, and has valid data + if ((m_pRasterGrid->Cell(nX, nY).bIsInundated()) && + (bFPIsEqual(m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(), 0.0, TOLERANCE))) + vSeeds.push_back(CGeom2DIPoint(nX, nY)); + } - if ((m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) && (bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(), 0.0, TOLERANCE))) - // This edge cell is below SWL and sea depth remains set to zero - CellByCellFillSea(nX, nY); + } + + // Phase 2: Sequential flood-fill from each seed + // This is inherently sequential due to shared state updates + for (const auto& seed : vSeeds) + { + CellByCellFillSea(seed.nGetX(), seed.nGetY()); } } //=============================================================================================================================== -//! Cell-by-cell fills all sea cells starting from a given cell. The cell-by-cell fill (aka 'floodfill') code used here is adapted from an example by Lode Vandevenne (http://lodev.org/cgtutor/floodfill.html#Scanline_Floodfill_Algorithm_With_Stack) +//! Cell-by-cell fills all sea cells starting from a given seed point, using constrained flood-fill that only expands through cells below SWL (still water level). This prevents incorrect flooding of isolated depressions behind elevation barriers. //=============================================================================================================================== void CSimulation::CellByCellFillSea(int const nXStart, int const nYStart) { // For safety check int const nRoundLoopMax = m_nXGridSize * m_nYGridSize; + // Create visited tracking array to avoid revisiting cells + vector> VVbVisited(m_nXGridSize, vector(m_nYGridSize, false)); + // Create an empty stack stack PtiStack; - // Start at the given edge cell, push this onto the stack + // Start at the given seed cell, push this onto the stack PtiStack.push(CGeom2DIPoint(nXStart, nYStart)); + VVbVisited[nXStart][nYStart] = true; - // Then do the cell-by-cell fill loop until there are no more cell coordinates on the stack + // Do the cell-by-cell fill loop until there are no more cell coordinates on the stack int nRoundLoop = 0; while (! PtiStack.empty()) @@ -143,35 +201,30 @@ void CSimulation::CellByCellFillSea(int const nXStart, int const nYStart) CGeom2DIPoint const Pti = PtiStack.top(); PtiStack.pop(); - int nX = Pti.nGetX(); + int const nX = Pti.nGetX(); int const nY = Pti.nGetY(); - while ((nX >= 0) && (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) && (m_pRasterGrid->m_Cell[nX][nY].bIsInundated())) - nX--; - - nX++; + // Check if this cell should be flooded (below SWL and not yet processed) + double const dElev = m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev(); - bool bSpanAbove = false; - bool bSpanBelow = false; - - while ((nX < m_nXGridSize) && (!m_pRasterGrid->m_Cell[nX][nY].bBasementElevIsMissingValue()) && (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) && (bFPIsEqual(m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(), 0.0, TOLERANCE))) + if (dElev < m_dThisIterSWL && bFPIsEqual(m_pRasterGrid->Cell(nX, nY).dGetSeaDepth(), 0.0, TOLERANCE)) { // Set the sea depth for this cell - m_pRasterGrid->m_Cell[nX][nY].SetSeaDepth(); + m_pRasterGrid->Cell(nX, nY).SetSeaDepth(); - CRWCellLandform* pLandform = m_pRasterGrid->m_Cell[nX][nY].pGetLandform(); + CRWCellLandform* pLandform = m_pRasterGrid->Cell(nX, nY).pGetLandform(); int const nCat = pLandform->nGetLFCategory(); // Have we had sediment input here? if ((nCat == LF_CAT_SEDIMENT_INPUT) || (nCat == LF_CAT_SEDIMENT_INPUT_SUBMERGED) || (nCat == LF_CAT_SEDIMENT_INPUT_NOT_SUBMERGED)) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInundated()) + if (m_pRasterGrid->Cell(nX, nY).bIsInundated()) { pLandform->SetLFCategory(LF_CAT_SEDIMENT_INPUT_SUBMERGED); - m_pRasterGrid->m_Cell[nX][nY].SetInContiguousSea(); + m_pRasterGrid->Cell(nX, nY).SetInContiguousSea(); - // Set this sea cell to have deep water (off-shore) wave orientation and height, will change this later for cells closer to the shoreline if we have on-shore waves - m_pRasterGrid->m_Cell[nX][nY].SetWaveValuesToDeepWaterWaveValues(); + // Set this sea cell to have deep water (off-shore) wave orientation and height + m_pRasterGrid->Cell(nX, nY).SetWaveValuesToDeepWaterWaveValues(); } else { @@ -181,14 +234,14 @@ void CSimulation::CellByCellFillSea(int const nXStart, int const nYStart) else { // No sediment input here, just mark as sea - m_pRasterGrid->m_Cell[nX][nY].SetInContiguousSea(); + m_pRasterGrid->Cell(nX, nY).SetInContiguousSea(); pLandform->SetLFCategory(LF_CAT_SEA); - // Set this sea cell to have deep water (off-shore) wave orientation and height, will change this later for cells closer to the shoreline if we have on-shore waves - m_pRasterGrid->m_Cell[nX][nY].SetWaveValuesToDeepWaterWaveValues(); + // Set this sea cell to have deep water (off-shore) wave orientation and height + m_pRasterGrid->Cell(nX, nY).SetWaveValuesToDeepWaterWaveValues(); } - // Now sort out the x-y extremities of the contiguous sea for the bounding box (used later in wave propagation) + // Update bounding box for wave propagation if (nX < m_nXMinBoundingBox) m_nXMinBoundingBox = nX; @@ -204,100 +257,40 @@ void CSimulation::CellByCellFillSea(int const nXStart, int const nYStart) // Update count m_ulThisIterNumSeaCells++; - if ((! bSpanAbove) && (nY > 0) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bBasementElevIsMissingValue()) && (m_pRasterGrid->m_Cell[nX][nY - 1].bIsInundated())) - { - PtiStack.push(CGeom2DIPoint(nX, nY - 1)); - bSpanAbove = true; - } - else if (bSpanAbove && (nY > 0) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bBasementElevIsMissingValue()) && (!m_pRasterGrid->m_Cell[nX][nY - 1].bIsInundated())) - { - bSpanAbove = false; - } + // Check all 4 neighbors (N, S, E, W) and add to stack if they meet criteria + int const VnDX[] = {0, 1, 0, -1}; // Neighbor offsets: N, E, S, W + int const VnDY[] = {-1, 0, 1, 0}; - if ((! bSpanBelow) && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bBasementElevIsMissingValue()) && (m_pRasterGrid->m_Cell[nX][nY + 1].bIsInundated())) - { - PtiStack.push(CGeom2DIPoint(nX, nY + 1)); - bSpanBelow = true; - } - else if (bSpanBelow && (nY < m_nYGridSize - 1) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bBasementElevIsMissingValue()) && (!m_pRasterGrid->m_Cell[nX][nY + 1].bIsInundated())) + for (int dir = 0; dir < 4; dir++) { - bSpanBelow = false; - } + int const nXN = nX + VnDX[dir]; + int const nYN = nY + VnDY[dir]; - nX++; - } - } + // Check bounds + if (nXN < 0 || nXN >= m_nXGridSize || nYN < 0 || nYN >= m_nYGridSize) + continue; - // // DEBUG CODE =========================================================================================================== - // string strOutFile = m_strOutPath + "is_contiguous_sea_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // int n = 0; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[n++] = m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea(); - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // if (nRet == CE_Failure) - // return; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== - - // // DEBUG CODE =========================================================================================================== - // string strOutFile = m_strOutPath + "is_inundated_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // - // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // int n = 0; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[n++] = m_pRasterGrid->m_Cell[nX][nY].bIsInundated(); - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // if (nRet == CE_Failure) - // return; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== + // Check if already visited + if (VVbVisited[nXN][nYN]) + continue; + + // Check for missing data + if (m_pRasterGrid->Cell(nXN, nYN).bBasementElevIsMissingValue()) + continue; - // // DEBUG CODE =========================================================================================================== - // LogStream << m_ulIter << ": cell-by-cell fill of sea from [" << nXStart << "][" << nYStart << "] = {" << dGridCentroidXToExtCRSX(nXStart) << ", " << dGridCentroidYToExtCRSY(nYStart) << "} with SWL = " << m_dThisIterSWL << ", " << m_ulThisIterNumSeaCells << " of " << m_ulNumCells << " cells now marked as sea (" << fixed << setprecision(3) << 100.0 * m_ulThisIterNumSeaCells / m_ulNumCells << " %)" << endl; + // Get neighbor elevation + double const dElevN = m_pRasterGrid->Cell(nXN, nYN).dGetSedimentTopElev(); - // LogStream << " m_nXMinBoundingBox = " << m_nXMinBoundingBox << " m_nXMaxBoundingBox = " << m_nXMaxBoundingBox << " m_nYMinBoundingBox = " << m_nYMinBoundingBox << " m_nYMaxBoundingBox = " << m_nYMaxBoundingBox << endl; - // // DEBUG CODE =========================================================================================================== + // KEY CONSTRAINT: Only expand through cells below SWL + // This prevents flooding isolated depressions behind barriers + if (dElevN < m_dThisIterSWL) + { + PtiStack.push(CGeom2DIPoint(nXN, nYN)); + VVbVisited[nXN][nYN] = true; + } + } + } + } } //=============================================================================================================================== @@ -335,17 +328,17 @@ int CSimulation::nTraceAllCoasts(void) int const nYNext = m_VEdgeCell[n + 1].nGetY(); // Get "Is it sea?" information for 'this' and 'next' cells - bool const bThisCellIsSea = m_pRasterGrid->m_Cell[nXThis][nYThis].bIsInContiguousSea(); - bool const bNextCellIsSea = m_pRasterGrid->m_Cell[nXNext][nYNext].bIsInContiguousSea(); + bool const bThisCellIsSea = m_pRasterGrid->Cell(nXThis, nYThis).bIsInContiguousSea(); + bool const bNextCellIsSea = m_pRasterGrid->Cell(nXNext, nYNext).bIsInContiguousSea(); // Are we at a coast? if ((! bThisCellIsSea) && bNextCellIsSea) { // 'This' cell is just inland, has it already been flagged as a possible start for a coastline (even if this subsequently 'failed' as a coastline)? - if (! m_pRasterGrid->m_Cell[nXThis][nYThis].bIsPossibleCoastStartCell()) + if (! m_pRasterGrid->Cell(nXThis, nYThis).bIsPossibleCoastStartCell()) { // It has not, so flag it - m_pRasterGrid->m_Cell[nXThis][nYThis].SetPossibleCoastStartCell(); + m_pRasterGrid->Cell(nXThis, nYThis).SetPossibleCoastStartCell(); if (m_nLogFileDetail >= LOG_FILE_ALL) LogStream << m_ulIter << ": flagging [" << nXThis << "][" << nYThis << "] = {" << dGridCentroidXToExtCRSX(nXThis) << ", " << dGridCentroidYToExtCRSY(nYThis) << "} as possible coast start cell (left_handed edge)" << endl; @@ -360,10 +353,10 @@ int CSimulation::nTraceAllCoasts(void) else if (bThisCellIsSea && (! bNextCellIsSea)) { // The 'next' cell is just inland, has it already been flagged as a possible start for a coastline (even if this subsequently 'failed' as a coastline)? - if (! m_pRasterGrid->m_Cell[nXNext][nYNext].bIsPossibleCoastStartCell()) + if (! m_pRasterGrid->Cell(nXNext, nYNext).bIsPossibleCoastStartCell()) { // It has not, so flag it - m_pRasterGrid->m_Cell[nXNext][nYNext].SetPossibleCoastStartCell(); + m_pRasterGrid->Cell(nXNext, nYNext).SetPossibleCoastStartCell(); if (m_nLogFileDetail >= LOG_FILE_ALL) LogStream << m_ulIter << ": flagging [" << nXNext << "][" << nYNext << "] = {" << dGridCentroidXToExtCRSX(nXNext) << ", " << dGridCentroidYToExtCRSY(nYNext) << "} as possible coast start cell (right_handed edge)" << endl; @@ -496,7 +489,7 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in bHasLeftStartEdge = true; // Flag this cell to ensure that it is not chosen as a coastline start cell later - m_pRasterGrid->m_Cell[nX][nY].SetPossibleCoastStartCell(); + m_pRasterGrid->Cell(nX, nY).SetPossibleCoastStartCell(); // LogStream << "Flagging [" << nX << "][" << nY << "] as possible coast start cell NOT YET LEFT EDGE" << endl; } @@ -548,217 +541,217 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in // Set up the variables switch (nHandedness) { - case RIGHT_HANDED: - // The sea is to the right-hand side of the coast as we traverse it. We are just inland, so we need to keep heading right to find the sea - switch (nSearchDirection) - { - case NORTH: - // The sea is towards the RHS (E) of the coast, so first try to go right (to the E) - nXSeaward = nX + 1; - nYSeaward = nY; - nSeawardNewDirection = EAST; - - // If can't do this, try to go straight on (to the N) - nXStraightOn = nX; - nYStraightOn = nY - 1; - - // If can't do either of these, try to go anti-seaward i.e. towards the LHS (W) - nXAntiSeaward = nX - 1; - nYAntiSeaward = nY; - nAntiSeawardNewDirection = WEST; - - // As a last resort, go back (to the S) - nXGoBack = nX; - nYGoBack = nY + 1; - nGoBackNewDirection = SOUTH; + case RIGHT_HANDED: + // The sea is to the right-hand side of the coast as we traverse it. We are just inland, so we need to keep heading right to find the sea + switch (nSearchDirection) + { + case NORTH: + // The sea is towards the RHS (E) of the coast, so first try to go right (to the E) + nXSeaward = nX + 1; + nYSeaward = nY; + nSeawardNewDirection = EAST; + + // If can't do this, try to go straight on (to the N) + nXStraightOn = nX; + nYStraightOn = nY - 1; + + // If can't do either of these, try to go anti-seaward i.e. towards the LHS (W) + nXAntiSeaward = nX - 1; + nYAntiSeaward = nY; + nAntiSeawardNewDirection = WEST; + + // As a last resort, go back (to the S) + nXGoBack = nX; + nYGoBack = nY + 1; + nGoBackNewDirection = SOUTH; - break; + break; - case EAST: - // The sea is towards the RHS (S) of the coast, so first try to go right (to the S) - nXSeaward = nX; - nYSeaward = nY + 1; - nSeawardNewDirection = SOUTH; + case EAST: + // The sea is towards the RHS (S) of the coast, so first try to go right (to the S) + nXSeaward = nX; + nYSeaward = nY + 1; + nSeawardNewDirection = SOUTH; - // If can't do this, try to go straight on (to the E) - nXStraightOn = nX + 1; - nYStraightOn = nY; + // If can't do this, try to go straight on (to the E) + nXStraightOn = nX + 1; + nYStraightOn = nY; - // If can't do either of these, try to go anti-seaward i.e. towards the LHS (N) - nXAntiSeaward = nX; - nYAntiSeaward = nY - 1; - nAntiSeawardNewDirection = NORTH; + // If can't do either of these, try to go anti-seaward i.e. towards the LHS (N) + nXAntiSeaward = nX; + nYAntiSeaward = nY - 1; + nAntiSeawardNewDirection = NORTH; - // As a last resort, go back (to the W) - nXGoBack = nX - 1; - nYGoBack = nY; - nGoBackNewDirection = WEST; + // As a last resort, go back (to the W) + nXGoBack = nX - 1; + nYGoBack = nY; + nGoBackNewDirection = WEST; - break; + break; - case SOUTH: - // The sea is towards the RHS (W) of the coast, so first try to go right (to the W) - nXSeaward = nX - 1; - nYSeaward = nY; - nSeawardNewDirection = WEST; + case SOUTH: + // The sea is towards the RHS (W) of the coast, so first try to go right (to the W) + nXSeaward = nX - 1; + nYSeaward = nY; + nSeawardNewDirection = WEST; - // If can't do this, try to go straight on (to the S) - nXStraightOn = nX; - nYStraightOn = nY + 1; + // If can't do this, try to go straight on (to the S) + nXStraightOn = nX; + nYStraightOn = nY + 1; - // If can't do either of these, try to go anti-seaward i.e. towards the LHS (E) - nXAntiSeaward = nX + 1; - nYAntiSeaward = nY; - nAntiSeawardNewDirection = EAST; + // If can't do either of these, try to go anti-seaward i.e. towards the LHS (E) + nXAntiSeaward = nX + 1; + nYAntiSeaward = nY; + nAntiSeawardNewDirection = EAST; - // As a last resort, go back (to the N) - nXGoBack = nX; - nYGoBack = nY - 1; - nGoBackNewDirection = NORTH; + // As a last resort, go back (to the N) + nXGoBack = nX; + nYGoBack = nY - 1; + nGoBackNewDirection = NORTH; - break; + break; - case WEST: - // The sea is towards the RHS (N) of the coast, so first try to go right (to the N) - nXSeaward = nX; - nYSeaward = nY - 1; - nSeawardNewDirection = NORTH; + case WEST: + // The sea is towards the RHS (N) of the coast, so first try to go right (to the N) + nXSeaward = nX; + nYSeaward = nY - 1; + nSeawardNewDirection = NORTH; - // If can't do this, try to go straight on (to the W) - nXStraightOn = nX - 1; - nYStraightOn = nY; + // If can't do this, try to go straight on (to the W) + nXStraightOn = nX - 1; + nYStraightOn = nY; - // If can't do either of these, try to go anti-seaward i.e. towards the LHS (S) - nXAntiSeaward = nX; - nYAntiSeaward = nY + 1; - nAntiSeawardNewDirection = SOUTH; + // If can't do either of these, try to go anti-seaward i.e. towards the LHS (S) + nXAntiSeaward = nX; + nYAntiSeaward = nY + 1; + nAntiSeawardNewDirection = SOUTH; - // As a last resort, go back (to the E) - nXGoBack = nX + 1; - nYGoBack = nY; - nGoBackNewDirection = EAST; + // As a last resort, go back (to the E) + nXGoBack = nX + 1; + nYGoBack = nY; + nGoBackNewDirection = EAST; + + break; + } break; - } - break; + case LEFT_HANDED: - case LEFT_HANDED: + // The sea is to the left-hand side of the coast as we traverse it. We are just inland, so we need to keep heading left to find the sea + switch (nSearchDirection) + { + case NORTH: + // The sea is towards the LHS (W) of the coast, so first try to go left (to the W) + nXSeaward = nX - 1; + nYSeaward = nY; + nSeawardNewDirection = WEST; + + // If can't do this, try to go straight on (to the N) + nXStraightOn = nX; + nYStraightOn = nY - 1; + + // If can't do either of these, try to go anti-seaward i.e. towards the RHS (E) + nXAntiSeaward = nX + 1; + nYAntiSeaward = nY; + nAntiSeawardNewDirection = EAST; + + // As a last resort, go back (to the S) + nXGoBack = nX; + nYGoBack = nY + 1; + nGoBackNewDirection = SOUTH; - // The sea is to the left-hand side of the coast as we traverse it. We are just inland, so we need to keep heading left to find the sea - switch (nSearchDirection) - { - case NORTH: - // The sea is towards the LHS (W) of the coast, so first try to go left (to the W) - nXSeaward = nX - 1; - nYSeaward = nY; - nSeawardNewDirection = WEST; - - // If can't do this, try to go straight on (to the N) - nXStraightOn = nX; - nYStraightOn = nY - 1; - - // If can't do either of these, try to go anti-seaward i.e. towards the RHS (E) - nXAntiSeaward = nX + 1; - nYAntiSeaward = nY; - nAntiSeawardNewDirection = EAST; - - // As a last resort, go back (to the S) - nXGoBack = nX; - nYGoBack = nY + 1; - nGoBackNewDirection = SOUTH; + break; - break; + case EAST: + // The sea is towards the LHS (N) of the coast, so first try to go left (to the N) + nXSeaward = nX; + nYSeaward = nY - 1; + nSeawardNewDirection = NORTH; - case EAST: - // The sea is towards the LHS (N) of the coast, so first try to go left (to the N) - nXSeaward = nX; - nYSeaward = nY - 1; - nSeawardNewDirection = NORTH; + // If can't do this, try to go straight on (to the E) + nXStraightOn = nX + 1; + nYStraightOn = nY; - // If can't do this, try to go straight on (to the E) - nXStraightOn = nX + 1; - nYStraightOn = nY; + // If can't do either of these, try to go anti-seaward i.e. towards the RHS (S) + nXAntiSeaward = nX; + nYAntiSeaward = nY + 1; + nAntiSeawardNewDirection = SOUTH; - // If can't do either of these, try to go anti-seaward i.e. towards the RHS (S) - nXAntiSeaward = nX; - nYAntiSeaward = nY + 1; - nAntiSeawardNewDirection = SOUTH; + // As a last resort, go back (to the W) + nXGoBack = nX - 1; + nYGoBack = nY; + nGoBackNewDirection = WEST; - // As a last resort, go back (to the W) - nXGoBack = nX - 1; - nYGoBack = nY; - nGoBackNewDirection = WEST; + break; - break; + case SOUTH: + // The sea is towards the LHS (E) of the coast, so first try to go left (to the E) + nXSeaward = nX + 1; + nYSeaward = nY; + nSeawardNewDirection = EAST; - case SOUTH: - // The sea is towards the LHS (E) of the coast, so first try to go left (to the E) - nXSeaward = nX + 1; - nYSeaward = nY; - nSeawardNewDirection = EAST; + // If can't do this, try to go straight on (to the S) + nXStraightOn = nX; + nYStraightOn = nY + 1; - // If can't do this, try to go straight on (to the S) - nXStraightOn = nX; - nYStraightOn = nY + 1; + // If can't do either of these, try to go anti-seaward i.e. towards the RHS (W) + nXAntiSeaward = nX - 1; + nYAntiSeaward = nY; + nAntiSeawardNewDirection = WEST; - // If can't do either of these, try to go anti-seaward i.e. towards the RHS (W) - nXAntiSeaward = nX - 1; - nYAntiSeaward = nY; - nAntiSeawardNewDirection = WEST; + // As a last resort, go back (to the N) + nXGoBack = nX; + nYGoBack = nY - 1; + nGoBackNewDirection = NORTH; - // As a last resort, go back (to the N) - nXGoBack = nX; - nYGoBack = nY - 1; - nGoBackNewDirection = NORTH; + break; - break; + case WEST: + // The sea is towards the LHS (S) of the coast, so first try to go left (to the S) + nXSeaward = nX; + nYSeaward = nY + 1; + nSeawardNewDirection = SOUTH; - case WEST: - // The sea is towards the LHS (S) of the coast, so first try to go left (to the S) - nXSeaward = nX; - nYSeaward = nY + 1; - nSeawardNewDirection = SOUTH; + // If can't do this, try to go straight on (to the W) + nXStraightOn = nX - 1; + nYStraightOn = nY; - // If can't do this, try to go straight on (to the W) - nXStraightOn = nX - 1; - nYStraightOn = nY; + // If can't do either of these, try to go anti-seaward i.e. towards the RHS (N) + nXAntiSeaward = nX; + nYAntiSeaward = nY - 1; + nAntiSeawardNewDirection = NORTH; - // If can't do either of these, try to go anti-seaward i.e. towards the RHS (N) - nXAntiSeaward = nX; - nYAntiSeaward = nY - 1; - nAntiSeawardNewDirection = NORTH; + // As a last resort, go back (to the E) + nXGoBack = nX + 1; + nYGoBack = nY; + nGoBackNewDirection = EAST; - // As a last resort, go back (to the E) - nXGoBack = nX + 1; - nYGoBack = nY; - nGoBackNewDirection = EAST; + break; + } break; - } - - break; } // Now do the actual search for this timestep: first try going in the direction of the sea. Is this seaward cell still within the grid? if (bIsWithinValidGrid(nXSeaward, nYSeaward)) { // It is, so check if the cell in the seaward direction is a sea cell - if (m_pRasterGrid->m_Cell[nXSeaward][nYSeaward].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nXSeaward, nYSeaward).bIsInContiguousSea()) { // There is sea in this seaward direction, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a coast cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->m_Cell[nX][nY].dGetInterventionTopElev() >= m_dThisIterSWL)) + if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->Cell(nX, nY).dGetInterventionTopElev() >= m_dThisIterSWL)) { // It is, so add it to the vector ILTempGridCRS.Append(&Pti); } - else if (m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() >= m_dThisIterSWL) + else if (m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() >= m_dThisIterSWL) { // The sediment top is above SWL so add it to the vector object ILTempGridCRS.Append(&Pti); @@ -781,21 +774,21 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in if (bIsWithinValidGrid(nXStraightOn, nYStraightOn)) { // It is, so check if there is sea immediately in front - if (m_pRasterGrid->m_Cell[nXStraightOn][nYStraightOn].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nXStraightOn, nYStraightOn).bIsInContiguousSea()) { // Sea is in front, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a coast cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->m_Cell[nX][nY].dGetInterventionTopElev() >= m_dThisIterSWL)) + if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->Cell(nX, nY).dGetInterventionTopElev() >= m_dThisIterSWL)) { // It is, so add it to the vector object ILTempGridCRS.Append(&Pti); } - else if (m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() >= m_dThisIterSWL) + else if (m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() >= m_dThisIterSWL) { // The sediment top is above SWL so add it to the vector object ILTempGridCRS.Append(&Pti); @@ -817,21 +810,21 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in if (bIsWithinValidGrid(nXAntiSeaward, nYAntiSeaward)) { // It is, so check if there is sea in this anti-seaward cell - if (m_pRasterGrid->m_Cell[nXAntiSeaward][nYAntiSeaward].bIsInContiguousSea()) + if (m_pRasterGrid->Cell(nXAntiSeaward, nYAntiSeaward).bIsInContiguousSea()) { // There is sea on the anti-seaward side, so we are on the coast bAtCoast = true; // Has the current cell already marked been marked as a coast cell? - if (! m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + if (! m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { // Not already marked, is this an intervention cell with the top above SWL? - if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->m_Cell[nX][nY].dGetInterventionTopElev() >= m_dThisIterSWL)) + if ((bIsInterventionCell(nX, nY)) && (m_pRasterGrid->Cell(nX, nY).dGetInterventionTopElev() >= m_dThisIterSWL)) { // It is, so add it to the vector object ILTempGridCRS.Append(&Pti); } - else if (m_pRasterGrid->m_Cell[nX][nY].dGetSedimentTopElev() >= m_dThisIterSWL) + else if (m_pRasterGrid->Cell(nX, nY).dGetSedimentTopElev() >= m_dThisIterSWL) { // The sediment top is above SWL so add it to the vector object ILTempGridCRS.Append(&Pti); @@ -936,7 +929,7 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in if ((nCoastEndX != nEndX) || (nCoastEndY != nEndY)) { // The grid-edge cell at nEndX, nEndY is not already at end of ILTempGridCRS. But is the final cell in ILTempGridCRS already at the edge of the grid? - if (! m_pRasterGrid->m_Cell[nCoastEndX][nCoastEndY].bIsBoundingBoxEdge()) + if (! m_pRasterGrid->Cell(nCoastEndX, nCoastEndY).bIsBoundingBoxEdge()) { // The final cell in ILTempGridCRS is not a grid-edge cell, so add the grid-edge cell and mark the cell as coastline ILTempGridCRS.Append(nEndX, nEndY); @@ -945,8 +938,8 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in } // Need to specify start edge and end edge for smoothing routines - int const nStartEdge = m_pRasterGrid->m_Cell[nStartX][nStartY].nGetBoundingBoxEdge(); - int const nEndEdge = m_pRasterGrid->m_Cell[nEndX][nEndY].nGetBoundingBoxEdge(); + int const nStartEdge = m_pRasterGrid->Cell(nStartX, nStartY).nGetBoundingBoxEdge(); + int const nEndEdge = m_pRasterGrid->Cell(nEndX, nEndY).nGetBoundingBoxEdge(); // Next, convert the grid coordinates in ILTempGridCRS (integer values stored as doubles) to external CRS coordinates (which will probably be non-integer, again stored as doubles). This is done now, so that smoothing is more effective CGeomLine LTempExtCRS; @@ -976,7 +969,7 @@ int CSimulation::nTraceCoastLine(unsigned int const nTraceFromStartCellIndex, in // Now mark the coastline on the grid for (int n = 0; n < nCoastSize; n++) - m_pRasterGrid->m_Cell[ILTempGridCRS[n].nGetX()][ILTempGridCRS[n].nGetY()].SetAsCoastline(nCoast); + m_pRasterGrid->Cell(ILTempGridCRS[n].nGetX(), ILTempGridCRS[n].nGetY()).SetAsCoastline(nCoast); // Set the coastline (Ext CRS) m_VCoast[nCoast].SetCoastlineExtCRS(<empExtCRS); @@ -1065,27 +1058,27 @@ int CSimulation::nLocateFloodAndCoasts(void) // Have we created any coasts? switch (m_nLevel) { - case 0: // WAVESETUP + SURGE: - { - if (m_VFloodWaveSetupSurge.empty()) + case 0: // WAVESETUP + SURGE: { - cerr << m_ulIter << ": " << ERR << "no flood coastline located: this iteration SWL = " << m_dThisIterSWL << ", maximum DEM top surface elevation = " << m_dThisIterTopElevMax << ", minimum DEM top surface elevation = " << m_dThisIterTopElevMin << endl; - return RTN_ERR_NO_COAST; - } + if (m_VFloodWaveSetupSurge.empty()) + { + cerr << m_ulIter << ": " << ERR << "no flood coastline located: this iteration SWL = " << m_dThisIterSWL << ", maximum DEM top surface elevation = " << m_dThisIterTopElevMax << ", minimum DEM top surface elevation = " << m_dThisIterTopElevMin << endl; + return RTN_ERR_NO_COAST; + } - break; - } + break; + } - case 1: // WAVESETUP + SURGE + RUNUP: - { - if (m_VFloodWaveSetupSurgeRunup.empty()) + case 1: // WAVESETUP + SURGE + RUNUP: { - cerr << m_ulIter << ": " << ERR << "no flood coastline located: this iteration SWL = " << m_dThisIterSWL << ", maximum DEM top surface elevation = " << m_dThisIterTopElevMax << ", minimum DEM top surface elevation = " << m_dThisIterTopElevMin << endl; - return RTN_ERR_NO_COAST; - } + if (m_VFloodWaveSetupSurgeRunup.empty()) + { + cerr << m_ulIter << ": " << ERR << "no flood coastline located: this iteration SWL = " << m_dThisIterSWL << ", maximum DEM top surface elevation = " << m_dThisIterTopElevMax << ", minimum DEM top surface elevation = " << m_dThisIterTopElevMin << endl; + return RTN_ERR_NO_COAST; + } - break; - } + break; + } } return RTN_OK; @@ -1100,9 +1093,9 @@ int CSimulation::FindAllInundatedCells(void) { for (int nY = 0; nY < m_nYGridSize; nY++) { - m_pRasterGrid->m_Cell[nX][nY].UnSetCheckFloodCell(); - m_pRasterGrid->m_Cell[nX][nY].UnSetInContiguousFlood(); - m_pRasterGrid->m_Cell[nX][nY].SetAsFloodline(false); + m_pRasterGrid->Cell(nX, nY).UnSetCheckFloodCell(); + m_pRasterGrid->Cell(nX, nY).UnSetInContiguousFlood(); + m_pRasterGrid->Cell(nX, nY).SetAsFloodline(false); } } @@ -1124,7 +1117,7 @@ int CSimulation::FindAllInundatedCells(void) int const nX = m_VEdgeCell[n].nGetX(); int const nY = m_VEdgeCell[n].nGetY(); - if ((! m_pRasterGrid->m_Cell[nX][nY].bIsCellFloodCheck()) && (m_pRasterGrid->m_Cell[nX][nY].bIsInundated())) + if ((! m_pRasterGrid->Cell(nX, nY).bIsCellFloodCheck()) && (m_pRasterGrid->Cell(nX, nY).bIsInundated())) { // This edge cell is below SWL and sea depth remains set to zero FloodFillLand(nX, nY); diff --git a/src/multiple_coastlines.cpp b/src/multiple_coastlines.cpp index c5372c666..13cf3d881 100644 --- a/src/multiple_coastlines.cpp +++ b/src/multiple_coastlines.cpp @@ -79,10 +79,10 @@ int CSimulation::nDoMultipleCoastlines(void) int const nY = pCell->nGetY(); // Have we hit a cell which is 'under' another coastline? - if (m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + if (m_pRasterGrid->Cell(nX, nY).bIsCoastline()) { // Yes this is a coastline cell, as well as a coast-normal profile cell - int const nHitCoast = m_pRasterGrid->m_Cell[nX][nY].nGetCoastline(); + int const nHitCoast = m_pRasterGrid->Cell(nX, nY).nGetCoastline(); if (nHitCoast != nCoast) { // We have hit a different coastline, so truncate this profile @@ -98,10 +98,10 @@ int CSimulation::nDoMultipleCoastlines(void) if (nY+1 >= m_nYGridSize) nYTmp = nY-1; - if (m_pRasterGrid->m_Cell[nX][nYTmp].bIsCoastline()) + if (m_pRasterGrid->Cell(nX, nYTmp).bIsCoastline()) { // Yes this is a coastline cell, as well as a coast-normal profile cell - int const nHitCoast = m_pRasterGrid->m_Cell[nX][nYTmp].nGetCoastline(); + int const nHitCoast = m_pRasterGrid->Cell(nX, nYTmp).nGetCoastline(); if (nHitCoast != nCoast) { // We have hit a different coastline, so truncate this profile @@ -116,11 +116,11 @@ int CSimulation::nDoMultipleCoastlines(void) if (! pProfile->bIsGridEdge()) { // Have we hit a cell which is 'under' a coast-normal profile belonging to another coast? NOTE Is a problem if get more than two coast normals passing through this cell - int nHitProfileCoast = m_pRasterGrid->m_Cell[nX][nY].nGetProfileCoastID(); + int nHitProfileCoast = m_pRasterGrid->Cell(nX, nY).nGetProfileCoastID(); if ((nHitProfileCoast != INT_NODATA) && (nHitProfileCoast != nCoast)) { // Yes, we have hit a profile which belongs to a different coast - int const nHitProfile = m_pRasterGrid->m_Cell[nX][nY].nGetProfileID(); + int const nHitProfile = m_pRasterGrid->Cell(nX, nY).nGetProfileID(); // Safety check if (nHitProfile != INT_NODATA) @@ -138,11 +138,11 @@ int CSimulation::nDoMultipleCoastlines(void) if (nY+1 >= m_nYGridSize) nYTmp = nY-1; - nHitProfileCoast = m_pRasterGrid->m_Cell[nX][nYTmp].nGetProfileCoastID(); + nHitProfileCoast = m_pRasterGrid->Cell(nX, nYTmp).nGetProfileCoastID(); if ((nHitProfileCoast != INT_NODATA) && (nHitProfileCoast != nCoast)) { // Yes, we have hit a profile which belongs to a different coast - int const nHitProfile = m_pRasterGrid->m_Cell[nX][nYTmp].nGetProfileID(); + int const nHitProfile = m_pRasterGrid->Cell(nX, nYTmp).nGetProfileID(); // Safety check if (nHitProfile != INT_NODATA) @@ -241,8 +241,8 @@ int CSimulation::nTruncateProfilesDifferentCoasts(int const nThisProfileCoast, i int const nXTmp = pVThisProfileCells->at(nn).nGetX(); int const nYTmp = pVThisProfileCells->at(nn).nGetY(); - if ((m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetProfileID() == nThisProfile) && (m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetProfileCoastID() == nThisProfileCoast)) - m_pRasterGrid->m_Cell[nXTmp][nYTmp].SetCoastAndProfileID(INT_NODATA, INT_NODATA); + if ((m_pRasterGrid->Cell(nXTmp, nYTmp).nGetProfileID() == nThisProfile) && (m_pRasterGrid->Cell(nXTmp, nYTmp).nGetProfileCoastID() == nThisProfileCoast)) + m_pRasterGrid->Cell(nXTmp, nYTmp).SetCoastAndProfileID(INT_NODATA, INT_NODATA); // LogStream << "For coast " << nThisProfileCoast << " profile " << nThisProfile << " unmarking [" << nXTmp << "][" << nYTmp << "]" << endl; } @@ -253,8 +253,8 @@ int CSimulation::nTruncateProfilesDifferentCoasts(int const nThisProfileCoast, i int const nXTmp = pVHitProfileCells->at(nn).nGetX(); int const nYTmp = pVHitProfileCells->at(nn).nGetY(); - if ((m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetProfileID() == nHitProfile) && (m_pRasterGrid->m_Cell[nXTmp][nYTmp].nGetProfileCoastID() == nHitProfileCoast)) - m_pRasterGrid->m_Cell[nXTmp][nYTmp].SetCoastAndProfileID(INT_NODATA, INT_NODATA); + if ((m_pRasterGrid->Cell(nXTmp, nYTmp).nGetProfileID() == nHitProfile) && (m_pRasterGrid->Cell(nXTmp, nYTmp).nGetProfileCoastID() == nHitProfileCoast)) + m_pRasterGrid->Cell(nXTmp, nYTmp).SetCoastAndProfileID(INT_NODATA, INT_NODATA); // LogStream << "For coast " << nHitProfileCoast << " profile " << nHitProfile << " unmarking [" << nXTmp << "][" << nYTmp << "]" << endl; } @@ -343,8 +343,8 @@ int CSimulation::nTruncateProfileHitDifferentCoast(int const nCoast, int const n int const nXThis = pVProfileCells->at(nn).nGetX(); int const nYThis = pVProfileCells->at(nn).nGetY(); - if ((m_pRasterGrid->m_Cell[nXThis][nYThis].nGetProfileID() == nProfile) && (m_pRasterGrid->m_Cell[nXThis][nYThis].nGetProfileCoastID() == nCoast)) - m_pRasterGrid->m_Cell[nXThis][nYThis].SetCoastAndProfileID(INT_NODATA, INT_NODATA); + if ((m_pRasterGrid->Cell(nXThis, nYThis).nGetProfileID() == nProfile) && (m_pRasterGrid->Cell(nXThis, nYThis).nGetProfileCoastID() == nCoast)) + m_pRasterGrid->Cell(nXThis, nYThis).SetCoastAndProfileID(INT_NODATA, INT_NODATA); } // Truncate the list of cells in the profile, and then update diff --git a/src/profile.cpp b/src/profile.cpp index e43cb8928..f6e84710d 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -556,7 +556,7 @@ int CGeomProfile::nGetCellGivenDepth(CGeomRasterGrid const* pGrid, double const int const nX = m_VCellInProfile[n].nGetX(); int const nY = m_VCellInProfile[n].nGetY(); - double const dCellDepth = pGrid->m_Cell[nX][nY].dGetSeaDepth(); + double const dCellDepth = pGrid->Cell(nX, nY).dGetSeaDepth(); if (dCellDepth >= dDepthIn) { diff --git a/src/raster_grid.cpp b/src/raster_grid.cpp index 64e7f8937..f22dda83c 100644 --- a/src/raster_grid.cpp +++ b/src/raster_grid.cpp @@ -39,20 +39,17 @@ CGeomRasterGrid::CGeomRasterGrid(CSimulation* pSimIn) m_dD50Sand(0), m_dD50Coarse(0), m_pSim(pSimIn), - m_Cell(NULL) + m_nXSize(0), + m_nYSize(0), + m_CellData(NULL) { } //! Destructor CGeomRasterGrid::~CGeomRasterGrid(void) { - int const nXMax = m_pSim->nGetGridXMax(); - - // Free the m_Cell memory - for (int nX = 0; nX < nXMax; nX++) - delete[] m_Cell[nX]; - - delete[] m_Cell; + // Free the single contiguous cell array + delete[] m_CellData; } //! Returns a pointer to the simulation object @@ -61,25 +58,22 @@ CSimulation* CGeomRasterGrid::pGetSim(void) return m_pSim; } -// CGeomCell* CGeomRasterGrid::pGetCell(int const nX, int const nY) -// { -// return &m_Cell[nX][nY]; -// } - -//! Creates the 2D CGeomCell array +//! Creates a single contiguous CGeomCell array for optimal cache performance int CGeomRasterGrid::nCreateGrid(void) { - // Create the 2D CGeomCell array (this is faster than using 2D STL vectors) - int const nXMax = m_pSim->nGetGridXMax(); - int const nYMax = m_pSim->nGetGridYMax(); + // Store grid dimensions + m_nXSize = m_pSim->nGetGridXMax(); + m_nYSize = m_pSim->nGetGridYMax(); + + // Calculate total cells needed + int const nTotalCells = m_nXSize * m_nYSize; // TODO 038 Do better error handling if insufficient memory try { - m_Cell = new CGeomCell*[nXMax]; - - for (int nX = 0; nX < nXMax; nX++) - m_Cell[nX] = new CGeomCell[nYMax]; + // Single contiguous allocation - much faster than array-of-arrays + // Memory layout: all cells in a single block, indexed as [nX * m_nYSize + nY] + m_CellData = new CGeomCell[nTotalCells]; } catch (bad_alloc&) diff --git a/src/raster_grid.h b/src/raster_grid.h index 832231d9d..935f25e95 100644 --- a/src/raster_grid.h +++ b/src/raster_grid.h @@ -50,8 +50,13 @@ class CGeomRasterGrid //! A pointer to the CSimulation object CSimulation* m_pSim; - //! The 2D array of m_Cell objects. A c-style 2D array seems to be faster than using 2D STL vectors - CGeomCell** m_Cell; + //! Grid dimensions for indexing + int m_nXSize; + int m_nYSize; + + //! Single contiguous array of cell objects for optimal cache performance + //! __restrict__ tells compiler this pointer doesn't alias, enabling better vectorization + CGeomCell* __restrict__ m_CellData; protected: public: @@ -59,7 +64,12 @@ class CGeomRasterGrid ~CGeomRasterGrid(void); CSimulation* pGetSim(void); - // CGeomCell* pGetCell(int const, int const); int nCreateGrid(void); + + //! Accessor for cell at (nX, nY) - provides optimal cache-friendly indexing + //! Implementation in raster_grid_inline.h to avoid circular dependency + inline CGeomCell& Cell(int const nX, int const nY); + inline CGeomCell const& Cell(int const nX, int const nY) const; }; + #endif // RASTERGRID_H diff --git a/src/raster_grid_inline.h b/src/raster_grid_inline.h new file mode 100644 index 000000000..4e036d54c --- /dev/null +++ b/src/raster_grid_inline.h @@ -0,0 +1,36 @@ +/*! + \file raster_grid_inline.h + \brief Inline implementations for CGeomRasterGrid + \details This file contains inline method implementations that require the full + CGeomCell definition. It must be included after both raster_grid.h and cell.h + to avoid circular dependency issues. + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License +*/ + +#ifndef RASTERGRID_INLINE_H +#define RASTERGRID_INLINE_H + +// Ensure required headers are included before this file +#ifndef RASTERGRID_H +#error "raster_grid.h must be included before raster_grid_inline.h" +#endif + +#ifndef CELL_H +#error "cell.h must be included before raster_grid_inline.h" +#endif + +// Inline accessor implementations - eliminates function call overhead in hot loops +inline CGeomCell& CGeomRasterGrid::Cell(int const nX, int const nY) +{ + return m_CellData[nX * m_nYSize + nY]; +} + +inline CGeomCell const& CGeomRasterGrid::Cell(int const nX, int const nY) const +{ + return m_CellData[nX * m_nYSize + nY]; +} + +#endif // RASTERGRID_INLINE_H diff --git a/src/read_input.cpp b/src/read_input.cpp index 5ef4e0c7a..7d48f6ab6 100644 --- a/src/read_input.cpp +++ b/src/read_input.cpp @@ -62,20 +62,46 @@ using std::random_device; #include "yaml_parser.h" //=============================================================================================================================== -//! The bReadIniFile member function reads the initialisation file +//! The bReadIniFile member function reads the initialisation file (tries YAML first, then falls back to .ini) //=============================================================================================================================== bool CSimulation::bReadIniFile(void) { - // Check if user has supplied home directory + // Store the original CME directory + string strOrigCMEDir = m_strCMEDir; + string strOrigCMEIni = m_strCMEIni; + + // Try YAML format first if (m_strCMEIni.empty()) - // if not use the cme executable directory to find cme.ini m_strCMEIni = m_strCMEDir; - else - { - // if user has supplied home directory replace cme run directory with user supplied dir m_strCMEDir = m_strCMEIni; + + string strYamlPath = m_strCMEIni + CME_YAML; + + // Check if YAML file exists + ifstream yamlTest(strYamlPath.c_str()); + if (yamlTest.good()) + { + yamlTest.close(); + // YAML file exists, try to read it + if (bReadIniYamlFile()) + return true; + + // If YAML reading failed, show error and fall through to try .ini + cerr << "Warning: failed to read " << strYamlPath << ", trying .ini format" << endl; } + else + yamlTest.close(); + + // Reset paths for .ini file attempt + m_strCMEDir = strOrigCMEDir; + m_strCMEIni = strOrigCMEIni; + + // Fall back to .ini format + if (m_strCMEIni.empty()) + m_strCMEIni = m_strCMEDir; + else + m_strCMEDir = m_strCMEIni; m_strCMEIni.append(CME_INI); @@ -241,6 +267,132 @@ bool CSimulation::bReadIniFile(void) return true; } +//=============================================================================================================================== +//! Reads the initialization file in YAML format +//=============================================================================================================================== +bool CSimulation::bReadIniYamlFile(void) +{ + // Check if user has supplied home directory + if (m_strCMEIni.empty()) + // if not use the cme executable directory to find cme.yaml + m_strCMEIni = m_strCMEDir; + + else + { + // if user has supplied home directory replace cme run directory with user supplied dir + m_strCMEDir = m_strCMEIni; + } + + m_strCMEIni.append(CME_YAML); + + // The .yaml file is assumed to be in the CoastalME executable's directory + string const strFilePathName(m_strCMEIni); + + // Tell the user what is happening + cout << READING_FILE_LOCATIONS << strFilePathName << endl; + + // Create a YAML parser + CYamlParser parser; + + // Try to parse the YAML file + if (! parser.bParseFile(strFilePathName)) + { + // Error: cannot parse YAML file + cerr << ERR << "cannot parse " << strFilePathName << ": " << parser.GetError() << endl; + return false; + } + + // Get the root node + CYamlNode root = parser.GetRoot(); + + // Read input data file path + if (root.HasChild("input_data_file")) + { + string strRH = root.GetChild("input_data_file").GetValue(); + + if (strRH.empty()) + { + cerr << ERR << "path and name of main datafile is empty in " << strFilePathName << endl; + return false; + } + + // First check that we don't already have an input run-data filename, e.g. one entered on the command-line + if (m_strDataPathName.empty()) + { + // We don't: so first check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter + if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == TILDE) || (strRH[1] == COLON)) + // It has an absolute path, so use it 'as is' + m_strDataPathName = strRH; + + else + { + // It has a relative path, so prepend the CoastalME dir + m_strDataPathName = m_strCMEDir; + m_strDataPathName.append(strRH); + } + } + } + else + { + cerr << ERR << "missing 'input_data_file' in " << strFilePathName << endl; + return false; + } + + // Read output path + if (root.HasChild("output_path")) + { + string strRH = root.GetChild("output_path").GetValue(); + + if (strRH.empty()) + { + cerr << ERR << "path for CoastalME output is empty in " << strFilePathName << endl; + return false; + } + + // Check for trailing slash on CoastalME output directory name (is vital) + if (strRH[strRH.size() - 1] != PATH_SEPARATOR) + strRH.push_back(PATH_SEPARATOR); + + // Now check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter + if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == TILDE) || (strRH[1] == COLON)) + // It is an absolute path, so use it 'as is' + m_strOutPath = strRH; + + else + { + // It is a relative path, so prepend the CoastalME dir + m_strOutPath = m_strCMEDir; + m_strOutPath.append(strRH); + } + } + else + { + cerr << ERR << "missing 'output_path' in " << strFilePathName << endl; + return false; + } + + // Read email address (optional, only useful if running under Linux/Unix) + if (root.HasChild("email_address")) + { + string strRH = root.GetChild("email_address").GetValue(); + + if (! strRH.empty()) + { + // Something was entered, do rudimentary check for valid email address + if (strRH.find('@') == string::npos) + { + cerr << ERR << "invalid email address in " << strFilePathName << endl + << "'" << strRH << "'" << endl; + return false; + } + + m_strMailAddress = strRH; + } + } + + return true; +} + //=============================================================================================================================== //! Detects whether the input file is in YAML or .dat format //=============================================================================================================================== @@ -3368,6 +3520,31 @@ bool CSimulation::bReadRunDataFile(void) strErr = "line " + to_string(nLine) + ": cliff toe slope limit must be > 0"; } + break; + + case 91: + // Sea flood fill seed point shapefile [optional - if blank, use grid edge cells] + if (! strRH.empty()) + { +#ifdef _WIN32 + // For Windows, make sure has backslashes, not Unix-style slashes + strRH = pstrChangeToBackslash(&strRH); +#endif + + // Check for absolute or relative path + if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == TILDE) || (strRH[1] == COLON)) + { + // Absolute path, use as-is + m_strSeaFloodSeedPointShapefile = strRH; + } + else + { + // Relative path, prepend CME dir + m_strSeaFloodSeedPointShapefile = m_strCMEDir; + m_strSeaFloodSeedPointShapefile.append(strRH); + } + } + break; } @@ -4264,21 +4441,43 @@ bool CSimulation::bConfigureFromYamlFile(CConfiguration &config) config.SetFinalWaterLevel( hydro.GetChild("final_water_level").GetDoubleValue()); - // Wave data configuration - if (hydro.HasChild("wave_height_time_series")) - config.SetWaveHeightTimeSeries( - hydro.GetChild("wave_height_time_series").GetValue()); - if (hydro.HasChild("wave_height_shape_file")) - config.SetWaveStationDataFile( - hydro.GetChild("wave_height_shape_file").GetValue()); - if (hydro.HasChild("wave_height")) - config.SetDeepWaterWaveHeight( - hydro.GetChild("wave_height").GetDoubleValue()); - if (hydro.HasChild("wave_orientation")) - config.SetDeepWaterWaveOrientation( - hydro.GetChild("wave_orientation").GetDoubleValue()); - if (hydro.HasChild("wave_period")) - config.SetWavePeriod(hydro.GetChild("wave_period").GetDoubleValue()); + // Wave data configuration - read wave_input_mode first + string strWaveInputMode = "fixed"; // default + if (hydro.HasChild("wave_input_mode")) + { + strWaveInputMode = hydro.GetChild("wave_input_mode").GetValue(); + config.SetWaveInputMode(strWaveInputMode); + } + + // Conditionally read wave parameters based on input mode + if (strWaveInputMode == "time_series") + { + // Read time series wave inputs + if (hydro.HasChild("wave_height_time_series")) + config.SetWaveHeightTimeSeries( + processFilePath(hydro.GetChild("wave_height_time_series").GetValue())); + if (hydro.HasChild("wave_height_shape_file")) + config.SetWaveStationDataFile( + processFilePath(hydro.GetChild("wave_height_shape_file").GetValue())); + } + else if (strWaveInputMode == "fixed") + { + // Read fixed wave condition inputs + if (hydro.HasChild("wave_height")) + config.SetDeepWaterWaveHeight( + hydro.GetChild("wave_height").GetDoubleValue()); + if (hydro.HasChild("wave_orientation")) + config.SetDeepWaterWaveOrientation( + hydro.GetChild("wave_orientation").GetDoubleValue()); + if (hydro.HasChild("wave_period")) + config.SetWavePeriod(hydro.GetChild("wave_period").GetDoubleValue()); + } + else + { + cerr << ERR << "Unknown wave_input_mode '" << strWaveInputMode + << "'. Must be 'fixed' or 'time_series'" << endl; + return false; + } // Tide data configuration if (hydro.HasChild("tide_data_file")) @@ -4322,6 +4521,9 @@ bool CSimulation::bConfigureFromYamlFile(CConfiguration &config) if (grid.HasChild("max_beach_elevation")) config.SetMaxBeachElevation( grid.GetChild("max_beach_elevation").GetDoubleValue()); + if (grid.HasChild("sea_flood_seed_shapefile")) + config.SetSeaFloodSeedPointShapefile( + processFilePath(grid.GetChild("sea_flood_seed_shapefile").GetValue())); } // Layers and Files @@ -5280,7 +5482,8 @@ bool CSimulation::bApplyConfiguration(CConfiguration const &config) { m_bHaveWaveStationData = true; m_strDeepWaterWaveStationsShapefile = config.GetWaveStationDataFile(); - m_dAllCellsDeepWaterWaveHeight = config.GetDeepWaterWaveHeight(); + // m_dAllCellsDeepWaterWaveHeight = config.GetDeepWaterWaveHeight(); + m_strDeepWaterWavesInputFile = config.GetWaveHeightTimeSeries(); } // Case 41: Tide data file (can be blank). This is the change (m) from still @@ -5291,6 +5494,9 @@ bool CSimulation::bApplyConfiguration(CConfiguration const &config) // double m_dBreakingWaveHeightDepthRatio = config.GetBreakingWaveRatio(); + // Case 91: Sea flood fill seed point shapefile (can be blank) + m_strSeaFloodSeedPointShapefile = config.GetSeaFloodSeedPointShapefile(); + // Case 43: Simulate coast platform erosion? m_bDoShorePlatformErosion = config.GetCoastPlatformErosion(); @@ -5353,7 +5559,7 @@ bool CSimulation::bApplyConfiguration(CConfiguration const &config) m_dDeanProfileStartAboveSWL = config.GetBermHeight(); // Case 59: Simulate cliff collapse? - m_dDeanProfileStartAboveSWL = config.GetCliffCollapse(); + m_bDoCliffCollapse = config.GetCliffCollapse(); // Case 60: Cliff resistance to erosion if (m_bHaveConsolidatedSediment && m_bDoCliffCollapse) diff --git a/src/run_cmake.sh b/src/run_cmake.sh index 329a4b035..7de6eb43e 100755 --- a/src/run_cmake.sh +++ b/src/run_cmake.sh @@ -20,7 +20,7 @@ done # Change this to change build type # buildtype=DEBUG -#buildtype=RELEASE +# buildtype=RELEASE # buildtype=PRERELEASE buildtype=RELWITHDEBINFO # Not yet implemented in CMakeLists.txt #buildtype=MINSIZEREL # Not yet implemented in CMakeLists.txt @@ -28,8 +28,8 @@ buildtype=RELWITHDEBINFO # Not yet implemented in CMakeLists.txt #buildtype=CALLGRIND # Change this to select the Linux compiler -# compiler=GNU -compiler=CLANG +compiler=GNU +# compiler=CLANG # Change this to select the CShore library type cshorelibrary=STATIC @@ -46,9 +46,9 @@ cd cshore if [ "$cflag" = "true" ]; then rm -f ../lib/* make clean + ./make_cshore_lib.sh fi -./make_cshore_lib.sh cd .. # Note: The cshore Makefile now correctly names libraries for MacOS automatically echo "" @@ -75,11 +75,11 @@ if [ "$cflag" = "true" ]; then rm -f CMakeCache.txt fi # CMAKE_COMPILER_ARGS="PKG_CPPFLAGS='-Xclang -fopenmp' PKG_LIBS=-lomp" -# cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$buildtype -DCOMPILER=$compiler -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout $CMAKE_COMPILER_ARGS . +# usr/bin/cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$buildtype -DCOMPILER=$compiler -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout $CMAKE_COMPILER_ARGS . #cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$buildtype -DCOMPILER=$compiler -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout -DCMAKE_VERBOSE_MAKEFILE=ON $CMAKE_COMPILER_ARGS . #cmake -DCMAKE_BUILD_TYPE=$buildtype -DCOMPILER=$compiler -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout $CMAKE_COMPILER_ARGS . -G"CodeBlocks - Unix Makefiles" # Or Ninja? -cmake -G Ninja -DCMAKE_BUILD_TYPE=$buildtype -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout $CMAKE_COMPILER_ARGS . +cmake -G Ninja -DCMAKE_BUILD_TYPE=$buildtype -DCOMPILER=$compiler -DCSHORE_LIBRARY=$cshorelibrary -DCSHORE_INOUT=$cshoreinout $CMAKE_COMPILER_ARGS . if [ "$iflag" = "true" ]; then # make install diff --git a/src/simulation.cpp b/src/simulation.cpp index fe150eea0..1dacb51cc 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -1,1459 +1,1469 @@ -/*! - - \file simulation.cpp - \brief The start-of-simulation routine - \details TODO 001 A more detailed description of this routine. - \author David Favis-Mortlock - \author Andres Payo - \date 2025 - \copyright GNU General Public License - -*/ - -/* ============================================================================================================================== - - This file is part of CoastalME, the Coastal Modelling Environment. - - CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -==============================================================================================================================*/ -#include - -#include -#include -#include - -#include - -#include -using std::fixed; - -#include -using std::cerr; -using std::cin; -using std::endl; -using std::ios; - -#include -using std::setprecision; - -#include - -#include -using std::to_string; - -#include // C++17 and later, needed for missing output directory creation -using std::filesystem::is_directory; -using std::filesystem::exists; -using std::filesystem::create_directories; - -#include - -#include "cme.h" -#include "simulation.h" -#include "raster_grid.h" -#include "coast.h" - -//=============================================================================================================================== -//! The CSimulation constructor -//=============================================================================================================================== -CSimulation::CSimulation(void) -{ - // Initialization - m_bHaveFineSediment = - m_bHaveSandSediment = - m_bHaveCoarseSediment = - m_bBasementElevSave = - m_bSedimentTopSurfSave = - m_bTopSurfSave = - m_bSliceSave = - m_bSeaDepthSave = - m_bAvgSeaDepthSave = - m_bWaveHeightSave = - m_bAvgWaveHeightSave = - m_bWaveAngleSave = - m_bAvgWaveAngleSave = - m_bWaveAngleAndHeightSave = - m_bAvgWaveAngleAndHeightSave = - m_bDeepWaterWaveAngleAndHeightSave = - m_bBeachProtectionSave = - m_bWaveEnergySinceCollapseSave = - m_bMeanWaveEnergySave = - m_bBreakingWaveHeightSave = - m_bPotentialPlatformErosionSave = - m_bActualPlatformErosionSave = - m_bTotalPotentialPlatformErosionSave = - m_bTotalActualPlatformErosionSave = - m_bPotentialBeachErosionSave = - m_bActualBeachErosionSave = - m_bTotalPotentialBeachErosionSave = - m_bTotalActualBeachErosionSave = - m_bBeachDepositionSave = - m_bTotalBeachDepositionSave = - m_bLandformSave = - m_bSlopeConsSedSave = - m_bSlopeSaveForCliffToe = - m_bInterventionClassSave = - m_bInterventionHeightSave = - m_bSuspSedSave = - m_bAvgSuspSedSave = - m_bFineUnconsSedSave = - m_bSandUnconsSedSave = - m_bCoarseUnconsSedSave = - m_bFineConsSedSave = - m_bSandConsSedSave = - m_bCoarseConsSedSave = - m_bRasterCoastlineSave = - m_bRasterNormalProfileSave = - m_bActiveZoneSave = - m_bCliffCollapseSave = - m_bTotCliffCollapseSave = - m_bCliffCollapseDepositionSave = - m_bTotCliffCollapseDepositionSave = - m_bCliffNotchAllSave = - m_bCliffCollapseTimestepSave = - m_bRasterPolygonSave = - m_bPotentialPlatformErosionMaskSave = - m_bSeaMaskSave = - m_bBeachMaskSave = - m_bShadowZoneCodesSave = - m_bSaveRegular = - m_bCoastSave = - m_bCliffEdgeSave = - m_bNormalsSave = - m_bInvalidNormalsSave = - m_bCoastCurvatureSave = - m_bPolygonNodeSave = - m_bPolygonBoundarySave = - m_bCliffNotchSave = - m_bWaveTransectPointsSave = - m_bShadowBoundarySave = - m_bShadowDowndriftBoundarySave = - m_bDeepWaterWaveAngleSave = - m_bDeepWaterWaveHeightSave = - m_bDeepWaterWavePeriodSave = - m_bPolygonUnconsSedUpOrDownDriftSave = - m_bPolygonUnconsSedGainOrLossSave = - m_bCliffToeSave = - m_bSeaAreaTSSave = - m_bSWLTSSave = - m_bActualPlatformErosionTSSave = - m_bSuspSedTSSave = - m_bFloodSetupSurgeTSSave = - m_bFloodSetupSurgeRunupTSSave = - m_bCliffCollapseDepositionTSSave = - m_bCliffCollapseErosionTSSave = - m_bCliffCollapseNetTSSave = - m_bBeachErosionTSSave = - m_bBeachDepositionTSSave = - m_bBeachSedimentChangeNetTSSave = - m_bCliffNotchElevTSSave = - m_bSaveGISThisIter = - m_bOutputConsolidatedProfileData = - m_bOutputParallelProfileData = - m_bOutputErosionPotentialData = - m_bOmitSearchNorthEdge = - m_bOmitSearchSouthEdge = - m_bOmitSearchWestEdge = - m_bOmitSearchEastEdge = - m_bDoShorePlatformErosion = - m_bDoCliffCollapse = - m_bDoBeachSedimentTransport = - m_bGDALCanWriteFloat = - m_bGDALCanWriteInt32 = - m_bScaleRasterOutput = - m_bWorldFile = - m_bSingleDeepWaterWaveValues = - m_bHaveWaveStationData = - m_bSedimentInput = - m_bSedimentInputAtPoint = - m_bSedimentInputAtCoast = - m_bSedimentInputAlongLine = - m_bSedimentInputThisIter = - m_bSedimentInputEventSave = - m_bWaveSetupSave = - m_bStormSurgeSave = - m_bRiverineFlooding = - m_bRunUpSave = - m_bSetupSurgeFloodMaskSave = - m_bSetupSurgeRunupFloodMaskSave = - m_bRasterWaveFloodLineSave = - m_bVectorWaveFloodLineSave = - m_bFloodLocationSave = - m_bFloodSWLSetupLineSave = - m_bFloodSWLSetupSurgeLine = - m_bFloodSWLSetupSurgeRunupLineSave = - m_bGISSaveDigitsSequential = - m_bHaveConsolidatedSediment = - m_bGDALOptimisations = - m_bCliffToeLocate = - m_bHighestSWLSoFar = - m_bLowestSWLSoFar = false; - - m_bGDALCanCreate = true; - m_bCSVPerTimestepResults = true; // Default to CSV output format - m_bYamlInputFormat = false; // Default to .dat format - - m_papszGDALRasterOptions = - m_papszGDALVectorOptions = NULL; - - m_nLayers = - m_nCoastSmooth = - m_nCoastSmoothingWindowSize = - m_nSavGolCoastPoly = - m_nCliffEdgeSmooth = - m_nCliffEdgeSmoothWindow = - m_nSavGolCliffEdgePoly = - m_nProfileSmoothWindow = - m_nCoastNormalSpacing = - m_nCoastNormalInterventionSpacing = - m_nCoastCurvatureInterval = - m_nGISMaxSaveDigits = - m_nGISSave = - m_nUSave = - m_nThisSave = - m_nXGridSize = - m_nYGridSize = - m_nCoastMax = - m_nCoastMin = - //m_nNThisIterCliffCollapse = - //m_nNTotCliffCollapse = - m_nUnconsSedimentHandlingAtGridEdges = - m_nBeachErosionDepositionEquation = - m_nWavePropagationModel = - m_nSimStartSec = - m_nSimStartMin = - m_nSimStartHour = - m_nSimStartDay = - m_nSimStartMonth = - m_nSimStartYear = - m_nDeepWaterWaveDataNumTimeSteps = - m_nLogFileDetail = - m_nRunUpEquation = - m_nLevel = - m_nDefaultTalusWidthInCells = - m_nTalusProfileMinLenInCells = 0; - - // TODO 011 May wish to make this a user-supplied value - m_nGISMissingValue = - m_nMissingValue = INT_NODATA; - - m_nXMinBoundingBox = INT_MAX; - m_nXMaxBoundingBox = INT_MIN; - m_nYMinBoundingBox = INT_MAX; - m_nYMaxBoundingBox = INT_MIN; - - // cppcheck-suppress useInitializationList - m_GDALWriteIntDataType = GDT_Unknown; - // cppcheck-suppress useInitializationList - m_GDALWriteFloatDataType = GDT_Unknown; - - m_lGDALMaxCanWrite = - m_lGDALMinCanWrite = 0; - - m_ulIter = - m_ulTotTimestep = - m_ulThisIterNumPotentialBeachErosionCells = - m_ulThisIterNumActualBeachErosionCells = - m_ulThisIterNumBeachDepositionCells = - m_ulTotPotentialPlatformErosionOnProfiles = - m_ulTotPotentialPlatformErosionBetweenProfiles = - m_ulMissingValueBasementCells =0; - m_ulNumCells = - m_ulThisIterNumSeaCells = - m_ulThisIterNumCoastCells = - m_ulThisIterNumPotentialPlatformErosionCells = - m_ulThisIterNumActualPlatformErosionCells = 0; - - m_ulMissingValue = UNSIGNED_LONG_NODATA; - - for (int i = 0; i < NUMBER_OF_RNGS; i++) - m_ulRandSeed[i] = 0; - - for (int i = 0; i < SAVEMAX; i++) - m_dUSaveTime[i] = 0; - - m_dDurationUnitsMult = - m_dNorthWestXExtCRS = - m_dNorthWestYExtCRS = - m_dSouthEastXExtCRS = - m_dSouthEastYExtCRS = - m_dExtCRSGridArea = - m_dCellSide = - m_dCellDiagonal = - m_dInvCellSide = - m_dInvCellDiagonal = - m_dCellArea = - m_dSimDuration = - m_dTimeStep = - m_dSimElapsed = - m_dRegularSaveTime = - m_dRegularSaveInterval = - m_dClkLast = - m_dCPUClock = - m_dSeaWaterDensity = - m_dThisIterSWL = - m_dThisIterMeanSWL = - m_dInitialMeanSWL = - m_dFinalMeanSWL = - m_dDeltaSWLPerTimestep = - m_dBreakingWaveHeight = - m_dC_0 = - m_dL_0 = - m_dWaveDepthRatioForWaveCalcs = - m_dAllCellsDeepWaterWaveHeight = - m_dAllCellsDeepWaterWaveAngle = - m_dAllCellsDeepWaterWavePeriod = - m_dMaxUserInputWaveHeight = - m_dMaxUserInputWavePeriod = - m_dR = - m_dD50Fine = - m_dD50Sand = - m_dD50Coarse = - m_dBeachSedimentDensity = - m_dBeachSedimentPorosity = - m_dFineErodibility = - m_dSandErodibility = - m_dCoarseErodibility = - m_dFineErodibilityNormalized = - m_dSandErodibilityNormalized = - m_dCoarseErodibilityNormalized = - m_dKLS = - m_dKamphuis = - m_dG = - m_dInmersedToBulkVolumetric = - m_dDepthOfClosure = - m_dCoastNormalSpacing = - m_dCoastNormalInterventionSpacing = - m_dCoastNormalLength = - m_dThisIterTotSeaDepth = - m_dThisIterPotentialSedLostBeachErosion = - m_dThisIterLeftGridUnconsFine =//TODO067 - m_dThisIterLeftGridUnconsSand = - m_dThisIterLeftGridUnconsCoarse = - m_dThisIterPotentialPlatformErosion = - m_dThisIterActualPlatformErosionFineCons = - m_dThisIterActualPlatformErosionSandCons = - m_dThisIterActualPlatformErosionCoarseCons = - m_dThisIterPotentialBeachErosion = - m_dThisIterBeachErosionFine = - m_dThisIterBeachErosionSand = - m_dThisIterBeachErosionCoarse = - m_dThisIterBeachDepositionSand = - m_dThisIterBeachDepositionCoarse = - m_dThisIterFineSedimentToSuspension = - m_dDepositionSandDiff = - m_dDepositionCoarseDiff = - m_dDepthOverDBMax = - m_dTotPotentialPlatformErosionOnProfiles = - m_dTotPotentialPlatformErosionBetweenProfiles = - m_dProfileMaxSlope = - m_dMaxBeachElevAboveSWL = - m_dCliffErosionResistance = - m_dNotchDepthAtCollapse = - m_dThisIterNotchBaseElev = - m_dNotchBaseBelowSWL = - m_dCliffDepositionA = - m_dCliffDepositionPlanviewWidth = - m_dCliffTalusMinDepositionLength = - m_dMinCliffTalusHeightFrac = - m_dThisIterCliffCollapseErosionFineUncons = - m_dThisIterCliffCollapseErosionSandUncons = - m_dThisIterCliffCollapseErosionCoarseUncons = - m_dThisIterCliffCollapseErosionFineCons = - m_dThisIterCliffCollapseErosionSandCons = - m_dThisIterCliffCollapseErosionCoarseCons = - m_dThisIterUnconsSandCliffDeposition = - m_dThisIterUnconsCoarseCliffDeposition = - m_dThisIterCliffCollapseFineErodedDuringDeposition = - m_dThisIterCliffCollapseSandErodedDuringDeposition = - m_dThisIterCliffCollapseCoarseErodedDuringDeposition = - m_dCoastNormalRandSpacingFactor = - m_dDeanProfileStartAboveSWL = - m_dAccumulatedSeaLevelChange = - m_dBreakingWaveHeightDepthRatio = - m_dWaveDataWrapHours = - m_dThisIterTopElevMax = - m_dThisIterTopElevMin = - m_dThisiterUnconsFineInput = - m_dThisiterUnconsSandInput = - m_dThisiterUnconsCoarseInput = - m_dStartIterSuspFineAllCells = - m_dStartIterSuspFineInPolygons = - m_dStartIterUnconsFineAllCells = - m_dStartIterUnconsSandAllCells = - m_dStartIterUnconsCoarseAllCells = - m_dStartIterConsFineAllCells = - m_dStartIterConsSandAllCells = - m_dStartIterConsCoarseAllCells = - m_dThisIterDiffTotWaterLevel = - m_dThisIterDiffWaveSetupWaterLevel = - m_dThisIterDiffWaveSetupSurgeWaterLevel = - m_dThisIterDiffWaveSetupSurgeRunupWaterLevel = - m_dTotalFineUnconsInPolygons = - m_dTotalSandUnconsInPolygons = - m_dTotalCoarseUnconsInPolygons = - m_dUnconsSandNotDepositedLastIter = - m_dUnconsCoarseNotDepositedLastIter = - m_dTotalFineConsInPolygons = - m_dTotalSandConsInPolygons = - m_dTotalCoarseConsInPolygons = - m_dSlopeThresholdForCliffToe = 0; - - m_dMinSWLSoFar = DBL_MAX; - m_dMaxSWLSoFar = DBL_MIN; - - for (int i = 0; i < 6; i++) - m_dGeoTransform[i] = 0; - - // TODO 011 May wish to make this a user-supplied value - m_dGISMissingValue = - m_dMissingValue = DBL_NODATA; - - m_ldGTotPotentialPlatformErosion = - m_ldGTotFineActualPlatformErosion = - m_ldGTotSandActualPlatformErosion = - m_ldGTotCoarseActualPlatformErosion = - m_ldGTotPotentialSedLostBeachErosion = - m_ldGTotActualFineLostBeachErosion = - m_ldGTotActualSandLostBeachErosion = - m_ldGTotActualCoarseLostBeachErosion = - m_ldGTotSandSedLostCliffCollapse = - m_ldGTotCoarseSedLostCliffCollapse = - m_ldGTotCliffCollapseFine = - m_ldGTotCliffCollapseSand = - m_ldGTotCliffCollapseCoarse = - m_ldGTotCliffTalusFineToSuspension = - m_ldGTotCliffTalusSandDeposition = - m_ldGTotCliffTalusCoarseDeposition = - m_ldGTotCliffCollapseFineErodedDuringDeposition = - m_ldGTotCliffCollapseSandErodedDuringDeposition = - m_ldGTotCliffCollapseCoarseErodedDuringDeposition = - m_ldGTotPotentialBeachErosion = - m_ldGTotActualFineBeachErosion = - m_ldGTotActualSandBeachErosion = - m_ldGTotActualCoarseBeachErosion = - m_ldGTotSandBeachDeposition = - m_ldGTotCoarseBeachDeposition = - m_ldGTotSuspendedSediment = - m_ldGTotSandDepositionDiff = - m_ldGTotCoarseDepositionDiff = - m_ldGTotFineSedimentInput = - m_ldGTotSandSedimentInput = - m_ldGTotCoarseSedimentInput = 0; - - m_tSysStartTime = - m_tSysEndTime = 0; - - m_pRasterGrid = NULL; -} - -//=============================================================================================================================== -//! The CSimulation destructor -//=============================================================================================================================== -CSimulation::~CSimulation(void) -{ - // Close output files if open - if (LogStream && LogStream.is_open()) - { - LogStream.flush(); - LogStream.close(); - } - - if (OutStream && OutStream.is_open()) - { - OutStream.flush(); - OutStream.close(); - } - - if (SeaAreaTSStream && SeaAreaTSStream.is_open()) - { - SeaAreaTSStream.flush(); - SeaAreaTSStream.close(); - } - - if (SWLTSStream && SWLTSStream.is_open()) - { - SWLTSStream.flush(); - SWLTSStream.close(); - } - - if (PlatformErosionTSStream && PlatformErosionTSStream.is_open()) - { - PlatformErosionTSStream.flush(); - PlatformErosionTSStream.close(); - } - - if (CliffCollapseErosionTSStream && CliffCollapseErosionTSStream.is_open()) - { - CliffCollapseErosionTSStream.flush(); - CliffCollapseErosionTSStream.close(); - } - - if (CliffCollapseDepositionTSStream && CliffCollapseDepositionTSStream.is_open()) - { - CliffCollapseDepositionTSStream.flush(); - CliffCollapseDepositionTSStream.close(); - } - - if (CliffCollapseNetChangeTSStream && CliffCollapseNetChangeTSStream.is_open()) - { - CliffCollapseNetChangeTSStream.flush(); - CliffCollapseNetChangeTSStream.close(); - } - - if (FineSedSuspensionTSStream && FineSedSuspensionTSStream.is_open()) - { - FineSedSuspensionTSStream.flush(); - FineSedSuspensionTSStream.close(); - } - - if (FloodSetupSurgeTSStream && FloodSetupSurgeTSStream.is_open()) - { - FloodSetupSurgeTSStream.flush(); - FloodSetupSurgeTSStream.close(); - } - - if (FloodSetupSurgeRunupTSStream && FloodSetupSurgeRunupTSStream.is_open()) - { - FloodSetupSurgeRunupTSStream.flush(); - FloodSetupSurgeRunupTSStream.close(); - } - - if (CliffNotchElevTSStream && CliffNotchElevTSStream.is_open()) - { - CliffNotchElevTSStream.flush(); - CliffNotchElevTSStream.close(); - } - - if (m_pRasterGrid) - delete m_pRasterGrid; -} - -//=============================================================================================================================== -//! Returns the double Missing Value code -//=============================================================================================================================== -double CSimulation::dGetMissingValue(void) const -{ - return m_dMissingValue; -} - -//=============================================================================================================================== -//! Returns the still water level (SWL) -//=============================================================================================================================== -double CSimulation::dGetThisIterSWL(void) const -{ - return m_dThisIterSWL; -} - -//=============================================================================================================================== -//! Returns the this-iteration total water level -//=============================================================================================================================== -double CSimulation::dGetThisIterTotWaterLevel(void) const -{ - return m_dThisIterDiffTotWaterLevel; -} - -// //=============================================================================================================================== -// //! Returns the max elevation of the beach above SWL -// //=============================================================================================================================== -// double CSimulation::dGetMaxBeachElevAboveSWL (void) const -// { -// return m_dMaxBeachElevAboveSWL; -// } - -//=============================================================================================================================== -// Returns the cell side length -//=============================================================================================================================== -// double CSimulation::dGetCellSide(void) const -// { -// return m_dCellSide; -// } - -//=============================================================================================================================== -//! Returns X grid max -//=============================================================================================================================== -int CSimulation::nGetGridXMax(void) const -{ - return m_nXGridSize; -} - -//=============================================================================================================================== -//! Returns Y grid max -//=============================================================================================================================== -int CSimulation::nGetGridYMax(void) const -{ - return m_nYGridSize; -} - -//=============================================================================================================================== -//! Returns D50 for fine sediment -//=============================================================================================================================== -double CSimulation::dGetD50Fine(void) const -{ - return m_dD50Fine; -} - -//=============================================================================================================================== -//! Returns D50 for sand sediment -//=============================================================================================================================== -double CSimulation::dGetD50Sand(void) const -{ - return m_dD50Sand; -} - -//=============================================================================================================================== -//! Returns D50 for coarse sediment -//=============================================================================================================================== -double CSimulation::dGetD50Coarse(void) const -{ - return m_dD50Coarse; -} - -//=============================================================================================================================== -//! The nDoSimulation member function of CSimulation sets up and runs the simulation -//=============================================================================================================================== -int CSimulation::nDoSimulation(int nArg, char const* pcArgv[]) -{ - // ================================================== initialisation section ================================================ - // Hello, World! - AnnounceStart(); - - // Start the clock ticking - StartClock(); - - // Deal with command-line parameters - int nRet = nHandleCommandLineParams(nArg, pcArgv); - - if (nRet != RTN_OK) - return (nRet); - - // Find out the folder in which the CoastalME executable sits, in order to open the .ini file (they are assumed to be in the same folder) - if (! bFindExeDir(pcArgv[0])) - return (RTN_ERR_CMEDIR); - - // OK, we are off, tell the user about the licence and the start time - AnnounceLicence(); - - // Read the .ini file and get the name of the run-data file, and path for output etc. - if (! bReadIniFile()) - return (RTN_ERR_INI); - - // Check if output dir exists - if ((! is_directory(m_strOutPath.c_str())) || (! exists(m_strOutPath.c_str()))) - { - // Output dir does not exist - bool bCreateDir = false; - - if ((isatty(fileno(stdout))) && (isatty(fileno(stderr)))) - { - // Running with stdout and stderr as a tty, so ask the user if they wish to create it - char ch; - cerr << endl - << "Output folder '" << m_strOutPath << "' does not exist. Create it? (Y/N) "; - cerr.flush(); - cin.get(ch); - - if ((ch == 'y') || (ch == 'Y')) - bCreateDir = true; - } - else - { - // Running with stdout or stderr not a tty, so create output dir rather than abort - bCreateDir = true; - } - - if (bCreateDir) - { - // Yes, so create the directory - create_directories(m_strOutPath.c_str()); - cerr << m_strOutPath << " created" << endl << endl; - } - else - // Nope, just end the run - return RTN_USER_ABORT; - } - - // We have the name of the run-data input file, so read it - if (! bReadRunDataFile()) - return RTN_ERR_RUNDATA; - - // Check raster GIS output format - if (! bCheckRasterGISOutputFormat()) - return (RTN_ERR_RASTER_GIS_OUT_FORMAT); - - // Check vector GIS output format - if (! bCheckVectorGISOutputFormat()) - return (RTN_ERR_VECTOR_GIS_OUT_FORMAT); - - // Open log file - if (! bOpenLogFile()) - return (RTN_ERR_LOGFILE); - - // Set up the time series output files - if (! bSetUpTSFiles()) - return (RTN_ERR_TSFILE); - - // Initialize the random number generators - for (int n = 0; n < NUMBER_OF_RNGS; n++) - m_Rand[n].seed(m_ulRandSeed[n]); - - // If we are doing Savitzky-Golay smoothing of the vector coastline(s), calculate the filter coefficients - if (m_nCoastSmooth == SMOOTH_SAVITZKY_GOLAY) - CalcSavitzkyGolayCoeffs(); - - // Create the raster grid object - m_pRasterGrid = new CGeomRasterGrid(this); - - // Read in the basement layer (must have this file), create the raster grid, then read in the basement DEM data to the array - AnnounceReadBasementDEM(); - nRet = nReadRasterBasementDEM(); - - if (nRet != RTN_OK) - return nRet; - - // If we are simulating cliff collapse: then now that we have a value for m_dCellSide, we can check some more input parameters. Talus must be more than one cell wide, and since the number of cells must be odd, three cells is the minimum width - if (m_bDoCliffCollapse) - { - int const nTmp = nConvertMetresToNumCells(m_dCliffDepositionPlanviewWidth); - if (nTmp < 3) - { - string const strErr = ERR + "cliff deposition must have a planview width of at least three cells. The current setting of " + to_string(m_dCliffDepositionPlanviewWidth) + " m gives a planview width of " + to_string(nTmp) + " cells. Please edit " + m_strDataPathName; - cerr << strErr << endl; - LogStream << strErr << endl; - OutStream << strErr << endl; - return RTN_ERR_RUNDATA; - } - } - - // Do some more initialisation - // cppcheck-suppress truncLongCastAssignment - m_ulNumCells = m_nXGridSize * m_nYGridSize; - - // Mark edge cells, as defined by the basement layer - nRet = nMarkBoundingBoxEdgeCells(); - - if (nRet != RTN_OK) - return nRet; - - // // DEBUG CODE ================================================================================================================= - // for (int n = 0; n < m_VEdgeCell.size(); n++) - // { - // LogStream << "[" << m_VEdgeCell[n].nGetX() << "][" << m_VEdgeCell[n].nGetY() << "] = {" << dGridCentroidXToExtCRSX(m_VEdgeCell[n].nGetX()) << ", " << dGridCentroidYToExtCRSY(m_VEdgeCell[n].nGetY()) << "} " << m_VEdgeCellEdge[n] << endl; - // } - // // DEBUG CODE ================================================================================================================= - - // If we are using the default cell spacing, then now that we know the size of the raster cells, we can set the size of profile spacing in m - if (bFPIsEqual(m_dCoastNormalSpacing, 0.0, TOLERANCE)) - m_dCoastNormalSpacing = DEFAULT_PROFILE_SPACING * m_dCellSide; - else - { - // The user specified a profile spacing, is this too small? - m_nCoastNormalSpacing = nRound(m_dCoastNormalSpacing / m_dCellSide); - - if (m_nCoastNormalSpacing < DEFAULT_PROFILE_SPACING) - { - cerr << ERR << "profile spacing was specified as " << m_dCoastNormalSpacing << " m, which is " << m_nCoastNormalSpacing << " cells. Polygon creation works poorly if profile spacing is less than " << DEFAULT_PROFILE_SPACING << " cells, i.e. " << DEFAULT_PROFILE_SPACING * m_dCellSide << " m" << endl; - - LogStream << ERR << "profile spacing was specified as " << m_dCoastNormalSpacing << " m, which is " << m_nCoastNormalSpacing << " cells. Polygon creation works poorly if profile spacing is less than " << DEFAULT_PROFILE_SPACING << " cells, i.e. " << DEFAULT_PROFILE_SPACING * m_dCellSide << " m" << endl; - - return RTN_ERR_PROFILE_SPACING; - } - } - - // Set the profile spacing on interventions - m_dCoastNormalInterventionSpacing = m_dCoastNormalSpacing * INTERVENTION_PROFILE_SPACING_FACTOR; - m_nCoastNormalInterventionSpacing = nRound(m_dCoastNormalInterventionSpacing / m_dCellSide); - - // We have at least one filename for the first layer, so add the correct number of layers. Note the the number of layers does not change during the simulation: however layers can decrease in thickness until they have zero thickness - AnnounceAddLayers(); - - for (int nX = 0; nX < m_nXGridSize; nX++) - for (int nY = 0; nY < m_nYGridSize; nY++) - m_pRasterGrid->m_Cell[nX][nY].AppendLayers(m_nLayers); - - // Tell the user what is happening then read in the layer files - AnnounceReadRasterFiles(); - - for (int nLayer = 0; nLayer < m_nLayers; nLayer++) - { - if (! m_VstrInitialFineUnconsSedimentFile[nLayer].empty()) - { - // Read in the initial fine unconsolidated sediment depth file(s) - AnnounceReadInitialFineUnconsSedGIS(nLayer); - nRet = nReadRasterGISFile(FINE_UNCONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - - if (! m_VstrInitialSandUnconsSedimentFile[nLayer].empty()) - { - // Read in the initial sand unconsolidated sediment depth file - AnnounceReadInitialSandUnconsSedGIS(nLayer); - nRet = nReadRasterGISFile(SAND_UNCONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - - if (! m_VstrInitialCoarseUnconsSedimentFile[nLayer].empty()) - { - // Read in the initial coarse unconsolidated sediment depth file - AnnounceReadInitialCoarseUnconsSedGIS(nLayer); - nRet = nReadRasterGISFile(COARSE_UNCONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - - if (! m_VstrInitialFineConsSedimentFile[nLayer].empty()) - { - // Read in the initial fine consolidated sediment depth file - AnnounceReadInitialFineConsSedGIS(nLayer); - nRet = nReadRasterGISFile(FINE_CONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - - if (! m_VstrInitialSandConsSedimentFile[nLayer].empty()) - { - // Read in the initial sand consolidated sediment depth file - AnnounceReadInitialSandConsSedGIS(nLayer); - nRet = nReadRasterGISFile(SAND_CONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - - if (! m_VstrInitialCoarseConsSedimentFile[nLayer].empty()) - { - // Read in the initial coarse consolidated sediment depth file - AnnounceReadInitialCoarseConsSedGIS(nLayer); - nRet = nReadRasterGISFile(COARSE_CONS_RASTER, nLayer); - if (nRet != RTN_OK) - return (nRet); - } - } - - if (! m_strInitialSuspSedimentFile.empty()) - { - // Read in the initial suspended sediment depth file - AnnounceReadInitialSuspSedGIS(); - nRet = nReadRasterGISFile(SUSP_SED_RASTER, 0); - if (nRet != RTN_OK) - return (nRet); - } - - - // Maybe read in the landform class data, otherwise calculate this during the first timestep using identification rules - if (! m_strInitialLandformFile.empty()) - { - AnnounceReadLGIS(); - nRet = nReadRasterGISFile(LANDFORM_RASTER, 0); - if (nRet != RTN_OK) - return (nRet); - } - - // Maybe read in intervention data - if (! m_strInterventionClassFile.empty()) - { - AnnounceReadICGIS(); - nRet = nReadRasterGISFile(INTERVENTION_CLASS_RASTER, 0); - if (nRet != RTN_OK) - return (nRet); - - AnnounceReadIHGIS(); - nRet = nReadRasterGISFile(INTERVENTION_HEIGHT_RASTER, 0); - if (nRet != RTN_OK) - return (nRet); - } - - // Maybe read in the tide data - if (! m_strTideDataFile.empty()) - { - AnnounceReadTideData(); - nRet = nReadTideDataFile(); - if (nRet != RTN_OK) - return (nRet); - } - - // Read in the erosion potential shape function data - AnnounceReadSCAPEShapeFunctionFile(); - nRet = nReadShapeFunctionFile(); - if (nRet != RTN_OK) - return (nRet); - - // Do we want to output the erosion potential look-up values, for checking purposes? - if (m_bOutputErosionPotentialData) - WriteLookUpData(); - - // OK, now read in the vector files (if any) - if (m_bHaveWaveStationData || m_bSedimentInput) - AnnounceReadVectorFiles(); - - // Maybe read in deep water wave station data - if (m_bHaveWaveStationData) - { - // We are reading deep water wave height, orientation and period from a file of vector points and file time series - AnnounceReadDeepWaterWaveValuesGIS(); - - // Read in vector points - nRet = nReadVectorGISFile(DEEP_WATER_WAVE_STATIONS_VEC); - if (nRet != RTN_OK) - return (nRet); - - int const nWaveStations = static_cast(m_VnDeepWaterWaveStationID.size()); - - if (nWaveStations == 1) - m_bSingleDeepWaterWaveValues = true; - - // Read in time series values, and initialise the vector which stores each timestep's deep water wave height, orientation and period - nRet = nReadWaveStationInputFile(nWaveStations); - if (nRet != RTN_OK) - return (nRet); - } - - // Maybe read in sediment input event data - if (m_bSedimentInput) - { - // We are reading sediment input event data - AnnounceReadSedimentEventInputValuesGIS(); - - // Read in vector points for sediment input events - nRet = nReadVectorGISFile(SEDIMENT_INPUT_EVENT_LOCATION_VEC); - if (nRet != RTN_OK) - return (nRet); - - // Read in the time series values for sediment input events - nRet = nReadSedimentInputEventFile(); - if (nRet != RTN_OK) - return (nRet); - } - - // Maybe read in flood input location - if (m_bFloodLocationSave) - { - // We are reading sediment input event data - AnnounceReadFloodLocationGIS(); - - // Read in vector points for sediment input events - nRet = nReadVectorGISFile(FLOOD_LOCATION_VEC); - if (nRet != RTN_OK) - return (nRet); - } - - // Open the main output - OutStream.open(m_strOutFile.c_str(), ios::out | ios::trunc); - - if (!OutStream) - { - // Error, cannot open Out file - cerr << ERR << "cannot open " << m_strOutFile << " for output" << endl; - return (RTN_ERR_OUTFILE); - } - - // Write beginning-of-run information to Out and Log files - WriteStartRunDetails(); - - // Final stage of initialization - AnnounceFinalInitialization(); - - // Misc initialisation calcs - m_nCoastMax = COAST_LENGTH_MAX * tMax(m_nXGridSize, m_nYGridSize); // Arbitrary but probably OK - m_nCoastMin = tMin(m_nXGridSize, m_nYGridSize); // In some cases the following rule doesn't work TODO 007 Finish surge and runup stuff - // nRound(COAST_LENGTH_MIN_X_PROF_SPACE * m_dCoastNormalSpacing / m_dCellSide); // TODO 007 Finish surge and runup stuff - m_nCoastCurvatureInterval = tMax(nRound(m_dCoastNormalSpacing / (m_dCellSide * 2)), 2); // TODO 007 Finish surge and runup stuff - - // For beach erosion/deposition, conversion from immersed weight to bulk volumetric (sand and voids) transport rate (Leo Van Rijn) TODO 007 need full reference - m_dInmersedToBulkVolumetric = 1 / ((m_dBeachSedimentDensity - m_dSeaWaterDensity) * (1 - m_dBeachSedimentPorosity) * m_dG); - - m_bConsChangedThisIter.resize(m_nLayers, false); - m_bUnconsChangedThisIter.resize(m_nLayers, false); - - // Normalize sediment erodibility values, so that none are > 1 - double const dTmp = m_dFineErodibility + m_dSandErodibility + m_dCoarseErodibility; - m_dFineErodibilityNormalized = m_dFineErodibility / dTmp; - m_dSandErodibilityNormalized = m_dSandErodibility / dTmp; - m_dCoarseErodibilityNormalized = m_dCoarseErodibility / dTmp; - - // Intialise SWL - m_dThisIterSWL = m_dInitialMeanSWL; - - // If SWL changes during the simulation, calculate the per-timestep increment (could be -ve) - if (! bFPIsEqual(m_dFinalMeanSWL, m_dInitialMeanSWL, TOLERANCE)) - { - m_dDeltaSWLPerTimestep = (m_dTimeStep * (m_dFinalMeanSWL - m_dInitialMeanSWL)) / m_dSimDuration; - m_dAccumulatedSeaLevelChange -= m_dDeltaSWLPerTimestep; - } - - // Calculate default planview width of cliff collapse talus, in cells - m_nDefaultTalusWidthInCells = nConvertMetresToNumCells(m_dCliffDepositionPlanviewWidth); - - // The default talus collapse width must be an odd number of cells in width i.e. centred on the cliff collapse cell (but only if we are not at the end of the coast) - if ((m_nDefaultTalusWidthInCells % 2) == 0) - m_nDefaultTalusWidthInCells++; - - // This is the minimum planview length (in cells) of the Dean profile. The initial length will be increased if we can't deposit sufficient talus - m_nTalusProfileMinLenInCells = nConvertMetresToNumCells(m_dCliffTalusMinDepositionLength); - - // ===================================================== The main loop ====================================================== - // Tell the user what is happening - AnnounceIsRunning(); - - while (true) - { - // Check that we haven't gone on too long: if not then update timestep number etc. - if (bTimeToQuit()) - break; - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - LogStream << "TIMESTEP " << m_ulIter << " " << string(154, '=') << endl; - - LogStream << fixed << setprecision(3); - - // Check to see if there is a new intervention in place: if so, update it on the RasterGrid array - nRet = nUpdateIntervention(); - if (nRet != RTN_OK) - return nRet; - - // Calculate changes due to external forcing (change in still water level, tide level and deep water waves height, orientation and period) - nRet = nCalcExternalForcing(); - if (nRet != RTN_OK) - return nRet; - - // Do per-timestep initialisation: set up the grid cells ready for this timestep, also initialise per-timestep totals. Note that in the first timestep, all cells (including hinterland cells) are given the deep water wave values - nRet = nInitGridAndCalcStillWaterLevel(); - if (nRet != RTN_OK) - return nRet; - - // Next find out which cells are inundated and locate the coastline(s). This also gives to all sea cells, wave values which are the same as the deep water values. For shallow water sea cells, these wave values will be changed later, in nDoAllPropagateWaves() - nRet = nLocateSeaAndCoasts(); - if (nRet != RTN_OK) - return nRet; - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // Locate estuaries TODO someday... - - if (m_bHaveConsolidatedSediment && m_bDoCliffCollapse && m_bCliffToeLocate) - { - // Locate and trace cliff toe - nRet = nLocateCliffToe(); - if (nRet != RTN_OK) - return nRet; - } - - // For all cells, use classification rules to assign sea and hinterland landform categories - nRet = nAssignLandformsForAllCells(); - if (nRet != RTN_OK) - return nRet; - - // For every coastline, use classification rules to assign landform categories - nRet = nAssignLandformsForAllCoasts(); - if (nRet != RTN_OK) - return nRet; - - // Create all coastline-normal profiles, in coastline-concave-curvature sequence - nRet = nCreateAllProfiles(); - if (nRet != RTN_OK) - return nRet; - - // Check the coastline-normal profiles for intersection, modify the profiles if they intersect, then mark valid profiles on the raster grid - nRet = nCheckAndMarkAllProfiles(); - if (nRet != RTN_OK) - return nRet; - - if (m_VCoast.size() > 1) - { - // We have multiple coastlines - nRet = nDoMultipleCoastlines(); - if (nRet != RTN_OK) - return nRet; - } - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // Create the coast polygons - nRet = nCreateAllPolygons(); - if (nRet != RTN_OK) - return nRet; - - // Mark cells of the raster grid that are within each polygon, and do some polygon initialisation - MarkPolygonCells(); - - // // DEBUG CODE ================ - // m_nGISSave++; - // if (! bWriteVectorGISFile(VECTOR_PLOT_COAST, &VECTOR_PLOT_COAST_TITLE)) - // return false; - // if (! bWriteVectorGISFile(VECTOR_PLOT_NORMALS, &VECTOR_PLOT_NORMALS_TITLE)) - // return false; - // if (! bWriteVectorGISFile(VECTOR_PLOT_INVALID_NORMALS, &VECTOR_PLOT_INVALID_NORMALS_TITLE)) - // return false; - // if (! bWriteRasterGISFile(RASTER_PLOT_NORMAL_PROFILE, &RASTER_PLOT_NORMAL_PROFILE_TITLE)) - // return false; - // if (! bWriteRasterGISFile(RASTER_PLOT_COAST, &RASTER_PLOT_COAST_TITLE)) - // return false; - // if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON, &RASTER_PLOT_POLYGON_TITLE)) - // return false; - // if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_BOUNDARY, &VECTOR_PLOT_POLYGON_BOUNDARY_TITLE)) - // return false; - // // DEBUG CODE ================ - - // Calculate the length of the shared normal between each polygon and the adjacent polygon(s) - nRet = nDoPolygonSharedBoundaries(); - if (nRet != RTN_OK) - return nRet; - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // // DEBUG CODE ========================================================================================================= - // nNODATA = 0; - // nPoly0 = 0; - // nPoly24 = 0; - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // int nTmp = m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID(); - // if (nTmp == INT_NODATA) - // nNODATA++; - // - // if (nTmp == 0) - // nPoly0++; - // - // if (nTmp == 24) - // nPoly24++; - // } - // } - // LogStream << "After marking polygon cells, N cells with NODATA polygon ID = " << nNODATA << endl; - // // LogStream << "After marking polygon cells, N cells with zero polygon ID = " << nPoly0 << endl; - // LogStream << "After marking polygon cells, N cells with 24 polygon ID = " << nPoly24 << endl; - // // DEBUG CODE ========================================================================================================= - // PropagateWind(); - - // Give every coast point a value for deep water wave height and direction - nRet = nSetAllCoastpointDeepWaterWaveValues(); - if (nRet != RTN_OK) - return nRet; - - // // DEBUG CODE =============== - // for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) - // { - // LogStream << "====================" << endl; - // - // for (int nProfile = 0; nProfile < m_VCoast[nCoast].nGetNumProfiles(); nProfile++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); - // int nCell = pProfile->nGetNumCellsInProfile(); - // LogStream << "Profile " << pProfile->nGetProfileID() << " nGetNumCellsInProfile() = " << nCell << endl; - // } - // - // LogStream << endl; - // - // for (int nProfile = 0; nProfile < m_VCoast[nCoast].nGetNumProfiles(); nProfile++) - // { - // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(nProfile); - // int nCell = pProfile->nGetNumCellsInProfile(); - // LogStream << "Profile " << pProfile->nGetProfileID() << " nGetNumCellsInProfile() = " << nCell << endl; - // } - // - // LogStream << "====================" << endl; - // } - // // DEBUG CODE ===================== - - // Change the wave properties in all shallow water sea cells: propagate waves and define the active zone, also locate wave shadow zones - nRet = nDoAllPropagateWaves(); - if (nRet != RTN_OK) - return nRet; - - // Output polygon share table and pre-existing sediment table to log file - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - { - WritePolygonInfoTable(); - WritePolygonPreExistingSedimentTable(); - } - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // // DEBUG CODE =========================================================================================================== - // string strOutFile = m_strOutPath; - // strOutFile += "sea_wave_height_CHECKPOINT_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // int nn = 0; - // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== - // - // // DEBUG CODE =========================================================================================================== - // strOutFile = m_strOutPath; - // strOutFile += "sea_wave_angle_CHECKPOINT_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // nn = 0; - // pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); - // } - // } - // - // pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== - - // Save the not-deposited values, to be shown in the logfile after we've finished beach sediment movement - m_dUnconsSandNotDepositedLastIter = m_dDepositionSandDiff; - m_dUnconsCoarseNotDepositedLastIter = m_dDepositionCoarseDiff; - - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - if (m_dDepositionSandDiff > MASS_BALANCE_TOLERANCE) - { - LogStream << m_ulIter << ": AT ITERATION START m_dDepositionSandDiff = " << m_dDepositionSandDiff * m_dCellArea << " m_dUnconsSandNotDepositedLastIter = " << m_dUnconsSandNotDepositedLastIter << endl; - LogStream << m_ulIter << ": AT ITERATION START m_dDepositionCoarseDiff = " << m_dDepositionCoarseDiff * m_dCellArea << " m_dUnconsCoarseNotDepositedLastIter = " << m_dUnconsCoarseNotDepositedLastIter << endl; - } - - if (m_bDoShorePlatformErosion) - { - // Calculate elevation change on the consolidated sediment which comprises the coastal platform - nRet = nDoAllShorePlatFormErosion(); - if (nRet != RTN_OK) - return nRet; - } - - // Output shore platform erosion table to log file - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - WritePolygonShorePlatformErosion(); - - // Are we considering cliff collapse? - if (m_bHaveConsolidatedSediment && m_bDoCliffCollapse) - { - // Do all cliff collapses for this timestep (if any) - nRet = nDoAllWaveEnergyToCoastLandforms(); - if (nRet != RTN_OK) - return nRet; - - // Output cliff collapse table to log file - if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) - WritePolygonCliffCollapseErosion(); - } - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - if (m_bDoBeachSedimentTransport) - { - // Next simulate beach erosion and deposition i.e. simulate alongshore transport of unconsolidated sediment (longshore drift) between polygons. First calculate potential sediment movement between polygons - DoAllPotentialBeachErosion(); - - // Do within-sediment redistribution of unconsolidated sediment, constraining potential sediment movement to give actual (i.e. supply-limited) sediment movement to/from each polygon in three size classes - nRet = nDoAllActualBeachErosionAndDeposition(); - if (nRet != RTN_OK) - return nRet; - } - - // If we have sediment input events, then check to see whether this is time for an event to occur. If it is, then do it - if (m_bSedimentInput) - { - nRet = nCheckForSedimentInputEvent(); - if (nRet != RTN_OK) - return nRet; - - // If we have had at least one sediment input event this iteration, then output the sediment event per polygon table to the log file - if (m_bSedimentInputThisIter && (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL)) - WritePolygonSedimentInputEventTable(); - } - - // // Add the fine sediment that was eroded this timestep (from the shore platform, from cliff collapse, from erosion of existing fine sediment during cliff collapse talus deposition, and from beach erosion; minus the fine sediment from beach erosion that went off-grid) to the suspended sediment load - // double dFineThisIter = m_dThisIterActualPlatformErosionFineCons + m_dThisIterCliffCollapseErosionFineUncons + m_dThisIterCliffCollapseErosionFineCons + m_dThisIterCliffCollapseFineErodedDuringDeposition + m_dThisIterBeachErosionFine - m_dThisIterLeftGridUnconsFine; - // - // m_dThisIterFineSedimentToSuspension += dFineThisIter; - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // // DEBUG CODE =========================================================================================================== - // string strOutFile = m_strOutPath; - // strOutFile += "sea_wave_height_CHECKPOINT_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // int nn = 0; - // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveHeight(); - // } - // } - // - // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== - // - // // DEBUG CODE =========================================================================================================== - // strOutFile = m_strOutPath; - // strOutFile += "sea_wave_angle_CHECKPOINT_"; - // strOutFile += to_string(m_ulIter); - // strOutFile += ".tif"; - // - // pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); - // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); - // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); - // pDataSet->SetGeoTransform(m_dGeoTransform); - // - // nn = 0; - // pdRaster = new double[m_nXGridSize * m_nYGridSize]; - // for (int nY = 0; nY < m_nYGridSize; nY++) - // { - // for (int nX = 0; nX < m_nXGridSize; nX++) - // { - // pdRaster[nn++] = m_pRasterGrid->m_Cell[nX][nY].dGetWaveAngle(); - // } - // } - // - // pBand = pDataSet->GetRasterBand(1); - // pBand->SetNoDataValue(m_dMissingValue); - // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); - // - // if (nRet == CE_Failure) - // return RTN_ERR_GRIDCREATE; - // - // GDALClose(pDataSet); - // delete[] pdRaster; - // // DEBUG CODE =========================================================================================================== - - // Do some end-of-timestep updates to the raster grid, also update per-timestep and running totals - nRet = nUpdateGrid(); - if (nRet != RTN_OK) - return nRet; - - // Make water level inundation on grid - if (m_bFloodSWLSetupSurgeLine || m_bSetupSurgeFloodMaskSave) - { - m_nLevel = 0; - - nRet = nLocateFloodAndCoasts(); - if (nRet != RTN_OK) - return nRet; - } - - if (m_bFloodSWLSetupSurgeRunupLineSave || m_bSetupSurgeRunupFloodMaskSave) - { - // TODO 007 Finish surge and runup stuff - m_nLevel = 1; - - nRet = nLocateFloodAndCoasts(); - if (nRet != RTN_OK) - return nRet; - } - - // Now save results, first the raster and vector GIS files if required - m_bSaveGISThisIter = false; - - if ((m_bSaveRegular && (m_dSimElapsed >= m_dRegularSaveTime) && (m_dSimElapsed < m_dSimDuration)) || (! m_bSaveRegular && (m_dSimElapsed >= m_dUSaveTime[m_nThisSave]))) - { - m_bSaveGISThisIter = true; - - // Save the values from the RasterGrid array into raster GIS files - if (! bSaveAllRasterGISFiles()) - return (RTN_ERR_RASTER_FILE_WRITE); - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // Save the vector GIS files - if (! bSaveAllVectorGISFiles()) - return (RTN_ERR_VECTOR_FILE_WRITE); - - // Tell the user how the simulation is progressing - AnnounceProgress(); - } - - // Output per-timestep results to the .out file - if (! bWritePerTimestepResults()) - return (RTN_ERR_TEXT_FILE_WRITE); - - // Now output time series CSV stuff - if (! bWriteTSFiles()) - return (RTN_ERR_TIMESERIES_FILE_WRITE); - - // Tell the user how the simulation is progressing - AnnounceProgress(); - - // Update grand totals - DoEndOfTimestepTotals(); - - } // ================================================ End of main loop ====================================================== - - // =================================================== post-loop tidying ===================================================== - // Tell the user what is happening - AnnounceSimEnd(); - - // Write end-of-run information to Out, Log and time-series files - nRet = nWriteEndRunDetails(); - if (nRet != RTN_OK) - return (nRet); - - // Do end-of-run memory clearance - DoEndOfRunDeletes(); - return RTN_OK; -} +/*! + + \file simulation.cpp + \brief The start-of-simulation routine + \details TODO 001 A more detailed description of this routine. + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License + +*/ + +/* ============================================================================================================================== + + This file is part of CoastalME, the Coastal Modelling Environment. + + CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +==============================================================================================================================*/ +#include + +#include +#include +#include + +#include + +#include +using std::fixed; + +#include +using std::cerr; +using std::cin; +using std::endl; +using std::ios; + +#include +using std::setprecision; + +#include + +#include +using std::to_string; + +#include // C++17 and later, needed for missing output directory creation +using std::filesystem::is_directory; +using std::filesystem::exists; +using std::filesystem::create_directories; + +#include + +#include "cme.h" +#include "simulation.h" +#include "raster_grid.h" +#include "coast.h" + +//=============================================================================================================================== +//! The CSimulation constructor +//=============================================================================================================================== +CSimulation::CSimulation(void) +{ + // Initialization + m_bHaveFineSediment = + m_bHaveSandSediment = + m_bHaveCoarseSediment = + m_bBasementElevSave = + m_bSedimentTopSurfSave = + m_bTopSurfSave = + m_bSliceSave = + m_bSeaDepthSave = + m_bAvgSeaDepthSave = + m_bWaveHeightSave = + m_bAvgWaveHeightSave = + m_bWaveAngleSave = + m_bAvgWaveAngleSave = + m_bWaveAngleAndHeightSave = + m_bAvgWaveAngleAndHeightSave = + m_bDeepWaterWaveAngleAndHeightSave = + m_bBeachProtectionSave = + m_bWaveEnergySinceCollapseSave = + m_bMeanWaveEnergySave = + m_bBreakingWaveHeightSave = + m_bPotentialPlatformErosionSave = + m_bActualPlatformErosionSave = + m_bTotalPotentialPlatformErosionSave = + m_bTotalActualPlatformErosionSave = + m_bPotentialBeachErosionSave = + m_bActualBeachErosionSave = + m_bTotalPotentialBeachErosionSave = + m_bTotalActualBeachErosionSave = + m_bBeachDepositionSave = + m_bTotalBeachDepositionSave = + m_bLandformSave = + m_bSlopeConsSedSave = + m_bSlopeSaveForCliffToe = + m_bInterventionClassSave = + m_bInterventionHeightSave = + m_bSuspSedSave = + m_bAvgSuspSedSave = + m_bFineUnconsSedSave = + m_bSandUnconsSedSave = + m_bCoarseUnconsSedSave = + m_bFineConsSedSave = + m_bSandConsSedSave = + m_bCoarseConsSedSave = + m_bRasterCoastlineSave = + m_bRasterNormalProfileSave = + m_bActiveZoneSave = + m_bCliffCollapseSave = + m_bTotCliffCollapseSave = + m_bCliffCollapseDepositionSave = + m_bTotCliffCollapseDepositionSave = + m_bCliffNotchAllSave = + m_bCliffCollapseTimestepSave = + m_bRasterPolygonSave = + m_bPotentialPlatformErosionMaskSave = + m_bSeaMaskSave = + m_bBeachMaskSave = + m_bShadowZoneCodesSave = + m_bSaveRegular = + m_bCoastSave = + m_bCliffEdgeSave = + m_bNormalsSave = + m_bInvalidNormalsSave = + m_bCoastCurvatureSave = + m_bPolygonNodeSave = + m_bPolygonBoundarySave = + m_bCliffNotchSave = + m_bWaveTransectPointsSave = + m_bShadowBoundarySave = + m_bShadowDowndriftBoundarySave = + m_bDeepWaterWaveAngleSave = + m_bDeepWaterWaveHeightSave = + m_bDeepWaterWavePeriodSave = + m_bPolygonUnconsSedUpOrDownDriftSave = + m_bPolygonUnconsSedGainOrLossSave = + m_bCliffToeSave = + m_bSeaAreaTSSave = + m_bSWLTSSave = + m_bActualPlatformErosionTSSave = + m_bSuspSedTSSave = + m_bFloodSetupSurgeTSSave = + m_bFloodSetupSurgeRunupTSSave = + m_bCliffCollapseDepositionTSSave = + m_bCliffCollapseErosionTSSave = + m_bCliffCollapseNetTSSave = + m_bBeachErosionTSSave = + m_bBeachDepositionTSSave = + m_bBeachSedimentChangeNetTSSave = + m_bCliffNotchElevTSSave = + m_bSaveGISThisIter = + m_bOutputConsolidatedProfileData = + m_bOutputParallelProfileData = + m_bOutputErosionPotentialData = + m_bOmitSearchNorthEdge = + m_bOmitSearchSouthEdge = + m_bOmitSearchWestEdge = + m_bOmitSearchEastEdge = + m_bDoShorePlatformErosion = + m_bDoCliffCollapse = + m_bDoBeachSedimentTransport = + m_bGDALCanWriteFloat = + m_bGDALCanWriteInt32 = + m_bScaleRasterOutput = + m_bWorldFile = + m_bSingleDeepWaterWaveValues = + m_bHaveWaveStationData = + m_bSedimentInput = + m_bSedimentInputAtPoint = + m_bSedimentInputAtCoast = + m_bSedimentInputAlongLine = + m_bSedimentInputThisIter = + m_bSedimentInputEventSave = + m_bWaveSetupSave = + m_bStormSurgeSave = + m_bRiverineFlooding = + m_bRunUpSave = + m_bSetupSurgeFloodMaskSave = + m_bSetupSurgeRunupFloodMaskSave = + m_bRasterWaveFloodLineSave = + m_bVectorWaveFloodLineSave = + m_bFloodLocationSave = + m_bFloodSWLSetupLineSave = + m_bFloodSWLSetupSurgeLine = + m_bFloodSWLSetupSurgeRunupLineSave = + m_bGISSaveDigitsSequential = + m_bHaveConsolidatedSediment = + m_bGDALOptimisations = + m_bCliffToeLocate = + m_bHighestSWLSoFar = + m_bLowestSWLSoFar = false; + + m_bGDALCanCreate = true; + m_bCSVPerTimestepResults = true; // Default to CSV output format + m_bYamlInputFormat = false; // Default to .dat format + + m_papszGDALRasterOptions = + m_papszGDALVectorOptions = NULL; + + m_nLayers = + m_nCoastSmooth = + m_nCoastSmoothingWindowSize = + m_nSavGolCoastPoly = + m_nCliffEdgeSmooth = + m_nCliffEdgeSmoothWindow = + m_nSavGolCliffEdgePoly = + m_nProfileSmoothWindow = + m_nCoastNormalSpacing = + m_nCoastNormalInterventionSpacing = + m_nCoastCurvatureInterval = + m_nGISMaxSaveDigits = + m_nGISSave = + m_nUSave = + m_nThisSave = + m_nXGridSize = + m_nYGridSize = + m_nCoastMax = + m_nCoastMin = + //m_nNThisIterCliffCollapse = + //m_nNTotCliffCollapse = + m_nUnconsSedimentHandlingAtGridEdges = + m_nBeachErosionDepositionEquation = + m_nWavePropagationModel = + m_nSimStartSec = + m_nSimStartMin = + m_nSimStartHour = + m_nSimStartDay = + m_nSimStartMonth = + m_nSimStartYear = + m_nDeepWaterWaveDataNumTimeSteps = + m_nLogFileDetail = + m_nRunUpEquation = + m_nLevel = + m_nDefaultTalusWidthInCells = + m_nTalusProfileMinLenInCells = 0; + + // TODO 011 May wish to make this a user-supplied value + m_nGISMissingValue = + m_nMissingValue = INT_NODATA; + + m_nXMinBoundingBox = INT_MAX; + m_nXMaxBoundingBox = INT_MIN; + m_nYMinBoundingBox = INT_MAX; + m_nYMaxBoundingBox = INT_MIN; + + // cppcheck-suppress useInitializationList + m_GDALWriteIntDataType = GDT_Unknown; + // cppcheck-suppress useInitializationList + m_GDALWriteFloatDataType = GDT_Unknown; + + m_lGDALMaxCanWrite = + m_lGDALMinCanWrite = 0; + + m_ulIter = + m_ulTotTimestep = + m_ulThisIterNumPotentialBeachErosionCells = + m_ulThisIterNumActualBeachErosionCells = + m_ulThisIterNumBeachDepositionCells = + m_ulTotPotentialPlatformErosionOnProfiles = + m_ulTotPotentialPlatformErosionBetweenProfiles = + m_ulMissingValueBasementCells =0; + m_ulNumCells = + m_ulThisIterNumSeaCells = + m_ulThisIterNumCoastCells = + m_ulThisIterNumPotentialPlatformErosionCells = + m_ulThisIterNumActualPlatformErosionCells = 0; + + m_ulMissingValue = UNSIGNED_LONG_NODATA; + + for (int i = 0; i < NUMBER_OF_RNGS; i++) + m_ulRandSeed[i] = 0; + + for (int i = 0; i < SAVEMAX; i++) + m_dUSaveTime[i] = 0; + + m_dDurationUnitsMult = + m_dNorthWestXExtCRS = + m_dNorthWestYExtCRS = + m_dSouthEastXExtCRS = + m_dSouthEastYExtCRS = + m_dExtCRSGridArea = + m_dCellSide = + m_dCellDiagonal = + m_dInvCellSide = + m_dInvCellDiagonal = + m_dCellArea = + m_dSimDuration = + m_dTimeStep = + m_dSimElapsed = + m_dRegularSaveTime = + m_dRegularSaveInterval = + m_dClkLast = + m_dCPUClock = + m_dSeaWaterDensity = + m_dThisIterSWL = + m_dThisIterMeanSWL = + m_dInitialMeanSWL = + m_dFinalMeanSWL = + m_dDeltaSWLPerTimestep = + m_dBreakingWaveHeight = + m_dC_0 = + m_dL_0 = + m_dWaveDepthRatioForWaveCalcs = + m_dAllCellsDeepWaterWaveHeight = + m_dAllCellsDeepWaterWaveAngle = + m_dAllCellsDeepWaterWavePeriod = + m_dMaxUserInputWaveHeight = + m_dMaxUserInputWavePeriod = + m_dR = + m_dD50Fine = + m_dD50Sand = + m_dD50Coarse = + m_dBeachSedimentDensity = + m_dBeachSedimentPorosity = + m_dFineErodibility = + m_dSandErodibility = + m_dCoarseErodibility = + m_dFineErodibilityNormalized = + m_dSandErodibilityNormalized = + m_dCoarseErodibilityNormalized = + m_dKLS = + m_dKamphuis = + m_dG = + m_dInmersedToBulkVolumetric = + m_dDepthOfClosure = + m_dCoastNormalSpacing = + m_dCoastNormalInterventionSpacing = + m_dCoastNormalLength = + m_dThisIterTotSeaDepth = + m_dThisIterPotentialSedLostBeachErosion = + m_dThisIterLeftGridUnconsFine =//TODO067 + m_dThisIterLeftGridUnconsSand = + m_dThisIterLeftGridUnconsCoarse = + m_dThisIterPotentialPlatformErosion = + m_dThisIterActualPlatformErosionFineCons = + m_dThisIterActualPlatformErosionSandCons = + m_dThisIterActualPlatformErosionCoarseCons = + m_dThisIterPotentialBeachErosion = + m_dThisIterBeachErosionFine = + m_dThisIterBeachErosionSand = + m_dThisIterBeachErosionCoarse = + m_dThisIterBeachDepositionSand = + m_dThisIterBeachDepositionCoarse = + m_dThisIterFineSedimentToSuspension = + m_dDepositionSandDiff = + m_dDepositionCoarseDiff = + m_dDepthOverDBMax = + m_dTotPotentialPlatformErosionOnProfiles = + m_dTotPotentialPlatformErosionBetweenProfiles = + m_dProfileMaxSlope = + m_dMaxBeachElevAboveSWL = + m_dCliffErosionResistance = + m_dNotchDepthAtCollapse = + m_dThisIterNotchBaseElev = + m_dNotchBaseBelowSWL = + m_dCliffDepositionA = + m_dCliffDepositionPlanviewWidth = + m_dCliffTalusMinDepositionLength = + m_dMinCliffTalusHeightFrac = + m_dThisIterCliffCollapseErosionFineUncons = + m_dThisIterCliffCollapseErosionSandUncons = + m_dThisIterCliffCollapseErosionCoarseUncons = + m_dThisIterCliffCollapseErosionFineCons = + m_dThisIterCliffCollapseErosionSandCons = + m_dThisIterCliffCollapseErosionCoarseCons = + m_dThisIterUnconsSandCliffDeposition = + m_dThisIterUnconsCoarseCliffDeposition = + m_dThisIterCliffCollapseFineErodedDuringDeposition = + m_dThisIterCliffCollapseSandErodedDuringDeposition = + m_dThisIterCliffCollapseCoarseErodedDuringDeposition = + m_dCoastNormalRandSpacingFactor = + m_dDeanProfileStartAboveSWL = + m_dAccumulatedSeaLevelChange = + m_dBreakingWaveHeightDepthRatio = + m_dWaveDataWrapHours = + m_dThisIterTopElevMax = + m_dThisIterTopElevMin = + m_dThisiterUnconsFineInput = + m_dThisiterUnconsSandInput = + m_dThisiterUnconsCoarseInput = + m_dStartIterSuspFineAllCells = + m_dStartIterSuspFineInPolygons = + m_dStartIterUnconsFineAllCells = + m_dStartIterUnconsSandAllCells = + m_dStartIterUnconsCoarseAllCells = + m_dStartIterConsFineAllCells = + m_dStartIterConsSandAllCells = + m_dStartIterConsCoarseAllCells = + m_dThisIterDiffTotWaterLevel = + m_dThisIterDiffWaveSetupWaterLevel = + m_dThisIterDiffWaveSetupSurgeWaterLevel = + m_dThisIterDiffWaveSetupSurgeRunupWaterLevel = + m_dTotalFineUnconsInPolygons = + m_dTotalSandUnconsInPolygons = + m_dTotalCoarseUnconsInPolygons = + m_dUnconsSandNotDepositedLastIter = + m_dUnconsCoarseNotDepositedLastIter = + m_dTotalFineConsInPolygons = + m_dTotalSandConsInPolygons = + m_dTotalCoarseConsInPolygons = + m_dSlopeThresholdForCliffToe = 0; + + m_dMinSWLSoFar = DBL_MAX; + m_dMaxSWLSoFar = DBL_MIN; + + for (int i = 0; i < 6; i++) + m_dGeoTransform[i] = 0; + + // TODO 011 May wish to make this a user-supplied value + m_dGISMissingValue = + m_dMissingValue = DBL_NODATA; + + m_ldGTotPotentialPlatformErosion = + m_ldGTotFineActualPlatformErosion = + m_ldGTotSandActualPlatformErosion = + m_ldGTotCoarseActualPlatformErosion = + m_ldGTotPotentialSedLostBeachErosion = + m_ldGTotActualFineLostBeachErosion = + m_ldGTotActualSandLostBeachErosion = + m_ldGTotActualCoarseLostBeachErosion = + m_ldGTotSandSedLostCliffCollapse = + m_ldGTotCoarseSedLostCliffCollapse = + m_ldGTotCliffCollapseFine = + m_ldGTotCliffCollapseSand = + m_ldGTotCliffCollapseCoarse = + m_ldGTotCliffTalusFineToSuspension = + m_ldGTotCliffTalusSandDeposition = + m_ldGTotCliffTalusCoarseDeposition = + m_ldGTotCliffCollapseFineErodedDuringDeposition = + m_ldGTotCliffCollapseSandErodedDuringDeposition = + m_ldGTotCliffCollapseCoarseErodedDuringDeposition = + m_ldGTotPotentialBeachErosion = + m_ldGTotActualFineBeachErosion = + m_ldGTotActualSandBeachErosion = + m_ldGTotActualCoarseBeachErosion = + m_ldGTotSandBeachDeposition = + m_ldGTotCoarseBeachDeposition = + m_ldGTotSuspendedSediment = + m_ldGTotSandDepositionDiff = + m_ldGTotCoarseDepositionDiff = + m_ldGTotFineSedimentInput = + m_ldGTotSandSedimentInput = + m_ldGTotCoarseSedimentInput = 0; + + m_tSysStartTime = + m_tSysEndTime = 0; + + m_pRasterGrid = NULL; +} + +//=============================================================================================================================== +//! The CSimulation destructor +//=============================================================================================================================== +CSimulation::~CSimulation(void) +{ + // Close output files if open + if (LogStream && LogStream.is_open()) + { + LogStream.flush(); + LogStream.close(); + } + + if (OutStream && OutStream.is_open()) + { + OutStream.flush(); + OutStream.close(); + } + + if (SeaAreaTSStream && SeaAreaTSStream.is_open()) + { + SeaAreaTSStream.flush(); + SeaAreaTSStream.close(); + } + + if (SWLTSStream && SWLTSStream.is_open()) + { + SWLTSStream.flush(); + SWLTSStream.close(); + } + + if (PlatformErosionTSStream && PlatformErosionTSStream.is_open()) + { + PlatformErosionTSStream.flush(); + PlatformErosionTSStream.close(); + } + + if (CliffCollapseErosionTSStream && CliffCollapseErosionTSStream.is_open()) + { + CliffCollapseErosionTSStream.flush(); + CliffCollapseErosionTSStream.close(); + } + + if (CliffCollapseDepositionTSStream && CliffCollapseDepositionTSStream.is_open()) + { + CliffCollapseDepositionTSStream.flush(); + CliffCollapseDepositionTSStream.close(); + } + + if (CliffCollapseNetChangeTSStream && CliffCollapseNetChangeTSStream.is_open()) + { + CliffCollapseNetChangeTSStream.flush(); + CliffCollapseNetChangeTSStream.close(); + } + + if (FineSedSuspensionTSStream && FineSedSuspensionTSStream.is_open()) + { + FineSedSuspensionTSStream.flush(); + FineSedSuspensionTSStream.close(); + } + + if (FloodSetupSurgeTSStream && FloodSetupSurgeTSStream.is_open()) + { + FloodSetupSurgeTSStream.flush(); + FloodSetupSurgeTSStream.close(); + } + + if (FloodSetupSurgeRunupTSStream && FloodSetupSurgeRunupTSStream.is_open()) + { + FloodSetupSurgeRunupTSStream.flush(); + FloodSetupSurgeRunupTSStream.close(); + } + + if (CliffNotchElevTSStream && CliffNotchElevTSStream.is_open()) + { + CliffNotchElevTSStream.flush(); + CliffNotchElevTSStream.close(); + } + + if (m_pRasterGrid) + delete m_pRasterGrid; +} + +//=============================================================================================================================== +//! Returns the double Missing Value code +//=============================================================================================================================== +double CSimulation::dGetMissingValue(void) const +{ + return m_dMissingValue; +} + +//=============================================================================================================================== +//! Returns the still water level (SWL) +//=============================================================================================================================== +double CSimulation::dGetThisIterSWL(void) const +{ + return m_dThisIterSWL; +} + +//=============================================================================================================================== +//! Returns the this-iteration total water level +//=============================================================================================================================== +double CSimulation::dGetThisIterTotWaterLevel(void) const +{ + return m_dThisIterDiffTotWaterLevel; +} + +// //=============================================================================================================================== +// //! Returns the max elevation of the beach above SWL +// //=============================================================================================================================== +// double CSimulation::dGetMaxBeachElevAboveSWL (void) const +// { +// return m_dMaxBeachElevAboveSWL; +// } + +//=============================================================================================================================== +// Returns the cell side length +//=============================================================================================================================== +// double CSimulation::dGetCellSide(void) const +// { +// return m_dCellSide; +// } + +//=============================================================================================================================== +//! Returns X grid max +//=============================================================================================================================== +int CSimulation::nGetGridXMax(void) const +{ + return m_nXGridSize; +} + +//=============================================================================================================================== +//! Returns Y grid max +//=============================================================================================================================== +int CSimulation::nGetGridYMax(void) const +{ + return m_nYGridSize; +} + +//=============================================================================================================================== +//! Returns D50 for fine sediment +//=============================================================================================================================== +double CSimulation::dGetD50Fine(void) const +{ + return m_dD50Fine; +} + +//=============================================================================================================================== +//! Returns D50 for sand sediment +//=============================================================================================================================== +double CSimulation::dGetD50Sand(void) const +{ + return m_dD50Sand; +} + +//=============================================================================================================================== +//! Returns D50 for coarse sediment +//=============================================================================================================================== +double CSimulation::dGetD50Coarse(void) const +{ + return m_dD50Coarse; +} + +//=============================================================================================================================== +//! The nDoSimulation member function of CSimulation sets up and runs the simulation +//=============================================================================================================================== +int CSimulation::nDoSimulation(int nArg, char const* pcArgv[]) +{ + // ================================================== initialisation section ================================================ + // Hello, World! + AnnounceStart(); + + // Start the clock ticking + StartClock(); + + // Deal with command-line parameters + int nRet = nHandleCommandLineParams(nArg, pcArgv); + + if (nRet != RTN_OK) + return (nRet); + + // Find out the folder in which the CoastalME executable sits, in order to open the .ini file (they are assumed to be in the same folder) + if (! bFindExeDir(pcArgv[0])) + return (RTN_ERR_CMEDIR); + + // OK, we are off, tell the user about the licence and the start time + AnnounceLicence(); + + // Read the .ini file and get the name of the run-data file, and path for output etc. + if (! bReadIniFile()) + return (RTN_ERR_INI); + + // Check if output dir exists + if ((! is_directory(m_strOutPath.c_str())) || (! exists(m_strOutPath.c_str()))) + { + // Output dir does not exist + bool bCreateDir = false; + + if ((isatty(fileno(stdout))) && (isatty(fileno(stderr)))) + { + // Running with stdout and stderr as a tty, so ask the user if they wish to create it + char ch; + cerr << endl + << "Output folder '" << m_strOutPath << "' does not exist. Create it? (Y/N) "; + cerr.flush(); + cin.get(ch); + + if ((ch == 'y') || (ch == 'Y')) + bCreateDir = true; + } + else + { + // Running with stdout or stderr not a tty, so create output dir rather than abort + bCreateDir = true; + } + + if (bCreateDir) + { + // Yes, so create the directory + create_directories(m_strOutPath.c_str()); + cerr << m_strOutPath << " created" << endl << endl; + } + else + // Nope, just end the run + return RTN_USER_ABORT; + } + + // We have the name of the run-data input file, so read it + if (! bReadRunDataFile()) + return RTN_ERR_RUNDATA; + + // Check raster GIS output format + if (! bCheckRasterGISOutputFormat()) + return (RTN_ERR_RASTER_GIS_OUT_FORMAT); + + // Check vector GIS output format + if (! bCheckVectorGISOutputFormat()) + return (RTN_ERR_VECTOR_GIS_OUT_FORMAT); + + // Open log file + if (! bOpenLogFile()) + return (RTN_ERR_LOGFILE); + + // Set up the time series output files + if (! bSetUpTSFiles()) + return (RTN_ERR_TSFILE); + + // Initialize the random number generators + for (int n = 0; n < NUMBER_OF_RNGS; n++) + m_Rand[n].seed(m_ulRandSeed[n]); + + // If we are doing Savitzky-Golay smoothing of the vector coastline(s), calculate the filter coefficients + if (m_nCoastSmooth == SMOOTH_SAVITZKY_GOLAY) + CalcSavitzkyGolayCoeffs(); + + // Create the raster grid object + m_pRasterGrid = new CGeomRasterGrid(this); + + // Read in the basement layer (must have this file), create the raster grid, then read in the basement DEM data to the array + AnnounceReadBasementDEM(); + nRet = nReadRasterBasementDEM(); + + if (nRet != RTN_OK) + return nRet; + + // If we are simulating cliff collapse: then now that we have a value for m_dCellSide, we can check some more input parameters. Talus must be more than one cell wide, and since the number of cells must be odd, three cells is the minimum width + if (m_bDoCliffCollapse) + { + int const nTmp = nConvertMetresToNumCells(m_dCliffDepositionPlanviewWidth); + if (nTmp < 3) + { + string const strErr = ERR + "cliff deposition must have a planview width of at least three cells. The current setting of " + to_string(m_dCliffDepositionPlanviewWidth) + " m gives a planview width of " + to_string(nTmp) + " cells. Please edit " + m_strDataPathName; + cerr << strErr << endl; + LogStream << strErr << endl; + OutStream << strErr << endl; + return RTN_ERR_RUNDATA; + } + } + + // Do some more initialisation + // cppcheck-suppress truncLongCastAssignment + m_ulNumCells = m_nXGridSize * m_nYGridSize; + + // Mark edge cells, as defined by the basement layer + nRet = nMarkBoundingBoxEdgeCells(); + + if (nRet != RTN_OK) + return nRet; + + // // DEBUG CODE ================================================================================================================= + // for (int n = 0; n < m_VEdgeCell.size(); n++) + // { + // LogStream << "[" << m_VEdgeCell[n].nGetX() << "][" << m_VEdgeCell[n].nGetY() << "] = {" << dGridCentroidXToExtCRSX(m_VEdgeCell[n].nGetX()) << ", " << dGridCentroidYToExtCRSY(m_VEdgeCell[n].nGetY()) << "} " << m_VEdgeCellEdge[n] << endl; + // } + // // DEBUG CODE ================================================================================================================= + + // If we are using the default cell spacing, then now that we know the size of the raster cells, we can set the size of profile spacing in m + if (bFPIsEqual(m_dCoastNormalSpacing, 0.0, TOLERANCE)) + m_dCoastNormalSpacing = DEFAULT_PROFILE_SPACING * m_dCellSide; + else + { + // The user specified a profile spacing, is this too small? + m_nCoastNormalSpacing = nRound(m_dCoastNormalSpacing / m_dCellSide); + + if (m_nCoastNormalSpacing < DEFAULT_PROFILE_SPACING) + { + cerr << ERR << "profile spacing was specified as " << m_dCoastNormalSpacing << " m, which is " << m_nCoastNormalSpacing << " cells. Polygon creation works poorly if profile spacing is less than " << DEFAULT_PROFILE_SPACING << " cells, i.e. " << DEFAULT_PROFILE_SPACING * m_dCellSide << " m" << endl; + + LogStream << ERR << "profile spacing was specified as " << m_dCoastNormalSpacing << " m, which is " << m_nCoastNormalSpacing << " cells. Polygon creation works poorly if profile spacing is less than " << DEFAULT_PROFILE_SPACING << " cells, i.e. " << DEFAULT_PROFILE_SPACING * m_dCellSide << " m" << endl; + + return RTN_ERR_PROFILE_SPACING; + } + } + + // Set the profile spacing on interventions + m_dCoastNormalInterventionSpacing = m_dCoastNormalSpacing * INTERVENTION_PROFILE_SPACING_FACTOR; + m_nCoastNormalInterventionSpacing = nRound(m_dCoastNormalInterventionSpacing / m_dCellSide); + + // We have at least one filename for the first layer, so add the correct number of layers. Note the the number of layers does not change during the simulation: however layers can decrease in thickness until they have zero thickness + AnnounceAddLayers(); + + for (int nX = 0; nX < m_nXGridSize; nX++) + for (int nY = 0; nY < m_nYGridSize; nY++) + m_pRasterGrid->Cell(nX, nY).AppendLayers(m_nLayers); + + // Tell the user what is happening then read in the layer files + AnnounceReadRasterFiles(); + + for (int nLayer = 0; nLayer < m_nLayers; nLayer++) + { + if (! m_VstrInitialFineUnconsSedimentFile[nLayer].empty()) + { + // Read in the initial fine unconsolidated sediment depth file(s) + AnnounceReadInitialFineUnconsSedGIS(nLayer); + nRet = nReadRasterGISFile(FINE_UNCONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + + if (! m_VstrInitialSandUnconsSedimentFile[nLayer].empty()) + { + // Read in the initial sand unconsolidated sediment depth file + AnnounceReadInitialSandUnconsSedGIS(nLayer); + nRet = nReadRasterGISFile(SAND_UNCONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + + if (! m_VstrInitialCoarseUnconsSedimentFile[nLayer].empty()) + { + // Read in the initial coarse unconsolidated sediment depth file + AnnounceReadInitialCoarseUnconsSedGIS(nLayer); + nRet = nReadRasterGISFile(COARSE_UNCONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + + if (! m_VstrInitialFineConsSedimentFile[nLayer].empty()) + { + // Read in the initial fine consolidated sediment depth file + AnnounceReadInitialFineConsSedGIS(nLayer); + nRet = nReadRasterGISFile(FINE_CONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + + if (! m_VstrInitialSandConsSedimentFile[nLayer].empty()) + { + // Read in the initial sand consolidated sediment depth file + AnnounceReadInitialSandConsSedGIS(nLayer); + nRet = nReadRasterGISFile(SAND_CONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + + if (! m_VstrInitialCoarseConsSedimentFile[nLayer].empty()) + { + // Read in the initial coarse consolidated sediment depth file + AnnounceReadInitialCoarseConsSedGIS(nLayer); + nRet = nReadRasterGISFile(COARSE_CONS_RASTER, nLayer); + if (nRet != RTN_OK) + return (nRet); + } + } + + if (! m_strInitialSuspSedimentFile.empty()) + { + // Read in the initial suspended sediment depth file + AnnounceReadInitialSuspSedGIS(); + nRet = nReadRasterGISFile(SUSP_SED_RASTER, 0); + if (nRet != RTN_OK) + return (nRet); + } + + + // Maybe read in the landform class data, otherwise calculate this during the first timestep using identification rules + if (! m_strInitialLandformFile.empty()) + { + AnnounceReadLGIS(); + nRet = nReadRasterGISFile(LANDFORM_RASTER, 0); + if (nRet != RTN_OK) + return (nRet); + } + + // Maybe read in intervention data + if (! m_strInterventionClassFile.empty()) + { + AnnounceReadICGIS(); + nRet = nReadRasterGISFile(INTERVENTION_CLASS_RASTER, 0); + if (nRet != RTN_OK) + return (nRet); + + AnnounceReadIHGIS(); + nRet = nReadRasterGISFile(INTERVENTION_HEIGHT_RASTER, 0); + if (nRet != RTN_OK) + return (nRet); + } + + // Maybe read in the tide data + if (! m_strTideDataFile.empty()) + { + AnnounceReadTideData(); + nRet = nReadTideDataFile(); + if (nRet != RTN_OK) + return (nRet); + } + + // Read in the erosion potential shape function data + AnnounceReadSCAPEShapeFunctionFile(); + nRet = nReadShapeFunctionFile(); + if (nRet != RTN_OK) + return (nRet); + + // Do we want to output the erosion potential look-up values, for checking purposes? + if (m_bOutputErosionPotentialData) + WriteLookUpData(); + + // OK, now read in the vector files (if any) + if (m_bHaveWaveStationData || m_bSedimentInput) + AnnounceReadVectorFiles(); + + // Maybe read in deep water wave station data + if (m_bHaveWaveStationData) + { + // We are reading deep water wave height, orientation and period from a file of vector points and file time series + AnnounceReadDeepWaterWaveValuesGIS(); + + // Read in vector points + nRet = nReadVectorGISFile(DEEP_WATER_WAVE_STATIONS_VEC); + if (nRet != RTN_OK) + return (nRet); + + int const nWaveStations = static_cast(m_VnDeepWaterWaveStationID.size()); + + if (nWaveStations == 1) + m_bSingleDeepWaterWaveValues = true; + + // Read in time series values, and initialise the vector which stores each timestep's deep water wave height, orientation and period + nRet = nReadWaveStationInputFile(nWaveStations); + if (nRet != RTN_OK) + return (nRet); + } + + // Maybe read in sediment input event data + if (m_bSedimentInput) + { + // We are reading sediment input event data + AnnounceReadSedimentEventInputValuesGIS(); + + // Read in vector points for sediment input events + nRet = nReadVectorGISFile(SEDIMENT_INPUT_EVENT_LOCATION_VEC); + if (nRet != RTN_OK) + return (nRet); + + // Read in the time series values for sediment input events + nRet = nReadSedimentInputEventFile(); + if (nRet != RTN_OK) + return (nRet); + } + + // Maybe read in flood input location + if (m_bFloodLocationSave) + { + // We are reading sediment input event data + AnnounceReadFloodLocationGIS(); + + // Read in vector points for sediment input events + nRet = nReadVectorGISFile(FLOOD_LOCATION_VEC); + if (nRet != RTN_OK) + return (nRet); + } + + // Read sea flood fill seed points from shapefile if specified + if (! m_strSeaFloodSeedPointShapefile.empty()) + { + LogStream << "Reading sea flood fill seed points from " << m_strSeaFloodSeedPointShapefile << endl; + + nRet = nReadSeaFloodSeedPointShapefile(); + if (nRet != RTN_OK) + return (nRet); + } + + // Open the main output + OutStream.open(m_strOutFile.c_str(), ios::out | ios::trunc); + + if (!OutStream) + { + // Error, cannot open Out file + cerr << ERR << "cannot open " << m_strOutFile << " for output" << endl; + return (RTN_ERR_OUTFILE); + } + + // Write beginning-of-run information to Out and Log files + WriteStartRunDetails(); + + // Final stage of initialization + AnnounceFinalInitialization(); + + // Misc initialisation calcs + m_nCoastMax = COAST_LENGTH_MAX * tMax(m_nXGridSize, m_nYGridSize); // Arbitrary but probably OK + m_nCoastMin = tMin(m_nXGridSize, m_nYGridSize); // In some cases the following rule doesn't work TODO 007 Finish surge and runup stuff + // nRound(COAST_LENGTH_MIN_X_PROF_SPACE * m_dCoastNormalSpacing / m_dCellSide); // TODO 007 Finish surge and runup stuff + m_nCoastCurvatureInterval = tMax(nRound(m_dCoastNormalSpacing / (m_dCellSide * 2)), 2); // TODO 007 Finish surge and runup stuff + + // For beach erosion/deposition, conversion from immersed weight to bulk volumetric (sand and voids) transport rate (Leo Van Rijn) TODO 007 need full reference + m_dInmersedToBulkVolumetric = 1 / ((m_dBeachSedimentDensity - m_dSeaWaterDensity) * (1 - m_dBeachSedimentPorosity) * m_dG); + + m_bConsChangedThisIter.resize(m_nLayers, false); + m_bUnconsChangedThisIter.resize(m_nLayers, false); + + // Normalize sediment erodibility values, so that none are > 1 + double const dTmp = m_dFineErodibility + m_dSandErodibility + m_dCoarseErodibility; + m_dFineErodibilityNormalized = m_dFineErodibility / dTmp; + m_dSandErodibilityNormalized = m_dSandErodibility / dTmp; + m_dCoarseErodibilityNormalized = m_dCoarseErodibility / dTmp; + + // Intialise SWL + m_dThisIterSWL = m_dInitialMeanSWL; + + // If SWL changes during the simulation, calculate the per-timestep increment (could be -ve) + if (! bFPIsEqual(m_dFinalMeanSWL, m_dInitialMeanSWL, TOLERANCE)) + { + m_dDeltaSWLPerTimestep = (m_dTimeStep * (m_dFinalMeanSWL - m_dInitialMeanSWL)) / m_dSimDuration; + m_dAccumulatedSeaLevelChange -= m_dDeltaSWLPerTimestep; + } + + // Calculate default planview width of cliff collapse talus, in cells + m_nDefaultTalusWidthInCells = nConvertMetresToNumCells(m_dCliffDepositionPlanviewWidth); + + // The default talus collapse width must be an odd number of cells in width i.e. centred on the cliff collapse cell (but only if we are not at the end of the coast) + if ((m_nDefaultTalusWidthInCells % 2) == 0) + m_nDefaultTalusWidthInCells++; + + // This is the minimum planview length (in cells) of the Dean profile. The initial length will be increased if we can't deposit sufficient talus + m_nTalusProfileMinLenInCells = nConvertMetresToNumCells(m_dCliffTalusMinDepositionLength); + + // ===================================================== The main loop ====================================================== + // Tell the user what is happening + AnnounceIsRunning(); + + while (true) + { + // Check that we haven't gone on too long: if not then update timestep number etc. + if (bTimeToQuit()) + break; + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + LogStream << "TIMESTEP " << m_ulIter << " " << string(154, '=') << endl; + + LogStream << fixed << setprecision(3); + + // Check to see if there is a new intervention in place: if so, update it on the RasterGrid array + nRet = nUpdateIntervention(); + if (nRet != RTN_OK) + return nRet; + + // Calculate changes due to external forcing (change in still water level, tide level and deep water waves height, orientation and period) + nRet = nCalcExternalForcing(); + if (nRet != RTN_OK) + return nRet; + + // Do per-timestep initialisation: set up the grid cells ready for this timestep, also initialise per-timestep totals. Note that in the first timestep, all cells (including hinterland cells) are given the deep water wave values + nRet = nInitGridAndCalcStillWaterLevel(); + if (nRet != RTN_OK) + return nRet; + + // Next find out which cells are inundated and locate the coastline(s). This also gives to all sea cells, wave values which are the same as the deep water values. For shallow water sea cells, these wave values will be changed later, in nDoAllPropagateWaves() + nRet = nLocateSeaAndCoasts(); + if (nRet != RTN_OK) + return nRet; + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // Locate estuaries TODO someday... + + if (m_bHaveConsolidatedSediment && m_bDoCliffCollapse && m_bCliffToeLocate) + { + // Locate and trace cliff toe + nRet = nLocateCliffToe(); + if (nRet != RTN_OK) + return nRet; + } + + // For all cells, use classification rules to assign sea and hinterland landform categories + nRet = nAssignLandformsForAllCells(); + if (nRet != RTN_OK) + return nRet; + + // For every coastline, use classification rules to assign landform categories + nRet = nAssignLandformsForAllCoasts(); + if (nRet != RTN_OK) + return nRet; + + // Create all coastline-normal profiles, in coastline-concave-curvature sequence + nRet = nCreateAllProfiles(); + if (nRet != RTN_OK) + return nRet; + + // Check the coastline-normal profiles for intersection, modify the profiles if they intersect, then mark valid profiles on the raster grid + nRet = nCheckAndMarkAllProfiles(); + if (nRet != RTN_OK) + return nRet; + + if (m_VCoast.size() > 1) + { + // We have multiple coastlines + nRet = nDoMultipleCoastlines(); + if (nRet != RTN_OK) + return nRet; + } + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // Create the coast polygons + nRet = nCreateAllPolygons(); + if (nRet != RTN_OK) + return nRet; + + // Mark cells of the raster grid that are within each polygon, and do some polygon initialisation + MarkPolygonCells(); + + // // DEBUG CODE ================ + // m_nGISSave++; + // if (! bWriteVectorGISFile(VECTOR_PLOT_COAST, &VECTOR_PLOT_COAST_TITLE)) + // return false; + // if (! bWriteVectorGISFile(VECTOR_PLOT_NORMALS, &VECTOR_PLOT_NORMALS_TITLE)) + // return false; + // if (! bWriteVectorGISFile(VECTOR_PLOT_INVALID_NORMALS, &VECTOR_PLOT_INVALID_NORMALS_TITLE)) + // return false; + // if (! bWriteRasterGISFile(RASTER_PLOT_NORMAL_PROFILE, &RASTER_PLOT_NORMAL_PROFILE_TITLE)) + // return false; + // if (! bWriteRasterGISFile(RASTER_PLOT_COAST, &RASTER_PLOT_COAST_TITLE)) + // return false; + // if (! bWriteRasterGISFile(RASTER_PLOT_POLYGON, &RASTER_PLOT_POLYGON_TITLE)) + // return false; + // if (! bWriteVectorGISFile(VECTOR_PLOT_POLYGON_BOUNDARY, &VECTOR_PLOT_POLYGON_BOUNDARY_TITLE)) + // return false; + // // DEBUG CODE ================ + + // Calculate the length of the shared normal between each polygon and the adjacent polygon(s) + nRet = nDoPolygonSharedBoundaries(); + if (nRet != RTN_OK) + return nRet; + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // // DEBUG CODE ========================================================================================================= + // nNODATA = 0; + // nPoly0 = 0; + // nPoly24 = 0; + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // int nTmp = m_pRasterGrid->Cell(nX, nY).nGetPolygonID(); + // if (nTmp == INT_NODATA) + // nNODATA++; + // + // if (nTmp == 0) + // nPoly0++; + // + // if (nTmp == 24) + // nPoly24++; + // } + // } + // LogStream << "After marking polygon cells, N cells with NODATA polygon ID = " << nNODATA << endl; + // // LogStream << "After marking polygon cells, N cells with zero polygon ID = " << nPoly0 << endl; + // LogStream << "After marking polygon cells, N cells with 24 polygon ID = " << nPoly24 << endl; + // // DEBUG CODE ========================================================================================================= + // PropagateWind(); + + // Give every coast point a value for deep water wave height and direction + nRet = nSetAllCoastpointDeepWaterWaveValues(); + if (nRet != RTN_OK) + return nRet; + + // // DEBUG CODE =============== + // for (int nCoast = 0; nCoast < static_cast(m_VCoast.size()); nCoast++) + // { + // LogStream << "====================" << endl; + // + // for (int nProfile = 0; nProfile < m_VCoast[nCoast].nGetNumProfiles(); nProfile++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfile(nProfile); + // int nCell = pProfile->nGetNumCellsInProfile(); + // LogStream << "Profile " << pProfile->nGetProfileID() << " nGetNumCellsInProfile() = " << nCell << endl; + // } + // + // LogStream << endl; + // + // for (int nProfile = 0; nProfile < m_VCoast[nCoast].nGetNumProfiles(); nProfile++) + // { + // CGeomProfile* pProfile = m_VCoast[nCoast].pGetProfileWithDownCoastSeq(nProfile); + // int nCell = pProfile->nGetNumCellsInProfile(); + // LogStream << "Profile " << pProfile->nGetProfileID() << " nGetNumCellsInProfile() = " << nCell << endl; + // } + // + // LogStream << "====================" << endl; + // } + // // DEBUG CODE ===================== + + // Change the wave properties in all shallow water sea cells: propagate waves and define the active zone, also locate wave shadow zones + nRet = nDoAllPropagateWaves(); + if (nRet != RTN_OK) + return nRet; + + // Output polygon share table and pre-existing sediment table to log file + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + { + WritePolygonInfoTable(); + WritePolygonPreExistingSedimentTable(); + } + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // // DEBUG CODE =========================================================================================================== + // string strOutFile = m_strOutPath; + // strOutFile += "sea_wave_height_CHECKPOINT_"; + // strOutFile += to_string(m_ulIter); + // strOutFile += ".tif"; + // + // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); + // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); + // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); + // pDataSet->SetGeoTransform(m_dGeoTransform); + // + // int nn = 0; + // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); + // } + // } + // + // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); + // pBand->SetNoDataValue(m_dMissingValue); + // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); + // + // if (nRet == CE_Failure) + // return RTN_ERR_GRIDCREATE; + // + // GDALClose(pDataSet); + // delete[] pdRaster; + // // DEBUG CODE =========================================================================================================== + // + // // DEBUG CODE =========================================================================================================== + // strOutFile = m_strOutPath; + // strOutFile += "sea_wave_angle_CHECKPOINT_"; + // strOutFile += to_string(m_ulIter); + // strOutFile += ".tif"; + // + // pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); + // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); + // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); + // pDataSet->SetGeoTransform(m_dGeoTransform); + // + // nn = 0; + // pdRaster = new double[m_nXGridSize * m_nYGridSize]; + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); + // } + // } + // + // pBand = pDataSet->GetRasterBand(1); + // pBand->SetNoDataValue(m_dMissingValue); + // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); + // + // if (nRet == CE_Failure) + // return RTN_ERR_GRIDCREATE; + // + // GDALClose(pDataSet); + // delete[] pdRaster; + // // DEBUG CODE =========================================================================================================== + + // Save the not-deposited values, to be shown in the logfile after we've finished beach sediment movement + m_dUnconsSandNotDepositedLastIter = m_dDepositionSandDiff; + m_dUnconsCoarseNotDepositedLastIter = m_dDepositionCoarseDiff; + + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + if (m_dDepositionSandDiff > MASS_BALANCE_TOLERANCE) + { + LogStream << m_ulIter << ": AT ITERATION START m_dDepositionSandDiff = " << m_dDepositionSandDiff * m_dCellArea << " m_dUnconsSandNotDepositedLastIter = " << m_dUnconsSandNotDepositedLastIter << endl; + LogStream << m_ulIter << ": AT ITERATION START m_dDepositionCoarseDiff = " << m_dDepositionCoarseDiff * m_dCellArea << " m_dUnconsCoarseNotDepositedLastIter = " << m_dUnconsCoarseNotDepositedLastIter << endl; + } + + if (m_bDoShorePlatformErosion) + { + // Calculate elevation change on the consolidated sediment which comprises the coastal platform + nRet = nDoAllShorePlatFormErosion(); + if (nRet != RTN_OK) + return nRet; + } + + // Output shore platform erosion table to log file + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + WritePolygonShorePlatformErosion(); + + // Are we considering cliff collapse? + if (m_bHaveConsolidatedSediment && m_bDoCliffCollapse) + { + // Do all cliff collapses for this timestep (if any) + nRet = nDoAllWaveEnergyToCoastLandforms(); + if (nRet != RTN_OK) + return nRet; + + // Output cliff collapse table to log file + if (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL) + WritePolygonCliffCollapseErosion(); + } + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + if (m_bDoBeachSedimentTransport) + { + // Next simulate beach erosion and deposition i.e. simulate alongshore transport of unconsolidated sediment (longshore drift) between polygons. First calculate potential sediment movement between polygons + DoAllPotentialBeachErosion(); + + // Do within-sediment redistribution of unconsolidated sediment, constraining potential sediment movement to give actual (i.e. supply-limited) sediment movement to/from each polygon in three size classes + nRet = nDoAllActualBeachErosionAndDeposition(); + if (nRet != RTN_OK) + return nRet; + } + + // If we have sediment input events, then check to see whether this is time for an event to occur. If it is, then do it + if (m_bSedimentInput) + { + nRet = nCheckForSedimentInputEvent(); + if (nRet != RTN_OK) + return nRet; + + // If we have had at least one sediment input event this iteration, then output the sediment event per polygon table to the log file + if (m_bSedimentInputThisIter && (m_nLogFileDetail >= LOG_FILE_MIDDLE_DETAIL)) + WritePolygonSedimentInputEventTable(); + } + + // // Add the fine sediment that was eroded this timestep (from the shore platform, from cliff collapse, from erosion of existing fine sediment during cliff collapse talus deposition, and from beach erosion; minus the fine sediment from beach erosion that went off-grid) to the suspended sediment load + // double dFineThisIter = m_dThisIterActualPlatformErosionFineCons + m_dThisIterCliffCollapseErosionFineUncons + m_dThisIterCliffCollapseErosionFineCons + m_dThisIterCliffCollapseFineErodedDuringDeposition + m_dThisIterBeachErosionFine - m_dThisIterLeftGridUnconsFine; + // + // m_dThisIterFineSedimentToSuspension += dFineThisIter; + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // // DEBUG CODE =========================================================================================================== + // string strOutFile = m_strOutPath; + // strOutFile += "sea_wave_height_CHECKPOINT_"; + // strOutFile += to_string(m_ulIter); + // strOutFile += ".tif"; + // + // GDALDriver* pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); + // GDALDataset* pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); + // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); + // pDataSet->SetGeoTransform(m_dGeoTransform); + // + // int nn = 0; + // double* pdRaster = new double[m_nXGridSize * m_nYGridSize]; + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveHeight(); + // } + // } + // + // GDALRasterBand* pBand = pDataSet->GetRasterBand(1); + // pBand->SetNoDataValue(m_dMissingValue); + // int nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); + // + // if (nRet == CE_Failure) + // return RTN_ERR_GRIDCREATE; + // + // GDALClose(pDataSet); + // delete[] pdRaster; + // // DEBUG CODE =========================================================================================================== + // + // // DEBUG CODE =========================================================================================================== + // strOutFile = m_strOutPath; + // strOutFile += "sea_wave_angle_CHECKPOINT_"; + // strOutFile += to_string(m_ulIter); + // strOutFile += ".tif"; + // + // pDriver = GetGDALDriverManager()->GetDriverByName("gtiff"); + // pDataSet = pDriver->Create(strOutFile.c_str(), m_nXGridSize, m_nYGridSize, 1, GDT_Float64, m_papszGDALRasterOptions); + // pDataSet->SetProjection(m_strGDALBasementDEMProjection.c_str()); + // pDataSet->SetGeoTransform(m_dGeoTransform); + // + // nn = 0; + // pdRaster = new double[m_nXGridSize * m_nYGridSize]; + // for (int nY = 0; nY < m_nYGridSize; nY++) + // { + // for (int nX = 0; nX < m_nXGridSize; nX++) + // { + // pdRaster[nn++] = m_pRasterGrid->Cell(nX, nY).dGetWaveAngle(); + // } + // } + // + // pBand = pDataSet->GetRasterBand(1); + // pBand->SetNoDataValue(m_dMissingValue); + // nRet = pBand->RasterIO(GF_Write, 0, 0, m_nXGridSize, m_nYGridSize, pdRaster, m_nXGridSize, m_nYGridSize, GDT_Float64, 0, 0, NULL); + // + // if (nRet == CE_Failure) + // return RTN_ERR_GRIDCREATE; + // + // GDALClose(pDataSet); + // delete[] pdRaster; + // // DEBUG CODE =========================================================================================================== + + // Do some end-of-timestep updates to the raster grid, also update per-timestep and running totals + nRet = nUpdateGrid(); + if (nRet != RTN_OK) + return nRet; + + // Make water level inundation on grid + if (m_bFloodSWLSetupSurgeLine || m_bSetupSurgeFloodMaskSave) + { + m_nLevel = 0; + + nRet = nLocateFloodAndCoasts(); + if (nRet != RTN_OK) + return nRet; + } + + if (m_bFloodSWLSetupSurgeRunupLineSave || m_bSetupSurgeRunupFloodMaskSave) + { + // TODO 007 Finish surge and runup stuff + m_nLevel = 1; + + nRet = nLocateFloodAndCoasts(); + if (nRet != RTN_OK) + return nRet; + } + + // Now save results, first the raster and vector GIS files if required + m_bSaveGISThisIter = false; + + if ((m_bSaveRegular && (m_dSimElapsed >= m_dRegularSaveTime) && (m_dSimElapsed < m_dSimDuration)) || (! m_bSaveRegular && (m_dSimElapsed >= m_dUSaveTime[m_nThisSave]))) + { + m_bSaveGISThisIter = true; + + // Save the values from the RasterGrid array into raster GIS files + if (! bSaveAllRasterGISFiles()) + return (RTN_ERR_RASTER_FILE_WRITE); + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // Save the vector GIS files + if (! bSaveAllVectorGISFiles()) + return (RTN_ERR_VECTOR_FILE_WRITE); + + // Tell the user how the simulation is progressing + AnnounceProgress(); + } + + // Output per-timestep results to the .out file + if (! bWritePerTimestepResults()) + return (RTN_ERR_TEXT_FILE_WRITE); + + // Now output time series CSV stuff + if (! bWriteTSFiles()) + return (RTN_ERR_TIMESERIES_FILE_WRITE); + + // Tell the user how the simulation is progressing + AnnounceProgress(); + + // Update grand totals + DoEndOfTimestepTotals(); + + } // ================================================ End of main loop ====================================================== + + // =================================================== post-loop tidying ===================================================== + // Tell the user what is happening + AnnounceSimEnd(); + + // Write end-of-run information to Out, Log and time-series files + nRet = nWriteEndRunDetails(); + if (nRet != RTN_OK) + return (nRet); + + // Do end-of-run memory clearance + DoEndOfRunDeletes(); + return RTN_OK; +} diff --git a/src/simulation.h b/src/simulation.h index 9dcfa20a3..fd47e5de5 100644 --- a/src/simulation.h +++ b/src/simulation.h @@ -66,6 +66,7 @@ class CGeomCoastPolygon; class CRWCliff; class CSedInputEvent; class CRWCellLandform; +class CGeomCell; class CSimulation { @@ -1334,6 +1335,12 @@ class CSimulation //! The name of the flood loction events shape file string m_strFloodLocationShapefile; + //! The name of the sea flood fill seed points shapefile + string m_strSeaFloodSeedPointShapefile; + + //! Vector to store seed points read from shapefile (in grid coordinates) + vector m_VSeaFloodSeedPoint; + //! System start-simulation time time_t m_tSysStartTime; @@ -1599,6 +1606,7 @@ class CSimulation // Input and output routines int nHandleCommandLineParams(int, char const*[]); bool bReadIniFile(void); + bool bReadIniYamlFile(void); bool bReadRunDataFile(void); bool bReadYamlFile(void); bool bDetectFileFormat(string const& strFileName, bool& bIsYaml); @@ -1628,6 +1636,7 @@ class CSimulation int nReadRasterBasementDEM(void); int nReadRasterGISFile(int const, int const); int nReadVectorGISFile(int const); + int nReadSeaFloodSeedPointShapefile(void); bool bWriteRasterGISFile(int const, string const*, int const = 0, double const = 0); bool bWriteVectorGISFile(int const, string const*); void GetRasterOutputMinMax(int const, double&, double&, int const, double const); @@ -1695,7 +1704,7 @@ class CSimulation double dCalcBeachProtectionFactor(int const, int const, double const); void FillInBeachProtectionHoles(void); void FillPotentialPlatformErosionHoles(void); - void DoActualPlatformErosionOnCell(int const, int const); + void DoActualPlatformErosionOnCell(int const, int const, CGeomCell&); double dLookUpErosionPotential(double const); static CGeom2DPoint PtChooseEndPoint(int const, CGeom2DPoint const*, CGeom2DPoint const*, double const, double const, double const, double const); int nGetCoastNormalEndPoint(int const, int const, int const, CGeom2DPoint const*, double const, CGeom2DPoint *, CGeom2DIPoint *, bool const); @@ -1713,6 +1722,9 @@ class CSimulation void ModifyBreakingWavePropertiesWithinShadowZoneToCoastline(int const, int const); static double dCalcCurvature(int const, CGeom2DPoint const*, CGeom2DPoint const*, CGeom2DPoint const*); void CalcD50AndFillWaveCalcHoles(void); + void CalcD50(void); + void FillWaveCalcHoles(void); + void ProcessNeighborForWaveCalc(int const, int const, int&, int&, int&, int&, int&, int&, int&, double&, double&); int nDoAllShadowZones(void); static bool bOnOrOffShoreAndUpOrDownCoast(double const, double const, int const, bool&); static CGeom2DIPoint PtiFollowWaveAngle(CGeom2DIPoint const*, double const, double&); diff --git a/src/spatial_interpolation.cpp b/src/spatial_interpolation.cpp index f2b08e1a2..6faa779ba 100644 --- a/src/spatial_interpolation.cpp +++ b/src/spatial_interpolation.cpp @@ -176,7 +176,7 @@ void SpatialInterpolator::Interpolate(std::vector const& query_points, std::vector indices(k); std::vector sq_dists(k); - #pragma omp for schedule(guided, 128) nowait + #pragma omp for schedule(static) nowait for (size_t i = 0; i < query_points.size(); i++) { double const query_pt[2] = {query_points[i].x, query_points[i].y}; @@ -367,7 +367,7 @@ void DualSpatialInterpolator::Interpolate(std::vector const& query_poin std::vector indices(k); std::vector sq_dists(k); - #pragma omp for schedule(guided, 128) nowait + #pragma omp for schedule(static) nowait for (size_t i = 0; i < query_points.size(); i++) { InterpolatePoint(query_points[i].x, query_points[i].y, diff --git a/src/spatial_interpolation.h b/src/spatial_interpolation.h index 5bf352f70..51f4fc19e 100644 --- a/src/spatial_interpolation.h +++ b/src/spatial_interpolation.h @@ -91,6 +91,13 @@ class SpatialInterpolator int k_neighbors = 12, double power = 2.0); + // Constructor for sharing k-d tree between interpolators (now public) + SpatialInterpolator(PointCloud const& cloud, + KDTree* kdtree, + std::vector const& values, + int k_neighbors, + double power); + ~SpatialInterpolator(); // Interpolate at a single point @@ -101,7 +108,7 @@ class SpatialInterpolator std::vector& results) const; // Get the k-d tree (for sharing between interpolators) - KDTree const* GetKDTree() const { return m_kdtree; } + KDTree* GetKDTree() const { return m_kdtree; } // Get the point cloud (for sharing between interpolators) PointCloud const& GetPointCloud() const { return m_cloud; } @@ -116,13 +123,7 @@ class SpatialInterpolator static constexpr double EPSILON = 1e-10; - // Private constructor for sharing k-d tree friend class DualSpatialInterpolator; - SpatialInterpolator(PointCloud const& cloud, - KDTree* kdtree, - std::vector const& values, - int k_neighbors, - double power); }; // Optimized dual interpolator for X and Y values sharing same spatial points diff --git a/src/update_grid.cpp b/src/update_grid.cpp index b21d1516a..6cde7b3a8 100644 --- a/src/update_grid.cpp +++ b/src/update_grid.cpp @@ -20,7 +20,7 @@ #include #ifdef _OPENMP -#include + #include #endif #include "cme.h" @@ -33,6 +33,11 @@ //=============================================================================================================================== int CSimulation::nUpdateGrid(void) { + // No sea cells? + if (m_ulThisIterNumSeaCells == 0) + // All land, assume this is an error + return RTN_ERR_NOSEACELLS; + // Go through all cells in the raster grid and calculate some this-timestep totals m_dThisIterTopElevMax = -DBL_MAX; m_dThisIterTopElevMin = DBL_MAX; @@ -41,28 +46,31 @@ int CSimulation::nUpdateGrid(void) m_ulThisIterNumCoastCells = 0; m_dThisIterTotSeaDepth = 0; - // Use OpenMP parallel reduction for thread-safe accumulation and min/max calculations -#ifdef _OPENMP -#pragma omp parallel for collapse(2) \ - reduction(+ : m_ulThisIterNumCoastCells, m_dThisIterTotSeaDepth) \ - reduction(max : m_dThisIterTopElevMax) \ - reduction(min : m_dThisIterTopElevMin) -#endif + // Now go through all cells again and sort out suspended sediment load + double const dSuspPerSeaCell = m_dThisIterFineSedimentToSuspension / static_cast(m_ulThisIterNumSeaCells); + +// Use OpenMP parallel reduction for thread-safe accumulation and min/max calculations +#pragma omp parallel for collapse(2) schedule(static) \ + reduction(+ : m_ulThisIterNumCoastCells, m_dThisIterTotSeaDepth) \ + reduction(max : m_dThisIterTopElevMax) \ + reduction(min : m_dThisIterTopElevMin) for (int nX = 0; nX < m_nXGridSize; nX++) { for (int nY = 0; nY < m_nYGridSize; nY++) { - if (m_pRasterGrid->m_Cell[nX][nY].bIsCoastline()) + auto this_cell = m_pRasterGrid->Cell(nX, nY); + if (this_cell.bIsCoastline()) m_ulThisIterNumCoastCells++; - if (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) + if (this_cell.bIsInContiguousSea()) { // Is a sea cell - m_dThisIterTotSeaDepth += m_pRasterGrid->m_Cell[nX][nY].dGetSeaDepth(); + m_dThisIterTotSeaDepth += this_cell.dGetSeaDepth(); + this_cell.AddSuspendedSediment(dSuspPerSeaCell); } - double const dTopElev = m_pRasterGrid->m_Cell[nX][nY].dGetOverallTopElev(); + double const dTopElev = this_cell.dGetOverallTopElev(); // Get highest and lowest elevations of the top surface of the DEM if (dTopElev > m_dThisIterTopElevMax) @@ -73,28 +81,6 @@ int CSimulation::nUpdateGrid(void) } } - // No sea cells? - if (m_ulThisIterNumSeaCells == 0) - // All land, assume this is an error - return RTN_ERR_NOSEACELLS; - - // Now go through all cells again and sort out suspended sediment load - double const dSuspPerSeaCell = m_dThisIterFineSedimentToSuspension / static_cast(m_ulThisIterNumSeaCells); - - // Parallelize the sediment distribution loop -#ifdef _OPENMP -#pragma omp parallel for collapse(2) -#endif - - for (int nX = 0; nX < m_nXGridSize; nX++) - { - for (int nY = 0; nY < m_nYGridSize; nY++) - { - if (m_pRasterGrid->m_Cell[nX][nY].bIsInContiguousSea()) - m_pRasterGrid->m_Cell[nX][nY].AddSuspendedSediment(dSuspPerSeaCell); - } - } - // Go along each coastline and update the grid with landform attributes, ready for next timestep for (int i = 0; i < static_cast(m_VCoast.size()); i++) { diff --git a/src/utils.cpp b/src/utils.cpp index 05e9a34a1..c88b567cd 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,2995 +1,2999 @@ -/*! - \file utils.cpp - \brief Utility routines - \details TODO 001 A more detailed description of this routine. - \author David Favis-Mortlock - \author Andres Payo - \date 2025 - \copyright GNU General Public License -*/ - -/* ============================================================================================================================== - This file is part of CoastalME, the Coastal Modelling Environment. - - CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -==============================================================================================================================*/ -#include - -#ifdef _WIN32 -#include // Needed for CalcProcessStats() -#include -#include // For isatty() -#elif defined __GNUG__ -#include // Needed for CalcProcessStats() -#include // For isatty() -#include -#endif - -#ifdef _OPENMP -#include -#endif - -#include - -#include - -#include -using std::tolower; - -#include -using std::floor; - -#include -using std::clock; -using std::clock_t; -using std::difftime; -using std::localtime; -using std::time; - -#include -using std::fixed; - -#include -using std::cerr; -using std::cout; -using std::endl; -using std::ios; - -#include -using std::put_time; -using std::setprecision; -using std::setw; - -#include -using std::to_string; - -#include -using std::stringstream; - -#include -using std::transform; - -#include - -#include "cme.h" -#include "simulation.h" -#include "coast.h" -#include "2di_point.h" - -//=============================================================================================================================== -//! Handles command-line parameters -//=============================================================================================================================== -int CSimulation::nHandleCommandLineParams(int nArg, char const* pcArgv[]) -{ - if ((!isatty(fileno(stdout))) || (!isatty(fileno(stderr)))) - // Running with stdout or stderr not a tty, so either redirected or running as a background job. Ignore all command line parameters - return RTN_OK; - - // Process the parameters following the name of the executable - for (int i = 1; i < nArg; i++) - { - string strArg = pcArgv[i]; - strArg = strTrim(&strArg); - -#ifdef _WIN32 - // Swap any forward slashes to backslashes - strArg = pstrChangeToBackslash(&strArg); -#endif - - if (strArg.find("--gdal") != string::npos) - { - // User wants to know what GDAL raster drivers are available - cout << GDAL_DRIVERS << endl - << endl; - - for (int j = 0; j < GDALGetDriverCount(); j++) - { - GDALDriverH hDriver = GDALGetDriver(j); - - string strTmp(GDALGetDriverShortName(hDriver)); - strTmp.append(" "); - strTmp.append(GDALGetDriverLongName(hDriver)); - - cout << strTmp << endl; - } - - return (RTN_HELP_ONLY); - } - - else - { - if (strArg.find("--about") != string::npos) - { - // User wants information about CoastalME - cout << ABOUT << endl; - cout << THANKS << endl; - - return (RTN_HELP_ONLY); - } - - else - { - if (strArg.find("--yaml") != string::npos) - { - // User wants to use YAML format for input datafile - m_bYamlInputFormat = true; - } - - else if (strArg.find("--home") != string::npos) - { - // Read in user defined runtime directory - // string strTmp; - - // Find the position of '=' - size_t const pos = strArg.find('='); - - // Was '=' found? - if (pos != string::npos) - { - // Yes, so get the substring after '=' and assign it to the global variable - m_strCMEIni = strArg.substr(pos + 1); - } - - else - { - // No - cout << "No '=' found in the input string" << endl; - } - - return (RTN_OK); - } - - // TODO 049 Handle other command line parameters e.g. path to .ini file, path to datafile - else - { - // Display usage information - cout << USAGE << endl; - cout << USAGE1 << endl; - cout << USAGE2 << endl; - cout << USAGE3 << endl; - cout << USAGE4 << endl; - cout << USAGE5 << endl; - cout << USAGE6 << endl; - - return (RTN_HELP_ONLY); - } - } - } - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! Tells the user that we have started the simulation -//=============================================================================================================================== -void CSimulation::AnnounceStart(void) -{ - cout << endl - << PROGRAM_NAME << " for " << PLATFORM << " " << strGetBuild() << endl; - #ifdef _OPENMP - cout << "OpenMP is ENABLED" << endl; - cout << "Max threads available: " << omp_get_max_threads() << endl; - #pragma omp parallel - { - #pragma omp single - std::cout << "Actually running with " << omp_get_num_threads() << " threads" << std::endl; - } - #else - std::cout << "OpenMP is NOT ENABLED - code will run serially!" << std::endl; - #endif // !_OPENMP -} - -//=============================================================================================================================== -//! Starts the clock ticking -//=============================================================================================================================== -void CSimulation::StartClock(void) -{ - // First start the 'CPU time' clock ticking - if (static_cast(-1) == clock()) - { - // There's a problem with the clock, but continue anyway - LogStream << NOTE << "CPU time not available" << endl; - m_dCPUClock = -1; - } - - else - { - // All OK, so get the time in m_dClkLast (this is needed to check for clock rollover on long runs) - m_dClkLast = static_cast(clock()); - m_dClkLast -= CLOCK_T_MIN; // necessary if clock_t is signed to make m_dClkLast unsigned - } - - // And now get the actual time we started - m_tSysStartTime = time(nullptr); -} - -//=============================================================================================================================== -//! Finds the folder (directory) in which the CoastalME executable is located -//=============================================================================================================================== -bool CSimulation::bFindExeDir(char const* pcArg) -{ - string strTmp; - char szBuf[BUF_SIZE] = ""; - -#ifdef _WIN32 - - if (0 != GetModuleFileName(NULL, szBuf, BUF_SIZE)) - strTmp = szBuf; - - else - // It failed, so try another approach - strTmp = pcArg; - -#else - // char* pResult = getcwd(szBuf, BUF_SIZE); // Used to use this, but what if cwd is not the same as the CoastalME dir? - - if (-1 != readlink("/proc/self/exe", szBuf, BUF_SIZE)) - strTmp = szBuf; - - else - // It failed, so try another approach - strTmp = pcArg; - -#endif - - // Neither approach has worked, so give up - if (strTmp.empty()) - return false; - - // It's OK, so trim off the executable's name - int const nPos = static_cast(strTmp.find_last_of(PATH_SEPARATOR)); - m_strCMEDir = strTmp.substr(0, nPos + 1); // Note that this must be terminated with a backslash - - return true; -} -//=============================================================================================================================== -//! Tells the user about the licence -//=============================================================================================================================== -void CSimulation::AnnounceLicence(void) -{ - cout << COPYRIGHT << endl - << endl; - cout << LINE << endl; - cout << DISCLAIMER1 << endl; - cout << DISCLAIMER2 << endl; - cout << DISCLAIMER3 << endl; - cout << DISCLAIMER4 << endl; - cout << DISCLAIMER5 << endl; - cout << DISCLAIMER6 << endl; - cout << LINE << endl - << endl; - - cout << START_NOTICE << strGetComputerName() << " at " << put_time(localtime(&m_tSysStartTime), "%T on %A %d %B %Y") << endl; - cout << INITIALIZING_NOTICE << endl; -} - -//=============================================================================================================================== -//! Given a string containing time units, this returns the appropriate multiplier -//=============================================================================================================================== -double CSimulation::dGetTimeMultiplier(string const* strIn) -{ - // First decide what the time units are - int const nTimeUnits = nDoTimeUnits(strIn); - - // Then return the correct multiplier, since m_dTimeStep is in hours - switch (nTimeUnits) - { - case TIME_UNKNOWN: - return TIME_UNKNOWN; - break; - - case TIME_HOURS: - return 1; // Multiplier for hours - break; - - case TIME_DAYS: - return 24; // Multiplier for days -> hours - break; - - case TIME_MONTHS: - return 24 * 30.416667; // Multiplier for months -> hours (assume 30 + 5/12 day months, no leap years) - break; - - case TIME_YEARS: - return 24 * 365.25; // Multiplier for years -> hours - break; - } - - return 0; -} - -//=============================================================================================================================== -//! Given a string containing time units, this sets up the appropriate multiplier and display units for the simulation -//=============================================================================================================================== -int CSimulation::nDoSimulationTimeMultiplier(string const* strIn) -{ - // First decide what the time units are - int const nTimeUnits = nDoTimeUnits(strIn); - - // Next set up the correct multiplier, since m_dTimeStep is in hours - switch (nTimeUnits) - { - case TIME_UNKNOWN: - return RTN_ERR_TIME_UNITS; - break; - - case TIME_HOURS: - m_dDurationUnitsMult = 1; // Multiplier for hours - m_strDurationUnits = "hours"; - break; - - case TIME_DAYS: - m_dDurationUnitsMult = 24; // Multiplier for days -> hours - m_strDurationUnits = "days"; - break; - - case TIME_MONTHS: - m_dDurationUnitsMult = 24 * 30.416667; // Multiplier for months -> hours (assume 30 + 5/12 day months, no leap years) - m_strDurationUnits = "months"; - break; - - case TIME_YEARS: - m_dDurationUnitsMult = 24 * 365.25; // Multiplier for years -> hours - m_strDurationUnits = "years"; - break; - } - - return RTN_OK; -} - -//=============================================================================================================================== -//! This finds time units in a string -//=============================================================================================================================== -int CSimulation::nDoTimeUnits(string const* strIn) -{ - if (strIn->find("hour") != string::npos) - return TIME_HOURS; - - else if (strIn->find("day") != string::npos) - return TIME_DAYS; - - else if (strIn->find("month") != string::npos) - return TIME_MONTHS; - - else if (strIn->find("year") != string::npos) - return TIME_YEARS; - - else - return TIME_UNKNOWN; -} - -//=============================================================================================================================== -//! Opens the log file -//=============================================================================================================================== -bool CSimulation::bOpenLogFile(void) -{ - if (m_nLogFileDetail == 0) - { - LogStream.open("/dev/null", ios::out | ios::trunc); - cout << "Warning: log file is not writting" << endl; - } - - else - LogStream.open(m_strLogFile.c_str(), ios::out | ios::trunc); - - if (!LogStream) - { - // Error, cannot open log file - cerr << ERR << "cannot open " << m_strLogFile << " for output" << endl; - return false; - } - - return true; -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the DEM file -//=============================================================================================================================== -void CSimulation::AnnounceReadBasementDEM(void) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_BASEMENT << pstrChangeToForwardSlash(&m_strInitialBasementDEMFile) << endl; -#else - cout << READING_BASEMENT << m_strInitialBasementDEMFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now allocating memory -//=============================================================================================================================== -void CSimulation::AnnounceAllocateMemory(void) -{ - cout << ALLOCATE_MEMORY << endl; -} - -//=============================================================================================================================== -//! Tells the user that we are now adding layers -//=============================================================================================================================== -void CSimulation::AnnounceAddLayers(void) -{ - // Tell the user what is happening - cout << ADD_LAYERS << endl; -} - -//=============================================================================================================================== -//! Now reading raster GIS files -//=============================================================================================================================== -void CSimulation::AnnounceReadRasterFiles(void) -{ - cout << READING_RASTER_FILES << endl; -} - -//=============================================================================================================================== -//! Now reading vector GIS files -//=============================================================================================================================== -void CSimulation::AnnounceReadVectorFiles(void) -{ - cout << READING_VECTOR_FILES << endl; -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the Landscape category GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadLGIS(void) const -{ - // Tell the user what is happening - if (! m_strInitialLandformFile.empty()) -#ifdef _WIN32 - cout << READING_LANDFORM_FILE << pstrChangeToForwardSlash(&m_strInitialLandformFile) << endl; - -#else - cout << READING_LANDFORM_FILE << m_strInitialLandformFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the Intervention class GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadICGIS(void) const -{ - // Tell the user what is happening - if (! m_strInterventionClassFile.empty()) -#ifdef _WIN32 - cout << READING_INTERVENTION_CLASS_FILE << pstrChangeToForwardSlash(&m_strInterventionClassFile) << endl; - -#else - cout << READING_INTERVENTION_CLASS_FILE << m_strInterventionClassFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the Intervention height GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadIHGIS(void) const -{ - // Tell the user what is happening - if (! m_strInterventionHeightFile.empty()) -#ifdef _WIN32 - cout << READING_INTERVENTION_HEIGHT_FILE << pstrChangeToForwardSlash(&m_strInterventionHeightFile) << endl; - -#else - cout << READING_INTERVENTION_HEIGHT_FILE << m_strInterventionHeightFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the deep water wave values GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadDeepWaterWaveValuesGIS(void) const -{ - // Tell the user what is happening - if (! m_strDeepWaterWavesInputFile.empty()) -#ifdef _WIN32 - cout << READING_DEEP_WATER_WAVE_FILE << pstrChangeToForwardSlash(&m_strDeepWaterWavesInputFile) << endl; - -#else - cout << READING_DEEP_WATER_WAVE_FILE << m_strDeepWaterWavesInputFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the sediment input events GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadSedimentEventInputValuesGIS(void) const -{ - // Tell the user what is happening - if (! m_strSedimentInputEventFile.empty()) -#ifdef _WIN32 - cout << READING_SED_INPUT_EVENT_FILE << pstrChangeToForwardSlash(&m_strSedimentInputEventFile) << endl; - -#else - cout << READING_SED_INPUT_EVENT_FILE << m_strSedimentInputEventFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the flood location GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadFloodLocationGIS(void) const -{ - // Tell the user what is happening - if (! m_strFloodLocationShapefile.empty()) -#ifdef _WIN32 - cout << READING_FLOOD_LOCATION << pstrChangeToForwardSlash(&m_strFloodLocationShapefile) << endl; - -#else - cout << READING_FLOOD_LOCATION << m_strFloodLocationShapefile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial suspended sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialSuspSedGIS(void) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_SUSPENDED_SEDIMENT_FILE << pstrChangeToForwardSlash(&m_strInitialSuspSedimentFile) << endl; -#else - cout << READING_SUSPENDED_SEDIMENT_FILE << m_strInitialSuspSedimentFile << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial fine unconsolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialFineUnconsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_UNCONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialFineUnconsSedimentFile[nLayer]) << endl; -#else - cout << READING_UNCONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialFineUnconsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial sand unconsolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialSandUnconsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_UNCONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialSandUnconsSedimentFile[nLayer]) << endl; -#else - cout << READING_UNCONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialSandUnconsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial coarse unconsolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialCoarseUnconsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_UNCONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialCoarseUnconsSedimentFile[nLayer]) << endl; -#else - cout << READING_UNCONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialCoarseUnconsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial fine consolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialFineConsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_CONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialFineConsSedimentFile[nLayer]) << endl; -#else - cout << READING_CONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialFineConsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial sand consolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialSandConsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_CONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialSandConsSedimentFile[nLayer]) << endl; -#else - cout << READING_CONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialSandConsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Tells the user that we are now reading the initial coarse consolidated sediment depth GIS file -//=============================================================================================================================== -void CSimulation::AnnounceReadInitialCoarseConsSedGIS(int const nLayer) const -{ - // Tell the user what is happening -#ifdef _WIN32 - cout << READING_CONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialCoarseConsSedimentFile[nLayer]) << endl; -#else - cout << READING_CONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialCoarseConsSedimentFile[nLayer] << endl; -#endif -} - -//=============================================================================================================================== -//! Now reading tide data file -//=============================================================================================================================== -void CSimulation::AnnounceReadTideData(void) const -{ -#ifdef _WIN32 - cout << READING_TIDE_DATA_FILE << pstrChangeToForwardSlash(&m_strTideDataFile) << endl; -#else - cout << READING_TIDE_DATA_FILE << m_strTideDataFile << endl; -#endif -} - -//=============================================================================================================================== -//! Now reading the SCAPE shape function file -//=============================================================================================================================== -void CSimulation::AnnounceReadSCAPEShapeFunctionFile(void) -{ - cout << READING_SCAPE_SHAPE_FUNCTION_FILE << endl; -} - -//=============================================================================================================================== -//! Tells the user that we are now initializing -//=============================================================================================================================== -void CSimulation::AnnounceFinalInitialization(void) -{ - // Tell the user what is happening - cout << INITIALIZING_FINAL << endl; -} - -//=============================================================================================================================== -//! Tell the user that the simulation is now running -//=============================================================================================================================== -void CSimulation::AnnounceIsRunning(void) -{ - cout << RUN_NOTICE << endl; -} - -//=============================================================================================================================== -//! Return a space-separated string containing the names of the raster GIS output files -//=============================================================================================================================== -string CSimulation::strListRasterFiles(void) const -{ - string strTmp; - - if (m_bBasementElevSave) - { - strTmp.append(RASTER_BASEMENT_ELEVATION_CODE); - strTmp.append(", "); - } - - if (m_bSedimentTopSurfSave) - { - strTmp.append(RASTER_SEDIMENT_TOP_CODE); - strTmp.append(", "); - } - - if (m_bTopSurfSave) - { - strTmp.append(RASTER_TOP_CODE); - strTmp.append(", "); - } - - if (m_bSeaDepthSave) - { - strTmp.append(RASTER_SEA_DEPTH_NAME); - strTmp.append(", "); - } - - if (m_bAvgSeaDepthSave) - { - strTmp.append(RASTER_AVG_SEA_DEPTH_CODE); - strTmp.append(", "); - } - - if (m_bSeaMaskSave) - { - strTmp.append(RASTER_INUNDATION_MASK_CODE); - strTmp.append(", "); - } - - if (m_bWaveHeightSave) - { - strTmp.append(RASTER_WAVE_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bWaveAngleSave) - { - strTmp.append(RASTER_WAVE_ORIENTATION_CODE); - strTmp.append(", "); - } - - if (m_bAvgWaveHeightSave) - { - strTmp.append(RASTER_AVG_WAVE_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bBeachProtectionSave) - { - strTmp.append(RASTER_BEACH_PROTECTION_CODE); - strTmp.append(", "); - } - - if (m_bPotentialPlatformErosionSave) - { - strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bPotentialPlatformErosionMaskSave) - { - strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_CODE); - strTmp.append(", "); - } - - if (m_bBeachDepositionSave) - { - strTmp.append(RASTER_BEACH_DEPOSITION_CODE); - strTmp.append(", "); - } - - if (m_bTotalBeachDepositionSave) - { - strTmp.append(RASTER_TOTAL_BEACH_DEPOSITION_CODE); - strTmp.append(", "); - } - - if (m_bBeachMaskSave) - { - strTmp.append(RASTER_BEACH_MASK_CODE); - strTmp.append(", "); - } - - if (m_bActualPlatformErosionSave) - { - strTmp.append(RASTER_ACTUAL_PLATFORM_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bTotalPotentialPlatformErosionSave) - { - strTmp.append(RASTER_TOTAL_POTENTIAL_PLATFORM_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bTotalActualPlatformErosionSave) - { - strTmp.append(RASTER_TOTAL_ACTUAL_PLATFORM_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bPotentialBeachErosionSave) - { - strTmp.append(RASTER_POTENTIAL_BEACH_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bActualBeachErosionSave) - { - strTmp.append(RASTER_ACTUAL_BEACH_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bTotalPotentialBeachErosionSave) - { - strTmp.append(RASTER_TOTAL_POTENTIAL_BEACH_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bTotalActualBeachErosionSave) - { - strTmp.append(RASTER_TOTAL_ACTUAL_BEACH_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bLandformSave) - { - strTmp.append(RASTER_LANDFORM_CODE); - strTmp.append(", "); - } - - if (m_bSlopeConsSedSave) - { - strTmp.append(RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_CODE); - strTmp.append(", "); - } - - if (m_bInterventionClassSave) - { - strTmp.append(RASTER_INTERVENTION_CLASS_CODE); - strTmp.append(", "); - } - - if (m_bInterventionHeightSave) - { - strTmp.append(RASTER_INTERVENTION_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bHaveFineSediment && m_bSuspSedSave) - { - strTmp.append(RASTER_SUSP_SED_CODE); - strTmp.append(", "); - } - - if (m_bHaveFineSediment && m_bAvgSuspSedSave) - { - strTmp.append(RASTER_AVG_SUSP_SED_CODE); - strTmp.append(", "); - } - - if (m_bHaveFineSediment && m_bFineUnconsSedSave) - { - strTmp.append(RASTER_FINE_UNCONS_CODE); - strTmp.append(", "); - } - - if (m_bHaveSandSediment && m_bSandUnconsSedSave) - { - strTmp.append(RASTER_SAND_UNCONS_CODE); - strTmp.append(", "); - } - - if (m_bHaveCoarseSediment && m_bCoarseUnconsSedSave) - { - strTmp.append(RASTER_COARSE_UNCONS_CODE); - strTmp.append(", "); - } - - if (m_bHaveFineSediment && m_bFineConsSedSave) - { - strTmp.append(RASTER_FINE_CONS_CODE); - strTmp.append(", "); - } - - if (m_bHaveSandSediment && m_bSandConsSedSave) - { - strTmp.append(RASTER_SAND_CONS_CODE); - strTmp.append(", "); - } - - if (m_bHaveCoarseSediment && m_bCoarseConsSedSave) - { - strTmp.append(RASTER_COARSE_CONS_CODE); - strTmp.append(", "); - } - - if (m_bRasterCoastlineSave) - { - strTmp.append(RASTER_COAST_CODE); - strTmp.append(", "); - } - - if (m_bRasterNormalProfileSave) - { - strTmp.append(RASTER_COAST_NORMAL_CODE); - strTmp.append(", "); - } - - if (m_bActiveZoneSave) - { - strTmp.append(RASTER_ACTIVE_ZONE_CODE); - strTmp.append(", "); - } - - if (m_bRasterPolygonSave) - { - strTmp.append(RASTER_POLYGON_CODE); - strTmp.append(", "); - } - - if (m_bPotentialPlatformErosionMaskSave) - { - strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_CODE); - strTmp.append(", "); - } - - if (m_bSedimentInputEventSave) - { - strTmp.append(RASTER_SEDIMENT_INPUT_EVENT_CODE); - strTmp.append(", "); - } - - // Remove the trailing comma and space - if (strTmp.size() > 2) - strTmp.resize(strTmp.size() - 2); - - return strTmp; -} - -//=============================================================================================================================== -//! Return a space-separated string containing the names of the vector GIS output files -//=============================================================================================================================== -string CSimulation::strListVectorFiles(void) const -{ - string strTmp; - - if (m_bCoastSave) - { - strTmp.append(VECTOR_COAST_CODE); - strTmp.append(", "); - } - - if (m_bNormalsSave) - { - strTmp.append(VECTOR_NORMALS_CODE); - strTmp.append(", "); - } - - if (m_bInvalidNormalsSave) - { - strTmp.append(VECTOR_INVALID_NORMALS_CODE); - strTmp.append(", "); - } - - if (m_bWaveAngleAndHeightSave) - { - strTmp.append(VECTOR_WAVE_ANGLE_AND_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bAvgWaveAngleAndHeightSave) - { - strTmp.append(VECTOR_AVG_WAVE_ANGLE_AND_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bCoastCurvatureSave) - { - strTmp.append(VECTOR_COAST_CURVATURE_CODE); - strTmp.append(", "); - } - - if (m_bWaveEnergySinceCollapseSave) - { - strTmp.append(VECTOR_WAVE_ENERGY_SINCE_COLLAPSE_CODE); - strTmp.append(", "); - } - - if (m_bMeanWaveEnergySave) - { - strTmp.append(VECTOR_MEAN_WAVE_ENERGY_CODE); - strTmp.append(", "); - } - - if (m_bBreakingWaveHeightSave) - { - strTmp.append(VECTOR_BREAKING_WAVE_HEIGHT_CODE); - strTmp.append(", "); - } - - if (m_bPolygonNodeSave) - { - strTmp.append(VECTOR_POLYGON_NODE_CODE); - strTmp.append(", "); - } - - if (m_bPolygonBoundarySave) - { - strTmp.append(VECTOR_POLYGON_BOUNDARY_CODE); - strTmp.append(", "); - } - - if (m_bCliffNotchSave) - { - strTmp.append(VECTOR_CLIFF_NOTCH_ACTIVE_CODE); - strTmp.append(", "); - } - - if (m_bShadowBoundarySave) - { - strTmp.append(VECTOR_SHADOW_ZONE_BOUNDARY_CODE); - strTmp.append(", "); - } - - if (m_bShadowDowndriftBoundarySave) - { - strTmp.append(VECTOR_DOWNDRIFT_ZONE_BOUNDARY_CODE); - strTmp.append(", "); - } - - if (m_bDeepWaterWaveAngleAndHeightSave) - { - strTmp.append(VECTOR_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT_CODE); - strTmp.append(", "); - } - - // Remove the trailing comma and space - if (strTmp.size() > 2) - strTmp.resize(strTmp.size() - 2); - - return strTmp; -} - -//=============================================================================================================================== -//! Return a space-separated string containing the names of the time series output files -//=============================================================================================================================== -string CSimulation::strListTSFiles(void) const -{ - string strTmp; - - if (m_bSeaAreaTSSave) - { - strTmp.append(TIME_SERIES_SEA_AREA_CODE); - strTmp.append(", "); - } - - if (m_bSWLTSSave) - { - strTmp.append(TIME_SERIES_SWL_CODE); - strTmp.append(", "); - } - - if (m_bActualPlatformErosionTSSave) - { - strTmp.append(TIME_SERIES_PLATFORM_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bCliffCollapseErosionTSSave) - { - strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bCliffCollapseDepositionTSSave) - { - strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_DEPOSITION_CODE); - strTmp.append(", "); - } - - if (m_bCliffCollapseNetTSSave) - { - strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_NET_CODE); - strTmp.append(", "); - } - - if (m_bBeachErosionTSSave) - { - strTmp.append(TIME_SERIES_BEACH_EROSION_CODE); - strTmp.append(", "); - } - - if (m_bBeachDepositionTSSave) - { - strTmp.append(TIME_SERIES_BEACH_DEPOSITION_CODE); - strTmp.append(", "); - } - - if (m_bBeachSedimentChangeNetTSSave) - { - strTmp.append(TIME_SERIES_BEACH_CHANGE_NET_CODE); - strTmp.append(", "); - } - - if (m_bSuspSedTSSave) - { - strTmp.append(TIME_SERIES_SUSPENDED_SEDIMENT_CODE); - strTmp.append(", "); - } - - if (m_bFloodSetupSurgeTSSave) - { - strTmp.append(TIME_SERIES_FLOOD_SETUP_SURGE_CODE); - strTmp.append(", "); - } - - if (m_bFloodSetupSurgeRunupTSSave) - { - strTmp.append(TIME_SERIES_FLOOD_SETUP_SURGE_RUNUP_CODE); - strTmp.append(", "); - } - - if (m_bCliffNotchElevTSSave) - { - strTmp.append(TIME_SERIES_CLIFF_NOTCH_ELEV_CODE); - strTmp.append(", "); - } - - // Remove the trailing comma and space - if (strTmp.size() > 2) - strTmp.resize(strTmp.size() - 2); - - return strTmp; -} - -//=============================================================================================================================== -//! This member function intialises the time series files -//=============================================================================================================================== -bool CSimulation::bSetUpTSFiles(void) -{ - string strTSFile; - - if (m_bSeaAreaTSSave) - { - // Start with wetted area - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_SEA_AREA_NAME); - strTSFile.append(CSVEXT); - - // Open sea area time-series CSV file - SeaAreaTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! SeaAreaTSStream) - { - // Error, cannot open wetted area time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bSWLTSSave) - { - // Now SWL - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_SWL_NAME); - strTSFile.append(CSVEXT); - - // Open SWL time-series CSV file - SWLTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! SWLTSStream) - { - // Error, cannot open SWL time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bActualPlatformErosionTSSave) - { - // Erosion (fine, sand, coarse) - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_PLATFORM_EROSION_NAME); - strTSFile.append(CSVEXT); - - // Open erosion time-series CSV file - PlatformErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! PlatformErosionTSStream) - { - // Error, cannot open erosion time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bCliffCollapseErosionTSSave) - { - // Erosion due to cliff collapse - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_EROSION_NAME); - strTSFile.append(CSVEXT); - - // Open cliff collapse erosion time-series CSV file - CliffCollapseErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! CliffCollapseErosionTSStream) - { - // Error, cannot open cliff collapse erosion time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bCliffCollapseDepositionTSSave) - { - // Deposition due to cliff collapse - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_DEPOSITION_NAME); - strTSFile.append(CSVEXT); - - // Open cliff collapse deposition time-series CSV file - CliffCollapseDepositionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! CliffCollapseDepositionTSStream) - { - // Error, cannot open cliff collapse deposition time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bCliffCollapseNetTSSave) - { - // Net change in unconsolidated sediment due to cliff collapse - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_NET_NAME); - strTSFile.append(CSVEXT); - - // Open net cliff collapse time-series CSV file - CliffCollapseNetChangeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! CliffCollapseNetChangeTSStream) - { - // Error, cannot open net cliff collapse time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bBeachErosionTSSave) - { - // Beach erosion - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_BEACH_EROSION_NAME); - strTSFile.append(CSVEXT); - - // Open beach erosion time-series CSV file - BeachErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! BeachErosionTSStream) - { - // Error, cannot open beach erosion time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bBeachDepositionTSSave) - { - // Beach deposition - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_BEACH_DEPOSITION_NAME); - strTSFile.append(CSVEXT); - - // Open beach deposition time-series CSV file - BeachDepositionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! BeachDepositionTSStream) - { - // Error, cannot open beach deposition time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bBeachSedimentChangeNetTSSave) - { - // Beach sediment change - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_BEACH_CHANGE_NET_NAME); - strTSFile.append(CSVEXT); - - // Open net beach sediment change time-series CSV file - BeachSedimentNetChangeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! BeachSedimentNetChangeTSStream) - { - // Error, cannot open beach sediment change time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bSuspSedTSSave) - { - // Sediment load - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_SUSPENDED_SEDIMENT_NAME); - strTSFile.append(CSVEXT); - - // Open sediment load time-series CSV file - FineSedSuspensionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! FineSedSuspensionTSStream) - { - // Error, cannot open sediment load time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bFloodSetupSurgeTSSave) - { - // Sediment load - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_FLOOD_SETUP_SURGE_CODE); - strTSFile.append(CSVEXT); - - // Open sediment load time-series CSV file - FloodSetupSurgeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! FloodSetupSurgeTSStream) - { - // Error, cannot open sediment load time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bFloodSetupSurgeRunupTSSave) - { - // Sediment load - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_FLOOD_SETUP_SURGE_RUNUP_CODE); - strTSFile.append(CSVEXT); - - // Open sediment load time-series CSV file - FloodSetupSurgeRunupTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! FloodSetupSurgeRunupTSStream) - { - // Error, cannot open sediment load time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - if (m_bCliffNotchElevTSSave) - { - // Elevation of cliff notch - strTSFile = m_strOutPath; - strTSFile.append(TIME_SERIES_CLIFF_NOTCH_ELEV_NAME); - strTSFile.append(CSVEXT); - - // Open cliff notch elevation time-series CSV file - CliffNotchElevTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); - if (! CliffNotchElevTSStream) - { - // Error, cannot open cliff notch elevation time-series file - cerr << ERR << "cannot open " << strTSFile << " for output" << endl; - return false; - } - } - - return true; -} - -//=============================================================================================================================== -//! Checks to see if the simulation has gone on too long, amongst other things -//=============================================================================================================================== -bool CSimulation::bTimeToQuit(void) -{ - // Add timestep to the total time simulated so far - m_dSimElapsed += m_dTimeStep; - - if (m_dSimElapsed >= m_dSimDuration) - { - // It is time to quit - m_dSimElapsed = m_dSimDuration; - AnnounceProgress(); - return true; - } - - // Not quitting, so increment the timestep count, and recalc total timesteps - m_ulIter++; - m_ulTotTimestep = static_cast(dRound(m_dSimDuration / m_dTimeStep)); - - // Check to see if we have done CLOCK_CHECK_ITERATION timesteps: if so, it is time to reset the CPU time running total in case the clock() function later rolls over - if (0 == m_ulIter % CLOCK_CHECK_ITERATION) - DoCPUClockReset(); - - // Not yet time to quit - return false; -} - -//=============================================================================================================================== -//! Returns a string, hopefully giving the name of the computer on which the simulation is running -//=============================================================================================================================== -string CSimulation::strGetComputerName(void) -{ - string strComputerName; - -#ifdef _WIN32 - // Being compiled to run under Windows, either by MS VC++, Borland C++, or Cygwin - strComputerName = getenv("COMPUTERNAME"); -#else - // Being compiled for another platform; assume for Linux-Unix - char szHostName[BUF_SIZE] = ""; - gethostname(szHostName, BUF_SIZE); - - strComputerName = szHostName; - - if (strComputerName.empty()) - strComputerName = "Unknown Computer"; - -#endif - - return strComputerName; -} - -//=============================================================================================================================== -//! Resets the CPU clock timer to prevent it 'rolling over', as can happen during long runs. This is a particularly problem under Unix systems where the value returned by clock() is defined in microseconds (for compatibility with systems that have CPU clocks with much higher resolution) i.e. CLOCKS_PER_SEC is 1000000 rather than the more usual 1000. In this case, the value returned from clock() will wrap around after accumulating only 2147 seconds of CPU time (about 36 minutes). -//=============================================================================================================================== -void CSimulation::DoCPUClockReset(void) -{ - if (static_cast(-1) == clock()) - { - // Error - LogStream << "CPU time not available" << endl; - m_dCPUClock = -1; - return; - } - - // OK, so carry on - double dClkThis = static_cast(clock()); - dClkThis -= CLOCK_T_MIN; // necessary when clock_t is signed, to make dClkThis unsigned - - if (dClkThis < m_dClkLast) - { - // Clock has 'rolled over' - m_dCPUClock += (CLOCK_T_RANGE + 1 - m_dClkLast); // this elapsed before rollover - m_dCPUClock += dClkThis; // this elapsed after rollover - -#ifdef CLOCKCHECK - // For debug purposes - LogStream << "Rolled over: dClkThis=" << dClkThis << " m_dClkLast=" << m_dClkLast << endl - << "\t" - << " before rollover=" << (CLOCK_T_RANGE + 1 - m_dClkLast) << endl - << "\t" - << " after rollover=" << dClkThis << endl - << "\t" - << " ADDED=" << (CLOCK_T_RANGE + 1 - m_dClkLast + dClkThis) << endl; -#endif - } - - else - { - // No rollover - m_dCPUClock += (dClkThis - m_dClkLast); - -#ifdef CLOCKCHECK - // For debug purposes - LogStream << "No rollover: dClkThis=" << dClkThis << " m_dClkLast=" << m_dClkLast << " ADDED=" << dClkThis - m_dClkLast << endl; -#endif - } - - // Reset for next time - m_dClkLast = dClkThis; -} - -//=============================================================================================================================== -//! Announce the end of the simulation -//=============================================================================================================================== -void CSimulation::AnnounceSimEnd(void) -{ - cout << endl << FINAL_OUTPUT << endl; -} - -// //=============================================================================================================================== -// //! Calculates and displays time elapsed in terms of CPU time and real time, also calculates time per timestep in terms of both CPU time and real time -// //=============================================================================================================================== -// void CSimulation::CalcTime(double const dRunLength) -// { -// // Reset CPU count for last time -// DoCPUClockReset(); -// -// if (! bFPIsEqual(m_dCPUClock, -1.0, TOLERANCE)) -// { -// // Calculate CPU time in secs -// double const dDuration = m_dCPUClock / CLOCKS_PER_SEC; -// -// // And write CPU time out to OutStream and LogStream -// OutStream << "CPU time elapsed: " << strDispTime(dDuration, false, true); -// LogStream << "CPU time elapsed: " << strDispTime(dDuration, false, true); -// -// // Calculate CPU time per timestep -// double const dPerTimestep = dDuration / static_cast(m_ulTotTimestep); -// -// // And write CPU time per timestep to OutStream and LogStream -// OutStream << fixed << setprecision(4) << " (" << dPerTimestep << " per timestep)" << endl; -// LogStream << fixed << setprecision(4) << " (" << dPerTimestep << " per timestep)" << endl; -// -// // Calculate ratio of CPU time to time simulated -// OutStream << resetiosflags(ios::floatfield); -// OutStream << fixed << setprecision(0) << "In terms of CPU time, this is "; -// LogStream << resetiosflags(ios::floatfield); -// LogStream << fixed << setprecision(0) << "In terms of CPU time, this is "; -// -// if (dDuration > dRunLength) -// { -// OutStream << dDuration / dRunLength << " x slower than reality" << endl; -// LogStream << dDuration / dRunLength << " x slower than reality" << endl; -// } -// -// else -// { -// OutStream << dRunLength / dDuration << " x faster than reality" << endl; -// LogStream << dRunLength / dDuration << " x faster than reality" << endl; -// } -// } -// -// // Calculate run time -// double const dDuration = difftime(m_tSysEndTime, m_tSysStartTime); -// -// // And write run time out to OutStream and LogStream -// OutStream << "Run time elapsed: " << strDispTime(dDuration, false, false); -// LogStream << "Run time elapsed: " << strDispTime(dDuration, false, false); -// -// // Calculate run time per timestep -// double const dPerTimestep = dDuration / static_cast(m_ulTotTimestep); -// -// // And write run time per timestep to OutStream and LogStream -// OutStream << resetiosflags(ios::floatfield); -// OutStream << " (" << fixed << setprecision(4) << dPerTimestep << " per timestep)" << endl; -// LogStream << resetiosflags(ios::floatfield); -// LogStream << " (" << fixed << setprecision(4) << dPerTimestep << " per timestep)" << endl; -// -// // Calculate ratio of run time to time simulated -// OutStream << fixed << setprecision(0) << "In terms of run time, this is "; -// LogStream << fixed << setprecision(0) << "In terms of run time, this is "; -// -// if (dDuration > dRunLength) -// { -// OutStream << dDuration / dRunLength << " x slower than reality" << endl; -// LogStream << dDuration / dRunLength << " x slower than reality" << endl; -// } -// -// else -// { -// OutStream << dRunLength / dDuration << " x faster than reality" << endl; -// LogStream << dRunLength / dDuration << " x faster than reality" << endl; -// } -// } - -//=============================================================================================================================== -//! strDispSimTime returns a string formatted as year Julian_day hour, given a parameter in hours -//=============================================================================================================================== -string CSimulation::strDispSimTime(const double dTimeIn) -{ - // Make sure no negative times - double dTmpTime = tMax(dTimeIn, 0.0); - - string strTime; - - // Constants - double const dHoursInYear = 24 * 365; // it was 365.25 - double const dHoursInDay = 24; - - // Display years - if (dTmpTime >= dHoursInYear) - { - double const dYears = floor(dTmpTime / dHoursInYear); - dTmpTime -= (dYears * dHoursInYear); - - strTime = to_string(static_cast(dYears)); - strTime.append("y "); - } - else - strTime = "0y "; - - // Display Julian days - if (dTmpTime >= dHoursInDay) - { - double const dJDays = floor(dTmpTime / dHoursInDay); - dTmpTime -= (dJDays * dHoursInDay); - - stringstream ststrTmp; - ststrTmp << FillToWidth('0', 3) << static_cast(dJDays); - strTime.append(ststrTmp.str()); - strTime.append("d "); - } - else - strTime.append("000d "); - - // Display hours - stringstream ststrTmp; - ststrTmp << FillToWidth('0', 2) << static_cast(dTmpTime); - strTime.append(ststrTmp.str()); - strTime.append("h"); - - return strTime; -} - -//=============================================================================================================================== -//! strDispTime returns a string formatted as h:mm:ss, given a parameter in seconds, with rounding and fractions of a second if desired -//=============================================================================================================================== -string CSimulation::strDispTime(const double dTimeIn, const bool bRound, const bool bFrac) -{ - // Make sure no negative times - double dTime = tMax(dTimeIn, 0.0); - - string strTime; - - if (bRound) - dTime = dRound(dTime); - - unsigned long ulTimeIn = static_cast(floor(dTime)); - dTime -= static_cast(ulTimeIn); - - // Hours - if (ulTimeIn >= 3600) - { - // Display some hours - unsigned long const ulHours = ulTimeIn / 3600ul; - ulTimeIn -= (ulHours * 3600ul); - - strTime = to_string(ulHours); - strTime.append(":"); - } - else - strTime = "0:"; - - // Minutes - if (ulTimeIn >= 60) - { - // display some minutes - unsigned long const ulMins = ulTimeIn / 60ul; - ulTimeIn -= (ulMins * 60ul); - - stringstream ststrTmp; - ststrTmp << FillToWidth('0', 2) << ulMins; - strTime.append(ststrTmp.str()); - strTime.append(":"); - } - else - strTime.append("00:"); - - // Seconds - stringstream ststrTmp; - ststrTmp << FillToWidth('0', 2) << ulTimeIn; - strTime.append(ststrTmp.str()); - - if (bFrac) - { - // Fractions of a second - strTime.append("."); - ststrTmp.clear(); - ststrTmp.str(string()); - ststrTmp << FillToWidth('0', 2) << static_cast(dTime * 100); - strTime.append(ststrTmp.str()); - } - - return strTime; -} - -//=============================================================================================================================== -//! Returns the date and time on which the program was compiled -//=============================================================================================================================== -string CSimulation::strGetBuild(void) -{ - string strBuild("("); - strBuild.append(__TIME__); - strBuild.append(" "); - strBuild.append(__DATE__); -#ifdef _DEBUG - strBuild.append(" DEBUG"); -#endif - strBuild.append(" build)"); - - return strBuild; -} - -//=============================================================================================================================== -//! Displays information regarding the progress of the simulation -//=============================================================================================================================== -void CSimulation::AnnounceProgress(void) -{ - if (isatty(fileno(stdout))) - { - // Stdout is connected to a tty, so not running as a background job - static double sdElapsed = 0; - static double sdToGo = 0; - time_t const tNow = time(nullptr); - - // Calculate time elapsed and remaining - sdElapsed = difftime(tNow, m_tSysStartTime); - sdToGo = (sdElapsed * m_dSimDuration / m_dSimElapsed) - sdElapsed; - - // Tell the user about progress (note need to make several separate calls to cout here, or MS VC++ compiler appears to get confused) - cout << SIMULATING << strDispSimTime(m_dSimElapsed); - cout << fixed << setprecision(3) << setw(9) << 100 * m_dSimElapsed / m_dSimDuration; - cout << "% (elapsed " << strDispTime(sdElapsed, false, false) << " remaining "; - - cout << strDispTime(sdToGo, false, false) << ") "; - - // Add a 'marker' for GIS saves etc. - if (m_bSaveGISThisIter) - cout << setw(9) << "GIS" + to_string(m_nGISSave); - else if (m_bSedimentInputThisIter) - cout << setw(9) << "SED INPUT"; - else - cout << setw(9) << SPACE; - - cout.flush(); - } -} - -//=============================================================================================================================== -//! This calculates and displays process statistics -//=============================================================================================================================== -void CSimulation::CalcProcessStats(void) -{ - string const NA = "Not available"; - - OutStream << endl; - OutStream << "Process statistics" << endl; - OutStream << "------------------" << endl; - -#ifdef _WIN32 - // First, find out which version of Windows we are running under - OSVERSIONINFOEX osvi; - BOOL bOsVersionInfoEx; - - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); // fill this much memory with zeros - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - - if (!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*)&osvi))) - { - // OSVERSIONINFOEX didn't work so try OSVERSIONINFO instead - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - - if (!GetVersionEx((OSVERSIONINFO*)&osvi)) - { - // That didn't work either, too risky to proceed so give up - OutStream << NA << endl; - return; - } - } - - // OK, we have Windows version so display it - OutStream << "Running under \t: "; - - switch (osvi.dwPlatformId) - { - case VER_PLATFORM_WIN32_NT: - if (osvi.dwMajorVersion <= 4) - OutStream << "Windows NT "; - - else if (5 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) - OutStream << "Windows 2000 "; - - else if (5 == osvi.dwMajorVersion && 1 == osvi.dwMinorVersion) - OutStream << "Windows XP "; - - else if (6 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) - OutStream << "Windows Vista "; - - else if (6 == osvi.dwMajorVersion && 1 == osvi.dwMinorVersion) - OutStream << "Windows 7 "; - - else if (6 == osvi.dwMajorVersion && 2 == osvi.dwMinorVersion) - OutStream << "Windows 8 "; - - else if (6 == osvi.dwMajorVersion && 3 == osvi.dwMinorVersion) - OutStream << "Windows 8.1 "; - - else if (10 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) - OutStream << "Windows 10 "; - - else if (11 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) - OutStream << "Windows 11 "; - - else - OutStream << "unknown Windows version "; - - // Display version, service pack (if any), and build number - if (osvi.dwMajorVersion <= 4) - OutStream << "version " << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << " " << osvi.szCSDVersion << " (Build " << (osvi.dwBuildNumber & 0xFFFF) << ")" << endl; - - else - OutStream << osvi.szCSDVersion << " (Build " << (osvi.dwBuildNumber & 0xFFFF) << ")" << endl; - - break; - - case VER_PLATFORM_WIN32_WINDOWS: - if (4 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) - { - OutStream << "Windows 95"; - - if ('C' == osvi.szCSDVersion[1] || 'B' == osvi.szCSDVersion[1]) - OutStream << " OSR2"; - - OutStream << endl; - } - - else if (4 == osvi.dwMajorVersion && 10 == osvi.dwMinorVersion) - { - OutStream << "Windows 98"; - - if ('A' == osvi.szCSDVersion[1]) - OutStream << "SE"; - - OutStream << endl; - } - - else if (4 == osvi.dwMajorVersion && 90 == osvi.dwMinorVersion) - OutStream << "Windows Me" << endl; - - else - OutStream << "unknown 16-bit Windows version " << endl; - - break; - - case VER_PLATFORM_WIN32s: - OutStream << "Win32s" << endl; - break; - } - - // Now get process timimgs: this only works under 32-bit windows - if (VER_PLATFORM_WIN32_NT == osvi.dwPlatformId) - { - FILETIME ftCreate, ftExit, ftKernel, ftUser; - - if (GetProcessTimes(GetCurrentProcess(), &ftCreate, &ftExit, &ftKernel, &ftUser)) - { - ULARGE_INTEGER ul; - ul.LowPart = ftUser.dwLowDateTime; - ul.HighPart = ftUser.dwHighDateTime; - OutStream << "Time spent executing user code \t: " << strDispTime(static_cast(ul.QuadPart) * 1e-7, false) << endl; - ul.LowPart = ftKernel.dwLowDateTime; - ul.HighPart = ftKernel.dwHighDateTime; - OutStream << "Time spent executing kernel code \t: " << strDispTime(static_cast(ul.QuadPart) * 1e-7, false) << endl; - } - } - - else - OutStream << "Process timings \t: " << NA << endl; - - // Finally get more process statistics: this needs psapi.dll, so only proceed if it is present on this system - HINSTANCE hDLL = LoadLibrary("psapi.dll"); - - if (hDLL != NULL) - { - // The dll has been found - typedef BOOL(__stdcall * DLLPROC)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD); - DLLPROC ProcAdd; - - // Try to get the address of the function we will call - ProcAdd = (DLLPROC)GetProcAddress(hDLL, "GetProcessMemoryInfo"); - - if (ProcAdd) - { - // Address was found - PROCESS_MEMORY_COUNTERS pmc; - - // Now call the function - if ((ProcAdd)(GetCurrentProcess(), &pmc, sizeof(pmc))) - { - OutStream << "Peak working set size \t: " << pmc.PeakWorkingSetSize / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Current working set size \t: " << pmc.WorkingSetSize / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Peak paged pool usage \t: " << pmc.QuotaPeakPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Current paged pool usage \t: " << pmc.QuotaPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Peak non-paged pool usage \t: " << pmc.QuotaPeakNonPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Current non-paged pool usage \t: " << pmc.QuotaNonPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Peak pagefile usage \t: " << pmc.PeakPagefileUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "Current pagefile usage \t: " << pmc.PagefileUsage / (1024.0 * 1024.0) << " Mb" << endl; - OutStream << "No. of page faults \t: " << pmc.PageFaultCount << endl; - } - } - - // Free the memory used by the dll - FreeLibrary(hDLL); - } - -#elif defined __GNUG__ - rusage ru; - - if (getrusage(RUSAGE_SELF, &ru) >= 0) - { - OutStream << "Time spent executing user code \t: " << strDispTime(static_cast(ru.ru_utime.tv_sec), false, true) << endl; - // OutStream << "ru_utime.tv_usec \t: " << ru.ru_utime.tv_usec << endl; - OutStream << "Time spent executing kernel code \t: " << strDispTime(static_cast(ru.ru_stime.tv_sec), false, true) << endl; - // OutStream << "ru_stime.tv_usec \t: " << ru.ru_stime.tv_usec << endl; - // OutStream << "Maximum resident set size \t: " << ru.ru_maxrss/1024.0 << " Mb" << endl; - // OutStream << "ixrss (???) \t: " << ru.ru_ixrss << endl; - // OutStream << "Sum of rm_asrss (???) \t: " << ru.ru_idrss << endl; - // OutStream << "isrss (???) \t: " << ru.ru_isrss << endl; - OutStream << "No. of page faults not requiring physical I/O\t: " << ru.ru_minflt << endl; - OutStream << "No. of page faults requiring physical I/O \t: " << ru.ru_majflt << endl; - // OutStream << "No. of times swapped out of main memory \t: " << ru.ru_nswap << endl; - // OutStream << "No. of times performed input (read request) \t: " << ru.ru_inblock << endl; - // OutStream << "No. of times performed output (write request)\t: " << ru.ru_oublock << endl; - // OutStream << "No. of signals received \t: " << ru.ru_nsignals << endl; - OutStream << "No. of voluntary context switches \t: " << ru.ru_nvcsw << endl; - OutStream << "No. of involuntary context switches \t: " << ru.ru_nivcsw << endl; - } - - else - OutStream << NA << endl; - -#else - OutStream << NA << endl; -#endif - - OutStream << endl; - -#ifdef _OPENMP -#pragma omp parallel - { - if (0 == omp_get_thread_num()) - { - OutStream << "Number of OpenMP threads \t: " << omp_get_num_threads() << endl; - OutStream << "Number of OpenMP processors \t: " << omp_get_num_procs() << endl; - - LogStream << "Number of OpenMP threads \t: " << omp_get_num_threads() << endl; - LogStream << "Number of OpenMP processors \t: " << omp_get_num_procs() << endl; - } - } -#endif - - time_t const tRunTime = m_tSysEndTime - m_tSysStartTime; - struct tm* ptmRunTime = gmtime(&tRunTime); - - OutStream << "Time required for simulation \t: " << put_time(ptmRunTime, "%T") << endl; - LogStream << "Time required for simulation \t: " << put_time(ptmRunTime, "%T") << endl; - - double const dSpeedUp = m_dSimDuration * 3600 / static_cast(tRunTime); - OutStream << setprecision(0); - OutStream << "Time simulated / time required for simulation\t: " << dSpeedUp << " x faster than reality" << endl; - - LogStream << setprecision(0); - LogStream << "Time simulated / time required for simulation\t: " << dSpeedUp << " x faster than reality" << endl; -} - -//=============================================================================================================================== -//! Returns an error message given an error code -//=============================================================================================================================== -string CSimulation::strGetErrorText(int const nErr) -{ - string strErr; - - switch (nErr) - { - case RTN_USER_ABORT: - strErr = "run ended by user"; - break; - - case RTN_ERR_BADPARAM: - strErr = "error in command-line parameter"; - break; - - case RTN_ERR_INI: - strErr = "error reading initialisation file"; - break; - - case RTN_ERR_CMEDIR: - strErr = "error in directory name"; - break; - - case RTN_ERR_RUNDATA: - strErr = "error reading run details file"; - break; - - case RTN_ERR_SCAPE_SHAPE_FUNCTION_FILE: - strErr = "error reading SCAPE shape function file"; - break; - - case RTN_ERR_TIDEDATAFILE: - strErr = "error reading tide data file"; - break; - - case RTN_ERR_LOGFILE: - strErr = "error creating log file"; - break; - - case RTN_ERR_OUTFILE: - strErr = "error creating text output file"; - break; - - case RTN_ERR_TSFILE: - strErr = "error creating time series file"; - break; - - case RTN_ERR_DEMFILE: - strErr = "error reading initial DEM file"; - break; - - case RTN_ERR_RASTER_FILE_READ: - strErr = "error reading raster GIS file"; - break; - - case RTN_ERR_VECTOR_FILE_READ: - strErr = "error reading vector GIS file"; - break; - - case RTN_ERR_MEMALLOC: - strErr = "error allocating memory"; - break; - - case RTN_ERR_RASTER_GIS_OUT_FORMAT: - strErr = "problem with raster GIS output format"; - break; - - case RTN_ERR_VECTOR_GIS_OUT_FORMAT: - strErr = "problem with vector GIS output format"; - break; - - case RTN_ERR_TEXT_FILE_WRITE: - strErr = "error writing text output file"; - break; - - case RTN_ERR_RASTER_FILE_WRITE: - strErr = "error writing raster GIS output file"; - break; - - case RTN_ERR_VECTOR_FILE_WRITE: - strErr = "error writing vector GIS output file"; - break; - - case RTN_ERR_TIMESERIES_FILE_WRITE: - strErr = "error writing time series output file"; - break; - - case RTN_ERR_LINETOGRID: - strErr = "error putting linear feature onto raster grid"; - break; - - case RTN_ERR_NOSEACELLS: - strErr = "no sea cells found"; - break; - - case RTN_ERR_GRID_TO_LINE: - strErr = "error when searching grid for linear feature"; - break; - - case RTN_ERR_NO_COAST: - strErr = "no coastlines found. Is the SWL correct?"; - break; - - case RTN_ERR_PROFILE_WRITE: - strErr = "error writing coastline-normal profiles"; - break; - - case RTN_ERR_TIME_UNITS: - strErr = "error in time units"; - break; - - case RTN_ERR_NO_SOLUTION_FOR_ENDPOINT: - strErr = "no solution when finding end point for coastline-normal line"; - break; - - case RTN_ERR_PROFILE_ENDPOINT_IS_INLAND: - strErr = "end point for coastline-normal line is not in the contiguous sea"; - break; - - case RTN_ERR_CLIFF_NOTCH: - strErr = "cliff notch is above sediment top elevation"; - break; - - case RTN_ERR_CLIFF_CANNOT_DEPOSIT_ALL: - strErr = "unable to deposit enough unconsolidated sediment (talus) from cliff collapse"; - break; - - case RTN_ERR_PROFILE_SPACING: - strErr = "coastline-normal profiles are too closely spaced"; - break; - - case RTN_ERR_NO_PROFILES_1: - strErr = "no coastline-normal profiles created, check the SWL"; - break; - - case RTN_ERR_NO_PROFILES_2: - strErr = "no coastline-normal profiles created during rasterization"; - break; - - case RTN_ERR_EDGE_OF_GRID: - strErr = "hit grid edge when eroding beach"; - break; - - case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_BEACH_EROSION: - strErr = "could not locate seaward end of profile when creating Dean profile for beach erosion"; - break; - - case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_UPCOAST_BEACH_DEPOSITION: - strErr = "could not locate seaward end of profile when creating Dean profile for up-coast beach deposition"; - break; - - case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_DOWNCOAST_BEACH_DEPOSITION: - strErr = "could not locate seaward end of profile when creating Dean profile for down-coast beach deposition"; - break; - - case RTN_ERR_LANDFORM_TO_GRID: - strErr = "updating grid with landforms"; - break; - - case RTN_ERR_NO_TOP_LAYER: - strErr = "no top layer of sediment"; - break; - - case RTN_ERR_NO_ADJACENT_POLYGON: - strErr = "problem with polygon-to-polygon sediment routing sequence"; - break; - - case RTN_ERR_BAD_MULTILINE: - strErr = "inconsistent multiline"; - break; - - case RTN_ERR_CANNOT_INSERT_POINT: - strErr = "cannot insert point into multiline"; - break; - - case RTN_ERR_CANNOT_ASSIGN_COASTAL_LANDFORM: - strErr = "cannot assign coastal landform"; - break; - - case RTN_ERR_SHADOW_ZONE_FLOOD_FILL_NOGRID: - strErr = "start point for cell-by-cell fill of wave shadow zone is outside grid"; - break; - - case RTN_ERR_SHADOW_ZONE_FLOOD_START_POINT: - strErr = "could not find start point for cell-by-cell fill of wave shadow zone"; - break; - - case RTN_ERR_CSHORE_EMPTY_PROFILE: - strErr = "empty profile during during CShore wave propagation"; - break; - - case RTN_ERR_CSHORE_FILE_INPUT: - strErr = "creating file for CShore input"; - break; - - case RTN_ERR_READING_CSHORE_FILE_OUTPUT: - strErr = "reading CShore output file"; - break; - - case RTN_ERR_WAVE_INTERPOLATION_LOOKUP: - strErr = "during wave interpolation lookup"; - break; - - case RTN_ERR_GRIDCREATE: - strErr = "while running GDALGridCreate()"; - break; - - case RTN_ERR_COAST_CANT_FIND_EDGE_CELL: - strErr = "cannot find edge cell while constructing grid-edge profile"; - break; - - case RTN_ERR_CSHORE_ERROR: - strErr = "CShore did not finish correctly"; - break; - - case RTN_ERR_NO_CELL_UNDER_COASTLINE: - strErr = "Could not find cell under coastline"; - break; - - case RTN_ERR_OPEN_DEEP_WATER_WAVE_DATA: - strErr = "opening deep sea wave time series file"; - break; - - case RTN_ERR_READING_DEEP_WATER_WAVE_DATA: - strErr = "reading deep sea wave time series file"; - break; - - case RTN_ERR_BOUNDING_BOX: - strErr = "finding edges of the bounding box"; - break; - - case RTN_ERR_READING_SEDIMENT_INPUT_EVENT: - strErr = "reading sediment input event time series file"; - break; - - case RTN_ERR_SEDIMENT_INPUT_EVENT: - strErr = "simulating sediment input event"; - break; - - case RTN_ERR_SEDIMENT_INPUT_EVENT_LOCATION: - strErr = "location of sediment input event is outside grod"; - break; - - case RTN_ERR_WAVESTATION_LOCATION: - strErr = "location of wavestation is outside grid"; - break; - - case RTN_ERR_CLIFF_NOT_IN_POLYGON: - strErr = "cliff not in polygon"; - break; - - case RTN_ERR_CELL_MARKED_PROFILE_COAST_BUT_NOT_PROFILE: - strErr = "Cell marked as profile coast but not as profile"; - break; - - case RTN_ERR_TRACING_FLOOD: - strErr = "error tracing flood line on grid"; - break; - - case RTN_ERR_NO_START_FINISH_POINTS_TRACING_COAST: - strErr = "error tracing coastline on grid, no coast start-finish points found"; - break; - - case RTN_ERR_NO_VALID_COAST: - strErr = "error tracing coastline on grid, no valid coast found"; - break; - - case RTN_ERR_REPEATING_WHEN_TRACING_COAST: - strErr = "error tracing coastline on grid, coast search just repeats"; - break; - - case RTN_ERR_ZERO_LENGTH_COAST: - strErr = "error tracing coastline on grid, zero-length coast found"; - break; - - case RTN_ERR_COAST_TOO_SMALL: - strErr = "error tracing coastline on grid, coast below minimum permitted length"; - break; - - case RTN_ERR_IGNORING_COAST: - strErr = "error tracing coastline on grid, coast ignored"; - break; - - case RTN_ERR_TOO_LONG_TRACING_COAST: - strErr = "error tracing coastline on grid, too many times round tracing loop"; - break; - - case RTN_ERR_CELL_NOT_FOUND_IN_HIT_PROFILE_DIFFERENT_COASTS: - strErr = "intersection cell not found in hit profile"; - break; - - case RTN_ERR_POINT_NOT_FOUND_IN_MULTILINE_DIFFERENT_COASTS: - strErr = "point not found when truncating multiline for different coasts"; - break; - - case RTN_ERR_CELL_NOT_FOUND_IN_HIT_PROFILE: - strErr = "cell not found in hit profile"; - break; - - case RTN_ERR_CELL_IN_POLY_BUT_NO_POLY_COAST: - strErr = "cell marked as in polygon, but does not have polygon's coast"; - break; - - case RTN_ERR_UNKNOWN: - strErr = "unknown error"; - break; - - default: - // should never get here - strErr = " error"; - } - - return strErr; -} - -//=============================================================================================================================== -//! Notifies the user that the simulation has ended, asks for keypress if necessary, and if compiled under GNU can send an email -//=============================================================================================================================== -void CSimulation::DoSimulationEnd(int const nRtn) -{ - // If we don't know the time that the run ended (e.g. because it did not finish correctly), then get it now - if (m_tSysEndTime == 0) - m_tSysEndTime = time(nullptr); - - switch (nRtn) - { - case (RTN_OK): - // normal ending - cout << RUN_END_NOTICE << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; - break; - - case (RTN_HELP_ONLY): - case (RTN_CHECK_ONLY): - return; - - default: - // Aborting because of some error - cerr << RUN_END_NOTICE << "iteration " << m_ulIter << ERROR_NOTICE << nRtn << ": \"" << strGetErrorText(nRtn) << "\", " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; - - if (m_ulIter > 1) - { - // If the run has actually started, then output all GIS files: this is very helpful in tracking down problems - m_bSaveGISThisIter = true; - m_nGISSave = 998; // Will get incremented to 999 when we write the files - bSaveAllRasterGISFiles(); - bSaveAllVectorGISFiles(); - } - - // Write the error message to the logfile and to stdout - if (LogStream && LogStream.is_open()) - { - LogStream << ERR << strGetErrorText(nRtn) << " (error code " << nRtn << ") on " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; - LogStream.flush(); - } - - if (OutStream && OutStream.is_open()) - { - OutStream << ERR << strGetErrorText(nRtn) << " (error code " << nRtn << ") on " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; - OutStream.flush(); - } - } - -#ifdef __GNUG__ - if (isatty(fileno(stdout))) - { - // Stdout is connected to a tty, so not running as a background job - // cout << endl - // << PRESS_KEY; - // cout.flush(); - // getchar(); - } - else - { - // Stdout is not connected to a tty, so must be running in the background; if we have something entered for the email address, then send an email - if (! m_strMailAddress.empty()) - { - cout << SEND_EMAIL << m_strMailAddress << endl; - - string strCmd("echo \""); - - stringstream ststrTmp; - ststrTmp << put_time(localtime(&m_tSysEndTime), "%T on %A %d %B %Y") << endl; - - // Send an email using Linux/Unix mail command - if (RTN_OK == nRtn) - { - // Finished normally - strCmd.append("Simulation "); - strCmd.append(m_strRunName); - strCmd.append(", running on "); - strCmd.append(strGetComputerName()); - strCmd.append(", completed normally at "); - strCmd.append(ststrTmp.str()); - strCmd.append("\" | mail -s \""); - strCmd.append(PROGRAM_NAME); - strCmd.append(": normal completion\" "); - strCmd.append(m_strMailAddress); - } - else - { - // Error, so give some information to help debugging - strCmd.append("Simulation "); - strCmd.append(m_strRunName); - strCmd.append(", running on "); - strCmd.append(strGetComputerName()); - strCmd.append(", aborted with error code "); - strCmd.append(to_string(nRtn)); - strCmd.append(": "); - strCmd.append(strGetErrorText(nRtn)); - strCmd.append(" at timestep "); - strCmd.append(to_string(m_ulIter)); - strCmd.append(" ("); - strCmd.append(strDispSimTime(m_dSimElapsed)); - strCmd.append(").\n\nThis message sent at "); - strCmd.append(ststrTmp.str()); - strCmd.append("\" | mail -s \""); - strCmd.append(PROGRAM_NAME); - strCmd.append(": ERROR\" "); - strCmd.append(m_strMailAddress); - } - - int const nRet = system(strCmd.c_str()); - - if (WEXITSTATUS(nRet) != 0) - cerr << ERR << EMAIL_ERROR << endl; - } - } -#endif -} - -//=============================================================================================================================== -//! Changes all forward slashes in the input string to backslashes, leaving the original unchanged -//=============================================================================================================================== -string CSimulation::pstrChangeToBackslash(string const* strIn) -{ - string strOut(*strIn); - strOut.replace(strOut.begin(), strOut.end(), '/', '\\'); - return strOut; -} - -//=============================================================================================================================== -//! Swaps all backslashes in the input string to forward slashes, leaving the original unchanged -//=============================================================================================================================== -string CSimulation::pstrChangeToForwardSlash(string const* strIn) -{ - string strOut(*strIn); - strOut.replace(strOut.begin(), strOut.end(), '\\', '/'); - return strOut; -} - -//=============================================================================================================================== -//! Trims whitespace from the left side of a string, does not change the original string -//=============================================================================================================================== -string CSimulation::strTrimLeft(string const* strIn) -{ - // Trim leading spaces - size_t const nStartpos = strIn->find_first_not_of(" \t"); - - if (nStartpos == string::npos) - return *strIn; - - else - return strIn->substr(nStartpos); -} - -//=============================================================================================================================== -//! Trims whitespace from the right side of a string, does not change the original string -//=============================================================================================================================== -string CSimulation::strTrimRight(string const* strIn) -{ - string strTmp(*strIn); - - // Remove any stray carriage returns (can happen if file was edited in Windows) - strTmp.erase(remove(strTmp.begin(), strTmp.end(), '\r'), strTmp.end()); - - // Trim trailing spaces - size_t const nEndpos = strTmp.find_last_not_of(" \t"); - - if (nEndpos == string::npos) - return strTmp; - - else - return strTmp.substr(0, nEndpos + 1); -} - -//=============================================================================================================================== -//! Trims whitespace from both sides of a string, does not change the original string -//=============================================================================================================================== -string CSimulation::strTrim(string const* strIn) -{ - string strTmp = *strIn; - - // Remove any stray carriage returns (can happen if file was edited in Windows) - strTmp.erase(remove(strTmp.begin(), strTmp.end(), '\r'), strTmp.end()); - - // Trim trailing spaces - size_t nPos = strTmp.find_last_not_of(" \t"); - - if (nPos != string::npos) - strTmp.resize(nPos + 1); - - // Trim leading spaces - nPos = strTmp.find_first_not_of(" \t"); - - if (nPos != string::npos) - strTmp = strTmp.substr(nPos); - - return strTmp; -} - -//=============================================================================================================================== -//! Returns the lower case version of an string, leaving the original unchanged -//=============================================================================================================================== -string CSimulation::strToLower(string const* strIn) -{ - string strOut = *strIn; - transform(strIn->begin(), strIn->end(), strOut.begin(), tolower); - return strOut; -} - -//=============================================================================================================================== -// Returns the upper case version of an string, leaving the original unchanged -//=============================================================================================================================== -// string CSimulation::strToUpper(string const* strIn) -// { -// string strOut = *strIn; -// transform(strIn->begin(), strIn->end(), strOut.begin(), toupper); -// return strOut; -// } - -//=============================================================================================================================== -//! Returns a string with a substring removed, and with whitespace trimmed -//=============================================================================================================================== -string CSimulation::strRemoveSubstr(string* pStrIn, string const* pStrSub) -{ - size_t const nPos = pStrIn->find(*pStrSub); - - if (nPos != string::npos) - { - // OK, found the substring - pStrIn->replace(nPos, pStrSub->size(), ""); - return strTrim(pStrIn); - } - - else - { - // If not found, return the string unchanged - return *pStrIn; - } -} - -//=============================================================================================================================== -//! From http://stackoverflow.com/questions/236129/split-a-string-in-c They implement (approximately) Python's split() function. This first version puts the results into a pre-constructed string vector. It ignores empty items -//=============================================================================================================================== -vector* CSimulation::VstrSplit(string const* s, char const delim, vector* elems) -{ - stringstream ss(*s); - string item; - - while (getline(ss, item, delim)) - { - if (!item.empty()) - elems->push_back(item); - } - - return elems; -} - -//=============================================================================================================================== -//! From http://stackoverflow.com/questions/236129/split-a-string-in-c They implement (approximately) Python's split() function. This second version returns a new string vector (it calls the first version) -//=============================================================================================================================== -vector CSimulation::VstrSplit(string const* s, char const delim) -{ - vector elems; - VstrSplit(s, delim, &elems); - return elems; -} - -// //=============================================================================================================================== -// //! Calculates the vector cross product of three points -// //=============================================================================================================================== -// double CSimulation::dCrossProduct(double const dX1, double const dY1, double const dX2, double const dY2, double const dX3, double const dY3) -// { -// // Based on code at http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm -// return (dX2 - dX1) * (dY3 - dY2) - ((dY2 - dY1) * (dX3 - dX2)); -// } - -// //=============================================================================================================================== -// //! Calculates the mean of a pointer to a vector of doubles -// //=============================================================================================================================== -// double CSimulation::dGetMean(vector const* pV) -// { -// double dSum = accumulate(pV->begin(), pV->end(), 0.0); -// double dMean = dSum / static_cast(pV->size()); -// return dMean; -// } - -// //=============================================================================================================================== -// //! Calculates the standard deviation of a pointer to a vector of doubles. From http://stackoverflow.com/questions/7616511/calculate-mean-and-standard-deviation-from-a-vector-of-samples-in-c-using-boos -// //=============================================================================================================================== -// double CSimulation::dGetStdDev(vector const* pV) -// { -// double dSum = accumulate(pV->begin(), pV->end(), 0.0); -// double dMean = dSum / static_cast(pV->size()); -// -// double dSqSum = inner_product(pV->begin(), pV->end(), pV->begin(), 0.0); -// double dStdDev = sqrt(dSqSum / static_cast(pV->size()) - dMean * dMean); -// -// return dStdDev; -// } - -//=============================================================================================================================== -//! Appends a CGeom2DIPoint to a vector, making sure that the new end point touches the previous end point i.e. that there is no gap between the two points -//=============================================================================================================================== -void CSimulation::AppendEnsureNoGap(vector* pVPtiPoints, CGeom2DIPoint const* pPti) -{ - int const nX = pPti->nGetX(); - int const nY = pPti->nGetY(); - int const nXLast = pVPtiPoints->back().nGetX(); - int const nYLast = pVPtiPoints->back().nGetY(); - int const nXDiff = nX - nXLast; - int const nYDiff = nY - nYLast; - int const nXDiffA = tAbs(nXDiff); - int const nYDiffA = tAbs(nYDiff); - int const nDiff = tMax(nXDiffA, nYDiffA); - - if (nDiff > 1) - { - // We have a gap - double - dXInc = 0, - dYInc = 0; - - if (nXDiffA > 1) - dXInc = static_cast(nXDiff) / nDiff; - - if (nYDiffA > 1) - dYInc = static_cast(nYDiff) / nDiff; - - for (int n = 1; n < nDiff; n++) - { - CGeom2DIPoint const Pti(nXLast + nRound(n * dXInc), nYLast + nRound(n * dYInc)); - pVPtiPoints->push_back(Pti); - } - } - - pVPtiPoints->push_back(CGeom2DIPoint(nX, nY)); -} - -//=============================================================================================================================== -//! Calculates a Dean equilibrium profile h(y) = A * y^(2/3) where h(y) is the distance below the highest point in the Dean profile at a distance y from the landward start of the profile -//=============================================================================================================================== -void CSimulation::CalcDeanProfile(vector* pdVDeanProfile, double const dInc, double const dDeanTopElev, double const dA, bool const bDeposition, int const nSeawardOffset, double const dStartCellElev) -{ - double dDistFromProfileStart = 0; - - if (bDeposition) - { - // This Dean profile is for deposition i.e. seaward displacement of the profile - pdVDeanProfile->at(0) = dStartCellElev; // Is talus-top elev for cliffs, coast elevation for coasts - - for (int n = 1; n < static_cast(pdVDeanProfile->size()); n++) - { - if (n <= nSeawardOffset) - // As we extend the profile seaward, the elevation of any points coastward of the new coast point of the Dean profile are set to the elevation of the original coast or the talus top (is this realistic for talus?) - pdVDeanProfile->at(n) = dStartCellElev; - - else - { - double const dDistBelowTop = dA * pow(dDistFromProfileStart, DEAN_POWER); - pdVDeanProfile->at(n) = dDeanTopElev - dDistBelowTop; - - dDistFromProfileStart += dInc; - } - } - } - - else - { - // This Dean profile is for erosion i.e. landward displacement of the profile - for (int n = 0; n < static_cast(pdVDeanProfile->size()); n++) - { - double const dDistBelowTop = dA * pow(dDistFromProfileStart, DEAN_POWER); - pdVDeanProfile->at(n) = dDeanTopElev - dDistBelowTop; - - dDistFromProfileStart += dInc; - } - } -} - -//=============================================================================================================================== -//! Calculate the total elevation difference between every point in two elevation profiles (first profile - second profile) -//=============================================================================================================================== -double CSimulation::dSubtractProfiles(vector const* pdVFirstProfile, vector const* pdVSecondProfile, vector const* pbVIsValid) -{ - double dTotElevDiff = 0; - - // Note that this assumes that all three vectors are of equal length, should really check this - for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) - { - if (pbVIsValid->at(n)) - { - double const dProfileDiff = pdVFirstProfile->at(n) - pdVSecondProfile->at(n); - - dTotElevDiff += dProfileDiff; - } - } - - // // DEBUG CODE ----------------------------------------------------- - // LogStream << endl; - // LogStream << "First profile = "; - // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) - // { - // LogStream << pdVFirstProfile->at(n) << " "; - // } - // LogStream << endl; - // LogStream << "Second profile = "; - // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) - // { - // LogStream << pdVSecondProfile->at(n) << " "; - // } - // LogStream << endl; - // LogStream << "Difference = "; - // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) - // { - // LogStream << pdVFirstProfile->at(n) - pdVSecondProfile->at(n) << " "; - // } - // LogStream << endl; - // // DEBUG CODE ----------------------------------------------------- - - return dTotElevDiff; -} - -//=============================================================================================================================== -//! Calculate the depth of closure -//=============================================================================================================================== -void CSimulation::CalcDepthOfClosure(void) -{ - double - dDeepWaterWaveHeight, - dDeepWaterPeriod; - - if (m_bSingleDeepWaterWaveValues) - { - dDeepWaterWaveHeight = m_dAllCellsDeepWaterWaveHeight; - dDeepWaterPeriod = m_dAllCellsDeepWaterWavePeriod; - } - - else - { - dDeepWaterWaveHeight = m_dMaxUserInputWaveHeight; - dDeepWaterPeriod = m_dMaxUserInputWavePeriod; - } - - // TODO 051 Calculate depth of closure using 'average of the maximum values observed during a typical year' - // dL = 2.28 * Hsx − (68.5 * Hsx^2 / (g * Tsx^2)) - // where: - // Hsx is the nearshore storm wave height that is exceeded only 12 hours each year - // Tsx is the associated wave period - // from Hallermeier, R.J. (1978). Uses for a calculated limit depth to beach erosion. Proc. 16th Coastal Engineering Conf., ASCE, New York. Pp 1493 - 1512 - // - // For the time being, and since we assume wave height and period constant just use the actual wave height and period to calculate the depth of closure - // m_dDepthOfClosure = (2.28 * dDeepWaterWaveHeight) - (68.5 * dDeepWaterWaveHeight * dDeepWaterWaveHeight / (m_dG * dDeepWaterPeriod * dDeepWaterPeriod)); - - // An alternative (which produces smaller depth of closure estimates) is Birkemeier (1985) TODO 007 Full reference needed - // dL = 1.75 * Hsx - (57.9 * Hsx^2/ (g * Tsx^2)) - m_dDepthOfClosure = (1.75 * dDeepWaterWaveHeight) - (57.9 * dDeepWaterWaveHeight * dDeepWaterWaveHeight / (m_dG * dDeepWaterPeriod * dDeepWaterPeriod)); -} - -// //=============================================================================================================================== -// //! Tests a reference to a string to see if it is numeric (modified from https://tfetimes.com/c-determine-if-a-string-is-numeric/) -// //=============================================================================================================================== -// bool CSimulation::bIsNumeric(string const*strIn) -// { -// return all_of(strIn->begin(), strIn->end(), isdigit); -// } - -//=============================================================================================================================== -//! Parses a date string into days, months, and years, and checks each of them -//=============================================================================================================================== -bool CSimulation::bParseDate(string const* strDate, int& nDay, int& nMonth, int& nYear) -{ - vector VstrTmp = VstrSplit(strDate, SLASH); - - if (VstrTmp.size() < 3) - { - cerr << "date string must include day, month, and year '" << strDate << "'" << endl; - return false; - } - - // Sort out day - if (! bIsStringValidInt(VstrTmp[0])) - { - cerr << "invalid integer for day in date '" << strDate << "'" << endl; - return false; - } - - nDay = stoi(VstrTmp[0]); - - if ((nDay < 1) || (nDay > 31)) - { - cerr << "day must be between 1 and 31 in date '" << strDate << "'" << endl; - return false; - } - - // Sort out month - if (! bIsStringValidInt(VstrTmp[1])) - { - cerr << "invalid integer for month in date '" << strDate << "'" << endl; - return false; - } - - nMonth = stoi(VstrTmp[1]); - - if ((nMonth < 1) || (nMonth > 12)) - { - cerr << "month must be between 1 and 12 in date '" << strDate << "'" << endl; - return false; - } - - // Sort out year - if (! bIsStringValidInt(VstrTmp[2])) - { - cerr << "invalid integer for year in date '" << strDate << "'" << endl; - return false; - } - - nYear = stoi(VstrTmp[2]); - - if (nYear < 0) - { - cerr << "year must be > 0 in date '" << strDate << "'" << endl; - return false; - } - - return true; -} - -//=============================================================================================================================== -//! Parses a time string into hours, minutes, and seconds, and checks each of them -//=============================================================================================================================== -bool CSimulation::bParseTime(string const* strTime, int& nHour, int& nMin, int& nSec) -{ - vector VstrTmp = VstrSplit(strTime, DASH); - - if (VstrTmp.size() < 3) - { - cerr << "time string must include hours, minutes, and seconds '" << strTime << "'" << endl; - return false; - } - - // Sort out hour - if (! bIsStringValidInt(VstrTmp[0])) - { - cerr << "invalid integer for hours in time '" << strTime << "'" << endl; - return false; - } - - nHour = stoi(VstrTmp[0]); - - if ((nHour < 0) || (nHour > 23)) - { - cerr << "hour must be between 0 and 23 in time '" << strTime << "'" << endl; - return false; - } - - // Sort out minutes - if (! bIsStringValidInt(VstrTmp[1])) - { - cerr << "invalid integer for minutes in time '" << strTime << "'" << endl; - return false; - } - - nMin = stoi(VstrTmp[1]); - - if ((nMin < 0) || (nMin > 59)) - { - cerr << "minutes must be betwen 0 and 59 in time '" << strTime << "'" << endl; - return false; - } - - // Sort out seconds - if (! bIsStringValidInt(VstrTmp[2])) - { - cerr << "invalid integer for seconds in time '" << strTime << "'" << endl; - return false; - } - - nSec = stoi(VstrTmp[2]); - - if ((nSec < 0) || (nSec > 59)) - { - cerr << "seconds must be between 0 and 59 in time '" << strTime << "'" << endl; - return false; - } - - return true; -} - -//=============================================================================================================================== -//! For sediment input events, parses a string that may be relative (a number of hours or days after the start of the simulation), or absolute (a time/date in the format hh-mm-ss dd/mm/yyyy). Returns the timestep in which the sediment input event occurs -//=============================================================================================================================== -unsigned long CSimulation::ulConvertToTimestep(string const* pstrIn) const -{ - unsigned long ulTimeStep = 0; - - // Convert to lower case, remove leading and trailing whitespace - string strDate = strToLower(pstrIn); - strDate = strTrim(&strDate); - - if (strDate.find("hour") != string::npos) - { - // OK, this is a number of hours (a relative time, from the start of simulation) - vector VstrTmp = VstrSplit(&strDate, SPACE); - - if ((VstrTmp.size() < 2) || (! bIsStringValidInt(VstrTmp[0]))) - { - cerr << "Error in number of hours '" + strDate + "' for sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - double const dHours = stod(strTrim(&VstrTmp[0])); - - if (dHours > m_dSimDuration) - { - cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - ulTimeStep = static_cast(dRound(dHours / m_dTimeStep)); - } - - else if (strDate.find("day") != string::npos) - { - // OK, this is a number of days (a relative time, from the start of simulation) - vector VstrTmp = VstrSplit(&strDate, SPACE); - - if ((VstrTmp.size() < 2) || (! bIsStringValidInt(VstrTmp[0]))) - { - cerr << "Error in number of days '" + strDate + "' for sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - double const dHours = stod(strTrim(&VstrTmp[0])) * 24; - - if (dHours > m_dSimDuration) - { - cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - ulTimeStep = static_cast(dRound(dHours / m_dTimeStep)); - } - - else - { - // This is an absolute time/date in the format hh-mm-ss dd/mm/yyyy - vector VstrTmp = VstrSplit(&strDate, SPACE); - - if (VstrTmp.size() < 2) - { - cerr << "Error in time/date '" + strDate + "' of sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - int nHour = 0; - int nMin = 0; - int nSec = 0; - - // OK, first sort out the time - if (! bParseTime(&VstrTmp[0], nHour, nMin, nSec)) - { - cerr << "Error in time '" + VstrTmp[0] + "' of sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - int nDay = 0; - int nMonth = 0; - int nYear = 0; - - // Now sort out the time - if (! bParseDate(&VstrTmp[1], nDay, nMonth, nYear)) - { - cerr << "Error in date '" + VstrTmp[1] + "' of sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - // This is modified from https://stackoverflow.com/questions/14218894/number-of-days-between-two-dates-c - struct tm tmSimStart = {}; - tmSimStart.tm_sec = m_nSimStartSec; - tmSimStart.tm_min = m_nSimStartMin; - tmSimStart.tm_hour = m_nSimStartHour; - tmSimStart.tm_mday = m_nSimStartDay; - tmSimStart.tm_mon = m_nSimStartMonth - 1; - tmSimStart.tm_year = m_nSimStartYear - 1900; - - struct tm tmSimEvent = {}; - tmSimEvent.tm_sec = nSec; - tmSimEvent.tm_min = nMin; - tmSimEvent.tm_hour = nHour; - tmSimEvent.tm_mday = nDay; - tmSimEvent.tm_mon = nMonth - 1; - tmSimEvent.tm_year = nYear - 1900; - - time_t const tStart = mktime(&tmSimStart); - time_t const tEvent = mktime(&tmSimEvent); - - if (tStart == (time_t)(-1)) - { - cerr << "Error in simulation start time/date" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - if (tEvent == (time_t)(-1)) - { - cerr << "Error in time/date '" + strDate + "' of sediment input event" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - double const dHours = difftime(tEvent, tStart) / (60 * 60); - - if (dHours < 0) - { - cerr << "Sediment input event '" + strDate + "' occurs before start of simulation" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - if (dHours > m_dSimDuration) - { - cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; - return SEDIMENT_INPUT_EVENT_ERROR; - } - - ulTimeStep = static_cast(dHours / m_dTimeStep); - } - - return ulTimeStep; -} - -//=============================================================================================================================== -//! Returns true if the cell is an intervention -//=============================================================================================================================== -bool CSimulation::bIsInterventionCell(int const nX, int const nY) const -{ - if (m_pRasterGrid->m_Cell[nX][nY].pGetLandform()->nGetLFCategory() == LF_CAT_INTERVENTION) - return true; - - return false; -} - -//=============================================================================================================================== -//! Do end-of-run memory clearance -//=============================================================================================================================== -void CSimulation::DoEndOfRunDeletes(void) -{ - // Clear all vector coastlines, profiles, and polygons - m_VCoast.clear(); - - // m_VFloodWaveSetup.clear(); - m_VFloodWaveSetupSurge.clear(); - m_VFloodWaveSetupSurgeRunup.clear(); -} +/*! + \file utils.cpp + \brief Utility routines + \details TODO 001 A more detailed description of this routine. + \author David Favis-Mortlock + \author Andres Payo + \date 2025 + \copyright GNU General Public License +*/ + +/* ============================================================================================================================== + This file is part of CoastalME, the Coastal Modelling Environment. + + CoastalME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +==============================================================================================================================*/ +#include + +#ifdef _WIN32 +#include // Needed for CalcProcessStats() +#include +#include // For isatty() +#elif defined __GNUG__ +#include // Needed for CalcProcessStats() +#include // For isatty() +#include +#endif + +#ifdef _OPENMP +#include +#endif + +#include + +#include + +#include +using std::tolower; + +#include +using std::floor; + +#include +using std::clock; +using std::clock_t; +using std::difftime; +using std::localtime; +using std::time; + +#include +using std::fixed; + +#include +using std::cerr; +using std::cout; +using std::endl; +using std::ios; + +#include +using std::put_time; +using std::setprecision; +using std::setw; + +#include +using std::to_string; + +#include +using std::stringstream; + +#include +using std::transform; + +#include + +#include "cme.h" +#include "simulation.h" +#include "coast.h" +#include "2di_point.h" + +//=============================================================================================================================== +//! Handles command-line parameters +//=============================================================================================================================== +int CSimulation::nHandleCommandLineParams(int nArg, char const* pcArgv[]) +{ + if ((!isatty(fileno(stdout))) || (!isatty(fileno(stderr)))) + // Running with stdout or stderr not a tty, so either redirected or running as a background job. Ignore all command line parameters + return RTN_OK; + + // Process the parameters following the name of the executable + for (int i = 1; i < nArg; i++) + { + string strArg = pcArgv[i]; + strArg = strTrim(&strArg); + +#ifdef _WIN32 + // Swap any forward slashes to backslashes + strArg = pstrChangeToBackslash(&strArg); +#endif + + if (strArg.find("--gdal") != string::npos) + { + // User wants to know what GDAL raster drivers are available + cout << GDAL_DRIVERS << endl + << endl; + + for (int j = 0; j < GDALGetDriverCount(); j++) + { + GDALDriverH hDriver = GDALGetDriver(j); + + string strTmp(GDALGetDriverShortName(hDriver)); + strTmp.append(" "); + strTmp.append(GDALGetDriverLongName(hDriver)); + + cout << strTmp << endl; + } + + return (RTN_HELP_ONLY); + } + + else + { + if (strArg.find("--about") != string::npos) + { + // User wants information about CoastalME + cout << ABOUT << endl; + cout << THANKS << endl; + + return (RTN_HELP_ONLY); + } + + else + { + if (strArg.find("--yaml") != string::npos) + { + // User wants to use YAML format for input datafile + m_bYamlInputFormat = true; + } + + else if (strArg.find("--home") != string::npos) + { + // Read in user defined runtime directory + // string strTmp; + + // Find the position of '=' + size_t const pos = strArg.find('='); + + // Was '=' found? + if (pos != string::npos) + { + // Yes, so get the substring after '=' and assign it to the global variable + m_strCMEIni = strArg.substr(pos + 1); + } + + else + { + // No + cout << "No '=' found in the input string" << endl; + } + + return (RTN_OK); + } + + // TODO 049 Handle other command line parameters e.g. path to .ini file, path to datafile + else + { + // Display usage information + cout << USAGE << endl; + cout << USAGE1 << endl; + cout << USAGE2 << endl; + cout << USAGE3 << endl; + cout << USAGE4 << endl; + cout << USAGE5 << endl; + cout << USAGE6 << endl; + + return (RTN_HELP_ONLY); + } + } + } + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! Tells the user that we have started the simulation +//=============================================================================================================================== +void CSimulation::AnnounceStart(void) +{ + cout << endl + << PROGRAM_NAME << " for " << PLATFORM << " " << strGetBuild() << endl; + #ifdef _OPENMP + cout << "OpenMP is ENABLED" << endl; + cout << "Max threads available: " << omp_get_max_threads() << endl; + #pragma omp parallel + { + #pragma omp single + std::cout << "Actually running with " << omp_get_num_threads() << " threads" << std::endl; + } + #else + std::cout << "OpenMP is NOT ENABLED - code will run serially!" << std::endl; + #endif // !_OPENMP +} + +//=============================================================================================================================== +//! Starts the clock ticking +//=============================================================================================================================== +void CSimulation::StartClock(void) +{ + // First start the 'CPU time' clock ticking + if (static_cast(-1) == clock()) + { + // There's a problem with the clock, but continue anyway + LogStream << NOTE << "CPU time not available" << endl; + m_dCPUClock = -1; + } + + else + { + // All OK, so get the time in m_dClkLast (this is needed to check for clock rollover on long runs) + m_dClkLast = static_cast(clock()); + m_dClkLast -= CLOCK_T_MIN; // necessary if clock_t is signed to make m_dClkLast unsigned + } + + // And now get the actual time we started + m_tSysStartTime = time(nullptr); +} + +//=============================================================================================================================== +//! Finds the folder (directory) in which the CoastalME executable is located +//=============================================================================================================================== +bool CSimulation::bFindExeDir(char const* pcArg) +{ + string strTmp; + char szBuf[BUF_SIZE] = ""; + +#ifdef _WIN32 + + if (0 != GetModuleFileName(NULL, szBuf, BUF_SIZE)) + strTmp = szBuf; + + else + // It failed, so try another approach + strTmp = pcArg; + +#else + // char* pResult = getcwd(szBuf, BUF_SIZE); // Used to use this, but what if cwd is not the same as the CoastalME dir? + + if (-1 != readlink("/proc/self/exe", szBuf, BUF_SIZE)) + strTmp = szBuf; + + else + // It failed, so try another approach + strTmp = pcArg; + +#endif + + // Neither approach has worked, so give up + if (strTmp.empty()) + return false; + + // It's OK, so trim off the executable's name + int const nPos = static_cast(strTmp.find_last_of(PATH_SEPARATOR)); + m_strCMEDir = strTmp.substr(0, nPos + 1); // Note that this must be terminated with a backslash + + return true; +} +//=============================================================================================================================== +//! Tells the user about the licence +//=============================================================================================================================== +void CSimulation::AnnounceLicence(void) +{ + cout << COPYRIGHT << endl + << endl; + cout << LINE << endl; + cout << DISCLAIMER1 << endl; + cout << DISCLAIMER2 << endl; + cout << DISCLAIMER3 << endl; + cout << DISCLAIMER4 << endl; + cout << DISCLAIMER5 << endl; + cout << DISCLAIMER6 << endl; + cout << LINE << endl + << endl; + + cout << START_NOTICE << strGetComputerName() << " at " << put_time(localtime(&m_tSysStartTime), "%T on %A %d %B %Y") << endl; + cout << INITIALIZING_NOTICE << endl; +} + +//=============================================================================================================================== +//! Given a string containing time units, this returns the appropriate multiplier +//=============================================================================================================================== +double CSimulation::dGetTimeMultiplier(string const* strIn) +{ + // First decide what the time units are + int const nTimeUnits = nDoTimeUnits(strIn); + + // Then return the correct multiplier, since m_dTimeStep is in hours + switch (nTimeUnits) + { + case TIME_UNKNOWN: + return TIME_UNKNOWN; + break; + + case TIME_HOURS: + return 1; // Multiplier for hours + break; + + case TIME_DAYS: + return 24; // Multiplier for days -> hours + break; + + case TIME_MONTHS: + return 24 * 30.416667; // Multiplier for months -> hours (assume 30 + 5/12 day months, no leap years) + break; + + case TIME_YEARS: + return 24 * 365.25; // Multiplier for years -> hours + break; + } + + return 0; +} + +//=============================================================================================================================== +//! Given a string containing time units, this sets up the appropriate multiplier and display units for the simulation +//=============================================================================================================================== +int CSimulation::nDoSimulationTimeMultiplier(string const* strIn) +{ + // First decide what the time units are + int const nTimeUnits = nDoTimeUnits(strIn); + + // Next set up the correct multiplier, since m_dTimeStep is in hours + switch (nTimeUnits) + { + case TIME_UNKNOWN: + return RTN_ERR_TIME_UNITS; + break; + + case TIME_HOURS: + m_dDurationUnitsMult = 1; // Multiplier for hours + m_strDurationUnits = "hours"; + break; + + case TIME_DAYS: + m_dDurationUnitsMult = 24; // Multiplier for days -> hours + m_strDurationUnits = "days"; + break; + + case TIME_MONTHS: + m_dDurationUnitsMult = 24 * 30.416667; // Multiplier for months -> hours (assume 30 + 5/12 day months, no leap years) + m_strDurationUnits = "months"; + break; + + case TIME_YEARS: + m_dDurationUnitsMult = 24 * 365.25; // Multiplier for years -> hours + m_strDurationUnits = "years"; + break; + } + + return RTN_OK; +} + +//=============================================================================================================================== +//! This finds time units in a string +//=============================================================================================================================== +int CSimulation::nDoTimeUnits(string const* strIn) +{ + if (strIn->find("hour") != string::npos) + return TIME_HOURS; + + else if (strIn->find("day") != string::npos) + return TIME_DAYS; + + else if (strIn->find("month") != string::npos) + return TIME_MONTHS; + + else if (strIn->find("year") != string::npos) + return TIME_YEARS; + + else + return TIME_UNKNOWN; +} + +//=============================================================================================================================== +//! Opens the log file +//=============================================================================================================================== +bool CSimulation::bOpenLogFile(void) +{ + if (m_nLogFileDetail == 0) + { + LogStream.open("/dev/null", ios::out | ios::trunc); + cout << "Warning: log file is not writting" << endl; + } + + else + LogStream.open(m_strLogFile.c_str(), ios::out | ios::trunc); + + if (!LogStream) + { + // Error, cannot open log file + cerr << ERR << "cannot open " << m_strLogFile << " for output" << endl; + return false; + } + + return true; +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the DEM file +//=============================================================================================================================== +void CSimulation::AnnounceReadBasementDEM(void) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_BASEMENT << pstrChangeToForwardSlash(&m_strInitialBasementDEMFile) << endl; +#else + cout << READING_BASEMENT << m_strInitialBasementDEMFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now allocating memory +//=============================================================================================================================== +void CSimulation::AnnounceAllocateMemory(void) +{ + cout << ALLOCATE_MEMORY << endl; +} + +//=============================================================================================================================== +//! Tells the user that we are now adding layers +//=============================================================================================================================== +void CSimulation::AnnounceAddLayers(void) +{ + // Tell the user what is happening + cout << ADD_LAYERS << endl; +} + +//=============================================================================================================================== +//! Now reading raster GIS files +//=============================================================================================================================== +void CSimulation::AnnounceReadRasterFiles(void) +{ + cout << READING_RASTER_FILES << endl; +} + +//=============================================================================================================================== +//! Now reading vector GIS files +//=============================================================================================================================== +void CSimulation::AnnounceReadVectorFiles(void) +{ + cout << READING_VECTOR_FILES << endl; +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the Landscape category GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadLGIS(void) const +{ + // Tell the user what is happening + if (! m_strInitialLandformFile.empty()) +#ifdef _WIN32 + cout << READING_LANDFORM_FILE << pstrChangeToForwardSlash(&m_strInitialLandformFile) << endl; + +#else + cout << READING_LANDFORM_FILE << m_strInitialLandformFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the Intervention class GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadICGIS(void) const +{ + // Tell the user what is happening + if (! m_strInterventionClassFile.empty()) +#ifdef _WIN32 + cout << READING_INTERVENTION_CLASS_FILE << pstrChangeToForwardSlash(&m_strInterventionClassFile) << endl; + +#else + cout << READING_INTERVENTION_CLASS_FILE << m_strInterventionClassFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the Intervention height GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadIHGIS(void) const +{ + // Tell the user what is happening + if (! m_strInterventionHeightFile.empty()) +#ifdef _WIN32 + cout << READING_INTERVENTION_HEIGHT_FILE << pstrChangeToForwardSlash(&m_strInterventionHeightFile) << endl; + +#else + cout << READING_INTERVENTION_HEIGHT_FILE << m_strInterventionHeightFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the deep water wave values GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadDeepWaterWaveValuesGIS(void) const +{ + // Tell the user what is happening + if (! m_strDeepWaterWavesInputFile.empty()) +#ifdef _WIN32 + cout << READING_DEEP_WATER_WAVE_FILE << pstrChangeToForwardSlash(&m_strDeepWaterWavesInputFile) << endl; + +#else + cout << READING_DEEP_WATER_WAVE_FILE << m_strDeepWaterWavesInputFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the sediment input events GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadSedimentEventInputValuesGIS(void) const +{ + // Tell the user what is happening + if (! m_strSedimentInputEventFile.empty()) +#ifdef _WIN32 + cout << READING_SED_INPUT_EVENT_FILE << pstrChangeToForwardSlash(&m_strSedimentInputEventFile) << endl; + +#else + cout << READING_SED_INPUT_EVENT_FILE << m_strSedimentInputEventFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the flood location GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadFloodLocationGIS(void) const +{ + // Tell the user what is happening + if (! m_strFloodLocationShapefile.empty()) +#ifdef _WIN32 + cout << READING_FLOOD_LOCATION << pstrChangeToForwardSlash(&m_strFloodLocationShapefile) << endl; + +#else + cout << READING_FLOOD_LOCATION << m_strFloodLocationShapefile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial suspended sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialSuspSedGIS(void) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_SUSPENDED_SEDIMENT_FILE << pstrChangeToForwardSlash(&m_strInitialSuspSedimentFile) << endl; +#else + cout << READING_SUSPENDED_SEDIMENT_FILE << m_strInitialSuspSedimentFile << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial fine unconsolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialFineUnconsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_UNCONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialFineUnconsSedimentFile[nLayer]) << endl; +#else + cout << READING_UNCONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialFineUnconsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial sand unconsolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialSandUnconsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_UNCONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialSandUnconsSedimentFile[nLayer]) << endl; +#else + cout << READING_UNCONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialSandUnconsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial coarse unconsolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialCoarseUnconsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_UNCONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialCoarseUnconsSedimentFile[nLayer]) << endl; +#else + cout << READING_UNCONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialCoarseUnconsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial fine consolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialFineConsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_CONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialFineConsSedimentFile[nLayer]) << endl; +#else + cout << READING_CONS_FINE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialFineConsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial sand consolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialSandConsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_CONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialSandConsSedimentFile[nLayer]) << endl; +#else + cout << READING_CONS_SAND_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialSandConsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Tells the user that we are now reading the initial coarse consolidated sediment depth GIS file +//=============================================================================================================================== +void CSimulation::AnnounceReadInitialCoarseConsSedGIS(int const nLayer) const +{ + // Tell the user what is happening +#ifdef _WIN32 + cout << READING_CONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << pstrChangeToForwardSlash(&m_VstrInitialCoarseConsSedimentFile[nLayer]) << endl; +#else + cout << READING_CONS_COARSE_SEDIMENT_FILE << nLayer + 1 << "): " << m_VstrInitialCoarseConsSedimentFile[nLayer] << endl; +#endif +} + +//=============================================================================================================================== +//! Now reading tide data file +//=============================================================================================================================== +void CSimulation::AnnounceReadTideData(void) const +{ +#ifdef _WIN32 + cout << READING_TIDE_DATA_FILE << pstrChangeToForwardSlash(&m_strTideDataFile) << endl; +#else + cout << READING_TIDE_DATA_FILE << m_strTideDataFile << endl; +#endif +} + +//=============================================================================================================================== +//! Now reading the SCAPE shape function file +//=============================================================================================================================== +void CSimulation::AnnounceReadSCAPEShapeFunctionFile(void) +{ + cout << READING_SCAPE_SHAPE_FUNCTION_FILE << endl; +} + +//=============================================================================================================================== +//! Tells the user that we are now initializing +//=============================================================================================================================== +void CSimulation::AnnounceFinalInitialization(void) +{ + // Tell the user what is happening + cout << INITIALIZING_FINAL << endl; +} + +//=============================================================================================================================== +//! Tell the user that the simulation is now running +//=============================================================================================================================== +void CSimulation::AnnounceIsRunning(void) +{ + cout << RUN_NOTICE << endl; +} + +//=============================================================================================================================== +//! Return a space-separated string containing the names of the raster GIS output files +//=============================================================================================================================== +string CSimulation::strListRasterFiles(void) const +{ + string strTmp; + + if (m_bBasementElevSave) + { + strTmp.append(RASTER_BASEMENT_ELEVATION_CODE); + strTmp.append(", "); + } + + if (m_bSedimentTopSurfSave) + { + strTmp.append(RASTER_SEDIMENT_TOP_CODE); + strTmp.append(", "); + } + + if (m_bTopSurfSave) + { + strTmp.append(RASTER_TOP_CODE); + strTmp.append(", "); + } + + if (m_bSeaDepthSave) + { + strTmp.append(RASTER_SEA_DEPTH_NAME); + strTmp.append(", "); + } + + if (m_bAvgSeaDepthSave) + { + strTmp.append(RASTER_AVG_SEA_DEPTH_CODE); + strTmp.append(", "); + } + + if (m_bSeaMaskSave) + { + strTmp.append(RASTER_INUNDATION_MASK_CODE); + strTmp.append(", "); + } + + if (m_bWaveHeightSave) + { + strTmp.append(RASTER_WAVE_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bWaveAngleSave) + { + strTmp.append(RASTER_WAVE_ORIENTATION_CODE); + strTmp.append(", "); + } + + if (m_bAvgWaveHeightSave) + { + strTmp.append(RASTER_AVG_WAVE_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bBeachProtectionSave) + { + strTmp.append(RASTER_BEACH_PROTECTION_CODE); + strTmp.append(", "); + } + + if (m_bPotentialPlatformErosionSave) + { + strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bPotentialPlatformErosionMaskSave) + { + strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_CODE); + strTmp.append(", "); + } + + if (m_bBeachDepositionSave) + { + strTmp.append(RASTER_BEACH_DEPOSITION_CODE); + strTmp.append(", "); + } + + if (m_bTotalBeachDepositionSave) + { + strTmp.append(RASTER_TOTAL_BEACH_DEPOSITION_CODE); + strTmp.append(", "); + } + + if (m_bBeachMaskSave) + { + strTmp.append(RASTER_BEACH_MASK_CODE); + strTmp.append(", "); + } + + if (m_bActualPlatformErosionSave) + { + strTmp.append(RASTER_ACTUAL_PLATFORM_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bTotalPotentialPlatformErosionSave) + { + strTmp.append(RASTER_TOTAL_POTENTIAL_PLATFORM_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bTotalActualPlatformErosionSave) + { + strTmp.append(RASTER_TOTAL_ACTUAL_PLATFORM_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bPotentialBeachErosionSave) + { + strTmp.append(RASTER_POTENTIAL_BEACH_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bActualBeachErosionSave) + { + strTmp.append(RASTER_ACTUAL_BEACH_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bTotalPotentialBeachErosionSave) + { + strTmp.append(RASTER_TOTAL_POTENTIAL_BEACH_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bTotalActualBeachErosionSave) + { + strTmp.append(RASTER_TOTAL_ACTUAL_BEACH_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bLandformSave) + { + strTmp.append(RASTER_LANDFORM_CODE); + strTmp.append(", "); + } + + if (m_bSlopeConsSedSave) + { + strTmp.append(RASTER_SLOPE_OF_CONSOLIDATED_SEDIMENT_CODE); + strTmp.append(", "); + } + + if (m_bInterventionClassSave) + { + strTmp.append(RASTER_INTERVENTION_CLASS_CODE); + strTmp.append(", "); + } + + if (m_bInterventionHeightSave) + { + strTmp.append(RASTER_INTERVENTION_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bHaveFineSediment && m_bSuspSedSave) + { + strTmp.append(RASTER_SUSP_SED_CODE); + strTmp.append(", "); + } + + if (m_bHaveFineSediment && m_bAvgSuspSedSave) + { + strTmp.append(RASTER_AVG_SUSP_SED_CODE); + strTmp.append(", "); + } + + if (m_bHaveFineSediment && m_bFineUnconsSedSave) + { + strTmp.append(RASTER_FINE_UNCONS_CODE); + strTmp.append(", "); + } + + if (m_bHaveSandSediment && m_bSandUnconsSedSave) + { + strTmp.append(RASTER_SAND_UNCONS_CODE); + strTmp.append(", "); + } + + if (m_bHaveCoarseSediment && m_bCoarseUnconsSedSave) + { + strTmp.append(RASTER_COARSE_UNCONS_CODE); + strTmp.append(", "); + } + + if (m_bHaveFineSediment && m_bFineConsSedSave) + { + strTmp.append(RASTER_FINE_CONS_CODE); + strTmp.append(", "); + } + + if (m_bHaveSandSediment && m_bSandConsSedSave) + { + strTmp.append(RASTER_SAND_CONS_CODE); + strTmp.append(", "); + } + + if (m_bHaveCoarseSediment && m_bCoarseConsSedSave) + { + strTmp.append(RASTER_COARSE_CONS_CODE); + strTmp.append(", "); + } + + if (m_bRasterCoastlineSave) + { + strTmp.append(RASTER_COAST_CODE); + strTmp.append(", "); + } + + if (m_bRasterNormalProfileSave) + { + strTmp.append(RASTER_COAST_NORMAL_CODE); + strTmp.append(", "); + } + + if (m_bActiveZoneSave) + { + strTmp.append(RASTER_ACTIVE_ZONE_CODE); + strTmp.append(", "); + } + + if (m_bRasterPolygonSave) + { + strTmp.append(RASTER_POLYGON_CODE); + strTmp.append(", "); + } + + if (m_bPotentialPlatformErosionMaskSave) + { + strTmp.append(RASTER_POTENTIAL_PLATFORM_EROSION_MASK_CODE); + strTmp.append(", "); + } + + if (m_bSedimentInputEventSave) + { + strTmp.append(RASTER_SEDIMENT_INPUT_EVENT_CODE); + strTmp.append(", "); + } + + // Remove the trailing comma and space + if (strTmp.size() > 2) + strTmp.resize(strTmp.size() - 2); + + return strTmp; +} + +//=============================================================================================================================== +//! Return a space-separated string containing the names of the vector GIS output files +//=============================================================================================================================== +string CSimulation::strListVectorFiles(void) const +{ + string strTmp; + + if (m_bCoastSave) + { + strTmp.append(VECTOR_COAST_CODE); + strTmp.append(", "); + } + + if (m_bNormalsSave) + { + strTmp.append(VECTOR_NORMALS_CODE); + strTmp.append(", "); + } + + if (m_bInvalidNormalsSave) + { + strTmp.append(VECTOR_INVALID_NORMALS_CODE); + strTmp.append(", "); + } + + if (m_bWaveAngleAndHeightSave) + { + strTmp.append(VECTOR_WAVE_ANGLE_AND_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bAvgWaveAngleAndHeightSave) + { + strTmp.append(VECTOR_AVG_WAVE_ANGLE_AND_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bCoastCurvatureSave) + { + strTmp.append(VECTOR_COAST_CURVATURE_CODE); + strTmp.append(", "); + } + + if (m_bWaveEnergySinceCollapseSave) + { + strTmp.append(VECTOR_WAVE_ENERGY_SINCE_COLLAPSE_CODE); + strTmp.append(", "); + } + + if (m_bMeanWaveEnergySave) + { + strTmp.append(VECTOR_MEAN_WAVE_ENERGY_CODE); + strTmp.append(", "); + } + + if (m_bBreakingWaveHeightSave) + { + strTmp.append(VECTOR_BREAKING_WAVE_HEIGHT_CODE); + strTmp.append(", "); + } + + if (m_bPolygonNodeSave) + { + strTmp.append(VECTOR_POLYGON_NODE_CODE); + strTmp.append(", "); + } + + if (m_bPolygonBoundarySave) + { + strTmp.append(VECTOR_POLYGON_BOUNDARY_CODE); + strTmp.append(", "); + } + + if (m_bCliffNotchSave) + { + strTmp.append(VECTOR_CLIFF_NOTCH_ACTIVE_CODE); + strTmp.append(", "); + } + + if (m_bShadowBoundarySave) + { + strTmp.append(VECTOR_SHADOW_ZONE_BOUNDARY_CODE); + strTmp.append(", "); + } + + if (m_bShadowDowndriftBoundarySave) + { + strTmp.append(VECTOR_DOWNDRIFT_ZONE_BOUNDARY_CODE); + strTmp.append(", "); + } + + if (m_bDeepWaterWaveAngleAndHeightSave) + { + strTmp.append(VECTOR_DEEP_WATER_WAVE_ANGLE_AND_HEIGHT_CODE); + strTmp.append(", "); + } + + // Remove the trailing comma and space + if (strTmp.size() > 2) + strTmp.resize(strTmp.size() - 2); + + return strTmp; +} + +//=============================================================================================================================== +//! Return a space-separated string containing the names of the time series output files +//=============================================================================================================================== +string CSimulation::strListTSFiles(void) const +{ + string strTmp; + + if (m_bSeaAreaTSSave) + { + strTmp.append(TIME_SERIES_SEA_AREA_CODE); + strTmp.append(", "); + } + + if (m_bSWLTSSave) + { + strTmp.append(TIME_SERIES_SWL_CODE); + strTmp.append(", "); + } + + if (m_bActualPlatformErosionTSSave) + { + strTmp.append(TIME_SERIES_PLATFORM_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bCliffCollapseErosionTSSave) + { + strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bCliffCollapseDepositionTSSave) + { + strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_DEPOSITION_CODE); + strTmp.append(", "); + } + + if (m_bCliffCollapseNetTSSave) + { + strTmp.append(TIME_SERIES_CLIFF_COLLAPSE_NET_CODE); + strTmp.append(", "); + } + + if (m_bBeachErosionTSSave) + { + strTmp.append(TIME_SERIES_BEACH_EROSION_CODE); + strTmp.append(", "); + } + + if (m_bBeachDepositionTSSave) + { + strTmp.append(TIME_SERIES_BEACH_DEPOSITION_CODE); + strTmp.append(", "); + } + + if (m_bBeachSedimentChangeNetTSSave) + { + strTmp.append(TIME_SERIES_BEACH_CHANGE_NET_CODE); + strTmp.append(", "); + } + + if (m_bSuspSedTSSave) + { + strTmp.append(TIME_SERIES_SUSPENDED_SEDIMENT_CODE); + strTmp.append(", "); + } + + if (m_bFloodSetupSurgeTSSave) + { + strTmp.append(TIME_SERIES_FLOOD_SETUP_SURGE_CODE); + strTmp.append(", "); + } + + if (m_bFloodSetupSurgeRunupTSSave) + { + strTmp.append(TIME_SERIES_FLOOD_SETUP_SURGE_RUNUP_CODE); + strTmp.append(", "); + } + + if (m_bCliffNotchElevTSSave) + { + strTmp.append(TIME_SERIES_CLIFF_NOTCH_ELEV_CODE); + strTmp.append(", "); + } + + // Remove the trailing comma and space + if (strTmp.size() > 2) + strTmp.resize(strTmp.size() - 2); + + return strTmp; +} + +//=============================================================================================================================== +//! This member function intialises the time series files +//=============================================================================================================================== +bool CSimulation::bSetUpTSFiles(void) +{ + string strTSFile; + + if (m_bSeaAreaTSSave) + { + // Start with wetted area + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_SEA_AREA_NAME); + strTSFile.append(CSVEXT); + + // Open sea area time-series CSV file + SeaAreaTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! SeaAreaTSStream) + { + // Error, cannot open wetted area time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bSWLTSSave) + { + // Now SWL + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_SWL_NAME); + strTSFile.append(CSVEXT); + + // Open SWL time-series CSV file + SWLTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! SWLTSStream) + { + // Error, cannot open SWL time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bActualPlatformErosionTSSave) + { + // Erosion (fine, sand, coarse) + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_PLATFORM_EROSION_NAME); + strTSFile.append(CSVEXT); + + // Open erosion time-series CSV file + PlatformErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! PlatformErosionTSStream) + { + // Error, cannot open erosion time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bCliffCollapseErosionTSSave) + { + // Erosion due to cliff collapse + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_EROSION_NAME); + strTSFile.append(CSVEXT); + + // Open cliff collapse erosion time-series CSV file + CliffCollapseErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! CliffCollapseErosionTSStream) + { + // Error, cannot open cliff collapse erosion time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bCliffCollapseDepositionTSSave) + { + // Deposition due to cliff collapse + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_DEPOSITION_NAME); + strTSFile.append(CSVEXT); + + // Open cliff collapse deposition time-series CSV file + CliffCollapseDepositionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! CliffCollapseDepositionTSStream) + { + // Error, cannot open cliff collapse deposition time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bCliffCollapseNetTSSave) + { + // Net change in unconsolidated sediment due to cliff collapse + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_CLIFF_COLLAPSE_NET_NAME); + strTSFile.append(CSVEXT); + + // Open net cliff collapse time-series CSV file + CliffCollapseNetChangeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! CliffCollapseNetChangeTSStream) + { + // Error, cannot open net cliff collapse time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bBeachErosionTSSave) + { + // Beach erosion + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_BEACH_EROSION_NAME); + strTSFile.append(CSVEXT); + + // Open beach erosion time-series CSV file + BeachErosionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! BeachErosionTSStream) + { + // Error, cannot open beach erosion time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bBeachDepositionTSSave) + { + // Beach deposition + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_BEACH_DEPOSITION_NAME); + strTSFile.append(CSVEXT); + + // Open beach deposition time-series CSV file + BeachDepositionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! BeachDepositionTSStream) + { + // Error, cannot open beach deposition time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bBeachSedimentChangeNetTSSave) + { + // Beach sediment change + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_BEACH_CHANGE_NET_NAME); + strTSFile.append(CSVEXT); + + // Open net beach sediment change time-series CSV file + BeachSedimentNetChangeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! BeachSedimentNetChangeTSStream) + { + // Error, cannot open beach sediment change time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bSuspSedTSSave) + { + // Sediment load + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_SUSPENDED_SEDIMENT_NAME); + strTSFile.append(CSVEXT); + + // Open sediment load time-series CSV file + FineSedSuspensionTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! FineSedSuspensionTSStream) + { + // Error, cannot open sediment load time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bFloodSetupSurgeTSSave) + { + // Sediment load + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_FLOOD_SETUP_SURGE_CODE); + strTSFile.append(CSVEXT); + + // Open sediment load time-series CSV file + FloodSetupSurgeTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! FloodSetupSurgeTSStream) + { + // Error, cannot open sediment load time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bFloodSetupSurgeRunupTSSave) + { + // Sediment load + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_FLOOD_SETUP_SURGE_RUNUP_CODE); + strTSFile.append(CSVEXT); + + // Open sediment load time-series CSV file + FloodSetupSurgeRunupTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! FloodSetupSurgeRunupTSStream) + { + // Error, cannot open sediment load time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + if (m_bCliffNotchElevTSSave) + { + // Elevation of cliff notch + strTSFile = m_strOutPath; + strTSFile.append(TIME_SERIES_CLIFF_NOTCH_ELEV_NAME); + strTSFile.append(CSVEXT); + + // Open cliff notch elevation time-series CSV file + CliffNotchElevTSStream.open(strTSFile.c_str(), ios::out | ios::trunc); + if (! CliffNotchElevTSStream) + { + // Error, cannot open cliff notch elevation time-series file + cerr << ERR << "cannot open " << strTSFile << " for output" << endl; + return false; + } + } + + return true; +} + +//=============================================================================================================================== +//! Checks to see if the simulation has gone on too long, amongst other things +//=============================================================================================================================== +bool CSimulation::bTimeToQuit(void) +{ + // Add timestep to the total time simulated so far + m_dSimElapsed += m_dTimeStep; + + if (m_dSimElapsed >= m_dSimDuration) + { + // It is time to quit + m_dSimElapsed = m_dSimDuration; + AnnounceProgress(); + return true; + } + + // Not quitting, so increment the timestep count, and recalc total timesteps + m_ulIter++; + m_ulTotTimestep = static_cast(dRound(m_dSimDuration / m_dTimeStep)); + + // Check to see if we have done CLOCK_CHECK_ITERATION timesteps: if so, it is time to reset the CPU time running total in case the clock() function later rolls over + if (0 == m_ulIter % CLOCK_CHECK_ITERATION) + DoCPUClockReset(); + + // Not yet time to quit + return false; +} + +//=============================================================================================================================== +//! Returns a string, hopefully giving the name of the computer on which the simulation is running +//=============================================================================================================================== +string CSimulation::strGetComputerName(void) +{ + string strComputerName; + +#ifdef _WIN32 + // Being compiled to run under Windows, either by MS VC++, Borland C++, or Cygwin + strComputerName = getenv("COMPUTERNAME"); +#else + // Being compiled for another platform; assume for Linux-Unix + char szHostName[BUF_SIZE] = ""; + gethostname(szHostName, BUF_SIZE); + + strComputerName = szHostName; + + if (strComputerName.empty()) + strComputerName = "Unknown Computer"; + +#endif + + return strComputerName; +} + +//=============================================================================================================================== +//! Resets the CPU clock timer to prevent it 'rolling over', as can happen during long runs. This is a particularly problem under Unix systems where the value returned by clock() is defined in microseconds (for compatibility with systems that have CPU clocks with much higher resolution) i.e. CLOCKS_PER_SEC is 1000000 rather than the more usual 1000. In this case, the value returned from clock() will wrap around after accumulating only 2147 seconds of CPU time (about 36 minutes). +//=============================================================================================================================== +void CSimulation::DoCPUClockReset(void) +{ + if (static_cast(-1) == clock()) + { + // Error + LogStream << "CPU time not available" << endl; + m_dCPUClock = -1; + return; + } + + // OK, so carry on + double dClkThis = static_cast(clock()); + dClkThis -= CLOCK_T_MIN; // necessary when clock_t is signed, to make dClkThis unsigned + + if (dClkThis < m_dClkLast) + { + // Clock has 'rolled over' + m_dCPUClock += (CLOCK_T_RANGE + 1 - m_dClkLast); // this elapsed before rollover + m_dCPUClock += dClkThis; // this elapsed after rollover + +#ifdef CLOCKCHECK + // For debug purposes + LogStream << "Rolled over: dClkThis=" << dClkThis << " m_dClkLast=" << m_dClkLast << endl + << "\t" + << " before rollover=" << (CLOCK_T_RANGE + 1 - m_dClkLast) << endl + << "\t" + << " after rollover=" << dClkThis << endl + << "\t" + << " ADDED=" << (CLOCK_T_RANGE + 1 - m_dClkLast + dClkThis) << endl; +#endif + } + + else + { + // No rollover + m_dCPUClock += (dClkThis - m_dClkLast); + +#ifdef CLOCKCHECK + // For debug purposes + LogStream << "No rollover: dClkThis=" << dClkThis << " m_dClkLast=" << m_dClkLast << " ADDED=" << dClkThis - m_dClkLast << endl; +#endif + } + + // Reset for next time + m_dClkLast = dClkThis; +} + +//=============================================================================================================================== +//! Announce the end of the simulation +//=============================================================================================================================== +void CSimulation::AnnounceSimEnd(void) +{ + cout << endl << FINAL_OUTPUT << endl; +} + +// //=============================================================================================================================== +// //! Calculates and displays time elapsed in terms of CPU time and real time, also calculates time per timestep in terms of both CPU time and real time +// //=============================================================================================================================== +// void CSimulation::CalcTime(double const dRunLength) +// { +// // Reset CPU count for last time +// DoCPUClockReset(); +// +// if (! bFPIsEqual(m_dCPUClock, -1.0, TOLERANCE)) +// { +// // Calculate CPU time in secs +// double const dDuration = m_dCPUClock / CLOCKS_PER_SEC; +// +// // And write CPU time out to OutStream and LogStream +// OutStream << "CPU time elapsed: " << strDispTime(dDuration, false, true); +// LogStream << "CPU time elapsed: " << strDispTime(dDuration, false, true); +// +// // Calculate CPU time per timestep +// double const dPerTimestep = dDuration / static_cast(m_ulTotTimestep); +// +// // And write CPU time per timestep to OutStream and LogStream +// OutStream << fixed << setprecision(4) << " (" << dPerTimestep << " per timestep)" << endl; +// LogStream << fixed << setprecision(4) << " (" << dPerTimestep << " per timestep)" << endl; +// +// // Calculate ratio of CPU time to time simulated +// OutStream << resetiosflags(ios::floatfield); +// OutStream << fixed << setprecision(0) << "In terms of CPU time, this is "; +// LogStream << resetiosflags(ios::floatfield); +// LogStream << fixed << setprecision(0) << "In terms of CPU time, this is "; +// +// if (dDuration > dRunLength) +// { +// OutStream << dDuration / dRunLength << " x slower than reality" << endl; +// LogStream << dDuration / dRunLength << " x slower than reality" << endl; +// } +// +// else +// { +// OutStream << dRunLength / dDuration << " x faster than reality" << endl; +// LogStream << dRunLength / dDuration << " x faster than reality" << endl; +// } +// } +// +// // Calculate run time +// double const dDuration = difftime(m_tSysEndTime, m_tSysStartTime); +// +// // And write run time out to OutStream and LogStream +// OutStream << "Run time elapsed: " << strDispTime(dDuration, false, false); +// LogStream << "Run time elapsed: " << strDispTime(dDuration, false, false); +// +// // Calculate run time per timestep +// double const dPerTimestep = dDuration / static_cast(m_ulTotTimestep); +// +// // And write run time per timestep to OutStream and LogStream +// OutStream << resetiosflags(ios::floatfield); +// OutStream << " (" << fixed << setprecision(4) << dPerTimestep << " per timestep)" << endl; +// LogStream << resetiosflags(ios::floatfield); +// LogStream << " (" << fixed << setprecision(4) << dPerTimestep << " per timestep)" << endl; +// +// // Calculate ratio of run time to time simulated +// OutStream << fixed << setprecision(0) << "In terms of run time, this is "; +// LogStream << fixed << setprecision(0) << "In terms of run time, this is "; +// +// if (dDuration > dRunLength) +// { +// OutStream << dDuration / dRunLength << " x slower than reality" << endl; +// LogStream << dDuration / dRunLength << " x slower than reality" << endl; +// } +// +// else +// { +// OutStream << dRunLength / dDuration << " x faster than reality" << endl; +// LogStream << dRunLength / dDuration << " x faster than reality" << endl; +// } +// } + +//=============================================================================================================================== +//! strDispSimTime returns a string formatted as year Julian_day hour, given a parameter in hours +//=============================================================================================================================== +string CSimulation::strDispSimTime(const double dTimeIn) +{ + // Make sure no negative times + double dTmpTime = tMax(dTimeIn, 0.0); + + string strTime; + + // Constants + double const dHoursInYear = 24 * 365; // it was 365.25 + double const dHoursInDay = 24; + + // Display years + if (dTmpTime >= dHoursInYear) + { + double const dYears = floor(dTmpTime / dHoursInYear); + dTmpTime -= (dYears * dHoursInYear); + + strTime = to_string(static_cast(dYears)); + strTime.append("y "); + } + else + strTime = "0y "; + + // Display Julian days + if (dTmpTime >= dHoursInDay) + { + double const dJDays = floor(dTmpTime / dHoursInDay); + dTmpTime -= (dJDays * dHoursInDay); + + stringstream ststrTmp; + ststrTmp << FillToWidth('0', 3) << static_cast(dJDays); + strTime.append(ststrTmp.str()); + strTime.append("d "); + } + else + strTime.append("000d "); + + // Display hours + stringstream ststrTmp; + ststrTmp << FillToWidth('0', 2) << static_cast(dTmpTime); + strTime.append(ststrTmp.str()); + strTime.append("h"); + + return strTime; +} + +//=============================================================================================================================== +//! strDispTime returns a string formatted as h:mm:ss, given a parameter in seconds, with rounding and fractions of a second if desired +//=============================================================================================================================== +string CSimulation::strDispTime(const double dTimeIn, const bool bRound, const bool bFrac) +{ + // Make sure no negative times + double dTime = tMax(dTimeIn, 0.0); + + string strTime; + + if (bRound) + dTime = dRound(dTime); + + unsigned long ulTimeIn = static_cast(floor(dTime)); + dTime -= static_cast(ulTimeIn); + + // Hours + if (ulTimeIn >= 3600) + { + // Display some hours + unsigned long const ulHours = ulTimeIn / 3600ul; + ulTimeIn -= (ulHours * 3600ul); + + strTime = to_string(ulHours); + strTime.append(":"); + } + else + strTime = "0:"; + + // Minutes + if (ulTimeIn >= 60) + { + // display some minutes + unsigned long const ulMins = ulTimeIn / 60ul; + ulTimeIn -= (ulMins * 60ul); + + stringstream ststrTmp; + ststrTmp << FillToWidth('0', 2) << ulMins; + strTime.append(ststrTmp.str()); + strTime.append(":"); + } + else + strTime.append("00:"); + + // Seconds + stringstream ststrTmp; + ststrTmp << FillToWidth('0', 2) << ulTimeIn; + strTime.append(ststrTmp.str()); + + if (bFrac) + { + // Fractions of a second + strTime.append("."); + ststrTmp.clear(); + ststrTmp.str(string()); + ststrTmp << FillToWidth('0', 2) << static_cast(dTime * 100); + strTime.append(ststrTmp.str()); + } + + return strTime; +} + +//=============================================================================================================================== +//! Returns the date and time on which the program was compiled +//=============================================================================================================================== +string CSimulation::strGetBuild(void) +{ + string strBuild("("); + strBuild.append(__TIME__); + strBuild.append(" "); + strBuild.append(__DATE__); +#ifdef _DEBUG + strBuild.append(" DEBUG"); +#endif + strBuild.append(" build)"); + + return strBuild; +} + +//=============================================================================================================================== +//! Displays information regarding the progress of the simulation +//=============================================================================================================================== +void CSimulation::AnnounceProgress(void) +{ + if (isatty(fileno(stdout))) + { + // Stdout is connected to a tty, so not running as a background job + static double sdElapsed = 0; + static double sdToGo = 0; + static double sdExpectedRuntime = 0; + time_t const tNow = time(nullptr); + + // Calculate time elapsed and remaining + sdElapsed = difftime(tNow, m_tSysStartTime); + sdExpectedRuntime = (sdElapsed * m_dSimDuration / m_dSimElapsed); + sdToGo = sdExpectedRuntime - sdElapsed; + + // Tell the user about progress (note need to make several separate calls to cout here, or MS VC++ compiler appears to get confused) + cout << SIMULATING << strDispSimTime(m_dSimElapsed); + cout << fixed << setprecision(3) << setw(9) << 100 * m_dSimElapsed / m_dSimDuration; + cout << "% (elapsed " << strDispTime(sdElapsed, false, false) << " remaining "; + + cout << strDispTime(sdToGo, false, false) << " total expected "; + + cout << strDispTime(sdExpectedRuntime, false, false) << ") "; + + // Add a 'marker' for GIS saves etc. + if (m_bSaveGISThisIter) + cout << setw(9) << "GIS" + to_string(m_nGISSave); + else if (m_bSedimentInputThisIter) + cout << setw(9) << "SED INPUT"; + else + cout << setw(9) << SPACE; + + cout.flush(); + } +} + +//=============================================================================================================================== +//! This calculates and displays process statistics +//=============================================================================================================================== +void CSimulation::CalcProcessStats(void) +{ + string const NA = "Not available"; + + OutStream << endl; + OutStream << "Process statistics" << endl; + OutStream << "------------------" << endl; + +#ifdef _WIN32 + // First, find out which version of Windows we are running under + OSVERSIONINFOEX osvi; + BOOL bOsVersionInfoEx; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); // fill this much memory with zeros + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + if (!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*)&osvi))) + { + // OSVERSIONINFOEX didn't work so try OSVERSIONINFO instead + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + if (!GetVersionEx((OSVERSIONINFO*)&osvi)) + { + // That didn't work either, too risky to proceed so give up + OutStream << NA << endl; + return; + } + } + + // OK, we have Windows version so display it + OutStream << "Running under \t: "; + + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + if (osvi.dwMajorVersion <= 4) + OutStream << "Windows NT "; + + else if (5 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) + OutStream << "Windows 2000 "; + + else if (5 == osvi.dwMajorVersion && 1 == osvi.dwMinorVersion) + OutStream << "Windows XP "; + + else if (6 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) + OutStream << "Windows Vista "; + + else if (6 == osvi.dwMajorVersion && 1 == osvi.dwMinorVersion) + OutStream << "Windows 7 "; + + else if (6 == osvi.dwMajorVersion && 2 == osvi.dwMinorVersion) + OutStream << "Windows 8 "; + + else if (6 == osvi.dwMajorVersion && 3 == osvi.dwMinorVersion) + OutStream << "Windows 8.1 "; + + else if (10 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) + OutStream << "Windows 10 "; + + else if (11 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) + OutStream << "Windows 11 "; + + else + OutStream << "unknown Windows version "; + + // Display version, service pack (if any), and build number + if (osvi.dwMajorVersion <= 4) + OutStream << "version " << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << " " << osvi.szCSDVersion << " (Build " << (osvi.dwBuildNumber & 0xFFFF) << ")" << endl; + + else + OutStream << osvi.szCSDVersion << " (Build " << (osvi.dwBuildNumber & 0xFFFF) << ")" << endl; + + break; + + case VER_PLATFORM_WIN32_WINDOWS: + if (4 == osvi.dwMajorVersion && 0 == osvi.dwMinorVersion) + { + OutStream << "Windows 95"; + + if ('C' == osvi.szCSDVersion[1] || 'B' == osvi.szCSDVersion[1]) + OutStream << " OSR2"; + + OutStream << endl; + } + + else if (4 == osvi.dwMajorVersion && 10 == osvi.dwMinorVersion) + { + OutStream << "Windows 98"; + + if ('A' == osvi.szCSDVersion[1]) + OutStream << "SE"; + + OutStream << endl; + } + + else if (4 == osvi.dwMajorVersion && 90 == osvi.dwMinorVersion) + OutStream << "Windows Me" << endl; + + else + OutStream << "unknown 16-bit Windows version " << endl; + + break; + + case VER_PLATFORM_WIN32s: + OutStream << "Win32s" << endl; + break; + } + + // Now get process timimgs: this only works under 32-bit windows + if (VER_PLATFORM_WIN32_NT == osvi.dwPlatformId) + { + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (GetProcessTimes(GetCurrentProcess(), &ftCreate, &ftExit, &ftKernel, &ftUser)) + { + ULARGE_INTEGER ul; + ul.LowPart = ftUser.dwLowDateTime; + ul.HighPart = ftUser.dwHighDateTime; + OutStream << "Time spent executing user code \t: " << strDispTime(static_cast(ul.QuadPart) * 1e-7, false) << endl; + ul.LowPart = ftKernel.dwLowDateTime; + ul.HighPart = ftKernel.dwHighDateTime; + OutStream << "Time spent executing kernel code \t: " << strDispTime(static_cast(ul.QuadPart) * 1e-7, false) << endl; + } + } + + else + OutStream << "Process timings \t: " << NA << endl; + + // Finally get more process statistics: this needs psapi.dll, so only proceed if it is present on this system + HINSTANCE hDLL = LoadLibrary("psapi.dll"); + + if (hDLL != NULL) + { + // The dll has been found + typedef BOOL(__stdcall * DLLPROC)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD); + DLLPROC ProcAdd; + + // Try to get the address of the function we will call + ProcAdd = (DLLPROC)GetProcAddress(hDLL, "GetProcessMemoryInfo"); + + if (ProcAdd) + { + // Address was found + PROCESS_MEMORY_COUNTERS pmc; + + // Now call the function + if ((ProcAdd)(GetCurrentProcess(), &pmc, sizeof(pmc))) + { + OutStream << "Peak working set size \t: " << pmc.PeakWorkingSetSize / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Current working set size \t: " << pmc.WorkingSetSize / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Peak paged pool usage \t: " << pmc.QuotaPeakPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Current paged pool usage \t: " << pmc.QuotaPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Peak non-paged pool usage \t: " << pmc.QuotaPeakNonPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Current non-paged pool usage \t: " << pmc.QuotaNonPagedPoolUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Peak pagefile usage \t: " << pmc.PeakPagefileUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "Current pagefile usage \t: " << pmc.PagefileUsage / (1024.0 * 1024.0) << " Mb" << endl; + OutStream << "No. of page faults \t: " << pmc.PageFaultCount << endl; + } + } + + // Free the memory used by the dll + FreeLibrary(hDLL); + } + +#elif defined __GNUG__ + rusage ru; + + if (getrusage(RUSAGE_SELF, &ru) >= 0) + { + OutStream << "Time spent executing user code \t: " << strDispTime(static_cast(ru.ru_utime.tv_sec), false, true) << endl; + // OutStream << "ru_utime.tv_usec \t: " << ru.ru_utime.tv_usec << endl; + OutStream << "Time spent executing kernel code \t: " << strDispTime(static_cast(ru.ru_stime.tv_sec), false, true) << endl; + // OutStream << "ru_stime.tv_usec \t: " << ru.ru_stime.tv_usec << endl; + // OutStream << "Maximum resident set size \t: " << ru.ru_maxrss/1024.0 << " Mb" << endl; + // OutStream << "ixrss (???) \t: " << ru.ru_ixrss << endl; + // OutStream << "Sum of rm_asrss (???) \t: " << ru.ru_idrss << endl; + // OutStream << "isrss (???) \t: " << ru.ru_isrss << endl; + OutStream << "No. of page faults not requiring physical I/O\t: " << ru.ru_minflt << endl; + OutStream << "No. of page faults requiring physical I/O \t: " << ru.ru_majflt << endl; + // OutStream << "No. of times swapped out of main memory \t: " << ru.ru_nswap << endl; + // OutStream << "No. of times performed input (read request) \t: " << ru.ru_inblock << endl; + // OutStream << "No. of times performed output (write request)\t: " << ru.ru_oublock << endl; + // OutStream << "No. of signals received \t: " << ru.ru_nsignals << endl; + OutStream << "No. of voluntary context switches \t: " << ru.ru_nvcsw << endl; + OutStream << "No. of involuntary context switches \t: " << ru.ru_nivcsw << endl; + } + + else + OutStream << NA << endl; + +#else + OutStream << NA << endl; +#endif + + OutStream << endl; + +#ifdef _OPENMP +#pragma omp parallel + { + if (0 == omp_get_thread_num()) + { + OutStream << "Number of OpenMP threads \t: " << omp_get_num_threads() << endl; + OutStream << "Number of OpenMP processors \t: " << omp_get_num_procs() << endl; + + LogStream << "Number of OpenMP threads \t: " << omp_get_num_threads() << endl; + LogStream << "Number of OpenMP processors \t: " << omp_get_num_procs() << endl; + } + } +#endif + + time_t const tRunTime = m_tSysEndTime - m_tSysStartTime; + struct tm* ptmRunTime = gmtime(&tRunTime); + + OutStream << "Time required for simulation \t: " << put_time(ptmRunTime, "%T") << endl; + LogStream << "Time required for simulation \t: " << put_time(ptmRunTime, "%T") << endl; + + double const dSpeedUp = m_dSimDuration * 3600 / static_cast(tRunTime); + OutStream << setprecision(0); + OutStream << "Time simulated / time required for simulation\t: " << dSpeedUp << " x faster than reality" << endl; + + LogStream << setprecision(0); + LogStream << "Time simulated / time required for simulation\t: " << dSpeedUp << " x faster than reality" << endl; +} + +//=============================================================================================================================== +//! Returns an error message given an error code +//=============================================================================================================================== +string CSimulation::strGetErrorText(int const nErr) +{ + string strErr; + + switch (nErr) + { + case RTN_USER_ABORT: + strErr = "run ended by user"; + break; + + case RTN_ERR_BADPARAM: + strErr = "error in command-line parameter"; + break; + + case RTN_ERR_INI: + strErr = "error reading initialisation file"; + break; + + case RTN_ERR_CMEDIR: + strErr = "error in directory name"; + break; + + case RTN_ERR_RUNDATA: + strErr = "error reading run details file"; + break; + + case RTN_ERR_SCAPE_SHAPE_FUNCTION_FILE: + strErr = "error reading SCAPE shape function file"; + break; + + case RTN_ERR_TIDEDATAFILE: + strErr = "error reading tide data file"; + break; + + case RTN_ERR_LOGFILE: + strErr = "error creating log file"; + break; + + case RTN_ERR_OUTFILE: + strErr = "error creating text output file"; + break; + + case RTN_ERR_TSFILE: + strErr = "error creating time series file"; + break; + + case RTN_ERR_DEMFILE: + strErr = "error reading initial DEM file"; + break; + + case RTN_ERR_RASTER_FILE_READ: + strErr = "error reading raster GIS file"; + break; + + case RTN_ERR_VECTOR_FILE_READ: + strErr = "error reading vector GIS file"; + break; + + case RTN_ERR_MEMALLOC: + strErr = "error allocating memory"; + break; + + case RTN_ERR_RASTER_GIS_OUT_FORMAT: + strErr = "problem with raster GIS output format"; + break; + + case RTN_ERR_VECTOR_GIS_OUT_FORMAT: + strErr = "problem with vector GIS output format"; + break; + + case RTN_ERR_TEXT_FILE_WRITE: + strErr = "error writing text output file"; + break; + + case RTN_ERR_RASTER_FILE_WRITE: + strErr = "error writing raster GIS output file"; + break; + + case RTN_ERR_VECTOR_FILE_WRITE: + strErr = "error writing vector GIS output file"; + break; + + case RTN_ERR_TIMESERIES_FILE_WRITE: + strErr = "error writing time series output file"; + break; + + case RTN_ERR_LINETOGRID: + strErr = "error putting linear feature onto raster grid"; + break; + + case RTN_ERR_NOSEACELLS: + strErr = "no sea cells found"; + break; + + case RTN_ERR_GRID_TO_LINE: + strErr = "error when searching grid for linear feature"; + break; + + case RTN_ERR_NO_COAST: + strErr = "no coastlines found. Is the SWL correct?"; + break; + + case RTN_ERR_PROFILE_WRITE: + strErr = "error writing coastline-normal profiles"; + break; + + case RTN_ERR_TIME_UNITS: + strErr = "error in time units"; + break; + + case RTN_ERR_NO_SOLUTION_FOR_ENDPOINT: + strErr = "no solution when finding end point for coastline-normal line"; + break; + + case RTN_ERR_PROFILE_ENDPOINT_IS_INLAND: + strErr = "end point for coastline-normal line is not in the contiguous sea"; + break; + + case RTN_ERR_CLIFF_NOTCH: + strErr = "cliff notch is above sediment top elevation"; + break; + + case RTN_ERR_CLIFF_CANNOT_DEPOSIT_ALL: + strErr = "unable to deposit enough unconsolidated sediment (talus) from cliff collapse"; + break; + + case RTN_ERR_PROFILE_SPACING: + strErr = "coastline-normal profiles are too closely spaced"; + break; + + case RTN_ERR_NO_PROFILES_1: + strErr = "no coastline-normal profiles created, check the SWL"; + break; + + case RTN_ERR_NO_PROFILES_2: + strErr = "no coastline-normal profiles created during rasterization"; + break; + + case RTN_ERR_EDGE_OF_GRID: + strErr = "hit grid edge when eroding beach"; + break; + + case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_BEACH_EROSION: + strErr = "could not locate seaward end of profile when creating Dean profile for beach erosion"; + break; + + case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_UPCOAST_BEACH_DEPOSITION: + strErr = "could not locate seaward end of profile when creating Dean profile for up-coast beach deposition"; + break; + + case RTN_ERR_NO_SEAWARD_END_OF_PROFILE_DOWNCOAST_BEACH_DEPOSITION: + strErr = "could not locate seaward end of profile when creating Dean profile for down-coast beach deposition"; + break; + + case RTN_ERR_LANDFORM_TO_GRID: + strErr = "updating grid with landforms"; + break; + + case RTN_ERR_NO_TOP_LAYER: + strErr = "no top layer of sediment"; + break; + + case RTN_ERR_NO_ADJACENT_POLYGON: + strErr = "problem with polygon-to-polygon sediment routing sequence"; + break; + + case RTN_ERR_BAD_MULTILINE: + strErr = "inconsistent multiline"; + break; + + case RTN_ERR_CANNOT_INSERT_POINT: + strErr = "cannot insert point into multiline"; + break; + + case RTN_ERR_CANNOT_ASSIGN_COASTAL_LANDFORM: + strErr = "cannot assign coastal landform"; + break; + + case RTN_ERR_SHADOW_ZONE_FLOOD_FILL_NOGRID: + strErr = "start point for cell-by-cell fill of wave shadow zone is outside grid"; + break; + + case RTN_ERR_SHADOW_ZONE_FLOOD_START_POINT: + strErr = "could not find start point for cell-by-cell fill of wave shadow zone"; + break; + + case RTN_ERR_CSHORE_EMPTY_PROFILE: + strErr = "empty profile during during CShore wave propagation"; + break; + + case RTN_ERR_CSHORE_FILE_INPUT: + strErr = "creating file for CShore input"; + break; + + case RTN_ERR_READING_CSHORE_FILE_OUTPUT: + strErr = "reading CShore output file"; + break; + + case RTN_ERR_WAVE_INTERPOLATION_LOOKUP: + strErr = "during wave interpolation lookup"; + break; + + case RTN_ERR_GRIDCREATE: + strErr = "while running GDALGridCreate()"; + break; + + case RTN_ERR_COAST_CANT_FIND_EDGE_CELL: + strErr = "cannot find edge cell while constructing grid-edge profile"; + break; + + case RTN_ERR_CSHORE_ERROR: + strErr = "CShore did not finish correctly"; + break; + + case RTN_ERR_NO_CELL_UNDER_COASTLINE: + strErr = "Could not find cell under coastline"; + break; + + case RTN_ERR_OPEN_DEEP_WATER_WAVE_DATA: + strErr = "opening deep sea wave time series file"; + break; + + case RTN_ERR_READING_DEEP_WATER_WAVE_DATA: + strErr = "reading deep sea wave time series file"; + break; + + case RTN_ERR_BOUNDING_BOX: + strErr = "finding edges of the bounding box"; + break; + + case RTN_ERR_READING_SEDIMENT_INPUT_EVENT: + strErr = "reading sediment input event time series file"; + break; + + case RTN_ERR_SEDIMENT_INPUT_EVENT: + strErr = "simulating sediment input event"; + break; + + case RTN_ERR_SEDIMENT_INPUT_EVENT_LOCATION: + strErr = "location of sediment input event is outside grod"; + break; + + case RTN_ERR_WAVESTATION_LOCATION: + strErr = "location of wavestation is outside grid"; + break; + + case RTN_ERR_CLIFF_NOT_IN_POLYGON: + strErr = "cliff not in polygon"; + break; + + case RTN_ERR_CELL_MARKED_PROFILE_COAST_BUT_NOT_PROFILE: + strErr = "Cell marked as profile coast but not as profile"; + break; + + case RTN_ERR_TRACING_FLOOD: + strErr = "error tracing flood line on grid"; + break; + + case RTN_ERR_NO_START_FINISH_POINTS_TRACING_COAST: + strErr = "error tracing coastline on grid, no coast start-finish points found"; + break; + + case RTN_ERR_NO_VALID_COAST: + strErr = "error tracing coastline on grid, no valid coast found"; + break; + + case RTN_ERR_REPEATING_WHEN_TRACING_COAST: + strErr = "error tracing coastline on grid, coast search just repeats"; + break; + + case RTN_ERR_ZERO_LENGTH_COAST: + strErr = "error tracing coastline on grid, zero-length coast found"; + break; + + case RTN_ERR_COAST_TOO_SMALL: + strErr = "error tracing coastline on grid, coast below minimum permitted length"; + break; + + case RTN_ERR_IGNORING_COAST: + strErr = "error tracing coastline on grid, coast ignored"; + break; + + case RTN_ERR_TOO_LONG_TRACING_COAST: + strErr = "error tracing coastline on grid, too many times round tracing loop"; + break; + + case RTN_ERR_CELL_NOT_FOUND_IN_HIT_PROFILE_DIFFERENT_COASTS: + strErr = "intersection cell not found in hit profile"; + break; + + case RTN_ERR_POINT_NOT_FOUND_IN_MULTILINE_DIFFERENT_COASTS: + strErr = "point not found when truncating multiline for different coasts"; + break; + + case RTN_ERR_CELL_NOT_FOUND_IN_HIT_PROFILE: + strErr = "cell not found in hit profile"; + break; + + case RTN_ERR_CELL_IN_POLY_BUT_NO_POLY_COAST: + strErr = "cell marked as in polygon, but does not have polygon's coast"; + break; + + case RTN_ERR_UNKNOWN: + strErr = "unknown error"; + break; + + default: + // should never get here + strErr = " error"; + } + + return strErr; +} + +//=============================================================================================================================== +//! Notifies the user that the simulation has ended, asks for keypress if necessary, and if compiled under GNU can send an email +//=============================================================================================================================== +void CSimulation::DoSimulationEnd(int const nRtn) +{ + // If we don't know the time that the run ended (e.g. because it did not finish correctly), then get it now + if (m_tSysEndTime == 0) + m_tSysEndTime = time(nullptr); + + switch (nRtn) + { + case (RTN_OK): + // normal ending + cout << RUN_END_NOTICE << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; + break; + + case (RTN_HELP_ONLY): + case (RTN_CHECK_ONLY): + return; + + default: + // Aborting because of some error + cerr << RUN_END_NOTICE << "iteration " << m_ulIter << ERROR_NOTICE << nRtn << ": \"" << strGetErrorText(nRtn) << "\", " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; + + if (m_ulIter > 1) + { + // If the run has actually started, then output all GIS files: this is very helpful in tracking down problems + m_bSaveGISThisIter = true; + m_nGISSave = 998; // Will get incremented to 999 when we write the files + bSaveAllRasterGISFiles(); + bSaveAllVectorGISFiles(); + } + + // Write the error message to the logfile and to stdout + if (LogStream && LogStream.is_open()) + { + LogStream << ERR << strGetErrorText(nRtn) << " (error code " << nRtn << ") on " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; + LogStream.flush(); + } + + if (OutStream && OutStream.is_open()) + { + OutStream << ERR << strGetErrorText(nRtn) << " (error code " << nRtn << ") on " << put_time(localtime(&m_tSysEndTime), "%T %A %d %B %Y") << endl; + OutStream.flush(); + } + } + +#ifdef __GNUG__ + if (isatty(fileno(stdout))) + { + // Stdout is connected to a tty, so not running as a background job + // cout << endl + // << PRESS_KEY; + // cout.flush(); + // getchar(); + } + else + { + // Stdout is not connected to a tty, so must be running in the background; if we have something entered for the email address, then send an email + if (! m_strMailAddress.empty()) + { + cout << SEND_EMAIL << m_strMailAddress << endl; + + string strCmd("echo \""); + + stringstream ststrTmp; + ststrTmp << put_time(localtime(&m_tSysEndTime), "%T on %A %d %B %Y") << endl; + + // Send an email using Linux/Unix mail command + if (RTN_OK == nRtn) + { + // Finished normally + strCmd.append("Simulation "); + strCmd.append(m_strRunName); + strCmd.append(", running on "); + strCmd.append(strGetComputerName()); + strCmd.append(", completed normally at "); + strCmd.append(ststrTmp.str()); + strCmd.append("\" | mail -s \""); + strCmd.append(PROGRAM_NAME); + strCmd.append(": normal completion\" "); + strCmd.append(m_strMailAddress); + } + else + { + // Error, so give some information to help debugging + strCmd.append("Simulation "); + strCmd.append(m_strRunName); + strCmd.append(", running on "); + strCmd.append(strGetComputerName()); + strCmd.append(", aborted with error code "); + strCmd.append(to_string(nRtn)); + strCmd.append(": "); + strCmd.append(strGetErrorText(nRtn)); + strCmd.append(" at timestep "); + strCmd.append(to_string(m_ulIter)); + strCmd.append(" ("); + strCmd.append(strDispSimTime(m_dSimElapsed)); + strCmd.append(").\n\nThis message sent at "); + strCmd.append(ststrTmp.str()); + strCmd.append("\" | mail -s \""); + strCmd.append(PROGRAM_NAME); + strCmd.append(": ERROR\" "); + strCmd.append(m_strMailAddress); + } + + int const nRet = system(strCmd.c_str()); + + if (WEXITSTATUS(nRet) != 0) + cerr << ERR << EMAIL_ERROR << endl; + } + } +#endif +} + +//=============================================================================================================================== +//! Changes all forward slashes in the input string to backslashes, leaving the original unchanged +//=============================================================================================================================== +string CSimulation::pstrChangeToBackslash(string const* strIn) +{ + string strOut(*strIn); + strOut.replace(strOut.begin(), strOut.end(), '/', '\\'); + return strOut; +} + +//=============================================================================================================================== +//! Swaps all backslashes in the input string to forward slashes, leaving the original unchanged +//=============================================================================================================================== +string CSimulation::pstrChangeToForwardSlash(string const* strIn) +{ + string strOut(*strIn); + strOut.replace(strOut.begin(), strOut.end(), '\\', '/'); + return strOut; +} + +//=============================================================================================================================== +//! Trims whitespace from the left side of a string, does not change the original string +//=============================================================================================================================== +string CSimulation::strTrimLeft(string const* strIn) +{ + // Trim leading spaces + size_t const nStartpos = strIn->find_first_not_of(" \t"); + + if (nStartpos == string::npos) + return *strIn; + + else + return strIn->substr(nStartpos); +} + +//=============================================================================================================================== +//! Trims whitespace from the right side of a string, does not change the original string +//=============================================================================================================================== +string CSimulation::strTrimRight(string const* strIn) +{ + string strTmp(*strIn); + + // Remove any stray carriage returns (can happen if file was edited in Windows) + strTmp.erase(remove(strTmp.begin(), strTmp.end(), '\r'), strTmp.end()); + + // Trim trailing spaces + size_t const nEndpos = strTmp.find_last_not_of(" \t"); + + if (nEndpos == string::npos) + return strTmp; + + else + return strTmp.substr(0, nEndpos + 1); +} + +//=============================================================================================================================== +//! Trims whitespace from both sides of a string, does not change the original string +//=============================================================================================================================== +string CSimulation::strTrim(string const* strIn) +{ + string strTmp = *strIn; + + // Remove any stray carriage returns (can happen if file was edited in Windows) + strTmp.erase(remove(strTmp.begin(), strTmp.end(), '\r'), strTmp.end()); + + // Trim trailing spaces + size_t nPos = strTmp.find_last_not_of(" \t"); + + if (nPos != string::npos) + strTmp.resize(nPos + 1); + + // Trim leading spaces + nPos = strTmp.find_first_not_of(" \t"); + + if (nPos != string::npos) + strTmp = strTmp.substr(nPos); + + return strTmp; +} + +//=============================================================================================================================== +//! Returns the lower case version of an string, leaving the original unchanged +//=============================================================================================================================== +string CSimulation::strToLower(string const* strIn) +{ + string strOut = *strIn; + transform(strIn->begin(), strIn->end(), strOut.begin(), tolower); + return strOut; +} + +//=============================================================================================================================== +// Returns the upper case version of an string, leaving the original unchanged +//=============================================================================================================================== +// string CSimulation::strToUpper(string const* strIn) +// { +// string strOut = *strIn; +// transform(strIn->begin(), strIn->end(), strOut.begin(), toupper); +// return strOut; +// } + +//=============================================================================================================================== +//! Returns a string with a substring removed, and with whitespace trimmed +//=============================================================================================================================== +string CSimulation::strRemoveSubstr(string* pStrIn, string const* pStrSub) +{ + size_t const nPos = pStrIn->find(*pStrSub); + + if (nPos != string::npos) + { + // OK, found the substring + pStrIn->replace(nPos, pStrSub->size(), ""); + return strTrim(pStrIn); + } + + else + { + // If not found, return the string unchanged + return *pStrIn; + } +} + +//=============================================================================================================================== +//! From http://stackoverflow.com/questions/236129/split-a-string-in-c They implement (approximately) Python's split() function. This first version puts the results into a pre-constructed string vector. It ignores empty items +//=============================================================================================================================== +vector* CSimulation::VstrSplit(string const* s, char const delim, vector* elems) +{ + stringstream ss(*s); + string item; + + while (getline(ss, item, delim)) + { + if (!item.empty()) + elems->push_back(item); + } + + return elems; +} + +//=============================================================================================================================== +//! From http://stackoverflow.com/questions/236129/split-a-string-in-c They implement (approximately) Python's split() function. This second version returns a new string vector (it calls the first version) +//=============================================================================================================================== +vector CSimulation::VstrSplit(string const* s, char const delim) +{ + vector elems; + VstrSplit(s, delim, &elems); + return elems; +} + +// //=============================================================================================================================== +// //! Calculates the vector cross product of three points +// //=============================================================================================================================== +// double CSimulation::dCrossProduct(double const dX1, double const dY1, double const dX2, double const dY2, double const dX3, double const dY3) +// { +// // Based on code at http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm +// return (dX2 - dX1) * (dY3 - dY2) - ((dY2 - dY1) * (dX3 - dX2)); +// } + +// //=============================================================================================================================== +// //! Calculates the mean of a pointer to a vector of doubles +// //=============================================================================================================================== +// double CSimulation::dGetMean(vector const* pV) +// { +// double dSum = accumulate(pV->begin(), pV->end(), 0.0); +// double dMean = dSum / static_cast(pV->size()); +// return dMean; +// } + +// //=============================================================================================================================== +// //! Calculates the standard deviation of a pointer to a vector of doubles. From http://stackoverflow.com/questions/7616511/calculate-mean-and-standard-deviation-from-a-vector-of-samples-in-c-using-boos +// //=============================================================================================================================== +// double CSimulation::dGetStdDev(vector const* pV) +// { +// double dSum = accumulate(pV->begin(), pV->end(), 0.0); +// double dMean = dSum / static_cast(pV->size()); +// +// double dSqSum = inner_product(pV->begin(), pV->end(), pV->begin(), 0.0); +// double dStdDev = sqrt(dSqSum / static_cast(pV->size()) - dMean * dMean); +// +// return dStdDev; +// } + +//=============================================================================================================================== +//! Appends a CGeom2DIPoint to a vector, making sure that the new end point touches the previous end point i.e. that there is no gap between the two points +//=============================================================================================================================== +void CSimulation::AppendEnsureNoGap(vector* pVPtiPoints, CGeom2DIPoint const* pPti) +{ + int const nX = pPti->nGetX(); + int const nY = pPti->nGetY(); + int const nXLast = pVPtiPoints->back().nGetX(); + int const nYLast = pVPtiPoints->back().nGetY(); + int const nXDiff = nX - nXLast; + int const nYDiff = nY - nYLast; + int const nXDiffA = tAbs(nXDiff); + int const nYDiffA = tAbs(nYDiff); + int const nDiff = tMax(nXDiffA, nYDiffA); + + if (nDiff > 1) + { + // We have a gap + double + dXInc = 0, + dYInc = 0; + + if (nXDiffA > 1) + dXInc = static_cast(nXDiff) / nDiff; + + if (nYDiffA > 1) + dYInc = static_cast(nYDiff) / nDiff; + + for (int n = 1; n < nDiff; n++) + { + CGeom2DIPoint const Pti(nXLast + nRound(n * dXInc), nYLast + nRound(n * dYInc)); + pVPtiPoints->push_back(Pti); + } + } + + pVPtiPoints->push_back(CGeom2DIPoint(nX, nY)); +} + +//=============================================================================================================================== +//! Calculates a Dean equilibrium profile h(y) = A * y^(2/3) where h(y) is the distance below the highest point in the Dean profile at a distance y from the landward start of the profile +//=============================================================================================================================== +void CSimulation::CalcDeanProfile(vector* pdVDeanProfile, double const dInc, double const dDeanTopElev, double const dA, bool const bDeposition, int const nSeawardOffset, double const dStartCellElev) +{ + double dDistFromProfileStart = 0; + + if (bDeposition) + { + // This Dean profile is for deposition i.e. seaward displacement of the profile + pdVDeanProfile->at(0) = dStartCellElev; // Is talus-top elev for cliffs, coast elevation for coasts + + for (int n = 1; n < static_cast(pdVDeanProfile->size()); n++) + { + if (n <= nSeawardOffset) + // As we extend the profile seaward, the elevation of any points coastward of the new coast point of the Dean profile are set to the elevation of the original coast or the talus top (is this realistic for talus?) + pdVDeanProfile->at(n) = dStartCellElev; + + else + { + double const dDistBelowTop = dA * pow(dDistFromProfileStart, DEAN_POWER); + pdVDeanProfile->at(n) = dDeanTopElev - dDistBelowTop; + + dDistFromProfileStart += dInc; + } + } + } + + else + { + // This Dean profile is for erosion i.e. landward displacement of the profile + for (int n = 0; n < static_cast(pdVDeanProfile->size()); n++) + { + double const dDistBelowTop = dA * pow(dDistFromProfileStart, DEAN_POWER); + pdVDeanProfile->at(n) = dDeanTopElev - dDistBelowTop; + + dDistFromProfileStart += dInc; + } + } +} + +//=============================================================================================================================== +//! Calculate the total elevation difference between every point in two elevation profiles (first profile - second profile) +//=============================================================================================================================== +double CSimulation::dSubtractProfiles(vector const* pdVFirstProfile, vector const* pdVSecondProfile, vector const* pbVIsValid) +{ + double dTotElevDiff = 0; + + // Note that this assumes that all three vectors are of equal length, should really check this + for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) + { + if (pbVIsValid->at(n)) + { + double const dProfileDiff = pdVFirstProfile->at(n) - pdVSecondProfile->at(n); + + dTotElevDiff += dProfileDiff; + } + } + + // // DEBUG CODE ----------------------------------------------------- + // LogStream << endl; + // LogStream << "First profile = "; + // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) + // { + // LogStream << pdVFirstProfile->at(n) << " "; + // } + // LogStream << endl; + // LogStream << "Second profile = "; + // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) + // { + // LogStream << pdVSecondProfile->at(n) << " "; + // } + // LogStream << endl; + // LogStream << "Difference = "; + // for (int n = 0; n < static_cast(pdVFirstProfile->size()); n++) + // { + // LogStream << pdVFirstProfile->at(n) - pdVSecondProfile->at(n) << " "; + // } + // LogStream << endl; + // // DEBUG CODE ----------------------------------------------------- + + return dTotElevDiff; +} + +//=============================================================================================================================== +//! Calculate the depth of closure +//=============================================================================================================================== +void CSimulation::CalcDepthOfClosure(void) +{ + double + dDeepWaterWaveHeight, + dDeepWaterPeriod; + + if (m_bSingleDeepWaterWaveValues) + { + dDeepWaterWaveHeight = m_dAllCellsDeepWaterWaveHeight; + dDeepWaterPeriod = m_dAllCellsDeepWaterWavePeriod; + } + + else + { + dDeepWaterWaveHeight = m_dMaxUserInputWaveHeight; + dDeepWaterPeriod = m_dMaxUserInputWavePeriod; + } + + // TODO 051 Calculate depth of closure using 'average of the maximum values observed during a typical year' + // dL = 2.28 * Hsx − (68.5 * Hsx^2 / (g * Tsx^2)) + // where: + // Hsx is the nearshore storm wave height that is exceeded only 12 hours each year + // Tsx is the associated wave period + // from Hallermeier, R.J. (1978). Uses for a calculated limit depth to beach erosion. Proc. 16th Coastal Engineering Conf., ASCE, New York. Pp 1493 - 1512 + // + // For the time being, and since we assume wave height and period constant just use the actual wave height and period to calculate the depth of closure + // m_dDepthOfClosure = (2.28 * dDeepWaterWaveHeight) - (68.5 * dDeepWaterWaveHeight * dDeepWaterWaveHeight / (m_dG * dDeepWaterPeriod * dDeepWaterPeriod)); + + // An alternative (which produces smaller depth of closure estimates) is Birkemeier (1985) TODO 007 Full reference needed + // dL = 1.75 * Hsx - (57.9 * Hsx^2/ (g * Tsx^2)) + m_dDepthOfClosure = (1.75 * dDeepWaterWaveHeight) - (57.9 * dDeepWaterWaveHeight * dDeepWaterWaveHeight / (m_dG * dDeepWaterPeriod * dDeepWaterPeriod)); +} + +// //=============================================================================================================================== +// //! Tests a reference to a string to see if it is numeric (modified from https://tfetimes.com/c-determine-if-a-string-is-numeric/) +// //=============================================================================================================================== +// bool CSimulation::bIsNumeric(string const*strIn) +// { +// return all_of(strIn->begin(), strIn->end(), isdigit); +// } + +//=============================================================================================================================== +//! Parses a date string into days, months, and years, and checks each of them +//=============================================================================================================================== +bool CSimulation::bParseDate(string const* strDate, int& nDay, int& nMonth, int& nYear) +{ + vector VstrTmp = VstrSplit(strDate, SLASH); + + if (VstrTmp.size() < 3) + { + cerr << "date string must include day, month, and year '" << strDate << "'" << endl; + return false; + } + + // Sort out day + if (! bIsStringValidInt(VstrTmp[0])) + { + cerr << "invalid integer for day in date '" << strDate << "'" << endl; + return false; + } + + nDay = stoi(VstrTmp[0]); + + if ((nDay < 1) || (nDay > 31)) + { + cerr << "day must be between 1 and 31 in date '" << strDate << "'" << endl; + return false; + } + + // Sort out month + if (! bIsStringValidInt(VstrTmp[1])) + { + cerr << "invalid integer for month in date '" << strDate << "'" << endl; + return false; + } + + nMonth = stoi(VstrTmp[1]); + + if ((nMonth < 1) || (nMonth > 12)) + { + cerr << "month must be between 1 and 12 in date '" << strDate << "'" << endl; + return false; + } + + // Sort out year + if (! bIsStringValidInt(VstrTmp[2])) + { + cerr << "invalid integer for year in date '" << strDate << "'" << endl; + return false; + } + + nYear = stoi(VstrTmp[2]); + + if (nYear < 0) + { + cerr << "year must be > 0 in date '" << strDate << "'" << endl; + return false; + } + + return true; +} + +//=============================================================================================================================== +//! Parses a time string into hours, minutes, and seconds, and checks each of them +//=============================================================================================================================== +bool CSimulation::bParseTime(string const* strTime, int& nHour, int& nMin, int& nSec) +{ + vector VstrTmp = VstrSplit(strTime, DASH); + + if (VstrTmp.size() < 3) + { + cerr << "time string must include hours, minutes, and seconds '" << strTime << "'" << endl; + return false; + } + + // Sort out hour + if (! bIsStringValidInt(VstrTmp[0])) + { + cerr << "invalid integer for hours in time '" << strTime << "'" << endl; + return false; + } + + nHour = stoi(VstrTmp[0]); + + if ((nHour < 0) || (nHour > 23)) + { + cerr << "hour must be between 0 and 23 in time '" << strTime << "'" << endl; + return false; + } + + // Sort out minutes + if (! bIsStringValidInt(VstrTmp[1])) + { + cerr << "invalid integer for minutes in time '" << strTime << "'" << endl; + return false; + } + + nMin = stoi(VstrTmp[1]); + + if ((nMin < 0) || (nMin > 59)) + { + cerr << "minutes must be betwen 0 and 59 in time '" << strTime << "'" << endl; + return false; + } + + // Sort out seconds + if (! bIsStringValidInt(VstrTmp[2])) + { + cerr << "invalid integer for seconds in time '" << strTime << "'" << endl; + return false; + } + + nSec = stoi(VstrTmp[2]); + + if ((nSec < 0) || (nSec > 59)) + { + cerr << "seconds must be between 0 and 59 in time '" << strTime << "'" << endl; + return false; + } + + return true; +} + +//=============================================================================================================================== +//! For sediment input events, parses a string that may be relative (a number of hours or days after the start of the simulation), or absolute (a time/date in the format hh-mm-ss dd/mm/yyyy). Returns the timestep in which the sediment input event occurs +//=============================================================================================================================== +unsigned long CSimulation::ulConvertToTimestep(string const* pstrIn) const +{ + unsigned long ulTimeStep = 0; + + // Convert to lower case, remove leading and trailing whitespace + string strDate = strToLower(pstrIn); + strDate = strTrim(&strDate); + + if (strDate.find("hour") != string::npos) + { + // OK, this is a number of hours (a relative time, from the start of simulation) + vector VstrTmp = VstrSplit(&strDate, SPACE); + + if ((VstrTmp.size() < 2) || (! bIsStringValidInt(VstrTmp[0]))) + { + cerr << "Error in number of hours '" + strDate + "' for sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + double const dHours = stod(strTrim(&VstrTmp[0])); + + if (dHours > m_dSimDuration) + { + cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + ulTimeStep = static_cast(dRound(dHours / m_dTimeStep)); + } + + else if (strDate.find("day") != string::npos) + { + // OK, this is a number of days (a relative time, from the start of simulation) + vector VstrTmp = VstrSplit(&strDate, SPACE); + + if ((VstrTmp.size() < 2) || (! bIsStringValidInt(VstrTmp[0]))) + { + cerr << "Error in number of days '" + strDate + "' for sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + double const dHours = stod(strTrim(&VstrTmp[0])) * 24; + + if (dHours > m_dSimDuration) + { + cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + ulTimeStep = static_cast(dRound(dHours / m_dTimeStep)); + } + + else + { + // This is an absolute time/date in the format hh-mm-ss dd/mm/yyyy + vector VstrTmp = VstrSplit(&strDate, SPACE); + + if (VstrTmp.size() < 2) + { + cerr << "Error in time/date '" + strDate + "' of sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + int nHour = 0; + int nMin = 0; + int nSec = 0; + + // OK, first sort out the time + if (! bParseTime(&VstrTmp[0], nHour, nMin, nSec)) + { + cerr << "Error in time '" + VstrTmp[0] + "' of sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + int nDay = 0; + int nMonth = 0; + int nYear = 0; + + // Now sort out the time + if (! bParseDate(&VstrTmp[1], nDay, nMonth, nYear)) + { + cerr << "Error in date '" + VstrTmp[1] + "' of sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + // This is modified from https://stackoverflow.com/questions/14218894/number-of-days-between-two-dates-c + struct tm tmSimStart = {}; + tmSimStart.tm_sec = m_nSimStartSec; + tmSimStart.tm_min = m_nSimStartMin; + tmSimStart.tm_hour = m_nSimStartHour; + tmSimStart.tm_mday = m_nSimStartDay; + tmSimStart.tm_mon = m_nSimStartMonth - 1; + tmSimStart.tm_year = m_nSimStartYear - 1900; + + struct tm tmSimEvent = {}; + tmSimEvent.tm_sec = nSec; + tmSimEvent.tm_min = nMin; + tmSimEvent.tm_hour = nHour; + tmSimEvent.tm_mday = nDay; + tmSimEvent.tm_mon = nMonth - 1; + tmSimEvent.tm_year = nYear - 1900; + + time_t const tStart = mktime(&tmSimStart); + time_t const tEvent = mktime(&tmSimEvent); + + if (tStart == (time_t)(-1)) + { + cerr << "Error in simulation start time/date" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + if (tEvent == (time_t)(-1)) + { + cerr << "Error in time/date '" + strDate + "' of sediment input event" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + double const dHours = difftime(tEvent, tStart) / (60 * 60); + + if (dHours < 0) + { + cerr << "Sediment input event '" + strDate + "' occurs before start of simulation" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + if (dHours > m_dSimDuration) + { + cerr << "Sediment input event '" + strDate + "' occurs after end of simulation" << endl; + return SEDIMENT_INPUT_EVENT_ERROR; + } + + ulTimeStep = static_cast(dHours / m_dTimeStep); + } + + return ulTimeStep; +} + +//=============================================================================================================================== +//! Returns true if the cell is an intervention +//=============================================================================================================================== +bool CSimulation::bIsInterventionCell(int const nX, int const nY) const +{ + if (m_pRasterGrid->Cell(nX, nY).pGetLandform()->nGetLFCategory() == LF_CAT_INTERVENTION) + return true; + + return false; +} + +//=============================================================================================================================== +//! Do end-of-run memory clearance +//=============================================================================================================================== +void CSimulation::DoEndOfRunDeletes(void) +{ + // Clear all vector coastlines, profiles, and polygons + m_VCoast.clear(); + + // m_VFloodWaveSetup.clear(); + m_VFloodWaveSetupSurge.clear(); + m_VFloodWaveSetupSurgeRunup.clear(); +} diff --git a/src/write_output.cpp b/src/write_output.cpp index 68281c556..4a7eb6111 100644 --- a/src/write_output.cpp +++ b/src/write_output.cpp @@ -2264,11 +2264,11 @@ void CSimulation::DoEndOfTimestepTotals(void) { for (int nY = 0; nY < m_nYGridSize; nY++) { - dEndIterConsFineAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsFineThickConsiderNotch(); - dEndIterConsSandAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsSandThickConsiderNotch(); - dEndIterConsCoarseAllCells += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsCoarseThickConsiderNotch(); + dEndIterConsFineAllCells += m_pRasterGrid->Cell(nX, nY).dGetTotConsFineThickConsiderNotch(); + dEndIterConsSandAllCells += m_pRasterGrid->Cell(nX, nY).dGetTotConsSandThickConsiderNotch(); + dEndIterConsCoarseAllCells += m_pRasterGrid->Cell(nX, nY).dGetTotConsCoarseThickConsiderNotch(); - double dSuspFine = m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); + double dSuspFine = m_pRasterGrid->Cell(nX, nY).dGetSuspendedSediment(); if (dSuspFine > 0) { @@ -2276,7 +2276,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nSuspFineCellsAllCells++; } - double dUnconsFine = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsFine(); + double dUnconsFine = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsFine(); if (dUnconsFine > 0) { @@ -2284,7 +2284,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nUnconsFineCellsAllCells++; } - double dUnconsSand = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsSand(); + double dUnconsSand = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsSand(); if (dUnconsSand > 0) { @@ -2292,7 +2292,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nUnconsSandCellsAllCells++; } - double dUnconsCoarse = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsCoarse(); + double dUnconsCoarse = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsCoarse(); if (dUnconsCoarse > 0) { @@ -2301,14 +2301,14 @@ void CSimulation::DoEndOfTimestepTotals(void) } // Is this cell within a polygon? - if (m_pRasterGrid->m_Cell[nX][nY].nGetPolygonID() != INT_NODATA) + if (m_pRasterGrid->Cell(nX, nY).nGetPolygonID() != INT_NODATA) { // It is within a polygon - dEndIterConsFineInPolygons += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsFineThickConsiderNotch(); - dEndIterConsSandInPolygons += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsSandThickConsiderNotch(); - dEndIterConsCoarseInPolygons += m_pRasterGrid->m_Cell[nX][nY].dGetTotConsCoarseThickConsiderNotch(); + dEndIterConsFineInPolygons += m_pRasterGrid->Cell(nX, nY).dGetTotConsFineThickConsiderNotch(); + dEndIterConsSandInPolygons += m_pRasterGrid->Cell(nX, nY).dGetTotConsSandThickConsiderNotch(); + dEndIterConsCoarseInPolygons += m_pRasterGrid->Cell(nX, nY).dGetTotConsCoarseThickConsiderNotch(); - dSuspFine = m_pRasterGrid->m_Cell[nX][nY].dGetSuspendedSediment(); + dSuspFine = m_pRasterGrid->Cell(nX, nY).dGetSuspendedSediment(); if (dSuspFine > 0) { @@ -2316,7 +2316,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nSuspFineCellsInPolygons++; } - dUnconsFine = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsFine(); + dUnconsFine = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsFine(); if (dUnconsFine > 0) { @@ -2324,7 +2324,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nUnconsFineCellsInPolygons++; } - dUnconsSand = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsSand(); + dUnconsSand = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsSand(); if (dUnconsSand > 0) { @@ -2332,7 +2332,7 @@ void CSimulation::DoEndOfTimestepTotals(void) nUnconsSandCellsInPolygons++; } - dUnconsCoarse = m_pRasterGrid->m_Cell[nX][nY].dGetTotUnconsCoarse(); + dUnconsCoarse = m_pRasterGrid->Cell(nX, nY).dGetTotUnconsCoarse(); if (dUnconsCoarse > 0) {