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);//開啟動畫效果

2015年5月10日 星期日

OSMDROID:MapView


簡介


  OpenStreetMap最核心的部分,就是他的地圖囉,而在OSMDROID中當然也會有個東西用來顯示地圖的元件,那就是我們現在要來介紹的MapView
OSMDROID的MapView是改寫Android本身的MapView,簡單來說就是把原本是Google Map的MapView變成是OpenStreetMap的。

使用方式


OSMDROID中用MapView有兩種方式,一種是寫在Layout裡面,一種是寫在Activity裡面 首先先介紹如果寫在Layout裡面會是什麼樣子
<org.osmdroid.views.MapView
        android:id="@+id/mapview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tilesource="Mapnik"
        />

然後是Activity中的寫法
public class MainActivity extends Activity {
    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        MapView map = (MapView) findViewById(R.id.map);
        map.setTileSource(TileSourceFactory.MAPNIK);
    }
}

這兩種寫法都很棒,就看個人習慣囉

另外MapView還有提供一些其他的方法,像是
這是在MapView上新增內建的縮放工具
map.setBuiltInZoomControls(true);

這個是啟用兩指縮放功能
map.setMultiTouchControls(true);

這是限制最大縮放等級
map.setMaxZoomLevel(16);
這是限制最小縮放等級
map.setMinZoomLevel(1);
這個是在設定說是否當本機沒有圖資時,允許讓MapView去跟Tile Server要圖資
map.useDataConnection(true);
最後這個是個很重要的東西,因為Android能支援的螢幕解析度太多了,但圖資的解析度是固定的,所以可能會出現到明明Zoom Level已經到最細緻的部分了,但還是沒辦法看到全部的東西,所以OSMDROID中提供這個方法可以根據手機的螢幕解析度去將圖資做一些調整,讓圖資顯示正常。
map.setTilesScaledToDpi(true);

2015年5月6日 星期三

OSMDROID: Introduction



簡介  Introduction

OSMDROID是一個Android的函式庫,主要目的是要提供與OpenStreetMap(簡稱OSM)互動的工具,裡面包含了:


MapView

    提供一個類似Android中MapView的環境,讓OSM的圖資可以顯示在Android中,並可以做拖拉,縮放,圖資來源設定等動作。
MapController

    這是個設定工具,也就是對於MapView的Zoom Level與起始位置做設定。
TileProvider

    TileProvider主要是負責MapView圖資的部分,OSMDROID本身的MapView是需要給他一個圖資來源才能運作的,而提供圖資這個部分就是TileProvider的工作。
Overlay

    Overlay照字面上就是圖層,在OSMDROID中底層是MapView,但如果我們需要去新增一些事件, 要繪製矩形區域, 放置指標等行為就會需要去新增一個圖層定義其行為模式。



安裝  Install

  1. 使用Android Studio

    Android Studio真的是個很棒的工具,因為在安裝Library上面真的非常的方便,像OSMDROID你只需要在專案中建立一個libs的資料夾,然後把osmdroid的jar檔跟slf4j-android(載點)放進去就可以囉
    p.s 如果你發現你放進去了但是卻不能使用的話,請檢查一下專案中的build.gradle中的dependencies大括號裡面有沒有 compile fileTree(dir: 'libs', include: ['*.jar']) 這行,如果沒有補上去之後Sync就會正常了。
  2. 使用Maven


    <dependency>
      <groupid>org.osmdroid</groupid>
      <artifactid>osmdroid-android</artifactid>
      <version>4.3</version>
    </dependency>
    


    <dependency>
        <groupid>org.slf4j</groupid>
        <artifactid>slf4j-android</artifactid>
        <version>1.6.1-RC1</version> 
    </dependency>
    

    放進Maven設定檔中就可以囉。
  3. 使用Eclipse

    OSMDROID的Jar檔跟slf4j-android(載點)下載好,之後
    對著專案點右鍵 內容(Properties) > Java Build Path > Add External JARs > 選擇剛下載的OSMDROID跟slf4j-android > 確定 這樣就完成囉。
最後記得別忘了在AndroidManifest.xml中把這幾行權限加進去喔
<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_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />


下載  Download


    你們可以從(網址)取得最新版本(OSMDROID在Google Code上面也有上傳相關的檔案,但是版本到4.2就停止更新了)
當你進去的時候你會發現有三種不同的檔名,分別是:

  1. osmdroid-android-x.x-javadoc.jar

        這個裡面存放的是OSMDROID這個版本中的所有API的說明,解壓縮之後裡面會是個網站,點擊index.html之後你就會看到















    這個漂亮的網站囉~
  2. osmdroid-android-x.x-sources.jar

        這個檔案中存放的就是完整OSMDROID的原始碼,也就是可以讓你離線也能查閱原始碼喔,當然如果你喜歡線上版的你也可以參閱他們Github的專案
  3. osmdroid-android-x.x.jar

        這個就是我們要放在我們專案中的jar檔囉,裡面放了全部OSMDROID編譯過的檔案,讓我們可以在我們的程式中使用。

2015年4月30日 星期四

2015/04/30 OSMDFE開發日誌(二)

最近在準備期中考比較忙一點
就有點忘記更新了...
有點糟糕
先來Po目前的進度截圖~
 先大致說明一下
我底圖是用MapNIK做為圖資來源
藍色發光點代表的是使用者目前的所在位置
如果使用者按住底圖的某個點
就會出現綠色的標籤
那個代表使用者要新增的地點
然後PopWindows中有三個按鈕
Save:將資料寫入儲存空間&伺服器
Reset:將去伺服器取得前一個版本的內容並顯示出來
Delete:刪除目前這個新增標籤
以上前端的部分

至於後端的部分
我已經大致把與伺服器連接的相關方法已經都定義出來了
目前有這兩種型態的postData
postData(String ip, JSONObject json,Context context)//這個用來傳送使用者所新增的點
postData(String ip,Double latitude, Double longitude,Context context) //這個用來取得使用者周邊的點
至於REST API的部分撰寫正在進行中,就會全面轉換成用RestAPI做溝通
應該近期內就會完成

再來就是伺服器的部分
上次討論結果大致架構如下
1.App與資料庫資料
App <----REST API---->資料庫
2.網頁呈現方式
Umap <----REST API---->資料庫
3.OpenData
客戶端 ----REST API---->資料庫
客戶端<----轉為Json格式----資料庫
資料庫部分目前我也還在規劃當中
近期也會開始進行開發的部分

最後要來講一下目前的一些小小問題
目前正在研究我該怎麼在PopWindow中直接操作到MapView
因為我要讓Delete可以運作啊!!

心情抒發時間~
案子開發到現在...覺得自己進度有點...不對是非常緩慢,至於為什麼呢~大概就是不知道為什麼,教授都喜歡在期中前開始塞一大堆東西給我們,雖然也見怪不怪了但是每次做完就累癱了,真的開發時間就縮短很多... 我真的不是故意的...OAQ。Whatever, 期中考完之後會有段輕鬆期,要好好在這個時期把進度趕回來(握拳) 。

2015年3月26日 星期四

2015/03/27 OSMDFE開發日誌(一)

OSMDFE計畫也執行了一段時間,程式架構一改再改也終於訂出一個還算滿意的架構,算是蠻值得慶幸的事~ 所以也已經可以正式Coding啦,目前整個計畫我執行到地圖正常顯示且也已經能將使用者的位置標示在地圖上(如果抓不到的話就顯示整個台灣),雖然還是有些小Bug存在但也找到一些解決方案所以不算有太大的阻礙。既然這是開發日誌還是要放一些我遇到的問題與解決方案囉。
1.首先我遇到的問題就是在OSMDROID中Mapcontroller.setCenter()這個的offset問題,這個問題算折騰我還蠻久的,但還是解決啦~
 解決方案:將OSMDROID的版本從4.2更新至4.3

2.接下來我遇到的就是TileSource的問題,TileSource這個算是兩個問題

 (1) Zoom Level調到18(最高)卻只有相較於OpenStreetMap官網的Zoom Level 17的效果
 解決方案:使用 mapview.setTilesScaledToDpi(true)來讓TileSource可以吻合螢幕解析度

 (2)離線TileSource的問題
  其實這不算個問題,只是純粹搞不清楚OSMDROID背後在做什麼而已,舊版的OSMDROID4.2他會一直從伺服器抓資料下來,但到了4.3他就會去自動偵測SD卡中有沒有相對應的Tile,如果有就直接建構就不去伺服器拉資料,真的聰明很多。

3. Marker 問題
 解決方案:原本OSMDROID內建的Marker不是很好用,但我在網路上找到一個算擴充包的API叫做 OSMBONUSPACK ,他裡面提供的Marker就非常的易於使用(p.s 因為Google Code要關閉了,而OSMBONUSPACK本來就是託管在Google Code上面所以我就把它的程式碼拉到我的Github了)
 參考網址:Google Code:https://code.google.com/p/osmbonuspack/
        我轉移到我Github:https://github.com/lienching/osmbonuspack


目前一些基本問題都已經有起色了,我最後來說一下我目前使用了哪些API並他們的功用吧
  • OSMDROID_4.3 : 提供MapView、MapController等與OSM互動的工具
  • OSMBONUSPACK : 提供一些OSMDROID中沒有的工具或寫得比OSMDROID更好的工具
  • geojson : 這是用來解析與製作geojson的api, 我們目前是打算使用geojson來作為資料交換的格式