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