Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
30
tools/topography_generator/CMakeLists.txt
Normal file
30
tools/topography_generator/CMakeLists.txt
Normal 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
|
||||
)
|
||||
24
tools/topography_generator/filters_impl.cpp
Normal file
24
tools/topography_generator/filters_impl.cpp
Normal 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
|
||||
195
tools/topography_generator/filters_impl.hpp
Normal file
195
tools/topography_generator/filters_impl.hpp
Normal 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
|
||||
731
tools/topography_generator/generator.cpp
Normal file
731
tools/topography_generator/generator.cpp
Normal 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, ¶ms, 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,
|
||||
[¶ms](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
|
||||
95
tools/topography_generator/generator.hpp
Normal file
95
tools/topography_generator/generator.hpp
Normal 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
|
||||
135
tools/topography_generator/isolines_profile.hpp
Normal file
135
tools/topography_generator/isolines_profile.hpp
Normal 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
|
||||
35
tools/topography_generator/isolines_utils.hpp
Normal file
35
tools/topography_generator/isolines_utils.hpp
Normal 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
|
||||
206
tools/topography_generator/main.cpp
Normal file
206
tools/topography_generator/main.cpp
Normal 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;
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
128
tools/topography_generator/marching_squares/marching_squares.hpp
Normal file
128
tools/topography_generator/marching_squares/marching_squares.hpp
Normal 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
|
||||
222
tools/topography_generator/marching_squares/square.hpp
Normal file
222
tools/topography_generator/marching_squares/square.hpp
Normal 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
|
||||
94
tools/topography_generator/tile_filter.hpp
Normal file
94
tools/topography_generator/tile_filter.hpp
Normal 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
|
||||
106
tools/topography_generator/utils/contours.hpp
Normal file
106
tools/topography_generator/utils/contours.hpp
Normal 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
|
||||
138
tools/topography_generator/utils/contours_serdes.hpp
Normal file
138
tools/topography_generator/utils/contours_serdes.hpp
Normal 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
|
||||
16
tools/topography_generator/utils/values_provider.hpp
Normal file
16
tools/topography_generator/utils/values_provider.hpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue