如何使用entgo与pgx存储PostGIS Point类型数据?
解决EntGo + PGX 保存PostGIS Point类型数据的问题
问题原因
你用pgtype.Point对应PostGIS的geometry(point,4326)字段时出错,核心原因是两者序列化格式不兼容:
- PostgreSQL原生的
pgtype.Point会被序列化为(x,y)格式的字符串 - PostGIS的
geometry类型需要符合OGC标准的WKT(如SRID=4326;POINT(x y))或EWKB格式,无法解析原生point的格式,因此抛出invalid geometry错误。
解决方案
方案一:自定义类型适配pgtype.Point
通过自定义类型实现PGX的编解码接口,将pgtype.Point转换为PostGIS可识别的格式:
import ( "context" "fmt" "github.com/jackc/pgx/v5/pgtype" ) // PostGISPoint 包装pgtype.Point,实现PostGIS兼容的编解码 type PostGISPoint struct { pgtype.Point } // Encode 将PostGISPoint编码为PostGIS支持的WKT格式 func (p PostGISPoint) Encode(ctx context.Context, ci *pgtype.ConnInfo, buf []byte) ([]byte, error) { if !p.Point.Status.Present { return append(buf, 'n', 'u', 'l', 'l'), nil } // 生成带SRID的WKT字符串 wkt := fmt.Sprintf("SRID=4326;POINT(%f %f)", p.Point.X, p.Point.Y) return append(buf, '\'', []byte(wkt)..., '\''), nil } // Decode 从PostGIS的geometry格式解码为PostGISPoint func (p *PostGISPoint) Decode(ctx context.Context, ci *pgtype.ConnInfo, data []byte, format int16) error { if string(data) == "null" { p.Point.Status = pgtype.Null return nil } // 解析带SRID的WKT字符串(简化实现,生产环境建议用专业库解析) var srid int var x, y float64 _, err := fmt.Sscanf(string(data), "'SRID=%d;POINT(%f %f)'", &srid, &x, &y) if err != nil { return err } p.Point = pgtype.Point{ X: x, Y: y, Status: pgtype.Present, } return nil }
修改Ent Schema,使用自定义类型:
field.Other("location", &PostGISPoint{}). SchemaType(map[string]string{ dialect.Postgres: "geometry(point, 4326)", }). Optional(),
方案二:使用go-geom库(推荐)
go-geom是专门处理地理几何类型的Go库,原生支持PostGIS格式,兼容性更好:
- 安装依赖:
go get github.com/twpayne/go-geom go get github.com/twpayne/go-geom/pgx
- 修改Ent Schema,使用
geom.Point类型:
import ( "github.com/twpayne/go-geom" "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/schema/field" "entgo.io/ent/schema/annotation" ) // Fields of the Event. func (EventSchema) Fields() []ent.Field { return []ent.Field{ field.UUID("id", uuid.UUID{}).Immutable(). Annotations(&annotation.EntSQL{ Default: "gen_random_uuid()", }), field.Other("location", geom.NewPoint(geom.XY)). SchemaType(map[string]string{ dialect.Postgres: "geometry(point, 4326)", }). Optional(), } }
- 初始化PGX连接时注册go-geom编解码器:
import ( "context" "github.com/jackc/pgx/v5" "github.com/twpayne/go-geom/pgx/geompgx" "entgo.io/ent/dialect/sql" ) func main() { // 建立PGX连接 conn, err := pgx.Connect(context.Background(), "postgres://user:password@host:port/dbname") if err != nil { panic(err) } defer conn.Close(context.Background()) // 注册go-geom的PGX编解码器 geompgx.Register(conn.ConnInfo()) // 初始化Ent客户端 client, err := ent.Open("postgres", "postgres://user:password@host:port/dbname", ent.Driver(sql.OpenDB("postgres", conn))) if err != nil { panic(err) } defer client.Close() }
- CRUD示例:
// 创建带坐标的Point(SRID=4326) point := geom.NewPoint(geom.XY).MustSetCoords([]float64{116.397428, 39.90923}) point.SetSRID(4326) // 创建Event event, err := client.Event.Create().SetLocation(point).Save(context.Background()) if err != nil { panic(err) } // 查询并获取坐标 event, err = client.Event.Get(context.Background(), event.ID) if err != nil { panic(err) } loc := event.Location.(*geom.Point) fmt.Println("坐标:", loc.Coords()) fmt.Println("SRID:", loc.SRID())
内容的提问来源于stack exchange,提问作者stoniemahonie
相关产品推荐
相关产品推荐

