Spatialite에서 공간 데이터를 가지는 Table 생성하기

Spatialite에서는 테이블 생성시 바로 공간 데이터 필드를 추가할 수 없다. 먼저 공간 데이터 필드를 제외하고 테이블을 생성한다.

CREATE TABLE  main_item (
    id INTEGER PRIMARY KEY AUTOINCREMENT, 
    layer TEXT,
    title TEXT,
    feature_id INTEGER
);

이제 공간 데이터 필드를 추가한다.

SELECT AddGeometryColumn('main_item', 'geometry', -1, 'GEOMETRY', 'XY');

AddGeometryColumn 함수의 3번째는 EPSG 코드값이며, 4번째는 POINT, LINESTRING, POLYGON, MULTIPOINT 등이 올 수 있다. GEOMETRY는 모든 타입의 공간 데이터를 받을 수 있다.

공간 데이터 필드에 공간 인덱스를 건다.

SELECT CreateSpatialIndex('main_item', 'geometry');

끝으로 Row를 추가하는 SQL문은 다음과 같다.

INSERT INTO main_item (layer, title, feature_id, geometry) 
VALUES ('layer1', 'title1', 100, ST_GEOMFROMTEXT('POINT(128.32132 37.34322)', -1));

[Android] 화면 터치 중 Swiping을 이용한 View 전환

화면 터치가 가능한 모바일 단말기에서 터치를 통한 UI의 조작은 매우 효과적입니다. 이러한 터치 기반의 UI의 활용에 대해 자연스러운 사용은 사용자에게 프로그램의 친밀도를 높여줍니다. 화면 터치에 대한 조작 중 Swiping은 사용자가 화면을 스치듯이 상하좌우로 쓸어넘기는 행위입니다. 이러한 Swiping 중 좌우에 대한 이벤트를 처리하기 위한 클래스는 다음과 같습니다.

package geoservice.nexgen

import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View

abstract class OnSwipeTouchListener(context: Context?) : View.OnTouchListener {
    companion object {
        private const val SWIPE_DISTANCE_THRESHOLD = 100
        private const val SWIPE_VELOCITY_THRESHOLD = 100
    }

    private val gestureDetector: GestureDetector

    abstract fun onSwipeLeft()
    abstract fun onSwipeRight()

    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        return gestureDetector.onTouchEvent(event)
    }

    private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
        override fun onDown(e: MotionEvent): Boolean {
            return true
        }

        override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
            val distanceX = e2.x - e1.x
            val distanceY = e2.y - e1.y
            if (Math.abs(distanceX) > Math.abs(distanceY) 
                    && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD 
                    && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                if (distanceX > 0) onSwipeRight() else onSwipeLeft()
                return true
            }
            return false
        }
    }

    init {
        gestureDetector = GestureDetector(context, GestureListener())
    }
}

위 클래스를 실제 View에 적용하는 코드의 예는 다음과 같습니다.

llListScroll.setOnTouchListener(object: OnSwipeTouchListener(context) {
    override fun onSwipeLeft() { btnNext.performClick() }
    override fun onSwipeRight() { btnPrevious.performClick() }
})

실제 위의 코드는 모바일 기반의 GIS 솔루션인 Mobile NexGen에 반영된 코드인데요. 위의 코드와 연관된 기능에 대한 시연 영상은 아래와 같습니다.

코틀린의 observable, vetoable 위임자

코틀린은 2011년 중순에 공개되어 지속적으로 발전되어 오다가 2019년에 구글 안드로이드 개발 주요 개발언어로 채택되면서 현대적인 프로그래밍 언어중 하나입니다. 이 글은 코틀린의 데이터 변수에 obserable과 vetoable 위임자를 지정하여 변수의 값이 변경될 경우 원하는 로직을 실행하거나 변수의 값의 변경시 특정 조건과 맞지 않으면 변경을 취소하는 내용을 대해 설명합니다.

먼저 obserable 위임자를 통한 변수값 변경시 처리입니다.

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("초기값") {
        prop, old, new -> println("$old 값이 $new 값으로 변경됩니다.")
    }
}

fun main() {
    val user = User()
    user.name = "홍길동"
    user.name = "임꺽정"
}

실행 결과는 다음과 같습니다.

초기값 값이 홍길동 값으로 변경됩니다.
홍길동 값이 임꺽정 값으로 변경됩니다.

다음은 vetoable 위임자입니다. 값의 변경시 특정한 조건에 따라 변경을 취소할 수 있습니다.

import kotlin.properties.Delegates

class MoreBiggerInt(initValue: Int) {
    var value: Int by Delegates.vetoable(initValue) {
        property, oldValue, newValue -> {
        val result = newValue > oldValue
        if(result) {
            println("더 큰 값이므로 값을 변경합니다.")
        } else {
            println("작은 값이므로 변경을 취소합니다.")
        }
        result
    }()
    }
}

fun main() {
    val vv = MoreBiggerInt(10)

    vv.value = 20
    println("${vv.value}")

    vv.value = 5
    println("${vv.value}")
}

실행 결과는 다음과 같습니다.

더 큰 값이므로 값을 변경합니다.
20
작은 값이므로 변경을 취소합니다.
20

[GoF] Visitor 패턴

패턴명칭

Visitor

필요한 상황

데이터와 이 데이터의 처리를 분리하여 구현하고자 할때 사용되는 패턴입니다. 데이터는 Composite 패턴으로 구현되므로 집합을 구성하는 단일 요소 역시 집합으로 저장될 수 있습니다. 이러한 집합에 대한 집합으로 구성된 데이터를 처리하는 로직을 독립적으로 구현할 수 있습니다.

예제 코드

Visitor 인터페이스는 데이터를 처리하는 클래스가 구현해야할 공통 인터페이스입니다. 코드는 다음과 같습니다.

package tstThread;

public interface Visitor {
	void visit(Unit unit);
}

이 Visitor를 구현하는 클래스로는 SumVisitor, MaxVisitor과 위의 클래스 다이어그램에는 표시되어 있지 않지만 MinVisitor, AvgVisitor이 있습니다. 이 네 클래스는 각각 데이터의 총합 계산, 데이터 중 최대값 파악, 데이터 중 최소값 파악, 데이터의 평균값 계산입니다. 데이터는 Unit 인터페이스를 구현해야 하며 코드는 다음과 같습니다.

package tstThread;

public interface Unit {
	void accept(Visitor visitor);
}

이 Unit 인터페이스를 구현하는 Item에는 하나의 정수값이 저장되며 ItemList는 여러개의 Unit 객체를 담을 수 있습니다. 먼저 Item 클래스는 다음과 같습니다.

package tstThread;

public class Item implements Unit {
	private int value;
	
	public Item(int value) {
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
	
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
}

ItemList 클래스는 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.Iterator;

public class ItemList implements Unit {
	private String name;
	
	private ArrayList<Unit> list = new ArrayList<Unit>();
	
	public ItemList(String name) {
		this.name = name;
	}
	
	public String getName() {
		return this.name;
	}
	
	public void add(Unit unit) {
		list.add(unit);
	}
	
	@Override
	public void accept(Visitor visitor) {
		Iterator<Unit> iter = list.iterator();
		
		while(iter.hasNext()) {
			Unit unit = iter.next();
			visitor.visit(unit);
		}
	}
}

이제 이러한 데이터를 처리하는 Visitor 인터페이스의 구현 클래스를 살펴보겠습니다. 먼저 SumVisitor 클래스입니다.

package tstThread;

public class SumVisitor implements Visitor {
	private int sum = 0;
	
	public int getValue() {
		return sum;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			sum += ((Item)unit).getValue();
		} else {
			unit.accept(this);			
		}
	}
}

다음은 MaxVisitor 클래스입니다.

package tstThread;

public class MaxVisitor implements Visitor {
	private int max = Integer.MIN_VALUE;
	private String name = null;
	private String visitedName = null;
	
	public int getValue() {
		return max;
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			int value = ((Item)unit).getValue();
			if(value > max) {
				max = value;
				name = visitedName;
			}
		} else {
			visitedName = ((ItemList)unit).getName();
			unit.accept(this);			
		}
	}
}

다음은 MinVisitor 클래스입니다.

package tstThread;

public class MinVisitor implements Visitor {
	private int min = Integer.MAX_VALUE;
	private String name = null;
	private String visitedName = null;
	
	public int getValue() {
		return min;
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			int value = ((Item)unit).getValue();
			if(value < min) {
				name = visitedName;
				min = value;
			}
		} else {
			visitedName = ((ItemList)unit).getName();
			unit.accept(this);			
		}
	}
}

다음은 AvgVisitor 클래스입니다.

package tstThread;

public class AvgVisitor implements Visitor {
	private int sum = 0;
	private int count = 0;
	public double getValue() {
		return sum / count;
	}
	
	@Override
	public void visit(Unit unit) {
		if(unit instanceof Item) {
			sum += ((Item)unit ).getValue();
			count++;
		} else {
			unit.accept(this);			
		}
	}
}

지금까지의 클래스를 사용하는 예제 코드는 다음과 같습니다.

package tstThread;

public class Main {
	public static void main(String[] args) {
		ItemList root = new ItemList("root");
		root.add(new Item(10));
		root.add(new Item(20));
		root.add(new Item(40));
		
		ItemList subList1 = new ItemList("sub1");
		subList1.add(new Item(5));
		subList1.add(new Item(16));
		subList1.add(new Item(36));
		
		ItemList subList2 = new ItemList("sub2");
		subList2.add(new Item(50));
		subList2.add(new Item(70));
		
		ItemList subList3 = new ItemList("sub2-sub");
		subList3.add(new Item(8));
		subList3.add(new Item(21));
		subList3.add(new Item(37));
		
		root.add(subList1);
		root.add(subList2);
		subList2.add(subList3);
		
		SumVisitor sum = new SumVisitor();
		root.accept(sum);
		System.out.println("Sum: " + sum.getValue());
		
		MaxVisitor max = new MaxVisitor();
		root.accept(max);
		System.out.println("Max: " + max.getValue() + " @" + max.getName());
		
		MinVisitor min = new MinVisitor();
		root.accept(min);
		System.out.println("Min: " + min.getValue() + " @" + min.getName());
		
		AvgVisitor avg = new AvgVisitor();
		root.accept(avg);
		System.out.println("Avg: " + avg.getValue());
	}
}

실행 결과는 다음과 같습니다.

Sum: 313
Max: 70 @sub2
Min: 5 @sub1
Avg: 28.0
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.