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); }
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; }