先了解幾個(gè)基本概念奶赔,有助于了解本文應(yīng)用場(chǎng)景
- 地理信息系統(tǒng)GIS-百度百科
地理信息系統(tǒng)(Geographic Information System或 Geo-Information system,GIS)有時(shí)又稱(chēng)為“地學(xué)信息系統(tǒng)”。它是一種特定的十分重要的空間信息系統(tǒng)。它是在計(jì)算機(jī)硬、軟件系統(tǒng)支持下,對(duì)整個(gè)或部分地球表層(包括大氣層)空間中的有關(guān)地理分布數(shù)據(jù)進(jìn)行采集豪诲、儲(chǔ)存、管理挂绰、運(yùn)算屎篱、分析、顯示和描述的技術(shù)系統(tǒng)
- ArcGIS平臺(tái)
ArcGIS產(chǎn)品線為用戶提供一個(gè)可伸縮的葵蒂,全面的GIS平臺(tái)交播。ArcObjects包含了許多的可編程組件,從細(xì)粒度的對(duì)象(例如單個(gè)的幾何對(duì)象)到粗粒度的對(duì)象(例如與現(xiàn)有ArcMap文檔交互的地圖對(duì)象)涉及面極廣践付,這些對(duì)象為開(kāi)發(fā)者集成了全面的GIS功能秦士。
- Geometry數(shù)據(jù)類(lèi)型
Geometry是一種空間幾何數(shù)據(jù)類(lèi)型,常用于描述空間幾何信息永高,例如坐標(biāo)點(diǎn)隧土、線、面乏梁、三維信息等次洼。
也就是說(shuō)关贵,GIS一般使用Geometry數(shù)據(jù)類(lèi)型來(lái)存儲(chǔ)及展示地理信息遇骑。
常見(jiàn)的支持Geometry的數(shù)據(jù)庫(kù)有Oracle、SqlServer揖曾、Mysql落萎、PostgreSQL
Sql-Server中支持的Geometry文檔以及Sql-Server中可選擇的地理數(shù)據(jù)類(lèi)型如圖
-
應(yīng)用場(chǎng)景示例
如圖亥啦,該應(yīng)用主要為使用ArcGIS做地理信息系統(tǒng),使用SqlServer作為空間數(shù)據(jù)庫(kù)练链,使用Java搭建后臺(tái)服務(wù)處理數(shù)據(jù)翔脱,使用JS的Vue結(jié)合ArcGIS的API做前端的渲染,成功的將地理信息高亮標(biāo)識(shí)在地圖上
本文關(guān)注點(diǎn):使用java對(duì)Geometry數(shù)據(jù)進(jìn)行處理媒鼓,附帶對(duì)arcgis系統(tǒng)一些簡(jiǎn)單使用說(shuō)明 簡(jiǎn)單描述一個(gè)gis應(yīng)用系統(tǒng)的處理流程
1. 使用ArcGIS創(chuàng)建地理數(shù)據(jù)庫(kù)届吁,發(fā)布GIS服務(wù)
數(shù)據(jù)庫(kù)和 ArcGIS Enterprise
如何注冊(cè)數(shù)據(jù)庫(kù)到 ArcGIS Server 站點(diǎn)
發(fā)布地圖服務(wù)
通過(guò)以上的文檔,我們可以創(chuàng)建一個(gè)地理數(shù)據(jù)庫(kù)绿鸣,并將數(shù)據(jù)庫(kù)通過(guò)arcgis進(jìn)行連接疚沐,發(fā)布為一個(gè)Restful服務(wù)
訪問(wèn)該接口,如圖
那這個(gè)接口提供了那些功能呢潮模?
支持的操作大概如下:
Supported Operations: Query Apply Edits Add Features Update Features Delete Features Calculate Validate SQL Generate Renderer Return Updates Iteminfo Thumbnail Metadata
由ArcGIS提供的這些服務(wù)亮蛔,我們可以以可視化的方式,對(duì)空間地理信息進(jìn)行查詢及展示
后面我們將做進(jìn)一步的說(shuō)明
2. 空間數(shù)據(jù)以及WKT熟知文本
好的擎厢,我們有了數(shù)據(jù)庫(kù)究流,也有了arcgis提供的服務(wù),現(xiàn)在該如何新增空間數(shù)據(jù)呢动遭?
先通過(guò)WKT了解下空間數(shù)據(jù)Geometry大概長(zhǎng)什么樣
WKT芬探,是一種文本標(biāo)記語(yǔ)言,用于表示矢量幾何對(duì)象厘惦、空間參照系統(tǒng)及空間參照系統(tǒng)之間的轉(zhuǎn)換灯节。它的二進(jìn)制表示方式,亦即WKB(well-known binary)則勝于在傳輸和在數(shù)據(jù)庫(kù)中存儲(chǔ)相同的信息绵估。該格式由開(kāi)放地理空間聯(lián)盟(OGC)制定炎疆。
WKT可以表示的幾何對(duì)象包括:點(diǎn),線国裳,多邊形形入,TIN(不規(guī)則三角網(wǎng))及多面體》熳螅可以通過(guò)幾何集合的方式來(lái)表示不同維度的幾何對(duì)象亿遂。
幾何物體的坐標(biāo)可以是2D(x,y),3D(x,y,z),4D(x,y,z,m),加上一個(gè)屬于線性參照系統(tǒng)的m值。
以下為幾何WKT字串樣例:
POINT(6 10)
LINESTRING(3 4,10 50,20 25)
POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))
MULTIPOINT(3.5 5.6, 4.8 10.5)
MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))
MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),((6 3,9 2,9 4,6 3)))
GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))
POINT ZM (1 1 5 60)
POINT M (1 1 80)
POINT EMPTY
MULTIPOLYGON EMPTY
到這里我們清楚了渺杉,所謂空間數(shù)據(jù)就是一個(gè)或一組坐標(biāo)蛇数,這個(gè)坐標(biāo)或坐標(biāo)組有類(lèi)型(POINT點(diǎn)LINESTRING線POLYGON面),通過(guò)這些坐標(biāo)是越,GIS系統(tǒng)可以完整地定位查詢繪制這些坐標(biāo)信息
3. 向空間數(shù)據(jù)庫(kù)插入數(shù)據(jù)
那數(shù)據(jù)庫(kù)如何新增這些空間數(shù)據(jù)呢耳舅?
先了解一下SQLSERVER空間數(shù)據(jù)的插入語(yǔ)句長(zhǎng)啥樣
--GEOM是類(lèi)型為Geometry的字段--
--我們向該字段新增了一條3D的多邊形數(shù)據(jù)--
--geometry :: STGeomFromText () 是由SQLSERVER提供的函數(shù),它能將WKT文本轉(zhuǎn)換為數(shù)據(jù)庫(kù)geometry類(lèi)型的數(shù)據(jù)--
INSERT INTO [dbo].[TEST_GEO_TABLE] ( [GEOM] )
VALUES
( geometry :: STGeomFromText (
'POLYGON ((
113.507259000000005 22.24814946 8,
113.507188600000006 22.248088559999999 9,
113.507117399999998 22.24802743 10,
113.507046099999997 22.24796624 11,
113.507017300000001 22.247888209999999 12
))',4326 )
);
也就是說(shuō)倚评,將坐標(biāo)轉(zhuǎn)化為WKT文本浦徊,我們就可以插入空間數(shù)據(jù)馏予。接下來(lái)我們要考慮的是如何產(chǎn)生WKT文本
4. 使用Java創(chuàng)建Geometry對(duì)象
4.1 常見(jiàn)Geometry的JavaAPI
wkt文本僅僅是一個(gè)字符串而已,直接將坐標(biāo)點(diǎn)拼接成符合WKT格式的字符串不就可以了嗎盔性?
道理是這個(gè)道理霞丧,要做好可就難了。
- 拼接工作量巨大
- 拼接過(guò)程容易出錯(cuò)
- 拼接的結(jié)果不一定合法可用
我們需要一套JAVA API對(duì)數(shù)據(jù)進(jìn)行處理冕香,能夠方便的創(chuàng)建Geometry對(duì)象蛹尝,進(jìn)行地理信息的繪制、創(chuàng)建悉尾、驗(yàn)證等等功能
市面上常見(jiàn)的GeometryApi有
Esri/geometry-api-java
locationtech/jts (推薦)
Esri是Arcgis官方提供的javaSDK箩言,可惜功能不多,甚至不能提供基本的空間計(jì)算功能焕襟。
jts功能較為齊全陨收,資料也相對(duì)豐富一點(diǎn)
點(diǎn)擊上面鏈接可以訪問(wèn)其Github地址,本文將以jts為例進(jìn)行說(shuō)明
4.2 JTS的部分API使用方式
@Test
public void geoTest() throws ParseException {
/**
* GeometryFactory工廠鸵赖,參數(shù)一:數(shù)據(jù)精度 參數(shù)二空間參考系SRID
*/
GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), 4326);
/**
* 熟知文本W(wǎng)KT閱讀器务漩,可以將WKT文本轉(zhuǎn)換為Geometry對(duì)象
*/
WKTReader wktReader = new WKTReader(geometryFactory);
/**
* Geometry對(duì)象,包含Point它褪、LineString饵骨、Polygon等子類(lèi)
*/
Geometry geometry = wktReader.read("POINT (113.53896635 22.36429837)");
/**
* 將二進(jìn)制流的形式讀取Geometry對(duì)象
*/
WKBReader wkbReader = new WKBReader(geometryFactory);
/**
* 單純的一個(gè)坐標(biāo)點(diǎn),單點(diǎn)可以創(chuàng)建Point茫打,多點(diǎn)可以創(chuàng)建LineString居触、Polygon等
*/
Coordinate coordinate = new Coordinate(1.00, 2.00);
Point point = geometryFactory.createPoint(coordinate);
Polygon polygon = geometryFactory.createPolygon(new Coordinate[]{
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
});
Geometry geometry1 = point;
Geometry geometry2 = polygon;
/**
* WKT輸出器,將Geometry對(duì)象寫(xiě)出為WKT文本
*/
WKTWriter wktWriter = new WKTWriter();
String write = wktWriter.write(point);
}
4.3 JTS中Geometry數(shù)據(jù)類(lèi)型的子類(lèi)
根據(jù)github中相關(guān)文檔介紹老赤,我們已經(jīng)可以在項(xiàng)目中引入相關(guān)坐標(biāo)轮洋,并創(chuàng)建Geometry對(duì)象,構(gòu)造WKT文本了
5. 使用JAVA向空間數(shù)據(jù)庫(kù)新增數(shù)據(jù)
根據(jù)上面測(cè)試類(lèi)中Api的使用抬旺,讓我們總結(jié)幾個(gè)要點(diǎn)
- 工廠類(lèi)對(duì)象只需初始化一次弊予,應(yīng)放在配置類(lèi)注入到Spring容器中
- 由前端或Excel導(dǎo)入相關(guān)坐標(biāo)數(shù)據(jù),生成Geometry對(duì)象
- 持久化Geometry對(duì)象到SqlServer
本例中推薦兩種方式進(jìn)行Geometry對(duì)象的持久化:
- 獲取Geometry對(duì)象的WKT文本开财,再使用SqlServer提供的
geometry :: STGeomFromText ()
函數(shù)將WKT文本存儲(chǔ)為數(shù)據(jù)庫(kù)Geometry類(lèi)型 - 將jts包中Geometry對(duì)象轉(zhuǎn)換成SqlServer JDBC包中的Geometry對(duì)象汉柒,將Geometry對(duì)象以二進(jìn)制的形式持久化到數(shù)據(jù)庫(kù)
環(huán)境:
本例代碼基于JTS、SpringBoot责鳍、Mybatis-Plus碾褂、mssql-jdbc環(huán)境
6. 使用TypeHandler
映射自定義對(duì)象字段插入Geometry數(shù)據(jù)
6.1 自定義TypeHandler
當(dāng)我們使用Mybatis框架時(shí),Mybatis提供了自定義類(lèi)型轉(zhuǎn)換器TypeHandler實(shí)現(xiàn)特殊對(duì)象與Sql字段的映射關(guān)系
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTReader;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author wangqichang
* @since 2019/8/28
*/
@Slf4j
@MappedTypes(value = {Geometry.class})
public class GeometryTypeHandler extends BaseTypeHandler<Geometry> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Geometry geometry, JdbcType jdbcType) throws SQLException {
/**
* 獲取jts包對(duì)象的wkt文本历葛,再轉(zhuǎn)換成sqlserver的Geometry對(duì)象
* 調(diào)用ps的setBytes()方法正塌,以二進(jìn)制持久化該geometry對(duì)象
*/
com.microsoft.sqlserver.jdbc.Geometry geo = com.microsoft.sqlserver.jdbc.Geometry.STGeomFromText(geometry.toText(), geometry.getSRID());
preparedStatement.setBytes(i, geo.STAsBinary());
}
@Override
public Geometry getNullableResult(ResultSet resultSet, String s) {
try {
/**
* 從ResultSet中讀取二進(jìn)制轉(zhuǎn)換為SqlServer的Geometry對(duì)象
* 使用jts的WKTReader將wkt文本轉(zhuǎn)成jts的Geometryd對(duì)象
*/
com.microsoft.sqlserver.jdbc.Geometry geometry1 = com.microsoft.sqlserver.jdbc.Geometry.STGeomFromWKB(resultSet.getBytes(s));
String s1 = geometry1.toString();
WKTReader wktReader = SpringContextUtil.getBean(WKTReader.class);
Geometry read = wktReader.read(s1);
return read;
} catch (Exception e) {
log.error(e.getMessage());
throw new ServiceException(e.getMessage());
}
}
@Override
public Geometry getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Geometry getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
6.2 實(shí)體對(duì)象
實(shí)體對(duì)象如下:
-
objectid
為Integer類(lèi)型,非自增(此字段為Arcgis維護(hù),不能修改)@TableId
是mybatis-plus插件的注解传货,告知插件該字段為主鍵字段屎鳍,字段名為OBJECT宏娄,主鍵策略為用戶輸入 -
shape
為jts的Geometry對(duì)象(該對(duì)象JSON序列化結(jié)果非常嚇人问裕,所以使用@JsonIgnore
修飾) -
@KeySequence
也是mybatis-plus的插件,作用是標(biāo)識(shí)該對(duì)象需要使用的主鍵序列名孵坚。此處我實(shí)現(xiàn)了一個(gè)IKeyGenerator
粮宛,作用類(lèi)似于插入數(shù)據(jù)前查詢Oracle的序列名以填充主鍵。
@Data
@TableName("LINE_WELL")
@KeySequence(value = "LINE_WELL",clazz = Integer.class)
public class Well extends MyGeometry implements Serializable {
@TableId(value = "OBJECTID", type = IdType.INPUT)
private Integer objectid;
@JsonIgnore
protected Geometry shape;
}
6.3 自定義主鍵生成策略
在arcgis中卖宠,空間表中的主鍵字段為int巍杈,并且非自增,不能進(jìn)行修改扛伍。當(dāng)修改為自增時(shí)arcgis會(huì)出現(xiàn)一些錯(cuò)誤筷畦。因此,java后臺(tái)插入空間數(shù)據(jù)需要自己完成主鍵的查詢生成刺洒。
IKeyGenerator
是Mybatis-Plus提供的接口鳖宾。此實(shí)現(xiàn)的作用是,當(dāng)指定這個(gè)主鍵生成策略時(shí)逆航,mp框架將會(huì)在新增數(shù)據(jù)前調(diào)用此實(shí)現(xiàn)鼎文,將結(jié)果賦值給對(duì)象的ID(類(lèi)似于Oracle的序列)
注意,該類(lèi)需要注入到Spring容器中
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
/**
* @author wangqichang
* @since 2019/8/30
*/
public class SqlServerKeyGenerator implements IKeyGenerator {
@Override
public String executeSql(String incrementerName) {
return "select max(OBJECTID)+1 from " + incrementerName;
}
}
6.4 Geometry對(duì)象持久化
當(dāng)我們調(diào)用mybatis-plus提供的方法持久化對(duì)象
String str = "POLYGON ((113.52048666400003 22.248443089000034, 113.5206744190001 22.24822462700007, 113.52082998700007 22.248343788000057, 113.52060468200011 22.248547355000028, 113.52048666400003 22.248443089000034))";
Geometry read = null;
try {
/**
* 這里使用wkt文本生成了一個(gè)jts包下的Geometry對(duì)象
*/
read = SpringContextUtil.getBean(WKTReader.class).read(str);
} catch (ParseException e) {
e.printStackTrace();
}
Well well = new Well();
well.setShape(read);
//這里是Mybatis-Plus提供的save接口因俐,調(diào)用其內(nèi)部實(shí)現(xiàn)直接儲(chǔ)存對(duì)象
wellService.save(well);
System.out.println("持久化成功");
執(zhí)行日志如下:
數(shù)據(jù)插入前執(zhí)行了SqlServerKeyGenerator
中的sql獲取主鍵
插入代碼中字段shape為Geometry對(duì)象的二進(jìn)制
2019-08-30 15:54:23.541 INFO 8484 --- [nio-8905-exec-1] jdbc.sqltiming : SELECT max(OBJECTID) + 1 FROM LINE_WELL
{executed in 4 msec}
2019-08-30 15:54:23.631 INFO 8484 --- [nio-8905-exec-1] jdbc.sqltiming : INSERT INTO LINE_WELL (OBJECTID, shape) VALUES (3, '<byte[]>')
{executed in 17 msec}
7. 手寫(xiě)xml插入Geometry數(shù)據(jù)
使用SqlServer提供的函數(shù)geometry :: STGeomFromText( #{wktText},4326)
將Geometry轉(zhuǎn)換成WKT文本再進(jìn)行插入
<insert id="insertCorridorBySql" parameterType="com.zh.xxx.entity.xxx" useGeneratedKeys="true"
keyProperty="objectid">
INSERT INTO [LINE_CORRIDOR] (
shape
)
values (
geometry :: STGeomFromText( #{wktText},4326)
)
</insert>
注意,wktText是一個(gè)非表字段的臨時(shí)字段拇惋,我在此定義了一個(gè)父類(lèi),所有包含Geometry的空間表實(shí)體均繼承此類(lèi)抹剩,用于處理wkt文本
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTWriter;
import java.io.Serializable;
/**
* 針對(duì)Geometry獲取Wkt文本字段做處理的Geometry父類(lèi)撑帖,getWktText替代getText,輸出三維wkt文本
* 針對(duì)sql_server無(wú)法識(shí)別POLYGON Z 語(yǔ)法,對(duì)wkt文本進(jìn)行替換
*/
@Data
public class MyGeometry implements Serializable {
/**
* 三維wkt輸出澳眷,默認(rèn)為2D不帶Z
*/
@TableField(exist = false)
@JsonIgnore
private WKTWriter wktWriter = new WKTWriter(3);
/**
* sql_server 與 jts wkt不兼容問(wèn)題
*/
@TableField(exist = false)
@JsonIgnore
private static final String THREE_D_PRIFIX = "POLYGON Z";
@TableField(exist = false)
@JsonIgnore
private static final String TWO_D_PRIFIX = "POLYGON";
@JsonIgnore
protected Geometry shape;
@TableField(exist = false)
@JsonIgnore
private String wktText;
public String getWktText() {
if (StrUtil.isBlank(wktText)){
if (getShape() != null) {
String wkt = wktWriter.write(shape);
if (wkt.startsWith(THREE_D_PRIFIX)) {
wktText = StrUtil.replace(wkt, THREE_D_PRIFIX, TWO_D_PRIFIX);
} else {
wktText = wkt;
}
}
}
return wktText;
}
}
8 采坑記錄:
8.1 jts與sqlserver識(shí)別的wkt不兼容
[2019-07-01 16:40:20,637] [ERROR] [http-nio-8905-exec-5] jdbc.audit 111 7. PreparedStatement.execute() INSERT INTO [zhundergroundcableline].[dbo].[LINE_CORRIDOR] ( [Shape] ) values ( geometry :: STGeomFromText( 'POLYGON Z((113.5079365 22.24850034
0, 113.5078521 22.24845659 0, 113.5077674 22.24841271 0, 113.5076826 22.24836872 0, 113.5075978 22.24832498 0))',4326) )
com.microsoft.sqlserver.jdbc.SQLServerException: 在執(zhí)行用戶定義例程或聚合“geometry”期間出現(xiàn) .NET Framework 錯(cuò)誤:
System.FormatException: 24142: 在位置 8 處應(yīng)為 "("磷仰,但輸入中實(shí)際為 "Z"。
System.FormatException:
在 Microsoft.SqlServer.Types.WellKnownTextReader.RecognizeToken(Char token)
在 Microsoft.SqlServer.Types.SqlGeometry.GeometryFromText(OpenGisType type, SqlChars text, Int32 srid)