1import collections.abc
2
3from geopy.point import Point
4
5
6def _location_tuple(location):
7    return location._address, (location._point[0], location._point[1])
8
9
10class Location:
11    """
12    Contains a parsed geocoder response. Can be iterated over as
13    ``(location<String>, (latitude<float>, longitude<Float))``.
14    Or one can access the properties ``address``, ``latitude``,
15    ``longitude``, or ``raw``. The last
16    is a dictionary of the geocoder's response for this item.
17    """
18
19    __slots__ = ("_address", "_point", "_tuple", "_raw")
20
21    def __init__(self, address, point, raw):
22        if address is None:
23            raise TypeError("`address` must not be None")
24        self._address = address
25
26        if isinstance(point, Point):
27            self._point = point
28        elif isinstance(point, str):
29            self._point = Point(point)
30        elif isinstance(point, collections.abc.Sequence):
31            self._point = Point(point)
32        else:
33            raise TypeError(
34                "`point` is of unsupported type: %r" % type(point)
35            )
36        self._tuple = _location_tuple(self)
37
38        if raw is None:
39            raise TypeError("`raw` must not be None")
40        self._raw = raw
41
42    @property
43    def address(self):
44        """
45        Location as a formatted string returned by the geocoder or constructed
46        by geopy, depending on the service.
47
48        :rtype: str
49        """
50        return self._address
51
52    @property
53    def latitude(self):
54        """
55        Location's latitude.
56
57        :rtype: float
58        """
59        return self._point[0]
60
61    @property
62    def longitude(self):
63        """
64        Location's longitude.
65
66        :rtype: float
67        """
68        return self._point[1]
69
70    @property
71    def altitude(self):
72        """
73        Location's altitude.
74
75        .. note::
76            Geocoding services usually don't consider altitude neither in
77            requests nor in responses, so almost always the value of this
78            property would be zero.
79
80        :rtype: float
81        """
82        return self._point[2]
83
84    @property
85    def point(self):
86        """
87        :class:`geopy.point.Point` instance representing the location's
88        latitude, longitude, and altitude.
89
90        :rtype: :class:`geopy.point.Point`
91        """
92        return self._point
93
94    @property
95    def raw(self):
96        """
97        Location's raw, unparsed geocoder response. For details on this,
98        consult the service's documentation.
99
100        :rtype: dict
101        """
102        return self._raw
103
104    def __getitem__(self, index):
105        """
106        Backwards compatibility with geopy<0.98 tuples.
107        """
108        return self._tuple[index]
109
110    def __str__(self):
111        return self._address
112
113    def __repr__(self):
114        return "Location(%s, (%s, %s, %s))" % (
115            self._address, self.latitude, self.longitude, self.altitude
116        )
117
118    def __iter__(self):
119        return iter(self._tuple)
120
121    def __getstate__(self):
122        return self._address, self._point, self._raw
123
124    def __setstate__(self, state):
125        self._address, self._point, self._raw = state
126        self._tuple = _location_tuple(self)
127
128    def __eq__(self, other):
129        return (
130            isinstance(other, Location) and
131            self._address == other._address and
132            self._point == other._point and
133            self.raw == other.raw
134        )
135
136    def __ne__(self, other):
137        return not (self == other)
138
139    def __len__(self):
140        return len(self._tuple)
141