001/* 002 * POIGeomReader -- A class that can read a POI formatted geometry file. 003 * 004 * Copyright (C) 2000-2024, 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.*; 028import java.text.MessageFormat; 029import java.util.Locale; 030import static java.util.Objects.isNull; 031import static java.util.Objects.nonNull; 032import static java.util.Objects.requireNonNull; 033import javolution.text.Text; 034import javolution.text.TypeFormat; 035import javolution.util.FastTable; 036 037/** 038 * A {@link GeomReader} for reading vehicle geometry from a POINTS (POI) formatted 039 * geometry file. This is the geometry file format used by A502 (PANAIR) and A633 040 * (TRANAIR) for input geometry. 041 * 042 * <p> Modified by: Joseph A. Huwaldt </p> 043 * 044 * @author Joseph A. Huwaldt, Date: April 14, 2000 045 * @version January 1, 2024 046 */ 047public class POIGeomReader extends AbstractGeomReader { 048 049 // Debug output flag. 050 private static final boolean DEBUG = false; 051 052 // A brief description of the data read by this reaader. 053 private static final String DESCRIPTION = RESOURCES.getString("poiDescription"); 054 055 // Some error messages. 056 private static final String INC_ROWS_COLS = RESOURCES.getString("incRowsColsErr"). 057 replace("<TYPE/>", DESCRIPTION); 058 059 // The number of characters per number stored in the POI file. 060 private static final int FIELDSIZE = 10; 061 062 // The preferred file extension for files of this reader's type. 063 public static final String EXTENSION = "poi"; 064 065 /** 066 * Returns a string representation of the object. This will return a brief description 067 * of the format read by this reader. 068 * 069 * @return A brief description of the format read by this reader. 070 */ 071 @Override 072 public String toString() { 073 return DESCRIPTION; 074 } 075 076 /** 077 * Returns the preferred file extension (not including the ".") for files of this 078 * GeomReader's type. 079 * 080 * @return The preferred extension for this file type. 081 */ 082 @Override 083 public String getExtension() { 084 return EXTENSION; 085 } 086 087 /** 088 * Method that determines if this reader can read paneled geometry from the specified 089 * input file. 090 * 091 * @param inputFile The input file containing the geometry to be read in. 092 * @return GeomReader.NO if the file format is not recognized by this reader. 093 * GeomReader.YES if the file format is definitely recognized by this reader. 094 * GeomReader.MAYBE if the file format might be readable by this reader, but 095 * that can't easily be determined without actually reading the file. 096 * @throws java.io.IOException If there is a problem reading from the specified 097 * file. 098 */ 099 @Override 100 public int canReadData(File inputFile) throws IOException { 101 102 int response = NO; 103 String name = inputFile.getName(); 104 name = name.toLowerCase().trim(); 105 if (name.endsWith(".poi")) 106 response = MAYBE; 107 108 // POI files are required to be in ASCII with U.S. style number formatting. 109 // Get the default locale and save it. Then change the default locale to US. 110 Locale defLocale = Locale.getDefault(); 111 Locale.setDefault(Locale.US); 112 113 // Create an input stream from the file. 114 try (FileInputStream input = new FileInputStream(inputFile)) { 115 116 // Create a buffer to hold the data read in from the file. 117 byte[] buffer = new byte[10240]; 118 119 // Read in up to 10k worth of data. 120 int byteCount = input.read(buffer, 0, 10240); 121 if (byteCount < 0) 122 return NO; 123 124 // Turn the buffer into a (potentially 10k long) string. 125 String str = new String(buffer, 0, byteCount); 126 127 // Convert the string into a line number reader. 128 LineNumberReader lnr = new LineNumberReader(new StringReader(str)); 129 130 // Search for a line that starts with "$POI". 131 String line = lnr.readLine(); 132 while (nonNull(line)) { 133 if (line.startsWith("$POI")) { 134 response = YES; 135 break; 136 } 137 line = lnr.readLine(); 138 } 139 140 } finally { 141 // Restore the default locale. 142 Locale.setDefault(defLocale); 143 } 144 145 return response; 146 } 147 148 /** 149 * Reads in a POINTS (POI) geometry file from the specified input file and returns a 150 * {@link PointVehicle} object that contains the geometry from the POI file. 151 * <p> 152 * Each component will have an Integer stored in the user data under the key 153 * "A502A633TypeCode" that contains the A502-A633 array type code for all the networks 154 * contained in that component. 155 * </p> 156 * <p> 157 * WARNING: This file format is not unit aware. You must set the units 158 * to be used by calling "setFileUnits()" before calling this method! 159 * </p> 160 * 161 * @param inputFile The input file containing the geometry to be read in. May not be 162 * null. 163 * @return A {@link PointVehicle} object containing the geometry read in from the 164 * file. If the file has no geometry in it, then this list will have no 165 * components in it (will have a size() of zero). 166 * @throws IOException If there is a problem reading the specified file. 167 * @see #setFileUnits(javax.measure.unit.Unit) 168 */ 169 @Override 170 public PointVehicle read(File inputFile) throws IOException { 171 requireNonNull(inputFile); 172 _warnings.clear(); 173 174 // Create an empty vehicle with the provided name as the vehicle name. 175 PointVehicle vehicle = PointVehicle.newInstance(inputFile.getName()); 176 177 // POI files are required to be in ASCII with U.S. style number formatting. 178 // Get the default locale and save it. Then change the default locale to US. 179 Locale defLocale = Locale.getDefault(); 180 Locale.setDefault(Locale.US); 181 182 // Create a reader to access the ASCII file. 183 try (LineNumberReader reader = new LineNumberReader(new FileReader(inputFile))) { 184 185 // Loop over all the components stored in the file. 186 String aLine = reader.readLine(); 187 while (nonNull(aLine)) { 188 189 // Skip ahead to the next "$POI" line. 190 while (nonNull(aLine) && !aLine.startsWith("$POI")) 191 aLine = reader.readLine(); 192 if (isNull(aLine)) 193 break; 194 195 // A $POI line was found, read in the component. 196 PointComponent comp = readComponent(reader); 197 vehicle.add(comp); 198 199 // Begin searching for the next component. 200 aLine = reader.readLine(); 201 } 202 203 } finally { 204 // Restore the default locale. 205 Locale.setDefault(defLocale); 206 } 207 208 return vehicle; 209 } 210 211 /** 212 * This method always returns <code>false</code> as POI files do not encode the units 213 * that are being used. You must call <code>setFileUnits</code> to set the units being 214 * used before reading from a file of this format. 215 * 216 * @return This implementation always returns false. 217 * @see #setFileUnits(javax.measure.unit.Unit) 218 */ 219 @Override 220 public boolean isUnitAware() { 221 return false; 222 } 223 224 /** 225 * Reads a single component made up of multiple networks of the same type from an 226 * input stream pointing to a POI file. This method assumes that the stream starts 227 * immediately after the $POI line in a POI file. 228 * 229 * @param in Reader for the POI file we are reading (positioned so that the next read 230 * will occur on the line following the $POI line). 231 * @return The component read in from the file. 232 */ 233 private PointComponent readComponent(LineNumberReader in) throws IOException { 234 PointComponent comp = null; 235 236 try { 237 // Read in the number of arrays. 238 String aLine = readLine(in); 239 String subStr = aLine.substring(0, FIELDSIZE - 1).trim(); 240 int numArrs = Integer.parseInt(subStr); 241 242 // Read in the component type. 243 aLine = readLine(in); 244 subStr = aLine.substring(0, FIELDSIZE - 1).trim(); 245 Integer typeCode = Integer.valueOf(subStr); 246 String typeStr = "Type " + typeCode; 247 248 // Loop over each array and read it in, adding it to a new component. 249 comp = PointComponent.newInstance(typeStr); 250 comp.putUserData("A502A633TypeCode", typeCode); 251 for (int i = 0; i < numArrs; ++i) { 252 PointArray net = readNetwork(in); 253 comp.add(net); 254 } 255 256 } catch (NumberFormatException e) { 257 throw new IOException(MessageFormat.format( 258 RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber())); 259 } 260 261 return comp; 262 } 263 264 /** 265 * Reads a single array or network from an input reader (pointing to a POI file). This 266 * method assumes that the stream starts immediately after the line containing the 267 * component type in a POI file (two lines after the $POI line). 268 * 269 * @param in Reader for the POI file we are reading (positioned so that the next read 270 * will occur on the line following the line identifying component type). 271 * @return The network read in from the file. 272 * @throws java.io.IOException if there is a problem reading from the input reader. 273 */ 274 private PointArray readNetwork(LineNumberReader in) throws IOException { 275 PointArray net = null; 276 277 // Create the needed tables. 278 FastTable<Point> pointList = FastTable.newInstance(); 279 FastTable<PointString<Point>> stringList = FastTable.newInstance(); 280 281 try { 282 // Read in the number of rows and columns. 283 String aLine = readLine(in); 284 TextTokenizer tokenizer = TextTokenizer.valueOf(aLine, " "); 285 if (tokenizer.countTokens() != 3) 286 throw new IOException(MessageFormat.format( 287 RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber())); 288 289 Text token = tokenizer.nextToken(); 290 int numCols = (int)TypeFormat.parseDouble(token); 291 292 token = tokenizer.nextToken(); 293 int numRows = (int)TypeFormat.parseDouble(token); 294 295 // Do a sanity check. 296 if (numRows < 0 || numRows > 1000000 || numCols < 0 || numCols > 1000000) 297 throw new IOException(INC_ROWS_COLS + in.getLineNumber() + "."); 298 299 // Read in the name of this network. 300 Text name = tokenizer.nextToken(); 301 302 if (DEBUG) 303 System.out.println("name = " + name + ", numRows = " + numRows + ", numCols = " + numCols); 304 305 // Loop over each row. 306 for (int i = 0; i < numRows; ++i) { 307 if (DEBUG) 308 System.out.println("row = " + i); 309 aLine = readLine(in); 310 tokenizer.setText(aLine); 311 if (tokenizer.countTokens() != 3 && tokenizer.countTokens() != 6) 312 throw new IOException(MessageFormat.format( 313 RESOURCES.getString("incPointCount"), in.getLineNumber())); 314 315 // Loop over each column. 316 int count = 0; 317 for (int j = 0; j < numCols; ++j) { 318 319 // There are two points per line. 320 if (count > 1) { 321 count = 0; 322 aLine = readLine(in); 323 tokenizer.setText(aLine); 324 if (tokenizer.countTokens() != 3 && tokenizer.countTokens() != 6) 325 throw new IOException(MessageFormat.format( 326 RESOURCES.getString("incPointCount"), in.getLineNumber())); 327 } 328 329 // Read in X coordinate. 330 token = tokenizer.nextToken(); 331 double xValue = TypeFormat.parseDouble(token); 332 333 // Read in Y coordinate. 334 token = tokenizer.nextToken(); 335 double yValue = TypeFormat.parseDouble(token); 336 337 // Read in Z coordinate. 338 token = tokenizer.nextToken(); 339 double zValue = TypeFormat.parseDouble(token); 340 341 if (DEBUG) 342 System.out.println("col = " + j + ", x,y,z = " + xValue + ", " + yValue + ", " + zValue); 343 344 // Create a Point object. 345 Point point = Point.valueOf(xValue, yValue, zValue, getFileUnits()); 346 pointList.add(point); 347 348 ++count; 349 } 350 351 // Create a PointString object from the points. 352 PointString<Point> string = PointString.valueOf(null, pointList); 353 stringList.add(string); 354 355 // Clear the point list for the next row. 356 pointList.clear(); 357 } 358 359 // Create a new network from the points just read in. 360 net = PointArray.valueOf(null, stringList); 361 362 } catch (NumberFormatException e) { 363 e.printStackTrace(); 364 throw new IOException(MessageFormat.format( 365 RESOURCES.getString("parseErrMsg"), DESCRIPTION, in.getLineNumber())); 366 367 } finally { 368 // Recycle the lists. 369 FastTable.recycle(pointList); 370 FastTable.recycle(stringList); 371 } 372 373 return net; 374 } 375 376}