PageRenderTime 41ms CodeModel.GetById 17ms app.highlight 19ms RepoModel.GetById 2ms app.codeStats 0ms

/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 */
  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}