1// Copyright 2012-present Oliver Eilhard. All rights reserved.
2// Use of this source code is governed by a MIT-license.
3// See http://olivere.mit-license.org/license.txt for details.
4
5package elastic
6
7// GeoBoundingBoxQuery allows to filter hits based on a point location using
8// a bounding box.
9//
10// For more details, see:
11// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-geo-bounding-box-query.html
12type GeoBoundingBoxQuery struct {
13	name             string
14	topLeft          interface{} // can be a GeoPoint, a GeoHash (string), or a lat/lon pair as float64
15	topRight         interface{}
16	bottomRight      interface{} // can be a GeoPoint, a GeoHash (string), or a lat/lon pair as float64
17	bottomLeft       interface{}
18	wkt              interface{}
19	typ              string
20	validationMethod string
21	ignoreUnmapped   *bool
22	queryName        string
23}
24
25// NewGeoBoundingBoxQuery creates and initializes a new GeoBoundingBoxQuery.
26func NewGeoBoundingBoxQuery(name string) *GeoBoundingBoxQuery {
27	return &GeoBoundingBoxQuery{
28		name: name,
29	}
30}
31
32// TopLeft position from longitude (left) and latitude (top).
33func (q *GeoBoundingBoxQuery) TopLeft(top, left float64) *GeoBoundingBoxQuery {
34	q.topLeft = []float64{left, top}
35	return q
36}
37
38// TopLeftFromGeoPoint from a GeoPoint.
39func (q *GeoBoundingBoxQuery) TopLeftFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
40	return q.TopLeft(point.Lat, point.Lon)
41}
42
43// TopLeftFromGeoHash from a Geo hash.
44func (q *GeoBoundingBoxQuery) TopLeftFromGeoHash(topLeft string) *GeoBoundingBoxQuery {
45	q.topLeft = topLeft
46	return q
47}
48
49// BottomRight position from longitude (right) and latitude (bottom).
50func (q *GeoBoundingBoxQuery) BottomRight(bottom, right float64) *GeoBoundingBoxQuery {
51	q.bottomRight = []float64{right, bottom}
52	return q
53}
54
55// BottomRightFromGeoPoint from a GeoPoint.
56func (q *GeoBoundingBoxQuery) BottomRightFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
57	return q.BottomRight(point.Lat, point.Lon)
58}
59
60// BottomRightFromGeoHash from a Geo hash.
61func (q *GeoBoundingBoxQuery) BottomRightFromGeoHash(bottomRight string) *GeoBoundingBoxQuery {
62	q.bottomRight = bottomRight
63	return q
64}
65
66// BottomLeft position from longitude (left) and latitude (bottom).
67func (q *GeoBoundingBoxQuery) BottomLeft(bottom, left float64) *GeoBoundingBoxQuery {
68	q.bottomLeft = []float64{bottom, left}
69	return q
70}
71
72// BottomLeftFromGeoPoint from a GeoPoint.
73func (q *GeoBoundingBoxQuery) BottomLeftFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
74	return q.BottomLeft(point.Lat, point.Lon)
75}
76
77// BottomLeftFromGeoHash from a Geo hash.
78func (q *GeoBoundingBoxQuery) BottomLeftFromGeoHash(bottomLeft string) *GeoBoundingBoxQuery {
79	q.bottomLeft = bottomLeft
80	return q
81}
82
83// TopRight position from longitude (right) and latitude (top).
84func (q *GeoBoundingBoxQuery) TopRight(top, right float64) *GeoBoundingBoxQuery {
85	q.topRight = []float64{right, top}
86	return q
87}
88
89// TopRightFromGeoPoint from a GeoPoint.
90func (q *GeoBoundingBoxQuery) TopRightFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
91	return q.TopRight(point.Lat, point.Lon)
92}
93
94// TopRightFromGeoHash from a Geo hash.
95func (q *GeoBoundingBoxQuery) TopRightFromGeoHash(topRight string) *GeoBoundingBoxQuery {
96	q.topRight = topRight
97	return q
98}
99
100// WKT initializes the bounding box from Well-Known Text (WKT),
101// e.g. "BBOX (-74.1, -71.12, 40.73, 40.01)".
102func (q *GeoBoundingBoxQuery) WKT(wkt interface{}) *GeoBoundingBoxQuery {
103	q.wkt = wkt
104	return q
105}
106
107// Type sets the type of executing the geo bounding box. It can be either
108// memory or indexed. It defaults to memory.
109func (q *GeoBoundingBoxQuery) Type(typ string) *GeoBoundingBoxQuery {
110	q.typ = typ
111	return q
112}
113
114// ValidationMethod accepts IGNORE_MALFORMED, COERCE, and STRICT (default).
115// IGNORE_MALFORMED accepts geo points with invalid lat/lon.
116// COERCE tries to infer the correct lat/lon.
117func (q *GeoBoundingBoxQuery) ValidationMethod(method string) *GeoBoundingBoxQuery {
118	q.validationMethod = method
119	return q
120}
121
122// IgnoreUnmapped indicates whether to ignore unmapped fields (and run a
123// MatchNoDocsQuery in place of this).
124func (q *GeoBoundingBoxQuery) IgnoreUnmapped(ignoreUnmapped bool) *GeoBoundingBoxQuery {
125	q.ignoreUnmapped = &ignoreUnmapped
126	return q
127}
128
129// QueryName gives the query a name. It is used for caching.
130func (q *GeoBoundingBoxQuery) QueryName(queryName string) *GeoBoundingBoxQuery {
131	q.queryName = queryName
132	return q
133}
134
135// Source returns JSON for the function score query.
136func (q *GeoBoundingBoxQuery) Source() (interface{}, error) {
137	// {
138	//   "geo_bounding_box" : {
139	//     ...
140	//   }
141	// }
142
143	source := make(map[string]interface{})
144	params := make(map[string]interface{})
145	source["geo_bounding_box"] = params
146
147	box := make(map[string]interface{})
148	if q.wkt != nil {
149		box["wkt"] = q.wkt
150	} else {
151		if q.topLeft != nil {
152			box["top_left"] = q.topLeft
153		}
154		if q.topRight != nil {
155			box["top_right"] = q.topRight
156		}
157		if q.bottomLeft != nil {
158			box["bottom_left"] = q.bottomLeft
159		}
160		if q.bottomRight != nil {
161			box["bottom_right"] = q.bottomRight
162		}
163	}
164	params[q.name] = box
165
166	if q.typ != "" {
167		params["type"] = q.typ
168	}
169	if q.validationMethod != "" {
170		params["validation_method"] = q.validationMethod
171	}
172	if q.ignoreUnmapped != nil {
173		params["ignore_unmapped"] = *q.ignoreUnmapped
174	}
175	if q.queryName != "" {
176		params["_name"] = q.queryName
177	}
178
179	return source, nil
180}
181