001/*
002 * Copyright (c) 2015 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-12-23 23:08:48 +0000 (Wed, 23 Dec 2015) $' 
007 * '$Revision: 34417 $'
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 */
029package org.kepler.gis.actor.bbox;
030
031import java.util.ArrayList;
032import java.util.List;
033
034import org.geotools.geometry.jts.ReferencedEnvelope;
035import org.kepler.gis.data.BoundingBoxToken;
036import org.opengis.referencing.crs.CoordinateReferenceSystem;
037
038import ptolemy.actor.TypedAtomicActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.parameters.PortParameter;
041import ptolemy.data.ArrayToken;
042import ptolemy.data.BooleanToken;
043import ptolemy.data.DoubleToken;
044import ptolemy.data.RecordToken;
045import ptolemy.data.StringToken;
046import ptolemy.data.Token;
047import ptolemy.data.expr.Parameter;
048import ptolemy.data.type.ArrayType;
049import ptolemy.data.type.BaseType;
050import ptolemy.data.type.RecordType;
051import ptolemy.data.type.Type;
052import ptolemy.kernel.CompositeEntity;
053import ptolemy.kernel.util.Attribute;
054import ptolemy.kernel.util.IllegalActionException;
055import ptolemy.kernel.util.NameDuplicationException;
056import ptolemy.kernel.util.SingletonAttribute;
057
058/** An actor that covers a bounding box with non-overlapping
059 *  regions each whose side is less than a maximum length.
060 *  
061 *  @author Daniel Crawl
062 *  @version $Id: CoverBoundingBox.java 34417 2015-12-23 23:08:48Z crawl $
063 */
064public class CoverBoundingBox extends TypedAtomicActor {
065
066        public CoverBoundingBox(CompositeEntity container, String name)
067                        throws IllegalActionException, NameDuplicationException {
068                super(container, name);
069                
070                input = new TypedIOPort(this, "input", true, false);
071                input.setTypeEquals(BoundingBoxToken.BOUNDING_BOX);
072            new SingletonAttribute(input, "_showName");
073
074                maxLength = new PortParameter(this, "maxLength");
075                maxLength.setTypeEquals(BaseType.DOUBLE);
076                new SingletonAttribute(maxLength.getPort(), "_showName");
077
078                regions = new TypedIOPort(this, "regions", false, true);
079
080                labelEachRegion = new Parameter(this, "labelEachRegion");
081                labelEachRegion.setTypeEquals(BaseType.BOOLEAN);
082                labelEachRegion.setToken(BooleanToken.TRUE);
083        }
084        
085        @Override
086        public void attributeChanged(Attribute attribute) throws IllegalActionException {
087                
088                if(attribute == labelEachRegion) {
089                        _labelEachRegion = ((BooleanToken)labelEachRegion.getToken()).booleanValue();
090                        if(_labelEachRegion) {
091                                regions.setTypeEquals(_outputTypeWithID);
092                        } else {
093                                regions.setTypeEquals(_outputType);
094                        }
095                } else {
096                        super.attributeChanged(attribute);
097                }
098        }
099
100        /*
101    @Override
102    public Object clone(Workspace workspace) throws CloneNotSupportedException {
103        CoverBoundingBox newObject = (CoverBoundingBox) super.clone(workspace);
104        newObject._regionPrefix = _regionPrefix;
105        return newObject;
106    }
107    */
108
109        @Override
110        public void fire() throws IllegalActionException {
111                
112                maxLength.update();
113
114                Token token = input.get(0);
115                if(token == null) {
116                    throw new IllegalActionException(this, "Must provide bounding box. ");
117                }
118                ReferencedEnvelope envelope = ((BoundingBoxToken)token).boundingBoxValue();
119                
120                CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
121                
122                final double maxY = envelope.getMaxY();
123                final double minY = envelope.getMinY();
124                final double minX = envelope.getMinX();
125                final double maxX = envelope.getMaxX();
126                
127                // perform sanity checks
128                if(maxY <= minY) {
129                        throw new IllegalActionException(this,
130                                "Top is less than or equal to the bottom coordinates.");
131                }
132                
133                if(maxX <= minX) {
134                        throw new IllegalActionException(this,
135                                "Right is smaller than or equal to the left coordinates.");
136                }
137
138                final double maxLengthValue = _readValue(maxLength);
139                
140                if(maxLengthValue <= 0) {
141                        throw new IllegalActionException(this,
142                                "Maximum length must be greater than 0.");
143                }
144                        
145                // determine height
146                final double height = maxY - minY;
147                final double numRegionsHeight = Math.ceil(height / maxLengthValue);
148                final double regionHeightLength = height / numRegionsHeight;
149                //System.out.println("# regions h = " + regionsHeightLength + ", length = " + regionsHeightLength);
150                                
151                // determine width
152                final double width = maxX - minX;
153                final double numRegionsWidth = Math.ceil(width / maxLengthValue);
154                final double regionWidthLength = width / numRegionsWidth;
155                //System.out.println("# regions w = " + numRegionsWidth + ", length = " + regionsWidthLength);
156                
157                List<Token> tokens = new ArrayList<Token>();
158                int curID = 0;
159                for(int x = 1; x <= numRegionsWidth; x++) {
160                        for(int y = 1; y <= numRegionsHeight; y++) {
161                                double curMaxY = minY + y*regionHeightLength;
162                                double curMinY = curMaxY - regionHeightLength;
163                                double curMaxX = minX + x*regionWidthLength;
164                                double curMinX = curMaxX - regionWidthLength;
165                                BoundingBoxToken bboxToken = new BoundingBoxToken(
166                                        new ReferencedEnvelope(curMinX, curMaxX, curMinY, curMaxY, crs));
167                                Token curToken;
168                                if(_labelEachRegion) {
169                                    curToken = new RecordToken(_recordNamesWithID,
170                                                new Token[] {bboxToken,
171                                                new StringToken(_regionPrefix + curID)});
172                                        curID++;
173                                } else {
174                                    curToken = bboxToken;
175                                }
176                                
177                                tokens.add(curToken);
178                                
179                                //System.out.println(curTop + " " + curBottom + " " + curRight + " " + curLeft);
180                        }                       
181                }
182
183                if(_debugging) {
184                    _debug("Cover has " + tokens.size() + " regions.");
185                }
186                
187                regions.broadcast(new ArrayToken(tokens.toArray(new Token[tokens.size()])));
188
189        }
190        
191        private double _readValue(PortParameter pp) throws IllegalActionException {
192                Token token = pp.getToken();
193                if(token == null) {
194                        throw new IllegalActionException(this,
195                                "Missing value for " + pp.getName());
196                }
197                return ((DoubleToken)token).doubleValue();
198        }
199        
200        /** The input bounding box. */
201        public TypedIOPort input;
202        
203        /** The maximum length of any side of a region within the bounding box.
204         *  The units of this length are the same as the units of the 
205         *  coordinate system used by the bounding box.
206         */
207        public PortParameter maxLength;
208
209        /** If true, each region is given a unique label. */
210        public Parameter labelEachRegion;
211        
212        /** The non-overlapping regions that cover the bounding box. Each region
213         *  is a bounding box.
214         */
215        public TypedIOPort regions;
216        
217        private final static String[] _recordNamesWithID =
218                new String[] {"bbox", "id"};
219        
220        private final static Type _outputType =
221                new ArrayType(BoundingBoxToken.BOUNDING_BOX);
222
223        private final static Type _outputTypeWithID =
224                new ArrayType(new RecordType(_recordNamesWithID,
225                new Type[] {
226                    BoundingBoxToken.BOUNDING_BOX,
227                        BaseType.STRING}));
228
229        private boolean _labelEachRegion = true;
230        
231        // TODO either make final static or changeable via a parameter.
232        private String _regionPrefix = "region";
233}