Integrating geosearch with client maps

This guide will show you how to fetch locations and show them on a map, using data from the Geosearch endpoint.

Most client-side map libraries offer an onRegionChangeComplete event or similar, which is triggered when the user moves the map.
This event should contain data for the current map position, like latitude, longitude, longitudeDelta, latitudeDelta.

To get the correct amount of locations and aggregations, you need to know the aggregationPrecision, latitude, longitude and distance. Fortunately all of this can be retrieved or calculated from the map's Region data.

Let's look at the main function that will handle that. This sample code is written in TypeScript, but the logic will be the same in all programming languages.

// This function calculates the necessary data and fetches locations from the Geosearch endpoint
async function getLocationsForRegion(region: Region) {
  // get data from the map region
  const { latitude, latitudeDelta, longitudeDelta } = region;

  // calculate the distance for latitudeDelta
  const latDistance = (latitudeDelta / 360) * 2 * Math.PI * EARTH_RADIUS;

  // calculate the distance for longitudeDelta
  const lonDistance =
    (longitudeDelta / 360) *
    2 *
    Math.PI *
    EARTH_RADIUS *
    Math.cos((latitude * Math.PI) / 180);

  // use the larger of the two distances
  const distance = Math.max(latDistance, lonDistance);

  // calculate aggregation precision based on latitude delta
  const aggregationPrecision = getClusteringPrecision(region);

  // fetch locations from the Geosearch endpoint
  try {
    const locations = await driveClient.fetchLocations({
      latitude: region.latitude,
      longitude: region.longitude,
      distance: distance,
      aggregate: true,
      aggregationPrecision: aggregationPrecision,
    });
    return locations;
  } catch (e) {
    console.log(e);
    return {
      locations: [],
      aggregates: [],
    };
  }
}

The aggregationPrecision plays an important role in what you get from the Geosearch endpoint, it controls how tightly clustered locations should be.
Let's dive into the details now: how to get a correct aggregationPrecision and how to adjust it to your needs.

// minimal supported aggregation precision via Geosearch API is 2
const MINIMUM_PRECISION = 2;
// maximal supported aggregation precision via Geosearch API is 9
const MAX_PRECISION = 9;
//controls how tightly markers should be clustered
const K = 0.00001;

// calculate precision from a region's latitudeDelta
function precisionFrom(latitudeDelta: number) {
  const x = latitudeDelta / K;
  switch (true) {
    case x > 1800000:
      return MINIMUM_PRECISION;
    case x > 162000:
      return 3;
    case x > 54000:
      return 4;
    case x > 10800:
      return 5;
    case x > 5400:
      return 6;
    case x > 4500:
      return 7;
    case x > 3600:
      return 8;
    default:
      return MAX_PRECISION;
  }
}

export function getClusteringPrecision(region: Region) {
    return precisionFrom(region.latitudeDelta),
}

The easiest way to manipulate how tightly locations are aggregated is via the K constant, here you can see examples for different values. Different K values generate very different output, so adjust it to your application needs.

📘

It is strongly advised to cluster locations as much as possible, because getting a lot of locations at once and rendering it on the map might be slow, both because of the Geosearch response time and the rendering of markers on the map. It will also be harder for the user to press on the marker desired.
| K Value | 0.000001 | 0.00001 | 0.0001 |
| ------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Image | | | |

The last step is rendering the markers on map, this differs a lot between popular map libraries, but let's look at an example built with react-native and the react-native-maps library. We will render red pins for locations and blue pins for aggregations.

type LocationResponse = {
  locations: Locations; // type for locations from Geosearch response
  aggregates: Aggregates; // type for aggregations from Geosearch response
};

// set default state
const [response, setResponse] = useState({
  locations: [],
  aggregates: [],
});

const Map = () => {
  const onRegionChangeComplete = async (region: Region) => {
    // use getLocationsForRegion function showed before
    const { locations, aggregates } = await getLocationsForRegion(region);
    // set fetched locations to state
    setResponse({
      locations,
      aggregates,
    });
  };

  return (
    // add onRegionChangeComplete event handler to fetch locations
    // when user finishes moving map
    <MapView onRegionChangeComplete={onRegionChangeComplete}>
      {response.locations.map((location) => (
        // render location markers
        <Marker
          key={location.id}
          coordinate={{
            latitude: parseFloat(location.coordinates.latitude),
            longitude: parseFloat(location.coordinates.longitude),
          }}
          pinColor="red"
        />
      ))}

      {response.aggregates.map((aggregate) => (
        // render aggregates markers
        <Marker
          key={aggregate.coordinates.latitude + aggregate.coordinates.longitude}
          coordinate={{
            latitude: parseFloat(aggregate.coordinates.latitude),
            longitude: parseFloat(aggregate.coordinates.longitude),
          }}
          pinColor="blue"
        />
      ))}
    </MapView>
  );
};

This is just an example to show how to display locations as on a map, the Drive API is not meant to be called from client apps. Instead, it should be integrated on your backend, as described in this page.