Kstars

catalogcsvimport.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Valentin Boettcher <hiro at protagon.space; @hiro98:tchncs.de>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "catalogcsvimport.h"
8 #include "rapidcsv.h"
9 #include "ui_catalogcsvimport.h"
10 #include <QFileDialog>
11 #include <QMessageBox>
12 #include <QDebug>
13 #include <QLabel>
14 #include <QComboBox>
15 #include <QFormLayout>
16 
17 /**
18  * Maps the name of the field to a tuple [Tooltip, Unit, Can be ignored?]
19  *
20  * @todo Maybe centralize that in the catalogobject class.
21  */
22 const std::vector<std::pair<QString, std::tuple<QString, QString, bool>>> fields{
23  { "Type", { "", "", true } },
24  { "Right Ascension", { "", "", false } },
25  { "Declination", { "", "", false } },
26  { "Magnitude", { "", "", false } },
27  { "Name", { "", "", false } },
28  { "Long Name", { "", "", true } },
29  { "Identifier", { "", "", true } },
30  { "Major Axis", { "", "arcmin", true } },
31  { "Minor Axis", { "", "arcmin", true } },
32  { "Position Angle", { "", "°", true } },
33  { "Flux", { "", "", true } },
34 };
35 
36 QWidget *type_selector_widget()
37 {
38  auto *pWidget = new QWidget{}; // no parent as receiver takes posession
39  auto *pLayout = new QHBoxLayout(pWidget);
40  auto *pCombo = new QComboBox{};
41 
42  for (int i = 0; i < SkyObject::TYPE::NUMBER_OF_KNOWN_TYPES; i++)
43  {
44  pCombo->addItem(SkyObject::typeName(i), i);
45  }
46  pLayout->setAlignment(Qt::AlignCenter);
47  pLayout->setContentsMargins(0, 0, 0, 0);
48  pLayout->addWidget(pCombo);
49  pWidget->setLayout(pLayout);
50 
51  return pWidget;
52 }
53 
54 CatalogCSVImport::CatalogCSVImport(QWidget *parent)
55  : QDialog(parent), ui(new Ui::CatalogCSVImport)
56 {
57  ui->setupUi(this);
58  reset_mapping();
59 
60  ui->separator->setText(QString(default_separator));
61  ui->comment_prefix->setText(QString(default_comment));
62 
63  ui->preview->setModel(&m_preview_model);
64  ui->preview->horizontalHeader()->setSectionResizeMode(
65  QHeaderView::ResizeMode::Stretch);
66 
67  connect(ui->file_select_button, &QPushButton::clicked, this,
68  &CatalogCSVImport::select_file);
69 
70  connect(ui->add_map, &QPushButton::clicked, this,
71  &CatalogCSVImport::type_table_add_map);
72 
73  connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
74  &CatalogCSVImport::read_objects);
75 
76  connect(ui->remove_map, &QPushButton::clicked, this,
77  &CatalogCSVImport::type_table_remove_map);
78 
79  connect(ui->preview_button, &QPushButton::clicked, this, [&]() {
80  read_n_objects(default_preview_size);
81  m_preview_model.setObjects(m_objects);
82  });
83 
84  init_column_mapping();
85  init_type_table();
86 
87  for (auto *box : { ui->ra_units, ui->dec_units })
88  {
89  box->addItem(i18n("Degrees"));
90  box->addItem(i18n("Hours"));
91  }
92 }
93 
94 CatalogCSVImport::~CatalogCSVImport()
95 {
96  delete ui;
97 }
98 
99 void CatalogCSVImport::select_file()
100 {
101  QFileDialog dialog(this, i18nc("@title:window", "Import Catalog"), QDir::homePath(),
102  QString("CSV") + i18n("File") + QString(" (*.csv);;") +
103  i18n("Any File") + QString(" (*);;"));
104  dialog.setAcceptMode(QFileDialog::AcceptOpen);
105  dialog.setDefaultSuffix("csv");
106 
107  if (dialog.exec() != QDialog::Accepted)
108  return;
109 
110  const auto &fileName = dialog.selectedUrls().value(0).toLocalFile();
111 
112  if (!QFile::exists(fileName))
113  {
114  reset_mapping();
115  QMessageBox::warning(this, i18n("Warning"),
116  i18n("Could not open the csv file.<br>It does not exist."));
117 
118  return;
119  }
120 
121  if (ui->separator->text().length() < 1)
122  {
123  ui->separator->setText(QString(default_separator));
124  }
125 
126  if (ui->comment_prefix->text().length() < 1)
127  {
128  ui->separator->setText(QString(default_separator));
129  }
130 
131  const auto &separator = ui->separator->text();
132  const auto &comment_prefix = ui->comment_prefix->text();
133 
134  ui->file_path_label->setText(fileName);
135 
136  m_doc.Load(fileName.toStdString(), rapidcsv::LabelParams(),
137  rapidcsv::SeparatorParams(separator[0].toLatin1(), true),
139  rapidcsv::LineReaderParams(true, comment_prefix[0].toLatin1()));
140 
141  init_mapping_selectors();
142 };
143 
144 void CatalogCSVImport::reset_mapping()
145 {
146  ui->column_mapping->setEnabled(false);
147  ui->preview_button->setEnabled(false);
148  ui->obj_count->setEnabled(false);
149  m_doc.Clear();
150  ui->buttonBox->buttons()[0]->setEnabled(false);
151 };
152 
153 void CatalogCSVImport::init_mapping_selectors()
154 {
155  const auto &columns = m_doc.GetColumnNames();
156 
157  for (const auto &field : fields)
158  {
159  const auto can_be_ignored = std::get<2>(field.second);
160  auto *selector = m_selectors[field.first];
161 
162  selector->clear();
163 
164  selector->setMaxCount(columns.size() + (can_be_ignored ? 0 : 1));
165 
166  if (can_be_ignored)
167  selector->addItem(i18n("Ignore"), -2);
168 
169  int i = 0;
170  for (const auto &col : columns)
171  selector->addItem(col.c_str(), i++);
172  }
173 
174  ui->column_mapping->setEnabled(true);
175  ui->buttonBox->buttons()[0]->setEnabled(true);
176  ui->obj_count->setEnabled(true);
177  ui->preview_button->setEnabled(true);
178  ui->obj_count->setText(i18np("%1 Object", "%1 Objects", m_doc.GetRowCount()));
179 };
180 
181 void CatalogCSVImport::init_type_table()
182 {
183  auto *const table = ui->type_table;
184  table->setHorizontalHeaderLabels(QStringList() << i18n("Text") << i18n("Type"));
185 
186  table->setColumnCount(2);
187  table->setRowCount(1);
188  auto *item = new QTableWidgetItem{ i18n("default") };
189  item->setFlags(Qt::NoItemFlags);
190  table->setItem(0, 0, item);
191 
192  table->setCellWidget(0, 1, type_selector_widget());
193  table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch);
194 };
195 
196 void CatalogCSVImport::type_table_add_map()
197 {
198  auto *const table = ui->type_table;
199  const auto cur_row = table->rowCount();
200 
201  table->setRowCount(cur_row + 1);
202  table->setItem(cur_row, 0, new QTableWidgetItem{ "" });
203  table->setCellWidget(cur_row, 1, type_selector_widget());
204 };
205 
206 void CatalogCSVImport::type_table_remove_map()
207 {
208  auto *const table = ui->type_table;
209 
210  for (const auto *item : table->selectedItems())
211  {
212  const auto row = item->row();
213  if (row > 0)
214  table->removeRow(row);
215  }
216 };
217 
218 CatalogCSVImport::type_map CatalogCSVImport::get_type_mapping()
219 {
220  auto *const table = ui->type_table;
221  type_map map{};
222 
223  for (int i = 0; i <= table->rowCount(); i++)
224  {
225  const auto *key = table->item(i, 0);
226  const auto *type = table->cellWidget(i, 1);
227 
228  if (key == nullptr || type == nullptr)
229  continue;
230 
231  map[key->text().toStdString()] = static_cast<SkyObject::TYPE>(
232  dynamic_cast<QComboBox *>(type->layout()->itemAt(0)->widget())
233  ->currentData()
234  .toInt());
235  }
236 
237  return map;
238 };
239 
240 void CatalogCSVImport::init_column_mapping()
241 {
242  auto *cmapping = new QFormLayout();
243  for (const auto &field : fields)
244  {
245  auto name = field.first;
246  const auto &tooltip = std::get<0>(field.second);
247  const auto &unit = std::get<1>(field.second);
248 
249  if (unit.length() > 0)
250  name += QString(" [%1]").arg(unit);
251 
252  auto *label = new QLabel(name);
253  label->setToolTip(tooltip);
254 
255  auto *selector = new QComboBox();
256  selector->setEditable(true);
257  m_selectors[field.first] = selector;
258 
259  cmapping->addRow(label, selector);
260  }
261 
262  ui->column_mapping->setLayout(cmapping);
263 };
264 
265 CatalogCSVImport::column_map CatalogCSVImport::get_column_mapping()
266 {
267  CatalogCSVImport::column_map map{};
268  const auto &names = m_doc.GetColumnNames();
269 
270  for (const auto &item : m_selectors)
271  {
272  const auto &name = item.first;
273  const auto *selector = item.second;
274  auto selected_value = selector->currentData().toInt();
275 
276  QString val_string;
277  if (selected_value >= 0 &&
278  (selector->currentText() != names[selected_value].c_str()))
279  {
280  selected_value = -1;
281  val_string = selector->currentText();
282  }
283  else
284  {
285  val_string = "";
286  }
287 
288  map[name] = { selected_value, val_string };
289  }
290 
291  return map;
292 };
293 
294 void CatalogCSVImport::read_n_objects(size_t n)
295 {
296  const auto &type_map = get_type_mapping();
297  const auto &column_map = get_column_mapping();
298  const CatalogObject defaults{};
299 
300  m_objects.clear();
301  m_objects.reserve(std::min(m_doc.GetRowCount(), n));
302 
303  // pure magic, it's like LISP macros
304  const auto make_getter = [this, &column_map](const QString &field, auto def) {
305  const auto &conf = column_map.at(field);
306  const auto &default_val = get_default(conf, def);
307  const auto index = conf.first;
308 
309  std::function<decltype(def)(const size_t)> getter;
310  if (conf.first >= 0)
311  getter = [=](const size_t row) {
312  try
313  {
314  return m_doc.GetCell<decltype(def)>(index, row);
315  }
316  catch (...)
317  {
318  return default_val;
319  };
320  };
321  else
322  getter = [=](const size_t) { return default_val; };
323 
324  return getter;
325  };
326 
327  const auto make_coord_getter = [this, &column_map](const QString &field, auto def,
328  coord_unit unit) {
329  const auto &conf = column_map.at(field);
330  const auto default_val =
331  (unit == coord_unit::deg) ?
332  get_default<typed_dms<coord_unit::deg>>(column_map.at(field), { def })
333  .data :
334  get_default<typed_dms<coord_unit::hours>>(column_map.at(field), { def })
335  .data;
336  const auto index = conf.first;
337 
338  std::function<decltype(def)(const size_t)> getter;
339  if (conf.first >= 0)
340  {
341  if (unit == coord_unit::deg)
342  getter = [=](const size_t row) {
343  try
344  {
345  return m_doc.GetCell<typed_dms<coord_unit::deg>>(index, row).data;
346  }
347  catch (...)
348  {
349  return default_val;
350  };
351  };
352  else
353  getter = [=](const size_t row) {
354  try
355  {
356  return m_doc.GetCell<typed_dms<coord_unit::hours>>(index, row)
357  .data;
358  }
359  catch (...)
360  {
361  return default_val;
362  };
363  };
364  }
365  else
366  getter = [=](const size_t) { return default_val; };
367 
368  return getter;
369  };
370 
371  const auto ra_type = static_cast<coord_unit>(ui->ra_units->currentIndex());
372  const auto dec_type = static_cast<coord_unit>(ui->dec_units->currentIndex());
373 
374  const auto get_ra = make_coord_getter("Right Ascension", defaults.ra(), ra_type);
375  const auto get_dec = make_coord_getter("Declination", defaults.dec(), dec_type);
376  const auto get_mag = make_getter("Magnitude", defaults.mag());
377  const auto get_name = make_getter("Name", defaults.name());
378  const auto get_type = make_getter("Type", std::string{ "default" });
379  const auto get_long_name = make_getter("Long Name", defaults.name());
380  const auto get_identifier = make_getter("Identifier", defaults.catalogIdentifier());
381  const auto get_a = make_getter("Major Axis", defaults.a());
382  const auto get_b = make_getter("Minor Axis", defaults.b());
383  const auto get_pa = make_getter("Position Angle", defaults.pa());
384  const auto get_flux = make_getter("Flux", defaults.flux());
385 
386  for (size_t i = 0; i < std::min(m_doc.GetRowCount(), n); i++)
387  {
388  const auto &raw_type = get_type(i);
389 
390  const auto type = parse_type(raw_type, type_map);
391 
392  const auto ra = get_ra(i);
393  const auto dec = get_dec(i);
394  const auto mag = get_mag(i);
395  const auto name = get_name(i);
396  const auto long_name = get_long_name(i);
397  const auto identifier = get_identifier(i);
398  const auto a = get_a(i);
399  const auto b = get_b(i);
400  const auto pa = get_pa(i);
401  const auto flux = get_flux(i);
402 
403  m_objects.emplace_back(CatalogObject::oid{}, type, ra, dec, mag, name, long_name,
404  identifier, -1, a, b, pa, flux);
405  }
406 };
407 
408 SkyObject::TYPE CatalogCSVImport::parse_type(const std::string &type,
409  const type_map &type_map)
410 {
411  if (type_map.count(type) == 0)
412  return type_map.at("default");
413 
414  return type_map.at(type);
415 };
AlignCenter
Datastructure holding parameters controlling how special line formats should be treated.
Definition: rapidcsv.h:345
Type type(const QSqlDatabase &db)
void clicked(bool checked)
QString homePath()
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
bool exists() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
NoItemFlags
QString i18n(const char *text, const TYPE &arg...)
Datastructure holding parameters controlling which row and column should be treated as labels.
Definition: rapidcsv.h:262
int toInt(bool *ok, int base) const const
QTextStream & dec(QTextStream &stream)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString label(StandardShortcut id)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString name(StandardShortcut id)
KGuiItem defaults()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void addItem(const QString &text, const QVariant &userData)
Datastructure holding parameters controlling how the CSV data fields are separated.
Definition: rapidcsv.h:292
Datastructure holding parameters controlling how invalid numbers (including empty strings) should be ...
Definition: rapidcsv.h:40
QFuture< void > map(Sequence &sequence, MapFunctor function)
A simple container object to hold the minimum information for a Deeb Sky Object to be drawn on the sk...
Definition: catalogobject.h:40
QString typeName() const
Definition: skyobject.cpp:389
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 19 2022 03:57:49 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.