diff --git a/README.md b/README.md index 067b5ad..9bfba46 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,18 @@ gdal-jni-with-native ====== [](https://jitpack.io/#REASY/gdal-jni-with-native) -This project builds GDAL, extract native libraries and makes it available as Java library. At the moment it only supports GDAL on Ubuntu x64 (Linux) +This project builds GDAL, extracts native libraries and makes them available as Java library. At the moment it only supports GDAL on Ubuntu x64 (Linux) ### Build In order to build the project the following should happen: 1. Build GDAL docker image, [build_gdal.sh](scripts.build_gdal.sh) is responsible for that 2. Build [lddtopo-rs](https://github.com/REASY/lddtopo-rs), [build_lddtopo.sh](scripts/build_lddtopo.sh) is responsible for that 3. Analyze the dependencies of /usr/share/java/libgdalalljni.so by building DAG and running topological sort on it -4. Copy all required native modules to src/main/resources/native -5. Generate src/main/resources/native/modules.txt that contains new line separated list of modules to be loaded. The order comes from topological sort! +4. Copy all required native modules to `src/main/resources/native/%os%-%arch%` +5. Copy proj DB to [src/main/resources/proj/proj.db](src/main/resources/proj/proj.db) +7. Generate `src/main/resources/native/%os%-%arch%.txt` that contains new line separated list of modules to be loaded. The order comes from topological sort! All of this is done in a script [generate_native_modules.sh](scripts/generate_native_modules.sh), just run it from root folder of the repo to get the final JAR ```bash ./scripts/generate_native_modules.sh ``` - -### Note on the usage -Please, make sure that you set an environment variable PROJ_DATA to writable (preferably temporary) folder, for example `/tmp/gdal-jni-with-native/proj/`. That env variable is expected by PROJ, so it cannot be set on runtime by the same process. diff --git a/build.gradle b/build.gradle index 748e8b4..5c7dae9 100755 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group = 'gdal-jni-with-native' -version = '3.5.3.2' +version = '3.5.3.3' buildscript { repositories { diff --git a/scripts/generate_native_modules.sh b/scripts/generate_native_modules.sh index 206e563..fb567c5 100755 --- a/scripts/generate_native_modules.sh +++ b/scripts/generate_native_modules.sh @@ -36,5 +36,5 @@ docker run -u $(id -u):$(id -g) \ lib_name=$(./gradlew properties | grep ^name | sed 's/name: //g') lib_version=$(./gradlew properties | grep ^version | sed 's/version: //g') lib_full_name="$lib_name-$lib_version.jar" -./gradlew clean build && PROJ_DATA=/tmp/gdal-jni-with-native/proj/ java -cp "build/libs/$lib_full_name" com.github.reasy.gdal.GdalExample +./gradlew clean build && java -cp "build/libs/$lib_full_name" com.github.reasy.gdal.GdalExample diff --git a/src/main/java/com/github/reasy/gdal/GdalExample.java b/src/main/java/com/github/reasy/gdal/GdalExample.java index be1e8f8..16187cc 100644 --- a/src/main/java/com/github/reasy/gdal/GdalExample.java +++ b/src/main/java/com/github/reasy/gdal/GdalExample.java @@ -49,7 +49,10 @@ private static void writeCogGeoTiff() throws IOException { Dataset memDs = drv.Create("", xSize, ySize, 1, type); System.out.printf("Created %d x %d dataset with 1 band and type %d\n", xSize, ySize, type); - memDs.SetProjection("EPSG:32611"); + int err = memDs.SetProjection("EPSG:32611"); + if (err != gdalconstConstants.CE_None) { + throw new IllegalStateException(String.format("Could not set projection, error: %d", err)); + } memDs.GetRasterBand(1).WriteRaster(0, 0, xSize, ySize, data); memDs.BuildOverviews("NEAREST", new int[]{2, 4, 8, 16, 32}); @@ -87,7 +90,6 @@ public static void main(String[] args) throws Exception { // Force to load GDAL JNI and all dependencies JNIGdalLoader.load(); - gdal.AllRegister(); runExamples(); diff --git a/src/main/java/com/github/reasy/gdal/JNIGdalLoader.java b/src/main/java/com/github/reasy/gdal/JNIGdalLoader.java index d4267e6..ccae08d 100644 --- a/src/main/java/com/github/reasy/gdal/JNIGdalLoader.java +++ b/src/main/java/com/github/reasy/gdal/JNIGdalLoader.java @@ -1,14 +1,11 @@ package com.github.reasy.gdal; +import com.sun.jna.Native; import com.sun.jna.Platform; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -20,25 +17,41 @@ public class JNIGdalLoader { public static final boolean IS_LOADED; public static final String PATH_TO_MODULES = String.format("native/%s.txt", Platform.RESOURCE_PREFIX); public static final String PROJ_DB_PATH = "proj/proj.db"; - public static final String TEMP_PATH_FOR_PROJ_DATA; + public static final File TEMP_PATH_FOR_PROJ_DATA; + + public static final LibC libc; + static { - if (System.getenv("PROJ_DATA") == null) { - throw new IllegalStateException("There must be an env variable `PROJ_DATA` set to writable (preferably temporary) folder, for example `/tmp/gdal-jni-with-native/proj/`"); - } - TEMP_PATH_FOR_PROJ_DATA = System.getenv("PROJ_DATA"); + // TODO: Add support of Windows (need to load msvcrt and use `_putenv` instead + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv?view=msvc-170 + libc = Native.load("c", LibC.class); try { + TEMP_PATH_FOR_PROJ_DATA = Files.createTempDirectory("JNIGdalLoader").toFile(); + cleanUpOnShutdown(); + System.out.println("TEMP_PATH_FOR_PROJ_DATA: " + TEMP_PATH_FOR_PROJ_DATA); + + copyProjDb(TEMP_PATH_FOR_PROJ_DATA + "/proj.db"); + + // Make PROJ used by GDAL aware of a path to proj.db via setting PROJ_DATA + // https://proj.org/usage/environmentvars.html#envvar-PROJ_DATA + final int err = libc.setenv("PROJ_DATA", TEMP_PATH_FOR_PROJ_DATA.getAbsolutePath(), 1); + if (err != 0) { + throw new IllegalStateException(String.format("Could not set env variable `PROJ_DATA` to %s", TEMP_PATH_FOR_PROJ_DATA)); + } + + // Get the list of modules to load MODULES_TO_LOAD = getModules(); System.out.printf("JNIGdalLoader: loading %d shared library...\n", MODULES_TO_LOAD.length); + // Load modules NativeLibraryLoader loader = new NativeLibraryLoader("/native"); for (String module : MODULES_TO_LOAD) { loader.load(module); } System.out.println("JNIGdalLoader: loaded all shared libraries"); - copyProjDb(); IS_LOADED = true; } catch (Exception ex) { @@ -49,7 +62,6 @@ public class JNIGdalLoader { } } - public static void load() { // Just make sure it that it is loaded. // If we reach here, it must always be true (because otherwise the RuntimeException should have been thrown before in static ctor) @@ -71,14 +83,28 @@ private static String[] getModules() throws IOException { return modules.toArray(new String[0]); } - private static void copyProjDb() throws IOException { + private static void copyProjDb(String destPath) throws IOException { try (InputStream in = NativeLibraryLoader.class.getResourceAsStream("/" + PROJ_DB_PATH)) { Objects.requireNonNull(in, String.format("Resource as stream for '%s' is null", PROJ_DB_PATH)); - Files.createDirectories(Paths.get(TEMP_PATH_FOR_PROJ_DATA)); - String destPath = TEMP_PATH_FOR_PROJ_DATA + "/proj.db"; Files.copy(in, Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); System.out.printf("JNIGdalLoader java: Copied resource at %s to %s\n", PROJ_DB_PATH, destPath); } } + private static void cleanUpOnShutdown() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + cleanUp(); + } catch (IOException ex) { + System.err.printf("Could not clean-up TEMP_PATH_FOR_PROJ_DATA '%s'. Error: %s [%s]%n", TEMP_PATH_FOR_PROJ_DATA, ex.getClass(), ex.getMessage()); + ex.printStackTrace(); + } + })); + } + + private static void cleanUp() throws IOException { + Files.delete(Paths.get(TEMP_PATH_FOR_PROJ_DATA + "/proj.db")); + TEMP_PATH_FOR_PROJ_DATA.delete(); + } + } diff --git a/src/main/java/com/github/reasy/gdal/LibC.java b/src/main/java/com/github/reasy/gdal/LibC.java new file mode 100644 index 0000000..77ca880 --- /dev/null +++ b/src/main/java/com/github/reasy/gdal/LibC.java @@ -0,0 +1,7 @@ +package com.github.reasy.gdal; + +import com.sun.jna.Library; + +public interface LibC extends Library { + int setenv(String name, String value, int overwrite); +} \ No newline at end of file