usse/funda-scraper/venv/lib/python3.10/site-packages/googlemaps/convert.py

387 lines
10 KiB
Python

#
# Copyright 2014 Google Inc. All rights reserved.
#
#
# 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.
#
"""Converts Python types to string representations suitable for Maps API server.
For example:
sydney = {
"lat" : -33.8674869,
"lng" : 151.2069902
}
convert.latlng(sydney)
# '-33.8674869,151.2069902'
"""
def format_float(arg):
"""Formats a float value to be as short as possible.
Truncates float to 8 decimal places and trims extraneous
trailing zeros and period to give API args the best
possible chance of fitting within 2000 char URL length
restrictions.
For example:
format_float(40) -> "40"
format_float(40.0) -> "40"
format_float(40.1) -> "40.1"
format_float(40.001) -> "40.001"
format_float(40.0010) -> "40.001"
format_float(40.000000001) -> "40"
format_float(40.000000009) -> "40.00000001"
:param arg: The lat or lng float.
:type arg: float
:rtype: string
"""
return ("%.8f" % float(arg)).rstrip("0").rstrip(".")
def latlng(arg):
"""Converts a lat/lon pair to a comma-separated string.
For example:
sydney = {
"lat" : -33.8674869,
"lng" : 151.2069902
}
convert.latlng(sydney)
# '-33.8674869,151.2069902'
For convenience, also accepts lat/lon pair as a string, in
which case it's returned unchanged.
:param arg: The lat/lon pair.
:type arg: string or dict or list or tuple
"""
if is_string(arg):
return arg
normalized = normalize_lat_lng(arg)
return "%s,%s" % (format_float(normalized[0]), format_float(normalized[1]))
def normalize_lat_lng(arg):
"""Take the various lat/lng representations and return a tuple.
Accepts various representations:
1) dict with two entries - "lat" and "lng"
2) list or tuple - e.g. (-33, 151) or [-33, 151]
:param arg: The lat/lng pair.
:type arg: dict or list or tuple
:rtype: tuple (lat, lng)
"""
if isinstance(arg, dict):
if "lat" in arg and "lng" in arg:
return arg["lat"], arg["lng"]
if "latitude" in arg and "longitude" in arg:
return arg["latitude"], arg["longitude"]
# List or tuple.
if _is_list(arg):
return arg[0], arg[1]
raise TypeError(
"Expected a lat/lng dict or tuple, "
"but got %s" % type(arg).__name__)
def location_list(arg):
"""Joins a list of locations into a pipe separated string, handling
the various formats supported for lat/lng values.
For example:
p = [{"lat" : -33.867486, "lng" : 151.206990}, "Sydney"]
convert.waypoint(p)
# '-33.867486,151.206990|Sydney'
:param arg: The lat/lng list.
:type arg: list
:rtype: string
"""
if isinstance(arg, tuple):
# Handle the single-tuple lat/lng case.
return latlng(arg)
else:
return "|".join([latlng(location) for location in as_list(arg)])
def join_list(sep, arg):
"""If arg is list-like, then joins it with sep.
:param sep: Separator string.
:type sep: string
:param arg: Value to coerce into a list.
:type arg: string or list of strings
:rtype: string
"""
return sep.join(as_list(arg))
def as_list(arg):
"""Coerces arg into a list. If arg is already list-like, returns arg.
Otherwise, returns a one-element list containing arg.
:rtype: list
"""
if _is_list(arg):
return arg
return [arg]
def _is_list(arg):
"""Checks if arg is list-like. This excludes strings and dicts."""
if isinstance(arg, dict):
return False
if isinstance(arg, str): # Python 3-only, as str has __iter__
return False
return _has_method(arg, "__getitem__") if not _has_method(arg, "strip") else _has_method(arg, "__iter__")
def is_string(val):
"""Determines whether the passed value is a string, safe for 2/3."""
try:
basestring
except NameError:
return isinstance(val, str)
return isinstance(val, basestring)
def time(arg):
"""Converts the value into a unix time (seconds since unix epoch).
For example:
convert.time(datetime.now())
# '1409810596'
:param arg: The time.
:type arg: datetime.datetime or int
"""
# handle datetime instances.
if _has_method(arg, "timestamp"):
arg = arg.timestamp()
if isinstance(arg, float):
arg = int(arg)
return str(arg)
def _has_method(arg, method):
"""Returns true if the given object has a method with the given name.
:param arg: the object
:param method: the method name
:type method: string
:rtype: bool
"""
return hasattr(arg, method) and callable(getattr(arg, method))
def components(arg):
"""Converts a dict of components to the format expected by the Google Maps
server.
For example:
c = {"country": "US", "postal_code": "94043"}
convert.components(c)
# 'country:US|postal_code:94043'
:param arg: The component filter.
:type arg: dict
:rtype: basestring
"""
# Components may have multiple values per type, here we
# expand them into individual key/value items, eg:
# {"country": ["US", "AU"], "foo": 1} -> "country:AU", "country:US", "foo:1"
def expand(arg):
for k, v in arg.items():
for item in as_list(v):
yield "%s:%s" % (k, item)
if isinstance(arg, dict):
return "|".join(sorted(expand(arg)))
raise TypeError(
"Expected a dict for components, "
"but got %s" % type(arg).__name__)
def bounds(arg):
"""Converts a lat/lon bounds to a comma- and pipe-separated string.
Accepts two representations:
1) string: pipe-separated pair of comma-separated lat/lon pairs.
2) dict with two entries - "southwest" and "northeast". See convert.latlng
for information on how these can be represented.
For example:
sydney_bounds = {
"northeast" : {
"lat" : -33.4245981,
"lng" : 151.3426361
},
"southwest" : {
"lat" : -34.1692489,
"lng" : 150.502229
}
}
convert.bounds(sydney_bounds)
# '-34.169249,150.502229|-33.424598,151.342636'
:param arg: The bounds.
:type arg: dict
"""
if is_string(arg) and arg.count("|") == 1 and arg.count(",") == 2:
return arg
elif isinstance(arg, dict):
if "southwest" in arg and "northeast" in arg:
return "%s|%s" % (latlng(arg["southwest"]),
latlng(arg["northeast"]))
raise TypeError(
"Expected a bounds (southwest/northeast) dict, "
"but got %s" % type(arg).__name__)
def size(arg):
if isinstance(arg, int):
return "%sx%s" % (arg, arg)
elif _is_list(arg):
return "%sx%s" % (arg[0], arg[1])
raise TypeError(
"Expected a size int or list, "
"but got %s" % type(arg).__name__)
def decode_polyline(polyline):
"""Decodes a Polyline string into a list of lat/lng dicts.
See the developer docs for a detailed description of this encoding:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
:param polyline: An encoded polyline
:type polyline: string
:rtype: list of dicts with lat/lng keys
"""
points = []
index = lat = lng = 0
while index < len(polyline):
result = 1
shift = 0
while True:
b = ord(polyline[index]) - 63 - 1
index += 1
result += b << shift
shift += 5
if b < 0x1f:
break
lat += (~result >> 1) if (result & 1) != 0 else (result >> 1)
result = 1
shift = 0
while True:
b = ord(polyline[index]) - 63 - 1
index += 1
result += b << shift
shift += 5
if b < 0x1f:
break
lng += ~(result >> 1) if (result & 1) != 0 else (result >> 1)
points.append({"lat": lat * 1e-5, "lng": lng * 1e-5})
return points
def encode_polyline(points):
"""Encodes a list of points into a polyline string.
See the developer docs for a detailed description of this encoding:
https://developers.google.com/maps/documentation/utilities/polylinealgorithm
:param points: a list of lat/lng pairs
:type points: list of dicts or tuples
:rtype: string
"""
last_lat = last_lng = 0
result = ""
for point in points:
ll = normalize_lat_lng(point)
lat = int(round(ll[0] * 1e5))
lng = int(round(ll[1] * 1e5))
d_lat = lat - last_lat
d_lng = lng - last_lng
for v in [d_lat, d_lng]:
v = ~(v << 1) if v < 0 else v << 1
while v >= 0x20:
result += (chr((0x20 | (v & 0x1f)) + 63))
v >>= 5
result += (chr(v + 63))
last_lat = lat
last_lng = lng
return result
def shortest_path(locations):
"""Returns the shortest representation of the given locations.
The Elevations API limits requests to 2000 characters, and accepts
multiple locations either as pipe-delimited lat/lng values, or
an encoded polyline, so we determine which is shortest and use it.
:param locations: The lat/lng list.
:type locations: list
:rtype: string
"""
if isinstance(locations, tuple):
# Handle the single-tuple lat/lng case.
locations = [locations]
encoded = "enc:%s" % encode_polyline(locations)
unencoded = location_list(locations)
if len(encoded) < len(unencoded):
return encoded
else:
return unencoded