├── .gitignore ├── COPYING ├── COPYING.LESSER ├── README.md ├── api ├── popup.css ├── popup.js ├── popup.min.js ├── search.css ├── search.js └── search.min.js ├── example ├── default.html └── popup.html └── lib ├── autoload.php ├── classes ├── client.php ├── indexer │ ├── checkenv.php │ ├── createdb.php │ ├── download.php │ ├── download_updates.php │ ├── set_postcode.php │ ├── sqlite_convert.php │ ├── update.php │ └── verifydb.php ├── parser │ ├── newaddress.php │ ├── newjibeon.php │ ├── newpobox.php │ ├── ranges_jibeon.php │ ├── ranges_oldcode.php │ ├── ranges_oldcode_special.php │ ├── ranges_roads.php │ └── road_list.php ├── server.php ├── server │ ├── areas.php │ ├── cache.php │ ├── database.php │ ├── query.php │ └── result.php ├── textfilereader.php ├── utility.php └── zipreader.php ├── config.example.php ├── emulators ├── sir.php └── xe.php ├── indexer.php ├── resources ├── mysqldump.sh ├── romaja.php └── schema.php └── search.php /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | lib/config.php 3 | -------------------------------------------------------------------------------- /COPYING.LESSER: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Postcodify 소개 3 | 4 | Postcodify는 웹 페이지에서 도로명주소, 지번주소, 영문주소 등을 편리하게 검색할 수 있도록 해주는 프로그램입니다. 5 | 6 | 6백만 건이 넘는 도로명주소 DB를 직접 구축하거나 관리할 필요도 없고, 어렵게 검색 알고리듬을 개발할 필요도 없습니다. 7 | 우편번호 검색이 필요한 웹 페이지에 몇 줄의 jQuery 코드를 붙여넣기만 하면 검색창을 뚝딱 만들어 드립니다. 8 | 9 |
10 | 11 | 12 | 15 | 16 | 자세한 사용법, 기능 소개, 커스터마이징 방법은 17 | [공식 사이트](https://www.poesis.org/postcodify/)의 매뉴얼을 참조하시기 바랍니다. 18 | 19 | - [구현 예제](https://www.poesis.org/postcodify/guide/example) 20 | - [무료 API 및 CDN 활용 안내](https://www.poesis.org/postcodify/guide/freeapi) 21 | - [jQuery 팝업창 매뉴얼](https://www.poesis.org/postcodify/guide/jquery_popup) 22 | - [jQuery 플러그인 매뉴얼](https://www.poesis.org/postcodify/guide/jquery_plugin) 23 | - [검색서버 구축 안내](https://www.poesis.org/postcodify/guide/owndb) 24 | - [API 에뮬레이션 기능 안내](https://www.poesis.org/postcodify/guide/emulation) 25 | 26 | ### 라이센스 27 | 28 | DB 생성 스크립트와 서버측 검색 API, 클라이언트 API 등 29 | Postcodify의 모든 구성요소는 LGPLv3 라이센스 하에 오픈소스로 공개되어 있습니다. 30 | 31 | 개인, 기업, 단체, 공공기관 등 누구나 무료로 사용하실 수 있으며 32 | 상용 프로그램에 포함하여 판매하셔도 되지만, 33 | 버그를 수정하거나 기능을 개선하신 경우 가능하면 GitHub을 통해 공개하셔서 34 | 더 많은 사람들이 개선된 프로그램을 사용할 수 있도록 해주시면 감사하겠습니다. 35 | 36 | ### 다른 구현물 37 | 38 | 다른 어플리케이션이나 프로그래밍 언어에서 Postcodify와 연동할 수 있도록 구현한 프로그램들입니다. 39 | Postcodify에서 공식적으로 지원하지는 않으며, 버전에 따라 호환성에 차이가 있을 수 있습니다. 40 | 41 | - Excel: http://blog.naver.com/lastingchild/220315968310 42 | - Perl: https://github.com/aanoaa/p5-postcodify 43 | 44 | ### 기타 45 | 46 | Postcodify는 새주소 전환을 돕고 웹마스터 여러분의 수고를 덜어드리기 위해 만든 프로그램입니다. 47 | 개발자는 Postcodify와 관련하여 저작권료, 사용료, 자문료 등 어떠한 이윤도 추구하지 않으며, 48 | 무료 API도 자비와 후원금으로 운영중입니다. 49 | 따라서 검색서버에 불필요한 부담이 발생하지 않도록 사용자들에게 검색 요령을 잘 안내해 주시기 바랍니다. 50 | 51 | 무료 API는 검색서버의 원활한 운영과 공평한 사용을 위해 52 | 도메인당, 방문자 IP당 [일일 쿼리수 제한](https://www.poesis.org/postcodify/guide/quota)을 두고 있으며, 53 | 검색 형태에 따라 가중치를 부여합니다. 54 | 쿼리수가 많은 사이트라면 매뉴얼을 참조하여 검색서버를 직접 구축하시거나, 55 | API 운영비를 [후원](https://www.poesis.org/postcodify/guide/sponsor)해 주시면 감사하겠습니다. 56 | 57 | Postcodify는 무료 API를 통해 수집한 주소 정보를 절대 광고에 사용하거나 제3자에게 판매하지 않습니다. 58 | 59 | 1.8, 2.0, 3.0 버전에서 많은 변화가 있었습니다. 60 | 버전별 변경 내역은 [여기](https://www.poesis.org/postcodify/guide/changelog)를 참조하시기 바랍니다. 61 | 62 | 버그 신고, 그 밖의 문의, 기술지원 상담은 [kijin@poesis.org](mailto:kijin@poesis.org?subject=Postcodify)로 연락 주시기 바랍니다. 63 | -------------------------------------------------------------------------------- /api/popup.css: -------------------------------------------------------------------------------- 1 | search.css -------------------------------------------------------------------------------- /api/popup.js: -------------------------------------------------------------------------------- 1 | search.js -------------------------------------------------------------------------------- /api/popup.min.js: -------------------------------------------------------------------------------- 1 | search.min.js -------------------------------------------------------------------------------- /api/search.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Postcodify - 도로명주소 우편번호 검색 프로그램 (클라이언트측 API) 4 | * 5 | * Copyright (c) 2014-2016, Poesis 6 | * 7 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 8 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 9 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 10 | * 개작하거나 재배포할 수 있습니다. 11 | * 12 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 13 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 14 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 15 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 16 | * 17 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 18 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 19 | */ 20 | 21 | div.postcodify_search_form { 22 | clear: both; margin: 12px; padding: 0; position: relative; 23 | font: 13px/160% "Malgun Gothic", "Gulim", sans-serif; 24 | } 25 | 26 | div.postcodify_search_controls { 27 | margin-bottom: 12px; 28 | } 29 | 30 | div.postcodify_search_result { 31 | clear: both; position: relative; width: 100%; 32 | padding: 4px 6px; margin: 0 0 4px -6px; 33 | box-sizing: content-box; word-break: keep-all; 34 | } 35 | 36 | div.postcodify_search_result:hover { background: #f8f8f8; } 37 | div.postcodify_search_result:hover div.address a.selector { font-weight: bold; } 38 | div.postcodify_search_result div.code { position: absolute; left: 6px; top: 4px; color: #c33; } 39 | div.postcodify_search_result div.code6 { position: absolute; left: 6px; top: 4px; color: #24c; display: none; } 40 | div.postcodify_search_result div.code5 { position: absolute; left: 6px; top: 4px; color: #c33; } 41 | div.postcodify_search_result div.address { clear: both; margin-left: 46px; margin-right: 30px; position: relative; cursor: pointer; } 42 | div.postcodify_search_result div.address a.selector { text-decoration: none; cursor: pointer; } 43 | div.postcodify_search_result div.address a.selector span.address_info { color: #000; display: inline-block; margin-right: 8px; } 44 | div.postcodify_search_result div.address a.selector span.extra_info { color: #444; display: inline-block; } 45 | div.postcodify_search_result div.address a.show_old_addresses { display: none; } 46 | div.postcodify_search_result div.old_addresses { clear: both; color: #888; margin: 1px 0 0 46px; font-size: 12px; cursor: pointer; } 47 | div.postcodify_search_result div.map_link { position: absolute; right: 6px; top: 4px; } 48 | div.postcodify_search_result div.map_link a { color: #444; font-size: 11px; text-decoration: none; } 49 | div.postcodify_search_result div.map_link a:hover { text-decoration: underline; } 50 | 51 | div.postcodify_search_status { text-align: center; color: #222; padding: 16px 8px; border: 1px solid #cacaca; background: #f8f8f8; margin-bottom: 8px; } 52 | div.postcodify_search_status.message { color: #222; } 53 | div.postcodify_search_status.too_many { color: #222; } 54 | div.postcodify_search_status.summary { 55 | clear: both; padding: 8px 0 0 0; margin: 12px 0 -8px 0; 56 | border: 0; background: none; border-top: 1px solid #cacaca; text-align: right; 57 | } 58 | div.postcodify_search_status.summary div { 59 | display: inline-block; margin: 0 0 0 12px; 60 | font-size: 11px; line-height: 120%; color: #888; 61 | } 62 | 63 | div.postcodify_popup_background { 64 | width: 100%; height: 100%; 65 | position: fixed; left: 0; top: 0; 66 | background: #000; opacity: 0.55; 67 | z-index: 2147483646; 68 | box-sizing: content-box; 69 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=55)"; 70 | filter: alpha(opacity=55); 71 | } 72 | div.postcodify_popup_background.ie6fix { 73 | display: none; 74 | } 75 | 76 | div.postcodify_popup_layer { 77 | width: 640px; height: 640px; 78 | position: fixed; left: 50%; top: 50%; margin-left: -320px; margin-top: -320px; 79 | font: 13px/160% "Malgun Gothic", "Gulim", sans-serif; 80 | background: #fff; 81 | z-index: 2147483647; 82 | border-radius: 4px; 83 | box-sizing: content-box; 84 | overflow: hidden; 85 | box-shadow: 0 2px 5px 4px rgba(0, 0, 0, 0.55); 86 | _position: absolute; _top: expression(eval(document.body.scrollTop)); 87 | } 88 | div.postcodify_popup_layer.fill_horizontally { 89 | width: 100%; left: 0; margin-left: 0; border-radius: 0; 90 | } 91 | div.postcodify_popup_layer.fill_vertically { 92 | height: 100%; top: 0; margin-top: 0; border-radius: 0; 93 | } 94 | div.postcodify_popup_layer.full_screen { 95 | width: 100%; height: 100%; position: absolute; border-radius: 0; 96 | } 97 | div.postcodify_popup_layer.ie6fix { 98 | top: 0; margin-top: 16px; 99 | border: 1px solid #aaa; 100 | } 101 | 102 | div.postcodify_popup_layer div.postcodify_controls { 103 | clear: both; margin: 0; padding: 0; position: relative; 104 | height: 41px; background: #78909C; 105 | border-top-left-radius: 2px; 106 | border-top-right-radius: 2px; 107 | box-sizing: content-box; 108 | } 109 | 110 | div.postcodify_popup_layer.fill_horizontally div.postcodify_controls, 111 | div.postcodify_popup_layer.fill_vertically div.postcodify_controls, 112 | div.postcodify_popup_layer.full_screen div.postcodify_controls { 113 | border-radius: 0; 114 | } 115 | 116 | div.postcodify_popup_layer div.postcodify_search_controls { 117 | height: 40px; margin: 0 119px 0 0; padding: 0 12px 0 0; 118 | background-color: #f0f0f0; position: relative; 119 | border-bottom: 1px solid #a0a0a0; 120 | border-top-left-radius: 2px; 121 | box-sizing: content-box; 122 | } 123 | div.postcodify_popup_layer div.postcodify_placeholder { 124 | position: absolute; left: 12px; top: 7px; 125 | font-family: inherit; font-size: 16px; line-height: 24px; 126 | border: 0; color: #bbb; 127 | } 128 | div.postcodify_popup_layer label.keyword_label { 129 | position: absolute; top: -24px; 130 | font-size: 16px; color: transparent; 131 | } 132 | div.postcodify_popup_layer input.keyword { 133 | width: 100%; height: 26px; padding: 7px 0 7px 12px; 134 | font-family: inherit; font-size: 16px; line-height: 24px; 135 | background: transparent; border: 0; 136 | border-top-left-radius: 2px; 137 | box-sizing: content-box; 138 | } 139 | div.postcodify_popup_layer input.keyword:focus { 140 | outline: none; 141 | } 142 | div.postcodify_popup_layer div.postcodify_curve_slice { 143 | position: absolute; right: 80px; top: 0; 144 | width: 40px; height: 40px; background: #f0f0f0; 145 | border-bottom-right-radius: 40px; 146 | box-sizing: content-box; 147 | } 148 | div.postcodify_popup_layer div.postcodify_button_area { 149 | position: absolute; right: 0; top: 0; 150 | width: 80px; height: 40px; 151 | box-sizing: content-box; 152 | } 153 | 154 | div.postcodify_popup_layer button.search_button { 155 | position: absolute; right: 30px; top: 7px; 156 | width: 45px; height: 26px; padding-bottom: 2px; 157 | font-family: inherit; font-weight: bold; line-height: 16px; 158 | border: 0; background: #78909C; color: #fff; 159 | box-sizing: border-box; 160 | } 161 | div.postcodify_popup_layer button.search_button:hover, 162 | div.postcodify_popup_layer button.search_button:active { 163 | background: #546E7A; 164 | } 165 | div.postcodify_popup_layer button.close_button { 166 | position: absolute; right: 5px; top: 7px; 167 | width: 25px; height: 26px; 168 | font: bold 16px/16px Tahoma, sans-serif; 169 | border: 0; background: #78909C; color: #fff; 170 | box-sizing: border-box; 171 | } 172 | div.postcodify_popup_layer button.close_button:hover, 173 | div.postcodify_popup_layer button.close_button:active { 174 | background: #546E7A; 175 | } 176 | 177 | div.postcodify_popup_layer div.postcodify_displays { 178 | clear: both; margin: 0; padding: 0; 179 | overflow-x: none; overflow-y: auto; 180 | box-sizing: content-box; 181 | } 182 | 183 | div.postcodify_popup_layer div.postcodify_results { 184 | clear: both; margin: 10px 12px 20px 12px; padding: 0; 185 | box-sizing: content-box; min-height: 48px; 186 | } 187 | div.postcodify_popup_layer.fill_vertically div.postcodify_results { 188 | min-height: 0; 189 | } 190 | 191 | div.postcodify_popup_layer div.postcodify_search_status { 192 | margin: 0 0 8px 0; 193 | padding: 16px 8px; 194 | background: #f8f8f8; 195 | border: 1px solid #cacaca; 196 | border-radius: 0; 197 | color: #222; text-align: center; 198 | } 199 | div.postcodify_popup_layer div.postcodify_search_status.message { color: #222; } 200 | div.postcodify_popup_layer div.postcodify_search_status.too_many { color: #222; } 201 | div.postcodify_popup_layer div.postcodify_search_status.summary { 202 | clear: both; padding: 8px 12px 0 0; margin: 12px -12px 0 -12px; 203 | border: 0; background: none; border-top: 1px solid #cacaca; text-align: right; 204 | box-sizing: content-box; 205 | } 206 | div.postcodify_popup_layer div.postcodify_search_status.summary div { 207 | display: inline-block; margin: 0 0 0 12px; 208 | font-size: 11px; line-height: 120%; color: #888; 209 | } 210 | 211 | div.postcodify_popup_layer div.postcodify_help { 212 | clear: both; margin: 24px 12px 24px 12px; color: #222; 213 | box-sizing: content-box; 214 | } 215 | div.postcodify_popup_layer div.postcodify_help p { 216 | margin: 0 0 16px 0; padding: 0; font-size: 16px; color: #36f; text-align: center; 217 | } 218 | div.postcodify_popup_layer div.postcodify_help ul { 219 | margin: 0 auto 32px auto; padding: 6px 0 0 0; width: 100%; max-width: 540px; box-sizing: border-box; 220 | border: 1px solid #d8d8d8; border-radius: 2px; font: inherit; 221 | } 222 | div.postcodify_popup_layer div.postcodify_help ul li { 223 | margin: 0 0 6px 0; padding: 0 0 6px 0; border-bottom: 1px dotted #e0e0e0; 224 | text-align: center; list-style: none !important; 225 | } 226 | div.postcodify_popup_layer div.postcodify_help ul li:last-child { margin-bottom: 0; border-bottom: 0; } 227 | div.postcodify_popup_layer div.postcodify_help ul li span { 228 | display: inline-block; 229 | } 230 | div.postcodify_popup_layer div.postcodify_help table { 231 | clear: both; margin: 0 auto 32px auto; padding: 0; width: 100%; max-width: 540px; box-sizing: border-box; 232 | border-collapse: collapse; border: 1px solid #d8d8d8; border-radius: 2px; font: inherit; 233 | } 234 | div.postcodify_popup_layer div.postcodify_help table tr { margin: 0; padding: 0; } 235 | div.postcodify_popup_layer div.postcodify_help table th { 236 | margin: 0; padding: 3px; background: #f6f6f6; border: 1px solid #d8d8d8; border-radius: 2px; 237 | text-align: center; font: inherit; font-weight: bold; 238 | } 239 | div.postcodify_popup_layer div.postcodify_help table td { 240 | margin: 0; padding: 3px; background: #fff; border: 1px solid #d8d8d8; border-radius: 2px; 241 | text-align: center; font: inherit; word-break: keep-all; 242 | } 243 | div.postcodify_popup_layer div.postcodify_help table td:first-child { min-width: 74px; } 244 | 245 | div.postcodify_popup_layer div.postcodify_logo { 246 | width: 100%; position: absolute; bottom: 0; padding: 10px 0 0 0; height: 21px; 247 | font: normal 10px/10px "Segoe UI", sans-serif; color: #666; text-align: center; 248 | background: #f4f4f4; border-top: 1px solid #cacaca; 249 | border-bottom-left-radius: 2px; 250 | border-bottom-right-radius: 2px; 251 | box-sizing: content-box; 252 | } 253 | div.postcodify_popup_layer div.postcodify_logo a { 254 | color: #666; text-decoration: none; 255 | } 256 | div.postcodify_popup_layer div.postcodify_logo a:hover { 257 | color: #36f; text-decoration: underline; 258 | } 259 | -------------------------------------------------------------------------------- /example/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 우편번호 검색 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 33 | 34 |

우편번호 검색

35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 76 | 77 | 78 | 79 |
80 |

81 |

82 |

83 |

84 |

85 |

86 |
87 | 88 | 89 | 90 |
91 |

검색 요령

92 |
    93 |
  • 도로명주소 검색 : 도로명과 건물번호를 입력하세요.   예: 세종대로 110
  • 94 |
  • 지번주소 검색 : "동" 또는 "리" 이름과 번지수를 입력하세요.   예: 연산동 1000
  • 95 |
  • 건물명 검색 : 빌딩 또는 아파트 이름을 입력하세요.   예: 방배동 래미안, 수곡동 주공3차
  • 96 |
  • 사서함 검색 : 사서함 이름과 번호를 입력하세요.   예: 광화문우체국사서함 123-4
  • 97 |
98 |

주의사항

99 |
    100 |
  • 시·군·구·읍·면 등은 쓰지 않아도 되지만, 만약 쓰실 경우 반드시 띄어쓰기를 해 주세요.
  • 101 |
  • 도로명에 "××번길" 등이 포함되어 있는 경우에도 잊지 말고 써 주세요.
  • 102 |
  • 건물명보다는 도로명주소 또는 지번 주소로 검색하시는 것이 빠르고 정확합니다.
  • 103 |
104 |
105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /example/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 우편번호 검색 7 | 8 | 9 | 10 | 11 | 12 | 13 | 32 | 33 |

우편번호 검색

34 | 35 | 36 | 37 |
38 |

39 | – 40 |   41 | 42 |

43 |

44 |

45 |

46 |

47 |

48 |
49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Client 23 | { 24 | // 무료 API 경로 및 기타 설정 기본값들. 25 | 26 | const FREEAPI_MAIN_URL = '//api.poesis.kr/post/search.php'; 27 | const FREEAPI_BACKUP_URL = '//api.poesis.kr/post/search.php'; 28 | const MAIN_TIMEOUT = 1000; 29 | const BACKUP_TIMEOUT = 2000; 30 | const USER_AGENT = 'Postcodify Client %s'; 31 | 32 | // 현재 인스턴스 설정. 33 | 34 | protected $config = array(); 35 | 36 | // 사용할 문자셋을 지정한다. 37 | 38 | public function set_charset($charset) 39 | { 40 | if (strtoupper($charset) === 'UTF-8') return; 41 | $this->config['charset'] = $charset; 42 | } 43 | 44 | // 도메인을 지정한다. 45 | 46 | public function set_domain($domain) 47 | { 48 | $this->config['domain'] = $domain; 49 | $this->config['domain_is_valid'] = preg_match('/^[^.|_:\/$#~*]+(\.[^.|_:\/$#~*]+)+$/', $domain); 50 | } 51 | 52 | // API 경로를 지정한다. 53 | 54 | public function set_api_url($url) 55 | { 56 | $this->config['main_url'] = $url; 57 | } 58 | 59 | // API 백업서버 경로를 지정한다. 60 | 61 | public function set_backup_api_url($url) 62 | { 63 | $this->config['backup_url'] = $url; 64 | } 65 | 66 | // API 타임아웃을 지정한다. 단위는 밀리초(ms)이다. 67 | 68 | public function set_timeout($ms) 69 | { 70 | $this->config['main_timeout'] = intval($ms, 10); 71 | } 72 | 73 | // API 백업서버 타임아웃을 지정한다. 단위는 밀리초(ms)이다. 74 | 75 | public function set_backup_timeout($ms) 76 | { 77 | $this->config['backup_timeout'] = intval($ms, 10); 78 | } 79 | 80 | // User-Agent 값을 지정한다. 81 | 82 | public function set_user_agent($ua) 83 | { 84 | $this->config['user_agent'] = trim($ua); 85 | } 86 | 87 | // SSL을 사용하도록 설정한다. 88 | 89 | public function use_ssl() 90 | { 91 | $this->config['use_ssl'] = true; 92 | } 93 | 94 | // 검색을 수행한다. 95 | 96 | public function search($keywords) 97 | { 98 | // 도메인이 지정되었는지 확인한다. 99 | 100 | if (!isset($this->config['domain']) || !$this->config['domain_is_valid']) 101 | { 102 | throw new Exception('Please set a valid domain.'); 103 | } 104 | 105 | // 검색 파라미터를 정리한다. 106 | 107 | $params = http_build_query(array( 108 | 'v' => POSTCODIFY_VERSION, 109 | 'q' => isset($this->config['charset']) ? iconv($this->config['charset'], 'UTF-8', $keywords) : $keywords, 110 | 'ref' => $this->config['domain'], 111 | 'cdn' => '', 112 | )); 113 | 114 | // 메인서버 접속 정보를 정리한다. 115 | 116 | $config = array( 117 | 'params' => $params, 118 | 'url' => isset($this->config['main_url']) ? $this->config['main_url'] : ((isset($this->config['use_ssl']) ? 'https:' : 'http:') . self::FREEAPI_MAIN_URL), 119 | 'timeout' => isset($this->config['main_timeout']) ? $this->config['main_timeout'] : self::MAIN_TIMEOUT, 120 | 'user_agent' => isset($this->config['user_agent']) ? $this->config['user_agent'] : sprintf(self::USER_AGENT, POSTCODIFY_VERSION), 121 | ); 122 | 123 | // 메인서버에서 검색을 시도한다. 124 | 125 | $result = $this->send_curl_request($config); 126 | 127 | // 검색 성공시 검색 결과를 반환한다. 128 | 129 | if ($result !== null && $result !== false) return $result; 130 | 131 | // 검색 실패시 백업서버 경로를 구한다. 132 | 133 | if (!isset($this->config['backup_url']) && !isset($this->config['main_url'])) 134 | { 135 | $this->config['backup_url'] = (isset($this->config['use_ssl']) ? 'https:' : 'http:') . self::FREEAPI_BACKUP_URL; 136 | } 137 | 138 | // 백업서버가 없는 경우 여기서 검색을 그만두고 false를 반환한다. 139 | 140 | if (!isset($this->config['backup_url'])) return false; 141 | 142 | // 백업서버 접속 정보를 정리한다. 143 | 144 | $config['timeout'] = isset($this->config['backup_timeout']) ? $this->config['backup_timeout'] : self::MAIN_TIMEOUT; 145 | $config['url'] = $this->config['backup_url']; 146 | 147 | // 백업서버에서 검색을 시도한다. 148 | 149 | $result = $this->send_curl_request($config); 150 | 151 | // 검색 성공시 검색 결과를 반환하고, 실패시 false를 반환한다. 152 | 153 | if ($result !== null && $result !== false) return $result; 154 | return false; 155 | } 156 | 157 | // cURL 요청을 보내는 메소드. 158 | 159 | protected function send_curl_request($config) 160 | { 161 | $ch = curl_init(); 162 | curl_setopt_array($ch, array( 163 | CURLOPT_URL => $config['url'] . '?' . $config['params'], 164 | CURLOPT_CONNECTTIMEOUT => $config['timeout'], 165 | CURLOPT_USERAGENT => $config['user_agent'], 166 | CURLOPT_RETURNTRANSFER => 1, 167 | )); 168 | 169 | $response = strval(curl_exec($ch)); 170 | $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); 171 | curl_close($ch); 172 | 173 | if ($status == 200 && $response !== '' && $response[0] === '{') 174 | { 175 | return @json_decode($response); 176 | } 177 | else 178 | { 179 | return false; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /lib/classes/indexer/checkenv.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_CheckEnv 23 | { 24 | // 구동 환경을 점검한다. 25 | 26 | public function check($add_old_postcodes) 27 | { 28 | // 기본적인 환경을 확인한다. 29 | 30 | if (version_compare(PHP_VERSION, '5.2', '<')) 31 | { 32 | echo '[ERROR] PHP 버전은 5.2 이상이어야 합니다.' . PHP_EOL; 33 | exit(2); 34 | } 35 | 36 | if (strtolower(PHP_SAPI) !== 'cli') 37 | { 38 | echo '[ERROR] 이 프로그램은 명령줄(CLI)에서 실행되어야 합니다.' . PHP_EOL; 39 | exit(2); 40 | } 41 | 42 | if (strtolower(substr(PHP_OS, 0, 3)) === 'win') 43 | { 44 | echo '[ERROR] 윈도우 환경은 지원하지 않습니다.' . PHP_EOL; 45 | exit(2); 46 | } 47 | 48 | // 필요한 모듈과 함수들을 확인한다. 49 | 50 | if (!class_exists('PDO') || !in_array('mysql', PDO::getAvailableDrivers())) 51 | { 52 | echo '[ERROR] PDO 모듈이 설치되지 않았거나 MySQL 드라이버를 사용할 수 없습니다.' . PHP_EOL; 53 | exit(2); 54 | } 55 | 56 | if (!class_exists('ZipArchive')) 57 | { 58 | echo '[ERROR] Zip 모듈이 설치되어 있지 않습니다.' . PHP_EOL; 59 | exit(2); 60 | } 61 | 62 | if (!function_exists('mb_check_encoding')) 63 | { 64 | echo '[ERROR] mbstring 모듈이 설치되어 있지 않습니다.' . PHP_EOL; 65 | exit(2); 66 | } 67 | 68 | if (!function_exists('pcntl_fork') || !function_exists('pcntl_wait')) 69 | { 70 | echo '[ERROR] pcntl_* 함수가 없거나 php.ini에서 막아 놓았습니다.' . PHP_EOL; 71 | exit(2); 72 | } 73 | 74 | if (!function_exists('shmop_open')) 75 | { 76 | echo '[ERROR] shmop_* 함수가 없거나 php.ini에서 막아 놓았습니다.' . PHP_EOL; 77 | exit(2); 78 | } 79 | 80 | // 필요한 데이터 파일이 모두 있는지 확인한다. 81 | 82 | $data_address_file = null; 83 | $data_files = scandir(dirname(POSTCODIFY_LIB_DIR) . '/data'); 84 | foreach ($data_files as $filename) 85 | { 86 | if (preg_match('/^20[0-9]{4}ALLRDNM\.zip$/', $filename, $matches)) 87 | { 88 | $data_address_file = $filename; 89 | } 90 | } 91 | 92 | if (!$data_address_file) 93 | { 94 | echo '[ERROR] ******ALLRDNM.zip 파일을 찾을 수 없습니다.' . PHP_EOL; 95 | exit(2); 96 | } 97 | 98 | if (!file_exists(dirname(POSTCODIFY_LIB_DIR) . '/data/areacd_pobox_DB.zip')) 99 | { 100 | echo '[ERROR] 우체국 사서함 (areacd_pobox_DB.zip) 파일을 찾을 수 없습니다.' . PHP_EOL; 101 | exit(2); 102 | } 103 | 104 | if (!file_exists(dirname(POSTCODIFY_LIB_DIR) . '/data/areacd_rangeaddr_DB.zip')) 105 | { 106 | echo '[ERROR] 우편번호 범위 (areacd_rangeaddr_DB.zip) 파일을 찾을 수 없습니다.' . PHP_EOL; 107 | exit(2); 108 | } 109 | 110 | if ($add_old_postcodes) 111 | { 112 | if (!file_exists(dirname(POSTCODIFY_LIB_DIR) . '/data/oldaddr_zipcode_DB.zip')) 113 | { 114 | echo '[ERROR] 구 우편번호 범위 (oldaddr_zipcode_DB.zip) 파일을 찾을 수 없습니다.' . PHP_EOL; 115 | exit(2); 116 | } 117 | 118 | if (!file_exists(dirname(POSTCODIFY_LIB_DIR) . '/data/oldaddr_special_DB.zip')) 119 | { 120 | echo '[ERROR] 구 우편번호 범위 (oldaddr_special_DB.zip) 파일을 찾을 수 없습니다.' . PHP_EOL; 121 | exit(2); 122 | } 123 | } 124 | 125 | // DB의 사양을 점검한다. 126 | 127 | if (!($db = Postcodify_Utility::get_db())) 128 | { 129 | echo '[ERROR] MySQL DB에 접속할 수 없습니다.' . PHP_EOL; 130 | exit(2); 131 | } 132 | 133 | $version_query = $db->query('SELECT VERSION()'); 134 | $version = $version_query->fetchColumn(); 135 | 136 | if (!version_compare($version, '5.0', '>=')) 137 | { 138 | echo '[ERROR] MySQL DB의 버전은 5.0 이상이어야 합니다. 현재 사용중인 DB의 버전은 ' . $version . '입니다.' . PHP_EOL; 139 | exit(2); 140 | } 141 | 142 | $innodb_found = false; 143 | $innodb_query = $db->query('SHOW ENGINES'); 144 | while ($row = $innodb_query->fetch(PDO::FETCH_NUM)) 145 | { 146 | if (strtolower($row[0]) === 'innodb') 147 | { 148 | $innodb_found = true; 149 | break; 150 | } 151 | unset($row); 152 | } 153 | 154 | if (!$innodb_found) 155 | { 156 | echo '[ERROR] MySQL DB가 InnoDB 테이블 저장 엔진을 지원하지 않습니다.' . PHP_EOL; 157 | exit(2); 158 | } 159 | 160 | $buffersize_query = $db->query('SHOW VARIABLES LIKE \'innodb_buffer_pool_size\''); 161 | $buffersize = $buffersize_query->fetchColumn(1); 162 | 163 | if ($buffersize < 128 * 1024 * 1024) 164 | { 165 | $buffersize = round($buffersize / 1024 / 1024) . 'M'; 166 | echo '[ERROR] MySQL DB의 InnoDB 버퍼 크기를 128M 이상으로 설정해 주십시오. 현재 설정은 ' . $buffersize . '입니다.' . PHP_EOL; 167 | exit(2); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/classes/indexer/download.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_Download 23 | { 24 | // 상수 선언 부분. 25 | 26 | const RELATIVE_DOMAIN = 'http://www.juso.go.kr'; 27 | const DOWNLOAD_URL = '/dn.do?reqType=ALLRDNM®Ymd=%1$04d&ctprvnCd=00&gubun=RDNM&stdde=%1$04d%2$02d&fileName=%1$04d%2$02d_%%EA%%B1%%B4%%EB%%AC%%BCDB_%%EC%%A0%%84%%EC%%B2%%B4%%EB%%B6%%84.zip&realFileName=%1$04d%2$02dALLRDNM00.zip&indutyCd=999&purpsCd=999&indutyRm=%%EC%%88%%98%%EC%%A7%%91%%EC%%A2%%85%%EB%%A3%%8C&purpsRm=%%EC%%88%%98%%EC%%A7%%91%%EC%%A2%%85%%EB%%A3%%8C'; 28 | const POBOX_URL = 'http://www.epost.go.kr/search/areacd/areacd_pobox_DB.zip'; 29 | const RANGES_URL = 'http://www.epost.go.kr/search/areacd/areacd_rangeaddr_DB.zip'; 30 | const OLDADDR_ZIPCODE_URL = 'http://cdn.poesis.kr/archives/oldaddr_zipcode_DB.zip'; 31 | const OLDADDR_SPECIAL_URL = 'http://cdn.poesis.kr/archives/oldaddr_special_DB.zip'; 32 | 33 | // 엔트리 포인트. 34 | 35 | public function start() 36 | { 37 | // 다운로드할 경로가 존재하는지 확인한다. 38 | 39 | Postcodify_Utility::print_message('Postcodify Indexer ' . POSTCODIFY_VERSION); 40 | Postcodify_Utility::print_newline(); 41 | 42 | $download_path = dirname(POSTCODIFY_LIB_DIR) . '/data'; 43 | $downloaded_files = 0; 44 | 45 | if ((!file_exists($download_path) || !is_dir($download_path)) && !@mkdir($download_path, 0755)) 46 | { 47 | echo '[ERROR] 다운로드 대상 경로(' . $download_path . ')가 존재하지 않습니다.' . PHP_EOL; 48 | exit(2); 49 | } 50 | 51 | // 데이터가 존재하는 가장 최근 년월을 찾는다. 52 | 53 | $current_day = intval(date('d')); 54 | $data_year = intval(date('Y', time() - (86400 * ($current_day > 15 ? 35 : 50)))); 55 | $data_month = intval(date('m', time() - (86400 * ($current_day > 15 ? 35 : 50)))); 56 | $data_day = intval(date('t', mktime(12, 0, 0, $data_month, 1, $data_year))); 57 | 58 | Postcodify_Utility::print_message('데이터 기준일은 ' . $data_year . '년 ' . $data_month . '월 ' . $data_day . '일입니다.'); 59 | 60 | // 주소 데이터를 다운로드한다. 61 | 62 | $download_url = self::RELATIVE_DOMAIN . sprintf(self::DOWNLOAD_URL, $data_year, $data_month); 63 | $filename = sprintf('%04d%02d%s.zip', $data_year, $data_month, 'ALLRDNM'); 64 | $filepath = $download_path . '/' . $filename; 65 | 66 | Postcodify_Utility::print_message('다운로드: ' . $filename); 67 | $result = Postcodify_Utility::download($download_url, $filepath, array(__CLASS__, 'progress')); 68 | if (!$result || !file_exists($filepath) || filesize($filepath) < 1024) 69 | { 70 | Postcodify_Utility::print_error(); 71 | exit(2); 72 | } 73 | else 74 | { 75 | Postcodify_Utility::print_ok(filesize($filepath)); 76 | $downloaded_files++; 77 | } 78 | 79 | // 우체국 사서함 파일을 다운로드한다. 80 | 81 | Postcodify_Utility::print_message('다운로드: ' . basename(self::POBOX_URL)); 82 | $filepath = $download_path . '/' . basename(self::POBOX_URL); 83 | $result = Postcodify_Utility::download(self::POBOX_URL, $filepath, array(__CLASS__, 'progress')); 84 | if (!$result || !file_exists($filepath) || filesize($filepath) < 1024) 85 | { 86 | Postcodify_Utility::print_error(); 87 | exit(2); 88 | } 89 | else 90 | { 91 | Postcodify_Utility::print_ok(filesize($filepath)); 92 | $downloaded_files++; 93 | } 94 | 95 | // 우편번호 범위 데이터를 다운로드한다. 96 | 97 | Postcodify_Utility::print_message('다운로드: ' . basename(self::RANGES_URL)); 98 | $filepath = $download_path . '/' . basename(self::RANGES_URL); 99 | $result = Postcodify_Utility::download(self::RANGES_URL, $filepath, array(__CLASS__, 'progress')); 100 | if (!$result || !file_exists($filepath) || filesize($filepath) < 1024) 101 | { 102 | Postcodify_Utility::print_error(); 103 | exit(2); 104 | } 105 | else 106 | { 107 | Postcodify_Utility::print_ok(filesize($filepath)); 108 | $downloaded_files++; 109 | } 110 | 111 | // 구 우편번호 범위 데이터를 다운로드한다. 112 | 113 | Postcodify_Utility::print_message('다운로드: ' . basename(self::OLDADDR_ZIPCODE_URL)); 114 | $filepath = $download_path . '/' . basename(self::OLDADDR_ZIPCODE_URL); 115 | $result = Postcodify_Utility::download(self::OLDADDR_ZIPCODE_URL, $filepath, array(__CLASS__, 'progress')); 116 | if (!$result || !file_exists($filepath) || filesize($filepath) < 1024) 117 | { 118 | Postcodify_Utility::print_error(); 119 | exit(2); 120 | } 121 | else 122 | { 123 | Postcodify_Utility::print_ok(filesize($filepath)); 124 | $downloaded_files++; 125 | } 126 | 127 | Postcodify_Utility::print_message('다운로드: ' . basename(self::OLDADDR_SPECIAL_URL)); 128 | $filepath = $download_path . '/' . basename(self::OLDADDR_SPECIAL_URL); 129 | $result = Postcodify_Utility::download(self::OLDADDR_SPECIAL_URL, $filepath, array(__CLASS__, 'progress')); 130 | if (!$result || !file_exists($filepath) || filesize($filepath) < 1024) 131 | { 132 | Postcodify_Utility::print_error(); 133 | exit(2); 134 | } 135 | else 136 | { 137 | Postcodify_Utility::print_ok(filesize($filepath)); 138 | $downloaded_files++; 139 | } 140 | 141 | // 파일 수가 맞는지 확인한다. 142 | 143 | if ($downloaded_files < 5) 144 | { 145 | echo '[ERROR] 다운로드한 파일 수가 일치하지 않습니다.' . PHP_EOL; 146 | exit(2); 147 | } 148 | } 149 | 150 | // 다운로드 진행 상황 표시 콜백 함수. 151 | 152 | public static function progress($ch, $fd = null, $size = null) 153 | { 154 | if ($size <= 0) return; 155 | Postcodify_Utility::print_progress($size); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/classes/indexer/download_updates.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_Download_Updates 23 | { 24 | // 상수 선언 부분. 25 | 26 | const RELATIVE_DOMAIN = 'http://www.juso.go.kr'; 27 | const DOWNLOAD_URL = '/dn.do?reqType=DC&stdde=%s&indutyCd=999&purpsCd=999&indutyRm=%%EC%%88%%98%%EC%%A7%%91%%EC%%A2%%85%%EB%%A3%%8C&purpsRm=%%EC%%88%%98%%EC%%A7%%91%%EC%%A2%%85%%EB%%A3%%8C'; 28 | 29 | // 엔트리 포인트. 30 | 31 | public function start() 32 | { 33 | // 다운로드할 경로가 존재하는지 확인한다. 34 | 35 | Postcodify_Utility::print_message('Postcodify Indexer ' . POSTCODIFY_VERSION); 36 | Postcodify_Utility::print_newline(); 37 | 38 | $download_path = dirname(POSTCODIFY_LIB_DIR) . '/data'; 39 | 40 | if ((!file_exists($download_path) || !is_dir($download_path)) && !@mkdir($download_path, 0755, true)) 41 | { 42 | echo '[ERROR] 다운로드 대상 경로(' . $download_path . ')가 존재하지 않습니다.' . PHP_EOL; 43 | exit(2); 44 | } 45 | 46 | // 어디까지 업데이트했는지 찾아본다. 47 | 48 | $db = Postcodify_Utility::get_db(); 49 | $updated_query = $db->query('SELECT v FROM postcodify_settings WHERE k = \'updated\''); 50 | $updated = $updated_query->fetchColumn(); 51 | $updated_query->closeCursor(); 52 | if (!preg_match('/^20[0-9]{6}$/', $updated)) 53 | { 54 | echo '[ERROR] 기존 DB의 데이터 기준일을 찾을 수 없습니다.' . PHP_EOL; 55 | exit(3); 56 | } 57 | 58 | $current_time = mktime(12, 0, 0, date('m'), date('d'), date('Y')); 59 | $updated_time = mktime(12, 0, 0, substr($updated, 4, 2), substr($updated, 6, 2), substr($updated, 0, 4)); 60 | if ($updated_time < $current_time - (86400 * 365)) 61 | { 62 | echo '[ERROR] 마지막 업데이트로부터 365일 이상이 경과하였습니다. DB를 새로 생성하시기 바랍니다.' . PHP_EOL; 63 | exit(3); 64 | } 65 | if ($updated_time >= $current_time) 66 | { 67 | echo '업데이트가 필요하지 않습니다.' . PHP_EOL; 68 | exit(0); 69 | } 70 | 71 | // 다운로드할 업데이트 목록을 생성한다. 72 | 73 | $updates = array(); 74 | for ($time = $updated_time + 86400; $time < $current_time; $time += 86400) 75 | { 76 | $updates[] = date('Ymd', $time); 77 | } 78 | 79 | // 업데이트를 다운로드한다. 80 | 81 | foreach ($updates as $date) 82 | { 83 | $filepath = $download_path . '/' . $date . '_dailynoticedata.zip'; 84 | if (file_exists($filepath)) 85 | { 86 | Postcodify_Utility::print_message('파일이 이미 존재함: ' . $date . '_dailynoticedata.zip'); 87 | continue; 88 | } 89 | 90 | Postcodify_Utility::print_message('다운로드: ' . $date . '_dailynoticedata.zip'); 91 | 92 | $link = self::RELATIVE_DOMAIN . sprintf(self::DOWNLOAD_URL, $date); 93 | $result = Postcodify_Utility::download($link, $filepath, array(__CLASS__, 'progress')); 94 | if (!$result || !file_exists($filepath)) 95 | { 96 | Postcodify_Utility::print_error(); 97 | @unlink($filepath); 98 | continue; 99 | } 100 | 101 | if (filesize($filepath) < 512 && stripos(file_get_contents($filepath), 'not found') !== false) 102 | { 103 | Postcodify_Utility::print_error(); 104 | @unlink($filepath); 105 | continue; 106 | } 107 | 108 | $zip = new ZipArchive; 109 | $result = $zip->open($filepath); 110 | if (!$result) 111 | { 112 | Postcodify_Utility::print_error(); 113 | @unlink($filepath); 114 | continue; 115 | } 116 | 117 | Postcodify_Utility::print_ok(filesize($filepath)); 118 | } 119 | } 120 | 121 | // 다운로드 진행 상황 표시 콜백 함수. 122 | 123 | public static function progress($ch, $fd, $size) 124 | { 125 | if ($size <= 0) return; 126 | Postcodify_Utility::print_progress($size); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/classes/indexer/set_postcode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_Set_Postcode 23 | { 24 | // 엔트리 포인트. 25 | 26 | public function start($args) 27 | { 28 | // 주어진 옵션을 확인한다. 29 | 30 | $valid_options = false; 31 | if (count($args->args) === 2 && ctype_digit($args->args[0]) && ctype_digit($args->args[1]) && (strlen($args->args[1]) === 5 || strlen($args->args[1]) === 6)) 32 | { 33 | $address_id = $args->args[0]; 34 | $postcode = $args->args[1]; 35 | } 36 | else 37 | { 38 | echo 'Usage: php indexer.php set-postcode ' . PHP_EOL; 39 | exit(1); 40 | } 41 | 42 | // DB 연결을 확인한다. 43 | 44 | Postcodify_Utility::print_message('Postcodify Indexer ' . POSTCODIFY_VERSION); 45 | Postcodify_Utility::print_newline(); 46 | 47 | if (!($db = Postcodify_Utility::get_db())) 48 | { 49 | echo '[ERROR] MySQL DB에 접속할 수 없습니다.' . PHP_EOL; 50 | exit(1); 51 | } 52 | 53 | // 주어진 주소가 존재하는지 확인한다. 54 | 55 | $ps_select = $db->prepare('SELECT pa.*, pr.* FROM postcodify_addresses pa JOIN postcodify_roads pr ON pa.road_id = pr.road_id WHERE pa.id = ? ORDER BY id LIMIT 1'); 56 | $ps_select->execute(array($address_id)); 57 | $entry = $ps_select->fetchObject(); 58 | 59 | if (!$entry) 60 | { 61 | echo '[ERROR] "' . $address_id . '" 레코드를 찾을 수 없습니다.' . PHP_EOL; 62 | exit(1); 63 | } 64 | 65 | // 우편번호를 업데이트한다. 66 | 67 | $column_name = strlen($postcode) === 6 ? 'postcode6' : 'postcode5'; 68 | $ps_update = $db->prepare('UPDATE postcodify_addresses SET ' . $column_name . ' = ? WHERE id = ?'); 69 | $ps_update->execute(array($postcode, $entry->id)); 70 | $entry->$column_name = $postcode; 71 | 72 | // 변경 내역을 표시한다. 73 | 74 | echo ' #' . $entry->id . ' ' . str_pad($entry->postcode6, 6, ' ') . ' ' . str_pad($entry->postcode5, 5, ' ') . ' ' . 75 | $this->format_address($entry) . PHP_EOL; 76 | } 77 | 78 | // 디버깅을 위해 주소를 포맷하는 메소드. 79 | 80 | public function format_address($entry) 81 | { 82 | $result = $entry->sido_ko . ' ' . $entry->sigungu_ko . ' ' . $entry->ilbangu_ko . ' ' . $entry->eupmyeon_ko . ' ' . 83 | $entry->road_name_ko . ' ' . $entry->num_major . ($entry->num_minor ? ('-' . $entry->num_minor) : ''); 84 | return preg_replace('/\s+/', ' ', $result); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/classes/indexer/sqlite_convert.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_SQLite_Convert 23 | { 24 | // 스키마 저장소. 25 | 26 | protected $_tables = array(); 27 | protected $_columns = array(); 28 | protected $_indexes = array(); 29 | 30 | // 엔트리 포인트. 31 | 32 | public function start($args) 33 | { 34 | // SQLite 파일명을 구한다. 35 | 36 | if (count($args->args)) 37 | { 38 | $filename = $args->args[0]; 39 | if (@file_put_contents($filename, '') === false) 40 | { 41 | echo $filename . ' 파일을 생성할 수 없습니다. 경로와 퍼미션을 확인해 주십시오.' . PHP_EOL; 42 | exit(1); 43 | } 44 | } 45 | else 46 | { 47 | echo 'SQLite 파일명을 지정해 주십시오.' . PHP_EOL; 48 | exit(1); 49 | } 50 | 51 | // MySQL DB에 연결한다. 52 | 53 | if (!($mysql = Postcodify_Utility::get_db())) 54 | { 55 | echo '[ERROR] MySQL DB에 접속할 수 없습니다.' . PHP_EOL; 56 | exit(1); 57 | } 58 | 59 | // SQLite DB를 초기화한다. 60 | 61 | Postcodify_Utility::print_message('Postcodify SQLite Converter ' . POSTCODIFY_VERSION); 62 | Postcodify_Utility::print_newline(); 63 | 64 | Postcodify_Utility::print_message('SQLite DB를 초기화하는 중...'); 65 | try 66 | { 67 | $sqlite = $this->initialize_db($filename); 68 | } 69 | catch (PDOException $e) 70 | { 71 | echo '[ERROR] SQLite DB 초기화에 실패했습니다.' . PHP_EOL; 72 | echo $e->getMessage() . PHP_EOL; 73 | exit(1); 74 | } 75 | Postcodify_Utility::print_ok(); 76 | 77 | // 데이터를 복사한다. 78 | 79 | $this->copy_data($mysql, $sqlite); 80 | 81 | // 인덱스를 생성한다. 82 | 83 | $this->create_indexes($sqlite); 84 | 85 | // 인덱스를 최적화한다. 86 | 87 | Postcodify_Utility::print_message('인덱스 최적화 중...'); 88 | $this->wrap_up($sqlite); 89 | Postcodify_Utility::print_ok(); 90 | } 91 | 92 | // SQLite DB를 초기화한다. 93 | 94 | public function initialize_db($filename) 95 | { 96 | $sqlite = new PDO('sqlite:' . $filename); 97 | $sqlite->exec('PRAGMA page_size = 4096'); 98 | $sqlite->exec('PRAGMA synchronous = OFF'); 99 | $sqlite->exec('PRAGMA journal_mode = OFF'); 100 | $sqlite->exec('PRAGMA encoding = "UTF-8"'); 101 | 102 | $schema = (include POSTCODIFY_LIB_DIR . '/resources/schema.php'); 103 | 104 | foreach ($schema as $table_name => $table_definition) 105 | { 106 | $columns = array(); 107 | foreach ($table_definition as $column_name => $column_definition) 108 | { 109 | switch ($column_name) 110 | { 111 | case '_initial': 112 | case '_interim': 113 | case '_indexes': 114 | foreach ($column_definition as $column) 115 | { 116 | $this->_indexes[$table_name][] = $column; 117 | } 118 | break; 119 | default: 120 | if ($column_name[0] !== '_') 121 | { 122 | $column_definition = preg_replace('/(SMALL|TINY)INT\b/', 'INT', $column_definition); 123 | $column_definition = str_replace('INT PRIMARY KEY AUTO_INCREMENT', 'INTEGER PRIMARY KEY', $column_definition); 124 | $column_definition = str_replace(' UNSIGNED', '', $column_definition); 125 | $column_definition = str_replace('NUMERIC', 'CHAR', $column_definition); 126 | $columns[] = $column_name . ' ' . $column_definition; 127 | } 128 | } 129 | } 130 | 131 | $table_query = 'CREATE TABLE ' . $table_name . ' (' . implode(', ', $columns) . ')'; 132 | $this->_tables[$table_name] = $table_query; 133 | 134 | reset($table_definition); 135 | $first_column = key($table_definition); 136 | $this->_columns[$table_name] = array($first_column, count($columns)); 137 | } 138 | 139 | foreach ($this->_tables as $table_query) 140 | { 141 | $sqlite->exec($table_query); 142 | } 143 | 144 | return $sqlite; 145 | } 146 | 147 | // 데이터를 복사한다. 148 | 149 | public function copy_data($mysql, $sqlite) 150 | { 151 | foreach ($this->_columns as $table_name => $table_info) 152 | { 153 | Postcodify_Utility::print_message($table_name . ' 데이터 복사 중...'); 154 | 155 | $row_count_query = $mysql->query('SELECT COUNT(*) FROM ' . $table_name); 156 | $row_count = intval($row_count_query->fetchColumn()); 157 | 158 | $columns_placeholder = implode(', ', array_fill(0, $table_info[1], '?')); 159 | $primary_key = $table_info[0]; 160 | $last_primary_key = 0; 161 | $increment = 2048; 162 | 163 | $ps = $sqlite->prepare('INSERT INTO ' . $table_name . ' VALUES (' . $columns_placeholder . ')'); 164 | 165 | for ($i = 0; $i < $row_count; $i += $increment) 166 | { 167 | Postcodify_Utility::print_progress($i, $row_count); 168 | 169 | $sqlite->beginTransaction(); 170 | 171 | if ($table_name === 'postcodify_settings') 172 | { 173 | $cond = ' ORDER BY ' . $primary_key; 174 | } 175 | else 176 | { 177 | $cond = ' WHERE ' . $primary_key . ' > ? ORDER BY ' . $primary_key . ' LIMIT ' . $increment; 178 | } 179 | 180 | $query = $mysql->prepare('SELECT * FROM ' . $table_name . $cond); 181 | if ($table_name !== 'postcodify_settings') 182 | { 183 | $query->bindParam(1, $last_primary_key, PDO::PARAM_INT); 184 | } 185 | $query->execute(); 186 | 187 | while ($row = $query->fetch(PDO::FETCH_NUM)) 188 | { 189 | $last_primary_key = $row[0]; 190 | $ps->execute($row); 191 | } 192 | 193 | $sqlite->commit(); 194 | } 195 | 196 | Postcodify_Utility::print_ok($row_count); 197 | } 198 | } 199 | 200 | // 인덱스를 생성한다. 201 | 202 | public function create_indexes($sqlite) 203 | { 204 | foreach ($this->_indexes as $table_name => $columns) 205 | { 206 | Postcodify_Utility::print_message($table_name . ' 인덱스 생성 중...'); 207 | 208 | $count = 0; 209 | foreach ($columns as $column) 210 | { 211 | Postcodify_Utility::print_progress($count++, count($columns)); 212 | $sqlite->exec('CREATE INDEX ' . $table_name . '_' . $column . ' ON ' . $table_name . ' (' . $column . ')'); 213 | } 214 | 215 | Postcodify_Utility::print_ok(count($columns)); 216 | } 217 | } 218 | 219 | // SQLite DB를 최적화한다. 220 | 221 | public function wrap_up($sqlite) 222 | { 223 | $sqlite->exec('ANALYZE'); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lib/classes/indexer/verifydb.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Indexer_VerifyDB 23 | { 24 | // 확인할 데이터 정의. 25 | 26 | protected $_schema; 27 | protected $_add_old_postcodes = false; 28 | 29 | // 엔트리 포인트. 30 | 31 | public function start($args) 32 | { 33 | Postcodify_Utility::print_message('Postcodify Indexer ' . POSTCODIFY_VERSION); 34 | Postcodify_Utility::print_newline(); 35 | 36 | if (in_array('--add-old-postcodes', $args->options)) 37 | { 38 | $this->_add_old_postcodes = true; 39 | } 40 | 41 | if (!($db = Postcodify_Utility::get_db())) 42 | { 43 | echo '[ERROR] MySQL DB에 접속할 수 없습니다.' . PHP_EOL; 44 | exit(1); 45 | } 46 | 47 | $this->_schema = (include POSTCODIFY_LIB_DIR . '/resources/schema.php'); 48 | $pass = true; 49 | 50 | echo '테이블 확인 중...' . PHP_EOL; 51 | $pass = $this->check_tables($db) && $pass; 52 | 53 | echo '인덱스 확인 중...' . PHP_EOL; 54 | $pass = $this->check_indexes($db) && $pass; 55 | 56 | if ($pass) 57 | { 58 | echo '데이터 확인 중...' . PHP_EOL; 59 | $pass = $pass && $this->check_data_count($db); 60 | $pass = $pass && $this->check_missing_postcodes($db); 61 | } 62 | else 63 | { 64 | echo 'DB 스키마에 문제가 있으므로 데이터 확인은 시도하지 않습니다.' . PHP_EOL; 65 | } 66 | 67 | if ($pass) 68 | { 69 | echo 'DB에 문제가 없습니다.' . PHP_EOL; 70 | } 71 | else 72 | { 73 | echo 'DB에 문제가 있습니다.' . PHP_EOL; 74 | exit(1); 75 | } 76 | } 77 | 78 | // 모든 테이블이 존재하는지 확인한다. 79 | 80 | public function check_tables($db) 81 | { 82 | $pass = true; 83 | $tables_query = $db->query("SHOW TABLES"); 84 | $tables = $tables_query->fetchAll(PDO::FETCH_NUM); 85 | 86 | foreach ($this->_schema as $table_name => $columns) 87 | { 88 | $found = false; 89 | foreach ($tables as $table) 90 | { 91 | if ($table[0] === $table_name) 92 | { 93 | $found = true; 94 | break; 95 | } 96 | } 97 | if (!$found) 98 | { 99 | echo '[ERROR] ' . $table_name . ' 테이블이 없습니다.' . PHP_EOL; 100 | $pass = false; 101 | } 102 | } 103 | 104 | return $pass; 105 | } 106 | 107 | // 모든 인덱스가 존재하는지 확인한다. 108 | 109 | public function check_indexes($db) 110 | { 111 | $pass = true; 112 | 113 | foreach ($this->_schema as $table_name => $columns) 114 | { 115 | try 116 | { 117 | $table_indexes_query = $db->query("SHOW INDEX FROM $table_name"); 118 | $table_indexes = $table_indexes_query->fetchAll(PDO::FETCH_NUM); 119 | } 120 | catch (PDOException $e) 121 | { 122 | echo '[ERROR] ' . $table_name . ' 테이블의 인덱스를 검사할 수 없습니다.' . PHP_EOL; 123 | $pass = false; 124 | continue; 125 | } 126 | 127 | $pk_found = false; 128 | foreach ($table_indexes as $table_index) 129 | { 130 | if ($table_index[2] === 'PRIMARY') 131 | { 132 | $pk_found = true; 133 | } 134 | } 135 | if (!$pk_found) 136 | { 137 | echo '[ERROR] ' . $table_name . ' 테이블에 PRIMARY KEY가 없습니다.' . PHP_EOL; 138 | $pass = false; 139 | } 140 | 141 | $indexes = array(); 142 | if (isset($columns['_initial'])) $indexes = array_merge($indexes, $columns['_initial']); 143 | if (isset($columns['_interim'])) $indexes = array_merge($indexes, $columns['_interim']); 144 | if (isset($columns['_indexes'])) $indexes = array_merge($indexes, $columns['_indexes']); 145 | 146 | foreach ($indexes as $index_name) 147 | { 148 | $found = false; 149 | foreach ($table_indexes as $table_index) 150 | { 151 | if ($table_index[4] === $index_name) 152 | { 153 | $found = true; 154 | break; 155 | } 156 | } 157 | if (!$found) 158 | { 159 | echo '[ERROR] ' . $table_name . ' 테이블에 ' . $index_name . ' 인덱스가 없습니다.' . PHP_EOL; 160 | $pass = false; 161 | } 162 | } 163 | } 164 | 165 | return $pass; 166 | } 167 | 168 | // 데이터를 검사한다. 169 | 170 | public function check_data_count($db) 171 | { 172 | $pass = true; 173 | 174 | foreach ($this->_schema as $table_name => $columns) 175 | { 176 | if (!isset($columns['_count'])) continue; 177 | 178 | try 179 | { 180 | $count_query = $db->query("SELECT COUNT(*) FROM $table_name"); 181 | $count = $count_query->fetchColumn(); 182 | 183 | if ($count < $columns['_count']) 184 | { 185 | echo '[ERROR] ' . $table_name . ' 테이블의 레코드 수가 부족합니다.' . PHP_EOL; 186 | $pass = false; 187 | } 188 | } 189 | catch (PDOException $e) 190 | { 191 | echo '[ERROR] ' . $table_name . ' 테이블을 조회할 수 없습니다.' . PHP_EOL; 192 | $pass = false; 193 | } 194 | } 195 | 196 | return $pass; 197 | } 198 | 199 | // 우편번호 누락 레코드를 확인한다. 200 | 201 | public function check_missing_postcodes($db) 202 | { 203 | $pass = true; 204 | 205 | if ($this->_add_old_postcodes) 206 | { 207 | $pc6_query = $db->query("SELECT pa.*, pr.* FROM postcodify_addresses pa JOIN postcodify_roads pr ON pa.road_id = pr.road_id " . 208 | "WHERE (postcode6 IS NULL OR postcode6 = '000000') AND building_id IS NOT NULL ORDER BY pa.id LIMIT 100"); 209 | if ($pc6_query->rowCount()) 210 | { 211 | echo '[ERROR] 우편번호(기존번호)가 누락된 레코드가 있습니다.' . PHP_EOL; 212 | while ($entry = $pc6_query->fetch(PDO::FETCH_OBJ)) 213 | { 214 | echo ' #' . $entry->id . ' ' . $this->format_address($entry) . PHP_EOL; 215 | } 216 | $pass = false; 217 | } 218 | } 219 | 220 | $pc5_query = $db->query("SELECT pa.*, pr.* FROM postcodify_addresses pa JOIN postcodify_roads pr ON pa.road_id = pr.road_id " . 221 | "WHERE (postcode5 IS NULL OR postcode5 = '00000') AND building_id IS NOT NULL ORDER BY pa.id LIMIT 100"); 222 | if ($pc5_query->rowCount()) 223 | { 224 | echo '[ERROR] 우편번호(기초구역번호)가 누락된 레코드가 있습니다.' . PHP_EOL; 225 | while ($entry = $pc5_query->fetch(PDO::FETCH_OBJ)) 226 | { 227 | echo ' #' . $entry->id . ' ' . $this->format_address($entry) . PHP_EOL; 228 | } 229 | $pass = false; 230 | } 231 | 232 | return $pass; 233 | } 234 | 235 | // 디버깅을 위해 주소를 포맷하는 메소드. 236 | 237 | public function format_address($entry) 238 | { 239 | if (preg_match('/사서함$/u', $entry->dongri_ko)) 240 | { 241 | $result = $entry->sido_ko . ' ' . $entry->sigungu_ko . ' ' . $entry->ilbangu_ko . ' ' . $entry->eupmyeon_ko . ' ' . 242 | $entry->dongri_ko . ' ' . $entry->other_addresses; 243 | } 244 | else 245 | { 246 | $result = $entry->sido_ko . ' ' . $entry->sigungu_ko . ' ' . $entry->ilbangu_ko . ' ' . $entry->eupmyeon_ko . ' ' . 247 | $entry->road_name_ko . ' ' . $entry->num_major . ($entry->num_minor ? ('-' . $entry->num_minor) : ''); 248 | } 249 | return preg_replace('/\s+/', ' ', $result); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /lib/classes/parser/newaddress.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_NewAddress extends Postcodify_ZipReader 23 | { 24 | // 생성자에서 문자셋을 지정한다. 25 | 26 | public function __construct() 27 | { 28 | $this->_charset = 'CP949'; 29 | } 30 | 31 | // 한 줄을 읽어 반환한다. 32 | 33 | public function read_line($delimiter = '|') 34 | { 35 | // 데이터를 읽는다. 36 | 37 | $line = parent::read_line($delimiter); 38 | if ($line === false || count($line) < 29) return false; 39 | 40 | // 행정구역 정보를 정리한다. 41 | 42 | $sido = trim($line[1]); 43 | $sigungu = trim($line[2]); if ($sigungu === '') $sigungu = null; 44 | $ilbangu = null; 45 | $eupmyeon = trim($line[3]); 46 | if ($eupmyeon === '' || !preg_match('/[읍면]$/u', $eupmyeon)) 47 | { 48 | $eupmyeon = null; 49 | } 50 | if ($sigungu && ($pos = strpos($sigungu, ' ')) !== false) 51 | { 52 | $ilbangu = substr($sigungu, $pos + 1); 53 | $sigungu = substr($sigungu, 0, $pos); 54 | } 55 | 56 | // 도로명주소를 정리한다. 57 | 58 | $road_name = trim($line[9]); 59 | $road_id = trim($line[8]); 60 | $road_section = trim($line[16]); 61 | $num_major = intval($line[11]); 62 | $num_minor = intval($line[12]); if (!$num_minor) $num_minor = null; 63 | $is_basement = intval($line[10]); 64 | $building_id = trim($line[15]); 65 | 66 | // 지번주소를 정리한다. 67 | 68 | $dongri = trim($line[4]); 69 | if ($dongri === '') $dongri = trim($line[3]); 70 | $dongri = preg_replace('/\\(.+\\)/', '', $dongri); 71 | $dongri_id = trim($line[0]); 72 | $admin_dongri = trim($line[18]); 73 | if (!$admin_dongri || !preg_match('/.+동$/u', $admin_dongri)) $admin_dongri = null; 74 | 75 | $jibeon_major = intval($line[6]); 76 | $jibeon_minor = intval($line[7]); if (!$jibeon_minor) $jibeon_minor = null; 77 | $is_mountain = intval($line[5]); 78 | 79 | // 우편번호를 정리한다. 80 | 81 | $postcode6 = trim($line[19]); 82 | if (strlen($postcode6) !== 6) $postcode6 = null; 83 | $postcode5 = trim($line[27]); 84 | if (strlen($postcode5) !== 5) $postcode5 = null; 85 | 86 | // 건물명을 정리한다. 87 | 88 | $is_common_residence = intval($line[26]); 89 | if ($is_common_residence && trim($line[13])) 90 | { 91 | $common_residence_name = trim(preg_replace('/(?:\s|(아파트|빌라|연립))제?[a-zA-Z0-9]+동$/u', '$1', trim($line[13]))); 92 | } 93 | else 94 | { 95 | $common_residence_name = null; 96 | } 97 | 98 | $building_names = array(); 99 | if (!$common_residence_name) 100 | { 101 | if (($building = trim($line[13])) !== '') $building_names[] = $building; 102 | } 103 | if (($building = trim($line[21])) !== '') $building_names[] = $building; 104 | if (($building = trim($line[25])) !== '') $building_names[] = $building; 105 | 106 | $building_names = array_unique($building_names); 107 | $building_detail = preg_replace('/[^가-힣a-zA-Z0-9\/\.\(\)-]/', '', trim($line[14])); 108 | if ($building_detail === '') $building_detail = null; 109 | $has_detail = intval($line[28]); 110 | 111 | // 변경내역을 정리한다. 112 | 113 | $previous_address = $line[24] === '' ? null : trim($line[24]); 114 | $change_reason = $line[22] === '' ? null : intval($line[22]); 115 | $updated = trim($line[23]); 116 | 117 | // 데이터를 정리하여 반환한다. 118 | 119 | return (object)array( 120 | 'sido' => $sido, 121 | 'sigungu' => $sigungu, 122 | 'ilbangu' => $ilbangu, 123 | 'eupmyeon' => $eupmyeon, 124 | 'road_name' => $road_name, 125 | 'road_id' => $road_id, 126 | 'road_section' => $road_section, 127 | 'num_major' => $num_major, 128 | 'num_minor' => $num_minor, 129 | 'is_basement' => $is_basement, 130 | 'dongri' => $dongri, 131 | 'dongri_id' => $dongri_id, 132 | 'admin_dongri' => $admin_dongri, 133 | 'jibeon_major' => $jibeon_major, 134 | 'jibeon_minor' => $jibeon_minor, 135 | 'is_mountain' => $is_mountain, 136 | 'postcode6' => $postcode6, 137 | 'postcode5' => $postcode5, 138 | 'is_common_residence' => $is_common_residence, 139 | 'common_residence_name' => $common_residence_name, 140 | 'building_id' => $building_id, 141 | 'building_names' => $building_names, 142 | 'building_detail' => $building_detail, 143 | 'has_detail' => $has_detail, 144 | 'previous_address' => $previous_address, 145 | 'change_reason' => $change_reason, 146 | 'updated' => $updated, 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/classes/parser/newjibeon.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_NewJibeon extends Postcodify_ZipReader 23 | { 24 | // 생성자에서 문자셋을 지정한다. 25 | 26 | public function __construct() 27 | { 28 | $this->_charset = 'CP949'; 29 | } 30 | 31 | // 한 줄을 읽어 반환한다. 32 | 33 | public function read_line($delimiter = '|') 34 | { 35 | // 데이터를 읽는다. 36 | 37 | $line = parent::read_line($delimiter); 38 | if ($line === false || count($line) < 14) return false; 39 | 40 | // 도로명주소를 정리한다. 41 | 42 | $road_id = trim($line[8]); 43 | $num_major = intval($line[10]); 44 | $num_minor = intval($line[11]); if (!$num_minor) $num_minor = null; 45 | $is_basement = intval($line[9]); 46 | 47 | // 지번주소를 정리한다. 48 | 49 | $dongri = trim($line[4]); 50 | if ($dongri === '') $dongri = trim($line[3]); 51 | $dongri = preg_replace('/\\(.+\\)/', '', $dongri); 52 | 53 | $jibeon_major = intval($line[6]); 54 | $jibeon_minor = intval($line[7]); if (!$jibeon_minor) $jibeon_minor = null; 55 | $is_mountain = intval($line[5]); 56 | 57 | // 변경내역을 정리한다. 58 | 59 | $change_reason = $line[13] === '' ? null : intval($line[13]); 60 | 61 | // 데이터를 정리하여 반환한다. 62 | 63 | return (object)array( 64 | 'road_id' => $road_id, 65 | 'num_major' => $num_major, 66 | 'num_minor' => $num_minor, 67 | 'is_basement' => $is_basement, 68 | 'dongri' => $dongri, 69 | 'jibeon_major' => $jibeon_major, 70 | 'jibeon_minor' => $jibeon_minor, 71 | 'is_mountain' => $is_mountain, 72 | 'change_reason' => $change_reason, 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/classes/parser/newpobox.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_NewPobox extends Postcodify_ZipReader 23 | { 24 | // 사서함 갯수를 세는 변수. 25 | 26 | protected $_count = 0; 27 | 28 | // 생성자에서 문자셋을 지정한다. 29 | 30 | public function __construct() 31 | { 32 | $this->_charset = 'UTF-8'; 33 | } 34 | 35 | // 한 줄을 읽어 반환한다. 36 | 37 | public function read_line($delimiter = '|') 38 | { 39 | // 데이터를 읽는다. 40 | 41 | $line = parent::read_line($delimiter); 42 | if ($line === false || count($line) < 10) return false; 43 | if (!ctype_digit($line[0])) return true; 44 | 45 | // 상세 데이터를 읽어들인다. 46 | 47 | $sido = trim($line[1]); 48 | $sigungu = trim($line[2]); if ($sigungu === '') $sigungu = null; 49 | $eupmyeon = trim($line[3]); 50 | $pobox_name = trim($line[4]); 51 | 52 | $range_start_major = trim($line[5]); if (!$range_start_major) $range_start_major = null; 53 | $range_start_minor = trim($line[6]); if (!$range_start_minor) $range_start_minor = null; 54 | $range_end_major = trim($line[7]); if (!$range_end_major) $range_end_major = null; 55 | $range_end_minor = trim($line[8]); if (!$range_end_minor) $range_end_minor = null; 56 | 57 | // 특별시/광역시 아래의 자치구와 행정시 아래의 일반구를 구분한다. 58 | 59 | if ($sigungu && ($pos = strpos($sigungu, ' ')) !== false) 60 | { 61 | $ilbangu = substr($sigungu, $pos + 1); 62 | $sigungu = substr($sigungu, 0, $pos); 63 | } 64 | else 65 | { 66 | $ilbangu = null; 67 | } 68 | 69 | // 우편번호를 정리한다. 70 | 71 | $postcode6 = trim($line[9]); 72 | if (strlen($postcode6) !== 6) $postcode6 = null; 73 | $postcode5 = trim($line[0]); 74 | if (strlen($postcode5) !== 5) $postcode5 = null; 75 | 76 | // 데이터를 정리하여 반환한다. 77 | 78 | return (object)array( 79 | 'postcode6' => $postcode6, 80 | 'postcode5' => $postcode5, 81 | 'sido' => $sido, 82 | 'sigungu' => $sigungu, 83 | 'ilbangu' => $ilbangu, 84 | 'eupmyeon' => $eupmyeon, 85 | 'pobox_name' => $pobox_name, 86 | 'range_start_major' => $range_start_major, 87 | 'range_start_minor' => $range_start_minor, 88 | 'range_end_major' => $range_end_major, 89 | 'range_end_minor' => $range_end_minor, 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/classes/parser/ranges_jibeon.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_Ranges_Jibeon extends Postcodify_ZipReader 23 | { 24 | // 생성자에서 문자셋을 지정한다. 25 | 26 | public function __construct() 27 | { 28 | $this->_charset = 'UTF-8'; 29 | } 30 | 31 | // 한 줄을 읽어 반환한다. 32 | 33 | public function read_line($delimiter = '|') 34 | { 35 | // 데이터를 읽는다. 36 | 37 | $line = parent::read_line($delimiter); 38 | if ($line === false || count($line) < 14) return false; 39 | if (!ctype_digit($line[0])) return true; 40 | 41 | // 상세 데이터를 읽어들인다. 42 | 43 | $sido_ko = trim($line[1]); 44 | $sido_en = str_replace('-si', '', trim($line[2])); 45 | $sigungu_ko = trim($line[3]); if (!$sigungu_ko) $sigungu_ko = null; 46 | $sigungu_en = trim($line[4]); if (!$sigungu_en) $sigungu_en = null; 47 | $eupmyeon_ko = trim($line[5]); 48 | $eupmyeon_en = trim($line[6]); 49 | $dongri_ko = trim($line[7]); if (!$dongri_ko) $dongri_ko = null; 50 | $dongri_en = null; 51 | $admin_dongri = trim($line[8]); if (!$admin_dongri) $admin_dongri = null; 52 | $is_mountain = intval(trim($line[9])) ? 1 : 0; 53 | $range_start_major = trim($line[10]); if (!$range_start_major) $range_start_major = null; 54 | $range_start_minor = trim($line[11]); if (!$range_start_minor) $range_start_minor = null; 55 | $range_end_major = trim($line[12]); if (!$range_end_major) $range_end_major = null; 56 | $range_end_minor = trim($line[13]); if (!$range_end_minor) $range_end_minor = null; 57 | 58 | // 특별시/광역시 아래의 자치구와 행정시 아래의 일반구를 구분한다. 59 | 60 | if ($sigungu_ko && ($pos = strpos($sigungu_ko, ' ')) !== false) 61 | { 62 | $ilbangu_ko = substr($sigungu_ko, $pos + 1); 63 | $sigungu_ko = substr($sigungu_ko, 0, $pos); 64 | if (($engpos = strpos($sigungu_en, ',')) !== false) 65 | { 66 | $ilbangu_en = trim(substr($sigungu_en, 0, $engpos)); 67 | $sigungu_en = trim(substr($sigungu_en, $engpos + 1)); 68 | } 69 | else 70 | { 71 | $ilbangu_en = null; 72 | } 73 | } 74 | else 75 | { 76 | $ilbangu_ko = null; 77 | $ilbangu_en = null; 78 | } 79 | 80 | // 읍면과 동을 구분한다. 81 | 82 | if (preg_match('/[동가]$/u', $eupmyeon_ko) && $dongri_ko === null) 83 | { 84 | $dongri_ko = $eupmyeon_ko; 85 | $dongri_en = $eupmyeon_en; 86 | $eupmyeon_ko = null; 87 | $eupmyeon_en = null; 88 | } 89 | 90 | // 데이터를 정리하여 반환한다. 91 | 92 | return (object)array( 93 | 'sido_ko' => $sido_ko, 94 | 'sido_en' => $sido_en, 95 | 'sigungu_ko' => $sigungu_ko, 96 | 'sigungu_en' => $sigungu_en, 97 | 'ilbangu_ko' => $ilbangu_ko, 98 | 'ilbangu_en' => $ilbangu_en, 99 | 'eupmyeon_ko' => $eupmyeon_ko, 100 | 'eupmyeon_en' => $eupmyeon_en, 101 | 'dongri_ko' => $dongri_ko, 102 | 'dongri_en' => $dongri_en, 103 | 'range_start_major' => $range_start_major, 104 | 'range_start_minor' => $range_start_minor, 105 | 'range_end_major' => $range_end_major, 106 | 'range_end_minor' => $range_end_minor, 107 | 'is_mountain' => $is_mountain, 108 | 'admin_dongri' => $admin_dongri, 109 | 'postcode5' => trim($line[0]), 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/classes/parser/ranges_oldcode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_Ranges_OldCode extends Postcodify_ZipReader 23 | { 24 | // 한 줄을 읽어 반환한다. 25 | 26 | public function read_line($delimiter = '|') 27 | { 28 | // 데이터를 읽는다. 29 | 30 | $line = fgetcsv($this->_fp); 31 | if ($line === false || count($line) < 15) return false; 32 | if (!ctype_digit($line[0])) return true; 33 | 34 | // 상세 데이터를 읽어들인다. 35 | 36 | $sido = trim($line[2]); 37 | $sigungu = trim($line[3]); if ($sigungu === '') $sigungu = null; 38 | $eupmyeon = trim($line[4]); if ($eupmyeon === '') $eupmyeon = null; 39 | $dongri = trim($line[5]); if ($dongri === '') $dongri = null; 40 | $island_name = trim($line[6]); if ($island_name === '') $island_name = null; 41 | $is_mountain = trim($line[7]) === '산' ? 1 : 0; 42 | $range_start_major = trim($line[8]); if (!$range_start_major) $range_start_major = null; 43 | $range_start_minor = trim($line[9]); if (!$range_start_minor) $range_start_minor = null; 44 | $range_end_major = trim($line[10]); if (!$range_end_major) $range_end_major = null; 45 | $range_end_minor = trim($line[11]); if (!$range_end_minor) $range_end_minor = null; 46 | $building_name = trim($line[12]); if ($building_name === '') $building_name = null; 47 | $building_num_start = trim($line[13]); if (!$building_num_start) $building_num_start = null; 48 | $building_num_end = trim($line[14]); if (!$building_num_end) $building_num_end = null; 49 | 50 | // 특별시/광역시 아래의 자치구와 행정시 아래의 일반구를 구분한다. 51 | 52 | if ($sigungu && ($pos = strpos($sigungu, ' ')) !== false) 53 | { 54 | $ilbangu = substr($sigungu, $pos + 1); 55 | $sigungu = substr($sigungu, 0, $pos); 56 | } 57 | else 58 | { 59 | $ilbangu = null; 60 | } 61 | 62 | // 읍면과 동을 구분한다. 63 | 64 | if ($eupmyeon && preg_match('/[동로가]$/u', $eupmyeon) && $dongri === null) 65 | { 66 | $dongri = $eupmyeon; 67 | $eupmyeon = null; 68 | } 69 | 70 | // 데이터를 정리하여 반환한다. 71 | 72 | return (object)array( 73 | 'postcode6' => trim($line[0]), 74 | 'sido' => $sido, 75 | 'sigungu' => $sigungu, 76 | 'ilbangu' => $ilbangu, 77 | 'eupmyeon' => $eupmyeon, 78 | 'dongri' => $dongri, 79 | 'range_start_major' => $range_start_major, 80 | 'range_start_minor' => $range_start_minor, 81 | 'range_end_major' => $range_end_major, 82 | 'range_end_minor' => $range_end_minor, 83 | 'is_mountain' => $is_mountain, 84 | 'island_name' => $island_name, 85 | 'building_name' => $building_name, 86 | 'building_num_start' => $building_num_start, 87 | 'building_num_end' => $building_num_end, 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/classes/parser/ranges_oldcode_special.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_Ranges_OldCode_Special extends Postcodify_ZipReader 23 | { 24 | // 한 줄을 읽어 반환한다. 25 | 26 | public function read_line($delimiter = '|') 27 | { 28 | // 데이터를 읽는다. 29 | 30 | $line = fgetcsv($this->_fp); 31 | if ($line === false || count($line) < 2) return false; 32 | if (!ctype_digit($line[0])) return true; 33 | 34 | // 데이터를 정리하여 반환한다. 35 | 36 | if (count($line) == 2) 37 | { 38 | return (object)array( 39 | 'postcode6' => trim($line[0]), 40 | 'building_id' => trim($line[1]), 41 | ); 42 | } 43 | else 44 | { 45 | return (object)array( 46 | 'postcode6' => trim($line[0]), 47 | 'building_id' => null, 48 | 'sido' => trim($line[2]), 49 | 'sigungu' => trim($line[3]), 50 | 'ilbangu' => trim($line[4]), 51 | 'eupmyeon' => trim($line[5]), 52 | 'pobox_name' => trim($line[6]), 53 | 'pobox_range' => trim($line[7] . ($line[8] ? (' ~ ' . $line[8]) : '')), 54 | ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/classes/parser/ranges_roads.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_Ranges_Roads extends Postcodify_ZipReader 23 | { 24 | // 생성자에서 문자셋을 지정한다. 25 | 26 | public function __construct() 27 | { 28 | $this->_charset = 'UTF-8'; 29 | } 30 | 31 | // 한 줄을 읽어 반환한다. 32 | 33 | public function read_line($delimiter = '|') 34 | { 35 | // 데이터를 읽는다. 36 | 37 | $line = parent::read_line($delimiter); 38 | if ($line === false || count($line) < 15) return false; 39 | if (!ctype_digit($line[0])) return true; 40 | 41 | // 상세 데이터를 읽어들인다. 42 | 43 | $sido_ko = trim($line[1]); 44 | $sido_en = str_replace('-si', '', trim($line[2])); 45 | $sigungu_ko = trim($line[3]); if (!$sigungu_ko) $sigungu_ko = null; 46 | $sigungu_en = trim($line[4]); if (!$sigungu_en) $sigungu_en = null; 47 | $eupmyeon_ko = trim($line[5]); 48 | $eupmyeon_en = trim($line[6]); 49 | $road_name_ko = trim($line[7]); 50 | $road_name_en = trim($line[8]); 51 | $is_basement = intval(trim($line[9])) ? 1 : 0; 52 | $range_start_major = trim($line[10]); if (!$range_start_major) $range_start_major = null; 53 | $range_start_minor = trim($line[11]); if (!$range_start_minor) $range_start_minor = null; 54 | $range_end_major = trim($line[12]); if (!$range_end_major) $range_end_major = null; 55 | $range_end_minor = trim($line[13]); if (!$range_end_minor) $range_end_minor = null; 56 | $range_type = intval(trim($line[14])); 57 | 58 | // 특별시/광역시 아래의 자치구와 행정시 아래의 일반구를 구분한다. 59 | 60 | if ($sigungu_ko && ($pos = strpos($sigungu_ko, ' ')) !== false) 61 | { 62 | $ilbangu_ko = substr($sigungu_ko, $pos + 1); 63 | $sigungu_ko = substr($sigungu_ko, 0, $pos); 64 | if (($engpos = strpos($sigungu_en, ',')) !== false) 65 | { 66 | $ilbangu_en = trim(substr($sigungu_en, 0, $engpos)); 67 | $sigungu_en = trim(substr($sigungu_en, $engpos + 1)); 68 | } 69 | else 70 | { 71 | $ilbangu_en = null; 72 | } 73 | } 74 | else 75 | { 76 | $ilbangu_ko = null; 77 | $ilbangu_en = null; 78 | } 79 | 80 | // 데이터를 정리하여 반환한다. 81 | 82 | return (object)array( 83 | 'sido_ko' => $sido_ko, 84 | 'sido_en' => $sido_en, 85 | 'sigungu_ko' => $sigungu_ko, 86 | 'sigungu_en' => $sigungu_en, 87 | 'ilbangu_ko' => $ilbangu_ko, 88 | 'ilbangu_en' => $ilbangu_en, 89 | 'eupmyeon_ko' => $eupmyeon_ko, 90 | 'eupmyeon_en' => $eupmyeon_en, 91 | 'road_name_ko' => $road_name_ko, 92 | 'road_name_en' => $road_name_en, 93 | 'range_start_major' => $range_start_major, 94 | 'range_start_minor' => $range_start_minor, 95 | 'range_end_major' => $range_end_major, 96 | 'range_end_minor' => $range_end_minor, 97 | 'range_type' => $range_type, 98 | 'is_basement' => $is_basement, 99 | 'postcode5' => trim($line[0]), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/classes/parser/road_list.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Parser_Road_List extends Postcodify_ZipReader 23 | { 24 | // 생성자에서 문자셋을 지정한다. 25 | 26 | public function __construct() 27 | { 28 | $this->_charset = 'CP949'; 29 | } 30 | 31 | // 한 줄을 읽어 반환한다. 32 | 33 | public function read_line($delimiter = '|') 34 | { 35 | // 데이터를 읽는다. 36 | 37 | $line = parent::read_line($delimiter); 38 | if ($line === false || count($line) < 20) return false; 39 | 40 | // 주소의 각 구성요소를 파악한다. 41 | 42 | $road_id = trim($line[0]) . trim($line[1]); 43 | $road_section = str_pad(trim($line[4]), 2, '0', STR_PAD_LEFT); 44 | $road_name = trim($line[2]); 45 | $road_name_en = trim($line[3]); 46 | $sido = trim($line[5]); 47 | $sido_en = str_replace('-si', '', trim($line[15])); 48 | $sigungu = trim($line[6]); 49 | $sigungu_en = trim($line[16]); 50 | $eupmyeon = trim($line[9]); 51 | $eupmyeon_en = trim($line[17]); 52 | $parent_road_id = $line[10] === '' ? null : (trim($line[0]) . trim($line[10])); 53 | $previous_road_id = $line[14] === '' ? null : trim($line[14]); 54 | $change_reason = $line[13] === '' ? null : intval($line[13]); 55 | $updated = trim($line[18]); 56 | 57 | // 동 정보는 여기서 기억할 필요가 없다. 58 | 59 | if ($eupmyeon === '' || !preg_match('/[읍면]$/u', $eupmyeon)) 60 | { 61 | $eupmyeon = null; 62 | $eupmyeon_en = null; 63 | } 64 | 65 | // 특별시/광역시 아래의 자치구와 행정시 아래의 일반구를 구분한다. 66 | 67 | if (($pos = strpos($sigungu, ' ')) !== false) 68 | { 69 | $ilbangu = substr($sigungu, $pos + 1); 70 | $sigungu = substr($sigungu, 0, $pos); 71 | if (($engpos = strpos($sigungu_en, ',')) !== false) 72 | { 73 | $ilbangu_en = trim(substr($sigungu_en, 0, $engpos)); 74 | $sigungu_en = trim(substr($sigungu_en, $engpos + 1)); 75 | } 76 | else 77 | { 78 | $ilbangu_en = null; 79 | } 80 | } 81 | else 82 | { 83 | $ilbangu = null; 84 | $ilbangu_en = null; 85 | } 86 | 87 | // 시군구가 없는 경우(세종시)를 처리한다. 88 | 89 | if ($sigungu === '') 90 | { 91 | $sigungu = null; 92 | $sigungu_en = null; 93 | } 94 | 95 | // 데이터를 정리하여 반환한다. 96 | 97 | return (object)array( 98 | 'road_id' => $road_id, 99 | 'road_section' => $road_section, 100 | 'road_name_ko' => $road_name, 101 | 'road_name_en' => $road_name_en, 102 | 'sido_ko' => $sido, 103 | 'sido_en' => $sido_en, 104 | 'sigungu_ko' => $sigungu, 105 | 'sigungu_en' => $sigungu_en, 106 | 'ilbangu_ko' => $ilbangu, 107 | 'ilbangu_en' => $ilbangu_en, 108 | 'eupmyeon_ko' => $eupmyeon, 109 | 'eupmyeon_en' => $eupmyeon_en, 110 | 'parent_road_id' => $parent_road_id, 111 | 'previous_road_id' => $previous_road_id, 112 | 'change_reason' => $change_reason, 113 | 'updated' => $updated, 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/classes/server.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server 23 | { 24 | // DB 설정. 25 | 26 | public $db_driver = 'mysql'; 27 | public $db_host = 'localhost'; 28 | public $db_port = 3306; 29 | public $db_user = ''; 30 | public $db_pass = ''; 31 | public $db_dbname = ''; 32 | protected $_dbh; 33 | 34 | // Memcached 설정. 35 | 36 | public $cache_driver = ''; 37 | public $cache_host = 'localhost'; 38 | public $cache_port = 11211; 39 | public $cache_ttl = 86400; 40 | protected $_ch; 41 | 42 | // 검색을 수행하는 메소드. Postcodify_Server_Result 객체를 반환한다. 43 | // 인코딩의 경우 EUC-KR을 사용하려면 CP949라고 입력해 주어야 한다. 44 | // 새주소 중 EUC-KR에서 지원되지 않는 문자가 포함된 것도 있기 때문이다. 45 | 46 | public function search($keywords, $encoding = 'UTF-8', $version = null) 47 | { 48 | // 버전을 확인한다. 49 | 50 | if ($version === null) $version = POSTCODIFY_VERSION; 51 | 52 | // 검색 시작 시각을 기록한다. 53 | 54 | $start_time = microtime(true); 55 | 56 | // 검색 키워드의 유효성을 확인한다. 57 | 58 | if (($keywords = trim($keywords)) === '') 59 | { 60 | return new Postcodify_Server_Result('Keyword Not Supplied'); 61 | } 62 | if (!mb_check_encoding($keywords, $encoding)) 63 | { 64 | return new Postcodify_Server_Result('Keyword is Not Valid ' . $encoding); 65 | } 66 | if ($encoding !== 'UTF-8') 67 | { 68 | $keywords = mb_convert_encoding($keywords, 'UTF-8', $encoding); 69 | } 70 | if (($len = mb_strlen($keywords, 'UTF-8')) < 3 || $len > 80) 71 | { 72 | return new Postcodify_Server_Result('Keyword is Too Long or Too Short'); 73 | } 74 | 75 | // 검색 키워드를 분석하여 쿼리 객체를 생성한다. 76 | 77 | $q = Postcodify_Server_Query::parse_keywords($keywords); 78 | 79 | // 캐시에 데이터가 있는지 확인한다. 80 | 81 | if ($this->cache_driver && !$q->numbers[0]) 82 | { 83 | if ($this->_ch === null) 84 | { 85 | $this->_ch = new Postcodify_Server_Cache($this->cache_driver, $this->cache_host, $this->cache_port, $this->cache_ttl); 86 | } 87 | $cache_key = sha1(strval($q)); 88 | $addresses = null; 89 | } 90 | else 91 | { 92 | $cache_key = null; 93 | $addresses = null; 94 | } 95 | 96 | // 캐시에 데이터가 있는지 확인한다. 97 | 98 | if ($cache_key !== null) 99 | { 100 | $data_source = 'cache'; 101 | list($addresses, $search_type, $search_error) = $this->_ch->get($cache_key); 102 | } 103 | 104 | // 캐시에서 찾지 못한 경우 DB에서 검색 쿼리를 실행한다. 105 | 106 | if ($addresses === null) 107 | { 108 | $data_source = 'db'; 109 | list($addresses, $search_type, $search_error) = $this->get_addresses($q); 110 | } 111 | 112 | // 오류가 발생한 경우 처리를 중단한다. 113 | 114 | if ($search_error !== null) 115 | { 116 | return new Postcodify_Server_Result('Database Error'); 117 | } 118 | 119 | // 검색 결과를 캐시에 저장한다. 120 | 121 | if ($cache_key !== null && $data_source === 'db') 122 | { 123 | $this->_ch->set($cache_key, $addresses, $search_type); 124 | } 125 | 126 | // 검색 결과 오브젝트를 생성한다. 127 | 128 | $result = new Postcodify_Server_Result; 129 | 130 | // 검색 언어, 정렬 방식 등을 기록한다. 131 | 132 | $result->lang = $q->lang; 133 | $result->sort = $q->sort; 134 | $result->nums = $q->numbers[0] . ($q->numbers[1] ? ('-' . $q->numbers[1]) : ''); 135 | $result->type = $search_type; 136 | $result->cache = $data_source === 'cache' ? 'HIT' : 'MISS'; 137 | 138 | // 각 레코드를 추가한다. 139 | 140 | foreach ($addresses as $row) 141 | { 142 | // 한글 도로명 및 지번주소를 정리한다. 143 | 144 | $ko_common = trim($row->sido_ko . ' ' . ($row->sigungu_ko ? ($row->sigungu_ko . ' ') : '') . 145 | ($row->ilbangu_ko ? ($row->ilbangu_ko . ' ') : '') . ($row->eupmyeon_ko ? ($row->eupmyeon_ko . ' ') : '')); 146 | $ko_doro = trim($row->road_name_ko . ' ' . ($row->is_basement ? '지하 ' : '') . 147 | ($row->num_major ? $row->num_major : '') . ($row->num_minor ? ('-' . $row->num_minor) : '')); 148 | $ko_jibeon = trim($row->dongri_ko . ' ' . ($row->is_mountain ? '산' : '') . 149 | ($row->jibeon_major ? $row->jibeon_major : '') . ($row->jibeon_minor ? ('-' . $row->jibeon_minor) : '')); 150 | 151 | // 영문 도로명 및 지번주소를 정리한다. 152 | 153 | $en_common = trim(($row->eupmyeon_en ? ($row->eupmyeon_en . ', ') : '') . 154 | ($row->ilbangu_en ? ($row->ilbangu_en . ', ') : '') . ($row->sigungu_en ? ($row->sigungu_en . ', ') : '') . 155 | $row->sido_en); 156 | $en_doro = trim(($row->is_basement ? 'Jiha ' : '') . 157 | ($row->num_major ? $row->num_major : '') . ($row->num_minor ? ('-' . $row->num_minor) : '') . 158 | ', ' . $row->road_name_en); 159 | $en_jibeon = trim(($row->is_mountain ? 'San ' : '') . 160 | ($row->jibeon_major ? $row->jibeon_major : '') . ($row->jibeon_minor ? ('-' . $row->jibeon_minor) : '') . 161 | ', ' . $row->dongri_en); 162 | 163 | // 추가정보를 정리한다. 164 | 165 | if ($result->sort === 'POBOX') 166 | { 167 | $ko_doro = $ko_jibeon = $row->dongri_ko . ' ' . $row->other_addresses; 168 | $en_doro = $en_jibeon = $row->dongri_en . ' ' . $row->other_addresses; 169 | $extra_long = $extra_short = $other_addresses = ''; 170 | } 171 | else 172 | { 173 | $extra_long = trim($ko_jibeon . (strval($row->building_name) !== '' ? (', ' . $row->building_name) : ''), ', '); 174 | $extra_short = trim($row->dongri_ko . (strval($row->building_name) !== '' ? (', ' . $row->building_name) : ''), ', '); 175 | $other_addresses = strval($row->other_addresses); 176 | } 177 | 178 | // 요청받은 버전에 따라 다른 형태로 작성한다. 179 | 180 | if (version_compare($version, '3', '>=')) 181 | { 182 | $record = new Postcodify_Server_Record_v3; 183 | $record->postcode5 = strval($row->postcode5); 184 | $record->postcode6 = strval($row->postcode6); 185 | $record->ko_common = $ko_common; 186 | $record->ko_doro = $ko_doro; 187 | $record->ko_jibeon = $ko_jibeon; 188 | $record->en_common = $en_common; 189 | $record->en_doro = $en_doro; 190 | $record->en_jibeon = $en_jibeon; 191 | $record->building_id = strval($row->building_id); 192 | $record->building_name = strval($row->building_name); 193 | $record->building_nums = isset($row->building_nums) ? strval($row->building_nums) : ''; 194 | $record->other_addresses = $other_addresses; 195 | $record->road_id = ($result->sort === 'POBOX') ? '' : substr($row->road_id, 0, 12); 196 | $record->internal_id = strval($row->id); 197 | $record->address_id = substr($row->dongri_id ?: $row->building_id, 0, 10) . ($row->is_mountain ? '2' : '1') . 198 | str_pad($row->jibeon_major ?: '', 4, '0', STR_PAD_LEFT) . str_pad($row->jibeon_minor ?: '', 4, '0', STR_PAD_LEFT); 199 | } 200 | elseif (version_compare($version, '1.8', '>=')) 201 | { 202 | $record = new Postcodify_Server_Record_v18; 203 | $record->dbid = strval($row->building_id); 204 | $record->code6 = substr($row->postcode6, 0, 3) . '-' . substr($row->postcode6, 3, 3); 205 | $record->code5 = strval($row->postcode5); 206 | $record->address = array('base' => $ko_common, 'new' => $ko_doro, 'old' => $ko_jibeon, 'building' => strval($row->building_name)); 207 | $record->english = array('base' => $en_common, 'new' => $en_doro, 'old' => $en_jibeon, 'building' => ''); 208 | $record->other = array( 209 | 'long' => strval($extra_long), 210 | 'short' => strval($extra_short), 211 | 'others' => strval($other_addresses), 212 | 'addrid' => strval($row->id), 213 | 'roadid' => ($result->sort === 'POBOX') ? '' : $row->road_id, 214 | 'bldnum' => isset($row->building_nums) ? strval($row->building_nums) : '', 215 | ); 216 | } 217 | else 218 | { 219 | $record = new Postcodify_Server_Record_v17; 220 | $record->dbid = strval($row->building_id); 221 | $record->code6 = substr($row->postcode6, 0, 3) . '-' . substr($row->postcode6, 3, 3); 222 | $record->code5 = strval($row->postcode5); 223 | $record->address = trim($ko_common . ' ' . $ko_doro); 224 | $record->canonical = strval($ko_jibeon); 225 | $record->extra_info_long = strval($extra_long); 226 | $record->extra_info_short = strval($extra_short); 227 | $record->english_address = trim($en_doro . ', ' . $en_common); 228 | $record->jibeon_address = trim($ko_common . ' ' . $ko_jibeon); 229 | $record->other = strval($other_addresses); 230 | } 231 | 232 | // 반환할 인코딩이 UTF-8이 아닌 경우 여기서 변환한다. 233 | 234 | if ($encoding !== 'UTF-8') 235 | { 236 | $properties = get_object_vars($record); 237 | foreach ($properties as $key => $value) 238 | { 239 | $record->$key = mb_convert_encoding($value, $encoding, 'UTF-8'); 240 | } 241 | } 242 | 243 | // 레코드를 추가하고 레코드 카운터를 조정한다. 244 | 245 | $result->results[] = $record; 246 | $result->count++; 247 | } 248 | 249 | // 정확한 주소만 반환하는 옵션을 처리한다. 250 | 251 | if (isset($_GET['exact']) && $_GET['exact'] === 'Y' && in_array($result->type, array('JUSO+NUMS', 'JIBEON+NUMS'))) 252 | { 253 | $matches = array(); 254 | foreach ($result->results as $record) 255 | { 256 | if ($record instanceof Postcodify_Server_Record_v3 && (isset($q->road) || isset($q->dongri)) && 257 | (($result->type === 'JUSO+NUMS' && preg_match('/' . preg_quote($q->road . ' ' . $result->nums, '/') . '$/', $record->ko_doro)) || 258 | ($result->type === 'JIBEON+NUMS' && preg_match('/' . preg_quote($q->dongri . ' ' . $result->nums, '/') . '$/', $record->ko_jibeon)))) 259 | { 260 | $matches[] = $record; 261 | } 262 | } 263 | if (count($matches) == 1) 264 | { 265 | $result->results = $matches; 266 | $result->count = 1; 267 | } 268 | } 269 | 270 | // 검색 소요 시간을 기록한다. 271 | 272 | $result->time = number_format(microtime(true) - $start_time, 3); 273 | 274 | // 결과를 반환한다. 275 | 276 | return $result; 277 | } 278 | 279 | // 주어진 쿼리를 DB에서 실행하는 메소드. 280 | 281 | protected function get_addresses($q) 282 | { 283 | // 반환할 변수들을 초기화한다. 284 | 285 | $addresses = array(); 286 | $search_type = 'NONE'; 287 | $search_error = null; 288 | 289 | try 290 | { 291 | // DB에 연결한다. 292 | 293 | if ($this->_dbh === null) 294 | { 295 | $this->_dbh = new Postcodify_Server_Database($this->db_driver, $this->db_host, $this->db_port, 296 | $this->db_user, $this->db_pass, $this->db_dbname); 297 | } 298 | 299 | // 쿼리 작성을 준비한다. 300 | 301 | $query = 'SELECT DISTINCT pa.*, pr.* FROM postcodify_addresses pa'; 302 | $joins = array('JOIN postcodify_roads pr ON pa.road_id = pr.road_id'); 303 | $conds = array('pa.building_id IS NOT NULL'); 304 | $args = array(); 305 | 306 | // 특정 지역으로 검색을 제한하는 경우를 처리한다. 307 | 308 | if ($q->use_area) 309 | { 310 | if ($q->sido) 311 | { 312 | $conds[] = 'pr.sido_ko = ?'; 313 | $args[] = $q->sido; 314 | } 315 | if ($q->sigungu) 316 | { 317 | $conds[] = 'pr.sigungu_ko = ?'; 318 | $args[] = $q->sigungu; 319 | } 320 | if ($q->ilbangu) 321 | { 322 | $conds[] = 'pr.ilbangu_ko = ?'; 323 | $args[] = $q->ilbangu; 324 | } 325 | if ($q->eupmyeon) 326 | { 327 | $conds[] = 'pr.eupmyeon_ko = ?'; 328 | $args[] = $q->eupmyeon; 329 | } 330 | } 331 | 332 | // 도로명주소로 검색하는 경우... 333 | 334 | if ($q->road !== null && !count($q->buildings)) 335 | { 336 | // 도로명 쿼리를 작성한다. 337 | 338 | $search_type = 'JUSO'; 339 | $joins[] = 'JOIN postcodify_keywords pk ON pa.id = pk.address_id'; 340 | if ($q->lang === 'KO') 341 | { 342 | $conds[] = 'pk.keyword_crc32 = ?'; 343 | $args[] = self::crc32_x64($q->road); 344 | } 345 | else 346 | { 347 | $joins[] = 'JOIN postcodify_english pe ON pe.ko_crc32 = pk.keyword_crc32'; 348 | $conds[] = 'pe.en_crc32 = ?'; 349 | $args[] = self::crc32_x64($q->road); 350 | } 351 | 352 | // 건물번호 쿼리를 작성한다. 353 | 354 | if ($q->numbers[0]) 355 | { 356 | $search_type .= '+NUMS'; 357 | $joins[] = 'JOIN postcodify_numbers pn ON pa.id = pn.address_id'; 358 | $conds[] = 'pn.num_major = ?'; 359 | $args[] = $q->numbers[0]; 360 | if ($q->numbers[1]) 361 | { 362 | $conds[] = 'pn.num_minor = ?'; 363 | $args[] = $q->numbers[1]; 364 | } 365 | } 366 | 367 | // 검색을 수행한다. 368 | 369 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 370 | } 371 | 372 | // 지번주소로 검색하는 경우... 373 | 374 | elseif ($q->dongri !== null && !count($q->buildings)) 375 | { 376 | // 동·리 쿼리를 작성한다. 377 | 378 | $search_type = 'JIBEON'; 379 | $joins[] = 'JOIN postcodify_keywords pk ON pa.id = pk.address_id'; 380 | if ($q->lang === 'KO') 381 | { 382 | $conds[] = 'pk.keyword_crc32 = ?'; 383 | $args[] = self::crc32_x64($q->dongri); 384 | } 385 | else 386 | { 387 | $joins[] = 'JOIN postcodify_english pe ON pe.ko_crc32 = pk.keyword_crc32'; 388 | $conds[] = 'pe.en_crc32 = ?'; 389 | $args[] = self::crc32_x64($q->dongri); 390 | } 391 | 392 | // 번지수 쿼리를 작성한다. 393 | 394 | if ($q->numbers[0]) 395 | { 396 | $search_type .= '+NUMS'; 397 | $joins[] = 'JOIN postcodify_numbers pn ON pa.id = pn.address_id'; 398 | $conds[] = 'pn.num_major = ?'; 399 | $args[] = $q->numbers[0]; 400 | if ($q->numbers[1]) 401 | { 402 | $conds[] = 'pn.num_minor = ?'; 403 | $args[] = $q->numbers[1]; 404 | } 405 | } 406 | 407 | // 일단 검색해 본다. 408 | 409 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 410 | 411 | // 검색 결과가 없다면 건물명을 동리로 잘못 해석했을 수도 있으므로 건물명 검색을 다시 시도해 본다. 412 | 413 | if ($q->numbers[0] === null && $q->numbers[1] === null && !count($addresses) && $q->lang === 'KO') 414 | { 415 | array_pop($joins); 416 | array_pop($conds); 417 | array_pop($args); 418 | 419 | $joins[] = 'JOIN postcodify_buildings pb ON pa.id = pb.address_id'; 420 | $conds[] = 'pb.keyword LIKE ?'; 421 | $args[] = '%' . $q->dongri . '%'; 422 | 423 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 424 | if (count($addresses)) 425 | { 426 | $search_type = 'BUILDING'; 427 | $q->sort = 'JUSO'; 428 | } 429 | } 430 | } 431 | 432 | // 건물명만으로 검색하는 경우... 433 | 434 | elseif (count($q->buildings) && $q->road === null && $q->dongri === null) 435 | { 436 | $search_type = 'BUILDING'; 437 | $joins[] = 'JOIN postcodify_buildings pb ON pa.id = pb.address_id'; 438 | foreach ($q->buildings as $building_name) 439 | { 440 | $conds[] = 'pb.keyword LIKE ?'; 441 | $args[] = '%' . $building_name . '%'; 442 | } 443 | 444 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 445 | } 446 | 447 | // 도로명 + 건물명으로 검색하는 경우... 448 | 449 | elseif (count($q->buildings) && $q->road !== null) 450 | { 451 | $search_type = 'BUILDING+JUSO'; 452 | $joins[] = 'JOIN postcodify_keywords pk ON pa.id = pk.address_id'; 453 | $conds[] = 'pk.keyword_crc32 = ?'; 454 | $args[] = self::crc32_x64($q->road); 455 | 456 | $joins[] = 'JOIN postcodify_buildings pb ON pa.id = pb.address_id'; 457 | foreach ($q->buildings as $building_name) 458 | { 459 | $conds[] = 'pb.keyword LIKE ?'; 460 | $args[] = '%' . $building_name . '%'; 461 | } 462 | 463 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 464 | } 465 | 466 | // 동리 + 건물명으로 검색하는 경우... 467 | 468 | elseif (count($q->buildings) && $q->dongri !== null) 469 | { 470 | $search_type = 'BUILDING+DONG'; 471 | $joins[] = 'JOIN postcodify_keywords pk ON pa.id = pk.address_id'; 472 | $conds[] = 'pk.keyword_crc32 = ?'; 473 | $args[] = self::crc32_x64($q->dongri); 474 | 475 | $joins[] = 'JOIN postcodify_buildings pb ON pa.id = pb.address_id'; 476 | foreach ($q->buildings as $building_name) 477 | { 478 | $conds[] = 'pb.keyword LIKE ?'; 479 | $args[] = '%' . $building_name . '%'; 480 | } 481 | 482 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 483 | } 484 | 485 | // 사서함으로 검색하는 경우... 486 | 487 | elseif ($q->pobox !== null) 488 | { 489 | $search_type = 'POBOX'; 490 | $joins[] = 'JOIN postcodify_pobox pp ON pa.id = pp.address_id'; 491 | $conds[] = 'pp.keyword LIKE ?'; 492 | $args[] = '%' . $q->pobox . '%'; 493 | 494 | if ($q->numbers[0]) 495 | { 496 | $conds[] = 'pp.range_start_major <= ? AND pp.range_end_major >= ?'; 497 | $args[] = $q->numbers[0]; 498 | $args[] = $q->numbers[0]; 499 | if ($q->numbers[1]) 500 | { 501 | $conds[] = '(pp.range_start_minor IS NULL OR (pp.range_start_minor <= ? AND pp.range_end_minor >= ?))'; 502 | $args[] = $q->numbers[1]; 503 | $args[] = $q->numbers[1]; 504 | } 505 | } 506 | 507 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 508 | } 509 | 510 | // 읍면으로 검색하는 경우... 511 | 512 | elseif ($q->use_area && $q->eupmyeon) 513 | { 514 | $search_type = 'EUPMYEON'; 515 | $conds[] = 'pa.postcode5 IS NOT NULL'; 516 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 517 | 518 | // 검색 결과가 없다면 건물명을 읍면으로 잘못 해석했을 수도 있으므로 건물명 검색을 다시 시도해 본다. 519 | 520 | if (!count($addresses) && $q->lang === 'KO') 521 | { 522 | array_pop($conds); 523 | array_pop($conds); 524 | array_pop($args); 525 | 526 | $joins[] = 'JOIN postcodify_buildings pb ON pa.id = pb.address_id'; 527 | $conds[] = 'pb.keyword LIKE ?'; 528 | $args[] = '%' . $q->eupmyeon . '%'; 529 | 530 | $addresses = $this->_dbh->query($query, $joins, $conds, $args, $q->lang, $q->sort); 531 | if (count($addresses)) 532 | { 533 | $search_type = 'BUILDING'; 534 | $q->sort = 'JUSO'; 535 | } 536 | } 537 | } 538 | 539 | // 그 밖의 경우 검색 결과가 없는 것으로 한다. 540 | 541 | else 542 | { 543 | $addresses = array(); 544 | } 545 | } 546 | catch (Exception $e) 547 | { 548 | error_log('Postcodify ("' . $q . '"): ' . $e->getMessage()); 549 | $search_type = 'ERROR'; 550 | $search_error = $e->getMessage(); 551 | $addresses = array(); 552 | } 553 | 554 | return array($addresses, $search_type, $search_error); 555 | } 556 | 557 | // 항상 64비트식으로 (음수가 나오지 않도록) CRC32를 계산하는 메소드. 558 | 559 | public static function crc32_x64($str) 560 | { 561 | $crc32 = crc32($str); 562 | return ($crc32 >= 0) ? $crc32 : ($crc32 + 4294967296); 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /lib/classes/server/areas.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server_Areas 23 | { 24 | // 전국의 시도 목록 (일반적인 축약형 포함). 25 | 26 | public static $sido = array( 27 | '서울' => '서울특별시', '서울시' => '서울특별시', '서울특별시' => '서울특별시', 28 | '부산' => '부산광역시', '부산시' => '부산광역시', '부산광역시' => '부산광역시', 29 | '대구' => '대구광역시', '대구시' => '대구광역시', '대구광역시' => '대구광역시', 30 | '대전' => '대전광역시', '대전시' => '대전광역시', '대전광역시' => '대전광역시', 31 | '인천' => '인천광역시', '인천시' => '인천광역시', '인천광역시' => '인천광역시', 32 | '광주' => '광주광역시', '광주시' => '광주광역시', '광주광역시' => '광주광역시', 33 | '울산' => '울산광역시', '울산시' => '울산광역시', '울산광역시' => '울산광역시', 34 | '세종' => '세종특별자치시', '세종시' => '세종특별자치시', '세종특별자치시' => '세종특별자치시', 35 | '제주' => '제주특별자치도', '제주도' => '제주특별자치도', '제주특별자치도' => '제주특별자치도', 36 | '강원' => '강원특별자치도', '강원도' => '강원특별자치도', '강원특별자치도' => '강원특별자치도', 37 | '경기' => '경기도', '경기도' => '경기도', 38 | '경남' => '경상남도', '경상남도' => '경상남도', 39 | '경북' => '경상북도', '경상북도' => '경상북도', 40 | '전남' => '전라남도', '전라남도' => '전라남도', 41 | '전북' => '전북특별자치도', '전라북도' => '전북특별자치도', '전북특별자치도' => '전북특별자치도', 42 | '충남' => '충청남도', '충청남도' => '충청남도', 43 | '충북' => '충청북도', '충청북도' => '충청북도', 44 | ); 45 | 46 | // 전국의 시군구 목록 (자치구만 포함, 나머지는 아래에 "일반구"로 별도 표기). 47 | 48 | public static $sigungu = array( 49 | // 서울 50 | '강남구', '강동구', '강북구', '강서구', '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구', 51 | '동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구', '영등포구', 52 | '용산구', '은평구', '종로구', '중구', '중랑구', 53 | // 부산 54 | '강서구', '금정구', '기장군', '남구', '동구', '동래구', '부산진구', '북구', '사상구', '사하구', 55 | '서구', '수영구', '연제구', '영도구', '중구', '해운대구', 56 | // 대구 57 | '남구', '달서구', '달성군', '동구', '북구', '서구', '수성구', '중구', 58 | // 인천 59 | '강화군', '계양구', '남구', '남동구', '동구', '부평구', '서구', '연수구', '옹진군', '중구', 60 | // 광주 61 | '광산구', '남구', '동구', '북구', '서구', 62 | // 대전 63 | '대덕구', '동구', '서구', '유성구', '중구', 64 | // 울산 65 | '남구', '동구', '북구', '울주군', '중구', 66 | // 제주 67 | '제주시', '서귀포시', 68 | // 강원 69 | '강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시', '인제군', 70 | '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군', 71 | // 경기 72 | '가평군', '고양시', '과천시', '광명시', '광주시', '구리시', '군포시', '김포시', '남양주시', '동두천시', 73 | '부천시', '성남시', '수원시', '시흥시', '안산시', '안성시', '안양시', '양주시', '양평군', '여주시', 74 | '연천군', '오산시', '용인시', '의왕시', '의정부시', '이천시', '파주시', '평택시', '포천시', '하남시', 75 | '화성시', 76 | // 경남 77 | '거제시', '거창군', '고성군', '김해시', '남해군', '밀양시', '사천시', '산청군', '양산시', '의령군', 78 | '진주시', '창녕군', '창원시', '통영시', '하동군', '함안군', '함양군', '합천군', 79 | // 경북 80 | '경산시', '경주시', '고령군', '구미시', '군위군', '김천시', '문경시', '봉화군', '상주시', '성주군', 81 | '안동시', '영덕군', '영양군', '영주시', '영천시', '예천군', '울릉군', '울진군', '의성군', '청도군', 82 | '청송군', '칠곡군', '포항시', 83 | // 전남 84 | '강진군', '고흥군', '곡성군', '광양시', '구례군', '나주시', '담양군', '목포시', '무안군', '보성군', 85 | '순천시', '신안군', '여수시', '영광군', '영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', 86 | '해남군', '화순군', 87 | // 전북 88 | '고창군', '군산시', '김제시', '남원시', '무주군', '부안군', '순창군', '완주군', '익산시', '임실군', 89 | '장수군', '전주시', '정읍시', '진안군', 90 | // 충남 91 | '계룡시', '공주시', '금산군', '논산시', '당진시', '보령시', '부여군', '서산시', '서천군', '아산시', 92 | '예산군', '천안시', '청양군', '태안군', '홍성군', 93 | // 충북 94 | '괴산군', '단양군', '보은군', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군', '청주시', 95 | '충주시', 96 | ); 97 | 98 | // 전국의 일반구 목록 (자치구가 아닌 구). 99 | 100 | public static $ilbangu = array( 101 | // 고양시 102 | '덕양구', '일산동구', '일산서구', 103 | // 부천시 104 | '소사구', '오정구', '원미구', 105 | // 성남시 106 | '분당구', '수정구', '중원구', 107 | // 수원시 108 | '권선구', '영통구', '장안구', '팔달구', 109 | // 안산시 110 | '단원구', '상록구', 111 | // 안양시 112 | '동안구', '만안구', 113 | // 용인시 114 | '기흥구', '수지구', '처인구', 115 | // 전주시 116 | '덕진구', '완산구', 117 | // 창원시 118 | '마산합포구', '마산회원구', '성산구', '의창구', '진해구', 119 | // 천안시 120 | '동남구', '서북구', 121 | // 청주시 122 | '상당구', '서원구', '청원구', '흥덕구', 123 | // 포항시 124 | '남구', '북구', 125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /lib/classes/server/cache.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server_Cache 23 | { 24 | // 캐시 핸들과 드라이버명. 25 | 26 | protected $_driver; 27 | protected $_handle; 28 | protected $_ttl; 29 | 30 | // 생성자. 31 | 32 | public function __construct($driver, $host, $port, $ttl) 33 | { 34 | // Memcached 사용시. 35 | 36 | if (strtolower($driver) === 'memcached') 37 | { 38 | if (class_exists('Memcached')) 39 | { 40 | $this->_driver = 'memcached'; 41 | $this->_handle = new Memcached; 42 | $this->_handle->addServer($host, $port); 43 | $this->_ttl = intval($ttl); 44 | } 45 | elseif (class_exists('Memcache')) 46 | { 47 | $this->_driver = 'memcache'; 48 | $this->_handle = new Memcache; 49 | $this->_handle->addServer($host, $port); 50 | $this->_ttl = intval($ttl); 51 | } 52 | else 53 | { 54 | throw new Exception('Cache driver not supported: memcached'); 55 | } 56 | } 57 | 58 | // Redis 사용시. 59 | 60 | if (strtolower($driver) === 'redis') 61 | { 62 | if (class_exists('Redis')) 63 | { 64 | $this->_driver = 'redis'; 65 | $this->_handle = new Redis; 66 | $this->_handle->connect($host, $port); 67 | $this->_ttl = intval($ttl); 68 | } 69 | else 70 | { 71 | throw new Exception('Cache driver not supported: redis'); 72 | } 73 | } 74 | } 75 | 76 | // GET 메소드. 77 | 78 | public function get($key) 79 | { 80 | $prefix = 'Postcodify:' . POSTCODIFY_VERSION . ':CACHE:'; 81 | $data = $this->_handle->get($prefix . $key); 82 | if ($data) 83 | { 84 | return json_decode($data); 85 | } 86 | else 87 | { 88 | return array(null, null, null); 89 | } 90 | } 91 | 92 | // SET 메소드. 93 | 94 | public function set($key, $rows, $search_type) 95 | { 96 | $prefix = 'Postcodify:' . POSTCODIFY_VERSION . ':CACHE:'; 97 | $data = json_encode(array($rows, $search_type, null)); 98 | switch ($this->_driver) 99 | { 100 | case 'memcached': 101 | return $this->_handle->set($prefix . $key, $data, $this->_ttl); 102 | case 'memcache': 103 | return $this->_handle->set($prefix . $key, $data, 0, $this->_ttl); 104 | case 'redis': 105 | return $this->_handle->setex($prefix . $key, $this->_ttl, $data); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/classes/server/database.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server_Database 23 | { 24 | // DB 핸들과 드라이버명. 25 | 26 | protected $_driver; 27 | protected $_dbh; 28 | 29 | // 생성자. 30 | 31 | public function __construct($driver, $host, $port, $user, $pass, $dbname) 32 | { 33 | // SQLite 사용시. 34 | 35 | if (strtolower($driver) === 'sqlite') 36 | { 37 | // PDO 모듈 사용 (기본값). 38 | 39 | if (class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers())) 40 | { 41 | $this->_driver = 'pdo_sqlite'; 42 | $this->_dbh = new PDO('sqlite:' . $dbname); 43 | $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 44 | $this->_dbh->exec('PRAGMA query_only = 1'); 45 | $this->_dbh->exec('PRAGMA case_sensitive_like = 0'); 46 | } 47 | 48 | // PDO 모듈을 사용할 수 없는 경우 예외를 던진다. 49 | 50 | else 51 | { 52 | throw new Exception('Database driver not supported: sqlite (No usable extension found)'); 53 | } 54 | } 55 | 56 | // MySQL 사용시. 57 | 58 | else 59 | { 60 | // PDO 모듈 사용 (기본값). 61 | 62 | if (class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers())) 63 | { 64 | $this->_driver = 'pdo_mysql'; 65 | $this->_dbh = new PDO('mysql:host=' . $host . ';port=' . $port . ';dbname=' . $dbname . ';charset=utf8', 66 | $user, $pass, array( 67 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 68 | PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', 69 | ) 70 | ); 71 | } 72 | 73 | // MySQLi 모듈 사용 (차선책). 74 | 75 | elseif (class_exists('mysqli')) 76 | { 77 | $this->_driver = 'mysqli'; 78 | $this->_dbh = @mysqli_connect($host, $user, $pass, $dbname, $port); 79 | if ($this->_dbh->connect_error) throw new Exception($this->_dbh->connect_error); 80 | $charset = @$this->_dbh->set_charset('utf8'); 81 | if (!$charset) throw new Exception($this->_dbh->error); 82 | $driver = new MySQLi_Driver; 83 | $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT; 84 | } 85 | 86 | // MySQL 모듈 사용 (최후의 수단). 87 | 88 | elseif (function_exists('mysql_connect')) 89 | { 90 | $this->_driver = 'mysql'; 91 | $this->_dbh = @mysql_connect($host . ':' . $port, $user, $pass); 92 | if (!$this->_dbh) throw new Exception(mysql_error($this->_dbh)); 93 | $seldb = @mysql_select_db($dbname, $this->_dbh); 94 | if (!$seldb) throw new Exception(mysql_error($this->_dbh)); 95 | $charset = function_exists('mysql_set_charset') ? @mysql_set_charset('utf8', $this->_dbh) : @mysql_query('SET NAMES utf8', $this->_dbh); 96 | if (!$charset) throw new Exception(mysql_error($this->_dbh)); 97 | } 98 | 99 | // 아무 것도 사용할 수 없는 경우 예외를 던진다. 100 | 101 | else 102 | { 103 | throw new Exception('Database driver not supported: mysql (No usable extension found)'); 104 | } 105 | } 106 | } 107 | 108 | // 쿼리 메소드. 109 | 110 | public function query($querystring, $joins, $conds, $args, $lang = 'KO', $sort = 'JUSO', $limit = 100, $offset = 0) 111 | { 112 | // 쿼리를 조합한다. 113 | 114 | $querystring = $querystring . ' ' . implode(' ', $joins) . ' WHERE ' . implode(' AND ', $conds); 115 | 116 | switch ($lang . $sort) 117 | { 118 | case 'KOJUSO': 119 | $order_by = 'sido_ko, sigungu_ko, ilbangu_ko, eupmyeon_ko, road_name_ko, num_major, num_minor'; break; 120 | case 'KOJIBEON': case 'KOPOBOX': 121 | $order_by = 'sido_ko, sigungu_ko, ilbangu_ko, eupmyeon_ko, dongri_ko, jibeon_major, jibeon_minor'; break; 122 | case 'ENJUSO': 123 | $order_by = 'sido_en, sigungu_en, ilbangu_en, eupmyeon_en, road_name_en, num_major, num_minor'; break; 124 | case 'ENJIBEON': case 'ENPOBOX': 125 | $order_by = 'sido_en, sigungu_en, ilbangu_en, eupmyeon_en, dongri_en, jibeon_major, jibeon_minor'; break; 126 | default: 127 | $order_by = 'sido_ko, sigungu_ko, ilbangu_ko, eupmyeon_ko, road_name_ko, num_major, num_minor'; break; 128 | } 129 | 130 | $querystring .= ' ORDER BY ' . $order_by . ' LIMIT ' . intval($limit) . ' OFFSET ' . intval($offset); 131 | 132 | // 쿼리를 실행한다. 133 | 134 | switch ($this->_driver) 135 | { 136 | case 'pdo_sqlite': 137 | return $this->query_pdo_sqlite($querystring, $args); 138 | case 'pdo_mysql': 139 | return $this->query_pdo_mysql($querystring, $args); 140 | case 'mysqli': 141 | return $this->query_mysqli($querystring, $args); 142 | case 'mysql': 143 | return $this->query_mysql($querystring, $args); 144 | default: 145 | return array(); 146 | } 147 | } 148 | 149 | // 쿼리 메소드 (PDO/SQLite). 150 | 151 | protected function query_pdo_sqlite($querystring, $args) 152 | { 153 | $ps = $this->_dbh->prepare($querystring); 154 | $ps->execute($args); 155 | return $ps->fetchAll(PDO::FETCH_OBJ); 156 | } 157 | 158 | // 쿼리 메소드 (PDO/MySQL). 159 | 160 | protected function query_pdo_mysql($querystring, $args) 161 | { 162 | $ps = $this->_dbh->prepare($querystring); 163 | $ps->execute($args); 164 | return $ps->fetchAll(PDO::FETCH_OBJ); 165 | } 166 | 167 | // 쿼리 메소드 (MySQLi). 168 | 169 | protected function query_mysqli($querystring, $args) 170 | { 171 | $querystring = explode('?', $querystring); 172 | foreach ($querystring as $key => $part) 173 | { 174 | if (isset($args[$key])) 175 | { 176 | if ($args[$key] === null) 177 | { 178 | $querystring[$key] .= 'null'; 179 | } 180 | elseif (is_numeric($args[$key])) 181 | { 182 | $querystring[$key] .= $args[$key]; 183 | } 184 | else 185 | { 186 | $querystring[$key] .= "'" . $this->_dbh->real_escape_string($args[$key]) . "'"; 187 | } 188 | } 189 | } 190 | $query = $this->_dbh->query(implode('', $querystring)); 191 | $result = array(); 192 | while ($row = $query->fetch_object()) 193 | { 194 | $result[] = $row; 195 | } 196 | return $result; 197 | } 198 | 199 | // 쿼리 메소드 (MySQL). 200 | 201 | protected function query_mysql($querystring, $args) 202 | { 203 | $querystring = explode('?', $querystring); 204 | foreach ($querystring as $key => $part) 205 | { 206 | if (isset($args[$key])) 207 | { 208 | if ($args[$key] === null) 209 | { 210 | $querystring[$key] .= 'null'; 211 | } 212 | elseif (is_numeric($args[$key])) 213 | { 214 | $querystring[$key] .= $args[$key]; 215 | } 216 | else 217 | { 218 | $querystring[$key] .= "'" . mysql_real_escape_string($args[$key], $this->_dbh) . "'"; 219 | } 220 | } 221 | } 222 | $query = @mysql_query(implode('', $querystring), $this->_dbh); 223 | if (!$query) throw new Exception(mysql_error($this->_dbh)); 224 | $result = array(); 225 | while ($row = mysql_fetch_object($query)) 226 | { 227 | $result[] = $row; 228 | } 229 | return $result; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /lib/classes/server/query.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server_Query 23 | { 24 | // 기본 속성들. 25 | 26 | public $sido; 27 | public $sigungu; 28 | public $ilbangu; 29 | public $eupmyeon; 30 | public $dongri; 31 | public $road; 32 | public $pobox; 33 | public $numbers; 34 | public $buildings = array(); 35 | public $use_area = false; 36 | public $lang = 'KO'; 37 | public $sort = 'JUSO'; 38 | 39 | // 검색어를 분석하여 쿼리 객체를 반환하는 메소드. 40 | 41 | public static function parse_keywords($keywords) 42 | { 43 | // 지번을 00번지 0호로 쓴 경우 검색 가능한 형태로 변환한다. 44 | 45 | $keywords = preg_replace('/([0-9]+)번지\\s?([0-9]+)호(?:\\s|$)/u', '$1-$2', $keywords); 46 | 47 | // 행정동, 도로명 등의 숫자 앞에 공백에 있는 경우 붙여쓴다. 48 | 49 | $keywords = preg_replace('/(^|\s)(?:' . 50 | '([가-힣]{1,3})\s+([0-9]{1,2}[동리가])|' . 51 | '([가-힣]+|[가-힣0-9.]+로)\s+([동서남북]?[0-9]+번?[가나다라마바사아자차카타파하동서남북안밖좌우옆갓상하샛윗아래]?[로길]))' . 52 | '(?=\s|\d|$)/u', '$1$2$3$4$5', $keywords, 1); 53 | 54 | // 검색어에서 불필요한 문자를 제거한다. 55 | 56 | $keywords = str_replace(array(',', '(', '|', ')'), ' ', $keywords); 57 | $keywords = preg_replace('/[^\\sㄱ-ㅎ가-힣a-z0-9@-]/u', '', strtolower($keywords)); 58 | 59 | // 쿼리 객체를 초기화한다. 60 | 61 | $q = new Postcodify_Server_Query; 62 | 63 | // 영문 도로명주소 또는 지번주소인지 확인한다. 64 | 65 | if (preg_match('/^(?:b|san|jiha)?(?:\\s*|-)([0-9]+)?(?:-([0-9]+))?\\s*([a-z0-9-\x20]+(ro|gil|dong|ri))(?:\\s|$)/i', $keywords, $matches)) 66 | { 67 | $addr_english = preg_replace('/[^a-z0-9]/', '', strtolower($matches[3])); 68 | $addr_type = strtolower($matches[4]); 69 | if ($addr_type === 'ro' || $addr_type === 'gil') 70 | { 71 | $q->road = $addr_english; 72 | } 73 | else 74 | { 75 | $q->dongri = $addr_english; 76 | $q->sort = 'JIBEON'; 77 | } 78 | $q->numbers = array($matches[1] ? $matches[1] : null, $matches[2] ? $matches[2] : null); 79 | $q->lang = 'EN'; 80 | return $q; 81 | } 82 | 83 | // 영문 사서함 주소인지 확인한다. 84 | 85 | if (preg_match('/p\\s*o\\s*box\\s*#?\\s*([0-9]+)(?:-([0-9]+))?/', $keywords, $matches)) 86 | { 87 | $q->pobox = '사서함'; 88 | $q->numbers = array($matches[1] ? $matches[1] : null, isset($matches[2]) ? $matches[2] : null); 89 | $q->lang = 'EN'; 90 | $q->sort = 'POBOX'; 91 | return $q; 92 | } 93 | 94 | // 검색어를 단어별로 분리한다. 95 | 96 | $keywords = preg_split('/\\s+/u', $keywords); 97 | 98 | // 각 단어의 의미를 파악한다. 99 | 100 | foreach ($keywords as $id => $keyword) 101 | { 102 | // 키워드가 "지하" 또는 한글 1글자인 경우 건너뛴다. ("읍", "면"은 예외) 103 | 104 | if ($keyword !== '읍' && $keyword !== '면' && ($keyword === '지하' || (mb_strlen($keyword, 'UTF-8') < 2 && !ctype_alnum($keyword)))) 105 | { 106 | continue; 107 | } 108 | 109 | // 첫 번째 구성요소가 시도인지 확인한다. 110 | 111 | if ($id == 0 && count($keywords) > 1) 112 | { 113 | if (isset(Postcodify_Server_Areas::$sido[$keyword])) 114 | { 115 | $q->sido = Postcodify_Server_Areas::$sido[$keyword]; 116 | $q->use_area = true; 117 | continue; 118 | } 119 | } 120 | 121 | // 이미 건물명이 나온 경우 건물명만 계속 검색한다. 122 | 123 | if (count($q->buildings)) 124 | { 125 | $keyword = preg_replace('/(?:[0-9a-z-]+|^[가나다라마바사])[동층호]?$/u', '', $keyword); 126 | if ($keyword !== '' && !in_array($keyword, $q->buildings)) 127 | { 128 | $q->buildings[] = preg_replace('/(?:아파트|a(?:pt)?|@)$/', '', $keyword); 129 | continue; 130 | } 131 | else 132 | { 133 | break; 134 | } 135 | } 136 | 137 | // 시군구읍면을 확인한다. 138 | 139 | if (preg_match('/.*([시군구읍면])$/u', $keyword, $matches)) 140 | { 141 | if ($matches[1] === '읍' || $matches[1] === '면') 142 | { 143 | if (!$q->sigungu && preg_match('/^(.+)군([읍면])$/u', $keyword, $gun) && 144 | in_array($gun[1] . '군', Postcodify_Server_Areas::$sigungu)) 145 | { 146 | $q->sigungu = $gun[1] . '군'; 147 | $q->eupmyeon = $gun[1] . $gun[2]; 148 | } 149 | elseif ($q->sigungu && ($keyword === '읍' || $keyword === '면')) 150 | { 151 | $q->eupmyeon = preg_replace('/군$/u', $keyword, $q->sigungu); 152 | } 153 | else 154 | { 155 | $q->eupmyeon = $keyword; 156 | } 157 | $q->use_area = true; 158 | continue; 159 | } 160 | elseif (!$q->sigungu && in_array($keyword, Postcodify_Server_Areas::$sigungu)) 161 | { 162 | $q->sigungu = $keyword; 163 | $q->use_area = true; 164 | continue; 165 | } 166 | elseif (!$q->ilbangu && in_array($keyword, Postcodify_Server_Areas::$ilbangu)) 167 | { 168 | $q->ilbangu = $keyword; 169 | $q->use_area = true; 170 | continue; 171 | } 172 | else 173 | { 174 | if (count($keywords) > $id + 1) continue; 175 | } 176 | } 177 | elseif (!$q->sigungu && in_array($keyword . '시', Postcodify_Server_Areas::$sigungu)) 178 | { 179 | $q->sigungu = $keyword . '시'; 180 | $q->use_area = true; 181 | continue; 182 | } 183 | elseif (!$q->sigungu && in_array($keyword . '군', Postcodify_Server_Areas::$sigungu)) 184 | { 185 | $q->sigungu = $keyword . '군'; 186 | $q->use_area = true; 187 | continue; 188 | } 189 | 190 | // 도로명+건물번호를 확인한다. 191 | 192 | if (preg_match('/^(.+[로길])((?:지하)?([0-9]+(?:-[0-9]+)?)(?:번지?)?)?$/u', $keyword, $matches)) 193 | { 194 | $q->road = $matches[1]; 195 | $q->sort = 'JUSO'; 196 | if (isset($matches[3]) && $matches[3]) 197 | { 198 | $q->numbers = $matches[3]; 199 | break; 200 | } 201 | continue; 202 | } 203 | 204 | // 동리+지번을 확인한다. 205 | 206 | if (preg_match('/^(.{1,5}(?:[0-9]가|[동리가]))(산?([0-9]+(?:-[0-9]+)?)(?:번지?)?)?$/u', $keyword, $matches) && empty($q->dongri)) 207 | { 208 | $q->dongri = $matches[1]; 209 | $q->sort = 'JIBEON'; 210 | if (isset($matches[3]) && $matches[3]) 211 | { 212 | $q->dongri = preg_replace(array('/[0-9]+동/u', '/[0-9]+가/u'), array('동', ''), $q->dongri); 213 | $q->numbers = $matches[3]; 214 | break; 215 | } 216 | continue; 217 | } 218 | 219 | // 사서함을 확인한다. 220 | 221 | if (preg_match('/^(.*사서함)(([0-9]+(?:-[0-9]+)?)번?)?$/u', $keyword, $matches)) 222 | { 223 | $q->pobox = $matches[1]; 224 | $q->sort = 'POBOX'; 225 | if (isset($matches[3]) && $matches[3]) 226 | { 227 | $q->numbers = $matches[3]; 228 | break; 229 | } 230 | continue; 231 | } 232 | 233 | // 건물번호, 지번, 사서함 번호를 따로 적은 경우를 확인한다. 234 | 235 | if (preg_match('/^(?:산|지하)?([0-9]+(?:-[0-9]+)?)(?:번지?)?$/u', $keyword, $matches)) 236 | { 237 | if ($q->dongri) 238 | { 239 | $q->dongri = preg_replace(array('/[0-9]+동/u', '/[0-9]+가/u'), array('동', ''), $q->dongri); 240 | } 241 | $q->numbers = $matches[1]; 242 | break; 243 | } 244 | 245 | // 그 밖의 키워드는 건물명으로 취급하되, 동·층·호수는 취급하지 않는다. 246 | 247 | if (!preg_match('/(?:[0-9a-z-]+|^[가나다라마바사])[동층호]?$/u', $keyword)) 248 | { 249 | $q->buildings[] = preg_replace('/(?:아파트|a(?:pt)?|@)$/', '', $keyword); 250 | continue; 251 | } 252 | 253 | // 그 밖의 키워드가 나오면 그만둔다. 254 | 255 | break; 256 | } 257 | 258 | // 건물번호 또는 지번을 주번과 부번으로 분리한다. 259 | 260 | if (isset($q->numbers)) 261 | { 262 | $q->numbers = explode('-', $q->numbers); 263 | if (!isset($q->numbers[1])) $q->numbers[1] = null; 264 | } 265 | else 266 | { 267 | $q->numbers = array(null, null); 268 | } 269 | 270 | // 쿼리 객체를 반환한다. 271 | 272 | return $q; 273 | } 274 | 275 | // 객체를 문자열로 변환할 경우 모든 내용을 붙여서 반환한다. 276 | 277 | public function __toString() 278 | { 279 | $result = array(); 280 | if ($this->sido !== null) $result[] = $this->sido; 281 | if ($this->sigungu !== null) $result[] = $this->sigungu; 282 | if ($this->ilbangu !== null) $result[] = $this->ilbangu; 283 | if ($this->eupmyeon !== null) $result[] = $this->eupmyeon; 284 | if ($this->dongri !== null) $result[] = $this->dongri; 285 | if ($this->road !== null) $result[] = $this->road; 286 | if ($this->pobox !== null) $result[] = $this->pobox; 287 | if (isset($this->numbers[0])) $result[] = $this->numbers[0] . 288 | (isset($this->numbers[1]) ? ('-' . $this->numbers[1]) : ''); 289 | if (count($this->buildings)) $result[] = implode(' ', $this->buildings); 290 | return implode(' ', $result); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /lib/classes/server/result.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Server_Result 23 | { 24 | public function __construct($error = '') 25 | { 26 | $this->version = POSTCODIFY_VERSION; 27 | $this->error = $error; 28 | } 29 | 30 | public $version = ''; 31 | public $error = ''; 32 | public $msg = ''; 33 | public $count = 0; 34 | public $time = 0; 35 | public $lang = 'KO'; 36 | public $sort = 'JUSO'; 37 | public $type = ''; 38 | public $nums = ''; 39 | public $cache = 'MISS'; 40 | public $results = array(); 41 | } 42 | 43 | class Postcodify_Server_Record { } 44 | 45 | class Postcodify_Server_Record_v17 extends Postcodify_Server_Record 46 | { 47 | public $code6; 48 | public $code5; 49 | public $address; 50 | public $canonical; 51 | public $extra_info_long; 52 | public $extra_info_short; 53 | public $english_address; 54 | public $jibeon_address; 55 | public $other; 56 | public $dbid; 57 | } 58 | 59 | class Postcodify_Server_Record_v18 extends Postcodify_Server_Record 60 | { 61 | public $code6; 62 | public $code5; 63 | public $address; 64 | public $english; 65 | public $other; 66 | public $dbid; 67 | } 68 | 69 | class Postcodify_Server_Record_v3 extends Postcodify_Server_Record 70 | { 71 | public $postcode5; 72 | public $postcode6; 73 | public $ko_common; 74 | public $ko_doro; 75 | public $ko_jibeon; 76 | public $en_common; 77 | public $en_doro; 78 | public $en_jibeon; 79 | public $address_id; 80 | public $building_id; 81 | public $building_name; 82 | public $building_nums; 83 | public $other_addresses; 84 | public $road_id; 85 | public $internal_id; 86 | } 87 | -------------------------------------------------------------------------------- /lib/classes/textfilereader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_TextFileReader 23 | { 24 | protected $_charset; 25 | protected $_fp; 26 | 27 | // 새로운 텍스트 파일을 열거나, 이미 열린 파일 포인터를 삽입할 수 있다. 28 | 29 | public function __construct($fp = null) 30 | { 31 | if (is_resource($fp) || $fp === null) 32 | { 33 | $this->_fp = $fp; 34 | } 35 | elseif (file_exists($fp) && is_readable($fp)) 36 | { 37 | $this->open($fp); 38 | } 39 | } 40 | 41 | // 텍스트 파일을 연다. 42 | 43 | public function open($filename) 44 | { 45 | $this->_fp = fopen($filename, 'rb'); 46 | } 47 | 48 | // 문자셋을 지정한다. 49 | 50 | public function set_charset($charset) 51 | { 52 | $this->_charset = $charset; 53 | } 54 | 55 | // 한 줄을 읽어 반환한다. 56 | 57 | public function read_line($delimiter = '|') 58 | { 59 | $line = fgets($this->_fp); 60 | if ($line === false) return false; 61 | 62 | if ($this->_charset === 'UTF-8') 63 | { 64 | // no-op 65 | } 66 | elseif ($this->_charset !== null) 67 | { 68 | $line = mb_convert_encoding($line, 'UTF-8', $this->_charset); 69 | } 70 | else 71 | { 72 | if (mb_check_encoding($line, 'UTF-8')) 73 | { 74 | $this->_charset = 'UTF-8'; 75 | } 76 | else 77 | { 78 | $this->_charset = 'CP949'; 79 | $line = mb_convert_encoding($line, 'UTF-8', $this->_charset); 80 | } 81 | } 82 | 83 | return explode($delimiter, $line); 84 | } 85 | 86 | // 파일을 닫는다. 87 | 88 | public function close() 89 | { 90 | if (is_resource($this->_fp)) 91 | { 92 | fclose($this->_fp); 93 | $this->_fp = null; 94 | } 95 | } 96 | 97 | // 파일 포인터를 반환한다. 98 | 99 | public function get_fp() 100 | { 101 | return $this->_fp; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/classes/utility.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_Utility 23 | { 24 | // 인덱서가 지원하는 명령 목록. 25 | 26 | public static $indexer_commands = array( 27 | 'download', 28 | 'createdb', 29 | 'verifydb', 30 | 'sqlite-convert', 31 | 'download-updates', 32 | 'update', 33 | 'set-postcode', 34 | ); 35 | 36 | // 캐시를 위한 변수들. 37 | 38 | public static $road_cache = array(); 39 | public static $building_cache = array(); 40 | public static $building_number_cache = array(); 41 | public static $english_cache = array(); 42 | public static $oldcode_cache = array(); 43 | 44 | // DB에 연결하는 함수. 45 | 46 | public static function get_db() 47 | { 48 | if (POSTCODIFY_DB_DRIVER === 'mysql') 49 | { 50 | $dsn = POSTCODIFY_DB_DRIVER . ':host=' . POSTCODIFY_DB_HOST . ';port=' . POSTCODIFY_DB_PORT . ';dbname=' . POSTCODIFY_DB_DBNAME; 51 | $dsn .= ';charset=utf8'; 52 | 53 | $pdo_options = array( 54 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 55 | PDO::ATTR_EMULATE_PREPARES => false, 56 | PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', 57 | ); 58 | } 59 | else 60 | { 61 | $dsn = 'sqlite:' . POSTCODIFY_DB_DBNAME; 62 | $pdo_options = array( 63 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 64 | PDO::ATTR_EMULATE_PREPARES => false, 65 | ); 66 | } 67 | 68 | $db = new PDO($dsn, POSTCODIFY_DB_USER, POSTCODIFY_DB_PASS, $pdo_options); 69 | return $db; 70 | } 71 | 72 | // 항상 64비트식으로 (음수 없이) CRC32를 계산하는 함수. 73 | 74 | public static function crc32_x64($str) 75 | { 76 | $crc32 = crc32($str); 77 | return ($crc32 >= 0) ? $crc32 : ($crc32 + 4294967296); 78 | } 79 | 80 | // 파일 다운로드 함수. 81 | 82 | public static function download($url, $target_filename = false, $progress_callback = false) 83 | { 84 | $ch = curl_init($url); 85 | if ($target_filename) 86 | { 87 | $fp = fopen($target_filename, 'w'); 88 | curl_setopt($ch, CURLOPT_FILE, $fp); 89 | } 90 | else 91 | { 92 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 93 | } 94 | 95 | if ($progress_callback && defined('CURLOPT_PROGRESSFUNCTION')) 96 | { 97 | curl_setopt($ch, CURLOPT_NOPROGRESS, false); 98 | curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, $progress_callback); 99 | } 100 | 101 | curl_setopt($ch, CURLOPT_HEADER, 0); 102 | curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Compatible; Postcodify Downloader)'); 103 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 3); 104 | 105 | $result = curl_exec($ch); 106 | curl_close($ch); 107 | if ($target_filename) 108 | { 109 | fclose($fp); 110 | } 111 | else 112 | { 113 | if (!preg_match('/charset="?utf-?8"?/i', $result)) 114 | { 115 | $result = iconv('CP949', 'UTF-8', $result); 116 | } 117 | } 118 | return $result; 119 | } 120 | 121 | // 터미널에서 입력한 변수들을 확인하는 함수. 122 | 123 | public static function get_terminal_args() 124 | { 125 | $result = array( 126 | 'command' => null, 127 | 'args' => array(), 128 | 'options' => array(), 129 | ); 130 | 131 | foreach ($_SERVER['argv'] as $key => $arg) 132 | { 133 | if ($key === 0) 134 | { 135 | continue; 136 | } 137 | elseif (in_array($arg, self::$indexer_commands)) 138 | { 139 | $result['command'] = $arg; 140 | } 141 | elseif (preg_match('/^--[a-z0-9-]+$/', $arg)) 142 | { 143 | $result['options'][] = $arg; 144 | } 145 | else 146 | { 147 | $result['args'][] = $arg; 148 | } 149 | } 150 | 151 | return (object)$result; 152 | } 153 | 154 | // 터미널의 가로 폭을 측정하는 함수. 155 | 156 | public static function get_terminal_width() 157 | { 158 | static $width = null; 159 | if ($width !== null) return $width; 160 | 161 | $width = intval(trim(exec('tput cols'))); 162 | if (!$width) $width = 80; 163 | return $width; 164 | } 165 | 166 | // 터미널에서 커서를 후퇴시키는 함수. 167 | 168 | public static function print_negative_spaces($count) 169 | { 170 | echo "\033[{$count}D"; 171 | } 172 | 173 | // 터미널에 메시지를 출력하고 커서를 오른쪽 끝으로 이동한다. 174 | 175 | public static function print_message($str) 176 | { 177 | $spaces = self::get_terminal_width() - mb_strwidth($str, 'UTF-8'); 178 | echo $str . ($spaces > 0 ? str_repeat(' ', $spaces) : ''); 179 | } 180 | 181 | // 터미널에 진행 상황을 출력한다. 182 | 183 | public static function print_progress($num, $max = null) 184 | { 185 | if ($max === null) 186 | { 187 | self::print_negative_spaces(25); 188 | echo str_pad(number_format($num), 23, ' ', STR_PAD_LEFT) . ' '; 189 | } 190 | else 191 | { 192 | self::print_negative_spaces(25); 193 | echo str_pad(number_format($num) . ' / ' . number_format($max), 23, ' ', STR_PAD_LEFT) . ' '; 194 | } 195 | } 196 | 197 | // 터미널에 OK 메시지를 출력하고 커서를 다음 줄로 이동한다. 198 | 199 | public static function print_ok($count = null) 200 | { 201 | self::print_negative_spaces(25); 202 | if ($count !== null) 203 | { 204 | echo str_pad(number_format($count), 17, ' ', STR_PAD_LEFT) . ' ' . '[ OK ]'; 205 | } 206 | else 207 | { 208 | echo str_repeat(' ', 19) . '[ OK ]'; 209 | } 210 | echo PHP_EOL; 211 | } 212 | 213 | // 터미널에 ERROR 메시지를 출력하고 커서를 다음 줄로 이동한다. 214 | 215 | public static function print_error() 216 | { 217 | self::print_negative_spaces(25); 218 | echo str_repeat(' ', 16) . '[ ERROR ]'; 219 | echo PHP_EOL; 220 | } 221 | 222 | // 터미널의 커서를 다음 줄로 이동한다. 223 | 224 | public static function print_newline() 225 | { 226 | echo PHP_EOL; 227 | } 228 | 229 | // 터미널에 인덱서 사용 방법을 출력하고 종료한다. 230 | 231 | public static function print_usage_instructions() 232 | { 233 | $stderr = fopen('php://stderr', 'w'); 234 | fwrite($stderr, 'Usage: php indexer.php [filename]' . PHP_EOL); 235 | fwrite($stderr, 'Valid commands:' . PHP_EOL); 236 | foreach (self::$indexer_commands as $command) 237 | { 238 | fwrite($stderr, ' ' . $command . PHP_EOL); 239 | } 240 | fwrite($stderr, 'Valid options:' . PHP_EOL); 241 | fwrite($stderr, ' filename (only with `sqlite-convert`)' . PHP_EOL); 242 | fwrite($stderr, ' --add-old-postcodes (since 3.3.0)' . PHP_EOL); 243 | fwrite($stderr, ' --no-old-postcodes (since 3.1.0, deprecated since 3.3.0)' . PHP_EOL); 244 | fwrite($stderr, 'Invalid options:' . PHP_EOL); 245 | fwrite($stderr, ' --dry-run (not supported since 3.0.0)' . PHP_EOL); 246 | fclose($stderr); 247 | exit(1); 248 | } 249 | 250 | // 검색 키워드에서 불필요한 문자와 띄어쓰기를 제거하는 함수. 251 | 252 | public static function get_canonical($str) 253 | { 254 | $str = strval($str); 255 | $str = str_replace(array('(주)', '(유)', '(사)', '(재)', '(아)', '㈜'), '', $str); 256 | return preg_replace('/[^ㄱ-ㅎ가-힣a-z0-9.-]/uU', '', strtolower($str)); 257 | } 258 | 259 | // 주소를 영문으로 변환하는 함수. 260 | 261 | public static function get_english($str) 262 | { 263 | $str = strval($str); 264 | if (isset(self::$english_cache[$str])) 265 | { 266 | return self::$english_cache[$str]; 267 | } 268 | 269 | static $romaja_loaded = false; 270 | if (!$romaja_loaded) 271 | { 272 | include_once POSTCODIFY_LIB_DIR . '/resources/romaja.php'; 273 | $romaja_loaded = true; 274 | } 275 | 276 | return self::$english_cache[$str] = Hangeul_Romaja::convert($str, Hangeul_Romaja::TYPE_ADDRESS); 277 | } 278 | 279 | // 도로명의 일반적인 변형들을 구하는 함수. 280 | 281 | public static function get_variations_of_road_name($str) 282 | { 283 | $str = strval($str); 284 | $str = preg_replace('/[^\\sㄱ-ㅎ가-힣a-z0-9]/u', '', $str); 285 | $keywords = array($str); 286 | 287 | if (preg_match('/^(.+)([동서남북]?)([0-9-]+)번?로$/uU', $str, $matches)) 288 | { 289 | $keywords[] = $matches[1] . '로'; 290 | $keywords[] = $matches[1] . $matches[3] . '로'; 291 | $keywords[] = $matches[1] . $matches[3]; 292 | if ($matches[2]) 293 | { 294 | $keywords[] = $matches[1] . $matches[2] . '로'; 295 | $keywords[] = $matches[1] . $matches[2] . $matches[3] . '로'; 296 | $keywords[] = $matches[1] . $matches[2] . $matches[3]; 297 | } 298 | } 299 | elseif (preg_match('/^(.+)([동서남북]?)([0-9-]+)번?([가나라다마바사아자차카타파하동서남북안밖좌우옆갓상하샛윗아래]?)길$/uU', $str, $matches)) 300 | { 301 | if (preg_match('/[로길]$/uU', $matches[1])) 302 | { 303 | $keywords[] = $matches[1]; 304 | $keywords[] = $matches[1] . $matches[3]; 305 | $keywords[] = $matches[1] . $matches[3] . '길'; 306 | } 307 | else 308 | { 309 | $keywords[] = $matches[1] . '길'; 310 | $keywords[] = $matches[1] . $matches[3] . '길'; 311 | $keywords[] = $matches[1] . $matches[3]; 312 | if ($matches[2]) 313 | { 314 | $keywords[] = $matches[1] . $matches[2] . '길'; 315 | $keywords[] = $matches[1] . $matches[2] . $matches[3] . '길'; 316 | $keywords[] = $matches[1] . $matches[2] . $matches[3]; 317 | } 318 | if ($matches[4]) 319 | { 320 | $keywords[] = $matches[1] . $matches[4] . '길'; 321 | $keywords[] = $matches[1] . $matches[3] . $matches[4] . '길'; 322 | } 323 | } 324 | } 325 | 326 | return array_unique($keywords); 327 | } 328 | 329 | // 동명 및 리명의 일반적인 변형들을 구하는 함수. 330 | 331 | public static function get_variations_of_dongri($str) 332 | { 333 | $str = strval($str); 334 | $keywords = preg_match('/[.,-]/', $str) ? array() : array($str); 335 | 336 | if (preg_match('/^(.+)제?([0-9.,-]+)([동리])$/uU', $str, $matches)) 337 | { 338 | $keywords[] = $str = $matches[1] . $matches[3]; 339 | $matches[2] = preg_split('/[.,-]/', $matches[2]); 340 | foreach ($matches[2] as $match) 341 | { 342 | if (ctype_digit(trim($match))) 343 | { 344 | $keywords[] = $matches[1] . $match . $matches[3]; 345 | $keywords[] = $matches[1] . '제' . $match . $matches[3]; 346 | } 347 | } 348 | $keywords[] = $matches[1] . implode('', $matches[2]) . $matches[3]; 349 | $keywords[] = $matches[1] . '제' . implode('', $matches[2]) . $matches[3]; 350 | } 351 | elseif (!count($keywords)) 352 | { 353 | $split_keywords = preg_split('/[.,-]/', $str); 354 | $split_keywords_count = count($split_keywords); 355 | foreach ($split_keywords as $key => $value) 356 | { 357 | if ($key < $split_keywords_count - 1) $value .= substr($str, strlen($str) - 3); 358 | $keywords[] = $value; 359 | $keywords[] = preg_replace('/[0-9.]+/', '', $value); 360 | } 361 | return array_unique($keywords); 362 | } 363 | 364 | if (preg_match('/^(.+)([0-9]+)가동$/uU', $str, $matches)) 365 | { 366 | $keywords[] = $str = $matches[1] . '동'; 367 | $keywords[] = $str = $matches[1] . '동' . $matches[2] . '가'; 368 | } 369 | elseif (preg_match('/^([가-힣]+)([동로])([0-9]+)가$/uU', $str, $matches)) 370 | { 371 | $keywords[] = $str = $matches[1] . $matches[2]; 372 | $keywords[] = $str = $matches[1] . $matches[2] . $matches[3] . '가'; 373 | $keywords[] = $str = $matches[1] . $matches[3] . '가동'; 374 | if ($matches[2] === '로') 375 | { 376 | $keywords[] = $str = $matches[1] . '로' . $matches[3] . '가동'; 377 | } 378 | } 379 | 380 | if (strlen($str) > 9 && substr($str, strlen($str) - 6) === '본동') 381 | { 382 | $keywords[] = substr($str, 0, strlen($str) - 6) . '동'; 383 | } 384 | 385 | rsort($keywords); 386 | return array_unique($keywords); 387 | } 388 | 389 | // 상세건물명 번호를 정리하여 문자열로 반환하는 메소드. 아파트 동 범위를 작성할 때 사용한다. 390 | 391 | public static function consolidate_building_nums($nums) 392 | { 393 | $intermediate = array('numeric' => array(), 'alphabet' => array(), 'other' => array()); 394 | foreach ($nums as $num) 395 | { 396 | if (ctype_digit($num)) 397 | { 398 | $intermediate['numeric'][] = intval($num); 399 | } 400 | elseif (ctype_alnum($num)) 401 | { 402 | $intermediate['alphabet'][] = strtoupper($num); 403 | } 404 | else 405 | { 406 | switch ($num) 407 | { 408 | case '에이': $intermediate['alphabet'][] = 'A'; break; 409 | case '비': $intermediate['alphabet'][] = 'B'; break; 410 | case '시': case '씨': $intermediate['alphabet'][] = 'C'; break; 411 | case '디': $intermediate['alphabet'][] = 'D'; break; 412 | default: $intermediate['other'][] = strtoupper($num); 413 | } 414 | } 415 | } 416 | 417 | sort($intermediate['numeric']); 418 | natsort($intermediate['alphabet']); 419 | natsort($intermediate['other']); 420 | 421 | $output = array(); 422 | foreach ($intermediate as $key => $val) 423 | { 424 | if ($key === 'other') 425 | { 426 | foreach ($val as $vals) 427 | { 428 | $output[] = $vals . '동'; 429 | } 430 | } 431 | else 432 | { 433 | switch (count($val)) 434 | { 435 | case 0: 436 | break; 437 | case 1: 438 | $output[] = reset($val) . '동'; 439 | break; 440 | default: 441 | $output[] = reset($val) . '~' . end($val) . '동'; 442 | } 443 | } 444 | } 445 | 446 | return implode(', ', $output); 447 | } 448 | 449 | // 건물명 목록에서 불필요하거나 중복되는 것을 제거하여 반환하는 메소드. 450 | 451 | public static function consolidate_building_names($names, $skip_name = null) 452 | { 453 | // 불필요한 건물명을 제거한다. 454 | 455 | $input = array(); 456 | foreach ($names as $val) 457 | { 458 | if (ctype_digit($val)) continue; 459 | if (self::is_ignorable_building_name($val)) continue; 460 | if ($skip_name !== null && strpos($skip_name, $val) !== false) continue; 461 | if (preg_match('/(?:(?:근린생활|동\.?식물관련|노유자|발전|창고)시설|(?:단독|다세대|다가구)주택)/u', $val)) continue; 462 | if (preg_match('/(?:주|(?:주|부속)건축물)제?(?:[0-9a-zA-Z-]+|에이|비|씨|디|[가나다라마바사아자차카타파하])(?:호|동|호동)/u', $val)) continue; 463 | if (preg_match('/[0-9a-zA-Z-]+(?:블럭|로트|롯트)/u', $val)) continue; 464 | if (preg_match('/^(?:[가-힣0-9]+[읍면]\s?)?[가-힣0-9]+[동리가]\s?[0-9]+(?:-[0-9]+)?(?:번지|\s)/u', $val)) continue; 465 | if (preg_match('/\((?:[가-힣0-9]{3,}\)?$|[가-힣0-9]{0,2})$/u', $val)) continue; 466 | $input[] = str_replace('㈜', '(주)', $val); 467 | } 468 | 469 | // 건물명 목록을 긴 것부터 짧은 순으로 정렬한다. 470 | 471 | usort($input, 'Postcodify_Utility::sort_building_names'); 472 | 473 | // 짧은 건물명이 긴 건물명에 포함되어 있는 경우 제거한다. 474 | 475 | $output = array(); 476 | foreach ($input as $val) 477 | { 478 | if ($val === '') continue; 479 | $exists = false; 480 | foreach ($output as $compare) 481 | { 482 | if (strpos($compare, $val) !== false) 483 | { 484 | $exists = true; 485 | break; 486 | } 487 | } 488 | if (!$exists) 489 | { 490 | $output[] = $val; 491 | } 492 | } 493 | 494 | return $output; 495 | } 496 | 497 | // 건물명 목록을 압축하여 문자열로 반환하는 메소드. 건물명 검색 테이블을 생성할 때 사용한다. 498 | 499 | public static function compress_building_names($names) 500 | { 501 | // 검색어에 포함될 수 없는 문자를 모두 제거한다. 502 | 503 | $result = array(); 504 | foreach ($names as $key => $val) 505 | { 506 | $val = self::get_canonical($val); 507 | if (empty($val) || trim($val) === '') unset($names[$key]); 508 | $result[] = $val; 509 | } 510 | 511 | // 하나로 합쳐서 반환한다. 512 | 513 | return implode(',', $result); 514 | } 515 | 516 | // 건물명의 길이를 비교하는 메소드. 중복 건물명 제거를 위한 콜백 메소드이다. 517 | 518 | protected static function sort_building_names($a, $b) 519 | { 520 | return strlen($b) - strlen($a); 521 | } 522 | 523 | // 무시할 건물명인지 확인하는 메소드. 524 | 525 | protected static function is_ignorable_building_name($str) 526 | { 527 | static $ignore_list = array(); 528 | if (!count($ignore_list)) 529 | { 530 | $ignore_list_human_readable = array( 531 | '주택', '단독주택', '창고', '화장실', '차고', '본관', '본관동', '별관', '별관동', '증축', '관사', '교회', 532 | '소매점', '일반음식점', '음식점', '우체국', '미술관', '주유소', '사무실', '관리실', '대웅전', 533 | '관리사무소', '노인정', '이발관', '상가', '폐상가', '공가', '폐가', '축사', '폐축사', '가건물', 534 | '다세대주택', '공장', '정비공장', '제실', '컨테이너', '사무소', '무벽건물', '재실', '철거', '제각', '퇴비사', 535 | '슈퍼', '민박', '경로당', '정자', '비닐하우스', '하우스', '우사', '돈사', '견사', '양계장', '건물', 536 | '빈집', '다가구주택', '상점', '고물상', '원룸', '폐창고', '농막', '사찰', '회관', '관리사', '폐공장', 537 | '식당', '주차장', '사당', '온실', '빌라', '일반공장', '공중화장실', '마을공동시설', '방앗간', 538 | '여관', '학원', '수리점', '약국', '다세대', '다가구', '미용실', '고시원', '세탁소', '공사중', 539 | '동.식물관련시설', '노유자시설', '발전시설', '창고시설', '컨테이너', 540 | ); 541 | foreach ($ignore_list_human_readable as $building_name) 542 | { 543 | $ignore_list[$building_name] = true; 544 | } 545 | } 546 | 547 | return isset($ignore_list[$str]); 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /lib/classes/zipreader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | class Postcodify_ZipReader 23 | { 24 | protected $_charset; 25 | protected $_current; 26 | protected $_zip; 27 | protected $_fp; 28 | 29 | // 새로운 Zip 아카이브를 열거나, 이미 열린 Zip 아카이브를 삽입할 수 있다. 30 | 31 | public function __construct($zip = null) 32 | { 33 | if ($zip instanceof ZipArchive || $zip === null) 34 | { 35 | $this->_zip = $zip; 36 | } 37 | elseif (file_exists($zip) && is_readable($zip)) 38 | { 39 | $this->open_archive($zip); 40 | } 41 | } 42 | 43 | // Zip 아카이브를 연다. 44 | 45 | public function open_archive($filename) 46 | { 47 | $this->_zip = new ZipArchive; 48 | $this->_zip->open($filename); 49 | } 50 | 51 | // Zip 아카이브 중 다음 파일을 연다. 52 | // 성공하면 파일명을 반환하고, 실패하면 false를 반환한다. 53 | 54 | public function open_next_file() 55 | { 56 | $index = ($this->_current === null) ? 0 : ($this->_current + 1); 57 | $first_filename = @$this->_zip->getNameIndex($index); 58 | if ($first_filename === false) return false; 59 | $this->_fp = $this->_zip->getStream($first_filename); 60 | $this->_current = $index; 61 | return $first_filename; 62 | } 63 | 64 | // Zip 아카이브 중 이름에 주어진 문자열이 포함된 파일을 연다. 65 | // 성공하면 파일명을 반환하고, 실패하면 false를 반환한다. 66 | 67 | public function open_named_file($str) 68 | { 69 | $count = $this->_zip->numFiles; 70 | if (!$count) return false; 71 | 72 | if (mb_check_encoding($str, 'UTF-8')) 73 | { 74 | $str1 = iconv('UTF-8', 'CP949', $str); 75 | } 76 | else 77 | { 78 | $str1 = $str; 79 | } 80 | $str2 = iconv('CP437', 'UTF-8', $str1); 81 | 82 | for ($i = 0; $i < $count; $i++) 83 | { 84 | $filename = $this->_zip->getNameIndex($i); 85 | if (stripos($filename, $str) !== false || stripos($filename, $str1) !== false || stripos($filename, $str2) !== false) 86 | { 87 | $this->_fp = $this->_zip->getStream($filename); 88 | return $filename; 89 | } 90 | } 91 | return false; 92 | } 93 | 94 | // Zip 아카이브 중 가장 큰 파일을 연다. 95 | // 성공하면 파일명을 반환하고, 실패하면 false를 반환한다. 96 | 97 | public function open_largest_file() 98 | { 99 | $count = $this->_zip->numFiles; 100 | if (!$count) return false; 101 | 102 | $sizes = array(); 103 | for ($i = 0; $i < $count; $i++) 104 | { 105 | $stat = $this->_zip->statIndex($i); 106 | $sizes[$i] = intval($stat['size']); 107 | } 108 | arsort($sizes); 109 | reset($sizes); 110 | 111 | $filename = $this->_zip->getNameIndex(key($sizes)); 112 | $this->_fp = $this->_zip->getStream($filename); 113 | return $filename; 114 | } 115 | 116 | // 문자셋을 지정한다. 117 | 118 | public function set_charset($charset) 119 | { 120 | $this->_charset = $charset; 121 | } 122 | 123 | // 한 줄을 읽어 반환한다. 124 | 125 | public function read_line($delimiter = '|') 126 | { 127 | $line = fgets($this->_fp); 128 | if ($line === false) return false; 129 | 130 | if ($this->_charset === 'UTF-8') 131 | { 132 | // no-op 133 | } 134 | elseif ($this->_charset !== null) 135 | { 136 | $line = mb_convert_encoding($line, 'UTF-8', $this->_charset); 137 | } 138 | else 139 | { 140 | if (mb_check_encoding($line, 'UTF-8')) 141 | { 142 | $this->_charset = 'UTF-8'; 143 | } 144 | else 145 | { 146 | $this->_charset = 'CP949'; 147 | $line = mb_convert_encoding($line, 'UTF-8', $this->_charset); 148 | } 149 | } 150 | 151 | return explode($delimiter, $line); 152 | } 153 | 154 | // 열린 파일을 닫는다. 155 | 156 | public function close_file() 157 | { 158 | if (is_resource($this->_fp)) 159 | { 160 | fclose($this->_fp); 161 | $this->_fp = null; 162 | } 163 | } 164 | 165 | // Zip 아카이브를 닫는다. 166 | 167 | public function close() 168 | { 169 | if (is_resource($this->_fp)) 170 | { 171 | fclose($this->_fp); 172 | $this->_fp = null; 173 | } 174 | 175 | if ($this->_zip) 176 | { 177 | $this->_zip->close(); 178 | $this->_zip = null; 179 | } 180 | } 181 | 182 | // 파일 포인터를 반환한다. 183 | 184 | public function get_fp() 185 | { 186 | return $this->_fp; 187 | } 188 | 189 | // Zip 아카이브를 반환한다. 190 | 191 | public function get_zip() 192 | { 193 | return $this->_zip; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/config.example.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | date_default_timezone_set('Asia/Seoul'); 23 | error_reporting(-1); 24 | require_once dirname(__FILE__) . '/../autoload.php'; 25 | 26 | // GET 또는 터미널 파라미터로부터 검색 키워드를 조합한다. 27 | 28 | if (isset($_GET['gugun']) && strlen($_GET['gugun']) && isset($_GET['q']) && strlen($_GET['q'])) 29 | { 30 | $_GET['q'] = $_GET['gugun'] . ' ' . $_GET['q']; 31 | } 32 | 33 | if (isset($_GET['sido']) && strlen($_GET['sido']) && isset($_GET['q']) && strlen($_GET['q'])) 34 | { 35 | $_GET['q'] = $_GET['sido'] . ' ' . $_GET['q']; 36 | } 37 | 38 | $keywords = isset($_GET['q']) ? trim($_GET['q']) : (isset($argv[1]) ? trim($argv[1], ' "\'') : ''); 39 | 40 | // 키워드의 한글 인코딩 방식이 EUC-KR인 경우 UTF-8로 변환한다. 41 | 42 | if ((isset($_GET['charset']) && stripos($_GET['charset'], 'euc') !== false) || !mb_check_encoding($keywords, 'UTF-8')) 43 | { 44 | $keywords = @mb_convert_encoding($keywords, 'UTF-8', 'CP949'); 45 | } 46 | 47 | // JSONP 콜백 함수명과 클라이언트 버전을 구한다. 48 | 49 | $callback = isset($_GET['callback']) ? $_GET['callback'] : null; 50 | $client_version = isset($_GET['v']) ? trim($_GET['v']) : POSTCODIFY_VERSION; 51 | if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) $keywords = stripslashes($keywords); 52 | if (preg_match('/[^a-zA-Z0-9_.]/', $callback)) $callback = null; 53 | 54 | // 검색을 수행한다. 55 | 56 | header('Content-Type: application/javascript; charset=UTF-8'); 57 | header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0'); 58 | header('Expires: Sat, 01 Jan 2000 00:00:00 GMT'); 59 | 60 | if (!isset($result) || !is_object($result)) 61 | { 62 | $server = new Postcodify_Server; 63 | $server->db_driver = POSTCODIFY_DB_DRIVER; 64 | $server->db_dbname = POSTCODIFY_DB_DBNAME; 65 | $server->db_host = POSTCODIFY_DB_HOST; 66 | $server->db_port = POSTCODIFY_DB_PORT; 67 | $server->db_user = POSTCODIFY_DB_USER; 68 | $server->db_pass = POSTCODIFY_DB_PASS; 69 | $result = $server->search($keywords, 'UTF-8'); 70 | } 71 | 72 | // 검색 결과를 juso.sir.co.kr API와 같은 포맷으로 변환한다. 73 | 74 | ob_start(); ?> 75 |
  • 76 |

    77 | 78 | %3$s  / %4$s 79 |

    80 |

    81 | 82 | %5$s  (%8$s) 83 |

    84 |
  • 85 | error) 88 | { 89 | $json = array( 90 | 'error' => "검색 서버와 통신 중 오류가 발생하였습니다.\n잠시 후 다시 시도해 주시기 바랍니다.", 91 | 'juso' => $result->error, 92 | ); 93 | } 94 | else 95 | { 96 | $json = array('error' => '', 'juso' => array()); 97 | $json['juso'][] = sprintf('
    검색결과 %d%s' . 98 | '
    ' . 99 | 'Powered by Postcodify
    ' . 100 | '
    ', $result->count, ($result->count === 100) ? '+' : ''); 101 | if (!count($result->results)) 102 | { 103 | $json['juso'][] = '
    ' . 104 | '검색 결과가 없습니다.
    정확한 도로명+건물번호 또는 동·리+번지로 검색해 주십시오.
    '; 105 | } 106 | elseif (count($result->results) === 100) 107 | { 108 | $json['juso'][] = '
    ' . 109 | '검색 결과가 너무 많아 100건만 표시합니다.
    정확한 도로명+건물번호 또는 동·리+번지로 검색해 주십시오.
    '; 110 | } 111 | $json['juso'][] = '
      '; 112 | foreach ($result->results as $entry) 113 | { 114 | $code6 = array(substr($entry->postcode6, 0, 3), substr($entry->postcode6, 3, 3)); 115 | $code5 = array(substr($entry->postcode5, 0, 3), substr($entry->postcode5, 3, 2)); 116 | $juso = htmlspecialchars($entry->ko_common . ' ' . $entry->ko_doro, ENT_COMPAT, 'UTF-8'); 117 | $jibeon = htmlspecialchars($entry->ko_common . ' ' . $entry->ko_jibeon, ENT_COMPAT, 'UTF-8'); 118 | $extra_input = htmlspecialchars(preg_replace('/\s.+$/', '', $entry->ko_jibeon) . ($entry->building_name === '' ? '' : (', ' . $entry->building_name)), ENT_COMPAT, 'UTF-8'); 119 | $extra_display = htmlspecialchars($entry->ko_jibeon . ($entry->building_name === '' ? '' : (', ' . $entry->building_name)) . 120 | ($entry->building_nums ? (' ' . $entry->building_nums) : ''), ENT_COMPAT, 'UTF-8'); 121 | if (isset($_GET['pc']) && $_GET['pc'] === '6') 122 | { 123 | $json['juso'][] = sprintf($template, $code6[0], $code6[1], implode('-', $code6), $entry->postcode5, $juso, $jibeon, $extra_input, $extra_display); 124 | } 125 | else 126 | { 127 | $json['juso'][] = sprintf($template, $code5[0], $code5[1], $entry->postcode5, implode('-', $code6), $juso, $jibeon, $extra_input, $extra_display); 128 | } 129 | } 130 | $json['juso'][] = '
    '; 131 | $json['juso'][] = ''; 137 | $json['juso'] = implode("\n", $json['juso']); 138 | } 139 | 140 | $json_options = (PHP_SAPI === 'cli' && defined('JSON_PRETTY_PRINT')) ? 384 : 0; 141 | echo ($callback ? ($callback . '(') : '') . json_encode($json, $json_options) . ($callback ? ');' : '') . "\n"; 142 | 143 | if (isset($_GET['merge']) && $_GET['merge'] !== 'N'): ?> 144 | 145 | (function($) { 146 | 147 | if (typeof window.put_data_postcodify === "undefined") { 148 | window.put_data_postcodify = window.put_data; 149 | window.put_data = function(zip1, zip2, addr1, addr3, jibeon) { 150 | $(window.opener.document).find("input.postcodify_merged_zip").val(zip1 + (zip2.length > 2 ? "-" : "") + zip2); 151 | window.put_data_postcodify(zip1, zip2, addr1, addr3, jibeon); 152 | }; 153 | } 154 | 155 | var form = null; 156 | var form_match = window.location.search.match(/frm_name=([^&]+)/); 157 | if (form_match) { 158 | form = $("form[name='" + form_match[1] + "']", window.opener.document) 159 | } else { 160 | return; 161 | } 162 | 163 | var container = form.find("input[name$='_zip1']").parent(); 164 | if (container.size() < 1) { 165 | container = $(window.opener.document).find("input[name$='_zip1']").parent(); 166 | } 167 | if (container.find("input.postcodify_merged_zip").size() > 0) { 168 | return; 169 | } 170 | 171 | var old_zip1 = container.find("input[name$='_zip1']").hide(); 172 | var old_zip2 = container.find("input[name$='_zip2']").hide(); 173 | container.find("label[for$='_zip1']").remove(); 174 | container.find("label[for$='_zip2']").remove(); 175 | container.contents().filter(function() { return this.nodeType === 3 && $.trim(this.nodeValue) === '-'; }).remove(); 176 | 177 | var old_id = old_zip1.attr("id") ? old_zip1.attr("id") : "postcodify_replacement_zip1"; 178 | var new_id = old_id.replace("zip1", "zip0"); 179 | var new_zip = $(''); 180 | new_zip.addClass("postcodify_merged_zip").attr("readonly", "readonly"); 181 | new_zip.val(old_zip1.val() + (old_zip2.val().length > 2 ? "-" : "") + old_zip2.val()); 182 | new_zip.prependTo(container); 183 | var new_label = $('').prependTo(container); 184 | 185 | }(window.opener.jQuery)); 186 | 187 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | date_default_timezone_set('Asia/Seoul'); 23 | error_reporting(-1); 24 | require_once dirname(__FILE__) . '/../autoload.php'; 25 | 26 | // 광역시도 목록 검색인 경우 여기서 결과를 반환한다. 27 | 28 | if (isset($_GET['request']) && $_GET['request'] === 'addr1') 29 | { 30 | $sido = array_unique(Postcodify_Server_Areas::$sido); 31 | sort($sido); 32 | echo preg_replace('/[^a-zA-Z0-9_.]/', '', $_GET['callback']) . '(' . json_encode(array( 33 | 'result' => true, 34 | 'values' => $sido, 35 | )) . ');' . "\n"; 36 | exit; 37 | } 38 | 39 | // 시군구 목록 검색인 경우 세종시처럼 빈 문자열을 반환하여 곧장 키워드 입력 단계로 넘어가도록 한다. 40 | 41 | if (isset($_GET['request']) && $_GET['request'] === 'addr2') 42 | { 43 | echo preg_replace('/[^a-zA-Z0-9_.]/', '', $_GET['callback']) . '(' . json_encode(array( 44 | 'result' => true, 45 | 'values' => array(''), 46 | )) . ');' . "\n"; 47 | exit; 48 | } 49 | 50 | // GET 또는 터미널 파라미터로부터 검색 키워드를 조합한다. 51 | 52 | if (isset($_GET['search_addr1']) && strlen($_GET['search_addr1']) && isset($_GET['search_word']) && strlen($_GET['search_word'])) 53 | { 54 | $keywords = $_GET['search_addr1'] . ' ' . $_GET['search_word']; 55 | } 56 | elseif (isset($_GET['search_word']) && strlen($_GET['search_word'])) 57 | { 58 | $keywords = $_GET['search_word']; 59 | } 60 | elseif (isset($argv[1])) 61 | { 62 | $keywords = $argv[1]; 63 | } 64 | else 65 | { 66 | $keywords = ''; 67 | } 68 | 69 | // JSONP 콜백 함수명과 클라이언트 버전을 구한다. 70 | 71 | $callback = isset($_GET['callback']) ? $_GET['callback'] : null; 72 | $client_version = isset($_GET['v']) ? trim($_GET['v']) : POSTCODIFY_VERSION; 73 | if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) $keywords = stripslashes($keywords); 74 | if (preg_match('/[^a-zA-Z0-9_.]/', $callback)) $callback = null; 75 | 76 | // 검색을 수행한다. 77 | 78 | header('Content-Type: application/javascript; charset=UTF-8'); 79 | header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0'); 80 | header('Expires: Sat, 01 Jan 2000 00:00:00 GMT'); 81 | 82 | if (!isset($result) || !is_object($result)) 83 | { 84 | $server = new Postcodify_Server; 85 | $server->db_driver = POSTCODIFY_DB_DRIVER; 86 | $server->db_dbname = POSTCODIFY_DB_DBNAME; 87 | $server->db_host = POSTCODIFY_DB_HOST; 88 | $server->db_port = POSTCODIFY_DB_PORT; 89 | $server->db_user = POSTCODIFY_DB_USER; 90 | $server->db_pass = POSTCODIFY_DB_PASS; 91 | $result = $server->search($keywords, 'UTF-8'); 92 | } 93 | 94 | // 검색 결과를 XE KRZIP API와 같은 포맷으로 변환한다. 95 | 96 | if ($result->error) 97 | { 98 | $json = array('result' => false, 'msg' => "검색 서버와 통신 중 오류가 발생하였습니다.\n잠시 후 다시 시도해 주시기 바랍니다.\n\n" + $result->error); 99 | } 100 | elseif (!count($result->results)) 101 | { 102 | $json = array('result' => false, 'msg' => "검색 결과가 없습니다. 정확한 도로명주소 또는 지번주소로 검색해 주시기 바랍니다.\n\n" + $result->error); 103 | } 104 | else 105 | { 106 | $json = array('result' => true, 'values' => array('address' => array(), 'next' => -1)); 107 | foreach ($result->results as $entry) 108 | { 109 | $json['values']['address'][] = array( 110 | 'seq' => $entry->building_id, 111 | 'addr1' => $entry->ko_common, 112 | 'addr2_new' => $entry->ko_doro, 113 | 'addr2_old' => $entry->ko_jibeon, 114 | 'bdname' => $entry->building_name, 115 | 'zipcode' => $entry->postcode5, 116 | ); 117 | } 118 | } 119 | 120 | $json_options = (PHP_SAPI === 'cli' && defined('JSON_PRETTY_PRINT')) ? 384 : 0; 121 | echo ($callback ? ($callback . '(') : '') . json_encode($json, $json_options) . ($callback ? ');' : '') . "\n"; 122 | exit; 123 | -------------------------------------------------------------------------------- /lib/indexer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | ini_set('default_socket_timeout', -1); 23 | ini_set('display_errors', 'on'); 24 | ini_set('memory_limit', '1024M'); 25 | date_default_timezone_set('Asia/Seoul'); 26 | error_reporting(-1); 27 | if (function_exists('gc_enable')) gc_enable(); 28 | 29 | require dirname(__FILE__) . '/autoload.php'; 30 | 31 | // 명령줄에 주어진 옵션을 파악한다. 32 | 33 | if (PHP_SAPI !== 'cli') 34 | { 35 | Postcodify_Utility::print_usage_instructions(); 36 | } 37 | 38 | $args = Postcodify_Utility::get_terminal_args(); 39 | 40 | if ($args->command === null) 41 | { 42 | Postcodify_Utility::print_usage_instructions(); 43 | } 44 | if (in_array('--dry-run', $args->options)) 45 | { 46 | Postcodify_Utility::print_usage_instructions(); 47 | } 48 | 49 | // 필요한 클래스를 호출한다. 50 | 51 | $start_time = time(); 52 | $class_name = 'Postcodify_Indexer_' . ucfirst(str_replace('-', '_', $argv[1])); 53 | $obj = new $class_name(); 54 | $obj->start($args); 55 | 56 | // 소요된 시간을 출력한다. 57 | 58 | echo str_repeat('-', Postcodify_Utility::get_terminal_width()) . PHP_EOL; 59 | 60 | $elapsed = time() - $start_time; 61 | $elapsed_hours = floor($elapsed / 3600); 62 | $elapsed = $elapsed - ($elapsed_hours * 3600); 63 | $elapsed_minutes = floor($elapsed / 60); 64 | $elapsed_seconds = $elapsed % 60; 65 | 66 | echo '작업을 모두 마쳤습니다. 경과 시간 : '; 67 | if ($elapsed_hours) echo $elapsed_hours . '시간 '; 68 | if ($elapsed_hours || $elapsed_minutes) echo $elapsed_minutes . '분 '; 69 | echo $elapsed_seconds . '초'; 70 | echo PHP_EOL; 71 | exit(0); 72 | -------------------------------------------------------------------------------- /lib/resources/mysqldump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MYSQL_HOST="localhost" 4 | MYSQL_USER="" 5 | MYSQL_PASS="" 6 | MYSQL_DBNAME="" 7 | 8 | mysqldump --opt --order-by-primary \ 9 | --single-transaction --no-autocommit --skip-tz-utc \ 10 | -h$MYSQL_HOST -u$MYSQL_USER -p$MYSQL_PASS \ 11 | $MYSQL_DBNAME | gzip > dump.sql.gz 12 | -------------------------------------------------------------------------------- /lib/resources/romaja.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * All rights reserved. 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to 14 | * deal in the Software without restriction, including without limitation 15 | * the right to use, copy, modify, merge, publish, distribute, sublicense, 16 | * and/or sell copies of the Software, and to permit persons to whom the 17 | * Software is furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included 20 | * in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | class Hangeul_Romaja 32 | { 33 | // 설정을 위한 상수들. 34 | 35 | const TYPE_DEFAULT = 0; 36 | const TYPE_NAME = 1; 37 | const TYPE_ADDRESS = 2; 38 | const CAPITALIZE_NONE = 4; 39 | const CAPITALIZE_FIRST = 8; 40 | const CAPITALIZE_WORDS = 16; 41 | const PRONOUNCE_NUMBERS = 32; 42 | 43 | // 주어진 한글 단어 또는 문장을 로마자로 변환한다. 44 | 45 | public static function convert($str, $type = 0, $options = 0) 46 | { 47 | // 빈 문자열은 처리하지 않는다. 48 | 49 | if ($str === '') return ''; 50 | 51 | // 이름인 경우 별도 처리. 52 | 53 | if ($type === self::TYPE_NAME) 54 | { 55 | if (mb_strlen($str, 'UTF-8') > 2) 56 | { 57 | $possible_surname = mb_substr($str, 0, 2, 'UTF-8'); 58 | if (in_array($possible_surname, self::$long_surnames)) 59 | { 60 | $surname = $possible_surname; 61 | $firstname = mb_substr($str, 2, null, 'UTF-8'); 62 | } 63 | else 64 | { 65 | $surname = mb_substr($str, 0, 1, 'UTF-8'); 66 | $firstname = mb_substr($str, 1, null, 'UTF-8'); 67 | } 68 | } 69 | else 70 | { 71 | $surname = mb_substr($str, 0, 1, 'UTF-8'); 72 | $firstname = mb_substr($str, 1, null, 'UTF-8'); 73 | } 74 | $str = $surname . ' ' . $firstname; 75 | $options |= self::CAPITALIZE_WORDS; 76 | } 77 | 78 | // 주소인 경우 별도 처리. 79 | 80 | if ($type === self::TYPE_ADDRESS) 81 | { 82 | $str = implode(', ', array_reverse(preg_split('/\s+/', $str))); 83 | $str = preg_replace('/([동리])([0-9]+)가/u', '$1 $2가', $str); 84 | $str = preg_replace('/([0-9]+)가([0-9]+)동/u', ' $1가 $2동', $str); 85 | $str = preg_replace('/\b(.+[로길])(.+길)\b/u', '$1 $2', $str); 86 | $str = preg_replace_callback('/([0-9]+)?([시도군구읍면동리로길가])(?=$|,|\s)/u', array(__CLASS__, 'conv_address'), $str); 87 | $str = preg_replace('/([문산])로 ([0-9]+)-ga/u', '$1노 $2-ga', $str); 88 | $str = trim($str); 89 | $options |= self::CAPITALIZE_WORDS; 90 | } 91 | 92 | // 문자열을 한 글자씩 자른다. 93 | 94 | $chars = preg_split('//u', $str); 95 | 96 | // 각 글자를 초성, 중성, 종성으로 분리한다. 97 | 98 | $parts = array(); 99 | foreach ($chars as $char) 100 | { 101 | if ($char === '') 102 | { 103 | continue; 104 | } 105 | elseif (preg_match('/[가-힣]/u', $char)) 106 | { 107 | $char = hexdec(substr(json_encode($char), 3, 4)) - 44032; 108 | $part3 = intval($char % 28); 109 | $part2 = intval($char / 28) % 21; 110 | $part1 = intval($char / 28 / 21); 111 | $parts[] = array(1, $part1); 112 | $parts[] = array(2, $part2); 113 | $parts[] = array(3, $part3); 114 | } 115 | else 116 | { 117 | $parts[] = array(0, $char); 118 | } 119 | } 120 | 121 | // 각 문자를 처리한다. 122 | 123 | $parts_count = count($parts); 124 | $result = array(); 125 | for ($i = 0; $i < $parts_count; $i++) 126 | { 127 | $parttype = $parts[$i][0]; 128 | $part = $parts[$i][1]; 129 | 130 | switch ($parttype) 131 | { 132 | case 1: 133 | $result[] = self::$charmap1[$part]; 134 | break; 135 | 136 | case 2: 137 | $result[] = self::$charmap2[$part]; 138 | break; 139 | 140 | case 3: 141 | if ($i < $parts_count - 1 && $part > 0) 142 | { 143 | $nextpart = $parts[$i + 1]; 144 | if ($nextpart[0] === 1) 145 | { 146 | $newparts = self::transform($part, $nextpart[1], $parttype); 147 | $part = $newparts[0]; 148 | $parts[$i + 1][1] = $newparts[1]; 149 | } 150 | } 151 | $result[] = self::$charmap3[$part]; 152 | break; 153 | 154 | default: 155 | $result[] = $part; 156 | } 157 | } 158 | 159 | // 불필요한 공백이나 반복되는 글자를 제거한다. 160 | 161 | $result = implode('', $result); 162 | $result = str_replace(array('kkk', 'ttt', 'ppp'), array('kk', 'tt', 'pp'), $result); 163 | $result = preg_replace('/\s+/', ' ', $result); 164 | 165 | // 숫자 발음표현 처리를 거친다. 166 | 167 | if ($options & self::PRONOUNCE_NUMBERS) 168 | { 169 | if ($type === self::TYPE_ADDRESS) 170 | { 171 | $result = explode(', ', $result); 172 | foreach ($result as $i => $word) 173 | { 174 | if (preg_match('/[a-z]/i', $word)) 175 | { 176 | $result[$i] = preg_replace_callback('/[0-9]+/', array(__CLASS__, 'conv_number'), $word); 177 | } 178 | } 179 | $result = implode(', ', $result); 180 | } 181 | else 182 | { 183 | $result = preg_replace_callback('/[0-9]+/', array(__CLASS__, 'conv_number'), $result); 184 | } 185 | } 186 | 187 | // 대문자 처리를 거친다. 188 | 189 | if ($options & self::CAPITALIZE_WORDS) 190 | { 191 | $result = implode(' ', array_map('ucfirst', explode(' ', $result))); 192 | } 193 | elseif ($options & self::CAPITALIZE_FIRST) 194 | { 195 | $result = ucfirst($result); 196 | } 197 | 198 | // 결과를 반환한다. 199 | 200 | return $result; 201 | } 202 | 203 | // 주소를 처리한다. 204 | 205 | protected static function conv_address($matches) 206 | { 207 | if ($matches[1]) 208 | { 209 | return ' ' . $matches[1] . '-' . self::convert($matches[2]); 210 | } 211 | else 212 | { 213 | return '-' . self::convert($matches[2]); 214 | } 215 | } 216 | 217 | // 숫자 발음표현을 처리한다. 218 | 219 | protected static function conv_number($matches) 220 | { 221 | $number = strval(intval($matches[0])); 222 | $pronounced = ''; 223 | $largest_place = strlen($number) - 1; 224 | for ($i = 0; $i <= $largest_place; $i++) 225 | { 226 | $digit = self::$numbers_pronunciation['digits'][intval($number[$i])]; 227 | $place = self::$numbers_pronunciation['places'][$largest_place - $i]; 228 | if ($digit === '일' && $place !== '') $digit = ''; 229 | if ($digit !== '영') $pronounced .= ($digit . $place); 230 | } 231 | $pronounced = self::convert($pronounced); 232 | return "$number($pronounced)"; 233 | } 234 | 235 | // 자음 동화를 처리한다. 236 | 237 | protected static function transform($part, $nextpart, $type) 238 | { 239 | $key = self::$ordmap3[$part] . self::$ordmap1[$nextpart]; 240 | if (isset(self::$transforms_always[$key])) 241 | { 242 | $resultkey = str_replace(' ', ' ', self::$transforms_always[$key]); 243 | $result = array( 244 | intval(array_search(substr($resultkey, 0, 3), self::$ordmap3)), 245 | intval(array_search(substr($resultkey, 3, 3), self::$ordmap1)), 246 | ); 247 | } 248 | elseif ($type !== self::TYPE_ADDRESS && isset(self::$transforms_non_address[$key])) 249 | { 250 | $resultkey = str_replace(' ', ' ', self::$transforms_non_address[$key]); 251 | $result = array( 252 | intval(array_search(substr($resultkey, 0, 3), self::$ordmap3)), 253 | intval(array_search(substr($resultkey, 3, 3), self::$ordmap1)), 254 | ); 255 | } 256 | else 257 | { 258 | $result = array($part, $nextpart); 259 | } 260 | 261 | if ($result[0] == 8 && $result[1] == 5) 262 | { 263 | $result[1] = 19; 264 | } 265 | return $result; 266 | } 267 | 268 | // 숫자 발음표현 목록. 269 | 270 | protected static $numbers_pronunciation = array( 271 | 'digits' => array('영', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구'), 272 | 'places' => array('', '십', '백', '천', '만'), 273 | ); 274 | 275 | // 자음 동화 목록 (항상 적용). 276 | 277 | protected static $transforms_always = array( 278 | 'ㄱㄴ' => 'ㅇㄴ', 279 | 'ㄱㄹ' => 'ㅇㄴ', 280 | 'ㄱㅁ' => 'ㅇㅁ', 281 | 'ㄱㅇ' => ' ㄱ', 282 | 'ㄲㄴ' => 'ㅇㄴ', 283 | 'ㄲㄹ' => 'ㅇㄴ', 284 | 'ㄲㅁ' => 'ㅇㅁ', 285 | 'ㄲㅇ' => ' ㄲ', 286 | 'ㄳㅇ' => 'ㄱㅅ', 287 | 'ㄴㄹ' => 'ㄹㄹ', 288 | 'ㄴㅋ' => 'ㅇㅋ', 289 | 'ㄵㄱ' => 'ㄴㄲ', 290 | 'ㄵㄷ' => 'ㄴㄸ', 291 | 'ㄵㄹ' => 'ㄹㄹ', 292 | 'ㄵㅂ' => 'ㄴㅃ', 293 | 'ㄵㅅ' => 'ㄴㅆ', 294 | 'ㄵㅇ' => 'ㄴㅈ', 295 | 'ㄵㅈ' => 'ㄴㅉ', 296 | 'ㄵㅋ' => 'ㅇㅋ', 297 | 'ㄵㅎ' => 'ㄴㅊ', 298 | 'ㄶㄱ' => 'ㄴㅋ', 299 | 'ㄶㄷ' => 'ㄴㅌ', 300 | 'ㄶㄹ' => 'ㄹㄹ', 301 | 'ㄶㅂ' => 'ㄴㅍ', 302 | 'ㄶㅈ' => 'ㄴㅊ', 303 | 'ㄷㄴ' => 'ㄴㄴ', 304 | 'ㄷㄹ' => 'ㄴㄴ', 305 | 'ㄷㅁ' => 'ㅁㅁ', 306 | 'ㄷㅂ' => 'ㅂㅂ', 307 | 'ㄷㅇ' => ' ㄷ', 308 | 'ㄹㄴ' => 'ㄹㄹ', 309 | 'ㄹㅇ' => ' ㄹ', 310 | 'ㄺㄴ' => 'ㄹㄹ', 311 | 'ㄺㅇ' => 'ㄹㄱ', 312 | 'ㄻㄴ' => 'ㅁㄴ', 313 | 'ㄻㅇ' => 'ㄹㅁ', 314 | 'ㄼㄴ' => 'ㅁㄴ', 315 | 'ㄼㅇ' => 'ㄹㅂ', 316 | 'ㄽㄴ' => 'ㄴㄴ', 317 | 'ㄽㅇ' => 'ㄹㅅ', 318 | 'ㄾㄴ' => 'ㄷㄴ', 319 | 'ㄾㅇ' => 'ㄹㅌ', 320 | 'ㄿㄴ' => 'ㅁㄴ', 321 | 'ㄿㅇ' => 'ㄹㅍ', 322 | 'ㅀㄴ' => 'ㄴㄴ', 323 | 'ㅀㅇ' => ' ㄹ', 324 | 'ㅁㄹ' => 'ㅁㄴ', 325 | 'ㅂㄴ' => 'ㅁㄴ', 326 | 'ㅂㄹ' => 'ㅁㄴ', 327 | 'ㅂㅁ' => 'ㅁㅁ', 328 | 'ㅂㅇ' => ' ㅂ', 329 | 'ㅄㄴ' => 'ㅁㄴ', 330 | 'ㅄㄹ' => 'ㅁㄴ', 331 | 'ㅄㄹ' => 'ㅁㄴ', 332 | 'ㅄㅁ' => 'ㅁㅁ', 333 | 'ㅄㅇ' => 'ㅂㅅ', 334 | 'ㅅㄴ' => 'ㄴㄴ', 335 | 'ㅅㄹ' => 'ㄴㄴ', 336 | 'ㅅㅁ' => 'ㅁㅁ', 337 | 'ㅅㅂ' => 'ㅂㅂ', 338 | 'ㅅㅇ' => ' ㅅ', 339 | 'ㅆㄴ' => 'ㄴㄴ', 340 | 'ㅆㄹ' => 'ㄴㄴ', 341 | 'ㅆㅁ' => 'ㅁㅁ', 342 | 'ㅆㅂ' => 'ㅂㅂ', 343 | 'ㅆㅇ' => ' ㅆ', 344 | 'ㅇㄹ' => 'ㅇㄴ', 345 | 'ㅈㅇ' => ' ㅈ', 346 | 'ㅊㅇ' => ' ㅊ', 347 | 'ㅋㅇ' => ' ㅋ', 348 | 'ㅌㅇ' => ' ㅌ', 349 | 'ㅍㅇ' => ' ㅍ', 350 | ); 351 | 352 | // 자음 동화 목록 (주소에서는 적용하지 않음). 353 | 354 | protected static $transforms_non_address = array( 355 | 'ㄴㄱ' => 'ㅇㄱ', 356 | 'ㄴㅁ' => 'ㅁㅁ', 357 | 'ㄴㅂ' => 'ㅁㅂ', 358 | 'ㄴㅍ' => 'ㅁㅍ', 359 | ); 360 | 361 | // 두 글자짜리 성씨 목록. 362 | 363 | protected static $long_surnames = array( 364 | '남궁', 365 | '독고', 366 | '동방', 367 | '사공', 368 | '서문', 369 | '선우', 370 | '소봉', 371 | '제갈', 372 | '황보', 373 | ); 374 | 375 | // 초성 목록 (번역표). 376 | 377 | protected static $charmap1 = array( 378 | 'g', // ㄱ 379 | 'kk', // ㄲ 380 | 'n', // ㄴ 381 | 'd', // ㄷ 382 | 'tt', // ㄸ 383 | 'r', // ㄹ 384 | 'm', // ㅁ 385 | 'b', // ㅂ 386 | 'pp', // ㅃ 387 | 's', // ㅅ 388 | 'ss', // ㅆ 389 | '', // ㅇ 390 | 'j', // ㅈ 391 | 'jj', // ㅉ 392 | 'ch', // ㅊ 393 | 'k', // ㅋ 394 | 't', // ㅌ 395 | 'p', // ㅍ 396 | 'h', // ㅎ 397 | 'l', // ㄹㄹ 398 | ); 399 | 400 | // 중성 목록 (번역표). 401 | 402 | protected static $charmap2 = array( 403 | 'a', // ㅏ 404 | 'ae', // ㅐ 405 | 'ya', // ㅑ 406 | 'yae', // ㅒ 407 | 'eo', // ㅓ 408 | 'e', // ㅔ 409 | 'yeo', // ㅕ 410 | 'ye', // ㅖ 411 | 'o', // ㅗ 412 | 'wa', // ㅘ 413 | 'wae', // ㅙ 414 | 'oe', // ㅚ 415 | 'yo', // ㅛ 416 | 'u', // ㅜ 417 | 'wo', // ㅝ 418 | 'we', // ㅞ 419 | 'wi', // ㅟ 420 | 'yu', // ㅠ 421 | 'eu', // ㅡ 422 | 'ui', // ㅢ 423 | 'i', // ㅣ 424 | ); 425 | 426 | // 종성 목록 (번역표). 427 | 428 | protected static $charmap3 = array( 429 | '', // 받침이 없는 경우 430 | 'k', // ㄱ 431 | 'k', // ㄲ 432 | 'k', // ㄳ 433 | 'n', // ㄴ 434 | 'n', // ㄵ 435 | 'n', // ㄶ 436 | 't', // ㄷ 437 | 'l', // ㄹ 438 | 'k', // ㄺ 439 | 'm', // ㄻ 440 | 'p', // ㄼ 441 | 't', // ㄽ 442 | 't', // ㄾ 443 | 'p', // ㄿ 444 | 'l', // ㅀ 445 | 'm', // ㅁ 446 | 'p', // ㅂ 447 | 'p', // ㅄ 448 | 't', // ㅅ 449 | 't', // ㅆ 450 | 'ng', // ㅇ 451 | 't', // ㅈ 452 | 't', // ㅊ 453 | 'k', // ㅋ 454 | 't', // ㅌ 455 | 'p', // ㅍ 456 | '', // ㅎ 457 | ); 458 | 459 | // 초성 목록 (순서표). 460 | 461 | protected static $ordmap1 = array( 462 | 'ㄱ', 463 | 'ㄲ', 464 | 'ㄴ', 465 | 'ㄷ', 466 | 'ㄸ', 467 | 'ㄹ', 468 | 'ㅁ', 469 | 'ㅂ', 470 | 'ㅃ', 471 | 'ㅅ', 472 | 'ㅆ', 473 | 'ㅇ', 474 | 'ㅈ', 475 | 'ㅉ', 476 | 'ㅊ', 477 | 'ㅋ', 478 | 'ㅌ', 479 | 'ㅍ', 480 | 'ㅎ', 481 | ); 482 | 483 | // 중성 목록 (순서표). 484 | 485 | protected static $ordmap2 = array( 486 | 'ㅏ', 487 | 'ㅐ', 488 | 'ㅑ', 489 | 'ㅒ', 490 | 'ㅓ', 491 | 'ㅔ', 492 | 'ㅕ', 493 | 'ㅖ', 494 | 'ㅗ', 495 | 'ㅘ', 496 | 'ㅙ', 497 | 'ㅚ', 498 | 'ㅛ', 499 | 'ㅜ', 500 | 'ㅝ', 501 | 'ㅞ', 502 | 'ㅟ', 503 | 'ㅠ', 504 | 'ㅡ', 505 | 'ㅢ', 506 | 'ㅣ', 507 | ); 508 | 509 | // 종성 목록 (순서표). 510 | 511 | protected static $ordmap3 = array( 512 | ' ', // 받침이 없는 경우 513 | 'ㄱ', 514 | 'ㄲ', 515 | 'ㄳ', 516 | 'ㄴ', 517 | 'ㄵ', 518 | 'ㄶ', 519 | 'ㄷ', 520 | 'ㄹ', 521 | 'ㄺ', 522 | 'ㄻ', 523 | 'ㄼ', 524 | 'ㄽ', 525 | 'ㄾ', 526 | 'ㄿ', 527 | 'ㅀ', 528 | 'ㅁ', 529 | 'ㅂ', 530 | 'ㅄ', 531 | 'ㅅ', 532 | 'ㅆ', 533 | 'ㅇ', 534 | 'ㅈ', 535 | 'ㅊ', 536 | 'ㅋ', 537 | 'ㅌ', 538 | 'ㅍ', 539 | 'ㅎ', 540 | ); 541 | } 542 | -------------------------------------------------------------------------------- /lib/resources/schema.php: -------------------------------------------------------------------------------- 1 | array( 8 | 'road_id' => 'NUMERIC(14) PRIMARY KEY', 9 | 'road_name_ko' => 'VARCHAR(80)', 10 | 'road_name_en' => 'VARCHAR(120)', 11 | 'sido_ko' => 'VARCHAR(20)', 12 | 'sido_en' => 'VARCHAR(40)', 13 | 'sigungu_ko' => 'VARCHAR(20)', 14 | 'sigungu_en' => 'VARCHAR(40)', 15 | 'ilbangu_ko' => 'VARCHAR(20)', 16 | 'ilbangu_en' => 'VARCHAR(40)', 17 | 'eupmyeon_ko' => 'VARCHAR(20)', 18 | 'eupmyeon_en' => 'VARCHAR(40)', 19 | 'updated' => 'NUMERIC(8)', 20 | 'deleted' => 'NUMERIC(8)', 21 | '_indexes' => array('sido_ko', 'sigungu_ko', 'ilbangu_ko', 'eupmyeon_ko'), 22 | '_count' => 320000, 23 | ), 24 | 25 | // 주소 정보 테이블. 26 | 27 | 'postcodify_addresses' => array( 28 | 'id' => 'INT PRIMARY KEY AUTO_INCREMENT', 29 | 'postcode5' => 'CHAR(5)', 30 | 'postcode6' => 'CHAR(6)', 31 | 'road_id' => 'NUMERIC(14)', 32 | 'num_major' => 'SMALLINT(5) UNSIGNED', 33 | 'num_minor' => 'SMALLINT(5) UNSIGNED', 34 | 'is_basement' => 'TINYINT(1) DEFAULT 0', 35 | 'dongri_id' => 'NUMERIC(10)', 36 | 'dongri_ko' => 'VARCHAR(80)', 37 | 'dongri_en' => 'VARCHAR(80)', 38 | 'jibeon_major' => 'SMALLINT(5) UNSIGNED', 39 | 'jibeon_minor' => 'SMALLINT(5) UNSIGNED', 40 | 'is_mountain' => 'TINYINT(1) DEFAULT 0', 41 | 'building_id' => 'NUMERIC(25)', 42 | 'building_name' => 'VARCHAR(80)', 43 | 'building_nums' => 'VARCHAR(240)', 44 | 'other_addresses' => 'TEXT', 45 | 'updated' => 'NUMERIC(8)', 46 | 'deleted' => 'NUMERIC(8)', 47 | '_interim' => array('road_id', 'num_major', 'num_minor'), 48 | '_indexes' => array('postcode5', 'postcode6', 'dongri_id'), 49 | '_count' => 5800000, 50 | ), 51 | 52 | // 한글 검색 키워드 테이블. 53 | 54 | 'postcodify_keywords' => array( 55 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 56 | 'address_id' => 'INT NOT NULL', 57 | 'keyword_crc32' => 'INT UNSIGNED', 58 | '_indexes' => array('address_id', 'keyword_crc32'), 59 | '_count' => 21000000, 60 | ), 61 | 62 | // 지번 및 건물번호 검색 테이블. 63 | 64 | 'postcodify_numbers' => array( 65 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 66 | 'address_id' => 'INT NOT NULL', 67 | 'num_major' => 'SMALLINT(5) UNSIGNED', 68 | 'num_minor' => 'SMALLINT(5) UNSIGNED', 69 | '_indexes' => array('address_id', 'num_major', 'num_minor'), 70 | '_count' => 12000000, 71 | ), 72 | 73 | // 건물명 검색 키워드 테이블. 74 | 75 | 'postcodify_buildings' => array( 76 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 77 | 'address_id' => 'INT NOT NULL', 78 | 'keyword' => 'VARCHAR(5000)', 79 | '_indexes' => array('address_id'), 80 | '_count' => 600000, 81 | ), 82 | 83 | // 사서함 검색 키워드 테이블. 84 | 85 | 'postcodify_pobox' => array( 86 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 87 | 'address_id' => 'INT NOT NULL', 88 | 'keyword' => 'VARCHAR(40)', 89 | 'range_start_major' => 'SMALLINT(5) UNSIGNED', 90 | 'range_start_minor' => 'SMALLINT(5) UNSIGNED', 91 | 'range_end_major' => 'SMALLINT(5) UNSIGNED', 92 | 'range_end_minor' => 'SMALLINT(5) UNSIGNED', 93 | '_indexes' => array('address_id', 'range_start_major', 'range_start_minor', 'range_end_major', 'range_end_minor'), 94 | '_count' => 2000, 95 | ), 96 | 97 | // 영문 검색 키워드 테이블. 98 | 99 | 'postcodify_english' => array( 100 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 101 | 'ko' => 'VARCHAR(40)', 102 | 'ko_crc32' => 'INT UNSIGNED', 103 | 'en' => 'VARCHAR(40)', 104 | 'en_crc32' => 'INT UNSIGNED', 105 | '_indexes' => array('ko', 'ko_crc32', 'en', 'en_crc32'), 106 | '_count' => 130000, 107 | ), 108 | 109 | // 우편번호 범위 테이블 (도로명주소). 110 | 111 | 'postcodify_ranges_roads' => array( 112 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 113 | 'sido_ko' => 'VARCHAR(40)', 114 | 'sido_en' => 'VARCHAR(40)', 115 | 'sigungu_ko' => 'VARCHAR(40)', 116 | 'sigungu_en' => 'VARCHAR(40)', 117 | 'ilbangu_ko' => 'VARCHAR(40)', 118 | 'ilbangu_en' => 'VARCHAR(40)', 119 | 'eupmyeon_ko' => 'VARCHAR(40)', 120 | 'eupmyeon_en' => 'VARCHAR(40)', 121 | 'road_name_ko' => 'VARCHAR(80)', 122 | 'road_name_en' => 'VARCHAR(80)', 123 | 'range_start_major' => 'SMALLINT(5) UNSIGNED', 124 | 'range_start_minor' => 'SMALLINT(5) UNSIGNED', 125 | 'range_end_major' => 'SMALLINT(5) UNSIGNED', 126 | 'range_end_minor' => 'SMALLINT(5) UNSIGNED', 127 | 'range_type' => 'TINYINT(1) DEFAULT 0', 128 | 'is_basement' => 'TINYINT(1) DEFAULT 0', 129 | 'postcode5' => 'CHAR(5)', 130 | '_initial' => array('sido_ko', 'sigungu_ko', 'ilbangu_ko', 'eupmyeon_ko', 'road_name_ko', 'range_start_major', 'range_start_minor', 'range_end_major', 'range_end_minor', 'range_type', 'postcode5'), 131 | ), 132 | 133 | // 우편번호 범위 테이블 (지번주소). 134 | 135 | 'postcodify_ranges_jibeon' => array( 136 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 137 | 'sido_ko' => 'VARCHAR(40)', 138 | 'sido_en' => 'VARCHAR(40)', 139 | 'sigungu_ko' => 'VARCHAR(40)', 140 | 'sigungu_en' => 'VARCHAR(40)', 141 | 'ilbangu_ko' => 'VARCHAR(40)', 142 | 'ilbangu_en' => 'VARCHAR(40)', 143 | 'eupmyeon_ko' => 'VARCHAR(40)', 144 | 'eupmyeon_en' => 'VARCHAR(40)', 145 | 'dongri_ko' => 'VARCHAR(80)', 146 | 'dongri_en' => 'VARCHAR(80)', 147 | 'range_start_major' => 'SMALLINT(5) UNSIGNED', 148 | 'range_start_minor' => 'SMALLINT(5) UNSIGNED', 149 | 'range_end_major' => 'SMALLINT(5) UNSIGNED', 150 | 'range_end_minor' => 'SMALLINT(5) UNSIGNED', 151 | 'is_mountain' => 'TINYINT(1) DEFAULT 0', 152 | 'admin_dongri' => 'VARCHAR(80)', 153 | 'postcode5' => 'CHAR(5)', 154 | '_initial' => array('sido_ko', 'sigungu_ko', 'ilbangu_ko', 'eupmyeon_ko', 'dongri_ko', 'range_start_major', 'range_start_minor', 'range_end_major', 'range_end_minor', 'admin_dongri', 'postcode5'), 155 | ), 156 | 157 | // 우편번호 범위 테이블 (구 우편번호). 158 | 159 | 'postcodify_ranges_oldcode' => array( 160 | 'seq' => 'INT PRIMARY KEY AUTO_INCREMENT', 161 | 'sido_ko' => 'VARCHAR(40)', 162 | 'sido_en' => 'VARCHAR(40)', 163 | 'sigungu_ko' => 'VARCHAR(40)', 164 | 'sigungu_en' => 'VARCHAR(40)', 165 | 'ilbangu_ko' => 'VARCHAR(40)', 166 | 'ilbangu_en' => 'VARCHAR(40)', 167 | 'eupmyeon_ko' => 'VARCHAR(40)', 168 | 'eupmyeon_en' => 'VARCHAR(40)', 169 | 'dongri_ko' => 'VARCHAR(80)', 170 | 'dongri_en' => 'VARCHAR(80)', 171 | 'range_start_major' => 'SMALLINT(5) UNSIGNED', 172 | 'range_start_minor' => 'SMALLINT(5) UNSIGNED', 173 | 'range_end_major' => 'SMALLINT(5) UNSIGNED', 174 | 'range_end_minor' => 'SMALLINT(5) UNSIGNED', 175 | 'is_mountain' => 'TINYINT(1) DEFAULT 0', 176 | 'island_name' => 'VARCHAR(80)', 177 | 'building_name' => 'VARCHAR(80)', 178 | 'building_num_start' => 'SMALLINT(5) UNSIGNED', 179 | 'building_num_end' => 'SMALLINT(5) UNSIGNED', 180 | 'postcode6' => 'CHAR(6)', 181 | '_initial' => array('sido_ko', 'sigungu_ko', 'ilbangu_ko', 'eupmyeon_ko', 'dongri_ko', 'range_start_major', 'range_start_minor', 'range_end_major', 'range_end_minor', 'postcode6'), 182 | ), 183 | 184 | // 각종 설정을 저장하는 테이블. 185 | 186 | 'postcodify_settings' => array( 187 | 'k' => 'VARCHAR(20) PRIMARY KEY', 188 | 'v' => 'VARCHAR(40)', 189 | '_indexes' => array('k'), 190 | ), 191 | 192 | ); 193 | -------------------------------------------------------------------------------- /lib/search.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * 이 프로그램은 자유 소프트웨어입니다. 이 소프트웨어의 피양도자는 자유 9 | * 소프트웨어 재단이 공표한 GNU 약소 일반 공중 사용 허가서 (GNU LGPL) 제3판 10 | * 또는 그 이후의 판을 임의로 선택하여, 그 규정에 따라 이 프로그램을 11 | * 개작하거나 재배포할 수 있습니다. 12 | * 13 | * 이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만, 14 | * 특정한 목적에 맞는 적합성 여부나 판매용으로 사용할 수 있으리라는 묵시적인 15 | * 보증을 포함한 어떠한 형태의 보증도 제공하지 않습니다. 보다 자세한 사항에 16 | * 대해서는 GNU 약소 일반 공중 사용 허가서를 참고하시기 바랍니다. 17 | * 18 | * GNU 약소 일반 공중 사용 허가서는 이 프로그램과 함께 제공됩니다. 19 | * 만약 허가서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다. 20 | */ 21 | 22 | date_default_timezone_set('Asia/Seoul'); 23 | error_reporting(-1); 24 | require_once dirname(__FILE__) . '/autoload.php'; 25 | 26 | // 검색 키워드, JSONP 콜백 함수명, 클라이언트 버전을 구한다. 27 | 28 | $keywords = isset($_GET['q']) ? trim($_GET['q']) : (isset($argv[1]) ? trim($argv[1], ' "\'') : ''); 29 | $callback = isset($_GET['callback']) ? $_GET['callback'] : null; 30 | $client_version = isset($_GET['v']) ? trim($_GET['v']) : POSTCODIFY_VERSION; 31 | if (function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) $keywords = stripslashes($keywords); 32 | if (preg_match('/[^a-zA-Z0-9_.]/', $callback ?: '')) $callback = null; 33 | 34 | // 검색 서버를 설정한다. 35 | 36 | $server = new Postcodify_Server; 37 | $server->db_driver = POSTCODIFY_DB_DRIVER; 38 | $server->db_dbname = POSTCODIFY_DB_DBNAME; 39 | $server->db_host = POSTCODIFY_DB_HOST; 40 | $server->db_port = POSTCODIFY_DB_PORT; 41 | $server->db_user = POSTCODIFY_DB_USER; 42 | $server->db_pass = POSTCODIFY_DB_PASS; 43 | $server->cache_driver = defined('POSTCODIFY_CACHE_DRIVER') ? POSTCODIFY_CACHE_DRIVER : ''; 44 | $server->cache_host = defined('POSTCODIFY_CACHE_HOST') ? POSTCODIFY_CACHE_HOST : 'localhost'; 45 | $server->cache_port = defined('POSTCODIFY_CACHE_PORT') ? POSTCODIFY_CACHE_PORT : 11211; 46 | $server->cache_ttl = defined('POSTCODIFY_CACHE_TTL') ? POSTCODIFY_CACHE_TTL : 86400; 47 | 48 | // 검색을 수행하고 결과를 전송한다. 49 | 50 | header('Content-Type: application/javascript; charset=UTF-8'); 51 | header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0'); 52 | header('Expires: Sat, 01 Jan 2000 00:00:00 GMT'); 53 | 54 | $result = $server->search($keywords, 'UTF-8', $client_version); 55 | $json_options = (PHP_SAPI === 'cli' && defined('JSON_PRETTY_PRINT')) ? 384 : 0; 56 | echo ($callback ? ($callback . '(') : '') . json_encode($result, $json_options) . ($callback ? ');' : '') . "\n"; 57 | exit; 58 | --------------------------------------------------------------------------------