前回までで、やりたいと思っていたことのうち高低図の表示以外はできるようになったので、一旦ここまでを総括してみたい。
必要な機能
まず、なぜこのアプリを開発しようと思い立ったかと言うと、以前はガーミンのサイコンを使っていたんですが、老眼になって小さな画面が見辛くなったので、もう少し大きい画面のものはないかと探してスマホのアプリでサイコンとして使えるものがあることを知り、使っていましたが、どうも自分のニーズにぴったりのアプリがない。それなら、自分で作ってしまえと思ったのがことの始まり。しかし、アンドロイドのアプリは作ったことがなく、一から勉強することになって悪戦苦闘の連続でした。まあ、それでもそれなりに使えるところまでやってきました。
自分的に欲しい機能は次の通り。
- オフラインで地図が使える
- GPX 形式のルートが表示できる
- 地図上に右折、左折などの記号が表示できる
- 高低図の表示
オフラインマップはオープンストリートマップが有名ですが既存のアプリは色々制約があって使いづらい。今回の開発にあたってはオープンストリートマップ系ではあるが保存形式の異なるmapsforge を使いました。特別な理由と言うほどのものもなかったのですが、日本語で記事を書いておられる伊勢在住のプログラマーさんのブログが参考になったのと、それまで使っていたアプリのipbike でmapsforge の地図を使っていて馴染みがあったことが理由でした。
ただ、mapsforge 本家のサイトは不親切そのものでとてもアプリ開発初心者がとっつけるものではありませんでした。とにかく伊勢在住のプログラマーさんのブログを読んで地図を表示させるところから始めました。ソースコードを読んでいるうちになんとなくやっていることが理解できるようになってきたので、mapsforge 本家サイトのサンプルプログラムを読んでやりたい機能を付け足していきました。そんなこんなで高低図以外は一応できるようになりました。まあ、汎用性はまったくない自分専用のアプリになっていますが、自分が使えればそれで良いので。でも、未完成ながら使っているうちに少し使い勝手の悪い部分が見えてきました。主な改善点は次の通りです。
- スクリーンが最大30分で消える
- 地図の回転がいま一つ
- 既に通過したところは色が変わった方が見やすい
- 高低図はやっぱり欲しい
スクリーンの問題は端末側の設定では30分以上には伸ばせないのでアプリで解決するしかなさそうです。やり方は調べてわかったので簡単にできそうです。
地図の回転に関しては地磁気センサーを使って方位を測定しているのですが、進行方向と地図の向きがずれてるように感じることがままあります。センサーを使わずGPS だけで方位を決めてやった方が違和感が無くなるかもしれません。車に付けている中華製のアンドロイドナビはGPS オンリーですが方位の違和感を感じた事がありません。ただ、動き出さないと正しい方位を示しませんが。そうすると止まった状態で地図の向きが合わないと言う事が起こります。このあたりは使いながらどっちがいいか判断していくしかないでしょう。
通ったところを色を変えると言うのは、地図の方位がずれると言うのと少し関係していて、パッと見てこれから行く方向がわかりづらいときがあって、そんなとき色分けがしてあればわかりやすくなると思ってます。
高低図に関しては、やっぱり登りであとどのくらいで峠なのかを知りたいので、欲しい機能です。ただ、実装するとなるといろいろとハードルがあります。特に、ルートを外れたときの処理が厄介そうです。
ソースコード
アプリは沢山のクラス、xmlなどから構成されています。
長くなるので、サンプルプログラムをそのまま使ったものについては、ソースコードを省略します。また、全文はとっても長いので3回に分けることにしました。
なお、開発環境は、AndroidStudioのVer3.4でパソコンのOSはwindows10です。実端末はXperiaZ4でAndroidのバージョンは6.0です。また、AQUOS SENSE PLUS (Android Version 8.0)でも動くことは確認しています。
クラスは大きく次の3種類からなります。
- サンプルプログラムと同じもの
- サンプルプログラムを少し変えたもの
- 自分で作ったもの
自分で作ったものは次回に書くことにし、それ以外について書きます。
それでもめちゃ長くなりますが。なお、すべてのソースでimportの部分は省略しています。AndroidStudioでは勝手に書き込んでくれますので。
サンプルプログラムと同じもの
- DefaultTheme
- MapScaleBarImpl
- MapScarlBarView
- Utils
- Stting(2020/3/15追記、もう一つあるのを忘れていました)
これらは、サンプルプログラムのソースコードをそのまま使っています。
ここからコピーして使いました。
mapsforge/mapsforge-samples-android/src/main/java/org/mapsforge/samples/android at master · mapsforge/mapsforge · GitHub
サンプルプログラムを少し変えたもの
- OvarlayMapViewer
- RotateMapViewer
- SamplesApplication
- SamplesBaseActivity
- SimplestMapViewew
OvarlayMapViewerは大幅改変です。サンプルプログラムを土台にしているので、ゴミがいっぱい残っています。そのうちに整理するつもりですが、機能に影響ないのでそのままにしています。このままだとベルリン市街にいろいろなマークが表示されます。マップにいろいろなものをオーバーレイで表示するためのクラスです。GPXファイルを読み込んでルートを表示したり、POIファイルを読み込んでマークを表示する機能を追加しています。
public class OverlayMapViewer extends DefaultTheme implements LocationListener { private final static int PERMISSION_GPS_CODE = 1001; private LocationManager locationManager; private SamplesApplication setGPS; private MyLocationOverlay myLocationOverlay; private double Latitude; private double Longitude; protected LatLong latLong1 = new LatLong(52.5, 13.4); protected LatLong latLong2 = new LatLong(52.499, 13.402); protected LatLong latLong3 = new LatLong(52.503, 13.399); protected LatLong latLong4 = new LatLong(52.51, 13.401); protected LatLong latLong5 = new LatLong(52.508, 13.408); protected LatLong latLong6 = new LatLong(52.515, 13.420); protected LatLong latLong7 = new LatLong(52.51, 13.41); protected LatLong latLong8 = new LatLong(52.51, 13.42); protected LatLong latLong9 = new LatLong(52.52, 13.43); protected LatLong latLong10 = new LatLong(52.514, 13.413); protected LatLong latLong11 = new LatLong(52.514, 13.423); protected LatLong latLong12 = new LatLong(52.524, 13.433); protected LatLong latLong13 = new LatLong(52.516, 13.4145); protected LatLong latLong14 = new LatLong(52.516, 13.4245); protected LatLong latLong15 = new LatLong(52.526, 13.4345); protected LatLong latLongX = new LatLong( 34.859881, 135.577572); protected LatLong latLongY = new LatLong( 34.859158, 135.581306); private static String GPX_FILE = ""; private static String POI_FILE = ""; private static String Path =""; String sdPath = Environment.getExternalStorageDirectory().getPath(); private String AtName; private String AtValue; private String XPname; private String POIname; private double lati = 0; private double longi = 0; @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.LOLLIPOP) protected void addOverlayLayers(Layers layers) { SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); GPX_FILE = preferences.getString("gpx", "null.gpx"); POI_FILE = preferences.getString("poi", "null.poi"); Path = preferences.getString("path", sdPath); //File file = new File(Path, GPX_FILE); Polyline polyline = new Polyline(Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.BLUE), (int) (4 * mapView.getModel().displayModel.getScaleFactor()), Style.STROKE), AndroidGraphicFactory.INSTANCE); List<LatLong> latLongs = new ArrayList<>(); FileInputStream is = null; ArrayList<Marker> markers = new ArrayList<Marker>(); //GPXファイルの解析 try { String listXmlPath = Path + "/"+ GPX_FILE; is = new FileInputStream(new File(listXmlPath)); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); //String content = new Scanner( // new File(listXmlPath)).useDelimiter("\\z").next(); //XMLファイルをまとめて読み込み XmlPullParser xpp = Xml.newPullParser(); xpp.setInput(reader); //解析するXMLファイルの中身を渡す int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_DOCUMENT: Log.i("MainActivity", "ドキュメント開始"); break; case XmlPullParser.START_TAG: XPname = xpp.getName(); Log.i("MainActivity", XPname + "要素開始"); int attrCount = xpp.getAttributeCount(); for (int i = 0; i < attrCount; ++i) { AtName = xpp.getAttributeName(i); AtValue = xpp.getAttributeValue(i); Log.i("MainActivity", " " + i + "番目の属性 = " + xpp.getAttributeName(i)); Log.i("MainActivity"," " + i + "番目の値 = " + xpp.getAttributeValue(i)); if(AtName.equals("lat")){ lati = parseDouble(AtValue); } if( AtName.equals("lon")) { longi = parseDouble(AtValue); LatLong latLong = new LatLong(lati, longi); latLongs.add(latLong); } } break; case XmlPullParser.TEXT: Log.i("MainActivity", "テキスト = " + xpp.getText()); break; case XmlPullParser.END_TAG: Log.i("MainActivity", xpp.getName() + "要素終了"); break; } eventType = xpp.next(); //次のトークンに進む } Log.i("MainActivity", "ドキュメント終了"); } catch (XmlPullParserException e) { Log.e("MainActivity", "XMLの解析失敗."); } catch (IOException e) { Log.e("MainActivity", "XMLファイルの読み込みに失敗."); } //latLongs.add(latLong1); //latLongs.add(latLong2); //latLongs.add(latLong3); polyline.setPoints(latLongs); // this illustrates that bitmap shaders can be used on a path, but then any dash effect // will not be applied. Paint shaderPaint = Utils.createPaint(AndroidGraphicFactory.INSTANCE.createColor(Color.GREEN), 90, Style.STROKE); shaderPaint.setBitmapShader(AndroidGraphicFactory.convertToBitmap(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.marker_green) : getResources().getDrawable(R.drawable.marker_green))); Polyline polylineWithShader = new Polyline(shaderPaint, AndroidGraphicFactory.INSTANCE, true) { @Override public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) { if (contains(tapXY, mapView.getMapViewProjection())) { Toast.makeText(OverlayMapViewer.this, "Polyline tap\n" + tapLatLong, Toast.LENGTH_SHORT).show(); return true; } return false; } }; List<LatLong> latLongs2 = new ArrayList<>(); latLongs2.add(latLong7); latLongs2.add(latLong8); latLongs2.add(latLong9); polylineWithShader.setPoints(latLongs2); Paint paintFill = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.GREEN), 2, Style.FILL); Paint paintStroke = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.BLACK), 2, Style.STROKE); Polygon polygon = new Polygon(paintFill, paintStroke, AndroidGraphicFactory.INSTANCE) { @Override public boolean onTap(LatLong tapLatLong, Point layerXY, Point tapXY) { if (contains(tapLatLong)) { Toast.makeText(OverlayMapViewer.this, "Polygon tap\n" + tapLatLong, Toast.LENGTH_SHORT).show(); return true; } return false; } }; List<LatLong> latLongs3 = new ArrayList<>(); latLongs3.add(latLong2); latLongs3.add(latLong3); latLongs3.add(latLong4); latLongs3.add(latLong5); polygon.setPoints(latLongs3); // A polygon filled with a shader, where the shader is not aligned Paint paintFill2 = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.GREEN), 2, Style.FILL); paintFill2.setBitmapShader(AndroidGraphicFactory.convertToBitmap(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.marker_green) : getResources().getDrawable(R.drawable.marker_green))); Paint paintStroke2 = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.BLACK), 2, Style.STROKE); Polygon polygonWithShaderNonAligned = new Polygon(paintFill2, paintStroke2, AndroidGraphicFactory.INSTANCE); List<LatLong> latLongs4 = new ArrayList<>(); latLongs4.add(latLong10); latLongs4.add(latLong11); latLongs4.add(latLong12); latLongs4.add(latLong10); polygonWithShaderNonAligned.setPoints(latLongs4); Paint paintFill3 = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.RED), 2, Style.FILL); paintFill3.setBitmapShader(AndroidGraphicFactory.convertToBitmap(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.marker_red) : getResources().getDrawable(R.drawable.marker_red))); Paint paintStroke3 = Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.BLACK), 2, Style.STROKE); Polygon polygonWithShaderAligned = new Polygon(paintFill3, paintStroke3, AndroidGraphicFactory.INSTANCE, true); List<LatLong> latLongs5 = new ArrayList<>(); latLongs5.add(latLong13); latLongs5.add(latLong14); latLongs5.add(latLong15); latLongs5.add(latLong13); polygonWithShaderAligned.setPoints(latLongs5); //POIファイルの解析 try { String listXmlPath = Path + "/"+ POI_FILE; is = new FileInputStream(new File(listXmlPath)); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); //String content = new Scanner( // new File(listXmlPath)).useDelimiter("\\z").next(); //XMLファイルをまとめて読み込み XmlPullParser xpp = Xml.newPullParser(); xpp.setInput(reader); //解析するXMLファイルの中身を渡す int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_DOCUMENT: Log.i("MainActivity", "ドキュメント開始"); break; case XmlPullParser.START_TAG: XPname = xpp.getName(); Log.i("MainActivity", XPname + "要素開始"); int attrCount = xpp.getAttributeCount(); for (int i = 0; i < attrCount; ++i) { AtName = xpp.getAttributeName(i); AtValue = xpp.getAttributeValue(i); Log.i("MainActivity", " " + i + "番目の属性 = " + xpp.getAttributeName(i)); Log.i("MainActivity"," " + i + "番目の値 = " + xpp.getAttributeValue(i)); if(AtName.equals("lat")){ lati = parseDouble(AtValue); } if( AtName.equals("lon")) { longi = parseDouble(AtValue); latLongX = new LatLong(lati, longi); //latLongs.add(latLong); } } break; case XmlPullParser.TEXT: POIname = xpp.getText(); Log.i("MainActivity", "テキスト = " + POIname); if (XPname.equals("icon")){ switch (POIname) { case "1001009": markers.add( Utils.createTappableMarker(this, R.drawable.left1, latLongX)); break; case "1001010": markers.add( Utils.createTappableMarker(this, R.drawable.left2, latLongX)); break; case "1001011": markers.add( Utils.createTappableMarker(this, R.drawable.left3, latLongX)); break; case "1001012": markers.add( Utils.createTappableMarker(this, R.drawable.left4, latLongX)); break; case "1001013": markers.add( Utils.createTappableMarker(this, R.drawable.left5, latLongX)); break; case "1001014": markers.add( Utils.createTappableMarker(this, R.drawable.left6, latLongX)); break; case "1001015": markers.add( Utils.createTappableMarker(this, R.drawable.left7, latLongX)); break; case "1001016": markers.add( Utils.createTappableMarker(this, R.drawable.left8, latLongX)); break; case "1001001": markers.add( Utils.createTappableMarker(this, R.drawable.right1, latLongX)); break; case "1001002": markers.add( Utils.createTappableMarker(this, R.drawable.right2, latLongX)); break; case "1001003": markers.add( Utils.createTappableMarker(this, R.drawable.right3, latLongX)); break; case "1001004": markers.add( Utils.createTappableMarker(this, R.drawable.right4, latLongX)); break; case "1001005": markers.add( Utils.createTappableMarker(this, R.drawable.right5, latLongX)); break; case "1001006": markers.add( Utils.createTappableMarker(this, R.drawable.right6, latLongX)); break; case "1001007": markers.add( Utils.createTappableMarker(this, R.drawable.right7, latLongX)); break; case "1001008": markers.add( Utils.createTappableMarker(this, R.drawable.right8, latLongX)); break; } } break; case XmlPullParser.END_TAG: Log.i("MainActivity", xpp.getName() + "要素終了"); break; } eventType = xpp.next(); //次のトークンに進む } Log.i("MainActivity", "ドキュメント終了"); } catch (XmlPullParserException e) { Log.e("MainActivity", "XMLの解析失敗."); } catch (IOException e) { Log.e("MainActivity", "XMLファイルの読み込みに失敗."); } //ArrayList<Marker> markers = new ArrayList<Marker>(); //markers.add( Utils.createTappableMarker(this, //R.drawable.left1, latLongX)); //markers.add( Utils.createTappableMarker(this, //R.drawable.right1, latLongY)); Marker marker1 = Utils.createMarker(this, R.drawable.right1, latLongY); Circle circle = new Circle(latLong3, 100, Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.WHITE), 0, Style.FILL), null) { @Override public boolean onTap(LatLong geoPoint, Point viewPosition, Point tapPoint) { if (this.contains(viewPosition, tapPoint, geoPoint.latitude, mapView.getModel().mapViewPosition.getZoomLevel())) { Toast.makeText(OverlayMapViewer.this, "The Circle was tapped " + geoPoint.toString(), Toast.LENGTH_SHORT).show(); return true; } return false; } }; FixedPixelCircle tappableCircle = new FixedPixelCircle( latLong6, 20, Utils.createPaint( AndroidGraphicFactory.INSTANCE.createColor(Color.GREEN), 0, Style.FILL), null) { @Override public boolean onTap(LatLong geoPoint, Point viewPosition, Point tapPoint) { if (this.contains(viewPosition, tapPoint)) { Toast.makeText(OverlayMapViewer.this, "The Circle was tapped " + geoPoint.toString(), Toast.LENGTH_SHORT).show(); return true; } return false; } }; layers.add(polyline); layers.add(polylineWithShader); layers.add(polygon); layers.add(polygonWithShaderAligned); layers.add(polygonWithShaderNonAligned); layers.add(circle); for (int i=0; i < markers.size(); i++ ){ layers.add(markers.get(i)); } layers.add(tappableCircle); } @Override protected void createLayers() { super.createLayers(); setGPS=(SamplesApplication)getApplication(); // we just add a few more overlays addOverlayLayers(mapView.getLayerManager().getLayers()); Drawable drawable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.ic_maps_indicator_current_position) : getResources().getDrawable(R.drawable.ic_maps_indicator_current_position); Marker marker = new Marker(null, AndroidGraphicFactory.convertToBitmap(drawable), 0, 0); Circle circle = new Circle(null, 0, getPaint(AndroidGraphicFactory.INSTANCE.createColor(48, 0, 0, 255), 0, Style.FILL), getPaint(AndroidGraphicFactory.INSTANCE.createColor(160, 0, 0, 255), 2, Style.STROKE)); myLocationOverlay = new MyLocationOverlay(marker, circle); mapView.getLayerManager().getLayers().add(myLocationOverlay); } private static Paint getPaint(int color, int strokeWidth, Style style) { Paint paint = AndroidGraphicFactory.INSTANCE.createPaint(); paint.setColor(color); paint.setStrokeWidth(strokeWidth); paint.setStyle(style); return paint; } private Marker createMarker(LatLong latlong, int resource) { Drawable drawable = getResources().getDrawable(resource); Bitmap bitmap = convertToBitmap(drawable); return new Marker(latlong, bitmap, 0, -bitmap.getHeight() / 2); } @Override public void onStart() { super.onStart(); Log.d("DEBUG", "GPSStart"); if (setGPS.getGPScondition()==true) { locationStart(); } } @Override public void onStop() { super.onStop(); Log.d("DEBUG", "GPSStop"); if (setGPS.getGPScondition()) { SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putFloat("Latitude", (float) Latitude); editor.putFloat("Longitude", (float) Longitude); editor.apply(); } locationStop(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initLocationManager(); } private void initLocationManager() { locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); } private void checkPermission() { if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { Log.d("debug", "location manager Enabled"); } else { // GPSを設定するように促す Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(settingsIntent); Log.d("debug", "not gpsEnable, startActivity"); } if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},1001); } } private void locationStart() { checkPermission(); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000,0,this); } private void locationStop() { locationManager.removeUpdates(this); } @Override public void onLocationChanged(Location location) { Latitude = location.getLatitude(); Longitude = location.getLongitude(); this.myLocationOverlay.setPosition(Latitude, Longitude, location.getAccuracy()); // Follow location this.mapView.setCenter(new LatLong(Latitude, Longitude)); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { Log.d("DEBUG", "called onStatusChanged"); switch (status) { case LocationProvider.AVAILABLE: Log.d("DEBUG", "AVAILABLE"); break; case LocationProvider.OUT_OF_SERVICE: Log.d("DEBUG", "OUT_OF_SERVICE"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: Log.d("DEBUG", "TEMPORARILY_UNAVAILABLE"); break; default: Log.d("DEBUG", "DEFAULT"); break; } } @Override public void onProviderDisabled(String provider) { Log.d("DEBUG", "called onProviderDisabled"); } @Override public void onProviderEnabled(String provider) { Log.d("DEBUG", "called onProviderEnabled"); } }
RotateMapViewer
RotateMapViewerは地図を回転表示させるクラスです。自分のアプリではGPSのON/OFFを切り替えて、ONの時は現在地を表示するとともに、地図を端末の向いている方位に合わせて回転させています。回転角は地磁気センサーを使って取得しています。GPSがOFFの時は地図は北を向いて表示するようにしています。ルート全体を見たい時、この先のルートを見たい時などに使うようにしています。GPSがONの状態だと、地図で別の場所を見ようとしても強制的に現在地に引き戻されてしまうので、OFFの機能を付けています。
public class RotateMapViewer extends OverlayMapViewer { private SensorManager sensorManager = null; private SensorEventListener sensorEventListener = null; private float[] fAccell = null; private float[] fMagnetic = null; float[] saveAcceleVal = new float[3]; float[] saveMagneticVal = new float[3]; private float Latitude; private float Longitude; private SamplesApplication setGPS; @Override protected void createControls() { //Button rotateButton = (Button) findViewById(R.id.rotateButton); //rotateButton.setOnClickListener(new View.OnClickListener() { // @Override // public void onClick(View v) { // RotateView rotateView = (RotateView) findViewById(R.id.rotateView); // rotateView.setHeading(rotateView.getHeading() - 45f); // rotateView.postInvalidate(); // } // }); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: fAccell = event.values.clone(); LowPassFilter(fAccell); break; case Sensor.TYPE_MAGNETIC_FIELD: fMagnetic = event.values.clone(); LowPassFilter2(fMagnetic); if(fAccell!=null) { float[] inR = new float[9]; SensorManager.getRotationMatrix(inR, null, fAccell, fMagnetic); float[] outR = new float[9]; SensorManager.remapCoordinateSystem(inR, SensorManager.AXIS_X, SensorManager.AXIS_Y, outR); float[] fAttitude = new float[3]; SensorManager.getOrientation(outR, fAttitude); //String buf = // "---------- Orientation --------\n" + // String.format( "方位角\n\t%d\n", (int)rad2deg( fAttitude[0] )) + // String.format( "前後の傾斜\n\t%d\n", (int)rad2deg( fAttitude[1] )) + // String.format( "左右の傾斜\n\t%d\n", (int)rad2deg( fAttitude[2] )); //TextView t = (TextView) findViewById( R.id.textView1 ); //t.setText( buf ); RotateView rotateView = (RotateView) findViewById(R.id.rotateView); rotateView.setHeading((int) rad2deg(fAttitude[0])); rotateView.postInvalidate(); } break; } //if (fAccell != null && fMagnetic != null){ //} } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; ImageButton zoomInButton = (ImageButton) findViewById(R.id.zoomInButton); zoomInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mapView.getModel().mapViewPosition.zoomIn(); } }); ImageButton zoomOutButton = (ImageButton) findViewById(R.id.zoomOutButton); zoomOutButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mapView.getModel().mapViewPosition.zoomOut(); } }); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override protected void createMapViews() { mapView = getMapView(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { findViewById(R.id.rotateView).setLayerType(View.LAYER_TYPE_SOFTWARE, null); } mapView.getModel().frameBufferModel.setOverdrawFactor(1.0d); mapView.getModel().init(this.preferencesFacade); mapView.setClickable(true); // Use external scale bar mapView.getMapScaleBar().setVisible(false); MapScaleBarImpl mapScaleBar = new MapScaleBarImpl( mapView.getModel().mapViewPosition, mapView.getModel().mapViewDimension, AndroidGraphicFactory.INSTANCE, mapView.getModel().displayModel); mapScaleBar.setVisible(true); mapScaleBar.setScaleBarMode(DefaultMapScaleBar.ScaleBarMode.BOTH); mapScaleBar.setDistanceUnitAdapter(MetricUnitAdapter.INSTANCE); mapScaleBar.setSecondaryDistanceUnitAdapter(ImperialUnitAdapter.INSTANCE); MapScaleBarView mapScaleBarView = (MapScaleBarView) findViewById(R.id.mapScaleBarView); mapScaleBarView.setMapScaleBar(mapScaleBar); mapView.getModel().mapViewPosition.addObserver(mapScaleBarView); mapView.setBuiltInZoomControls(hasZoomControls()); mapView.getMapZoomControls().setZoomLevelMin(getZoomLevelMin()); mapView.getMapZoomControls().setZoomLevelMax(getZoomLevelMax()); //initializePosition(mapView.getModel().mapViewPosition); SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); Latitude = preferences.getFloat("Latitude", (float) 34.491297); Longitude = preferences.getFloat("Longitude", (float) 136.709685); //Latitude = (float) 34.491297; //Longitude = (float) 136.709685; mapView.setCenter(new LatLong(Latitude, Longitude)); // 伊勢市駅 } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) @SuppressWarnings("deprecation") @Override protected void createTileCaches() { boolean persistent = sharedPreferences.getBoolean( SamplesApplication.SETTING_TILECACHE_PERSISTENCE, true); Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)) .getDefaultDisplay(); final int hypot; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { android.graphics.Point point = new android.graphics.Point(); display.getSize(point); hypot = (int) Math.hypot(point.x, point.y); } else { hypot = (int) Math.hypot(display.getWidth(), display.getHeight()); } this.tileCaches.add(AndroidUtil.createTileCache(this, getPersistableId(), this.mapView.getModel().displayModel.getTileSize(), hypot, hypot, this.mapView.getModel().frameBufferModel.getOverdrawFactor(), persistent)); } @Override protected int getLayoutId() { return R.layout.rotatemapviewer; } @Override protected boolean hasZoomControls() { return false; } private float rad2deg( float rad ) { return rad * (float) 180.0 / (float) Math.PI; } final float filterVal = 0.8f; public void LowPassFilter(float[] target ){ float outVal[] = new float[3]; outVal[0] = (float)(saveAcceleVal[0] * filterVal + target[0] * (1-filterVal)); outVal[1] = (float)(saveAcceleVal[1] * filterVal + target[1] * (1-filterVal)); outVal[2] = (float)(saveAcceleVal[2] * filterVal + target[2] * (1-filterVal)); //現在の測定値を次の計算に使うため保存する saveAcceleVal = target.clone(); //加速度センサーから得た値を書き換える fAccell = outVal.clone(); return ; } public void LowPassFilter2(float[] target ){ float outVal[] = new float[3]; outVal[0] = (float)(saveMagneticVal[0] * filterVal + target[0] * (1-filterVal)); outVal[1] = (float)(saveMagneticVal[1] * filterVal + target[1] * (1-filterVal)); outVal[2] = (float)(saveMagneticVal[2] * filterVal + target[2] * (1-filterVal)); //現在の測定値を次の計算に使うため保存する saveMagneticVal = target.clone(); //加速度センサーから得た値を書き換える fMagnetic = outVal.clone(); return ; } public void onStart() { // ⇔ onStop setGPS=(SamplesApplication)getApplication(); super.onStart(); if(setGPS.getGPScondition()==true) { sensorManager.registerListener( sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), (int) 1e6); sensorManager.registerListener( sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), (int) 1e6); } } public void onStop() { // ⇔ onStart super.onStop(); sensorManager.unregisterListener( sensorEventListener ); } }
SamplesApplication
SamplesApplicationでは、GPSのON/OFFを返すルーチンだけを追加しています。
public class SamplesApplication extends Application { public static final String TAG = "Mapsforge Samples"; public static final String SETTING_DEBUG_TIMING = "debug_timing"; public static final String SETTING_LANGUAGE_SHOWLOCAL = "language_showlocal"; public static final String SETTING_PREFERRED_LANGUAGE = "language_selection"; public static final String SETTING_RENDERING_THREADS = "rendering_threads"; public static final String SETTING_SCALE = "scale"; public static final String SETTING_TEXTWIDTH = "textwidth"; public static final String SETTING_TILECACHE_PERSISTENCE = "tilecache_persistence"; public static final String SETTING_WAYFILTERING = "wayfiltering"; public static final String SETTING_WAYFILTERING_DISTANCE = "wayfiltering_distance"; private boolean setGPS; @Override public void onCreate() { super.onCreate(); AndroidGraphicFactory.createInstance(this); Log.e(TAG, "Device scale factor " + Float.toString(DisplayModel.getDeviceScaleFactor())); SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences(this); float fs = Float.valueOf(preferences.getString(SETTING_SCALE, Float.toString(DisplayModel.getDefaultUserScaleFactor()))); Log.e(TAG, "User ScaleFactor " + Float.toString(fs)); if (fs != DisplayModel.getDefaultUserScaleFactor()) { DisplayModel.setDefaultUserScaleFactor(fs); } MapFile.wayFilterEnabled = preferences.getBoolean(SETTING_WAYFILTERING, true); if (MapFile.wayFilterEnabled) { MapFile.wayFilterDistance = Integer.parseInt(preferences.getString(SETTING_WAYFILTERING_DISTANCE, "20")); } MapWorkerPool.DEBUG_TIMING = preferences.getBoolean(SETTING_DEBUG_TIMING, false); setGPS = false; } public boolean getGPScondition() { return setGPS; } public void setGPScondition(boolean setGPS) { this.setGPS = setGPS; } }
SmplesBaseActivity
あまりよくわかっていませんが、基本的なセッティングなどを扱っているようです。ここも、基本は大きく変えていませんが、地図ファイルのファイル名やディレクトリなどをSharedPreferenceから読み込むようにしています。
public abstract class SamplesBaseActivity extends MapViewerTemplate implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String SETTING_SCALEBAR = "scalebar"; public static final String SETTING_SCALEBAR_METRIC = "metric"; public static final String SETTING_SCALEBAR_IMPERIAL = "imperial"; public static final String SETTING_SCALEBAR_NAUTICAL = "nautical"; public static final String SETTING_SCALEBAR_BOTH = "both"; public static final String SETTING_SCALEBAR_NONE = "none"; protected static final int DIALOG_ENTER_COORDINATES = 2923878; protected SharedPreferences sharedPreferences; @Override protected int getLayoutId() { return R.layout.mapviewer; } @Override protected int getMapViewId() { return R.id.mapView; } @Override protected MapPosition getInitialPosition() { int tileSize = this.mapView.getModel().displayModel.getTileSize(); byte zoomLevel = LatLongUtils.zoomForBounds(new Dimension(tileSize * 4, tileSize * 4), getMapFile().boundingBox(), tileSize); return new MapPosition(getMapFile().boundingBox().getCenterPoint(), zoomLevel); } @Override protected void createLayers() { TileRendererLayer tileRendererLayer = AndroidUtil.createTileRendererLayer(this.tileCaches.get(0), mapView.getModel().mapViewPosition, getMapFile(), getRenderTheme(), false, true, false, getHillsRenderConfig()); this.mapView.getLayerManager().getLayers().add(tileRendererLayer); // needed only for samples to hook into Settings. setMaxTextWidthFactor(); } @Override protected void createControls() { super.createControls(); setMapScaleBar(); } @Override protected void createMapViews() { super.createMapViews(); mapView.getMapZoomControls().setZoomControlsOrientation(MapZoomControls.Orientation.VERTICAL_IN_OUT); mapView.getMapZoomControls().setZoomInResource(R.drawable.zoom_control_in); mapView.getMapZoomControls().setZoomOutResource(R.drawable.zoom_control_out); mapView.getMapZoomControls().setMarginHorizontal(getResources().getDimensionPixelOffset(R.dimen.controls_margin)); mapView.getMapZoomControls().setMarginVertical(getResources().getDimensionPixelOffset(R.dimen.controls_margin)); } @Override protected void createTileCaches() { boolean persistent = sharedPreferences.getBoolean(SamplesApplication.SETTING_TILECACHE_PERSISTENCE, true); this.tileCaches.add(AndroidUtil.createTileCache(this, getPersistableId(), this.mapView.getModel().displayModel.getTileSize(), this.getScreenRatio(), this.mapView.getModel().frameBufferModel.getOverdrawFactor(), persistent)); } @Override protected String getMapFileName() { SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); String mapfile = (MainActivity.launchUrl == null) ? null : MainActivity.launchUrl.getQueryParameter("mapfile"); if (mapfile != null) { return mapfile; } return preferences.getString("map", "berlin.map"); //return "berlin.map"; } @Override protected File getMapFileDirectory() { SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); String mapdir = (MainActivity.launchUrl == null) ? null : MainActivity.launchUrl.getQueryParameter("mapdir"); if (mapdir != null) { File file = new File(mapdir); if (file.exists() && file.isDirectory()) { return file; } throw new RuntimeException(file + " does not exist or is not a directory (configured in launch URI " + MainActivity.launchUrl + " )"); } return new File(preferences.getString("path", Environment.getExternalStorageDirectory().getPath())); //return super.getMapFileDirectory(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(getClass().getSimpleName()); } @Override protected void onDestroy() { this.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); super.onDestroy(); } /* * Settings related methods. */ @Override protected void createSharedPreferences() { super.createSharedPreferences(); this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // problem that the first call to getAll() returns nothing, apparently the // following two calls have to be made to read all the values correctly // http://stackoverflow.com/questions/9310479/how-to-iterate-through-all-keys-of-shared-preferences this.sharedPreferences.edit().clear(); PreferenceManager.setDefaultValues(this, R.xml.preferences, true); this.sharedPreferences.registerOnSharedPreferenceChangeListener(this); } @SuppressWarnings("deprecation") @SuppressLint("InflateParams") @Override protected Dialog onCreateDialog(int id) { AlertDialog.Builder builder = new AlertDialog.Builder(this); LayoutInflater factory = LayoutInflater.from(this); switch (id) { case DIALOG_ENTER_COORDINATES: builder.setIcon(android.R.drawable.ic_menu_mylocation); builder.setTitle(R.string.dialog_location_title); final View view = factory.inflate(R.layout.dialog_enter_coordinates, null); builder.setView(view); builder.setPositiveButton(R.string.okbutton, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { double lat = Double.parseDouble(((EditText) view.findViewById(R.id.latitude)).getText() .toString()); double lon = Double.parseDouble(((EditText) view.findViewById(R.id.longitude)).getText() .toString()); byte zoomLevel = (byte) ((((SeekBar) view.findViewById(R.id.zoomlevel)).getProgress()) + SamplesBaseActivity.this.mapView.getModel().mapViewPosition.getZoomLevelMin()); SamplesBaseActivity.this.mapView.getModel().mapViewPosition.setMapPosition( new MapPosition(new LatLong(lat, lon), zoomLevel)); } }); builder.setNegativeButton(R.string.cancelbutton, null); return builder.create(); } return null; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options_menu, menu); return true; } @SuppressWarnings("deprecation") @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent; switch (item.getItemId()) { case R.id.menu_preferences: intent = new Intent(this, Setting.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); if (renderThemeStyleMenu != null) { intent.putExtra(Setting.RENDERTHEME_MENU, renderThemeStyleMenu); } startActivity(intent); return true; case R.id.menu_position_enter_coordinates: showDialog(DIALOG_ENTER_COORDINATES); break; case R.id.menu_svgclear: AndroidGraphicFactory.clearResourceFileCache(); break; } return false; } @SuppressWarnings("deprecation") @Override protected void onPrepareDialog(int id, final Dialog dialog) { if (id == DIALOG_ENTER_COORDINATES) { IMapViewPosition currentPosition = SamplesBaseActivity.this.mapView.getModel().mapViewPosition; LatLong currentCenter = currentPosition.getCenter(); EditText editText = (EditText) dialog.findViewById(R.id.latitude); editText.setText(Double.toString(currentCenter.latitude)); editText = (EditText) dialog.findViewById(R.id.longitude); editText.setText(Double.toString(currentCenter.longitude)); SeekBar zoomlevel = (SeekBar) dialog.findViewById(R.id.zoomlevel); zoomlevel.setMax(currentPosition.getZoomLevelMax() - currentPosition.getZoomLevelMin()); zoomlevel.setProgress(SamplesBaseActivity.this.mapView.getModel().mapViewPosition.getZoomLevel() - currentPosition.getZoomLevelMin()); final TextView textView = (TextView) dialog.findViewById(R.id.zoomlevelValue); textView.setText(String.valueOf(zoomlevel.getProgress())); zoomlevel.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { textView.setText(String.valueOf(progress)); } @Override public void onStartTrackingTouch(SeekBar arg0) { // nothing } @Override public void onStopTrackingTouch(SeekBar arg0) { // nothing } }); } else { super.onPrepareDialog(id, dialog); } } @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { if (SamplesApplication.SETTING_SCALE.equals(key)) { this.mapView.getModel().displayModel.setUserScaleFactor(DisplayModel.getDefaultUserScaleFactor()); Log.d(SamplesApplication.TAG, "Tilesize now " + this.mapView.getModel().displayModel.getTileSize()); AndroidUtil.restartActivity(this); } if (SamplesApplication.SETTING_PREFERRED_LANGUAGE.equals(key)) { String language = preferences.getString(SamplesApplication.SETTING_PREFERRED_LANGUAGE, null); Log.d(SamplesApplication.TAG, "Preferred language now " + language); AndroidUtil.restartActivity(this); } if (SamplesApplication.SETTING_TILECACHE_PERSISTENCE.equals(key)) { if (!preferences.getBoolean(SamplesApplication.SETTING_TILECACHE_PERSISTENCE, false)) { Log.d(SamplesApplication.TAG, "Purging tile caches"); for (TileCache tileCache : this.tileCaches) { tileCache.purge(); } } AndroidUtil.restartActivity(this); } if (SamplesApplication.SETTING_TEXTWIDTH.equals(key)) { AndroidUtil.restartActivity(this); } if (SETTING_SCALEBAR.equals(key)) { setMapScaleBar(); } if (SamplesApplication.SETTING_DEBUG_TIMING.equals(key)) { MapWorkerPool.DEBUG_TIMING = preferences.getBoolean(SamplesApplication.SETTING_DEBUG_TIMING, false); } if (SamplesApplication.SETTING_RENDERING_THREADS.equals(key)) { Parameters.NUMBER_OF_THREADS = preferences.getInt(SamplesApplication.SETTING_RENDERING_THREADS, 1); AndroidUtil.restartActivity(this); } if (SamplesApplication.SETTING_WAYFILTERING_DISTANCE.equals(key) || SamplesApplication.SETTING_WAYFILTERING.equals(key)) { MapFile.wayFilterEnabled = preferences.getBoolean(SamplesApplication.SETTING_WAYFILTERING, true); if (MapFile.wayFilterEnabled) { MapFile.wayFilterDistance = preferences.getInt(SamplesApplication.SETTING_WAYFILTERING_DISTANCE, 20); } } } /** * Sets the scale bar from preferences. */ protected void setMapScaleBar() { String value = this.sharedPreferences.getString(SETTING_SCALEBAR, SETTING_SCALEBAR_BOTH); if (SETTING_SCALEBAR_NONE.equals(value)) { AndroidUtil.setMapScaleBar(this.mapView, null, null); } else { if (SETTING_SCALEBAR_BOTH.equals(value)) { AndroidUtil.setMapScaleBar(this.mapView, MetricUnitAdapter.INSTANCE, ImperialUnitAdapter.INSTANCE); } else if (SETTING_SCALEBAR_METRIC.equals(value)) { AndroidUtil.setMapScaleBar(this.mapView, MetricUnitAdapter.INSTANCE, null); } else if (SETTING_SCALEBAR_IMPERIAL.equals(value)) { AndroidUtil.setMapScaleBar(this.mapView, ImperialUnitAdapter.INSTANCE, null); } else if (SETTING_SCALEBAR_NAUTICAL.equals(value)) { AndroidUtil.setMapScaleBar(this.mapView, NauticalUnitAdapter.INSTANCE, null); } } } /** * sets the value for breaking line text in labels. */ protected void setMaxTextWidthFactor() { mapView.getModel().displayModel.setMaxTextWidthFactor(Float.valueOf(sharedPreferences.getString(SamplesApplication.SETTING_TEXTWIDTH, "0.7"))); } }
SimplestMapViewer
ここが地図を描画する本体部分です。ここもマップファイル名を読み込む部分だけSharedPreferenceから読み込むようにしているだけです。
public class SimplestMapViewer extends MapViewerTemplate { /** * This MapViewer uses the built-in default theme. * * @return the render theme to use */ @Override protected XmlRenderTheme getRenderTheme() { return InternalRenderTheme.DEFAULT; } /** * This MapViewer uses the standard xml layout in the Samples app. */ @Override protected int getLayoutId() { return R.layout.mapviewer; } /** * The id of the mapview inside the layout. * * @return the id of the MapView inside the layout. */ @Override protected int getMapViewId() { return R.id.mapView; } /** * The name of the map file. * * @return map file name */ @Override protected String getMapFileName() { SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE); //return "berlin.map"; return preferences.getString("map", "berlin.map"); } /** * Creates a simple tile renderer layer with the AndroidUtil helper. */ @Override protected void createLayers() { TileRendererLayer tileRendererLayer = AndroidUtil.createTileRendererLayer(this.tileCaches.get(0), this.mapView.getModel().mapViewPosition, getMapFile(), getRenderTheme(), false, true, false); this.mapView.getLayerManager().getLayers().add(tileRendererLayer); } @Override protected void createMapViews() { super.createMapViews(); } /** * Creates the tile cache with the AndroidUtil helper */ @Override protected void createTileCaches() { this.tileCaches.add(AndroidUtil.createTileCache(this, getPersistableId(), this.mapView.getModel().displayModel.getTileSize(), this.getScreenRatio(), this.mapView.getModel().frameBufferModel.getOverdrawFactor())); } @Override protected MapPosition getInitialPosition() { int tileSize = this.mapView.getModel().displayModel.getTileSize(); byte zoomLevel = LatLongUtils.zoomForBounds(new Dimension(tileSize * 4, tileSize * 4), getMapFile().boundingBox(), tileSize); return new MapPosition(getMapFile().boundingBox().getCenterPoint(), zoomLevel); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(getClass().getSimpleName()); } }
ふう!! 長くなったので続きは次回に。