/webportal/src/main/java/org/ala/spatial/data/Legend.java

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