diff --git a/examples/src/test/kotlin/GeoTest.kt b/examples/src/test/kotlin/GeoTest.kt new file mode 100644 index 00000000..5a4286a6 --- /dev/null +++ b/examples/src/test/kotlin/GeoTest.kt @@ -0,0 +1,401 @@ + +import com.mongodb.client.model.Filters +import com.mongodb.client.model.Indexes +import com.mongodb.client.model.Projections +import com.mongodb.client.model.Projections.* +import com.mongodb.client.model.geojson.Point +import com.mongodb.client.model.geojson.Polygon +import com.mongodb.client.model.geojson.Position +import com.mongodb.kotlin.client.coroutine.MongoClient +import io.github.cdimascio.dotenv.dotenv +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* +import kotlin.test.* + + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class SearchGeospatialTest { + + // :snippet-start: theater-data-class + data class Theater( + val theaterId: Int, + val location: Location + ) { + data class Location( + val address: Address, + val geo: Point + ) { + data class Address( + val street1: String, + val street2: String? = null, + val city: String, + val state: String, + val zipcode: String + ) + } + } + // :snippet-end: + + // :snippet-start: results-data-class + data class TheaterResults( + val location: Location + ) { + data class Location( + val address: Address + ) { + data class Address( + val city: String + ) + } + } + + // :snippet-end: + + companion object { + val dotenv = dotenv() + val client = MongoClient.create(dotenv["MONGODB_CONNECTION_URI"]) + val database = client.getDatabase("sample_mflix") + val collection = database.getCollection("theaters") + + + @BeforeAll + @JvmStatic + private fun beforeAll() { + runBlocking { + collection.insertMany( + listOf( + Theater( + theaterId = 1467, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "660 Sunrise Hwy", + city = "Baldwin", + state = "NY", + zipcode = "11510" + ), + geo = Point( + Position(-73.612717, 40.65556) + ) + ) + ), + Theater( + theaterId = 1940, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "One Sunrise Mall", + street2 = "#2090", + city = "Massapequa", + state = "NY", + zipcode = "11758" + ), + geo = Point( + Position(-73.436827, 40.679252) + ) + ) + ), + Theater( + theaterId = 1172, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "610 Exterior Street", + city = "Bronx", + state = "NY", + zipcode = "10451" + ), + geo = Point( + Position(-73.9310055, 40.8172327) + ) + ) + ), + Theater( + theaterId = 1448, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "1880 Broadway", + city = "New York", + state = "NY", + zipcode = "10023" + ), + geo = Point( + Position(-73.982094, 40.769882) + ) + ) + ), + Theater( + theaterId = 1531, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + street2 = "#64", + city = "New York", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1267, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Jersey City", + state = "NJ", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1005, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Levittown", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1538, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Secaucus", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1250, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "New York", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1081, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Long Island City", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1221, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Westbury", + state = "NY", + zipcode = "10003" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1535, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + city = "Mount Vernon", + state = "NY", + zipcode = "10550" + ), + geo = Point( + Position(-73.9905737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1581, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "52 E 14th St", + street2 = "#22", + city = "Elmhurst", + state = "NY", + zipcode = "11375" + ), + geo = Point( + Position(-73.9875737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1098, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "108 Queens Blvd", + city = "Elmhurst", + state = "NY", + zipcode = "11375" + ), + geo = Point( + Position(-73.9905737, 40.73492209) + ) + ) + ), + Theater( + theaterId = 1012, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "28 Ulmer St", + city = "Flushing", + state = "NY", + zipcode = "11354" + ), + geo = Point( + Position(-73.95505737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1555, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "190 Horace Harding Expy", + city = "Flushing", + state = "NY", + zipcode = "11365" + ), + geo = Point( + Position(-73.9922737, 40.7349109) + ) + ) + ), + Theater( + theaterId = 1532, + location = Theater.Location( + address = Theater.Location.Address( + street1 = "72 Main St", + city = "Flushing", + state = "NY", + zipcode = "11367" + ), + geo = Point( + Position(-73.9905717, 40.7349109) + ) + ) + ) + ) + ) + } + } + + @AfterAll + @JvmStatic + private fun afterAll() { + runBlocking { + + collection.drop() + client.close() + + } + } + } + + @BeforeEach + fun beforeEach() { runBlocking { + collection.createIndex((Indexes.geo2dsphere("${Theater::location.name}.${Theater.Location::geo.name}"))) + collection.createIndex((Indexes.geo2d("coordinates"))) + }} + + @AfterEach + fun afterEach() { + runBlocking { + collection.dropIndexes() + }} + + @Test + fun indexTest() = runBlocking { + collection.dropIndexes() + // :snippet-start: geo2dsphere-index + collection.createIndex((Indexes.geo2dsphere("location.geo"))) + // :snippet-end: + // :snippet-start: geo2d-index + collection.createIndex((Indexes.geo2d("coordinates"))) + // :snippet-end: + val results = collection.listIndexes().toList() + assertTrue(results.isNotEmpty()) + } + + @Test + fun geospatialQueryTest() = runBlocking { + // :snippet-start: proximity-query + val database = client.getDatabase("sample_mflix") + val collection = database.getCollection("theaters") + val centralPark = Point(Position(-73.9667, 40.78)) + val query = Filters.near( + "${Theater::location.name}.${Theater.Location::geo.name}", centralPark, 10000.0, 5000.0 + ) + val projection = Projections.fields( + Projections.include( + "${Theater::location.name}.${Theater.Location::address.name}.${Theater.Location.Address::city.name}"), + Projections.excludeId() + ) + val resultsFlow = collection.find(query).projection(projection) + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.any { it.location.address.city == "Bronx" }) + } + + @Test + fun queryRangeTest() = runBlocking { + // :snippet-start: query-range + val longIslandTriangle = Polygon( + listOf( + Position(-72.0, 40.0), + Position(-74.0, 41.0), + Position(-72.0, 39.0), + Position(-72.0, 40.0) + ) + ) + val projection = Projections.fields( + Projections.include( + "${Theater::location.name}.${Theater.Location::address.name}.${Theater.Location.Address::city.name}"), + Projections.excludeId() + ) + val geoWithinComparison = Filters.geoWithin( + "${Theater::location.name}.${Theater.Location::geo.name}", longIslandTriangle + ) + val resultsFlow = collection.find(geoWithinComparison) + .projection(projection) + resultsFlow.collect { println(it) } + // :snippet-end: + val results = resultsFlow.toList() + assertTrue(results.any { it.location.address.city == "Massapequa" }) + } +} \ No newline at end of file diff --git a/source/examples/generated/GeoTest.snippet.geo2d-index.kt b/source/examples/generated/GeoTest.snippet.geo2d-index.kt new file mode 100644 index 00000000..682b26f5 --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.geo2d-index.kt @@ -0,0 +1 @@ +collection.createIndex((Indexes.geo2d("coordinates"))) diff --git a/source/examples/generated/GeoTest.snippet.geo2dsphere-index.kt b/source/examples/generated/GeoTest.snippet.geo2dsphere-index.kt new file mode 100644 index 00000000..73ded3bb --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.geo2dsphere-index.kt @@ -0,0 +1 @@ +collection.createIndex((Indexes.geo2dsphere("location.geo"))) diff --git a/source/examples/generated/GeoTest.snippet.proximity-query.kt b/source/examples/generated/GeoTest.snippet.proximity-query.kt new file mode 100644 index 00000000..e244518e --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.proximity-query.kt @@ -0,0 +1,13 @@ +val database = client.getDatabase("sample_mflix") +val collection = database.getCollection("theaters") +val centralPark = Point(Position(-73.9667, 40.78)) +val query = Filters.near( + "${Theater::location.name}.${Theater.Location::geo.name}", centralPark, 10000.0, 5000.0 +) +val projection = Projections.fields( + Projections.include( + "${Theater::location.name}.${Theater.Location::address.name}.${Theater.Location.Address::city.name}"), + Projections.excludeId() +) +val resultsFlow = collection.find(query).projection(projection) +resultsFlow.collect { println(it) } diff --git a/source/examples/generated/GeoTest.snippet.query-range.kt b/source/examples/generated/GeoTest.snippet.query-range.kt new file mode 100644 index 00000000..cad9f39c --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.query-range.kt @@ -0,0 +1,19 @@ +val longIslandTriangle = Polygon( + listOf( + Position(-72.0, 40.0), + Position(-74.0, 41.0), + Position(-72.0, 39.0), + Position(-72.0, 40.0) + ) +) +val projection = Projections.fields( + Projections.include( + "${Theater::location.name}.${Theater.Location::address.name}.${Theater.Location.Address::city.name}"), + Projections.excludeId() +) +val geoWithinComparison = Filters.geoWithin( + "${Theater::location.name}.${Theater.Location::geo.name}", longIslandTriangle +) +val resultsFlow = collection.find(geoWithinComparison) + .projection(projection) +resultsFlow.collect { println(it) } diff --git a/source/examples/generated/GeoTest.snippet.results-data-class.kt b/source/examples/generated/GeoTest.snippet.results-data-class.kt new file mode 100644 index 00000000..a8eb3ecb --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.results-data-class.kt @@ -0,0 +1,12 @@ +data class TheaterResults( + val location: Location +) { + data class Location( + val address: Address + ) { + data class Address( + val city: String + ) + } +} + diff --git a/source/examples/generated/GeoTest.snippet.theater-data-class.kt b/source/examples/generated/GeoTest.snippet.theater-data-class.kt new file mode 100644 index 00000000..7c2409d1 --- /dev/null +++ b/source/examples/generated/GeoTest.snippet.theater-data-class.kt @@ -0,0 +1,17 @@ +data class Theater( + val theaterId: Int, + val location: Location +) { + data class Location( + val address: Address, + val geo: Point + ) { + data class Address( + val street1: String, + val street2: String? = null, + val city: String, + val state: String, + val zipcode: String + ) + } +} diff --git a/source/fundamentals/crud/read-operations/geo.txt b/source/fundamentals/crud/read-operations/geo.txt index d0096366..ad2b09b5 100644 --- a/source/fundamentals/crud/read-operations/geo.txt +++ b/source/fundamentals/crud/read-operations/geo.txt @@ -14,7 +14,7 @@ Overview -------- In this guide, you can learn how to search **geospatial data** with the -MongoDB Java Driver, and the different geospatial data formats supported by MongoDB. +MongoDB Kotlin Driver, and the different geospatial data formats supported by MongoDB. Geospatial data is data that represents a geographical location on the surface of the Earth. Examples of geospatial data include: @@ -42,8 +42,6 @@ Here is the location of MongoDB headquarters in GeoJSON: For definitive information on GeoJSON, see the `official IETF specification `__. -.. external resource - GeoJSON Positions ~~~~~~~~~~~~~~~~~ @@ -83,8 +81,6 @@ Here are some common GeoJSON types and how you can specify them with positions: To learn more about the shapes you can use in MongoDB, see the :manual:`GeoJSON manual entry `. -.. external resource - Index ~~~~~ @@ -92,16 +88,12 @@ To query data stored in the GeoJSON format, add the field containing GeoJSON data to a ``2dsphere`` index. The following snippet creates a ``2dsphere`` index on the ``location.geo`` field using the ``Indexes`` builder: -.. code-block:: java - - // - collection.createIndex(Indexes.geo2dsphere("location.geo")); +.. literalinclude:: /examples/generated/GeoTest.snippet.geo2dsphere-index.kt + :language: kotlin For more information on the ``Indexes`` builder, see our :doc:`guide on the Indexes builder `. -.. guide resource - Coordinates on a 2D Plane ------------------------- @@ -125,10 +117,8 @@ To query data stored as legacy coordinate pairs, you must add the field containi legacy coordinate pairs to a ``2d`` index. The following snippet creates a ``2d`` index on the ``coordinates`` field using the ``Indexes`` builder: -.. code-block:: java - - // - collection.createIndex(Indexes.geo2d("coordinates")); +.. literalinclude:: /examples/generated/GeoTest.snippet.geo2d-index.kt + :language: kotlin For more information on the ``Indexes`` builder, see our :doc:`guide on the Indexes builder `. @@ -136,8 +126,6 @@ For more information on the ``Indexes`` builder, see our For more information on legacy coordinate pairs, see the :manual:`MongoDB server manual page on legacy coordinate pairs `. -.. external resource - .. tip:: Supported Operators Spherical (``2dsphere``) and flat (``2d``) indexes support some, but @@ -145,8 +133,6 @@ For more information on legacy coordinate pairs, see the and their index compatibility, see the :manual:`manual entry for geospatial queries `. - .. external resource - Geospatial Queries ------------------ @@ -163,15 +149,13 @@ To query your geospatial data, use one of the following query operators: - ``$nearSphere`` - ``$geoIntersects`` *requires a 2dsphere index* -You can specify these query operators in the MongoDB Java driver with the +You can specify these query operators in the MongoDB Kotlin driver with the ``near()``, ``geoWithin()``, ``nearSphere()``, and ``geoIntersects()`` utility methods of the ``Filters`` builder class. For more information on geospatial query operators, see the :manual:`manual entry for geospatial queries `. -.. external resource - For more information on ``Filters``, see our :doc:`guide on the Filters builder `. @@ -180,15 +164,13 @@ Query Parameters To specify a shape to use in a geospatial query, use the ``Position``, ``Point``, ``LineString``, and ``Polygon`` classes of the MongoDB -Java driver. +Kotlin driver. -For a full list of the GeoJSON shapes available in the MongoDB Java driver, see the +For a full list of the GeoJSON shapes available in the MongoDB Kotlin driver, see the `GeoJSON package <{+api+}/apidocs/mongodb-driver-core/com/mongodb/client/model/geojson/package-summary.html>`__ API Documentation. -.. external resource - Examples -------- @@ -197,21 +179,34 @@ to set up your own free-tier Atlas cluster and how to load the sample dataset in our :doc:`quick start guide `. The examples use the ``theaters`` collection in the ``sample_mflix`` database -from the sample dataset. The ``theaters`` collection contains a ``2dsphere`` index -on the ``location.geo`` field. +from the sample dataset. The examples require the following imports: -.. literalinclude:: /includes/fundamentals/code-snippets/Geo.java - :language: java - :dedent: - :start-after: begin exampleImports - :end-before: end exampleImports +.. code-block:: + :source: kotlin + + import com.mongodb.client.model.geojson.Point + import com.mongodb.client.model.geojson.Polygon + import com.mongodb.client.model.geojson.Position + import com.mongodb.client.model.Filters.near + import com.mongodb.client.model.Filters.geoWithin + import com.mongodb.client.model.Projections.fields + import com.mongodb.client.model.Projections.include + import com.mongodb.client.model.Projections.excludeId + +The data is modeled using the following Kotlin data class: + +.. literalinclude:: /examples/generated/GeoTest.snippet.theater-data-class.kt + :language: kotlin -You can find the -`source code for the examples on Github here `__. +The results are modeled using the following Kotlin data class: -.. external resource +.. literalinclude:: /examples/generated/GeoTest.snippet.results-data-class.kt + :language: kotlin + +The ``theaters`` collection already contains a ``2dsphere`` index on the +``"${Theater::location.name}.${Theater.Location::geo.name}"`` field. Query by Proximity ~~~~~~~~~~~~~~~~~~ @@ -221,32 +216,28 @@ the ``near()`` static utility method of the ``Filters`` builder class. The ``near()`` method constructs a query with the ``$near`` query operator. The following example queries for theaters between ``10,000`` and ``5,000`` -meters from the -`Great Lawn of Central Park `__. +meters from the Great Lawn of Central Park: -.. literalinclude:: /includes/fundamentals/code-snippets/Geo.java - :language: java - :dedent: - :start-after: begin findExample - :end-before: end findExample +.. io-code-block:: -The output of the code snippet should look something like this: + .. input:: /examples/generated/GeoTest.snippet.proximity-query.kt + :language: kotlin -.. code-block:: json - :copyable: false + .. output:: + :language: console - {"location": {"address": {"city": "Bronx"}}} - {"location": {"address": {"city": "New York"}}} - {"location": {"address": {"city": "New York"}}} - {"location": {"address": {"city": "Long Island City"}}} - {"location": {"address": {"city": "New York"}}} - {"location": {"address": {"city": "Secaucus"}}} - {"location": {"address": {"city": "Jersey City"}}} - {"location": {"address": {"city": "Elmhurst"}}} - {"location": {"address": {"city": "Flushing"}}} - {"location": {"address": {"city": "Flushing"}}} - {"location": {"address": {"city": "Flushing"}}} - {"location": {"address": {"city": "Elmhurst"}}} + TheaterResults(location=Location(address=Address(city=Bronx))) + TheaterResults(location=Location(address=Address(city=New York))) + TheaterResults(location=Location(address=Address(city=New York))) + TheaterResults(location=Location(address=Address(city=Long Island City))) + TheaterResults(location=Location(address=Address(city=New York))) + TheaterResults(location=Location(address=Address(city=Secaucus))) + TheaterResults(location=Location(address=Address(city=Jersey City))) + TheaterResults(location=Location(address=Address(city=Elmhurst))) + TheaterResults(location=Location(address=Address(city=Flushing))) + TheaterResults(location=Location(address=Address(city=Flushing))) + TheaterResults(location=Location(address=Address(city=Flushing))) + TheaterResults(location=Location(address=Address(city=Elmhurst))) .. tip:: Fun Fact @@ -254,11 +245,11 @@ The output of the code snippet should look something like this: :manual:`same reference system ` as GPS satellites to calculate geometries over the Earth. -For more information on the ``$near`` operator, see -:manual:`the reference documentation for $near `. +For more information on the ``$near`` operator, see the +:manual:`reference documentation for $near `. -For more information on ``Filters``, -:doc:`see our guide on the Filters builder `. +For more information on ``Filters``, see +:doc:`our guide on the Filters builder `. Query Within a Range ~~~~~~~~~~~~~~~~~~~~ @@ -271,22 +262,19 @@ The following example searches for movie theaters in a section of Long Island. .. _example_range_query: -.. literalinclude:: /includes/fundamentals/code-snippets/Geo.java - :language: java - :dedent: - :start-after: begin rangeExample - :end-before: end rangeExample +.. io-code-block:: -The output of the code snippet should look something like this: + .. input:: /examples/generated/GeoTest.snippet.query-range.kt + :language: kotlin -.. code-block:: json - :copyable: false + .. output:: + :language: console - {"location": {"address": {"city": "Baldwin"}}} - {"location": {"address": {"city": "Levittown"}}} - {"location": {"address": {"city": "Westbury"}}} - {"location": {"address": {"city": "Mount Vernon"}}} - {"location": {"address": {"city": "Massapequa"}}} + TheaterResults(location=Location(address=Address(city=Baldwin)))) + TheaterResults(location=Location(address=Address(city=Levittown))) + TheaterResults(location=Location(address=Address(city=Westbury))) + TheaterResults(location=Location(address=Address(city=Mount Vernon))) + TheaterResults(location=Location(address=Address(city=Massapequa))) The following figure shows the polygon defined by the ``longIslandTriangle`` variable and dots representing the locations of @@ -298,9 +286,5 @@ the movie theaters returned by our query. For more information on the ``$geoWithin`` operator, see the :manual:`reference documentation for $geoWithin ` -.. external resource - For more information on the operators you can use in your query, see the :manual:`MongoDB server manual page on geospatial query operators ` - -.. external resource