use luci::index::Index;
use luci::mapping::{FieldType, Mapping};
use serde_json::json;
fn search(
index: &mut luci::index::Index,
query: serde_json::Value,
size: usize,
) -> luci::search::results::SearchResults {
let expr = luci::search::expression::parse_search(query, size).unwrap();
index.search(&expr).unwrap()
}
fn test_dir(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("luci_geo_shape_{}_{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
dir
}
fn cleanup(path: &std::path::Path) {
let _ = std::fs::remove_dir_all(path);
}
fn build_shape_index(name: &str) -> (std::path::PathBuf, Index) {
let path = test_dir(name);
let schema = Mapping::builder()
.field("name", FieldType::Keyword)
.field("boundary", FieldType::GeoShape)
.build();
let index = Index::create_with_mapping(&path, schema).unwrap();
index
.add(json!({
"name": "nyc",
"boundary": {
"type": "Polygon",
"coordinates": [[
[-74.05, 40.68], [-73.90, 40.68], [-73.90, 40.82],
[-74.05, 40.82], [-74.05, 40.68]
]]
}
}))
.unwrap();
index
.add(json!({
"name": "london",
"boundary": {
"type": "Polygon",
"coordinates": [[
[-0.30, 51.40], [0.10, 51.40], [0.10, 51.60],
[-0.30, 51.60], [-0.30, 51.40]
]]
}
}))
.unwrap();
index
.add(json!({
"name": "paris",
"boundary": {
"type": "Polygon",
"coordinates": [[
[2.20, 48.80], [2.50, 48.80], [2.50, 48.92],
[2.20, 48.92], [2.20, 48.80]
]]
}
}))
.unwrap();
index
.add(json!({
"name": "empire_state",
"boundary": {
"type": "Point",
"coordinates": [-73.9857, 40.7484]
}
}))
.unwrap();
(path, index)
}
#[test]
fn geo_shape_intersects() {
let (path, mut index) = build_shape_index("intersects");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "Polygon",
"coordinates": [[
[-74.00, 40.74], [-73.96, 40.74], [-73.96, 40.76],
[-74.00, 40.76], [-74.00, 40.74]
]]
},
"relation": "intersects"
}
}
}
}),
10,
);
let names: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("name")?.as_str().map(String::from))
.collect();
assert!(
names.contains(&"nyc".to_string()),
"NYC should intersect: {names:?}"
);
assert!(
names.contains(&"empire_state".to_string()),
"Empire State point should intersect: {names:?}"
);
assert!(
!names.contains(&"london".to_string()),
"London should NOT intersect: {names:?}"
);
assert!(
!names.contains(&"paris".to_string()),
"Paris should NOT intersect: {names:?}"
);
cleanup(&path);
}
#[test]
fn geo_shape_disjoint() {
let (path, mut index) = build_shape_index("disjoint");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "Polygon",
"coordinates": [[
[-74.00, 40.74], [-73.96, 40.74], [-73.96, 40.76],
[-74.00, 40.76], [-74.00, 40.74]
]]
},
"relation": "disjoint"
}
}
}
}),
10,
);
let names: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("name")?.as_str().map(String::from))
.collect();
assert!(
names.contains(&"london".to_string()),
"London should be disjoint: {names:?}"
);
assert!(
names.contains(&"paris".to_string()),
"Paris should be disjoint: {names:?}"
);
assert!(
!names.contains(&"nyc".to_string()),
"NYC should NOT be disjoint: {names:?}"
);
cleanup(&path);
}
#[test]
fn geo_shape_within() {
let (path, mut index) = build_shape_index("within");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "Polygon",
"coordinates": [[
[-75.0, 40.0], [-75.0, 52.0], [3.0, 52.0],
[3.0, 40.0], [-75.0, 40.0]
]]
},
"relation": "within"
}
}
}
}),
10,
);
let names: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("name")?.as_str().map(String::from))
.collect();
assert!(
names.contains(&"nyc".to_string()),
"NYC should be within: {names:?}"
);
assert!(
names.contains(&"london".to_string()),
"London should be within: {names:?}"
);
assert!(
names.contains(&"paris".to_string()),
"Paris should be within: {names:?}"
);
assert!(
names.contains(&"empire_state".to_string()),
"Empire State should be within: {names:?}"
);
cleanup(&path);
}
#[test]
fn geo_shape_contains() {
let (path, mut index) = build_shape_index("contains");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "Point",
"coordinates": [-73.98, 40.75]
},
"relation": "contains"
}
}
}
}),
10,
);
let names: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("name")?.as_str().map(String::from))
.collect();
assert!(
names.contains(&"nyc".to_string()),
"NYC polygon should contain the point: {names:?}"
);
assert!(
!names.contains(&"london".to_string()),
"London should NOT contain it: {names:?}"
);
cleanup(&path);
}
#[test]
fn geo_shape_envelope() {
let (path, mut index) = build_shape_index("envelope");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "envelope",
"coordinates": [[-74.1, 40.85], [-73.85, 40.65]]
},
"relation": "intersects"
}
}
}
}),
10,
);
let names: Vec<String> = results
.iter()
.filter_map(|h| h.source()?.get("name")?.as_str().map(String::from))
.collect();
assert!(
names.contains(&"nyc".to_string()),
"NYC should intersect envelope: {names:?}"
);
assert!(
names.contains(&"empire_state".to_string()),
"Empire State should intersect envelope: {names:?}"
);
cleanup(&path);
}
#[test]
fn geo_shape_default_relation() {
let (path, mut index) = build_shape_index("default_relation");
let results = search(
&mut index,
json!({
"query": {
"geo_shape": {
"boundary": {
"shape": {
"type": "Point",
"coordinates": [-73.98, 40.75]
}
}
}
}
}),
10,
);
assert!(results.total_hits().value >= 1);
cleanup(&path);
}