libmoost
|
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