예제 #1
0
def distance(ctx, features, profile, output):
    """The Distance API returns all travel times between
    many points (also known as Distance Matrix). This is often
    used as input for solving routing optimization problems.

      $ mapbox distance "[-122.681, 45.528]" "[-122.716, 45.525]"

    The output is a json object with a "durations" key
    containing a 2D array of travel times between waypoints.

    An access token is required, see `mapbox --help`.
    """
    stdout = click.open_file(output, 'w')
    access_token = (ctx.obj and ctx.obj.get('access_token')) or None
    service = mapbox.Distance(access_token=access_token)

    try:
        res = service.distances(
            features,
            profile=profile)
    except mapbox.errors.ValidationError as exc:
        raise click.BadParameter(str(exc))

    if res.status_code == 200:
        click.echo(res.text, file=stdout)
    else:
        raise MapboxCLIException(res.text.strip())
예제 #2
0
def test_distance():

    responses.add(
        responses.POST,
        'https://api.mapbox.com/distances/v1/mapbox/driving?access_token=pk.test',
        match_querystring=True,
        body='{"durations":[[0,4977,5951],[4963,0,9349],[5881,9317,0]]}',
        status=200,
        content_type='application/json')

    res = mapbox.Distance(access_token='pk.test').distances(points)
    assert res.status_code == 200
    assert list(res.json().keys()) == ["durations", ]
예제 #3
0
def test_distances_matrix():

    responses.add(
        responses.POST,
        'https://api.mapbox.com/distances/v1/mapbox/driving?access_token=pk.test',
        match_querystring=True,
        body='{"durations":[[0,4977,5951],[4963,0,9349],[5881,9317,0]]}',
        status=200,
        content_type='application/json')

    res = mapbox.Distance(access_token='pk.test').distances(points)
    matrix = res.json()['durations']
    # 3x3 list
    assert len(matrix) == 3
    assert len(matrix[0]) == 3
예제 #4
0
def test_distances_matrix():

    responses.add(
        responses.GET,
        'https://api.mapbox.com/directions-matrix/v1/mapbox/driving/-87.337875,36.539156;-86.577791,36.722137;-88.247685,36.922175?access_token=pk.test',
        match_querystring=True,
        body='{"durations":[[0,4977,5951],[4963,0,9349],[5881,9317,0]]}',
        status=200,
        content_type='application/json')

    with pytest.warns(MapboxDeprecationWarning):
        res = mapbox.Distance(access_token='pk.test').distances(points)
    matrix = res.json()['durations']
    # 3x3 list
    assert len(matrix) == 3
    assert len(matrix[0]) == 3
예제 #5
0
import click
import cligj

import mapbox
from mapboxcli.errors import MapboxCLIException


@click.command(short_help="Distance matrix of travel times between waypoints.")
@cligj.features_in_arg
@click.option('--profile', default="driving",
              type=click.Choice(mapbox.Distance().valid_profiles),
              help="Mapbox direction profile id")
@click.option('--output', '-o', default='-',
              help="Save output to a file.")
@click.pass_context
def distance(ctx, features, profile, output):
    """The Distance API returns all travel times between
    many points (also known as Distance Matrix). This is often
    used as input for solving routing optimization problems.

      $ mapbox distance "[-122.681, 45.528]" "[-122.716, 45.525]"

    The output is a json object with a "durations" key
    containing a 2D array of travel times between waypoints.

    An access token is required, see `mapbox --help`.
    """
    stdout = click.open_file(output, 'w')
    access_token = (ctx.obj and ctx.obj.get('access_token')) or None
    service = mapbox.Distance(access_token=access_token)
예제 #6
0
def test_profile_valid(profile):
    """Profiles are valid"""
    assert profile == mapbox.Distance(
        access_token='pk.test')._validate_profile(profile)
예제 #7
0
def test_profile_invalid():
    """'jetpack' is not a valid profile."""
    with pytest.raises(ValueError):
        mapbox.Distance(access_token='pk.test')._validate_profile('jetpack')
예제 #8
0
def test_class_attrs():
    """Get expected class attr values"""
    serv = mapbox.Distance()
    assert serv.api_name == 'distances'
    assert serv.api_version == 'v1'
예제 #9
0
def optimal_tour(features, mode, profile, out_points, solver):
    """
    A command line interface for solving the traveling salesman problem

    Input geojson features with point geometries
    and output the optimal tour as geojson feature collection.

     \b
      $ optimal_tour waypoints.geojson | geojson-summary
      19 points and 1 line

    If using geodesic or directions modes, input must be in lonlat coordinates

    Directions mode requires a Mapbox account and a valid token set as
    the MAPBOX_ACCESS_TOKEN environment variable.
    """
    log("Get point features")
    features = [f for f in features if f['geometry']['type'] == 'Point']
    if len(features) <= 2:
        raise click.UsageError(
            "Need at least 3 point features to create route")

    if mode != 'cartesian' and not is_lonlat(features):
        raise click.UsageError(
            "For this {} mode, input must be in lonlat coordinates".format(
                mode))

    log("Create travel cost matrix")
    if mode == 'cartesian':
        matrix = local_matrix(features, 'cartesian')
    elif mode == 'geodesic':
        matrix = local_matrix(features, 'geodesic')
    elif mode == 'directions':
        dist_api = mapbox.Distance()
        res = dist_api.distances(features, profile=profile)
        if res.status_code == 200:
            matrix = res.json()['durations']
        else:
            raise Exception(
                "Got a {0} error from the Distances API: {1}".format(
                    res.status_code, res.content))

    log("Prep data")
    matrix_sym = atsp_tsp(matrix, strategy="avg")

    outf = "/tmp/myroute.tsp"
    with open(outf, 'w') as dest:
        dest.write(dumps_matrix(matrix_sym, name="My Route"))

    log("Run TSP solver")
    tour = run(outf, start=0, solver=solver)
    order = tour['tour']

    features_ordered = [features[i] for i in order]

    log("Create lines connecting the tour")
    if mode == 'directions':
        # gather geojson linestring features along actual route via directions
        directions_api = mapbox.Directions()
        route_features = []
        for chunk in split_overlap(features_ordered + [features_ordered[0]],
                                   24):
            res = directions_api.directions(chunk, profile='mapbox.' + profile)
            if res.status_code == 200:
                route_features.append(res.geojson()['features'][0])
            else:
                raise Exception(
                    "Got a {0} error from the Directions API: {1}".format(
                        res.status_code, res.content))
    else:
        # Alternative, straight line distance between points
        route_coords = [f['geometry']['coordinates'] for f in features_ordered]
        route_coords.append(features_ordered[0]['geometry']['coordinates'])
        route_features = [{
            'type': 'Feature',
            'properties': {
                'tour': tour
            },
            'geometry': {
                'type': 'LineString',
                'coordinates': route_coords
            }
        }]

    # meld into one geojson feature collection
    log("Output feature collection")
    out_features = route_features
    if out_points:
        out_features += features_ordered

    collection = {'type': 'FeatureCollection', 'features': out_features}

    click.echo(json.dumps(collection))