FingerEyes-Xr을 이용한 HeatMap 시각화

웹 GIS 엔진인 FingerEyes-Xr은 밀도도를 시각화하기 위한 방법 중의 하나인 HeatMap 기능을 제공합니다. 바로 Xr.layers.HeatMapLayer 클래스를 통해 수행이 가능합니다. HeatMap이 아닌 또 다른 밀도도에 대한 시각화 방법은 Xr.layers.GridLayer 클래스를 사용하는 것인데, Xr.layers.GridLayer는 셀 기반의 연산을 이용해 밀도도 분석을 수행합니다. 좀더 정밀한 밀도도 분석은 Xr.layers.GridLayer이 Xr.layers.HeatMapLayer보다 우수하지만 연산 시간은 Xr.layers.HeatMapLayer가 훨씬 빨라 실시간으로 밀도도를 시각화할 수 있다는 장점을 갖습니다.

아래의 영상은 FingerEyes-Xr의 Xr.layers.HeatMapLayer를 사용하여 생성된 밀도도입니다.

이에 대한 코드는 다음처럼 간단 명료합니다.

let heatMapLayer = map.layers('heatmap');
if (!heatMapLayer) {
    heatMapLayer = new Xr.layers.HeatMapLayer('heatmap');
    map.layers().add(heatMapLayer);
}
            
heatMapLayer.generateByLayer(lyr);

밀도도 분석을 위한 입력 데이터로 공간상에 분포된 포인트 좌표들이 필요하며, 이 포인트 좌표는 7번 코드의 lyr 변수명의 레이어를 통해 입력됩니다.

Xr.layers.HeatMapLayer는 밀도도 분석이 매우 빨라 실시간으로 밀도도를 생성할 수 있습니다. 아래의 코드는 입력 데이터가 변경되는 즉시 밀도도를 새롭게 생성해 표시합니다.

map.addEventListener(Xr.Events.LayerUpdateCompleted, function (e) {
    if (e.layerName === lyr.name()) {
        let heatMapLayer = map.layers('heatmap');
        if (heatMapLayer) {
            heatMapLayer.generateByLayer(lyr); 
        }
    }
});

끝으로 보다 정밀한 밀도도를 생성하기 위한 또다른 방식인 Xr.layers.GridLayer에 대한 실제 활용예는 아래의 글을 참고하기 바랍니다.

NexGen, 공간 데이터의 분포경향 분석을 위한 밀도맵 기능

ViewPropertyAnimator를 이용한 개별 뷰(View) 단위 애니메이션

안드로이드에서 하나의 뷰에 대한 특정 속성값을 애니메이션화하기 위한 API인 ViewPropertyAnimator에 대한 내용입니다. 먼저 아래와 같은 레이아웃에 애니메이션을 위한 버튼 뷰를 하나 배치합니다.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:scrollbars="none">

    <Button
        android:id="@+id/myView"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:text="Hello" />

</ScrollView>

이제 버튼을 터치할때 애니매이션이 시작될 수 있도록 다음 코드를 추가합니다.

myView.setOnClickListener {
    myView
        .animate()
        .scaleX(5.0f)
        .scaleY(5.0f)
        .alpha(0.0f)
        .translationX(400.0f)
        .translationY(400.0f)
        .rotation(360.0f)
        .setDuration(1000)
        .withStartAction {
            Toast.makeText(this, "애니메이션 시작", Toast.LENGTH_SHORT).show()
        }
        .withEndAction {
            myView.scaleX = 1.0f;
            myView.scaleY = 1.0f;
            myView.alpha = 1.0f;
            myView.x = 0.0f;
            myView.y = 0.0f;
            myView.rotation = 0.0f;
            Toast.makeText(this, "완료", Toast.LENGTH_SHORT).show()
        }.start()
}

참고로 언어는 코들린입니다. 1초간 뷰의 크기를 5배로, 점점 투명하게, X와 Y의 위치를 각각 400과 400으로, z축으로 360회전 하도록 하는애니메이션입니다. 그 결과는 아래와 같습니다.

안드로이드의 Shape 형태의 Drawable

직사각형(Rectangle) 형태의 Shape Drawable 정의는 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <stroke android:width="10dp"
            android:color="#ffff00" />
    <solid android:color="@android:color/transparent" />
    <corners android:radius="50dp" />
</shape>

위 형태에 대한 결과는 다음과 같다.

실제 뷰의 배경(Background)에 적용할 수 있는데, 그 예는 다음과 같다.

<LinearLayout android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="@drawable/rectangle_shape_drawable"
              android:orientation="vertical">

...

Javascript의 async, await 정리

이 글을 이해하기 위해서는 먼저 Promise API에 대해 알고 있어야 하며 아래의 글을 참고하시기 바랍니다.

Javascript의 Promise API 요약

async와 awit의 사용은 비동기 처리에 대한 혼란스러운 코드의 가독성을 향상 시켜줌으로써 코드의 유지보수 및 견고한 코드를 작성할 수 있습니다.

먼저 아래의 코드는 Promise API를 이용한 비동기처리입니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let result = { name: 'Dip2K', age: 44 };
            resolve(result);
        }, 2000);
    });
}

function callback(result) {
    console.log(result);
}

console.log('1');
getItem().then(callback);
console.log('2');

위의 코드를 async와 await를 이용해 동일하게 작동하도록 코드를 작성하면 다음과 같습니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let result = { name: 'Dip2K', age: 44 };
            resolve(result);
        }, 2000);
    });
}

async function get() {
    let result = await getItem();
    console.log(result);
}

console.log('1');
get();
console.log('2');

콜백함수 없이 비동기처리가 된 경우로, 순차적인 흐름의 코드로 작성되었습니다.

아래의 코드는 Promise의 예외의 처리를 async 및 await 구분에서 어떻게 처리 하는지를 보여주는 코드입니다.

function getItem() {
    return new Promise((resolve, reject) => {
        setTimeout(function() {
            let bOK = false;

            if(bOK) {
                let result = { name: 'Dip2K', age: 44 };
                resolve(result);
            } else {
                reject(null);
            }
        }, 2000);
    });
}

async function get() {
    try {
        let result = await getItem();
        console.log(result);
    } catch(e) {
        console.log(e);
    }
}

console.log('1');
get();
console.log('2');

await와 fetch를 함께 사용하는 코드 예시를 마지막으로 언급하고 마무리 합니다.

async function fetchData() {
  const response = await fetch("http:/...")
  const data = await response.json()
  cosnole.log(data)
}

fetchData()

Javascript의 Promise API 요약

Promise는 코드의 실행 흐름에서 비동기처리를 유연하게 처리하기 위한 API입니다. 코드를 통해 살펴보겠습니다.

function test(callback) {
    setTimeout(() => {
        callback();
    }, 2000);
}

function callback() {
    console.log('Hello!')
}

test(callback);

위의 코드는 2초 뒤에 콘솔에 Hello!를 출력합니다. 이 코드를 Promise API로 대체하면 아래와 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(); 
        }, 2000);
    })
}

function callback() {
    console.log('Hello!');
}

test().then(callback);

좀더 완전한 Promise API를 위한 위의 코드에서 확장된 코드를 살펴보면 아래와 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let bOK = true;

            console.log('1');            
            if(bOK) {
                resolve(1); // [Fulfilled State] 1, 2를 콘솔에 출력하고 적당한 시점에 then으로 넘겨준 함수를 호출해 줌           
            } else {
                reject(-1); // [Rejected State] 1, 2를 콘솔에 출력하고 적당한 시점에 예외를 발생시킴
            }
            console.log('2'); 
        }, 2000);
    })
}

function callback(result) {
    console.log('Hello! : ' + result)
}

let p = test(); // [Pending State]
p.then(callback); // Promise의 resolve 호출에 의해 callback이 호출됨

Promise는 3가지 상태를 갖는다고 합니다. Promise 객체가 생성되어 사용될 준비가 된 Pending, 비동기 처리에 의해 원하는 올바른 결과를 얻어와 그 결과를 정상적으로 처리하고자 resolve가 호출된 Fulfilled 상태, 무언가 잘못되어 예외로 처리하고자 reject가 호출된 Rejected 상태입니다. 위의 짧은 코드를 살펴보면, 어떤 이 3가지 상태에 대한 시점을 확인할 수 있습니다. resolve와 reject는 각각 성공과 실패에 대한 결과값을 얻을 수 있는 객체를 전달할 수 있습니다. 위의 코드에서는 각각 1와 -1을 넘겨주고 있습니다. 주목할 점은 resolve와 reject는 동시에 같이 실행될 수 없으며 배타성을 갖습니다. 즉, resolve가 호출되면 reject 코드가 호출된다고 해도 실행되지 않으며, reject 코드가 호출되면 resolve가 호출되어도 reject가 호출되지 않습니다. 또 하나는 resolve에 의해 실행되는 callback은 resolve가 호출될때 즉시 실행되지 않습니다. 위의 코드의 주석을 보면 console.log(‘1’)과 consloe.log(‘2’)의 위치 사이에 각각 resolve와 reject가 있는데.. 실행순서는 console.log(‘1’)과 console.log(‘2)가 먼저 실행되고 resolve 또는 reject에 연결된 함수가 호출된다는 점입니다. 이 글의 마지막으로.. reject가 호출되면 예외를 던지게 됩니다. 위의 코드는 적당한 예외에 대한 처리를 하고 있지 않습니다. 이 예외 처리까지 포함된 코드는 다음과 같습니다.

function test() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let bOK = false;

            if(bOK) {
                resolve(1);           
            } else {
                reject(-1); // Promise의 catch로 넘겨준 함수를 호출해 줌
            }
        }, 2000);
    })
}

function callback(result) {
    console.log('Hello! : ' + result)
}

function callbackError(result) {
    console.log('Oh my god! : ' + result)
}

test().then(callback, callbackError).catch(callbackError); // then과 catch는 Promise 객체를 반환해줌
// test().then(callback, callbackError); // 바로 위의 코드와 동일함

reject 함수의 호출로 인해 Promise 객체의 catch로 넘겨준 함수를 호출할 수 있게 되어 적절한 예외 처리가 가능해 집니다.

Promise는 사실 기반 API입니다. 즉, Promise를 기반으로 상대적으로 상위 레벨의 API를 통해 Promise를 사용하게 되는데요. async/await나 fetch가 바로 그것인데요, fetch는 아래의 글을 참고하시기 바랍니다.

ES6의 Fetch API