@vtmap/vtmap-sdk-js
Version:
JS SDK for accessing Viettelmaps APIs
458 lines (425 loc) • 14.3 kB
JavaScript
'use strict';
var polyline = require('@mapbox/polyline');
var v = require('./service-helpers/validator');
var createServiceFactory = require('./service-helpers/create-service-factory');
var pick = require('./service-helpers/pick');
/**
* Static Images API service.
*
* Learn more about this service and its responses in
* [the HTTP service documentation](https://docs.mapbox.com/api/maps/#static-images).
*/
var Static = {};
/**
* Get a static map image.
*
* **If you just want the URL for the static map image, create a request
* and get it's URL with `MapiRequest#url`.** This is what prior versions of the
* SDK returned.
*
* @param {Object} config
* @param {string} config.ownerId - The owner of the map style.
* @param {string} config.styleId - The map's style ID.
* @param {number} config.width - Width of the image in pixels, between 1 and 1280.
* @param {number} config.height - Height of the image in pixels, between 1 and 1280.
* @param {'auto'|Object} config.position - If `"auto"`, the viewport will fit the bounds of the overlay(s).
* If an object, it could be either a bbox or a coordinate and a zoom as the required parameters.
* ` bbox` (required): Is an array of coordinate pairs, with the first coordinate pair referring to the southwestern
* corner of the box (the minimum longitude and latitude) and the second referring to the northeastern corner of the box (the maximum longitude and latitude).
* Otherwise the maps' position is described by an object with the following properties:
* `coordinates` (required): [`coordinates`](#coordinates) for the center of image.
* `zoom` (required): Between 0 and 20.
* `bearing` (optional): Between 0 and 360.
* `pitch` (optional): Between 0 and 60.
* @param {string} config.padding - A string value that denotes the minimum padding per side of the image.
* This can only be used with auto or bbox. The value resembles the CSS specification for padding and accepts 1-4 integers without units
*
* @param {Array<Overlay>} [config.overlays] - Overlays should be in z-index
* order: the first in the array will be on the bottom; the last will be on
* the top. Overlays are objects that match one of the following types:
* [`SimpleMarkerOverlay`](#simplemarkeroverlay),
* [`CustomMarkerOverlay`](#custommarkeroverlay),
* [`PathOverlay`](#pathoverlay),
* [`GeoJsonOverlay`](#geojsonoverlay)
*
* @param {boolean} [config.highRes=false]
* @param {string} [config.before_layer] - The ID of the style layer
* that overlays should be inserted *before*.
* @param {Object} [config.addlayer] - Adds a Mapbox style layer to the map's style at render time. Can be combined with before_layer.
* @param {Array} [config.setfilter] - Applies a filter to an existing layer in a style using Mapbox's expression syntax. Must be used with layer_id.
* @param {string} [config.layer_id] - Denotes the layer in the style that the filter specified in setfilter is applied to.
* @param {boolean} [config.attribution=true] - Whether there is attribution
* on the map image.
* @param {boolean} [config.logo=true] - Whether there is a Mapbox logo
* on the map image.
* @return {MapiRequest}
*
* @example
* staticClient.getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* coordinates: [12, 13],
* zoom: 4
* }
* })
* .send()
* .then(response => {
* const image = response.body;
* });
*
* @example
* staticClient.getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* // position as a bounding box
* bbox: [-77.04,38.8,-77.02,38.91],
* },
* padding: '4'
* })
* .send()
* .then(response => {
* const image = response.body;
* });
*
* @example
* staticClient.getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* coordinates: [12, 13],
* zoom: 3
* },
* overlays: [
* // Simple markers.
* {
* marker: {
* coordinates: [12.2, 12.8]
* }
* },
* {
* marker: {
* size: 'large',
* coordinates: [14, 13.2],
* label: 'm',
* color: '#000'
* }
* },
* {
* marker: {
* coordinates: [15, 15.2],
* label: 'airport',
* color: '#ff0000'
* }
* },
* // Custom marker
* {
* marker: {
* coordinates: [10, 11],
* url: 'https://upload.wikimedia.org/wikipedia/commons/6/6f/0xff_timetracker.png'
* }
* }
* ]
* })
* .send()
* .then(response => {
* const image = response.body;
* });
*
* @example
* // To get the URL instead of the image, create a request
* // and get its URL without sending it.
* const request = staticClient
* .getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* coordinates: [12, 13],
* zoom: 4
* }
* });
* const staticImageUrl = request.url();
* // Now you can open staticImageUrl in a browser.
*
* @example
* // Filter all buildings that have a height value that is less than 300 meters
* const request = staticClient
* .getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* coordinates: [12, 13],
* zoom: 4
* },
* setfilter: [">","height",300],
* layer_id: 'building',
* });
* const staticImageUrl = request.url();
* // Now you can open staticImageUrl in a browser.
*
* @example
* // Paint all the state and province level boundaries associated with the US worldview with a dashed line and insert it below the road-label layer
* const request = staticClient
* .getStaticImage({
* ownerId: 'mapbox',
* styleId: 'streets-v11',
* width: 200,
* height: 300,
* position: {
* coordinates: [12, 13],
* zoom: 4
* },
* addlayer: {"id":"better-boundary","type":"line","source":"composite","source-layer":"admin","filter":["all",["==",["get","admin_level"],1],["==",["get","maritime"],"false"],["match",["get","worldview"],["all","US"],true,false]],"layout":{"line-join":"bevel"},"paint":{"line-color":"%236898B3","line-width":1.5,"line-dasharray":[1.5,1]}},
* before_layer: 'road-label',
* });
* const staticImageUrl = request.url();
* // Now you can open staticImageUrl in a browser.
*/
Static.getStaticImage = function(config) {
v.assertShape({
ownerId: v.required(v.string),
styleId: v.required(v.string),
width: v.required(v.range([1, 1280])),
height: v.required(v.range([1, 1280])),
position: v.required(
v.oneOfType(
v.oneOf('auto'),
v.strictShape({
coordinates: v.required(v.coordinates),
zoom: v.required(v.range([0, 20])),
bearing: v.range([0, 360]),
pitch: v.range([0, 60])
}),
v.strictShape({ bbox: v.required(v.arrayOf(v.number)) })
)
),
padding: v.string,
overlays: v.arrayOf(v.plainObject),
highRes: v.boolean,
before_layer: v.string,
addlayer: v.plainObject,
setfilter: v.plainArray,
layer_id: v.string,
attribution: v.boolean,
logo: v.boolean
})(config);
var encodedOverlay = (config.overlays || [])
.map(function(overlayItem) {
if (overlayItem.marker) {
return encodeMarkerOverlay(overlayItem.marker);
}
if (overlayItem.path) {
return encodePathOverlay(overlayItem.path);
}
return encodeGeoJsonOverlay(overlayItem.geoJson);
})
.join(',');
var encodedPosition = encodePosition(config.position);
var encodedDimensions = config.width + 'x' + config.height;
if (config.highRes) {
encodedDimensions += '@2x';
}
var preEncodedUrlParts = [encodedOverlay, encodedPosition, encodedDimensions]
.filter(Boolean)
.join('/');
var query = {};
if (config.attribution !== undefined) {
query.attribution = String(config.attribution);
}
if (config.logo !== undefined) {
query.logo = String(config.logo);
}
if (config.before_layer !== undefined) {
query.before_layer = config.before_layer;
}
if (config.addlayer !== undefined) {
query.addlayer = JSON.stringify(config.addlayer); // stringify to retain object shape
}
if (config.setfilter !== undefined) {
query.setfilter = JSON.stringify(config.setfilter); // stringify to retain array shape
}
if (config.layer_id !== undefined) {
query.layer_id = config.layer_id;
}
if (config.padding !== undefined) {
query.padding = config.padding;
}
if (config.setfilter !== undefined && config.layer_id === undefined) {
throw new Error('Must include layer_id in setfilter request');
}
if (
(config.setfilter !== undefined || config.addlayer !== undefined) &&
config.position === 'auto' &&
config.overlays === undefined
) {
throw new Error(
'Auto extent cannot be used with style parameters and no overlay'
);
}
if (config.addlayer !== undefined && config.setfilter !== undefined) {
throw new Error(
'addlayer and setfilter cannot be used in the same request'
);
}
if (
config.padding !== undefined &&
config.position !== 'auto' &&
config.position.bbox === undefined
) {
throw new Error(
'Padding can only be used with auto or bbox as the position.'
);
}
if (config.position.bbox !== undefined && config.position.bbox.length !== 4) {
throw new Error('bbox must be four coordinates');
}
return this.client.createRequest({
method: 'GET',
path: '/styles/v1/:ownerId/:styleId/static/' + preEncodedUrlParts,
params: pick(config, ['ownerId', 'styleId']),
query: query,
encoding: 'binary'
});
};
function encodePosition(position) {
if (position === 'auto') return 'auto';
if (position.bbox) return JSON.stringify(position.bbox);
return position.coordinates
.concat([
position.zoom,
position.pitch && !position.bearing ? 0 : position.bearing, // if pitch is set, but bearing is not, bearing must be 0
position.pitch === 0 ? undefined : position.pitch
])
.filter(function(el) {
return el === 0 || el; // filter out undefined and allow 0 values
})
.join(',');
}
function encodeMarkerOverlay(o) {
if (o.url) {
return encodeCustomMarkerOverlay(o);
}
return encodeSimpleMarkerOverlay(o);
}
/**
* A simple marker overlay.
* @typedef {Object} SimpleMarkerOverlay
* @property {Object} marker
* @property {[number, number]} marker.coordinates - `[longitude, latitude]`
* @property {'large'|'small'} [marker.size='small']
* @property {string} [marker.label] - Marker symbol. Options are an alphanumeric label `a`
* through `z`, `0` through `99`, or a valid [Maki](https://www.mapbox.com/maki/)
* icon. If a letter is requested, it will be rendered in uppercase only.
* @property {string} [marker.color] - A 3- or 6-digit hexadecimal color code.
*/
function encodeSimpleMarkerOverlay(o) {
v.assertShape({
coordinates: v.required(v.coordinates),
size: v.oneOf('large', 'small'),
label: v.string,
color: v.string
})(o);
var result = o.size === 'large' ? 'pin-l' : 'pin-s';
if (o.label) {
result += '-' + String(o.label).toLowerCase();
}
if (o.color) {
result += '+' + sanitizeHexColor(o.color);
}
result += '(' + o.coordinates.join(',') + ')';
return result;
}
/**
* A marker overlay with a custom image.
* @typedef {Object} CustomMarkerOverlay
* @property {Object} marker
* @property {[number, number]} marker.coordinates - `[longitude, latitude]`
* @property {string} marker.url
*/
function encodeCustomMarkerOverlay(o) {
v.assertShape({
coordinates: v.required(v.coordinates),
url: v.required(v.string)
})(o);
var result = 'url-' + encodeURIComponent(o.url);
result += '(' + o.coordinates.join(',') + ')';
return result;
}
/**
* A stylable line.
* @typedef {Object} PathOverlay
* @property {Object} path
* @property {Array<Coordinates>} path.coordinates - An array of coordinates
* describing the path.
* @property {number} [path.strokeWidth]
* @property {string} [path.strokeColor]
* @property {number} [path.strokeOpacity] - Must be paired with strokeColor.
* @property {string} [path.fillColor] - Must be paired with strokeColor.
* @property {number} [path.fillOpacity] - Must be paired with fillColor.
*/
function encodePathOverlay(o) {
v.assertShape({
coordinates: v.required(v.arrayOf(v.coordinates)),
strokeWidth: v.number,
strokeColor: v.string,
strokeOpacity: v.number,
fillColor: v.string,
fillOpacity: v.number
})(o);
if (o.strokeOpacity !== undefined && o.strokeColor === undefined) {
throw new Error('strokeOpacity requires strokeColor');
}
if (o.fillColor !== undefined && o.strokeColor === undefined) {
throw new Error('fillColor requires strokeColor');
}
if (o.fillOpacity !== undefined && o.fillColor === undefined) {
throw new Error('fillOpacity requires fillColor');
}
var result = 'path';
if (o.strokeWidth) {
result += '-' + o.strokeWidth;
}
if (o.strokeColor) {
result += '+' + sanitizeHexColor(o.strokeColor);
}
if (o.strokeOpacity) {
result += '-' + o.strokeOpacity;
}
if (o.fillColor) {
result += '+' + sanitizeHexColor(o.fillColor);
}
if (o.fillOpacity) {
result += '-' + o.fillOpacity;
}
// polyline expects each coordinate to be in reversed order: [lat, lng]
var reversedCoordinates = o.coordinates.map(function(c) {
return [c[1], c[0]];
});
var encodedPolyline = polyline.encode(reversedCoordinates);
result += '(' + encodeURIComponent(encodedPolyline) + ')';
return result;
}
/**
* GeoJSON to overlay the map.
* @typedef {Object} GeoJsonOverlay
* @property {Object} geoJson - Valid GeoJSON.
*/
function encodeGeoJsonOverlay(o) {
v.assert(v.required(v.plainObject))(o);
return 'geojson(' + encodeURIComponent(JSON.stringify(o)) + ')';
}
function sanitizeHexColor(color) {
return color.replace(/^#/, '');
}
module.exports = createServiceFactory(Static);