/* * Copyright (c) 2013, Will Szumski * Copyright (c) 2013, Doug Szumski * * This file is part of Cyclismo. * * Cyclismo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cyclismo 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 Cyclismo. If not, see <http://www.gnu.org/licenses/>. */ /** * inspired from http://soulsolutions.com.au/Articles/Encodingforperformance/tabid/96/Default.aspx */ package org.fluxoid.utils; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; public class PolyLineUtils { public static final Logger logger = Logger.getLogger(PolyLineUtils.class.toString()); /** * Utility class */ private PolyLineUtils() { } public static final int CHUNK_SIZE = 5; public static final int ASCII_OFFSET = 63; /** * @param polyLine * @return */ public static List<LatLong> decode(CharSequence polyLine) throws DecodeException { try { List<LatLong> latLongs = new ArrayList<LatLong>(); TrackedCharSequence polyLineChars = new TrackedCharSequence(polyLine); double latitude = 0; double longitude = 0; while (polyLineChars.hasNext()) { //getValue iterates through the TrackedCharSequence latitude += getValue(polyLineChars); longitude += getValue(polyLineChars); latLongs.add(new LatLong(latitude, longitude)); } return latLongs; } catch (RuntimeException e) { throw new DecodeException(e); } } /** * Encodes list of latlongs into a polyline */ public static CharSequence encode(List<LatLong> latLongs) { StringBuilder polyLine = new StringBuilder(); int lastLat = 0; int lastLong = 0; for (LatLong l : latLongs) { int currentLat = (int) Math.round(l.getLatitude() * Math.pow(10, 5)); int currentLong = (int) Math.round(l.getLongitude() * Math.pow(10, 5)); polyLine.append(encodeNumber(currentLat - lastLat)); polyLine.append(encodeNumber(currentLong - lastLong)); lastLat = currentLat; lastLong = currentLong; } return polyLine; } public static CharSequence encodeNumber(int number) { int shifted = number << 1; if (number < 0) { shifted = ~shifted; } StringBuilder encoded = new StringBuilder(); while (shifted >= 0x20) { //while another chunk follows encoded.append((char) ((0x20 | (shifted & 0x1f)) + ASCII_OFFSET)); //OR value with 0x20, convert to decimal and add 63 shifted >>= CHUNK_SIZE; //shift to next chunk } encoded.append((char) (shifted + ASCII_OFFSET)); return encoded; } /** * Escape for string literal */ public static CharSequence escapePolyLine(CharSequence c) { String str = new StringBuilder(c).toString(); return str.replace("/", "//"); } /** * unescape string literals */ public static CharSequence unEscapePolyLine(CharSequence c) { String str = new StringBuilder(c).toString(); return str.replace("//", "/"); } /** * Returns the next value, iterating through the TrackedCharSequence until the required * number of chars have been processed * * @return the next lat/long value */ public static double getValue(TrackedCharSequence polyLineChars) { long result = 0; int shift = 0; int currentByte = 0; if (!polyLineChars.hasNext()) { return 0.; } do { currentByte = (int) polyLineChars.next() - ASCII_OFFSET; //ignore extra chunk bit (use 5 bits from chunk) logger.finer("currentbyte: " + (currentByte & 0x1f)); result |= (currentByte & 0x1f) << shift; shift += CHUNK_SIZE; } while (currentByte >= 0x20); //6th bit set when an extra chunk is needed // the zero that was introduced from the initial left shift will have been inverted to one if // original was negative result = ((result & 1) > 0) ? ~(result >> 1) : (result >> 1); return (double) result / Math.pow(10, 5); } }