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 de8f56290 Index and search DataTable with Lucene (#902)
de8f56290 is described below

commit de8f5629039a7ea7dc6ec93aed7a43bb26809ebb
Author: Bertil Chapuis <[email protected]>
AuthorDate: Wed Nov 6 09:08:56 2024 +0100

    Index and search DataTable with Lucene (#902)
    
    * Refactor the geocoder package
    
    * Add consumer, mapper and query for datatable abstration
    
    * Add abstractions to create index from data tables
---
 .../org/apache/baremaps/cli/geocoder/Search.java   |   2 +-
 .../DataRowConsumer.java}                          |  27 +--
 .../apache/baremaps/geocoder/DataRowMapper.java    | 241 +++++++++++++++++++++
 .../baremaps/geocoder/DataTableQueryBuilder.java   | 133 ++++++++++++
 .../{ => geonames}/GeonamesDocumentMapper.java     |   2 +-
 .../geocoder/{ => geonames}/GeonamesEntry.java     |   2 +-
 .../{ => geonames}/GeonamesQueryBuilder.java       |   5 +-
 .../geocoder/{ => geonames}/GeonamesReader.java    |   2 +-
 .../OpenStreetMapDocumentMapper.java}              |   6 +-
 .../OpenStreetMapEntityConsumer.java}              |  12 +-
 .../openstreetmap/OpenStreetMapQuery.java}         |   6 +-
 .../openstreetmap}/OsmTags.java                    |   2 +-
 .../org/apache/baremaps/iploc/IpLocMapper.java     |   2 +-
 .../tasks/CreateGeocoderOpenStreetMap.java         |   8 +-
 .../workflow/tasks/CreateGeonamesIndex.java        |   4 +-
 ...namesIndexTest.java => DataTableIndexTest.java} |  66 +++---
 .../geocoder/{ => geonames}/GeonamesIndexTest.java |   2 +-
 .../{ => geonames}/GeonamesReaderTest.java         |   2 +-
 .../openstreetmap/OpenStreetMapIndexTest.java}     |   6 +-
 .../geoparquet/GeoParquetToPostgresTest.java       |   6 +-
 .../apache/baremaps/data/storage/DataRowImpl.java  |   2 +-
 .../baremaps/data/storage/DataSchemaImpl.java      |  33 +--
 .../geoparquet/GeoParquetGroupFactory.java         |   9 +
 .../apache/baremaps/server/GeocoderResource.java   |   2 +-
 24 files changed, 455 insertions(+), 127 deletions(-)

diff --git 
a/baremaps-cli/src/main/java/org/apache/baremaps/cli/geocoder/Search.java 
b/baremaps-cli/src/main/java/org/apache/baremaps/cli/geocoder/Search.java
index e14fbc2a8..49eefe598 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/geocoder/Search.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/geocoder/Search.java
@@ -21,7 +21,7 @@ package org.apache.baremaps.cli.geocoder;
 
 import java.nio.file.Path;
 import java.util.concurrent.Callable;
-import org.apache.baremaps.geocoder.GeonamesQueryBuilder;
+import org.apache.baremaps.geocoder.geonames.GeonamesQueryBuilder;
 import org.apache.lucene.search.*;
 import org.apache.lucene.store.FSDirectory;
 import org.slf4j.Logger;
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
 b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowConsumer.java
similarity index 55%
copy from 
baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
copy to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowConsumer.java
index ea7f6616b..02ff1c10d 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowConsumer.java
@@ -15,36 +15,33 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder;
 
 import java.util.function.Consumer;
-import org.apache.baremaps.openstreetmap.model.Element;
-import org.apache.baremaps.openstreetmap.model.Entity;
+import org.apache.baremaps.data.storage.DataRow;
 import org.apache.lucene.index.IndexWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class GeocoderOsmConsumerEntity implements Consumer<Entity> {
+public class DataRowConsumer implements Consumer<DataRow> {
+
+  private static final Logger logger = 
LoggerFactory.getLogger(DataRowConsumer.class);
+
   private final IndexWriter indexWriter;
-  private final GeocoderOsmDocumentMapper geocoderOsmDocumentMapper =
-      new GeocoderOsmDocumentMapper();
 
-  private static final Logger logger = 
LoggerFactory.getLogger(GeocoderOsmConsumerEntity.class);
+  private final DataRowMapper dataRowMapper = new DataRowMapper();
 
-  public GeocoderOsmConsumerEntity(IndexWriter indexWriter) {
+  public DataRowConsumer(IndexWriter indexWriter) {
     this.indexWriter = indexWriter;
   }
 
   @Override
-  public void accept(Entity entity) {
+  public void accept(DataRow row) {
     try {
-      if (entity instanceof Element element) {
-        var document = geocoderOsmDocumentMapper.apply(element);
-        indexWriter.addDocument(document);
-      }
+      var document = dataRowMapper.apply(row);
+      indexWriter.addDocument(document);
     } catch (Exception e) {
-      // Tolerate the failure of processing an element, partial data failure 
mode
-      logger.warn("The following OSM entity ({}) is not processed due to {}", 
entity, e);
+      logger.warn("The following row ({}) is not processed due to {}", row, e);
     }
   }
 }
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowMapper.java 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowMapper.java
new file mode 100644
index 000000000..c0cf39404
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataRowMapper.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.geocoder;
+
+import java.net.InetAddress;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.baremaps.data.storage.DataColumn;
+import org.apache.baremaps.data.storage.DataRow;
+import org.apache.baremaps.data.storage.DataSchema;
+import org.apache.lucene.document.*;
+import org.locationtech.jts.geom.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DataRowMapper implements Function<DataRow, Document> {
+
+  private static final Logger logger = 
LoggerFactory.getLogger(DataRowMapper.class);
+
+  @Override
+  public Document apply(DataRow dataRow) {
+    Document doc = new Document();
+    DataSchema schema = dataRow.schema();
+    List<DataColumn> columns = schema.columns();
+    for (int i = 0; i < columns.size(); i++) {
+      Object value = dataRow.get(i);
+      if (value == null) {
+        continue;
+      }
+
+      DataColumn column = columns.get(i);
+      applyValue(column, doc, value);
+    }
+    return doc;
+  }
+
+  @SuppressWarnings("squid:S6541")
+  private void applyValue(DataColumn column, Document doc, Object value) {
+    String columnName = column.name();
+    DataColumn.Type type = column.type();
+    try {
+      switch (type) {
+        case BINARY:
+          doc.add(new StoredField(columnName, (byte[]) value));
+          break;
+        case BYTE:
+          doc.add(new IntPoint(columnName, ((Byte) value).intValue()));
+          doc.add(new StoredField(columnName, ((Byte) value).intValue()));
+          break;
+        case BOOLEAN:
+          doc.add(new StringField(columnName, value.toString(), 
Field.Store.YES));
+          break;
+        case SHORT:
+          doc.add(new IntPoint(columnName, ((Short) value).intValue()));
+          doc.add(new StoredField(columnName, ((Short) value).intValue()));
+          break;
+        case INTEGER:
+          doc.add(new IntPoint(columnName, (Integer) value));
+          doc.add(new StoredField(columnName, (Integer) value));
+          break;
+        case LONG:
+          doc.add(new LongPoint(columnName, (Long) value));
+          doc.add(new StoredField(columnName, (Long) value));
+          break;
+        case FLOAT:
+          doc.add(new FloatPoint(columnName, (Float) value));
+          doc.add(new StoredField(columnName, (Float) value));
+          break;
+        case DOUBLE:
+          doc.add(new DoublePoint(columnName, (Double) value));
+          doc.add(new StoredField(columnName, (Double) value));
+          break;
+        case STRING:
+          doc.add(new TextField(columnName, (String) value, Field.Store.YES));
+          break;
+        case COORDINATE:
+          Coordinate coord = (Coordinate) value;
+          double lat = coord.getY();
+          double lon = coord.getX();
+          doc.add(new LatLonPoint(columnName, lat, lon));
+          doc.add(new StoredField(columnName + "_lat", lat));
+          doc.add(new StoredField(columnName + "_lon", lon));
+          break;
+        case POINT:
+          Point point = (Point) value;
+          double pointLat = point.getY();
+          double pointLon = point.getX();
+          doc.add(new LatLonPoint(columnName, pointLat, pointLon));
+          doc.add(new StoredField(columnName + "_lat", pointLat));
+          doc.add(new StoredField(columnName + "_lon", pointLon));
+          break;
+        case LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, 
GEOMETRYCOLLECTION, GEOMETRY:
+          Geometry geometry = (Geometry) value;
+          if (geometry != null) {
+            Field[] shapeFields = createShapeFields(columnName, geometry);
+            for (Field field : shapeFields) {
+              doc.add(field);
+            }
+            doc.add(new StoredField(columnName + "_wkt", geometry.toText()));
+          }
+          break;
+        case ENVELOPE:
+          Envelope envelope = (Envelope) value;
+          String envelopeStr = envelope.toString();
+          doc.add(new StringField(columnName, envelopeStr, Field.Store.YES));
+          break;
+        case INET_ADDRESS, INET4_ADDRESS, INET6_ADDRESS:
+          InetAddress addr = (InetAddress) value;
+          doc.add(new StringField(columnName, addr.getHostAddress(), 
Field.Store.YES));
+          break;
+        case LOCAL_DATE:
+          LocalDate date = (LocalDate) value;
+          doc.add(new StringField(columnName, date.toString(), 
Field.Store.YES));
+          break;
+        case LOCAL_TIME:
+          LocalTime time = (LocalTime) value;
+          doc.add(new StringField(columnName, time.toString(), 
Field.Store.YES));
+          break;
+        case LOCAL_DATE_TIME:
+          LocalDateTime dateTime = (LocalDateTime) value;
+          doc.add(new StringField(columnName, dateTime.toString(), 
Field.Store.YES));
+          break;
+        case NESTED:
+          Map<String, Object> map = (Map<String, Object>) value;
+          for (Map.Entry<String, Object> entry : map.entrySet()) {
+            String nestedKey = columnName + "." + entry.getKey();
+            Object nestedValue = entry.getValue();
+            if (nestedValue != null) {
+              doc.add(new TextField(nestedKey, nestedValue.toString(), 
Field.Store.YES));
+            }
+          }
+          break;
+        default:
+          doc.add(new StringField(columnName, value.toString(), 
Field.Store.YES));
+          break;
+      }
+    } catch (Exception e) {
+      logger.error("Error processing column '{}' with value '{}': {}", 
columnName, value,
+          e.getMessage());
+    }
+  }
+
+  private Field[] createShapeFields(String fieldName, Geometry geometry) {
+    if (geometry instanceof Point point) {
+      double lat = point.getY();
+      double lon = point.getX();
+      return new Field[] {new LatLonPoint(fieldName, lat, lon)};
+    } else if (geometry instanceof LineString lineString) {
+      return LatLonShape.createIndexableFields(fieldName, 
convertToLuceneLine(lineString));
+    } else if (geometry instanceof Polygon polygon) {
+      org.apache.lucene.geo.Polygon lucenePolygon = 
convertToLucenePolygon(polygon);
+      return LatLonShape.createIndexableFields(fieldName, lucenePolygon);
+    } else if (geometry instanceof MultiPolygon multiPolygon) {
+      return createFieldsFromMultiPolygon(fieldName, multiPolygon);
+    } else if (geometry instanceof GeometryCollection collection) {
+      List<Field> fieldList = new ArrayList<>();
+      for (int i = 0; i < collection.getNumGeometries(); i++) {
+        Geometry geom = collection.getGeometryN(i);
+        Field[] fields = createShapeFields(fieldName, geom);
+        fieldList.addAll(Arrays.asList(fields));
+      }
+      return fieldList.toArray(new Field[0]);
+    } else {
+      logger.warn("Unsupported geometry type '{}' for field '{}'", 
geometry.getGeometryType(),
+          fieldName);
+      return new Field[0];
+    }
+  }
+
+  private org.apache.lucene.geo.Line convertToLuceneLine(LineString 
lineString) {
+    Coordinate[] coords = lineString.getCoordinates();
+    double[] lats = new double[coords.length];
+    double[] lons = new double[coords.length];
+    for (int i = 0; i < coords.length; i++) {
+      lats[i] = coords[i].getY();
+      lons[i] = coords[i].getX();
+    }
+    return new org.apache.lucene.geo.Line(lats, lons);
+  }
+
+  private org.apache.lucene.geo.Polygon convertToLucenePolygon(
+      org.locationtech.jts.geom.Polygon jtsPolygon) {
+    LinearRing shell = jtsPolygon.getExteriorRing();
+    Coordinate[] shellCoords = shell.getCoordinates();
+    double[] lats = new double[shellCoords.length];
+    double[] lons = new double[shellCoords.length];
+    for (int i = 0; i < shellCoords.length; i++) {
+      lats[i] = shellCoords[i].getY();
+      lons[i] = shellCoords[i].getX();
+    }
+
+    int numHoles = jtsPolygon.getNumInteriorRing();
+    org.apache.lucene.geo.Polygon[] holes = new 
org.apache.lucene.geo.Polygon[numHoles];
+    for (int i = 0; i < numHoles; i++) {
+      LinearRing hole = jtsPolygon.getInteriorRingN(i);
+      Coordinate[] holeCoords = hole.getCoordinates();
+      double[] holeLats = new double[holeCoords.length];
+      double[] holeLons = new double[holeCoords.length];
+      for (int j = 0; j < holeCoords.length; j++) {
+        holeLats[j] = holeCoords[j].getY();
+        holeLons[j] = holeCoords[j].getX();
+      }
+      holes[i] = new org.apache.lucene.geo.Polygon(holeLats, holeLons);
+    }
+
+    return new org.apache.lucene.geo.Polygon(lats, lons, holes);
+  }
+
+  private Field[] createFieldsFromMultiPolygon(String fieldName, MultiPolygon 
multiPolygon) {
+    List<Field> fieldList = new ArrayList<>();
+    for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
+      org.locationtech.jts.geom.Polygon polygon =
+          (org.locationtech.jts.geom.Polygon) multiPolygon.getGeometryN(i);
+      org.apache.lucene.geo.Polygon lucenePolygon = 
convertToLucenePolygon(polygon);
+      Field[] fields = LatLonShape.createIndexableFields(fieldName, 
lucenePolygon);
+      fieldList.addAll(Arrays.asList(fields));
+    }
+    return fieldList.toArray(new Field[0]);
+  }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataTableQueryBuilder.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataTableQueryBuilder.java
new file mode 100644
index 000000000..947907b3b
--- /dev/null
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/DataTableQueryBuilder.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.geocoder;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.baremaps.data.storage.DataColumn;
+import org.apache.baremaps.data.storage.DataSchema;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.queryparser.classic.QueryParserBase;
+import org.apache.lucene.queryparser.simple.SimpleQueryParser;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+
+/**
+ * A builder for creating queries on a data table.
+ */
+public class DataTableQueryBuilder {
+
+  private final Analyzer analyzer;
+
+  private Map<String, Float> fields = new HashMap<>();
+
+  private String query;
+
+  /**
+   * Constructs a query builder with the default analyzer.
+   */
+  public DataTableQueryBuilder() {
+    this(GeocoderConstants.ANALYZER);
+  }
+
+  /**
+   * Constructs a query builder with the specified analyzer.
+   *
+   * @param analyzer the analyzer
+   */
+  public DataTableQueryBuilder(Analyzer analyzer) {
+    this.analyzer = analyzer;
+  }
+
+  /**
+   * Replace all the fields with the columns of the schema and a boost of 1.0.
+   *
+   * @param schema the schema
+   * @return the query builder
+   */
+  public DataTableQueryBuilder schema(DataSchema schema) {
+    this.fields = new HashMap<>(schema.columns().stream()
+        .collect(Collectors.toMap(DataColumn::name, column -> 1.0f)));
+    return this;
+  }
+
+  /**
+   * Replace all the fields with the specified fields and boosts.
+   *
+   * @param fields the fields and boosts
+   * @return the query builder
+   */
+  public DataTableQueryBuilder columns(Map<DataColumn, Float> fields) {
+    this.fields = new HashMap<>(fields.entrySet().stream()
+        .collect(Collectors.toMap(entry -> entry.getKey().name(), 
Map.Entry::getValue)));
+    return this;
+  }
+
+  /**
+   * Add a column with a specified boost.
+   *
+   * @param column the column
+   * @param boost the boost
+   * @return the query builder
+   */
+  public DataTableQueryBuilder column(DataColumn column, float boost) {
+    return column(column.name(), boost);
+  }
+
+  /**
+   * Add a column with a specified boost.
+   *
+   * @param column the column
+   * @param boost the boost
+   * @return the query builder
+   */
+  public DataTableQueryBuilder column(String column, float boost) {
+    fields.put(column, boost);
+    return this;
+  }
+
+  /**
+   * Set the query text.
+   *
+   * @param query the query text
+   * @return the query builder
+   */
+  public DataTableQueryBuilder query(String query) {
+    this.query = query;
+    return this;
+  }
+
+  /**
+   * Build the query.
+   *
+   * @return the query
+   */
+  public Query build() {
+    var builder = new BooleanQuery.Builder();
+
+    var parser = new SimpleQueryParser(analyzer, fields);
+    var escapedQuery = QueryParserBase.escape(query);
+    var termsQuery = parser.parse(escapedQuery);
+
+    // at least one terms of the queryText must be present
+    builder.add(termsQuery, BooleanClause.Occur.MUST);
+    return builder.build();
+  }
+}
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesDocumentMapper.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesDocumentMapper.java
similarity index 98%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesDocumentMapper.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesDocumentMapper.java
index 7f704aff2..24b262138 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesDocumentMapper.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesDocumentMapper.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 
 
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesEntry.java 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesEntry.java
similarity index 99%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesEntry.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesEntry.java
index 601cc3b05..6393537b0 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesEntry.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesEntry.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 /**
  * A record from the Geonames database.
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesQueryBuilder.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesQueryBuilder.java
similarity index 97%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesQueryBuilder.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesQueryBuilder.java
index 8b4958ac2..d1d702ef1 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesQueryBuilder.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesQueryBuilder.java
@@ -15,13 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 
 
 import com.google.common.base.Strings;
 import java.text.ParseException;
 import java.util.Map;
+import org.apache.baremaps.geocoder.GeocoderConstants;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.expressions.Expression;
 import org.apache.lucene.expressions.SimpleBindings;
@@ -50,8 +51,8 @@ public class GeonamesQueryBuilder {
   private boolean scoringByPopulation;
 
   private boolean andOperator;
-  private String featureCode;
 
+  private String featureCode;
 
   public GeonamesQueryBuilder() {
     this(GeocoderConstants.ANALYZER);
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesReader.java 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesReader.java
similarity index 98%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesReader.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesReader.java
index b35c4c45f..aa1b04271 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoder/GeonamesReader.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/geonames/GeonamesReader.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 
 
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmDocumentMapper.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapDocumentMapper.java
similarity index 94%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmDocumentMapper.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapDocumentMapper.java
index 60e318ecc..cc478c01f 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmDocumentMapper.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapDocumentMapper.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder.openstreetmap;
 
 
 
@@ -35,8 +35,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
-public class GeocoderOsmDocumentMapper implements Function<Element, Document> {
-  private static final Logger logger = 
LoggerFactory.getLogger(GeocoderOsmDocumentMapper.class);
+public class OpenStreetMapDocumentMapper implements Function<Element, 
Document> {
+  private static final Logger logger = 
LoggerFactory.getLogger(OpenStreetMapDocumentMapper.class);
 
   @Override
   public Document apply(Element element) {
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapEntityConsumer.java
similarity index 79%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapEntityConsumer.java
index ea7f6616b..ab29a2954 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmConsumerEntity.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapEntityConsumer.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder.openstreetmap;
 
 import java.util.function.Consumer;
 import org.apache.baremaps.openstreetmap.model.Element;
@@ -24,14 +24,14 @@ import org.apache.lucene.index.IndexWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class GeocoderOsmConsumerEntity implements Consumer<Entity> {
+public class OpenStreetMapEntityConsumer implements Consumer<Entity> {
   private final IndexWriter indexWriter;
-  private final GeocoderOsmDocumentMapper geocoderOsmDocumentMapper =
-      new GeocoderOsmDocumentMapper();
+  private final OpenStreetMapDocumentMapper geocoderOsmDocumentMapper =
+      new OpenStreetMapDocumentMapper();
 
-  private static final Logger logger = 
LoggerFactory.getLogger(GeocoderOsmConsumerEntity.class);
+  private static final Logger logger = 
LoggerFactory.getLogger(OpenStreetMapEntityConsumer.class);
 
-  public GeocoderOsmConsumerEntity(IndexWriter indexWriter) {
+  public OpenStreetMapEntityConsumer(IndexWriter indexWriter) {
     this.indexWriter = indexWriter;
   }
 
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmQuery.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapQuery.java
similarity index 92%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmQuery.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapQuery.java
index bd326369c..435917539 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/GeocoderOsmQuery.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapQuery.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder.openstreetmap;
 
 import org.apache.baremaps.geocoder.GeocoderConstants;
 import org.apache.lucene.queryparser.classic.QueryParserBase;
@@ -24,11 +24,11 @@ import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
 
-public class GeocoderOsmQuery {
+public class OpenStreetMapQuery {
 
   private final String query;
 
-  public GeocoderOsmQuery(String query) {
+  public OpenStreetMapQuery(String query) {
     this.query = query;
   }
 
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/OsmTags.java 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OsmTags.java
similarity index 95%
rename from 
baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/OsmTags.java
rename to 
baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OsmTags.java
index 3be62f7b4..7385b4a8d 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/geocoderosm/OsmTags.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/geocoder/openstreetmap/OsmTags.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder.openstreetmap;
 
 public enum OsmTags {
   NAME("name"),
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/iploc/IpLocMapper.java 
b/baremaps-core/src/main/java/org/apache/baremaps/iploc/IpLocMapper.java
index 5602f09cd..23ee3e5a4 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/iploc/IpLocMapper.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/iploc/IpLocMapper.java
@@ -27,7 +27,7 @@ import java.util.Optional;
 import java.util.function.Function;
 import java.util.regex.Pattern;
 import net.ripe.ipresource.IpResourceRange;
-import org.apache.baremaps.geocoder.GeonamesQueryBuilder;
+import org.apache.baremaps.geocoder.geonames.GeonamesQueryBuilder;
 import org.apache.baremaps.utils.IsoCountriesUtils;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.SearcherManager;
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeocoderOpenStreetMap.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeocoderOpenStreetMap.java
index 1b258922b..62f1a1b7f 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeocoderOpenStreetMap.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeocoderOpenStreetMap.java
@@ -24,7 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.StringJoiner;
 import org.apache.baremaps.geocoder.GeocoderConstants;
-import org.apache.baremaps.geocoderosm.GeocoderOsmConsumerEntity;
+import org.apache.baremaps.geocoder.openstreetmap.OpenStreetMapEntityConsumer;
 import org.apache.baremaps.openstreetmap.pbf.PbfEntityReader;
 import org.apache.baremaps.openstreetmap.stream.StreamUtils;
 import org.apache.baremaps.workflow.Task;
@@ -38,8 +38,6 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Experimental feature.
- *
- * @see org.apache.baremaps.geocoderosm
  */
 public class CreateGeocoderOpenStreetMap implements Task {
 
@@ -75,7 +73,7 @@ public class CreateGeocoderOpenStreetMap implements Task {
     var config = new IndexWriterConfig(GeocoderConstants.ANALYZER);
 
     try (var indexWriter = new IndexWriter(directory, config)) {
-      var importer = new GeocoderOsmConsumerEntity(indexWriter);
+      var importer = new OpenStreetMapEntityConsumer(indexWriter);
       execute(
           path,
           coordinateMap,
@@ -88,7 +86,7 @@ public class CreateGeocoderOpenStreetMap implements Task {
       Path path,
       Map<Long, Coordinate> coordinateMap,
       Map<Long, List<Long>> referenceMap,
-      GeocoderOsmConsumerEntity importer) throws IOException {
+      OpenStreetMapEntityConsumer importer) throws IOException {
 
     // configure the block reader
     var reader = new PbfEntityReader()
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeonamesIndex.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeonamesIndex.java
index 07655c37e..f15160483 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeonamesIndex.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/workflow/tasks/CreateGeonamesIndex.java
@@ -22,8 +22,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.StringJoiner;
 import org.apache.baremaps.geocoder.GeocoderConstants;
-import org.apache.baremaps.geocoder.GeonamesDocumentMapper;
-import org.apache.baremaps.geocoder.GeonamesReader;
+import org.apache.baremaps.geocoder.geonames.GeonamesDocumentMapper;
+import org.apache.baremaps.geocoder.geonames.GeonamesReader;
 import org.apache.baremaps.workflow.Task;
 import org.apache.baremaps.workflow.WorkflowContext;
 import org.apache.lucene.document.Document;
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/DataTableIndexTest.java
similarity index 57%
copy from 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
copy to 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/DataTableIndexTest.java
index b9d392ff2..7725c5629 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/DataTableIndexTest.java
@@ -24,10 +24,12 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import org.apache.baremaps.storage.geoparquet.GeoParquetDataTable;
 import org.apache.baremaps.testing.TestFiles;
 import org.apache.baremaps.utils.FileUtils;
-import org.apache.baremaps.workflow.WorkflowContext;
-import org.apache.baremaps.workflow.tasks.CreateGeonamesIndex;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.SearcherFactory;
 import org.apache.lucene.search.SearcherManager;
@@ -37,7 +39,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 
-public class GeonamesIndexTest {
+public class DataTableIndexTest {
 
   private static Path directory;
   private static IndexSearcher searcher;
@@ -48,10 +50,17 @@ public class GeonamesIndexTest {
     directory = Files.createTempDirectory(Paths.get("."), "geocoder_");
 
     // Create the geonames index
-    var data = TestFiles.resolve("baremaps-testing/data/geonames/sample.txt");
-    var task = new CreateGeonamesIndex(data, directory);
-    task.execute(new WorkflowContext());
     var dir = FSDirectory.open(directory);
+    var data = 
TestFiles.resolve("baremaps-testing/data/samples/example.parquet");
+    var config = new IndexWriterConfig(GeocoderConstants.ANALYZER);
+    try (var indexWriter = new IndexWriter(dir, config)) {
+      indexWriter.deleteAll();
+      var documents = new GeoParquetDataTable(data.toUri())
+          .stream()
+          .map(new DataRowMapper());
+      indexWriter.addDocuments((Iterable<Document>) documents::iterator);
+    }
+
     var searcherManager = new SearcherManager(dir, new SearcherFactory());
     searcher = searcherManager.acquire();
   }
@@ -62,48 +71,23 @@ public class GeonamesIndexTest {
   }
 
   @Test
-  void testCreateIndex() throws Exception {
-    var geonamesQuery =
-        new 
GeonamesQueryBuilder().queryText("yverdon").countryCode("CH").build();
-    var topDocs = searcher.search(geonamesQuery, 1);
-    var doc = 
searcher.doc(Arrays.stream(topDocs.scoreDocs).findFirst().get().doc);
-    assertEquals("Yverdon-les-bains", doc.getField("name").stringValue());
-  }
-
-  @Test
-  void testOrQuery() throws Exception {
-    var geonamesQuery = new GeonamesQueryBuilder()
-        .queryText("bains cheseaux")
-        .countryCode("CH")
-        .build();
-    var topDocs = searcher.search(geonamesQuery, 2);
-    assertEquals(2, topDocs.totalHits.value);
-    var doc0 = searcher.doc(topDocs.scoreDocs[0].doc);
-    assertEquals("Yverdon-les-bains", doc0.getField("name").stringValue());
-    var doc1 = searcher.doc(topDocs.scoreDocs[1].doc);
-    assertEquals("Route de Cheseaux 1", doc1.getField("name").stringValue());
-  }
-
-  @Test
-  void testAndQueryNoHits() throws Exception {
-    var geonamesQuery = new GeonamesQueryBuilder()
-        .queryText("bains cheseaux")
-        .andOperator()
-        .countryCode("CH")
+  void testQueryNoHits() throws Exception {
+    var geonamesQuery = new DataTableQueryBuilder()
+        .column("continent", 1.0f)
+        .query("test")
         .build();
     var topDocs = searcher.search(geonamesQuery, 1);
     assertEquals(0, topDocs.totalHits.value);
   }
 
   @Test
-  void testAndQuery() throws Exception {
-    var geonamesQuery =
-        new GeonamesQueryBuilder().queryText("yverdon bains")
-            .andOperator()
-            .countryCode("CH")
-            .build();
+  void testQuery() throws Exception {
+    var geonamesQuery = new DataTableQueryBuilder()
+        .column("continent", 1.0f)
+        .query("oceania")
+        .build();
     var topDocs = searcher.search(geonamesQuery, 1);
     var doc = 
searcher.doc(Arrays.stream(topDocs.scoreDocs).findFirst().get().doc);
-    assertEquals("Yverdon-les-bains", doc.getField("name").stringValue());
+    assertEquals("Oceania", doc.getField("continent").stringValue());
   }
 }
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesIndexTest.java
similarity index 98%
rename from 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
rename to 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesIndexTest.java
index b9d392ff2..4bced3332 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesIndexTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesIndexTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesReaderTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesReaderTest.java
similarity index 96%
rename from 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesReaderTest.java
rename to 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesReaderTest.java
index efc90e6d9..c02bf59e1 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoder/GeonamesReaderTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/geonames/GeonamesReaderTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoder;
+package org.apache.baremaps.geocoder.geonames;
 
 import static org.junit.jupiter.api.Assertions.*;
 
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoderosm/OSMIndexTest.java 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapIndexTest.java
similarity index 97%
rename from 
baremaps-core/src/test/java/org/apache/baremaps/geocoderosm/OSMIndexTest.java
rename to 
baremaps-core/src/test/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapIndexTest.java
index f267f6007..605685be3 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/geocoderosm/OSMIndexTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/geocoder/openstreetmap/OpenStreetMapIndexTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.baremaps.geocoderosm;
+package org.apache.baremaps.geocoder.openstreetmap;
 
 import static org.apache.baremaps.testing.TestFiles.SAMPLE_OSM_PBF;
 import static org.junit.jupiter.api.Assertions.*;
@@ -49,7 +49,7 @@ import org.junit.jupiter.api.Test;
 
 
 @Disabled("prototype implementation")
-public class OSMIndexTest {
+public class OpenStreetMapIndexTest {
 
   private static Path directory;
   private static IndexSearcher searcher;
@@ -76,7 +76,7 @@ public class OSMIndexTest {
   @Test
   void testCreateIndex() throws Exception {
     var query =
-        new GeocoderOsmQuery("vaduz").build();
+        new OpenStreetMapQuery("vaduz").build();
     var topDocs = searcher.search(query, 1);
     var doc = 
searcher.doc(Arrays.stream(topDocs.scoreDocs).findFirst().get().doc);
     assertEquals("Vaduz", doc.getField("name").stringValue());
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
index f96001dad..afa020743 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/storage/geoparquet/GeoParquetToPostgresTest.java
@@ -43,12 +43,8 @@ class GeoParquetToPostgresTest extends PostgresContainerTest 
{
     // Check the table in Postgres
     var postgresTable = postgresStore.get("geoparquet");
 
-    for (var row : postgresTable) {
-      System.out.println(row);
-    }
-
     assertEquals("geoparquet", postgresTable.schema().name());
-    assertEquals(4, postgresTable.schema().columns().size());
+    assertEquals(7, postgresTable.schema().columns().size());
     assertEquals(5L, postgresTable.size());
     assertEquals(5L, postgresTable.stream().count());
   }
diff --git 
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataRowImpl.java 
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataRowImpl.java
index 72c7c6dea..eca44b246 100644
--- 
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataRowImpl.java
+++ 
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataRowImpl.java
@@ -17,6 +17,7 @@
 
 package org.apache.baremaps.data.storage;
 
+
 import java.util.List;
 
 /**
@@ -67,5 +68,4 @@ public record DataRowImpl(DataSchema schema, List<Object> 
values) implements Dat
   public void set(int index, Object value) {
     values.set(index, value);
   }
-
 }
diff --git 
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataSchemaImpl.java
 
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataSchemaImpl.java
index c9b20c7a6..e0f79b8e8 100644
--- 
a/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataSchemaImpl.java
+++ 
b/baremaps-data/src/main/java/org/apache/baremaps/data/storage/DataSchemaImpl.java
@@ -23,38 +23,7 @@ import java.util.List;
 /**
  * A {@link DataSchema} defines the structure of a table.
  */
-public class DataSchemaImpl implements DataSchema {
-
-  private final String name;
-
-  private final List<DataColumn> columns;
-
-  /**
-   * Constructs a schema with the specified name and columns.
-   *
-   * @param name the name of the schema
-   * @param columns the columns of the schema
-   */
-  public DataSchemaImpl(String name, List<DataColumn> columns) {
-    this.name = name;
-    this.columns = columns;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public String name() {
-    return name;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public List<DataColumn> columns() {
-    return columns;
-  }
+public record DataSchemaImpl(String name, List<DataColumn> columns) implements 
DataSchema {
 
   /**
    * {@inheritDoc}
diff --git 
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/GeoParquetGroupFactory.java
 
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/GeoParquetGroupFactory.java
index a88f3b13e..29d254d14 100644
--- 
a/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/GeoParquetGroupFactory.java
+++ 
b/baremaps-geoparquet/src/main/java/org/apache/baremaps/geoparquet/GeoParquetGroupFactory.java
@@ -20,6 +20,7 @@ package org.apache.baremaps.geoparquet;
 import java.util.List;
 import org.apache.baremaps.geoparquet.GeoParquetSchema.*;
 import org.apache.parquet.schema.GroupType;
+import org.apache.parquet.schema.LogicalTypeAnnotation;
 import org.apache.parquet.schema.PrimitiveType;
 import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
 
@@ -89,6 +90,12 @@ class GeoParquetGroupFactory {
             geoParquetSchema);
       }
 
+      // Handle logical types
+      else if (field.getLogicalTypeAnnotation() != null
+          && 
field.getLogicalTypeAnnotation().equals(LogicalTypeAnnotation.stringType())) {
+        return new StringField(field.getName(), cardinality);
+      }
+
       // Handle primitive columns
       else {
         PrimitiveType primitiveType = field.asPrimitiveType();
@@ -110,6 +117,8 @@ class GeoParquetGroupFactory {
     return new GeoParquetSchema(schema.getName(), fields);
   }
 
+
+
   /**
    * Creates a new {@link GeoParquetGroup}.
    *
diff --git 
a/baremaps-server/src/main/java/org/apache/baremaps/server/GeocoderResource.java
 
b/baremaps-server/src/main/java/org/apache/baremaps/server/GeocoderResource.java
index 5a6d595da..071e6521e 100644
--- 
a/baremaps-server/src/main/java/org/apache/baremaps/server/GeocoderResource.java
+++ 
b/baremaps-server/src/main/java/org/apache/baremaps/server/GeocoderResource.java
@@ -33,7 +33,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import org.apache.baremaps.geocoder.GeonamesQueryBuilder;
+import org.apache.baremaps.geocoder.geonames.GeonamesQueryBuilder;
 import org.apache.baremaps.openstreetmap.stream.StreamException;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.ScoreDoc;


Reply via email to