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 }