xapian-core  2.0.0
api_geospatial.cc
Go to the documentation of this file.
1 
4 /* Copyright 2008 Lemur Consulting Ltd
5  * Copyright 2010,2011 Richard Boulton
6  * Copyright 2012,2016 Olly Betts
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, see
20  * <https://www.gnu.org/licenses/>.
21  */
22 
23 #include <config.h>
24 #include "api_geospatial.h"
25 #include <xapian.h>
26 
27 #include "apitest.h"
28 #include "testsuite.h"
29 #include "testutils.h"
30 
31 using namespace std;
32 using namespace Xapian;
33 
34 // #######################################################################
35 // # Tests start here
36 
37 static void
39 {
40  Xapian::LatLongCoord coord1(10, 10);
41  Xapian::LatLongCoord coord2(20, 10);
42  Xapian::LatLongCoord coord3(30, 10);
43 
44  Xapian::Document doc;
45  doc.add_value(0, coord1.serialise());
46  db.add_document(doc);
47 
48  doc = Xapian::Document();
49  doc.add_value(0, coord2.serialise());
50  db.add_document(doc);
51 
52  doc = Xapian::Document();
53  doc.add_value(0, coord3.serialise());
54  db.add_document(doc);
55 }
56 
58 DEFINE_TESTCASE(latlongpostingsource1, backend && !remote && !inmemory) {
59  Xapian::Database db = get_database("coords1", builddb_coords1, "");
60  Xapian::LatLongCoord coord1(10, 10);
61  Xapian::LatLongCoord coord2(20, 10);
62  Xapian::LatLongCoord coord3(30, 10);
63 
65  Xapian::LatLongCoords centre;
66  centre.append(coord1);
67  double coorddist = metric(coord1, coord2);
68  TEST_EQUAL_DOUBLE(coorddist, metric(coord2, coord3));
69 
70  // Test a search with no range restriction.
71  {
72  Xapian::LatLongDistancePostingSource ps(0, coord1, metric);
73  ps.reset(db, 0);
74 
75  ps.next(0.0);
76  TEST_EQUAL(ps.at_end(), false);
77  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
78  TEST_EQUAL(ps.get_docid(), 1);
79 
80  ps.next(0.0);
81  TEST_EQUAL(ps.at_end(), false);
82  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
83  TEST_EQUAL(ps.get_docid(), 2);
84 
85  ps.next(0.0);
86  TEST_EQUAL(ps.at_end(), false);
87  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist * 2));
88  TEST_EQUAL(ps.get_docid(), 3);
89 
90  ps.next(0.0);
91  TEST_EQUAL(ps.at_end(), true);
92  }
93 
94  // Test a search with no range restriction and implicit metric.
95  {
97  ps.reset(db, 0);
98 
99  ps.next(0.0);
100  TEST_EQUAL(ps.at_end(), false);
101  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
102  TEST_EQUAL(ps.get_docid(), 1);
103 
104  ps.next(0.0);
105  TEST_EQUAL(ps.at_end(), false);
106  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
107  TEST_EQUAL(ps.get_docid(), 2);
108 
109  ps.next(0.0);
110  TEST_EQUAL(ps.at_end(), false);
111  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist * 2));
112  TEST_EQUAL(ps.get_docid(), 3);
113 
114  ps.next(0.0);
115  TEST_EQUAL(ps.at_end(), true);
116  }
117 
118  // Test a search with a tight range restriction
119  {
120  Xapian::LatLongDistancePostingSource ps(0, centre, metric, coorddist * 0.5);
121  ps.reset(db, 0);
122 
123  ps.next(0.0);
124  TEST_EQUAL(ps.at_end(), false);
125  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
126 
127  ps.next(0.0);
128  TEST_EQUAL(ps.at_end(), true);
129  }
130 
131  // Test a search with a tight range restriction and implicit metric.
132  {
133  Xapian::LatLongDistancePostingSource ps(0, centre, coorddist * 0.5);
134  ps.reset(db, 0);
135 
136  ps.next(0.0);
137  TEST_EQUAL(ps.at_end(), false);
138  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
139 
140  ps.next(0.0);
141  TEST_EQUAL(ps.at_end(), true);
142  }
143 
144  // Test a search with a looser range restriction
145  {
146  Xapian::LatLongDistancePostingSource ps(0, centre, metric, coorddist);
147  ps.reset(db, 0);
148 
149  ps.next(0.0);
150  TEST_EQUAL(ps.at_end(), false);
151  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
152 
153  ps.next(0.0);
154  TEST_EQUAL(ps.at_end(), false);
155  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
156  TEST_EQUAL(ps.get_docid(), 2);
157 
158  ps.next(0.0);
159  TEST_EQUAL(ps.at_end(), true);
160  }
161 
162  // Test a search with a looser range restriction and implicit metric.
163  {
164  Xapian::LatLongDistancePostingSource ps(0, centre, coorddist);
165  ps.reset(db, 0);
166 
167  ps.next(0.0);
168  TEST_EQUAL(ps.at_end(), false);
169  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
170 
171  ps.next(0.0);
172  TEST_EQUAL(ps.at_end(), false);
173  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
174  TEST_EQUAL(ps.get_docid(), 2);
175 
176  ps.next(0.0);
177  TEST_EQUAL(ps.at_end(), true);
178  }
179 
180  // Test a search with a looser range restriction, but not enough to return
181  // the next document.
182  {
183  Xapian::LatLongDistancePostingSource ps(0, centre, metric, coorddist * 1.5);
184  ps.reset(db, 0);
185 
186  ps.next(0.0);
187  TEST_EQUAL(ps.at_end(), false);
188  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
189 
190  ps.next(0.0);
191  TEST_EQUAL(ps.at_end(), false);
192  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
193  TEST_EQUAL(ps.get_docid(), 2);
194 
195  ps.next(0.0);
196  TEST_EQUAL(ps.at_end(), true);
197  }
198 
199  // Test a search with a looser range restriction, but not enough to return
200  // the next document and implicit metric.
201  {
202  Xapian::LatLongDistancePostingSource ps(0, centre, coorddist * 1.5);
203  ps.reset(db, 0);
204 
205  ps.next(0.0);
206  TEST_EQUAL(ps.at_end(), false);
207  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
208 
209  ps.next(0.0);
210  TEST_EQUAL(ps.at_end(), false);
211  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
212  TEST_EQUAL(ps.get_docid(), 2);
213 
214  ps.next(0.0);
215  TEST_EQUAL(ps.at_end(), true);
216  }
217 
218  // Test a search with a loose enough range restriction that all docs should
219  // be returned.
220  {
221  Xapian::LatLongDistancePostingSource ps(0, centre, metric, coorddist * 2.5);
222  ps.reset(db, 0);
223 
224  ps.next(0.0);
225  TEST_EQUAL(ps.at_end(), false);
226  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
227 
228  ps.next(0.0);
229  TEST_EQUAL(ps.at_end(), false);
230  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
231  TEST_EQUAL(ps.get_docid(), 2);
232 
233  ps.next(0.0);
234  TEST_EQUAL(ps.at_end(), false);
235  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist * 2));
236  TEST_EQUAL(ps.get_docid(), 3);
237 
238  ps.next(0.0);
239  TEST_EQUAL(ps.at_end(), true);
240  }
241 
242  // Test a search with a loose enough range restriction that all docs should
243  // be returned and implicit metric.
244  {
245  Xapian::LatLongDistancePostingSource ps(0, centre, coorddist * 2.5);
246  ps.reset(db, 0);
247 
248  ps.next(0.0);
249  TEST_EQUAL(ps.at_end(), false);
250  TEST_EQUAL_DOUBLE(ps.get_weight(), 1.0);
251 
252  ps.next(0.0);
253  TEST_EQUAL(ps.at_end(), false);
254  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist));
255  TEST_EQUAL(ps.get_docid(), 2);
256 
257  ps.next(0.0);
258  TEST_EQUAL(ps.at_end(), false);
259  TEST_EQUAL_DOUBLE(ps.get_weight(), 1000.0 / (1000.0 + coorddist * 2));
260  TEST_EQUAL(ps.get_docid(), 3);
261 
262  ps.next(0.0);
263  TEST_EQUAL(ps.at_end(), true);
264  }
265 }
266 
267 // Test various methods of LatLongCoord and LatLongCoords
268 DEFINE_TESTCASE(latlongcoords1, !backend) {
269  LatLongCoord c1(0, 0);
270  LatLongCoord c2(1, 0);
271  LatLongCoord c3(1, 0);
272  LatLongCoord c4(0, 1);
273 
274  // Test comparison
276  // Exactly one of these inequalities should be true.
277  TEST((c1 < c2) ^ (c2 < c1));
279  TEST(!(c2 < c3) && !(c3 < c2));
281  // Exactly one of these inequalities should be true. This is a regression
282  // test for bug found prior to 1.3.0.
283  TEST((c3 < c4) ^ (c4 < c3));
284 
285  // Test serialisation
286  std::string s1 = c1.serialise();
287  LatLongCoord c5;
288  c4.unserialise(s1);
289  TEST(!(c1 < c4 || c4 < c1));
290  const char * ptr = s1.data();
291  const char * end = ptr + s1.size();
292  c5.unserialise(&ptr, end);
294  TEST_EQUAL(c1.get_description(), "Xapian::LatLongCoord(0, 0)");
295  TEST_EQUAL(ptr, end);
296 
297  // Test uninitialised iterator constructor
299 
300  // Test building a set of LatLongCoords
301  LatLongCoords g1(c1);
302  TEST(!g1.empty());
303  TEST_EQUAL(g1.size(), 1);
304  TEST_EQUAL(g1.get_description(), "Xapian::LatLongCoords((0, 0))");
305  g1.append(c2);
306  TEST_EQUAL(g1.size(), 2);
307  TEST_EQUAL(g1.get_description(), "Xapian::LatLongCoords((0, 0), (1, 0))");
308 
309  // Test iterating through a set of LatLongCoords
310  i1 = g1.begin();
311  TEST(i1 != g1.end());
312  TEST_EQUAL((*i1).serialise(), c1.serialise());
313  TEST_EQUAL((*i1).serialise(), c1.serialise());
314  ++i1;
315  TEST(i1 != g1.end());
316  TEST_EQUAL((*i1).serialise(), c2.serialise());
317  i1 = g1.begin();
318  ++i1;
319  TEST_EQUAL((*i1).serialise(), c2.serialise());
320  TEST(i1 != g1.end());
321  ++i1;
322  TEST(i1 == g1.end());
323 
324  // Test that duplicates are allowed in the list of coordinates, now.
325  g1.append(c3);
326  TEST_EQUAL(g1.size(), 3);
327  TEST_EQUAL(g1.get_description(), "Xapian::LatLongCoords((0, 0), (1, 0), (1, 0))");
328 
329  // Test building an empty LatLongCoords
330  LatLongCoords g2;
331  TEST(g2.empty());
332  TEST_EQUAL(g2.size(), 0);
333  TEST_EQUAL(g2.get_description(), "Xapian::LatLongCoords()");
334  TEST(g2.begin() == g2.end());
335 }
336 
337 // Test various methods of LatLongMetric
338 DEFINE_TESTCASE(latlongmetric1, !backend) {
339  LatLongCoord c1(0, 0);
340  LatLongCoord c2(1, 0);
342  double d1 = m1(c1, c2);
343  TEST_REL(d1, >, 111226.0);
344  TEST_REL(d1, <, 111227.0);
345 
346  // Let's make another metric, this time using the radius of mars, so
347  // distances should be quite a bit smaller.
348  Xapian::GreatCircleMetric m2(3310000);
349  double d2 = m2(c1, c2);
350  TEST_REL(d2, >, 57770.0);
351  TEST_REL(d2, <, 57771.0);
352 
353  // Check serialise and unserialise.
354  Xapian::Registry registry;
355  std::string s1 = m2.serialise();
356  const Xapian::LatLongMetric * m3;
357  m3 = registry.get_lat_long_metric(m2.name());
358  TEST(m3 != NULL);
359  m3 = m3->unserialise(s1);
360  double d3 = (*m3)(c1, c2);
361  TEST_EQUAL_DOUBLE(d2, d3);
362 
363  delete m3;
364 }
365 
366 // Test LatLongMetric on lists of coords.
367 DEFINE_TESTCASE(latlongmetric2, !backend) {
368  LatLongCoord c1(0, 0);
369  LatLongCoord c2(1, 0);
370  LatLongCoords cl1(c1);
371  LatLongCoords cl2(c2);
372  string c2_str = c2.serialise();
373  string cl2_str = cl2.serialise();
374  TEST_EQUAL(c2_str, cl2_str);
375 
376  LatLongCoord c2_check(5, 5);
377  c2_check.unserialise(c2_str);
378  TEST_EQUAL(c2_check.latitude, c2.latitude);
379  TEST_EQUAL(c2_check.longitude, c2.longitude);
380 
382  double d1 = m1(c1, c2);
383  double dl1 = m1(cl1, cl2);
384  TEST_EQUAL(d1, dl1);
385  double d1_str = m1(cl1, c2_str);
386  TEST_EQUAL(d1, d1_str);
387 }
388 
389 // Test a LatLongDistanceKeyMaker directly.
390 DEFINE_TESTCASE(latlongkeymaker1, !backend) {
391  Xapian::GreatCircleMetric m1(3310000);
392  LatLongCoord c1(0, 0);
393  LatLongCoord c2(1, 0);
394  LatLongCoord c3(2, 0);
395  LatLongCoord c4(3, 0);
396 
397  LatLongCoords g1(c1);
398  g1.append(c2);
399 
400  LatLongDistanceKeyMaker keymaker(0, g1, m1);
401  Xapian::Document doc1;
402  doc1.add_value(0, g1.serialise());
403  Xapian::Document doc2;
404  doc2.add_value(0, c3.serialise());
405  Xapian::Document doc3;
406  doc3.add_value(0, c4.serialise());
407  Xapian::Document doc4;
408 
409  std::string k1 = keymaker(doc1);
410  std::string k2 = keymaker(doc2);
411  std::string k3 = keymaker(doc3);
412  std::string k4 = keymaker(doc4);
413  TEST_REL(k1, <, k2);
414  TEST_REL(k2, <, k3);
415  TEST_REL(k3, <, k4);
416 
417  LatLongDistanceKeyMaker keymaker2(0, g1, m1, 0);
418  std::string k3b = keymaker2(doc3);
419  std::string k4b = keymaker2(doc4);
420  TEST_EQUAL(k3, k3b);
421  TEST_REL(k3b, >, k4b);
422 }
DEFINE_TESTCASE(latlongpostingsource1, backend &&!remote &&!inmemory)
Test behaviour of the LatLongDistancePostingSource.
static void builddb_coords1(Xapian::WritableDatabase &db, const string &)
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:47
test functionality of the Xapian API
An indexed database of documents.
Definition: database.h:75
Class representing a document.
Definition: document.h:64
void add_value(Xapian::valueno slot, std::string_view value)
Add a value to a slot in this document.
Definition: document.cc:191
Calculate the great-circle distance between two coordinates on a sphere.
Definition: geospatial.h:398
std::string serialise() const
Serialise object parameters into a string.
std::string name() const
Return the full name of the metric.
An iterator across the values in a LatLongCoords object.
Definition: geospatial.h:164
A sequence of latitude-longitude coordinates.
Definition: geospatial.h:231
size_t size() const
Get the number of coordinates in the container.
Definition: geospatial.h:247
LatLongCoordsIterator begin() const
Get a begin iterator for the coordinates.
Definition: geospatial.h:237
void append(const LatLongCoord &coord)
Append a coordinate to the end of the sequence.
Definition: geospatial.h:259
std::string serialise() const
Return a serialised form of the coordinate list.
LatLongCoordsIterator end() const
Get an end iterator for the coordinates.
Definition: geospatial.h:242
bool empty() const
Return true if and only if there are no coordinates in the container.
Definition: geospatial.h:253
std::string get_description() const
Return a string describing this object.
KeyMaker subclass which sorts by distance from a latitude/longitude.
Definition: geospatial.h:550
Posting source which returns a weight based on geospatial distance.
Definition: geospatial.h:454
void next(double min_wt)
Advance the current position to the next matching document.
double get_weight() const
Return the weight contribution for the current document.
void reset(const Database &db_, Xapian::doccount shard_index)
Set this PostingSource to the start of the list of postings.
Base class for calculating distances between two lat/long coordinates.
Definition: geospatial.h:302
virtual LatLongMetric * unserialise(const std::string &serialised) const =0
Create object given string serialisation returned by serialise().
Registry for user subclasses.
Definition: registry.h:47
const Xapian::LatLongMetric * get_lat_long_metric(std::string_view name) const
Get a lat-long metric given a name.
Definition: registry.cc:359
bool at_end() const
Return true if the current position is past the last entry in this list.
Xapian::docid get_docid() const
Return the current docid.
This class provides read/write access to a database.
Definition: database.h:964
Xapian::docid add_document(const Xapian::Document &doc)
Add a document to the database.
Definition: database.cc:561
The Xapian namespace contains public interfaces for the Xapian library.
Definition: compactor.cc:82
A latitude-longitude coordinate.
Definition: geospatial.h:81
std::string get_description() const
Return a string describing this object.
Definition: latlongcoord.cc:84
void unserialise(std::string_view serialised)
Unserialise a string and set this object to its coordinate.
Definition: latlongcoord.cc:48
double latitude
A latitude, as decimal degrees.
Definition: geospatial.h:88
double longitude
A longitude, as decimal degrees.
Definition: geospatial.h:98
std::string serialise() const
Return a serialised representation of the coordinate.
Definition: latlongcoord.cc:76
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:35
a generic test suite engine
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:276
#define TEST_EQUAL_DOUBLE(a, b)
Test two doubles for near equality.
Definition: testsuite.h:293
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:273
#define TEST_NOT_EQUAL(a, b)
Test for non-equality of two things.
Definition: testsuite.h:303
Xapian-specific test helper functions and macros.
Public interfaces for the Xapian library.