2020년 2월 27일 목요일

빅데이터 처리를 위한 윈도우 버전 hadoop, spark 설치 및 간단한 사용법

빅데이터 처리를 할 수 있으면 SNS, 사물인터넷, 공간정보 등에서 발생되는 수많은 데이터에서 인사이트 있는 정보를 쉽게 얻을 수 있다. 이 글은 윈도우 환경에서 빅데이터 분산 처리를 위한 hadoop, spark 설치 및 사용 방법을 간단히 다룬다.

보통 빅데이터 처리할 때 다음과 같은 아키텍처를 가지고 있는 데, hadoop과 spark는 각각 분산 저장 및 관리, hadoop 기반 분석 및 통계 처리 역할을 한다.

빅데이터 활용 초기에는 하둡이 많이 사용되었으나, 빈번한 디스크 입출력으로 인한 속도저하와 빅데이터 처리 프로그램 개발 및 배포의 불편함(java 기반) 등으로 인해, 스파크가 점차 인기를 끌고 있다. 스파크는 In-memory 처리 방식으로 분산 처리 성능을 크게 향상시켰다. RDD(resilient distributed datasets)을 이용해 메모리 내에서 분산 처리 연산 및 분산된 데이터 유실에 대한 문제를 해결해 가용성을 높였다. 이 방법은 Hadoop의 HDFS(Hadoop Distributed File System) 읽기쓰기 시 디스크 작업 속도보다 10~100배 빠르다.

다음은 일반적인 빅데이터처리 아키텍처를 보여준다. 카프카로 IoT, SNS 등 massive data를 스트리밍해, No SQL로 저장한다. 만약, 빅데이터 분산 저장 및 분석 등이 필요한 경우 스파크를 통해 처리한다. 이를 다양한 가시화 도구를 통해 결과를 보여준다.
빅데이터 처리 시스템 구조 개념도

다음 그림은 다양한 소스를 통해 얻어지는 빅데이터를 카프카나 하둡으로 얻어 스파크를 통해 No SQL DB에 분산 저장하거나 처리한 후 저장한다. 로그 형식으로 발생되는 데이터는 좀 더 단순한 플럼(Flume참고)으로 스트리밍 저장할 수 도 있다. 이를 스카파크 SQL 등을 통해 질의한다. 정재된 데이터는 유사도, 군집, 데이터 마이닝을 위해 머하웃(Mohout참고)을 사용할 수도 있다. 머하웃은 하둡 맵리듀스(map reduce) 기반 분산 계산이 가능하다. 이 과정은 BI(business intelligent)에서 사용하는 ETL(Extract, Transform and Load)와 매우 유사하다.
빅데이터 처피 파이프 라인 예시(Big Data, Streaming Data - ETL Analytics Pipeline)

이렇게 구축된 플랫폼위에 트위터에서 사용되는 스톰(Storm)과 같은 실시간 분석 분산 시스템, 하둡파일을 SQL 질의할 수 있는 Hive, 대용량 데이터셋을 좀 더 쉽게 다룰수 있는 스크립트를 지원하는 피그(pig), 솔라(Solr)와 같은 웹 기반 검색 엔진을 사용할 수 있다.
빅데이터 처리 시스템
(Reference: https://intellipaat.com/blog/tutorial/hadoop-tutorial/introduction-hadoop/)

윈도우 환경은 우분투에서 설치하는 것보다 방법이 좀 까다롭다. 이 글은 윈도우 환경에서 하둡, 스파크 설치 테크트리를 정리해 놓는다. 참고로, 이 글은 분산 메시지 스트리밍 플랫폼으로 많이 사용되는 카파(kafka)는 다루지 않는다. 카파 설치 및 사용법은 아래 링크를 참고바란다.
기타, 이 예제에 대한 상세 내용은 레퍼런스를 참고 바란다.

소프트웨어 다운로드 및 설치
다음 링크에서 Java, Hadoop, Spark 를 다운로드 한다. 
만약, 오라클 Java Licence 문제를 피하고 싶다면 OpenJDK를 대신 다운받아 설치한다. Java, Skala는 설치하고, 다른 프로그램은 압축을 푼 후 hadoop, spark 폴더를 만들어 복사해 넣는다(주의 - 폴더에 공백이나 한글이 포함되면 안됨).

환경 변수 설정
각 설치 프로그램에 대한 실행 경로나 환경 변수를 설정해야 한다. 다음과 같이 시스템 환경 변수를 설정한다.
  • JAVA_HOME: Java 설치 폴더
  • HADOOP_HOME: hadoop 설치 폴더
  • SPARK_HOME: spark 설치폴더
시스템 환경변수 설정 화면
설정된 HADOO_HOME 환경변수

그리고 hadoop, spark, scala 각 프로그램의 bin, sbin폴더를 시스템  Path에 추가해 놓는다. 본인의 경우 다음과 같이 설정하였다.

제대로 환경변수가 설정되었다면, cmd 창을 실행해 각 프로그램 버전을 다음과 같이 확인할 수 있다.
설치 프로그램 버전 확인

설치 후 다음과 같이 hadoop 폴더에 datanode와 namenode 폴더를 만든다.

HADOOP 설정
Hadoop 설치 폴더의 etc/hadoop 폴더에 설정파일을 수정해야 한다. 다음과 같이 수정한다.
다음과 같이 core-site.xml를 수정한다.
<configuration>
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value>
  </property>
</configuration>

다음과 같이 mapred-site.xml 을 수정한다.
<configuration>
  <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
  </property>
</configuration>

다음과 같이 hdfs-site.xml 을 수정한다. 그리고, C:/hadoop 부분을 경로에 맞게 수정한다.
<configuration>
  <property>
    <name>dfs.replication</name>
    <value>1</value>
  </property>
  <property>
    <name>dfs.namenode.name.dir</name>
    <value>file:///C:/Hadoop/hadoop-<version>/namenode</value>
  </property>
  <property>
    <name>dfs.datanode.data.dir</name>
    <value>file:///C:/Hadoop/hadoop-<version>/datanode</value>
  </property>
</configuration>

다음과 같이 yarn-site.xml 을 수정한다.
<configuration>
  <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
  </property>
  <property>
    <name>yarn.nodemanager.auxservices.mapreduce.shuffle.class</name>  
    <value>org.apache.hadoop.mapred.ShuffleHandler</value>
  </property>
</configuration>

Winutils hadoop  윈도우버전 패치
winutils는 hadoop을 윈도우에서 동작하도록 해준다. winutils 링크에서 폴더를 통채로 다운로드 하고, 압축을 푼 후, hadoop-3.1.2/bin의 파일들을 설치된 hadoop의 bin에 폴더에 덮어 쓴다. 

이제 다음과 같이 hdfs namenode 을 포맷한다. 
hdfs namenode -format


그리고, hadoop 설치폴더의 /share/hadoop/yarn/timelineservice 폴더 내 hadoop-yarn-server-timelineservice-<version> 파일을 hadoop 설치폴더 /share/hadoop/yarn 폴더로 복사해 넣는다.

HDFS 서버 시작하기 
이제 다음과 같이 HDFS 서버를 시작한다.
start-dfs.cmd
start-yarn.cmd

그럼 다음과 같이 서버가 실행된다.
HDFS 서버 실행 모습

현재 HDFS 실행 상태를 확인하기 위해 다음과 같이 jps 명령을 입력한다.
jps 실행 후 모습

데쉬보드를 확인하기 위해 크롬에서 localhost:8088, localhost:9870 을 다음처럼 입력한다.
Hadoop 서버 데쉬보드

HADOOP과 Spark 사용 테스트
하둡과 스파크 간단한 테스트를 위해 다음과 같이 hadoop 파일 시스템 내 test 폴더를 만든다.
hdfs dfs -mkdir /test
hdfs dfs -ls /
실행 결과

다음과 같이 spark 설치 폴더의 README 파일을 하둡 파일 시스템에 put 하고 이를 스파크로 테스트해본다.
hdfs dfs -put README.md /test
hdfs dfs -ls -R /
set SPARK_LOCAL_HOSTNAME=localhost
spark-shell

그럼 다음과 같이 spark shell 이 실행된다. 참고로, 실행 전에 SPARK_LOCAL_HOSTNAME=localhost 를 꼭 설정해야 한다(참고 - SparkContext 초기화 에러). 아울러, 스파크는 스칼라 이외에 파이썬, 자바 등 다양한 언어를 제공한다. 파이썬의 경우 pyspark를 실행해 입력할 수 있다.

다음과 같이 코딩해 본다.
scala> List(1, 2, 3).zip(List("a", "b", "c"))
res2: List[(Int, String)] = List((1,a), (2,b), (3,c))

다음과 같이 spark 에서 hadoop 파일을 열고, 읽어본다.
var textFile = sc.textFile("hdfs://localhost:9000/test/README.md")
textFile.first()

다음과 같이 count, filter, map, reduce 함수를 사용해 코딩해 본다.
textFile.count()
textFile.filter(line => line.contains("Spark")).count()
textFile.map(line => line.split(" ").size).reduce((a, b) => if(a > b) a else b)

아래와 같이 결과가 출력되면 성공한 것이다.

스파크는 http://127.0.0.1:4040 에 웹UI를 제공한다.
스파크 웹 UI

스파크 어플리케이션을 스칼라, 파이선, 자바 등으로 개발해 jar 파일을 만들면, spark-submit을 이용해 실행할 수 있다. 예를 들어, NoSQL에 저장된 IoT센서 빅데이터를  스파크, Hadoop으로 병렬처리해 일별, 월별, 년별 분석하는 등의 프로그램을 미리 만들어 놓고 필요할 때 실행할 수 있다. 이에 대한 예제는 다음 링크를 참고한다.

빅데이터 분석 가시화와 NoSQL 데이터베이스
명령행에서 스칼라 코딩하기 어렵거나 분석된 데이터를 손쉽게 가시화하고 싶다면, 데이터 분석 시각화 도구인 제플린(zeppelin Binary package with all interpreters download) 노트북을 다운로드한다. 제플린에서는 UI에서 스파크 함수를 사용해 프로그램 실행할 수 있다.

다운로드 받은 제플린 압축을 풀고, 다음 같이 제플린 서버를 실행한다.
cd bin
zeppelin.cmd

앞서 언급한 환경이 제대로 설정되어 있다면, 다음과 같이 서버가 잘 실행될 것이다.
제플린 서버 실행 모습

브라우저에서 http://localhost:8080/#/ 에 접속한다.
제플린 서버 접속 모습

제플린에서 Create new note 를 클릭한다. Zeppelin Tutorial에서 다음 예제와 데이터를 얻어 실행해 본다. 참고로, 제플린과 스파크 간에 호환성 문제가 있어 에러 발생할 수 있다. 이 경우, 아파치 스파크 2.3.2와 제플린 0.8 버전을 사용한다.
val bankText = sc.textFile("D:/Program/zeppelin-0.8.2-bin-all/sample/bank-full.csv")

case class Bank(age:Integer, job:String, marital : String, education : String, balance : Integer)

// split each line, filter out header (starts with "age"), and map it into Bank case class
val bank = bankText.map(s=>s.split(";")).filter(s=>s(0)!="\"age\"").map(
    s=>Bank(s(0).toInt, 
            s(1).replaceAll("\"", ""),
            s(2).replaceAll("\"", ""),
            s(3).replaceAll("\"", ""),
            s(5).replaceAll("\"", "").toInt
        )
)

// convert to DataFrame and create temporal table
bank.toDF().registerTempTable("bank")


빅데이터 분석된 정보는 NoSQL 데이터베이스인 몽고 DB(MongoDB), 카산드라(Cassandra), HBase 등에 저장할 수 있다. NoSQL은 각 DB마다 장단점이 있고, 응용 목적에 따라 성능이 다르니 이에 맞게 사용해야 한다(참고 - NoSQL DB 비교 #1, #2).

스칼라를 이용해 빅데이터를 시간, 날짜, 월, 년도별로 분석해 MongoDB로 저장하고 싶거나, 센서 데이터를 필터링한 후 필요한 데이터만 저장하고 싶다면, 다음 예시를 참고한다.

마무리
빅데이터 처리를 위해 하둡은 데이터를 분산 저장, 처리 및 병합하는 Map reduce 방식을 사용한다. 스파크는 이를 이용해 대용량 데이터 분석 및 통계 처리를 할 수 있다. 환경 설정이 쉽지는 않지만, 앞의 내용을 잘 따라 설치하면 큰 문제 없이 빅데이터 처리를 지원하는 하둡 개발 환경을 만들 수 있을 것이다.

레퍼런스
스파크 실행 및 관련 환경 예시는 다음을 참고한다.
스칼라 설명 및 스파크 어플리케이션 개발은 다음을 참고한다.
제플린 설치 방법은 아래 링크를 참고한다.
IntelliJ을 설치한 후 바로 Scala 플러그인을 설치해 IDE환경에서 편하게 코딩할 수 있다.
IntelliJ 에서 Scala 플러그인 설치 모습

Cesium 기반 건물 시설물 모니터링 웹 서비스

이 글은 Cesium 기반 건물 시설물 웹 서비스 개발을 위해 필요한 내용을 정리한다. 가능한 검증된 오픈소스를 사용해 확장성과 유지관리성이 좋은 Cesium 기술 스택을 사용한다.


아래 링크를 입력하면 관련 코드를 확인할 수 있다.
이 소스는 Ceisum의 Cesium3DTileset 객체를 생성한 후 primitives 형상으로 뷰어의 scene그래프에 추가하여 3D BIM 형상 모델을 생성한다.
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

var tileset = scene.primitives.add(
    new Cesium.Cesium3DTileset({
        url: Cesium.IonResource.fromAssetId(8564)
    })
);

tileset.readyPromise.then(function(tileset) {
    viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.2, tileset.boundingSphere.radius * 4.0));
}).otherwise(function(error) {
    console.log(error);
});

뷰어 캔버스에 마우스 이벤트를 설정해 픽을 할 경우, 선택된 모델 형상이 무엇인지 확인 가능하다. 이를 이용해 형상의 속성정보 등을 확인할 수 있다.
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function(movement) {
    if (!picking) {
        return;
    }

    var feature = scene.pick(movement.endPosition);

    unselectFeature(selectedFeature);

    if (feature instanceof Cesium.Cesium3DTileFeature) {
        selectFeature(feature);
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

참고로, LoD(level of detail) 옵션을 사용해 형상의 LOD 표현을 다음과 같이 제어할 수 있다.
var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({
     url : 'http://localhost:8002/tilesets/Seattle/tileset.json',
     skipLevelOfDetail : true,
     baseScreenSpaceError : 1024,
     skipScreenSpaceErrorFactor : 16,
     skipLevels : 1,
     immediatelyLoadDesiredLevelOfDetail : false,
     loadSiblings : false,
     cullWithChildrenBounds : true
}));

이 예제는 오픈소스이므로 다운로드한 후 HTTP 서버를 구동해 쉽게 실행할 수 있다.

레퍼런스

2020년 2월 19일 수요일

GeoServer, PostgreSQL 기반 CesiumJS 지도 웹 서버 개발 방법

이 글은 GeoServer 기반 CesiumJS 지도 웹 서버 어플리케이션 개발 방법을 간략히 설명한다. Cesium을 이용하면 WebGL 기반 공간정보 서비스가 가능하며, 3차원 그래픽 모델 등을 표현하기 쉽다. Geoserver를 이용해 사용자가 직접 만든 지도 데이터를 레이어로 렌더링하여, Cesium 플랫폼 위에 보여줄 수 있다.

Cesium 개념 및 사용 방법을 전혀 모른다면 다음 링크를 미리 참고한다.
머리말
이 예제는 Google map, OSM(OpenStreetMap) 등에서 제공하지 않는 형태의 지도 데이터를 사용해 인터렉티브한 맵 어플리케이션을 개발하고 싶을 때 유용하다. 이 예제는 오픈소스로 유명한 GeoServer, PosgreSQL, PostGIS를 사용한다. 이 예제에 대한 좀 더 상세한 내용은 이 글의 마지막 레퍼런스들을 참고하길 바란다. 참고로, 이 예제는 윈도우즈10 환경에서 개발하지만, 환경 설치나 예제 실행은 우분투, 리눅스에서도 똑 같이 동작한다. 다음은 예제에서 사용하는 기술 스택이다.
각 컴포넌트 역할은 다음과 같다.
  • CesiumJS: Cesium 기반 웹 서비스 개발을 지원하는 Javascript 모듈
  • Cesium: WebGL 기반 동적 그래픽 지도 렌더링 및 이벤트 처리를 지원하는 플랫폼
  • Geoserver: 다양한 형식의 지도 레이어 렌더링을 지원하는 서버
  • PostGIS: PostgreSQL 데이터베이스의 정보 검색 SQL 질의 애드인(addin). 
  • PostgreSQL: 객체 관계형 데이터베이스. 다양한 형식의 지도 데이터를 체계적으로 저장, 관리하기 위해 사용
Geoserver, PostgreSQL, PostGIS 설치
자제 지도 데이터를 사용하기 위해서 다양한 형식의 지도 데이터를 렌더링할 수 있는 Geoserver를 사용한다. Geoserver를 설치하기 위해서는 Java SDK가 필요하다. 공간정보 데이터베이스가 필요하다면 PostgreSQL DBMS와 공간정보 질의 기능을 지원하는 PostGIS 애드인을 미리 설치해야 한다. 다음 링크에서 각 설치 프로그램을 다운로드할 수 있다.
우선 Java SDK를 다운로드받아 설치한다. 이후, GeoServer를 다음과 같이 설치한다.
Geoserver 설치
Geoserver start 화면 

다음과 같이 PostgreSQL 설치 후 바로 실행되는 Stack Builder를 통해 PostGIS 애드인을 설치한다. 그 후, 설치된 pgAmin4를 실행해 PostgreSQL 데이터베이스 서버를 시작해 본다.
PostgreSQL과 PostGIS 설치
Stack Builder를 통한 PostGIS 설치 완료 

설치된 프로그램 중 PostgreSQL DBMS 관리를 지원하는 pgAdmin4를 실행한다. 다음과 같은 화면이 보이면 성공한 것이다.
pgAdmin4를 통한 PostgreSQL 서버 실행 모습

CesiumJS Starter Kit 준비 및 실행
Javascript로 동작되는 cesiumjs는 9000 포트, GeoServer는 8080 포트를 사용한다. cesiumjs 어플리케이션 개발을 지원하기 위해, cesium에서는 예제 템플릿과 설명을 다음과 같이 제공한다.
다음과 같이 Starter Kit 소스코드를 다운로드한다.
git clone https://github.com/Cingulara/cesium-starterkit.git

git clone으로 코드를 다운로드 받으면 폴더 안에 index.html 이 있고, 그 안에 css, javascript import 등이 코딩되어 있다. 폴더 내 cesium-scripts.js는 cesiumjs API, 웹 맵 서버와 상호동작 등의 기능을 포함하고 있다. js 파일 18행에 cesium 사용을 위해 다음과 같이 cesium API key 값을 입력한다.
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5MDA1OGJhMi04NTY0LTQ0NDgtOWZkNi02ZWM1YzE1Y2VkODUiLCJpZCI6MjI3NzksInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1ODIwMzI3MTJ9.le7e8JdjCMK94zUohux5Wu-CWF0ydrrh7vSvxU3ZR4U';

이 예제를 웹 서버로 실행하려면 HTTP 서버를 먼저 설치해야 한다.  웹 서버 중에 아파치(설치), NGINX(설치 및 사용법)가 유명하나 이 경우에는 간단하게 python에 내장된 SimpleHTTPServer(python 2.x 버전), http.server(python 3.x 버전)를 이용한다.

우선 Geoserver를 실행한다. 그 후, 다음처럼 HTTP 서버를 실행한다(python 3.x 버전의 경우. 상세 내용은 Local server, Mozilla 참고).
python -m http.server 9000
Geosever와 HTTP server 실행 모습

HTTP 서버 실행 후 크롬에서 localhost:9000 접속하면 다음 같은 결과를 볼 수 있다.
실행 결과

다른 서버 도메인의 자바 스크립트 어플리케이션에서 GeoServer 를 사용하려면 GeoServer의 CORS(Cross-Origin Resource Sharing)을 활성화해야 한다(도메인이 서로 다르면 Javascript에서 Geoserver 데이터를 다운로드할 수 없다). 우선 GeoServer 가 설치된 경로에서 web.xml 파일을 찾아 CORS filter 및 filter-mapping xml 태그 주석을 아래와 같이 해제한다. 그리고, Geoserver를 재시작한다.
geoserver web.xml 수정

CORS에 대한 좀 더 상세한 내용은 다음 링크를 참고한다.
Geoserver와 CesiumJS 연동 코드 분석
Geoserver와 같은 지도 이미지 제공 서버를 위해 세슘은 Cesium.WebMapServiceImageryProvider 함수를 제공한다. cesium-scripts.js의 44행에는 상호 연동을 위한 코드가 다음과 같이 구현되어 있다. 이 함수는 Geoserver webservice API url 및 관련 parameters를 입력받는다. 코드를 분석해 보면, 2개의 레이어를 추가하고 있는 데, 플로리다 이미지(florida:NE1_50M_SR_W) 및 도로망 맵(florida:FL_planet_osm_roads)이다. 이 맵 데이터 모두 geoserver에서 webservice 방식인 wms(web mapping server)로 제공한다.
    var worldMap = new Cesium.WebMapServiceImageryProvider({
        url : 'http://localhost:8080/geoserver/wms',
        parameters: {
            format:'image/png',
            transparent:'true',
            tiled: true,
            enablePickFeatures: true
        },
        layers : 'florida:NE1_50M_SR_W',  // comma separated listing
        maximumLevel : 12
    }); 
...
    var detailedMaps = new Cesium.WebMapServiceImageryProvider({
        url : 'http://localhost:8080/geoserver/wms',
        parameters: {
            format:'image/png',
            transparent:'true',
            tiled: true,
            enablePickFeatures: true
        },
        layers : 'florida:FL_planet_osm_roads',  // comma separated listing
        maximumLevel : 20
    }); 

florida:NE1_50M_SR_W, florida:FL_planet_osm_roads 모두 geoserver에 등록되어 있지 않으므로, 해당 지도 데이터를 준비해 geoserver 레이어로 추가해야 한다.

참고로, js 소스 안에 레이어들을 켜고 끌 수 있는 changeLayer함수가 정의되어 있다. 원하면, 레이어를 이런 방식으로 조작할 수 있다.
function changeLayers(value) {
    if (value == "satellite"){
        viewer.imageryLayers._layers[0].show = true;
        viewer.imageryLayers._layers[1].show = false;
        viewer.imageryLayers._layers[2].show = false;
    }
    else if (value == "roads"){
        viewer.imageryLayers._layers[0].show = true;
        viewer.imageryLayers._layers[1].show = false;
        viewer.imageryLayers._layers[2].show = true;
    }
    else if (value == "hybrid"){
        viewer.imageryLayers._layers[0].show = false;
        viewer.imageryLayers._layers[1].show = true;
        viewer.imageryLayers._layers[2].show = true;
    }
}

GeoServer 지도 데이터 레이어 추가
앞서 설명했듯이 본 예제의 js 소스코드에 두개 지도 데이터 형식을 Geoserver에 레이어로 추가해야 한다. 오픈소스 QGIS, Arcgis, CAD를 이용해 지도 데이터를 만들 수 있겠지만, 지금은 맵 서비스 개발 방법 이해가 중요하므로, 기존에 만들어진 지도 데이터를 다운로드해 사용할 것이다. 이 예제에서 지도 데이터를 가공해 맵 서비스를 개발하는 과정은 다음과 같다.

우선, florida:NE1_50M_SR_W 레이어 용 GeoTiff 형식 데이터를 준비한다. 이를 위해, naturalearthdata.com 을 방문하여 Shaded Relief and Water 데이터를 다음과 같이 다운로드 한다.
다운로드 압축파일을 풀고, geoserver의 data 폴더에 worldmap 폴더를 아래와 같이 만들어 넣는다.

Geoserver를 시작하고, 다음과 같이 새로운 작업공간을 만든다.

Geoserver에서 새로운 데이터 저장소를 선택하여, 다음과 같이 GeoTiff 메뉴를 선택해 앞에서 다운로드한 NE1_50M_SR_W.tif 파일을 업로드한다.



GeoTiff 파일이 추가되면, 다음과 같이 레이어 미리보기에서 해당 지도 데이터를 확인할 수 있다.


이제 florida:FL_planet_osm_roads 지도 데이터 레이어를 만들 차례다. psql CLI(command line interface)를 실행해 다음과 같은 순서로 PostgreSQL에 데이터베이스를 만들고, postgis를 사용하기 위해 확장 구조를 DB에 등록한다.
create database floridamaps;
\c floridamaps;
create extension postgis;
create extension postgis_topology;
create extension hstore;

psql 명령 입력 모습
PostgreSQL DB 생성 결과

geofabrik.de 사이트에서 OSM 지도 데이터를 다운로드 받는다. 압축울 풀고, osm2pgsql 프로그램(설치 방법)을 이용해 다음과 같이 PostgreSQL DB(database)로 변환한다.
osm2pgsql -C 2048 -s -H localhost -P 5432 -d floridamaps ./florida-latest.osm -C 2048 --slim --number-processes 2 --password --username postgres

osm2pgsql 의 --username 은 PostgreSQL DB username 과 동일하다. PostgreSQL 설치 시 디폴트는 postgres 이다. --password 옵션을 추가하면 사용자 암호 입력을 받는다. 설치 시 postgres 유저에 대한 암호를 입력하면, 다음과 같이 변환 과정이 실행된다.
osm2pgsql 변환 과정

변환 작업이 끝나면, 다음과 같이 pgAdmin의 SQL창에서 변환된 지도 데이터를 확인해 본다. 쿼리 결과가 올바르면 OSM 지도 데이터가 정상 변환 된 것이다.
SELECT COUNT(*) FROM public.planet_osm_point
UNION
SELECT COUNT(*) FROM public.planet_osm_line
UNION
SELECT COUNT(*) FROM public.planet_osm_polygon

쿼리 결과

이제 다음과 같이 새로운 저장소로 PostGIS을 추가한다. 앞서 생성한 PostgreSQL database, user, password를 입력한다.

다음과 같이 Geoserver에 생성된 저장소의 planet_osm_roads를  florida:FL_planet_osm_roads 레이어로 추가한다. 이 때, 레이어 최소 경계 영역은 '데이터로부터 계산하기', '원본 영역으로부터 계산하기'버튼을 클릭해 자동 입력해 넣는다.
레이어 추가 화면

레이어가 정상 추가되었다면 다음과 같이 레이어 미리보기를 할 수 있다. OpenLayers 버튼을 클릭해 지도가 제대로 표시되는 지 확인하자.
레이어 미리보기
미리보기 결과 

GeoServer에 다음과 같이 레이어 맵이 추가되어 있다면, Cesium에서 이 레이어를 렌더링해 오버레이 할 수 있다. 레이어 명과 앞서 설명한 cesium-scripts.js 소스코드 안에 지정된 레이어 명은 서로 같아야 함에 주의한다.

다시 HTTP server를 실행해 Geoserver에서 제공되는 레이어가 표시된 CesiumJS 지도 앱 어플리케이션 실행 결과를 확인해 본다. 
Cesium Roads 레이어 켜졌을 때 지도 앱 모습 
 
지도 앱 어플리케이션 실행 결과
Geoserver에서 다운로드되는 지도 데이터 로그와 PostgreSQL Transactions 처리 모습

CesiumJS 기반 지도 앱 서버가 잘 실행되는 것을 확인할 수 있다. 만약, 제대로 동작하지 않는 다면 크롬에서 개발자 도구로 디버깅 로그를 확인해 보길 바란다. 각 단계를 따라하였다면 잘 실행될 것이다.

마무리
최근 스마트시티, 디지털 트윈, 스마트 건설과 같은 서비스 요구가 많아지고 있다. 이런 서비스에 공간정보는 중요한 서비스 기반이다. 세슘 플랫폼을 이용하면 다양한 데이터 소스와 연결하여 공간정보 기반으로 시각화하고 의사결정 데이터 서비스를 쉽게 개발할 수 있다.

본 글에서 설명한 Cesium Start kit 예제를 목적에 맞게 확장하기는 어렵지 않다. Geoserver를 사용하면 사용자가 Cesium에서 기본 제공하지 않은 지도 형식을 쉽게 확장할 수 있다. cesuim은 webgl을 기반으로 하는 다양한 데이터 소스 연결, 2D/3D 그래픽 생성, 이벤트 처리, 서버 연결 방법 등을 제공하고 있기 때문에 인터렉티브한 맵 어플리케이션 서버 개발이 그리 어렵지 않을 것이다.

레퍼런스

2020년 2월 16일 일요일

OpenStreetMap 타일 서버 개발

이 글은 OSM(OpenStreetMap) 타일 서버 개발하는 예제를 간략히 다룬다. 

타일 서버 개발 결과

OSM이란
OSM은 전세계 지도를 커버하는 사용자가 작성하는 맵이다. OSM은 매분 업데이트되고, 무료이다. 그리고, 상세한 데이터를 제공한다. 참고로, 이 예제는 우분투 기반으로 실습한다.

HW 환경
영국 지도만 4G RAM, 60GB 디스크가 필요하다. 전체 planet map은 32G RAM, 1TB SSD가 필요하다.

소프트웨어 준비
sudo apt update; sudo apt upgrade

PostgreSQL Database Server와 PostGIS Extension 설치
다음과 같은 명령을 통해 PostgreSQL DB와 PostGIS 확장을 설치한다.
sudo apt install postgresql postgresql-contrib postgis postgresql-10-postgis-2.4
sudo -u postgres -i
createuser osm
createdb -E UTF8 -O osm gis

psql -c "CREATE EXTENSION postgis;" -d gis
psql -c "CREATE EXTENSION hstore;" -d gis
Set osm as the table owner.

psql -c "ALTER TABLE spatial_ref_sys OWNER TO osm;" -d gis
Exit from the postgres user.
exit

sudo adduser osm

맵 스타일시트와 맵 데이터 다운로드
맵 데이터를 다음 명령으로 다운로드 한다.
su - osm
wget https://github.com/gravitystorm/openstreetmap-carto/archive/v4.20.0.tar.gz
tar xvf v4.20.0.tar.gz

만약 planet data 다운로드하려면 아래와 같이 진행한다.
wget -c http://planet.openstreetmap.org/pbf/planet-latest.osm.pbf

다음은 영국지도이고 다운로드를 위해서는 985M 용량이 필요하다.
wget -c http://download.geofabrik.de/europe/great-britain-latest.osm.pbf
Exit from osm user.
exit

맵 데이터 임포트
맵 용량이 크므로 swap file을 조정한다.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile

Output:
Setting up swapspace version 1, size = 2097148 KiB
no label, UUID=h32b3e10-0779-4865-9ea0-6e2af8f3kea9
Enable the swap file

sudo swapon /swapfile
sudo nano /etc/ssh/ssh_config
ServerAliveInterval 60

PostgreSQL에 맵 데이터 임포트 
맵 데이터 임포트를 위해, osm2pgsql을 사용해 OSM 데이터를 postGIS 지원용 PostgreSQL DB로 변환한다.
sudo apt install osm2pgsql
su - osm
osm2pgsql --slim -d gis --hstore --multi-geometry --number-processes 8 --tag-transform-script /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.lua --style /home/osm/openstreetmap-carto-4.20.0/openstreetmap-carto.style -C 12000 /home/osm/great-britain-latest.osm.pbf
exit

mod_tile와 Renderd 설치
다음과 같이 mod-tile과 renderd를 설치한다.
sudo add-apt-repository ppa:osmadmins/ppa
sudo apt install libapache2-mod-tile renderd
systemctl status renderd

Mapnik Stylesheet 생성
다음과 같이 mapnik과 sytlesheet를 생성한다.
sudo apt install curl unzip gdal-bin mapnik-utils libmapnik-dev nodejs npm
sudo npm install -g carto
su - osm
cd /home/osm/openstreetmap-carto-4.20.0/
scripts/get-shapefiles.py
carto project.mml > style.xml
exit

Fonts 설치
맵에 사용될 폰트로 설치한다.
sudo apt install ttf-dejavu
sudo apt install fonts-noto-cjk fonts-noto-hinted fonts-noto-unhinted ttf-unifont

renderd 설정
renderd를 다음과 같이 설정한다.
sudo nano /etc/renderd.conf
num_threads=4
XML=/home/osm/openstreetmap-carto-4.20.0/style.xml
HOST=map.your-domain.com

plugins_dir=/usr/lib/mapnik/3.0/input/
mapnik-config --input-plugins

font_dir=/usr/share/fonts/truetype
font_dir_recurse=true

sudo nano /etc/init.d/renderd
RUNASUSER=osm

renderd를 실행한다.
sudo chown osm:osm /var/lib/mod_tile/ -R
sudo systemctl daemon-reload
sudo systemctl restart renderd
sudo journalctl -eu renderd

Apache 설정
아파치 웹 서버를 설정한다.
sudo nano /etc/apache2/sites-available/tileserver_site.conf
ServerName map.yourdomain.com
sudo systemctl restart apache2
map.your-domain.com/osm/0/0/0.png
osm-tile-for-world-map

Tiled Web Map 표시
openlayers를 다운로드해 설치한다.
cd /var/www/
sudo wget https://github.com/openlayers/openlayers/releases/download/v5.3.0/v5.3.0.zip
sudo unzip v5.3.0.zip

index.html file 생성
index파일을 아래와 같이 수정한다.
sudo nano /var/www/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Accessible Map</title>
<link rel="stylesheet" href="http://map.yourdomain.com/v5.3.0/css/ol.css" type="text/css">
<script src="http://map.yourdomain.com/v5.3.0/build/ol.js"></script>
<style>
  a.skiplink {
    position: absolute;
    clip: rect(1px, 1px, 1px, 1px);
    padding: 0;
    border: 0;
    height: 1px;
    width: 1px;
    overflow: hidden;
  }
  a.skiplink:focus {
    clip: auto;
    height: auto;
    width: auto;
    background-color: #fff;
    padding: 0.3em;
  }
  #map:focus {
    outline: #4A74A8 solid 0.15em;
  }
</style>
</head>
<body>
  <a class="skiplink" href="#map">Go to map</a>
  <div id="map" class="map" tabindex="0"></div>
  <button id="zoom-out">Zoom out</button>
  <button id="zoom-in">Zoom in</button>
  <script>
    var map = new ol.Map({
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM({
             url: 'http://map.yourdomain.com/osm/{z}/{x}/{y}.png'
          })
       })
     ],
     target: 'map',
     controls: ol.control.defaults({
        attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
          collapsible: false
        })
     }),
    view: new ol.View({
       center: [244780.24508882355, 7386452.183179816],
       zoom:5
    })
 });

  document.getElementById('zoom-out').onclick = function() {
    var view = map.getView();
    var zoom = view.getZoom();
    view.setZoom(zoom - 1);
  };

  document.getElementById('zoom-in').onclick = function() {
     var view = map.getView();
     var zoom = view.getZoom();
     view.setZoom(zoom + 1);
  };
</script>
</body>
</html>

다음과 같이 leaflet 패키지를 설치한다.
cd /var/www/
sudo wget http://cdn.leafletjs.com/leaflet/v1.4.0/leaflet.zip
sudo unzip leaflet.zip

index파일에 leaflet을 사용할 수 있도록 지정한다.
sudo nano /var/www/index.html

<html>
<head>
<meta charset="UTF-8">
<title>My first osm</title>
<link rel="stylesheet" type="text/css" href="leaflet.css"/>
<script type="text/javascript" src="leaflet.js"></script>
<style>
   #map{width:100%;height:100%}
</style>
</head>

<body>
  <div id="map"></div>
  <script>
    var map = L.map('map').setView([53.555,9.899],5);
    L.tileLayer('http://map.yourdomain.com/osm/{z}/{x}/{y}.png',{maxZoom:18}).addTo(map);
</script>
</body>
</html>

map.yourdomain.com/index.html

Pre-render Tiles
지도 타일을 렌더링한다.
render_list -m default -a -z 0 -Z 15 --num-threads=8
render_list -m default -a -z 0 -Z 15 --num-threads=8 --force
sudo add-apt-repository ppa:certbot/certbot
sudo apt install certbot
sudo apt install python3-certbot-apache
sudo certbot --apache --agree-tos --redirect --hsts --staple-ocsp --must-staple --email
osm tile server ubuntu 18.04 install

sudo a2enmod http2
sudo nano /etc/apache2/sites-enabled/tileserver_site-le-ssl.conf
Protocols h2 http/1.1
sudo systemctl restart apache2
nano /home/osm/openstreetmap-carto-4.20.0/project.mml

osm2pgsql: &osm2pgsql
  type: "postgis"
  dbname: "gis"
  key_field: ""
  geometry_field: "way"
  extent: "-20037508,-20037508,20037508,20037508"
Specify the IP address of PostgreSQL database server.

osm2pgsql: &osm2pgsql
  type: "postgis"
  host: "10.0.0.2"
  dbname: "gis"
  key_field: ""
  geometry_field: "way"
  extent: "-20037508,-20037508,20037508,20037508"

스타일을 설정하고, postgresql과 renderd를 재시작한다.
carto project.mml > style.xml
sudo nano /etc/postgresql/10/main/postgresql.conf
sudo nano /etc/postgresql/10/main/pg_hba.conf
host   gis   osm   10.0.0.1/32   trust
sudo systemctl restart postgresql
sudo systemctl restart renderd

sudo journalctl -eu renderd
sudo ufw allow in from 10.0.0.1 to any port 5432

레퍼런스