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