mirror of
https://github.com/Pygmalion69/OpenTopoMapViewer.git
synced 2025-10-06 00:02:42 +02:00
Remove ORS module
This commit is contained in:
@@ -99,15 +99,14 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':ors-client')
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
//noinspection GradleCompatible
|
||||
implementation 'androidx.core:core-ktx:1.16.0'
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.2"
|
||||
implementation 'androidx.core:core-ktx:1.17.0'
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.3"
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.4.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
implementation 'org.osmdroid:osmdroid-android:6.1.20'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
|
||||
@@ -135,8 +134,9 @@ dependencies {
|
||||
}
|
||||
playImplementation 'com.google.android.gms:play-services-ads:24.5.0'
|
||||
playImplementation 'com.google.android.ump:user-messaging-platform:3.2.0'
|
||||
implementation 'com.github.Pygmalion69:ors-android-client:0.1.2'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.7.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,6 @@ import org.nitri.opentopo.nearby.entity.NearbyItem
|
||||
import org.nitri.opentopo.util.Utils
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import androidx.core.net.toUri
|
||||
import org.nitri.ors.DefaultOrsClient
|
||||
import org.nitri.ors.Ors
|
||||
import org.nitri.ors.OrsClient
|
||||
|
||||
|
@@ -26,6 +26,7 @@ allprojects {
|
||||
password = System.getenv("GPR_TOKEN") ?: findProperty("gpr.token")
|
||||
}
|
||||
}
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
tasks.withType(JavaCompile).tap {
|
||||
configureEach {
|
||||
|
2
ors-client/.gitignore
vendored
2
ors-client/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/build
|
||||
/src/main/res/values/api_key.xml
|
@@ -1,73 +0,0 @@
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0"
|
||||
id("kotlin-kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.nitri.ors"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
testOptions {
|
||||
targetSdk = 35
|
||||
}
|
||||
|
||||
lint {
|
||||
targetSdk = 35
|
||||
}
|
||||
|
||||
publishing { singleVariant("release") { withSourcesJar() } }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.17.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:3.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:5.1.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
implementation("androidx.test:monitor:1.8.0")
|
||||
implementation("androidx.test.ext:junit-ktx:1.3.0")
|
||||
|
||||
// Unit testing
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
|
||||
testImplementation("org.mockito:mockito-core:5.19.0")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0")
|
||||
|
||||
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
|
||||
androidTestImplementation("androidx.test:runner:1.7.0")
|
||||
|
||||
androidTestImplementation("androidx.test:rules:1.7.0")
|
||||
}
|
21
ors-client/proguard-rules.pro
vendored
21
ors-client/proguard-rules.pro
vendored
@@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@@ -1,10 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.nitri.ors.test">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:label="ORS Test"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:allowBackup="true" />
|
||||
</manifest>
|
@@ -1,65 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.elevation.ElevationLineRequestBuilderJ
|
||||
import org.nitri.ors.domain.elevation.ElevationPointRequestBuilderJ
|
||||
import org.nitri.ors.domain.elevation.ElevationFormats
|
||||
import org.nitri.ors.domain.elevation.elevationLineRequest
|
||||
import org.nitri.ors.domain.elevation.elevationPointRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ElevationDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsElevationLine_polyline() {
|
||||
val req = elevationLineRequest {
|
||||
polyline(8.681495 to 49.41461, 8.687872 to 49.420318)
|
||||
formatOut = ElevationFormats.GEOJSON
|
||||
}
|
||||
assertEquals(ElevationFormats.POLYLINE, req.formatIn)
|
||||
assertEquals(ElevationFormats.GEOJSON, req.formatOut)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_geometry_for_elevationLine() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
elevationLineRequest { formatIn = ElevationFormats.POLYLINE }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_buildsElevationPoint_point() {
|
||||
val req = elevationPointRequest {
|
||||
point(8.681495, 49.41461)
|
||||
formatIn = "point"
|
||||
formatOut = "geojson"
|
||||
}
|
||||
assertEquals("geojson", req.formatOut)
|
||||
assertEquals("point", req.formatIn)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsElevationLine_polyline() {
|
||||
val req = ElevationLineRequestBuilderJ()
|
||||
.polyline(listOf(listOf(8.0,48.0), listOf(9.0,49.0)))
|
||||
.formatOut(ElevationFormats.GEOJSON)
|
||||
.build()
|
||||
assertEquals(ElevationFormats.POLYLINE, req.formatIn)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsElevationPoint_point() {
|
||||
val req = ElevationPointRequestBuilderJ()
|
||||
.point(8.0, 48.0)
|
||||
.formatOut("geojson")
|
||||
.build()
|
||||
assertEquals("geojson", req.formatOut)
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.ElevationHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ElevationInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, ElevationHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = ElevationHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testElevation_point_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// A point near Heidelberg, Germany
|
||||
val lon = 8.681495
|
||||
val lat = 49.41461
|
||||
|
||||
val response = with(helper) { client.getElevationPoint(lon = lon, lat = lat) }
|
||||
|
||||
assertNotNull("Elevation point response should not be null", response)
|
||||
assertNotNull("Geometry should not be null", response.geometry)
|
||||
assertEquals("Geometry type should be Point", "Point", response.geometry.type)
|
||||
|
||||
val coords = response.geometry.coordinates
|
||||
assertTrue("Point coordinates should contain at least [lon, lat]", coords.size >= 2)
|
||||
// Typically API returns elevation as third value
|
||||
if (coords.size >= 3) {
|
||||
// elevation could be any double, just ensure it's a number
|
||||
val elevation = coords[2]
|
||||
assertTrue("Elevation should be a finite number", elevation.isFinite())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testElevation_line_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// A short line segment around Heidelberg
|
||||
val coordinates = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
|
||||
val response = with(helper) { client.getElevationLine(coordinates = coordinates) }
|
||||
|
||||
assertNotNull("Elevation line response should not be null", response)
|
||||
assertEquals("Geometry type should be LineString", "LineString", response.geometry.type)
|
||||
val lineCoords = response.geometry.coordinates
|
||||
assertTrue("LineString should have at least 2 points", lineCoords.size >= 2)
|
||||
|
||||
val first = lineCoords.first()
|
||||
assertTrue("Each coordinate should have at least [lon, lat]", first.size >= 2)
|
||||
if (first.size >= 3) {
|
||||
val elevation = first[2]
|
||||
assertTrue("Elevation should be a finite number", elevation.isFinite())
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.export.ExportRequestBuilderJ
|
||||
import org.nitri.ors.domain.export.exportRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExportDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsExportRequest_andValidates() {
|
||||
val req = exportRequest {
|
||||
bbox(8.681495, 49.41461, 8.686507, 49.41943)
|
||||
id("test-id")
|
||||
geometry(true)
|
||||
}
|
||||
assertEquals("test-id", req.id)
|
||||
assertEquals(listOf(listOf(8.681495,49.41461), listOf(8.686507,49.41943)), req.bbox)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_bbox() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
exportRequest { geometry(false) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsExportRequest_andValidates() {
|
||||
val req = ExportRequestBuilderJ()
|
||||
.bbox(8.0, 48.0, 9.0, 49.0)
|
||||
.id("jid")
|
||||
.geometry(null)
|
||||
.build()
|
||||
assertEquals("jid", req.id)
|
||||
assertEquals(listOf(listOf(8.0,48.0), listOf(9.0,49.0)), req.bbox)
|
||||
}
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.ExportHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExportInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, ExportHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = ExportHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExport_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Bounding box around Heidelberg, Germany
|
||||
val bbox = listOf(
|
||||
listOf(8.681495, 49.41461), // minLon, minLat
|
||||
listOf(8.686507, 49.41943) // maxLon, maxLat
|
||||
)
|
||||
|
||||
val response = with(helper) { client.export(bbox = bbox, profile = Profile.DRIVING_CAR) }
|
||||
|
||||
assertNotNull("Export response should not be null", response)
|
||||
// Basic sanity checks on structure
|
||||
assertTrue("Points list should not be empty", response.nodes.isNotEmpty())
|
||||
assertTrue("Edges list should not be empty", response.edges.isNotEmpty())
|
||||
}
|
||||
|
||||
// Exceeds rate limit
|
||||
// @Test
|
||||
// fun testExportJson_successful() = runBlocking {
|
||||
// val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
//
|
||||
// val (client, helper) = create(context)
|
||||
// val bbox = listOf(
|
||||
// listOf(8.681495, 49.41461),
|
||||
// listOf(8.686507, 49.41943)
|
||||
// )
|
||||
//
|
||||
// val response = with(helper) { client.exportJson(bbox = bbox, profile = "driving-car") }
|
||||
//
|
||||
// assertNotNull("JSON Export response should not be null", response)
|
||||
// assertTrue("Points list should not be empty", response.nodes.isNotEmpty())
|
||||
// }
|
||||
|
||||
// Exceeds rate limit
|
||||
// @Test
|
||||
// fun testExportTopoJson_successful() = runBlocking {
|
||||
// val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
// val (client, helper) = create(context)
|
||||
//
|
||||
// val bbox = listOf(
|
||||
// listOf(8.681495, 49.41461),
|
||||
// listOf(8.686507, 49.41943)
|
||||
// )
|
||||
//
|
||||
// val topo = with(helper) { client.exportTopoJson(bbox = bbox, profile = "driving-car") }
|
||||
//
|
||||
// assertNotNull("TopoJSON Export response should not be null", topo)
|
||||
// assertTrue("Type should be present", topo.type.isNotBlank())
|
||||
// }
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.GeocodeHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GeocodeInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, GeocodeHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = GeocodeHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeocode_search_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
|
||||
val response = with(helper) {
|
||||
client.search(
|
||||
text = "Heidelberg",
|
||||
apiKey = apiKey,
|
||||
size = 5
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Search response should not be null", response)
|
||||
assertTrue("Features should not be empty for Heidelberg search", response.features.isNotEmpty())
|
||||
|
||||
val first = response.features.first()
|
||||
// Basic geometry sanity
|
||||
assertNotNull("First feature geometry should not be null", first.geometry)
|
||||
val geom = first.geometry!!
|
||||
assertTrue("Geometry coordinates should have at least [lon, lat]", geom.coordinates.size >= 2)
|
||||
// Basic properties sanity
|
||||
assertNotNull("First feature properties should not be null", first.properties)
|
||||
val name = first.properties?.name ?: first.properties?.label
|
||||
assertTrue("Feature should have a name or label", !name.isNullOrBlank())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeocode_reverse_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
|
||||
// Point near Heidelberg, Germany
|
||||
val lon = 8.681495
|
||||
val lat = 49.41461
|
||||
|
||||
val response = with(helper) {
|
||||
client.reverse(
|
||||
apiKey = apiKey,
|
||||
lon = lon,
|
||||
lat = lat,
|
||||
size = 5
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Reverse response should not be null", response)
|
||||
assertTrue("Reverse should return at least one feature", response.features.isNotEmpty())
|
||||
val first = response.features.first()
|
||||
assertNotNull("First reverse feature properties should not be null", first.properties)
|
||||
val label = first.properties?.label ?: first.properties?.name
|
||||
assertTrue("Reverse feature should provide a label/name", !label.isNullOrBlank())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeocode_autocomplete_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
|
||||
val response = with(helper) {
|
||||
client.autocomplete(
|
||||
apiKey = apiKey,
|
||||
text = "Heidelb",
|
||||
size = 5
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Autocomplete response should not be null", response)
|
||||
assertTrue("Autocomplete should return suggestions", response.features.isNotEmpty())
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.isochrones.IsochronesRequestBuilderJ
|
||||
import org.nitri.ors.domain.isochrones.isochronesRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IsochronesDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsIsochronesRequest_andValidates() {
|
||||
val req = isochronesRequest {
|
||||
location(8.681495, 49.41461)
|
||||
rangeSeconds(600)
|
||||
rangeType = "time"
|
||||
intersections = true
|
||||
}
|
||||
assertEquals(listOf(listOf(8.681495,49.41461)), req.locations)
|
||||
assertEquals(listOf(600), req.range)
|
||||
assertEquals("time", req.rangeType)
|
||||
assertEquals(true, req.intersections)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_location_and_range() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
isochronesRequest { rangeSeconds(300) }
|
||||
}
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
isochronesRequest { location(8.0, 48.0) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsIsochronesRequest_andValidates() {
|
||||
val req = IsochronesRequestBuilderJ()
|
||||
.location(8.0, 48.0)
|
||||
.addRange(900)
|
||||
.rangeType("time")
|
||||
.intersections(false)
|
||||
.build()
|
||||
assertEquals(listOf(listOf(8.0,48.0)), req.locations)
|
||||
assertEquals(listOf(900), req.range)
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.IsochronesHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IsochronesInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, IsochronesHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = IsochronesHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsochrones_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Heidelberg, Germany [lon, lat]
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461)
|
||||
)
|
||||
// 5 minutes (300 seconds)
|
||||
val range = listOf(300)
|
||||
val profile = Profile.DRIVING_CAR
|
||||
|
||||
val response = with(helper) {
|
||||
client.getIsochrones(
|
||||
locations = locations,
|
||||
range = range,
|
||||
profile = profile,
|
||||
attributes = null,
|
||||
rangeType = "time"
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Isochrones response should not be null", response)
|
||||
assertTrue("Features should not be empty", response.features.isNotEmpty())
|
||||
|
||||
val first = response.features.first()
|
||||
assertNotNull("Feature geometry should not be null", first.geometry)
|
||||
|
||||
// Basic metadata sanity
|
||||
assertTrue("Response type should not be blank", response.type.isNotBlank())
|
||||
assertNotNull("Metadata should be present", response.metadata)
|
||||
assertTrue("BBox should have 4 numbers", response.bbox.size == 4)
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.matrix.MatrixRequestBuilderJ
|
||||
import org.nitri.ors.domain.matrix.matrixRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MatrixDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsMatrixRequest_andValidates() {
|
||||
val req = matrixRequest {
|
||||
location(8.681495, 49.41461)
|
||||
location(8.686507, 49.41943)
|
||||
metrics = listOf("duration", "distance")
|
||||
resolveLocations = false
|
||||
}
|
||||
assertEquals(2, req.locations.size)
|
||||
assertEquals(listOf(8.681495,49.41461), req.locations.first())
|
||||
assertEquals(listOf("duration","distance"), req.metrics)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_location() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
matrixRequest { /* no locations */ }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsMatrixRequest_andValidates() {
|
||||
val req = MatrixRequestBuilderJ()
|
||||
.location(8.0, 48.0)
|
||||
.location(9.0, 49.0)
|
||||
.metrics(listOf("duration"))
|
||||
.resolveLocations(true)
|
||||
.build()
|
||||
assertEquals(2, req.locations.size)
|
||||
assertEquals(listOf(8.0,48.0), req.locations.first())
|
||||
assertEquals(listOf("duration"), req.metrics)
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.MatrixHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MatrixInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, MatrixHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = MatrixHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMatrix_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Two locations in/near Heidelberg, Germany [lon, lat]
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461), // Heidelberg center
|
||||
listOf(8.687872, 49.420318) // Nearby point
|
||||
)
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val metrics = listOf("duration", "distance")
|
||||
|
||||
val response = with(helper) {
|
||||
client.getMatrix(
|
||||
locations = locations,
|
||||
profile = profile,
|
||||
metrics = metrics,
|
||||
resolveLocations = false
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Matrix response should not be null", response)
|
||||
|
||||
// Durations and distances should be present when requested
|
||||
val durations = response.durations
|
||||
val distances = response.distances
|
||||
assertNotNull("Durations should be present when requested", durations)
|
||||
assertNotNull("Distances should be present when requested", distances)
|
||||
|
||||
// Expect a 2x2 matrix for two input points
|
||||
durations!!
|
||||
distances!!
|
||||
assertEquals("Durations should have size equal to number of sources", 2, durations.size)
|
||||
assertEquals("Distances should have size equal to number of sources", 2, distances.size)
|
||||
assertEquals("Each durations row should have size equal to number of destinations", 2, durations[0].size)
|
||||
assertEquals("Each distances row should have size equal to number of destinations", 2, distances[0].size)
|
||||
|
||||
// Sanity: diagonal (origin to same point) should be zero or near-zero for distance
|
||||
assertTrue("Distance from a point to itself should be >= 0", distances[0][0] >= 0.0)
|
||||
assertTrue("Duration from a point to itself should be >= 0", durations[0][0] >= 0.0)
|
||||
|
||||
// Metadata should be included
|
||||
assertNotNull("Metadata should be present", response.metadata)
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.optimization.OptimizationRequestBuilderJ
|
||||
import org.nitri.ors.domain.optimization.optimizationRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OptimizationDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsOptimizationRequest_andValidates() {
|
||||
val req = optimizationRequest {
|
||||
vehicle(id = 1, profile = "driving-car", startLon = 8.68, startLat = 49.41)
|
||||
job(id = 10, locationLon = 8.69, locationLat = 49.42, service = 300, priority = 50)
|
||||
}
|
||||
assertEquals(1, req.vehicles.size)
|
||||
assertEquals(1, req.jobs?.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_vehicle_and_job_or_shipment() {
|
||||
assertThrows(IllegalStateException::class.java) {
|
||||
optimizationRequest { job(id = 10, locationLon = 8.0, locationLat = 48.0) }
|
||||
}
|
||||
assertThrows(IllegalStateException::class.java) {
|
||||
optimizationRequest { vehicle(id = 1, profile = "driving-car") }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsOptimizationRequest_andValidates() {
|
||||
val req = OptimizationRequestBuilderJ()
|
||||
.addVehicle(1, "driving-car", 8.0, 48.0, null, null, null)
|
||||
.addJob(10, 8.1, 48.1, 120, 10)
|
||||
.build()
|
||||
assertEquals(1, req.vehicles.size)
|
||||
assertEquals(1, req.jobs?.size)
|
||||
}
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.optimization.Job
|
||||
import org.nitri.ors.domain.optimization.Vehicle
|
||||
import org.nitri.ors.helper.OptimizationHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OptimizationInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, OptimizationHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = OptimizationHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptimization_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Simple scenario in/near Heidelberg, Germany
|
||||
val vehicle = Vehicle(
|
||||
id = 1,
|
||||
profile = "driving-car",
|
||||
// Start and end at/near Heidelberg castle parking
|
||||
start = listOf(8.6910, 49.4100),
|
||||
end = listOf(8.6910, 49.4100)
|
||||
)
|
||||
|
||||
val jobs = listOf(
|
||||
Job(
|
||||
id = 101,
|
||||
location = listOf(8.681495, 49.41461) // Heidelberg center
|
||||
),
|
||||
Job(
|
||||
id = 102,
|
||||
location = listOf(8.687872, 49.420318) // Nearby point
|
||||
)
|
||||
)
|
||||
|
||||
val response = with(helper) {
|
||||
client.getOptimization(
|
||||
vehicles = listOf(vehicle),
|
||||
jobs = jobs
|
||||
)
|
||||
}
|
||||
|
||||
// Basic assertions
|
||||
assertNotNull("Optimization response should not be null", response)
|
||||
assertNotNull("Summary should be present", response.summary)
|
||||
assertTrue("Routes list should be present", response.routes != null)
|
||||
assertTrue("Routes should not be empty for solvable small case", response.routes.isNotEmpty())
|
||||
|
||||
val firstRoute = response.routes.first()
|
||||
assertTrue("Route steps should not be empty", firstRoute.steps.isNotEmpty())
|
||||
// Code is typically 0 for success in VROOM-like APIs; ensure non-negative as a safe check
|
||||
assertTrue("Response code should be non-negative", response.code >= 0)
|
||||
|
||||
// Optional additional sanity checks
|
||||
assertTrue("Total duration should be >= 0", response.summary.duration >= 0)
|
||||
assertTrue("Total service should be >= 0", response.summary.service >= 0)
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.pois.PoisRequestBuilderJ
|
||||
import org.nitri.ors.domain.pois.poisRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PoisDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsPoisRequest_bbox() {
|
||||
val req = poisRequest {
|
||||
bbox(8.67, 49.40, 8.70, 49.43)
|
||||
limit = 10
|
||||
sortby = "distance"
|
||||
}
|
||||
assertEquals(10, req.limit)
|
||||
assertEquals("distance", req.sortby)
|
||||
requireNotNull(req.geometry.bbox)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsPoisRequest_point() {
|
||||
val req = PoisRequestBuilderJ()
|
||||
.point(8.68, 49.41)
|
||||
.limit(5)
|
||||
.build()
|
||||
assertEquals(5, req.limit)
|
||||
requireNotNull(req.geometry.geojson)
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.PoisHelper
|
||||
|
||||
//@RunWith(AndroidJUnit4::class)
|
||||
class PoisInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, PoisHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = PoisHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
// Leave it out for now: https://github.com/GIScience/openpoiservice/issues/150
|
||||
// @Test
|
||||
// fun testPois_byBbox_successful() = runBlocking {
|
||||
// val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
// val (client, helper) = create(context)
|
||||
//
|
||||
// // Bounding box around Heidelberg, Germany
|
||||
// val bbox = listOf(
|
||||
// listOf(8.67, 49.40), // minLon, minLat
|
||||
// listOf(8.70, 49.43) // maxLon, maxLat
|
||||
// )
|
||||
//
|
||||
// val response = with(helper) {
|
||||
// client.getPoisByBbox(
|
||||
// bbox = bbox,
|
||||
// limit = 10
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// assertNotNull("POIs response should not be null", response)
|
||||
// assertEquals("GeoJSON type should be FeatureCollection", "FeatureCollection", response.type)
|
||||
// assertNotNull("Information block should be present", response.information)
|
||||
// assertTrue("Features should not be empty in a city bbox", response.features.isNotEmpty())
|
||||
//
|
||||
// val first = response.features.first()
|
||||
// assertEquals("Feature type should be Feature", "Feature", first.type)
|
||||
// assertEquals("Geometry type should be Point", "Point", first.geometry.type)
|
||||
// assertEquals("Point coordinates should be [lon, lat]", 2, first.geometry.coordinates.size)
|
||||
//
|
||||
// // Basic properties sanity
|
||||
// val props = first.properties
|
||||
// assertTrue("OSM id should be positive", props.osmId > 0)
|
||||
// assertTrue("Distance should be non-negative", props.distance >= 0.0)
|
||||
// }
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.route.routeRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RouteDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun buildRouteRequest_withDsl_buildsCoordinatesAndLanguage() {
|
||||
val req = routeRequest {
|
||||
language = "en"
|
||||
start(8.68, 49.41)
|
||||
coordinate(8.69, 49.42)
|
||||
end(8.70, 49.43)
|
||||
}
|
||||
assertEquals(3, req.coordinates.size)
|
||||
assertEquals(listOf(8.68, 49.41), req.coordinates.first())
|
||||
assertEquals(listOf(8.70, 49.43), req.coordinates.last())
|
||||
assertEquals("en", req.language)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun buildRouteRequest_withDsl_throwsIfLessThanTwoPoints() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
routeRequest {
|
||||
start(8.68, 49.41)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dslIntegration_getRoute_succeedsBasic() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
|
||||
val req = routeRequest {
|
||||
// Very short route to minimize load
|
||||
start(8.681495, 49.41461)
|
||||
end(8.686507, 49.41943)
|
||||
language = "en"
|
||||
}
|
||||
|
||||
val response = client.getRoute(Profile.DRIVING_CAR, req)
|
||||
assertNotNull(response)
|
||||
// basic sanity: at least one route and segments present
|
||||
require(response.routes.isNotEmpty())
|
||||
}
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.helper.RouteHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RouteInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, RouteHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val repo = RouteHelper()
|
||||
return client to repo
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val route = with(repository) { client.getRoute(start, end, "driving-car") }
|
||||
|
||||
assertNotNull("Route should not be null", route)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchGpxRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val gpxXml = with(repository) { client.getRouteGpx(start, end, "driving-car") }
|
||||
|
||||
assertNotNull("GPX response body should not be null", gpxXml)
|
||||
assert(gpxXml.contains("<gpx")) { "Response does not appear to be valid GPX" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchGeoJsonRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val route: GeoJsonRouteResponse = with(repository) { client.getRouteGeoJson(start, end,
|
||||
Profile.DRIVING_CAR) }
|
||||
|
||||
assertNotNull("Route should not be null", route)
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.route.RouteRequestBuilderJ
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RouteRequestBuilderJInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun builder_buildsCoordinatesAndLanguage() {
|
||||
val req = RouteRequestBuilderJ()
|
||||
.start(8.68, 49.41)
|
||||
.add(8.69, 49.42)
|
||||
.end(8.70, 49.43)
|
||||
.language("de")
|
||||
.build()
|
||||
|
||||
assertEquals(3, req.coordinates.size)
|
||||
assertEquals(listOf(8.68, 49.41), req.coordinates.first())
|
||||
assertEquals(listOf(8.70, 49.43), req.coordinates.last())
|
||||
assertEquals("de", req.language)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builder_throwsIfLessThanTwoPoints() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
RouteRequestBuilderJ()
|
||||
.start(8.68, 49.41)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun builderIntegration_getRoute_succeedsBasic() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
|
||||
val req = RouteRequestBuilderJ()
|
||||
.start(8.681495, 49.41461)
|
||||
.end(8.686507, 49.41943)
|
||||
.language("en")
|
||||
.build()
|
||||
|
||||
val response = client.getRoute(Profile.DRIVING_CAR, req)
|
||||
assertNotNull(response)
|
||||
require(response.routes.isNotEmpty())
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.domain.snap.SnapRequestBuilderJ
|
||||
import org.nitri.ors.domain.snap.snapRequest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SnapDslInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun dsl_buildsSnapRequest_andValidates() {
|
||||
val req = snapRequest {
|
||||
location(8.681495, 49.41461)
|
||||
radius(50)
|
||||
id = "snap-1"
|
||||
}
|
||||
assertEquals(1, req.locations.size)
|
||||
assertEquals(50, req.radius)
|
||||
assertEquals("snap-1", req.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dsl_requires_location_and_radius() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
snapRequest { radius(10) }
|
||||
}
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
snapRequest { location(8.0, 48.0) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun javaBuilder_buildsSnapRequest_andValidates() {
|
||||
val req = SnapRequestBuilderJ()
|
||||
.location(8.0, 48.0)
|
||||
.radius(25)
|
||||
.id("jid")
|
||||
.build()
|
||||
assertEquals(1, req.locations.size)
|
||||
assertEquals(25, req.radius)
|
||||
assertEquals("jid", req.id)
|
||||
}
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.helper.SnapHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SnapInstrumentedTest {
|
||||
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, SnapHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = SnapHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnap_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Two locations in/near Heidelberg, Germany [lon, lat]
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461), // Heidelberg center
|
||||
listOf(8.687872, 49.420318) // Nearby point
|
||||
)
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50 // meters
|
||||
|
||||
val response = with(helper) {
|
||||
client.getSnap(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_test"
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Snap response should not be null", response)
|
||||
assertNotNull("Metadata should be present", response.metadata)
|
||||
assertTrue("Locations should not be empty", response.locations.isNotEmpty())
|
||||
assertEquals("Should have as many results as inputs", locations.size, response.locations.size)
|
||||
|
||||
val first = response.locations.first()
|
||||
assertNotNull("First snapped location should have coordinates", first.location)
|
||||
assertEquals("Snapped coordinates should have 2 values [lon, lat]", 2, first.location.size)
|
||||
assertTrue("Snapped distance should be non-negative", first.snappedDistance >= 0.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnapJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, helper) = create(context)
|
||||
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50
|
||||
|
||||
val response = with(helper) {
|
||||
client.getSnapJson(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_json_test"
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Snap JSON response should not be null", response)
|
||||
assertNotNull("Metadata should be present", response.metadata)
|
||||
assertTrue("Locations should not be empty", response.locations.isNotEmpty())
|
||||
response.locations.forEach { loc ->
|
||||
assertEquals("Coordinate should be [lon, lat]", 2, loc.location.size)
|
||||
assertTrue("Snapped distance should be non-negative", loc.snappedDistance >= 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnapGeoJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50
|
||||
|
||||
val response = with(repository) {
|
||||
client.getSnapGeoJson(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_geojson_test"
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Snap GeoJSON response should not be null", response)
|
||||
assertEquals("GeoJSON type should be FeatureCollection", "FeatureCollection", response.type)
|
||||
assertNotNull("Metadata should be present", response.metadata)
|
||||
assertTrue("Features should not be empty", response.features.isNotEmpty())
|
||||
|
||||
val feature = response.features.first()
|
||||
assertEquals("Feature type should be Feature", "Feature", feature.type)
|
||||
assertEquals("Geometry type should be Point", "Point", feature.geometry.type)
|
||||
assertEquals("Point coordinates should be [lon, lat]", 2, feature.geometry.coordinates.size)
|
||||
assertTrue("snapped_distance should be non-negative", feature.properties.snappedDistance >= 0.0)
|
||||
assertTrue("source_id should be non-negative", feature.properties.sourceId >= 0)
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
@@ -1,300 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import org.nitri.ors.domain.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.domain.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationPointResponse
|
||||
import org.nitri.ors.domain.export.ExportRequest
|
||||
import org.nitri.ors.domain.export.ExportResponse
|
||||
import org.nitri.ors.domain.export.TopoJsonExportResponse
|
||||
import org.nitri.ors.domain.geocode.GeocodeSearchResponse
|
||||
import org.nitri.ors.domain.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.domain.isochrones.IsochronesResponse
|
||||
import org.nitri.ors.domain.matrix.MatrixRequest
|
||||
import org.nitri.ors.domain.matrix.MatrixResponse
|
||||
import org.nitri.ors.domain.optimization.OptimizationRequest
|
||||
import org.nitri.ors.domain.optimization.OptimizationResponse
|
||||
import org.nitri.ors.domain.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.domain.pois.PoisRequest
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.domain.route.RouteRequest
|
||||
import org.nitri.ors.domain.route.RouteResponse
|
||||
import org.nitri.ors.domain.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.domain.snap.SnapRequest
|
||||
import org.nitri.ors.domain.snap.SnapResponse
|
||||
import org.nitri.ors.restclient.OpenRouteServiceRestClient
|
||||
|
||||
/**
|
||||
* Default implementation of [OrsClient] using the Retrofit based
|
||||
* [OpenRouteServiceRestClient].
|
||||
*/
|
||||
class DefaultOrsClient(apiKey: String, context: Context) : OrsClient {
|
||||
private val api = OpenRouteServiceRestClient.create(apiKey, context)
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getRoute(
|
||||
profile: Profile,
|
||||
routeRequest: RouteRequest
|
||||
): RouteResponse {
|
||||
return api.getRoute(profile.key, routeRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getRouteGpx(
|
||||
profile: Profile,
|
||||
routeRequest: RouteRequest
|
||||
): String {
|
||||
return api.getRouteGpx(profile.key, routeRequest).body()?.string() ?: ""
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getRouteGeoJson(
|
||||
profile: Profile,
|
||||
routeRequest: RouteRequest
|
||||
): GeoJsonRouteResponse {
|
||||
return api.getRouteGeoJson(profile.key, routeRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun export(
|
||||
profile: Profile,
|
||||
exportRequest: ExportRequest
|
||||
): ExportResponse {
|
||||
return api.export(profile.key, exportRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun exportJson(
|
||||
profile: Profile,
|
||||
exportRequest: ExportRequest
|
||||
): ExportResponse {
|
||||
return api.exportJson(profile.key, exportRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun exportTopoJson(
|
||||
profile: Profile,
|
||||
exportRequest: ExportRequest
|
||||
): TopoJsonExportResponse {
|
||||
return api.exportTopoJson(profile.key, exportRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getIsochrones(
|
||||
profile: Profile,
|
||||
isochronesRequest: IsochronesRequest
|
||||
): IsochronesResponse {
|
||||
return api.getIsochrones(profile.key, isochronesRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getMatrix(
|
||||
profile: Profile,
|
||||
matrixRequest: MatrixRequest
|
||||
): MatrixResponse {
|
||||
return api.getMatrix(profile.key, matrixRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getSnap(
|
||||
profile: Profile,
|
||||
snapRequest: SnapRequest
|
||||
): SnapResponse {
|
||||
return api.getSnap(profile.key, snapRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getSnapJson(
|
||||
profile: Profile,
|
||||
snapRequest: SnapRequest
|
||||
): SnapResponse {
|
||||
return api.getSnapJson(profile.key, snapRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getSnapGeoJson(
|
||||
profile: Profile,
|
||||
snapRequest: SnapRequest
|
||||
): SnapGeoJsonResponse {
|
||||
return api.getSnapGeoJson(profile.key, snapRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getPois(poisRequest: PoisRequest): PoisGeoJsonResponse {
|
||||
val raw = api.getPois(poisRequest)
|
||||
fun PoisGeoJsonResponse.sanitized(): PoisGeoJsonResponse =
|
||||
copy(bbox = bbox?.takeIf { it.size == 4 && it.all(Double::isFinite) })
|
||||
return raw.sanitized()
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getOptimization(optimizationRequest: OptimizationRequest): OptimizationResponse {
|
||||
return api.getOptimization(optimizationRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getElevationLine(
|
||||
elevationLineRequest: ElevationLineRequest
|
||||
): ElevationLineResponse {
|
||||
return api.getElevationLine(elevationLineRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun getElevationPoint(
|
||||
elevationPointRequest: ElevationPointRequest
|
||||
): ElevationPointResponse {
|
||||
return api.getElevationPoint(elevationPointRequest)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun geocodeSearch(
|
||||
text: String,
|
||||
apiKey: String,
|
||||
focusLon: Double?,
|
||||
focusLat: Double?,
|
||||
rectMinLon: Double?,
|
||||
rectMinLat: Double?,
|
||||
rectMaxLon: Double?,
|
||||
rectMaxLat: Double?,
|
||||
circleLon: Double?,
|
||||
circleLat: Double?,
|
||||
circleRadiusMeters: Double?,
|
||||
boundaryGid: String?,
|
||||
boundaryCountry: String?,
|
||||
sourcesCsv: String?,
|
||||
layersCsv: String?,
|
||||
size: Int?
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeSearch(
|
||||
text = text,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadiusMeters = circleRadiusMeters,
|
||||
boundaryGid = boundaryGid,
|
||||
boundaryCountry = boundaryCountry,
|
||||
sourcesCsv = sourcesCsv,
|
||||
layersCsv = layersCsv,
|
||||
size = size,
|
||||
apiKey = apiKey
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun geocodeAutocomplete(
|
||||
apiKey: String,
|
||||
text: String,
|
||||
focusLon: Double?,
|
||||
focusLat: Double?,
|
||||
rectMinLon: Double?,
|
||||
rectMinLat: Double?,
|
||||
rectMaxLon: Double?,
|
||||
rectMaxLat: Double?,
|
||||
circleLon: Double?,
|
||||
circleLat: Double?,
|
||||
circleRadius: Double?,
|
||||
country: String?,
|
||||
sources: List<String>?,
|
||||
layers: List<String>?,
|
||||
size: Int?
|
||||
): GeocodeSearchResponse {
|
||||
return api.autocomplete(
|
||||
apiKey = apiKey,
|
||||
text = text,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadius = circleRadius,
|
||||
country = country,
|
||||
sources = sources,
|
||||
layers = layers,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun geocodeStructured(
|
||||
apiKey: String,
|
||||
address: String?,
|
||||
neighbourhood: String?,
|
||||
borough: String?,
|
||||
locality: String?,
|
||||
county: String?,
|
||||
region: String?,
|
||||
country: String?,
|
||||
postalcode: String?,
|
||||
focusLon: Double?,
|
||||
focusLat: Double?,
|
||||
rectMinLon: Double?,
|
||||
rectMinLat: Double?,
|
||||
rectMaxLon: Double?,
|
||||
rectMaxLat: Double?,
|
||||
circleLon: Double?,
|
||||
circleLat: Double?,
|
||||
circleRadiusMeters: Double?,
|
||||
boundaryCountry: String?,
|
||||
layers: List<String>?,
|
||||
sources: List<String>?,
|
||||
size: Int?
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeStructured(
|
||||
apiKey = apiKey,
|
||||
address = address,
|
||||
neighbourhood = neighbourhood,
|
||||
borough = borough,
|
||||
locality = locality,
|
||||
county = county,
|
||||
region = region,
|
||||
country = country,
|
||||
postalcode = postalcode,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadiusMeters = circleRadiusMeters,
|
||||
boundaryCountry = boundaryCountry,
|
||||
layers = layers,
|
||||
sources = sources,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
override suspend fun geocodeReverse(
|
||||
apiKey: String,
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
radiusKm: Double?,
|
||||
size: Int?,
|
||||
layers: List<String>?,
|
||||
sources: List<String>?,
|
||||
boundaryCountry: String?
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeReverse(
|
||||
apiKey = apiKey,
|
||||
lon = lon,
|
||||
lat = lat,
|
||||
radiusKm = radiusKm,
|
||||
size = size,
|
||||
layers = layers,
|
||||
sources = sources,
|
||||
boundaryCountry = boundaryCountry
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* Factory for obtaining [OrsClient] instances.
|
||||
*/
|
||||
object Ors {
|
||||
/**
|
||||
* Creates a default [OrsClient] backed by Retrofit.
|
||||
*
|
||||
* @param apiKey API key obtained from openrouteservice.org
|
||||
* @param context Android context used for HTTP client construction
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(apiKey: String, context: Context): OrsClient = DefaultOrsClient(apiKey, context)
|
||||
}
|
@@ -1,196 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import org.nitri.ors.domain.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.domain.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationPointResponse
|
||||
import org.nitri.ors.domain.export.ExportRequest
|
||||
import org.nitri.ors.domain.export.ExportResponse
|
||||
import org.nitri.ors.domain.export.TopoJsonExportResponse
|
||||
import org.nitri.ors.domain.geocode.GeocodeSearchResponse
|
||||
import org.nitri.ors.domain.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.domain.isochrones.IsochronesResponse
|
||||
import org.nitri.ors.domain.matrix.MatrixRequest
|
||||
import org.nitri.ors.domain.matrix.MatrixResponse
|
||||
import org.nitri.ors.domain.optimization.OptimizationRequest
|
||||
import org.nitri.ors.domain.optimization.OptimizationResponse
|
||||
import org.nitri.ors.domain.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.domain.pois.PoisRequest
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.domain.route.RouteRequest
|
||||
import org.nitri.ors.domain.route.RouteResponse
|
||||
import org.nitri.ors.domain.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.domain.snap.SnapRequest
|
||||
import org.nitri.ors.domain.snap.SnapResponse
|
||||
|
||||
/**
|
||||
* Supported routing profiles used across the OpenRouteService endpoints.
|
||||
*
|
||||
* The [key] corresponds to the profile identifier expected by the HTTP API.
|
||||
*/
|
||||
enum class Profile(val key: String) {
|
||||
DRIVING_CAR("driving-car"),
|
||||
DRIVING_HGV("driving-hgv"),
|
||||
CYCLING_REGULAR("cycling-regular"),
|
||||
CYCLING_ROAD("cycling-road"),
|
||||
CYCLING_MOUNTAIN("cycling-mountain"),
|
||||
CYCLING_ELECTRIC("cycling-electric"),
|
||||
FOOT_WALKING("foot-walking"),
|
||||
FOOT_HIKING("foot-hiking"),
|
||||
WHEELCHAIR("wheelchair")
|
||||
}
|
||||
|
||||
/** Longitude value wrapper used by the DSL builders. */
|
||||
@JvmInline
|
||||
value class Lon(val v: Double)
|
||||
|
||||
/** Latitude value wrapper used by the DSL builders. */
|
||||
@JvmInline
|
||||
value class Lat(val v: Double)
|
||||
|
||||
/** Convenience pair for longitude/latitude coordinates. */
|
||||
data class LonLat(val lon: Double, val lat: Double)
|
||||
|
||||
/**
|
||||
* Abstraction over the OpenRouteService HTTP API.
|
||||
*
|
||||
* Implementations map these suspending functions to the respective REST
|
||||
* endpoints as documented in the
|
||||
* [OpenRouteService API reference](https://openrouteservice.org/dev/#/api-docs).
|
||||
*/
|
||||
interface OrsClient {
|
||||
|
||||
// Directions
|
||||
/**
|
||||
* Requests a route using the Directions endpoint.
|
||||
* `GET /v2/directions/{profile}`
|
||||
*/
|
||||
suspend fun getRoute(profile: Profile, routeRequest: RouteRequest): RouteResponse
|
||||
|
||||
/** Retrieves the route as GPX. */
|
||||
suspend fun getRouteGpx(profile: Profile, routeRequest: RouteRequest): String
|
||||
|
||||
/** Retrieves the route as GeoJSON feature collection. */
|
||||
suspend fun getRouteGeoJson(profile: Profile, routeRequest: RouteRequest): GeoJsonRouteResponse
|
||||
|
||||
// Export
|
||||
/** Calls the export endpoint returning plain JSON. */
|
||||
suspend fun export(profile: Profile, exportRequest: ExportRequest): ExportResponse
|
||||
|
||||
/** Same as [export] but explicitly requesting JSON output. */
|
||||
suspend fun exportJson(profile: Profile, exportRequest: ExportRequest): ExportResponse
|
||||
|
||||
/** Requests TopoJSON output from the export endpoint. */
|
||||
suspend fun exportTopoJson(profile: Profile, exportRequest: ExportRequest): TopoJsonExportResponse
|
||||
|
||||
// Isochrones
|
||||
/** Accesses the isochrones endpoint. */
|
||||
suspend fun getIsochrones(profile: Profile, isochronesRequest: IsochronesRequest): IsochronesResponse
|
||||
|
||||
// Matrix
|
||||
/** Calls the matrix endpoint and returns distance/duration matrices. */
|
||||
suspend fun getMatrix(profile: Profile, matrixRequest: MatrixRequest): MatrixResponse
|
||||
|
||||
// Snapping
|
||||
/** Snaps coordinates to the road network. */
|
||||
suspend fun getSnap(profile: Profile, snapRequest: SnapRequest): SnapResponse
|
||||
|
||||
/** Snaps coordinates and returns the JSON variant of the response. */
|
||||
suspend fun getSnapJson(profile: Profile, snapRequest: SnapRequest): SnapResponse
|
||||
|
||||
/** Snaps coordinates and returns a GeoJSON response. */
|
||||
suspend fun getSnapGeoJson(profile: Profile, snapRequest: SnapRequest): SnapGeoJsonResponse
|
||||
|
||||
// POIs
|
||||
/** Queries points of interest and returns a GeoJSON feature collection. */
|
||||
suspend fun getPois(poisRequest: PoisRequest): PoisGeoJsonResponse
|
||||
|
||||
// Optimization
|
||||
/** Delegates to the VROOM based optimization service. */
|
||||
suspend fun getOptimization(optimizationRequest: OptimizationRequest): OptimizationResponse
|
||||
|
||||
// Elevation
|
||||
/** Calls the elevation/line endpoint. */
|
||||
suspend fun getElevationLine(elevationLineRequest: ElevationLineRequest): ElevationLineResponse
|
||||
|
||||
/** Calls the elevation/point endpoint. */
|
||||
suspend fun getElevationPoint(elevationPointRequest: ElevationPointRequest): ElevationPointResponse
|
||||
|
||||
// Geocode
|
||||
/** Forward geocoding search endpoint. */
|
||||
suspend fun geocodeSearch(
|
||||
text: String,
|
||||
apiKey: String,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadiusMeters: Double? = null,
|
||||
boundaryGid: String? = null,
|
||||
boundaryCountry: String? = null,
|
||||
sourcesCsv: String? = null,
|
||||
layersCsv: String? = null,
|
||||
size: Int? = 10,
|
||||
): GeocodeSearchResponse
|
||||
|
||||
/** Autocomplete endpoint returning suggestions for a partial query. */
|
||||
suspend fun geocodeAutocomplete(
|
||||
apiKey: String,
|
||||
text: String,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadius: Double? = null,
|
||||
country: String? = null,
|
||||
sources: List<String>? = null,
|
||||
layers: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse
|
||||
|
||||
/** Structured forward geocoding using discrete address fields. */
|
||||
suspend fun geocodeStructured(
|
||||
apiKey: String,
|
||||
address: String? = null,
|
||||
neighbourhood: String? = null,
|
||||
borough: String? = null,
|
||||
locality: String? = null,
|
||||
county: String? = null,
|
||||
region: String? = null,
|
||||
country: String? = null,
|
||||
postalcode: String? = null,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadiusMeters: Double? = null,
|
||||
boundaryCountry: String? = null,
|
||||
layers: List<String>? = null,
|
||||
sources: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse
|
||||
|
||||
/** Reverse geocoding for a single coordinate. */
|
||||
suspend fun geocodeReverse(
|
||||
apiKey: String,
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
radiusKm: Double? = null,
|
||||
size: Int? = null,
|
||||
layers: List<String>? = null,
|
||||
sources: List<String>? = null,
|
||||
boundaryCountry: String? = null,
|
||||
): GeocodeSearchResponse
|
||||
}
|
@@ -1,261 +0,0 @@
|
||||
package org.nitri.ors.api
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import org.nitri.ors.domain.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.domain.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationPointResponse
|
||||
import org.nitri.ors.domain.export.ExportRequest
|
||||
import org.nitri.ors.domain.export.ExportResponse
|
||||
import org.nitri.ors.domain.export.TopoJsonExportResponse
|
||||
import org.nitri.ors.domain.geocode.GeocodeSearchResponse
|
||||
import org.nitri.ors.domain.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.domain.isochrones.IsochronesResponse
|
||||
import org.nitri.ors.domain.matrix.MatrixRequest
|
||||
import org.nitri.ors.domain.matrix.MatrixResponse
|
||||
import org.nitri.ors.domain.optimization.OptimizationRequest
|
||||
import org.nitri.ors.domain.optimization.OptimizationResponse
|
||||
import org.nitri.ors.domain.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.domain.pois.PoisRequest
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.domain.route.RouteRequest
|
||||
import org.nitri.ors.domain.route.RouteResponse
|
||||
import org.nitri.ors.domain.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.domain.snap.SnapRequest
|
||||
import org.nitri.ors.domain.snap.SnapResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface OpenRouteServiceApi {
|
||||
|
||||
// Directions
|
||||
@GET("v2/directions/{profile}")
|
||||
suspend fun getRouteSimple(
|
||||
@Path("profile") profile: String,
|
||||
@Query("start") start: String, // e.g. "8.681495,49.41461"
|
||||
@Query("end") end: String // e.g. "8.687872,49.420318"
|
||||
): RouteResponse
|
||||
|
||||
@POST("v2/directions/{profile}")
|
||||
suspend fun getRoute(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: RouteRequest
|
||||
): RouteResponse
|
||||
|
||||
@POST("v2/directions/{profile}/json")
|
||||
suspend fun getRouteJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: RouteRequest
|
||||
): RouteResponse
|
||||
|
||||
@POST("v2/directions/{profile}/gpx")
|
||||
suspend fun getRouteGpx(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: RouteRequest
|
||||
): Response<ResponseBody>
|
||||
|
||||
@POST("v2/directions/{profile}/geojson")
|
||||
suspend fun getRouteGeoJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: RouteRequest
|
||||
): GeoJsonRouteResponse
|
||||
|
||||
// Export endpoints
|
||||
@POST("v2/export/{profile}")
|
||||
suspend fun export(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: ExportRequest
|
||||
): ExportResponse
|
||||
|
||||
@POST("v2/export/{profile}/json")
|
||||
suspend fun exportJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: ExportRequest
|
||||
): ExportResponse
|
||||
|
||||
@POST("v2/export/{profile}/topojson")
|
||||
suspend fun exportTopoJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: ExportRequest
|
||||
): TopoJsonExportResponse
|
||||
|
||||
// Isochrones endpoint
|
||||
@POST("v2/isochrones/{profile}")
|
||||
suspend fun getIsochrones(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: IsochronesRequest
|
||||
): IsochronesResponse
|
||||
|
||||
// Matrix endpoint
|
||||
@POST("v2/matrix/{profile}")
|
||||
suspend fun getMatrix(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: MatrixRequest
|
||||
): MatrixResponse
|
||||
|
||||
// Snapping
|
||||
|
||||
@POST("v2/snap/{profile}")
|
||||
suspend fun getSnap(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: SnapRequest
|
||||
): SnapResponse
|
||||
|
||||
@POST("v2/snap/{profile}/json")
|
||||
suspend fun getSnapJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: SnapRequest
|
||||
): SnapResponse
|
||||
|
||||
@POST("v2/snap/{profile}/geojson")
|
||||
suspend fun getSnapGeoJson(
|
||||
@Path("profile") profile: String,
|
||||
@Body request: SnapRequest
|
||||
): SnapGeoJsonResponse
|
||||
|
||||
// POIs
|
||||
|
||||
@POST("pois")
|
||||
suspend fun getPois(
|
||||
@Body request: PoisRequest
|
||||
): PoisGeoJsonResponse
|
||||
|
||||
// Optimization
|
||||
|
||||
@POST("optimization")
|
||||
suspend fun getOptimization(
|
||||
@Body request: OptimizationRequest
|
||||
): OptimizationResponse
|
||||
|
||||
// Elevation
|
||||
|
||||
@POST("elevation/line")
|
||||
suspend fun getElevationLine(
|
||||
@Body request: ElevationLineRequest
|
||||
): ElevationLineResponse
|
||||
|
||||
@GET("elevation/point")
|
||||
suspend fun getElevationPointSimple(
|
||||
@Query("geometry") start: String, // e.g. "8.681495,49.41461"
|
||||
): RouteResponse
|
||||
|
||||
@POST("elevation/point")
|
||||
suspend fun getElevationPoint(
|
||||
@Body request: ElevationPointRequest
|
||||
): ElevationPointResponse
|
||||
|
||||
// Geocode
|
||||
|
||||
@GET("geocode/search")
|
||||
suspend fun geocodeSearch(
|
||||
@Query("text") text: String,
|
||||
|
||||
// Optional focus point
|
||||
@Query("focus.point.lon") focusLon: Double? = null,
|
||||
@Query("focus.point.lat") focusLat: Double? = null,
|
||||
|
||||
// Optional rectangular boundary
|
||||
@Query("boundary.rect.min_lon") rectMinLon: Double? = null,
|
||||
@Query("boundary.rect.min_lat") rectMinLat: Double? = null,
|
||||
@Query("boundary.rect.max_lon") rectMaxLon: Double? = null,
|
||||
@Query("boundary.rect.max_lat") rectMaxLat: Double? = null,
|
||||
|
||||
// Optional circular boundary
|
||||
@Query("boundary.circle.lon") circleLon: Double? = null,
|
||||
@Query("boundary.circle.lat") circleLat: Double? = null,
|
||||
@Query("boundary.circle.radius") circleRadiusMeters: Double? = null,
|
||||
|
||||
// Other optional filters
|
||||
@Query("boundary.gid") boundaryGid: String? = null,
|
||||
// Pass comma-separated if multiple, e.g. "DE,AT"
|
||||
@Query("boundary.country") boundaryCountry: String? = null,
|
||||
// Pelias expects comma-separated values; join your list before passing.
|
||||
@Query("sources") sourcesCsv: String? = null, // e.g. "osm,oa,gn,wof"
|
||||
@Query("layers") layersCsv: String? = null, // e.g. "region,country,locality,address"
|
||||
@Query("size") size: Int? = 10,
|
||||
|
||||
// Geocoder uses api_key as query
|
||||
@Query("api_key") apiKey: String
|
||||
): GeocodeSearchResponse
|
||||
|
||||
@GET("geocode/autocomplete")
|
||||
suspend fun autocomplete(
|
||||
@Query("api_key") apiKey: String,
|
||||
@Query("text") text: String,
|
||||
@Query("focus.point.lon") focusLon: Double? = null,
|
||||
@Query("focus.point.lat") focusLat: Double? = null,
|
||||
@Query("boundary.rect.min_lon") rectMinLon: Double? = null,
|
||||
@Query("boundary.rect.min_lat") rectMinLat: Double? = null,
|
||||
@Query("boundary.rect.max_lon") rectMaxLon: Double? = null,
|
||||
@Query("boundary.rect.max_lat") rectMaxLat: Double? = null,
|
||||
@Query("boundary.circle.lon") circleLon: Double? = null,
|
||||
@Query("boundary.circle.lat") circleLat: Double? = null,
|
||||
@Query("boundary.circle.radius") circleRadius: Double? = null,
|
||||
@Query("boundary.country") country: String? = null,
|
||||
@Query("sources") sources: List<String>? = null,
|
||||
@Query("layers") layers: List<String>? = null,
|
||||
@Query("size") size: Int? = null
|
||||
): GeocodeSearchResponse
|
||||
|
||||
|
||||
@GET("geocode/search/structured")
|
||||
suspend fun geocodeStructured(
|
||||
@Query("api_key") apiKey: String,
|
||||
|
||||
// Structured query parts (all optional; send only what you have)
|
||||
@Query("address") address: String? = null,
|
||||
@Query("neighbourhood") neighbourhood: String? = null,
|
||||
@Query("borough") borough: String? = null,
|
||||
@Query("locality") locality: String? = null, // e.g., city
|
||||
@Query("county") county: String? = null,
|
||||
@Query("region") region: String? = null, // e.g., state/province
|
||||
@Query("country") country: String? = null, // ISO code or name
|
||||
@Query("postalcode") postalcode: String? = null,
|
||||
|
||||
// Focus point (used for ranking)
|
||||
@Query("focus.point.lon") focusLon: Double? = null,
|
||||
@Query("focus.point.lat") focusLat: Double? = null,
|
||||
|
||||
// Bounding rectangle (optional)
|
||||
@Query("boundary.rect.min_lon") rectMinLon: Double? = null,
|
||||
@Query("boundary.rect.min_lat") rectMinLat: Double? = null,
|
||||
@Query("boundary.rect.max_lon") rectMaxLon: Double? = null,
|
||||
@Query("boundary.rect.max_lat") rectMaxLat: Double? = null,
|
||||
|
||||
// Bounding circle (optional)
|
||||
@Query("boundary.circle.lon") circleLon: Double? = null,
|
||||
@Query("boundary.circle.lat") circleLat: Double? = null,
|
||||
@Query("boundary.circle.radius") circleRadiusMeters: Double? = null,
|
||||
|
||||
// Limit results to a specific country (ISO code)
|
||||
@Query("boundary.country") boundaryCountry: String? = null,
|
||||
|
||||
// Filters
|
||||
@Query("layers") layers: List<String>? = null, // e.g. ["address","venue","street","locality","region","country"]
|
||||
@Query("sources") sources: List<String>? = null, // e.g. ["osm","oa","gn","wof"]
|
||||
|
||||
// Number of results (default 10)
|
||||
@Query("size") size: Int? = null
|
||||
): GeocodeSearchResponse
|
||||
|
||||
|
||||
@GET("geocode/reverse")
|
||||
suspend fun geocodeReverse(
|
||||
@Query("api_key") apiKey: String,
|
||||
|
||||
// required
|
||||
@Query("point.lon") lon: Double,
|
||||
@Query("point.lat") lat: Double,
|
||||
|
||||
// optional ranking/filters
|
||||
@Query("boundary.circle.radius") radiusKm: Double? = null, // Pelias expects km
|
||||
@Query("size") size: Int? = null, // default 10
|
||||
@Query("layers") layers: List<String>? = null, // e.g. ["address","venue"]
|
||||
@Query("sources") sources: List<String>? = null, // e.g. ["osm","oa","gn","wof"]
|
||||
@Query("boundary.country") boundaryCountry: String? = null // ISO code, e.g. "FR"
|
||||
): GeocodeSearchResponse
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
|
||||
/** DSL for building [ElevationLineRequest] instances. */
|
||||
inline fun elevationLineRequest(build: ElevationLineRequestBuilder.() -> Unit): ElevationLineRequest =
|
||||
ElevationLineRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [elevationLineRequest]. */
|
||||
class ElevationLineRequestBuilder {
|
||||
var formatIn: String = ElevationFormats.POLYLINE
|
||||
var formatOut: String = ElevationFormats.GEOJSON
|
||||
var dataset: String? = null
|
||||
|
||||
// flexible geometry holder; convenience helpers below
|
||||
private var geometry: JsonElement? = null
|
||||
|
||||
fun geometry(element: JsonElement) = apply { this.geometry = element }
|
||||
|
||||
// Convenience: provide a simple list of [lon,lat] pairs for polyline format
|
||||
fun polyline(vararg coords: Pair<Double, Double>) = apply {
|
||||
formatIn = ElevationFormats.POLYLINE
|
||||
geometry = buildJsonArray {
|
||||
coords.forEach { (lon, lat) ->
|
||||
add(buildJsonArray { add(JsonPrimitive(lon)); add(JsonPrimitive(lat)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): ElevationLineRequest {
|
||||
val g = requireNotNull(geometry) { "geometry is required" }
|
||||
return ElevationLineRequest(
|
||||
formatIn = formatIn,
|
||||
formatOut = formatOut,
|
||||
geometry = g,
|
||||
dataset = dataset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** DSL for constructing [ElevationPointRequest] objects. */
|
||||
inline fun elevationPointRequest(build: ElevationPointRequestBuilder.() -> Unit): ElevationPointRequest =
|
||||
ElevationPointRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [elevationPointRequest]. */
|
||||
class ElevationPointRequestBuilder {
|
||||
var formatIn: String = "point"
|
||||
var formatOut: String = "geojson"
|
||||
var dataset: String? = null
|
||||
private var lon: Double? = null
|
||||
private var lat: Double? = null
|
||||
|
||||
fun point(lon: Double, lat: Double) = apply { this.lon = lon; this.lat = lat }
|
||||
|
||||
fun build(): ElevationPointRequest {
|
||||
val lo = requireNotNull(lon) { "lon is required" }
|
||||
val la = requireNotNull(lat) { "lat is required" }
|
||||
return ElevationPointRequest(
|
||||
formatIn = formatIn,
|
||||
formatOut = formatOut,
|
||||
dataset = dataset,
|
||||
geometry = listOf(lo, la)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Java-friendly builders
|
||||
class ElevationLineRequestBuilderJ {
|
||||
private val dsl = ElevationLineRequestBuilder()
|
||||
fun formatIn(v: String) = apply { dsl.formatIn = v }
|
||||
fun formatOut(v: String) = apply { dsl.formatOut = v }
|
||||
fun dataset(v: String?) = apply { dsl.dataset = v }
|
||||
fun geometry(element: JsonElement) = apply { dsl.geometry(element) }
|
||||
fun polyline(coords: List<List<Double>>) = apply {
|
||||
// Accept list of [lon,lat]; convert to JsonArray
|
||||
val arr: JsonArray = buildJsonArray {
|
||||
coords.forEach { c ->
|
||||
add(buildJsonArray { add(JsonPrimitive(c[0])); add(JsonPrimitive(c[1])) })
|
||||
}
|
||||
}
|
||||
dsl.formatIn = ElevationFormats.POLYLINE
|
||||
dsl.geometry(arr)
|
||||
}
|
||||
fun build(): ElevationLineRequest = dsl.build()
|
||||
}
|
||||
|
||||
class ElevationPointRequestBuilderJ {
|
||||
private val dsl = ElevationPointRequestBuilder()
|
||||
fun formatIn(v: String) = apply { dsl.formatIn = v }
|
||||
fun formatOut(v: String) = apply { dsl.formatOut = v }
|
||||
fun dataset(v: String?) = apply { dsl.dataset = v }
|
||||
fun point(lon: Double, lat: Double) = apply { dsl.point(lon, lat) }
|
||||
fun build(): ElevationPointRequest = dsl.build()
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
|
||||
@Serializable
|
||||
data class ElevationLineRequest(
|
||||
/** One of: "geojson", "polyline", "encodedpolyline5", "encodedpolyline6" */
|
||||
@SerialName("format_in") val formatIn: String,
|
||||
|
||||
/** One of: "geojson", "polyline", "encodedpolyline5", "encodedpolyline6" */
|
||||
@SerialName("format_out") val formatOut: String,
|
||||
|
||||
/**
|
||||
* Geometry payload. The API accepts different shapes depending on format:
|
||||
* - format_in = "geojson" -> a GeoJSON LineString object
|
||||
* - format_in = "polyline" -> [[lon,lat], [lon,lat], ...]
|
||||
* - format_in = "encodedpolyline5/6"-> a single encoded polyline string
|
||||
*
|
||||
* Using JsonElement keeps this field flexible for all variants.
|
||||
*/
|
||||
val geometry: JsonElement,
|
||||
|
||||
/** Optional: pick a specific elevation dataset (e.g., "SRTM", "COP90", …) */
|
||||
val dataset: String? = null
|
||||
)
|
||||
|
||||
object ElevationFormats {
|
||||
const val GEOJSON = "geojson"
|
||||
const val POLYLINE = "polyline"
|
||||
const val ENCODED_5 = "encodedpolyline5"
|
||||
const val ENCODED_6 = "encodedpolyline6"
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ElevationLineResponse(
|
||||
val attribution: String? = null,
|
||||
val geometry: ElevationLineGeometry,
|
||||
val timestamp: Long? = null,
|
||||
val version: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ElevationLineGeometry(
|
||||
val type: String, // "LineString"
|
||||
/** Coordinates with elevation: [lon, lat, ele] (API may return [lon,lat] if ele missing) */
|
||||
val coordinates: List<List<Double>>
|
||||
)
|
@@ -1,22 +0,0 @@
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Request for the `elevation/point` endpoint. */
|
||||
@Serializable
|
||||
data class ElevationPointRequest(
|
||||
@SerialName("format_in")
|
||||
/** Input format, e.g. `point`. */
|
||||
val formatIn: String,
|
||||
|
||||
@SerialName("format_out")
|
||||
/** Output format: `geojson` or `point`. */
|
||||
val formatOut: String = "geojson",
|
||||
|
||||
/** Optional elevation dataset name, e.g. `srtm`. */
|
||||
val dataset: String? = null,
|
||||
|
||||
/** Coordinate as `[lon, lat]`. */
|
||||
val geometry: List<Double>
|
||||
)
|
@@ -1,17 +0,0 @@
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ElevationPointResponse(
|
||||
val attribution: String,
|
||||
val geometry: ElevationPointGeometry,
|
||||
val timestamp: Long,
|
||||
val version: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ElevationPointGeometry(
|
||||
val coordinates: List<Double>, // [lon, lat, elevation]
|
||||
val type: String // "Point"
|
||||
)
|
@@ -1,45 +0,0 @@
|
||||
package org.nitri.ors.domain.export
|
||||
|
||||
/** DSL for creating [ExportRequest] objects. */
|
||||
inline fun exportRequest(build: ExportRequestBuilder.() -> Unit): ExportRequest =
|
||||
ExportRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [exportRequest]. */
|
||||
class ExportRequestBuilder {
|
||||
private var bbox: List<List<Double>>? = null
|
||||
private var id: String? = null
|
||||
private var geometry: Boolean? = null
|
||||
|
||||
fun bbox(minLon: Double, minLat: Double, maxLon: Double, maxLat: Double) = apply {
|
||||
bbox = listOf(listOf(minLon, minLat), listOf(maxLon, maxLat))
|
||||
}
|
||||
|
||||
fun id(id: String) = apply { this.id = id }
|
||||
fun geometry(geometry: Boolean?) = apply { this.geometry = geometry }
|
||||
|
||||
fun build(): ExportRequest {
|
||||
val b = requireNotNull(bbox) { "bbox is required" }
|
||||
val i = id ?: "export_request"
|
||||
return ExportRequest(bbox = b, id = i, geometry = geometry)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class ExportRequestBuilderJ {
|
||||
private var bbox: List<List<Double>>? = null
|
||||
private var id: String? = null
|
||||
private var geometry: Boolean? = null
|
||||
|
||||
fun bbox(minLon: Double, minLat: Double, maxLon: Double, maxLat: Double) = apply {
|
||||
bbox = listOf(listOf(minLon, minLat), listOf(maxLon, maxLat))
|
||||
}
|
||||
|
||||
fun id(id: String) = apply { this.id = id }
|
||||
fun geometry(geometry: Boolean?) = apply { this.geometry = geometry }
|
||||
|
||||
fun build(): ExportRequest {
|
||||
val b = requireNotNull(bbox) { "bbox is required" }
|
||||
val i = id ?: "export_request"
|
||||
return ExportRequest(bbox = b, id = i, geometry = geometry)
|
||||
}
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
package org.nitri.ors.domain.export
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Request payload for the export endpoint. */
|
||||
@Serializable
|
||||
data class ExportRequest(
|
||||
/** Bounding box specified as `[[minLon,minLat],[maxLon,maxLat]]`. */
|
||||
val bbox: List<List<Double>>,
|
||||
/** Client-specified identifier. */
|
||||
val id: String,
|
||||
/** Whether to include full geometry in the response. */
|
||||
val geometry: Boolean? = null
|
||||
)
|
@@ -1,59 +0,0 @@
|
||||
package org.nitri.ors.domain.export
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.doubleOrNull
|
||||
|
||||
/** Graph export response containing nodes and edges. */
|
||||
@Serializable
|
||||
data class ExportResponse(
|
||||
val nodes: List<Node> = emptyList(),
|
||||
val edges: List<GraphEdge> = emptyList()
|
||||
)
|
||||
|
||||
/** Node entry in an [ExportResponse]. */
|
||||
@Serializable
|
||||
data class Node(
|
||||
@SerialName("nodeId") val nodeId: Long,
|
||||
/** `[lon, lat]` coordinate. */
|
||||
val location: List<Double>
|
||||
)
|
||||
|
||||
/** Graph edge entry in an [ExportResponse]. */
|
||||
@Serializable
|
||||
data class GraphEdge(
|
||||
@SerialName("fromId") val fromId: Long,
|
||||
@SerialName("toId") val toId: Long,
|
||||
@Serializable(with = StringAsDoubleSerializer::class)
|
||||
val weight: Double
|
||||
)
|
||||
|
||||
/**
|
||||
* ORS sometimes returns numeric fields (e.g., "weight") as strings.
|
||||
* This serializer accepts either a JSON number or a string number.
|
||||
*/
|
||||
object StringAsDoubleSerializer : KSerializer<Double> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("StringAsDouble", PrimitiveKind.DOUBLE)
|
||||
|
||||
override fun deserialize(decoder: Decoder): Double {
|
||||
return if (decoder is JsonDecoder) {
|
||||
val prim = decoder.decodeJsonElement() as JsonPrimitive
|
||||
prim.doubleOrNull ?: prim.content.toDouble()
|
||||
} else {
|
||||
decoder.decodeDouble()
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Double) {
|
||||
encoder.encodeDouble(value)
|
||||
}
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
package org.nitri.ors.domain.export
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** TopoJSON variant of the export response. */
|
||||
@Serializable
|
||||
data class TopoJsonExportResponse(
|
||||
val type: String,
|
||||
val objects: TopoObjects,
|
||||
/** Top-level arcs represented as `[lon, lat]` coordinate pairs. */
|
||||
val arcs: List<List<List<Double>>>,
|
||||
val bbox: List<Double>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TopoObjects(
|
||||
val network: GeometryCollection
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeometryCollection(
|
||||
val type: String,
|
||||
val geometries: List<TopoGeometry>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TopoGeometry(
|
||||
val type: String,
|
||||
/** Indices into the top-level [TopoJsonExportResponse.arcs] array. */
|
||||
val arcs: List<Int>,
|
||||
val properties: GeometryProps? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeometryProps(
|
||||
/** Edge weight often supplied as a string. */
|
||||
val weight: String? = null,
|
||||
@SerialName("node_from") val nodeFrom: Long? = null,
|
||||
@SerialName("node_to") val nodeTo: Long? = null
|
||||
)
|
||||
|
@@ -1,157 +0,0 @@
|
||||
package org.nitri.ors.domain.geocode
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Serializable
|
||||
data class GeocodeSearchResponse(
|
||||
val geocoding: Geocoding? = null,
|
||||
val type: String? = null, // "FeatureCollection"
|
||||
val features: List<GeocodeFeature> = emptyList(),
|
||||
val bbox: List<Double>? = null,
|
||||
val errors: List<String>? = null,
|
||||
val warnings: List<String>? = null,
|
||||
val engine: Engine? = null,
|
||||
val timestamp: Long? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Geocoding(
|
||||
val version: String? = null, // "0.2"
|
||||
val attribution: String? = null,
|
||||
val query: GeocodeQuery? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Engine(
|
||||
val name: String? = null, // "Pelias"
|
||||
val author: String? = null, // "Mapzen"
|
||||
val version: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Pelias "query" block varies by endpoint; everything is optional.
|
||||
* Fields like "point.lat" are addressed via @SerialName to match JSON.
|
||||
*/
|
||||
@Serializable
|
||||
data class GeocodeQuery(
|
||||
// common
|
||||
val text: String? = null,
|
||||
val size: Int? = null,
|
||||
val layers: List<String>? = null,
|
||||
val sources: List<String>? = null,
|
||||
val private: Boolean? = null,
|
||||
val parsed_text: JsonObject? = null, // present on some search/structured responses
|
||||
val lang: PeliasLang? = null,
|
||||
val querySize: Int? = null,
|
||||
|
||||
// focus point (search/autocomplete)
|
||||
@SerialName("focus.point.lon") val focusPointLon: Double? = null,
|
||||
@SerialName("focus.point.lat") val focusPointLat: Double? = null,
|
||||
|
||||
// reverse & circle bounds
|
||||
@SerialName("point.lon") val pointLon: Double? = null,
|
||||
@SerialName("point.lat") val pointLat: Double? = null,
|
||||
@SerialName("boundary.circle.lon") val boundaryCircleLon: Double? = null,
|
||||
@SerialName("boundary.circle.lat") val boundaryCircleLat: Double? = null,
|
||||
@SerialName("boundary.circle.radius") val boundaryCircleRadius: Double? = null,
|
||||
|
||||
// rect bounds (search/autocomplete/structured)
|
||||
@SerialName("boundary.rect.min_lon") val rectMinLon: Double? = null,
|
||||
@SerialName("boundary.rect.min_lat") val rectMinLat: Double? = null,
|
||||
@SerialName("boundary.rect.max_lon") val rectMaxLon: Double? = null,
|
||||
@SerialName("boundary.rect.max_lat") val rectMaxLat: Double? = null,
|
||||
|
||||
// country limit (ISO-3166-1 alpha-2)
|
||||
@SerialName("boundary.country") val boundaryCountry: String? = null,
|
||||
|
||||
// structured forward fields (all optional; API requires at least one)
|
||||
val venue: String? = null,
|
||||
val address: String? = null,
|
||||
val neighbourhood: String? = null,
|
||||
val borough: String? = null,
|
||||
val locality: String? = null,
|
||||
val county: String? = null,
|
||||
val region: String? = null,
|
||||
val country: String? = null,
|
||||
val postcode: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PeliasLang(
|
||||
val name: String? = null, // "English"
|
||||
val iso6391: String? = null, // "en"
|
||||
val iso6393: String? = null, // "eng"
|
||||
val via: String? = null, // "header"
|
||||
val defaulted: Boolean? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeocodeFeature(
|
||||
val type: String? = null, // "Feature"
|
||||
val geometry: GeocodeGeometry? = null,
|
||||
val properties: GeocodeProperties? = null,
|
||||
val bbox: List<Double>? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeocodeGeometry(
|
||||
val type: String? = null, // typically "Point"
|
||||
val coordinates: List<Double> = emptyList() // [lon, lat] (+ elevation if provided)
|
||||
)
|
||||
|
||||
/**
|
||||
* Properties are rich and vary by source; keep them optional.
|
||||
* `addendum` is left as JsonObject to pass through extra provider-specific content (e.g., OSM).
|
||||
*/
|
||||
@Serializable
|
||||
data class GeocodeProperties(
|
||||
val id: String? = null,
|
||||
val gid: String? = null,
|
||||
val layer: String? = null,
|
||||
val source: String? = null,
|
||||
@SerialName("source_id") val sourceId: String? = null,
|
||||
|
||||
val name: String? = null,
|
||||
val confidence: Double? = null,
|
||||
val distance: Double? = null,
|
||||
val accuracy: String? = null,
|
||||
|
||||
// address-ish
|
||||
val label: String? = null,
|
||||
val street: String? = null,
|
||||
val housenumber: String? = null,
|
||||
val postalcode: String? = null,
|
||||
|
||||
// admin hierarchy
|
||||
val country: String? = null,
|
||||
@SerialName("country_gid") val countryGid: String? = null,
|
||||
@SerialName("country_a") val countryA: String? = null,
|
||||
|
||||
val macroregion: String? = null,
|
||||
@SerialName("macroregion_gid") val macroregionGid: String? = null,
|
||||
@SerialName("macroregion_a") val macroregionA: String? = null,
|
||||
|
||||
val region: String? = null,
|
||||
@SerialName("region_gid") val regionGid: String? = null,
|
||||
@SerialName("region_a") val regionA: String? = null,
|
||||
|
||||
val localadmin: String? = null,
|
||||
@SerialName("localadmin_gid") val localadminGid: String? = null,
|
||||
|
||||
val locality: String? = null,
|
||||
@SerialName("locality_gid") val localityGid: String? = null,
|
||||
|
||||
val borough: String? = null,
|
||||
@SerialName("borough_gid") val boroughGid: String? = null,
|
||||
|
||||
val neighbourhood: String? = null,
|
||||
@SerialName("neighbourhood_gid") val neighbourhoodGid: String? = null,
|
||||
|
||||
val continent: String? = null,
|
||||
@SerialName("continent_gid") val continentGid: String? = null,
|
||||
|
||||
// provider extras: e.g. wheelchair, website, wikidata, etc.
|
||||
val addendum: JsonObject? = null
|
||||
)
|
@@ -1,84 +0,0 @@
|
||||
package org.nitri.ors.domain.isochrones
|
||||
|
||||
/** DSL for constructing [IsochronesRequest] objects. */
|
||||
inline fun isochronesRequest(build: IsochronesRequestBuilder.() -> Unit): IsochronesRequest =
|
||||
IsochronesRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [isochronesRequest]. */
|
||||
class IsochronesRequestBuilder {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
private var range: MutableList<Int> = mutableListOf()
|
||||
|
||||
var rangeType: String? = null
|
||||
var attributes: List<String>? = null
|
||||
var id: String? = null
|
||||
var intersections: Boolean? = null
|
||||
var interval: Int? = null
|
||||
var locationType: String? = null
|
||||
var smoothing: Double? = null
|
||||
var options: IsochronesOptions? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
fun rangeSeconds(vararg seconds: Int) = apply { range.addAll(seconds.toList()) }
|
||||
|
||||
fun build(): IsochronesRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
require(range.isNotEmpty()) { "At least one range value is required" }
|
||||
return IsochronesRequest(
|
||||
locations = locations.toList(),
|
||||
range = range.toList(),
|
||||
rangeType = rangeType,
|
||||
attributes = attributes,
|
||||
id = id,
|
||||
intersections = intersections,
|
||||
interval = interval,
|
||||
locationType = locationType,
|
||||
smoothing = smoothing,
|
||||
options = options
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class IsochronesRequestBuilderJ {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
private val range: MutableList<Int> = mutableListOf()
|
||||
|
||||
private var rangeType: String? = null
|
||||
private var attributes: List<String>? = null
|
||||
private var id: String? = null
|
||||
private var intersections: Boolean? = null
|
||||
private var interval: Int? = null
|
||||
private var locationType: String? = null
|
||||
private var smoothing: Double? = null
|
||||
private var options: IsochronesOptions? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
fun addRange(seconds: Int) = apply { range.add(seconds) }
|
||||
|
||||
fun rangeType(rangeType: String?) = apply { this.rangeType = rangeType }
|
||||
fun attributes(attributes: List<String>?) = apply { this.attributes = attributes }
|
||||
fun id(id: String?) = apply { this.id = id }
|
||||
fun intersections(intersections: Boolean?) = apply { this.intersections = intersections }
|
||||
fun interval(interval: Int?) = apply { this.interval = interval }
|
||||
fun locationType(locationType: String?) = apply { this.locationType = locationType }
|
||||
fun smoothing(smoothing: Double?) = apply { this.smoothing = smoothing }
|
||||
fun options(options: IsochronesOptions?) = apply { this.options = options }
|
||||
|
||||
fun build(): IsochronesRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
require(range.isNotEmpty()) { "At least one range value is required" }
|
||||
return IsochronesRequest(
|
||||
locations = locations.toList(),
|
||||
range = range.toList(),
|
||||
rangeType = rangeType,
|
||||
attributes = attributes,
|
||||
id = id,
|
||||
intersections = intersections,
|
||||
interval = interval,
|
||||
locationType = locationType,
|
||||
smoothing = smoothing,
|
||||
options = options
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,68 +0,0 @@
|
||||
package org.nitri.ors.domain.isochrones
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
/**
|
||||
* ORS Isochrones request
|
||||
* Docs: /v2/isochrones/{profile}
|
||||
*
|
||||
* Required: locations, range
|
||||
* Optional are nullable and omitted from JSON when null.
|
||||
*/
|
||||
@Serializable
|
||||
data class IsochronesRequest(
|
||||
/** [lon, lat] pairs */
|
||||
val locations: List<List<Double>>,
|
||||
|
||||
/** In seconds for range_type="time" (default) or in meters for "distance". */
|
||||
val range: List<Int>,
|
||||
|
||||
/** "time" (default) or "distance" */
|
||||
@SerialName("range_type")
|
||||
val rangeType: String? = null,
|
||||
|
||||
/** e.g. ["area"] (see docs for more) */
|
||||
val attributes: List<String>? = null,
|
||||
|
||||
/** Optional request id that is echoed back in metadata */
|
||||
val id: String? = null,
|
||||
|
||||
/** If true, include intersection info in response */
|
||||
val intersections: Boolean? = null,
|
||||
|
||||
/** If set, returns multiple bands every `interval` (same unit as range_type) */
|
||||
val interval: Int? = null,
|
||||
|
||||
/** "start" (default) or "destination" */
|
||||
@SerialName("location_type")
|
||||
val locationType: String? = null,
|
||||
|
||||
/** Smoothing factor, e.g. 25 */
|
||||
val smoothing: Double? = null,
|
||||
|
||||
/** Extra tweaks */
|
||||
val options: IsochronesOptions? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Subset of useful options. Extend as needed.
|
||||
*/
|
||||
@Serializable
|
||||
data class IsochronesOptions(
|
||||
/** "controlled" or "all" */
|
||||
@SerialName("avoid_borders")
|
||||
val avoidBorders: String? = null,
|
||||
|
||||
/** e.g. ["ferries","tollways"] */
|
||||
@SerialName("avoid_features")
|
||||
val avoidFeatures: List<String>? = null,
|
||||
|
||||
/**
|
||||
* Avoid polygons (GeoJSON geometry). Keep generic to stay flexible:
|
||||
* supply a Feature/Geometry object you serialize yourself.
|
||||
*/
|
||||
@SerialName("avoid_polygons")
|
||||
val avoidPolygons: JsonElement? = null
|
||||
)
|
@@ -1,61 +0,0 @@
|
||||
package org.nitri.ors.domain.isochrones
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** GeoJSON response returned by the isochrones endpoint. */
|
||||
@Serializable
|
||||
data class IsochronesResponse(
|
||||
val type: String,
|
||||
val bbox: List<Double>,
|
||||
val features: List<IsochroneFeature>,
|
||||
val metadata: IsochronesMetadata
|
||||
)
|
||||
|
||||
/** Individual isochrone feature. */
|
||||
@Serializable
|
||||
data class IsochroneFeature(
|
||||
val type: String,
|
||||
val properties: IsochroneProperties,
|
||||
val geometry: IsochroneGeometry
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IsochroneProperties(
|
||||
@SerialName("group_index") val groupIndex: Int,
|
||||
val value: Double, // <-- was Int
|
||||
val center: List<Double>
|
||||
)
|
||||
|
||||
/** Geometry of an isochrone feature. */
|
||||
@Serializable
|
||||
data class IsochroneGeometry(
|
||||
val type: String,
|
||||
val coordinates: List<List<List<Double>>>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IsochronesMetadata(
|
||||
val attribution: String,
|
||||
val service: String,
|
||||
val timestamp: Long,
|
||||
val query: IsochronesQuery,
|
||||
val engine: IsochronesEngine
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IsochronesQuery(
|
||||
val profile: String,
|
||||
val profileName: String,
|
||||
val locations: List<List<Double>>,
|
||||
val range: List<Double>, // <-- was List<Int>
|
||||
@SerialName("range_type") val rangeType: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class IsochronesEngine(
|
||||
val version: String,
|
||||
@SerialName("build_date") val buildDate: String,
|
||||
@SerialName("graph_date") val graphDate: String,
|
||||
@SerialName("osm_date") val osmDate: String
|
||||
)
|
@@ -1,58 +0,0 @@
|
||||
package org.nitri.ors.domain.matrix
|
||||
|
||||
/** DSL for building [MatrixRequest] instances. */
|
||||
inline fun matrixRequest(build: MatrixRequestBuilder.() -> Unit): MatrixRequest =
|
||||
MatrixRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [matrixRequest]. */
|
||||
class MatrixRequestBuilder {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
var destinations: List<Int>? = null
|
||||
var id: String? = null
|
||||
var metrics: List<String>? = null
|
||||
var resolveLocations: Boolean? = null
|
||||
var sources: List<Int>? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
|
||||
fun build(): MatrixRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
return MatrixRequest(
|
||||
locations = locations.toList(),
|
||||
destinations = destinations,
|
||||
id = id,
|
||||
metrics = metrics,
|
||||
resolveLocations = resolveLocations,
|
||||
sources = sources
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class MatrixRequestBuilderJ {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
private var destinations: List<Int>? = null
|
||||
private var id: String? = null
|
||||
private var metrics: List<String>? = null
|
||||
private var resolveLocations: Boolean? = null
|
||||
private var sources: List<Int>? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
fun destinations(destinations: List<Int>?) = apply { this.destinations = destinations }
|
||||
fun id(id: String?) = apply { this.id = id }
|
||||
fun metrics(metrics: List<String>?) = apply { this.metrics = metrics }
|
||||
fun resolveLocations(resolveLocations: Boolean?) = apply { this.resolveLocations = resolveLocations }
|
||||
fun sources(sources: List<Int>?) = apply { this.sources = sources }
|
||||
|
||||
fun build(): MatrixRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
return MatrixRequest(
|
||||
locations = locations.toList(),
|
||||
destinations = destinations,
|
||||
id = id,
|
||||
metrics = metrics,
|
||||
resolveLocations = resolveLocations,
|
||||
sources = sources
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package org.nitri.ors.domain.matrix
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents a request for the matrix service.
|
||||
*
|
||||
* @property locations A list of coordinates as `[longitude, latitude]` pairs.
|
||||
* @property destinations Optional. A list of indices that refers to the list of locations (starting with `0`).
|
||||
* `{ "locations": [[9.70093,48.477473],[9.207916,49.153868],[37.573242,55.801281],[115.663757,38.106467]], "destinations": [0,1] }`
|
||||
* will calculate distances from origin 0 to destinations 0 and 1, and from origin 1 to destinations 0 and 1.
|
||||
* Default is all locations.
|
||||
* @property id Optional. An arbitrary identification string of the request.
|
||||
* This field is not used by the service and only returned in the response.
|
||||
* @property metrics Optional. Specifies a list of returned metrics.
|
||||
* - `distance` - Returns distance matrix in meters.
|
||||
* - `duration` - Returns duration matrix in seconds.
|
||||
* Default is `["duration"]`.
|
||||
* @property resolveLocations Optional. Specifies whether given locations are resolved or not. If `true`, locations are resolved to the road network,
|
||||
* if `false` just the locations are used.
|
||||
* Default is `false`.
|
||||
* @property sources Optional. A list of indices that refers to the list of locations (starting with `0`).
|
||||
* `{ "locations": [[9.70093,48.477473],[9.207916,49.153868],[37.573242,55.801281],[115.663757,38.106467]], "sources": [0,1] }`
|
||||
* will calculate distances from origin 0 to all destinations and from origin 1 to all destinations.
|
||||
* Default is all locations.
|
||||
*/
|
||||
@Serializable
|
||||
data class MatrixRequest(
|
||||
|
||||
/** A list of coordinates as `[longitude, latitude]` pairs. */
|
||||
val locations: List<List<Double>>,
|
||||
|
||||
/** Optional. A list of indices that refers to the list of locations (starting with `0`). */
|
||||
val destinations: List<Int>? = null,
|
||||
|
||||
/** Optional. An arbitrary identification string of the request. */
|
||||
val id: String? = null,
|
||||
|
||||
/** Optional. Specifies a list of returned metrics. */
|
||||
val metrics: List<String>? = null,
|
||||
|
||||
@SerialName("resolve_locations")
|
||||
/** Optional. Specifies whether given locations are resolved or not. */
|
||||
val resolveLocations: Boolean? = null,
|
||||
|
||||
/** Optional. A list of indices that refers to the list of locations (starting with `0`). */
|
||||
val sources: List<Int>? = null
|
||||
)
|
@@ -1,59 +0,0 @@
|
||||
package org.nitri.ors.domain.matrix
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Represents the response from an ORS Matrix API request.
|
||||
*
|
||||
* The matrix response contains information about travel times or distances
|
||||
* between a set of source and destination points.
|
||||
*
|
||||
* @property durations A list of lists representing the travel duration matrix.
|
||||
* Present only if 'durations' was requested in the `metrics` parameter.
|
||||
* The outer list corresponds to sources, and the inner list corresponds to destinations.
|
||||
* Each value is the duration in seconds.
|
||||
* @property distances A list of lists representing the travel distance matrix.
|
||||
* Present only if 'distances' was requested in the `metrics` parameter.
|
||||
* The outer list corresponds to sources, and the inner list corresponds to destinations.
|
||||
* Each value is the distance in meters.
|
||||
* @property sources A list of enriched waypoint information for the source locations.
|
||||
* This is typically present when `resolve_locations=true` is used in the request,
|
||||
* but might sometimes be included even if `resolve_locations=false`.
|
||||
* @property destinations A list of enriched waypoint information for the destination locations.
|
||||
* This is typically present when `resolve_locations=true` is used in the request,
|
||||
* but might sometimes be included even if `resolve_locations=false`.
|
||||
* @property metadata Standard ORS metadata including information about the service, engine, query, etc.
|
||||
*/
|
||||
@Serializable
|
||||
data class MatrixResponse(
|
||||
// Present only if requested in `metrics`
|
||||
val durations: List<List<Double>>? = null,
|
||||
val distances: List<List<Double>>? = null,
|
||||
|
||||
// Enriched waypoint info when resolve_locations=true (or sometimes even if false)
|
||||
val sources: List<MatrixWaypoint>? = null,
|
||||
val destinations: List<MatrixWaypoint>? = null,
|
||||
|
||||
// Standard ORS metadata (service, engine, query, etc.)
|
||||
val metadata: Metadata? = null
|
||||
)
|
||||
/**
|
||||
* Represents a waypoint used in the ORS Matrix API response, providing details about a specific location.
|
||||
*
|
||||
* @property location The coordinates of the waypoint as a list of [longitude, latitude].
|
||||
* @property name An optional name associated with the waypoint, which ORS may include.
|
||||
* @property snappedDistance The distance in meters from the input coordinate to the snapped network coordinate.
|
||||
* This is an optional extra that ORS may include.
|
||||
*/
|
||||
@Serializable
|
||||
data class MatrixWaypoint(
|
||||
// [lon, lat]
|
||||
val location: List<Double>,
|
||||
|
||||
// Optional extras ORS may include
|
||||
val name: String? = null,
|
||||
@SerialName("snapped_distance")
|
||||
val snappedDistance: Double? = null
|
||||
)
|
@@ -1,26 +0,0 @@
|
||||
package org.nitri.ors.domain.meta
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
val id: String? = null,
|
||||
val attribution: String,
|
||||
val service: String,
|
||||
val timestamp: Long,
|
||||
val query: JsonElement, // could be String, Map<String, JsonElement>, or a specific model
|
||||
val engine: Engine,
|
||||
@SerialName("system_message")
|
||||
val systemMessage: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Engine(
|
||||
val version: String,
|
||||
@SerialName("build_date")
|
||||
val buildDate: String,
|
||||
@SerialName("graph_date")
|
||||
val graphDate: String
|
||||
)
|
@@ -1,86 +0,0 @@
|
||||
package org.nitri.ors.domain.optimization
|
||||
|
||||
/** DSL for building [OptimizationRequest] payloads. */
|
||||
inline fun optimizationRequest(build: OptimizationRequestBuilder.() -> Unit): OptimizationRequest =
|
||||
OptimizationRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [optimizationRequest]. */
|
||||
class OptimizationRequestBuilder {
|
||||
private val jobs = mutableListOf<Job>()
|
||||
private val shipments = mutableListOf<Shipment>()
|
||||
private val vehicles = mutableListOf<Vehicle>()
|
||||
private var matrices: Map<String, CustomMatrix>? = null
|
||||
private var options: Map<String, kotlinx.serialization.json.JsonElement>? = null
|
||||
|
||||
fun job(id: Int, locationLon: Double? = null, locationLat: Double? = null, service: Int? = null, priority: Int? = null) = apply {
|
||||
val loc = if (locationLon != null && locationLat != null) listOf(locationLon, locationLat) else null
|
||||
jobs.add(Job(id = id, service = service, priority = priority, location = loc))
|
||||
}
|
||||
|
||||
fun shipment(
|
||||
id: Int,
|
||||
pickupLon: Double, pickupLat: Double,
|
||||
deliveryLon: Double, deliveryLat: Double,
|
||||
servicePickup: Int? = null,
|
||||
serviceDelivery: Int? = null
|
||||
) = apply {
|
||||
val pickup = ShipmentStep(service = servicePickup, location = listOf(pickupLon, pickupLat))
|
||||
val delivery = ShipmentStep(service = serviceDelivery, location = listOf(deliveryLon, deliveryLat))
|
||||
shipments.add(Shipment(id = id, amount = null, pickup = pickup, delivery = delivery))
|
||||
}
|
||||
|
||||
fun vehicle(id: Int, profile: String, startLon: Double? = null, startLat: Double? = null, endLon: Double? = null, endLat: Double? = null, capacity: List<Int>? = null) = apply {
|
||||
val start = if (startLon != null && startLat != null) listOf(startLon, startLat) else null
|
||||
val end = if (endLon != null && endLat != null) listOf(endLon, endLat) else null
|
||||
vehicles.add(
|
||||
Vehicle(
|
||||
id = id,
|
||||
profile = profile,
|
||||
capacity = capacity,
|
||||
start = start,
|
||||
end = end
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun matrices(m: Map<String, CustomMatrix>?) = apply { this.matrices = m }
|
||||
fun options(o: Map<String, kotlinx.serialization.json.JsonElement>?) = apply { this.options = o }
|
||||
|
||||
fun build(): OptimizationRequest {
|
||||
if (vehicles.isEmpty()) {
|
||||
throw IllegalStateException("At least one vehicle is required")
|
||||
}
|
||||
if (jobs.isEmpty() && shipments.isEmpty()) {
|
||||
error("Provide at least one job or shipment")
|
||||
}
|
||||
return OptimizationRequest(
|
||||
jobs = jobs.takeIf { it.isNotEmpty() },
|
||||
shipments = shipments.takeIf { it.isNotEmpty() },
|
||||
vehicles = vehicles.toList(),
|
||||
matrices = matrices,
|
||||
options = options
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class OptimizationRequestBuilderJ {
|
||||
private val dsl = OptimizationRequestBuilder()
|
||||
|
||||
fun addJob(id: Int, locationLon: Double?, locationLat: Double?, service: Int?, priority: Int?) = apply {
|
||||
dsl.job(id, locationLon, locationLat, service, priority)
|
||||
}
|
||||
|
||||
fun addShipment(id: Int, pickupLon: Double, pickupLat: Double, deliveryLon: Double, deliveryLat: Double, servicePickup: Int?, serviceDelivery: Int?) = apply {
|
||||
dsl.shipment(id, pickupLon, pickupLat, deliveryLon, deliveryLat, servicePickup, serviceDelivery)
|
||||
}
|
||||
|
||||
fun addVehicle(id: Int, profile: String, startLon: Double?, startLat: Double?, endLon: Double?, endLat: Double?, capacity: List<Int>?) = apply {
|
||||
dsl.vehicle(id, profile, startLon, startLat, endLon, endLat, capacity)
|
||||
}
|
||||
|
||||
fun matrices(m: Map<String, CustomMatrix>?) = apply { dsl.matrices(m) }
|
||||
fun options(o: Map<String, kotlinx.serialization.json.JsonElement>?) = apply { dsl.options(o) }
|
||||
|
||||
fun build(): OptimizationRequest = dsl.build()
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
package org.nitri.ors.domain.optimization
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
/**
|
||||
* Root payload sent to /optimization
|
||||
* See: https://github.com/VROOM-Project/vroom/blob/master/docs/API.md
|
||||
*/
|
||||
@Serializable
|
||||
data class OptimizationRequest(
|
||||
/** Required: either jobs and/or shipments, and vehicles */
|
||||
val jobs: List<Job>? = null,
|
||||
val shipments: List<Shipment>? = null,
|
||||
val vehicles: List<Vehicle>,
|
||||
|
||||
/** Optional custom matrices keyed by routing profile (e.g. "driving-car") */
|
||||
val matrices: Map<String, CustomMatrix>? = null,
|
||||
|
||||
/** Optional free-form options bag passed to the optimization engine */
|
||||
val options: Map<String, JsonElement>? = null
|
||||
)
|
||||
|
||||
/* ---------------------------- Basics & helpers ---------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class TimeWindow(
|
||||
/** seconds since midnight */
|
||||
val start: Int,
|
||||
/** seconds since midnight */
|
||||
val end: Int
|
||||
)
|
||||
|
||||
typealias LonLat = List<Double> // [lon, lat]
|
||||
typealias Amount = List<Int> // capacities/quantities
|
||||
typealias Skills = List<Int> // integers as in VROOM API
|
||||
|
||||
/* --------------------------------- Jobs ---------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class Job(
|
||||
/** Unique job id (required) */
|
||||
val id: Int,
|
||||
|
||||
/** Service time in seconds spent on site (optional) */
|
||||
val service: Int? = null,
|
||||
|
||||
/** Demand to be delivered/picked as a quantity vector (optional) */
|
||||
val delivery: Amount? = null,
|
||||
val pickup: Amount? = null,
|
||||
val amount: Amount? = null, // generic synonym supported by VROOM
|
||||
|
||||
/** Location as coordinates or index into the matrix, provide one of them */
|
||||
val location: LonLat? = null,
|
||||
@SerialName("location_index") val locationIndex: Int? = null,
|
||||
|
||||
/** Allowed vehicles list (restrict which vehicles may handle this job) */
|
||||
@SerialName("allowed_vehicles") val allowedVehicles: List<Int>? = null,
|
||||
|
||||
/** Disallowed vehicles list */
|
||||
@SerialName("disallowed_vehicles") val disallowedVehicles: List<Int>? = null,
|
||||
|
||||
/** Skills required for this job */
|
||||
val skills: Skills? = null,
|
||||
|
||||
/** 0..100 (higher = more important) */
|
||||
val priority: Int? = null,
|
||||
|
||||
/** Multiple time windows supported */
|
||||
@SerialName("time_windows") val timeWindows: List<TimeWindow>? = null
|
||||
)
|
||||
|
||||
/* ------------------------------- Shipments -------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class Shipment(
|
||||
/** Unique shipment id (required) */
|
||||
val id: Int,
|
||||
|
||||
/** Overall amount carried by the shipment */
|
||||
val amount: Amount? = null,
|
||||
|
||||
/** Pickup and delivery legs (each looks like a job) */
|
||||
val pickup: ShipmentStep,
|
||||
val delivery: ShipmentStep,
|
||||
|
||||
/** Skills & priority rules apply to the *whole* shipment */
|
||||
val skills: Skills? = null,
|
||||
val priority: Int? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ShipmentStep(
|
||||
/** Service time in seconds at this step */
|
||||
val service: Int? = null,
|
||||
|
||||
/** Coordinates or index, provide one of them */
|
||||
val location: LonLat? = null,
|
||||
@SerialName("location_index") val locationIndex: Int? = null,
|
||||
|
||||
/** Time windows at this step */
|
||||
@SerialName("time_windows") val timeWindows: List<TimeWindow>? = null
|
||||
)
|
||||
|
||||
/* -------------------------------- Vehicles -------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class Vehicle(
|
||||
/** Unique vehicle id (required) */
|
||||
val id: Int,
|
||||
|
||||
/** Routing profile, e.g., "driving-car" (required) */
|
||||
val profile: String,
|
||||
|
||||
/** Vehicle capacity vector */
|
||||
val capacity: Amount? = null,
|
||||
|
||||
/** Start/end positions as coordinates or as matrix indices (pick one style) */
|
||||
val start: LonLat? = null,
|
||||
val end: LonLat? = null,
|
||||
@SerialName("start_index") val startIndex: Int? = null,
|
||||
@SerialName("end_index") val endIndex: Int? = null,
|
||||
|
||||
/** Skills the vehicle provides */
|
||||
val skills: Skills? = null,
|
||||
|
||||
/** When the vehicle is available to operate */
|
||||
@SerialName("time_window") val timeWindow: TimeWindow? = null,
|
||||
|
||||
/** Optional list of breaks (each with service & time windows) */
|
||||
val breaks: List<VehicleBreak>? = null,
|
||||
|
||||
/** Earliest/latest start/end (advanced; seconds since midnight) */
|
||||
@SerialName("earliest_start") val earliestStart: Int? = null,
|
||||
@SerialName("latest_end") val latestEnd: Int? = null,
|
||||
|
||||
/** Max tasks or max travel time limits (seconds) */
|
||||
@SerialName("max_tasks") val maxTasks: Int? = null,
|
||||
@SerialName("max_travel_time") val maxTravelTime: Int? = null,
|
||||
|
||||
/** Optional arbitrary metadata to round-trip through the solver */
|
||||
val description: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VehicleBreak(
|
||||
/** Service time in seconds spent for the break */
|
||||
val service: Int? = null,
|
||||
/** Allowed time windows for this break */
|
||||
@SerialName("time_windows") val timeWindows: List<TimeWindow>
|
||||
)
|
||||
|
||||
/* ------------------------------ Custom matrix ----------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class CustomMatrix(
|
||||
/** Square matrix in seconds; required if provided */
|
||||
val durations: List<List<Int>>? = null,
|
||||
/** Optional distances in meters */
|
||||
val distances: List<List<Int>>? = null
|
||||
)
|
@@ -1,112 +0,0 @@
|
||||
package org.nitri.ors.domain.optimization
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Top-level result from /optimization */
|
||||
@Serializable
|
||||
data class OptimizationResponse(
|
||||
val code: Int,
|
||||
val summary: OptimizationSummary,
|
||||
val unassigned: List<Unassigned> = emptyList(),
|
||||
val routes: List<OptimizedRoute> = emptyList()
|
||||
)
|
||||
|
||||
/* -------------------------------- Summary -------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class OptimizationSummary(
|
||||
val cost: Int,
|
||||
val routes: Int,
|
||||
val unassigned: Int,
|
||||
|
||||
/** Totals – present depending on your payload/constraints */
|
||||
val delivery: List<Int>? = null,
|
||||
val amount: List<Int>? = null,
|
||||
val pickup: List<Int>? = null,
|
||||
|
||||
val setup: Int,
|
||||
val service: Int,
|
||||
val duration: Int,
|
||||
@SerialName("waiting_time") val waitingTime: Int,
|
||||
val priority: Int,
|
||||
|
||||
/** Usually empty unless you use hard constraints */
|
||||
val violations: List<Violation> = emptyList(),
|
||||
|
||||
@SerialName("computing_times") val computingTimes: ComputingTimes? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ComputingTimes(
|
||||
val loading: Int? = null,
|
||||
val solving: Int? = null,
|
||||
val routing: Int? = null
|
||||
)
|
||||
|
||||
/* ------------------------------ Unassigned ------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class Unassigned(
|
||||
val id: Int,
|
||||
val location: LonLat,
|
||||
/** e.g. "job" */
|
||||
val type: String
|
||||
)
|
||||
|
||||
/* --------------------------------- Routes -------------------------------- */
|
||||
|
||||
@Serializable
|
||||
data class OptimizedRoute(
|
||||
val vehicle: Int,
|
||||
val cost: Int,
|
||||
|
||||
/** Per-route totals (optional keys depending on your model) */
|
||||
val delivery: List<Int>? = null,
|
||||
val amount: List<Int>? = null,
|
||||
val pickup: List<Int>? = null,
|
||||
|
||||
val setup: Int,
|
||||
val service: Int,
|
||||
val duration: Int,
|
||||
@SerialName("waiting_time") val waitingTime: Int,
|
||||
val priority: Int,
|
||||
|
||||
val steps: List<RouteStep> = emptyList(),
|
||||
val violations: List<Violation> = emptyList()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouteStep(
|
||||
/** "start" | "job" | "end" | … */
|
||||
val type: String,
|
||||
|
||||
val location: LonLat,
|
||||
|
||||
/** Only for job-like steps */
|
||||
val id: Int? = null,
|
||||
val job: Int? = null,
|
||||
|
||||
val setup: Int? = null,
|
||||
val service: Int? = null,
|
||||
@SerialName("waiting_time") val waitingTime: Int? = null,
|
||||
|
||||
/** Vehicle load after performing this step */
|
||||
val load: List<Int>? = null,
|
||||
|
||||
/** Timestamps & cumulated travel */
|
||||
val arrival: Int? = null,
|
||||
val duration: Int? = null,
|
||||
|
||||
val violations: List<Violation> = emptyList()
|
||||
)
|
||||
|
||||
/* ------------------------------- Violations ------------------------------ */
|
||||
/** Kept flexible; VROOM may return several shapes depending on constraint hit. */
|
||||
@Serializable
|
||||
data class Violation(
|
||||
val type: String? = null, // e.g., "capacity", "skills", "time_window", …
|
||||
val id: Int? = null, // job/shipment id if applicable
|
||||
val job: Int? = null,
|
||||
val description: String? = null
|
||||
)
|
@@ -1,79 +0,0 @@
|
||||
package org.nitri.ors.domain.pois
|
||||
|
||||
/** DSL for constructing [PoisRequest] objects. */
|
||||
inline fun poisRequest(build: PoisRequestBuilder.() -> Unit): PoisRequest =
|
||||
PoisRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [poisRequest]. */
|
||||
class PoisRequestBuilder {
|
||||
private var bbox: List<List<Double>>? = null
|
||||
private var geojson: GeoJsonGeometry? = null
|
||||
private var buffer: Int? = null
|
||||
|
||||
var filters: Map<String, String>? = null
|
||||
var limit: Int? = null
|
||||
var sortby: String? = null
|
||||
|
||||
fun bbox(minLon: Double, minLat: Double, maxLon: Double, maxLat: Double) = apply {
|
||||
bbox = listOf(listOf(minLon, minLat), listOf(maxLon, maxLat))
|
||||
}
|
||||
|
||||
fun point(lon: Double, lat: Double) = apply {
|
||||
geojson = GeoJsonGeometry(type = "Point", coordinates = listOf(lon, lat))
|
||||
}
|
||||
|
||||
fun buffer(meters: Int?) = apply { this.buffer = meters }
|
||||
|
||||
fun build(): PoisRequest {
|
||||
val geom = Geometry(
|
||||
bbox = bbox,
|
||||
geojson = geojson,
|
||||
buffer = buffer
|
||||
)
|
||||
return PoisRequest(
|
||||
geometry = geom,
|
||||
filters = filters,
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class PoisRequestBuilderJ {
|
||||
private var bbox: List<List<Double>>? = null
|
||||
private var geojson: GeoJsonGeometry? = null
|
||||
private var buffer: Int? = null
|
||||
|
||||
private var filters: Map<String, String>? = null
|
||||
private var limit: Int? = null
|
||||
private var sortby: String? = null
|
||||
|
||||
fun bbox(minLon: Double, minLat: Double, maxLon: Double, maxLat: Double) = apply {
|
||||
bbox = listOf(listOf(minLon, minLat), listOf(maxLon, maxLat))
|
||||
}
|
||||
|
||||
fun point(lon: Double, lat: Double) = apply {
|
||||
geojson = GeoJsonGeometry(type = "Point", coordinates = listOf(lon, lat))
|
||||
}
|
||||
|
||||
fun buffer(meters: Int?) = apply { this.buffer = meters }
|
||||
|
||||
fun filters(filters: Map<String, String>?) = apply { this.filters = filters }
|
||||
fun limit(limit: Int?) = apply { this.limit = limit }
|
||||
fun sortby(sortby: String?) = apply { this.sortby = sortby }
|
||||
|
||||
fun build(): PoisRequest {
|
||||
val geom = Geometry(
|
||||
bbox = bbox,
|
||||
geojson = geojson,
|
||||
buffer = buffer
|
||||
)
|
||||
return PoisRequest(
|
||||
geometry = geom,
|
||||
filters = filters,
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package org.nitri.ors.domain.pois
|
||||
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Request for the POIs endpoint. */
|
||||
@Serializable
|
||||
data class PoisRequest(
|
||||
@Required val request: String = "pois",
|
||||
val geometry: Geometry,
|
||||
val filters: Map<String, String>? = null,
|
||||
val limit: Int? = null,
|
||||
val sortby: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Geometry(
|
||||
/** Bounding box `[[minLon,minLat],[maxLon,maxLat]]` if set. */
|
||||
val bbox: List<List<Double>>? = null,
|
||||
/** Optional GeoJSON geometry. */
|
||||
val geojson: GeoJsonGeometry? = null,
|
||||
/** Optional buffer in meters applied to the geometry. */
|
||||
val buffer: Int? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeoJsonGeometry(
|
||||
val type: String,
|
||||
/** `[lon, lat]` pair defining the point location. */
|
||||
val coordinates: List<Double>
|
||||
)
|
@@ -1,83 +0,0 @@
|
||||
package org.nitri.ors.domain.pois
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Top-level POIs GeoJSON response:
|
||||
* {
|
||||
* "type": "FeatureCollection",
|
||||
* "bbox": [minLon, minLat, maxLon, maxLat],
|
||||
* "features": [...],
|
||||
* "information": {...}
|
||||
* }
|
||||
*/
|
||||
@Serializable
|
||||
data class PoisGeoJsonResponse(
|
||||
val type: String, // "FeatureCollection"
|
||||
val bbox: List<Double>? = null, // [minLon, minLat, maxLon, maxLat]
|
||||
val features: List<PoiFeature>,
|
||||
val information: PoisInformation? = null // metadata (sometimes called "information")
|
||||
)
|
||||
|
||||
/** A single GeoJSON feature (Point) */
|
||||
@Serializable
|
||||
data class PoiFeature(
|
||||
val type: String, // "Feature"
|
||||
val geometry: PoiGeometry,
|
||||
val properties: PoiProperties
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PoiGeometry(
|
||||
val type: String, // "Point"
|
||||
val coordinates: List<Double> // [lon, lat]
|
||||
)
|
||||
|
||||
/**
|
||||
* Properties seen in your sample. Some fields are optional across results.
|
||||
* - category_ids is an object with integer keys -> map to Map<Int, CategoryInfo>
|
||||
* - osm_tags is a free-form OSM tag map (strings)
|
||||
*/
|
||||
@Serializable
|
||||
data class PoiProperties(
|
||||
@SerialName("osm_id") val osmId: Long,
|
||||
@SerialName("osm_type") val osmType: Int,
|
||||
val distance: Double,
|
||||
@SerialName("category_ids") val categoryIds: Map<Int, CategoryInfo>? = null,
|
||||
@SerialName("osm_tags") val osmTags: Map<String, String>? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CategoryInfo(
|
||||
@SerialName("category_name") val categoryName: String,
|
||||
@SerialName("category_group") val categoryGroup: String
|
||||
)
|
||||
|
||||
/**
|
||||
* The “information” block at the end of the response.
|
||||
* Note: ORS uses "information" (not "metadata") here.
|
||||
*/
|
||||
@Serializable
|
||||
data class PoisInformation(
|
||||
val attribution: String? = null,
|
||||
val version: String? = null,
|
||||
val timestamp: Long? = null,
|
||||
val query: PoisQueryInfo? = null
|
||||
)
|
||||
|
||||
/** Echo of the request inside the information block */
|
||||
@Serializable
|
||||
data class PoisQueryInfo(
|
||||
val request: String? = null, // "pois"
|
||||
val geometry: PoisQueryGeometry? = null
|
||||
)
|
||||
|
||||
/** Mirrors the request’s geometry wrapper */
|
||||
@Serializable
|
||||
data class PoisQueryGeometry(
|
||||
val bbox: List<List<Double>>? = null, // [[minLon,minLat],[maxLon,maxLat]]
|
||||
val geojson: GeoJsonGeometry? = null,
|
||||
val buffer: Int? = null
|
||||
)
|
||||
|
@@ -1,23 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import org.nitri.ors.LonLat
|
||||
|
||||
/** DSL for constructing [RouteRequest] instances. */
|
||||
inline fun routeRequest(build: RouteRequestBuilder.() -> Unit): RouteRequest =
|
||||
RouteRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [routeRequest]. */
|
||||
class RouteRequestBuilder {
|
||||
private val coords = mutableListOf<LonLat>()
|
||||
var language: String? = null
|
||||
|
||||
fun start(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
fun end(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
fun coordinate(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
|
||||
fun build(): RouteRequest {
|
||||
require(coords.size >= 2) { "At least start and end coordinates required" }
|
||||
val coordinates = coords.map { listOf(it.lon, it.lat) }
|
||||
return RouteRequest(coordinates, language = language)
|
||||
}
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/** GeoJSON response for the directions endpoint. */
|
||||
@Serializable
|
||||
data class GeoJsonRouteResponse(
|
||||
val type: String,
|
||||
val bbox: List<Double>,
|
||||
val features: List<Feature>,
|
||||
val metadata: Metadata
|
||||
)
|
||||
|
||||
/** A single GeoJSON feature within the route response. */
|
||||
@Serializable
|
||||
data class Feature(
|
||||
val type: String,
|
||||
val geometry: Geometry,
|
||||
val properties: Map<String, kotlinx.serialization.json.JsonElement> = emptyMap()
|
||||
)
|
||||
|
||||
/** Geometry of a route feature. */
|
||||
@Serializable
|
||||
data class Geometry(
|
||||
val type: String,
|
||||
val coordinates: List<List<Double>> // or List<List<List<Double>>> for MultiLineString, etc.
|
||||
)
|
@@ -1,33 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
/** GPX response returned by the directions endpoint when requesting GPX. */
|
||||
@Serializable
|
||||
data class GpxResponse(
|
||||
val metadata: GpxMetadata,
|
||||
val routes: List<JsonElement> = emptyList(), // route content can vary
|
||||
val extensions: JsonElement? = null,
|
||||
val gpxRouteElements: List<JsonElement> = emptyList()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GpxMetadata(
|
||||
val name: String? = null,
|
||||
val description: String? = null,
|
||||
val author: JsonElement? = null,
|
||||
val copyright: JsonElement? = null,
|
||||
val timeGenerated: String? = null,
|
||||
val bounds: GpxBounds? = null,
|
||||
val extensions: JsonElement? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GpxBounds(
|
||||
val asArray: List<Double>? = null,
|
||||
val minLon: Double,
|
||||
val maxLon: Double,
|
||||
val minLat: Double,
|
||||
val maxLat: Double
|
||||
)
|
@@ -1,102 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Payload for the directions `v2/directions/{profile}` endpoint.
|
||||
*
|
||||
* All parameters map to the official ORS API; nullable properties are omitted
|
||||
* from the serialized JSON when not provided.
|
||||
*/
|
||||
@Serializable
|
||||
data class RouteRequest(
|
||||
/** List of `[lon, lat]` coordinate pairs in order of travel. */
|
||||
val coordinates: List<List<Double>>,
|
||||
/** Search radiuses around each coordinate in meters. */
|
||||
val radiuses: List<Double>? = null,
|
||||
/** List of `[bearing, range]` constraints for each coordinate. */
|
||||
val bearings: List<List<Double>>? = null,
|
||||
/** Include elevation data in response. */
|
||||
val elevation: Boolean? = null,
|
||||
@SerialName("extra_info")
|
||||
/** Additional information to include, e.g., `waytype`. */
|
||||
val extraInfo: List<String>? = null,
|
||||
/** Whether to return turn-by-turn instructions. */
|
||||
val instructions: Boolean? = null,
|
||||
@SerialName("instructions_format")
|
||||
/** Format of instructions such as `html` or `text`. */
|
||||
val instructionsFormat: String? = null,
|
||||
/** Preferred language for textual parts of the response. */
|
||||
val language: String? = null,
|
||||
/** Routing preference such as `fastest` or `shortest`. */
|
||||
val preference: String? = null,
|
||||
/** Unit system for distances. */
|
||||
val units: String? = null,
|
||||
/** Whether to include geometry. */
|
||||
val geometry: Boolean? = null,
|
||||
@SerialName("geometry_simplify")
|
||||
/** Simplify the returned geometry. */
|
||||
val geometrySimplify: Boolean? = null,
|
||||
@SerialName("roundabout_exits")
|
||||
/** Return `exit` indices for roundabouts. */
|
||||
val roundaboutExits: Boolean? = null,
|
||||
/** Additional attributes to include for each segment. */
|
||||
val attributes: List<String>? = null,
|
||||
/** Include a list of maneuvers. */
|
||||
val maneuvers: Boolean? = null,
|
||||
@SerialName("continue_straight")
|
||||
/** Force continue straight at waypoints? */
|
||||
val continueStraight: Boolean? = null,
|
||||
/** Optional advanced options. */
|
||||
val options: RouteOptions? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RouteOptions(
|
||||
@SerialName("avoid_features")
|
||||
/** Features to avoid, e.g., `ferries` or `tollways`. */
|
||||
val avoidFeatures: List<String>? = null,
|
||||
@SerialName("avoid_polygons")
|
||||
/** Optional polygon geometry to avoid. */
|
||||
val avoidPolygons: AvoidPolygons? = null,
|
||||
@SerialName("profile_params")
|
||||
/** Profile specific parameters such as restrictions. */
|
||||
val profileParams: ProfileParams? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AvoidPolygons(
|
||||
val type: String,
|
||||
val coordinates: List<List<List<Double>>>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ProfileParams(
|
||||
val weightings: Weightings? = null,
|
||||
val restrictions: Restrictions? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Weightings(
|
||||
val green: WeightingFactor? = null,
|
||||
val quiet: WeightingFactor? = null,
|
||||
val shortest: WeightingFactor? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WeightingFactor(
|
||||
val factor: Double
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Restrictions(
|
||||
@SerialName("max_height")
|
||||
val maxHeight: Double? = null,
|
||||
@SerialName("max_width")
|
||||
val maxWidth: Double? = null,
|
||||
@SerialName("max_weight")
|
||||
val maxWeight: Double? = null,
|
||||
@SerialName("max_length")
|
||||
val maxLength: Double? = null
|
||||
)
|
@@ -1,22 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import org.nitri.ors.LonLat
|
||||
|
||||
/**
|
||||
* Java-friendly builder
|
||||
*/
|
||||
class RouteRequestBuilderJ {
|
||||
private val coords = mutableListOf<LonLat>()
|
||||
private var language: String? = null
|
||||
|
||||
fun start(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
fun end(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
fun add(lon: Double, lat: Double) = apply { coords.add(LonLat(lon, lat)) }
|
||||
fun language(language: String) = apply { this.language = language }
|
||||
|
||||
fun build(): RouteRequest {
|
||||
require(coords.size >= 2) { "At least start and end coordinates required" }
|
||||
val coordinates = coords.map { listOf(it.lon, it.lat) }
|
||||
return RouteRequest(coordinates, language = language)
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/** Response from the directions endpoint. */
|
||||
@Serializable
|
||||
data class RouteResponse(
|
||||
val routes: List<Route>,
|
||||
val bbox: List<Double>,
|
||||
val metadata: Metadata? = null
|
||||
)
|
||||
|
||||
/** Single route variant returned by the API. */
|
||||
@Serializable
|
||||
data class Route(
|
||||
val summary: RouteSummary,
|
||||
val segments: List<Segment>,
|
||||
val geometry: String? = null,
|
||||
@SerialName("way_points")
|
||||
val wayPoints: List<Int>? = null
|
||||
)
|
||||
|
||||
/** Aggregated summary of a route. */
|
||||
@Serializable
|
||||
data class RouteSummary(
|
||||
val distance: Double,
|
||||
val duration: Double,
|
||||
val ascent: Double? = null,
|
||||
val descent: Double? = null
|
||||
)
|
||||
|
||||
/** One segment between intermediate waypoints. */
|
||||
@Serializable
|
||||
data class Segment(
|
||||
val distance: Double,
|
||||
val duration: Double,
|
||||
val steps: List<Step>? = null,
|
||||
val ascent: Double? = null,
|
||||
val descent: Double? = null,
|
||||
@SerialName("detour_factor")
|
||||
val detourFactor: Double? = null,
|
||||
val percentage: Double? = null
|
||||
)
|
||||
|
||||
/** Turn instruction within a segment. */
|
||||
@Serializable
|
||||
data class Step(
|
||||
val distance: Double,
|
||||
val duration: Double,
|
||||
val instruction: String,
|
||||
val name: String,
|
||||
val type: Int,
|
||||
@SerialName("way_points")
|
||||
val wayPoints: List<Int>,
|
||||
@SerialName("exit_number")
|
||||
val exitNumber: Int? = null
|
||||
)
|
||||
|
@@ -1,38 +0,0 @@
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
/** DSL for constructing [SnapRequest] objects. */
|
||||
inline fun snapRequest(build: SnapRequestBuilder.() -> Unit): SnapRequest =
|
||||
SnapRequestBuilder().apply(build).build()
|
||||
|
||||
/** Builder used by [snapRequest]. */
|
||||
class SnapRequestBuilder {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
private var radius: Int? = null
|
||||
var id: String? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
fun radius(meters: Int) = apply { this.radius = meters }
|
||||
|
||||
fun build(): SnapRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
val r = requireNotNull(radius) { "radius is required" }
|
||||
return SnapRequest(locations = locations.toList(), radius = r, id = id)
|
||||
}
|
||||
}
|
||||
|
||||
/** Java-friendly builder counterpart. */
|
||||
class SnapRequestBuilderJ {
|
||||
private val locations = mutableListOf<List<Double>>()
|
||||
private var radius: Int? = null
|
||||
private var id: String? = null
|
||||
|
||||
fun location(lon: Double, lat: Double) = apply { locations.add(listOf(lon, lat)) }
|
||||
fun radius(meters: Int) = apply { this.radius = meters }
|
||||
fun id(id: String?) = apply { this.id = id }
|
||||
|
||||
fun build(): SnapRequest {
|
||||
require(locations.isNotEmpty()) { "At least one location is required" }
|
||||
val r = requireNotNull(radius) { "radius is required" }
|
||||
return SnapRequest(locations = locations.toList(), radius = r, id = id)
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Snap response in GeoJSON format
|
||||
*/
|
||||
@Serializable
|
||||
data class SnapGeoJsonResponse(
|
||||
val type: String, // "FeatureCollection"
|
||||
val features: List<SnapFeature>,
|
||||
val metadata: Metadata,
|
||||
val bbox: List<Double>? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* A GeoJSON Feature with snapped location info
|
||||
*/
|
||||
@Serializable
|
||||
data class SnapFeature(
|
||||
val type: String, // "Feature"
|
||||
val properties: SnapProperties,
|
||||
val geometry: SnapGeometry
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SnapProperties(
|
||||
val name: String? = null,
|
||||
|
||||
@SerialName("snapped_distance")
|
||||
val snappedDistance: Double,
|
||||
|
||||
@SerialName("source_id")
|
||||
val sourceId: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SnapGeometry(
|
||||
val type: String, // "Point"
|
||||
val coordinates: List<Double> // [lon, lat]
|
||||
)
|
@@ -1,17 +0,0 @@
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Snap request for ORS /v2/snap/{profile}/json
|
||||
*
|
||||
* @param locations List of [lon, lat] coordinates to snap.
|
||||
* @param radius Maximum radius (meters) around given coordinates to search for graph edges.
|
||||
* @param id Optional client-provided identifier.
|
||||
*/
|
||||
@Serializable
|
||||
data class SnapRequest(
|
||||
val locations: List<List<Double>>,
|
||||
val radius: Int,
|
||||
val id: String? = null
|
||||
)
|
@@ -1,30 +0,0 @@
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Response for ORS /v2/snap/{profile}[/json]
|
||||
*/
|
||||
@Serializable
|
||||
data class SnapResponse(
|
||||
val locations: List<SnapLocation>,
|
||||
val metadata: Metadata
|
||||
)
|
||||
|
||||
/**
|
||||
* One snapped input coordinate.
|
||||
*/
|
||||
@Serializable
|
||||
data class SnapLocation(
|
||||
/** [lon, lat] of the snapped position */
|
||||
val location: List<Double>,
|
||||
|
||||
/** Optional street name if available */
|
||||
val name: String? = null,
|
||||
|
||||
/** Distance in meters from the input to the snapped position */
|
||||
@SerialName("snapped_distance")
|
||||
val snappedDistance: Double
|
||||
)
|
@@ -1,66 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.domain.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.domain.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.domain.elevation.ElevationPointResponse
|
||||
|
||||
/** Helper functions for the ORS elevation endpoints. */
|
||||
class ElevationHelper {
|
||||
|
||||
/**
|
||||
* Convenience helper to request elevation for a LineString provided as list of [lon, lat] pairs.
|
||||
* Builds a GeoJSON LineString payload and requests GeoJSON output.
|
||||
*/
|
||||
suspend fun OrsClient.getElevationLine(
|
||||
coordinates: List<List<Double>>, // [[lon,lat], [lon,lat], ...]
|
||||
dataset: String? = null,
|
||||
formatOut: String = "geojson",
|
||||
): ElevationLineResponse {
|
||||
val geometry: JsonElement = JsonObject(
|
||||
mapOf(
|
||||
"type" to JsonPrimitive("LineString"),
|
||||
"coordinates" to JsonArray(
|
||||
coordinates.map { pair ->
|
||||
JsonArray(pair.map { JsonPrimitive(it) })
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
val request = ElevationLineRequest(
|
||||
formatIn = "geojson",
|
||||
formatOut = formatOut,
|
||||
geometry = geometry,
|
||||
dataset = dataset
|
||||
)
|
||||
return getElevationLine(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the ORS elevation/point POST endpoint for a single coordinate.
|
||||
*
|
||||
* @param lon longitude
|
||||
* @param lat latitude
|
||||
* @param formatOut either "geojson" or "point"
|
||||
* @param dataset optional dataset (e.g., "srtm")
|
||||
*/
|
||||
suspend fun OrsClient.getElevationPoint(
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
formatOut: String = "geojson",
|
||||
dataset: String? = null,
|
||||
): ElevationPointResponse {
|
||||
val request = ElevationPointRequest(
|
||||
formatIn = "point",
|
||||
formatOut = formatOut,
|
||||
dataset = dataset,
|
||||
geometry = listOf(lon, lat)
|
||||
)
|
||||
return getElevationPoint(request)
|
||||
}
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.Profile
|
||||
import org.nitri.ors.domain.export.ExportRequest
|
||||
import org.nitri.ors.domain.export.ExportResponse
|
||||
import org.nitri.ors.domain.export.TopoJsonExportResponse
|
||||
|
||||
/** Helpers for the export endpoints. */
|
||||
class ExportHelper {
|
||||
|
||||
/** Requests the export endpoint with bounding box [bbox]. */
|
||||
suspend fun OrsClient.export(bbox: List<List<Double>>, geometry: Boolean? = null, profile: Profile): ExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request",
|
||||
geometry = geometry
|
||||
)
|
||||
return export(profile, request)
|
||||
}
|
||||
|
||||
/** Requests the export endpoint asking explicitly for JSON output. */
|
||||
suspend fun OrsClient.exportJson(bbox: List<List<Double>>, geometry: Boolean? = null, profile: Profile): ExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request_json",
|
||||
geometry = geometry
|
||||
)
|
||||
return exportJson(profile, request)
|
||||
}
|
||||
|
||||
/** Requests the export endpoint with TopoJSON output. */
|
||||
suspend fun OrsClient.exportTopoJson(bbox: List<List<Double>>, geometry: Boolean? = null, profile: Profile): TopoJsonExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request_topo_json",
|
||||
geometry = geometry
|
||||
)
|
||||
return exportTopoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,170 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.domain.geocode.GeocodeSearchResponse
|
||||
|
||||
/**
|
||||
* Repository for ORS Geocoding endpoints using GET requests only.
|
||||
*
|
||||
* Methods are member extensions on [OrsClient] and delegate to it.
|
||||
*/
|
||||
class GeocodeHelper {
|
||||
|
||||
/**
|
||||
* Forward geocoding search.
|
||||
*/
|
||||
suspend fun OrsClient.search(
|
||||
text: String,
|
||||
apiKey: String,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadiusMeters: Double? = null,
|
||||
boundaryGid: String? = null,
|
||||
boundaryCountry: String? = null,
|
||||
sourcesCsv: String? = null,
|
||||
layersCsv: String? = null,
|
||||
size: Int? = 10,
|
||||
): GeocodeSearchResponse {
|
||||
return geocodeSearch(
|
||||
text = text,
|
||||
apiKey = apiKey,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadiusMeters = circleRadiusMeters,
|
||||
boundaryGid = boundaryGid,
|
||||
boundaryCountry = boundaryCountry,
|
||||
sourcesCsv = sourcesCsv,
|
||||
layersCsv = layersCsv,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocomplete search; returns suggestions for a partial query.
|
||||
*/
|
||||
suspend fun OrsClient.autocomplete(
|
||||
apiKey: String,
|
||||
text: String,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadius: Double? = null,
|
||||
country: String? = null,
|
||||
sources: List<String>? = null,
|
||||
layers: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return geocodeAutocomplete(
|
||||
apiKey = apiKey,
|
||||
text = text,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadius = circleRadius,
|
||||
country = country,
|
||||
sources = sources,
|
||||
layers = layers,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Structured forward geocoding using address fields.
|
||||
*/
|
||||
suspend fun OrsClient.structured(
|
||||
apiKey: String,
|
||||
address: String? = null,
|
||||
neighbourhood: String? = null,
|
||||
borough: String? = null,
|
||||
locality: String? = null,
|
||||
county: String? = null,
|
||||
region: String? = null,
|
||||
country: String? = null,
|
||||
postalcode: String? = null,
|
||||
focusLon: Double? = null,
|
||||
focusLat: Double? = null,
|
||||
rectMinLon: Double? = null,
|
||||
rectMinLat: Double? = null,
|
||||
rectMaxLon: Double? = null,
|
||||
rectMaxLat: Double? = null,
|
||||
circleLon: Double? = null,
|
||||
circleLat: Double? = null,
|
||||
circleRadiusMeters: Double? = null,
|
||||
boundaryCountry: String? = null,
|
||||
layers: List<String>? = null,
|
||||
sources: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return geocodeStructured(
|
||||
apiKey = apiKey,
|
||||
address = address,
|
||||
neighbourhood = neighbourhood,
|
||||
borough = borough,
|
||||
locality = locality,
|
||||
county = county,
|
||||
region = region,
|
||||
country = country,
|
||||
postalcode = postalcode,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
rectMinLat = rectMinLat,
|
||||
rectMaxLon = rectMaxLon,
|
||||
rectMaxLat = rectMaxLat,
|
||||
circleLon = circleLon,
|
||||
circleLat = circleLat,
|
||||
circleRadiusMeters = circleRadiusMeters,
|
||||
boundaryCountry = boundaryCountry,
|
||||
layers = layers,
|
||||
sources = sources,
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse geocoding for a point.
|
||||
*/
|
||||
suspend fun OrsClient.reverse(
|
||||
apiKey: String,
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
radiusKm: Double? = null,
|
||||
size: Int? = null,
|
||||
layers: List<String>? = null,
|
||||
sources: List<String>? = null,
|
||||
boundaryCountry: String? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return geocodeReverse(
|
||||
apiKey = apiKey,
|
||||
lon = lon,
|
||||
lat = lat,
|
||||
radiusKm = radiusKm,
|
||||
size = size,
|
||||
layers = layers,
|
||||
sources = sources,
|
||||
boundaryCountry = boundaryCountry
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.Profile
|
||||
import org.nitri.ors.domain.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.domain.isochrones.IsochronesResponse
|
||||
|
||||
/** Helpers for requesting isochrones. */
|
||||
class IsochronesHelper {
|
||||
|
||||
/** Calls the isochrones endpoint with the given parameters. */
|
||||
suspend fun OrsClient.getIsochrones(
|
||||
locations: List<List<Double>>,
|
||||
range: List<Int>,
|
||||
profile: Profile,
|
||||
attributes: List<String>? = null,
|
||||
rangeType: String? = null,
|
||||
): IsochronesResponse {
|
||||
val request = IsochronesRequest(
|
||||
locations, range, rangeType, attributes
|
||||
)
|
||||
return getIsochrones(profile, request)
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.Profile
|
||||
import org.nitri.ors.domain.matrix.MatrixRequest
|
||||
import org.nitri.ors.domain.matrix.MatrixResponse
|
||||
|
||||
/** Helpers for the matrix endpoint. */
|
||||
class MatrixHelper {
|
||||
|
||||
/** Calls the ORS Matrix endpoint for the given [profile]. */
|
||||
suspend fun OrsClient.getMatrix(
|
||||
locations: List<List<Double>>,
|
||||
profile: Profile,
|
||||
metrics: List<String>? = null,
|
||||
sources: List<Int>? = null,
|
||||
destinations: List<Int>? = null,
|
||||
resolveLocations: Boolean? = null,
|
||||
id: String? = null,
|
||||
): MatrixResponse {
|
||||
val request = MatrixRequest(
|
||||
locations = locations,
|
||||
destinations = destinations,
|
||||
id = id,
|
||||
metrics = metrics,
|
||||
resolveLocations = resolveLocations,
|
||||
sources = sources
|
||||
)
|
||||
return getMatrix(profile, request)
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.domain.optimization.CustomMatrix
|
||||
import org.nitri.ors.domain.optimization.Job
|
||||
import org.nitri.ors.domain.optimization.OptimizationRequest
|
||||
import org.nitri.ors.domain.optimization.OptimizationResponse
|
||||
import org.nitri.ors.domain.optimization.Shipment
|
||||
import org.nitri.ors.domain.optimization.Vehicle
|
||||
|
||||
/**
|
||||
* Repository for the OpenRouteService Optimization endpoint using [OrsClient].
|
||||
*/
|
||||
class OptimizationHelper {
|
||||
|
||||
/**
|
||||
* Calls the ORS Optimization endpoint with provided arguments and builds the request.
|
||||
*/
|
||||
suspend fun OrsClient.getOptimization(
|
||||
vehicles: List<Vehicle>,
|
||||
jobs: List<Job>? = null,
|
||||
shipments: List<Shipment>? = null,
|
||||
matrices: Map<String, CustomMatrix>? = null,
|
||||
options: Map<String, JsonElement>? = null
|
||||
): OptimizationResponse {
|
||||
val request = OptimizationRequest(
|
||||
jobs = jobs,
|
||||
shipments = shipments,
|
||||
vehicles = vehicles,
|
||||
matrices = matrices,
|
||||
options = options
|
||||
)
|
||||
return getOptimization(request)
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.domain.pois.GeoJsonGeometry
|
||||
import org.nitri.ors.domain.pois.Geometry
|
||||
import org.nitri.ors.domain.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.domain.pois.PoisRequest
|
||||
|
||||
/** Convenience extensions for the POIs endpoints. */
|
||||
class PoisHelper {
|
||||
|
||||
/**
|
||||
* Query POIs within a bounding box.
|
||||
*
|
||||
* @param bbox [[minLon,minLat],[maxLon,maxLat]]
|
||||
* @param filters Optional filters map as supported by ORS POIs
|
||||
* @param limit Optional limit for number of features returned
|
||||
* @param sortby Optional sort field
|
||||
* @param buffer Optional buffer in meters applied to the geometry
|
||||
*/
|
||||
suspend fun OrsClient.getPoisByBbox(
|
||||
bbox: List<List<Double>>,
|
||||
filters: Map<String, String>? = null,
|
||||
limit: Int? = null,
|
||||
sortby: String? = null,
|
||||
buffer: Int? = null
|
||||
): PoisGeoJsonResponse {
|
||||
val request = PoisRequest(
|
||||
geometry = Geometry(bbox = bbox, buffer = buffer),
|
||||
filters = filters,
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
return getPois(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query POIs around a point with a buffer radius.
|
||||
*
|
||||
* @param point [lon, lat]
|
||||
* @param buffer Buffer radius in meters
|
||||
*/
|
||||
suspend fun OrsClient.getPoisByPoint(
|
||||
point: List<Double>,
|
||||
buffer: Int,
|
||||
filters: Map<String, String>? = null,
|
||||
limit: Int? = null,
|
||||
sortby: String? = null
|
||||
): PoisGeoJsonResponse {
|
||||
val request = PoisRequest(
|
||||
geometry = Geometry(
|
||||
geojson = GeoJsonGeometry(type = "Point", coordinates = point),
|
||||
buffer = buffer
|
||||
),
|
||||
filters = filters,
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
return getPois(request)
|
||||
}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.Profile
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.domain.route.RouteRequest
|
||||
import org.nitri.ors.domain.route.RouteResponse
|
||||
|
||||
/**
|
||||
* Convenience extensions for invoking the directions endpoints.
|
||||
*/
|
||||
class RouteHelper {
|
||||
|
||||
/** Converts a profile key string to the corresponding [Profile] enum. */
|
||||
private fun profileFromKey(key: String): Profile =
|
||||
Profile.entries.firstOrNull { it.key == key }
|
||||
?: throw IllegalArgumentException("Unknown profile key: $key")
|
||||
|
||||
/**
|
||||
* Retrieves a route between two coordinates.
|
||||
*/
|
||||
suspend fun OrsClient.getRoute(
|
||||
start: Pair<Double, Double>,
|
||||
end: Pair<Double, Double>,
|
||||
profile: String
|
||||
): RouteResponse {
|
||||
val request = RouteRequest(
|
||||
coordinates = listOf(
|
||||
listOf(start.first, start.second),
|
||||
listOf(end.first, end.second)
|
||||
)
|
||||
)
|
||||
return getRoute(profileFromKey(profile), request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a route as GPX between two coordinates.
|
||||
*/
|
||||
suspend fun OrsClient.getRouteGpx(
|
||||
start: Pair<Double, Double>,
|
||||
end: Pair<Double, Double>,
|
||||
profile: String
|
||||
): String {
|
||||
val request = RouteRequest(
|
||||
coordinates = listOf(
|
||||
listOf(start.first, start.second),
|
||||
listOf(end.first, end.second)
|
||||
)
|
||||
)
|
||||
return getRouteGpx(profileFromKey(profile), request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a route as GPX for an arbitrary coordinate list.
|
||||
*/
|
||||
suspend fun OrsClient.getRouteGpx(
|
||||
coordinates: List<List<Double>>,
|
||||
language: String,
|
||||
profile: String
|
||||
): String {
|
||||
val request = RouteRequest(coordinates = coordinates, language = language)
|
||||
return getRouteGpx(profileFromKey(profile), request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a route as GeoJSON feature collection.
|
||||
*/
|
||||
suspend fun OrsClient.getRouteGeoJson(
|
||||
start: Pair<Double, Double>,
|
||||
end: Pair<Double, Double>,
|
||||
profile: Profile
|
||||
): GeoJsonRouteResponse {
|
||||
val request = RouteRequest(
|
||||
coordinates = listOf(
|
||||
listOf(start.first, start.second),
|
||||
listOf(end.first, end.second)
|
||||
)
|
||||
)
|
||||
return getRouteGeoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.Profile
|
||||
import org.nitri.ors.domain.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.domain.snap.SnapRequest
|
||||
import org.nitri.ors.domain.snap.SnapResponse
|
||||
|
||||
/** Helpers for the snap endpoints. */
|
||||
class SnapHelper {
|
||||
|
||||
/** Calls the ORS Snap endpoint for the given [profile]. */
|
||||
suspend fun OrsClient.getSnap(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: Profile,
|
||||
id: String? = null,
|
||||
): SnapResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return getSnap(profile, request)
|
||||
}
|
||||
|
||||
/** Calls the ORS Snap JSON endpoint. */
|
||||
suspend fun OrsClient.getSnapJson(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: Profile,
|
||||
id: String? = null,
|
||||
): SnapResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return getSnapJson(profile, request)
|
||||
}
|
||||
|
||||
/** Calls the ORS Snap GeoJSON endpoint. */
|
||||
suspend fun OrsClient.getSnapGeoJson(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: Profile,
|
||||
id: String? = null,
|
||||
): SnapGeoJsonResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return getSnapGeoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
package org.nitri.ors.restclient
|
||||
|
||||
import android.content.Context
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.create
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Builds a Retrofit HTTP client configured for the public
|
||||
* [OpenRouteService](https://openrouteservice.org/) API.
|
||||
*/
|
||||
object OpenRouteServiceRestClient {
|
||||
/**
|
||||
* Creates an [OpenRouteServiceApi] with authorization and sensible defaults.
|
||||
*
|
||||
* @param apiKey ORS API key used in the Authorization header
|
||||
* @param context Android context used for user-agent construction
|
||||
*/
|
||||
fun create(apiKey: String, context: Context): OpenRouteServiceApi {
|
||||
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val appVersion = packageInfo.versionName ?: "unknown"
|
||||
val userAgent = "${packageInfo.packageName}/$appVersion (ORS-Android-Client)"
|
||||
|
||||
val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowSpecialFloatingPointValues = true
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
})
|
||||
.addInterceptor { chain ->
|
||||
val original = chain.request()
|
||||
|
||||
val newRequest = original.newBuilder()
|
||||
// ORS expects the API key in the Authorization header (no Bearer prefix)
|
||||
.addHeader("Authorization", apiKey)
|
||||
.addHeader("User-Agent", userAgent)
|
||||
.addHeader("Accept", "application/json, application/geo+json, application/xml, text/xml, application/gpx+xml")
|
||||
.build()
|
||||
|
||||
chain.proceed(newRequest)
|
||||
}
|
||||
// Retry on 429 Too Many Requests with simple backoff honoring Retry-After when present
|
||||
.addInterceptor { chain ->
|
||||
val maxAttempts = 3
|
||||
var waitMs = 500L
|
||||
var req = chain.request()
|
||||
|
||||
repeat(maxAttempts) { attempt ->
|
||||
val response = chain.proceed(req)
|
||||
if (response.code != 429 || attempt == maxAttempts - 1) {
|
||||
return@addInterceptor response
|
||||
}
|
||||
|
||||
val retryAfter = response.header("Retry-After")
|
||||
response.close()
|
||||
val delayMs = retryAfter?.toLongOrNull()?.times(1000) ?: waitMs
|
||||
try {
|
||||
Thread.sleep(delayMs)
|
||||
} catch (_: InterruptedException) {
|
||||
return@addInterceptor chain.proceed(req)
|
||||
}
|
||||
waitMs *= 2
|
||||
}
|
||||
|
||||
// compiler sees this as unreachable
|
||||
error("Unexpected state: retry loop exited without returning")
|
||||
}
|
||||
// Increase timeouts to accommodate heavier endpoints like POIs
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl("https://api.openrouteservice.org/")
|
||||
.client(client)
|
||||
// Prefer application/json for requests; also support application/geo+json responses
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.addConverterFactory(json.asConverterFactory("application/geo+json".toMediaType()))
|
||||
.build()
|
||||
|
||||
return retrofit.create()
|
||||
}
|
||||
}
|
||||
|
@@ -1,49 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.nitri.ors.domain.export.ExportResponse
|
||||
|
||||
class ExportResponseStringAsDoubleSerializerTest {
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
@Test
|
||||
fun parses_weight_when_number_or_string() {
|
||||
val payload = """
|
||||
{
|
||||
"nodes": [],
|
||||
"edges": [
|
||||
{"fromId":1, "toId":2, "weight": "12.34"},
|
||||
{"fromId":2, "toId":3, "weight": 56.78}
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val resp = json.decodeFromString<ExportResponse>(payload)
|
||||
assertEquals(2, resp.edges.size)
|
||||
assertEquals(12.34, resp.edges[0].weight, 1e-9)
|
||||
assertEquals(56.78, resp.edges[1].weight, 1e-9)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializes_weight_as_number() {
|
||||
val payload = """
|
||||
{
|
||||
"nodes": [],
|
||||
"edges": [
|
||||
{"fromId":1, "toId":2, "weight": "10"}
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
val resp = json.decodeFromString<ExportResponse>(payload)
|
||||
|
||||
val out = json.encodeToString(resp)
|
||||
// The serializer writes numbers, so we expect \"weight\":10.0 (or 10) in output
|
||||
// We assert that there is no quoted weight in the serialized JSON.
|
||||
assert(!out.contains("\"weight\": \""))
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.nitri.ors.domain.route.Route
|
||||
import org.nitri.ors.domain.route.RouteRequest
|
||||
import org.nitri.ors.domain.route.RouteResponse
|
||||
import org.nitri.ors.domain.route.RouteSummary
|
||||
import org.nitri.ors.helper.RouteHelper
|
||||
|
||||
class RouteHelperTest {
|
||||
|
||||
private lateinit var client: OrsClient
|
||||
private lateinit var routeHelper: RouteHelper
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
client = mock(OrsClient::class.java)
|
||||
routeHelper = RouteHelper()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getRoute returns response from API`() = runTest {
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
val profile = "driving-car"
|
||||
|
||||
val expectedResponse = RouteResponse(
|
||||
routes = listOf(
|
||||
Route(
|
||||
summary = RouteSummary(1000.0, 600.0),
|
||||
geometry = "encodedPolyline",
|
||||
segments = emptyList()
|
||||
)
|
||||
),
|
||||
bbox = listOf(0.0, 0.0, 1.0, 1.0)
|
||||
)
|
||||
|
||||
val expectedRequest = RouteRequest(
|
||||
coordinates = listOf(
|
||||
listOf(start.first, start.second),
|
||||
listOf(end.first, end.second)
|
||||
)
|
||||
)
|
||||
|
||||
`when`(client.getRoute(Profile.DRIVING_CAR, expectedRequest)).thenReturn(expectedResponse)
|
||||
|
||||
val result = with(routeHelper) { client.getRoute(start, end, profile) }
|
||||
|
||||
assert(result == expectedResponse)
|
||||
verify(client).getRoute(Profile.DRIVING_CAR, expectedRequest)
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.nitri.ors.domain.route.RouteRequestBuilderJ
|
||||
|
||||
class RouteRequestBuilderJTest {
|
||||
|
||||
@Test
|
||||
fun builds_coordinates_and_language() {
|
||||
val req = RouteRequestBuilderJ()
|
||||
.start(8.68, 49.41)
|
||||
.add(8.69, 49.42)
|
||||
.end(8.70, 49.43)
|
||||
.language("de")
|
||||
.build()
|
||||
|
||||
assertEquals(3, req.coordinates.size)
|
||||
assertEquals(listOf(8.68, 49.41), req.coordinates.first())
|
||||
assertEquals(listOf(8.70, 49.43), req.coordinates.last())
|
||||
assertEquals("de", req.language)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun throws_when_less_than_two_points() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
RouteRequestBuilderJ()
|
||||
.start(8.68, 49.41)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.nitri.ors.domain.snap.SnapRequestBuilderJ
|
||||
|
||||
class SnapRequestBuilderTest {
|
||||
|
||||
@Test
|
||||
fun builds_snap_request() {
|
||||
val req = SnapRequestBuilderJ()
|
||||
.location(8.0, 48.0)
|
||||
.radius(25)
|
||||
.id("jid")
|
||||
.build()
|
||||
assertEquals(1, req.locations.size)
|
||||
assertEquals(25, req.radius)
|
||||
assertEquals("jid", req.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun requires_location_and_radius() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
SnapRequestBuilderJ()
|
||||
.radius(10)
|
||||
.build()
|
||||
}
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
SnapRequestBuilderJ()
|
||||
.location(8.0, 48.0)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,4 +6,4 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
|
||||
include(":app", ":ors-client")
|
||||
include(":app")
|
||||
|
Reference in New Issue
Block a user