renamed and stuff
This commit is contained in:
7
particle-track-and-trace/.gitignore
vendored
Normal file
7
particle-track-and-trace/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
src/.DS_Store
|
||||
src/.cache
|
||||
src/build
|
||||
.idea
|
||||
src/cmake-build-debug
|
||||
compile_commands.json
|
||||
34
particle-track-and-trace/README.md
Normal file
34
particle-track-and-trace/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## Vtk
|
||||
This folder contains the Vtk program which actually displays the simulated data. The code is driven by the `Program` class, which contains the upper level of the vtk pipeline: the class has attributes for a vtkRenderWindow and vtkRenderWindowInteractor. vtkRenderers are managed through an abstract `Layer` class, which the program keeps track of trough a vector attribute.
|
||||
|
||||
Each layer implementation contains and manages one vtkRenderer, this includes managing which layer of the vtkrenderwindow ths layer renders to. Currently implemented are three such layers:
|
||||
* the `BackgroundImage` class reads in image data and displays this to the screen on the 0th layer - the background.
|
||||
* the `EGlyphLayer` class renders a visualization of the Eulerian flow-velocities as a grid of arrow-glyphs (in which the direction and length of the glyph represents the direction and strength of the velocity at that point). Right now it spoofs the data for these glyphs, but this class will interface with the code for reading h5 data to accurately display the velocities at a given timestamp.
|
||||
* the `LGlyphLayer` class renders a given set of particles as circular glyphs. These particles are advected according to an advection function, which in this implementation is spoofed. Like the EglyphLayer class, this layer will interact with the code for advecting particles according to the actual dataset, to accurately simulate its particles.
|
||||
|
||||
The `LGlyphLayer` deserves some more explanation, as it depends on the `SpawnpointCallback` class to place particles in its dataset. The `SpawnpointCallback` makes use of the vtkCallbackCommand class and the vtk observer pattern to create new particles on mouseclick. It does so through a shared reference to the LGlyphLayer's `data` and `points` attributes, which the SpawnpointCallback then edits directlr.
|
||||
|
||||
The program also adds a second observer to the vtk pattern through the `TimerCallbackCommand`. This class subscribes to a vtkTimerEvent to manage the simulation of the program. To this end the TimerCallbackCommand has attributes for a timestep (dt) and current time (time). On every callback, the current time is updated according to the dt attribute, and this change is propagated to the layers containing the data by use of the program and layer's `updateData()` functions.
|
||||
|
||||
|
||||
## Location of data
|
||||
The data path is hardcoded such that the following tree structure is assumed:
|
||||
```
|
||||
data/
|
||||
grid.h5
|
||||
hydrodynamic_U.h5
|
||||
hydrodynamic_V.h5
|
||||
interactive-track-and-trace/
|
||||
particle-track-and-trace/
|
||||
...
|
||||
```
|
||||
|
||||
## Compiling
|
||||
Let the current directory be the `src` directory. Run:
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
92
particle-track-and-trace/src/CMakeLists.txt
Normal file
92
particle-track-and-trace/src/CMakeLists.txt
Normal file
@@ -0,0 +1,92 @@
|
||||
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
|
||||
|
||||
project(ParticleTrackTrace)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_package(VTK COMPONENTS
|
||||
CommonColor
|
||||
CommonColor
|
||||
CommonCore
|
||||
CommonDataModel
|
||||
FiltersGeneral
|
||||
FiltersGeometry
|
||||
FiltersProgrammable
|
||||
FiltersSources
|
||||
ImagingSources
|
||||
InteractionStyle
|
||||
IOImage
|
||||
RenderingContextOpenGL2
|
||||
RenderingCore
|
||||
RenderingCore
|
||||
RenderingFreeType
|
||||
RenderingGL2PSOpenGL2
|
||||
RenderingOpenGL2)
|
||||
|
||||
|
||||
if (NOT VTK_FOUND)
|
||||
message(FATAL_ERROR "VtkBase: Unable to find the VTK build folder.")
|
||||
endif()
|
||||
|
||||
# netcdf setup
|
||||
find_package(netCDF REQUIRED)
|
||||
|
||||
add_executable(ParticleTrackTrace MACOSX_BUNDLE main.cpp
|
||||
layers/BackgroundImage.cpp
|
||||
layers/BackgroundImage.h
|
||||
layers/EGlyphLayer.cpp
|
||||
layers/EGlyphLayer.h
|
||||
layers/Layer.cpp
|
||||
layers/Layer.h
|
||||
layers/LGlyphLayer.cpp
|
||||
layers/LGlyphLayer.h
|
||||
Program.cpp
|
||||
Program.h
|
||||
commands/TimerCallbackCommand.h
|
||||
commands/TimerCallbackCommand.cpp
|
||||
commands/SpawnPointCallback.h
|
||||
commands/SpawnPointCallback.cpp
|
||||
CartographicTransformation.cpp
|
||||
advection/AdvectionKernel.h
|
||||
advection/EulerAdvectionKernel.cpp
|
||||
advection/EulerAdvectionKernel.h
|
||||
advection/interpolate.cpp
|
||||
advection/interpolate.h
|
||||
advection/readdata.cpp
|
||||
advection/readdata.h
|
||||
advection/RK4AdvectionKernel.cpp
|
||||
advection/RK4AdvectionKernel.h
|
||||
advection/UVGrid.cpp
|
||||
advection/UVGrid.h
|
||||
advection/Vel.cpp
|
||||
advection/Vel.h
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND nc-config --includedir
|
||||
OUTPUT_VARIABLE NETCDF_INCLUDE_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND ncxx4-config --libdir
|
||||
OUTPUT_VARIABLE NETCDFCXX_LIB_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
target_include_directories(ParticleTrackTrace PUBLIC ${netCDF_INCLUDE_DIR})
|
||||
|
||||
find_library(NETCDF_LIB NAMES netcdf-cxx4 netcdf_c++4 PATHS ${NETCDFCXX_LIB_DIR} NO_DEFAULT_PATH)
|
||||
|
||||
# Prevent a "command line is too long" failure in Windows.
|
||||
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
|
||||
target_link_libraries(ParticleTrackTrace ${NETCDF_LIB} ${VTK_LIBRARIES})
|
||||
|
||||
# vtk_module_autoinit is needed
|
||||
vtk_module_autoinit(
|
||||
TARGETS ParticleTrackTrace
|
||||
MODULES ${VTK_LIBRARIES}
|
||||
)
|
||||
46
particle-track-and-trace/src/CartographicTransformation.cpp
Normal file
46
particle-track-and-trace/src/CartographicTransformation.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "CartographicTransformation.h"
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkTransformFilter.h>
|
||||
|
||||
vtkSmartPointer<vtkCamera> createNormalisedCamera() {
|
||||
vtkSmartPointer<vtkCamera> camera = vtkSmartPointer<vtkCamera>::New();
|
||||
camera->ParallelProjectionOn(); // Enable parallel projection
|
||||
|
||||
camera->SetPosition(0, 0, 1000); // Place the camera above the center
|
||||
camera->SetFocalPoint(0, 0, 0); // Look at the center
|
||||
camera->SetViewUp(0, 1, 0); // Set the up vector to be along the Y-axis
|
||||
camera->SetParallelScale(1); // x,y in [-1, 1]
|
||||
|
||||
return camera;
|
||||
}
|
||||
|
||||
vtkSmartPointer<vtkMatrix4x4> getCartographicTransformMatrix(const std::shared_ptr<UVGrid> uvGrid) {
|
||||
const double XMin = uvGrid->lons.front();
|
||||
const double XMax = uvGrid->lons.back();
|
||||
const double YMin = uvGrid->lats.front();
|
||||
const double YMax = uvGrid->lats.back();
|
||||
|
||||
double eyeTransform[] = {
|
||||
2/(XMax-XMin), 0, 0, -(XMax+XMin)/(XMax-XMin),
|
||||
0, 2/(YMax-YMin), 0, -(YMax+YMin)/(YMax-YMin),
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
};
|
||||
|
||||
auto matrix = vtkSmartPointer<vtkMatrix4x4>::New();
|
||||
matrix->DeepCopy(eyeTransform);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
// Assumes Normalised camera is used
|
||||
vtkSmartPointer<vtkTransformFilter> createCartographicTransformFilter(const std::shared_ptr<UVGrid> uvGrid) {
|
||||
vtkNew<vtkTransform> transform;
|
||||
|
||||
transform->SetMatrix(getCartographicTransformMatrix(uvGrid));
|
||||
|
||||
vtkSmartPointer<vtkTransformFilter> transformFilter = vtkSmartPointer<vtkTransformFilter>::New();
|
||||
transformFilter->SetTransform(transform);
|
||||
|
||||
return transformFilter;
|
||||
}
|
||||
31
particle-track-and-trace/src/CartographicTransformation.h
Normal file
31
particle-track-and-trace/src/CartographicTransformation.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkTransformFilter.h>
|
||||
#include "advection/UVGrid.h"
|
||||
|
||||
#ifndef NORMALISEDCARTOGRAPHICCAMERA_H
|
||||
#define NORMALISEDCARTOGRAPHICCAMERA_H
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a orthographically projected camera that looks at the square x,y in [-1, 1] with z = 0 and w = 1.
|
||||
* The space [-1,1] x [-1,1] x {0} will be referred to as the normalised space.
|
||||
* @return pointer to camera
|
||||
*/
|
||||
vtkSmartPointer<vtkCamera> createNormalisedCamera();
|
||||
|
||||
/**
|
||||
* Constructs a 4x4 projection matrix that maps homogenious (longitude, latitude, 0, 1) points
|
||||
* to the normalised space.
|
||||
* TODO: This transformation has room for improvement see:
|
||||
* https://github.com/MakeNEnjoy/interactive-track-and-trace/issues/12
|
||||
* @return pointer to 4x4 matrix
|
||||
*/
|
||||
vtkSmartPointer<vtkMatrix4x4> getCartographicTransformMatrix(const std::shared_ptr<UVGrid> uvGrid);
|
||||
|
||||
/**
|
||||
* Convenience function that converts the 4x4 projection matrix into a vtkTransformFilter
|
||||
* @return pointer to transform filter
|
||||
*/
|
||||
vtkSmartPointer<vtkTransformFilter> createCartographicTransformFilter(const std::shared_ptr<UVGrid> uvGrid);
|
||||
|
||||
#endif //NORMALISEDCARTOGRAPHICCAMERA_H
|
||||
88
particle-track-and-trace/src/Program.cpp
Normal file
88
particle-track-and-trace/src/Program.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkDoubleArray.h>
|
||||
#include <vtkGlyphSource2D.h>
|
||||
#include <vtkRegularPolygonSource.h>
|
||||
#include <vtkGlyph2D.h>
|
||||
#include <vtkActor2D.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkPolyDataMapper2D.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkProperty2D.h>
|
||||
#include <vtkVertexGlyphFilter.h>
|
||||
#include <netcdf>
|
||||
#include <vtkArrowSource.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include <vtkInteractorStyleUser.h>
|
||||
|
||||
#include "Program.h"
|
||||
#include "commands/TimerCallbackCommand.h"
|
||||
#include "commands/SpawnPointCallback.h"
|
||||
|
||||
void Program::setWinProperties() {
|
||||
this->win->SetWindowName("Simulation");
|
||||
this->win->SetSize(661, 661);
|
||||
this->win->SetDesiredUpdateRate(60);
|
||||
|
||||
this->interact->SetRenderWindow(this->win);
|
||||
this->interact->Initialize();
|
||||
|
||||
vtkNew<vtkInteractorStyleUser> style;
|
||||
interact->SetInteractorStyle(style);
|
||||
}
|
||||
|
||||
void Program::setupTimer(int dt) {
|
||||
auto callback = vtkSmartPointer<TimerCallbackCommand>::New(this);
|
||||
callback->SetClientData(this);
|
||||
callback->setDt(dt);
|
||||
this->interact->AddObserver(vtkCommand::TimerEvent, callback);
|
||||
this->interact->AddObserver(vtkCommand::KeyPressEvent, callback);
|
||||
this->interact->CreateRepeatingTimer(17); // 60 fps == 1000 / 60 == 16.7 ms per frame
|
||||
}
|
||||
|
||||
Program::Program(int timerDT) {
|
||||
this->win = vtkSmartPointer<vtkRenderWindow>::New();
|
||||
this->interact = vtkSmartPointer<vtkRenderWindowInteractor>::New();
|
||||
|
||||
this->win->SetNumberOfLayers(0);
|
||||
setWinProperties();
|
||||
setupTimer(timerDT);
|
||||
}
|
||||
|
||||
|
||||
void Program::addLayer(Layer *layer) {
|
||||
this->layers.push_back(layer);
|
||||
this->win->AddRenderer(layer->getLayer());
|
||||
this->win->SetNumberOfLayers(this->win->GetNumberOfLayers() + 1);
|
||||
}
|
||||
|
||||
void Program::removeLayer(Layer *layer) {
|
||||
this->win->RemoveRenderer(layer->getLayer());
|
||||
|
||||
auto it = std::find(this->layers.begin(), this->layers.end(), layer);
|
||||
if (it != this->layers.end()) {
|
||||
this->layers.erase(it);
|
||||
this->win->SetNumberOfLayers(this->win->GetNumberOfLayers() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Program::updateData(int t) {
|
||||
win->Render();
|
||||
for (Layer *l: layers) {
|
||||
l->updateData(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Program::setupInteractions() {
|
||||
for (Layer *l: layers) {
|
||||
l->addObservers(interact);
|
||||
}
|
||||
}
|
||||
|
||||
void Program::render() {
|
||||
setupInteractions();
|
||||
win->Render();
|
||||
interact->Start();
|
||||
}
|
||||
68
particle-track-and-trace/src/Program.h
Normal file
68
particle-track-and-trace/src/Program.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef PROGRAM_H
|
||||
#define PROGRAM_H
|
||||
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkRenderer.h>
|
||||
|
||||
#include "layers/Layer.h"
|
||||
#include "commands/SpawnPointCallback.h"
|
||||
|
||||
/** This class manages the upper levels of the vtk pipeline; it has attributes for the vtkrenderWindow and a vector of Layers to represent a variable number of vtkRenderers.
|
||||
* It can also set up a vtkTimer by connecting an instance of TimerCallbackCommand with its contained vtkRenderWindowInteractor.
|
||||
*/
|
||||
class Program {
|
||||
private:
|
||||
/** This attribute models a variable number of vtkRenderers, managed through the abstract Layer class.
|
||||
*/
|
||||
std::vector<Layer *> layers;
|
||||
|
||||
/** The window this program's layers render to.
|
||||
*/
|
||||
vtkSmartPointer<vtkRenderWindow> win;
|
||||
|
||||
/** The interactor through which the layers can interact with the window.
|
||||
*/
|
||||
vtkSmartPointer<vtkRenderWindowInteractor> interact;
|
||||
|
||||
/** This function sets some default properties on the vtkRenderWindow. Extracted to its' own function to keep the constructor from becoming cluttered.
|
||||
*/
|
||||
void setWinProperties();
|
||||
|
||||
/** This function sets up and connects a TimerCallbackCommand with the program.
|
||||
*/
|
||||
void setupTimer(int dt);
|
||||
|
||||
void setupInteractions();
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
*/
|
||||
Program(int timerDT);
|
||||
|
||||
/** This function adds a new layer (and thus vtkRenderer) to the program.
|
||||
* The layer is expected to set its own position in the vtkRenderWindow layer system.
|
||||
* @param layer : pointer to the layer to add.
|
||||
*/
|
||||
void addLayer(Layer *layer);
|
||||
|
||||
/** This function removes a given layer from the vtkRenderWindow and layers vector.
|
||||
* If the given layer is not actually in the program, nothing happens.
|
||||
* @param layer : the layer to removeLayer
|
||||
*/
|
||||
void removeLayer(Layer *layer);
|
||||
|
||||
/** This function updates the data for the associated layers to the given timestamp.
|
||||
* Also updates the renderWindow.
|
||||
* @param t : the timestamp to update the data to.
|
||||
*/
|
||||
void updateData(int t);
|
||||
|
||||
/**
|
||||
* This function renders the vtkRenderWindow for the first time.
|
||||
* Only call this function once!
|
||||
*/
|
||||
void render();
|
||||
};
|
||||
|
||||
#endif
|
||||
31
particle-track-and-trace/src/advection/AdvectionKernel.h
Normal file
31
particle-track-and-trace/src/advection/AdvectionKernel.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef ADVECTIONKERNEL_H
|
||||
#define ADVECTIONKERNEL_H
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "Vel.h"
|
||||
|
||||
/*
|
||||
* Implement this class for every integration method.
|
||||
*/
|
||||
class AdvectionKernel {
|
||||
public:
|
||||
/**
|
||||
* This function must take a time, latitude and longitude of a particle and must output
|
||||
* a new latitude and longitude after being advected once for AdvectionKernel::DT time as defined above.
|
||||
* @param time Time since the beginning of the data
|
||||
* @param latitude Latitude of particle
|
||||
* @param longitude Longitude of particle
|
||||
* @return A pair of latitude and longitude of particle.
|
||||
*/
|
||||
virtual std::pair<double, double> advect(int time, double latitude, double longitude, int dt) const = 0;
|
||||
|
||||
// Taken from Parcels https://github.com/OceanParcels/parcels/blob/daa4b062ed8ae0b2be3d87367d6b45599d6f95db/parcels/tools/converters.py#L155
|
||||
const static double metreToDegrees(double metre) {
|
||||
return metre / 1000. / 1.852 / 60.;
|
||||
}
|
||||
|
||||
virtual ~AdvectionKernel() = default; // Apparently I need this, idk why
|
||||
};
|
||||
|
||||
#endif //ADVECTIONKERNEL_H
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
#include "EulerAdvectionKernel.h"
|
||||
#include "interpolate.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
EulerAdvectionKernel::EulerAdvectionKernel(std::shared_ptr<UVGrid> grid) : grid(grid) {}
|
||||
|
||||
std::pair<double, double> EulerAdvectionKernel::advect(int time, double latitude, double longitude, int dt) const {
|
||||
auto [u, v] = bilinearinterpolate(*grid, time, latitude, longitude);
|
||||
|
||||
return {latitude + metreToDegrees(v * dt), longitude + metreToDegrees(u * dt)};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef EULERADVECTIONKERNEL_H
|
||||
#define EULERADVECTIONKERNEL_H
|
||||
|
||||
#include "AdvectionKernel.h"
|
||||
#include "UVGrid.h"
|
||||
|
||||
/**
|
||||
* Implementation of AdvectionKernel.
|
||||
* The basic equation is:
|
||||
* new_latitude = latitude + v*DT
|
||||
* new_longitude = longitude + u*DT
|
||||
*
|
||||
* Uses bilinear interpolation as implemented in interpolate.h
|
||||
*/
|
||||
class EulerAdvectionKernel: public AdvectionKernel {
|
||||
private:
|
||||
std::shared_ptr<UVGrid> grid;
|
||||
public:
|
||||
explicit EulerAdvectionKernel(std::shared_ptr<UVGrid> grid);
|
||||
std::pair<double, double> advect(int time, double latitude, double longitude, int dt) const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //EULERADVECTIONKERNEL_H
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "RK4AdvectionKernel.h"
|
||||
#include "interpolate.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
RK4AdvectionKernel::RK4AdvectionKernel(std::shared_ptr<UVGrid> grid): grid(grid) { }
|
||||
|
||||
std::pair<double, double> RK4AdvectionKernel::advect(int time, double latitude, double longitude, int dt) const {
|
||||
auto [u1, v1] = bilinearinterpolate(*grid, time, latitude, longitude);
|
||||
// lon1, lat1 = (particle.lon + u1*.5*particle.dt, particle.lat + v1*.5*particle.dt);
|
||||
double lon1 = longitude + metreToDegrees(u1 * 0.5*dt);
|
||||
double lat1 = latitude + metreToDegrees(v1 * 0.5*dt);
|
||||
|
||||
// (u2, v2) = fieldset.UV[time + .5 * particle.dt, particle.depth, lat1, lon1, particle]
|
||||
auto [u2, v2] = bilinearinterpolate(*grid, time + 0.5 * dt, lat1, lon1);
|
||||
|
||||
// lon2, lat2 = (particle.lon + u2*.5*particle.dt, particle.lat + v2*.5*particle.dt)
|
||||
double lon2 = longitude + metreToDegrees(u2 * 0.5 * dt);
|
||||
double lat2 = latitude + metreToDegrees(v2 * 0.5 * dt);
|
||||
|
||||
// (u3, v3) = fieldset.UV[time + .5 * particle.dt, particle.depth, lat2, lon2, particle]
|
||||
auto [u3, v3] = bilinearinterpolate(*grid, time + 0.5 * dt, lat2, lon2);
|
||||
|
||||
// lon3, lat3 = (particle.lon + u3*particle.dt, particle.lat + v3*particle.dt)
|
||||
double lon3 = longitude + metreToDegrees(u3 * dt);
|
||||
double lat3 = latitude + metreToDegrees(v3 * dt);
|
||||
|
||||
// (u4, v4) = fieldset.UV[time + particle.dt, particle.depth, lat3, lon3, particle]
|
||||
auto [u4, v4] = bilinearinterpolate(*grid, time + dt, lat3, lon3);
|
||||
|
||||
double lonFinal = longitude + metreToDegrees((u1 + 2 * u2 + 2 * u3 + u4) / 6.0 * dt);
|
||||
double latFinal = latitude + metreToDegrees((v1 + 2 * v2 + 2 * v3 + v4) / 6.0 * dt);
|
||||
|
||||
return {latFinal, lonFinal};
|
||||
}
|
||||
22
particle-track-and-trace/src/advection/RK4AdvectionKernel.h
Normal file
22
particle-track-and-trace/src/advection/RK4AdvectionKernel.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef RK4ADVECTIONKERNEL_H
|
||||
#define RK4ADVECTIONKERNEL_H
|
||||
|
||||
#include "AdvectionKernel.h"
|
||||
#include "UVGrid.h"
|
||||
|
||||
/**
|
||||
* Implementation of Advection kernel using RK4 integration
|
||||
* See https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods for more details.
|
||||
* Uses bilinear interpolation as implemented in interpolate.h
|
||||
*/
|
||||
class RK4AdvectionKernel: public AdvectionKernel {
|
||||
private:
|
||||
std::shared_ptr<UVGrid> grid;
|
||||
public:
|
||||
explicit RK4AdvectionKernel(std::shared_ptr<UVGrid> grid);
|
||||
std::pair<double, double> advect(int time, double latitude, double longitude, int dt) const override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //RK4ADVECTIONKERNEL_H
|
||||
66
particle-track-and-trace/src/advection/UVGrid.cpp
Normal file
66
particle-track-and-trace/src/advection/UVGrid.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <ranges>
|
||||
|
||||
#include "UVGrid.h"
|
||||
#include "readdata.h"
|
||||
|
||||
#define sizeError2 "The sizes of the hydrodynamic data files are different"
|
||||
#define sizeError "The sizes of the hydrodynamicU or -V files does not correspond with the sizes of the grid file"
|
||||
|
||||
using namespace std;
|
||||
|
||||
UVGrid::UVGrid() {
|
||||
auto us = readHydrodynamicU();
|
||||
auto vs = readHydrodynamicV();
|
||||
if (us.size() != vs.size()) {
|
||||
throw domain_error(sizeError2);
|
||||
}
|
||||
|
||||
tie(times, lats, lons) = readGrid();
|
||||
|
||||
timeSize = times.size();
|
||||
latSize = lats.size();
|
||||
lonSize = lons.size();
|
||||
|
||||
size_t gridSize = timeSize * latSize * lonSize;
|
||||
if (gridSize != us.size()) {
|
||||
throw domain_error(sizeError);
|
||||
}
|
||||
|
||||
uvData.reserve(gridSize);
|
||||
|
||||
for (auto vel: views::zip(us, vs)) {
|
||||
uvData.push_back(Vel(vel));
|
||||
}
|
||||
}
|
||||
|
||||
const Vel &UVGrid::operator[](size_t timeIndex, size_t latIndex, size_t lonIndex) const {
|
||||
if (timeIndex < 0 or timeIndex >= timeSize
|
||||
or latIndex < 0 or latIndex >= latSize
|
||||
or lonIndex < 0 or lonIndex >= lonSize) {
|
||||
throw std::out_of_range("Index out of bounds");
|
||||
}
|
||||
size_t index = timeIndex * (latSize * lonSize) + latIndex * lonSize + lonIndex;
|
||||
return uvData[index];
|
||||
}
|
||||
|
||||
double UVGrid::lonStep() const {
|
||||
return lons[1] - lons[0];
|
||||
}
|
||||
|
||||
double UVGrid::latStep() const {
|
||||
return lats[1] - lats[0];
|
||||
}
|
||||
|
||||
int UVGrid::timeStep() const {
|
||||
return times[1] - times[0];
|
||||
}
|
||||
|
||||
void UVGrid::streamSlice(ostream &os, size_t t) {
|
||||
for (int x = 0; x < latSize; x++) {
|
||||
for (int y = 0; y < lonSize; y++) {
|
||||
auto vel = (*this)[t, x, y];
|
||||
os << vel << " ";
|
||||
}
|
||||
os << endl;
|
||||
}
|
||||
}
|
||||
65
particle-track-and-trace/src/advection/UVGrid.h
Normal file
65
particle-track-and-trace/src/advection/UVGrid.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#ifndef UVGRID_H
|
||||
#define UVGRID_H
|
||||
|
||||
#include <vector>
|
||||
#include "Vel.h"
|
||||
|
||||
class UVGrid {
|
||||
private:
|
||||
/**
|
||||
* 1D data vector of all the us and vs
|
||||
*/
|
||||
std::vector<Vel> uvData;
|
||||
public:
|
||||
UVGrid();
|
||||
|
||||
/**
|
||||
* The matrix has shape (timeSize, latSize, lonSize)
|
||||
*/
|
||||
size_t timeSize;
|
||||
size_t latSize;
|
||||
size_t lonSize;
|
||||
|
||||
/**
|
||||
* Assuming grid is a regular grid, gives the longitudinal spacing of grid.
|
||||
* @return longitudinal spacing
|
||||
*/
|
||||
double lonStep() const;
|
||||
|
||||
/**
|
||||
* Assuming grid is a regular grid, gives the latitudinal spacing of grid.
|
||||
* @return latitudinal spacing
|
||||
*/
|
||||
double latStep() const;
|
||||
|
||||
/**
|
||||
* Assuming grid is a regular grid, gives the time spacing of grid.
|
||||
* @return time spacing
|
||||
*/
|
||||
int timeStep() const;
|
||||
|
||||
/**
|
||||
* times, lats, lons are vector of length timeSize, latSize, lonSize respectively.
|
||||
* The maintain the following invariant:
|
||||
* grid[timeIndex,latIndex,lonIndex] gives the u,v at the point with latitude at lats[latIndex],
|
||||
* with longitude at lons[lonIndex], and with time at times[timeIndex].
|
||||
*/
|
||||
std::vector<int> times;
|
||||
std::vector<double> lats;
|
||||
std::vector<double> lons;
|
||||
|
||||
/**
|
||||
* The 3D index into the data. The array is sized by [8761][67][116]
|
||||
* @return Velocity at that index
|
||||
*/
|
||||
const Vel& operator[](size_t timeIndex, size_t latIndex, size_t lonIndex) const;
|
||||
|
||||
/**
|
||||
* Streams a slice at timeIndex t of the matrix to the outstream given by os
|
||||
* @param os outstream
|
||||
* @param t index with which to slice matrix
|
||||
*/
|
||||
void streamSlice(std::ostream &os, size_t t);
|
||||
};
|
||||
|
||||
#endif //UVGRID_H
|
||||
40
particle-track-and-trace/src/advection/Vel.cpp
Normal file
40
particle-track-and-trace/src/advection/Vel.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "Vel.h"
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Vel::Vel(double u, double v) : u(u), v(v) {}
|
||||
|
||||
Vel::Vel(const std::pair<double, double>& p) : u(p.first), v(p.second) {}
|
||||
|
||||
Vel& Vel::operator=(const std::pair<double, double>& p) {
|
||||
u = p.first;
|
||||
v = p.second;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vel Vel::operator+(const Vel& other) const {
|
||||
return Vel(u + other.u, v + other.v);
|
||||
}
|
||||
|
||||
Vel& Vel::operator+=(const Vel& other) {
|
||||
u += other.u;
|
||||
v += other.v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Scalar>
|
||||
Vel Vel::operator/(Scalar scalar) const {
|
||||
if (scalar == 0) throw std::runtime_error("Division by zero");
|
||||
return Vel(u / scalar, v / scalar);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(ostream& os, const Vel& vel) {
|
||||
os << "(";
|
||||
os << fixed << setprecision(2) << setw(5) << vel.u;
|
||||
os << ", ";
|
||||
os << fixed << setprecision(2) << setw(5) << vel.v;
|
||||
os << ")";
|
||||
return os;
|
||||
}
|
||||
44
particle-track-and-trace/src/advection/Vel.h
Normal file
44
particle-track-and-trace/src/advection/Vel.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef VEL_H
|
||||
#define VEL_H
|
||||
|
||||
#include <utility>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <format>
|
||||
|
||||
class Vel {
|
||||
public:
|
||||
double u; // Eastward Current Velocity in the Water Column
|
||||
double v; // Northward Current Velocity in the Water Column
|
||||
|
||||
Vel(double u, double v);
|
||||
Vel(const std::pair<double, double>& p); // Conversion constructor
|
||||
Vel& operator=(const std::pair<double, double>& p);
|
||||
|
||||
// Operator + to add two Vel objects
|
||||
Vel operator+(const Vel& other) const;
|
||||
|
||||
// Operator += to add another Vel object to this object
|
||||
Vel& operator+=(const Vel& other);
|
||||
|
||||
// Operator * to multiply Vel by a scalar, defined as a member template
|
||||
template<typename Scalar>
|
||||
Vel operator*(Scalar scalar) const {
|
||||
return Vel(u * scalar, v * scalar);
|
||||
}
|
||||
|
||||
// Operator / to divide Vel by a scalar, defined as a member template
|
||||
template<typename Scalar>
|
||||
Vel operator/(Scalar scalar) const;
|
||||
|
||||
// Friend declaration for the stream insertion operator
|
||||
friend std::ostream& operator<<(std::ostream& os, const Vel& vel);
|
||||
};
|
||||
|
||||
// Non-member function for scalar multiplication on the left
|
||||
template<typename Scalar>
|
||||
Vel operator*(Scalar scalar, const Vel& p) {
|
||||
return Vel(p.u * scalar, p.v * scalar);
|
||||
}
|
||||
|
||||
#endif //VEL_H
|
||||
47
particle-track-and-trace/src/advection/interpolate.cpp
Normal file
47
particle-track-and-trace/src/advection/interpolate.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "interpolate.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Vel bilinearinterpolate(const UVGrid &uvGrid, int time, double lat, double lon) {
|
||||
double latStep = uvGrid.latStep();
|
||||
double lonStep = uvGrid.lonStep();
|
||||
int timeStep = uvGrid.timeStep();
|
||||
|
||||
int latIndex = (lat - uvGrid.lats[0]) / latStep;
|
||||
int lonIndex = (lon - uvGrid.lons[0]) / lonStep;
|
||||
int timeIndex = (time - uvGrid.times[0]) / timeStep;
|
||||
|
||||
double timeRatio = (static_cast<double>(time) - uvGrid.times[timeIndex]) / timeStep;
|
||||
double latRatio = (lat - uvGrid.lats[latIndex]) / latStep;
|
||||
double lonRatio = (lon - uvGrid.lons[lonIndex]) / lonStep;
|
||||
|
||||
Vel point = {0, 0};
|
||||
for (int timeOffset = 0; timeOffset <= 1; timeOffset++) {
|
||||
for (int latOffset = 0; latOffset <= 1; latOffset++) {
|
||||
for (int lonOffset = 0; lonOffset <= 1; lonOffset++) {
|
||||
auto vertex = uvGrid[
|
||||
timeIndex + 1 < uvGrid.timeSize ? timeIndex + timeOffset : timeIndex,
|
||||
latIndex + 1 < uvGrid.latSize ? latIndex + latOffset : latIndex,
|
||||
lonIndex + 1 < uvGrid.lonSize ? lonIndex + lonOffset : lonIndex
|
||||
];
|
||||
|
||||
double timeRation = (1 - timeOffset) * (1 - timeRatio) + timeOffset * timeRatio;
|
||||
double latRation = (1 - latOffset) * (1 - latRatio) + latOffset * latRatio;
|
||||
double lonRation = (1 - lonOffset) * (1 - lonRatio) + lonOffset * lonRatio;
|
||||
point += timeRation * latRation * lonRation * vertex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
vector<Vel> bilinearinterpolation(const UVGrid &uvGrid, vector<tuple<int, double, double>> points) {
|
||||
vector<Vel> result;
|
||||
result.reserve(points.size());
|
||||
for (auto [time, lat, lon]: points) {
|
||||
result.push_back(bilinearinterpolate(uvGrid, time, lat, lon));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
28
particle-track-and-trace/src/advection/interpolate.h
Normal file
28
particle-track-and-trace/src/advection/interpolate.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef INTERPOLATE_H
|
||||
#define INTERPOLATE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "UVGrid.h"
|
||||
|
||||
/**
|
||||
* Bilinearly interpolate the point (time, lat, lon) to produce the interpolated velocity.
|
||||
* Since it is in 3D, this means that it interpolates against 8 points (excluding edges).
|
||||
* As described in https://numerical.recipes/book.html Chapter 3.6
|
||||
* @param uvGrid velocity grid
|
||||
* @param time time of point
|
||||
* @param lat latitude of point
|
||||
* @param lon longitude of point
|
||||
* @return interpolated velocity
|
||||
*/
|
||||
Vel bilinearinterpolate(const UVGrid &uvGrid, int time, double lat, double lon);
|
||||
|
||||
/**
|
||||
* Helper function for bilnearly interpolating a vector of points
|
||||
* @param uvGrid velocity grid
|
||||
* @param points vector of points
|
||||
* @return interpolated velocities
|
||||
*/
|
||||
std::vector<Vel> bilinearinterpolation(const UVGrid &uvGrid, std::vector<std::tuple<int, double, double>> points);
|
||||
|
||||
#endif //INTERPOLATE_H
|
||||
50
particle-track-and-trace/src/advection/readdata.cpp
Normal file
50
particle-track-and-trace/src/advection/readdata.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <netcdf>
|
||||
|
||||
#include "readdata.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace netCDF;
|
||||
|
||||
template <typename T>
|
||||
vector<T> getVarVector(const NcVar &var) {
|
||||
int length = 1;
|
||||
for (NcDim dim : var.getDims()) {
|
||||
length *= dim.getSize();
|
||||
}
|
||||
|
||||
vector<T> vec(length);
|
||||
|
||||
var.getVar(vec.data());
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
vector<double> readHydrodynamicU() {
|
||||
// Vs and Us flipped cause the files are named incorrectly
|
||||
netCDF::NcFile data("../../../../data/hydrodynamic_V.h5", netCDF::NcFile::read);
|
||||
|
||||
multimap< string, NcVar > vars = data.getVars();
|
||||
|
||||
return getVarVector<double>(vars.find("vo")->second);
|
||||
}
|
||||
|
||||
vector<double> readHydrodynamicV() {
|
||||
// Vs and Us flipped cause the files are named incorrectly
|
||||
netCDF::NcFile data("../../../../data/hydrodynamic_U.h5", netCDF::NcFile::read);
|
||||
|
||||
multimap< string, NcVar > vars = data.getVars();
|
||||
|
||||
return getVarVector<double>(vars.find("uo")->second);
|
||||
}
|
||||
|
||||
tuple<vector<int>, vector<double>, vector<double>> readGrid() {
|
||||
netCDF::NcFile data("../../../../data/grid.h5", netCDF::NcFile::read);
|
||||
multimap< string, NcVar > vars = data.getVars();
|
||||
vector<int> time = getVarVector<int>(vars.find("times")->second);
|
||||
vector<double> longitude = getVarVector<double>(vars.find("longitude")->second);
|
||||
vector<double> latitude = getVarVector<double>(vars.find("latitude")->second);
|
||||
|
||||
return {time, latitude, longitude};
|
||||
}
|
||||
22
particle-track-and-trace/src/advection/readdata.h
Normal file
22
particle-track-and-trace/src/advection/readdata.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef READDATA_H
|
||||
#define READDATA_H
|
||||
|
||||
/**
|
||||
* reads the file hydrodynamic_U.h5
|
||||
* @return the data vector of us
|
||||
*/
|
||||
std::vector<double> readHydrodynamicU();
|
||||
|
||||
/**
|
||||
* reads the file hydrodynamic_V.h5
|
||||
* @return the data vector of vs
|
||||
*/
|
||||
std::vector<double> readHydrodynamicV();
|
||||
|
||||
/**
|
||||
* Reads the file grid.h5
|
||||
* @return a tuple of (times, latitude, longitude)
|
||||
*/
|
||||
std::tuple<std::vector<int>, std::vector<double>, std::vector<double>> readGrid();
|
||||
|
||||
#endif //READDATA_H
|
||||
82
particle-track-and-trace/src/commands/SpawnPointCallback.cpp
Normal file
82
particle-track-and-trace/src/commands/SpawnPointCallback.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "SpawnPointCallback.h"
|
||||
|
||||
#include <vtkVertex.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkCommand.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
|
||||
#include "../CartographicTransformation.h"
|
||||
|
||||
void convertDisplayToWorld(vtkRenderer *renderer, int x, int y, double *worldPos) {
|
||||
double displayPos[3] = {static_cast<double>(x), static_cast<double>(y), 0.0};
|
||||
renderer->SetDisplayPoint(displayPos);
|
||||
renderer->DisplayToWorld();
|
||||
renderer->GetWorldPoint(worldPos);
|
||||
}
|
||||
|
||||
void SpawnPointCallback::Execute(vtkObject *caller, unsigned long evId, void *callData) {
|
||||
// Note the use of reinterpret_cast to cast the caller to the expected type.
|
||||
auto interactor = reinterpret_cast<vtkRenderWindowInteractor *>(caller);
|
||||
|
||||
if (evId == vtkCommand::LeftButtonPressEvent) {
|
||||
dragging = true;
|
||||
}
|
||||
if (evId == vtkCommand::LeftButtonReleaseEvent) {
|
||||
dragging = false;
|
||||
}
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
interactor->GetEventPosition(x, y);
|
||||
|
||||
double worldPos[4] = {2, 0, 0, 0};
|
||||
double displayPos[3] = {static_cast<double>(x), static_cast<double>(y), 0.0};
|
||||
ren->SetDisplayPoint(displayPos);
|
||||
ren->DisplayToWorld();
|
||||
ren->GetWorldPoint(worldPos);
|
||||
inverseCartographicProjection->MultiplyPoint(worldPos, worldPos);
|
||||
cout << "clicked on lon = " << worldPos[0] << " and lat = " << worldPos[1] << endl;
|
||||
|
||||
vtkIdType id = points->InsertNextPoint(worldPos[0], worldPos[1], 0);
|
||||
data->SetPoints(points);
|
||||
|
||||
vtkSmartPointer<vtkVertex> vertex = vtkSmartPointer<vtkVertex>::New();
|
||||
vertex->GetPointIds()->SetId(0, id);
|
||||
|
||||
vtkSmartPointer<vtkCellArray> vertices = vtkSmartPointer<vtkCellArray>::New();
|
||||
vertices->InsertNextCell(vertex);
|
||||
data->SetVerts(vertices);
|
||||
ren->GetRenderWindow()->Render();
|
||||
}
|
||||
|
||||
|
||||
SpawnPointCallback::SpawnPointCallback() : data(nullptr),
|
||||
points(nullptr),
|
||||
inverseCartographicProjection(nullptr),
|
||||
uvGrid(nullptr) { }
|
||||
|
||||
SpawnPointCallback *SpawnPointCallback::New() {
|
||||
return new SpawnPointCallback;
|
||||
}
|
||||
|
||||
void SpawnPointCallback::setData(const vtkSmartPointer<vtkPolyData> &data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
void SpawnPointCallback::setPoints(const vtkSmartPointer<vtkPoints> &points) {
|
||||
this->points = points;
|
||||
}
|
||||
|
||||
void SpawnPointCallback::setRen(const vtkSmartPointer<vtkRenderer> &ren) {
|
||||
this->ren = ren;
|
||||
}
|
||||
|
||||
void SpawnPointCallback::setUVGrid(const std::shared_ptr<UVGrid> &uvGrid) {
|
||||
this->uvGrid = uvGrid;
|
||||
inverseCartographicProjection = getCartographicTransformMatrix(uvGrid);
|
||||
inverseCartographicProjection->Invert();
|
||||
}
|
||||
40
particle-track-and-trace/src/commands/SpawnPointCallback.h
Normal file
40
particle-track-and-trace/src/commands/SpawnPointCallback.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef SPAWNPOINTCALLBACK_H
|
||||
#define SPAWNPOINTCALLBACK_H
|
||||
|
||||
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkMatrix4x4.h>
|
||||
#include "../advection/UVGrid.h"
|
||||
|
||||
class SpawnPointCallback : public vtkCallbackCommand {
|
||||
|
||||
public:
|
||||
static SpawnPointCallback *New();
|
||||
|
||||
SpawnPointCallback();
|
||||
|
||||
void setPoints(const vtkSmartPointer<vtkPoints> &points);
|
||||
|
||||
void setData(const vtkSmartPointer<vtkPolyData> &data);
|
||||
|
||||
void setRen(const vtkSmartPointer<vtkRenderer> &ren);
|
||||
|
||||
void setUVGrid(const std::shared_ptr<UVGrid> &uvGrid);
|
||||
|
||||
private:
|
||||
vtkSmartPointer<vtkPolyData> data;
|
||||
vtkSmartPointer<vtkPoints> points;
|
||||
vtkSmartPointer<vtkRenderer> ren;
|
||||
std::shared_ptr<UVGrid> uvGrid;
|
||||
vtkSmartPointer<vtkMatrix4x4> inverseCartographicProjection;
|
||||
|
||||
void Execute(vtkObject *caller, unsigned long evId, void *callData) override;
|
||||
|
||||
bool dragging = false;
|
||||
};
|
||||
|
||||
|
||||
#endif //SPAWNPOINTCALLBACK_H
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "TimerCallbackCommand.h"
|
||||
#include "../Program.h"
|
||||
|
||||
|
||||
TimerCallbackCommand::TimerCallbackCommand() : dt(3600), maxTime(3600*24*365), time(0) {}
|
||||
|
||||
TimerCallbackCommand* TimerCallbackCommand::New(Program *program) {
|
||||
TimerCallbackCommand *cb = new TimerCallbackCommand();
|
||||
cb->setProgram(program);
|
||||
cb->setPaused(false);
|
||||
return cb;
|
||||
}
|
||||
|
||||
void TimerCallbackCommand::Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData)) {
|
||||
auto intr = reinterpret_cast<vtkRenderWindowInteractor *>(caller);
|
||||
|
||||
if (eventId == vtkCommand::KeyPressEvent and not strcmp("space", intr->GetKeySym())) {
|
||||
this->paused = ! this->paused;
|
||||
} else if (eventId == vtkCommand::TimerEvent and not this->paused) {
|
||||
this->time += this->dt;
|
||||
|
||||
if (this->time >= this->maxTime) {
|
||||
return;
|
||||
// TODO: how do we deal with reaching the end of the simulated dataset? Do we just stop simulating, loop back around? What about the location of the particles in this case? Just some ideas to consider, but we should iron this out pretty soon.
|
||||
}
|
||||
|
||||
this->program->updateData(this->time);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TimerCallbackCommand::setProgram(Program *program) {
|
||||
this->program = program;
|
||||
}
|
||||
|
||||
|
||||
void TimerCallbackCommand::setPaused(const bool val) {
|
||||
this->paused = val;
|
||||
}
|
||||
|
||||
void TimerCallbackCommand::setDt(int dt) {
|
||||
this->dt = dt;
|
||||
}
|
||||
26
particle-track-and-trace/src/commands/TimerCallbackCommand.h
Normal file
26
particle-track-and-trace/src/commands/TimerCallbackCommand.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef TIMERCALLBACKCOMMAND_H
|
||||
#define TIMERCALLBACKCOMMAND_H
|
||||
|
||||
#include <vtkCallbackCommand.h>
|
||||
#include "../Program.h"
|
||||
|
||||
class TimerCallbackCommand : public vtkCallbackCommand {
|
||||
public:
|
||||
TimerCallbackCommand();
|
||||
static TimerCallbackCommand* New(Program *program);
|
||||
void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData)) override;
|
||||
|
||||
void setProgram(Program *program);
|
||||
void setPaused(const bool val);
|
||||
|
||||
void setDt(int dt);
|
||||
|
||||
private:
|
||||
int time;
|
||||
int dt;
|
||||
int maxTime;
|
||||
bool paused;
|
||||
Program *program;
|
||||
};
|
||||
|
||||
#endif
|
||||
62
particle-track-and-trace/src/layers/BackgroundImage.cpp
Normal file
62
particle-track-and-trace/src/layers/BackgroundImage.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "BackgroundImage.h"
|
||||
#include <vtkCamera.h>
|
||||
#include <vtkImageActor.h>
|
||||
#include <vtkImageData.h>
|
||||
#include <vtkImageReader2.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
BackgroundImage::BackgroundImage(string imagePath) : imagePath(imagePath) {
|
||||
this->ren = vtkSmartPointer<vtkRenderer>::New();
|
||||
this->ren->SetLayer(0);
|
||||
this->ren->InteractiveOff();
|
||||
updateImage();
|
||||
}
|
||||
|
||||
|
||||
void BackgroundImage::updateImage() {
|
||||
vtkSmartPointer<vtkImageData> imageData;
|
||||
|
||||
vtkSmartPointer<vtkImageReader2> imageReader;
|
||||
|
||||
imageReader.TakeReference(this->readerFactory->CreateImageReader2(this->imagePath.c_str()));
|
||||
imageReader->SetFileName(this->imagePath.c_str());
|
||||
imageReader->Update();
|
||||
imageData = imageReader->GetOutput();
|
||||
|
||||
vtkNew<vtkImageActor> imageActor;
|
||||
imageActor->SetInputData(imageData);
|
||||
|
||||
this->ren->AddActor(imageActor);
|
||||
|
||||
|
||||
// camera stuff
|
||||
// essentially sets the camera to the middle of the background, and points it at the background
|
||||
// TODO: extract this to its own function, separate from the background class.
|
||||
double origin[3], spacing[3];
|
||||
int extent[6];
|
||||
imageData->GetOrigin(origin);
|
||||
imageData->GetSpacing(spacing);
|
||||
imageData->GetExtent(extent);
|
||||
|
||||
vtkCamera *camera = this->ren->GetActiveCamera();
|
||||
camera->ParallelProjectionOn();
|
||||
|
||||
double xc = origin[0] + 0.5 * (extent[0] + extent[1]) * spacing[0];
|
||||
double yc = origin[1] + 0.5 * (extent[2] + extent[3]) * spacing[1];
|
||||
double yd = (extent[3] - extent[2] + 1) * spacing[1];
|
||||
double d = camera->GetDistance();
|
||||
camera->SetParallelScale(0.5 * yd);
|
||||
camera->SetFocalPoint(xc, yc, 0.0);
|
||||
camera->SetPosition(xc, yc, d);
|
||||
}
|
||||
|
||||
|
||||
string BackgroundImage::getImagePath() {
|
||||
return this->imagePath;
|
||||
}
|
||||
|
||||
void BackgroundImage::setImagePath(string imagePath) {
|
||||
this->imagePath = imagePath;
|
||||
updateImage();
|
||||
}
|
||||
37
particle-track-and-trace/src/layers/BackgroundImage.h
Normal file
37
particle-track-and-trace/src/layers/BackgroundImage.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef BACKGROUND_H
|
||||
#define BACKGROUND_H
|
||||
|
||||
#include "Layer.h"
|
||||
#include <vtkImageReader2Factory.h>
|
||||
|
||||
/** Implements the Layer class for the case of a background image.
|
||||
* Specifically, reads a backgroundImage given by the imagePath attribute and puts it on layer 0.
|
||||
*/
|
||||
class BackgroundImage : public Layer {
|
||||
private:
|
||||
std::string imagePath;
|
||||
vtkSmartPointer<vtkImageReader2Factory> readerFactory;
|
||||
|
||||
/** This private function updates the background image using the imagePath attribute.
|
||||
*/
|
||||
void updateImage();
|
||||
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
* @param imagePath : String to the path of the image to use as background.
|
||||
*/
|
||||
BackgroundImage(std::string imagePath);
|
||||
|
||||
/** Getter.
|
||||
* @return the imagePath attribute.
|
||||
*/
|
||||
std::string getImagePath();
|
||||
|
||||
/** Setter. Can be used to change the background image
|
||||
* @param imagePath : String to the path of the new image to use.
|
||||
*/
|
||||
void setImagePath(std::string imagePath);
|
||||
};
|
||||
|
||||
#endif
|
||||
109
particle-track-and-trace/src/layers/EGlyphLayer.cpp
Normal file
109
particle-track-and-trace/src/layers/EGlyphLayer.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "EGlyphLayer.h"
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkDoubleArray.h>
|
||||
#include <vtkGlyphSource2D.h>
|
||||
#include <vtkRegularPolygonSource.h>
|
||||
#include <vtkGlyph2D.h>
|
||||
#include <vtkActor2D.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkPolyDataMapper2D.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkProperty.h>
|
||||
#include <vtkProperty2D.h>
|
||||
#include <vtkVertexGlyphFilter.h>
|
||||
#include <vtkArrowSource.h>
|
||||
#include "../CartographicTransformation.h"
|
||||
#include "../advection/readdata.h"
|
||||
#include "../advection/interpolate.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
EGlyphLayer::EGlyphLayer(std::shared_ptr<UVGrid> uvGrid) {
|
||||
this->ren = vtkSmartPointer<vtkRenderer>::New();
|
||||
this->ren->SetLayer(1);
|
||||
this->ren->InteractiveOff();
|
||||
|
||||
this->uvGrid = uvGrid;
|
||||
|
||||
this->data = vtkSmartPointer<vtkPolyData>::New();
|
||||
this->direction = vtkSmartPointer<vtkDoubleArray>::New();
|
||||
this->direction->SetName("direction");
|
||||
readCoordinates();
|
||||
}
|
||||
|
||||
|
||||
void EGlyphLayer::readCoordinates() {
|
||||
vtkNew<vtkPoints> points;
|
||||
this->numLats = uvGrid->lats.size();
|
||||
this->numLons = uvGrid->lons.size();
|
||||
|
||||
this->direction->SetNumberOfComponents(3);
|
||||
this->direction->SetNumberOfTuples(numLats * numLons);
|
||||
points->Allocate(numLats * numLons);
|
||||
|
||||
auto camera = createNormalisedCamera();
|
||||
ren->SetActiveCamera(camera);
|
||||
|
||||
int i = 0;
|
||||
int latIndex = 0;
|
||||
for (double lat: uvGrid->lats) {
|
||||
int lonIndex = 0;
|
||||
for (double lon: uvGrid->lons) {
|
||||
auto [u, v] = (*uvGrid)[0, latIndex, lonIndex];
|
||||
direction->SetTuple3(i, 5*u, 5*v, 0);
|
||||
points->InsertPoint(i++, lon, lat, 0);
|
||||
lonIndex++;
|
||||
}
|
||||
latIndex++;
|
||||
}
|
||||
this->data->SetPoints(points);
|
||||
this->data->GetPointData()->AddArray(this->direction);
|
||||
this->data->GetPointData()->SetActiveVectors("direction");
|
||||
|
||||
vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter(uvGrid);
|
||||
transformFilter->SetInputData(data);
|
||||
|
||||
vtkNew<vtkGlyphSource2D> arrowSource;
|
||||
arrowSource->SetGlyphTypeToArrow();
|
||||
arrowSource->SetScale(0.2); //TODO: set this properly
|
||||
arrowSource->Update();
|
||||
|
||||
vtkNew<vtkGlyph2D> glyph2D;
|
||||
glyph2D->SetSourceConnection(arrowSource->GetOutputPort());
|
||||
glyph2D->SetInputConnection(transformFilter->GetOutputPort());
|
||||
glyph2D->OrientOn();
|
||||
glyph2D->ClampingOn();
|
||||
glyph2D->SetScaleModeToScaleByVector();
|
||||
glyph2D->SetVectorModeToUseVector();
|
||||
glyph2D->Update();
|
||||
|
||||
// vtkNew<vtkCoordinate> coordinate;
|
||||
// coordinate->SetCoordinateSystemToWorld();
|
||||
|
||||
vtkNew<vtkPolyDataMapper>(mapper);
|
||||
// mapper->SetTransformCoordinate(coordinate);
|
||||
mapper->SetInputConnection(glyph2D->GetOutputPort());
|
||||
mapper->Update();
|
||||
|
||||
vtkNew<vtkActor> actor;
|
||||
actor->SetMapper(mapper);
|
||||
|
||||
actor->GetProperty()->SetColor(0, 0, 0);
|
||||
actor->GetProperty()->SetOpacity(0.2);
|
||||
|
||||
this->ren->AddActor(actor);
|
||||
}
|
||||
|
||||
|
||||
void EGlyphLayer::updateData(int t) {
|
||||
int i = 0;
|
||||
for (int lat = 0; lat < uvGrid->latSize; lat++) {
|
||||
for (int lon = 0; lon < uvGrid->lonSize; lon++) {
|
||||
auto [u, v] = (*uvGrid)[t/3600, lat, lon];
|
||||
// TODO: 5*v scaling stuff should really be a filter transform
|
||||
this->direction->SetTuple3(i, 5*u, 5*v, 0);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
this->direction->Modified();
|
||||
}
|
||||
38
particle-track-and-trace/src/layers/EGlyphLayer.h
Normal file
38
particle-track-and-trace/src/layers/EGlyphLayer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef EGLYPHLAYER_H
|
||||
#define EGLYPHLAYER_H
|
||||
|
||||
#include "Layer.h"
|
||||
#include <vtkPolyData.h>
|
||||
|
||||
#include "../advection/UVGrid.h"
|
||||
|
||||
/** Implements the Layer class for the case of a Eulerian visualization.
|
||||
* Specifically, this class models the eulerian flow-fields of the simulation using the 'glyph' mark and 'direction' and 'form' channels to denote direction and strength of velocities.
|
||||
*/
|
||||
class EGlyphLayer : public Layer {
|
||||
private:
|
||||
vtkSmartPointer<vtkPolyData> data;
|
||||
vtkSmartPointer<vtkDoubleArray> direction;
|
||||
std::shared_ptr<UVGrid> uvGrid;
|
||||
int numLats;
|
||||
int numLons;
|
||||
|
||||
/** This private function sets up the initial coordinates for the glyphs in the dataset.
|
||||
* It also reads some initial data to actually display.
|
||||
*/
|
||||
void readCoordinates();
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
*/
|
||||
EGlyphLayer(std::shared_ptr<UVGrid> uvGrid);
|
||||
|
||||
/** updates the glyphs to reflect the given timestamp in the dataset.
|
||||
* @param t : the time at which to fetch the data.
|
||||
*/
|
||||
void updateData(int t);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
101
particle-track-and-trace/src/layers/LGlyphLayer.cpp
Normal file
101
particle-track-and-trace/src/layers/LGlyphLayer.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "LGlyphLayer.h"
|
||||
#include "../commands/SpawnPointCallback.h"
|
||||
#include <vtkActor2D.h>
|
||||
#include <vtkGlyph2D.h>
|
||||
#include <vtkGlyphSource2D.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkPolyDataMapper2D.h>
|
||||
#include <vtkProperty2D.h>
|
||||
#include <vtkVertexGlyphFilter.h>
|
||||
#include <vtkInteractorStyle.h>
|
||||
#include <vtkInteractorStyleUser.h>
|
||||
#include <vtkTransform.h>
|
||||
#include <vtkTransformFilter.h>
|
||||
#include <vtkPolyDataMapper.h>
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkCamera.h>
|
||||
|
||||
#include "../CartographicTransformation.h"
|
||||
|
||||
vtkSmartPointer<SpawnPointCallback> LGlyphLayer::createSpawnPointCallback() {
|
||||
auto newPointCallBack = vtkSmartPointer<SpawnPointCallback>::New();
|
||||
newPointCallBack->setData(data);
|
||||
newPointCallBack->setPoints(points);
|
||||
newPointCallBack->setRen(ren);
|
||||
newPointCallBack->setUVGrid(uvGrid);
|
||||
return newPointCallBack;
|
||||
}
|
||||
|
||||
// Further notes; current thinking is to allow tracking a particle's age by using a scalar array in the VtkPolyData. This would be incremented for every tick/updateData function call.
|
||||
// Another challenge is the concept of beaching; dead particles must not be included in the advect function call (wasted computations), but they should not be outright deleted from the vtkPoints either (we still want to display them). Working Solution: have another array of ints in the vtkPolyData, which tracks for how many calls of UpdateData a given particle has not had its position changed. If this int reaches some treshold (5? 10? 3? needs some testing), exclude the particle from the advect call.
|
||||
// TODO: modelling all this in vtkClasses is workable, but ideally i would want to work with a native C++ class. See if this is doable and feasible.
|
||||
|
||||
LGlyphLayer::LGlyphLayer(std::shared_ptr<UVGrid> uvGrid, std::unique_ptr<AdvectionKernel> advectionKernel) {
|
||||
this->ren = vtkSmartPointer<vtkRenderer>::New();
|
||||
this->ren->SetLayer(2);
|
||||
|
||||
this->points = vtkSmartPointer<vtkPoints>::New();
|
||||
this->data = vtkSmartPointer<vtkPolyData>::New();
|
||||
this->data->SetPoints(this->points);
|
||||
|
||||
advector = std::move(advectionKernel);
|
||||
this->uvGrid = uvGrid;
|
||||
|
||||
auto camera = createNormalisedCamera();
|
||||
ren->SetActiveCamera(camera);
|
||||
|
||||
vtkSmartPointer<vtkTransformFilter> transformFilter = createCartographicTransformFilter(uvGrid);
|
||||
transformFilter->SetInputData(data);
|
||||
|
||||
vtkNew<vtkGlyphSource2D> circleSource;
|
||||
circleSource->SetGlyphTypeToCircle();
|
||||
circleSource->SetScale(0.05);
|
||||
circleSource->Update();
|
||||
|
||||
vtkNew<vtkGlyph2D> glyph2D;
|
||||
glyph2D->SetSourceConnection(circleSource->GetOutputPort());
|
||||
glyph2D->SetInputConnection(transformFilter->GetOutputPort());
|
||||
glyph2D->SetColorModeToColorByScalar();
|
||||
glyph2D->Update();
|
||||
|
||||
vtkNew<vtkPolyDataMapper> mapper;
|
||||
mapper->SetInputConnection(glyph2D->GetOutputPort());
|
||||
mapper->Update();
|
||||
|
||||
vtkNew<vtkActor> actor;
|
||||
actor->SetMapper(mapper);
|
||||
|
||||
this->ren->AddActor(actor);
|
||||
}
|
||||
|
||||
// creates a few points so we can test the updateData function
|
||||
void LGlyphLayer::spoofPoints() {
|
||||
this->points->InsertNextPoint(-4.125, 61.375, 0);
|
||||
this->points->InsertNextPoint(6.532949683882039, 53.24308582564463, 0); // Coordinates of Zernike
|
||||
this->points->InsertNextPoint(5.315307819255385, 60.40001057122271, 0); // Coordinates of Bergen
|
||||
this->points->InsertNextPoint(6.646210231365825, 46.52346296009023, 0); // Coordinates of Lausanne
|
||||
this->points->InsertNextPoint(-6.553894313570932, 62.39522131195857,0); // Coordinates of the top of the Faroe islands
|
||||
|
||||
this->points->Modified();
|
||||
}
|
||||
|
||||
void LGlyphLayer::updateData(int t) {
|
||||
const int SUPERSAMPLINGRATE = 4;
|
||||
double point[3];
|
||||
for (vtkIdType n = 0; n < this->points->GetNumberOfPoints(); n++) {
|
||||
this->points->GetPoint(n, point);
|
||||
for (int i = 0; i < SUPERSAMPLINGRATE; i++) {
|
||||
std::tie(point[1], point[0]) = advector->advect(t, point[1], point[0], (t-lastT)/SUPERSAMPLINGRATE);
|
||||
}
|
||||
this->points->SetPoint(n, point[0], point[1], 0);
|
||||
}
|
||||
lastT = t;
|
||||
this->points->Modified();
|
||||
}
|
||||
|
||||
void LGlyphLayer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
auto newPointCallBack = createSpawnPointCallback();
|
||||
interactor->AddObserver(vtkCommand::LeftButtonPressEvent, newPointCallBack);
|
||||
interactor->AddObserver(vtkCommand::LeftButtonReleaseEvent, newPointCallBack);
|
||||
interactor->AddObserver(vtkCommand::MouseMoveEvent, newPointCallBack);
|
||||
}
|
||||
40
particle-track-and-trace/src/layers/LGlyphLayer.h
Normal file
40
particle-track-and-trace/src/layers/LGlyphLayer.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef LGLYPHLAYER_H
|
||||
#define LGLYPHLAYER_H
|
||||
|
||||
#include "Layer.h"
|
||||
#include "../advection/AdvectionKernel.h"
|
||||
#include "../commands/SpawnPointCallback.h"
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkInteractorStyle.h>
|
||||
|
||||
/** Implements the Layer class for the case of a Lagrangian visualization.
|
||||
* Specifically, this class models the Lagrangian particles in the simulation using the 'glyph' mark and 'transparency' channel to denote age.
|
||||
*/
|
||||
class LGlyphLayer : public Layer {
|
||||
private:
|
||||
vtkSmartPointer<vtkPoints> points;
|
||||
vtkSmartPointer<vtkPolyData> data;
|
||||
std::unique_ptr<AdvectionKernel> advector;
|
||||
std::shared_ptr<UVGrid> uvGrid;
|
||||
int lastT = 1000;
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
*/
|
||||
LGlyphLayer(std::shared_ptr<UVGrid> uvGrid, std::unique_ptr<AdvectionKernel> advectionKernel);
|
||||
|
||||
/** This function spoofs a few points in the dataset. Mostly used for testing.
|
||||
*/
|
||||
void spoofPoints();
|
||||
|
||||
/** updates the glyphs to reflect the given timestamp in the dataset.
|
||||
* @param t : the time at which to fetch the data.
|
||||
*/
|
||||
void updateData(int t) override;
|
||||
|
||||
vtkSmartPointer<SpawnPointCallback> createSpawnPointCallback();
|
||||
|
||||
void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
17
particle-track-and-trace/src/layers/Layer.cpp
Normal file
17
particle-track-and-trace/src/layers/Layer.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "Layer.h"
|
||||
#include <vtkRenderWindow.h>
|
||||
#include <vtkRenderWindowInteractor.h>
|
||||
|
||||
using std::string;
|
||||
|
||||
vtkSmartPointer<vtkRenderer> Layer::getLayer() {
|
||||
return this->ren;
|
||||
}
|
||||
|
||||
void Layer::updateData(int t) {
|
||||
// By default, do nothing
|
||||
}
|
||||
|
||||
void Layer::addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor) {
|
||||
// By default, do nothing
|
||||
}
|
||||
32
particle-track-and-trace/src/layers/Layer.h
Normal file
32
particle-track-and-trace/src/layers/Layer.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef LAYER_H
|
||||
#define LAYER_H
|
||||
|
||||
#include <vtkInteractorStyle.h>
|
||||
#include <vtkRenderer.h>
|
||||
|
||||
/** This class represents one abstract layer to be rendered to VTK.
|
||||
* It exists to manage multiple different layers under the Program class.
|
||||
*/
|
||||
class Layer {
|
||||
protected:
|
||||
vtkSmartPointer<vtkRenderer> ren;
|
||||
|
||||
public:
|
||||
/** gets the vtkRenderer to assign it to the vtkRenderWindow of the program class.
|
||||
* @return pointer to the vtkRenderer of this class.
|
||||
*/
|
||||
virtual vtkSmartPointer<vtkRenderer> getLayer();
|
||||
|
||||
|
||||
/** updates the data in the layer to reflect the given timestamp.
|
||||
* @param t : the timestamp which the data should reflect.
|
||||
*/
|
||||
virtual void updateData(int t);
|
||||
|
||||
/** Adds observers to the renderWindowinteractor within which this layer is active.
|
||||
* @param interactor : pointer to the interactor that observers can be added to.
|
||||
*/
|
||||
virtual void addObservers(vtkSmartPointer<vtkRenderWindowInteractor> interactor);
|
||||
};
|
||||
|
||||
#endif
|
||||
38
particle-track-and-trace/src/main.cpp
Normal file
38
particle-track-and-trace/src/main.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <netcdf>
|
||||
#include <vtkActor2D.h>
|
||||
#include <vtkNamedColors.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkPolyData.h>
|
||||
#include <vtkPolyDataMapper2D.h>
|
||||
#include <vtkProperty2D.h>
|
||||
#include <vtkRenderer.h>
|
||||
#include <vtkVertexGlyphFilter.h>
|
||||
#include <memory>
|
||||
|
||||
#include "layers/BackgroundImage.h"
|
||||
#include "layers/EGlyphLayer.h"
|
||||
#include "layers/LGlyphLayer.h"
|
||||
#include "Program.h"
|
||||
#include "advection/UVGrid.h"
|
||||
#include "advection/RK4AdvectionKernel.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DT 60 * 60 // 60 sec/min * 60 mins
|
||||
|
||||
int main() {
|
||||
shared_ptr<UVGrid> uvGrid = std::make_shared<UVGrid>();
|
||||
auto kernelRK4 = make_unique<RK4AdvectionKernel>(uvGrid);
|
||||
|
||||
auto l = new LGlyphLayer(uvGrid, std::move(kernelRK4));
|
||||
|
||||
Program *program = new Program(DT);
|
||||
program->addLayer(new BackgroundImage("../../../../data/map_661-661.png"));
|
||||
program->addLayer(new EGlyphLayer(uvGrid));
|
||||
program->addLayer(l);
|
||||
|
||||
program->render();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
1
particle-track-and-trace/src/modules.json
Normal file
1
particle-track-and-trace/src/modules.json
Normal file
File diff suppressed because one or more lines are too long
226
particle-track-and-trace/src/script.py
Executable file
226
particle-track-and-trace/src/script.py
Executable file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import collections
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_program_parameters(argv):
|
||||
import argparse
|
||||
description = 'Generate a find_package(VTK COMPONENTS ...) command for CMake.'
|
||||
epilogue = '''
|
||||
Uses modules.json and your source files to generate a
|
||||
find_package(VTK COMPONENTS ...) command listing all the vtk modules
|
||||
needed by the C++ source and header files in your code.
|
||||
|
||||
Paths for more than one source path can be specified.
|
||||
|
||||
Note than if there are spaces in the paths, enclose the path in quotes.
|
||||
|
||||
If it is unable to find modules for your headers then
|
||||
a list of these, along with the files they are in, is produced
|
||||
so you can manually add the corresponding modules or rebuild VTK
|
||||
to include the missing modules.
|
||||
|
||||
You will need to manually add any third-party modules
|
||||
(if used) to the find_package command.
|
||||
'''
|
||||
parser = argparse.ArgumentParser(description=description, epilog=epilogue,
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument('json', default=['modules.json'], help='The path to the VTK JSON file (modules.json).')
|
||||
parser.add_argument('sources', nargs='+', help='The path to the source files.')
|
||||
parser.add_argument('-f', '--file', help='The file name to write the output too.')
|
||||
args = parser.parse_args()
|
||||
return args.json, args.sources, args.file
|
||||
|
||||
|
||||
class Patterns:
|
||||
header_pattern = re.compile(r'^#include *[<\"](\S+)[>\"]')
|
||||
vtk_include_pattern = re.compile(r'^(vtk\S+)')
|
||||
vtk_qt_include_pattern = re.compile(r'^(QVTK\S+)')
|
||||
|
||||
|
||||
def get_headers_modules(json_data):
|
||||
"""
|
||||
From the parsed JSON data file make a dictionary whose key is the
|
||||
header filename and value is the module.
|
||||
:param json_data: The parsed JSON file modules.json.
|
||||
:return:
|
||||
"""
|
||||
|
||||
# The headers should be unique to a module, however we will not assume this.
|
||||
res = collections.defaultdict(set)
|
||||
for k, v in json_data['modules'].items():
|
||||
if 'headers' in v:
|
||||
for k1 in v['headers']:
|
||||
res[k1].add(k)
|
||||
return res
|
||||
|
||||
|
||||
def get_vtk_components(jpath, paths):
|
||||
"""
|
||||
Get the VTK components
|
||||
:param jpath: The path to the JSON file.
|
||||
:param paths: The C++ file paths.
|
||||
:return:
|
||||
"""
|
||||
|
||||
with open(jpath) as data_file:
|
||||
json_data = json.load(data_file)
|
||||
vtk_headers_modules = get_headers_modules(json_data)
|
||||
|
||||
modules = set()
|
||||
inc_no_mod = set()
|
||||
inc_no_mod_headers = collections.defaultdict(set)
|
||||
mod_implements = collections.defaultdict(set)
|
||||
headers = collections.defaultdict(set)
|
||||
|
||||
for path in paths:
|
||||
if path.is_file():
|
||||
content = path.read_text().split('\n')
|
||||
for line in content:
|
||||
m = Patterns.header_pattern.match(line.strip())
|
||||
if m:
|
||||
# We have a header name, split it from its path (if the path exists).
|
||||
header_parts = os.path.split(m.group(1))
|
||||
m = Patterns.vtk_include_pattern.match(header_parts[1])
|
||||
if m:
|
||||
headers[m.group(1)].add(path)
|
||||
continue
|
||||
m = Patterns.vtk_qt_include_pattern.match(header_parts[1])
|
||||
if m:
|
||||
headers[m.group(1)].add(path)
|
||||
for incl in headers:
|
||||
if incl in vtk_headers_modules:
|
||||
m = vtk_headers_modules[incl]
|
||||
for v in m:
|
||||
modules.add(v)
|
||||
else:
|
||||
inc_no_mod.add(incl)
|
||||
inc_no_mod_headers[incl] = headers[incl]
|
||||
|
||||
if headers:
|
||||
for m in modules:
|
||||
if not json_data['modules'][m]['implementable']:
|
||||
continue
|
||||
for i in json_data['modules']:
|
||||
if i in modules:
|
||||
continue
|
||||
if m in json_data['modules'][i]['implements']:
|
||||
# Suggest module i since it implements m
|
||||
mod_implements[i].add(m)
|
||||
|
||||
return modules, mod_implements, inc_no_mod, inc_no_mod_headers
|
||||
|
||||
|
||||
def disp_components(modules, module_implements):
|
||||
"""
|
||||
For the found modules display them in a form that the user can
|
||||
copy/paste into their CMakeLists.txt file.
|
||||
:param modules: The modules.
|
||||
:param module_implements: Modules implementing other modules.
|
||||
:return:
|
||||
"""
|
||||
res = ['find_package(VTK\n COMPONENTS']
|
||||
for m in sorted(modules):
|
||||
res.append(' {:s}'.format(m.split('::')[1]))
|
||||
if module_implements:
|
||||
keys = sorted(module_implements)
|
||||
max_width = len(max(keys, key=len).split('::')[1])
|
||||
comments = [
|
||||
' #',
|
||||
' # These modules are suggested since they implement an existing module.',
|
||||
' # You may need to uncomment one or more of these.',
|
||||
' # If vtkRenderWindow is used and you want to use OpenGL,',
|
||||
' # you also need the RenderingOpenGL2 module.',
|
||||
' # If vtkRenderWindowInteractor is used,',
|
||||
' # uncomment RenderingUI and possibly InteractionStyle.',
|
||||
' # If text rendering is used, uncomment RenderingFreeType',
|
||||
' #'
|
||||
]
|
||||
res.extend(comments)
|
||||
for key in keys:
|
||||
res.append(
|
||||
f' # {key.split("::")[1]:<{max_width}} # implements {", ".join(sorted(module_implements[key]))}')
|
||||
res.append(')\n')
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def disp_missing_components(inc_no_mod, inc_no_mod_headers):
|
||||
"""
|
||||
Display the headers along with the missing VTK modules.
|
||||
|
||||
:param inc_no_mod: Missing modules.
|
||||
:param inc_no_mod_headers: Headers with missing modules.
|
||||
:return:
|
||||
"""
|
||||
if inc_no_mod:
|
||||
res = [''
|
||||
'*' * 64,
|
||||
'You will need to manually add the modules that',
|
||||
' use these headers to the find_package command.',
|
||||
'These could be external modules not in the modules.json file.',
|
||||
'Or you may need to rebuild VTK to include the missing modules.',
|
||||
'',
|
||||
'Here are the vtk headers and corresponding files:']
|
||||
sinmd = sorted(inc_no_mod)
|
||||
for i in sinmd:
|
||||
sh = sorted(list(inc_no_mod_headers[i]))
|
||||
res.append(f'in {i}:')
|
||||
for j in sh:
|
||||
res.append(f' {j}')
|
||||
res.append('*' * 64)
|
||||
|
||||
return res
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def main(json_path, src_paths, ofn):
|
||||
jpath = Path(json_path)
|
||||
if jpath.is_dir():
|
||||
jpath = jpath / 'modules.json'
|
||||
if not jpath.is_file():
|
||||
print(f'Non existent JSON file: {jpath}')
|
||||
return
|
||||
|
||||
paths = list()
|
||||
valid_ext = ['.h', '.hxx', '.cxx', '.cpp', '.txx']
|
||||
path_list = list()
|
||||
for fn in src_paths:
|
||||
path = Path(fn)
|
||||
if path.is_file() and path.suffix in valid_ext:
|
||||
paths.append(path)
|
||||
elif path.is_dir():
|
||||
for e in valid_ext:
|
||||
path_list += list(Path(fn).rglob(f'*{e}'))
|
||||
program_path = Path(__file__)
|
||||
for path in path_list:
|
||||
if path.resolve() != program_path.resolve():
|
||||
paths.append(path)
|
||||
else:
|
||||
print(f'Non existent path: {path}')
|
||||
|
||||
modules, mod_implements, inc_no_mod, inc_no_mod_headers = get_vtk_components(jpath, paths)
|
||||
|
||||
res = '\n'.join(disp_components(modules, mod_implements))
|
||||
if inc_no_mod:
|
||||
res += '\n'.join(disp_missing_components(inc_no_mod, inc_no_mod_headers))
|
||||
|
||||
if ofn:
|
||||
path = Path(ofn)
|
||||
if path.suffix == '':
|
||||
path = Path(ofn).with_suffix('.txt')
|
||||
path.write_text(res)
|
||||
else:
|
||||
print(res)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
json_paths, src_paths, ofn = get_program_parameters(sys.argv)
|
||||
main(json_paths, src_paths, ofn)
|
||||
Reference in New Issue
Block a user