Skip to content

Commit 2a4aa2e

Browse files
authored
Make multiplatform (#19)
* migrate to kmp (second try) * convert app to kotlin * for java, mark as throwing io exception * consistent naming * rather use CountryBoundariesUtils on java * update readme * use shared utility functions for test (because there is some bug in kmp...) * add yarn.lock * correct name * Revert "add yarn.lock" This reverts commit aa73f3a. * re-add benchmark script but as an own module
1 parent d773b9a commit 2a4aa2e

50 files changed

Lines changed: 1259 additions & 1589 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
/local.properties
44
/.idea
55
.DS_Store
6-
/build
6+
**/build
77
/captures
88
.externalNativeBuild
9-
/boundaries.osm
9+
/boundaries.osm
10+
.kotlin

README.md

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,58 @@
11
# countryboundaries
22

3-
Java library to enable fast offline reverse country geocoding: Find out the country / state in which a geo position is located.
4-
5-
It is well tested, does not have any dependencies, works well on Android and most importantly, is very fast.
6-
7-
Requires Java 8.
3+
Kotlin multiplatform library to find in which region a given geo position is located fast.
84

95
## Copyright and License
106

11-
© 2018-2023 Tobias Zwick. This library is released under the terms of the [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-3.0.html) (LGPL).
7+
© 2018-2025 Tobias Zwick. This library is released under the terms of the [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-3.0.html) (LGPL).
128
The default data used is derived from OpenStreetMap and thus © OpenStreetMap contributors and licensed under the [Open Data Commons Open Database License](https://opendatacommons.org/licenses/odbl/) (ODbL).
139

1410
## Usage
1511

16-
Add [`de.westnordost:countryboundaries:2.1`](https://mvnrepository.com/artifact/de.westnordost/countryboundaries/2.1) as a Maven dependency or download the jar from there.
12+
Add `de.westnordost:countryboundaries:3.0.0` as a Maven dependency or download the jar from there.
1713

18-
```java
14+
```kotlin
1915
// load data. You should do this once and use CountryBoundaries as a singleton.
20-
byte[] bytes = Files.readAllBytes(new File("boundaries.ser").toPath());
21-
CountryBoundaries boundaries = CountryBoundaries.load(new ByteArrayInputStream(bytes));
22-
23-
// get country ids by position
24-
boundaries.getIds(-96.7954, 32.7816); // returns "US-TX","US"
25-
26-
// check if a position is in a country
27-
boundaries.isIn(8.6910, 47.6973, "DE"); // returns true
16+
val source = FileSystem.source("boundaries.ser").buffered()
17+
val boundaries = source.use { CountryBoundaries.deserializeFrom(it) }
18+
19+
// get ids of regions at position
20+
boundaries.getIds(
21+
longitude = -96.7954,
22+
latitude = 32.7816
23+
) // returns "US-TX","US"
24+
25+
// check if a position is in region with specified id
26+
boundaries.isIn(
27+
longitude = 8.6910,
28+
latitude = 47.6973,
29+
id = "DE"
30+
) // returns true
31+
32+
// get ids of the regions that are present in the given bounds
33+
boundaries.getIntersectingIds(
34+
minLongitude = 50.6,
35+
minLatitude = 5.9,
36+
maxLongitude = 50.8,
37+
maxLatitude = 6.1
38+
) // returns "NL", "LU", "DE", "BE", "BE-VLG", "BE-WAL"
39+
40+
// get ids of the regions that completely cover the given bounds
41+
boundaries.getContainingIds(
42+
minLongitude = 50.6,
43+
minLatitude = 5.9,
44+
maxLongitude = 50.8,
45+
maxLatitude = 6.1
46+
) // returns empty list
47+
```
2848

29-
// get which country ids can be found within the given bounding box
30-
boundaries.getIntersectingIds(50.6, 5.9, 50.8, 6.1) // returns "NL", "LU", "DE", "BE", "BE-VLG", "BE-WAL"
49+
On Java, use this to load the boundaries:
3150

32-
// get which country ids completely cover the given bounding box
33-
boundaries.getContainingIds(50.6, 5.9, 50.8, 6.1) // returns empty list
51+
```java
52+
CountryBoundaries boundaries = null;
53+
try (FileInputStream fis = new FileInputStream("boundaries.ser")) {
54+
boundaries = CountryBoundariesUtils.deserializeFrom(fis);
55+
}
3456
```
3557

3658
The default data file is in `/data/`. Don't forget to give attribution when distributing it. See below.

benchmark/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
id("java")
3+
id("application")
4+
}
5+
6+
dependencies {
7+
implementation(project(":library"))
8+
}

library/src/test/java/de/westnordost/countryboundaries/PerformanceTest.java renamed to benchmark/src/main/java/de/westnordost/countryboundaries/benchmark/Main.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
package de.westnordost.countryboundaries;
1+
package de.westnordost.countryboundaries.benchmark;
22

3+
import de.westnordost.countryboundaries.CountryBoundaries;
4+
import de.westnordost.countryboundaries.CountryBoundariesUtils;
35

46
import java.io.ByteArrayInputStream;
57
import java.io.File;
68
import java.nio.file.Files;
79
import java.util.Locale;
810
import java.util.Random;
911

10-
public class PerformanceTest {
12+
public class Main {
1113

1214
public static void main(String[] args) throws Exception {
1315
long time = System.nanoTime();
1416
byte[] bytes = Files.readAllBytes(new File("data/boundaries360x180.ser").toPath());
15-
CountryBoundaries boundaries = CountryBoundaries.load(new ByteArrayInputStream(bytes));
17+
CountryBoundaries boundaries = CountryBoundariesUtils.deserializeFrom(new ByteArrayInputStream(bytes));
1618
long timeSpentOnLoading = System.nanoTime() - time;
1719
System.out.println(
1820
"Loading data took " +
@@ -37,11 +39,11 @@ public static void main(String[] args) throws Exception {
3739
long timeSpentOnBoundaries = timeSpent - timeSpentOnRandom;
3840

3941
System.out.println(
40-
"Querying " + checks + " random locations took " +
41-
String.format(Locale.US, "%,.2f", timeSpentOnBoundaries * 1e-9) + " seconds " +
42-
"- so on average " +
43-
Math.round(timeSpentOnBoundaries * 1.0 / checks) + " nanoseconds"
42+
"Querying " + checks + " random locations took " +
43+
String.format(Locale.US, "%,.2f", timeSpentOnBoundaries * 1e-9) + " seconds " +
44+
"- so on average " +
45+
Math.round(timeSpentOnBoundaries * 1.0 / checks) + " nanoseconds"
4446
);
4547
}
4648

47-
}
49+
}

build.gradle renamed to build.gradle.kts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,11 @@ buildscript {
66
mavenCentral()
77
}
88
dependencies {
9-
classpath 'com.android.tools.build:gradle:7.4.2'
9+
classpath("com.android.tools.build:gradle:8.9.2")
10+
classpath(kotlin("gradle-plugin", version = "2.1.21"))
1011
}
1112
}
1213

13-
allprojects {
14-
repositories {
15-
google()
16-
mavenCentral()
17-
}
18-
}
19-
20-
tasks.register('clean', Delete) {
21-
delete rootProject.buildDir
22-
}
14+
plugins {
15+
id("org.jetbrains.kotlin.multiplatform") version "2.1.21" apply false
16+
}

generator/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

generator/src/main/java/de/westnordost/countryboundaries/CountryBoundariesGenerator.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
import com.vividsolutions.jts.geom.PrecisionModel;
1212
import com.vividsolutions.jts.index.strtree.STRtree;
1313

14-
import java.util.ArrayList;
15-
import java.util.HashMap;
16-
import java.util.List;
17-
import java.util.Map;
14+
import java.util.*;
1815

1916
public class CountryBoundariesGenerator
2017
{
@@ -57,7 +54,7 @@ public CountryBoundaries generate(int width, int height, List<Geometry> boundari
5754
}
5855
if(listener != null) listener.onProgress(1);
5956

60-
return new CountryBoundaries(raster, width, geometrySizes);
57+
return new CountryBoundaries(Arrays.asList(raster), width, geometrySizes);
6158
}
6259

6360
private CountryBoundariesCell createCell(
@@ -96,7 +93,8 @@ else if(!im.isDisjoint())
9693
private CountryAreas createCountryAreas(
9794
String areaId, Geometry intersection, double lonMin, double latMin, double lonMax, double latMax)
9895
{
99-
List<Point[]> outer = new ArrayList<>(), inner = new ArrayList<>();
96+
List<List<Point>> outer = new ArrayList<>();
97+
List<List<Point>> inner = new ArrayList<>();
10098

10199
if(intersection instanceof Polygon)
102100
{
@@ -120,10 +118,10 @@ private CountryAreas createCountryAreas(
120118
}
121119
}
122120
}
123-
return new CountryAreas(areaId,outer.toArray(new Point[][]{}), inner.toArray(new Point[][]{}));
121+
return new CountryAreas(areaId, outer, inner);
124122
}
125123

126-
private Point[] createPoints(LineString ring, double lonMin, double latMin, double lonMax, double latMax)
124+
private List<Point> createPoints(LineString ring, double lonMin, double latMin, double lonMax, double latMax)
127125
{
128126
Coordinate[] coords = ring.getCoordinates();
129127
// leave out last - not necessary
@@ -135,7 +133,7 @@ private Point[] createPoints(LineString ring, double lonMin, double latMin, doub
135133
int y = (int) ((coord.y - latMin) * 0xffff / (latMax - latMin));
136134
result[i] = new Point(x, y);
137135
}
138-
return result;
136+
return Arrays.asList(result);
139137
}
140138

141139
private STRtree buildIndex(List<Geometry> geometries)
@@ -171,7 +169,7 @@ private String getAreaId(Geometry g)
171169
if(g instanceof Polygonal)
172170
{
173171
Object data = g.getUserData();
174-
if(data != null && data instanceof String)
172+
if(data instanceof String)
175173
{
176174
return (String) data;
177175
}

generator/src/main/java/de/westnordost/countryboundaries/CountryBoundariesSerializer.java

Lines changed: 0 additions & 74 deletions
This file was deleted.

generator/src/main/java/de/westnordost/countryboundaries/Main.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.vividsolutions.jts.geom.GeometryFactory;
66

77
import java.io.ByteArrayOutputStream;
8-
import java.io.DataOutputStream;
98
import java.io.FileInputStream;
109
import java.io.FileOutputStream;
1110
import java.io.InputStreamReader;
@@ -27,7 +26,6 @@ public static void main(String[] args) throws Exception {
2726
System.err.println("Missing parameters. F.e. 'boundaries.osm 360 180' or 'boundaries.osm boundaries.json' ");
2827
return;
2928
}
30-
3129
String filename = args[0];
3230
FileInputStream is = new FileInputStream(filename);
3331
GeometryCollection geometries;
@@ -105,8 +103,7 @@ else if(filename.endsWith(".osm"))
105103
CountryBoundaries boundaries = generator.generate(width, height, geometryList);
106104
try(FileOutputStream fos = new FileOutputStream("boundaries.ser"))
107105
{
108-
DataOutputStream dos = new DataOutputStream(fos);
109-
new CountryBoundariesSerializer().write(boundaries, dos);
106+
CountryBoundariesUtils.serializeTo(fos, boundaries);
110107
}
111108
}
112109
}

0 commit comments

Comments
 (0)