/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.util;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import org.opentripplanner.util.model.EncodedPolylineBean;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
public class PolylineEncoder {
public static EncodedPolylineBean createEncodings(double[] lat, double[] lon) {
return createEncodings(new PointAdapterList(lat, lon));
}
public static EncodedPolylineBean createEncodings(double[] lat, double[] lon, int level) {
return createEncodings(new PointAdapterList(lat, lon), level);
}
public static EncodedPolylineBean createEncodings(double[] lat, double[] lon, int offset,
int length, int level) {
return createEncodings(new PointAdapterList(lat, lon, offset, length), level);
}
public static EncodedPolylineBean createEncodings(Iterable<Coordinate> points) {
return createEncodings(points, -1);
}
public static EncodedPolylineBean createEncodings(Geometry geometry) {
if (geometry instanceof LineString) {
LineString string = (LineString) geometry;
Coordinate[] coordinates = string.getCoordinates();
return createEncodings(new CoordinateList(coordinates));
} else if (geometry instanceof MultiLineString) {
MultiLineString mls = (MultiLineString) geometry;
return createEncodings(new CoordinateList(mls.getCoordinates()));
} else {
throw new IllegalArgumentException(geometry.toString());
}
}
/**
* If level < 0, then {@link EncodedPolylineBean#getLevels()} will be null.
*
* @param points
* @param level
* @return
*/
public static EncodedPolylineBean createEncodings(Iterable<Coordinate> points, int level) {
StringBuilder encodedPoints = new StringBuilder();
StringBuilder encodedLevels = new StringBuilder();
int plat = 0;
int plng = 0;
int count = 0;
for (Coordinate point : points) {
int late5 = floor1e5(point.y);
int lnge5 = floor1e5(point.x);
int dlat = late5 - plat;
int dlng = lnge5 - plng;
plat = late5;
plng = lnge5;
encodedPoints.append(encodeSignedNumber(dlat)).append(encodeSignedNumber(dlng));
if (level >= 0)
encodedLevels.append(encodeNumber(level));
count++;
}
String pointsString = encodedPoints.toString();
String levelsString = level >= 0 ? encodedLevels.toString() : null;
return new EncodedPolylineBean(pointsString, levelsString, count);
}
public static List<Coordinate> decode(EncodedPolylineBean polyline) {
String pointString = polyline.getPoints();
double lat = 0;
double lon = 0;
int strIndex = 0;
List<Coordinate> points = new ArrayList<Coordinate>();
while (strIndex < pointString.length()) {
int[] rLat = decodeSignedNumberWithIndex(pointString, strIndex);
lat = lat + rLat[0] * 1e-5;
strIndex = rLat[1];
int[] rLon = decodeSignedNumberWithIndex(pointString, strIndex);
lon = lon + rLon[0] * 1e-5;
strIndex = rLon[1];
points.add(new Coordinate(lat, lon));
}
return points;
}
/*****************************************************************************
* Private Methods
****************************************************************************/
private static final int floor1e5(double coordinate) {
return (int) Math.floor(coordinate * 1e5);
}
public static String encodeSignedNumber(int num) {
int sgn_num = num << 1;
if (num < 0) {
sgn_num = ~(sgn_num);
}
return (encodeNumber(sgn_num));
}
public static int decodeSignedNumber(String value) {
int[] r = decodeSignedNumberWithIndex(value, 0);
return r[0];
}
public static int[] decodeSignedNumberWithIndex(String value, int index) {
int[] r = decodeNumberWithIndex(value, index);
int sgn_num = r[0];
if ((sgn_num & 0x01) > 0) {
sgn_num = ~(sgn_num);
}
r[0] = sgn_num >> 1;
return r;
}
public static String encodeNumber(int num) {
StringBuffer encodeString = new StringBuffer();
while (num >= 0x20) {
int nextValue = (0x20 | (num & 0x1f)) + 63;
encodeString.append((char) (nextValue));
num >>= 5;
}
num += 63;
encodeString.append((char) (num));
return encodeString.toString();
}
public static int decodeNumber(String value) {
int[] r = decodeNumberWithIndex(value, 0);
return r[0];
}
public static int[] decodeNumberWithIndex(String value, int index) {
if (value.length() == 0)
throw new IllegalArgumentException("string is empty");
int num = 0;
int v = 0;
int shift = 0;
do {
v = value.charAt(index++) - 63;
num |= (v & 0x1f) << shift;
shift += 5;
} while (v >= 0x20);
return new int[] { num, index };
}
private static class PointAdapterList extends AbstractList<Coordinate> {
private double[] _lat;
private double[] _lon;
private int _offset;
private int _length;
public PointAdapterList(double[] lat, double[] lon) {
this(lat, lon, 0, lat.length);
}
public PointAdapterList(double[] lat, double[] lon, int offset, int length) {
_lat = lat;
_lon = lon;
_offset = offset;
_length = length;
}
@Override
public Coordinate get(int index) {
return new Coordinate(_lon[_offset + index], _lat[_offset + index]);
}
@Override
public int size() {
return _length;
}
}
private static class CoordinateList extends AbstractList<Coordinate> {
private Coordinate[] _coordinates;
public CoordinateList(Coordinate[] coordinates) {
_coordinates = coordinates;
}
@Override
public Coordinate get(int index) {
return _coordinates[index];
}
@Override
public int size() {
return _coordinates.length;
}
}
}