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).
Autocomplete search
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 })returnsresult.data?.placesas an array- Results include
longitudeandlatitude(may bestring | number— useNumber()) - Use
cameraRef.current.setCamera()withanimationMode: 'flyTo'to animate to the selected place
Optional autocomplete parameters:
| Parameter | Type | Description |
|---|---|---|
bangla | boolean | Include 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 })returnsresult.data?.place— a single object (not an array)- Check
distance_within_metersto gauge how accurate the result is - Use
PointAnnotationfor the marker so users can see the exact tapped position
Optional reverse geocode parameters:
| Parameter | Type | Description |
|---|---|---|
district | boolean | Include district data |
bangla | boolean | Include Bangla names |
thana | boolean | Include thana/upazila data |
division | boolean | Include 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 })returnsresult.data?.placessorted by distanceradiusis in kilometers,limitcaps the result count- Each result includes
distance_in_metersandpType(place category) - Response fields like
longitude,latitude,distance_in_metersmay bestring | number— useNumber()