Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[android] bounds can go over the antimeridian / date line. #11333

Merged
merged 1 commit into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ public class GeometryConstants {
*/
public static final double MIN_LATITUDE = -90;

/**
* This constant represents the latitude span when representing a geolocation.
*
* @since 6.0.0
*/
public static final double LATITUDE_SPAN = 180;

/**
* This constant represents the longitude span when representing a geolocation.
*
* @since 6.0.0
*/
public static final double LONGITUDE_SPAN = 360;

/**
* This constant represents the highest latitude value available to represent a geolocation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public class LatLngBounds implements Parcelable {
* Construct a new LatLngBounds based on its corners, given in NESW
* order.
*
* If eastern longitude is smaller than the western one, bounds will include antimeridian.
* For example, if the NE point is (10, -170) and the SW point is (-10, 170), then bounds will span over 20 degrees
* and cross the antimeridian.
*
* @param northLatitude Northern Latitude
* @param eastLongitude Eastern Longitude
* @param southLatitude Southern Latitude
Expand All @@ -48,10 +52,9 @@ public class LatLngBounds implements Parcelable {
* @return the bounds representing the world
*/
public static LatLngBounds world() {
return new LatLngBounds.Builder()
.include(new LatLng(GeometryConstants.MAX_LATITUDE, GeometryConstants.MAX_LONGITUDE))
.include(new LatLng(GeometryConstants.MIN_LATITUDE, GeometryConstants.MIN_LONGITUDE))
.build();
return LatLngBounds.from(
GeometryConstants.MAX_LATITUDE, GeometryConstants.MAX_LONGITUDE,
GeometryConstants.MIN_LATITUDE, GeometryConstants.MIN_LONGITUDE);
}

/**
Expand All @@ -61,8 +64,21 @@ public static LatLngBounds world() {
* @return LatLng center of this LatLngBounds
*/
public LatLng getCenter() {
return new LatLng((this.latitudeNorth + this.latitudeSouth) / 2,
(this.longitudeEast + this.longitudeWest) / 2);
double latCenter = (this.latitudeNorth + this.latitudeSouth) / 2.0;
double longCenter;

if (this.longitudeEast > this.longitudeWest) {
longCenter = (this.longitudeEast + this.longitudeWest) / 2;
} else {
double halfSpan = (GeometryConstants.LONGITUDE_SPAN + this.longitudeEast - this.longitudeWest) / 2.0;
longCenter = this.longitudeWest + halfSpan;
if (longCenter >= GeometryConstants.MAX_LONGITUDE) {
longCenter = this.longitudeEast - halfSpan;
}
return new LatLng(latCenter, longCenter);
}

return new LatLng(latCenter, longCenter);
}

/**
Expand Down Expand Up @@ -163,10 +179,26 @@ public double getLatitudeSpan() {
* @return Span distance
*/
public double getLongitudeSpan() {
return Math.abs(this.longitudeEast - this.longitudeWest);
double longSpan = Math.abs(this.longitudeEast - this.longitudeWest);
if (this.longitudeEast > this.longitudeWest) {
return longSpan;
}

// shortest span contains antimeridian
return GeometryConstants.LONGITUDE_SPAN - longSpan;
}


static double getLongitudeSpan(final double longEast, final double longWest) {
double longSpan = Math.abs(longEast - longWest);
if (longEast > longWest) {
return longSpan;
}

// shortest span contains antimeridian
return GeometryConstants.LONGITUDE_SPAN - longSpan;
}

/**
* Validate if LatLngBounds is empty, determined if absolute distance is
*
Expand Down Expand Up @@ -196,21 +228,44 @@ public String toString() {
*/
static LatLngBounds fromLatLngs(final List<? extends ILatLng> latLngs) {
double minLat = GeometryConstants.MAX_LATITUDE;
double minLon = GeometryConstants.MAX_LONGITUDE;
double maxLat = GeometryConstants.MIN_LATITUDE;
double maxLon = GeometryConstants.MIN_LONGITUDE;

double eastLon = latLngs.get(0).getLongitude();
double westLon = latLngs.get(1).getLongitude();
double lonSpan = Math.abs(eastLon - westLon);
if (lonSpan < GeometryConstants.LONGITUDE_SPAN / 2) {
if (eastLon < westLon) {
double temp = eastLon;
eastLon = westLon;
westLon = temp;
}
} else {
lonSpan = GeometryConstants.LONGITUDE_SPAN - lonSpan;
if (westLon < eastLon) {
double temp = eastLon;
eastLon = westLon;
westLon = temp;
}
}

for (final ILatLng gp : latLngs) {
final double latitude = gp.getLatitude();
final double longitude = gp.getLongitude();

minLat = Math.min(minLat, latitude);
minLon = Math.min(minLon, longitude);
maxLat = Math.max(maxLat, latitude);
maxLon = Math.max(maxLon, longitude);

final double longitude = gp.getLongitude();
if (!containsLongitude(eastLon, westLon, longitude)) {
final double eastSpan = getLongitudeSpan(longitude, westLon);
final double westSpan = getLongitudeSpan(eastLon, longitude);
if (eastSpan <= westSpan) {
eastLon = longitude;
} else {
westLon = longitude;
}
}
}

return new LatLngBounds(maxLat, maxLon, minLat, minLon);
return new LatLngBounds(maxLat, eastLon, minLat, westLon);
}

/**
Expand Down Expand Up @@ -322,19 +377,33 @@ public boolean equals(final Object o) {
return false;
}


private boolean containsLatitude(final double latitude) {
return (latitude <= this.latitudeNorth)
&& (latitude >= this.latitudeSouth);
}

private boolean containsLongitude(final double longitude) {
return containsLongitude(this.longitudeEast, this.longitudeWest, longitude);
}

static boolean containsLongitude(final double eastLon, final double westLon, final double longitude) {
if (eastLon > westLon) {
return (longitude <= eastLon)
&& (longitude >= westLon);
}
return (longitude < eastLon) || (longitude > westLon);
}

/**
* Determines whether this LatLngBounds contains a point.
*
* @param latLng the point which may be contained
* @return true, if the point is contained within the bounds
*/
public boolean contains(final ILatLng latLng) {
final double latitude = latLng.getLatitude();
final double longitude = latLng.getLongitude();
return ((latitude <= this.latitudeNorth)
&& (latitude >= this.latitudeSouth))
&& ((longitude <= this.longitudeEast)
&& (longitude >= this.longitudeWest));
return containsLatitude(latLng.getLatitude())
&& containsLongitude(latLng.getLongitude());
}

/**
Expand All @@ -344,7 +413,8 @@ public boolean contains(final ILatLng latLng) {
* @return true, if the bounds is contained within the bounds
*/
public boolean contains(final LatLngBounds other) {
return contains(other.getNorthEast()) && contains(other.getSouthWest());
return contains(other.getNorthEast())
&& contains(other.getSouthWest());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,82 @@ public void coordinateSpan() {
assertEquals("LatLngSpan should be the same", new LatLngSpan(2, 2), latLngSpan);
}

@Test
public void dateLineSpanBuilder1() {
latLngBounds = new LatLngBounds.Builder()
.include(new LatLng(10, -170))
.include(new LatLng(-10, 170))
.build();

LatLngSpan latLngSpan = latLngBounds.getSpan();
assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
latLngSpan);
}

@Test
public void dateLineSpanBuilder2() {
latLngBounds = new LatLngBounds.Builder()
.include(new LatLng(-10, -170))
.include(new LatLng(10, 170))
.build();

LatLngSpan latLngSpan = latLngBounds.getSpan();
assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
latLngSpan);
}

@Test
public void dateLineSpanFrom1() {
latLngBounds = LatLngBounds.from(10, -170, -10, 170);
LatLngSpan latLngSpan = latLngBounds.getSpan();
assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 20),
latLngSpan);
}

@Test
public void dateLineSpanFrom2() {
latLngBounds = LatLngBounds.from(10, 170, -10, -170);
LatLngSpan latLngSpan = latLngBounds.getSpan();
assertEquals("LatLngSpan should be shortest distance", new LatLngSpan(20, 340),
latLngSpan);
}

@Test
public void nearDateLineCenter1() {
latLngBounds = LatLngBounds.from(10, -175, -10, 165);
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(0, 175), center);
}

@Test
public void nearDateLineCenter2() {
latLngBounds = LatLngBounds.from(10, -165, -10, 175);
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(0, -175), center);
}

@Test
public void nearDateLineCenter3() {
latLngBounds = LatLngBounds.from(10, -170, -10, 170);
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(0, -180), center);
}

@Test
public void nearDateLineCenter4() {
latLngBounds = LatLngBounds.from(10, -180, -10, 0);
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(0, 90), center);
}

@Test
public void nearDateLineCenter5() {
latLngBounds = LatLngBounds.from(10, 180, -10, 0);
LatLng center = latLngBounds.getCenter();
assertEquals("Center should match", new LatLng(0, 90), center);
}


@Test
public void center() {
LatLng center = latLngBounds.getCenter();
Expand Down Expand Up @@ -120,6 +196,46 @@ public void includes() {
assertEquals("LatLngBounds should match", latLngBounds1, latLngBounds2);
}

@Test
public void includesOverDateline1() {

LatLngBounds latLngBounds = new LatLngBounds.Builder()
.include(new LatLng(10, -170))
.include(new LatLng(-10, -175))
.include(new LatLng(0, 170))
.build();

assertEquals("LatLngSpan should be the same",
new LatLngSpan(20, 20), latLngBounds.getSpan());
}

@Test
public void includesOverDateline2() {

LatLngBounds latLngBounds = new LatLngBounds.Builder()
.include(new LatLng(10, 170))
.include(new LatLng(-10, 175))
.include(new LatLng(0, -170))
.build();

assertEquals("LatLngSpan should be the same",
new LatLngSpan(20, 20), latLngBounds.getSpan());
}

@Test
public void includesOverDateline3() {

LatLngBounds latLngBounds = new LatLngBounds.Builder()
.include(new LatLng(10, 170))
.include(new LatLng(-10, -170))
.include(new LatLng(0, -180))
.include(new LatLng(5, 180))
.build();

assertEquals("LatLngSpan should be the same",
new LatLngSpan(20, 20), latLngBounds.getSpan());
}

@Test
public void containsNot() {
assertFalse("LatLng should not be included", latLngBounds.contains(new LatLng(3, 1)));
Expand All @@ -130,6 +246,21 @@ public void containsBoundsInWorld() {
assertTrue("LatLngBounds should be contained in the world", LatLngBounds.world().contains(latLngBounds));
}

@Test
public void worldSpan() {
assertEquals("LatLngBounds world span should be 180, 360",
GeometryConstants.LATITUDE_SPAN, LatLngBounds.world().getLatitudeSpan(), DELTA);
assertEquals("LatLngBounds world span should be 180, 360",
GeometryConstants.LONGITUDE_SPAN, LatLngBounds.world().getLongitudeSpan(), DELTA);
}

@Test
public void emptySpan() {
LatLngBounds latLngBounds = LatLngBounds.from(GeometryConstants.MIN_LATITUDE, GeometryConstants.MAX_LONGITUDE,
GeometryConstants.MIN_LATITUDE, GeometryConstants.MAX_LONGITUDE);
assertTrue("LatLngBounds empty span", latLngBounds.isEmptySpan());
}

@Test
public void containsBounds() {
LatLngBounds inner = new LatLngBounds.Builder()
Expand Down