processArea.cc [tools/Rusle2SoilsXMLCreator/pg/rusle2_from_ssurgo_build_newbackup] Revision:   Date:
#include "libpq-fe.h"
#include <algorithm>
#include <iostream>
#include <string>
#include <cmath>
#include "stdio.h"
#include "rusle2_from_ssurgo.h"
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#include <boost/algorithm/string.hpp>


using namespace std;
using namespace boost;

/* 
 * Lets get the xml filename fit for Linux 
 */
char 
win2nixSlash( char in ) 
{
	if( in == '\\' )
		return '/';
	return in;
}


/**
 *  Convert the NASIS hydrologic class to the Rusle2 hydrologic class.
 *  The NASIS classes are "A" - "D".
 *  This returns the highest class on error, to preserve existing functionality.
 */

string
Rusle2HydrologicClass ( string pszHydrologicClass )
{
	if(pszHydrologicClass.length() == 0 )
		return "HYDROLOGIC_CLASS_HIGH_RO";
		/* TODO: change to return "NaN" on error. */

	switch ( *pszHydrologicClass.c_str() )
	{
		case 'A':	return "HYDROLOGIC_CLASS_LOW_RO";
		case 'B':	return "HYDROLOGIC_CLASS_MOD_LOW_RO";
		case 'C':	return "HYDROLOGIC_CLASS_MOD_HIGH_RO";
		case 'D':	return "HYDROLOGIC_CLASS_HIGH_RO";
		default:	return "HYDROLOGIC_CLASS_HIGH_RO";
		/* TODO: change to return "NaN" on error. */
	}
}

string
EscapeSingleQuotes ( string inStr )
{
	if(inStr.length() == 0)
	    return inStr; 
	string _inStr = "";
	for (int i=0; i < inStr.length(); i++)
        {
	    if (inStr[i]=='\'')
	        _inStr += "'";
            _inStr += inStr[i];
        }
	return _inStr;
}

/**
 *  processArea() 
 *  Import Rusle2 soil records from a NASIS (SSURGO) soil MDB export file.
 *  SSURGO is an output format for subsets of the NASIS information.
 *  These formats are documented at URL "http://nasis.usda.gov/documents/metadata/".
 *  In NASIS, "map units" (geographical regions) can have an unlimited number of "components" (soil types),
 *    and components can have an unlimited number of "horizons" (soil layers).
 *  For now, make a mapunit folder and in it, put each component (each row of the component table) as a soil object,
 *    with a name "component name (% occurring)"
 *  Returns the number of files successfully imported, or FAILURE (-1) on errors.
 *  @since 2005-05-09 the rules for what should be imported has changed.  In the order they should be done, they are:
 *  1) in the component table, look for compkind = "miscellaneous area".  This will be for water, pits, etc, that should be thrown out completely
 *  2) if the soil is a Histosol (taxorder == "Histosol"), just grab the first horizon and get whatever we can out of that
 *  3) if not a Histosol, search through the horizons, looking for one that has a valid kfactor, then use that.  This is to sort out soils where the
 *     top layers are organic matter; it will grab the first mineral layer
 * 
 *  NAMING
 *  Rusle2 soil filenames are currently algorithmically generated as follows:
 * 
 *  sFullname = "soils\\" + surveyArea + "\\" + muFolderName + "\\" + fullComponentName;
 * 
 *  The values below are from the import code in ReadNASISSoilData(), edited for readability:
 * 
 *  surveyArea = sSANAME;
 *  muFolderName = sMUSYM + " " + sMUNAME;
 *  fullComponentName = componentName + " " + texture + " " + localPhase + " " + dominanceStr + "%";
 * 
 *  sMUSYM  = GetNasisString("musym", ...);             // Map unit symbol
 *  sMUNAME = GetNasisString("muname", ...);            // Map unit name
 *  sSANAME = GetNasisString("areaname", ...);
 * 
 *  componentName = GetNasisString("compName", ...);
 *  texture = GetNasisString("texdesc", ...); 
 *  localPhase = GetNasisString("localPhase", ...);
 *  dominanceStr = GetFieldValue("comppct_r", ...);
 * 
 *  The user is able to enter (override) the surveyArea variable when importing, which determines the 
 *    folder name. This is the only part of the import that allows the user to enter a name to use. 
 *  However, I believe the default name is almost universally used when importing these files. 
 *  The default name is taken from the value of the "areaname" field in the SSURGO download. The names
 *    in this field are highly inconsistent in NASIS, and this is the source of the inconsistent 
 *    naming in Rusle2.
 * 
 *  @note At the time of the import we have the FIPS code available to us (which is stored in Rusle2 
 *    parameter "NASIS_SASYM" (e.g. NH602). This appears to be a concatenation of the FIPS 2-letter code 
 *    for the state with the FIPS 3-digit code for the county. It would be easy to use this to generate 
 *    a more consistent name for the area than is currently in the SSURGO "surveyArea" field.
 *
 *	@note May 2011 - This has been re-written by Eric Hudish <hudish@zedxinc.com> in cooperation with NRCS, Jim Lyon <jplyon.csu@gmail.com> advising.
 *  Code has been changed to work with a PostgresSQL + Postgis database as opposed to Microsoft Access mdb format.
 *  Some of the functions have been kept to mimic the procedured, others removed completely.
 *  Messages about number of failures have been removed completely.
 *  libxml2 was implemented to generate XML instead of custom functions
 */	
bool
processArea( PGconn* conn, PGresult* res, string dir_out, int pROW)
{
#ifndef NDEBUG
	cout << "processArea()" << endl;
#endif
	if( pROW >= PQntuples(res) )
		return false;

	string aSym 	=  GetNasisString("areasymbol", res, pROW);
	string aName 	=  GetNasisString("areaname", res, pROW);
	string aExt     =  ".xml";
	string aLKEY 	=  GetNasisString("lkey", res, pROW);
 
	cerr << "Processing:'" << aSym << "'" << endl;
	cerr << "lkey:'" << aLKEY << "'" << endl;

	/*
	*	Start the soil string
	*/
	string sFullname = dir_out + aName + "\\";

	/* 
	* Query out the [MAPUNIT] information
	*/
	//string muQuery = "SELECT musym, m.mukey, muname, compname, kffact, tfact, slope_r, c.cokey, sandtotal_r, hydgrp, taxorder, kwfact, om_r, hzthk_r from ssurgo.mapunit m, ssurgo.component c, ssurgo.chorizon h where m.mukey = c.mukey and c.cokey = h.cokey and h.hzdept_r=0 and lkey=$1";
	//string muQuery = "SELECT musym, m.mukey, muname, compname, tfact, slope_r, c.cokey from ssurgo.mapunit m, ssurgo.component c where m.mukey = c.mukey and lkey=$1";
	//string muQuery = "SELECT musym, mukey, muname from ssurgo.mapunit where lkey=$1";


// Original PG query
        string muQuery = "SELECT musym, m.mukey, muname, compname, compkind, majcompflag, localphase, kffact, tfact, slope_r, c.cokey, sandtotal_r, hydgrp, taxorder, kwfact, om_r, hzthk_r, otherph, comppct_r from ssurgo.mapunit m, ssurgo.component c, ssurgo.chorizon h where m.mukey = c.mukey and c.cokey = h.cokey and h.hzdept_r in (select hzdept_r from ssurgo.minhzdept_t where minhzdept_t.cokey=c.cokey and minhzdept_t.areasymbol='" + aSym + "' and minhzdept_t.musym=m.musym and minhzdept_t.mukey=m.mukey) and lkey=$1";

// FDW - PG to SQL SVR query
        //string muQuery = "SELECT musym, m.mukey, muname, compname, kffact, tfact, slope_r, c.cokey, sandtotal_r, hydgrp, taxorder, kwfact, om_r, hzthk_r from ss_mapunit m, ss_component c, ss_chorizon h where m.mukey = c.mukey and c.cokey = h.cokey and h.hzdept_r in (select hzdept_r from ss_minhzdept_t where minhzdept_t.cokey=c.cokey and minhzdept_t.areasymbol='" + aSym + "' and minhzdept_t.musym=m.musym and minhzdept_t.mukey=m.mukey) and lkey=$1";


        //string muQuery = "with mindepths as (select hzdept_r, cokey,  musym, mukey from ssurgo.minhzdept_t where minhzdept_t.areasymbol='" + aSym + "') SELECT musym, m.mukey, muname, c.compname, c.compkind, c.majcompflag, c.localphase, kffact, tfact, c.slope_r, c.cokey, sandtotal_r, hydgrp, taxorder, kwfact, om_r, hzthk_r, h.hzdept_r, avgfragvol_r from ssurgo.mapunit m, ssurgo.component c, ssurgo.chorizon h, ssurgo.avgfragvol f where m.mukey = c.mukey and c.cokey = h.cokey and c.cokey = f.cokey and h.hzdept_r in (select hzdept_r from mindepths min where min.cokey=c.cokey and min.musym=m.musym and min.mukey=m.mukey ) and lkey=$1";
	//string muQuery = "with mindepths as (select hzdept_r, cokey,  musym, mukey from ssurgo.minhzdept_t where minhzdept_t.areasymbol='" + aSym + "') SELECT musym, m.mukey, muname, c.compname, c.compkind, c.majcompflag, c.localphase, kffact, tfact, c.slope_r, c.cokey, sandtotal_r, hydgrp, taxorder, kwfact, om_r, hzthk_r, h.hzdept_r, avgfragvol_r, c.otherph, c.comppct_r from ssurgo.mapunit m join ssurgo.component c on (m.mukey = c.mukey) join ssurgo.chorizon h on (c.cokey = h.cokey) left outer join ssurgo.avgfragvol f on (c.cokey = f.cokey) where h.hzdept_r in (select hzdept_r from mindepths min where min.cokey=c.cokey and min.musym=m.musym and min.mukey=m.mukey) and lkey=$1"; 

	cerr << "query:" << muQuery << endl;

	const char * paramValues[1]; 
	paramValues[0] = aLKEY.c_str();
	int numParams = 1;
	PGresult *muRes;

	muRes = PQexecParams( conn,
		muQuery.c_str(),
		numParams,
		NULL,
		paramValues,
		NULL,
		NULL,
		0);
	if( PQresultStatus(muRes) != PGRES_TUPLES_OK ){
		PQclear(muRes);
		exit_nicely(conn);
	}

	cerr << "query *" << aSym << "* ROW_COUNT=" << PQntuples(muRes) << endl;

	for( int i=0; i<PQntuples(muRes); i++){

		/* copy the string for the local version of this string */
		string _sFullname = sFullname;

		/* get the [MAPUNIT] components */
		string muSym 	= GetNasisString("musym", muRes, i);
		string muName 	= GetNasisString("muname", muRes, i);
		string muKEY 	= GetNasisString("mukey", muRes, i);
                string compname = GetNasisString("compname", muRes, i);
                string compkind = GetNasisString("compkind", muRes, i);
                string majcompflag = GetNasisString("majcompflag", muRes, i);
                string localphase = GetNasisString("localphase", muRes, i);
                string _kffact = GetNasisString("kffact", muRes, i);
                string tfact = GetNasisString("tfact", muRes, i);
                string sloper = GetNasisString("slope_r", muRes, i);
                string cokey = GetNasisString("cokey", muRes, i);
                string sandtotal_r = GetNasisString("sandtotal_r", muRes, i);
                string _hydgrp = GetNasisString("hydgrp", muRes, i);
                string taxorder = GetNasisString("taxorder", muRes, i);
                string kwfact = GetNasisString("kwfact", muRes, i);
                string om_r = GetNasisString("om_r", muRes, i);
                string hzthk_r = GetNasisString("hzthk_r", muRes, i);
                //string avgfragvol_r = GetNasisString("avgfragvol_r", muRes, i);  
                string avgfragvol_r = "0";
                string otherph = GetNasisString("otherph", muRes, i);
		string comppct_r = GetNasisString("comppct_r", muRes, i);
                if (tfact.length() == 0)
                   tfact = "0";
                if (sloper.length() == 0)
                   sloper = "0";
                if (sandtotal_r.length() == 0)
                   sandtotal_r = "0";
                if (om_r.length() == 0)
                   om_r = "0";
                if (hzthk_r.length() == 0)
                   hzthk_r = "0";
                if (avgfragvol_r.length() == 0)
                  avgfragvol_r = "0";
              

		cerr << "Processing " << cokey << " --- " << compname << " --- " << aSym << endl;
		//cerr << "_hydgrp=" << _hydgrp << endl;
		//cerr << "taxorder=" << taxorder << endl;
		//cerr << "kwfact=" << kwfact << endl;
		//cerr << "om_r=" << om_r << endl;
		//cerr << "hzthk_r=" << hzthk_r << endl;

		/* add on the [MAPUNIT] components */
		_sFullname += muSym + " " + muName;
		_sFullname = (char*)xmlEncodeSpecialChars(NULL, ConvertInput(_sFullname.c_str(), MY_ENCODING) );

		int cRow = 0;
		PGresult *coRes = queryComponents(conn, muKEY);
		int numComponents = PQntuples(coRes);

		/* Note: The horizKey is passed back so we can query the specific horizon by chkey
		*  This allows us to do all the XML here rather than generate downstream and pass it back
		* 
		*  Just makes more sense to me this way, then we know we've hit a good component/horizon pair
		*  and don't waste our time generating xml which won't be used if we fail to find a good horizon
		*  or something like that...
		* 
		*/	
		string compString, horizKey;
		while( getComponentString(compString, horizKey, coRes, cRow, conn) == true ){

                        //string compname = GetNasisString("compname", coRes, cRow);
                        //string tfact = GetNasisString("tfact", coRes, cRow);
                        //string sloper = GetNasisString("slope_r", coRes, cRow);
                        //string cokey = GetNasisString("cokey", coRes, cRow);

			/*
			* 1) in the component table, look for compkind = "miscellaneous area".  
			* This will be for water, pits, etc, that should be thrown out completely
			*/
			if( GetNasisString("compkind", coRes, cRow) == "Miscellaneous area" ){
				cRow++;
				continue;
			}

			/* Yay, we have a valid component string */
			if(compString.length() > 0 ){

				compString = (char*)xmlEncodeSpecialChars(NULL, ConvertInput(compString.c_str(), MY_ENCODING) );
				trim(compString);
				string xmlFilename = _sFullname + "\\" + compString + aExt;
				string sFullname = xmlFilename;
				

				transform( xmlFilename.begin(), xmlFilename.end(), xmlFilename.begin(), win2nixSlash );

				/* This is just the directory */
				string tmpDir = _sFullname.c_str();
				transform( tmpDir.begin(), tmpDir.end(), tmpDir.begin(), win2nixSlash );
				if( VERBOSE > 0 )
					cout << "\t" << tmpDir << endl;

				/* 
				 *  There is a create_directories function in boost/filesystem/convenience.hpp
				 *  I'd like to try using that...but CentOS has an older version where boost/filesystem
				 *  isn't really supported.
				 *  Plus there are some comments online that it still doesn't work properly with spaces
				 *  in the name....but might be worth a shot.
				 *
				 *  TODO : Verify the filename is correctly encoded.
				 *   < -> &lt;
				 *   > -> &gt;
				 *   " -> &quot;
				 * 
				 *	I believe use of xmlEncodeSpecialChars() is *good enough*, but it would take a pretty good
				 *  case example to verify. Seeing as how NRCS would know better than me I'll just leave this
				 *  note and hope they can patch it if necessary.
				*/
			
				char cmd[255];
				sprintf(cmd, "mkdir -p \"%s\"", tmpDir.c_str() );
				system(cmd);


				/* *** XML 
				*  Here we generate the XML with libxml2
				*  I only check the init rc values for success.
				*  Once generated, at this point the rest of the elements should complete because
				*  we got through all the components, horizons etc get a true value back
				* 
				*  If there is something that needs to be checked, simply compare the rc value.
				* 
				*** */
				
				int rc;
				xmlTextWriterPtr writer;
				xmlChar *tmp;
				/* init the XML Writer 
				* libxml2 - See xmlsoft.org/examples
				*/
				writer = xmlNewTextWriterFilename(xmlFilename.c_str(), 0);
				if( rc < 0 ){
					cerr << "Error creating the xml writer=[" << rc << "] filename:" << xmlFilename.c_str() << endl;
					exit_nicely(conn);
				}

				/* Start the document with the xml default for the version,
				* encoding ISO 8859-1 and the default for the standalone
				* declaration. */
				rc = xmlTextWriterStartDocument(writer, NULL, MY_ENCODING, NULL);
				if (rc < 0) {
					cerr << "testXmlwriterFilename: Error at xmlTextWriterStartDocument filename:" << xmlFilename.c_str() << endl;;
					exit_nicely(conn);
				}
				/* Start root element named "Obj" */
				rc = xmlTextWriterStartElement(writer, BAD_CAST "Obj");
				if (rc < 0) {
					cerr << "testXmlwriterFilename: Error at xmlTextWriterStartElement filename:" << xmlFilename.c_str() << endl;
					exit_nicely(conn);
				}

				rc = xmlTextWriterWriteElement(writer, BAD_CAST "Type", ConvertInput("SOIL", MY_ENCODING) );
				rc = xmlTextWriterWriteElement(writer, BAD_CAST "Filename", ConvertInput(sFullname.c_str(), MY_ENCODING) );
				rc = xmlTextWriterWriteElement(writer, BAD_CAST "Science", ConvertInput(SCIENCEVERSION, MY_ENCODING) );

				/* **** Start COMPONENT XML **** */

				/* slopelenusle */
				rc = AttrXML(writer, "Flt", "TYPICAL_LENGTH", 
						GetNasisString("slopelenusle_l", coRes, cRow), 
						GetNasisString("slopelenusle_r", coRes, cRow), 
						GetNasisString("slopelenusle_h", coRes, cRow),
						"",
						"U_METER" );
				/* slope */
				rc = AttrXML(writer, "Flt", "TYPICAL_STEEPNESS", 
						GetNasisString("slope_l", coRes, cRow), 
                                                sloper,
						//GetNasisString("slope_r", coRes, cRow), 
						GetNasisString("slope_h", coRes, cRow),
						"",
						"U_PERCENT" );
				/* tfact */
				rc = AttrXML(writer, "Flt", "SOIL_T_VALUE", 
						GetNasisString("tfact", coRes, cRow),
						"",
						"U_TON_P_AC_YR" );

				/* Hydrological Class */
				string sTiledHydrologicClass, sHydrologicClass;
				sTiledHydrologicClass = sHydrologicClass = "Nan";
				string hydgrp = GetNasisString("hydgrp", coRes, cRow);
				if ( hydgrp.length() != 0 ){
					/* Extract undrained and drained hyd groups from possibly concatenated string "A/D" */
					string undrained, drained;
					if (hydgrp.length() == 3){
						/* If A/D, first category is drained (low runoff), latter category is undrained (high runoff) */
						drained = hydgrp[0];
						undrained = hydgrp[2];
					}else{
						/* drained is undefined, so set same as undrained */
						undrained = hydgrp[0];
						drained = undrained;
					}
					sTiledHydrologicClass 	= Rusle2HydrologicClass(drained);
					sHydrologicClass 		= Rusle2HydrologicClass(undrained);
				}
				/* TiledHydrologicClass */
				rc = AttrXML(writer, "Lst", "TILED_HYDROLOGIC_CLASS", 
						sTiledHydrologicClass,
						"",
						"" );
				/* HydrologicClass */
				rc = AttrXML(writer, "Lst", "HYDROLOGIC_CLASS", 
						sHydrologicClass,
						"",
						"" );
				/* Soil Description */
				rc = AttrXML(writer, "Str", "SOIL_DESCRIP", 
						GetNasisString("geomdesc", coRes, cRow),
						"",
						"" );
				/* **** END COMPONENT XML **** */

				/* **** START HORIZON XML **** */
				PGresult *hzXMLRes = queryHorizonForXML(conn, horizKey);

				/* 
				*  Get more values from the chorizon table.  Need to set the NASIS organic matter value into two places.  The first is just a place-holder,
				*  while the second is the value actually used in the calculations
				*/
				/* Organic Matter */
				rc = AttrXML(writer, "Flt", "NASIS_OM_REP_HOR_1", 
						GetNasisString("om_l", hzXMLRes, 0), 
						GetNasisString("om_r", hzXMLRes, 0), 
						GetNasisString("om_h", hzXMLRes, 0),
						"",
						"U_PERCENT" );
				/* Organic Matter */
				rc = AttrXML(writer, "Flt", "ORGANIC_MATTER", 
						GetNasisString("om_l", hzXMLRes, 0), 
						GetNasisString("om_r", hzXMLRes, 0), 
						GetNasisString("om_h", hzXMLRes, 0),
						"",
						"U_PERCENT" );
				/* I don't know what this is... */
				rc = AttrXML(writer, "Flt", "NASIS_PH_1TO1_H20_REP_HOR_1", 
						GetNasisString("ph1to1h2o_l", hzXMLRes, 0), 
						GetNasisString("ph1to1h2o_r", hzXMLRes, 0), 
						GetNasisString("ph1to1h2o_h", hzXMLRes, 0),
						"",
						"U_PH_UNITS" );
				/* Erodibility */
				string taxOrder = GetNasisString("taxorder", coRes, cRow);
				string taxSubGrp = GetNasisString("taxsubgrp", coRes, cRow);
				/*
				*  User-entry ERODIBILITY values are now stored in ERODIBILITY_HAND instead.
				*  We also set ERODIBILITY_OPTION_PTR to "ERODIBILITY_OPTION_SET_BY_USER", although that is the default value.
				*  We still set ERODIBILITY directly, even though it is now calculated, because we are faking the science date.
				*/
				string kffact = GetNasisString("kffact", hzXMLRes, 0);
                                //string sandtotal_r = GetNasisString("sandtotal_r", hzXMLRes, 0);
				if( taxOrder == "Histosols" || taxSubGrp.find("Histic") != string::npos || kffact.length() == 0 ){
					/* for Histosols, set the erodibility = 0.02 */
					rc = AttrXML(writer, "Flt", "ERODIBILITY", "0.02", "", "U_ENGLISH_EROD" );
					rc = AttrXML(writer, "Flt", "ERODIBILITY_HAND", "0.02", "", "U_ENGLISH_EROD" );
					rc = AttrXML(writer, "SbR", "ERODIBILITY_OPTION_PTR", "ERODIBILITY_OPTION_SET_BY_USER", "", "" );
					
				}else{
					/* good kffact value */
					rc = AttrXML(writer, "Flt", "ERODIBILITY", kffact, "", "U_ENGLISH_EROD" );
					rc = AttrXML(writer, "Flt", "ERODIBILITY_HAND", kffact, "", "U_ENGLISH_EROD" );
					rc = AttrXML(writer, "SbR", "ERODIBILITY_OPTION_PTR", "ERODIBILITY_OPTION_SET_BY_USER", "", "" );
				}
				/* TODO: The above and below can probably be condensed...
				*  histosols back into this at Lightle's request (051026) because wanted to be able to open these soils for SCI calculations 
				*/
				if( taxOrder == "Histosols" || taxSubGrp.find("Histic") != string::npos || kffact.length() == 0 ){
					/* Have already put in a bogus erodibility = 0.02 for these, so might as well give them bogus texture! */
					rc = AttrXML(writer, "Flt", "SAND",  "0.0",  "", "U_PERCENT");
					rc = AttrXML(writer, "Flt", "SILT",  "0.0",  "", "U_PERCENT");
					rc = AttrXML(writer, "Flt", "CLAY",  "100.0",  "", "U_PERCENT");
				}else{
					bool changedVals = false;
					string sandStr = GetNasisString("sandtotal_r", hzXMLRes, 0);
					string siltStr = GetNasisString("silttotal_r", hzXMLRes, 0);
					string clayStr = GetNasisString("claytotal_r", hzXMLRes, 0);
					/* Note: I don't think they CAN match "NaN"...but I'll leave it for now */
					if( sandStr.length() == 0 || sandStr == "NaN" ){ sandStr = "0"; changedVals = true; }
					if( siltStr.length() == 0 || siltStr == "NaN" ){ siltStr = "0"; changedVals = true; }
					if( clayStr.length() == 0 || clayStr == "NaN" ){ clayStr = "0"; changedVals = true; }
					/* Ensure they add up */
					if( !changedVals ) {
						double sandVal = atof( sandStr.c_str() );
						double siltVal = atof( siltStr.c_str() );
						double clayVal = atof( clayStr.c_str() );
						if( fabs(sandVal + siltVal + clayVal - 100.0) > 0.1)
							changedVals = true;
						
					}
					if(changedVals){
						/* something changed, so warn of that and store nonsense values (unless is Histosol) */
						rc = AttrXML(writer, "Flt", "SAND",  "0.0",  "", "U_PERCENT");
						rc = AttrXML(writer, "Flt", "SILT",  "0.0",  "", "U_PERCENT");
						rc = AttrXML(writer, "Flt", "CLAY",  "0.0",  "", "U_PERCENT");
					}else{
						/* Everything OK */
						rc = AttrXML(writer, "Flt", "SAND",  sandStr,  "", "U_PERCENT");
						rc = AttrXML(writer, "Flt", "SILT",  siltStr,  "", "U_PERCENT");
						rc = AttrXML(writer, "Flt", "CLAY",  clayStr,  "", "U_PERCENT");
						
					}
				}
				/* Add NASIS parameters to identify this soil. */
				rc = AttrXML(writer, "Str", "NASIS_MUSYM", muSym, "1", "");
				rc = AttrXML(writer, "Str", "NASIS_SASYM", muName, "1", "");


				/* Add new NASIS parameters desired by Dabney 2010-04-17 */
				/* CeC */
				rc = AttrXML(writer, "Flt", "NASIS_CEC_7", 
						GetNasisString("cec7_l", hzXMLRes, 0), 
						GetNasisString("cec7_r", hzXMLRes, 0), 
						GetNasisString("cec7_h", hzXMLRes, 0),
						"",
						"U_MEQ_P_100_ML" );
				/* Water Content Third Bar */
				rc = AttrXML(writer, "Flt", "NASIS_WATER_CONTENT_THIRD_BAR", 
						GetNasisString("wthirdbar_l", hzXMLRes, 0), 
						GetNasisString("wthirdbar_r", hzXMLRes, 0), 
						GetNasisString("wthirdbar_h", hzXMLRes, 0),
						"",
						"U_PERCENT" );
				/* Water Content Fifteen Bar */
				rc = AttrXML(writer, "Flt", "NASIS_WATER_CONTENT_15_BAR", 
						GetNasisString("wfifteenbar_l", hzXMLRes, 0), 
						GetNasisString("wfifteenbar_r", hzXMLRes, 0), 
						GetNasisString("wfifteenbar_h", hzXMLRes, 0),
						"",
						"U_PERCENT" );
				/* Bulk Density Third Bar */
				rc = AttrXML(writer, "Flt", "NASIS_BULK_DENSITY_THIRD_BAR", 
						GetNasisString("dbthirdbar_l", hzXMLRes, 0), 
						GetNasisString("dbthirdbar_r", hzXMLRes, 0), 
						GetNasisString("dbthirdbar_h", hzXMLRes, 0),
						"",
						"U_PERCENT" );
				

				/* **** END HORIZON XML **** */
				

				xmlTextWriterEndDocument(writer);
				xmlFreeTextWriter(writer);
				/* END XML */


				/* output
				 * For now we'll just output as we need to
				 * but theoretically we could add a function here to output in different modes
				 */	
				fprintf(ofile, "INSERT INTO map_soils VALUES('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s',%s,%s,'%s',%s,'%s','%s','%s',%s,%s,%s,'%s',%s);\n",
							aSym.c_str(), 
							muSym.c_str(),
							muKEY.c_str(),
							EscapeSingleQuotes(_sFullname).c_str(),
							EscapeSingleQuotes(compString).c_str(),
                                                        muName.c_str(), 
                                                        compname.c_str(),
                                                        compkind.c_str(),
                                                        majcompflag.c_str(),
                                                        localphase.c_str(),
                                                        _kffact.c_str(),
                                                        tfact.c_str(),
                                                        sloper.c_str(),
                                                        cokey.c_str(),
                                                        sandtotal_r.c_str(),
                                                        _hydgrp.c_str(),
                                                        taxorder.c_str(),
                                                        kwfact.c_str(),
                                                        om_r.c_str(),
                                                        hzthk_r.c_str(),
                                                        avgfragvol_r.c_str(),
							otherph.c_str(),
							comppct_r.c_str()
					);
//				cout << aSym << ","_sFullname << "\\" << compString << endl;

			}/* End check for compString.length */

			/* Empty component strings are considered invalid
			 * So be sure to empty the component before the next iter
			 */
			cRow++;
			compString.erase(0);
		}

		
	}

#ifndef NDEBUG
	cout << "leaving processArea()" << endl;
#endif	
	return pROW < PQntuples(res) ? true : false;
}