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

org.elasticsearch.common.geo.ShapeBuilder Maven / Gradle / Ivy

There is a newer version: 8.14.1
Show newest version
/*
 * Licensed to ElasticSearch and Shay Banon under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. ElasticSearch 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.elasticsearch.common.geo;

import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.impl.PointImpl;
import com.spatial4j.core.shape.impl.RectangleImpl;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;

/**
 * Utility class for building {@link Shape} instances like {@link Point},
 * {@link Rectangle} and Polygons.
 */
public class ShapeBuilder {

    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();

    private ShapeBuilder() {
    }

    /**
     * Creates a new {@link Point}
     *
     * @param lon Longitude of point
     * @param lat Latitude of point
     * @return Point with the latitude and longitude
     */
    public static Point newPoint(double lon, double lat) {
        return new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
    }

    /**
     * Creates a new {@link RectangleBuilder} to build a {@link Rectangle}
     *
     * @return RectangleBuilder instance
     */
    public static RectangleBuilder newRectangle() {
        return new RectangleBuilder();
    }

    /**
     * Creates a new {@link PolygonBuilder} to build a Polygon
     *
     * @return PolygonBuilder instance
     */
    public static PolygonBuilder newPolygon() {
        return new PolygonBuilder();
    }

    /**
     * Creates a new {@link MultiPolygonBuilder} to build a MultiPolygon
     *
     * @return MultiPolygonBuilder instance
     */
    public static MultiPolygonBuilder newMultiPolygon() {
        return new MultiPolygonBuilder();
    }

    /**
     * Converts the given Shape into the JTS {@link Geometry} representation.
     * If the Shape already uses a Geometry, that is returned.
     *
     * @param shape Shape to convert
     * @return Geometry representation of the Shape
     */
    public static Geometry toJTSGeometry(Shape shape) {
        if (shape instanceof JtsGeometry) {
            return ((JtsGeometry) shape).getGeom();
        } else if (shape instanceof JtsPoint) {
            return ((JtsPoint) shape).getGeom();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;

            if (rectangle.getCrossesDateLine()) {
                throw new IllegalArgumentException("Cannot convert Rectangles that cross the dateline into JTS Geometrys");
            }

            return newPolygon().point(rectangle.getMinX(), rectangle.getMaxY())
                    .point(rectangle.getMaxX(), rectangle.getMaxY())
                    .point(rectangle.getMaxX(), rectangle.getMinY())
                    .point(rectangle.getMinX(), rectangle.getMinY())
                    .point(rectangle.getMinX(), rectangle.getMaxY()).toPolygon();
        } else if (shape instanceof Point) {
            Point point = (Point) shape;
            return GEOMETRY_FACTORY.createPoint(new Coordinate(point.getX(), point.getY()));
        }

        throw new IllegalArgumentException("Shape type [" + shape.getClass().getSimpleName() + "] not supported");
    }

    /**
     * Builder for creating a {@link Rectangle} instance
     */
    public static class RectangleBuilder {

        private Point topLeft;
        private Point bottomRight;

        /**
         * Sets the top left point of the Rectangle
         *
         * @param lon Longitude of the top left point
         * @param lat Latitude of the top left point
         * @return this
         */
        public RectangleBuilder topLeft(double lon, double lat) {
            this.topLeft = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
            return this;
        }

        /**
         * Sets the bottom right point of the Rectangle
         *
         * @param lon Longitude of the bottom right point
         * @param lat Latitude of the bottom right point
         * @return this
         */
        public RectangleBuilder bottomRight(double lon, double lat) {
            this.bottomRight = new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT);
            return this;
        }

        /**
         * Builds the {@link Rectangle} instance
         *
         * @return Built Rectangle
         */
        public Rectangle build() {
            return new RectangleImpl(topLeft.getX(), bottomRight.getX(), bottomRight.getY(), topLeft.getY(), GeoShapeConstants.SPATIAL_CONTEXT);
        }
    }

    /**
     * Builder for creating a {@link Shape} instance of a MultiPolygon
     */
    public static class MultiPolygonBuilder {
        private final ArrayList> polygons = new ArrayList>();

        /**
         * Add a new polygon to the multipolygon
         * 
         * @return builder for the new polygon
         */
        public EmbededPolygonBuilder polygon() {
            EmbededPolygonBuilder builder = new EmbededPolygonBuilder(this);
            polygons.add(builder);
            return builder;
        }

        public Shape build() {
            return new JtsGeometry(toMultiPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
        }

        public MultiPolygon toMultiPolygon() {
            Polygon[] polygons = new Polygon[this.polygons.size()];
            for (int i = 0; i polygon : polygons) {
                polygon.emdedXContent(null, xcontent);
            }
            xcontent.endArray();
        }

    }

    /**
     * Builder for creating a {@link Shape} instance of a single Polygon
     */
    public static class PolygonBuilder extends EmbededPolygonBuilder {

        private PolygonBuilder() {
            super(null);
        }

        @Override
        public PolygonBuilder close() {
            super.close();
            return this;
        }
    }

    /**
     * Builder for creating a {@link Shape} instance of a Polygon
     */
    public static class EmbededPolygonBuilder {

        private final E parent;
        private final LinearRingBuilder> ring = new LinearRingBuilder>(this);
        private final ArrayList>> holes = new ArrayList>>();

        private EmbededPolygonBuilder(E parent) {
            super();
            this.parent = parent;
        }

        /**
         * Adds a point to the Polygon
         *
         * @param lon Longitude of the point
         * @param lat Latitude of the point
         * @return this
         */
        public EmbededPolygonBuilder point(double lon, double lat) {
            ring.point(lon, lat);
            return this;
        }

        /**
         * Start creating a new hole within the polygon
         * @return a builder for holes
         */
        public LinearRingBuilder> hole() {
            LinearRingBuilder> builder = new LinearRingBuilder>(this);
            this.holes.add(builder);
            return builder;
        }

        /**
         * Builds a {@link Shape} instance representing the {@link Polygon}
         *
         * @return Built LinearRing
         */
        public Shape build() {
            return new JtsGeometry(toPolygon(), GeoShapeConstants.SPATIAL_CONTEXT, true);
        }

        /**
         * Creates the raw {@link Polygon}
         *
         * @return Built polygon
         */
        public Polygon toPolygon() {
            this.ring.close();
            LinearRing ring = this.ring.toLinearRing();
            LinearRing[] rings = new LinearRing[holes.size()];
            for (int i = 0; i < rings.length; i++) {
                rings[i] = this.holes.get(i).toLinearRing();
            }
            return GEOMETRY_FACTORY.createPolygon(ring, rings);
        }

        /**
         * Close the linestring by copying the first point if necessary
         * @return parent object
         */
        public E close() {
            this.ring.close();
            return parent;
        }

        public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
            if(name != null) {
                xcontent.startObject(name);
            } else {
                xcontent.startObject();
            }
            xcontent.field("type", "polygon");
            emdedXContent("coordinates", xcontent);
            xcontent.endObject();
            return xcontent;
        }

        protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
            if(name != null) {
                xcontent.startArray(name);
            } else {
                xcontent.startArray();
            }
            ring.emdedXContent(null, xcontent);
            for (LinearRingBuilder ring : holes) {
                ring.emdedXContent(null, xcontent);
            }
            xcontent.endArray();
        }

    }

    /**
     * Builder for creating a {@link Shape} instance of a Polygon
     */
    public static class LinearRingBuilder {

        private final E parent;
        private final List points = new ArrayList();

        private LinearRingBuilder(E parent) {
            super();
            this.parent = parent;
        }

        /**
         * Adds a point to the Ring
         *
         * @param lon Longitude of the point
         * @param lat Latitude of the point
         * @return this
         */
        public LinearRingBuilder point(double lon, double lat) {
            points.add(new PointImpl(lon, lat, GeoShapeConstants.SPATIAL_CONTEXT));
            return this;
        }

        /**
         * Builds a {@link Shape} instance representing the ring
         *
         * @return Built LinearRing
         */
        protected Shape build() {
            return new JtsGeometry(toLinearRing(), GeoShapeConstants.SPATIAL_CONTEXT, true);
        }

        /**
         * Creates the raw {@link Polygon}
         *
         * @return Built LinearRing
         */
        protected LinearRing toLinearRing() {
            this.close();
            Coordinate[] coordinates = new Coordinate[points.size()];
            for (int i = 0; i < coordinates.length; i++) {
                coordinates[i] = new Coordinate(points.get(i).getX(), points.get(i).getY());
            }

            return GEOMETRY_FACTORY.createLinearRing(coordinates);
        }

        /**
         * Close the linestring by copying the first point if necessary
         * @return parent object
         */
        public E close() {
            Point first = points.get(0);
            Point last = points.get(points.size()-1);
            if(first.getX() != last.getX() || first.getY() != last.getY()) {
                points.add(first);
            }

            if(points.size()<4) {
                throw new ElasticSearchIllegalArgumentException("A linear ring is defined by a least four points");
            }

            return parent;
        }

        public XContentBuilder toXContent(String name, XContentBuilder xcontent) throws IOException {
            if(name != null) {
                xcontent.startObject(name);
            } else {
                xcontent.startObject();
            }
            xcontent.field("type", "linestring");
            emdedXContent("coordinates", xcontent);
            xcontent.endObject();
            return xcontent;
        }

        protected void emdedXContent(String name, XContentBuilder xcontent) throws IOException {
            if(name != null) {
                xcontent.startArray(name);
            } else {
                xcontent.startArray();
            }
            for(Point point : points) {
                xcontent.startArray().value(point.getY()).value(point.getX()).endArray();
            }
            xcontent.endArray();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy