WisarWisar
Dasturlash kitobi/8-QISM — NestJS27 daqiqa

8.28-bob: Geolokatsiya va xaritalar

8-QISM — NestJS (chuqur) · 28-mavzu · Amaliy real mavzu


1. Kirish va motivatsiya

Endi joylashuvga oid real mavzu — geolokatsiya (gelocation). O'zbekistonda eng ko'p o'sayotgan ilovalar — yetkazib berish (ovqat, do'kon), taksi (Yandex Go, MyTaxi), kuryer, "eng yaqin filial/bankomat" — barchasi joylashuv bilan ishlaydi: foydalanuvchi qayerda, eng yaqin do'kon qayerda, yetkazib berish masofasi qancha, kuryer hozir qayerda. 8.12 (Telegram bot — location) va 8.13 (Mongo geo) da qisman ko'rdik; endi to'liq: koordinatalar, masofa hisoblash, eng yaqinlarni topish (geo-qidiruv), geo-indeks, xarita integratsiyasi.

Geolokatsiyaning asosi — koordinata (kenglik — latitude, uzunlik — longitude). Lekin koordinatalar bilan ishlash oddiy emas: ikki nuqta orasidagi masofani hisoblash (Yer sharsimon — to'g'ri chiziq emas), "5 km radiusdagi do'konlar"ni topish (har do'konni tekshirish — sekin geo-indeks kerak), manzilni koordinataga aylantirish (geocoding) va aksincha (reverse geocoding). Bu — DB (PostGIS, MongoDB geo) va xarita xizmatlari (Yandex Maps — O'zbekistonda, Google Maps) bilan hal qilinadi.

Bu bob: koordinatalar (lat/lng), masofa hisoblash (Haversine), eng yaqinlarni topish (geo-qidiruv — eng muhim), geo-indeks (PostGIS, MongoDB 2dsphere — tezlik), geocoding (manzilkoordinata), xarita xizmatlari (Yandex/Google — O'zbekiston), yetkazib berish zonasi (poligon ichidami), va real-time kuzatuv (kuryer — WebSocket — 8.18). Bu bob 8.13 (Mongo geo), 6.6 (PostgreSQL), 8.18 (real-time) bilan bog'liq. Geolokatsiya — yetkazib berish/lokatsiya ilovalarining yuragi.

O'xshatish: geolokatsiya — navigator va xarita. Koordinata (lat/lng) — Yerdagi aniq nuqta (GPS manzili). Masofa hisoblash — navigatorning "manzilgacha 3.5 km" deyishi (lekin Yer yumaloq — to'g'ri chiziq emas, egri — Haversine formulasi). "Eng yaqin do'kon" — navigatorning "yaqin-atrofdagi kafelar" ko'rsatishi (har kafeni tekshirmaydi — geo-indeks bilan tez topadi). Yetkazib berish zonasi — "biz faqat shu hududga yetkazamiz" (poligon — xaritadagi chegaralangan maydon). Backend — raqamli navigator: joylashuv, masofa, yaqinlik bilan ishlaydi.

Nega muhim?

  • O'zbekiston bozori — yetkazib berish, taksi, kuryer (eng o'sayotgan).
  • Eng yaqin — do'kon, filial, kuryer (geo-qidiruv).
  • Masofa/zona — yetkazib berish narxi, hudud.
  • Real-time kuzatuv — kuryer joylashuvi (taksi, yetkazish).

2. Nazariya — chuqur tushuntirish

2.1. Koordinatalar (lat/lng)

text
  KOORDINATA — Yerdagi nuqta (2 son):
  - Latitude (kenglik): -90 dan +90 gacha (shimol/janub) — Toshkent ~41.31
  - Longitude (uzunlik): -180 dan +180 gacha (sharq/g'arb) — Toshkent ~69.24

   Tartib chalkashligi: GeoJSON [lng, lat]; ko'p API [lat, lng]
   diqqat: qaysi tartib (eng keng xato)

  Saqlash: ikki ustun (lat, lng) yoki geo turi (POINT — PostGIS/Mongo)

  KOORDINATA TIZIMI (SRID):
  - WGS84 (SRID 4326) — GPS/GeoJSON standarti (gradus: lat/lng)
     deyarli hamma joyda (GPS, telefon, xarita API) shu
  - Web Mercator (SRID 3857) — xarita rasmini chizishda (metr, tekislik)
   DB da har doim aniq SRID: geography(POINT, 4326)

Koordinatalar: latitude (kenglik — shimol/janub — Toshkent ~41.31) va longitude (uzunlik — sharq/g'arb — ~69.24). Tartib chalkashligi (eng keng xato): GeoJSON [lng, lat] (uzunlik birinchi!), ko'p API [lat, lng] (kenglik birinchi). Diqqat qaysi tartib (aks holda nuqta okeanga tushadi). Saqlash: ikki ustun yoki geo turi (POINT — geo-indeks uchun — 2.4). Bu — geolokatsiyaning asosi.

Koordinata tizimi (SRID va WGS84). Bir xil "41.31, 69.24" turli tizimlarda turli nuqtani anglatishi mumkin, shuning uchun har bir koordinatada koordinata tizimi (SRID — Spatial Reference Identifier) bo'lishi kerak. GPS, telefon, GeoJSON va deyarli hamma xarita API'lari WGS84 tizimidan foydalanadi — uning SRID raqami 4326 (gradusda o'lchanadigan lat/lng). Xarita rasmini tekislikda chizishda Web Mercator (SRID 3857 — metrda) ishlatiladi, lekin backend'da ma'lumotni saqlashda deyarli har doim 4326 kerak. PostGIS'da tur e'lon qilganda SRID'ni yozib qo'yiladi: geography(POINT, 4326). SRID chalkashligi (masalan 4326 va 3857 aralashtirilishi) masofa hisobini butunlay buzadi — ikkala nuqta ham bir xil SRID'da bo'lishi shart.

2.2. Masofa hisoblash (Haversine)

typescript
// Ikki koordinata orasidagi masofa (Yer sharsimon — Haversine formulasi)
function masofa(lat1: number, lng1: number, lat2: number, lng2: number): number {
  const R = 6371;                                     // Yer radiusi (km)
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLng = ((lng2 - lng1) * Math.PI) / 180;
  const a = Math.sin(dLat / 2) ** 2 +
    Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLng / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));   // km
}

Masofa hisoblash (Haversine): ikki koordinata orasidagi masofa. Yer sharsimon — to'g'ri chiziq (Pifagor) xato (uzoq masofada katta xato). Haversine formulasi — sharsimon masofa (km). Qisqa masofa uchun yetadi; juda aniq (geodeziya) — Vincenty. Lekin "eng yaqin"ni topishda har nuqtani qo'lda hisoblash sekin (geo-indeks — DB hal qiladi — 2.3, 2.4). Haversine — bitta masofa yoki tekshiruv uchun.

2.3. Eng yaqinlarni topish (geo-qidiruv — eng muhim)

text
  MUAMMO: "5 km radiusdagi do'konlar" — har do'konni Haversine bilan
  tekshirish  100000 do'kon  100000 hisoblash (SEKIN!)

  YECHIM — GEO-INDEKS (DB darajasida — tez):
  - PostgreSQL: PostGIS (geography turi + GIST indeks)
  - MongoDB: 2dsphere indeks 8.13-bob + $near/$geoWithin
   DB indeks bilan eng yaqinlarni TEZ topadi (millisekund)

  "Eng yaqin 10 do'kon" / "5 km radiusdagi" — DB so'rovi (qo'lda emas)

Eng yaqinlarni topish (geo-qidiruv — geolokatsiyaning eng muhim amali): "5 km radiusdagi do'konlar" — har do'konni qo'lda tekshirish sekin (100000 hisoblash). Yechim: geo-indeks (DB darajasida — PostGIS GIST, MongoDB 2dsphere — 8.13). DB indeks bilan eng yaqinlarni tez topadi (millisekund — 6.10). Qo'lda Haversine emas — DB geo-so'rov. Bu — yetkazib berish/eng yaqin filial ilovalarining yuragi.

2.4. Geo-indeks (PostGIS va MongoDB)

typescript
// MongoDB — 2dsphere indeks + $near (8.13)
@Schema()
export class Filial {
  @Prop({ type: { type: String, enum: ["Point"], default: "Point" }, coordinates: [Number] })
  joylashuv: { type: string; coordinates: [number, number] };   // [lng, lat] — GeoJSON (2.1)
}
FilialSchema.index({ joylashuv: "2dsphere" });        // geo-indeks (tezlik — 8.13)

// Eng yaqin filiallar (DB so'rovi — tez)
async engYaqin(lat: number, lng: number, radiusKm: number) {
  return this.model.find({
    joylashuv: {
      $near: {
        $geometry: { type: "Point", coordinates: [lng, lat] },   // [lng, lat]!
        $maxDistance: radiusKm * 1000,                // metr
      },
    },
  }).limit(10);
}

Geo-indeks (DB tezligi): MongoDB2dsphere indeks 8.13-bob + $near (eng yaqin) / $geoWithin (zona ichi); PostgreSQL — PostGIS (geography turi + GIST indeks) + ST_DWithin/ST_Distance. Koordinata GeoJSON Point ([lng, lat] — 2.1). DB so'rovi avtomatik eng yaqinlarni saralab beradi (indeks bilan tez). Geo-indeks'siz geo-qidiruv — sekin (to'liq skanlash). Bu — geolokatsiyaning texnik asosi.

2.4a. PostGIS — chuqur (PostgreSQL geo-kengaytmasi)

Agar loyihada asosiy ma'lumot allaqachon PostgreSQL'da bo'lsa 6.6-bob, geolokatsiya uchun alohida MongoDB ko'tarishning hojati yo'q — PostGIS kengaytmasi PostgreSQL'ni to'laqonli geo-ma'lumotlar bazasiga aylantiradi. PostGIS — sanoat standarti (Uber, taksi platformalari shunga o'xshash yechim ishlatadi), juda kuchli va tez.

sql
-- 1) Kengaytmani yoqish (bir marta, DB'da)
CREATE EXTENSION IF NOT EXISTS postgis;

-- 2) Geo-ustun qo'shish (SRID 4326 = WGS84 — 2.1)
ALTER TABLE filiallar ADD COLUMN joylashuv geography(POINT, 4326);

-- 3) Koordinata yozish ( ST_MakePoint(lng, lat) — uzunlik BIRINCHI)
UPDATE filiallar
SET joylashuv = ST_SetSRID(ST_MakePoint(69.24, 41.31), 4326)::geography;

-- 4) GiST spatial indeks (tezlik — buni unutmang!)
CREATE INDEX idx_filiallar_geo ON filiallar USING GIST (joylashuv);
text
  geometry  vs  geography — ikki tur, farqni bilish MUHIM:

  geometry (tekislik):
  - koordinatalarni yassi (Dekart) tekislik deb hisoblaydi
  - hisoblash TEZ, lekin katta masofada Yer egriligini hisobga olmaydi
  - masofa — koordinata birligida (gradus — km emas!)
  - kichik hudud / Web Mercator xarita uchun

  geography (sfera):
  - Yerni shar deb hisoblaydi (WGS84)
  - ST_Distance  METR qaytaradi (real masofa, egrilik bilan)
  - biroz sekinroq, lekin masofa/radius uchun TO'G'RI
  - yetkazib berish, "5 km radius" uchun  geography TANLANG

geometry vs geography — PostGIS'da eng muhim tanlov. geometry koordinatalarni yassi tekislik deb hisoblaydi: tez, lekin masofa gradusda chiqadi (km emas) va Yer egriligini e'tiborsiz qoldiradi — kichik hududlar yoki xarita rasmini chizish uchun. geography Yerni shar deb oladi: ST_Distance to'g'ridan-to'g'ri metrda real masofa qaytaradi (egrilik bilan). Yetkazib berish, taksi, "5 km radius" kabi masofaga tayanadigan amallarda geography to'g'ri tanlov. geometry bilan ST_DWithin(..., 5000) deb yozsangiz 5000 gradus (butun Yer) chiqadi — masofa metr emas. Geo-turlar: POINT (nuqta — filial), POLYGON (poligon — yetkazish zonasi — 2.6), LINESTRING (chiziq — marshrut).

Asosiy funksiyalar (PostGIS). ST_Distance(a, b) — ikki nuqta orasidagi masofa (geography'da — metr). ST_DWithin(a, b, r)a va b orasi r masofadan (geography'da — metr) yaqinmi (radius ichida — WHEREda ishlatiladi, chunki indeksdan foydalanadi). ST_Contains(poligon, nuqta) / ST_Within(nuqta, poligon) — nuqta poligon ichidami (geofencing — 2.6; ikkalasi bir ishni teskari tartibda qiladi). ST_MakePoint(lng, lat) — koordinatadan nuqta yasaydi ( lng birinchi). ST_AsGeoJSON(geo) — geo-ustunni GeoJSON matnga aylantiradi (API javobiga — 2.5a). ST_X/ST_Y — nuqtadan lng/lat ajratib olish.

Nearest-neighbor (<-> KNN operatori). "Eng yaqin 10 filial"ni topishning ikki yo'li bor. Birinchisi — ST_DWithin bilan radiusni belgilab, ST_Distance bo'yicha saralash (radius ma'lum bo'lsa). Ikkinchisi — <-> (masofa) operatori: bu KNN (K-Nearest-Neighbor) so'rovi bo'lib, GiST indeks yordamida radius belgilamasdan ham eng yaqinlarni juda tez topadi.

sql
-- <-> KNN: radius belgilamasdan eng yaqin 10 ta (indeks bilan juda tez)
SELECT nom,
       joylashuv <-> ST_SetSRID(ST_MakePoint(69.24, 41.31), 4326)::geography AS masofa
FROM filiallar
ORDER BY joylashuv <-> ST_SetSRID(ST_MakePoint(69.24, 41.31), 4326)::geography
LIMIT 10;
--  ORDER BY ... <-> ... LIMIT n  GiST indeks KNN rejimida ishlaydi (index-only, tez)

<-> (KNN) operatori — radius oldindan noma'lum bo'lganda ("shunchaki eng yaqin 5 ta") ideal. ORDER BY joylashuv <-> nuqta LIMIT n shakli GiST indeksni KNN rejimida ishlatadi: DB butun jadvalni skanlab masofa hisoblamaydi, balki indeks bo'ylab eng yaqindan tartibda tortadi — millionlab qatorda ham millisekundlarda. Radius chegarasi kerak bo'lsa — ST_DWithin (u ham GiST indeksdan foydalanadi). Ikkalasini birlashtirsa bo'ladi: WHERE ST_DWithin(...) (radius filtri) + ORDER BY ... <-> ... (saralash).

PostGIS'siz sof SQL/JS Haversine. Agar PostGIS o'rnatib bo'lmasa (masalan boshqariladigan hosting kengaytmaga ruxsat bermasa), Haversine formulasini 2.2-bob sof SQL yoki JS'da yozib, oddiy lat/lng ustunlar bilan ishlash mumkin. Bu geo-indeksdan foydalanmaydi (har qatorni hisoblaydi — sekin), shuning uchun avval lat/lng bo'yicha oddiy bounding box filtri (2.5a) bilan qatorlar sonini keskin kamaytirish kerak.

sql
-- Sof SQL Haversine (PostGIS'siz — 6371 km Yer radiusi)
SELECT nom,
       6371 * 2 * asin(sqrt(
         power(sin(radians($2 - lat) / 2), 2) +
         cos(radians(lat)) * cos(radians($2)) *
         power(sin(radians($1 - lng) / 2), 2)
       )) AS masofa_km              -- $1=lng, $2=lat
FROM filiallar
WHERE lat BETWEEN $2 - 0.1 AND $2 + 0.1     -- oldindan bounding box (indeks — tez)
  AND lng BETWEEN $1 - 0.1 AND $1 + 0.1
ORDER BY masofa_km ASC LIMIT 10;

TypeORM/Prisma bilan PostGIS. ORM'lar geo-turlarni to'la qo'llab-quvvatlamaydi, shuning uchun geo-so'rovlar odatda raw query orqali yoziladi (yuqoridagi SQL'ni dataSource.query(...) ichida — Misol 4). TypeORM'da geo-ustunni @Column({ type: "geography", spatialFeatureType: "Point", srid: 4326 }) deb e'lon qilish va @Index(..., { spatial: true }) bilan GiST indeks yaratish mumkin (drayver postgres, DB'da PostGIS yoqilgan bo'lishi shart). Prisma'da geo-tur Unsupported("geography(Point, 4326)") sifatida modelga qo'shiladi (o'qish/yozish $queryRaw orqali). Ikkala holatda ham ST_DWithin, <-> kabi so'rovlar parametrli raw query bilan bajariladi (8.4 — SQL-injection'dan himoya).

typescript
// TypeORM: geo-ustun + GiST indeks (entity)
@Entity("filiallar")
@Index("idx_filial_geo", { synchronize: false })       // GiST raw migration'da
export class FilialEntity {
  @PrimaryGeneratedColumn() id: number;
  @Column() nom: string;

  @Column({
    type: "geography",
    spatialFeatureType: "Point",
    srid: 4326,                                          // WGS84 (2.1)
    nullable: true,
  })
  joylashuv: string;                                     // WKT/GeoJSON sifatida
}
// So'rov — raw (parametrli — 8.4)
async engYaqin(lat: number, lng: number, limit = 10) {
  return this.repo.query(
    `SELECT id, nom,
            joylashuv <-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography AS masofa
     FROM filiallar
     ORDER BY joylashuv <-> ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography
     LIMIT $3`,
    [lng, lat, limit],                                   // $1=lng, $2=lat
  );
}

2.5a. GeoJSON, bounding box va clustering (API formati)

text
  GeoJSON — geo-ma'lumot uchun standart JSON format (API javobi):

  Point (nuqta):
  { "type": "Point", "coordinates": [69.24, 41.31] }   // [lng, lat]!

  Polygon (zona — 2.6):
  { "type": "Polygon", "coordinates": [[[lng,lat],[lng,lat],...]] }
   birinchi va oxirgi nuqta bir xil (yopiq halqa)

  Feature (nuqta + xossalar):
  { "type": "Feature",
    "geometry": { "type":"Point", "coordinates":[69.24,41.31] },
    "properties": { "nom": "Filial 1", "narx": 15000 } }

GeoJSON — geo-ma'lumotlarni JSON'da ifodalash standarti (RFC 7946). Frontend xarita kutubxonalari (Leaflet, Yandex, Google — 2.7) GeoJSON'ni to'g'ridan-to'g'ri xaritaga chizadi, shuning uchun geo-API javoblarini GeoJSON'da qaytarish qulay. Asosiy turlar: Point (nuqta), Polygon (zona — birinchi va oxirgi nuqta bir xil — yopiq halqa), LineString (marshrut), Feature (geometriya + properties — nom, narx kabi qo'shimcha ma'lumot), FeatureCollection (ko'p Feature). GeoJSON har doim [lng, lat] tartibda 2.1-bob. PostGIS'da ST_AsGeoJSON, MongoDB'da ma'lumot allaqachon GeoJSON shaklida saqlanadi.

Bounding box (chegara to'rtburchak). Xaritada foydalanuvchi ko'rayotgan hudud — to'rtburchak (min lat/lng, max lat/lng). "Xaritaning shu ko'rinishidagi barcha filiallar"ni so'rashda aynan shu to'rtburchak ichidagilarni qaytariladi. Bounding box — eng arzon filtr (oddiy BETWEEN taqqoslash), shuning uchun uni og'ir geo-hisobdan oldin qo'yish tezlikni oshiradi (2.4a Haversine misoli).

typescript
// Bounding box: xarita ko'rinishidagi filiallar (arzon filtr)
async xaritaViewport(minLat: number, minLng: number, maxLat: number, maxLng: number) {
  // MongoDB $geoWithin + $box (to'rtburchak — [pastki-chap, yuqori-o'ng])
  return this.model.find({
    joylashuv: {
      $geoWithin: { $box: [[minLng, minLat], [maxLng, maxLat]] },   // [lng, lat]!
    },
  });
  // PostGIS muqobili: WHERE joylashuv && ST_MakeEnvelope(minLng,minLat,maxLng,maxLat,4326)
}

Clustering (ko'p nuqtani guruhlash). Xaritada minglab nuqta bo'lsa (masalan butun shahar bo'ylab do'konlar), hammasini bittalab ko'rsatish xaritani ham, brauzerni ham bo'g'adi. Yechim — yaqin nuqtalarni klasterga (masalan "23 ta do'kon") birlashtirish. Odatda buni frontend qiladi (Leaflet MarkerCluster, Yandex clusterer), lekin nuqta juda ko'p bo'lsa backend'da ham guruhlash mumkin: PostGIS'da ST_ClusterKMeans/ST_ClusterDBSCAN yoki grid (kvadratchalar) bo'yicha GROUP BY bilan har hujayradagi nuqtalar sonini qaytarish. Zoom darajasi oshgani sayin klaster maydalanadi.

2.5. Geocoding (manzil koordinata)

text
  GEOCODING — manzil  koordinata:
  "Toshkent, Amir Temur ko'chasi 1"  { lat: 41.31, lng: 69.27 }

  REVERSE GEOCODING — koordinata  manzil:
  { lat: 41.31, lng: 69.27 }  "Toshkent, Amir Temur ko'chasi"

  Xizmatlar (O'zbekiston):
  - Yandex Geocoder API (O'zbekistonda eng aniq)
  - Google Geocoding API
  - OpenStreetMap Nominatim (bepul, kamroq aniq)

   Foydalanuvchi manzil yozadi  geocoding  koordinata (saqlash/masofa)

Geocoding (manzilkoordinata) va reverse geocoding (koordinatamanzil). Foydalanuvchi manzil yozadi geocoding koordinata (saqlash, masofa hisoblash). GPS'dan koordinata reverse o'qiladigan manzil. Xizmatlar: Yandex (O'zbekistonda eng aniq), Google, OpenStreetMap (bepul). API key kerak (.env — 8.14), kvota/narx (kesh qilish — 8.15 — bir manzilni qayta so'ramaslik). Tashqi API (8.20 webhook emas, oddiy so'rov).

Xizmatlarni tanlash. Google Geocoding API — global miqyosda eng barqaror, lekin pullik (so'rov soniga kvota) va O'zbekiston bo'yicha ba'zi hududlarda Yandex'dan kamroq batafsil. Yandex Geocoder — O'zbekiston, Rossiya va MDH mintaqasi uchun odatda eng aniq (mahalliy manzillar, ko'chalar). Nominatim (OpenStreetMap ustida — bepul, ochiq kodli) — mustaqil loyihalar uchun yaxshi, lekin ommaviy bepul serverida qat'iy limit bor: soniyasiga 1 so'rov, User-Agent sarlavhasi shart, ko'p yuklama uchun ta'qiqlangan. Katta hajm kerak bo'lsa Nominatim'ni o'z serveringizga o'rnatib olish mumkin (OSM ma'lumotlaridan). Geocoding taxminiy — bitta manzil turli xizmatlarda biroz farqli koordinata berishi mumkin; kritik holatda foydalanuvchidan xaritada nuqtani tasdiqlashni so'rash yaxshi.

Bepul muqobil — Nominatim (geocoding) va OSRM (marshrut). Byudjeti cheklangan yoki API kalitisiz boshlaydigan loyiha uchun OpenStreetMap ekotizimi to'liq bepul zaxira beradi. Nominatim manzilni koordinataga aylantiradi (va reverse — koordinatani manzilga), OSRM (Open Source Routing Machine) esa yo'l bo'yicha real masofa/vaqt hisoblaydi (Distance Matrix muqobili — 2.7). Ikkalasining ham ommaviy bepul serveri qattiq cheklangan, shuning uchun ishlab chiqarishda kesh 8.15-bob va rate limit majburiy, katta hajmda esa o'z serveringizni ko'tarish tavsiya etiladi.

typescript
// Nominatim (OSM) — bepul geocoding.  User-Agent SHART, soniyasiga 1 so'rov
async geocodeNominatim(manzil: string): Promise<{ lat: number; lng: number } | null> {
  const { data } = await firstValueFrom(this.http.get("https://nominatim.openstreetmap.org/search", {
    params: { q: manzil, format: "jsonv2", limit: 1 },
    headers: { "User-Agent": "MeningIlovam/1.0 (aloqa@example.uz)" },   // majburiy (aks holda bloklanadi)
  }));
  if (!data?.length) return null;
  return { lat: Number(data[0].lat), lng: Number(data[0].lon) };        // Nominatim: string qaytaradi
}

// Reverse (koordinata  manzil)
async reverseNominatim(lat: number, lng: number): Promise<string | null> {
  const { data } = await firstValueFrom(this.http.get("https://nominatim.openstreetmap.org/reverse", {
    params: { lat, lon: lng, format: "jsonv2" },
    headers: { "User-Agent": "MeningIlovam/1.0 (aloqa@example.uz)" },
  }));
  return data?.display_name ?? null;                                    // to'liq manzil matni
}

// OSRM — yo'l bo'yicha real masofa/vaqt (bepul Distance Matrix muqobili — 2.7)
//  OSRM URL tartibi: {lng},{lat} — uzunlik BIRINCHI (2.1)
async realMasofaOsrm(from: { lat: number; lng: number }, to: { lat: number; lng: number }) {
  const koord = `${from.lng},${from.lat};${to.lng},${to.lat}`;
  const { data } = await firstValueFrom(
    this.http.get(`https://router.project-osrm.org/route/v1/driving/${koord}`, {
      params: { overview: "false" },                                   // geometriya kerak emas — faqat masofa/vaqt
    }),
  );
  const r = data?.routes?.[0];
  if (!r) return null;
  return { masofa: r.distance, vaqt: r.duration };                     // metr, soniya (yo'l bo'yicha)
}

2.6. Yetkazib berish zonasi (poligon)

typescript
// Zona — poligon (xaritadagi maydon). Nuqta zona ichidami?
async zonadaMi(lat: number, lng: number): Promise<DeliveryZone | null> {
  // MongoDB $geoIntersects — nuqta poligon ichidami
  return this.zoneModel.findOne({
    hudud: {
      $geoIntersects: {
        $geometry: { type: "Point", coordinates: [lng, lat] },
      },
    },
  });
}
// Zona hudud: GeoJSON Polygon (koordinatalar ro'yxati — chegara)
//  "Bu manzilga yetkazamizmi?" + qaysi zona (narx farqi)

Yetkazib berish zonasi (poligon — chegaralangan hudud): "Bu manzilga yetkazamizmi?" — nuqta zona (poligon) ichidami. MongoDB $geoIntersects / PostGIS ST_Contains — nuqta poligon ichini tekshiradi. Zona — GeoJSON Polygon (chegara koordinatalari). Har zona — narx farqi (markaz arzon, chekka qimmat). Yetkazib berish ilovasida zarur (xizmat hududi, narx). Poligon — xarita muharririda chiziladi.

2.7. Xarita xizmatlari (O'zbekiston)

text
  XARITA XIZMATLARI:
  ┌──────────────┬────────────────────────────────────────────┐
  │ Yandex Maps  │ O'zb  istonda ENG ANIQ (manzil, marshrut)   │
  │              │ Geocoder, Routing, Distance Matrix          │
  ├──────────────┼────────────────────────────────────────────┤
  │ Google Maps  │ Global, kuchli (ba'zi joyda O'zb kamroq)    │
  ├──────────────┼────────────────────────────────────────────┤
  │ OpenStreetMap│ Bepul, ochiq (Nominatim geocode, OSRM rout)│
  │ + Leaflet    │ Frontend xarita (bepul)                    │
  └──────────────┴────────────────────────────────────────────┘
  Backend: geocoding, distance matrix (real masofa — yo'l bo'yicha)
  Frontend (11): xarita ko'rsatish (Yandex/Leaflet)

Xarita xizmatlari: Yandex Maps (O'zbekistonda eng aniq — manzil, marshrut — Geocoder/Routing/Distance Matrix), Google Maps (global), OpenStreetMap+Leaflet (bepul). Backend: geocoding 2.5-bob, Distance Matrix (real masofa — yo'l bo'yicha, Haversine to'g'ri chiziqdan farqli — traffic bilan). Frontend (11): xarita ko'rsatish. O'zbekiston uchun Yandex afzal (mahalliy aniqlik). API key + kvota.

Provayder API'sini backend orqali o'rash (proxy, rate limit, kesh). Xarita provayderi (Yandex/Google/OSRM) — pullik yoki kvotali tashqi xizmat, shuning uchun uni to'g'ridan-to'g'ri frontend'dan chaqirish ikki jiddiy xatoga olib keladi. Birinchidan, API kalit maxfiyligi: kalitni brauzer kodiga qo'ysangiz har bir foydalanuvchi uni ko'radi (DevTools Network) va o'g'irlab, sizning hisobingizdan foydalanishi mumkin (hisobingizga katta hisob-kitob keladi). Kalit faqat backend'da (.env — 8.14) turishi va hech qachon frontend'ga chiqmasligi kerak. Ikkinchidan, kvota va narx nazorati: agar har bir foydalanuvchi to'g'ridan-to'g'ri provayderga so'rov yuborsa, siz nechta so'rov ketayotganini nazorat qila olmaysiz. Yechim — barcha geo-so'rovlarni o'z backend proxy'ingiz orqali o'tkazish: frontend sizning /geo/geocode endpoint'ingizga so'raydi, backend esa provayderga (kalit bilan) so'rab, javobni qaytaradi. Bu joyda siz uch qatlam himoya qo'shasiz — kesh (bir xil so'rovni qayta yubormaslik — kvota tejash, 8.15), rate limit (bir foydalanuvchi soatiga necha marta — 8.6 @nestjs/throttler), va provayder xatosini (kvota tugadi, 429) yumshoq qayta ishlash.

typescript
// Provayder proxy: kalit backend'da, kesh + rate limit (frontend to'g'ridan-to'g'ri provayderga bormaydi)
@Controller("geo")
@UseGuards(ThrottlerGuard)                                // 8.6 — rate limit
export class GeoController {
  constructor(private geocoding: GeocodingService) {}

  @Get("geocode")
  @Throttle({ default: { limit: 20, ttl: 60_000 } })     // 1 foydalanuvchi: 20 so'rov / daqiqa
  async geocode(@Query("q") manzil: string) {
    if (!manzil || manzil.length < 3) {
      throw new BadRequestException("Manzil juda qisqa");  // arzon so'rovlarni oldini olish
    }
    const natija = await this.geocoding.geocode(manzil);   // ichida kesh (Misol 5) — kvota tejash
    if (!natija) throw new NotFoundException("Manzil topilmadi");
    return natija;                                         // API kalit hech qachon frontend'ga chiqmaydi
  }
}

API kalitini hech qachon frontend'ga qo'ymang. Geo-provayderni doim backend proxy orqali o'rang: kalit .env'da 8.14-bob, so'rovlar keshlanadi (kvota — 8.15), rate limit qo'yiladi (suiiste'mol — 8.6), provayder xatosi (429 kvota) yumshoq qaytariladi. Bu — geolokatsiya backend'ining eng muhim amaliy qoidasi (aks holda hisobingiz o'g'irlanadi yoki kvota bir kunda tugaydi).

2.8. Real-time kuzatuv (kuryer — 8.18)

typescript
// Kuryer joylashuvini real-time kuzatish (taksi/yetkazib berish)
@SubscribeMessage("kuryer_joylashuv")                 // WebSocket (8.18)
async kuryerJoylashuv(@MessageBody() data: { kuryerId: string; lat: number; lng: number }) {
  // Joylashuvni saqlash (Redis — tez, vaqtinchalik — 8.15)
  await this.redis.geoadd("kuryerlar", data.lng, data.lat, data.kuryerId);
  // Buyurtma mijoziga uzatish (kuryer xaritada harakatlanadi)
  const buyurtma = await this.findActiveOrder(data.kuryerId);
  if (buyurtma) {
    this.server.to(`order_${buyurtma.id}`).emit("kuryer_harakati", { lat: data.lat, lng: data.lng });
  }
}
// Redis GEO — geo-qidiruv tez (GEOSEARCH — eng yaqin kuryer)

Real-time kuzatuv (kuryer — taksi/yetkazib berish): kuryer joylashuvini muntazam yuboradi (WebSocket — 8.18) saqlash (Redis GEO — tez, vaqtinchalik — 8.15) mijozga uzatish (kuryer xaritada harakatlanadi). Redis GEO (GEOADD/GEOSEARCH) — eng yaqin kuryerni tez topish (taksi — "eng yaqin haydovchi"). DB emas (tez-tez o'zgaradi — Redis). Bu — taksi/yetkazib berish UX'ining yuragi (jonli kuzatuv).

2.9. Geolokatsiya xavfsizligi va aniqlik (14)

text
   Joylashuv MAXFIY (foydalanuvchi uyi — ruxsat bilan, EXIF tozalash — 8.24)
   Faqat kerakli aniqlik (taksi — aniq; statistika — shahar darajasi)
   Koordinata validatsiyasi (lat -90..90, lng -180..180 — 8.5)
   Geocoding kesh (kvota/narx — 8.15, 2.5)
   Tartib diqqat ([lng,lat] vs [lat,lng] — 2.1)
   Geo-indeks (tezlik — 2.4); Redis GEO (real-time — 2.8)
   Joylashuv tarixini saqlash muddati (maxfiylik — kerakli vaqt)

Geolokatsiya xavfsizligi: joylashuv — maxfiy ma'lumot (foydalanuvchi uyi/harakati — ruxsat bilan, EXIF tozalash — 8.24, GDPR); faqat kerakli aniqlik (statistika — shahar darajasi yetadi, aniq emas); koordinata validatsiyasi 8.5-bob; geocoding kesh (kvota/narx — 8.15); tartib diqqat 2.1-bob; saqlash muddati (joylashuv tarixi — kerakli vaqt). Joylashuv — nozik (suiiste'mol — kuzatuv). Maxfiylikni hurmat qilish.

2.10. Best practices (geolokatsiya)

text
   Geo-indeks (eng yaqin — tez — DB so'rovi — 2.3, 2.4)
   GeoJSON tartib ([lng, lat] — diqqat — 2.1)
   Haversine (oddiy masofa); Distance Matrix (real yo'l — 2.2, 2.7)
   Geocoding kesh (kvota — 8.15, 2.5)
   Zona — poligon ($geoIntersects — 2.6)
   Real-time  Redis GEO + WebSocket 2.8-bob
   Yandex (O'zbekiston aniqlik — 2.7)
   Joylashuv maxfiyligi (ruxsat, aniqlik, muddat — 14, 2.9)
   Koordinata validatsiya (8.5)

3. Sintaksis — tez ma'lumotnoma

typescript
// Koordinata 2.1-bob: GeoJSON Point { type: "Point", coordinates: [lng, lat] }
// Masofa 2.2-bob: Haversine (R=6371 km)
// Geo-indeks 2.4-bob: Mongo 2dsphere; PostGIS GIST
// Eng yaqin 2.4-bob: $near + $maxDistance / ST_DWithin
// Zona 2.6-bob: $geoIntersects / ST_Contains (poligon)
// Real-time 2.8-bob: Redis GEOADD/GEOSEARCH + WebSocket

4. Batafsil kod namunalari

Misol 1 — Geo schema + indeks (Mongo — 2.4, 8.13)

typescript
@Schema({ timestamps: true })
export class Filial {
  @Prop({ required: true }) nom: string;
  @Prop({ required: true }) manzil: string;

  @Prop({
    type: { type: String, enum: ["Point"], default: "Point" },
    coordinates: { type: [Number], required: true },   // [lng, lat] — GeoJSON (2.1)
  })
  joylashuv: { type: string; coordinates: [number, number] };
}
export const FilialSchema = SchemaFactory.createForClass(Filial);
FilialSchema.index({ joylashuv: "2dsphere" });        // GEO-INDEKS (2.4)

Misol 2 — Eng yaqin filiallar (2.3, 2.4)

typescript
@Injectable()
export class FilialService {
  constructor(@InjectModel(Filial.name) private model: Model<Filial>) {}

  async engYaqin(lat: number, lng: number, radiusKm = 5, limit = 10) {
    return this.model.find({
      joylashuv: {
        $near: {                                       // eng yaqin (saralangan)
          $geometry: { type: "Point", coordinates: [lng, lat] },   // [lng, lat]!
          $maxDistance: radiusKm * 1000,              // metr
        },
      },
    }).limit(limit).exec();
    //  eng yaqindan tartibda, radius ichida (indeks bilan tez)
  }

  // Masofa bilan (aggregation — $geoNear)
  async engYaqinMasofaBilan(lat: number, lng: number) {
    return this.model.aggregate([
      {
        $geoNear: {
          near: { type: "Point", coordinates: [lng, lat] },
          distanceField: "masofa",                     // har filialga masofa (metr)
          maxDistance: 10000, spherical: true,
        },
      },
      { $limit: 10 },
    ]);
    //  [{ nom, masofa: 1234 }, ...] (masofa bilan)
  }
}

Misol 3 — Controller + validatsiya (2.9, 8.5)

typescript
class NearbyDto {
  @Type(() => Number) @IsLatitude() lat: number;       // -90..90 (validatsiya — 8.5)
  @Type(() => Number) @IsLongitude() lng: number;      // -180..180
  @Type(() => Number) @IsOptional() @Min(0.1) @Max(50) radius?: number = 5;
}

@Controller("filiallar")
export class FilialController {
  @Get("nearby")
  engYaqin(@Query() dto: NearbyDto) {                  // ?lat=41.31&lng=69.24&radius=5
    return this.service.engYaqin(dto.lat, dto.lng, dto.radius);
  }
}

Misol 4 — PostGIS (PostgreSQL — 2.4)

typescript
// PostGIS — geography turi + GIST indeks
// migration: ALTER TABLE filiallar ADD COLUMN joylashuv geography(POINT);
//            CREATE INDEX ON filiallar USING GIST(joylashuv);

@Injectable()
export class FilialPgService {
  // Eng yaqin (PostGIS — ST_DWithin + ST_Distance)
  async engYaqin(lat: number, lng: number, radiusKm: number) {
    return this.dataSource.query(`
      SELECT *, ST_Distance(joylashuv, ST_MakePoint($1, $2)::geography) AS masofa
      FROM filiallar
      WHERE ST_DWithin(joylashuv, ST_MakePoint($1, $2)::geography, $3)   -- radius (metr)
      ORDER BY masofa ASC LIMIT 10
    `, [lng, lat, radiusKm * 1000]);                  // $1=lng, $2=lat (8.4 parametrli)
  }
}

Misol 5 — Geocoding (Yandex — 2.5)

typescript
@Injectable()
export class GeocodingService {
  constructor(
    private http: HttpService,                         // (2.18-JS Axios)
    private config: ConfigService,
    @Inject(CACHE_MANAGER) private cache: Cache,       // kesh (8.15)
  ) {}

  // Manzil  koordinata (kesh bilan — kvota — 2.5)
  async geocode(manzil: string): Promise<{ lat: number; lng: number } | null> {
    const keshKalit = `geocode:${manzil}`;
    const keshda = await this.cache.get(keshKalit);
    if (keshda) return keshda as any;                  // keshdan (kvota tejash)

    const { data } = await firstValueFrom(this.http.get("https://geocode-maps.yandex.ru/1.x", {
      params: { apikey: this.config.get("YANDEX_API_KEY"), geocode: manzil, format: "json" },
    }));
    const pos = data.response?.GeoObjectCollection?.featureMember?.[0]?.GeoObject?.Point?.pos;
    if (!pos) return null;
    const [lng, lat] = pos.split(" ").map(Number);     // Yandex: "lng lat"
    const natija = { lat, lng };
    await this.cache.set(keshKalit, natija, 86400000); // 1 kun kesh
    return natija;
  }
}

Misol 6 — Yetkazib berish zonasi (poligon — 2.6)

typescript
@Schema()
export class DeliveryZone {
  @Prop() nom: string;
  @Prop() narx: number;                                // shu zona yetkazish narxi
  @Prop({
    type: { type: String, enum: ["Polygon"] },
    coordinates: { type: [[[Number]]] },               // poligon (GeoJSON)
  })
  hudud: { type: string; coordinates: number[][][] };
}
DeliveryZoneSchema.index({ hudud: "2dsphere" });

// Manzil zonadami + narx
async yetkazishNarxi(lat: number, lng: number): Promise<number> {
  const zona = await this.zoneModel.findOne({
    hudud: { $geoIntersects: { $geometry: { type: "Point", coordinates: [lng, lat] } } },
  });
  if (!zona) throw new BadRequestException("Bu manzilga yetkazib bermaymiz");
  return zona.narx;
}

Misol 7 — Distance Matrix (real masofa/vaqt — 2.7)

typescript
// Yo'l bo'yicha real masofa va vaqt (Haversine to'g'ri chiziq emas — traffic)
async realMasofa(from: { lat: number; lng: number }, to: { lat: number; lng: number }) {
  const { data } = await firstValueFrom(this.http.get("https://api.routing.yandex.net/v2/route", {
    params: {
      apikey: this.config.get("YANDEX_ROUTING_KEY"),
      waypoints: `${from.lat},${from.lng}|${to.lat},${to.lng}`,
    },
  }));
  return {
    masofa: data.distance,                             // real yo'l masofasi (metr)
    vaqt: data.duration,                               // traffic bilan vaqt (soniya)
  };
  //  yetkazish vaqti taxmini, narx (real masofa — Haversine'dan aniqroq)
}

Misol 8 — Real-time kuryer kuzatuvi (Redis GEO + WebSocket — 2.8)

typescript
@WebSocketGateway({ namespace: "tracking" })
export class TrackingGateway {
  @WebSocketServer() server: Server;
  constructor(@InjectRedis() private redis: Redis) {}

  // Kuryer joylashuvini yangilash
  @SubscribeMessage("kuryer_update")
  async kuryerUpdate(@MessageBody() d: { kuryerId: string; lat: number; lng: number; orderId: string }) {
    await this.redis.geoadd("kuryerlar", d.lng, d.lat, d.kuryerId);   // Redis GEO (2.8)
    // Mijozga uzatish (kuryer xaritada harakatlanadi)
    this.server.to(`order_${d.orderId}`).emit("kuryer_harakati", { lat: d.lat, lng: d.lng });
  }

  // Eng yaqin kuryer (taksi — buyurtma uchun)
  async engYaqinKuryer(lat: number, lng: number) {
    return this.redis.geosearch("kuryerlar", "FROMLONLAT", lng, lat,
      "BYRADIUS", 5, "km", "ASC", "COUNT", 5);        // 5 km, eng yaqin 5
  }
}

Misol 9 — To'liq yetkazib berish oqimi (2.6, 2.7, 2.8)

typescript
@Injectable()
export class DeliveryService {
  // Buyurtma berishda — manzil tekshirish + narx
  async buyurtmaTekshir(lat: number, lng: number) {
    const narx = await this.yetkazishNarxi(lat, lng);          // zona (Misol 6)
    const filial = await this.filialService.engYaqin(lat, lng, 10, 1);   // eng yaqin filial (Misol 2)
    const masofa = await this.realMasofa(                       // real masofa (Misol 7)
      { lat: filial[0].joylashuv.coordinates[1], lng: filial[0].joylashuv.coordinates[0] },
      { lat, lng },
    );
    return {
      yetkaziladi: true, narx, filial: filial[0].nom,
      taxminiyVaqt: Math.ceil(masofa.vaqt / 60),               // daqiqa
    };
  }
}

Misol 10 — Geo modul (to'liq)

text
  src/geo/
  ├── geocoding.service.ts       (manzilkoordinata — Misol 5)
  ├── filial.service.ts          (eng yaqin — Misol 2)
  ├── delivery.service.ts        (zona, narx — Misol 6, 9)
  ├── tracking.gateway.ts        (real-time — Misol 8)
  ├── routing.service.ts         (distance matrix — Misol 7)
  └── geo.module.ts

  Oqim (yetkazib berish):
  - Manzil  geocoding  koordinata (Misol 5)
  - Zona tekshir + narx (Misol 6); eng yaqin filial (Misol 2)
  - Real masofa/vaqt (Misol 7)
  - Kuryer real-time kuzatuv (Redis GEO + WebSocket — Misol 8)

   Geo-indeks (tez); GeoJSON [lng,lat]; Yandex (O'zbekiston); maxfiylik (14)

5. To'g'ri va noto'g'ri holatlar

1) Geo-indekssiz eng yaqin

text
 har nuqtani Haversine (sekin — 2.3)
 geo-indeks ($near/ST_DWithin)

2) [lat, lng] vs [lng, lat] chalkashlik

text
 tartib aralash (nuqta okeanda — 2.1)
 GeoJSON [lng, lat] (diqqat)

3) Geocoding har safar (kesh yo'q)

text
 bir manzil qayta-qayta (kvota/narx — 2.5)
 kesh (8.15)

4) To'g'ri chiziq masofa (yetkazish)

text
 Haversine (yo'l emas — 2.7)
 Distance Matrix (real yo'l/traffic)

5) Joylashuv ruxsatsiz/cheksiz saqlash

text
 maxfiy joylashuv suiiste'mol (14, 2.9)
 ruxsat + kerakli aniqlik + muddat

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Eng yaqin sekin

Sababi: geo-indeks yo'q 2.4-bob. Yechimi: 2dsphere/GIST indeks.

Xato 2 — Nuqta noto'g'ri joyda

Sababi: [lat,lng] tartib 2.1-bob. Yechimi: GeoJSON [lng, lat].

Xato 3 — Geocoding kvota tugadi

Sababi: kesh yo'q 2.5-bob. Yechimi: kesh.

Xato 4 — $near indeks xatosi

Sababi: 2dsphere indeks yo'q. Yechimi: schema.index 2dsphere.

Xato 5 — Masofa noto'g'ri (uzoq)

Sababi: to'g'ri chiziq 2.2-bob. Yechimi: Haversine; real — Distance Matrix.

Xato 6 — Koordinata noto'g'ri (validatsiyasiz)

Sababi: lat/lng tekshirilmagan 2.9-bob. Yechimi: @IsLatitude/@IsLongitude 8.5-bob.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Mongo geo 8.13-bob: 2dsphere, $near.
  • PostgreSQL 6.6-bob: PostGIS.
  • Real-time 8.18-bob: kuryer kuzatuv.
  • Redis 8.15-bob: GEO, kesh.
  • Telegram bot 8.12-bob: location.
  • DTO 8.5-bob: koordinata validatsiya.
  • Frontend (11): xarita (Yandex/Leaflet).
  • Xavfsizlik (14): joylashuv maxfiyligi.

8. Eng yaxshi amaliyotlar (best practices)

  • Geo-indeks (eng yaqin — tez — 2.3, 2.4).
  • GeoJSON tartib ([lng, lat] — 2.1).
  • Haversine (oddiy) / Distance Matrix (real yo'l) (2.2, 2.7).
  • Geocoding kesh (kvota — 2.5).
  • Zona poligon ($geoIntersects — 2.6).
  • Real-time Redis GEO + WebSocket 2.8-bob.
  • Yandex (O'zbekiston aniqlik — 2.7).
  • Joylashuv maxfiyligi (ruxsat, aniqlik, muddat — 14, 2.9).
  • Koordinata validatsiya 8.5-bob.
  • Kerakli aniqlik (statistika — shahar; taksi — aniq — 2.9).

9. Amaliy loyiha: "Yetkazib Berish Geo Tizimi"

Geolokatsiyani amalda mustahkamlash.

Maqsad

Yetkazib berish geo-tizimi: eng yaqin filial, zona/narx, geocoding, real-time kuryer.

Talablar (requirements)

  1. Geo schema: 2dsphere indeks (Misol 1, 2.4).
  2. Eng yaqin: $near + masofa (Misol 2, 2.3).
  3. Validatsiya: koordinata (Misol 3, 2.9).
  4. PostGIS (muqobil): ST_DWithin (Misol 4).
  5. Geocoding: Yandex + kesh (Misol 5, 2.5).
  6. Zona: poligon + narx (Misol 6, 2.6).
  7. Distance Matrix: real masofa/vaqt (Misol 7, 2.7).
  8. Real-time: Redis GEO + WebSocket (Misol 8, 2.8).
  9. To'liq oqim: buyurtma tekshir (Misol 9).
  10. Xavfsizlik: maxfiylik, validatsiya 2.9-bob.

Maslahatlar (hint)

  • Geo-indeks (2.4, 1-xato).
  • [lng, lat] (2.1, 2-xato).
  • Geocoding kesh (2.5, 3-xato).
  • Distance Matrix real (2.7, 5-xato).
  • Redis GEO 2.8-bob.
  • Maxfiylik (14, 5-holat).

"Tayyor" mezonlari (acceptance criteria)

  • Geo schema (indeks).
  • Eng yaqin.
  • Validatsiya.
  • PostGIS.
  • Geocoding (kesh).
  • Zona.
  • Distance Matrix.
  • Real-time.
  • To'liq oqim.
  • Xavfsizlik.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda geolokatsiyani o'rgandik:

  • Koordinatalar (lat/lng, tartib — 2.1); masofa (Haversine — 2.2); eng yaqin (geo-qidiruv — 2.3); geo-indeks (2dsphere/PostGIS — 2.4).
  • Geocoding (manzilkoordinata — 2.5); zona (poligon — 2.6); xarita xizmatlari (Yandex — 2.7).
  • Real-time kuzatuv (Redis GEO + WebSocket — 2.8); xavfsizlik (maxfiylik — 2.9).

Keyingi bob — 8.29: Subscription va davriy to'lov (recurring billing). Geolokatsiyani bildik; endi SaaS biznes modelining moliyaviy tomonini — obuna va davriy to'lov (oylik/yillik, trial, upgrade, bekor qilish) — o'rganamiz. Multi-tenancy 8.25-bob bilan birga to'liq SaaS.


Foydalanilgan rasmiy/ishonchli manbalar

  • MongoDB Geospatial Queries — rasmiy hujjat (2dsphere indeks, $near, $geoNear, $geoWithin, $geoIntersects)
  • PostGIS rasmiy hujjati — geometry vs geography, ST_Distance, ST_DWithin, ST_Contains, <-> (KNN), GiST spatial indeks
  • GeoJSON standarti — RFC 7946 (Point, Polygon, Feature, koordinata tartibi [lng, lat])
  • WGS84 / EPSG:4326 va Web Mercator / EPSG:3857 — koordinata tizimlari (SRID)
  • Yandex Maps API — Geocoder, Routing, Distance Matrix (O'zbekiston mintaqasi)
  • Google Maps Platform — Geocoding API, Directions API, Distance Matrix API
  • OpenStreetMap Nominatim (geocoding — foydalanish siyosati) va OSRM (marshrut)
  • Redis GEO buyruqlari — GEOADD, GEOSEARCH, GEODIST
  • TypeORM (spatial ustunlar) va Prisma (Unsupported tur, $queryRaw) — PostGIS bilan ishlash

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.28-bob: Geolokatsiya va xaritalar — Wisar