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

net.sf.eBusx.geo.GeoPolygon Maven / Gradle / Ivy

//
// Copyright 2021 Charles W. Rapp
//
// Licensed 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 net.sf.eBusx.geo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import net.sf.eBus.util.Validator;

/**
 * A polygon is one or more linear rings and a linear ring is a
 * {@link LineString} meeting the following constraints:
 * 
    *
  • * Has four or more closed positions. *
  • *
  • * First and last positions are equivalent, and * must contain identical values; their * representation should also be identical. *
  • *
  • * Is a boundary of a surface or the boundary of a hole in a surface. *
  • *
  • * Must follow the right-hand rule with * respect to the area it bounds, i.e., exterior rings are * counterclockwise, and holes are clockwise. *
  • *
* (Note: GeoJSON Format Specification (June, * 2008) did not discuss linear ring winding order. For * backwards compatibility, parsers should not * reject Polygons that do not follow the right-hand rule.) *

* Although a linear ring is not explicitly represented as a * GeoJSON geometry type, it leads to a canonical formulation of * the Polygon geometry type definition as follows: *

*
    *
  • * For type "Polygon", the "coordinates" member * must be an array of linear rings. *
  • *
  • * For Polygons with more than one of these rings, the first * must be the exterior ring, and any others * must be interior rings. The exterior ring * bounds the surface, and the interior rings (if present) * bound holes within the surface. *
  • *
* Note: {@code GeoPolygon} does not * enforce the linear rings requirement. This means that an * invalid set of {@link LineString}s which do not form linear * rings may be entered. If you wish to transmit a random list of * line strings please use {@link GeoLineString} instead. * * @see GeoMultiLineString * * @author Charles W. Rapp */ public final class GeoPolygon extends GeoObject { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * Serialization version identifier. */ private static final long serialVersionUID = 0x050700L; /** * A linear ring must have at least {@value} positions. */ private static final int MIN_RING_SIZE = 4; private static final String LINEAR_RINGS = "linearRings"; //----------------------------------------------------------- // Locals. // /** * Array of one or more linear rings defining polygon. */ public final LineString[] linearRings; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Constructor is private because a polygon instance may only * be created using a builder. * @param builder builder containing valid polygon * configuration. */ private GeoPolygon(final Builder builder) { super (builder); this.linearRings = builder.linearRings(); } // end of GeoPolygon(Builder) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Object Method Overrides. // /** * Returns text containing linear rings defining the polygon. * @return polygon as human-readable text. */ @Override public String toString() { final int numRings = linearRings.length; String sep; int i; final StringBuilder retval = new StringBuilder(); retval.append('[').append(super.toString()) .append(", rings={"); for (i = 0, sep = ""; i < numRings; ++i, sep = ", ") { retval.append(sep).append(linearRings[i]); } retval.append("}]"); return (retval.toString()); } // end of toString() /** * Returns {@code true} if {@code o} is a * non-{@code nuull GeoPolygon} with a lineear ring array * equaling {@code this GeoPolygo} linear ring array. * Otherwise returns {@code false}. * @param o comparison object. * @return {@code true} if {@code o} equals {@code this} * object. */ @Override public boolean equals(final Object o) { boolean retcode = super.equals(o); if (!retcode && o instanceof GeoPolygon) { retcode = Arrays.equals( linearRings, ((GeoPolygon) o).linearRings); } return (retcode); } // end of equals(Object) /** * Returns hash code based on contained linear rings. * @return linear rings hash code. */ @Override public int hashCode() { return (Objects.hash((Object[]) linearRings)); } // end of hashCode() // // end of Object Method Overrides. //----------------------------------------------------------- /** * Returns a new GeoJSON polygon builder instance. * @return new polygon builder instance. */ public static Builder builder() { return (new Builder()); } // end of builder() //--------------------------------------------------------------- // Inner classes. // /** * Builder class used to create {@code GeoPolygon} instances. * A {@code Builder} instance is obtained by calling * {@link #builder()} method. */ public static final class Builder extends GeoObject.GeoBuilder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // private final List mRings; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private Builder() { super (GeoPolygon.class, GeoType.POLYGON); mRings = new ArrayList<>(); } // end of Builder() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Builder Method Overrides. // /** * Check if: *
    *
  1. * at least one linear ring was provided, *
  2. *
  3. * each linear ring has at least four positions, and *
  4. *
  5. * each linear ring is * {@link LineString#isClosed() is closed}. *
  6. *
* Note: does not validate * if rings are properly ordered or follow the * "right-hand rule" as mentioned in * GeoJSON specification. * @param problems append detected problems to this * validator. * @return {@code problems} to allow for method chaining. */ @Override protected Validator validate(final Validator problems) { int i = 0; super.validate(problems) .requireTrue(!mRings.isEmpty(), LINEAR_RINGS, "linerRings is empty"); // Validate each line string has least four positions // and is closed. for (LineString ls : mRings) { problems.requireTrue(ls.positions.length > 3, LINEAR_RINGS, "ring[" + i + "] has < 4 positions") .requireTrue(ls.isClosed(), LINEAR_RINGS, "ring[" + i + "] is not closed"); ++i; } return (problems); } // end of validate(Validator) /** * Returns a new {@code GeoPolygon} instance created * from this builder's validated settings. * @return new GeoJSON polygon instance. */ @Override protected GeoPolygon buildImpl() { return (new GeoPolygon(this)); } // end of buildImpl() // // end of Builder Method Overrides. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns linear rings list as an array. * @return linear rings array. */ private LineString[] linearRings() { return ( mRings.toArray(new LineString[mRings.size()])); } // end of linearRings() // // end of Get Methods. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Appends linear ring to rings list. * @param ring add this linear ring. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code ring} is {@code null}. * * @see #addAll(LineString[]) * @see #addAll(Collection) * @see #linearRings(LineString[]) */ public Builder add(final LineString ring) { mRings.add(Objects.requireNonNull(ring, "ring is null")); return (this); } // end of add(LineString) /** * Appends linear rings array to linear rings list. * @param rings append all rings to list. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code rings} is {@code null}. * * @see #add(LineString) * @see #addAll(Collection) * @see #linearRings(LineString[]) */ public Builder addAll(final LineString[] rings) { if (rings != null) { Collections.addAll(mRings, rings); } return (this); } // end of addAll(LineString[]) /** * Appends linear rings collection to linear rings list. * @param rings append all rings to list. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code rings} is {@code null}. * * @see #add(LineString) * @see #addAll(LineString[]) * @see #linearRings(LineString[]) */ public Builder addAll(final Collection rings) { if (rings != null && !rings.isEmpty()) { mRings.addAll(rings); } return (this); } // end of addAll(Collection<>) /** * Sets linear rings list to the given linear ring * values. Note that all previously added rings are * removed from the list prior to adding these rings but * only after verifying that the linear rings * array is not {@code null}. * @param rings set linear rings list to this array. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code rings} is {@code null}. If this * exception is thrown then linear rings list is * unchanged. * * @see #add(LineString) * @see #addAll(LineString[]) * @see #addAll(Collection) */ public Builder linearRings(final LineString[] rings) { Objects.requireNonNull(rings, "rings is null"); mRings.clear(); Collections.addAll(mRings, rings); return (this); } // end of linearRings(LineString[]) // // end of Set Methods. //------------------------------------------------------- } // end of class Builder } // end of class GeoPolygon




© 2015 - 2024 Weber Informatics LLC | Privacy Policy