Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,30 @@
project(topography_generator_tool)
set(SRC
filters_impl.cpp
filters_impl.hpp
generator.cpp
generator.hpp
isolines_utils.hpp
isolines_profile.hpp
main.cpp
marching_squares/contours_builder.cpp
marching_squares/contours_builder.hpp
marching_squares/marching_squares.hpp
marching_squares/square.hpp
tile_filter.hpp
utils/contours.hpp
utils/contours_serdes.hpp
utils/values_provider.hpp
)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
storage
indexer
platform
coding
gflags::gflags
)

View file

@ -0,0 +1,24 @@
#include "topography_generator/filters_impl.hpp"
namespace topography_generator
{
std::vector<double> CalculateGaussianLinearKernel(double standardDeviation, double radiusFactor)
{
auto const kernelRadius = static_cast<int64_t>(ceil(radiusFactor * standardDeviation));
auto const kernelSize = 2 * kernelRadius + 1;
std::vector<double> linearKernel(kernelSize, 0);
double sum = 1.0;
linearKernel[kernelRadius] = 1.0;
for (int64_t i = 1; i <= kernelRadius; ++i)
{
double const val = exp(-i * i / (2 * standardDeviation * standardDeviation));
linearKernel[kernelRadius - i] = linearKernel[kernelRadius + i] = val;
sum += 2.0 * val;
}
for (auto & val : linearKernel)
val /= sum;
return linearKernel;
}
} // namespace topography_generator

View file

@ -0,0 +1,195 @@
#pragma once
#include "topography_generator/utils/values_provider.hpp"
#include "geometry/latlon.hpp"
#include <algorithm>
#include <vector>
namespace topography_generator
{
template <typename ValueType>
void GetExtendedTile(ms::LatLon const & leftBottom, size_t stepsInDegree, size_t tileSize, size_t tileSizeExtension,
ValuesProvider<ValueType> & valuesProvider, std::vector<ValueType> & extTileValues)
{
size_t const extendedTileSize = tileSize + 2 * tileSizeExtension;
extTileValues.resize(extendedTileSize * extendedTileSize);
double const step = 1.0 / stepsInDegree;
double const offset = step * tileSizeExtension;
// Store values from North to South.
ms::LatLon startPos = ms::LatLon(leftBottom.m_lat + 1.0 + offset, leftBottom.m_lon - offset);
for (size_t i = 0; i < extendedTileSize; ++i)
{
for (size_t j = 0; j < extendedTileSize; ++j)
{
auto pos = ms::LatLon(startPos.m_lat - i * step, startPos.m_lon + j * step);
auto val = valuesProvider.GetValue(pos);
if (val == valuesProvider.GetInvalidValue() && ((i < tileSizeExtension) || (i >= tileSizeExtension + tileSize) ||
(j < tileSizeExtension) || (j >= tileSizeExtension + tileSize)))
{
auto const ni = std::max(std::min(i, tileSizeExtension + tileSize - 1), tileSizeExtension);
auto const nj = std::max(std::min(j, tileSizeExtension + tileSize - 1), tileSizeExtension);
auto npos = ms::LatLon(startPos.m_lat - ni * step, startPos.m_lon + nj * step);
val = valuesProvider.GetValue(npos);
}
extTileValues[i * extendedTileSize + j] = val;
}
}
}
template <typename ValueType>
void ProcessWithLinearKernel(std::vector<double> const & kernel, size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues, std::vector<ValueType> & dstValues,
ValueType invalidValue)
{
auto const kernelSize = kernel.size();
auto const kernelRadius = kernel.size() / 2;
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
std::vector<ValueType> tempValues(tileSize, 0);
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
tempValues[j] = 0.0;
auto const origValue = srcValues[i * tileSize + j];
if (origValue == invalidValue)
{
tempValues[j] = invalidValue;
}
else
{
for (size_t k = 0; k < kernelSize; ++k)
{
size_t const srcIndex = i * tileSize + j - kernelRadius + k;
auto srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
tempValues[j] += kernel[k] * srcValue;
}
}
}
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
dstValues[i * tileSize + j] = tempValues[j];
}
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
tempValues[i] = 0.0;
auto const origValue = dstValues[i * tileSize + j];
if (origValue == invalidValue)
{
tempValues[i] = invalidValue;
}
else
{
for (size_t k = 0; k < kernelSize; ++k)
{
size_t const srcIndex = (i - kernelRadius + k) * tileSize + j;
auto srcValue = dstValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
tempValues[i] += kernel[k] * srcValue;
}
}
}
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
dstValues[i * tileSize + j] = tempValues[i];
}
}
template <typename ValueType>
void ProcessWithSquareKernel(std::vector<double> const & kernel, size_t kernelSize, size_t tileSize, size_t tileOffset,
std::vector<ValueType> const & srcValues, std::vector<ValueType> & dstValues,
ValueType invalidValue)
{
CHECK_EQUAL(kernelSize * kernelSize, kernel.size(), ());
size_t const kernelRadius = kernelSize / 2;
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
size_t const dstIndex = i * tileSize + j;
auto const origValue = srcValues[dstIndex];
if (origValue == invalidValue)
{
dstValues[dstIndex] = invalidValue;
}
else
{
dstValues[dstIndex] = 0;
for (size_t ki = 0; ki < kernelSize; ++ki)
{
for (size_t kj = 0; kj < kernelSize; ++kj)
{
size_t const srcIndex = (i - kernelRadius + ki) * tileSize + j - kernelRadius + kj;
auto const srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
dstValues[dstIndex] += kernel[ki * kernelSize + kj] * srcValue;
}
}
}
}
}
}
template <typename ValueType>
void ProcessMedian(size_t kernelRadius, size_t tileSize, size_t tileOffset, std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue)
{
CHECK_LESS_OR_EQUAL(kernelRadius, tileOffset, ());
CHECK_GREATER(tileSize, tileOffset * 2, ());
CHECK_EQUAL(dstValues.size(), tileSize * tileSize, ());
size_t const kernelSize = kernelRadius * 2 + 1;
std::vector<ValueType> kernel(kernelSize * kernelSize);
for (size_t i = tileOffset; i < tileSize - tileOffset; ++i)
{
for (size_t j = tileOffset; j < tileSize - tileOffset; ++j)
{
size_t const dstIndex = i * tileSize + j;
auto const origValue = srcValues[dstIndex];
if (origValue == invalidValue)
{
dstValues[dstIndex] = invalidValue;
}
else
{
size_t const startI = i - kernelRadius;
size_t const startJ = j - kernelRadius;
for (size_t ki = 0; ki < kernelSize; ++ki)
{
for (size_t kj = 0; kj < kernelSize; ++kj)
{
size_t const srcIndex = (startI + ki) * tileSize + startJ + kj;
auto srcValue = srcValues[srcIndex];
if (srcValue == invalidValue)
srcValue = origValue;
kernel[ki * kernelSize + kj] = srcValue;
}
}
std::sort(kernel.begin(), kernel.end());
dstValues[dstIndex] = kernel[kernelRadius];
}
}
}
}
// Calculate separable kernel for Gaussian blur. https://en.wikipedia.org/wiki/Gaussian_blur
std::vector<double> CalculateGaussianLinearKernel(double standardDeviation, double radiusFactor);
} // namespace topography_generator

View file

@ -0,0 +1,731 @@
#include "topography_generator/generator.hpp"
#include "topography_generator/isolines_utils.hpp"
#include "topography_generator/marching_squares/marching_squares.hpp"
#include "topography_generator/utils/contours_serdes.hpp"
#include "platform/platform.hpp"
#include "generator/srtm_parser.hpp"
#include "geometry/mercator.hpp"
#include "base/thread_pool_computational.hpp"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <set>
#include <vector>
namespace topography_generator
{
namespace
{
size_t constexpr kArcSecondsInDegree = 60 * 60;
int constexpr kAsterTilesLatTop = 60;
int constexpr kAsterTilesLatBottom = -60;
void MercatorRectToTilesRange(m2::RectD const & rect, int & left, int & bottom, int & right, int & top)
{
auto const leftBottom = mercator::ToLatLon(rect.LeftBottom());
auto const rightTop = mercator::ToLatLon(rect.RightTop());
left = static_cast<int>(floor(leftBottom.m_lon));
bottom = static_cast<int>(floor(leftBottom.m_lat));
right = std::min(179, static_cast<int>(floor(rightTop.m_lon)));
top = std::min(89, static_cast<int>(floor(rightTop.m_lat)));
}
std::string GetTileProfilesDir(std::string const & tilesDir)
{
return base::JoinPath(tilesDir, "tiles_profiles");
}
std::string GetTileProfilesFilePath(int lat, int lon, std::string const & profilesDir)
{
return GetIsolinesFilePath(lat, lon, profilesDir) + ".profiles";
}
std::string GetTilesDir(std::string const & tilesDir, std::string const & profileName)
{
return base::JoinPath(tilesDir, profileName);
}
void AppendTileProfile(std::string const & fileName, std::string const & profileName)
{
std::ofstream fout(fileName, std::ios::app);
fout << profileName << std::endl;
}
bool LoadTileProfiles(std::string const & fileName, std::set<std::string> & profileNames)
{
std::ifstream fin(fileName);
if (!fin)
return false;
std::string line;
while (std::getline(fin, line))
if (!line.empty())
profileNames.insert(line);
return true;
}
class SrtmProvider : public ValuesProvider<Altitude>
{
public:
explicit SrtmProvider(std::string const & srtmDir) : m_srtmManager(srtmDir) {}
void SetPrefferedTile(ms::LatLon const & pos)
{
m_preferredTile = &m_srtmManager.GetTile(pos);
m_leftBottomOfPreferredTile = {std::floor(pos.m_lat), std::floor(pos.m_lon)};
}
Altitude GetValue(ms::LatLon const & pos) override
{
auto const alt = GetValueImpl(pos);
if (IsValidAltitude(alt))
return alt;
return GetMedianValue(pos);
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
static bool IsValidAltitude(Altitude alt) { return alt != kInvalidAltitude && alt > -435 && alt < 8850; }
private:
Altitude GetValueImpl(ms::LatLon pos)
{
if (m_preferredTile != nullptr)
{
using mercator::kPointEqualityEps;
// Each SRTM tile overlaps the top row in the bottom tile and the right row in the left tile.
// Try to prevent loading a new tile if the position can be found in the loaded one.
auto const latDist = pos.m_lat - m_leftBottomOfPreferredTile.m_lat;
auto const lonDist = pos.m_lon - m_leftBottomOfPreferredTile.m_lon;
if (latDist > -kPointEqualityEps && latDist < 1.0 + kPointEqualityEps && lonDist > -kPointEqualityEps &&
lonDist < 1.0 + kPointEqualityEps)
{
if (latDist < 0.0)
pos.m_lat += kPointEqualityEps;
else if (latDist >= 1.0)
pos.m_lat -= kPointEqualityEps;
if (lonDist < 0.0)
pos.m_lon += kPointEqualityEps;
else if (lonDist >= 1.0)
pos.m_lon -= kPointEqualityEps;
return m_preferredTile->GetAltitude(pos);
}
}
return m_srtmManager.GetAltitude(pos);
}
Altitude GetMedianValue(ms::LatLon const & pos)
{
// Look around the position with invalid altitude
// and return median of surrounding valid altitudes.
double const step = 1.0 / kArcSecondsInDegree;
int const kMaxKernelRadius = 3;
std::vector<Altitude> kernel;
int kernelRadius = 0;
while (kernel.empty() && kernelRadius < kMaxKernelRadius)
{
++kernelRadius;
auto const kernelSize = static_cast<size_t>(kernelRadius * 2 + 1);
kernel.reserve(4 * (kernelSize - 1));
for (int i = -kernelRadius; i <= kernelRadius; ++i)
{
for (int j = -kernelRadius; j <= kernelRadius; ++j)
{
if (abs(i) != kernelRadius && abs(j) != kernelRadius)
continue;
auto const alt = GetValueImpl({pos.m_lat + i * step, pos.m_lon + j * step});
if (IsValidAltitude(alt))
kernel.push_back(alt);
}
}
}
if (kernel.empty())
{
LOG(LWARNING, ("Can't fix invalid value", GetValueImpl(pos), "at the position", pos));
return kInvalidAltitude;
}
std::nth_element(kernel.begin(), kernel.begin() + kernel.size() / 2, kernel.end());
return kernel[kernel.size() / 2];
}
generator::SrtmTileManager m_srtmManager;
generator::SrtmTile const * m_preferredTile = nullptr;
ms::LatLon m_leftBottomOfPreferredTile;
};
class RawAltitudesTile : public ValuesProvider<Altitude>
{
public:
RawAltitudesTile(std::vector<Altitude> const & values, int leftLon, int bottomLat)
: m_values(values)
, m_leftLon(leftLon)
, m_bottomLat(bottomLat)
{}
/// @todo Should we use the same approach as in SrtmTile::GetTriangleHeight/GetBilinearHeight?
/// This function is used in ASTER filter only.
Altitude GetValue(ms::LatLon const & pos) override
{
double ln = pos.m_lon - m_leftLon;
double lt = pos.m_lat - m_bottomLat;
lt = 1 - lt; // From North to South, the same direction as inside the SRTM tiles.
auto const row = std::lround(kArcSecondsInDegree * lt);
auto const col = std::lround(kArcSecondsInDegree * ln);
auto const ix = row * (kArcSecondsInDegree + 1) + col;
CHECK(ix < m_values.size(), (pos));
return m_values[ix];
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
private:
std::vector<Altitude> const & m_values;
int m_leftLon;
int m_bottomLat;
};
class SeamlessAltitudeProvider : public ValuesProvider<Altitude>
{
public:
using IsOnBorderFn = std::function<bool(ms::LatLon const & pos)>;
SeamlessAltitudeProvider(ValuesProvider<Altitude> & originalProvider, ValuesProvider<Altitude> & filteredProvider,
IsOnBorderFn && isOnBorderFn)
: m_originalProvider(originalProvider)
, m_filteredProvider(filteredProvider)
, m_isOnBorderFn(std::move(isOnBorderFn))
{}
Altitude GetValue(ms::LatLon const & pos) override
{
if (m_isOnBorderFn(pos))
{
// Check that we have original neighboring tile, use filtered if haven't.
auto const alt = m_originalProvider.GetValue(pos);
if (alt != kInvalidAltitude)
return alt;
}
return m_filteredProvider.GetValue(pos);
}
Altitude GetInvalidValue() const override { return kInvalidAltitude; }
private:
ValuesProvider<Altitude> & m_originalProvider;
ValuesProvider<Altitude> & m_filteredProvider;
IsOnBorderFn m_isOnBorderFn;
};
class TileIsolinesTask
{
public:
TileIsolinesTask(int left, int bottom, int right, int top, std::string const & srtmDir,
TileIsolinesParams const * params, bool forceRegenerate)
: m_srtmDir(srtmDir)
, m_srtmProvider(srtmDir)
, m_params(params)
, m_forceRegenerate(forceRegenerate)
{
CHECK(params != nullptr, ());
Init(left, bottom, right, top);
}
TileIsolinesTask(int left, int bottom, int right, int top, std::string const & srtmDir,
TileIsolinesProfileParams const * profileParams, bool forceRegenerate)
: m_srtmDir(srtmDir)
, m_srtmProvider(srtmDir)
, m_profileParams(profileParams)
, m_forceRegenerate(forceRegenerate)
{
CHECK(profileParams != nullptr, ());
Init(left, bottom, right, top);
}
void Do()
{
for (int lat = m_bottom; lat <= m_top; ++lat)
for (int lon = m_left; lon <= m_right; ++lon)
ProcessTile(lat, lon);
}
private:
void Init(int left, int bottom, int right, int top)
{
CHECK(right >= -179 && right <= 180, (right));
CHECK(left >= -180 && left <= 179, (left));
CHECK(top >= -89 && top <= 90, (top));
CHECK(bottom >= -90 && bottom <= 89, (bottom));
m_left = left;
m_bottom = bottom;
m_right = right;
m_top = top;
}
void ProcessTile(int lat, int lon)
{
auto const tileName = GetIsolinesTileBase(lat, lon);
if (m_profileParams != nullptr)
{
auto const profilesPath = GetTileProfilesFilePath(lat, lon, m_profileParams->m_tilesProfilesDir);
if (!GetPlatform().IsFileExistsByFullPath(profilesPath))
{
LOG(LINFO, ("SRTM tile", tileName, "doesn't have profiles, skip processing."));
return;
}
}
auto const & pl = GetPlatform();
if (!pl.IsFileExistsByFullPath(base::JoinPath(m_srtmDir, tileName + ".hgt")) &&
!pl.IsFileExistsByFullPath(generator::SrtmTile::GetPath(m_srtmDir, tileName)))
{
LOG(LINFO, ("SRTM tile", tileName, "doesn't exist, skip processing."));
return;
}
std::ostringstream os;
os << tileName << " (" << lat << ", " << lon << ")";
m_debugId = os.str();
if (m_profileParams != nullptr)
{
auto const profilesPath = GetTileProfilesFilePath(lat, lon, m_profileParams->m_tilesProfilesDir);
std::set<std::string> profileNames;
CHECK(LoadTileProfiles(profilesPath, profileNames) && !profileNames.empty(), (tileName));
for (auto const & profileName : profileNames)
{
auto const & params = m_profileParams->m_profiles.at(profileName);
ProcessTile(lat, lon, tileName, profileName, params);
}
}
else
{
ProcessTile(lat, lon, tileName, "none", *m_params);
}
}
void ProcessTile(int lat, int lon, std::string const & tileName, std::string const & profileName,
TileIsolinesParams const & params)
{
auto const outFile = GetIsolinesFilePath(lat, lon, params.m_outputDir);
if (!m_forceRegenerate && GetPlatform().IsFileExistsByFullPath(outFile))
{
LOG(LINFO, ("Isolines for", tileName, ", profile", profileName, "are ready, skip processing."));
return;
}
LOG(LINFO, ("Begin generating isolines for tile", tileName, ", profile", profileName));
m_srtmProvider.SetPrefferedTile({lat + 0.5, lon + 0.5});
Contours<Altitude> contours;
if (!params.m_filters.empty() && (lat >= kAsterTilesLatTop || lat < kAsterTilesLatBottom))
{
// Filter tiles converted from ASTER, cause they are noisy enough.
std::vector<Altitude> filteredValues = FilterTile(params.m_filters, ms::LatLon(lat, lon), kArcSecondsInDegree,
kArcSecondsInDegree + 1, m_srtmProvider);
RawAltitudesTile filteredProvider(filteredValues, lon, lat);
GenerateSeamlessContours(lat, lon, params, filteredProvider, contours);
}
else
{
GenerateSeamlessContours(lat, lon, params, m_srtmProvider, contours);
}
LOG(LINFO, ("Isolines for tile", tileName, ", profile", profileName, "min altitude", contours.m_minValue,
"max altitude", contours.m_maxValue, "invalid values count", contours.m_invalidValuesCount));
if (params.m_simplificationZoom > 0)
SimplifyContours(params.m_simplificationZoom, contours);
SaveContrours(outFile, std::move(contours));
LOG(LINFO, ("End generating isolines for tile", tileName, ", profile", profileName));
}
void GenerateSeamlessContours(int lat, int lon, TileIsolinesParams const & params,
ValuesProvider<Altitude> & altProvider, Contours<Altitude> & contours)
{
// Avoid seam between SRTM and ASTER.
if ((lat == kAsterTilesLatTop) || (lat == kAsterTilesLatBottom - 1))
{
m_srtmProvider.SetPrefferedTile(ms::LatLon(lat == kAsterTilesLatTop ? lat - 0.5 : lat + 0.5, lon));
SeamlessAltitudeProvider seamlessAltProvider(m_srtmProvider, altProvider, [](ms::LatLon const & pos)
{
// In case when two altitudes sources are used for altitudes extraction,
// for the same position on the border could be returned different altitudes.
// Force to use altitudes near the srtm/aster border from srtm source,
// it helps to avoid contours gaps due to different altitudes for equal positions.
return fabs(pos.m_lat - kAsterTilesLatTop) < mercator::kPointEqualityEps ||
fabs(pos.m_lat - kAsterTilesLatBottom) < mercator::kPointEqualityEps;
});
GenerateContours(lat, lon, params, seamlessAltProvider, contours);
}
else
{
GenerateContours(lat, lon, params, altProvider, contours);
}
}
void GenerateContours(int lat, int lon, TileIsolinesParams const & params, ValuesProvider<Altitude> & altProvider,
Contours<Altitude> & contours)
{
auto const leftBottom = ms::LatLon(lat, lon);
auto const rightTop = ms::LatLon(lat + 1.0, lon + 1.0);
auto const squaresStep = 1.0 / kArcSecondsInDegree * params.m_latLonStepFactor;
MarchingSquares<Altitude> squares(leftBottom, rightTop, squaresStep, params.m_alitudesStep, altProvider, m_debugId);
squares.GenerateContours(contours);
}
int m_left;
int m_bottom;
int m_right;
int m_top;
std::string m_srtmDir;
SrtmProvider m_srtmProvider;
TileIsolinesParams const * m_params = nullptr;
TileIsolinesProfileParams const * m_profileParams = nullptr;
bool m_forceRegenerate;
std::string m_debugId;
};
template <typename ParamsType>
void RunGenerateIsolinesTasks(int left, int bottom, int right, int top, std::string const & srtmPath,
ParamsType const & params, long threadsCount, long maxCachedTilesPerThread,
bool forceRegenerate)
{
std::vector<std::unique_ptr<TileIsolinesTask>> tasks;
CHECK_GREATER(right, left, ());
CHECK_GREATER(top, bottom, ());
int tilesRowPerTask = top - bottom;
int tilesColPerTask = right - left;
if (tilesRowPerTask * tilesColPerTask <= static_cast<long>(threadsCount))
{
tilesRowPerTask = 1;
tilesColPerTask = 1;
}
else
{
while (tilesRowPerTask * tilesColPerTask > static_cast<long>(maxCachedTilesPerThread))
if (tilesRowPerTask > tilesColPerTask)
tilesRowPerTask = (tilesRowPerTask + 1) / 2;
else
tilesColPerTask = (tilesColPerTask + 1) / 2;
}
base::ComputationalThreadPool threadPool(threadsCount);
for (int lat = bottom; lat < top; lat += tilesRowPerTask)
{
int const topLat = std::min(lat + tilesRowPerTask - 1, top - 1);
for (int lon = left; lon < right; lon += tilesColPerTask)
{
int const rightLon = std::min(lon + tilesColPerTask - 1, right - 1);
auto task = std::make_unique<TileIsolinesTask>(lon, lat, rightLon, topLat, srtmPath, &params, forceRegenerate);
threadPool.SubmitWork([task = std::move(task)]() { task->Do(); });
}
}
}
} // namespace
Generator::Generator(std::string const & srtmPath, long threadsCount, long maxCachedTilesPerThread,
bool forceRegenerate)
: m_threadsCount(threadsCount)
, m_maxCachedTilesPerThread(maxCachedTilesPerThread)
, m_srtmPath(srtmPath)
, m_forceRegenerate(forceRegenerate)
{}
void Generator::GenerateIsolines(int left, int bottom, int right, int top, TileIsolinesParams const & params)
{
RunGenerateIsolinesTasks(left, bottom, right, top, m_srtmPath, params, m_threadsCount, m_maxCachedTilesPerThread,
m_forceRegenerate);
}
void Generator::GenerateIsolines(int left, int bottom, int right, int top, std::string const & tilesProfilesDir)
{
TileIsolinesProfileParams params(m_profileToTileParams, tilesProfilesDir);
RunGenerateIsolinesTasks(left, bottom, right, top, m_srtmPath, params, m_threadsCount, m_maxCachedTilesPerThread,
m_forceRegenerate);
}
void Generator::GenerateIsolinesForCountries()
{
auto const & pl = GetPlatform();
if (!pl.IsFileExistsByFullPath(m_isolinesTilesOutDir) && !pl.MkDirRecursively(m_isolinesTilesOutDir))
{
LOG(LERROR, ("Can't create directory", m_isolinesTilesOutDir));
return;
}
std::set<std::string> checkedProfiles;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const profileName = countryParams.second.m_profileName;
if (checkedProfiles.find(profileName) != checkedProfiles.end())
continue;
checkedProfiles.insert(profileName);
auto const profileTilesDir = GetTilesDir(m_isolinesTilesOutDir, profileName);
if (!pl.IsFileExistsByFullPath(profileTilesDir) && !pl.MkDirChecked(profileTilesDir))
{
LOG(LERROR, ("Can't create directory", profileTilesDir));
return;
}
}
auto const tmpTileProfilesDir = GetTileProfilesDir(m_isolinesTilesOutDir);
Platform::RmDirRecursively(tmpTileProfilesDir);
if (!pl.MkDirChecked(tmpTileProfilesDir))
{
LOG(LERROR, ("Can't create directory", tmpTileProfilesDir));
return;
}
m2::RectI boundingRect;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & countryId = countryParams.first;
auto const & params = countryParams.second;
auto const countryFile = GetIsolinesFilePath(countryId, m_isolinesCountriesOutDir);
if (!m_forceRegenerate && pl.IsFileExistsByFullPath(countryFile))
{
LOG(LINFO, ("Isolines for", countryId, "are ready, skip processing."));
continue;
}
m2::RectD countryRect;
std::vector<m2::RegionD> countryRegions;
GetCountryRegions(countryId, countryRect, countryRegions);
for (auto const & region : countryRegions)
{
countryRect = region.GetRect();
int left, bottom, right, top;
MercatorRectToTilesRange(countryRect, left, bottom, right, top);
boundingRect.Add(m2::PointI(left, bottom));
boundingRect.Add(m2::PointI(right, top));
for (int lat = bottom; lat <= top; ++lat)
{
for (int lon = left; lon <= right; ++lon)
{
if (params.NeedSkipTile(lat, lon))
continue;
auto const tileProfilesFilePath = GetTileProfilesFilePath(lat, lon, tmpTileProfilesDir);
AppendTileProfile(tileProfilesFilePath, params.m_profileName);
}
}
}
}
if (!boundingRect.IsValid())
return;
LOG(LINFO, ("Generate isolines for tiles rect", boundingRect));
GenerateIsolines(boundingRect.LeftBottom().x, boundingRect.LeftBottom().y, boundingRect.RightTop().x + 1,
boundingRect.RightTop().y + 1, tmpTileProfilesDir);
}
void Generator::PackIsolinesForCountry(storage::CountryId const & countryId, IsolinesPackingParams const & params)
{
PackIsolinesForCountry(countryId, params, nullptr /*needSkipTileFn*/);
}
void Generator::PackIsolinesForCountry(storage::CountryId const & countryId, IsolinesPackingParams const & params,
NeedSkipTileFn const & needSkipTileFn)
{
auto const outFile = GetIsolinesFilePath(countryId, params.m_outputDir);
if (!m_forceRegenerate && GetPlatform().IsFileExistsByFullPath(outFile))
{
LOG(LINFO, ("Isolines for", countryId, "are ready, skip processing."));
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;
std::vector<m2::RegionD> countryRegions;
GetCountryRegions(countryId, countryRect, countryRegions);
int left, bottom, right, top;
MercatorRectToTilesRange(countryRect, left, bottom, right, top);
Contours<Altitude> countryIsolines;
countryIsolines.m_minValue = std::numeric_limits<Altitude>::max();
countryIsolines.m_maxValue = std::numeric_limits<Altitude>::min();
for (int lat = bottom; lat <= top; ++lat)
{
for (int lon = left; lon <= right; ++lon)
{
if (needSkipTileFn && needSkipTileFn(lat, lon))
continue;
Contours<Altitude> isolines;
auto const tileFilePath = GetIsolinesFilePath(lat, lon, params.m_isolinesTilesPath);
if (!LoadContours(tileFilePath, isolines))
continue;
LOG(LINFO, ("Begin packing isolines from tile", tileFilePath));
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);
countryIsolines.m_minValue = std::min(isolines.m_minValue, countryIsolines.m_minValue);
countryIsolines.m_maxValue = std::max(isolines.m_maxValue, countryIsolines.m_maxValue);
countryIsolines.m_valueStep = isolines.m_valueStep;
countryIsolines.m_invalidValuesCount += isolines.m_invalidValuesCount;
for (auto & levelIsolines : isolines.m_contours)
{
auto & dst = countryIsolines.m_contours[levelIsolines.first];
std::move(levelIsolines.second.begin(), levelIsolines.second.end(), std::back_inserter(dst));
}
LOG(LINFO, ("End packing isolines from tile", tileFilePath));
}
}
LOG(LINFO, ("End packing isolines for country", countryId, "min altitude", countryIsolines.m_minValue, "max altitude",
countryIsolines.m_maxValue));
SaveContrours(outFile, std::move(countryIsolines));
LOG(LINFO, ("Isolines saved to", outFile));
}
void Generator::PackIsolinesForCountries()
{
if (!GetPlatform().IsFileExistsByFullPath(m_isolinesCountriesOutDir) &&
!GetPlatform().MkDirRecursively(m_isolinesCountriesOutDir))
{
LOG(LERROR, ("Can't create directory", m_isolinesCountriesOutDir));
return;
}
base::ComputationalThreadPool threadPool(m_threadsCount);
size_t taskInd = 0;
size_t tasksCount = m_countriesToGenerate.m_countryParams.size();
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & countryId = countryParams.first;
auto const & params = countryParams.second;
threadPool.SubmitWork([this, countryId, taskInd, tasksCount, params]()
{
LOG(LINFO, ("Begin task", taskInd, "/", tasksCount, countryId));
auto const & packingParams = m_profileToPackingParams.at(params.m_profileName);
PackIsolinesForCountry(countryId, packingParams,
[&params](int lat, int lon) { return params.NeedSkipTile(lat, lon); });
LOG(LINFO, ("End task", taskInd, "/", tasksCount, countryId));
});
++taskInd;
}
}
void Generator::InitCountryInfoGetter(std::string const & dataDir)
{
CHECK(m_infoReader == nullptr, ());
GetPlatform().SetResourceDir(dataDir);
m_infoReader = storage::CountryInfoReader::CreateCountryInfoReader(GetPlatform());
CHECK(m_infoReader, ());
}
void Generator::InitProfiles(std::string const & isolinesProfilesFileName,
std::string const & countriesToGenerateFileName, std::string const & isolinesTilesOutDir,
std::string const & isolinesCountriesOutDir)
{
CHECK(Deserialize(isolinesProfilesFileName, m_profilesCollection), ());
CHECK(Deserialize(countriesToGenerateFileName, m_countriesToGenerate), ());
auto const & profiles = m_profilesCollection.m_profiles;
for (auto const & countryParams : m_countriesToGenerate.m_countryParams)
{
auto const & params = countryParams.second;
CHECK(profiles.find(params.m_profileName) != profiles.end(), ("Unknown profile name", params.m_profileName));
}
m_isolinesTilesOutDir = isolinesTilesOutDir;
m_isolinesCountriesOutDir = isolinesCountriesOutDir;
for (auto const & profile : m_profilesCollection.m_profiles)
{
auto const & profileName = profile.first;
auto const & profileParams = profile.second;
TileIsolinesParams tileParams;
tileParams.m_outputDir = GetTilesDir(isolinesTilesOutDir, profileName);
tileParams.m_latLonStepFactor = profileParams.m_latLonStepFactor;
tileParams.m_alitudesStep = profileParams.m_alitudesStep;
tileParams.m_simplificationZoom = profileParams.m_simplificationZoom;
if (profileParams.m_medianFilterR > 0)
tileParams.m_filters.emplace_back(std::make_unique<MedianFilter<Altitude>>(profileParams.m_medianFilterR));
if (profileParams.m_gaussianFilterStDev > 0.0 && profileParams.m_gaussianFilterRFactor > 0)
{
tileParams.m_filters.emplace_back(std::make_unique<GaussianFilter<Altitude>>(
profileParams.m_gaussianFilterStDev, profileParams.m_gaussianFilterRFactor));
}
m_profileToTileParams.emplace(profileName, std::move(tileParams));
IsolinesPackingParams packingParams;
packingParams.m_outputDir = isolinesCountriesOutDir;
packingParams.m_simplificationZoom = 0;
packingParams.m_alitudesStepFactor = 1;
packingParams.m_isolinesTilesPath = GetTilesDir(isolinesTilesOutDir, profileName);
packingParams.m_maxIsolineLength = profileParams.m_maxIsolinesLength;
m_profileToPackingParams.emplace(profileName, std::move(packingParams));
}
}
void Generator::GetCountryRegions(storage::CountryId const & countryId, m2::RectD & countryRect,
std::vector<m2::RegionD> & countryRegions)
{
countryRect = m_infoReader->GetLimitRectForLeaf(countryId);
size_t id;
for (id = 0; id < m_infoReader->GetCountries().size(); ++id)
if (m_infoReader->GetCountries().at(id).m_countryId == countryId)
break;
CHECK_LESS(id, m_infoReader->GetCountries().size(), ());
/// @todo Refactor using Memory[Mapped] reader for countries.
std::lock_guard guard(m_infoMutex);
m_infoReader->LoadRegionsFromDisk(id, countryRegions);
}
} // namespace topography_generator

View file

@ -0,0 +1,95 @@
#pragma once
#include "topography_generator/isolines_profile.hpp"
#include "topography_generator/isolines_utils.hpp"
#include "topography_generator/tile_filter.hpp"
#include "storage/country_info_getter.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/region2d.hpp"
#include <memory>
#include <string>
namespace topography_generator
{
struct TileIsolinesParams
{
Altitude m_alitudesStep = 10;
size_t m_latLonStepFactor = 1;
int m_simplificationZoom = 17; // Value == 0 disables simplification.
FiltersSequence<Altitude> m_filters;
std::string m_outputDir;
};
using ProfileToTileIsolinesParams = std::map<std::string, TileIsolinesParams>;
struct TileIsolinesProfileParams
{
TileIsolinesProfileParams(ProfileToTileIsolinesParams const & profiles, std::string const & tilesProfilesDir)
: m_profiles(profiles)
, m_tilesProfilesDir(tilesProfilesDir)
{}
ProfileToTileIsolinesParams const & m_profiles;
std::string m_tilesProfilesDir;
};
struct IsolinesPackingParams
{
size_t m_maxIsolineLength = 1000;
int m_simplificationZoom = 17; // Value == 0 disables simplification.
size_t m_alitudesStepFactor = 1;
std::string m_isolinesTilesPath;
std::string m_outputDir;
};
using ProfileToIsolinesPackingParams = std::map<std::string, IsolinesPackingParams>;
class Generator
{
public:
Generator(std::string const & srtmPath, long threadsCount, long maxCachedTilesPerThread, bool forceRegenerate);
void InitCountryInfoGetter(std::string const & dataDir);
void GenerateIsolines(int left, int bottom, int right, int top, TileIsolinesParams const & params);
void PackIsolinesForCountry(storage::CountryId const & countryId, IsolinesPackingParams const & params);
void InitProfiles(std::string const & isolinesProfilesFileName, std::string const & countriesToGenerateFileName,
std::string const & isolinesTilesOutDir, std::string const & isolinesCountriesOutDir);
void GenerateIsolinesForCountries();
void PackIsolinesForCountries();
private:
void GenerateIsolines(int left, int bottom, int right, int top, std::string const & tilesProfilesDir);
using NeedSkipTileFn = std::function<bool(int lat, int lon)>;
void PackIsolinesForCountry(storage::CountryId const & countryId, IsolinesPackingParams const & params,
NeedSkipTileFn const & needSkipTileFn);
void GetCountryRegions(storage::CountryId const & countryId, m2::RectD & countryRect,
std::vector<m2::RegionD> & countryRegions);
std::string m_isolinesTilesOutDir;
std::string m_isolinesCountriesOutDir;
CountriesToGenerate m_countriesToGenerate;
IsolinesProfilesCollection m_profilesCollection;
ProfileToTileIsolinesParams m_profileToTileParams;
ProfileToIsolinesPackingParams m_profileToPackingParams;
std::mutex m_infoMutex;
std::unique_ptr<storage::CountryInfoReader> m_infoReader;
// They can't be negative, it is done to avoid compiler warnings.
long m_threadsCount;
long m_maxCachedTilesPerThread;
std::string m_srtmPath;
bool m_forceRegenerate;
};
} // namespace topography_generator

View file

@ -0,0 +1,135 @@
#pragma once
#include "topography_generator/isolines_utils.hpp"
#include "storage/storage_defines.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/serdes_json.hpp"
#include "base/visitor.hpp"
#include <map>
#include <string>
#include <unordered_set>
namespace topography_generator
{
struct IsolinesProfile
{
uint32_t m_alitudesStep = 10;
uint8_t m_latLonStepFactor = 1;
uint32_t m_maxIsolinesLength = 1000;
uint8_t m_simplificationZoom = 17; // Value == 0 disables simplification.
uint8_t m_medianFilterR = 1; // Value == 0 disables filter.
double m_gaussianFilterStDev = 2.0; // Value == 0.0 disables filter.
double m_gaussianFilterRFactor = 1.0; // Value == 0.0 disables filter.
DECLARE_VISITOR_AND_DEBUG_PRINT(IsolinesProfile, visitor(m_alitudesStep, "alitudesStep"),
visitor(m_latLonStepFactor, "latLonStepFactor"),
visitor(m_maxIsolinesLength, "maxIsolinesLength"),
visitor(m_simplificationZoom, "simplificationZoom"),
visitor(m_medianFilterR, "medianFilterR"),
visitor(m_gaussianFilterStDev, "gaussianFilterStDev"),
visitor(m_gaussianFilterRFactor, "gaussianFilterRFactor"))
};
struct IsolinesProfilesCollection
{
std::map<std::string, IsolinesProfile> m_profiles;
DECLARE_VISITOR_AND_DEBUG_PRINT(IsolinesProfilesCollection, visitor(m_profiles, "profiles"))
};
struct TileCoord
{
TileCoord() = default;
TileCoord(int bottomLat, int leftLon) : m_leftLon(leftLon), m_bottomLat(bottomLat) {}
int32_t m_leftLon = 0;
int32_t m_bottomLat = 0;
bool operator==(TileCoord const & rhs) const { return m_leftLon == rhs.m_leftLon && m_bottomLat == rhs.m_bottomLat; }
DECLARE_VISITOR_AND_DEBUG_PRINT(TileCoord, visitor(m_bottomLat, "bottomLat"), visitor(m_leftLon, "leftLon"))
};
struct TileCoordHash
{
size_t operator()(TileCoord const & coord) const
{
return (static_cast<size_t>(coord.m_leftLon) << 32u) | static_cast<size_t>(coord.m_bottomLat);
}
};
struct CountryIsolinesParams
{
std::string m_profileName;
std::unordered_set<TileCoord, TileCoordHash> m_tileCoordsSubset;
bool m_tilesAreBanned;
bool NeedSkipTile(int lat, int lon) const
{
if (m_tileCoordsSubset.empty())
return false;
TileCoord coord(lat, lon);
auto const found = m_tileCoordsSubset.find(coord) != m_tileCoordsSubset.end();
return m_tilesAreBanned == found;
}
DECLARE_VISITOR_AND_DEBUG_PRINT(CountryIsolinesParams, visitor(m_profileName, "profileName"),
visitor(m_tileCoordsSubset, "tileCoordsSubset"),
visitor(m_tilesAreBanned, "tilesAreBanned"))
};
struct CountriesToGenerate
{
std::map<storage::CountryId, CountryIsolinesParams> m_countryParams;
DECLARE_VISITOR_AND_DEBUG_PRINT(CountriesToGenerate, visitor(m_countryParams, "countryParams"))
};
template <typename DataType>
bool Serialize(std::string const & fileName, DataType const & data)
{
try
{
FileWriter writer(fileName);
coding::SerializerJson<FileWriter> ser(writer);
ser(data);
return true;
}
catch (base::Json::Exception & ex)
{
LOG(LERROR, ("Serialization to json failed, file name", fileName, ", reason:", ex.Msg()));
}
catch (FileWriter::Exception const & ex)
{
LOG(LERROR, ("Can't write file", fileName, ", reason:", ex.Msg()));
}
return false;
}
template <typename DataType>
bool Deserialize(std::string const & fileName, DataType & data)
{
try
{
FileReader reader(fileName);
NonOwningReaderSource source(reader);
coding::DeserializerJson des(source);
des(data);
return true;
}
catch (base::Json::Exception & ex)
{
LOG(LERROR, ("Deserialization from json failed, file name", fileName, ", reason:", ex.Msg()));
}
catch (FileReader::Exception const & ex)
{
LOG(LERROR, ("Can't read file", fileName, ", reason:", ex.Msg()));
}
return false;
}
} // namespace topography_generator

View file

@ -0,0 +1,35 @@
#pragma once
#include "generator/srtm_parser.hpp"
#include "storage/storage_defines.hpp"
#include "indexer/feature_altitude.hpp"
#include "base/file_name_utils.hpp"
#include <string>
namespace topography_generator
{
using Altitude = geometry::Altitude;
Altitude constexpr kInvalidAltitude = geometry::kInvalidAltitude;
constexpr char const * const kIsolinesExt = ".isolines";
inline std::string GetIsolinesTileBase(int bottomLat, int leftLon)
{
auto const centerPoint = ms::LatLon(bottomLat + 0.5, leftLon + 0.5);
return generator::SrtmTile::GetBase(centerPoint);
}
inline std::string GetIsolinesFilePath(int bottomLat, int leftLon, std::string const & dir)
{
auto const fileName = GetIsolinesTileBase(bottomLat, leftLon);
return base::JoinPath(dir, fileName + kIsolinesExt);
}
inline std::string GetIsolinesFilePath(storage::CountryId const & countryId, std::string const & dir)
{
return base::JoinPath(dir, countryId + kIsolinesExt);
}
} // namespace topography_generator

View file

@ -0,0 +1,206 @@
#include "generator.hpp"
#include "generator/utils.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <gflags/gflags.h>
#include <cstdlib>
// Common option for all modes
DEFINE_bool(force, false, "Force to regenerate isolines for tiles and countries.");
// Options for automatic isolines generating mode.
DEFINE_string(profiles_path, "",
"Automatic isolines generating mode. "
"Path to a json file with isolines profiles.");
DEFINE_string(countries_to_generate_path, "",
"Automatic isolines generating mode. "
"Path to a json file with countries to generate.");
DEFINE_string(tiles_isolines_out_dir, "",
"Automatic isolines generating mode. Path to output "
"intermediate directory with tiles isolines.");
DEFINE_string(countries_isolines_out_dir, "",
"Automatic isolines generating mode. "
"Path to output directory with countries isolines.");
// Common option for automatic isolines generating mode and custom packing mode.
DEFINE_string(data_dir, "", "Path to data directory.");
// Common option for automatic isolines generating mode and custom generating mode.
DEFINE_string(srtm_path, "", "Path to srtm directory.");
DEFINE_uint64(threads, 4, "Number of threads.");
DEFINE_uint64(tiles_per_thread, 9, "Max cached tiles per thread");
// Common options for custom isolines generating and custom packing modes.
DEFINE_string(out_dir, "", "Path to output directory.");
DEFINE_uint64(simpl_zoom, 16, "Isolines simplification zoom.");
// Options for custom isolines packing mode.
DEFINE_string(countryId, "", "Custom isolines packing mode. Pack isolines for countryId.");
DEFINE_string(isolines_path, "", "Custom isolines packing mode. Path to the directory with isolines tiles.");
DEFINE_uint64(max_length, 1000, "Custom isolines packing mode. Isolines max length.");
DEFINE_uint64(alt_step_factor, 1, "Custom isolines packing mode. Altitude step factor.");
// Options for custom isolines generating mode.
DEFINE_int32(left, 0, "Custom isolines generating mode. Left longitude of tiles rect [-180, 179].");
DEFINE_int32(right, 0, "Custom isolines generating mode. Right longitude of tiles rect [-179, 180].");
DEFINE_int32(bottom, 0, "Custom isolines generating mode. Bottom latitude of tiles rect [-90, 89].");
DEFINE_int32(top, 0, "Custom isolines generating mode. Top latitude of tiles rect [-89, 90].");
DEFINE_uint64(isolines_step, 10, "Custom isolines generating mode. Isolines step in meters.");
DEFINE_uint64(latlon_step_factor, 2, "Custom isolines generating mode. Lat/lon step factor.");
DEFINE_double(gaussian_st_dev, 2.0, "Custom isolines generating mode. Gaussian filter standard deviation.");
DEFINE_double(gaussian_r_factor, 1.0, "Custom isolines generating mode. Gaussian filter radius factor.");
DEFINE_uint64(median_r, 1, "Custom isolines generating mode. Median filter radius.");
using namespace topography_generator;
MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
{
gflags::SetUsageMessage(
"\n\nThis tool generates isolines and works in the following modes:\n"
"1. Automatic isolines generating mode. Generates a binary file with isolines for each\n"
" country id from the countries_to_generate_path. Gets options for isolines generating\n"
" from the corresponding to the country profile in profiles_path.\n"
" Stores intermediate binary isolines tiles to tiles_isolines_out_dir and result countries\n"
" isolines to countries_isolines_out_dir.\n"
"2. Custom isolines generating mode. Generates binary tile with isolines for each SRTM tile in the\n"
" specified tile rect.\n"
" Mode activates by passing a valid tiles rect.\n"
" An isoline would be generated for each isolines_step difference in height.\n"
" Tiles for lat >= 60 && lat < -60 (converted from ASTER source) can be filtered by\n"
" median and/or gaussian filters.\n"
" Median filter activates by nonzero filter kernel radius median_r.\n"
" Gaussian filter activates by gaussian_st_dev > 0.0 && gaussian_r_factor > 0.0 parameters.\n"
" Contours generating steps through altitudes matrix of SRTM tile can be adjusted by\n"
" latlon_step_factor parameter.\n"
" Isolines simplification activates by nonzero simpl_zoom [1..17]\n"
"\n"
"3. Custom packing isolines from ready tiles into a binary file for specified country id.\n"
" Mode activates by passing a countryId parameter.\n"
" Tool gets isolines from the tiles, covered by the country regions, selects\n"
" altitude levels with alt_step_factor (if a tile stores altitudes for each 10 meters\n"
" and alt_step_factor == 5, the result binary file will store altitudes for each 50 meters).\n"
" Isolines cropped by the country regions and cut by max_length parameter.\n"
" Isolines simplification activates by nonzero simpl_zoom [1..17]\n\n");
gflags::ParseCommandLineFlags(&argc, &argv, true);
bool isCustomGeneratingMode = false;
bool isCustomPackingMode = false;
bool const isAutomaticMode = !FLAGS_profiles_path.empty() || !FLAGS_countries_to_generate_path.empty() ||
!FLAGS_tiles_isolines_out_dir.empty() || !FLAGS_countries_isolines_out_dir.empty();
if (isAutomaticMode)
{
if (FLAGS_profiles_path.empty() || FLAGS_countries_to_generate_path.empty() ||
FLAGS_tiles_isolines_out_dir.empty() || FLAGS_countries_isolines_out_dir.empty())
{
LOG(LERROR, ("To run automatic isolines generating mode all the params profiles_path, "
"countries_to_generate_path, tiles_isolines_out_dir, countries_isolines_out_dir "
"must be set."));
return EXIT_FAILURE;
}
}
else
{
if (FLAGS_out_dir.empty())
{
LOG(LERROR, ("out_dir must be set."));
return EXIT_FAILURE;
}
auto const validTilesRect = FLAGS_right > FLAGS_left && FLAGS_top > FLAGS_bottom && FLAGS_right <= 180 &&
FLAGS_left >= -180 && FLAGS_top <= 90 && FLAGS_bottom >= -90;
isCustomGeneratingMode = validTilesRect;
isCustomPackingMode = !FLAGS_countryId.empty();
if (isCustomGeneratingMode && isCustomPackingMode)
{
LOG(LERROR, ("Both tiles rect and country id are set. Сhoose one operation: "
"generating of the tiles rect or packing tiles for the country"));
return EXIT_FAILURE;
}
if (!isCustomGeneratingMode && !isCustomPackingMode)
{
LOG(LERROR, ("Specify isolines profiles and countries to generate, or a valid tiles rect, "
"or a country id."));
return EXIT_FAILURE;
}
}
if (isCustomPackingMode || isAutomaticMode)
{
if (FLAGS_data_dir.empty())
{
LOG(LERROR, ("data_dir must be set."));
return EXIT_FAILURE;
}
}
if (isCustomGeneratingMode || isAutomaticMode)
{
if (FLAGS_srtm_path.empty())
{
LOG(LERROR, ("srtm_path must be set."));
return EXIT_FAILURE;
}
}
Generator generator(FLAGS_srtm_path, FLAGS_threads, FLAGS_tiles_per_thread, FLAGS_force);
if (isAutomaticMode)
{
generator.InitCountryInfoGetter(FLAGS_data_dir);
generator.InitProfiles(FLAGS_profiles_path, FLAGS_countries_to_generate_path, FLAGS_tiles_isolines_out_dir,
FLAGS_countries_isolines_out_dir);
generator.GenerateIsolinesForCountries();
generator.PackIsolinesForCountries();
return EXIT_SUCCESS;
}
if (isCustomPackingMode)
{
if (FLAGS_isolines_path.empty())
{
LOG(LERROR, ("isolines_path must be set."));
return EXIT_FAILURE;
}
IsolinesPackingParams params;
params.m_outputDir = FLAGS_out_dir;
params.m_simplificationZoom = static_cast<int>(FLAGS_simpl_zoom);
params.m_maxIsolineLength = FLAGS_max_length;
params.m_alitudesStepFactor = FLAGS_alt_step_factor;
params.m_isolinesTilesPath = FLAGS_isolines_path;
generator.InitCountryInfoGetter(FLAGS_data_dir);
generator.PackIsolinesForCountry(FLAGS_countryId, params);
return EXIT_SUCCESS;
}
CHECK(isCustomGeneratingMode, ());
TileIsolinesParams params;
if (FLAGS_median_r > 0)
params.m_filters.emplace_back(std::make_unique<MedianFilter<Altitude>>(FLAGS_median_r));
if (FLAGS_gaussian_st_dev > 0.0 && FLAGS_gaussian_r_factor > 0)
{
params.m_filters.emplace_back(
std::make_unique<GaussianFilter<Altitude>>(FLAGS_gaussian_st_dev, FLAGS_gaussian_r_factor));
}
params.m_outputDir = FLAGS_out_dir;
params.m_alitudesStep = FLAGS_isolines_step;
params.m_latLonStepFactor = FLAGS_latlon_step_factor;
params.m_simplificationZoom = static_cast<int>(FLAGS_simpl_zoom);
generator.GenerateIsolines(FLAGS_left, FLAGS_bottom, FLAGS_right, FLAGS_top, params);
return EXIT_SUCCESS;
});

View file

@ -0,0 +1,94 @@
#include "topography_generator/marching_squares/contours_builder.hpp"
#include "geometry/mercator.hpp"
namespace topography_generator
{
ContoursBuilder::ContoursBuilder(size_t levelsCount, std::string const & debugId)
: m_levelsCount(levelsCount)
, m_finalizedContours(levelsCount)
, m_activeContours(levelsCount)
, m_debugId(debugId)
{}
void ContoursBuilder::AddSegment(size_t levelInd, ms::LatLon const & beginPos, ms::LatLon const & endPos)
{
if (beginPos.EqualDxDy(endPos, mercator::kPointEqualityEps))
return;
CHECK_LESS(levelInd, m_levelsCount, (m_debugId));
auto contourItBefore = FindContourWithEndPoint(levelInd, beginPos);
auto contourItAfter = FindContourWithStartPoint(levelInd, endPos);
auto const connectStart = contourItBefore != m_activeContours[levelInd].end();
auto const connectEnd = contourItAfter != m_activeContours[levelInd].end();
if (connectStart && connectEnd && contourItBefore != contourItAfter)
{
std::move(contourItAfter->m_countour.begin(), contourItAfter->m_countour.end(),
std::back_inserter(contourItBefore->m_countour));
contourItBefore->m_active = true;
m_activeContours[levelInd].erase(contourItAfter);
}
else if (connectStart)
{
contourItBefore->m_countour.push_back(endPos);
contourItBefore->m_active = true;
}
else if (connectEnd)
{
contourItAfter->m_countour.push_front(beginPos);
contourItAfter->m_active = true;
}
else
{
m_activeContours[levelInd].emplace_back(ContourRaw({beginPos, endPos}));
}
}
void ContoursBuilder::BeginLine()
{
for (auto & contoursList : m_activeContours)
for (auto & activeContour : contoursList)
activeContour.m_active = false;
}
void ContoursBuilder::EndLine(bool finalLine)
{
for (size_t levelInd = 0; levelInd < m_levelsCount; ++levelInd)
{
auto & contours = m_activeContours[levelInd];
auto it = contours.begin();
while (it != contours.end())
{
if (!it->m_active || finalLine)
{
m_finalizedContours[levelInd].push_back(std::move(it->m_countour));
it = contours.erase(it);
}
else
{
++it;
}
}
}
}
ContoursBuilder::ActiveContourIter ContoursBuilder::FindContourWithStartPoint(size_t levelInd, ms::LatLon const & pos)
{
auto & contours = m_activeContours[levelInd];
for (auto it = contours.begin(); it != contours.end(); ++it)
if (it->m_countour.front().EqualDxDy(pos, mercator::kPointEqualityEps))
return it;
return contours.end();
}
ContoursBuilder::ActiveContourIter ContoursBuilder::FindContourWithEndPoint(size_t levelInd, ms::LatLon const & pos)
{
auto & contours = m_activeContours[levelInd];
for (auto it = contours.begin(); it != contours.end(); ++it)
if (it->m_countour.back().EqualDxDy(pos, mercator::kPointEqualityEps))
return it;
return contours.end();
}
} // namespace topography_generator

View file

@ -0,0 +1,70 @@
#pragma once
#include "topography_generator/utils/contours.hpp"
#include "geometry/latlon.hpp"
#include "geometry/mercator.hpp"
#include <algorithm>
#include <deque>
#include <list>
#include <unordered_map>
#include <vector>
namespace topography_generator
{
class ContoursBuilder
{
public:
ContoursBuilder(size_t levelsCount, std::string const & debugId);
void AddSegment(size_t levelInd, ms::LatLon const & beginPos, ms::LatLon const & endPos);
void BeginLine();
void EndLine(bool finalLine);
template <typename ValueType>
void GetContours(ValueType minValue, ValueType valueStep,
std::unordered_map<ValueType, std::vector<Contour>> & contours)
{
contours.clear();
for (size_t i = 0; i < m_finalizedContours.size(); ++i)
{
auto const levelValue = minValue + i * valueStep;
auto const & contoursList = m_finalizedContours[i];
for (auto const & contour : contoursList)
{
Contour contourMerc;
contourMerc.reserve(contour.size());
std::transform(contour.begin(), contour.end(), std::back_inserter(contourMerc),
[](ms::LatLon const & pt) { return mercator::FromLatLon(pt); });
contours[levelValue].emplace_back(std::move(contourMerc));
}
}
}
private:
using ContourRaw = std::deque<ms::LatLon>;
using ContoursList = std::list<ContourRaw>;
struct ActiveContour
{
explicit ActiveContour(ContourRaw && isoline) : m_countour(std::move(isoline)) {}
ContourRaw m_countour;
bool m_active = true;
};
using ActiveContoursList = std::list<ActiveContour>;
using ActiveContourIter = ActiveContoursList::iterator;
ActiveContourIter FindContourWithStartPoint(size_t levelInd, ms::LatLon const & pos);
ActiveContourIter FindContourWithEndPoint(size_t levelInd, ms::LatLon const & pos);
size_t const m_levelsCount;
std::vector<ContoursList> m_finalizedContours;
std::vector<ActiveContoursList> m_activeContours;
std::string m_debugId;
};
} // namespace topography_generator

View file

@ -0,0 +1,128 @@
#pragma once
#include "topography_generator/marching_squares/contours_builder.hpp"
#include "topography_generator/marching_squares/square.hpp"
#include "topography_generator/utils/contours.hpp"
#include "topography_generator/utils/values_provider.hpp"
#include "base/logging.hpp"
namespace topography_generator
{
template <typename ValueType>
class MarchingSquares
{
public:
MarchingSquares(ms::LatLon const & leftBottom, ms::LatLon const & rightTop, double step, ValueType valueStep,
ValuesProvider<ValueType> & valuesProvider, std::string const & debugId)
: m_leftBottom(leftBottom)
, m_rightTop(rightTop)
, m_step(step)
, m_valueStep(valueStep)
, m_valuesProvider(valuesProvider)
, m_debugId(debugId)
{
CHECK_GREATER(m_rightTop.m_lon, m_leftBottom.m_lon, ());
CHECK_GREATER(m_rightTop.m_lat, m_leftBottom.m_lat, ());
m_stepsCountLon = static_cast<size_t>((m_rightTop.m_lon - m_leftBottom.m_lon) / step);
m_stepsCountLat = static_cast<size_t>((m_rightTop.m_lat - m_leftBottom.m_lat) / step);
CHECK_GREATER(m_stepsCountLon, 0, ());
CHECK_GREATER(m_stepsCountLat, 0, ());
}
void GenerateContours(Contours<ValueType> & result)
{
std::vector<ValueType> grid((m_stepsCountLat + 1) * (m_stepsCountLon + 1));
ScanValuesInRect(result, grid);
result.m_valueStep = m_valueStep;
auto const levelsCount = static_cast<size_t>(result.m_maxValue - result.m_minValue) / m_valueStep;
if (levelsCount == 0)
{
LOG(LINFO, ("Contours can't be generated: min and max values are equal:", result.m_minValue));
return;
}
ContoursBuilder contoursBuilder(levelsCount, m_debugId);
Square<ValueType> square(result.m_minValue, m_valueStep, m_debugId);
for (size_t i = 0; i < m_stepsCountLat; ++i)
{
contoursBuilder.BeginLine();
for (size_t j = 0; j < m_stepsCountLon; ++j)
{
// This point should be calculated _exact_ the same way as in ScanValuesInRect.
// leftBottom + m_step doesn't work due to different floating results.
square.Init(m_leftBottom.m_lon + m_step * j, // Left
m_leftBottom.m_lat + m_step * i, // Bottom
m_leftBottom.m_lon + m_step * (j + 1), // Right
m_leftBottom.m_lat + m_step * (i + 1), // Top
grid[Idx(i, j)], // LB
grid[Idx(i, j + 1)], // RB
grid[Idx(i + 1, j)], // LT
grid[Idx(i + 1, j + 1)], // RT
m_valuesProvider.GetInvalidValue());
square.GenerateSegments(contoursBuilder);
}
contoursBuilder.EndLine(i == m_stepsCountLat - 1 /* finalLine */);
}
contoursBuilder.GetContours(result.m_minValue, result.m_valueStep, result.m_contours);
}
private:
size_t Idx(size_t iLat, size_t jLon) const { return iLat * (m_stepsCountLon + 1) + jLon; }
void ScanValuesInRect(Contours<ValueType> & res, std::vector<ValueType> & grid) const
{
res.m_minValue = res.m_maxValue = m_valuesProvider.GetValue(m_leftBottom);
res.m_invalidValuesCount = 0;
for (size_t i = 0; i <= m_stepsCountLat; ++i)
{
for (size_t j = 0; j <= m_stepsCountLon; ++j)
{
ms::LatLon const pos(m_leftBottom.m_lat + m_step * i, m_leftBottom.m_lon + m_step * j);
auto const value = m_valuesProvider.GetValue(pos);
grid[Idx(i, j)] = value;
if (value == m_valuesProvider.GetInvalidValue())
{
++res.m_invalidValuesCount;
continue;
}
if (value < res.m_minValue)
res.m_minValue = value;
if (value > res.m_maxValue)
res.m_maxValue = value;
}
}
if (res.m_invalidValuesCount > 0)
LOG(LWARNING, ("Tile", m_debugId, "contains", res.m_invalidValuesCount, "invalid values."));
Square<ValueType>::ToLevelsRange(m_valueStep, res.m_minValue, res.m_maxValue);
CHECK_GREATER_OR_EQUAL(res.m_maxValue, res.m_minValue, (m_debugId));
}
ms::LatLon const m_leftBottom;
ms::LatLon const m_rightTop;
double const m_step;
ValueType const m_valueStep;
ValuesProvider<ValueType> & m_valuesProvider;
size_t m_stepsCountLon;
size_t m_stepsCountLat;
std::string m_debugId;
};
} // namespace topography_generator

View file

@ -0,0 +1,222 @@
#pragma once
#include "topography_generator/marching_squares/contours_builder.hpp"
namespace topography_generator
{
template <typename ValueType>
class Square
{
public:
Square(ValueType minValue, ValueType valueStep, std::string const & debugId)
: m_minValue(minValue)
, m_valueStep(valueStep)
, m_debugId(debugId)
{
static_assert(std::is_integral<ValueType>::value && std::is_signed<ValueType>::value);
}
void Init(double left, double bottom, double right, double top, ValueType lb, ValueType rb, ValueType lt,
ValueType rt, ValueType invalid)
{
m_isValid = true;
m_left = left;
m_bottom = bottom;
m_right = right;
m_top = top;
m_valueLB = GetValue(lb, invalid);
m_valueRB = GetValue(rb, invalid);
m_valueLT = GetValue(lt, invalid);
m_valueRT = GetValue(rt, invalid);
}
void GenerateSegments(ContoursBuilder & builder) const
{
if (!m_isValid)
return;
ValueType minVal = std::min(m_valueLB, std::min(m_valueLT, std::min(m_valueRT, m_valueRB)));
ValueType maxVal = std::max(m_valueLB, std::max(m_valueLT, std::max(m_valueRT, m_valueRB)));
ToLevelsRange(m_valueStep, minVal, maxVal);
CHECK_GREATER_OR_EQUAL(minVal, m_minValue, (m_debugId));
for (auto val = minVal; val < maxVal; val += m_valueStep)
AddSegments(val, (val - m_minValue) / m_valueStep, builder);
}
static void ToLevelsRange(ValueType step, ValueType & minVal, ValueType & maxVal)
{
if (minVal > 0)
minVal = step * ((minVal + step - 1) / step);
else
minVal = step * (minVal / step);
if (maxVal > 0)
maxVal = step * ((maxVal + step) / step);
else
maxVal = step * ((maxVal + 1) / step);
}
private:
enum class Rib
{
None,
Left,
Top,
Right,
Bottom,
Unclear,
};
ValueType GetValue(ValueType val, ValueType invalid)
{
// If a contour goes right through the corner of the square false segments can be generated.
// Shift the value slightly from the corner.
if (val == invalid)
{
// LOG(LWARNING, ("Invalid value at the position", pos, m_debugId));
m_isValid = false;
return val;
}
if (abs(val) % m_valueStep == 0)
return val + 1;
return val;
}
void AddSegments(ValueType val, uint16_t ind, ContoursBuilder & builder) const
{
// Segment is a vector directed so that higher values is on the right.
static std::pair<Rib, Rib> const intersectedRibs[] = {
{Rib::None, Rib::None}, // 0000
{Rib::Left, Rib::Bottom}, // 0001
{Rib::Top, Rib::Left}, // 0010
{Rib::Top, Rib::Bottom}, // 0011
{Rib::Right, Rib::Top}, // 0100
{Rib::Unclear, Rib::Unclear}, // 0101
{Rib::Right, Rib::Left}, // 0110
{Rib::Right, Rib::Bottom}, // 0111
{Rib::Bottom, Rib::Right}, // 1000
{Rib::Left, Rib::Right}, // 1001
{Rib::Unclear, Rib::Unclear}, // 1010
{Rib::Top, Rib::Right}, // 1011
{Rib::Bottom, Rib::Top}, // 1100
{Rib::Left, Rib::Top}, // 1101
{Rib::Bottom, Rib::Left}, // 1110
{Rib::None, Rib::None}, // 1111
};
uint8_t const pattern = (m_valueLB > val ? 1u : 0u) | ((m_valueLT > val ? 1u : 0u) << 1u) |
((m_valueRT > val ? 1u : 0u) << 2u) | ((m_valueRB > val ? 1u : 0u) << 3u);
auto const ribs = intersectedRibs[pattern];
if (ribs.first == Rib::None)
return;
if (ribs.first != Rib::Unclear)
{
builder.AddSegment(ind, InterpolatePoint(ribs.first, val), InterpolatePoint(ribs.second, val));
}
else
{
auto const leftPos = InterpolatePoint(Rib::Left, val);
auto const rightPos = InterpolatePoint(Rib::Right, val);
auto const bottomPos = InterpolatePoint(Rib::Bottom, val);
auto const topPos = InterpolatePoint(Rib::Top, val);
ValueType const avVal = (m_valueLB + m_valueLT + m_valueRT + m_valueRB) / 4;
if (avVal > val)
{
if (m_valueLB > val)
{
builder.AddSegment(ind, leftPos, topPos);
builder.AddSegment(ind, rightPos, bottomPos);
}
else
{
builder.AddSegment(ind, bottomPos, leftPos);
builder.AddSegment(ind, topPos, rightPos);
}
}
else if (m_valueLB > val)
{
builder.AddSegment(ind, leftPos, bottomPos);
builder.AddSegment(ind, rightPos, topPos);
}
else
{
builder.AddSegment(ind, topPos, leftPos);
builder.AddSegment(ind, bottomPos, rightPos);
}
}
}
ms::LatLon InterpolatePoint(Square::Rib rib, ValueType val) const
{
double val1;
double val2;
double lat;
double lon;
switch (rib)
{
case Rib::Left:
val1 = static_cast<double>(m_valueLB);
val2 = static_cast<double>(m_valueLT);
lon = m_left;
break;
case Rib::Right:
val1 = static_cast<double>(m_valueRB);
val2 = static_cast<double>(m_valueRT);
lon = m_right;
break;
case Rib::Top:
val1 = static_cast<double>(m_valueLT);
val2 = static_cast<double>(m_valueRT);
lat = m_top;
break;
case Rib::Bottom:
val1 = static_cast<double>(m_valueLB);
val2 = static_cast<double>(m_valueRB);
lat = m_bottom;
break;
default: UNREACHABLE();
}
CHECK_NOT_EQUAL(val, val2, (m_debugId));
double const coeff = (val1 - val) / (val - val2);
switch (rib)
{
case Rib::Left:
case Rib::Right: lat = (m_bottom + m_top * coeff) / (1 + coeff); break;
case Rib::Bottom:
case Rib::Top: lon = (m_left + m_right * coeff) / (1 + coeff); break;
default: UNREACHABLE();
}
return {lat, lon};
}
double m_left;
double m_right;
double m_bottom;
double m_top;
ValueType m_valueLB;
ValueType m_valueLT;
ValueType m_valueRT;
ValueType m_valueRB;
ValueType m_minValue;
ValueType m_valueStep;
std::string m_debugId;
bool m_isValid;
};
} // namespace topography_generator

View file

@ -0,0 +1,94 @@
#pragma once
#include "topography_generator/filters_impl.hpp"
namespace topography_generator
{
template <typename ValueType>
class FilterInterface
{
public:
virtual ~FilterInterface() = default;
virtual size_t GetKernelRadius() const = 0;
virtual void Process(size_t tileSize, size_t tileOffset, std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const = 0;
};
template <typename ValueType>
class MedianFilter : public FilterInterface<ValueType>
{
public:
explicit MedianFilter(size_t kernelRadius) : m_kernelRadius(kernelRadius) {}
size_t GetKernelRadius() const override { return m_kernelRadius; }
void Process(size_t tileSize, size_t tileOffset, std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const override
{
ProcessMedian(m_kernelRadius, tileSize, tileOffset, srcValues, dstValues, invalidValue);
}
private:
size_t m_kernelRadius;
};
template <typename ValueType>
class GaussianFilter : public FilterInterface<ValueType>
{
public:
GaussianFilter(double standardDeviation, double radiusFactor)
: m_kernel(CalculateGaussianLinearKernel(standardDeviation, radiusFactor))
{}
size_t GetKernelRadius() const override { return m_kernel.size() / 2; }
void Process(size_t tileSize, size_t tileOffset, std::vector<ValueType> const & srcValues,
std::vector<ValueType> & dstValues, ValueType invalidValue) const override
{
ProcessWithLinearKernel(m_kernel, tileSize, tileOffset, srcValues, dstValues, invalidValue);
}
private:
std::vector<double> m_kernel;
};
template <typename ValueType>
using FiltersSequence = std::vector<std::unique_ptr<FilterInterface<ValueType>>>;
template <typename ValueType>
std::vector<ValueType> FilterTile(FiltersSequence<ValueType> const & filters, ms::LatLon const & leftBottom,
size_t stepsInDegree, size_t tileSize, ValuesProvider<ValueType> & valuesProvider)
{
size_t combinedOffset = 0;
for (auto const & filter : filters)
combinedOffset += filter->GetKernelRadius();
std::vector<ValueType> extTileValues;
GetExtendedTile(leftBottom, stepsInDegree, tileSize, combinedOffset, valuesProvider, extTileValues);
std::vector<ValueType> extTileValues2(extTileValues.size());
size_t const extTileSize = tileSize + 2 * combinedOffset;
CHECK_EQUAL(extTileSize * extTileSize, extTileValues.size(), ());
size_t currentOffset = 0;
for (auto const & filter : filters)
{
currentOffset += filter->GetKernelRadius();
filter->Process(extTileSize, currentOffset, extTileValues, extTileValues2, kInvalidAltitude);
extTileValues.swap(extTileValues2);
}
std::vector<ValueType> result(tileSize * tileSize);
for (size_t i = combinedOffset; i < extTileSize - combinedOffset; ++i)
{
for (size_t j = combinedOffset; j < extTileSize - combinedOffset; ++j)
{
size_t const dstIndex = (i - combinedOffset) * tileSize + j - combinedOffset;
result[dstIndex] = extTileValues[i * extTileSize + j];
}
}
return result;
}
} // namespace topography_generator

View file

@ -0,0 +1,106 @@
#pragma once
#include "generator/feature_helpers.hpp"
#include "geometry/point2d.hpp"
#include "geometry/region2d.hpp"
#include "indexer/scales.hpp"
#include <unordered_map>
#include <vector>
namespace topography_generator
{
using Contour = std::vector<m2::PointD>;
template <typename ValueType>
struct Contours
{
enum class Version : uint8_t
{
V0 = 0,
Latest = V0
};
std::unordered_map<ValueType, std::vector<Contour>> m_contours;
ValueType m_minValue = 0;
ValueType m_maxValue = 0;
ValueType m_valueStep = 0;
size_t m_invalidValuesCount = 0; // for debug purpose only.
};
template <typename ValueType>
void CropContours(m2::RectD & rect, std::vector<m2::RegionD> & regions, size_t maxLength, size_t valueStepFactor,
Contours<ValueType> & contours)
{
static_assert(std::is_integral<ValueType>::value, "Only integral types are supported.");
contours.m_minValue = std::numeric_limits<ValueType>::max();
contours.m_maxValue = std::numeric_limits<ValueType>::min();
auto it = contours.m_contours.begin();
while (it != contours.m_contours.end())
{
std::vector<Contour> levelCroppedContours;
if (it->first % static_cast<ValueType>(contours.m_valueStep * valueStepFactor) == 0)
{
for (auto const & contour : it->second)
{
Contour cropped;
cropped.reserve(contour.size());
for (auto const & pt : contour)
{
cropped.push_back(pt);
auto const isInside = rect.IsPointInside(pt) && RegionsContain(regions, pt);
if (!isInside || cropped.size() == maxLength)
{
if (cropped.size() > 1)
levelCroppedContours.emplace_back(std::move(cropped));
cropped.clear();
if (isInside)
cropped.push_back(pt);
}
}
if (cropped.size() > 1)
levelCroppedContours.emplace_back(std::move(cropped));
}
}
it->second = std::move(levelCroppedContours);
if (!it->second.empty())
{
contours.m_minValue = std::min(it->first, contours.m_minValue);
contours.m_maxValue = std::max(it->first, contours.m_maxValue);
++it;
}
else
{
it = contours.m_contours.erase(it);
}
}
}
template <typename ValueType>
void SimplifyContours(int simplificationZoom, Contours<ValueType> & contours)
{
for (auto & levelContours : contours.m_contours)
{
for (auto & contour : levelContours.second)
{
std::vector<m2::PointD> contourSimple;
feature::SimplifyPoints(m2::SquaredDistanceFromSegmentToPoint(), simplificationZoom, contour, contourSimple);
CHECK_GREATER(contourSimple.size(), 1, ());
// 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.front() == contourSimple.back() &&
!scales::IsGoodOutlineForLevel(simplificationZoom, contourSimple))
contour.clear();
else
contour = std::move(contourSimple);
}
}
}
} // namespace topography_generator

View file

@ -0,0 +1,138 @@
#pragma once
#include "topography_generator/utils/contours.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/internal/file_data.hpp"
namespace topography_generator
{
template <typename ValueType>
class SerializerContours
{
public:
explicit SerializerContours(Contours<ValueType> && contours) : m_contours(std::move(contours)) {}
template <typename Sink>
void Serialize(Sink & sink)
{
WriteToSink(sink, base::Underlying(Contours<ValueType>::Version::Latest));
WriteToSink(sink, m_contours.m_minValue);
WriteToSink(sink, m_contours.m_maxValue);
WriteToSink(sink, m_contours.m_valueStep);
WriteToSink(sink, static_cast<uint32_t>(m_contours.m_invalidValuesCount));
WriteToSink(sink, static_cast<uint32_t>(m_contours.m_contours.size()));
for (auto const & levelContours : m_contours.m_contours)
SerializeContours(sink, levelContours.first, levelContours.second);
}
private:
template <typename Sink>
void SerializeContours(Sink & sink, ValueType value, std::vector<topography_generator::Contour> const & contours)
{
WriteToSink(sink, value);
WriteToSink(sink, static_cast<uint32_t>(contours.size()));
for (auto const & contour : contours)
SerializeContour(sink, contour);
}
template <typename Sink>
void SerializeContour(Sink & sink, topography_generator::Contour const & contour)
{
serial::GeometryCodingParams codingParams(kPointCoordBits, contour[0]);
codingParams.Save(sink);
serial::SaveOuterPath(contour, codingParams, sink);
}
Contours<ValueType> m_contours;
};
template <typename ValueType>
class DeserializerContours
{
public:
template <typename Reader>
void Deserialize(Reader & reader, Contours<ValueType> & contours)
{
NonOwningReaderSource source(reader);
using VersT = typename Contours<ValueType>::Version;
auto const v = static_cast<VersT>(ReadPrimitiveFromSource<std::underlying_type_t<VersT>>(source));
CHECK(v == Contours<ValueType>::Version::Latest, ());
contours.m_minValue = ReadPrimitiveFromSource<ValueType>(source);
contours.m_maxValue = ReadPrimitiveFromSource<ValueType>(source);
contours.m_valueStep = ReadPrimitiveFromSource<ValueType>(source);
contours.m_invalidValuesCount = ReadPrimitiveFromSource<uint32_t>(source);
size_t const levelsCount = ReadPrimitiveFromSource<uint32_t>(source);
for (size_t i = 0; i < levelsCount; ++i)
{
ValueType levelValue;
std::vector<topography_generator::Contour> levelContours;
DeserializeContours(source, levelValue, levelContours);
contours.m_contours[levelValue] = std::move(levelContours);
}
}
private:
void DeserializeContours(NonOwningReaderSource & source, ValueType & value,
std::vector<topography_generator::Contour> & contours)
{
value = ReadPrimitiveFromSource<ValueType>(source);
size_t const contoursCount = ReadPrimitiveFromSource<uint32_t>(source);
contours.resize(contoursCount);
for (auto & contour : contours)
DeserializeContour(source, contour);
}
void DeserializeContour(NonOwningReaderSource & source, topography_generator::Contour & contour)
{
serial::GeometryCodingParams codingParams;
codingParams.Load(source);
std::vector<m2::PointD> points;
serial::LoadOuterPath(source, codingParams, points);
contour = std::move(points);
}
};
template <typename ValueType>
bool SaveContrours(std::string const & filePath, Contours<ValueType> && contours)
{
auto const tmpFilePath = filePath + ".tmp";
try
{
FileWriter file(tmpFilePath);
SerializerContours<ValueType> ser(std::move(contours));
ser.Serialize(file);
}
catch (FileWriter::Exception const & ex)
{
LOG(LWARNING, ("File writer exception raised:", ex.what(), ", file", tmpFilePath));
return false;
}
CHECK(base::RenameFileX(tmpFilePath, filePath), (tmpFilePath, filePath));
return true;
}
template <typename ValueType>
bool LoadContours(std::string const & filePath, Contours<ValueType> & contours)
{
try
{
FileReader file(filePath);
DeserializerContours<ValueType> des;
des.Deserialize(file, contours);
}
catch (FileReader::Exception const & ex)
{
LOG(LWARNING, ("File reader exception raised:", ex.what(), ", file", filePath));
return false;
}
return true;
}
} // namespace topography_generator

View file

@ -0,0 +1,16 @@
#pragma once
#include "geometry/latlon.hpp"
namespace topography_generator
{
template <typename ValueType>
class ValuesProvider
{
public:
virtual ~ValuesProvider() = default;
virtual ValueType GetValue(ms::LatLon const & pos) = 0;
virtual ValueType GetInvalidValue() const = 0;
};
} // namespace topography_generator