diff --git a/examples/kotlin/settings.gradle.kts b/examples/kotlin/settings.gradle.kts index 6ab54d41b1f..750af5ee1fc 100644 --- a/examples/kotlin/settings.gradle.kts +++ b/examples/kotlin/settings.gradle.kts @@ -9,7 +9,7 @@ pluginManagement { dependencyResolutionManagement { versionCatalogs { create("libs") { - version("realm", "1.10.0") + version("realm", "1.11.0-SNAPSHOT") version("kotlinx-coroutines", "1.7.0") version("kotlinx-serialization", "1.5.0") library("realm-plugin", "io.realm.kotlin", "gradle-plugin").versionRef("realm") diff --git a/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/Geospatial.kt b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/Geospatial.kt new file mode 100644 index 00000000000..6e15b928420 --- /dev/null +++ b/examples/kotlin/shared/src/commonTest/kotlin/com/mongodb/realm/realmkmmapp/Geospatial.kt @@ -0,0 +1,195 @@ +package com.mongodb.realm.realmkmmapp + +import io.realm.kotlin.Realm +import io.realm.kotlin.RealmConfiguration +import io.realm.kotlin.annotations.ExperimentalGeoSpatialApi +import io.realm.kotlin.ext.query +import io.realm.kotlin.ext.realmListOf +import io.realm.kotlin.internal.platform.runBlocking +import io.realm.kotlin.types.EmbeddedRealmObject +import io.realm.kotlin.types.RealmList +import io.realm.kotlin.types.RealmObject +import io.realm.kotlin.types.annotations.Ignore +import io.realm.kotlin.types.annotations.PrimaryKey +import io.realm.kotlin.types.geo.* +import org.mongodb.kbson.ObjectId +import kotlin.test.Test +import kotlin.test.assertEquals + + +class Geospatial : RealmTest() { + + // :snippet-start: geopoint-model + class Company : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var location: CustomGeoPoint? = null + } + // :snippet-end: + + // :snippet-start: custom-geopoint + class CustomGeoPoint : EmbeddedRealmObject { + constructor(latitude: Double, longitude: Double) { + coordinates.apply { + add(longitude) + add(latitude) + } + } + // Empty constructor required by Realm + constructor() : this(0.0, 0.0) + + // Name and type required by Realm + var coordinates: RealmList = realmListOf() + + // Name, type, and value required by Realm + private var type: String = "Point" + + @Ignore + var latitude: Double + get() = coordinates[1] + set(value) { + coordinates[1] = value + } + + @Ignore + var longitude: Double + get() = coordinates[0] + set(value) { + coordinates[0] = value + } + } + // :snippet-end: + + @OptIn(ExperimentalGeoSpatialApi::class) + @Test + fun geospatialTest() { + val REALM_NAME = getRandom() + runBlocking { + val config = RealmConfiguration.Builder(schema = setOf(Company::class, CustomGeoPoint::class)) + .directory(TMP_PATH) + .name(REALM_NAME) + .build() + val realm = Realm.open(config) + + // :snippet-start: create-geopoint + realm.writeBlocking { + copyToRealm( + Company().apply { + location = CustomGeoPoint(47.68, -122.35) + } + ) + copyToRealm( + Company().apply { + location = CustomGeoPoint(47.9, -121.85) + } + ) + } + // :snippet-end: + // :snippet-start: geocircle + val circle1 = GeoCircle.create( + center = GeoPoint.create(47.8, -122.6), + radius = Distance.fromKilometers(44.4) + ) + val circle2 = GeoCircle.create( + center = GeoPoint.create(47.3, -121.9), + radius = Distance.fromDegrees(0.25) + ) + // :snippet-end: + // :snippet-start: geobox + val box1 = GeoBox.Companion.create( + bottomLeft = GeoPoint.create(47.3, -122.7), + topRight = GeoPoint.create(48.1, -122.1) + ) + val box2 = GeoBox.Companion.create( + bottomLeft = GeoPoint.create(47.5, -122.4), + topRight = GeoPoint.create(47.9, -121.8) + ) + // :snippet-end: + + // :snippet-start: geopolygon + // Create a basic polygon + val basicPolygon = GeoPolygon.create( + listOf( + GeoPoint.create(48.0, -122.8), + GeoPoint.create(48.2, -121.8), + GeoPoint.create(47.6, -121.6), + GeoPoint.create(47.0, -122.0), + GeoPoint.create(47.2, -122.6), + GeoPoint.create(48.0, -122.8) + ) + ) + + // Create a polygon with a single hole + val outerRing = listOf( + GeoPoint.create(48.0, -122.8), + GeoPoint.create(48.2, -121.8), + GeoPoint.create(47.6, -121.6), + GeoPoint.create(47.0, -122.0), + GeoPoint.create(47.2, -122.6), + GeoPoint.create(48.0, -122.8) + ) + + val hole1 = listOf( + GeoPoint.create(47.8, -122.6), + GeoPoint.create(47.7, -122.2), + GeoPoint.create(47.4, -122.6), + GeoPoint.create(47.6, -122.5), + GeoPoint.create(47.8, -122.6) + ) + + val polygonWithOneHole = GeoPolygon.create(outerRing, hole1) + + // Add a second hole to the polygon + val hole2 = listOf( + GeoPoint.create(47.55, -122.05), + GeoPoint.create(47.5, -121.9), + GeoPoint.create(47.3, -122.1), + GeoPoint.create(47.55, -122.05) + ) + + val polygonWithTwoHoles = GeoPolygon.create(outerRing, hole1, hole2) + // :snippet-end: + + // :snippet-start: geopoint-query + var geopointQuery = + realm.query("location GEOWITHIN $circle1").find() + // :snippet-end: + + // :snippet-start: geocircle-query + val companiesInLargeCircle = + realm.query("location GEOWITHIN $circle1").find() + println("Companies in large circle: ${companiesInLargeCircle.size}") + + val companiesInSmallCircle = + realm.query("location GEOWITHIN $circle2").find() + println("Companies in small circle: ${companiesInSmallCircle.size}") + // :snippet-end: + // :snippet-start: geobox-query + val companiesInLargeBox = + realm.query("location GEOWITHIN $box1").find() + println("Companies in large box: ${companiesInLargeBox.size}") + + val companiesInSmallBox = + realm.query("location GEOWITHIN $box2").find() + println("Companies in small box: ${companiesInSmallBox.size}") + // :snippet-end: + // :snippet-start: geopolygon-query + val companiesInBasicPolygon = + realm.query("location GEOWITHIN $basicPolygon").find() + println("Companies in basic polygon: ${companiesInBasicPolygon.size}") + + val companiesInPolygonWithHoles = + realm.query("location GEOWITHIN $polygonWithTwoHoles").find() + println("Companies in polygon with holes: ${companiesInPolygonWithHoles.size}") + // :snippet-end: + assertEquals(1, companiesInLargeCircle.size) + assertEquals(0, companiesInSmallCircle.size) + assertEquals(1, companiesInLargeBox.size) + assertEquals(2, companiesInSmallBox.size) + assertEquals(2, companiesInBasicPolygon.size) + assertEquals(1, companiesInPolygonWithHoles.size) + realm.close() + Realm.deleteRealm(config) + } + } +} \ No newline at end of file diff --git a/source/examples/generated/kotlin/Geospatial.snippet.create-geopoint.kt b/source/examples/generated/kotlin/Geospatial.snippet.create-geopoint.kt new file mode 100644 index 00000000000..d7261104313 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.create-geopoint.kt @@ -0,0 +1,12 @@ +realm.writeBlocking { + copyToRealm( + Company().apply { + location = CustomGeoPoint(47.68, -122.35) + } + ) + copyToRealm( + Company().apply { + location = CustomGeoPoint(47.9, -121.85) + } + ) +} diff --git a/source/examples/generated/kotlin/Geospatial.snippet.custom-geopoint.kt b/source/examples/generated/kotlin/Geospatial.snippet.custom-geopoint.kt new file mode 100644 index 00000000000..80243e124e2 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.custom-geopoint.kt @@ -0,0 +1,30 @@ +class CustomGeoPoint : EmbeddedRealmObject { + constructor(latitude: Double, longitude: Double) { + coordinates.apply { + add(longitude) + add(latitude) + } + } + // Empty constructor required by Realm + constructor() : this(0.0, 0.0) + + // Name and type required by Realm + var coordinates: RealmList = realmListOf() + + // Name, type, and value required by Realm + private var type: String = "Point" + + @Ignore + var latitude: Double + get() = coordinates[1] + set(value) { + coordinates[1] = value + } + + @Ignore + var longitude: Double + get() = coordinates[0] + set(value) { + coordinates[0] = value + } +} diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geobox-query.kt b/source/examples/generated/kotlin/Geospatial.snippet.geobox-query.kt new file mode 100644 index 00000000000..ae11fc0ae96 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geobox-query.kt @@ -0,0 +1,7 @@ +val companiesInLargeBox = + realm.query("location GEOWITHIN $box1").find() +println("Companies in large box: ${companiesInLargeBox.size}") + +val companiesInSmallBox = + realm.query("location GEOWITHIN $box2").find() +println("Companies in small box: ${companiesInSmallBox.size}") diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geobox.kt b/source/examples/generated/kotlin/Geospatial.snippet.geobox.kt new file mode 100644 index 00000000000..fc68f3489ec --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geobox.kt @@ -0,0 +1,8 @@ +val box1 = GeoBox.Companion.create( + bottomLeft = GeoPoint.create(47.3, -122.7), + topRight = GeoPoint.create(48.1, -122.1) +) +val box2 = GeoBox.Companion.create( + bottomLeft = GeoPoint.create(47.5, -122.4), + topRight = GeoPoint.create(47.9, -121.8) +) diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geocircle-query.kt b/source/examples/generated/kotlin/Geospatial.snippet.geocircle-query.kt new file mode 100644 index 00000000000..6c45f71f230 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geocircle-query.kt @@ -0,0 +1,7 @@ +val companiesInLargeCircle = + realm.query("location GEOWITHIN $circle1").find() +println("Companies in large circle: ${companiesInLargeCircle.size}") + +val companiesInSmallCircle = + realm.query("location GEOWITHIN $circle2").find() +println("Companies in small circle: ${companiesInSmallCircle.size}") diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geocircle.kt b/source/examples/generated/kotlin/Geospatial.snippet.geocircle.kt new file mode 100644 index 00000000000..a542fa96996 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geocircle.kt @@ -0,0 +1,8 @@ +val circle1 = GeoCircle.create( + center = GeoPoint.create(47.8, -122.6), + radius = Distance.fromKilometers(44.4) +) +val circle2 = GeoCircle.create( + center = GeoPoint.create(47.3, -121.9), + radius = Distance.fromDegrees(0.25) +) diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geopoint-model.kt b/source/examples/generated/kotlin/Geospatial.snippet.geopoint-model.kt new file mode 100644 index 00000000000..9fca95d27cf --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geopoint-model.kt @@ -0,0 +1,5 @@ +class Company : RealmObject { + @PrimaryKey + var _id: ObjectId = ObjectId() + var location: CustomGeoPoint? = null +} diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geopoint-query.kt b/source/examples/generated/kotlin/Geospatial.snippet.geopoint-query.kt new file mode 100644 index 00000000000..7d7c6d75663 --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geopoint-query.kt @@ -0,0 +1,2 @@ +var geopointQuery = + realm.query("location GEOWITHIN $circle1").find() diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geopolygon-query.kt b/source/examples/generated/kotlin/Geospatial.snippet.geopolygon-query.kt new file mode 100644 index 00000000000..32c6c7abb0d --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geopolygon-query.kt @@ -0,0 +1,7 @@ +val companiesInBasicPolygon = + realm.query("location GEOWITHIN $basicPolygon").find() +println("Companies in basic polygon: ${companiesInBasicPolygon.size}") + +val companiesInPolygonWithHoles = + realm.query("location GEOWITHIN $polygonWithTwoHoles").find() +println("Companies in polygon with holes: ${companiesInPolygonWithHoles.size}") diff --git a/source/examples/generated/kotlin/Geospatial.snippet.geopolygon.kt b/source/examples/generated/kotlin/Geospatial.snippet.geopolygon.kt new file mode 100644 index 00000000000..6359c076feb --- /dev/null +++ b/source/examples/generated/kotlin/Geospatial.snippet.geopolygon.kt @@ -0,0 +1,41 @@ +// Create a basic polygon +val basicPolygon = GeoPolygon.create( + listOf( + GeoPoint.create(48.0, -122.8), + GeoPoint.create(48.2, -121.8), + GeoPoint.create(47.6, -121.6), + GeoPoint.create(47.0, -122.0), + GeoPoint.create(47.2, -122.6), + GeoPoint.create(48.0, -122.8) + ) +) + +// Create a polygon with a single hole +val outerRing = listOf( + GeoPoint.create(48.0, -122.8), + GeoPoint.create(48.2, -121.8), + GeoPoint.create(47.6, -121.6), + GeoPoint.create(47.0, -122.0), + GeoPoint.create(47.2, -122.6), + GeoPoint.create(48.0, -122.8) +) + +val hole1 = listOf( + GeoPoint.create(47.8, -122.6), + GeoPoint.create(47.7, -122.2), + GeoPoint.create(47.4, -122.6), + GeoPoint.create(47.6, -122.5), + GeoPoint.create(47.8, -122.6) +) + +val polygonWithOneHole = GeoPolygon.create(outerRing, hole1) + +// Add a second hole to the polygon +val hole2 = listOf( + GeoPoint.create(47.55, -122.05), + GeoPoint.create(47.5, -121.9), + GeoPoint.create(47.3, -122.1), + GeoPoint.create(47.55, -122.05) +) + +val polygonWithTwoHoles = GeoPolygon.create(outerRing, hole1, hole2) diff --git a/source/sdk/kotlin/realm-database/schemas.txt b/source/sdk/kotlin/realm-database/schemas.txt index e2d186aaa11..014754430d9 100644 --- a/source/sdk/kotlin/realm-database/schemas.txt +++ b/source/sdk/kotlin/realm-database/schemas.txt @@ -8,7 +8,7 @@ Model Data - Kotlin SDK :titlesonly: Define an Object Model - Data Types + Supported Data Types Property Annotations Relationships Change an Object Model diff --git a/source/sdk/kotlin/realm-database/schemas/data-types.txt b/source/sdk/kotlin/realm-database/schemas/data-types.txt new file mode 100644 index 00000000000..de1d16d2603 --- /dev/null +++ b/source/sdk/kotlin/realm-database/schemas/data-types.txt @@ -0,0 +1,22 @@ +.. _kotlin-realm-data-types: + +============================= +Realm Data Types - Kotlin SDK +============================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. toctree:: + :titlesonly: + :hidden: + + Data Types + Geospatial Data + + +- :doc:`Data Types ` +- :doc:`Geospatial Data ` diff --git a/source/sdk/kotlin/realm-database/schemas/data-types/geospatials.txt b/source/sdk/kotlin/realm-database/schemas/data-types/geospatials.txt new file mode 100644 index 00000000000..80cd95ec232 --- /dev/null +++ b/source/sdk/kotlin/realm-database/schemas/data-types/geospatials.txt @@ -0,0 +1,242 @@ +.. _kotlin-geospatial: + +================================== +Geospatial Data Types - Kotlin SDK +================================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Geospatial data, or "geodata", specifies points and geometric objects on the Earth's +surface. With the geodata types, you can create queries that check whether a given +point is contained within a shape. For example, you can find all coffee shops within +15 km of a specified point. + +Geospatial Data Types +--------------------- + +The Kotlin SDK supports the following geospatial data types: + +- GeoPoint +- GeoCircle +- GeoBox +- GeoPolygon + +.. _kotlin-geopoint: + +GeoPoint +~~~~~~~~ +A `GeoPoint <{+kotlin-local-prefix+}reference/Realms.GeoPoint.html>`__ defines a specific +location on the Earth's surface. All of the geospatial data types use ``GeoPoints`` +to define their location. + +.. note:: + + In methods that take a ``GeoPoint``, you can alternatively provide a tuple of + doubles, where the first double is the latitude and the second double is the + longitude. The SDK interprets these tuples as ``GeoPoints``. The examples on + this page demonstrate both approaches. + +.. _kotlin-geocircle: + +GeoCircle +~~~~~~~~~ +A `GeoCircle <{+kotlin-local-prefix+}reference/Realms.GeoCircle.html>`__ defines a circle on +the Earth's surface. You define a ``GeoCircle`` by providing a ``GeoPoint`` +for the center of the circle and a `Distance <{+kotlin-local-prefix+}reference/Realms.Distance.html>`__ +object to specify the radius of the circle. + +.. note:: + + You can define the radius in kilometers, miles, degrees, or radians. + +The following code shows two examples of creating a circle: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.geocircle.kt + :language: kotlin + +.. figure:: /images/geocircles.png + :alt: Two GeoCircles + :width: 150 + :lightbox: + +.. _kotlin-geobox: + +GeoBox +~~~~~~ + +A `GeoBox <{+kotlin-local-prefix+}reference/Realms.GeoBox.html>`__ defines a rectangle on +the Earth's surface. You define the rectangle by specifying the bottom left +(southwest) corner and the top right (northeast) corner. The following example +creates 2 boxes: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.geobox.kt + :language: kotlin + +.. figure:: /images/geoboxes.png + :alt: 2 GeoBoxes + :width: 150 + :lightbox: + +.. _kotlin-geopolygon: + +GeoPolygon +~~~~~~~~~~ + +A `GeoPolygon <{+kotlin-local-prefix+}reference/Realms.GeoPolygon.html>`__ defines a polygon +on the Earth's surface. Because a polygon is a closed shape, you must provide a +minimum of 4 points: 3 points to define the polygon's shape, and a fourth to +close the shape. + +.. important:: + + The fourth point in a polygon must be the same as the first point. + +You can also exclude areas within a polygon by defining one or more "holes". A +hole is another polygon whose bounds fit completely within the outer polygon. +The following example creates 3 polygons: one is a basic polygon with 5 points, +one is the same polygon with a single hole, and the third is the same polygon +with two holes: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.geopolygon.kt + :language: kotlin + +.. figure:: /images/geopolygons.png + :alt: 3 GeoPolygons + :width: 250 + :lightbox: + +.. _kotlin-persist-geopoint: + +Persisting GeoPoint Data +------------------------ + +If you want to persist ``GeoPoint`` data, it must conform to the +`GeoJSON spec `__. + +Creating a GeoJSON-Compatible Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To create a class that conforms to the GeoJSON spec, you: + +1. Create an :ref:`embedded realm object ` + (a class that inherits from + `EmbeddedRealmObject <{+kotlin-local-prefix+}io.realm.kotlin.types/-embedded-realm-object/index.html>`__). + +#. At a minimum, add the two fields required by the GeoJSON spec: + + - A field of type ``String`` property that maps to a ``type`` property + with the value of ``"Point"``: ``var type: String = "Point"`` + + - A field of type ``RealmList`` that maps to a ``coordinates`` + property in the realm schema containing a latitude/longitude + pair: ``var coordinates: RealmList = realmListOf()`` + +The following example shows an embedded class named ``CustomGeoPoint`` that is used +to persist GeoPoint data: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.custom-geopoint.kt + :language: kotlin + +Using the Embedded Class +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the custom ``GeoPoint`` class in your realm model, as shown in the +following example: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.geopoint-model.kt + :language: kotlin + :emphasize-lines: 4 + +You can then add instances of your class to realm just like any other Realm model: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.create-geopoint.kt + :language: kotlin + +The following image shows the results of creating these two ``Company`` objects: + +.. figure:: /images/geopoints.png + :alt: 2 GeoPoints + :width: 150 + :lightbox: + +Querying Geospatial Data +------------------------ +To query against geospatial data, you can use the ``GEOWITHIN`` operator with +:ref:`RQL `. This method takes the ``coordinates`` +property of an embedded object and checks if that point is contained within +the geospatial shape for that object. + +The format for querying geospatial data is the same regardless the shape of +the geodata region. + +.. important:: + + You cannot use parameterized queries with geospatial data. + +Examples +~~~~~~~~ + +The following example shows how to query for geospatial data: + +.. literalinclude:: /examples/generated/kotlin/Geospatial.snippet.geopoint-query.kt + :language: kotlin + +The following examples show querying against various shapes to return a list of +companies within the shape: + +GeoCircle +````````` + +.. io-code-block:: + + .. input:: /examples/generated/kotlin/Geospatial.snippet.geocircle-query.kt + :language: kotlin + + .. output:: + + Companies in large circle: 1 + Companies in small circle: 0 + +.. figure:: /images/geocircles-query.png + :alt: Querying a GeoCircle example + :width: 150 + :lightbox: + +GeoBox +`````` + +.. io-code-block:: + + .. input:: /examples/generated/kotlin/Geospatial.snippet.geobox-query.kt + :language: kotlin + + .. output:: + + Companies in large box: 1 + Companies in small box: 2 + +.. figure:: /images/geoboxes-query.png + :alt: Querying a GeoBox example + :width: 150 + +GeoPolygon +`````````` + +.. io-code-block:: + + .. input:: /examples/generated/kotlin/Geospatial.snippet.geopolygon-query.kt + :language: kotlin + + .. output:: + + Companies in basic polygon: 2 + Companies in polygon with holes: 1 + +.. figure:: /images/geopolygons-query.png + :alt: Querying a GeoPolygon example + :width: 150 + :lightbox: diff --git a/source/sdk/kotlin/realm-database/schemas/supported-types.txt b/source/sdk/kotlin/realm-database/schemas/data-types/supported-types.txt similarity index 98% rename from source/sdk/kotlin/realm-database/schemas/supported-types.txt rename to source/sdk/kotlin/realm-database/schemas/data-types/supported-types.txt index 2133835dd9b..c2153e095fc 100644 --- a/source/sdk/kotlin/realm-database/schemas/supported-types.txt +++ b/source/sdk/kotlin/realm-database/schemas/data-types/supported-types.txt @@ -54,6 +54,12 @@ Realm supports the following field data types: - ``BacklinksDelegate``, a `backlinks <{+kotlin-local-prefix+}io.realm.kotlin.ext/backlinks.html>`__ delegate used to define an inverse relationship between `RealmObjects <{+kotlin-local-prefix+}io.realm.kotlin.types/-realm-object/index.html>`__. +- The following :ref:`geospatial types `: + + - ``GeoPoint`` + - ``GeoCircle`` + - ``GeoBox`` + - ``GeoPolygon`` Realm stores all non-decimal numeric types as ``Long`` values. Similarly, Realm stores all decimal numeric types as ``Double`` @@ -267,6 +273,8 @@ or ``EmbeddedRealmObject``, the value must be declared nullable. .. literalinclude:: /examples/generated/kotlin/CreateTest.snippet.percent-encode-disallowed-characters.kt :language: kotlin + + .. _kotlin-additional-types: Additional Supported Data Types