Cesium.js, v-world WebGL 3d 지도 표출 (feat.Vue.js)

강정우·2023년 8월 12일
0

JavaScript

목록 보기
42/53
post-thumbnail

지도 생성

  • 앞으로 설명은 vue.js를 기준으로 설명된다. 물론 ui를 업뎃하기위해 react의 state나 vue의 ref로 관리하는 것은 같은 개념이니 크게 다르지 않을 것이라 생각된다.

설치 (front-end frameworks)

  • 우선 가장 애를 먹은 설치 부분이다. 우선 v-world에서 제공하는 WebGL의 3d 지도를 사용하기위해서는 해당 라이브러리를 설치해주어야한다.
    하지만 당연 npm에서 제공하는 라이브러리는 따로없고 오직 CDN 방법을 통해서만 설치가 가능하다.

  • 이때 가장 먼저 생각나는 것은 당연 "<script>를 react, vue 차원에서 추가하면 되는 거겠지?" 라고 생각할 수 있다.

  • 그럼 우선 시행착오부터 살펴보자. 위 하이퍼링크를 통해 해당 사이트에 들어가면

<script type="text/javascript" src="http://map.vworld.kr/js/webglMapInit.js.do?version=2.0&apiKey=[인증키]"></script>
  • 위 태그를 추가하라고 나와있다. 그래서
const script = document.createElement("script");
  script.src = "http://map.vworld.kr/js/webglMapInit.js.do?version=2.0&apiKey=[apikey]&domain=[domainurl]";
  script.async = true;
  document.head.appendChild(script);
  • 이러한 JS 동적으로 추가하면 되겠지? 하면 틀렸다!!! => 이런식으로 추가하게되면 요청한 url에 맞춰 해당 값만 window 객체에 추가되게 되는데 여기서 가장 중요한 vw 객체가 없다. => 뭔소리임?
var v_protocol="https://"; 
var vworldUrl='https://map.vworld.kr'; 
var vworld2DCache='https://2d.vworld.kr/2DCache'; 
var vworldBaseMapUrl='https://xdworld.vworld.kr/2d'; 
var vworldStyledMapUrl='https://2d.vworld.kr/stmap'; 
var vworldIsValid = 'true'; 
var vworldErrMsg = ''; 
var vworldApiKey = '본인 key 값'; 
var vworld3DUrl = '/js/webglMapInit.js.do'; document.write(""); document.write(""); document.write("");
  • 본인의 key값에 맞춰 <script>에 src url을 검색해보면 나오는 위의 값들이 window 객체에 추가되는데 여기서는 눈씻고 찾아봐도 vw 객체를 찾아볼 수 없다.
    그럼이게 동적으로 추가되는 거니까 next.js를 이용해서 serverside에서 완성시켜서 넘겨주면 되지 않을까? => 결론 부터 말하자면 똑같이 안 된다.
    결국은 JS에서 만들어져서 넘어오는 거라 client side와 결과값은 똑같다.

즉, FE에서 v-world의 WebGL을 사용하고 싶다면 index.html에 위 <script> 태그를 추가해주어야한다. 물론 FE의 근본적인 원칙에 어긋나지만 어쩔 수 없다....
아니면 해당 페이지만 따로 vanilla로 작성하여 spring static폴더에 넣어주면 된다.

사용

vw.value = window.vw
var mapOptions = new vw.value.MapOptions(
  vw.value.BasemapType.GRAPHIC,
  "",
  vw.value.DensityType.FULL,
  vw.value.DensityType.BASIC,
  false,
  new vw.value.CameraPosition(
    new vw.value.CoordZ(126.921883, 37.524370, 482400),
    new vw.value.Direction(0, -90, 0),
  ),
  new vw.value.CameraPosition(
    new vw.value.CoordZ(126.921883, 37.524370, 2982400),
    new vw.value.Direction(0, -90, 0)
  )
);
map3d.value = new vw.value.Map("vmap", mapOptions);
ws3d.value = window.ws3d;
vw3dViewer.value = window.ws3d.viewer;
  • 참고로 위 코드는 vue.js 코드로 작성된 부분이다. window 객체에 담겨있는 vw 객체에 접근하여 기본적인 Map을 만들어주는 코드를 작성한 후
    Cesium.js의 viewer를 대신할 vw3dViewer를 ref 변수에 저장한다.
    또한 Cesium.js의 각종 변수를 담는 vw3d 변수를 생성하여 담아준다.

주의점

  • 이제 map을 생성하였다면 v-world에서 기능을 찾아보고 없는 부분에 한하여 Cesium.js에서 구현하면 된다.

  • 이때 코드가 이상하게 적용이 안 되어서 애를 먹었던 2부분에 대하여 설명하겠다.

1. Cesium 객체의 색상은 적용 안 되는 문제

  • 참고로 폴리곤을 생성하는 코드는 v-world에도 역시 있지만 코드가 매우 복잡하여 더 불편해서 v-world의 원천 기술인 Cesium.js를 이용하여 작성하기로 하였다.
vw3dViewer.value.entities.add({
  name: "flood",
  position: position,
  cylinder: {
    length: height,
    topRadius: radius,
    bottomRadius: radius,
    material: ws3d.value.common.Color.ROYALBLUE.withAlpha(0.5),
    outline: true,
    outlineColor: ws3d.value.common.Color.ROYALBLUE.withAlpha(0.5),
  },
});
  • 참고로 예제 코드가 너무 잘 되어있으니 가서 확인해보면 된다.

  • 하지만 예제 코드와 다른 점이 한 곳 있다. 바로 색상을 입혀주는 부분이다. 저때 Cesium 객체의 color 값은 들어가지 않으니 앞서 vw객체에서 파생되었던 ws3d 객체를 사용하면 된다.

2. v-world의 pop-up 1개 문제

  • 요구사항 중 하나는 어떠한 이벤트가 발생하면 해당되는 아이콘에 대하여 모두 팝업을 띄워졌으면 좋겠다는 요구사항이었다.

  • 이때 v-world에 올린 질문을 보면 알 수 있는데 작성일(23.08.12) 기준으로 아직 2개의 팝업은 지원하지 않는다. 따라서 Cesium.js를 사용해야하는데 이때 2가지의 단점이 또 존재한다.

  1. 무조껀 image로 하여야한다. => Cesium.js역시 일단 2d 지도처럼 사용하는 것이 목적이 아니기에 일반적이 오버레이 기능이 존재하지 않는다. 그래서 billboard 기능으로 억지로 image를 만들어서 넣어줘야한다.
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 300;
const image = new Image();
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
                    <foreignObject width="100%" height="100%">
                      <div xmlns="http://www.w3.org/1999/xhtml">
                      	<div>뭔갈 작성하셈</div>
                      </div>
                    </foreignObject>
                  </svg>`;
image.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
image.onload = function () {
  canvas!.getContext('2d').drawImage(image, 0, 0);
  vw3dViewer.value.entities.add({
    id: child.id,
    position: Cesium.Cartesian3.fromDegrees(child.equipLat, child.equipLon, 200),
    billboard: {
      image: canvas,
      horizontalOrigin:Cesium.VerticalOrigin.CENTER,
      eyeOffset: new Cesium.Cartesian3(125.0, 0,0.0),
    },
    description: '<p>Copy Wright by KJW</p>'
  });
};

  1. gifs는 지원하지 않는다. => gif 기능을 구현하려면 setInterval 기능으로 image를 생성했다 삭제했다 해야한다.
  2. 또한 class기능도 지원하지 않는다. => class에 붙어있는 css를 모두 inline style로 구현해야만 한다.
  3. image에 클릭 이벤트를 넣을 수 없다. => 물론 방법이 아예없는 것은 아닌 듯 하다. 이 역시 scene 이라는 객체를 이용하면 되는 듯 하다... =>
    참조

v-world 기능 나열

카메라 기능

1. 아이템 전체 숨기기 보여주기

for(const data of DummyData){
  const selectedObj = map3d.value.getObjectById(data.id);
  if ( selectedObj != null ) {
    selectedObj.show();
    selectedObj.hide();
  }
}
  • 참고로 그룹마커를 생성하면 매우 쉽게 마커들을 숨겼다 보여줬다 할 수 있다.
    다만 문제는 해당 객체들이 그룹으로 편성되기 때문에 각각의 obj로 존재해야하는 클러스터링 을 할 수 없다는 것이 문제이다.

2. 카메라 초기 위치로 보내기

vw.value.NavigationZoom.initHome();

lod 변경 및 색상 적용

function setLod1 (){
  map3d.value.getElementById('facility_build').hide();
  map3d.value.getElementById('facility_build_lod1').show();
  map3d.value.getElementById('facility_build_lod1').setStyle({
    color: {
      conditions:[
        ["true", "rgba(185,185,185,1.0)"],
      ]
    }
  })
}

기타 지도에 도움이 되는 함수들

tts 기능

const msg = new SpeechSynthesisUtterance('tts 내용을 입력하세요');
speechSynthesis.speak(msg);

두 점사이의 거리 구하기

function degToRad(deg:number) {
  return deg * (Math.PI / 180);
}

function getDistance(lat1:number, lon1:number, lat2:number, lon2:number) {
  const earthRadiusKm = 6371;
  const dLat = degToRad(lat2 - lat1);
  const dLon = degToRad(lon2 - lon1);

  const a =
        Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = earthRadiusKm * c;
  return distance;
}
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글