You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 11:00:01