/**
* @module server/api/validators/geojson
*/
const _ = require('lodash');
/**
* Returns a ValDSL validator that checks whether a value is a well-formatted bounding box string.
*
* A bounding box string is composed of 4 comma-separated numbers:
*
* * The first 2 numbers are the coordinates (longitude & latitude) of the bounding box's south-west corner.
* * The last 2 numbers are the coordinates (longitude & latitude) of the bounding box's north-east corner.
*
* @returns {function} A validator function.
*/
exports.bboxString = function() {
return async function(ctx) {
// Make sure the value is a string.
const bbox = ctx.get('value');
if (!_.isString(bbox)) {
return ctx.addError({
validator: 'bboxString',
cause: 'wrongType',
message: 'must be a string'
})
}
// Make sure it has 4 values.
const coordinates = bbox.split(',').map(value => parseFloat(value));
if (coordinates.length != 4) {
return ctx.addError({
validator: 'bboxString',
cause: 'wrongLength',
actualLength: coordinates.length,
message: `must have 4 comma-separated coordinates; got ${coordinates.length}`
});
}
// Make sure the 4 values are valid longitudes and latitudes.
await Promise.all([
validateBboxStringCoordinate(ctx, coordinates, 0, coordinateCtx => validateLongitude(coordinateCtx)),
validateBboxStringCoordinate(ctx, coordinates, 1, coordinateCtx => validateLatitude(coordinateCtx)),
validateBboxStringCoordinate(ctx, coordinates, 2, coordinateCtx => validateLongitude(coordinateCtx)),
validateBboxStringCoordinate(ctx, coordinates, 3, coordinateCtx => validateLatitude(coordinateCtx))
]);
};
};
/**
* Returns a ValDSL validator that checks whether a value is a GeoJSON object of type Point.
*
* const { point: validateGeoJsonPoint } = require('../validators/geojson');
*
* this.validate(
* this.json('geometry'),
* validateGeoJsonPoint()
* )
*
* @returns {function} A validator function.
*/
exports.point = function() {
return function(ctx) {
return ctx.series(
ctx.type('object'),
ctx.properties('type', 'coordinates'),
ctx.parallel(
ctx.validate(
ctx.json('/type'),
ctx.required(),
ctx.type('string'),
ctx.equals('Point')
),
ctx.validate(
ctx.json('/coordinates'),
ctx.required(),
ctx.type('array'),
validateCoordinates,
ctx.parallel(
ctx.validate(
ctx.json('/0'),
ctx.type('number'),
validateLongitude
),
ctx.validate(
ctx.json('/1'),
ctx.type('number'),
validateLatitude
)
)
)
)
);
};
}
function validateBboxStringCoordinate(ctx, coordinates, i, callback) {
return ctx.validate(coordinateCtx => {
// Make the error indicate the index of the invalid coordinate
// within the bbox string, e.g. "bbox[1]".
coordinateCtx.set({
location: `${coordinateCtx.get('location')}[${i}]`,
value: coordinates[i]
});
return callback(coordinateCtx);
});
}
function validateCoordinates(ctx) {
const coordinates = ctx.get('value');
if (!_.isArray(coordinates) || coordinates.length != 2) {
ctx.addError({
validator: 'coordinates',
message: 'must be an array of 2 numbers (longitude & latitude)'
});
}
}
function validateLongitude(ctx) {
const longitude = ctx.get('value');
if (!_.isFinite(longitude) || longitude < -180 || longitude > 180) {
ctx.addError({
validator: 'longitude',
message: 'must be a number between -180 and 180'
});
}
}
function validateLatitude(ctx) {
const latitude = ctx.get('value');
if (!_.isFinite(latitude) || latitude < -90 || latitude > 90) {
ctx.addError({
validator: 'latitude',
message: 'must be a number between -90 and 90'
});
}
}