/alaspatial/src/main/java/org/ala/spatial/util/SimpleShapeFile.java
Java | 2042 lines | 1132 code | 271 blank | 639 comment | 184 complexity | 09891b38d3739fef4d038aebd052cbf7 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1package org.ala.spatial.util; 2 3import java.io.BufferedInputStream; 4import java.io.BufferedOutputStream; 5import java.io.File; 6import java.io.FileInputStream; 7import java.io.FileOutputStream; 8import java.io.ObjectInputStream; 9import java.io.ObjectOutputStream; 10import java.io.Serializable; 11import java.nio.ByteBuffer; 12import java.nio.ByteOrder; 13import java.nio.channels.FileChannel; 14import java.util.ArrayList; 15import java.util.Comparator; 16import java.util.TreeSet; 17import java.util.Vector; 18import java.util.concurrent.CountDownLatch; 19import java.util.concurrent.LinkedBlockingQueue; 20import org.ala.spatial.analysis.service.ShapeLookup; 21 22/** 23 * SimpleShapeFile is a representation of a Shape File for 24 * intersections with points 25 * 26 * .shp MULTIPOLYGON only 27 * .dbf can read values from String and Number columns only 28 * 29 * TODO: finish serialization 30 * 31 * @author Adam Collins 32 */ 33public class SimpleShapeFile extends Object implements Serializable { 34 35 static final long serialVersionUID = -9046250209453575076L; 36 /** 37 * .shp file header contents 38 */ 39 ShapeHeader shapeheader; 40 /** 41 * .shp file record contents 42 */ 43 ShapeRecords shaperecords; 44 /** 45 * list of ComplexRegions, one per .shp record 46 */ 47 ArrayList<ComplexRegion> regions; 48 /** 49 * .dbf contents 50 */ 51 DBF dbf; 52 /** 53 * for balancing shape files with large numbers of shapes. 54 */ 55 ShapesReference shapesreference; 56 /** 57 * one dbf column, for use after loading from a file 58 */ 59 short[] singleColumn; 60 /** 61 * one lookup for a dbf column, for use after loading from a file 62 */ 63 String[] singleLookup; 64 65 /** 66 * Constructor for a SimpleShapeFile, requires .dbf and .shp files present 67 * on the fileprefix provided. 68 * 69 * @param fileprefix file path for valid files after appending .shp and .dbf 70 */ 71 public SimpleShapeFile(String fileprefix) { 72 //If fileprefix exists as-is it is probably a saved SimpleShapeFile 73 if (loadRegion(fileprefix)) { 74 //previously saved region loaded 75 } else { 76 /* read dbf */ 77 dbf = new DBF(fileprefix + ".dbf"); 78 79 /* read shape header */ 80 shapeheader = new ShapeHeader(fileprefix); 81 82 /* read shape records */ 83 shaperecords = new ShapeRecords(fileprefix, shapeheader.getShapeType()); 84 85 /* get ComplexRegion list from shape records */ 86 regions = shaperecords.getRegions(); 87 88 /* create shapes reference for intersections */ 89 shapesreference = new ShapesReference(shaperecords); 90 } 91 } 92 93 /** 94 * Constructor for a SimpleShapeFile, requires .dbf and .shp files present 95 * on the fileprefix provided. 96 * 97 * @param fileprefix file path for valid files after appending .shp and .dbf 98 */ 99 SimpleShapeFile(String fileprefix, boolean loadDbf) { 100 //If fileprefix exists as-is it is probably a saved SimpleShapeFile 101 if (loadRegion(fileprefix)) { 102 //previously saved region loaded 103 } else { 104 /* read dbf */ 105 if (loadDbf) { 106 dbf = new DBF(fileprefix + ".dbf"); 107 } 108 109 /* read shape header */ 110 shapeheader = new ShapeHeader(fileprefix); 111 112 /* read shape records */ 113 shaperecords = new ShapeRecords(fileprefix, shapeheader.getShapeType()); 114 115 /* get ComplexRegion list from shape records */ 116 regions = shaperecords.getRegions(); 117 118 /* create shapes reference for intersections */ 119 if (loadDbf) { 120 shapesreference = new ShapesReference(shaperecords); 121 } 122 } 123 } 124 125 public static SimpleRegion readRegions(String shapeFileName) { 126 SimpleShapeFile ssf = new SimpleShapeFile(shapeFileName, false); 127 128 if (ssf.regions.size() == 1) { 129 return ssf.regions.get(0); 130 } else { 131 ComplexRegion r = new ComplexRegion(); 132 for (int i = 0; i < ssf.regions.size(); i++) { 133 for (int j = 0; j < ssf.regions.get(i).simpleregions.size(); j++) { 134 r.addPolygon(ssf.regions.get(i).simpleregions.get(j)); 135 } 136 } 137 return r; 138 } 139 140 } 141 142 /** 143 * save partial file (enough to reload and use intersect function) 144 * 145 * @param filename 146 */ 147 public void saveRegion(String filename, int column) { 148 try { 149 FileOutputStream fos = new FileOutputStream(filename); 150 BufferedOutputStream bos = new BufferedOutputStream(fos); 151 ObjectOutputStream oos = new ObjectOutputStream(bos); 152 shapesreference.sr.records = null; //cleanup 153 oos.writeObject(shapesreference); 154 155 singleLookup = getColumnLookup(column); 156 oos.writeObject(singleLookup); 157 158 singleColumn = new short[regions.size()]; 159 for (int i = 0; i < singleColumn.length; i++) { 160 singleColumn[i] = (short) java.util.Arrays.binarySearch(singleLookup, dbf.getValue(i, column)); 161 } 162 oos.writeObject(singleColumn); 163 164 oos.close(); 165 } catch (Exception e) { 166 e.printStackTrace(); 167 } 168 } 169 170 /** 171 * save partial file (enough to reload and use intersect function) 172 * 173 * for each unique value in the shape file 174 * 175 * Note: excludes regions that share the same column value as another. 176 * 177 * @param filename 178 */ 179 public void saveEachRegion(String filename, int column) { 180 int m, i; 181 for (m = 0; m < singleLookup.length; m++) { 182 for (i = 0; i < regions.size(); i++) { 183 if (singleColumn[i] == (short) m) { 184 //check that no other column has this 'm' value 185 int j = 0; 186 for (j = 0; j < singleColumn.length; j++) { 187 if (i != j && singleColumn[i] == singleColumn[j]) { 188 break; 189 } 190 } 191 if (j == singleColumn.length) { 192 try { 193 FileOutputStream fos = new FileOutputStream(filename + "_" + m); 194 BufferedOutputStream bos = new BufferedOutputStream(fos); 195 ObjectOutputStream oos = new ObjectOutputStream(bos); 196 ComplexRegion cr = shapesreference.sr.region.get(i); 197 oos.writeObject(cr); 198 oos.close(); 199 } catch (Exception e) { 200 e.printStackTrace(); 201 } 202 } 203 } 204 } 205 } 206 } 207 208 /** 209 * save partial file (enough to reload and use intersect function) 210 * 211 * @param filename 212 * @return true when successful 213 */ 214 public boolean loadRegion(String filename) { 215 if (new File(filename).exists()) { 216 try { 217 FileInputStream fis = new FileInputStream(filename); 218 BufferedInputStream bis = new BufferedInputStream(fis); 219 ObjectInputStream ois = new ObjectInputStream(bis); 220 shapesreference = (ShapesReference) ois.readObject(); 221 222 singleLookup = (String[]) ois.readObject(); 223 224 singleColumn = (short[]) ois.readObject(); 225 ois.close(); 226 return true; 227 } catch (Exception e) { 228 //e.printStackTrace(); 229 } 230 } 231 return false; 232 } 233 234 /** 235 * save partial file (enough to reload and use intersect function) 236 * 237 * @param filename 238 */ 239 static public ComplexRegion loadShapeInRegion(String filename, int idx) { 240 ComplexRegion cr = null; 241 try { 242 FileInputStream fis = new FileInputStream(filename + "_" + idx); 243 BufferedInputStream bis = new BufferedInputStream(fis); 244 ObjectInputStream ois = new ObjectInputStream(bis); 245 cr = (ComplexRegion) ois.readObject(); 246 ois.close(); 247 } catch (Exception e) { 248 e.printStackTrace(); 249 } 250 return cr; 251 } 252 253 /** 254 * loads a complex region, adds cells (used in GridCutter), save region 255 * 256 * @param filename 257 */ 258 static public void addCellsToShapeRegion(String filename) { 259 SpatialLogger.log("addCellsToShapeRegion(" + filename + ") start"); 260 261 int idx = 0; 262 LinkedBlockingQueue<String> lbq = new LinkedBlockingQueue<String>(); 263 while (idx < 100000) { 264 if (new File(filename + "_" + idx).exists()) { 265 lbq.add(filename + "_" + idx); 266 } 267 idx++; 268 } 269 CountDownLatch cdl = new CountDownLatch(lbq.size()); 270 271 //run these functions at the same time as other build_all requests 272 class AddCellsThread extends Thread { 273 274 public CountDownLatch cdl; 275 public LinkedBlockingQueue<String> lbq; 276 277 @Override 278 public void run() { 279 try { 280 setPriority(MIN_PRIORITY); 281 } catch (Exception e) { 282 e.printStackTrace(); 283 } 284 try { 285 while (true) { 286 String filename = lbq.take(); 287 int p = filename.lastIndexOf('_'); 288 ComplexRegion cr = loadShapeInRegion(filename.substring(0, p), Integer.parseInt(filename.substring(p + 1))); 289 cr.setAttribute("cells", cr.getOverlapGridCells( 290 TabulationSettings.grd_xmin, TabulationSettings.grd_ymin, 291 TabulationSettings.grd_xmax, TabulationSettings.grd_ymax, 292 TabulationSettings.grd_ncols, TabulationSettings.grd_nrows, 293 null)); 294 295 FileOutputStream fos = new FileOutputStream(filename); 296 BufferedOutputStream bos = new BufferedOutputStream(fos); 297 ObjectOutputStream oos = new ObjectOutputStream(bos); 298 oos.writeObject(cr); 299 oos.close(); 300 cdl.countDown(); 301 } 302 } catch (InterruptedException e) { 303 } catch (Exception e) { 304 e.printStackTrace(); 305 } 306 } 307 } 308 309 AddCellsThread[] adc = new AddCellsThread[TabulationSettings.analysis_threads]; 310 for (int i = 0; i < adc.length; i++) { 311 adc[i] = new AddCellsThread(); 312 adc[i].cdl = cdl; 313 adc[i].lbq = lbq; 314 adc[i].start(); 315 } 316 317 try { 318 cdl.await(); 319 } catch (Exception e) { 320 e.printStackTrace(); 321 } 322 323 //end threads 324 for (int i = 0; i < adc.length; i++) { 325 adc[i].interrupt(); 326 } 327 328 SpatialLogger.log("addCellsToShapeRegion(" + filename + ") end"); 329 } 330 331 static public void main(String[] args) { 332 TabulationSettings.load(); 333 334 if (args.length > 0) { 335 SimpleShapeFile.addCellsToShapeRegion( 336 TabulationSettings.index_path 337 + args[0]); 338 } 339 /* 340 SimpleShapeFile.addCellsToShapeRegion( 341 TabulationSettings.index_path 342 + "aus2"); 343 SimpleShapeFile.addCellsToShapeRegion( 344 TabulationSettings.index_path 345 + "imcra4_pb"); 346 SimpleShapeFile.addCellsToShapeRegion( 347 TabulationSettings.index_path 348 + "ibra_reg_shape"); 349 */ 350 } 351 352 /** 353 * returns a list of column names in the 354 * .dbf file 355 * 356 * @return list of column names as String [] 357 */ 358 public String[] listColumns() { 359 return dbf.getColumnNames(); 360 } 361 362 /** 363 * returns set of values found in the .dbf file 364 * at a column number, zero base. 365 * 366 * @param column integer representing column whose set of values 367 * is to be returned. see <code>listColumns()</code> for listing 368 * column names. 369 * 370 * @return set of values in the column as String [] sorted 371 */ 372 public String[] getColumnLookup(int column) { 373 return dbf.getColumnLookup(column); 374 } 375 376 /** 377 * returns the position, zero indexed, of the provided 378 * column_name from within the .dbf 379 * 380 * @param column_name 381 * @return -1 if not found, otherwise column index number, zero base 382 */ 383 public int getColumnIdx(String column_name) { 384 return dbf.getColumnIdx(column_name); 385 } 386 387 /** 388 * use when created from a shape file 389 * 390 * identifies the index within a lookup list provided 391 * for each provided point, or -1 for not found. 392 * 393 * @param points double [n][2] 394 * where 395 * n is number of points 396 * [][0] is longitude 397 * [][1] is latitude 398 * @param lookup String [], same as output from <code>getColumnLookup(column)</code> 399 * @param column .dbf column value to use 400 * @return index within a lookup list provided 401 * for each provided point, or -1 for not found as int [] 402 */ 403 public int[] intersect(double[][] points, String[] lookup, int column) { 404 int i; 405 406 //copy, tag and sort points 407 PointPos[] p = new PointPos[points.length]; 408 for (i = 0; i < p.length; i++) { 409 p[i] = new PointPos(points[i][0], points[i][1], i); 410 } 411 java.util.Arrays.sort(p, new Comparator<PointPos>() { 412 413 @Override 414 public int compare(PointPos o1, PointPos o2) { 415 if (o1.x == o2.x) { 416 return ((o1.y - o2.y) > 0) ? 1 : -1; 417 } else { 418 return ((o1.x - o2.x) > 0) ? 1 : -1; 419 } 420 } 421 }); 422 423 /* setup for thread count */ 424 int threadcount = TabulationSettings.analysis_threads + 5; 425 ArrayList<Integer> threadstart = new ArrayList(threadcount * 10); 426 int step = (int) Math.ceil(points.length / (double) (threadcount * 10)); 427 if (step % 2 != 0) { 428 step++; 429 } 430 int pos = 0; 431 for (i = 0; i < threadcount * 10; i++) { 432 threadstart.add(new Integer(pos)); 433 pos += step; 434 } 435 436 LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue(threadstart); 437 CountDownLatch cdl = new CountDownLatch(lbq.size()); 438 439 IntersectionThread[] it = new IntersectionThread[threadcount]; 440 441 int[] target = new int[points.length]; 442 443 for (i = 0; i < threadcount; i++) { 444 it[i] = new IntersectionThread(shapesreference, p, lbq, step, target, cdl); 445 } 446 447 //wait for all parts to be finished 448 try { 449 cdl.await(); 450 } catch (Exception e) { 451 e.printStackTrace(); 452 } 453 454 //end threads 455 for (i = 0; i < threadcount; i++) { 456 it[i].interrupt(); 457 } 458 459 //transform target from shapes_idx to column_idx 460 for (i = 0; i < target.length; i++) { 461 String s = dbf.getValue(target[i], column); 462 int v = java.util.Arrays.binarySearch(lookup, s); 463 if (v < 0) { 464 v = -1; 465 } 466 target[i] = v; 467 } 468 469 return target; 470 } 471 472 /** 473 * use from saved shape file 474 * 475 * e.g 476 * 477 * <code>int[] output = (new SimpleShapeFile(filename)).intersect(points, 1);</code> 478 * 479 * identifies the index within a lookup list provided 480 * for each provided point, or -1 for not found. 481 * 482 * @param points double [n][2] 483 * where 484 * n is number of points 485 * [][0] is longitude 486 * [][1] is latitude 487 * @param lookup String [], same as output from <code>getColumnLookup(column)</code> 488 * @param column .dbf column value to use 489 * @return index within a lookup list provided 490 * for each provided point, or -1 for not found as int [] 491 */ 492 public int[] intersect(double[][] points, int threadcount) { 493 int i; 494 495 //copy, tag and sort points 496 PointPos[] p = new PointPos[points.length]; 497 for (i = 0; i < p.length; i++) { 498 p[i] = new PointPos(points[i][0], points[i][1], i); 499 } 500 java.util.Arrays.sort(p, new Comparator<PointPos>() { 501 502 @Override 503 public int compare(PointPos o1, PointPos o2) { 504 if (o1.x == o2.x) { 505 return ((o1.y - o2.y) > 0) ? 1 : -1; 506 } else { 507 return ((o1.x - o2.x) > 0) ? 1 : -1; 508 } 509 } 510 }); 511 512 513 /* setup for thread count */ 514 ArrayList<Integer> threadstart = new ArrayList(threadcount * 10); 515 int step = (int) Math.ceil(points.length / (double) (threadcount * 10)); 516 if (step % 2 != 0) { 517 step++; 518 } 519 int pos = 0; 520 for (i = 0; i < threadcount * 10; i++) { 521 threadstart.add(new Integer(pos)); 522 pos += step; 523 } 524 525 System.out.println("parts count = " + threadstart.size()); 526 LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue(threadstart); 527 CountDownLatch cdl = new CountDownLatch(lbq.size()); 528 529 IntersectionThread[] it = new IntersectionThread[threadcount]; 530 531 int[] target = new int[points.length]; 532 533 for (i = 0; i < threadcount; i++) { 534 it[i] = new IntersectionThread(shapesreference, p, lbq, step, target, cdl); 535 } 536 537 try { 538 cdl.await(); 539 } catch (Exception e) { 540 e.printStackTrace(); 541 } 542 543 for (i = 0; i < threadcount; i++) { 544 it[i].interrupt(); 545 } 546 547 //transoform target from shapes_idx to column_idx 548 for (i = 0; i < target.length; i++) { 549 if (target[i] >= 0 && target[i] < singleColumn.length) { 550 target[i] = singleColumn[target[i]]; 551 } else { 552 target[i] = -1; 553 } 554 } 555 556 return target; 557 } 558 559 /** 560 * gets shape header as String 561 * @return String 562 */ 563 public String getHeaderString() { 564 return shapeheader.toString(); 565 } 566 567 public String getValueString(int idx) { 568 return singleLookup[idx]; 569 } 570 571 public String[] getColumnLookup() { 572 return singleLookup; 573 } 574 575 /** 576 * for use with SimpleShapeFile constructed from a Shape File 577 * 578 * generates a list of 'values' per grid cell, for input grid 579 * definition, from .dbf column. 580 * 581 * @param column .dbf column whose sorted set of values is used as index source 582 * @param longitude1 bounding box point 1 longitude 583 * @param latitude1 bounding box point 1 latitude 584 * @param longitude2 bounding box point 2 longitude 585 * @param latitude2 bounding box point 2 latitude 586 * @param width bounding box width in number of cells 587 * @param height bounding box height in number of cells 588 * @return list of (cell x, cell y, cell value) as Tile [] for at least partial cell 589 * coverage 590 */ 591 public Tile[] getTileList(int column, double longitude1, double latitude1, double longitude2, double latitude2, int width, int height) { 592 int i, j, k; 593 594 String[] lookup = getColumnLookup(column); 595 596 Vector<Tile> tiles = new Vector<Tile>(); 597 byte[][] map; 598 int m; 599 600 byte[][] mask = new byte[height][width]; 601 602 for (m = 0; m < lookup.length; m++) { 603 for (i = 0; i < regions.size(); i++) { 604 if (dbf.getValue(i, column).equals(lookup[m])) { 605 map = new byte[height][width]; 606 regions.get(i).getOverlapGridCells(longitude1, latitude1, longitude2, latitude2, width, height, map); 607 608 /* merge on first in basis for partial or complete cells */ 609 for (j = 0; j < map.length; j++) { 610 for (k = 0; k < map[j].length; k++) { 611 if (map[j][k] == SimpleRegion.GI_PARTIALLY_PRESENT 612 || map[j][k] == SimpleRegion.GI_FULLY_PRESENT) { 613 mask[j][k] = 1; //indicate presence 614 } 615 } 616 } 617 } 618 } 619 620 /* add to tiles */ 621 for (i = 0; i < height; i++) { 622 for (j = 0; j < width; j++) { 623 if (mask[i][j] > 0) { //from above indicated presence 624 mask[i][j] = 0; //reset for next region in loop 625 tiles.add(new Tile((float) m, (height - 1 - i) * width + j)); 626 } 627 } 628 } 629 } 630 631 // return as [] instead of Vector 632 Tile[] tilesa = new Tile[tiles.size()]; 633 tiles.toArray(tilesa); 634 return tilesa; 635 } 636 637 /** 638 * for use after loading previously saved SimpleShapeFile 639 * 640 * generates a list of 'values' per grid cell, for input grid 641 * definition, from .dbf column. 642 * 643 * @param longitude1 bounding box point 1 longitude 644 * @param latitude1 bounding box point 1 latitude 645 * @param longitude2 bounding box point 2 longitude 646 * @param latitude2 bounding box point 2 latitude 647 * @param width bounding box width in number of cells 648 * @param height bounding box height in number of cells 649 * @return list of (cell x, cell y, cell value) as Tile [] for at least partial cell 650 * coverage 651 */ 652 public Tile[] getTileList(double longitude1, double latitude1, double longitude2, double latitude2, int width, int height) { 653 int i, j, k; 654 655 String[] lookup = singleLookup; 656 657 Vector<Tile> tiles = new Vector<Tile>(); 658 byte[][] map; 659 int m; 660 661 byte[][] mask = new byte[height][width]; 662 663 for (m = 0; m < lookup.length; m++) { 664 for (i = 0; i < regions.size(); i++) { 665 if (singleColumn[i] == (short) m) { 666 map = new byte[height][width]; 667 668 shapesreference.sr.region.get(i).getOverlapGridCells(longitude1, latitude1, longitude2, latitude2, width, height, map); 669 670 /* merge on first in basis for partial or complete cells */ 671 for (j = 0; j < map.length; j++) { 672 for (k = 0; k < map[j].length; k++) { 673 if (map[j][k] == SimpleRegion.GI_PARTIALLY_PRESENT 674 || map[j][k] == SimpleRegion.GI_FULLY_PRESENT) { 675 mask[j][k] = 1; //indicate presence 676 } 677 } 678 } 679 } 680 } 681 682 /* add to tiles */ 683 for (i = 0; i < height; i++) { 684 for (j = 0; j < width; j++) { 685 if (mask[i][j] > 0) { //from above indicated presence 686 mask[i][j] = 0; //reset for next region in loop 687 tiles.add(new Tile((float) m, (height - 1 - i) * width + j)); 688 } 689 } 690 } 691 } 692 693 // return as [] instead of Vector 694 Tile[] tilesa = new Tile[tiles.size()]; 695 tiles.toArray(tilesa); 696 return tilesa; 697 } 698 699 /** 700 * defines a region by a points string, POLYGON only 701 * 702 * TODO: define better format for parsing, including BOUNDING_BOX and CIRCLE 703 * 704 * @param pointsString points separated by ',' with longitude and latitude separated by ':' 705 * @return SimpleRegion object 706 */ 707 public static SimpleRegion parseWKT(String pointsString) { 708 if (pointsString == null) { 709 return null; 710 } 711 712 ArrayList<ArrayList<SimpleRegion>> regions = new ArrayList<ArrayList<SimpleRegion>>(); 713 714 if (pointsString.startsWith("GEOMETRYCOLLECTION")) { 715 regions.addAll(parseGeometryCollection(pointsString.substring("GEOMETRYCOLLECTION(((".length(), pointsString.length() - 1))); 716 } else if (pointsString.startsWith("MULTIPOLYGON")) { 717 regions.addAll(parseMultipolygon(pointsString.substring("MULTIPOLYGON(((".length(), pointsString.length() - 3))); 718 } else if (pointsString.startsWith("POLYGON")) { 719 regions.add(parsePolygon(pointsString.substring("POLYGON((".length(), pointsString.length() - 2))); 720 } 721 722 if (regions.size() == 0) { 723 return null; 724 } else if (regions.size() == 1 && regions.get(0).size() == 1) { 725 return regions.get(0).get(0); 726 } else { 727 ComplexRegion cr = new ComplexRegion(); 728 for (int i = 0; i < regions.size(); i++) { 729 cr.addSet(regions.get(i)); 730 } 731 cr.useMask(-1, -1, -1); 732 return cr; 733 } 734 } 735 736 static ArrayList<ArrayList<SimpleRegion>> parseGeometryCollection(String pointsString) { 737 ArrayList<String> stringsList = new ArrayList<String>(); 738 739 int posStart = minPos(pointsString, "POLYGON", "MULTIPOLYGON", 0); 740 int posEnd = minPos(pointsString, "POLYGON", "MULTIPOLYGON", posStart + 10); 741 while (posEnd > 0) { 742 stringsList.add(pointsString.substring(posStart, posEnd - 1)); 743 posStart = posEnd; 744 posEnd = minPos(pointsString, "POLYGON", "MULTIPOLYGON", posStart + 10); 745 } 746 stringsList.add(pointsString.substring(posStart, pointsString.length())); 747 748 ArrayList<ArrayList<SimpleRegion>> regions = new ArrayList<ArrayList<SimpleRegion>>(); 749 for (int i = 0; i < stringsList.size(); i++) { 750 if (stringsList.get(i).startsWith("MULTIPOLYGON")) { 751 //remove trailing ")))" 752 regions.addAll(parseMultipolygon(stringsList.get(i).substring("MULTIPOLYGON(((".length(), stringsList.get(i).length() - 3))); 753 } else if (stringsList.get(i).startsWith("POLYGON")) { 754 //remove trailing "))" 755 regions.add(parsePolygon(stringsList.get(i).substring("POLYGON((".length(), stringsList.get(i).length() - 2))); 756 } 757 } 758 759 return regions; 760 } 761 762 static ArrayList<ArrayList<SimpleRegion>> parseMultipolygon(String multipolygon) { 763 ArrayList<ArrayList<SimpleRegion>> regions = new ArrayList<ArrayList<SimpleRegion>>(); 764 String[] splitMultipolygon = multipolygon.split("\\)\\),\\(\\("); 765 for (int j = 0; j < splitMultipolygon.length; j++) { 766 regions.add(parsePolygon(splitMultipolygon[j])); 767 } 768 return regions; 769 } 770 771 static ArrayList<SimpleRegion> parsePolygon(String polygon) { 772 ArrayList<SimpleRegion> regions = new ArrayList<SimpleRegion>(); 773 for (String p : polygon.split("\\),\\(")) { 774 regions.add(SimpleRegion.parseSimpleRegion(p)); 775 } 776 return regions; 777 } 778 779 static int minPos(String lookIn, String lookFor1, String lookFor2, int startPos) { 780 int pos, p1, p2; 781 p1 = lookIn.indexOf(lookFor1, startPos); 782 p2 = lookIn.indexOf(lookFor2, startPos); 783 if (p1 < 0) { 784 pos = p2; 785 } else if (p2 < 0) { 786 pos = p1; 787 } else { 788 pos = Math.min(p1, p2); 789 } 790 return pos; 791 } 792} 793 794/** 795 * represents partial shape file header structure 796 * 797 * @author adam 798 * 799 */ 800class ShapeHeader extends Object implements Serializable { 801 802 static final long serialVersionUID = 1219127870707511387L; 803 804 /* from .shp file header specification */ 805 int filecode; 806 int filelength; 807 int version; 808 int shapetype; 809 double[] boundingbox; 810 811 /* TODO: use appropriately for failed constructor */ 812 boolean isvalid; 813 814 /** 815 * constructor takes shapefile file path, appends .shp itself, 816 * and reads in the shape file header values. 817 * 818 * TODO: any validation 819 * 820 * @param fileprefix 821 */ 822 public ShapeHeader(String fileprefix) { 823 try { 824 FileInputStream fis = new FileInputStream(fileprefix + ".shp"); 825 FileChannel fc = fis.getChannel(); 826 ByteBuffer buffer = ByteBuffer.allocate(1024); //header will be smaller 827 828 fc.read(buffer); 829 buffer.flip(); 830 831 buffer.order(ByteOrder.BIG_ENDIAN); 832 filecode = buffer.getInt(); 833 buffer.getInt(); 834 buffer.getInt(); 835 buffer.getInt(); 836 buffer.getInt(); 837 buffer.getInt(); 838 filelength = buffer.getInt(); 839 840 buffer.order(ByteOrder.LITTLE_ENDIAN); 841 version = buffer.getInt(); 842 shapetype = buffer.getInt(); 843 boundingbox = new double[8]; 844 for (int i = 0; i < 8; i++) { 845 boundingbox[i] = buffer.getDouble(); 846 } 847 848 fis.close(); 849 850 isvalid = true; 851 } catch (Exception e) { 852 System.out.println("loading header error: " + fileprefix + ": " + e.toString()); 853 e.printStackTrace(); 854 } 855 } 856 857 /** 858 * returns shape file type as indicated in header 859 * 860 * @return shape file type as int 861 */ 862 public int getShapeType() { 863 return shapetype; 864 } 865 866 /** 867 * format .shp header contents 868 * @return 869 */ 870 @Override 871 public String toString() { 872 StringBuffer sb = new StringBuffer(); 873 874 sb.append("\r\nFile Code: \r\n"); 875 sb.append(String.valueOf(filecode)); 876 877 sb.append("\r\nFile Length: \r\n"); 878 sb.append(String.valueOf(filelength)); 879 880 sb.append("\r\nVersion: \r\n"); 881 sb.append(String.valueOf(version)); 882 883 sb.append("\r\nShape Type: \r\n"); 884 sb.append(String.valueOf(shapetype)); 885 886 int i = 0; 887 888 sb.append("\r\nXmin: \r\n"); 889 sb.append(String.valueOf(boundingbox[i++])); 890 891 sb.append("\r\nYmin: \r\n"); 892 sb.append(String.valueOf(boundingbox[i++])); 893 894 sb.append("\r\nXmax: \r\n"); 895 sb.append(String.valueOf(boundingbox[i++])); 896 897 898 sb.append("\r\nYmax: \r\n"); 899 sb.append(String.valueOf(boundingbox[i++])); 900 901 902 sb.append("\r\nZmin: \r\n"); 903 sb.append(String.valueOf(boundingbox[i++])); 904 905 sb.append("\r\nZmax: \r\n"); 906 sb.append(String.valueOf(boundingbox[i++])); 907 908 sb.append("\r\nMmin: \r\n"); 909 sb.append(String.valueOf(boundingbox[i++])); 910 911 sb.append("\r\nMmax: \r\n"); 912 sb.append(String.valueOf(boundingbox[i++])); 913 914 return sb.toString(); 915 } 916 917 /** 918 * @return true iff header loaded and passed validation 919 */ 920 public boolean isValid() { 921 return isvalid; 922 } 923} 924 925/** 926 * collection of shape file records 927 * 928 * @author adam 929 * 930 */ 931class ShapeRecords extends Object implements Serializable { 932 933 static final long serialVersionUID = -8141403235810528840L; 934 /** 935 * list of ShapeRecord 936 */ 937 ArrayList<ShapeRecord> records; 938 /** 939 * true if constructor was successful 940 */ 941 boolean isvalid; 942 ArrayList<ComplexRegion> region; 943 944 /** 945 * constructor creates the shape file from filepath 946 * with specified shape type (only 5, MULTIPOLYGON for now) 947 * 948 * TODO: static 949 * TODO: validation 950 * 951 * @param fileprefix 952 * @param shapetype 953 */ 954 public ShapeRecords(String fileprefix, int shapetype) { 955 isvalid = false; 956 try { 957 FileInputStream fis = new FileInputStream(fileprefix + ".shp"); 958 FileChannel fc = fis.getChannel(); 959 ByteBuffer buffer = ByteBuffer.allocate((int) fc.size() - 100); 960 961 fc.read(buffer, 100); //records header starts at 100 962 buffer.flip(); 963 buffer.order(ByteOrder.BIG_ENDIAN); 964 965 records = new ArrayList(); 966 967 while (buffer.hasRemaining()) { 968 records.add(new ShapeRecord(buffer, shapetype)); 969 } 970 971 fis.close(); 972 973 isvalid = true; 974 } catch (Exception e) { 975 System.out.println("loading shape records error: " + fileprefix + ": " + e.toString()); 976 e.printStackTrace(); 977 } 978 } 979 980 /** 981 * @return true iff records loaded and passed validation 982 */ 983 public boolean isValid() { 984 return isvalid; 985 } 986 987 /** 988 * creates a list of ComplexRegion objects from 989 * loaded shape file records * 990 * 991 * @return 992 */ 993 public ArrayList<ComplexRegion> getRegions() { 994 if (region == null) { 995 /* object for output */ 996 ArrayList<ComplexRegion> sra = new ArrayList(); 997 998 for (int i = 0; i < records.size(); i++) { 999 ShapeRecord shr = records.get(i); 1000 ComplexRegion sr = new ComplexRegion(); 1001 ArrayList<SimpleRegion> regions = new ArrayList(); 1002 1003 /* add each polygon (list of points) belonging to 1004 * this shape record to the new ComplexRegion 1005 */ 1006 for (int j = 0; j < shr.getNumberOfParts(); j++) { 1007 SimpleRegion s = new SimpleRegion(); 1008 s.setPolygon(shr.getPoints(j)); 1009 //sr.addPolygon(s); 1010 regions.add(s); 1011 } 1012 1013 sr.addSet(regions); 1014 1015 sra.add(sr); 1016 } 1017 region = sra; 1018 } 1019 return region; 1020 } 1021 1022 /** 1023 * gets number of shape records 1024 * 1025 * @return 1026 */ 1027 public int getNumberOfRecords() { 1028 return records.size(); 1029 } 1030} 1031 1032/** 1033 * collection of shape file records 1034 * 1035 * @author adam 1036 * 1037 */ 1038class ShapeRecord extends Object implements Serializable { 1039 1040 static final long serialVersionUID = -4426292545633280160L; 1041 int recordnumber; 1042 int contentlength; 1043 Shape shape; 1044 1045 public ShapeRecord(ByteBuffer bb, int shapetype) { 1046 bb.order(ByteOrder.BIG_ENDIAN); 1047 recordnumber = bb.getInt(); 1048 contentlength = bb.getInt(); 1049 1050 switch (shapetype) { 1051 case 5: 1052 shape = new Polygon(bb); 1053 break; 1054 default: 1055 System.out.println("unknown shape type: " + shapetype); 1056 } 1057 } 1058 1059 /** 1060 * format .shp record summary 1061 * @return String 1062 */ 1063 @Override 1064 public String toString() { 1065 if (shape != null) { 1066 return "Record Number: " + recordnumber + ", Content Length: " + contentlength 1067 + shape.toString(); 1068 } 1069 1070 return "Record Number: " + recordnumber + ", Content Length: " + contentlength; 1071 } 1072 1073 /** 1074 * gets the list of points for a shape part 1075 * 1076 * @param part index of shape part 1077 * @return points as double[][2] 1078 * where 1079 * [][0] is longitude 1080 * [][1] is latitude 1081 */ 1082 public double[][] getPoints(int part) { 1083 return shape.getPoints(part); 1084 } 1085 1086 /** 1087 * gets number of parts in this shape 1088 * 1089 * @return number of parts as int 1090 */ 1091 public int getNumberOfParts() { 1092 return shape.getNumberOfParts(); 1093 } 1094} 1095 1096/** 1097 * empty shape template 1098 * 1099 * TODO: abstract 1100 * 1101 * @author adam 1102 * 1103 */ 1104class Shape extends Object implements Serializable { 1105 1106 static final long serialVersionUID = 8573677305368105719L; 1107 1108 /** 1109 * default constructor 1110 * 1111 */ 1112 public Shape() { 1113 } 1114 1115 /** 1116 * returns a list of points for the numbered shape part. 1117 * 1118 * @param part index of part to return 1119 * 1120 * @return double[][2] containing longitude and latitude 1121 * pairs where 1122 * [][0] is longitude 1123 * [][1] is latitude 1124 */ 1125 public double[][] getPoints(int part) { 1126 return null; 1127 } 1128 1129 /** 1130 * returns number of parts in this shape 1131 * 1132 * @return number of parts as int 1133 */ 1134 public int getNumberOfParts() { 1135 return 0; 1136 } 1137} 1138 1139/** 1140 * object for shape file POLYGON 1141 * 1142 * @author adam 1143 * 1144 */ 1145class Polygon extends Shape { 1146 1147 /** 1148 * shape file POLYGON record fields 1149 */ 1150 int shapetype; 1151 double[] boundingbox; 1152 int numparts; 1153 int numpoints; 1154 int[] parts; 1155 double[] points; 1156 1157 /** 1158 * creates a shape file POLYGON from a ByteBuffer 1159 * 1160 * TODO: static 1161 * 1162 * @param bb ByteBuffer containing record bytes 1163 * from a shape file POLYGON record 1164 */ 1165 public Polygon(ByteBuffer bb) { 1166 int i; 1167 1168 bb.order(ByteOrder.LITTLE_ENDIAN); 1169 shapetype = bb.getInt(); 1170 1171 boundingbox = new double[4]; 1172 for (i = 0; i < 4; i++) { 1173 boundingbox[i] = bb.getDouble(); 1174 } 1175 1176 numparts = bb.getInt(); 1177 1178 numpoints = bb.getInt(); 1179 1180 parts = new int[numparts]; 1181 for (i = 0; i < numparts; i++) { 1182 parts[i] = bb.getInt(); 1183 } 1184 1185 points = new double[numpoints * 2]; //x,y pairs 1186 for (i = 0; i < numpoints * 2; i++) { 1187 points[i] = bb.getDouble(); 1188 } 1189 } 1190 1191 /** 1192 * output .shp POLYGON summary 1193 * @return String 1194 */ 1195 @Override 1196 public String toString() { 1197 StringBuffer sb = new StringBuffer(); 1198 1199 sb.append("("); 1200 sb.append(String.valueOf(boundingbox[0])); 1201 sb.append(", "); 1202 sb.append(String.valueOf(boundingbox[1])); 1203 1204 sb.append(") ("); 1205 sb.append(String.valueOf(boundingbox[2])); 1206 sb.append(", "); 1207 sb.append(String.valueOf(boundingbox[3])); 1208 1209 sb.append(") parts="); 1210 sb.append(String.valueOf(numparts)); 1211 sb.append(" points="); 1212 sb.append(String.valueOf(numpoints)); 1213 1214 return sb.toString(); 1215 } 1216 1217 /** 1218 * returns number of parts in this shape 1219 * 1220 * one part == one polygon 1221 * 1222 * @return number of parts as int 1223 */ 1224 @Override 1225 public int getNumberOfParts() { 1226 return numparts; 1227 } 1228 1229 /** 1230 * returns a list of points for the numbered shape part. 1231 * 1232 * @param part index of part to return 1233 * 1234 * @return double[][2] containing longitude and latitude 1235 * pairs where 1236 * [][0] is longitude 1237 * [][1] is latitude 1238 */ 1239 @Override 1240 public double[][] getPoints(int part) { 1241 double[][] output; //data to return 1242 int start = parts[part]; //first index of this part 1243 1244 /* last index of this part */ 1245 int end = numpoints; 1246 if (part < numparts - 1) { 1247 end = parts[part + 1]; 1248 } 1249 1250 /* fill output */ 1251 output = new double[end - start][2]; 1252 for (int i = start; i < end; i++) { 1253 output[i - start][0] = points[i * 2]; 1254 output[i - start][1] = points[i * 2 + 1]; 1255 } 1256 return output; 1257 } 1258} 1259 1260/** 1261 * .dbf file object 1262 * 1263 * @author adam 1264 * 1265 */ 1266class DBF extends Object implements Serializable { 1267 1268 static final long serialVersionUID = -1631837349804567374L; 1269 /** 1270 * .dbf file header object 1271 */ 1272 DBFHeader dbfheader; 1273 /** 1274 * .dbf records 1275 */ 1276 DBFRecords dbfrecords; 1277 1278 /** 1279 * constructor for new DBF from .dbf filename 1280 * @param filename path and file name of .dbf file 1281 */ 1282 public DBF(String filename) { 1283 /* get file header */ 1284 dbfheader = new DBFHeader(filename); 1285 1286 /* get records */ 1287 dbfrecords = new DBFRecords(filename, dbfheader); 1288 } 1289 1290 /** 1291 * returns index of a column by column name 1292 * 1293 * @param column_name column name as String 1294 * @return index of column_name as int 1295 * -1 for none 1296 */ 1297 public int getColumnIdx(String column_name) { 1298 return dbfheader.getColumnIdx(column_name); 1299 } 1300 1301 /** 1302 * gets the list of column names in column order 1303 * 1304 * @return column names as String [] 1305 */ 1306 public String[] getColumnNames() { 1307 return dbfheader.getColumnNames(); 1308 } 1309 1310 /** 1311 * gets the value at a row and column 1312 * @param row row index as int 1313 * @param column column index as int 1314 * @return value as String 1315 */ 1316 public String getValue(int row, int column) { 1317 return dbfrecords.getValue(row, column); 1318 } 1319 1320 /** 1321 * lists all values in a specified column as a sorted set 1322 * 1323 * @param column index of column values to return 1324 * @return sorted set of values in the column as String[] 1325 */ 1326 public String[] getColumnLookup(int column) { 1327 int i, len; 1328 TreeSet<String> ts = new TreeSet<String>(); 1329 1330 /* build set */ 1331 len = dbfheader.getNumberOfRecords(); 1332 for (i = 0; i < len; i++) { 1333 ts.add(getValue(i, column)); 1334 } 1335 1336 /* convert to sorted [] */ 1337 String[] sa = new String[ts.size()]; 1338 ts.toArray(sa); 1339 1340 return sa; 1341 } 1342 1343 /** 1344 * lists values that are occur on more than one row 1345 * 1346 * @param column index of column values to return 1347 * @return sorted set of duplicated values in the column as String[] 1348 */ 1349 public String[] getColumnMultipleValues(int column) { 1350 int i, len; 1351 TreeSet<String> ts = new TreeSet<String>(); 1352 TreeSet<String> dup = new TreeSet<String>(); 1353 1354 /* build set */ 1355 len = dbfheader.getNumberOfRecords(); 1356 for (i = 0; i < len; i++) { 1357 String v = getValue(i, column); 1358 if (ts.contains(v)) { 1359 dup.add(v); 1360 } else { 1361 ts.add(v); 1362 } 1363 } 1364 1365 /* convert to sorted [] */ 1366 String[] sa = new String[dup.size()]; 1367 dup.toArray(sa); 1368 1369 return sa; 1370 } 1371} 1372 1373/** 1374 * .dbf header object 1375 * 1376 * @author adam 1377 * 1378 */ 1379class DBFHeader extends Object implements Serializable { 1380 1381 static final long serialVersionUID = -1807390252140128281L; 1382 /** 1383 * .dbf header fields (partial) 1384 */ 1385 int filetype; 1386 int[] lastupdate; 1387 int numberofrecords; 1388 int recordsoffset; 1389 int recordlength; 1390 int tableflags; 1391 /* int codepagemark; */ 1392 /** 1393 * list of fields/columns in the .dbf header 1394 */ 1395 ArrayList<DBFField> fields; 1396 /* TODO: use appropriately for failed constructor */ 1397 boolean isvalid; 1398 1399 /** 1400 * constructor for DBFHeader from a .dbf filename 1401 * @param filename filename of .dbf file as String 1402 */ 1403 public DBFHeader(String filename) { 1404 1405 isvalid = false; 1406 try { 1407 int i; 1408 1409 /* load whole file */ 1410 FileInputStream fis = new FileInputStream(filename); 1411 FileChannel fc = fis.getChannel(); 1412 ByteBuffer buffer = ByteBuffer.allocate((int) fc.size()); 1413 fc.read(buffer); 1414 buffer.flip(); //ready to read 1415 buffer.order(ByteOrder.BIG_ENDIAN); 1416 1417 filetype = (0xFF & buffer.get()); 1418 lastupdate = new int[3]; 1419 for (i = 0; i < 3; i++) { 1420 lastupdate[i] = (0xFF & buffer.get()); 1421 } 1422 numberofrecords = (0xFF & buffer.get()) + 256 * ((0xFF & buffer.get()) 1423 + 256 * ((0xFF & buffer.get()) + 256 * (0xFF & buffer.get()))); 1424 recordsoffset = (0xFF & buffer.get()) + 256 * (0xFF & buffer.get()); 1425 recordlength = (0xFF & buffer.get()) + 256 * (0xFF & buffer.get()); 1426 for (i = 0; i < 16; i++) { 1427 buffer.get(); 1428 } 1429 tableflags = (0xFF & buffer.get()); 1430 /* codepagemark = */ buffer.get(); 1431 buffer.get(); 1432 buffer.get(); 1433 1434 fields = new ArrayList(); 1435 byte nextfsr; 1436 while ((nextfsr = buffer.get()) != 0x0D) { 1437 fields.add(new DBFField(nextfsr, buffer)); 1438 } 1439 /* don't care dbc, skip */ 1440 1441 fis.close(); 1442 1443 isvalid = true; 1444 } catch (Exception e) { 1445 System.out.println("loading dbfheader error: " + filename + ": " + e.toString()); 1446 e.printStackTrace(); 1447 } 1448 } 1449 1450 /** 1451 * gets field list 1452 * @return all fields as ArrayList<DBFField> 1453 */ 1454 public ArrayList<DBFField> getFields() { 1455 return fields; 1456 } 1457 1458 /** 1459 * gets records offset in .dbf file 1460 * 1461 * @return record offset as int 1462 */ 1463 public int getRecordsOffset() { 1464 return recordsoffset; 1465 } 1466 1467 /** 1468 * gets the number of records in the .dbf 1469 * @return number of records as int 1470 */ 1471 public int getNumberOfRecords() { 1472 return numberofrecords; 1473 } 1474 1475 /** 1476 * gets the list of column names in column order 1477 * 1478 * @return column names as String [] 1479 */ 1480 public String[] getColumnNames() { 1481 String[] s = new String[fields.size()]; 1482 for (int i = 0; i < s.length; i++) { 1483 s[i] = fields.get(i).getName(); 1484 } 1485 return s; 1486 } 1487 1488 /** 1489 * gets the index of a column from a column name 1490 * 1491 * case insensitive 1492 * 1493 * @param column_name column name as String 1494 * @return index of column name 1495 * -1 for not found 1496 */ 1497 public int getColumnIdx(String column_name) { 1498 for (int i = 0; i < fields.size(); i++) { 1499 if (fields.get(i).getName().equalsIgnoreCase(column_name)) { 1500 return i; 1501 } 1502 } 1503 1504 return -1; 1505 } 1506 1507 /** 1508 * format .dbf header summary 1509 * @return String 1510 */ 1511 @Override 1512 public String toString() { 1513 if (!isvalid) { 1514 return "invalid header"; 1515 } 1516 1517 StringBuffer sb = new StringBuffer(); 1518 1519 sb.append("filetype: "); 1520 sb.append(String.valueOf(filetype)); 1521 sb.append("\r\nlastupdate: "); 1522 sb.append(String.valueOf(lastupdate[0])); 1523 sb.append(" "); 1524 sb.append(String.valueOf(lastupdate[1])); 1525 sb.append(" "); 1526 sb.append(String.valueOf(lastupdate[2])); 1527 sb.append("\r\nnumberofrecords: "); 1528 sb.append(String.valueOf(numberofrecords)); 1529 sb.append("\r\nrecordsoffset: "); 1530 sb.append(String.valueOf(recordsoffset)); 1531 sb.append("\r\nrecordlength: "); 1532 sb.append(String.valueOf(recordlength)); 1533 sb.append("\r\ntableflags: "); 1534 sb.append(String.valueOf(tableflags)); 1535 sb.append("\r\nnumber of fields: "); 1536 sb.append(String.valueOf(fields.size())); 1537 1538 for (int i = 0; i < fields.size(); i++) { 1539 sb.append(fields.get(i).toString()); 1540 } 1541 1542 return sb.toString(); 1543 } 1544} 1545 1546/** 1547 * .dbf header field object 1548 * 1549 * @author adam 1550 * 1551 */ 1552class DBFField extends Object implements Serializable { 1553 1554 static final long serialVersionUID = 6130879839715559815L; 1555 /* 1556 * .dbf Field records (partial) 1557 */ 1558 String name; 1559 char type; 1560 int displacement; 1561 int length; 1562 int decimals; 1563 int flags; 1564 byte[] data; //placeholder for reading byte blocks 1565 /* don't care autoinc */ 1566 1567 void test() { 1568 } 1569 1570 /** 1571 * constructor for DBFField with first byte separated from 1572 * rest of the data structure 1573 * 1574 * TODO: static and more cleaner 1575 * 1576 * @param firstbyte first byte of .dbf field record as byte 1577 * @param buffer remaining byes of .dbf field record as ByteBuffer 1578 */ 1579 public DBFField(byte firstbyte, ByteBuffer buffer) { 1580 int i; 1581 byte[] ba = new byte[12]; 1582 ba[0] = firstbyte; 1583 for (i = 1; i < 11; i++) { 1584 ba[i] = buffer.get(); 1585 } 1586 try { 1587 name = (new String(ba, "US-ASCII")).trim().toUpperCase(); 1588 } catch (Exception e) { 1589 System.out.println(e.toString()); 1590 e.printStackTrace(); 1591 } 1592 1593 byte[] ba2 = new byte[1]; 1594 ba2[0] = buffer.get(); 1595 try { 1596 type = (new String(ba2, "US-ASCII")).charAt(0); 1597 } catch (Exception e) { 1598 System.out.println(e.toString()); 1599 e.printStackTrace(); 1600 } 1601 1602 displacement = (0xFF & buffer.get()) + 256 * ((0xFF & buffer.get()) 1603 + 256 * ((0xFF & buffer.get()) + 256 * (0xFF & buffer.get()))); 1604 1605 length = (0xFF & buffer.get()); 1606 data = new byte[length]; 1607 1608 decimals = (0xFF & buffer.get()); 1609 flags = (0xFF & buffer.get()); 1610 1611 /* skip over end */ 1612 for (i = 0; i < 13; i++) { 1613 buffer.get(); 1614 } 1615 } 1616 1617 /** 1618 * gets field name 1619 * @return field name as String 1620 */ 1621 public String getName() { 1622 return name; 1623 } 1624 1625 /** 1626 * gets field type 1627 * @return field type as char 1628 */ 1629 public char getType() { 1630 return type; 1631 } 1632 1633 /** 1634 * gets data block 1635 * 1636 * for use in .read(getDataBlock()) like functions 1637 * when reading records 1638 * 1639 * @return 1640 */ 1641 public byte[] getDataBlock() { 1642 return data; 1643 } 1644 1645 /** 1646 * format Field summary 1647 * @return String 1648 */ 1649 @Override 1650 public String toString() { 1651 return "name: " + name + " type: " + type + " displacement: " + displacement + " length: " + length + "\r\n"; 1652 } 1653} 1654 1655/** 1656 * collection of .dbf records 1657 * 1658 * @author adam 1659 * 1660 */ 1661class DBFRecords extends Object implements Serializable { 1662 1663 static final long serialVersionUID = -2450196133919654852L; 1664 /** 1665 * list of DBFRecord 1666 */ 1667 ArrayList<DBFRecord> records; 1668 /* TODO: use appropriately for failed constructor */ 1669 boolean isvalid; 1670 1671 /** 1672 * constructor for collection of DBFRecord from a DBFHeader and 1673 * .dbf filename 1674 * @param filename .dbf file as String 1675 * @param header dbf header from filename as DBFHeader 1676 */ 1677 public DBFRecords(String filename, DBFHeader header) { 1678 /* init */ 1679 records = new ArrayList(); 1680 isvalid = false; 1681 1682 try { 1683 /* load all records */ 1684 FileInputStream fis = new FileInputStream(filename); 1685 FileChannel fc = fis.getChannel(); 1686 ByteBuffer buffer = ByteBuffer.allocate((int) fc.size() - header.getRecordsOffset()); 1687 fc.read(buffer, header.getRecordsOffset()); 1688 buffer.flip(); //prepare for reading 1689 1690 1691 1692 /* add each …
Large files files are truncated, but you can click here to view the full file