| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| GeoJson |
|
| 1.9333333333333333;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 | } |