001/*
002 * Copyright (c) 2003-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.io.File;
033
034import ptolemy.actor.TypedAtomicActor;
035import ptolemy.actor.TypedIOPort;
036import ptolemy.data.BooleanToken;
037import ptolemy.data.StringToken;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.expr.StringParameter;
040import ptolemy.data.type.BaseType;
041import ptolemy.kernel.CompositeEntity;
042import ptolemy.kernel.util.IllegalActionException;
043import ptolemy.kernel.util.NameDuplicationException;
044import util.PersistentVector;
045
046/**
047 * <p>
048 * <b>Name:</b> MergeGrids.java<br/>
049 * </p>
050 * <p>
051 * <b>Purpose:</b> The purpose of this actor is to 'merge' two ASC grids. The
052 * precise meaning of 'merge' will depend on the 'merge' operator. One example
053 * is the combination of 2 grids into a new grid whose extent is a rectangle
054 * that includes both input bounding box retangles, averageing values from both
055 * inputs. Simple math operations (add, subtract) are other examples.<br/>
056 * 
057 * Order of the input grids may be significant( e.g for subtraction). Extent of
058 * the output will always include the combined extent of the inputs, but the
059 * cell size will match that of the first grid.
060 * </p>
061 * 
062 * @author : Dan Higgins NCEAS UC Santa Barbara
063 * 
064 */
065
066public class MergeGrids extends TypedAtomicActor {
067        /**
068         * This parameter describes the type of merge to be executed. Choices
069         * include" AVERAGE, ADD, SUBTRACT, MASK, NOT_MASK<br/>
070         * MASK - grid2 missing values will mask correponding points in grid1<br/>
071         * NOT_MASK - grid2 NOT-missing values will mask correponding points in
072         * grid1
073         */
074        public StringParameter mergeOperation;
075        int mergeOp = 0;
076        // input ports
077        /**
078         * The first grid file (*.asc format) to be merged
079         */
080        public TypedIOPort grid1FileName = new TypedIOPort(this, "grid1FileName",
081                        true, false);
082        /**
083         * The second grid file (*.asc format) to be merged
084         */
085        public TypedIOPort grid2FileName = new TypedIOPort(this, "grid2FileName",
086                        true, false);
087        /**
088         * The file name to be given to the result
089         */
090        public TypedIOPort mergedGridFileName = new TypedIOPort(this,
091                        "mergedGridFileName", true, false);
092
093        /**
094         * The resulting merged grid filename.
095         */
096        public TypedIOPort mergedGridFileResult = new TypedIOPort(this,
097                        "mergedGridFileResult", false, true);
098
099        /**
100         * Boolean setting to determine whether or not to use disk for storing grid
101         * data rather than putting all data in RAM arrays
102         */
103        public Parameter useDisk;
104
105        private Grid grid1;
106        private Grid grid2;
107        private Grid mergedGrid;
108
109        private boolean useDiskValue = true;
110
111        private static final int NEAREST_NEIGHBOR = 0;
112        private static final int INVERSE_DISTANCE = 1;
113
114        // merge operations
115        private static final int AVERAGE = 0;
116        private static final int ADD = 1;
117        private static final int SUBTRACT = 2;
118        private static final int MASK = 3; // grid2 missing values will mask
119                                                                                // correponding points in grid1
120        private static final int NOT_MASK = 4; // grid2 NOT-missing values will mask
121                                                                                        // correponding points in grid1
122
123        public MergeGrids(CompositeEntity container, String name)
124                        throws NameDuplicationException, IllegalActionException {
125                super(container, name);
126
127                mergeOperation = new StringParameter(this, "mergeOperation");
128                mergeOperation.setExpression("Average");
129                mergeOperation.addChoice("Average");
130                mergeOperation.addChoice("Add");
131                mergeOperation.addChoice("Subtract");
132                mergeOperation.addChoice("Mask");
133                mergeOperation.addChoice("(NOT)Mask");
134
135                grid1FileName.setTypeEquals(BaseType.STRING);
136                grid2FileName.setTypeEquals(BaseType.STRING);
137                mergedGridFileResult.setTypeEquals(BaseType.STRING);
138                mergedGridFileResult.setTypeEquals(BaseType.STRING);
139
140                useDisk = new Parameter(this, "useDisk");
141                useDisk.setDisplayName("Use disk storage (for large grids)");
142                useDisk.setTypeEquals(BaseType.BOOLEAN);
143                useDisk.setToken(BooleanToken.TRUE);
144        }
145
146        /**
147   *
148   */
149        public void initialize() throws IllegalActionException {
150
151        }
152
153        /**
154   *
155   */
156        public boolean prefire() throws IllegalActionException {
157                return super.prefire();
158        }
159
160        /**
161   *
162   */
163        public void fire() throws IllegalActionException {
164                super.fire();
165                useDiskValue = ((BooleanToken) useDisk.getToken()).booleanValue();
166
167                String temp = mergeOperation.stringValue();
168                if (temp.equals("Average")) {
169                        mergeOp = AVERAGE;
170                } else if (temp.equals("Add")) {
171                        mergeOp = ADD;
172                } else if (temp.equals("Subtract")) {
173                        mergeOp = SUBTRACT;
174                } else if (temp.equals("Mask")) {
175                        mergeOp = MASK;
176                } else if (temp.equals("(NOT)Mask")) {
177                        mergeOp = NOT_MASK;
178                }
179
180                StringToken grid1FileToken = (StringToken) grid1FileName.get(0);
181                String grid1FileNameStr = grid1FileToken.stringValue();
182                StringToken grid2FileToken = (StringToken) grid2FileName.get(0);
183                String grid2FileNameStr = grid2FileToken.stringValue();
184
185                File grid1File = new File(grid1FileNameStr);
186                File grid2File = new File(grid2FileNameStr);
187                grid1 = new Grid(grid1File, !useDiskValue);
188                grid2 = new Grid(grid2File, !useDiskValue);
189
190                double minx = grid1.xllcorner;
191                if (grid2.xllcorner < minx)
192                        minx = grid2.xllcorner;
193                double miny = grid1.yllcorner;
194                if (grid2.yllcorner < miny)
195                        miny = grid2.yllcorner;
196                double maxx = grid1.xllcorner + grid1.ncols * grid1.delx;
197                if ((grid2.xllcorner + grid2.ncols * grid2.delx) > maxx)
198                        maxx = grid2.xllcorner + grid2.ncols * grid2.delx;
199                double maxy = grid1.yllcorner + grid1.nrows * grid1.dely;
200                if ((grid2.yllcorner + grid2.nrows * grid2.dely) > maxy)
201                        maxy = grid2.yllcorner + grid2.nrows * grid2.dely;
202                ;
203                double new_cs = grid1.delx; // remember, delx and dely are equal!
204                int new_ncols = (int) ((maxx - minx) / new_cs);
205                int new_nrows = (int) ((maxy - miny) / new_cs);
206
207                mergedGrid = new Grid(new_ncols, new_nrows, new_cs, new_cs, minx, miny);
208                // new merged grid has now been created but no storage or values alloted
209                merge(NEAREST_NEIGHBOR, mergeOp);
210
211                StringToken outputFileToken = (StringToken) mergedGridFileName.get(0);
212                String outFileStr = outputFileToken.stringValue();
213                mergedGrid.createAsc(outFileStr);
214                mergedGridFileResult.broadcast(new StringToken(outFileStr));
215        }
216
217        /**
218         * Post fire the actor. Return false to indicate that the process has
219         * finished. If it returns true, the process will continue indefinitely.
220         * 
221         *       */
222        public boolean postfire() throws IllegalActionException {
223                grid1.delete(); // remove potentially large data storage associated with
224                                                // grid1
225                grid2.delete(); // remove potentially large data storage associated with
226                                                // grid2
227                mergedGrid.delete(); // remove potentially large data storage associated
228                                                                // with mergedGrid
229                return super.postfire();
230        }
231
232        private void merge(int scalingAlgorithm, int mergeOperation) {
233                int nr = mergedGrid.nrows;
234                int nc = mergedGrid.ncols;
235                double ymin = mergedGrid.yllcorner;
236                double xmin = mergedGrid.xllcorner;
237                double dx = mergedGrid.delx;
238                double dy = mergedGrid.dely;
239                if (!useDiskValue) {
240                        double[][] newDataArray = new double[nc][nr];
241                        mergedGrid.dataArray = newDataArray;
242                        for (int j = 0; j < nr; j++) {
243                                double yloc = ymin + nr * dy - j * dy;
244                                for (int i = 0; i < nc; i++) {
245                                        double xloc = xmin + i * dx;
246                                        double val1 = grid1.interpValue(xloc, yloc,
247                                                        scalingAlgorithm);
248                                        double val2 = grid2.interpValue(xloc, yloc,
249                                                        scalingAlgorithm);
250                                        double val = getMergedValue(val1, val2, mergeOperation);
251                                        newDataArray[i][j] = val;
252                                }
253                        }
254                } else { // using PersistentVector for data storage
255                        mergedGrid.pv = new PersistentVector();
256                        mergedGrid.pv.setFirstRow(6);
257                        mergedGrid.pv.setFieldDelimiter("#x20");
258                        String[] rowvals = new String[nc];
259                        for (int j = 0; j < nr; j++) {
260                                double yloc = ymin + nr * dy - j * dy;
261                                for (int i = 0; i < nc; i++) {
262                                        double xloc = xmin + i * dx;
263                                        double val1 = grid1.interpValue(xloc, yloc,
264                                                        scalingAlgorithm);
265                                        double val2 = grid2.interpValue(xloc, yloc,
266                                                        scalingAlgorithm);
267                                        double val = getMergedValue(val1, val2, mergeOperation);
268
269                                        String valStr;
270                                        if (val > 1.0E100) {
271                                                valStr = Grid.NODATA_value_String;
272                                        } else {
273                                                valStr = (new Double(val)).toString();
274                                        }
275
276                                        rowvals[i] = valStr;
277                                }
278                                mergedGrid.pv.addElement(rowvals);
279                                rowvals = new String[nc]; // needed to make sure new object
280                                                                                        // added to pv
281                        }
282                }
283        }
284
285        private double getMergedValue(double val1, double val2, int mergeOperation) {
286
287                if (mergeOperation == ADD) {
288                        // if either grid point is a missing value, return a missing value
289                        if ((val1 > 1.0e100) || (val2 > 1.0e100)) {
290                                return 1.0e101;
291                        } else {
292                                return (val1 + val2);
293                        }
294                } else if (mergeOperation == SUBTRACT) {
295                        // if either grid point is a missing value, return a missing value
296                        if ((val1 > 1.0e100) || (val2 > 1.0e100)) {
297                                return 1.0e101;
298                        } else {
299                                return (val1 - val2);
300                        }
301                } else if (mergeOperation == MASK) {
302                        if (val2 > 1.0e100) {
303                                return 1.0e101;
304                        } else {
305                                return val1;
306                        }
307                } else if (mergeOperation == NOT_MASK) {
308                        if (val2 < 1.0e100) {
309                                return 1.0e101;
310                        } else {
311                                return val1;
312                        }
313                } else { // 'AVERAGE' operation
314                        if ((val1 > 1.0e100) && (val2 > 1.0e100)) {
315                                return 1.0e101;
316                        } else if (val1 > 1.0e100) {
317                                return val2;
318                        } else if (val2 > 1.0e100) {
319                                return val1;
320                        } else {
321                                return ((val1 + val2) / 2.0);
322                        }
323                }
324        }
325}