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 */
22const 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
36QWidget *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
54CatalogCSVImport::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
94CatalogCSVImport::~CatalogCSVImport()
95{
96 delete ui;
97}
98
99void 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
144void 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
153void 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
181void 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
196void 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
206void 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
218CatalogCSVImport::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
240void 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
265CatalogCSVImport::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
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
289 }
290
291 return map;
292};
293
294void 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
408SkyObject::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};
A simple container object to hold the minimum information for a Deep Sky Object to be drawn on the sk...
QString typeName() const
TYPE
The type classification of the SkyObject.
Definition skyobject.h:112
void Clear()
Clears loaded Document data.
Definition rapidcsv.h:499
std::vector< std::string > GetColumnNames()
Get column names.
Definition rapidcsv.h:1255
T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const
Get cell by index.
Definition rapidcsv.h:991
void Load(const std::string &pPath, const LabelParams &pLabelParams=LabelParams(), const SeparatorParams &pSeparatorParams=SeparatorParams(), const ConverterParams &pConverterParams=ConverterParams(), const LineReaderParams &pLineReaderParams=LineReaderParams())
Read Document data from file.
Definition rapidcsv.h:439
size_t GetRowCount() const
Get number of data rows (excluding label rows).
Definition rapidcsv.h:977
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
KGuiItem defaults()
QString label(StandardShortcut id)
QString name(StandardShortcut id)
void clicked(bool checked)
QString homePath()
bool exists() const const
Int toInt() const const
StandardButton warning(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
QString arg(Args &&... args) const const
QString first(qsizetype n) const const
AlignCenter
NoItemFlags
QTextStream & dec(QTextStream &stream)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setupUi(QWidget *widget)
Datastructure holding parameters controlling how invalid numbers (including empty strings) should be ...
Definition rapidcsv.h:41
Datastructure holding parameters controlling which row and column should be treated as labels.
Definition rapidcsv.h:263
Datastructure holding parameters controlling how special line formats should be treated.
Definition rapidcsv.h:346
Datastructure holding parameters controlling how the CSV data fields are separated.
Definition rapidcsv.h:293
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.