diff --git a/pom.xml b/pom.xml
index 7782f15d..14c006bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,21 @@
msgpack-core
0.8.22
+
+ org.slf4j
+ slf4j-api
+ 1.7.30
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.30
+
+
+ log4j
+ log4j
+ 1.2.17
+
org.junit.jupiter
diff --git a/src/main/java/com/onthegomap/flatmap/App.java b/src/main/java/com/onthegomap/flatmap/App.java
deleted file mode 100644
index a50ff83c..00000000
--- a/src/main/java/com/onthegomap/flatmap/App.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.onthegomap.flatmap;
-
-public record App() {
-
- record Greeting(int x) {
-
- }
-
- public static void main(String[] args) {
- System.out.println(new App().getGreeting());
- }
-
- public String getGreeting() {
- return "hello world " + new Greeting(1);
- }
-}
diff --git a/src/main/java/com/onthegomap/flatmap/GeoUtils.java b/src/main/java/com/onthegomap/flatmap/GeoUtils.java
new file mode 100644
index 00000000..034b3ce2
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/GeoUtils.java
@@ -0,0 +1,53 @@
+package com.onthegomap.flatmap;
+
+public class GeoUtils {
+
+ private static final double DEGREES_TO_RADIANS = Math.PI / 180;
+ private static final double RADIANS_TO_DEGREES = 180 / Math.PI;
+ private static final double MAX_LAT = getWorldLat(-0.1);
+ private static final double MIN_LAT = getWorldLat(1.1);
+ public static double[] WORLD_BOUNDS = new double[]{0, 0, 1, 1};
+ public static double[] WORLD_LAT_LON_BOUNDS = toLatLonBoundsBounds(WORLD_BOUNDS);
+
+ public static double[] toLatLonBoundsBounds(double[] worldBounds) {
+ return new double[]{
+ getWorldLon(worldBounds[0]),
+ getWorldLat(worldBounds[1]),
+ getWorldLon(worldBounds[2]),
+ getWorldLat(worldBounds[3])
+ };
+ }
+
+ public static double[] toWorldBounds(double[] lonLatBounds) {
+ return new double[]{
+ getWorldX(lonLatBounds[0]),
+ getWorldY(lonLatBounds[1]),
+ getWorldX(lonLatBounds[2]),
+ getWorldY(lonLatBounds[3])
+ };
+ }
+
+ public static double getWorldLon(double x) {
+ return x * 360 - 180;
+ }
+
+ public static double getWorldLat(double y) {
+ double n = Math.PI - 2 * Math.PI * y;
+ return RADIANS_TO_DEGREES * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
+ }
+
+ public static double getWorldX(double lon) {
+ return (lon + 180) / 360;
+ }
+
+ public static double getWorldY(double lat) {
+ if (lat <= MIN_LAT) {
+ return 1.1;
+ }
+ if (lat >= MAX_LAT) {
+ return -0.1;
+ }
+ double sin = Math.sin(lat * DEGREES_TO_RADIANS);
+ return 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
+ }
+}
diff --git a/src/main/java/com/onthegomap/flatmap/OsmInputFile.java b/src/main/java/com/onthegomap/flatmap/OsmInputFile.java
new file mode 100644
index 00000000..e5b93fb3
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/OsmInputFile.java
@@ -0,0 +1,62 @@
+package com.onthegomap.flatmap;
+
+import com.google.protobuf.ByteString;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import org.openstreetmap.osmosis.osmbinary.Fileformat.Blob;
+import org.openstreetmap.osmosis.osmbinary.Fileformat.BlobHeader;
+import org.openstreetmap.osmosis.osmbinary.Osmformat.HeaderBBox;
+import org.openstreetmap.osmosis.osmbinary.Osmformat.HeaderBlock;
+import org.openstreetmap.osmosis.osmbinary.file.FileFormatException;
+
+public class OsmInputFile {
+
+ private final File file;
+
+ public OsmInputFile(File file) {
+ this.file = file;
+ }
+
+ public double[] getBounds() {
+ try (var input = new FileInputStream(file)) {
+ var datinput = new DataInputStream(input);
+ int headersize = datinput.readInt();
+ if (headersize > 65536) {
+ throw new FileFormatException(
+ "Unexpectedly long header 65536 bytes. Possibly corrupt file " + file);
+ }
+ byte[] buf = datinput.readNBytes(headersize);
+ BlobHeader header = BlobHeader.parseFrom(buf);
+ if (!header.getType().equals("OSMHeader")) {
+ throw new IllegalArgumentException("Expecting OSMHeader got " + header.getType());
+ }
+ buf = datinput.readNBytes(header.getDatasize());
+ Blob blob = Blob.parseFrom(buf);
+ ByteString data = null;
+ if (blob.hasRaw()) {
+ data = blob.getRaw();
+ } else if (blob.hasZlibData()) {
+ byte[] buf2 = new byte[blob.getRawSize()];
+ Inflater decompresser = new Inflater();
+ decompresser.setInput(blob.getZlibData().toByteArray());
+ decompresser.inflate(buf2);
+ decompresser.end();
+ data = ByteString.copyFrom(buf2);
+ }
+ HeaderBlock headerblock = HeaderBlock.parseFrom(data);
+ HeaderBBox bbox = headerblock.getBbox();
+ return new double[]{
+ bbox.getLeft() / 1e9,
+ bbox.getBottom() / 1e9,
+ bbox.getRight() / 1e9,
+ bbox.getTop() / 1e9
+ };
+ } catch (IOException | DataFormatException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/onthegomap/flatmap/profiles/Arguments.java b/src/main/java/com/onthegomap/flatmap/profiles/Arguments.java
new file mode 100644
index 00000000..28e7718c
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/profiles/Arguments.java
@@ -0,0 +1,78 @@
+package com.onthegomap.flatmap.profiles;
+
+import com.onthegomap.flatmap.GeoUtils;
+import com.onthegomap.flatmap.OsmInputFile;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Arguments {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Arguments.class);
+
+ public Arguments(String[] args) {
+ }
+
+ private String getArg(String key, String defaultValue) {
+ return System.getProperty(key, defaultValue).trim();
+ }
+
+ public double[] bounds(String arg, String description, OsmInputFile osmInputFile) {
+ String input = System.getProperty(arg, null);
+ double[] result;
+ if (input == null) {
+ // get from osm.pbf
+ result = osmInputFile.getBounds();
+ } else if ("world".equalsIgnoreCase(input)) {
+ result = GeoUtils.WORLD_LAT_LON_BOUNDS;
+ } else {
+ result = Stream.of(input.split("[\\s,]+")).mapToDouble(Double::parseDouble).toArray();
+ }
+ LOGGER.info(description + ": " + Arrays.toString(result));
+ return result;
+ }
+
+ public String get(String arg, String description, String defaultValue) {
+ String value = getArg(arg, defaultValue);
+ LOGGER.info(description + ": " + value);
+ return value;
+ }
+
+ public File file(String arg, String description, String defaultValue) {
+ String value = getArg(arg, defaultValue);
+ File file = new File(value);
+ LOGGER.info(description + ": " + value);
+ return file;
+ }
+
+ public File inputFile(String arg, String description, String defaultValue) {
+ File file = file(arg, description, defaultValue);
+ if (!file.exists()) {
+ throw new IllegalArgumentException(file + " does not exist");
+ }
+ return file;
+ }
+
+ public boolean get(String arg, String description, boolean defaultValue) {
+ boolean value = "true".equalsIgnoreCase(getArg(arg, Boolean.toString(defaultValue)));
+ LOGGER.info(description + ": " + value);
+ return value;
+ }
+
+ public List get(String arg, String description, String[] defaultValue) {
+ String value = getArg(arg, String.join(",", defaultValue));
+ List results = List.of(value.split("[\\s,]+"));
+ LOGGER.info(description + ": " + value);
+ return results;
+ }
+
+ public int threads() {
+ String value = getArg("threads", Integer.toString(Runtime.getRuntime().availableProcessors()));
+ int threads = Math.max(2, Integer.parseInt(value));
+ LOGGER.info("num threads: " + threads);
+ return threads;
+ }
+}
diff --git a/src/main/java/com/onthegomap/flatmap/profiles/OpenMapTilesProfile.java b/src/main/java/com/onthegomap/flatmap/profiles/OpenMapTilesProfile.java
new file mode 100644
index 00000000..7955904d
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/profiles/OpenMapTilesProfile.java
@@ -0,0 +1,41 @@
+package com.onthegomap.flatmap.profiles;
+
+
+import com.graphhopper.util.StopWatch;
+import com.onthegomap.flatmap.OsmInputFile;
+import java.io.File;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OpenMapTilesProfile implements Profile {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(OpenMapTilesProfile.class);
+
+ public static void main(String[] args) {
+ StopWatch watch = new StopWatch().start();
+ Arguments arguments = new Arguments(args);
+ OsmInputFile osmInputFile = new OsmInputFile(arguments.inputFile("input", "OSM input file",
+ "./data/sources/massachusetts-latest.osm.pbf"));
+ File centerlines = arguments.inputFile("centerline", "lake centerlines input",
+ "./data/sources/lake_centerline.shp.zip");
+ File naturalEarth = arguments.inputFile("natural_earth", "natural earth input",
+ "./data/sources/natural_earth_vector.sqlite.zip");
+ File waterPolygons = arguments.inputFile("water_polygons", "water polygons input",
+ "./data/sources/water-polygons-split-3857.zip");
+ double[] bounds = arguments.bounds("bounds", "bounds", osmInputFile);
+ int threads = arguments.threads();
+ File tmpDir = arguments.file("tmpdir", "temp directory", "./data/tmp");
+ boolean fetchWikidata = arguments.get("fetch_wikidata", "fetch wikidata translations", false);
+ boolean useWikidata = arguments.get("use_wikidata", "use wikidata translations", true);
+ File wikidataNamesFile = arguments.file("wikidata_cache", "wikidata cache file",
+ "./data/sources/wikidata_names.json");
+ String output = arguments.get("output", "mbtiles output file", "./massachusetts.mbtiles");
+ List languages = arguments.get("name_languages", "languages to use",
+ "en,ru,ar,zh,ja,ko,fr,de,fi,pl,es,be,br,he".split(","));
+
+ var profile = new OpenMapTilesProfile();
+
+ LOGGER.info("FINISHED! " + watch.stop());
+ }
+}
diff --git a/src/main/java/com/onthegomap/flatmap/profiles/Profile.java b/src/main/java/com/onthegomap/flatmap/profiles/Profile.java
new file mode 100644
index 00000000..a18cf663
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/profiles/Profile.java
@@ -0,0 +1,5 @@
+package com.onthegomap.flatmap.profiles;
+
+public interface Profile {
+
+}
diff --git a/src/main/java/com/onthegomap/flatmap/stats/Stats.java b/src/main/java/com/onthegomap/flatmap/stats/Stats.java
new file mode 100644
index 00000000..79321b74
--- /dev/null
+++ b/src/main/java/com/onthegomap/flatmap/stats/Stats.java
@@ -0,0 +1,5 @@
+package com.onthegomap.flatmap.stats;
+
+public interface Stats {
+
+}
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
new file mode 100644
index 00000000..52cbe313
--- /dev/null
+++ b/src/main/resources/log4j.properties
@@ -0,0 +1,7 @@
+# Root logger option
+log4j.rootLogger=INFO, stdout
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss.SSS} [%t] %p - %m%n
diff --git a/src/test/java/com/onthegomap/flatmap/AppTest.java b/src/test/java/com/onthegomap/flatmap/AppTest.java
deleted file mode 100644
index ec83f5a0..00000000
--- a/src/test/java/com/onthegomap/flatmap/AppTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.onthegomap.flatmap;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-public class AppTest {
-
- @Test
- public void testFast() {
- assertEquals("hello world", new App().getGreeting());
- }
-}
diff --git a/src/test/java/com/onthegomap/flatmap/GeoUtilsTest.java b/src/test/java/com/onthegomap/flatmap/GeoUtilsTest.java
new file mode 100644
index 00000000..9a88a88c
--- /dev/null
+++ b/src/test/java/com/onthegomap/flatmap/GeoUtilsTest.java
@@ -0,0 +1,5 @@
+package com.onthegomap.flatmap;
+
+public class GeoUtilsTest {
+
+}