Timeshape

repository·master·Indexed 42 days ago

https://github.com/romaniakovlev/timeshape

A Java library for determining the time zone of geographic coordinates based on OpenStreetMap data. It supports high-performance queries for single points, overlapping zones, and polylines. The library utilizes a quad tree spatial index and compressed Protocol Buffers with ZStandard compression to optimize memory and speed. Version 2026b.29 supports Java 8 and Android.

Tokens
2.8K
Snippets
14
Records
16
Agent score
53%

What's inside Timeshape

  1. Enable Accelerated Geometry for faster queries

    master

    When calling TimeZoneEngine.initialize, you can pass a boolean parameter accelerateGeometry. If set to true, Timeshape builds a bitmap index for each polygon. This increases initialization time and memory usage but speeds up typical point-in-polygon queries by approximately 2.5 times.

    // Example of enabling acceleration (parameter is the last argument in relevant overloads)
    TimeZoneEngine engine = TimeZoneEngine.initialize(timeZones, true);
  2. How Timeshape works and its data format

    master

    Timeshape converts GeoJSON source data containing time zones into a highly efficient format for storage and querying. It packages the converted data together with the querying code into a single JAR file.

    Key technical details:

    • Data Format: Uses compressed Protocol Buffers (protobuf) with ZStandard compression.
    • Optimization: Uses float instead of double for geographic coordinates to reduce size (8 bytes per point instead of 16 bytes), which provides significant savings without sacrificing necessary precision.
    • Indexing: At runtime, it builds a spatial index using a quad tree (via the Esri geometry API) to enable efficient querying.
  3. Build and run Timeshape locally with sbt

    master

    Timeshape uses sbt as its build system. To build and run the project locally:

    1. Install sbt and JDK (JDK 8 is proven, but newer versions are recommended).
    2. Navigate to the source code directory and run the sbt command.
    3. Use the following commands in the sbt console:
      • core/test: Executes the tests for the core project.
      • testApp/run: Runs the test application, which queries one time zone and prints memory usage.
      • core/publishLocal: Publishes the core project to your local Ivy repository (default: $HOME/.ivy2/local/). Use this if you have made local modifications.
      • core/publishM2: Publishes the core project to your local Maven repository (default: $HOME/.m2/repository/).

    Note: Set the version to snapshot to ensure local publication works most effectively.

    sbt
    > core/test
    > testApp/run
    > core/publishLocal
    > core/publishM2
  4. Install Timeshape on Android

    master

    Android developers must configure the app-level build.gradle to exclude the standard compression library and explicitly import the native component AAR for zstd-jni to ensure compatibility.

    implementation('net.iakovlev:timeshape:2026b.29') {
      // Exclude standard compression library
      exclude group: 'com.github.luben', module: 'zstd-jni'
    }
    // Import aar for native component compilation
    implementation 'com.github.luben:zstd-jni:1.5.7-11@aar'
  5. Use a locally modified version of Timeshape

    master

    If you have published a modified version of Timeshape locally using core/publishLocal or core/publishM2, you must configure your project's build definition to include the local repository.

    ### With sbt/scala
    ```scala
    resolvers += Resolver.mavenLocal

    With gradle/java

    repositories {
        mavenCentral()
        mavenLocal()
    }
    
    dependencies {
        compile group: 'net.iakovlev', name: 'timeshape', version: '2023b.18-SNAPSHOT'
    }
  6. Install Timeshape via Maven

    master

    Add the following dependency to your pom.xml to use Timeshape in your Java project. Note that starting from release 2023b.20, the library supports Java 8.

    <dependency>
        <groupId>net.iakovlev</groupId>
        <artifactId>timeshape</artifactId>
        <version>2026b.29</version>
    </dependency>
  7. Reduce memory usage with TimeZoneEngine.initialize

    master

    The default memory footprint for loading the entire world's time zone data is approximately 128 MB. You can reduce this memory usage by limiting the geographic area of the time zones loaded. This is achieved by calling TimeZoneEngine.initialize with specific coordinate boundaries.

    TimeZoneEngine.initialize(double minlat, double minlon, double maxlat, double maxlon);
  8. Initialize the TimeZoneEngine

    master

    The net.iakovlev.timeshape.TimeZoneEngine class is the primary entry point. Initialization builds an index and reads data from resources, which takes approximately 1 second. It is recommended to perform initialization only once during the program's lifetime.

    There are several ways to initialize the engine:

    1. Full World Data: Loads all available time zone data.
    2. Bounding Box: Reduces memory usage by loading only data within a specific geographic area. Note: The time zone must be completely covered by the bounding box, not just intersected by it.
    3. Specific ZoneIds: Loads only the requested ZoneIds.
    4. External File: Uses a TarArchiveInputStream to load data from an external file (format must match the builder sbt project output). The caller is responsible for closing the input stream.
    // Initialize with the whole world
    TimeZoneEngine engine = TimeZoneEngine.initialize();
    
    // Initialize with a bounding box (minLat, minLon, maxLat, maxLon)
    TimeZoneEngine engine = TimeZoneEngine.initialize(47.0599, 4.8237, 55.3300, 15.2486);
    
    // Initialize with specific ZoneIds
    Set<ZoneId> timeZones = new HashSet<>();
    timeZones.add(ZoneId.of("Europe/Berlin"));
    timeZones.add(ZoneId.of("America/Detroit"));
    TimeZoneEngine engine = TimeZoneEngine.initialize(timeZones, true);
  9. Query a polyline for optimized performance

    master

    To query a sequence of points (like a GPS trace), use engine.queryPolyline(double[] points). This method is significantly faster than calling query() individually for every point in the trace.

    // points should be an array of coordinates
    List<SameZoneSpan> spans = engine.queryPolyline(points);
  10. Query all ZoneIds for overlapping coordinates

    master

    Because some geographic points belong to multiple time zones, use engine.queryAll(double latitude, double longitude) to retrieve a List<ZoneId> containing all applicable zones. This is preferred over query() when you need to apply specific business logic to choose between overlapping zones.

    import java.time.ZoneId;
    import java.util.List;
    
    List<ZoneId> allZones = engine.queryAll(52.52, 13.40);
  11. Query a single ZoneId for a coordinate

    master

    Use engine.query(double latitude, double longitude) to find the ZoneId for a specific point. If the point belongs to multiple overlapping time zones, this method returns only one arbitrary ZoneId wrapped in an Optional.

    import java.util.Optional;
    import java.time.ZoneId;
    
    Optional<ZoneId> maybeZoneId = engine.query(52.52, 13.40);