Repository: mapbox/mapbox-sdk-py
Pull request #256 (2018-07-07 23:49:59)

Tilequery API
This attempts to address #255.  I would be happy to make changes to the code, tests, etc.  Thank you!

critical-path: @sgillies 

This is ready for review.  Thank you!
sgillies: Thanks, @critical-path. Your work is good and appreciated, but we need time to give it a proper review.
critical-path: @sgillies 

Thank you for being open to my contributions!

Patch:
diff --git a/README.rst b/README.rst
index a90b7dc..d19fdcb 100644
--- a/README.rst
+++ b/README.rst
@@ -59,6 +59,10 @@ Services
   - Manage editable collections of GeoJSON features
   - Persistent storage for custom geographic data
 
+- **Tilequery V4** `examples <./docs/tilequery.md#tilequery>`__, `website <https://www.mapbox.com/api-documentation/?language=Python#tilequery>`__
+
+  - Retrieve data about specific features from a vector tileset
+
 - **Maps V4** `examples <./docs/maps.md#maps>`__, `website <https://www.mapbox.com/api-documentation/?language=Python#maps>`__
 
   - Retrieve an image tile, vector tile, or UTFGrid in the specified format
diff --git a/docs/access_tokens.md b/docs/access_tokens.md
index 48993b5..fc5cb5c 100644
--- a/docs/access_tokens.md
+++ b/docs/access_tokens.md
@@ -1,20 +1,28 @@
 # Access Tokens
 
-All Mapbox API's require an access token. Your Mapbox access token can be exported
-into your environment
+All Mapbox APIs require an access token. Thus all service object constructors
+take an `access_token` keyword argument. Access can be granted to a geocoding
+service, for example, like so:
 
-```bash
+```python
+>>> from mapbox import Geocoder
+>>> geocoder = Geocoder(access_token="pk.YOUR_ACCESS_TOKEN")
+
+```
+
+Please note that an actual token string must be used. Tokens may be generated
+using the web application at [https://www.mapbox.com/account/access-tokens](https://www.mapbox.com/account/access-tokens).
 
-export MAPBOX_ACCESS_TOKEN="YOUR_ACCESS_TOKEN"
+Your Mapbox access token can also be set in the environment of your program
 
+```bash
+export MAPBOX_ACCESS_TOKEN="pk.YOUR_ACCESS_TOKEN"
 ```
 
-and it will be found automatically when creating a new instance. We'll use the `Geocoder` in this 
-example but the same applies for all `mapbox` classes.
+and it will be found automatically when creating a new instance. We'll use the
+`Geocoder` in this example but the same applies for all `mapbox` classes.
 
 ```python
-
->>> from mapbox import Geocoder
 >>> geocoder = Geocoder()
 >>> import os
 >>> geocoder.session.params['access_token'] == os.environ['MAPBOX_ACCESS_TOKEN']
@@ -22,16 +30,6 @@ True
 
 ```
 
-Or it can be passed explicitly to the constructor.
-
-```python
-
-
->>> YOUR_ACCESS_TOKEN = os.environ['MAPBOX_ACCESS_TOKEN']
->>> geocoder = Geocoder(access_token=YOUR_ACCESS_TOKEN)
-
-```
-
 Best practice for access tokens and geocoding sources is to create a new
 instance for each new access token or source dataset.
 
diff --git a/docs/index.rst b/docs/index.rst
index 45a9acc..3f32719 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -57,7 +57,11 @@ Services
   - Manage editable collections of GeoJSON features
   - Persistent storage for custom geographic data
 
-- **Maps V4** `examples <./docs/maps.md#maps>`__, `website <https://www.mapbox.com/api-documentation/?language=Python#maps>`__
+- **Tilequery V4** `examples <./tilequery.html#tilequery>`__, `website <https://www.mapbox.com/api-documentation/?language=Python#tilequery>`__
+
+  - Retrieve data about specific features from a vector tileset
+
+- **Maps V4** `examples <./maps.html#maps>`__, `website <https://www.mapbox.com/api-documentation/?language=Python#maps>`__
 
   - Retrieve an image tile, vector tile, or UTFGrid in the specified format
   - Retrieve vector features from Mapbox Editor projects as GeoJSON or KML
@@ -111,6 +115,7 @@ Documentation
    analytics.md
    mapmatching.md
    static_style.md
+   tilequery.md
    maps.md
    api/mapbox.rst
    api/mapbox.services.rst
diff --git a/docs/tilequery.md b/docs/tilequery.md
new file mode 100644
index 0000000..0aa3284
--- /dev/null
+++ b/docs/tilequery.md
@@ -0,0 +1,50 @@
+# Tilequery
+
+The `Tilequery` class provides access to the Mapbox Tilequery API.  You can import it from either the `mapbox` module or the `mapbox.services.tilequery` module.
+
+__mapbox__:
+
+```python
+>>> from mapbox import Tilequery
+
+```
+
+__mapbox.services.tilequery__:
+
+```python
+>>> from mapbox.services.tilequery import Tilequery
+
+```
+
+See https://www.mapbox.com/api-documentation/#tilequery for general documentation of the API.
+
+Use of the Tilequery API requires an access token, which you should set in your environment.  For more information, see the [access tokens](access_tokens.md) documentation.
+
+## Tilequery Method
+
+The public method of the `Tilequery` class provides access to the Tilequery API and returns an instance of [`requests.Response`](http://docs.python-requests.org/en/latest/api/#requests.Response).
+
+
+## Usage: Retrieving Features
+
+Instantiate `Tilequery`.
+
+```python
+>>> tilequery = Tilequery()
+
+```
+
+Call the `tilequery` method, passing in values for `map_id`, `lon`, and `lat`.  Pass in values for optional arguments as necessary - `radius`, `limit`, `dedupe`, `geometry`, and `layers`.
+
+```python
+>>> response = tilequery.tilequery("mapbox.mapbox-streets-v10", 0.0, 1.1)
+
+```
+
+Evaluate whether the request succeeded, and retrieve the features from the response object.
+
+```python
+>>> if response.status_code == 200:
+...     features = response.json()
+
+```
diff --git a/mapbox/__init__.py b/mapbox/__init__.py
index a3449a5..a2b3f17 100644
--- a/mapbox/__init__.py
+++ b/mapbox/__init__.py
@@ -12,4 +12,5 @@ from .services.static import Static
 from .services.static_style import StaticStyle
 from .services.uploads import Uploader
 from .services.analytics import Analytics
-from .services.maps import Maps
+from .services.tilequery import Tilequery
+from .services.maps import Maps
\ No newline at end of file
diff --git a/mapbox/services/tilequery.py b/mapbox/services/tilequery.py
new file mode 100644
index 0000000..5186350
--- /dev/null
+++ b/mapbox/services/tilequery.py
@@ -0,0 +1,222 @@
+"""The Tilequery class provides access to Mapbox's Tilequery API."""
+
+from mapbox.errors import (
+    InvalidCoordError,
+    InvalidParameterError
+)
+
+from mapbox.services.base import Service
+
+from uritemplate import URITemplate
+
+
+class Tilequery(Service):
+    """Access to Tilequery API V4
+
+    Attributes
+    ----------
+    api_name : str
+        The API's name.
+
+    api_version : str
+        The API's version number.
+
+    valid_geometries : list
+        The possible values for geometry.
+
+    base_uri : str
+        The API's base URI, currently https://api.mapbox.com/v4.
+    """
+
+    api_name = "tilequery"
+
+    api_version = "v4"
+
+    valid_geometries = [
+        "linestring",
+        "point",
+        "polygon"
+    ]
+
+    @property
+    def base_uri(self):
+        """Forms base URI."""
+
+        return "https://{}/{}".format(self.host, self.api_version)
+
+    def _validate_lon(self, lon):
+        """Validates longitude, raising error if invalid."""
+
+        if (lon < -180) or (lon > 180):
+            raise InvalidCoordError(
+                "Longitude must be between -180 and 180"
+            )
+
+        return lon
+
+    def _validate_lat(self, lat):
+        """Validates latitude, raising error if invalid."""
+
+        if (lat < -85.0511) or (lat > 85.0511):
+            raise InvalidCoordError(
+                "Latitude must be between -85.0511 and 85.0511"
+            )
+
+        return lat
+
+    def _validate_radius(self, radius):
+        """Validates radius, raising error if invalid."""
+
+        if radius is not None\
+            and radius < 0:
+                raise InvalidParameterError(
+                    "Radius must be greater than or equal to 0"
+                )
+
+        return radius
+
+    def _validate_limit(self, limit):
+        """Validates limit, raising error if invalid."""
+
+        if limit is not None\
+            and ((limit < 1) or (limit > 50)) :
+                raise InvalidParameterError(
+                    "Limit must be between 1 and 50"
+                )
+
+        return limit
+
+    def _validate_geometry(self, geometry):
+        """Validates geometry, raising error if invalid."""
+
+        if geometry is not None\
+            and geometry not in self.valid_geometries:
+                raise InvalidParameterError(
+                    "{} is not a valid geometry".format(geometry)
+                )
+
+        return geometry
+
+    def tilequery(self, map_id, lon, lat, radius=None, limit=None, 
+                  dedupe=None, geometry=None, layers=None):
+
+        """Returns data about specific features 
+        from a vector tileset.
+
+        Parameters
+        ----------
+        map_id : str or list
+            The tileset's unique identifier in the
+            format username.id.
+            
+            map_id may be either a str with one value 
+            or a list with multiple values.
+
+        lon : float
+            The longitude to query, where -180 
+            is the minimum value and 180 is the 
+            maximum value.
+
+        lat : float
+            The latitude to query, where -85.0511 
+            is the minimum value and 85.0511 is the 
+            maximum value.
+
+        radius : int, optional
+            The approximate distance in meters to 
+            query, where 0 is the minimum value.
+            (There is no maximum value.)
+
+            If None, the default value is 0.
+
+        limit : int, optional
+            The number of features to return, where
+            1 is the minimum value and 50 is the
+            maximum value.
+
+            If None, the default value is 5.
+
+        dedupe : bool, optional
+            Whether to remove duplicate results.
+
+            If None, the default value is True.
+
+        geometry : str, optional
+            The geometry type to query.
+
+        layers : list, optional
+            The list of layers to query.
+
+            If a specified layer does not exist,
+            then the Tilequery API will skip it.  
+            If no layers exist, then the API will 
+            return an empty GeoJSON FeatureCollection.
+
+        Returns
+        -------
+        request.Response
+            The response object with a GeoJSON
+            FeatureCollection of features at or near
+            the specified longitude and latitude.
+        """
+
+        # If map_id is a list, then convert it to a str
+        # of comma-separated values.
+
+        if isinstance(map_id, list):
+            map_id = ",".join(map_id)
+
+        # Validate lon and lat.
+
+        lon = self._validate_lon(lon)
+        lat = self._validate_lat(lat)
+
+        # Create dict to assist in building URI resource path.
+
+        path_values = dict(
+            map_id=map_id,
+            api_name=self.api_name,
+            coordinates="{},{}".format(lon, lat)
+        )
+        
+        # Build URI resource path.
+
+        path_part = "/{map_id}/{api_name}/{coordinates}.json"
+        uri = URITemplate(self.base_uri + path_part).expand(**path_values)
+
+        # Build URI query_parameters.
+
+        query_parameters = dict()
+
+        if radius is not None:
+            radius = self._validate_radius(radius)
+            query_parameters["radius"] = radius
+
+        if limit is not None:
+            limit = self._validate_limit(limit)
+            query_parameters["limit"] = limit
+
+        if dedupe is not None:
+            query_parameters["dedupe"] = "true" if True else "false"
+
+        if geometry is not None:
+            geometry = self._validate_geometry(geometry)
+            query_parameters["geometry"] = geometry
+
+        if layers is not None:
+            query_parameters["layers"] = ",".join(layers)
+
+        # Send HTTP GET request.
+
+        response = self.session.get(uri, params=query_parameters)
+        self.handle_http_error(response)
+
+        # To be consistent with other services, 
+        # add geojson method to response object.
+        
+        def geojson():
+            return response.json()
+        
+        response.geojson = geojson
+        
+        return response


Test patch:
diff --git a/tests/test_tilequery.py b/tests/test_tilequery.py
new file mode 100644
index 0000000..d52f3aa
--- /dev/null
+++ b/tests/test_tilequery.py
@@ -0,0 +1,658 @@
+from mapbox.errors import (
+    InvalidCoordError,
+    InvalidParameterError
+)
+
+from mapbox.services.tilequery import Tilequery
+
+from pytest import (
+    mark,
+    raises
+)
+
+from responses import (
+    activate,
+    add,
+    GET
+)
+
+
+def test_object_properties():
+    tilequery = Tilequery()
+
+    assert tilequery.api_name
+    assert tilequery.api_version
+    assert tilequery.valid_geometries
+    assert tilequery.base_uri
+
+
+@mark.parametrize("lon", [-181, 181])
+def test_validate_lon_invalid(lon):
+    tilequery = Tilequery()
+
+    with raises(InvalidCoordError) as exception:
+        tilequery._validate_lon(lon)
+
+
+@mark.parametrize("lon", [-180, 0, 180])
+def test_validate_lon_valid(lon):
+    tilequery = Tilequery()
+    result = tilequery._validate_lon(lon)
+    assert result == lon
+
+
+@mark.parametrize("lat", [-86, 86])
+def test_validate_lat_invalid(lat):
+    tilequery = Tilequery()
+
+    with raises(InvalidCoordError) as exception:
+        tilequery._validate_lat(lat)
+
+
+@mark.parametrize("lat", [-85.0511, 0, 85.0511])
+def test_validate_lat_valid(lat):
+    tilequery = Tilequery()
+    result = tilequery._validate_lat(lat)
+    assert result == lat
+
+
+def test_validate_radius_invalid():
+    tilequery = Tilequery()
+
+    with raises(InvalidParameterError) as exception:
+        tilequery._validate_radius(-1)
+
+
+@mark.parametrize("radius", [0, 1000000])
+def test_validate_radius_valid(radius):
+    tilequery = Tilequery()
+    result = tilequery._validate_radius(radius)
+    assert result == radius
+
+
+@mark.parametrize("limit", [0, 51])
+def test_validate_limit_invalid(limit):
+    tilequery = Tilequery()
+
+    with raises(InvalidParameterError) as exception:
+        tilequery._validate_limit(limit)
+
+
+@mark.parametrize("limit", [1, 25, 50])
+def test_validate_limit_valid(limit):
+    tilequery = Tilequery()
+    result = tilequery._validate_limit(limit)
+    assert result == limit
+
+
+def test_validate_geometry_invalid():
+    tilequery = Tilequery()
+
+    with raises(InvalidParameterError) as exception:
+        tilequery._validate_geometry("invalid")
+
+
+@mark.parametrize("geometry", ["linestring", "point", "polygon"])
+def test_validate_radius_geometry(geometry):
+    tilequery = Tilequery()
+    result = tilequery._validate_geometry(geometry)
+    assert result == geometry
+
+
+@activate
+def test_tilequery_one_mapid():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1
+    )
+
+    assert response.status_code == 200
+
+    
+@activate
+def test_tilequery_two_mapids():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v9%2Cmapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        ["mapbox.mapbox-streets-v9", "mapbox.mapbox-streets-v10"],
+        0.0,
+        1.1
+    )
+
+    assert response.status_code == 200
+    
+    
+@activate
+def test_tilequery_with_radius():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_limit():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&limit=25",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        limit=25
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_dedupe():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&dedupe=true",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        dedupe=True
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_geometry():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&geometry=linestring",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        geometry="linestring"
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_layers():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&layers=layer0%2Clayer1%2Clayer2",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        layers=[
+            "layer0",
+            "layer1",
+            "layer2"
+        ]
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_and_limit():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_and_dedupe():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&dedupe=true",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        dedupe=True
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_and_geometry():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&geometry=linestring",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        geometry="linestring"
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_and_layers():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&layers=layer0%2Clayer1%2Clayer2",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        layers=[
+            "layer0",
+            "layer1",
+            "layer2"
+        ]
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_and_dedupe():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&dedupe=true",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        dedupe=True
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_and_geometry():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&geometry=linestring",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        geometry="linestring"
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_and_layers():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&layers=layer0%2Clayer1%2Clayer2",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        layers=[
+            "layer0",
+            "layer1",
+            "layer2"
+        ]
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_dedupe_and_geometry():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&dedupe=true" +
+        "&geometry=linestring",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        dedupe=True,
+        geometry="linestring"
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_dedupe_and_layers():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&dedupe=true" +
+        "&layers=layer0%2Clayer1%2Clayer2",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        dedupe=True,
+        layers=[
+            "layer0",
+            "layer1",
+            "layer2"
+        ]
+    )
+
+    assert response.status_code == 200
+
+
+@activate
+def test_tilequery_with_radius_limit_dedupe_geometry_and_layers():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test" +
+        "&radius=25" +
+        "&limit=25" +
+        "&dedupe=true" +
+        "&geometry=linestring" +
+        "&layers=layer0%2Clayer1%2Clayer2",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1,
+        radius=25,
+        limit=25,
+        dedupe=True,
+        geometry="linestring",
+        layers=[
+            "layer0",
+            "layer1",
+            "layer2"
+        ]
+    )
+
+    assert response.status_code == 200
+    
+    
+@activate
+def test_tilequery_geojson_method():
+    add(
+        method=GET,
+        url="https://api.mapbox.com" +
+        "/v4" +
+        "/mapbox.mapbox-streets-v10" +
+        "/tilequery" +
+        "/0.0%2C1.1.json" +
+        "?access_token=pk.test",
+        match_querystring=True,
+        body="{\"key\": \"value\"}",
+        status=200
+    )
+
+    tilequery = Tilequery(access_token="pk.test")
+
+    response = tilequery.tilequery(
+        "mapbox.mapbox-streets-v10",
+        0.0,
+        1.1
+    )
+
+    assert response.status_code == 200
+    assert response.geojson() == response.json()