159 lines
4.9 KiB
Python
159 lines
4.9 KiB
Python
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
|