루씬(lucene.net)을 이용한 도로명 주소
Background 배경
lucene은 hadoop의 아버지 더그 컷팅이라는 사람이 2005년에 만든 오픈 소스 검색 엔진이다. 그냥 작은 구글같은거라고 생각하면 된다. 무언가를 찾을 때, 쉽게 쓸 수 있도록 정리해 놓는 도구이다. 이를테면, 도서관의 십진분류법에 따라 정리해주는 사서와 같은 역활을 한다. 우리가 해야 할 일은, 사서에게 적절한 책을 주고, 사서에게 필요한 책의 내용을 이야기하는 일 밖에는 없다.
오늘은 lucene을 소개할 것은 아니고, '도로명'에 적용할 수 있도록 작성된 예제 소스를 보도록 하겠다. '도로명'은 말도 많고 탈도 많은 2014년 1월부터 시행되는 땅의 번호가 아닌 도로가 중심이 되는 새로운 주소 체계이다.
Introduction 소개
lucene은 단언컨데, 그 자체로만으로도 가장 깔끔한 엔진이다. 이미, 대부분의 것들은 구현되어 있으므로, 우리가 할 일은 Document를 만들어 lucene이 index를 생성할 수 있도록 하는 일이다. 그리고 앞에서 생성한 index를 검색하는 일이 전부이다.
Projects
- AddressByStreet : Document 생성 및 Index를 검색하는 등, 실제적으로 Lucene을 작동시키는 프로젝트
- AddressByStreetTest : AddressByStreet의 테스트 프로젝트
- AddressBook : AddressByStreet는 단순히 Library이므로, 이를 보여줄 간단한 WPF View 프로젝트
Class
Data Object
- RawAddress : Data Objects, juso.or.kr에서 배포하는 형태의 주소 object
- Address : Data Object, lucene에서 사용되는 주소 object
- AddressReader : Index를 읽어 들여서, 처리하는 class
Related Lucene
- AddressSearch : Lucene.Net.Index.IndexReader를 이용해서, 검색을 하는 클래스
- CreateLuceneIndex(Address, IndexWriter) : Index를 생성하는 메소드
- Search(string searchQuery, string searchField) : 생성된 Index를 바탕으로 검색을 하는 메소드
Library
- Enviornment : 사용되는 옵션에 대한 전역 변수
- Preference : 저장할 필요가 있는 사용자 설정 값의 Container, '.lucene'이라는 이름으로 저장된다.
Requirement
- Lucene.Net : nuget으로 쉽게 설치할 수 있다.
Enviornment Variables
- Enviornment.DataDir : Document의 Source가 되는 폴더, 즉 여기에 도로명 주소의 TXT파일 혹은 ZIP파일이 위치해야 한다. 디폴트 값은 '<실행파일의 폴더>LuceneData'이다.
- Enviornment.IndexDir : Document를 바탕으로 만들어진 Index가 실질적으로 저장되는 폴더, Lucene에 의해 관리되므로 실질적으로 신경쓸 내용은 없다. 디폴트 값은 '<실행파일의 폴더>LuceneIndex'이다.
- Enviornment.AllowLeadingWildcard : '*'에 의한 검색의 지원 여부
Explanation
- Index를 생성하는 부분
public void AddToLuceneIndex(IEnumerable<Address> dataToIndex) { var analyzer = new StandardAnalyzer(Version.LUCENE_30); using (var writer = new IndexWriter(Directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED)) { foreach (var element in dataToIndex) CreateLuceneIndex(element, writer); analyzer.Close(); writer.Optimize(); writer.Dispose(); } IsIndexed = true; }
- Search를 수행하는 부분
public IEnumerable<Address> Search(string searchQuery, string searchField) { if (!IsIndexed) throw new FileNotFoundException("Index is not exists."); using (var searcher = new IndexSearcher(Directory, false)) { var analyzer = new StandardAnalyzer(Version.LUCENE_30); QueryParser parser; if (!string.IsNullOrEmpty(searchField)) { parser = new QueryParser(Version.LUCENE_30, searchField, analyzer); } else { parser = new MultiFieldQueryParser(Version.LUCENE_30, new[] {"Street", "LandLot", "Post"}, analyzer); } parser.AllowLeadingWildcard = Environment.AllowLeadingWildcard; var query = ParseQuery(searchQuery, parser); var hits = searcher.Search(query, null, Environment.MaxResult, Sort.RELEVANCE).ScoreDocs; var results = MapLuceneToDataList(hits, searcher); analyzer.Close(); searcher.Dispose(); return results; } }