001/*
002 *   LaWGSGeomReader  -- A class that can read a LaWGS formatted geometry file.
003 *
004 *   Copyright (C) 2009-2016, Joseph A. Huwaldt
005 *   All rights reserved.
006 *   
007 *   This library is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *   
012 *   This library is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 *   Lesser General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public License
018 *   along with this program; if not, write to the Free Software
019 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
020 *   Or visit:  http://www.gnu.org/licenses/lgpl.html
021 */
022package geomss.geom.reader;
023
024import geomss.geom.*;
025import static geomss.geom.reader.AbstractGeomReader.RESOURCES;
026import jahuwaldt.js.util.TextTokenizer;
027import java.io.File;
028import java.io.FileReader;
029import java.io.IOException;
030import java.io.LineNumberReader;
031import java.text.MessageFormat;
032import java.util.Locale;
033import static java.util.Objects.isNull;
034import static java.util.Objects.nonNull;
035import static java.util.Objects.requireNonNull;
036import javolution.text.Text;
037import javolution.text.TypeFormat;
038import javolution.util.FastTable;
039
040/**
041 * A {@link GeomReader} for reading vehicle geometry from a LaWGS (WGS) formatted geometry
042 * file. This is the Langley Wireframe Geometry Standard (LaWGS) defined in NASA-TM-85767.
043 * This implementation ignores the local symmetry and local transformation information!
044 *
045 * <p> Modified by: Joseph A. Huwaldt </p>
046 *
047 * @author Joseph A. Huwaldt, Date: April 27, 2009
048 * @version September 9, 2016
049 */
050public class LaWGSGeomReader extends AbstractGeomReader {
051
052    //  Debug output flag.
053    private static final boolean DEBUG = false;
054
055    //  A brief description of the data read by this reaader.
056    private static final String DESCRIPTION = RESOURCES.getString("lawgsDescription");
057
058    //  The preferred file extension for files of this reader's type.
059    public static final String EXTENSION = "wgs";
060
061    //  Any of the delimiters that could be found in the file.
062    private static final String DELIMITERS = " ,/";
063
064    /**
065     * Returns a string representation of the object. This will return a brief description
066     * of the format read by this reader.
067     *
068     * @return A brief description of the format read by this reader.
069     */
070    @Override
071    public String toString() {
072        return DESCRIPTION;
073    }
074
075    /**
076     * Returns the preferred file extension (not including the ".") for files of this
077     * GeomReader's type.
078     *
079     * @return The preferred file extension for files of this readers type.
080     */
081    @Override
082    public String getExtension() {
083        return EXTENSION;
084    }
085
086    /**
087     * Method that determines if this reader can read geometry from the specified input
088     * file.
089     *
090     * @param inputFile The input file containing the geometry to be read in.
091     * @return GeomReader.NO if the file format is not recognized by this reader.
092     *         GeomReader.YES if the file format is definitely recognized by this reader.
093     *         GeomReader.MAYBE if the file format might be readable by this reader, but
094     *         that can't easily be determined without actually reading the file.
095     * @throws java.io.IOException If there is a problem reading from the specified
096     * file.
097     */
098    @Override
099    public int canReadData(File inputFile) throws IOException {
100
101        int response = NO;
102        String name = inputFile.getName();
103        name = name.toLowerCase().trim();
104        if (name.endsWith(".wgs"))
105            response = MAYBE;
106
107        return response;
108    }
109
110    /**
111     * Reads in a LaWGS geometry file from the specified input file and returns a
112     * {@link PointVehicle} object that contains the geometry from the LaWGS file.
113     * <p>
114     * WARNING: This file format is not unit aware. You must set the units
115     * to be used by calling "setFileUnits()" before calling this method!
116     * </p>
117     *
118     * @param inputFile The input file containing the geometry to be read in. May not be
119     *                  null.
120     * @return A {@link PointVehicle} object containing the geometry read in from the
121     *         file. If the file has no geometry in it, then this list will have no
122     *         components in it (will have a size() of zero).
123     * @throws IOException If there is a problem reading the specified file.
124     * @see #setFileUnits(javax.measure.unit.Unit) 
125     */
126    @Override
127    public PointVehicle read(File inputFile) throws IOException {
128        requireNonNull(inputFile);
129        _warnings.clear();
130
131        // Create an empty vehicle.
132        PointVehicle vehicle = PointVehicle.newInstance();
133
134        //  LaWGS files are required to be in ASCII with U.S. style number formatting.
135        //  Get the default locale and save it.  Then change the default locale to US.
136        Locale defLocale = Locale.getDefault();
137        Locale.setDefault(Locale.US);
138
139        // Create a reader to access the ASCII file.
140        try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) {
141
142
143            //  Skip any leading blank lines.
144            String aLine = readLine(reader).trim();
145            while (aLine.length() == 0)
146                aLine = readLine(reader).trim();
147
148            //  Extract the configuration identification.
149            String idcomf = parseQuotedText(Text.valueOf(aLine), "'");
150            if (isNull(idcomf))
151                idcomf = inputFile.getName();
152
153            // Set the name as the vehicle name.
154            vehicle.setName(idcomf);
155
156            //  There is only a single component, so create it.
157            PointComponent comp = PointComponent.newInstance("Component");
158            vehicle.add(comp);
159
160            // Loop over all the arrays stored in the file.
161            aLine = reader.readLine();
162            while (nonNull(aLine)) {
163
164                String arrName = parseQuotedText(Text.valueOf(aLine), "'");
165                PointArray array = readArray(reader, arrName);
166                comp.add(array);
167
168                // Begin searching for the next component.
169                aLine = reader.readLine();
170            }
171
172        } finally {
173            //  Restore the default locale.
174            Locale.setDefault(defLocale);
175        }
176
177        return vehicle;
178    }
179
180    /**
181     * This method always returns <code>false</code> as LaWGS files do not encode the
182     * units that are being used. You must call <code>setFileUnits</code> to set the units
183     * being used before reading or writing to a file of this format.
184     *
185     * @return this implementation always returns false
186     * @see #setFileUnits(javax.measure.unit.Unit) 
187     */
188    @Override
189    public boolean isUnitAware() {
190        return false;
191    }
192
193    /**
194     * Reads a single array or network from an input stream (pointing to the appropriate
195     * location in a LaWGS file).
196     *
197     * @param in        Reader for the LaWGS file we are reading (positioned so that the
198     *                  next read will occur on the line following the line containing the
199     *                  name of the array).
200     * @param arrayName The name of the array.
201     * @return The network read in from the file.
202     * @throws java.io.IOException if there is a problem reading the array.
203     */
204    private PointArray readArray(LineNumberReader in, String arrayName) throws IOException {
205        PointArray net = null;
206
207        // Create the needed tables.
208        FastTable<Point> pointList = FastTable.newInstance();
209        FastTable<PointString<Point>> stringList = FastTable.newInstance();
210
211        try {
212            // Read in the object information line.
213            String aLine = readLine(in);
214            TextTokenizer tokenizer = TextTokenizer.valueOf(aLine, DELIMITERS);
215            if (tokenizer.countTokens() != 14)
216                throw new IOException(MessageFormat.format(
217                        RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber()));
218
219            //  Parse out the object ID number.
220            Text token = tokenizer.nextToken();
221            int nObj = TypeFormat.parseInt(token);
222
223            //  Parse out the number of contour lines.
224            token = tokenizer.nextToken();
225            int nLine = TypeFormat.parseInt(token);
226
227            //  Parse out the number of points in each contour line.
228            token = tokenizer.nextToken();
229            int nPnt = TypeFormat.parseInt(token);
230
231            // Do a sanity check.
232            if (nLine < 0 || nLine > 1000000 || nPnt < 0 || nPnt > 1000000)
233                throw new IOException(MessageFormat.format(
234                        RESOURCES.getString("incRowsColsErr"), DESCRIPTION, in.getLineNumber()));
235
236            //  Parse out the local symmetry code.
237            token = tokenizer.nextToken();
238            int iSymL = TypeFormat.parseInt(token);
239            if (iSymL < 0 || iSymL > 3)
240                throw new IOException(MessageFormat.format(
241                        RESOURCES.getString("lawgsUnknownLocalSymm"), in.getLineNumber()));
242
243            //  Parse out the rotation angles.
244            token = tokenizer.nextToken();
245            double rx = TypeFormat.parseDouble(token);
246
247            token = tokenizer.nextToken();
248            double ry = TypeFormat.parseDouble(token);
249
250            token = tokenizer.nextToken();
251            double rz = TypeFormat.parseDouble(token);
252
253            //  Parse out the translations.
254            token = tokenizer.nextToken();
255            double tx = TypeFormat.parseDouble(token);
256
257            token = tokenizer.nextToken();
258            double ty = TypeFormat.parseDouble(token);
259
260            token = tokenizer.nextToken();
261            double tz = TypeFormat.parseDouble(token);
262
263            //  Parse out the scales in each axis.
264            token = tokenizer.nextToken();
265            double xScale = TypeFormat.parseDouble(token);
266
267            token = tokenizer.nextToken();
268            double yScale = TypeFormat.parseDouble(token);
269
270            token = tokenizer.nextToken();
271            double zScale = TypeFormat.parseDouble(token);
272
273            //  Parse out the global symmetry code.
274            token = tokenizer.nextToken();
275            int iSymG = TypeFormat.parseInt(token);
276            if (iSymG < 0 || iSymG > 3)
277                throw new IOException(MessageFormat.format(
278                        RESOURCES.getString("lawgsUnknownGlobalSymm"), in.getLineNumber()));
279
280            if (DEBUG) {
281                System.out.println("arrayName = " + arrayName + ", nLine = " + nLine + ", nPnt = " + nPnt + ", iSymL = " + iSymL);
282                System.out.println("  rx,ry,rz = " + rx + "," + ry + "," + rz);
283                System.out.println("  tx,ty,tz = " + tx + "," + ty + "," + tz);
284                System.out.println("  sx,sy,sz = " + xScale + "," + yScale + "," + zScale);
285                System.out.println("  iSymG = " + iSymG);
286            }
287
288            // Loop over each line.
289            for (int i = 0; i < nLine; ++i) {
290                if (DEBUG)
291                    System.out.println("i = " + i);
292                aLine = readLine(in);
293                tokenizer.setText(aLine);
294                if (tokenizer.countTokens() != 3 && tokenizer.countTokens() != 6)
295                    throw new IOException(MessageFormat.format(
296                            RESOURCES.getString("incPointCount"), in.getLineNumber()));
297
298                int pntsPerLine = tokenizer.countTokens() / 3;
299
300                // Loop over each point.
301                int count = 1;
302                for (int j = 0; j < nPnt; ++j) {
303
304                    // There may be a limited number of points per line.
305                    if (count > pntsPerLine) {
306                        count = 1;
307                        aLine = readLine(in);
308                        tokenizer.setText(aLine);
309                        if (tokenizer.countTokens() != 3 && tokenizer.countTokens() != 6)
310                            throw new IOException(MessageFormat.format(
311                                    RESOURCES.getString("incPointCount"), in.getLineNumber()));
312                    }
313
314                    // Read in X coordinate.
315                    token = tokenizer.nextToken();
316                    double xValue = TypeFormat.parseDouble(token);
317
318                    // Read in Y coordinate.
319                    token = tokenizer.nextToken();
320                    double yValue = TypeFormat.parseDouble(token);
321
322                    // Read in Z coordinate.
323                    token = tokenizer.nextToken();
324                    double zValue = TypeFormat.parseDouble(token);
325
326                    if (DEBUG)
327                        System.out.println("col = " + j + ", x,y,z = " + xValue + ", " + yValue + ", " + zValue);
328
329                    //  Create a Point object.
330                    Point point = Point.valueOf(xValue, yValue, zValue, getFileUnits());
331                    pointList.add(point);
332
333                    ++count;
334                }
335
336                //  Create a PointString object from the points.
337                PointString<Point> string = PointString.valueOf(null, pointList);
338                stringList.add(string);
339
340                //  Clear the point list for the next row.
341                pointList.clear();
342            }
343
344            // Create a new network from the points just read in.
345            net = PointArray.valueOf(arrayName, stringList);
346
347        } catch (NumberFormatException e) {
348            e.printStackTrace();
349            throw new IOException(
350                    MessageFormat.format(RESOURCES.getString("parseErrMsg"),
351                            DESCRIPTION, in.getLineNumber()));
352
353        } finally {
354            //  Recycle the lists.
355            FastTable.recycle(pointList);
356            FastTable.recycle(stringList);
357        }
358
359        return net;
360    }
361
362    /**
363     * Method that parses out quoted text from a string of text and returns whatever is
364     * between the quotes.
365     *
366     * @param input     The text containing a quote.
367     * @param quoteChar The character that delimits the quote.
368     * @return All the text between the 1st and last occurrence of quoteChar in the input
369     *         Text.
370     */
371    private static String parseQuotedText(Text input, CharSequence quoteChar) {
372        int idx1 = input.indexOf(quoteChar, 0) + 1;
373        int idx2 = input.lastIndexOf(quoteChar);
374        Text output;
375        if (idx1 < 0 || idx2 < 0)
376            output = input;
377        else
378            output = input.subtext(idx1, idx2);
379
380        if (isNull(output))
381            return null;
382
383        output = output.trim();
384        if (Text.EMPTY.equals(output))
385            return null;
386        return output.toString();
387    }
388
389}