Skip to content

Commit

Permalink
feat: Do not depend anymore on providing env variable PROJ_DATA. In…
Browse files Browse the repository at this point in the history
…stead, generate temp path and use LibC to set it as env variable
  • Loading branch information
REASY committed Jan 20, 2023
1 parent 36a8d88 commit 3576aa5
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 25 deletions.
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ gdal-jni-with-native
======
[<img src="https://jitpack.io/v/REASY/gdal-jni-with-native.svg">](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.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group = 'gdal-jni-with-native'
version = '3.5.3.2'
version = '3.5.3.3'

buildscript {
repositories {
Expand Down
2 changes: 1 addition & 1 deletion scripts/generate_native_modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

6 changes: 4 additions & 2 deletions src/main/java/com/github/reasy/gdal/GdalExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -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});

Expand Down Expand Up @@ -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();
Expand Down
56 changes: 41 additions & 15 deletions src/main/java/com/github/reasy/gdal/JNIGdalLoader.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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();
}

}
7 changes: 7 additions & 0 deletions src/main/java/com/github/reasy/gdal/LibC.java
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 3576aa5

Please sign in to comment.