Ignore:
Timestamp:
Aug 20, 2014, 1:06:16 PM (11 years ago)
Author:
Frederik Heber <heber@…>
Children:
ef3885
Parents:
1cde4e8
git-author:
Frederik Heber <heber@…> (07/09/14 22:08:37)
git-committer:
Frederik Heber <heber@…> (08/20/14 13:06:16)
Message:

Added calculation of center of minimum distance by bisection.

  • this will give us a unique a definite point independent of the (rotational) position of the point set on the unit sphere.
  • added unit test.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/Fragmentation/Exporters/SphericalPointDistribution.cpp

    r1cde4e8 ra2f8a9  
    4343
    4444#include <algorithm>
    45 #include <boost/math/quaternion.hpp>
     45#include <boost/assign.hpp>
    4646#include <cmath>
    4747#include <functional>
     
    5858#include "LinearAlgebra/Vector.hpp"
    5959
     60using namespace boost::assign;
     61
    6062// static entities
    6163const double SphericalPointDistribution::warn_amplitude = 1e-2;
     
    8385 */
    8486inline
    85 Vector calculateCenter(
     87Vector calculateGeographicMidpoint(
    8688    const SphericalPointDistribution::VectorArray_t &_positions,
    8789    const SphericalPointDistribution::IndexList_t &_indices)
     
    9698
    9799  return Center;
     100}
     101
     102inline
     103double calculateMinimumDistance(
     104    const Vector &_center,
     105    const SphericalPointDistribution::VectorArray_t &_points,
     106    const SphericalPointDistribution::IndexList_t & _indices)
     107{
     108  double MinimumDistance = 0.;
     109  for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
     110      iter != _indices.end(); ++iter) {
     111    const double angle = _center.Angle(_points[*iter]);
     112    MinimumDistance += angle*angle;
     113  }
     114  return sqrt(MinimumDistance);
     115}
     116
     117/** Calculates the center of minimum distance for a given set of points \a _points.
     118 *
     119 * According to http://www.geomidpoint.com/calculation.html this goes a follows:
     120 * -# Let CurrentPoint be the geographic midpoint found in Method A. this is used as the starting point for the search.
     121 * -# Let MinimumDistance be the sum total of all distances from the current point to all locations in 'Your Places'.
     122 * -# Find the total distance between each location in 'Your Places' and all other locations in 'Your Places'. If any one of these locations has a new smallest distance then that location becomes the new CurrentPoint and MinimumDistance.
     123 * -# Let TestDistance be PI/2 radians (6225 miles or 10018 km).
     124 * -# Find the total distance between each of 8 test points and all locations in 'Your Places'. The test points are arranged in a circular pattern around the CurrentPoint at a distance of TestDistance to the north, northeast, east, southeast, south, southwest, west and northwest.
     125 * -# If any of these 8 points has a new smallest distance then that point becomes the new CurrentPoint and MinimumDistance and go back to step 5 using that point.
     126 * -# If none of the 8 test points has a new smallest distance then divide TestDistance by 2 and go back to step 5 using the same point.
     127 * -# Repeat steps 5 to 7 until no new smallest distance can be found or until TestDistance is less than 0.00000002 radians.
     128 *
     129 * \param _points set of points
     130 * \return Center of minimum distance for all these points, is always of length 1
     131 */
     132Vector SphericalPointDistribution::calculateCenterOfMinimumDistance(
     133    const SphericalPointDistribution::VectorArray_t &_positions,
     134    const SphericalPointDistribution::IndexList_t &_indices)
     135{
     136  ASSERT( _positions.size() >= _indices.size(),
     137      "calculateCenterOfMinimumDistance() - less positions than indices given.");
     138  Vector center(1.,0.,0.);
     139
     140  /// first treat some special cases
     141  // no positions given: return x axis vector (NOT zero!)
     142  {
     143    if (_indices.empty())
     144      return center;
     145    // one position given: return it directly
     146    if (_positions.size() == (size_t)1)
     147      return _positions[0];
     148    // two positions on a line given: return closest point to (1.,0.,0.)
     149    if (fabs(_positions[0].ScalarProduct(_positions[1]) + 1.)
     150        < std::numeric_limits<double>::epsilon()*1e4) {
     151      Vector candidate;
     152      if (_positions[0].ScalarProduct(center) > _positions[1].ScalarProduct(center))
     153        candidate = _positions[0];
     154      else
     155        candidate = _positions[1];
     156      // non-uniqueness: all positions on great circle, normal to given line are valid
     157      // so, we just pick one because returning a unique point is topmost priority
     158      Vector normal;
     159      normal.GetOneNormalVector(candidate);
     160      Vector othernormal = candidate;
     161      othernormal.VectorProduct(normal);
     162      // now both normal and othernormal describe the plane containing the great circle
     163      Plane greatcircle(normal, zeroVec, othernormal);
     164      // check which axis is contained and pick the one not
     165      if (greatcircle.isContained(center)) {
     166        center = Vector(0.,1.,0.);
     167        if (greatcircle.isContained(center))
     168          center = Vector(0.,0.,1.);
     169        // now we are done cause a plane cannot contain all three axis vectors
     170      }
     171      center = greatcircle.getClosestPoint(candidate);
     172      // assure length of 1
     173      center.Normalize();
     174    }
     175  }
     176
     177  // start with geographic midpoint
     178  center = calculateGeographicMidpoint(_positions, _indices);
     179  if (!center.IsZero()) {
     180    center.Normalize();
     181    LOG(4, "DEBUG: Starting with geographical midpoint of " << _positions << " under indices "
     182        << _indices << " is " << center);
     183  } else {
     184    // any point is good actually
     185    center = _positions[0];
     186    LOG(4, "DEBUG: Starting with first position " << center);
     187  }
     188
     189  // calculate initial MinimumDistance
     190  double MinimumDistance = calculateMinimumDistance(center, _positions, _indices);
     191  LOG(5, "DEBUG: MinimumDistance to this center is " << MinimumDistance);
     192
     193  // check all present points whether they may serve as a better center
     194  for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
     195      iter != _indices.end(); ++iter) {
     196    const Vector &centerCandidate = _positions[*iter];
     197    const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
     198    if (candidateDistance < MinimumDistance) {
     199      MinimumDistance = candidateDistance;
     200      center = centerCandidate;
     201      LOG(5, "DEBUG: new MinimumDistance to current test point " << MinimumDistance
     202          << " is " << center);
     203    }
     204  }
     205  LOG(5, "DEBUG: new MinimumDistance to center " << center << " is " << MinimumDistance);
     206
     207  // now iterate over TestDistance
     208  double TestDistance = Vector(1.,0.,0.).Angle(Vector(0.,1.,0.));
     209//  LOG(6, "DEBUG: initial TestDistance is " << TestDistance);
     210
     211  const double threshold = sqrt(std::numeric_limits<double>::epsilon());
     212  // check each of eight test points at N, NE, E, SE, S, SW, W, NW
     213  typedef std::vector<double> angles_t;
     214  angles_t testangles;
     215  testangles += 0./180.*M_PI, 45./180.*M_PI, 90./180.*M_PI, 135./180.*M_PI,
     216      180./180.*M_PI, 225./180.*M_PI, 270./180.*M_PI, 315./180.*M_PI;
     217  while (TestDistance > threshold) {
     218    Vector OneNormal;
     219    OneNormal.GetOneNormalVector(center);
     220    Line RotationAxis(zeroVec, OneNormal);
     221    Vector North = RotationAxis.rotateVector(center,TestDistance);
     222    Line CompassRose(zeroVec, center);
     223    bool updatedflag = false;
     224    for (angles_t::const_iterator angleiter = testangles.begin();
     225        angleiter != testangles.end(); ++angleiter) {
     226      Vector centerCandidate = CompassRose.rotateVector(North, *angleiter);
     227//      centerCandidate.Normalize();
     228      const double candidateDistance = calculateMinimumDistance(centerCandidate, _positions, _indices);
     229      if (candidateDistance < MinimumDistance) {
     230        MinimumDistance = candidateDistance;
     231        center = centerCandidate;
     232        updatedflag = true;
     233        LOG(5, "DEBUG: new MinimumDistance to test point at " << *angleiter/M_PI*180.
     234            << "° is " << MinimumDistance);
     235      }
     236    }
     237
     238    // if no new point, decrease TestDistance
     239    if (!updatedflag) {
     240      TestDistance *= 0.5;
     241//      LOG(6, "DEBUG: TestDistance is now " << TestDistance);
     242    }
     243  }
     244  LOG(4, "DEBUG: Final MinimumDistance to center " << center << " is " << MinimumDistance);
     245
     246  return center;
     247}
     248
     249Vector calculateCenterOfMinimumDistance(
     250    const SphericalPointDistribution::PolygonWithIndices &_points)
     251{
     252  return SphericalPointDistribution::calculateCenterOfMinimumDistance(_points.polygon, _points.indices);
     253}
     254
     255/** Calculate the center of a given set of points in \a _positions but only
     256 * for those indicated by \a _indices.
     257 *
     258 */
     259inline
     260Vector calculateCenter(
     261    const SphericalPointDistribution::VectorArray_t &_positions,
     262    const SphericalPointDistribution::IndexList_t &_indices)
     263{
     264//  Vector Center;
     265//  Center.Zero();
     266//  for (SphericalPointDistribution::IndexList_t::const_iterator iter = _indices.begin();
     267//      iter != _indices.end(); ++iter)
     268//    Center += _positions[*iter];
     269//  if (!_indices.empty())
     270//    Center *= 1./(double)_indices.size();
     271//
     272//  return Center;
     273  return SphericalPointDistribution::calculateCenterOfMinimumDistance(_positions, _indices);
    98274}
    99275
     
    744920            oldCenter, rotatednewCenter,
    745921            oldSet, rotatednewSet);
    746         // NOTE: Center must not necessarily lie on the sphere with norm 1, hence, we
    747         // have to normalize it just as before, as oldCenter and newCenter lengths may differ.
    748         if ((!oldCenter.IsZero()) && (!rotatednewCenter.IsZero())) {
    749           oldCenter.Normalize();
    750           rotatednewCenter.Normalize();
    751           LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
    752               << ", oldCenter is " << oldCenter);
    753           const double difference = (rotatednewCenter - oldCenter).NormSquared();
    754           ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
    755               "matchSphericalPointDistributions() - rotation does not work as expected by "
    756               +toString(difference)+".");
    757         }
     922        oldCenter.Normalize();
     923        rotatednewCenter.Normalize();
     924        // check whether centers are anti-parallel (factor -1)
     925        // then we have the "non-unique poles" situation: points lie on great circle
     926        // and both poles are valid solution
     927        if (fabs(oldCenter.ScalarProduct(rotatednewCenter) + 1.)
     928            < std::numeric_limits<double>::epsilon()*1e4)
     929          rotatednewCenter *= -1.;
     930        LOG(4, "CHECK: rotatednewCenter is " << rotatednewCenter
     931            << ", oldCenter is " << oldCenter);
     932        const double difference = (rotatednewCenter - oldCenter).NormSquared();
     933        ASSERT( difference < std::numeric_limits<double>::epsilon()*1e4,
     934            "matchSphericalPointDistributions() - rotation does not work as expected by "
     935            +toString(difference)+".");
    758936      }
    759937#endif
Note: See TracChangeset for help on using the changeset viewer.