6

I have this following code :

public SensorEventListener sensorEventListener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        float pressure_value = 0.0f;
        float height = 0.0f;
        if (Sensor.TYPE_PRESSURE == event.sensor.getType())
        {
            pressure_value = event.values[0];
            height = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure_value);
        }
        value = String.valueOf(height);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
};

I've got the altitude meter e.g 43,xxxxxxx at 09.AM.

And i have check it again in 09.PM, the result is changing. It change 2 meter or more.

Is it because the pressure changed by moon or anything else ?

And How to fix this ?

I have read the following thread : Android: How to get accurate altitude?

But i still got confused.Could you guide me how to write the code ? And for note, i want to using it based on barometric sensor.

Community
  • 1
  • 1
Leonard Febrianto
  • 964
  • 2
  • 12
  • 37

3 Answers3

7

Barometers provide pressure readings, not altitude ones.

Now because there's a gradient of pressure, and in general it decreases with altitude, you could theoretically infer the difference in altitude from the difference in pressure. Thats all you know with a non-calibrated altimeter: you don't know your current altitude, but you will know when you climb or descent and how much.

To calculate the altitude from your current pressure you need a reference pressure. For instance, you need to know what the pressure is at sea level. So if at sea level the pressure is x hPa, and your barometer reads x+3 hPa, and you know pressure increases +1hPa per meter, your altitude would be 3m above sea level.

The problem is that any reference pressure will always be changing due to meteorological phenomena. So it will only be valid for some time in a certain area. Airports provide this info for aircraft so that they can set their altimeters with the correct reference pressure (for instance, QNH). Once you know this reference pressure normalized to sea level (QNH is not, you better use QFF), you can pass it as the first parameter to this method:

SensorManager.getAltitude(<reference pressure at sea level>, pressure_value);

And remember that you can't hardcode the reference pressure. It is constantly changing, like temperature. You need to look it up in the internet, or using aviation meteorological services.

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • Could show me somr example that ? Is there any way to get current lattitude ? The most accurate one i mean – Leonard Febrianto Aug 03 '15 at 23:19
  • You could pick a meteorological station near you and read the pressure, then normalize it to sea level knowing the altitude of the station. Or the easiest way, just pick the nearest airport, consult the METAR (a type of message they broadcast) and look for the sea level pressure field (SLP). There are METAR decoder apps in Google Play and all over the internet. – Mister Smith Aug 03 '15 at 23:30
  • I have read from my nearest metar. I've got sea-level pressure is 1012 hPa. From that what i must do ? – Leonard Febrianto Aug 03 '15 at 23:36
  • You just call `SensorManager.getAltitude(1012, pressure_value);` and the result should be your aproximate altitude. – Mister Smith Aug 03 '15 at 23:38
  • So if the sea level pressure changing, is the altitude will result the same ? Sorry, i'm still confused. – Leonard Febrianto Aug 03 '15 at 23:41
  • No, it will change as well. You probably got a reading similar to the standard based ones. The `PRESSURE_STANDARD_ATMOSPHERE` constant in your original code is actually 1013 so it is close to your current pressure and the result will be similar. – Mister Smith Aug 03 '15 at 23:45
  • But if i put that one , does it will be the same problem for me ? For exampe at noon my pressure value is e.g 43.xxxxx and at night my pressure value e.g 45.xxxx at second floor in the building ? – Leonard Febrianto Aug 03 '15 at 23:47
  • The reference value will only ve valid for some time (hours?). It changes with winds, temperature, etc. In a room inside a building, PV =nRT ([law of ideal gases](https://en.wikipedia.org/wiki/Ideal_gas_law)) so it will increase and decrease proportional to temperature. The night/day cycle, or even an air conditioner wll make it change. – Mister Smith Aug 03 '15 at 23:52
  • So you mean the sea level pressure that i've got from nearest metar will change for several time ? – Leonard Febrianto Aug 03 '15 at 23:55
  • @LeonardFebrianto The weather is constantly changing, all the time. Yes, of course! Sometimes slower than other times, but changing none-the-less. – Steve Aug 05 '15 at 20:41
  • The standard pressure changed 0.6mb, from 1013.2 to 1012.6, in the 20 minutes I spent writing my answer below. – TeasingDart Aug 06 '15 at 19:39
  • @LeonardFebrianto - if I'm sitting up on a pole, 200 feet off the surface of the long island sound on a clear, sunny day, what pressure do I read? Maybe 1000 mb. Now a storm rolls through. I haven't changed height, but because there's a giant storm cloud overhead, my pressure reading increases - maybe 1100 mb. This is why you have to know the reference sea level pressure - it's always changing. – antiduh Aug 07 '15 at 18:39
  • Additionally, you have to know the reference sea level pressure *for your locality* - you can't use long island sounds reference sea level pressure if you're out in the middle of iowa. Which means that you need to use course-grained location services to know where you are, and then lookup a pressure reference that's nearby. Even worse, it's still going to fluctuate, especially when there are localized storms nearby. If I live in northeast Rochester, but the nearest reference is the airport south east by 15 miles, and storm rolls over it and not me, the reference is going to be a little wrong. – antiduh Aug 07 '15 at 18:43
4

Change of temperature between day and night results in difference of air pressure and thats the reason you get different altitude readings. If you can account the effects of temperature the error can be corrected.

3

A barometric pressure sensor measures the pressure from the column of air above the sensor -- it does not measure altitude above the ground. Fortunately, there is a (mostly) direct correlation. However, air compresses and expands and the pressure exerted by the column is not constant. The correlation between pressure and altitude needs to be corrected for this local variation in air density. This correction is of great interest to airplane pilots, so the government makes sure every airport makes that information freely available.

Because the temperature and barometric pressure are randomly changing all the time, so to be the most accurate you will need to know the current standard pressure in your locale.

You can first fetch the current barometer value for the nearest airport, then use that value instead of PRESSURE_STANDARD_ATMOSPHERE. You will need to know the station ID, or specify a lat/long rectangle. See METAR station specifications.

BUT... If your current error is only 2 meters, that is already very good. You will never get an exact altitude from a barometric altimeter -- that is why radar altimeters were invented.

http://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&stationString=KSFO&hoursBeforeNow=1&mostRecent=true

This returns the most recent METAR for KSFO (San Francisco):

<?xml version="1.0" encoding="UTF-8"?>
<response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XML-Schema-instance" version="1.2" xsi:noNamespaceSchemaLocation="http://aviationweather.gov/adds/schema/metar1_2.xsd">
  <request_index>34600121</request_index>
  <data_source name="metars" />
  <request type="retrieve" />
  <errors />
  <warnings />
  <time_taken_ms>1</time_taken_ms>
  <data num_results="1">
    <METAR>
      <raw_text>KSFO 061756Z 30011KT 10SM FEW008 BKN160 19/13 A2992 RMK AO2 SLP132 T01890133 10189 20150 51006</raw_text>
      <station_id>KSFO</station_id>
      <observation_time>2015-08-06T17:56:00Z</observation_time>
      <latitude>37.62</latitude>
      <longitude>-122.37</longitude>
      <temp_c>18.9</temp_c>
      <dewpoint_c>13.3</dewpoint_c>
      <wind_dir_degrees>300</wind_dir_degrees>
      <wind_speed_kt>11</wind_speed_kt>
      <visibility_statute_mi>10.0</visibility_statute_mi>
      <altim_in_hg>29.920275</altim_in_hg>
      <sea_level_pressure_mb>1013.2</sea_level_pressure_mb>
      <quality_control_flags>
        <auto_station>TRUE</auto_station>
      </quality_control_flags>
      <sky_condition sky_cover="FEW" cloud_base_ft_agl="800" />
      <sky_condition sky_cover="BKN" cloud_base_ft_agl="16000" />
      <flight_category>VFR</flight_category>
      <three_hr_pressure_tendency_mb>0.6</three_hr_pressure_tendency_mb>
      <maxT_c>18.9</maxT_c>
      <minT_c>15.0</minT_c>
      <metar_type>SPECI</metar_type>
      <elevation_m>3.0</elevation_m>
    </METAR>
  </data>
</response>

You want to pull the value for sea_level_pressure_mb which is 1013.2.

Here is a fragment that displays the current barometric pressure on a map of your current location.

I started writing the "right" code to parse the XML, but lost interest when I had to walk down the nodes. I left that as a learning exercise and wrote the "hack" version instead.

/***********************************************************************/
/** Page2: Simple example of using Google Map.                        **/
/** TeasingDart                                                       **/
/***********************************************************************/
package com.nlited.twopages;

import android.app.Activity;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class Page2 extends MapFragment implements LocationListener {
  private static Page2 mObj;
  private Activity mActivity;
  private View mView;
  private MapView mMapView;
  private GoogleMap mMap;
  private LocationManager mLocMgr;
  private String mLocProvider;
  private Location mLoc;
  private LatLng mLatLng= new LatLng(43,-122);
  private boolean mSlpFound= false;
  private float mSLP= 1012;
  private String mStationID;
  private long mNextSlpCheck= 0;

  static public Page2 getInstance() {
    if(mObj==null)
      mObj= new Page2();
    return(mObj);
  }

  public Page2() {
    Debug("constructor %s", this.toString());
    mObj= this;
  }

  @Override public void onAttach(Activity activity) {
    Debug("onAttach()");
    mActivity= (Main)activity;
    super.onAttach(activity);
    startLocation();
    new Thread(new GetSLP(),"GetSLP").start();
  }

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
    mActivity= (Main)getActivity();
    Debug("onCreateView()");
    super.onCreateView(inflater, container, state);
    mView= inflater.inflate(R.layout.frag_page2, container, false);
    mMapView= (MapView)mView.findViewById(R.id.map);
    mMapView.onCreate(state);
    mMapView.onResume();
    mMap= mMapView.getMap();
    mapUpdate();
    return(mView);
  }

  /***********************************************************************/
  /** Update the map using my GPS location and current SLP.             **/
  /***********************************************************************/
  private void mapUpdate() {
    MarkerOptions opt = new MarkerOptions();
    if(mMap!=null) {
      String status= mSlpFound ? String.format("%.1f %s",mSLP,mStationID) : "????";
      mMap.clear();
      mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mLatLng, 15));
      mMap.addMarker(opt.position(mLatLng).title(status));
    }
  }

  /***********************************************************************/
  /** Retrieve my current GPS location.                                 **/
  /***********************************************************************/
  private void startLocation() {
    mLocMgr= (LocationManager)mActivity.getSystemService(Activity.LOCATION_SERVICE);
    if(mLocMgr==null) {
      Debug("Location services are not available.");
    } else if(!mLocMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
      Debug("GPS is not enabled.");
    } else {
      mLocProvider= mLocMgr.getBestProvider(new Criteria(),false);
      if(mLocProvider==null) {
        Debug("No GPS providers available.");
      } else {
        mLoc = mLocMgr.getLastKnownLocation(mLocProvider);
        mLocMgr.requestLocationUpdates(mLocProvider,30000,1,this);
      }
    }
  }

  @Override public void onLocationChanged(Location loc) {
    Debug("Updated location: %f %f",loc.getLatitude(),loc.getLongitude());
    mLoc= loc;
    mLatLng= new LatLng(mLoc.getLatitude(), mLoc.getLongitude());
    if(System.currentTimeMillis() >= mNextSlpCheck)
      new Thread(new GetSLP(),"GetSLP").start();
    mapUpdate();
  }

  @Override public void onStatusChanged(String provider, int status, Bundle state) {
    Debug("Location %s state is now %d.",provider,status);
    if(status>0)
      mapUpdate();
  }

  @Override public void onProviderEnabled(String provider) {
    Debug("Location %s is now enabled.",provider);
  }

  @Override public void onProviderDisabled(String provider) {
    Debug("Location %s is now disabled.",provider);
  }

  /***********************************************************************/
  /** Background task to request the sea level pressure (SLP) from      **/
  /** the closest reporting station.                                    **/
  /***********************************************************************/
  private class GetSLP implements Runnable {
    public void run() {
      int range;
      boolean found= false;
      //Next check in 15 minutes from now.
      mNextSlpCheck= System.currentTimeMillis()+15*60*1000;
      for(range=10;range<100;range+=10) {
        URL url = buildUrl(range);
        if(url!=null) {
          String xml = fetch(url);
          if(xml!=null) {
            //found= parse(xml);
            String slp= hack("sea_level_pressure_mb",xml);
            if(slp!=null) {
              mSLP= Float.parseFloat(slp);
              mStationID= hack("station_id", xml);
              found = true;
              break;
            }
          }
        }
      }
      if(found) {
        Debug("Station found within %dkm.", range);
        mSlpFound = true;
        mActivity.runOnUiThread(new Runnable(){ public void run() { mapUpdate(); } });
      } else {
        Debug("No stations found within 100km!");
      }
    }

    //Build the request URL using a radial range from the current (longitude,latitude)
    private URL buildUrl(int range) {
      URL url= null;
      String protocol= "http";
      String host= "www.aviationweather.gov";
      String page= "adds/dataserver_current/httpparam";
      String parms= "dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=1&mostRecent=true";
      parms+= String.format("&radialDistance=%d;%f,%f",range,mLatLng.longitude,mLatLng.latitude);
      String urlStr= String.format("%s://%s/%s?%s",protocol,host,page,parms);
      try {
        url = new URL(urlStr);
      } catch(Exception ex) {
        Debug("buildUrl(%s) blew up.", urlStr);
      }
      return(url);
    }

    //Fetch the most current METARS report as an xml document.
    private String fetch(URL url) {
      String text= null;
      try {
        HttpURLConnection connect = (HttpURLConnection)url.openConnection();
        InputStream strm= connect.getInputStream();
        BufferedReader reader= new BufferedReader(new InputStreamReader(strm));
        StringBuilder xml= new StringBuilder();
        String line;
        while((line= reader.readLine())!=null) {
          xml.append(line);
        }
        reader.close();
        text= xml.toString();
      } catch(Exception ex) {
        Debug("GetSLP.fetch() blew up.");
      }
      return(text);
    }

    //Quick hack version to extract a value from the xml string.
    private String hack(String name, String xml) {
      String value= null;
      String tag= String.format("<%s>",name);
      int nStart= xml.indexOf(tag);
      if(nStart>=0) {
        int nEnd = xml.indexOf("</", nStart+tag.length());
        if(nEnd>nStart) {
          value = xml.substring(nStart+tag.length(), nEnd);
        }
      }
      return(value);
    }

    //The proper (but incomplete) method to parse the xml document.
    private boolean parse(String xml) {
      boolean found= false;
      Float slp= 0.0f;
      try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputSource src= new InputSource();
        src.setCharacterStream(new StringReader(xml));
        Document doc = builder.parse(src);
        //TODO: Walk the nodes down to "sea_level_pressure_mb"
        //slp= Float.parseFloat(value);
        //found= true;
      } catch(Exception ex) {
        Debug("GetSLP.parse() blew up.");
      }
      return(found);
    }
  }

  private void Debug(String fmt, Object... args) { Log.w("2Page:Page2", String.format(fmt, args)); }
}
//EOF: PAGE2.JAVA

Layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:background="@color/ForestGreen"
  tools:context=".Page2"
>
  <TextView
    android:id="@+id/Page2_Title"
    android:text="This is Page Two"
    android:textSize="24pt"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:gravity="center"
  />
  <com.google.android.gms.maps.MapView
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_below="@+id/Page2_Title"
  />
</RelativeLayout>
TeasingDart
  • 371
  • 1
  • 6
  • The SLP changes constantly but slowly, so I would start the HTTP GET for the METAR when the app starts and consider the response valid for about 15 minutes or so. You don't want to be waiting for a web response every time you check the sensor. – TeasingDart Aug 06 '15 at 22:57
  • I didn't understand what do you mean. Could you guide me from step to step ? I do understand for the metar data must be loaded frequently. I just didn't how to retrieve it from www.aviationweather.gov – Leonard Febrianto Aug 06 '15 at 23:03
  • Google "android http get xml" [SO](http://stackoverflow.com/questions/3395154/android-read-an-xml-file-with-http-get) – TeasingDart Aug 06 '15 at 23:06
  • So i just retrieve it from aviationweather.gov ? And i don't need make any xml site based on your code ? – Leonard Febrianto Aug 07 '15 at 01:39
  • My latest edit is a completely self-contained fragment, just drop it into your activity. If it helps, please remember to mark my answer as "Answered" and award the bounty. – TeasingDart Aug 07 '15 at 01:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/85362/discussion-between-leonard-febrianto-and-teasingdart). – Leonard Febrianto Aug 07 '15 at 01:51