2016年10月8日 星期六
Simple Location Sender: 團隊動態追蹤的好範例
這個範例使用了幾個開源函式庫
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做為開發語言,所以我們所需要的套件如下:
- ModestMaps : 用來提供網頁版的地圖
- Python Imaging Library (PIL) : 用來合成圖檔
- Simplejson : 用來解析JSON
- Mapnik : 基本地圖樣式(底圖)
- Werkzeug : WSGI函式庫 for Python
TileStache兩種使用方式:
- 直接用Script資料夾內的python script
# 用來啟動伺服器(服務) tilestache-server.py # 用來快取圖檔(網路圖資) tilestache-seed.py # 用來產生自定義圖層 tilestache-render.py
- 將tilestache工具加入指令集
python setup.py install
從TileStache取得圖塊
Requesting Tiles from TileStache
- 從 HTTP 取得
- 取得單一圖塊 Single Tile
格式 : /{圖層名稱}/{縮放等級}/{x}/{y}.{副檔名(e.g. png, jpg}
範例 : http://example.org/osm/12/656/1582.png -
動態地圖(類似Google Map)
格式 : /{圖層名稱}/preview.html
範例 : http://example.org/osm/preview.html
- 取得單一圖塊 Single Tile
- 使用TileStache的API
- 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)
- 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的腳本,用來判斷是否轉址成功
- TileStache.getTile(layer, coord, extension[, ignore_cached=FALSE]): 直接取得圖塊 Get Tile Directly
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,不囉嗦~建立方式如下
- 下載 Cygwin (載點)
- 安裝 Cygwin , 並在選擇安裝套件的時候選擇安裝以下套件
- gcc-core
- git
- openssh
- python
- python-pyasn1
- python-imaging
- python-magic
- python-paramiko
- python-requests
- python-setuptools
- rsync
- wget
- 開啟一個Cygwin的bash介面,然後輸入
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
Step By Step 簡易Mapsforge App開發:MapDownloadManager
目標Achievements
這個類別是用來去做圖資下載相關工作,我在範例中是使用第一種,但是為了學習方便我把另外一種也寫出來了
程式碼Code
1. 使用DownloadManager
package lien.ching.maptracker.api;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.util.Log;
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.layer.Layer;
import org.mapsforge.map.layer.LayerManager;
import org.mapsforge.map.layer.Layers;
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.net.URL;
import lien.ching.maptracker.Constant;
import lien.ching.maptracker.overlay.NowLocationLayout;
/**
* Created by lienching on 12/21/15.
*/
public class mapDownloadManager implements Runnable{
private DownloadManager downloadManager;
private Context context;
private Long enqueue;
public MapView mapview;
public String target;
private BroadcastReceiver receiver;
private NowLocationLayout locationLayout;
public mapDownloadManager(MapView mapView,NowLocationLayout locationLayout,Context context, final String target){
super();
this.context = context;
this.target = target;
this.mapview = mapView;
this.locationLayout = locationLayout;
}
@Override
public void run() {
downloadManager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse("http://download.mapsforge.org/maps/" + target));
request.setDestinationInExternalPublicDir("mapsforge/maps/",target);
enqueue = downloadManager.enqueue(request); //Active Download Process
receiver = new LayerAdder(mapview,locationLayout,target,enqueue); //Created exclusive receiver
context.registerReceiver(receiver,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); //Registered Reciever
}
}
2. 使用HTTP Download (Async)
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.net.Uri; import android.os.AsyncTask; import android.os.Environment; 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; import lien.ching.maptracker.MainActivity; /** * Created by lienching on 11/27/15. * In this class, I want to put downloading process in to Async * I directly implementing the HTTP Download in doInBackground() * In the app I didn't use this class as the download class * if you want to see the source code that use in the app * please check mapDownloadManager.class for more detail */ public class MapDownloadManager extends AsyncTask{ private Context context; private Activity activity; private ProgressDialog dialog; public PowerManager.WakeLock wakeLock; public MapDownloadManager(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.exists()){ output = new FileOutputStream(outputFile); } else{ EnvCheck envCheck = new EnvCheck(context,activity); envCheck.isMapResourceDirExist(continent); outputFile.createNewFile(); output = new FileOutputStream(outputFile); } byte data[] = new byte[8192]; int count; long total = 0; while((count = input.read(data)) != -1){ if(isCancelled()) { input.close(); return false; } total += count; output.write(data,0,count); } Log.d("MapDownloadManager","Write "+total+" bytes"); }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) { } outputFile.setLastModified(connection.getLastModified()); 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 Downing"); 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
第一種方式非常的單純也比較不需要擔心使用者不小心離開程式的時候會發生問題,但是缺點就是需要用Reciever去做下載完成判斷
第二種就是比較資工系的做法,直接實作HTTP Download,這個方法的好處就是可以對於"下載"這個動作做很多客製化的事情,但是要處理的事情就很多
Step By Step 簡易Mapsforge App開發:LayerAdder
目標Achievements
這個類別目的是要用來做動態新增圖層
程式碼Code
package lien.ching.maptracker.api;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
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 org.mapsforge.map.rendertheme.rule.RenderTheme;
import java.io.File;
import lien.ching.maptracker.Constant;
import lien.ching.maptracker.overlay.NowLocationLayout;
/**
* Created by lienching on 1/7/16.
*/
//http://developer.android.com/reference/android/content/BroadcastReceiver.html
public class LayerAdder extends BroadcastReceiver {
private MapView mapView;
private String target;
private NowLocationLayout locationLayout;
private Long enqueue;
public LayerAdder(MapView mapView,NowLocationLayout locationLayout,String target,Long enqueue){
super();
this.mapView = mapView;
this.target = target;
this.enqueue = enqueue;
this.locationLayout = locationLayout;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
if(downloadId == enqueue){
MapFile targetFile = new MapFile(new File(Constant.PATH_MAPSFORGE+target));
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);
mapView.getLayerManager().getLayers().remove(locationLayout);//LocationLayout need be the top layout of the mapview, to keep user location visible
mapView.getLayerManager().getLayers().add(locationLayout);
Log.d("LayerAdder",target+" Download Completed"+", LayerAdd");
}
}
}
}
說明Explanation
BroadcastReceiver是我用來去偵測檔案是否下載完成的類別
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
-
使用者所需具備能力
- 基本的Java概念
- 基本的Android APP開發概念
- 熱愛開發的心
-
專案所需API(mapsforge系列載點、androidsvg載點、kXML2載點)
- mapsforge-map
- mapsforge-core
- mapsforge-map-android
- mapsforge-map-reader
- androidsvg
- 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個部分,分別為
- MainActivity:主程序
- NowLocationLayout:定位圖層
- EnvCheck:環境檢查,用於檢查圖資是否存在與是否需要更新
- MapDownloadManager:用於圖資下載
- LayerAdder:動態圖層
- MapUpdateManager:用於圖資更新
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";
}
訂閱:
意見 (Atom)