2017年11月12日 星期日

HTML2Canvas:用Javascript來做螢幕截圖

HTML2Canvas專案位置: https://html2canvas.hertzen.com/


簡介: HTML2Canvas(以下簡稱H2C)是一個Javascript工具用來將HTML元素轉換成Canvas,這樣就能很容易的把元素轉換成圖檔


簡單的範例

擷取整個網頁,並且將結果顯示在當前網頁上
html2canvas(document.body, {
  onrendered: function(canvas) {
    document.body.appendChild(canvas);
  }
});
擷取整個網頁並只取從左上角開始300x300的畫面,並且將結果顯示在當前網頁上
html2canvas(document.body, {
  onrendered: function(canvas) {
    document.body.appendChild(canvas);
  },
  width: 300,
  height: 300
});
在上面兩個範例可以看到一個很主要的事件叫做onrendered,這個事件會在完成擷取所指定之元素之後被觸發,也就是可以針對擷取結果(Canvas)做操作的地方。

如何下載擷取結果

這個工具的擷取結果是Canvas,所以只需要用Canvas本身的匯出功能就可以了(Canvas支援輸出jpeg, png)

html2canvas(document.body, {
  onrendered: function(canvas) {
    var image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); //結果會是被Base64編碼後的圖片,但是要觸發下載必須先把型態改成octet-stream(用來騙瀏覽器而已,它還是png格式)
    window.location.href = image;
  }
});

Troubleshooting: 怎麼辦!!!SVG沒辦法被截取

SVG因為某些原因讓H2C沒辦法正常的擷取,解決方案就是用canvg先把SVG轉成Canvas之後再一起擷取

2016年10月8日 星期六

Simple Location Sender: 團隊動態追蹤的好範例


這個是什麼? What



這是一個使用者GPS位置回報系統的App端範例



為什麼需要這個? Why



這個範例嘗試用最簡單的方式來撰寫,也因此不論是修改還是學習都非常容易



什麼地方會用到? Where



當你在開發任何需要使用者回報地理位置給伺服器的應用



什麼時候會用到? When



在處理使用者回報資訊給伺服器的時候



誰會需要? Who



任何想要開發相關追蹤應用的開發者或著任何對於使用者端與伺服器端之間溝通有興趣的人



這個如何實作的? How



Project: https://github.com/lienching/LocationReportSender


這個範例使用了幾個開源函式庫
  • geojson : 用來處理 json
  • okhttp3 : 用來處理網路操作
  • realm : 資料庫

這整個範例可以分為四個部份
  1. LocationManager : 負責處理GPS位置,這樣就能得到使用者的位置
  2. JSONBuilder : 建立JSON檔,作為與伺服器溝通的格式
  3. TrackingService : 讓使用者位置的發送可以在Background執行
  4. BookMark(BookMarkHolder) : 這個範例使用Realm作為背後的資料庫,並使用AutoCompleteTextView來實現書籤選單,這個範例也支援將書籤輸入與輸出(格式為csv)

2016年6月12日 星期日

地圖圖層隨便你疊,地圖樣式你決定 - TileStache (基本介紹篇)


前言Preface



使用OpenStreetMap有一個很大的優勢,就是基本上地圖的樣式都是可以自主來去做定義。 但是其實OpenStreetMap原本的自訂圖塊( Custom Tiles ) 並不是我們想像中的那麼的容易操作而且有其限制( 沒辦法將多圖層合併為單一圖層,資源消耗大 ),但是感謝廣大的開源社群相關的工具如雨後春筍一般的一個個冒出來( 如 TileCache, OpenLayer , TileStache ),讓 OpenStreetMap 的地圖多樣性有了革命性的改變,因為可以將多圖層合併在雲端完成,也就可以讓設計者與地圖製作者們可以專心繪製自己的圖層,剩下的合併交給那些工具就好。

TileStache介紹
Brief introduction of TileStache



這次要介紹的TileStache它的前身是TileCache,它們都是用來做圖層渲染(多圖層渲染成一個),這對於設計者與地圖製作者都非常的方便,當然對於一些效能不太好的客戶端也可以帶來一些不錯的效能提升,而TileStache的設計目的是為了讓整個工具更貼近設計者與地圖製作者,讓它們可以在越少的步驟中產生他們所需要的東西。


TileStache設定
Installation of TileStache




TileStache取得請到 官網 or My Github , 目前這個工具只能用在Linux或OSX上


TileStache 是以Python做為開發語言,所以我們所需要的套件如下:




TileStache兩種使用方式:


  1. 直接用Script資料夾內的python script
    # 用來啟動伺服器(服務)
    tilestache-server.py
    
    # 用來快取圖檔(網路圖資)
    tilestache-seed.py
    
    # 用來產生自定義圖層
    tilestache-render.py
    
  2. 將tilestache工具加入指令集
    python setup.py install
    



從TileStache取得圖塊
Requesting Tiles from TileStache




  • 從 HTTP 取得
    1. 取得單一圖塊 Single Tile
      格式 : /{圖層名稱}/{縮放等級}/{x}/{y}.{副檔名(e.g. png, jpg}
      範例 : http://example.org/osm/12/656/1582.png
    2. 動態地圖(類似Google Map)
      格式 : /{圖層名稱}/preview.html
      範例 : http://example.org/osm/preview.html

  • 使用TileStache的API
    1. TileStache.getTile(layer, coord, extension[, ignore_cached=FALSE]): 直接取得圖塊 Get Tile Directly
      • layer: 想要取得得圖層( Core.Layer )
      • coord: 傳入所要取得的位置( ModestMaps.Core.Coordinate )
      • extension: 副檔名("jpg", "png") (String)
      • ignore_cached: (可有可無,預設為FALSE)是否忽略快取圖塊 (Boolean) 
    2. TileStache.requestHandler(config, path_info[,query_string=None][, script_name='']): 透過HTTP Requesting取得圖塊 Get Tile by HTTP Requesting
      • config: JSON設定檔的路徑(String) 或 TileStache.Config.Configuration物件(需要 cache, layers, dirpath 三個參數)
      • path_info: 傳入Request URL, 格式同從HTTP取得之取得單一圖塊(範例: /osm/12/656/1582.png )
      • query_string: (可有可無,預設為None)使用 JSONP 才會用到
      • script_name: (可有可無,預設為'') 給CGI的腳本,用來判斷是否轉址成功

2016年4月7日 星期四

創造自己的App市集( Using F-Droid )


介紹Introduction


F-Droid根據官方說法是一個在Android平台上的FOSS(Free and Open Source Software 免費與開源軟體)可安裝目錄程式,顧名思義就是非常類似我們所熟知了App市集囉。
那肯定會有人問說,既然有了Google Play為什麼還要另外自己去建立一個市集呢? 那是因為有些地方是沒辦法連得上Google Play,或著不喜歡目前市面上所有App市集的上架規則。 那用F-Droid架自己的市集有什麼好處呢?
  • 私人的程式庫,小小程式樂園
  • 自己的市集,自己說的算
  • 開源軟體,自己的模式自己建立



環境建立Setup



首先一定是要下載好工具囉

  • Linux

  • 有套件管理工具的好處就是一件完成安裝!

     sudo apt-get install fdroidserver
    

    當然還有一些進階的操作,例如如果有希望讓F-Droid自己把Source code編譯起來的話,可以用以下的指令(假設你什麼都沒有...)

    # 安裝一些用來編譯程式程式的套件
    sudo apt-get install openjdk-7-jdk libstdc++6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386 
    # 從android官網用wget抓android-sdk
    cd ~
    # 筆者目前最新版本是r24.4.1,建議讀者到 ( http://developer.android.com/sdk/index.html#Other ) 去查看最新的SDK
    wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
    # 用tar解壓縮我們剛剛的檔案
    tar zxcf android-sdk_r24.4.1-linux.tgz
    # 接下來我們要把ANDROID_HOME設定起來(假設你解壓縮出來的資料夾名字為 android-sdk-linux )
    export ANDROID_HOME=~/android-sdk-linux
    # 再來就是要把ANDROID_HOME丟進我們的 .bashrc (如果是zsh就會是 .zshrc ) 這樣我們的設定就會被記錄住了
    echo 'export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools' >> .bashrc
    

    還有其他一些有的沒有的選項設定,可以參考這邊

  • OSX

  • Using MacPorts

    port install fdroidserver
    

    Using HomeBrew

    brew install fdroidserver
    

    Using easy_install

    sudo easy_install fdroidserver
    
  • Windows

  • Windows基本上Command-Line base的東西就蠻殘缺的所以會需要借助Cygwin,不囉嗦~建立方式如下

    1. 下載 Cygwin (載點)
    2. 安裝 Cygwin , 並在選擇安裝套件的時候選擇安裝以下套件
      • gcc-core
      • git
      • openssh
      • python
      • python-pyasn1
      • python-imaging
      • python-magic
      • python-paramiko
      • python-requests
      • python-setuptools
      • rsync
      • wget
    3. 開啟一個Cygwin的bash介面,然後輸入
    4. easy_install fdroidserver
      
  • From Source

  •  git clone https://gitlab.com/fdroid/fdroidserver.git
     cd fdroidserver
     virtualenv --system-site-packages env/
     . env/bin/activate
     pip install -e .
     python setup.py install
    

接下來就是建立環境囉 (用Linux做示範)

# 伺服器這邊筆者選用Nginx,當然Apache Web Server也是可以
sudo apt-get install nginx
# 建立F-Droid專用的資料夾
sudo mkdir /usr/share/nginx/www/fdroid
sudo chown -R $USER /usr/share/nginx/www/fdroid
cd /usr/share/nginx/www/fdroid
# 初始化環境
fdroid init

然後在當前的資料夾裡面就會多了一個repo的資料夾,這邊就是我們放程式(.apk)的地方,程式放完就可以輸入下面的指令,來產生MetaData(程式敘述資料)

fdroid update --create-metadata

這樣我們的 Repo Server 已經建立完成囉~ 接下來把位置放進F-Droid的Client就可以連接到了



Config.py 設置


基本上要改的就這幾個

# Repo Server的網址
repo_url = "https://MyFirstFDroidRepo.org/fdroid/repo"
# Repo Server的名字
repo_name = "My First F-Droid Repo Demo"
# Repo Server的Icon
repo_icon = "fdroid-icon.png"
# Repo Server的敘述
repo_description = ""

改完之後要重置伺服器,用下方的指令

fdroid server update -v

2016年3月5日 星期六

Step By Step 簡易Mapsforge App開發:MapUpdateManager


目標Achievements


這個類別是用來做圖資更新檢查與更新


程式碼Code


package lien.ching.maptracker.api;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import lien.ching.maptracker.Constant;

/**
 * Created by lienching on 12/1/15.
 * In this class, I had no idea how to update a file by using DownloadManager
 * so I just try to use standard HTTP Download as the method
 */
public class MapUpdateManager extends AsyncTask {

    private ProgressDialog dialog;
    private PowerManager.WakeLock wakeLock;
    private Context context;
    private Activity activity;
    public MapUpdateManager(Context context, Activity activity){
        super();
        this.activity = activity;
        this.context = context;
    }
    @Override
    protected Boolean doInBackground(String... params) {
        //For Details, please check http://stackoverflow.com/a/3028660
        String continent = params[1];
        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        File outputFile = new File(Constant.PATH_MAPSFORGE+params[0]);
        try{
            URL url = new URL("http://download.mapsforge.org/maps/"+params[0]);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();

            if(connection.getResponseCode() != HttpURLConnection.HTTP_OK){
                Toast.makeText(context,"Connection Fail..."+connection.getResponseCode(),Toast.LENGTH_SHORT).show();
                return false;
            }
            input = connection.getInputStream();


            if (outputFile.lastModified() < connection.getLastModified()){ //if the date is older than the latest file then close the download process
                input.close();
                output.close();
                return true;
            }
            dialog.setMessage("Map Source Updating");
            outputFile.delete();
            outputFile.createNewFile();
            output = new FileOutputStream(outputFile);
            byte data[] = new byte[8192];
            int count;
            while((count = input.read(data)) != -1){
                if(isCancelled()){
                    input.close();
                    return false;
                }

                output.write(data,0,count);
            }
        }catch (Exception e){
            Log.e("MapDownloadManager","Writing Data:"+e.toString());
            return false;
        }finally {
            try {
                if (output != null)
                    output.close();
                if (input != null)
                    input.close();

            } catch (IOException ignored) { }

            if (connection != null)
                connection.disconnect();
        }
        return false;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, activity.getClass().getName());
        wakeLock.acquire();
        dialog = new ProgressDialog(activity);
        dialog.setMessage("Map Source Checking");
        dialog.setCancelable(false);
        dialog.setIndeterminate(true);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.show();
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
        if(dialog.isShowing()){
            dialog.dismiss();
        }
        if(wakeLock.isHeld())
            wakeLock.release();
        try {
            if (!aBoolean)
                Toast.makeText(context, "Download error", Toast.LENGTH_LONG).show();
            else
                Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
        }
        catch (Exception e){
            Log.d("MapUpdateManager", e.toString());
        }
    }
}



說明Explanation


這個整體程式使用的下載方式請參照這裡
這邊程式運作的方式是Async,可以在這邊瞭解更多
第57~61行:檢查圖資是不是比資料庫的還要舊,如果是就不繼續做更新的動作,否則就繼續做

Step By Step 簡易Mapsforge App開發:EnvCheck


目標Achievements


這個類別的工作是去偵測使用者是否有我們所需的地圖資料,若有那就去偵測是否有新的版本,否則就去下載。這個類別還做了一件事就是將圖資進行渲染(為了讓圖資檔案比較小,所以使用了向量圖的儲存方式)並喂給MapView去做顯示。


程式碼Code


package lien.ching.maptracker.api;

import android.app.Activity;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.labels.LabelLayer;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;

import java.io.File;
import java.util.concurrent.TimeUnit;

import lien.ching.maptracker.Constant;
import lien.ching.maptracker.R;
import lien.ching.maptracker.overlay.NowLocationLayout;

/**
 * Created by lienching on 11/27/15.
 * This class is use for checking if the device have the required map file or not.
 */
public class EnvCheck {
    private Context context;
    private String externalpath = Environment.getExternalStorageDirectory().getPath();
    private MapDownloadManager mapDownloadManager;
    private MapUpdateManager mapUpdateManager;
    private mapDownloadManager downloadManger;
    private Activity activity;
    private MapView mapView;
    private NowLocationLayout locationLayout;
    public EnvCheck(MapView mapView,NowLocationLayout locationLayout,Context context,Activity activity){
        this.activity = activity;
        this.context = context;
        this.mapView = mapView;
        this.locationLayout = locationLayout;
    }
    public EnvCheck(Context context,Activity activity){
        this.activity = activity;
        this.context = context;
        this.mapView = null;
    }

    public void CheckAndDownload(String continent, String sourcefile) {
        String mapfile = continent+"/"+sourcefile;
        if(!this.isMapResourceExist(mapfile)) {
            downloadManger = new mapDownloadManager(mapView,locationLayout, context, mapfile);
            Thread thread = new Thread(downloadManger);
            thread.run();
        }
        else{
            MapFile targetFile = new MapFile(new File(Constant.PATH_MAPSFORGE+mapfile));
            TileCache tileCache = AndroidUtil.createTileCache(context, "mapcache", mapView.getModel().displayModel.getTileSize(), 1f, mapView.getModel().frameBufferModel.getOverdrawFactor());
            TileRendererLayer tileRendererLayer = AndroidUtil.createTileRendererLayer(tileCache,mapView.getModel().mapViewPosition,targetFile, InternalRenderTheme.OSMARENDER,true,true);
            tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.OSMARENDER);
            mapView.getLayerManager().getLayers().add(tileRendererLayer);
        }
    }


    public boolean isMapResourceDirExist(final String continent){
        File dir = new File(Constant.PATH_MAPSFORGE+continent);
        if(!dir.exists()) {
            if (!dir.mkdirs()) {
                Log.e("EnvCheck","Dir create failed");
                return false;
            }
            Log.d("EnvCheck","Dir Create Sucess!");
        }
        return true;
    }
    public boolean isMapResourceExist(final String mapfile){
        if(!(new File(externalpath+"/mapsforge").exists()))
            createDirectory();
        File searchFile = new File(externalpath+"/mapsforge/maps/"+mapfile);
        if(searchFile.exists())
            return true;
        return false;
    }

    public void createDirectory(){
        File target = new File(externalpath+"/mapsforge/maps");
        target.mkdirs();
        if(target.exists()&&target.isDirectory()){
            Toast.makeText(context, R.string.toast_dir_success, Toast.LENGTH_SHORT).show();
        }
        else{
            Toast.makeText(context, R.string.toast_dir_fail_retry,Toast.LENGTH_SHORT).show();
            target.mkdir();
            if(target.exists()&&target.isDirectory()){
                Toast.makeText(context, R.string.toast_dir_success, Toast.LENGTH_SHORT).show();
            }
            else{
                Toast.makeText(context, R.string.toast_dir_fail, Toast.LENGTH_SHORT).show();
            }
        }
    }
}


說明Explanation


第64行:建立圖資渲染暫存區,For Detail 第65~66行: 建立圖資圖層並設定地圖風格

Step By Step 簡易Mapsforge App開發:NowLocationLayout


目標Achievements


這個類別顧名思義就是要去顯示目前使用者的地理位置並顯示在我們的地圖上面。


程式碼Code


package lien.ching.maptracker.overlay;

import android.Manifest;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.location.Criteria;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.widget.TextView;

import org.mapsforge.core.graphics.Bitmap;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.graphics.GraphicFactory;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.Point;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.Layer;
import org.mapsforge.map.layer.overlay.Circle;
import org.mapsforge.map.layer.overlay.Marker;
import org.mapsforge.map.layer.overlay.Polyline;
import org.mapsforge.map.model.MapViewPosition;
import org.mapsforge.map.android.util.AndroidSupportUtil;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import lien.ching.maptracker.R;

/**
 * Created by lienching on 12/2/15.
 */


//This class is using LocationManager to provided location information it may cause some issue, for more reliable location information use  Google Play services location APIs(http://developer.android.com/training/location/index.html)

public class NowLocationLayout extends Layer implements LocationListener,GpsStatus.Listener,ActivityCompat.OnRequestPermissionsResultCallback {


    private Activity activity;
    private MapViewPosition mapViewPosition;
    private MapView mapView;
    private LocationManager locationManager;
    private List history_path;

    private Boolean track;

    //For drawing purpose (http://mapsforge.org/docs/0.6.0/org/mapsforge/map/android/graphics/AndroidGraphicFactory.html)
    private static final GraphicFactory GRAPHIC_FACTORY = AndroidGraphicFactory.INSTANCE;
    private Circle accuray_circle;
    private Marker loc_marker;
    private Polyline usr_path;




    //Constructor
    public NowLocationLayout(Activity activity,MapViewPosition mapViewPosition,MapView mapView){
        this.activity = activity;
        this.mapViewPosition = mapViewPosition;
        this.mapView = mapView;
        this.locationManager = (LocationManager) activity.getSystemService(activity.LOCATION_SERVICE);

        track = false;

        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L,0, this);
        //Check if location services available
        if(!locationManager.isProviderEnabled (LocationManager.GPS_PROVIDER)){
            activity.startActivityForResult(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0);
        }

        locationManager.addGpsStatusListener(this);//To add satellite listener


        //setDisplayModel(this.displayModel) set circle, polyline, marker etc... in to mapview's display model, so DisplayModel.getTileSize() won't point to a null object.
        accuray_circle = new Circle(null,0,getDefaultCircleFill(),getDefaultCircleStroke());//Circle(LatLong,radius,color,stroke);
        accuray_circle.setDisplayModel(mapView.getModel().displayModel);

        usr_path = new Polyline(getDefaultPolylineStroke(),GRAPHIC_FACTORY);
        usr_path.setDisplayModel(mapView.getModel().displayModel);

        //AndroidGraphicFactory.convertToBitmap() for detail please check AndroidGraphicFactory
        Drawable drawable = activity.getResources().getDrawable(R.drawable.locationmarker);
        Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(drawable);
        loc_marker = new Marker(null, bitmap, 1, 0);
        loc_marker.setDisplayModel(mapView.getModel().displayModel);

        history_path = new LinkedList();//record user path
    }

    //Transfer Function

    private LatLong LocationToLatLong(Location location){
        LatLong latlong = new LatLong(location.getLatitude(),location.getLongitude());
        return latlong;
    }

    //Getter
    protected Paint getPaint(int color, float strokeWidth, Style style){
        Paint paint = GRAPHIC_FACTORY.createPaint();
        paint.setColor(color);
        paint.setStrokeWidth(strokeWidth);
        paint.setStyle(style);
        return paint;
    }

    private Paint getDefaultCircleFill() {
        return this.getPaint(GRAPHIC_FACTORY.createColor(48, 0, 0, 255), 0, Style.FILL);
    }

    private Paint getDefaultCircleStroke() {
        return this.getPaint(GRAPHIC_FACTORY.createColor(160, 0, 0, 255), 2, Style.STROKE);
    }

    private Paint getDefaultPolylineStroke() {
        return this.getPaint(GRAPHIC_FACTORY.createColor(160, 0, 255, 0), 20, Style.STROKE);
    }


    //Enable Location Tracking
    public void startTrack(){
        this.track = true;
    }


    @Override
    public void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint) {
        if(track) {
            loc_marker.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
            accuray_circle.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
        }
        List temp = usr_path.getLatLongs(); //To get all the LatLong that had been drew
        temp.removeAll(temp); //Remove all
        temp.addAll(history_path);//To re-add all path
        usr_path.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
    }

    @Override
    public void onGpsStatusChanged(int event) {

    }

    @Override
    public void onLocationChanged(Location location) {

        LatLong latLong = this.LocationToLatLong(location);
        loc_marker.setLatLong(latLong);
        accuray_circle.setLatLong(latLong);
        history_path.add(latLong);

        accuray_circle.setRadius(location.getAccuracy());

        mapViewPosition.setCenter(latLong);
        mapViewPosition.setZoomLevel((byte) 17);
        requestRedraw();

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {}

    @Override
    public void onProviderEnabled(String provider) {
        locationManager.requestLocationUpdates(provider, 1000L, 0, this);
        if(locationManager.GPS_PROVIDER.equals(provider)) {
            track = true;
        }
    }

    @Override
    public void onProviderDisabled(String provider) {
        if(locationManager.GPS_PROVIDER.equals(provider)){
            track = false;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {}

}


說明Explanation


第79行:這行是用來設定GPS更新頻率,參數大概為(地理資訊提供(GPS, Network), 更新時間(單位:毫秒), 更新距離(當距離超過就更新), 接收資訊的Listener),For Detail
第90、93、99行:如果不把那些物件的DisplayModel設定成MapView的,會出現NullPointerException
第145~147行:第145行是Pass by Reference所以我們可以直接用temp去操作,然後我們先把原本的List清空,再把歷史紀錄再放回去
第164行:這句是利用GPS準確度來當作圓的半徑,For Detail