golang实现webgis后端开发

news2024/12/26 23:35:59

目录

前言

二、实现步骤

1.postgis数据库和model的绑定

2.将pg库中的要素转换为geojson

(1)几何定义

(2)将wkb解析为几何类型

(3)定义geojson类型

(4)数据转换

(5)数据返回

 2.前端传入的geojson储存到数据库

3、其他功能实现

总结


前言

        停更了接近一个月都在研究一门新语言golang,以及如何利用golang完成一个webgis后端平台的开发。go语言作为一门强类型语言,在web后端开发中目前有高性能、语法简洁、编译速度快、自带高并发的特性,它既没有C/C++这样有着复杂,冗余的语法,又拥有一些弱类型语言的特性,比如自带垃圾回收机制,自带变量类型判断,关系是golang的打包是所有语言中最为先进的,编译器只会打包引入的代码,而不是想java,python一样会将导入的库整体打包。同样一个项目,用go打包出来可能只有几M,而用python打包出来会有几十上百兆。其运行速度和Java不相上下,部分计算还快过java,但是占用的内存比java小太多。不得不说golang是一门伟大的语言,大名鼎鼎的容器dock,yarn都是go语言写出来的。

        但是golang的缺点也很明显,就是生态不够完善,参考资料太少。很多功能都没有现成的需要自己手写一一实现,并且golang的各种类库的作者也是非常随意,各种变量,函数想改就改,让使用者门非常苦恼,只有按个看源码来学习功能。

        这篇文章主要介绍如何用golang结合postgis数据库,使用gin、grom框架实现后端的MVC的接口搭建。


一、整体思路

一个健全的webgis后端必须实现以下几个功能:

1、postgis数据库和model的绑定。

2、如何将pg库中的要素转换为geojson

3、将前端传入的geojson储存到数据库

4、动态矢量瓦片的实现

5、实现地图数据几何分析功能

6、完成各类复杂业务的分析模型

二、实现步骤

1.postgis数据库和model的绑定

参考grom库的官方文档https://gorm.io/zh_CN/docs/create.html

grom是golang对数据库实现orm操作的第三方库,在github上面获得了广泛的好评,目前支持MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB这几种数据库。

首先在项目中创建一个model的包,并在这个包中定义你需要的数据库映射表。我们设计了几个字段,其中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)"`
}

创建一个core.go 存储数据库链接信息,创建一个全局变量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数据库中,几何信息都是通过wkb格式进行存储,所以我们需要将wkb解析为golang中我们可以操作的几何对象,那么在解析wkb之前我们需要定义好全部的几何要素

1、定义点类型,直接用两位浮点切片定义,如果需要z值就三位,我这里目前只需要二维数据就定义的2维。

type Point [2]float64

2、定义线类型,线类型由点类型组成

type LineString []Point

3、定义环类型,该类型主要在存在环岛面的时候使用,环类型的特点就是起点和终点坐标一致

type Ring LineString

4、定义单面类型,该面由环类型切片构成,我们还可以给在该类型中定义一些简单的几何函数

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编码包括两部分:类型及坐标信息, 类型部分用一个字节表示,其中前四位表示几何类型,后四位表示SRID(空间参考系统编号),坐标信息根据不同几何类型分别编码,如点的坐标用x,y两个double类型表示,线的坐标是一系列的点坐标等。以下代码实现了将wkb转换为上面定义的几何要素。因为每个几何类型的解析方式不一样这里我将每种方式单独做成了函数。

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)数据转换

先将grom查询到的数据库对象传递到该函数,通过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)数据返回

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调接口,数据完美返回geojson

 2.前端传入的geojson储存到数据库

这一步其实和取是一样的,只需要把思路反过来,将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
}

完成数据存储

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、其他功能实现

动态矢量瓦片直接用go语言重写我之前博客用python做的那部分即可,至于复杂的地理数据分析可以采用postgis函数实现,复杂的业务分析模块可以直接使用golang调用fme实现,这里就不过多介绍,后期博客会更新相关内容。


总结

golang实在是太COOL了,语法简洁,运行高效,部署简单,打包完美,我愿称之为python之后最好用的语言,唯一的缺陷就是生态还有所欠缺,不过随着开发者们的拥护,我相信golang会有光明的未来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从java直接上手SpringBoot【CTGU启明星】

本文适合刚刚学完java包括面向对象的同学&#xff0c;直接上手SpringBoot教程。 一、写在前面 先说说为什么这样做&#xff0c;现在主流的java后端学习是 面向对象->ssm框架->SpringBoot 其间还穿插数据库的学习&#xff0c;例如MyBatis等。 但是我们认为从项目入手…

来自腾讯AI实验室的Real-ESRGAN将模糊老照片和视频修复成高清晰(一些错误处理)

Real-ESRGAN:Enhanced Super-Resolution GAN&#xff1a;增强的超分辨率的对抗生成网络&#xff0c;对于GAN相信大家都比较熟悉&#xff0c;前有阿尔法狗&#xff0c;现有很多GAN的延伸版本&#xff0c;StyleGAN1~3系列以及DragGAN对于图片的生成和编辑&#xff0c;出来的效果都…

Tomcat顶层架构、服务部署、虚拟主机配置

Tomcat顶层架构、服务部署、虚拟主机配置 一、Tomcat概述1、Tomcat介绍2、Tomcat三个核心组件3、Java Servlet4、JSP 二、Tomcat顶层架构三、Tomcat请求过程四、Tomcat服务部署1、关闭防火墙&#xff0c;将安装 Tomcat 所需软件包传到/opt目录下2、安装JDK3、设置JDK环境变量4、…

Java中导出Excel步骤总结(案例学习)

【辰兮要努力】&#xff1a;hello你好我是辰兮&#xff0c;很高兴你能来阅读&#xff0c;昵称是希望自己能不断精进&#xff0c;向着优秀程序员前行&#xff01; 博客来源于项目以及编程中遇到的问题总结&#xff0c;偶尔会有读书分享&#xff0c;我会陆续更新Java前端、后台、…

怎么把伴奏提取出来?分享两个方法给大家~

对于音乐制作人和音乐爱好者而言&#xff0c;创作个人音乐作品是一项令人兴奋的体验。然而&#xff0c;有时我们希望使用一首现有歌曲的伴奏来创作自己的音乐作品&#xff0c;但却无法找到原版伴奏。为了解决这一难题&#xff0c;现在可以使用记灵在线工具来提取音频伴奏。本文…

瞬息全宇宙:苹果发布虚拟现实头显Vision Pro

WWDC23巅峰时刻 熬夜看了WWDC苹果发布会&#xff0c;传闻了N年的苹果MR&#xff08;增强现实&#xff09;产品&#xff0c;终于来了。 One More Thing&#xff0c;库克说出了这句话&#xff0c;巅峰时刻终于到来&#xff01; 新的交互 名字叫Vision Pro&#xff0c;虽然这名…

《计算机网络——自顶向下方法》精炼——4.1-4.3.0

书读得越多而不假思索&#xff0c;你就会觉得你知道的很多&#xff1b;而当你读书而思考的越多的时候&#xff0c;你就会越清楚的看到&#xff0c;你知道的还很少——伏尔泰 文章目录 概述转发、路由选择网络服务模型 虚电路和数据报网络虚电路网络数据报网络 路由器工作原理 概…

chatgpt赋能python:Python如何出图

Python如何出图 作为一种广泛使用的编程语言&#xff0c;Python不仅仅可以用于数据分析、机器学习等领域&#xff0c;还可以用来生成图像。在这篇文章中&#xff0c;我们将介绍Python如何出图&#xff0c;并将重点放在与SEO相关的方面。 1. Python出图的基本原理 Python有多…

刷题记录:哈希 | leetcode-2352. 相等行列对 2023/6/6

2352. 相等行列对 这题还是非常简单的。如果用模拟的方法&#xff0c;时间复杂度要达到O(n^3)了&#xff0c;感觉不太可。 这回学聪明了&#xff0c;没有一上来就想着暴力模拟。用哈希的办法&#xff0c;可以把时间复杂度降为O(n^2)。 我的思路是先转置矩阵&#xff0c;再用…

Matlab论文插图绘制模板第99期—正负柱状图

在之前的文章中&#xff0c;分享了很多Matlab柱状图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一种特殊的柱状图&#xff1a;正负柱状图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下载…

HTML+JS 实现手机号码归属地查询功能

手机号码归属地 API 是一种提供号码归属地信息的接口&#xff0c;它通过与运营商和电信数据库交互&#xff0c;根据手机号码查询相关归属地信息并返回结果。通过使用手机号码归属地API&#xff0c;开发者可以轻松地集成号码归属地查询功能到他们的应用程序和服务中&#xff0c;…

简介- 谷粒商城项目微服务架构图

目录 项目前置知识一、前后端分离开发&#xff0c;分为 内网部署 和 外网部署。二、用户是通过使用 客户端 来完成各种的功能三、网关的作用四、Sentiel组件五、Feign组件六、OAuth2.0认证中心七、SpringSecurity组件八、关于数据存储的解决方案九、定位bug十、注册中心十一、配…

Java 进阶 -- Lambda 表达式

Lambda Expressions 匿名类&#xff08;anonymous classes&#xff09;的一个问题是&#xff0c;如果匿名类的实现非常简单&#xff0c;比如一个只包含一个方法的接口&#xff0c;那么匿名类的语法可能会显得笨拙和不清晰。在这些情况下&#xff0c;您通常试图将功能作为参数传…

Spark RDD计算总分与平均分

文章目录 一&#xff0c;提出任务二&#xff0c;实现思路三&#xff0c;准备工作1、启动HDFS服务2、启动Spark服务3、在本地创建成绩文件4、将成绩文件上传到HDFS 四&#xff0c;完成任务1、在Spark Shell里完成任务&#xff08;1&#xff09;读取成绩文件&#xff0c;生成RDD&…

从Java BIO到NIO再到多路复用,看这篇就够了

从一次优化说起 近期优化了一个老的网关系统&#xff0c;在dubbo调用接口rt1000ms时吞吐量提升了25倍&#xff0c;而线程数却由64改到8。其他的优化手段不做展开&#xff0c;比较有意思的是为什么线程数减少&#xff0c;吞吐量却可以大幅提升&#xff1f;这就得从IO模型说起&a…

消息队列kafka使用技巧和常见问题

目录 【消息队列概述】 【kafka】 消息丢失问题 消息重复问题 消费顺序问题 消息积压问题 kafka集群部署 【消息队列概述】 消息队列主要解决应用耦合、异步消息、流量削锋等问题&#xff0c;是大型分布式系统不可缺少的中间件。消息生产者 只管把消息发布到 MQ 中而不…

【CMake 入门与进阶(4)】 CMakeLists.txt 语法规则基础及部分常用指令-续(附使用代码)

由于篇幅问题本篇接着上文继续介绍 CMakeLists.txt 语法规则基础及常用指令。 aux_source_directory aux_source_directory 命令会查找目录中的所有源文件&#xff0c;其命令定义如下&#xff1a; aux_source_directory(<dir> <variable>)从指定的目录中查找所有…

开发者工具调试

Console控制台 F12打开控制台 选择其他tab面板时&#xff0c;ESC打开Console面板enter直接执行Console的代码&#xff0c;shiftEnter输入多行代码 Source面板 左键单机行号设置断点&#xff0c;或在代码中添加debugger;右键单机行号设置条件断点&#xff08;条件表达式为tr…

PowerShell install 一键部署mariadb10.11

mariadb MariaDB数据库管理系统是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 MariaDB的目的是完全兼容MySQL&#xff0c;包括API和命令行&#xff0c;使之能轻松成为MySQL的代替品。在存储引擎方面&#xff0c;使用XtraDB来代替MySQL的Inno…

ChatGPT 国内镜像网站大全(含GPT-4.0版本)之什么年代还在写传统文章。

前言&#xff1a; 临近期末&#xff0c;大量水课的节课作业都是论文&#xff0c;一篇就是几千字&#xff0c;这对于还要复习专业课的我们可以说是压力巨大&#xff1a;心理健康论文&#xff0c;安全教育论文&#xff0c;大学语文论文&#xff0c;书法赏析论文&#xff0c;劳动…