001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 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.search.builder.predicate; 021 022import ca.uhn.fhir.context.RuntimeSearchParam; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.model.RequestPartitionId; 025import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder; 026import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 027import ca.uhn.fhir.jpa.util.CoordCalculator; 028import ca.uhn.fhir.model.api.IQueryParameterType; 029import ca.uhn.fhir.model.dstu2.resource.Location; 030import com.healthmarketscience.sqlbuilder.BinaryCondition; 031import com.healthmarketscience.sqlbuilder.ComboCondition; 032import com.healthmarketscience.sqlbuilder.Condition; 033import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn; 034import org.hibernate.search.engine.spatial.GeoBoundingBox; 035 036public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder { 037 038 private final DbColumn myColumnLatitude; 039 private final DbColumn myColumnLongitude; 040 041 /** 042 * Constructor 043 */ 044 public CoordsPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) { 045 super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_SPIDX_COORDS")); 046 047 myColumnLatitude = getTable().addColumn("SP_LATITUDE"); 048 myColumnLongitude = getTable().addColumn("SP_LONGITUDE"); 049 } 050 051 public DbColumn getColumnLatitude() { 052 return myColumnLatitude; 053 } 054 055 public DbColumn getColumnLongitude() { 056 return myColumnLongitude; 057 } 058 059 public Condition createPredicateCoords( 060 SearchParameterMap theParams, 061 IQueryParameterType theParam, 062 String theResourceName, 063 RuntimeSearchParam theSearchParam, 064 CoordsPredicateBuilder theFrom, 065 RequestPartitionId theRequestPartitionId) { 066 067 ParsedLocationParam params = ParsedLocationParam.from(theParams, theParam); 068 double distanceKm = params.getDistanceKm(); 069 double latitudeValue = params.getLatitudeValue(); 070 double longitudeValue = params.getLongitudeValue(); 071 072 Condition latitudePredicate; 073 Condition longitudePredicate; 074 if (distanceKm == 0.0) { 075 latitudePredicate = theFrom.createPredicateLatitudeExact(latitudeValue); 076 longitudePredicate = theFrom.createPredicateLongitudeExact(longitudeValue); 077 } else if (distanceKm < 0.0) { 078 throw new IllegalArgumentException(Msg.code(1233) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" 079 + distanceKm + "' must be >= 0.0"); 080 } else if (distanceKm > CoordCalculator.MAX_SUPPORTED_DISTANCE_KM) { 081 throw new IllegalArgumentException(Msg.code(1234) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" 082 + distanceKm + "' must be <= " + CoordCalculator.MAX_SUPPORTED_DISTANCE_KM); 083 } else { 084 GeoBoundingBox box = CoordCalculator.getBox(latitudeValue, longitudeValue, distanceKm); 085 latitudePredicate = theFrom.createLatitudePredicateFromBox(box); 086 longitudePredicate = theFrom.createLongitudePredicateFromBox(box); 087 } 088 ComboCondition singleCode = ComboCondition.and(latitudePredicate, longitudePredicate); 089 return combineWithHashIdentityPredicate(theResourceName, theSearchParam.getName(), singleCode); 090 } 091 092 public Condition createPredicateLatitudeExact(double theLatitudeValue) { 093 return BinaryCondition.equalTo(myColumnLatitude, generatePlaceholder(theLatitudeValue)); 094 } 095 096 public Condition createPredicateLongitudeExact(double theLongitudeValue) { 097 return BinaryCondition.equalTo(myColumnLongitude, generatePlaceholder(theLongitudeValue)); 098 } 099 100 public Condition createLatitudePredicateFromBox(GeoBoundingBox theBox) { 101 return ComboCondition.and( 102 BinaryCondition.greaterThanOrEq( 103 myColumnLatitude, 104 generatePlaceholder(theBox.bottomRight().latitude())), 105 BinaryCondition.lessThanOrEq( 106 myColumnLatitude, generatePlaceholder(theBox.topLeft().latitude()))); 107 } 108 109 public Condition createLongitudePredicateFromBox(GeoBoundingBox theBox) { 110 if (theBox.bottomRight().longitude() < theBox.topLeft().longitude()) { 111 return ComboCondition.or( 112 BinaryCondition.greaterThanOrEq( 113 myColumnLongitude, 114 generatePlaceholder(theBox.bottomRight().longitude())), 115 BinaryCondition.lessThanOrEq( 116 myColumnLongitude, 117 generatePlaceholder(theBox.topLeft().longitude()))); 118 } else { 119 return ComboCondition.and( 120 BinaryCondition.greaterThanOrEq( 121 myColumnLongitude, 122 generatePlaceholder(theBox.topLeft().longitude())), 123 BinaryCondition.lessThanOrEq( 124 myColumnLongitude, 125 generatePlaceholder(theBox.bottomRight().longitude()))); 126 } 127 } 128}