/** @file ParserTherion.java
*
* @author marco corvi
* @date may 2012
*
* @brief TopoDroid Therion parser
*
* --------------------------------------------------------
* Copyright This sowftare is distributed under GPL-3.0 or later
* See the file COPYING.
* ----------------------------------------------------------
*/
package com.topodroid.DistoX;
import java.io.File;
import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.Stack;
import java.util.regex.Pattern;
import android.util.Log;
// TODO this class can be made extend ImportParser
public class ParserTherion
{
final static String EMPTY = "";
public String mName = null; // survey name
public String mDate = null; // survey date
public String mTeam = "";
public String mTitle = "";
public float mDeclination = 0.0f; // one-survey declination
private boolean mApplyDeclination = false;
Stack< ParserTherionState > mStates; // states stack (LIFO)
void pushState( ParserTherionState state )
{
mStates.push( state );
}
ParserTherionState popState()
{
return mStates.pop();
}
/** fix station:
* fix stations are supposed to be referred to the same coord system
*/
class Fix
{
// private CS cs;
String name;
float e, n, z; // north east, vertical (upwards)
public Fix( String nm, float e0, float n0, float z0 )
{
name = nm;
e = e0;
n = n0;
z = z0;
}
}
class Station
{
String name;
String comment;
long flag;
public Station( String n, String c, long f )
{
name = n;
comment = c;
flag = f;
}
}
private ArrayList< Fix > fixes;
private ArrayList< Station > stations;
private ArrayList< ParserShot > shots; // centerline shots
private ArrayList< ParserShot > splays; // splay shots
public int getShotNumber() { return shots.size(); }
public int getSplayNumber() { return splays.size(); }
public ArrayList< ParserShot > getShots() { return shots; }
public ArrayList< ParserShot > getSplays() { return splays; }
public ArrayList< Station > getStations() { return stations; }
public ArrayList< Fix > getFixes() { return fixes; }
// same as in ImportParser.java
public String initStation()
{
for ( ParserShot sh : shots ) {
if ( sh.from != null && sh.from.length() > 0 ) return sh.from;
}
return "0";
}
public ParserTherion( String filename, boolean apply_declination ) throws ParserException
{
fixes = new ArrayList< Fix >();
stations = new ArrayList< Station >();
shots = new ArrayList< ParserShot >();
splays = new ArrayList< ParserShot >();
mStates = new Stack< ParserTherionState >();
mApplyDeclination = apply_declination;
ParserTherionState state = new ParserTherionState();
readFile( filename, "", state );
}
private String nextLine( BufferedReader br ) throws IOException
{
StringBuilder ret = new StringBuilder();
{
String line = br.readLine();
if ( line == null ) return null; // EOF
while ( line != null && line.endsWith( "\\" ) ) {
ret.append( line.replace( '\\', ' ' ) ); // FIXME
line = br.readLine();
}
if ( line != null ) ret.append( line );
}
return ret.toString();
}
private String extractStationName( String fullname )
{
int idx = fullname.indexOf('@');
if ( idx > 0 ) {
return fullname.substring(0,idx); // + "@" + path + "." + vals[1].substring(idx+1);
}
return fullname;
}
/** read input file
* @param filename name of the file to parse
* @param basepath survey pathname base
* @param usd use survey declination
* @param sd survey decliunation
* @param ul units of length (as multiple of 1 meter)
* @param ub units of bearing (as multiple of 1 degree)
* @param uc units of clino
*/
private void readFile( String filename, String basepath, ParserTherionState state )
throws ParserException
{
String path = basepath; // survey pathname(s)
int[] survey_pos = new int[50]; // current survey pos in the pathname FIXME max 50 levels
int ks = 0; // survey index
int jFrom = 0;
int jTo = 1;
int jLength = 2;
int jCompass = 3;
int jClino = 4;
Pattern pattern = Pattern.compile( "\\s+" );
try {
String dirname = "./";
int i = filename.lastIndexOf('/');
if ( i > 0 ) dirname = filename.substring(0, i+1);
// System.out.println("readFile dir " + dirname + " filename " + filename );
// TDLog.Log( TDLog.LOG_THERION, "reading file " + filename + " dir " + dirname );
FileReader fr = new FileReader( filename );
BufferedReader br = new BufferedReader( fr );
String line = nextLine( br );
while ( line != null ) {
// TDLog.Log( TDLog.LOG_THERION, "TH " + line );
// Log.v( TopoDroidApp.TAG, "TH " + state.in_survey + " " + state.in_centerline + " " + state.in_data + " : " + line );
line = line.trim();
int pos = line.indexOf( '#' );
if ( pos >= 0 ) {
line = line.substring( 0, pos );
}
if ( line.length() > 0 ) {
String[] vals = pattern.split(line); // line.split( "\\s+" );
// int vals_len = 0;
// for ( int k=0; k<vals.length; ++k ) {
// vals[vals_len] = vals[k];
// if ( vals[vals_len].length() > 0 ) {
// ++ vals_len;
// }
// }
// Log.v( TopoDroidApp.TAG, "vals " + vals.length + " " + vals_len );
int vals_len = vals.length;
if ( vals_len > 0 ) {
String cmd = vals[0];
if ( cmd.equals("encoding" ) ) {
// ignore
} else if ( cmd.equals("import") ) {
// ignore
} else if ( ! state.in_centerline && cmd.equals("grade") ) {
// ignore
} else if ( cmd.equals("revise") ) {
// ignore
} else if ( cmd.equals("join") ) {
// ignore
} else if ( cmd.equals("input") ) { // ignore
// int j = 1;
// while ( vals[j] != null ) {
// if ( vals[j].length() > 0 ) {
// filename = vals[j];
// if ( filename.endsWith( ".th" ) ) {
// readFile( dirname + '/' + filename,
// path,
// use_survey_declination, survey_declination,
// units_len, units_ber, units_cln );
// }
// break;
// }
// }
} else if ( cmd.equals("surface") ) {
// TODO check not already in_surface
state.in_surface = true;
} else if ( cmd.equals("map") ) {
// TODO check not already in_map
state.in_map = true;
} else if ( cmd.equals("scrap") ) {
// TODO check not already in_scrap
state.in_scrap = true;
} else if ( state.in_scrap && cmd.equals("line") ) {
// TODO check not already in_line
state.in_line = true;
} else if ( state.in_scrap && cmd.equals("area") ) {
// TODO check not already in_area
state.in_area = true;
} else if ( state.in_line && cmd.equals("endline") ) {
state.in_line = false;
} else if ( state.in_area && cmd.equals("endarea" ) ) {
state.in_area = false;
} else if ( state.in_scrap && cmd.equals("endscrap" ) ) {
state.in_scrap = false;
} else if ( state.in_map && cmd.equals("endmap" ) ) {
state.in_map = false;
} else if ( state.in_surface && cmd.equals("endsurface" ) ) {
state.in_surface = false;
} else if ( state.in_map || state.in_surface || state.in_scrap || state.in_line || state.in_area ) {
// ignore
} else if ( cmd.equals("survey") ) {
survey_pos[ks] = path.length(); // set current survey pos in pathname
path = path + "." + vals[1]; // add survey name to path
++ks;
pushState( state );
state = new ParserTherionState( state );
state.mSurveyLevel ++;
state.in_survey= true;
// parse survey id
if ( mName == null ) {
mName = vals[1];
}
// parse survey options
for ( int j=2; j<vals_len; ++j ) {
if ( vals[j].equals("-declination") && j+1 < vals_len ) {
try {
state.mDeclination = Float.parseFloat( vals[j+1] );
++j;
if ( j+1 < vals_len ) { // check for units
state.mDeclination *= parseAngleUnit( vals[j+1] );
++j;
}
if ( ! mApplyDeclination ) mDeclination = state.mDeclination;
} catch ( NumberFormatException e ) {
TDLog.Error( "therion parser error: -declination " + line );
}
} else if ( vals[j].equals("-title") && j+1 < vals_len ) {
for ( ++j; j<vals_len; ++j ) {
if ( vals[j].length() == 0 ) continue;
if ( vals[j].startsWith("\"") ) {
mTitle = vals[j].substring(1);
for ( ++j; j<vals_len; ++j ) {
if ( vals[j].length() == 0 ) continue;
if ( vals[j].endsWith( "\"" ) ) {
mTitle += " " + vals[j].substring(0, vals[j].length()-1);
break;
} else {
mTitle += " " + vals[j];
}
}
} else {
mTitle = vals[j];
}
break;
}
}
}
} else if ( state.in_centerline ) {
if ( cmd.equals("endcenterline") || cmd.equals("endcentreline") ) {
// state.in_data = false;
// state.in_centerline = false;
state = popState();
} else if ( cmd.equals("date") ) {
String date = vals[1];
if ( mDate == null ) mDate = date; // save centerline date
} else if ( cmd.equals("team") ) {
for ( int j = 1; j < vals_len; ++j ) {
mTeam += " " + vals[j];
}
// } else if ( cmd.equals("explo-date") ) {
// } else if ( cmd.equals("explo-team") ) {
// } else if ( cmd.equals("instrument") ) {
} else if ( cmd.equals("calibrate") ) {
boolean clen = false;
boolean cber = false;
boolean ccln = false;
for ( int k=1; k<vals_len - 1; ++k ) {
if ( vals[k].equals("length") || vals[k].equals("tape") ) clen = true;
if ( vals[k].equals("compass") || vals[k].equals("bearing") ) cber = true;
if ( vals[k].equals("clino") || vals[k].equals("gradient") ) ccln = true;
}
float zero = 0.0f;
float scale = 1.0f;
try {
scale = Float.parseFloat( vals[vals_len-1] );
zero = Float.parseFloat( vals[vals_len-2] );
} catch ( NumberFormatException e ) {
TDLog.Error( "therion parser error: scale/zero " + line );
zero = scale;
}
if ( clen ) {
state.mZeroLen = zero;
state.mScaleLen = scale;
}
if ( cber ) {
state.mZeroBer = zero;
state.mScaleBer = scale;
}
if ( ccln ) {
state.mZeroCln = zero;
state.mScaleCln = scale;
}
} else if ( cmd.equals("units") ) { // units quantity_list [factor] unit
boolean ulen = false;
boolean uber = false;
boolean ucln = false;
for ( int k=1; k<vals_len - 1; ++k ) {
if ( vals[k].equals("length") || vals[k].equals("tape") ) ulen = true;
if ( vals[k].equals("compass") || vals[k].equals("bearing") ) uber = true;
if ( vals[k].equals("clino") || vals[k].equals("gradient") ) ucln = true;
}
float factor = 1.0f;
try {
factor = Float.parseFloat( vals[vals_len-2] );
} catch ( NumberFormatException e ) {
TDLog.Debug( "therion parser: units without factor " + line ); // this is OK
}
if ( ulen ) {
state.mUnitLen = factor * parseLengthUnit( vals[vals_len-1] );
}
if ( uber ) {
state.mUnitBer = factor * parseAngleUnit( vals[vals_len-1] );
}
if ( ucln ) {
state.mUnitCln = factor * parseAngleUnit( vals[vals_len-1] );
}
} else if ( cmd.equals("sd") ) {
// ignore
} else if ( cmd.equals("grade") ) {
// ignore
} else if ( cmd.equals("declination") ) {
if ( 1 < vals_len ) {
try {
float declination = Float.parseFloat( vals[1] );
if ( 2 < vals_len ) {
declination *= parseAngleUnit( vals[2] );
}
state.mDeclination = declination;
if ( ! mApplyDeclination ) mDeclination = state.mDeclination;
} catch ( NumberFormatException e ) {
TDLog.Error( "therion parser error: declination " + line );
}
}
} else if ( cmd.equals("instrument") ) {
// ignore
} else if ( cmd.equals("flags") ) {
if ( vals_len >= 2 ) {
if ( vals[1].startsWith("dup") || vals[1].startsWith("splay") ) {
state.mDuplicate = true;
} else if ( vals[1].startsWith("surf") ) {
state.mSurface = true;
} else if ( vals[1].equals("not") && vals_len >= 3 ) {
if ( vals[2].startsWith("dup") || vals[2].startsWith("splay") ) {
state.mDuplicate = false;
} else if ( vals[2].startsWith("surf") ) {
state.mSurface = false;
}
}
}
} else if ( cmd.equals("cs") ) {
// TODO cs
} else if ( cmd.equals("mark") ) { // ***** fix station east north Z (ignored std-dev's)
String flag_str = vals[ vals_len - 1 ];
int flag = 0;
if ( "painted".equals( vals[ vals_len-1 ] ) ) {
flag = CurrentStation.STATION_PAINTED;
} else if ( "fixed".equals( vals[ vals_len-1 ] ) ) {
flag = CurrentStation.STATION_FIXED;
}
// Log.v("DistoX", "Therion parser: mark flag " + flag + " " + flag_str );
if ( flag != 0 ) {
for ( int k=1; k<vals_len-1; ++k ) {
String name = extractStationName( vals[k] );
// Log.v("DistoX", "mark station " + name );
boolean must_add = true;
for ( Station st : stations ) if ( st.name.equals( name ) ) {
must_add = false;
st.flag = flag;
break;
}
if ( must_add ) stations.add( new Station( name, "", flag ) );
}
}
} else if ( cmd.equals("station") ) { // ***** station name "comment"
if ( vals_len > 2 ) {
String name = extractStationName( vals[1] );
String comment = vals[2];
if ( comment.startsWith( "\"" ) ) {
int len = comment.length();
StringBuilder sb = new StringBuilder();
sb.append( comment.substring( 1 ) );
for ( int kk=3; kk<vals_len; ++kk ) {
if ( vals[kk].endsWith("\"") ) {
sb.append( " " + vals[kk].substring(0, vals[kk].length()-1) );
break;
} else {
sb.append( " " + vals[kk] );
}
}
comment = sb.toString();
}
// Log.v("DistoX", "Therion parser station " + name + " comment <" + comment + ">" );
if ( comment.length() > 0 ) {
boolean must_add = true;
for ( Station st : stations ) if ( st.name.equals( name ) ) {
must_add = false;
st.comment = comment;
break;
}
if ( must_add ) stations.add( new Station( name, comment, 0 ) );
}
}
} else if ( cmd.equals("fix") ) { // ***** fix station east north Z (ignored std-dev's)
if ( vals_len > 4 ) {
String name = extractStationName( vals[1] );
try {
fixes.add( new Fix( name,
Float.parseFloat( vals[2] ),
Float.parseFloat( vals[3] ),
Float.parseFloat( vals[4] ) ) );
} catch ( NumberFormatException e ) {
TDLog.Error( "therion parser error: fix " + line );
}
}
} else if ( cmd.equals("equate") ) {
if ( vals_len > 2 ) {
String from, to;
int idx = vals[1].indexOf('@');
if ( idx > 0 ) {
from = vals[1].substring(0,idx); // + "@" + path + "." + vals[1].substring(idx+1);
} else {
from = vals[1]; // + "@" + path;
}
for ( int j=2; j<vals_len; ++j ) {
idx = vals[j].indexOf('@');
if ( idx > 0 ) {
to = vals[j].substring(0,idx); // + "@" + path + "." + vals[j].substring(idx+1);
} else {
to = vals[j]; // + "@" + path;
}
shots.add( new ParserShot( state.mPrefix + from + state.mSuffix, state.mPrefix + to + state.mSuffix,
0.0f, 0.0f, 0.0f, 0.0f, 0, true, false, false, "" ) );
}
}
} else if ( cmd.startsWith("explo") ) { // explo-date explo-team
// ignore
} else if ( cmd.equals("break") ) {
// ignore
} else if ( cmd.equals("infer") ) {
// ignore
} else if ( cmd.equals("group") ) {
pushState( state );
state = new ParserTherionState( state );
} else if ( cmd.equals("endgroup") ) {
state = popState();
} else if ( cmd.equals("walls") ) {
// ignore
} else if ( cmd.equals("vthreshold") ) {
// ignore
} else if ( cmd.equals("extend") ) {
if ( vals_len == 2 ) {
state.mExtend = parseExtend( vals[1], state.mExtend );
} else { // not implemented "extend value station [station]
}
} else if ( cmd.equals("station_names") ) {
state.mPrefix = "";
state.mSuffix = "";
if ( vals_len > 1 ) {
int off = vals[1].indexOf( '"' );
if ( off >= 0 ) {
int end = vals[1].lastIndexOf( '"' );
state.mPrefix = vals[1].substring(off+1, end );
}
if ( vals_len > 2 ) {
off = vals[2].indexOf( '"' );
if ( off >= 0 ) {
int end = vals[2].lastIndexOf( '"' );
state.mSuffix = vals[2].substring(off+1, end );
}
}
}
} else if ( cmd.equals("data") ) {
// data normal from to length compass clino ...
if ( vals[1].equals("normal") ) {
jFrom = jTo = jLength = jCompass = jClino = -1;
int j0 = 0;
for ( int j=2; j < vals_len; ++j ) {
if ( vals[j].equals("from") ) {
jFrom = j0; ++j0;
} else if ( vals[j].equals("to") ) {
jTo = j0; ++j0;
} else if ( vals[j].equals("length") || vals[j].equals("tape") ) {
jLength = j0; ++j0;
} else if ( vals[j].equals("compass") || vals[j].equals("bearing") ) {
jCompass = j0; ++j0;
} else if ( vals[j].equals("clino") || vals[j].equals("gradient") ) {
jClino = j0; ++j0;
} else {
++j0;
}
}
state.in_data = (jFrom >= 0) && (jTo >= 0) && (jLength >= 0) && (jCompass >= 0) && (jClino >= 0);
// TODO other style syntax
// } else if ( vals[1].equals("topofil") ) {
// } else if ( vals[1].equals("diving") ) {
// } else if ( vals[1].equals("cartesian") ) {
// } else if ( vals[1].equals("cylpolar") ) {
// } else if ( vals[1].equals("dimensions") ) {
// } else if ( vals[1].equals("nosurvey") ) {
}
} else if ( state.in_data && vals_len >= 5 ) {
// FIXME
try {
String from = vals[jFrom];
String to = vals[jTo];
float len = Float.parseFloat( vals[jLength] );
float ber = Float.parseFloat( vals[jCompass] );
float cln = Float.parseFloat( vals[jClino] );
len = state.mZeroLen + (len*state.mUnitLen) / state.mScaleLen;
if ( mApplyDeclination ) {
ber = state.mZeroBer + (ber*state.mUnitBer + state.mDeclination) / state.mScaleBer;
} else {
ber = state.mZeroBer + (ber*state.mUnitBer) / state.mScaleBer;
}
cln = state.mZeroCln + (cln*state.mUnitCln) / state.mScaleCln;
// TODO add shot
if ( to.equals("-") || to.equals(".") ) { // splay shot
// from = from + "@" + path;
// FIXME splays
shots.add( new ParserShot( state.mPrefix + from + state.mSuffix, EMPTY,
len, ber, cln, 0.0f,
state.mExtend, state.mDuplicate, state.mSurface, false, "" ) );
} else {
// from = from + "@" + path;
// to = to + "@" + path;
// Log.v( TopoDroidApp.TAG, "add shot " + from + " -- " + to);
shots.add( new ParserShot( state.mPrefix + from + state.mSuffix, state.mPrefix + to + state.mSuffix,
len, ber, cln, 0.0f,
state.mExtend, state.mDuplicate, state.mSurface, false, "" ) );
}
} catch ( NumberFormatException e ) {
TDLog.Error( "therion parser error: data " + line );
}
}
} else if ( cmd.equals("centerline") || cmd.equals("centreline") ) {
pushState( state );
state = new ParserTherionState( state );
state.in_centerline = true;
state.in_data = false;
} else if ( cmd.equals("endsurvey") ) {
state = popState();
--ks;
path = path.substring(survey_pos[ks]); // return to previous survey_pos in path
state.in_survey = ( ks > 0 );
}
}
}
line = nextLine( br );
}
} catch ( IOException e ) {
// TODO
throw new ParserException();
}
if ( mDate == null ) {
mDate = TopoDroidUtil.currentDate();
}
TDLog.Log( TDLog.LOG_THERION, "ParserTherion shots "+ shots.size() +" splays "+ splays.size() +" fixes "+ fixes.size() );
// Log.v( TopoDroidApp.TAG, "ParserTherion shots "+ shots.size() + " splays "+ splays.size() +" fixes "+ fixes.size() );
}
float parseAngleUnit( String unit )
{
// not handled "percent"
if ( unit.startsWith("min") ) return 1/60.0f;
if ( unit.startsWith("grad") ) return (float)TopoDroidUtil.GRAD2DEG;
if ( unit.startsWith("mil") ) return (float)TopoDroidUtil.GRAD2DEG;
// if ( unit.startsWith("deg") ) return 1.0f;
return 1.0f;
}
float parseLengthUnit( String unit )
{
if ( unit.startsWith("c") ) return 0.01f; // cm centimeter
if ( unit.startsWith("f") ) return (float)TopoDroidUtil.FT2M; // ft feet
if ( unit.startsWith("i") ) return (float)TopoDroidUtil.IN2M; // in inch
if ( unit.startsWith("milli") || unit.equals("mm") ) return 0.001f; // mm millimeter
if ( unit.startsWith("y") ) return (float)TopoDroidUtil.YD2M; // yd yard
// if ( unit.startsWith("m") ) return 1.0f;
return 1.0f;
}
int parseExtend( String extend, int old_extend )
{
// skip: hide, start
if ( extend.equals("hide") || extend.equals("start") ) {
return old_extend;
}
if ( extend.equals("left") || extend.equals("reverse") ) {
return DistoXDBlock.EXTEND_LEFT;
}
if ( extend.startsWith("vert") ) {
return DistoXDBlock.EXTEND_VERT;
}
if ( extend.startsWith("ignore") ) {
return DistoXDBlock.EXTEND_IGNORE;
}
// if ( extend.equals("right") || extend.equals("normal") ) {
// return DistoXDBlock.EXTEND_RIGHT;
// }
return DistoXDBlock.EXTEND_RIGHT;
}
}