مسیریابی

مستندات جامع اندروید

نسخه 1.0.3

هدف از این بخش از پروژه، مسیریابی بین دو نقطه از نقشه ، به وسیله سرویس مسیریابی نشان است. با استفاده از این سرویس ، بهترین مسیر جهت رفتن از نقطه مبدا به نقشه مقصد محاسبه می شود.

فهرست مطالب این صفحه

برای آشنایی سریع با وب سرویس مسیریابی نشان ، میتوانید از لینک زیر استفاده کنید

معرفی وب سرویس مسیریابی نشان

در این پروژه برای ارسال درخواست مسیریابی و دریافت نتیجه از SDK سرویس های نشان استفاده شده است که در ادامه با نحوه استفاده از آن آشنا می شویم.

می توانید سورس پروژه SDK سرویس های نشان را در لینک زیر مشاهده کنید

سورس کد پروژه SDK سرویس های نشان

AndroidManifest.xml:

دسترسی‌ زیر را برای استفاده از اینترنت به فایل مانیفست برنامه اضافه کنید:

            <uses-permission android:name="android.permission.INTERNET"/>
        

activity_routing.xml:

در این صفحه علاوه بر المان نقشه ، دوToggleButton برای فعالسازی نمایش مسیر دریافت شده از سرور بر روی نقشه وجود دارد و یک Guideline که جهت کمک به چیدمان این 2 کلید استفاده شده است. به این شکل که Guideline در وسط صفحه قرار گرفته و ToggleButtonها یکی در سمت چپ و یکی در سمت راست آن ها قرار می گیرند.

            <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context="org.neshan.sample.starter.activity.Routing">

    <org.neshan.mapsdk.MapView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/map"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />


    <ToggleButton
        android:id="@+id/overviewToggleButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/toggle_button_bg"
        android:checked="false"
        android:drawablePadding="8dp"
        android:drawableTint="@color/toggle_button_text_color"
        android:elevation="8dp"
        android:onClick="findRoute"
        android:paddingStart="8dp"
        android:paddingEnd="8dp"
        android:textColor="@color/toggle_button_text_color"
        android:textOff="رسم Overview"
        android:textOn="رسم Overview"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        tools:targetApi="m" />

    <ToggleButton
        android:id="@+id/stepByStepToggleButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/toggle_button_bg"
        android:checked="false"
        android:drawablePadding="8dp"
        android:drawableTint="@color/toggle_button_text_color"
        android:elevation="8dp"
        android:onClick="findRoute"
        android:paddingStart="8dp"
        android:paddingEnd="8dp"
        android:textColor="@color/toggle_button_text_color"
        android:textOff="رسم StepByStep"
        android:textOn="رسم StepByStep"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        tools:targetApi="m" />

</androidx.constraintlayout.widget.ConstraintLayout>
        

Routing.java:

متد initLayoutRefrences برای دادن مقادیر اولیه به تمامی المان‌های مربوط به رابط کاربری نوشته شده‌است.

همچنین Listener مربوط به نقشه نیز در همین متد قرار دارد .

با افزودن setOnMapLongClickListener به نقشه پس از هر بار کلیک طولانی بر روی نقشه بررسی می شود که اگر تعداد نشانه های روی نقشه کمتر از 2 عدد است، یک نشانه جدید به قشه اضافه شود و اگر با افزودن نشانه جدید، تعداد نشانه ها 2 عدد شد، یعنی مبدا و مقصد تعیین شده و متد neshanRoutingApi جهت دریافت مسیر صدا زده می شود تا مسیریابی انجام شود.

            // Initializing layout references (views, map and events)
    private void initLayoutReferences() {
        // Initializing views
        initViews();
        // Initializing mapView element
        initMap();

        // when long clicked on map, a marker is added in clicked location
        map.setOnMapLongClickListener(latLng -> {
            if (markers.size() < 2) {
                markers.add(addMarker(latLng));
                if (markers.size() == 2) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            overviewToggleButton.setChecked(true);
                            neshanRoutingApi();
                        }
                    });
                }
            } else {
                runOnUiThread(() -> Toast.makeText(Routing.this, "مسیریابی بین دو نقطه انجام میشود!", Toast.LENGTH_SHORT).show());
            }
        });
    }
        

در متد initViews ارتباط بین Viewهای فایل activity_routing.xml و اشیای مربوطه در جاوا برقرار شده است. همچین یک OnCheckedChangeListener تعریف و به هر دو ToggleButton اضافه می شود تا با فعال شدن هر کدام، کلید دیگر غیرفعال شود و در صورت نیاز، مسیر ترسیم شده بر روی نقشه که در متغیر onMapPolyline ذخیره شده، از روی نقشه حذف شود.

            // We use findViewByID for every element in our layout file here
    private void initViews() {
        map = findViewById(R.id.map);
        // CheckChangeListener for Toggle buttons
        CompoundButton.OnCheckedChangeListener changeChecker = new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton toggleButton, boolean isChecked) {
                // if any toggle button checked:
                if (isChecked) {
                    // if overview toggle button checked other toggle button is uncheck
                    if (toggleButton == overviewToggleButton) {
                        stepByStepToggleButton.setChecked(false);
                        overview = true;
                    }
                    if (toggleButton == stepByStepToggleButton) {
                        overviewToggleButton.setChecked(false);
                        overview = false;
                    }
                }
                if (!isChecked && onMapPolyline!=null) {
                    map.removePolyline(onMapPolyline);
                }
            }
        };

        // each toggle button has a checkChangeListener for uncheck other toggle button
        overviewToggleButton = findViewById(R.id.overviewToggleButton);
        overviewToggleButton.setOnCheckedChangeListener(changeChecker);

        stepByStepToggleButton = findViewById(R.id.stepByStepToggleButton);
        stepByStepToggleButton.setOnCheckedChangeListener(changeChecker);
    }
        

قبل از بررسی متد neshanRoutingApi، یادآوری می کنیم که برای استفاده از سرویس های نشان به API_KEY مرتبط با سرویس مورد نظر خود نیاز دارید که میتوانید به صورت رایگان از پنل توسعه دهندگان نشان بدست آروید .

این متد بعد از تعیین دو نقطه مبدا و مقصد صدا زده می شود. ابتدا با کمک متد Builder از کلاس NeshanDirection مقادیر مورد نیاز مبدا، مقصد، کلید دسترسی و سایر پارامترهای ممکن ست شده و با صدا زدن متد build یک شی از نوع NeshanDirection ساخته شده می شود. اکنون با صدا زدن متد call این شیء ساخته شده میتوانیم درخواست مسیریابی را به سرور ارسال نماییم. ورودی این متد یک Callback است که با override کردن تابع onResponse و onFailure میتوان از نتیجه وب سرویس در آن مطلع شد.

سپس با استفاده از متد Builder از کلاس NeshanSearch، پارامترهای ورودی وب سرویس مقدار دهی می شود و شی از نوع NeshanSearch با صدا زدن متد build ساخته می شود. حال با صدا زدن متد call این شی می توانیم وب سرویس را صدا زده و نتیجه آن را در Callback که به عنوان ورودی متد call تعریف کرده ایم دریافت کنیم.

در صورت موفقیت آمیز بودن دریافت پاسخ از سرور، onResponse فراخوانی می شود و بدنه ( body ) پارامتر response که از نوع NeshanDirectionResult است شامل اطلاعات مسیرهای پیشنهادی خواهد بود. ( توضیحات اجزای پاسخ وب سرویس مسیریابی را می توانید در اینجا ببینید ) مسیرها در پاسخی که از سرور دریافت می شوند به شکل encode شده هستند که می توان با استفاده از متد decode از کلاس PolylineEncoding آن ها را به آرایه ای از نقاط تبدیل و در رسم روی نقشه استفاده کرد.

            private void neshanRoutingApi() {
      new NeshanDirection.Builder("service.VNlPhrWb3wYRzEYmstQh3GrAXyhyaN55AqUSRR3V", markers.get(0).getLatLng(), markers.get(1).getLatLng())
              .build().call(new Callback<NeshanDirectionResult>() {
          @Override
          public void onResponse(Call<NeshanDirectionResult> call, Response<NeshanDirectionResult> response) {

              // two type of routing
              Route route = response.body().getRoutes().get(0);
              routeOverviewPolylinePoints = new ArrayList<>(PolylineEncoding.decode(route.getOverviewPolyline().getEncodedPolyline()));
              decodedStepByStepPath = new ArrayList<>();

              // decoding each segment of steps and putting to an array
              for (DirectionStep step : route.getLegs().get(0).getDirectionSteps()) {
                  decodedStepByStepPath.addAll(PolylineEncoding.decode(step.getEncodedPolyline()));
              }

              onMapPolyline = new Polyline(routeOverviewPolylinePoints, getLineStyle());
              //draw polyline between route points
              map.addPolyline(onMapPolyline);
              // focusing camera on first point of drawn line
              mapSetPosition(overview);
          }

          @Override
          public void onFailure(Call<NeshanDirectionResult> call, Throwable t) {

          }
      });