Repository: mapbox/mapbox-sdk-py
Pull request #92 (2015-12-09 21:06:57)

More map matching
A couple of details I missed in the first PR:

* add to README
* validate features
* kwarg for gps precision


Patch:
diff --git a/README.rst b/README.rst
index 5f34390..f01d68b 100644
--- a/README.rst
+++ b/README.rst
@@ -31,6 +31,10 @@ Services
   - Forward (place names ⇢ longitude, latitude)
   - Reverse (longitude, latitude ⇢ place names)
 
+- **Map Matching** `examples <./docs/mapmatching.md#map-matching>`__, `website <https://www.mapbox.com/developers/api/map-matching/>`__
+
+  - Snap GPS traces to OpenStreetMap data
+
 - **Static Maps** `examples <./docs/static.md#static-maps>`__, `website <https://www.mapbox.com/developers/api/static/>`__
 
   - Generate standalone images from existing Mapbox mapids
diff --git a/mapbox/services/mapmatching.py b/mapbox/services/mapmatching.py
index 3dd5d92..cdeb1c6 100644
--- a/mapbox/services/mapmatching.py
+++ b/mapbox/services/mapmatching.py
@@ -17,15 +17,28 @@ class MapMatcher(Service):
             raise ValueError("{} is not a valid profile".format(profile))
         return profile
 
-    def match(self, feature, profile='mapbox.driving'):
+    def match(self, feature, gps_precision=None, profile='mapbox.driving'):
         profile = self._validate_profile(profile)
-        # todo validate single feature with linestring geometry up to 100 pts
+
+        # validate single feature with linestring geometry up to 100 pts
+        try:
+            assert feature['type'] == 'Feature'
+            assert feature['geometry']['type'] == 'LineString'
+            assert len(feature['geometry']['coordinates']) <= 100
+        except (TypeError, KeyError, AssertionError):
+            raise ValueError("feature must have LineString geometry "
+                             "with <= 100 points")
+
         geojson_line_feature = json.dumps(feature)
 
         uri = URITemplate('%s/{profile}.json' % self.baseuri).expand(
             profile=profile)
 
-        res = self.session.post(uri, data=geojson_line_feature,
+        params = None
+        if gps_precision:
+            params = {'gps_precision': gps_precision}
+
+        res = self.session.post(uri, data=geojson_line_feature, params=params,
                                 headers={'Content-Type': 'application/json'})
         self.handle_http_error(res)
 


Test patch:
diff --git a/tests/test_mapmatching.py b/tests/test_mapmatching.py
index 2277866..0791a88 100644
--- a/tests/test_mapmatching.py
+++ b/tests/test_mapmatching.py
@@ -39,6 +39,31 @@ def test_matching(line_feature):
     service = mapbox.MapMatcher(access_token='pk.test')
     res = service.match(line_feature)
     assert res.status_code == 200
+    assert res.json() == res.geojson()
+
+
+@responses.activate
+def test_matching_precision(line_feature):
+
+    body = '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"confidence":0.8165504318718629,"matchedPoints":[[13.418805122375488,52.5005989074707],[13.419145584106445,52.501094818115234],[13.419618606567383,52.50175094604492],[13.420042037963867,52.50233459472656],[13.420494079589844,52.50298309326172]],"indices":[0,1,2,3,4]},"geometry":{"type":"LineString","coordinates":[[13.418805,52.500599],[13.418851,52.500659],[13.419121,52.501057],[13.419146,52.501095],[13.419276,52.501286],[13.419446,52.501518],[13.419619,52.501753],[13.419981,52.502249],[13.420042,52.502335],[13.420494,52.502984]]}}]}'
+
+    responses.add(
+        responses.POST,
+        'https://api.mapbox.com/matching/v4/mapbox.cycling.json?gps_precision=4&access_token=pk.test',
+        match_querystring=True,
+        body=body, status=200,
+        content_type='application/json')
+
+    service = mapbox.MapMatcher(access_token='pk.test')
+    res = service.match(line_feature, gps_precision=4, profile='mapbox.cycling')
+    assert res.status_code == 200
+
+
+def test_invalid_feature():
+    service = mapbox.MapMatcher(access_token='pk.test')
+    with pytest.raises(ValueError) as exc:
+        service.match({'type': 'not-a-feature'})
+        assert 'feature must have' in exc.message
 
 
 def test_invalid_profile(line_feature):