Joylashuv va xarita — expo-location, react-native-maps

~20 daq · React Native · Qurilma imkoniyatlari

Muammodan boshlaylik

Yetkazib berish ilovasi:

  • Manzilni qo'lda yozish — "Chilonzor, 9-kvartal, 12-uy, 3-podyezd..." — xatolar va kuryer adashadi
  • Kerak — "Mening joylashuvim" tugmasi: GPS dan koordinata, xaritada belgi, foydalanuvchi belgini aniq uyiga suradi
  • Va xaritada eng yaqin do'konlar

O'rnatish

npx expo install expo-location react-native-maps
"plugins": [
  ["expo-location", {
    "locationWhenInUsePermission": "Yetkazib berish manzilini va eng yaqin do'konni aniqlash uchun."
  }]
]

Joriy joylashuv

import * as Location from 'expo-location';

async function joylashuvniOl() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status !== 'granted') {
    Alert.alert('Ruxsat kerak', "Manzilni xaritadan qo'lda tanlashingiz mumkin.");
    return null;
  }

  const joy = await Location.getCurrentPositionAsync({
    accuracy: Location.Accuracy.Balanced,
  });
  return { lat: joy.coords.latitude, lng: joy.coords.longitude };
}

Aniqlik

Accuracy Aniqlik Batareya Qachon
Lowest / Low ~3 km / ~1 km Kam Shaharni aniqlash
Balanced ~100 m O'rtacha Eng yaqin do'kon
High ~10 m Ko'p Yetkazish manzili
BestForNavigation Eng aniq Juda ko'p Navigatsiya

GPS signali binolar ichida zaif — High bir necha soniya olishi mumkin. Tez javob uchun — avval oxirgi ma'lum joylashuv:

const tez = await Location.getLastKnownPositionAsync();   // bir zumda, lekin eski bo'lishi mumkin

Koordinata ↔ manzil

// Koordinatadan manzil
const [manzil] = await Location.reverseGeocodeAsync({ latitude: 41.3111, longitude: 69.2797 });
// { city: 'Tashkent', district: 'Yunusobod', street: '...', name: '...' }

// Manzildan koordinata
const [joy] = await Location.geocodeAsync('Amir Temur ko\'chasi, Toshkent');

Tizim geokoderi O'zbekiston manzillarini har doim ham aniq bilmaydi. Professional ilovalarda — Yandex, Google yoki 2GIS geocoding API lari.


Xarita

import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';

const TOSHKENT = { latitude: 41.3111, longitude: 69.2797, latitudeDelta: 0.1, longitudeDelta: 0.1 };

export default function Xarita() {
  return (
    <MapView
      style={{ flex: 1 }}
      initialRegion={TOSHKENT}
      showsUserLocation
      showsMyLocationButton
    >
      <Marker
        coordinate={{ latitude: 41.3111, longitude: 69.2797 }}
        title="Markaziy do'kon"
        description="09:00 – 22:00"
      />
    </MapView>
  );
}
  • region / initialRegion — markaz va delta (masshtab: kichik delta — yaqinroq)
  • showsUserLocation — foydalanuvchi joylashuvi ko'k nuqta bilan
  • iOS da standart — Apple Maps; Android da — Google Maps

[!INFO] Android da Google Maps production build uchun API kalit talab qiladi (app.json → android.config.googleMaps.apiKey). Expo Go da sinash uchun kalit kerak emas. Kalitni Google Cloud Console da faqat ilovangiz paket nomi va imzosi bilan cheklang.

Do'konlar ro'yxati

<MapView style={{ flex: 1 }} initialRegion={TOSHKENT}>
  {dokonlar.map((d) => (
    <Marker
      key={d.id}
      coordinate={{ latitude: d.lat, longitude: d.lng }}
      title={d.nom}
      pinColor="#0284c7"
      onCalloutPress={() => router.push(`/dokon/${d.id}`)}
    />
  ))}
</MapView>

Yuzlab belgi bo'lsa — klasterlash kutubxonasi (react-native-map-clustering), aks holda xarita sekinlashadi.


Manzil tanlash — markazdagi belgi

Yetkazish ilovalaridagi klassik naqsh: belgi ekran markazida qotib turadi, foydalanuvchi xaritani suradi:

export default function ManzilTanlash() {
  const ref = useRef<MapView>(null);
  const [markaz, setMarkaz] = useState(TOSHKENT);
  const [manzil, setManzil] = useState('');

  async function menga() {
    const j = await joylashuvniOl();
    if (j) ref.current?.animateToRegion({ latitude: j.lat, longitude: j.lng, latitudeDelta: 0.005, longitudeDelta: 0.005 }, 500);
  }

  async function harakatTugadi(r: Region) {
    setMarkaz(r);
    const [m] = await Location.reverseGeocodeAsync({ latitude: r.latitude, longitude: r.longitude });
    setManzil([m?.street, m?.name].filter(Boolean).join(', '));
  }

  return (
    <View style={{ flex: 1 }}>
      <MapView ref={ref} style={{ flex: 1 }} initialRegion={TOSHKENT} onRegionChangeComplete={harakatTugadi} />

      {/* Markazdagi belgi — xarita ustida, harakatsiz */}
      <View pointerEvents="none" style={[StyleSheet.absoluteFill, { justifyContent: 'center', alignItems: 'center' }]}>
        <Ionicons name="location" size={44} color="#dc2626" style={{ marginBottom: 44 }} />
      </View>

      <Pressable onPress={menga} style={{ position: 'absolute', right: 16, bottom: 180, backgroundColor: 'white', padding: 12, borderRadius: 24 }}>
        <Ionicons name="locate" size={24} color="#0284c7" />
      </Pressable>

      <View style={{ padding: 16, gap: 12, backgroundColor: 'white' }}>
        <Text style={{ fontSize: 16 }}>{manzil || 'Manzilni tanlang'}</Text>
        <Tugma matn="Shu manzil" onPress={() => saqla({ lat: markaz.latitude, lng: markaz.longitude, matn: manzil })} />
      </View>
    </View>
  );
}
  • pointerEvents="none" — belgi xarita imo-ishoralarini to'smasin
  • marginBottom: 44 — belgining uchi markazga tushsin, o'rtasi emas
  • onRegionChangeComplete — surish tugaganda (har kadrda emas) — geokoder so'rovlari kamroq

Masofa — eng yaqin do'kon

function masofaKm(a: { lat: number; lng: number }, b: { lat: number; lng: number }) {
  const R = 6371;
  const dLat = ((b.lat - a.lat) * Math.PI) / 180;
  const dLng = ((b.lng - a.lng) * Math.PI) / 180;
  const x = Math.sin(dLat / 2) ** 2 +
    Math.cos((a.lat * Math.PI) / 180) * Math.cos((b.lat * Math.PI) / 180) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(x));
}

const yaqin = [...dokonlar].sort((d1, d2) => masofaKm(men, d1) - masofaKm(men, d2))[0];

Haversine formulasi — to'g'ri chiziqdagi masofa. Yo'l bo'yicha masofa va vaqt — marshrut API lari (Google Directions, OSRM).

Ko'p do'kon bo'lsa — saralashni serverda qiling (MySQL ST_Distance_Sphere, PostgreSQL PostGIS).


Fon rejimida kuzatish

Kuryer ilovasi — ilova yopiq bo'lsa ham joylashuvni yuborish:

await Location.requestBackgroundPermissionsAsync();
await Location.startLocationUpdatesAsync('kuryer-kuzatuv', { accuracy: Location.Accuracy.High, timeInterval: 15000 });

expo-task-manager bilan, development build da. Fon joylashuvi — eng "nozik" ruxsat: Google Play va App Store qat'iy asoslash talab qiladi, foydalanuvchilar esa ko'pincha rad etadi. Faqat haqiqatan zarur bo'lganda.


Nima uchun shunday?

Nima uchun joylashuvning turli aniqlik darajalari bor — doim eng aniqini olsa bo'lmaydimi?

Chunki aniqlik — batareya va vaqt evaziga. Eng aniq joylashuv uchun GPS sun'iy yo'ldoshlarini qidirish kerak — bu bir necha soniya va ko'p energiya, ayniqsa binolar ichida. Past aniqlik esa Wi-Fi va mobil minoralar orqali bir zumda keladi. "Qaysi shahardasiz?" savoli uchun GPS ni yoqish — isrof. Mobil dasturlashda har bir imkoniyat kerakli darajada ishlatiladi.


Xulosa

Eslab qoling:

  • requestForegroundPermissionsAsync → getCurrentPositionAsync({ accuracy })
  • Aniqlik — vazifaga mos: Balanced — do'kon, High — manzil
  • getLastKnownPositionAsync — tez, lekin eski bo'lishi mumkin
  • reverseGeocodeAsync / geocodeAsync — koordinata ↔ manzil
  • MapView, Marker, showsUserLocation, animateToRegion
  • Manzil tanlash: markazdagi qotgan belgi + onRegionChangeComplete
  • Android Google Maps — production da API kalit
  • Fon joylashuvi — faqat zarur bo'lganda

Keyingi dars

42-dars: Push bildirishnomalar — expo-notifications