Home | History | Annotate | Download | only in geolocation
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/geolocation/gps_location_provider_linux.h"
      6 
      7 #include <errno.h>
      8 
      9 #include <algorithm>
     10 #include <cmath>
     11 
     12 #include "base/bind.h"
     13 #include "base/compiler_specific.h"
     14 #include "base/logging.h"
     15 #include "base/memory/scoped_ptr.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/strings/stringprintf.h"
     18 #include "content/public/common/geoposition.h"
     19 
     20 namespace content {
     21 namespace {
     22 
     23 const int kGpsdReconnectRetryIntervalMillis = 10 * 1000;
     24 
     25 // As per http://gpsd.berlios.de/performance.html#id374524, poll twice per sec.
     26 const int kPollPeriodMovingMillis = 500;
     27 
     28 // Poll less frequently whilst stationary.
     29 const int kPollPeriodStationaryMillis = kPollPeriodMovingMillis * 3;
     30 
     31 // GPS reading must differ by more than this amount to be considered movement.
     32 const int kMovementThresholdMeters = 20;
     33 
     34 // This algorithm is reused from the corresponding code in the Gears project.
     35 // The arbitrary delta is decreased (Gears used 100 meters); if we need to
     36 // decrease it any further we'll likely want to do some smarter filtering to
     37 // remove GPS location jitter noise.
     38 bool PositionsDifferSiginificantly(const Geoposition& position_1,
     39                                    const Geoposition& position_2) {
     40   const bool pos_1_valid = position_1.Validate();
     41   if (pos_1_valid != position_2.Validate())
     42     return true;
     43   if (!pos_1_valid) {
     44     DCHECK(!position_2.Validate());
     45     return false;
     46   }
     47   double delta = std::sqrt(
     48       std::pow(std::fabs(position_1.latitude - position_2.latitude), 2) +
     49       std::pow(std::fabs(position_1.longitude - position_2.longitude), 2));
     50   // Convert to meters. 1 minute of arc of latitude (or longitude at the
     51   // equator) is 1 nautical mile or 1852m.
     52   delta *= 60 * 1852;
     53   return delta > kMovementThresholdMeters;
     54 }
     55 
     56 }  // namespace
     57 
     58 #if defined(USE_LIBGPS)
     59 
     60 // See http://crbug.com/103751.
     61 COMPILE_ASSERT(GPSD_API_MAJOR_VERSION == 5, GPSD_API_version_is_not_5);
     62 
     63 namespace {
     64 
     65 const char kLibGpsName[] = "libgps.so.20";
     66 
     67 }  // namespace
     68 
     69 LibGps::LibGps()
     70     : gps_data_(new gps_data_t),
     71       is_open_(false) {
     72 }
     73 
     74 LibGps::~LibGps() {
     75   Stop();
     76 }
     77 
     78 LibGps* LibGps::New() {
     79   scoped_ptr<LibGps> libgps(new LibGps);
     80   if (!libgps->libgps_loader_.Load(kLibGpsName))
     81     return NULL;
     82 
     83   return libgps.release();
     84 }
     85 
     86 bool LibGps::Start() {
     87   if (is_open_)
     88     return true;
     89 
     90   errno = 0;
     91   if (libgps_loader_.gps_open(GPSD_SHARED_MEMORY, 0, gps_data_.get()) != 0) {
     92     // See gps.h NL_NOxxx for definition of gps_open() error numbers.
     93     DLOG(WARNING) << "gps_open() failed " << errno;
     94     return false;
     95   }
     96 
     97   is_open_ = true;
     98   return true;
     99 }
    100 
    101 void LibGps::Stop() {
    102   if (is_open_)
    103     libgps_loader_.gps_close(gps_data_.get());
    104   is_open_ = false;
    105 }
    106 
    107 bool LibGps::Read(Geoposition* position) {
    108   DCHECK(position);
    109   position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
    110   if (!is_open_) {
    111       DLOG(WARNING) << "No gpsd connection";
    112       position->error_message = "No gpsd connection";
    113       return false;
    114   }
    115 
    116   if (libgps_loader_.gps_read(gps_data_.get()) < 0) {
    117       DLOG(WARNING) << "gps_read() fails";
    118       position->error_message = "gps_read() fails";
    119       return false;
    120   }
    121 
    122   if (!GetPositionIfFixed(position)) {
    123       DLOG(WARNING) << "No fixed position";
    124       position->error_message = "No fixed position";
    125       return false;
    126   }
    127 
    128   position->error_code = Geoposition::ERROR_CODE_NONE;
    129   position->timestamp = base::Time::Now();
    130   if (!position->Validate()) {
    131     // GetPositionIfFixed returned true, yet we've not got a valid fix.
    132     // This shouldn't happen; something went wrong in the conversion.
    133     NOTREACHED() << "Invalid position from GetPositionIfFixed: lat,long "
    134                  << position->latitude << "," << position->longitude
    135                  << " accuracy " << position->accuracy << " time "
    136                  << position->timestamp.ToDoubleT();
    137     position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
    138     position->error_message = "Bad fix from gps";
    139     return false;
    140   }
    141   return true;
    142 }
    143 
    144 bool LibGps::GetPositionIfFixed(Geoposition* position) {
    145   DCHECK(position);
    146   if (gps_data_->status == STATUS_NO_FIX) {
    147     DVLOG(2) << "Status_NO_FIX";
    148     return false;
    149   }
    150 
    151   if (isnan(gps_data_->fix.latitude) || isnan(gps_data_->fix.longitude)) {
    152     DVLOG(2) << "No valid lat/lon value";
    153     return false;
    154   }
    155 
    156   position->latitude = gps_data_->fix.latitude;
    157   position->longitude = gps_data_->fix.longitude;
    158 
    159   if (!isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
    160     position->accuracy = std::max(gps_data_->fix.epx, gps_data_->fix.epy);
    161   } else if (isnan(gps_data_->fix.epx) && !isnan(gps_data_->fix.epy)) {
    162     position->accuracy = gps_data_->fix.epy;
    163   } else if (!isnan(gps_data_->fix.epx) && isnan(gps_data_->fix.epy)) {
    164     position->accuracy = gps_data_->fix.epx;
    165   } else {
    166     // TODO(joth): Fixme. This is a workaround for http://crbug.com/99326
    167     DVLOG(2) << "libgps reported accuracy NaN, forcing to zero";
    168     position->accuracy = 0;
    169   }
    170 
    171   if (gps_data_->fix.mode == MODE_3D && !isnan(gps_data_->fix.altitude)) {
    172     position->altitude = gps_data_->fix.altitude;
    173     if (!isnan(gps_data_->fix.epv))
    174       position->altitude_accuracy = gps_data_->fix.epv;
    175   }
    176 
    177   if (!isnan(gps_data_->fix.track))
    178     position->heading = gps_data_->fix.track;
    179   if (!isnan(gps_data_->fix.speed))
    180     position->speed = gps_data_->fix.speed;
    181   return true;
    182 }
    183 
    184 #else  // !defined(USE_LIBGPS)
    185 
    186 // Stub implementation of LibGps.
    187 LibGps::LibGps() {
    188 }
    189 
    190 LibGps::~LibGps() {
    191 }
    192 
    193 LibGps* LibGps::New() {
    194   return NULL;
    195 }
    196 
    197 bool LibGps::Start() {
    198   return false;
    199 }
    200 
    201 void LibGps::Stop() {
    202 }
    203 
    204 bool LibGps::Read(Geoposition* position) {
    205   return false;
    206 }
    207 
    208 bool LibGps::GetPositionIfFixed(Geoposition* position) {
    209   return false;
    210 }
    211 
    212 #endif  // !defined(USE_LIBGPS)
    213 
    214 GpsLocationProviderLinux::GpsLocationProviderLinux(LibGpsFactory libgps_factory)
    215     : gpsd_reconnect_interval_millis_(kGpsdReconnectRetryIntervalMillis),
    216       poll_period_moving_millis_(kPollPeriodMovingMillis),
    217       poll_period_stationary_millis_(kPollPeriodStationaryMillis),
    218       libgps_factory_(libgps_factory),
    219       weak_factory_(this) {
    220   DCHECK(libgps_factory_);
    221 }
    222 
    223 GpsLocationProviderLinux::~GpsLocationProviderLinux() {
    224 }
    225 
    226 bool GpsLocationProviderLinux::StartProvider(bool high_accuracy) {
    227   if (!high_accuracy) {
    228     StopProvider();
    229     return true;  // Not an error condition, so still return true.
    230   }
    231   if (gps_ != NULL) {
    232     DCHECK(weak_factory_.HasWeakPtrs());
    233     return true;
    234   }
    235   position_.error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
    236   gps_.reset(libgps_factory_());
    237   if (gps_ == NULL) {
    238     DLOG(WARNING) << "libgps could not be loaded";
    239     return false;
    240   }
    241   ScheduleNextGpsPoll(0);
    242   return true;
    243 }
    244 
    245 void GpsLocationProviderLinux::StopProvider() {
    246   weak_factory_.InvalidateWeakPtrs();
    247   gps_.reset();
    248 }
    249 
    250 void GpsLocationProviderLinux::GetPosition(Geoposition* position) {
    251   DCHECK(position);
    252   *position = position_;
    253   DCHECK(position->Validate() ||
    254          position->error_code != Geoposition::ERROR_CODE_NONE);
    255 }
    256 
    257 void GpsLocationProviderLinux::RequestRefresh() {
    258   ScheduleNextGpsPoll(0);
    259 }
    260 
    261 void GpsLocationProviderLinux::OnPermissionGranted() {
    262 }
    263 
    264 void GpsLocationProviderLinux::DoGpsPollTask() {
    265   if (!gps_->Start()) {
    266     DLOG(WARNING) << "Couldn't start GPS provider.";
    267     ScheduleNextGpsPoll(gpsd_reconnect_interval_millis_);
    268     return;
    269   }
    270 
    271   Geoposition new_position;
    272   if (!gps_->Read(&new_position)) {
    273     ScheduleNextGpsPoll(poll_period_stationary_millis_);
    274     return;
    275   }
    276 
    277   DCHECK(new_position.Validate() ||
    278          new_position.error_code != Geoposition::ERROR_CODE_NONE);
    279   const bool differ = PositionsDifferSiginificantly(position_, new_position);
    280   ScheduleNextGpsPoll(differ ? poll_period_moving_millis_ :
    281                                poll_period_stationary_millis_);
    282   if (differ || new_position.error_code != Geoposition::ERROR_CODE_NONE) {
    283     // Update if the new location is interesting or we have an error to report.
    284     position_ = new_position;
    285     NotifyCallback(position_);
    286   }
    287 }
    288 
    289 void GpsLocationProviderLinux::ScheduleNextGpsPoll(int interval) {
    290   weak_factory_.InvalidateWeakPtrs();
    291   base::MessageLoop::current()->PostDelayedTask(
    292       FROM_HERE,
    293       base::Bind(&GpsLocationProviderLinux::DoGpsPollTask,
    294                  weak_factory_.GetWeakPtr()),
    295       base::TimeDelta::FromMilliseconds(interval));
    296 }
    297 
    298 LocationProvider* NewSystemLocationProvider() {
    299   return new GpsLocationProviderLinux(LibGps::New);
    300 }
    301 
    302 }  // namespace content
    303