در این درس یک مسیر کامل یادگیری PostGIS را طی میکنیم: از نصب و مفاهیم پایه تا تحلیلهای پیشرفته برداری و رستری. همهچیز را با یک سناریوی واقعی «تحلیل دسترسی به پارکهای شهری» پیش میبریم تا در پایان بتوانید پرسوجوهای فضایی دقیق، بهینه و قابل اتکا بنویسید.
سناریو و هدف تحلیل
سناریوی واقعی ما «اندازهگیری دسترسی شهروندان به فضاهای سبز شهری» است. دادههای ورودی شامل:
- پارکها و فضاهای سبز (Polygon)
- ساختمانها یا مراکز جمعیتی (Point)
- محدوده شهر (Polygon)
- در صورت دسترس: رستر جمعیت (Grid با مقادیر عددی)
هدفها:
- محاسبه نسبت مساحت تحت پوشش پارکها با یک شعاع دسترسی مشخص (مثلاً 500 متر)
- شمارش ساختمانها/جمعیت تحت پوشش
- شناسایی نواحی کمبرخوردار و خوشهبندی آنها برای برنامهریزی
نصب و راهاندازی 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);"
ایندکسها و بهینهسازی پرسوجو
ایندکس 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;
تحلیل رستری در 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;
خطاهای رایج و رفع اشکال
چکلیست پایش صحت و کارایی
- SRID ثابت و صحیح است؟ (SELECT Find_SRID(...))
- هندسهها معتبرند؟ (ST_IsValid)
- ایندکسهای GIST ساخته و ANALYZE اجرا شده؟
- پرسوجوها ابتدا با && فیلتر میشوند؟
- برای مساحت/فاصله از 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 تا تحلیلهای برداری و رستری و یک مثال جامع شهری پیش رفتیم. اکنون میتوانید دادههای مکانی را وارد کنید، ایندکس بسازید، پرسوجوهای کارا بنویسید و خروجی مناسب برای اپلیکیشنها تهیه کنید. برای پیشروی، تمرین با دادههای واقعی شهر خود، آزمون پارامترهای خوشهبندی، و ترکیب با ابزارهای مسیریابی (مانند کتابخانههای اختصاصی شبکه جادهای) پیشنهاد میشود.