diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/Store.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/Store.java new file mode 100644 index 000000000..e144ef803 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/Store.java @@ -0,0 +1,51 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.mongodb.customer; + +import lombok.Data; +import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.util.Assert; + +/** + * An entity to represent a Store with a service area. + * + * @author Rishabh Saraswat + */ +@Data +@Document +public class Store { + + private String id; + private final String name; + @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) + private final GeoJsonPolygon serviceArea; // unlike Polygon, GeoJsonPolygon is closed boundary + + /** + * Creates a new {@link Store} with the given name and service area. + * + * @param name must not be {@literal null} or empty. + * @param serviceArea must not be {@literal null}. + */ + public Store(String name, GeoJsonPolygon serviceArea) { + Assert.hasText(name, "Name must not be empty"); + Assert.notNull(serviceArea, "Service area must not be null"); + this.name = name; + this.serviceArea = serviceArea; + } +} \ No newline at end of file diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/StoreRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/StoreRepository.java new file mode 100644 index 000000000..132490639 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/StoreRepository.java @@ -0,0 +1,11 @@ +package example.springdata.mongodb.customer; + +import org.springframework.data.repository.CrudRepository; + +/** + * Repository interface for {@link Store} instances. + * + * @author Rishabh Saraswat + */ +interface StoreRepository extends CrudRepository{ +} diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java index 5a3e76dd6..576aa83e8 100644 --- a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java +++ b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,43 +15,47 @@ */ package example.springdata.mongodb.customer; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.data.Offset.offset; - import example.springdata.mongodb.util.MongoContainers; - -import java.util.stream.Stream; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.data.domain.Limit; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; +import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.querydsl.QSort; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; - import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; + /** * Integration test for {@link CustomerRepository}. * * @author Oliver Gierke + * @author Rishabh Saraswat */ @Testcontainers @DataMongoTest class CustomerRepositoryIntegrationTest { @Container // - private static MongoDBContainer mongoDBContainer = MongoContainers.getDefaultContainer(); + private static final MongoDBContainer mongoDBContainer = MongoContainers.getDefaultContainer(); @DynamicPropertySource static void setProperties(DynamicPropertyRegistry registry) { @@ -60,17 +64,23 @@ static void setProperties(DynamicPropertyRegistry registry) { @Autowired CustomerRepository repository; @Autowired MongoOperations operations; + @Autowired StoreRepository storeRepository; private Customer dave, oliver, carter; + private Store store; @BeforeEach void setUp() { repository.deleteAll(); + GeoJsonPolygon polygon = new GeoJsonPolygon(new Point(0.0, 0.0), new Point(0.0, 1.0), new Point(1.0, 1.0), new Point(1.0, 0.0), new Point(0.0, 0.0)); + dave = repository.save(new Customer("Dave", "Matthews")); oliver = repository.save(new Customer("Oliver August", "Matthews")); carter = repository.save(new Customer("Carter", "Beauford")); + + store = storeRepository.save(new Store("store-1", polygon)); } /** @@ -146,4 +156,28 @@ void exposesGeoSpatialFunctionality() { assertThat(distanceToFirstStore.getMetric()).isEqualTo(Metrics.KILOMETERS); assertThat(distanceToFirstStore.getValue()).isCloseTo(0.862, offset(0.001)); } + + /** + * Test case to show the usage of the geospatial operator {@code $geoIntersects}. + */ + @Test + void supportsGeoIntersectsPointInside() { + operations.indexOps(Store.class).createIndex(new GeospatialIndex("serviceArea")); + GeoJsonPoint pointInside = new GeoJsonPoint(0.5, 0.5); + Query pointInsideQuery = Query.query(Criteria.where("serviceArea").intersects(pointInside)); + + List stores = operations.find(pointInsideQuery, Store.class); + assertThat(stores).hasSize(1); + assertThat(stores.get(0).getName()).isEqualTo("store-1"); + } + + @Test + void supportsGeoIntersectsPointOutside() { + operations.indexOps(Store.class).createIndex(new GeospatialIndex("serviceArea")); + GeoJsonPoint pointOutside = new GeoJsonPoint(0.5, 0.5); + Query pointOutsideQuery = Query.query(Criteria.where("serviceArea").intersects(pointOutside)); + + List stores = operations.find(pointOutsideQuery, Store.class); + assertThat(stores).isEmpty(); + } }