نقشه سیدار مپ و API های آن
در جلسه گذشته به نحوه نمایش نقشه Google Maps در اندروید پرداختیم. به دلیل محدودیتهایی که به واسطه پرداخت ارزی جهت استفاده از API های گوگل مپ برای توسعه دهندگان و برنامه نویسان اندرویدی داخل ایران وجود دارد تصمیم گرفتیم نحوه کار با یکی از سرویس دهندههای نقشه ایرانی را آموزش دهیم. در این جلسه و جلسات بعد به آموزش استفاده از نقشه سیدار مپ (CedarMaps) در اندروید میپردازیم.
- معرفی نقشه سیدار مپ و API های آن
- نحوه اضافه کردن آنلاین و آفلاین کتابخانه SDK سیدار مپ در اندروید استودیو و رفع مشکل احتمالی
- مجوز یا Permission های مورد نیاز برای استفاده از سیدار مپ و API ها
- نحوه استفاده از ClientID و ClientSecret دریافتی از پشتیبانی سیدار مپ
- استفاده از منوی BottomNavigationView برای نمایش فرگمنتها
- دریافت مجوز دسترسی به موقعیت مکانی توسط متد MapBox
- پیاده سازی API مکان یابی و نمایش نقشه
- پیاده سازی API نقطه یابی (Reverse Geocoding) و تبدیل نقطه به آدرس
- پیاده سازی API جستجو بر اساس نام مکان (Forward Geocoding)
- پیاده سازی API مسیریابی و تخمین مسافت (Direction)
تصاویر نهایی پروژه:
این جلسه در قالب PDF و در ۶۹ صفحه تهیه شده که در ادامه چند صفحه ابتدایی را مشاهده میکنید:
چرا سیدار مپ؟
اگر آموزش جلسه قبل را مطالعه کرده باشید گفتیم که مدتیست گوگل سیاست خود در قبال ارائه API های مرتبط با Google Map را تغییر داده و برای استفاده از پلن رایگان و محدود هم نیاز به احراز هویت از طریق Billing دارد که تهیه آن برای اکثر برنامه نویسان و توسعه دهندگان داخل ایران ممکن نیست. ضمن اینکه هر لحظه ممکن است تحریمهای جدیدی روی این سرویسها اعمال شده و به عنوان مثال حتی امکان استفاده رایگان از SDK نقشه هم وجود نداشته باشد. بنابراین انتخاب منطقی این است که از سایر گزینههای روی میز استفاده کنیم!
سرویسهای داخلی متعددی در حال حاضر بر سر ارائه API نقشه به رقابت با یکدیگر پرداختهاند که میتوان به سیدار مپ، نشان و مپ اشاره کرد. هرکدام از این سرویسها SDK مربوط به اندروید را در اختیار توسعه دهندگان قرار دادهاند که با اضافه کردن آن به پروژه میتوان از API ها و قابلیتهای مختلف از جمله نمایش موقعیت مکانی روی نقشه، مکان یابی، مسیریابی و تبدیل نقطه جغرافیایی به آدرس استفاده کرد.
هرکدام از این سرویس دهندهها مزایا و معایبی داشته و علاوه بر آن در نحوه تعرفه گذاری نیز تفاوتهایی دارند.
تصویر فوق مربوط به تعرفههای سیدار مپ در زمان تهیه این آموزش است. پلن رایگان آن شامل ۲۰۰۰ API Call در روز است. یعنی تا ۲۰۰۰ درخواست از سمت کاربران برنامه ما بطور رایگان پاسخ داده میشود. این درخواستها شامل نمایش موقعیت مکانی کاربر، تبدیل موقعیت جغرافیایی به آدرس (شامل استان، شهر، منطقه، خیابان، کوچه و…)، مسیریابی و تخمین مسافت خواهد بود که توسط کاربران برنامه ارسال میشود.
همچنین در پلن رایگان نمایش و لود نقشه تا سقف ۱۰۰۰۰ مرتبه در روز امکان پذیر است. بنابراین واضح است که صرف نمایش نقشه روی برنامه، یک API Call محسوب نمیشود و فقط مصرف پهنای باند را در پی خواهد داشت که در این پلن تا ۱۰۰ گیگابایت در ماه تعیین شده.
با توجه به مشخصات پلن رایگان CedarMap میتوان گفت برای اکثر اپلیکیشنهای با مخاطب کم، همین پلن کفایت کرده و حداقل برای ابتدای کار نیازی به ارتقاء به پلنهای بالاتر و پرداخت هزینه نیست. برای استفاده از پلن رایگان باید درخواست خود را در قسمت مربوطه داخل سایت سیدار مپ ارسال کنید. معمولا در کمتر از ۲۴ ساعت اطلاعات مورد نیاز برای استفاده از نقشه به ایمیل شما ارسال خواهد شد. این اطلاعات شامل یک Client ID و Client Secret است که باید در پروژه تعریف شود.
اگر صفحه نخست وب سایت سیدار مپ را بررسی کرده باشید عمده تمرکز آن روی مقایسه نقشه گوگل و سیدار مپ است. از جمله خوانایی و زیبایی، سرعت، جزئیات نقشه، دقت بیشتر در مسیریابی و… که در تمام موارد برتری سیدار مپ نتیجه گرفته شده. البته که بنده تمام این برتریها را تایید نمیکنم و در برخی موارد اغراق شده. حتی در تستها مشخص شد در برخی موارد از جمله تبدیل موقعیت جغرافیایی به آدرس (بخصوص در مناطقی غیر از کلان شهرها) سیدار مپ از دقت کمتری برخوردار بوده و هنوز تا رسیدن به حد مطلوب جای کار دارد.
با اینحال از نظر بنده و در مقایسهای که بین چند سرویس دهنده داخلی انجام دادم در نهایت این سرویس را انتخاب کردم.
پیاده سازی نقشه سیدار مپ در اپلیکیشن اندرویدی
در صفحه مستندات نقشه سیدار مپ اطلاعات مربوط به نحوه پیاده سازی نقشه و API های آن در اندروید، iOS و وب در دسترس توسعه دهندگان قرار گرفته است:
با کلیک روی گزینه Android به صفحه SDK سیدار مپ برای پلتفرم اندروید در گیت هاب منتقل میشوم:
https://github.com/cedarstudios/cedarmaps-android-sdk
در این صفحه توضیحاتی در خصوص نحوه اضافه کردن کتابخانه SDK سیدار مپ به پروژه اندرویدی در اندروید استودیو توضیحاتی ارائه شده. همچنین به این نکته اشاره شده که SDK سیدار مپ بر پایه SDK مپ باکس (MapBox) ساخته شده و امکاناتی به آن اضافه شده است. MapBox خود یک سرویس دهنده نقشه جایگزین گوگل مپ بوده که البته برخلاف سیدار مپ ایرانی نیست.
بیشتر از این درگیر مباحث تئوری نشده و در ادامه آموزش به نحوه کار با نقشه سیدار مپ در اندروید استودیو میپردازم.
ساخت پروژه اندرویدی سیدار مپ
در این پروژه قصد داریم در قالب چند فرگمنت، امکانات و API های مختلف نقشه سیدار مپ را آموزش دهیم. طبق آموزش ساخت پروژه در اندروید استودیو یک پروژه با نام CedarMap با یک Empty Activity و زبان Java ایجاد میکنم.
ابتدا مطابق توضیحات موجود در صفحه گیت هاب سیدار مپ اندروید، هردو فایل build.gradle سطح project و app را ویرایش میکنم. در قدم نخست و در build.gradle سطح پروژه، آدرس مخزن سیدار مپ را در بلاک repositories زیرمجموعه بلاک allprojects اضافه میکنم:
build.gradle (Project)
buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.5.3' } } allprojects { repositories { jcenter() google() maven { url "https://repo.cedarmaps.com/android/" } } } task clean(type: Delete) { delete rootProject.buildDir }
در قدم بعد فایل build.gradle سطح app را ویرایش میکنم. برای جلوگیری از بروز خطای مربوط به نسخه جاوا، مطابق توضیحات موجود در صفحه گیت هاب سیدار مپ یک بلاک با نام compileOptions به بلاک android باید اضافه شود:
build.gradle (app)
android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { applicationId "ir.android_studio.cedarmap" minSdkVersion 19 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
در ادامه کتابخانههای مورد نیاز برای نقشه را در بلاک dependencies اضافه و پروژه را سینک میکنم:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.cedarmaps:CedarMapsSDK:4.2.1' implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0' implementation 'com.google.android.material:material:1.2.0-alpha04' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' }
کتابخانه اصلی و ضروری سیدار مپ مورد اول یعنی CedarMapsSDK است که مربوط به SDK نقشه میباشد. ۳ مورد دیگر به ترتیب برای استفاده از سایر امکانات SDK مپ باکس، اضافه کردن دکمه FAB و Bottom Navigation و ترسیم تصاویر وکتوری تعریف شدهاند.
البته ممکن است با رعایت نکات فوق باز هم موفق به سینک کردن پروژه نشوید. مانند آنچه برای من اتفاق افتاد! مکاتبات زیادی هم با بخش پشتیبانی سیدار مپ داشتم اما ظاهرا نه سرور ایرادی دارد نه اندروید استودیو من. بنابراین تنها پیشنهادی که ارائه شد اضافه کردن آفلاین کتابخانه CedarMapsSDK بود که لینک دانلود آن را در ایمیل ارسال کردند.
نحوه افزودن آفلاین کتابخانهها در اندروید استودیو را قبلا در مطلب اکتیویتیها در اندروید توضیح دادهام. ابتدا فایل .jar یا .aar کتابخانه در پوشه app>libs قرار میگیرد:
SDK سیدار مپ از سه کتابخانه دیگر استفاده میکند. بنابراین لازم است در حالت نصب آفلاین کتابخانه سیدار مپ، این ۳ کتابخانه را جداگانه تعریف کنیم که به ترتیب mapbox-android-sdk، okhttp و gson هستند. در نهایت لیست کتابخانهها به اینصورت است:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation files('libs/CedarMapsSDK-4.2.1.aar') api 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.4.0' implementation 'com.squareup.okhttp3:okhttp:3.12.6' implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v7:0.6.0' implementation 'com.google.android.material:material:1.2.0-alpha04' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' }
حالا پروژه را سینک میکنم. در حالت آفلاین از ابزارهای تغییر آیپی مانند FOD هم میتوان استفاده کرد زیرا دیگر نیازی به اتصال به سرور سیدار مپ نداریم.
در صورتی که نسخه جدیدتری از CedarMapsSDK منتشر شده بود برای دریافت فایل نسخه جدید به پشتیبانی سیدار مپ ایمیل زده و یا ورژن را در لینک زیر جایگزین کنید:
https://repo.cedarmaps.com/android/com/cedarmaps/CedarMapsSDK/4.2.1/CedarMapsSDK-4.2.1.aar
حالا پروژه را سینک میکنم.
در مرحله بعد باید دسترسیهای مورد نیاز نقشه سیدار مپ را در مانیفست پروژه تعریف کنیم که در مطلب پیاده سازی Runtime Permission در اندروید به طور مفصل با پرمیشنها آشنا شدیم.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ir.android_studio.cedarmap"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
دو مجوز دسترسی به اینترنت و موقعیت مکانی در مانیفست تعریف شد. در صورتی که بخواهیم مانند جلسه قبل صرفا یک موقعیت جغرافیایی از پیش تعیین شده را روی نقشه نشان دهیم مجوز دسترسی به اینترنت برای دریافت تایلهای نقشه و نمایش آنها کفایت میکند. اما ما به دریافت موقعیت فعلی کاربر هم نیاز داریم.
در توضیحات صفحه گیت هاب قید شده که ClientID و ClientSecret بهتر است در یک subclass از کلاس Application پروژه تعریف شود. بنابراین یک کلاس با نام دلخواه CedarID به پروژه اضافه کرده و آنرا از کلاس Application ارث بری میکنم. سپس متد onCreate() را به کلاس اضافه و کد مربوطه را درون متد قرار میدهم:
CedarID.java
package ir.android_studio.cedarmap; import android.app.Application; import com.cedarstudios.cedarmapssdk.CedarMaps; import com.cedarstudios.cedarmapssdk.model.MapID; public class CedarID extends Application { @Override public void onCreate() { super.onCreate(); CedarMaps.getInstance() .setClientID("YOUR_CLIENT_ID") .setClientSecret("YOUR_CLIENT_SECRET") .setContext(this) .setMapID(MapID.MIX); } }
کافیست در کد فوق، دو کد دریافتی از سیدار مپ را جایگزین کنید. توجه داشته باشید subclass کلاس Application باید درون مانیفست تعریف شود. برای اینکار کافیست ویژگی name به تگ application مانیفست اضافه شود که مقدار آن، نام کلاس است:
<application android:name=".CedarID" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
در layout اکتیویتی یک FrameLayout برای نمایش فرگمنتها و یک BottomNavigationView برای نمایش منوی پایین صفحه تعریف میکنم:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <FrameLayout android:id="@+id/content" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </FrameLayout> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/navigationView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="?android:attr/windowBackground" app:labelVisibilityMode="labeled" app:menu="@menu/navigation" /> </LinearLayout>
منوی BottomNavigation شامل ۴ آیتم است که با نام navigation.xml و در قالب یک menu در پروژه تعریف شده:
navigation.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_map" android:icon="@drawable/ic_map_marker" android:title="نقشه" /> <item android:id="@+id/navigation_reverse" android:icon="@drawable/ic_reverse_geocode" android:title="نقطه یابی" /> <item android:id="@+id/navigation_forward" android:icon="@drawable/ic_forward_geocode" android:title="جستجو" /> <item android:id="@+id/navigation_direction" android:icon="@drawable/ic_direction" android:title="مسیریابی" /> </menu>
حالا در اکتیویتی کدهای لازم را اضافه میکنم:
package ir.android_studio.cedarmap; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import com.google.android.material.bottomnavigation.BottomNavigationView; public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
ابتدا کلاس اکتیویتی را implement میکنم به BottomNavigationView.OnNavigationItemSelectedListener. سپس با قرار دادن نشانگر موس روی بدنه کلاس و زدن alt + enter متد onNavigationItemSelected را به کلاس اضافه میکنم:
MainActivity.java
package ir.android_studio.cedarmap; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.MenuItem; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.mapbox.mapboxsdk.geometry.LatLng; public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { public static final LatLng VANAK_SQUARE = new LatLng(35.7572, 51.4099); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BottomNavigationView navigation = findViewById(R.id.navigationView); navigation.setOnNavigationItemSelectedListener(MainActivity.this); navigation.setSelectedItemId(R.id.navigation_map); } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { return false; } }
یک LatLng با نام VANAK_SQUARE تعریف شده که مختصات میدان ونک تهران در آن ذخیره شده. از این نقطه برای نمایش محل پیش فرض نقشه استفاده خواهیم کرد.
درون متد onCreate() اکتیویتی ویجت BottomNavigation را تعریف کرده و همچنین مشخص کردم آیتم با شناسه navigation_map به صورت پیش فرض و هنگام اجرای اپلیکیشن انتخاب شده باشد که مربوط به فرگمنت نمایش نقشه و موقعیت مکانی فرد خواهد بود.
متد onNavigationItemSelected را به صورت زیر تکمیل میکنم:
@Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { Fragment fragment = null; switch (item.getItemId()) { case R.id.navigation_map: setTitle("نقشه"); fragment = new MapFragment(); break; case R.id.navigation_reverse: setTitle("نقطه یابی"); fragment = new ReverseGeocodeFragment(); break; case R.id.navigation_forward: setTitle(""); fragment = new ForwardGeocodeFragment(); break; case R.id.navigation_direction: setTitle("مسیریابی"); fragment = new DirectionFragment(); break; } if (fragment != null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (getSupportFragmentManager().getFragments().isEmpty()) { transaction.add(R.id.content, fragment, String.format(Locale.US, "item: %d", item.getItemId())).commit(); } else { transaction.replace(R.id.content, fragment, String.format(Locale.US, "item: %d", item.getItemId())).commit(); invalidateOptionsMenu(); } return true; } return false; } با لمس هریک از آیتمهای منو، فرگمنت مربوطه اجرا خواهد شد. دسترسی به موقعیت جغرافیایی کاربر جزء مجوزهای خطرناک (Dangerous Permissions) بحساب میآید بنابراین طبق مبحث Runtime Permission باید برای اندروید ۶ و به بالا هنگام اجرای برنامه مجوز مربوطه را از کاربر دریافت کنیم. برای اینکار متد onRequestPermissionsResult را در کلاس اکتیویتی Override میکنم: @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
متد را به صورت زیر تکمیل میکنم:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { List allFragments = getSupportFragmentManager().getFragments(); if (allFragments.isEmpty()) { return; } Fragment currentFragment = (Fragment) allFragments.get(allFragments.size() - 1); if (currentFragment instanceof PermissionsListener) { currentFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
ابتدا بررسی میشود آیا فرگمنتی وجود دارد یا نه. در صورتی که جواب منفی بود (یعنی allFragments برابر با isEmpty باشد) هیچ کاری انجام نشده و return میشود. در ادامه شرطی را تعریف میکنم تا صرفا هنگامی مجوز دسترسی به موقعیت مکانی از کاربر دریافت شود که به آن نیاز داشته باشیم. به عنوان مثال در API جستجو بر اساس نام مکان و یا مسیریابی، نیازی به موقعیت کاربر نیست و چنانچه شخص صرفا از این قابلیت استفاده کند منطقی نیست دسترسی موقعیت را درخواست کنیم.
ابتدا فرگمنت در حال اجرا در currentFragment قرار میگیرد. سپس بررسی میکنیم اگر از فرگمنت موجود در currentFragment درخواست مجوز دسترسی ارسال شده، پیغام مربوطه را به کاربر نمایش بده تا دسترسی را تایید یا لغو کند. در اینجا خبری از کدهای طولانی Runtime Permission پیش فرض اندروید نیست و مدیریت اینکار توسط اینترفیس PermissionsListener انجام میشود که مربوط به MapBox است. در واقع MapBox توسعه دهنده را از نوشتن کدهای اضافی بی نیاز کرده. البته استفاده از این قابلیت مپ باکس الزامی نبوده و میتوان به صورت عادی و طبق آنچه در مبحث مربوط به Runtime Permission تمرین کرده بودیم مجوز دسترسی به موقعیت جغرافیایی را هنگام اجرای برنامه یا هنگام اجرای فرگمنت مدنظر از کاربر دریافت کنیم.
if (currentFragment instanceof PermissionsListener)
در خط فوق بررسی میشود آیا فرگمنت ذخیره شده در متغیر currentFragment از جنس PermissionsListener هست یا نه. به عبارت دیگر بررسی میشود آیا در فرگمنت فعلی مجوزی نیاز هست که مربوط به MapBox باشد یا خیر. به طور خلاصه دستور instanceof در جاوا وظیفه برگرداندن نتیجه مقایسه جنس دو آیتم را بر عهده دارد.
کد نهایی اکتیویتی:
MainActivity.java
package ir.android_studio.cedarmap; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import android.os.Bundle; import android.view.MenuItem; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.mapbox.android.core.permissions.PermissionsListener; import com.mapbox.mapboxsdk.geometry.LatLng; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { public static final LatLng VANAK_SQUARE = new LatLng(35.7572, 51.4099); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BottomNavigationView navigation = findViewById(R.id.navigationView); navigation.setOnNavigationItemSelectedListener(MainActivity.this); navigation.setSelectedItemId(R.id.navigation_map); } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { Fragment fragment = null; switch (item.getItemId()) { case R.id.navigation_map: setTitle("نقشه"); fragment = new MapFragment(); break; case R.id.navigation_reverse: setTitle("نقطه یابی"); fragment = new ReverseGeocodeFragment(); break; case R.id.navigation_forward: setTitle(""); fragment = new ForwardGeocodeFragment(); break; case R.id.navigation_direction: setTitle("مسیریابی"); fragment = new DirectionFragment(); break; } if (fragment != null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (getSupportFragmentManager().getFragments().isEmpty()) { transaction.add(R.id.content, fragment, String.format(Locale.US, "item: %d", item.getItemId())).commit(); } else { transaction.replace(R.id.content, fragment, String.format(Locale.US, "item: %d", item.getItemId())).commit(); invalidateOptionsMenu(); } return true; } return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { List allFragments = getSupportFragmentManager().getFragments(); if (allFragments.isEmpty()) { return; } Fragment currentFragment = (Fragment) allFragments.get(allFragments.size() - 1); if (currentFragment instanceof PermissionsListener) { currentFragment.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید
توجه : سورس پروژه درون پوشه Exercises قرار دارد
با توجه به اینکه آموزشهای پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینههای وب سایت و تهیه آموزشهای آتی میشود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.
تعداد صفحات : ۶۹
حجم : ۳ مگابایت
قیمت : ۳۶ هزار تومان
توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.
افزودن به سبد دانلود درگاه پشتیبان
سلام تشکر بابت آموزشهای مفیدتون – تقریبا صفحه ۸ فایل pdf این آموزش از تنطیمات build.gradle (Project) گفته شده که در ورژن ۲۰۲۱٫۲٫۱ ، allprojects و .. وجود نداره . باید خودمون بهش اضافه کنیم ؟
تغییرات رو در مستندات موجود در وب سایت سیدارمپ بررسی کنید
سلام من این بسته رو خریدم ولی کار نمیکنه
چی کار نمیکنه؟؟
سلام ببخشید یه سوال داشتم،من میخوام از apiمسیریابی استفاده کنم گوگل کلود که نمیشه از سیدار یاopenstressmap استفاده کنم؟اگه هیچکدوم چیکار باید بکنم؟
سیدار یا OSM که فرمودید گزینه های مناسبی هستن
سلام استاد
منظورتون از ابزار ، چیه؟
میشه بنده رو راهنمایی کنید.
سیدارمپ این کار رو انجام میده؟
سیدارمپ که خودش تایلها رو نمایش میده.
یه نمونهش سرویس mapbox.com هست که از OSM استفاده می کنه
سلام استاد
خسته نباشید
ممنونم از آموزش های خوبتون
استاد من میخوام از نقشه توی یک دیوایس که داخل اتومبیل کار گداشته میشه استفاده کنم.
در واقع قراره مسیریابی کنه،موقعیت ماشین رو تشخیص بده و…
به نظرتون باید از سیدارمپ استفاده کنم یا OSM؟
راستش تفاوت این دو تا رو متوجه نشدم.میشه توضیح بدید واسم؟
OSM فقط تایلهای نقشه رو در اختیار میذاره و لازمه از ابزار دیگه ای در کنارش برای نمایش نقطه و مسیریابی استفاده کنید
سلام
من سورس رو خریداری کردم باگ داره ، کتابخانه سیدار مپ رو هم آفلاین و آنلاین تست کردم با فیلتر شکن هم تست کردم باز همون خطارو میده ، خطا مربوط به شناسایی خود نقشه هست . MapFragment.java خط ۶۳ . لطفا یا یک فایل نصبی برام ارسال کنید ببینم حداقل توی سیستم شما کار میکنه اشکال از من هست و یا بفرمایید که چطور میشه این خطا رو رفع کرد هرچند دارم دنبال راهکار های دیگه ای میگردم . حتما فایل نصبی رو برام ارسال کنید .
hosinabbasy@gmail.com
لطفا خطایی که گرفتید رو بگید
باسلام بسیار تشکر میکنم از سایت زیباتون ، خیلی کاربردی و مفید بود زنده باشید .
سید جان این نقشه OpenStreetMap رو میشه ردیف کنی یا هر نقشه رایگان دیگه رو بندازم تو \روژه بیاد بالا تموم بشه اسیر شدم خدایی
تو برنامه هستش ولی فعلا چندتا مبحث دیگه تو برنامه هست که باید اول اونها رو آماده کنم
سلام وقتتون بخیر این هم که سرکاری بود اکانت رایگان نداره مبلغ سرویس هاشم از ۹۵۰۰۰۰ تومان شروع میشه
خیر بزرگوار سرکاری درکار نیست! چند مدت بعد تهیه این آموزش متاسفانه پلن رایگان رو حذف کردن از سرویسشون و هنوز هم اضافه نشده مجدد
علت سینک نشدن پروژه در جزوه pdf توضیح داده نشده بود. به خاطر این بود که ویندوز ۳۲ بیت بود و gradle properties اخطار می داد ۲ روز search کردم تا اون رو رفعش کردم حالا رسیدم به گیر بعدی
کار شسته و رفته کلا تو هیچ پیج داخلی نیست
همه دنبال دست کردن تو جیب همدیگه اند
بخدا تو سایت خارجی اصلا این مسایل نیست
چرا وقتی جنسی فروخته شده دقیقا خطای احتمالی توضیح داده نمیشه
حالا خواهشا ایراد ذکر شده مربوط به MapFragment.java:63 رو برطرف کنید. ممنون
جنسی به شما فروخته نشده! محتوای آموزشی هست! خطای احتمالی که فرمودید میتونه صدها و هزاران مورد باشه! ضمن اینکه تعهدی به رفع صد در صدی مشکلات داده نشده که بخواید از الفاظ رکیک استفاده کنید (اشاره به کامنتهای قبلی شما). جایی قید نشده پشتیبانی ارائه میشه با اینحال هرجا در توانم بوده دوستان رو راهنمایی کردم.
لطفا مشکلتون رو از همون وبسایت خارجی آموزشی که همه چند هزار خطا رو یکجا توضیح داده پیدا کنید و بخاطر این چند هزار تومن انگ دست درازی به جیب ملت رو به بقیه نزنید!
سلام اولین فایل رو که خریدم،بلافاصله بعد از مطالعه همه ی پکیج رو خریداری کردم….واقعا عالی و درجه یک
از اینکه مفید واقع شده خوشحالم
موفق و پیروز باشید
دوست عزیز هدف اصلی از اراِئه این آموزش ها اینه که شما چارچوب مطلب رو بفهمید و بعدش با سرچ تو سایت های خارجی بدون پرداخت هزینه مجزا ، مطلب رو بفهمید.
من تا الان سایت های مختلفی رو دیدم و آموزش ها رو دنبال کردم ، اینجا اکثر آموزش ها رو لطف کردن رایگان گذاشتن و من به شخصه تا این قسمت که پیش اومدم فقط قسمت های مربوط به سمت سرور رو خریدم و الباقی رایگان ها برای من کافی بود و سایر موارد خریدنی رو هم بلد بودم.
در حالی که سایر سایت ها حتی یک آموزش پولی رو هم ناقص ارائه میدن و شما مجبوری قسمت های بعد رو بخری
به نظرم بابت پرداخت این مبلغ درست نیست ارزش کار ایشون رو کم کنید چون بیشک روی ادامه کار ایشون ممکنه اثر گزار باشه. ببخشید طولانی شد ولی بعد از تمام قسمت هایی که تا اینجا دنبال کردم وظیفه خودم دونستم این پیام رو بنویسم.
سلام
من هیچ کدی نزدم عینا کپی کردم و الان چک کردم دقیقا شبیه هم هستن ولی باز همین اخطار رو میده.
return inflater.inflate(R.layout.fragment_map, container, false);
مربوط به این خط است.
ظاهرا تونستید سینک کنید پروژه رو. بالاخره ایراد کار از من بود یا شما؟
آقا من بر اساس فایل pdf و سورس کدی که خریداری کردم برنامه رو در اندروید پیاده سازی کردم اما وقتی روی گوشی می خوام اجرا کنم ارور میده unfortunatly cedar map has stoped
وقتی logcat گرفتم علت ارور مربوط به Using MapView requires calling Mapbox.getInstance(Context context, String accessToken) before inflating or creating the view. The access token parameter is required when using a Mapbox service.
بود که در فایل (MapFragment.java:63) است. راهنمایی بفرمایید
کدها رو با دقت با سورس پروژه مقایسه کنید ببینید چه قسمتی رو اشتباه زدید. آدرس هم که دارید دیگه. فایل MapFragment.java خط ۶۳
من میگم همه اون رو مطالعه کردم نمیشه
ادا در میاری میگی برو pdf رو بخون
اگه تذکر صفحه ۹ فایل PDF به چشمتون می خورد نه بیخود اعصاب خودتون رو خورد می کردید نه به کسی توهین می کردید! اینکه شما تست کردید نشده مسئولیتش با من یا کس دیگه ای نیست. راهکار داده شده. ببینید کجای کار ایراد داشته که نشده. یه درصد احتمال بدید سرور سیدارمپ مشکل مقطعی داشته. از دست من و شما چه کاری بر میاد؟
آقا خطا میده سینک میشه طبق صفحه ۱۰ میرم جلو نمیشه خطای
Unable to resolve dependency for ‘:app@debugUnitTest/compileclassPath’: could not resolve com.cedarmaps:CedarMapsSDK:4.2.1.
تذکر صفحه ۹ رو مطالعه کنید بزرگوار
آقا یعنی چی توضیح داده شده
این دیگه چه نوع کلاهبردارییه
من فیلترشکن دارم ولی این اصلا سینک نمیشه
فقط پول میگیرید
واقعا متاسفم براتون
“توضیح داده شده” یعنی مشکلی که برای شما بوجود اومده داخل فایل PDF ای که دریافت کردید توضیح داده شده چطور باید رفعش کنید! بجای اینکه ابراز تاسف کنید، چند دقیقه وقت بذارید و آموزش رو با دقت و خط به خط مطالعه کنید
سلام من این دوره رو با فایل سورس دانلود کردم در صورتی که سینک نمیشه
الان چکار باید کنم
در متن آموزش توضیح داده شده علتش
سلام وقتتون به خیر ببخشید یک سوال داشتم شما میدونید که در سیدار مپ قابلیتی وجود داره که بگه توی هر منطقه حداکثر سرعت مجازی که ماشین میتونه داشته باشه چه قدر است اگه داره میشه بگید چطوری به این داده دسترسی داشته باشیم منظورم این که کلاس خاصی هست که توی sdk سیدارمپ باشه وما بتونیم واسه این موضوع استفاده کنیم و یا اینکه روش دیگری داره ممنون میشم که راهنمایی بکنید من را آقای مطهری .
قابلیت های نقشه تو سایتش معرفی شدن. بعید میدونم بجز اون موارد، امکانات دیگه ای هم داشته باشه
سلام متاسفانه بخش رایگان api سایت سیدارمپ در حال حاضر در دسترس نیست و اینکه هیچ پاسخی هم نمیدن برای تمرین هم حتی نمیشه آموزش رو جلو برد
چی شده استاد؟!
چه بد! الان متوجه شدم. ایمیل زدم امیدوارم جواب بدن ببینم برنامه شون چیه. اگه قصد ارائه مجدد نداشته باشن آموزش یه سرویس دهنده دیگه رو تهیه و جایگزین می کنم
نه استاد امروز پاسخ دادن که فعلا ارائه نمیشه
فقط نسخه پولی رو ارائه میدن
خیلی بد شد
استاد چرا open street map رو توضیح ندادین؟
OpenStreetMap صرفا یه نقشهس و مثل سیدار و… API های مسیریابی رو نداره
پس با این حال کدوم بهتره بریم سراغش؟!
نشان چطوره؟!
و اینکه پس اسنپ چجوری با openStreetMap میاد برای راننده هاش مسیریابی رو انجام میده؟!
اسنپ از OSM برای نمایش (تایلهای) نقشه استفاده میکنه و امکانات مسیریابی و… جدای از اون هست که حالا یا اختصاصی خودشون هست یا از API هایی مثل MapBox استفاده کردن
سلام.من قبلا پکیج اموزشی رو خریدم و طبق اعلام سایت الان دانلود این اموزش باید رایگان باشه ولی نیست؟!
خود دوره رو باید دوباره دانلود کنید. حساب کاربری > دانلودهای من
سلام
آیا سرویس سیدارمپ از قابلیت نمایش زنده کلاینت روی نقشه پشتیبانی میکنه؟
بعنوان مثال میشه برای پیاده سازی تاکسی اینترنتی که یکی از قابلیت های مورد نیازش نمایش آنلاین مکان کلاینت روی مسیر هستش استفاده کرد یا خیر؟
باتشکر
بله پشتیبانی میکنه
باسلام تو آموزش سیدارمپ ایا موقع لود اپ یا هنگام ران شدن اپ میاد permission برای دسترسی به اینترنت وصل هست یانه و چک کردن لوکشن روشن هس یانه در صورت نبودن بیاد یه دیالوگ نمایش بده بعد کلیک بره به تنظیماتش و فعلا کنه اونه – اینو کار کردین تو این اموزش ؟ اگر ممکنه این آموزش رو بزارین ؟
بزرگوار قسمت ابتدایی آموزش که تو همین صفحه قرار گرفته رو بررسی کنید میبینید پرمیشن گرفته میشه از کاربر. بدون گرفتن مجوز دسترسی به لوکیشن که نمیشه کار کرد اصلا