org.maplibre.geojson.utils.PolylineUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-sdk-geojson Show documentation
Show all versions of android-sdk-geojson Show documentation
MapLibre Android Java Utilities
package org.maplibre.geojson.utils;
import androidx.annotation.NonNull;
import org.maplibre.geojson.Point;
import java.util.ArrayList;
import java.util.List;
/**
* Polyline utils class contains method that can decode/encode a polyline, simplify a line, and
* more.
*
* @since 1.0.0
*/
public final class PolylineUtils {
private PolylineUtils() {
// Prevent initialization of this class
}
// 1 by default (in the same metric as the point coordinates)
private static final double SIMPLIFY_DEFAULT_TOLERANCE = 1;
// False by default (excludes distance-based preprocessing step which leads to highest quality
// simplification but runs slower)
private static final boolean SIMPLIFY_DEFAULT_HIGHEST_QUALITY = false;
/**
* Decodes an encoded path string into a sequence of {@link Point}.
*
* @param encodedPath a String representing an encoded path string
* @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5
* @return list of {@link Point} making up the line
* @see Part of algorithm came from this source
* @see Part of algorithm came from this source.
* @since 1.0.0
*/
@NonNull
public static List decode(@NonNull final String encodedPath, int precision) {
int len = encodedPath.length();
// OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5
double factor = Math.pow(10, precision);
// For speed we preallocate to an upper bound on the final length, then
// truncate the array before returning.
final List path = new ArrayList<>();
int index = 0;
int lat = 0;
int lng = 0;
while (index < len) {
int result = 1;
int shift = 0;
int temp;
do {
temp = encodedPath.charAt(index++) - 63 - 1;
result += temp << shift;
shift += 5;
}
while (temp >= 0x1f);
lat += (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
result = 1;
shift = 0;
do {
temp = encodedPath.charAt(index++) - 63 - 1;
result += temp << shift;
shift += 5;
}
while (temp >= 0x1f);
lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1);
path.add(Point.fromLngLat(lng / factor, lat / factor));
}
return path;
}
/**
* Encodes a sequence of Points into an encoded path string.
*
* @param path list of {@link Point}s making up the line
* @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5
* @return a String representing a path string
* @since 1.0.0
*/
@NonNull
public static String encode(@NonNull final List path, int precision) {
long lastLat = 0;
long lastLng = 0;
final StringBuilder result = new StringBuilder();
// OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5
double factor = Math.pow(10, precision);
for (final Point point : path) {
long lat = Math.round(point.latitude() * factor);
long lng = Math.round(point.longitude() * factor);
long varLat = lat - lastLat;
long varLng = lng - lastLng;
encode(varLat, result);
encode(varLng, result);
lastLat = lat;
lastLng = lng;
}
return result.toString();
}
private static void encode(long variable, StringBuilder result) {
variable = variable < 0 ? ~(variable << 1) : variable << 1;
while (variable >= 0x20) {
result.append(Character.toChars((int) ((0x20 | (variable & 0x1f)) + 63)));
variable >>= 5;
}
result.append(Character.toChars((int) (variable + 63)));
}
/*
* Polyline simplification method. It's a direct port of simplify.js to Java.
* See: https://github.com/mourner/simplify-js/blob/master/simplify.js
*/
/**
* Reduces the number of points in a polyline while retaining its shape, giving a performance
* boost when processing it and also reducing visual noise.
*
* @param points an array of points
* @return an array of simplified points
* @see JavaScript implementation
* @since 1.2.0
*/
@NonNull
public static List simplify(@NonNull List points) {
return simplify(points, SIMPLIFY_DEFAULT_TOLERANCE, SIMPLIFY_DEFAULT_HIGHEST_QUALITY);
}
/**
* Reduces the number of points in a polyline while retaining its shape, giving a performance
* boost when processing it and also reducing visual noise.
*
* @param points an array of points
* @param tolerance affects the amount of simplification (in the same metric as the point
* coordinates)
* @return an array of simplified points
* @see JavaScript implementation
* @since 1.2.0
*/
@NonNull
public static List simplify(@NonNull List points, double tolerance) {
return simplify(points, tolerance, SIMPLIFY_DEFAULT_HIGHEST_QUALITY);
}
/**
* Reduces the number of points in a polyline while retaining its shape, giving a performance
* boost when processing it and also reducing visual noise.
*
* @param points an array of points
* @param highestQuality excludes distance-based preprocessing step which leads to highest quality
* simplification
* @return an array of simplified points
* @see JavaScript implementation
* @since 1.2.0
*/
@NonNull
public static List simplify(@NonNull List points, boolean highestQuality) {
return simplify(points, SIMPLIFY_DEFAULT_TOLERANCE, highestQuality);
}
/**
* Reduces the number of points in a polyline while retaining its shape, giving a performance
* boost when processing it and also reducing visual noise.
*
* @param points an array of points
* @param tolerance affects the amount of simplification (in the same metric as the point
* coordinates)
* @param highestQuality excludes distance-based preprocessing step which leads to highest quality
* simplification
* @return an array of simplified points
* @see JavaScript implementation
* @since 1.2.0
*/
@NonNull
public static List simplify(@NonNull List points, double tolerance,
boolean highestQuality) {
if (points.size() <= 2) {
return points;
}
double sqTolerance = tolerance * tolerance;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
/**
* Square distance between 2 points.
*
* @param p1 first {@link Point}
* @param p2 second Point
* @return square of the distance between two input points
*/
private static double getSqDist(Point p1, Point p2) {
double dx = p1.longitude() - p2.longitude();
double dy = p1.latitude() - p2.latitude();
return dx * dx + dy * dy;
}
/**
* Square distance from a point to a segment.
*
* @param point {@link Point} whose distance from segment needs to be determined
* @param p1 point defining the segment
* @param p2 point defining the segment
* @return square of the distance between first input point and segment defined by
* other two input points
*/
private static double getSqSegDist(Point point, Point p1, Point p2) {
double horizontal = p1.longitude();
double vertical = p1.latitude();
double diffHorizontal = p2.longitude() - horizontal;
double diffVertical = p2.latitude() - vertical;
if (diffHorizontal != 0 || diffVertical != 0) {
double total = ((point.longitude() - horizontal) * diffHorizontal + (point.latitude()
- vertical) * diffVertical) / (diffHorizontal * diffHorizontal + diffVertical
* diffVertical);
if (total > 1) {
horizontal = p2.longitude();
vertical = p2.latitude();
} else if (total > 0) {
horizontal += diffHorizontal * total;
vertical += diffVertical * total;
}
}
diffHorizontal = point.longitude() - horizontal;
diffVertical = point.latitude() - vertical;
return diffHorizontal * diffHorizontal + diffVertical * diffVertical;
}
/**
* Basic distance-based simplification.
*
* @param points a list of points to be simplified
* @param sqTolerance square of amount of simplification
* @return a list of simplified points
*/
private static List simplifyRadialDist(List points, double sqTolerance) {
Point prevPoint = points.get(0);
ArrayList newPoints = new ArrayList<>();
newPoints.add(prevPoint);
Point point = null;
for (int i = 1, len = points.size(); i < len; i++) {
point = points.get(i);
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.add(point);
prevPoint = point;
}
}
if (!prevPoint.equals(point)) {
newPoints.add(point);
}
return newPoints;
}
private static List simplifyDpStep(
List points, int first, int last, double sqTolerance, List simplified) {
double maxSqDist = sqTolerance;
int index = 0;
ArrayList stepList = new ArrayList<>();
for (int i = first + 1; i < last; i++) {
double sqDist = getSqSegDist(points.get(i), points.get(first), points.get(last));
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) {
stepList.addAll(simplifyDpStep(points, first, index, sqTolerance, simplified));
}
stepList.add(points.get(index));
if (last - index > 1) {
stepList.addAll(simplifyDpStep(points, index, last, sqTolerance, simplified));
}
}
return stepList;
}
/**
* Simplification using Ramer-Douglas-Peucker algorithm.
*
* @param points a list of points to be simplified
* @param sqTolerance square of amount of simplification
* @return a list of simplified points
*/
private static List simplifyDouglasPeucker(List points, double sqTolerance) {
int last = points.size() - 1;
ArrayList simplified = new ArrayList<>();
simplified.add(points.get(0));
simplified.addAll(simplifyDpStep(points, 0, last, sqTolerance, simplified));
simplified.add(points.get(last));
return simplified;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy