Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
682a22223b A DEMO of area fills with no zoom limits
Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2022-06-29 15:05:24 +03:00
265c2b9f3b [indexer] Allow feature visibility override
- read 3 extra scale indices
- fallback to a nearest geometry if requested one doesn't exist
- OFF by default, except for a "designer tool"
- turns ON if a user has added custom style files
- fine control via search commands
  "?[no-]styles-override" and "?[no-]visibility-override"

Needed-for: #1145

Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
2022-06-28 20:31:01 +03:00
12 changed files with 173 additions and 28 deletions

View file

@ -268,6 +268,7 @@ void RuleDrawer::ProcessAreaStyle(FeatureType & f, Stylist const & s,
{
double const heightInMeters = GetBuildingHeightInMeters(f);
double const minHeightInMeters = GetBuildingMinHeightInMeters(f);
// Loads geometry of the feature.
featureCenter = feature::GetCenter(f, zoomLevel);
double const lon = mercator::XToLon(featureCenter.x);
double const lat = mercator::YToLat(featureCenter.y);
@ -283,12 +284,18 @@ void RuleDrawer::ProcessAreaStyle(FeatureType & f, Stylist const & s,
if (applyPointStyle)
{
if (!is3dBuilding)
{
// Loads geometry of the feature.
featureCenter = feature::GetCenter(f, zoomLevel);
}
applyPointStyle = m_globalRect.IsPointInside(featureCenter);
}
if (applyPointStyle || is3dBuilding)
{
// At this point a proper geometry is loaded already.
minVisibleScale = feature::GetMinDrawableScale(f);
}
ApplyAreaFeature apply(m_context->GetTileKey(), insertShape, f.GetID(),
m_currentScaleGtoP, isBuilding,

View file

@ -58,15 +58,23 @@ public:
Points const & GetSourcePoints()
{
// For short lines keep simplifying the previous version to ensure points visibility is consistent.
return !m_current.empty() ? m_current : m_fb.GetOuterGeometry();
}
// Its important AddPoints is called sequentially from upper scales to lower.
void AddPoints(Points const & points, int scaleIndex)
{
if (m_ptsInner && points.size() <= m_maxNumTriangles)
{
// Store small features inline and keep a mask for individual points scale visibility.
if (m_buffer.m_innerPts.empty())
{
// FIXME: if geometry is added for the most detailed scale 3 only
// then the mask is never updated and left == 0,
// which means the geometry could be used on any scale.
m_buffer.m_innerPts = points;
}
else
FillInnerPointsMask(points, scaleIndex);
m_current = points;

View file

@ -1,4 +1,6 @@
#include "indexer/data_source.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/scale_index.hpp"
#include "indexer/unique_index.hpp"
@ -47,6 +49,10 @@ public:
feature::DataHeader const & header = mwmValue->GetHeader();
CheckUniqueIndexes checkUnique;
// Read 3 additional scale indices to allow visibility changes
// for style designers and for custom style users.
if (GetStyleReader().IsVisibilityOverrideEnabled())
scale += 7;
// In case of WorldCoasts we should pass correct scale in ForEachInIntervalAndScale.
auto const lastScale = header.GetLastScale();
if (scale > lastScale)

View file

@ -428,7 +428,14 @@ namespace
AddRule<Line>(p, de.scale(), line, de.lines(k), apply_if);
if (de.has_area())
{
AddRule<Area>(p, de.scale(), area, de.area(), apply_if);
for (int n = 10; n < de.scale(); ++n)
{
if (!p->IsDrawable(n))
AddRule<Area>(p, n, area, de.area(), apply_if);
}
}
if (de.has_symbol())
AddRule<Symbol>(p, de.scale(), symbol, de.symbol(), apply_if);

View file

@ -153,6 +153,8 @@ bool GetBoundingBoxArea(FeatureType & ft, double & sqM)
if (feature::GeomType::Area != ft.GetGeomType())
return false;
// FIXME: the best geometry is loaded here always, use the current zoom level
// see https://github.com/organicmaps/organicmaps/issues/2840
sqM = mercator::AreaOnEarth(ft.GetLimitRect(scales::GetUpperScale()));
return true;
}

View file

@ -2,10 +2,12 @@
#include "indexer/classificator.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_impl.hpp"
#include "indexer/feature_utils.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/map_object.hpp"
#include "indexer/map_style_reader.hpp"
#include "indexer/scales.hpp"
#include "indexer/shared_load_info.hpp"
@ -344,6 +346,7 @@ void FeatureType::ParseHeader2()
if (headerGeomType == HeaderGeomType::Line)
{
ptsCount = bitSource.Read(4);
// If a mask of outer geometry is present.
if (ptsCount == 0)
ptsMask = bitSource.Read(4);
else
@ -363,6 +366,7 @@ void FeatureType::ParseHeader2()
{
if (ptsCount > 0)
{
// Inner geometry.
int const count = ((ptsCount - 2) + 4 - 1) / 4;
ASSERT_LESS(count, 4, ());
@ -378,6 +382,7 @@ void FeatureType::ParseHeader2()
}
else
{
// Outer geometry: first point is stored in the header (coding params).
m_points.emplace_back(serial::LoadPoint(src, cp));
ReadOffsets(*m_loadInfo, src, ptsMask, m_offsets.m_pts);
}
@ -435,8 +440,13 @@ uint32_t FeatureType::ParseGeometry(int scale)
{
ASSERT_EQUAL(count, 1, ());
// outer geometry
int const ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_pts);
// Outer geometry.
int ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_pts);
// If there is no geometry for the requested scale, fallback to a closest available one.
// TODO: if there is need we can remove excess points by a primitive filter
// similar to the one used in apply_feature_functors.cpp::ApplyLineFeatureGeometry::operator()
if (ind == -1 && GetStyleReader().IsVisibilityOverrideEnabled())
ind = GetScaleIndex(*m_loadInfo, FeatureType::WORST_GEOMETRY, m_offsets.m_pts);
if (ind != -1)
{
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetGeometryReader(ind));
@ -451,7 +461,7 @@ uint32_t FeatureType::ParseGeometry(int scale)
}
else
{
// filter inner geometry
// Filter inner geometry.
FeatureType::Points points;
points.reserve(count);
@ -460,11 +470,25 @@ uint32_t FeatureType::ParseGeometry(int scale)
ASSERT_LESS(scaleIndex, m_loadInfo->GetScalesCount(), ());
points.emplace_back(m_points.front());
int minScale = m_loadInfo->GetScalesCount() - 1;
int pointScale = 0;
for (size_t i = 1; i + 1 < count; ++i)
{
// check for point visibility in needed scaleIndex
if (static_cast<int>((m_ptsSimpMask >> (2 * (i - 1))) & 0x3) <= scaleIndex)
// Check for point visibility in needed scaleIndex.
pointScale = static_cast<int>((m_ptsSimpMask >> (2 * (i - 1))) & 0x3);
if (pointScale <= scaleIndex)
points.emplace_back(m_points[i]);
else if (points.size() == 1 && minScale > pointScale)
minScale = pointScale;
}
// Fallback to a closest available geometry.
if (points.size() == 1 && GetStyleReader().IsVisibilityOverrideEnabled())
{
for (size_t i = 1; i + 1 < count; ++i)
{
if (static_cast<int>((m_ptsSimpMask >> (2 * (i - 1))) & 0x3) == minScale)
points.emplace_back(m_points[i]);
}
}
points.emplace_back(m_points.back());
@ -491,7 +515,10 @@ uint32_t FeatureType::ParseTriangles(int scale)
{
if (m_triangles.empty())
{
auto const ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_trg);
auto ind = GetScaleIndex(*m_loadInfo, scale, m_offsets.m_trg);
// If there is no geometry for the requested scale, fallback to a closest available one.
if (ind == -1 && GetStyleReader().IsVisibilityOverrideEnabled())
ind = GetScaleIndex(*m_loadInfo, FeatureType::WORST_GEOMETRY, m_offsets.m_trg);
if (ind != -1)
{
ReaderSource<FilesContainerR::TReader> src(m_loadInfo->GetTrianglesReader(ind));
@ -503,6 +530,16 @@ uint32_t FeatureType::ParseTriangles(int scale)
}
CalcRect(m_triangles, m_limitRect);
// After scanning extra visibility/scale indices some too small
// area features (that were initially excluded from the index
// because of their small size) appear again - filter them out.
if (GetStyleReader().IsVisibilityOverrideEnabled()
&& !IsDrawableForIndexGeometryOnly(TypesHolder(*this), m_limitRect, scale))
{
m_triangles.clear();
m_limitRect = m2::RectD();
}
}
m_parsed.m_triangles = true;
}

View file

@ -12,8 +12,8 @@
namespace feature
{
/// @returns point on a feature that is the closest to f.GetLimitRect().Center().
/// It is used by many ednities in the core of mapsme. Do not modify it's
/// logic if you really-really know what you are doing.
/// It is used by many entities in the core. Do not modify it's
/// logic unless you really-really know what you are doing.
m2::PointD GetCenter(FeatureType & f, int scale)
{
GeomType const type = f.GetGeomType();

View file

@ -229,6 +229,7 @@ bool IsDrawableForIndexGeometryOnly(TypesHolder const & types, m2::RectD limitRe
static uint32_t const buildingPartType = c.GetTypeByPath({"building:part"});
// Exclude too small area features unless it's a part of a coast or a building.
if (types.GetGeomType() == GeomType::Area && !types.Has(c.GetCoastType()) &&
!types.Has(buildingPartType) && !scales::IsGoodForLevel(level, limitRect))
return false;

View file

@ -91,21 +91,28 @@ bool StyleReader::IsCarNavigationStyle() const
m_mapStyle == MapStyle::MapStyleVehicleDark;
}
ReaderPtr<Reader> StyleReader::GetDrawingRulesReader() const
ReaderPtr<Reader> StyleReader::GetDrawingRulesReader()
{
std::string rulesFile =
std::string("drules_proto") + GetStyleRulesSuffix(GetCurrentStyle()) + ".bin";
auto overriddenRulesFile =
base::JoinPath(GetPlatform().WritableDir(), kStylesOverrideDir, rulesFile);
if (GetPlatform().IsFileExistsByFullPath(overriddenRulesFile))
rulesFile = overriddenRulesFile;
Platform const & pl = GetPlatform();
if (m_isStylesOverrideEnabled)
{
auto overriddenRulesFile =
base::JoinPath(pl.WritableDir(), kStylesOverrideDir, rulesFile);
if (pl.IsFileExistsByFullPath(overriddenRulesFile))
{
rulesFile = overriddenRulesFile;
m_isVisibilityOverrideEnabled = true;
}
}
#ifdef BUILD_DESIGNER
// For Designer tool we have to look first into the resource folder.
return GetPlatform().GetReader(rulesFile, "rwf");
return pl.GetReader(rulesFile, "rwf");
#else
return GetPlatform().GetReader(rulesFile);
return pl.GetReader(rulesFile);
#endif
}
@ -116,15 +123,19 @@ ReaderPtr<Reader> StyleReader::GetResourceReader(std::string const & file,
std::string("resources-") + density + GetStyleResourcesSuffix(GetCurrentStyle());
std::string resFile = base::JoinPath(resourceDir, file);
auto overriddenResFile = base::JoinPath(GetPlatform().WritableDir(), kStylesOverrideDir, resFile);
if (GetPlatform().IsFileExistsByFullPath(overriddenResFile))
resFile = overriddenResFile;
Platform const & pl = GetPlatform();
if (m_isStylesOverrideEnabled)
{
auto overriddenResFile = base::JoinPath(pl.WritableDir(), kStylesOverrideDir, resFile);
if (pl.IsFileExistsByFullPath(overriddenResFile))
resFile = overriddenResFile;
}
#ifdef BUILD_DESIGNER
// For Designer tool we have to look first into the resource folder.
return GetPlatform().GetReader(resFile, "rwf");
return pl.GetReader(resFile, "rwf");
#else
return GetPlatform().GetReader(resFile);
return pl.GetReader(resFile);
#endif
}

View file

@ -16,12 +16,43 @@ public:
MapStyle GetCurrentStyle() const;
bool IsCarNavigationStyle() const;
ReaderPtr<Reader> GetDrawingRulesReader() const;
ReaderPtr<Reader> GetDrawingRulesReader();
ReaderPtr<Reader> GetResourceReader(std::string const & file, std::string const & density) const;
ReaderPtr<Reader> GetDefaultResourceReader(std::string const & file) const;
// If enabled allows to load custom style files
// from the "styles/" subdir of the writable dir.
inline void ToggleStylesOverride(bool enabled)
{
m_isStylesOverrideEnabled = enabled;
}
inline bool IsStylesOverrideEnabled() const
{
return m_isStylesOverrideEnabled;
}
// If enabled allows rendering of features at up to 3 lower zoom levels
// by reading extra scale/visibility indices and using a geometry fallback.
inline void ToggleVisibilityOverride(bool enabled)
{
m_isVisibilityOverrideEnabled = enabled;
}
inline bool IsVisibilityOverrideEnabled() const
{
return m_isVisibilityOverrideEnabled;
}
private:
bool m_isStylesOverrideEnabled = true;
#ifdef BUILD_DESIGNER
bool m_isVisibilityOverrideEnabled = true;
#else
bool m_isVisibilityOverrideEnabled = false;
#endif
std::atomic<MapStyle> m_mapStyle;
};

View file

@ -47,6 +47,11 @@ public:
m_cellsInBucket.resize(m_bucketsCount);
}
// Every feature should be indexed at most once, namely for the smallest possible scale where
// -- its geometry is non-empty;
// -- it is visible;
// -- it is allowed by the classificator.
// If the feature is invisible at all scales, do not index it.
template <class Feature>
void operator()(Feature & ft, uint32_t index) const
{
@ -56,12 +61,15 @@ public:
// The classificator won't allow this feature to be drawable for smaller
// scales so the first buckets can be safely skipped.
// todo(@pimenov) Parallelizing this loop may be helpful.
// TODO: skip index building for scales [0,9] for country files and scales 10+ for the world file.
for (uint32_t bucket = minScaleClassif; bucket < m_bucketsCount; ++bucket)
{
// There is a one-to-one correspondence between buckets and scales.
// This is not immediately obvious and in fact there was an idea to map
// a bucket to a contiguous range of scales.
// todo(@pimenov): We probably should remove scale_index.hpp altogether.
// Check feature's geometry and visibility.
if (!FeatureShouldBeIndexed(ft, static_cast<int>(bucket), bucket == minScaleClassif /* needReset */))
{
continue;
@ -78,11 +86,6 @@ public:
}
private:
// Every feature should be indexed at most once, namely for the smallest possible scale where
// -- its geometry is non-empty;
// -- it is visible;
// -- it is allowed by the classificator.
// If the feature is invisible at all scales, do not index it.
template <class Feature>
bool FeatureShouldBeIndexed(Feature & ft, int scale, bool needReset) const
{
@ -136,9 +139,10 @@ void IndexScales(feature::DataHeader const & header, FeaturesVector const & feat
};
using TDisplacementManager = DisplacementManager<decltype(PushCFT)>;
// Heuristically rearrange and filter single-point features to simplify
// Single-point features are heuristically rearranged and filtered to simplify
// the runtime decision of whether we should draw a feature
// or sacrifice it for the sake of more important ones.
// or sacrifice it for the sake of more important ones ("displacement").
// Lines and areas are not displaceable and are just passed on to the index.
TDisplacementManager manager(PushCFT);
std::vector<uint32_t> featuresInBucket(bucketsCount);
std::vector<uint32_t> cellsInBucket(bucketsCount);

View file

@ -2593,6 +2593,8 @@ bool Framework::ParseDrapeDebugCommand(string const & query)
if (desiredStyle != MapStyleCount)
{
#if defined(OMIM_OS_ANDROID)
// TODO: might not always work, sometimes SetMapStyle() is needed,
// see android/jni/com/mapswithme/maps/Framework.cpp::MarkMapStyle()
MarkMapStyle(desiredStyle);
#else
SetMapStyle(desiredStyle);
@ -2632,6 +2634,35 @@ bool Framework::ParseDrapeDebugCommand(string const & query)
m_isolinesManager.SetEnabled(false /* enable */);
return true;
}
if (query == "?styles-override")
{
GetStyleReader().ToggleStylesOverride(true);
// The visibility override will be enabled automatically
// if there are any custom style files present.
GetStyleReader().ToggleVisibilityOverride(false);
// Reload in case style files were changed.
classificator::Load();
SetMapStyle(GetStyleReader().GetCurrentStyle());
return true;
}
if (query == "?no-styles-override")
{
GetStyleReader().ToggleStylesOverride(false);
GetStyleReader().ToggleVisibilityOverride(false);
classificator::Load();
SetMapStyle(GetStyleReader().GetCurrentStyle());
return true;
}
if (query == "?visibility-override")
{
GetStyleReader().ToggleVisibilityOverride(true);
return true;
}
if (query == "?no-visibility-override")
{
GetStyleReader().ToggleVisibilityOverride(false);
return true;
}
if (query == "?debug-info")
{
m_drapeEngine->ShowDebugInfo(true /* shown */);