All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.util.SpatialUtils Maven / Gradle / Ivy

There is a newer version: 9.6.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.util;

import java.text.ParseException;

import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.solr.common.SolrException;

/** Utility methods pertaining to spatial. */
public class SpatialUtils {

  private SpatialUtils() {}

  /**
   * Parses a 'geom' parameter (might also be used to parse shapes for indexing). {@code geomStr} can either be WKT or
   * a rectangle-range syntax (see {@link #parseRectangle(String, org.locationtech.spatial4j.context.SpatialContext)}.
   */
  public static Shape parseGeomSolrException(String geomStr, SpatialContext ctx) {
    if (geomStr.length() == 0) {
      throw new IllegalArgumentException("0-length geometry string");
    }
    char c = geomStr.charAt(0);
    if (c == '[' || c == '{') {
      return parseRectangeSolrException(geomStr, ctx);
    }
    //TODO parse a raw point?
    try {
      return ctx.readShapeFromWkt(geomStr);
    } catch (ParseException e) {
      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
          "Expecting WKT or '[minPoint TO maxPoint]': " + e, e);
    }

  }

  /** Parses either "lat, lon" (spaces optional on either comma side) or "x y" style formats. Spaces can be basically
   * anywhere.  And not any whitespace, just the space char.
   *
   * @param str Non-null; may have leading or trailing spaces
   * @param ctx Non-null
   * @return Non-null
   * @throws InvalidShapeException If for any reason there was a problem parsing the string or creating the point.
   */
  public static Point parsePoint(String str, SpatialContext ctx) throws InvalidShapeException {
    //note we don't do generic whitespace, just a literal space char detection
    try {
      double x, y;
      str = str.trim();//TODO use findIndexNotSpace instead?
      int commaIdx = str.indexOf(',');
      if (commaIdx == -1) {
        //  "x y" format
        int spaceIdx = str.indexOf(' ');
        if (spaceIdx == -1)
          throw new InvalidShapeException("Point must be in 'lat, lon' or 'x y' format: " + str);
        int middleEndIdx = findIndexNotSpace(str, spaceIdx + 1, +1);
        x = Double.parseDouble(str.substring(0, spaceIdx));
        y = Double.parseDouble(str.substring(middleEndIdx));
      } else {
        // "lat, lon" format
        int middleStartIdx = findIndexNotSpace(str, commaIdx - 1, -1);
        int middleEndIdx = findIndexNotSpace(str, commaIdx + 1, +1);
        y = Double.parseDouble(str.substring(0, middleStartIdx + 1));
        x = Double.parseDouble(str.substring(middleEndIdx));
      }

      x = ctx.normX(x);//by default norm* methods do nothing but perhaps it's been customized
      y = ctx.normY(y);
      return ctx.makePoint(x, y);//will verify x & y fit in boundary
    } catch (InvalidShapeException e) {
      throw e;
    } catch (Exception e) {
      throw new InvalidShapeException(e.toString(), e);
    }
  }

  private static int findIndexNotSpace(String str, int startIdx, int inc) {
    assert inc == +1 || inc == -1;
    int idx = startIdx;
    while (idx >= 0 && idx < str.length() && str.charAt(idx) == ' ')
      idx += inc;
    return idx;
  }

  /** Calls {@link #parsePoint(String, org.locationtech.spatial4j.context.SpatialContext)} and wraps
   * the exception with {@link org.apache.solr.common.SolrException} with a helpful message. */
  public static Point parsePointSolrException(String externalVal, SpatialContext ctx) throws SolrException {
    try {
      return parsePoint(externalVal, ctx);
    } catch (InvalidShapeException e) {
      String message = e.getMessage();
      if (!message.contains(externalVal))
        message = "Can't parse point '" + externalVal + "' because: " + message;
      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
    }
  }

  /**
   * Parses {@code str} in the format of '[minPoint TO maxPoint]' where {@code minPoint} is the lower left corner
   * and maxPoint is the upper-right corner of the bounding box.  Both corners may optionally be wrapped with a quote
   * and then it's parsed via {@link #parsePoint(String, org.locationtech.spatial4j.context.SpatialContext)}.
   * @param str Non-null; may *not* have leading or trailing spaces
   * @param ctx Non-null
   * @return the Rectangle
   * @throws InvalidShapeException If for any reason there was a problem parsing the string or creating the rectangle.
   */
  public static Rectangle parseRectangle(String str, SpatialContext ctx) throws InvalidShapeException {
    //note we don't do generic whitespace, just a literal space char detection
    try {
      int toIdx = str.indexOf(" TO ");
      if (toIdx == -1 || str.charAt(0) != '[' || str.charAt(str.length() - 1) != ']') {
        throw new InvalidShapeException("expecting '[bottomLeft TO topRight]'");
      }
      String leftPart = unwrapQuotes(str.substring(1, toIdx).trim());
      String rightPart = unwrapQuotes(str.substring(toIdx + " TO ".length(), str.length() - 1).trim());
      return ctx.makeRectangle(parsePoint(leftPart, ctx), parsePoint(rightPart, ctx));
    } catch (InvalidShapeException e) {
      throw e;
    } catch (Exception e) {
      throw new InvalidShapeException(e.toString(), e);
    }
  }

  /**
   * Calls {@link #parseRectangle(String, org.locationtech.spatial4j.context.SpatialContext)} and wraps the exception with
   * {@link org.apache.solr.common.SolrException} with a helpful message.
   */
  public static Rectangle parseRectangeSolrException(String externalVal, SpatialContext ctx) throws SolrException {
    try {
      return parseRectangle(externalVal, ctx);
    } catch (InvalidShapeException e) {
      String message = e.getMessage();
      if (!message.contains(externalVal))
        message = "Can't parse rectangle '" + externalVal + "' because: " + message;
      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
    }
  }

  private static String unwrapQuotes(String str) {
    if (str.length() >= 2 && str.charAt(0) == '\"' && str.charAt(str.length()-1) == '\"') {
      return str.substring(1, str.length()-1);
    }
    return str;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy