Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Koordinatalar va SRID
- 2.1 Kenglik va uzunlik
- 2.2 SRID 4326
- 2.3 geometry yoki geography
- 3. PostGIS'siz masofa: Haversine
- 3.1 Formulani SQL'da
- 3.2 Xuddi shu — JavaScript'da
- 4. Asosiy so'rovlar
- 4.1 Radius ichida: ST_DWithin
- 4.2 Eng yaqinlar: KNN <->
- 4.3 Poligon ichida nuqta
- 5. Tuzoq: kenglik va uzunlik almashsa
- 6. Indeks: GiST va o'lchov
- 6.1 Nega ST_DWithin tez, ST_Distance < sekin
- 7. Halollik va chegaralar
- 8. Ko'p uchraydigan xatolar
- 8.1 POINT(kenglik uzunlik)
- 8.2 geometry 4326 da masofa
- 8.3 Radius — ST_Distance < N
- 8.4 GiST indeksni unutish
- 8.5 KNN radiusiz
- 9. Mashqlar
- 1-mashq (oson): Eng yaqin topshirish punkti (JS)
- 2-mashq (o'rta): Radius ichidagilar (SQL, PostGIS'siz)
- 3-mashq (qiyin): Yetkazib berish so'rovi (PostGIS)
- 10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 11. Real ishda
- Xulosa
- Manbalar
PostGIS va geo so'rovlar: geography, ST_DWithin va eng yaqin nuqtalar
Qisqacha: PostGIS — Postgres'ga xarita ma'lumotlarini qo'shadigan extension (kengaytma). Joylashuv uchun
geography(Point, 4326)turi masofani metrda beradi (geometryesa gradusda). "3 km ichidagilar" —ST_DWithin(indeksdan foydalanadi), "eng yaqin 10 ta" —ORDER BY joy <-> nuqta LIMIT 10(KNN). Ikkalasi ham GiST indeks bilan: kanonda 200 000 nuqtada indekssiz 315 ms, GiST bilan 2.3 ms, eng yaqin 5 ta — 0.19 ms. Eng ko'p uchraydigan xato —POINT(kenglik uzunlik): to'g'risiPOINT(uzunlik kenglik).
Bu darsda
geometryvageographyfarqini, SRID 4326 nimaligini tushuntirasiz.ST_Distance,ST_DWithin,ST_Coversva KNN<->bilan so'rov yozasiz.- Nega
ST_Distance(…) < Nsekin,ST_DWithintez ekanini EXPLAIN orqali ko'rsatasiz. - Uzunlik/kenglik tartibi xatosini taniysiz.
- PostGIS'siz ham masofani hisoblay olasiz (Haversine formulasi — SQL va JS'da).
Oldin bilishingiz kerak: Extension'lar, Indeks turlari: GIN, GiST, BRIN va pg_trgm, EXPLAIN ANALYZE'ni o'qish.
1. Nega bu kerak?
Yandex Go "eng yaqin haydovchini" topadi. Uzum Market "sizga eng yaqin topshirish punktini" ko'rsatadi. Bank ilovasida "yaqindagi bankomatlar". Hammasi bitta savol: "menga eng yaqin 10 ta nuqta qaysi va ular qancha uzoqda?"
Birinchi fikr: kenglik va uzunlikni ikki float ustunda saqlab, Pifagor bilan hisoblash. Ikki muammo: Yer — shar (aniqrog'i, biroz yassilangan shar), gradus esa metr emas; va har so'rovda har qator uchun formula — indekssiz to'liq o'qish.
O'xshatish: kutubxona javonlari va katalog kartochkalari. Har kitobni ochib "bu fizika kitobimi?" deb ko'rish — sekin. Katalog esa "fizika — 5-qator, 3-javon" deydi. Geo indeks (GiST) — xarita uchun katalog: "bu hududdagi nuqtalar — mana bu bloklarda".
Halol eslatma: PostGIS Postgres bilan birga kelmaydi (Extension'lar darsida extension "postgis" is not available ni ko'rdik). Kanon uni alohida Docker image'da sinagan: postgis/postgis:18-3.6 (PostGIS loyihasining image'i, Docker Official Image emas), PostGIS 3.6.4, PostgreSQL 18.6 (kanon o'lchovi, IlmHamroh, 2026-10). Bizning PostgreSQL 17.6 klasterimizda PostGIS yo'q — shuning uchun PostGIS so'rovlari ostidagi natijalar — kanon chiqishi. PostGIS'siz hisoblar (Haversine) esa o'zimiz ishga tushirgan. Koordinatalar xarita bo'yicha taxminiy, o'quv misoli.
2. Koordinatalar va SRID
2.1 Kenglik va uzunlik
Yer sharidagi har nuqta ikki son bilan:
- Kenglik (latitude, lat) — ekvatordan shimol yoki janubga, −90…90 gradus. Toshkent ≈ 41.3.
- Uzunlik (longitude, lon) — Grinvichdan sharq yoki g'arbga, −180…180. Toshkent ≈ 69.2.
Google Maps "41.3266, 69.2346" deb ko'rsatadi — avval kenglik. PostGIS va GeoJSON esa avval uzunlik: POINT(69.2346 41.3266) — chunki matematikada avval x (gorizontal — uzunlik), keyin y. Bu tartib farqi — butun mavzudagi eng ko'p xato manbai (pastda o'lchangan).
2.2 SRID 4326
SRID (Spatial Reference ID) — koordinatalar qaysi tizimda ekanini bildiradigan raqam. 4326 — WGS 84: GPS, telefonlar, Google va Yandex xaritalari ishlatadigan tizim. Amalda: telefondan kelgan har koordinata — 4326.
2.3 geometry yoki geography
PostGIS ikki tur beradi:
geometry |
geography |
|
|---|---|---|
| Yer shakli | tekis tekislik | sferoid (haqiqiy Yer) |
| 4326 da masofa | gradusda | metrda |
| Tezlik va funksiyalar | tezroq, funksiyalar ko'p | sekinroq, asosiy funksiyalar |
| Qachon | xaritada chizish, kichik hudud (proyeksiyada) | "qancha metr?", butun dunyo bo'ylab |
Kanon ikkalasini bir xil nuqtalarda solishtirgan:
CREATE TABLE joylar (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
nom text NOT NULL,
joy geography(Point, 4326) NOT NULL);
INSERT INTO joylar (nom, joy) VALUES
-- POINT(uzunlik kenglik) — TARTIB: lon lat!
('Chorsu bozori', 'SRID=4326;POINT(69.2346 41.3266)'),
('Amir Temur xiyoboni', 'SRID=4326;POINT(69.2797 41.3111)'),
('Toshkent temir yo''l vokzali',
'SRID=4326;POINT(69.2880 41.2920)'),
('Samarqand Registon', 'SRID=4326;POINT(66.9750 39.6547)');
CREATE INDEX joylar_joy_idx ON joylar USING gist (joy);
SELECT a.nom, b.nom, round(ST_Distance(a.joy, b.joy)) AS metr,
round(ST_Distance(a.joy::geometry,
b.joy::geometry)::numeric, 4) AS gradus
FROM joylar a, joylar b WHERE a.id = 1 AND b.id > 1 ORDER BY b.id;Kanon chiqishi (PostgreSQL 18.6, PostGIS 3.6.4):
nom | nom | metr | gradus
---------------+-----------------------------+--------+--------
Chorsu bozori | Amir Temur xiyoboni | 4150 | 0.0477
Chorsu bozori | Toshkent temir yo'l vokzali | 5896 | 0.0636
Chorsu bozori | Samarqand Registon | 266750 | 2.8109
(3 rows)geography — 4 150 m: Chorsu'dan Amir Temur xiyobonigacha taxminan 4 km, haqiqatga mos. geometry — 0.0477 "gradus": bu tekislikdagi masofa, metrga aylantirib bo'lmaydi (bir gradus uzunlik ekvatorda ~111 km, Toshkentda ~84 km). Ilova uchun geography — "qancha metr" savoli uchun to'g'ri tur.
'SRID=4326;POINT(…)' — EWKT (Extended Well-Known Text) shakli: nuqtani oddiy matn bilan yozish, oldida SRID raqami bilan. Funksiya bilan ham bo'ladi: ST_SetSRID(ST_MakePoint(uzunlik, kenglik), 4326)::geography — e'tibor bering, bu yerda ham avval uzunlik.
Tekshirib ko'ring: Nega
geometryuchun "0.0477 gradus" ni oddiy 111 000 ga ko'paytirib metr qilib bo'lmaydi?
Javob
Uzunlik gradusining metrdagi qiymati kenglikka bog'liq: ekvatorda ~111 km, Toshkentda (41°) ~84 km, qutbda 0. geometry masofasi esa ikki o'qni aralashtirib, tekislikdagi Pifagor bilan hisoblaydi — natija gradusning "aralash" birligi. Qaysi koeffitsientga ko'paytirish — yo'nalishga bog'liq. geography buni sferoid ustida to'g'ri hisoblaydi.
3. PostGIS'siz masofa: Haversine
3.1 Formulani SQL'da
PostGIS nima qilayotganini tushunish uchun masofani o'zimiz hisoblaymiz. Haversine formulasi — shar ustidagi ikki nuqta orasidagi eng qisqa yo'l. Uni oddiy Postgres'da, PostGIS'siz ishga tushirdik (PostgreSQL 17.6):
CREATE TABLE m10_joylar (id int PRIMARY KEY, nom text,
uzunlik float8, kenglik float8);
INSERT INTO m10_joylar VALUES
(1, 'Chorsu bozori', 69.2346, 41.3266),
(2, 'Amir Temur xiyoboni', 69.2797, 41.3111),
(3, 'Toshkent temir yo''l vokzali', 69.2880, 41.2920),
(4, 'Samarqand Registon', 66.9750, 39.6547);
SELECT b.nom, round(2 * 6371000 * asin(sqrt(
sin(radians(b.kenglik - a.kenglik) / 2) ^ 2
+ cos(radians(a.kenglik)) * cos(radians(b.kenglik))
* sin(radians(b.uzunlik - a.uzunlik) / 2) ^ 2))) AS metr
FROM m10_joylar a, m10_joylar b
WHERE a.id = 1 AND b.id > 1 ORDER BY b.id;
DROP TABLE m10_joylar;CREATE TABLE
INSERT 0 4
nom | metr
-----------------------------+--------
Amir Temur xiyoboni | 4142
Toshkent temir yo'l vokzali | 5890
Samarqand Registon | 266578
(3 rows)
DROP TABLEPostGIS'ning geography natijasi bilan solishtiring: 4 142 va 4 150 m, 266 578 va 266 750 m — farq 0.2 % atrofida. Haversine Yerni ideal shar (radius 6 371 km) deb oladi, PostGIS esa sferoid (qutblarda biroz yassilangan WGS 84 modeli). Taksi narxi uchun ikkalasi ham yetadi; geodeziya uchun — sferoid.
3.2 Xuddi shu — JavaScript'da
Ko'pincha masofa brauzer yoki serverda ham kerak (masalan, "kuryer 300 metr qoldi" xabari). Formula bir xil — bu blokni saytda «Ishga tushir» bilan sinab ko'ring:
const R = 6371000; // Yer radiusi, metr (o'rtacha)
const rad = (gradus) => (gradus * Math.PI) / 180;
function masofaMetr([uz1, ken1], [uz2, ken2]) {
const dKen = rad(ken2 - ken1);
const dUz = rad(uz2 - uz1);
const a = Math.sin(dKen / 2) ** 2
+ Math.cos(rad(ken1)) * Math.cos(rad(ken2))
* Math.sin(dUz / 2) ** 2;
return Math.round(2 * R * Math.asin(Math.sqrt(a)));
}
const chorsu = [69.2346, 41.3266]; // [uzunlik, kenglik]
console.log(masofaMetr(chorsu, [69.2797, 41.3111])); // 4142
console.log(masofaMetr(chorsu, [66.9750, 39.6547])); // 266578
console.log(masofaMetr(chorsu, [41.3266, 69.2346])); // 3501472Uchinchi qatorga qarang: o'sha Chorsu, faqat sonlar almashgan — va 3 501 km. Bu tuzoqqa PostGIS bo'limida qaytamiz.
Haversine bitta masofani yaxshi hisoblaydi. Lekin "200 000 nuqtadan 1 km ichidagilarni top" uchun har qatorda formula — to'liq o'qish. Indeks kerak — PostGIS shu uchun.
4. Asosiy so'rovlar
4.1 Radius ichida: ST_DWithin
"Amir Temur xiyobonidan 3 km ichidagi joylar":
SELECT nom FROM joylar
WHERE ST_DWithin(joy,
'SRID=4326;POINT(69.2797 41.3111)'::geography, 3000)
ORDER BY id;Kanon chiqishi:
nom
-----------------------------
Amir Temur xiyoboni
Toshkent temir yo'l vokzali
(2 rows)Chorsu (4.15 km) — tashqarida, vokzal — ichida. ST_DWithin(a, b, metr) — "orasidagi masofa ≤ metr". Uni ST_Distance(a, b) <= 3000 bilan almashtirish mumkindek tuyuladi — natija bir xil. Lekin tezlik emas (pastda o'lchov).
4.2 Eng yaqinlar: KNN <->
"Menga eng yaqin 2 ta joy" — KNN (k-nearest neighbors, k ta eng yaqin qo'shni). <-> operatori — masofa; ORDER BY … <-> … LIMIT k ni GiST indeks to'g'ridan-to'g'ri bajaradi:
SELECT nom,
round(joy <-> 'SRID=4326;POINT(69.2400 41.3200)'::geography)
AS metr
FROM joylar
ORDER BY joy <-> 'SRID=4326;POINT(69.2400 41.3200)'::geography
LIMIT 2;Kanon chiqishi:
nom | metr
---------------------+------
Chorsu bozori | 861
Amir Temur xiyoboni | 3460
(2 rows)Foydalanuvchi Chorsu yaqinida (861 m). Indeks butun jadvalni saralamaydi — "eng yaqin" bloklardan boshlab, kerakli 2 tasini topgach to'xtaydi.
Maslahat:
geographyuchun<->masofani shar modelida beradi (PostGIS hujjati),ST_Distanceesa sferoidda. Farq kichik (~0.2 %), lekin aniq metr kerak bo'lsa — javobdagi masofaniST_Distancebilan hisoblang,<->ni esa faqat saralash uchun qoldiring.
Amalda ikkalasi birga: "5 km ichidagi eng yaqin 10 ta filial" — WHERE ST_DWithin(…, 5000) ORDER BY joy <-> … LIMIT 10. ST_DWithin siz KNN yuzlab km uzoqdagi filialni ham "eng yaqin" deb qaytarishi mumkin (agar yaqinida boshqasi yo'q bo'lsa).
4.3 Poligon ichida nuqta
Yetkazib berish zonasi — poligon (ko'pburchak): "bu manzil zonamizdami?" Kanon taxminiy "Toshkent markazi" to'rtburchagini chizgan (oxirgi nuqta birinchisi bilan bir xil — poligon yopiq):
CREATE TABLE hudud (nom text, chegara geography(Polygon, 4326));
INSERT INTO hudud VALUES ('Markaz (taxminiy)',
'SRID=4326;POLYGON((69.22 41.28, 69.30 41.28, 69.30 41.34, '
|| '69.22 41.34, 69.22 41.28))');
SELECT j.nom, ST_Covers(h.chegara, j.joy) AS ichida
FROM joylar j CROSS JOIN hudud h ORDER BY j.id;
SELECT round((ST_Area(chegara) / 1e6)::numeric, 1) AS km2
FROM hudud;Kanon chiqishi (kanon poligonni bitta satrda yozgan; biz telefon uchun || bilan bo'ldik):
nom | ichida
-----------------------------+--------
Chorsu bozori | t
Amir Temur xiyoboni | t
Toshkent temir yo'l vokzali | t
Samarqand Registon | f
(4 rows)
km2
------
44.6
(1 row)ST_Covers(zona, nuqta) — "zona nuqtani qoplaydimi" (chegaradagi nuqta ham kiradi). ST_Area — maydon kvadrat metrda; / 1e6 — km². Real ilovada zonalarni xaritada chizib, GeoJSON sifatida yuklashadi.
5. Tuzoq: kenglik va uzunlik almashsa
Kanon ataylab xato qildi — Chorsu koordinatalarini Google Maps tartibida (avval kenglik) yozdi:
SELECT ST_AsText('SRID=4326;POINT(41.3266 69.2346)'::geography)
AS teskari,
round(ST_Distance(
'SRID=4326;POINT(41.3266 69.2346)'::geography, joy) / 1000)
AS km_xato
FROM joylar WHERE id = 1;Kanon chiqishi:
teskari | km_xato
------------------------+---------
POINT(41.3266 69.2346) | 3507
(1 row)Xato yo'q, ogohlantirish yo'q. Nuqta shunchaki Arktikada (kenglik 69° — Shimoliy muz okeani yaqini) va asl Chorsu'dan 3 507 km uzoqda. Bizning JS hisobimiz ham shuni ko'rsatdi (3 501 km — shar va sferoid farqi). Baza buni ushlay olmaydi: 69 — haqiqiy kenglik. Himoya ilovada:
- Ma'lumot qayerdan kelsa ham, kirishda nomlangan maydonlar:
{ kenglik, uzunlik }([a, b]massiv emas). - Hudud tekshiruvi: O'zbekiston uchun kenglik ~37–46, uzunlik ~56–74. Tashqarida bo'lsa — xato.
- Testda bitta ma'lum masofa (Chorsu → Amir Temur ≈ 4.1 km) — xato tartib darhol ko'rinadi.
Frontend xaritasi uchun natija odatda GeoJSON formatida beriladi. GeoJSON — xarita ma'lumotlari uchun kelishilgan JSON shakli (RFC 7946 standarti). Unda ham tartib [uzunlik, kenglik]:
SELECT ST_AsGeoJSON(joy) FROM joylar WHERE id = 1; st_asgeojson
--------------------------------------------------
{"type":"Point","coordinates":[69.2346,41.3266]}
(1 row)Leaflet (brauzerda xarita chizadigan JS kutubxonasi; kursda alohida o'tmaymiz, hozir bilish shart emas) esa L.marker([kenglik, uzunlik]) qabul qiladi — teskari! Bir loyihada ikki tartib uchraydi; har chegarada (baza ↔ API ↔ xarita) aniq bo'ling.
Tekshirib ko'ring: Telefon GPS'i
latitude: 41.31, longitude: 69.28qaytardi. PostGIS uchun EWKT satrini yozing.
Javob
'SRID=4326;POINT(69.28 41.31)' — avval uzunlik (longitude), keyin kenglik (latitude). Yoki ST_SetSRID(ST_MakePoint(69.28, 41.31), 4326)::geography. Nomlangan maydonlardan qurish xatoni kamaytiradi: ST_MakePoint(longitude, latitude).
6. Indeks: GiST va o'lchov
6.1 Nega ST_DWithin tez, ST_Distance < sekin
Kanon 200 000 tasodifiy nuqta (Toshkent atrofi) yaratib, 1 km radiusni o'lchagan. Indekssiz ikkala so'rov ham Parallel Seq Scan — 200 000 qatorning har birida formula (ST_DWithin 315 ms, ST_Distance < 1000 278 ms). Keyin GiST indeks:
CREATE INDEX nuqtalar_joy_idx ON nuqtalar USING gist (joy);
EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY ON)
SELECT count(*) FROM nuqtalar
WHERE ST_DWithin(joy,
'SRID=4326;POINT(69.2797 41.3111)'::geography, 1000);Kanon chiqishi (indeks 970 ms da qurildi, hajmi 14 MB — jadval bilan bir xil; qisqartirilgan — Buffers qatorlarisiz, uzun qatorlar psql'dagidek). Uzun hex satrlarga qaramang — bu shunchaki nuqtaning ichki yozuvi. Uchta narsaga qarang: Index Cond, Filter va actual rows sonlari.
Aggregate (actual rows=1.00 loops=1)
-> Bitmap Heap Scan on nuqtalar (actual rows=718.00 loops=1)
Filter: st_dwithin(joy, '0101000020E61000003D2CD49AE65151408FE4F21FD2A74440'::geography, '1000'::double precision, true)
Rows Removed by Filter: 475
-> Bitmap Index Scan on nuqtalar_joy_idx (actual rows=1193.00 loops=1)
Index Cond: (joy && _st_expand('0101000020E61000003D2CD49AE65151408FE4F21FD2A74440'::geography, '1000'::double precision))
Execution Time: 2.348 msNima bo'ldi? So'rov ikki bosqichda bajarildi:
Index Cond: (joy && _st_expand(…))—ST_DWithinichida avval "nuqta atrofida 1 km lik quti" yasaladi va indeksdan shu qutiga tushgan nuqtalar olinadi (1 193 ta).&&— "qutilar kesishadimi" — GiST buni juda tez qiladi.Filter: st_dwithin(…)— faqat shu 1 193 tasi uchun aniq masofa. Burchakdagilar (475 ta) tashlandi, 718 tasi — natija.
Diagrammada xuddi shu yo'l — 200 000 nuqtadan 718 tagacha:
flowchart TD
A["200 000 nuqta"] --> B["GiST: 1 km lik quti<br/>joy && _st_expand"]
B -->|"1 193 nomzod"| C["Aniq masofa<br/>st_dwithin"]
C -->|"475 burchakda"| D["Tashlandi"]
C -->|"718 ta"| E["Natija, 2.3 ms"]Doira qutiga sig'adi, lekin qutining burchaklari doiradan tashqarida — shuning uchun ikkinchi, aniq bosqich kerak.
ST_Distance(…) < 1000 esa — "funksiya natijasi sondan kichik" — planner unda quti yasashni bilmaydi va indeksni ishlata olmaydi. Kanon bu so'rovni faqat indekssiz o'lchagan (278 ms); indeks bo'lganda ham uni ishlatmasligi — PostGIS hujjatiga ko'ra. Shuning uchun qoida: radius — doim ST_DWithin.
Eng yaqin 5 ta (KNN) — Index Scan … Order By: (joy <-> …), 0.19 ms: indeks nuqtalarni masofa tartibida beradi, 5 tadan keyin to'xtaydi.
- ST_DWithin, indekssiz315 ms
- ST_Distance < 1000indekssiz; indeksni ishlata olmaydi (hujjat)278 ms
- ST_DWithin + GiST2,3 ms
- KNN top-5 + GiST0,19 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-24: PostgreSQL 18.6, PostGIS 3.6.4 (postgis/postgis:18-3.6), 200 000 tasodifiy nuqta
GiST — Indeks turlari darsida range va EXCLUDE uchun ko'rgan "ichida/kesishadi" indeksi. Geo uchun u nuqtalarni ichma-ich to'rtburchak qutilarga guruhlaydi (R-tree g'oyasi).
7. Halollik va chegaralar
- Sinalmagan: Linux'dagi PostGIS paketlari, managed bazalardagi PostGIS (ko'p provayderlarda bor, versiyasi turlicha — Managed va serverless Postgres), geocoding (manzil → koordinata) xizmatlari.
geographyfunksiyalarigeometrydagidan kam. Murakkab tahlil (buferlar, kesishma, marshrut) uchungeometry+ mahalliy proyeksiya (masalan, UTM zonasi) ishlatiladi — bu GIS mutaxassisligi, kursimiz doirasidan tashqarida.- Real yo'l masofasi (ko'chalar bo'ylab) — boshqa vazifa: marshrutlash xizmatlari (OSRM va boshqalar). PostGIS "qush uchib o'tadigan" masofani beradi.
- Geolokatsiya mavzusi (IP bo'yicha joy, kuryer kuzatuvi, Redis GEO, Mongo geo) — kursda alohida dars bor (Geolokatsiya); hozir bilish shart emas.
8. Ko'p uchraydigan xatolar
8.1 POINT(kenglik uzunlik)
Nuqta boshqa qit'ada, xato xabari yo'q. POINT(uzunlik kenglik), nomlangan maydonlar, hudud tekshiruvi.
8.2 geometry 4326 da masofa
Natija gradusda — ma'nosiz. Metr kerak bo'lsa — geography (yoki ::geography ga o'tkazish).
8.3 Radius — ST_Distance < N
Indeks ishlamaydi. ST_DWithin.
8.4 GiST indeksni unutish
Har so'rov — to'liq o'qish (kanon: 315 ms → 2.3 ms). CREATE INDEX … USING gist (joy).
8.5 KNN radiusiz
"Eng yaqin" yuzlab km uzoqda bo'lishi mumkin. ST_DWithin bilan chegaralang.
9. Mashqlar
PostGIS sizning kompyuteringizda bo'lmasligi mumkin — shuning uchun 1- va 2-mashq PostGIS'siz, 3-mashq — PostGIS so'rovini yozish (sinash — PostGIS bor bazada).
1-mashq (oson): Eng yaqin topshirish punkti (JS)
masofaMetr funksiyasidan foydalanib, foydalanuvchi [69.2400, 41.3200] nuqtasidan uchta punktgacha masofani hisoblang va eng yaqinini chiqaring: Chorsu [69.2346, 41.3266], Amir Temur [69.2797, 41.3111], Vokzal [69.2880, 41.2920]. Ishora: map va toSorted (Metod zanjiri va ma'lumot pipeline).
Yechim
const R = 6371000;
const rad = (gradus) => (gradus * Math.PI) / 180;
function masofaMetr([uz1, ken1], [uz2, ken2]) {
const a = Math.sin(rad(ken2 - ken1) / 2) ** 2
+ Math.cos(rad(ken1)) * Math.cos(rad(ken2))
* Math.sin(rad(uz2 - uz1) / 2) ** 2;
return Math.round(2 * R * Math.asin(Math.sqrt(a)));
}
const men = [69.2400, 41.3200];
const punktlar = [
{ nom: "Chorsu", joy: [69.2346, 41.3266] },
{ nom: "Amir Temur", joy: [69.2797, 41.3111] },
{ nom: "Vokzal", joy: [69.2880, 41.2920] },
];
const saralangan = punktlar
.map((p) => ({ nom: p.nom, metr: masofaMetr(men, p.joy) }))
.toSorted((a, b) => a.metr - b.metr);
for (const p of saralangan) console.log(`${p.nom}: ${p.metr} m`);
console.log("Eng yaqini:", saralangan[0].nom);Konsolda:
Chorsu: 861 m
Amir Temur: 3460 m
Vokzal: 5076 m
Eng yaqini: ChorsuChorsu — 861 m, Amir Temur — 3 460 m: kanondagi PostGIS KNN natijasi bilan aynan bir xil. Nega ST_Distance (4 150) bilan Haversine (4 142) farq qilgan-u, bu yerda farq yo'q? PostGIS hujjatiga ko'ra geography uchun <-> masofani shar ustida hisoblaydi (sferoid emas) — ya'ni bizning Haversine bilan bir xil model. Bu usul o'nlab punkt uchun yetadi; minglab bo'lsa — bazada GiST bilan.
2-mashq (o'rta): Radius ichidagilar (SQL, PostGIS'siz)
m10_filial (nom text, uzunlik float8, kenglik float8) jadvaliga uchta joyni qo'shing (1-mashqdagi punktlar) va [69.2400, 41.3200] dan 4 km ichidagilarni masofasi bilan, yaqinidan uzog'iga chiqaring. Haversine'ni m10_masofa(uz1, ken1, uz2, ken2) funksiyasiga o'rang (LANGUAGE sql IMMUTABLE, Funksiyalar va PL/pgSQL).
Yechim
CREATE FUNCTION m10_masofa(uz1 float8, ken1 float8,
uz2 float8, ken2 float8)
RETURNS float8 LANGUAGE sql IMMUTABLE
RETURN 2 * 6371000 * asin(sqrt(
sin(radians(ken2 - ken1) / 2) ^ 2
+ cos(radians(ken1)) * cos(radians(ken2))
* sin(radians(uz2 - uz1) / 2) ^ 2));
CREATE TABLE m10_filial (nom text, uzunlik float8, kenglik float8);
INSERT INTO m10_filial VALUES ('Chorsu', 69.2346, 41.3266),
('Amir Temur', 69.2797, 41.3111), ('Vokzal', 69.2880, 41.2920);
SELECT nom || ': ' || round(m10_masofa(69.2400, 41.3200,
uzunlik, kenglik)) || ' m'
FROM m10_filial
WHERE m10_masofa(69.2400, 41.3200, uzunlik, kenglik) <= 4000
ORDER BY m10_masofa(69.2400, 41.3200, uzunlik, kenglik);
DROP TABLE m10_filial;
DROP FUNCTION m10_masofa;Biz ishga tushirdik (psql -X -q -A -t, PostgreSQL 17.6):
Chorsu: 861 m
Amir Temur: 3460 mVokzal (5.1 km) tushib qoldi. Bu so'rov ishlaydi, lekin har qatorda formula — 200 000 filialda kanondagi indekssiz holat kabi sekin. Real ishda PostGIS + GiST yoki hech bo'lmasa avval "quti" bo'yicha filtr: kenglik BETWEEN 41.28 AND 41.36 kabi oddiy B-tree indeksli shart, keyin aniq formula.
3-mashq (qiyin): Yetkazib berish so'rovi (PostGIS)
"Bahor" oshxonasi filiallar (id, nom, joy geography(Point, 4326)) va zonalar (filial_id, chegara geography(Polygon, 4326)) jadvallarini ishlatadi. Mijoz [69.2400, 41.3200] da. Bitta so'rov yozing: mijozni zonasi qoplaydigan filiallar ichidan, 5 km dan yaqinlarini, eng yaqinidan boshlab, ko'pi bilan 3 tasini, masofasi (metr, butun) bilan qaytarsin. Qaysi indekslar kerak?
Yechim
WITH mijoz AS (
SELECT 'SRID=4326;POINT(69.2400 41.3200)'::geography AS nuqta)
SELECT f.nom, round(f.joy <-> m.nuqta) AS metr
FROM filiallar f
JOIN zonalar z ON z.filial_id = f.id
CROSS JOIN mijoz m
WHERE ST_Covers(z.chegara, m.nuqta)
AND ST_DWithin(f.joy, m.nuqta, 5000)
ORDER BY f.joy <-> m.nuqta
LIMIT 3;Bu so'rov bizning muhitda sinalmagan (PostGIS yo'q); naqsh kanondagi ST_Covers, ST_DWithin va <-> so'rovlaridan yig'ilgan. Indekslar: CREATE INDEX ON filiallar USING gist (joy); (radius va KNN), CREATE INDEX ON zonalar USING gist (chegara); (qoplash tekshiruvi), zonalar (filial_id) — JOIN uchun. Nuqtani WITH ga chiqarish — bir xil ifodani uch marta yozmaslik uchun (CTE (WITH)).
10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
vazifalar sxemasiga PostGIS qo'shilmagan. Sabablar:
- Loyihada joylashuvga bog'liq imkoniyat yo'q ("vazifa joyi" g'oyasi kanonning misoli edi, API'da bunday maydon yo'q).
- PostGIS boshqa Docker image talab qiladi — loyihaning
postgres:18image'idan voz kechish faqat kerakli imkoniyat uchun arziydi. - Kanon tajribasi alohida vaqtinchalik konteynerda o'tkazildi va ish tugagach o'chirildi — asosiy bazaga tegilmadi.
Agar o'z loyihangizda "vazifani joyga bog'lash" (masalan, "Chorsu'ga yetganda eslat") kerak bo'lsa — bu darsdagi joylar jadvali va GiST indeksi tayyor andoza.
11. Real ishda
- Taksi va yetkazib berish xizmatlari — "eng yaqin haydovchi/kuryer" va "manzil zonadami" — har soniyada minglab KNN va poligon so'rovlari.
- E'lonlar va ko'chmas mulk saytlari — "xaritada qidirish", "metro yaqinida".
- Kuryer kuzatuvi — tez o'zgaradigan koordinatalar ko'pincha Redis GEO'da (Redis'ni shu qismda o'rganamiz — Redis nima va ulanish), tarix va tahlil — PostGIS'da.
- Intervyu: "Eng yaqin 10 ta nuqtani qanday tez topasiz?", "geometry va geography farqi?".
Xulosa
- PostGIS — alohida kengaytma (alohida image/paket); kanon PostGIS 3.6.4 da sinagan.
geography(Point, 4326)— masofa metrda;geometry4326 da — gradusda.POINT(uzunlik kenglik)— avval uzunlik; almashsa — xatosiz 3 507 km xato.- Radius —
ST_DWithin(indeks quti bilan),ST_Distance < Nemas; eng yaqinlar —ORDER BY <-> LIMIT k. - GiST indeks: 200 000 nuqtada 315 ms → 2.3 ms, KNN 0.19 ms.
- PostGIS'siz — Haversine (shar modeli, ~0.2 % farq), lekin indekssiz.
geographyuchun<->ham shar modelida hisoblaydi.
Keyingi dars: Rollar va huquqlar — ilova superuser bilan ulanmasligi uchun migratsiya, ilova va hisobot rollarini quramiz.
Manbalar
- PostGIS hujjati: PostGIS 3.6 Manual —
geography,ST_DWithin,ST_Distance,ST_Covers,<->— 2026-10-25 da ko'rildi. - PostGIS hujjati:
<->operatori — "geography KNN is based on sphere rather than spheroid" — 2026-10-27 da ko'rildi. - PostGIS hujjati: Using PostGIS — spatial indexes.
- EPSG:4326 (WGS 84) — epsg.io/4326.
- GeoJSON standarti: RFC 7946 (koordinatalar tartibi —
[longitude, latitude]).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!