先說明一點,代碼固然重要,數據同樣不容忽視。有些情況代碼報錯,有可能是數據格式的問題,具體原因後面再講,各位謹記。
本篇的架構爲:
隨便用QQ截圖工具做的,湊活看吧...
地圖離不開數據,下面我先談數據製作與發佈,再說代碼。
一、數據製作與發佈
下面是一些gis基礎,不想看的可以直接從分割線往下看。
在gis中數據按類型可分爲點、線、面。
數據可以存放在數據庫中,也可以存放在文件中。
- 數據庫需要支持空間屬性存儲纔行,比如oracle、postgresql、sqlserver,當然不能直連,需要一些“中間件”或插件,如:ArcSDE、postgis。
- 文件可以是shapefile(.shp格式,下稱shp文件)、mdb、gdb、txt等。
只有相同類型的數據纔可以放在一個表或文件中,比如線數據和麪數據無法放在同一個shp文件中。
一般情況下,我們都會把相同類型、具有共同屬性且被我們歸爲一類的數據放在同一個表或文件中。
一個數據庫表或一個文件,在gis中又可稱爲一個圖層。
知識鋪墊over!
----------------------------------------------------------分割線----------------------------------------------------------------
本篇文章我們分別演示點、線、面的增、刪、改操作。
1、創建shp文件
可以用Arcmap工具生成點、線、面文件,這裏我們生成shp文件。
創建文件前,如果你要做三維數據的操作,那麼你可以不用看這篇文章了。如果是二維數據操作,無論是新建或是有現成文件,請確保紅框處未勾選,如果現成文件已勾選,請重新創建新文件並嘗試將原文件數據導入。
隨便做的數據如下
2、導入postgis
沒有安裝postgresql、postgis的可以看postgresql+postgis安裝、postgresql漢化。
創建用戶、建庫等步驟比較簡單,就不再贅述,直接上圖。
用戶:
數據庫:
打開postgis的“postgis shapefile and dbf loader exporter”程序。
點擊“View connection details”,連接postgresql數據庫。輸入之前建好的數據庫和用戶名密碼
連接成功後,logwindow會提示成功。
點擊Options,勾選如下,點OK。
點擊Add File,選擇要導入的shp文件,可以多選。修改SRID,本圖層採用的座標系爲WGS84,因此SRID爲4326(postgis的SRID等同於arcgis裏的wkid)。點擊Import導入,可以看到log出現導入完畢提示。
----------------------------------------------------小小分割線---------------------------------------------------
導入線圖層提示如上圖。
導入點圖層提示:
導入面圖層提示:
疑問:爲什麼線圖層的Shapefile type是Arc,按理說應該是Line或LineString或Polyline纔對(小聲bb...)。
--------------------------------------------------分割線結束-------------------------------------------------
依次導入其他數據後,就可以打開postgresql看到了。
3、將表發佈到Geoserver中
登錄Geoserver。
創建工作區,“命名”無所謂,只用於在geoserver內部使用,“命名空間URI”很重要(命名自己看着來吧)。
創建新的數據連接到postgis,根據紅框輸入postgis的連接信息即可,工作區選擇上面創建的工作區。
發佈圖層,選擇對應的數據源。
可能還需要設置這個:
設置完工作區、數據存儲後,只需要重複發佈圖層的步驟即可,發佈完成後,就可在layer preview裏查看了。
二、代碼
openlayers中要達到動態顯示增刪改效果,一定要選用WFS服務。
創建WFS服務的方法(以點圖層爲例):
var pointSource = new ol.source.Vector({
url: 'http://localhost:9010/geoserver/wfs?' +
'service=WFS&' +
'version=1.1.0&' +
'request=GetFeature&' +
'typeNames=postgis:point&' + // Here goes your-workspace:your-layer
'outputFormat=json&' +
'srsname=EPSG:4326',
format: new ol.format.GeoJSON({
geometryName: 'geom'//postgis的空間存儲字段名
})
});
var pointLayer = new ol.layer.Vector({
// preload: Infinity,
source: pointSource
});
公共增刪改方法:
//transType爲增刪改類型
//feat爲要增刪改的要素
//layerName爲要進行操作的目標圖層名稱
function transact(transType, feat, layerName) {
if(layerName == ''){
return;
}
var formatWFS = new ol.format.WFS();
var formatGML = new ol.format.GML({
featureNS: 'http://localhost/postgis', // Your namespace
featureType: layerName, //此處填寫圖層名稱
gmlOptions:{srsName: 'EPSG:4326'}
});
switch (transType) {
case 'insert':
node = formatWFS.writeTransaction([feat], null, null, formatGML);
break;
case 'update':
node = formatWFS.writeTransaction(null, [feat], null, formatGML);
break;
case 'delete':
node = formatWFS.writeTransaction(null, null, [feat], formatGML);
break;
}
s = new XMLSerializer();
str = s.serializeToString(node);
$.ajax('http://localhost:9010/geoserver/wfs',{
type: 'POST',
// dataType: 'xml',
// processData: false,
contentType: 'text/xml',
data: str
}).done();
}
(新增)繪製的方法:
map.getInteractions().clear();
drawInteraction = new ol.interaction.Draw({
source: pointSource,//上面創建的點圖層數據源
type: 'Point',//可選Point,LineString,Polygon
geometryName: 'geometry',
freehand:freemode//是否徒手模式
});
map.addInteraction(drawInteraction);
drawInteraction.on('drawend', function (e) {
var geometry = e.feature.getGeometry().clone();
// 設置feature對應的屬性,這些屬性是根據數據源的字段來設置的
var newFeature = new ol.Feature();
newFeature.setGeometryName('geom');//這裏必須命名爲postgis對應字段名
newFeature.setGeometry(geometry);
newFeature.set('id', 2);//這裏可以設置其他屬性
transact('insert', newFeature, 'point');
});
編輯的方法:
map.getInteractions().clear();
var select = new ol.interaction.Select();
var modify = new ol.interaction.Modify({
features: select.getFeatures()
});
map.addInteraction(select);
map.addInteraction(modify);
modify.on('modifyend', function (e) {
var feature = e.features.item(0).clone();
feature.setId(e.features.item(0).getId());//確定編輯要素的主鍵
// 調換經緯度座標,以符合wfs協議中經緯度的位置
feature.getGeometry().applyTransform(function(flatCoordinates, flatCoordinates2, stride) {
for (var j = 0; j < flatCoordinates.length; j += stride) {
var y = flatCoordinates[j];
var x = flatCoordinates[j + 1];
flatCoordinates[j] = x;
flatCoordinates[j + 1] = y;
}
});
transact('update',feature, 'point');
});
編輯這裏要多說一句,“applyTransform”方法其實是將橫縱座標顛倒,原理還不清楚(猜測是geoserver要求),通過查看控制檯,很直觀的看到輸入:
目前只有編輯需要調換這一步驟。
刪除的方法:
map.getInteractions().clear();
var select = new ol.interaction.Select();
map.addInteraction(select);
select.on('select', function (e) {
if(select.getFeatures().getLength() == 0) {
console.log('null');
} else {
var f;
f = pointSource.getFeatureById(e.target.getFeatures().getArray()[0].getId());
pointSource.removeFeature(f);
e.target.getFeatures().clear();
transact('delete', f, 'point');
}
});
下面上全部代碼:
html:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="v5.2.0/css/ol.css" type="text/css">
<script src="v5.2.0/build/ol.js"></script>
<script src="js/jquery-3.3.1.min.js"></script>
</head>
<body>
<div id="map" class="map"></div>
<div style="position: absolute;top: 50px;left: 50px;">
<input id="draw-tool" type="checkbox" value="add" />新增
<select id="selectType">
<option value="line">線</option>
<option value="point">點</option>
<option value="polygon">面</option>
</select>
徒手模式:<select id="drawMode">
<option value="true">是</option>
<option value="false">否</option>
</select>
<hr>
<input type="checkbox" value="modify" id="modify-tool" />編輯
<hr>
<input type="button" value="刪除選中Feature" id="delete-tool" />
<!-- <input type="button" value="保存"/> -->
</div>
<script type="text/javascript" src="js/editTool.js"></script>
</body>
</html>
editTool.js
var drawInteraction;
// // 創建ImageWMS數據源
// var wmsSource = new ol.source.TileWMS({
// url: 'http://localhost:9010/geoserver/postgis/wms',
// params: {'LAYERS': 'postgis:sishui'},
// serverType: 'geoserver',
// projection: "EPSG:4326"
// });
// // 創建Image圖層
// var wmsLayer = new ol.layer.Tile({
// source: wmsSource
// });
var pointSource = new ol.source.Vector({//點數據源
url: 'http://localhost:9010/geoserver/wfs?' +
'service=WFS&' +
'version=1.1.0&' +
'request=GetFeature&' +
'typeNames=postgis:point&' + // Here goes your-workspace:your-layer
'outputFormat=json&' +
'srsname=EPSG:4326',
format: new ol.format.GeoJSON({geometryName: 'geom'})
});
var polygonSource = new ol.source.Vector({//面數據源
url: 'http://localhost:9010/geoserver/wfs?' +
'service=wfs&' +
'version=1.1.0&' +
'request=GetFeature&' +
'typeNames=postgis:sishui&' + // Here goes your-workspace:your-layer
'outputFormat=application/json&' +
'srsname=EPSG:4326',
format: new ol.format.GeoJSON({
geometryName: 'geom' // 將shp格式矢量文件導入PostgreGIS數據庫中,對應的表中增加了一個字段名爲geom的字段,所有這裏的名稱就是數據庫表中增加的那個字段名稱
})
});
var lineSource = new ol.source.Vector({//線數據源
url: 'http://localhost:9010/geoserver/wfs?' +
'service=WFS&' +
'version=1.1.0&' +
'request=GetFeature&' +
'typeNames=postgis:road&' + // Here goes your-workspace:your-layer
'outputFormat=application/json&' +
'srsname=EPSG:4326',
format: new ol.format.GeoJSON({geometryName: 'geom'})
});
var pointLayer = new ol.layer.Vector({//點圖層
// preload: Infinity,
source: pointSource
});
var polygonLayer = new ol.layer.Vector({//面圖層
// preload: Infinity,
source: polygonSource
});
var lineLayer = new ol.layer.Vector({//線圖層
preload: Infinity,
source: lineSource
});
var view = new ol.View({
center: [117.331,35.647],
zoom: 11,
projection:"EPSG:4326"
});
var map = new ol.Map({
layers: [new ol.layer.Tile({//加載open street map
source:new ol.source.OSM()
})],
target: 'map',
view: view
});
map.addLayer(lineLayer);
map.addLayer(polygonLayer);
map.addLayer(pointLayer);
//重選繪製類型時,清空所有的操作工具
$('#selectType').change(function () {
map.getInteractions().clear();
});
//根據選擇的繪製類型,動態返回繪圖工具類型和數據源
function defineType() {
var geom_type;
var geom_source;
switch($('#selectType').val()) {
case 'polygon':
geom_type = 'Polygon';
geom_source = polygonSource;
return [geom_type, geom_source];
case 'line':
geom_type = 'LineString';
geom_source = lineSource;
return [geom_type, geom_source];
case 'point':
geom_type = 'Point';
geom_source = pointSource;
return [geom_type, geom_source];
default:
console.log('Nothing selected!!!');
}
}
//返回是否是徒手模式
function freeMode(){
switch($('#drawMode').val()){
case 'true':
return true;
case 'false':
return false;
default:
console.log('Nothing selected!!!');
}
}
//新增按鈕點擊事件
$('#draw-tool').click(function () {
if(this.checked){
var type = defineType()[0];//當前要繪製的是點?線?面?
var source = defineType()[1];//對應的數據源
var freemode = freeMode();//是否徒手模式
map.getInteractions().clear();
drawInteraction = new ol.interaction.Draw({
source: source,
type: type,
geometryName: 'geometry',//這裏必須是geometry,不能提前定義成geom
freehand:freemode
});
map.addInteraction(drawInteraction);
drawInteraction.on('drawend', function (e) {//繪製完成
var layerName = '';
var geometry = e.feature.getGeometry().clone();
// 設置feature對應的屬性,這些屬性是根據數據源的字段來設置的
var newFeature = new ol.Feature();
if(defineType()[0]=='LineString'){
newFeature.setGeometryName('geom');
// newFeature.set('geom', null);
// newFeature.setGeometry(new ol.geom.MultiLineString([geometry.getCoordinates()]));
newFeature.setGeometry(geometry);
newFeature.set('id', 2);
// newFeature.setId('road.'+1);
layerName = 'road';
}else if(defineType()[0]=='Polygon'){
newFeature.setGeometryName('geom');
// newFeature.set('geom', null);
// newFeature.setGeometry(new ol.geom.MultiPolygon([geometry.getCoordinates()]));
newFeature.setGeometry(geometry);
newFeature.set('id', 2);
// newFeature.setId('sishui.'+1);
layerName = 'sishui';
}else if(defineType()[0]=='Point'){
newFeature.setGeometryName('geom');
// newFeature.set('geom', null);
// newFeature.setGeometry(new ol.geom.MultiPoint([geometry.getCoordinates()]));
newFeature.setGeometry(geometry);
newFeature.set('id', 2);
// newFeature.setId('point.'+1);
layerName = 'point';
}
transact('insert', newFeature, layerName);
});
}else{
map.removeInteraction(drawInteraction);
// if (drawedFeature) {
// drawLayer.getSource().removeFeature(drawedFeature);
// }
// drawedFeature = null;
}
});
//Modify
$('#modify-tool').click(function () {
map.getInteractions().clear();
var select = new ol.interaction.Select();
var modify = new ol.interaction.Modify({
features: select.getFeatures()
});
map.addInteraction(select);
map.addInteraction(modify);
modify.on('modifyend', function (e) {
var layerName = '';
var feature = e.features.item(0).clone();
feature.setId(e.features.item(0).getId());
var geomType = feature.getGeometry().getType().toLowerCase();//openlayers繪製類型
if(geomType=='linestring'){
layerName = 'road';
}else if(geomType=='polygon'){
layerName = 'sishui';
}else if(geomType=='point'){
layerName = 'point';
}
// 調換經緯度座標,以符合wfs協議中經緯度的位置
feature.getGeometry().applyTransform(function(flatCoordinates, flatCoordinates2, stride) {
for (var j = 0; j < flatCoordinates.length; j += stride) {
var y = flatCoordinates[j];
var x = flatCoordinates[j + 1];
flatCoordinates[j] = x;
flatCoordinates[j + 1] = y;
}
});
transact('update',feature, layerName);
});
});
//Delete
$('#delete-tool').click(function () {
map.getInteractions().clear();
var select = new ol.interaction.Select();
map.addInteraction(select);
select.on('select', function (e) {
if(select.getFeatures().getLength() == 0) {
console.log('null');
} else {
var geomType = e.target.getFeatures().getArray()[0].getGeometry().getType().toLowerCase();
//var geomType = e.target.getFeatures().item(0).getGeometry().getType().toLowerCase();//getArray()[0]和item(0)均可
var layerName = '';
var f;
switch(geomType) {
case 'polygon':
layerName = 'sishui';
f = polygonSource.getFeatureById(e.target.getFeatures().getArray()[0].getId());
polygonSource.removeFeature(f);
e.target.getFeatures().clear();
break;
case 'linestring':
layerName = 'road';
f = lineSource.getFeatureById(e.target.getFeatures().getArray()[0].getId());
lineSource.removeFeature(f);
e.target.getFeatures().clear();
break;
case 'point':
layerName = 'point';
f = pointSource.getFeatureById(e.target.getFeatures().getArray()[0].getId());
pointSource.removeFeature(f);
e.target.getFeatures().clear();
break;
default:
console.log('Type of feature unknown!!!');
}
transact('delete', f, layerName);
}
});
});
/** -------------------------------------------- */
function transact(transType, feat, layerName) {
if(layerName == ''){
return;
}
var formatWFS = new ol.format.WFS();
var formatGML = new ol.format.GML({
featureNS: 'http://localhost/postgis', // Your namespace
featureType: layerName,
gmlOptions:{srsName: 'EPSG:4326'}
});
switch (transType) {
case 'insert':
node = formatWFS.writeTransaction([feat], null, null, formatGML);
break;
case 'update':
node = formatWFS.writeTransaction(null, [feat], null, formatGML);
break;
case 'delete':
node = formatWFS.writeTransaction(null, null, [feat], formatGML);
break;
}
s = new XMLSerializer();
str = s.serializeToString(node);
$.ajax('http://localhost:9010/geoserver/wfs',{
type: 'POST',
// dataType: 'xml',
// processData: false,
contentType: 'text/xml',
data: str
}).done();
}
研究總結:總體還是比較難受的,ol調用geoserver的示例少,而且數據格式不正確都能引起異常,更可怕的異常還看不懂。
列舉一下容易出錯的點吧:
1.導入shp文件時,一定要檢查是否帶Z值!
如果導入帶Z值的文件,導入日誌會發現type:PolygonZ。
調用時會發現,這都是些啥?通過控制檯可以看到,數據變成了3維數組,但截取的過程中發生了什麼問題,導致數據亂了。
這是正常的:
2.導入時,最好要在Options中勾選最後一個,導入時內部會自動將MULTI 類型轉成Simple類型,會使得代碼相對簡單。當然也可以不勾選。如果未勾選,在導入時log下會提示PostGIS type:【MULTI】xxx,可以區分出來。
如果導入的是【MULTI】xxx類型,原有的代碼將不能成功運行(參考如下新增的代碼片段):
// newFeature.setGeometry(new ol.geom.MultiLineString([geometry.getCoordinates()]));
newFeature.setGeometry(geometry);
在geoserver/logs/wrapper.log文件中可以看到提示,提示插入要素錯誤,原因類型不匹配(錯誤一大串,需要往下翻纔看到)。
此時,就要用被注掉的代碼,用這種方式新增。
newFeature.setGeometry(new ol.geom.MultiLineString([geometry.getCoordinates()]));
3.注意區分Openlayers和PostGIS中點、線、面類型的名稱。
在Openlayers中爲Point、LineString、Polygon。
在PostGIS返回的數據類型中爲point、linestring、polygon(如果是multi類型的,好像是multilinestring)。
4.代碼新增數據時,一定要設置新feature的geometry名稱。
newFeature.setGeometryName('geom');//geom爲postgis表的控件屬性存儲字段名稱
newFeature.setGeometry(geometry);
否則會發現,控制檯中拼裝的是<geometry>,而geoserver識別的是geom,得到null。
5.代碼創建點線面數據源時,一定設置format。
否則會在編輯、刪除時報錯。應識別的屬性爲geom,然而默認是geometry,各位也可以仿照新增,單獨設置geometryName,在source設置一勞永逸罷了。
6.其他的想不起來了,想到再加吧,反正報錯信息也看不懂,瞎試吧【大笑】
參考文檔:
geoserver+postGIs+openlayer 空間要素的增刪改查
使用Openlayer利用GeoServer編輯要素到postGIS注意問題(WFS-T)
Openlayers-3 WFS-T (Post feature to postgis via geoserver)
參考資源: