我对如何实现"个人指南针"感到困惑,即指向特定方位而不是标准的"北极"的指南针…不幸的是,我目前的尝试是错误的(没有指向给定的方位)。它还与加速器连接在一起,能够根据用户转向的方向动态调整自己。
这是我目前的尝试(更新箭头的onSensorChanged()
-方法):
public void onSensorChanged( SensorEvent event ) {
// If we don't have a Location, we break out
if ( LocationObj == null ) return;
float azimuth = event.values[0];
float baseAzimuth = azimuth;
GeomagneticField geoField = new GeomagneticField( Double
.valueOf( LocationObj.getLatitude() ).floatValue(), Double
.valueOf( LocationObj.getLongitude() ).floatValue(),
Double.valueOf( LocationObj.getAltitude() ).floatValue(),
System.currentTimeMillis() );
azimuth += geoField.getDeclination(); // converts magnetic north into true north
//Correct the azimuth
azimuth = azimuth % 360;
//This is where we choose to point it
float direction = azimuth + LocationObj.bearingTo( destinationObj );
rotateImageView( arrow, R.drawable.arrow, direction );
//Set the field
if( baseAzimuth > 0 && baseAzimuth < 45 ) fieldBearing.setText("S");
else if( baseAzimuth >= 45 && baseAzimuth < 90 ) fieldBearing.setText("SW");
else if( baseAzimuth > 0 && baseAzimuth < 135 ) fieldBearing.setText("W");
else if( baseAzimuth > 0 && baseAzimuth < 180 ) fieldBearing.setText("NW");
else if( baseAzimuth > 0 && baseAzimuth < 225 ) fieldBearing.setText("N");
else if( baseAzimuth > 0 && baseAzimuth < 270 ) fieldBearing.setText("NE");
else if( baseAzimuth > 0 && baseAzimuth < 315 ) fieldBearing.setText("E");
else if( baseAzimuth > 0 && baseAzimuth < 360 ) fieldBearing.setText("SE");
else fieldBearing.setText("?");
}
这是旋转ImageView (rotateImageView()
)的方法:
private void rotateImageView( ImageView imageView, int drawable, float rotate ) {
// Decode the drawable into a bitmap
Bitmap bitmapOrg = BitmapFactory.decodeResource( getResources(),
drawable );
// Get the width/height of the drawable
DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = bitmapOrg.getWidth(), height = bitmapOrg.getHeight();
// Initialize a new Matrix
Matrix matrix = new Matrix();
// Decide on how much to rotate
rotate = rotate % 360;
// Actually rotate the image
matrix.postRotate( rotate, width, height );
// recreate the new Bitmap via a couple conditions
Bitmap rotatedBitmap = Bitmap.createBitmap( bitmapOrg, 0, 0, width, height, matrix, true );
//BitmapDrawable bmd = new BitmapDrawable( rotatedBitmap );
//imageView.setImageBitmap( rotatedBitmap );
imageView.setImageDrawable(new BitmapDrawable(getResources(), rotatedBitmap));
imageView.setScaleType( ScaleType.CENTER );
}
任何帮助将是非常感激的,因为我不太知道如何进行。我在尝试时得到的"读数"有些不准确,而且指向错误的方向。是我做错了什么,还是我的试运行太糟糕了?
你的rotateImageView函数应该工作得很好,但是在你的旋转计算中有一些东西需要改变。
//This is where we choose to point it
float direction = azimuth + LocationObj.bearingTo( destinationObj );
rotateImageView( arrow, R.drawable.arrow, direction );
问题是,bearingTo会给你一个范围从-180到180,这会让事情有点混乱。我们需要将该值转换为0到360的范围,以获得正确的旋转。
这是一个我们真正想要的表,与bearingTo给我们的比较
<>之前+-----------+--------------+→轴承→真正的轴承+-----------+--------------+| 0 | 0 |+-----------+--------------+| 90 | 90 |+-----------+--------------+| 180 | 180+-----------+--------------+| -90 | 270 |+-----------+--------------+| -135 | 225 |+-----------+--------------+| -180 | 180 |+-----------+--------------+之前即使方位在-180到180的范围内,0仍然是真北,这将留给我们这样的计算:
// Store the bearingTo in the bearTo variable
float bearTo = LocationObj.bearingTo( destinationObj );
// If the bearTo is smaller than 0, add 360 to get the rotation clockwise.
if (bearTo < 0) {
bearTo = bearTo + 360;
}
如果我们添加一些虚拟值来测试我们的新公式:
float bearTo = -100;
// This will now equal to true
if (-100 < 0) {
bearTo = -100 + 360 = 360 - 100 = 260;
}
我们现在已经整理出了方位,让我们来看看方位角!
你需要减去而不是加上赤纬,因为当我们把手机直接指向真北时,我们希望方位角为0,而不是把赤纬加到方位角上,这样当我们把手机指向真北时,我们就会得到两倍的赤纬。通过减去赤纬而不是加上赤纬来纠正这个错误。
azimuth -= geoField.getDeclination(); // converts magnetic north into true north
当我们现在把手机转向真北,方位角将等于0
校正方位角的代码不再需要了。
// Remove / uncomment this line
azimuth = azimuth % 360;
我们现在将继续计算实际旋转的点。但首先,我将总结一下我们现在有什么类型的值,并解释它们到底是什么:
bearTo =从真北到目的地位置的角度,从我们现在站的点算起。
方位角=你从真北方向旋转手机的角度
这样说,如果你把手机直接指向真北,我们真的希望箭头旋转bearTo设置的角度。如果你将手机指向正北方向45度,我们希望箭头的旋转角度比bearTo小45度。这就留给我们如下的计算:
float direction = bearTo - azimuth;
但是,如果我们输入一些虚拟值:bearTo = 45;方位角= 180;
direction = 45 - 180 = -135;
这意味着箭头应该逆时针旋转135度。我们需要输入一个类似于处理bearTo!的if条件。
// If the direction is smaller than 0, add 360 to get the rotation clockwise.
if (direction < 0) {
direction = direction + 360;
}
你的方位文本,N, E, S和W是错误的,所以我在下面的最后方法中纠正了它们。
你的onSensorChanged方法应该是这样的:
public void onSensorChanged( SensorEvent event ) {
// If we don't have a Location, we break out
if ( LocationObj == null ) return;
float azimuth = event.values[0];
float baseAzimuth = azimuth;
GeomagneticField geoField = new GeomagneticField( Double
.valueOf( LocationObj.getLatitude() ).floatValue(), Double
.valueOf( LocationObj.getLongitude() ).floatValue(),
Double.valueOf( LocationObj.getAltitude() ).floatValue(),
System.currentTimeMillis() );
azimuth -= geoField.getDeclination(); // converts magnetic north into true north
// Store the bearingTo in the bearTo variable
float bearTo = LocationObj.bearingTo( destinationObj );
// If the bearTo is smaller than 0, add 360 to get the rotation clockwise.
if (bearTo < 0) {
bearTo = bearTo + 360;
}
//This is where we choose to point it
float direction = bearTo - azimuth;
// If the direction is smaller than 0, add 360 to get the rotation clockwise.
if (direction < 0) {
direction = direction + 360;
}
rotateImageView( arrow, R.drawable.arrow, direction );
//Set the field
String bearingText = "N";
if ( (360 >= baseAzimuth && baseAzimuth >= 337.5) || (0 <= baseAzimuth && baseAzimuth <= 22.5) ) bearingText = "N";
else if (baseAzimuth > 22.5 && baseAzimuth < 67.5) bearingText = "NE";
else if (baseAzimuth >= 67.5 && baseAzimuth <= 112.5) bearingText = "E";
else if (baseAzimuth > 112.5 && baseAzimuth < 157.5) bearingText = "SE";
else if (baseAzimuth >= 157.5 && baseAzimuth <= 202.5) bearingText = "S";
else if (baseAzimuth > 202.5 && baseAzimuth < 247.5) bearingText = "SW";
else if (baseAzimuth >= 247.5 && baseAzimuth <= 292.5) bearingText = "W";
else if (baseAzimuth > 292.5 && baseAzimuth < 337.5) bearingText = "NW";
else bearingText = "?";
fieldBearing.setText(bearingText);
}
您应该能够将矩阵设置为ImageView而无需每次重新创建位图,并且…"正常化"(是这个词吗?)读数
float b = mLoc.getBearing();
if(b < 0)
b = 360 + b;
float h = item.mHeading;
if(h < 0)
h = 360 + h;
float r = (h - b) - 360;
matrix.reset();
matrix.postRotate(r, width/2, height/2);
在上面的例子中,mLoc是由gps提供程序返回的位置,getBearing返回当前行进方向偏东偏北的度数。项。mHeading是使用location. bearingto()函数使用mLoc和项目的位置计算出来的。宽度和高度是图像视图的尺寸。
所以,确保你的变量是度而不是弧度,并尝试"规范化"(让标题进入0-360的范围,而不是-180-180)。此外,如果结果偏离了180度,请确保你得到的是到目标的方位,而不是从目标到你的角度。
上面的矩阵可以在具有ScaleType的ImageView中设置。矩阵
imageView.setMatrix(matrix);
imageview.setScaleType(ScaleType.Matrix);
因为你是围绕着imageView的中心点旋转(postRotate中的宽度/2,高度/2),你的可绘制对象应该指向上方,并在绘制时旋转,而不是每次重新创建一个新的位图
一个周末我花了大约40个小时来做这件事。
屁股疼,希望我能让你不疼。
好的,我警告你,这是一些丑陋的代码。我在紧要关头完成它,它没有命名方案,但我尽力为你评论它。
它被用来定位在田野里堆放的成堆的坚果。
使用手机当前的纬度和经度,目的地的经纬度,指南针传感器,以及一些代数运算,我能够计算到目的地的方向。
纬度和传感器读数是从MainApplication类
中提取的这是arrow.class的一些代码,我用它在画布上朝着一个方向画一个箭头。
//The location you want to go to//
//"Given North"
double lat=0;
double lon=0;
//////////////////////////////////
protected void onDraw(Canvas canvas) {
//Sensor values from another class managing Sensor
float[] v = MainApplication.getValues();
//The current location of the device, retrieved from another class managing GPS
double ourlat= MainApplication.getLatitudeD();
double ourlon= MainApplication.getLongitudeD();
//Manually calculate the direction of the pile from the device
double a= Math.abs((lon-ourlon));
double b= Math.abs((lat-ourlat));
//archtangent of a/b is equal to the angle of the device from 0-degrees in the first quadrant. (Think of a unit circle)
double thetaprime= Math.atan(a/b);
double theta= 0;
//Determine the 'quadrant' that the desired location is in
//ASTC (All, Sin, Tan, Cos) Determines which value is positive
//Gotta love Highschool algebra
if((lat<ourlat)&&(lon>ourlon)){//-+
//theta is 180-thetaprime because it is in the 2nd quadrant
theta= ((Math.PI)-thetaprime);
//subtract theta from the compass value retrieved from the sensor to get our final direction
theta=theta - Math.toRadians(v[0]);
}else if((lat<ourlat)&&(lon<ourlon)){//--
//Add 180 degrees because it is in the third quadrant
theta= ((Math.PI)+thetaprime);
//subtract theta from the compass value retreived from the sensor to get our final direction
theta=theta - Math.toRadians(v[0]);
}else if((lat>ourlat)&&(lon>ourlon)){ //++
//No change is needed in the first quadrant
theta= thetaprime;
//subtract theta from the compass value retreived from the sensor to get our final direction
theta=theta - Math.toRadians(v[0]);
}else if((lat>ourlat)&&(lon<ourlon)){ //+-
//Subtract thetaprime from 360 in the fourth quadrant
theta= ((Math.PI*2)-thetaprime);
//subtract theta from the compass value retreived from the sensor to get our final direction
theta=theta - Math.toRadians(v[0]);
}
canvas.drawBitmap(_bitmap, 0, 0, paint);
float[] results = {0}; //Store data
Location.distanceBetween(ourlat, ourlon, lat, lon, results);
try{
//Note, pileboundary is a value retreived from a database
//This changes the color of the canvas based upon how close you are to the destination
//Green < 100 (or database value), Yellow < (100)*2, Otherwise red
if((results[0])<(pileboundary==0?100:pileboundary)){
_canvas.drawColor(Color.GREEN);
}else if((results[0])<(pileboundary==0?100:pileboundary)*2){
_canvas.drawColor(Color.YELLOW);
}else{
_canvas.drawColor(Color.rgb(0xff, 113, 116)); //RED-ish
}
//Draw the distance(in feet) from the destination
canvas.drawText("Distance: "+Integer.toString((int) (results[0]*3.2808399))+ " Feet", 3, height-3, textpaint);
}catch(IllegalArgumentException ex){
//im a sloppy coder
}
int w = canvas.getWidth();
int h = height;
int x = w / 2; //put arrow in center
int y = h / 2;
canvas.translate(x, y);
if (v != null) {
// Finally, we rotate the canvas to the desired direction
canvas.rotate((float)Math.toDegrees(theta));
}
//Draw the arrow!
canvas.drawPath(thearrow, paint);
}
//Some of my declarations, once again sorry :P
GeomagneticField gf;
Bitmap _bitmap;
Canvas _canvas;
int _height;
int _width;
Bitmap b;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Get the current GeomagneticField (Should be valid until 2016, according to android docs)
gf = new GeomagneticField((float)lat,(float)lon,(float)MainApplication.getAltitude(),System.currentTimeMillis());
_height = View.MeasureSpec.getSize(heightMeasureSpec);
_width = View.MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(_width, _height);
_bitmap = Bitmap.createBitmap(_width, _height, Bitmap.Config.ARGB_8888);
_canvas = new Canvas(_bitmap);
b=Bitmap.createBitmap(_bitmap);
drawBoard();
invalidate();
}
//Here is the code to draw the arrow
thearrow.moveTo(0, -50);
thearrow.lineTo(-20, 50);
thearrow.lineTo(0, 50);
thearrow.lineTo(20, 50);
thearrow.close();
thearrow.setFillType(FillType.EVEN_ODD);
希望你能设法阅读我的代码…如果我有时间,我会把它画得漂亮一点。
如果你需要解释,请告诉我。
-MrZander