Android開發筆記-Google地圖地標

※注意:本文已過時,新版API請參考官方網站

內容說明:在Google地圖中加入地標

資料來源:Google!Android 2手機應用程式設計入門第三版

建立Google地圖之後,我們可以在上面標示地標。就如同在Google地圖定位中提到的,我們必需先建立一個地標層(ItemizedOverlay),然後在上面加入地標。

和定位層不一樣的地方在於,實作地標層的 ItemizedOverlay 類別是一個抽象類別(可參考官方網站),所以我們必須要先建立 ItemizedOverlay 類別才能繼續寫程式。在我們宣告 class landmark extends ItemizedOverlay 之後,根據系統指示產生下面的程式碼:

class landmark extends ItemizedOverlay<OverlayItem> {
    public landmark(Drawable defaultMarker) {
        super(defaultMarker);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected OverlayItem createItem(int i) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int size() {
        // TODO Auto-generated method stub
        return 0;
    }
}

接著我們必需要修改 ItemizedOverlay 類別,將所要的地標加入物件中:

class landmark extends ItemizedOverlay<OverlayItem> {
    private List<OverlayItem> items = new ArrayList<OverlayItem>();
    private Context context;    //主程式
    public landmark(Drawable defaultMarker) {
        super(defaultMarker);
    }
    
    public landmark(Drawable defaultMarker, Context context) {
        super(defaultMarker);
        this.context = context;
        
        GeoPoint station_taipei = new GeoPoint(
                (int)(25.047192 * 1000000),
                (int)(121.516981 * 1000000)
        );          //設定地圖座標值:緯度,經度
        GeoPoint station_tainan = new GeoPoint(
                (int)(22.99724179778664 * 1000000),
                (int)(120.2126014372952 * 1000000)
        );          //設定地圖座標值:緯度,經度
        
        //加入地標座標值(Point)、標題(Title)、說明(Snippet)
        items.add(new OverlayItem(station_taipei, "台北車站", "台北火車站與高鐵、捷運三鐵共構"));
        items.add(new OverlayItem(station_tainan, "台南車站", "台南火車站"));
        
        populate(); //自動完成所有處理程序,呼叫createItem取得所有地標
    }

    @Override
    protected OverlayItem createItem(int i) {
        // TODO Auto-generated method stub
        return items.get(i);    //取得第i個地標
    }

    @Override
    public int size() {
        // TODO Auto-generated method stub
        return items.size();    //地標總數量,會被populate()呼叫
    }

    @Override
    protected boolean onTap(int index) {    //點擊地標時的反應
        // TODO Auto-generated method stub
        //彈出視窗
        AlertDialog.Builder builder = new AlertDialog.Builder(context); //顯示於主程式main
        builder.setTitle(items.get(index).getTitle());  //加入標題
        builder.setMessage(items.get(index).getSnippet());  //加入說明
        builder.setPositiveButton("OK",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {}
            }
        );
        builder.show();
        return super.onTap(index);
    }
}

Context 的宣告是為了要給彈出視窗使用的,當使用者點擊地標時,就會彈出說明視窗。

接著回去修改 setupMap() 方法,如果有加過定位層就會知道,必須要先建立一個座標層:

private void setupMap() {
        ...
        List<Overlay> overlays = map.getOverlays();  //在地圖上建立一個座標層
        ...
    }

接著就可以把地標層加入座標層中了。這裡我們必需設定地標的圖示,使用android.R.drawable來讀取Android內建的圖示,圖示說明可參考MENU設定官方說明

//宣告地標圖示
        Drawable point_star = getResources().getDrawable(android.R.drawable.star_on);
        //設定圖示大小
        point_star.setBounds(0, 0, point_star.getMinimumWidth(), point_star.getMinimumHeight());
        
        landmark myLandmark = new landmark(point_star, this);
        overlays.add(myLandmark);   //將地標層加入地圖座標層中

地標圖示置中 (2012.03.16 新增)

上面範例結果的地標是以圖示左上角對齊地標點,要修正這個問題的話,首先在 landmark 類別裡加入 boundCenter 的改寫(參考ItemizedOverlay說明):

public static Drawable boundCenter(Drawable d) {    //使圖示中心點對齊地標
        d.setBounds(- d.getIntrinsicWidth() / 2, - d.getIntrinsicHeight() / 2,
            d.getIntrinsicWidth() / 2, d.getIntrinsicHeight() / 2);
        return d;
    }

接著回到 setupMap() 方法修改最後一段的程式:

//宣告地標圖示
        Drawable point_star = getResources().getDrawable(android.R.drawable.star_on);
        //將圖示中心置於地標上
        point_star = landmark.boundCenter(point_star);
        
        landmark myLandmark = new landmark(point_star, this);
        overlays.add(myLandmark);   //將地標層加入地圖座標層中

上面的方法就可以將圖示中心對齊地標點上了。單純使用 Drawable.setBounds() 時,利用上面的運算也可以讓圖片產生置中對齊的效果。

多層地標 (2012.08.16 新增)

如果想要在不同種類的地標上標示不同的圖樣,可以加入好幾層的地標層。不過我們可以看到 GeoPoint 地標資料是寫在 landmark 類別裡的,所以首先我們要修改這個類別,把 GeoPoint 的設定分離出來,這樣 landmark 類別才能給所有的地標層使用。修改的方式如下,主要是把 GeoPoint 獨立出來,但因為設定完 GeoPoint 之後必須呼叫 101 行的 populate(),所以這一行程式碼也要另外獨立成一個方法,以便在 GeoPoint 設定結束後可以進行呼叫:

public landmark(Drawable defaultMarker, Context context) {
        super(defaultMarker);
        this.context = context;
    }
    
    public void setPoint(GeoPoint gp, String title, String snippet) {
        //加入地標座標值(Point)、標題(Title)、說明(Snippet)
        items.add(new OverlayItem(gp, title, snippet));
    }

    public void finish() {
        populate(); //自動完成所有處理程序,呼叫createItem取得所有地標
    }

接著回到 setupMap() 做四個動作,一、宣告多個地標層;二、設定每個地標層的地標位置;三、設定每個地標層的圖樣;四、呼叫 populate() 完成加入地標的動作。當然,最後別忘了把所有的地標層放入地圖中。參考程式碼如下:

//宣告地標圖示
        Drawable point_star1 = getResources().getDrawable(android.R.drawable.star_on);
        Drawable point_star2 = getResources().getDrawable(android.R.drawable.star_big_off);

        landmark myLandmark1, myLandmark2;
        myLandmark1 = new landmark(landmark.boundCenter(point_star1), this);    
        myLandmark2 = new landmark(landmark.boundCenter(point_star2), this);
        
        GeoPoint station_taipei = new GeoPoint(
                (int)(25.047192 * 1000000),
                (int)(121.516981 * 1000000)
        );          //設定地圖座標值:緯度,經度
        GeoPoint station_tainan = new GeoPoint(
                (int)(22.99724179778664 * 1000000),
                (int)(120.2126014372952 * 1000000)
        );          //設定地圖座標值:緯度,經度
        
        myLandmark1.setPoint(station_taipei, "台北車站", "台北火車站與高鐵、捷運三鐵共構");
        myLandmark2.setPoint(station_tainan, "台南車站", "台南火車站");
        
        myLandmark1.finish();   //已加入所有的地標
        myLandmark2.finish();
        
        overlays.add(myLandmark1);  //將地標層加入地圖座標層中
        overlays.add(myLandmark2);