示例#1
0
 public function load_addresses($sidos)
 {
     // DB를 준비한다.
     $db = Postcodify_Utility::get_db();
     $db->beginTransaction();
     $ps_addr_insert = $db->prepare('INSERT INTO postcodify_addresses (postcode5, postcode6, ' . 'road_id, num_major, num_minor, is_basement, dongri_ko, dongri_en, jibeon_major, jibeon_minor, is_mountain, ' . 'building_id, building_name, building_nums, other_addresses) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
     $ps_kwd_insert = $db->prepare('INSERT INTO postcodify_keywords (address_id, keyword_crc32) VALUES (?, ?)');
     $ps_num_insert = $db->prepare('INSERT INTO postcodify_numbers (address_id, num_major, num_minor) VALUES (?, ?, ?)');
     $ps_building_insert = $db->prepare('INSERT INTO postcodify_buildings (address_id, keyword) VALUES (?, ?)');
     // 이 쓰레드에서 처리할 시·도 목록을 구한다.
     $sidos = explode('|', $sidos);
     // Zip 파일을 연다.
     $zip = new Postcodify_Parser_NewAddress();
     $zip->open_archive($this->_data_dir . '/' . substr($this->_data_date, 0, 6) . 'RDNMADR.zip');
     // Update 클래스의 인스턴스를 생성한다. (누락된 우편번호 입력에 사용된다.)
     $update_class = new Postcodify_Indexer_Update();
     // 카운터를 초기화한다.
     $count = 0;
     // 시·도를 하나씩 처리한다.
     foreach ($sidos as $sido) {
         // 시·도 데이터 파일을 연다.
         $open_status = $zip->open_named_file('건물정보_' . $sido);
         if (!$open_status) {
             throw new Exception('Failed to open 건물정보_' . $sido);
         }
         // 이전 주소를 초기화한다.
         $last_entry = null;
         $last_nums = array();
         // 데이터를 한 줄씩 읽어 처리한다.
         while (true) {
             // 읽어온 줄을 분석한다.
             $entry = $zip->read_line();
             // 이전 주소가 없다면 방금 읽어온 줄을 이전 주소로 설정한다.
             if ($last_entry === null) {
                 $last_entry = $entry;
                 if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                     $last_nums = array(preg_replace('/동$/u', '', $entry->building_detail));
                 } elseif ($entry->building_detail) {
                     $last_entry->building_names[] = $last_entry->building_detail;
                     $last_nums = array();
                 } else {
                     $last_nums = array();
                 }
             } elseif ($entry === false || $last_entry->road_id !== $entry->road_id || $last_entry->road_section !== $entry->road_section || $last_entry->num_major !== $entry->num_major || $last_entry->num_minor !== $entry->num_minor || $last_entry->is_basement !== $entry->is_basement) {
                 // 상세건물명과 기타 주소를 정리한다.
                 $building_nums = Postcodify_Utility::consolidate_building_nums($last_nums);
                 if ($building_nums === '') {
                     $building_nums = null;
                 }
                 $other_addresses = array();
                 if ($last_entry->admin_dongri && $last_entry->admin_dongri !== $last_entry->dongri) {
                     $other_addresses[] = $last_entry->admin_dongri;
                 }
                 $last_entry->building_names = array_unique($last_entry->building_names);
                 $last_entry->building_names = Postcodify_Utility::consolidate_building_names($last_entry->building_names, $last_entry->common_residence_name);
                 natcasesort($last_entry->building_names);
                 foreach ($last_entry->building_names as $building_name) {
                     $other_addresses[] = str_replace(';', ':', $building_name);
                 }
                 $other_addresses = implode('; ', $other_addresses);
                 if ($other_addresses === '') {
                     $other_addresses = null;
                 }
                 // 공동주택인데 공동주택명이 없는 경우 다른 건물명을 이용한다.
                 if ($last_entry->is_common_residence && $last_entry->common_residence_name === null) {
                     if (strpos($other_addresses, '; ') === false) {
                         $last_entry->common_residence_name = $other_addresses;
                         $other_addresses = null;
                     }
                 }
                 // 도로 정보를 구한다.
                 if (isset(Postcodify_Utility::$road_cache[$last_entry->road_id . $last_entry->road_section])) {
                     $road_info = explode('|', Postcodify_Utility::$road_cache[$last_entry->road_id . $last_entry->road_section]);
                     $road_info = (object) array('road_name_ko' => $road_info[0], 'sido_ko' => $road_info[1], 'sigungu_ko' => $road_info[2], 'ilbangu_ko' => $road_info[3], 'eupmyeon_ko' => $road_info[4]);
                 } else {
                     $road_info = null;
                 }
                 // 우편번호가 누락된 경우, 범위 데이터를 사용하여 찾는다.
                 if (!$this->_no_old_postcodes && ($last_entry->postcode6 === null || $last_entry->postcode6 === '000000')) {
                     $last_entry->postcode6 = $update_class->find_postcode6($db, $road_info, $last_entry->dongri, $last_entry->admin_dongri, $last_entry->jibeon_major, $last_entry->jibeon_minor);
                 }
                 if ($last_entry->postcode5 === null || $last_entry->postcode5 === '00000') {
                     $last_entry->postcode5 = $update_class->find_postcode5($db, $road_info, $last_entry->num_major, $last_entry->num_minor, $last_entry->dongri, $last_entry->admin_dongri, $last_entry->jibeon_major, $last_entry->jibeon_minor, $last_entry->postcode6);
                 }
                 // 주소 테이블에 입력한다.
                 $ps_addr_insert->execute(array($last_entry->postcode5, $last_entry->postcode6, $last_entry->road_id . $last_entry->road_section, $last_entry->num_major, $last_entry->num_minor, $last_entry->is_basement, $last_entry->dongri, Postcodify_Utility::$english_cache[$last_entry->dongri], $last_entry->jibeon_major, $last_entry->jibeon_minor, $last_entry->is_mountain, $last_entry->building_id, $last_entry->common_residence_name, $building_nums, $other_addresses));
                 $proxy_id = $db->lastInsertId();
                 // 도로명 키워드를 입력한다.
                 $road_name_array = Postcodify_Utility::get_variations_of_road_name($road_info->road_name_ko);
                 foreach ($road_name_array as $keyword) {
                     if (!$keyword) {
                         continue;
                     }
                     $ps_kwd_insert->execute(array($proxy_id, Postcodify_Utility::crc32_x64($keyword)));
                 }
                 // 동·리 키워드를 입력한다.
                 $dongri_array1 = Postcodify_Utility::get_variations_of_dongri($last_entry->dongri);
                 $dongri_array2 = Postcodify_Utility::get_variations_of_dongri($last_entry->admin_dongri);
                 $dongri_array = array_unique(array_merge($dongri_array1, $dongri_array2));
                 foreach ($dongri_array as $keyword) {
                     if (!$keyword) {
                         continue;
                     }
                     $ps_kwd_insert->execute(array($proxy_id, Postcodify_Utility::crc32_x64($keyword)));
                 }
                 // 건물번호 및 지번 키워드를 입력한다.
                 $ps_num_insert->execute(array($proxy_id, $last_entry->num_major, $last_entry->num_minor));
                 $ps_num_insert->execute(array($proxy_id, $last_entry->jibeon_major, $last_entry->jibeon_minor));
                 // 건물명 키워드를 입력한다.
                 if ($last_entry->common_residence_name || count($last_entry->building_names)) {
                     if ($last_entry->common_residence_name !== null) {
                         $last_entry->building_names[] = $last_entry->common_residence_name;
                     }
                     $building_names_str = Postcodify_Utility::compress_building_names($last_entry->building_names);
                     if ($building_names_str !== '') {
                         $ps_building_insert->execute(array($proxy_id, $building_names_str));
                     }
                 }
                 // 불필요한 변수들을 unset한다.
                 unset($road_info, $road_name_array, $dongri_array1, $dongri_array2, $dongri_array);
                 unset($keyword, $building_names, $building_names_str, $proxy_id);
                 unset($last_entry, $last_nums);
                 // 방금 읽어온 줄을 새로운 이전 주소로 설정한다.
                 if ($entry !== false) {
                     $last_entry = $entry;
                     if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                         $last_nums = array(preg_replace('/동$/u', '', $entry->building_detail));
                     } elseif ($entry->building_detail) {
                         $last_entry->building_names[] = $last_entry->building_detail;
                         $last_nums = array();
                     } else {
                         $last_nums = array();
                     }
                 }
             } else {
                 if (count($entry->building_names)) {
                     $last_entry->building_names = array_merge($last_entry->building_names, $entry->building_names);
                 }
                 if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                     $last_nums[] = preg_replace('/동$/u', '', $entry->building_detail);
                 } elseif ($entry->building_detail) {
                     $last_entry->building_names[] = $entry->building_detail;
                 }
             }
             // 카운터를 표시한다.
             if (++$count % 512 === 0) {
                 $shmop = shmop_open($this->_shmop_key, 'w', 0, 0);
                 $prev = current(unpack('L', shmop_read($shmop, 0, 4)));
                 shmop_write($shmop, pack('L', $prev + 512), 0);
                 shmop_close($shmop);
             }
             // 더이상 데이터가 없는 경우 루프를 탈출한다.
             if ($entry === false) {
                 break;
             }
             // 메모리 누수를 방지하기 위해 모든 배열을 unset한다.
             unset($entry);
         }
         // 시·도 데이터 파일을 닫는다.
         $zip->close_file();
     }
     // 뒷정리.
     $zip->close();
     unset($zip);
     $db->commit();
     unset($db);
 }
示例#2
0
 public function load_updates($after_date)
 {
     // DB를 준비한다.
     $db = Postcodify_Utility::get_db();
     $db->beginTransaction();
     // 도로명코드 테이블 관련 쿼리들.
     $ps_road_select = $db->prepare('SELECT * FROM postcodify_roads WHERE road_id = ?');
     $ps_road_insert = $db->prepare('INSERT INTO postcodify_roads (road_id, road_name_ko, road_name_en, ' . 'sido_ko, sido_en, sigungu_ko, sigungu_en, ilbangu_ko, ilbangu_en, eupmyeon_ko, eupmyeon_en, updated) ' . 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
     $ps_road_update = $db->prepare('UPDATE postcodify_roads SET road_name_ko = ?, road_name_en = ?, ' . 'sido_ko = ?, sido_en = ?, sigungu_ko = ?, sigungu_en = ?, ilbangu_ko = ?, ilbangu_en = ?, ' . 'eupmyeon_ko = ?, eupmyeon_en = ?, updated = ? WHERE road_id = ?');
     // 주소 테이블 관련 쿼리들.
     $ps_addr_select = $db->prepare('SELECT * FROM postcodify_addresses WHERE road_id >= ? AND road_id <= ? AND ' . 'num_major = ? AND (num_minor = ? OR (? IS NULL AND num_minor IS NULL))' . 'AND is_basement = ? ORDER BY id LIMIT 1');
     $ps_addr_insert = $db->prepare('INSERT INTO postcodify_addresses (postcode5, postcode6, ' . 'road_id, num_major, num_minor, is_basement, dongri_ko, dongri_en, jibeon_major, jibeon_minor, is_mountain, ' . 'building_id, building_name, building_nums, other_addresses, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
     $ps_addr_update = $db->prepare('UPDATE postcodify_addresses SET postcode5 = ?, postcode6 = ?, ' . 'road_id = ?, num_major = ?, num_minor = ?, is_basement = ?, dongri_ko = ?, dongri_en = ?, ' . 'jibeon_major = ?, jibeon_minor = ?, is_mountain = ?, building_id = ?, building_name = ?, building_nums = ?, ' . 'other_addresses = ?, updated = ? WHERE id = ?');
     $ps_addr_update_other = $db->prepare('UPDATE postcodify_addresses SET other_addresses = ? WHERE id = ?');
     // 도로명 및 동·리 키워드 관련 쿼리들.
     $ps_kwd_select = $db->prepare('SELECT keyword_crc32 FROM postcodify_keywords WHERE address_id = ?');
     $ps_kwd_insert = $db->prepare('INSERT INTO postcodify_keywords (address_id, keyword_crc32) VALUES (?, ?)');
     // 건물번호 및 지번 키워드 관련 쿼리들.
     $ps_num_select = $db->prepare('SELECT num_major, num_minor FROM postcodify_numbers WHERE address_id = ?');
     $ps_num_insert = $db->prepare('INSERT INTO postcodify_numbers (address_id, num_major, num_minor) VALUES (?, ?, ?)');
     // 건물명 키워드 관련 쿼리들.
     $ps_building_select = $db->prepare('SELECT keyword FROM postcodify_buildings WHERE address_id = ?');
     $ps_building_insert = $db->prepare('INSERT INTO postcodify_buildings (address_id, keyword) VALUES (?, ?)');
     $ps_building_update = $db->prepare('UPDATE postcodify_buildings SET keyword = ? WHERE address_id = ?');
     $ps_building_delete = $db->prepare('DELETE FROM postcodify_buildings WHERE address_id = ?');
     // 데이터 파일 목록을 구한다.
     $files = glob($this->_data_dir . '/????????_dailynoticedata.zip');
     $last_date = $after_date;
     // 각 파일을 순서대로 파싱한다.
     foreach ($files as $filename) {
         // 데이터 기준일 이전의 파일은 무시한다.
         $file_date = substr(basename($filename), 0, 8);
         if (strcmp($file_date, $after_date) <= 0) {
             continue;
         }
         if (strcmp($file_date, $last_date) > 0) {
             $last_date = $file_date;
         }
         Postcodify_Utility::print_message('업데이트 파일: ' . $file_date . '_dailynoticedata.zip');
         // 도로정보 파일을 연다.
         Postcodify_Utility::print_message('  - 도로명코드');
         $zip = new Postcodify_Parser_Road_List();
         $zip->open_archive($filename);
         $open_status = $zip->open_named_file('TI_SPRD_STRET');
         if (!$open_status) {
             continue;
         }
         // 카운터를 초기화한다.
         $count = 0;
         // 데이터를 한 줄씩 읽는다.
         while ($entry = $zip->read_line()) {
             // 카운터를 표시한다.
             if (++$count % 32 === 0) {
                 Postcodify_Utility::print_progress($count);
             }
             // 폐지된 도로는 무시한다.
             if ($entry->change_reason === 1) {
                 unset($entry);
                 continue;
             }
             // 이미 존재하는 도로인지 확인한다.
             $ps_road_select->execute(array($entry->road_id . $entry->road_section));
             $road_exists = $ps_road_select->fetchColumn();
             $ps_road_select->closeCursor();
             // 신규 또는 변경된 도로 정보를 DB에 저장한다.
             if (!$road_exists) {
                 $ps_road_insert->execute(array($entry->road_id . $entry->road_section, $entry->road_name_ko, $entry->road_name_en, $entry->sido_ko, $entry->sido_en, $entry->sigungu_ko, $entry->sigungu_en, $entry->ilbangu_ko, $entry->ilbangu_en, $entry->eupmyeon_ko, $entry->eupmyeon_en, $entry->updated));
             } else {
                 $ps_road_update->execute(array($entry->road_name_ko, $entry->road_name_en, $entry->sido_ko, $entry->sido_en, $entry->sigungu_ko, $entry->sigungu_en, $entry->ilbangu_ko, $entry->ilbangu_en, $entry->eupmyeon_ko, $entry->eupmyeon_en, $entry->updated, $entry->road_id . $entry->road_section));
             }
             // 뒷정리.
             unset($entry);
         }
         // 파일을 닫는다.
         $zip->close();
         unset($zip);
         Postcodify_Utility::print_ok($count);
         // 건물정보 파일을 연다.
         Postcodify_Utility::print_message('  - 건물정보');
         $zip = new Postcodify_Parser_NewAddress();
         $zip->open_archive($filename);
         $open_status = $zip->open_named_file('TI_SPBD_BULD');
         if (!$open_status) {
             continue;
         }
         // 카운터를 초기화한다.
         $count = 0;
         // 이전 주소를 초기화한다.
         $is_first_entry = true;
         $last_entry = null;
         $last_nums = array();
         // 데이터를 한 줄씩 읽어 처리한다.
         while (true) {
             // 읽어온 줄을 분석한다.
             $entry = $zip->read_line();
             if ($is_first_entry && $entry === false) {
                 break;
             }
             $is_first_entry = false;
             // 이전 주소가 없다면 방금 읽어온 줄을 이전 주소로 설정한다.
             if ($last_entry === null) {
                 $last_entry = $entry;
                 if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                     $last_nums = array(preg_replace('/동$/u', '', $entry->building_detail));
                 } elseif ($entry->building_detail) {
                     $last_entry->building_names[] = $last_entry->building_detail;
                     $last_nums = array();
                 } else {
                     $last_nums = array();
                 }
             } elseif ($entry === false || $last_entry->road_id !== $entry->road_id || $last_entry->road_section !== $entry->road_section || $last_entry->num_major !== $entry->num_major || $last_entry->num_minor !== $entry->num_minor || $last_entry->is_basement !== $entry->is_basement) {
                 // 디버깅을 위한 변수들.
                 $update_type = false;
                 $postcode6_is_guess = false;
                 $postcode5_is_guess = false;
                 // 이미 존재하는 주소인지 확인한다.
                 $ps_addr_select->execute(array($last_entry->road_id . '00', $last_entry->road_id . '99', $last_entry->num_major, $last_entry->num_minor, $last_entry->num_minor, $last_entry->is_basement));
                 $address_info = $ps_addr_select->fetchObject();
                 $ps_addr_select->closeCursor();
                 // 도로 정보를 파악한다.
                 $ps_road_select->execute(array($last_entry->road_id . $last_entry->road_section));
                 $road_info = $ps_road_select->fetchObject();
                 $ps_road_select->closeCursor();
                 // 도로 정보가 없는 경우, 더미 레코드를 생성한다.
                 if (!$road_info) {
                     // 더미 레코드를 DB에 입력한다.
                     $ps_road_insert->execute(array($last_entry->road_id . $last_entry->road_section, $last_entry->road_name, $this->find_english_name($db, $last_entry->road_name), $last_entry->sido, $this->find_english_name($db, $last_entry->sido), $last_entry->sigungu, $this->find_english_name($db, $last_entry->sigungu), $last_entry->ilbangu, $this->find_english_name($db, $last_entry->ilbangu), $last_entry->eupmyeon, $this->find_english_name($db, $last_entry->eupmyeon), '99999999'));
                     // 입력한 더미 레코드를 DB에서 다시 불러온다.
                     $ps_road_select->execute(array($last_entry->road_id . $last_entry->road_section));
                     $road_info = $ps_road_select->fetchObject();
                     $ps_road_select->closeCursor();
                 }
                 // 이미 존재하는 주소인 경우, 기존의 검색 키워드와 번호들을 가져온다.
                 $existing_keywords = $existing_numbers = $existing_buildings = array();
                 if ($address_info) {
                     $ps_kwd_select->execute(array($address_info->id));
                     while ($row = $ps_kwd_select->fetchColumn()) {
                         $existing_keywords[$row] = true;
                     }
                     $ps_kwd_select->closeCursor();
                     $ps_num_select->execute(array($address_info->id));
                     while ($row = $ps_num_select->fetch(PDO::FETCH_NUM)) {
                         $existing_numbers[implode('-', $row)] = true;
                     }
                     $ps_num_select->closeCursor();
                     $ps_building_select->execute(array($address_info->id));
                     while ($row = $ps_building_select->fetchColumn()) {
                         $existing_buildings[] = $row;
                     }
                     $ps_building_select->closeCursor();
                 }
                 // 신설과 변경 주소 구분은 변경코드에 의존하지 않고,
                 // 로컬 DB에 해당 주소가 이미 존재하는지에 따라 판단한다.
                 if ($last_entry->change_reason !== self::ADDR_DELETED) {
                     // 우편번호가 누락된 경우, 범위 데이터를 사용하여 찾거나 기존 주소의 우편번호를 그대로 사용한다.
                     if ($last_entry->postcode6 === null || $last_entry->postcode6 === '000000') {
                         if ($address_info && $address_info->postcode6 !== null) {
                             $last_entry->postcode6 = $address_info->postcode6;
                         } elseif ($this->_add_old_postcodes) {
                             $last_entry->postcode6 = $this->find_postcode6($db, $road_info, $last_entry->dongri, $last_entry->admin_dongri, $last_entry->jibeon_major, $last_entry->jibeon_minor);
                             $postcode6_is_guess = true;
                         }
                     }
                     if ($last_entry->postcode5 === null || $last_entry->postcode5 === '00000') {
                         if ($address_info && $address_info->postcode5 !== null) {
                             $last_entry->postcode5 = $address_info->postcode5;
                         } else {
                             $last_entry->postcode5 = $this->find_postcode5($db, $road_info, $last_entry->num_major, $last_entry->num_minor, $last_entry->dongri, $last_entry->admin_dongri, $last_entry->jibeon_major, $last_entry->jibeon_minor, $last_entry->postcode6);
                             $postcode5_is_guess = true;
                         }
                     }
                     // 영문 동·리를 구한다.
                     if ($address_info && $last_entry->dongri === $address_info->dongri_ko) {
                         $dongri_en = $address_info->dongri_en;
                     } else {
                         $dongri_en = $this->find_english_name($db, $last_entry->dongri);
                     }
                     // 상세건물명과 기타 주소를 정리한다.
                     $building_nums = Postcodify_Utility::consolidate_building_nums($last_nums);
                     if ($building_nums === '') {
                         $building_nums = null;
                     }
                     $other_addresses = array();
                     if ($last_entry->admin_dongri && $last_entry->admin_dongri !== $last_entry->dongri) {
                         $other_addresses[] = $last_entry->admin_dongri;
                     }
                     $last_entry->building_names = array_unique($last_entry->building_names);
                     $last_entry->building_names = Postcodify_Utility::consolidate_building_names($last_entry->building_names, $last_entry->common_residence_name);
                     natcasesort($last_entry->building_names);
                     foreach ($last_entry->building_names as $building_name) {
                         $other_addresses[] = str_replace(';', ':', $building_name);
                     }
                     // 더미 레코드에 관련지번이 먼저 입력되어 있는 경우, 다시 추가한다.
                     if ($address_info && strval($address_info->updated) === '99999999' && strval($address_info->other_addresses) !== '') {
                         $other_addresses[] = $address_info->other_addresses;
                     }
                     $other_addresses = implode('; ', $other_addresses);
                     if ($other_addresses === '') {
                         $other_addresses = null;
                     }
                     // 공동주택인데 공동주택명이 없는 경우 다른 건물명을 이용한다.
                     if ($last_entry->is_common_residence && $last_entry->common_residence_name === null) {
                         if (strpos($other_addresses, '; ') === false) {
                             $last_entry->common_residence_name = $other_addresses;
                             $other_addresses = null;
                         }
                     }
                     // 신설 주소인 경우...
                     if (!$address_info) {
                         $ps_addr_insert->execute(array($last_entry->postcode5, $last_entry->postcode6, $last_entry->road_id . $last_entry->road_section, $last_entry->num_major, $last_entry->num_minor, $last_entry->is_basement, $last_entry->dongri, $dongri_en, $last_entry->jibeon_major, $last_entry->jibeon_minor, $last_entry->is_mountain, $last_entry->building_id, $last_entry->common_residence_name, $building_nums, $other_addresses, $last_entry->updated));
                         $proxy_id = $db->lastInsertId();
                         $update_type = 'C';
                     } else {
                         $ps_addr_update->execute(array($last_entry->postcode5, $last_entry->postcode6, $last_entry->road_id . $last_entry->road_section, $last_entry->num_major, $last_entry->num_minor, $last_entry->is_basement, $last_entry->dongri, $dongri_en, $last_entry->jibeon_major, $last_entry->jibeon_minor, $last_entry->is_mountain, $last_entry->building_id, $last_entry->common_residence_name, $building_nums, $other_addresses, $last_entry->updated, $address_info->id));
                         $proxy_id = $address_info->id;
                         $update_type = 'M';
                     }
                     // 검색 키워드를 정리하여 저장한다.
                     $keywords = array();
                     $keywords = array_merge($keywords, Postcodify_Utility::get_variations_of_road_name($road_info->road_name_ko));
                     $keywords = array_merge($keywords, Postcodify_Utility::get_variations_of_dongri($last_entry->dongri));
                     $keywords = array_merge($keywords, Postcodify_Utility::get_variations_of_dongri($last_entry->admin_dongri));
                     $keywords = array_unique($keywords);
                     foreach ($keywords as $keyword) {
                         $keyword_crc32 = Postcodify_Utility::crc32_x64($keyword);
                         if (isset($existing_keywords[$keyword_crc32])) {
                             continue;
                         }
                         $ps_kwd_insert->execute(array($proxy_id, $keyword_crc32));
                     }
                     // 번호들을 정리하여 저장한다.
                     $numbers = array(array($last_entry->num_major, $last_entry->num_minor), array($last_entry->jibeon_major, $last_entry->jibeon_minor));
                     /*
                     if (preg_match('/([0-9]+)번?길$/u', $road_info->road_name_ko, $road_name_matches))
                     {
                         $numbers[] = array(intval($road_name_matches[1]), null);
                     }
                     */
                     foreach ($numbers as $number) {
                         $number_key = implode('-', $number);
                         if (isset($existing_numbers[$number_key])) {
                             continue;
                         }
                         $ps_num_insert->execute(array($proxy_id, $number[0], $number[1]));
                     }
                     // 건물명을 정리하여 저장한다.
                     $building_names = array_merge($existing_buildings, $last_entry->building_names);
                     $building_names_str = Postcodify_Utility::compress_building_names($building_names);
                     if ($building_names_str !== '' && !in_array($building_names_str, $existing_buildings)) {
                         if (count($existing_buildings)) {
                             $ps_building_update->execute(array($building_names_str, $proxy_id));
                         } else {
                             $ps_building_insert->execute(array($proxy_id, $building_names_str));
                         }
                     }
                 }
                 // 폐지된 주소인 경우...
                 if ($last_entry->change_reason === self::ADDR_DELETED) {
                     // 행자부에서 멀쩡한 주소를 삭제했다가 며칠 후 다시 추가하는 경우가 종종 있다.
                     // 이걸 너무 열심히 따라하면 애꿎은 사용자들이 불편을 겪게 되므로
                     // 주소가 폐지된 것으로 나오더라도 DB에는 그대로 두는 것이 좋다.
                     // 나중에 다시 추가될 경우 위의 코드에 따라 업데이트로 처리하면 그만이다.
                     $update_type = 'D';
                 }
                 // 카운터를 표시한다.
                 if (++$count % 32 === 0) {
                     Postcodify_Utility::print_progress($count);
                 }
                 // 불필요한 변수들을 unset한다.
                 unset($address_info, $road_info, $road_name_matches, $existing_keywords, $existing_numbers, $existing_buildings);
                 unset($keywords, $numbers, $building_names, $building_names_str);
                 unset($last_entry, $last_nums);
                 // 방금 읽어온 줄을 새로운 이전 주소로 설정한다.
                 if ($entry !== false) {
                     $last_entry = $entry;
                     if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                         $last_nums = array(preg_replace('/동$/u', '', $entry->building_detail));
                     } elseif ($entry->building_detail) {
                         $last_entry->building_names[] = $last_entry->building_detail;
                         $last_nums = array();
                     } else {
                         $last_nums = array();
                     }
                 }
             } else {
                 if (count($entry->building_names)) {
                     $last_entry->building_names = array_merge($last_entry->building_names, $entry->building_names);
                 }
                 if (($entry->has_detail || $entry->is_common_residence) && preg_match('/.+동$/u', $entry->building_detail)) {
                     $last_nums[] = preg_replace('/동$/u', '', $entry->building_detail);
                 } elseif ($entry->building_detail) {
                     $last_entry->building_names[] = $entry->building_detail;
                 }
             }
             // 더이상 데이터가 없는 경우 루프를 탈출한다.
             if ($entry === false) {
                 break;
             }
             // 메모리 누수를 방지하기 위해 모든 배열을 unset한다.
             unset($entry);
         }
         // 건물정보 파일을 닫는다.
         $zip->close();
         unset($zip);
         Postcodify_utility::print_ok($count);
         // 관련지번 파일을 연다.
         Postcodify_Utility::print_message('  - 관련지번');
         $zip = new Postcodify_Parser_NewJibeon();
         $zip->open_archive($filename);
         $open_status = $zip->open_named_file('TI_SCCO_MVMN');
         if (!$open_status) {
             continue;
         }
         // 카운터를 초기화한다.
         $count = 0;
         // 관련지번 업데이트는 건물정보 파일처럼 도로명주소 단위로 연속되어 나온다는 보장이 없다.
         // 따라서 $last_entry를 사용하는 방식은 적절하지 않고, 1천 건 단위로 분류하여 사용한다.
         // 1천 건의 경계선에 걸리는 주소는 두 번에 걸쳐 업데이트되는 비효율성이 있으나,
         // 현실적으로 하루에 1천 건 이상 업데이트되는 경우는 드물다.
         while (true) {
             // 1천 건 단위로 읽어와서 도로명주소 단위로 분류한다.
             $entries = array();
             for ($i = 0; $i < 1000; $i++) {
                 $entry = $zip->read_line();
                 if ($entry === false) {
                     break;
                 }
                 $key = $entry->road_id . '|' . $entry->num_major . '|' . $entry->num_minor . '|' . $entry->is_basement;
                 $entries[$key][] = array($entry->dongri, $entry->jibeon_major, $entry->jibeon_minor, $entry->is_mountain);
                 unset($entry);
             }
             // 더이상 데이터가 없는 경우 루프를 탈출한다.
             if (!count($entries)) {
                 break;
             }
             // 분류한 데이터를 처리한다.
             foreach ($entries as $key => $jibeons) {
                 // 분류에 사용했던 키를 분해하여 원래의 도로명주소를 구한다.
                 list($road_id, $num_major, $num_minor, $is_basement) = explode('|', $key);
                 $num_major = intval($num_major);
                 $num_minor = intval($num_minor);
                 if (!$num_minor) {
                     $num_minor = null;
                 }
                 $is_basement = intval($is_basement);
                 // 이 주소에 해당하는 도로명주소 레코드를 가져온다.
                 $ps_addr_select->execute(array($road_id . '00', $road_id . '99', $num_major, $num_minor, $num_minor, $is_basement));
                 $address_info = $ps_addr_select->fetchObject();
                 $ps_addr_select->closeCursor();
                 // 레코드를 찾은 경우, 기존 정보를 가져온다.
                 if ($address_info) {
                     // 기존의 건물명 및 지번 목록을 파싱한다.
                     $other_addresses = array('b' => array(), 'j' => array());
                     $other_addresses_raw = explode('; ', $address_info->other_addresses);
                     foreach ($other_addresses_raw as $i => $other_address) {
                         if (preg_match('/^(.+[동리로가])\\s(산?[0-9-]+(?:,\\s산?[0-9-]+)*)$/u', $other_address, $matches)) {
                             $dongri = $matches[1];
                             $nums = explode(', ', $matches[2]);
                             $other_addresses[$dongri] = $nums;
                         } elseif (strlen($other_address)) {
                             $other_addresses['b'][] = $other_address;
                         }
                     }
                     // 기존의 검색 키워드와 번호들을 가져온다.
                     $existing_keywords = array();
                     $ps_kwd_select->execute(array($address_info->id));
                     while ($row = $ps_kwd_select->fetchColumn()) {
                         $existing_keywords[$row] = true;
                     }
                     $ps_kwd_select->closeCursor();
                     $existing_numbers = array();
                     $ps_num_select->execute(array($address_info->id));
                     while ($row = $ps_num_select->fetch(PDO::FETCH_NUM)) {
                         $existing_numbers[implode('-', $row)] = true;
                     }
                     $ps_num_select->closeCursor();
                 } else {
                     // 더미 레코드를 DB에 입력한다.
                     $ps_addr_insert->execute(array(null, null, $road_id . '00', $num_major, $num_minor, $is_basement, $jibeons[0][0], $this->find_english_name($db, $jibeons[0][0]), $jibeons[0][1], $jibeons[0][2], $jibeons[0][3], null, null, null, null, '99999999'));
                     // 입력한 더미 레코드를 DB에서 다시 불러온다.
                     $ps_addr_select->execute(array($road_id . '00', $road_id . '99', $num_major, $num_minor, $num_minor, $is_basement));
                     $address_info = $ps_addr_select->fetchObject();
                     $ps_addr_select->closeCursor();
                     // 기존의 건물명, 지번 목록, 검색 키워드와 번호들은 빈 배열로 초기화한다.
                     $other_addresses = array('b' => array(), 'j' => array());
                     $existing_keywords = array();
                     $existing_numbers = array();
                 }
                 // 업데이트된 지번 목록을 추가하고, 중복을 제거한다.
                 foreach ($jibeons as $last_num) {
                     $numtext = ($last_num[3] ? '산' : '') . $last_num[1] . ($last_num[2] ? '-' . $last_num[2] : '');
                     $other_addresses['j'][$last_num[0]][] = $numtext;
                 }
                 foreach ($other_addresses['j'] as $dongri => $nums) {
                     $other_addresses['j'][$dongri] = array_unique($other_addresses['j'][$dongri]);
                 }
                 // 기타 주소 목록을 정리하여 업데이트한다.
                 $other_addresses_temp = array();
                 foreach ($other_addresses['b'] as $building_name) {
                     $other_addresses_temp[] = $building_name;
                 }
                 foreach ($other_addresses['j'] as $dongri => $nums) {
                     natsort($nums);
                     $other_addresses_temp[] = $dongri . ' ' . implode(', ', $nums);
                 }
                 $ps_addr_update_other->execute(array(implode('; ', $other_addresses_temp), $address_info->id));
                 // 업데이트된 검색 키워드와 번호들을 추가한다.
                 $keywords = array();
                 foreach ($jibeons as $last_num) {
                     $keywords = array_merge($keywords, Postcodify_Utility::get_variations_of_dongri($last_num[0]));
                 }
                 $keywords = array_unique($keywords);
                 foreach ($keywords as $keyword) {
                     $keyword_crc32 = Postcodify_Utility::crc32_x64($keyword);
                     if (isset($existing_keywords[$keyword_crc32])) {
                         continue;
                     }
                     $ps_kwd_insert->execute(array($address_info->id, $keyword_crc32));
                 }
                 // 번호들을 정리하여 저장한다.
                 foreach ($jibeons as $last_num) {
                     $number_key = $last_num[1] . '-' . $last_num[2];
                     if (isset($existing_numbers[$number_key])) {
                         continue;
                     }
                     $ps_num_insert->execute(array($address_info->id, $last_num[1], $last_num[2]));
                 }
                 // 카운터를 표시한다.
                 if (++$count % 32 === 0) {
                     Postcodify_Utility::print_progress($count);
                 }
                 // 불필요한 변수들을 unset한다.
                 unset($key, $jibeons, $road_id, $num_major, $num_minor, $is_basement);
                 unset($address_info, $other_addresses, $other_addresses_raw, $other_addresses_temp, $nums, $last_num);
                 unset($existing_keywords, $existing_numbers);
                 unset($keywords, $numbers);
             }
             // 불필요한 변수들을 unset한다.
             unset($entries);
         }
         // 관련지번 파일을 닫는다.
         $zip->close();
         unset($zip);
         Postcodify_utility::print_ok($count);
     }
     // 뒷정리.
     $db->commit();
     unset($db);
     // 마지막으로 처리한 파일의 기준일을 반환한다.
     return $last_date;
 }