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)
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)
// 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)
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)
// 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): MongoDB —
2dsphereindeks 8.13-bob +$near(eng yaqin) /$geoWithin(zona ichi); PostgreSQL — PostGIS (geographyturi + 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.
-- 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); 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
geometryvsgeography— PostGIS'da eng muhim tanlov.geometrykoordinatalarni yassi tekislik deb hisoblaydi: tez, lekin masofa gradusda chiqadi (km emas) va Yer egriligini e'tiborsiz qoldiradi — kichik hududlar yoki xarita rasmini chizish uchun.geographyYerni shar deb oladi:ST_Distanceto'g'ridan-to'g'ri metrda real masofa qaytaradi (egrilik bilan). Yetkazib berish, taksi, "5 km radius" kabi masofaga tayanadigan amallardageographyto'g'ri tanlov.geometrybilanST_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.
-- <-> 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 nshakli 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.
-- 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).
// 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)
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'daST_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).
// 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_ClusterDBSCANyoki grid (kvadratchalar) bo'yichaGROUP BYbilan har hujayradagi nuqtalar sonini qaytarish. Zoom darajasi oshgani sayin klaster maydalanadi.
2.5. Geocoding (manzil koordinata)
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.
// 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)
// 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/ PostGISST_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)
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.
// 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)
// 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)
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)
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
// 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 + WebSocket4. Batafsil kod namunalari
Misol 1 — Geo schema + indeks (Mongo — 2.4, 8.13)
@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)
@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)
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)
// 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)
@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)
@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)
// 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)
@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)
@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)
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
har nuqtani Haversine (sekin — 2.3)
geo-indeks ($near/ST_DWithin)2) [lat, lng] vs [lng, lat] chalkashlik
tartib aralash (nuqta okeanda — 2.1)
GeoJSON [lng, lat] (diqqat)3) Geocoding har safar (kesh yo'q)
bir manzil qayta-qayta (kvota/narx — 2.5)
kesh (8.15)4) To'g'ri chiziq masofa (yetkazish)
Haversine (yo'l emas — 2.7)
Distance Matrix (real yo'l/traffic)5) Joylashuv ruxsatsiz/cheksiz saqlash
maxfiy joylashuv suiiste'mol (14, 2.9)
ruxsat + kerakli aniqlik + muddat6. 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)
- Geo schema: 2dsphere indeks (Misol 1, 2.4).
- Eng yaqin: $near + masofa (Misol 2, 2.3).
- Validatsiya: koordinata (Misol 3, 2.9).
- PostGIS (muqobil): ST_DWithin (Misol 4).
- Geocoding: Yandex + kesh (Misol 5, 2.5).
- Zona: poligon + narx (Misol 6, 2.6).
- Distance Matrix: real masofa/vaqt (Misol 7, 2.7).
- Real-time: Redis GEO + WebSocket (Misol 8, 2.8).
- To'liq oqim: buyurtma tekshir (Misol 9).
- 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 (
2dsphereindeks,$near,$geoNear,$geoWithin,$geoIntersects) - PostGIS rasmiy hujjati —
geometryvsgeography,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 (
Unsupportedtur,$queryRaw) — PostGIS bilan ishlash
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!