Skip to content

Commit 3576aa5

Browse files
committed
feat: Do not depend anymore on providing env variable PROJ_DATA. Instead, generate temp path and use LibC to set it as env variable
1 parent 36a8d88 commit 3576aa5

File tree

6 files changed

+58
-25
lines changed

6 files changed

+58
-25
lines changed

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@ gdal-jni-with-native
22
======
33
[<img src="https://jitpack.io/v/REASY/gdal-jni-with-native.svg">](https://jitpack.io/#REASY/gdal-jni-with-native)
44

5-
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)
5+
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)
66

77
### Build
88
In order to build the project the following should happen:
99
1. Build GDAL docker image, [build_gdal.sh](scripts.build_gdal.sh) is responsible for that
1010
2. Build [lddtopo-rs](https://github.com/REASY/lddtopo-rs), [build_lddtopo.sh](scripts/build_lddtopo.sh) is responsible for that
1111
3. Analyze the dependencies of /usr/share/java/libgdalalljni.so by building DAG and running topological sort on it
12-
4. Copy all required native modules to src/main/resources/native
13-
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!
12+
4. Copy all required native modules to `src/main/resources/native/%os%-%arch%`
13+
5. Copy proj DB to [src/main/resources/proj/proj.db](src/main/resources/proj/proj.db)
14+
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!
1415

1516
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
1617
```bash
1718
./scripts/generate_native_modules.sh
1819
```
19-
20-
### Note on the usage
21-
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.

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group = 'gdal-jni-with-native'
2-
version = '3.5.3.2'
2+
version = '3.5.3.3'
33

44
buildscript {
55
repositories {

scripts/generate_native_modules.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ docker run -u $(id -u):$(id -g) \
3636
lib_name=$(./gradlew properties | grep ^name | sed 's/name: //g')
3737
lib_version=$(./gradlew properties | grep ^version | sed 's/version: //g')
3838
lib_full_name="$lib_name-$lib_version.jar"
39-
./gradlew clean build && PROJ_DATA=/tmp/gdal-jni-with-native/proj/ java -cp "build/libs/$lib_full_name" com.github.reasy.gdal.GdalExample
39+
./gradlew clean build && java -cp "build/libs/$lib_full_name" com.github.reasy.gdal.GdalExample
4040

src/main/java/com/github/reasy/gdal/GdalExample.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ private static void writeCogGeoTiff() throws IOException {
4949
Dataset memDs = drv.Create("", xSize, ySize, 1, type);
5050
System.out.printf("Created %d x %d dataset with 1 band and type %d\n", xSize, ySize, type);
5151

52-
memDs.SetProjection("EPSG:32611");
52+
int err = memDs.SetProjection("EPSG:32611");
53+
if (err != gdalconstConstants.CE_None) {
54+
throw new IllegalStateException(String.format("Could not set projection, error: %d", err));
55+
}
5356
memDs.GetRasterBand(1).WriteRaster(0, 0, xSize, ySize, data);
5457
memDs.BuildOverviews("NEAREST", new int[]{2, 4, 8, 16, 32});
5558

@@ -87,7 +90,6 @@ public static void main(String[] args) throws Exception {
8790

8891
// Force to load GDAL JNI and all dependencies
8992
JNIGdalLoader.load();
90-
9193
gdal.AllRegister();
9294

9395
runExamples();

src/main/java/com/github/reasy/gdal/JNIGdalLoader.java

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package com.github.reasy.gdal;
22

3+
import com.sun.jna.Native;
34
import com.sun.jna.Platform;
45

5-
import java.io.BufferedReader;
6-
import java.io.IOException;
7-
import java.io.InputStream;
8-
import java.io.InputStreamReader;
6+
import java.io.*;
97
import java.nio.charset.StandardCharsets;
108
import java.nio.file.Files;
11-
import java.nio.file.Path;
129
import java.nio.file.Paths;
1310
import java.nio.file.StandardCopyOption;
1411
import java.util.ArrayList;
@@ -20,25 +17,41 @@ public class JNIGdalLoader {
2017
public static final boolean IS_LOADED;
2118
public static final String PATH_TO_MODULES = String.format("native/%s.txt", Platform.RESOURCE_PREFIX);
2219
public static final String PROJ_DB_PATH = "proj/proj.db";
23-
public static final String TEMP_PATH_FOR_PROJ_DATA;
20+
public static final File TEMP_PATH_FOR_PROJ_DATA;
21+
22+
public static final LibC libc;
23+
2424

2525
static {
26-
if (System.getenv("PROJ_DATA") == null) {
27-
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/`");
28-
}
29-
TEMP_PATH_FOR_PROJ_DATA = System.getenv("PROJ_DATA");
26+
// TODO: Add support of Windows (need to load msvcrt and use `_putenv` instead
27+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv?view=msvc-170
28+
libc = Native.load("c", LibC.class);
3029

3130
try {
31+
TEMP_PATH_FOR_PROJ_DATA = Files.createTempDirectory("JNIGdalLoader").toFile();
32+
cleanUpOnShutdown();
33+
System.out.println("TEMP_PATH_FOR_PROJ_DATA: " + TEMP_PATH_FOR_PROJ_DATA);
34+
35+
copyProjDb(TEMP_PATH_FOR_PROJ_DATA + "/proj.db");
36+
37+
// Make PROJ used by GDAL aware of a path to proj.db via setting PROJ_DATA
38+
// https://proj.org/usage/environmentvars.html#envvar-PROJ_DATA
39+
final int err = libc.setenv("PROJ_DATA", TEMP_PATH_FOR_PROJ_DATA.getAbsolutePath(), 1);
40+
if (err != 0) {
41+
throw new IllegalStateException(String.format("Could not set env variable `PROJ_DATA` to %s", TEMP_PATH_FOR_PROJ_DATA));
42+
}
43+
44+
// Get the list of modules to load
3245
MODULES_TO_LOAD = getModules();
3346
System.out.printf("JNIGdalLoader: loading %d shared library...\n", MODULES_TO_LOAD.length);
3447

48+
// Load modules
3549
NativeLibraryLoader loader = new NativeLibraryLoader("/native");
3650
for (String module : MODULES_TO_LOAD) {
3751
loader.load(module);
3852
}
3953
System.out.println("JNIGdalLoader: loaded all shared libraries");
4054

41-
copyProjDb();
4255

4356
IS_LOADED = true;
4457
} catch (Exception ex) {
@@ -49,7 +62,6 @@ public class JNIGdalLoader {
4962
}
5063
}
5164

52-
5365
public static void load() {
5466
// Just make sure it that it is loaded.
5567
// 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 {
7183
return modules.toArray(new String[0]);
7284
}
7385

74-
private static void copyProjDb() throws IOException {
86+
private static void copyProjDb(String destPath) throws IOException {
7587
try (InputStream in = NativeLibraryLoader.class.getResourceAsStream("/" + PROJ_DB_PATH)) {
7688
Objects.requireNonNull(in, String.format("Resource as stream for '%s' is null", PROJ_DB_PATH));
77-
Files.createDirectories(Paths.get(TEMP_PATH_FOR_PROJ_DATA));
78-
String destPath = TEMP_PATH_FOR_PROJ_DATA + "/proj.db";
7989
Files.copy(in, Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING);
8090
System.out.printf("JNIGdalLoader java: Copied resource at %s to %s\n", PROJ_DB_PATH, destPath);
8191
}
8292
}
8393

94+
private static void cleanUpOnShutdown() {
95+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
96+
try {
97+
cleanUp();
98+
} catch (IOException ex) {
99+
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());
100+
ex.printStackTrace();
101+
}
102+
}));
103+
}
104+
105+
private static void cleanUp() throws IOException {
106+
Files.delete(Paths.get(TEMP_PATH_FOR_PROJ_DATA + "/proj.db"));
107+
TEMP_PATH_FOR_PROJ_DATA.delete();
108+
}
109+
84110
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.reasy.gdal;
2+
3+
import com.sun.jna.Library;
4+
5+
public interface LibC extends Library {
6+
int setenv(String name, String value, int overwrite);
7+
}

0 commit comments

Comments
 (0)