Source

components/Map/Map.js

import React, { useEffect, useState } from 'react';
import {
  FormGroup,
  FormControlLabel,
  Accordion,
  Switch,
  Tooltip,
} from '@material-ui/core';
import GoogleMap from 'google-map-react/dist/index.js';
import Api from '../../lib/Http/Api';
import Marker from './Marker';
import {
  getMappedRodentData,
  getMappedCrimeData,
  getMappedComplaintData,
  switchesTooltips,
} from '../../services/mapService';
import './Map.scss';

const emptyProp = { positions: [], options: {} };

/**
 * A Map component that includes the visualization of New York and listing location markers.
 *
 * @component
 * @prop {array} listings The listings to be shown on the map
 * @prop {function} setListing An event handler when a listing is clicked on
 * @prop {string} placeSearch The propagated search value from the Searchbar
 * @prop {array} recommendations Recommended listings based on the selected listing
 * @prop {Object} selected The currently clicked on listing
 */
const Map = (props) => {
  const defaultProps = {
    center: { lat: 40.72, lng: -74 },
    zoom: 11,
  };
  const {
    listings,
    setListing,
    placeSearch,
    recommendations,
    selected,
  } = props;
  const [toggle, setToggle] = useState({
    crime: false,
    transit: false,
    rodent: false,
    recommendations: false,
  });
  const [crime, setCrime] = useState();
  const [rodent, setRodent] = useState();
  const [complaints, setComplaints] = useState();
  const [apiData, setApiData] = useState();
  const [places, setPlaces] = useState([]);

  const crimeData = getMappedCrimeData(crime);
  const rodentData = getMappedRodentData(rodent);
  const complaintData = getMappedComplaintData(complaints);

  //compose heatmap data
  const data = [
    toggle.crime ? crimeData : emptyProp,
    toggle.rodent ? rodentData : emptyProp,
    toggle.complaints ? complaintData : emptyProp,
  ];

  // switches to toggle heatmaps and recommendations
  const Switches = (props) => {
    return props.inputs.map((e, idx) => {
      return (
        <Tooltip title={switchesTooltips[e]} key={idx}>
          <FormControlLabel
            control={
              <Switch checked={toggle[e]} onChange={handleChange} name={e} />
            }
            label={
              !props.label
                ? e.charAt(0).toUpperCase() + e.slice(1)
                : props.label
            }
            style={{ marginLeft: '16px' }}
            disabled={
              props.disabled ? props.disabled : !crime && !rodent && !complaints
            }
          />
        </Tooltip>
      );
    });
  };

  useEffect(() => {
    const loadData = async () => {
      const crimeRes = await Api.get('layers/crime');
      const healthRes = await Api.get('layers/health');
      const complaintRes = await Api.get('layers/complaints');
      setCrime(crimeRes.data.entries);
      setRodent(healthRes.data.entries);
      setComplaints(complaintRes.data.entries);
    };
    loadData();
  }, []);

  useEffect(() => {
    // if a searchterm was entered, call google places api to look
    // for matches that are close to new york
    if (placeSearch) {
      const { map, maps } = apiData;
      const service = new maps.places.PlacesService(map);
      const request = {
        query: placeSearch,
        fields: ['name', 'geometry'],
        location: { lat: 40, lng: -74 },
        radius: '50000',
      };
      service.textSearch(request, (results, status) => {
        if (status === 200 || status === 'OK') {
          setPlaces(results);
        }
      });
    } else {
      setPlaces([]);
    }
  }, [placeSearch]);

  const handleChange = (e) => {
    setToggle({ ...toggle, [e.target.name]: e.target.checked });
  };

  const apiHasLoaded = (map, maps) => {
    // google maps internal functions
    setApiData({ map, maps });
  };

  const getMiddlePosition = (values) => {
    const { g, i } = values;
    return (g + i) / 2;
  };

  return (
    <>
      <Accordion>
        <div
          style={{ minHeight: '50vh', width: '100%' }}
          id='map-container-inner'
        >
          <GoogleMap
            yesIWantToUseGoogleMapApiInternals
            bootstrapURLKeys={{
              key: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
              libraries: ['visualization', 'places', 'geometry'],
            }}
            defaultCenter={defaultProps.center}
            defaultZoom={defaultProps.zoom}
            heatmapLibrary={true}
            heatmap={data}
            layerTypes={toggle.transit ? ['TransitLayer'] : []}
            style={{ height: '500px', paddingBottom: '50px' }}
            onGoogleApiLoaded={({ map, maps }) => apiHasLoaded(map, maps)}
          >
            {!toggle.recommendations
              ? listings &&
                listings.map((e) => {
                  return (
                    <Marker
                      lat={e.latitude}
                      lng={e.longitude}
                      key={e.id}
                      setListing={setListing}
                      id={e.id}
                      type={'listings'}
                    />
                  );
                })
              : null}
            {places &&
              places.map((e) => {
                return (
                  <Marker
                    lng={getMiddlePosition(e.geometry.viewport.La)}
                    lat={getMiddlePosition(e.geometry.viewport.Ua)}
                    key={e.place_id}
                    id={e.place_id}
                    type={'marker'}
                    place={e}
                  />
                );
              })}
            {recommendations &&
              recommendations.map((e) => {
                return (
                  <Marker
                    lat={e.latitude}
                    lng={e.longitude}
                    key={e.id}
                    setListing={setListing}
                    id={e.id}
                    type={'recommendations'}
                  />
                );
              })}
            {selected ? (
              <Marker
                lat={selected.latitude}
                lng={selected.longitude}
                key={selected.id}
                setListing={setListing}
                id={selected.id}
                type={'selected'}
              />
            ) : null}
          </GoogleMap>
        </div>
      </Accordion>
      <Accordion style={{ display: 'flex' }}>
        <FormGroup row className='form-group-switches'>
          <Switches
            disabled={recommendations && recommendations.length < 1}
            inputs={['recommendations']}
            label={'Show Recommendations Only'}
          />
          <div>
            <Switches inputs={['crime', 'rodent', 'complaints', 'transit']} />
          </div>
        </FormGroup>
      </Accordion>
    </>
  );
};
export default Map;