QGIS API Documentation
4.3.0-Master (9dce731a7a1)
Toggle main menu visibility
Loading...
Searching...
No Matches
src
core
qgsdatadefinedutils.h
Go to the documentation of this file.
1
/***************************************************************************
2
qgsdatadefinedutils.h
3
-----------------
4
begin : October 2026
5
copyright : (C) Nyall Dawson
6
email : nyall dot dawson at gmail dot com
7
***************************************************************************
8
* *
9
* This program is free software; you can redistribute it and/or modify *
10
* it under the terms of the GNU General Public License as published by *
11
* the Free Software Foundation; either version 2 of the License, or *
12
* (at your option) any later version. *
13
* *
14
***************************************************************************/
15
16
#ifndef QGSDATADEFINEDUTILS_H
17
#define QGSDATADEFINEDUTILS_H
18
19
#include <algorithm>
20
#include <array>
21
#include <optional>
22
23
#include "qgis_core.h"
24
25
#define SIP_NO_FILE
26
28
template
<
typename
V>
struct
DataDefinedDecoderEntry
29
{
30
QStringView first;
31
V second;
32
};
34
44
template
<
typename
V,
size_t
N>
class
QgsDataDefinedStringDecoder
45
{
46
public
:
50
constexpr
explicit
QgsDataDefinedStringDecoder
(
const
DataDefinedDecoderEntry<V> ( &values )[N] )
51
{
52
for
(
size_t
i = 0; i < N; ++i )
53
{
54
const
QStringView key = values[i].first;
55
56
// compile-time assumption checks
57
if
( key.isEmpty() )
58
throw
"QgsDataDefinedStringDecoder: keys cannot be empty"
;
59
60
if
( key[0].unicode() < u
'a'
|| key[0].unicode() > u
'z'
)
61
throw
"QgsDataDefinedStringDecoder: keys must start with a lowercase letter"
;
62
63
if
( key.size() > MAX_KEY_SIZE )
64
throw
"QgsDataDefinedStringDecoder: keys must be less than MAX_KEY_SIZE characters length"
;
65
66
for
(
int
j = 0; j < key.size(); ++j )
67
{
68
if
( key[j].unicode() >= u
'A'
&& key[j].unicode() <= u
'Z'
)
69
throw
"QgsDataDefinedStringDecoder: keys must be lowercase"
;
70
71
if
( shouldIgnoreCharacter( key[j].unicode() ) )
72
throw
"QgsDataDefinedStringDecoder: keys must not contain spaces, hyphens, or underscores"
;
73
}
74
75
mLookupTable[i] = values[i];
76
}
77
78
// sort entries alphabetically
79
std::sort( mLookupTable.begin(), mLookupTable.end(), [](
const
DataDefinedDecoderEntry<V> &a,
const
DataDefinedDecoderEntry<V> &b ) { return compareStringViews( a.first, b.first ) < 0; } );
80
81
// now our entries are sorted, lets make sure they are unique too...
82
for
(
size_t
i = 1; i < N; ++i )
83
{
84
if
( compareStringViews( mLookupTable[i - 1].first, mLookupTable[i].first ) == 0 )
85
throw
"QgsDataDefinedStringDecoder: keys must be unique"
;
86
}
87
88
// this function may be called MANY MANY thousands of times per map render, and for some data-defined
89
// enums there are many keys to lookup. This adds up! We can optimize this a bit by pre-allocating
90
// the keys into alphabetical partitions, so that we only have to compare candidate values to keys
91
// with matching first letters
92
for
(
size_t
i = 0; i < N; ++i )
93
{
94
// work out what partition this key falls into - the partition index is just the alphabetical index of the first letter
95
const
int
partitionIndex = getPartitionIndexForChar( mLookupTable[i].first[0].unicode() );
96
if
( partitionIndex < 0 )
97
throw
"Something went wrong -- partition index < 0"
;
98
if
( partitionIndex >= 26 )
99
throw
"Something went wrong -- partition index >= 26"
;
100
101
if
( mEntryPartitions[partitionIndex].count == 0 )
102
{
103
mEntryPartitions[partitionIndex].offset =
static_cast<
uint16_t
>
( i );
104
}
105
mEntryPartitions[partitionIndex].count++;
106
}
107
}
108
112
[[nodiscard]] std::optional<V>
lookup
( QStringView
string
)
const
113
{
114
if
(
string
.isEmpty() )
115
return
{};
116
117
// clean up the candidate string -- remove whitespace and other unwanted characters
118
std::array<char16_t, MAX_KEY_SIZE> cleanedString;
119
int
cleanedStringLength = 0;
120
for
( qsizetype i = 0; i <
string
.size(); ++i )
121
{
122
char16_t
unicode =
string
[i].unicode();
123
if
( shouldIgnoreCharacter( unicode ) )
124
continue
;
125
126
// strings with more than MAX_KEY_SIZE non-ignored characters cannot possibly match anything,
127
// cos we have compile-time assertion that all valid keys are < this length...
128
if
( cleanedStringLength >= MAX_KEY_SIZE )
129
return
{};
130
131
static
constexpr
int
UPPER_TO_LOWER_CHAR_OFFSET = 32;
132
if
( unicode >= u
'A'
&& unicode <= u
'Z'
)
133
unicode += UPPER_TO_LOWER_CHAR_OFFSET;
134
135
cleanedString[cleanedStringLength++] = unicode;
136
}
137
138
if
( cleanedStringLength == 0 )
139
return
{};
140
141
// find the corresponding partition
142
const
int
partitionIndex = getPartitionIndexForChar( cleanedString[0] );
143
if
( partitionIndex < 0 )
144
{
145
// doesn't start with an a-z character, so cannot possibly match any key (since we have
146
// compile-time requirement that keys start with a-z)
147
return
{};
148
}
149
150
const
Partition &partition = mEntryPartitions[partitionIndex];
151
if
( partition.count == 0 )
152
return
{};
// no keys match the start character of the string
153
154
const
DataDefinedDecoderEntry<V> *partitionData = mLookupTable.data() + partition.offset;
155
const
QStringView cleanedStringView( cleanedString.data(), cleanedStringLength );
156
for
( uint16_t i = 0; i < partition.count; ++i )
157
{
158
if
( partitionData[i].first.size() == cleanedStringLength && partitionData[i].first.compare( cleanedStringView ) == 0 )
159
{
160
return
partitionData[i].second;
161
}
162
}
163
164
return
{};
165
}
166
167
private
:
168
static
constexpr
int
MAX_KEY_SIZE = 32;
169
std::array<DataDefinedDecoderEntry<V>, N> mLookupTable {};
170
171
// we have one partition per a-z character, allowing us to jump immediately to the
172
// candidates with the matching first letter
173
struct
Partition
174
{
175
uint16_t offset = 0;
176
uint16_t count = 0;
177
};
178
std::array<Partition, 26> mEntryPartitions {};
179
180
// dang, can't use Qt's QStringView comparison methods, they aren't constexpr :(
181
static
constexpr
int
compareStringViews( QStringView a, QStringView b )
noexcept
182
{
183
for
( qsizetype i = 0; i < std::min( a.size(), b.size() ); ++i )
184
{
185
const
char16_t
aUnicode = a[i].unicode();
186
const
char16_t
bUnicode = b[i].unicode();
187
if
( aUnicode < bUnicode )
188
return
-1;
189
if
( aUnicode > bUnicode )
190
return
1;
191
}
192
if
( a.size() < b.size() )
193
return
-1;
194
if
( a.size() > b.size() )
195
return
1;
196
return
0;
197
}
198
199
static
constexpr
int
getPartitionIndexForChar(
char16_t
c
)
200
{
201
// work out what partition this character falls into - the partition index is just the alphabetical index of the character
202
if
(
c
>= u
'a'
&&
c
<= u
'z'
)
203
{
204
return
c
- u
'a'
;
205
}
206
else
if
(
c
>= u
'A'
&&
c
<= u
'Z'
)
207
{
208
return
c
- u
'A'
;
209
}
210
211
return
-1;
212
}
213
214
static
constexpr
bool
shouldIgnoreCharacter(
char16_t
unicode )
noexcept
215
{
216
// when matching strings to keys, these characters will be completely ignored.
217
// this gives us tolerant matching, so e.g any of "top-right", "top right", "top_right" will match to the "topright" key
218
return
unicode == u
' '
|| unicode == u
'-'
|| unicode == u
'_'
;
219
}
220
};
221
256
template
<
typename
V,
size_t
N>
constexpr
QgsDataDefinedStringDecoder<V, N>
makeDataDefinedStringDecoder
(
const
DataDefinedDecoderEntry<V> ( &values )[N] )
257
{
258
return
QgsDataDefinedStringDecoder<V, N>
( values );
259
}
260
261
#endif
// QGSDATADEFINEDUTILS_H
QgsDataDefinedStringDecoder
A template based class for efficiently decoding a string value to a corresponding enum value.
Definition
qgsdatadefinedutils.h:45
QgsDataDefinedStringDecoder::QgsDataDefinedStringDecoder
constexpr QgsDataDefinedStringDecoder(const DataDefinedDecoderEntry< V >(&values)[N])
Constructor for QgsDataDefinedStringDecoder.
Definition
qgsdatadefinedutils.h:50
QgsDataDefinedStringDecoder::lookup
std::optional< V > lookup(QStringView string) const
Looks up a string in the lookup table, returning the corresponding enum value if found.
Definition
qgsdatadefinedutils.h:112
c
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Definition
porting_processing.dox:1
makeDataDefinedStringDecoder
constexpr QgsDataDefinedStringDecoder< V, N > makeDataDefinedStringDecoder(const DataDefinedDecoderEntry< V >(&values)[N])
Creates a QgsDataDefinedStringDecoder from a hardcoded lookup table.
Definition
qgsdatadefinedutils.h:256
Generated on
for QGIS API Documentation by
1.17.0