net.sf.eBusx.geo.Position 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.math.BigDecimal;
import java.util.Objects;
import javax.annotation.Nullable;
import net.sf.eBus.messages.EField;
import net.sf.eBus.util.Validator;
import static net.sf.eBusx.geo.GeoObject.validateLatitude;
import static net.sf.eBusx.geo.GeoObject.validateLongitude;
/**
* A position is the fundamental geometry construct. The
* "coordinates" member of a Geometry object is composed of
* either:
*
* -
* one position in the case of a Point geometry,
*
* -
* an array of positions in the case of a LineString or
* MultiPoint geometry,
*
* -
* an array of LineString or linear ring coordinates in the
* case of a Polygon or MultiLineString geometry, or
*
* -
* an array of Polygon coordinates in the case of a
* MultiPolygon geometry.
*
*
* A position is an array of numbers. There MUST be two or more
* elements. The first two elements are longitude and latitude,
* or easting and northing, precisely in that order and using
* decimal numbers. Altitude or elevation may be
* included as an optional third element.
*
* Implementations should not extend positions
* beyond three elements because the semantics of extra elements
* are unspecified and ambiguous. Historically, some
* implementations have used a fourth element to carry a linear
* referencing measure (sometimes denoted as "M") or a numerical
* timestamp, but in most situations a parser will not be able to
* properly interpret these values. The interpretation and
* meaning of additional elements is beyond the scope of this
* specification, and additional elements may be ignored
* by parsers.
*
*
* @author Charles W. Rapp
*/
public final class Position
extends EField
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
/**
* Serialization version identifier.
*/
private static final long serialVersionUID = 0x000100L;
//-----------------------------------------------------------
// Locals.
//
/**
* Latitude coordinate in degrees (North of equator is
* positive) using the standard WGS84 projection. Some
* applications may not accept latitudes above/below ±85
* degrees for some projections.
*/
public final BigDecimal latitude;
/**
* Longitude coordinate in degrees (East of Greenwich is
* positive) using the standard WGS84 projection. Note that
* geographic poles will be exactly at latitude ±90 degrees
* but in that case the longitude will be set to an arbitrary
* value within this range.
*/
public final BigDecimal longitude;
/**
* Elevation is an optional third parameter and may be set to
* {@code null}.
*/
@Nullable
public final BigDecimal elevation;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new GeoJSON position based on the builder
* settings.
* @param builder contains position latitude and longitude.
*/
private Position(final Builder builder)
{
super (builder);
this.latitude = builder.mLatitude;
this.longitude = builder.mLongitude;
this.elevation = builder.mElevation;
} // end of Position(Builder)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Object Method Overrides.
//
/**
* Returns {@code true} if either {@code o} is the same
* {@code Position} instance as {@code this} or is a
* non-{@code null Position} instance with the same latitude
* and longitude as {@code this Position}.
* @param o comparison object.
* @return if {@code o} is a non-{@code null Position}
* instance with the same latitude and longitude as
* {@code this Position}.
*/
@Override
public boolean equals(final Object o)
{
boolean retcode = (this == o);
if (!retcode && o instanceof Position)
{
final Position pos = (Position) o;
retcode = (latitude.equals(pos.latitude) &&
longitude.equals(pos.longitude) &&
Objects.equals(elevation, pos.elevation));
}
return (retcode);
} // end of equals(Object)
/**
* Returns a hash of the latitude and longitude.
* @return hash of the GeoJSON position.
*/
@Override
public int hashCode()
{
return (Objects.hash(latitude, longitude, elevation));
} // end of hashCode()
/**
* Returns human-readable text containing the position
* latitude and longitude.
* @return GeoJSON position as human-readable text.
*/
@Override
public String toString()
{
final StringBuilder retval = new StringBuilder();
retval.append("[lat=").append(latitude.toPlainString())
.append(", long=").append(longitude.toPlainString());
if (elevation != null)
{
retval.append(", elev=")
.append(elevation.toPlainString());
}
retval.append("]");
return (retval.toString());
} // end of toString()
//
// end of Object Method Overrides.
//-----------------------------------------------------------
/**
* Returns a new GeoJSON position builder instance.
* @return new {@link Builder} instance.
*/
public static Builder builder()
{
return (new Builder());
} // end of builder()
//---------------------------------------------------------------
// Inner classes.
//
/**
* {@code Position} instances may be created only by using
* a {@code Builder} instance. A {@code Builder} instance is
* obtained by calling {@link #builder()} which returns a
* newly instantiated builder instance. Once obtained,
* call the setter methods for each field and
* {@link #build()} to create a {@code Position} instance.
*/
public static final class Builder
extends EField.Builder
{
//-----------------------------------------------------------
// Member data.
//
//-------------------------------------------------------
// Locals.
//
private BigDecimal mLatitude;
private BigDecimal mLongitude;
private BigDecimal mElevation;
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
private Builder()
{
super (Position.class);
} // end of Builder()
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// Builder Method Overrides.
//
/**
* Checks if latitude and longitude are set. Elevation
* may be null.
* @param problems add each detected problem to this
* validator.
* @return {@code problems}.
*/
@Override
protected Validator validate(final Validator problems)
{
return (super.validate(problems)
.requireNotNull(mLatitude, "latitude")
.requireNotNull(mLongitude, "longitude"));
} // end of validate(Validator)
/**
* Returns a new position instance based on this
* builder's settings.
* @return new position instance.
*/
@Override
protected Position buildImpl()
{
return (new Position(this));
} // end of buildImpl()
//
// end of Builder Method Overrides.
//-------------------------------------------------------
//-------------------------------------------------------
// Set Methods.
//
/**
* Sets position latitude. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param latitude position latitude.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code position} is {@code null}.
* @throws IllegalArgumentException
* if {@code latitude} <
* {@link GeoObject#MIN_LATITUDE} or >
* {@link GeoObject#MAX_LATITUDE}.
*/
public Builder latitude(final BigDecimal latitude)
{
mLatitude = validateLatitude(latitude);
return (this);
} // end of latitude(BigDecimal)
/**
* Sets position longitude. Returns
* {@code this Builder} instance so that builder method
* calls can be chained.
* @param longitude position longitude.
* @return {@code this Builder} instance.
* @throws NullPointerException
* if {@code longitude} is {@code null}.
* @throws IllegalArgumentException
* if {@code longitude} <
* {@link GeoObject#MIN_LONGITUDE} or >
* {@link GeoObject#MAX_LONGITUDE}.
*/
public Builder longitude(final BigDecimal longitude)
{
mLongitude = validateLongitude(longitude);
return (this);
} // end of longitude(BigDecimal)
/**
* Sets position elevation. Returns
* {@code this Builder} instance so that builder method
* calls can be chained. May be set to {@code null}. All
* values are accepted.
* @param elevation position elevation.
* @return {@code this Builder} instance.
*/
public Builder elevation(final BigDecimal elevation)
{
mElevation = elevation;
return (this);
} // end of elevation(BigDecimal)
//
// end of Set Methods.
//-------------------------------------------------------
} // end of class Builder
} // end of class Position