2011年1月18日 星期二

心的方向!Go~








昨天到台北參加聚餐,往高鐵站的路上,被後照鏡上火紅的夕陽閃了許久,讓我哼起了這首歌。有點久沒騎車到高鐵站,除了騎過頭外,沒想到連高鐵前面常常一排排違規停車的機車群,忽然都沒看到,害我一時不知要停在哪。


最近還是有非技術類的心情想要記下來,但每每寫到一半就被我 cancel 掉,實在是三五句提到公司福利不然就政治八卦等,這樣的行為讓我對自己感到失落。去年底開始警惕自己不要談論這類話題,希望 2011 年有好的開始。盡管背負的壓力還沒減輕,但開始比較不會只為錢工作了。今年的工作壓力理當比去年大,計畫趕不上變化,但還好,除了每週唸不熟領域的 paper 外,剩下的應該都還能應付吧?


對於 2010 的感觸,除了提不得的暗黑八卦外,大概是對資源管控有所感觸。例如一天 24 小時,某件事做多了,另一件事就必然做少了。有時也不得不跟別人搶資源,例如宿舍樓下的烘乾機啊,或是火車坐位上永遠只有 3 個扶手。也曾跟大神吃飯,當時探問他對 Android Market 或 iOS App store 有無興趣投入,得到的回應當然是有,但有更重要的事要擺在前頭 -- 陪孩子成長,這時我也才比較會回過頭思考一些事情,投入熱門的產業追求的目標是什麼?有沒有比這些更重要的目標呢?很佩服一些人,像青蛙學弟,可以大膽地嘗試一些新東西,不要只為錢工作,也讓我反省一下,該抽空弄弄點更有意義的東西。


最近對未來有些新的方向了,願能持之以恆,離目標越來越近。


2011年1月6日 星期四

Android 開發筆記 - 模擬與繪製 GPS 移動路線

mygpspathwithmarker
上頭紅色的是繪製的路徑,藍色點則是模擬時,顯示的 GPS 座標位置,將隨著時間變動。這是根據此篇 Android 開發教學筆記 - 透過 Google Maps API 畫出 GPS 路徑Android 開發教學筆記 - 調整 GPS 座標個數,以提昇路徑繪圖效率 的筆記所做的延伸練習。


給定一段 GPS 紀錄的路徑,經過個數的縮減壓縮並在地圖上完成路徑的繪製,接著想要了解原始 GPS 移動與繪製的路徑差異有多大,所以就有了這個練習。依照原始 GPS 座標資訊,畫製一個 marker 隨著時間擺放到指定的 GPS 位置,這樣的效果除了能確認原始 GPS 路線與壓縮後的路徑位置的差異,也可以了解實際收集到的 GPS 座標是否出現在 Google Maps 繪製的地圖道路。


實做上在 class MyGPSPath 裡,新增並且改變一些 member 宣告方式:


Handler jobs;
final double srcLogs[]={ 48.138050017878413, 16.481179967522621 /* ,  ... */ };
double logs[] = {};
int gps_at;


其中 jobs 用來更新畫面,srcLogs用來紀錄原始的不重複的 GPS 資訊,而 logs 會是壓縮過後的 GPS 個數,而 gps_at 則是等會模擬時,用來紀錄走到那個 GPS 點。


接著在 onCreate 裡,初始化 gps_at, jobs, logs 變數,以及調整 overlay :


gps_at = 0;
jobs = new Handler();

ArrayList<Double> data = new ArrayList<Double>();
for( int i=0; i<srcLogs.length ; ++i )
    data.add( new Double( srcLogs[i] ) );
data = rebuildGSPLogsByAngleCheck( data , 0 );
        
logs = new double[data.size()];
for( int i=0; i<data.size() ; ++i)
    logs[i] = data.get(i).floatValue();

List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
ol.clear();
ol.add(new MyPathOverlay());

MapController mapController = mapView.getController();
if( mapController != null )
{
    mapController.setCenter(new GeoPoint( (int) (srcLogs[0]* 1000000) , (int)(srcLogs[1]* 1000000) ) );
    mapController.setZoom(15);
}

new Thread( new Runnable(){
    public void run()
    {
        gps_at = 2;
        while( gps_at < srcLogs.length )
        {
            try{
                jobs.post( new Runnable(){
                    public void run(){
                        List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
                        while( ol.size() > 1 )
                            ol.remove(1);
                        ol.add(new MyMarkerOverlay( srcLogs[gps_at] , srcLogs[gps_at+1]));
                        
                        GeoPoint in = new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000) );
                        Point out = new Point();
                        mapView.getProjection().toPixels(in, out);
                        int diff = 100;
                        if( out.x < diff || out.y < diff || out.x > mapView.getWidth() - diff || out.y > mapView.getHeight() - diff )
                        {
                            MapController mc = mapView.getController();
                            mc.animateTo(new GeoPoint( (int)(srcLogs[gps_at]*1000000) , (int)(srcLogs[gps_at+1]*1000000 )) );
                        }
                    }
                });
                Thread.sleep(500);
            }
            catch(Exception e)
            {
            }
            gps_at +=2 ;
        }
    }
}).start();


上頭的 MyPathOverlay 則是之前例子中的 MyMapOverlay,在此僅改變成較有意義的名稱,而 onCreate 最後面使用到的 Thread,就是用來模擬 GPS 變化的過程,每 0.5 秒更動 gps_at 時,則重新擺放 MyMarkerOverlay 的位置,並且稍微計算一下,當 Marker 太靠近可視地圖的邊框時,自動調整地圖位置。


另外,還需實做 MyMarkerOverlay,用來標記 GPS 移動:


class MyMarkerOverlay extends com.google.android.maps.Overlay
{
    double at_lat,at_lng;
    MyMarkerOverlay( double _lat, double _lng )
    {
        super();
        at_lat = _lat;
        at_lng = _lng;
    }
    @Override
    public boolean draw(Canvas canvas, MapView mapView,boolean shadow, long when)
    {
        super.draw(canvas, mapView, shadow);
            
        if( !shadow )
        {
            GeoPoint in = new GeoPoint((int) (at_lat * 1000000) , (int) (at_lng * 1000000) );
            Point out = new Point();
            mapView.getProjection().toPixels(in, out);
                
            int r = 6;
            Paint p = new Paint();
                
            p.setColor(Color.BLUE);
            p.setAntiAlias(true);
            RectF oval=new RectF( out.x - r, out.y - r, out.x + r, out.y + r);

            canvas.drawOval(oval, p);
        }
        return true;
    }
}


也可以實做 ItemizedOverlay<OverlayItem> 的方式,好處是用法接近 Web API 的使用,把你想要新增的點,加到下面的 list 即可:


class MyItemMarkerOverlay extends com.google.android.maps.ItemizedOverlay<OverlayItem>
{
    private ArrayList<OverlayItem> myOverlays = new ArrayList<OverlayItem>();
    private Context myContext;
    public MyItemMarkerOverlay(Drawable defaultMarker, Context context)
    {
        super(boundCenterBottom(defaultMarker));
        myContext = context;
    }
    @Override
    protected OverlayItem createItem(int i)
    {
        return myOverlays.get(i);
    }
    @Override
    public int size()
    {
        return myOverlays.size();
    }
    @Override
    protected boolean onTap(int index)
    {
        OverlayItem item = myOverlays.get(index);
        //AlertDialog dialog = new AlertDialog.Builder(MyGPSPath.this).create();
        AlertDialog dialog = new AlertDialog.Builder(myContext).create();
        dialog.setTitle(item.getTitle());
        dialog.setMessage(item.getSnippet());
        dialog.setButton("OK", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which)
            {
                dialog.cancel();
            }
        } );
        dialog.show();
        return true;
    }
    public void addOverlay(OverlayItem overlay)
    {
        myOverlays.add(overlay);
        populate();
    }
}


用法:


List<com.google.android.maps.Overlay> olay = mapView.getOverlays();
Drawable drawable = this.getResources().getDrawable(R.drawable.icon);
MyItemMarkerOverlay markersOverlay = new MyItemMarkerOverlay( drawable, this ); // new MyItemMarkerOverlay( drawable, MyPGSPath.this );

markersOverlay.addOverlay(
    new OverlayItem(
        new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000)),
        "Title",
        "Snippet"
    )
);

olay.add(markersOverlay);


此練習的 GPS 變化,也可以透過 DDMS 來處理,例如定時 send 一些 GPS 座標等,但我還沒嘗試,看來下個練習方向可以用用看 DDMS 囉。


Android 開發筆記 - 調整 GPS 座標個數,以提昇路徑繪圖效率

Android 開發教學筆記 - 透過 Google Maps API 畫出 GPS 路徑 提到關於繪製 GPS 路徑的方式之一,然而,有時路徑長度過長,如兩三千個 GPS 座標,則可能造成路徑繪圖的負擔。


在此嘗試降低 GPS 個數的方式:



  • 以連續兩點為單位,計算差距,差距小於某數值則刪去

  • 以連續三個點為單位,所建立的兩段直線的角度,若角度大過某個數值,則把中間點去除


其中前者會存在一些哪些點可刪或不可刪問題,並且去除個數有限,後者則是計算量大,刪除的效果不錯,在 2011_01_03_Schwechat_Brno.gpx 測資中,可以從不重複的一千多點縮小到兩、三百點,但仍會有畫出的路徑與道路偏移的問題,例外則是角度選擇的部份


簡單的角度實做,就是計算兩向量的 cosine 數值,將越靠近 -1 的進行刪除的動作,也就是兩向量越接近 180 度的直線。


片段程式碼:


public static void main(String a[] )
{
    double logs[] = { 25.051981, 121.522751, /* ... , */ 25.064867, 121.526154 };

    ArrayList<Double> data = new ArrayList<Double>();
    for( int i=0; i<logs.length ; ++i )
        data.add( new Double( logs[i] ) );

    System.out.println( data.size() );
    data = rebuildGSPLogs( data , 0 );
    System.out.println( data.size() );
}

//
// list = { lat1, lng1, lat2, lng2, ... };
//
private ArrayList<Double> rebuildGSPLogsByAngleCheck( ArrayList<Double> list , int at )
{
    if( at < 2 )    // begin at second point
        at = 2;
//    if( list.size() > at + 6 && at >= 0 )
    while( list.size() > at + 6 && at >= 0 )
    {
        double x1 = list.get(at).floatValue() , y1 = list.get(at+1).floatValue() , x2 = list.get(at+2).floatValue(), y2 = list.get(at+3).floatValue() , x3 = list.get(at+4).floatValue() , y3 = list.get(at+5).floatValue();
        double nx1 = x1-x2 ,nx2 = x3 - x2, ny1 = y1-y2 , ny2 = y3-y2;
        double len1 = Math.sqrt( nx1*nx1 + ny1*ny1 ) , len2 = Math.sqrt( nx2*nx2 + ny2*ny2 );
            
        if( len1 == 0 )
        {
            list.remove(at+2);    // lat
            list.remove(at+2);    // lng
        }
        else if( len2 == 0 )
        {
            list.remove(at+4);    // lat
            list.remove(at+4);    // lng
        }
        else
        {
            double checkCos = (nx1*nx2 + ny1*ny2 )/len1/len2 ;
            if( checkCos > -0.986025 )  //  >= -1 : keep all
            {
                at+=2;
            }
            else
            {
                list.remove(at+2);    // lat
                list.remove(at+2);    // lng
            }
        }
//        return rebuildGSPLogsByAngleCheck( list , at );
    }
    return list;
}


有些需要留意的點,原先把 rebuildGSPLogsByAngleCheck 寫成遞迴,在桌機上跑很正常,但移植到 Android 模擬器上,大概沒跑多久就掛了,查了錯誤訊息才得知 out of stack,所幸程式碼只需改個 2 行就改成非遞迴式。


除此之外,在繪圖方面,也可以採用一些方式提昇效率:



  • 扣除重複位置的點

  • 僅繪出可視地圖範圍內的路徑


對於前者部分,是因為就算 GPS 座標不同,在進行繪製的過程中,會先轉成對應的繪圖座標,在這樣的情境下,有些非常相近的 GPS 座標,其實是會被對應成同一個繪圖點,解法很簡單,就只要用個 Hash 就可以解掉;後者問題是在於一條路徑很長,但顯是在地圖上只是很小的一段路程,因此可以只挑出目前地圖看得見的部分進行繪製,也就是將 GPS 座標轉成繪圖座標,此時去判斷該繪圖座標的位置是不是落在目前螢幕顯示的區域,如判斷 X >= 0 && Y >= 0 && X < width && Y < height 。


然而,處理可見路徑的繪圖部分,有以下的問題:


由於兩點構成一條線,因此有可能下一個點已經超出目前顯示地圖的範圍,但剛剛畫的點離 MapView 邊框還有段距離,依據上述的條件則不會畫出連接線,導致看起來像路徑已經斷掉,這種情況也會發生在移動地圖時,前一個點已超出範圍,則會看到之前的路線斷掉。


解決方式有兩種:



  • 使用一個 diff 數值來判斷,例如原先是判斷畫點得座標必須在 0 ~ mapView.getWidth() 且 0 ~ mapView.getHeight() 範圍才顯示,現在再加上一點數值來增大範圍

    • x + diff >= 0 && x <= mapView.getWidth() && y + diff >= 0 && y <= mapView.getHeight()



  • 使用兩個 list 來處理,假設整條路徑共有 n 個點,且第 x 點代表第一個被畫在螢幕上的點,那使用一個 stack 記錄 1~ x - 1 個點,第二個 list 記錄 x+1 ~ n 個點,以輔助繪出路徑 x-1 與 x 的路徑,以及 x 與 x+1 的路徑,以此解決路線斷掉的問題。如果並沒有一個 x 點會被畫出,那只好去找尋在可視地圖中,有沒有哪條線比較接近目前的位置,有的話則把它會出來,在此使用 cosine 的計算,挑選數值最接近 -1 的,代表夾角接近 180 度


第一種解法:


int diff = 100;
boolean useMoveTo = true;
for( int i=0; i<logs.length ; i+=2 )
{
    out = getPixelXYFromGeoValue( p, logs[i], logs[i+1] );

    if( out.x + diff < 0 || out.y + diff < 0 || out.x > mapView.getWidth() + diff || out.y > mapView.getHeight() + diff )
    {
        useMoveTo = true;
    }
    else if( useMoveTo )  // i = 0
    {
        myPath.moveTo(out.x, out.y);
        useMoveTo = false;
    }
    else if( i+2 != logs.length )
    {
        myPath.lineTo(out.x, out.y);
    }
    else
    {
        myPath.setLastPoint(out.x, out.y);
    }
}


第二種:


boolean useMoveTo = true;
int pCnt = 0;
HashMap<Point,String> hashMap = new HashMap<Point,String>();
Point check = getPixelXYFromGeoValue( p, srcLogs[gps_at], srcLogs[gps_at+1]);
ArrayList<Point> UseFF = new ArrayList<Point>();
ArrayList<Point> UsePP = new ArrayList<Point>();
                
for( int i=0; i<logs.length ; i+=2 )
{
    out = getPixelXYFromGeoValue( p, logs[i], logs[i+1] );

    if( hashMap.containsKey(out) )
        continue;
    hashMap.put(out, out.x+"-"+out.y);
                    
    if( out.x < 0 || out.y < 0 || out.x > mapView.getWidth() || out.y > mapView.getHeight() )
    {
        if( pCnt < 1 )
            UsePP.add( out );
        else
            UseFF.add( out );
        continue;
    }
    else if ( pCnt < 1 )
    {
        int size = UsePP.size();
        if( size > 0 )
        {
            myPath.moveTo( UsePP.get(size-1).x, UsePP.get(size-1).y);
            useMoveTo = false;
            UsePP.clear();
        }
    }
    else
    {
        int size = UseFF.size();
        if( size > 0 )
        {
            myPath.lineTo( UseFF.get(0).x, UseFF.get(0).y);
            if( size > 1 )
            {
                myPath.moveTo( UseFF.get(size-1).x, UseFF.get(size-1).y);
            }
            UseFF.clear();
            useMoveTo = false;
        }
    }

    pCnt++;
                    
    if( useMoveTo )
    {
        myPath.moveTo(out.x, out.y);
        useMoveTo = false;
    }
    else if( i+2 != logs.length )
    {
        myPath.lineTo(out.x, out.y);
    }
    else
    {
        myPath.setLastPoint(out.x, out.y);
    }
}

if ( pCnt < 1 )
{
    int size = UsePP.size();
    if( size > 1 )
    {
        double diff = 1;
        int checkAt = -1;
        for( int i=0 ; i+1<size; ++i )
        {
            double x1 = UsePP.get(i).x - check.x ;
            double y1 = UsePP.get(i).y - check.y ;
            double x2 = UsePP.get(i+1).x - check.x ;
            double y2 = UsePP.get(i+1).y - check.y ;
            double l1 = Math.sqrt( x1*x1 + y1*y1 );
            double l2 = Math.sqrt( x2*x2 + y2*y2 );
            double checkCos = (x1*x2 + y1*y2 )/l1/l2 ;
            if( checkCos < diff )
            {
                diff = checkCos;
                checkAt = i;
            }
        }

        if( checkAt > -1 )
        {
            myPath.moveTo( UsePP.get(checkAt).x, UsePP.get(checkAt).y);
            myPath.lineTo( UsePP.get(checkAt+1).x, UsePP.get(checkAt+1).y);
        }
        UsePP.clear();
    }
}
else
{
    int size = UseFF.size();
    if( size > 0 )
    {
        myPath.lineTo( UseFF.get(0).x, UseFF.get(0).y);
        UseFF.clear();
    }
}


雖然降低 GPS 個數可以提升路徑繪圖的效率,但也因此失去精準度,特別是 Zoom Level 越高時,會看到路徑偏移很嚴重,或許還可以搭配 Zoom Level 來決定是否要做 GPS 個數的刪減囉


2011年1月5日 星期三

Android 開發筆記 - 透過 Google Maps API 畫出 GPS 路徑

mygpspath2
紅色線即為GPS logs 的路徑


想要練習把一連串的 GPS logs 畫在 Google Map 上,首先要解決的問題有兩種:



  • 哪裡找 GPS logs

  • 要怎樣在 Google Maps 上畫線


關於第一個問題,可以在 OpenStreetMap 上找到測資,有個頁面是 GPS 軌跡,在那邊可以取得別人公開的 GPS logs,此例為 2011_01_03_Schwechat_Brno.gpx,然而下載的資料格式是 xml 檔案,而測試僅需使用經緯度,因此要稍微處理一下,以 Ubuntu 系統為例:


$ wget http://www.openstreetmap.org/trace/896382/data
$ grep -P '<trkpt lat="([0-9.]+)" lon="([0-9.]+)"' 896382.gpx | awk -F '"' '{ print $2, $4; }' > /tmp/test.data


如此一來,/tmp/test.data 就是只有經緯度的資料,但是行車紀錄的過程,可能停留在某處,而導致資料重複的問題,在此可以透過 hash 的概念,再更新 awk 的使用:


$ grep -P '<trkpt lat="([0-9.]+)" lon="([0-9.]+)"' 896382.gpx | awk -F '"' '{ if(!hash[$2$4] ){ hash[$2$4] = 1; print $2,$4;  } }'


然後我又比較偷懶,想乾脆轉成 double array 來使用,故流程多了點:


$ grep -P '<trkpt lat="([0-9.]+)" lon="([0-9.]+)"' 896382.gpx | awk -F '"' 'BEGIN{out=""}{ if(!hash[$2$4] ){ hash[$2$4] = 1; if( out != "" ) out = out "," ;  out = out""$2","$4; } }END{print "double logs[]={"out"};";}' > java.log


這時候 java.log 檔案內就是一個 double array 的宣告,把他複製起來擺到程式裡吧 :P 畢竟這只是個測試,先不用開檔讀資料的建法。


接著就是建立 Android Project 囉


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyGPSPath
Build Target: Google APIs/2.2/8
Application name: MyWidget
Package name: com.test.map.path
Create activity: MyGPSPath
Min SDK Version: 8


修改 AndroidManifest.xml,增加使用網路的權限以及使用 Google Maps lib


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.map.path"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyGPSPath"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <uses-library android:name="com.google.android.maps" />
    </application>
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


修改 layout,使用 MapView 並且填寫 Google Map API Key,另外新增一個按鈕,以供 debug 使用


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainlayout"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    
    <com.google.android.maps.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:clickable="true"
        android:apiKey="GOOGLE_MAP_API_KEY"
    />
    
    <Button
        android:text="Report"
        android:id="@+id/GetReport"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true">
    </Button>
</RelativeLayout>


程式碼:


package com.test.map.path;

import java.util.List;

import com.google.android.maps.*;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MyGPSPath extends MapActivity {

    StringBuilder debugOut;
    MapView mapView;

    // 從 openstreetmap 取出的 gps logs, 在此僅擺上頭尾兩點
    double logs[]={48.138050017878413,16.481179967522621,/* ... , */ 49.179430995136499,16.558635039255023}; 

    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        debugOut = new StringBuilder();
        
        if( ( mapView = (MapView) findViewById(R.id.mapview) ) != null )
        {
            mapView.setBuiltInZoomControls(true);
            mapView.setTraffic(true);
        }
        
        Button b;
        if( ( b = (Button) findViewById(R.id.GetReport) ) != null )
        {
            b.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    debugOut.append("logs length:"+logs.length+"\nbegin at:"+logs[0]+","+logs[1]+"\n");
                    AlertDialog alertDialog = new AlertDialog.Builder(MyGPSPath.this).create();
                    alertDialog.setTitle("debug");
                    alertDialog.setMessage(debugOut);
                    alertDialog.setButton("OK", new DialogInterface.OnClickListener(){
                        public void onClick(DialogInterface dialog, int which)
                        {
                            dialog.cancel();
                        }
                    } );
                    alertDialog.show();
                }
            });
        }
        
        List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
        ol.clear();
        ol.add(new MyMapOverlay());
        mapView.invalidate();
        
        MapController mapController = mapView.getController();
        if( mapController != null )
        {
            mapController.animateTo(new GeoPoint( (int) (logs[0]* 1000000) , (int)(logs[1]* 1000000) ) );
            mapController.setZoom(8);
        }
    }
    
    class MyMapOverlay extends com.google.android.maps.Overlay
    {
        private Point getPixelXYFromGeoValue( Projection p , double lat, double lng )
        {
            GeoPoint in = new GeoPoint((int) (lat * 1000000) , (int) (lng * 1000000) );
            Point out = new Point();
            p.toPixels(in, out);
            return out;
        }
        @Override
        public boolean draw(Canvas canvas, MapView mapView,boolean shadow, long when)
        {
            super.draw(canvas, mapView, shadow);
            
            //if( !shadow )
            {
                Projection p = mapView.getProjection();
                Point out;
                Path myPath = new Path();
                
                for( int i=0; i<logs.length ; i+=2 )
                {
                    out = getPixelXYFromGeoValue( p, logs[i], logs[i+1] );
                    if( i == 0 )
                        myPath.moveTo(out.x, out.y);
                    else if( i+2 != logs.length )
                        myPath.lineTo(out.x, out.y);
                    else
                        myPath.setLastPoint(out.x, out.y);
                }
                
                Paint myPaint = new Paint();
                myPaint.setColor(Color.RED);
                myPaint.setStyle(Paint.Style.STROKE);
                myPaint.setStrokeWidth(10);
                myPaint.setAlpha(70);
               
                canvas.drawPath(myPath, myPaint);
            }
            return true;
        }
    }
}


成果:


mygpspath
紅色線則是這條 GPS logs 所繪製出來的路徑


mygpspathdebug
按下右上角的按鈕,可以顯示一些訊息,如點的個數和起點位置,另外也可以擺一些 debug 訊息也不錯


其他部份,還有很多待改善的地方,例如 GPS logs 過長時,移動地圖所造成的重繪之效率問題


[Unix] 以 line 為單位,去除重複的資料

有一堆 GPS 資料想要使用,但在紀錄資料時,有可能某處停留較久,因此會有重複資料的問題。解決方式可以寫簡單的程式,但我一直想不出有哪個洽當的 Unix 指令可以用,就先挑 awk 來使用。


資料格式:


25.064903,121.526657,
25.064903,121.526657,
25.064903,121.526657,
25.064898,121.526573,
25.064898,121.526573,
25.064898,121.526573,
25.064890,121.526489,
25.064890,121.526489,
25.064886,121.526436,
25.064886,121.526436,


輸出目標:


25.064903,121.526657,
25.064898,121.526573,
25.064890,121.526489,
25.064886,121.526436,


指令:


$ awk '{ if( !a[$0] ){ print $0 ; a[$0] = 1; } }' in.txt


使用簡單的 hash 概念。但比較重要的是指令少用,紀錄一下,以節省以後的時間花費。


感謝許青蛙補充!若不在意順序還可以用 sort -u 就好


感謝 fin 補充,對於連續的資料可以採用 uniq 來處理就行啦,所以在這個情境下就是連續性的刪除,可以用:


$ cat in.txt | uniq