/* * Java GPX Library (@__identifier__@). * Copyright (c) @__year__@ Franz Wilhelmstötter * * 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. * * Author: * Franz Wilhelmstötter ([email protected]) */ package io.jenetics.jpx; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.String.format; import static java.util.Objects.hash; import static java.util.Objects.requireNonNull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Objects; import java.util.stream.Collector; /** * Two lat/lon pairs defining the extent of an element. * * @author <a href="mailto:[email protected]">Franz Wilhelmstötter</a> * @version 1.6 * @since 1.0 */ public final class Bounds implements Serializable { private static final long serialVersionUID = 2L; private final Latitude _minLatitude; private final Longitude _minLongitude; private final Latitude _maxLatitude; private final Longitude _maxLongitude; /** * Create a new {@code Bounds} object with the given extent. * * @param minLatitude the minimum latitude * @param minLongitude the minimum longitude * @param maxLatitude the maximum latitude * @param maxLongitude the maximum longitude * @throws NullPointerException if one of the arguments is {@code null} */ private Bounds( final Latitude minLatitude, final Longitude minLongitude, final Latitude maxLatitude, final Longitude maxLongitude ) { _minLatitude = requireNonNull(minLatitude); _minLongitude = requireNonNull(minLongitude); _maxLatitude = requireNonNull(maxLatitude); _maxLongitude = requireNonNull(maxLongitude); } /** * Return the minimum latitude. * * @return the minimum latitude */ public Latitude getMinLatitude() { return _minLatitude; } /** * Return the minimum longitude. * * @return the minimum longitude */ public Longitude getMinLongitude() { return _minLongitude; } /** * Return the maximum latitude. * * @return the maximum latitude */ public Latitude getMaxLatitude() { return _maxLatitude; } /** * Return the maximum longitude * * @return the maximum longitude */ public Longitude getMaxLongitude() { return _maxLongitude; } @Override public int hashCode() { return hash(_minLatitude, _minLongitude, _maxLatitude, _maxLongitude); } @Override public boolean equals(final Object obj) { return obj == this || obj instanceof Bounds && Objects.equals(((Bounds)obj)._minLatitude, _minLatitude) && Objects.equals(((Bounds)obj)._minLongitude, _minLongitude) && Objects.equals(((Bounds)obj)._maxLatitude, _maxLatitude) && Objects.equals(((Bounds)obj)._maxLongitude, _maxLongitude); } @Override public String toString() { return format( "[%s, %s][%s, %s]", _minLatitude, _minLongitude, _maxLatitude, _maxLongitude ); } /** * Return a collector which calculates the bounds of a given way-point * stream. The following example shows how to calculate the bounds of all * track-points of a given GPX object. * * <pre>{@code * final Bounds bounds = gpx.tracks() * .flatMap(Track::segments) * .flatMap(TrackSegment::points) * .collect(Bounds.toBounds()); * }</pre> * * If the collecting way-point stream is empty, the collected {@code Bounds} * object is {@code null}. * * @since 1.6 * * @param <P> The actual point type * @return a new bounds collector */ public static <P extends Point> Collector<P, ?, Bounds> toBounds() { return Collector.of( () -> { final double[] a = new double[4]; a[0] = Double.MAX_VALUE; a[1] = Double.MAX_VALUE; a[2] = -Double.MAX_VALUE; a[3] = -Double.MAX_VALUE; return a; }, (a, b) -> { a[0] = min(b.getLatitude().doubleValue(), a[0]); a[1] = min(b.getLongitude().doubleValue(), a[1]); a[2] = max(b.getLatitude().doubleValue(), a[2]); a[3] = max(b.getLongitude().doubleValue(), a[3]); }, (a, b) -> { a[0] = min(a[0], b[0]); a[1] = min(a[1], b[1]); a[2] = max(a[2], b[2]); a[3] = max(a[3], b[3]); return a; }, a -> a[0] == Double.MAX_VALUE ? null : Bounds.of(a[0], a[1], a[2], a[3]) ); } /* ************************************************************************* * Static object creation methods * ************************************************************************/ /** * Create a new {@code Bounds} object with the given extent. * * @param minLatitude the minimum latitude * @param minLongitude the minimum longitude * @param maxLatitude the maximum latitude * @param maxLongitude the maximum longitude * @return a new {@code Bounds} object with the given extent * @throws NullPointerException if one of the arguments is {@code null} */ public static Bounds of( final Latitude minLatitude, final Longitude minLongitude, final Latitude maxLatitude, final Longitude maxLongitude ) { return new Bounds(minLatitude, minLongitude, maxLatitude, maxLongitude); } /** * Create a new {@code Bounds} object with the given extent. * * @param minLatitudeDegree the minimum latitude * @param minLongitudeDegree the minimum longitude * @param maxLatitudeDegree the maximum latitude * @param maxLongitudeDegree the maximum longitude * @return a new {@code Bounds} object with the given extent * @throws IllegalArgumentException if the latitude values are not within * the range of {@code [-90..90]} * @throws IllegalArgumentException if the longitudes value are not within * the range of {@code [-180..180]} */ public static Bounds of( final double minLatitudeDegree, final double minLongitudeDegree, final double maxLatitudeDegree, final double maxLongitudeDegree ) { return new Bounds( Latitude.ofDegrees(minLatitudeDegree), Longitude.ofDegrees(minLongitudeDegree), Latitude.ofDegrees(maxLatitudeDegree), Longitude.ofDegrees(maxLongitudeDegree) ); } /* ************************************************************************* * Java object serialization * ************************************************************************/ private Object writeReplace() { return new Serial(Serial.BOUNDS, this); } private void readObject(final ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Serialization proxy required."); } void write(final DataOutput out) throws IOException { out.writeDouble(_minLatitude.toDegrees()); out.writeDouble(_minLongitude.toDegrees()); out.writeDouble(_maxLatitude.toDegrees()); out.writeDouble(_maxLongitude.toDegrees()); } static Bounds read(final DataInput in) throws IOException { return Bounds.of( in.readDouble(), in.readDouble(), in.readDouble(), in.readDouble() ); } /* ************************************************************************* * XML stream object serialization * ************************************************************************/ static final XMLWriter<Bounds> WRITER = XMLWriter.elem("bounds", XMLWriter.attr("minlat").map(Bounds::getMinLatitude), XMLWriter.attr("minlon").map(Bounds::getMinLongitude), XMLWriter.attr("maxlat").map(Bounds::getMaxLatitude), XMLWriter.attr("maxlon").map(Bounds::getMaxLongitude) ); static final XMLReader<Bounds> READER = XMLReader.elem( v -> Bounds.of( (Latitude)v[0], (Longitude)v[1], (Latitude)v[2], (Longitude)v[3] ), "bounds", XMLReader.attr("minlat").map(Latitude::parse), XMLReader.attr("minlon").map(Longitude::parse), XMLReader.attr("maxlat").map(Latitude::parse), XMLReader.attr("maxlon").map(Longitude::parse) ); }