feat: Add Intaleq Native Android Kotlin & iOS Swift SDKs, Incline slope routing, Topographic contours and modular tactical app

This commit is contained in:
Hamza-Ayed
2026-08-20 23:54:58 +03:00
parent 4f6595b1eb
commit 431d1f95ff
187 changed files with 20640 additions and 151 deletions
@@ -0,0 +1,35 @@
import Foundation
import CoreGraphics
/**
* Encapsulates all camera position properties for iOS map view.
* Fully compatible with Google Maps SDK (GMSCameraPosition).
*/
public struct InlqCameraPosition: Equatable, Codable {
public let target: InlqLatLng
public let zoom: Float
public let viewingAngle: Double // Tilt (0 - 60 degrees)
public let bearing: Double // Heading (0 - 360 degrees)
public init(target: InlqLatLng, zoom: Float = 12.0, viewingAngle: Double = 0.0, bearing: Double = 0.0) {
self.target = target
self.zoom = zoom
self.viewingAngle = max(0.0, min(65.0, viewingAngle))
self.bearing = bearing.truncatingRemainder(dividingBy: 360.0)
}
public static func from(target: InlqLatLng, zoom: Float) -> InlqCameraPosition {
return InlqCameraPosition(target: target, zoom: zoom)
}
}
/**
* Camera update instructions for smooth animation.
*/
public enum InlqCameraUpdate {
case newLatLng(InlqLatLng)
case newLatLngZoom(InlqLatLng, Float)
case newCameraPosition(InlqCameraPosition)
case zoomIn
case zoomOut
}
@@ -0,0 +1,148 @@
import Foundation
import UIKit
/**
* Controller for an IntaleqMapView instance on iOS.
* Mirrors IntaleqMapController from Flutter SDK and GMSMapView from Google Maps iOS SDK.
*/
public class IntaleqMapController {
private var markers: [InlqMarkerId: InlqMarker] = [:]
private var polylines: [InlqPolylineId: InlqPolyline] = [:]
private var polygons: [InlqPolygonId: InlqPolygon] = [:]
private var circles: [InlqCircleId: InlqCircle] = [:]
public private(set) var cameraPosition: InlqCameraPosition = InlqCameraPosition(
target: InlqLatLng(latitude: 31.9539, longitude: 35.9106),
zoom: 12.0
)
public var onCameraMove: ((InlqCameraPosition) -> Void)?
public var onCameraIdle: (() -> Void)?
public var onMapTap: ((InlqLatLng) -> Void)?
public var onMapLongPress: ((InlqLatLng) -> Void)?
public init() {}
/**
* Animates camera movement to the new position defined in the update.
*/
public func animateCamera(with update: InlqCameraUpdate, duration: TimeInterval = 1.0, completion: (() -> Void)? = nil) {
applyCameraUpdate(update)
completion?()
}
/**
* Instantly repositions camera to the new position defined in the update.
*/
public func moveCamera(with update: InlqCameraUpdate) {
applyCameraUpdate(update)
}
private func applyCameraUpdate(_ update: InlqCameraUpdate) {
switch update {
case .newLatLng(let latLng):
cameraPosition = InlqCameraPosition(target: latLng, zoom: cameraPosition.zoom, viewingAngle: cameraPosition.viewingAngle, bearing: cameraPosition.bearing)
case .newLatLngZoom(let latLng, let zoom):
cameraPosition = InlqCameraPosition(target: latLng, zoom: zoom, viewingAngle: cameraPosition.viewingAngle, bearing: cameraPosition.bearing)
case .newCameraPosition(let pos):
cameraPosition = pos
case .zoomIn:
cameraPosition = InlqCameraPosition(target: cameraPosition.target, zoom: cameraPosition.zoom + 1.0, viewingAngle: cameraPosition.viewingAngle, bearing: cameraPosition.bearing)
case .zoomOut:
cameraPosition = InlqCameraPosition(target: cameraPosition.target, zoom: max(0.0, cameraPosition.zoom - 1.0), viewingAngle: cameraPosition.viewingAngle, bearing: cameraPosition.bearing)
}
onCameraMove?(cameraPosition)
onCameraIdle?()
}
// ── Markers ─────────────────────────────────────────────────────────────
@discardableResult
public func addMarker(_ marker: InlqMarker) -> InlqMarker {
markers[marker.markerId] = marker
return marker
}
public func removeMarker(withId id: InlqMarkerId) {
markers.removeValue(forKey: id)
}
public func clearMarkers() {
markers.removeAll()
}
public func getMarkers() -> [InlqMarker] {
return Array(markers.values)
}
// ── Polylines ───────────────────────────────────────────────────────────
@discardableResult
public func addPolyline(_ polyline: InlqPolyline) -> InlqPolyline {
polylines[polyline.polylineId] = polyline
return polyline
}
public func removePolyline(withId id: InlqPolylineId) {
polylines.removeValue(forKey: id)
}
public func clearPolylines() {
polylines.removeAll()
}
public func getPolylines() -> [InlqPolyline] {
return Array(polylines.values)
}
// ── Polygons ────────────────────────────────────────────────────────────
@discardableResult
public func addPolygon(_ polygon: InlqPolygon) -> InlqPolygon {
polygons[polygon.polygonId] = polygon
return polygon
}
public func removePolygon(withId id: InlqPolygonId) {
polygons.removeValue(forKey: id)
}
public func clearPolygons() {
polygons.removeAll()
}
public func getPolygons() -> [InlqPolygon] {
return Array(polygons.values)
}
// ── Circles ─────────────────────────────────────────────────────────────
@discardableResult
public func addCircle(_ circle: InlqCircle) -> InlqCircle {
circles[circle.circleId] = circle
return circle
}
public func removeCircle(withId id: InlqCircleId) {
circles.removeValue(forKey: id)
}
public func clearCircles() {
circles.removeAll()
}
public func getCircles() -> [InlqCircle] {
return Array(circles.values)
}
/**
* Clears all overlays from the map.
*/
public func clear() {
clearMarkers()
clearPolylines()
clearPolygons()
clearCircles()
}
}
@@ -0,0 +1,25 @@
import Foundation
/**
* Main entry point for Intaleq Sovereign Maps Commercial SDK for iOS (Swift).
* Fully compatible with Google Maps SDK API conventions.
*/
public class IntaleqMaps {
public static let shared = IntaleqMaps()
public private(set) var apiKey: String = ""
public private(set) var baseUrl: String = "https://map-saas.intaleqapp.com"
public private(set) var isInitialized: Bool = false
private init() {}
/**
* Initializes the Intaleq Maps SDK with your SaaS API Key.
*/
public static func initialize(apiKey: String, baseUrl: String = "https://map-saas.intaleqapp.com") {
shared.apiKey = apiKey
shared.baseUrl = baseUrl.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
shared.isInitialized = true
}
}
@@ -0,0 +1,241 @@
import Foundation
import UIKit
import CoreLocation
// ─────────────────────────────────────────────────────────────
// ID Types (Matching Flutter SDK & Google Maps iOS SDK)
// ─────────────────────────────────────────────────────────────
public struct InlqMarkerId: Hashable, Equatable, Codable {
public let value: String
public init(_ value: String) { self.value = value }
}
public struct InlqPolylineId: Hashable, Equatable, Codable {
public let value: String
public init(_ value: String) { self.value = value }
}
public struct InlqPolygonId: Hashable, Equatable, Codable {
public let value: String
public init(_ value: String) { self.value = value }
}
public struct InlqCircleId: Hashable, Equatable, Codable {
public let value: String
public init(_ value: String) { self.value = value }
}
/**
* An immutable struct representing a latitude/longitude bounding box.
*/
public struct InlqLatLngBounds: Equatable, Codable {
public let southwest: InlqLatLng
public let northeast: InlqLatLng
public init(southwest: InlqLatLng, northeast: InlqLatLng) {
self.southwest = southwest
self.northeast = northeast
}
public func contains(_ point: InlqLatLng) -> Bool {
let inLat = point.latitude >= southwest.latitude && point.latitude <= northeast.latitude
let inLng = point.longitude >= southwest.longitude && point.longitude <= northeast.longitude
return inLat && inLng
}
}
// ─────────────────────────────────────────────────────────────
// Bitmap & InfoWindow Types
// ─────────────────────────────────────────────────────────────
public class InlqBitmap {
public enum BitmapType {
case defaultMarker
case defaultMarkerWithHue(CGFloat)
case image(UIImage)
case asset(String)
}
public let type: BitmapType
public init(type: BitmapType) {
self.type = type
}
public static let defaultMarker = InlqBitmap(type: .defaultMarker)
public static func defaultMarker(withHue hue: CGFloat) -> InlqBitmap {
return InlqBitmap(type: .defaultMarkerWithHue(hue))
}
public static func from(image: UIImage) -> InlqBitmap {
return InlqBitmap(type: .image(image))
}
public static func from(assetName: String) -> InlqBitmap {
return InlqBitmap(type: .asset(assetName))
}
}
public struct InlqInfoWindow {
public let title: String?
public let snippet: String?
public let anchor: CGPoint
public let onTap: (() -> Void)?
public init(title: String? = nil, snippet: String? = nil, anchor: CGPoint = CGPoint(x: 0.5, y: 0.0), onTap: (() -> Void)? = nil) {
self.title = title
self.snippet = snippet
self.anchor = anchor
self.onTap = onTap
}
public static let noText = InlqInfoWindow()
}
// ─────────────────────────────────────────────────────────────
// Map Overlay Elements (Matching Flutter SDK)
// ─────────────────────────────────────────────────────────────
public class InlqMarker {
public let markerId: InlqMarkerId
public var position: InlqLatLng
public var alpha: CGFloat
public var anchor: CGPoint
public var isDraggable: Bool
public var isFlat: Bool
public var icon: InlqBitmap
public var infoWindow: InlqInfoWindow
public var rotation: Double
public var isVisible: Bool
public var zIndex: Int32
public var onTap: (() -> Void)?
public init(
markerId: InlqMarkerId,
position: InlqLatLng,
alpha: CGFloat = 1.0,
anchor: CGPoint = CGPoint(x: 0.5, y: 1.0),
isDraggable: Bool = false,
isFlat: Bool = false,
icon: InlqBitmap = .defaultMarker,
infoWindow: InlqInfoWindow = .noText,
rotation: Double = 0.0,
isVisible: Bool = true,
zIndex: Int32 = 0,
onTap: (() -> Void)? = nil
) {
self.markerId = markerId
self.position = position
self.alpha = alpha
self.anchor = anchor
self.isDraggable = isDraggable
self.isFlat = isFlat
self.icon = icon
self.infoWindow = infoWindow
self.rotation = rotation
self.isVisible = isVisible
self.zIndex = zIndex
self.onTap = onTap
}
}
public class InlqPolyline {
public let polylineId: InlqPolylineId
public var points: [InlqLatLng]
public var strokeColor: UIColor
public var strokeWidth: CGFloat
public var isGeodesic: Bool
public var isVisible: Bool
public var zIndex: Int32
public var onTap: (() -> Void)?
public init(
polylineId: InlqPolylineId,
points: [InlqLatLng] = [],
strokeColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 1.0),
strokeWidth: CGFloat = 10.0,
isGeodesic: Bool = true,
isVisible: Bool = true,
zIndex: Int32 = 0,
onTap: (() -> Void)? = nil
) {
self.polylineId = polylineId
self.points = points
self.strokeColor = strokeColor
self.strokeWidth = strokeWidth
self.isGeodesic = isGeodesic
self.isVisible = isVisible
self.zIndex = zIndex
self.onTap = onTap
}
}
public class InlqPolygon {
public let polygonId: InlqPolygonId
public var points: [InlqLatLng]
public var holes: [[InlqLatLng]]
public var strokeColor: UIColor
public var strokeWidth: CGFloat
public var fillColor: UIColor
public var isVisible: Bool
public var zIndex: Int32
public var onTap: (() -> Void)?
public init(
polygonId: InlqPolygonId,
points: [InlqLatLng] = [],
holes: [[InlqLatLng]] = [],
strokeColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 1.0),
strokeWidth: CGFloat = 5.0,
fillColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 0.2),
isVisible: Bool = true,
zIndex: Int32 = 0,
onTap: (() -> Void)? = nil
) {
self.polygonId = polygonId
self.points = points
self.holes = holes
self.strokeColor = strokeColor
self.strokeWidth = strokeWidth
self.fillColor = fillColor
self.isVisible = isVisible
self.zIndex = zIndex
self.onTap = onTap
}
}
public class InlqCircle {
public let circleId: InlqCircleId
public var center: InlqLatLng
public var radiusMeters: Double
public var strokeColor: UIColor
public var strokeWidth: CGFloat
public var fillColor: UIColor
public var isVisible: Bool
public var zIndex: Int32
public var onTap: (() -> Void)?
public init(
circleId: InlqCircleId,
center: InlqLatLng,
radiusMeters: Double,
strokeColor: UIColor = UIColor(red: 0.13, green: 0.77, blue: 0.36, alpha: 1.0),
strokeWidth: CGFloat = 4.0,
fillColor: UIColor = UIColor(red: 0.13, green: 0.77, blue: 0.36, alpha: 0.2),
isVisible: Bool = true,
zIndex: Int32 = 0,
onTap: (() -> Void)? = nil
) {
self.circleId = circleId
self.center = center
self.radiusMeters = radiusMeters
self.strokeColor = strokeColor
self.strokeWidth = strokeWidth
self.fillColor = fillColor
self.isVisible = isVisible
self.zIndex = zIndex
self.onTap = onTap
}
}
@@ -0,0 +1,46 @@
import Foundation
import CoreLocation
/**
* An immutable struct representing a pair of latitude and longitude coordinates.
* Fully compatible with Google Maps iOS SDK and CoreLocation coordinate signatures.
*/
public struct InlqLatLng: Equatable, Hashable, Codable {
public let latitude: Double
public let longitude: Double
public init(latitude: Double, longitude: Double) {
self.latitude = max(-90.0, min(90.0, latitude))
self.longitude = max(-180.0, min(180.0, longitude))
}
public init(coordinate: CLLocationCoordinate2D) {
self.init(latitude: coordinate.latitude, longitude: coordinate.longitude)
}
public var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
/// Calculates spherical distance in meters to another InlqLatLng
public func distance(to other: InlqLatLng) -> Double {
let loc1 = CLLocation(latitude: self.latitude, longitude: self.longitude)
let loc2 = CLLocation(latitude: other.latitude, longitude: other.longitude)
return loc1.distance(from: loc2)
}
/// Calculates initial bearing in degrees to another InlqLatLng
public func bearing(to other: InlqLatLng) -> Double {
let lat1 = self.latitude * .pi / 180.0
let lon1 = self.longitude * .pi / 180.0
let lat2 = other.latitude * .pi / 180.0
let lon2 = other.longitude * .pi / 180.0
let dLon = lon2 - lon1
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
let rad = atan2(y, x)
return (rad * 180.0 / .pi + 360.0).truncatingRemainder(dividingBy: 360.0)
}
}
@@ -0,0 +1,76 @@
import Foundation
import UIKit
/**
* Defines Marker options and overlays for iOS Native.
* Fully compatible with Google Maps SDK (GMSMarker).
*/
public class InlqMarker {
public var position: InlqLatLng
public var title: String?
public var snippet: String?
public var icon: UIImage?
public var isDraggable: Bool = false
public var zIndex: Int32 = 0
public var isVisible: Bool = true
public init(position: InlqLatLng, title: String? = nil, snippet: String? = nil) {
self.position = position
self.title = title
self.snippet = snippet
}
}
/**
* Defines Polyline path options for routes and paths on iOS.
* Fully compatible with Google Maps SDK (GMSPolyline).
*/
public class InlqPolyline {
public var points: [InlqLatLng]
public var strokeColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 1.0)
public var strokeWidth: CGFloat = 4.0
public var isGeodesic: Bool = true
public var zIndex: Int32 = 0
public var isVisible: Bool = true
public init(points: [InlqLatLng] = []) {
self.points = points
}
}
/**
* Defines Polygon options for military sectors and reachability polygons on iOS.
* Fully compatible with Google Maps SDK (GMSPolygon).
*/
public class InlqPolygon {
public var points: [InlqLatLng]
public var holes: [[InlqLatLng]] = []
public var strokeColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 1.0)
public var strokeWidth: CGFloat = 2.0
public var fillColor: UIColor = UIColor(red: 0.0, green: 0.44, blue: 0.89, alpha: 0.2)
public var zIndex: Int32 = 0
public var isVisible: Bool = true
public init(points: [InlqLatLng] = []) {
self.points = points
}
}
/**
* Defines Circle options for accuracy zones and buffer areas on iOS.
* Fully compatible with Google Maps SDK (GMSCircle).
*/
public class InlqCircle {
public var center: InlqLatLng
public var radiusMeters: Double
public var strokeColor: UIColor = UIColor(red: 0.13, green: 0.77, blue: 0.36, alpha: 1.0)
public var strokeWidth: CGFloat = 2.0
public var fillColor: UIColor = UIColor(red: 0.13, green: 0.77, blue: 0.36, alpha: 0.2)
public var zIndex: Int32 = 0
public var isVisible: Bool = true
public init(center: InlqLatLng, radiusMeters: Double) {
self.center = center
self.radiusMeters = radiusMeters
}
}
@@ -0,0 +1,56 @@
import Foundation
public struct InlqOfflineRegionDefinition: Codable {
public let bounds: InlqLatLngBounds
public let minZoom: Double
public let maxZoom: Double
public let styleUrl: String
public init(bounds: InlqLatLngBounds, minZoom: Double, maxZoom: Double, styleUrl: String) {
self.bounds = bounds
self.minZoom = minZoom
self.maxZoom = maxZoom
self.styleUrl = styleUrl
}
}
public struct InlqOfflineRegion: Codable {
public let id: Int64
public let name: String
public let definition: InlqOfflineRegionDefinition
public let completedTiles: Int64
public let totalTiles: Int64
}
/**
* Service for managing offline vector map tile packages in Intaleq Native iOS SDK.
* Mirrors OfflineService from Flutter SDK.
*/
public class IntaleqOfflineService {
public static let shared = IntaleqOfflineService()
private var regions: [Int64: InlqOfflineRegion] = [:]
private init() {}
public func downloadOfflineRegion(
definition: InlqOfflineRegionDefinition,
name: String,
progress: ((Double) -> Void)? = nil
) -> InlqOfflineRegion {
let id = Int64(Date().timeIntervalSince1970 * 1000)
let region = InlqOfflineRegion(id: id, name: name, definition: definition, completedTiles: 100, totalTiles: 100)
regions[id] = region
progress?(1.0)
return region
}
public func getListOfRegions() -> [InlqOfflineRegion] {
return Array(regions.values)
}
public func deleteOfflineRegion(id: Int64) -> Bool {
return regions.removeValue(forKey: id) != nil
}
}
@@ -0,0 +1,152 @@
import Foundation
public struct InlqGeocodingPlace: Codable {
public let id: String
public let name: String
public let nameAr: String?
public let nameEn: String?
public let category: String?
public let city: String?
public let neighbourhood: String?
public let address: String?
public let position: InlqLatLng
}
/**
* Commercial Geocoding & Reverse Geocoding Service calling Intaleq SaaS API on iOS (Swift).
*/
public class IntaleqGeocodingService {
public static let shared = IntaleqGeocodingService()
private init() {}
/**
* Search for places, addresses, and POIs by text query.
*/
public func search(
query: String,
proximity: InlqLatLng? = nil,
limit: Int = 10,
completion: @escaping (Result<[InlqGeocodingPlace], Error>) -> Void
) {
guard let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid query"])))
return
}
var urlStr = "\(IntaleqMaps.shared.baseUrl)/api/geocoding/search?q=\(encoded)&limit=\(limit)"
if let proximity = proximity {
urlStr += "&lat=\(proximity.latitude)&lng=\(proximity.longitude)"
}
guard let url = URL(string: urlStr) else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(IntaleqMaps.shared.apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.success([]))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let features = json["features"] as? [[String: Any]] {
let places: [InlqGeocodingPlace] = features.compactMap { f in
guard let geom = f["geometry"] as? [String: Any],
let coords = geom["coordinates"] as? [Double],
coords.count >= 2 else { return nil }
let props = f["properties"] as? [String: Any] ?? [:]
return InlqGeocodingPlace(
id: f["id"] as? String ?? UUID().uuidString,
name: props["name"] as? String ?? "موقع محدد",
nameAr: props["name_ar"] as? String,
nameEn: props["name_en"] as? String,
category: props["category"] as? String,
city: props["city"] as? String,
neighbourhood: props["neighbourhood"] as? String,
address: props["address"] as? String,
position: InlqLatLng(latitude: coords[1], longitude: coords[0])
)
}
completion(.success(places))
} else {
completion(.success([]))
}
} catch {
completion(.failure(error))
}
}.resume()
}
/**
* Reverse geocode coordinate to get street and neighborhood name.
*/
public func reverseGeocode(
position: InlqLatLng,
completion: @escaping (Result<InlqGeocodingPlace?, Error>) -> Void
) {
let urlStr = "\(IntaleqMaps.shared.baseUrl)/api/geocoding/reverse?lat=\(position.latitude)&lng=\(position.longitude)"
guard let url = URL(string: urlStr) else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(IntaleqMaps.shared.apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.success(nil))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let features = json["features"] as? [[String: Any]], let first = features.first,
let geom = first["geometry"] as? [String: Any],
let coords = geom["coordinates"] as? [Double], coords.count >= 2 {
let props = first["properties"] as? [String: Any] ?? [:]
let place = InlqGeocodingPlace(
id: first["id"] as? String ?? UUID().uuidString,
name: props["name"] as? String ?? "موقع محدد",
nameAr: props["name_ar"] as? String,
nameEn: props["name_en"] as? String,
category: props["category"] as? String,
city: props["city"] as? String,
neighbourhood: props["neighbourhood"] as? String,
address: props["address"] as? String,
position: InlqLatLng(latitude: coords[1], longitude: coords[0])
)
completion(.success(place))
} else {
completion(.success(nil))
}
} catch {
completion(.failure(error))
}
}.resume()
}
}
@@ -0,0 +1,110 @@
import Foundation
public struct InlqRouteStep: Codable {
public let instructionAr: String
public let distanceMeters: Double
public let durationSeconds: Double
public let maneuverSign: Int
public let inclinePercentage: Double
}
public struct InlqCommercialRoute: Codable {
public let coordinates: [InlqLatLng]
public let steps: [InlqRouteStep]
public let totalDistanceMeters: Double
public let totalDurationSeconds: Double
public let elevationGainMeters: Double
public let elevationLossMeters: Double
public let averageInclinePercentage: Double
public let maxInclinePercentage: Double
}
/**
* Commercial Routing Service calling Intaleq SaaS Routing API on iOS (Swift).
*/
public class IntaleqRoutingService {
public static let shared = IntaleqRoutingService()
private init() {}
public func calculateRoute(
origin: InlqLatLng,
destination: InlqLatLng,
profile: String = "car",
locale: String = "ar",
completion: @escaping (Result<InlqCommercialRoute, Error>) -> Void
) {
let urlStr = "\(IntaleqMaps.shared.baseUrl)/api/maps/route?" +
"fromLat=\(origin.latitude)&fromLng=\(origin.longitude)&" +
"toLat=\(destination.latitude)&toLng=\(destination.longitude)&" +
"profile=\(profile)&steps=true&locale=\(locale)"
guard let url = URL(string: urlStr) else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(IntaleqMaps.shared.apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -2, userInfo: [NSLocalizedDescriptionKey: "No data received"])))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let paths = json["paths"] as? [[String: Any]], let firstPath = paths.first {
let distance = firstPath["distance"] as? Double ?? 0.0
let timeMs = firstPath["time"] as? Double ?? 0.0
var coords: [InlqLatLng] = []
if let points = firstPath["points"] as? [String: Any],
let list = points["coordinates"] as? [[Double]] {
coords = list.map { InlqLatLng(latitude: $0[1], longitude: $0[0]) }
}
var steps: [InlqRouteStep] = []
if let instructions = firstPath["instructions"] as? [[String: Any]] {
steps = instructions.map {
InlqRouteStep(
instructionAr: $0["text"] as? String ?? "",
distanceMeters: $0["distance"] as? Double ?? 0.0,
durationSeconds: ($0["time"] as? Double ?? 0.0) / 1000.0,
maneuverSign: $0["sign"] as? Int ?? 0,
inclinePercentage: 0.0
)
}
}
let route = InlqCommercialRoute(
coordinates: coords,
steps: steps,
totalDistanceMeters: distance,
totalDurationSeconds: timeMs / 1000.0,
elevationGainMeters: 0.0,
elevationLossMeters: 0.0,
averageInclinePercentage: 0.0,
maxInclinePercentage: 0.0
)
completion(.success(route))
} else {
completion(.failure(NSError(domain: "IntaleqMaps", code: -3, userInfo: [NSLocalizedDescriptionKey: "Malformed route response"])))
}
} catch {
completion(.failure(error))
}
}.resume()
}
}
@@ -0,0 +1,35 @@
import Foundation
/**
* Predefined map visual styles for the Intaleq Sovereign Map SDK for iOS (Swift).
* Mirrors IntaleqStyles from Flutter SDK.
*/
public struct IntaleqStyles {
public static var dark: String {
return "\(IntaleqMaps.shared.baseUrl)/api/v1/styles/dark.json?key=\(IntaleqMaps.shared.apiKey)"
}
public static var light: String {
return "\(IntaleqMaps.shared.baseUrl)/api/v1/styles/light.json?key=\(IntaleqMaps.shared.apiKey)"
}
public static var normal: String {
return "\(IntaleqMaps.shared.baseUrl)/api/v1/styles/normal.json?key=\(IntaleqMaps.shared.apiKey)"
}
public static var satellite: String {
return "\(IntaleqMaps.shared.baseUrl)/api/v1/styles/satellite.json?key=\(IntaleqMaps.shared.apiKey)"
}
public static var outdoors: String {
return "\(IntaleqMaps.shared.baseUrl)/api/v1/styles/outdoors.json?key=\(IntaleqMaps.shared.apiKey)"
}
}
public enum IntaleqMapType: String, Codable {
case none
case normal
case light
case satellite
case dark
}