223 lines
14 KiB
Markdown
223 lines
14 KiB
Markdown
# سناریوی نهایی: API مسیریابی ماتریسی با pgRouting
|
||
|
||
این سند سناریوی کامل پیادهسازی **Matrix Routing API** بر اساس **pgRouting** و دادههای OSM در دیتابیس `gis` را شرح میدهد. **سرویس فقط برای نقاط واقع در محدوده ایران فعال است؛ در غیر این صورت خطا برگردانده میشود.**
|
||
|
||
---
|
||
|
||
## ۱. وضعیت فعلی (پیشفرض)
|
||
|
||
- **دیتابیس:** PostgreSQL با PostGIS، نام دیتابیس `gis`
|
||
- **جداول OSM:** `planet_osm_point`, `planet_osm_line`, `planet_osm_polygon` (اسکیمای استاندارد osm2pgsql)
|
||
- **اتصال اپ:** PDO با کاربر `search_user` به `gis`
|
||
- **مسیریابی:** وجود ندارد؛ فقط جستجو، وکتور تایل و امکانات مشابه
|
||
|
||
---
|
||
|
||
## ۲. معماری کلی
|
||
|
||
```
|
||
کلاینت
|
||
│
|
||
│ POST /api/matrix (origins[], destinations[])
|
||
▼
|
||
Symfony (MatrixController / MatrixService)
|
||
│
|
||
│ ۱) اعتبارسنجی و محدودیت N×M
|
||
│ ۲) Snap نقاط به گراف (یافتن نزدیکترین گره)
|
||
│ ۳) فراخوانی pgRouting برای ماتریس
|
||
▼
|
||
PostgreSQL (gis)
|
||
│ - extension: pgrouting
|
||
│ - جداول گراف: ways_routing, ways_routing_vertices_pgr
|
||
│ - توابع: pgr_dijkstraCostMatrix یا حلقه pgr_dijkstra
|
||
▼
|
||
پاسخ JSON: { distances: [[...]], durations: [[...]] }
|
||
```
|
||
|
||
---
|
||
|
||
## ۳. لایهٔ دادهٔ مسیریابی (گراف)
|
||
|
||
### ۳.۱ نیازمندی
|
||
|
||
- جداول فعلی `planet_osm_*` برای مسیریابی بهصورت مستقیم استفاده نمیشوند؛ pgRouting به یک **گراف جهتدار** نیاز دارد:
|
||
- **گرهها (vertices):** نقاط اتصال جادهها
|
||
- **یالها (edges):** قطعات جاده با فیلدهای `source`, `target`, `cost` (و ترجیحاً `reverse_cost` برای دوطرفه)
|
||
|
||
### ۳.۲ دو راه برای ساخت گراف
|
||
|
||
**راه الف) استفاده از osm2pgrouting (توصیه برای شروع)**
|
||
|
||
- ورودی: همان فایل OSM (مثلاً `iran-latest.osm.pbf`) که برای بهروزرسانی دیتابیس استفاده میکنید.
|
||
- خروجی در همان دیتابیس `gis`: جداول با نامهای مشابه `ways`, `ways_vertices_pgr` (بسته به mapconfig).
|
||
- مزیت: استاندارد و مستند؛ فیلدهای `length`, `cost`, `reverse_cost` از قبل محاسبه میشوند.
|
||
- نام schema/جدول را در سناریو بهصورت **`routing`** در نظر میگیریم (مثلاً `routing.ways`, `routing.ways_vertices_pgr`) تا از جداول OSM اصلی جدا باشد.
|
||
|
||
**راه ب) ساخت گراف از `planet_osm_line`**
|
||
|
||
- با اسکریپت/تابع SQL یا ابزار سفارشی، از خطوط با `highway IS NOT NULL` جدول یالها و گرهها ساخته شود و در جداول جدا (مثلاً `routing.ways`, `routing.ways_vertices_pgr`) ذخیره شود.
|
||
- مزیت: وابستگی به همان دیتایی که الان در PostGIS دارید؛ یک منبع حقیقت.
|
||
- معایب: پیادهسازی و تست بیشتر؛ مدیریت one-way و سرعت بر اساس `highway` بر عهدهٔ شماست.
|
||
|
||
در سناریو نهایی فرض میکنیم جداول گراف با نامهای زیر وجود دارند (در schemaی جدا یا در `public`):
|
||
|
||
- **`routing_ways`** (یا `ways`):
|
||
حداقل فیلدها: `id`, `source`, `target`, `cost` (متر یا ثانیه), `reverse_cost`, `geom` (geometry)
|
||
- **`routing_ways_vertices_pgr`** (یا `ways_vertices_pgr`):
|
||
حداقل فیلدها: `id`, `the_geom` (geometry)
|
||
|
||
و در صورت استفاده از osm2pgrouting، نام دقیق جداول طبق mapconfig (مثلاً `ways`, `ways_vertices_pgr`) در همان schema در نظر گرفته میشود.
|
||
|
||
---
|
||
|
||
## ۴. نصب و تنظیم pgRouting
|
||
|
||
- نصب extension در دیتابیس `gis`:
|
||
```sql
|
||
CREATE EXTENSION IF NOT EXISTS pgrouting;
|
||
```
|
||
- اطمینان از وجود PostGIS (قبلاً دارید).
|
||
- کاربر `search_user` (یا کاربری که اپ از آن استفاده میکند) باید دسترسی **SELECT** به جداول و توابع pgRouting و جداول گراف داشته باشد.
|
||
|
||
---
|
||
|
||
## ۵. بهروزرسانی داده (هماهنگ با بهروزرسانی OSM)
|
||
|
||
- هر بار که داده OSM را در سرور بهروز میکنید (مثلاً با osm2pgsql برای `planet_osm_*` یا با جایگزینی فایل OSM):
|
||
1. **اگر از osm2pgrouting استفاده میکنید:** بعد از بهروزرسانی، دوباره برای همان فایل OSM (یا اکسپورت فعلی ایران) اجرا کنید و خروجی را در جداول گراف (مثلاً در schema `routing`) بریزید؛ ترجیحاً با TRUNCATE + INSERT یا DROP + CREATE برای یکپارچگی.
|
||
2. **اگر گراف را از `planet_osm_line` میسازید:** بعد از بهروزرسانی جداول OSM، اسکریپت/تابع بازسازی گراف را اجرا کنید تا `routing_ways` و `routing_ways_vertices_pgr` بهروز شوند.
|
||
- این مرحله میتواند در همان اسکریپت/کرون بهروزرسانی سرور قرار گیرد تا بعد از هر بهروز OSM، گراف مسیریابی هم یکپارچه بهروز شود.
|
||
|
||
---
|
||
|
||
## ۶. طراحی API
|
||
|
||
### ۶.۱ Endpoint
|
||
|
||
- **مسیر پیشنهادی:** `POST /api/matrix`
|
||
- **متد:** POST (بهدلیل تعداد نقاط و طول URL در GET)
|
||
- **CORS:** مانند سایر APIها (مثلاً همان listener فعلی) برای دامنههای مجاز.
|
||
|
||
### ۶.۲ ورودی (Request Body – JSON)
|
||
|
||
```json
|
||
{
|
||
"origins": [
|
||
[51.42, 35.69],
|
||
[51.38, 35.72]
|
||
],
|
||
"destinations": [
|
||
[51.43, 35.70],
|
||
[51.39, 35.71],
|
||
[51.40, 35.68]
|
||
],
|
||
"options": {
|
||
"metrics": ["distance", "duration"],
|
||
"max_snap_distance_m": 500
|
||
}
|
||
}
|
||
```
|
||
|
||
- **origins:** آرایهٔ نقاط مبدأ؛ هر نقطه `[longitude, latitude]` (WGS84).
|
||
- **destinations:** آرایهٔ نقاط مقصد؛ همان فرمت.
|
||
- **options (اختیاری):**
|
||
- **metrics:** `["distance"]` و/یا `["duration"]`؛ تعیین میکند در پاسخ فاصله (متر) و/یا زمان (ثانیه) برگردانده شود.
|
||
- **max_snap_distance_m:** حداکثر فاصله (متر) برای snap نقطه به گراف؛ اگر نزدیکترین گره دورتر باشد، آن نقطه نامعتبر تلقی شده و در پاسخ با مقدار `null` یا کد خطا مشخص میشود.
|
||
|
||
### ۶.۳ محدودیتهای سایز
|
||
|
||
- حداکثر تعداد **origins** و **destinations** تا حد معقول (مثلاً هر کدام حداکثر 25 یا 50) تا زمان پاسخ و بار دیتابیس قابل کنترل باشد.
|
||
- حداکثر کل جفتها (N×M) هم میتواند محدود شود (مثلاً 1000 یا 2500).
|
||
- در صورت نقض محدودیت: پاسخ **400** با پیام واضح (مثلاً «حداکثر تعداد origins 25 است»).
|
||
|
||
### ۶.۴ خروجی (Response – JSON)
|
||
|
||
```json
|
||
{
|
||
"origins": [
|
||
{ "index": 0, "snapped": [51.4201, 35.6902], "valid": true },
|
||
{ "index": 1, "snapped": [51.3802, 35.7201], "valid": true }
|
||
],
|
||
"destinations": [
|
||
{ "index": 0, "snapped": [51.4300, 35.7001], "valid": true },
|
||
{ "index": 1, "snapped": [51.3901, 35.7100], "valid": true },
|
||
{ "index": 2, "snapped": null, "valid": false }
|
||
],
|
||
"distances": [
|
||
[1200, 3500, null],
|
||
[4100, 800, null]
|
||
],
|
||
"durations": [
|
||
[180, 420, null],
|
||
[520, 95, null]
|
||
]
|
||
}
|
||
```
|
||
|
||
- **distances:** ماتریس N×M؛ مقدار هر سلول فاصله به متر (یا `null` اگر مسیر یافت نشود یا نقطه نامعتبر باشد).
|
||
- **durations:** ماتریس N×M؛ مقدار هر سلول زمان به ثانیه (یا `null` در همان شرایط).
|
||
- **origins/destinations:** برای هر نقطه، ایندکس، مختصات snapped (در WGS84) و اینکه آیا در محدودهٔ snap قرار گرفته و در گراف استفاده شده است (`valid: true/false`).
|
||
|
||
در صورت خطای کلی (مثلاً پارامتر نامعتبر یا خطای دیتابیس): **500** با بدنهٔ JSON حاوی پیام خطا (و در حالت توسعه، جزئیات بیشتر).
|
||
|
||
---
|
||
|
||
## ۷. مراحل پردازش در سمت سرور (Symfony)
|
||
|
||
1. **دریافت و اعتبارسنجی ورودی**
|
||
- پارس JSON؛ بررسی وجود `origins` و `destinations` و آرایه بودن آنها.
|
||
- اعمال سقف روی تعداد origins، destinations و N×M؛ در صورت نقض برگرداندن 400.
|
||
|
||
2. **Snap به گراف**
|
||
- برای هر نقطه (هر مبدأ و هر مقصد): با یک کوئری PostGIS نزدیکترین رأس در `routing_ways_vertices_pgr` را پیدا کنید (مثلاً با `ST_DWithin` + `ORDER BY ST_Distance(the_geom, ST_Transform(ST_SetSRID(ST_Point(lon,lat), 4326), SRID_گراف)) LIMIT 1`).
|
||
- اگر فاصله بیشتر از `max_snap_distance_m` بود، نقطه را نامعتبر علامت بزنید و در ماتریس برای آن سطر/ستون `null` بگذارید.
|
||
- خروجی این مرحله: دو لیست از شناسهٔ گره (vertex id) برای مبدأها و مقصدها (بههمراه پرچم valid).
|
||
|
||
3. **محاسبهٔ ماتریس با pgRouting**
|
||
- **روش ۱:** اگر pgRouting شما `pgr_dijkstraCostMatrix` پشتیبانی میکند و محدودیت تعداد رأس را رعایت میکنید، یک بار برای تمام گرههای مبدأ و مقصد (اتحاد آنها) ماتریس cost را بگیرید و سپس سطر/ستونهای مربوط به origins و destinations را استخراج کنید.
|
||
- **روش ۲:** برای هر مبدأ، یک بار `pgr_dijkstra` از آن مبدأ به همهٔ مقصدها؛ جمعآوری cost (فاصله یا زمان) در یک ماتریس N×M.
|
||
- اگر cost در جدول یال بهصورت فاصله (متر) ذخیره شده، خروجی همان distances است؛ برای durations یا باید cost را بر اساس طول و سرعت متوسط محاسبه کرده باشید، یا ستون جدا (مثلاً `travel_time_sec`) در جدول یال داشته باشید و از آن استفاده کنید.
|
||
|
||
4. **ساخت پاسخ**
|
||
- پر کردن آرایههای `distances` و `durations` (در صورت درخواست)؛ برای نقاط نامعتبر یا مسیر ناموجود مقدار `null`.
|
||
- برگرداندن JSON با ساختار بالا و هدرهای CORS مناسب.
|
||
|
||
---
|
||
|
||
## ۸. هزینهٔ یال (cost) و مدت زمان (duration)
|
||
|
||
- در جدول یالها حداقل یک **cost** لازم است (معمولاً طول به متر یا زمان به ثانیه).
|
||
- برای **duration**: یا در زمان ساخت گراف یک فیلد `travel_time_sec` (یا مشابه) بر اساس نوع جاده و سرعت پیشفرض محاسبه و ذخیره شود، یا در همان cost از «زمان» استفاده شود و فاصله را در لایهٔ اپ با ضریب سرعت تقریبی به زمان تبدیل کنید (کمتر توصیه میشود ولی امکانپذیر است).
|
||
- در سناریو نهایی فرض میکنیم جدول گراف هم **cost_length** (متر) و هم **cost_time** (ثانیه) دارد، یا فقط length داریم و duration در اپ با سرعت متوسط (مثلاً 50 km/h برای جادههای درونشهری) تقریب زده میشود تا در پاسخ `durations` قابل ارائه باشد.
|
||
|
||
---
|
||
|
||
## ۹. امنیت و دسترسی
|
||
|
||
- کاربر دیتابیس (`search_user`) فقط به **SELECT** روی جداول گراف و توابع pgRouting نیاز دارد؛ نیازی به INSERT/UPDATE روی جداول مسیریابی در زمان سرویسدهی نیست.
|
||
- API میتواند در مرحلهٔ اول بدون احراز هویت باشد (مشابه وکتور تایل و جستجو)؛ در صورت نیاز بعداً میتوان API key یا rate limit اضافه کرد.
|
||
|
||
---
|
||
|
||
## ۱۰. خلاصهٔ چکلیست پیادهسازی
|
||
|
||
| مرحله | توضیح |
|
||
|-------|--------|
|
||
| ۱ | نصب extension pgRouting در دیتابیس `gis` |
|
||
| ۲ | ساخت/وارد کردن گراف (osm2pgrouting یا اسکریپت از planet_osm_line) در جداول `routing_ways` و `routing_ways_vertices_pgr` (یا نامهای نهایی انتخابشده) |
|
||
| ۳ | تعریف cost (و در صورت امکان duration) برای یالها و اطمینان از SRID یکسان برای هندسهٔ گراف |
|
||
| ۴ | اسکریپت/کرون بهروزرسانی گراف پس از هر بهروزرسانی OSM |
|
||
| ۵ | در Symfony: سرویس/ریپازیتوری برای اتصال به `gis` و توابع Snap + Matrix (در صورت نیاز جدا از OsmSearchRepository یا استفاده از همان اتصال) |
|
||
| ۶ | کنترلر `POST /api/matrix` با اعتبارسنجی، محدودیت سایز، فراخوانی سرویس ماتریس و برگرداندن JSON |
|
||
| ۷ | تست با چند مبدأ/مقصد واقعی و مقایسه با نقشه |
|
||
| ۸ | مستندسازی endpoint در سایت/صفحهٔ API (مثلاً در راهنمای استفاده یا enterprise) |
|
||
|
||
---
|
||
|
||
## ۱۱. نکات نهایی
|
||
|
||
- **SRID:** گراف معمولاً در یک SRID متریک (مثلاً 3857 یا 32639 برای UTM ایران) نگهداری میشود؛ در Snap ورودی WGS84 را به SRID گراف تبدیل کنید.
|
||
- **یکطرفه/دوطرفه:** جدول یال باید `reverse_cost` داشته باشد تا جادههای یکطرفه درست محاسبه شوند؛ در غیر این صورت همهٔ یالها دوطرفه در نظر گرفته میشوند.
|
||
- **عملکرد:** برای N,M کوچک (مثلاً تا 25) پاسخ در حد قابل قبول است؛ برای سایزهای بزرگتر ممکن است نیاز به timeout یا محدودیت سختتر یا کش کردن نتایج پرتکرار باشد.
|
||
|
||
پس از تأیید این سناریو میتوان مرحلهٔ ۱ تا ۸ را در پروژه پیادهسازی کرد.
|