feat: Add Intaleq Native Android Kotlin & iOS Swift SDKs, Incline slope routing, Topographic contours and modular tactical app
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user