mirror of
https://github.com/Pygmalion69/OpenTopoMapViewer.git
synced 2025-10-06 00:02:42 +02:00
Merge branch “feature-ors”
This commit is contained in:
@@ -45,15 +45,16 @@ import org.nitri.opentopo.viewmodel.GpxViewModel
|
||||
import org.nitri.opentopo.nearby.NearbyFragment
|
||||
import org.nitri.opentopo.nearby.entity.NearbyItem
|
||||
import org.nitri.opentopo.util.Utils
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
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
|
||||
|
||||
open class BaseMainActivity : AppCompatActivity(), MapFragment.OnFragmentInteractionListener,
|
||||
GpxDetailFragment.OnFragmentInteractionListener, NearbyFragment.OnFragmentInteractionListener {
|
||||
private val parser = GPXParser()
|
||||
private var openRouteServiceApi: OpenRouteServiceApi? = null
|
||||
private var orsClient: OrsClient? = null
|
||||
private var geoPointFromIntent: GeoPointDto? = null
|
||||
private var gpxUriString: String? = null
|
||||
private var gpxUri: Uri? = null
|
||||
@@ -83,7 +84,7 @@ open class BaseMainActivity : AppCompatActivity(), MapFragment.OnFragmentInterac
|
||||
|
||||
private val orsApiKeyChangesReceiver = object: BroadcastReceiver() {
|
||||
override fun onReceive(p0: Context?, intent: Intent?) {
|
||||
createOrsApi()
|
||||
createOrsClient()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +96,8 @@ open class BaseMainActivity : AppCompatActivity(), MapFragment.OnFragmentInterac
|
||||
// NOP
|
||||
}
|
||||
|
||||
override fun getOpenRouteServiceApi(): OpenRouteServiceApi? {
|
||||
return openRouteServiceApi
|
||||
override fun getOpenRouteServiceClient(): OrsClient? {
|
||||
return orsClient
|
||||
}
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
@@ -182,13 +183,13 @@ open class BaseMainActivity : AppCompatActivity(), MapFragment.OnFragmentInterac
|
||||
// Log.d("ORS", "Distance: ${result.routes.firstOrNull()?.summary?.distance} m")
|
||||
// }
|
||||
|
||||
createOrsApi()
|
||||
createOrsClient()
|
||||
}
|
||||
|
||||
private fun createOrsApi() {
|
||||
private fun createOrsClient() {
|
||||
val apiKey = sharedPreferences.getString(PREF_ORS_API_KEY, "")
|
||||
if (apiKey?.isNotEmpty() == true) {
|
||||
openRouteServiceApi = OpenRouteServiceClient.create(apiKey, this@BaseMainActivity)
|
||||
orsClient = Ors.create(apiKey, applicationContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -52,7 +52,7 @@ import org.nitri.opentopo.util.OrientationSensor
|
||||
import org.nitri.opentopo.util.Utils
|
||||
import org.nitri.opentopo.viewmodel.LocationViewModel
|
||||
import org.nitri.opentopo.viewmodel.MarkerViewModel
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.osmdroid.config.Configuration
|
||||
import org.osmdroid.events.DelayedMapListener
|
||||
import org.osmdroid.events.MapEventsReceiver
|
||||
@@ -398,10 +398,10 @@ class MapFragment : Fragment(), LocationListener, PopupMenu.OnMenuItemClickListe
|
||||
}
|
||||
val locale = Resources.getSystem().configuration.locales.get(0)
|
||||
val language = locale.toLanguageTag().lowercase()
|
||||
listener?.getOpenRouteServiceApi()?.let { api ->
|
||||
listener?.getOpenRouteServiceClient()?.let { client ->
|
||||
val profile = sharedPreferences.getString(PREF_ORS_PROFILE, "driving-car")
|
||||
profile?.let {
|
||||
val directions = Directions(api, it)
|
||||
val directions = Directions(client, it)
|
||||
directions.getRouteGpx(coordinates, language, object : Directions.RouteGpResult {
|
||||
override fun onSuccess(gpx: String) {
|
||||
Log.d(TAG, "GPX: $gpx")
|
||||
@@ -1060,9 +1060,9 @@ class MapFragment : Fragment(), LocationListener, PopupMenu.OnMenuItemClickListe
|
||||
fun showPrivacyOptionsForm()
|
||||
|
||||
/**
|
||||
* Get the ORS API if available
|
||||
* Get the ORS client if available
|
||||
*/
|
||||
fun getOpenRouteServiceApi(): OpenRouteServiceApi?
|
||||
fun getOpenRouteServiceClient(): OrsClient?
|
||||
|
||||
/**
|
||||
* Parse GPX string
|
||||
|
@@ -4,17 +4,17 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.repository.RouteRepository
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.helper.RouteHelper
|
||||
|
||||
class Directions(val api: OpenRouteServiceApi, private val profile: String) {
|
||||
class Directions(val client: OrsClient, private val profile: String) {
|
||||
|
||||
val repository = RouteRepository(api)
|
||||
val routeHelper = RouteHelper()
|
||||
|
||||
fun getRouteGpx(coordinates: List<List<Double>>, language: String, result: RouteGpResult) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val gpxXml = repository.getRouteGpx(coordinates, language, profile)
|
||||
val gpxXml = with(routeHelper) { client.getRouteGpx(coordinates, language, profile) }
|
||||
withContext(Dispatchers.Main) {
|
||||
if (gpxXml.isNotBlank()) {
|
||||
result.onSuccess(gpxXml)
|
||||
|
@@ -9,7 +9,7 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "org.nitri.ors"
|
||||
compileSdk = 35
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
@@ -42,6 +42,8 @@ android {
|
||||
lint {
|
||||
targetSdk = 35
|
||||
}
|
||||
|
||||
publishing { singleVariant("release") { withSourcesJar() } }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@@ -0,0 +1,65 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -9,28 +9,28 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.ElevationRepository
|
||||
import org.nitri.ors.helper.ElevationHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ElevationInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): ElevationRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, ElevationHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return ElevationRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = ElevationHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testElevation_point_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// A point near Heidelberg, Germany
|
||||
val lon = 8.681495
|
||||
val lat = 49.41461
|
||||
|
||||
val response = repository.getElevationPoint(lon = lon, lat = lat)
|
||||
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)
|
||||
@@ -49,7 +49,7 @@ class ElevationInstrumentedTest {
|
||||
@Test
|
||||
fun testElevation_line_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// A short line segment around Heidelberg
|
||||
val coordinates = listOf(
|
||||
@@ -57,7 +57,7 @@ class ElevationInstrumentedTest {
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
|
||||
val response = repository.getElevationLine(coordinates = coordinates)
|
||||
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)
|
||||
|
@@ -0,0 +1,42 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
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,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.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.model.export.ExportRequest
|
||||
import org.nitri.ors.repository.ExportRepository
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExportRepositoryInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): ExportRepository {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return ExportRepository(api)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExport_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(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 = repository.export(bbox = bbox, 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())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExportJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
|
||||
val bbox = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.686507, 49.41943)
|
||||
)
|
||||
|
||||
|
||||
val response = repository.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())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExportTopoJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
|
||||
val bbox = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.686507, 49.41943)
|
||||
)
|
||||
|
||||
val topo = repository.exportTopoJson(bbox = bbox, profile = "driving-car")
|
||||
|
||||
assertNotNull("TopoJSON Export response should not be null", topo)
|
||||
assertTrue("Type should be present", topo.type.isNotBlank())
|
||||
}
|
||||
}
|
@@ -8,29 +8,31 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.GeocodeRepository
|
||||
import org.nitri.ors.helper.GeocodeHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class GeocodeInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): GeocodeRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, GeocodeHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return GeocodeRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = GeocodeHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeocode_search_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repo = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
|
||||
val response = repo.search(
|
||||
text = "Heidelberg",
|
||||
apiKey = apiKey,
|
||||
size = 5
|
||||
)
|
||||
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())
|
||||
@@ -49,19 +51,21 @@ class GeocodeInstrumentedTest {
|
||||
@Test
|
||||
fun testGeocode_reverse_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repo = createRepository(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 = repo.reverse(
|
||||
apiKey = apiKey,
|
||||
lon = lon,
|
||||
lat = lat,
|
||||
size = 5
|
||||
)
|
||||
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())
|
||||
@@ -74,14 +78,16 @@ class GeocodeInstrumentedTest {
|
||||
@Test
|
||||
fun testGeocode_autocomplete_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repo = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
|
||||
val response = repo.autocomplete(
|
||||
apiKey = apiKey,
|
||||
text = "Heidelb",
|
||||
size = 5
|
||||
)
|
||||
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())
|
||||
|
@@ -0,0 +1,49 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -8,22 +8,22 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.IsochronesRepository
|
||||
import org.nitri.ors.helper.IsochronesHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IsochronesInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): IsochronesRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, IsochronesHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return IsochronesRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = IsochronesHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsochrones_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Heidelberg, Germany [lon, lat]
|
||||
val locations = listOf(
|
||||
@@ -31,15 +31,17 @@ class IsochronesInstrumentedTest {
|
||||
)
|
||||
// 5 minutes (300 seconds)
|
||||
val range = listOf(300)
|
||||
val profile = "driving-car"
|
||||
val profile = Profile.DRIVING_CAR
|
||||
|
||||
val response = repository.getIsochrones(
|
||||
locations = locations,
|
||||
range = range,
|
||||
profile = profile,
|
||||
attributes = null,
|
||||
rangeType = "time"
|
||||
)
|
||||
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())
|
||||
|
@@ -0,0 +1,46 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -9,37 +9,39 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.MatrixRepository
|
||||
import org.nitri.ors.helper.MatrixHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MatrixInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): MatrixRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, MatrixHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return MatrixRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = MatrixHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMatrix_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(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 = "driving-car"
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val metrics = listOf("duration", "distance")
|
||||
|
||||
val response = repository.getMatrix(
|
||||
locations = locations,
|
||||
profile = profile,
|
||||
metrics = metrics,
|
||||
resolveLocations = false
|
||||
)
|
||||
val response = with(helper) {
|
||||
client.getMatrix(
|
||||
locations = locations,
|
||||
profile = profile,
|
||||
metrics = metrics,
|
||||
resolveLocations = false
|
||||
)
|
||||
}
|
||||
|
||||
assertNotNull("Matrix response should not be null", response)
|
||||
|
||||
|
@@ -0,0 +1,43 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -8,24 +8,24 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.model.optimization.Job
|
||||
import org.nitri.ors.model.optimization.Vehicle
|
||||
import org.nitri.ors.repository.OptimizationRepository
|
||||
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 createRepository(context: Context): OptimizationRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, OptimizationHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return OptimizationRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = OptimizationHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOptimization_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
|
||||
// Simple scenario in/near Heidelberg, Germany
|
||||
val vehicle = Vehicle(
|
||||
@@ -47,10 +47,12 @@ class OptimizationInstrumentedTest {
|
||||
)
|
||||
)
|
||||
|
||||
val response = repository.getOptimization(
|
||||
vehicles = listOf(vehicle),
|
||||
jobs = jobs
|
||||
)
|
||||
val response = with(helper) {
|
||||
client.getOptimization(
|
||||
vehicles = listOf(vehicle),
|
||||
jobs = jobs
|
||||
)
|
||||
}
|
||||
|
||||
// Basic assertions
|
||||
assertNotNull("Optimization response should not be null", response)
|
||||
|
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -9,47 +9,50 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.PoisRepository
|
||||
import org.nitri.ors.helper.PoisHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
//@RunWith(AndroidJUnit4::class)
|
||||
class PoisInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): PoisRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, PoisHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return PoisRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = PoisHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPois_byBbox_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(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 = repository.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)
|
||||
}
|
||||
// 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)
|
||||
// }
|
||||
}
|
||||
|
@@ -0,0 +1,58 @@
|
||||
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())
|
||||
}
|
||||
}
|
@@ -4,34 +4,31 @@ import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.ResponseBody
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.model.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.repository.ExportRepository
|
||||
import org.nitri.ors.repository.RouteRepository
|
||||
import retrofit2.Response
|
||||
import org.nitri.ors.domain.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.helper.RouteHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class RouteRepositoryInstrumentedTest {
|
||||
class RouteInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): RouteRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, RouteHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return RouteRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val repo = RouteHelper()
|
||||
return client to repo
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFetchRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val route = repository.getRoute(start, end, "driving-car")
|
||||
val route = with(repository) { client.getRoute(start, end, "driving-car") }
|
||||
|
||||
assertNotNull("Route should not be null", route)
|
||||
}
|
||||
@@ -39,12 +36,12 @@ class RouteRepositoryInstrumentedTest {
|
||||
@Test
|
||||
fun testFetchGpxRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val gpxXml = repository.getRouteGpx(start, end,"driving-car")
|
||||
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" }
|
||||
@@ -53,12 +50,13 @@ class RouteRepositoryInstrumentedTest {
|
||||
@Test
|
||||
fun testFetchGeoJsonRoute_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val start = Pair(8.681495, 49.41461)
|
||||
val end = Pair(8.687872, 49.420318)
|
||||
|
||||
val route: GeoJsonRouteResponse = repository.getRouteGeoJson(start, end, "driving-car")
|
||||
val route: GeoJsonRouteResponse = with(repository) { client.getRouteGeoJson(start, end,
|
||||
Profile.DRIVING_CAR) }
|
||||
|
||||
assertNotNull("Route should not be null", route)
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
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())
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -9,37 +9,39 @@ import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.SnapRepository
|
||||
import org.nitri.ors.helper.SnapHelper
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SnapInstrumentedTest {
|
||||
|
||||
private fun createRepository(context: Context): SnapRepository {
|
||||
private fun create(context: Context): Pair<DefaultOrsClient, SnapHelper> {
|
||||
val apiKey = context.getString(R.string.ors_api_key)
|
||||
val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
return SnapRepository(api)
|
||||
val client = DefaultOrsClient(apiKey, context)
|
||||
val helper = SnapHelper()
|
||||
return client to helper
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSnap_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(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 = "driving-car"
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50 // meters
|
||||
|
||||
val response = repository.getSnap(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_test"
|
||||
)
|
||||
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)
|
||||
@@ -55,21 +57,23 @@ class SnapInstrumentedTest {
|
||||
@Test
|
||||
fun testSnapJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, helper) = create(context)
|
||||
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
val profile = "driving-car"
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50
|
||||
|
||||
val response = repository.getSnapJson(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_json_test"
|
||||
)
|
||||
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)
|
||||
@@ -83,21 +87,23 @@ class SnapInstrumentedTest {
|
||||
@Test
|
||||
fun testSnapGeoJson_successful() = runBlocking {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val repository = createRepository(context)
|
||||
val (client, repository) = create(context)
|
||||
|
||||
val locations = listOf(
|
||||
listOf(8.681495, 49.41461),
|
||||
listOf(8.687872, 49.420318)
|
||||
)
|
||||
val profile = "driving-car"
|
||||
val profile = Profile.DRIVING_CAR
|
||||
val radius = 50
|
||||
|
||||
val response = repository.getSnapGeoJson(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
profile = profile,
|
||||
id = "snap_geojson_test"
|
||||
)
|
||||
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)
|
||||
|
300
ors-client/src/main/java/org/nitri/ors/DefaultOrsClient.kt
Normal file
300
ors-client/src/main/java/org/nitri/ors/DefaultOrsClient.kt
Normal file
@@ -0,0 +1,300 @@
|
||||
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,10 +0,0 @@
|
||||
package org.nitri.ors
|
||||
|
||||
import android.content.Context
|
||||
import org.nitri.ors.client.OpenRouteServiceClient
|
||||
import org.nitri.ors.repository.RouteRepository
|
||||
|
||||
class OpenRouteService(apiKey: String, context: Context) {
|
||||
private val api = OpenRouteServiceClient.create(apiKey, context)
|
||||
val routeRepository = RouteRepository(api)
|
||||
}
|
17
ors-client/src/main/java/org/nitri/ors/Ors.kt
Normal file
17
ors-client/src/main/java/org/nitri/ors/Ors.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
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)
|
||||
}
|
196
ors-client/src/main/java/org/nitri/ors/OrsClient.kt
Normal file
196
ors-client/src/main/java/org/nitri/ors/OrsClient.kt
Normal file
@@ -0,0 +1,196 @@
|
||||
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,28 +1,28 @@
|
||||
package org.nitri.ors.api
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import org.nitri.ors.model.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.model.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.model.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.model.elevation.ElevationPointResponse
|
||||
import org.nitri.ors.model.export.ExportRequest
|
||||
import org.nitri.ors.model.export.ExportResponse
|
||||
import org.nitri.ors.model.export.TopoJsonExportResponse
|
||||
import org.nitri.ors.model.geocode.GeocodeSearchResponse
|
||||
import org.nitri.ors.model.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.model.isochrones.IsochronesResponse
|
||||
import org.nitri.ors.model.matrix.MatrixRequest
|
||||
import org.nitri.ors.model.matrix.MatrixResponse
|
||||
import org.nitri.ors.model.optimization.OptimizationRequest
|
||||
import org.nitri.ors.model.optimization.OptimizationResponse
|
||||
import org.nitri.ors.model.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.model.pois.PoisRequest
|
||||
import org.nitri.ors.model.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.model.route.RouteRequest
|
||||
import org.nitri.ors.model.route.RouteResponse
|
||||
import org.nitri.ors.model.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.model.snap.SnapRequest
|
||||
import org.nitri.ors.model.snap.SnapResponse
|
||||
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
|
||||
@@ -33,7 +33,6 @@ import retrofit2.http.Query
|
||||
interface OpenRouteServiceApi {
|
||||
|
||||
// Directions
|
||||
|
||||
@GET("v2/directions/{profile}")
|
||||
suspend fun getRouteSimple(
|
||||
@Path("profile") profile: String,
|
||||
|
@@ -0,0 +1,97 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.elevation
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -1,4 +1,4 @@
|
||||
package org.nitri.ors.model.elevation
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@@ -0,0 +1,22 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.elevation
|
||||
package org.nitri.ors.domain.elevation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@@ -0,0 +1,45 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.export
|
||||
package org.nitri.ors.domain.export
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -12,23 +12,26 @@ 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] */
|
||||
/** `[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,
|
||||
@SerialName("toId") val toId: Long,
|
||||
@Serializable(with = StringAsDoubleSerializer::class)
|
||||
val weight: Double
|
||||
)
|
@@ -0,0 +1,42 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.geocode
|
||||
package org.nitri.ors.domain.geocode
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -0,0 +1,84 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.isochrones
|
||||
package org.nitri.ors.domain.isochrones
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -1,8 +1,9 @@
|
||||
package org.nitri.ors.model.isochrones
|
||||
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,
|
||||
@@ -11,6 +12,7 @@ data class IsochronesResponse(
|
||||
val metadata: IsochronesMetadata
|
||||
)
|
||||
|
||||
/** Individual isochrone feature. */
|
||||
@Serializable
|
||||
data class IsochroneFeature(
|
||||
val type: String,
|
||||
@@ -25,12 +27,11 @@ data class IsochroneProperties(
|
||||
val center: List<Double>
|
||||
)
|
||||
|
||||
/** Geometry of an isochrone feature. */
|
||||
@Serializable
|
||||
data class IsochroneGeometry(
|
||||
val type: String, // "Polygon" (sometimes "MultiPolygon")
|
||||
// ORS is returning Polygon for your case:
|
||||
val type: String,
|
||||
val coordinates: List<List<List<Double>>>
|
||||
// If you later see MultiPolygon, change to List<List<List<List<Double>>>> or make it polymorphic.
|
||||
)
|
||||
|
||||
@Serializable
|
@@ -0,0 +1,58 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.matrix
|
||||
package org.nitri.ors.domain.matrix
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -1,8 +1,8 @@
|
||||
package org.nitri.ors.model.matrix
|
||||
package org.nitri.ors.domain.matrix
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.model.meta.Metadata
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Represents the response from an ORS Matrix API request.
|
@@ -1,4 +1,4 @@
|
||||
package org.nitri.ors.model.meta
|
||||
package org.nitri.ors.domain.meta
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -0,0 +1,86 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.optimization
|
||||
package org.nitri.ors.domain.optimization
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -1,4 +1,4 @@
|
||||
package org.nitri.ors.model.optimization
|
||||
package org.nitri.ors.domain.optimization
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -0,0 +1,79 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
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,4 +1,4 @@
|
||||
package org.nitri.ors.model.pois
|
||||
package org.nitri.ors.domain.pois
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
@@ -0,0 +1,23 @@
|
||||
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,8 +1,9 @@
|
||||
package org.nitri.ors.model.route
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.model.meta.Metadata
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/** GeoJSON response for the directions endpoint. */
|
||||
@Serializable
|
||||
data class GeoJsonRouteResponse(
|
||||
val type: String,
|
||||
@@ -11,6 +12,7 @@ data class GeoJsonRouteResponse(
|
||||
val metadata: Metadata
|
||||
)
|
||||
|
||||
/** A single GeoJSON feature within the route response. */
|
||||
@Serializable
|
||||
data class Feature(
|
||||
val type: String,
|
||||
@@ -18,6 +20,7 @@ data class Feature(
|
||||
val properties: Map<String, kotlinx.serialization.json.JsonElement> = emptyMap()
|
||||
)
|
||||
|
||||
/** Geometry of a route feature. */
|
||||
@Serializable
|
||||
data class Geometry(
|
||||
val type: String,
|
@@ -1,8 +1,9 @@
|
||||
package org.nitri.ors.model.route
|
||||
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,
|
@@ -1,41 +1,67 @@
|
||||
package org.nitri.ors.model.route
|
||||
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
|
||||
)
|
||||
|
@@ -0,0 +1,22 @@
|
||||
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,9 +1,10 @@
|
||||
package org.nitri.ors.model.route
|
||||
package org.nitri.ors.domain.route
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.model.meta.Metadata
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/** Response from the directions endpoint. */
|
||||
@Serializable
|
||||
data class RouteResponse(
|
||||
val routes: List<Route>,
|
||||
@@ -11,6 +12,7 @@ data class RouteResponse(
|
||||
val metadata: Metadata? = null
|
||||
)
|
||||
|
||||
/** Single route variant returned by the API. */
|
||||
@Serializable
|
||||
data class Route(
|
||||
val summary: RouteSummary,
|
||||
@@ -20,6 +22,7 @@ data class Route(
|
||||
val wayPoints: List<Int>? = null
|
||||
)
|
||||
|
||||
/** Aggregated summary of a route. */
|
||||
@Serializable
|
||||
data class RouteSummary(
|
||||
val distance: Double,
|
||||
@@ -28,6 +31,7 @@ data class RouteSummary(
|
||||
val descent: Double? = null
|
||||
)
|
||||
|
||||
/** One segment between intermediate waypoints. */
|
||||
@Serializable
|
||||
data class Segment(
|
||||
val distance: Double,
|
||||
@@ -40,6 +44,7 @@ data class Segment(
|
||||
val percentage: Double? = null
|
||||
)
|
||||
|
||||
/** Turn instruction within a segment. */
|
||||
@Serializable
|
||||
data class Step(
|
||||
val distance: Double,
|
@@ -0,0 +1,38 @@
|
||||
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,8 +1,8 @@
|
||||
package org.nitri.ors.model.snap
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.model.meta.Metadata
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Snap response in GeoJSON format
|
@@ -1,4 +1,4 @@
|
||||
package org.nitri.ors.model.snap
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package org.nitri.ors.model.snap
|
||||
package org.nitri.ors.domain.snap
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.nitri.ors.model.meta.Metadata
|
||||
import org.nitri.ors.domain.meta.Metadata
|
||||
|
||||
/**
|
||||
* Response for ORS /v2/snap/{profile}[/json]
|
@@ -1,29 +1,23 @@
|
||||
package org.nitri.ors.repository
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.elevation.ElevationLineRequest
|
||||
import org.nitri.ors.model.elevation.ElevationLineResponse
|
||||
import org.nitri.ors.model.elevation.ElevationPointRequest
|
||||
import org.nitri.ors.model.elevation.ElevationPointResponse
|
||||
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
|
||||
|
||||
class ElevationRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
/**
|
||||
* Calls the ORS elevation/line POST endpoint with a prepared request.
|
||||
*/
|
||||
suspend fun getElevationLine(request: ElevationLineRequest): ElevationLineResponse =
|
||||
api.getElevationLine(request)
|
||||
/** 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 getElevationLine(
|
||||
suspend fun OrsClient.getElevationLine(
|
||||
coordinates: List<List<Double>>, // [[lon,lat], [lon,lat], ...]
|
||||
dataset: String? = null,
|
||||
formatOut: String = "geojson",
|
||||
@@ -44,7 +38,7 @@ class ElevationRepository(private val api: OpenRouteServiceApi) {
|
||||
geometry = geometry,
|
||||
dataset = dataset
|
||||
)
|
||||
return api.getElevationLine(request)
|
||||
return getElevationLine(request)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +49,7 @@ class ElevationRepository(private val api: OpenRouteServiceApi) {
|
||||
* @param formatOut either "geojson" or "point"
|
||||
* @param dataset optional dataset (e.g., "srtm")
|
||||
*/
|
||||
suspend fun getElevationPoint(
|
||||
suspend fun OrsClient.getElevationPoint(
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
formatOut: String = "geojson",
|
||||
@@ -67,6 +61,6 @@ class ElevationRepository(private val api: OpenRouteServiceApi) {
|
||||
dataset = dataset,
|
||||
geometry = listOf(lon, lat)
|
||||
)
|
||||
return api.getElevationPoint(request)
|
||||
return getElevationPoint(request)
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
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,21 +1,19 @@
|
||||
package org.nitri.ors.repository
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.geocode.GeocodeSearchResponse
|
||||
import org.nitri.ors.OrsClient
|
||||
import org.nitri.ors.domain.geocode.GeocodeSearchResponse
|
||||
|
||||
/**
|
||||
* Repository for ORS Geocoding endpoints using GET requests only.
|
||||
*
|
||||
* Note: ORS Pelias geocoding requires an api_key query parameter even though
|
||||
* the client also sends an Authorization header. Therefore, methods here accept
|
||||
* apiKey explicitly and pass it through to the API interface.
|
||||
* Methods are member extensions on [OrsClient] and delegate to it.
|
||||
*/
|
||||
class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
class GeocodeHelper {
|
||||
|
||||
/**
|
||||
* Forward geocoding search.
|
||||
*/
|
||||
suspend fun search(
|
||||
suspend fun OrsClient.search(
|
||||
text: String,
|
||||
apiKey: String,
|
||||
focusLon: Double? = null,
|
||||
@@ -33,8 +31,9 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
layersCsv: String? = null,
|
||||
size: Int? = 10,
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeSearch(
|
||||
return geocodeSearch(
|
||||
text = text,
|
||||
apiKey = apiKey,
|
||||
focusLon = focusLon,
|
||||
focusLat = focusLat,
|
||||
rectMinLon = rectMinLon,
|
||||
@@ -48,15 +47,14 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
boundaryCountry = boundaryCountry,
|
||||
sourcesCsv = sourcesCsv,
|
||||
layersCsv = layersCsv,
|
||||
size = size,
|
||||
apiKey = apiKey
|
||||
size = size
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocomplete search; returns suggestions for a partial query.
|
||||
*/
|
||||
suspend fun autocomplete(
|
||||
suspend fun OrsClient.autocomplete(
|
||||
apiKey: String,
|
||||
text: String,
|
||||
focusLon: Double? = null,
|
||||
@@ -73,7 +71,7 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
layers: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return api.autocomplete(
|
||||
return geocodeAutocomplete(
|
||||
apiKey = apiKey,
|
||||
text = text,
|
||||
focusLon = focusLon,
|
||||
@@ -95,7 +93,7 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
/**
|
||||
* Structured forward geocoding using address fields.
|
||||
*/
|
||||
suspend fun structured(
|
||||
suspend fun OrsClient.structured(
|
||||
apiKey: String,
|
||||
address: String? = null,
|
||||
neighbourhood: String? = null,
|
||||
@@ -119,7 +117,7 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
sources: List<String>? = null,
|
||||
size: Int? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeStructured(
|
||||
return geocodeStructured(
|
||||
apiKey = apiKey,
|
||||
address = address,
|
||||
neighbourhood = neighbourhood,
|
||||
@@ -148,7 +146,7 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
/**
|
||||
* Reverse geocoding for a point.
|
||||
*/
|
||||
suspend fun reverse(
|
||||
suspend fun OrsClient.reverse(
|
||||
apiKey: String,
|
||||
lon: Double,
|
||||
lat: Double,
|
||||
@@ -158,7 +156,7 @@ class GeocodeRepository(private val api: OpenRouteServiceApi) {
|
||||
sources: List<String>? = null,
|
||||
boundaryCountry: String? = null,
|
||||
): GeocodeSearchResponse {
|
||||
return api.geocodeReverse(
|
||||
return geocodeReverse(
|
||||
apiKey = apiKey,
|
||||
lon = lon,
|
||||
lat = lat,
|
@@ -0,0 +1,24 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
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,12 +1,13 @@
|
||||
package org.nitri.ors.repository
|
||||
package org.nitri.ors.helper
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.pois.GeoJsonGeometry
|
||||
import org.nitri.ors.model.pois.Geometry
|
||||
import org.nitri.ors.model.pois.PoisGeoJsonResponse
|
||||
import org.nitri.ors.model.pois.PoisRequest
|
||||
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
|
||||
|
||||
class PoisRepository(private val api: OpenRouteServiceApi) {
|
||||
/** Convenience extensions for the POIs endpoints. */
|
||||
class PoisHelper {
|
||||
|
||||
/**
|
||||
* Query POIs within a bounding box.
|
||||
@@ -17,7 +18,7 @@ class PoisRepository(private val api: OpenRouteServiceApi) {
|
||||
* @param sortby Optional sort field
|
||||
* @param buffer Optional buffer in meters applied to the geometry
|
||||
*/
|
||||
suspend fun getPoisByBbox(
|
||||
suspend fun OrsClient.getPoisByBbox(
|
||||
bbox: List<List<Double>>,
|
||||
filters: Map<String, String>? = null,
|
||||
limit: Int? = null,
|
||||
@@ -30,7 +31,7 @@ class PoisRepository(private val api: OpenRouteServiceApi) {
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
return api.getPois(request)
|
||||
return getPois(request)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ class PoisRepository(private val api: OpenRouteServiceApi) {
|
||||
* @param point [lon, lat]
|
||||
* @param buffer Buffer radius in meters
|
||||
*/
|
||||
suspend fun getPoisByPoint(
|
||||
suspend fun OrsClient.getPoisByPoint(
|
||||
point: List<Double>,
|
||||
buffer: Int,
|
||||
filters: Map<String, String>? = null,
|
||||
@@ -55,6 +56,6 @@ class PoisRepository(private val api: OpenRouteServiceApi) {
|
||||
limit = limit,
|
||||
sortby = sortby
|
||||
)
|
||||
return api.getPois(request)
|
||||
return getPois(request)
|
||||
}
|
||||
}
|
81
ors-client/src/main/java/org/nitri/ors/helper/RouteHelper.kt
Normal file
81
ors-client/src/main/java/org/nitri/ors/helper/RouteHelper.kt
Normal file
@@ -0,0 +1,81 @@
|
||||
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)
|
||||
}
|
||||
}
|
56
ors-client/src/main/java/org/nitri/ors/helper/SnapHelper.kt
Normal file
56
ors-client/src/main/java/org/nitri/ors/helper/SnapHelper.kt
Normal file
@@ -0,0 +1,56 @@
|
||||
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,17 +0,0 @@
|
||||
package org.nitri.ors.model.elevation
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ElevationPointRequest(
|
||||
@SerialName("format_in")
|
||||
val formatIn: String, // Input format, must be provided (e.g., "point")
|
||||
|
||||
@SerialName("format_out")
|
||||
val formatOut: String = "geojson", // "geojson" or "point"
|
||||
|
||||
val dataset: String? = null, // Optional dataset, e.g. "srtm"
|
||||
|
||||
val geometry: List<Double> // [lon, lat]
|
||||
)
|
@@ -1,10 +0,0 @@
|
||||
package org.nitri.ors.model.export
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ExportRequest(
|
||||
val bbox: List<List<Double>>, // [minLon, minLat, maxLon, maxLat]
|
||||
val id: String,
|
||||
val geometry: Boolean? = null
|
||||
)
|
@@ -1,41 +0,0 @@
|
||||
package org.nitri.ors.model.export
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TopoJsonExportResponse(
|
||||
val type: String, // "Topology"
|
||||
val objects: TopoObjects,
|
||||
/** Top-level arcs are arrays of [lon, lat] Double coordinates */
|
||||
val arcs: List<List<List<Double>>>,
|
||||
val bbox: List<Double> // [minLon, minLat, maxLon, maxLat]
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TopoObjects(
|
||||
val network: GeometryCollection
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeometryCollection(
|
||||
val type: String, // "GeometryCollection"
|
||||
val geometries: List<TopoGeometry>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TopoGeometry(
|
||||
val type: String, // "LineString" (currently)
|
||||
/** These are indices into the top-level arcs array (can be negative to indicate reversal) */
|
||||
val arcs: List<Int>,
|
||||
val properties: GeometryProps? = null // be lenient; fields can vary
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeometryProps(
|
||||
// weight often arrives as a string in this endpoint
|
||||
val weight: String? = null,
|
||||
@SerialName("node_from") val nodeFrom: Long? = null,
|
||||
@SerialName("node_to") val nodeTo: Long? = null
|
||||
)
|
||||
|
@@ -1,26 +0,0 @@
|
||||
package org.nitri.ors.model.pois
|
||||
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@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(
|
||||
val bbox: List<List<Double>>? = null, // [[minLon,minLat],[maxLon,maxLat]]
|
||||
val geojson: GeoJsonGeometry? = null, // optional: GeoJSON geometry
|
||||
val buffer: Int? = null // optional: buffer in meters
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GeoJsonGeometry(
|
||||
val type: String, // e.g., "Point"
|
||||
val coordinates: List<Double> // [lon, lat]
|
||||
)
|
@@ -1,36 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.export.ExportRequest
|
||||
import org.nitri.ors.model.export.ExportResponse
|
||||
import org.nitri.ors.model.export.TopoJsonExportResponse
|
||||
|
||||
class ExportRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
suspend fun export(bbox: List<List<Double>>, geometry: Boolean? = null, profile: String): ExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request",
|
||||
geometry = geometry
|
||||
)
|
||||
return api.export(profile, request)
|
||||
}
|
||||
|
||||
suspend fun exportJson(bbox: List<List<Double>>, geometry: Boolean? = null, profile: String): ExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request_json",
|
||||
geometry = geometry
|
||||
)
|
||||
return api.exportJson(profile, request)
|
||||
}
|
||||
|
||||
suspend fun exportTopoJson(bbox: List<List<Double>>, geometry: Boolean? = null, profile: String): TopoJsonExportResponse {
|
||||
val request = ExportRequest(
|
||||
bbox = bbox,
|
||||
id = "export_request_topo_json",
|
||||
geometry = geometry
|
||||
)
|
||||
return api.exportTopoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.export.ExportRequest
|
||||
import org.nitri.ors.model.export.ExportResponse
|
||||
import org.nitri.ors.model.export.TopoJsonExportResponse
|
||||
import org.nitri.ors.model.isochrones.IsochronesRequest
|
||||
import org.nitri.ors.model.isochrones.IsochronesResponse
|
||||
class IsochronesRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
suspend fun getIsochrones(
|
||||
locations: List<List<Double>>,
|
||||
range: List<Int>,
|
||||
profile: String,
|
||||
attributes: List<String>? = null,
|
||||
rangeType: String? = null,
|
||||
): IsochronesResponse {
|
||||
val request = IsochronesRequest(
|
||||
locations, range, rangeType, attributes
|
||||
)
|
||||
return api.getIsochrones(profile, request)
|
||||
}
|
||||
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.matrix.MatrixRequest
|
||||
import org.nitri.ors.model.matrix.MatrixResponse
|
||||
|
||||
class MatrixRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
/**
|
||||
* Calls the ORS Matrix endpoint for the given profile.
|
||||
*
|
||||
* @param locations List of [lon, lat] coordinate pairs.
|
||||
* @param profile ORS profile, e.g. "driving-car", "foot-hiking", etc.
|
||||
* @param metrics Optional list of metrics to include (e.g., ["duration"], ["distance"], or both).
|
||||
* @param sources Optional list of indices into locations used as sources.
|
||||
* @param destinations Optional list of indices into locations used as destinations.
|
||||
* @param resolveLocations Optional flag to resolve/snaps locations to the network.
|
||||
* @param id Optional arbitrary request id.
|
||||
*/
|
||||
suspend fun getMatrix(
|
||||
locations: List<List<Double>>,
|
||||
profile: String,
|
||||
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 api.getMatrix(profile, request)
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.optimization.CustomMatrix
|
||||
import org.nitri.ors.model.optimization.Job
|
||||
import org.nitri.ors.model.optimization.OptimizationRequest
|
||||
import org.nitri.ors.model.optimization.OptimizationResponse
|
||||
import org.nitri.ors.model.optimization.Shipment
|
||||
import org.nitri.ors.model.optimization.Vehicle
|
||||
|
||||
/**
|
||||
* Repository for the OpenRouteService Optimization endpoint.
|
||||
*
|
||||
* This is a thin wrapper around the Retrofit API, similar to other repositories
|
||||
* in this package. The repository builds the OptimizationRequest from the
|
||||
* provided arguments.
|
||||
*/
|
||||
class OptimizationRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
/**
|
||||
* Calls the ORS Optimization endpoint with provided arguments and builds the request.
|
||||
*
|
||||
* @param vehicles Required list of vehicles.
|
||||
* @param jobs Optional list of jobs.
|
||||
* @param shipments Optional list of shipments.
|
||||
* @param matrices Optional custom matrices keyed by profile.
|
||||
* @param options Optional free-form options.
|
||||
*/
|
||||
suspend fun 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 api.getOptimization(request)
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.route.GeoJsonRouteResponse
|
||||
import org.nitri.ors.model.route.RouteRequest
|
||||
import org.nitri.ors.model.route.RouteResponse
|
||||
|
||||
class RouteRepository(private val api: OpenRouteServiceApi) {
|
||||
suspend fun 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 api.getRoute(profile, request)
|
||||
}
|
||||
|
||||
suspend fun 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 api.getRouteGpx(profile, request).body()?.string() ?: ""
|
||||
}
|
||||
|
||||
suspend fun getRouteGpx(coordinates: List<List<Double>>, language: String, profile: String): String {
|
||||
val request = RouteRequest(coordinates = coordinates, language = language)
|
||||
return api.getRouteGpx(profile, request).body()?.string() ?: ""
|
||||
}
|
||||
|
||||
suspend fun getRouteGeoJson(start: Pair<Double, Double>, end: Pair<Double, Double>, profile: String): GeoJsonRouteResponse {
|
||||
val request = RouteRequest(coordinates = listOf(
|
||||
listOf(start.first, start.second),
|
||||
listOf(end.first, end.second)
|
||||
))
|
||||
return api.getRouteGeoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
package org.nitri.ors.repository
|
||||
|
||||
import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.snap.SnapGeoJsonResponse
|
||||
import org.nitri.ors.model.snap.SnapRequest
|
||||
import org.nitri.ors.model.snap.SnapResponse
|
||||
|
||||
class SnapRepository(private val api: OpenRouteServiceApi) {
|
||||
|
||||
/**
|
||||
* Calls the ORS Snap endpoint for the given profile.
|
||||
*
|
||||
* @param locations List of [lon, lat] coordinates to snap.
|
||||
* @param radius Maximum radius (meters) around given coordinates to search for graph edges.
|
||||
* @param profile ORS profile, e.g. "driving-car", "foot-hiking", etc.
|
||||
* @param id Optional arbitrary request id echoed back by the service.
|
||||
*/
|
||||
suspend fun getSnap(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: String,
|
||||
id: String? = null,
|
||||
): SnapResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return api.getSnap(profile, request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the ORS Snap JSON endpoint.
|
||||
*/
|
||||
suspend fun getSnapJson(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: String,
|
||||
id: String? = null,
|
||||
): SnapResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return api.getSnapJson(profile, request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the ORS Snap GeoJSON endpoint.
|
||||
*/
|
||||
suspend fun getSnapGeoJson(
|
||||
locations: List<List<Double>>,
|
||||
radius: Int,
|
||||
profile: String,
|
||||
id: String? = null,
|
||||
): SnapGeoJsonResponse {
|
||||
val request = SnapRequest(
|
||||
locations = locations,
|
||||
radius = radius,
|
||||
id = id
|
||||
)
|
||||
return api.getSnapGeoJson(profile, request)
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package org.nitri.ors.client
|
||||
package org.nitri.ors.restclient
|
||||
|
||||
import android.content.Context
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
@@ -11,14 +11,28 @@ import org.nitri.ors.api.OpenRouteServiceApi
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object OpenRouteServiceClient {
|
||||
/**
|
||||
* 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 }
|
||||
val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowSpecialFloatingPointValues = true
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
@@ -36,6 +50,32 @@ object OpenRouteServiceClient {
|
||||
|
||||
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)
|
@@ -0,0 +1,49 @@
|
||||
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,28 +1,26 @@
|
||||
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.api.OpenRouteServiceApi
|
||||
import org.nitri.ors.model.route.Route
|
||||
import org.nitri.ors.model.route.RouteRequest
|
||||
import org.nitri.ors.model.route.RouteResponse
|
||||
import org.nitri.ors.model.route.RouteSummary
|
||||
import org.nitri.ors.repository.RouteRepository
|
||||
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 RouteRepositoryTest {
|
||||
class RouteHelperTest {
|
||||
|
||||
private lateinit var api: OpenRouteServiceApi
|
||||
private lateinit var repository: RouteRepository
|
||||
private lateinit var client: OrsClient
|
||||
private lateinit var routeHelper: RouteHelper
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
api = mock(OpenRouteServiceApi::class.java)
|
||||
repository = RouteRepository(api)
|
||||
client = mock(OrsClient::class.java)
|
||||
routeHelper = RouteHelper()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -49,11 +47,11 @@ class RouteRepositoryTest {
|
||||
)
|
||||
)
|
||||
|
||||
`when`(api.getRoute(profile, expectedRequest)).thenReturn(expectedResponse)
|
||||
`when`(client.getRoute(Profile.DRIVING_CAR, expectedRequest)).thenReturn(expectedResponse)
|
||||
|
||||
val result = repository.getRoute(start, end, profile)
|
||||
val result = with(routeHelper) { client.getRoute(start, end, profile) }
|
||||
|
||||
assert(result == expectedResponse)
|
||||
verify(api).getRoute(profile, expectedRequest)
|
||||
verify(client).getRoute(Profile.DRIVING_CAR, expectedRequest)
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user