001/* 002 * GGPGeomReader -- A class that can read a GGP formatted geometry file. 003 * 004 * Copyright (C) 2000-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 jahuwaldt.js.util.TextTokenizer; 026import java.io.File; 027import java.io.FileReader; 028import java.io.IOException; 029import java.io.LineNumberReader; 030import java.text.MessageFormat; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.Locale; 034import static java.util.Objects.isNull; 035import static java.util.Objects.nonNull; 036import static java.util.Objects.requireNonNull; 037import javolution.context.StackContext; 038import javolution.text.Text; 039import javolution.text.TypeFormat; 040import javolution.util.FastTable; 041 042/** 043 * A {@link GeomReader} for reading vehicle geometry from a GGP formatted geometry file. 044 * This is a geometry file format often used in conjunction with solution output from A502 045 * (PANAIR) and A633 (TRANAIR). This class will only read in parameters from the GGP file 046 * with the names "X", "Y", and "Z". All other parameters are ignored. This class also 047 * assumes that all the parameters are contained in a single line and that the 1st format 048 * is duplicated throughout the file. 049 * 050 * <p> Modified by: Joseph A. Huwaldt </p> 051 * 052 * @author Joseph A. Huwaldt, Date: May 11, 2000 053 * @version July 3, 2019 054 */ 055public class GGPGeomReader extends AbstractGeomReader { 056 057 // A brief description of the data read by this reaader. 058 private static final String DESCRIPTION = RESOURCES.getString("ggpDescription"); 059 060 // The preferred file extension for files of this reader's type. 061 public static final String EXTENSION = "ggp"; 062 063 // Define the comment characters that are possible. 064 private static final String[] COMMENT_CHARACTERS = {"*", "$", "("}; 065 066 /** 067 * A description of the format of the arrays in the GGP file. 068 */ 069 private final FastTable<Integer> formatLst = FastTable.newInstance(); 070 071 /** 072 * Returns a string representation of the object. This will return a brief description 073 * of the format read by this reader. 074 * 075 * @return A brief description of the format read by this reader. 076 */ 077 @Override 078 public String toString() { 079 return DESCRIPTION; 080 } 081 082 /** 083 * Returns the preferred file extension (not including the ".") for files of this 084 * GeomReader's type. 085 * 086 * @return The preferred file extension for files of this readers type. 087 */ 088 @Override 089 public String getExtension() { 090 return EXTENSION; 091 } 092 093 /** 094 * Method that determines if this reader can read geometry from the specified input 095 * file. 096 * 097 * @param inputFile The input file containing the geometry to be read in. May not be 098 * null. 099 * @return GeomReader.NO if the file format is not recognized by this reader. 100 * GeomReader.YES if the file format is definitely recognized by this reader. 101 * GeomReader.MAYBE if the file format might be readable by this reader, but 102 * that can't easily be determined without actually reading the file. 103 * @throws java.io.IOException If there is a problem reading from the specified 104 * file. 105 */ 106 @Override 107 public int canReadData(File inputFile) throws IOException { 108 109 int response = NO; 110 String name = inputFile.getName(); 111 name = name.toLowerCase().trim(); 112 if (name.endsWith(".ggp")) 113 response = MAYBE; 114 115 return response; 116 } 117 118 /** 119 * Reads in a GGP formatted geometry file from the specified input file and returns a 120 * {@link PointVehicle} object that contains the geometry from the GGP file. 121 * <p> 122 * A GGP file does not support multiple components, therefore, the vehicle returned 123 * will always contain a single component (or none at all if there was no data in the 124 * file). 125 * </p> 126 * <p> 127 * WARNING: This file format is not unit aware. You must set the units to be used by 128 * calling "setFileUnits()" before calling this method! 129 * </p> 130 * 131 * @param inputFile The input file containing the geometry to be read in. May not be 132 * null. 133 * @return A {@link PointVehicle} object containing the geometry read in from the 134 * file. If the file has no geometry in it, then this list will have no 135 * components in it (will have a size() of zero). 136 * @throws IOException If there is a problem reading the specified file. 137 * @see #setFileUnits(javax.measure.unit.Unit) 138 */ 139 @Override 140 public PointVehicle read(File inputFile) throws IOException { 141 requireNonNull(inputFile); 142 _warnings.clear(); 143 144 PointVehicle vehicle = PointVehicle.newInstance(inputFile.getName()); 145 146 // GGP files are required to be in ASCII with U.S. style number formatting. 147 // Get the default locale and save it. Then change the default locale to US. 148 Locale defLocale = Locale.getDefault(); 149 Locale.setDefault(Locale.US); 150 151 // Create a reader to access the ASCII file. 152 try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) { 153 reader.mark(8192); 154 155 // Start by parsing the "format" line (must be 1st line if it exists). 156 String aLine = readLine(reader); 157 parseFormatString(aLine); 158 reader.reset(); 159 160 // Read in the networks. 161 PointComponent comp = readNetworks(reader); 162 163 // Add the component to the vehicle. 164 if (!comp.isEmpty()) 165 vehicle.add(comp); 166 167 } finally { 168 // Restore the default locale. 169 Locale.setDefault(defLocale); 170 } 171 172 return vehicle; 173 } 174 175 /** 176 * This method always returns <code>false</code> as GGP files do not encode the units 177 * that are being used. You must call <code>setFileUnits</code> to set the units being 178 * used before reading from a file of this format. 179 * 180 * @return true if this reader (and its format) is unit aware. 181 * @see #setFileUnits(javax.measure.unit.Unit) 182 */ 183 @Override 184 public boolean isUnitAware() { 185 return false; 186 } 187 188 /** 189 * Parse the format line of the GGP file. The format line is always the 1st line and 190 * must contain a FORTRAN style number format declaration such as (3G15.7) or 191 * (F5.0,7F13.4). All that this reader cares about is how many columns there are and 192 * how many characters are contained in each column. This method sets a class variable 193 * "formatLst" such that each entry corresponds to a column and each entry records how 194 * many characters are in each column. 195 * 196 * @param formatStr A string containing the format statement. 197 * @throws IOException if unable to parse the format line. 198 */ 199 private void parseFormatString(String formatStr) throws IOException { 200 formatLst.clear(); 201 formatStr = formatStr.trim(); 202 203 if (formatStr.startsWith("(")) { 204 try { 205 206 // Search for commas that separate different formats 207 int start = 1; 208 int end = formatStr.indexOf(",", start); 209 while (end != -1) { 210 String subStr = formatStr.substring(start, end); 211 parseSingleFormat(subStr); 212 start = end + 1; 213 end = formatStr.indexOf(",", start); 214 } 215 parseSingleFormat(formatStr.substring(start, formatStr.length() - 1)); 216 217 } catch (NumberFormatException e) { 218 throw new IOException(RESOURCES.getString("ggpformatParseErr")); 219 } 220 221 } else { 222 223 // Create the default format list if one isn't provided. 224 for (int i = 0; i < 3; ++i) { 225 formatLst.add(15); 226 } 227 } 228 } 229 230 /** 231 * Parse a single FORTRAN style number format declaration such as "3G15.7" or "F5.0" 232 * or "7F13.4". All that this reader cares about is how many columns there are and how 233 * many characters are contained in each column. This method sets a class variable 234 * "formatLst" such that each entry corresponds to a column and each entry records how 235 * many characters are in each column. 236 * 237 * @param formatStr A string containing the format statement. 238 * @throws NumberFormatException if unable to parse the format declaration. 239 */ 240 private void parseSingleFormat(String formatStr) throws NumberFormatException { 241 formatStr = formatStr.trim(); 242 243 // Convert string to a character array. 244 char[] array = formatStr.toCharArray(); 245 246 // Check for a line feed. 247 if (array[0] == '/') { 248 formatLst.add(-1); 249 return; 250 } 251 252 // Extract the number of columns using this format. 253 int pos1 = 0; 254 int pos2 = 0; 255 while (Character.isDigit(array[pos2])) { 256 ++pos2; 257 } 258 259 int nCol; 260 if (pos2 == pos1) 261 nCol = 1; 262 else 263 nCol = Integer.parseInt(formatStr.substring(pos1, pos2)); 264 265 // Skip the format type (F,G, or E). 266 pos1 = pos2 + 1; 267 268 // Extract the number of characters per column. 269 pos2 = pos1; 270 while (Character.isDigit(array[pos2])) { 271 ++pos2; 272 } 273 274 if (pos2 < pos1 + 1) 275 throw new NumberFormatException(); 276 277 Integer nChar = Integer.valueOf(formatStr.substring(pos1, pos2)); 278 279 // Create list of formats, one for each column. 280 for (int i = 0; i < nCol; ++i) { 281 formatLst.add(nChar); 282 } 283 } 284 285 /** 286 * Read in all the networks in this geometry file. 287 * 288 */ 289 private PointComponent readNetworks(LineNumberReader reader) throws IOException { 290 291 // Create a component object to contain the networks read in. 292 PointComponent comp = PointComponent.newInstance(); 293 294 // Read until we get something that is not a comment. 295 String aLine = readSkippingComments(reader); 296 297 // Read in the network ID and optional name. 298 String netID = parseNetID(aLine); 299 reader.mark(8192); 300 301 // Parse the parameter name line. 302 FastTable<Text> paramNames = parseParamNames(reader); 303 reader.reset(); 304 305 // Determine which columns contain the X,Y,Z parameters. 306 int[] columns = new int[3]; 307 columns[0] = findParam(paramNames, "X"); 308 columns[1] = findParam(paramNames, "Y"); 309 columns[2] = findParam(paramNames, "Z"); 310 if (columns[0] < 0 || columns[1] < 0 || columns[2] < 0) { 311 throw new IOException( 312 MessageFormat.format(RESOURCES.getString("ggpMissingParameter"), 313 reader.getLineNumber())); 314 } 315 316 // Keep reading until we reach the end of the file. 317 while (nonNull(netID)) { 318 319 // Parse the optional network name. 320 String netName = parseOptionalNetName(aLine); 321 322 // Skip the header line. 323 parseParamNames(reader); 324 325 // Read in a single list of data points. 326 PointString dataStr = readString(reader, columns); 327 328 // Create a list of strings (lists of data points) and add the 329 // 1st one to it. 330 PointArray strList = PointArray.newInstance(); 331 strList.setName(netName); 332 strList.add(dataStr); 333 334 // Read in the remaining strings of points in this network/array. 335 String oldNetID = netID; 336 aLine = readSkippingComments(reader); 337 netID = parseNetID(aLine); 338 while (nonNull(netID) && netID.equals(oldNetID)) { 339 340 // Skip header line. 341 parseParamNames(reader); 342 343 // Read in the string of points. 344 dataStr = readString(reader, columns); 345 346 // Add it to the list of strings of points (array). 347 strList.add(dataStr); 348 349 // Check for the end of the file. 350 reader.mark(64); 351 aLine = reader.readLine(); 352 reader.reset(); 353 if (isNull(aLine)) { 354 // End-of-File encountered. 355 netID = null; 356 break; 357 } 358 aLine = readSkippingComments(reader); 359 netID = parseNetID(aLine); 360 } 361 362 // Add the new network to the component. 363 comp.add(strList); 364 365 } 366 367 return comp; 368 } 369 370 /** 371 * Determines if this line is a comment line. 372 * 373 * @param aLine The line to be tested. 374 * @return true if the line is a comment line, false if it is not. If aLine is null, 375 * false is returned. 376 */ 377 private boolean isComment(String aLine) { 378 boolean retVal = false; 379 380 aLine = aLine.trim(); 381 int length = COMMENT_CHARACTERS.length; 382 for (int i = 0; i < length; ++i) { 383 if (aLine.startsWith(COMMENT_CHARACTERS[i]) && !aLine.startsWith("*EOD") 384 && !aLine.startsWith("*EOF")) { 385 retVal = true; 386 break; 387 } 388 } 389 390 return retVal; 391 } 392 393 /** 394 * Read in lines from the GGP file while skipping comment lines. 395 * 396 * @param reader The reader for the GGP file. 397 * @return The 1st line that is not a comment line or null if the end of file is 398 * reached. 399 */ 400 private String readSkippingComments(LineNumberReader reader) throws IOException { 401 String aLine = readLine(reader); 402 while (isComment(aLine)) { 403 aLine = readLine(reader); 404 } 405 return aLine; 406 } 407 408 /** 409 * Parse the network ID from the string name line. The network ID is a letter/number 410 * combination that uniquely identifies the network that this string of points belongs 411 * to. Immediately following the network ID is the string ID which this program 412 * ignores. 413 * 414 * @param aLine The line containing the net/string ID. 415 * @param The network ID is returned. 416 */ 417 private String parseNetID(String aLine) { 418 419 aLine = aLine.trim(); 420 char[] array = aLine.toCharArray(); 421 int length = array.length; 422 423 // Find end of letter part of ID. 424 int pos = 0; 425 while (pos < length) { 426 if (Character.isDigit(array[pos])) 427 break; 428 ++pos; 429 } 430 431 // Find end of number part of ID. 432 ++pos; 433 while (pos < length) { 434 if (!Character.isDigit(array[pos])) { 435 ++pos; 436 break; 437 } 438 ++pos; 439 } 440 441 // Extract the ID string. 442 return aLine.substring(0, pos - 1); 443 } 444 445 /** 446 * Parse the list of parameters. 447 * 448 * @param reader The reader for our GGP file. 449 * @return A list of strings where each element is a parameter in this GGP file. 450 */ 451 private FastTable<Text> parseParamNames(LineNumberReader reader) throws IOException { 452 453 String aLine = readSkippingComments(reader); 454 455 int nParams = formatLst.size(); 456 FastTable<Text> array = FastTable.newInstance(); 457 TextTokenizer tokenizer = TextTokenizer.valueOf(aLine, " "); 458 459 // Extract one token (param name) for each element identified 460 // in the format line. 461 for (int i = 0; i < nParams; ++i) { 462 if (formatLst.get(i) == -1) { 463 // Read the next line. 464 aLine = readSkippingComments(reader); 465 tokenizer.setText(aLine); 466 continue; 467 } 468 469 Text token = tokenizer.nextToken(); 470 array.add(token); 471 } 472 473 return array; 474 } 475 476 /** 477 * Identify which parameter in the parameter string matches the target parameter. 478 * 479 * @param nameList The list of parameter names. 480 * @param target The target parameter that we are looking for. 481 * @return The data column corresponding the the target parameter. If the parameter 482 * could not be found, -1 is returned. 483 */ 484 private int findParam(List<Text> nameList, CharSequence target) { 485 Text targetText = Text.valueOf(target); 486 487 int length = nameList.size(); 488 int pos = 0; 489 while (pos < length) { 490 if (nameList.get(pos).equals(targetText)) 491 break; 492 ++pos; 493 } 494 495 if (pos == length) 496 pos = -1; 497 498 return pos; 499 } 500 501 /** 502 * Parse the optional network name from the string name line. The network name is 503 * different from the network ID. The optional network name comes after the network ID 504 * and string ID and is separated from them by at least one space. 505 * 506 * @param aLine The line containing the string & network name. 507 * @return A string containing the optional network name or null if that name is not 508 * found. 509 */ 510 private String parseOptionalNetName(String aLine) { 511 aLine = aLine.trim(); 512 int pos = aLine.lastIndexOf(" "); 513 514 String retVal = null; 515 if (pos > 0) 516 retVal = aLine.substring(pos); 517 518 // Extract the ID string. 519 return retVal; 520 } 521 522 /** 523 * Read a string of points from the GGP file. A string of points consists of the 524 * parameters read in, 1 point per line, until the *EOD record is encountered. This 525 * version of this method does not require the string to be of any length. Data is 526 * read in until the *EOD card is reached. 527 * 528 * @param reader The reader for the GGP file. 529 * @param columns An array containing the indices of the data columns (parameters) to 530 * be read in. 531 * @return A list of PointString objects. 532 * 533 */ 534 private PointString readString(LineNumberReader reader, int[] columns) 535 throws IOException { 536 537 // A string of 3D points. 538 ArrayList<Point> string = new ArrayList(); 539 540 StackContext.enter(); 541 try { 542 FastTable<Text> coordinates = FastTable.newInstance(); 543 int nParams = formatLst.size(); 544 545 // Read a line of data. 546 String aLine = readSkippingComments(reader); 547 548 // If *EOD, then the string is finished. 549 String trimLine = aLine.trim(); 550 while (!trimLine.equals("*EOD") && !trimLine.equals("*EOF")) { 551 TextTokenizer tokenizer = TextTokenizer.valueOf(trimLine, " "); 552 553 // Loop over the parameters in the file. 554 for (int pos=0; pos < nParams; ++pos) { 555 if (formatLst.get(pos) == -1) { 556 // Read in the next line. 557 aLine = readSkippingComments(reader); 558 trimLine = aLine.trim(); 559 tokenizer = TextTokenizer.valueOf(trimLine, " "); 560 continue; 561 } 562 Text token = tokenizer.nextToken(); 563 564 // Check if this parameter is one of the columns requested. 565 for (int i = 0; i < columns.length; ++i) { 566 if (columns[i] == pos) { 567 568 // This one is requested, so store it. 569 coordinates.add(token); 570 break; 571 } 572 } 573 } 574 575 // Parse the stored coordinates. 576 double xValue = TypeFormat.parseDouble(coordinates.get(0)); 577 double yValue = TypeFormat.parseDouble(coordinates.get(1)); 578 double zValue = TypeFormat.parseDouble(coordinates.get(2)); 579 coordinates.clear(); 580 581 // Create and store a point. 582 Point point = Point.valueOf(xValue, yValue, zValue, getFileUnits()); 583 string.add(StackContext.outerCopy(point)); 584 585 // Read the next line. 586 aLine = readSkippingComments(reader); 587 trimLine = aLine.trim(); 588 } 589 590 } catch (NumberFormatException e) { 591 e.printStackTrace(); 592 throw new IOException( 593 MessageFormat.format(RESOURCES.getString("parseErrMsg"), 594 DESCRIPTION, reader.getLineNumber())); 595 596 } finally { 597 StackContext.exit(); 598 } 599 600 // Convert ArrayList to a PointString 601 PointString pstr = PointString.valueOf(null, string); 602 return pstr; 603 } 604}