diff --git a/base/file_name_utils.hpp b/base/file_name_utils.hpp
index 26a6e9a8df..7c957d14b1 100644
--- a/base/file_name_utils.hpp
+++ b/base/file_name_utils.hpp
@@ -51,7 +51,6 @@ std::string JoinPath(std::string const & folder, Args &&... args)
 template <typename... Args>
 std::string JoinPath(std::string const & dir, std::string const & fileOrDir, Args &&... args)
 {
-  ASSERT(!dir.empty(), ("JoinPath dir is empty"));
   ASSERT(!fileOrDir.empty(), ("JoinPath fileOrDir is empty"));
   return impl::JoinPath(dir, fileOrDir, std::forward<Args>(args)...);
 }
diff --git a/generator/feature_builder.cpp b/generator/feature_builder.cpp
index 07b948dff7..6aec4dc7e6 100644
--- a/generator/feature_builder.cpp
+++ b/generator/feature_builder.cpp
@@ -80,6 +80,17 @@ bool FeatureBuilder::IsGeometryClosed() const
   return (poly.size() > 2 && poly.front() == poly.back());
 }
 
+bool FeatureBuilder::IsClosedLine() const
+{
+  if (GetGeomType() == GeomType::Line)
+  {
+    PointSeq const & points = GetOuterGeometry();
+    CHECK(points.size() > 1, (*this));
+    return points.front() == points.back();
+  }
+  return false;
+}
+
 m2::PointD FeatureBuilder::GetGeometryCenter() const
 {
   m2::PointD ret(0.0, 0.0);
@@ -567,14 +578,6 @@ bool FeatureBuilder::HasOsmId(base::GeoObjectId const & id) const
   return false;
 }
 
-int FeatureBuilder::GetMinFeatureDrawScale() const
-{
-  int const minScale = GetMinDrawableScale(GetTypesHolder(), m_limitRect);
-
-  // some features become invisible after merge processing, so -1 is possible
-  return (minScale == -1 ? 1000 : minScale);
-}
-
 bool FeatureBuilder::AddName(std::string_view lang, std::string_view name)
 {
   return m_params.AddName(lang, name);
@@ -602,7 +605,7 @@ bool FeatureBuilder::IsDrawableInRange(int lowScale, int highScale) const
     auto const types = GetTypesHolder();
     while (lowScale <= highScale)
     {
-      if (IsDrawableForIndex(types, m_limitRect, lowScale++))
+      if (IsDrawableForIndex(types, m_limitRect, IsClosedLine(), lowScale++))
         return true;
     }
   }
@@ -618,7 +621,7 @@ bool FeatureBuilder::PreSerializeAndRemoveUselessNamesForMwm(SupportingData cons
   {
     if (data.m_ptsMask == 0 && data.m_innerPts.empty())
     {
-      LOG(LWARNING, ("Skip feature with empty geometry", GetMostGenericOsmId()));
+      LOG(LINFO, ("Skip feature with empty geometry", GetMostGenericOsmId()));
       return false;
     }
   }
@@ -626,7 +629,7 @@ bool FeatureBuilder::PreSerializeAndRemoveUselessNamesForMwm(SupportingData cons
   {
     if (data.m_trgMask == 0 && data.m_innerTrg.empty())
     {
-      LOG(LWARNING, ("Skip feature with empty geometry", GetMostGenericOsmId()));
+      LOG(LINFO, ("Skip feature with empty geometry", GetMostGenericOsmId()));
       return false;
     }
   }
@@ -776,7 +779,7 @@ std::string DebugPrint(FeatureBuilder const & fb)
   default: out << "ERROR: unknown geometry type"; break;
   }
 
-  out << " " << DebugPrint(fb.GetLimitRect())
+  out << " " << DebugPrint(mercator::ToLatLon(fb.GetLimitRect()))
       << " " << DebugPrint(fb.GetParams())
       << " " << ::DebugPrint(fb.m_osmIds);
   return out.str();
diff --git a/generator/feature_builder.hpp b/generator/feature_builder.hpp
index 69d89ea748..a42229f128 100644
--- a/generator/feature_builder.hpp
+++ b/generator/feature_builder.hpp
@@ -64,7 +64,10 @@ public:
   Geometry const & GetGeometry() const { return m_polygons; }
   PointSeq const & GetOuterGeometry() const { return m_polygons.front(); }
   GeomType GetGeomType() const { return m_params.GetGeomType(); }
+  // Used for good areas and lines.
   bool IsGeometryClosed() const;
+  // Used for lines incl. degenerate ones.
+  bool IsClosedLine() const;
   m2::PointD GetGeometryCenter() const;
   m2::PointD GetKeyPoint() const;
   size_t GetPointsCount() const;
@@ -161,7 +164,6 @@ public:
   // Clear name if it's not visible in scale range [minS, maxS].
   void RemoveNameIfInvisible(int minS = 0, int maxS = 1000);
   void RemoveUselessNames();
-  int GetMinFeatureDrawScale() const;
   bool IsDrawableInRange(int lowScale, int highScale) const;
 
   /// @name Serialization.
diff --git a/generator/feature_helpers.cpp b/generator/feature_helpers.cpp
index 08a7bcfd88..56df078101 100644
--- a/generator/feature_helpers.cpp
+++ b/generator/feature_helpers.cpp
@@ -13,7 +13,7 @@ CalculateMidPoints::CalculateMidPoints()
 {
   m_minDrawableScaleFn = [](FeatureBuilder const & fb)
   {
-    return GetMinDrawableScale(fb.GetTypesHolder(), fb.GetLimitRect());
+    return GetMinDrawableScale(fb.GetTypesHolder(), fb.GetLimitRect(), fb.IsClosedLine());
   };
 }
 
diff --git a/generator/feature_maker.cpp b/generator/feature_maker.cpp
index 0f51c556a8..4c7627a2c9 100644
--- a/generator/feature_maker.cpp
+++ b/generator/feature_maker.cpp
@@ -53,6 +53,7 @@ bool FeatureMakerSimple::BuildFromWay(OsmElement & p, FeatureBuilderParams const
 
   fb.SetOsmId(base::MakeOsmWay(p.m_id));
   fb.SetParams(params);
+  // TODO : convert degenerate 3-points closed ways into 2-points lines?
   if (fb.IsGeometryClosed())
     fb.SetArea();
   else
diff --git a/generator/feature_sorter.cpp b/generator/feature_sorter.cpp
index 1a5c6e87e7..b6f98d2f9c 100644
--- a/generator/feature_sorter.cpp
+++ b/generator/feature_sorter.cpp
@@ -14,6 +14,7 @@
 #include "indexer/feature_algo.hpp"
 #include "indexer/feature_impl.hpp"
 #include "indexer/feature_processor.hpp"
+#include "indexer/ftypes_matcher.hpp"
 #include "indexer/scales.hpp"
 #include "indexer/scales_patch.hpp"
 
@@ -151,21 +152,30 @@ public:
     GeometryHolder holder([this](int i) -> FileWriter & { return *m_geoFile[i]; },
                           [this](int i) -> FileWriter & { return *m_trgFile[i]; }, fb, m_header);
 
-    bool const isLine = fb.IsLine();
-    bool const isArea = fb.IsArea();
-    CHECK(!isLine || !isArea, ("A feature can't have both points and triangles geometries:", fb.GetMostGenericOsmId()));
+    bool isValidLine = fb.IsLine();
+    bool isValidArea = fb.IsArea();
+    CHECK(!isValidLine || !isValidArea, ("A feature can't have both points and triangles geometries:", fb.GetMostGenericOsmId()));
+    bool isClosedLine = fb.IsClosedLine();
+    bool const isRoad = isValidLine && routing::IsRoad(fb.GetTypes());
+    bool const isCoast = fb.IsCoastCell();
+    m2::RectD const rect = fb.GetLimitRect();
 
     int const scalesStart = static_cast<int>(m_header.GetScalesCount()) - 1;
     for (int i = scalesStart; i >= 0; --i)
     {
       int const level = m_header.GetScale(i);
+      // TODO : re-checks geom limit rect size via IsDrawableForIndexGeometryOnly() which was checked already in CalculateMidPoints.
       if (fb.IsDrawableInRange(scales::PatchMinDrawableScale(i > 0 ? m_header.GetScale(i - 1) + 1 : 0),
                                scales::PatchMaxDrawableScale(level)))
       {
-        bool const isCoast = fb.IsCoastCell();
-        m2::RectD const rect = fb.GetLimitRect();
-
         // Simplify and serialize geometry.
+
+        // Only valid geometry should come from the OSM data (feature_maker.cpp
+        // filters it to min 2 points for ways and min 3 points for closed ways)
+        // and *.isolines files (the topography_generator filters it
+        // to 2 points for lines and min 4 points for closed lines).
+        // But the geometry can degenerate during simplification so we re-filter it here.
+
         // The same line simplification algo is used both for lines
         // and areas. For the latter polygon's outline is simplified and
         // tesselated afterwards. But e.g. simplifying a circle from 50
@@ -174,19 +184,49 @@ public:
         // is much higher than between trg1 and trg2.
         Points points;
 
-        // Do not change linear geometry for the upper scale.
-        if (isLine && i == scalesStart && IsCountry() && routing::IsRoad(fb.GetTypes()))
-          points = holder.GetSourcePoints();
-        else if (isLine || holder.NeedProcessTriangles())
+        // Do not change the most detailed linear geometry for roads (for precise distances calculation)
+        // and for isolines (they had been simplified already while being generated).
+        bool isKeepSourceLine = false;
+        if (isValidLine && i == scalesStart && IsCountry() &&
+            (isRoad || ftypes::IsIsolineChecker::Instance()(fb.GetTypes())))
+        {
+          points = fb.GetOuterGeometry();
+          // As an exception we keep degenerate 3 points closed geom3 roads
+          // (to avoid breaking routing) in a form of a regular 2-points line.
+          if (isRoad && isClosedLine && points.size() == 3)
+          {
+            LOG(LWARNING, ("Convert a 3-points degenerate closed road line into a 2-points line", DebugPrint(fb)));
+            points.pop_back();
+            isClosedLine = false;
+          }
+          // TODO : keep filtering until *.isolines files are regenerated to be pre-filtered; remove the if afterwards.
+          if (!ftypes::IsIsolineChecker::Instance()(fb.GetTypes()))
+            isKeepSourceLine = true;
+        }
+        else if (isValidLine || holder.NeedProcessTriangles())
           SimplifyPoints(level, isCoast, rect, holder.GetSourcePoints(), points);
 
-        if (isLine)
-          holder.AddPoints(points, i);
-
-        if (isArea && holder.NeedProcessTriangles())
+        if (isValidLine)
         {
-          // simplify and serialize triangles
-          bool const good = isCoast || IsGoodArea(points, level);
+          // Discard closed lines which are degenerate (<=3 points, first == last)
+          // or too small for the current geom level (except roads).
+          if (isClosedLine && !isKeepSourceLine &&
+              (points.size() <= 3 || (!isRoad && !scales::IsGoodOutlineForLevel(level, points))))
+          {
+            LOG(LDEBUG, ("Closed line: too small or degenerate, points count", points.size(), "at scale", i, DebugPrint(fb)));
+            isValidLine = false;
+          }
+          else
+          {
+            CHECK(!isClosedLine && points.size() > 1 || points.size() > 3, (DebugPrint(fb)));
+            holder.AddPoints(points, i);
+          }
+        }
+
+        if (isValidArea && holder.NeedProcessTriangles())
+        {
+          // Simplify and serialize triangles.
+          bool const good = isCoast || scales::IsGoodOutlineForLevel(level, points);
 
           // At this point we don't need last point equal to first.
           CHECK_GREATER(points.size(), 0, ());
@@ -202,6 +242,8 @@ public:
             simplified.push_back({});
             simplified.back().swap(points);
           }
+          else
+            LOG(LDEBUG, ("Area: too small or degenerate 1st polygon of", polys.size(), ", points count", points.size(), "at scale", i, DebugPrint(fb)));
 
           auto iH = polys.begin();
           for (++iH; iH != polys.end(); ++iH)
@@ -212,20 +254,25 @@ public:
 
             // Increment level check for coastline polygons for the first scale level.
             // This is used for better coastlines quality.
-            if (IsGoodArea(simplified.back(), (isCoast && i == 0) ? level + 1 : level))
+            if (scales::IsGoodOutlineForLevel((isCoast && i == 0) ? level + 1 : level, simplified.back()))
             {
               // At this point we don't need last point equal to first.
-              CHECK_GREATER(simplified.back().size(), 0, ());
               simplified.back().pop_back();
             }
             else
             {
-              // Remove small polygon.
+              // Remove small or degenerate polygon.
               simplified.pop_back();
+              LOG(LDEBUG, ("Area: too small or degenerate 2nd+ polygon of", polys.size(), ", points count", simplified.back().size(), "at scale", i, DebugPrint(fb)));
             }
           }
 
-          if (!simplified.empty())
+          if (simplified.empty())
+          {
+            // Stop processing this area.
+            isValidArea = false;
+          }
+          else
             holder.AddTriangles(simplified, i);
         }
       }
@@ -266,19 +313,6 @@ private:
 
   using TmpFiles = std::vector<std::unique_ptr<TmpFile>>;
 
-  static bool IsGoodArea(Points const & poly, int level)
-  {
-    // Area has the same first and last points. That's why minimal number of points for
-    // area is 4.
-    if (poly.size() < 4)
-      return false;
-
-    m2::RectD r;
-    CalcRect(poly, r);
-
-    return scales::IsGoodForLevel(level, r);
-  }
-
   bool IsCountry() const { return m_header.GetType() == feature::DataHeader::MapType::Country; }
 
   static void SimplifyPoints(int level, bool isCoast, m2::RectD const & rect, Points const & in, Points & out)
@@ -320,6 +354,7 @@ bool GenerateFinalFeatures(feature::GenerateInfo const & info, std::string const
   std::string const srcFilePath = info.GetTmpFileName(name);
   std::string const dataFilePath = info.GetTargetFileName(name);
 
+  LOG(LINFO, ("Calculating middle points"));
   // Store cellIds for middle points.
   CalculateMidPoints midPoints;
   ForEachFeatureRawFormat(srcFilePath, [&midPoints](FeatureBuilder const & fb, uint64_t pos) {
@@ -357,6 +392,7 @@ bool GenerateFinalFeatures(feature::GenerateInfo const & info, std::string const
       // FeaturesCollector2 will create temporary file `dataFilePath + FEATURES_FILE_TAG`.
       // We cannot remove it in ~FeaturesCollector2(), we need to remove it in SCOPE_GUARD.
       SCOPE_GUARD(_, [&]() { Platform::RemoveFileIfExists(info.GetTargetFileName(name, FEATURES_FILE_TAG)); });
+      LOG(LINFO, ("Simplifying and filtering geometry for all geom levels"));
       FeaturesCollector2 collector(name, info, header, regionData, info.m_versionDate);
       for (auto const & point : midPoints.GetVector())
       {
@@ -368,10 +404,14 @@ bool GenerateFinalFeatures(feature::GenerateInfo const & info, std::string const
         collector(fb);
       }
 
+      LOG(LINFO, ("Writing features' data to", dataFilePath));
+
       // Update bounds with the limit rect corresponding to region borders.
       // Bounds before update can be too big because of big invisible features like a
       // relation that contains an entire country's border.
-      // Borders file may be unavailable when building test mwms.
+      // TODO: Borders file may be unavailable when building test mwms (why?).
+      // Probably its the reason resulting mwm sizes are unpredictable
+      // when building not the whole planet.
       m2::RectD bordersRect;
       if (borders::GetBordersRect(info.m_targetDir, name, bordersRect))
         collector.SetBounds(bordersRect);
diff --git a/generator/filter_world.cpp b/generator/filter_world.cpp
index 436b301d8b..65541079af 100644
--- a/generator/filter_world.cpp
+++ b/generator/filter_world.cpp
@@ -37,8 +37,11 @@ bool FilterWorld::IsInternationalAirport(feature::FeatureBuilder const & fb)
 // static
 bool FilterWorld::IsGoodScale(feature::FeatureBuilder const & fb)
 {
-  // GetMinFeatureDrawScale also checks suitable size for AREA features
-  return scales::GetUpperWorldScale() >= fb.GetMinFeatureDrawScale();
+  // GetMinDrawableScale also checks suitable size for AREA features.
+  int const minScale = GetMinDrawableScale(fb.GetTypesHolder(), fb.GetLimitRect(), fb.IsClosedLine());
+
+  // Some features become invisible after merge processing, so -1 is possible.
+  return scales::GetUpperWorldScale() >= minScale && minScale != -1;
 }
 
 // static
diff --git a/generator/geometry_holder.hpp b/generator/geometry_holder.hpp
index 6f708d370f..2a48256a7c 100644
--- a/generator/geometry_holder.hpp
+++ b/generator/geometry_holder.hpp
@@ -73,15 +73,7 @@ public:
     {
       // Inner geometry: store inside feature's header and keep
       // a simplification mask for scale-specific visibility of each point.
-      if (m_buffer.m_innerPts.empty())
-      {
-        // If geometry is added for the most detailed scale 3 only then
-        // the mask is never updated and left == 0, which works OK
-        // as the feature is not supposed to be using lower geometry scales anyway.
-        m_buffer.m_innerPts = points;
-      }
-      else
-        FillInnerPointsMask(points, scaleIndex);
+      FillInnerPoints(points, scaleIndex);
       m_current = points;
     }
     else
@@ -202,6 +194,7 @@ private:
     auto cp = m_header.GetGeometryCodingParams(i);
 
     // Optimization: Store first point once in header for outer linear features.
+    // @pastk: how does it help? why not store all points in the outer?
     cp.SetBasePoint(points[0]);
     // Can optimize here, but ... Make copy of vector.
     Points toSave(points.begin() + 1, points.end());
@@ -268,14 +261,24 @@ private:
     return true;
   }
 
-  void FillInnerPointsMask(Points const & points, uint32_t scaleIndex)
+  void FillInnerPoints(Points const & points, uint32_t scaleIndex)
   {
     auto const & src = m_buffer.m_innerPts;
-    CHECK(!src.empty(), ());
+
+    if (src.empty())
+    {
+      m_buffer.m_innerPts = points;
+      // Init the simplification mask to the scaleIndex.
+      // First and last points are present always, hence not stored in the mask.
+      for (size_t i = 1; i < points.size() - 1; ++i)
+        m_buffer.m_ptsSimpMask |= (scaleIndex << (2 * (i - 1)));
+      return;
+    }
 
     CHECK(feature::ArePointsEqual(src.front(), points.front()), ());
     CHECK(feature::ArePointsEqual(src.back(), points.back()), ());
 
+    uint32_t const mask = 0x3;
     size_t j = 1;
     for (size_t i = 1; i < points.size() - 1; ++i)
     {
@@ -283,8 +286,7 @@ private:
       {
         if (feature::ArePointsEqual(src[j], points[i]))
         {
-          // set corresponding 2 bits for source point [j] to scaleIndex
-          uint32_t mask = 0x3;
+          // Update corresponding 2 bits for the source point [j] to the scaleIndex.
           m_buffer.m_ptsSimpMask &= ~(mask << (2 * (j - 1)));
           m_buffer.m_ptsSimpMask |= (scaleIndex << (2 * (j - 1)));
           break;
diff --git a/generator/world_map_generator.hpp b/generator/world_map_generator.hpp
index c7ad602a55..09f797268b 100644
--- a/generator/world_map_generator.hpp
+++ b/generator/world_map_generator.hpp
@@ -62,7 +62,8 @@ class WorldMapGenerator
     /// This function is called after merging linear features.
     void operator()(feature::FeatureBuilder const & fb) override
     {
-      // do additional check for suitable size of feature
+      // Do additional check for suitable size of feature, because
+      // same check in NeedPushToWorld() applies to areas and closed lines only.
       if (NeedPushToWorld(fb) &&
           scales::IsGoodForLevel(scales::GetUpperWorldScale(), fb.GetLimitRect()))
         PushSure(fb);
diff --git a/geometry/algorithm.cpp b/geometry/algorithm.cpp
index 723bcedb1f..cbb13309ce 100644
--- a/geometry/algorithm.cpp
+++ b/geometry/algorithm.cpp
@@ -22,7 +22,10 @@ PointD CalculatePolyLineCenter::GetResult() const
   if (e == m_poly.begin())
   {
     /// @todo It's very strange, but we have linear objects with zero length.
+    // It's degenerate closed lines (size == 2, first == last), now we filter them
+    // in feature_sorter.cpp, but they could be present in older mwms still.
     LOG(LWARNING, ("Zero length linear object"));
+    CHECK_GREATER(m_poly.size(), 0, ());
     return e->m_p;
   }
 
diff --git a/indexer/feature.cpp b/indexer/feature.cpp
index 4e2681bae9..105a81f359 100644
--- a/indexer/feature.cpp
+++ b/indexer/feature.cpp
@@ -335,6 +335,13 @@ int8_t FeatureType::GetLayer()
   return m_params.layer;
 }
 
+// TODO: there is a room to store more information in Header2 (geometry header),
+// but it needs a mwm version change.
+// 1st bit - inner / outer flag
+// 4 more bits - inner points/triangles count or outer geom offsets mask
+//   (but actually its enough to store number of the first existing geom level only - 2 bits)
+// 3-5 more bits are spare
+// One of them could be used for a closed line flag to avoid storing identical first + last points.
 void FeatureType::ParseHeader2()
 {
   if (m_parsed.m_header2)
@@ -351,6 +358,7 @@ void FeatureType::ParseHeader2()
   {
     elemsCount = bitSource.Read(4);
     // For outer geometry read the geom scales (offsets) mask.
+    // For inner geometry remaining 4 bits are not used.
     if (elemsCount == 0)
       geomScalesMask = bitSource.Read(4);
     else
@@ -475,6 +483,13 @@ void FeatureType::ParseGeometry(int scale)
         }
         points.emplace_back(m_points.back());
 
+        // Treat closed degenerate zero-length lines (2 points, first == last) as an empty geometry,
+        // because the first and the last points are not excludable via the simplification mask.
+        // Don't discard 3-points degenerate lines, because there could be unfiltered closed roads
+        // like this in the older mwms (new mwms will have them converted into regular 2-points lines).
+        if (points.size() == 2 && points.front() == points.back())
+          points.clear();
+
         m_points.swap(points);
       }
 
@@ -736,6 +751,16 @@ bool FeatureType::IsEmptyGeometry(int scale)
   }
 }
 
+bool FeatureType::IsClosedLine() const
+{
+  if (GetGeomType() == GeomType::Line)
+  {
+    ASSERT(m_parsed.m_points, (m_id));
+    return m_points.size() > 1 && m_points.front() == m_points.back();
+  }
+  return false;
+}
+
 size_t FeatureType::GetPointsCount() const
 {
   ASSERT(m_parsed.m_points, ());
diff --git a/indexer/feature.hpp b/indexer/feature.hpp
index e55a278096..7b932b6bbf 100644
--- a/indexer/feature.hpp
+++ b/indexer/feature.hpp
@@ -96,6 +96,7 @@ public:
 
   m2::RectD GetLimitRect(int scale);
   m2::RectD const & GetLimitRectChecked() const;
+  bool IsClosedLine() const;
 
   bool IsEmptyGeometry(int scale);
 
diff --git a/indexer/feature_visibility.cpp b/indexer/feature_visibility.cpp
index 5fbcb3430e..8bed180bb6 100644
--- a/indexer/feature_visibility.cpp
+++ b/indexer/feature_visibility.cpp
@@ -10,6 +10,8 @@
 #include "base/assert.hpp"
 #include "base/checked_cast.hpp"
 
+#include "routing/routing_helpers.hpp"
+
 #include <algorithm>
 #include <array>
 
@@ -223,16 +225,25 @@ bool CanGenerateLike(vector<uint32_t> const & types, GeomType geomType)
 
 namespace
 {
-bool IsDrawableForIndexGeometryOnly(TypesHolder const & types, m2::RectD const & limitRect, int level)
-{
-  Classificator const & c = classif();
 
-  static uint32_t const buildingPartType = c.GetTypeByPath({"building:part"});
+// Used by the generator when
+// - building scale / visibility index (via FeatureShouldBeIndexed())
+// - simplifying geometry (via CalculateMidPoints and IsDrawableInRange())
+// - including features into the World map (via FilterWorld::IsAccepted() and NeedPushToWorld())
+bool IsDrawableForIndexGeometryOnly(TypesHolder const & types, m2::RectD const & limitRect, bool isClosedLine, int level)
+{
+  // Exclude too small closed lines (except roads).
+  if (isClosedLine && !routing::IsRoad(types) && !scales::IsGoodForLevel(level, limitRect))
+    return false;
 
   // 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;
+  if (types.GetGeomType() == GeomType::Area)
+  {
+    Classificator const & c = classif();
+    static uint32_t const buildingPartType = c.GetTypeByPath({"building:part"});
+    if (!types.Has(c.GetCoastType()) && !types.Has(buildingPartType) && !scales::IsGoodForLevel(level, limitRect))
+      return false;
+  }
 
   return true;
 }
@@ -259,15 +270,15 @@ bool IsDrawableForIndex(FeatureType & ft, int level)
          IsDrawableForIndexClassifOnly(TypesHolder(ft), level);
 }
 
-bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, int level)
+bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, bool isClosedLine, int level)
 {
-  return IsDrawableForIndexGeometryOnly(types, limitRect, level) &&
+  return IsDrawableForIndexGeometryOnly(types, limitRect, isClosedLine, level) &&
          IsDrawableForIndexClassifOnly(types, level);
 }
 
 bool IsDrawableForIndexGeometryOnly(FeatureType & ft, int level)
 {
-  return IsDrawableForIndexGeometryOnly(TypesHolder(ft), ft.GetLimitRectChecked(), level);
+  return IsDrawableForIndexGeometryOnly(TypesHolder(ft), ft.GetLimitRectChecked(), ft.IsClosedLine(), level);
 }
 
 bool IsUsefulType(uint32_t t, GeomType geomType, bool emptyName)
@@ -303,16 +314,16 @@ bool RemoveUselessTypes(vector<uint32_t> & types, GeomType geomType, bool emptyN
 
 int GetMinDrawableScale(FeatureType & ft)
 {
-  return GetMinDrawableScale(TypesHolder(ft), ft.GetLimitRectChecked());
+  return GetMinDrawableScale(TypesHolder(ft), ft.GetLimitRectChecked(), ft.IsClosedLine());
 }
 
-int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect)
+int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect, bool isClosedLine)
 {
   int const upBound = scales::GetUpperStyleScale();
 
   for (int level = 0; level <= upBound; ++level)
   {
-    if (IsDrawableForIndex(types, limitRect, level))
+    if (IsDrawableForIndex(types, limitRect, isClosedLine, level))
       return level;
   }
 
diff --git a/indexer/feature_visibility.hpp b/indexer/feature_visibility.hpp
index 48b8c6c986..d297060376 100644
--- a/indexer/feature_visibility.hpp
+++ b/indexer/feature_visibility.hpp
@@ -16,7 +16,7 @@ namespace feature
   bool IsCategoryNondrawableType(uint32_t type);
   bool IsUsefulType(uint32_t type);
   bool IsDrawableForIndex(FeatureType & ft, int level);
-  bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, int level);
+  bool IsDrawableForIndex(TypesHolder const & types, m2::RectD const & limitRect, bool isClosedLine, int level);
 
   // The separation into ClassifOnly and GeometryOnly versions is needed to speed up
   // the geometrical index (see indexer/scale_index_builder.hpp).
@@ -38,7 +38,7 @@ namespace feature
   /// @}
 
   int GetMinDrawableScale(FeatureType & ft);
-  int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect);
+  int GetMinDrawableScale(TypesHolder const & types, m2::RectD const & limitRect, bool isClosedLine);
   //int GetMinDrawableScaleGeometryOnly(TypesHolder const & types, m2::RectD limitRect);
   int GetMinDrawableScaleClassifOnly(TypesHolder const & types);
 
diff --git a/indexer/scale_index_builder.hpp b/indexer/scale_index_builder.hpp
index 40d35f8542..5f9a5d0f12 100644
--- a/indexer/scale_index_builder.hpp
+++ b/indexer/scale_index_builder.hpp
@@ -102,7 +102,7 @@ private:
     if (ft.IsEmptyGeometry(scale))
       return false;
 
-    // This function assumes that geometry rect for the needed scale is already initialized.
+    // Checks feature's geometry is suitable for the needed scale; assumes the geometry rect is already initialized.
     return feature::IsDrawableForIndexGeometryOnly(ft, scale);
   }
 
diff --git a/indexer/scales.cpp b/indexer/scales.cpp
index e577a76046..6ec1075fd2 100644
--- a/indexer/scales.cpp
+++ b/indexer/scales.cpp
@@ -1,6 +1,7 @@
 #include "indexer/scales.hpp"
-#include "geometry/mercator.hpp"
 
+#include "indexer/feature_algo.hpp"
+#include "geometry/mercator.hpp"
 #include "base/math.hpp"
 
 #include <algorithm>
@@ -55,10 +56,12 @@ namespace scales
 
   double GetEpsilonForSimplify(int level)
   {
-    // Keep better geometries on highest zoom to allow scaling them deeper
+    // Keep better geometries on highest zoom to allow scaling them deeper.
+    // Effectively it leads to x26 precision difference from geom scale 2 to 3.
+    // Keep crude geometries for all other zooms,
+    // x4 precision difference from geom scale 0 to 1 and 1 to 2.
     if (level == GetUpperScale())
       return GetEpsilonImpl(level, 0.4);
-    // Keep crude geometries for all other zooms
     else
       return GetEpsilonImpl(level, 1.3);
   }
@@ -69,4 +72,17 @@ namespace scales
     // assume that feature is always visible in upper scale
     return (level == GetUpperScale() || std::max(r.SizeX(), r.SizeY()) > GetEpsilonForLevel(level));
   }
+
+  bool IsGoodOutlineForLevel(int level, Points const & poly)
+  {
+    // Areas and closed lines have the same first and last points.
+    // Hence the minimum number of outline points is 4.
+    if (poly.size() < 4)
+      return false;
+
+    m2::RectD r;
+    feature::CalcRect(poly, r);
+
+    return IsGoodForLevel(level, r);
+  }
 } // namespace scales
diff --git a/indexer/scales.hpp b/indexer/scales.hpp
index cd9b94b827..4277bfdb91 100644
--- a/indexer/scales.hpp
+++ b/indexer/scales.hpp
@@ -39,4 +39,7 @@ namespace scales
   double GetEpsilonForLevel(int level);
   double GetEpsilonForSimplify(int level);
   bool IsGoodForLevel(int level, m2::RectD const & r);
+
+  using Points = std::vector<m2::PointD>;
+  bool IsGoodOutlineForLevel(int level, Points const & poly);
 }
diff --git a/platform/platform.hpp b/platform/platform.hpp
index e18ff07000..7864f26aea 100644
--- a/platform/platform.hpp
+++ b/platform/platform.hpp
@@ -132,7 +132,7 @@ public:
   /// @return always the same writable dir for current user with slash at the end
   std::string const & WritableDir() const
   {
-    ASSERT(!m_writableDir.empty(), ());
+    // The generator_tool could be run without -data_path (writableDir) option.
     return m_writableDir;
   }
   /// Set writable dir — use for testing and linux stuff only
diff --git a/topography_generator/generator.cpp b/topography_generator/generator.cpp
index ea50a98057..ed7988389e 100644
--- a/topography_generator/generator.cpp
+++ b/topography_generator/generator.cpp
@@ -593,6 +593,10 @@ void Generator::PackIsolinesForCountry(storage::CountryId const & countryId,
     return;
   }
 
+  // TODO : prepare simplified and filtered isolones for all geom levels here
+  // (ATM its the most detailed geom3 only) instead of in the generator
+  // to skip re-doing it for every maps gen. And it'll be needed anyway
+  // for the longer term vision to supply isolines in separately downloadable files.
   LOG(LINFO, ("Begin packing isolines for country", countryId));
 
   m2::RectD countryRect;
@@ -622,6 +626,8 @@ void Generator::PackIsolinesForCountry(storage::CountryId const & countryId,
 
       CropContours(countryRect, countryRegions, params.m_maxIsolineLength,
                    params.m_alitudesStepFactor, isolines);
+      // Simplification is done already while processing tiles in ProcessTile().
+      // But now a different country-specific simpificationZoom could be applied.
       if (params.m_simplificationZoom > 0)
         SimplifyContours(params.m_simplificationZoom, isolines);
 
diff --git a/topography_generator/utils/contours.hpp b/topography_generator/utils/contours.hpp
index b51e4a7e54..a3788a5705 100644
--- a/topography_generator/utils/contours.hpp
+++ b/topography_generator/utils/contours.hpp
@@ -5,6 +5,8 @@
 #include "geometry/point2d.hpp"
 #include "geometry/region2d.hpp"
 
+#include "indexer/scales.hpp"
+
 #include <vector>
 #include <unordered_map>
 
@@ -90,7 +92,14 @@ void SimplifyContours(int simplificationZoom, Contours<ValueType> & contours)
       std::vector<m2::PointD> contourSimple;
       feature::SimplifyPoints(m2::SquaredDistanceFromSegmentToPoint(),
                               simplificationZoom, contour, contourSimple);
-      contour = std::move(contourSimple);
+      // Discard closed lines which are degenerate (<=3 points) or too small for the requested zoom level.
+      /// @todo it doesn't fix all cases as the simplification algo
+      /// can produce e.g. a 5 points closed but degenerate line ("flat", not a loop anymore).
+      if (contourSimple.size() > 1 && contourSimple.front() == contourSimple.back() &&
+          !scales::IsGoodOutlineForLevel(simplificationZoom, contourSimple))
+        contour.clear();
+      else
+        contour = std::move(contourSimple);
     }
   }
 }