libmoost
/home/mhx/git/github/libmoost/include/moost/container/geo_map.hpp
Go to the documentation of this file.
00001 /* vim:set ts=3 sw=3 sts=3 et: */
00028 #ifndef MOOST_CONTIANER_GEO_MAP_HPP
00029 #define MOOST_CONTIANER_GEO_MAP_HPP
00030 
00031 #include <vector>
00032 #include <algorithm>
00033 #include <cmath>
00034 #include <stdexcept>
00035 
00036 namespace moost { namespace container {
00037 
00046 template<class Data>
00047 class geo_map
00048 {
00049 public:
00050 
00055    struct location
00056    {
00057       location() {}
00058       location(float latitude_, float longitude_) : latitude(latitude_), longitude(longitude_) {}
00059       float latitude;
00060       float longitude;
00061    };
00062 
00064    typedef std::pair<location, Data> value_type;
00065 
00067    typedef typename std::vector<value_type>::const_iterator const_iterator;
00068 
00069 private:
00070 
00071    // TODO: until i figure out a better way to ensure constness of just the location, all public iterators are const
00072    typedef typename std::vector<value_type>::iterator iterator;
00073 
00075    static double pi()
00076    { return 3.141592653589793238462643383279502884197; }
00077 
00079    static double R()
00080    { return 6371.0; }
00081 
00082    static bool cmp_value_type(const value_type & value1, const value_type & value2)
00083    {
00084       return value1.first.longitude < value2.first.longitude;
00085    }
00086 
00088    void degrees2radians(location& loc)
00089    {
00090       loc.latitude *= static_cast<float>(pi() / 180.0);
00091       loc.longitude *= static_cast<float>(pi() / 180.0);
00092    }
00093 
00095    float radius2deltalon(location& loc, float radius)
00096    {
00097       // construct a delta with a bearing due east (pi / 2)
00098       float dLon = static_cast<float>(
00099                       std::atan2(std::sin(pi() / 2.0) * std::sin(radius / R()) * std::cos(loc.latitude),
00100                                  std::cos(radius / R()) - std::sin(loc.latitude) * std::sin(loc.latitude))
00101                                  );
00102 
00103       // if our delta is negative, because our radius is very big or we are at an extreme latitude so the radius wraps around,
00104       // just search everything
00105       if (dLon < 0.0F)
00106         dLon = static_cast<float>(pi());
00107 
00108       return dLon;
00109    }
00110 
00112    float haversine_dist(location& x, location& y)
00113    {
00114       double a =  std::sin((x.latitude - y.latitude) / 2.0)
00115                    * std::sin((x.latitude - y.latitude) / 2.0)
00116                    + std::cos(y.latitude) * std::cos(x.latitude)
00117                    * std::sin((x.longitude - y.longitude) / 2.0)
00118                    * std::sin((x.longitude - y.longitude) / 2.0);
00119       return static_cast<float>( R() * 2.0 * std::atan2(std::sqrt(a), std::sqrt(1.0 - a)) );
00120    }
00121 
00122    // kinda funky that we don't actually store a vector of value_type
00123    // the const requirement of the location doesn't play well with vectors
00124    // requiring the assignment operator for moving elements during inserts/erases
00125    std::vector< value_type > m_values;
00126 
00127 public:
00128 
00130    geo_map()
00131    {}
00132 
00134    void reserve(int num_entries)
00135    {
00136       m_values.reserve(num_entries);
00137    }
00138 
00140    const_iterator insert(const value_type & value, bool ordered = true)
00141    {
00142       if (   std::abs(value.first.latitude) >= 90.0
00143           || std::abs(value.first.longitude) >= 180.0)
00144           throw std::invalid_argument("bad location coordinates");
00145       // convert to radians
00146       value_type v( location( static_cast<float>(value.first.latitude * pi() / 180.0),
00147                               static_cast<float>(value.first.longitude * pi() / 180.0)),
00148                     value.second);
00149       if (ordered)
00150          return m_values.insert(std::lower_bound(m_values.begin(), m_values.end(), v, geo_map<Data>::cmp_value_type), v);
00151       else
00152          return m_values.insert(m_values.end(), v);
00153    }
00154 
00157    void order()
00158    {
00159       std::sort(m_values.begin(), m_values.end(), geo_map<Data>::cmp_value_type);
00160    }
00161 
00167    template <class OutputIterator>
00168    void find(location query, float radius, OutputIterator result)
00169    {
00170       if (   std::abs(query.latitude) >= 90.0
00171           || std::abs(query.longitude) >= 180.0)
00172           throw std::invalid_argument("bad location coordinates");
00173 
00174       // convert location to radians
00175       degrees2radians(query);
00176 
00177       // construct a minimum and maximum longitude to binary search our collection
00178       // longitude given a distance and bearing:
00179       // lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
00180       // lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
00181       // d/R is the angular distance (in radians), where d is the distance traveled and R is the earth’s radius
00182       // also note that because we're going due east, lat2 == lat1
00183 
00184       float dLon = radius2deltalon(query, radius);
00185 
00186       iterator it = std::lower_bound(m_values.begin(), m_values.end(), value_type(location(0.0F, query.longitude - dLon), Data()), geo_map<Data>::cmp_value_type);
00187       iterator end = std::upper_bound(m_values.begin(), m_values.end(), value_type(location(0.0F, query.longitude + dLon), Data()), geo_map<Data>::cmp_value_type);
00188 
00189       for (; it != end; ++it)
00190       {
00191          float d = haversine_dist(it->first, query);
00192          if (d <= radius)
00193             *result++ = *it;
00194       }
00195    }
00196 
00202    template <class OutputIterator>
00203    void find_distances(location query, float radius, OutputIterator result)
00204    {
00205       if (   std::abs(query.latitude) >= 90.0
00206           || std::abs(query.longitude) >= 180.0)
00207           throw std::invalid_argument("bad location coordinates");
00208 
00209       degrees2radians(query);
00210       float dLon = radius2deltalon(query, radius);
00211 
00212       iterator it = std::lower_bound(m_values.begin(), m_values.end(), value_type(location(0.0F, query.longitude - dLon), Data()), geo_map<Data>::cmp_value_type);
00213       iterator end = std::upper_bound(m_values.begin(), m_values.end(), value_type(location(0.0F, query.longitude + dLon), Data()), geo_map<Data>::cmp_value_type);
00214 
00215       for (; it != end; ++it)
00216       {
00217          float d = haversine_dist(it->first, query);
00218          if (d <= radius)
00219             *result++ = std::make_pair(*it, d);
00220       }
00221    }
00222 
00228    template <class OutputIterator>
00229    void find(location min, location max, OutputIterator result)
00230    {
00231       // convert locations to radians
00232       min.latitude *= static_cast<float>( pi() / 180.0 );
00233       min.longitude *= static_cast<float>( pi() / 180.0 );
00234       max.latitude *= static_cast<float>( pi() / 180.0 );
00235       max.longitude *= static_cast<float>( pi() / 180.0 );
00236 
00237       iterator it = std::lower_bound(m_values.begin(), m_values.end(), value_type(min, Data()), geo_map<Data>::cmp_value_type);
00238       iterator end = std::upper_bound(m_values.begin(), m_values.end(), value_type(max, Data()), geo_map<Data>::cmp_value_type);
00239 
00240       for (; it != end; ++it)
00241       {
00242          if (   it->first.latitude >= min.latitude
00243              && it->first.latitude <= max.latitude
00244              && it->first.longitude >= min.longitude
00245              && it->first.longitude <= max.longitude)
00246             *result++ = *it;
00247       }
00248    }
00249 
00251    void clear()
00252    {
00253       m_values.clear();
00254    }
00255 
00259    void swap(geo_map<Data> & table)
00260    {
00261       m_values.swap(table.m_values);
00262    }
00263 
00266    size_t size()
00267    {
00268       return m_values.size();
00269    }
00270 
00272    const_iterator begin() const { return m_values.begin(); }
00274    const_iterator end() const { return m_values.end(); }
00275 };
00276 
00277 }} // moost::container
00278 
00279 #endif // MOOST_CONTIANER_GEO_MAP_HPP