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

http://alageospatialportal.googlecode.com/ · Java · 622 lines · 413 code · 66 blank · 143 comment · 201 complexity · 630d210101a2e30699ccfd159334021a MD5 · raw file

  1. package org.ala.spatial.util;
  2. import java.util.ArrayList;
  3. /**
  4. * ComplexRegion is a collection of SimpleRegion, expect POLYGONs for now.
  5. *
  6. * treat as a shape file, overlapping regions cancel out presence.
  7. *
  8. * TODO: clockwise/anticlockwise identification
  9. *
  10. * @author Adam Collins
  11. */
  12. public class ComplexRegion extends SimpleRegion {
  13. static public SimpleRegion parseComplexRegion(String[] polygons) {
  14. ComplexRegion cr = new ComplexRegion();
  15. for (String s : polygons) {
  16. cr.addPolygon(SimpleRegion.parseSimpleRegion(s));
  17. }
  18. cr.useMask(-1, -1, -1);
  19. return cr;
  20. }
  21. /**
  22. * list of SimpleRegion members
  23. */
  24. ArrayList<SimpleRegion> simpleregions;
  25. /**
  26. * bounding box for all, see SimpleRegion boundingbox.
  27. */
  28. double[][] boundingbox_all;
  29. /**
  30. * value assigned
  31. */
  32. int value;
  33. /**
  34. * array for speeding up isWithin
  35. */
  36. byte[][] mask;
  37. Object[][] maskDepth;
  38. /**
  39. * mask height
  40. */
  41. int mask_height;
  42. /**
  43. * mask width
  44. */
  45. int mask_width;
  46. /**
  47. * mask multiplier for longitude inputs
  48. */
  49. double mask_long_multiplier;
  50. /**
  51. * mask mulitplier for latitude inputs
  52. */
  53. double mask_lat_multiplier;
  54. /**
  55. * maintain mapping for simpleregions belonging to the same polygon
  56. */
  57. ArrayList<Integer> polygons;
  58. /**
  59. * Constructor for empty ComplexRegion
  60. */
  61. public ComplexRegion() {
  62. super();
  63. simpleregions = new ArrayList();
  64. boundingbox_all = new double[2][2];
  65. value = -1;
  66. mask = null;
  67. polygons = new ArrayList();
  68. }
  69. /**
  70. * sets integer value stored
  71. * @param value_ as int
  72. */
  73. public void setValue(int value_) {
  74. value = value_;
  75. }
  76. /**
  77. * gets integer value stored
  78. * @return int
  79. */
  80. public int getValue() {
  81. return value;
  82. }
  83. /**
  84. * gets the bounding box for shapes in this ComplexRegion
  85. *
  86. * @return bounding box for ComplexRegion as double [][]
  87. */
  88. @Override
  89. public double[][] getBoundingBox() {
  90. return boundingbox_all;
  91. }
  92. /**
  93. * adds a new polygon
  94. *
  95. * note: if a mask is in use must call <code>useMask</code> again
  96. * @param points_ points = double[n][2]
  97. * where
  98. * n is number of points
  99. * [][0] is longitude
  100. * [][1] is latitude
  101. */
  102. public void addPolygon(SimpleRegion sr) {
  103. simpleregions.add(sr);
  104. /* update boundingbox_all */
  105. double[][] bb = sr.getBoundingBox();
  106. if (simpleregions.size() == 1 || boundingbox_all[0][0] > bb[0][0]) {
  107. boundingbox_all[0][0] = bb[0][0];
  108. }
  109. if (simpleregions.size() == 1 || boundingbox_all[1][0] < bb[1][0]) {
  110. boundingbox_all[1][0] = bb[1][0];
  111. }
  112. if (simpleregions.size() == 1 || boundingbox_all[0][1] > bb[0][1]) {
  113. boundingbox_all[0][1] = bb[0][1];
  114. }
  115. if (simpleregions.size() == 1 || boundingbox_all[1][1] < bb[1][1]) {
  116. boundingbox_all[1][1] = bb[1][1];
  117. }
  118. bounding_box = boundingbox_all;
  119. }
  120. /**
  121. * returns true when the point provided is within the ComplexRegion
  122. *
  123. * uses <code>mask</code> when available
  124. *
  125. * note: type UNDEFINED implies no boundary, always returns true.
  126. *
  127. * @param longitude
  128. * @param latitude
  129. * @return true iff point is within or on the edge of this ComplexRegion
  130. */
  131. @Override
  132. public boolean isWithin(double longitude, double latitude) {
  133. if (simpleregions.size() == 1) {
  134. return simpleregions.get(0).isWithin(longitude, latitude);
  135. }
  136. if (boundingbox_all[0][0] > longitude || boundingbox_all[1][0] < longitude
  137. || boundingbox_all[0][1] > latitude || boundingbox_all[1][1] < latitude) {
  138. return false;
  139. }
  140. short[] countsIn = new short[polygons.get(polygons.size() - 1) + 1];
  141. //int count_in = 0; //count of regions overlapping the point
  142. if (mask != null) {
  143. /* use mask if exists */
  144. int long1 = (int) Math.floor((longitude - boundingbox_all[0][0]) * mask_long_multiplier);
  145. int lat1 = (int) Math.floor((latitude - boundingbox_all[0][1]) * mask_lat_multiplier);
  146. if (long1 == mask[0].length) {
  147. long1--;
  148. }
  149. if (lat1 == mask.length) {
  150. lat1--;
  151. }
  152. if (mask[lat1][long1] == SimpleRegion.GI_FULLY_PRESENT) {
  153. return true;
  154. } else if (mask[lat1][long1] == SimpleRegion.GI_UNDEFINED
  155. || mask[lat1][long1] == SimpleRegion.GI_ABSENCE) {
  156. return false;
  157. }
  158. //partial, try maskDepth and sum overlaps
  159. if (maskDepth != null && maskDepth[lat1][long1] != null) {
  160. int[] d = (int[]) maskDepth[lat1][long1];
  161. for (int i = 0; i < d.length; i++) {
  162. if (simpleregions.get(d[i]).isWithin(longitude, latitude)) {
  163. countsIn[polygons.get(d[i])]++;
  164. }
  165. }
  166. /* true iif within an odd number of regions for any polygon*/
  167. for (int i = 0; i < countsIn.length; i++) {
  168. if (countsIn[i] % 2 == 1) {
  169. return true;
  170. }
  171. }
  172. }
  173. }
  174. /* check for all SimpleRegions */
  175. for (int i = 0; i < simpleregions.size(); i++) {
  176. if (simpleregions.get(i).isWithin(longitude, latitude)) {
  177. countsIn[polygons.get(i)]++;
  178. }
  179. }
  180. /* true iif within an odd number of regions for any polygon*/
  181. for (int i = 0; i < countsIn.length; i++) {
  182. if (countsIn[i] % 2 == 1) {
  183. return true;
  184. }
  185. }
  186. return false;
  187. }
  188. /**
  189. * builds a grid (mask) to speed up isWithin.
  190. *
  191. * TODO: split out shapes with large numbers of points in GI_PARTIALLY_PRESENT grid cells.
  192. *
  193. * TODO: automatic(best) height/width specification
  194. *
  195. * @param width
  196. * @param height
  197. */
  198. public void useMask(int width, int height, int depthThreashold) {
  199. //calculate defaults for -1 inputs
  200. double[][] bb = getBoundingBox();
  201. int length = 0;
  202. for (SimpleRegion sr : simpleregions) {
  203. length += sr.getNumberOfPoints();
  204. }
  205. int w = (int) ((bb[1][0] - bb[0][0]) * 3);
  206. int h = (int) ((bb[1][1] - bb[0][1]) * 3);
  207. if (length > 5000) {
  208. w = 200;
  209. h = 200;
  210. }
  211. if (w > 200) {
  212. w = 200;
  213. }
  214. if (h > 200) {
  215. h = 200;
  216. }
  217. if (width == -1) {
  218. width = w;
  219. }
  220. if (height == -1) {
  221. height = h;
  222. }
  223. if (depthThreashold == -1) {
  224. depthThreashold = 100;
  225. }
  226. if (width < 3 || height < 3) {
  227. return;
  228. }
  229. int i, j;
  230. /* class variables assignment */
  231. mask_width = width;
  232. mask_height = height;
  233. mask_long_multiplier =
  234. mask_width / (double) (boundingbox_all[1][0] - boundingbox_all[0][0]);
  235. mask_lat_multiplier =
  236. mask_height / (double) (boundingbox_all[1][1] - boundingbox_all[0][1]);
  237. /* end result mask */
  238. mask = new byte[height][width];
  239. ArrayList<Integer>[][] md = null;
  240. if (simpleregions.size() > depthThreashold) {
  241. //use mask depth as well
  242. md = new ArrayList[height][width];
  243. }
  244. /* temp mask for current Polygon */
  245. byte[][] shapemask = new byte[height][width];
  246. /* temp mask for current SimpleRegion */
  247. byte[][] shapemaskregion = new byte[height][width];
  248. int k = 0;
  249. while (k < simpleregions.size()) {
  250. int p = k;
  251. for (; k < simpleregions.size()
  252. && (p == k || polygons.get(k - 1) == polygons.get(k)); k++) {
  253. SimpleRegion sr = simpleregions.get(k);
  254. sr.getOverlapGridCells(boundingbox_all[0][0], boundingbox_all[0][1], boundingbox_all[1][0], boundingbox_all[1][1], width, height, shapemaskregion);
  255. //shapemaskregion into shapemask
  256. for (i = 0; i < height; i++) {
  257. for (j = 0; j < width; j++) {
  258. if (shapemaskregion[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT
  259. || shapemask[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT) {
  260. shapemask[i][j] = SimpleRegion.GI_PARTIALLY_PRESENT; //partially inside
  261. if (md != null) {
  262. if (md[i][j] == null) {
  263. md[i][j] = new ArrayList<Integer>();
  264. }
  265. md[i][j].add(k);
  266. }
  267. } else if (shapemaskregion[i][j] == SimpleRegion.GI_FULLY_PRESENT) {
  268. if (shapemask[i][j] == SimpleRegion.GI_FULLY_PRESENT) {
  269. shapemask[i][j] = SimpleRegion.GI_ABSENCE; //completely inside
  270. } else {
  271. shapemask[i][j] = SimpleRegion.GI_FULLY_PRESENT;//completely outside (inside of a cutout region)
  272. }
  273. if (md != null) {
  274. if (md[i][j] == null) {
  275. md[i][j] = new ArrayList<Integer>();
  276. }
  277. md[i][j].add(k);
  278. }
  279. }
  280. /* reset shapemaskregion for next part */
  281. shapemaskregion[i][j] = 0;
  282. }
  283. }
  284. }
  285. //shapemask into mask
  286. for (i = 0; i < height; i++) {
  287. for (j = 0; j < width; j++) {
  288. if (shapemask[i][j] == SimpleRegion.GI_FULLY_PRESENT
  289. || mask[i][j] == SimpleRegion.GI_FULLY_PRESENT) {
  290. mask[i][j] = SimpleRegion.GI_FULLY_PRESENT; //partially inside
  291. } else if (shapemask[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT) {
  292. mask[i][j] = SimpleRegion.GI_PARTIALLY_PRESENT;
  293. }
  294. /* reset shapemask for next part */
  295. shapemask[i][j] = 0;
  296. }
  297. }
  298. }
  299. //maskDepth to int[]
  300. if (md != null) {
  301. maskDepth = new Object[md.length][md[0].length];
  302. for (i = 0; i < height; i++) {
  303. for (j = 0; j < width; j++) {
  304. if (md[i][j] != null && mask[i][j] == SimpleRegion.GI_PARTIALLY_PRESENT) {
  305. int[] d = new int[md[i][j].size()];
  306. for (k = 0; k < d.length; k++) {
  307. d[k] = md[i][j].get(k);
  308. }
  309. maskDepth[i][j] = d;
  310. }
  311. }
  312. }
  313. }
  314. }
  315. /**
  316. * determines overlap with a grid
  317. *
  318. * for type POLYGON
  319. * when <code>three_state_map</code> is not null populate it with one of:
  320. * GI_UNDEFINED
  321. * GI_PARTIALLY_PRESENT
  322. * GI_FULLY_PRESENT
  323. * GI_ABSENCE
  324. *
  325. * @param longitude1
  326. * @param latitude1
  327. * @param longitude2
  328. * @param latitude2
  329. * @param xres number of longitude segements as int
  330. * @param yres number of latitude segments as int
  331. * @return (x,y) as double [][2] for each grid cell at least partially falling
  332. * within the specified region of the specified resolution beginning at 0,0
  333. * for minimum longitude and latitude through to xres,yres for maximums *
  334. */
  335. @Override
  336. public int[][] getOverlapGridCells(double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
  337. int i, j;
  338. // if no threestate map exists, create one
  339. if (mask == null) {
  340. mask = new byte[height][width];
  341. three_state_map = mask;
  342. }
  343. byte[][] tmpMask = new byte[height][width];
  344. int k = 0;
  345. while (k < simpleregions.size()) {
  346. int p = k;
  347. for (; k < simpleregions.size()
  348. && (p == k || polygons.get(k - 1).equals(polygons.get(k))); k++) {
  349. SimpleRegion sr = simpleregions.get(k);
  350. sr.getOverlapGridCells_Acc(longitude1, latitude1, longitude2, latitude2, width, height, tmpMask);
  351. }
  352. fillAccMask(p, k - 1, longitude1, latitude1, longitude2, latitude2, width, height, tmpMask, true);
  353. //tmpMask into mask
  354. for (i = 0; i < height; i++) {
  355. for (j = 0; j < width; j++) {
  356. if (tmpMask[i][j] == 2 || mask[i][j] == 2) {
  357. mask[i][j] = 2;
  358. } else if (tmpMask[i][j] == 1) {
  359. mask[i][j] = 1;
  360. }
  361. /* reset shapemask for next part */
  362. tmpMask[i][j] = 0;
  363. }
  364. }
  365. }
  366. boolean cellsReturned = !noCellsReturned;
  367. if (cellsReturned) {
  368. int[][] data = new int[width * height][2];
  369. int p = 0;
  370. for (i = 0; i < height; i++) {
  371. for (j = 0; j < width; j++) {
  372. if (mask[i][j] != GI_UNDEFINED) { //undefined == absence
  373. data[p][0] = j;
  374. data[p][1] = i;
  375. p++;
  376. }
  377. }
  378. }
  379. data = java.util.Arrays.copyOf(data, p);
  380. }
  381. return null;
  382. }
  383. @Override
  384. public boolean isWithin_EPSG900913(double longitude, double latitude) {
  385. short[] countsIn = new short[polygons.get(polygons.size() - 1) + 1];
  386. /* check for all SimpleRegions */
  387. for (int i = 0; i < simpleregions.size(); i++) {
  388. if (simpleregions.get(i).isWithin_EPSG900913(longitude, latitude)) {
  389. countsIn[polygons.get(i)]++;
  390. }
  391. }
  392. /* true iif within an odd number of regions for any polygon*/
  393. for (int i = 0; i < countsIn.length; i++) {
  394. if (countsIn[i] % 2 == 1) {
  395. return true;
  396. }
  397. }
  398. return false;
  399. }
  400. @Override
  401. public int[][] getOverlapGridCells_EPSG900913(double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
  402. int i, j;
  403. int[][] output = null;
  404. byte[][] mask = three_state_map;
  405. // if no threestate map exists, create one
  406. if (mask == null) {
  407. mask = new byte[height][width];
  408. three_state_map = mask;
  409. }
  410. byte[][] tmpMask = new byte[height][width];
  411. int k = 0;
  412. while (k < simpleregions.size()) {
  413. int p = k;
  414. for (; k < simpleregions.size()
  415. && (p == k || polygons.get(k - 1).equals(polygons.get(k))); k++) {
  416. SimpleRegion sr = simpleregions.get(k);
  417. sr.getOverlapGridCells_Acc_EPSG900913(longitude1, latitude1, longitude2, latitude2, width, height, tmpMask);
  418. }
  419. fillAccMask_EPSG900913(k, p - 1, longitude1, latitude1, longitude2, latitude2, width, height, tmpMask, true);
  420. //tmpMask into mask
  421. for (i = 0; i < height; i++) {
  422. for (j = 0; j < width; j++) {
  423. if (tmpMask[i][j] == 2 || mask[i][j] == 2) {
  424. mask[i][j] = 2;
  425. } else if (tmpMask[i][j] == 1) {
  426. mask[i][j] = 1;
  427. }
  428. /* reset shapemask for next part */
  429. tmpMask[i][j] = 0;
  430. }
  431. }
  432. }
  433. boolean cellsReturned = !noCellsReturned;
  434. if (cellsReturned) {
  435. int[][] data = new int[width * height][2];
  436. int p = 0;
  437. for (i = 0; i < height; i++) {
  438. for (j = 0; j < width; j++) {
  439. if (mask[i][j] != GI_UNDEFINED) { //undefined == absence
  440. data[p][0] = j;
  441. data[p][1] = i;
  442. p++;
  443. }
  444. }
  445. }
  446. data = java.util.Arrays.copyOf(data, p);
  447. return data;
  448. }
  449. return null;
  450. }
  451. void addSet(ArrayList<SimpleRegion> simpleRegions) {
  452. int nextSetNumber = (polygons.size() > 0) ? polygons.get(polygons.size() - 1) + 1 : 0;
  453. for (int i = 0; i < simpleRegions.size(); i++) {
  454. addPolygon(simpleRegions.get(i));
  455. polygons.add(nextSetNumber);
  456. }
  457. }
  458. public int[][] fillAccMask(int startPolygon, int endPolygon, double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
  459. double divx = (longitude2 - longitude1) / width;
  460. double divy = (latitude2 - latitude1) / height;
  461. int i, j;
  462. //do raster check
  463. int[][] data = null;
  464. boolean cellsReturned = !noCellsReturned;
  465. if (cellsReturned) {
  466. data = new int[width * height][2];
  467. }
  468. int p = 0;
  469. for (j = 0; j < three_state_map[0].length; j++) {
  470. for (i = 0; i < three_state_map.length; i++) {
  471. if (three_state_map[i][j] == GI_PARTIALLY_PRESENT) {
  472. //if it is partially present, do nothing
  473. } else if ((j == 0 || three_state_map[i][j - 1] == GI_PARTIALLY_PRESENT)) {
  474. if (i > 0
  475. && (three_state_map[i - 1][j] == GI_FULLY_PRESENT
  476. || three_state_map[i - 1][j] == GI_ABSENCE)) {
  477. //use same as LHS
  478. three_state_map[i][j] = three_state_map[i - 1][j];
  479. } else {
  480. int count = 0;
  481. for (int k = startPolygon; k <= endPolygon; k++) {
  482. count += isWithin(j * divx + divx / 2 + longitude1, i * divy + divy / 2 + latitude1) ? 1 : 0;
  483. }
  484. if (count % 2 == 1) {
  485. //if the previous was partially present, test
  486. three_state_map[i][j] = GI_FULLY_PRESENT;
  487. } //else absent
  488. } //else absent
  489. } else {
  490. //if the previous was fully present, repeat
  491. //if the previous was absent, repeat
  492. three_state_map[i][j] = three_state_map[i][j - 1];
  493. }
  494. //apply to cells;
  495. if (cellsReturned && three_state_map[i][j] != GI_UNDEFINED) { //undefined == absence
  496. data[p][0] = j;
  497. data[p][1] = i;
  498. p++;
  499. }
  500. }
  501. }
  502. if (data != null) {
  503. data = java.util.Arrays.copyOf(data, p);
  504. }
  505. return data;
  506. }
  507. public int[][] fillAccMask_EPSG900913(int startPolygon, int endPolygon, double longitude1, double latitude1, double longitude2, double latitude2, int width, int height, byte[][] three_state_map, boolean noCellsReturned) {
  508. double divx = (longitude2 - longitude1) / width;
  509. double divy = (latitude2 - latitude1) / height;
  510. int i, j;
  511. //do raster check
  512. int[][] data = null;
  513. boolean cellsReturned = !noCellsReturned;
  514. if (cellsReturned) {
  515. data = new int[width * height][2];
  516. }
  517. int p = 0;
  518. for (j = 0; j < three_state_map[0].length; j++) {
  519. for (i = 0; i < three_state_map.length; i++) {
  520. if (three_state_map[i][j] == GI_PARTIALLY_PRESENT) {
  521. //if it is partially present, do nothing
  522. } else if ((j == 0 || three_state_map[i][j - 1] == GI_PARTIALLY_PRESENT)) {
  523. if (i > 0
  524. && (three_state_map[i - 1][j] == GI_FULLY_PRESENT
  525. || three_state_map[i - 1][j] == GI_ABSENCE)) {
  526. //use same as LHS
  527. three_state_map[i][j] = three_state_map[i - 1][j];
  528. } else {
  529. int count = 0;
  530. for (int k = startPolygon; k <= endPolygon; k++) {
  531. count += isWithin_EPSG900913(j * divx + divx / 2 + longitude1, i * divy + divy / 2 + latitude1) ? 1 : 0;
  532. }
  533. if (count % 2 == 1) {
  534. //if the previous was partially present, test
  535. three_state_map[i][j] = GI_FULLY_PRESENT;
  536. } //else absent
  537. } //else absent
  538. } else {
  539. //if the previous was fully present, repeat
  540. //if the previous was absent, repeat
  541. three_state_map[i][j] = three_state_map[i][j - 1];
  542. }
  543. //apply to cells;
  544. if (cellsReturned && three_state_map[i][j] != GI_UNDEFINED) { //undefined == absence
  545. data[p][0] = j;
  546. data[p][1] = i;
  547. p++;
  548. }
  549. }
  550. }
  551. if (data != null) {
  552. data = java.util.Arrays.copyOf(data, p);
  553. }
  554. return data;
  555. }
  556. }