Swift如何从MariaDB的POINT类型字段提取经纬度?
解决MariaDB POINT类型字段的Swift读取问题
方法一:SQL查询时直接提取经纬度(推荐)
MariaDB提供ST_X()和ST_Y()函数可直接从POINT类型中提取经度(X值)和纬度(Y值),无需在Swift端处理二进制数据,实现成本最低。
示例代码:
假设你使用Fluent MySQL驱动,可通过原生SQL查询获取解析后的经纬度:
// 定义包含经纬度的临时结构体 struct UserWithLocation: Decodable { var id: UUID? var name: String var longitude: Double // 对应ST_X(location) var latitude: Double // 对应ST_Y(location) } // 执行查询 let users = try await User.query(on: db) .raw("SELECT *, ST_X(location) as longitude, ST_Y(location) as latitude FROM users") .decode(UserWithLocation.self)
方法二:Swift端解码POINT二进制数据
如果需要直接处理POINT类型的原始空间数据,可以自定义类型并实现解码逻辑,解析MySQL的WKB(Well-Known Binary)格式POINT数据。
步骤1:定义Point结构体并实现解码
struct Point: Codable { let latitude: Double let longitude: Double init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let data = try container.decode(Data.self) // 验证POINT数据长度:字节序(1) + 类型码(4) + X(8) + Y(8) = 21字节 guard data.count == 21 else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "无效的POINT数据长度") } let byteOrder = data[0] // 读取类型码(POINT类型的编码为1) let type = data.subdata(in: 1..<5).withUnsafeBytes { $0.load(as: UInt32.self) } guard type == 1 else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "不是POINT类型数据") } // 提取经度(X)和纬度(Y)的字节数据 let xBytes = data.subdata(in: 5..<13) let yBytes = data.subdata(in: 13..<21) // 根据字节序转换为Double值 if byteOrder == 0x00 { // 小端字节序 self.longitude = xBytes.reversed().withUnsafeBytes { $0.load(as: Double.self) } self.latitude = yBytes.reversed().withUnsafeBytes { $0.load(as: Double.self) } } else { // 大端字节序 self.longitude = xBytes.withUnsafeBytes { $0.load(as: Double.self) } self.latitude = yBytes.withUnsafeBytes { $0.load(as: Double.self) } } } }
步骤2:在模型中使用自定义Point类型
如果使用Fluent,需要让Point实现MySQLDataConvertible协议以适配驱动:
extension Point: MySQLDataConvertible { var mysqlData: MySQLData { // 若需要将Point编码回数据库,可在此实现逻辑,仅读取时可暂时抛出错误 fatalError("编码逻辑未实现") } static func fromMySQLData(_ data: MySQLData) throws -> Self { guard case .geometry(let geometryData) = data.storage else { throw DecodingError.dataCorruptedError(forKey: .init(stringValue: "location"), in: nil, debugDescription: "不是GEOMETRY类型数据") } return try JSONDecoder().decode(Self.self, from: geometryData) } } // 在模型中定义字段 final class User: Model { static let schema = "users" @ID(key: .id) var id: UUID? @Field(key: "name") var name: String @Field(key: "location") var location: Point? }
错误原因说明
你之前直接用String类型读取POINT字段失败,是因为MariaDB的POINT类型存储的是二进制GEOMETRY数据,无法直接解码为字符串类型,必须通过上述两种方法处理。
内容的提问来源于stack exchange,提问作者Andrey
相关产品推荐
相关产品推荐

