/* * Copyright 2013 Google Inc. * * 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. */ package com.car2go.maps.util; import com.car2go.maps.model.LatLng; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class PolyUtil { /** * Decodes an encoded path string into a sequence of LatLngs. */ public static List<LatLng> decode(String encodedPath) { int len = encodedPath.length(); ArrayList<LatLng> path = new ArrayList<>(); int index = 0; int lat = 0; int lng = 0; while (index < len) { int result = 1; int shift = 0; int b; do { b = encodedPath.charAt(index++) - 63 - 1; result += b << shift; shift += 5; } while (b >= 31); lat += (result & 1) != 0 ? ~(result >> 1) : result >> 1; result = 1; shift = 0; do { b = encodedPath.charAt(index++) - 63 - 1; result += b << shift; shift += 5; } while (b >= 31); lng += (result & 1) != 0 ? ~(result >> 1) : result >> 1; path.add(new LatLng((double) lat * 1.0E-5D, (double) lng * 1.0E-5D)); } return path; } /** * Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0. * See http://williams.best.vwh.net/avform.htm . */ private static double tanLatGC(double lat1, double lat2, double lng2, double lng3) { return (Math.tan(lat1) * Math.sin(lng2 - lng3) + Math.tan(lat2) * Math.sin(lng3)) / Math.sin(lng2); } /** * Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0. */ private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) { return (MathUtil.mercator(lat1) * (lng2 - lng3) + MathUtil.mercator(lat2) * lng3) / lng2; } /** * Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment * (lat1, lng1) to (lat2, lng2). * Longitudes are offset by -lng1; the implicit lng1 becomes 0. */ private static boolean intersects(double lat1, double lat2, double lng2, double lat3, double lng3, boolean geodesic) { if ((lng3 < 0.0D || lng3 < lng2) && (lng3 >= 0.0D || lng3 >= lng2)) { if (lat3 <= -1.5707963267948966D) { return false; } else if (lat1 > -1.5707963267948966D && lat2 > -1.5707963267948966D && lat1 < 1.5707963267948966D && lat2 < 1.5707963267948966D) { if (lng2 <= -3.141592653589793D) { return false; } else { double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2; return lat1 >= 0.0D && lat2 >= 0.0D && lat3 < linearLat ? false : (lat1 <= 0.0D && lat2 <= 0.0D && lat3 >= linearLat ? true : (lat3 >= 1.5707963267948966D ? true : (geodesic ? Math.tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) : MathUtil.mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3)))); } } else { return false; } } else { return false; } } /** * Computes whether the given point lies inside the specified polygon. * The polygon is always cosidered closed, regardless of whether the last point equals * the first or not. * Inside is defined as not containing the South Pole -- the South Pole is always outside. * The polygon is formed of great circle segments if geodesic is true, and of rhumb * (loxodromic) segments otherwise. */ public static boolean containsLocation(LatLng point, List<LatLng> polygon, boolean geodesic) { int size = polygon.size(); if (size == 0) { return false; } else { double lat3 = Math.toRadians(point.latitude); double lng3 = Math.toRadians(point.longitude); LatLng prev = polygon.get(size - 1); double lat1 = Math.toRadians(prev.latitude); double lng1 = Math.toRadians(prev.longitude); int nIntersect = 0; double lng2; for (Iterator i$ = polygon.iterator(); i$.hasNext(); lng1 = lng2) { LatLng point2 = (LatLng) i$.next(); double dLng3 = MathUtil.wrap(lng3 - lng1, -3.141592653589793D, 3.141592653589793D); if (lat3 == lat1 && dLng3 == 0.0D) { return true; } double lat2 = Math.toRadians(point2.latitude); lng2 = Math.toRadians(point2.longitude); if (intersects(lat1, lat2, MathUtil.wrap(lng2 - lng1, -3.141592653589793D, 3.141592653589793D), lat3, dLng3, geodesic)) { ++nIntersect; } lat1 = lat2; } return (nIntersect & 1) != 0; } } }