package cgeo.geocaching.location; import cgeo.geocaching.models.ICoordinates; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; public final class Viewport { @NonNull public final Geopoint center; @NonNull public final Geopoint bottomLeft; @NonNull public final Geopoint topRight; public Viewport(@NonNull final ICoordinates point1, @NonNull final ICoordinates point2) { final Geopoint gp1 = point1.getCoords(); final Geopoint gp2 = point2.getCoords(); this.bottomLeft = new Geopoint(Math.min(gp1.getLatitude(), gp2.getLatitude()), Math.min(gp1.getLongitude(), gp2.getLongitude())); this.topRight = new Geopoint(Math.max(gp1.getLatitude(), gp2.getLatitude()), Math.max(gp1.getLongitude(), gp2.getLongitude())); this.center = new Geopoint((gp1.getLatitude() + gp2.getLatitude()) / 2, (gp1.getLongitude() + gp2.getLongitude()) / 2); } public Viewport(@NonNull final ICoordinates center, final double latSpan, final double lonSpan) { this.center = center.getCoords(); final double centerLat = this.center.getLatitude(); final double centerLon = this.center.getLongitude(); final double latHalfSpan = Math.abs(latSpan) / 2; final double lonHalfSpan = Math.abs(lonSpan) / 2; bottomLeft = new Geopoint(centerLat - latHalfSpan, centerLon - lonHalfSpan); topRight = new Geopoint(centerLat + latHalfSpan, centerLon + lonHalfSpan); } public double getLatitudeMin() { return bottomLeft.getLatitude(); } public double getLatitudeMax() { return topRight.getLatitude(); } public double getLongitudeMin() { return bottomLeft.getLongitude(); } public double getLongitudeMax() { return topRight.getLongitude(); } @NonNull public Geopoint getCenter() { return center; } public double getLatitudeSpan() { return getLatitudeMax() - getLatitudeMin(); } public double getLongitudeSpan() { return getLongitudeMax() - getLongitudeMin(); } /** * Check whether a point is contained in this viewport. * * @param point * the coordinates to check * @return true if the point is contained in this viewport, false otherwise or if the point contains no coordinates */ public boolean contains(@NonNull final ICoordinates point) { final Geopoint coords = point.getCoords(); return coords != null && coords.getLongitudeE6() >= bottomLeft.getLongitudeE6() && coords.getLongitudeE6() <= topRight.getLongitudeE6() && coords.getLatitudeE6() >= bottomLeft.getLatitudeE6() && coords.getLatitudeE6() <= topRight.getLatitudeE6(); } /** * Count the number of points present in the viewport. * * @param points a collection of (possibly null) points * @return the number of non-null points in the viewport */ public int count(@NonNull final Collection<? extends ICoordinates> points) { int total = 0; for (final ICoordinates point: points) { if (point != null && contains(point)) { total += 1; } } return total; } /** * Filter return the points present in the viewport. * * @param points * a collection of (possibly null) points * @return a new collection containing the points in the viewport */ public <T extends ICoordinates> Collection<T> filter(@NonNull final Collection<T> points) { final Collection<T> result = new ArrayList<>(); for (final T point : points) { if (point != null && contains(point)) { result.add(point); } } return result; } @Override public String toString() { return "(" + bottomLeft.toString() + "," + topRight.toString() + ")"; } /** * Check whether another viewport is fully included into the current one. * * @param vp * the other viewport * @return true if the viewport is fully included into this one, false otherwise */ public boolean includes(@NonNull final Viewport vp) { return contains(vp.bottomLeft) && contains(vp.topRight); } /** * Return the "where" part of the string appropriate for a SQL query. * * @param dbTable * the database table to use as prefix, or null if no prefix is required * @return the string without the "where" keyword */ @NonNull public StringBuilder sqlWhere(@Nullable final String dbTable) { final String prefix = dbTable == null ? "" : (dbTable + "."); return new StringBuilder(prefix).append("latitude >= ").append(getLatitudeMin()).append(" and ") .append(prefix).append("latitude <= ").append(getLatitudeMax()).append(" and ") .append(prefix).append("longitude >= ").append(getLongitudeMin()).append(" and ") .append(prefix).append("longitude <= ").append(getLongitudeMax()); } /** * Return a widened or shrunk viewport. * * @param factor * multiplicative factor for the latitude and longitude span (> 1 to widen, < 1 to shrink) * @return a widened or shrunk viewport */ @NonNull public Viewport resize(final double factor) { return new Viewport(getCenter(), getLatitudeSpan() * factor, getLongitudeSpan() * factor); } /** * Return the smallest viewport containing all the given points. * * @param points * a set of points. Point with null coordinates (or null themselves) will be ignored * @return the smallest viewport containing the non-null coordinates, or null if no coordinates are non-null */ @Nullable public static Viewport containing(final Collection<? extends ICoordinates> points) { boolean valid = false; double latMin = Double.MAX_VALUE; double latMax = -Double.MAX_VALUE; double lonMin = Double.MAX_VALUE; double lonMax = -Double.MAX_VALUE; for (final ICoordinates point : points) { if (point != null) { final Geopoint coords = point.getCoords(); if (coords != null) { valid = true; final double latitude = coords.getLatitude(); final double longitude = coords.getLongitude(); latMin = Math.min(latMin, latitude); latMax = Math.max(latMax, latitude); lonMin = Math.min(lonMin, longitude); lonMax = Math.max(lonMax, longitude); } } } if (!valid) { return null; } return new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax)); } @Override public boolean equals(final Object other) { if (this == other) { return true; } if (!(other instanceof Viewport)) { return false; } final Viewport vp = (Viewport) other; return bottomLeft.equals(vp.bottomLeft) && topRight.equals(vp.topRight); } @Override public int hashCode() { return bottomLeft.hashCode() ^ topRight.hashCode(); } }