Android SDK лавлах
mn.dock:dock-android Kotlin сангийн нийтийн зарлал бүр, эх кодонд бичигдсэнээр, KDoc тайлбартайгаа: Kotlin хөрвүүлэгчийн өөрийн задлагчаар уншсан, эх файл бүрт нэг блок, Client.kt эхэнд. Биеийг болон annotation-ийг (@Serializable) орхисон. Тайлбар нь кодонд бичигдсэн тул англиар. sdk/android/src/main/kotlin/mn/dock-оос scripts/android-reference.ts бичнэ; энэ хуудас кодоос хоцорсон, төрөл, функц, класс, interface эсвэл object-ийн гишүүн тайлбаргүй, эсвэл зарлал төрлөө бичээгүй бол make test-docs унана. Дуудлага бүрийг жишээтэй нь: Android SDK.
Client.kt
kotlin
/** Version of this SDK. Semantic versioning (PLAN.md 21). */
const val DOCK_SDK_VERSION: String
/** The dock API, used when Client(key) gets no baseUrl. */
const val DEFAULT_BASE_URL: String
/** Ends per side in one matrix call, as the API's MATRIX_MAX (api/src/modules/route/route-input.ts). */
const val MATRIX_MAX: Int
/** Contours in one isochrone call, as the API's CONTOURS_MAX (api/src/modules/route/route-input.ts). */
const val ISOCHRONE_CONTOURS_MAX: Int
/** The longest contour in minutes, as the API's CONTOUR_MINUTES_MAX. */
const val ISOCHRONE_MINUTES_MAX: Int
/** Points in one matching call, as the API's TRACE_MAX (api/src/modules/route/route-input.ts); at least 2. */
const val MATCHING_MAX: Int
/** Stops in one optimizedTrips call, as the API's STOPS_MAX (api/src/modules/route/route-input.ts); at least 2. */
const val OPTIMIZED_TRIPS_MAX: Int
/** The widest tilequery radius in metres, as the API's TILEQUERY_RADIUS_MAX_M (api/src/modules/tiles/tilequery.ts). */
const val TILEQUERY_RADIUS_MAX_M: Double
/** Features in one tilequery answer, as the API's TILEQUERY_LIMIT_MAX; 5 when no limit is sent. */
const val TILEQUERY_LIMIT_MAX: Int
/** Ends a route passes between from and to, as the API's VIA_MAX (api/src/modules/route/route-input.ts). */
const val ROUTE_VIA_MAX: Int
/** Same shape as the API's key check (api/src/modules/auth/api-key.ts): dk_ and 32 of 0-9, a-z without i, l, o, u. */
fun isValidKey(key: String): Boolean
/** One request. toString shows method and URL only: the key lives in [headers]. */
class HttpRequest(val method: String, val url: String, val headers: Map<String, String>, val body: ByteArray?) {
override fun toString(): String
}
/** One answer. [headers] look up case-insensitively. */
class HttpResponse(val status: Int, headers: Map<String, String>, val body: ByteArray) {
/** The response headers. Names look up case-insensitively. */
val headers: Map<String, String>
}
/** Sends one request. HttpURLConnection by default; tests and apps with their own networking (OkHttp) pass another.
* Throw IOException when no answer came back. */
fun interface DockTransport {
/** The answer to [request], whatever its status. Throws IOException when no answer came back. */
suspend fun send(request: HttpRequest): HttpResponse
}
/** HttpURLConnection on Dispatchers.IO. On Android, plain http (the local stack) needs a network security config. */
class UrlConnectionTransport(
connectTimeoutMs: Int = 15_000,
readTimeoutMs: Int = 30_000,
) : DockTransport {
override suspend fun send(request: HttpRequest): HttpResponse
}
/** An API refusal or a failed request. [code] is the API's `error` field (invalid_code, not_found, rate_limited,
* quota_exceeded, miss_cap, ...), or `network` when no response came back. */
class DockError(
/** HTTP status, 0 when no response came back. */
val status: Int,
val code: String,
override val message: String,
/** Seconds, from Retry-After on 429 and 503. null otherwise. */
val retryAfter: Double? = null,
cause: Throwable? = null,
) : Exception(message, cause) {
override fun toString(): String
}
/** A route the API marks deprecated (Deprecation, Sunset and Link headers, PLAN.md 17). */
data class DeprecationNotice(
/** "GET /v1/door/:code", the route, not the URL: no codes, no query. */
val route: String,
/** Day it was announced, YYYY-MM-DD Ulaanbaatar, or null if the header did not parse. */
val deprecated: String?,
/** Day the route stops answering (410 sunset from 00:00 Ulaanbaatar), YYYY-MM-DD, or null. */
val sunset: String?,
/** The status page with the replacement route, or null. */
val link: String?,
)
/** Notice from response headers, null when the route is not deprecated. */
fun deprecationNotice(route: String, headers: Map<String, String>): DeprecationNotice?
/**
* The dock API v1. Calls send the key in the Authorization header. Map requests to this API (TileJSON, tiles)
* get ?access_token= through [authorizedUrl], for MapLibre Native. The key never shows in toString or logs.
*
* @param key dk_ and 32 characters. Lives here only.
* @param baseUrl default https://geo.dock.mn.
* @param onDeprecation called once per route when an answer says the route is deprecated. Default: one line to
* System.out per route. Pass `{}` to stay quiet.
* @param telemetry share geocode searches and picks with dock (PLAN.md 18). Default false. Set true only after the
* user opted in; [setTelemetry] changes it later. Sent: the typed text, the result count, the pick. Not sent: time,
* point, device, session.
* @param placeCache places the user picked, kept on this device so [geocode] still answers when the network is down
* (PLAN.md 18). Default on, in memory, 100 places. Pass `PlaceCacheOptions(store = ...)` to keep them across
* launches, null for none. Throws IllegalArgumentException when max is not 1 to 1000.
*/
class Client(
key: String,
baseUrl: String = DEFAULT_BASE_URL,
transport: DockTransport = UrlConnectionTransport(),
onDeprecation: ((DeprecationNotice) -> Unit)? = null,
telemetry: Boolean = false,
placeCache: PlaceCacheOptions? = PlaceCacheOptions(),
) {
/** Where calls go: https://geo.dock.mn unless another was given. */
val baseUrl: String
override fun toString(): String
/** Name or address to places, best first. Needs geocode:read. limit 1 to 20. With no network (DockError code
* network) the answer comes from places the user picked on this device, with `offline` true, score = times
* picked, fields as last seen; with none of them matching, the network error is thrown. `proximity` is where
* the person is or the map centre: among names the query starts equally well, nearer places come first. It is
* sent with this request only, never kept or sent with telemetry; offline answers do not read it. [types] (API
* 0.70.0, Mapbox's types): only these kinds, chosen before the limit, offline too; sent once each, in the order
* given. null or empty: every kind. */
suspend fun geocode(
q: String, limit: Int? = null, proximity: LonLat? = null, types: List<GeocodeType>? = null,
): GeocodeResponse
/** The user's consent changed. Off drops every search not yet sent. On, searches go in batches of 50, on
* [appInBackground] and on [flushTelemetry]. */
fun setTelemetry(on: Boolean): Unit
/** The user chose this result from a geocode list. It is kept on this device for offline search (placeCache),
* and goes to telemetry only while telemetry is on. */
fun pick(result: GeocodeResult)
/** Places the user picked on this device, most picked first: a "your places" list. Empty with placeCache null. */
fun places(): List<Place>
/** The user asked to forget their places: clears memory and the store. */
fun forgetPlaces()
/** Sends what is waiting now, for example when an order is placed. null when telemetry is off or nothing waits.
* A failed send throws and its searches are not kept: no retry, nothing stored on the device. */
suspend fun flushTelemetry(): SearchTelemetryReceipt?
/** The app went to the background: sends waiting searches without waiting for the answer. Call it from onStop
* of your activity, or from a ProcessLifecycleOwner observer. Does nothing while telemetry is off. A failed send
* is dropped. */
fun appInBackground()
/** Starts recording the user's trip (PLAN.md 15). Off until called; call it only after the user said yes. Points
* stay in memory, never on disk. [source] gives the fixes (the app's location callback through a [PositionFeed]).
* Throws trace_running when a trip is being recorded or still waits to be sent. */
fun startTrace(consent: Consent, source: PositionSource, maxAccuracyM: Double = TRACE_MAX_ACCURACY_M)
/** true while a trip is being recorded. */
val tracing: Boolean
/** Stops recording and sends the trip to POST /v1/traces, split every 20,000 points. Needs trace:write. Keep each
* receipt's id: [deleteTrace] erases the trip. Empty when nothing was recorded or under 2 points. A failed send
* throws and keeps the unsent points in memory: call stopTrace() again to retry, [cancelTrace] to drop them. */
suspend fun stopTrace(): List<TraceReceipt>
/** Stops recording and drops the trip unsent: the user changed their mind. */
fun cancelTrace()
/** One of your organization's trips as stored, GeoJSON [lon, lat, t]: the export. Needs trace:write. */
suspend fun trace(id: String): Trace
/** Erases a trip's points and times now. The id is kept, never reused. Twice is fine. Needs trace:write. */
suspend fun deleteTrace(id: String): TraceDeleted
/** Trips per 500 m cell in a month, all partners together (PLAN.md 18, 19). [month] is YYYY-MM (Ulaanbaatar),
* published 14 days after it ends (400 month_open before). [bbox] inside Mongolia, sides at most 1 degree. A cell
* shows only with trips from 3 or more organizations; no cell means too few, not zero. Needs insights:read. */
suspend fun traceCells(month: String, bbox: BBox): TraceCells
/** Moving speed per 500 m cell in a month, all partners together: where traffic is slow (PLAN.md 18, 19). The
* same grid, month and bbox as traceCells. kmH is the counted meters over the counted seconds, to 0.1; parking and
* the last 200 m are left out. Per cell, not per road. A cell shows only under the trip cells rule; no cell means
* too few trips, not a slow road. No trip count. Needs insights:read. */
suspend fun traceSpeeds(month: String, bbox: BBox): TraceSpeeds
/** Moving speed per road in a month, all partners together: speed profiles per road (PLAN.md 18, 19). Each trip is
* matched to OSM ways with Valhalla when it is stored; a segment counts on a way under the traceSpeeds rule when
* both its points matched that way. kmH is the counted meters over the counted seconds, to 0.1. A road shows only
* under the trip cells rule; no road means too few trips, not a slow road. The line is dock's current road with
* that OSM id. No trip count. [band] gives one hour band's trips only, under the same rule on its own trips; null,
* all hours. rule["hours"] says which. [band] needs API 0.49.0.
* Needs insights:read. */
suspend fun traceRoads(month: String, bbox: BBox, band: RoadBand? = null): TraceRoads
/** Your organization's own trips per cell, the same grid: your dashboard (PLAN.md 18, 20.13). Every cell with one
* of your trips, no other organization read. The month you are in is answered too; `closed` is true once the
* counts are final. Needs trace:read, a read-only scope: trace:write does not read it. */
suspend fun ownTraceCells(month: String, bbox: BBox): OwnTraceCells
/** Your organization's arrival minutes in a month: how long your trips spend in the last 200 m, parking and the
* walk to the door included (PLAN.md 18). [month] is YYYY-MM (Ulaanbaatar). Month totals with median, p75 and
* p90, trips per minute, and up to 100 end cells on the trip cells grid, slowest first. No other organization is
* read. `closed` is true from 14 days after the month ends. Needs trace:read. */
suspend fun ownArrivals(month: String): OwnArrivals
/** When your organization's trips start in a month: each trip once, at the Ulaanbaatar weekday and hour of its
* first stored point (PLAN.md 18). [month] is YYYY-MM (Ulaanbaatar). Month total, 7 weekday counts, 24 hour
* counts, and every weekday and hour with trips. No other organization is read. `closed` is true from 14 days after
* the month ends. Needs trace:read. */
suspend fun ownHours(month: String): OwnHours
/** When trips start in a month, all partners together: road load by weekday and hour (PLAN.md 18, 19). [month] is
* YYYY-MM (Ulaanbaatar), published 14 days after it ends (400 month_open before). A weekday and hour shows only
* under the trip cells rule; no slot means too few, not zero. No total. Needs insights:read. */
suspend fun allHours(month: String): AllHours
/** Where and when trips end in a month, all partners together: trips per khoroo and Ulaanbaatar hour of arrival
* (PLAN.md 18, 19). [month] is YYYY-MM (Ulaanbaatar), published 14 days after it ends (400 month_open before). A
* khoroo and hour shows only under the trip cells rule; no hour means too few, not zero. No khoroo total. Current
* khoroo polygons only. Needs insights:read. */
suspend fun traceKhoroos(month: String): TraceKhoroos
/** Your organization's own trips per khoroo and Ulaanbaatar hour of arrival in a month: your dashboard (PLAN.md 18,
* 20.13). [month] is YYYY-MM (Ulaanbaatar), an open month too; closed once final. No group rule. Each trip counts
* once: in one khoroo, or in noKhoroo where no current khoroo polygon holds its end. Needs trace:read. */
suspend fun ownKhoroos(month: String): OwnKhoroos
/** Your organization's own trips per kind of building at the trip end in a month: what kind of deliveries you make
* (PLAN.md 18, 20.13). [month] is YYYY-MM (Ulaanbaatar), an open month too; closed once final. No group rule. Each
* trip counts once, by the building type when it was stored; unknown is a building with no type, none no building
* within 30 m. Needs trace:read. */
suspend fun ownKinds(month: String): OwnKinds
/** Point to the places at and around it, nearest first. Needs geocode:read. [types] (API 0.69.0, Mapbox's types):
* only these kinds, chosen before the limit, so limit doors are the nearest doors; sent once each, in the order
* given. null or empty: every kind.
* [point] is sent as lon and lat; the API reads lng as lon too, for a hand-written request. */
suspend fun reverse(
point: LonLat, radius: Double? = null, limit: Int? = null, types: List<ReverseType>? = null,
): ReverseResponse
/** Places of these categories near a point, nearest first, each with its category and distance (API 0.80.0). Needs
* geocode:read. A chip such as "Эмийн сан" is [NearbyCategory.PHARMACY], "Хоол" RESTAURANT and CAFE. [radius] in
* metres, 1000 when null, at most 10000; [limit] 10 when null, at most 20. Categories are sent once each, in the
* order given; none is refused here with the API's 400 invalid_category: no request. A store comes with code null:
* show "код алга". [open] true sends open=1: only places open now; those with no readable hours are counted in
* hoursUnknown.
* [point] is sent as lon and lat; the API reads lng as lon too, for a hand-written request. */
suspend fun nearby(
point: LonLat, categories: List<NearbyCategory>, radius: Double? = null, limit: Int? = null,
open: Boolean = false, photo: Boolean = false,
): NearbyResponse
/** The building entrances nearest a point, nearest first, each with its building and distance (API 0.85.0): for a
* long-press on the map or "Энд байгаа хаалга" from the user's position. Generated entrances count (source generated,
* confidence 0: show them as таамаг). [radius] in metres, 50 when null, at most 1000; [limit] 1 when null, at most
* 20. Needs geocode:read. [point] is sent as lon and lat; the API reads lng as lon too, for a hand-written request. */
suspend fun entrancesNear(point: LonLat, radius: Double? = null, limit: Int? = null): NearEntrances
/** The organizations registered in a building, by floor, for "Энд байгаа байгууллагууд" on an office building or
* mall card. The id is a building's, from geocode, reverse or the building tile, or its unit code (a хашаа code
* answers an empty list). Only organizations matched to an
* official registration are listed; a floor or unit the register does not give is null. Needs geocode:read.
* 410 retired, 404 not_found (unknown, not a building, or restricted). */
suspend fun directory(buildingId: String): BuildingDirectory
/** A door from its 5-character code. Cyrillic lookalike keys are accepted. [unit] is the flat or office typed
* after the code ("7QSVX 17"). Needs door:read. 404 not_found, 410 retired_code, 400 invalid_code.
* A text that cannot be a code ([parseDoorCode]) is refused here with the API's 400 invalid_code: no request. */
suspend fun door(code: String, unit: String? = null): Door
/** Register a spoken name for one of your doors ("Сансар бэйкери" for 7QSVX), held for a year (API 0.52.0). Your
* own name with another code moves to that door. Needs names:write and the door_names feature (403 feature_off
* until dock opens it for your organization). 409 name_taken, 410 retired_code, 404 not_found, 422 too_many. */
suspend fun registerDoorName(name: String, code: String): DoorNameReceipt
/** Your organization's door names, A to Z, expired ones flagged until another organization takes them. Needs
* names:write. */
suspend fun doorNames(): OwnDoorNames
/** The door a name names, as door() answers it, official address first. Any spelling of the name finds it.
* Needs door:read and the door_names feature. 404 not_found counts against the door miss cap. A name the API
* would refuse (no letter, a phone number or an e-mail) is refused here with its 400: no request. */
suspend fun doorByName(name: String): NamedDoor
/** A one-time code that lets another organization claim one of your names (API 0.53.0). Shown once; a new one
* replaces the old. Works for 7 days, or until the name expires if sooner. Needs names:write. */
suspend fun doorNameTransferCode(name: String): DoorNameTransfer
/** Clears the waiting transfer code of one of your names. `cancelled` is false when none was waiting. Needs
* names:write. */
suspend fun cancelDoorNameTransfer(name: String): DoorNameTransferCancel
/** Take a name with the transfer code its holder gave you. The door and expiresAt stay; the code is used up;
* status is transferred. Needs names:write. 409 transfer_refused (wrong, cleared or timed out; counts a door
* miss), 409 already_yours, 422 too_many. */
suspend fun claimDoorName(name: String, transferCode: String): DoorNameReceipt
/** Write how guests find the entrance of the door one of your names points at, in mn, en or both (API 0.58.0).
* The note waits for a person to review it; door() serves the notes the map holds until then. Writing again
* replaces your waiting text. Needs names:write and the hotel.notes feature (403 feature_off until dock opens it).
* 400 personal_data for a phone number or e-mail in a note, invalid_notes, invalid_body; 404 not_found for a name
* your organization does not hold. A name the API would refuse is refused here with its 400: no request. */
suspend fun writeDoorNotes(name: String, instructionsMn: String? = null, instructionsEn: String? = null): DoorNoteReceipt
/** Your notes for the door one of your names points at, each waiting, accepted or refused (API 0.59.0). Never
* another organization's. Needs names:write and the hotel.notes feature; 404 not_found as for writeDoorNotes. */
suspend fun doorNotes(name: String): DoorNotes
/** Send a gate photo for the door one of your names points at: the entrance as a guest sees it on arrival (API
* 0.60.0). [jpeg] is one JPEG file as the camera saved it, sent as is: up to 2 MB, 320 to 8000 px a side. The API
* removes its metadata (GPS, the phone, the time) before it stores it; the orientation stays. The photo waits for
* a person to review it, who refuses one showing a face, a car plate or a phone number. Sending again replaces
* your waiting photo. Needs names:write and the hotel.notes feature (403 feature_off until dock opens it). 400
* not_jpeg, invalid_photo, photo_too_small, photo_too_big; 413 too_large; 404 not_found for a name your
* organization does not hold or whose door was retired. A name the API would refuse is refused here with its 400:
* no request. */
suspend fun sendDoorPhoto(name: String, jpeg: ByteArray): DoorPhotoReceipt
/** The gate photo of yours a person accepted last, for the door one of your names points at (API 0.61.0). Never a
* waiting or refused one, never another organization's. Show it inside your app only, never on a public page of
* the place. Pass the [sha256] of the photo you hold: when it is still the one served, the answer has jpeg null
* and no bytes came. A sha256 that is not 64 hex characters is not sent: the photo comes whole. Needs names:write
* and the hotel.notes feature (403 feature_off). 404 not_found for a name your organization does not hold, a
* retired door, a restricted site, or no accepted photo. */
suspend fun doorPhoto(name: String, sha256: String? = null): DoorPhoto
/** One end of a route: a building code (routes to its street side or gate) or a point. */
sealed class End {
/** A building code: the route goes to the street side or the gate. */
data class Code(val code: String) : End()
/** A point. */
data class Point(val point: LonLat) : End()
}
/** Route between two ends. A code routes to the street side of its building or the gate of its yard; `to.gapM` is the walk left from the road.
* Needs route:read, and door:read for a code. 422 no_route, end_in_closure (an end inside a closure active
* today; closureAt checks offline), closures_over_limit, 404 not_found. departAt (API 0.50.0): a driving route's
* measured reads the speeds of its hour band; sent in UTC. via (API 0.67.0): up to ROUTE_VIA_MAX ends passed on the
* way, in order; the answer has one leg per hop and each via end where the line passes it. An empty list is none.
* A long list, or a code holding ";", is refused here with the API's 400 invalid_via: no request. 404 not_found
* names a via end by its place ("via[1]: ..."). */
suspend fun route(
from: End, to: End, mode: RouteMode? = null, departAt: Date? = null, via: List<End> = emptyList(),
): Route
/** Free-flow time and distance from every end in [from] to every end in [to], in one call (API 0.63.0): up to
* MATRIX_MAX by MATRIX_MAX, each end a building code or a point as in route. `durationsS[i][j]` goes from `from[i]` to
* `to[j]`; null where no path joins them. Needs route:read, and door:read for a code. An empty or long list, or a
* code holding ";", is refused here with the API's 400 invalid_from or invalid_to: no request. 404 not_found names
* the end by its place ("to[1]: ..."); 422 no_route, end_in_closure, closures_over_limit as for route. */
suspend fun matrix(from: List<End>, to: List<End>, mode: RouteMode? = null): Matrix
/** The area reached from one end within each of [contoursMinutes] (API 0.64.0), as a GeoJSON FeatureCollection:
* one Polygon or MultiPolygon per contour, the largest first. [from] is a building code (from its street side or gate) or a point
* as in route. 1 to ISOCHRONE_CONTOURS_MAX whole minutes from 1 to ISOCHRONE_MINUTES_MAX, increasing; anything
* else is refused here with the API's 400 invalid_contours: no request. Needs route:read, and door:read for a
* code. 404 not_found; 422 no_route (no road near from), end_in_closure, closures_over_limit, not_accepted as for
* route. */
suspend fun isochrone(from: End, contoursMinutes: List<Int>, mode: RouteMode? = null): Isochrone
/** A GPS trace matched to the road network (API 0.65.0): the roads it went along, one matching per unbroken piece,
* and one tracepoint per point, null where no road was found or the point is in a restricted site. [coordinates] is
* 2 to MATCHING_MAX points in the order recorded; [timestamps], optional, one whole Unix second per point, never
* going back. Anything else is refused here with the API's 400 invalid_coordinates or invalid_timestamps: no
* request. Needs route:read. 422 no_match (no road near the trace), not_accepted (fewer than 2 points outside
* restricted sites). Closures are not avoided. The API neither stores nor logs the trace; to share a trip, use
* startTrace. */
suspend fun matching(coordinates: List<LonLat>, timestamps: List<Long>? = null, mode: RouteMode? = null): Match
/** The order to visit stops in with the least time on the road, and the trip in that order (API 0.66.0). [stops]
* is 2 to OPTIMIZED_TRIPS_MAX ends, each a building code (to its street side or gate) or a point as in route; the trip starts at
* the first. [roundtrip] true, the API's default, goes back to it; false ends at the last sent. `waypoints` are the
* stops in the order sent, each with `waypointIndex`, its place in the trip. A short or long list, or a code holding
* ";", is refused here with the API's 400 invalid_coordinates: no request. Needs route:read, and door:read for a
* code. 404 not_found names the stop by its place ("coordinates[1]: ..."); 422 no_route (a stop no road reaches),
* not_accepted, end_in_closure, closures_over_limit as for route. Free-flow times, no time at the stops. */
suspend fun optimizedTrips(stops: List<End>, roundtrip: Boolean? = null, mode: RouteMode? = null): OptimizedTrip
/** The buildings and entrances at or near a point (API 0.68.0), as Mapbox's Tilequery API: what a tap on the map
* is on. [sources] is building, entrance or both (the default), each once. [radius] in metres, 0 to
* TILEQUERY_RADIUS_MAX_M, sent to the centimetre; null or 0, the API's default, finds the building the point is
* inside, and an entrance needs a radius. [limit] 1 to TILEQUERY_LIMIT_MAX, the API's default 5. [geometry] keeps
* one type. GeoJSON, nearest first: each feature the Point of it nearest the point sent, its tile fields and
* `tilequery` (distance in metres, geometry, layer). No building codes: door(code) takes a code. Anything the API would
* refuse is refused here with its error: 404 not_found (sources), 400 invalid_point, invalid_radius,
* invalid_limit; no request. Needs tiles:read; counts against rate_limit and daily_quota. Coverage: Ulaanbaatar. */
suspend fun tilequery(
point: LonLat, sources: List<TilequerySource> = listOf(TilequerySource.BUILDING, TilequerySource.ENTRANCE), radius: Double? = null,
limit: Int? = null, geometry: TilequeryGeometry? = null,
): Tilequery
/** Road closures not over today (PLAN.md 20.14), each with the ring Valhalla avoids. No partner name, no note.
* Keep the answer for offline use; excludePolygons(closures, day) picks the rings for a route day. idsSha256
* changes when a closure is added or lifted. Needs route:read. */
suspend fun closures(): ClosuresResponse
/** Report a stretch or a crossing that cannot be driven (PLAN.md 20.14). Kept forever: never edited or deleted,
* lift it when the road opens. Needs closures:write. Pass [idempotencyKey] (UUID.randomUUID().toString() fits), and
* the same key and report when you retry after a timeout: the API answers the first report and stores nothing
* twice; the same key with another report is 422 idempotency_key_reused. A key the API would refuse is refused
* here with its 400 bad_idempotency_key: no request. 400 note_has_phone, outside_mongolia, line_too_long,
* restricted and the rest come from the API. */
suspend fun reportClosure(report: ClosureReport, idempotencyKey: String? = null): Closure
/** One of your organization's closure reports as stored, note included: your export. Needs closures:write.
* 404 not_found for others' ids. */
suspend fun closure(id: String): Closure
/** The road is open again: lift one of your reports from [on] (YYYY-MM-DD Ulaanbaatar), null for today, never a
* later day (400 lift_in_future) nor before its from (400 lift_before_from). Lifting twice keeps the first day.
* Needs closures:write. */
suspend fun liftClosure(id: String, on: String? = null): Closure
/** Tell dock a door is wrong. A person reviews it; door data does not change until then. Needs door:read. */
suspend fun reportDoor(report: DoorReport): DoorReportReceipt
/** One place again, by the id geocode, reverse or changes gave: the place fields plus `parents`. Needs
* geocode:read. 410 retired (the id is never reused), 404 not_found. */
suspend fun place(id: String): Address
/** Ids of places changed in [since, until), oldest first, one page. Needs changes:read (partner keys). Pass the
* page's `next` as [cursor] until it is null, then poll again with since = until. First sync: since Date(0).
* limit 1 to 1000, default 100, on every call: the cursor does not keep it. */
suspend fun changes(since: Date?, limit: Int? = null, cursor: String? = null): ChangesResponse
/** A driver on site says a door is there, elsewhere or gone. Needs confirm:write (partner keys). Map data does
* not change at once. 422 implausible when device is over 40 m from the tap, too_far when the tap is over 500 m
* from the entrance: ask the driver to tap again on site. */
suspend fun confirm(confirmation: Confirmation): ConfirmReceipt
/** Your drivers' confirmations and door reports in a month: your dashboard (PLAN.md 18). [month] is YYYY-MM
* (Ulaanbaatar). Counts by result for the month and each day on site, different drivers (never the ids), and up to
* 100 failure points. The month you are in is answered too; `closed` is true from 8 days after it ends. No other
* organization is read. Needs confirm:read, a read-only scope: confirm:write does not read it. */
suspend fun ownConfirmations(month: String): OwnConfirmations
/** This key's organization: caps and the last [days] days (1 to 90). Free, answers after the quota. */
suspend fun usage(days: Int? = null): UsageResponse
/** TileJSON URL of a tile source (building, entrance), without the key. */
fun tileJsonUrl(source: String): String
/** Adds the key to a URL of this API only, as ?access_token=. Other hosts (a partner basemap, fonts) get nothing.
* Use it where MapLibre Native asks for a resource URL. */
fun authorizedUrl(url: String): String
/** The dock style ([dockStyle]) as a string for MapLibre Native's Style.Builder().fromJson, its tile sources
* keyed. Holds the key: do not log it. */
fun styleJson(options: StyleOptions = StyleOptions()): String
/** A style saved with POST /v1/styles or the style editor, keyed, for Style.Builder().fromUri. The API carries the
* key into the style's sources and tiles. Throws IllegalArgumentException unless [styleId] is "st_" and 10
* characters. */
fun styleUrl(styleId: String): String
}Basemap.kt
kotlin
/** An offline pack's manifest block `basemap` (null in packs built before 2026-10-03, or with no base map). */
data class PackBasemap(
val method: String,
val file: String,
val format: String,
val schema: String,
val schemaVersion: String,
val tileType: String,
val tiles: Long,
val minzoom: Int,
val maxzoom: Int,
val bounds: List<Double>,
val layers: List<String>,
val excludedLayers: List<String>,
val languages: List<String>,
val osmTime: String? = null,
/** Show on the map: the source carries it, so MapLibre's attribution shows it. */
val attribution: String,
val rule: String,
val ruleMn: String,
) {
companion object {
/** The block from the pack's manifest.json text; null when the pack has no base map. */
fun fromManifest(text: String): PackBasemap?
}
}
/**
* @param lang label language, one of the block's languages ("mn" or "en"). A name missing in it falls back to OSM's
* name.
* @param glyphs a glyphs URL ({fontstack}, {range}). The pack carries no fonts: without glyphs the style has no labels.
* @param fonts font stack on that glyphs URL. Default Noto Sans Regular.
* @param background background color. Default the dock style's paper tone.
*/
data class BasemapStyleOptions(
val lang: String = "mn",
val glyphs: String? = null,
val fonts: List<String>? = null,
val background: String? = null,
)
/** Layers the base map must not carry: business names (shown only when registered) and OSM house numbers. */
val BASEMAP_EXCLUDED: List<String>
/**
* A whole style for an offline pack's base map: land, water, tracks and roads, boundaries, buildings, and with
* glyphs also place, water and road names. [url] is where the app keeps basemap.pmtiles, a file:// URL; the source
* asks for it as pmtiles://, which MapLibre Native reads itself. MapLibre Native 13.6.1 draws nothing from an
* asset:// URL: copy the file out of the APK first. Throws IllegalArgumentException for
* no block, a block that is not the pack's base map or carries a poi or housenumber layer, a lang the block lacks,
* or an empty url.
*/
fun basemapStyle(basemap: PackBasemap?, url: String, options: BasemapStyleOptions = BasemapStyleOptions()): JsonObjectClosures.kt
kotlin
/** Valhalla's exclude_polygons limits per route request, all rings together. */
data class ValhallaLimits(val perimeterM: Double, val vertices: Int)
/** One closure in GET /v1/closures. No partner name, no note. */
data class ClosureItem(
/** cl-000001. Never reused. */
val id: String,
/** flood, washout, bridge_out, snow, mud, works or other. */
val kind: String,
/** First closed day, YYYY-MM-DD Ulaanbaatar. */
val from: String,
/** Last closed day, or null: until lifted. */
val to: String?,
/** One partner's word, not field checked. */
val confidence: Double,
val perimeterM: Double,
/** Ring vertices, the closing one included. */
val vertices: Int,
/** Closed ring. One of Valhalla's exclude_polygons. */
val ring: List<LonLat>,
) {
/** from <= day, and day <= to when to is set. Days compare as YYYY-MM-DD strings. */
fun isActive(day: String): Boolean
}
/** GET /v1/closures: every closure not over today, the same shape as an offline pack's closures block. */
data class ClosuresResponse(
val method: String,
val source: String,
/** Today when it was fetched, YYYY-MM-DD Ulaanbaatar. */
val asOf: String,
val current: Int,
/** sha256 of the sorted ids joined by commas; null for none. An offline pack's closures_sha256. */
val idsSha256: String?,
val bufferM: Double,
val rule: String,
val activeWhen: String,
/** Per route request, all rings together. */
val valhallaLimits: ValhallaLimits,
val items: List<ClosureItem>,
)
/** What to send Valhalla for one route day: the rings of the closures active that day. */
data class ExcludePolygons(
/** YYYY-MM-DD, Ulaanbaatar. */
val day: String,
/** The closures avoided, in list order. Show them with the route. */
val ids: List<String>,
/** Valhalla's exclude_polygons. */
val rings: List<List<LonLat>>,
val perimeterM: Double,
val vertices: Int,
) {
/**
* Offline: the id of the first closure here whose ring holds [point], or null. Valhalla drops every road inside an
* exclude ring, so a route from or to that point has no road to start from: it answers "No path could be found
* for input". Check both ends before asking your own Valhalla, as GET /v1/route does (422 end_in_closure).
*/
fun closureAt(point: LonLat): String?
}
/** The day's closures are over Valhalla's limits for one request. Refuse the route: never drop a closure. */
class ClosuresOverLimitException(val day: String, count: Int, val perimeterM: Double, val vertices: Int, limits: ValhallaLimits) :
RuntimeException(
"dock: $count closures on $day: ${Math.round(perimeterM)} m of ring and $vertices vertices, " +
"Valhalla takes ${limits.perimeterM.toLong()} m and ${limits.vertices}",
)
/** Today in Ulaanbaatar (UTC+8, no daylight saving), YYYY-MM-DD. */
fun ubToday(now: Date = Date()): String
/**
* Offline: the rings to send Valhalla as exclude_polygons for a route on [day] (default today, Ulaanbaatar), from a
* GET /v1/closures answer or an offline pack's closures block. Throws IllegalArgumentException for a bad day or a day
* before the list's asOf (closures that ended before it are not in it: fetch again), and ClosuresOverLimitException
* over Valhalla's limits.
*/
fun excludePolygons(closures: ClosuresResponse, day: String = ubToday()): ExcludePolygons
/** What is closed, in a report (POST /v1/closures). */
enum class ClosureKind(wire: String) {
FLOOD("flood"),
WASHOUT("washout"),
BRIDGE_OUT("bridge_out"),
SNOW("snow"),
MUD("mud"),
WORKS("works"),
OTHER("other")
}
/** A closure's place: a point on the stretch (not on a junction), or a line of 2 or more points, at most 3000 m. */
sealed class ClosureGeometry {
/** A point on the stretch, not on a junction. */
data class Point(val point: LonLat) : ClosureGeometry()
/** A line of 2 or more points, at most 3000 m. */
data class LineString(val points: List<LonLat>) : ClosureGeometry()
}
/** POST /v1/closures. The sender is the key's organization. */
data class ClosureReport(
val geometry: ClosureGeometry,
val kind: ClosureKind,
/** First closed day, YYYY-MM-DD Ulaanbaatar. null: today. */
val from: String? = null,
/** Last closed day. null: until lifted. */
val to: String? = null,
/** Your words for your own records, at most 200 characters; never served to others. No names, no phone numbers. */
val note: String? = null,
)
/** A closure's fields, in Closure.properties. */
data class ClosureProperties(
/** flood, washout, bridge_out, snow, mud, works or other. */
val kind: String,
val from: String,
val to: String?,
/** Open again from this day, YYYY-MM-DD; set once. null: not lifted. */
val lifted: String?,
/** As sent; "" for none. */
val note: String,
val confidence: Double,
/** UTC ISO 8601. */
val reportedAt: String,
)
/** One of your organization's closure reports, as stored (reportClosure, closure, liftClosure). */
data class Closure(
/** "Feature". */
val type: String,
/** cl-000001. Never reused. */
val id: String,
val geometry: ClosureGeometry,
val properties: ClosureProperties,
)
/** The API's Idempotency-Key rule (api/src/modules/closures/closure-input.ts IDEM_KEY). Use matches(), whole text. */
val IDEMPOTENCY_KEY_RE: RegexCode.kt
kotlin
/** Digits and consonants: no vowels, no I, L, O, U. */
const val DOOR_CODE_ALPHABET: String
/** Characters in a building code. */
const val DOOR_CODE_LENGTH: Int
/** Why a text cannot be a building code. */
enum class DoorCodeError {
/** Nothing typed. */
EMPTY,
/** Not 5 characters. */
LENGTH,
/** A character no code uses (a vowel, I, L, O, U, a Cyrillic letter with no Latin twin, a sign). */
LETTERS
}
/** What [parseDoorCode] read. */
sealed class DoorCodeInput {
/** The code, upper case Latin, and whatever followed it (flat number, household name) as typed, or null. */
data class Code(val code: String, val unit: String?) : DoorCodeInput()
/** [reason] is the API's invalid_code message, in English; show the person your own words for [error]. */
data class Invalid(val error: DoorCodeError, val reason: String) : DoorCodeInput()
}
/** Reads a typed or pasted building code as the API would: "7QSVX", "7qsvx 17", "7QSVХ Бат". No request. Use it to
* check a field as the person types, and send only [DoorCodeInput.Code] to door(): a code that finds no door counts
* against the daily miss cap; one that cannot be a code is caught here first. */
fun parseDoorCode(text: String): DoorCodeInput
/** A door link (PLAN.md 4a: "send the code, a link, or a QR"; PLAN 18 entry paths 1 and 4): this base, the code,
* and `?u=` with the unit when there is one. sdk/testdata/door-link.json holds the cases the three SDKs agree on. */
const val DOOR_LINK_BASE: String
/** The link to a door, for a share sheet, a message or a QR: "https://map.dock.mn/d/7QSVX?u=17". [text] and [unit]
* are read as [parseDoorCode] reads `text unit`, so the code may be typed in Cyrillic lookalikes and the unit (flat
* number, household name) goes in percent-encoded. null when the text cannot be a code. No request: the link says
* nothing about whether a door has this code. */
fun doorLink(text: String, unit: String? = null): String?
/** Reads the first door link in a text: a pasted link, a share message with a link in it, or the URL an app link
* opened the app with. http or https or none, the host in any case, `?u=` for the unit, other parameters and `#`
* ignored. null when the text has no door link; otherwise what [parseDoorCode] reads from the code and the unit,
* so a link with a broken code is [DoorCodeInput.Invalid] with the same reasons. No request. */
fun parseDoorLink(text: String): DoorCodeInput?Crossings.kt
kotlin
/** Modelled monthly flow at a river crossing (lane D's dock_steppe.rivers). Rivers only: streams have none. */
data class CrossingFlow(
val riverId: Long,
/** 1991 to 2020 monthly normals, January to December, m3/s. */
val normalM3s: List<Double>,
/** Months 1 to 12 whose normal is at least 1.5 times the mean of the 12. */
val highMonths: List<Int>,
/** agrees, doubtful (the model reach is 10x below its OSM river's), unchecked. */
val reach: String,
/** 0.4, or LOW_FLOW_CONFIDENCE (0.1) on a doubtful reach and on snap none_fits or main_stem. */
val confidence: Double,
/**
* How the reach was picked. matched: it follows the OSM river. none_fits: none does, the nearest is kept.
* main_stem: the channel follows only a small reach, the river's main stem is taken. nearest: the nearest, no
* lines to match on. null in packs built before the match.
*/
val snap: String? = null,
)
/**
* A delta channel: the river splits into a lake there and no model reach says what share a channel carries. The
* crossing has no flow. Rule text in the pack's flow.delta.rule and rule_mn.
*/
data class CrossingDelta(
/** The lake's OSM name, folded to one script when it mixed Latin and Cyrillic in a word. */
val lake: String? = null,
/** The lake's OSM element, as relation/114846. */
val lakeOsm: String,
/** The river whose mouths form the delta. */
val river: String,
/**
* The lake's OSM name as written, when lake is folded. null when lake is the OSM text, and in packs before national
* crossings v11. Show lake.
*/
val lakeNameOsm: String? = null,
)
/**
* An unnamed river or stream whose two ends lie on one named OSM river: a channel of it. From the OSM drawing, not
* checked on the ground. Rule text in the pack's channel_rule and channel_rule_mn.
*/
data class CrossingChannelOf(
/** The river's OSM name, as on its riverWayId way. */
val name: String,
val nameEn: String? = null,
/** The OSM way the name was read from. */
val riverWayId: Long,
)
/**
* An unnamed river or stream, not a channel, and the named OSM river its water reaches downstream by the direction the
* ways are drawn. From the OSM drawing, not checked on the ground. Rule text in the pack's tributary_rule and
* tributary_rule_mn.
*/
data class CrossingFlowsInto(
/** The river's OSM name, as on its riverWayId way. */
val name: String,
val nameEn: String? = null,
/** The OSM way the name was read from. */
val riverWayId: Long,
/** The fewest unnamed ways from this crossing's way to the river, its own included. */
val ways: Int,
)
/** The OSM tags of the route ways at a crossing. Rule text in the pack's road_rule and road_rule_mn. */
data class CrossingRoad(
/** The ways' OSM highway tags, as track or trunk. */
val highway: List<String>,
/** The ways' OSM surface tags; empty when none has one. */
val surface: List<String>,
/**
* true: a way has a paved surface. false: every way has an unpaved one. null: a way has no surface tag. A none
* crossing on a paved road may be a bridge or culvert OSM does not tag; not checked.
*/
val paved: Boolean? = null,
)
/** At or under this flow confidence the model reach is not sure to be this river's: show the months, not the m3/s. */
const val LOW_FLOW_CONFIDENCE: Double
/** A GeoJSON Point: type "Point" and [lon, lat]. The geometry of a crossing, a pass or a place in a pack. */
data class CrossingGeometry(val type: String, val coordinates: LonLat)
/** A crossing's fields, in CrossingFeature.properties. */
data class CrossingProperties(
/** ford: OSM says ford. none: OSM has no bridge or ford there, not that there is none. */
val kind: String,
/** river, stream, canal, or unknown on a ford node with no waterway line (fordNodeId set). */
val waterway: String,
val name: String? = null,
val nameEn: String? = null,
val intermittent: Boolean,
/** null for streams and canals, and in packs built before the flow block. */
val flow: CrossingFlow? = null,
/** Set on a delta channel, with flow null. null in packs built before the delta rule. */
val delta: CrossingDelta? = null,
/** Set on an unnamed channel of a named river. null in packs built before the channel rule. */
val channelOf: CrossingChannelOf? = null,
/** Set on an unnamed river or stream that reaches one named river. null in packs built before the tributary rule. */
val flowsInto: CrossingFlowsInto? = null,
/** null on a new track alone (no OSM way), and in packs built before the road rule. */
val road: CrossingRoad? = null,
/**
* The OSM ford node, on a ford whose water OSM does not draw as a line; waterway is unknown. null on line crossings
* and in packs built before the ford node rule.
*/
val fordNodeId: Long? = null,
/**
* The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. null when
* name is the OSM text. Show name. Packs before offline v13 lack it.
*/
val nameOsm: String? = null,
)
/** One crossing point in an offline pack's crossings.geojson (files[1]). */
data class CrossingFeature(val type: String, val geometry: CrossingGeometry, val properties: CrossingProperties)
/** The pack's flow block: the attribution to show with a high water month. */
data class CrossingsFlowBlock(val attribution: String)
/** An offline pack's crossings.geojson. Check its sha256 against the manifest's files[1] before use. */
data class PackCrossings(
val type: String,
/** A route passes a crossing when its line comes this close, m. */
val matchM: Double,
val note: String,
val noteMn: String,
/** null in packs built before the flow block. */
val flow: CrossingsFlowBlock? = null,
val features: List<CrossingFeature>,
/** Crossings with road.paved true. null in packs built before the road rule, as the two rule texts. */
val onPaved: Int? = null,
val roadRule: String? = null,
val roadRuleMn: String? = null,
/**
* None crossings that became the crossing of a bridge drawn just off the water, so left out of the pack. null in
* packs built before the bridge rule, as the two rule texts.
*/
val nearBridge: Int? = null,
val bridgeRule: String? = null,
val bridgeRuleMn: String? = null,
/**
* Crossings joined into the one beside them because their water way is split into two ways at the road. null in
* packs built before the split rule, as the two rule texts.
*/
val onePoint: Int? = null,
val splitRule: String? = null,
val splitRuleMn: String? = null,
/**
* Crossings marked intermittent from OSM seasonal=* alone, no intermittent tag. null in packs built before that
* rule, as the two rule texts.
*/
val fromSeasonal: Int? = null,
val intermittentRule: String? = null,
val intermittentRuleMn: String? = null,
/**
* Fords from an OSM ford node alone, no waterway line drawn. null in packs built before that rule, as the two rule
* texts.
*/
val fordNodes: Int? = null,
val fordNodeRule: String? = null,
val fordNodeRuleMn: String? = null,
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. null in packs before offline
* v13. */
val namesFolded: Int? = null,
val nameRule: String? = null,
val nameRuleMn: String? = null,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackCrossings
}
}
/** A ford or unbridged crossing the route passes. */
data class CrossingWarning(
/** ford or none. */
val kind: String,
val point: LonLat,
val waterway: String,
val name: String?,
val nameEn: String?,
val intermittent: Boolean,
/** Along the route to its point nearest the crossing, m, rounded. A tie goes to the earlier pass. */
val alongM: Double,
/** From the route line, m, one decimal. */
val offM: Double,
/** The month is one of highMonths. null when the crossing has no flow (streams, canals). */
val highWater: Boolean?,
val highMonths: List<Int>,
/**
* The month's normal, m3/s. null with no flow, on a doubtful reach, and at LOW_FLOW_CONFIDENCE or under (snap
* none_fits, main_stem): show the months, not the number.
*/
val monthM3s: Double?,
val reach: String?,
val confidence: Double?,
/** The flow's snap. null with no flow or in a pack built before the match. */
val snap: String? = null,
/** A delta channel: say "delta channel, flow unknown". null elsewhere and in packs built before the delta rule. */
val delta: CrossingDelta? = null,
/**
* An unnamed channel of a named river: say "unnamed channel of <name>". null elsewhere and in packs built before
* the channel rule.
*/
val channelOf: CrossingChannelOf? = null,
/**
* An unnamed river or stream that reaches a named river: say "unnamed, flows into <name>". channelOf wins when both
* are set. null elsewhere and in packs built before the tributary rule.
*/
val flowsInto: CrossingFlowsInto? = null,
/**
* The road the route crosses on. With kind none and paved true, say "paved road, OSM tags no bridge here". null on
* a new track alone and in packs built before the road rule.
*/
val road: CrossingRoad? = null,
/**
* The OSM ford node of a ford whose water OSM does not draw as a line, waterway unknown: say "OSM ford, water not
* drawn". null on line crossings and in packs built before the ford node rule.
*/
val fordNodeId: Long? = null,
/** The OSM text when name was folded to one script, else null. Show name. */
val nameOsm: String? = null,
)
/** What crossingsOnRoute returns: the warnings in route order, with the pack's note. */
data class RouteCrossings(
/** 1 to 12. */
val month: Int,
val matchM: Double,
/** In route order. */
val warnings: List<CrossingWarning>,
/** Show with the warnings: from OSM tags, not field checked. */
val note: String,
val noteMn: String,
/** Show when a warning has flow; else null. */
val flowAttribution: String?,
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. null when the pack has no rule. */
val nameRule: String? = null,
val nameRuleMn: String? = null,
)
/** This month in Ulaanbaatar (UTC+8), 1 to 12. crossingsOnRoute's default month. */
fun ubMonth(now: Date = Date()): Int
/**
* Offline: the ford and unbridged crossings a route line passes, from an offline pack's crossings.geojson, in route
* order. [month] (1 to 12, default this month in Ulaanbaatar) sets highWater. Throws IllegalArgumentException for a
* line under 2 points or a bad month.
*/
fun crossingsOnRoute(crossings: PackCrossings, line: List<LonLat>, month: Int = ubMonth()): RouteCrossingsDoorNames.kt
kotlin
/** A door name your organization holds, as POST /v1/door-names and the claim answer it. */
data class DoorNameReceipt(
/** As registered, spaces collapsed. */
val name: String,
/** The door it names. */
val code: String,
val registeredAt: String,
/** After this the name finds nothing and any organization may take it. */
val expiresAt: String,
/** registered: yours from now. moved: your name now names this door. unchanged: it already did. A claim answers
* transferred. */
val status: String,
)
/** One of your organization's names in doorNames(). */
data class OwnDoorName(
val name: String,
val code: String,
val registeredAt: String,
val expiresAt: String,
/** true after expiresAt: it finds nothing, and stays listed until another organization takes it. */
val expired: Boolean,
/** When the transfer code you made stops working (API 0.53.0); null when none is waiting. */
val transferUntil: String? = null,
)
/** GET /v1/door-names: your organization's names, A to Z. */
data class OwnDoorNames(
/** Unexpired names one organization may hold at once. */
val limit: Int,
val names: List<OwnDoorName>,
)
/** GET /v1/door-names/{name}: the door as door() answers it, official address first. The holder is never shown. */
data class NamedDoor(
val name: String,
val expiresAt: String,
val door: Door,
)
/** POST /v1/door-names/{name}/transfer-code. The code is shown once, never again. */
data class DoorNameTransfer(
val name: String,
/** dnt_ and 24 characters. Anyone holding it can claim the name: give it only to the organization taking it. */
val transferCode: String,
/** Refused after this: 7 days, or the name's expiresAt if sooner. */
val transferUntil: String,
)
/** DELETE /v1/door-names/{name}/transfer-code. cancelled: a waiting code was cleared; false: none was waiting. */
data class DoorNameTransferCancel(
val name: String,
val cancelled: Boolean,
)
/** POST /v1/door-names/{name}/notes (API 0.58.0, feature hotel.notes): your waiting note. */
data class DoorNoteReceipt(
/** The waiting note; writing again keeps it. */
val id: String,
/** queued: a new note waits for review. updated: your waiting note has this text now. unchanged: it had. */
val status: String,
val name: String,
/** The door the name points at, which the note is for. */
val code: String,
val instructionsMn: String? = null,
val instructionsEn: String? = null,
/** When the waiting text was last written. */
val receivedAt: String,
/** review: a person reviews it. Until then door() serves the notes the map holds, not this one. */
val next: String,
)
/** One of your notes in doorNotes(). */
data class DoorNoteState(
val id: String,
/** waiting: not reviewed, guests do not see it. accepted: door() serves the languages it gave. refused: not used. */
val state: String,
val instructionsMn: String? = null,
val instructionsEn: String? = null,
/** When this text was last written. */
val receivedAt: String,
/** When a person accepted or refused it; null while it waits. */
val reviewedAt: String? = null,
)
/** GET /v1/door-names/{name}/notes (API 0.59.0): your organization's notes for the door, never another's. */
data class DoorNotes(
val name: String,
/** The door the name points at now. */
val code: String,
/** The waiting one first, then the newest review, at most 20. Empty: you wrote none. */
val notes: List<DoorNoteState>,
)
/** POST /v1/door-names/{name}/photo (API 0.60.0, feature hotel.notes): your waiting gate photo. */
data class DoorPhotoReceipt(
/** The waiting photo; sending again keeps it. */
val id: String,
/** queued: a new photo waits for review. updated: your waiting photo is now this one. unchanged: it was. */
val status: String,
val name: String,
/** The door the name points at, which the photo is for. */
val code: String,
/** SHA-256 of the photo as stored, after its metadata was removed: not the hash of the file you sent. doorPhoto()
* answers it as the sha256 of the photo once a person accepts it. */
val sha256: String,
/** Size as stored, in bytes. */
val bytes: Int,
/** Pixels as stored, before the EXIF orientation turns it. */
val width: Int,
val height: Int,
/** When the waiting photo was last sent. */
val receivedAt: String,
/** review: a person reviews the photo. Once accepted, doorPhoto() serves it. */
val next: String,
)
/** GET /v1/door-names/{name}/photo (API 0.61.0): your accepted gate photo. [jpeg] null means unchanged: the photo you
* hold with this [sha256] is still the one served, and no bytes came. */
class DoorPhoto(
/** SHA-256 of the JPEG served, 64 lower case hex: the ETag, and the sha256 the send answered. */
val sha256: String,
/** The JPEG as stored, metadata removed, orientation kept; null when unchanged. */
val jpeg: ByteArray?,
) {
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
/** No bytes: the size only. */
override fun toString(): String
}Format.kt
kotlin
/** Language of a label. */
enum class Lang {
MN,
EN
}
/**
* The spoken form, then the name, then the long form, then the code, each in [lang] first. Never the id.
* Anything without a matched official address ends in "(албан бус)" / "(unofficial)": VISION rule 1, PLAN.md 4d.
*/
fun label(door: Door, lang: Lang = Lang.MN): String
/** The same for the door at one end of a route: the spoken form, then the code. */
fun label(door: RouteDoor, lang: Lang = Lang.MN): String
/** The same for a geocode result: the name, then the long form, then the code. */
fun label(place: GeocodeResult, lang: Lang = Lang.MN): String
/** The same for a reverse result: the name, then the long form, then the code. */
fun label(place: ReverseResult, lang: Lang = Lang.MN): String
/** The same for an address field answer: the name, then the long form, then the code. */
fun label(place: Address, lang: Lang = Lang.MN): StringPack.kt
kotlin
/** One row of an offline pack manifest's `files`: a file in the pack folder, its size and sha256. */
data class PackFile(
val name: String,
val bytes: Long,
/** Lower case hex. */
val sha256: String,
)
/** The part of an offline pack's manifest.json [checkPack] reads. */
data class PackManifestFiles(val files: List<PackFile>) {
companion object {
/** The whole manifest.json's text; only `files` is read. */
fun fromJson(text: String): PackManifestFiles
}
}
/** OK: size and sha256 match. MISSING: no such file. BYTES: another size, not hashed. SHA256: same size, other bytes. */
enum class PackFileStatus {
OK,
MISSING,
BYTES,
SHA256
}
/** One file of a pack manifest as checkPack found it. */
data class PackFileCheck(
val name: String,
val status: PackFileStatus,
/** The file's size, null when missing. */
val bytes: Long?,
val expectedBytes: Long,
/** The file's sha256, null when missing or another size. */
val sha256: String?,
val expectedSha256: String,
)
/** What checkPack returns. */
data class PackCheck(
/** Every listed file is there with its size and sha256. Use no file from the pack otherwise. */
val ok: Boolean,
/** In manifest order. */
val files: List<PackFileCheck>,
/** Names of the files not ok, in manifest order. Download them again. */
val bad: List<String>,
)
/**
* Offline: checks each file the manifest lists in [directory], in its order, by size and then sha256, reading 1 MB at
* a time. A folder where a file is listed is missing. Files the manifest does not list are not read. Blocks while it
* hashes (the base map is over 100 MB): call it off the main thread, as on Dispatchers.IO. Throws
* IllegalArgumentException for a manifest with no files, a name that is not a plain file name, a name listed twice,
* a negative bytes value, or a sha256 that is not 64 lower case hex; a file that cannot be read throws its own error.
*/
fun checkPack(manifest: PackManifestFiles, directory: File): PackCheckPackRoute.kt
kotlin
/** What every route on a pack sends: costing auto, costing_options.auto.use_tracks 0.5 today. Keys as written. */
data class PackRouteRequest(
val costing: String,
/** Valhalla's costing_options, as in the manifest. null when it has none. */
val costingOptions: JsonObject? = null,
)
/** The part of an offline pack's manifest.json [packRoute] reads. */
data class PackRouteManifest(
val id: String,
/** [west, south, east, north]. */
val bbox: List<Double>,
val request: PackRouteRequest,
/** scored, or osm_only: no trace scores yet. */
val confidence: String,
val attribution: String,
val note: String,
val closures: ClosuresResponse,
/** What [confidence] means, for the driver, en and mn. Packs from offline v10; null before. */
val confidenceNote: String? = null,
val confidenceNoteMn: String? = null,
) {
companion object {
/** The whole manifest.json's text. costingOptions keeps its keys as written (use_tracks). */
fun fromJson(text: String): PackRouteManifest
}
}
/** What packRoute returns: the Valhalla request and what to show with the route. */
data class PackRoute(
/**
* POST it to /route on your own Valhalla, the pack's tiles.tar as mjolnir.tile_extract: locations, costing,
* costing_options, exclude_polygons, units km.
*/
val body: JsonObject,
/** The manifest's id. */
val pack: String,
val day: String,
/** The closures the body avoids, in list order. Show them with the route. */
val closures: List<String>,
/** The pack's route costs come from trace scores. */
val scored: Boolean,
val confidence: String,
/** Show it with every route. */
val attribution: String,
val note: String,
/** Say it to the driver when the pack has no trace scores: [confidenceNote], en and mn; null when scored. */
val traceNote: String?,
val traceNoteMn: String?,
/**
* What [confidence] means, for the driver: the manifest's confidenceNote and confidenceNoteMn when it has both;
* else [PACK_TRACE_NOTE] and [PACK_TRACE_NOTE_MN] when the pack is not scored; else null.
*/
val confidenceNote: String?,
val confidenceNoteMn: String?,
)
/** A stop inside a closure ring: Valhalla has no road there. Refuse the route, as GET /v1/route does (422 end_in_closure). */
class EndInClosureException(val stop: Int, val closure: String) :
RuntimeException("dock: stop $stop is inside closure $closure: no road there")
/** What to say when a pack has no trace scores (confidence osm_only), in English. */
const val PACK_TRACE_NOTE: String
/** The same in Mongolian. */
const val PACK_TRACE_NOTE_MN: String
/**
* Offline: the Valhalla request for a route through [stops] (2 or more) on the pack: the manifest's costing and
* costingOptions, the route day's closure rings as exclude_polygons (from [closures], a newer GET /v1/closures answer,
* else the pack's block), units km. Throws IllegalArgumentException for a manifest with no costing or bbox, under 2
* stops, a stop that is not a finite lon, lat or is outside the pack's bbox, and for the day as [excludePolygons];
* ClosuresOverLimitException; EndInClosureException for a stop inside an avoided ring.
*/
fun packRoute(
manifest: PackRouteManifest,
stops: List<LonLat>,
day: String = ubToday(),
closures: ClosuresResponse? = null,
): PackRoutePasses.kt
kotlin
/** A pass's fields, in PassFeature.properties. */
data class PassProperties(
/** mountain_pass when OSM has that tag, else saddle. */
val kind: String,
val name: String? = null,
/**
* The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. null when
* name is the OSM text. Show name. Packs before offline v12 lack it.
*/
val nameOsm: String? = null,
val nameEn: String? = null,
/** The OSM ele tag read as metres. null when OSM has none, or only other text, as "7000 ft": see eleOsm. */
val eleM: Double? = null,
/** The OSM ele tag as written. */
val eleOsm: String? = null,
/** The OSM node id. */
val nodeId: Long,
/** The OSM car ways through the node. Empty on a new track alone. */
val routeWayIds: List<Long>,
val newTrack: Boolean,
/** The ways' OSM highway and surface tags, as on crossings. null on a new track alone. */
val road: CrossingRoad? = null,
/**
* The Copernicus GLO-30 DEM height of the cell the node falls in, whole metres: a second source beside eleM, never
* mixed into it. null when the pack had no DEM tile there; packs built before 2026-10-03 lack it.
*/
val demM: Double? = null,
/**
* True when eleM and demM are both there and more than dem.differM (50 m) apart, false within, null when either is
* missing. True says one of the two is wrong, not which. Packs built before passes v3 lack it.
*/
val heightsDiffer: Boolean? = null,
)
/** The pack's DEM block: where demM comes from. Show attribution wherever a DEM height shows. */
data class PassDem(
val source: String,
val attribution: String,
val rule: String,
val ruleMn: String,
val note: String,
val noteMn: String,
/** DEM tiles read. */
val tiles: Int,
/** Tiles the build had no copy of: passes there have demM null. */
val missing: List<String>,
/** Heights further apart than this, m, set heightsDiffer. Packs before passes v3 lack it and the three below. */
val differM: Double? = null,
val differRule: String? = null,
val differRuleMn: String? = null,
/** Passes with heightsDiffer true, all Mongolia. */
val differ: Int? = null,
)
/** One pass in an offline pack's passes.geojson: an OSM mountain_pass=yes or natural=saddle node of a car way. */
data class PassFeature(val type: String, val geometry: CrossingGeometry, val properties: PassProperties)
/**
* An offline pack's passes.geojson (manifest files row and passes block; index.json passes_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none.
*/
data class PackPasses(
val type: String,
/** Passes in the file, all Mongolia. */
val written: Int,
/** Per kind, all Mongolia. */
val counts: Map<String, Int>,
val named: Int,
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. null in packs before offline
* v12. */
val namesFolded: Int? = null,
val nameRule: String? = null,
val nameRuleMn: String? = null,
/** Passes with a height in metres. */
val withEle: Int,
/** A route goes over a pass when its line comes this close, m. */
val matchM: Double,
val rule: String,
val ruleMn: String,
val source: String,
val note: String,
val noteMn: String,
val features: List<PassFeature>,
/** Passes with a DEM height. null in packs built before 2026-10-03. */
val withDem: Int? = null,
/** null in packs built before 2026-10-03. */
val dem: PassDem? = null,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackPasses
}
}
/** A pass the route goes over. */
data class PassOnRoute(
/** mountain_pass or saddle. */
val kind: String,
val point: LonLat,
val name: String?,
val nameEn: String?,
val eleM: Double?,
val eleOsm: String?,
val nodeId: Long,
val newTrack: Boolean,
val road: CrossingRoad?,
/** Along the route to its point nearest the pass, m, rounded. A tie goes to the earlier pass. */
val alongM: Double,
/** From the route line, m, rounded to 0.1. */
val offM: Double,
/** The DEM height, whole metres, beside eleM. null when the pack has none for this pass. Label it DEM. */
val demM: Double? = null,
/**
* The pack's flag: true when eleM and demM disagree by more than dem.differM. null when either is missing or the
* pack has no flag. Show both heights and the dem differRule when true.
*/
val heightsDiffer: Boolean? = null,
/** The OSM text when name was folded to one script, else null. Show name. */
val nameOsm: String? = null,
)
/** What passesOnRoute returns. */
data class RoutePasses(
val matchM: Double,
/** In route order. */
val passes: List<PassOnRoute>,
/** The highest eleM of these passes, m. null when none has a height: not "flat", OSM has no number. */
val topM: Double?,
/** Show with the passes: from OSM tags, not field checked. */
val note: String,
val noteMn: String,
val rule: String,
val ruleMn: String,
/** The highest demM of these passes, m. null when none has one. Not mixed with topM. */
val topDemM: Double? = null,
/** The pack's DEM block, null when the pack has none. Show its attribution with a DEM height. */
val dem: PassDem? = null,
/** How many of these passes have heightsDiffer true. null when the pack has no flag (no dem.differRule): not
* checked, not 0. */
val differ: Int? = null,
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. null when the pack has no rule. */
val nameRule: String? = null,
val nameRuleMn: String? = null,
)
/**
* Offline: the OSM mountain passes a route line goes over, from an offline pack's passes.geojson, in route order. A
* route goes over a pass when its line comes within the pack's matchM (5 m) of the pass node, as lane D's route
* pages. Throws IllegalArgumentException for a line under 2 points.
*/
fun passesOnRoute(passes: PackPasses, line: List<LonLat>): RoutePassesPlaces.kt
kotlin
/** Default number of places kept. The least picked, then the longest unpicked, go first. */
const val PLACES_MAX: Int
/** Key in the partner's store. A new format gets a new key. Same key and format as the JS SDK. */
const val PLACES_KEY: String
/** Where kept places live across app launches: SharedPreferences (see the Android page), a test map. */
interface PlaceStore {
/** The value kept under [key], null when none. */
fun getString(key: String): String?
/** Keeps [value] under [key], replacing the old one. A thrown exception is ignored: memory still answers. */
fun putString(key: String, value: String)
/** Forgets [key]. */
fun remove(key: String)
}
/** @param max 1 to 1000. @param store keeps the places across app launches; null: memory only, gone with the client. */
data class PlaceCacheOptions(val max: Int = PLACES_MAX, val store: PlaceStore? = null)
/** A place the user picked: the fields of a geocode result without score, layer and near, which belong to one
* answer, not to the place. */
data class Place(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
val code: String?,
val kind: String,
/** Stable id. Never show it to people as an address. */
val id: String,
val nameMn: String?,
val nameEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val confidence: Int?,
val source: String,
val sourceDate: String? = null,
val floor: Int? = null,
val flat: String? = null,
val claimed: Boolean? = null,
val buildingType: String? = null,
) {
constructor(r: GeocodeResult)
}Pois.kt
kotlin
/** A place's fields, in PoiFeature.properties. */
data class PoiProperties(
/** fuel or ger_camp. */
val kind: String,
/** The OSM element, N or W and the id, as N123. */
val osm: String,
/** Clean name from the state registry match. */
val name: String,
val nameEn: String? = null,
/** State registration number. */
val regNumber: String,
)
/** One registered place in an offline pack's pois.geojson. Unregistered places are never in the file. */
data class PoiFeature(val type: String, val geometry: CrossingGeometry, val properties: PoiProperties)
/** Places of one kind, all Mongolia: matched to an official registration, and not. */
data class PoiKindCount(val registered: Int, val unregistered: Int)
/** Per kind, all Mongolia. */
data class PoiCounts(val fuel: PoiKindCount? = null, val gerCamp: PoiKindCount? = null)
/** An offline pack's pois.geojson (files[2]; index.json pois_sha256). Check its sha256 against the manifest before use. */
data class PackPois(
val type: String,
/** false: the registry could not be read when the pack was built, so no place is listed. */
val registryChecked: Boolean,
val counts: PoiCounts,
val written: Int,
val rule: String,
val ruleMn: String,
val source: String,
val note: String,
val noteMn: String,
val features: List<PoiFeature>,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackPois
}
}
/** A registered fuel station or ger camp near the route. */
data class PoiOnRoute(
/** fuel or ger_camp. */
val kind: String,
val point: LonLat,
val osm: String,
val name: String,
val nameEn: String?,
val regNumber: String,
/** Along the route to its point nearest the place, m, rounded. A tie goes to the earlier pass. */
val alongM: Double,
/** From the route line, m, rounded. */
val offM: Double,
)
/** What poisOnRoute returns. */
data class RoutePois(
val nearM: Double,
/** Registered places only, in route order. */
val pois: List<PoiOnRoute>,
/** false: no place was listed because the registry was not read; say so, do not say "none". */
val registryChecked: Boolean,
/**
* Unregistered fuel stations in the pack, all Mongolia, not near this route: the pack has no place for them. An
* empty pois is not "no fuel on the route" while this is above 0.
*/
val unlistedFuel: Int,
/** Unregistered ger camps in the pack, all Mongolia, as unlistedFuel. */
val unlistedGerCamp: Int,
/** Show with the places: from OSM, not field checked; unregistered places are counted, not shown. */
val note: String,
val noteMn: String,
)
/** lane D's route pages: fuel and ger camps within 2 km of the line. */
const val POI_NEAR_M: Double
/**
* Offline: the registered fuel stations and ger camps within [nearM] (default 2000) of a route line, from an offline
* pack's pois.geojson, in route order. Throws IllegalArgumentException for a line under 2 points or a nearM not
* above 0.
*/
fun poisOnRoute(pois: PackPois, line: List<LonLat>, nearM: Double = POI_NEAR_M): RoutePoisQr.kt
kotlin
/**
* A QR as rows of modules: `modules[y][x]` is true for a dark module. [size] modules a side (21 to 177), no quiet
* zone: leave 4 light modules around it when drawing. [link] is the text it holds.
*/
data class DoorQr(val link: String, val version: Int, val size: Int, val modules: List<List<Boolean>>)
/**
* A door QR as ARGB pixels, black on white, [side] a side, row by row: for
* `Bitmap.createBitmap(argb, side, side, Bitmap.Config.ARGB_8888)`.
*/
class DoorQrPixels(val link: String, val side: Int, val argb: IntArray)
/**
* The QR of a door: the modules of doorLink(text, unit). null when the text cannot be a code. A link longer than
* level M holds (2,331 bytes, a unit of about 2,300 characters) is also null.
*/
fun doorQr(text: String, unit: String? = null): DoorQr?
/**
* The QR of a door as an SVG document, black on white, for a WebView, a share sheet or print. null as doorQr. Each
* module is [px] wide; [margin] light modules around it (4, the standard's minimum quiet zone). The SVG is
* (size + 2 * margin) * px a side. Throws IllegalArgumentException for px not above 0 or a margin under 0.
*/
fun doorQrSvg(text: String, unit: String? = null, px: Double = 4.0, margin: Int = 4): String?
/**
* The QR of a door as pixels, [px] a module and [margin] light modules around it, for an Android Bitmap. null as
* doorQr. Throws IllegalArgumentException for px under 1 or a margin under 0.
*/
fun doorQrPixels(text: String, unit: String? = null, px: Int = 8, margin: Int = 4): DoorQrPixels?Sand.kt
kotlin
/** A GeoJSON LineString: the stretch of the way inside the sand area. */
data class SandGeometry(val type: String, val coordinates: List<LonLat>)
/** A stretch's fields, in SandFeature.properties. */
data class SandProperties(
/** The area's OSM element, as way/123 or relation/123. */
val area: String,
/** The area's OSM name. */
val name: String? = null,
/**
* The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. null when
* name is the OSM text. Show name. Packs before offline v11 lack it.
*/
val nameOsm: String? = null,
/** The stretch's length, km. */
val km: Double,
/** The car way's OSM id. null on a new track alone. */
val wayId: Long? = null,
val newTrack: Boolean,
/** The way's OSM highway and surface tags, as on crossings. null on a new track alone. */
val road: CrossingRoad? = null,
)
/** One stretch in an offline pack's sand.geojson: the part of one car way inside an OSM natural=sand area. */
data class SandFeature(val type: String, val geometry: SandGeometry, val properties: SandProperties)
/**
* An offline pack's sand.geojson (manifest files row and sand block; index.json sand_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none.
*/
data class PackSand(
val type: String,
/** Stretches in the file, all Mongolia. */
val written: Int,
val km: Double,
/** OSM sand areas the stretches lie in. */
val areas: Int,
/** Stretches in a named area. */
val named: Int,
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. null in packs before offline
* v11. */
val namesFolded: Int? = null,
val nameRule: String? = null,
val nameRuleMn: String? = null,
/** Stretches on a paved road (road.paved true), as town streets drawn inside sand. */
val onPaved: Int,
/** A route runs on a stretch where its line comes this close, m. */
val matchM: Double,
val rule: String,
val ruleMn: String,
val source: String,
val note: String,
val noteMn: String,
val features: List<SandFeature>,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackSand
}
}
/** A stretch through OSM sand the route runs on. */
data class SandOnRoute(
val area: String,
val name: String?,
/** The whole stretch, km, from the pack. The route may run on part of it: onM. */
val km: Double,
val wayId: Long?,
val newTrack: Boolean,
val road: CrossingRoad?,
/** Along the route to where it meets the stretch, m, rounded. */
val alongM: Double,
/** Metres of the stretch the route runs on, rounded. */
val onM: Double,
/** The stretch's first point the route meets, in route order. */
val point: LonLat,
/** The OSM text when name was folded to one script, else null. Show name. */
val nameOsm: String? = null,
)
/** What sandOnRoute returns. */
data class RouteSand(
val matchM: Double,
val minOnM: Double,
/** In route order. */
val stretches: List<SandOnRoute>,
/** Sum of onM, m. */
val onM: Double,
/** Show with the stretches: from OSM tags, not field checked. */
val note: String,
val noteMn: String,
val rule: String,
val ruleMn: String,
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. null when the pack has no rule. */
val nameRule: String? = null,
val nameRuleMn: String? = null,
)
/** A route that runs on less of a stretch than this, m, only crosses it, unless it runs on all of the stretch. */
const val SAND_MIN_ON_M: Double
/**
* Offline: the stretches through OSM sand a route line runs on, from an offline pack's sand.geojson, in route order.
* The same rule as [wetlandsOnRoute]: the stretch is cut in pieces of at most matchM; a piece is on the route when both
* its ends lie within matchM of the line. A stretch is on the route when its pieces on the route make at least
* [SAND_MIN_ON_M], or when all of it is on the route. Throws IllegalArgumentException for a line under 2 points.
*/
fun sandOnRoute(sand: PackSand, line: List<LonLat>): RouteSandStyle.kt
kotlin
/** Ulaanbaatar, Sükhbaatar Square. */
val UB_CENTER: LonLat
/** A saved style id: "st_" and 10 characters of the key alphabet. */
val STYLE_ID_RE: Regex
/**
* @param background background color. Default a light paper tone.
* @param building building fill (or extrusion) color. Default a warm grey.
* @param buildingOutline building outline color.
* @param extrude extrude buildings by height_m (or levels x 3 m). Default false: flat 2D.
*/
data class StyleOptions(
val background: String? = null,
val building: String? = null,
val buildingOutline: String? = null,
val extrude: Boolean = false,
)
/** Sources named dock-building and dock-entrance, TileJSON from the API, without the key. */
fun dockSources(baseUrl: String): JsonObject
/**
* Building fill and outline, entrance dots by confidence. Add on top of any basemap style. Entrance colors are fixed:
* confidence reads the same on every map (PLAN.md 5).
*/
fun dockLayers(options: StyleOptions = StyleOptions()): JsonArray
/** A whole style: background, dock buildings, dock entrances. Source URLs without the key: see Client.styleJson. */
fun dockStyle(baseUrl: String, options: StyleOptions = StyleOptions()): JsonObjectSuggest.kt
kotlin
/** PLAN 18: "suggestions from the third character". */
const val SUGGEST_MIN_CHARS: Int
/** Wait after the last key before asking, ms. Under the 250 ms people notice, over most gaps between keys. */
const val SUGGEST_DELAY_MS: Long
/** A door read from a typed code, or a geocode result. */
sealed class Suggestion {
/** The door a typed code reads as. */
data class Door(val door: mn.dock.Door) : Suggestion()
/** A geocode result. */
data class Result(val result: GeocodeResult) : Suggestion()
}
/** What to show a person for a suggestion. Anything without a matched official address ends in "(албан бус)" /
* "(unofficial)". */
fun label(item: Suggestion, lang: Lang = Lang.MN): String
/**
* Text in, suggestions out. [input] on every change of the field; [show] gets the list for the text as it stands,
* never an older one. Under [minChars] the list is emptied at once and nothing is sent. A text that reads as a building
* code ([parseDoorCode]) is looked up as a door first; a code with no door falls through to the words. A pasted door
* link ([parseDoorLink]) is looked up as a door only. An empty list
* for a text of [minChars] or more means no match. Call [input], [cancel] and [choose] from one thread, the main one.
*
* @param scope where requests run and [show] and [fail] are called: the screen's scope, on Dispatchers.Main.
* @param limit geocode results per list, 1 to 20. Default 5. A door from a typed code comes on top of them.
* @param proximity asked on each request: the person or the map centre. Nearer places first.
* @param types only these kinds, as geocode types: a street picker passes listOf(STREET). null or
* empty: every kind. A typed code or a pasted door link is looked up as a door only when types has DOOR.
* @param show the list and the text it is for.
* @param fail a DockError or another failure of the request.
*/
class Suggester(
client: Client,
scope: CoroutineScope,
limit: Int = SUGGEST_LIMIT,
proximity: (() -> LonLat?)? = null,
types: List<GeocodeType>? = null,
minChars: Int = SUGGEST_MIN_CHARS,
delayMs: Long = SUGGEST_DELAY_MS,
show: (List<Suggestion>, String) -> Unit,
fail: (Throwable) -> Unit,
) {
/** Reads [text]: from minChars characters, asks delayMs after the last call; under them, empties the list. */
fun input(text: String)
/** Stops the wait and the request in flight. Their answers are dropped. */
fun cancel()
/** The person chose this suggestion. Stops what is in flight; a geocode result goes to [Client.pick] (offline
* places, opted-in telemetry), a door from a code does not. Show label(item) in the field. */
fun choose(item: Suggestion)
}Telemetry.kt
kotlin
/** API limits (api/src/modules/telemetry/telemetry-input.ts). */
const val TELEMETRY_BATCH: Int
/** Characters of typed text kept per search, counted as the API counts them (code points). The rest is cut. */
const val TELEMETRY_MAX_CHARS: Int
/** Results kept per search. */
const val TELEMETRY_MAX_RESULTS: Int
/** What the user picked: kind and id as geocode gave them, rank 1 = the first result shown. */
data class SearchPick(val kind: String, val id: String, val rank: Int)
/** One search as the API takes it. Text, count and pick only: no time, point, device or session. */
data class SearchEvent(val q: String, val results: Int, val picked: SearchPick? = null)Trace.kt
kotlin
/** API limits (api/src/modules/traces/trace-input.ts). */
const val TRACE_FORMAT: String
/** Points per upload. stopTrace() splits a longer trip. */
const val TRACE_MAX_POINTS: Int
/** Fixes worse than this, in meters, are left out. Accuracy itself is never sent. */
const val TRACE_MAX_ACCURACY_M: Double
/** The user's yes. The only value: there is no way to record or send without it. */
enum class Consent(wire: String) {
OPT_IN("opt_in")
}
/**
* One position from the device. Only lon, lat and time are ever sent. From an android.location.Location `l`:
* `PositionFix(l.longitude, l.latitude, if (l.hasAccuracy()) l.accuracy.toDouble() else null, l.time)`.
*
* @param accuracy meters, null when not known (kept).
* @param timeMs milliseconds since 1970 UTC, as Location.getTime().
*/
data class PositionFix(val lon: Double, val lat: Double, val accuracy: Double? = null, val timeMs: Long)
/** Where fixes come from: a LocationListener or FusedLocationProviderClient callback in the app, or a test. The SDK
* never asks for location permission itself; the app does, and its own permission text says why. */
interface PositionSource {
/** Call [onFix] for each new position until [stop]. */
fun start(onFix: (PositionFix) -> Unit)
/** Stop calling onFix. The client calls it when the trip stops or is cancelled. */
fun stop()
}
/** A PositionSource the app pushes fixes into, from its location callback:
* `for (l in result.locations) feed.push(PositionFix(...))`. A fix pushed while no trip is recorded is dropped. */
class PositionFeed : PositionSource {
override fun start(onFix: (PositionFix) -> Unit)
override fun stop()
/** Hands [fix] to the trip being recorded. Dropped when none is. */
fun push(fix: PositionFix)
}
/** [lon, lat, t]: degrees WGS84 and whole seconds UTC. Nothing else per point. */
data class TracePoint(val lon: Double, val lat: Double, val t: Long)Types.kt
kotlin
/** A WGS84 point. On the wire it is [lon, lat], longitude first. */
data class LonLat(val lon: Double, val lat: Double)
/** National format, as written by the source. */
data class OfficialAddress(
val country: String,
val cityOrAimag: String,
val districtOrSoum: String,
val khorooOrBag: String?,
val streetOrMicrodistrict: String?,
val buildingOrKhashaa: String?,
val entrance: String?,
val floor: String?,
val unit: String?,
/** Unified postal code from the state register, "13381-0017". */
val postalCode: String? = null,
/** The building's own name from the state register, when it has one. */
val buildingName: String? = null,
)
/** The entrance a building code points to. */
data class Entrance(
/** door, gate, vehicle_gate or stairwell. */
val kind: String,
/** Entrance or gate number as people say it. */
val letter: String?,
val floorsServed: List<Int>?,
)
/** The flat or office typed after a code. Unit in the JS and Swift SDKs; DoorUnit here, so kotlin.Unit stays itself. */
data class DoorUnit(
val number: String,
val floor: Int?,
/** flat, office, shop, stall or kiosk. */
val kind: String,
)
/** A door. Official address first, code after it. No owner, no phone. */
data class Door(
val officialAddress: OfficialAddress?,
/** matched, partial or none. */
val officialStatus: String,
/** true unless officialStatus is matched. Show "unofficial" when true. */
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
val code: String,
val naturalMn: String?,
val naturalEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val entrance: Entrance?,
val unit: DoorUnit?,
/** PLAN 5 level, 0 to 4. null means unknown. */
val confidence: Int?,
val source: String,
val instructionsMn: String?,
val instructionsEn: String?,
val aliases: List<String>,
/** The date of the door or unit row behind the code, YYYY-MM-DD. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** Confirmed driver taps for this door since its point last moved. 0 when none. null from an API before this field. */
val confirmations: Int? = null,
)
/** One geocode answer. kind: door, business, building, district, khoroo, area, street or landmark. */
data class GeocodeResult(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
/** Building code. null when the place has none. */
val code: String?,
val kind: String,
/** Stable id. Never show it to people as an address. */
val id: String,
val nameMn: String?,
val nameEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val confidence: Int?,
val source: String,
/** Higher is better. Compare only with results of the same layer in one response. */
val score: Double,
/** 1: the words of a name or address matched. 2: the place description matched the meaning; after every layer 1, except near rows. 3: near rows reordered by a reranking model (API 0.30.0). */
val layer: Int,
/** The landmark a landmark-relative query named ("Хүннү моллын ард"), for a place found around it. null otherwise. */
val near: GeocodeNear? = null,
/** A bus stop: the city bus lines that call at it ("Ч:41"). null for every other place. */
val lines: List<String>? = null,
/** A business or landmark: its map category, the one nearby gives ("pharmacy"). null for any other place and when unknown. */
val category: String? = null,
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** A company from a building's directory (API 0.90.0): its floor from the legal address in the state register. null when not said, and for every other place. */
val floor: Int? = null,
/** A company from a building's directory (API 0.90.0): its flat or office number (N тоот). null when not said. */
val flat: String? = null,
/** A business: true when its company has a claim the state register confirmed, false when not or pending (API 0.95). null for any other kind. */
val claimed: Boolean? = null,
/** A building: apartment, ger_house, commercial, public, industrial, garage or other. null when the building has no type yet, and for any other kind. */
val buildingType: String? = null,
/** A business, landmark or street: the khoroo number its point stands in. null when no polygon covers it, and for any other kind. */
val khoroo: Int? = null,
/** A business: true when its point lies inside a restricted site, so a client hides the claim link and any edit. null for any other kind. */
val restricted: Boolean? = null,
/** A building: true when its whole name is only a kind word (Үсчин, Дэлгүүр), so a client shows it as a kind. null when not checked, and for any other kind. */
val nameIsKindWord: Boolean? = null,
/** A building: the OSM category of the one POI in its footprint ("shop=hairdresser"). null when unknown, and for any other kind. */
val poiCategory: String? = null,
/** A building: up to 5 company names registered at it. Empty when none; null for any other kind. */
val poiNames: List<String>? = null,
/** The official address as one Cyrillic line, postal code last. null when none is matched. */
val officialLine: String? = null,
/** A building with an indoor map: venue_id, levels (floor numbers, lowest first) and the units the query names. null for every other row. */
val indoor: JsonElement? = null,
)
/** The landmark, building or door a near-me query was read against. */
data class GeocodeNear(
/** landmark, building or door. */
val kind: String,
val id: String,
val nameMn: String?,
val nameEn: String?,
/** north, east, south or west (within 55 degrees), near or beside (the whole circle). */
val relation: String,
/** From the landmark to the place, 150 at most. */
val metres: Double,
)
/** GET /v1/geocode, or places picked on this device when offline is true. */
data class GeocodeResponse(
val results: List<GeocodeResult>,
/** true only when the answer came from this device's picked places, not the API. Not on the wire. */
val offline: Boolean = false,
/** Set when the whole query is a unit code, alone ("7QSVX") or spoken ("7QSVX, 12 давхар, 1204 тоот"). null
* otherwise and offline. */
val unit: GeocodeUnit? = null,
/** Units inside a building with an indoor map that the query names (up to 5), each with venue_id, level, name_mn and point. null when none is named. */
val indoorUnits: List<JsonElement>? = null,
)
/** A unit code found in the query: the unit card as GET /v1/door/{code} answers it, plus the floor and flat said
* after the code. */
data class GeocodeUnit(
val officialAddress: OfficialAddress?,
val officialStatus: String,
/** true unless officialStatus is matched. Show "unofficial" when true. */
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
val code: String,
/** building or khashaa. */
val kind: String,
/** The official address as one Cyrillic line, postal code last. null when nothing is known. */
val officialLine: String?,
val nameMn: String?,
val nameEn: String?,
/** A point inside the footprint. */
val point: LonLat,
/** N in "N давхар"; null when not said. */
val floor: Int?,
/** N in "N тоот"; null when not said. */
val flat: String?,
/** Where the unit row came from. null when unknown. */
val source: String? = null,
/** How sure dock is of the building row, 0 to 4; lower for a state-only building. null: unknown. */
val confidence: Double? = null,
/** The date of the door or unit row behind the code, YYYY-MM-DD. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** The building holds an indoor map: its venue id (venue()). null when it holds none. */
val venueId: String? = null,
/** District and khoroo the building lies in, only when its address is not matched. Never the official address. null when matched or not known. */
val areaLine: String? = null,
)
/** One reverse result. */
data class ReverseResult(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
val code: String?,
val kind: String,
val id: String,
val nameMn: String?,
val nameEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val confidence: Int?,
val source: String,
/** Metres from the query point. 0 means the point is inside. */
val distanceM: Double,
/** A bus stop: the city bus lines that call at it ("Ч:41"). null for every other place. */
val lines: List<String>? = null,
/** A business or landmark: its map category, the one nearby gives ("pharmacy"). null for any other place and when unknown. */
val category: String? = null,
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** A business: true when its company has a claim the state register confirmed, false when not or pending (API 0.95). null for any other kind. */
val claimed: Boolean? = null,
/** A building: apartment, ger_house, commercial, public, industrial, garage or other. null when the building has no type yet, and for any other kind. */
val buildingType: String? = null,
/** A business, landmark or street: the khoroo number its point stands in. null when no polygon covers it, and for any other kind. */
val khoroo: Int? = null,
/** A business: true when its point lies inside a restricted site, so a client hides the claim link and any edit. null for any other kind. */
val restricted: Boolean? = null,
/** A building: true when its whole name is only a kind word (Үсчин, Дэлгүүр), so a client shows it as a kind. null when not checked, and for any other kind. */
val nameIsKindWord: Boolean? = null,
/** A building: the OSM category of the one POI in its footprint ("shop=hairdresser"). null when unknown, and for any other kind. */
val poiCategory: String? = null,
/** A building: up to 5 company names registered at it. Empty when none; null for any other kind. */
val poiNames: List<String>? = null,
/** The official address as one Cyrillic line, postal code last. null when none is matched. */
val officialLine: String? = null,
/** A building with an indoor map: venue_id, levels (floor numbers, lowest first) and the units the query names. null for every other row. */
val indoor: JsonElement? = null,
)
/** A kind geocode can keep (types), as the API's PLACE_KINDS. */
enum class GeocodeType(wire: String) {
DOOR("door"),
BUSINESS("business"),
BUILDING("building"),
DISTRICT("district"),
KHOROO("khoroo"),
AREA("area"),
STREET("street"),
LANDMARK("landmark")
}
/** A kind reverse can keep (types), as the API's REVERSE_KINDS: district and khoroo always come with the answer. */
enum class ReverseType(wire: String) {
DOOR("door"),
BUSINESS("business"),
BUILDING("building"),
LANDMARK("landmark")
}
/** GET /v1/reverse. */
data class ReverseResponse(
val point: LonLat,
val districtMn: String?,
val districtEn: String?,
/** null when no khoroo polygon covers the point. Never guessed. */
val khoroo: Int?,
/** The soum and aimag covering the point (OSM boundaries, API 0.104.0). null when no polygon covers it. */
val soumMn: String? = null,
val aimagMn: String? = null,
val results: List<ReverseResult>,
/** true when the point itself lies inside a restricted site: results is then empty, and a client hides the claim link and any edit. */
val restricted: Boolean = false,
)
/** A category nearby takes: the map's place icons, as the API's NEARBY_CATEGORIES. atm, bank, cafe, car_wash, fuel, hotel, pharmacy,
* restaurant and shop are registered businesses only, so they answer nothing until a business matches the registry:
* a business through its door, or a store of a registered chain (code null). */
enum class NearbyCategory(wire: String) {
ATM("atm"),
BANK("bank"),
BRIDGE("bridge"),
CAFE("cafe"),
CAR_WASH("car_wash"),
CULTURE("culture"),
EMBASSY("embassy"),
FUEL("fuel"),
GOVERNMENT("government"),
HOSPITAL("hospital"),
HOTEL("hotel"),
KINDERGARTEN("kindergarten"),
MALL("mall"),
MARKET("market"),
MONUMENT("monument"),
OFFICE("office"),
PARK("park"),
PHARMACY("pharmacy"),
RELIGION("religion"),
RESTAURANT("restaurant"),
SCHOOL("school"),
SHOP("shop"),
SPORT("sport"),
STATION("station"),
STOP("stop"),
UNIVERSITY("university")
}
/** One nearby result. */
data class NearbyResult(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
/** Unit code of the place: a door, a business through its door, or the building or хашаа a store or company
* stands in. null when none is minted yet: show "код алга" and the official address, or none. */
val code: String?,
val kind: String,
val id: String,
val nameMn: String?,
val nameEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val confidence: Int?,
val source: String,
/** The map category, as the place icon. String, so a new category from the API never fails a decode. */
val category: String,
/** The OpenStreetMap opening_hours tag as written; may be old. null when the source has none. */
val openingHours: String? = null,
/** Where openingHours came from. null when openingHours is null. */
val hoursSource: String? = null,
/** Open now in Ulaanbaatar time, from a plain opening_hours tag. null when unknown: no tag, or not a plain one. */
val openNow: Boolean? = null,
/** When the place closes today, HH:MM in Ulaanbaatar time, while openNow is true. null when closed, unknown, or open 24/7. */
val openUntil: String? = null,
/** Only when nearby was asked for photo: one openly licensed photo of a landmark or building. null when none or not asked. */
val photo: PlacePhoto? = null,
/** Metres from the query point. */
val distanceM: Double,
/** A bus stop: the city bus lines that call at it ("Ч:41"). null when the capital's public transport data lists none. */
val lines: List<String>? = null,
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** A registered company from a building's directory: its floor from the state register. null when not said. Absent before API 0.92.1. */
val floor: Int? = null,
/** A company's flat or office number (N тоот). null when not said. Absent before API 0.92.1. */
val flat: String? = null,
/** A business: true when its company has a claim the state register confirmed, false when not or pending (API 0.95). null for any other kind. */
val claimed: Boolean? = null,
/** A business, landmark or street: the khoroo number its point stands in. null when no polygon covers it, and for any other kind. */
val khoroo: Int? = null,
/** A business: true when its point lies inside a restricted site, so a client hides the claim link and any edit. null for any other kind. */
val restricted: Boolean? = null,
/** The official address as one Cyrillic line, postal code last. null when none is matched. */
val officialLine: String? = null,
/** A building with an indoor map: venue_id, levels (floor numbers, lowest first) and the units the query names. null for every other row. */
val indoor: JsonElement? = null,
)
/** GET /v1/nearby. */
data class NearbyResponse(
val point: LonLat,
/** Nearest first, within radius. Empty when none. */
val results: List<NearbyResult>,
/** How many more places of these categories lie within radius past limit. 0 when results holds them all. */
val more: Int,
/** Only with open=1: how many of the nearest 20 places have no readable hours (openNow null). They are not in results. */
val hoursUnknown: Int? = null,
)
/** Second-source check of a building footprint. confidence is null when the run gave none: unknown. */
data class FootprintCheck(
/** confirmed, new or doubtful. */
val verdict: String,
val source: String,
val confidence: Double? = null,
val checkedAt: String,
)
/** One openly licensed photo of a place. Show credit and license beside it. */
data class PlacePhoto(
val url: String,
val credit: String,
/** CC BY, CC BY-SA, CC0 or public domain. */
val license: String,
val source: String,
)
/** Where to point the map from above when a place has no photo. */
data class MapFromAbove(
val lon: Double,
val lat: Double,
val zoom: Int,
)
/** Ids of what a door stands in. Ids only, never shown to people as an address. null when not known. */
data class Parents(
/** The door of a business. null for a door. */
val door: String?,
val building: String?,
/** Khashaa (plot). */
val plot: String?,
val area: String?,
val street: String?,
/** null when no khoroo polygon covers the door. Never guessed. */
val khoroo: String?,
val district: String?,
)
/** GET /v1/address/{id}: one place by the id geocode, reverse or changes gave. */
data class Address(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
val code: String?,
val kind: String,
val id: String,
val nameMn: String?,
val nameEn: String?,
val longMn: String?,
val longEn: String?,
val point: LonLat,
val confidence: Int?,
val source: String,
/** Doors and businesses; a khoroo carries its district only; null for every other kind. */
val parents: Parents?,
/** A bus stop: the city bus lines that call at it ("Ч:41"). null for every other place. */
val lines: List<String>? = null,
/** A business or landmark: its map category, the one nearby gives ("pharmacy"). null for any other place and when unknown. */
val category: String? = null,
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
/** A company from a building's directory (API 0.90.0): its floor from the legal address in the state register. null when not said, and for every other place. */
val floor: Int? = null,
/** A company from a building's directory (API 0.90.0): its flat or office number (N тоот). null when not said. */
val flat: String? = null,
/** A district, khoroo or area (next API patch): its boundary as a GeoJSON Polygon or MultiPolygon, about 5 m simplified. null for every other kind and where no polygon is held. */
val outline: JsonElement? = null,
/** A business: true when its company has a claim the state register confirmed, false when not or pending (API 0.95). null for any other kind. */
val claimed: Boolean? = null,
/** A district, khoroo or area: current buildings and doors inside its boundary, nothing inside a restricted site. null for every other kind and where the register has no polygon: unknown. 0 is a real zero. */
val counts: JsonElement? = null,
/** A business, landmark or street: the khoroo number its point stands in. null when no polygon covers it, and for any other kind. */
val khoroo: Int? = null,
/** A business: true when its point lies inside a restricted site, so a client hides the claim link and any edit. null for any other kind. */
val restricted: Boolean? = null,
/** A building: whether its footprint is backed by a second source. null for other kinds and for a building not yet checked. */
val footprintCheck: FootprintCheck? = null,
/** A landmark, business or street: the district its point stands in, in Mongolian. null when no polygon covers it, and for any other kind. */
val districtMn: String? = null,
/** A landmark or building: one openly licensed photo within 30 m. null when there is none. */
val photo: PlacePhoto? = null,
/** Set only when photo is null: where to point the map from above (zoom 19 for a building, door or business, 17 otherwise). null when there is a photo or the site is restricted. */
val mapFromAbove: MapFromAbove? = null,
/** The official address as one Cyrillic line, postal code last. null when none is matched. */
val officialLine: String? = null,
/** A building with an indoor map: venue_id, levels (floor numbers, lowest first) and the units the query names. null for every other row. */
val indoor: JsonElement? = null,
)
/** One place changed, in changes(). */
data class Change(
/** The id place() takes. */
val id: String,
val kind: String,
/** current: fetch it again with place(). retired: drop it, the id is never reused. hidden: drop it, it may come back. */
val state: String,
/** Last change, UTC, microseconds. */
val changedAt: String,
)
/** GET /v1/changes. Window [since, until). */
data class ChangesResponse(
val since: String,
/** The last index refresh. When next is null, poll again with since = until. */
val until: String,
/** Oldest first. One row per place, its last change. */
val changes: List<Change>,
/** More in this window: pass as cursor. null: window done. */
val next: String?,
)
/** How the route is travelled. */
enum class RouteMode(wire: String) {
DRIVING("driving"),
WALKING("walking"),
CYCLING("cycling")
}
/** The door at one end of a route. Show "(unofficial)" when unofficial is true. */
data class RouteDoor(
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
val code: String,
val naturalMn: String?,
val naturalEn: String?,
val entrance: Entrance?,
val confidence: Int?,
)
/**
* The entrance at one end of a route, when that end was sent as an entrance id. Show "(unofficial)" when unofficial
* is true; confidence 0 with source generated was placed by rule (таамаг).
*/
data class RouteEntrance(
val id: String,
val building: String,
/** null: no door minted for this entrance yet. */
val code: String?,
val officialAddress: OfficialAddress?,
val officialStatus: String,
val unofficial: Boolean,
/** door, gate, vehicle_gate or stairwell. */
val kind: String,
val letter: String?,
val confidence: Int?,
val source: String,
)
/** One end of a route. */
data class RouteEnd(
/** What was asked for: the entrance when a code was given. */
val point: LonLat,
/** Where the line starts or ends, on the road network. */
val snapped: LonLat,
/** Straight metres from snapped to point. Walked, never drawn as road. */
val gapM: Double,
val door: RouteDoor?,
/** The entrance, when this end was sent as an entrance id. Absent from older APIs. */
val entrance: RouteEntrance? = null,
/** The address unit this end was sent as, by its code; null for any other end. */
val unit: RouteUnit? = null,
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
val sourceDate: String? = null,
)
/** The address unit (building or khashaa) a route end was given as, by its code. The route ends at its street side. */
data class RouteUnit(
val code: String,
/** building or khashaa. */
val kind: String,
val officialAddress: OfficialAddress?,
val officialStatus: String,
/** true unless officialStatus is matched. Show "unofficial" when true. */
val unofficial: Boolean,
/** true: the end is the gate, or the outline of the building or khashaa at the street, within about 25 m of where the line meets the street. false: the outline is farther than that from the street, the walk is longer. */
val streetSide: Boolean,
)
/** A GeoJSON LineString: type "LineString" and [lon, lat] points. */
data class LineString(val type: String, val coordinates: List<LonLat>)
/** The road closures a route avoided (GET /v1/closures, active on asOf). */
data class RouteClosures(
/** The route day, YYYY-MM-DD, Ulaanbaatar. */
val asOf: String,
/** Closure ids, in id order. Each was kept out of the route, whether or not it came near. Show them with it. */
val avoided: List<String>,
)
/** Driving time with all partners' measured moving speed per road (API 0.48.0). */
data class RouteMeasured(
/** YYYY-MM: the latest month GET /v1/traces/roads publishes. */
val month: String,
/** The hour band of departAt whose speeds were read (API 0.50.0), a RoadBand wire name: "am_peak", "pm_peak",
* "off_peak". null without departAt (all hours) or from an API before 0.50.0. */
val band: String? = null,
/** Meters of the line on roads with a published speed that month. 0: none, and durationS is the free-flow one. */
val roadsM: Double,
/** Seconds with those roads at their measured moving speed, the rest at the router's. A month average of the band. */
val durationS: Double,
)
/** GET /v1/route. durationS is free-flow; measured has partners' speeds. */
data class Route(
val mode: String,
val distanceM: Double,
/** Seconds at the router's free-flow speeds. */
val durationS: Double,
val geometry: LineString,
val from: RouteEnd,
val to: RouteEnd,
val closures: RouteClosures,
val source: String,
/** Driving only; null when walking or cycling, when speeds could not be read, or from an API before 0.48.0. */
val measured: RouteMeasured? = null,
/** One per hop, in order: from to via[0], ..., the last to to; one without via. null from an API before 0.67.0. */
val legs: List<TripLeg>? = null,
/** The via ends in the order sent, snapped where the line passes them; empty without. null before API 0.67.0. */
val via: List<RouteEnd>? = null,
/** Driving: seconds with a typical load by road class, hour and weekday; null when walking, cycling or unreadable (API 0.97.19). */
val expectedDurationS: Int? = null,
/** Where that load comes from: model (a table), probes (partners' phones, not yet). */
val trafficSource: String? = null,
/** The label to show beside expectedDurationS. */
val trafficNote: String? = null,
)
/** GET /v1/matrix (API 0.63.0): free-flow travel time and distance from every from to every to in one call. Cell
* [i][j] goes from from[i] to to[j]; null where no path joins them. No measured speeds, no line. */
data class Matrix(
val mode: String,
/** Seconds at the router's free-flow speeds, as durationS in a route. */
val durationsS: List<List<Double?>>,
/** Meters from from[i].snapped to to[j].snapped; null where durationsS is null. */
val distancesM: List<List<Double?>>,
/** The sources in the order asked, each as a route end. */
val from: List<RouteEnd>,
/** The targets in the order asked, each as a route end. */
val to: List<RouteEnd>,
val closures: RouteClosures,
val source: String,
)
/** GET /v1/isochrone (API 0.64.0): GeoJSON, one feature per contour, the largest first, so drawn in order each
* smaller one lies on top. Free-flow speeds, closures avoided. mode, from, closures and source sit beside the features. */
data class Isochrone(
/** "FeatureCollection". */
val type: String,
val features: List<IsochroneFeature>,
val mode: String,
/** Where the areas are reached from, as a route end. */
val from: RouteEnd,
val closures: RouteClosures,
val source: String,
)
/** One isochrone contour: the area reached within `properties.contour` minutes. */
data class IsochroneFeature(
/** "Feature". */
val type: String,
val properties: IsochroneProperties,
val geometry: IsochroneGeometry,
)
/** An isochrone contour's fields, in IsochroneFeature.properties. */
data class IsochroneProperties(
/** Minutes: the area reached from from within this time. */
val contour: Int,
/** Always "time": minutes, as Mapbox's contours_minutes. */
val metric: String,
)
/** An isochrone's area: a Polygon, or a MultiPolygon where the area is in pieces. Every ring closed, [lon, lat]. */
sealed class IsochroneGeometry {
/** Every piece of the area, each as its rings (outer first, then holes), one for a Polygon: fill them all the same. */
abstract val polygons: List<List<List<LonLat>>>
/** The area in one piece: its rings. */
data class Polygon(val rings: List<List<LonLat>>) : IsochroneGeometry() {
override val polygons: List<List<List<LonLat>>>
}
/** The area in pieces. */
data class MultiPolygon(override val polygons: List<List<List<LonLat>>>) : IsochroneGeometry()
}
/** GET /v1/optimized-trips (API 0.66.0): the stops in the order with the least time on the road. trips holds one
* trip, as Mapbox's answer does; waypoints are the stops in the order sent. Today's closures avoided. */
data class OptimizedTrip(
val mode: String,
/** true: the trip goes back to the first stop. false: it ends at the last stop sent. */
val roundtrip: Boolean,
val trips: List<Trip>,
val waypoints: List<TripWaypoint>,
val closures: RouteClosures,
val source: String,
)
/** The whole trip in the order driven, snapped stop to snapped stop. */
data class Trip(
val distanceM: Double,
/** Seconds on the road at the router's free-flow speeds. No live traffic, no measured speeds, no time at the stops. */
val durationS: Double,
val geometry: LineString,
/** legs[k] goes from the stop with waypointIndex k to the one with k + 1; on a round trip the last goes back to
* the first. */
val legs: List<TripLeg>,
)
/** One hop of an optimized trip, from one stop to the next in the order driven, or of a route with via (API 0.67.0). */
data class TripLeg(
/** Meters from one stop to the next along the road network. */
val distanceM: Double,
/** Seconds from one stop to the next at the router's free-flow speeds. */
val durationS: Double,
)
/** One stop as sent, a route end with its place in the trip. */
data class TripWaypoint(
/** What was asked for: the entrance when a code was given. */
val point: LonLat,
/** Where the line starts or ends, on the road network. */
val snapped: LonLat,
/** Straight metres from snapped to point. Walked, never drawn as road. */
val gapM: Double,
val door: RouteDoor?,
/** Where this stop comes in the trip: 0 is the first, always the first sent. */
val waypointIndex: Int,
val entrance: RouteEntrance? = null,
/** The address unit this stop was sent as, by its code; null for any other stop. */
val unit: RouteUnit? = null,
/** The date of the door, unit or entrance behind this stop, YYYY-MM-DD; null for a bare point. Absent before API 0.87.0. */
val sourceDate: String? = null,
)
/** A tile source tilequery reads: the building, entrance and place layers of the map. */
enum class TilequerySource(wire: String) {
BUILDING("building"),
ENTRANCE("entrance"),
PLACE("place")
}
/** A feature's geometry type in its tile, as Mapbox's tilequery.geometry; keeps one type in a tilequery. */
enum class TilequeryGeometry(wire: String) {
POLYGON("polygon"),
LINESTRING("linestring"),
POINT("point")
}
/** GET /v1/tiles/{sources}/tilequery/{lon},{lat} (API 0.68.0), as Mapbox's Tilequery API: GeoJSON, nearest first,
* then by layer and id. The same rows as the tiles: current, not demolished, nothing in a restricted site. */
data class Tilequery(
/** "FeatureCollection". */
val type: String,
val features: List<TilequeryFeature>,
)
/** One building or entrance at or near the point: a Point, the part of it nearest the point sent. */
data class TilequeryFeature(
/** "Feature". */
val type: String,
val geometry: TilequeryPoint,
val properties: TilequeryProperties,
)
/** A tilequery feature's place: the point sent when it is inside. The 7th decimal is a mark for your organization. */
data class TilequeryPoint(
/** "Point". */
val type: String,
val coordinates: LonLat,
)
/** The fields of the feature's tile layer, and nothing more: a building has building fields, an entrance entrance
* fields. null where the map does not know. No building codes. */
data class TilequeryProperties(
/** The building or entrance id, as in its tile. Never an address. */
val id: String,
/** Building only. */
val nameMn: String? = null,
/** Building only. */
val nameEn: String? = null,
/** Building only: apartment, ger_house, commercial, public, industrial, garage or other. */
val type: String? = null,
/** Building only: planned, under_construction or built. */
val status: String? = null,
/** Building only. Metres. */
val heightM: Double? = null,
/** Building only: where heightM came from. osm:height a tag; levels levels x 3.1 m; default:* a type default;
* estimated:* footprint shape and size; unknown none. */
val heightSource: String? = null,
/** Building only: 3 a tag or editor, 2 levels, 1 type default, 0 estimated. 0 and 1 are estimates, never
* measurements. null: no height. */
val heightConfidence: Int? = null,
/** Building only. */
val levels: Int? = null,
/** Building only: official building number from the state register. null: no matched or partial official form. */
val buildingNumber: String? = null,
/** Building only: the building's name when it is a name, not a purpose. null: no name or no official form. */
val buildingName: String? = null,
/** Entrance only: door, gate, vehicle_gate or stairwell. */
val kind: String? = null,
/** Entrance only. */
val letter: String? = null,
/** Entrance only: its building. null: none on record. */
val buildingId: String? = null,
/** 0 to 5 (PLAN.md 5). null: not rated yet. */
val confidence: Int?,
val source: String,
/** Place only: the map category (bank, pharmacy, stop, religion, etc.). */
val cat: String? = null,
/** Place only: the map rank (higher shows over lower in collisions). */
val rank: Double? = null,
val tilequery: TilequeryInfo,
/** A building: whether its footprint is backed by a second source. null for other kinds and for a building not yet checked. */
val footprintCheck: FootprintCheck? = null,
)
/** How a tilequery feature lies from the point sent. */
data class TilequeryInfo(
/** Metres from the point sent to the nearest part of the feature, to 0.1 m. 0: the point is inside it. */
val distance: Double,
/** polygon, linestring or point. */
val geometry: String,
/** The tile source and layer the feature is in: building or entrance. */
val layer: String,
)
/** GET /v1/matching (API 0.65.0): a GPS trace on the road network. One matching per unbroken piece; one tracepoint
* per point sent, in its order, null where no road was found for it or it is inside a restricted site. */
data class Match(
val mode: String,
val matchings: List<Matching>,
val tracepoints: List<MatchTracepoint?>,
val source: String,
)
/** One unbroken piece of a matched trace: the roads it went along, in its order. */
data class Matching(
val distanceM: Double,
/** Seconds at the router's free-flow speed on each road, no turn costs; not the time the trace took. */
val durationS: Double,
val geometry: LineString,
)
/** Where one point sent lies on the matched line. */
data class MatchTracepoint(
/** The matching this point lies on, an index into matchings. */
val matchingsIndex: Int,
/** The point on the matched line. */
val location: LonLat,
/** Straight-line meters from the point sent to location. */
val gapM: Double,
)
/** POST /v1/door/report. A person reviews it; door data does not change until then. */
sealed class DoorReport {
/** Words for the reviewer, or null. */
abstract val note: String?
/** The door with [code] is at [point] instead. */
data class WrongLocation(val code: String, val point: LonLat, override val note: String? = null) : DoorReport()
/** No door is where [code] points. */
data class Missing(val code: String, override val note: String? = null) : DoorReport()
/** The door with [code] is the same door as the one with [duplicateOf]. */
data class Duplicate(val code: String, val duplicateOf: String, override val note: String? = null) : DoorReport()
/** A door at [point] that has no code. */
data class Unmapped(val point: LonLat, override val note: String? = null) : DoorReport()
}
/** The answer to POST /v1/door/report. */
data class DoorReportReceipt(
val id: String,
/** queued or already_queued. */
val status: String,
val kind: String,
val code: String?,
/** door, building, plot, or null. */
val target: String?,
val movedM: Double?,
val receivedAt: String,
val next: String,
)
/** POST /v1/confirm body. A driver on site says a door is there, elsewhere or gone. */
data class Confirmation(
val target: Target,
val result: Result,
/** Phone GPS on site. A check only, never the placed point: within 40 m of the tap. */
val device: LonLat,
/** Your own opaque id for the driver (A-Z a-z 0-9 . _ : -, up to 64). Never a phone number, e-mail or name. */
val driver: String? = null,
val note: String? = null,
/** When the driver was on site, for a confirmation queued offline. At most 7 days ago. */
val observedAt: Date? = null,
) {
/** The door by code, or by the entrance id in the entrance tile layer for entrances with no code yet. */
sealed class Target {
/** A door by its code. */
data class Code(val code: String) : Target()
/** An entrance with no code yet, by its id in the entrance tile layer. */
data class Entrance(val id: String) : Target()
}
/** Confirmed and WrongLocation carry the point the driver tapped on the map: the placed point. */
sealed class Result {
/** The door is where the map has it. */
data class Confirmed(val tap: LonLat) : Result()
/** The door is elsewhere: [tap] is where. */
data class WrongLocation(val tap: LonLat) : Result()
/** No door there. No tap: there is no door to tap. */
object Missing : Result() {
override fun toString(): String
}
}
}
/** POST /v1/confirm. Map data does not change at once. */
data class ConfirmReceipt(
/** Same id for a repeat. */
val id: String,
/** recorded, or already_recorded: the same driver (or your org, with no driver) said the same in the last 24 h. */
val status: String,
val result: String,
/** door, entrance or unit (the code named a building or khashaa: an arrival). */
val target: String,
val code: String?,
/** The entrance confirmed, also for a door code. null for a unit code. */
val entrance: String?,
/** Metres from the tap to the entrance on the map. null for missing. */
val movedM: Double?,
/** Metres from the tap (missing: the entrance) to the phone GPS. */
val gpsM: Double,
val observedAt: String?,
val receivedAt: String,
/** confidence: counts toward the entrance confidence (PLAN.md 5). review: a person checks it first. arrival: recorded as an arrival at a building or khashaa, no entrance raised. */
val next: String,
)
/** Confirmations and door reports by result. duplicate and unmapped come from door reports. */
data class ConfirmCounts(
val confirmed: Int,
val wrongLocation: Int,
val missing: Int,
val duplicate: Int,
/** A real door with no code yet. */
val unmapped: Int,
)
/** One Ulaanbaatar day the drivers were on site (observed_at, else received). The counts as in [ConfirmCounts]. */
data class ConfirmDay(
/** YYYY-MM-DD. */
val day: String,
/** Different drivers that day, by your driver ids. Rows with no driver id are not counted here. */
val drivers: Int,
val confirmed: Int,
val wrongLocation: Int,
val missing: Int,
val duplicate: Int,
val unmapped: Int,
)
/** A failure point: a door or entrance your drivers said is wrong, missing or a duplicate. */
data class ConfirmFlagged(
/** null when your drivers named the entrance, not a code. */
val code: String?,
/** The entrance id, as in the entrance tile layer. */
val entrance: String,
/** true when the door or entrance has since been retired. */
val retired: Boolean,
val wrongLocation: Int,
val missing: Int,
val duplicate: Int,
/** The newest of them, on site. */
val lastAt: String,
)
/** GET /v1/confirm/own: your organization's rows only. */
data class OwnConfirmations(
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while confirmations queued offline can still join; true from 8 days after the month ends. */
val closed: Boolean,
val totals: ConfirmCounts,
/** Different drivers in the month, by your driver ids. Never the ids. */
val drivers: Int,
/** Days with at least one row, oldest first. */
val days: List<ConfirmDay>,
/** Most first, at most 100. None inside a restricted site. */
val flagged: List<ConfirmFlagged>,
)
/** Daily caps; null is no cap. */
data class UsageCaps(val dailyQuota: Int?, val tileDailyQuota: Int?, val doorMissCap: Int?)
/** What a free org gets on the partner plan. Prices are not published yet. */
data class UsageUpgrade(
val plan: String,
val caps: UsageCaps,
val rateLimit: Int,
val tileRateLimit: Int,
val addedScopes: List<String>,
)
/** The key that made the usage call. */
data class UsageKey(
/** First 11 characters of the key that made this call. */
val prefix: String?,
val scopes: List<String>,
val rateLimit: Int,
val tileRateLimit: Int,
)
/** One day in usage(). */
data class UsageDay(
/** Ulaanbaatar day, YYYY-MM-DD. */
val day: String,
val calls: Int,
val tiles: Int,
val doorMisses: Int,
val keys: Int,
)
/** One route of the org over the days asked (API 0.62.0). */
data class UsageEndpoint(
/** Method and route pattern, e.g. `GET /v1/door/:code`. Never a code, a name or a query. */
val endpoint: String,
/** Requests to this route. The tiles route counts tiles. */
val calls: Int,
/** Of those, lookups that found no door. */
val misses: Int,
)
/** GET /v1/usage: the calling key's organization. Caps are null when there is no cap. */
data class UsageResponse(
val org: String,
/** free: made by POST /v1/signup. custom: a partner org. */
val plan: String,
/** The partner plan for a free org, null for a partner org. */
val upgrade: UsageUpgrade?,
val timezone: String,
/** Unix seconds of the next daily reset. */
val reset: Long,
val key: UsageKey,
val caps: UsageCaps,
/** Newest first. */
val days: List<UsageDay>,
/** Features open to the org, A to Z, e.g. `cargo.eta` (API 0.51.0). Routes of any other feature answer 403
* `feature_off`. null from an API before 0.51.0. */
val features: List<String>? = null,
/** Every route the org called over the same days, most calls first, then A to Z (API 0.62.0). null from an API
* before 0.62.0. */
val endpoints: List<UsageEndpoint>? = null,
)
/** POST /v1/telemetry/search. */
data class SearchTelemetryReceipt(
val stored: Int,
/** Text that was only a phone or an e-mail address, or a pick not in the index. */
val dropped: Int,
)
/** POST /v1/traces. */
data class TraceReceipt(
/** Keep it to fetch or delete the trip. null: nothing was stored (every point was inside a restricted site). */
val id: String?,
val points: Int,
/** Points left out: inside a restricted site. */
val restricted: Int,
/** Your organization sent this trip before; id is the first upload. */
val duplicate: Boolean,
/** Raw points are erased after this day, YYYY-MM-DD. */
val purgeAfter: String?,
)
/** A stored trip's line. Each coordinate is [lon, lat, t]. */
data class TraceLine(val type: String, val coordinates: List<List<Double>>)
/** A stored trip's fields, in Trace.properties. */
data class TraceProperties(
val receivedDay: String,
val purgeAfter: String,
val points: Int,
val restricted: Int,
val deletedAt: String?,
/** partner or retention, null while the points are kept. */
val deletedReason: String?,
)
/** GET /v1/traces/{id}: your trip as stored, GeoJSON. geometry is null once deleted or past purge_after. */
data class Trace(val type: String, val id: String, val geometry: TraceLine?, val properties: TraceProperties)
/** DELETE /v1/traces/{id}. deleted is true also when the points were gone before. */
data class TraceDeleted(val id: String, val deleted: Boolean)
/** west, south, east, north in degrees. */
data class BBox(val west: Double, val south: Double, val east: Double, val north: Double)
/** A cell's square, GeoJSON Polygon: one closed ring of [lon, lat]. */
data class TraceCellPolygon(val type: String, val coordinates: List<List<List<Double>>>)
/** A cell's fields, in TraceCell.properties. */
data class TraceCellProperties(
/** Distinct trips through the cell that month. */
val trips: Int,
)
/** One 500 m cell of the fixed grid in a month. id is "x/y", the same cell every month. */
data class TraceCell(val type: String, val id: String, val geometry: TraceCellPolygon, val properties: TraceCellProperties)
/** GET /v1/traces/cells: all partners together, only cells that meet [rule]. No cell means too few, not zero. */
data class TraceCells(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** Keys as the API writes them (min_orgs, cell, period). */
val rule: JsonObject,
val features: List<TraceCell>,
)
/** A cell's fields, in TraceSpeed.properties. */
data class TraceSpeedProperties(
/** Counted meters over counted seconds of all partners' trips in the cell, to 0.1. No trip count. */
val kmH: Double,
)
/** One 500 m cell of the trip cells grid with all partners' moving speed that month. id is "x/y", as TraceCell. */
data class TraceSpeed(val type: String, val id: String, val geometry: TraceCellPolygon, val properties: TraceSpeedProperties)
/** GET /v1/traces/speeds: all partners together, only cells that meet [rule]. No cell means too few trips, not slow. */
data class TraceSpeeds(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** The group rule each cell passed, and how a segment and km_h are measured, keys as the API writes them. */
val rule: JsonObject,
/** Cells north to south, then west to east. */
val features: List<TraceSpeed>,
)
/** A road's line: dock's current road with that OSM id, a LineString, or a MultiLineString when it is in pieces. */
sealed class TraceRoadGeometry {
/** Every piece of the line, one for a LineString: draw them all the same way. */
abstract val lines: List<List<LonLat>>
/** The road in one piece. */
data class LineString(val points: List<LonLat>) : TraceRoadGeometry() {
override val lines: List<List<LonLat>>
}
/** The road in pieces. */
data class MultiLineString(override val lines: List<List<LonLat>>) : TraceRoadGeometry()
}
/** A road's fields, in TraceRoad.properties. */
data class TraceRoadProperties(
/** Counted meters over counted seconds of all partners' trips matched to the way, to 0.1. No trip count. */
val kmH: Double,
/** The road's names and OSM highway class, null when unknown. */
val nameMn: String?,
val nameEn: String?,
val highway: String?,
)
/** One OSM way with all partners' moving speed that month. id is the OSM way id as text: it can pass 2^53 one day. */
data class TraceRoad(val type: String, val id: String, val geometry: TraceRoadGeometry, val properties: TraceRoadProperties)
/** Hour band of GET /v1/traces/roads (API 0.49.0), by the Ulaanbaatar time of a trip's first stored point: AM_PEAK Monday
* to Friday 07:00 to 09:59, PM_PEAK Monday to Friday 17:00 to 19:59, OFF_PEAK every other hour, Saturday and Sunday whole. */
enum class RoadBand(wire: String) {
AM_PEAK("am_peak"),
PM_PEAK("pm_peak"),
OFF_PEAK("off_peak")
}
/** GET /v1/traces/roads: all partners together, only roads that meet [rule]. No road means too few trips, not slow. */
data class TraceRoads(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** The group rule each road passed, and how a trip is matched and km_h measured, keys as the API writes them. */
val rule: JsonObject,
/** Roads by OSM way id, smallest first. */
val features: List<TraceRoad>,
)
/** GET /v1/traces/cells/own: your organization's trips only, every cell with one of them. */
data class OwnTraceCells(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while trips received now still count; true from 14 days after the month ends. */
val closed: Boolean,
val rule: JsonObject,
val features: List<TraceCell>,
)
/** Trips per whole arrival minute. */
data class ArrivalMinute(
/** Whole minutes; 60 is 60 or more. */
val minutes: Int,
val trips: Int,
)
/** A cell's fields, in ArrivalCell.properties. */
data class ArrivalCellProperties(
/** Your trips that ended in the cell that month. */
val trips: Int,
/** Of them, arrivals of 5 minutes or more. */
val slow: Int,
/** Median arrival minutes in the cell, nearest rank. */
val median: Int,
)
/** A 500 m cell of the trip cells grid where your trips ended. id is "x/y", the same cell every month. */
data class ArrivalCell(val type: String, val id: String, val geometry: TraceCellPolygon, val properties: ArrivalCellProperties)
/** GET /v1/traces/arrivals/own: how long your trips spend in the last 200 m. Your organization's trips only. */
data class OwnArrivals(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while trips received now still count; true from 14 days after the month ends. */
val closed: Boolean,
/** How arrival minutes are measured, keys as the API writes them (radius_m, max_minutes, slow_minutes, ...). */
val rule: JsonObject,
/** Your trips with an arrival that month, every cell. */
val trips: Int,
/** Of them, arrivals of 5 minutes or more. */
val slow: Int,
/** Nearest rank. null with no trips. */
val median: Int?,
val p75: Int?,
val p90: Int?,
/** Trips per arrival minute, fewest minutes first; minutes with none left out. */
val histogram: List<ArrivalMinute>,
/** Cells your trips ended in; features lists up to 100 of them. */
val cells: Int,
/** By slow trips, then trips, most first. */
val features: List<ArrivalCell>,
)
/** Trips that started in one Ulaanbaatar weekday and hour: yours in OwnHours, all partners' in AllHours. */
data class HourSlot(
/** ISO weekday in Ulaanbaatar, 1 Monday to 7 Sunday. */
val weekday: Int,
/** Hour in Ulaanbaatar, 0 to 23. */
val hour: Int,
val trips: Int,
)
/** GET /v1/traces/hours/own: when your trips start, by weekday and hour. Your organization's trips only. */
data class OwnHours(
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while trips received now still count; true from 14 days after the month ends. */
val closed: Boolean,
/** How the weekday and hour are read, keys as the API writes them. */
val rule: JsonObject,
/** Your trips that month, each once, at the weekday and hour of its first stored point. */
val trips: Int,
/** 7 counts, Monday first. */
val byWeekday: List<Int>,
/** 24 counts, hour 0 first. */
val byHour: List<Int>,
/** Weekday and hour with trips, weekday then hour; slots with none left out. */
val slots: List<HourSlot>,
)
/** GET /v1/traces/hours: when trips start, by weekday and hour, all partners together. A published month only. */
data class AllHours(
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** The group rule each slot passed, and how the weekday and hour are read, keys as the API writes them. */
val rule: JsonObject,
/** Weekday and hour that passed the rule, weekday then hour. A slot left out had too few trips, not zero. No total. */
val slots: List<HourSlot>,
)
/** A khoroo's area, GeoJSON MultiPolygon: polygons of closed rings of [lon, lat]. */
data class KhorooPolygon(val type: String, val coordinates: List<List<List<List<Double>>>>)
/** Trips that ended in one khoroo in one Ulaanbaatar hour, all partners together. */
data class KhorooHour(
/** Hour in Ulaanbaatar of the trip end, 0 to 23. */
val hour: Int,
val trips: Int,
)
/** A khoroo's fields, in TraceKhoroo.properties. */
data class TraceKhorooProperties(
val districtMn: String,
val districtEn: String?,
/** Khoroo number in its district. */
val khoroo: Int,
/** Hours that passed the rule, from 0. An hour left out had too few trips, not zero. No khoroo total. */
val hours: List<KhorooHour>,
)
/** A current khoroo polygon with the hours of arrival that passed the rule. id is the polygon's, never an address. */
data class TraceKhoroo(val type: String, val id: String, val geometry: KhorooPolygon, val properties: TraceKhorooProperties)
/** GET /v1/traces/khoroos: where and when trips end, per khoroo and hour, all partners together. A published month only. */
data class TraceKhoroos(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** The group rule each khoroo and hour passed, and how they are read, keys as the API writes them. */
val rule: JsonObject,
/** Khoroos with at least one hour shown, by district then number. */
val features: List<TraceKhoroo>,
)
/** A khoroo's fields, in OwnKhoroo.properties. */
data class OwnKhorooProperties(
val districtMn: String,
val districtEn: String?,
/** Khoroo number in its district. */
val khoroo: Int,
/** Your trips that ended in this khoroo that month. */
val trips: Int,
/** Your trips per Ulaanbaatar hour of arrival, from 0. Hours with none left out. */
val hours: List<KhorooHour>,
)
/** A current khoroo polygon with your trips that ended in it. id is the polygon's, never an address. */
data class OwnKhoroo(val type: String, val id: String, val geometry: KhorooPolygon, val properties: OwnKhorooProperties)
/** GET /v1/traces/khoroos/own: where and when your trips end, per khoroo and hour. Your organization only, no group rule. */
data class OwnKhoroos(
val type: String,
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while trips received now still count; true from 14 days after the month ends. */
val closed: Boolean,
/** How the khoroo and hour are read, keys as the API writes them. */
val rule: JsonObject,
/** Your trips that month. */
val trips: Int,
/** Your trips that month ending in no current khoroo polygon. trips is noKhoroo plus every khoroo's trips. */
val noKhoroo: Int,
/** Khoroos with at least one of your trips, by district then number. */
val features: List<OwnKhoroo>,
)
/** Your trips that month ending at one kind of building. */
data class OwnKind(
/** The building type: apartment, ger_house, commercial, public, industrial, garage, other; unknown is a building
* with no type yet, none no building within 30 m. */
val kind: String,
val trips: Int,
)
/** GET /v1/traces/kinds/own: what kind of building your trips end at, per month. Your organization only, no group rule. */
data class OwnKinds(
/** YYYY-MM, Ulaanbaatar. */
val month: String,
/** false while trips received now still count; true from 14 days after the month ends. */
val closed: Boolean,
/** How the building and its kind are read, keys as the API writes them. */
val rule: JsonObject,
/** Your trips that month, the sum of every kind's trips. */
val trips: Int,
/** Kinds with at least one of your trips, most trips first, then by name. */
val kinds: List<OwnKind>,
)
/** One entrance in entrancesNear(): nearest first, each with its building and distance. source generated +
* confidence 0: show as таамаг. No door minted yet: code null. */
data class NearEntrance(
val officialAddress: OfficialAddress?,
/** matched, partial or none. */
val officialStatus: String,
/** true unless officialStatus is matched. Show "unofficial" when true. */
val unofficial: Boolean,
val officialSource: String?,
val officialDate: String?,
/** null: no door minted for this entrance yet. */
val code: String?,
/** Never show as an address. */
val id: String,
/** door, gate, vehicle_gate or stairwell. */
val kind: String,
/** Entrance or gate number as people say it. */
val letter: String?,
val floorsServed: List<Int>?,
val point: LonLat,
/** PLAN 5 level; 0 with source generated: таамаг. */
val confidence: Int?,
val source: String,
/** The building id: pass to place(id) or entrances(). */
val building: String,
val distanceM: Double,
/** The date of this entrance's current version, YYYY-MM-DD. null: not known. Absent before API 0.87.1. */
val sourceDate: String? = null,
)
/** GET /v1/entrances/near. */
data class NearEntrances(
val point: LonLat,
/** Nearest first, within radius. Empty when no entrance is that close. */
val results: List<NearEntrance>,
)
/** One organization in a building's directory. Only organizations matched to an official registration are listed. */
data class DirectoryEntry(
val name: String,
/** Floor (давхар). null when the register does not say. */
val floor: Int?,
/** Unit (тоот). null when unknown. */
val unit: String?,
val category: String?,
val source: String,
val confidence: Double?,
/** Date this version of the entry was recorded. */
val sourceDate: String,
)
/** GET /v1/address/{id}/directory: "Энд байгаа байгууллагууд" for an office building or mall card. */
data class BuildingDirectory(
/** The building id, as sent or the one the code names. null for a хашаа code. */
val building: String?,
/** The building's code. null until one is minted. */
val code: String?,
/** By floor (unknown floor last), then unit, then name. Empty when none is known. */
val entries: List<DirectoryEntry>,
)Venue.kt
kotlin
/** One floor of a venue. [ordinal] is the floor as people say it: 0 ground, 1 first, -1 basement. */
data class IndoorLevel(
val ordinal: Int, val shortNameMn: String, val nameMn: String? = null, val outdoor: Boolean, val source: String,
val confidence: Double? = null,
)
/** Where a venue stands: the district and khoroo covering it, and soum and aimag from the OSM admin polygons. */
data class VenueAdmin(
val districtMn: String? = null, val districtEn: String? = null, val khoroo: Int? = null, val soumMn: String? = null,
val aimagMn: String? = null,
)
/** GET /v1/venue/{id}: a building with an indoor map, its outline (GeoJSON Polygon) and its current floors, lowest first. */
data class Venue(
val id: String, val nameMn: String, val nameEn: String? = null, val category: String, val source: String,
val confidence: Double? = null,
/** The building code of the real-map building the venue stands in; null until it has one. */
val buildingCode: String? = null,
val admin: VenueAdmin? = null, val outline: JsonElement, val levels: List<IndoorLevel>,
)
/** One unit, opening or amenity on a floor. `properties["feature_type"]` says which. */
data class LevelFeature(val type: String, val id: String, val geometry: JsonElement, val properties: JsonElement)
/** GET /v1/venue/{id}/level/{n}: one floor as GeoJSON. */
data class VenueLevel(val type: String, val venueId: String, val level: Int, val features: List<LevelFeature>)
/** GET /v1/venue/{id}/spot: what a printed plate for one unit says. [link] is what the plate's QR holds. */
data class VenueSpot(
val venueId: String, val venueNameMn: String, val unitId: String, val level: Int, val levelNameMn: String,
val nameMn: String? = null, val number: String? = null, val label: String, val point: LonLat, val link: String,
val source: String, val confidence: Double? = null,
)
/** One step of a walk inside a venue, in Mongolian, with the floor it is on. [at] is where the step begins. */
data class IndoorStep(val floor: Int, val text: String, val distanceM: Double, val at: LonLat)
/** One floor of a walk: its line and how it ends: stairs, escalator, elevator or arrive. [toFloor] is the floor a
* connector reaches, null on arrival. */
data class IndoorLeg(
val floor: Int, val distanceM: Double, val geometry: LineString, val endsWith: String, val toFloor: Int? = null,
)
/** GET /v1/venue/{id}/route: the walk inside a venue to one unit, floor by floor. [to] is the unit: level, point,
* name_mn, number, source, confidence. */
data class VenueRoute(
val venueId: String, val distanceM: Double, val durationS: Double, val legs: List<IndoorLeg>,
val steps: List<IndoorStep>, val to: JsonElement,
)
/** The street entrance a road route ends at. */
data class VenueEntrance(val level: Int, val point: LonLat, val coreEntranceId: String? = null)
/** One road step, in both languages. */
data class VenueRoadStep(val mn: String, val en: String, val distanceM: Double, val at: LonLat)
/** The road part of venue directions: the line to the street entrance. */
data class VenueRoad(
val mode: String, val distanceM: Double, val durationS: Double, val geometry: LineString,
val steps: List<VenueRoadStep>, val entrance: VenueEntrance,
)
/** GET /v1/venue/{id}/directions: the road to the street entrance, then the walk inside. Distance and duration are
* road plus walk. */
data class VenueDirections(
val venueId: String, val distanceM: Double, val durationS: Double, val road: VenueRoad, val indoor: VenueRoute,
)
/** A building with an indoor map: outline and floors. [venueId] is the `indoor.venueId` a geocode row carries.
* Needs door:read. 404 not_found for an unknown id. */
suspend fun Client.venue(venueId: String): Venue
/** One floor of a venue as GeoJSON: its units, openings and amenities, each with source and confidence. [level] is
* the floor number (0 ground, -1 basement). Needs door:read. 404 not_found for an unknown venue or floor. */
suspend fun Client.venueLevel(venueId: String, level: Int): VenueLevel
/** What a printed plate for one unit says: venue, floor, name or number, a point, and the link its QR holds.
* [unitId] is a unit's id from venueLevel. Needs door:read. 404 not_found for an unknown venue or unit. */
suspend fun Client.venueSpot(venueId: String, unitId: String): VenueSpot
/** The walk inside a venue to one unit, floor by floor, with steps in Mongolian and the floor on each. [from] is a
* point on floor [fromLevel] (0 when null): the entrance a road route used, or a tapped spot. Needs door:read.
* 404 not_found; 422 no_route where no drawn corridor joins the start and the unit. */
suspend fun Client.venueRoute(venueId: String, from: LonLat, unitId: String, fromLevel: Int? = null): VenueRoute
/** The whole way to a unit in one answer: the road to the right street entrance, then the walk inside. [mode] is
* driving when null. Needs route:read. 404 not_found; 422 no_entrance when no street entrance is drawn, 422
* no_route when no road or no corridor reaches it. */
suspend fun Client.venueDirections(venueId: String, from: LonLat, unitId: String, mode: RouteMode? = null): VenueDirectionsWater.kt
kotlin
/** A water point's fields, in WaterFeature.properties. */
data class WaterProperties(
/** The first of drinking_water, water_point, water_tap, water_well, spring the OSM element carries. */
val kind: String,
val name: String? = null,
/**
* The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. null when
* name is the OSM text. Show name. Packs before offline v12 lack it.
*/
val nameOsm: String? = null,
val nameEn: String? = null,
/**
* Only what OSM says: true for drinking_water=yes (or amenity=drinking_water with no such tag), false for
* drinking_water=no, null when OSM says nothing. Never guessed from kind.
*/
val drinkable: Boolean? = null,
/** The OSM drinking_water tag as written. */
val drinkingWater: String? = null,
/** The OSM pump tag as written. */
val pump: String? = null,
/** The OSM element, as node/1 or way/1. */
val osm: String,
)
/** One water point in an offline pack's water.geojson: an OSM node or way, a way at a point on its surface. */
data class WaterFeature(val type: String, val geometry: CrossingGeometry, val properties: WaterProperties)
/** Water points per drinkable value. */
data class WaterDrinkable(
val yes: Int,
val no: Int,
/** OSM says nothing. */
val unknown: Int,
)
/**
* An offline pack's water.geojson (manifest files row and water block; index.json water_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none.
*/
data class PackWater(
val type: String,
/** Water points in the file, all Mongolia. */
val written: Int,
/** Per kind, all Mongolia. */
val counts: Map<String, Int>,
val named: Int,
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. null in packs before offline
* v12. */
val namesFolded: Int? = null,
val nameRule: String? = null,
val nameRuleMn: String? = null,
/** All Mongolia. */
val drinkable: WaterDrinkable,
/** A water point is near a route within this many metres of its line, as fuel and ger camps. */
val nearM: Double,
val rule: String,
val ruleMn: String,
val nearRule: String,
val nearRuleMn: String,
val source: String,
val note: String,
val noteMn: String,
val features: List<WaterFeature>,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackWater
}
}
/** A water point near the route. */
data class WaterOnRoute(
val kind: String,
val point: LonLat,
val name: String?,
val nameEn: String?,
val drinkable: Boolean?,
val drinkingWater: String?,
val pump: String?,
val osm: String,
/** Along the route to its point nearest the water, m, rounded. A tie goes to the earlier pass. */
val alongM: Double,
/** From the route line, m, rounded. */
val offM: Double,
/** The OSM text when name was folded to one script, else null. Show name. */
val nameOsm: String? = null,
)
/** What waterOnRoute returns. */
data class RouteWater(
val nearM: Double,
/** In route order. */
val water: List<WaterOnRoute>,
/** Of these points. unknown: OSM says nothing, not "not drinkable". */
val drinkable: WaterDrinkable,
/** Show with the points: from OSM tags, not field checked; a well may be dry, broken or locked. */
val note: String,
val noteMn: String,
val rule: String,
val ruleMn: String,
val nearRule: String,
val nearRuleMn: String,
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. null when the pack has no rule. */
val nameRule: String? = null,
val nameRuleMn: String? = null,
)
/**
* Offline: the OSM water points (wells, springs, taps, drinking water) within [nearM] of a route line, from an
* offline pack's water.geojson, in route order. nearM defaults to the pack's nearM (2000), lane D's route pages.
* Throws IllegalArgumentException for a line under 2 points or a nearM not above 0.
*/
fun waterOnRoute(water: PackWater, line: List<LonLat>, nearM: Double = water.nearM): RouteWaterWetlands.kt
kotlin
/** A GeoJSON LineString: the stretch of the way inside the wetland area. */
data class WetlandGeometry(val type: String, val coordinates: List<LonLat>)
/** A stretch's fields, in WetlandFeature.properties. */
data class WetlandProperties(
/** The area's OSM wetland tag, as marsh, bog, wet_meadow or fen; unknown when the area has none. */
val wetland: String,
/** The area's OSM element, as way/123 or relation/123. */
val area: String,
/** The area's OSM name. */
val name: String? = null,
/**
* The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. null when
* name is the OSM text. Show name. Packs before offline v12 lack it.
*/
val nameOsm: String? = null,
/** The stretch's length, km. */
val km: Double,
/** The car way's OSM id. null on a new track alone. */
val wayId: Long? = null,
val newTrack: Boolean,
/** The way's OSM highway and surface tags, as on crossings. null on a new track alone. */
val road: CrossingRoad? = null,
)
/** One stretch in an offline pack's wetlands.geojson: the part of one car way inside an OSM natural=wetland area. */
data class WetlandFeature(val type: String, val geometry: WetlandGeometry, val properties: WetlandProperties)
/** Stretches of one wetland kind, all Mongolia, and their length in km. */
data class WetlandCount(val stretches: Int, val km: Double)
/**
* An offline pack's wetlands.geojson (manifest files row and wetlands block; index.json wetlands_sha256). Check its
* sha256 against the manifest before use. Packs built before 2026-10-03 have none.
*/
data class PackWetlands(
val type: String,
/** Stretches in the file, all Mongolia. */
val written: Int,
val km: Double,
/** OSM wetland areas the stretches lie in. */
val areas: Int,
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. null in packs before offline
* v12. */
val namesFolded: Int? = null,
val nameRule: String? = null,
val nameRuleMn: String? = null,
/** Per wetland kind, all Mongolia. */
val counts: Map<String, WetlandCount>,
/** A route runs on a stretch where its line comes this close, m. */
val matchM: Double,
val rule: String,
val ruleMn: String,
val source: String,
val note: String,
val noteMn: String,
val features: List<WetlandFeature>,
) {
companion object {
/** The file's text, as in the pack. */
fun fromJson(text: String): PackWetlands
}
}
/** A stretch through OSM wetland the route runs on. */
data class WetlandOnRoute(
val wetland: String,
val area: String,
val name: String?,
/** The whole stretch, km, from the pack. The route may run on part of it: onM. */
val km: Double,
val wayId: Long?,
val newTrack: Boolean,
val road: CrossingRoad?,
/** Along the route to where it meets the stretch, m, rounded. */
val alongM: Double,
/** Metres of the stretch the route runs on, rounded. */
val onM: Double,
/** The stretch's first point the route meets, in route order. */
val point: LonLat,
/** The OSM text when name was folded to one script, else null. Show name. */
val nameOsm: String? = null,
)
/** What wetlandsOnRoute returns. */
data class RouteWetlands(
val matchM: Double,
val minOnM: Double,
/** In route order. */
val stretches: List<WetlandOnRoute>,
/** Sum of onM, m. */
val onM: Double,
/** Show with the stretches: from OSM tags, not field checked. */
val note: String,
val noteMn: String,
val rule: String,
val ruleMn: String,
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. null when the pack has no rule. */
val nameRule: String? = null,
val nameRuleMn: String? = null,
)
/** A route that runs on less of a stretch than this, m, only crosses it, unless it runs on all of the stretch. */
const val WETLAND_MIN_ON_M: Double
/**
* Offline: the stretches through OSM wetland a route line runs on, from an offline pack's wetlands.geojson, in route
* order. The stretch is cut in pieces of at most matchM; a piece is on the route when both its ends lie within
* matchM of the line. A stretch is on the route when its pieces on the route make at least [WETLAND_MIN_ON_M], or
* when all of it is on the route. Throws IllegalArgumentException for a line under 2 points.
*/
fun wetlandsOnRoute(wetlands: PackWetlands, line: List<LonLat>): RouteWetlands