1 /* 2 * #%L 3 * AggregationGeoNear.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 static com.allanbank.mongodb.util.Assertions.assertNotEmpty; 24 import static com.allanbank.mongodb.util.Assertions.assertNotNull; 25 26 import java.awt.geom.Point2D; 27 28 import com.allanbank.mongodb.bson.Document; 29 import com.allanbank.mongodb.bson.DocumentAssignable; 30 import com.allanbank.mongodb.bson.builder.BuilderFactory; 31 import com.allanbank.mongodb.bson.builder.DocumentBuilder; 32 33 /** 34 * AggregationGeoNear provides the options for the {@code $geoNear} pipeline 35 * stage of an aggregation. 36 * 37 * @api.yes This class is part of the driver's API. Public and protected members 38 * will be deprecated for at least 1 non-bugfix release (version 39 * numbers are <major>.<minor>.<bugfix>) before being 40 * removed or modified. 41 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved 42 * 43 * @since MongoDB 2.4 44 */ 45 public class AggregationGeoNear implements DocumentAssignable { 46 47 /** 48 * Creates a new builder for an {@link AggregationGeoNear}. 49 * 50 * @return The builder to construct an {@link AggregationGeoNear}. 51 */ 52 public static Builder builder() { 53 return new Builder(); 54 } 55 56 /** 57 * The name of the field to place the distance from the source 58 * {@link #getLocation() location}. 59 */ 60 private final String myDistanceField; 61 62 /** 63 * The distance multiplier to use in the {@code $geoNear}, if set. 64 * <code>null</code> otherwise. 65 */ 66 private final Double myDistanceMultiplier; 67 68 /** 69 * The maximum number of documents to return, if set. <code>null</code> 70 * otherwise. 71 */ 72 private final Long myLimit; 73 74 /** The location to find documents near. */ 75 private final Point2D myLocation; 76 77 /** 78 * The name of the field to place the location information from the 79 * document, if set. <code>null</code> otherwise. 80 */ 81 private final String myLocationField; 82 83 /** 84 * The maximum distance to return documents from the specified location, if 85 * set. <code>null</code> otherwise. 86 */ 87 private final Double myMaxDistance; 88 89 /** 90 * The optional query for further refining the documents to add to the 91 * pipeline. 92 */ 93 private final Document myQuery; 94 95 /** 96 * If true the {@code $geoNear} should compute distances using spherical 97 * coordinates instead of planar coordinates. Defaults to false. 98 */ 99 private final boolean mySpherical; 100 101 /** 102 * If true the {@code $geoNear} should only return documents once. Defaults 103 * to true. 104 */ 105 private final boolean myUniqueDocs; 106 107 /** 108 * Creates a new AggregationGeoNear. 109 * 110 * @param builder 111 * he builder for the AggregationGeoNear stage. 112 * @throws IllegalArgumentException 113 * If the {@link #getLocation() location} or 114 * {@link #getDistanceField() distance field} have not been set. 115 */ 116 protected AggregationGeoNear(final Builder builder) 117 throws IllegalArgumentException { 118 119 assertNotNull(builder.myLocation, "You must specify a location for " 120 + "a geoNear in an aggregation pipeline."); 121 assertNotEmpty(builder.myDistanceField, 122 "You must specify a distance field locations for " 123 + "a geoNear in an aggregation pipeline."); 124 125 myDistanceField = builder.myDistanceField; 126 myDistanceMultiplier = builder.myDistanceMultiplier; 127 myLocationField = builder.myLocationField; 128 myLimit = builder.myLimit; 129 myLocation = builder.myLocation; 130 myMaxDistance = builder.myMaxDistance; 131 myQuery = builder.myQuery; 132 mySpherical = builder.mySpherical; 133 myUniqueDocs = builder.myUniqueDocs; 134 } 135 136 /** 137 * {@inheritDoc} 138 * <p> 139 * Overridden to return the $geoNear aggregation pipeline's options 140 * document. This does not include the $geoNear operator, just the options 141 * document. 142 * </p> 143 */ 144 @Override 145 public Document asDocument() { 146 final DocumentBuilder builder = BuilderFactory.start(); 147 148 GeoJson.addRaw(builder.pushArray("near"), myLocation); 149 builder.add("distanceField", myDistanceField); 150 builder.add("spherical", mySpherical); 151 builder.add("uniqueDocs", myUniqueDocs); 152 153 if (myLimit != null) { 154 builder.add("limit", myLimit.longValue()); 155 } 156 if (myMaxDistance != null) { 157 builder.add("maxDistance", myMaxDistance); 158 } 159 if (myQuery != null) { 160 builder.add("query", myQuery); 161 } 162 if (myDistanceMultiplier != null) { 163 builder.add("distanceMultiplier", 164 myDistanceMultiplier.doubleValue()); 165 } 166 if (myLocationField != null) { 167 builder.add("includeLocs", myLocationField); 168 } 169 170 return builder.build(); 171 } 172 173 /** 174 * Returns the name of the field to place the distance from the source 175 * {@link #getLocation() location}. 176 * 177 * @return The name of the field to place the distance from the source 178 * {@link #getLocation() location}. 179 */ 180 public String getDistanceField() { 181 return myDistanceField; 182 } 183 184 /** 185 * If set returns the distance multiplier to use in the {@code $geoNear}. 186 * 187 * @return The distance multiplier to use in the {@code $geoNear}, if set. 188 * <code>null</code> otherwise. 189 */ 190 public Double getDistanceMultiplier() { 191 return myDistanceMultiplier; 192 } 193 194 /** 195 * If set returns the maximum number of documents to return. 196 * 197 * @return The maximum number of documents to return, if set. 198 * <code>null</code> otherwise. 199 */ 200 public Long getLimit() { 201 return myLimit; 202 } 203 204 /** 205 * Returns the location to find documents near. 206 * 207 * @return The location to find documents near. 208 */ 209 public Point2D getLocation() { 210 return myLocation; 211 } 212 213 /** 214 * If set returns the name of the field to place the location information 215 * from the document. 216 * 217 * @return The name of the field to place the location information from the 218 * document, if set. <code>null</code> otherwise. 219 */ 220 public String getLocationField() { 221 return myLocationField; 222 } 223 224 /** 225 * If set returns the maximum distance to return documents from the 226 * specified location 227 * 228 * @return The maximum distance to return documents from the specified 229 * location, if set. <code>null</code> otherwise. 230 */ 231 public Double getMaxDistance() { 232 return myMaxDistance; 233 } 234 235 /** 236 * If set returns the optional query for further refining the documents to 237 * add to the pipeline. 238 * 239 * @return The optional query for further refining the documents to add to 240 * the pipeline, if set. <code>null</code> otherwise. 241 */ 242 public Document getQuery() { 243 return myQuery; 244 } 245 246 /** 247 * Returns true if the {@code $geoNear} should compute distances using 248 * spherical coordinates instead of planar coordinates. Defaults to false. 249 * 250 * @return True if the {@code $geoNear} should compute distances using 251 * spherical coordinates instead of planar coordinates. 252 */ 253 public boolean isSpherical() { 254 return mySpherical; 255 } 256 257 /** 258 * Returns true if the {@code $geoNear} should only return documents once. 259 * Defaults to true. 260 * 261 * @return True if the {@code $geoNear} should only return documents once. 262 */ 263 public boolean isUniqueDocs() { 264 return myUniqueDocs; 265 } 266 267 /** 268 * Helper for creating immutable {@link Find} queries. 269 * 270 * @api.yes This class is part of the driver's API. Public and protected 271 * members will be deprecated for at least 1 non-bugfix release 272 * (version numbers are <major>.<minor>.<bugfix>) 273 * before being removed or modified. 274 * @copyright 2013, Allanbank Consulting, Inc., All Rights Reserved 275 */ 276 public static class Builder { 277 /** 278 * The name of the field to place the distance from the source 279 * {@link #getLocation() location}. 280 */ 281 protected String myDistanceField; 282 283 /** 284 * The distance multiplier to use in the {@code $geoNear}, if set. 285 * <code>null</code> otherwise. 286 */ 287 protected Double myDistanceMultiplier; 288 289 /** 290 * The maximum number of documents to return, if set. <code>null</code> 291 * otherwise. 292 */ 293 protected Long myLimit; 294 295 /** The location to find documents near. */ 296 protected Point2D myLocation; 297 298 /** 299 * The name of the field to place the location information from the 300 * document, if set. <code>null</code> otherwise. 301 */ 302 protected String myLocationField; 303 304 /** 305 * The maximum distance to return documents from the specified location, 306 * if set. <code>null</code> otherwise. 307 */ 308 protected Double myMaxDistance; 309 310 /** 311 * The optional query for further refining the documents to add to the 312 * pipeline. 313 */ 314 protected Document myQuery; 315 316 /** 317 * If true the {@code $geoNear} should compute distances using spherical 318 * coordinates instead of planar coordinates. Defaults to false. 319 */ 320 protected boolean mySpherical; 321 322 /** 323 * If true the {@code $geoNear} should only return documents once. 324 * Defaults to true. 325 */ 326 protected boolean myUniqueDocs; 327 328 /** 329 * Creates a new Builder. 330 */ 331 public Builder() { 332 reset(); 333 } 334 335 /** 336 * Constructs a new {@link AggregationGeoNear} object from the state of 337 * the builder. 338 * 339 * @return The new {@link AggregationGeoNear} object. 340 * @throws IllegalArgumentException 341 * If the {@link #setLocation(Point2D) location} or 342 * {@link #setDistanceField(String) distance field} have not 343 * been set. 344 */ 345 public AggregationGeoNear build() { 346 return new AggregationGeoNear(this); 347 } 348 349 /** 350 * Sets the name of the field to place the distance from the source 351 * {@link #getLocation() location}. 352 * <p> 353 * This method delegates to {@link #setDistanceField(String)}. 354 * </p> 355 * 356 * @param distanceField 357 * The new name of the field to place the distance from the 358 * source {@link #setLocation(Point2D) location}. 359 * @return This builder for chaining method calls. 360 */ 361 public Builder distanceField(final String distanceField) { 362 return setDistanceField(distanceField); 363 } 364 365 /** 366 * Sets the distance multiplier to use in the {@code $geoNear}. 367 * <p> 368 * This method delegates to {@link #setDistanceMultiplier(double)}. 369 * </p> 370 * 371 * @param distanceMultiplier 372 * The new distance multiplier to use in the {@code $geoNear} 373 * . 374 * @return This builder for chaining method calls. 375 */ 376 public Builder distanceMultiplier(final double distanceMultiplier) { 377 return setDistanceMultiplier(distanceMultiplier); 378 } 379 380 /** 381 * Sets the maximum number of documents to return. 382 * <p> 383 * This method delegates to {@link #setLimit(long)}. 384 * </p> 385 * 386 * @param limit 387 * The new maximum number of documents to return. 388 * @return This builder for chaining method calls. 389 */ 390 public Builder limit(final long limit) { 391 return setLimit(limit); 392 } 393 394 /** 395 * Sets the location to find documents near. 396 * <p> 397 * This method delegates to {@link #setLocation(Point2D)}. 398 * </p> 399 * 400 * @param location 401 * The new location to find documents near. 402 * @return This builder for chaining method calls. 403 * @see GeoJson#p 404 */ 405 public Builder location(final Point2D location) { 406 return setLocation(location); 407 } 408 409 /** 410 * Sets the name of the field to place the location information from the 411 * document. 412 * <p> 413 * This method delegates to {@link #setLocationField(String)}. 414 * </p> 415 * 416 * @param locationField 417 * The new name of the field to place the location 418 * information from the document. 419 * @return This builder for chaining method calls. 420 */ 421 public Builder locationField(final String locationField) { 422 return setLocationField(locationField); 423 } 424 425 /** 426 * Sets the maximum distance to return documents from the specified 427 * location. 428 * <p> 429 * This method delegates to {@link #setMaxDistance(double)}. 430 * </p> 431 * 432 * @param maxDistance 433 * The new maximum distance to return documents from the 434 * specified location. 435 * @return This builder for chaining method calls. 436 */ 437 public Builder maxDistance(final double maxDistance) { 438 return setMaxDistance(maxDistance); 439 } 440 441 /** 442 * Sets the optional query for further refining the documents to add to 443 * the pipeline. 444 * <p> 445 * This method delegates to {@link #setQuery(DocumentAssignable)}. 446 * </p> 447 * 448 * @param query 449 * The new optional query for further refining the documents 450 * to add to the pipeline. 451 * @return This builder for chaining method calls. 452 */ 453 public Builder query(final DocumentAssignable query) { 454 return setQuery(query); 455 } 456 457 /** 458 * Resets the builder back to its initial state for reuse. 459 * 460 * @return This builder for chaining method calls. 461 */ 462 public Builder reset() { 463 myDistanceField = null; 464 myDistanceMultiplier = null; 465 myLimit = null; 466 myLocation = null; 467 myLocationField = null; 468 myMaxDistance = null; 469 myQuery = null; 470 mySpherical = false; 471 myUniqueDocs = true; 472 return this; 473 } 474 475 /** 476 * Sets the name of the field to place the distance from the source 477 * {@link #getLocation() location}. 478 * 479 * @param distanceField 480 * The new name of the field to place the distance from the 481 * source {@link #setLocation(Point2D) location}. 482 * @return This builder for chaining method calls. 483 */ 484 public Builder setDistanceField(final String distanceField) { 485 myDistanceField = distanceField; 486 return this; 487 } 488 489 /** 490 * Sets the distance multiplier to use in the {@code $geoNear}. 491 * 492 * @param distanceMultiplier 493 * The new distance multiplier to use in the {@code $geoNear} 494 * . 495 * @return This builder for chaining method calls. 496 */ 497 public Builder setDistanceMultiplier(final double distanceMultiplier) { 498 myDistanceMultiplier = Double.valueOf(distanceMultiplier); 499 return this; 500 } 501 502 /** 503 * Sets the maximum number of documents to return. 504 * 505 * @param limit 506 * The new maximum number of documents to return. 507 * @return This builder for chaining method calls. 508 */ 509 public Builder setLimit(final long limit) { 510 myLimit = Long.valueOf(limit); 511 return this; 512 } 513 514 /** 515 * Sets the location to find documents near. 516 * 517 * @param location 518 * The new location to find documents near. 519 * @return This builder for chaining method calls. 520 * @see GeoJson#p 521 */ 522 public Builder setLocation(final Point2D location) { 523 myLocation = location; 524 return this; 525 } 526 527 /** 528 * Sets the name of the field to place the location information from the 529 * document. 530 * 531 * @param locationField 532 * The new name of the field to place the location 533 * information from the document. 534 * @return This builder for chaining method calls. 535 */ 536 public Builder setLocationField(final String locationField) { 537 myLocationField = locationField; 538 return this; 539 } 540 541 /** 542 * Sets the maximum distance to return documents from the specified 543 * location. 544 * 545 * @param maxDistance 546 * The new maximum distance to return documents from the 547 * specified location. 548 * @return This builder for chaining method calls. 549 */ 550 public Builder setMaxDistance(final double maxDistance) { 551 myMaxDistance = Double.valueOf(maxDistance); 552 return this; 553 } 554 555 /** 556 * Sets the optional query for further refining the documents to add to 557 * the pipeline. 558 * 559 * @param query 560 * The new optional query for further refining the documents 561 * to add to the pipeline. 562 * @return This builder for chaining method calls. 563 */ 564 public Builder setQuery(final DocumentAssignable query) { 565 if (query != null) { 566 myQuery = query.asDocument(); 567 } 568 else { 569 myQuery = null; 570 } 571 return this; 572 } 573 574 /** 575 * Sets if (true) the {@code $geoNear} should compute distances using 576 * spherical coordinates instead of planar coordinates. Defaults to 577 * false. 578 * 579 * @param spherical 580 * The new value for if the {@code $geoNear} should compute 581 * distances using spherical coordinates instead of planar 582 * coordinates 583 * @return This builder for chaining method calls. 584 */ 585 public Builder setSpherical(final boolean spherical) { 586 mySpherical = spherical; 587 return this; 588 } 589 590 /** 591 * Sets if (true) the {@code $geoNear} should only return documents 592 * once. Defaults to true. 593 * 594 * @param uniqueDocs 595 * The new value for if the {@code $geoNear} should only 596 * return documents once. 597 * @return This builder for chaining method calls. 598 */ 599 public Builder setUniqueDocs(final boolean uniqueDocs) { 600 myUniqueDocs = uniqueDocs; 601 return this; 602 } 603 604 /** 605 * Sets the {@code $geoNear} to compute distances using spherical 606 * coordinates instead of planar coordinates. 607 * <p> 608 * This method delegates to {@link #setSpherical(boolean) 609 * setSpherical(true)}. 610 * </p> 611 * 612 * 613 * @return This builder for chaining method calls. 614 */ 615 public Builder spherical() { 616 return setSpherical(true); 617 } 618 619 /** 620 * Sets if (true) the {@code $geoNear} should compute distances using 621 * spherical coordinates instead of planar coordinates. Defaults to 622 * false. 623 * <p> 624 * This method delegates to {@link #setSpherical(boolean)}. 625 * </p> 626 * 627 * @param spherical 628 * The new value for if the {@code $geoNear} should compute 629 * distances using spherical coordinates instead of planar 630 * coordinates 631 * @return This builder for chaining method calls. 632 */ 633 public Builder spherical(final boolean spherical) { 634 return setSpherical(spherical); 635 } 636 637 /** 638 * Sets if (true) the {@code $geoNear} should only return documents 639 * once. Defaults to true. 640 * <p> 641 * This method delegates to {@link #setUniqueDocs(boolean)}. 642 * </p> 643 * 644 * @param uniqueDocs 645 * The new value for if the {@code $geoNear} should only 646 * return documents once. 647 * @return This builder for chaining method calls. 648 */ 649 public Builder uniqueDocs(final boolean uniqueDocs) { 650 return setUniqueDocs(uniqueDocs); 651 } 652 } 653 }