/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Reimplementation of Mark McClures Javascript PolylineEncoder * * All the mathematical logic is more or less copied by McClure * * The gmaps java api is available at * http://sourceforge.net/project/showfiles.php?group_id=169331 Adapted by * Cassio Melo - cassio.ufpe[at]gmail[dot]com * * @author Mark Rambow * @e-mail markrambow[at]gmail[dot]com * @version 0.1 * */ package org.onebusaway.geospatial.services; import org.onebusaway.geospatial.model.CoordinatePoint; import org.onebusaway.geospatial.model.EncodedPolylineBean; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; 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<CoordinatePoint> points) { return createEncodings(points, -1); } /** * If level < 0, then {@link EncodedPolylineBean#getLevels()} will be null. * * @param points * @param level * @return */ public static EncodedPolylineBean createEncodings( Iterable<CoordinatePoint> points, int level) { StringBuilder encodedPoints = new StringBuilder(); StringBuilder encodedLevels = new StringBuilder(); int plat = 0; int plng = 0; int count = 0; for (CoordinatePoint trackpoint : points) { int late5 = floor1e5(trackpoint.getLat()); int lnge5 = floor1e5(trackpoint.getLon()); 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<CoordinatePoint> decode(EncodedPolylineBean polyline) { return decode(polyline.getPoints()); } public static List<CoordinatePoint> decode(String pointString) { double lat = 0; double lon = 0; int strIndex = 0; List<CoordinatePoint> points = new ArrayList<CoordinatePoint>(); 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 CoordinatePoint(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<CoordinatePoint> { 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 CoordinatePoint get(int index) { return new CoordinatePoint(_lat[_offset + index], _lon[_offset + index]); } @Override public int size() { return _length; } } }