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 public static final Version MULTI_SUPPORT_VERSION = Version.VERSION_2_6; 82 83 /** The class for an integer {@link Point} */ 84 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 if (points.size() < 2) { 103 throw new IllegalArgumentException( 104 "A GeoJSON LineString must have at least 2 postions."); 105 } 106 final DocumentBuilder builder = BuilderFactory.start(); 107 builder.add("type", "LineString"); 108 109 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 110 add(coordinates, points); 111 112 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 final DocumentBuilder builder = BuilderFactory.start(); 133 builder.add("type", "LineString"); 134 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 135 136 add(coordinates, p1); 137 add(coordinates, p2); 138 for (final Point2D point : remaining) { 139 add(coordinates, point); 140 } 141 142 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 final DocumentBuilder builder = BuilderFactory.start(); 168 builder.add("type", "MultiLineString"); 169 170 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 171 172 add(coordinates.pushArray(), firstLineString); 173 174 for (final List<? extends Point2D> additionalLineString : additionalLineStrings) { 175 add(coordinates.pushArray(), additionalLineString); 176 } 177 178 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 final DocumentBuilder builder = BuilderFactory.start(); 192 builder.add("type", "MultiPoint"); 193 194 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 195 for (final Point2D position : positions) { 196 add(coordinates, position); 197 198 } 199 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 final DocumentBuilder builder = BuilderFactory.start(); 216 builder.add("type", "MultiPoint"); 217 218 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 219 add(coordinates, firstPosition); 220 for (final Point2D additionalPosition : additionalPositions) { 221 add(coordinates, additionalPosition); 222 223 } 224 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 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 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 final DocumentBuilder builder = BuilderFactory.start(); 264 builder.add("type", "Point"); 265 266 addRaw(builder.pushArray("coordinates"), position); 267 268 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 final DocumentBuilder builder = BuilderFactory.start(); 287 builder.add("type", "Polygon"); 288 289 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 290 291 lineRing(coordinates.pushArray(), boundary); 292 293 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 final DocumentBuilder builder = BuilderFactory.start(); 321 builder.add("type", "Polygon"); 322 323 final ArrayBuilder coordinates = builder.pushArray("coordinates"); 324 325 lineRing(coordinates.pushArray(), boundary); 326 327 for (final List<? extends Point2D> hole : holes) { 328 lineRing(coordinates.pushArray(), hole); 329 } 330 331 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 for (final Point2D position : positions) { 345 add(coordinates, position); 346 } 347 } 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 addRaw(coordinates.pushArray(), position); 360 } 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 if (position.getClass() == POINT_CLASS) { 373 final Point p = (Point) position; 374 arrayBuilder.add(p.x).add(p.y); 375 } 376 else { 377 arrayBuilder.add(position.getX()).add(position.getY()); 378 } 379 } 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 if (positions.size() < 4) { 397 throw new IllegalArgumentException( 398 "A GeoJSON LineRing must have at least 4 postions."); 399 } 400 401 final Point2D first = positions.get(0); 402 Point2D last = first; 403 for (final Point2D point : positions) { 404 add(positionArray, point); 405 last = point; 406 } 407 // The LineString must loop to form a LineRing. 408 if (!last.equals(first)) { 409 throw new IllegalArgumentException( 410 "A GeoJSON LineRing's first and last postion must be equal."); 411 } 412 } 413 414 /** 415 * Creates a new GeoJson. 416 */ 417 private GeoJson() { /* Static Class */ 418 } 419 }