2016年2月19日 星期五

Step By Step 簡易Mapsforge App開發:MainActivity


目標Achievements


這個Class是我們App的導入點,我稱這種類別為Main類別,那我們在這個類別中我們就需要去做整個程式大方向的規劃.

程式碼Code


package lien.ching.maptracker;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;

import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
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.datastore.MapDataStore;
import org.mapsforge.map.datastore.MultiMapDataStore;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;

import java.io.File;

import lien.ching.maptracker.api.EnvCheck;
import lien.ching.maptracker.overlay.NowLocationLayout;

/**
 * Created by lienching on 11/27/15.
 */
public class MainActivity extends Activity {
    private MapView mapView;
    private MapDataStore taiwanMap,worldMap;
    private MultiMapDataStore multiMapDataStore;
    private TileCache tileCache;
    private TileRendererLayer tileRendererLayer;
    private EnvCheck envCheck;
    private NowLocationLayout nowLocationLayout;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AndroidGraphicFactory.createInstance(this.getApplication()); //Initiation Mapsforge Library
        mapView = new MapView(this);
        mapView.setClickable(true);
        mapView.getMapScaleBar().setVisible(true);
        mapView.setBuiltInZoomControls(true);
        mapView.getMapZoomControls().setZoomLevelMin((byte) 5);
        mapView.getMapZoomControls().setZoomLevelMax((byte) 20);
        mapView.getModel().mapViewPosition.setMapPosition(new MapPosition(new LatLong(23.6, 121), (byte) 7));
        //Disable hardware acceleration at mapview, for more detail about this line of code please check (https://goo.gl/v5weg9)
        mapView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        setContentView(mapView);
        nowLocationLayout = new NowLocationLayout(this,mapView.getModel().mapViewPosition,mapView);
        envCheck = new EnvCheck(mapView,nowLocationLayout,this.getApplicationContext(),this);
        //To keep screen on(http://developer.android.com/training/scheduling/wakelock.html)
        this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    }

    @Override
    protected void onStart() {
        super.onStart();

        envCheck.CheckAndDownload("world", "world-lowres-0-7.map"); //Check if the map source exist
        envCheck.CheckAndDownload("asia", "taiwan.map");
        mapView.getLayerManager().getLayers().add(nowLocationLayout);//Add Location Layer
        nowLocationLayout.startTrack();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        this.mapView.destroyAll();
    }

}


說明Explanation


第41行:這一行因為Mapsforge所用的androidsvg必須要做一些初始化,所以我們在第一行就將他呼叫避免後續問題
第50行:用來關閉硬體加速( For Detail
) 第55行:因為這個範例是要用來紀錄使用者的移動軌跡,所以需要使用這行(For Detail)
第63~64行:檢查檔案是否存在或有沒有新版本,(詳細運作請參考EnvCheck)
第65~66行:因為前兩行需要花時間但也必須要讓使用者知道程式有在運作,所以先讓定位開始運作

2016年2月5日 星期五

Step By Step 簡易Mapsforge App開發:Introduction


前言


先前筆者有稍微介紹了Mapsforge,不知道大家目前有沒對於Mapsforge產生了一點興趣呢? 這次筆者打算用一個比較不抽象的方式來教大家Mapsforge要如何使用~ 本次專案的程式碼將會發佈在 https://github.com/lienching/MapTracker

前置準備Preparation


  1. 使用者所需具備能力
    1. 基本的Java概念
    2. 基本的Android APP開發概念
    3. 熱愛開發的心
  2. 專案所需API(mapsforge系列載點、androidsvg載點、kXML2載點)
    1. mapsforge-map
    2. mapsforge-core
    3. mapsforge-map-android
    4. mapsforge-map-reader
    5. androidsvg
    6. kXML2


AndroidManifest設置Setup


權限部分(詳細請參考這裡)
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

這個教學將會有6個部分,分別為 然後他們有一個共用的常數庫 Constant.java
package lien.ching.maptracker;

import android.os.Environment;

/**
 * Created by lienching on 11/27/15.
 */
public class Constant {
    public static final String PATH_MAPSFORGE = Environment.getExternalStorageDirectory().toString() + "/mapsforge/maps/";
    public static final String PATH_WORLDMAP = PATH_MAPSFORGE + "world/world-lowres-0-7.map";
    public static final String PATH_TAIWANMAP = PATH_MAPSFORGE + "asia/taiwan.map";

}

2015年11月3日 星期二

Mapsforge:Get Started


前置準備Preparation


Mapsforge是一個很棒的Vector Map Library,相信讀者一定很想要趕快著手開發 但在開始前需要先準備好一些東西

API部分

  • mapsforge-core: 這是Mapsforge的核心Library所有相關Mapsforge的Library都是以他為基礎
  • mapsforge-map: 這個是負責Mapsforge中地圖相關處裡的Library
  • mapsforge-map-android: 這個Library中提供了一些如MapView, AndroidGraphicFactory等讓Android可以充分使用Mapsforge的物件
  • mapsforge-map-reader: 這個Mapsforge的.map檔的解析器Library
以上可以在 Mapsforge專案 中找到

圖資部分

這個範例是離線地圖,所以首先你需要2個圖資
  • 1. world-lowres-0-7.map
  • 2. taiwan.map
以上兩個檔案須放在外部儲存空間中的mapsforge/maps/(所屬的洲)裡 所以如果兩個檔案的完整路徑則為
  • 1. mapsforge/maps/world/world-lowres-0-7.map
  • 2. mapsforge/maps/asia/taiwan.map

範例程式Sample

(本範例所使用的Mapsforge版本為0.6.0-rc1)




SampleMapView.java
import android.app.Activity;
import android.os.Bundle;

import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.MapPosition;
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.datastore.MapDataStore;
import org.mapsforge.map.datastore.MultiMapDataStore;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.rendertheme.InternalRenderTheme;

import java.io.File;

/**
 * Created by lienching on 10/17/15.
 */
public class SimpleMapView extends Activity {

    private MapView mapView;
    private MultiMapDataStore multiMapDataStore;
    private MapDataStore worldMap,taiwanMap;
    private TileCache tileCache;
    private TileRendererLayer tileRendererLayer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AndroidGraphicFactory.createInstance(this.getApplication());//使用Mapsforge Library必須呼叫此方法來初始化
        mapView = new MapView(this);
        mapView.setClickable(true);
        mapView.getMapScaleBar().setVisible(true);
        mapView.setBuiltInZoomControls(true);
        mapView.getMapZoomControls().setZoomLevelMin((byte) 5);
        mapView.getMapZoomControls().setZoomLevelMax((byte) 20);

        worldMap = new MapFile(new File(Constant.PATH_WORLDMAP));
        taiwanMap = new MapFile(new File(Constant.PATH_TAIWANMAP));
        multiMapDataStore = new MultiMapDataStore(MultiMapDataStore.DataPolicy.RETURN_ALL);

        tileCache = AndroidUtil.createTileCache(this, "mapcache", mapView.getModel().displayModel.getTileSize(), 1f, this.mapView.getModel().frameBufferModel.getOverdrawFactor());
        setContentView(mapView);
    }

    @Override
    protected void onStart() {
        super.onStart();
        multiMapDataStore.addMapDataStore(worldMap,true,true);
        multiMapDataStore.addMapDataStore(taiwanMap,false,false);
        //multiMapDataStore.setStartPosition(new LatLong(23, 121));
        //multiMapDataStore.setStartZoomLevel((byte) 7);
        tileRendererLayer = new TileRendererLayer(tileCache,multiMapDataStore,mapView.getModel().mapViewPosition,false,true, AndroidGraphicFactory.INSTANCE);
        tileRendererLayer.setXmlRenderTheme(InternalRenderTheme.OSMARENDER);

        mapView.getModel().mapViewPosition.setMapPosition(new MapPosition(new LatLong(23, 121), (byte) 7));
        mapView.getLayerManager().getLayers().add(tileRendererLayer);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.mapView.destroyAll();
    }
}

Constant.java
import android.os.Environment;

/**
 * Created by lienching on 10/19/15.
 */
public class Constant {
    private static final String PATH_MAPSFORGE = Environment.getExternalStorageDirectory().toString() + "/mapsforge/maps/";
    public static final String PATH_WORLDMAP = PATH_MAPSFORGE + "world/world-lowres-0-7.map";
    public static final String PATH_TAIWANMAP = PATH_MAPSFORGE + "asia/taiwan.map";

}


Q&A


1. 如果我很懶,我有甚麼辦法可以讓App自己下載圖資呢?

那妳可以建立一個專門下載圖資的類別,範例如下
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;


/**
 * Created by lienching on 10/14/15.
 * The purpose of this class is to define the map resource download methods, so we
 * can easily download a map resource by this class.
 */
public class MapDownloadManager{
    private DownloadManager downloadManager;
    private DownloadManager.Request request;
    private String external_path = Environment.getExternalStorageDirectory().toString();
    private Context context;
    public MapDownloadManager(Context context){
        this.context = context;
        downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);

    }
    public void downloadMapResource(String mapfile){
        Uri download_URI = Uri.parse("http://download.mapsforge.org/maps/"+mapfile);
        request = new DownloadManager.Request(download_URI);
        //Set the network type which is allow to process the download request
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
        //Set whether this download may proceed over a roaming connection.
        request.setAllowedOverRoaming(false);
        request.setTitle("Mapsforge Map Download");
        request.setDestinationInExternalPublicDir("/mapsforge/maps/",mapfile);
        downloadManager.enqueue(request);
    }
}

之後只要在主類別中呼叫就能使用囉~

2015年10月27日 星期二

Mapsforge: Introduction


前言

    經過筆者跟 OSMDROID 過招多次之後,我發覺它在離線地圖的實現上真的不怎麼理想,而筆者對於向量圖的處理技術並不成熟,所以筆者就決定要搜尋其他Library來取代OSMDROID,也因此找到了Mapsforge這個Library。


Mapsforge簡介

    Mapsforge提供一個免費且開源的離線向量圖函式庫給Android與Java平台,這個函式庫的特色就是可以用簡短的幾行程式碼就能輕易的實現OpenStreetMap相關應用程式的開發。這個函式庫計畫起初是由德國柏林自由大學的資訊科學研究所(Computer Science Institute of Freie Universität Berlin)在2008年發起並開始開發並以LGPL3開源,而目前這個函式庫是由眾多志工一起維護。


為什麼Mapsforge


  • 可以在本機直接渲染OpenStreetMap地圖資料 - 實現離線地圖
  • 強力且具彈性的圖層API - 讓地圖更加豐富
  • 壓縮的圖資檔案格式 - 圖資檔案小離線地圖無負擔
  • 支援多點觸控 - 定義更多手勢實現更多功能
  • 支援多種OSM標籤 - 地圖顯示更細緻展現更多
  • 只要Android版本在2.3以上就能輕易使用 - 讓你的App更多人能用
  • 函式庫檔案小且其中已包含圖片 - App更輕巧
  • API的操作非常簡單還有大量範例可以參考 - 輕鬆入門
  • 100% 免費且是LGPL3開源 - 開發成本DOWN~靈活性Up!!!



開始用Mapsforge開發OSM應用

2015年8月10日 星期一

OSMDROID: Overlay


介紹Introduction


Overlay 可以說是OSMDROID中一個恨重要的一部分,因為這個物件可以讓我們的地圖擁有更多的功能,像是在地圖上加上其他地圖(如鐵軌地圖等),或著是增加判定(如按住時要出現什麼功能)

範例Example


首先我們先建立一個類別,然後將他繼承Overlay 繼承之後他會顯示錯誤,因為必須要在Override
draw(Canvas canvas, MapView mapView, boolean b)
這個method
public class DemoOverlay extends Overlay {
    @Override
    protected void draw(Canvas canvas, MapView mapView, boolean b) {}//繪製地圖
}
當我們Override之後還是會發現,IDE依舊顯示說他錯誤,原因在於Overlay本身沒有預設建構元,所以我們必須針對我們所定義的類別寫一個建構元, 但就如同我前面所寫,Overlay本身沒有預設建構元,那我們就會想問那要怎麼樣才能正確的定義好呢?這時候我們就要來思考Overlay這個類別特性了 Overlay他是一個建立在MapView上面的一層圖層,而MapView是Activity的內容之一,那我們就可以針對這點去查一下API,果然Overlay本身就有一個建構元叫做
public Overlay(Context context)
這時我們的IDE就不會再報錯了,那我們的基本型也就完成了
public class DemoOverlay extends Overlay {
    public DemoOverlay(Context ctx){ //初始建構元,如果沒有這個會報錯誤,因為Overlay本身沒有定義空白建構元
        super(ctx);
    }
    @Override
    protected void draw(Canvas canvas, MapView mapView, boolean b) {} //繪製地圖
}
以下是個小小的範例,這個程式碼是在說當使用者按住螢幕的時候,就會顯示"Long Press"的提示文字
public class DemoOverlay extends Overlay {
    Context ctx;
    public DemoOverlay(Context ctx){ //初始建構元,如果沒有這個會報錯誤,因為Overlay本身沒有定義空白建構元
        super(ctx);
        this.ctx = ctx;
    }
    @Override
    protected void draw(Canvas canvas, MapView mapView, boolean b) { //繪製地圖

    }

    @Override
    public boolean onLongPress(MotionEvent e, MapView mapView) { //判定按住時,要做什麼
        Toast.makeText(ctx,"Long Press",Toast.LENGTH_SHORT);
        return super.onLongPress(e, mapView);
    }
}

那我們現在完成Overlay了,但是你會發現為什麼我們的Overlay沒有作用呢? 那是因為我們還需要把他載入到我們的MapView當中 載入方式非常簡單
DemoOverlay demoOverlay = new DemoOverlay(this);//宣告一個DemoOverlay的物件
mapView.getOverlays().add(demoOverlay);//mapView是你所宣告的MapView物件

mapView加入Overlay有先後順序,所以我建議如果是顯示層的Overlay(如鐵路地圖、標示等)要比判定層的Overlay(如長按增測)還要早加入MapView當中

2015年6月28日 星期日

OSMDROID:TileProvider


介紹Introduction


  TileProvider是在OSMDROID中很重要的一個部分,他負責從Tile Server或記憶卡中將圖資拿出來並喂給MapView去做顯示。

OSMDROID內建的圖資


  在OSMDROID中有支援以下幾種圖資:
OpenStreetMap提供
(1)MAPNIK
OpenCycleMap提供
(2)CYCLEMAP
Open Public Transport Map提供
(3)PUBLIC_TRANSPORT

MapQuest提供
(4)MAPQUESTOSM
(5)MAPQUESTAERIAL
(6)MAPQUESTAERIAL_US

CloudMade提供
(7)CLOUDMADESTANDARDTILES
(8)CLOUDMADESMALLTILES

OpenStreetMap Nederland提供
(9)FIETS_OVERLAY_NL
(10)BASE_OVERLAY_NL
(11)ROADS_OVERLAY_NL

以上圖資使用方式(以MAPNIK為例)
mapview.setTileSource(TileSourceFactory.MAPNIK);

自訂TileSource


看不慣內建的TileSource嗎?一定在想說可不可以更換對吧答案是可以的喔~,只要這樣寫就好了(以CartoDB Light為例,更多TileSource可以去OpenStreetMap的Wiki查看)
public static final OnlineTileSourceBase CARTODB = new XYTileSource("CartoDB", ResourceProxy.string.unknown,2,19,256,".png",new String[]{
            "http://a.basemaps.cartocdn.com/light_all/"});
先來說一下XYTileSource的參數說明
XYTileSource("Tile的名稱", Tile的ID(如果在ResourceProxy中沒有定義直接用unknown就好), Tile最小的zoom level, Tile最大的zoom level, Tile的圖資大小(256x256就寫256,如果是512x512就寫512), "Tile圖資的副檔名(如果是png就寫 .png)", 圖資的網址或位置(如果有多個位置可以用String[]來定義多個位置) );
這樣宣告完就能設定MapView的圖資是我們自己自訂的囉~
mapview.setTileSource(CARTODB);

2015年5月22日 星期五

OSMDROID: MapController


介紹Introduction


  MapController拆開來是 Map Controller但是雖然說是Controller但是能控制的真的還蠻有限的...當然這個類別只是讓一些操作比較容易一點,所以如果你想直接操作MapView的話也是可以。在MapController中主要就是在設定啟動時的Map Center與設定Map的Zoom Level,還有就是縮放特效的調整。

方法介紹Method Introduction


設定螢幕正中央的經緯度
GeoPoint startPoint = new GeoPoint(latitude, longitude);//GeoPoint(緯度y,經度x)
mapController.setCenter(startPoint);
地圖Zoom Level設定(通常值是介於1~18,這取決於圖資所能提供的Zoom Level來決定,值越大就越多細節)
mapController.setZoom(12); //將Zoom Level設定在12
縮放動畫設定
mapController.stopAnimation(true);//停止動畫效果
mapController.stopAnimation(false);//開啟動畫效果