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

Commit

Permalink
[android] bounds can go over the antimeridian / date line.
Browse files Browse the repository at this point in the history
  • Loading branch information
“osana” committed Jan 26, 2018
1 parent 082df68 commit 805a23a
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 21 deletions.
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

0 comments on commit 805a23a

Please sign in to comment.