คอมไพล์และบิวท์ไลบราลี PROJ4 รุ่น 9.3.0 แบบเนทีฟสำหรับแอนดรอยด์

ตอนนี้น่าจะเป็นตอนที่ต่อจาก 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 จากนั้นทำการลบไฟล์ในโฟลเดอร์ของสคริปต์ให้เหลือแต่ตัวสคริปต์

Visual Studio (VS) ทีใช้พัฒนาแอพ Ezy Geo Pro ด้วยภาษาดาร์ทและฟลัตเตอร์

ก็ขอจบบทความนี้ หวังว่าคงเป็นแนวทางสำหรับผู้ที่ต้องการใช้ไลบรานี PROJ4 และนำไปใช้พัฒนาแอพบนแพล็ตฟอร์มแอนดรอยด์ต่อไป โปรดติดตามบทความตอนต่อไปครับ

Leave a Reply

Your email address will not be published. Required fields are marked *