-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add memory efficient polyline decoder
- Loading branch information
Showing
5 changed files
with
202 additions
and
35 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineDecoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package com.mapbox.geojson.utils; | ||
|
||
import androidx.annotation.Nullable; | ||
import com.mapbox.geojson.Point; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.Iterator; | ||
|
||
/** | ||
* Decodes an encoded path string as an iterator of {@link Point}. | ||
* This is a memory efficient version of {@link PolylineUtils#decode}. | ||
* | ||
* @see <a href="https://github.com/mapbox/polyline/blob/master/src/polyline.js">Part of algorithm came from this source</a> | ||
* @see <a href="https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/PolyUtil.java">Part of algorithm came from this source.</a> | ||
* @since 6.10.0 | ||
*/ | ||
public class PolylineDecoder implements Iterator<Point>, Closeable { | ||
|
||
private final InputStream inputStream; | ||
|
||
// OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5 | ||
private final double factor; | ||
|
||
// For speed we preallocate to an upper bound on the final length, then | ||
// truncate the array before returning. | ||
private int lat = 0; | ||
private int lng = 0; | ||
private int data = -1; | ||
private Point current; | ||
|
||
/** | ||
* Decodes an encoded input stream into a sequence of {@link Point}. | ||
* | ||
* @param inputStream InputStream that reads a String as bytes | ||
* @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5 | ||
*/ | ||
public PolylineDecoder(InputStream inputStream, int precision) { | ||
this.inputStream = inputStream; | ||
this.factor = Math.pow(10, precision); | ||
loadNext(); | ||
} | ||
|
||
/** | ||
* Returns the current [Point] for the iterator. Every call to [next] will update the [current]. | ||
*/ | ||
@Nullable | ||
public Point getCurrent() { | ||
return current; | ||
} | ||
|
||
/** | ||
* Returns true if the geometry has more points. | ||
*/ | ||
@Override | ||
public boolean hasNext() { | ||
return data != -1; | ||
} | ||
|
||
/** | ||
* Returns the next point in the geometry. | ||
*/ | ||
@Override | ||
public Point next() { | ||
int result = 1; | ||
int shift = 0; | ||
int temp; | ||
do { | ||
temp = data - 63 - 1; | ||
loadNext(); | ||
result += temp << shift; | ||
shift += 5; | ||
} | ||
while (temp >= 0x1f); | ||
lat += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); | ||
|
||
result = 1; | ||
shift = 0; | ||
do { | ||
temp = data - 63 - 1; | ||
loadNext(); | ||
result += temp << shift; | ||
shift += 5; | ||
} | ||
while (temp >= 0x1f); | ||
lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); | ||
|
||
Point next = Point.fromLngLat(lng / factor, lat / factor); | ||
current = next; | ||
return next; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
try { | ||
inputStream.close(); | ||
} catch (IOException exception) { | ||
// Safe close | ||
} | ||
} | ||
|
||
private void loadNext() throws RuntimeException { | ||
try { | ||
this.data = inputStream.read(); | ||
} catch (IOException exception) { | ||
this.data = -1; | ||
throw new RuntimeException("Failed to read the encoded path", exception); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
services-geojson/src/test/java/com/mapbox/geojson/utils/PolylineDecoderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.mapbox.geojson.utils; | ||
|
||
import com.mapbox.geojson.Point; | ||
import com.mapbox.geojson.TestUtils; | ||
import org.junit.Test; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.InputStream; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
public class PolylineDecoderTest extends TestUtils { | ||
|
||
@Test | ||
public void testHasNext() { | ||
String geometry = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; | ||
|
||
InputStream inputStream = new ByteArrayInputStream(geometry.getBytes()); | ||
PolylineDecoder polylineDecoder = new PolylineDecoder(inputStream, 5); | ||
|
||
assertTrue(polylineDecoder.hasNext()); | ||
polylineDecoder.next(); | ||
assertTrue(polylineDecoder.hasNext()); | ||
polylineDecoder.next(); | ||
assertTrue(polylineDecoder.hasNext()); | ||
polylineDecoder.next(); | ||
assertFalse(polylineDecoder.hasNext()); | ||
|
||
polylineDecoder.close(); | ||
} | ||
|
||
@Test | ||
public void testNextValues() { | ||
String geometry = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; | ||
|
||
InputStream inputStream = new ByteArrayInputStream(geometry.getBytes()); | ||
PolylineDecoder polylineDecoder = new PolylineDecoder(inputStream, 5); | ||
|
||
Point point = polylineDecoder.next(); | ||
assertEquals(38.5, point.latitude(), 0.0); | ||
assertEquals(-120.2, point.longitude(), 0.0); | ||
point = polylineDecoder.next(); | ||
assertEquals(40.7, point.latitude(), 0.0); | ||
assertEquals(-120.95, point.longitude(), 0.0); | ||
point = polylineDecoder.next(); | ||
assertEquals(43.252, point.latitude(), 0.0); | ||
assertEquals(-126.453, point.longitude(), 0.0); | ||
} | ||
|
||
@Test | ||
public void testCurrentValue() { | ||
String geometry = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; | ||
|
||
InputStream inputStream = new ByteArrayInputStream(geometry.getBytes()); | ||
PolylineDecoder polylineDecoder = new PolylineDecoder(inputStream, 5); | ||
|
||
assertNull(polylineDecoder.getCurrent()); | ||
assertEquals(polylineDecoder.next(), polylineDecoder.getCurrent()); | ||
assertEquals(polylineDecoder.next(), polylineDecoder.getCurrent()); | ||
assertEquals(polylineDecoder.next(), polylineDecoder.getCurrent()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.