مستندات سرویس نقشه الوکادو
با یک خط اسکریپت، نقشه کامل ایران را داخل سایت خودتان میآورید: تایلهای وکتور آفلاین،
مارکر و پاپآپ، مسیریابی جادهای، محاسبه مسافت و زمان، و نمایش موقعیت زندهٔ کاربر.
هیچ وابستگی بیرونی وجود ندارد؛ همهٔ فایلها (Leaflet، MapLibre، فونت، تایل و گراف مسیر)
از سرور https://map.bitjob.ir سرو میشوند.
رابط برنامهنویسی سمت مرورگر روی Leaflet 1.9 ساخته شده است، پس هر چیزی که در مستندات Leaflet بلدید اینجا هم کار میکند. موتور رندر تایلها MapLibre GL است.
فهرست
- شروع سریع
- گرفتن کلید API و محدودکردن دامنه
- ساختن نقشه
- جابهجایی، زوم و کنترلها
- مارکر، پاپآپ و آیکون سفارشی
- خط، چندضلعی و دایره
- رویدادها و گرفتن مختصات
- مسیریابی جادهای
- جستجو، تکمیل خودکار و ژئوکدینگ
- ماتریس فاصله و زمان
- تصویر ثابت نقشه
- تم شب
- محاسبه مسافت و زمان
- موقعیت زندهٔ کاربر
- نمایش داده GeoJSON
- دسترسی مستقیم به MapLibre
- مرجع REST API
- خطاها و کدهای وضعیت
- هزینهها و سشن
- محدودیتها و نکات مهم
- نمونهٔ کامل (نقشه + مسیریابی + مسافت)
- عیبیابی
۱) شروع سریع
سه قدم تا نقشهٔ زنده:
<!-- ۱. اسکریپت سرویس با کلید شما -->
<script src="https://map.bitjob.ir/map.js?key=YOUR_API_KEY"></script>
<!-- ۲. عنصری که نقشه داخلش ساخته میشود (ارتفاع الزامی است) -->
<div id="map" style="height:400px"></div>
<!-- ۳. ساخت نقشه -->
<script>
AlooMap.ready(function () {
var map = AlooMap.map('map', { center: [35.6892, 51.3890], zoom: 12 });
});
</script>
کلید را میتوانید بهجای query string با data-key هم بدهید:
<script src="https://map.bitjob.ir/map.js" data-key="YOUR_API_KEY">
نکات ضروری
- عنصر نقشه حتماً باید ارتفاع داشته باشد؛ وگرنه نقشه دیده نمیشود.
- ساخت نقشه غیرهمزمان است: یا داخل
AlooMap.ready()بنویسید یا ازAlooMap.createMap()استفاده کنید. - مختصات همهجا به شکل
[lat, lng](عرض، طول) است — مثل Leaflet و برعکس GeoJSON.
۲) گرفتن کلید API و محدودکردن دامنه
- از ورود با الوکادو وارد شوید (ثبتنام جدا ندارد؛ کد تایید در پیامرسان الوکادو میآید).
- در داشبورد یک کلید جدید بسازید.
- از صفحهٔ شارژ حساب را شارژ کنید.
دو نوع کلید
کلید عمومی mk_… | کلید سرّی sk_… | |
|---|---|---|
| کجا استفاده میشود | داخل HTML سایت شما (map.js) |
فقط روی سرور خودتان |
| چه کسی میبیندش | همه — در سورس صفحه پیداست | هیچکس؛ هرگز نباید به مرورگر برسد |
| دامنهٔ مجاز | اجباری | لازم نیست (Origin ندارد) |
| مناسب برای | نمایش نقشه، مارکر، موقعیت کاربر | مسیریابی، ماتریس، جستجو، تصویر ثابت |
محدودکردن کلید عمومی به دامنهٔ خودتان
هنگام ساخت کلید عمومی، فیلد «دامنههای مجاز» را پر کنید (با کاما جدا کنید، مثل
example.com, shop.example.com). زیردامنهها خودکار مجاز هستند
(example.com شامل www.example.com هم میشود).
403) — نه فقط درخواستِ
دامنهٔ اشتباه. دلیلش این است که کلید عمومی در سورس صفحهٔ شما پیداست و اگر درخواستِ
بدون Origin پذیرفته میشد، هرکسی میتوانست کلید را کپی کند و با یک اسکریپت ساده
موجودی حساب شما را خرج کند.
برای فراخوانی سرور-به-سرور از کلید سرّی استفاده کنید، نه کلید عمومی.
اگر کلیدی جایی لو رفت، لازم نیست حذفش کنید: از داشبورد دکمهٔ چرخش را بزنید تا مقدارش عوض شود و نام و دامنهها و آمارش سرِ جایش بماند. در داشبورد میتوانید آستانهٔ هشدار موجودی کم را هم تنظیم کنید تا سرویس نقشهٔ سایتتان بیخبر قطع نشود.
۳) ساختن نقشه
روش ۱ — داخل ready (همزمان)
AlooMap.ready(function (session) {
var map = AlooMap.map('map', {
center: [35.6892, 51.3890], // میدان آزادی تهران
zoom: 12,
maxZoom: 18,
attribution: 'نقشه سایت من', // متن پایین-چپ؛ '' یعنی بدون متن
leaflet: { zoomControl: false, scrollWheelZoom: false } // هر گزینهٔ خام Leaflet
});
console.log('موجودی باقیمانده:', session.balance);
});
روش ۲ — createMap (Promise، بدون ready)
AlooMap.createMap('map', { center: [35.6892, 51.3890], zoom: 12 })
.then(function (map) {
// نقشه آماده است
})
.catch(function (err) {
// خطا خودکار داخل خود عنصر نقشه هم نمایش داده میشود
console.error(err.message);
});
روش ۳ — افزودن لایهٔ نقشه به یک نقشهٔ Leaflet موجود
اگر خودتان از قبل Leaflet دارید و فقط تایل میخواهید:
AlooMap.ready(function () {
var map = L.map('map').setView([35.69, 51.39], 12);
AlooMap.tileLayer().addTo(map);
});
گزینههای AlooMap.map / AlooMap.createMap
| گزینه | پیشفرض | توضیح |
|---|---|---|
center | [32.4279, 53.6880] | مرکز اولیه [lat, lng] (پیشفرض: مرکز ایران) |
zoom | 6 | سطح زوم اولیه (۰ تا 18) |
maxZoom | 18 | بیشترین زوم مجاز |
attribution | OpenStreetMap + نقشه الوکادو | متن انتساب؛ رشتهٔ خالی یعنی حذف کامل |
leaflet | — | آبجکت گزینههای خام Leaflet که مستقیم به L.map() پاس میشود |
متدهای AlooMap
| متد | خروجی | توضیح |
|---|---|---|
AlooMap.ready(cb) | Promise<session> | لود کتابخانهها و ساخت سشن؛ callback اختیاری است |
AlooMap.map(el, opts) | L.Map | ساخت همزمان نقشه — فقط داخل ready |
AlooMap.createMap(el, opts) | Promise<L.Map> | ساخت نقشه بدون نیاز به ready؛ خطا را داخل عنصر نشان میدهد |
AlooMap.tileLayer(opts) | L.Layer | فقط لایهٔ تایل، برای نقشهٔ Leaflet موجود |
AlooMap.locate(map, opts) | Promise<{stop()}> | نمایش زندهٔ موقعیت کاربر |
AlooMap.base | string | آدرس سرور سرویس (برای صدازدن REST API) |
AlooMap._session | object | سشن جاری: token، expiresAt، balance، styleUrl |
el هم میتواند id عنصر (رشته) باشد و هم خود عنصر DOM.
۴) جابهجایی، زوم و کنترلها
map.setView([36.2972, 59.6067], 13); // پرش به مشهد
map.flyTo([29.5918, 52.5837], 13); // انیمیشن نرم به شیراز
map.panTo([35.70, 51.40]); // فقط جابهجایی
map.setZoom(15); map.zoomIn(); map.zoomOut();
map.getCenter(); // {lat, lng}
map.getZoom(); // عدد
map.getBounds(); // کادر دید فعلی
// قابکردن چند نقطه در دید
map.fitBounds([[35.68, 51.35], [35.75, 51.45]], { padding: [30, 30] });
// محدودکردن حرکت کاربر به محدودهٔ ایران
map.setMaxBounds([[24.0, 43.5], [40.5, 64.0]]);
// اگر نقشه داخل تب/آکاردئون مخفی ساخته شد، بعد از نمایانشدن حتماً:
map.invalidateSize();
کنترلها
L.control.scale({ metric: true, imperial: false, position: 'bottomright' }).addTo(map);
map.zoomControl.setPosition('topleft');
// دکمهٔ تمامصفحه ساده
L.control.attribution({ prefix: false }).addTo(map);
۵) مارکر، پاپآپ و آیکون سفارشی
مارکر ساده + پاپآپ + تولتیپ
var marker = L.marker([35.6892, 51.3890]).addTo(map);
marker.bindPopup('<b>دفتر مرکزی</b><br>تهران، میدان آزادی').openPopup();
marker.bindTooltip('دفتر مرکزی'); // با هاور نشان داده میشود
marker.setLatLng([35.70, 51.40]); // جابهجا کردن
map.removeLayer(marker); // حذف
مارکر با آیکون سفارشی
var myIcon = L.icon({
iconUrl: 'https://example.com/pin.png',
iconSize: [32, 42], // اندازهٔ تصویر
iconAnchor: [16, 42], // نقطهای از تصویر که روی مختصات مینشیند (نوک پین)
popupAnchor:[0, -40] // محل بازشدن پاپآپ نسبت به iconAnchor
});
L.marker([35.6892, 51.3890], { icon: myIcon }).addTo(map);
مارکر HTML/CSS (بدون تصویر)
var badge = L.divIcon({
className: '',
html: '<div style="background:#1a73e8;color:#fff;border-radius:14px;' +
'padding:3px 9px;font:12px Tahoma;white-space:nowrap">۲۵۰,۰۰۰ تومان</div>',
iconSize: null
});
L.marker([35.72, 51.42], { icon: badge }).addTo(map);
مارکر قابل جابهجایی (انتخاب آدرس توسط کاربر)
var pin = L.marker([35.6892, 51.3890], { draggable: true }).addTo(map);
pin.on('dragend', function () {
var p = pin.getLatLng();
document.getElementById('lat').value = p.lat.toFixed(6);
document.getElementById('lng').value = p.lng.toFixed(6);
});
گذاشتن مارکر با کلیک کاربر
var picked = null;
map.on('click', function (e) {
if (picked) picked.setLatLng(e.latlng);
else picked = L.marker(e.latlng).addTo(map);
picked.bindPopup('موقعیت انتخابی: ' + e.latlng.lat.toFixed(5) + ', ' +
e.latlng.lng.toFixed(5)).openPopup();
});
مارکرهای زیاد + حذف یکجا
var group = L.layerGroup().addTo(map);
var places = [
{ name: 'شعبه ونک', lat: 35.7575, lng: 51.4100 },
{ name: 'شعبه تجریش', lat: 35.8020, lng: 51.4340 },
{ name: 'شعبه آزادی', lat: 35.6997, lng: 51.3380 }
];
places.forEach(function (p) {
L.marker([p.lat, p.lng]).bindPopup(p.name).addTo(group);
});
// قابکردن همهٔ مارکرها در دید
map.fitBounds(L.featureGroup(group.getLayers()).getBounds(), { padding: [40, 40] });
group.clearLayers(); // حذف همهٔ مارکرها با هم
۶) خط، چندضلعی و دایره
// خط (مثلاً مسیر پیمودهشده)
var line = L.polyline([[35.69, 51.39], [35.75, 51.45], [35.80, 51.43]], {
color: '#1a73e8', weight: 5, opacity: 0.85
}).addTo(map);
// چندضلعی (محدودهٔ سرویسدهی)
L.polygon([[35.70, 51.33], [35.78, 51.36], [35.76, 51.48], [35.68, 51.45]], {
color: '#15803d', fillOpacity: 0.15
}).addTo(map).bindPopup('محدودهٔ ارسال رایگان');
// دایره با شعاع متر (شعاع پوشش)
L.circle([35.6892, 51.3890], { radius: 3000, color: '#b91c1c', fillOpacity: 0.08 }).addTo(map);
// مستطیل
L.rectangle([[35.66, 51.32], [35.74, 51.46]], { weight: 1 }).addTo(map);
// ویرایش و حذف
line.setStyle({ color: 'red' });
line.setLatLngs([[35.69, 51.39], [35.72, 51.42]]);
map.removeLayer(line);
۷) رویدادها و گرفتن مختصات
map.on('click', function (e) {
console.log(e.latlng.lat, e.latlng.lng); // مختصات نقطهٔ کلیکشده
});
map.on('moveend', function () { console.log('مرکز جدید:', map.getCenter()); });
map.on('zoomend', function () { console.log('زوم:', map.getZoom()); });
marker.on('click', function () { /* کلیک روی مارکر */ });
map.on('contextmenu', function (e) { /* کلیک راست */ });
// بارگذاری داده بر اساس کادر دید فعلی
map.on('moveend', function () {
var b = map.getBounds();
fetch('/api/places?north=' + b.getNorth() + '&south=' + b.getSouth() +
'&east=' + b.getEast() + '&west=' + b.getWest());
});
۸) مسیریابی جادهای
مسیریابی روی گراف جادههای ایران (داده OpenStreetMap) و کاملاً آفلاین روی سرور ما انجام میشود. خروجی شامل مسافت جادهای، زمان تقریبی، شکل مسیر و دستورهای گامبهگام است.
سادهترین شکل
AlooMap.route([35.6892, 51.3890], [36.2972, 59.6067]).then(function (r) {
AlooMap.drawRoute(map, r); // رسم مسیر و زوم روی آن
console.log(AlooMap.formatDistance(r.meters), AlooMap.formatDuration(r.seconds));
r.steps.forEach(function (s) { console.log(s.text); });
});
نقاط میانی و پروفایل
// مسیر با توقفهای میانی — یک درخواست، نه چند تا
AlooMap.route([
[35.70, 51.40], // مبدأ
[35.75, 51.45], // توقف ۱
[35.80, 51.50] // مقصد
], { profile: 'car' });
پروفایلهای ممکن: car (پیشفرض)، pedestrian،
bicycle. پروفایلهای در دسترس به گرافی که روی سرور ساخته شده
بستگی دارد؛ فهرست دقیق در /readyz میآید و درخواستِ پروفایلِ
ناموجود پاسخ 400 با فهرست پشتیبانیشدهها میگیرد. هزینه بهازای هر
بخش مسیر حساب میشود، پس مسیر سهنقطهای دو برابر مسیر ساده هزینه دارد.
درخواست خام
POST https://map.bitjob.ir/api/v1/route
Content-Type: application/json
{
"key": "YOUR_API_KEY",
"points": [[35.6892, 51.3890], [36.2972, 59.6067]],
"profile": "car"
}
شکل قدیمی fromLat/fromLng/toLat/toLng هم همچنان کار میکند.
پاسخ موفق (۲۰۰)
{
"ok": true,
"meters": 892340, // مسافت جادهای بر حسب متر
"seconds": 32150, // زمان تقریبی بر حسب ثانیه
"latlng": [[35.6892,51.3890], ... ], // شکل مسیر، آمادهٔ L.polyline
"steps": [ // دستورهای گامبهگام
{
"meters": 1200,
"seconds": 95,
"name": "خیابان ولیعصر",
"maneuver": "right", // straight | left | right | sharp-left | sharp-right | arrive
"text": "۱٫۲ کیلومتر در خیابان ولیعصر، سپس به راست بپیچید به بزرگراه همت",
"latlng": [35.7100, 51.4020] // نقطهٔ پیچ
}
],
"balance": 148900 // موجودی باقیماندهٔ حساب (تومان)
}
رسم مسیر روی نقشه
function alooRoute(from, to) {
return fetch(AlooMap.base + '/api/v1/route', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: 'YOUR_API_KEY',
fromLat: from[0], fromLng: from[1],
toLat: to[0], toLng: to[1]
})
}).then(function (res) {
return res.json().then(function (data) {
if (!res.ok) throw new Error(data.error || 'خطای مسیریابی');
return data;
});
});
}
var routeLine = null;
alooRoute([35.6892, 51.3890], [36.2972, 59.6067]).then(function (r) {
if (routeLine) map.removeLayer(routeLine);
routeLine = L.polyline(r.latlng, { color: '#1a73e8', weight: 5, opacity: 0.9 }).addTo(map);
map.fitBounds(routeLine.getBounds(), { padding: [40, 40] });
L.marker(r.latlng[0]).addTo(map).bindPopup('مبدأ');
L.marker(r.latlng[r.latlng.length - 1]).addTo(map).bindPopup('مقصد');
console.log((r.meters / 1000).toFixed(1) + ' کیلومتر — ' +
Math.round(r.seconds / 60) + ' دقیقه');
});
مسیر چندنقطهای (توقفهای میانی)
هر درخواست فقط مبدأ و مقصد میگیرد؛ برای مسیر با توقف، بخشها را پشتسرهم بگیرید و جمع کنید. هر بخش یک درخواست مسیریابی جداگانه حساب میشود.
async function multiRoute(points) {
var total = { meters: 0, seconds: 0, latlng: [] };
for (var i = 0; i < points.length - 1; i++) {
var seg = await alooRoute(points[i], points[i + 1]);
total.meters += seg.meters;
total.seconds += seg.seconds;
total.latlng = total.latlng.concat(seg.latlng);
}
return total;
}
multiRoute([[35.69, 51.39], [35.82, 50.95], [36.27, 50.00]]).then(function (r) {
L.polyline(r.latlng, { color: '#1a73e8', weight: 5 }).addTo(map);
});
صدازدن از سرور (پیشنهادی برای کلیدهای حساس)
curl -X POST https://map.bitjob.ir/api/v1/route \
-H "Content-Type: application/json" \
-d '{"key":"YOUR_API_KEY","fromLat":35.6892,"fromLng":51.3890,"toLat":36.2972,"toLng":59.6067}'
// C#
var payload = new { key = "YOUR_API_KEY", fromLat = 35.6892, fromLng = 51.3890,
toLat = 36.2972, toLng = 59.6067 };
var res = await http.PostAsJsonAsync("https://map.bitjob.ir/api/v1/route", payload);
var route = await res.Content.ReadFromJsonAsync<RouteResult>();
درخواست سرور-به-سرور هدر Origin ندارد، پس محدودیت دامنهٔ کلید روی آن اعمال نمیشود؛
بهترین کار این است که کلید را در سرور خودتان نگه دارید و مسیریابی را از آنجا صدا بزنید.
۹) محاسبه مسافت و زمان
الف) مسافت جادهای (دقیق، از سرویس مسیریابی)
عددی که در فاکتور ارسال یا کرایه لازم دارید همین است — طول واقعی مسیر روی جاده:
alooRoute(origin, destination).then(function (r) {
var km = r.meters / 1000; // کیلومتر
var minutes = r.seconds / 60; // دقیقه
var fare = Math.round(km * 4500); // مثال: کرایه هر کیلومتر ۴٬۵۰۰ تومان
document.getElementById('fare').textContent = fare.toLocaleString('fa-IR') + ' تومان';
});
ب) فاصلهٔ مستقیم / هوایی (رایگان، بدون درخواست شبکه)
برای فیلترکردن «نزدیکترین شعبه» یا بررسی «داخل شعاع سرویسدهی» همین کافی است:
// هر دو خروجی بر حسب متر است
var meters = map.distance([35.6892, 51.3890], [35.7575, 51.4100]);
var same = L.latLng(35.6892, 51.3890).distanceTo([35.7575, 51.4100]);
// آیا داخل شعاع ۵ کیلومتری هستیم؟
if (map.distance(userLatLng, shopLatLng) <= 5000) { /* داخل محدوده */ }
// نزدیکترین شعبه به کاربر
var nearest = places
.map(function (p) { return { p: p, d: map.distance(userLatLng, [p.lat, p.lng]) }; })
.sort(function (a, b) { return a.d - b.d; })[0];
فاصلهٔ مستقیم همیشه از مسافت جادهای کمتر است. برای قیمتگذاری از حالت (الف) استفاده کنید و از (ب) فقط برای مرتبسازی و فیلتر اولیه.
ج) طول یک خط (مسیر رسمشده یا مسیر پیمودهشده)
function polylineLength(latlngs) {
var sum = 0;
for (var i = 1; i < latlngs.length; i++)
sum += L.latLng(latlngs[i - 1]).distanceTo(latlngs[i]);
return sum; // متر
}
polylineLength(routeLine.getLatLngs());
د) اندازهگیری با کلیک کاربر (خطکش)
var pts = [], ruler = null;
map.on('click', function (e) {
pts.push(e.latlng);
if (ruler) map.removeLayer(ruler);
ruler = L.polyline(pts, { color: '#b91c1c', dashArray: '6,6' }).addTo(map);
if (pts.length > 1)
ruler.bindTooltip(fmtDistance(polylineLength(pts)), { permanent: true }).openTooltip();
});
ه) قالببندی فارسی مسافت و زمان
function fmtDistance(m) {
return m < 1000
? Math.round(m) + ' متر'
: (m / 1000).toFixed(m < 10000 ? 1 : 0) + ' کیلومتر';
}
function fmtDuration(s) {
var h = Math.floor(s / 3600), m = Math.round((s % 3600) / 60);
if (h && m) return h + ' ساعت و ' + m + ' دقیقه';
if (h) return h + ' ساعت';
return Math.max(1, m) + ' دقیقه';
}
fmtDistance(892340); // "892 کیلومتر"
fmtDuration(32150); // "8 ساعت و 56 دقیقه"
seconds زمان آزادراهی بر اساس سرعت مجاز جادههاست و ترافیک لحظهای را
در نظر نمیگیرد؛ برای شهرهای شلوغ معمولاً ضریبی (مثلاً ۱٫۳) روی آن اعمال میکنند.
۱۰) موقعیت زندهٔ کاربر
مارکر آبی + دایرهٔ دقت را روی نقشه میآورد و با هر بهروزرسانی GPS جابهجا میکند
(enableHighAccuracy فعال است).
AlooMap.locate(map, {
follow: true, // نقشه همراه کاربر حرکت کند
zoom: 16, // زوم اولین fix
timeout: 15000, // میلیثانیه
onUpdate: function (pos) {
console.log(pos.coords.latitude, pos.coords.longitude,
'دقت:', pos.coords.accuracy + ' متر');
}
}).then(function (tracker) {
// پایاندادن به ردیابی و حذف مارکر
document.getElementById('stopBtn').onclick = function () { tracker.stop(); };
}).catch(function (err) {
alert('دسترسی به موقعیت ممکن نشد: ' + err.message);
});
مرورگرها موقعیتیابی را فقط روی HTTPS اجازه میدهند و کاربر باید اجازه بدهد.
فاصلهٔ کاربر تا مقصد را با map.distance() یا مسیریابی حساب کنید.
۱۱) نمایش داده GeoJSON
fetch('/api/my-branches.geojson')
.then(function (r) { return r.json(); })
.then(function (geo) {
var layer = L.geoJSON(geo, {
style: { color: '#1a73e8', weight: 3 },
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, { radius: 6, fillColor: '#1a73e8', fillOpacity: 1, weight: 2, color: '#fff' });
},
onEachFeature: function (feature, lyr) {
lyr.bindPopup(feature.properties.name);
}
}).addTo(map);
map.fitBounds(layer.getBounds());
});
توجه: در GeoJSON ترتیب مختصات [lng, lat] است (برعکس Leaflet)؛
L.geoJSON خودش این تبدیل را انجام میدهد.
۱۲) دسترسی مستقیم به MapLibre (پیشرفته)
لایهٔ تایل یک نقشهٔ MapLibre GL است. اگر به قابلیتهای سطحپایین (تغییر استایل لایهها، چرخش، شیب سهبعدی) نیاز دارید:
AlooMap.ready(function () {
var map = AlooMap.map('map', { center: [35.69, 51.39], zoom: 12 });
var gl = AlooMap.tileLayer(); // یا لایهای که createMap ساخته
gl.addTo(map);
var mlMap = gl.getMaplibreMap(); // شیء maplibregl.Map
mlMap.on('load', function () {
mlMap.setPaintProperty('water', 'fill-color', '#a8d8ff');
});
});
استفاده از استایل بدون Leaflet
AlooMap.ready(function (session) {
var m = new maplibregl.Map({
container: 'map',
style: session.styleUrl, // شامل توکن سشن است
center: [51.3890, 35.6892], // دقت کنید: MapLibre به شکل [lng, lat]
zoom: 11
});
});
جستجوی مکان و ژئوکدینگ
نام شهر، روستا، خیابان یا مرکز (بیمارستان، مرکز خرید، پمپ بنزین …) را میگیرد و مختصات
برمیگرداند. کاملاً آفلاین روی دادههای استخراجشده از OpenStreetMap ایران.
جستجو نسبت به «ی/ک» عربی، اعراب، کشیدگی، نیمفاصله و ارقام فارسی بیتفاوت است؛
كرج و کرج یک نتیجه میدهند.
جستجو
AlooMap.search('میدان آزادی').then(function (r) {
r.results.forEach(function (p) {
// مثلاً: «میدان آزادی — شهرستان تهران، استان تهران»
console.log(p.name + ' — ' + p.context, p.kind, p.lat, p.lng);
});
});
// با سوگیری مکانی: نتایج نزدیکتر بالاتر میآیند
AlooMap.search('ولیعصر', { near: [35.6892, 51.3890], limit: 5 })
.then(function (r) {
r.results.forEach(function (p) {
console.log(p.name, AlooMap.formatDistance(p.distanceMeters));
});
});
kind یکی از city، town،
village، suburb، street
یا poi است. برای poi فیلد
category نوع دقیقتر را میگوید (hospital،
pharmacy، mall …).
context تقسیمات کشوریِ دربرگیرندهٔ مکان است به شکل
«شهرستان، استان» — از مرزهای اداری OpenStreetMap محاسبه میشود، نه از نزدیکترین شهر.
همین فیلد است که نتایج همنام را از هم جدا میکند: چند «شهرک ولیعصر» وجود دارد و
بدون آن معلوم نیست کدام کجاست. اگر مکانی بیرون از هر مرز شناختهشده بیفتد،
null میماند.
تکمیل خودکار حین تایپ (صورتحساب سشنی)
برای جعبهٔ جستجویی که با هر کلید کاربر پیشنهاد میدهد،
/api/v1/search مناسب نیست: هر ضربهٔ کلید یک بار هزینه میشد و
نوشتن «میدان ولیعصر» ده برابر یک جستجو خرج برمیداشت.
autocomplete سشنی حساب میشود — همهٔ ضربههای کلیدِ یک
جستجو روی یک سشن میروند و مجموعاً یک بار هزینه دارند.
var input = document.getElementById('q');
var timer;
input.addEventListener('input', function () {
clearTimeout(timer);
timer = setTimeout(function () {
AlooMap.autocomplete(input.value, { near: [35.6892, 51.3890] })
.then(function (r) {
render(r.results); // { id, name, context, kind, lat, lng, distanceMeters }
});
}, 200); // debounce: کمتر درخواست، تجربهٔ بهتر
});
function onUserPicked(place) {
map.setView([place.lat, place.lng], 16);
AlooMap.endSearchSession(); // جستجوی بعدی سشن (و هزینهٔ) تازه میگیرد
}
شناسهٔ سشن را خود map.js میسازد و نگه میدارد؛ فقط بعد از اینکه کاربر
یک نتیجه را انتخاب کرد AlooMap.endSearchSession() را صدا بزنید.
اگر مستقیم با REST کار میکنید، پارامتر session را خودتان با یک
UUID پر کنید و تا پایان همان جستجو ثابت نگه دارید.
charged در پاسخ میگوید همین درخواست هزینه داشته یا نه —
در یک سشن سالم فقط اولی true است.
هر سشن حداکثر 25 درخواست و 5
دقیقه عمر دارد. بعد از آن پاسخ 409 میگیرید که یعنی «سشن تازه بساز»؛
map.js این حالت را خودکار مدیریت میکند. سقف عمداً وجود دارد تا یک سشنِ
یکبار پرداختشده به API جستجوی نامحدودِ رایگان تبدیل نشود.
ژئوکدینگ معکوس
نزدیکترین مکان شناختهشده به یک مختصات — مثلاً برای نشاندادن «شما اینجا هستید».
map.on('click', function (e) {
AlooMap.reverse(e.latlng.lat, e.latlng.lng).then(function (r) {
L.popup().setLatLng(e.latlng)
.setContent(r.result.name + ' — ' + AlooMap.formatDistance(r.result.distanceMeters))
.openOn(map);
}).catch(function () { });
});
صدازدن از سرور
GET https://map.bitjob.ir/api/v1/search?key=SECRET_KEY&q=میدان آزادی&limit=5&lat=35.69&lng=51.39 GET https://map.bitjob.ir/api/v1/reverse?key=SECRET_KEY&lat=35.6997&lng=51.3381
ماتریس فاصله و زمان
زمان سفر بین چند مبدأ و چند مقصد را یکجا حساب میکند — چیزی که سامانههای پخش، تخصیص راننده و انتخاب نزدیکترین شعبه لازم دارند. یک درخواست ماتریس بهمراتب ارزانتر و سریعتر از صدا زدن جداگانهٔ مسیریابی برای هر جفت است.
AlooMap.matrix(
[[35.70, 51.40], [35.75, 51.45]], // مبدأها (رانندگان)
[[35.72, 51.38], [35.68, 51.42], [35.80, 51.50]] // مقصدها (سفارشها)
).then(function (r) {
// r.seconds[i][j] = زمان از مبدأ i تا مقصد j (ثانیه) — null یعنی مسیری نیست
r.seconds.forEach(function (row, i) {
var best = row.indexOf(Math.min.apply(null, row.filter(function (v) { return v !== null; })));
console.log('راننده', i, '→ نزدیکترین سفارش', best);
});
});
ماتریس مسافت اختیاری است و با
{ includeDistance: true } فعال میشود؛ چون گراف برای متریکِ
زمان پیشمحاسبه شده، محاسبهٔ مسافت مسیر کندتری دارد و برای ماتریسهای بزرگ توصیه نمیشود.
هزینه بر اساس تعداد خانهها (مبدأها × مقصدها) حساب میشود.
تصویر ثابت نقشه
یک PNG از نقشه، رندرشده روی سرور — برای ایمیل، فاکتور، PDF و هر جایی که جاوااسکریپت اجرا نمیشود.
<img src="https://map.bitjob.ir/api/v1/staticmap?key=YOUR_KEY¢er=35.6892,51.3890&zoom=13&size=600x400&markers=35.6892,51.3890,red" />
یا از داخل جاوااسکریپت:
var url = AlooMap.staticMapUrl({
center: [35.6892, 51.3890],
zoom: 13,
size: [600, 400],
theme: 'dark',
markers: [[35.6892, 51.3890, 'red'], [35.7448, 51.3750, 'blue']],
path: routeResult.latlng // خروجی AlooMap.route را مستقیم بدهید
});
| پارامتر | توضیح |
|---|---|
center | lat,lng — اجباری |
zoom | ۰ تا ۱۸ (پیشفرض ۱۲) |
size | عرضxارتفاع بر حسب پیکسل (پیشفرض 600x400) |
theme | dark برای تم شب |
markers | lat,lng[,رنگ] جدا شده با | |
path | lat,lng جدا شده با | — خط مسیر |
روی تصویر ثابت عمداً برچسب متنی رسم نمیشود: شکلدهی راستبهچپِ فارسی روی رستر سمت سرور نتیجهٔ بدی میدهد و نقشهٔ بیبرچسبِ تمیز بهتر از برچسبهای شکسته است.
تم شب
سادهترین راه، پارامتر theme روی خود آدرس اسکریپت است:
<!-- همیشه شب --> <script src="https://map.bitjob.ir/map.js?key=YOUR_KEY&theme=dark"></script> <!-- پیروی از تنظیم سیستم کاربر --> <script src="https://map.bitjob.ir/map.js?key=YOUR_KEY&theme=auto"></script>
یا هنگام ساخت نقشه، و جابهجایی در زمان اجرا:
AlooMap.ready(function () {
var layer = AlooMap.tileLayer({ theme: 'dark' });
var map = L.map('map').setView([35.6892, 51.3890], 12);
layer.addTo(map);
// بعداً، مثلاً با کلید تغییر تم سایت:
AlooMap.setTheme(layer, 'light');
});
۱۳) مرجع REST API
آدرس پایه: https://map.bitjob.ir — همهٔ endpointها CORS باز دارند.
| متد و مسیر | ورودی | خروجی |
|---|---|---|
POST /api/v1/sessions |
{ key, page } |
{ token, expiresAt, cost, balance, maxZoom, styleUrl, darkStyleUrl } — ساخت سشن لود نقشه (هزینهدار) |
GET /api/v1/sessions/status |
?key=…&t=TOKEN |
{ ok } — بررسی رایگان سشن ذخیرهشده؛ 402 یعنی اعتبار تمام شده، 401 یعنی سشن تازه لازم است |
POST /api/v1/route |
{ key, fromLat, fromLng, toLat, toLng } یا { key, points: [[lat,lng], …], profile } |
{ ok, meters, seconds, latlng, steps, balance } — محاسبه مسیر (هزینهدار؛ هزینه بهازای هر بخش) |
POST /api/v1/matrix |
{ key, sources, targets, profile, includeDistance } |
{ ok, seconds, meters, balance } — ماتریس فاصله (هزینه بهازای هر خانه) |
GET /api/v1/autocomplete |
key, session, q, limit, lat, lng |
{ ok, results, charged, balance } — صورتحساب سشنی: یک بار بهازای هر سشن |
GET /api/v1/search |
key, q, limit, lat, lng |
{ ok, results: [{ id, name, nameEn, kind, category, context, lat, lng, distanceMeters }], balance } |
GET /api/v1/reverse |
key, lat, lng |
{ ok, result, balance } — نزدیکترین مکان شناختهشده |
GET /api/v1/staticmap |
key, center, zoom, size, theme, markers, path |
تصویر image/png (هزینهدار) |
GET /api/map/style.json?t=TOKEN |
توکن سشن، و theme=dark برای تم شب |
استایل MapLibre با آدرس مطلق تایل و گلیف |
GET /api/map/{z}/{x}/{y}.pbf?t=TOKEN |
توکن سشن | تایل وکتور MVT؛ 204 یعنی تایل خالی (بدون داده) |
GET /api/map/info |
— | وضعیت فایل نقشه: available, minZoom, maxZoom, bounds, ... |
GET /healthz |
— | { ok: true } — فقط زنده بودن پروسه |
GET /readyz |
— | وضعیت واقعی سرویس: فایل نقشه، گراف مسیریابی و دیتابیس |
همهٔ endpointها سقف نرخ درخواست دارند؛ عبور از آن پاسخ 429 با
هدر Retry-After میدهد.
اسکریپت map.js خودش سشن میسازد و توکن را مدیریت میکند؛ بهندرت لازم است
/api/v1/sessions را دستی صدا بزنید (مثلاً وقتی میخواهید مستقیم با MapLibre کار کنید).
۱۴) خطاها و کدهای وضعیت
بدنهٔ همهٔ خطاها به شکل { "error": "توضیح فارسی" } است.
| کد | معنی | راهحل |
|---|---|---|
400 | کلید API ارسال نشده، یا مختصات نامعتبر است | پارامتر key و بازهٔ مختصات را بررسی کنید |
401 | کلید نامعتبر یا غیرفعال است | کلید را از داشبورد دوباره بگیرید / فعال کنید |
402 | اعتبار سرویس نقشه تمام شده است | از صفحهٔ شارژ حساب را شارژ کنید |
403 |
دامنهٔ درخواست جزو دامنههای مجاز کلید نیست، یا کلید عمومیِ دامنهدار بدون هدر Origin صدا زده شده است | دامنه را در داشبورد به کلید اضافه کنید؛ برای فراخوانی سرور-به-سرور کلید سرّی بسازید |
422 | مسیر پیدا نشد، یا مبدأ/مقصد به هیچ جادهای نزدیک نیست | نقطه را روی جادهای معتبر و داخل ایران انتخاب کنید |
429 | تعداد درخواست از سقف نرخ گذشته است | هدر Retry-After را ببینید و درخواستها را پخش کنید |
503 | گراف مسیریابی یا نمایهٔ جستجو روی سرور در دسترس نیست | موقتی است؛ بعداً دوباره تلاش کنید |
مدیریت خطا در کد
alooRoute(a, b)
.then(drawRoute)
.catch(function (err) {
// پیامهای سرویس فارسی و قابل نمایش مستقیم به کاربرند
document.getElementById('routeError').textContent = err.message;
});
۱۵) هزینهها و سشن
| عملیات | هزینه | توضیح |
|---|---|---|
| لود نقشه (ساخت سشن جدید) | 23 تومان | یک بار در هر مرورگر، تا انقضای سشن |
| هر بخش مسیریابی | 39 تومان | مسیر ساده = ۱ بخش؛ مسیر با ۲ توقف = ۳ بخش |
| هر خانهٔ ماتریس | 20 تومان | مبدأها × مقصدها (حداکثر 2,500 خانه) |
| هر جستجو یا ژئوکدینگ معکوس | 39 تومان | /api/v1/search و /api/v1/reverse |
| هر سشن تکمیل خودکار | 39 تومان | یک بار برای کل تایپ کاربر، نه بهازای هر ضربهٔ کلید |
| هر تصویر ثابت نقشه | 23 تومان | /api/v1/staticmap |
| تایل، استایل، فونت | رایگان | داخل سشن معتبر، بدون سقف |
برای برآورد هزینهٔ ماهانه از ماشینحساب تعرفهها استفاده کنید.
سشن 6 ساعت معتبر است و در localStorage مرورگر کاربر
ذخیره میشود؛ بنابراین رفرشکردن صفحه، جابهجایی بین صفحات سایت و باز کردن تب جدید
هزینهٔ تازه ندارند. فقط انقضای سشن یعنی یک لود جدید.
با این حال، هر لود صفحه سشن ذخیرهشده را (رایگان) با سرور میسنجد: اگر موجودی به هزینهٔ یک لود
نرسد یا کلید غیرفعال شود، نقشه بهجای نمایش، پیام شارژ حساب را نشان میدهد.
موجودی و ریز تراکنشها را در داشبورد میبینید — به همراه
نمودار مصرف ۳۰ روز اخیر به تفکیک کلید و نوع درخواست. مقدار balance در پاسخ
هر endpoint هزینهدار هم موجودی لحظهای را برمیگرداند. برای اینکه سرویس بیخبر قطع نشود،
در داشبورد آستانهٔ هشدار موجودی کم را تنظیم کنید.
۱۶) محدودیتها و نکات مهم
- پوشش جغرافیایی: فقط ایران. نقاط خارج از مرز روی نقشه خالیاند و مسیریابی برایشان
422میدهد. - بیشترین زوم: 18.
- پروفایل مسیریابی:
car، و در صورت ساختهشدن گراف،pedestrianوbicycle. فهرست دقیقِ در دسترس در/readyzمیآید. - شکل مسیر: برای مسیرهای خیلی طولانی تا حداکثر ۱۵۰۰ نقطه نازکسازی میشود (شکل مسیر سادهتر، ولی مسافت و زمان دقیق باقی میماند).
- بدون ترافیک: زمان بر اساس سرعت مجاز جاده محاسبه میشود، نه ترافیک لحظهای.
-
دقت جستجو: نمایه از داده OpenStreetMap ساخته میشود و شامل شهر، روستا، محله،
خیابان و مراکز شناختهشده است — نه پلاک و کد پستی. نتایج با نوع مکان، و در صورت
دادن
nearبا نزدیکی، مرتب میشوند. - تصویر ثابت: بدون برچسب متنی رندر میشود (شکلدهی فارسی روی رستر سمت سرور نتیجهٔ خوبی نمیدهد).
- ترتیب مختصات: در Leaflet و این API
[lat, lng]، در MapLibre و GeoJSON[lng, lat]. - HTTPS: برای موقعیتیابی کاربر الزامی است.
- داده: بر پایهٔ OpenStreetMap؛ متن انتساب را حذف نکنید مگر انتساب معادلی نمایش دهید.
۱۷) نمونهٔ کامل — نقشه + انتخاب مبدأ/مقصد + مسیر و مسافت
این فایل را کامل کپی کنید، فقط YOUR_API_KEY را عوض کنید:
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>مسیریاب من</title>
<style>
body { font-family: Tahoma, sans-serif; margin: 0; padding: 16px; }
#map { height: 70vh; border-radius: 12px; }
.bar { margin-bottom: 10px; font-size: 14px; }
.bar b { color: #1a73e8; }
</style>
</head>
<body>
<div class="bar">
روی نقشه کلیک کنید: اولین کلیک مبدأ، دومین کلیک مقصد.
<span id="out"></span>
<button id="reset">شروع دوباره</button>
</div>
<div id="map"></div>
<script src="https://map.bitjob.ir/map.js?key=YOUR_API_KEY"></script>
<script>
var KEY = 'YOUR_API_KEY';
function fmtDistance(m) {
return m < 1000 ? Math.round(m) + ' متر' : (m / 1000).toFixed(1) + ' کیلومتر';
}
function fmtDuration(s) {
var h = Math.floor(s / 3600), m = Math.round((s % 3600) / 60);
return (h ? h + ' ساعت و ' : '') + Math.max(1, m) + ' دقیقه';
}
AlooMap.createMap('map', { center: [35.6892, 51.3890], zoom: 11 }).then(function (map) {
var from = null, to = null, fromPin = null, toPin = null, line = null;
var out = document.getElementById('out');
function reset() {
[fromPin, toPin, line].forEach(function (l) { if (l) map.removeLayer(l); });
from = to = fromPin = toPin = line = null;
out.textContent = '';
}
document.getElementById('reset').onclick = reset;
map.on('click', function (e) {
if (from && to) reset();
if (!from) {
from = e.latlng;
fromPin = L.marker(from, { draggable: true }).addTo(map).bindPopup('مبدأ').openPopup();
out.textContent = 'حالا مقصد را انتخاب کنید…';
return;
}
to = e.latlng;
toPin = L.marker(to, { draggable: true }).addTo(map).bindPopup('مقصد');
out.textContent = 'در حال محاسبه مسیر…';
fetch(AlooMap.base + '/api/v1/route', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: KEY,
fromLat: from.lat, fromLng: from.lng,
toLat: to.lat, toLng: to.lng
})
})
.then(function (res) {
return res.json().then(function (d) {
if (!res.ok) throw new Error(d.error || 'خطای مسیریابی');
return d;
});
})
.then(function (r) {
line = L.polyline(r.latlng, { color: '#1a73e8', weight: 5 }).addTo(map);
map.fitBounds(line.getBounds(), { padding: [40, 40] });
out.innerHTML = 'مسافت: <b>' + fmtDistance(r.meters) + '</b> — ' +
'زمان تقریبی: <b>' + fmtDuration(r.seconds) + '</b> — ' +
'فاصله مستقیم: ' + fmtDistance(map.distance(from, to));
})
.catch(function (err) { out.textContent = err.message; });
});
});
</script>
</body>
</html>
۱۸) عیبیابی
| مشکل | علت و راهحل |
|---|---|
| نقشه دیده نمیشود / کادر خالی است | عنصر نقشه ارتفاع ندارد. برایش height بگذارید. |
| نقشه نصفه یا خاکستری رندر شده | نقشه در حالت مخفی (تب/مودال/آکاردئون) ساخته شده؛ بعد از نمایانشدن map.invalidateSize() را صدا بزنید. |
| خطای «AlooMap هنوز آماده نیست» | AlooMap.map() را بیرون از ready صدا زدهاید؛ یا داخل ready ببرید یا از createMap استفاده کنید. |
| پیام «این دامنه برای کلید API مجاز نیست» | دامنهٔ فعلی در فهرست دامنههای مجاز کلید نیست (localhost هم باید اضافه شود یا فهرست خالی بماند). |
| «اعتبار سرویس نقشه تمام شده است» | موجودی کمتر از هزینهٔ یک لود است؛ حساب را شارژ کنید. |
مسیریابی 422 میدهد |
نقطه بیرون از ایران است یا هیچ جادهای تا شعاع ۵ کیلومتریاش نیست؛ نقطه را نزدیکتر به جاده انتخاب کنید. |
| مارکر پیشفرض بدون تصویر است | استایل Leaflet را دستکاری کردهاید یا CSS سایت آن را override میکند؛ از L.icon با تصویر خودتان استفاده کنید. |
| حروف فارسی روی نقشه بههمریخته است | پلاگین RTL خودکار لود میشود؛ اگر MapLibre را مستقیم میسازید، خودتان setRTLTextPlugin را صدا بزنید. |
| موقعیت کاربر گرفته نمیشود | سایت باید روی HTTPS باشد و کاربر اجازه بدهد؛ داخل iframe نیاز به allow="geolocation" دارد. |
سؤالی باقی مانده؟ از داشبورد با نمونهٔ کد و پیام خطا تماس بگیرید.