You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@baremaps.apache.org by bc...@apache.org on 2022/12/02 22:04:36 UTC

[incubator-baremaps] branch main updated: Leverage Java 17 features (#547)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new cd80dfc8 Leverage Java 17 features (#547)
cd80dfc8 is described below

commit cd80dfc858ebaea3b8635b440ef133a353cfa299
Author: Bertil Chapuis <bc...@gmail.com>
AuthorDate: Fri Dec 2 23:04:31 2022 +0100

    Leverage Java 17 features (#547)
    
    * Replace visitor pattern by instanceof
    
    * Use sealed and final classes in the openstreetmap model
    
    * Remove unnecessary classes
    
    * Improve class and variable names
---
 .gitignore                                         |  5 +-
 .../benchmarks/OpenStreetMapBenchmark.java         | 27 +++----
 .../OpenStreetMapGeometriesBenchmark.java          | 37 ++++-----
 .../{SaveBlockConsumer.java => BlockImporter.java} | 41 +++++-----
 ...SaveChangeConsumer.java => ChangeImporter.java} | 75 +++++++----------
 .../org/apache/baremaps/database/DiffService.java  | 88 +++++++++-----------
 .../apache/baremaps/database/ImportService.java    | 43 +++++-----
 .../apache/baremaps/database/UpdateService.java    | 56 ++++++-------
 .../database/collection/PostgresReferenceMap.java  |  6 +-
 .../repository/PostgresNodeRepository.java         |  2 +-
 .../repository/PostgresRelationRepository.java     |  2 +-
 .../database/repository/PostgresWayRepository.java |  2 +-
 .../{BlockFunction.java => BlobToBlockMapper.java} | 47 +++++------
 .../openstreetmap/function/BlockConsumer.java      | 53 ------------
 .../function/BlockConsumerAdapter.java             | 28 -------
 ...tityConsumer.java => BlockEntitiesHandler.java} | 30 +++----
 .../openstreetmap/function/CacheBuilder.java       | 57 +++++++++++++
 ...ityConsumer.java => ChangeEntitiesHandler.java} |  6 +-
 .../openstreetmap/function/ChangeFunction.java     | 48 -----------
 .../openstreetmap/function/EntityConsumer.java     | 80 ------------------
 .../function/EntityConsumerAdapter.java            | 40 ---------
 .../openstreetmap/function/EntityFunction.java     | 89 --------------------
 .../function/EntityGeometryBuilder.java            | 59 ++++++++++++++
 ...sumer.java => EntityProjectionTransformer.java} | 33 ++------
 .../function/EntityToGeometryMapper.java           | 46 +++++++++++
 .../function/ExtractGeometryFunction.java          | 57 -------------
 ...hangeConsumer.java => NodeGeometryBuilder.java} | 32 +++-----
 ...yConsumer.java => RelationGeometryBuilder.java} | 94 +++++++---------------
 .../openstreetmap/function/WayGeometryBuilder.java | 64 +++++++++++++++
 .../apache/baremaps/openstreetmap/model/Blob.java  |  2 +-
 .../apache/baremaps/openstreetmap/model/Block.java | 26 +-----
 .../apache/baremaps/openstreetmap/model/Bound.java | 16 +---
 .../baremaps/openstreetmap/model/Change.java       | 12 ---
 .../baremaps/openstreetmap/model/DataBlock.java    | 15 +---
 .../baremaps/openstreetmap/model/Element.java      |  6 +-
 .../baremaps/openstreetmap/model/Entity.java       | 13 +--
 .../baremaps/openstreetmap/model/Header.java       | 14 +---
 .../baremaps/openstreetmap/model/HeaderBlock.java  | 16 +---
 .../apache/baremaps/openstreetmap/model/Info.java  |  2 +-
 .../apache/baremaps/openstreetmap/model/Node.java  | 14 ----
 .../baremaps/openstreetmap/model/Relation.java     | 14 ----
 .../apache/baremaps/openstreetmap/model/State.java |  2 +-
 .../apache/baremaps/openstreetmap/model/Way.java   | 14 ----
 .../openstreetmap/pbf/DataBlockReader.java         |  2 +-
 .../openstreetmap/pbf/HeaderBlockReader.java       |  2 +-
 .../baremaps/openstreetmap/pbf/PbfBlockReader.java | 67 ++++++---------
 .../openstreetmap/pbf/PbfEntityReader.java         | 15 +++-
 .../openstreetmap/store/DataStoreConsumer.java     | 50 ------------
 .../{geometry => utils}/GeometryUtils.java         |  6 +-
 .../{progress => utils}/InputStreamProgress.java   |  2 +-
 .../{progress => utils}/ProgressLogger.java        |  2 +-
 .../{geometry => utils}/ProjectionTransformer.java |  2 +-
 .../{progress => utils}/StreamProgress.java        |  2 +-
 ...orm.java => FeatureSetProjectionTransform.java} |  6 +-
 .../baremaps/workflow/tasks/ExportVectorTiles.java |  3 +-
 .../baremaps/workflow/tasks/ImportGeoPackage.java  |  6 +-
 .../workflow/tasks/ImportOpenStreetMap.java        | 12 +--
 .../baremaps/workflow/tasks/ImportShapefile.java   |  6 +-
 .../workflow/tasks/UpdateOpenStreetMap.java        |  8 +-
 ...ockConsumerTest.java => BlockImporterTest.java} | 10 +--
 .../database/database/ImportUpdateDataTest.java    |  6 +-
 .../database/ImportUpdateLiechtensteinTest.java    | 22 ++---
 .../database/database/ImportUpdateMonacoTest.java  | 19 ++---
 .../baremaps/openstreetmap/OpenStreetMapTest.java  | 26 ++----
 ...lerTest.java => EntityGeometryBuilderTest.java} | 30 +++----
 .../geometry/ProjectionTransformerTest.java        |  1 +
 ...yTest.java => RelationGeometryBuilderTest.java} | 10 +--
 .../org/apache/baremaps/ogcapi/PostgisPlugin.java  |  2 +-
 68 files changed, 616 insertions(+), 1114 deletions(-)

diff --git a/.gitignore b/.gitignore
index c2303122..d4050416 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,9 @@ examples/output/
 # Release
 /baremaps/
 
+# Map
+/map/data/
+
 # JMH
 jmh-result.json
 
@@ -24,4 +27,4 @@ _site/
 Gemfile.lock
 
 # Logs
-*.log
\ No newline at end of file
+*.log
diff --git a/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapBenchmark.java b/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapBenchmark.java
index bf19ac36..cb3f9c7f 100644
--- a/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapBenchmark.java
+++ b/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapBenchmark.java
@@ -24,7 +24,6 @@ import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
-import org.apache.baremaps.openstreetmap.function.EntityConsumerAdapter;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
 import org.apache.baremaps.openstreetmap.model.Way;
@@ -72,23 +71,15 @@ public class OpenStreetMapBenchmark {
     AtomicLong relations = new AtomicLong(0);
 
     try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) {
-      new PbfEntityReader(new PbfBlockReader()).stream(inputStream)
-          .forEach(new EntityConsumerAdapter() {
-            @Override
-            public void match(Node node) {
-              nodes.incrementAndGet();
-            }
-
-            @Override
-            public void match(Way way) {
-              ways.incrementAndGet();
-            }
-
-            @Override
-            public void match(Relation relation) {
-              relations.incrementAndGet();
-            }
-          });
+      new PbfEntityReader(new PbfBlockReader()).stream(inputStream).forEach(entity -> {
+        if (entity instanceof Node node) {
+          nodes.incrementAndGet();
+        } else if (entity instanceof Way way) {
+          ways.incrementAndGet();
+        } else if (entity instanceof Relation) {
+          relations.incrementAndGet();
+        }
+      });
     }
   }
 
diff --git a/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapGeometriesBenchmark.java b/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapGeometriesBenchmark.java
index 3d6e4faa..e7e38aff 100644
--- a/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapGeometriesBenchmark.java
+++ b/baremaps-benchmark/src/main/java/org/apache/baremaps/benchmarks/OpenStreetMapGeometriesBenchmark.java
@@ -33,7 +33,6 @@ import org.apache.baremaps.collection.memory.OnHeapMemory;
 import org.apache.baremaps.collection.type.CoordinateDataType;
 import org.apache.baremaps.collection.type.LongListDataType;
 import org.apache.baremaps.collection.utils.FileUtils;
-import org.apache.baremaps.openstreetmap.function.EntityConsumerAdapter;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
 import org.apache.baremaps.openstreetmap.model.Way;
@@ -74,36 +73,28 @@ public class OpenStreetMapGeometriesBenchmark {
 
   @Benchmark
   @BenchmarkMode(Mode.SingleShotTime)
-  @Warmup(iterations = 0)
-  @Measurement(iterations = 1)
+  @Warmup(iterations = 2)
+  @Measurement(iterations = 3)
   public void store() throws IOException {
     Path directory = Files.createTempDirectory(Paths.get("."), "baremaps_");
-    LongDataMap<Coordinate> coordinates = new LongDataOpenHashMap<>(
+    LongDataMap<Coordinate> coordinateMap = new LongDataOpenHashMap<>(
         new DataStore<>(new CoordinateDataType(), new OnDiskDirectoryMemory(directory)));
-    LongDataMap<List<Long>> references =
+    LongDataMap<List<Long>> referenceMap =
         new LongDataOpenHashMap<>(new DataStore<>(new LongListDataType(), new OnHeapMemory()));
     AtomicLong nodes = new AtomicLong(0);
     AtomicLong ways = new AtomicLong(0);
     AtomicLong relations = new AtomicLong(0);
     try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) {
-      new PbfEntityReader(
-          new PbfBlockReader().coordinates(coordinates).references(references).projection(4326))
-              .stream(inputStream).forEach(new EntityConsumerAdapter() {
-                @Override
-                public void match(Node node) {
-                  nodes.incrementAndGet();
-                }
-
-                @Override
-                public void match(Way way) {
-                  ways.incrementAndGet();
-                }
-
-                @Override
-                public void match(Relation relation) {
-                  relations.incrementAndGet();
-                }
-              });
+      new PbfEntityReader(new PbfBlockReader().coordinateMap(coordinateMap)
+          .referenceMap(referenceMap).projection(4326)).stream(inputStream).forEach(entity -> {
+            if (entity instanceof Node node) {
+              nodes.incrementAndGet();
+            } else if (entity instanceof Way way) {
+              ways.incrementAndGet();
+            } else if (entity instanceof Relation) {
+              relations.incrementAndGet();
+            }
+          });
     }
     FileUtils.deleteRecursively(directory);
   }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/SaveBlockConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/database/BlockImporter.java
similarity index 58%
rename from baremaps-core/src/main/java/org/apache/baremaps/database/SaveBlockConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/database/BlockImporter.java
index 5e5c3489..5468f72c 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/SaveBlockConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/BlockImporter.java
@@ -14,17 +14,13 @@ package org.apache.baremaps.database;
 
 
 
+import java.util.function.Consumer;
 import org.apache.baremaps.database.repository.Repository;
-import org.apache.baremaps.openstreetmap.function.BlockConsumerAdapter;
-import org.apache.baremaps.openstreetmap.model.DataBlock;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.HeaderBlock;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
+import org.apache.baremaps.openstreetmap.model.*;
+import org.apache.baremaps.stream.StreamException;
 
-/** A consumer for saving OpenStreetMap blocks in a database. */
-public class SaveBlockConsumer implements BlockConsumerAdapter {
+/** A consumer for importing OpenStreetMap blocks in a database. */
+public class BlockImporter implements Consumer<Block> {
 
   private final Repository<Long, Header> headerRepository;
   private final Repository<Long, Node> nodeRepository;
@@ -39,7 +35,7 @@ public class SaveBlockConsumer implements BlockConsumerAdapter {
    * @param wayRepository the way table
    * @param relationRepository the relation table
    */
-  public SaveBlockConsumer(Repository<Long, Header> headerRepository,
+  public BlockImporter(Repository<Long, Header> headerRepository,
       Repository<Long, Node> nodeRepository, Repository<Long, Way> wayRepository,
       Repository<Long, Relation> relationRepository) {
     this.headerRepository = headerRepository;
@@ -48,18 +44,19 @@ public class SaveBlockConsumer implements BlockConsumerAdapter {
     this.relationRepository = relationRepository;
   }
 
-  /** {@inheritDoc} */
   @Override
-  public void match(HeaderBlock headerBlock) throws Exception {
-    headerRepository.put(headerBlock.getHeader());
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(DataBlock dataBlock) throws Exception {
-    nodeRepository.copy(dataBlock.getDenseNodes());
-    nodeRepository.copy(dataBlock.getNodes());
-    wayRepository.copy(dataBlock.getWays());
-    relationRepository.copy(dataBlock.getRelations());
+  public void accept(Block block) {
+    try {
+      if (block instanceof HeaderBlock headerBlock) {
+        headerRepository.put(headerBlock.getHeader());
+      } else if (block instanceof DataBlock dataBlock) {
+        nodeRepository.copy(dataBlock.getDenseNodes());
+        nodeRepository.copy(dataBlock.getNodes());
+        wayRepository.copy(dataBlock.getWays());
+        relationRepository.copy(dataBlock.getRelations());
+      }
+    } catch (Exception e) {
+      throw new StreamException(e);
+    }
   }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/SaveChangeConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/database/ChangeImporter.java
similarity index 55%
rename from baremaps-core/src/main/java/org/apache/baremaps/database/SaveChangeConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/database/ChangeImporter.java
index b2160266..ab91bb2c 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/SaveChangeConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/ChangeImporter.java
@@ -14,17 +14,17 @@ package org.apache.baremaps.database;
 
 
 
+import java.util.function.Consumer;
 import org.apache.baremaps.database.repository.Repository;
-import org.apache.baremaps.openstreetmap.function.ChangeConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityConsumerAdapter;
+import org.apache.baremaps.database.repository.RepositoryException;
 import org.apache.baremaps.openstreetmap.model.Change;
-import org.apache.baremaps.openstreetmap.model.Entity;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
 import org.apache.baremaps.openstreetmap.model.Way;
+import org.apache.baremaps.stream.StreamException;
 
-/** A consumer for saving OpenStreetMap changes in a database. */
-public class SaveChangeConsumer implements ChangeConsumer {
+/** A consumer for importing OpenStreetMap changes in a database. */
+public class ChangeImporter implements Consumer<Change> {
 
   private final Repository<Long, Node> nodeRepository;
   private final Repository<Long, Way> wayRepository;
@@ -37,8 +37,8 @@ public class SaveChangeConsumer implements ChangeConsumer {
    * @param wayRepository the way table
    * @param relationRepository the relation table
    */
-  public SaveChangeConsumer(Repository<Long, Node> nodeRepository,
-      Repository<Long, Way> wayRepository, Repository<Long, Relation> relationRepository) {
+  public ChangeImporter(Repository<Long, Node> nodeRepository, Repository<Long, Way> wayRepository,
+      Repository<Long, Relation> relationRepository) {
     this.nodeRepository = nodeRepository;
     this.wayRepository = wayRepository;
     this.relationRepository = relationRepository;
@@ -46,48 +46,35 @@ public class SaveChangeConsumer implements ChangeConsumer {
 
   /** {@inheritDoc} */
   @Override
-  public void match(Change change) throws Exception {
-    for (Entity entity : change.getEntities()) {
-      entity.visit(new EntityConsumerAdapter() {
-        @Override
-        public void match(Node node) throws Exception {
-          switch (change.getType()) {
-            case CREATE:
-            case MODIFY:
+  public void accept(Change change) {
+    try {
+      for (var entity : change.getEntities()) {
+        switch (change.getType()) {
+          case CREATE:
+          case MODIFY:
+            if (entity instanceof Node node) {
               nodeRepository.put(node);
-              break;
-            case DELETE:
-              nodeRepository.delete(node.getId());
-              break;
-          }
-        }
-
-        @Override
-        public void match(Way way) throws Exception {
-          switch (change.getType()) {
-            case CREATE:
-            case MODIFY:
+            } else if (entity instanceof Way way) {
               wayRepository.put(way);
-              break;
-            case DELETE:
-              wayRepository.delete(way.getId());
-              break;
-          }
-        }
-
-        @Override
-        public void match(Relation relation) throws Exception {
-          switch (change.getType()) {
-            case CREATE:
-            case MODIFY:
+            } else if (entity instanceof Relation relation) {
               relationRepository.put(relation);
-              break;
-            case DELETE:
+            }
+            break;
+          case DELETE:
+            if (entity instanceof Node node) {
+              nodeRepository.delete(node.getId());
+            } else if (entity instanceof Way way) {
+              wayRepository.delete(way.getId());
+            } else if (entity instanceof Relation relation) {
               relationRepository.delete(relation.getId());
-              break;
-          }
+            }
+            break;
         }
-      });
+      }
+    } catch (RepositoryException e) {
+      throw new StreamException(e);
     }
   }
+
+
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/DiffService.java b/baremaps-core/src/main/java/org/apache/baremaps/database/DiffService.java
index 8862b3cf..289c7bdc 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/DiffService.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/DiffService.java
@@ -15,7 +15,6 @@ package org.apache.baremaps.database;
 import static org.apache.baremaps.stream.ConsumerUtils.consumeThenReturn;
 
 import java.io.BufferedInputStream;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
@@ -31,17 +30,12 @@ import org.apache.baremaps.collection.LongDataMap;
 import org.apache.baremaps.database.repository.HeaderRepository;
 import org.apache.baremaps.database.repository.Repository;
 import org.apache.baremaps.database.tile.Tile;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
-import org.apache.baremaps.openstreetmap.function.ExtractGeometryFunction;
-import org.apache.baremaps.openstreetmap.geometry.ProjectionTransformer;
-import org.apache.baremaps.openstreetmap.model.Bound;
-import org.apache.baremaps.openstreetmap.model.Change;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
+import org.apache.baremaps.openstreetmap.function.EntityGeometryBuilder;
+import org.apache.baremaps.openstreetmap.function.EntityToGeometryMapper;
+import org.apache.baremaps.openstreetmap.model.*;
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
 import org.apache.baremaps.openstreetmap.xml.XmlChangeReader;
+import org.apache.baremaps.stream.StreamException;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
 import org.slf4j.Logger;
@@ -51,8 +45,8 @@ public class DiffService implements Callable<List<Tile>> {
 
   private static final Logger logger = LoggerFactory.getLogger(DiffService.class);
 
-  private final LongDataMap<Coordinate> coordinates;
-  private final LongDataMap<List<Long>> references;
+  private final LongDataMap<Coordinate> coordinateMap;
+  private final LongDataMap<List<Long>> referenceMap;
   private final HeaderRepository headerRepository;
   private final Repository<Long, Node> nodeRepository;
   private final Repository<Long, Way> wayRepository;
@@ -60,12 +54,12 @@ public class DiffService implements Callable<List<Tile>> {
   private final int srid;
   private final int zoom;
 
-  public DiffService(LongDataMap<Coordinate> coordinates, LongDataMap<List<Long>> references,
+  public DiffService(LongDataMap<Coordinate> coordinateMap, LongDataMap<List<Long>> referenceMap,
       HeaderRepository headerRepository, Repository<Long, Node> nodeRepository,
       Repository<Long, Way> wayRepository, Repository<Long, Relation> relationRepository, int srid,
       int zoom) {
-    this.coordinates = coordinates;
-    this.references = references;
+    this.coordinateMap = coordinateMap;
+    this.referenceMap = referenceMap;
     this.headerRepository = headerRepository;
     this.nodeRepository = nodeRepository;
     this.wayRepository = wayRepository;
@@ -78,13 +72,13 @@ public class DiffService implements Callable<List<Tile>> {
   public List<Tile> call() throws Exception {
     logger.info("Importing changes");
 
-    Header header = headerRepository.selectLatest();
-    String replicationUrl = header.getReplicationUrl();
-    Long sequenceNumber = header.getReplicationSequenceNumber() + 1;
-    URL changeUrl = resolve(replicationUrl, sequenceNumber, "osc.gz");
+    var header = headerRepository.selectLatest();
+    var replicationUrl = header.getReplicationUrl();
+    var sequenceNumber = header.getReplicationSequenceNumber() + 1;
+    var changeUrl = resolve(replicationUrl, sequenceNumber, "osc.gz");
 
-    ProjectionTransformer projectionTransformer = new ProjectionTransformer(srid, 4326);
-    try (InputStream changeInputStream =
+    var projectionTransformer = new ProjectionTransformer(srid, 4326);
+    try (var changeInputStream =
         new GZIPInputStream(new BufferedInputStream(changeUrl.openStream()))) {
       return new XmlChangeReader().stream(changeInputStream).flatMap(this::geometriesForChange)
           .map(projectionTransformer::transform).flatMap(this::tilesForGeometry).distinct()
@@ -114,48 +108,40 @@ public class DiffService implements Callable<List<Tile>> {
   }
 
   private Stream<Geometry> geometriesForPreviousVersion(Change change) {
-    return change.getEntities().stream().map(new EntityFunction<Optional<Geometry>>() {
-      @Override
-      public Optional<Geometry> match(Header header) {
-        return Optional.empty();
-      }
-
-      @Override
-      public Optional<Geometry> match(Bound bound) {
-        return Optional.empty();
-      }
+    return change.getEntities().stream().map(this::geometriesForPreviousVersion)
+        .flatMap(Optional::stream);
+  }
 
-      @Override
-      public Optional<Geometry> match(Node node) throws Exception {
-        Node previousNode = nodeRepository.get(node.getId());
+  private Optional<Geometry> geometriesForPreviousVersion(Entity entity) {
+    try {
+      if (entity instanceof Node node) {
+        var previousNode = nodeRepository.get(node.getId());
         return Optional.ofNullable(previousNode).map(Node::getGeometry);
-      }
-
-      @Override
-      public Optional<Geometry> match(Way way) throws Exception {
-        Way previousWay = wayRepository.get(way.getId());
+      } else if (entity instanceof Way way) {
+        var previousWay = wayRepository.get(way.getId());
         return Optional.ofNullable(previousWay).map(Way::getGeometry);
-      }
-
-      @Override
-      public Optional<Geometry> match(Relation relation) throws Exception {
-        Relation previousRelation = relationRepository.get(relation.getId());
+      } else if (entity instanceof Relation relation) {
+        var previousRelation = relationRepository.get(relation.getId());
         return Optional.ofNullable(previousRelation).map(Relation::getGeometry);
+      } else {
+        return Optional.empty();
       }
-    }).flatMap(Optional::stream);
+    } catch (Exception e) {
+      throw new StreamException(e);
+    }
   }
 
   private Stream<Geometry> geometriesForNextVersion(Change change) {
     return change.getEntities().stream()
-        .map(consumeThenReturn(new CreateGeometryConsumer(coordinates, references)))
-        .flatMap(new ExtractGeometryFunction().andThen(Optional::stream));
+        .map(consumeThenReturn(new EntityGeometryBuilder(coordinateMap, referenceMap)))
+        .flatMap(new EntityToGeometryMapper().andThen(Optional::stream));
   }
 
   public URL resolve(String replicationUrl, Long sequenceNumber, String extension)
       throws MalformedURLException {
-    String s = String.format("%09d", sequenceNumber);
-    String uri = String.format("%s/%s/%s/%s.%s", replicationUrl, s.substring(0, 3),
-        s.substring(3, 6), s.substring(6, 9), extension);
+    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/main/java/org/apache/baremaps/database/ImportService.java b/baremaps-core/src/main/java/org/apache/baremaps/database/ImportService.java
index c4bb15ac..ab1b58ad 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/ImportService.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/ImportService.java
@@ -20,41 +20,37 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.function.Consumer;
-import java.util.function.Function;
 import org.apache.baremaps.collection.LongDataMap;
 import org.apache.baremaps.database.repository.HeaderRepository;
 import org.apache.baremaps.database.repository.Repository;
-import org.apache.baremaps.openstreetmap.function.BlockEntityConsumer;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
-import org.apache.baremaps.openstreetmap.function.ReprojectEntityConsumer;
-import org.apache.baremaps.openstreetmap.model.Block;
-import org.apache.baremaps.openstreetmap.model.Entity;
+import org.apache.baremaps.openstreetmap.function.BlockEntitiesHandler;
+import org.apache.baremaps.openstreetmap.function.CacheBuilder;
+import org.apache.baremaps.openstreetmap.function.EntityGeometryBuilder;
+import org.apache.baremaps.openstreetmap.function.EntityProjectionTransformer;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
 import org.apache.baremaps.openstreetmap.model.Way;
 import org.apache.baremaps.openstreetmap.pbf.PbfBlockReader;
-import org.apache.baremaps.openstreetmap.store.DataStoreConsumer;
 import org.locationtech.jts.geom.Coordinate;
 
 public class ImportService implements Callable<Void> {
 
   private final Path path;
-  private final LongDataMap<Coordinate> coordinates;
-  private final LongDataMap<List<Long>> references;
+  private final LongDataMap<Coordinate> coordinateMap;
+  private final LongDataMap<List<Long>> referenceMap;
   private final HeaderRepository headerRepository;
   private final Repository<Long, Node> nodeRepository;
   private final Repository<Long, Way> wayRepository;
   private final Repository<Long, Relation> relationRepository;
   private final int databaseSrid;
 
-  public ImportService(Path path, LongDataMap<Coordinate> coordinates,
-      LongDataMap<List<Long>> references, HeaderRepository headerRepository,
+  public ImportService(Path path, LongDataMap<Coordinate> coordinateMap,
+      LongDataMap<List<Long>> referenceMap, HeaderRepository headerRepository,
       Repository<Long, Node> nodeRepository, Repository<Long, Way> wayRepository,
       Repository<Long, Relation> relationRepository, Integer databaseSrid) {
     this.path = path;
-    this.coordinates = coordinates;
-    this.references = references;
+    this.coordinateMap = coordinateMap;
+    this.referenceMap = referenceMap;
     this.headerRepository = headerRepository;
     this.nodeRepository = nodeRepository;
     this.wayRepository = wayRepository;
@@ -64,17 +60,18 @@ public class ImportService implements Callable<Void> {
 
   @Override
   public Void call() throws Exception {
-    Consumer<Block> cacheBlock = new DataStoreConsumer(coordinates, references);
-    Consumer<Entity> createGeometry = new CreateGeometryConsumer(coordinates, references);
-    Consumer<Entity> reprojectGeometry = new ReprojectEntityConsumer(4326, databaseSrid);
-    Consumer<Block> prepareGeometries =
-        new BlockEntityConsumer(createGeometry.andThen(reprojectGeometry));
-    Function<Block, Block> prepareBlock = consumeThenReturn(cacheBlock.andThen(prepareGeometries));
-    Consumer<Block> saveBlock =
-        new SaveBlockConsumer(headerRepository, nodeRepository, wayRepository, relationRepository);
+    var cacheBuilder = new CacheBuilder(coordinateMap, referenceMap);
+    var entityGeometryBuilder = new EntityGeometryBuilder(coordinateMap, referenceMap);
+    var entityProjectionTransformer = new EntityProjectionTransformer(4326, databaseSrid);
+    var blockEntitiesHandler =
+        new BlockEntitiesHandler(entityGeometryBuilder.andThen(entityProjectionTransformer));
+    var blockMapper = consumeThenReturn(cacheBuilder.andThen(blockEntitiesHandler));
+    var blockImporter =
+        new BlockImporter(headerRepository, nodeRepository, wayRepository, relationRepository);
     try (InputStream inputStream = Files.newInputStream(path)) {
-      batch(new PbfBlockReader().stream(inputStream).map(prepareBlock)).forEach(saveBlock);
+      batch(new PbfBlockReader().stream(inputStream).map(blockMapper)).forEach(blockImporter);
     }
     return null;
   }
+
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/UpdateService.java b/baremaps-core/src/main/java/org/apache/baremaps/database/UpdateService.java
index 6b45ed69..39fe14cb 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/UpdateService.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/UpdateService.java
@@ -15,27 +15,21 @@ package org.apache.baremaps.database;
 import static org.apache.baremaps.stream.ConsumerUtils.consumeThenReturn;
 
 import java.io.BufferedInputStream;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.util.List;
 import java.util.concurrent.Callable;
-import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.zip.GZIPInputStream;
 import org.apache.baremaps.collection.LongDataMap;
 import org.apache.baremaps.database.repository.HeaderRepository;
 import org.apache.baremaps.database.repository.Repository;
-import org.apache.baremaps.openstreetmap.function.ChangeEntityConsumer;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
-import org.apache.baremaps.openstreetmap.function.ReprojectEntityConsumer;
-import org.apache.baremaps.openstreetmap.model.Change;
-import org.apache.baremaps.openstreetmap.model.Entity;
+import org.apache.baremaps.openstreetmap.function.ChangeEntitiesHandler;
+import org.apache.baremaps.openstreetmap.function.EntityGeometryBuilder;
+import org.apache.baremaps.openstreetmap.function.EntityProjectionTransformer;
 import org.apache.baremaps.openstreetmap.model.Header;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.State;
 import org.apache.baremaps.openstreetmap.model.Way;
 import org.apache.baremaps.openstreetmap.state.StateReader;
 import org.apache.baremaps.openstreetmap.xml.XmlChangeReader;
@@ -43,20 +37,20 @@ import org.locationtech.jts.geom.Coordinate;
 
 public class UpdateService implements Callable<Void> {
 
-  private final LongDataMap<Coordinate> coordinates;
-  private final LongDataMap<List<Long>> references;
+  private final LongDataMap<Coordinate> coordinateMap;
+  private final LongDataMap<List<Long>> referenceMap;
   private final HeaderRepository headerRepository;
   private final Repository<Long, Node> nodeRepository;
   private final Repository<Long, Way> wayRepository;
   private final Repository<Long, Relation> relationRepository;
   private final int srid;
 
-  public UpdateService(LongDataMap<Coordinate> coordinates, LongDataMap<List<Long>> references,
+  public UpdateService(LongDataMap<Coordinate> coordinateMap, LongDataMap<List<Long>> referenceMap,
       HeaderRepository headerRepository, Repository<Long, Node> nodeRepository,
       Repository<Long, Way> wayRepository, Repository<Long, Relation> relationRepository,
       int srid) {
-    this.coordinates = coordinates;
-    this.references = references;
+    this.coordinateMap = coordinateMap;
+    this.referenceMap = referenceMap;
     this.headerRepository = headerRepository;
     this.nodeRepository = nodeRepository;
     this.wayRepository = wayRepository;
@@ -66,27 +60,25 @@ public class UpdateService implements Callable<Void> {
 
   @Override
   public Void call() throws Exception {
-    Header header = headerRepository.selectLatest();
-    String replicationUrl = header.getReplicationUrl();
-    Long sequenceNumber = header.getReplicationSequenceNumber() + 1;
+    var header = headerRepository.selectLatest();
+    var replicationUrl = header.getReplicationUrl();
+    var sequenceNumber = header.getReplicationSequenceNumber() + 1;
 
-    Consumer<Entity> createGeometry = new CreateGeometryConsumer(coordinates, references);
-    Consumer<Entity> reprojectGeometry = new ReprojectEntityConsumer(4326, srid);
-    Consumer<Change> prepareGeometries =
-        new ChangeEntityConsumer(createGeometry.andThen(reprojectGeometry));
-    Function<Change, Change> prepareChange = consumeThenReturn(prepareGeometries);
-    Consumer<Change> saveChange =
-        new SaveChangeConsumer(nodeRepository, wayRepository, relationRepository);
+    var createGeometry = new EntityGeometryBuilder(coordinateMap, referenceMap);
+    var reprojectGeometry = new EntityProjectionTransformer(4326, srid);
+    var prepareGeometries = new ChangeEntitiesHandler(createGeometry.andThen(reprojectGeometry));
+    var prepareChange = consumeThenReturn(prepareGeometries);
+    var saveChange = new ChangeImporter(nodeRepository, wayRepository, relationRepository);
 
-    URL changeUrl = resolve(replicationUrl, sequenceNumber, "osc.gz");
-    try (InputStream changeInputStream =
+    var changeUrl = resolve(replicationUrl, sequenceNumber, "osc.gz");
+    try (var changeInputStream =
         new GZIPInputStream(new BufferedInputStream(changeUrl.openStream()))) {
       new XmlChangeReader().stream(changeInputStream).map(prepareChange).forEach(saveChange);
     }
 
-    URL stateUrl = resolve(replicationUrl, sequenceNumber, "state.txt");
-    try (InputStream stateInputStream = new BufferedInputStream(stateUrl.openStream())) {
-      State state = new StateReader().state(stateInputStream);
+    var stateUrl = resolve(replicationUrl, sequenceNumber, "state.txt");
+    try (var stateInputStream = new BufferedInputStream(stateUrl.openStream())) {
+      var state = new StateReader().state(stateInputStream);
       headerRepository.put(new Header(state.getSequenceNumber(), state.getTimestamp(),
           header.getReplicationUrl(), header.getSource(), header.getWritingProgram()));
     }
@@ -96,9 +88,9 @@ public class UpdateService implements Callable<Void> {
 
   public URL resolve(String replicationUrl, Long sequenceNumber, String extension)
       throws MalformedURLException {
-    String s = String.format("%09d", sequenceNumber);
-    String uri = String.format("%s/%s/%s/%s.%s", replicationUrl, s.substring(0, 3),
-        s.substring(3, 6), s.substring(6, 9), extension);
+    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/main/java/org/apache/baremaps/database/collection/PostgresReferenceMap.java b/baremaps-core/src/main/java/org/apache/baremaps/database/collection/PostgresReferenceMap.java
index 4a5e9242..a9a4844a 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/collection/PostgresReferenceMap.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/collection/PostgresReferenceMap.java
@@ -75,7 +75,7 @@ public class PostgresReferenceMap implements LongDataMap<List<Long>> {
         PreparedStatement statement = connection.prepareStatement(SELECT_IN)) {
       statement.setArray(1, connection.createArrayOf("int8", keys.toArray()));
       try (ResultSet result = statement.executeQuery()) {
-        Map<Long, List<Long>> references = new HashMap<>();
+        Map<Long, List<Long>> referenceMap = new HashMap<>();
         while (result.next()) {
           List<Long> nodes = new ArrayList<>();
           long key = result.getLong(1);
@@ -83,9 +83,9 @@ public class PostgresReferenceMap implements LongDataMap<List<Long>> {
           if (array != null) {
             nodes = Arrays.asList((Long[]) array.getArray());
           }
-          references.put(key, nodes);
+          referenceMap.put(key, nodes);
         }
-        return keys.stream().map(references::get).toList();
+        return keys.stream().map(referenceMap::get).toList();
       }
     } catch (SQLException e) {
       throw new StoreException(e);
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresNodeRepository.java b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresNodeRepository.java
index 726c4d0b..b307d622 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresNodeRepository.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresNodeRepository.java
@@ -26,9 +26,9 @@ import java.util.List;
 import java.util.Map;
 import javax.sql.DataSource;
 import org.apache.baremaps.database.copy.CopyWriter;
-import org.apache.baremaps.openstreetmap.geometry.GeometryUtils;
 import org.apache.baremaps.openstreetmap.model.Info;
 import org.apache.baremaps.openstreetmap.model.Node;
+import org.apache.baremaps.openstreetmap.utils.GeometryUtils;
 import org.locationtech.jts.geom.Geometry;
 import org.postgresql.PGConnection;
 import org.postgresql.copy.PGCopyOutputStream;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresRelationRepository.java b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresRelationRepository.java
index d18411af..6d37d1aa 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresRelationRepository.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresRelationRepository.java
@@ -29,11 +29,11 @@ import java.util.Map;
 import java.util.stream.Collectors;
 import javax.sql.DataSource;
 import org.apache.baremaps.database.copy.CopyWriter;
-import org.apache.baremaps.openstreetmap.geometry.GeometryUtils;
 import org.apache.baremaps.openstreetmap.model.Info;
 import org.apache.baremaps.openstreetmap.model.Member;
 import org.apache.baremaps.openstreetmap.model.Member.MemberType;
 import org.apache.baremaps.openstreetmap.model.Relation;
+import org.apache.baremaps.openstreetmap.utils.GeometryUtils;
 import org.locationtech.jts.geom.Geometry;
 import org.postgresql.PGConnection;
 import org.postgresql.copy.PGCopyOutputStream;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresWayRepository.java b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresWayRepository.java
index a198458a..8360f79f 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresWayRepository.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/database/repository/PostgresWayRepository.java
@@ -29,9 +29,9 @@ import java.util.List;
 import java.util.Map;
 import javax.sql.DataSource;
 import org.apache.baremaps.database.copy.CopyWriter;
-import org.apache.baremaps.openstreetmap.geometry.GeometryUtils;
 import org.apache.baremaps.openstreetmap.model.Info;
 import org.apache.baremaps.openstreetmap.model.Way;
+import org.apache.baremaps.openstreetmap.utils.GeometryUtils;
 import org.locationtech.jts.geom.Geometry;
 import org.postgresql.PGConnection;
 import org.postgresql.copy.PGCopyOutputStream;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockFunction.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlobToBlockMapper.java
similarity index 50%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockFunction.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlobToBlockMapper.java
index 8c5bc871..2b8ecf4d 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockFunction.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlobToBlockMapper.java
@@ -15,44 +15,33 @@ package org.apache.baremaps.openstreetmap.function;
 
 
 import java.util.function.Function;
+import org.apache.baremaps.openstreetmap.model.Blob;
 import org.apache.baremaps.openstreetmap.model.Block;
-import org.apache.baremaps.openstreetmap.model.DataBlock;
-import org.apache.baremaps.openstreetmap.model.HeaderBlock;
+import org.apache.baremaps.openstreetmap.pbf.DataBlockReader;
+import org.apache.baremaps.openstreetmap.pbf.HeaderBlockReader;
 import org.apache.baremaps.stream.StreamException;
 
 /**
- * Represents an function on blocks of different types.
- *
- * @param <T>
+ * Maps a blob to a block.
  */
-public interface BlockFunction<T> extends Function<Block, T> {
+public class BlobToBlockMapper implements Function<Blob, Block> {
 
   /** {@inheritDoc} */
   @Override
-  default T apply(Block block) {
+  public Block apply(Blob blob) {
     try {
-      return block.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
+      switch (blob.header().getType()) {
+        case "OSMHeader":
+          return new HeaderBlockReader(blob).read();
+        case "OSMData":
+          return new DataBlockReader(blob).read();
+        default:
+          throw new StreamException("Unknown blob type");
+      }
+    } catch (StreamException exception) {
+      throw exception;
+    } catch (Exception exception) {
+      throw new StreamException(exception);
     }
   }
-
-  /**
-   * Applies a function on a {@code HeaderBlock}.
-   *
-   * @param headerBlock the header block
-   * @throws Exception
-   */
-  T match(HeaderBlock headerBlock) throws Exception;
-
-  /**
-   * Applies a function on a {@code DataBlock}.
-   *
-   * @param dataBlock
-   * @return the function result
-   * @throws Exception
-   */
-  T match(DataBlock dataBlock) throws Exception;
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumer.java
deleted file mode 100644
index cd320d89..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumer.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import java.util.function.Consumer;
-import org.apache.baremaps.openstreetmap.model.Block;
-import org.apache.baremaps.openstreetmap.model.DataBlock;
-import org.apache.baremaps.openstreetmap.model.HeaderBlock;
-import org.apache.baremaps.stream.StreamException;
-
-/** Represents an operation on blocks of different types. */
-public interface BlockConsumer extends Consumer<Block> {
-
-  /** {@inheritDoc} */
-  @Override
-  default void accept(Block block) {
-    try {
-      block.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
-    }
-  }
-
-  /**
-   * Matches an operation on a {@code HeaderBlock}.
-   *
-   * @param headerBlock the header block
-   * @throws Exception
-   */
-  void match(HeaderBlock headerBlock) throws Exception;
-
-  /**
-   * Matches an operation on a {@code DataBlock}.
-   *
-   * @param dataBlock the data block
-   * @throws Exception
-   */
-  void match(DataBlock dataBlock) throws Exception;
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumerAdapter.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumerAdapter.java
deleted file mode 100644
index 3879d376..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockConsumerAdapter.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import org.apache.baremaps.openstreetmap.model.DataBlock;
-import org.apache.baremaps.openstreetmap.model.HeaderBlock;
-
-/** {@inheritDoc} */
-public interface BlockConsumerAdapter extends BlockConsumer {
-
-  /** {@inheritDoc} */
-  default void match(HeaderBlock headerBlock) throws Exception {}
-
-  /** {@inheritDoc} */
-  default void match(DataBlock dataBlock) throws Exception {}
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntityConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntitiesHandler.java
similarity index 60%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntityConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntitiesHandler.java
index f0b87c34..32d6b94e 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntityConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/BlockEntitiesHandler.java
@@ -15,12 +15,14 @@ package org.apache.baremaps.openstreetmap.function;
 
 
 import java.util.function.Consumer;
+import org.apache.baremaps.openstreetmap.model.Block;
 import org.apache.baremaps.openstreetmap.model.DataBlock;
 import org.apache.baremaps.openstreetmap.model.Entity;
 import org.apache.baremaps.openstreetmap.model.HeaderBlock;
+import org.apache.baremaps.stream.StreamException;
 
 /** Represents an operation on the entities of blocks of different types. */
-public class BlockEntityConsumer implements BlockConsumer {
+public class BlockEntitiesHandler implements Consumer<Block> {
 
   private final Consumer<Entity> consumer;
 
@@ -29,23 +31,23 @@ public class BlockEntityConsumer implements BlockConsumer {
    *
    * @param consumer the entity consumer
    */
-  public BlockEntityConsumer(Consumer<Entity> consumer) {
+  public BlockEntitiesHandler(Consumer<Entity> consumer) {
     this.consumer = consumer;
   }
 
-  /** {@inheritDoc} */
   @Override
-  public void match(HeaderBlock headerBlock) throws Exception {
-    consumer.accept(headerBlock.getHeader());
-    consumer.accept(headerBlock.getBound());
+  public void accept(Block block) {
+    if (block instanceof HeaderBlock headerBlock) {
+      consumer.accept(headerBlock.getHeader());
+      consumer.accept(headerBlock.getBound());
+    } else if (block instanceof DataBlock dataBlock) {
+      dataBlock.getDenseNodes().forEach(consumer);
+      dataBlock.getNodes().forEach(consumer);
+      dataBlock.getWays().forEach(consumer);
+      dataBlock.getRelations().forEach(consumer);
+    } else {
+      throw new StreamException("Unknown block type.");
+    }
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void match(DataBlock dataBlock) throws Exception {
-    dataBlock.getDenseNodes().forEach(consumer);
-    dataBlock.getNodes().forEach(consumer);
-    dataBlock.getWays().forEach(consumer);
-    dataBlock.getRelations().forEach(consumer);
-  }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CacheBuilder.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CacheBuilder.java
new file mode 100644
index 00000000..435867eb
--- /dev/null
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CacheBuilder.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.baremaps.openstreetmap.function;
+
+
+
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.baremaps.collection.LongDataMap;
+import org.apache.baremaps.openstreetmap.model.Block;
+import org.apache.baremaps.openstreetmap.model.DataBlock;
+import org.apache.baremaps.stream.StreamException;
+import org.locationtech.jts.geom.Coordinate;
+
+/** A consumer that caches openstreetmap coordinates and references. */
+public class CacheBuilder implements Consumer<Block> {
+
+  private final LongDataMap<Coordinate> coordinateMap;
+  private final LongDataMap<List<Long>> referenceMap;
+
+  /**
+   * Constructs a {@code CacheBlockConsumer} with the provided caches.
+   *
+   * @param coordinateMap the map of coordinates
+   * @param referenceMap the map of references
+   */
+  public CacheBuilder(LongDataMap<Coordinate> coordinateMap, LongDataMap<List<Long>> referenceMap) {
+    this.coordinateMap = coordinateMap;
+    this.referenceMap = referenceMap;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void accept(Block block) {
+    try {
+      if (block instanceof DataBlock dataBlock) {
+        dataBlock.getDenseNodes().stream().forEach(
+            node -> coordinateMap.put(node.getId(), new Coordinate(node.getLon(), node.getLat())));
+        dataBlock.getNodes().stream().forEach(
+            node -> coordinateMap.put(node.getId(), new Coordinate(node.getLon(), node.getLat())));
+        dataBlock.getWays().stream().forEach(way -> referenceMap.put(way.getId(), way.getNodes()));
+      }
+    } catch (Exception e) {
+      throw new StreamException(e);
+    }
+  }
+}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntityConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntitiesHandler.java
similarity index 86%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntityConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntitiesHandler.java
index 1662e6f6..7e0e07ea 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntityConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeEntitiesHandler.java
@@ -19,7 +19,7 @@ import org.apache.baremaps.openstreetmap.model.Change;
 import org.apache.baremaps.openstreetmap.model.Entity;
 
 /** Represents an operation on the entities of changes of different types. */
-public class ChangeEntityConsumer implements ChangeConsumer {
+public class ChangeEntitiesHandler implements Consumer<Change> {
 
   private final Consumer<Entity> consumer;
 
@@ -29,13 +29,13 @@ public class ChangeEntityConsumer implements ChangeConsumer {
    *
    * @param consumer
    */
-  public ChangeEntityConsumer(Consumer<Entity> consumer) {
+  public ChangeEntitiesHandler(Consumer<Entity> consumer) {
     this.consumer = consumer;
   }
 
   /** {@inheritDoc} */
   @Override
-  public void match(Change change) throws Exception {
+  public void accept(Change change) {
     change.getEntities().forEach(consumer);
   }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeFunction.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeFunction.java
deleted file mode 100644
index 8f9510bb..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeFunction.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import java.util.function.Function;
-import org.apache.baremaps.openstreetmap.model.Change;
-import org.apache.baremaps.stream.StreamException;
-
-/**
- * Represents a function that transforms entities of different types.
- *
- * @param <T>
- */
-public interface ChangeFunction<T> extends Function<Change, T> {
-
-  /** {@inheritDoc} */
-  @Override
-  default T apply(Change change) {
-    try {
-      return change.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
-    }
-  }
-
-  /**
-   * Applies a function on a {@code Change}.
-   *
-   * @param change the change
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Change change) throws Exception;
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumer.java
deleted file mode 100644
index 7890ada8..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumer.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import java.util.function.Consumer;
-import org.apache.baremaps.openstreetmap.model.Bound;
-import org.apache.baremaps.openstreetmap.model.Entity;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
-import org.apache.baremaps.stream.StreamException;
-
-/** Represents an operation on entities of different types. */
-public interface EntityConsumer extends Consumer<Entity> {
-
-  /** {@inheritDoc} */
-  @Override
-  default void accept(Entity entity) {
-    try {
-      entity.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
-    }
-  }
-
-  /**
-   * Matches an operation on a {@code Header}.
-   *
-   * @param header the header
-   * @throws Exception
-   */
-  void match(Header header) throws Exception;
-
-  /**
-   * Matches an operation on a {@code Bound}.
-   *
-   * @param bound the bound
-   * @throws Exception
-   */
-  void match(Bound bound) throws Exception;
-
-  /**
-   * Matches an operation on a {@code Node}.
-   *
-   * @param node the node
-   * @throws Exception
-   */
-  void match(Node node) throws Exception;
-
-  /**
-   * Matches an operation on a {@code Way}.
-   *
-   * @param way the way
-   * @throws Exception
-   */
-  void match(Way way) throws Exception;
-
-  /**
-   * Matches an operation on a {@code Relation}.
-   *
-   * @param relation the relation
-   * @throws Exception
-   */
-  void match(Relation relation) throws Exception;
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumerAdapter.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumerAdapter.java
deleted file mode 100644
index 3f857874..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityConsumerAdapter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import org.apache.baremaps.openstreetmap.model.Bound;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
-
-/** {@inheritDoc} */
-public interface EntityConsumerAdapter extends EntityConsumer {
-
-  /** {@inheritDoc} */
-  default void match(Header header) throws Exception {}
-
-  /** {@inheritDoc} */
-  default void match(Bound bound) throws Exception {}
-
-  /** {@inheritDoc} */
-  default void match(Node node) throws Exception {}
-
-  /** {@inheritDoc} */
-  default void match(Way way) throws Exception {}
-
-  /** {@inheritDoc} */
-  default void match(Relation relation) throws Exception {}
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityFunction.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityFunction.java
deleted file mode 100644
index 6861418c..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityFunction.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import java.util.function.Function;
-import org.apache.baremaps.openstreetmap.model.Bound;
-import org.apache.baremaps.openstreetmap.model.Entity;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
-import org.apache.baremaps.stream.StreamException;
-
-/**
- * Represents a function that transforms entities of different types.
- *
- * @param <T>
- */
-public interface EntityFunction<T> extends Function<Entity, T> {
-
-  /** {@inheritDoc} */
-  @Override
-  default T apply(Entity entity) {
-    try {
-      return entity.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
-    }
-  }
-
-  /**
-   * Applies a function on a {@code Header}.
-   *
-   * @param header the header
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Header header) throws Exception;
-
-  /**
-   * Applies a function on a {@code Bound}.
-   *
-   * @param bound the bound
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Bound bound) throws Exception;
-
-  /**
-   * Applies a function on a {@code Node}.
-   *
-   * @param node the node
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Node node) throws Exception;
-
-  /**
-   * Applies a function on a {@code Way}.
-   *
-   * @param way the way
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Way way) throws Exception;
-
-  /**
-   * Applies a function on a {@code Relation}.
-   *
-   * @param relation the relation
-   * @return the function result
-   * @throws Exception
-   */
-  T match(Relation relation) throws Exception;
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java
new file mode 100644
index 00000000..d1f0188b
--- /dev/null
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.baremaps.openstreetmap.function;
+
+
+
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.baremaps.collection.LongDataMap;
+import org.apache.baremaps.openstreetmap.model.Entity;
+import org.apache.baremaps.openstreetmap.model.Node;
+import org.apache.baremaps.openstreetmap.model.Relation;
+import org.apache.baremaps.openstreetmap.model.Way;
+import org.locationtech.jts.geom.Coordinate;
+
+/** A consumer that builds and sets the geometry of OpenStreetMap entities via side effects. */
+public class EntityGeometryBuilder implements Consumer<Entity> {
+
+  private final NodeGeometryBuilder nodeGeometryBuilder;
+  private final WayGeometryBuilder wayGeometryBuilder;
+  private final RelationGeometryBuilder relationGeometryBuilder;
+
+  /**
+   * Constructs a consumer that uses the provided caches to create and set geometries.
+   *
+   * @param coordinateMap the coordinate cache
+   * @param referenceMap the reference cache
+   */
+  public EntityGeometryBuilder(LongDataMap<Coordinate> coordinateMap,
+      LongDataMap<List<Long>> referenceMap) {
+    this.nodeGeometryBuilder = new NodeGeometryBuilder();
+    this.wayGeometryBuilder = new WayGeometryBuilder(coordinateMap);
+    this.relationGeometryBuilder = new RelationGeometryBuilder(coordinateMap, referenceMap);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void accept(Entity entity) {
+    if (entity instanceof Node node) {
+      nodeGeometryBuilder.accept(node);
+    } else if (entity instanceof Way way) {
+      wayGeometryBuilder.accept(way);
+    } else if (entity instanceof Relation relation) {
+      relationGeometryBuilder.accept(relation);
+    } else {
+      // do nothing
+    }
+  }
+}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ReprojectEntityConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityProjectionTransformer.java
similarity index 60%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ReprojectEntityConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityProjectionTransformer.java
index 61b5ce31..beb1a010 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ReprojectEntityConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityProjectionTransformer.java
@@ -14,15 +14,13 @@ package org.apache.baremaps.openstreetmap.function;
 
 
 
-import org.apache.baremaps.openstreetmap.geometry.ProjectionTransformer;
-import org.apache.baremaps.openstreetmap.model.Element;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
+import java.util.function.Consumer;
+import org.apache.baremaps.openstreetmap.model.*;
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
 import org.locationtech.jts.geom.Geometry;
 
 /** Changes the projection of the geometry of an entity via side-effects. */
-public class ReprojectEntityConsumer implements EntityConsumerAdapter {
+public class EntityProjectionTransformer implements Consumer<Entity> {
 
   private final ProjectionTransformer projectionTransformer;
 
@@ -32,31 +30,14 @@ public class ReprojectEntityConsumer implements EntityConsumerAdapter {
    * @param inputSRID the input SRID
    * @param outputSRID the output SRID
    */
-  public ReprojectEntityConsumer(int inputSRID, int outputSRID) {
+  public EntityProjectionTransformer(int inputSRID, int outputSRID) {
     this.projectionTransformer = new ProjectionTransformer(inputSRID, outputSRID);
   }
 
   /** {@inheritDoc} */
   @Override
-  public void match(Node node) {
-    handleElement(node);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(Way way) {
-    handleElement(way);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(Relation relation) {
-    handleElement(relation);
-  }
-
-  /** {@inheritDoc} */
-  private void handleElement(Element element) {
-    if (element.getGeometry() != null) {
+  public void accept(Entity entity) {
+    if (entity instanceof Element element && element.getGeometry() != null) {
       Geometry geometry = projectionTransformer.transform(element.getGeometry());
       element.setGeometry(geometry);
     }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityToGeometryMapper.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityToGeometryMapper.java
new file mode 100644
index 00000000..cbeca714
--- /dev/null
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/EntityToGeometryMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.baremaps.openstreetmap.function;
+
+
+
+import java.util.Optional;
+import java.util.function.Function;
+import org.apache.baremaps.openstreetmap.model.*;
+import org.apache.baremaps.stream.StreamException;
+import org.locationtech.jts.geom.Geometry;
+
+/** A function that maps an {@code Entity} to its {@code Geometry}. */
+public class EntityToGeometryMapper implements Function<Entity, Optional<Geometry>> {
+
+  @Override
+  public Optional<Geometry> apply(Entity entity) {
+    try {
+      if (entity instanceof Node node) {
+        return Optional.ofNullable(node.getGeometry());
+      } else if (entity instanceof Way way) {
+        return Optional.ofNullable(way.getGeometry());
+      } else if (entity instanceof Relation relation) {
+        return Optional.ofNullable(relation.getGeometry());
+      } else if (entity instanceof Header header) {
+        return Optional.empty();
+      } else if (entity instanceof Bound bound) {
+        return Optional.empty();
+      } else {
+        throw new StreamException("Unknown entity type.");
+      }
+    } catch (Exception e) {
+      throw new StreamException(e);
+    }
+  }
+}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ExtractGeometryFunction.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ExtractGeometryFunction.java
deleted file mode 100644
index 1552e354..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ExtractGeometryFunction.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.function;
-
-
-
-import java.util.Optional;
-import org.apache.baremaps.openstreetmap.model.Bound;
-import org.apache.baremaps.openstreetmap.model.Header;
-import org.apache.baremaps.openstreetmap.model.Node;
-import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
-import org.locationtech.jts.geom.Geometry;
-
-/** A function that maps an {@code Entity} to its {@code Geometry}. */
-public class ExtractGeometryFunction implements EntityFunction<Optional<Geometry>> {
-
-  /** {@inheritDoc} */
-  @Override
-  public Optional<Geometry> match(Header header) throws Exception {
-    return Optional.empty();
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public Optional<Geometry> match(Bound bound) throws Exception {
-    return Optional.empty();
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public Optional<Geometry> match(Node node) throws Exception {
-    return Optional.ofNullable(node.getGeometry());
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public Optional<Geometry> match(Way way) throws Exception {
-    return Optional.ofNullable(way.getGeometry());
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public Optional<Geometry> match(Relation relation) throws Exception {
-    return Optional.ofNullable(relation.getGeometry());
-  }
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/NodeGeometryBuilder.java
similarity index 53%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/NodeGeometryBuilder.java
index a9507c2e..3fc5c9a6 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/ChangeConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/NodeGeometryBuilder.java
@@ -12,32 +12,22 @@
 
 package org.apache.baremaps.openstreetmap.function;
 
-
+import static org.apache.baremaps.openstreetmap.utils.GeometryUtils.GEOMETRY_FACTORY_WGS84;
 
 import java.util.function.Consumer;
-import org.apache.baremaps.openstreetmap.model.Change;
-import org.apache.baremaps.stream.StreamException;
+import org.apache.baremaps.openstreetmap.model.Node;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Point;
 
-/** Represents an operation on changes of different types. */
-public interface ChangeConsumer extends Consumer<Change> {
+/**
+ * A consumer that builds and sets a node geometry via side effects.
+ */
+public class NodeGeometryBuilder implements Consumer<Node> {
 
   /** {@inheritDoc} */
   @Override
-  default void accept(Change change) {
-    try {
-      change.visit(this);
-    } catch (StreamException e) {
-      throw e;
-    } catch (Exception e) {
-      throw new StreamException(e);
-    }
+  public void accept(Node node) {
+    Point point = GEOMETRY_FACTORY_WGS84.createPoint(new Coordinate(node.getLon(), node.getLat()));
+    node.setGeometry(point);
   }
-
-  /**
-   * Matches an operation on a {@code Change}.
-   *
-   * @param change the change
-   * @throws Exception
-   */
-  void match(Change change) throws Exception;
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CreateGeometryConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/RelationGeometryBuilder.java
similarity index 62%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CreateGeometryConsumer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/RelationGeometryBuilder.java
index 22857e08..552837f3 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/CreateGeometryConsumer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/RelationGeometryBuilder.java
@@ -12,29 +12,15 @@
 
 package org.apache.baremaps.openstreetmap.function;
 
+import static org.apache.baremaps.openstreetmap.utils.GeometryUtils.GEOMETRY_FACTORY_WGS84;
 
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+import java.util.function.Consumer;
 import org.apache.baremaps.collection.LongDataMap;
 import org.apache.baremaps.openstreetmap.model.Member;
-import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
-import org.apache.baremaps.openstreetmap.model.Way;
 import org.apache.baremaps.stream.StreamException;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
-import org.locationtech.jts.geom.LineString;
-import org.locationtech.jts.geom.LinearRing;
-import org.locationtech.jts.geom.MultiPolygon;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.locationtech.jts.geom.PrecisionModel;
+import org.locationtech.jts.geom.*;
 import org.locationtech.jts.geom.prep.PreparedGeometry;
 import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
 import org.locationtech.jts.geom.util.PolygonExtracter;
@@ -43,58 +29,31 @@ import org.locationtech.jts.operation.union.CascadedPolygonUnion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** A consumer that creates and sets the geometry of OpenStreetMap entities via side-effects. */
-public class CreateGeometryConsumer implements EntityConsumerAdapter {
+/**
+ * A consumer that builds and sets a relation geometry via side effects.
+ */
+public class RelationGeometryBuilder implements Consumer<Relation> {
 
-  private static final Logger logger = LoggerFactory.getLogger(CreateGeometryConsumer.class);
+  private static final Logger logger = LoggerFactory.getLogger(RelationGeometryBuilder.class);
 
-  protected final GeometryFactory geometryFactory;
-  private final LongDataMap<Coordinate> coordinates;
-  private final LongDataMap<List<Long>> references;
+  private final LongDataMap<Coordinate> coordinateMap;
+  private final LongDataMap<List<Long>> referenceMap;
 
   /**
-   * Constructs a consumer that uses the provided caches to create and set geometries.
+   * Constructs a relation geometry builder.
    *
-   * @param coordinates the coordinate cache
-   * @param references the reference cache
+   * @param coordinateMap the coordinates map
+   * @param referenceMap the references map
    */
-  public CreateGeometryConsumer(LongDataMap<Coordinate> coordinates,
-      LongDataMap<List<Long>> references) {
-    this.geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
-    this.coordinates = coordinates;
-    this.references = references;
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(Node node) {
-    Point point = geometryFactory.createPoint(new Coordinate(node.getLon(), node.getLat()));
-    node.setGeometry(point);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(Way way) {
-    try {
-      List<Coordinate> list = way.getNodes().stream().map(coordinates::get).toList();
-      Coordinate[] array = list.toArray(new Coordinate[list.size()]);
-      LineString line = geometryFactory.createLineString(array);
-      if (!line.isEmpty()) {
-        if (!line.isClosed()) {
-          way.setGeometry(line);
-        } else {
-          Polygon polygon = geometryFactory.createPolygon(line.getCoordinates());
-          way.setGeometry(polygon);
-        }
-      }
-    } catch (Exception e) {
-      logger.warn("Unable to build the geometry for way #" + way.getId(), e);
-    }
+  public RelationGeometryBuilder(LongDataMap<Coordinate> coordinateMap,
+      LongDataMap<List<Long>> referenceMap) {
+    this.coordinateMap = coordinateMap;
+    this.referenceMap = referenceMap;
   }
 
   /** {@inheritDoc} */
   @Override
-  public void match(Relation relation) {
+  public void accept(Relation relation) {
     try {
       Map<String, String> tags = relation.getTags();
 
@@ -124,7 +83,7 @@ public class CreateGeometryConsumer implements EntityConsumerAdapter {
         relation.setGeometry(polygon);
       } else if (polygons.size() > 1) {
         MultiPolygon multiPolygon =
-            geometryFactory.createMultiPolygon(polygons.toArray(new Polygon[0]));
+            GEOMETRY_FACTORY_WGS84.createMultiPolygon(polygons.toArray(new Polygon[0]));
         relation.setGeometry(multiPolygon);
       }
     } catch (Exception e) {
@@ -147,7 +106,8 @@ public class CreateGeometryConsumer implements EntityConsumerAdapter {
           it.remove();
         }
       }
-      Polygon polygon = geometryFactory.createPolygon(shell, holes.toArray(new LinearRing[0]));
+      Polygon polygon =
+          GEOMETRY_FACTORY_WGS84.createPolygon(shell, holes.toArray(new LinearRing[0]));
       polygons.add(polygon);
     }
     return polygons;
@@ -182,7 +142,7 @@ public class CreateGeometryConsumer implements EntityConsumerAdapter {
         .filter(m -> role.equals(m.getRole())).forEach(member -> {
           LineString line = createLine(member);
           if (line.isClosed()) {
-            Polygon polygon = geometryFactory.createPolygon(line.getCoordinates());
+            Polygon polygon = GEOMETRY_FACTORY_WGS84.createPolygon(line.getCoordinates());
             polygons.add(polygon);
           } else {
             lineMerger.add(line);
@@ -191,7 +151,7 @@ public class CreateGeometryConsumer implements EntityConsumerAdapter {
     lineMerger.getMergedLineStrings().stream().forEach(geometry -> {
       LineString line = (LineString) geometry;
       if (line.isClosed()) {
-        Polygon polygon = geometryFactory.createPolygon(line.getCoordinates());
+        Polygon polygon = GEOMETRY_FACTORY_WGS84.createPolygon(line.getCoordinates());
         polygons.add(polygon);
       }
     });
@@ -200,10 +160,10 @@ public class CreateGeometryConsumer implements EntityConsumerAdapter {
 
   private LineString createLine(Member member) {
     try {
-      List<Long> refs = this.references.get(member.getRef());
-      List<Coordinate> coords = refs.stream().map(coordinates::get).toList();
+      List<Long> refs = referenceMap.get(member.getRef());
+      List<Coordinate> coords = refs.stream().map(coordinateMap::get).toList();
       Coordinate[] array = coords.toArray(new Coordinate[coords.size()]);
-      return geometryFactory.createLineString(array);
+      return GEOMETRY_FACTORY_WGS84.createLineString(array);
     } catch (Exception e) {
       throw new StreamException(e);
     }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/WayGeometryBuilder.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/WayGeometryBuilder.java
new file mode 100644
index 00000000..392cb909
--- /dev/null
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/function/WayGeometryBuilder.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.baremaps.openstreetmap.function;
+
+import static org.apache.baremaps.openstreetmap.utils.GeometryUtils.GEOMETRY_FACTORY_WGS84;
+
+import java.util.List;
+import java.util.function.Consumer;
+import org.apache.baremaps.collection.LongDataMap;
+import org.apache.baremaps.openstreetmap.model.Way;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Polygon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A consumer that builds and sets a way geometry via side effects.
+ */
+public class WayGeometryBuilder implements Consumer<Way> {
+
+  private static final Logger logger = LoggerFactory.getLogger(WayGeometryBuilder.class);
+
+  private final LongDataMap<Coordinate> coordinateMap;
+
+  /**
+   * Constructs a way geometry builder.
+   *
+   * @param coordinateMap the coordinates map
+   */
+  public WayGeometryBuilder(LongDataMap<Coordinate> coordinateMap) {
+    this.coordinateMap = coordinateMap;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void accept(Way way) {
+    try {
+      List<Coordinate> list = way.getNodes().stream().map(coordinateMap::get).toList();
+      Coordinate[] array = list.toArray(new Coordinate[list.size()]);
+      LineString line = GEOMETRY_FACTORY_WGS84.createLineString(array);
+      if (!line.isEmpty()) {
+        if (!line.isClosed()) {
+          way.setGeometry(line);
+        } else {
+          Polygon polygon = GEOMETRY_FACTORY_WGS84.createPolygon(line.getCoordinates());
+          way.setGeometry(polygon);
+        }
+      }
+    } catch (Exception e) {
+      logger.warn("Unable to build the geometry for way #" + way.getId(), e);
+    }
+  }
+}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Blob.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Blob.java
index e3974a32..0426dea5 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Blob.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Blob.java
@@ -23,7 +23,7 @@ import org.apache.baremaps.osm.binary.Fileformat;
 import org.apache.baremaps.osm.binary.Fileformat.BlobHeader;
 
 /** Represents a raw blob of data in an OpenStreetMap dataset. */
-public class Blob {
+public final class Blob {
 
   private final BlobHeader header;
   private final byte[] rawData;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Block.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Block.java
index 47e10bb7..5a3d6cf6 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Block.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Block.java
@@ -13,12 +13,11 @@
 package org.apache.baremaps.openstreetmap.model;
 
 
-
-import org.apache.baremaps.openstreetmap.function.BlockConsumer;
-import org.apache.baremaps.openstreetmap.function.BlockFunction;
-
 /** Represents a block of data in an OpenStreetMap dataset. */
-public abstract class Block {
+public abstract sealed
+class Block
+permits HeaderBlock, DataBlock
+{
 
   private final Blob blob;
 
@@ -40,21 +39,4 @@ public abstract class Block {
     return blob;
   }
 
-  /**
-   * Accepts the specified block consumer.
-   *
-   * @param consumer the consumer
-   * @throws Exception
-   */
-  public abstract void visit(BlockConsumer consumer) throws Exception;
-
-  /**
-   * Applies the specified block function.
-   *
-   * @param function the function
-   * @param <T> the return type of the function
-   * @return the function result
-   * @throws Exception
-   */
-  public abstract <T> T visit(BlockFunction<T> function) throws Exception;
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Bound.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Bound.java
index 1fa39ea9..d6358ac7 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Bound.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Bound.java
@@ -16,11 +16,9 @@ package org.apache.baremaps.openstreetmap.model;
 
 import java.util.Objects;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
 
 /** Represents the bounds of an OpenStreetMap dataset. */
-public class Bound implements Entity {
+public final class Bound implements Entity {
 
   private final double maxLat;
 
@@ -81,18 +79,6 @@ public class Bound implements Entity {
     return minLon;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(EntityConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(EntityFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   /** {@inheritDoc} */
   @Override
   public boolean equals(Object o) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Change.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Change.java
index 5cfd942e..c8c8d354 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Change.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Change.java
@@ -16,8 +16,6 @@ package org.apache.baremaps.openstreetmap.model;
 
 import java.util.List;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.ChangeConsumer;
-import org.apache.baremaps.openstreetmap.function.ChangeFunction;
 
 /** Represents a change in an OpenStreetMap dataset. */
 public final class Change {
@@ -59,16 +57,6 @@ public final class Change {
     return entities;
   }
 
-  /** Visits the entity with the provided entity consumer. */
-  public void visit(ChangeConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** Visits the entity with the provided entity function. */
-  public <T> T visit(ChangeFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   /** {@inheritDoc} */
   @Override
   public String toString() {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/DataBlock.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/DataBlock.java
index 1bb88aee..5fa79205 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/DataBlock.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/DataBlock.java
@@ -15,11 +15,9 @@ package org.apache.baremaps.openstreetmap.model;
 
 
 import java.util.List;
-import org.apache.baremaps.openstreetmap.function.BlockConsumer;
-import org.apache.baremaps.openstreetmap.function.BlockFunction;
 
 /** Represents a data block in an OpenStreetMap dataset. */
-public class DataBlock extends Block {
+public final class DataBlock extends Block {
 
   private final List<Node> denseNodes;
   private final List<Node> nodes;
@@ -80,15 +78,4 @@ public class DataBlock extends Block {
     return relations;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(BlockConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(BlockFunction<T> function) throws Exception {
-    return function.match(this);
-  }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Element.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Element.java
index f1a19d44..52200f87 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Element.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Element.java
@@ -23,7 +23,11 @@ import org.locationtech.jts.geom.Geometry;
  * Represents an element in an OpenStreetMap dataset. Elements are a basis to model the physical
  * world.
  */
-public abstract class Element implements Entity {
+public sealed
+
+abstract class Element implements Entity
+permits Node, Way, Relation
+{
 
   protected final long id;
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Entity.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Entity.java
index 26a2d97f..561f1a20 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Entity.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Entity.java
@@ -14,18 +14,13 @@ package org.apache.baremaps.openstreetmap.model;
 
 
 
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
-
 /**
  * Represents an entity in an OpenStreetMap dataset. Entities are a basis to model all the objects
  * in OpenStreetMap.
  */
-public interface Entity {
-
-  /** Visits the entity with the provided entity consumer. */
-  void visit(EntityConsumer consumer) throws Exception;
+public sealed
+interface Entity
+permits Header, Bound, Element
+{
 
-  /** Visits the entity with the provided entity function. */
-  <T> T visit(EntityFunction<T> function) throws Exception;
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Header.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Header.java
index 9f3aa9ea..3661315d 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Header.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Header.java
@@ -17,11 +17,9 @@ package org.apache.baremaps.openstreetmap.model;
 import java.time.LocalDateTime;
 import java.util.Objects;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
 
 /** Represents a header entity in an OpenStreetMap dataset. */
-public class Header implements Entity {
+public final class Header implements Entity {
 
   private final Long replicationSequenceNumber;
   private final LocalDateTime replicationTimestamp;
@@ -92,16 +90,6 @@ public class Header implements Entity {
     return writingProgram;
   }
 
-  @Override
-  public void visit(EntityConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  @Override
-  public <T> T visit(EntityFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/HeaderBlock.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/HeaderBlock.java
index c046ce0d..69a067af 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/HeaderBlock.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/HeaderBlock.java
@@ -14,11 +14,8 @@ package org.apache.baremaps.openstreetmap.model;
 
 
 
-import org.apache.baremaps.openstreetmap.function.BlockConsumer;
-import org.apache.baremaps.openstreetmap.function.BlockFunction;
-
 /** Represents a header block in an OpenStreetMap dataset. */
-public class HeaderBlock extends Block {
+public final class HeaderBlock extends Block {
 
   private final Header header;
 
@@ -55,15 +52,4 @@ public class HeaderBlock extends Block {
     return bound;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(BlockConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(BlockFunction<T> function) throws Exception {
-    return function.match(this);
-  }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Info.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Info.java
index cd0bd2bc..a020b938 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Info.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Info.java
@@ -19,7 +19,7 @@ import java.util.Objects;
 import java.util.StringJoiner;
 
 /** Represents all the metadata associated to an element in an OpenStreetMap dataset. */
-public class Info {
+public final class Info {
 
   protected final int version;
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Node.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Node.java
index a98845ef..7444bac4 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Node.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Node.java
@@ -17,8 +17,6 @@ package org.apache.baremaps.openstreetmap.model;
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
 import org.locationtech.jts.geom.Geometry;
 
 /** Represents a node element in an OpenStreetMap dataset. */
@@ -78,18 +76,6 @@ public final class Node extends Element {
     return lat;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(EntityConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(EntityFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   /** {@inheritDoc} */
   @Override
   public boolean equals(Object o) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java
index 00ce47af..653f2a1d 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java
@@ -18,8 +18,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
 import org.locationtech.jts.geom.Geometry;
 
 /** Represents a relation element in an OpenStreetMap dataset. */
@@ -64,18 +62,6 @@ public final class Relation extends Element {
     return members;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(EntityConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(EntityFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   /** {@inheritDoc} */
   @Override
   public boolean equals(Object o) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/State.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/State.java
index 2ecebdee..7ce3a6ac 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/State.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/State.java
@@ -17,7 +17,7 @@ package org.apache.baremaps.openstreetmap.model;
 import java.time.LocalDateTime;
 
 /** Represents the state of an OpenStreetMap dataset, enabling its replication. */
-public class State {
+public final class State {
 
   private final long sequenceNumber;
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Way.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Way.java
index 389980fd..5513b1d5 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Way.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/model/Way.java
@@ -18,8 +18,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
-import org.apache.baremaps.openstreetmap.function.EntityFunction;
 import org.locationtech.jts.geom.Geometry;
 
 /** Represents a way element in an OpenStreetMap dataset. */
@@ -63,18 +61,6 @@ public final class Way extends Element {
     return nodes;
   }
 
-  /** {@inheritDoc} */
-  @Override
-  public void visit(EntityConsumer consumer) throws Exception {
-    consumer.match(this);
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public <T> T visit(EntityFunction<T> function) throws Exception {
-    return function.match(this);
-  }
-
   /** {@inheritDoc} */
   @Override
   public boolean equals(Object o) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/DataBlockReader.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/DataBlockReader.java
index e8834ff4..3b6f92b1 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/DataBlockReader.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/DataBlockReader.java
@@ -38,7 +38,7 @@ import org.apache.baremaps.osm.binary.Osmformat.PrimitiveGroup;
 import org.apache.baremaps.stream.StreamException;
 
 /** A reader that extracts data blocks and entities from OpenStreetMap data blobs. */
-class DataBlockReader {
+public class DataBlockReader {
 
   private final Blob blob;
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/HeaderBlockReader.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/HeaderBlockReader.java
index 8d44f776..056ec721 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/HeaderBlockReader.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/HeaderBlockReader.java
@@ -28,7 +28,7 @@ import org.apache.baremaps.osm.binary.Osmformat.HeaderBBox;
 import org.apache.baremaps.stream.StreamException;
 
 /** A reader that extracts header blocks and entities from OpenStreetMap header blobs. */
-class HeaderBlockReader {
+public class HeaderBlockReader {
 
   public static final DateTimeFormatter format =
       DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfBlockReader.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfBlockReader.java
index 68f1367a..7e74c969 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfBlockReader.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfBlockReader.java
@@ -16,18 +16,11 @@ import static org.apache.baremaps.stream.ConsumerUtils.consumeThenReturn;
 
 import java.io.InputStream;
 import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Stream;
 import org.apache.baremaps.collection.LongDataMap;
 import org.apache.baremaps.openstreetmap.OsmReader;
-import org.apache.baremaps.openstreetmap.function.BlockEntityConsumer;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
-import org.apache.baremaps.openstreetmap.function.ReprojectEntityConsumer;
-import org.apache.baremaps.openstreetmap.model.Blob;
+import org.apache.baremaps.openstreetmap.function.*;
 import org.apache.baremaps.openstreetmap.model.Block;
-import org.apache.baremaps.openstreetmap.model.Entity;
-import org.apache.baremaps.openstreetmap.store.DataStoreConsumer;
 import org.apache.baremaps.stream.StreamUtils;
 import org.locationtech.jts.geom.Coordinate;
 
@@ -40,9 +33,9 @@ public class PbfBlockReader implements OsmReader<Block> {
 
   private int srid = 4326;
 
-  private LongDataMap<Coordinate> coordinates;
+  private LongDataMap<Coordinate> coordinateMap;
 
-  private LongDataMap<List<Long>> references;
+  private LongDataMap<List<Long>> referenceMap;
 
   /**
    * Gets the number of blobs buffered by the parser to parallelize deserialization.
@@ -109,18 +102,18 @@ public class PbfBlockReader implements OsmReader<Block> {
    *
    * @return the map of coordinates
    */
-  public LongDataMap<Coordinate> coordinates() {
-    return coordinates;
+  public LongDataMap<Coordinate> coordinateMap() {
+    return coordinateMap;
   }
 
   /**
    * Sets the map used to store coordinates for generating geometries.
    *
-   * @param coordinates the map of coordinates
+   * @param coordinateMap the map of coordinates
    * @return the parser
    */
-  public PbfBlockReader coordinates(LongDataMap<Coordinate> coordinates) {
-    this.coordinates = coordinates;
+  public PbfBlockReader coordinateMap(LongDataMap<Coordinate> coordinateMap) {
+    this.coordinateMap = coordinateMap;
     return this;
   }
 
@@ -129,18 +122,18 @@ public class PbfBlockReader implements OsmReader<Block> {
    *
    * @return the map of references
    */
-  public LongDataMap<List<Long>> references() {
-    return references;
+  public LongDataMap<List<Long>> referenceMap() {
+    return referenceMap;
   }
 
   /**
    * Sets the map used to store references for generating geometries.
    *
-   * @param references the map of references
+   * @param referenceMap the map of references
    * @return the parser
    */
-  public PbfBlockReader references(LongDataMap<List<Long>> references) {
-    this.references = references;
+  public PbfBlockReader referenceMap(LongDataMap<List<Long>> referenceMap) {
+    this.referenceMap = referenceMap;
     return this;
   }
 
@@ -151,32 +144,18 @@ public class PbfBlockReader implements OsmReader<Block> {
    * @return a stream of blocks
    */
   public Stream<Block> stream(InputStream inputStream) {
-    Stream<Block> blocks =
-        StreamUtils.bufferInSourceOrder(StreamUtils.stream(new BlobIterator(inputStream)),
-            this::read, Runtime.getRuntime().availableProcessors());
+    var blocks = StreamUtils.bufferInSourceOrder(StreamUtils.stream(new BlobIterator(inputStream)),
+        new BlobToBlockMapper(), Runtime.getRuntime().availableProcessors());
     if (geometry) {
-      Consumer<Block> cacheBlock = new DataStoreConsumer(coordinates, references);
-      Consumer<Entity> createGeometry = new CreateGeometryConsumer(coordinates, references);
-      if (srid != 4326) {
-        Consumer<Entity> reprojectGeometry = new ReprojectEntityConsumer(4326, srid);
-        createGeometry = createGeometry.andThen(reprojectGeometry);
-      }
-      Consumer<Block> prepareGeometries = new BlockEntityConsumer(createGeometry);
-      Function<Block, Block> prepareBlock =
-          consumeThenReturn(cacheBlock.andThen(prepareGeometries));
-      blocks = blocks.map(prepareBlock);
+      var cacheBuilder = new CacheBuilder(coordinateMap, referenceMap);
+      var entityGeometryBuilder = new EntityGeometryBuilder(coordinateMap, referenceMap);
+      var entityProjectionTransformer = new EntityProjectionTransformer(4326, srid);
+      var entityHandler = srid == 4326 ? entityGeometryBuilder
+          : entityGeometryBuilder.andThen(entityProjectionTransformer);
+      var blockEntitiesHandler = new BlockEntitiesHandler(entityHandler);
+      var blockMapper = consumeThenReturn(cacheBuilder.andThen(blockEntitiesHandler));
+      blocks = blocks.map(blockMapper);
     }
     return blocks;
   }
-
-  public Block read(Blob blob) {
-    switch (blob.header().getType()) {
-      case "OSMHeader":
-        return HeaderBlockReader.read(blob);
-      case "OSMData":
-        return DataBlockReader.read(blob);
-      default:
-        throw new RuntimeException("Unknown blob type");
-    }
-  }
 }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfEntityReader.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfEntityReader.java
index 4697481d..fb173fea 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfEntityReader.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/pbf/PbfEntityReader.java
@@ -17,8 +17,9 @@ package org.apache.baremaps.openstreetmap.pbf;
 import java.io.InputStream;
 import java.util.stream.Stream;
 import org.apache.baremaps.openstreetmap.OsmReader;
-import org.apache.baremaps.openstreetmap.function.BlockEntityConsumer;
+import org.apache.baremaps.openstreetmap.model.DataBlock;
 import org.apache.baremaps.openstreetmap.model.Entity;
+import org.apache.baremaps.openstreetmap.model.HeaderBlock;
 import org.apache.baremaps.stream.StreamException;
 
 /** A utility class for flattening the blocks streamed by a {@link PbfBlockReader}. */
@@ -45,7 +46,17 @@ public class PbfEntityReader implements OsmReader<Entity> {
     return reader.stream(inputStream).flatMap(block -> {
       try {
         Stream.Builder<Entity> entities = Stream.builder();
-        block.visit(new BlockEntityConsumer(entities::add));
+        if (block instanceof HeaderBlock headerBlock) {
+          entities.add(headerBlock.getHeader());
+          entities.add(headerBlock.getBound());
+        } else if (block instanceof DataBlock dataBlock) {
+          dataBlock.getDenseNodes().forEach(entities::add);
+          dataBlock.getNodes().forEach(entities::add);
+          dataBlock.getWays().forEach(entities::add);
+          dataBlock.getRelations().forEach(entities::add);
+        } else {
+          throw new StreamException("Unknown block type.");
+        }
         return entities.build();
       } catch (Exception e) {
         throw new StreamException(e);
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/store/DataStoreConsumer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/store/DataStoreConsumer.java
deleted file mode 100644
index 5121f7b6..00000000
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/store/DataStoreConsumer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package org.apache.baremaps.openstreetmap.store;
-
-
-
-import java.util.List;
-import org.apache.baremaps.collection.LongDataMap;
-import org.apache.baremaps.openstreetmap.function.BlockConsumerAdapter;
-import org.apache.baremaps.openstreetmap.model.DataBlock;
-import org.locationtech.jts.geom.Coordinate;
-
-/** A consumer that stores osm nodes and ways in the provided caches. */
-public class DataStoreConsumer implements BlockConsumerAdapter {
-
-  private final LongDataMap<Coordinate> coordinates;
-  private final LongDataMap<List<Long>> references;
-
-  /**
-   * Constructs a {@code CacheBlockConsumer} with the provided caches.
-   *
-   * @param coordinates the map of coordinates
-   * @param references the map of references
-   */
-  public DataStoreConsumer(LongDataMap<Coordinate> coordinates,
-      LongDataMap<List<Long>> references) {
-    this.coordinates = coordinates;
-    this.references = references;
-  }
-
-  /** {@inheritDoc} */
-  @Override
-  public void match(DataBlock dataBlock) throws Exception {
-    dataBlock.getDenseNodes().stream().forEach(
-        node -> coordinates.put(node.getId(), new Coordinate(node.getLon(), node.getLat())));
-    dataBlock.getNodes().stream().forEach(
-        node -> coordinates.put(node.getId(), new Coordinate(node.getLon(), node.getLat())));
-    dataBlock.getWays().stream().forEach(way -> references.put(way.getId(), way.getNodes()));
-  }
-}
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/GeometryUtils.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
similarity index 93%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/GeometryUtils.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
index 3a9df8e4..3faadf67 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/GeometryUtils.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
@@ -10,12 +10,13 @@
  * the License.
  */
 
-package org.apache.baremaps.openstreetmap.geometry;
+package org.apache.baremaps.openstreetmap.utils;
 
 import static org.locationtech.jts.io.WKBConstants.wkbNDR;
 
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.PrecisionModel;
 import org.locationtech.jts.io.ParseException;
 import org.locationtech.jts.io.WKBReader;
 import org.locationtech.jts.io.WKBWriter;
@@ -27,6 +28,9 @@ import org.locationtech.proj4j.CoordinateTransformFactory;
 /** Utility methods for serializing and deserializing geometries. */
 public class GeometryUtils {
 
+  public static final GeometryFactory GEOMETRY_FACTORY_WGS84 =
+      new GeometryFactory(new PrecisionModel(), 4326);
+
   private GeometryUtils() {}
 
   /**
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/InputStreamProgress.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/InputStreamProgress.java
similarity index 97%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/InputStreamProgress.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/InputStreamProgress.java
index 4c28fa74..6472f59c 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/InputStreamProgress.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/InputStreamProgress.java
@@ -10,7 +10,7 @@
  * the License.
  */
 
-package org.apache.baremaps.openstreetmap.progress;
+package org.apache.baremaps.openstreetmap.utils;
 
 
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/ProgressLogger.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProgressLogger.java
similarity index 97%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/ProgressLogger.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProgressLogger.java
index a4969cbc..7b043921 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/ProgressLogger.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProgressLogger.java
@@ -10,7 +10,7 @@
  * the License.
  */
 
-package org.apache.baremaps.openstreetmap.progress;
+package org.apache.baremaps.openstreetmap.utils;
 
 
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformer.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProjectionTransformer.java
similarity index 99%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformer.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProjectionTransformer.java
index 0ac208cb..4859a09a 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformer.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/ProjectionTransformer.java
@@ -10,7 +10,7 @@
  * the License.
  */
 
-package org.apache.baremaps.openstreetmap.geometry;
+package org.apache.baremaps.openstreetmap.utils;
 
 
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/StreamProgress.java b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/StreamProgress.java
similarity index 96%
rename from baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/StreamProgress.java
rename to baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/StreamProgress.java
index 52d8a62f..dbacdf4b 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/progress/StreamProgress.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/openstreetmap/utils/StreamProgress.java
@@ -10,7 +10,7 @@
  * the License.
  */
 
-package org.apache.baremaps.openstreetmap.progress;
+package org.apache.baremaps.openstreetmap.utils;
 
 
 
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureProjectionTransform.java b/baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureSetProjectionTransform.java
similarity index 93%
rename from baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureProjectionTransform.java
rename to baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureSetProjectionTransform.java
index f8aba5be..a7743a6a 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureProjectionTransform.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/storage/FeatureSetProjectionTransform.java
@@ -16,7 +16,7 @@ package org.apache.baremaps.storage;
 
 import java.util.Optional;
 import java.util.stream.Stream;
-import org.apache.baremaps.openstreetmap.geometry.ProjectionTransformer;
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.event.StoreEvent;
@@ -28,13 +28,13 @@ import org.opengis.geometry.Envelope;
 import org.opengis.metadata.Metadata;
 import org.opengis.util.GenericName;
 
-public class FeatureProjectionTransform implements FeatureSet {
+public class FeatureSetProjectionTransform implements FeatureSet {
 
   private final FeatureSet featureSet;
 
   private final ProjectionTransformer projectionTransformer;
 
-  public FeatureProjectionTransform(FeatureSet featureSet,
+  public FeatureSetProjectionTransform(FeatureSet featureSet,
       ProjectionTransformer projectionTransformer) {
     this.featureSet = featureSet;
     this.projectionTransformer = projectionTransformer;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ExportVectorTiles.java b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ExportVectorTiles.java
index c856d728..e806281a 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ExportVectorTiles.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ExportVectorTiles.java
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import javax.sql.DataSource;
@@ -32,7 +31,7 @@ import org.apache.baremaps.database.tile.Tile;
 import org.apache.baremaps.database.tile.TileChannel;
 import org.apache.baremaps.database.tile.TileStore;
 import org.apache.baremaps.database.tile.TileStoreException;
-import org.apache.baremaps.openstreetmap.progress.StreamProgress;
+import org.apache.baremaps.openstreetmap.utils.StreamProgress;
 import org.apache.baremaps.stream.StreamUtils;
 import org.apache.baremaps.tileset.Tileset;
 import org.apache.baremaps.tileset.TilesetQuery;
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportGeoPackage.java b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportGeoPackage.java
index 7dc348bc..f6f9ec99 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportGeoPackage.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportGeoPackage.java
@@ -12,8 +12,8 @@
 
 package org.apache.baremaps.workflow.tasks;
 
-import org.apache.baremaps.openstreetmap.geometry.ProjectionTransformer;
-import org.apache.baremaps.storage.FeatureProjectionTransform;
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
+import org.apache.baremaps.storage.FeatureSetProjectionTransform;
 import org.apache.baremaps.storage.geopackage.GeoPackageDatabase;
 import org.apache.baremaps.storage.postgres.PostgresDatabase;
 import org.apache.baremaps.workflow.Task;
@@ -40,7 +40,7 @@ public record ImportGeoPackage(String file, String database, Integer sourceSRID,
       var postgresDatabase = new PostgresDatabase(dataSource);
       for (var resource : geoPackageStore.components()) {
         if (resource instanceof FeatureSet featureSet) {
-          postgresDatabase.add(new FeatureProjectionTransform(
+          postgresDatabase.add(new FeatureSetProjectionTransform(
             featureSet, new ProjectionTransformer(sourceSRID, targetSRID)));
         }
       }
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportOpenStreetMap.java b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportOpenStreetMap.java
index 9ff67e52..353f319e 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportOpenStreetMap.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportOpenStreetMap.java
@@ -60,16 +60,16 @@ public record ImportOpenStreetMap(String file, String database, Integer database
 
     var cacheDir = Files.createTempDirectory(Paths.get("."), "cache_");
 
-    LongDataMap<Coordinate> coordinates;
+    LongDataMap<Coordinate> coordinateMap;
     if (Files.size(path) > 1 << 30) {
       var coordinatesDir = Files.createDirectories(cacheDir.resolve("coordinates"));
-      coordinates = new LongSizedDataDenseMap<>(
+      coordinateMap = new LongSizedDataDenseMap<>(
         new LonLatDataType(),
         new OnDiskDirectoryMemory(coordinatesDir));
     } else {
       var coordinatesKeysDir = Files.createDirectories(cacheDir.resolve("coordinates_keys"));
       var coordinatesValsDir = Files.createDirectories(cacheDir.resolve("coordinates_vals"));
-      coordinates =
+      coordinateMap =
         new LongDataSortedMap<>(
           new AlignedDataList<>(
             new PairDataType<>(new LongDataType(), new LongDataType()),
@@ -82,7 +82,7 @@ public record ImportOpenStreetMap(String file, String database, Integer database
 
     var referencesKeysDir = Files.createDirectories(cacheDir.resolve("references_keys"));
     var referencesValuesDir = Files.createDirectories(cacheDir.resolve("references_vals"));
-    var references =
+    var referenceMap =
       new LongDataSortedMap<>(
         new AlignedDataList<>(
           new PairDataType<>(new LongDataType(), new LongDataType()),
@@ -94,8 +94,8 @@ public record ImportOpenStreetMap(String file, String database, Integer database
 
     new ImportService(
       path,
-      coordinates,
-      references,
+      coordinateMap,
+      referenceMap,
       headerRepository,
       nodeRepository,
       wayRepository,
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportShapefile.java b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportShapefile.java
index f82b68b9..85ebf59a 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportShapefile.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/ImportShapefile.java
@@ -12,8 +12,8 @@
 
 package org.apache.baremaps.workflow.tasks;
 
-import org.apache.baremaps.openstreetmap.geometry.ProjectionTransformer;
-import org.apache.baremaps.storage.FeatureProjectionTransform;
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
+import org.apache.baremaps.storage.FeatureSetProjectionTransform;
 import org.apache.baremaps.storage.postgres.PostgresDatabase;
 import org.apache.baremaps.storage.shapefile.ShapefileFeatureSet;
 import org.apache.baremaps.workflow.Task;
@@ -37,7 +37,7 @@ public record ImportShapefile(String file, String database, Integer sourceSRID,
     try (var featureSet = new ShapefileFeatureSet(path)) {
       var dataSource = context.getDataSource(database);
       var postgresDatabase = new PostgresDatabase(dataSource);
-      postgresDatabase.add(new FeatureProjectionTransform(
+      postgresDatabase.add(new FeatureSetProjectionTransform(
         featureSet, new ProjectionTransformer(sourceSRID, targetSRID)));
       logger.info("Finished importing {} into {}", file, database);
     } catch (Exception e) {
diff --git a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOpenStreetMap.java b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOpenStreetMap.java
index 643fc212..558ecbdb 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOpenStreetMap.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/UpdateOpenStreetMap.java
@@ -39,16 +39,16 @@ public record UpdateOpenStreetMap(String database, Integer databaseSrid) impleme
   public void execute(WorkflowContext context) throws Exception {
     logger.info("Updating {}", database);
     var datasource = context.getDataSource(database);
-    LongDataMap<Coordinate> coordinates = new PostgresCoordinateMap(datasource);
-    LongDataMap<List<Long>> references = new PostgresReferenceMap(datasource);
+    LongDataMap<Coordinate> coordinateMap = new PostgresCoordinateMap(datasource);
+    LongDataMap<List<Long>> referenceMap = new PostgresReferenceMap(datasource);
     HeaderRepository headerRepository = new PostgresHeaderRepository(datasource);
     Repository<Long, Node> nodeRepository = new PostgresNodeRepository(datasource);
     Repository<Long, Way> wayRepository = new PostgresWayRepository(datasource);
     Repository<Long, Relation> relationRepository = new PostgresRelationRepository(datasource);
     var action =
       new UpdateService(
-        coordinates,
-        references,
+        coordinateMap,
+        referenceMap,
         headerRepository,
         nodeRepository,
         wayRepository,
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/database/database/SaveBlockConsumerTest.java b/baremaps-core/src/test/java/org/apache/baremaps/database/database/BlockImporterTest.java
similarity index 91%
rename from baremaps-core/src/test/java/org/apache/baremaps/database/database/SaveBlockConsumerTest.java
rename to baremaps-core/src/test/java/org/apache/baremaps/database/database/BlockImporterTest.java
index 73e22bc3..cfd69618 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/database/database/SaveBlockConsumerTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/database/database/BlockImporterTest.java
@@ -22,8 +22,8 @@ import java.nio.file.Files;
 import java.sql.Connection;
 import java.sql.SQLException;
 import javax.sql.DataSource;
+import org.apache.baremaps.database.BlockImporter;
 import org.apache.baremaps.database.PostgresUtils;
-import org.apache.baremaps.database.SaveBlockConsumer;
 import org.apache.baremaps.database.repository.PostgresHeaderRepository;
 import org.apache.baremaps.database.repository.PostgresNodeRepository;
 import org.apache.baremaps.database.repository.PostgresRelationRepository;
@@ -36,7 +36,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
-class SaveBlockConsumerTest extends PostgresContainerTest {
+class BlockImporterTest extends PostgresContainerTest {
 
   public DataSource dataSource;
   public PostgresHeaderRepository headerRepository;
@@ -62,12 +62,12 @@ class SaveBlockConsumerTest extends PostgresContainerTest {
   @Tag("integration")
   void test() throws RepositoryException, URISyntaxException, IOException {
     // Import data
-    SaveBlockConsumer dataImporter = new SaveBlockConsumer(headerRepository, nodeRepository,
-        tableRepository, relationRepository);
+    BlockImporter blockImporter =
+        new BlockImporter(headerRepository, nodeRepository, tableRepository, relationRepository);
 
     try (InputStream inputStream = Files.newInputStream(TestFiles.resolve("simple/data.osm.pbf"))) {
 
-      new PbfBlockReader().stream(inputStream).forEach(dataImporter);
+      new PbfBlockReader().stream(inputStream).forEach(blockImporter);
 
       // Check node importation
       assertNull(nodeRepository.get(0l));
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateDataTest.java b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateDataTest.java
index ec0f6ba9..7e274c7d 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateDataTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateDataTest.java
@@ -51,13 +51,13 @@ class ImportUpdateDataTest extends DatabaseContainerTest {
     PostgresWayRepository wayRepository = new PostgresWayRepository(dataSource());
     PostgresRelationRepository relationRepository = new PostgresRelationRepository(dataSource());
 
-    LongDataMap<Coordinate> coordinates =
+    LongDataMap<Coordinate> coordinateMap =
         new LongDataOpenHashMap<>(new DataStore<>(new CoordinateDataType(), new OnHeapMemory()));
-    LongDataMap<List<Long>> references =
+    LongDataMap<List<Long>> referenceMap =
         new LongDataOpenHashMap<>(new DataStore<>(new LongListDataType(), new OnHeapMemory()));
 
     // Import data
-    new ImportService(SIMPLE_DATA_OSM_PBF, coordinates, references, headerRepository,
+    new ImportService(SIMPLE_DATA_OSM_PBF, coordinateMap, referenceMap, headerRepository,
         nodeRepository, wayRepository, relationRepository, 3857).call();
 
     headerRepository.put(new Header(0l, LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0),
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateLiechtensteinTest.java b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateLiechtensteinTest.java
index 33df9dd5..80ae6916 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateLiechtensteinTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateLiechtensteinTest.java
@@ -48,13 +48,13 @@ class ImportUpdateLiechtensteinTest extends DatabaseContainerTest {
     PostgresWayRepository wayRepository = new PostgresWayRepository(dataSource());
     PostgresRelationRepository relationRepository = new PostgresRelationRepository(dataSource());
 
-    LongDataMap<Coordinate> coordinates =
+    LongDataMap<Coordinate> coordinateMap =
         new LongDataOpenHashMap<>(new DataStore<>(new CoordinateDataType(), new OnHeapMemory()));
-    LongDataMap<List<Long>> references =
+    LongDataMap<List<Long>> referenceMap =
         new LongDataOpenHashMap<>(new DataStore<>(new LongListDataType(), new OnHeapMemory()));
 
     // Import data
-    new ImportService(LIECHTENSTEIN_OSM_PBF, coordinates, references, headerRepository,
+    new ImportService(LIECHTENSTEIN_OSM_PBF, coordinateMap, referenceMap, headerRepository,
         nodeRepository, wayRepository, relationRepository, 3857).call();
     assertEquals(2434l, headerRepository.selectLatest().getReplicationSequenceNumber());
 
@@ -62,28 +62,28 @@ class ImportUpdateLiechtensteinTest extends DatabaseContainerTest {
     headerRepository.put(new Header(2434l, LocalDateTime.of(2019, 11, 18, 21, 19, 5, 0),
         "file:///" + LIECHTENSTEIN_DIR, "", ""));
 
-    coordinates = new PostgresCoordinateMap(dataSource());
-    references = new PostgresReferenceMap(dataSource());
+    coordinateMap = new PostgresCoordinateMap(dataSource());
+    referenceMap = new PostgresReferenceMap(dataSource());
 
-    assertEquals(0, new DiffService(coordinates, references, headerRepository, nodeRepository,
+    assertEquals(0, new DiffService(coordinateMap, referenceMap, headerRepository, nodeRepository,
         wayRepository, relationRepository, 3857, 14).call().size());
 
     // Update the database
-    new UpdateService(coordinates, references, headerRepository, nodeRepository, wayRepository,
+    new UpdateService(coordinateMap, referenceMap, headerRepository, nodeRepository, wayRepository,
         relationRepository, 3857).call();
     assertEquals(2435l, headerRepository.selectLatest().getReplicationSequenceNumber());
 
-    assertEquals(2, new DiffService(coordinates, references, headerRepository, nodeRepository,
+    assertEquals(2, new DiffService(coordinateMap, referenceMap, headerRepository, nodeRepository,
         wayRepository, relationRepository, 3857, 14).call().size());
 
-    new UpdateService(coordinates, references, headerRepository, nodeRepository, wayRepository,
+    new UpdateService(coordinateMap, referenceMap, headerRepository, nodeRepository, wayRepository,
         relationRepository, 3857).call();
     assertEquals(2436l, headerRepository.selectLatest().getReplicationSequenceNumber());
 
-    assertEquals(0, new DiffService(coordinates, references, headerRepository, nodeRepository,
+    assertEquals(0, new DiffService(coordinateMap, referenceMap, headerRepository, nodeRepository,
         wayRepository, relationRepository, 3857, 14).call().size());
 
-    new UpdateService(coordinates, references, headerRepository, nodeRepository, wayRepository,
+    new UpdateService(coordinateMap, referenceMap, headerRepository, nodeRepository, wayRepository,
         relationRepository, 3857).call();
     assertEquals(2437l, headerRepository.selectLatest().getReplicationSequenceNumber());
   }
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateMonacoTest.java b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateMonacoTest.java
index ce8edebf..6b2f2154 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateMonacoTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/database/database/ImportUpdateMonacoTest.java
@@ -47,14 +47,15 @@ class ImportUpdateMonacoTest extends DatabaseContainerTest {
     PostgresWayRepository wayRepository = new PostgresWayRepository(dataSource());
     PostgresRelationRepository relationRepository = new PostgresRelationRepository(dataSource());
 
-    LongDataMap<Coordinate> coordinates =
+    LongDataMap<Coordinate> coordinateMap =
         new LongDataOpenHashMap<>(new DataStore<>(new CoordinateDataType(), new OnHeapMemory()));
-    LongDataMap<List<Long>> references =
+    LongDataMap<List<Long>> referenceMap =
         new LongDataOpenHashMap<>(new DataStore<>(new LongListDataType(), new OnHeapMemory()));
 
     // Import data
-    new ImportService(TestFiles.resolve("monaco/monaco-210801.osm.pbf"), coordinates, references,
-        headerRepository, nodeRepository, wayRepository, relationRepository, 3857).call();
+    new ImportService(TestFiles.resolve("monaco/monaco-210801.osm.pbf"), coordinateMap,
+        referenceMap, headerRepository, nodeRepository, wayRepository, relationRepository, 3857)
+            .call();
 
     assertEquals(3047l, headerRepository.selectLatest().getReplicationSequenceNumber());
 
@@ -63,16 +64,16 @@ class ImportUpdateMonacoTest extends DatabaseContainerTest {
     headerRepository.put(new Header(3047l, LocalDateTime.of(2021, 8, 01, 20, 21, 41, 0),
         "file:///" + TestFiles.resolve("monaco"), "", ""));
 
-    coordinates = new PostgresCoordinateMap(dataSource());
-    references = new PostgresReferenceMap(dataSource());
+    coordinateMap = new PostgresCoordinateMap(dataSource());
+    referenceMap = new PostgresReferenceMap(dataSource());
 
     // Generate the diff and update the database
     long replicationSequenceNumber = headerRepository.selectLatest().getReplicationSequenceNumber();
     while (replicationSequenceNumber < 3075) {
-      new DiffService(coordinates, references, headerRepository, nodeRepository, wayRepository,
+      new DiffService(coordinateMap, referenceMap, headerRepository, nodeRepository, wayRepository,
           relationRepository, 3857, 14).call();
-      new UpdateService(coordinates, references, headerRepository, nodeRepository, wayRepository,
-          relationRepository, 3857).call();
+      new UpdateService(coordinateMap, referenceMap, headerRepository, nodeRepository,
+          wayRepository, relationRepository, 3857).call();
       long nextReplicationSequenceNumber =
           headerRepository.selectLatest().getReplicationSequenceNumber();
       assertEquals(replicationSequenceNumber + 1, nextReplicationSequenceNumber);
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 5a303624..441b29a1 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
@@ -32,7 +32,6 @@ import java.time.LocalDateTime;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import org.apache.baremaps.openstreetmap.function.EntityConsumer;
 import org.apache.baremaps.openstreetmap.model.Bound;
 import org.apache.baremaps.openstreetmap.model.Entity;
 import org.apache.baremaps.openstreetmap.model.Header;
@@ -150,38 +149,25 @@ class OpenStreetMapTest {
     AtomicLong nodes = new AtomicLong(0);
     AtomicLong ways = new AtomicLong(0);
     AtomicLong relations = new AtomicLong(0);
-    stream.forEach(new EntityConsumer() {
-      @Override
-      public void match(Header header) {
+    stream.forEach(entity -> {
+      if (entity instanceof Header header) {
         assertNotNull(header);
         assertEquals("osmium/1.8.0", header.getWritingProgram());
         headers.incrementAndGet();
-      }
-
-      @Override
-      public void match(Bound bound) {
+      } else if (entity instanceof Bound bound) {
         assertNotNull(bound);
         assertEquals(43.75169, bound.getMaxLat(), 0.000001);
         assertEquals(7.448637, bound.getMaxLon(), 0.000001);
         assertEquals(43.72335, bound.getMinLat(), 0.000001);
         assertEquals(7.409205, bound.getMinLon(), 0.000001);
         bounds.incrementAndGet();
-      }
-
-      @Override
-      public void match(Node node) {
+      } else if (entity instanceof Node node) {
         assertNotNull(node);
         nodes.incrementAndGet();
-      }
-
-      @Override
-      public void match(Way way) {
+      } else if (entity instanceof Way way) {
         assertNotNull(way);
         ways.incrementAndGet();
-      }
-
-      @Override
-      public void match(Relation relation) {
+      } else if (entity instanceof Relation relation) {
         assertNotNull(relation);
         relations.incrementAndGet();
       }
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/GeometryHandlerTest.java b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/EntityGeometryBuilderTest.java
similarity index 91%
rename from baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/GeometryHandlerTest.java
rename to baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/EntityGeometryBuilderTest.java
index 5247c883..69c515c4 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/GeometryHandlerTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/EntityGeometryBuilderTest.java
@@ -24,7 +24,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.baremaps.collection.LongDataMap;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
+import org.apache.baremaps.openstreetmap.function.EntityGeometryBuilder;
 import org.apache.baremaps.openstreetmap.model.Info;
 import org.apache.baremaps.openstreetmap.model.Member;
 import org.apache.baremaps.openstreetmap.model.Member.MemberType;
@@ -46,7 +46,7 @@ import org.locationtech.proj4j.CoordinateTransform;
 import org.locationtech.proj4j.Proj4jException;
 import org.locationtech.proj4j.ProjCoordinate;
 
-class GeometryHandlerTest {
+class EntityGeometryBuilderTest {
 
   static final CRSFactory CRS_FACTORY = new CRSFactory();
 
@@ -153,16 +153,16 @@ class GeometryHandlerTest {
       Arrays.asList(new Member(2l, MemberType.WAY, "outer"),
           new Member(4l, MemberType.WAY, "inner"), new Member(5l, MemberType.WAY, "inner")));
 
-  static final CreateGeometryConsumer GEOMETRY_BUILDER =
-      new CreateGeometryConsumer(COORDINATE_CACHE, REFERENCE_CACHE);
+  static final EntityGeometryBuilder GEOMETRY_BUILDER =
+      new EntityGeometryBuilder(COORDINATE_CACHE, REFERENCE_CACHE);
 
   @Test
   void handleNode() {
-    GEOMETRY_BUILDER.match(NODE_0);
+    GEOMETRY_BUILDER.accept(NODE_0);
     Point p0 = (Point) NODE_0.getGeometry();
     assertEquals(0, p0.getX());
     assertEquals(0, p0.getY());
-    GEOMETRY_BUILDER.match(NODE_2);
+    GEOMETRY_BUILDER.accept(NODE_2);
     Point p1 = (Point) NODE_2.getGeometry();
     assertEquals(4, p1.getX());
     assertEquals(4, p1.getY());
@@ -170,31 +170,31 @@ class GeometryHandlerTest {
 
   @Test
   void handleWay() {
-    GEOMETRY_BUILDER.match(WAY_0);
+    GEOMETRY_BUILDER.accept(WAY_0);
     assertNull(WAY_0.getGeometry());
-    GEOMETRY_BUILDER.match(WAY_1);
+    GEOMETRY_BUILDER.accept(WAY_1);
     assertTrue(WAY_1.getGeometry() instanceof LineString);
-    GEOMETRY_BUILDER.match(WAY_2);
+    GEOMETRY_BUILDER.accept(WAY_2);
     assertTrue(WAY_2.getGeometry() instanceof Polygon);
   }
 
   @Test
   void handleRelation() {
-    GEOMETRY_BUILDER.match(RELATION_0);
+    GEOMETRY_BUILDER.accept(RELATION_0);
     assertNull(RELATION_0.getGeometry());
-    GEOMETRY_BUILDER.match(RELATION_1);
+    GEOMETRY_BUILDER.accept(RELATION_1);
     assertNull(RELATION_1.getGeometry());
-    GEOMETRY_BUILDER.match(RELATION_2);
+    GEOMETRY_BUILDER.accept(RELATION_2);
     assertTrue(RELATION_2.getGeometry() instanceof Polygon);
-    GEOMETRY_BUILDER.match(RELATION_3);
+    GEOMETRY_BUILDER.accept(RELATION_3);
     assertTrue(RELATION_3.getGeometry() instanceof Polygon);
-    GEOMETRY_BUILDER.match(RELATION_4);
+    GEOMETRY_BUILDER.accept(RELATION_4);
     assertTrue(RELATION_4.getGeometry() instanceof MultiPolygon);
   }
 
   @Test
   void handleRelationWithHole() {
-    GEOMETRY_BUILDER.match(RELATION_5);
+    GEOMETRY_BUILDER.accept(RELATION_5);
     assertTrue(RELATION_5.getGeometry() instanceof Polygon);
     assertNotNull(((Polygon) RELATION_5.getGeometry()).getExteriorRing());
     assertEquals(1, ((Polygon) RELATION_5.getGeometry()).getNumInteriorRing());
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformerTest.java b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformerTest.java
index ccf6cfb4..de280c72 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformerTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/ProjectionTransformerTest.java
@@ -14,6 +14,7 @@ package org.apache.baremaps.openstreetmap.geometry;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import org.apache.baremaps.openstreetmap.utils.ProjectionTransformer;
 import org.junit.jupiter.api.Test;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.GeometryFactory;
diff --git a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryTest.java b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryBuilderTest.java
similarity index 91%
rename from baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryTest.java
rename to baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryBuilderTest.java
index 44903584..5512d1cd 100644
--- a/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryTest.java
+++ b/baremaps-core/src/test/java/org/apache/baremaps/openstreetmap/geometry/RelationGeometryBuilderTest.java
@@ -20,7 +20,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.zip.GZIPInputStream;
 import org.apache.baremaps.collection.LongDataMap;
-import org.apache.baremaps.openstreetmap.function.CreateGeometryConsumer;
+import org.apache.baremaps.openstreetmap.function.RelationGeometryBuilder;
 import org.apache.baremaps.openstreetmap.model.Entity;
 import org.apache.baremaps.openstreetmap.model.Node;
 import org.apache.baremaps.openstreetmap.model.Relation;
@@ -32,20 +32,20 @@ import org.junit.jupiter.api.Test;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
 
-class RelationGeometryTest {
+class RelationGeometryBuilderTest {
 
   Geometry handleRelation(String file) throws IOException {
     InputStream input = new GZIPInputStream(this.getClass().getResourceAsStream(file));
     List<Entity> entities = new XmlEntityReader().stream(input).toList();
-    LongDataMap<Coordinate> coordinates = new MockLongDataMap<>(
+    LongDataMap<Coordinate> coordinateMap = new MockLongDataMap<>(
         entities.stream().filter(e -> e instanceof Node).map(e -> (Node) e).collect(
             Collectors.toMap(n -> n.getId(), n -> new Coordinate(n.getLon(), n.getLat()))));
-    LongDataMap<List<Long>> references =
+    LongDataMap<List<Long>> referenceMap =
         new MockLongDataMap<>(entities.stream().filter(e -> e instanceof Way).map(e -> (Way) e)
             .collect(Collectors.toMap(w -> w.getId(), w -> w.getNodes())));
     Relation relation = entities.stream().filter(e -> e instanceof Relation).map(e -> (Relation) e)
         .findFirst().get();
-    new CreateGeometryConsumer(coordinates, references).match(relation);
+    new RelationGeometryBuilder(coordinateMap, referenceMap).accept(relation);
     return relation.getGeometry();
   }
 
diff --git a/baremaps-ogcapi/src/main/java/org/apache/baremaps/ogcapi/PostgisPlugin.java b/baremaps-ogcapi/src/main/java/org/apache/baremaps/ogcapi/PostgisPlugin.java
index fb1e19cc..da1dec94 100644
--- a/baremaps-ogcapi/src/main/java/org/apache/baremaps/ogcapi/PostgisPlugin.java
+++ b/baremaps-ogcapi/src/main/java/org/apache/baremaps/ogcapi/PostgisPlugin.java
@@ -17,7 +17,7 @@ package org.apache.baremaps.ogcapi;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import org.apache.baremaps.openstreetmap.geometry.GeometryUtils;
+import org.apache.baremaps.openstreetmap.utils.GeometryUtils;
 import org.jdbi.v3.core.Jdbi;
 import org.jdbi.v3.core.argument.AbstractArgumentFactory;
 import org.jdbi.v3.core.argument.Argument;