<filename>mapbox/services/static_style.py<gh_stars>100-1000
import json
import warnings

from uritemplate import URITemplate

from mapbox import errors
from mapbox.services.base import Service
from mapbox.utils import normalize_geojson_featurecollection


def validate_lat(val):
    if val < -85.0511 or val > 85.0511:
        raise errors.InvalidCoordError(
            "Latitude must be between -85.0511 and 85.0511")
    return val


def validate_lon(val):
    if val < -180 or val > 180:
        raise errors.InvalidCoordError(
            "Longitude must be between -180 and 180")
    return val


def validate_image_size(val):
    if not 1 < val < 1280:
        raise errors.ImageSizeError(
            "Image height and width must be between 1 and 1280")
    return val


def validate_overlay(val):
    if len(val) > 2073:  # limit is 2083 minus the 'geojson()'
        raise errors.InputSizeError(
            "GeoJSON is too large for the static maps API, "
            "must be less than 2073 characters")
    return val


def validate_pitch(val):
    if val < 0 or val > 60:
        raise errors.InvalidCoordError("Pitch must be between 0 and 60")
    return val


def validate_bearing(val):
    if val < 0 or val > 365:
        raise errors.InvalidCoordError("Bearing must be between 0 and 365")
    return val


class StaticStyle(Service):
    """Access to the Static Map API V1"""

    api_name = 'styles'
    api_version = 'v1'

    def tile(self, username, style_id, z, x, y, tile_size=512, retina=False):
        "/styles/v1/{username}/{style_id}/tiles/{tileSize}/{z}/{x}/{y}{@2x}"
        if tile_size not in (256, 512):
            raise errors.ImageSizeError('tile_size must be 256 or 512 pixels')

        pth = '/{username}/{style_id}/tiles/{tile_size}/{z}/{x}/{y}'

        values = dict(username=username, style_id=style_id,
                      tile_size=tile_size, z=z, x=x, y=y)

        uri = URITemplate(self.baseuri + pth).expand(**values)
        if retina:
            uri += '@2x'
        res = self.session.get(uri)
        self.handle_http_error(res)
        return res

    def wmts(self, username, style_id):
        pth = '/{}/{}/wmts'.format(username, style_id)
        uri = URITemplate(self.baseuri + pth)
        res = self.session.get(uri)
        self.handle_http_error(res)
        return res

    def image(self, username, style_id, lon=None, lat=None, zoom=None, features=None,
              pitch=0, bearing=0, width=600, height=600, retina=None, sort_keys=False,
              attribution=None, logo=None, before_layer=None, twox=None):

        params = {}
        if attribution is not None:
            params['attribution'] = 'true' if attribution else 'false'
        if logo is not None:
            params['logo'] = 'true' if logo else 'false'
        if before_layer is not None:
            params['before_layer'] = before_layer

        # twox as a deprecated alias for retina
        if retina is None:
            if twox is not None:
                warnings.warn('twox is a deprecated alias for retina',
                              errors.MapboxDeprecationWarning)
                retina = twox
        else:
            if twox is not None:
                raise errors.ValidationError('Conflicting args; Remove twox and use retina')

        if lon is not None and lat is not None and zoom is not None:
            auto = False
            lat = validate_lat(lat)
            lon = validate_lon(lon)
        else:
            auto = True

        pitch = validate_pitch(pitch)
        bearing = validate_bearing(bearing)
        width = validate_image_size(width)
        height = validate_image_size(height)

        values = dict(
            username=username,
            style_id=style_id,
            pitch=str(pitch),
            bearing=str(bearing),
            lon=str(lon),
            lat=str(lat),
            zoom=str(zoom),
            auto=auto,
            width=str(width),
            height=str(height))

        if features:
            collection = normalize_geojson_featurecollection(features)
            values['overlay'] = json.dumps(
                collection, separators=(',', ':'), sort_keys=sort_keys)

            validate_overlay(values['overlay'])

            pth = '/{username}/{style_id}/static/geojson({overlay})/'
            if auto:
                # TODO what about {bearing} and {pitch}
                pth += 'auto/{width}x{height}'
            else:
                pth += '{lon},{lat},{zoom},{bearing},{pitch}/{width}x{height}'
        else:
            if auto:
                raise errors.InvalidCoordError(
                    "Must provide features or lat, lon, z")

            # No overlay
            pth = ('/{username}/{style_id}/static/'
                   '{lon},{lat},{zoom},{bearing},{pitch}/{width}x{height}')

        uri = URITemplate(self.baseuri + pth).expand(**values)

        # @2x handled separately to avoid HTML escaping the ampersand
        if retina:
            uri += '@2x'

        res = self.session.get(uri, params=params)
        self.handle_http_error(res)
        return res
