golang實現(xiàn)webgis后端開發(fā)的步驟詳解
前言
最近都在研究一門新語言golang,以及如何利用golang完成一個webgis后端平臺的開發(fā)。go語言作為一門強類型語言,在web后端開發(fā)中目前有高性能、語法簡潔、編譯速度快、自帶高并發(fā)的特性,它既沒有C/C++這樣有著復雜,冗余的語法,又擁有一些弱類型語言的特性,比如自帶垃圾回收機制,自帶變量類型判斷,關系是golang的打包是所有語言中最為先進的,編譯器只會打包引入的代碼,而不是想java,python一樣會將導入的庫整體打包。同樣一個項目,用go打包出來可能只有幾M,而用python打包出來會有幾十上百兆。其運行速度和Java不相上下,部分計算還快過java,但是占用的內存比java小太多。不得不說golang是一門偉大的語言,大名鼎鼎的容器dock,yarn都是go語言寫出來的。
但是golang的缺點也很明顯,就是生態(tài)不夠完善,參考資料太少。很多功能都沒有現(xiàn)成的需要自己手寫一一實現(xiàn),并且golang的各種類庫的作者也是非常隨意,各種變量,函數(shù)想改就改,讓使用者門非??鄲溃挥邪磦€看源碼來學習功能。
一、整體思路
一個健全的webgis后端必須實現(xiàn)以下幾個功能:
1、postgis數(shù)據(jù)庫和model的綁定。
2、如何將pg庫中的要素轉換為geojson
3、將前端傳入的geojson儲存到數(shù)據(jù)庫
4、動態(tài)矢量瓦片的實現(xiàn)
5、實現(xiàn)地圖數(shù)據(jù)幾何分析功能
6、完成各類復雜業(yè)務的分析模型
二、實現(xiàn)步驟
1.postgis數(shù)據(jù)庫和model的綁定
參考grom庫的官方文檔https://gorm.io/zh_CN/docs/create.html
grom是golang對數(shù)據(jù)庫實現(xiàn)orm操作的第三方庫,在github上面獲得了廣泛的好評,目前支持MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB這幾種數(shù)據(jù)庫。
首先在項目中創(chuàng)建一個model的包,并在這個包中定義你需要的數(shù)據(jù)庫映射表。我們設計了幾個字段,其中geom為幾何要素字段,MultiPolygon為幾何要素類型,4326為坐標系
package models type MyTable struct { ID uint `gorm:"primary_key"` Name string `gorm:"type:varchar(255)"` Bh string `gorm:"type:varchar(255)"` Geom string `gorm:"type:geometry(MultiPolygon,4326)"` }
創(chuàng)建一個core.go 存儲數(shù)據(jù)庫鏈接信息,創(chuàng)建一個全局變量DB,注意在go中全局變量首字母名必須為大寫
package models import ( "fmt" "gorm.io/driver/postgres" "gorm.io/gorm" ) var DB *gorm.DB var err error func init() { dsn := "host=localhost user=postgres password=1 dbname=gotest port=5432 sslmode=disable TimeZone=Asia/Shanghai" DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { fmt.Println(err) } }
2.將pg庫中的要素轉換為geojson
(1)幾何定義
在pg數(shù)據(jù)庫中,幾何信息都是通過wkb格式進行存儲,所以我們需要將wkb解析為golang中我們可以操作的幾何對象,那么在解析wkb之前我們需要定義好全部的幾何要素
1、定義點類型,直接用兩位浮點切片定義,如果需要z值就三位,我這里目前只需要二維數(shù)據(jù)就定義的2維。
type Point [2]float64
2、定義線類型,線類型由點類型組成
type LineString []Point
3、定義環(huán)類型,該類型主要在存在環(huán)島面的時候使用,環(huán)類型的特點就是起點和終點坐標一致
type Ring LineString
4、定義單面類型,該面由環(huán)類型切片構成,我們還可以給在該類型中定義一些簡單的幾何函數(shù)
type Polygon []Ring //判斷兩個面是否相等 func (p Polygon) Equal(polygon Polygon) bool { if len(p) != len(polygon) { return false } for i := range p { if !p[i].Equal(polygon[i]) { return false } } return true } //復制面 func (p Polygon) Clone() Polygon { if p == nil { return p } np := make(Polygon, 0, len(p)) for _, r := range p { np = append(np, r.Clone()) } return np }
5、定義聚合面MultiPolygon,該要素又面的切片構成
type MultiPolygon []Polygon
6、做一個幾何類,整合所有幾何要素,在golang中這個被叫做接口
type Geometry interface { GeoJSONType() string Dimensions() int Bound() Bound }
(2)將wkb解析為幾何類型
wkb作為一種開源的二進制格式,存儲幾何信息具備高性能,占用空間小等特點。WKB編碼包括兩部分:類型及坐標信息, 類型部分用一個字節(jié)表示,其中前四位表示幾何類型,后四位表示SRID(空間參考系統(tǒng)編號),坐標信息根據(jù)不同幾何類型分別編碼,如點的坐標用x,y兩個double類型表示,線的坐標是一系列的點坐標等。以下代碼實現(xiàn)了將wkb轉換為上面定義的幾何要素。因為每個幾何類型的解析方式不一樣這里我將每種方式單獨做成了函數(shù)。
func Unmarshal(data []byte) (Geometry, int, error) { order, typ, srid, geomData, err := unmarshalByteOrderType(data) if err != nil { return nil, 0, err } var g Geometry switch typ { case pointType: g, err = unmarshalPoint(order, geomData) case multiPointType: g, err = unmarshalMultiPoint(order, geomData) case lineStringType: g, err = unmarshalLineString(order, geomData) case multiLineStringType: g, err = unmarshalMultiLineString(order, geomData) case polygonType: g, err = unmarshalPolygon(order, geomData) case multiPolygonType: g, err = unmarshalMultiPolygon(order, geomData) case geometryCollectionType: g, _, err := NewDecoder(bytes.NewReader(data)).Decode() if err == io.EOF || err == io.ErrUnexpectedEOF { return nil, 0, ErrNotWKB } return g, srid, err default: return nil, 0, ErrUnsupportedGeometry } if err != nil { return nil, 0, err } return g, srid, nil }
以下是解析MultiPolygon的代碼
func readMultiPolygon(r io.Reader, order byteOrder, buf []byte) (geo.MultiPolygon, error) { num, err := readUint32(r, order, buf[:4]) if err != nil { return nil, err } alloc := num if alloc > MaxMultiAlloc { alloc = MaxMultiAlloc } result := make(orb.MultiPolygon, 0, alloc) for i := 0; i < int(num); i++ { pOrder, typ, _, err := readByteOrderType(r, buf) if err != nil { return nil, err } if typ != polygonType { return nil, errors.New("面要素錯誤") } p, err := readPolygon(r, pOrder, buf) if err != nil { return nil, err } result = append(result, p) } return result, nil }
(3)定義geojson類型
先定義幾種基礎的結構體
//定義Feature 結構體 type Feature struct { ID interface{} `json:"id,omitempty"` Type string `json:"type"` BBox BBox `json:"bbox,omitempty"` Geometry geo.Geometry `json:"geometry"` //這里為上一步定義的幾何類型 Properties Properties `json:"properties"` //這里為空map類型 } //定義FeatureCollection 結構體 type FeatureCollection struct { Type string `json:"type"` BBox BBox `json:"bbox,omitempty"` Features []*Feature `json:"features"` }
(4)數(shù)據(jù)轉換
先將grom查詢到的數(shù)據(jù)庫對象傳遞到該函數(shù),通過reflect映射字段,再將字段信息轉換為properties的map對象,最后組裝geojson返回
func Makegeojson(myTables []models.MyTable) interface{} { var FeaturesList []*geojson.Feature FeaturesList = []*geojson.Feature{} for _, t := range myTables { properties := make(map[string]interface{}) v := reflect.ValueOf(t) tt := reflect.TypeOf(t) for i := 0; i < v.NumField(); i++ { if tt.Field(i).Name != "Geom" { properties[strings.ToLower(tt.Field(i).Name)] = v.Field(i).Interface() } } wkbBytes, _ := hex.DecodeString(strings.Trim(t.Geom, " ")) geom, _ := wkb.Unmarshal(wkbBytes) feature := geojson.NewFeature(geom) feature.Properties = properties FeaturesList = append(FeaturesList, feature) } features := geojson.NewFeatureCollection() features.Features = FeaturesList GeoJSON, _ := json.Marshal(features) var obj interface{} json.Unmarshal(GeoJSON, &obj) return obj }
(5)數(shù)據(jù)返回
type UserController struct{} func (uc *UserController) OutGeo(c *gin.Context) { name := c.PostForm("name") var mytable []models.MyTable DB := models.DB DB.Where("Name = ?", name).Find(&mytable) data := methods.Makegeojson(mytable) c.JSON(http.StatusOK, data) }
通過postman調接口,數(shù)據(jù)完美返回geojson
2.前端傳入的geojson儲存到數(shù)據(jù)庫
這一步其實和取是一樣的,只需要把思路反過來,將geojson解析為我們定義的幾何結構,然后再將幾何結構解析成wkb。直接上代碼。
幾何要素轉換為wkb
func Marshal(geom geo.Geometry, byteOrder ...binary.ByteOrder) ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0, wkbcommon.GeomLength(geom, false))) e := NewEncoder(buf) if len(byteOrder) > 0 { e.SetByteOrder(byteOrder[0]) } err := e.Encode(geom) if err != nil { return nil, err } if buf.Len() == 0 { return nil, nil } return buf.Bytes(), nil } func GeoJsonToWKB(geo geojson.Feature) string { TempWkb, _ := wkb.Marshal(geo.Geometry) WkbHex := hex.EncodeToString(TempWkb) return WkbHex }
完成數(shù)據(jù)存儲
func (uc *UserController) InGeo(c *gin.Context) { var jsonData geojson.FeatureCollection c.BindJSON(&jsonData) DB := models.DB for _, t := range jsonData.Features { wkb_result := methods.GeoJsonToWKB(*t) DB.Model(models.MyTable{}).Create(map[string]interface{}{ "Bh": t.Properties["bh"], "Name": t.Properties["name"], "geom": clause.Expr{SQL: "ST_GeomFromWKB(decode(?, 'hex'))", Vars: []interface{}{wkb_result}}, }) } c.JSON(http.StatusOK, "ok") }
至于更新功能也是一樣的,幾何更新只需要更新geom字段就行了。
3、其他功能實現(xiàn)
動態(tài)矢量瓦片直接用go語言重寫我之前博客用python做的那部分即可,至于復雜的地理數(shù)據(jù)分析可以采用postgis函數(shù)實現(xiàn),復雜的業(yè)務分析模塊可以直接使用golang調用fme實現(xiàn),這里就不過多介紹,后期博客會更新相關內容。
總結
golang實在是太COOL了,語法簡潔,運行高效,部署簡單,打包完美,我愿稱之為python之后最好用的語言,唯一的缺陷就是生態(tài)還有所欠缺,不過隨著開發(fā)者們的擁護,我相信golang會有光明的未來。
以上就是golang實現(xiàn)webgis后端開發(fā)的步驟詳解的詳細內容,更多關于golang實現(xiàn)webgis后端開發(fā)的資料請關注腳本之家其它相關文章!
相關文章
Golang使用gob實現(xiàn)結構體的序列化過程詳解
Golang struct類型數(shù)據(jù)序列化用于網(wǎng)絡傳輸數(shù)據(jù)或在磁盤上寫入數(shù)據(jù)。在分布式系統(tǒng)中,一端生成數(shù)據(jù)、然后序列化、壓縮和發(fā)送;在另一端,接收數(shù)據(jù)、然后解壓縮、反序列化和處理數(shù)據(jù),整個過程必須快速有效2023-03-03GoFrame代碼優(yōu)化gconv類型轉換避免重復定義map
這篇文章主要為大家介紹了GoFrame代碼優(yōu)化gconv類型轉換避免重復定義map示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06