# ชุดฝึกการทำแผนที่ด้วยโดรนบนคอมพิวเตอร์

ชุดนี้ใช้ **Python 3.10 ขึ้นไปและ standard library เท่านั้น** ไม่ต้องติดตั้งแพ็กเกจเพิ่ม ไม่ใช้ GPU ไม่ต้องมีโดรน ไม่ต่อเครือข่าย และไม่มีภาพถ่ายบุคคลที่สามใน ZIP ข้อมูล CSV ทุกแถวสร้างขึ้นเพื่อฝึกคำนวณ ไม่ใช่ผลสำรวจจริง

คำสั่งออกแบบให้ใช้ได้บน Windows/Linux แต่หลักฐานการรันจริงระบุเฉพาะระบบและ Python ใน `verification-report.json` การมีคำสั่ง Linux ในคู่มือไม่ใช่หลักฐานว่าทดสอบ Linux แล้ว **ยังไม่ได้ติดตั้งหรือทดสอบ WebODM, ODM, QGIS, CloudCompare หรือ SamGeo ในงานนี้**

## เริ่มต้น

1. แตก ZIP แล้วเปิด Terminal/PowerShell ในโฟลเดอร์ `mapping-practice` ที่มี `mapping_lab.py`
2. ตรวจ Python ด้วย `python --version` บน Windows หรือ `python3 --version` บน Linux
3. ใช้คำสั่งด้านล่าง บน Linux ให้เปลี่ยนคำแรก `python` เป็น `python3` ผลลัพธ์เป็น JSON ทางหน้าจอ; หากต้องการบันทึกให้เติม `> result.json`

```text
python mapping_lab.py --help
python mapping_lab.py gsd --sensor-width-mm 13.2 --focal-length-mm 12 --image-width-px 4000 --height-agl-m 100
python mapping_lab.py rmse input/checkpoints.csv --units m --coordinate-system local --same-height-reference
python mapping_lab.py volume input/grid.csv --cell-size-m 2 --base-m 0 --units m
python mapping_lab.py volume input/grid.csv --cell-size-m 2 --base-m 2 --units m
python mapping_lab.py volume input/grid-nodata.csv --cell-size-m 2 --base-m 0 --units m
```

## M02: GSD จากกล้องสมมติ

สมมติกล้องมองลงตั้งฉาก พื้นราบ ภาพไม่ถูก crop/resize และใช้ความกว้างเซนเซอร์จริงที่สัมพันธ์กับความกว้างภาพ ความยาวโฟกัสเป็นค่าจริง ไม่ใช่ค่าเทียบเท่าฟิล์ม 35 mm

`GSD (m/pixel) = height_agl_m × sensor_width_mm / (focal_length_mm × image_width_px)`

ตัวอย่าง: `100 × 13.2 / (12 × 4000) = 0.0275 m/pixel = 2.75 cm/pixel` เพิ่มความสูงเป็น 200 m จะได้ 5.5 cm/pixel ตัวเลขนี้เป็นสถานการณ์คำนวณบน PC ไม่ใช่ความสูงที่อนุญาตให้บินจริง

GSD ไม่ใช่ RMSE และไม่รับประกันตำแหน่งภาพ แปรตามความสูงเหนือพื้นจริงและเรขาคณิตกล้อง พื้นลาด/ภาพเฉียงต้องพิจารณาเพิ่ม สูตรหน่วยกล้องสอดคล้องกับ [คู่มือคำนวณความสูงจาก GSD ของ PIX4D](https://support.pix4d.com/hc/en-us/articles/202557469)

## M03: จุดตรวจอิสระและ RMSE

`input/checkpoints.csv` มีเพียง 2 จุดสมมติในกรอบพิกัด local หน่วยเมตร ไม่ผูกกับ EPSG หรือสถานที่จริง ใช้เพื่อให้ตรวจเลขง่าย ไม่ใช่จำนวนจุดแนะนำสำหรับงานสำรวจ เครื่องมือกำหนดให้ `used_for_adjustment=false` ทุกจุด: ข้อมูลจริงต้องกันจุดเหล่านี้ออกจากการปรับโมเดลด้วย ผู้ใช้ต้องยืนยันความเป็นอิสระจากบันทึกการประมวลผล โปรแกรมตรวจค่าช่อง CSV ได้แต่พิสูจน์ประวัติข้อมูลไม่ได้ [หลักการ checkpoints ของ WebODM](https://docs.webodm.org/ground-control-points/)

แต่ละแถวมี `id,used_for_adjustment,reference_x_m,reference_y_m,reference_z_m,mapped_x_m,mapped_y_m,mapped_z_m` ตามลำดับนี้ ค่าคลาดเคลื่อนใช้ **mapped − reference**:

- `RMSE_x = sqrt(sum(dx²)/n)` และ `RMSE_y = sqrt(sum(dy²)/n)`
- `RMSE_horizontal = sqrt(sum(dx² + dy²)/n)` โดยหารด้วย n ไม่ใช่ 2n
- `RMSE_vertical = sqrt(sum(dz²)/n)` ไม่หักค่าเฉลี่ยของ error ออก

ตัวอย่างมี error `(0.03,0.04,0.10)` และ `(-0.03,-0.04,-0.10)` เมตร จึงได้แนวราบ 0.05 m และแนวดิ่ง 0.10 m ตรวจรายจุดเทียบ `expected-checkpoint-errors.csv` และค่าสรุปเทียบ `expected-results.json` ยอมให้ต่างจากเลขฐานสองไม่เกิน `1e-10` เมตร

ก่อนใช้ข้อมูลจริง ให้แปลงพิกัดทั้งสองชุดเป็น **projected CRS เดียวกันที่ใช้เมตร** หรือกรอบ Cartesian local เดียวกัน แล้วเลือก `--coordinate-system projected` หรือ `local` ตามจริง ต้องใช้ datum/epoch และแหล่งอ้างอิงความสูงที่สอดคล้องกัน ห้ามใส่ longitude/latitude หน่วยองศา หรือปน ellipsoidal height กับ orthometric height โดยไม่แปลง `--units m` และ `--same-height-reference` เป็นคำยืนยันของผู้ใช้ ไม่ได้แปลงหรือตรวจ CRS ให้ โปรแกรมปฏิเสธหน่วย `ft`, `degrees` และระบบ `geographic`

ผลนี้ไม่ใช่ใบรับรองมาตรฐาน ASPRS และไม่ได้ชดเชยความไม่แน่นอนของจุดอ้างอิง ต้องรายงานจำนวน/การกระจายจุด วิธีวัด และข้อจำกัดควบคู่

## M04: พื้นผิวกริดกับฐานปริมาตร

`input/grid.csv` เป็นกริด 2 × 2 ช่อง ขนาดช่องละ 2 m × 2 m มีความสูงตัวแทนแต่ละช่อง `1,2,3,4` m ในกรอบความสูงสมมติเดียวกัน ไม่มี CRS โลก ใช้ฐานราบคงที่ และถือว่าความสูงคงที่ทั้งช่อง

`signed volume = sum((surface_m − base_m) × cell_size_m²)`

| กรณี | เหนือฐาน (m³) | ต่ำกว่าฐาน (m³) | สุทธิ (m³) | พื้นที่คำนวณ (m²) |
|---|---:|---:|---:|---:|
| ฐาน 0 m | 40 | 0 | 40 | 16 |
| ฐาน 2 m | 12 | 4 | 8 | 16 |
| ฐาน 0 m และช่องสูง 4 m เป็น NoData | 24 | 0 | 24 | 12 |

เหนือฐาน/ต่ำกว่าฐานรายงานเป็นปริมาตรบวกทั้งคู่ สุทธิ = เหนือฐาน − ต่ำกว่าฐาน การจะเรียกว่า cut/fill ขึ้นกับว่าเลือกผิวและฐานแทนอะไร กรณี NoData มีพื้นที่กริดรวม 16 m² แต่คำนวณได้ 12 m² **24 m³ จึงเป็นผลเฉพาะพื้นที่มีข้อมูล** ไม่ได้ประมาณพื้นที่ว่าง

กริดต้องเริ่ม `row=0,column=0` ครบทุกช่องของสี่เหลี่ยม ไม่มีตำแหน่งซ้ำ ใช้ช่อง `surface_m` ว่างแทน NoData เท่านั้น ไม่ใช้ 0 หรือ NaN แทนข้อมูลหาย 0 m เป็นความสูงที่ถูกต้อง และความสูง/ฐานติดลบอนุญาตได้เมื่ออ้างอิง datum เดียวกัน ไม่เติมค่าหรือ resample โดยอัตโนมัติ การเปลี่ยน cell size โดยใช้ตัวเลขเดิมเป็นเพียงการทดลองพื้นที่ ไม่ใช่การเปลี่ยน resolution ของข้อมูลสำรวจจริง

นี่ไม่ใช่ตัวอ่าน GeoTIFF/DSM/DTM และยังไม่ใช่กระบวนการวัดกองวัสดุจริง อ่านหลักการกริดและพื้นที่ว่างต่อได้ที่ [CloudCompare 2.5D Volume](https://www.cloudcompare.org/doc/wiki/index.php/2.5D_Volume)

## การตรวจข้อมูลและการทำซ้ำ

รับเฉพาะเลข finite: ปฏิเสธ `NaN`, `inf`, ช่องตัวเลขว่าง และตัวอักษร ค่ากล้อง ความสูง AGL และขนาดเซลล์ต้องมากกว่า 0; ความกว้างภาพต้องเป็นจำนวนเต็มบวก; ตำแหน่งกริดต้องเป็นจำนวนเต็มไม่ติดลบ ปฏิเสธ header ผิด ช่องเกิน/ขาด ไฟล์ว่าง ID ซ้ำ จุดใช้ปรับโมเดล กริดขาดตำแหน่ง และกริด NoData ทั้งหมด ถ้าคำนวณเกินช่วงเลข finite จะหยุดพร้อมข้อความผิดพลาดและ exit code 2 ไม่มีผลสำเร็จบางส่วน

ผู้พัฒนาที่มี repository รัน `python tools/verify-mapping-practice.py` เพื่อตรวจ CLI ในสำเนา temporary รวมถึง ZIP ที่แตกใหม่ และ `python tools/verify-mapping-practice.py --refresh` เพื่อสร้างรายงานหลักฐานและ ZIP ใหม่หลังตรวจผ่าน คำตอบคำนวณอิสระด้วย Decimal และสูตรเรขาคณิต ไม่เรียกฟังก์ชัน implementation เพื่อสร้างค่าคาดหวัง

`verification-report.json` บันทึกเวลา UTC ที่รันจริง ระบบปฏิบัติการ Python รายการตรวจ และ SHA-256 ของไฟล์ต้นฉบับ เวลาใน metadata ของ ZIP ตั้งไว้ที่ 1980-01-01 **เพื่อให้การแพ็กไฟล์ชุดเดิมได้ byte เดิม ไม่ใช่วันตรวจ** รายงานใหม่เปลี่ยนเวลาและจึงเปลี่ยน ZIP hash ตามจริง ไม่มีคำกล่าวว่าผ่านระบบอื่นที่ไม่ได้รัน

## M01 และ M05: เส้นทางต่อยอดที่ยังไม่ได้ทดสอบในงานนี้

### M01: Mygla → WebODM → QGIS

1. อ่าน [Mygla ต้นฉบับ](https://github.com/merkato/odm_mygla_dataset) และ [ทะเบียนชุดข้อมูล WebODM](https://webodm.org/datasets/) ก่อนดาวน์โหลดเอง ภาพ Mygla โดย Tomasz Nycz ระบุ CC BY 3.0 ต้องเก็บ attribution/license และบันทึกการดัดแปลง ชุดนี้ไม่ได้รวมภาพดังกล่าว
2. เลือกวิธีติดตั้งจาก [คู่มือ WebODM](https://docs.webodm.org/installation/) บันทึกรุ่น WebODM และ processing engine, OS, RAM และ options ที่ใช้ เว็บกับ engine อาจมีรุ่นและทางเลือกต่างกัน ไม่ถือว่า option ของ ODM ใช้ได้ทุกกรณี
3. สร้างงานด้วยภาพ Mygla ตามคู่มือรุ่นที่ติดตั้ง ประมวลผลแล้วเก็บรายงาน เวลา และ orthomosaic GeoTIFF ที่ส่งออก ถ้าภาพเหลื่อม/มีช่องว่าง ให้เก็บหลักฐานปัญหาด้วย
4. เปิดผลใน QGIS ตาม [Training Manual 3.44](https://docs.qgis.org/3.44/en/docs/training_manual/index.html) ตรวจ CRS ขอบเขตและหน่วยก่อนวัด จัดหน้า PDF มี legend, scale bar, CRS, แหล่งภาพและวันที่ข้อมูล เก็บ project กับไฟล์ประกอบเพื่อเปิดซ้ำ
5. ส่ง GeoTIFF, QGIS project, PDF และบันทึกรุ่น/options ผลมองด้วยตาไม่ใช่ค่าความถูกต้องเชิงตำแหน่ง: หากไม่มี checkpoints อิสระอย่าระบุ RMSE ที่ไม่มีหลักฐาน

### M05: GeoAI หลังการประมวลผลแผนที่

อ่าน [SamGeo](https://github.com/opengeos/segment-geospatial) เพื่อรู้จัก segmentation และ vector export ก่อนเลือกติดตั้ง ตรวจสิทธิ์โมเดล weights/dependencies แยกจาก MIT ของตัวแพ็กเกจ ทดลองพื้นที่เล็ก เก็บ mask/polygon แล้วให้คนตรวจเทียบ annotation แบ่ง train/test ตามพื้นที่เพื่อไม่ให้ tile ข้างเคียงรั่วข้ามชุดตรวจ บันทึกตัวอย่างผิดและค่าประเมินก่อนเผยแพร่ผ่านเครื่องมืออย่าง [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js)

M05 เป็นแนวทางอ่านต่อ ไม่มี model weights, ภาพ, notebook หรือผล AI ที่อ้างว่ารันแล้วใน ZIP นี้ และเป็น GeoAI หลังบิน ไม่ใช่ Edge AI บนโดรน

## สิทธิ์ใช้งาน

โค้ด คู่มือ และข้อมูลสังเคราะห์ต้นฉบับใช้ MIT ตาม `LICENSE` ลิงก์ซอฟต์แวร์/เอกสาร/ภาพบุคคลที่สามมีเงื่อนไขของเจ้าของ แหล่งเนื้อหาอ้างอิงจากการวิจัยโครงการวันที่ 8 กันยายน 2569; วันทดสอบโค้ดจริงอ่านจากรายงานเท่านั้น
