package mil.nga.giat.mage.observation; import android.content.res.Resources; import android.location.Location; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import android.view.View; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import java.util.List; import mil.nga.giat.mage.R; import mil.nga.giat.mage.sdk.datastore.observation.Observation; import mil.nga.giat.mage.sdk.utils.GeometryUtility; import mil.nga.sf.CompoundCurve; import mil.nga.sf.Geometry; import mil.nga.sf.GeometryCollection; import mil.nga.sf.GeometryEnvelope; import mil.nga.sf.GeometryType; import mil.nga.sf.LineString; import mil.nga.sf.MultiLineString; import mil.nga.sf.MultiPoint; import mil.nga.sf.MultiPolygon; import mil.nga.sf.Point; import mil.nga.sf.Polygon; import mil.nga.sf.PolyhedralSurface; import mil.nga.sf.proj.ProjectionConstants; import mil.nga.sf.util.GeometryEnvelopeBuilder; import mil.nga.sf.util.GeometryUtils; /** * Observation location containing the geometry and location collection information * * @author osbornb */ public class ObservationLocation implements Parcelable { /** * Provider for manually set locations */ public static final String MANUAL_PROVIDER = "manual"; /** * Default camera single point zoom level */ private static final int DEFAULT_POINT_ZOOM = 17; /** * Default camera padding percentage when building a camera update for geometry zoom */ private static final float DEFAULT_PADDING_PERCENTAGE = 1/5f; /** * Parcelable CREATOR implementation */ public static final Parcelable.Creator<ObservationLocation> CREATOR = new Parcelable.Creator<ObservationLocation>() { public ObservationLocation createFromParcel(Parcel source) { return new ObservationLocation(source); } public ObservationLocation[] newArray(int size) { return new ObservationLocation[size]; } }; /** * Location geometry such as a point, linestring, polygon, etc */ private Geometry geometry; /** * {@link Location#getAccuracy()} */ private Float accuracy; /** * {@link Location#getProvider()} or {@link #MANUAL_PROVIDER} */ private String provider; /** * {@link Location#getTime()} */ private long time = 0; /** * {@link Location#getElapsedRealtimeNanos()} */ private long elapsedRealtimeNanos = 0; /** * Default constructor */ public ObservationLocation() { } /** * Constructor with Android Location * * @param location collected location */ public ObservationLocation(Location location) { setGeometry(location); setAccuracy(location.getAccuracy()); setProvider(location.getProvider()); setTime(location.getTime()); setElapsedRealtimeNanos(location.getElapsedRealtimeNanos()); } public ObservationLocation(Observation observation) { setGeometry(observation.getGeometry()); setProvider(observation.getProvider()); setAccuracy(observation.getAccuracy()); } /** * Constructor with geometry * * @param geometry geometry */ public ObservationLocation(Geometry geometry) { this(null, geometry); } /** * Constructor with specified provider and geometry * * @param provider provider * @param geometry geometry */ public ObservationLocation(String provider, Geometry geometry) { setProvider(provider); setGeometry(geometry); } /** * Constructor with {@link LatLng} * * @param latLng lat lng point */ public ObservationLocation(LatLng latLng) { this(null, latLng); } /** * Constructor with specified provider and {@link LatLng} * * @param provider provider * @param latLng lat lng point */ public ObservationLocation(String provider, LatLng latLng) { this(provider, new Point(latLng.longitude, latLng.latitude)); } /** * Constructor to copy an existing Observation Location * * @param location observation location to copy */ public ObservationLocation(ObservationLocation location) { setGeometry(location.getGeometry()); setAccuracy(location.getAccuracy()); setProvider(location.getProvider()); setTime(location.getTime()); setElapsedRealtimeNanos(location.getElapsedRealtimeNanos()); } /** * Constructor for Parcelable implementation * * @param in parecel object */ public ObservationLocation(Parcel in) { byte[] geometryBytes = new byte[in.readInt()]; in.readByteArray(geometryBytes); geometry = GeometryUtility.toGeometry(geometryBytes); accuracy = (Float) in.readValue(Float.class.getClassLoader()); provider = in.readString(); time = in.readLong(); elapsedRealtimeNanos = in.readLong(); } /** * Get the geometry * * @return geometry */ public Geometry getGeometry() { return geometry; } /** * Set the geometry by Android location * * @param location collected location */ public void setGeometry(Location location) { this.geometry = new Point(location.getLongitude(), location.getLatitude()); } /** * Set the geometry * * @param geometry geometry */ public void setGeometry(Geometry geometry) { this.geometry = geometry; } /** * Get the accuracy * * @return accuracy */ public Float getAccuracy() { return accuracy; } /** * Set the accuracy * * @param accuracy accuracy */ public void setAccuracy(Float accuracy) { this.accuracy = accuracy; } /** * Get the provider * * @return provider */ public String getProvider() { return provider; } /** * Set the provider * * @param provider provider */ public void setProvider(String provider) { this.provider = provider; } /** * Get the time * * @return time */ public long getTime() { return time; } /** * Set the time * * @param time time */ public void setTime(long time) { this.time = time; } /** * Get the elapsed realtime nanos time * * @return elapsed realtime nanos time */ public long getElapsedRealtimeNanos() { return elapsedRealtimeNanos; } /** * Set the elapsed realtime nanos time * * @param elapsedRealtimeNanos elapsed realtime nanos time */ private void setElapsedRealtimeNanos(long elapsedRealtimeNanos) { this.elapsedRealtimeNanos = elapsedRealtimeNanos; } public boolean isManualProvider() { return MANUAL_PROVIDER.equals(provider); } public CircleOptions getAccuracyCircle(Resources resources) { CircleOptions circle = null; if (geometry.getGeometryType() == GeometryType.POINT && !isManualProvider() && accuracy != null) { circle = new CircleOptions() .fillColor(resources.getColor(R.color.accuracy_circle_fill)) .strokeColor(resources.getColor(R.color.accuracy_circle_stroke)) .strokeWidth(2) .center(getFirstLatLng()) .radius(accuracy); } return circle; } /** * Get the first point from the geometry * * @return point */ private Point getFirstPoint() { return getFirstPoint(geometry); } /** * Get the first point in the geometry * * @param geometry geometry * @return first point */ private Point getFirstPoint(Geometry geometry) { Point point = null; GeometryType type = geometry.getGeometryType(); switch (type) { case POINT: point = (Point) geometry; break; case MULTIPOINT: point = ((MultiPoint) geometry).getPoints().get(0); break; case LINESTRING: case CIRCULARSTRING: point = ((LineString) geometry).getPoints().get(0); break; case MULTILINESTRING: point = getFirstPoint(((MultiLineString) geometry).getLineStrings().get(0)); break; case COMPOUNDCURVE: point = getFirstPoint(((CompoundCurve) geometry).getLineStrings().get(0)); break; case POLYGON: case TRIANGLE: point = getFirstPoint(((Polygon) geometry).getRings().get(0)); break; case MULTIPOLYGON: point = getFirstPoint(((MultiPolygon) geometry).getPolygons().get(0)); break; case POLYHEDRALSURFACE: case TIN: point = getFirstPoint(((PolyhedralSurface) geometry).getPolygons().get(0)); break; case GEOMETRYCOLLECTION: @SuppressWarnings("unchecked") GeometryCollection<Geometry> geomCollection = (GeometryCollection<Geometry>) geometry; point = getFirstPoint(geomCollection.getGeometries().get(0)); break; default: Log.e(this.getClass().getSimpleName(), "Unsupported Geometry Type: " + type); } return point; } /** * Get the first point from the geometry as a {@link LatLng} * * @return lat lng */ private LatLng getFirstLatLng() { Point point = getFirstPoint(); return new LatLng(point.getY(), point.getX()); } /** * Get the geometry centroid * * @return centroid point */ public Point getCentroid() { return GeometryUtils.getCentroid(geometry); } /** * Get the geometry centroid as a LatLng * * @return centroid point lat lng */ public LatLng getCentroidLatLng() { Point point = GeometryUtils.getCentroid(geometry); return new LatLng(point.getY(), point.getX()); } /** * Get a geometry envelope that includes the entire geometry * * @return geometry envelope */ private GeometryEnvelope getGeometryEnvelope() { Geometry geometryCopy = geometry.copy(); GeometryUtils.minimizeGeometry(geometryCopy, ProjectionConstants.WGS84_HALF_WORLD_LON_WIDTH); return GeometryEnvelopeBuilder.buildEnvelope(geometryCopy); } /** * Get the camera update for zooming to the geometry * * @return camera update */ public CameraUpdate getCameraUpdate(View view) { return getCameraUpdate(view, DEFAULT_PADDING_PERCENTAGE); } /** * Get the camera update for zooming to the geometry * * @param paddingPercentage padding percentage * @return camera update */ private CameraUpdate getCameraUpdate(View view, float paddingPercentage) { return getCameraUpdate(view, false, DEFAULT_POINT_ZOOM, paddingPercentage); } CameraUpdate getCameraUpdate(View view, boolean zoomToAccuracy, float paddingPercentage) { return getCameraUpdate(view, zoomToAccuracy, DEFAULT_POINT_ZOOM, paddingPercentage); } /** * Get the camera update for zooming to the geometry * * @param paddingPercentage padding percentage * @return camera update */ private CameraUpdate getCameraUpdate(View view, boolean zoomToAccuracy, int pointZoomDefault, float paddingPercentage) { CameraUpdate update; if (geometry.getGeometryType() == GeometryType.POINT) { LatLng latLng = getFirstLatLng(); if (zoomToAccuracy && !isManualProvider() && accuracy != null) { double latitudePadding = (accuracy / 111325); LatLngBounds bounds = new LatLngBounds( new LatLng(latLng.latitude - latitudePadding, latLng.longitude), new LatLng(latLng.latitude + latitudePadding, latLng.longitude)); int padding = 0; if (view != null) { int minDimension = Math.min(view.getWidth(), view.getHeight()); padding = (int) Math.floor(minDimension * paddingPercentage); } update = CameraUpdateFactory.newLatLngBounds(bounds, padding); } else { update = CameraUpdateFactory.newLatLngZoom(latLng, pointZoomDefault); } } else { GeometryEnvelope envelope = getGeometryEnvelope(); final LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); boundsBuilder.include(new LatLng(envelope.getMinY(), envelope.getMinX())); boundsBuilder.include(new LatLng(envelope.getMinY(), envelope.getMaxX())); boundsBuilder.include(new LatLng(envelope.getMaxY(), envelope.getMinX())); boundsBuilder.include(new LatLng(envelope.getMaxY(), envelope.getMaxX())); int padding = 0; if (view != null) { int minDimension = Math.min(view.getWidth(), view.getHeight()); padding = (int) Math.floor(minDimension * paddingPercentage); } update = CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), padding); } return update; } /** * {@inheritDoc} */ @Override public int describeContents() { return 0; } /** * {@inheritDoc} */ @Override public void writeToParcel(Parcel out, int flags) { byte[] geometryBytes = GeometryUtility.toGeometryBytes(geometry); out.writeInt(geometryBytes.length); out.writeByteArray(geometryBytes); out.writeValue(accuracy); out.writeString(provider); out.writeLong(time); out.writeLong(elapsedRealtimeNanos); } /** * Check if the points form a rectangle * * @param points points * @return true if a rectangle */ public static boolean checkIfRectangle(List<Point> points) { return checkIfRectangleAndFindSide(points) != null; } /** * Check if the points form a rectangle and return if the side one has the same x * * @param points points * @return null if not a rectangle, true if same x side 1, false if same y side 1 */ public static Boolean checkIfRectangleAndFindSide(List<Point> points) { Boolean sameXSide1 = null; int size = points.size(); if (size == 4 || size == 5) { Point point1 = points.get(0); Point lastPoint = points.get(points.size() - 1); boolean closed = point1.getX() == lastPoint.getX() && point1.getY() == lastPoint.getY(); if ((closed && size == 5) || (!closed && size == 4)) { Point point2 = points.get(1); Point point3 = points.get(2); Point point4 = points.get(3); if (point1.getX() == point2.getX() && point2.getY() == point3.getY()) { if (point1.getY() == point4.getY() && point3.getX() == point4.getX()) { sameXSide1 = true; } } else if (point1.getY() == point2.getY() && point2.getX() == point3.getX()) { if (point1.getX() == point4.getX() && point3.getY() == point4.getY()) { sameXSide1 = false; } } } } return sameXSide1; } }