001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.ecoinformatics.seek.gis.java_gis;
031
032import java.awt.geom.Point2D;
033import java.io.BufferedReader;
034import java.io.File;
035import java.io.FileReader;
036import java.util.StringTokenizer;
037import java.util.Vector;
038
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.lib.Transformer;
041import ptolemy.data.DoubleToken;
042import ptolemy.data.StringToken;
043import ptolemy.data.type.BaseType;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047
048/**
049 * <p>
050 * This actor examinesof the values in individual pixels in an ascii grid file
051 * and summarizes those values.
052 * </p>
053 * 
054 * <p>
055 * There are three inputs; 'input'is an ASCI grid (output of GARP);
056 * 'pointFileName' is the file of testing locations (long, lat) used to evaluate
057 * the prediction; and 'ruleSetFileName is the ruleSetFile which is needed later
058 * to reproduce the predicted distribution.<br/>
059 * The output is a string containing the omission, commission, and the
060 * ruleSetFileName, separated by tabs. These should be saved (in a File?) for
061 * determination of the 'best' result.<br/>
062 * </p>
063 * <p>
064 * Ricardo Periera, provided the following recipe for calculating omission and
065 * commision in an e-mail to Dan Higgins, 10/5/2004<br/>
066 * </p>
067 * <p>
068 * However, those error statistics (omission and commission) could be calculated
069 * outside GARP code. Here is the recipe:<br/>
070 * </p>
071 * 
072 * <pre>
073 * 1) Get a set of presence data points (species occurrences - x, y coordinates) to test&lt;br/&gt;
074 * 2) Project the GARP model onto geography (map generated by GarpProjection actor)&lt;br/&gt;
075 * 3) Overlay the presence points from item #1 onto the map generated on #2. The percentage of those 
076 *  points that fall in a pixed not predicted present is your OMISSION. Say, out of 100 points, only 
077 *  45 fall on white pixels, the other 55 fall on black ones, your omission is 55% or 0.55.&lt;br/&gt;
078 * 4) Commission, when we don't have real absence points (our case) is the proportion of area predicted 
079 *  present with regard to the total area of interest, not counting masked pixels. So if 40% of the area 
080 *  is predicted present, your commission error is 40% or 0.40.&lt;br/&gt;
081 * 5) Then, select those GARP runs that show omission below a certain omission threshold, say 5 or 10%.&lt;br/&gt;
082 * 6) From those runs selected in #5, sort them by commission error, and then get the 50% of the models 
083 *  that are around the median value for commission. If you got, say, 20 models in item #5, now you have 
084 *  10 models that make up your best subset of models.&lt;br/&gt;
085 * 7) Sum up the maps for the best subset of models in item #6, that is your final prediction map for your 
086 *  species.&lt;br/&gt;
087 * </pre>
088 * 
089 * @author Dan Higgins NCEAS UC Santa Barbara
090 */
091public class GARPSummary extends Transformer {
092
093        // input ports
094        /**
095         *'pointFileName' is the file of testing locations (long, lat) used to
096         * evaluate the prediction
097         */
098        public TypedIOPort pointFileName;
099        /**
100         * 'ruleSetFileName is the ruleSetFile which is needed later to reproduce
101         * the predicted distribution.
102         */
103        public TypedIOPort ruleSetFileName;
104
105        /**
106         * The omission value
107         */
108        public TypedIOPort omissionValue;
109        /**
110         * The commission value
111         */
112        public TypedIOPort commissionValue;
113        /**
114         * output ruleset file name
115         */
116        public TypedIOPort outputRuleSetFileName;
117
118        Vector pointList; // vector of points as Point2D.Double objects
119
120        private double hit_value = 1.0E0; // indicated occurence in GARP output
121        private double miss_distance = 1.0e-3;
122        private Grid inputGrid;
123
124        /**
125         * constructor
126         * 
127         *@param container
128         *            The container.
129         *@param name
130         *            The name of this actor.
131         *@exception IllegalActionException
132         *                If the actor cannot be contained by the proposed
133         *                container.
134         *@exception NameDuplicationException
135         *                If the container already has an actor with this name.
136         */
137        public GARPSummary(CompositeEntity container, String name)
138                        throws NameDuplicationException, IllegalActionException {
139                super(container, name);
140                pointFileName = new TypedIOPort(this, "pointFileName", true, false);
141                pointFileName.setTypeEquals(BaseType.STRING);
142                ruleSetFileName = new TypedIOPort(this, "ruleSetFileName", true, false);
143                ruleSetFileName.setTypeEquals(BaseType.STRING);
144                input.setTypeEquals(BaseType.STRING);
145                output.setTypeEquals(BaseType.STRING);
146
147                omissionValue = new TypedIOPort(this, "omissionValue", false, true);
148                omissionValue.setTypeEquals(BaseType.DOUBLE);
149                commissionValue = new TypedIOPort(this, "commissionValue", false, true);
150                commissionValue.setTypeEquals(BaseType.DOUBLE);
151                outputRuleSetFileName = new TypedIOPort(this, "outputRuleSetFileName",
152                                false, true);
153                outputRuleSetFileName.setTypeEquals(BaseType.STRING);
154
155        }
156
157        /**
158         * 
159         *@exception IllegalActionException
160         *                If there is no director.
161         */
162        public void fire() throws IllegalActionException {
163                String ruleSetNameStr = "";
164                super.fire();
165
166                if (pointFileName.getWidth() > 0) { // has a connection
167                        if (pointFileName.hasToken(0)) { // has a token
168                                StringToken inputFileToken = (StringToken) pointFileName.get(0);
169                                String inputFileNameStr = inputFileToken.stringValue();
170                                File pointFile = new File(inputFileToken.stringValue());
171                                FileReader inReader = null;
172                                BufferedReader bufReader = null;
173                                try {
174                                        inReader = new FileReader(pointFile);
175                                        bufReader = new BufferedReader(inReader);
176                                        pointList = getPoints(bufReader);
177                                } catch (Exception ee) {
178                                        System.out.println("Exception reading points!");
179                                }
180                        }
181                }
182
183                if (ruleSetFileName.getWidth() > 0) { // has a connection
184                        if (ruleSetFileName.hasToken(0)) { // has a token
185                                StringToken ruleSetFileToken = (StringToken) ruleSetFileName
186                                                .get(0);
187                                ruleSetNameStr = ruleSetFileToken.stringValue();
188                                // on Windows machines, one sometimes get a file path with a
189                                // mixture of '/' and '\' symbols
190                                // convert all '\' to '/'
191                                ruleSetNameStr = ruleSetNameStr.replace('\\', '/');
192                        }
193                }
194
195                try {
196                        if (input.getWidth() > 0) { // has a connection
197                                if (input.hasToken(0)) { // has a token
198                                        String ascfilename = ((StringToken) input.get(0))
199                                                        .stringValue();
200
201                                        File file = new File(ascfilename);
202
203                                        if (file.exists()) {
204                                                inputGrid = new Grid(file);
205                                                System.out.println("nrows: " + inputGrid.nrows);
206                                                System.out.println("ncols: " + inputGrid.ncols);
207                                                System.out.println("delx: " + inputGrid.delx);
208                                                System.out.println("dely: " + inputGrid.dely);
209
210                                                double fnm = inputGrid.getFractionPixelsWithValue(
211                                                                hit_value, miss_distance);
212                                                java.lang.Double commissionD = new java.lang.Double(fnm);
213                                                String commission = commissionD.toString();
214
215                                                int hitCnt = 0;
216                                                if (pointList != null) {
217                                                        for (int i = 0; i < pointList.size(); i++) {
218                                                                double x2 = ((Point2D) pointList.elementAt(i))
219                                                                                .getX();
220                                                                double y2 = ((Point2D) pointList.elementAt(i))
221                                                                                .getY();
222                                                                double val = inputGrid.interpValue(x2, y2,
223                                                                                Grid.NEAREST_NEIGHBOR);
224                                                                if ((Math.abs(val - hit_value)) < miss_distance) {
225                                                                        hitCnt++;
226                                                                } else {
227                                                                        System.out.println("no hit at i = " + i
228                                                                                        + " --x=" + x2 + " --y=" + y2);
229                                                                }
230                                                        }
231                                                }
232                                                java.lang.Double omissionD = new java.lang.Double(
233                                                                (double) (pointList.size() - hitCnt)
234                                                                                / pointList.size());
235                                                String omission = omissionD.toString();
236                                                String outstring = omission + "\t" + commission + "\t"
237                                                                + ruleSetNameStr;
238                                                output.broadcast(new StringToken(outstring));
239
240                                                omissionValue.broadcast(new DoubleToken(omissionD
241                                                                .doubleValue()));
242                                                commissionValue.broadcast(new DoubleToken(commissionD
243                                                                .doubleValue()));
244                                                outputRuleSetFileName.broadcast(new StringToken(
245                                                                ruleSetNameStr));
246                                        } else {
247                                                throw new IllegalActionException("Input file "
248                                                                + ascfilename + " does not exist.");
249                                        }
250
251                                }
252                        }
253                } catch (Exception eee) {
254                        throw new IllegalActionException("Problem Reading File");
255                }
256        }
257
258        /**
259         * Post fire the actor. Return false to indicate that the process has
260         * finished. If it returns true, the process will continue indefinitely.
261         * 
262         *       */
263        public boolean postfire() throws IllegalActionException {
264                inputGrid.delete(); // remove potentially large data storage associated
265                                                        // with the inputGrid
266                return super.postfire();
267        }
268
269        /**
270         * Pre fire the actor. Calls the super class's prefire in case something is
271         * set there.
272         * 
273         *       *@exception IllegalActionException
274         */
275        public boolean prefire() throws IllegalActionException {
276                return super.prefire();
277        }
278
279        private Vector getPoints(BufferedReader br) {
280                String cachedLine = "";
281                double xval = 0.0;
282                double yval = 0.0;
283                Vector pts = new Vector();
284                // unsure exactly how many point lines there are
285                // but each line should have only two tokens
286                boolean eoh = false;
287                while (!eoh) {
288                        try {
289                                cachedLine = br.readLine();
290                                // System.out.println("cachedLine: "+cachedLine);
291                        } catch (Exception w) {
292                                System.out.println("error reading next line in points file!");
293                                eoh = true;
294                        }
295                        if (cachedLine == null)
296                                return pts;
297                        if (cachedLine.trim().length() > 0) {
298                                StringTokenizer st = new StringTokenizer(cachedLine);
299                                int cnt = st.countTokens(); // should be only 2
300                                if (cnt != 2)
301                                        eoh = true;
302                                String firstToken = st.nextToken().trim();
303                                String secondToken = st.nextToken().trim();
304                                try {
305                                        xval = java.lang.Double.parseDouble(firstToken);
306                                        yval = java.lang.Double.parseDouble(secondToken);
307                                } catch (Exception e) {
308                                        eoh = true;
309                                }
310                                if (!eoh) {
311                                        java.awt.geom.Point2D.Double pt2D = new java.awt.geom.Point2D.Double(
312                                                        xval, yval);
313                                        pts.addElement(pt2D);
314                                }
315                        }
316                }
317                return pts;
318        }
319
320        private Point2D findFirstPoint(Vector pts) {
321                double minx;
322                Point2D fp = (Point2D) pts.elementAt(0);
323                minx = fp.getX();
324                for (int i = 1; i < pts.size(); i++) {
325                        double tempx = ((Point2D) pts.elementAt(i)).getX();
326                        // System.out.println("i: "+i+"    tempx: "+tempx+
327                        // "    minx: "+minx);
328                        if (tempx < minx) {
329                                fp = (Point2D) pts.elementAt(i);
330                                minx = tempx;
331                        }
332                }
333                // System.out.println("MinX: "+fp.getX()+"   MinY: "+fp.getY());
334                return fp;
335        }
336
337}