Vapor4中使用PostGIS实现经纬度范围查询Pers模型的相关问题咨询
问题1:请求方式选择
- 优先选GET:你的场景是纯查询操作,没有服务端资源修改,参数只有经纬度两个简单值,放在URL Query参数中即可,天然支持缓存、书签保存,更符合REST规范。
- 仅当存在以下情况时选POST:后续需要传递大量复杂过滤参数、不希望经纬度出现在服务端访问日志中、需要兼容前端跨域复杂请求场景。
问题2:PostGIS适配步骤
第一步:添加依赖
首先确认你已经在Package.swift中引入了FluentPostGIS依赖,这是Fluent封装的PostGIS交互库。
第二步:修改Pers模型
你需要在模型中新增PostGIS对应的地理位置存储字段,不需要删除已有的经纬度字段:
import Foundation import Fluent import Vapor import FluentPostgresDriver import FluentPostGIS // 新增引入 final class Pers:Model,Content{ static let schema = "people" @ID(key: .id) var id:UUID? @Field(key: "姓名") var name: String @Field(key: "IG帳號") var account: String @Field(key: "頭像") var picture: String @Field(key: "年紀") var age: String @Field(key: "生日") var birth: String @Field(key:"緯度") var latitude: Double @Field(key:"經度") var longitude: Double // 新增PostGIS地理位置字段 @Field(key: "location") var location: GeometricPoint2D @Field(key: "居住城市") var city: String @Field(key: "興趣") var hobby : String @Parent(key: "user_id") var user: User init(){} init(id:UUID?=nil, name:String, account:String, picture:String ,age:String, birth:String,latitude: Double, longitude: Double, city:String, hobby:String, userId:UUID){ self.id=id self.name=name self.account=account self.picture=picture self.age=age self.birth=birth self.latitude = latitude self.longitude = longitude // 初始化时自动生成location字段 self.location = GeometricPoint2D(x: longitude, y: latitude) self.city=city self.hobby=hobby self.$user.id=userId } }
第三步:新增数据库迁移
你需要写迁移脚本给people表新增location字段,并且用已有的经纬度字段初始化历史数据的location值:
struct AddLocationToPers: AsyncMigration { func prepare(on database: Database) async throws { try await database.schema(Pers.schema) .field("location", .geographyPoint2D) // 对应PostGIS的Point类型 .update() // 初始化历史数据的location值 try await Pers.query(on: database).all().forEach { pers in pers.location = GeometricPoint2D(x: pers.longitude, y: pers.latitude) try await pers.save(on: database) } } func revert(on database: Database) async throws { try await database.schema(Pers.schema) .deleteField("location") .update() } }
第四步:参数传递说明
不需要在接口中传递GeometricPoint2D类型,这个类型是服务端和数据库交互用的内部类型。你可以:
- 如果用GET请求:接口调用方传
lat和lng两个Query参数,你在控制器中用try req.query.get(Double.self, at: "lat")和try req.query.get(Double.self, at: "lng")拿到值 - 如果用POST请求:可以定义单独的请求结构体,只包含lat和lng两个字段,不需要用整个Pers模型解码,更轻量化:
struct LocationQueryRequest: Content { let lat: Double let lng: Double }
问题3:坐标转换逻辑验证
你写的坐标转换逻辑是正确的,GeometricPoint2D的x参数对应经度、y参数对应纬度,完全符合PostGIS的Point坐标顺序要求。
优化后的控制器查询代码
import FluentPostGIS final class InformationController{ // PostGIS版本查询,性能远高于内存过滤 func gis (_ req:Request) throws -> EventLoopFuture<[Pers]> { // 如果用POST请求解码自定义结构体 let query = try req.content.decode(LocationQueryRequest.self) // 如果用GET请求就用下面的代码拿参数 // let lat = try req.query.get(Double.self, at: "lat") // let lng = try req.query.get(Double.self, at: "lng") let searchLocation = GeometricPoint2D(x: query.lng, y: query.lat) // 1000的单位是米,对应PostGIS的距离计算单位 return Pers.query(on: req.db) .filterGeometryDistanceWithin(\.$location, searchLocation, 1000) .all() } // 如果你暂时不想用PostGIS,修复你原有的calculate2方法也可以用(仅适合小数据量场景) func calculate2 ( req:Request) throws -> EventLoopFuture<[Pers]> { let userlocation = try req.content.decode(LocationQueryRequest.self) return Pers.query(on: req.db).all().map{ users in // 过滤出距离小于100米的用户 users.filter { self.getDistance( lat1: $0.latitude, lng1: $0.longitude, lat2: userlocation.lat, lng2: userlocation.lng ) < 100 } } } // 原有的距离计算方法,已修复EARTH_RADIUS拼写错误 func radian(d:Double) -> Double { return d * Double.pi/180.0 } func getDistance(lat1:Double,lng1:Double,lat2:Double,lng2:Double) -> Double { let EARTH_RADIUS:Double = 6378137.0 let radLat1:Double = self.radian(d: lat1) let radLat2:Double = self.radian(d: lat2) let radLng1:Double = self.radian(d: lng1) let radLng2:Double = self.radian(d: lng2) let a:Double = radLat1 - radLat2 let b:Double = radLng1 - radLng2 var s:Double = 2 * asin(sqrt(pow(sin(a/2), 2) + cos(radLat1) * cos(radLat2) * pow(sin(b/2), 2))) s = s * EARTH_RADIUS return s } }
内容的提问来源于stack exchange,提问作者HBN
相关产品推荐
相关产品推荐

