DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Edited on

Mapbox Api Integration with React Native

In this post we will be integrating multiple mapbox api's like Search, Retrieve, Geocoding, Reverse Geocoding, POIs, Directions.

React Native is a popular framework for building mobile applications using JavaScript and the Mapbox API is a set of APIs and SDKs for building custom maps and location-based applications. Mapbox provides several APIs that allow developers to add map functionality to their applications, including search, retrieve, geocoding, reverse geocoding, POIs, and directions. Here's a brief overview of each API:

Search API: Mapbox's Search API allows you to query for places based on a search term or location. You can search for places such as businesses, landmarks, and addresses. The Search API returns a list of suggested places that match your query.

Retrieve API: Mapbox's Retrieve API allows you to retrieve detailed information about a specific place. You can retrieve information such as the place's address, phone number, website, and hours of operation.

Geocoding API: Mapbox's Geocoding API allows you to convert an address or place name into geographic coordinates (latitude and longitude). This API can be used to geocode addresses for use in mapping applications.

Reverse Geocoding API: Mapbox's Reverse Geocoding API allows you to convert geographic coordinates into an address or place name. This API can be used to display the name of a location on a map or to find the nearest address to a set of coordinates.

POI (Point of Interest) API: Mapbox's POI API allows you to search for and display points of interest on a map. You can search for POIs such as restaurants, gas stations, and parks.

Directions API: Mapbox's Directions API allows you to get directions between two or more locations. You can specify the mode of transportation (driving, walking, cycling, or transit) and the API will return turn-by-turn directions and estimated travel time.

To use these APIs in your React Native application, you will need to install the Mapbox SDK and obtain an API key from Mapbox. You can then make API requests using the Mapbox SDK's APIs or by using an HTTP client library such as Axios.

Image description

Directions

The Mapbox Directions API will show you how to get where you're going. With the Directions API, you can:

Calculate optimal driving, walking, and cycling routes using traffic- and incident-aware routing
Produce turn-by-turn instructions
Produce routes with up to 25 coordinates for the driving, driving-traffic, walking, and cycling profiles
Calculate routes for electric vehicles to reach destinations with optimal charging stops as well as battery prediction
https://docs.mapbox.com/api/navigation/directions/

Search

The Mapbox Search Service includes two APIs: the Mapbox Geocoding API and the Mapbox Search API(to enable mapbox search feature dev need to connect with mapbox sales for enabling it).
https://docs.mapbox.com/api/search/

1. Geocoding
The Mapbox Geocoding API allows you to do forward and reverse geocoding operations. Forward Geocoding takes text in the form of an address or place and converts it to geographic coordinates (latitude/longitude). Reverse geocoding takes geographic coordinates (latitude/longitude) and converts it into an address or place in text form.
https://docs.mapbox.com/api/search/geocoding/

2. Search
The Mapbox Search API enables search suggestions and feature retrieval for an interactive Search experience.
The Search API includes six different endpoints: /suggest, /retrieve, /forward, /permanent/forward, /reverse, and /permanent/reverse.
https://docs.mapbox.com/api/search/search/

API COLLECTION:

  1. GET(Direction api)
curl --location --request GET 'https://api.mapbox.com/directions/v5/mapbox/driving/-122.42,37.78;-77.03,38.91?geometries=geojson&access_token=pk.eyJ1IjoiZGVlcGFrMTIyMSIsImEiOiJjbDhiZXQ5cmEwcXhzM3FvNTFhNDNpNms3In0.x6FPXW7P3lIWTKPs6PMaJA'
Enter fullscreen mode Exit fullscreen mode
  1. GET(Gives suggestion list of inputted text)
curl --location --request GET 'https://api.mapbox.com/search/v1/suggest/mec?language=ar&session_token=&country=SA&access_token=sk.eyJ1Ijoid2FzYWx0bWFwcyIsImEiOiJjbGM2OXUzODkwMjhhM3BtZ2x4dmtvd21hIn0.uLcik68kLtKKCqKvpLzD6A'
Enter fullscreen mode Exit fullscreen mode
  1. POST(Retrieve suggested item's more info)
curl --location -g --request POST 'https://api.mapbox.com/search/v1/retrieve?session_token=[GENERATED-UUID]&access_token=pk.eyJ1Ijoid2FzYWx0bWFwcyIsImEiOiJjbDlkdHhtOW8xbjVzM3dwOGFzcm0xbmI1In0.aKPN7qWIPGX8hbKcOV_hbA' \
--header 'Content-Type: application/json' \
--data-raw '{
                    "id": "GpLnHR-w8BMB2AxARJ1nZYvXueJQn4PQ1GKu3mT11_5uLytWHFBsI7LnCBhFmrtosuecy3HLMPRTbcte0bBJYqe4XmLO3DCCJoeqcrJwzBeK_n1Hrfq5zBS034KiwOObxR1Wmv4MTKFW0vanZjPlO3j9QOJZJLjrIaW2Olfdc91WmEAqB7yv2TxTpZFfOMmrvqhVWtRJyB_tgq5NzndtDxlG1wsXAl_1urUn0VsmEe3VpBkyOm6MnBZwUKWMq3RELeTduG4jG1zNElVvjjS_aiZfg_VXsfbhit5J4efCwiResxecDKrLlaDbLRuDGOeFOC8XCb5_eM1O35zZEi7U_HujRi_v5bMinyfvnQfo0kWJTIVmgvHyfUS5eyzOzdaaEWwoblfFfo4MxQA-j_wdFOMmsDvyHL9BwOaGufjaWuN-6gW0eZBqrY3tJnwfYgY9liRlUMdc8FxCuzg0D0QDCduFp8H2nUXI8ALZV2wIN510XgJAg3r80T85quTp80dBRyXvP4R7_9XG5mFXcflsRTeCZsjaRDBVC400ff-06rHPVBKtHtuIGqjWE7p9AEjf17RX9fwY8MFocA1JZrRrTDwHQFFBCV-wbcbUGL4s50BLn2jkkClPNr0-yiDt14AJg3JP0NDPJa73hjRtC2i5Jm74VqPy_XK632rgeMHKB8IDyabJjKPJvQk-EPE0LL3YgR-JH4wC4UCOMOxPciuBoZ4hxF3GChra-fiQRVF6M6c4N5r323922nyIYIv9sX1RS3c7DcnWRZ0KsXc5PWD-hd8ZR2uLN8nMVwwak2u0axjh2WzrUeD27Yk_5ZIhNxxWsH8VNNyVfMT5Ig2FT3_FYJj-Hb-KwSv629ohr1kEWeNWH4LhI_jmCAIzzleoVk3j7XrRKtUb8lUqWye4ah7f5GceHl4gANhPM2fdDMZDbO8tUOuepEWoO5BQRGJiChM4h-85cJ5JO7zDSzsaA1WwFQf9EyPdhEaJB8tN8UNAmpF4vtpM3HsvGmnv7AY63oLj0VkusigK-UEPvxu4_3bixk6PDCTwZXsJEpYB_TcXwKXmrPnKbrYmDqt9psvWG8SlziKs3YYyMIs5HoP1eVQpQGmR06YQ8hxw8O2XFQdRUKvptHYDiwuY03R2YGDspiYu1UD2fj7muraZNZsk-VWFjRfuztunsTe-vtuJtqdyilyXGpWgft9t14GXOl-F5HfnMqYP_tzlCW1CedCchrRG_WdDL0IWfxF_LhkK4BjSoh7o2-pb4gLxEJ-k0--xYRIxPZgpRZ9YrHdNi8hXYsPQFwv6x91nw7jSPAZXcwvgYb8VCOgJrca9D6BHzXAD1VXXKi-9h9LnYkUA1cGKQ1p_o4sQU2OLslOD3jgNTOVRzePfkvbqkjXeiIdWlzuz2o2fvdyaxym7jWR-1qiIFzeXIaCeT746pEmLBw=="
                }'
Enter fullscreen mode Exit fullscreen mode
  1. GET(Gives list of POIs)
curl --location --request GET 'https://api.mapbox.com/geocoding/v5/mapbox.places/fast food;college;university.json?type=poi&proximity=-74.70850,40.78375&access_token=pk.eyJ1IjoiZGVlcGFrMTIyMSIsImEiOiJjbDhiZXQ5cmEwcXhzM3FvNTFhNDNpNms3In0.x6FPXW7P3lIWTKPs6PMaJA'
Enter fullscreen mode Exit fullscreen mode
  1. GET(Gives info about inputed text))
curl --location --request GET 'https://api.mapbox.com/geocoding/v5/mapbox.places/Los%20Angeles.json?access_token=pk.eyJ1Ijoid2FzYWx0bWFwcyIsImEiOiJjbDlkdHhtOW8xbjVzM3dwOGFzcm0xbmI1In0.aKPN7qWIPGX8hbKcOV_hbA'
Enter fullscreen mode Exit fullscreen mode
  1. GET(Gives info about provided coordinates)
curl --location --request GET 'https://api.mapbox.com/search/v1/reverse/13.329048,52.51258?language=en&access_token=pk.eyJ1Ijoid2FzYWx0bWFwcyIsImEiOiJjbDlkdHhtOW8xbjVzM3dwOGFzcm0xbmI1In0.aKPN7qWIPGX8hbKcOV_hbA&country=SA'
Enter fullscreen mode Exit fullscreen mode

Code:

Mapview.js

import MapboxGL, {Camera, PointAnnotation, MarkerView} from '@rnmapbox/maps';
import {MAP_BOX_ACCESS_TOKEN} from '../../utils/constants/constants';
import * as turf from '@turf/turf';
import SearchLocationInput from './SearchLocationInput';

MapboxGL.setAccessToken(MAP_BOX_ACCESS_TOKEN);
const ANNOTATION_SIZE = 30;

const MAP_VIEW = ({navigation}) => {
  let _map = useRef(null);
  let camera = useRef(null);
  let pointAnnotation = useRef(null);
  const [poiList, setpoiList] = useState([]);
  const [currentPinCoordinate, setcurrentPinCoordinate] = useState([
    46.6753, 24.7136,
  ]);
  // const [currentSelectedPoi, setCurrentSelectedPoi] = useState({});
  const [routeGeoJOSN, setrouteGeoJOSN] = useState();
  const [centerOfLineString, setcenterOfLineString] = useState();

  useEffect(() => {
    if (currentPinCoordinate && currentPinCoordinate.length > 0) {
      console.log('currentPinCoordinate', currentPinCoordinate);
    }
  }, [currentPinCoordinate]);

  const defaultCamera = {
    centerCoordinate: [46.6753, 24.7136],
    zoomLevel: 15,
  };


  const onPressInterestSite = async name => {
    let response = await fetch(
      'https://api.mapbox.com/geocoding/v5/mapbox.places/mosque.json?' +
        new URLSearchParams({
          type: 'poi',
          proximity: '46.6753,24.7136',
          access_token: MAP_BOX_ACCESS_TOKEN,
          limit: 10,
        }),
    );
    let data = await response.json();
    console.log('onPressInterestSite', data);
    setpoiList(data);
  };

  const onSelectPoiFunc = async payload => {
    console.log('poi:', payload);
    let coords = payload?.center;
    // setCurrentSelectedPoi(payload);

    let response = await fetch(
      `https://api.mapbox.com/directions/v5/mapbox/driving/${currentPinCoordinate[0]},${currentPinCoordinate[1]};${coords[0]},${coords[1]}?` +
        new URLSearchParams({
          geometries: 'geojson',
          access_token: MAP_BOX_ACCESS_TOKEN,
        }),
    );
    let data = await response.json();
    let lineStringGeoJSON = {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          properties: {},
          geometry: data?.routes[0]?.geometry,
        },
      ],
    };
    setrouteGeoJOSN(lineStringGeoJSON);

    let allCoordinates = lineStringGeoJSON?.features[0]?.geometry?.coordinates;
    console.log(
      'onPressInterestSiteDirection',
      data,
      lineStringGeoJSON,
      allCoordinates[Math.round(allCoordinates.length / 2)],
    );
    let featuresCenter = turf.points(allCoordinates);

    let center = turf.center(featuresCenter);
    setcenterOfLineString(center?.geometry?.coordinates);
  };

  const RouteLineString = () => (
    <MapboxGL.ShapeSource id="routeLine" shape={routeGeoJOSN}>
      <MapboxGL.LineLayer id="lines" style={styles.mapBox} />
    </MapboxGL.ShapeSource>
  );

  const CustomCalloutView = ({message}) => {
    return (
      <View style={styles.callOutContainer}>
        <View style={styles.callOutInner} />
        <View style={[styles.triangle, styles.arrowDown]} />
      </View>
    );
  };

  const onDragEnd = payload => {
    let centerCoordinate = payload?.geometry?.coordinates;
    setcurrentPinCoordinate(centerCoordinate);
    camera?.current?.setCamera({
      centerCoordinate,
      zoomLevel: defaultCamera?.zoomLevel,
      animationDuration: 1000,
      animationMode: 'flyTo',
    });
  };

  const selectedItemResDropdown = payload => {
    console.log('SELECTED_DROPDOWN_ITEM_RESPONSE', payload);
    let centerCoordinate = payload?.geometry?.coordinates;
    setcurrentPinCoordinate(centerCoordinate);
    camera?.current?.setCamera({
      centerCoordinate,
      zoomLevel: defaultCamera?.zoomLevel,
      animationDuration: 3000,
      animationMode: 'flyTo',
    });
  };

  return (
    <View style={styles.page}>
      <TouchableOpacity
        style={styles.tempGoback}
        onPress={() => navigation.goBack()}
      />

      {/* REUSABLE MARKER SEARCH COMPONENT */}
      <SearchLocationInput
        textInputText={''}
        selectedItemRes={res => selectedItemResDropdown(res)}
        currentPinCoordinate={currentPinCoordinate}
      />
      {/* REUSABLE MARKER SEARCH COMPONENT */}

      <View style={styles.container}>
        <MapboxGL.MapView
          ref={_map}
          style={styles.map}
          styleURL={MapboxGL.StyleURL.Street}
          localizeLabels={true}
          attributionEnabled={false}
          logoEnabled={false}>
          <Camera ref={camera} defaultSettings={defaultCamera} />
          <PointAnnotation
            key={1}
            id={'1'}
            coordinate={currentPinCoordinate}
            draggable
            onDragEnd={onDragEnd}
            ref={pointAnnotation}>
            <View style={styles.annotationContainer}>
              <Image
                source={{uri: 'https://reactnative.dev/img/tiny_logo.png'}}
                style={styles.imgStyle}
                onLoad={() => pointAnnotation.current?.refresh()}
              />
            </View>
          </PointAnnotation>
          {poiList?.features && poiList?.features.length > 0
            ? poiList?.features.map((value, index) => (
                <MarkerView key={value.id} coordinate={value?.center}>
                  <Pressable onPress={() => onSelectPoiFunc(value)} key={index}>
                    <View style={styles.marker} />
                  </Pressable>
                </MarkerView>
              ))
            : null}

          {routeGeoJOSN && <RouteLineString />}
          {centerOfLineString && (
            <MapboxGL.MarkerView
              id="selectedFeatureMarkerView"
              coordinate={centerOfLineString}>
              <CustomCalloutView />
            </MapboxGL.MarkerView>
          )}
        </MapboxGL.MapView>
      </View>
      <View style={styles.bottomContainer}>
        <Button title={'SHOPPING'} onPress={onPressInterestSite} />
        <Button title={'RESTAURANT'} onPress={onPressInterestSite} />
        <Button title={'MOSQUE'} onPress={onPressInterestSite} />
        <Button title={'HOSPITAL'} onPress={onPressInterestSite} />
      </View>
    </View>
  );
};

export default MAP_VIEW;

const styles = StyleSheet.create({});
Enter fullscreen mode Exit fullscreen mode

SearchLocationInput.js

import React, {useEffect, useState} from 'react';
import {
  View,
  Text,
  FlatList,
  TextInput,
  StyleSheet,
  TouchableOpacity,
} from 'react-native';
import {MAP_BOX_ACCESS_TOKEN} from '../../../utils/constants/constants';
import {isRTL} from '../../../i18n';
import {useFirstRender} from '../../newTabsGallery/galleryComponents/reusableHooks';

const SearchLocationInput = ({
  textInputText = '',
  selectedItemRes,
  currentPinCoordinate = [],
}) => {
  const [resultList, setresultList] = useState([]);
  const [inputText, setinputText] = useState(textInputText);
  const firstRender = useFirstRender();

  useEffect(() => {
    if (!firstRender) {
      reverseGeocodingData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentPinCoordinate]);

  const reverseGeocodingData = async (bool = true) => {
    // bool true = poi, bool false = region
    let searchParams = new URLSearchParams({
      access_token: MAP_BOX_ACCESS_TOKEN,
      language: isRTL() ? 'ar' : 'en',
      country: 'SA',
      limit: 1,
      // type: 'poi',
    });
    let response = await fetch(
      bool
        ? `https://api.mapbox.com/geocoding/v5/mapbox.places/${currentPinCoordinate[0]},${currentPinCoordinate[1]}.json?` +
            searchParams
        : `https://api.mapbox.com/search/v1/reverse/${currentPinCoordinate[0]},${currentPinCoordinate[1]}?` +
            searchParams,
    );
    let result = await response.json();
    let resData = bool
      ? result?.features[0]?.text
      : result?.features[0]?.properties?.feature_name;
    console.log('reverseGeocodingData', resData);
    setinputText(resData);
  };

  const searchItems = async text => {
    try {
      setinputText(text);
      if (text.length > 2) {
        let response = await fetch(
          `https://api.mapbox.com/search/v1/suggest/${text}?` +
            new URLSearchParams({
              access_token: MAP_BOX_ACCESS_TOKEN,
              session_token: '',
              language: isRTL() ? 'ar' : 'en',
              country: 'SA',
              // types:
              //   'country, region, prefecture, postcode, district, place, city, locality, oaza, neighborhood, chome, block, street,  address',
            }),
        );
        let result = await response.json();
        setresultList(
          result?.suggestions.length > 0 ? result?.suggestions : [],
        );
      } else {
        setresultList([]);
      }
    } catch ({message}) {
      setinputText('');
      setresultList([]);
      console.log('searchItemsSiteERROR', message);
    }
  };

  const onPressListItem = async item => {
    try {
      let options = {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(item?.action?.body),
      };
      let response = await fetch(
        'https://api.mapbox.com/search/v1/retrieve?' +
          new URLSearchParams({
            access_token: MAP_BOX_ACCESS_TOKEN,
            session_token: '',
          }),
        options,
      );
      let result = await response.json();
      selectedItemRes(result?.features[0]);
      setinputText(result?.features[0]?.properties?.feature_name);
      setresultList([]);
    } catch ({message}) {
      console.log('onPressListItemERROR', message);
    }
  };

  const renderSeparator = () => {
    return (
      <View
        style={{
          height: 1,
          width: '100%',
          backgroundColor: '#CED0CE',
        }}
      />
    );
  };

  return (
    <View
      style={{
        flex: 1,
        width: '98%',
        position: 'absolute',
        top: 100,
        zIndex: 1,
        backgroundColor: 'white',
      }}>
      <TextInput
        style={{height: 60, borderColor: '#000', borderWidth: 1}}
        placeholder="   Type Here...Key word"
        onChangeText={searchItems}
        value={inputText}
      />
      {inputText && (
        <FlatList
          data={resultList}
          renderItem={({item}) => (
            <TouchableOpacity onPress={() => onPressListItem(item)}>
              <Text style={{padding: 10}}>{item?.feature_name} </Text>
            </TouchableOpacity>
          )}
          keyExtractor={(item, index) => index + ''}
          ItemSeparatorComponent={renderSeparator}
        />
      )}
    </View>
  );
};

export default SearchLocationInput;

const styles = StyleSheet.create({});

Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
rhenaldkarrel profile image
Rhenald Karrel

Does this API is free for development?

Collapse
 
ajmal_hasan profile image
Ajmal Hasan

Yes but there can be some limitation in free version . Read doc