เกือบทุกระบบที่มีฟอร์มที่อยู่ในไทยต้องเจอคำถามเดียวกัน: จะเอาข้อมูลจังหวัด อำเภอ ตำบล มาจากไหน จะเก็บในฐานข้อมูลอย่างไรให้ไม่พังเมื่อมีคนสะกดต่างกัน และจะดูแลอย่างไรเมื่อมีการตั้งอำเภอใหม่ หลายทีมจบด้วยการคัดลอก 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 บนเว็บไซต์ของคุณได้โดยตรง

js
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= ช่องเดียวแล้วให้เลือกจากผลลัพธ์

Terminal
curl "https://reefsight.tech/api/v1/tools/thai/address/search?q=บางกะปิ"
json
{
  "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

ค้นจากรหัสไปรษณีย์

Terminal
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 ให้เช็กว่ามีเวอร์ชันใหม่โดยไม่ต้องโหลดซ้ำ ข้อมูลเปลี่ยนปีละไม่กี่ครั้ง เช็กเดือนละครั้งก็เพียงพอ

Terminal
curl -s https://reefsight.tech/api/v1/tools/thai/address/all.json -o thai-address.json

endpoint อื่นตอบจากหน่วยความจำ ไม่จำกัดจำนวนครั้ง และ 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 ล้วนที่คัดลอกไปวางได้ทันที ไม่ต้องสมัคร ไม่จำกัดจำนวนครั้งเรียก

คุยกับทีม Reefsight