001/**
002 * TecplotGeomReader -- A class that can read and write an ASCII Tecplot formatted triangle file.
003 *
004 * Copyright (C) 2019, Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package geomss.geom.reader;
019
020import geomss.geom.GeomList;
021import geomss.geom.Point;
022import geomss.geom.Triangle;
023import geomss.geom.TriangleList;
024import java.io.*;
025import java.nio.file.Files;
026import java.text.MessageFormat;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.Locale;
030import static java.util.Objects.requireNonNull;
031import javax.measure.quantity.Length;
032import javax.measure.unit.Unit;
033import javolution.text.TypeFormat;
034import javolution.util.FastTable;
035
036/**
037 * A {@link GeomReader} for reading and writing geometry from/to an Tecplot ASCII
038 * formatted triangle file. The first 3 columns of data are assumed to be the X,Y,Z points.
039 *
040 * <p> Modified by: Joseph A. Huwaldt </p>
041 *
042 * @author Joseph A. Huwaldt, Date: July 12, 2019
043 * @version July 12, 2019
044 */
045public class TecplotGeomReader extends AbstractGeomReader {
046
047    //  Debug output flag.
048    //private static final boolean DEBUG = false;
049
050    //  A brief description of the data read by this reaader.
051    private static final String DESCRIPTION = RESOURCES.getString("tecplotDescription");
052
053    //  The preferred file extension for files of this reader's type.
054    public static final String EXTENSION = "dat";
055    
056    //  Used to split strings on white space.
057    private static final String WHITESPACE = "\\s+";
058
059    /**
060     * Returns a string representation of the object. This will return a brief description
061     * of the format read by this reader.
062     *
063     * @return A brief description of the format read by this reader.
064     */
065    @Override
066    public String toString() {
067        return DESCRIPTION;
068    }
069
070    /**
071     * Returns the preferred file extension (not including the ".") for files of this
072     * GeomReader's type.
073     *
074     * @return The preferred file extension for files of this readers type.
075     */
076    @Override
077    public String getExtension() {
078        return EXTENSION;
079    }
080
081    /**
082     * Method that determines if this reader can read geometry from the specified input
083     * file.
084     *
085     * @param inputFile The input file containing the geometry to be read in.
086     * @return GeomReader.NO if the file format is not recognized by this reader.
087     *         GeomReader.YES if the file has the extension ".dat" and contains a Tecplot header.
088     * @throws java.io.IOException If there is a problem reading from the specified file.
089     */
090    @Override
091    public int canReadData(File inputFile) throws IOException {
092
093        String name = inputFile.getName();
094        name = name.toLowerCase().trim();
095        if (name.endsWith(".dat")) {
096            //  Try reading the number of variables from the file.
097            try (BufferedReader reader = Files.newBufferedReader(inputFile.toPath())) {
098                
099                List<String> variables = getVariables(reader);
100                if (!variables.isEmpty())
101                    return YES;
102                
103            } catch (IOException e) {
104            }
105        }
106
107        return NO;
108    }
109
110    /**
111     * Reads in a Tecplot ASCII formatted triangle geometry file from the
112     * specified input file and returns a {@link GeomList} object that contains a set of
113     * {@link TriangleList} objects (1 for each "zone" in the Tecplot file).
114     * <p>
115     * WARNING: This file format is not unit aware. You must set the units
116     * to be used by calling "setFileUnits()" before calling this method!
117     * </p>
118     *
119     * @param inputFile The input file containing the geometry to be read in. May not be
120     *                  null.
121     * @return A {@link GeomList} object containing the geometry read in from the file. If
122     *         the file has no geometry in it, then this list will have no triangles in it
123     *         (will have a size() of zero).
124     * @throws IOException If there is a problem reading the specified file.
125     * @see #setFileUnits(javax.measure.unit.Unit) 
126     */
127    @Override
128    public GeomList<TriangleList> read(File inputFile) throws IOException {
129        requireNonNull(inputFile);
130        _warnings.clear();
131
132        //  Create an initially empty geometry list for output.
133        GeomList<TriangleList> output = GeomList.newInstance();
134
135        //  Tecplot ASCII files are required to be in ASCII with U.S. style number formatting.
136        //  Get the default locale and save it.  Then change the default locale to US.
137        Locale defLocale = Locale.getDefault();
138        Locale.setDefault(Locale.US);
139
140        // Create a reader to access the ASCII file.
141        try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) {
142            try {
143                Unit<Length> units = this.getFileUnits();
144                
145                // Get the variables in the file.
146                reader.mark(8192);
147                List<String> variables = getVariables(reader);
148                reader.reset();
149                if (variables.size() < 3) {
150                    _warnings.add("No variables found in the \"" + inputFile + "\" file.");
151                    return output;
152                }
153                
154                //  REad in the title.
155                String[] tokens = reader.readLine().split("=");
156                if (tokens.length > 1)
157                    output.setName(tokens[1].trim());
158                
159                //  Loop over all the zones in the file.
160                String zone = getNextZone(reader);
161                while (zone != null) {
162                    
163                    //  Read in the number of verticies and triangles in the file.
164                    reader.readLine();    //  Skip a line.
165                    String line = reader.readLine();
166                    tokens = line.trim().split(",");
167                    //  First one should be the number of verticies.
168                    String[] parts = tokens[0].split("=");
169                    int numVerts = TypeFormat.parseInt(parts[1]);
170                    //  Second one should be the number of triangles.
171                    parts = tokens[1].split("=");
172                    int numTris = TypeFormat.parseInt(parts[1]);
173                    
174                    //  Skip 2 lines.
175                    reader.readLine();
176                    reader.readLine();
177
178                    //  Read in all the vertices.
179                    FastTable<Point> vertices = FastTable.newInstance();
180                    for (int i = 0; i < numVerts; ++i) {
181                        line = reader.readLine();
182                        tokens = line.trim().split(WHITESPACE);
183                        double x = TypeFormat.parseDouble(tokens[0]);
184                        double y = TypeFormat.parseDouble(tokens[1]);
185                        double z = TypeFormat.parseDouble(tokens[2]);
186                        Point p = Point.valueOf(x, y, z, units);
187                        vertices.add(p);
188                    }
189                    
190                    
191                    //  Read in the array of indexes.
192                    int numIndexes = numTris * 3;
193                    int[] indexes = new int[numIndexes];
194                    int pos = 0;
195                    for (int i = 0; i < numTris; ++i) {
196                        line = reader.readLine();
197                        tokens = line.trim().split(WHITESPACE);
198                        for (int j = 0; j < 3; ++j) {
199                            int idx = TypeFormat.parseInt(tokens[j]) - 1;   //  Convert to 0 offset indexes.
200                            indexes[pos++] = idx;
201                        }
202                    }
203                    
204                    //  Convert the vertices and indexes into a list of triangles.
205                    TriangleList<Triangle> tris = TriangleList.valueOf(vertices, indexes);
206                    tris.setName(zone);
207                    output.add(tris);
208                    FastTable.recycle(vertices);
209
210                    //  Read the next zone.
211                    zone = getNextZone(reader);
212                }
213
214            } catch (NumberFormatException e) {
215                throw new IOException(MessageFormat.format(
216                        RESOURCES.getString("parseErrMsg"), DESCRIPTION, reader.getLineNumber()));
217            }
218            
219        } finally {
220            //  Restore the default locale.
221            Locale.setDefault(defLocale);
222        }
223        
224        return output;
225    }
226
227    /**
228     * Returns the variables used in the first zone in the file.  It is assumed that all
229     * zones have the same set of variables in the same order.
230     * 
231     * @param reader The buffered reader for this file.
232     * @return A list of the variables in the file.
233     * @throws IOException if there are any problems reading from the file.
234     */
235    private static List<String> getVariables(BufferedReader reader) throws IOException {
236        List<String> variables = new ArrayList();
237        
238        String line = reader.readLine();
239        while (line != null) {
240            line = line.trim();
241            if (line.startsWith("VARIABLES") || line.startsWith("\"")) {
242                String[] parts = line.split(WHITESPACE);
243                
244                //  Strip off white space and quote characters.
245                for (int j=0; j < parts.length; ++j) {
246                    String part = parts[j].trim();
247                    if (part.length() > 0 && !part.equals("=") && !part.startsWith("VARIABLES")) {
248                        part = part.replaceAll("^\"|\"$", "");
249                        variables.add(part);
250                    }
251                 }
252            } else if (line.startsWith("ZONE"))
253                break;
254            
255            line = reader.readLine();
256        }
257        
258        return variables;
259    }
260    
261    /**
262     * Return the next zone in the file or null if there are no more zones.
263     * 
264     * @param reader The buffered reader for this file.
265     * @return The name of the next zone or null if there are no more zones.
266     * @throws IOException If there is a problem reading from the file.
267     */
268    private static String getNextZone(BufferedReader reader) throws IOException {
269        String zone = null;
270        
271        String line = reader.readLine();
272        while (line != null) {
273            line = line.trim();
274            if (line.startsWith("ZONE")) {
275                int idx = line.indexOf("T=");
276                idx = line.indexOf("\"", idx);
277                ++idx;
278                int idx2 = line.indexOf("\"", idx);
279                zone = line.substring(idx, idx2);
280                break;
281            }
282            
283            line = reader.readLine();
284        }
285        
286        return zone;
287    }
288    /**
289     * This method always returns <code>false</code> as TRI files do not encode the units
290     * that are being used. You must call <code>setFileUnits</code> to set the units being
291     * used before reading from a file of this format.
292     *
293     * @return This implementation always returns false.
294     * @see #setFileUnits(javax.measure.unit.Unit) 
295     */
296    @Override
297    public boolean isUnitAware() {
298        return false;
299    }
300
301}