🏠 خانه برنامه‌نویسی PostGIS پیشرفته: از مبانی تا تحلیل جامع ...

PostGIS پیشرفته: از مبانی تا تحلیل جامع دسترسی شهری

یک آموزش گام‌به‌گام و پیشرفته برای تسلط بر PostGIS؛ از مفاهیم پایه مانند SRID، ایندکس‌های مکانی و پرس‌وجوهای فضایی تا تحلیل برداری و رستری. کل فرایند با یک سناریوی واقعی تحلیل دسترسی به فضاهای سبز شهری توضیح داده می‌شود تا مخاطب از سطح مبتدی تا حرفه‌ای پیش برود.

⌘ برنامه‌نویسی پیشرفته ⏱ 240 دقیقه 👁 20 بازدید 📅 2026/08/22
📄 محتوای آموزش

در این درس یک مسیر کامل یادگیری PostGIS را طی می‌کنیم: از نصب و مفاهیم پایه تا تحلیل‌های پیشرفته برداری و رستری. همه‌چیز را با یک سناریوی واقعی «تحلیل دسترسی به پارک‌های شهری» پیش می‌بریم تا در پایان بتوانید پرس‌وجوهای فضایی دقیق، بهینه و قابل اتکا بنویسید.

سناریو و هدف تحلیل

سناریوی واقعی ما «اندازه‌گیری دسترسی شهروندان به فضاهای سبز شهری» است. داده‌های ورودی شامل:

  • پارک‌ها و فضاهای سبز (Polygon)
  • ساختمان‌ها یا مراکز جمعیتی (Point)
  • محدوده شهر (Polygon)
  • در صورت دسترس: رستر جمعیت (Grid با مقادیر عددی)

هدف‌ها:

  • محاسبه نسبت مساحت تحت پوشش پارک‌ها با یک شعاع دسترسی مشخص (مثلاً 500 متر)
  • شمارش ساختمان‌ها/جمعیت تحت پوشش
  • شناسایی نواحی کم‌برخوردار و خوشه‌بندی آن‌ها برای برنامه‌ریزی
نکته: برای تحلیل دقیق فاصله و مساحت، از یک سیستم مختصات تصویری (Projected CRS) مناسب منطقه استفاده کنید؛ مثلاً برای تهران معمولاً UTM Zone 39N با SRID=32639 مناسب است. برای هر شهر، SRID مناسب را بر اساس طول/عرض جغرافیایی همان منطقه انتخاب کنید.

نصب و راه‌اندازی PostGIS

فعال‌سازی افزونه

-- اتصال به پایگاه‌داده هدف
\c citydb

-- فعال‌سازی PostGIS
CREATE EXTENSION IF NOT EXISTS postgis;

-- بررسی نسخه و اجزای فعال
SELECT postgis_full_version();

پیکربندی مقدماتی

  • تنظیمات حافظه و Parallelism را بر اساس منابع سرور و حجم داده‌ها تنظیم کنید.
  • برای بارگذاری‌های سنگین، work_mem و maintenance_work_mem را موقتاً افزایش دهید.
  • برای پایگاه‌داده‌های بزرگ، استفاده از Partitioning (تعریف‌شده در PostgreSQL) به‌همراه ایندکس‌های GIST توصیه می‌شود.
هشدار: فعال‌سازی افزونه‌ها در محیط‌های حساس باید با برنامه‌ریزی انجام شود. پیش از اعمال تغییرات، از پایگاه‌داده نسخه پشتیبان تهیه کنید.

مفاهیم پایه: Geometry، Geography، SRID و CRS

Geometry vs Geography

  • geometry: مختصات روی یک صفحه تصویری. مناسب برای مساحت و فاصله در CRSهای تصویری؛ سریع‌تر و انعطاف‌پذیرتر.
  • geography: مختصات روی کره زمین. برای فاصله‌های دقیق ژئودتیک روی WGS84 (SRID=4326). ساده‌تر اما نسبت به geometry کندتر و محدودتر.

SRID و تبدیل مختصات

  • SRID نوع سیستم مختصات را مشخص می‌کند؛ مثال: 4326 (WGS84)، 32639 (UTM Zone 39N).
  • تبدیل با ST_Transform انجام می‌شود؛ نوع هندسه و SRID باید سازگار باشد.
-- تبدیل نقطه از WGS84 به UTM 39N
SELECT ST_Transform(ST_SetSRID(ST_Point(51.4, 35.7), 4326), 32639);

نمایش‌های استاندارد هندسه

  • WKT/WKB: قالب‌های متنی/باینری استاندارد برای انتقال هندسه.
  • GeoJSON: خروجی مناسب برای اپلیکیشن‌های وب.

واردسازی داده و طراحی شِما

تعریف جدول‌ها با نوع هندسه

-- شِمای منطقی
CREATE SCHEMA IF NOT EXISTS urban;

-- پارک‌ها (Polygon، WGS84)
CREATE TABLE urban.parks (
  id BIGSERIAL PRIMARY KEY,
  name TEXT,
  geom geometry(Polygon, 4326) NOT NULL
);

-- ساختمان‌ها (Point، WGS84)
CREATE TABLE urban.buildings (
  id BIGSERIAL PRIMARY KEY,
  btype TEXT,
  geom geometry(Point, 4326) NOT NULL
);

-- محدوده شهر
CREATE TABLE urban.city_boundary (
  id BIGSERIAL PRIMARY KEY,
  name TEXT,
  geom geometry(Polygon, 4326) NOT NULL
);

واردسازی با ابزارها

  • shp2pgsql: برای Shapefile
  • ogr2ogr: برای فرمت‌های متعدد مانند GeoJSON، GeoPackage
# نمونه واردسازی GeoJSON به PostgreSQL با ogr2ogr
ogr2ogr -f PostgreSQL "PG:host=localhost dbname=citydb user=postgres password=***" \
  parks.geojson -nln urban.parks -nlt PROMOTE_TO_MULTI -lco GEOMETRY_NAME=geom -lco SPATIAL_INDEX=NONE

# اصلاح هندسه‌های ناسالم پس از واردسازی
psql -d citydb -c "UPDATE urban.parks SET geom = ST_MakeValid(geom);"
نکته: استفاده از typmod geometry(Polygon, 4326) هم نوع هندسه و هم SRID را enforce می‌کند. در صورت نیاز به تنوع هندسه‌ها، از MultiPolygon یا بدون typmod استفاده کنید.

ایندکس‌ها و بهینه‌سازی پرس‌وجو

ایندکس GIST و اعمال آن

-- ایجاد ایندکس‌های مکانی
CREATE INDEX parks_gix ON urban.parks USING GIST (geom);
CREATE INDEX buildings_gix ON urban.buildings USING GIST (geom);
CREATE INDEX city_boundary_gix ON urban.city_boundary USING GIST (geom);

-- بهبود آمار برای برنامه‌ریز پرس‌وجو
VACUUM ANALYZE urban.parks;
VACUUM ANALYZE urban.buildings;
VACUUM ANALYZE urban.city_boundary;

الگوهای کارای پرس‌وجو

  • اولویت با فیلترهای bounding box: عملگر && پیش از توابع سنگین
  • Subdivide برای هندسه‌های بسیار بزرگ: ST_Subdivide
  • اجتناب از cast غیرضروری بین geography و geometry
-- فیلتر سریع با bounding box و سپس تست دقیق
SELECT p.id, b.id
FROM urban.parks p
JOIN urban.buildings b
  ON p.geom && b.geom
WHERE ST_DWithin(ST_Transform(p.geom, 32639), ST_Transform(b.geom, 32639), 500);

پرس‌وجوهای فضایی ضروری

نام تابعکاربرد
ST_Intersectsبررسی تقاطع دو هندسه
ST_Withinقرار داشتن هندسه A درون هندسه B
ST_Containsدربر گرفتن کامل هندسه B توسط A
ST_DWithinفاصله کمتر یا مساوی آستانه
ST_Bufferایجاد بافر حول هندسه
ST_Unionادغام هندسه‌ها (حذف مرزهای داخلی)
ST_Collectتجمیع هندسه‌ها بدون حل مرزها
ST_Areaمساحت (در CRS تصویری)
ST_Lengthطول خطوط
ST_Centroidمرکز هندسه
ST_MakeValidاصلاح هندسه‌های ناسالم
ST_SimplifyPreserveTopologyساده‌سازی با حفظ توپولوژی
ST_Transformتبدیل سیستم مختصات
ST_AsGeoJSONخروجی GeoJSON برای اپلیکیشن‌ها

مثال: یافتن ساختمان‌هایی که درون پارک‌ها قرار دارند (غیرمعمول ولی آموزشی):

SELECT b.id, p.name
FROM urban.buildings b
JOIN urban.parks p
  ON ST_Within(b.geom, p.geom);

اندازه‌گیری و تحلیل‌های برداری

مساحت و پوشش

-- تبدیل به CRS تصویری قبل از محاسبه مساحت/فاصله
WITH boundary AS (
  SELECT ST_Transform(geom, 32639) AS geom FROM urban.city_boundary WHERE name = 'Tehran'
), parks_utm AS (
  SELECT ST_Transform(ST_MakeValid(geom), 32639) AS geom FROM urban.parks
), parks_union AS (
  SELECT ST_Union(geom) AS geom FROM parks_utm
), buffer500 AS (
  SELECT ST_Union(ST_Buffer(geom, 500)) AS geom FROM parks_union
)
SELECT 
  ST_Area(ST_Intersection(b.geom, buffer500.geom)) / ST_Area(b.geom) AS coverage_ratio
FROM boundary b, buffer500;

تجمیع و ساده‌سازی

-- ساده‌سازی مرزهای بافر برای نمایش سریع‌تر
UPDATE urban.parks
SET geom = ST_SimplifyPreserveTopology(geom, 0.0001)
WHERE name IS NOT NULL;

خوشه‌بندی نقاط کم‌برخوردار

-- شناسایی ساختمان‌های خارج از محدوده بافر 500 متر
WITH parks_utm AS (
  SELECT ST_Transform(geom, 32639) AS geom FROM urban.parks
), bufs AS (
  SELECT ST_Union(ST_Buffer(geom, 500)) AS geom FROM parks_utm
), bld_utm AS (
  SELECT id, ST_Transform(geom, 32639) AS geom FROM urban.buildings
), underserved AS (
  SELECT b.* FROM bld_utm b
  WHERE NOT ST_Contains((SELECT geom FROM bufs), b.geom)
)
SELECT cluster_id, COUNT(*) AS n, ST_Centroid(ST_Collect(geom)) AS centroid
FROM (
  SELECT (ST_ClusterDBSCAN(geom, eps := 300, minpoints := 10)) OVER () AS cluster_id, geom
  FROM underserved
) c
GROUP BY cluster_id
ORDER BY n DESC;
نکته: ST_ClusterDBSCAN برای شناسایی خوشه‌های با چگالی بالا مناسب است. پارامترهای eps و minpoints را بر اساس مقیاس شهر و تراکم نقاط تنظیم کنید.

تحلیل رستری در PostGIS

PostGIS مجموعه‌ای از توابع رستری برای برش، آمارگیری و هم‌پوشانی با هندسه‌ها دارد. در سناریوی ما، می‌توان رستر جمعیت را با بافر پارک‌ها تقاطع داد تا جمعیت تحت پوشش برآورد شود.

-- فرض: جدول pop_raster با ستون rast (یک باند)، CRS همخوان با 32639
-- برآورد جمعیت تحت پوشش بافر 500 متر
WITH bufs AS (
  SELECT ST_Union(ST_Buffer(ST_Transform(geom, 32639), 500)) AS geom
  FROM urban.parks
), clips AS (
  SELECT ST_Clip(r.rast, 1, bufs.geom, true) AS rast
  FROM pop_raster r, bufs
  WHERE ST_Intersects(r.rast, bufs.geom)
)
SELECT SUM((ST_SummaryStats(rast, true)).sum) AS served_population
FROM clips;
هشدار: دقت مقادیر رستری به رزولوشن و واحد پیکسل‌ها وابسته است. برای تبدیل واحدها یا نرمال‌سازی، متادیتا و مقیاس هر باند را بررسی کنید.

مثال جامع گام‌به‌گام: دسترسی به پارک‌های شهری

گام 1: آماده‌سازی و پاک‌سازی

-- 1) اطمینان از اعتبار هندسه‌ها
UPDATE urban.parks      SET geom = ST_MakeValid(geom);
UPDATE urban.city_boundary SET geom = ST_MakeValid(geom);

-- 2) ایندکس‌های مکانی
CREATE INDEX IF NOT EXISTS parks_gix       ON urban.parks USING GIST (geom);
CREATE INDEX IF NOT EXISTS buildings_gix   ON urban.buildings USING GIST (geom);
CREATE INDEX IF NOT EXISTS boundary_gix    ON urban.city_boundary USING GIST (geom);

گام 2: تعریف ناحیه مطالعه

-- انتخاب محدوده هدف (مثلاً تهران)
WITH tehran AS (
  SELECT ST_Transform(geom, 32639) AS geom FROM urban.city_boundary WHERE name = 'Tehran'
)
SELECT ST_Area(geom) AS area_m2 FROM tehran;

گام 3: محاسبه بافرهای دسترسی

WITH parks_utm AS (
  SELECT ST_Transform(geom, 32639) AS geom FROM urban.parks
), bufs AS (
  SELECT ST_Union(ST_Buffer(geom, 500)) AS geom FROM parks_utm
), city AS (
  SELECT ST_Transform(geom, 32639) AS geom FROM urban.city_boundary WHERE name = 'Tehran'
)
SELECT ST_Area(ST_Intersection(city.geom, bufs.geom)) / ST_Area(city.geom) AS coverage_ratio
FROM city, bufs;

گام 4: شمارش ساختمان‌های تحت پوشش

WITH bld AS (
  SELECT id, ST_Transform(geom, 32639) AS geom FROM urban.buildings
), bufs AS (
  SELECT ST_Union(ST_Buffer(ST_Transform(geom, 32639), 500)) AS geom FROM urban.parks
)
SELECT COUNT(*) AS served_buildings
FROM bld
WHERE ST_Contains((SELECT geom FROM bufs), bld.geom);

گام 5: شناسایی شکاف‌ها و خوشه‌بندی

WITH bld AS (
  SELECT id, ST_Transform(geom, 32639) AS geom FROM urban.buildings
), bufs AS (
  SELECT ST_Union(ST_Buffer(ST_Transform(geom, 32639), 500)) AS geom FROM urban.parks
), underserved AS (
  SELECT b.* FROM bld b WHERE NOT ST_Contains((SELECT geom FROM bufs), b.geom)
)
SELECT cluster_id, COUNT(*) AS n
FROM (
  SELECT (ST_ClusterKMeans(geom, k := 10)) OVER () AS cluster_id, geom
  FROM underserved
) t
GROUP BY cluster_id
ORDER BY n DESC;

خروجی این گام‌ها سه شاخص کلیدی ارائه می‌دهد: نسبت پوشش مکانی، تعداد ساختمان‌های تحت پوشش و خوشه‌های کم‌برخوردار. این اطلاعات قابل استفاده برای اولویت‌بندی توسعه پارک‌های جدید یا بهبود دسترسی هستند.

خروجی‌گرفتن و یکپارچه‌سازی با اپلیکیشن‌ها

خروجی GeoJSON برای وب

-- تولید GeoJSON از بافرهای دسترسی
WITH bufs AS (
  SELECT ST_Union(ST_Buffer(ST_Transform(geom, 32639), 500)) AS geom
  FROM urban.parks
)
SELECT ST_AsGeoJSON(geom, 6) AS geojson
FROM bufs;

نمایش سریع با Envelope یا Simplify

SELECT ST_AsGeoJSON(ST_SimplifyPreserveTopology(geom, 5.0))
FROM some_large_layer;
نکته: برای تهیه Tileهای برداری، می‌توانید از ST_AsMVT در PostgreSQL (در صورت نیاز و فعال بودن افزونه‌های مرتبط) استفاده کنید. برای مقیاس‌های بزرگ، تقسیم لایه‌ها با grid و استفاده از tiling جلوی بار اضافی را می‌گیرد.

خطاهای رایج و رفع اشکال

چک‌لیست پایش صحت و کارایی

  • SRID ثابت و صحیح است؟ (SELECT Find_SRID(...))
  • هندسه‌ها معتبرند؟ (ST_IsValid)
  • ایندکس‌های GIST ساخته و ANALYZE اجرا شده؟
  • پرس‌وجوها ابتدا با && فیلتر می‌شوند؟
  • برای مساحت/فاصله از CRS تصویری استفاده شده؟
هشدار: محاسبه فاصله با geometry در WGS84 (4326) بدون تبدیل ممکن است نتایج نادرست بدهد. برای فاصله‌های دقیق، یا از geography استفاده کنید یا به CRS تصویری مناسب تبدیل کنید.
-- خطای SRID ناسازگار
SELECT ST_DWithin(a.geom, b.geom, 500)
FROM layer_a a JOIN layer_b b ON true;
-- راه‌حل: یکسان‌سازی SRID
SELECT ST_DWithin(ST_Transform(a.geom, 32639), ST_Transform(b.geom, 32639), 500)
FROM layer_a a JOIN layer_b b ON true;

-- رفع Invalid Geometry
UPDATE urban.parks SET geom = ST_MakeValid(geom)
WHERE NOT ST_IsValid(geom);

جمع‌بندی و مسیر ادامه

در این درس از مفاهیم پایه PostGIS تا تحلیل‌های برداری و رستری و یک مثال جامع شهری پیش رفتیم. اکنون می‌توانید داده‌های مکانی را وارد کنید، ایندکس بسازید، پرس‌وجوهای کارا بنویسید و خروجی مناسب برای اپلیکیشن‌ها تهیه کنید. برای پیشروی، تمرین با داده‌های واقعی شهر خود، آزمون پارامترهای خوشه‌بندی، و ترکیب با ابزارهای مسیر‌یابی (مانند کتابخانه‌های اختصاصی شبکه جاده‌ای) پیشنهاد می‌شود.

نکته پایانی: کیفیت نتایج GIS به سه عامل بستگی دارد—دقت داده‌ها، انتخاب CRS مناسب و طراحی پرس‌وجوی بهینه. هر سه را جدی بگیرید تا تحلیل‌های شما قابل اتکا باشند.