1[/============================================================================== 2 Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands. 3 Copyright (c) 2008-2012 Bruno Lalande, Paris, France. 4 Copyright (c) 2009-2012 Mateusz Loskot, London, UK., London, UK 5 6 Use, modification and distribution is subject to the Boost Software License, 7 Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 8 http://www.boost.org/LICENSE_1_0.txt) 9===============================================================================/] 10 11[/ note the source code in this QBK is the only not (yet) checked by a compiler] 12 13[section:design Design Rationale] 14 15Suppose you need a C++ program to calculate the distance between two points. 16You might define a struct: 17 18 struct mypoint 19 { 20 double x, y; 21 }; 22 23and a function, containing the algorithm: 24 25 double distance(mypoint const& a, mypoint const& b) 26 { 27 double dx = a.x - b.x; 28 double dy = a.y - b.y; 29 return sqrt(dx * dx + dy * dy); 30 } 31 32Quite simple, and it is usable, but not generic. For a library it has to be 33designed way further. The design above can only be used for 2D points, for the 34struct [*mypoint] (and no other struct), in a Cartesian coordinate system. A 35generic library should be able to calculate the distance: 36 37* for any point class or struct, not on just this [*mypoint] type 38* in more than two dimensions 39* for other coordinate systems, e.g. over the earth or on a sphere 40* between a point and a line or between other geometry combinations 41* in higher precision than ['double] 42* avoiding the square root: often we don't want to do that because it is a relatively expensive 43 function, and for comparing distances it is not necessary 44 45In this and following sections we will make the design step by step more generic. 46 47[heading Using Templates] 48 49The distance function can be changed into a template function. This is trivial and allows 50calculating the distance between other point types than just [*mypoint]. We add two template 51parameters, allowing input of two different point types. 52 53 template <typename P1, typename P2> 54 double distance(P1 const& a, P2 const& b) 55 { 56 double dx = a.x - b.x; 57 double dy = a.y - b.y; 58 return std::sqrt(dx * dx + dy * dy); 59 } 60 61This template version is slightly better, but not much. 62 63Consider a C++ class where member variables are protected... 64Such a class does not allow to access `x` and `y` members directly. So, this paragraph is short 65and we just move on. 66 67[heading Using Traits] 68 69We need to take a generic approach and allow any point type as input to the distance function. 70Instead of accessing `x` and `y` members, we will add a few levels of indirection, using a 71traits system. The function then becomes: 72 73 template <typename P1, typename P2> 74 double distance(P1 const& a, P2 const& b) 75 { 76 double dx = get<0>(a) - get<0>(b); 77 double dy = get<1>(a) - get<1>(b); 78 return std::sqrt(dx * dx + dy * dy); 79 } 80 81This adapted distance function uses a generic get function, with dimension as a template parameter, 82to access the coordinates of a point. This get forwards to the traits system, defined as following: 83 84 namespace traits 85 { 86 template <typename P, int D> 87 struct access {}; 88 } 89 90which is then specialized for our [*mypoint] type, implementing a static method called `get`: 91 92 namespace traits 93 { 94 template <> 95 struct access<mypoint, 0> 96 { 97 static double get(mypoint const& p) 98 { 99 return p.x; 100 } 101 }; 102 // same for 1: p.y 103 ... 104 } 105 106Calling `traits::access<mypoint, 0>::get(a)` now returns us our `x` coordinate. Nice, isn't it? 107It is too verbose for a function like this, used so often in the library. We can shorten the syntax 108by adding an extra free function: 109 110 template <int D, typename P> 111 inline double get(P const& p) 112 { 113 return traits::access<P, D>::get(p); 114 } 115 116This enables us to call `get<0>(a)`, for any point having the traits::access specialization, 117as shown in the distance algorithm at the start of this paragraph. So we wanted to enable classes 118with methods like `x()`, and they are supported as long as there is a specialization of the access 119`struct` with a static `get` function returning `x()` for dimension 0, and similar for 1 and `y()`. 120 121[heading Dimension Agnosticism] 122 123Now we can calculate the distance between points in 2D, points of any structure or class. 124However, we wanted to have 3D as well. So we have to make it dimension agnostic. This complicates 125our distance function. We can use a `for` loop to walk through dimensions, but for loops have 126another performance than the straightforward coordinate addition which was there originally. 127However, we can make more usage of templates and make the distance algorithm as following, 128more complex but attractive for template fans: 129 130 template <typename P1, typename P2, int D> 131 struct pythagoras 132 { 133 static double apply(P1 const& a, P2 const& b) 134 { 135 double d = get<D-1>(a) - get<D-1>(b); 136 return d * d + pythagoras<P1, P2, D-1>::apply(a, b); 137 } 138 }; 139 140 template <typename P1, typename P2 > 141 struct pythagoras<P1, P2, 0> 142 { 143 static double apply(P1 const&, P2 const&) 144 { 145 return 0; 146 } 147 }; 148 149The distance function is calling that `pythagoras` structure, specifying the number of dimensions: 150 151 template <typename P1, typename P2> 152 double distance(P1 const& a, P2 const& b) 153 { 154 BOOST_STATIC_ASSERT(( dimension<P1>::value == dimension<P2>::value )); 155 156 return sqrt(pythagoras<P1, P2, dimension<P1>::value>::apply(a, b)); 157 } 158 159The dimension which is referred to is defined using another traits class: 160 161`` 162 namespace traits 163 { 164 template <typename P> 165 struct dimension {}; 166 } 167`` 168 169which has to be specialized again for the `struct mypoint`. 170 171Because it only has to publish a value, we conveniently derive it from the 172__boost_mpl__ `class boost::mpl::int_`: 173 174`` 175namespace traits 176{ 177 template <> 178 struct dimension<mypoint> : boost::mpl::int_<2> 179 {}; 180} 181`` 182 183Like the free get function, the library also contains a dimension meta-function. 184 185 template <typename P> 186 struct dimension : traits::dimension<P> 187 {}; 188 189Below is explained why the extra declaration is useful. Now we have agnosticism in the number of 190dimensions. Our more generic distance function now accepts points of three or more dimensions. 191The compile-time assertion will prevent point a having two dimension and point b having 192three dimensions. 193 194[heading Coordinate Type] 195 196We assumed double above. What if our points are in integer? 197 198We can easily add a traits class, and we will do that. However, the distance between two integer 199coordinates can still be a fractionized value. Besides that, a design goal was to avoid square 200roots. We handle these cases below, in another paragraph. For the moment we keep returning double, 201but we allow integer coordinates for our point types. To define the coordinate type, we add 202another traits class, `coordinate_type`, which should be specialized by the library user: 203 204 namespace traits 205 { 206 template <typename P> 207 struct coordinate_type{}; 208 209 // specialization for our mypoint 210 template <> 211 struct coordinate_type<mypoint> 212 { 213 typedef double type; 214 }; 215 } 216 217Like the access function, where we had a free get function, we add a proxy here as well. 218A longer version is presented later on, the short function would look like this: 219 220 template <typename P> 221 struct coordinate_type : traits::coordinate_type<P> {}; 222 223We now can modify our distance algorithm again. Because it still returns double, we only 224modify the `pythagoras` computation class. It should return the coordinate type of its input. 225But, it has two input, possibly different, point types. They might also differ in their 226coordinate types. Not that that is very likely, but we’re designing a generic library and we 227should handle those strange cases. We have to choose one of the coordinate types and of course 228we select the one with the highest precision. This is not worked out here, it would be too long, 229and it is not related to geometry. We just assume that there is a meta-function `select_most_precise` 230selecting the best type. 231 232So our computation class becomes: 233 234 template <typename P1, typename P2, int D> 235 struct pythagoras 236 { 237 typedef typename select_most_precise 238 < 239 typename coordinate_type<P1>::type, 240 typename coordinate_type<P2>::type 241 >::type computation_type; 242 243 static computation_type apply(P1 const& a, P2 const& b) 244 { 245 computation_type d = get<D-1>(a) - get<D-1>(b); 246 return d * d + pythagoras <P1, P2, D-1> ::apply(a, b); 247 } 248 }; 249 250[heading Different Geometries] 251 252We have designed a dimension agnostic system supporting any point type of any 253coordinate type. There are still some tweaks but they will be worked out later. 254Now we will see how we calculate the distance between a point and a polygon, or 255between a point and a line-segment. These formulae are more complex, and the 256influence on design is even larger. We don’t want to add a function with another 257name: 258 259 template <typename P, typename S> 260 double distance_point_segment(P const& p, S const& s) 261 262We want to be generic, the distance function has to be called from code not 263knowing the type of geometry it handles, so it has to be named distance. We also 264cannot create an overload because that would be ambiguous, having the same 265template signature. There are two solutions: 266 267* tag dispatching 268* SFINAE 269 270We select tag dispatching because it fits into the traits system. The earlier 271versions (previews) of Boost.Geometry used SFINAE but we found it had several 272drawbacks for such a big design, so the switch to tag dispatching was made. 273 274With tag dispatching the distance algorithm inspects the type of geometry of the 275input parameters. The distance function will be changed into this: 276 277 template <typename G1, typename G2> 278 double distance(G1 const& g1, G2 const& g2) 279 { 280 return dispatch::distance 281 < 282 typename tag<G1>::type, 283 typename tag<G2>::type, 284 G1, G2 285 >::apply(g1, g2); 286 } 287 288It is referring to the tag meta-function and forwarding the call to the [*apply] method of 289a ['dispatch::distance] structure. The [*tag] meta-function is another traits class, and should 290be specialized for per point type, both shown here: 291 292 namespace traits 293 { 294 template <typename G> 295 struct tag {}; 296 297 // specialization 298 template <> 299 struct tag<mypoint> 300 { 301 typedef point_tag type; 302 }; 303 } 304 305Free meta-function, like coordinate_system and get: 306 307 template <typename G> 308 struct tag : traits::tag<G> {}; 309 310[*Tags] (`point_tag`, `segment_tag`, etc) are empty structures with the purpose to specialize a 311dispatch struct. The dispatch struct for distance, and its specializations, are all defined 312in a separate namespace and look like the following: 313 314 namespace dispatch { 315 template < typename Tag1, typename Tag2, typename G1, typename G2 > 316 struct distance 317 {}; 318 319 template <typename P1, typename P2> 320 struct distance < point_tag, point_tag, P1, P2 > 321 { 322 static double apply(P1 const& a, P2 const& b) 323 { 324 // here we call pythagoras 325 // exactly like we did before 326 ... 327 } 328 }; 329 330 template <typename P, typename S> 331 struct distance 332 < 333 point_tag, segment_tag, P, S 334 > 335 { 336 static double apply(P const& p, S const& s) 337 { 338 // here we refer to another function 339 // implementing point-segment 340 // calculations in 2 or 3 341 // dimensions... 342 ... 343 } 344 }; 345 346 // here we might have many more 347 // specializations, 348 // for point-polygon, box-circle, etc. 349 350 } // namespace 351 352So yes, it is possible; the distance algorithm is generic now in the sense that it also 353supports different geometry types. One drawback: we have to define two dispatch specializations 354for point - segment and for segment - point separately. That will also be solved, in the paragraph 355reversibility below. The example below shows where we are now: different point types, 356geometry types, dimensions. 357 358 point a(1,1); 359 point b(2,2); 360 std::cout << distance(a,b) << std::endl; 361 segment s1(0,0,5,3); 362 std::cout << distance(a, s1) << std::endl; 363 rgb red(255, 0, 0); 364 rbc orange(255, 128, 0); 365 std::cout << "color distance: " << distance(red, orange) << std::endl; 366 367[heading Kernel Revisited] 368 369We described above that we had a traits class `coordinate_type`, defined in namespace traits, 370and defined a separate `coordinate_type` class as well. This was actually not really necessary 371before, because the only difference was the namespace clause. But now that we have another 372geometry type, a segment in this case, it is essential. We can call the `coordinate_type` 373meta-function for any geometry type, point, segment, polygon, etc, implemented again by tag dispatching: 374 375 template <typename G> 376 struct coordinate_type 377 { 378 typedef typename dispatch::coordinate_type 379 < 380 typename tag<G>::type, G 381 >::type type; 382 }; 383 384Inside the dispatch namespace this meta-function is implemented twice: a generic version and 385one specialization for points. The specialization for points calls the traits class. 386The generic version calls the point specialization, as a sort of recursive meta-function definition: 387 388 namespace dispatch 389 { 390 391 // Version for any geometry: 392 template <typename GeometryTag, typename G> 393 struct coordinate_type 394 { 395 typedef typename point_type 396 < 397 GeometryTag, G 398 >::type point_type; 399 400 // Call specialization on point-tag 401 typedef typename coordinate_type < point_tag, point_type >::type type; 402 }; 403 404 // Specialization for point-type: 405 template <typename P> 406 struct coordinate_type<point_tag, P> 407 { 408 typedef typename 409 traits::coordinate_type<P>::type 410 type; 411 }; 412 } 413 414So it calls another meta-function point_type. This is not elaborated in here but realize that it 415is available for all geometry types, and typedefs the point type which makes up the geometry, 416calling it type. 417 418The same applies for the meta-function dimension and for the upcoming meta-function coordinate system. 419 420[heading Coordinate System] 421 422Until here we assumed a Cartesian system. But we know that the Earth is not flat. 423Calculating a distance between two GPS-points with the system above would result in nonsense. 424So we again extend our design. We define for each point type a coordinate system type 425using the traits system again. Then we make the calculation dependant on that coordinate system. 426 427Coordinate system is similar to coordinate type, a meta-function, calling a dispatch function 428to have it for any geometry-type, forwarding to its point specialization, and finally calling 429a traits class, defining a typedef type with a coordinate system. We don’t show that all here again. 430We only show the definition of a few coordinate systems: 431 432 struct cartesian {}; 433 434 template<typename DegreeOrRadian> 435 struct geographic 436 { 437 typedef DegreeOrRadian units; 438 }; 439 440So Cartesian is simple, for geographic we can also select if its coordinates are stored in degrees 441or in radians. 442 443The distance function will now change: it will select the computation method for the corresponding 444coordinate system and then call the dispatch struct for distance. We call the computation method 445specialized for coordinate systems a strategy. So the new version of the distance function is: 446 447 template <typename G1, typename G2> 448 double distance(G1 const& g1, G2 const& g2) 449 { 450 typedef typename strategy_distance 451 < 452 typename coordinate_system<G1>::type, 453 typename coordinate_system<G2>::type, 454 typename point_type<G1>::type, 455 typename point_type<G2>::type, 456 dimension<G1>::value 457 >::type strategy; 458 459 return dispatch::distance 460 < 461 typename tag<G1>::type, 462 typename tag<G2>::type, 463 G1, G2, strategy 464 >::apply(g1, g2, strategy()); 465 } 466 467The strategy_distance mentioned here is a struct with specializations for different coordinate 468systems. 469 470 template <typename T1, typename T2, typename P1, typename P2, int D> 471 struct strategy_distance 472 { 473 typedef void type; 474 }; 475 476 template <typename P1, typename P2, int D> 477 struct strategy_distance<cartesian, cartesian, P1, P2, D> 478 { 479 typedef pythagoras<P1, P2, D> type; 480 }; 481 482So, here is our `pythagoras` again, now defined as a strategy. The distance dispatch function just 483calls its apply method. 484 485So this is an important step: for spherical or geographical coordinate systems, another 486strategy (computation method) can be implemented. For spherical coordinate systems 487have the haversine formula. So the dispatching traits struct is specialized like this 488 489 template <typename P1, typename P2, int D = 2> 490 struct strategy_distance<spherical, spherical, P1, P2, D> 491 { 492 typedef haversine<P1, P2> type; 493 }; 494 495 // struct haversine with apply function 496 // is omitted here 497 498For geography, we have some alternatives for distance calculation. There is the Andoyer method, 499fast and precise, and there is the Vincenty method, slower and more precise, and there are some 500less precise approaches as well. 501 502Per coordinate system, one strategy is defined as the default strategy. To be able to use 503another strategy as well, we modify our design again and add an overload for the distance 504algorithm, taking a strategy object as a third parameter. 505 506This new overload distance function also has the advantage that the strategy can be constructed 507outside the distance function. Because it was constructed inside above, it could not have 508construction parameters. But for Andoyer or Vincenty, or the haversine formula, it certainly 509makes sense to have a constructor taking the radius of the earth as a parameter. 510 511So, the distance overloaded function is: 512 513 template <typename G1, typename G2, typename S> 514 double distance(G1 const& g1, G2 const& g2, S const& strategy) 515 { 516 return dispatch::distance 517 < 518 typename tag<G1>::type, 519 typename tag<G2>::type, 520 G1, G2, S 521 >::apply(g1, g2, strategy); 522 } 523 524The strategy has to have a method apply taking two points as arguments (for points). It is not 525required that it is a static method. A strategy might define a constructor, where a configuration 526value is passed and stored as a member variable. In those cases a static method would be 527inconvenient. It can be implemented as a normal method (with the const qualifier). 528 529We do not list all implementations here, Vincenty would cover half a page of mathematics, 530but you will understand the idea. We can call distance like this: 531 532 distance(c1, c2) 533 534where `c1` and `c2` are Cartesian points, or like this: 535 536 distance(g1, g2) 537 538where `g1` and `g2` are Geographic points, calling the default strategy for Geographic 539points (e.g. Andoyer), and like this: 540 541 distance(g1, g2, vincenty<G1, G2>(6275)) 542 543where a strategy is specified explicitly and constructed with a radius. 544 545[/ 'TODO: What to do with this section? We have concepts already --mloskot] 546[heading Point Concept] 547The five traits classes mentioned in the previous sections form together the 548Point Concept. Any point type for which specializations are implemented in 549the traits namespace should be accepted as a valid type. So the Point Concept 550consists of: 551 552* a specialization for `traits::tag` 553* a specialization for `traits::coordinate_system` 554* a specialization for `traits::coordinate_type` 555* a specialization for `traits::dimension` 556* a specialization for `traits::access` 557 558The last one is a class, containing the method get and the (optional) method 559set, the first four are metafunctions, either defining type or declaring a 560value (conform MPL conventions). 561 562So we now have agnosticism for the number of dimensions, agnosticism for 563coordinate systems; the design can handle any coordinate type, and it can 564handle different geometry types. Furthermore we can specify our own 565strategies, the code will not compile in case of two points with different 566dimensions (because of the assertion), and it will not compile for two points 567with different coordinate systems (because there is no specialization). 568A library can check if a point type fulfills the requirements imposed by the 569concepts. This is handled in the upcoming section Concept Checking. 570 571[heading Return Type] 572 573We promised that calling `std::sqrt` was not always necessary. So we define a distance result `struct` 574that contains the squared value and is convertible to a double value. This, however, only has to 575be done for `pythagoras`. The spherical distance functions do not take the square root so for them 576it is not necessary to avoid the expensive square root call; they can just return their distance. 577 578So the distance result struct is dependant on strategy, therefore made a member type of 579the strategy. The result struct looks like this: 580 581 template<typename T = double> 582 struct cartesian_distance 583 { 584 T sq; 585 explicit cartesian_distance(T const& v) : sq (v) {} 586 587 inline operator T() const 588 { 589 return std::sqrt(sq); 590 } 591 }; 592 593It also has operators defined to compare itself to other results without taking the square root. 594 595Each strategy should define its return type, within the strategy class, for example: 596 597 typedef cartesian_distance<T> return_type; 598 599or: 600 601 typedef double return_type; 602 603for cartesian (pythagoras) and spherical, respectively. 604 605Again our distance function will be modified, as expected, to reflect the new return type. 606For the overload with a strategy it is not complex: 607 608 template < typename G1, typename G2, typename Strategy > 609 typename Strategy::return_type distance( G1 const& G1 , G2 const& G2 , S const& strategy) 610 611But for the one without strategy we have to select strategy, coordinate type, etc. 612It would be spacious to do it in one line so we add a separate meta-function: 613 614 template <typename G1, typename G2 = G1> 615 struct distance_result 616 { 617 typedef typename point_type<G1>::type P1; 618 typedef typename point_type<G2>::type P2; 619 typedef typename strategy_distance 620 < 621 typename cs_tag<P1>::type, 622 typename cs_tag<P2>::type, 623 P1, P2 624 >::type S; 625 626 typedef typename S::return_type type; 627 }; 628 629and modify our distance function: 630 631 template <typename G1, typename G2> 632 inline typename distance_result<G1, G2>::type distance(G1 const& g1, G2 const& g2) 633 { 634 // ... 635 } 636 637Of course also the apply functions in the dispatch specializations will return a result like this. 638They have a strategy as a template parameter everywhere, making the less verbose version possible. 639 640[heading Axis Order] 641 642This is an important note for users who develop GIS applications, require compliance with OGC 643standards or use variety of coordinate reference systems (CRS). 644 645Boost.Geometry convention is that coordinate values of 2D tuple is always given according to 646mathematical axis order: X,Y. Considering GIS applications, that always means easting,northing or, 647in terms of geographic coordinate system: longitude,latitude. 648Boost.Geometry does not allow or respect coordinate values listed in the axis order as 649specified by coordinate reference system (CRS). 650 651In practice, users may easily adapt point types with alternate axis ordering and 652specify coordinate access in order as expected by Boost.Geometry. 653 654[heading Summary] 655 656In this design rationale, __boost_geometry__ is step by step designed using tag dispatching, 657concepts, traits, and metaprogramming. We used the well-known distance function 658to show the design. 659 660__boost_geometry__ is designed like described here, with 661some more techniques as automatically reversing template arguments, tag casting, 662and reusing implementation classes or dispatch classes as policies in other 663dispatch classes. 664 665[endsect] [/ end of section Design Rationale] 666