Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ for the Linux x64 and Darwin x64 platforms.

## Unreleased Changes

## [4.5.0] - 2026-08-14
### Added
- `reverseDirectedEdge` function. (#207)

### Fixed
- Android AAR should link to libm correctly. (#211)

### Changed
- Upgraded the core library to v4.5.0. (#207)
- Added tests using upstream uber/h3 test files. (#210)

## [4.4.0] - 2025-12-12
### Added
- `constructCell`, `isValidIndex`, and `getIndexDigit` functions.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Coverage Status](https://coveralls.io/repos/github/uber/h3-java/badge.svg?branch=master)](https://coveralls.io/github/uber/h3-java?branch=master)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.uber/h3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.uber/h3)
[![H3 Version](https://img.shields.io/badge/h3-v4.4.1-blue.svg)](https://github.com/uber/h3/releases/tag/v4.4.1)
[![H3 Version](https://img.shields.io/badge/h3-v4.5.0-blue.svg)](https://github.com/uber/h3/releases/tag/v4.5.0)

This library provides Java bindings for the [H3 Core Library](https://github.com/uber/h3). For API reference, please see the [H3 Documentation](https://h3geo.org/).

Expand All @@ -18,14 +18,14 @@ Add it to your pom.xml:
<dependency>
<groupId>com.uber</groupId>
<artifactId>h3</artifactId>
<version>4.4.0</version>
<version>4.5.0</version>
</dependency>
```

Or, using Gradle:

```gradle
compile("com.uber:h3:4.4.0")
compile("com.uber:h3:4.5.0")
```

Encode a location into a hexagon address:
Expand Down
6 changes: 6 additions & 0 deletions docs/DEVELOPERS-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This directory contains documentation for building and integrating H3-Java.

## Formatting

```
./gradlew spotlessApply
```

## Troubleshooting

### docker: Error response from daemon: error while creating mount source path
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
org.gradle.configuration-cache=false

# No spaces on the following line, needed by release.yml:
version=4.4.1-SNAPSHOT
version=4.5.0
2 changes: 1 addition & 1 deletion h3version.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
h3.git.reference=v4.4.1
h3.git.reference=v4.5.0
17 changes: 17 additions & 0 deletions src/main/c/h3-java/src/jniapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,23 @@ Java_com_uber_h3core_NativeMethods_directedEdgeToBoundary(JNIEnv *env,
}
}

/*
* Class: com_uber_h3core_NativeMethods
* Method: reverseDirectedEdge
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_com_uber_h3core_NativeMethods_reverseDirectedEdge(
JNIEnv *env, jobject thiz, jlong h3) {
H3Index out;
H3Error err = reverseDirectedEdge(h3, &out);
if (err) {
ThrowH3Exception(env, err);
return -1;
}

return out;
}

/*
* Class: com_uber_h3core_NativeMethods
* Method: maxFaceCount
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/uber/h3core/H3Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,16 @@ public List<LatLng> directedEdgeToBoundary(String h3) {
return directedEdgeToBoundary(stringToH3(h3));
}

/** Returns the directed edge with origin and destination cells reversed. */
public long reverseDirectedEdge(long h3) {
return h3Api.reverseDirectedEdge(h3);
}

/** Returns the directed edge with origin and destination cells reversed. */
public String reverseDirectedEdge(String h3) {
return h3ToString(reverseDirectedEdge(stringToH3(h3)));
}

/**
* Find all icosahedron faces intersected by a given H3 index, represented as integers from 0-19.
*
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/uber/h3core/NativeMethods.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ native void polygonToCells(

native int directedEdgeToBoundary(long h3, double[] verts);

native long reverseDirectedEdge(long h3);

native int maxFaceCount(long h3);

native void getIcosahedronFaces(long h3, int[] faces);
Expand Down
4 changes: 4 additions & 0 deletions src/test/java/com/uber/h3core/TestDirectedEdges.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ void unidirectionalEdges() {

List<LatLng> boundary = h3.directedEdgeToBoundary(edge);
assertEquals(2, boundary.size());

String reversed = h3.reverseDirectedEdge(edge);
assertEquals(adjacent, h3.getDirectedEdgeOrigin(reversed));
assertEquals(start, h3.getDirectedEdgeDestination(reversed));
}

@Test
Expand Down
118 changes: 92 additions & 26 deletions src/test/java/com/uber/h3core/TestRegion.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,32 @@ void h3SetToMultiPolygonSingle() {
h3.cellsToMultiPolygon(ImmutableList.of(testIndex), true);

// This is tricky, because output in an order starting from any vertex
// would also be correct, but that's difficult to assert and there's
// value in being specific here
// would also be correct, but that's difficult to assert. We try to find
// some valid ordering and then assert from there.

assertEquals(1, multiBounds.size());
assertEquals(1, multiBounds.get(0).size());
assertEquals(actualBounds.size() + 1, multiBounds.get(0).get(0).size());

int[] expectedIndices = {0, 1, 2, 3, 4, 5, 0};
int startOffset = -1;
for (int i = 0; i < actualBounds.size(); i++) {
if (floatEquals(actualBounds.get(i).lat, multiBounds.get(0).get(0).get(0).lat)
&& floatEquals(actualBounds.get(i).lng, multiBounds.get(0).get(0).get(0).lng)) {
startOffset = i;
break;
}
}
assertTrue(startOffset >= 0);

int[] expectedIndices = {
startOffset,
(startOffset + 1) % 6,
(startOffset + 2) % 6,
(startOffset + 3) % 6,
(startOffset + 4) % 6,
(startOffset + 5) % 6,
startOffset
};

for (int i = 0; i < actualBounds.size(); i++) {
assertEquals(
Expand All @@ -389,14 +407,32 @@ void h3SetToMultiPolygonSingleNonGeoJson() {
h3.cellsToMultiPolygon(ImmutableList.of(testIndex), false);

// This is tricky, because output in an order starting from any vertex
// would also be correct, but that's difficult to assert and there's
// value in being specific here
// would also be correct, but that's difficult to assert. We try to find
// some valid ordering and then assert from there.

assertEquals(1, multiBounds.size());
assertEquals(1, multiBounds.get(0).size());
assertEquals(actualBounds.size(), multiBounds.get(0).get(0).size());

int[] expectedIndices = {0, 1, 2, 3, 4, 5};
int startOffset = -1;
for (int i = 0; i < actualBounds.size(); i++) {
if (floatEquals(actualBounds.get(i).lat, multiBounds.get(0).get(0).get(0).lat)
&& floatEquals(actualBounds.get(i).lng, multiBounds.get(0).get(0).get(0).lng)) {
startOffset = i;
break;
}
}
assertTrue(startOffset >= 0);

int[] expectedIndices = {
startOffset,
(startOffset + 1) % 6,
(startOffset + 2) % 6,
(startOffset + 3) % 6,
(startOffset + 4) % 6,
(startOffset + 5) % 6,
startOffset
};

for (int i = 0; i < actualBounds.size(); i++) {
assertEquals(
Expand All @@ -422,26 +458,56 @@ void h3SetToMultiPolygonContiguous2() {
assertEquals(1, multiBounds.get(0).size());
assertEquals(10, multiBounds.get(0).get(0).size());

assertEquals(actualBounds.get(1).lat, multiBounds.get(0).get(0).get(0).lat, EPSILON);
assertEquals(actualBounds.get(2).lat, multiBounds.get(0).get(0).get(1).lat, EPSILON);
assertEquals(actualBounds.get(3).lat, multiBounds.get(0).get(0).get(2).lat, EPSILON);
assertEquals(actualBounds.get(4).lat, multiBounds.get(0).get(0).get(3).lat, EPSILON);
assertEquals(actualBounds.get(5).lat, multiBounds.get(0).get(0).get(4).lat, EPSILON);
assertEquals(actualBounds2.get(4).lat, multiBounds.get(0).get(0).get(5).lat, EPSILON);
assertEquals(actualBounds2.get(5).lat, multiBounds.get(0).get(0).get(6).lat, EPSILON);
assertEquals(actualBounds2.get(0).lat, multiBounds.get(0).get(0).get(7).lat, EPSILON);
assertEquals(actualBounds2.get(1).lat, multiBounds.get(0).get(0).get(8).lat, EPSILON);
assertEquals(actualBounds2.get(2).lat, multiBounds.get(0).get(0).get(9).lat, EPSILON);
assertEquals(actualBounds.get(1).lng, multiBounds.get(0).get(0).get(0).lng, EPSILON);
assertEquals(actualBounds.get(2).lng, multiBounds.get(0).get(0).get(1).lng, EPSILON);
assertEquals(actualBounds.get(3).lng, multiBounds.get(0).get(0).get(2).lng, EPSILON);
assertEquals(actualBounds.get(4).lng, multiBounds.get(0).get(0).get(3).lng, EPSILON);
assertEquals(actualBounds.get(5).lng, multiBounds.get(0).get(0).get(4).lng, EPSILON);
assertEquals(actualBounds2.get(4).lng, multiBounds.get(0).get(0).get(5).lng, EPSILON);
assertEquals(actualBounds2.get(5).lng, multiBounds.get(0).get(0).get(6).lng, EPSILON);
assertEquals(actualBounds2.get(0).lng, multiBounds.get(0).get(0).get(7).lng, EPSILON);
assertEquals(actualBounds2.get(1).lng, multiBounds.get(0).get(0).get(8).lng, EPSILON);
assertEquals(actualBounds2.get(2).lng, multiBounds.get(0).get(0).get(9).lng, EPSILON);
int offset = -1;
for (int i = 0; i < multiBounds.get(0).get(0).size(); i++) {
if (floatEquals(actualBounds.get(1).lat, multiBounds.get(0).get(0).get(i).lat)
&& floatEquals(actualBounds.get(1).lng, multiBounds.get(0).get(0).get(i).lng)) {
offset = i;
break;
}
}
assertTrue(offset >= 0);

assertEquals(
actualBounds.get(1).lat, multiBounds.get(0).get(0).get((offset + 0) % 10).lat, EPSILON);
assertEquals(
actualBounds.get(2).lat, multiBounds.get(0).get(0).get((offset + 1) % 10).lat, EPSILON);
assertEquals(
actualBounds.get(3).lat, multiBounds.get(0).get(0).get((offset + 2) % 10).lat, EPSILON);
assertEquals(
actualBounds.get(4).lat, multiBounds.get(0).get(0).get((offset + 3) % 10).lat, EPSILON);
assertEquals(
actualBounds.get(5).lat, multiBounds.get(0).get(0).get((offset + 4) % 10).lat, EPSILON);
assertEquals(
actualBounds2.get(4).lat, multiBounds.get(0).get(0).get((offset + 5) % 10).lat, EPSILON);
assertEquals(
actualBounds2.get(5).lat, multiBounds.get(0).get(0).get((offset + 6) % 10).lat, EPSILON);
assertEquals(
actualBounds2.get(0).lat, multiBounds.get(0).get(0).get((offset + 7) % 10).lat, EPSILON);
assertEquals(
actualBounds2.get(1).lat, multiBounds.get(0).get(0).get((offset + 8) % 10).lat, EPSILON);
assertEquals(
actualBounds2.get(2).lat, multiBounds.get(0).get(0).get((offset + 9) % 10).lat, EPSILON);
assertEquals(
actualBounds.get(1).lng, multiBounds.get(0).get(0).get((offset + 0) % 10).lng, EPSILON);
assertEquals(
actualBounds.get(2).lng, multiBounds.get(0).get(0).get((offset + 1) % 10).lng, EPSILON);
assertEquals(
actualBounds.get(3).lng, multiBounds.get(0).get(0).get((offset + 2) % 10).lng, EPSILON);
assertEquals(
actualBounds.get(4).lng, multiBounds.get(0).get(0).get((offset + 3) % 10).lng, EPSILON);
assertEquals(
actualBounds.get(5).lng, multiBounds.get(0).get(0).get((offset + 4) % 10).lng, EPSILON);
assertEquals(
actualBounds2.get(4).lng, multiBounds.get(0).get(0).get((offset + 5) % 10).lng, EPSILON);
assertEquals(
actualBounds2.get(5).lng, multiBounds.get(0).get(0).get((offset + 6) % 10).lng, EPSILON);
assertEquals(
actualBounds2.get(0).lng, multiBounds.get(0).get(0).get((offset + 7) % 10).lng, EPSILON);
assertEquals(
actualBounds2.get(1).lng, multiBounds.get(0).get(0).get((offset + 8) % 10).lng, EPSILON);
assertEquals(
actualBounds2.get(2).lng, multiBounds.get(0).get(0).get((offset + 9) % 10).lng, EPSILON);
}

@Test
Expand Down
8 changes: 8 additions & 0 deletions src/test/java/com/uber/h3core/v3/BaseTestH3CoreV3.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ public abstract class BaseTestH3CoreV3 {
public static void setup() throws IOException {
h3 = H3CoreV3.newInstance();
}

public static boolean floatEquals(double f1, double f2) {
return floatEquals(f1, f2, EPSILON);
}

public static boolean floatEquals(double f1, double f2, double epsilon) {
return (Math.abs(f1 - f2) < epsilon);
}
}
Loading
Loading