// // Models.swift // Runner // // Created by Groot on 2025/5/9. // import Foundation import MapKit import ObjectiveC extension Decodable { static func fromJson(json: Any) -> Self? { do { let data = try JSONSerialization.data(withJSONObject: json, options: []) return try JSONDecoder().decode(Self.self, from: data) } catch { print(error.localizedDescription) return nil } } } extension Encodable { func toJson() -> [String: Any] { guard let data = try? JSONEncoder().encode(self) else { return [:] } return try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] } } struct ATMapCameraPosition: Codable { var latitude: CGFloat var longitude: CGFloat // 地图缩放比例,对应span var zoom: CGFloat } struct ATMapPadding: Codable { var top: CGFloat var left: CGFloat var bottom: CGFloat var right: CGFloat var padding: UIEdgeInsets { return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) } } // 定义关联对象的键 private var associatedLineIdKey = "associatedLineIdKey" private var associatedLineTypeKey = "associatedLineTypeKey" private var associatedColorKey = "associatedColorKey" private var associatedWidthKey = "associatedWidthKey" extension MKPolyline { // 关联 lineId 属性 var associatedLineId: String? { get { return objc_getAssociatedObject(self, &associatedLineIdKey) as? String } set { objc_setAssociatedObject(self, &associatedLineIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } // 关联 lineType 属性 var associatedLineType: String? { get { return objc_getAssociatedObject(self, &associatedLineTypeKey) as? String } set { objc_setAssociatedObject(self, &associatedLineTypeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } // 关联 color 属性 var associatedColor: String? { get { return objc_getAssociatedObject(self, &associatedColorKey) as? String } set { objc_setAssociatedObject(self, &associatedColorKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } // 关联 width 属性 var associatedWidth: Double? { get { return objc_getAssociatedObject(self, &associatedWidthKey) as? Double } set { objc_setAssociatedObject(self, &associatedWidthKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } class ATMapPolyline: NSObject, Codable { var id: String = UUID().uuidString var lineId: String // 新增:用于记录的线ID var lineType: String // 新增:用于记录线的样式 normal error selected color var points: [CLLocationCoordinate2D] var mapPadding: ATMapPadding? var color : String ///轨迹的颜色 var width: Double /// 轨迹的宽度 var polyline: MKPolyline { return MKPolyline(coordinates: points, count: points.count) } enum CodingKeys: String, CodingKey { case lineId, lineType, points, mapPadding,color,width } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.lineId) { lineId = try container.decodeIfPresent(String.self, forKey: .lineId) ?? "" } else { lineId = "" } if container.contains(.lineType) { lineType = try container.decodeIfPresent(String.self, forKey: .lineType) ?? "" } else { lineType = ""; } if container.contains(.points) { points = try container.decodeIfPresent([CLLocationCoordinate2D].self, forKey: .points) ?? [CLLocationCoordinate2D]() } else { points = [CLLocationCoordinate2D]() } if container.contains(.color) { color = try container.decodeIfPresent(String.self, forKey: .color) ?? "" } else { color = "" } if container.contains(.width) { width = try container.decodeIfPresent(Double.self, forKey: .width) ?? 0 } else { width = 0 } if container.contains(.mapPadding) { mapPadding = try container.decodeIfPresent(ATMapPadding.self, forKey: .mapPadding) ?? ATMapPadding(top: 0, left: 0, bottom: 0, right: 0) } else { mapPadding = ATMapPadding(top: 0, left: 0, bottom: 0, right: 0) } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(lineId, forKey: .lineId) try container.encode(lineType, forKey: .lineType) try container.encode(points, forKey: .points) try container.encodeIfPresent(mapPadding, forKey: .mapPadding) } // 初始化方法 init(lineId: String, lineType: String, points: [CLLocationCoordinate2D], mapPadding: ATMapPadding? = nil,color : String,width : Double) { self.lineId = lineId self.lineType = lineType self.points = points self.mapPadding = mapPadding self.color = color self.width = width super.init() } } class ATMapMarker: NSObject, Codable { var id: String var markerId: String var markerName: String? var customAvatarUrl : String? var latitude: CGFloat var longitude: CGFloat var isSelected: Bool var markerType: any MapMarkerSupportType var tags: [String : String]? init(id: String, markerId: String,markerName: String?, customAvatarUrl: String?,location: CLLocationCoordinate2D, markerType: any MapMarkerSupportType, isSelected: Bool = false,tags: [String : String]?) { self.id = id self.markerId = markerId self.markerName = markerName self.customAvatarUrl = customAvatarUrl self.latitude = location.latitude self.longitude = location.longitude self.markerType = markerType self.isSelected = isSelected self.tags = tags } func update(coordinate: CLLocationCoordinate2D, name: String? = nil, isSelect: Bool) { self.latitude = coordinate.latitude self.longitude = coordinate.longitude self.markerName = name self.isSelected = isSelect } enum CodingKeys: String, CodingKey { case id,markerId,markerName,customAvatarUrl, latitude, longitude, isSelected, markerType,tags } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) if id.isEmpty { id = "user_location" } if container.contains(.markerName) { markerName = try container.decodeIfPresent(String.self, forKey: .markerName) ?? "" } else { markerName = "" } if container.contains(.customAvatarUrl) { customAvatarUrl = try container.decodeIfPresent(String.self, forKey: .customAvatarUrl) ?? "" } else { customAvatarUrl = "" } if container.contains(.latitude) { latitude = try container.decodeIfPresent(CGFloat.self, forKey: .latitude) ?? 0 } else { latitude = 0 } if container.contains(.longitude) { longitude = try container.decodeIfPresent(CGFloat.self, forKey: .longitude) ?? 0 } else { longitude = 0 } if container.contains(.isSelected) { isSelected = try container.decodeIfPresent(Bool.self, forKey: .isSelected) ?? false } else { isSelected = false } // 使用工厂方法创建正确类型的markerType if container.contains(.markerType) { let rawValue = try container.decodeIfPresent(Int.self, forKey: .markerType) ?? 1 markerType = MarkerTypeFactory.markerType(from: rawValue) } else { markerType = MarkerTypeFactory.markerType(from: 1) } ///气泡内容 // 处理tags为空的情况 if container.contains(.tags) { tags = try container.decodeIfPresent([String: String].self, forKey: .tags) ?? [:] } else { tags = [:] // 默认为空字典 } if container.contains(.markerId) { markerId = try container.decodeIfPresent(String.self, forKey: .markerId) ?? "" } else { markerId = "" } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(markerName, forKey: .markerName) try container.encode(customAvatarUrl, forKey: .customAvatarUrl) try container.encode(latitude, forKey: .latitude) try container.encode(longitude, forKey: .longitude) try container.encode(isSelected, forKey: .isSelected) try container.encode(markerType.rawValue, forKey: .markerType) try container.encode(tags, forKey: .tags) try container.encode(markerId, forKey: .markerId) } } extension ATMapMarker: MKAnnotation { var coordinate: CLLocationCoordinate2D { return CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } } extension CLLocationCoordinate2D: Codable { enum CodingKeys: String, CodingKey { case latitude case longitude } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let latitude = try container.decode(Double.self, forKey: .latitude) let longitude = try container.decode(Double.self, forKey: .longitude) self.init(latitude: latitude, longitude: longitude) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(latitude, forKey: .latitude) try container.encode(longitude, forKey: .longitude) } } class ATMapLocation: NSObject, Codable { var latitude: CGFloat? var longitude: CGFloat? var address: String? var errorCode: Int = 0 var time: Int? var bearing: CGFloat? var speed: CGFloat? init(latitude: CGFloat? = nil, longitude: CGFloat? = nil, address: String? = nil, time: Int? = nil, bearing: CGFloat? = nil, speed: CGFloat? = nil) { self.latitude = latitude self.longitude = longitude self.address = address self.time = time self.bearing = bearing self.speed = speed super.init() } static func fromLocation(location: CLLocation) -> ATMapLocation { return ATMapLocation( latitude: CGFloat(location.coordinate.latitude), longitude: CGFloat(location.coordinate.longitude), address: nil, time: Int(location.timestamp.timeIntervalSince1970 * 1000), bearing: CGFloat(location.course), speed: CGFloat(location.speed) ) } }