feat: Add Intaleq Native Android Kotlin & iOS Swift SDKs, Incline slope routing, Topographic contours and modular tactical app
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
package com.intaleq.maps.service
|
||||
|
||||
import com.intaleq.maps.IntaleqMaps
|
||||
import com.intaleq.maps.model.LatLng
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class GeocodingPlace(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val nameAr: String?,
|
||||
val nameEn: String?,
|
||||
val category: String?,
|
||||
val city: String?,
|
||||
val neighbourhood: String?,
|
||||
val address: String?,
|
||||
val position: LatLng
|
||||
)
|
||||
|
||||
/**
|
||||
* Commercial Geocoding & Reverse Geocoding Service calling Intaleq SaaS API.
|
||||
*/
|
||||
class IntaleqGeocodingService {
|
||||
|
||||
/**
|
||||
* Search for places, addresses, and landmarks by query string.
|
||||
*/
|
||||
suspend fun search(
|
||||
query: String,
|
||||
proximity: LatLng? = null,
|
||||
limit: Int = 10
|
||||
): List<GeocodingPlace> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val encodedQuery = URLEncoder.encode(query, "UTF-8")
|
||||
var urlString = "${IntaleqMaps.baseUrl}/api/geocoding/search?q=$encodedQuery&limit=$limit"
|
||||
if (proximity != null) {
|
||||
urlString += "&lat=${proximity.latitude}&lng=${proximity.longitude}"
|
||||
}
|
||||
|
||||
val url = URL(urlString)
|
||||
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
connectTimeout = 8000
|
||||
readTimeout = 8000
|
||||
setRequestProperty("x-api-key", IntaleqMaps.apiKey)
|
||||
setRequestProperty("Accept", "application/json")
|
||||
}
|
||||
|
||||
if (conn.responseCode == 200) {
|
||||
val responseText = BufferedReader(InputStreamReader(conn.inputStream)).use { it.readText() }
|
||||
return@withContext parsePlaces(responseText)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return@withContext emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse geocode GPS coordinate to get Arabic street name, neighborhood, and city.
|
||||
*/
|
||||
suspend fun reverseGeocode(position: LatLng): GeocodingPlace? = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val urlString = "${IntaleqMaps.baseUrl}/api/geocoding/reverse?lat=${position.latitude}&lng=${position.longitude}"
|
||||
val url = URL(urlString)
|
||||
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
connectTimeout = 8000
|
||||
readTimeout = 8000
|
||||
setRequestProperty("x-api-key", IntaleqMaps.apiKey)
|
||||
setRequestProperty("Accept", "application/json")
|
||||
}
|
||||
|
||||
if (conn.responseCode == 200) {
|
||||
val responseText = BufferedReader(InputStreamReader(conn.inputStream)).use { it.readText() }
|
||||
val list = parsePlaces(responseText)
|
||||
return@withContext list.firstOrNull()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
private fun parsePlaces(jsonString: String): List<GeocodingPlace> {
|
||||
val places = mutableListOf<GeocodingPlace>()
|
||||
val root = JSONObject(jsonString)
|
||||
val features = if (root.has("features")) root.getJSONArray("features") else JSONArray()
|
||||
|
||||
for (i in 0 until features.length()) {
|
||||
val f = features.getJSONObject(i)
|
||||
val props = f.optJSONObject("properties") ?: JSONObject()
|
||||
val geom = f.optJSONObject("geometry") ?: JSONObject()
|
||||
val coords = geom.optJSONArray("coordinates")
|
||||
|
||||
if (coords != null && coords.length() >= 2) {
|
||||
val lat = coords.getDouble(1)
|
||||
val lng = coords.getDouble(0)
|
||||
places.add(
|
||||
GeocodingPlace(
|
||||
id = f.optString("id", i.toString()),
|
||||
name = props.optString("name", "موقع محدد"),
|
||||
nameAr = props.optString("name_ar", null),
|
||||
nameEn = props.optString("name_en", null),
|
||||
category = props.optString("category", null),
|
||||
city = props.optString("city", null),
|
||||
neighbourhood = props.optString("neighbourhood", null),
|
||||
address = props.optString("address", null),
|
||||
position = LatLng(lat, lng)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return places
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.intaleq.maps.service
|
||||
|
||||
import com.intaleq.maps.IntaleqMaps
|
||||
import com.intaleq.maps.model.LatLng
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class RouteStep(
|
||||
val instructionAr: String,
|
||||
val distanceMeters: Double,
|
||||
val durationSeconds: Double,
|
||||
val maneuverSign: Int,
|
||||
val inclinePercentage: Double = 0.0
|
||||
)
|
||||
|
||||
data class CommercialRoute(
|
||||
val coordinates: List<LatLng>,
|
||||
val steps: List<RouteStep>,
|
||||
val totalDistanceMeters: Double,
|
||||
val totalDurationSeconds: Double,
|
||||
val elevationGainMeters: Double = 0.0,
|
||||
val elevationLossMeters: Double = 0.0,
|
||||
val averageInclinePercentage: Double = 0.0,
|
||||
val maxInclinePercentage: Double = 0.0
|
||||
)
|
||||
|
||||
/**
|
||||
* Commercial Routing Service calling Intaleq SaaS Routing Engine.
|
||||
*/
|
||||
class IntaleqRoutingService {
|
||||
|
||||
suspend fun getRoute(
|
||||
origin: LatLng,
|
||||
destination: LatLng,
|
||||
profile: String = "car",
|
||||
locale: String = "ar"
|
||||
): CommercialRoute? = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val urlString = "${IntaleqMaps.baseUrl}/api/maps/route?" +
|
||||
"fromLat=${origin.latitude}&fromLng=${origin.longitude}&" +
|
||||
"toLat=${destination.latitude}&toLng=${destination.longitude}&" +
|
||||
"profile=$profile&steps=true&locale=$locale"
|
||||
|
||||
val url = URL(urlString)
|
||||
val conn = (url.openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
connectTimeout = 10000
|
||||
readTimeout = 10000
|
||||
setRequestProperty("x-api-key", IntaleqMaps.apiKey)
|
||||
setRequestProperty("Accept", "application/json")
|
||||
}
|
||||
|
||||
if (conn.responseCode == 200) {
|
||||
val reader = BufferedReader(InputStreamReader(conn.inputStream))
|
||||
val responseText = reader.use { it.readText() }
|
||||
return@withContext parseRouteResponse(responseText)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
private fun parseRouteResponse(jsonString: String): CommercialRoute {
|
||||
val root = JSONObject(jsonString)
|
||||
val paths = root.getJSONArray("paths")
|
||||
if (paths.length() == 0) throw IllegalStateException("No path returned")
|
||||
|
||||
val path = paths.getJSONObject(0)
|
||||
val distance = path.optDouble("distance", 0.0)
|
||||
val timeMs = path.optDouble("time", 0.0)
|
||||
|
||||
// Coordinates
|
||||
val coords = mutableListOf<LatLng>()
|
||||
val pointsObj = path.optJSONObject("points")
|
||||
if (pointsObj != null && pointsObj.has("coordinates")) {
|
||||
val arr = pointsObj.getJSONArray("coordinates")
|
||||
for (i in 0 until arr.length()) {
|
||||
val c = arr.getJSONArray(i)
|
||||
coords.add(LatLng(c.getDouble(1), c.getDouble(0)))
|
||||
}
|
||||
}
|
||||
|
||||
// Steps
|
||||
val steps = mutableListOf<RouteStep>()
|
||||
val instrs = path.optJSONArray("instructions")
|
||||
if (instrs != null) {
|
||||
for (i in 0 until instrs.length()) {
|
||||
val inst = instrs.getJSONObject(i)
|
||||
steps.add(
|
||||
RouteStep(
|
||||
instructionAr = inst.optString("text", ""),
|
||||
distanceMeters = inst.optDouble("distance", 0.0),
|
||||
durationSeconds = inst.optDouble("time", 0.0) / 1000.0,
|
||||
maneuverSign = inst.optInt("sign", 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return CommercialRoute(
|
||||
coordinates = coords,
|
||||
steps = steps,
|
||||
totalDistanceMeters = distance,
|
||||
totalDurationSeconds = timeMs / 1000.0
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user