เกือบทุกระบบที่มีฟอร์มที่อยู่ในไทยต้องเจอคำถามเดียวกัน: จะเอาข้อมูลจังหวัด อำเภอ ตำบล มาจากไหน จะเก็บในฐานข้อมูลอย่างไรให้ไม่พังเมื่อมีคนสะกดต่างกัน และจะดูแลอย่างไรเมื่อมีการตั้งอำเภอใหม่ หลายทีมจบด้วยการคัดลอก JSON จาก GitHub ที่หยุดอัปเดตไปหลายปีแล้วแก้เองทีละจุด บทความนี้อธิบายว่ารหัสเขตปกครองของไทยมีโครงสร้างอย่างไร ข้อมูลชุดนี้มาจากไหน วิธีทำ dropdown สามชั้นที่โหลดทีละชั้น และจุดที่ข้อมูลไม่ตรงกับความคาดหวังซึ่งควรรู้ก่อนขึ้นระบบจริง
ตัวเลขที่ควรจำ
ไทยมี 77 จังหวัด (นับกรุงเทพมหานคร) 930 อำเภอ/เขต และ 7,436 ตำบล/แขวง ตัวเลขนี้เปลี่ยนปีละไม่กี่ครั้งเมื่อมีการตั้งเขตปกครองใหม่หรือแก้สะกด ชุดข้อมูลที่ดีจึงต้องบอกวันที่อัปเดต (updatedAt) เพื่อให้รู้ว่าสำเนาที่ bundle ไว้ในแอปเก่าแค่ไหน
รหัสกรมการปกครอง: เก็บรหัส ไม่ใช่ชื่อ
ความผิดพลาดที่พบบ่อยที่สุดคือเก็บชื่ออำเภอเป็นข้อความในฐานข้อมูล พอมีการแก้สะกด หรือคนหนึ่งพิมพ์ "เขตบางกะปิ" อีกคนพิมพ์ "บางกะปิ" ข้อมูลก็ join กันไม่ได้ และรายงานก็นับซ้ำ ทางที่ดีคือเก็บ รหัสกรมการปกครอง ซึ่งเป็นรหัสที่ใช้ในทะเบียนราษฎรและชุดข้อมูลของรัฐ
รหัสนี้ซ้อนกันเป็นลำดับชั้น
| ระดับ | จำนวนหลัก | ตัวอย่าง |
|---|---|---|
| จังหวัด | 2 | กรุงเทพมหานคร = 10, เชียงใหม่ = 50 |
| อำเภอ/เขต | 4 (ขึ้นต้นด้วยรหัสจังหวัด) | เขตบางกะปิ = 1006, อำเภอเมืองเชียงใหม่ = 5001 |
| ตำบล/แขวง | 6 (ขึ้นต้นด้วยรหัสอำเภอ) | แขวงคลองจั่น = 100601, ตำบลศรีภูมิ = 500101 |
เพราะรหัสซ้อนกัน การ query จึงง่ายมาก: ตำบลทั้งหมดในจังหวัด 50 คือรหัสที่ขึ้นต้นด้วย 50 และอำเภอของตำบล 100601 คือ 4 หลักแรก 1006 โดยไม่ต้อง join ตารางใดเลย
ข้อมูลมาจากไหน
ชุดข้อมูลนี้แปลงมาจาก kongvut/thai-province-data (สัญญาอนุญาต MIT) โดยแปลง id ภายในของชุดนั้นเป็นรหัสกรมการปกครอง และตรวจความครบว่าได้ 77 / 930 / 7,436 พอดี เมื่อต้นทางอัปเดตตามการตั้งอำเภอหรือตำบลใหม่ เราอัปเดตตามและบันทึกใน changelog ของเว็บ เวอร์ชันที่ใช้อยู่ระบุใน updatedAt ของทุก response
ข้อมูลเสริมที่มีในชุด
- พิกัดกึ่งกลางตำบล 7,123 จาก 7,436 ตำบลมี
lat/lngทศนิยม 4 ตำแหน่ง (ความละเอียดราว 11 เมตร) ใช้ปักหมุดระดับตำบลได้ แต่ไม่ใช่พิกัดบ้าน - แขวงใน กทม. ทั้ง 180 แห่งยังไม่มีพิกัด ในชุดข้อมูลต้นทาง API จะคืน
centerกลางเมืองแทน - เส้นขอบอำเภอ 917 จาก 930 แห่ง จาก geoBoundaries ปี 2019 (CC BY 4.0, ย่อแล้ว) ขอผ่าน API ด้วย
geometry=1อำเภอที่ตั้งใหม่หลังจากนั้นและเขตวัฒนาไม่มีเส้นขอบ
Dropdown สามชั้นที่โหลดทีละชั้น
ไม่จำเป็นต้องโหลดตำบลทั้ง 7,436 แห่งตอนเปิดหน้า ให้โหลดจังหวัดครั้งเดียว เลือกแล้วค่อยขออำเภอ แล้วค่อยขอตำบล รหัสไปรษณีย์มากับตำบลอยู่แล้ว API ส่ง CORS header จึงเรียกจาก JavaScript บนเว็บไซต์ของคุณได้โดยตรง
const api = "https://reefsight.tech/api/v1/tools/thai/address";
const {data: {provinces}} = await fetch(`${api}/provinces`).then((r) => r.json());
// provinces[0] → {code: "10", nameTh: "กรุงเทพมหานคร", nameEn: "Bangkok"}
// ผู้ใช้เลือกจังหวัด 10
const {data: {districts}} = await fetch(`${api}/districts?province=10`).then((r) => r.json());
// 50 เขต แต่ละรายการมี prefixTh "เขต" และ prefixEn "Khet"
// ผู้ใช้เลือกเขต 1006
const {data: {subdistricts}} = await fetch(`${api}/subdistricts?district=1006`).then((r) => r.json());
// subdistricts[0] → {code: "100601", nameTh: "คลองจั่น", nameEn: "Khlong Chan", prefixTh: "แขวง", postcode: "10240"}API รับทั้งรหัสและชื่อ province=Chiang Mai หรือ district=เขตบางกะปิ ก็ได้ โดยตัดคำว่า เขต/อำเภอ ให้เอง และไม่สนตัวพิมพ์ใหญ่เล็กหรือช่องว่างในชื่ออังกฤษ ถ้าชื่ออำเภอซ้ำหลายจังหวัด เช่น "เฉลิมพระเกียรติ" ที่มีใน 5 จังหวัด จะตอบ 422 พร้อมรหัสทั้งหมด (1913, 3032, 3123, 5515, 8023) ให้เลือก ส่วนชื่อจังหวัดที่ไม่มีจริงอย่าง Atlantis ได้ 422 บอกให้ใช้รหัสหรือชื่อไทย/อังกฤษ
ค้นหาช่องเดียว
ถ้าอยากให้ผู้ใช้กรอกน้อยกว่าสามชั้น ใช้ /search?q= ช่องเดียวแล้วให้เลือกจากผลลัพธ์
curl "https://reefsight.tech/api/v1/tools/thai/address/search?q=บางกะปิ"{
"data": {
"q": "บางกะปิ",
"total": 2,
"areas": [
{"level": "district", "label": "เขตบางกะปิ กรุงเทพมหานคร", "district": {"code": "1006"}},
{"level": "subdistrict", "label": "แขวงบางกะปิ เขตห้วยขวาง กรุงเทพมหานคร 10310", "postcode": "10310"}
]
}
}ตัวอย่างนี้แสดงจุดที่คนมักสับสน: "บางกะปิ" เป็นทั้งชื่อเขต (รหัส 1006) และชื่อแขวงในเขตห้วยขวาง (รหัส 101702) ซึ่งรหัสไปรษณีย์ต่างกัน การค้นหาคืนทั้งสองรายการพร้อม level และ label เต็มเพื่อให้ผู้ใช้เลือกถูก ค้นภาษาอังกฤษแบบมีช่องว่าง bang kapi ก็เจอ และถ้าพิมพ์เลข 5 หลัก เช่น 50200 จะถือเป็นรหัสไปรษณีย์ให้เอง q ต้องยาวอย่างน้อย 2 ตัวอักษร และ limit สูงสุด 50
ค้นจากรหัสไปรษณีย์
curl "https://reefsight.tech/api/v1/tools/thai/address/postcode/10240"ได้ทุกแขวงที่ใช้รหัสนี้ (คลองจั่นและหัวหมาก ในเขตบางกะปิ) พร้อม label ที่ประกอบคำนำหน้าให้แล้ว รับเลขไทย ๕๐๒๐๐ ได้ รหัสที่ไม่มีใครใช้อย่าง 99999 ตอบ 404 และรหัสไม่ครบ 5 หลักตอบ 422
รหัสไปรษณีย์ไม่ได้ 1:1 กับตำบล
รหัสไปรษณีย์หนึ่งค่าครอบหลายตำบลได้ (10240 มีทั้งคลองจั่นและหัวหมาก) และตำบลบางแห่งไปรษณีย์ไทยแบ่งเป็นหลายรหัสตามเส้นทางนำจ่าย ชุดข้อมูลนี้ให้ หนึ่งรหัสหลักต่อตำบล ซึ่งเพียงพอสำหรับฟอร์มทั่วไป แต่ถ้างานต้องแม่นระดับหมู่บ้าน เช่น ระบบจัดส่งที่คิดค่าบริการตามรหัส ให้เช็กกับไปรษณีย์ไทยอีกชั้น
เรื่องกรุงเทพฯ อีกข้อที่มักพลาด: กทม. ใช้ เขต/แขวง ไม่ใช่ อำเภอ/ตำบล ชื่อฟิลด์ใน API ยังเป็น district/subdistrict เหมือนกันทั้งประเทศเพื่อให้โค้ดเขียนครั้งเดียว แต่ทุกรายการมี prefixTh และ prefixEn ให้แสดงคำนำหน้าที่ถูก อย่า hardcode คำว่า "อำเภอ" ในหน้าจอ
ใช้ offline หรือในแอปมือถือ
ดาวน์โหลด /api/v1/tools/thai/address/all.json ครั้งเดียว (ราว 1.3 MB, gzip ราว 200 KB) แล้วค้นในเครื่องหรือ bundle ในแอป ไฟล์นี้ cache ได้ 7 วันและมี ETag ให้เช็กว่ามีเวอร์ชันใหม่โดยไม่ต้องโหลดซ้ำ ข้อมูลเปลี่ยนปีละไม่กี่ครั้ง เช็กเดือนละครั้งก็เพียงพอ
curl -s https://reefsight.tech/api/v1/tools/thai/address/all.json -o thai-address.jsonendpoint อื่นตอบจากหน่วยความจำ ไม่จำกัดจำนวนครั้ง และ cache ได้ 1 วัน แต่ถ้าเรียกถี่มาก (เช่น ตรวจที่อยู่ทุกแถวตอน import ข้อมูลหลายแสนรายการ) แนะนำโหลด all.json ไปเก็บไว้เองจะเร็วกว่า
ข้อควรระวัง
- ไม่มีระดับหมู่บ้าน ถนน และไม่มีการตรวจบ้านเลขที่ ชุดข้อมูลครอบคลุมถึงตำบล/แขวงเท่านั้น
- รหัสไปรษณีย์หนึ่งค่าต่อตำบล ตำบลที่ไปรษณีย์ไทยแบ่งหลายรหัสจะได้รหัสหลัก งานที่ต้องแม่นระดับหมู่บ้านต้องเช็กเพิ่ม
- ชื่ออังกฤษเป็นแบบราชบัณฑิตยสภา อาจต่างจากที่ป้ายหรือคนท้องถิ่นใช้ เช่น
Chon Buriไม่ใช่Chonburiถ้าให้ผู้ใช้ค้นภาษาอังกฤษ ควรค้นแบบไม่สนช่องว่างซึ่ง API ทำให้อยู่แล้ว - อำเภอเมืองค้นด้วยคำว่า "เมือง" เฉยๆ ไม่เจอ เพราะชื่อเต็มคือ เมือง + ชื่อจังหวัด เช่น
เมืองเชียงใหม่ให้ค้นชื่อเต็มหรือค้นชื่อจังหวัดแล้วดูรายการอำเภอ - พิกัดและเส้นขอบไม่ครบ แขวงใน กทม. ไม่มีพิกัด และอำเภอที่ตั้งหลังปี 2019 ไม่มีเส้นขอบ โค้ดที่ปักหมุดควรรองรับกรณีไม่มี
lat/lng
คำถามที่พบบ่อย
รหัสที่ให้ตรงกับของทางการไหม ตรงกับรหัสกรมการปกครอง (จังหวัด 2 หลัก อำเภอ 4 หลัก ตำบล 6 หลัก) ซึ่งใช้ในทะเบียนราษฎรและชุดข้อมูลรัฐ เก็บรหัสแทนชื่อในฐานข้อมูลจะไม่พังเมื่อสะกดต่าง
ข้อมูลเปลี่ยนบ่อยแค่ไหน และจะรู้ได้อย่างไร
ปีละไม่กี่ครั้ง (ตั้งเขตปกครองใหม่หรือแก้สะกด) ดู updatedAt ใน response และ ETag ของ all.json ถ้า bundle ไว้ในแอป เช็ก all.json เดือนละครั้งก็พอ
เอาไปใช้ในงานเชิงพาณิชย์ได้ไหม
ได้ ข้อมูลต้นทางเป็น MIT ส่วนเส้นขอบอำเภอ (เฉพาะเมื่อขอ geometry=1) เป็น CC BY 4.0 ซึ่งต้องระบุที่มา geoBoundaries
ลองฟอร์มที่อยู่จำลองและดาวน์โหลดทั้งชุด
หน้า reefsight.tech › thai-address มีฟอร์มที่อยู่จำลองแบบเดียวกับที่คุณจะใส่ในเว็บของตัวเอง กล่องค้นหาช่องเดียว ปุ่มดู JSON ที่ API ตอบใต้ทุก dropdown และ snippet HTML + JavaScript ล้วนที่คัดลอกไปวางได้ทันที ไม่ต้องสมัคร ไม่จำกัดจำนวนครั้งเรียก
