Skip to main content

Search & Geocoding

Integrate Barikoi's location APIs with the map. The barikoiapis SDK provides typed methods for autocomplete search, reverse geocoding, and nearby place queries.

All examples below use the barikoiClient initialized in mapUtils.ts (see Getting Started).


Search for places in Bangladesh and display results as markers on the map.

import { Camera, MapView, MarkerView } from '@maplibre/maplibre-react-native';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
Animated,
FlatList,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import { barikoiClient, BARIKOI_COLORS, useBarikoiMapStyle } from '../utils/mapUtils';

type Place = {
id: number;
longitude: string | number;
latitude: string | number;
address: string;
area: string;
city: string;
postCode: string | number;
};

export default function AutocompleteSearchScreen() {
const { styleJson, loading: mapLoading, error: mapError } = useBarikoiMapStyle();
const [query, setQuery] = useState('');
const [results, setResults] = useState<Place[]>([]);
const [searching, setSearching] = useState(false);
const [selectedPlace, setSelectedPlace] = useState<Place | null>(null);
const bottomSheetAnim = useRef(new Animated.Value(0)).current;
const cameraRef = useRef<any>(null);
const debounceRef = useRef<ReturnType<typeof setTimeout>>();

// Debounced search using barikoiapis SDK
useEffect(() => {
if (!query.trim()) {
setResults([]);
return;
}

if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(async () => {
try {
setSearching(true);
const result = await barikoiClient.autocomplete({ q: query });
setResults((result.data?.places || []) as Place[]);
} catch {
setResults([]);
} finally {
setSearching(false);
}
}, 400);

return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
}, [query]);

const showBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 1, useNativeDriver: true }).start();
}, [bottomSheetAnim]);

const hideBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 0, useNativeDriver: true }).start();
setSelectedPlace(null);
}, [bottomSheetAnim]);

const selectPlace = useCallback((place: Place) => {
setSelectedPlace(place);
setResults([]);
setQuery(place.address);
showBottomSheet();
cameraRef.current?.setCamera({
centerCoordinate: [Number(place.longitude), Number(place.latitude)],
zoomLevel: 16,
animationDuration: 1000,
animationMode: 'flyTo',
});
}, [showBottomSheet]);

if (mapLoading) return <View style={styles.centered}><ActivityIndicator size="large" color={BARIKOI_COLORS.primary} /></View>;
if (mapError) return <View style={styles.centered}><Text>{mapError}</Text></View>;

return (
<View style={styles.container}>
<MapView style={styles.map} attributionEnabled={false} zoomEnabled compassEnabled mapStyle={styleJson}>
<Camera ref={cameraRef} centerCoordinate={[90.364159, 23.823724]} zoomLevel={11} animationMode="linearTo" />
{selectedPlace && (
<MarkerView coordinate={[Number(selectedPlace.longitude), Number(selectedPlace.latitude)]} anchor={{ x: 0.5, y: 1.0 }}>
<View style={styles.selectedMarker} />
</MarkerView>
)}
{results.map((place) => (
<MarkerView key={place.id} coordinate={[Number(place.longitude), Number(place.latitude)]} anchor={{ x: 0.5, y: 1.0 }}>
<View style={styles.resultMarker} />
</MarkerView>
))}
</MapView>

{/* Search bar */}
<View style={styles.searchContainer}>
<TextInput
style={styles.searchInput}
placeholder="Search for a place..."
value={query}
onChangeText={setQuery}
clearButtonMode="while-editing"
/>
{searching && <ActivityIndicator style={styles.searchSpinner} size="small" color={BARIKOI_COLORS.primary} />}
</View>

{/* Search results dropdown */}
{results.length > 0 && (
<View style={styles.resultsList}>
<FlatList
data={results}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<Pressable style={styles.resultItem} onPress={() => selectPlace(item)}>
<Text style={styles.resultAddress}>{item.address}</Text>
<Text style={styles.resultArea}>{item.area}, {item.city}</Text>
</Pressable>
)}
keyboardShouldPersistTaps="handled"
/>
</View>
)}

{/* Selected place bottom sheet */}
{selectedPlace && (
<Animated.View style={[styles.bottomSheet, {
transform: [{
translateY: bottomSheetAnim.interpolate({ inputRange: [0, 1], outputRange: [200, 0] })
}]
}]}>
<View style={styles.sheetContent}>
<View style={styles.sheetHeader}>
<View style={{ flex: 1 }}>
<Text style={styles.sheetTitle}>{selectedPlace.address}</Text>
<Text style={styles.sheetDesc}>{selectedPlace.area}, {selectedPlace.city} {selectedPlace.postCode}</Text>
</View>
<Pressable onPress={hideBottomSheet} style={styles.closeBtn}>
<Text style={styles.closeText}></Text>
</Pressable>
</View>
<View style={styles.coordBox}>
<View style={{ marginBottom: 8 }}>
<Text style={styles.coordLabel}>Latitude</Text>
<Text style={styles.coordValue}>{Number(selectedPlace.latitude).toFixed(6)}</Text>
</View>
<View style={{ height: 1, backgroundColor: 'rgba(0,0,0,0.1)', marginVertical: 8 }} />
<View>
<Text style={styles.coordLabel}>Longitude</Text>
<Text style={styles.coordValue}>{Number(selectedPlace.longitude).toFixed(6)}</Text>
</View>
</View>
</View>
</Animated.View>
)}
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
map: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
selectedMarker: { width: 20, height: 20, borderRadius: 10, backgroundColor: BARIKOI_COLORS.secondary, borderWidth: 3, borderColor: 'white' },
resultMarker: { width: 12, height: 12, borderRadius: 6, backgroundColor: BARIKOI_COLORS.primary },
searchContainer: { position: 'absolute', top: 16, left: 16, right: 16, backgroundColor: 'white', borderRadius: 12, elevation: 5, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3.84 },
searchInput: { padding: 14, fontSize: 16, color: BARIKOI_COLORS.text },
searchSpinner: { position: 'absolute', right: 16, top: 18 },
resultsList: { position: 'absolute', top: 70, left: 16, right: 16, maxHeight: 240, backgroundColor: 'white', borderRadius: 12, elevation: 5, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3.84 },
resultItem: { padding: 14, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' },
resultAddress: { fontSize: 15, fontWeight: '500', color: BARIKOI_COLORS.text },
resultArea: { fontSize: 13, color: BARIKOI_COLORS.text, opacity: 0.6, marginTop: 2 },
bottomSheet: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'white', borderTopLeftRadius: 20, borderTopRightRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: -2 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, paddingBottom: 20 },
sheetContent: { padding: 16 },
sheetHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 },
sheetTitle: { fontSize: 18, fontWeight: 'bold', color: BARIKOI_COLORS.text, marginBottom: 4 },
sheetDesc: { fontSize: 14, color: BARIKOI_COLORS.text, opacity: 0.7 },
closeBtn: { width: 30, height: 30, alignItems: 'center', justifyContent: 'center', borderRadius: 15, backgroundColor: BARIKOI_COLORS.background },
closeText: { fontSize: 16, color: BARIKOI_COLORS.text },
coordBox: { backgroundColor: BARIKOI_COLORS.background, borderRadius: 12, padding: 16 },
coordLabel: { fontSize: 12, color: BARIKOI_COLORS.text, opacity: 0.7, marginBottom: 4 },
coordValue: { fontSize: 16, color: BARIKOI_COLORS.text, fontWeight: '500' },
});

Key points:

  • Debounce the input — 400ms delay avoids hammering the API on every keystroke
  • barikoiClient.autocomplete({ q }) returns result.data?.places as an array
  • Results include longitude and latitude (may be string | number — use Number())
  • Use cameraRef.current.setCamera() with animationMode: 'flyTo' to animate to the selected place

Optional autocomplete parameters:

ParameterTypeDescription
banglabooleanInclude Bangla names in the response

Reverse geocoding

Tap the map to get the address at that coordinate.

import { Camera, MapView, PointAnnotation } from '@maplibre/maplibre-react-native';
import type { Feature, Geometry } from 'geojson';
import React, { useCallback, useRef, useState } from 'react';
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native';
import { barikoiClient, BARIKOI_COLORS, useBarikoiMapStyle } from '../utils/mapUtils';

type ReverseGeoResult = {
address: string;
address_bn: string;
area: string;
city: string;
postCode: string;
subType: string;
thana: string;
division: string;
distance_within_meters: number;
};

export default function ReverseGeocodingScreen() {
const { styleJson, loading, error } = useBarikoiMapStyle();
const cameraRef = useRef<any>(null);
const [markerCoord, setMarkerCoord] = useState<[number, number]>([90.364159, 23.823724]);
const [addressResult, setAddressResult] = useState<ReverseGeoResult | null>(null);
const [loadingAddress, setLoadingAddress] = useState(false);

const reverseGeocode = useCallback(async (lng: number, lat: number) => {
try {
setLoadingAddress(true);
const result = await barikoiClient.reverseGeocode({ longitude: lng, latitude: lat });
const place = result.data?.place;
if (place) {
setAddressResult({
address: place.address || '',
address_bn: place.address_bn || '',
area: place.area || '',
city: place.city || '',
postCode: place.postCode || '',
subType: place.subType || '',
thana: place.thana || '',
division: place.division || '',
distance_within_meters: place.distance_within_meters ?? 0,
});
} else {
setAddressResult(null);
}
} catch {
setAddressResult(null);
} finally {
setLoadingAddress(false);
}
}, []);

const handleMapPress = useCallback((payload: Feature<Geometry>) => {
if (payload.geometry?.type === 'Point') {
const [lng, lat] = payload.geometry.coordinates as [number, number];
setMarkerCoord([lng, lat]);
reverseGeocode(lng, lat);
}
}, [reverseGeocode]);

if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color={BARIKOI_COLORS.primary} /></View>;
if (error) return <View style={styles.centered}><Text>{error}</Text></View>;

return (
<View style={styles.container}>
<MapView
style={styles.map}
attributionEnabled={false}
zoomEnabled scrollEnabled compassEnabled
mapStyle={styleJson}
onPress={handleMapPress}
>
<Camera ref={cameraRef} centerCoordinate={markerCoord} zoomLevel={16} animationDuration={300} animationMode="easeTo" />
<PointAnnotation id="reverseGeo" coordinate={markerCoord}>
<View style={styles.marker} />
</PointAnnotation>
</MapView>

{/* Address card */}
<View style={styles.addressCard}>
{loadingAddress ? (
<View style={styles.loadingRow}>
<ActivityIndicator size="small" color={BARIKOI_COLORS.primary} />
<Text style={styles.loadingText}>Looking up address...</Text>
</View>
) : addressResult ? (
<View>
<Text style={styles.addressTitle}>{addressResult.address}</Text>
<Text style={styles.addressDetail}>{addressResult.area}, {addressResult.city} {addressResult.postCode}</Text>
<View style={styles.coordRow}>
<View style={styles.coordItem}>
<Text style={styles.coordLabel}>Lat</Text>
<Text style={styles.coordValue}>{markerCoord[1].toFixed(6)}</Text>
</View>
<View style={styles.coordItem}>
<Text style={styles.coordLabel}>Lng</Text>
<Text style={styles.coordValue}>{markerCoord[0].toFixed(6)}</Text>
</View>
</View>
</View>
) : (
<Text style={styles.tapPrompt}>Tap the map to look up an address</Text>
)}
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
map: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
marker: { width: 24, height: 24, borderRadius: 12, backgroundColor: BARIKOI_COLORS.secondary, borderWidth: 3, borderColor: 'white' },
addressCard: { position: 'absolute', bottom: 24, left: 16, right: 16, backgroundColor: 'white', borderRadius: 16, padding: 16, elevation: 10, shadowColor: '#000', shadowOpacity: 0.1, shadowOffset: { width: 0, height: 10 }, shadowRadius: 20 },
loadingRow: { flexDirection: 'row', alignItems: 'center', gap: 10 },
loadingText: { fontSize: 15, color: BARIKOI_COLORS.text, marginLeft: 10 },
addressTitle: { fontSize: 17, fontWeight: '600', color: BARIKOI_COLORS.text, marginBottom: 4 },
addressDetail: { fontSize: 14, color: BARIKOI_COLORS.text, opacity: 0.7, marginBottom: 12 },
coordRow: { flexDirection: 'row', gap: 12 },
coordItem: { flex: 1, backgroundColor: BARIKOI_COLORS.background, borderRadius: 10, padding: 10 },
coordLabel: { fontSize: 11, color: BARIKOI_COLORS.text, opacity: 0.6, marginBottom: 2 },
coordValue: { fontSize: 14, fontWeight: '500', color: BARIKOI_COLORS.text },
tapPrompt: { fontSize: 15, color: BARIKOI_COLORS.text, opacity: 0.5, textAlign: 'center' },
});

Key points:

  • barikoiClient.reverseGeocode({ latitude, longitude }) returns result.data?.place — a single object (not an array)
  • Check distance_within_meters to gauge how accurate the result is
  • Use PointAnnotation for the marker so users can see the exact tapped position

Optional reverse geocode parameters:

ParameterTypeDescription
districtbooleanInclude district data
banglabooleanInclude Bangla names
thanabooleanInclude thana/upazila data
divisionbooleanInclude division data
Extra credits

Each enabled optional parameter triggers additional API calls and consumes more credits.


Nearby places

Find nearby points of interest using the user's current location.

import type { Location } from '@maplibre/maplibre-react-native';
import { Camera, MapView, MarkerView, UserLocation } from '@maplibre/maplibre-react-native';
import * as ExpoLocation from 'expo-location';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Animated, FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import { barikoiClient, BARIKOI_COLORS, useBarikoiMapStyle } from '../utils/mapUtils';

type NearbyPlace = {
id: number;
name: string;
longitude: string;
latitude: string;
Address: string;
area: string;
city: string;
pType: string;
subType: string;
uCode: string;
distance_in_meters: string | number;
};

export default function NearbyPlacesScreen() {
const { styleJson, loading: mapLoading, error: mapError } = useBarikoiMapStyle();
const [userCoord, setUserCoord] = useState<[number, number]>([90.364159, 23.823724]);
const [places, setPlaces] = useState<NearbyPlace[]>([]);
const [loadingPlaces, setLoadingPlaces] = useState(false);
const [selectedPlace, setSelectedPlace] = useState<NearbyPlace | null>(null);
const bottomSheetAnim = useRef(new Animated.Value(0)).current;
const cameraRef = useRef<any>(null);

// Get user location
useEffect(() => {
(async () => {
const { status } = await ExpoLocation.requestForegroundPermissionsAsync();
if (status === 'granted') {
const loc = await ExpoLocation.getCurrentPositionAsync({});
setUserCoord([loc.coords.longitude, loc.coords.latitude]);
}
})();
}, []);

// Fetch nearby places using barikoiapis SDK
const fetchNearby = useCallback(async (lng: number, lat: number) => {
try {
setLoadingPlaces(true);
const result = await barikoiClient.nearby({
longitude: lng,
latitude: lat,
radius: 1, // 1km
limit: 20,
});
setPlaces((result.data?.places || []) as NearbyPlace[]);
} catch {
setPlaces([]);
} finally {
setLoadingPlaces(false);
}
}, []);

useEffect(() => {
fetchNearby(userCoord[0], userCoord[1]);
}, [userCoord, fetchNearby]);

const showBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 1, useNativeDriver: true }).start();
}, [bottomSheetAnim]);

const hideBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 0, useNativeDriver: true }).start();
setSelectedPlace(null);
}, [bottomSheetAnim]);

const selectPlace = useCallback((place: NearbyPlace) => {
setSelectedPlace(place);
showBottomSheet();
cameraRef.current?.setCamera({
centerCoordinate: [Number(place.longitude), Number(place.latitude)],
zoomLevel: 16,
animationDuration: 800,
animationMode: 'flyTo',
});
}, [showBottomSheet]);

if (mapLoading) return <View style={styles.centered}><ActivityIndicator size="large" color={BARIKOI_COLORS.primary} /></View>;
if (mapError) return <View style={styles.centered}><Text>{mapError}</Text></View>;

return (
<View style={styles.container}>
<MapView style={styles.map} attributionEnabled={false} zoomEnabled compassEnabled mapStyle={styleJson}>
<Camera ref={cameraRef} centerCoordinate={userCoord} zoomLevel={15} animationDuration={1000} animationMode="flyTo" />
<UserLocation visible animated renderMode="normal" />
{/* User location marker */}
<MarkerView coordinate={userCoord} anchor={{ x: 0.5, y: 0.5 }}>
<View style={styles.userDot} />
</MarkerView>
{/* Nearby place markers */}
{places.map((place) => (
<MarkerView key={place.id} coordinate={[Number(place.longitude), Number(place.latitude)]} anchor={{ x: 0.5, y: 1.0 }}>
<Pressable onPress={() => selectPlace(place)}>
<View style={[
styles.placeMarker,
selectedPlace?.id === place.id && styles.placeMarkerSelected
]}>
<Text style={styles.placeMarkerText}>{Math.round(Number(place.distance_in_meters))}m</Text>
</View>
</Pressable>
</MarkerView>
))}
</MapView>

{/* Places list */}
<View style={styles.listContainer}>
<View style={styles.listHeader}>
<Text style={styles.listTitle}>Nearby Places</Text>
{loadingPlaces && <ActivityIndicator size="small" color={BARIKOI_COLORS.primary} />}
</View>
<FlatList
data={places}
keyExtractor={(item) => String(item.id)}
horizontal
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => (
<Pressable
style={[styles.placeCard, selectedPlace?.id === item.id && styles.placeCardSelected]}
onPress={() => selectPlace(item)}
>
<Text style={styles.placeName} numberOfLines={1}>{item.name || item.Address}</Text>
<Text style={styles.placeType}>{item.pType}</Text>
<Text style={styles.placeDistance}>{Math.round(Number(item.distance_in_meters))}m away</Text>
</Pressable>
)}
/>
</View>

{/* Selected place bottom sheet */}
{selectedPlace && (
<Animated.View style={[styles.bottomSheet, {
transform: [{
translateY: bottomSheetAnim.interpolate({ inputRange: [0, 1], outputRange: [200, 0] })
}]
}]}>
<View style={styles.sheetContent}>
<View style={styles.sheetHeader}>
<View style={{ flex: 1 }}>
<Text style={styles.sheetTitle}>{selectedPlace.name || selectedPlace.Address}</Text>
<Text style={styles.sheetDesc}>{selectedPlace.area}, {selectedPlace.city}</Text>
</View>
<Pressable onPress={hideBottomSheet} style={styles.closeBtn}>
<Text style={styles.closeText}></Text>
</Pressable>
</View>
<View style={styles.detailRow}>
<View style={styles.detailChip}>
<Text style={styles.detailLabel}>Type</Text>
<Text style={styles.detailValue}>{selectedPlace.pType}</Text>
</View>
<View style={styles.detailChip}>
<Text style={styles.detailLabel}>Distance</Text>
<Text style={styles.detailValue}>{Math.round(Number(selectedPlace.distance_in_meters))}m</Text>
</View>
<View style={styles.detailChip}>
<Text style={styles.detailLabel}>Area</Text>
<Text style={styles.detailValue}>{selectedPlace.area}</Text>
</View>
</View>
</View>
</Animated.View>
)}
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
map: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
userDot: { width: 16, height: 16, borderRadius: 8, backgroundColor: '#4285F4', borderWidth: 3, borderColor: 'white' },
placeMarker: { backgroundColor: BARIKOI_COLORS.primary, borderRadius: 12, paddingHorizontal: 8, paddingVertical: 4, minWidth: 40, alignItems: 'center' },
placeMarkerSelected: { backgroundColor: BARIKOI_COLORS.secondary },
placeMarkerText: { color: 'white', fontSize: 11, fontWeight: '600' },
listContainer: { position: 'absolute', bottom: 16, left: 0, right: 0 },
listHeader: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, marginBottom: 8, gap: 8 },
listTitle: { fontSize: 16, fontWeight: '600', color: BARIKOI_COLORS.text },
placeCard: { backgroundColor: 'white', borderRadius: 12, padding: 12, marginRight: 8, width: 160, elevation: 3, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 4 },
placeCardSelected: { borderWidth: 2, borderColor: BARIKOI_COLORS.primary },
placeName: { fontSize: 14, fontWeight: '600', color: BARIKOI_COLORS.text, marginBottom: 2 },
placeType: { fontSize: 12, color: BARIKOI_COLORS.primary, marginBottom: 4 },
placeDistance: { fontSize: 12, color: BARIKOI_COLORS.text, opacity: 0.6 },
bottomSheet: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'white', borderTopLeftRadius: 20, borderTopRightRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: -2 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, paddingBottom: 20 },
sheetContent: { padding: 16 },
sheetHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 },
sheetTitle: { fontSize: 18, fontWeight: 'bold', color: BARIKOI_COLORS.text, marginBottom: 4 },
sheetDesc: { fontSize: 14, color: BARIKOI_COLORS.text, opacity: 0.7 },
closeBtn: { width: 30, height: 30, alignItems: 'center', justifyContent: 'center', borderRadius: 15, backgroundColor: BARIKOI_COLORS.background },
closeText: { fontSize: 16, color: BARIKOI_COLORS.text },
detailRow: { flexDirection: 'row', gap: 8 },
detailChip: { flex: 1, backgroundColor: BARIKOI_COLORS.background, borderRadius: 10, padding: 10 },
detailLabel: { fontSize: 11, color: BARIKOI_COLORS.text, opacity: 0.6, marginBottom: 2 },
detailValue: { fontSize: 14, fontWeight: '500', color: BARIKOI_COLORS.text },
});

Key points:

  • barikoiClient.nearby({ latitude, longitude, radius, limit }) returns result.data?.places sorted by distance
  • radius is in kilometers, limit caps the result count
  • Each result includes distance_in_meters and pType (place category)
  • Response fields like longitude, latitude, distance_in_meters may be string | number — use Number()