org.apache.lucene.spatial.spatial4j.Geo3dShapeFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.lucene.spatial.spatial4j.Geo3dShapeFactory.java

Source

/*
 * 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.lucene.spatial.spatial4j;

import java.util.ArrayList;
import java.util.List;

import com.google.common.geometry.S2Cell;
import com.google.common.geometry.S2CellId;
import com.google.common.geometry.S2Point;
import org.apache.lucene.spatial.prefix.tree.S2ShapeFactory;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoCompositeAreaShape;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPointShape;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.apache.lucene.spatial3d.geom.GeoPolygon;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoS2ShapeFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;

/**
 * Geo3d implementation of {@link S2ShapeFactory}
 *
 * @lucene.experimental
 */
public class Geo3dShapeFactory implements S2ShapeFactory {

    private final boolean normWrapLongitude;
    private SpatialContext context;
    private PlanetModel planetModel;

    /**
     * Default accuracy for circles when not using the unit sphere.
     * It is equivalent to ~10m on the surface of the earth.
     */
    private static final double DEFAULT_CIRCLE_ACCURACY = 1e-4;
    private double circleAccuracy = DEFAULT_CIRCLE_ACCURACY;

    @SuppressWarnings("unchecked")
    public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
        this.context = context;
        this.planetModel = ((Geo3dSpatialContextFactory) factory).planetModel;
        this.normWrapLongitude = context.isGeo() && factory.normWrapLongitude;
    }

    @Override
    public SpatialContext getSpatialContext() {
        return context;
    }

    /**
     * Set the accuracy for circles in decimal degrees. Note that accuracy has no effect
     * when the planet model is a sphere. In that case, circles are always fully precise.
     *
     * @param circleAccuracy the provided accuracy in decimal degrees.
     */
    public void setCircleAccuracy(double circleAccuracy) {
        this.circleAccuracy = circleAccuracy;
    }

    @Override
    public boolean isNormWrapLongitude() {
        return normWrapLongitude;
    }

    @Override
    public double normX(double x) {
        if (this.normWrapLongitude) {
            x = DistanceUtils.normLonDEG(x);
        }
        return x;
    }

    @Override
    public double normY(double y) {
        return y;
    }

    @Override
    public double normZ(double z) {
        return z;
    }

    @Override
    public double normDist(double distance) {
        return distance;
    }

    @Override
    public void verifyX(double x) {
        Rectangle bounds = this.context.getWorldBounds();
        if (x < bounds.getMinX() || x > bounds.getMaxX()) {
            throw new InvalidShapeException("Bad X value " + x + " is not in boundary " + bounds);
        }
    }

    @Override
    public void verifyY(double y) {
        Rectangle bounds = this.context.getWorldBounds();
        if (y < bounds.getMinY() || y > bounds.getMaxY()) {
            throw new InvalidShapeException("Bad Y value " + y + " is not in boundary " + bounds);
        }
    }

    @Override
    public void verifyZ(double v) {
    }

    @Override
    public Point pointXY(double x, double y) {
        GeoPointShape point = GeoPointShapeFactory.makeGeoPointShape(planetModel,
                y * DistanceUtils.DEGREES_TO_RADIANS, x * DistanceUtils.DEGREES_TO_RADIANS);
        return new Geo3dPointShape(point, context);
    }

    @Override
    public Point pointXYZ(double x, double y, double z) {
        GeoPoint point = new GeoPoint(x, y, z);
        GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, point.getLatitude(),
                point.getLongitude());
        return new Geo3dPointShape(pointShape, context);
        //throw new UnsupportedOperationException();
    }

    @Override
    public Rectangle rect(Point point, Point point1) {
        return rect(point.getX(), point1.getX(), point.getY(), point1.getY());
    }

    @Override
    public Rectangle rect(double minX, double maxX, double minY, double maxY) {
        GeoBBox bBox = GeoBBoxFactory.makeGeoBBox(planetModel, maxY * DistanceUtils.DEGREES_TO_RADIANS,
                minY * DistanceUtils.DEGREES_TO_RADIANS, minX * DistanceUtils.DEGREES_TO_RADIANS,
                maxX * DistanceUtils.DEGREES_TO_RADIANS);
        return new Geo3dRectangleShape(bBox, context, minX, maxX, minY, maxY);
    }

    @Override
    public Circle circle(double x, double y, double distance) {
        GeoCircle circle;
        if (planetModel.isSphere()) {
            circle = GeoCircleFactory.makeGeoCircle(planetModel, y * DistanceUtils.DEGREES_TO_RADIANS,
                    x * DistanceUtils.DEGREES_TO_RADIANS, distance * DistanceUtils.DEGREES_TO_RADIANS);
        } else {
            //accuracy is defined as a linear distance in this class. At tiny distances, linear distance
            //can be approximated to surface distance in radians.
            circle = GeoCircleFactory.makeExactGeoCircle(planetModel, y * DistanceUtils.DEGREES_TO_RADIANS,
                    x * DistanceUtils.DEGREES_TO_RADIANS, distance * DistanceUtils.DEGREES_TO_RADIANS,
                    circleAccuracy * DistanceUtils.DEGREES_TO_RADIANS);

        }
        return new Geo3dCircleShape(circle, context);
    }

    @Override
    public Circle circle(Point point, double distance) {
        return circle(point.getX(), point.getY(), distance);
    }

    @Override
    public Shape lineString(List<Point> list, double distance) {
        LineStringBuilder builder = lineString();
        for (Point point : list) {
            builder.pointXY(point.getX(), point.getY());
        }
        builder.buffer(distance);
        return builder.build();
    }

    @Override
    public <S extends Shape> ShapeCollection<S> multiShape(List<S> list) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LineStringBuilder lineString() {
        return new Geo3dLineStringBuilder();
    }

    @Override
    public PolygonBuilder polygon() {
        return new Geo3dPolygonBuilder();
    }

    @Override
    public <T extends Shape> MultiShapeBuilder<T> multiShape(Class<T> aClass) {
        return new Geo3dMultiShapeBuilder<>();
    }

    @Override
    public MultiPointBuilder multiPoint() {
        return new Geo3dMultiPointBuilder();
    }

    @Override
    public MultiLineStringBuilder multiLineString() {
        return new Geo3dMultiLineBuilder();
    }

    @Override
    public MultiPolygonBuilder multiPolygon() {
        return new Geo3dMultiPolygonBuilder();
    }

    @Override
    public Shape getS2CellShape(S2CellId cellId) {
        S2Cell cell = new S2Cell(cellId);
        GeoPoint point1 = getGeoPoint(cell.getVertexRaw(0));
        GeoPoint point2 = getGeoPoint(cell.getVertexRaw(1));
        GeoPoint point3 = getGeoPoint(cell.getVertexRaw(2));
        GeoPoint point4 = getGeoPoint(cell.getVertexRaw(3));
        return new Geo3dShape<>(GeoS2ShapeFactory.makeGeoS2Shape(planetModel, point1, point2, point3, point4),
                context);
    }

    private GeoPoint getGeoPoint(S2Point point) {
        return planetModel.createSurfacePoint(point.get(0), point.get(1), point.get(2));
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PointsBuilder} interface to
     * generate {@link GeoPoint}.
     *
     * @param <T> is normally this object
     */
    private class Geo3dPointBuilder<T> implements PointsBuilder<T> {

        List<GeoPoint> points = new ArrayList<>();

        @SuppressWarnings("unchecked")
        @Override
        public T pointXY(double x, double y) {
            GeoPoint point = new GeoPoint(planetModel, y * DistanceUtils.DEGREES_TO_RADIANS,
                    x * DistanceUtils.DEGREES_TO_RADIANS);
            points.add(point);
            return (T) this;
        }

        @SuppressWarnings("unchecked")
        @Override
        public T pointXYZ(double x, double y, double z) {
            GeoPoint point = new GeoPoint(x, y, z);
            if (!points.contains(point)) {
                points.add(point);
            }
            return (T) this;
        }
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate
     * line strings.
     */
    private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {

        double distance = 0;

        @Override
        public LineStringBuilder buffer(double distance) {
            this.distance = distance;
            return this;
        }

        @Override
        public Shape build() {
            GeoPath path = GeoPathFactory.makeGeoPath(planetModel, distance,
                    points.toArray(new GeoPoint[points.size()]));
            return new Geo3dShape<>(path, context);
        }
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PolygonBuilder} to generate
     * polygons.
     */
    private class Geo3dPolygonBuilder extends Geo3dPointBuilder<PolygonBuilder> implements PolygonBuilder {

        List<GeoPolygon> polyHoles;

        @Override
        public HoleBuilder hole() {
            return new Geo3dHoleBuilder();
        }

        class Geo3dHoleBuilder extends Geo3dPointBuilder<PolygonBuilder.HoleBuilder>
                implements PolygonBuilder.HoleBuilder {
            @Override
            public PolygonBuilder endHole() {
                if (polyHoles == null) {
                    polyHoles = new ArrayList<>();
                }
                polyHoles.add(GeoPolygonFactory.makeGeoPolygon(planetModel, points));
                return Geo3dPolygonBuilder.this;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public Shape build() {
            GeoPolygon polygon = GeoPolygonFactory.makeGeoPolygon(planetModel, points, polyHoles);
            return new Geo3dShape<>(polygon, context);
        }

        @Override
        public Shape buildOrRect() {
            return build();
        }
    }

    private class Geo3dMultiPointBuilder extends Geo3dPointBuilder<MultiPointBuilder> implements MultiPointBuilder {

        @Override
        public Shape build() {
            GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
            for (GeoPoint point : points) {
                GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, point.getLatitude(),
                        point.getLongitude());
                areaShape.addShape(pointShape);
            }
            return new Geo3dShape<>(areaShape, context);
        }
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiLineStringBuilder} to generate
     * multi-lines
     */
    private class Geo3dMultiLineBuilder implements MultiLineStringBuilder {

        List<LineStringBuilder> builders = new ArrayList<>();

        @Override
        public LineStringBuilder lineString() {
            return new Geo3dLineStringBuilder();
        }

        @Override
        public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) {
            builders.add(lineStringBuilder);
            return this;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Shape build() {
            GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
            for (LineStringBuilder builder : builders) {
                Geo3dShape<GeoPolygon> shape = (Geo3dShape<GeoPolygon>) builder.build();
                areaShape.addShape(shape.shape);
            }
            return new Geo3dShape<>(areaShape, context);
        }
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiPolygonBuilder} to generate
     * multi-polygons. We have chosen to use a composite shape but
     * it might be possible to use GeoComplexPolygon.
     */
    private class Geo3dMultiPolygonBuilder implements MultiPolygonBuilder {

        List<PolygonBuilder> builders = new ArrayList<>();

        @Override
        public PolygonBuilder polygon() {
            return new Geo3dPolygonBuilder();
        }

        @Override
        public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) {
            builders.add(polygonBuilder);
            return this;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Shape build() {
            GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
            for (PolygonBuilder builder : builders) {
                Geo3dShape<GeoPolygon> shape = (Geo3dShape<GeoPolygon>) builder.build();
                areaShape.addShape(shape.shape);
            }
            return new Geo3dShape<>(areaShape, context);
        }
    }

    /**
     * Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
     * geometry collections.
     *
     * @param <T> is the type of shapes.
     */
    private class Geo3dMultiShapeBuilder<T extends Shape> implements MultiShapeBuilder<T> {

        GeoCompositeAreaShape composite = new GeoCompositeAreaShape(planetModel);

        @Override
        public MultiShapeBuilder<T> add(T shape) {
            Geo3dShape<?> areaShape = (Geo3dShape<?>) shape;
            composite.addShape(areaShape.shape);
            return this;
        }

        @Override
        public Shape build() {
            return new Geo3dShape<>(composite, context);
        }
    }
}