From 860db016de33740b22347f468f2fe1a3f00ea54e Mon Sep 17 00:00:00 2001 From: Daniel Graf Date: Tue, 31 Mar 2026 10:34:52 +0200 Subject: [PATCH 1/2] refactor: extract GeoJSON conversion logic to `GeometryHelper` and remove duplication - Centralized `convertJtsToGeoJson` and related methods into the new `GeometryHelper` utility class. - Replaced in-line GeoJSON conversion in services with calls to `GeometryHelper`. - Enhanced `GeocodingController` to include metadata and data source information in responses. - Removed unused exception handling for missing POIs. --- .../controller/GeocodingController.java | 5 +- .../paikka/service/BoundaryService.java | 66 +------------ .../paikka/service/GeometryHelper.java | 94 +++++++++++++++++++ .../service/ReverseGeocodingService.java | 88 +---------------- 4 files changed, 103 insertions(+), 150 deletions(-) create mode 100644 src/main/java/com/dedicatedcode/paikka/service/GeometryHelper.java diff --git a/src/main/java/com/dedicatedcode/paikka/controller/GeocodingController.java b/src/main/java/com/dedicatedcode/paikka/controller/GeocodingController.java index d28583e..9891a3e 100644 --- a/src/main/java/com/dedicatedcode/paikka/controller/GeocodingController.java +++ b/src/main/java/com/dedicatedcode/paikka/controller/GeocodingController.java @@ -105,7 +105,10 @@ public ResponseEntity> reverse( "lang", lang, "limit", effectiveLimit )); - + Map metadata = metadataService.getMetadata(); + response.put("metadata", metadata); + response.put("dataSources", Map.of("OpenStreetMap", "https://openstreetmap.org/copyright")); + return ResponseEntity.ok() .header("X-Result-Count", String.valueOf(results.size())) .header("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0") // No caching diff --git a/src/main/java/com/dedicatedcode/paikka/service/BoundaryService.java b/src/main/java/com/dedicatedcode/paikka/service/BoundaryService.java index a7fb6a6..94c2746 100644 --- a/src/main/java/com/dedicatedcode/paikka/service/BoundaryService.java +++ b/src/main/java/com/dedicatedcode/paikka/service/BoundaryService.java @@ -147,7 +147,7 @@ public GeoJsonGeometry getBoundaryGeometry(long osmId) { WKBReader wkbReader = new WKBReader(); org.locationtech.jts.geom.Geometry jtsGeometry = wkbReader.read(geometryData); - return convertJtsToGeoJson(jtsGeometry); + return GeometryHelper.convertJtsToGeoJson(jtsGeometry); } catch (RocksDBException e) { logger.error("RocksDB error retrieving boundary for OSM ID {}: {}", osmId, e.getMessage()); @@ -175,68 +175,4 @@ private byte[] longToByteArray(long value) { (byte) value }; } - - /** - * Convert JTS Geometry to GeoJSON format. - * This is a simplified conversion that handles basic geometry types. - */ - private GeoJsonGeometry convertJtsToGeoJson(org.locationtech.jts.geom.Geometry geometry) { - String geometryType = geometry.getGeometryType(); - Object coordinates = null; - - switch (geometryType) { - case "Point": - coordinates = new double[]{geometry.getCoordinate().x, geometry.getCoordinate().y}; - break; - case "Polygon": - coordinates = extractPolygonCoordinates(geometry); - break; - case "MultiPolygon": - coordinates = extractMultiPolygonCoordinates(geometry); - break; - default: - logger.debug("Unsupported geometry type for GeoJSON conversion: {}", geometryType); - return new GeoJsonGeometry("Unknown", null); - } - - return new GeoJsonGeometry(geometryType, coordinates); - } - - private Object extractPolygonCoordinates(org.locationtech.jts.geom.Geometry polygon) { - try { - // Get exterior ring coordinates - org.locationtech.jts.geom.Coordinate[] coords = polygon.getCoordinates(); - double[][] ring = new double[coords.length][2]; - - for (int i = 0; i < coords.length; i++) { - ring[i][0] = coords[i].x; // longitude - ring[i][1] = coords[i].y; // latitude - } - - // GeoJSON polygon format: [[[x,y],[x,y],...]] - return new double[][][]{ring}; - } catch (Exception e) { - logger.warn("Failed to extract polygon coordinates: {}", e.getMessage()); - return null; - } - } - - private Object extractMultiPolygonCoordinates(org.locationtech.jts.geom.Geometry multiPolygon) { - try { - List polygons = new ArrayList<>(); - - for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { - org.locationtech.jts.geom.Geometry polygon = multiPolygon.getGeometryN(i); - Object polyCoords = extractPolygonCoordinates(polygon); - if (polyCoords instanceof double[][][]) { - polygons.add((double[][][]) polyCoords); - } - } - - return polygons.toArray(new double[0][][][]); - } catch (Exception e) { - logger.warn("Failed to extract multipolygon coordinates: {}", e.getMessage()); - return null; - } - } } diff --git a/src/main/java/com/dedicatedcode/paikka/service/GeometryHelper.java b/src/main/java/com/dedicatedcode/paikka/service/GeometryHelper.java new file mode 100644 index 0000000..beabd22 --- /dev/null +++ b/src/main/java/com/dedicatedcode/paikka/service/GeometryHelper.java @@ -0,0 +1,94 @@ +/* + * This file is part of paikka. + * + * Paikka is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 or + * any later version. + * + * Paikka is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with Paikka. If not, see . + */ + +package com.dedicatedcode.paikka.service; + +import com.dedicatedcode.paikka.dto.GeoJsonGeometry; +import org.locationtech.jts.geom.Geometry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class GeometryHelper { + private static final Logger logger = LoggerFactory.getLogger(GeometryHelper.class); + + public static GeoJsonGeometry convertJtsToGeoJson(Geometry geometry) { + String geometryType = geometry.getGeometryType(); + Object coordinates = null; + + switch (geometryType) { + case "Point": + coordinates = new double[]{geometry.getCoordinate().x, geometry.getCoordinate().y}; + break; + case "Polygon": + // For polygons, we need to extract the exterior ring coordinates + // This is a simplified implementation + coordinates = extractPolygonCoordinates(geometry); + break; + case "MultiPolygon": + // For multipolygons, extract all polygon coordinates + coordinates = extractMultiPolygonCoordinates(geometry); + break; + default: + // For other geometry types, just indicate the type + logger.debug("Unsupported geometry type for GeoJSON conversion: {}", geometryType); + return new GeoJsonGeometry("Unknown", null); + } + + return new GeoJsonGeometry(geometryType, coordinates); + } + + + private static Object extractPolygonCoordinates(Geometry polygon) { + try { + // Get exterior ring coordinates + org.locationtech.jts.geom.Coordinate[] coords = polygon.getCoordinates(); + double[][] ring = new double[coords.length][2]; + + for (int i = 0; i < coords.length; i++) { + ring[i][0] = coords[i].x; // longitude + ring[i][1] = coords[i].y; // latitude + } + + // GeoJSON polygon format: [[[x,y],[x,y],...]] + return new double[][][]{ring}; + } catch (Exception e) { + logger.warn("Failed to extract polygon coordinates: {}", e.getMessage()); + return null; + } + } + + private static Object extractMultiPolygonCoordinates(Geometry multiPolygon) { + try { + List polygons = new ArrayList<>(); + + for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { + Geometry polygon = multiPolygon.getGeometryN(i); + Object polyCoords = extractPolygonCoordinates(polygon); + if (polyCoords instanceof double[][][]) { + polygons.add((double[][][]) polyCoords); + } + } + + return polygons.toArray(new double[0][][][]); + } catch (Exception e) { + logger.warn("Failed to extract multipolygon coordinates: {}", e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/com/dedicatedcode/paikka/service/ReverseGeocodingService.java b/src/main/java/com/dedicatedcode/paikka/service/ReverseGeocodingService.java index 955c90b..0d44f50 100644 --- a/src/main/java/com/dedicatedcode/paikka/service/ReverseGeocodingService.java +++ b/src/main/java/com/dedicatedcode/paikka/service/ReverseGeocodingService.java @@ -19,7 +19,6 @@ import com.dedicatedcode.paikka.config.PaikkaConfiguration; import com.dedicatedcode.paikka.dto.GeoJsonGeometry; import com.dedicatedcode.paikka.dto.POIResponse; -import com.dedicatedcode.paikka.exception.POINotFoundException; import com.dedicatedcode.paikka.flatbuffers.*; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.WKBReader; @@ -105,18 +104,7 @@ public synchronized void reloadDatabase() { logger.warn("POI shards database reload completed but database is not available"); } } - - /** - * Find the nearest POI to the given coordinates. - */ - public POIResponse findNearestPOI(double lat, double lon, String lang) { - List results = findNearbyPOIs(lat, lon, lang, 1); - if (results.isEmpty()) { - throw new POINotFoundException(String.format("No POI found within search radius for coordinates lat=%.6f, lon=%.6f", lat, lon)); - } - return results.getFirst(); - } - + /** * Find nearby POIs to the given coordinates. * @@ -411,7 +399,7 @@ private POIResponse convertPOIToResponse(POIData poi, double queryLat, double qu Geometry geometry = wkbReader.read(poi.boundary()); // Create a simple GeoJSON representation - GeoJsonGeometry geoJsonGeometry = convertJtsToGeoJson(geometry); + GeoJsonGeometry geoJsonGeometry = GeometryHelper.convertJtsToGeoJson(geometry); response.setBoundary(geoJsonGeometry); logger.debug("POI {} has boundary geometry converted to GeoJSON", poi.id()); @@ -444,7 +432,7 @@ private void enhanceWithBuildingInfo(POIResponse response, POIData poi) { try { WKBReader wkbReader = new WKBReader(); Geometry geometry = wkbReader.read(buildingInfo.getBoundaryWkb()); - GeoJsonGeometry geoJsonGeometry = convertJtsToGeoJson(geometry); + GeoJsonGeometry geoJsonGeometry = GeometryHelper.convertJtsToGeoJson(geometry); response.setBoundary(geoJsonGeometry); logger.debug("Enhanced POI {} with building boundary", poi.id()); } catch (Exception e) { @@ -467,75 +455,7 @@ private record HierarchyData(int level, String type, String name, String code, l private record POIWithDistance(POIData poi, double distance) { } - - /** - * Convert JTS Geometry to GeoJSON format. - * This is a simplified conversion that handles basic geometry types. - */ - private GeoJsonGeometry convertJtsToGeoJson(Geometry geometry) { - String geometryType = geometry.getGeometryType(); - Object coordinates = null; - - switch (geometryType) { - case "Point": - coordinates = new double[]{geometry.getCoordinate().x, geometry.getCoordinate().y}; - break; - case "Polygon": - // For polygons, we need to extract the exterior ring coordinates - // This is a simplified implementation - coordinates = extractPolygonCoordinates(geometry); - break; - case "MultiPolygon": - // For multipolygons, extract all polygon coordinates - coordinates = extractMultiPolygonCoordinates(geometry); - break; - default: - // For other geometry types, just indicate the type - logger.debug("Unsupported geometry type for GeoJSON conversion: {}", geometryType); - return new GeoJsonGeometry("Unknown", null); - } - - return new GeoJsonGeometry(geometryType, coordinates); - } - - private Object extractPolygonCoordinates(Geometry polygon) { - try { - // Get exterior ring coordinates - org.locationtech.jts.geom.Coordinate[] coords = polygon.getCoordinates(); - double[][] ring = new double[coords.length][2]; - - for (int i = 0; i < coords.length; i++) { - ring[i][0] = coords[i].x; // longitude - ring[i][1] = coords[i].y; // latitude - } - - // GeoJSON polygon format: [[[x,y],[x,y],...]] - return new double[][][]{ring}; - } catch (Exception e) { - logger.warn("Failed to extract polygon coordinates: {}", e.getMessage()); - return null; - } - } - - private Object extractMultiPolygonCoordinates(Geometry multiPolygon) { - try { - List polygons = new ArrayList<>(); - - for (int i = 0; i < multiPolygon.getNumGeometries(); i++) { - Geometry polygon = multiPolygon.getGeometryN(i); - Object polyCoords = extractPolygonCoordinates(polygon); - if (polyCoords instanceof double[][][]) { - polygons.add((double[][][]) polyCoords); - } - } - - return polygons.toArray(new double[0][][][]); - } catch (Exception e) { - logger.warn("Failed to extract multipolygon coordinates: {}", e.getMessage()); - return null; - } - } - + /** * Build the geometry URL using the configured base URL and current data version. */ From 306725cd392b4934888a53626d21934f981a49db Mon Sep 17 00:00:00 2001 From: Daniel Graf Date: Tue, 31 Mar 2026 10:18:32 +0200 Subject: [PATCH 2/2] feat: add admin password property check for AdminController --- .../com/dedicatedcode/paikka/controller/AdminController.java | 2 ++ src/main/resources/application.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/dedicatedcode/paikka/controller/AdminController.java b/src/main/java/com/dedicatedcode/paikka/controller/AdminController.java index 6146856..0b55a8c 100644 --- a/src/main/java/com/dedicatedcode/paikka/controller/AdminController.java +++ b/src/main/java/com/dedicatedcode/paikka/controller/AdminController.java @@ -22,6 +22,7 @@ import com.dedicatedcode.paikka.service.BuildingService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -38,6 +39,7 @@ @RestController @RequestMapping("/admin") @ConditionalOnProperty(name = "paikka.import-mode", havingValue = "false", matchIfMissing = true) +@ConditionalOnExpression("!'${paikka.admin.password}'.trim().isEmpty()") public class AdminController { private static final Logger logger = LoggerFactory.getLogger(AdminController.class); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a8f3a67..9d40717 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -16,6 +16,8 @@ paikka.data-dir=./data paikka.import.threads=16 paikka.import.chunk-size=100000 +paikka.admin.password= + paikka.query.max-results=500 paikka.query.default-results=10 paikka.query.base-url=http://localhost:8080