1
1
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:
Pygmalion69
2025-08-24 12:18:35 +02:00
17 changed files with 937 additions and 3 deletions

View File

@@ -0,0 +1,70 @@
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.optimization.Job
import org.nitri.ors.model.optimization.Vehicle
import org.nitri.ors.repository.OptimizationRepository
@RunWith(AndroidJUnit4::class)
class OptimizationInstrumentedTest {
private fun createRepository(context: Context): OptimizationRepository {
val apiKey = context.getString(R.string.ors_api_key)
val api = OpenRouteServiceClient.create(apiKey, context)
return OptimizationRepository(api)
}
@Test
fun testOptimization_successful() = runBlocking {
val context = ApplicationProvider.getApplicationContext<Context>()
val repository = createRepository(context)
// Simple scenario in/near Heidelberg, Germany
val vehicle = Vehicle(
id = 1,
profile = "driving-car",
// Start and end at/near Heidelberg castle parking
start = listOf(8.6910, 49.4100),
end = listOf(8.6910, 49.4100)
)
val jobs = listOf(
Job(
id = 101,
location = listOf(8.681495, 49.41461) // Heidelberg center
),
Job(
id = 102,
location = listOf(8.687872, 49.420318) // Nearby point
)
)
val response = repository.getOptimization(
vehicles = listOf(vehicle),
jobs = jobs
)
// Basic assertions
assertNotNull("Optimization response should not be null", response)
assertNotNull("Summary should be present", response.summary)
assertTrue("Routes list should be present", response.routes != null)
assertTrue("Routes should not be empty for solvable small case", response.routes.isNotEmpty())
val firstRoute = response.routes.first()
assertTrue("Route steps should not be empty", firstRoute.steps.isNotEmpty())
// Code is typically 0 for success in VROOM-like APIs; ensure non-negative as a safe check
assertTrue("Response code should be non-negative", response.code >= 0)
// Optional additional sanity checks
assertTrue("Total duration should be >= 0", response.summary.duration >= 0)
assertTrue("Total service should be >= 0", response.summary.service >= 0)
}
}

View File

@@ -0,0 +1,55 @@
package org.nitri.ors
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.nitri.ors.client.OpenRouteServiceClient
import org.nitri.ors.repository.PoisRepository
@RunWith(AndroidJUnit4::class)
class PoisInstrumentedTest {
private fun createRepository(context: Context): PoisRepository {
val apiKey = context.getString(R.string.ors_api_key)
val api = OpenRouteServiceClient.create(apiKey, context)
return PoisRepository(api)
}
@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)
}
}

View File

@@ -0,0 +1,114 @@
package org.nitri.ors
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.nitri.ors.client.OpenRouteServiceClient
import org.nitri.ors.repository.SnapRepository
@RunWith(AndroidJUnit4::class)
class SnapInstrumentedTest {
private fun createRepository(context: Context): SnapRepository {
val apiKey = context.getString(R.string.ors_api_key)
val api = OpenRouteServiceClient.create(apiKey, context)
return SnapRepository(api)
}
@Test
fun testSnap_successful() = runBlocking {
val context = ApplicationProvider.getApplicationContext<Context>()
val repository = createRepository(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 radius = 50 // meters
val response = repository.getSnap(
locations = locations,
radius = radius,
profile = profile,
id = "snap_test"
)
assertNotNull("Snap response should not be null", response)
assertNotNull("Metadata should be present", response.metadata)
assertTrue("Locations should not be empty", response.locations.isNotEmpty())
assertEquals("Should have as many results as inputs", locations.size, response.locations.size)
val first = response.locations.first()
assertNotNull("First snapped location should have coordinates", first.location)
assertEquals("Snapped coordinates should have 2 values [lon, lat]", 2, first.location.size)
assertTrue("Snapped distance should be non-negative", first.snappedDistance >= 0.0)
}
@Test
fun testSnapJson_successful() = runBlocking {
val context = ApplicationProvider.getApplicationContext<Context>()
val repository = createRepository(context)
val locations = listOf(
listOf(8.681495, 49.41461),
listOf(8.687872, 49.420318)
)
val profile = "driving-car"
val radius = 50
val response = repository.getSnapJson(
locations = locations,
radius = radius,
profile = profile,
id = "snap_json_test"
)
assertNotNull("Snap JSON response should not be null", response)
assertNotNull("Metadata should be present", response.metadata)
assertTrue("Locations should not be empty", response.locations.isNotEmpty())
response.locations.forEach { loc ->
assertEquals("Coordinate should be [lon, lat]", 2, loc.location.size)
assertTrue("Snapped distance should be non-negative", loc.snappedDistance >= 0.0)
}
}
@Test
fun testSnapGeoJson_successful() = runBlocking {
val context = ApplicationProvider.getApplicationContext<Context>()
val repository = createRepository(context)
val locations = listOf(
listOf(8.681495, 49.41461),
listOf(8.687872, 49.420318)
)
val profile = "driving-car"
val radius = 50
val response = repository.getSnapGeoJson(
locations = locations,
radius = radius,
profile = profile,
id = "snap_geojson_test"
)
assertNotNull("Snap GeoJSON response should not be null", response)
assertEquals("GeoJSON type should be FeatureCollection", "FeatureCollection", response.type)
assertNotNull("Metadata should be present", response.metadata)
assertTrue("Features should not be empty", response.features.isNotEmpty())
val feature = response.features.first()
assertEquals("Feature type should be Feature", "Feature", feature.type)
assertEquals("Geometry type should be Point", "Point", feature.geometry.type)
assertEquals("Point coordinates should be [lon, lat]", 2, feature.geometry.coordinates.size)
assertTrue("snapped_distance should be non-negative", feature.properties.snappedDistance >= 0.0)
assertTrue("source_id should be non-negative", feature.properties.sourceId >= 0)
}
}

View File

@@ -1,6 +1,5 @@
package org.nitri.ors.api
import org.nitri.ors.model.matrix.MatrixResponse
import okhttp3.ResponseBody
import org.nitri.ors.model.export.ExportRequest
import org.nitri.ors.model.export.ExportResponse
@@ -8,9 +7,17 @@ import org.nitri.ors.model.export.TopoJsonExportResponse
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 retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
@@ -20,6 +27,8 @@ import retrofit2.http.Query
interface OpenRouteServiceApi {
// Directions
@GET("v2/directions/{profile}")
suspend fun getRouteSimple(
@Path("profile") profile: String,
@@ -84,5 +93,38 @@ interface OpenRouteServiceApi {
@Body request: MatrixRequest
): MatrixResponse
// Snapping
@POST("v2/snap/{profile}")
suspend fun getSnap(
@Path("profile") profile: String,
@Body request: SnapRequest
): SnapResponse
@POST("v2/snap/{profile}/json")
suspend fun getSnapJson(
@Path("profile") profile: String,
@Body request: SnapRequest
): SnapResponse
@POST("v2/snap/{profile}/geojson")
suspend fun getSnapGeoJson(
@Path("profile") profile: String,
@Body request: SnapRequest
): SnapGeoJsonResponse
// POIs
@POST("pois")
suspend fun getPois(
@Body request: PoisRequest
): PoisGeoJsonResponse
// Optimization
@POST("optimization")
suspend fun getOptimization(
@Body request: OptimizationRequest
): OptimizationResponse
}

View File

@@ -9,6 +9,7 @@ import retrofit2.Retrofit
import retrofit2.create
import org.nitri.ors.api.OpenRouteServiceApi
import okhttp3.MediaType.Companion.toMediaType
import java.util.concurrent.TimeUnit
object OpenRouteServiceClient {
fun create(apiKey: String, context: Context): OpenRouteServiceApi {
@@ -27,18 +28,26 @@ object OpenRouteServiceClient {
val original = chain.request()
val newRequest = original.newBuilder()
.addHeader("Authorization", "Bearer $apiKey")
// ORS expects the API key in the Authorization header (no Bearer prefix)
.addHeader("Authorization", apiKey)
.addHeader("User-Agent", userAgent)
.addHeader("Accept", "application/json, application/geo+json, application/xml, text/xml, application/gpx+xml")
.build()
chain.proceed(newRequest)
}
// Increase timeouts to accommodate heavier endpoints like POIs
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.openrouteservice.org/")
.client(client)
// Prefer application/json for requests; also support application/geo+json responses
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.addConverterFactory(json.asConverterFactory("application/geo+json".toMediaType()))
.build()
return retrofit.create()

View File

@@ -0,0 +1,162 @@
package org.nitri.ors.model.optimization
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
/**
* Root payload sent to /optimization
* See: https://github.com/VROOM-Project/vroom/blob/master/docs/API.md
*/
@Serializable
data class OptimizationRequest(
/** Required: either jobs and/or shipments, and vehicles */
val jobs: List<Job>? = null,
val shipments: List<Shipment>? = null,
val vehicles: List<Vehicle>,
/** Optional custom matrices keyed by routing profile (e.g. "driving-car") */
val matrices: Map<String, CustomMatrix>? = null,
/** Optional free-form options bag passed to the optimization engine */
val options: Map<String, JsonElement>? = null
)
/* ---------------------------- Basics & helpers ---------------------------- */
@Serializable
data class TimeWindow(
/** seconds since midnight */
val start: Int,
/** seconds since midnight */
val end: Int
)
typealias LonLat = List<Double> // [lon, lat]
typealias Amount = List<Int> // capacities/quantities
typealias Skills = List<Int> // integers as in VROOM API
/* --------------------------------- Jobs ---------------------------------- */
@Serializable
data class Job(
/** Unique job id (required) */
val id: Int,
/** Service time in seconds spent on site (optional) */
val service: Int? = null,
/** Demand to be delivered/picked as a quantity vector (optional) */
val delivery: Amount? = null,
val pickup: Amount? = null,
val amount: Amount? = null, // generic synonym supported by VROOM
/** Location as coordinates or index into the matrix, provide one of them */
val location: LonLat? = null,
@SerialName("location_index") val locationIndex: Int? = null,
/** Allowed vehicles list (restrict which vehicles may handle this job) */
@SerialName("allowed_vehicles") val allowedVehicles: List<Int>? = null,
/** Disallowed vehicles list */
@SerialName("disallowed_vehicles") val disallowedVehicles: List<Int>? = null,
/** Skills required for this job */
val skills: Skills? = null,
/** 0..100 (higher = more important) */
val priority: Int? = null,
/** Multiple time windows supported */
@SerialName("time_windows") val timeWindows: List<TimeWindow>? = null
)
/* ------------------------------- Shipments -------------------------------- */
@Serializable
data class Shipment(
/** Unique shipment id (required) */
val id: Int,
/** Overall amount carried by the shipment */
val amount: Amount? = null,
/** Pickup and delivery legs (each looks like a job) */
val pickup: ShipmentStep,
val delivery: ShipmentStep,
/** Skills & priority rules apply to the *whole* shipment */
val skills: Skills? = null,
val priority: Int? = null
)
@Serializable
data class ShipmentStep(
/** Service time in seconds at this step */
val service: Int? = null,
/** Coordinates or index, provide one of them */
val location: LonLat? = null,
@SerialName("location_index") val locationIndex: Int? = null,
/** Time windows at this step */
@SerialName("time_windows") val timeWindows: List<TimeWindow>? = null
)
/* -------------------------------- Vehicles -------------------------------- */
@Serializable
data class Vehicle(
/** Unique vehicle id (required) */
val id: Int,
/** Routing profile, e.g., "driving-car" (required) */
val profile: String,
/** Vehicle capacity vector */
val capacity: Amount? = null,
/** Start/end positions as coordinates or as matrix indices (pick one style) */
val start: LonLat? = null,
val end: LonLat? = null,
@SerialName("start_index") val startIndex: Int? = null,
@SerialName("end_index") val endIndex: Int? = null,
/** Skills the vehicle provides */
val skills: Skills? = null,
/** When the vehicle is available to operate */
@SerialName("time_window") val timeWindow: TimeWindow? = null,
/** Optional list of breaks (each with service & time windows) */
val breaks: List<VehicleBreak>? = null,
/** Earliest/latest start/end (advanced; seconds since midnight) */
@SerialName("earliest_start") val earliestStart: Int? = null,
@SerialName("latest_end") val latestEnd: Int? = null,
/** Max tasks or max travel time limits (seconds) */
@SerialName("max_tasks") val maxTasks: Int? = null,
@SerialName("max_travel_time") val maxTravelTime: Int? = null,
/** Optional arbitrary metadata to round-trip through the solver */
val description: String? = null
)
@Serializable
data class VehicleBreak(
/** Service time in seconds spent for the break */
val service: Int? = null,
/** Allowed time windows for this break */
@SerialName("time_windows") val timeWindows: List<TimeWindow>
)
/* ------------------------------ Custom matrix ----------------------------- */
@Serializable
data class CustomMatrix(
/** Square matrix in seconds; required if provided */
val durations: List<List<Int>>? = null,
/** Optional distances in meters */
val distances: List<List<Int>>? = null
)

View File

@@ -0,0 +1,112 @@
package org.nitri.ors.model.optimization
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/** Top-level result from /optimization */
@Serializable
data class OptimizationResponse(
val code: Int,
val summary: OptimizationSummary,
val unassigned: List<Unassigned> = emptyList(),
val routes: List<OptimizedRoute> = emptyList()
)
/* -------------------------------- Summary -------------------------------- */
@Serializable
data class OptimizationSummary(
val cost: Int,
val routes: Int,
val unassigned: Int,
/** Totals present depending on your payload/constraints */
val delivery: List<Int>? = null,
val amount: List<Int>? = null,
val pickup: List<Int>? = null,
val setup: Int,
val service: Int,
val duration: Int,
@SerialName("waiting_time") val waitingTime: Int,
val priority: Int,
/** Usually empty unless you use hard constraints */
val violations: List<Violation> = emptyList(),
@SerialName("computing_times") val computingTimes: ComputingTimes? = null
)
@Serializable
data class ComputingTimes(
val loading: Int? = null,
val solving: Int? = null,
val routing: Int? = null
)
/* ------------------------------ Unassigned ------------------------------- */
@Serializable
data class Unassigned(
val id: Int,
val location: LonLat,
/** e.g. "job" */
val type: String
)
/* --------------------------------- Routes -------------------------------- */
@Serializable
data class OptimizedRoute(
val vehicle: Int,
val cost: Int,
/** Per-route totals (optional keys depending on your model) */
val delivery: List<Int>? = null,
val amount: List<Int>? = null,
val pickup: List<Int>? = null,
val setup: Int,
val service: Int,
val duration: Int,
@SerialName("waiting_time") val waitingTime: Int,
val priority: Int,
val steps: List<RouteStep> = emptyList(),
val violations: List<Violation> = emptyList()
)
@Serializable
data class RouteStep(
/** "start" | "job" | "end" | … */
val type: String,
val location: LonLat,
/** Only for job-like steps */
val id: Int? = null,
val job: Int? = null,
val setup: Int? = null,
val service: Int? = null,
@SerialName("waiting_time") val waitingTime: Int? = null,
/** Vehicle load after performing this step */
val load: List<Int>? = null,
/** Timestamps & cumulated travel */
val arrival: Int? = null,
val duration: Int? = null,
val violations: List<Violation> = emptyList()
)
/* ------------------------------- Violations ------------------------------ */
/** Kept flexible; VROOM may return several shapes depending on constraint hit. */
@Serializable
data class Violation(
val type: String? = null, // e.g., "capacity", "skills", "time_window", …
val id: Int? = null, // job/shipment id if applicable
val job: Int? = null,
val description: String? = null
)

View File

@@ -0,0 +1,26 @@
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]
)

View File

@@ -0,0 +1,83 @@
package org.nitri.ors.model.pois
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Top-level POIs GeoJSON response:
* {
* "type": "FeatureCollection",
* "bbox": [minLon, minLat, maxLon, maxLat],
* "features": [...],
* "information": {...}
* }
*/
@Serializable
data class PoisGeoJsonResponse(
val type: String, // "FeatureCollection"
val bbox: List<Double>? = null, // [minLon, minLat, maxLon, maxLat]
val features: List<PoiFeature>,
val information: PoisInformation? = null // metadata (sometimes called "information")
)
/** A single GeoJSON feature (Point) */
@Serializable
data class PoiFeature(
val type: String, // "Feature"
val geometry: PoiGeometry,
val properties: PoiProperties
)
@Serializable
data class PoiGeometry(
val type: String, // "Point"
val coordinates: List<Double> // [lon, lat]
)
/**
* Properties seen in your sample. Some fields are optional across results.
* - category_ids is an object with integer keys -> map to Map<Int, CategoryInfo>
* - osm_tags is a free-form OSM tag map (strings)
*/
@Serializable
data class PoiProperties(
@SerialName("osm_id") val osmId: Long,
@SerialName("osm_type") val osmType: Int,
val distance: Double,
@SerialName("category_ids") val categoryIds: Map<Int, CategoryInfo>? = null,
@SerialName("osm_tags") val osmTags: Map<String, String>? = null
)
@Serializable
data class CategoryInfo(
@SerialName("category_name") val categoryName: String,
@SerialName("category_group") val categoryGroup: String
)
/**
* The “information” block at the end of the response.
* Note: ORS uses "information" (not "metadata") here.
*/
@Serializable
data class PoisInformation(
val attribution: String? = null,
val version: String? = null,
val timestamp: Long? = null,
val query: PoisQueryInfo? = null
)
/** Echo of the request inside the information block */
@Serializable
data class PoisQueryInfo(
val request: String? = null, // "pois"
val geometry: PoisQueryGeometry? = null
)
/** Mirrors the requests geometry wrapper */
@Serializable
data class PoisQueryGeometry(
val bbox: List<List<Double>>? = null, // [[minLon,minLat],[maxLon,maxLat]]
val geojson: GeoJsonGeometry? = null,
val buffer: Int? = null
)

View File

@@ -0,0 +1,43 @@
package org.nitri.ors.model.snap
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.nitri.ors.model.meta.Metadata
/**
* Snap response in GeoJSON format
*/
@Serializable
data class SnapGeoJsonResponse(
val type: String, // "FeatureCollection"
val features: List<SnapFeature>,
val metadata: Metadata,
val bbox: List<Double>? = null
)
/**
* A GeoJSON Feature with snapped location info
*/
@Serializable
data class SnapFeature(
val type: String, // "Feature"
val properties: SnapProperties,
val geometry: SnapGeometry
)
@Serializable
data class SnapProperties(
val name: String? = null,
@SerialName("snapped_distance")
val snappedDistance: Double,
@SerialName("source_id")
val sourceId: Int
)
@Serializable
data class SnapGeometry(
val type: String, // "Point"
val coordinates: List<Double> // [lon, lat]
)

View File

@@ -0,0 +1,17 @@
package org.nitri.ors.model.snap
import kotlinx.serialization.Serializable
/**
* Snap request for ORS /v2/snap/{profile}/json
*
* @param locations List of [lon, lat] coordinates to snap.
* @param radius Maximum radius (meters) around given coordinates to search for graph edges.
* @param id Optional client-provided identifier.
*/
@Serializable
data class SnapRequest(
val locations: List<List<Double>>,
val radius: Int,
val id: String? = null
)

View File

@@ -0,0 +1,30 @@
package org.nitri.ors.model.snap
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.nitri.ors.model.meta.Metadata
/**
* Response for ORS /v2/snap/{profile}[/json]
*/
@Serializable
data class SnapResponse(
val locations: List<SnapLocation>,
val metadata: Metadata
)
/**
* One snapped input coordinate.
*/
@Serializable
data class SnapLocation(
/** [lon, lat] of the snapped position */
val location: List<Double>,
/** Optional street name if available */
val name: String? = null,
/** Distance in meters from the input to the snapped position */
@SerialName("snapped_distance")
val snappedDistance: Double
)

View File

@@ -0,0 +1,46 @@
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)
}
}

View File

@@ -0,0 +1,60 @@
package org.nitri.ors.repository
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
class PoisRepository(private val api: OpenRouteServiceApi) {
/**
* Query POIs within a bounding box.
*
* @param bbox [[minLon,minLat],[maxLon,maxLat]]
* @param filters Optional filters map as supported by ORS POIs
* @param limit Optional limit for number of features returned
* @param sortby Optional sort field
* @param buffer Optional buffer in meters applied to the geometry
*/
suspend fun getPoisByBbox(
bbox: List<List<Double>>,
filters: Map<String, String>? = null,
limit: Int? = null,
sortby: String? = null,
buffer: Int? = null
): PoisGeoJsonResponse {
val request = PoisRequest(
geometry = Geometry(bbox = bbox, buffer = buffer),
filters = filters,
limit = limit,
sortby = sortby
)
return api.getPois(request)
}
/**
* Query POIs around a point with a buffer radius.
*
* @param point [lon, lat]
* @param buffer Buffer radius in meters
*/
suspend fun getPoisByPoint(
point: List<Double>,
buffer: Int,
filters: Map<String, String>? = null,
limit: Int? = null,
sortby: String? = null
): PoisGeoJsonResponse {
val request = PoisRequest(
geometry = Geometry(
geojson = GeoJsonGeometry(type = "Point", coordinates = point),
buffer = buffer
),
filters = filters,
limit = limit,
sortby = sortby
)
return api.getPois(request)
}
}

View File

@@ -0,0 +1,65 @@
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)
}
}