04 August 2013

Google Maps Android API v2 - การแสดงตำแหน่งปัจจุบันบน Google Maps

Updated on

        อัพเดทล่าสุด 06 / 06 / 2014
        หมายเหตุ - Google Maps Android API v2 ไม่สามารถรันทดสอบบน AVD Emulaotr ได้ ให้ทดสอบบนเครื่องจริงหรือใช้ Genymotion  แทน

        สำหรับคราวนี้ก็จะเป็นบทความ Google Maps ตามเดิม แต่จะเป็นเรื่องที่ผู้ที่หลงเข้ามาอ่านหลายๆคนมักจะถามเจ้าของบล็อกอยู่บ่อยๆ นั่นก็คือ อยากจะแสดงตำแหน่งตนเองบนแผนที่ ซึ่งจริงๆแล้วไม่ใช่เรื่องยากซักเท่าไร เพราะมันคือการประยุกต์ร่วมกันของ Location Manager กับ Google Maps นั่นเอง แต่ไหนๆแล้วก็ขอทำเป็นบทความไว้ดีกว่า


        สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ยังไม่เคยใช้งาน Google Maps Android API v2 โปรดกลับไปอ่านบทความเริ่มต้นเสียก่อนนะ [Android Code] การใช้งาน Google Map Android API v2

        หลักการก็ไม่มีอะไรมากมายนัก โดยเริ่มจากการอ่านตำแหน่งปัจจุบันด้วย Location Manager ให้ได้ก่อน เมื่อได้พิกัดแล้วจึงให้กำหนด Marker ลงบนแผนที่นั่นเอง โดยแนะนำให้ทดลองอ่านค่าพิกัดธรรมดาๆเสียก่อน [Android Code] การใช้งาน Location Manager (GPS) อย่าพึ่งรีบมารวมโค๊ด เพราะถ้ายังไม่เข้าใจอย่างใดอย่างหนึ่ง ก็จะไม่เข้าใจบทความนี้นะ

        จากบทความเรื่อง Location Manager จะเห็นว่าหัวใจสำคัญของพิกัดปัจจุบันนั้นจะอยู่ที่ฟังก์ชัน onLocationChanged เพราะฟังก์ชันนี้จะทำงานเมื่อสามารถจับพิกัดล่าสุดได้ ซึ่งเมื่อผู้ใช้เดินไปเรื่อยๆ ฟังก์ชันนี้ก็จะพยายามอัพเดทตามไปเรื่อยๆเช่นกัน ดังนั้นสิ่งที่ต้องคำก็คือใส่คำสั่งของ GMapsV2 ที่ต้องการลงใน onLocationChanged นั่นเอง


public void onLocationChanged(Location loc) {
    // ค่าพิกัดจะอยู่ใน loc ให้นำค่าพิกัดออกมา 
    // แล้วกำหนดเป็น Marker ลงในแผนที่
}

        นอกจากจะปัก Marker ก็ยังประยุกต์ไปเป็นอย่างอื่นได้อีกด้วย เช่น เลื่อนมุมกล้องของแผนที่ไปยังตำแหน่งนั้นๆ เป็นต้น


        สำหรับ Layout ก็ไม่มีอะไรมาก เพื่อให้เข้าใจได้ง่ายๆ ก็เลยมีแค่ Fragment ที่เป็นของ GMapsV2 เพียงอย่างเดียวเท่านั้น

main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"  >

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />

</RelativeLayout>


Main.java
package app.akexorcist.googlemapsv2mylocation;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.content.Context;
import android.support.v4.app.FragmentActivity;

public class Main extends FragmentActivity {
    GoogleMap mMap;
    Marker mMarker;
    LocationManager lm;
    double lat, lng;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mMap = ((SupportMapFragment)getSupportFragmentManager()
                .findFragmentById(R.id.map)).getMap();
    }
    
    LocationListener listener = new LocationListener() {
        public void onLocationChanged(Location loc) {
            LatLng coordinate = new LatLng(loc.getLatitude()
                    , loc.getLongitude());
            lat = loc.getLatitude();
            lng = loc.getLongitude();
            
            if(mMarker != null)
                mMarker.remove();
            
            mMarker = mMap.addMarker(new MarkerOptions()
                    .position(new LatLng(lat, lng)));
            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    coordinate, 16));
        }

        public void onStatusChanged(String provider, int status
                , Bundle extras) {}
        public void onProviderEnabled(String provider) {}
        public void onProviderDisabled(String provider) {}
    };
    
    public void onResume() {
        super.onResume();
        
        lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

        boolean isNetwork = 
                lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        boolean isGPS = 
                lm.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if(isNetwork) {
            lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER
                    , 5000, 10, listener);
            Location loc = lm.getLastKnownLocation(
                    LocationManager.NETWORK_PROVIDER);
            if(loc != null) {
                lat = loc.getLatitude();
                lng = loc.getLongitude();
            }
        }
        
        if(isGPS) {
            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER
                    , 5000, 10, listener);
            Location loc = lm.getLastKnownLocation(
                    LocationManager.GPS_PROVIDER);
            if(loc != null) {
                lat = loc.getLatitude();
                lng = loc.getLongitude();
            }
        }
    }
    
    public void onPause() {
        super.onPause();
        lm.removeUpdates(listener);
    }
}

        1. ประกาศคลาส Google Map เพื่อแสดงแผนที่ และ Marker เพื่อปักบนแผนที่ Location Manager ที่เป็นคลาสสำหรับจัดการในเรื่องของการอ่านพิกัดของผู้ใช้งาน และ Double สองตัวสำหรับเก็บพิกัดไว้คือละติจูดและลองติจูด

        2. กำหนดค่าให้กับคลาส Google Maps กำหนดเป็น R.id.map ที่สร้างไว้ใน Layout

        3. สร้างคลาส Location Listener ซึ่งเป็น Event Listener สำหรับ Location Manager โดยจะทำงานก็ต่อเมื่อตำแหน่งพิกัดหรืออุปกรณ์ที่อ่านพิกัดมีการเปลี่ยนแปลง จะมีด้วยกัน 4 กรณี แต่เจ้าของบล็อกจะสนใจแค่กรณีของ onLocationChanged ก็คือฟังก์ชันที่ทำงานเมื่อมีการอัพเดทพิกัดใหม่เข้ามา

        4. เก็บพิกัดไว้ในตัวแปร Double ที่ได้สร้างไว้ คือ lat กับ lng และเช็คก่อนว่ามี Marker อยู่แล้วหรือป่าว ถ้ามีก็ลบทิ้งก่อน แล้วนำพิกัดที่ได้ไปกำหนดเป็นพิกัดบนแผนที่เป็น Marker

        5. กำหนดค่าให้กับคลาส Location Manager และสร้าง Boolean สองตัว เพื่อเก็บสถานะว่าได้เปิดใช้การระบุพิกัดด้วย Network กับ GPS หรือไม่

        6. เช็คว่าเปิดใช้การระบุพิกัดด้วย Network หรือป่าว ถ้าใช่ก็จะกำหนดให้อ่านค่าพิกัดจาก Network และให้อัพเดทตำแหน่งทุกๆ 5 วินาที หรือ 10 เมตร โดยกำหนด Location Listener เป็น listener ในข้อที่ 3

        7. เช็คว่าเปิดใช้การระบุพิกัดด้วย GPS หรือป่าว ถ้าใช่ก็จะกำหนดให้อ่านค่าพิกัดจาก GPS และให้อัพเดทตำแหน่งทุกๆ 5 วินาที หรือ 10 เมตร โดยกำหนด Location Listener เป็น listener ในข้อ 3 จะเห็นว่าเหมือนกับข้อ 6 ซึ่งขึ้นอยู่กับว่าเปิดอะไร สมติว่าเปิด Network กับ GPS ก็จะเป็นการใช้ GPS ถ้าเปิดใช้ Network อย่างเดียวก็จะใช้ Network และถ้าเปิดใช้ GPS อย่างเดียวก็จะใช้ GPS

        8. ฟังก์ชัน onResume ที่ทำงานเมื่อปิดหรือย่อแอปพลิเคชั่นก็จะหยุดการใช้งาน Location Manager


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.googlemapsv2mylocation"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name=
            "com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"  />
     
    <uses-feature android:glEsVersion="0x00020000" android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.akexorcist.googlemapsv2mylocation.Main"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
           android:name="com.google.android.maps.v2.API_KEY"
           android:value="your_api_key" />
        
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        
    </application>

</manifest>

        เมื่อลองทดสอบดู ก็จะเห็นแผนที่เลื่อนไปยังตำแหน่งที่ผู้ที่หลงเข้ามาอ่านอยู่ ณ ตอนนั้น จะมี Marker ปักอยู่ และเมื่อเคลื่อนที่ก็เปลี่ยนแปลงตามพิกัดของผู้ใช้

        สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ต้องการไฟล์ตัวอย่าง สามารถดาวน์โหลดได้จาก Google Maps V2 My Location [Google Drive] หรือ Google Maps V2 My Location [GitHub]


        เมื่อดาวน์โหลดตัวอย่างไป Import แล้วให้ตรวจสอบตามนี้

                • ใส่ API Key ที่ไปขอจาก Google API ลงใน AndroidManifest.xml

                • เลือก Library ของ google-play-services_lib ให้ตรงกับเครื่อง

        อาจจะมีปัญหาพอสมควรสำหรับผู้ที่ดาวน์โหลดไปทดสอบ เพราะว่าการใช้ Google Maps นี้มีการขอ API Key และ Library ดังนั้นจึงควรตรวจสอบให้แน่ใจว่าได้กำหนดไว้ถูกต้องแล้ว และะก็อย่าลืมว่าทดสอบบน AVD หรือ Emulator ไม่ได้นะ เพราะต้องมี Google Play Services ซึ่ง AVD ไม่มี ทำให้แอป Force Close ดังนั้นจึงแนะนำให้ทดสอบบนเครื่องจริงหรือว่าใช้ Genymotion เพราะสามารถติดตั้ง Google Apps ได้