department-of-veterans-affairs/vets-website

View on GitHub
src/applications/facility-locator/actions/mapbox/genBBoxFromAddress.js

Summary

Maintainability
B
4 hrs
Test Coverage
import mbxGeo from '@mapbox/mapbox-sdk/services/geocoding';
import mapboxClient from '../../components/MapboxClient';
import {
  BOUNDING_RADIUS,
  EXPANDED_BOUNDING_RADIUS,
  MAPBOX_QUERY_TYPES,
  CountriesList,
} from '../../constants';
import {
  GEOCODE_STARTED,
  SEARCH_FAILED,
  SEARCH_QUERY_UPDATED,
  GEOCODE_COMPLETE,
  GEOCODE_FAILED,
} from '../../utils/actionTypes';
import { radiusFromBoundingBox } from '../../utils/facilityDistance';

const mbxClient = mbxGeo(mapboxClient);

/**
 * Calculates a bounding box (±BOUNDING_RADIUS°) centering on the current
 * address string as typed by the user.
 *
 * @param {Object<T>} query Current searchQuery state (`searchQuery.searchString` at a minimum)
 * @returns {Function<T>} A thunk for Redux to process OR a failure action object on bad input
 */
export const genBBoxFromAddress = (query, expandedRadius = false) => {
  // Prevent empty search request to Mapbox, which would result in error, and
  // clear results list to respond with message of no facilities found.
  if (!query.searchString) {
    return {
      type: SEARCH_FAILED,
      error: 'Empty search string/address. Search cancelled.',
    };
  }

  return dispatch => {
    dispatch({ type: GEOCODE_STARTED });

    // commas can be stripped from query if Mapbox is returning unexpected results
    let types = MAPBOX_QUERY_TYPES;
    // check for postcode search
    const isPostcode = query.searchString.match(/^\s*\d{5}\s*$/);

    if (isPostcode) {
      types = ['postcode'];
    }

    mbxClient
      .forwardGeocode({
        countries: CountriesList,
        types,
        autocomplete: false, // set this to true when build the predictive search UI (feature-flipped)
        query: query.searchString,
      })
      .send()
      .then(({ body: { features } }) => {
        const zip =
          features[0].context.find(v => v.id.includes('postcode')) || {};
        const coordinates = features[0].center;
        const zipCode = zip.text || features[0].place_name;
        const featureBox = features[0].box;

        dispatch({
          type: GEOCODE_COMPLETE,
          payload: query.usePredictiveGeolocation
            ? features.map(feature => ({
                placeName: feature.place_name,
                placeType: feature.place_type[0],
                bbox: feature.bbox,
                center: feature.center,
              }))
            : [],
        });

        const searchBoundingRadius = expandedRadius
          ? EXPANDED_BOUNDING_RADIUS
          : BOUNDING_RADIUS;

        let minBounds = [
          coordinates[0] - searchBoundingRadius,
          coordinates[1] - searchBoundingRadius,
          coordinates[0] + searchBoundingRadius,
          coordinates[1] + searchBoundingRadius,
        ];

        if (featureBox) {
          minBounds = [
            Math.min(featureBox[0], coordinates[0] - searchBoundingRadius),
            Math.min(featureBox[1], coordinates[1] - searchBoundingRadius),
            Math.max(featureBox[2], coordinates[0] + searchBoundingRadius),
            Math.max(featureBox[3], coordinates[1] + searchBoundingRadius),
          ];
        }
        const radius = radiusFromBoundingBox(
          features,
          query?.facilityType === 'provider',
        );
        dispatch({
          type: SEARCH_QUERY_UPDATED,
          payload: {
            ...query,
            radius,
            context: zipCode,
            id: Date.now(),
            inProgress: true,
            position: {
              latitude: coordinates[1],
              longitude: coordinates[0],
            },
            searchCoords: {
              lat: features[0].geometry.coordinates[1],
              lng: features[0].geometry.coordinates[0],
            },
            bounds: minBounds,
            zoomLevel: features[0].id.split('.')[0] === 'region' ? 7 : 9,
            currentPage: 1,
            mapBoxQuery: {
              placeName: features[0].place_name,
              placeType: features[0].place_type[0],
            },
            searchArea: null,
          },
        });
      })
      .catch(_ => {
        dispatch({ type: GEOCODE_FAILED });
        dispatch({ type: SEARCH_FAILED, error: { type: 'mapBox' } });
      });
  };
};