/** * 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.blur.analysis.type.spatial; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.io.ParseUtils; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import java.text.NumberFormat; import java.util.Locale; import java.util.StringTokenizer; /** * Reads and writes {@link Shape}s to strings. */ public class ShapeReadWriter<CTX extends SpatialContext> { protected CTX ctx; @SuppressWarnings("unchecked") public ShapeReadWriter(SpatialContext ctx) { this.ctx = (CTX) ctx; } /** * Reads a shape from a given string (ie, X Y, XMin XMax... WKT) * <ul> * <li>Point: X Y <br /> * 1.23 4.56</li> * <li>BOX: XMin YMin XMax YMax <br /> * 1.23 4.56 7.87 4.56</li> * <li><a href="http://en.wikipedia.org/wiki/Well-known_text"> WKT (Well Known * Text)</a> <br /> * POLYGON( ... ) <br /> * <b>Note:</b>Polygons and WKT might not be supported by this spatial * context; you'll have to use * {@link com.spatial4j.core.context.jts.JtsSpatialContext}.</li> * </ul> * * @param value * A string representation of the shape; not null. * @return A Shape; not null. * * @see #writeShape */ public Shape readShape(String value) throws InvalidShapeException { Shape s = readStandardShape(value); if (s == null) { throw new InvalidShapeException("Unable to read: " + value); } return s; } /** * Writes a shape to a String, in a format that can be read by * {@link #readShape(String)}. * * @param shape * Not null. * @return Not null. */ public String writeShape(Shape shape) { return writeShape(shape, makeNumberFormat(6)); } /** Overloaded to provide a number format. */ public String writeShape(Shape shape, NumberFormat nf) { if (shape instanceof Point) { Point point = (Point) shape; return nf.format(point.getX()) + " " + nf.format(point.getY()); } else if (shape instanceof Rectangle) { Rectangle rect = (Rectangle) shape; return nf.format(rect.getMinX()) + " " + nf.format(rect.getMinY()) + " " + nf.format(rect.getMaxX()) + " " + nf.format(rect.getMaxY()); } else if (shape instanceof Circle) { Circle c = (Circle) shape; return "Circle(" + nf.format(c.getCenter().getX()) + " " + nf.format(c.getCenter().getY()) + " " + "d=" + nf.format(c.getRadius()) + ")"; } return shape.toString(); } /** * A convenience method to create a suitable NumberFormat for writing numbers. */ public static NumberFormat makeNumberFormat(int fractionDigits) { NumberFormat nf = NumberFormat.getInstance(Locale.ROOT);// not thread-safe nf.setGroupingUsed(false); nf.setMaximumFractionDigits(fractionDigits); nf.setMinimumFractionDigits(fractionDigits); return nf; } protected Shape readStandardShape(String str) { if (str == null || str.length() == 0) { throw new InvalidShapeException(str); } if (Character.isLetter(str.charAt(0))) { if (str.startsWith("Circle(") || str.startsWith("CIRCLE(")) { int idx = str.lastIndexOf(')'); if (idx > 0) { String body = str.substring("Circle(".length(), idx); StringTokenizer st = new StringTokenizer(body, " "); String token = st.nextToken(); Point pt; if (token.indexOf(',') != -1) { pt = readLatCommaLonPoint(token); } else { double x = Double.parseDouble(token); double y = Double.parseDouble(st.nextToken()); pt = ctx.makePoint(x, y); } Double d = null; String arg = st.nextToken(); idx = arg.indexOf('='); if (idx > 0) { String k = arg.substring(0, idx); if (k.equals("d") || k.equals("distance")) { d = parseDistance(arg.substring(idx + 1)); } else { throw new InvalidShapeException("unknown arg: " + k + " :: " + str); } } else { d = parseDistance(arg); } if (st.hasMoreTokens()) { throw new InvalidShapeException("Extra arguments: " + st.nextToken() + " :: " + str); } if (d == null) { throw new InvalidShapeException("Missing Distance: " + str); } // NOTE: we are assuming the units of 'd' is the same as that of the // spatial context. return ctx.makeCircle(pt, d); } } return null; } if (str.indexOf(',') != -1) return readLatCommaLonPoint(str); StringTokenizer st = new StringTokenizer(str, " "); double p0 = Double.parseDouble(st.nextToken()); double p1 = Double.parseDouble(st.nextToken()); if (st.hasMoreTokens()) { double p2 = Double.parseDouble(st.nextToken()); double p3 = Double.parseDouble(st.nextToken()); if (st.hasMoreTokens()) throw new InvalidShapeException("Only 4 numbers supported (rect) but found more: " + str); return ctx.makeRectangle(p0, p2, p1, p3); } return ctx.makePoint(p0, p1); } private static Double parseDistance(String distanceString) { distanceString = distanceString.toLowerCase(); if (distanceString.endsWith("km")) { double distanceInKm = Double.parseDouble(distanceString.substring(0, distanceString.length() - 2)); return DistanceUtils.dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM); } else if (distanceString.endsWith("m")) { double distanceInM = Double.parseDouble(distanceString.substring(0, distanceString.length() - 2)); double distanceInKm = distanceInM * DistanceUtils.MILES_TO_KM; return DistanceUtils.dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM); } return Double.parseDouble(distanceString); } /** Reads geospatial latitude then a comma then longitude. */ private Point readLatCommaLonPoint(String value) throws InvalidShapeException { double[] latLon = ParseUtils.parseLatitudeLongitude(value); return ctx.makePoint(latLon[1], latLon[0]); } }