انتقل للمحتوى
PostgreSQLGISBackend

PostGIS ولا إحداثيات عادية: إمتى نحتاج الـ extension؟

17 أكتوبر 2026مدة القراءة: 6 دقايقعبدالرحمن سعيد

عمودين float كفاية، لحد ما تحتاجوا تدوّروا في جدول بالمسافة، أو تتحققوا إذا كانت النقط جوه polygons ولا لأ. هنا هنشوف إزاي نفرّق بين الحالتين، والـ queries اللي بتحافظ على استخدام الـ index بتاع PostGIS، والتفصيلة اللي محتاجة انتباه في Django مع الـ geography fields.

المقال ده اتكتب من جديد في أكتوبر 2026. نسخة أقدم منه عرضت benchmark على إنه اتقاس وقت بناء MURI، وعرضت queries مش بيشغّلها MURI. النسخة دي دليل عام، وبتقول بوضوح إيه اللي MURI بيستخدمه فعلًا.

وقت تصميم backend بيتعامل مع المواقع، فيه سؤال بيتكرر: نخزّن الـ latitude والـ longitude كرقمين، ولا نستخدم PostGIS؟ الإجابة بصراحة بتعتمد على الأسئلة اللي بتسألوها للبيانات، مش على عدد الصفوف اللي عندكم.

مقارنة نقطتين معروفتين: الإحداثيات العادية مفيهاش مشكلة

لو السؤال «السواق ده في حدود 100 متر من المحطة دي؟»، يبقى النقطتين موجودين أصلًا. حساب geodesic أو Haversine في كود التطبيق دقيق كفاية، وبياخد microseconds. في MURI، وهي منصة لنقل الطلاب اشتغلت عليها، ده الأسلوب اللي بتشتغل بيه عمليات التحقق عند بداية الرحلة ونهايتها وعند الـ pickup بالـ QR: المحطات متخزنة كـ PostGIS geography، بس التحقق من المسافة بيقارن موقع سواق واحد بنقطة واحدة معروفة في Python.

البحث في جدول بالمسافة: هنا PostGIS بيفيد

«أنهي سواقين متاحين في حدود 5 كيلومتر من نقطة الـ pickup دي؟» ده سؤال مختلف: بيعمل scan على صفوف كتير. مع عمودين float، الـ expression بتاع المسافة بيحط العمودين جوه functions، فالـ B-tree index العادي مش بيقدر يساعد، وكل صف مرشّح بيتقيّم. (ملحوظة كمان: PostgreSQL مش بيسمح بعمل filter على alias من الـ SELECT باستخدام HAVING بره aggregate — الـ filter لازم يكرر الـ expression في WHERE أو يستخدم subquery.)

sql
-- Plain columns: correct, but every available driver is evaluated
SELECT driver_id, distance_km
FROM (
  SELECT driver_id,
         6371 * acos(greatest(-1.0, least(1.0,
           cos(radians(:lat)) * cos(radians(lat)) * cos(radians(lng) - radians(:lng)) +
           sin(radians(:lat)) * sin(radians(lat))))) AS distance_km
  FROM drivers
  WHERE status = 'available'
) d
WHERE distance_km < 5
ORDER BY distance_km
LIMIT 20;

مع PostGIS، خزّنوا الموقع في عمود geography عليه GiST index، واسألوا السؤال مباشرةً بصيغة تخلّي قاعدة البيانات تقدر تستخدم الـ index. فيه تفصيلتين مهمين: ابنوا نقطة البحث بـ SRID صريح، واعملوا query على عمود الـ geography زي ما هو — استخدام cast أو function على العمود اللي عليه الـ index ممكن يخلّي الـ planner ميستخدمش الـ index.

sql
-- location is geography(Point, 4326) with a GiST index
CREATE INDEX drivers_location_gix ON drivers USING gist (location);

SELECT driver_id,
       ST_Distance(location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography) AS distance_m
FROM drivers
WHERE status = 'available'
  AND ST_DWithin(location, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography, 5000)
ORDER BY distance_m
LIMIT 20;

فرق السرعة بيعتمد على بياناتكم، فلازم تقيسوه: شغّلوا النسختين بـ EXPLAIN (ANALYZE, BUFFERS) على جدول ببيانات واقعية، ودوّروا على الـ index scan في الـ plan بتاع PostGIS.

النقط جوه الـ polygons

«أنهي منطقة خدمة فيها العنوان ده؟» دي حالة تانية PostGIS بيسهّل التعامل معاها. تنفيذ الـ point-in-polygon بنفسكم معناه ray casting في كود التطبيق؛ في PostGIS هو predicate واحد. على أعمدة الـ geography استخدموا ST_Covers؛ ST_Contains دي function للـ geometry.

sql
-- boundary is geography(Polygon, 4326)
SELECT id, name
FROM districts
WHERE ST_Covers(boundary, ST_SetSRID(ST_MakePoint(:lng, :lat), 4326)::geography);

في GeoDjango، الـ contains lookup على geography field ممكن يتحوّل داخليًا لـ geometry عشان العملية تتنفّذ. ده معناه إنكم مش بتستخدموا نفس دلالة الـ geography الأصلية، والـ cast ممكن يأثر على استخدام الـ index. لو المطلوب نقطة جوه حدود منطقة على سطح الأرض، استخدموا covers مباشرةً؛ و dwithin والـ distance lookups للمسافات.

python
from django.contrib.gis.geos import Point

point = Point(lng, lat, srid=4326)
district = District.objects.filter(boundary__covers=point).first()

إمتى مفيش داعي لـ PostGIS

  • بتخزّنوا موقع لكل record وبس، ومش بتدوّروا ولا بتعملوا filter بيه خالص.
  • كل تحقق من المسافة بيقارن نقطتين موجودين عندكم أصلًا.
  • قاعدة البيانات الـ managed اللي بتستخدموها مش بتوفّر الـ extension ده.
  • الإجابات التقريبية مقبولة، وتقدروا تعملوا filter بعد ما تجيبوا مجموعة صغيرة من الصفوف المرشّحة.

الخلاصة

استخدموا PostGIS لما تحتاجوا تدوّروا في الصفوف بالمسافة أو تربطوا نقط بمناطق؛ أما لو المطلوب مقارنة نقطة معروفة بنقطة تانية، فالحسابات العادية في التطبيق بتاعكم كفاية. وفي الحالتين، خزّنوا الإحداثيات بـ SRID صريح من أول يوم — وتغيير المرجع الجغرافي بعد تراكم البيانات ممكن يبقى مكلف.

كتبه

عبدالرحمن سعيد

قائد فريق برمجيات | مهندس برمجيات أول متخصص في الـ Backend