
001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.util; 021 022import org.hibernate.search.engine.spatial.GeoBoundingBox; 023import org.hibernate.search.engine.spatial.GeoPoint; 024import org.slf4j.Logger; 025 026import static ca.uhn.fhir.jpa.searchparam.extractor.GeopointNormalizer.normalizeLatitude; 027import static ca.uhn.fhir.jpa.searchparam.extractor.GeopointNormalizer.normalizeLongitude; 028import static org.slf4j.LoggerFactory.getLogger; 029 030/** 031 * Utility for calculating a symmetric GeoBoundingBox around a center point, 032 * ensuring each edge is at least the given distance in kilometers away. 033 */ 034public class CoordCalculator { 035 private static final Logger ourLog = getLogger(CoordCalculator.class); 036 public static final double MAX_SUPPORTED_DISTANCE_KM = 037 10000.0; // Slightly less than a quarter of the earth's circumference 038 private static final double RADIUS_EARTH_KM = 6378.1; 039 040 /** 041 * Computes a symmetric bounding box around the given latitude/longitude center, 042 * with each edge being at least the given distance in km away. 043 * 044 * @param lat center latitude 045 * @param lon center longitude 046 * @param distanceKm distance in kilometers from center to each edge 047 * @return GeoBoundingBox centered around the given coordinates 048 */ 049 public static GeoBoundingBox getBox(double lat, double lon, double distanceKm) { 050 // Approximate 1 degree latitude as 111 km 051 double deltaLat = distanceKm / 111.0; 052 053 // Longitude varies with latitude 054 double deltaLon = distanceKm / (111.320 * Math.cos(Math.toRadians(lat))); 055 056 double minLat = normalizeLatitude(lat - deltaLat); 057 double maxLat = normalizeLatitude(lat + deltaLat); 058 double minLon = normalizeLongitude(lon - deltaLon); 059 double maxLon = normalizeLongitude(lon + deltaLon); 060 061 GeoPoint topLeft = GeoPoint.of(maxLat, minLon); 062 GeoPoint bottomRight = GeoPoint.of(minLat, maxLon); 063 064 return GeoBoundingBox.of(topLeft, bottomRight); 065 } 066}