/webportal/src/main/java/org/ala/spatial/data/Legend.java
Java | 472 lines | 247 code | 68 blank | 157 comment | 64 complexity | a86e894c91029212be3405eb9d6df828 MD5 | raw file
1/* 2 * To change this template, choose Tools | Templates 3 * and open the template in the editor. 4 */ 5package org.ala.spatial.data; 6 7import java.awt.image.BufferedImage; 8import java.io.File; 9import java.io.FileWriter; 10import java.io.Serializable; 11import javax.imageio.ImageIO; 12 13/** 14 * 15 * @author Adam 16 */ 17public abstract class Legend implements Serializable { 18 19 /* 20 * Colours are all set as transparent for no reason 21 * 22 * There are groups+1 colours 23 */ 24 final public static int[] colours = {0x00002DD0, 0x00005BA2, 0x00008C73, 0x0000B944, 0x0000E716, 0x00A0FF00, 0x00FFFF00, 0x00FFC814, 0x00FFA000, 0x00FF5B00, 0x00FF0000}; 25 26 /* 27 * for determining the records that are equal to the maximum value 28 */ 29 double[] cutoffs; 30 31 /* 32 * for determining the records that are equal to the minimum value 33 */ 34 double[] cutoffMins; 35 36 /* 37 * cutoffs.length may not match colours.length, this a translation 38 */ 39 double[] cutoffsColours; 40 /** 41 * number of group members by Unique Value 42 */ 43 int[] groupSizes; 44 /** 45 * number of group members by Area 46 */ 47 int[] groupSizesArea; 48 49 /* 50 * min/max values 51 */ 52 double min, max; 53 54 /* 55 * number of non-NaN values 56 */ 57 int numberOfRecords; 58 59 /* 60 * number of unique values 61 */ 62 int numberOfUniqueValues; 63 64 /* 65 * array position of last value 66 */ 67 int lastValue; 68 69 /* 70 * number of cut points 71 */ 72 int divisions; 73 74 /* 75 * number of NaN values from counts 76 */ 77 int countOfNaN; 78 79 /** 80 * generate the legend cutoff points. 81 * 82 * default number of cutoffs = 10 83 * 84 * @param d asc sorted double [] 85 */ 86 public void generate(double[] d) { 87 generate(d, 10); 88 } 89 90 /** 91 * generate the legend cutoff points. 92 * 93 * @param d asc sorted double [] 94 * @param divisions number of cut points 95 */ 96 abstract public void generate(double[] d, int divisions); 97 98 /** 99 * return nice name for the method that this class uses to generate the 100 * cutoff points 101 * 102 * @return name as String 103 */ 104 abstract public String getTypeName(); 105 106 /** 107 * some common values 108 * 109 * @param d as sorted double [] 110 * @param divisions number of cutpoints 111 */ 112 void init(double[] d, int divisions) { 113 this.divisions = divisions; 114 115 //NaN sorted last. 116 min = d[0]; 117 118 for (int i = 0; i < d.length; i++) { 119 if (!Double.isNaN(d[i])) { 120 numberOfRecords++; 121 122 if (i == 0 || d[i] != d[i - 1]) { 123 numberOfUniqueValues++; 124 } 125 } 126 } 127 128 lastValue = numberOfRecords; 129 if (numberOfRecords == 0) { 130 max = Double.NaN; 131 } else { 132 max = d[numberOfRecords - 1]; 133 } 134 135 cutoffsColours = null; 136 } 137 138 /** 139 * size is represented by number of unique values. 140 * 141 * @param d double [] sorted in ascending order 142 */ 143 void determineGroupSizes(double[] d) { 144 if (cutoffs == null) { 145 return; 146 } 147 148 //fix cutoffs 149 for (int i = 1; i < cutoffs.length; i++) { 150 if (cutoffs[i] < cutoffs[i - 1]) { 151 for (int j = i; j < cutoffs.length; j++) { 152 cutoffs[j] = cutoffs[i - 1]; 153 } 154 break; 155 } 156 } 157 158 groupSizes = new int[cutoffs.length]; 159 160 int cutoffPos = 0; 161 countOfNaN = 0; 162 for (int i = 0; i < d.length; i++) { 163 if (Double.isNaN(d[i])) { 164 countOfNaN++; 165 continue; 166 } else if (d[i] > cutoffs[cutoffPos]) { 167 while (d[i] > cutoffs[cutoffPos]) { 168 cutoffPos++; 169 } 170 } 171 if (i == 0 || d[i - 1] != d[i]) { 172 groupSizes[cutoffPos]++; //max cutoff == max value 173 } 174 } 175 176 groupSizesArea = determineGroupSizesArea(d); 177 } 178 179 /** 180 * range better on features 181 * 182 * lower is better 183 * 184 * @param d 185 * @return 186 */ 187 public double evaluateStdDev(double[] d) { 188 if (Double.isNaN(max)) { 189 return Double.NaN; 190 } 191 determineGroupSizes(d); 192 193 double stdev = 0; 194 double mean = numberOfUniqueValues / (double) groupSizes.length; 195 for (int i = 0; i < groupSizes.length; i++) { 196 stdev += Math.pow(groupSizes[i] - mean, 2) / (double) groupSizes.length; 197 } 198 199 stdev = (double) Math.sqrt(stdev); 200 201 return stdev; 202 } 203 204 /** 205 * size is represented by number of unique values. 206 * 207 * @param d double [] sorted in ascending order 208 */ 209 int[] determineGroupSizesArea(double[] d) { 210 if (cutoffs == null) { 211 return null; 212 } 213 214 int[] grpSizes = new int[cutoffs.length]; 215 216 cutoffMins = new double[cutoffs.length]; 217 cutoffMins[0] = min; 218 219 int cutoffPos = 0; 220 for (int i = 0; i < d.length; i++) { 221 if (Double.isNaN(d[i])) { 222 continue; 223 } 224 while (d[i] > cutoffs[cutoffPos]) { 225 while (d[i] > cutoffs[cutoffPos]) { 226 cutoffPos++; 227 cutoffMins[cutoffPos] = d[i]; 228 } 229 } 230 grpSizes[cutoffPos]++; 231 } 232 233 return grpSizes; 234 } 235 236 /** 237 * range better on area 238 * 239 * lower is better 240 * 241 * @param d 242 * @return 243 */ 244 public double evaluateStdDevArea(double[] d) { 245 if (Double.isNaN(max)) { 246 return Double.NaN; 247 } 248 249 int[] grpSizes = determineGroupSizesArea(d); 250 int sum = 0; 251 for (int i = 0; i < grpSizes.length; i++) { 252 sum += grpSizes[i]; 253 } 254 255 double stdev = 0; 256 double mean = sum / (double) grpSizes.length; 257 for (int i = 0; i < grpSizes.length; i++) { 258 stdev += Math.pow(grpSizes[i] - mean, 2) / (double) grpSizes.length; 259 } 260 261 stdev = (double) Math.sqrt(stdev); 262 263 return stdev; 264 } 265 266 /** 267 * save to a file as a type (filename extension). 268 * 269 * Option to scale down image size by discarding values/pixels 270 * 271 * @param d double [] of raster data to have legend applied 272 * @param width row width 273 * @param filename output filename 274 */ 275 void exportImage(double[] d, int width, String filename, int scaleDownBy) { 276 try { 277 /* make image */ 278 BufferedImage image = new BufferedImage(width / scaleDownBy, d.length / width / scaleDownBy, 279 BufferedImage.TYPE_INT_BGR); 280 281 /* get bytes structure */ 282 int[] image_bytes = image.getRGB(0, 0, image.getWidth(), image.getHeight(), 283 null, 0, image.getWidth()); 284 285 //fill 286 for (int i = 0; i < image_bytes.length; i++) { 287 int x = i % (width / scaleDownBy); 288 int y = i / (width / scaleDownBy); 289 290 int dataX = x * scaleDownBy; 291 int dataY = y * scaleDownBy; 292 293 int dataI = dataX + dataY * width; 294 295 image_bytes[i] = getColour(d[dataI]); 296 } 297 298 /* write back image bytes */ 299 image.setRGB(0, 0, image.getWidth(), image.getHeight(), 300 image_bytes, 0, image.getWidth()); 301 302 /* write image */ 303 String extension = filename.substring(filename.length() - 3); 304 ImageIO.write(image, extension, new File(filename)); 305 306 } catch (Exception e) { 307 e.printStackTrace(); 308 } 309 } 310 311 /** 312 * Only correct when Legend is created with 10 divisions. 313 * 314 * @param d asc sorted double [] 315 * @return colour of d after applying cutoff's. 316 */ 317 public int getColour(double d) { 318 if (Double.isNaN(d)) { 319 return 0x00000000; //black 320 } 321 int pos = java.util.Arrays.binarySearch(cutoffs, d); 322 if (pos < 0) { 323 pos = (pos * -1) - 1; 324 } else { 325 //get first instance of this cutoff value 326 while (pos > 0 && cutoffs[pos] == cutoffs[pos - 1]) { 327 pos--; 328 } 329 } 330 if (divisions != 10) { 331 //TODO: fix for mismatch with colours.length 332 } 333 if (pos >= cutoffs.length) { 334 return 0x00000000; 335 } else { 336 double upper = cutoffs[pos]; 337 int upperPos = pos + 1; 338 double lower; 339 int lowerPos; 340 if (pos == 0) { 341 lower = min; 342 lowerPos = 0; 343 } else { 344 lower = cutoffs[pos - 1]; 345 lowerPos = pos; 346 } 347 348 //translate value to 0-1 position between the colours 349 if (upper == lower) { 350 while (pos > 0 && cutoffs[pos - 1] == lower) { 351 pos--; 352 } 353 return colours[pos]; 354 } 355 double v = (d - lower) / (upper - lower); 356 double vt = 1 - v; 357 358 359 //there are groups+1 colours 360 int red = (int) ((colours[lowerPos] & 0x00FF0000) * vt + (colours[upperPos] & 0x00FF0000) * v); 361 int green = (int) ((colours[lowerPos] & 0x0000FF00) * vt + (colours[upperPos] & 0x0000FF00) * v); 362 int blue = (int) ((colours[lowerPos] & 0x00000FF) * vt + (colours[upperPos] & 0x000000FF) * v); 363 364 return (red & 0x00FF0000) | (green & 0x0000FF00) | (blue & 0x000000FF) | 0xFF000000; 365 } 366 } 367 368 /** 369 * colourize input between provided ranges. 370 * 371 * @param d value to colourize as double 372 * @param min minimum of range as double 373 * @param max maximum of range as double 374 * @return colour of d scaled between min and max as int ARGB with A == 0xFF. 375 * Defaults to black. 376 */ 377 public static int getColour(double d, double min, double max) { 378 if (Double.isNaN(d) || d < min || d > max) { 379 return 0x00000000; 380 } 381 double range = max - min; 382 double a = (d - min) / range; 383 384 //10 colour steps 385 int pos = (int) (a); //fit 0 to 10 386 if (pos == 10) { 387 pos--; 388 } 389 double lower = (pos / 10.0) * range + min; 390 double upper = ((pos + 1) / 10.0) * range + min; 391 392 //translate value to 0-1 position between the colours 393 double v = (d - lower) / (upper - lower); 394 double vt = 1 - v; 395 396 //there are groups+1 colours 397 int red = (int) ((colours[pos] & 0x00FF0000) * vt + (colours[pos + 1] & 0x00FF0000) * v); 398 int green = (int) ((colours[pos] & 0x0000FF00) * vt + (colours[pos + 1] & 0x0000FF00) * v); 399 int blue = (int) ((colours[pos] & 0x00000FF) * vt + (colours[pos + 1] & 0x000000FF) * v); 400 401 return (red & 0x00FF0000) | (green & 0x0000FF00) | (blue & 0x000000FF) | 0xFF000000; 402 } 403 404 public static int getLinearColour(double d, double min, double max, int startColour, int endColour) { 405 //translate value to 0-1 position between the colours 406 double v = (d - min) / (max - min); 407 double vt = 1 - v; 408 409 int red = (int) ((startColour & 0x00FF0000) * vt + (endColour & 0x00FF0000) * v); 410 int green = (int) ((startColour & 0x0000FF00) * vt + (endColour & 0x0000FF00) * v); 411 int blue = (int) ((startColour & 0x00000FF) * vt + (endColour & 0x000000FF) * v); 412 413 return (red & 0x00FF0000) | (green & 0x0000FF00) | (blue & 0x000000FF) | 0xFF000000; 414 } 415 416 /** 417 * get cutoff values as String 418 * 419 * includes group sizes if calculated 420 * 421 * @return String of cutoff values 422 */ 423 public String getCutoffs() { 424 StringBuffer sb = new StringBuffer(); 425 //System.out.println(getTypeName()); 426 for (int i = 0; i < cutoffs.length; i++) { 427 if (groupSizes != null && groupSizesArea != null) { 428 sb.append(String.valueOf(cutoffs[i])).append("\t").append(String.valueOf(groupSizes[i])).append("\t").append(String.valueOf(groupSizesArea[i])).append("\n"); 429 } else { 430 sb.append(String.valueOf(cutoffs[i])).append("\n"); 431 } 432 } 433 return sb.toString(); 434 } 435 436 /** 437 * write the cutoff points to a file as text 438 * 439 * @param filename 440 */ 441 void exportLegend(String filename) { 442 try { 443 FileWriter fw = new FileWriter(filename); 444 fw.append(getCutoffs()); 445 fw.close(); 446 } catch (Exception e) { 447 e.printStackTrace(); 448 } 449 450 } 451 452 /** 453 * get cutoff's 454 * @return cutoff upper segment values as double[] (missing min value) 455 */ 456 public double[] getCutoffdoubles() { 457 return cutoffs; 458 } 459 460 /** 461 * 462 * @return double[] of [min, max] 463 */ 464 public double[] getMinMax() { 465 double[] f = {min, max}; 466 return f; 467 } 468 469 public double[] getCutoffMindoubles() { 470 return cutoffMins; 471 } 472}