[Javascript] OpenLayers + GeoServer + PostGIS + VWORLD + WFS,WMS
[목표]
제공받은 SHP파일을 DB에 저장하고 웹페이지에서 데이터를 불러와 수정할 수 있는 사이트를 개발
[PPT 정리]
drive.google.com/file/d/1rcVbrXGgZy5zdDSkk-uvDB16624rB0cw/view?usp=sharing
[환경설정]
*Windows 10 64bit
*Tomcat 9.0
*Java OpenJDK 1.8.0
[설치프로그램]
*postgreSQL
PostgreSQL: Downloads
Downloads PostgreSQL Downloads PostgreSQL is available for download as ready-to-use packages or installers for various platforms, as well as a source code archive if you want to build it yourself. Packages and Installers Select your operating system family
www.postgresql.org
설치가이드 : www.slideshare.net/ybh0616/postgis-101511460
PostGIS 시작하기
[1] 윈도우에서 PostgreSQL과 PostGIS 설치하기 [2] PostgreSQL/PostGIS에서 DB 생성하고 공간데이터 추가하기 [3] QGIS에서 PostGIS 레이어 추가하기 [4] PostgreSQL/PostGIS에서 SQL 언어 학습하기 [5] PostgreSQL/…
www.slideshare.net
*geoServer ( stable )
Download - GeoServer
Maintenance Long term support, so you have time to upgrade. GeoServer 2.16 releases: Nightly builds for the 2.17.x series can be found here.
geoserver.org
다운로드 파일에서 .war파일만 ./Tomcat/webapps/ 경로에 넣어주고 서버를 재시작합니다.
*Openlayers ( cdn 가능 )
OpenLayers - Welcome
A high-performance, feature-packed library for all your mapping needs.
openlayers.org
설치 및 셋팅 이슈사항
vworld 인증키 발급
공간정보 오픈플랫폼 포털
커뮤니티 지역에 대한 정보습득과 정보공유를 목적으로 하며, 지도기반의 다양한 정보를 효과적으로 공유 할 수 있습니다.
www.vworld.kr
CORS문제 해결
https://cafe.naver.com/gisapplication/988
[GeoServer] WFS 이용시 CORS 크로스 도메인
[GeoServer활용] WFS쿼리시 크로스도메인(cross-domain-policy) 해결하기먼저 WFS 및 WMS 서비스는일반적으로 지리 데이터에 액세스하기위한 OGC 웹...
cafe.naver.com
libsqlite3-0 not found 시
libsql3-0.dll Download로드 후 C:\Program Files\PostgreSQL\13\bin\postgisgui에 넣기
http://internetaccessmonitor.ru/dll/libsqlite3-0.dll
[Shp 파일 PostGIS에 등록]
예제 SHP 파일
data.seoul.go.kr/dataList/OA-13221/S/1/datasetView.do
서울시 법정구역 읍면동 공간정보 (좌표계: WGS1984)
데이터 이용하기-서울시 법정구역 읍면동 공간정보 (좌표계: WGS1984)
data.seoul.go.kr
SHP파일을 서버에 등록
3. Import 실행
[GeoServer - PostGIS 연결]
[geoserver 주소] : 톰캣 주소/geoserver , 도메인/geoserver/web/
처음 계정 : admin/geoserver
기본 작업공간, 저장소, 레이어 전부삭제
[GeoServer 설정]
*서비스의 모든탭 사용
레이어 미리보기로 확인
[Openlayers 연동]
1. WMS
- 이미지로 띄움
- 수정불가
- 속도 빠름, DB정보 표출가능
[wms.html]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<title>환경주제도 맵 예제(wms)</title>
<link rel="stylesheet" type="text/css" href="/resources/ol.css">
<link rel="stylesheet" type="text/css" href="/resources/wms.css">
<script src="/resources/ol.js"></script>
</head>
<body>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<div id="map" class="map"></div>
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content"></div>
</div>
<div id = "info"> </div>
<script src="/resources/wms.js"></script>
</body>
</html>
[wms.css]
@charset "UTF-8";
<style>
html, body, #map { margin: 0; padding: 0; width: 100%; height: 100%; }
.map {
width: 100%;
height:400px;
}
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 280px;
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: "✖";
}
</style>
[wms.js]
/**
* Elements that make up the popup.
*/
var container1 = document.getElementById('popup');
var content1 = document.getElementById('popup-content');
var closer1 = document.getElementById('popup-closer');
/**
* Create an overlay to anchor the popup to the map.
*/
var overlay = new ol.Overlay({
element : container1,
autoPan : true,
autoPanAnimation : {
duration : 250,
},
});
/**
* Add a click handler to hide the popup.
* @return {boolean} Don't follow the href.
*/
closer1.onclick = function () {
overlay.setPosition(undefined);
closer1.blur();
return false;
};
var wmsSource = new ol.source.TileWMS({
url: '/geoserver/seoul/wms',
params: {
'layers':'seoul:tc_spbe17_2015_w',
'TILED':true},
serverType: 'geoserver',
// Countries have transparency, so do not fade tiles:
transition: 0,
projection: 'EPSG:4326',
});
var layers = [
new ol.layer.Tile({
source : new ol.source.XYZ({
// Vworld Tile 변경
url : 'http://api.vworld.kr/req/wmts/1.0.0/[인증키]/Satellite/{z}/{y}/{x}.jpeg'
})
}),
new ol.layer.Tile({
source : wmsSource,
})
];
var view = new ol.View({
center: [14128579.82, 4512570.74],
zoom: 11
});
var map = new ol.Map({
layers: layers,
overlays: [overlay],
target: 'map',
view: view,
});
map.on('singleclick', function (evt) {
var viewResolution = /** @type {number} */(view.getResolution());
var url = wmsSource.getGetFeatureInfoUrl(evt.coordinate, viewResolution,
'EPSG:3857', {
'INFO_FORMAT' : 'text/html',
'QUERY_LAYERS' : 'tc_spbe17_2015_w'
});
if (url) {
fetch(url).then(function(response) {
return response.text();
}).then(function(html) {
document.getElementById('info').innerHTML = html;
content1.innerHTML = html;
overlay.setPosition(evt.coordinate);
});
}
});
[Openlayers 연동2]
2. WFS
- Vector로 띄움
- 수정가능
- 속도 느림
[wfs.html]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<title>공간지도 서비스(wfs)</title>
<style type="text/css">
html, body, #map { margin: 0; padding: 0; width: 100%; height: 100%; }
</style>
<link rel="stylesheet" type="text/css" href="/resources/ol.css">
<link rel="stylesheet" type="text/css" href="/resources/wfs.css">
<script src="/resources/ol.js"></script>
</head>
<body>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<div id="map" class="map"></div>
<form id="options-form" autocomplete="off">
<div class="radio">
<label>Mode </label>
<label> <input type="radio" name="interaction" value="draw" id="rdo_draw" >
Draw
</label>
<label> <input type="radio" name="interaction" value="modify" id="rdo_modify">
Modify
</label>
<label> <input type="radio" name="interaction" value="delete" id="rdo_delete">
Delete
</label>
<button type="button" onClick="deleteItem()">삭제</button>
</div>
<div class="radio">
</div>
<div class="form-group">
<label>Draw type </label>
<select name="draw-type" id="draw-type">
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon">Polygon</option>
<option value="Circle">Circle</option>
</select>
</div>
<div class="form-group">
<label>Map type </label>
<select name="map-type" id="map-type">
<option value="tc_spbe17_2015_w">tc_spbe17_2015_w</option>
<!-- map 추가 -->
</select>
</div>
</form>
<script src="/resources/wfs.js"></script>
</body>
</html>
[wfs.css]
@charset "UTF-8";
<style>
.map {
width: 100%;
height:400px;
}
</style>
[wfs.js]
/* ServerSide Value */
var formatWFS = new ol.format.WFS();
var xs = new XMLSerializer();
//맵생성 시 추가
var gmlJson = {'tc_spbe17_2015_w':{}};
var mapJson = {};
var target_map='tc_spbe17_2015_w';
/* GML 데이터 */
//맵생성시 추가
gmlJson['tc_spbe17_2015_w']['insert'] = new ol.format.GML({
featureNS : 'seoul',
featureType : 'tc_spbe17_2015_w',
srsName: 'EPSG:3857'
});
gmlJson['tc_spbe17_2015_w']['upddel'] = new ol.format.GML({
featureNS : 'http://localhost/geoserver/seoul/ows',
featureType : 'tc_spbe17_2015_w',
srsName: 'EPSG:3857'
});
function transactWFS(mode, f) {
var node;
switch (mode) {
case 'insert':
node = formatWFS.writeTransaction([ f ], null, null, gmlJson[target_map]['insert']);
break;
case 'update':
node = formatWFS.writeTransaction(null, [ f ], null, gmlJson[target_map]['upddel']);
break;
case 'delete':
node = formatWFS.writeTransaction(null, null, [ f ], gmlJson[target_map]['upddel']);
break;
}
var payload = xs.serializeToString(node);
switch (mode) {
case 'update':
var payload=payload.replace("feature:"+target_map,"seoul:"+target_map);
break;
}
$.ajax('http://localhost/geoserver/seoul/ows', {
type : 'POST',
dataType : 'xml',
processData : false,
contentType : 'text/xml',
data : payload
}).done(function(data1,data2,data3) {
console.log(data1);
console.log(data2);
//mapJson[target_map].clear();
}).always(function(){
switch (mode) {
case 'insert':
var propNodes = node.getElementsByTagName("Property");
for (var i = 0; i < propNodes.length; i++) {
var propNode = propNodes[i];
var propNameNode = propNode.firstElementChild;
var propNameNodeValue = propNameNode.firstChild;
if (propNameNodeValue.nodeValue === "geometry") {
propNode.parentNode.removeChild(propNode);
break;
}
}
break;
}
});
};
/* 맵데이터 */
temp_tc_spbe17_2015_w = new ol.source.Vector({
loader: function (extent) {
$.ajax('http://localhost/geoserver/seoul/ows', {
type: 'GET',
data: {
service: 'WFS',
version: '1.1.0',
request: 'GetFeature',
typename: 'seoul:tc_spbe17_2015_w',
srsname: 'EPSG:3857',
bbox: extent.join(',') + ',EPSG:3857'
}
}).done(function (response) {
temp_tc_spbe17_2015_w.addFeatures(formatWFS.readFeatures(response));
});
},
strategy: ol.loadingstrategy.bbox,
projection: 'EPSG:3857'
});
mapJson['tc_spbe17_2015_w'] = new ol.layer.Vector({
source : temp_tc_spbe17_2015_w,
style : new ol.style.Style({
fill : new ol.style.Fill({
color : 'rgba(255, 0, 0, 0.2)',
}),
stroke : new ol.style.Stroke({
color : '#ff0000',
width : 2,
}),
image : new ol.style.Circle({
radius : 7,
fill : new ol.style.Fill({
color : '#ff0000',
}),
}),
}),
});
var map = new ol.Map({
layers : [
new ol.layer.Tile({
source : new ol.source.XYZ({
url : 'http://api.vworld.kr/req/wmts/1.0.0/[인증키]/Satellite/{z}/{y}/{x}.jpeg'
})
}),
mapJson['tc_spbe17_2015_w']
],
target : document.getElementById('map'),
view : new ol.View({
center : [ 14128579.82, 4512570.74 ],
maxZoom : 19,
zoom : 11
})
});
/* Delete */
var ExampleDelete = {
init : function() {
this.select = new ol.interaction.Select();
map.addInteraction(this.select);
this.setEvents();
this.setActive(false);
this.selectItem = null;
},
setEvents : function() {
var selectedFeatures = this.select.getFeatures();
selectedFeatures.on('add', function(event){
this.selectItem = event.element;
}, this);
selectedFeatures.on('remove', function(event){
this.selectItem = null;
}, this);
},
setActive : function(active) {
this.select.setActive(active);
},
};
ExampleDelete.init();
function deleteItem(){
if( rdo_delete.checked == true ){
if( ExampleDelete.selectItem != null ){
if (confirm("정말 삭제하시겠습니까?")) {
transactWFS('delete', ExampleDelete.selectItem);
mapJson[target_map].getSource().removeFeature(ExampleDelete.selectItem);
ExampleDelete.select.getFeatures().clear();
}
}
}
}
/* ModiFy */
var ExampleModify = {
init : function() {
this.select = new ol.interaction.Select();
map.addInteraction(this.select);
this.modify = new ol.interaction.Modify({
features : this.select.getFeatures(),
});
map.addInteraction(this.modify);
this.setEvents();
this.setActive(false);
},
setEvents : function() {
var selectedFeatures = this.select.getFeatures();
this.select.on('change:active', function() {
selectedFeatures.forEach(function(each) {
selectedFeatures.remove(each);
});
});
this.dirty = {};
this.select.getFeatures().on('add', function (e) {
e.element.on('change', function (e) {
ExampleModify.dirty[e.target.getId()] = true;
});
});
this.select.getFeatures().on('remove', function (e) {
var f = e.element;
if (ExampleModify.dirty[f.getId()]) {
delete ExampleModify.dirty[f.getId()];
var featureProperties = f.getProperties();
delete featureProperties.boundedBy;
var clone = new ol.Feature(featureProperties);
clone.setId(f.getId());
transactWFS('update', clone);
}
});
},
setActive : function(active) {
this.select.setActive(active);
this.modify.setActive(active);
},
};
ExampleModify.init();
/* Draw */
var optionsForm = document.getElementById('options-form');
var drawObj = null;
var ExampleDraw = {
init : function() {
this.setDrawType();
map.addInteraction(this.Point);
map.addInteraction(this.LineString);
map.addInteraction(this.Polygon);
map.addInteraction(this.Circle);
this.Point.on('drawend',function(e){
transactWFS('insert', e.feature);
})
this.LineString.on('drawend',function(e){
transactWFS('insert', e.feature);
})
this.Polygon.on('drawend',function(e){
var f = e.feature;
f.set('emd_cd', '11111111');
f.set('emd_nm', '테스트');
f.set('emd_eng_nm', 'test');
f.setGeometryName('geom');
drawObj = f;
transactWFS('insert', e.feature);
})
this.Circle.on('drawend',function(e){
transactWFS('insert', e.feature);
})
this.Point.setActive(false);
this.LineString.setActive(false);
this.Polygon.setActive(false);
this.Circle.setActive(false);
// The snap interaction must be added after the Modify and Draw interactions
// in order for its map browser event handlers to be fired first. Its handlers
// are responsible of doing the snapping.
map.addInteraction(this.snap);
},
setDrawType:function(){
var source = mapJson[target_map].getSource();
this.Point = new ol.interaction.Draw({
source : source,
type : 'Point',
geometryName : 'geom',
});
this.LineString = new ol.interaction.Draw({
source : source,
type : 'MultiLineString',
geometryName : 'geom',
});
this.Polygon = new ol.interaction.Draw({
source : source,
type : 'MultiPolygon',
geometryName : 'geom',
});
this.Circle = new ol.interaction.Draw({
source : source,
type : 'Circle',
geometryName : 'geom',
});
this.snap = new ol.interaction.Snap({
source: source,
});
},
getActive : function() {
return this.activeType ? this[this.activeType].getActive() : false;
},
setActive : function(active) {
var type = optionsForm.elements['draw-type'].value;
if (active) {
this.activeType && this[this.activeType].setActive(false);
this[type].setActive(true);
this.activeType = type;
} else {
this.activeType && this[this.activeType].setActive(false);
this.activeType = null;
}
},
removeInteraction: function(){
map.removeInteraction(this.Point);
map.removeInteraction(this.LineString);
map.removeInteraction(this.Polygon);
map.removeInteraction(this.Circle);
map.removeInteraction(this.snap);
}
};
ExampleDraw.init();
/**
* Let user change the geometry type.
* @param {Event} e Change event.
*/
optionsForm.onchange = function (e) {
var type = e.target.getAttribute('name');
var value = e.target.value;
if (type == 'draw-type') {
ExampleDraw.getActive() && ExampleDraw.setActive(true);
}else if(type == 'map-type'){
if(rdo_draw.checked){
rdo_draw.checked = false;
}else if(rdo_modify.checked){
ExampleModify.setActive(false);
ExampleModify.setActive(true);
}else if(rdo_delete.checked){
ExampleDelete.setActive(false);
ExampleDelete.setActive(true);
}
map.removeLayer(mapJson[target_map]);
map.addLayer(mapJson[value]);
ExampleDraw.removeInteraction();
target_map = value;
ExampleDraw.init();
} else if (type == 'interaction') {
if (value == 'modify') {
ExampleDraw.setActive(false);
ExampleModify.setActive(true);
ExampleDelete.setActive(false);
} else if (value == 'draw') {
ExampleDraw.setActive(true);
ExampleModify.setActive(false);
ExampleDelete.setActive(false);
} else if (value == 'delete') {
ExampleDraw.setActive(false);
ExampleModify.setActive(false);
ExampleDelete.setActive(true);
}
}
};
wms화면
wfs 화면
Draw : Drawend 이벤트를 통하여 자동 반영 ( 이때 위에 postgis의 DB 테이블에서 geometery 컬럼을 확인하여 컬럼이름과 Geometry 도형중 어떤도형이 들어가는지 확인하여 그 도형만 그릴것, 예제는 MultiPolygon )
Update : remove 이벤트를 통하여 자동 반영
Delete : Prompt 호출 후 응답여부에 따라 저장