This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch update-basemap
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git


The following commit(s) were added to refs/heads/update-basemap by this push:
     new a79aa7fb Compute the sequence number for planet osm
a79aa7fb is described below

commit a79aa7fb43f1fe911413efaebf4965d3a471ee71
Author: Bertil Chapuis <[email protected]>
AuthorDate: Wed Nov 15 11:19:12 2023 +0100

    Compute the sequence number for planet osm
---
 .../baremaps/openstreetmap/state/StateReader.java  | 132 ++++++++++++++++++++-
 .../baremaps/workflow/tasks/UpdateOsmDatabase.java |  35 +++---
 .../baremaps/openstreetmap/OpenStreetMapTest.java  |   2 +-
 3 files changed, 151 insertions(+), 18 deletions(-)

diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/state/StateReader.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/state/StateReader.java
index 9d8e2294..181f185f 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/state/StateReader.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/state/StateReader.java
@@ -23,22 +23,40 @@ import com.google.common.io.CharStreams;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.baremaps.openstreetmap.model.State;
 
 public class StateReader {
 
+  private final String replicationUrl;
+
+  private final boolean balancedSearch;
+
+  public StateReader() {
+    this("https://planet.osm.org/replication/hour";, true);
+  }
+
+  public StateReader(String replicationUrl, boolean balancedSearch) {
+    this.replicationUrl = replicationUrl;
+    this.balancedSearch = balancedSearch;
+  }
+
   /**
    * Parse an OSM state file.
    *
    * @param input the OpenStreetMap state file
    * @return the state
    */
-  public State state(InputStream input) throws IOException {
+  public State readState(InputStream input) throws IOException {
     InputStreamReader reader = new InputStreamReader(input, 
StandardCharsets.UTF_8);
     Map<String, String> map = new HashMap<>();
     for (String line : CharStreams.readLines(reader)) {
@@ -52,4 +70,116 @@ public class StateReader {
     LocalDateTime timestamp = 
LocalDateTime.parse(map.get("timestamp").replace("\\", ""), format);
     return new State(sequenceNumber, timestamp);
   }
+
+  public Optional<State> getStateFromTimestamp(LocalDateTime timestamp) {
+    var upper = getState(Optional.empty());
+    if (upper.isEmpty()) {
+      return Optional.empty();
+    }
+    if (timestamp.isAfter(upper.get().getTimestamp()) || 
upper.get().getSequenceNumber() <= 0) {
+      return upper;
+    }
+    var lower = Optional.<State>empty();
+    var lowerId = Optional.of(0L);
+    while (lower.isEmpty()) {
+      lower = getState(lowerId);
+      if (lower.isPresent() && lower.get().getTimestamp().isAfter(timestamp)) {
+        if (lower.get().getSequenceNumber() == 0
+            || lower.get().getSequenceNumber() + 1 >= 
upper.get().getSequenceNumber()) {
+          return lower;
+        }
+        upper = lower;
+        lower = Optional.empty();
+        lowerId = Optional.of(0L);
+      }
+      if (lower.isEmpty()) {
+        var newId = (lowerId.get() + upper.get().getSequenceNumber()) / 2;
+        if (newId <= lowerId.get()) {
+          return upper;
+        }
+        lowerId = Optional.of(newId);
+      }
+    }
+    long baseSplitId;
+    while (true) {
+      if (balancedSearch) {
+        baseSplitId = ((lower.get().getSequenceNumber() + 
upper.get().getSequenceNumber()) / 2);
+      } else {
+        var tsInt = upper.get().getTimestamp().toEpochSecond(ZoneOffset.UTC) - 
lower.get().getTimestamp().toEpochSecond(ZoneOffset.UTC);
+        var seqInt = upper.get().getSequenceNumber() - 
lower.get().getSequenceNumber();
+        var goal = timestamp.getSecond() - 
lower.get().getTimestamp().getSecond();
+        baseSplitId = lower.get().getSequenceNumber() + (long) Math.ceil(goal 
* seqInt / tsInt);
+        if (baseSplitId >= upper.get().getSequenceNumber()) {
+          baseSplitId = upper.get().getSequenceNumber() - 1;
+        }
+      }
+      var split = getState(Optional.of(baseSplitId));
+      if (split.isEmpty()) {
+        var splitId = baseSplitId - 1;
+        while (split.isEmpty() && splitId > lower.get().getSequenceNumber()) {
+          split = getState(Optional.of(splitId));
+          splitId--;
+        }
+      }
+      if (split.isEmpty()) {
+        var splitId = baseSplitId + 1;
+        while (split.isEmpty() && splitId < upper.get().getSequenceNumber()) {
+          split = getState(Optional.of(splitId));
+          splitId++;
+        }
+      }
+      if (split.isEmpty()) {
+        return lower;
+      }
+      if (split.get().getTimestamp().isBefore(timestamp)) {
+        lower = split;
+      } else {
+        upper = split;
+      }
+      if (lower.get().getSequenceNumber() + 1 >= 
upper.get().getSequenceNumber()) {
+        return lower;
+      }
+    }
+  }
+
+  public Optional<State> getState(Optional<Long> sequenceNumber) {
+    for (int i = 0; i < 3; i++) {
+      try (var inputStream = getStateUrl(sequenceNumber).openStream()) {
+        var state = new StateReader().readState(inputStream);
+        return Optional.of(state);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+    return Optional.empty();
+  }
+
+  public URL getStateUrl(Optional<Long> sequenceNumber) throws 
MalformedURLException {
+    if (sequenceNumber.isPresent()) {
+
+      var s = String.format("%09d", sequenceNumber.get());
+      var uri = String.format("%s/%s/%s/%s.%s", replicationUrl, s.substring(0, 
3), s.substring(3, 6),
+              s.substring(6, 9), "state.txt");
+      return URI.create(uri).toURL();
+    } else {
+        return new URL(replicationUrl + "/state.txt");
+    }
+  }
+
+  public static URL resolve(String replicationUrl, Long sequenceNumber, String 
extension)
+      throws MalformedURLException {
+    var s = String.format("%09d", sequenceNumber);
+    var uri = String.format("%s/%s/%s/%s.%s", replicationUrl, s.substring(0, 
3), s.substring(3, 6),
+        s.substring(6, 9), extension);
+    return URI.create(uri).toURL();
+  }
+
+  public static void main(String... args) throws MalformedURLException {
+    var reader = new StateReader();
+    var state = 
reader.getStateFromTimestamp(LocalDateTime.now().minusDays(10));
+    System.out.println(state.get().getSequenceNumber());
+    System.out.println(resolve(reader.replicationUrl, 
state.get().getSequenceNumber(), "state.txt"));
+    System.out.println(resolve(reader.replicationUrl, 
state.get().getSequenceNumber(), "osc.gz"));
+  }
+
 }
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOsmDatabase.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOsmDatabase.java
index df08d1af..ef583905 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOsmDatabase.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOsmDatabase.java
@@ -20,9 +20,6 @@ package org.apache.baremaps.workflow.tasks;
 import static org.apache.baremaps.stream.ConsumerUtils.consumeThenReturn;
 
 import java.io.BufferedInputStream;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
 import java.util.List;
 import java.util.zip.GZIPInputStream;
 import org.apache.baremaps.database.collection.DataMap;
@@ -86,39 +83,45 @@ public record UpdateOsmDatabase(Object database, Integer 
databaseSrid,
       String replicationUrl) throws Exception {
 
     var header = headerRepository.selectLatest();
-    var sequenceNumber = header.getReplicationSequenceNumber() + 1;
 
+    // If the replicationUrl is not provided, use the one from the latest 
header.
     if (replicationUrl == null) {
       replicationUrl = header.getReplicationUrl();
     }
 
+    var stateReader = new 
StateReader("https://planet.osm.org/replication/hour";, true);
+    var sequenceNumber = header.getReplicationSequenceNumber();
+
+    // If the replicationTimestamp is not provided, guess it from the 
replication timestamp.
+    if (sequenceNumber <= 0) {
+      var replicationTimestamp = header.getReplicationTimestamp();
+      var state = stateReader.getStateFromTimestamp(replicationTimestamp);
+      if (state.isPresent()) {
+        sequenceNumber = state.get().getSequenceNumber();
+      }
+    }
+
+    var nextSequenceNumber = sequenceNumber + 1;
+    var changeUrl = StateReader.resolve(replicationUrl, nextSequenceNumber, 
"osc.gz");
+    logger.info("Updating the database with the changeset: {}", changeUrl);
+
     var createGeometry = new EntityGeometryBuilder(coordinateMap, 
referenceMap);
     var reprojectGeometry = new EntityProjectionTransformer(4326, 
databaseSrid);
     var prepareGeometries = new 
ChangeEntitiesHandler(createGeometry.andThen(reprojectGeometry));
     var prepareChange = consumeThenReturn(prepareGeometries);
     var importChange = new PutChangeImporter(nodeRepository, wayRepository, 
relationRepository);
 
-    var changeUrl = resolve(replicationUrl, sequenceNumber, "osc.gz");
-    logger.info("Updating the database with the changeset: {}", changeUrl);
-
     try (var changeInputStream =
         new GZIPInputStream(new BufferedInputStream(changeUrl.openStream()))) {
       new 
XmlChangeReader().stream(changeInputStream).map(prepareChange).forEach(importChange);
     }
 
-    var stateUrl = resolve(replicationUrl, sequenceNumber, "state.txt");
+    var stateUrl = StateReader.resolve(replicationUrl, nextSequenceNumber, 
"state.txt");
     try (var stateInputStream = new 
BufferedInputStream(stateUrl.openStream())) {
-      var state = new StateReader().state(stateInputStream);
+      var state = new StateReader().readState(stateInputStream);
       headerRepository.put(new Header(state.getSequenceNumber(), 
state.getTimestamp(),
           header.getReplicationUrl(), header.getSource(), 
header.getWritingProgram()));
     }
   }
 
-  public static URL resolve(String replicationUrl, Long sequenceNumber, String 
extension)
-      throws MalformedURLException {
-    var s = String.format("%09d", sequenceNumber);
-    var uri = String.format("%s/%s/%s/%s.%s", replicationUrl, s.substring(0, 
3), s.substring(3, 6),
-        s.substring(6, 9), extension);
-    return URI.create(uri).toURL();
-  }
 }
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/OpenStreetMapTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/OpenStreetMapTest.java
index ddb90f0c..c8d57544 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/OpenStreetMapTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/OpenStreetMapTest.java
@@ -123,7 +123,7 @@ class OpenStreetMapTest {
   @Test
   void monacoStateTxt() throws URISyntaxException, IOException {
     try (InputStream inputStream = Files.newInputStream(MONACO_STATE_TXT)) {
-      State state = new StateReader().state(inputStream);
+      State state = new StateReader().readState(inputStream);
       assertEquals(2788, state.getSequenceNumber());
       assertEquals(LocalDateTime.parse("2020-11-10T21:42:03"), 
state.getTimestamp());
     }

Reply via email to