Skip to content

Commit 93bcfe1

Browse files
Spamerczclaude
andcommitted
feat(query): add geo bounding box query
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 94266a7 commit 93bcfe1

3 files changed

Lines changed: 138 additions & 0 deletions

File tree

doc/02-query-objects.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,22 @@ new \Spameri\ElasticQuery\Query\GeoDistance(
441441
);
442442
```
443443

444+
##### GeoBoundingBox Query
445+
Match documents whose geo_point falls inside a top-left/bottom-right rectangle.
446+
- Class: `\Spameri\ElasticQuery\Query\GeoBoundingBox`
447+
- [Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-bounding-box-query.html)
448+
- [Implementation](https://github.com/Spameri/ElasticQuery/blob/master/src/Query/GeoBoundingBox.php)
449+
450+
```php
451+
new \Spameri\ElasticQuery\Query\GeoBoundingBox(
452+
field: 'location',
453+
topLeftLat: 40.73,
454+
topLeftLon: -74.1,
455+
bottomRightLat: 40.01,
456+
bottomRightLon: -71.12,
457+
);
458+
```
459+
444460
---
445461

446462
## Boolean Query Collections

src/Query/GeoBoundingBox.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Spameri\ElasticQuery\Query;
6+
7+
/**
8+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-bounding-box-query.html
9+
*/
10+
class GeoBoundingBox implements \Spameri\ElasticQuery\Query\LeafQueryInterface
11+
{
12+
13+
public function __construct(
14+
private string $field,
15+
private float $topLeftLat,
16+
private float $topLeftLon,
17+
private float $bottomRightLat,
18+
private float $bottomRightLon,
19+
private string|null $type = null,
20+
)
21+
{
22+
}
23+
24+
25+
public function key(): string
26+
{
27+
return 'geo_bounding_box_' . $this->field;
28+
}
29+
30+
31+
/**
32+
* @return array<string, array<string, mixed>>
33+
*/
34+
public function toArray(): array
35+
{
36+
$body = [
37+
$this->field => [
38+
'top_left' => [
39+
'lat' => $this->topLeftLat,
40+
'lon' => $this->topLeftLon,
41+
],
42+
'bottom_right' => [
43+
'lat' => $this->bottomRightLat,
44+
'lon' => $this->bottomRightLon,
45+
],
46+
],
47+
];
48+
49+
if ($this->type !== null) {
50+
$body['type'] = $this->type;
51+
}
52+
53+
return [
54+
'geo_bounding_box' => $body,
55+
];
56+
}
57+
58+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SpameriTests\ElasticQuery\Query;
4+
5+
require_once __DIR__ . '/../../bootstrap.php';
6+
7+
8+
class GeoBoundingBox extends \Tester\TestCase
9+
{
10+
11+
private const INDEX = 'spameri_test_query_geo_bounding_box';
12+
13+
14+
public function setUp(): void
15+
{
16+
$ch = \curl_init();
17+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
18+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
19+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'PUT');
20+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
21+
22+
\curl_exec($ch);
23+
}
24+
25+
26+
public function testToArray(): void
27+
{
28+
$gbb = new \Spameri\ElasticQuery\Query\GeoBoundingBox(
29+
field: 'location',
30+
topLeftLat: 40.73,
31+
topLeftLon: -74.1,
32+
bottomRightLat: 40.01,
33+
bottomRightLon: -71.12,
34+
);
35+
36+
$array = $gbb->toArray();
37+
38+
\Tester\Assert::same(40.73, $array['geo_bounding_box']['location']['top_left']['lat']);
39+
\Tester\Assert::same(-71.12, $array['geo_bounding_box']['location']['bottom_right']['lon']);
40+
}
41+
42+
43+
public function testKey(): void
44+
{
45+
$gbb = new \Spameri\ElasticQuery\Query\GeoBoundingBox('location', 1, 1, 0, 0);
46+
47+
\Tester\Assert::same('geo_bounding_box_location', $gbb->key());
48+
}
49+
50+
51+
public function tearDown(): void
52+
{
53+
$ch = \curl_init();
54+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
55+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
56+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
57+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
58+
59+
\curl_exec($ch);
60+
}
61+
62+
}
63+
64+
(new GeoBoundingBox())->run();

0 commit comments

Comments
 (0)