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

org.codegas.commons.lang.spacial.Polygon Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package org.codegas.commons.lang.spacial;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonValue;

import org.codegas.commons.lang.collection.CollectionUtil;
import org.codegas.commons.ende.json.JsonValueDecoder;

public final class Polygon {

    private static final int RANDOM_COORD_GENERATION_RANGE = 16;

    private static final JsonDecoder JSON_DECODER = new JsonDecoder();

    private List vertices;

    public Polygon(List vertices) {
        this.vertices = validateVertices(vertices);
    }

    protected Polygon() {

    }

    public static Polygon fromString(String json) {
        return jsonDecoder().decode(json);
    }

    public static JsonValueDecoder jsonDecoder() {
        return JSON_DECODER;
    }

    public boolean areVerticesAdjacent(Point vertex1, Point vertex2) {
        int vertex1Index = vertices.indexOf(vertex1);
        int previousVertexIndex = vertex1Index > 0 ? vertex1Index - 1 : vertices.size() - 1;
        int nextVertexIndex = vertex1Index < (vertices.size() - 1) ? vertex1Index + 1 : 0;
        return vertex1Index >= 0 && (vertices.get(previousVertexIndex).equals(vertex2) || vertices.get(nextVertexIndex).equals(vertex2));
    }

    // "Does this Line Segment affect the shape of this polygon from both the inside and out?"
    public boolean isIntersectedBy(LineSegment lineSegment) {
        return (!isVertex(lineSegment.getPoint1()) && !isVertex(lineSegment.getPoint2()) && contains(lineSegment.getPoint1()) != contains(lineSegment.getPoint2())) ||
            getSegments().stream().anyMatch(lineSegment::intersectsExclusive);
    }

    // "Can this Point only be reached by going 'through' this Polygon?"
    public boolean contains(Point point) {
        LineSegment outerSegment = generateOuterNonVertexContainingSegmentWithPoint(point);
        List segments = getSegments();
        return segments.stream().noneMatch(segment -> segment.containsInclusive(point)) &&
            segments.stream().filter(outerSegment::intersectsExclusive).count() % 2 == 1;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o == null || !getClass().equals(o.getClass())) {
            return false;
        }

        Polygon polygon = (Polygon) o;
        return vertices.size() == polygon.vertices.size() &&
            Collections.indexOfSubList(doubleVertices(), polygon.vertices) >= 0;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        for (Point point : vertices) {
            hash ^= point.hashCode();
        }
        return hash;
    }

    @Override
    public String toString() {
        return createValue().toString();
    }

    public JsonValue createValue() {
        return JSON_DECODER.toValue(this);
    }

    public List getSegments() {
        return IntStream.range(0, vertices.size())
            .mapToObj(i -> new LineSegment(vertices.get(i), vertices.get((i + 1) % vertices.size())))
            .collect(Collectors.toList());
    }

    public List getVertices() {
        return vertices;
    }

    private List validateVertices(List vertices) {
        if (vertices == null) {
            throw new IllegalArgumentException("Vertices for Polygon must not be null.");
        } else if (vertices.size() < 3) {
            throw new IllegalArgumentException("Number of vertices for Polygon must be at least 3.");
        } else {
            return vertices;
        }
    }

    private boolean isVertex(Point point) {
        return vertices.stream().anyMatch(point::equals);
    }

    private LineSegment generateOuterNonVertexContainingSegmentWithPoint(Point point) {
        Point nonInnerPoint = vertices.stream().reduce(vertices.get(0), maxPoint());
        LineSegment lineSegment;
        do {
            double xCoordOffset = randomCoordOffset();
            double yCoordOffset = randomCoordOffset(xCoordOffset <= 0d);
            lineSegment = new LineSegment(point, new Point(nonInnerPoint.getX() + xCoordOffset, nonInnerPoint.getY() + yCoordOffset));
        } while (vertices.stream().anyMatch(lineSegment::containsExclusive));
        return lineSegment;
    }

    private BinaryOperator maxPoint() {
        return (point1, point2) -> new Point(Math.max(point1.getX(), point2.getX()), Math.max(point1.getY(), point2.getY()));
    }

    private double randomCoordOffset(boolean forcePositive) {
        return forcePositive ? ((Math.random() * RANDOM_COORD_GENERATION_RANGE) / 2) + 1 : randomCoordOffset();
    }

    private double randomCoordOffset() {
        double random = (Math.random() - 0.5d) * RANDOM_COORD_GENERATION_RANGE;
        return random >= 0d ? random + 1 : random;
    }

    private List doubleVertices() {
        return CollectionUtil.concat(vertices, vertices);
    }

    private static final class JsonDecoder implements JsonValueDecoder {

        @Override
        public Polygon decode(JsonValue jsonValue) {
            return JsonValueDecoder.extractValue("vertices")
                .andThen(JsonValueDecoder.toValueStream())
                .andThen(stream -> stream.map(pointValue -> Point.jsonDecoder().decode(pointValue)).collect(Collectors.toList()))
                .andThen(Polygon::new)
                .apply(jsonValue);
        }

        protected JsonValue toValue(Polygon polygon) {
            return Json.createObjectBuilder()
                .add("vertices", createGeoPointArray(polygon.getVertices()))
                .build();
        }

        private static JsonArray createGeoPointArray(Collection points) {
            JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
            points.forEach(point -> arrayBuilder.add(point.createValue()));
            return arrayBuilder.build();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy