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<br/> 074 * 2) Project the GARP model onto geography (map generated by GarpProjection actor)<br/> 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.<br/> 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.<br/> 081 * 5) Then, select those GARP runs that show omission below a certain omission threshold, say 5 or 10%.<br/> 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.<br/> 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.<br/> 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}