View Javadoc
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 &lt;major&gt;.&lt;minor&gt;.&lt;bugfix&gt;)
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 }