[Git][debian-gis-team/mapnik][master] 6 commits: New upstream version 4.3.1+ds
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Fri Aug 28 16:01:32 BST 2026
Bas Couwenberg pushed to branch master at Debian GIS Project / mapnik
Commits:
407b4b9e by Bas Couwenberg at 2026-08-28T14:15:03+02:00
New upstream version 4.3.1+ds
- - - - -
a3df76a2 by Bas Couwenberg at 2026-08-28T14:15:07+02:00
Update upstream source from tag 'upstream/4.3.1+ds'
Update to upstream version '4.3.1+ds'
with Debian dir 4a20206f56f119037bca32385efb190bd9bc1b90
- - - - -
00f2a61f by Bas Couwenberg at 2026-08-28T14:15:21+02:00
New upstream release.
- - - - -
8e038874 by Bas Couwenberg at 2026-08-28T14:18:50+02:00
Refresh patches.
- - - - -
e1a436e3 by Bas Couwenberg at 2026-08-28T14:19:03+02:00
Set distribution to unstable.
- - - - -
9e52e51b by Bas Couwenberg at 2026-08-28T14:37:45+02:00
Don't use NDEBUG, already set via BUILD_TYPE.
- - - - -
18 changed files:
- CHANGELOG.md
- CMakeLists.txt
- debian/changelog
- debian/patches/boost1.89.patch
- debian/patches/repack.patch
- debian/rules
- include/mapnik/expression_evaluator.hpp
- include/mapnik/expression_node_types.hpp
- include/mapnik/feature.hpp
- include/mapnik/feature_style_processor_impl.hpp
- include/mapnik/rule_cache.hpp
- include/mapnik/text/harfbuzz_shaper.hpp
- include/mapnik/util/sort_by.hpp
- include/mapnik/version.hpp
- plugins/input/postgis/postgis_featureset.cpp
- test/unit/core/expressions_test.cpp
- test/unit/renderer/feature_style_processor.cpp
- test/unit/text/shaping.cpp
Changes:
=====================================
CHANGELOG.md
=====================================
@@ -6,6 +6,23 @@ Developers: Please commit along with changes.
For a complete change history, see the git log.
+## Mapnik 4.3.1
+
+Released August 28th, 2026
+
+(Packaged from [38c207ee0](https://github.com/mapnik/mapnik/commit/38c207ee0))
+
+- CMake build - enable NDEBUG by default
+- Avoid copying expression attributes e1e6d31808877171d14c74d5974b923cce423595
+- Compare string literals directly d0ed0964ad9d6c23cee6171656b002d1304fff40
+- Hash feature attribute lookups d744e094b451a12b070173547f638b896d1386d8
+- Prioritize common expression nodes e981e4b43ed57aa209175626777735036f97cc88
+- Evaluate filters as booleans d59329887dc708612a670117e562070f15634b63
+- Index rules by filter value 508dd835252b5a523bffef076b6779b0c21e4588
+- Store PostGIS attributes by index 40c36ee0125a7056af617f607bda61e9eb21bf08
+- Correction for "Rendering complex Unicode sequences across multiple fonts in a fontset" PR #4574
+- Upgrade SCons to v4.11.1
+
## Mapnik 4.3.0
Released July 24th, 2026
=====================================
CMakeLists.txt
=====================================
@@ -57,6 +57,7 @@ mapnik_option(USE_MULTITHREADED "enables the multithreaded features (threadsafe)
mapnik_option(USE_NO_ATEXIT "disable atexit" OFF)
mapnik_option(USE_NO_DLCLOSE "disable dlclose" OFF)
mapnik_option(USE_DEBUG_OUTPUT "enables some debug messages for development" OFF)
+mapnik_option(USE_NDEBUG "defines NDEBUG for non-Debug builds. Turning this off makes mapbox::variant dispatch non-inlinable and much slower" ON)
mapnik_option(USE_LOG "enables logging output. See log severity level." OFF)
# 0 = debug
# 1 = warn
@@ -295,6 +296,22 @@ if(USE_DEBUG_OUTPUT)
list(APPEND MAPNIK_COMPILE_DEFS MAPNIK_DEBUG)
endif()
+# NDEBUG does more for mapnik than disable assert(): mapbox::variant tags every
+# visit()/dispatcher::apply()/recursive_wrapper::get() with VARIANT_INLINE, which expands to
+# __attribute__((noinline)) when NDEBUG is not defined. mapnik's expression AST, mapnik::value
+# and all geometry visitation go through mapbox::variant, so an optimised build without NDEBUG
+# cannot inline its own hottest dispatch path (ref #4571).
+#
+# CMake only supplies -DNDEBUG via CMAKE_<LANG>_FLAGS_<CONFIG>, so a build that leaves
+# CMAKE_BUILD_TYPE empty or pins it to None - as Debian's `dh --buildsystem=cmake` does - never
+# gets it. Define it here instead of relying on the configuration name. Multi-config generators
+# pick the configuration at build time, where CMAKE_<LANG>_FLAGS_<CONFIG> already handles this.
+get_property(MAPNIK_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+string(TOUPPER "${CMAKE_BUILD_TYPE}" MAPNIK_BUILD_TYPE_UPPER)
+if(USE_NDEBUG AND NOT MAPNIK_IS_MULTI_CONFIG AND NOT MAPNIK_BUILD_TYPE_UPPER STREQUAL "DEBUG")
+ list(APPEND MAPNIK_COMPILE_DEFS NDEBUG)
+endif()
+
if(USE_LOG)
list(APPEND MAPNIK_COMPILE_DEFS MAPNIK_LOG MAPNIK_DEFAULT_LOG_SEVERITY=${USE_LOG_SEVERITY})
endif()
=====================================
debian/changelog
=====================================
@@ -1,9 +1,11 @@
-mapnik (4.3.0+ds2-2) UNRELEASED; urgency=medium
+mapnik (4.3.1+ds-1) unstable; urgency=medium
+ * New upstream release.
* Use CMAKE_BUILD_TYPE=RelWithDebInfo for -DNDEBUG.
See: https://github.com/mapnik/mapnik/pull/4572
+ * Refresh patches.
- -- Bas Couwenberg <sebastic at debian.org> Thu, 20 Aug 2026 10:19:36 +0200
+ -- Bas Couwenberg <sebastic at debian.org> Fri, 28 Aug 2026 14:18:53 +0200
mapnik (4.3.0+ds2-1) unstable; urgency=medium
=====================================
debian/patches/boost1.89.patch
=====================================
@@ -4,7 +4,7 @@ Forwarded: https://github.com/mapnik/mapnik/pull/4550
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -169,7 +169,7 @@ if(CMAKE_CXX_STANDARD VERSION_LESS 17)
+@@ -170,7 +170,7 @@ if(CMAKE_CXX_STANDARD VERSION_LESS 17)
set(USE_BOOST_FILESYSTEM ON CACHE BOOL "Use boost::filesystem" FORCE)
endif()
if(USE_BOOST_FILESYSTEM)
=====================================
debian/patches/repack.patch
=====================================
@@ -4,7 +4,7 @@ Forwarded: not-needed
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -446,7 +446,6 @@ add_subdirectory(deps)
+@@ -463,7 +463,6 @@ add_subdirectory(deps)
add_subdirectory(plugins)
add_subdirectory(src)
add_subdirectory(utils)
=====================================
debian/rules
=====================================
@@ -31,4 +31,5 @@ override_dh_auto_configure:
-DUSE_EXTERNAL_MAPBOX_POLYLABEL=ON \
-DUSE_EXTERNAL_MAPBOX_PROTOZERO=ON \
-DUSE_EXTERNAL_MAPBOX_VARIANT=ON \
+ -DUSE_NDEBUG=OFF \
-DUSE_PLUGIN_INPUT_TILES=OFF
=====================================
include/mapnik/expression_evaluator.hpp
=====================================
@@ -31,8 +31,84 @@
#include <mapnik/util/variant.hpp>
#include <mapnik/feature.hpp>
+// stl
+#include <type_traits>
+
namespace mapnik {
+namespace detail {
+
+template<typename Tag>
+struct ustring_compare
+{
+ static constexpr bool enabled = false;
+};
+
+template<>
+struct ustring_compare<mapnik::tags::equal_to>
+{
+ static constexpr bool enabled = true;
+
+ static bool apply(value_unicode_string const& a, value_unicode_string const& b) { return a == b; }
+
+ template<typename V>
+ static bool apply_value(V const& lhs, value_unicode_string const& rhs)
+ {
+ if (lhs.template is<value_unicode_string>())
+ {
+ return lhs.template get_unchecked<value_unicode_string>() == rhs;
+ }
+ return false;
+ }
+};
+
+template<>
+struct ustring_compare<mapnik::tags::not_equal_to>
+{
+ static constexpr bool enabled = true;
+
+ static bool apply(value_unicode_string const& a, value_unicode_string const& b) { return a != b; }
+
+ // Preserve value's asymmetric null/empty-string comparison.
+ template<typename V>
+ static bool apply_value(V const& lhs, value_unicode_string const& rhs)
+ {
+ if (lhs.template is<value_unicode_string>())
+ {
+ return lhs.template get_unchecked<value_unicode_string>() != rhs;
+ }
+ if (lhs.template is<value_null>())
+ {
+ return !rhs.isEmpty();
+ }
+ return true;
+ }
+};
+
+template<typename Tag>
+struct is_comparison_tag : std::false_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::less> : std::true_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::less_equal> : std::true_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::greater> : std::true_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::greater_equal> : std::true_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::equal_to> : std::true_type
+{};
+template<>
+struct is_comparison_tag<mapnik::tags::not_equal_to> : std::true_type
+{};
+
+} // namespace detail
+
template<typename T0, typename T1, typename T2>
struct evaluate
{
@@ -45,6 +121,31 @@ struct evaluate
vars_(v)
{}
+ using attribute_reference = decltype(std::declval<feature_type const&>().get(std::declval<std::string const&>()));
+ static constexpr bool borrowable_attributes =
+ std::is_lvalue_reference_v<attribute_reference> && std::is_same_v<std::decay_t<attribute_reference>, value_type>;
+
+ value_type const* borrow(expr_node const& node) const
+ {
+ if constexpr (borrowable_attributes)
+ {
+ if (node.template is<attribute>())
+ {
+ return &feature_.get(node.template get_unchecked<attribute>().name());
+ }
+ }
+ return nullptr;
+ }
+
+ value_bool eval_to_bool(expr_node const& node) const
+ {
+ if (value_type const* v = borrow(node))
+ {
+ return v->to_bool();
+ }
+ return util::apply_visitor(*this, node).to_bool();
+ }
+
value_type operator()(value_integer val) const { return val; }
value_type operator()(value_double val) const { return val; }
@@ -74,32 +175,83 @@ struct evaluate
value_type operator()(binary_node<tags::logical_and> const& x) const
{
- return (util::apply_visitor(*this, x.left).to_bool()) && (util::apply_visitor(*this, x.right).to_bool());
+ return eval_to_bool(x.left) && eval_to_bool(x.right);
}
value_type operator()(binary_node<tags::logical_or> const& x) const
{
- return (util::apply_visitor(*this, x.left).to_bool()) || (util::apply_visitor(*this, x.right).to_bool());
+ return eval_to_bool(x.left) || eval_to_bool(x.right);
}
template<typename Tag>
- value_type operator()(binary_node<Tag> const& x) const
+ bool compare(binary_node<Tag> const& x) const
{
typename make_op<Tag>::type operation;
+ value_type const* lhs = borrow(x.left);
+ value_type const* rhs = borrow(x.right);
+
+ if constexpr (detail::ustring_compare<Tag>::enabled)
+ {
+ if (lhs && !rhs && x.right.template is<value_unicode_string>())
+ {
+ return detail::ustring_compare<Tag>::apply_value(
+ *lhs,
+ x.right.template get_unchecked<value_unicode_string>());
+ }
+ if (rhs && !lhs && rhs->template is<value_unicode_string>() && x.left.template is<value_unicode_string>())
+ {
+ return detail::ustring_compare<Tag>::apply(x.left.template get_unchecked<value_unicode_string>(),
+ rhs->template get_unchecked<value_unicode_string>());
+ }
+ }
+
+ if (lhs)
+ {
+ if (rhs)
+ return operation(*lhs, *rhs);
+ return operation(*lhs, util::apply_visitor(*this, x.right));
+ }
+ if (rhs)
+ return operation(util::apply_visitor(*this, x.left), *rhs);
return operation(util::apply_visitor(*this, x.left), util::apply_visitor(*this, x.right));
}
+ template<typename Tag>
+ value_type operator()(binary_node<Tag> const& x) const
+ {
+ if constexpr (detail::is_comparison_tag<Tag>::value)
+ {
+ return value_type(compare(x));
+ }
+ else
+ {
+ typename make_op<Tag>::type operation;
+ value_type const* lhs = borrow(x.left);
+ value_type const* rhs = borrow(x.right);
+ if (lhs)
+ {
+ if (rhs)
+ return operation(*lhs, *rhs);
+ return operation(*lhs, util::apply_visitor(*this, x.right));
+ }
+ if (rhs)
+ return operation(util::apply_visitor(*this, x.left), *rhs);
+ return operation(util::apply_visitor(*this, x.left), util::apply_visitor(*this, x.right));
+ }
+ }
+
template<typename Tag>
value_type operator()(unary_node<Tag> const& x) const
{
typename make_op<Tag>::type func;
+ if (value_type const* v = borrow(x.expr))
+ {
+ return func(*v);
+ }
return func(util::apply_visitor(*this, x.expr));
}
- value_type operator()(unary_node<tags::logical_not> const& x) const
- {
- return !(util::apply_visitor(*this, x.expr).to_bool());
- }
+ value_type operator()(unary_node<tags::logical_not> const& x) const { return !eval_to_bool(x.expr); }
value_type operator()(regex_match_node const& x) const
{
@@ -130,6 +282,56 @@ struct evaluate
variable_type const& vars_;
};
+// Evaluates an expression for its truth value without materialising intermediate values.
+template<typename T0, typename T1, typename T2>
+struct evaluate_boolean
+{
+ using feature_type = T0;
+ using value_type = T1;
+ using variable_type = T2;
+ using result_type = value_bool;
+
+ explicit evaluate_boolean(feature_type const& f, variable_type const& v)
+ : eval_(f, v)
+ {}
+
+ result_type apply(expr_node const& node) const
+ {
+ if (value_type const* v = eval_.borrow(node))
+ {
+ return v->to_bool();
+ }
+ return util::apply_visitor(*this, node);
+ }
+
+ result_type operator()(binary_node<tags::logical_and> const& x) const { return apply(x.left) && apply(x.right); }
+
+ result_type operator()(binary_node<tags::logical_or> const& x) const { return apply(x.left) || apply(x.right); }
+
+ result_type operator()(unary_node<tags::logical_not> const& x) const { return !apply(x.expr); }
+
+ template<typename Tag>
+ result_type operator()(binary_node<Tag> const& x) const
+ {
+ if constexpr (detail::is_comparison_tag<Tag>::value)
+ {
+ return eval_.compare(x);
+ }
+ else
+ {
+ return eval_(x).to_bool();
+ }
+ }
+
+ template<typename T>
+ result_type operator()(T const& node) const
+ {
+ return eval_(node).to_bool();
+ }
+
+ evaluate<T0, T1, T2> eval_;
+};
+
} // namespace mapnik
#endif // MAPNIK_EXPRESSION_EVALUATOR_HPP
=====================================
include/mapnik/expression_node_types.hpp
=====================================
@@ -120,6 +120,8 @@ struct geometry_type_attribute;
struct unary_function_call;
struct binary_function_call;
+// Variant visitation tests alternatives in order. Keep common filter nodes first without
+// moving leaf types, whose order affects converting and default construction.
using expr_node = util::variant<value_null,
value_bool,
value_integer,
@@ -128,21 +130,21 @@ using expr_node = util::variant<value_null,
attribute,
global_attribute,
geometry_type_attribute,
+ util::recursive_wrapper<binary_node<mapnik::tags::logical_and>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::equal_to>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::not_equal_to>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::greater>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::greater_equal>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::less>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::less_equal>>,
+ util::recursive_wrapper<binary_node<mapnik::tags::logical_or>>,
+ util::recursive_wrapper<unary_node<mapnik::tags::logical_not>>,
util::recursive_wrapper<unary_node<mapnik::tags::negate>>,
util::recursive_wrapper<binary_node<mapnik::tags::plus>>,
util::recursive_wrapper<binary_node<mapnik::tags::minus>>,
util::recursive_wrapper<binary_node<mapnik::tags::mult>>,
util::recursive_wrapper<binary_node<mapnik::tags::div>>,
util::recursive_wrapper<binary_node<mapnik::tags::mod>>,
- util::recursive_wrapper<binary_node<mapnik::tags::less>>,
- util::recursive_wrapper<binary_node<mapnik::tags::less_equal>>,
- util::recursive_wrapper<binary_node<mapnik::tags::greater>>,
- util::recursive_wrapper<binary_node<mapnik::tags::greater_equal>>,
- util::recursive_wrapper<binary_node<mapnik::tags::equal_to>>,
- util::recursive_wrapper<binary_node<mapnik::tags::not_equal_to>>,
- util::recursive_wrapper<unary_node<mapnik::tags::logical_not>>,
- util::recursive_wrapper<binary_node<mapnik::tags::logical_and>>,
- util::recursive_wrapper<binary_node<mapnik::tags::logical_or>>,
util::recursive_wrapper<regex_match_node>,
util::recursive_wrapper<regex_replace_node>,
util::recursive_wrapper<unary_function_call>,
=====================================
include/mapnik/feature.hpp
=====================================
@@ -38,6 +38,7 @@
#include <memory>
#include <vector>
#include <map>
+#include <unordered_map>
#include <ostream> // for basic_ostream, operator<<, etc
#include <sstream> // for basic_stringstream
#include <stdexcept> // for out_of_range
@@ -73,17 +74,41 @@ class context : private util::noncopyable
{
size_type index = mapping_.size();
mapping_.emplace(name, index);
+ lookup_.emplace(name, index);
return index;
}
- inline void add(key_type const& name, size_type index) { mapping_.emplace(name, index); }
+ inline void add(key_type const& name, size_type index)
+ {
+ mapping_.emplace(name, index);
+ lookup_.emplace(name, index);
+ }
inline size_type size() const { return mapping_.size(); }
inline const_iterator begin() const { return mapping_.begin(); }
inline const_iterator end() const { return mapping_.end(); }
+ static constexpr size_type npos = static_cast<size_type>(-1);
+
+ inline size_type find_index(key_type const& name) const
+ {
+ auto itr = lookup_.find(name);
+ return (itr != lookup_.end()) ? itr->second : npos;
+ }
+
private:
+ inline key_type const* find_name(size_type index) const
+ {
+ for (auto const& entry : mapping_)
+ {
+ if (entry.second == index)
+ return &entry.first;
+ }
+ return nullptr;
+ }
+
map_type mapping_;
+ std::unordered_map<key_type, size_type> lookup_;
};
using context_type = context<std::map<std::string, std::size_t>>;
@@ -117,6 +142,12 @@ class MAPNIK_DECL feature_impl : private util::noncopyable
put(key, value(val));
}
+ template<typename T>
+ inline void put(std::size_t index, T const& val)
+ {
+ put(index, value(val));
+ }
+
template<typename T>
inline void put_new(context_type::key_type const& key, T const& val)
{
@@ -125,10 +156,10 @@ class MAPNIK_DECL feature_impl : private util::noncopyable
inline void put(context_type::key_type const& key, value&& val)
{
- context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
- if (itr != ctx_->mapping_.end() && itr->second < data_.size())
+ context_type::size_type const index = ctx_->find_index(key);
+ if (index != context_type::npos && index < data_.size())
{
- data_[itr->second] = std::move(val);
+ data_[index] = std::move(val);
}
else
{
@@ -136,12 +167,28 @@ class MAPNIK_DECL feature_impl : private util::noncopyable
}
}
+ inline void put(std::size_t index, value&& val)
+ {
+ if (index < data_.size())
+ {
+ data_[index] = std::move(val);
+ }
+ else
+ {
+ if (context_type::key_type const* key = ctx_->find_name(index))
+ {
+ throw std::out_of_range(std::string("Key does not exist: '") + *key + "'");
+ }
+ throw std::out_of_range(std::string("Attribute index does not exist: '") + std::to_string(index) + "'");
+ }
+ }
+
inline void put_new(context_type::key_type const& key, value&& val)
{
- context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
- if (itr != ctx_->mapping_.end() && itr->second < data_.size())
+ context_type::size_type const index = ctx_->find_index(key);
+ if (index != context_type::npos && index < data_.size())
{
- data_[itr->second] = std::move(val);
+ data_[index] = std::move(val);
}
else
{
@@ -151,13 +198,13 @@ class MAPNIK_DECL feature_impl : private util::noncopyable
}
}
- inline bool has_key(context_type::key_type const& key) const { return (ctx_->mapping_.count(key) == 1); }
+ inline bool has_key(context_type::key_type const& key) const { return ctx_->find_index(key) != context_type::npos; }
inline value_type const& get(context_type::key_type const& key) const
{
- context_type::map_type::const_iterator itr = ctx_->mapping_.find(key);
- if (itr != ctx_->mapping_.end())
- return get(itr->second);
+ context_type::size_type const index = ctx_->find_index(key);
+ if (index != context_type::npos)
+ return get(index);
else
return default_feature_value;
}
=====================================
include/mapnik/feature_style_processor_impl.hpp
=====================================
@@ -47,6 +47,8 @@
#include <mapnik/symbolizer_dispatch.hpp>
// stl
+#include <algorithm>
+#include <span>
#include <vector>
#include <stdexcept>
@@ -605,16 +607,69 @@ void feature_style_processor<Processor>::render_style(Processor& p,
mapnik::attributes vars = p.variables();
feature_ptr feature;
bool was_painted = false;
+
+ rule_cache::rule_ptrs const& if_rules = rc.get_if_rules();
+ std::span<std::size_t const> rules_without_precondition = rc.get_rules_without_precondition();
+ using resolved_group = std::pair<std::size_t, rule_cache::precondition_values const*>;
+ std::vector<resolved_group> precondition_groups;
+ rule_cache::rule_indices candidates;
+ context_type const* cached_context = nullptr;
+
while ((feature = features->next()))
{
+ context_type const* ctx = feature->context().get();
+ if (ctx != cached_context)
+ {
+ cached_context = ctx;
+ precondition_groups.clear();
+ precondition_groups.reserve(rc.get_precondition_groups().size());
+ for (rule_cache::precondition_group const& group : rc.get_precondition_groups())
+ {
+ precondition_groups.emplace_back(ctx->find_index(group.name), &group.rules);
+ }
+ }
+
+ std::span<std::size_t const> candidate_rules;
+ if (precondition_groups.empty())
+ {
+ candidate_rules = rules_without_precondition;
+ }
+ else if (precondition_groups.size() == 1 && rules_without_precondition.empty())
+ {
+ resolved_group const& group = precondition_groups.front();
+ value_type const& actual =
+ group.first == context_type::npos ? default_feature_value : feature->get(group.first);
+ auto const match = group.second->find(actual);
+ if (match != group.second->end())
+ {
+ candidate_rules = match->second;
+ }
+ }
+ else
+ {
+ candidates.assign(rules_without_precondition.begin(), rules_without_precondition.end());
+ for (resolved_group const& group : precondition_groups)
+ {
+ value_type const& actual =
+ group.first == context_type::npos ? default_feature_value : feature->get(group.first);
+ auto const match = group.second->find(actual);
+ if (match != group.second->end())
+ {
+ candidates.insert(candidates.end(), match->second.begin(), match->second.end());
+ }
+ }
+ // Restore stylesheet order after merging groups.
+ std::sort(candidates.begin(), candidates.end());
+ candidate_rules = candidates;
+ }
+
bool do_else = true;
bool do_also = false;
- for (rule const* r : rc.get_if_rules())
+ for (std::size_t index : candidate_rules)
{
+ rule const* r = if_rules[index];
expression_ptr const& expr = r->get_filter();
- value_type result =
- util::apply_visitor(evaluate<feature_impl, value_type, attributes>(*feature, vars), *expr);
- if (result.to_bool())
+ if (util::apply_visitor(evaluate_boolean<feature_impl, value_type, attributes>(*feature, vars), *expr))
{
was_painted = true;
do_else = false;
=====================================
include/mapnik/rule_cache.hpp
=====================================
@@ -24,29 +24,79 @@
#define MAPNIK_RULE_CACHE_HPP
// mapnik
+#include <mapnik/expression_node.hpp>
#include <mapnik/rule.hpp>
#include <mapnik/util/noncopyable.hpp>
// stl
+#include <string>
+#include <span>
+#include <unordered_map>
#include <vector>
-#include <type_traits>
namespace mapnik {
class rule_cache : private util::noncopyable
{
+ private:
+ struct precondition
+ {
+ std::string name;
+ value expected;
+ };
+
+ static bool extract_precondition(expr_node const& node, precondition& result)
+ {
+ if (node.is<binary_node<tags::equal_to>>())
+ {
+ auto const& equality = node.get_unchecked<binary_node<tags::equal_to>>();
+ if (equality.left.is<attribute>() && equality.right.is<value_unicode_string>())
+ {
+ result.name = equality.left.get_unchecked<attribute>().name();
+ result.expected = value(equality.right.get_unchecked<value_unicode_string>());
+ return true;
+ }
+ if (equality.right.is<attribute>() && equality.left.is<value_unicode_string>())
+ {
+ result.name = equality.right.get_unchecked<attribute>().name();
+ result.expected = value(equality.left.get_unchecked<value_unicode_string>());
+ return true;
+ }
+ return false;
+ }
+ if (node.is<binary_node<tags::logical_and>>())
+ {
+ auto const& conjunction = node.get_unchecked<binary_node<tags::logical_and>>();
+ return extract_precondition(conjunction.left, result) || extract_precondition(conjunction.right, result);
+ }
+ return false;
+ }
+
public:
using rule_ptrs = std::vector<rule const*>;
+ using rule_indices = std::vector<std::size_t>;
+ using precondition_values = std::unordered_map<value, rule_indices>;
+
+ struct precondition_group
+ {
+ std::string name;
+ precondition_values rules;
+ };
+
rule_cache()
: if_rules_(),
else_rules_(),
- also_rules_()
+ also_rules_(),
+ rules_without_precondition_(),
+ precondition_groups_()
{}
rule_cache(rule_cache&& rhs) // move ctor
: if_rules_(std::move(rhs.if_rules_)),
else_rules_(std::move(rhs.else_rules_)),
- also_rules_(std::move(rhs.also_rules_))
+ also_rules_(std::move(rhs.also_rules_)),
+ rules_without_precondition_(std::move(rhs.rules_without_precondition_)),
+ precondition_groups_(std::move(rhs.precondition_groups_))
{}
rule_cache& operator=(rule_cache&& rhs) // move assign
@@ -54,6 +104,8 @@ class rule_cache : private util::noncopyable
std::swap(if_rules_, rhs.if_rules_);
std::swap(else_rules_, rhs.else_rules_);
std::swap(also_rules_, rhs.also_rules_);
+ std::swap(rules_without_precondition_, rhs.rules_without_precondition_);
+ std::swap(precondition_groups_, rhs.precondition_groups_);
return *this;
}
@@ -69,7 +121,26 @@ class rule_cache : private util::noncopyable
}
else
{
+ std::size_t const index = if_rules_.size();
if_rules_.push_back(&r);
+ precondition condition;
+ expression_ptr const& filter = r.get_filter();
+ if (!filter || !extract_precondition(*filter, condition))
+ {
+ rules_without_precondition_.push_back(index);
+ return;
+ }
+
+ for (precondition_group& group : precondition_groups_)
+ {
+ if (group.name == condition.name)
+ {
+ group.rules[condition.expected].push_back(index);
+ return;
+ }
+ }
+ precondition_groups_.push_back({condition.name, {}});
+ precondition_groups_.back().rules[condition.expected].push_back(index);
}
}
@@ -79,10 +150,16 @@ class rule_cache : private util::noncopyable
rule_ptrs const& get_also_rules() const { return also_rules_; }
+ std::span<std::size_t const> get_rules_without_precondition() const { return rules_without_precondition_; }
+
+ std::span<precondition_group const> get_precondition_groups() const { return precondition_groups_; }
+
private:
rule_ptrs if_rules_;
rule_ptrs else_rules_;
rule_ptrs also_rules_;
+ rule_indices rules_without_precondition_;
+ std::vector<precondition_group> precondition_groups_;
};
} // namespace mapnik
=====================================
include/mapnik/text/harfbuzz_shaper.hpp
=====================================
@@ -316,7 +316,6 @@ struct harfbuzz_shaper
hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), &num_glyphs);
unsigned cluster = 0;
- bool in_cluster = false;
std::vector<unsigned> clusters;
std::vector<std::vector<glyph_face_info>> current_clusters;
current_clusters.resize(text.length());
@@ -332,11 +331,6 @@ struct harfbuzz_shaper
{
cluster = glyphs[i].cluster;
clusters.push_back(cluster);
- in_cluster = false;
- }
- else if (i != 0)
- {
- in_cluster = true;
}
if (glyphinfos.size() <= cluster)
{
@@ -359,13 +353,12 @@ struct harfbuzz_shaper
break;
}
}
- if (valid)
- {
- glyphinfos[cluster_id] = cluster_glyphs;
- }
- else if (glyphinfos[cluster_id].empty())
+ if (glyphinfos[cluster_id].empty())
{
- glyphinfos[cluster_id] = cluster_glyphs;
+ if (valid || pos == num_faces)
+ {
+ glyphinfos[cluster_id] = cluster_glyphs;
+ }
}
}
bool all_set = true;
=====================================
include/mapnik/util/sort_by.hpp
=====================================
@@ -38,7 +38,7 @@ inline bool parse_sort_by(std::string const& str, sort_by_type& result)
auto apply_sort_by = [&](auto const& ctx) {
result.first = _attr(ctx);
};
- auto apply_desc = [&](auto const& ctx) {
+ auto apply_desc = [&](auto const& /*ctx*/) {
result.second = true;
};
if (!x3::phrase_parse(itr,
=====================================
include/mapnik/version.hpp
=====================================
@@ -27,7 +27,7 @@
#define MAPNIK_MAJOR_VERSION 4
#define MAPNIK_MINOR_VERSION 3
-#define MAPNIK_PATCH_VERSION 0
+#define MAPNIK_PATCH_VERSION 1
#define MAPNIK_VERSION MAPNIK_VERSION_ENCODE(MAPNIK_MAJOR_VERSION, MAPNIK_MINOR_VERSION, MAPNIK_PATCH_VERSION)
=====================================
plugins/input/postgis/postgis_featureset.cpp
=====================================
@@ -67,16 +67,16 @@ feature_ptr postgis_featureset::next()
{
// new feature
unsigned pos = 1;
+ std::size_t attr_index = 0;
feature_ptr feature;
if (key_field_)
{
- std::string name = rs_->getFieldName(pos);
-
// null feature id is not acceptable
if (rs_->isNull(pos))
{
- MAPNIK_LOG_WARN(postgis) << "postgis_featureset: null value encountered for key_field: " << name;
+ MAPNIK_LOG_WARN(postgis) << "postgis_featureset: null value encountered for key_field: "
+ << rs_->getFieldName(pos);
continue;
}
// create feature with user driven id from attribute
@@ -102,7 +102,7 @@ feature_ptr postgis_featureset::next()
feature = feature_factory::create(ctx_, val);
if (key_field_as_attribute_)
{
- feature->put<mapnik::value_integer>(name, val);
+ feature->put<mapnik::value_integer>(attr_index++, val);
}
++pos;
}
@@ -135,14 +135,14 @@ feature_ptr postgis_featureset::next()
totalGeomSize_ += size;
unsigned num_attrs = ctx_->size() + 1;
- if (!key_field_as_attribute_)
+ if (key_field_ && !key_field_as_attribute_)
{
num_attrs++;
}
+
+ // SELECT columns and context entries are constructed in the same order.
for (; pos < num_attrs; ++pos)
{
- std::string name = rs_->getFieldName(pos);
-
// NOTE: we intentionally do not store null here
// since it is equivalent to the attribute not existing
if (!rs_->isNull(pos))
@@ -153,25 +153,25 @@ feature_ptr postgis_featureset::next()
{
case 16: // bool
{
- feature->put(name, (buf[0] != 0));
+ feature->put(attr_index, (buf[0] != 0));
break;
}
case 23: // int4
{
- feature->put<mapnik::value_integer>(name, int4net(buf));
+ feature->put<mapnik::value_integer>(attr_index, int4net(buf));
break;
}
case 21: // int2
{
- feature->put<mapnik::value_integer>(name, int2net(buf));
+ feature->put<mapnik::value_integer>(attr_index, int2net(buf));
break;
}
case 20: // int8/BigInt
{
- feature->put<mapnik::value_integer>(name, int8net(buf));
+ feature->put<mapnik::value_integer>(attr_index, int8net(buf));
break;
}
@@ -179,7 +179,7 @@ feature_ptr postgis_featureset::next()
{
float val;
float4net(val, buf);
- feature->put(name, static_cast<double>(val));
+ feature->put(attr_index, static_cast<double>(val));
break;
}
@@ -187,7 +187,7 @@ feature_ptr postgis_featureset::next()
{
double val;
float8net(val, buf);
- feature->put(name, val);
+ feature->put(attr_index, val);
break;
}
@@ -195,14 +195,14 @@ feature_ptr postgis_featureset::next()
case 1043: // varchar
case 705: // literal
{
- feature->put(name, tr_->transcode(buf));
+ feature->put(attr_index, tr_->transcode(buf));
break;
}
case 1042: // bpchar
{
std::string str = mapnik::util::trim_copy(buf);
- feature->put(name, tr_->transcode(str.c_str()));
+ feature->put(attr_index, tr_->transcode(str.c_str()));
break;
}
@@ -212,7 +212,7 @@ feature_ptr postgis_featureset::next()
std::string str = numeric2string(buf);
if (mapnik::util::string2double(str, val))
{
- feature->put(name, val);
+ feature->put(attr_index, val);
}
break;
}
@@ -224,6 +224,7 @@ feature_ptr postgis_featureset::next()
}
}
}
+ ++attr_index;
}
return feature;
}
=====================================
test/unit/core/expressions_test.cpp
=====================================
@@ -48,6 +48,14 @@ mapnik::value evaluate_string(mapnik::feature_ptr const& feature, std::string co
return evaluate(*feature, *expr);
}
+bool evaluate_boolean_string(mapnik::feature_ptr const& feature, std::string const& str)
+{
+ auto expr = mapnik::parse_expression(str);
+ return mapnik::util::apply_visitor(
+ mapnik::evaluate_boolean<mapnik::feature_impl, mapnik::value_type, mapnik::attributes>(*feature, {}),
+ *expr);
+}
+
std::string parse_and_dump(std::string const& str)
{
auto expr = mapnik::parse_expression(str);
@@ -56,6 +64,24 @@ std::string parse_and_dump(std::string const& str)
} // namespace
+TEST_CASE("feature attributes can be assigned by index")
+{
+ auto ctx = std::make_shared<mapnik::context_type>();
+ std::size_t const first = ctx->push("first");
+ std::size_t const second = ctx->push("second");
+ mapnik::feature_ptr feature(mapnik::feature_factory::create(ctx, 1));
+
+ feature->put(first, mapnik::value_integer(12));
+ feature->put(second, mapnik::value_unicode_string("value"));
+
+ CHECK(feature->get("first").to_int() == 12);
+ CHECK(feature->get("second").to_string() == "value");
+ CHECK_THROWS_WITH(feature->put(ctx->size(), mapnik::value_integer(0)), "Attribute index does not exist: '2'");
+
+ std::size_t const third = ctx->push("third");
+ CHECK_THROWS_WITH(feature->put(third, mapnik::value_integer(0)), "Key does not exist: 'third'");
+}
+
TEST_CASE("expressions")
{
using namespace std::placeholders;
@@ -76,6 +102,7 @@ TEST_CASE("expressions")
auto feature = make_test_feature(1, "POINT(100 200)", prop);
auto eval = std::bind(evaluate_string, feature, _1);
+ auto eval_bool = std::bind(evaluate_boolean_string, feature, _1);
auto approx = Approx::custom().epsilon(1e-6);
// primary expressions
@@ -174,6 +201,8 @@ TEST_CASE("expressions")
// logical
TRY_CHECK(eval(" [int] = 123 and [double] = 1.23456 && [bool] = true and [null] = null && [foo] = 'bar' ") == true);
TRY_CHECK(eval(" [int] = 456 or [foo].match('foo') || length([foo]) = 3 ") == true);
+ TRY_CHECK(eval_bool("[foo] = 'bar' and [int] > 100"));
+ TRY_CHECK(eval_bool("not ([foo] = 'missing' or length([foo]) != 3)"));
TRY_CHECK(eval(" not true and not true ") == false); // (not true) and (not true)
TRY_CHECK(eval(" not false and not true ") == false); // (not false) and (not true)
TRY_CHECK(eval(" not true or not false ") == true); // (not true) or (not false)
=====================================
test/unit/renderer/feature_style_processor.cpp
=====================================
@@ -23,6 +23,8 @@ struct rendering_result
unsigned start_style_processing = 0;
unsigned end_style_processing = 0;
+ std::size_t style_geometry_start = 0;
+ std::vector<std::size_t> style_geometry_counts;
std::vector<mapnik::box2d<double>> layer_query_extents;
std::vector<mapnik::geometry::geometry<double>> geometries;
@@ -51,9 +53,17 @@ class test_renderer : public mapnik::feature_style_processor<test_renderer>
void end_layer_processing(mapnik::layer const& lay) { result_.end_layer_processing++; }
- void start_style_processing(mapnik::feature_type_style const& st) { result_.start_style_processing++; }
+ void start_style_processing(mapnik::feature_type_style const& st)
+ {
+ result_.start_style_processing++;
+ result_.style_geometry_start = result_.geometries.size();
+ }
- void end_style_processing(mapnik::feature_type_style const& st) { result_.end_style_processing++; }
+ void end_style_processing(mapnik::feature_type_style const& st)
+ {
+ result_.end_style_processing++;
+ result_.style_geometry_counts.push_back(result_.geometries.size() - result_.style_geometry_start);
+ }
template<typename Symbolizer>
void process(Symbolizer const& sym, mapnik::feature_impl& feature, mapnik::proj_transform const& prj_trans)
@@ -341,4 +351,76 @@ TEST_CASE("feature_style_processor")
CHECK(datasource->last_bbox() == clipped_extent);
CHECK(datasource->last_unbuffered_bbox() == clipped_extent);
}
+
+ SECTION("rule indexing preserves filter behavior")
+ {
+ mapnik::parameters params;
+ params["type"] = "memory";
+ auto datasource = std::make_shared<mapnik::memory_datasource>(params);
+ auto context = std::make_shared<mapnik::context_type>();
+ auto feature = mapnik::feature_factory::create(context, 1);
+ feature->put_new("kind", mapnik::value_unicode_string("a"));
+ feature->put_new("other", mapnik::value_unicode_string("b"));
+ feature->set_geometry(mapnik::geometry::point<double>(1, 1));
+ datasource->push(feature);
+
+ auto make_rule = [](char const* filter, std::size_t symbol_count) {
+ mapnik::rule result;
+ if (filter)
+ {
+ result.set_filter(mapnik::parse_expression(filter));
+ }
+ for (std::size_t i = 0; i < symbol_count; ++i)
+ {
+ result.append(mapnik::line_symbolizer());
+ }
+ return result;
+ };
+
+ auto make_matching_style = [&](mapnik::filter_mode_e mode) {
+ mapnik::feature_type_style style;
+ style.set_filter_mode(mode);
+ style.add_rule(make_rule("[kind] = 'a'", 1));
+ style.add_rule(make_rule(nullptr, 2));
+ style.add_rule(make_rule("[other] = 'b'", 3));
+ return style;
+ };
+
+ mapnik::Map map(256, 256);
+ map.insert_style("all", make_matching_style(mapnik::filter_mode_enum::FILTER_ALL));
+ map.insert_style("first", make_matching_style(mapnik::filter_mode_enum::FILTER_FIRST));
+
+ mapnik::feature_type_style else_style;
+ else_style.add_rule(make_rule("[kind] = 'missing'", 1));
+ mapnik::rule else_rule = make_rule(nullptr, 4);
+ else_rule.set_else(true);
+ else_style.add_rule(std::move(else_rule));
+ map.insert_style("else", std::move(else_style));
+
+ mapnik::feature_type_style also_style;
+ also_style.add_rule(make_rule("[kind] = 'a'", 1));
+ mapnik::rule also_rule = make_rule(nullptr, 5);
+ also_rule.set_also(true);
+ also_style.add_rule(std::move(also_rule));
+ map.insert_style("also", std::move(also_style));
+
+ mapnik::layer layer("layer");
+ layer.set_datasource(datasource);
+ layer.add_style("all");
+ layer.add_style("first");
+ layer.add_style("else");
+ layer.add_style("also");
+ map.add_layer(layer);
+ map.zoom_to_box(mapnik::box2d<double>(0, 0, 2, 2));
+
+ rendering_result result;
+ test_renderer renderer(map, result);
+ renderer.apply();
+
+ REQUIRE(result.style_geometry_counts.size() == 4);
+ CHECK(result.style_geometry_counts[0] == 6);
+ CHECK(result.style_geometry_counts[1] == 1);
+ CHECK(result.style_geometry_counts[2] == 4);
+ CHECK(result.style_geometry_counts[3] == 6);
+ }
}
=====================================
test/unit/text/shaping.cpp
=====================================
@@ -31,26 +31,24 @@ void test_shaping(mapnik::font_set const& fontset,
mapnik::harfbuzz_shaper::shape_text(line, itemizer, width_map, fm, scale_factor, "");
std::size_t index = 0;
+ if (debug)
+ std::cerr << std::endl << str << std::endl;
for (auto const& g : line)
{
+ unsigned glyph_index, char_index;
+ std::tie(glyph_index, char_index) = expected[index++];
if (debug)
{
- if (index++ > 0)
- std::cerr << ",";
- std::cerr << "{" << g.glyph_index << ", "
- << g.char_index
- //<< ", " << g.face->family_name() << ":" << g.face->style_name()
- << "}";
+ std::cerr << "{" << glyph_index << "==" << g.glyph_index << ", " << char_index << "==" << g.char_index
+ << ", " << g.face->family_name() << ":" << g.face->style_name() << "}" << std::endl;
}
else
{
- unsigned glyph_index, char_index;
- CHECK(index < expected.size());
- std::tie(glyph_index, char_index) = expected[index++];
REQUIRE(glyph_index == g.glyph_index);
REQUIRE(char_index == g.char_index);
}
}
+ CHECK(index == expected.size());
}
} // namespace
View it on GitLab: https://salsa.debian.org/debian-gis-team/mapnik/-/compare/8016a39c6c762364e58497e71dd6a323552915f6...9e52e51bcf3e482dc14b26f074510d44c1764fda
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/mapnik/-/compare/8016a39c6c762364e58497e71dd6a323552915f6...9e52e51bcf3e482dc14b26f074510d44c1764fda
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260828/ae74f756/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list