Coverage Report - com.allanbank.mongodb.builder.GeoJson
 
Classes in this File Line Coverage Branch Coverage Complexity
GeoJson
97%
79/81
100%
22/22
1.933
 
 1  
 /*
 2  
  * #%L
 3  
  * GeoJson.java - mongodb-async-driver - Allanbank Consulting, Inc.
 4  
  * %%
 5  
  * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
 6  
  * %%
 7  
  * Licensed under the Apache License, Version 2.0 (the "License");
 8  
  * you may not use this file except in compliance with the License.
 9  
  * You may obtain a copy of the License at
 10  
  * 
 11  
  *      http://www.apache.org/licenses/LICENSE-2.0
 12  
  * 
 13  
  * Unless required by applicable law or agreed to in writing, software
 14  
  * distributed under the License is distributed on an "AS IS" BASIS,
 15  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16  
  * See the License for the specific language governing permissions and
 17  
  * limitations under the License.
 18  
  * #L%
 19  
  */
 20  
 
 21  
 package com.allanbank.mongodb.builder;
 22  
 
 23  
 import java.awt.Point;
 24  
 import java.awt.geom.Point2D;
 25  
 import java.util.List;
 26  
 
 27  
 import com.allanbank.mongodb.Version;
 28  
 import com.allanbank.mongodb.bson.Document;
 29  
 import com.allanbank.mongodb.bson.builder.ArrayBuilder;
 30  
 import com.allanbank.mongodb.bson.builder.BuilderFactory;
 31  
 import com.allanbank.mongodb.bson.builder.DocumentBuilder;
 32  
 
 33  
 /**
 34  
  * GeoJson provides static methods to help create <a
 35  
  * href="http://www.geojson.org/geojson-spec.html">GeoJSON</a> BSON Documents.
 36  
  * <p>
 37  
  * This class uses the {@link Point} and {@link Point2D} classes to represent
 38  
  * GeoJSON positions. To assist in converting raw (x, y) coordinates the
 39  
  * {@link #p(double, double)} and {@link #p(int, int)} methods are provides to
 40  
  * quickly construct the Point instances.
 41  
  * </p>
 42  
  * <p>
 43  
  * As an example of using this class consider the following Polygon with a hole
 44  
  * from the GeoJSON specification: <blockquote>
 45  
  * 
 46  
  * <pre>
 47  
  * <code>
 48  
  * { "type": "Polygon",
 49  
  *   "coordinates": [
 50  
  *     [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],
 51  
  *     [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]
 52  
  *     ]
 53  
  *  }
 54  
  * </code>
 55  
  * </pre>
 56  
  * 
 57  
  * </blockquote>
 58  
  * 
 59  
  * The equivalent BSON document can be constructed via:<blockquote>
 60  
  * 
 61  
  * <pre>
 62  
  * <code>
 63  
  * import static com.allanbank.mongodb.builder.GeoJson.polygon;
 64  
  * import static com.allanbank.mongodb.builder.GeoJson.p;
 65  
  * 
 66  
  * Document geoJsonPolygon = polygon(
 67  
  *      Arrays.asList( p(100.0, 0.0), p(101.0, 0.0), p(101.0, 1.0), p(100.0, 1.0), p(100.0, 0.0) ),
 68  
  *      Arrays.asList( p(100.2, 0.2), p(100.8, 0.2), p(100.8, 0.8), p(100.2, 0.8), p(100.2, 0.2) ) );
 69  
  * </code>
 70  
  * </pre>
 71  
  * 
 72  
  * </blockquote>
 73  
  * 
 74  
  * @see <a href="http://www.geojson.org/geojson-spec.html">The GeoJSON Format
 75  
  *      Specification</a>
 76  
  * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved
 77  
  */
 78  
 public final class GeoJson {
 79  
 
 80  
     /** The version of MongoDB that provided support for Multi* GeoJSON objects. */
 81  1
     public static final Version MULTI_SUPPORT_VERSION = Version.VERSION_2_6;
 82  
 
 83  
     /** The class for an integer {@link Point} */
 84  1
     private static final Class<Point> POINT_CLASS = Point.class;
 85  
 
 86  
     /**
 87  
      * Constructs a GeoJSON 'LineString' document from the coordinates provided.
 88  
      * 
 89  
      * @param points
 90  
      *            The positions in the line string. There should be at least 2
 91  
      *            positions for the document to be a valid LineString.
 92  
      * 
 93  
      * @return A GeoJSON LineString document from the coordinates provided.
 94  
      * @throws IllegalArgumentException
 95  
      *             If the list does not contain at least 2 points.
 96  
      * @see <a
 97  
      *      href="http://www.geojson.org/geojson-spec.html#linestring">GeoJSON
 98  
      *      LineString</a>
 99  
      */
 100  
     public static Document lineString(final List<? extends Point2D> points)
 101  
             throws IllegalArgumentException {
 102  2
         if (points.size() < 2) {
 103  1
             throw new IllegalArgumentException(
 104  
                     "A GeoJSON LineString must have at least 2 postions.");
 105  
         }
 106  1
         final DocumentBuilder builder = BuilderFactory.start();
 107  1
         builder.add("type", "LineString");
 108  
 
 109  1
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 110  1
         add(coordinates, points);
 111  
 
 112  1
         return builder.build();
 113  
     }
 114  
 
 115  
     /**
 116  
      * Constructs a GeoJSON 'LineString' document from the coordinates provided.
 117  
      * 
 118  
      * @param p1
 119  
      *            The first position in the line string.
 120  
      * @param p2
 121  
      *            The second position in the line string.
 122  
      * @param remaining
 123  
      *            The remaining positions in the line string.
 124  
      * 
 125  
      * @return A GeoJSON LineString document from the coordinates provided.
 126  
      * @see <a
 127  
      *      href="http://www.geojson.org/geojson-spec.html#linestring">GeoJSON
 128  
      *      LineString</a>
 129  
      */
 130  
     public static Document lineString(final Point2D p1, final Point2D p2,
 131  
             final Point2D... remaining) {
 132  1
         final DocumentBuilder builder = BuilderFactory.start();
 133  1
         builder.add("type", "LineString");
 134  1
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 135  
 
 136  1
         add(coordinates, p1);
 137  1
         add(coordinates, p2);
 138  2
         for (final Point2D point : remaining) {
 139  1
             add(coordinates, point);
 140  
         }
 141  
 
 142  1
         return builder.build();
 143  
     }
 144  
 
 145  
     /**
 146  
      * Constructs a GeoJSON 'MultiLineString' document from the coordinates
 147  
      * provided.
 148  
      * <p>
 149  
      * Note: You will need to add a {@code @SuppressWarnings("unchecked")} to
 150  
      * uses of this method as Java does not like mixing generics and varargs.
 151  
      * The {@code @SafeVarargs} annotation is not available in Java 1.6, the
 152  
      * minimum version for the driver.
 153  
      * </p>
 154  
      * 
 155  
      * @param firstLineString
 156  
      *            The first line string.
 157  
      * @param additionalLineStrings
 158  
      *            The remaining line strings.
 159  
      * @return A GeoJSON MultiLineString document from the coordinates provided.
 160  
      * 
 161  
      * @see <a href="http://www.geojson.org/geojson-spec.html#polygon">GeoJSON
 162  
      *      Polygon</a>
 163  
      */
 164  
     public static Document multiLineString(
 165  
             final List<? extends Point2D> firstLineString,
 166  
             final List<? extends Point2D>... additionalLineStrings) {
 167  1
         final DocumentBuilder builder = BuilderFactory.start();
 168  1
         builder.add("type", "MultiLineString");
 169  
 
 170  1
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 171  
 
 172  1
         add(coordinates.pushArray(), firstLineString);
 173  
 
 174  2
         for (final List<? extends Point2D> additionalLineString : additionalLineStrings) {
 175  1
             add(coordinates.pushArray(), additionalLineString);
 176  
         }
 177  
 
 178  1
         return builder.build();
 179  
     }
 180  
 
 181  
     /**
 182  
      * Constructs a GeoJSON 'MultiPoint' document from the positions provided.
 183  
      * 
 184  
      * @param positions
 185  
      *            The positions
 186  
      * @return A GeoJSON MultiPoint document from the coordinates provided.
 187  
      * @see <a href="http://www.geojson.org/geojson-spec.html#point">GeoJSON
 188  
      *      Point</a>
 189  
      */
 190  
     public static Document multiPoint(final List<? extends Point2D> positions) {
 191  1
         final DocumentBuilder builder = BuilderFactory.start();
 192  1
         builder.add("type", "MultiPoint");
 193  
 
 194  1
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 195  1
         for (final Point2D position : positions) {
 196  5
             add(coordinates, position);
 197  
 
 198  5
         }
 199  1
         return builder.build();
 200  
     }
 201  
 
 202  
     /**
 203  
      * Constructs a GeoJSON 'MultiPoint' document from the positions provided.
 204  
      * 
 205  
      * @param firstPosition
 206  
      *            The first position
 207  
      * @param additionalPositions
 208  
      *            The other positions
 209  
      * @return A GeoJSON MultiPoint document from the coordinates provided.
 210  
      * @see <a href="http://www.geojson.org/geojson-spec.html#point">GeoJSON
 211  
      *      Point</a>
 212  
      */
 213  
     public static Document multiPoint(final Point2D firstPosition,
 214  
             final Point2D... additionalPositions) {
 215  4
         final DocumentBuilder builder = BuilderFactory.start();
 216  4
         builder.add("type", "MultiPoint");
 217  
 
 218  4
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 219  4
         add(coordinates, firstPosition);
 220  11
         for (final Point2D additionalPosition : additionalPositions) {
 221  7
             add(coordinates, additionalPosition);
 222  
 
 223  
         }
 224  4
         return builder.build();
 225  
     }
 226  
 
 227  
     /**
 228  
      * Helper method to construct a {@link Point2D} from the (x, y) coordinates.
 229  
      * 
 230  
      * @param x
 231  
      *            The point's x position or longitude.
 232  
      * @param y
 233  
      *            The point's y position or latitude.
 234  
      * @return A Point for the coordinates provided.
 235  
      */
 236  
     public static Point2D p(final double x, final double y) {
 237  103
         return new Point2D.Double(x, y);
 238  
     }
 239  
 
 240  
     /**
 241  
      * Helper method to construct a {@link Point} from the (x, y) coordinates.
 242  
      * 
 243  
      * @param x
 244  
      *            The point's x position or longitude.
 245  
      * @param y
 246  
      *            The point's y position or latitude.
 247  
      * @return A Point for the coordinates provided.
 248  
      */
 249  
     public static Point p(final int x, final int y) {
 250  15
         return new Point(x, y);
 251  
     }
 252  
 
 253  
     /**
 254  
      * Constructs a GeoJSON 'Point' document from the coordinates provided.
 255  
      * 
 256  
      * @param position
 257  
      *            The point's position
 258  
      * @return A GeoJSON Point document from the coordinates provided.
 259  
      * @see <a href="http://www.geojson.org/geojson-spec.html#point">GeoJSON
 260  
      *      Point</a>
 261  
      */
 262  
     public static Document point(final Point2D position) {
 263  10
         final DocumentBuilder builder = BuilderFactory.start();
 264  10
         builder.add("type", "Point");
 265  
 
 266  10
         addRaw(builder.pushArray("coordinates"), position);
 267  
 
 268  10
         return builder.build();
 269  
     }
 270  
 
 271  
     /**
 272  
      * Constructs a GeoJSON 'Polygon' document from the coordinates provided.
 273  
      * 
 274  
      * @param boundary
 275  
      *            The boundary positions for the polygon.
 276  
      * @return A GeoJSON Polygon document from the coordinates provided.
 277  
      * @throws IllegalArgumentException
 278  
      *             If the line ring does not have at least 4 positions or the
 279  
      *             first and last positions are not equivalent.
 280  
      * 
 281  
      * @see <a href="http://www.geojson.org/geojson-spec.html#polygon">GeoJSON
 282  
      *      Polygon</a>
 283  
      */
 284  
     public static Document polygon(final List<? extends Point2D> boundary)
 285  
             throws IllegalArgumentException {
 286  13
         final DocumentBuilder builder = BuilderFactory.start();
 287  13
         builder.add("type", "Polygon");
 288  
 
 289  13
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 290  
 
 291  13
         lineRing(coordinates.pushArray(), boundary);
 292  
 
 293  11
         return builder.build();
 294  
     }
 295  
 
 296  
     /**
 297  
      * Constructs a GeoJSON 'Polygon' document from the coordinates provided.
 298  
      * <p>
 299  
      * Note: You will need to add a {@code @SuppressWarnings("unchecked")} to
 300  
      * uses of this method as Java does not like mixing generics and varargs.
 301  
      * The {@code @SafeVarargs} annotation is not available in Java 1.6, the
 302  
      * minimum version for the driver.
 303  
      * </p>
 304  
      * 
 305  
      * @param boundary
 306  
      *            The boundary positions for the polygon.
 307  
      * @param holes
 308  
      *            The positions for the holes within the polygon.
 309  
      * @return A GeoJSON Polygon document from the coordinates provided.
 310  
      * @throws IllegalArgumentException
 311  
      *             If the line ring does not have at least 4 positions or the
 312  
      *             first and last positions are not equivalent.
 313  
      * 
 314  
      * @see <a href="http://www.geojson.org/geojson-spec.html#polygon">GeoJSON
 315  
      *      Polygon</a>
 316  
      */
 317  
     public static Document polygon(final List<? extends Point2D> boundary,
 318  
             final List<? extends Point2D>... holes)
 319  
             throws IllegalArgumentException {
 320  1
         final DocumentBuilder builder = BuilderFactory.start();
 321  1
         builder.add("type", "Polygon");
 322  
 
 323  1
         final ArrayBuilder coordinates = builder.pushArray("coordinates");
 324  
 
 325  1
         lineRing(coordinates.pushArray(), boundary);
 326  
 
 327  2
         for (final List<? extends Point2D> hole : holes) {
 328  1
             lineRing(coordinates.pushArray(), hole);
 329  
         }
 330  
 
 331  1
         return builder.build();
 332  
     }
 333  
 
 334  
     /**
 335  
      * Adds a positions to the coordinates array.
 336  
      * 
 337  
      * @param coordinates
 338  
      *            The array to add the position to.
 339  
      * @param positions
 340  
      *            The positions to add.
 341  
      */
 342  
     protected static void add(final ArrayBuilder coordinates,
 343  
             final List<? extends Point2D> positions) {
 344  3
         for (final Point2D position : positions) {
 345  13
             add(coordinates, position);
 346  13
         }
 347  3
     }
 348  
 
 349  
     /**
 350  
      * Adds a position to the coordinates array.
 351  
      * 
 352  
      * @param coordinates
 353  
      *            The array to add the position to.
 354  
      * @param position
 355  
      *            The point to add.
 356  
      */
 357  
     protected static void add(final ArrayBuilder coordinates,
 358  
             final Point2D position) {
 359  102
         addRaw(coordinates.pushArray(), position);
 360  102
     }
 361  
 
 362  
     /**
 363  
      * Adds the (x,y) coordinates from the point directly to the array provided.
 364  
      * 
 365  
      * @param arrayBuilder
 366  
      *            The builder to append the (x,y) coordinates to.
 367  
      * @param position
 368  
      *            The (x,y) coordinates.
 369  
      */
 370  
     protected static void addRaw(final ArrayBuilder arrayBuilder,
 371  
             final Point2D position) {
 372  116
         if (position.getClass() == POINT_CLASS) {
 373  17
             final Point p = (Point) position;
 374  17
             arrayBuilder.add(p.x).add(p.y);
 375  17
         }
 376  
         else {
 377  99
             arrayBuilder.add(position.getX()).add(position.getY());
 378  
         }
 379  116
     }
 380  
 
 381  
     /**
 382  
      * Fills in the LineRing coordinates.
 383  
      * 
 384  
      * @param positionArray
 385  
      *            The array to fill with the positions.
 386  
      * @param positions
 387  
      *            The positions defining the LineRing.
 388  
      * @throws IllegalArgumentException
 389  
      *             If the line ring does not have at least 4 positions or the
 390  
      *             first and last positions are not equivalent.
 391  
      */
 392  
     protected static void lineRing(final ArrayBuilder positionArray,
 393  
             final List<? extends Point2D> positions)
 394  
             throws IllegalArgumentException {
 395  
 
 396  15
         if (positions.size() < 4) {
 397  1
             throw new IllegalArgumentException(
 398  
                     "A GeoJSON LineRing must have at least 4 postions.");
 399  
         }
 400  
 
 401  14
         final Point2D first = positions.get(0);
 402  14
         Point2D last = first;
 403  14
         for (final Point2D point : positions) {
 404  70
             add(positionArray, point);
 405  70
             last = point;
 406  70
         }
 407  
         // The LineString must loop to form a LineRing.
 408  14
         if (!last.equals(first)) {
 409  1
             throw new IllegalArgumentException(
 410  
                     "A GeoJSON LineRing's first and last postion must be equal.");
 411  
         }
 412  13
     }
 413  
 
 414  
     /**
 415  
      * Creates a new GeoJson.
 416  
      */
 417  0
     private GeoJson() { /* Static Class */
 418  0
     }
 419  
 }