ตอนนี้น่าจะเป็นตอนที่ต่อจาก Dart & Flutter : เส้นทางขวากหนามกับไลบรารี PROJ แบบเนทีฟบนแอนดรอยด์ ครั้งก่อนผมคอมไพล์และบิวท์รุ่น 9.1.0 เพื่อนำไลบรารีมาใช้กับแอพ Ezy Geo Pro สำหรับแอนดรอยด์ ตอนนี้ผ่านมาหนึ่งปีผมต้องการอัพเดทไลบรารี PROJ4 รุ่น 9.3.0 ที่ตัวไลบรารีเองมีการแก้บั๊กซ์และเพิ่มฟีเจอร์ใหม่ๆ ทั้งนี้นำมาคอมไพล์และบิวท์ด้วยเครื่องมือที่อัพเดทใหม่เช่นกันเช่น Android NDK, CMake มาดูกันครับ
สุดยอดไลบรารี PROJ
สำหรับไลบรารี PROJ หรือชื่อเดิมที่เรียกกัน PROJ4 นั้นเป็นทั้งซอฟท์แวร์และไลบรารีด้าน cartography ที่นำมาใช้ในการแปลงพิกัดข้ามพื้นหลักฐาน โดยรองรับพื้นหลักฐานและระบบพิกัดทั่วโลก ในขณะนี้ยังไม่มีไลบรารีใดแบบ open-source ที่มีความสามารถขนาดนี้ ดังนั้นโครงการ open-source ที่ดังๆอย่าง QGIS, GDAL และ PostGIS ก็อิมพลีเมนต์ไลบรารี PROJ ไปใช้กันทั้งนั้น
แอพ Ezy Geo Pro ใช้ไลบรารี PROJ เป็นแกนหลัก
เนื่องจากแอพของผม Ezy Geo Pro นั้นออกแบบมาให้แปลงระบบพิกัดในแถบอาเซียนตอนนี้สามารถแปลงพิกัดของประเทศไทย, สปป.ลาว, เมียนมา, เวียดนาม, ฟิลิปปินส์ และสิงคโปร์ และแถมบังคลาเทศมาให้ด้วย ส่วนกัมพูชา มาเลเซียและอินโดนีเซีย ยังไม่รองรับ ณ รุ่นนี้ นอกจากนั้นแอพยังรองรับยีออยด์โมเดลท้องถิ่นเช่น TGM2017 ของประเทศไทย PGM2018 ของฟิลิปปินส์ รวมถึง SGeoid09 ของสิงคโปร์ด้วย ส่วนประเทศอื่นๆที่ยังไม่มียีออยด์ท้องถิ้นนั้นใช้ EGM2008
ดังนั้นการแปลงระบบพิกัดอันหลากหลายนั้นต้องการไลบรารีมืออาชีพขนานแท้ เราไม่สามารถมาเขียนโค้ดเองได้เพราะมันซับซ้อนมาก ดังนั้นการเลือกใช้ไลบรารี PROJ มาใช้จึงเป็นสิ่งที่จำเป็นอย่างไม่ต้องลังเลสงสัย
ไฟล์สคริปต์สำหรับคอมไพล์และบิวท์ไลบรารี
แต่การจะคอมไพล์และบิวท์ไปใช้บนแอนดรอยด์นั้น ทางผู้พัฒนาไม่ได้เตรียมเครื่องมือและสคริปต์ไว้ให้ อย่างไรก็ตามผมไปได้สคริปต์เก่าที่มีคนทำไว้หลายปีแล้วและต้องรันบนลีนุกซ์ ผมนำมาดัดแปลงใหม่ การทำงานนั้นง่ายขอให้มีไฟล์สคริปต์และมีอินเทอร์เน็ตแรงๆก็พอ จากนั้นทำการรันไฟล์สคริปต์ โดยผมแยกไฟล์สคริปต์ตามแพล็ตฟอร์มของแอนดรอยด์ดังนี้
สคริปต์จะตาวน์โหลดทูลส์สำหรับคอมไฟล์เช่น android-ndk ที่มีขนาดไฟล์ซิปใหญ่มากกว่า 600 MB ส่วน CMake นั้นไม่ใหญ่นักขนาดประมาณมากกว่า 50 MB และดาวน์โหลดซอร์สโค้ดของ sqlite3 และที่สำคัญคือ proj โดยที่ผู้ใช้ไม่ต้องไปหาดาวน์โหลดมาแต่อย่างใด มีไฟล์สคริปต์เพียงไฟล์เดียวก็พอ
การคอมไพล์และบิวต์จะได้ไฟล์ไลบรารี *.so คือไฟล์ libproj.so และ libsqlite3.so และจะมีโฟลเดอร์ “Include” รวมไฟล์เฮดเดอร์ *.h ทั้งหลายและโฟลเดอร์ “Share” ของ PROJ ที่ขนเอาข้อมูลระบบพิกัดไปในไฟล์ proj.db หรืออื่นๆอีกหลายไฟล์
แพล็ตฟอร์มของแอนดรอยด์
สำหรับแพล็ตฟอร์มที่ต้องเตรียมให้ตามสถาปัตยกรรมของ ARM ได้แก่ arm-v8a, armeabi-v7a, x86 หรือ x86_64 จะมี 4 อย่างนี้หลักๆ แต่ armeabi-v7a นั้นเป็นรุ่นสถาปัตยกรรม 32 บิต ที่ล้าสมัยแล้ว ผมเจอในโทรศัพท์ราคาถูกๆ เช่น Samsung A12 ที่เป็น 32 บิต ตอนไปซื้อมาก็ไม่ได้คิดอะไร เอาสำหรับไว้บินโดรน แต่พอมาลง DJI Fly ไม่สามารถลงได้เพราะต้องการรุ่น 64 บิต ส่วน x86 และ X86_64 นั้นสำหรับผู้พัฒนาแอพทั้งหลายที่ต้องเอามารันอีมูเลเตอร์ของแอนดรอยด์ในขณะพัฒนาและทดสอบแอพ บนวินโดส์หรือลีนุกซ์
อนาคตไม่กี่ปีสถาปัตยกรรมคงมีแค่ arm-v8a และ x86_64 ส่วนจะมีสถาปัตยกรรมอื่นมาเพิ่มในอนาคตนั้นคงมีแน่นอนแต่ไม่น่าจะเร็วนัก
สคริปต์สำหรับแพล็ตฟอร์ม arm-v8a
#!/bin/sh set -e apt-get update -y # pkg-config sqlite3 for proj compilation DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wget unzip ccache curl ca-certificates \ pkg-config make binutils sqlite3 \ automake cd "$WORK_DIR" if test -f "$WORK_DIR/ccache.tar.gz"; then echo "Restoring ccache..." (cd $HOME && tar xzf "$WORK_DIR/ccache.tar.gz") fi # We need a recent cmake for recent NDK versions wget -q https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5-linux-x86_64.tar.gz tar xzf cmake-3.27.5-linux-x86_64.tar.gz export PATH=$PWD/cmake-3.27.5-linux-x86_64/bin:$PATH # Download Android NDK wget -q https://dl.google.com/android/repository/android-ndk-r26-linux.zip unzip -q android-ndk-r26-linux.zip export ANDROID_NDK=$PWD/android-ndk-r26 export NDK_TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 ccache -M 1G ccache -s # build sqlite3 wget -q https://sqlite.org/2023/sqlite-autoconf-3430100.tar.gz tar xzf sqlite-autoconf-3430100.tar.gz cd sqlite-autoconf-3430100 CC="ccache $NDK_TOOLCHAIN/bin/aarch64-linux-android24-clang" ./configure \ --prefix=/tmp/install --host=aarch64-linux-android24 make -j3 make install cd .. # Build proj wget -q https://download.osgeo.org/proj/proj-9.3.0.tar.gz tar xzf proj-9.3.0.tar.gz cd proj-9.3.0 mkdir build cd build # See later comment in GDAL build section about MAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY cmake .. \ -DUSE_CCACHE=ON \ -DENABLE_TIFF=OFF -DENABLE_CURL=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DEXE_SQLITE3=/usr/bin/sqlite3 make -j3 make install cd ../..
สคริปต์สำหรับแพล็ตฟอร์ม armeabi-v7a
#!/bin/sh set -e apt-get update -y # pkg-config sqlite3 for proj compilation DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wget unzip ccache curl ca-certificates \ pkg-config make binutils sqlite3 \ automake cd "$WORK_DIR" if test -f "$WORK_DIR/ccache.tar.gz"; then echo "Restoring ccache..." (cd $HOME && tar xzf "$WORK_DIR/ccache.tar.gz") fi # We need a recent cmake for recent NDK versions wget -q https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5-linux-x86_64.tar.gz tar xzf cmake-3.27.5-linux-x86_64.tar.gz export PATH=$PWD/cmake-3.27.5-linux-x86_64/bin:$PATH # Download Android NDK wget -q https://dl.google.com/android/repository/android-ndk-r26-linux.zip unzip -q android-ndk-r26-linux.zip export ANDROID_NDK=$PWD/android-ndk-r26 export NDK_TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 ccache -M 1G ccache -s # build sqlite3 wget -q https://sqlite.org/2023/sqlite-autoconf-3430100.tar.gz tar xzf sqlite-autoconf-3430100.tar.gz cd sqlite-autoconf-3430100 CC="ccache $NDK_TOOLCHAIN/bin/armv7a-linux-androideabi24-clang" ./configure \ --prefix=/tmp/install --host=armv7a-linux-androideabi24 make -j3 make install cd .. # Build proj wget -q https://download.osgeo.org/proj/proj-9.3.0.tar.gz tar xzf proj-9.3.0.tar.gz cd proj-9.3.0 mkdir build cd build # See later comment in GDAL build section about MAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY cmake .. \ -DUSE_CCACHE=ON \ -DENABLE_TIFF=OFF -DENABLE_CURL=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DEXE_SQLITE3=/usr/bin/sqlite3 make -j3 make install cd ../..
สคริปต์สำหรับแพล็ตฟอร์ม x86
#!/bin/sh set -e apt-get update -y # pkg-config sqlite3 for proj compilation DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wget unzip ccache curl ca-certificates \ pkg-config make binutils sqlite3 \ automake cd "$WORK_DIR" if test -f "$WORK_DIR/ccache.tar.gz"; then echo "Restoring ccache..." (cd $HOME && tar xzf "$WORK_DIR/ccache.tar.gz") fi # We need a recent cmake for recent NDK versions wget -q https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5-linux-x86_64.tar.gz tar xzf cmake-3.27.5-linux-x86_64.tar.gz export PATH=$PWD/cmake-3.27.5-linux-x86_64/bin:$PATH # Download Android NDK wget -q https://dl.google.com/android/repository/android-ndk-r26-linux.zip unzip -q android-ndk-r26-linux.zip export ANDROID_NDK=$PWD/android-ndk-r26 export NDK_TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 ccache -M 1G ccache -s # build sqlite3 wget -q https://sqlite.org/2023/sqlite-autoconf-3430100.tar.gz tar xzf sqlite-autoconf-3430100.tar.gz cd sqlite-autoconf-3430100 CC="ccache $NDK_TOOLCHAIN/bin/x86_64-linux-android24-clang" ./configure \ --prefix=/tmp/install --host=x86_64-linux-android24 make -j3 make install cd .. # Build proj wget -q https://download.osgeo.org/proj/proj-9.3.0.tar.gz tar xzf proj-9.3.0.tar.gz cd proj-9.3.0 mkdir build cd build # See later comment in GDAL build section about MAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY cmake .. \ -DUSE_CCACHE=ON \ -DENABLE_TIFF=OFF -DENABLE_CURL=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=x86_64 \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DEXE_SQLITE3=/usr/bin/sqlite3 make -j3 make install cd ../.. # Build GDAL mkdir build_android_cmake cd build_android_cmake # PKG_CONFIG_LIBDIR, CMAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY, CMAKE_FIND_USE_CMAKE_SYSTEM_PATH # are needed because we don't install dependencies (PROJ, SQLite3) in the NDK sysroot # This is definitely not the most idiomatic way of proceeding... PKG_CONFIG_LIBDIR=/tmp/install/lib/pkgconfig cmake .. \ -DUSE_CCACHE=ON \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=x86_64 \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ -DSFCGAL_CONFIG=disabled \ -DHDF5_C_COMPILER_EXECUTABLE=disabled \ -DHDF5_CXX_COMPILER_EXECUTABLE=disabled make -j3 make install cd ..
สคริปต์สำหรับแพล็ตฟอร์ม x86_64
#!/bin/sh set -e apt-get update -y # pkg-config sqlite3 for proj compilation DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ wget unzip ccache curl ca-certificates \ pkg-config make binutils sqlite3 \ automake cd "$WORK_DIR" if test -f "$WORK_DIR/ccache.tar.gz"; then echo "Restoring ccache..." (cd $HOME && tar xzf "$WORK_DIR/ccache.tar.gz") fi # We need a recent cmake for recent NDK versions wget -q https://github.com/Kitware/CMake/releases/download/v3.27.5/cmake-3.27.5-linux-x86_64.tar.gz tar xzf cmake-3.27.5-linux-x86_64.tar.gz export PATH=$PWD/cmake-3.27.5-linux-x86_64/bin:$PATH # Download Android NDK wget -q https://dl.google.com/android/repository/android-ndk-r26-linux.zip unzip -q android-ndk-r26-linux.zip export ANDROID_NDK=$PWD/android-ndk-r26 export NDK_TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64 ccache -M 1G ccache -s # build sqlite3 wget -q https://sqlite.org/2023/sqlite-autoconf-3430100.tar.gz tar xzf sqlite-autoconf-3430100.tar.gz cd sqlite-autoconf-3430100 CC="ccache $NDK_TOOLCHAIN/bin/x86_64-linux-android24-clang" ./configure \ --prefix=/tmp/install --host=x86_64-linux-android24 make -j3 make install cd .. # Build proj wget -q https://download.osgeo.org/proj/proj-9.3.0.tar.gz tar xzf proj-9.3.0.tar.gz cd proj-9.3.0 mkdir build cd build # See later comment in GDAL build section about MAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY cmake .. \ -DUSE_CCACHE=ON \ -DENABLE_TIFF=OFF -DENABLE_CURL=OFF -DBUILD_APPS=OFF -DBUILD_TESTING=OFF \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=x86_64 \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DEXE_SQLITE3=/usr/bin/sqlite3 make -j3 make install cd ../.. # Build GDAL mkdir build_android_cmake cd build_android_cmake # PKG_CONFIG_LIBDIR, CMAKE_FIND_ROOT_PATH_MODE_INCLUDE, CMAKE_FIND_ROOT_PATH_MODE_LIBRARY, CMAKE_FIND_USE_CMAKE_SYSTEM_PATH # are needed because we don't install dependencies (PROJ, SQLite3) in the NDK sysroot # This is definitely not the most idiomatic way of proceeding... PKG_CONFIG_LIBDIR=/tmp/install/lib/pkgconfig cmake .. \ -DUSE_CCACHE=ON \ -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=x86_64 \ -DCMAKE_SYSTEM_VERSION=24 \ "-DCMAKE_PREFIX_PATH=/tmp/install;$NDK_TOOLCHAIN/sysroot/usr/" \ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER \ -DCMAKE_FIND_USE_CMAKE_SYSTEM_PATH=NO \ -DSFCGAL_CONFIG=disabled \ -DHDF5_C_COMPILER_EXECUTABLE=disabled \ -DHDF5_CXX_COMPILER_EXECUTABLE=disabled make -j3 make install cd ..
การใช้งานสคริปต์
ตัวอย่างการรันสคริปต์ ใช้ Terminal ของลีนุกซ์ทั่วไป ไฟล์ที่ copy ไปจากสคริปต์ที่ผมลงไปให้ด้านบน ก่อนใช้ต้องคำสั่ง chmod +x ก่อนเพื่อให้ไฟล์สามารถรันได้ วิธีการคอมไพล์และบิวท์สามารถทำได้ดังต่อไปนี้
ไฟล์จะเริ่มดาวน์โหลดเครื่องมือในการคอมไพล์และบิวท์จาก Android-ndk และ CMake ตามลำดับจากนั้นจะดาวน์โหลดซอร์สโค้ดของ Sqlite3 และ PROJ จาก github จากนั้นก็เป็นการคอมไพล์และบิวท์ กระบวนการนี้จะเขียนเป็นไฟล์ผลลัพธ์ไปที่ไดเรคทอรีชัวคราวอยู่ใน /tmp/install ตามที่ตั้งไว้ในไฟล์สคริปต์
ตอนจบสุดท้ายไม่สวยมี error เตือนว่าหาไฟล์ CMakeLists.txt ไม่เจอ แต่ไม่เป็นไรแล้วครับเพราะว่าไลบรารี *.so ได้ถูกเขียนเป็นไฟล์มาเรียบร้อยที่ /tmp/install/lib
Copy ไฟล์ไปยังโครงการพัฒนาแอพ Flutter
เราจะทำการรันสคริปต์แบบนี้ทุกสคริปต์ อาจจะสังเกตุว่าต้องดาวน์โหลดเครื่องมือและซอร์สโค้ดทุกครั้ง แต่คงไม่เป็นไร สมัยนี้เน็ตเร็วและถูก เราเอาความสะดวกไว้ก่อน เมื่อได้ไฟล์ *.so แต่ละชุดออกมา (libsqlite3.so และ libproj.so) ให้ทำการ copy จาก /tmp/install ไปไว้ในไดเรคทอรี “Jnilibs” จากนั้นก็ copy ทั้งไดเรคทอรี “Jnilibs” ไปยังโครงการแอพของฟลัตเตอร์ หรือจะเป็น Android studio ก็ได้ถ้าผู้อ่านพัฒนาด้วยจาวาหรือฟลัตเตอร์บน Android studio จากนั้นทำการลบไฟล์ในโฟลเดอร์ของสคริปต์ให้เหลือแต่ตัวสคริปต์
ก็ขอจบบทความนี้ หวังว่าคงเป็นแนวทางสำหรับผู้ที่ต้องการใช้ไลบรานี PROJ4 และนำไปใช้พัฒนาแอพบนแพล็ตฟอร์มแอนดรอยด์ต่อไป โปรดติดตามบทความตอนต่อไปครับ