#include "feature_styler.hpp" #include "geometry_processors.hpp" #include "proto_to_styles.hpp" #include "../indexer/drawing_rules.hpp" #include "../indexer/feature.hpp" #include "../indexer/feature_visibility.hpp" #ifdef OMIM_PRODUCTION #include "../indexer/drules_struct_lite.pb.h" #else #include "../indexer/drules_struct.pb.h" #endif #include "../geometry/screenbase.hpp" #include "../graphics/glyph_cache.hpp" #include "../base/stl_add.hpp" #include "../std/iterator_facade.hpp" namespace { struct less_depth { bool operator() (di::DrawRule const & r1, di::DrawRule const & r2) const { return (r1.m_depth < r2.m_depth); } }; } namespace di { uint32_t DrawRule::GetID(size_t threadSlot) const { return (m_transparent ? m_rule->GetID2(threadSlot) : m_rule->GetID(threadSlot)); } void DrawRule::SetID(size_t threadSlot, uint32_t id) const { m_transparent ? m_rule->SetID2(threadSlot, id) : m_rule->SetID(threadSlot, id); } FeatureStyler::FeatureStyler(FeatureType const & f, int const zoom, double const visualScale, graphics::GlyphCache * glyphCache, ScreenBase const * convertor, m2::RectD const * rect) : m_hasPathText(false), m_visualScale(visualScale), m_glyphCache(glyphCache), m_convertor(convertor), m_rect(rect) { vector keys; string names; // for debug use only, in release it's empty pair type = feature::GetDrawRule(f, zoom, keys, names); // don't try to do anything to invisible feature if (keys.empty()) return; m_hasLineStyles = false; m_geometryType = type.first; m_isCoastline = type.second; f.GetPreferredDrawableNames(m_primaryText, m_secondaryText); m_refText = f.GetRoadNumber(); double const population = static_cast(f.GetPopulation()); if (population == 1) m_popRank = 0.0; else { double const upperBound = 3.0E6; m_popRank = min(upperBound, population) / upperBound / 4; } // low zoom heuristics if (zoom <= 5) { // hide superlong names on low zoom if (m_primaryText.size() > 50) m_primaryText.clear(); } double area = 0.0; if (m_geometryType != feature::GEOM_POINT) { m2::RectD const bbox = f.GetLimitRect(zoom); area = bbox.SizeX() * bbox.SizeY(); } double priorityModifier; if (area != 0) priorityModifier = min(1., area*10000.); // making area larger so it's not lost on double conversions else priorityModifier = static_cast(population) / 7E9; // dividing by planet population to get priorityModifier < 1 drule::MakeUnique(keys); int layer = f.GetLayer(); bool isTransparent = false; if (layer == feature::LAYER_TRANSPARENT_TUNNEL) layer = 0; bool hasIcon = false; bool hasCaptionWithoutOffset = false; m_fontSize = 0; size_t const count = keys.size(); m_rules.resize(count); for (size_t i = 0; i < count; ++i) { double depth = keys[i].m_priority; if (layer != 0) depth = (layer * drule::layer_base_priority) + fmod(depth, drule::layer_base_priority); if (keys[i].m_type == drule::symbol) hasIcon = true; if ((keys[i].m_type == drule::caption) || (keys[i].m_type == drule::symbol) || (keys[i].m_type == drule::circle) || (keys[i].m_type == drule::pathtext) || (keys[i].m_type == drule::waymarker)) { // show labels of larger objects first depth += priorityModifier; } else if (keys[i].m_type == drule::area) { // show smaller polygons on top depth -= priorityModifier; } if (!m_hasLineStyles && (keys[i].m_type == drule::line)) m_hasLineStyles = true; m_rules[i] = di::DrawRule( drule::rules().Find(keys[i]), depth, isTransparent); if ((m_geometryType == feature::GEOM_LINE) && !m_hasPathText && !m_primaryText.empty()) if (m_rules[i].m_rule->GetCaption(0) != 0) { m_hasPathText = true; if (!FilterTextSize(m_rules[i].m_rule)) m_fontSize = max(m_fontSize, GetTextFontSize(m_rules[i].m_rule)); } if (keys[i].m_type == drule::caption) if (m_rules[i].m_rule->GetCaption(0) != 0) if (!m_rules[i].m_rule->GetCaption(0)->has_offset_y()) hasCaptionWithoutOffset = true; } // placing a text on the path if (m_hasPathText && (m_fontSize != 0)) { typedef gp::filter_screenpts_adapter functor_t; functor_t::params p; p.m_convertor = m_convertor; p.m_rect = m_rect; p.m_intervals = &m_intervals; functor_t fun(p); f.ForEachPointRef(fun, zoom); LayoutTexts(fun.m_length); } if (hasIcon && hasCaptionWithoutOffset) // we need to delete symbol style (single one due to MakeUnique call above) for (size_t i = 0; i < count; ++i) { if (keys[i].m_type == drule::symbol) { m_rules[i] = m_rules[m_rules.size() - 1]; m_rules.pop_back(); break; } } sort(m_rules.begin(), m_rules.end(), less_depth()); } typedef pair RangeT; template class RangeIterT : public iterator_facade, RangeT, forward_traversal_tag, RangeT> { IterT m_iter; public: RangeIterT(IterT iter) : m_iter(iter) {} RangeT dereference() const { IterT next = m_iter; ++next; return RangeT(*m_iter, *next); } bool equal(RangeIterT const & r) const { return (m_iter == r.m_iter); } void increment() { ++m_iter; ++m_iter; } }; template class RangeInserter { ContT & m_cont; public: RangeInserter(ContT & cont) : m_cont(cont) {} RangeInserter & operator*() { return *this; } RangeInserter & operator++(int) { return *this; } RangeInserter & operator=(RangeT const & r) { m_cont.push_back(r.first); m_cont.push_back(r.second); return *this; } }; void FeatureStyler::LayoutTexts(double pathLength) { double const textLength = m_glyphCache->getTextLength(m_fontSize, GetPathName()); /// @todo Choose best constant for minimal space. double const emptySize = max(200 * m_visualScale, textLength); // multiply on factor because tiles will be rendered in smaller scales double const minPeriodSize = 1.5 * (emptySize + textLength); size_t textCnt = 0; double firstTextOffset = 0; if (pathLength > textLength) { textCnt = ceil((pathLength - textLength) / minPeriodSize); firstTextOffset = 0.5 * (pathLength - (textCnt * textLength + (textCnt - 1) * emptySize)); } if (textCnt != 0 && !m_intervals.empty()) { buffer_vector deadZones; for (size_t i = 0; i < textCnt; ++i) { double const deadZoneStart = firstTextOffset + minPeriodSize * i; double const deadZoneEnd = deadZoneStart + textLength; if (deadZoneStart > m_intervals.back()) break; deadZones.push_back(make_pair(deadZoneStart, deadZoneEnd)); } if (!deadZones.empty()) { buffer_vector res; // accumulate text layout intervals with cliping intervals typedef RangeIterT IterT; AccumulateIntervals1With2(IterT(m_intervals.begin()), IterT(m_intervals.end()), deadZones.begin(), deadZones.end(), RangeInserter(res)); m_intervals = res; ASSERT_EQUAL(m_intervals.size() % 2, 0, ()); // get final text offsets (belongs to final clipping intervals) size_t i = 0; size_t j = 0; while (i != deadZones.size() && j != m_intervals.size()) { ASSERT_LESS(deadZones[i].first, deadZones[i].second, ()); ASSERT_LESS(m_intervals[j], m_intervals[j+1], ()); if (deadZones[i].first < m_intervals[j]) { ++i; continue; } if (m_intervals[j+1] <= deadZones[i].first) { j += 2; continue; } ASSERT_LESS_OR_EQUAL(m_intervals[j], deadZones[i].first, ()); ASSERT_LESS_OR_EQUAL(deadZones[i].second, m_intervals[j+1], ()); m_offsets.push_back(deadZones[i].first); ++i; } } } } string const FeatureStyler::GetPathName() const { if (m_secondaryText.empty()) return m_primaryText; else return m_primaryText + " " + m_secondaryText; } bool FeatureStyler::IsEmpty() const { return m_rules.empty(); } uint8_t FeatureStyler::GetTextFontSize(drule::BaseRule const * pRule) const { return pRule->GetCaption(0)->height() * m_visualScale; } bool FeatureStyler::FilterTextSize(drule::BaseRule const * pRule) const { if (pRule->GetCaption(0)) return (GetFontSize(pRule->GetCaption(0)) < 3); else { // this rule is not a caption at all return true; } } }