text: /**
   * helper sanitizer methods for geo parameters
   */
  var groups = require('./_groups'),
      check = require('check-types'),
      wrap = require('./wrap'),
      _ = require('lodash');
  
  /**
   * Parse and validate rect parameter
   *
   * @param {string} key_prefix
   * @param {object} clean
   * @param {object} raw
   * @param {bool} bbox_is_required
   */
  function sanitize_rect( key_prefix, clean, raw, bbox_is_required ) {
    // calculate full property names from the key_prefix
    var properties = [ 'min_lat', 'max_lat', 'min_lon', 'max_lon' ].map(function(prop) {
      return key_prefix + '.' + prop;
    });
  
    // sanitize the rect property group, this throws an exception if
    // the group is not complete
    var bbox_present;
    if (bbox_is_required) {
      bbox_present = groups.required(raw, properties);
    } else {
      bbox_present = groups.optional(raw, properties);
    }
  
    // don't bother checking individual elements if bbox is not required
    // and not present
    if (!bbox_present) { return; }
  
    sanitize_bbox_min_max(raw, key_prefix);
    sanitize_bbox_bounds(raw, key_prefix);
  
    // use sanitize_coord to set values in `clean`
    properties.forEach(function(prop) {
      sanitize_coord(prop, clean, raw, true);
    });
  }
  
    // validate max is greater than min for lat and lon
  function sanitize_bbox_min_max(raw, key_prefix) {
    ['lat', 'lon'].forEach(function(dimension) {
      const max = parseFloat(raw[`${key_prefix}.max_${dimension}`]);
      const min = parseFloat(raw[`${key_prefix}.min_${dimension}`]);
  
      if (max <= min) {
        throw new Error(`${key_prefix}.min_${dimension} (${min}) must be less than ${key_prefix}.max_${dimension} (${max})`);
      }
    });
  }
  
  // validate lat/lon values are within bounds
  function sanitize_bbox_bounds(raw, key_prefix) {
    const bounds = [ { dimension: 'lat', range: 90},
                     { dimension: 'lon', range: 180}];
  
    bounds.forEach(function(bound) {
      const values = {
        max: parseFloat(raw[`${key_prefix}.max_${bound.dimension}`]),
        min: parseFloat(raw[`${key_prefix}.min_${bound.dimension}`])
      };
  
      ['min', 'max'].forEach(function(prefix) {
        if (Math.abs(values[prefix]) > bound.range) {
          const key =`${key_prefix}.${prefix}_${bound.dimension}`;
          throw new Error(`${key} value ${values[prefix]} is outside range -${bound.range},${bound.range}`);
        }
      });
    });
  }
  
  /**
   * Parse and validate circle parameter
   *
   * @param {string} key_prefix
   * @param {object} clean
   * @param {object} raw
   * @param {bool} circle_is_required
   */
  function sanitize_circle( key_prefix, clean, raw, circle_is_required ) {
    // sanitize both a point and a radius if radius is present
    // otherwise just sanittize the point
    if( check.assigned( raw[ key_prefix + '.radius' ] ) ){
      sanitize_coord( key_prefix + '.radius', clean, raw, true );
      sanitize_point( key_prefix, clean, raw, true);
    } else {
      sanitize_point( key_prefix, clean, raw, circle_is_required);
    }
  }
  
  /**
   * Parse and validate point parameter
   *
   * @param {string} key_prefix
   * @param {object} clean
   * @param {object} raw
   * @param {bool} point_is_required
   */
  function sanitize_point( key_prefix, clean, raw, point_is_required ) {
  
    // calculate full property names from the key_prefix
    var properties = [ 'lat', 'lon'].map(function(prop) {
      return key_prefix + '.' + prop;
    });
  
    // sanitize the rect property group, this throws an exception if
    // the group is not complete
    var point_present;
    if (point_is_required) {
      point_present = groups.required(raw, properties);
    } else {
      point_present = groups.optional(raw, properties);
    }
  
    // don't bother checking individual elements if point is not required
    // and not present
    if (!point_present) { return; }
  
    // check each property individually. now that it is known a bbox is present,
    // all properties must exist, so pass the true flag for coord_is_required
    properties.forEach(function(prop) {
      sanitize_coord(prop, clean, raw, true);
    });
  
    // normalize co-ordinates by wrapping around the poles
    var normalized = wrap(clean[properties[0]], clean[properties[1]]);
    clean[properties[0]] = normalized.lat;
    clean[properties[1]] = normalized.lon;
  }
  
  /**
   * Validate lat,lon values
   *
   * @param {string} key - which key to validate
   * @param {object} clean - cleaned parameters object
   * @param {object} raw - the raw request object
   * @param {bool} latlon_is_required
   */
  function sanitize_coord( key, clean, raw, latlon_is_required ) {
    var parsedValue = parseFloat( raw[key] );
  
    if ( _.isFinite( parsedValue ) ) {
      clean[key] = parsedValue;
    }
    else if (latlon_is_required) {
      throw new Error(`missing param '${key}'`);
    }
  }
  
  module.exports = {
    sanitize_rect: sanitize_rect,
    sanitize_coord: sanitize_coord,
    sanitize_circle: sanitize_circle,
    sanitize_point: sanitize_point
  };
  