001/*
002 * Copyright (c) 2015 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2016-04-20 21:55:48 +0000 (Wed, 20 Apr 2016) $' 
007 * '$Revision: 34469 $'
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.data;
030
031import java.io.Serializable;
032
033import org.geotools.geometry.GeneralEnvelope;
034import org.geotools.geometry.jts.ReferencedEnvelope;
035import org.geotools.referencing.CRS;
036import org.geotools.referencing.crs.AbstractCRS;
037import org.opengis.geometry.DirectPosition;
038import org.opengis.geometry.Envelope;
039import org.opengis.referencing.FactoryException;
040import org.opengis.referencing.crs.CoordinateReferenceSystem;
041
042import ptolemy.data.BooleanToken;
043import ptolemy.data.Token;
044import ptolemy.data.type.BaseType;
045import ptolemy.data.type.Type;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.util.StringUtilities;
048
049/** Token containing a GIS bounding box.
050 *  
051 *  @author Daniel Crawl
052 *  @version $Id: BoundingBoxToken.java 34469 2016-04-20 21:55:48Z crawl $
053 *  
054 */
055public class BoundingBoxToken extends Token {
056    
057    /** Create a new BoundingBoxToken from a string value. The value is
058     *  the format produced by toString().
059     */
060    public BoundingBoxToken(String value) throws IllegalActionException {
061
062        String parts[] = value.split(",", 2);
063        if(parts.length != 2) {
064            throw new IllegalActionException("Could not parse string " + value);
065        }
066        
067        // parse the coordinates
068        String[] coords = parts[0].split(" ");
069        if(coords.length < 1 || coords.length % 2 != 0) {
070            throw new IllegalActionException("Invalid number of coordinates: " + parts[0]);
071        }
072        final int halfLength = coords.length / 2;
073        double[] min = new double[halfLength];
074        double[] max = new double[halfLength];
075        int i;
076        for(i = 0; i < halfLength; i++) {
077            min[i] = Double.parseDouble(coords[i]);
078            max[i] = Double.parseDouble(coords[i + halfLength]);
079        }
080        
081        Envelope envelope = new GeneralEnvelope(min, max);
082        
083        // parse the WKT
084        CoordinateReferenceSystem crs;
085        try {
086            crs = CRS.parseWKT(parts[1]);
087        } catch (FactoryException e) {
088            throw new IllegalActionException("Could not parse WKT: " + e.getMessage() +
089                    "for: " + parts[1]);
090        }
091        
092        _envelope = ReferencedEnvelope.create(envelope, crs);
093    }
094    
095    /** Create a new BoundingBoxToken with an evelope. */
096    public BoundingBoxToken(ReferencedEnvelope envelope) {
097        _envelope = envelope;
098    }
099        
100    /** Create a BoundingBoxToken from a string value. */
101    public static BoundingBoxToken bbox(String value) throws IllegalActionException {
102        return new BoundingBoxToken(value);
103    }
104    
105    /** Get the bounding box. */
106    public ReferencedEnvelope boundingBoxValue() {
107        return _envelope;
108    }
109    
110    /** Get the token type. */
111    @Override
112    public Type getType() {
113        return BOUNDING_BOX;
114    }
115    
116    /** Test that the value of this Token is close to the first argument,
117     *  where "close" means that the distance between them is less than
118     *  or equal to the second argument.
119     *  @param other The token to test closeness of this token with.
120     *  @param epsilon The value that we use to determine whether two
121     *   tokens are close.
122     *  @return A boolean token that contains the value true if the
123     *   value of this token are close to those of the
124     *   argument token.
125     *  If either this token or the argument token is a nil token, then
126     *  a boolean token that contains the value false is returned.
127     *  @exception IllegalActionException If the argument token is not
128     *   of a type that can be compared with this token.
129     */
130    @Override
131    public BooleanToken isCloseTo(Token other, double epsilon)
132            throws IllegalActionException {
133        
134        if(other instanceof BoundingBoxToken) {
135            //System.out.println(_envelope.getCoordinateReferenceSystem().getName());
136            if(((AbstractCRS)_envelope.getCoordinateReferenceSystem()).equals((AbstractCRS)
137                    ((BoundingBoxToken)other)._envelope.getCoordinateReferenceSystem(), false) &&
138                    _envelope.boundsEquals2D(((BoundingBoxToken)other)._envelope, epsilon)) {
139                return BooleanToken.TRUE;
140            }
141        }
142        return BooleanToken.FALSE;
143    }
144    
145    /**
146     * Test for equality of the values of this Token and the argument Token. If
147     * the value of this token or the value of the rightArgument token is null,
148     * then we return False.
149     * 
150     * @param rightArgument
151     *            The Token to test against.
152     * @exception IllegalActionException
153     *                Not thrown in this base class.
154     * @return A boolean token that contains the value true if the dates are the
155     *         same.
156     */
157    @Override
158    public final BooleanToken isEqualTo(Token rightArgument)
159            throws IllegalActionException {
160
161        if(rightArgument != null && rightArgument instanceof BoundingBoxToken &&
162            _envelope.equals(((BoundingBoxToken)rightArgument)._envelope)) {
163            return BooleanToken.TRUE;
164        }
165        return BooleanToken.FALSE;
166    }
167
168    /** Get the maximum x coordinate. */
169    public double maxX() {
170        return _envelope.getMaxX();
171    }
172    
173    /** Get the maximum x coordinate. */
174    public double maxY() {
175        return _envelope.getMaxY();
176    }
177
178    /** Get the minimum x coordinate. */
179    public double minX() {
180        return _envelope.getMinX();
181    }
182    
183    /** Get the minimum y coordinate. */
184    public double minY() {
185        return _envelope.getMinY();
186    }
187
188    /** Return a string representation of the bounding box. */
189    @Override
190    public String toString() {
191
192        // the output format is:
193        // bbox("min1 min2 max1 max2, WKT")
194        
195        DirectPosition lower = _envelope.getLowerCorner();
196        DirectPosition upper = _envelope.getUpperCorner();
197        
198        StringBuilder buf = new StringBuilder("bbox(\"");
199        int i;
200        for(i = 0; i < lower.getDimension(); i++) {
201            buf.append(lower.getOrdinate(i))
202                .append(" ");
203        }
204        for(i = 0; i < upper.getDimension() - 1; i++) {
205            buf.append(upper.getOrdinate(i))
206                .append(" ");
207        }
208        
209        String crsStr;
210        CoordinateReferenceSystem crs = _envelope.getCoordinateReferenceSystem();
211        if(crs == null) {
212            // FIXME handle null crs
213            crsStr = "unknown";
214        } else {
215            crsStr = StringUtilities.escapeString(crs.toWKT());
216        }
217        
218        buf.append(upper.getOrdinate(i))
219            .append(", ")
220            .append(crsStr)
221            .append("\")");        
222                
223        return buf.toString();
224    }
225    
226    /** The Type for the BoundingBoxToken. */
227    public static class BoundingBoxType implements Cloneable, Type, Serializable {
228
229        private BoundingBoxType() {
230            BaseType.addType(this, "bbox", BoundingBoxToken.class);
231        }
232        
233        @Override
234        public Type add(Type rightArgumentType) {
235            // TODO Auto-generated method stub
236            return this;
237        }
238
239        @Override
240        public Object clone() {
241            return this;
242        }
243        
244        /** Convert the specified token into a BoundingBox token. */
245        @Override
246        public Token convert(Token token) throws IllegalActionException {
247            if (token instanceof BoundingBoxToken) {
248                return token;
249            } else {
250                // TODO this could be done for vector and raster tokens
251                throw new IllegalActionException("Attempt to convert token "
252                        + token + " into a BoundingBox token, which is not possible.");
253            }
254        }
255
256        @Override
257        public Type divide(Type rightArgumentType) {
258            // TODO Auto-generated method stub
259            return this;
260        }
261
262        @Override
263        public int getTypeHash() {
264            return hashCode();
265        }
266
267        @Override
268        public Class<?> getTokenClass() {
269            return BoundingBoxToken.class;
270        }
271
272        @Override
273        public boolean isAbstract() {
274            return false;
275        }
276
277        @Override
278        public boolean isCompatible(Type type) {
279            return type == this;
280        }
281
282        @Override
283        public boolean isConstant() {
284            return true;
285        }
286
287        @Override
288        public boolean isInstantiable() {
289            return true;
290        }
291
292        @Override
293        public boolean isSubstitutionInstance(Type type) {
294            return type == this;
295        }
296
297        @Override
298        public Type modulo(Type rightArgumentType) {
299            // TODO Auto-generated method stub
300            return this;
301        }
302
303        @Override
304        public Type multiply(Type rightArgumentType) {
305            // TODO Auto-generated method stub
306            return this;
307        }
308
309        @Override
310        public Type one() {
311            // TODO Auto-generated method stub
312            return this;
313        }
314
315        @Override
316        public Type subtract(Type rightArgumentType) {
317            // TODO Auto-generated method stub
318            return this;
319        }
320
321        @Override
322        public String toString() {
323            return "bbox";
324        }
325        
326        @Override
327        public Type zero() {
328            // TODO Auto-generated method stub
329            return this;
330        }
331
332    }
333    
334    /** The bounding box type. */
335    public static final BoundingBoxType BOUNDING_BOX = new BoundingBoxType();
336    
337    /** The GIS envelope containing the bounding box coordinates. */
338    private ReferencedEnvelope _envelope;
339    
340}