본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


OpenLayers(오픈레이어스)

Openlayers Flight Animation (오픈레이어스 항공 애니메이션, Openlayers 항공 애니메이션, Openlayers 비행 애니메이션)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2019. 9. 24.

 

 

이번엔 비행기가 지나가는 노선도 효과의 에니메이션 입니다.

레이어는 항상 사용하던 기본 OSM 레이어를 사용 하였습니다.

일단, 시작점과 끝점이 존재하는 배열을 생성 하였습니다. 출발나라와 도착 나라의 개념의 위경도를 배열에 담았습니다.

var flights = new Array();
flights[0] = [[126.11,37.12], [7.51,11.831]];
flights[1] = [[110.11,25.12], [178.21,33.561]];
flights[2] = [[55.11,43.12], [47.21,13.42]];
flights[3] = [[12.11,3.12], [126.21,37.42]];
flights[4] = [[-113.11,39.12], [45.42,37.21]];

 

다음 단계는 백터 레이어에 해당 배열의 값을 각각 그림을 그릴 객체로 집어넣는 부분 입니다.

var flightsSource = new VectorSource();
var flightsLayer = new VectorLayer({
  source: flightsSource,
  style: function(feature) {  //일단 스타일은 null이라는것만 기억하자.
    return null;
  }
});


function makeAirLine(){
  flights.forEach( (flight, i)=>{
    var from = flight[0];
    var to = flight[1];
    var arcGenerator = new arc.GreatCircle(  //이건 구조체를 그리는데 도움을 주는 라이브러리 이다.
      { x: from[0], y: from[1] },
      { x: to[0], y: to[1] }
    );
    var arcLine = arcGenerator.Arc(100, {offset: 50} );  //라인이 그려진다.
    if (arcLine.geometries.length === 1) {
      var line = new LineString(arcLine.geometries[0].coords);  //LineString 객체를 통해 맵에서 사용 가능한 형태로 조립하고
      line.transform('EPSG:4326', 'EPSG:3857');
      var feature = new Feature({  //구조물을 만들어
        geometry: line
      });
      feature.set('startTime', new Date().getTime());  //해당 값은 이벤트의 시작 종료를 위해 필요 하다.
      feature.set('myIndex', i);
      flightsSource.addFeature(feature);  //벡터레이어가 참조하는 백터소스에 추가하여준다.
    }  
  });  
}

makeAirLine();

 

makeAirLine이라는 함수를 통해서 배열의 값을 벡터레이어에서 사용 할 수 있는 벡터소스로 변환 하였습니다.

이때 startTime이라는 값을 현재 시간값을 주어서 이벤트가 발생한 시간 ~ 종료시간을 확인하기 위한 데이터로 사용 하였습니다.

여기까지하면 객체만 등록되었을 뿐 아무런 행위를 하지 않는 것으로 나옵니다.

제대로 등록되어있는지 확인 하려면 flightsLayer 부분에서 style을 null로 준 부분에 스타일을 주면 확인이 가능 합니다.

그리고 GreatCircle를 사용하려면 index.html에 아래 스크립트를 추가해야 합니다.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="https://api.mapbox.com/mapbox.js/plugins/arc.js/v0.1.0/arc.js"></script> <!-- 수학적(?) 계산을 도와주는 녀석이다.. -->
    <title>Air</title>
    <style>
      #map {
        width: 600px;
        height: 650px;
      }
      .left{
        float: left;
        margin: 2%;
      }
    </style>
  </head>
  <body>
    <div id="map" class="left"></div>

    <script src="./index.js"></script>
  </body>
</html>

 

1차로 적용시켜 본 스크립트 코드 입니다.

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import { OSM } from 'ol/source.js';
import OlTileLayer from 'ol/layer/Tile.js';
import point from 'ol/geom/Point';

//비행효과 라이브러리들
import Feature from 'ol/Feature.js';
import LineString from 'ol/geom/LineString.js';
import VectorSource from 'ol/source/Vector.js';
import {Vector as VectorLayer} from 'ol/layer.js';
import {Stroke, Style} from 'ol/style.js';

var pnt = new point([55.11,43.12]).transform('EPSG:4326', 'EPSG:3857');
var africa = pnt.getCoordinates();
   
var layer = new OlTileLayer({ 
  source: new OSM()  
});

var myView = new View({
  center: africa,
  zoom: 1
}); 


var style = new Style({
  stroke: new Stroke({
    color: 'rgba( 41, 129, 63 ,0.7)',
    width: 2
  })
});  

var flights = new Array();
flights[0] = [[126.11,37.12], [7.51,11.831]];
flights[1] = [[110.11,25.12], [178.21,33.561]];
flights[2] = [[55.11,43.12], [47.21,13.42]];
flights[3] = [[12.11,3.12], [126.21,37.42]];
flights[4] = [[-113.11,39.12], [45.42,37.21]];

var flightsSource = new VectorSource();
var flightsLayer = new VectorLayer({
  source: flightsSource,
  style: function(feature) {  //스타일을 줘버리면 첨부터 그려진상태로 진행된다.
    return style;  //디버그 의미로 한번 줘 보자
  }
});



function makeAirLine(){
  flights.forEach( (flight, i)=>{
    var from = flight[0];
    var to = flight[1];
    var arcGenerator = new arc.GreatCircle( //이건 구조체를 그리는데 도움을 주는 라이브러리 이다.
      { x: from[0], y: from[1] },
      { x: to[0], y: to[1] }
    );
    var arcLine = arcGenerator.Arc(100, {offset: 50} );  //라인이 그려진다.
    if (arcLine.geometries.length === 1) {
      var line = new LineString(arcLine.geometries[0].coords);  //LineString 객체를 통해 맵에서 사용 가능한 형태로 조립하고
      line.transform('EPSG:4326', 'EPSG:3857');
      var feature = new Feature({   //구조물을 만들어
        geometry: line
      });
      feature.set('startTime', new Date().getTime()); //해당 값은 이벤트의 시작 종료를 위해 필요 하다.
      feature.set('myIndex', i);
      flightsSource.addFeature(feature); //벡터레이어가 참조하는 백터소스에 추가하여준다.
    }  
  });  
}

makeAirLine();

var map = new Map({
  layers: [layer, flightsLayer],
  target: 'map',
  view: myView
});

 

특별한 문제가 없다면(뭔가 다르게 바꾸지 않았다면) 아래 사진처럼 노선도가 바로 띵~ 하고 동작하는 것을 볼 수 있습니다.

애니메이션 효과는 없지만 노선도 느낌이..나긴합니다.

 

다시 아까 스타일을 준 부분은 null처리하고 애니메이션을 위한 다음 작업을 하여 보겠습니다.

맵이 렌더링 하는동안 지속적으로 할 행동을 정의하여 줍니다.

var duration = 0.03;  //주기

function airPlainAnimation(evt) {  //이벤트를 받아서
  var vectorContext = evt.vectorContext;  //백터대상
  var frameState = evt.frameState;
  vectorContext.setStyle(style);  //스타일을 주고
  
  var features = flightsSource.getFeatures(); //이전에 만들어둔 백터소스를 가져와
  for (var i = 0; i < features.length; i++) {   //반복문을 통해서 이동시킨다.
    var feature = features[i];
    var coords = feature.getGeometry().getCoordinates();  //좌표
    var elapsedTime = frameState.time - feature.get('startTime');  //아까 만들어둔 시작시간
    var elapsedPoints = elapsedTime * duration;

    if (elapsedPoints >= coords.length) {  //끝나면 뭐할래?
      flightsSource.clear();
      makeAirLine();  //반복
    } else {
      //좌표 이동을 위한계산식
      var maxIndex = Math.min(elapsedPoints, coords.length);
      var currentLine = new LineString(coords.slice(0, maxIndex));
      vectorContext.drawGeometry(currentLine);      
    }
  }
  map.render();  //렌더링 시작하세요 맵!
}

 

해당 계산식 부분은 사실..작성한 필자도 100% 이해하지는 못 하였습니다..ㅠ

주석처리한 "끝나면 뭐할래?" 라는 부분을 보면 에어라인 그린 그림을 초기화하고 다시 그리는 부분을 넣어 주었 습니다.

해당 함수를 이제 맵에 달아줄 차례 입니다.

map.on('postcompose', airPlainAnimation);  //postcompose는 지도를 그린후 발생해라 의미

 

요기까지 작성하면 에니메이션 효과가 들어간 노선도 느낌의 모양을 볼 수 있습니다.

최종 완성된 스크립트 입니다.

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import { OSM } from 'ol/source.js';
import OlTileLayer from 'ol/layer/Tile.js';
import point from 'ol/geom/Point';

//비행효과 라이브러리들
import Feature from 'ol/Feature.js';
import LineString from 'ol/geom/LineString.js';
import VectorSource from 'ol/source/Vector.js';
import {Vector as VectorLayer} from 'ol/layer.js';
import {Stroke, Style} from 'ol/style.js';

var pnt = new point([55.11,43.12]).transform('EPSG:4326', 'EPSG:3857');
var africa = pnt.getCoordinates();
   
var layer = new OlTileLayer({ 
  source: new OSM()  
});

var myView = new View({
  center: africa,
  zoom: 1
}); 


var style = new Style({
  stroke: new Stroke({
    color: 'rgba( 41, 129, 63 ,0.7)',
    width: 2
  })
});  

var flights = new Array();
flights[0] = [[126.11,37.12], [7.51,11.831]];
flights[1] = [[110.11,25.12], [178.21,33.561]];
flights[2] = [[55.11,43.12], [47.21,13.42]];
flights[3] = [[12.11,3.12], [126.21,37.42]];
flights[4] = [[-113.11,39.12], [45.42,37.21]];

var flightsSource = new VectorSource();
var flightsLayer = new VectorLayer({
  source: flightsSource,
  style: function(feature) {  //스타일을 줘버리면 첨부터 그려진상태로 진행된다.
    return null;  
  }
});



function makeAirLine(){
  flights.forEach( (flight, i)=>{
    var from = flight[0];
    var to = flight[1];
    var arcGenerator = new arc.GreatCircle( //이건 구조체를 그리는데 도움을 주는 라이브러리 이다.
      { x: from[0], y: from[1] },
      { x: to[0], y: to[1] }
    );
    var arcLine = arcGenerator.Arc(100, {offset: 50} );  //라인이 그려진다.
    if (arcLine.geometries.length === 1) {
      var line = new LineString(arcLine.geometries[0].coords);  //LineString 객체를 통해 맵에서 사용 가능한 형태로 조립하고
      line.transform('EPSG:4326', 'EPSG:3857');
      var feature = new Feature({   //구조물을 만들어
        geometry: line
      });
      feature.set('startTime', new Date().getTime()); //해당 값은 이벤트의 시작 종료를 위해 필요 하다.
      feature.set('myIndex', i);
      flightsSource.addFeature(feature); //벡터레이어가 참조하는 백터소스에 추가하여준다.
    }  
  });  
}

makeAirLine();

var map = new Map({
  layers: [layer, flightsLayer],
  target: 'map',
  view: myView
});


var duration = 0.03;  //주기

function airPlainAnimation(evt) {  //이벤트를 받아서
  var vectorContext = evt.vectorContext;  //백터대상
  var frameState = evt.frameState;
  vectorContext.setStyle(style);  //스타일을 주고
  
  var features = flightsSource.getFeatures(); //이전에 만들어둔 백터소스를 가져와
  for (var i = 0; i < features.length; i++) {   //반복문을 통해서 이동시킨다.
    var feature = features[i];
    var coords = feature.getGeometry().getCoordinates();  //좌표
    var elapsedTime = frameState.time - feature.get('startTime');  //아까 만들어둔 시작시간
    var elapsedPoints = elapsedTime * duration;

    if (elapsedPoints >= coords.length) {  //끝나면 뭐할래?
      flightsSource.clear();
      makeAirLine();  //반복
    } else {
      //좌표 이동을 위한계산식
      var maxIndex = Math.min(elapsedPoints, coords.length);
      var currentLine = new LineString(coords.slice(0, maxIndex));
      vectorContext.drawGeometry(currentLine);      
    }
  }
  map.render();  //렌더링 시작하세요 맵!
}

map.on('postcompose', airPlainAnimation);  //postcompose는 지도를 그린후 발생해라 의미

 

실제로 동작하는 모습 입니다.

코리아 에어라인...

 

일단 오픈레이어스 4 부터가 ES6문법에다가 npm환경에서 개발하기 쉽도록 되어있어서 그런지 자료가 많지가 않았습니다.

공식 홈피에서도 내용 이해가 쉽지가 않아서 원하는 기능을 전부 만들지는 못 하였던 것 같습니다.

 

 

* 추가(2019-10-02)

노선도별 색깔을 바꾸고 싶다면 setStyle을 반복문 내부에서 하는 구조로 변경하면 됩니다.

var duration = 0.03;  //주기
var colorCode = ['rgba(41,129,63,0.8)','rgba(129,41,41,0.8)','rgba(54,41,129,0.8)','rgba(120,129,41,0.8)','rgba(34,147,169,0.8)','rgba(41,129,63,0.8)'];

function airPlainAnimation(evt) { 
  var vectorContext = evt.vectorContext;  
  var frameState = evt.frameState;
  
  //vectorContext.setStyle(style);  기존 내용 주석!!!!!!
  var features = flightsSource.getFeatures(); 
  for (var i = 0; i < features.length; i++) { 

    vectorContext.setStyle( new Style({  //원래 코드는 바깥에서 1개의 스타을을 바라보도록 되어 있었다.
      stroke: new Stroke({
        color: colorCode[i],
        width: 2
      })
    }));  //스타일을 각각 주기

    var feature = features[i];
    var coords = feature.getGeometry().getCoordinates();  
    var elapsedTime = frameState.time - feature.get('startTime'); 
    var elapsedPoints = elapsedTime * duration;

    if (elapsedPoints >= coords.length) {  
      flightsSource.clear();
      makeAirLine();  
    } else {
      var maxIndex = Math.min(elapsedPoints, coords.length);
      var currentLine = new LineString(coords.slice(0, maxIndex));
      vectorContext.drawGeometry(currentLine);      
    }
  }
  map.render();  
}

 

아래 화면은 색깔이 다양하게 표출되는 모습 입니다.

요렇게 각각 색깔로 나옵니다!

 

 

아래 첨부파일은 위의 스타일이 각각 적용된 버전이 아니라 일괄적용된 버전입니다.

Open_test.7z
0.72MB

 

* 해당 내용은 npm과 parcel 환경으로 구성되어 있습니다.

* 아래 주소를 통해 환경구성을 하면 이해하기 쉽습니다.

https://lts0606.tistory.com/194?category=726697

 

Openlayers 사용법 (Openlayers 개발 환경 구성,오픈레이어스 개발환경, Nodejs 활용)

오픈레이어스는 브라우저에서 지도를 활용한 정보를 표출하는 라이브러리이다. 네이버지도, 구글지도 및 다음지도 처럼 GIS(geographic information system) 라는 명칭으로 불리우며 지형정보 서버를 따로 구축 할..

lts0606.tistory.com

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글