Portal Example Source Files

main.cpp

  1/*
  2 * Copyright (c) 2014-2023 National Technology and Engineering
  3 * Solutions of Sandia, LLC. Under the terms of Contract DE-NA0003525
  4 * with National Technology and Engineering Solutions of Sandia, LLC,
  5 * the U.S. Government retains certain rights in this software.
  6 *
  7 * Redistribution and use in source and binary forms, with or without
  8 * modification, are permitted provided that the following conditions
  9 * are met:
 10 *
 11 * 1. Redistributions of source code must retain the above copyright
 12 * notice, this list of conditions and the following disclaimer.
 13 *
 14 * 2. Redistributions in binary form must reproduce the above copyright
 15 * notice, this list of conditions and the following disclaimer in the
 16 * documentation and/or other materials provided with the distribution.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 22 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 29 */
 30
 31#include "Portal.h"
 32#include "PortalPair.h"
 33
 34#include <tracktable/CommandLineFactories/AssemblerFromCommandLine.h>
 35#include <tracktable/CommandLineFactories/PointReaderFromCommandLine.h>
 36#include <tracktable/Domain/Terrestrial.h>
 37
 38#include <boost/timer/timer.hpp>
 39
 40using TrajectoryT = tracktable::domain::terrestrial::trajectory_type;
 41using PointT = typename TrajectoryT::point_type;
 42using PointReaderT = tracktable::PointReader<PointT>;
 43using PointReaderIteratorT = typename PointReaderT::iterator;
 44using AssemblerT = tracktable::AssembleTrajectories<TrajectoryT, PointReaderIteratorT>;
 45
 46static constexpr auto helpmsg = R"(
 47--------------------------------------------------------------------------------
 48The portal example takes trajectory data and attempts to find origin/destination
 49pairs. It breaks the USA into a grid, identifies what cells are populated by trajectories
 50and then refines the grid based on desired parameters. Each level of 'depth' is an
 51additional layer of refinement of the original grid. Each level is divided into
 52'bin-count' sections in both longitude and latitude. So each the number of cells:
 53
 54cells = 12*5*bins^(2+depth)
 55
 56empty cells are dropped but a cell is only empty if no trajectories pass through it
 57
 58The portal example demonstrates:
 59    - Using command line factories to read points and assemble trajectories
 60    - Using boost program options to take parameters from command lines(in addition to the factories)
 61    - Use of boost::geometry::intersects to test where trajectories overlap regions
 62
 63Typical use:
 64    ./portal-- input=/data/flights.tsv --depth=5 --min-value=12 --min-seperation=10 --bin-count=2
 65
 66Defaults assume a tab separated file formatted as :
 67
 68OBJECTID TIMESTAMP LON LAT
 69--------------------------------------------------------------------------------)";
 70
 71int main(int _argc, char* _argv[]) {
 72    constexpr auto timerFormat = "\u001b[30;1m %w seconds\u001b[0m\n";
 73    // Set log level to reduce unecessary output
 74    tracktable::set_log_level(tracktable::log::info);
 75    // Create a basic command line option with boost
 76    boost::program_options::options_description commandLineOptions;
 77    commandLineOptions.add_options()("help", "Print help");
 78    // Create command line factories
 79    tracktable::PointReaderFromCommandLine<PointT> readerFactory;
 80    tracktable::AssemblerFromCommandLine<TrajectoryT> assemblerFactory;
 81    // Add options from the factories
 82    readerFactory.addOptions(commandLineOptions);
 83    assemblerFactory.addOptions(commandLineOptions);
 84    /** Boost program options using a variable map to tie everything together.
 85     * one parse will have a single variable map. We need to let the factories know
 86     * about this variable map so they can pull information out of it */
 87    auto vm = std::make_shared<boost::program_options::variables_map>();
 88    readerFactory.setVariables(vm);
 89    assemblerFactory.setVariables(vm);
 90
 91    // Portal specific configuration
 92    auto seperationDistance = 10.0;
 93    auto depth = 5u;
 94    auto binSize = 2u;
 95    auto minValue = 16u;
 96    boost::program_options::options_description portalOptions("Portals");
 97    // clang-format off
 98    portalOptions.add_options()
 99        ("portal-sep", bpo::value(&seperationDistance)->default_value(10), "Set minimum portal separation distance (in lat-lon)")
100        ("depth", bpo::value(&depth)->default_value(5), "Set depth for portal decomposition")
101        ("bin-count", bpo::value(&binSize)->default_value(2), "Portal chopping factor (default is 2)")
102        ("min-value", bpo::value(&minValue)->default_value(16), "Minumum number of portal pairs (default is 16)")
103    ;
104    // clang-format on
105
106    // Parse the command lines, don't forget the 'notify' after
107    try {
108        // We use this try/catch to automatically display help when an unknown option is used
109        boost::program_options::store(
110            boost::program_options::command_line_parser(_argc, _argv).options(commandLineOptions).run(), *vm);
111        boost::program_options::notify(*vm);
112    } catch (boost::program_options::error e) {
113        std::cerr << e.what();
114        std::cerr << helpmsg << "\n\n";
115        std::cerr << commandLineOptions << std::endl;
116        return 1;
117    }
118    /** Parsing will give an error of an incorrect option is used, but it won't
119     * display the help unless we tell it too */
120    if (vm->count("help") != 0) {
121        std::cerr << helpmsg << "\n\n";
122        std::cerr << commandLineOptions << std::endl;
123        return 1;
124    }
125
126    // Create Point Reader and assembler
127    auto pointReader = readerFactory.createPointReader();
128    auto assembler = assemblerFactory.createAssembler(pointReader);
129
130    std::vector<std::shared_ptr<TrajectoryT>> trajectories = {};
131    // This block exists for easy timing of trajectory assembling using the boost auto timer
132    // Note that all feedback to the user is done on std::cerr, this allows us to only
133    // put desired results into std::cout, this make processing output easier.
134    {
135        std::cerr << "Assemble Trajectories" << std::endl;
136        boost::timer::auto_cpu_timer assembleTimer(std::cerr, timerFormat);
137        auto count = 0u;
138        std::cerr << std::right;
139        for (auto tIter = assembler->begin(); tIter != assembler->end(); ++tIter) {
140            if (tracktable::length(*tIter) < 100) {
141                continue;
142            }
143            std::cerr << "\b\b\b\b\b\b\b\b\b\b" << std::setw(10)  // Using backspaces for in place counter
144                      << count++;
145            trajectories.push_back(std::make_shared<TrajectoryT>(*tIter));
146        }
147        std::cerr << std::left << "\nStarting with " << trajectories.size() << " trajectories" << std::endl;
148    }
149
150    // Create box for the USA
151    PointT lowerLeft(-125.0, 25.0);  // lower left of USA
152    PointT upperRight(-65.0, 50.0);  // upper right of USA
153    auto USA = std::make_shared<Portal>(boost::geometry::model::box<PointT>(lowerLeft, upperRight));
154    USA->level = 0;
155    PairHeap pairs;
156    pairs.minimumSeperation = seperationDistance;
157    pairs.minimumValue = minValue;
158    pairs.depth = depth;
159    pairs.xDivisions = binSize;
160    pairs.yDivisions = binSize;
161    {
162        std::cerr << "Initializing Pair Heap" << std::endl;
163        boost::timer::auto_cpu_timer initializeTimer(std::cerr, timerFormat);
164        pairs.initialize(trajectories, USA);
165    }
166    {
167        std::cerr << "Finding Portals" << std::endl;
168        boost::timer::auto_cpu_timer findTimer(std::cerr, timerFormat);
169        pairs.find_portals();
170    }
171    return 0;
172}

Portal.h

 1/*
 2 * Copyright (c) 2014-2023 National Technology and Engineering
 3 * Solutions of Sandia, LLC. Under the terms of Contract DE-NA0003525
 4 * with National Technology and Engineering Solutions of Sandia, LLC,
 5 * the U.S. Government retains certain rights in this software.
 6 *
 7 * Redistribution and use in source and binary forms, with or without
 8 * modification, are permitted provided that the following conditions
 9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30#ifndef Portal_h
31#define Portal_h
32
33#include <tracktable/Domain/Terrestrial.h>
34
35#include <boost/geometry/geometries/box.hpp>
36#include <boost/geometry/geometries/register/box.hpp>
37
38#include <list>
39#include <memory>
40#include <set>
41
42using TrajectoryT = tracktable::domain::terrestrial::trajectory_type;
43using PointT = typename TrajectoryT::point_type;
44using BoxT = boost::geometry::model::box<PointT>;
45using TrajectoryPtrT = std::shared_ptr<TrajectoryT>;
46
47/**
48 * @brief A portal is simply a box defined by two points
49 * A portal has a set of trajectories that intersect it and the potentail to have a set of 'children' which
50 * are fully contained subdivisions
51 */
52class Portal : public BoxT {
53   public:
54    Portal() = delete;
55    Portal(const BoxT &b) : BoxT(b){};
56
57    /// Sort Portals basic on how many trajectories they contain
58    bool operator<(const Portal &p) const { return (trajectories.size() < p.trajectories.size()); }
59    /** @brief Divides portal into x*y children portals and assign trajectories accordingly
60     * @param _xDivision number of divisions in the x(longitude) direction
61     * @param _yDivisions number of divisions in the y(latitude) direction */
62    void divide(size_t _xDivision, size_t _yDivisions);
63    /** @brief Adds a list of trajectories to the portal
64     * @param _addList */
65    void add_trajectories(std::vector<TrajectoryPtrT> &_addList);
66    /** @brief Will assign the trajectory to this portal (does not check intersection) then adds the
67     * trajectory to all children (based on intersection)
68     * @note if the trajectory does not intersect the top level, it will still be added while not existing
69     * in any of the children (impact unknown)
70     * @param _t The trajectory to add */
71    void add_trajectory(const TrajectoryPtrT &_t);
72    /** @brief Removes a list of trajectories from the portal
73     * @param _removeList */
74    void remove_trajectories(const std::vector<TrajectoryPtrT> &_removeList);
75    /** @brief Removes a specific trajectory from portal and all children
76     * @param _t trajectory to remove */
77    void remove_trajectory(const TrajectoryPtrT &_t);
78
79   public:
80    size_t level;                                  ///< The level this portal is at
81    std::set<TrajectoryPtrT> trajectories;         ///< Trajectories that intersect this portal
82    std::list<std::shared_ptr<Portal> > children;  ///< Children portals (if any)
83};
84
85/// How we pass portal references around
86using PortalPtrT = std::shared_ptr<Portal>;
87/// Overide default sorting for PortalPtrT
88bool operator<(const PortalPtrT &p1, const PortalPtrT &p2);
89
90BOOST_GEOMETRY_REGISTER_BOX(Portal, PointT, min_corner(), max_corner());
91
92#endif  // portal_h

Portal.cpp

 1#include "Portal.h"
 2
 3#include <boost/geometry/arithmetic/arithmetic.hpp>
 4
 5void Portal::divide(size_t _xDivisions, size_t _yDivisions) {
 6    /** notes about boost geomtry arithmetic;
 7     * the first point is piecwise modified by the second point
 8     * to preserve the original point it is necessary to make copies first
 9     */
10    auto delta = this->max_corner();
11    boost::geometry::subtract_point(delta, this->min_corner());
12    boost::geometry::divide_point(delta,PointT(_xDivisions,_yDivisions));
13
14    for (auto i = 0u; i < _xDivisions; ++i) {
15        for (auto j = 0u; j < _yDivisions; ++j) {
16            auto ll = this->min_corner();
17            auto d = delta;
18            boost::geometry::multiply_point(d,PointT(i,j));
19            boost::geometry::add_point(ll,d);
20
21            auto ur = ll;
22            boost::geometry::add_point(ur,delta);
23
24            auto p = std::make_shared<Portal>(BoxT(ll, ur));
25            p->level = this->level + 1;
26            // Now, go through all of the trajectories associated with the parent portal
27            // and assign them to the child by testing intersection.
28            for (auto &t : this->trajectories) {
29                if (boost::geometry::intersects(*t, *p)) {
30                    p->trajectories.insert(t);
31                }
32            }
33            // only keep the child if it's non empty
34            if (!p->trajectories.empty()) {
35                this->children.push_back(p);
36            }
37        }
38    }
39}
40
41void Portal::add_trajectories(std::vector<TrajectoryPtrT> &_addList) {
42    for (auto &t : _addList) {
43        add_trajectory(t);
44    }
45}
46void Portal::add_trajectory(const TrajectoryPtrT &_t) {
47    trajectories.insert(_t);
48    for (auto &c : children) {
49        if (boost::geometry::intersects(*_t, *c)) {
50            c->add_trajectory(_t);
51        }
52    }
53}
54
55void Portal::remove_trajectories(const std::vector<TrajectoryPtrT> &_removeList) {
56    for (auto &t : _removeList) {
57        remove_trajectory( t);
58    }
59}
60
61void Portal::remove_trajectory(const TrajectoryPtrT &_t) {
62    if (0 == trajectories.erase(_t)) {
63        return; //if it doesn't intersect the parent, it doesn't intersect any of the children
64    }
65    for (auto &c : children) {
66        c->remove_trajectory(_t);
67    }
68}
69
70bool operator<(const PortalPtrT &p1, const PortalPtrT &p2) {
71    return p1->trajectories.size() < p2->trajectories.size();
72}

PortalPair.h

  1/*
  2 * Copyright (c) 2014-2023 National Technology and Engineering
  3 * Solutions of Sandia, LLC. Under the terms of Contract DE-NA0003525
  4 * with National Technology and Engineering Solutions of Sandia, LLC,
  5 * the U.S. Government retains certain rights in this software.
  6 *
  7 * Redistribution and use in source and binary forms, with or without
  8 * modification, are permitted provided that the following conditions
  9 * are met:
 10 *
 11 * 1. Redistributions of source code must retain the above copyright
 12 * notice, this list of conditions and the following disclaimer.
 13 *
 14 * 2. Redistributions in binary form must reproduce the above copyright
 15 * notice, this list of conditions and the following disclaimer in the
 16 * documentation and/or other materials provided with the distribution.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 22 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 29 */
 30#ifndef PortalPair_h
 31#define PortalPair_h
 32
 33#include "Portal.h"
 34
 35#include <algorithm>
 36#include <queue>
 37
 38using TrajectoryIterT = TrajectoryT::iterator;
 39
 40/** Used to trak and rank pairs of portal
 41 * The value of a pair represents how many trajectories travel between the two
 42 * portals */
 43class PortalPair {
 44   public:
 45    PortalPair(const PortalPtrT &_p1, const PortalPtrT &_p2) {
 46        if (_p1->trajectories.size() > _p2->trajectories.size()) {
 47            p1 = _p1;
 48            p2 = _p2;
 49        } else {
 50            p1 = _p2;
 51            p2 = _p1;
 52        }
 53        update_value();
 54        update_seperation();
 55    }
 56
 57    /// Sort based on custom value
 58    bool operator<(const PortalPair &_other) const {
 59        return (value * seperation) < (_other.value * _other.seperation);
 60    }
 61
 62    /** compute the value of this pair of portals
 63     * The value is equal to the number of trajectories that seem to start in one portal and end
 64     * in the other */
 65    void update_value();
 66    /** @brief updates the seperation between the two portals
 67     * No real reason this should ever change with normal use */
 68    void update_seperation();
 69    /** @brief Determines the 'innermost' points of a trajectory that connect the two portals
 70     *
 71     * @param trajectory Trajectory in question
 72     * @param first_pt determined first point
 73     * @param last_pt determined second point
 74     */
 75    void get_segment(const TrajectoryPtrT &trajectory, TrajectoryIterT &first_pt, TrajectoryIterT &last_pt) const;
 76    /** @brief determines if the innermost segment of a trajectory between the two portals constitutes the
 77     * bulk of the trajectory, this is the criteria for a trajectory being 'from/to' the pair of portals
 78     *
 79     * @param _trajectory The trajectory being tested
 80     * @param _ecc The deviation allowed
 81     * @return true The trajectory is 'from/to' the portals
 82     * @return false otherwise
 83     */
 84    bool is_within_portal_ellipse(const TrajectoryPtrT &_trajectory, const double &_ecc) const;
 85
 86   public:
 87    PortalPtrT p1;                             ///< First Portal
 88    PortalPtrT p2;                             ///< Second Portal
 89    size_t value;                              ///< value of portal pair
 90    double seperation;                         ///< distance between portals
 91    std::vector<TrajectoryPtrT> contributors;  ///< trajectories that contribute to the value of the pair
 92};
 93
 94/** Management object for a set of PortalPairs
 95 * Based on priority queue which keeps elements sorted
 96 * allows for management and refinement of pairs and their respective portals
 97 * @note Highly coupled to PortalPair and Portal
 98 * @sa PortalPair Portal */
 99class PairHeap : public std::priority_queue<PortalPair> {
100   public:
101    /** @brief If possible, subdivides one of the portal in the top ranked pair
102     * Returning false implies a 'best' result has bubbled to the top
103     *
104     * @return true If refinement was done
105     * @return false If refinement was unecessary or impossible*/
106    bool refine_pairs();
107    /** @brief takes a 'refinable' pair and subdivides one them
108     * The pair is then destroyed and a new set of pairs between the undivided portal and the newly created
109     * children of the divided portal. */
110    void refine_top_pair();
111    /** @brief MegaPop() removes the top pair from the list and removes any trajectories connecting that pair
112     * finally the rest of the heap is rescored*/
113    void remove_top_pair();
114    /** @brief Does initial intersection of _trajectories and _top
115     * Does initial subdivision of _top and assigns trajectories accordingly
116     * Creates an initial pair list based on populated divisions
117     *
118     * @param _trajectories trajectories of interest
119     * @param _startingPortal A presumably empty top level portal
120     */
121    void initialize(std::vector<TrajectoryPtrT> &_trajectories, PortalPtrT &_startingPortal);
122    /** @brief goes through the pair heap and corresponding Portal;
123     * recursively peeling off the top pair and refining it down to the depth specified
124     * in the heap.
125     *
126     * Every time the top pair has a level equal to the desired depth, it is recorded
127     * in a kml file and the corresponding trajectories are removed from all portals
128     * in the heirachy
129     */
130    void find_portals();
131
132   public:
133    double minimumSeperation = 10.0;  ///< Minimum Seperation for two portals to be considered a viable pair
134    size_t minimumValue = 16u;        ///< Minimum value for a pair to be worth keeping
135    size_t xDivisions = 2u;           ///< number of subdivisions to use in the x(longitude) direction
136    size_t yDivisions = 2u;           ///< number of subdivisions to use in the y(latitude) direction
137    size_t depth = 5u;                ///< maximum depth to subdivide to
138    PortalPtrT topPortal;             ///< Pointer to top level portal
139};
140
141#endif

PortalPair.cpp

  1/*
  2 * Copyright (c) 2014-2023 National Technology and Engineering
  3 * Solutions of Sandia, LLC. Under the terms of Contract DE-NA0003525
  4 * with National Technology and Engineering Solutions of Sandia, LLC,
  5 * the U.S. Government retains certain rights in this software.
  6 *
  7 * Redistribution and use in source and binary forms, with or without
  8 * modification, are permitted provided that the following conditions
  9 * are met:
 10 *
 11 * 1. Redistributions of source code must retain the above copyright
 12 * notice, this list of conditions and the following disclaimer.
 13 *
 14 * 2. Redistributions in binary form must reproduce the above copyright
 15 * notice, this list of conditions and the following disclaimer in the
 16 * documentation and/or other materials provided with the distribution.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 22 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 29 */
 30#include "PortalPair.h"
 31
 32#include <tracktable/RW/KmlOut.h>
 33
 34#include <boost/geometry/algorithms/intersects.hpp>
 35
 36void PortalPair::update_value() {
 37    value = 0;
 38    contributors.clear();
 39    std::set_intersection(p1->trajectories.begin(), p1->trajectories.end(), p2->trajectories.begin(),
 40                          p2->trajectories.end(), std::back_inserter(contributors));
 41
 42    contributors.erase(
 43        std::remove_if(contributors.begin(), contributors.end(),
 44                       [&](const TrajectoryPtrT _t) {
 45                           if (is_within_portal_ellipse(_t, 1.01)) {
 46                               ++value;  // cheating by incrementing value from within the lambda
 47                               return true;
 48                           }
 49                           return false;
 50                       }),
 51        contributors.end());
 52}
 53
 54void PortalPair::update_seperation() { seperation = boost::geometry::distance(*p1, *p2); }
 55
 56bool PortalPair::is_within_portal_ellipse(const TrajectoryPtrT &_trajectory, const double &_ecc) const {
 57    TrajectoryT::iterator first_pt, last_pt;
 58    get_segment(_trajectory, first_pt, last_pt);
 59    TrajectoryT segment(first_pt, last_pt);
 60
 61    return (!segment.empty() &&
 62            (tracktable::length(segment) < _ecc * tracktable::distance(segment.front(), segment.back())));
 63}
 64
 65void PortalPair::get_segment(const TrajectoryPtrT &trajectory, TrajectoryIterT &first_pt,
 66                             TrajectoryIterT &last_pt) const {
 67    // We know that the trajectory intersects both portals because it is in both portals list and they got
 68    // added to the list because they intersect
 69    unsigned int prev = 0;  // Has intersected neither
 70    TrajectoryIterT cur_box1, cur_box2;
 71
 72    if (trajectory->size() < 2)
 73        std::cout << "Warning, trajectory->size() = " << trajectory->size() << std::endl;
 74    // If the path zigzags in and out of portals, it will remember the two innermost intersections
 75    for (auto pt = trajectory->begin(); (pt + 1) != trajectory->end(); ++pt) {
 76        auto segment = boost::geometry::model::segment<PointT>(*pt, *(pt + 1));
 77        if (boost::geometry::intersects(segment, *p1)) {
 78            cur_box1 = pt;           // if we were leaving, this was the last point inside the box
 79            if (prev == 2) {         // means we are entering instead of leaving
 80                ++cur_box1;          // enter means the other point was the last one inside the box
 81                last_pt = cur_box1;  // entering the box means the segment is ending here
 82                first_pt = cur_box2;
 83            }
 84            prev = 1;
 85        }
 86        if (boost::geometry::intersects(segment, *p2)) {
 87            cur_box2 = pt;
 88            if (prev == 1) {  // last intersection was with portal 1
 89                ++cur_box2;
 90                first_pt = cur_box1;
 91                last_pt = cur_box2;
 92            }
 93            prev = 2;
 94        }
 95    }
 96    if (0 == prev) {
 97        std::cout << "Something is very wrong!" << std::endl;
 98    }
 99}
100
101bool PairHeap::refine_pairs() {
102    if (empty()) return false;
103    if (((top().p1->level >= depth) && (top().p2->level >= depth))) {
104        if (top().seperation > this->minimumSeperation) {
105            return false;
106        }
107        remove_top_pair();  // Consider removing a pair that is too close as a 'refinement'
108    } else {
109        refine_top_pair();
110    }
111    return true;
112}
113
114void PairHeap::refine_top_pair() {
115    // Decompose the first portal by default (it's the largest), or
116    // do the second if the first is already at desired depth
117    auto shrink = top().p1;
118    auto keep = top().p2;
119    if (shrink->level >= depth) {
120        assert(top().p2->level < depth);  // check done before call should not allow
121        std::swap(shrink, keep);
122    }
123    pop();
124
125    // If we haven't already created the children in the decomposition, do so
126    /* TODO: Is it possible to have it already divided? if it is, should the pairing below be done? should we
127     * have popped? */
128    if (shrink->children.empty()) {
129        shrink->divide(xDivisions, yDivisions);
130    }
131
132    // Now reassign the pairs, we do not enforce minimum seperation at this time
133    for (auto first = shrink->children.begin(); first != shrink->children.end(); ++first) {
134        PortalPair p(*first, keep);
135        if (p.value >= minimumValue) {
136            push(p);
137        }
138        for (auto second = std::next(first); second != shrink->children.end(); ++second) {
139            PortalPair p2(*first, *second);
140            if (p2.value >= minimumValue) {
141                push(p2);
142            }
143        }
144    }
145}
146
147void PairHeap::remove_top_pair() {
148    // remove the contributors to the top pairs value
149    topPortal->remove_trajectories(top().contributors);
150    // remove the top
151    pop();
152
153    // Rescore and filter the heap NOTE: 'c' comes from the base class
154    c.erase(std::remove_if(c.begin(), c.end(),
155                           [&](PortalPair &_p) {
156                               _p.update_value();
157                               return _p.value < minimumValue;
158                           }),
159            c.end());
160    // resort the heap and store it like 'priority_queue' prefers.
161    std::make_heap(c.begin(), c.end());
162}
163
164void PairHeap::initialize(std::vector<TrajectoryPtrT> &_trajectories, PortalPtrT &_startingPortal) {
165    topPortal = _startingPortal;
166    // Initialize the simulation by making one big portal out of the _startingPortal
167    // and then decomposing it.
168
169    for (auto &t : _trajectories) {
170        if (boost::geometry::intersects(*t, *topPortal)) {
171            topPortal->add_trajectory(t);
172        }
173    }
174
175    // Note: we are assuming _startingPortal is the USA and has an aspect ratio
176    // of 12 by 5.  Using a different aliquot than that in the command below
177    // will result in non-square portals.  Not that there is anything wrong
178    // with that.
179
180    topPortal->divide(12, 5);
181
182    // Now initialize pair list with all of the children...
183    // We do not enforce a separation at this time as it potentials blocks future children with a valid
184    // separation
185    for (auto first = topPortal->children.begin(); first != topPortal->children.end(); ++first) {
186        for (auto second = std::next(first); second != topPortal->children.end(); ++second) {
187            PortalPair p(*first, *second);
188            if (p.value > minimumValue /* && p.seperation > minimumSeperation*/) {
189                push(p);
190            }
191        }
192    }
193}
194
195void writeKmlPortalPair(const PortalPair &pp, const std::string &file_name);
196
197void PairHeap::find_portals() {
198    static int i = 0;
199    while (!empty()) {
200        while (refine_pairs())
201            ;
202        if (!empty()) {
203            std::stringstream filename;
204            filename << "flights" << i << ".kml";
205            writeKmlPortalPair(top(), filename.str());
206            remove_top_pair();
207            ++i;
208        }
209    }
210}
211
212using tracktable::kml;
213void writeKmlPortalPair(const PortalPair &pp, const std::string &file_name) {
214    std::ofstream out(file_name.c_str());
215    out.precision(15);
216    out << kml::header;
217    kml::width(3);
218    kml::write(out, pp.contributors);
219
220    out << kml::style("Portal", "FF0000FF", 1.0);
221    out << kml::startpm();
222    out << kml::startmulti();
223    out << kml::box(pp.p1->min_corner(), pp.p1->max_corner());
224    out << kml::box(pp.p2->min_corner(), pp.p2->max_corner());
225    out << kml::stopmulti();
226    out << kml::stoppm();
227    out << kml::footer;
228}