Skip to content

Commit 0c9f1cd

Browse files
dl3sdolpechacek
authored andcommitted
MapInformation: Dialog and report about map properties (OpenOrienteering#2125)
Co-authored-by: Kai Pastor <[email protected]> [lpechacek] Port to LibreMapper.
1 parent c15c9ad commit 0c9f1cd

File tree

12 files changed

+576
-6
lines changed

12 files changed

+576
-6
lines changed

code-check-wrapper.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ for I in \
5959
map_editor.cpp \
6060
map_find_feature.cpp \
6161
map_notes.cpp \
62+
map_information.cpp \
63+
map_information_dialog.cpp \
6264
map_printer \
6365
map_widget.cpp \
6466
mapper_proxystyle.cpp \

images/map-information.png

501 Bytes
Loading

resources.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,6 @@
117117
<file>images/view-zoom-out.png</file>
118118
<file>images/window-new.png</file>
119119
<file>images/mapper-icon/Mapper-128.png</file>
120+
<file>images/map-information.png</file>
120121
</qresource>
121122
</RCC>

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ set(Mapper_Common_SRCS
5353
core/map_color.cpp
5454
core/map_coord.cpp
5555
core/map_grid.cpp
56+
core/map_information.cpp
5657
core/map_part.cpp
5758
core/map_printer.cpp
5859
core/map_view.cpp
@@ -125,6 +126,7 @@ set(Mapper_Common_SRCS
125126
gui/map/map_editor_activity.cpp
126127
gui/map/map_find_feature.cpp
127128
gui/map/map_notes.cpp
129+
gui/map/map_information_dialog.cpp
128130
gui/map/map_widget.cpp
129131
gui/map/rotate_map_dialog.cpp
130132
gui/map/stretch_map_dialog.cpp

src/core/map_information.cpp

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later
2+
*
3+
* Copyright 2024 Kai Pastor (OpenOrienteering)
4+
* Copyright 2024 Matthias Kühlewein (OpenOrienteering)
5+
*
6+
* This file is part of LibreMapper.
7+
*/
8+
9+
#include "map_information.h"
10+
11+
#include <algorithm>
12+
#include <iterator>
13+
// IWYU pragma: no_include <memory>
14+
#include <numeric>
15+
#include <vector>
16+
17+
#include <QtGlobal>
18+
#include <QChar>
19+
#include <QCoreApplication>
20+
#include <QFontInfo>
21+
#include <QLatin1Char>
22+
#include <QLatin1String>
23+
24+
#include "core/georeferencing.h"
25+
#include "core/map.h"
26+
#include "core/map_color.h" // IWYU pragma: keep
27+
#include "core/map_part.h"
28+
#include "core/objects/object.h"
29+
#include "core/symbols/symbol.h"
30+
#include "core/symbols/text_symbol.h"
31+
#include "undo/undo_manager.h"
32+
33+
34+
namespace LibreMapper {
35+
36+
class MapInformationBuilder
37+
{
38+
public:
39+
explicit MapInformationBuilder(const Map& map);
40+
41+
/**
42+
* Creates the textual information from the stored information and
43+
* puts it in the tree widget.
44+
*/
45+
void buildTree(std::vector<MapInformation::TreeItem>& tree_items) const;
46+
47+
private:
48+
struct MapPartUsage {
49+
QString name;
50+
int object_count = 0;
51+
};
52+
53+
struct FontUsage {
54+
QString name;
55+
QString name_substitute; // the substituted font name, can be equal to 'name'
56+
int symbol_count = 0;
57+
};
58+
59+
struct SymbolUsage {
60+
const Symbol* symbol;
61+
QString name;
62+
int object_count = 0;
63+
std::vector<QString> colors = {};
64+
};
65+
66+
struct SymbolTypeUsage {
67+
QString name;
68+
int object_count = 0;
69+
std::vector<SymbolUsage> symbols = {};
70+
};
71+
72+
struct ColorUsage {
73+
QString name;
74+
std::vector<QString> symbols = {};
75+
};
76+
77+
QString crs;
78+
int scale;
79+
int undo_steps_count;
80+
int redo_steps_count;
81+
int templates_count;
82+
int symbols_count;
83+
int objects_count;
84+
int fonts_count;
85+
86+
std::vector<MapPartUsage> map_parts;
87+
88+
SymbolTypeUsage symbol_types[6];
89+
90+
std::vector<ColorUsage> colors;
91+
92+
std::vector<FontUsage> fonts;
93+
94+
/**
95+
* Returns the usage record for a given symbol type.
96+
*/
97+
SymbolTypeUsage& getSymbolTypeUsage(Symbol::Type type);
98+
};
99+
100+
101+
102+
MapInformationBuilder::SymbolTypeUsage& MapInformationBuilder::getSymbolTypeUsage(Symbol::Type type)
103+
{
104+
switch (type)
105+
{
106+
case Symbol::Point:
107+
return symbol_types[0];
108+
case Symbol::Line:
109+
return symbol_types[1];
110+
case Symbol::Area:
111+
return symbol_types[2];
112+
case Symbol::Combined:
113+
return symbol_types[3];
114+
case Symbol::Text:
115+
return symbol_types[4];
116+
default:
117+
return symbol_types[5];
118+
}
119+
}
120+
121+
// retrieve and store the information
122+
MapInformationBuilder::MapInformationBuilder(const Map& map)
123+
{
124+
scale = int(map.getScaleDenominator());
125+
126+
const auto& map_crs = map.getGeoreferencing().getProjectedCRSId();
127+
crs = map.getGeoreferencing().getProjectedCRSName();
128+
if (map_crs.isEmpty() && map.getGeoreferencing().getState() == Georeferencing::State::Geospatial)
129+
{
130+
crs = ::LibreMapper::Georeferencing::tr("Custom PROJ.4");
131+
}
132+
else if (map_crs != QLatin1String("Local") && map_crs != QLatin1String("PROJ.4"))
133+
{
134+
const auto& projected_crs_parameters = map.getGeoreferencing().getProjectedCRSParameters();
135+
if (!projected_crs_parameters.empty())
136+
{
137+
QString crs_details = QLatin1String(" (") + (map_crs == QLatin1String("EPSG") ? QCoreApplication::translate("LibreMapper::MapInformation", "code") : QCoreApplication::translate("LibreMapper::MapInformation", "zone")) + QChar::Space + projected_crs_parameters.front() + QLatin1Char(')');
138+
crs += crs_details;
139+
}
140+
}
141+
142+
templates_count = map.getNumTemplates();
143+
144+
colors.reserve(map.getNumColorPrios());
145+
map.applyOnAllColors([this, &map](const auto* color){
146+
colors.push_back({color->getName()});
147+
148+
map.applyOnMatchingSymbols(
149+
[this](const Symbol* symbol) { colors.back().symbols.push_back(symbol->getNumberAndPlainTextName()); },
150+
[color](const Symbol* symbol) { return symbol->containsColor(color); }
151+
);
152+
});
153+
154+
getSymbolTypeUsage(Symbol::Point).name = QCoreApplication::translate("LibreMapper::MapInformation", "Point symbols");
155+
getSymbolTypeUsage(Symbol::Line).name = QCoreApplication::translate("LibreMapper::MapInformation", "Line symbols");
156+
getSymbolTypeUsage(Symbol::Area).name = QCoreApplication::translate("LibreMapper::MapInformation", "Area symbols");
157+
getSymbolTypeUsage(Symbol::Combined).name = QCoreApplication::translate("LibreMapper::MapInformation", "Combined symbols");
158+
getSymbolTypeUsage(Symbol::Text).name = QCoreApplication::translate("LibreMapper::MapInformation", "Text symbols");
159+
getSymbolTypeUsage(Symbol::NoSymbol).name = QCoreApplication::translate("LibreMapper::MapInformation", "Undefined symbols");
160+
161+
symbols_count = map.getNumSymbols();
162+
map.applyOnAllSymbols([this, &map](const Symbol* symbol){
163+
auto& category = getSymbolTypeUsage(symbol->getType());
164+
category.symbols.push_back({symbol, symbol->getNumberAndPlainTextName()});
165+
166+
auto& colors = category.symbols.back().colors;
167+
colors.reserve(4);
168+
map.applyOnMatchingColors(
169+
[&colors](const MapColor* color) { colors.push_back(color->getName()); },
170+
[symbol](const MapColor* color) { return symbol->containsColor(color); }
171+
);
172+
});
173+
174+
auto category_text = getSymbolTypeUsage(Symbol::Text);
175+
fonts.reserve(category_text.symbols.size());
176+
for (const auto& item : category_text.symbols)
177+
{
178+
Q_ASSERT(item.symbol->getType() == Symbol::Text);
179+
const auto* text_symbol = item.symbol->asText();
180+
const auto& font_family = text_symbol->getFontFamily();
181+
auto font_family_substituted = QFontInfo(text_symbol->getQFont()).family();
182+
auto& font_names = fonts;
183+
auto found = std::find_if(begin(font_names), end(font_names), [&font_family](const FontUsage& font_count) { return font_count.name == font_family; });
184+
if (found == std::end(font_names))
185+
font_names.push_back({font_family, font_family_substituted, 1});
186+
else
187+
++found->symbol_count;
188+
}
189+
fonts_count = static_cast<int>(fonts.size());
190+
191+
map_parts.reserve(map.getNumParts());
192+
objects_count = 0;
193+
for (int i = 0; i < map.getNumParts(); ++i)
194+
{
195+
const auto* map_part = map.getPart(i);
196+
const auto map_part_objects = map_part->getNumObjects();
197+
objects_count += map_part_objects;
198+
map_parts.push_back({map_part->getName(), map_part_objects});
199+
200+
map_part->applyOnAllObjects([this](const Object* object) {
201+
const auto* const symbol = object->getSymbol();
202+
auto& object_category = getSymbolTypeUsage(symbol ? symbol->getType() : Symbol::NoSymbol);
203+
object_category.object_count++;
204+
auto s = std::find_if(object_category.symbols.begin(), object_category.symbols.end(), [symbol](const auto& s) { return symbol == s.symbol; } );
205+
if (s == object_category.symbols.end())
206+
s = object_category.symbols.insert(object_category.symbols.end(), {symbol, QCoreApplication::translate("LibreMapper::MapInformation", "<undefined>")});
207+
s->object_count++;
208+
});
209+
}
210+
211+
const auto& undo_manager = map.undoManager();
212+
undo_steps_count = undo_manager.canUndo() ? undo_manager.undoStepCount() : 0;
213+
redo_steps_count = undo_manager.canRedo() ? undo_manager.redoStepCount() : 0;
214+
}
215+
216+
void MapInformationBuilder::buildTree(std::vector<MapInformation::TreeItem>& tree_items) const
217+
{
218+
tree_items.clear();
219+
220+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Map"), QCoreApplication::translate("LibreMapper::MapInformation", "%n object(s)", nullptr, objects_count)});
221+
{
222+
tree_items.push_back({1, QCoreApplication::translate("LibreMapper::MapInformation", "Scale"), QString::fromLatin1("1:%1").arg(scale)});
223+
tree_items.push_back({1, QCoreApplication::translate("LibreMapper::MapInformation", "Coordinate reference system"), crs});
224+
if (undo_steps_count)
225+
tree_items.push_back({1, QCoreApplication::translate("LibreMapper::MapInformation", "Undo steps"), QCoreApplication::translate("LibreMapper::MapInformation", "%n step(s)", nullptr, undo_steps_count)});
226+
if (redo_steps_count)
227+
tree_items.push_back({1, QCoreApplication::translate("LibreMapper::MapInformation", "Redo steps"), QCoreApplication::translate("LibreMapper::MapInformation", "%n step(s)", nullptr, redo_steps_count)});
228+
}
229+
230+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Templates"), QCoreApplication::translate("LibreMapper::MapInformation", "%n template(s)", nullptr, templates_count)});
231+
232+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Map parts"), QCoreApplication::translate("LibreMapper::MapInformation", "%n part(s)", nullptr, map_parts.size())});
233+
for (const auto& map_part : map_parts)
234+
{
235+
tree_items.push_back({1, map_part.name, QCoreApplication::translate("LibreMapper::MapInformation", "%n object(s)", nullptr, map_part.object_count)});
236+
}
237+
238+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Symbols"), QCoreApplication::translate("LibreMapper::MapInformation", "%n symbol(s)", nullptr, symbols_count)});
239+
for (const auto& map_object : symbol_types)
240+
{
241+
if (map_object.object_count == 0 && map_object.name == QCoreApplication::translate("LibreMapper::MapInformation", "Undefined symbols"))
242+
continue;
243+
tree_items.push_back({1, map_object.name, QCoreApplication::translate("LibreMapper::MapInformation", "%n object(s)", nullptr, map_object.object_count)});
244+
for (const auto& symbol : map_object.symbols)
245+
{
246+
tree_items.push_back({2, symbol.name, QCoreApplication::translate("LibreMapper::MapInformation", "%n object(s)", nullptr, symbol.object_count)});
247+
for (const auto& color : symbol.colors)
248+
{
249+
tree_items.push_back({3, color});
250+
}
251+
}
252+
}
253+
254+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Colors"), QCoreApplication::translate("LibreMapper::MapInformation", "%n color(s)", nullptr, colors.size())});
255+
for (const auto& color : colors)
256+
{
257+
tree_items.push_back({1, color.name});
258+
for (const auto& symbol : color.symbols)
259+
tree_items.push_back({2, symbol});
260+
}
261+
262+
tree_items.push_back({0, QCoreApplication::translate("LibreMapper::MapInformation", "Fonts"), QCoreApplication::translate("LibreMapper::MapInformation", "%n font(s)", nullptr, fonts_count)});
263+
for (const auto& font_name : fonts)
264+
{
265+
auto name = font_name.name;
266+
if (name != font_name.name_substitute)
267+
name = QCoreApplication::translate("LibreMapper::MapInformation", "%1 (substituted by %2)").arg(name, font_name.name_substitute);
268+
tree_items.push_back({1, name, QCoreApplication::translate("LibreMapper::MapInformation", "%n symbol(s)", nullptr, font_name.symbol_count)});
269+
}
270+
}
271+
272+
273+
MapInformation::MapInformation(const Map* map)
274+
{
275+
if (map)
276+
MapInformationBuilder(*map).buildTree(tree_items);
277+
}
278+
279+
QString MapInformation::makeTextReport(int indent) const
280+
{
281+
QString text_report;
282+
283+
auto actual_indent = [indent](int level) {
284+
return level * indent;
285+
};
286+
auto max_item_length = std::accumulate(tree_items.begin(), tree_items.end(), 0, [actual_indent](auto acc, const auto& item) {
287+
return std::max<int>(acc, actual_indent(item.level) + item.label.length());
288+
});
289+
290+
for (const auto &tree_item : tree_items)
291+
{
292+
if (!text_report.isEmpty() && !tree_item.level) // separate items on topmost level
293+
text_report += QChar::LineFeed;
294+
auto item_value = QString(actual_indent(tree_item.level), QChar::Space);
295+
item_value.append(tree_item.label);
296+
if (!tree_item.value.isEmpty())
297+
{
298+
item_value = item_value.leftJustified(max_item_length + 5, QChar::Space);
299+
item_value.append(tree_item.value);
300+
}
301+
text_report += item_value + QChar::LineFeed;
302+
}
303+
return text_report;
304+
}
305+
306+
307+
} // namespace LibreMapper

src/core/map_information.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later
2+
*
3+
* Copyright 2024 Kai Pastor (OpenOrienteering)
4+
* Copyright 2024 Matthias Kühlewein (OpenOrienteering)
5+
*
6+
* This file is part of LibreMapper.
7+
*/
8+
9+
#ifndef LIBREMAPPER_MAP_INFORMATION_H
10+
#define LIBREMAPPER_MAP_INFORMATION_H
11+
12+
#include <vector>
13+
14+
#include <QString>
15+
16+
17+
namespace LibreMapper {
18+
19+
class Map;
20+
21+
class MapInformation
22+
{
23+
public:
24+
struct TreeItem {
25+
int level; ///< Depth in the hierarchy
26+
QString label; ///< Display label
27+
QString value = {}; ///< Auxiliary value
28+
};
29+
30+
/**
31+
* Constructs the map information object.
32+
*/
33+
explicit MapInformation(const Map* map);
34+
35+
/**
36+
* A sequence which defines a hierarchy of map information in text form.
37+
*/
38+
const std::vector<TreeItem>& treeItems() const { return tree_items; }
39+
40+
/**
41+
* Create a text report.
42+
*/
43+
QString makeTextReport(int indent = 2) const;
44+
45+
private:
46+
std::vector<TreeItem> tree_items;
47+
};
48+
49+
} // namespace LibreMapper
50+
51+
#endif // LIBREMAPPER_MAP_INFORMATION_H

0 commit comments

Comments
 (0)