diff --git a/src/main/java/de/komoot/photon/PhotonDoc.java b/src/main/java/de/komoot/photon/PhotonDoc.java index 6d065d3ae..908709ff5 100644 --- a/src/main/java/de/komoot/photon/PhotonDoc.java +++ b/src/main/java/de/komoot/photon/PhotonDoc.java @@ -187,7 +187,7 @@ public PhotonDoc addressType(AddressType type) { return this; } - public PhotonDoc postcode(String postcode) { + public PhotonDoc postcode(@Nullable String postcode) { this.postcode = postcode; return this; } diff --git a/src/main/java/de/komoot/photon/nominatim/NominatimImporter.java b/src/main/java/de/komoot/photon/nominatim/NominatimImporter.java index 4147a7747..85a7b486e 100644 --- a/src/main/java/de/komoot/photon/nominatim/NominatimImporter.java +++ b/src/main/java/de/komoot/photon/nominatim/NominatimImporter.java @@ -73,6 +73,7 @@ public void readCountry(String countryCode, ImportThread importThread) { doc.addAddresses(addressCache.getAddressList(rs.getString("addresslines"))); doc.addAddresses(address, dbProperties.getLanguages()); // take precedence over computed address + doc.postcode(rs.getString("postcode")); // ignore all other sources of postcode doc.setCountry(cnames); importThread.addDocument(new PhotonDocAddressSet(doc, address)); @@ -101,6 +102,7 @@ public void readCountry(String countryCode, ImportThread importThread) { } doc.addAddresses(addressCache.getAddressList(rs.getString("addresslines"))); doc.addAddresses(address, dbProperties.getLanguages()); // take precedence over computed address + doc.postcode(rs.getString("postcode")); // ignore all other sources of postcode doc.setCountry(cnames); importThread.addDocument(new PhotonDocAddressSet(doc, address)); @@ -123,6 +125,7 @@ public void readCountry(String countryCode, ImportThread importThread) { } doc.addAddresses(addressCache.getAddressList(rs.getString("addresslines"))); doc.addAddresses(dbutils.getMap(rs, "address"), dbProperties.getLanguages()); + doc.postcode(rs.getString("postcode")); // ignore all other sources of postcode doc.setCountry(cnames); diff --git a/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java b/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java index 6af189c48..82f2b7779 100644 --- a/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java +++ b/src/main/java/de/komoot/photon/nominatim/NominatimUpdater.java @@ -119,6 +119,7 @@ public NominatimUpdater(PostgresqlConfig config, DBDataAdapter dataAdapter, Data // Add address last, so it takes precedence. final var address = dbutils.getMap(rs, "address"); doc.addAddresses(address, dbProperties.getLanguages()); + doc.postcode(rs.getString("postcode")); // ignore all other sources of postcode assert countryNames != null; doc.setCountry(countryNames.get(rs.getString("country_code"))); @@ -143,6 +144,7 @@ public NominatimUpdater(PostgresqlConfig config, DBDataAdapter dataAdapter, Data doc.addAddresses( addressCache.getOrLoadAddressList(template, rs.getString("addresslines"))); doc.addAddresses(dbutils.getMap(rs, "address"), dbProperties.getLanguages()); + doc.postcode(rs.getString("postcode")); // ignore all other sources of postcode assert countryNames != null; doc.setCountry(countryNames.get(rs.getString("country_code"))); diff --git a/src/main/java/de/komoot/photon/nominatim/model/OsmlineRowMapper.java b/src/main/java/de/komoot/photon/nominatim/model/OsmlineRowMapper.java index bcbd25521..5bddf12d3 100644 --- a/src/main/java/de/komoot/photon/nominatim/model/OsmlineRowMapper.java +++ b/src/main/java/de/komoot/photon/nominatim/model/OsmlineRowMapper.java @@ -19,8 +19,7 @@ public PhotonDoc mapRow(ResultSet rs, int rowNum) throws SQLException { "place", "house_number") .countryCode(rs.getString("country_code")) .categories(List.of("osm.place.house_number")) - .addressType(AddressType.HOUSE) - .postcode(rs.getString("postcode")); + .addressType(AddressType.HOUSE); } public String makeBaseQuery(DBDataAdapter dbutils) { diff --git a/src/main/java/de/komoot/photon/nominatim/model/PlaceRowMapper.java b/src/main/java/de/komoot/photon/nominatim/model/PlaceRowMapper.java index 098b0aa7a..cde92aefe 100644 --- a/src/main/java/de/komoot/photon/nominatim/model/PlaceRowMapper.java +++ b/src/main/java/de/komoot/photon/nominatim/model/PlaceRowMapper.java @@ -69,8 +69,7 @@ public PhotonDoc mapRow(ResultSet rs, int rowNum) throws SQLException { .bbox(dbutils.extractGeometry(rs, "bbox")) .countryCode(rs.getString("country_code")) .centroid(Objects.requireNonNull(dbutils.extractGeometry(rs, "centroid"))) - .addressType(addressType) - .postcode(rs.getString("postcode")); + .addressType(addressType); if (useGeometryColumn) { try { diff --git a/src/main/java/de/komoot/photon/nominatim/model/PostcodeOldStyleRowMapper.java b/src/main/java/de/komoot/photon/nominatim/model/PostcodeOldStyleRowMapper.java index b0473d076..325d4001f 100644 --- a/src/main/java/de/komoot/photon/nominatim/model/PostcodeOldStyleRowMapper.java +++ b/src/main/java/de/komoot/photon/nominatim/model/PostcodeOldStyleRowMapper.java @@ -7,7 +7,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; -import java.util.Map; /** * Access to postcode tables prior to Nominatim 5.3. @@ -30,7 +29,7 @@ public PhotonDoc rowToDoc(ResultSet rs) throws SQLException { Long.toString(rs.getLong("place_id")), null, -1, "place", "postcode") - .names(NameMap.makeForPlace(Map.of("name", rs.getString("postcode")), List.of())) + .names(PostcodeUtils.postcodeToName(rs.getString("postcode"))) .centroid(centroid) .countryCode(rs.getString("country_code")) .categories(List.of("osm.place.postcode")) diff --git a/src/main/java/de/komoot/photon/nominatim/model/PostcodeRowMapper.java b/src/main/java/de/komoot/photon/nominatim/model/PostcodeRowMapper.java index abe9c9be4..f264b2ce9 100644 --- a/src/main/java/de/komoot/photon/nominatim/model/PostcodeRowMapper.java +++ b/src/main/java/de/komoot/photon/nominatim/model/PostcodeRowMapper.java @@ -3,18 +3,16 @@ import de.komoot.photon.PhotonDoc; import de.komoot.photon.nominatim.DBDataAdapter; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; -import java.util.Map; import java.util.Objects; /** * Access to postcode tables for Nominatim 5.3+. *
- * This table is now the only source for postcodes nad may contain full + * This table is now the only source for postcodes and may contain full * geometries for postcode areas. */ @NullMarked @@ -35,7 +33,7 @@ public PhotonDoc rowToDoc(ResultSet rs) throws SQLException { osmId == null ? null : "R", osmId == null ? -1 : (Long) osmId, "place", "postcode") - .names(NameMap.makeForPlace(Map.of("name", rs.getString("postcode")), List.of())) + .names(PostcodeUtils.postcodeToName(rs.getString("postcode"))) .centroid(Objects.requireNonNull(dbutils.extractGeometry(rs, "centroid"))) .countryCode(rs.getString("country_code")) .categories(List.of("osm.place.postcode")) diff --git a/src/main/java/de/komoot/photon/nominatim/model/PostcodeUtils.java b/src/main/java/de/komoot/photon/nominatim/model/PostcodeUtils.java new file mode 100644 index 000000000..2859042d0 --- /dev/null +++ b/src/main/java/de/komoot/photon/nominatim/model/PostcodeUtils.java @@ -0,0 +1,31 @@ +package de.komoot.photon.nominatim.model; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +@NullMarked +public class PostcodeUtils { + private PostcodeUtils() {} + + public static @Nullable String postcodeAltName(String postcode) { + String shortPostcode = postcode.replaceAll("[ -]", ""); + + return shortPostcode.length() == postcode.length() ? null : shortPostcode; + } + + public static NameMap postcodeToName(String postcode) { + String shortPostcode = postcodeAltName(postcode); + if (shortPostcode != null) { + return NameMap.makeForPlace(Map.of( + "name", postcode, + "alt_name", shortPostcode + ), List.of()); + } + + return NameMap.makeForPlace(Map.of("name", postcode), List.of()); + } + +} diff --git a/src/main/java/de/komoot/photon/opensearch/PhotonDocSerializer.java b/src/main/java/de/komoot/photon/opensearch/PhotonDocSerializer.java index 01cb69908..bdab6dbb7 100644 --- a/src/main/java/de/komoot/photon/opensearch/PhotonDocSerializer.java +++ b/src/main/java/de/komoot/photon/opensearch/PhotonDocSerializer.java @@ -6,6 +6,7 @@ import de.komoot.photon.DatabaseProperties; import de.komoot.photon.PhotonDoc; import de.komoot.photon.nominatim.model.AddressType; +import de.komoot.photon.nominatim.model.PostcodeUtils; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.locationtech.jts.geom.Envelope; @@ -61,6 +62,10 @@ public void serialize(PhotonDoc value, JsonGenerator gen, SerializerProvider pro if (value.getPostcode() != null) { gen.writeStringField(DocFields.POSTCODE, value.getPostcode()); termCollector.add(value.getPostcode(), 2); + var altPostcode = PostcodeUtils.postcodeAltName(value.getPostcode()); + if (altPostcode != null) { + termCollector.add(altPostcode, 2); + } } if (isNamed) { diff --git a/src/main/java/de/komoot/photon/searcher/QueryReranker.java b/src/main/java/de/komoot/photon/searcher/QueryReranker.java index 93ac049ce..c4be64596 100644 --- a/src/main/java/de/komoot/photon/searcher/QueryReranker.java +++ b/src/main/java/de/komoot/photon/searcher/QueryReranker.java @@ -1,5 +1,6 @@ package de.komoot.photon.searcher; +import de.komoot.photon.nominatim.model.PostcodeUtils; import de.komoot.photon.opensearch.DocFields; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -103,7 +104,11 @@ private double rescore(PhotonResult result) { mapNames(result.getLocalised(DocFields.STATE, language), resultTerms); mapNames(result.getLocalised(DocFields.COUNTY, language), resultTerms); mapNames(result.getLocalised(DocFields.DISTRICT, language), resultTerms); - mapNames((String) result.get(DocFields.POSTCODE), resultTerms); + var postcode = (String) result.get(DocFields.POSTCODE); + if (postcode != null) { + mapNames(postcode, resultTerms); + mapNames(PostcodeUtils.postcodeAltName(postcode), resultTerms); + } mapNames(result.getLocalised(DocFields.CITY, "default"), resultTerms); mapNames(result.getLocalised(DocFields.STATE, "default"), resultTerms); mapNames(result.getLocalised(DocFields.COUNTY, "default"), resultTerms); diff --git a/src/test/java/de/komoot/photon/json/JsonDumperTest.java b/src/test/java/de/komoot/photon/json/JsonDumperTest.java index 4ac6c55fa..4963b175c 100644 --- a/src/test/java/de/komoot/photon/json/JsonDumperTest.java +++ b/src/test/java/de/komoot/photon/json/JsonDumperTest.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.transaction.support.TransactionTemplate; diff --git a/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java b/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java index 6d0f82682..55785ae93 100644 --- a/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java +++ b/src/test/java/de/komoot/photon/nominatim/NominatimConnectorDBTest.java @@ -478,12 +478,12 @@ void testUsePostcodeFromPlacex() { } @Test - void testPreferPostcodeFromAddress() { + void testPreferPostcodeFromPostcodeField() { PlacexTestRow parent = PlacexTestRow.make_street("Main St").add(jdbc); PlacexTestRow place = new PlacexTestRow("building", "yes") .addr("housenumber", "34") - .addr("postcode", "45-234") - .postcode("XXX") + .addr("postcode", "XXX") + .postcode("45-234") .parent(parent).add(jdbc); PlacexTestRow postcode = new PlacexTestRow("boundary", "postal_code") .name("ref", "1234XZ").ranks(11).add(jdbc); @@ -537,6 +537,33 @@ void testPostcodeArea() { } + @Test + void testPostcodeWithSpaces() { + var parent = new PlacexTestRow("place", "city").name("Rio") + .ranks(16) + .country("nl") + .add(jdbc); + var postcode = new PostcodeLocationTestRow("33XV 46", "nl") + .geometry("POLYGON((-10.999 33.999, -11.001 34.001, -11.001 33.999, -10.999 33.999))") + .centroid(-11, 34) + .relation(55) + .parent(parent.getPlaceId()) + .add(jdbc); + + readEntireDatabase(); + + importer.assertThatByPlaceId(postcode.getPlaceString()) + .hasFieldOrPropertyWithValue("osmId", 55L) + .hasFieldOrPropertyWithValue("tagKey", "place") + .hasFieldOrPropertyWithValue("tagValue", "postcode") + .hasFieldOrPropertyWithValue("postcode", null) + .hasFieldOrPropertyWithValue("name", Map.of( + "default", "33XV 46", + "alt", "33XV46")) + ; + + } + @Test void testBoundariesHaveAdminLevelInExtraTags() { var place = new PlacexTestRow("boundary", "administrative").name("Rio")