محاسبه فاصله با کد پستی برای هزینه ارسال و نزدیکترین انبار
با مختصات کد پستی انبار و مشتری، فاصله خط راست را حساب کنید، آن را برای انتخاب انبار به کار ببرید و برای قیمت نهایی پله بسازید.
هزینه ارسال را نمیشود از نام شهر درآورد؛ دو مشتری در یک شهر ممکن است بیست کیلومتر با هم فاصله داشته باشند. با مختصات کد پستی، فاصله از انبار تا مشتری را در همان لحظه ثبت سفارش حساب میکنید.
این مقاله برای کسی است که قرار است این را در محصول بگذارد: فروشگاه با چند انبار، سرویس پیک، مارکتپلیس یا هر جا که «نزدیکترین» معنا دارد. روش ساده است. چند جا هم هست که سادهاش اشتباه از آب درمیآید، و دربارهٔ همانها حرف میزنیم.
مرحلهٔ اول: از کد پستی به نقطه
برای هر نشانی (انبار، شعبه، مشتری) یک بار مختصات کد پستی را میگیرید و ذخیره میکنید. سه چیز را کنار هم نگه دارید: کد پستی، عرض و طول جغرافیایی، و fetchedAt پاسخ.
bash curl -X POST 'https://<دامنه-ما>/api/v1/postal-code-geo/' \ -H 'Authorization: Bearer melli_live_…' \ -H 'Content-Type: application/json' \ -d '{"postalCode":"1312345678"}'
انبارهای شما چند تا هستند و کم تغییر میکنند؛ مختصاتشان را همان روز تعریف انبار بگیرید و در جدول انبارها بنویسید. مشتریها را هم هنگام ثبت اولین سفارش. بعد از آن فراخوان تکراری لازم نیست، چون نقطهٔ یک کد پستی عملاً عوض نمیشود. در مستندات API شکل کامل پاسخ و خطاها آمده؛ کلید را فقط از سرور خودتان بفرستید.
مرحلهٔ دوم: فاصلهٔ مستقیم
برای دو نقطهٔ عرض و طول، فاصلهٔ مستقیم روی کره را با فرمول هاورساین میشود حساب کرد. این فاصلهٔ خط راست است، نه مسیر خیابان:
js function km(a, b) { const R = 6371; // شعاع تقریبی زمین، کیلومتر const rad = (d) => (d * Math.PI) / 180; const dLat = rad(b.lat - a.lat); const dLng = rad(b.lng - a.lng); const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLng / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(h)); }
ورودی تابع باید { lat, lng } باشد. ورودی را از پاسخ سرویس با نام فیلد بسازید (latitude به lat و longitude به lng)؛ جابهجاکردن این دو رایجترین باگ است و روش وارسیاش را در راهنمای خواندن مختصات نوشتهایم.
چه وقت خط راست کافی است و چه وقت نه
به نظر من خط راست برای دو کار خوب است و برای یک کار گمراهکننده.
خوب است برای مرتبکردن انبارها. اگر سه انبار دارید و میخواهید نزدیکترین را انتخاب کنید، ترتیب خط راست معمولاً با ترتیب مسیر واقعی نزدیک است. بهویژه وقتی اختلاف انبارها زیاد باشد.
خوب است برای حذف سریع. مشتری ۸۰۰ کیلومتر دورتر از هر انبار است؟ لازم نیست مسیریابی بزنید. این فیلتر ارزان، صدها فراخوان مسیریابی را حذف میکند.
گمراهکننده است برای قیمت نهایی. رودخانه، اتوبان یکطرفه و کوه، فاصله مسیر را با خط راست متفاوت میکنند. اگر هزینه ارسال را مستقیم از خط راست میسازید، به مشتریان آن طرف یک مانع طبیعی بیش از حد کم میگیرید یا بیش از حد زیاد.
راه میانهای که زیاد به کار میآید: پلکانی بسازید («تا ۵ کیلومتر خط راست»، «تا ۱۵»، …) و پلهها را آگاهانه نرمتر از واقعیت تعریف کنید. این را در سناریوی فرضی ببینید: فرض کنید یک فروشگاه سه انبار دارد و هر پله یک ضریب هزینه دارد؛ وقتی گزارش شکایتها را مرور میکنند و میبینند همه از یک محلهاند، فهمیدهاند یک مانع طبیعی بین انبار و آن محله هست و میتوانند همان محله را دستی در پله بالاتر ببرند.
محدوده پوشش: دایره یا فهرست؟
دو راه ساده برای «آیا به این مشتری سرویس میدهیم؟»:
- دایره: فاصله تا مرکز انبار کمتر از شعاع باشد. ارزان و قابل فهم، ولی مرز آن صاف است و جغرافیای واقعی شهر را رعایت نمیکند.
- فهرست کد پستی مجاز: لیستی از کد پستیهایی که پوشش میدهید. دقیق است، ولی نگهداریاش خستهکننده میشود.
ترکیب این دو معمولاً بهتر از هرکدام است: دایره برای فیلتر اول، و یک فهرست کوتاه استثنا برای محلههایی که دایره دربارهشان غلط میگوید. مهم این است که کاربر پیش از پرداخت بداند، نه بعدش.
دو دام جدی
۱. نقطه دقیق در نیست. مختصات به کد پستی نگاشت شده و روی درِ ساختمان نمینشیند. برای مرز نزدیک شعاع (مثلاً مشتری ۴٫۹ کیلومتر در برابر شعاع ۵) نتیجه ناپایدار است. یا حاشیه امن بگذارید یا آن مورد را بررسی دستی کنید.
۲. کد پستی همهچیز نیست. اگر کاربر کد پستی را غلط وارد کند، همه محاسبهها غلط میشوند. پیش از هر چیز از او نشانی را هم بگیرید و از استعلام نشانی برای مقایسه استفاده کنید؛ اگر استان و شهر کد پستی با چیزی که کاربر نوشته نمیخواند، قبل از محاسبه مکث کنید.
آنچه این روش نمیکند
زمان رسیدن را نمیدهد. مسیر خیابانی را نمیدهد. وضع ترافیک و ساعت کاری را هم نمیداند. این سرویس فقط یک نقطه میدهد؛ مسیریابی و زمانبندی کار نقشه یا سرویس مسیریابی خودتان است.
و یادتان باشد پاسخ not_found معتبر است و هزینه دارد، ولی invalid_input هزینه ندارد. خطای گذرا (temporarily_unavailable) را فقط یک بار دوباره امتحان کنید و اگر نشد، سفارش را با هزینه ارسال ثابت یا بررسی دستی جلو ببرید، نه با عدد حدسی.
یک چکلیست برای شروع
- مختصات همهٔ انبارها را از سرویس مختصات کد پستی بگیرید و در جدول انبار بنویسید.
- ورودی تابع فاصله را همیشه با نام فیلد بسازید.
- برای ده سفارش گذشته فاصله را حساب کنید و با هزینه ارسال واقعی همان سفارشها مقایسه کنید.
- هر جا اختلاف بزرگ بود، مسیر واقعی را روی نقشه ببینید و علتش را بنویسید (مانع طبیعی، یکطرفه، دسترسی).
- پلهها را با همان یادداشتها تنظیم کنید.
مرحلهٔ سه و چهار از همه مهمترند و معمولاً حذف میشوند. بدونشان، پلههای هزینه روی حدس ساخته میشود.
هزینهٔ فراخوان در برابر فایدهٔ فاصله
سؤال طبیعی این است که برای هر سفارش مختصات را از نو بگیریم یا نه. پاسخ ساده است: برای انبارها نه، برای مشتری یک بار. اما یک جزئیات هست که معمولاً فراموش میشود: اگر مشتری نشانی عوض کند (کد پستی تازه)، مختصات قدیمی دیگر معتبر نیست. پس مختصات را به کد پستی ببندید، نه به شناسهٔ کاربر. همان کاربر با دو نشانی، دو رکورد مختصات دارد.
برای سفارشهای دستهای هم یک نکته: اگر هزار سفارش دارید که پانصد کد پستی یکتا دارند، پانصد فراخوان لازم است، نه هزار. پیش از فراخوان، فهرست کد پستیهای یکتا را بسازید و آنهایی که قبلاً ذخیره کردهاید حذف کنید.
اگر عجله دارید
مختصات انبارها را یک بار بگیرید و ذخیره کنید. مشتری را هنگام اولین سفارش. فاصله را با هاورساین حساب کنید، آن را برای انتخاب انبار و فیلتر محدوده به کار ببرید و برای قیمت نهایی پله بسازید، نه فرمول خطی. اگر جای شما بودم، اول با صد سفارش گذشته خودم هزینهٔ پلهای را بازبینی میکردم؛ همانجا مشخص میشود کدام محلهها را باید جدا کرد.
وبسرویس مرتبط با این راهنما
API مختصات جغرافیایی کد پستی
طول و عرض جغرافیایی نقطه متناظر با یک کد پستی.
راهنماهای مرتبط
پرسش و تجربه کاربران
اگر این راهنما جوابتان را نداد، همینجا بپرسید. پرسشها را تیمملی ایپیآی پاسخ میدهد و هر نظر پیش از انتشار بررسی میشود.
هنوز پرسشی ثبت نشده، اولین نفر باشید.
