Repository: mapbox/mapbox-sdk-py
Pull request #266 (2018-11-29 15:51:52)

Add annotations parameter support to DirectionsMatrix
Hi Mapbox team!

I'm doing an evaluation of the Mapbox API to do an integration (specifically, the Directions Matrix service). In order to finish the evaluation I need to retrieve both distance and time estimations by using the annotations parameter as specified in https://www.mapbox.com/api-documentation/?language=Python#matrix

So, I've added in DirectionsMatrix the support for the annotations parameter, the design/implementation was basically copied from the Directions service to not diverge with the internal conventions you might have.

If you have any questions or suggestions, do not hesitate in reaching out! ✌🏻


sgillies: @omenar thanks for the PR! Review of PRs for this project is currently backed up but we'll try to get to it as soon as we can.
acalvarez: Adding @briandaviddavidson for visibility

Patch:
diff --git a/CHANGES b/CHANGES
index 537d555..0c57ada 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,11 @@
 Changes
 =======
 
+0.17.2 (TBD)
+------------
+- Coordinates have been swapped to get the proper order in the GeoJSON response
+  from the Directions service (#262).
+
 0.17.1 (2018-09-26)
 -------------------
 
diff --git a/mapbox/__init__.py b/mapbox/__init__.py
index 7e974c4..cc1e810 100644
--- a/mapbox/__init__.py
+++ b/mapbox/__init__.py
@@ -1,5 +1,5 @@
 # mapbox
-__version__ = "0.17.1"
+__version__ = "0.17.2"
 
 from .services.datasets import Datasets
 from .services.directions import Directions
diff --git a/mapbox/services/directions.py b/mapbox/services/directions.py
index b867486..775ab7f 100644
--- a/mapbox/services/directions.py
+++ b/mapbox/services/directions.py
@@ -260,7 +260,7 @@ class Directions(Service):
                 # convert default polyline encoded geometry
                 geom = {
                     'type': 'LineString',
-                    'coordinates': polyline.decode(route['geometry'])}
+                    'coordinates': [(lng, lat) for lat, lng in polyline.decode(route['geometry'])]}
 
             feature = {
                 'type': 'Feature',
diff --git a/mapbox/services/matrix.py b/mapbox/services/matrix.py
index 9ca07cb..2290ed5 100644
--- a/mapbox/services/matrix.py
+++ b/mapbox/services/matrix.py
@@ -17,6 +17,7 @@ class DirectionsMatrix(Service):
     valid_profiles = [
         'mapbox/driving', 'mapbox/cycling', 'mapbox/walking',
         'mapbox/driving-traffic']
+    valid_annotations = ['duration', 'distance']
 
     @property
     def baseuri(self):
@@ -41,6 +42,18 @@ class DirectionsMatrix(Service):
                 "{0} is not a valid profile".format(profile))
         return profile
 
+    def _validate_annotations(self, annotations):
+        results = []
+        if annotations is None:
+            return None
+        for annotation in annotations:
+            if annotation not in self.valid_annotations:
+                raise errors.InvalidParameterError(
+                    "{0} is not a valid annotation".format(annotation))
+            else:
+                results.append(annotation)
+        return results
+
     def _make_query(self, srcindexes, dstindexes):
         params = {}
         if srcindexes is not None and isinstance(srcindexes, list):
@@ -49,8 +62,8 @@ class DirectionsMatrix(Service):
             params['destinations'] = ';'.join([str(idx) for idx in dstindexes])
         return params
 
-    def matrix(self, coordinates, profile='mapbox/driving',
-               sources=None, destinations=None):
+    def matrix(self, coordinates, profile='mapbox/driving', 
+               sources=None, destinations=None, annotations=None):
         """Request a directions matrix for trips between coordinates
 
         In the default case, the matrix returns a symmetric matrix,
@@ -67,6 +80,9 @@ class DirectionsMatrix(Service):
         profile : str
             The trip travel mode. Valid modes are listed in the class's
             valid_profiles attribute.
+        annotations : list
+            Used to specify the resulting matrices. Possible values are
+            listed in the class's valid_annotations attribute.
         sources : list
             Indices of source coordinates to include in the matrix.
             Default is all coordinates.
@@ -79,27 +95,44 @@ class DirectionsMatrix(Service):
         requests.Response
 
         Note: the directions matrix itself is obtained by calling the
-        response's json() method. The resulting mapping has 4 items.
+        response's json() method. The resulting mapping has a code,
+        the destinations and the sources, and depending of the
+        annotations specified, it can also contain a durations matrix,
+        a distances matrix or both of them (by default, only the
+        durations matrix is provided).
 
         code : str
             Status of the response
+        sources : list
+            Results of snapping selected coordinates to the nearest
+            addresses.
+        destinations : list
+            Results of snapping selected coordinates to the nearest
+            addresses.
         durations : list
             An array of arrays representing the matrix in row-major
             order.  durations[i][j] gives the travel time from the i-th
             source to the j-th destination. All values are in seconds.
             The duration between the same coordinate is always 0. If
             a duration can not be found, the result is null.
-        destinations : list
-            Results of snapping selected coordinates to the nearest
-            addresses.
-        destinations : list
-            Results of snapping selected coordinates to the nearest
-            addresses.
+        distances : list
+            An array of arrays representing the matrix in row-major
+            order.  distances[i][j] gives the distance from the i-th
+            source to the j-th destination. All values are in meters.
+            The distance between the same coordinate is always 0. If
+            a distance can not be found, the result is null.
+
         """
+        annotations = self._validate_annotations(annotations)
         profile = self._validate_profile(profile)
         coords = encode_waypoints(coordinates)
-        query = self._make_query(sources, destinations)
+
+        params = self._make_query(sources, destinations)
+
+        if annotations is not None:
+            params.update({'annotations': ','.join(annotations)})
+
         uri = '{0}/{1}/{2}'.format(self.baseuri, profile, coords)
-        res = self.session.get(uri, params=query)
+        res = self.session.get(uri, params=params)
         self.handle_http_error(res)
         return res
diff --git a/tox.ini b/tox.ini
index bceb578..2107b2c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,10 @@
 [tox]
 envlist = 
-    py27,py34,py35,py36
+    py27,py36
 
 [testenv]
 deps =
     pytest-cov
     responses
 commands = 
-    py.test --cov mapbox --cov-report term-missing
+    python -m pytest --cov mapbox --cov-report term-missing


Test patch:
diff --git a/tests/test_matrix.py b/tests/test_matrix.py
index f637709..ee9b965 100644
--- a/tests/test_matrix.py
+++ b/tests/test_matrix.py
@@ -46,6 +46,13 @@ def test_profile_valid(profile):
         access_token='pk.test')._validate_profile(profile)
 
 
+@pytest.mark.parametrize('annotations', [['distance'], ['duration'], ['distance', 'duration'], None])
+def test_annotations_valid(annotations):
+    """Annotations are valid"""
+    assert annotations == DirectionsMatrix(
+        access_token='pk.test')._validate_annotations(annotations)
+
+
 @pytest.mark.parametrize('profile', ['mapbox.driving', 'mapbox.cycling', 'mapbox.walking'])
 def test_deprecated_profile(profile):
     """Profiles are deprecated"""
@@ -68,8 +75,8 @@ def test_query():
 
 @responses.activate
 @pytest.mark.parametrize('waypoints', [points, [p['geometry'] for p in points], [p['geometry']['coordinates'] for p in points]])
-def test_matrix(waypoints):
-
+@pytest.mark.parametrize('annotations', [['distance'], ['duration'], ['distance', 'duration', None]])
+def test_matrix(waypoints, annotations):
     responses.add(
         responses.GET,
         'https://api.mapbox.com/directions-matrix/v1/mapbox/driving/-87,36;-86,36;-88,37?access_token=pk.test',