PageRenderTime 107ms CodeModel.GetById 20ms app.highlight 75ms RepoModel.GetById 1ms app.codeStats 1ms

/alaspatial/src/main/java/org/ala/spatial/util/SimpleShapeFile.java

http://alageospatialportal.googlecode.com/
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