1 /*!
2  *
3  * \file read_input.cpp
4  * \brief Reads non-GIS input files
5  * \details TODO A more detailed description of these routines.
6  * \author Andres Payo
7  * \author David Favis-Mortlock
8  * \author Martin Husrt
9  * \author Monica Palaseanu-Lovejoy
10  * \date 2017
11  * \copyright GNU General Public License
12  *
13  */
14 
15 /*==============================================================================================================================
16 
17  This file is part of CliffMetrics, the Coastal Modelling Environment.
18 
19  CliffMetrics 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.
20 
21  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.
22 
23  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.
24 
25 ==============================================================================================================================*/
26 #include <stdlib.h>                 // for atof()
27 #include <fstream>
28 using std::ifstream;
29 
30 #include <iostream>
31 using std::cout;
32 using std::cerr;
33 using std::endl;
34 using std::ios;
35 
36 #include "cliffmetrics.h"
37 #include "delineation.h"
38 
39 
40 /*==============================================================================================================================
41 
42  The bReadIni member function reads the initialization file
43 
44 ==============================================================================================================================*/
bReadIni(void)45 bool CDelineation::bReadIni(void)
46 {
47    m_strCLIFFIni = m_strCLIFFDir;
48    m_strCLIFFIni.append(CLIFF_INI);
49 
50    // The .ini file is assumed to be in the CliffMetrics executable's directory
51    string strFilePathName(m_strCLIFFIni);
52 
53    // Tell the user what is happening
54    cout << READFILELOC << strFilePathName << endl;
55 
56    // Create an ifstream object
57    ifstream InStream;
58 
59    // Try to open .ini file for input
60    InStream.open(strFilePathName.c_str(), ios::in);
61 
62    // Did it open OK?
63    if (! InStream.is_open())
64    {
65       // Error: cannot open .ini file for input
66       cerr << ERR << "cannot open " << strFilePathName << " for input" << endl;
67       return false;
68    }
69 
70    char szRec[BUFSIZE] = "";
71    int i = 0;
72    string strRec, strErr;
73 
74    while (InStream.getline(szRec, BUFSIZE, '\n'))
75    {
76       strRec = szRec;
77 
78       // Trim off leading and trailing whitespace
79       strRec = strTrimLeft(&strRec);
80       strRec = strTrimRight(&strRec);
81 
82       // If it is a blank line or a comment then ignore it
83       if ((! strRec.empty()) && (strRec[0] != QUOTE1) && (strRec[0] != QUOTE2))
84       {
85          // It isn't so increment counter
86          i++;
87 
88          // Find the colon: note that lines MUST have a colon separating data from leading description portion
89          size_t nPos = strRec.find(':');
90          if (nPos == string::npos)
91          {
92             // Error: badly formatted line (no colon)
93             cerr << ERR << "badly formatted line (no ':') in " << strFilePathName << endl << szRec << endl;
94             return false;
95          }
96 
97          if (nPos == strRec.size()-1)
98          {
99             // Error: badly formatted line (colon with nothing following)
100             cerr << ERR << "badly formatted line (nothing following ':') in " << strFilePathName << endl << szRec << endl;
101             return false;
102          }
103 
104          // Strip off leading portion (the bit up to and including the colon)
105          string strRH = strRec.substr(nPos+1);
106 
107          // Remove leading whitespace
108          strRH = strTrimLeft(&strRH);
109 
110          // Look for a trailing comment, if found then terminate string at that point and trim off any trailing whitespace
111          nPos = strRH.rfind(QUOTE1);
112          if (nPos != string::npos)
113             strRH = strRH.substr(0, nPos+1);
114 
115          nPos = strRH.rfind(QUOTE2);
116          if (nPos != string::npos)
117             strRH = strRH.substr(0, nPos+1);
118 
119          // Remove trailing whitespace
120          strRH = strTrimRight(&strRH);
121 
122          switch (i)
123          {
124          case 1:
125             // The main input run-data filename
126             if (strRH.empty())
127                strErr = "path and name of main datafile";
128             else
129             {
130                // First check that we don't already have an input run-data filename, e.g. one entered on the command-line
131                if (m_strDataPathName.empty())
132                {
133                   // We don't: so first check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter
134                   if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == '~') || (strRH[1] == ':'))
135                      // It has an absolute path, so use it 'as is'
136                      m_strDataPathName = strRH;
137                   else
138                   {
139                      // It has a relative path, so prepend the CliffMetrics dir
140                      m_strDataPathName = m_strCLIFFDir;
141                      m_strDataPathName.append(strRH);
142                   }
143                }
144             }
145             break;
146 
147          case 2:
148             // Path for CliffMetrics output
149             if (strRH.empty())
150                strErr = "path for CliffMetrics output";
151             else
152             {
153                // Check for trailing slash on CliffMetrics output directory name (is vital)
154                if (strRH[strRH.size()-1] != PATH_SEPARATOR)
155                   strRH.append(&PATH_SEPARATOR);
156 
157                // Now check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter
158                if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == '~') || (strRH[1] == ':'))
159                   // It is an absolute path, so use it 'as is'
160                   m_strOutPath = strRH;
161                else
162                {
163                   // It is a relative path, so prepend the CliffMetrics dir
164                   m_strOutPath = m_strCLIFFDir;
165                   m_strOutPath.append(strRH);
166                }
167             }
168             break;
169 
170          case 3:
171             // Email address, only useful if running under Linux/Unix
172             if (! strRH.empty())
173             {
174                // Something was entered, do rudimentary check for valid email address
175                if (strRH.find('@') == string::npos)
176                   strErr = "email address for messages";
177                else
178                   m_strMailAddress = strRH;
179             }
180             break;
181          }
182 
183          // Did an error occur?
184          if (! strErr.empty())
185          {
186             // Error in input to initialisation file
187             cerr << ERR << "reading " << strErr << " in " << strFilePathName << endl << "'" << strRec << "'" << endl;
188             InStream.close();
189 
190             return false;
191          }
192       }
193    }
194 
195    InStream.close();
196    return true;
197 }
198 
199 
200 /*==============================================================================================================================
201 
202  Reads the run details input file and does some initialization
203 
204 ==============================================================================================================================*/
bReadRunData(void)205 bool CDelineation::bReadRunData(void)
206 {
207    // Create an ifstream object
208    ifstream InStream;
209 
210    // Try to open run details file for input
211    InStream.open(m_strDataPathName.c_str(), ios::in);
212 
213    // Did it open OK?
214    if (! InStream.is_open())
215    {
216       // Error: cannot open run details file for input
217       cerr << ERR << "cannot open " << m_strDataPathName << " for input" << endl;
218       return false;
219    }
220 
221    char szRec[BUFSIZE] = "";
222    int
223       i = 0;
224       size_t nPos = 0;
225    string strRec, strErr;
226 
227    while (InStream.getline(szRec, BUFSIZE, '\n'))
228    {
229       strRec = szRec;
230 
231       // Trim off leading and trailing whitespace
232       strRec = strTrimLeft(&strRec);
233       strRec = strTrimRight(&strRec);
234 
235       // If it is a blank line or a comment then ignore it
236       if ((! strRec.empty()) && (strRec[0] != QUOTE1) && (strRec[0] != QUOTE2))
237       {
238          // It isn't so increment counter
239          i++;
240 
241          // Find the colon: note that lines MUST have a colon separating data from leading description portion
242          nPos = strRec.find(':');
243          if (nPos == string::npos)
244          {
245             // Error: badly formatted line (no colon)
246             cerr << ERR << "badly formatted line (no ':') in " << m_strDataPathName << endl << szRec << endl;
247             return false;
248          }
249 
250          // Strip off leading portion (the bit up to and including the colon)
251          string strRH = strRec.substr(nPos+1);
252 
253          // Remove leading whitespace after the colon
254          strRH = strTrimLeft(&strRH);
255 
256          // Look for trailing comments, if found then terminate string at that point and trim off any trailing whitespace
257          bool bFound = true;
258          while (bFound)
259          {
260             bFound = false;
261 
262             nPos = strRH.rfind(QUOTE1);
263             if (nPos != string::npos)
264             {
265                strRH = strRH.substr(0, nPos);
266                bFound = true;
267             }
268 
269             nPos = strRH.rfind(QUOTE2);
270             if (nPos != string::npos)
271             {
272                strRH = strRH.substr(0, nPos);
273                bFound = true;
274             }
275 
276             // Trim trailing spaces
277             strRH = strTrimRight(&strRH);
278          }
279 
280 #ifdef _WIN32
281             // For Windows, make sure has backslashes, not Unix-style slashes
282             strRH = pstrChangeToBackslash(&strRH);
283 #endif
284 
285         string strTmp;
286 
287          switch (i)
288          {
289          // ---------------------------------------------- Run Information -----------------------------------------------------
290          case 1:
291             // Text output file names, don't change case
292             if (strRH.empty())
293                strErr = "output file names";
294             else
295             {
296                m_strRunName = strRH;
297 
298                m_strOutFile = m_strOutPath;
299                m_strOutFile.append(strRH);
300                m_strOutFile.append(OUTEXT);
301 
302                m_strLogFile = m_strOutPath;
303                m_strLogFile.append(strRH);
304                m_strLogFile.append(LOGEXT);
305             }
306             break;
307 
308          case 2:
309             //  DTM file (can't be blank)
310             if (! strRH.empty())
311             {
312 #ifdef _WIN32
313                // For Windows, make sure has backslashes, not Unix-style slashes
314                strRH = pstrChangeToBackslash(&strRH);
315 #endif
316                // Now check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter
317                if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == '~') || (strRH[1] == ':'))
318                   // It has an absolute path, so use it 'as is'
319  		    m_strDTMFile = strRH;
320                else
321                {
322                   // It has a relative path, so prepend the CliffMetrics dir
323  	            m_strDTMFile = m_strCLIFFDir;
324                     m_strDTMFile.append(strRH);
325                }
326             }
327             break;
328 
329          case 3:
330             //  Still water level (m) used to extract the shoreline
331             m_dStillWaterLevel = atof(strRH.c_str());
332             break;
333 
334          case 4:
335             // Vector coastline smoothing algorithm: 0 = none, 1 = running mean, 2 = Savitsky-Golay
336             m_nCoastSmooth = atoi(strRH.c_str());
337             if ((m_nCoastSmooth < SMOOTH_NONE) || (m_nCoastSmooth > SMOOTH_SAVITZKY_GOLAY))
338                strErr = "coastline vector smoothing algorithm";
339             break;
340 
341          case 5:
342             // Size of coastline smoothing window: must be odd
343             m_nCoastSmoothWindow = atoi(strRH.c_str());
344             if ((m_nCoastSmoothWindow <= 0) || !(m_nCoastSmoothWindow % 2))
345                strErr = "size of coastline vector smoothing window (must be > 0 and odd)";
346             break;
347 
348          case 6:
349             // Order of coastline profile smoothing polynomial for Savitsky-Golay: usually 2 or 4, max is 6
350             m_nSavGolCoastPoly = atoi(strRH.c_str());
351             if ((m_nSavGolCoastPoly <= 0) || (m_nSavGolCoastPoly > 6))
352                strErr = "value of Savitsky-Golay polynomial for coastline smoothing (must be <= 6)";
353             break;
354 
355          case 7:
356             // Optional shoreline shape file (can be blank)
357             if (! strRH.empty())
358             {
359 #ifdef _WIN32
360                // For Windows, make sure has backslashes, not Unix-style slashes
361                strRH = pstrChangeToBackslash(&strRH);
362 #endif
363                // Now check for leading slash, or leading Unix home dir symbol, or occurrence of a drive letter
364                if ((strRH[0] == PATH_SEPARATOR) || (strRH[0] == '~') || (strRH[1] == ':'))
365                {
366                   // It has an absolute path, so use it 'as is'
367                   m_strInitialCoastlineFile = strRH;
368                }
369                else
370                {
371                   // It has a relative path, so prepend the CliffMetrics dir
372                   m_strInitialCoastlineFile = m_strCLIFFDir;
373                   m_strInitialCoastlineFile.append(strRH);
374                }
375             }
376             break;
377 
378 	 case 8:
379 	    if (! m_strInitialCoastlineFile.empty())
380 	    {
381 	       m_nCoastSeaHandiness = atoi(strRH.c_str());
382 	    }
383          case 9:
384             // Raster GIS output format (note must retain original case). Blank means use same format as input DEM file (if possible)
385             m_strRasterGISOutFormat = strTrimLeft(&strRH);
386             break;
387 
388          case 10:
389             // If needed, also output GIS raster world file?
390             strRH = strToLower(&strRH);
391 
392             m_bWorldFile = false;
393             if (strRH.find("y") != string::npos)
394                m_bWorldFile = true;
395             break;
396 
397          case 11:
398             // If needed, scale GIS raster output values?
399             strRH = strToLower(&strRH);
400 
401             m_bScaleRasterOutput = false;
402             if (strRH.find("y") != string::npos)
403                m_bScaleRasterOutput = true;
404             break;
405 
406         case 12:
407             // Vector GIS output format (note must retain original case)
408             m_strVectorGISOutFormat = strRH;
409 
410             if (strRH.empty())
411                strErr = "vector GIS output format";
412             break;
413 
414          case 13:
415             // Random edge for coastline search?
416             strRH = strToLower(&strRH);
417 
418             m_bRandomCoastEdgeSearch = false;
419             if (strRH.find("y") != string::npos)
420                m_bRandomCoastEdgeSearch = true;
421             break;
422 
423          case 14:
424             // Random number seed(s)
425             m_ulRandSeed[0] = atol(strRH.c_str());
426             if (0 == m_ulRandSeed[0])
427             {
428                strErr = "random number seed";
429                break;
430             }
431              // TODO rewrite this, similar to reading raster slice elevations
432             // OK, so find out whether we're dealing with a single seed or more than one: check for a space
433             nPos = strRH.find(SPACE);
434             if (nPos != string::npos)
435             {
436                // There's a space, so we must have more than one number
437                int n = 0;
438                do
439                {
440                   // Trim off the part before the first space then remove leading whitespace
441                   strRH = strRH.substr(nPos, strRH.size()-nPos);
442                   strRH = strTrimLeft(&strRH);
443 
444                   // Put the number into the array
445                   m_ulRandSeed[++n] = atol(strRH.c_str());
446 
447                   // Now look for another space
448                   nPos = strRH.find(SPACE);
449                }
450                while ((n < NRNG) && (nPos != string::npos));
451             }
452             else
453             {
454                // Only one seed specified, so make all seeds the same
455                for (int n = 1; n < NRNG; n++)
456                   m_ulRandSeed[n] = m_ulRandSeed[n-1];
457             }
458             break;
459 
460          case 15:
461             // Length of coastline normals (m)
462             m_dCoastNormalLength = atof(strRH.c_str());
463             if (m_dCoastNormalLength <= 0)
464                strErr = "length of coastline normals must be greater than zero";
465             break;
466 
467          case 16:
468             // Vertical tolerance avoid false CliffTops/Toes
469              m_dEleTolerance = atof(strRH.c_str());
470             if (m_dEleTolerance <= 0)
471                strErr = "vertical elevation tolerance must be greater than zero";
472             break;
473 	 }
474 
475          // Did an error occur?
476          if (! strErr.empty())
477          {
478             // Error in input to run details file
479             cerr << endl << ERR << strErr << ".\nPlease edit " << m_strDataPathName << " and change this line:" << endl << "'" << szRec << "'" << endl << endl;
480             InStream.close();
481             return false;
482          }
483       }
484    }
485    // Close file
486    InStream.close();
487 
488    return true;
489 }
490 
491