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.i18n.Msg;
023import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
024import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
025import ca.uhn.fhir.model.api.IQueryParameterType;
026import ca.uhn.fhir.rest.param.QuantityParam;
027import ca.uhn.fhir.rest.param.SpecialParam;
028import ca.uhn.fhir.rest.param.TokenParam;
029import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
030import ca.uhn.fhir.util.UrlUtil;
031import org.apache.commons.lang3.StringUtils;
032
033import static org.apache.commons.lang3.StringUtils.defaultString;
034import static org.apache.commons.lang3.StringUtils.isBlank;
035
036public class ParsedLocationParam {
037        private final double myLatitudeValue;
038        private final double myLongitudeValue;
039        private final double myDistanceKm;
040
041        private ParsedLocationParam(String theLatitudeValue, String theLongitudeValue, double theDistanceKm) {
042                myLatitudeValue = parseLatLonParameter(theLatitudeValue);
043                myLongitudeValue = parseLatLonParameter(theLongitudeValue);
044                myDistanceKm = theDistanceKm;
045        }
046
047        private static double parseLatLonParameter(String theValue) {
048                try {
049                        return Double.parseDouble(defaultString(theValue));
050                } catch (NumberFormatException e) {
051                        throw new InvalidRequestException(
052                                        Msg.code(2308) + "Invalid lat/lon parameter value: " + UrlUtil.sanitizeUrlPart(theValue));
053                }
054        }
055
056        public double getLatitudeValue() {
057                return myLatitudeValue;
058        }
059
060        public double getLongitudeValue() {
061                return myLongitudeValue;
062        }
063
064        public double getDistanceKm() {
065                return myDistanceKm;
066        }
067
068        public static ParsedLocationParam from(SearchParameterMap theParams, IQueryParameterType theParam) {
069                String latitudeValue;
070                String longitudeValue;
071                double distanceKm = 0.0;
072
073                if (theParam instanceof TokenParam) { // DSTU3
074                        TokenParam param = (TokenParam) theParam;
075                        String value = param.getValue();
076                        String[] parts = value.split(":");
077                        if (parts.length != 2) {
078                                throw new IllegalArgumentException(Msg.code(1228) + "Invalid position format '" + value
079                                                + "'.  Required format is 'latitude:longitude'");
080                        }
081                        latitudeValue = parts[0];
082                        longitudeValue = parts[1];
083                        if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
084                                throw new IllegalArgumentException(Msg.code(1229) + "Invalid position format '" + value
085                                                + "'.  Both latitude and longitude must be provided.");
086                        }
087                        QuantityParam distanceParam = theParams.getNearDistanceParam();
088                        if (distanceParam != null) {
089                                distanceKm = parseLatLonParameter(distanceParam.getValueAsString());
090                        }
091                } else if (theParam instanceof SpecialParam) { // R4
092                        SpecialParam param = (SpecialParam) theParam;
093                        String value = param.getValue();
094                        String[] parts = StringUtils.split(value, '|');
095                        if (parts.length < 2 || parts.length > 4) {
096                                throw new IllegalArgumentException(
097                                                Msg.code(1230) + "Invalid position format '" + value
098                                                                + "'.  Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
099                        }
100                        latitudeValue = parts[0];
101                        longitudeValue = parts[1];
102                        if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
103                                throw new IllegalArgumentException(Msg.code(1231) + "Invalid position format '" + value
104                                                + "'.  Both latitude and longitude must be provided.");
105                        }
106                        if (parts.length >= 3) {
107                                String distanceString = parts[2];
108                                if (!isBlank(distanceString)) {
109                                        distanceKm = parseLatLonParameter(distanceString);
110                                }
111
112                                if (parts.length >= 4) {
113                                        String distanceUnits = parts[3];
114                                        distanceKm = UcumServiceUtil.convert(distanceKm, distanceUnits, "km");
115                                }
116                        }
117                } else {
118                        throw new IllegalArgumentException(Msg.code(1232) + "Invalid position type: " + theParam.getClass());
119                }
120
121                return new ParsedLocationParam(latitudeValue, longitudeValue, distanceKm);
122        }
123}