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

280
generator/CMakeLists.txt Normal file
View file

@ -0,0 +1,280 @@
project(generator)
set(SRC
addresses_collector.cpp
addresses_collector.hpp
address_enricher.cpp
address_enricher.hpp
affiliation.cpp
affiliation.hpp
altitude_generator.cpp
altitude_generator.hpp
borders.cpp
borders.hpp
boundary_postcodes_enricher.cpp
boundary_postcodes_enricher.hpp
brands_loader.cpp
brands_loader.hpp
camera_info_collector.cpp
camera_info_collector.hpp
cells_merger.cpp
cells_merger.hpp
centers_table_builder.cpp
centers_table_builder.hpp
check_model.cpp
check_model.hpp
cities_boundaries_builder.cpp
cities_boundaries_builder.hpp
cities_boundaries_checker.cpp
cities_boundaries_checker.hpp
cities_ids_builder.cpp
cities_ids_builder.hpp
city_roads_generator.cpp
city_roads_generator.hpp
cluster_finder.hpp
coastlines_generator.cpp
coastlines_generator.hpp
collection_base.hpp
collector_boundary_postcode.cpp
collector_boundary_postcode.hpp
collector_building_parts.cpp
collector_building_parts.hpp
collector_camera.cpp
collector_camera.hpp
collector_collection.cpp
collector_collection.hpp
collector_interface.hpp
collector_mini_roundabout.cpp
collector_mini_roundabout.hpp
collector_routing_city_boundaries.cpp
collector_routing_city_boundaries.hpp
collector_tag.cpp
collector_tag.hpp
# complex_loader.cpp
# complex_loader.hpp
composite_id.cpp
composite_id.hpp
cross_mwm_osm_ways_collector.cpp
cross_mwm_osm_ways_collector.hpp
descriptions_section_builder.cpp
descriptions_section_builder.hpp
dumper.cpp
dumper.hpp
factory_utils.hpp
feature_builder.cpp
feature_builder.hpp
feature_emitter_iface.hpp
feature_generator.cpp
feature_generator.hpp
feature_helpers.cpp
feature_helpers.hpp
feature_maker.cpp
feature_maker.hpp
feature_maker_base.cpp
feature_maker_base.hpp
feature_merger.cpp
feature_merger.hpp
feature_processing_layers.cpp
feature_processing_layers.hpp
feature_sorter.cpp
feature_sorter.hpp
features_processing_helpers.hpp
filter_collection.cpp
filter_collection.hpp
# filter_complex.cpp
# filter_complex.hpp
filter_elements.cpp
filter_elements.hpp
filter_interface.hpp
filter_planet.cpp
filter_planet.hpp
filter_roads.cpp
filter_roads.hpp
filter_world.cpp
filter_world.hpp
final_processor_cities.cpp
final_processor_cities.hpp
final_processor_coastline.cpp
final_processor_coastline.hpp
# final_processor_complex.cpp
# final_processor_complex.hpp
final_processor_country.cpp
final_processor_country.hpp
final_processor_interface.hpp
final_processor_utils.cpp
final_processor_utils.hpp
final_processor_world.cpp
final_processor_world.hpp
gen_mwm_info.cpp
gen_mwm_info.hpp
generate_info.hpp
geometry_holder.hpp
# hierarchy.cpp
# hierarchy.hpp
# hierarchy_entry.cpp
# hierarchy_entry.hpp
holes.cpp
holes.hpp
intermediate_data.cpp
intermediate_data.hpp
intermediate_elements.hpp
isolines_generator.cpp
isolines_generator.hpp
isolines_section_builder.cpp
isolines_section_builder.hpp
maxspeeds_builder.cpp
maxspeeds_builder.hpp
maxspeeds_collector.cpp
maxspeeds_collector.hpp
maxspeeds_parser.cpp
maxspeeds_parser.hpp
metalines_builder.cpp
metalines_builder.hpp
mini_roundabout_info.cpp
mini_roundabout_info.hpp
mini_roundabout_transformer.cpp
mini_roundabout_transformer.hpp
node_mixer.cpp
node_mixer.hpp
osm2meta.cpp
osm2meta.hpp
osm2type.cpp
osm2type.hpp
osm_element.cpp
osm_element.hpp
osm_element_helpers.cpp
osm_element_helpers.hpp
osm_o5m_source.hpp
osm_source.cpp
osm_xml_source.hpp
place_processor.cpp
place_processor.hpp
platform_helpers.cpp
platform_helpers.hpp
popular_places_section_builder.cpp
popular_places_section_builder.hpp
postcode_points_builder.cpp
postcode_points_builder.hpp
processor_coastline.cpp
processor_coastline.hpp
# processor_complex.cpp
# processor_complex.hpp
processor_country.cpp
processor_country.hpp
processor_factory.hpp
processor_interface.hpp
processor_noop.hpp
# processor_simple.cpp
# processor_simple.hpp
processor_world.cpp
processor_world.hpp
raw_generator.cpp
raw_generator.hpp
raw_generator_writer.cpp
raw_generator_writer.hpp
region_meta.cpp
region_meta.hpp
relation_tags.cpp
relation_tags.hpp
relation_tags_enricher.cpp
relation_tags_enricher.hpp
restriction_collector.cpp
restriction_collector.hpp
restriction_generator.cpp
restriction_generator.hpp
restriction_writer.cpp
restriction_writer.hpp
road_access_generator.cpp
road_access_generator.hpp
road_penalty_generator.cpp
road_penalty_generator.hpp
routing_city_boundaries_processor.cpp
routing_city_boundaries_processor.hpp
routing_helpers.cpp
routing_helpers.hpp
routing_index_generator.cpp
routing_index_generator.hpp
routing_world_roads_generator.cpp
routing_world_roads_generator.hpp
search_index_builder.cpp
search_index_builder.hpp
srtm_parser.cpp
srtm_parser.hpp
statistics.cpp
statistics.hpp
tag_admixer.hpp
tesselator.cpp
tesselator.hpp
towns_dumper.cpp
towns_dumper.hpp
traffic_generator.cpp
traffic_generator.hpp
transit_generator.cpp
transit_generator.hpp
transit_generator_experimental.cpp
transit_generator_experimental.hpp
translator.cpp
translator.hpp
translator_coastline.cpp
translator_coastline.hpp
translator_collection.cpp
translator_collection.hpp
# translator_complex.cpp
# translator_complex.hpp
translator_country.cpp
translator_country.hpp
translator_factory.hpp
translator_interface.hpp
translator_world.cpp
translator_world.hpp
translators_pool.cpp
translators_pool.hpp
unpack_mwm.cpp
unpack_mwm.hpp
utils.cpp
utils.hpp
ways_merger.cpp
ways_merger.hpp
way_nodes_mapper.hpp
wiki_url_dumper.cpp
wiki_url_dumper.hpp
water_boundary_checker.hpp
world_map_generator.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
transit
search
routing
storage
descriptions
indexer
cppjansson
expat::expat
tess2
$<$<BOOL:CMAKE_DL_LIBS>:${CMAKE_DL_LIBS}> # dladdr from boost::stacktrace
)
# Disable unity build to avoid boost::geometry macro linker errors.
set_source_files_properties(
affiliation.cpp
final_processor_country.cpp
hierarchy.cpp
PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE
)
if (NOT (SKIP_TOOLS AND SKIP_TESTS))
# Used in both tests and some tools.
add_subdirectory(generator_tests_support)
endif()
omim_add_test_subdirectory(generator_tests)
omim_add_test_subdirectory(generator_integration_tests)
omim_add_tool_subdirectory(generator_tool)
#omim_add_tool_subdirectory(complex_generator)
omim_add_tool_subdirectory(feature_segments_checker)
omim_add_tool_subdirectory(srtm_coverage_checker)
add_subdirectory(world_roads_builder)
add_subdirectory(address_parser)

5
generator/TODO.txt Normal file
View file

@ -0,0 +1,5 @@
1. Refactoring osm_translator.cpp/hpp.
2. Refactoring using std in hpp and cpp.
3. Get rid of new, replace on make_unique, make_shared.
4. Fixed hack on EmitterRegion::operator()(FeatureBuilder1 & fb).
5. Fixed enum naming.

View file

@ -0,0 +1,358 @@
#include "address_enricher.hpp"
#include "generator/feature_maker_base.hpp"
#include "search/house_numbers_matcher.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/search_string_utils.hpp"
#include "geometry/distance_on_sphere.hpp"
#include "geometry/mercator.hpp"
#include "geometry/parametrized_segment.hpp"
#include "coding/point_coding.hpp"
namespace generator
{
using feature::InterpolType;
std::pair<uint64_t, uint64_t> AddressEnricher::RawEntryBase::GetHNRange() const
{
using namespace search::house_numbers;
std::vector<TokensT> from, to;
ParseHouseNumber(strings::MakeUniString(m_from), from);
ParseHouseNumber(strings::MakeUniString(m_to), to);
if (from.size() != 1 || to.size() != 1)
return kInvalidRange;
ASSERT(!from[0].empty() && !to[0].empty(), ());
auto const & f = from[0][0];
auto const & t = to[0][0];
if (f.m_type != Token::TYPE_NUMBER || t.m_type != Token::TYPE_NUMBER)
return kInvalidRange;
return {ToUInt(f.m_value), ToUInt(t.m_value)};
}
std::string DebugPrint(AddressEnricher::Stats const & s)
{
return "{ m_noStreet = " + std::to_string(s.m_noStreet) + "; m_existInterpol = " + std::to_string(s.m_existInterpol) +
"; m_existSingle = " + std::to_string(s.m_existSingle) +
"; m_enoughAddrs = " + std::to_string(s.m_enoughAddrs) +
"; m_addedSingle = " + std::to_string(s.m_addedSingle) +
"; m_addedBegEnd = " + std::to_string(s.m_addedBegEnd) +
"; m_addedInterpol = " + std::to_string(s.m_addedInterpol) + " }";
}
AddressEnricher::AddressEnricher()
{
auto const & cl = classif();
m_addrType = cl.GetTypeByPath({"building", "address"});
m_interpolType[InterpolType::Odd] = cl.GetTypeByPath({"addr:interpolation", "odd"});
m_interpolType[InterpolType::Even] = cl.GetTypeByPath({"addr:interpolation", "even"});
m_interpolType[InterpolType::Any] = cl.GetTypeByPath({"addr:interpolation"});
}
void AddressEnricher::AddSrc(feature::FeatureBuilder && fb)
{
auto const osmID = fb.GetMostGenericOsmId();
auto const & params = fb.GetParams();
auto const & types = fb.GetTypes();
bool const hasStreet = !params.GetStreet().empty();
if (hasStreet && !params.house.IsEmpty())
{
if (fb.IsLine())
{
LOG(LERROR, ("Linear address FB:", osmID));
return;
}
else
TransformToPoint(fb);
}
else if (ftypes::IsStreetOrSquareChecker::Instance()(types))
{
// Skip node "squares" for matching, as far as we make addr:interpolation FBs.
// https://www.openstreetmap.org/node/7315145526
if (fb.IsPoint())
{
LOG(LWARNING, ("Point street FB:", osmID));
return;
}
std::string_view name;
if (params.name.GetString(StringUtf8Multilang::kDefaultCode, name))
CHECK(!name.empty(), (osmID));
else if (params.ref.empty())
return;
}
else if (hasStreet && ftypes::IsAddressInterpolChecker::Instance()(types))
{
CHECK(!params.ref.empty(), (osmID));
}
else
return;
m_srcTree.Add(std::move(fb));
}
void AddressEnricher::ProcessRawEntries(std::string const & path, TFBCollectFn const & fn)
{
FileReader reader(path);
ReaderSource src(reader);
while (src.Size())
{
Entry e;
e.Load(src);
std::vector<int64_t> iPoints;
rw::Read(src, iPoints);
e.m_points.reserve(iPoints.size());
for (auto const i : iPoints)
e.m_points.push_back(Int64ToPointObsolete(i, kPointCoordBits));
auto const res = Match(e);
if (!res.street)
{
++m_stats.m_noStreet;
LOG(LDEBUG, ("No street found:", e.m_street, mercator::ToLatLon(e.m_points.front())));
continue;
}
if (res.interpol)
{
++m_stats.m_existInterpol;
continue;
}
auto const addNode = [&](m2::PointD const & p, std::string hn)
{
feature::FeatureBuilder fb;
fb.SetCenter(p);
fb.SetType(m_addrType);
auto & params = fb.GetParams();
params.SetStreet(e.m_street);
params.SetPostcode(e.m_postcode);
CHECK(!hn.empty(), ());
params.SetHouseNumberAndHouseName(std::move(hn), {});
params.SetGeomTypePointEx();
fn(std::move(fb));
};
if (e.m_points.size() == 1)
{
if (!res.from && !res.to && res.addrsInside == 0)
{
++m_stats.m_addedSingle;
addNode(e.m_points.front(), e.m_from + " - " + e.m_to);
}
else
++m_stats.m_existSingle;
}
else
{
if (res.addrsInside > 0)
{
double const distM = mercator::DistanceOnEarth(e.m_points.front(), e.m_points.back());
if (distM / kDistanceThresholdM <= res.addrsInside)
{
++m_stats.m_enoughAddrs;
continue;
}
}
if (!res.from)
{
addNode(e.m_points.front(), e.m_from);
++m_stats.m_addedBegEnd;
}
if (!res.to)
{
addNode(e.m_points.back(), e.m_to);
++m_stats.m_addedBegEnd;
}
// Add interpolation FB.
feature::FeatureBuilder fb;
fb.AssignPoints(std::move(e.m_points));
fb.SetLinear();
CHECK(e.m_interpol != InterpolType::None, ());
fb.SetType(m_interpolType[e.m_interpol]);
// Same as in AddressesHolder::Update.
auto & params = fb.GetParams();
params.SetStreet(e.m_street);
params.SetPostcode(e.m_postcode);
params.ref = e.m_from + ":" + e.m_to;
fn(std::move(fb));
++m_stats.m_addedInterpol;
}
}
}
AddressEnricher::FoundT AddressEnricher::Match(Entry & e) const
{
// Calc query rect with the threshold.
m2::RectD rect;
for (auto const & p : e.m_points)
rect.Add(p);
m2::RectD const inflation = mercator::RectByCenterXYAndSizeInMeters(rect.Center(), kDistanceThresholdM);
rect.Inflate(inflation.SizeX(), inflation.SizeY());
// Tiger usually has short (St) names, while OSM has full (Street) names.
auto const streetKey = search::GetNormalizedStreetName(e.m_street);
// Get HN range for the entry.
auto const range = e.GetHNRange();
CHECK(range != Entry::kInvalidRange, ());
CHECK(range.first <= range.second, (range));
// Prepare distance calculator for the entry.
std::vector<m2::ParametrizedSegment<m2::PointD>> eSegs;
eSegs.reserve(e.m_points.size() - 1);
for (size_t i = 1; i < e.m_points.size(); ++i)
eSegs.emplace_back(e.m_points[i - 1], e.m_points[i]);
/// @todo Check nodes distance for now. Should make more honest algo.
auto const isClose = [&e, &eSegs](feature::FeatureBuilder const & fb)
{
bool res = false;
fb.ForEachPoint([&](m2::PointD const & p)
{
if (res)
return;
auto const ll = mercator::ToLatLon(p);
auto const check = [&ll](m2::PointD const & p)
{ return ms::DistanceOnEarth(ll, mercator::ToLatLon(p)) < kDistanceThresholdM; };
if (!eSegs.empty())
{
for (auto const & seg : eSegs)
if (check(seg.ClosestPointTo(p)))
{
res = true;
return;
}
}
else
res = check(e.m_points.front());
});
return res;
};
FoundT res;
m_srcTree.ForEachInRect(rect, [&](feature::FeatureBuilder const & fb)
{
auto const osmID = fb.GetMostGenericOsmId();
auto const & params = fb.GetParams();
auto const & types = fb.GetTypes();
bool const isStreet = ftypes::IsStreetOrSquareChecker::Instance()(types);
// First of all - compare street's name.
strings::UniString fbStreetKey = search::GetNormalizedStreetName(params.GetStreet());
std::string streetName; // original street's name, valid if isStreet == true
if (isStreet)
{
// Fancy object, highway=pedestrian with addr: https://www.openstreetmap.org/way/415336229
if (!fbStreetKey.empty())
LOG(LWARNING, ("Street with address:", osmID));
// Take 'ref' if 'name' is empty, like here: https://www.openstreetmap.org/way/902910704
std::string_view name;
if (params.name.GetString(StringUtf8Multilang::kDefaultCode, name))
streetName = name;
else if (!params.ref.empty())
streetName = params.ref;
if (!streetName.empty())
fbStreetKey = search::GetNormalizedStreetName(streetName);
}
if (streetKey != fbStreetKey)
return;
if (!params.house.IsEmpty())
{
if (!isClose(fb))
return;
using namespace search::house_numbers;
std::vector<TokensT> tokens;
ParseHouseNumber(strings::MakeUniString(params.house.Get()), tokens);
// Iterate via all possible int values (eg: 100-200;202;204).
for (auto const & token : tokens)
for (auto const & t : token)
{
if (t.m_type == Token::TYPE_NUMBER)
{
uint64_t const num = ToUInt(t.m_value);
if (range.first <= num && num <= range.second)
{
++res.addrsInside;
if (range.first == num)
res.from = true;
if (range.second == num)
res.to = true;
}
}
}
}
else if (!res.street && isStreet)
{
/// @todo Select the closest street if many options?
// We can't call isClose here, should make "vice-versa" check.
auto const & geom = fb.GetOuterGeometry();
for (size_t i = 1; i < geom.size(); ++i)
{
m2::ParametrizedSegment<m2::PointD> seg(geom[i - 1], geom[i]);
/// @todo Calculate e.m_points LL once?
for (auto const & p : e.m_points)
if (mercator::DistanceOnEarth(p, seg.ClosestPointTo(p)) < kDistanceThresholdM)
{
res.street = true;
// Assign street's name from OSM for further search index builder matching
// (it doesn't work like GetNormalizedStreetName, but like GetStreetNameAsKey).
e.m_street = std::move(streetName);
return;
}
}
}
else if (!res.interpol)
{
auto const interpol = ftypes::IsAddressInterpolChecker::Instance().GetInterpolType(types);
if (interpol != InterpolType::None && isClose(fb))
{
if (interpol != e.m_interpol && interpol != InterpolType::Any && e.m_interpol != InterpolType::Any)
return;
// Compare ranges.
auto const & hnRange = params.ref;
size_t const i = hnRange.find(':');
CHECK(i != std::string::npos, (hnRange));
uint64_t left, right;
CHECK(strings::to_uint(hnRange.substr(0, i), left) && strings::to_uint(hnRange.substr(i + 1), right),
(hnRange));
res.interpol = !(left >= range.second || right <= range.first);
}
}
});
return res;
}
} // namespace generator

View file

@ -0,0 +1,104 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "indexer/feature_utils.hpp"
#include "geometry/tree4d.hpp"
#include "coding/read_write_utils.hpp"
#include <map>
namespace generator
{
class AddressEnricher
{
// Looks too big here? Avoiding duplicates is a thing, because we still check street name first.
// Was 20, see Generator_Filter_NY test.
/// @see https://github.com/organicmaps/organicmaps/pull/8502 for more threshold metrics.
static double constexpr kDistanceThresholdM = 50.0;
public:
struct RawEntryBase
{
std::string m_from, m_to, m_street, m_postcode;
feature::InterpolType m_interpol = feature::InterpolType::None;
/// @name Used to compare house numbers by its integer value.
/// @{
// 0 is a possible HN value.
static std::pair<uint64_t, uint64_t> constexpr kInvalidRange{-1, -1};
std::pair<uint64_t, uint64_t> GetHNRange() const;
/// @}
template <class TSink>
void Save(TSink & sink) const
{
rw::Write(sink, m_from);
rw::Write(sink, m_to);
rw::Write(sink, m_street);
rw::Write(sink, m_postcode);
WriteToSink(sink, static_cast<uint8_t>(m_interpol));
}
template <class TSource>
void Load(TSource & src)
{
rw::Read(src, m_from);
rw::Read(src, m_to);
rw::Read(src, m_street);
rw::Read(src, m_postcode);
m_interpol = static_cast<feature::InterpolType>(ReadPrimitiveFromSource<uint8_t>(src));
}
};
struct Entry : RawEntryBase
{
std::vector<m2::PointD> m_points;
};
AddressEnricher();
void AddSrc(feature::FeatureBuilder && fb);
using TFBCollectFn = std::function<void(feature::FeatureBuilder &&)>;
void ProcessRawEntries(std::string const & path, TFBCollectFn const & fn);
// Public for tests.
struct FoundT
{
int addrsInside = 0;
bool from = false, to = false, street = false, interpol = false;
};
FoundT Match(Entry & e) const;
struct Stats
{
uint32_t m_noStreet = 0, m_existInterpol = 0, m_existSingle = 0, m_enoughAddrs = 0;
uint32_t m_addedSingle = 0, m_addedBegEnd = 0, m_addedInterpol = 0;
void Add(Stats const & s)
{
m_noStreet += s.m_noStreet;
m_existInterpol += s.m_existInterpol;
m_existSingle += s.m_existSingle;
m_enoughAddrs += s.m_enoughAddrs;
m_addedSingle += s.m_addedSingle;
m_addedBegEnd += s.m_addedBegEnd;
m_addedInterpol += s.m_addedInterpol;
}
friend std::string DebugPrint(Stats const & s);
} m_stats;
private:
m4::Tree<feature::FeatureBuilder> m_srcTree;
uint32_t m_addrType;
std::map<feature::InterpolType, uint32_t> m_interpolType;
};
} // namespace generator

View file

@ -0,0 +1,23 @@
project(address_parser)
set(SRC
processor.cpp
processor.hpp
tiger_parser.cpp
tiger_parser.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
)
omim_add_executable(address_parser_tool parser_tool.cpp)
target_link_libraries(address_parser_tool
${PROJECT_NAME}
gflags::gflags
)
omim_add_test_subdirectory(address_parser_tests)

View file

@ -0,0 +1,6 @@
project(address_parser_tests)
omim_add_test(${PROJECT_NAME} parser_tests.cpp)
target_link_libraries(${PROJECT_NAME}
address_parser
generator_tests_support
)

View file

@ -0,0 +1,260 @@
#include "testing/testing.hpp"
#include "../processor.hpp"
#include "../tiger_parser.hpp"
#include "generator/generator_tests_support/test_generator.hpp"
#include "search/house_to_street_table.hpp"
#include "indexer/ftypes_matcher.hpp"
#include <fstream>
namespace addr_parser_tests
{
UNIT_TEST(TigerParser_Smoke)
{
double constexpr kEpsLL = 1.0E-6;
tiger::AddressEntry e;
TEST(!tiger::ParseLine("from;to;interpolation;street;city;state;postcode;geometry", e), ());
e = {};
TEST(tiger::ParseLine("9587;9619;all;;Platte;MO;64152;LINESTRING(-94.691917 39.210393,-94.692370 39.210351)", e), ());
e = {};
TEST(tiger::ParseLine("698;600;all;Boston St;Berkeley;WV;25401;LINESTRING(-77.970484 39.464604,-77.970540 39.464630)",
e),
());
TEST_EQUAL(e.m_from, "600", ());
TEST_EQUAL(e.m_to, "698", ());
TEST_EQUAL(e.m_street, "Boston St", ());
TEST_EQUAL(e.m_postcode, "25401", ());
TEST_EQUAL(e.m_interpol, feature::InterpolType::Any, ());
TEST_EQUAL(e.m_geom.size(), 1, ());
TEST(ms::LatLon((39.464604 + 39.464630) / 2.0, (-77.970484 - 77.970540) / 2.0).EqualDxDy(e.m_geom.back(), kEpsLL),
());
e = {};
TEST(tiger::ParseLine("798;700;all;Boston St;Berkeley;WV;25401;LINESTRING(-77.968929 39.463906,-77.969118 "
"39.463990,-77.969427 39.464129,-77.969946 39.464353,-77.970027 39.464389)",
e),
());
TEST_EQUAL(e.m_from, "700", ());
TEST_EQUAL(e.m_to, "798", ());
TEST_EQUAL(e.m_street, "Boston St", ());
TEST_EQUAL(e.m_postcode, "25401", ());
TEST_EQUAL(e.m_interpol, feature::InterpolType::Any, ());
TEST_EQUAL(e.m_geom.size(), 4, ());
TEST(ms::LatLon(39.463906, -77.968929).EqualDxDy(e.m_geom.back(), kEpsLL), ());
TEST(ms::LatLon(39.464389, -77.970027).EqualDxDy(e.m_geom.front(), kEpsLL), ());
TEST(tiger::ParseLine("0;98;even;Austin Ln;Mifflin;PA;17044;LINESTRING(-77.634119 40.597239,-77.634200 "
"40.597288,-77.634679 40.598169,-77.634835 40.598393,-77.635116 40.598738,-77.635518 "
"40.599388,-77.635718 40.599719,-77.635833 40.599871,-77.635856 40.599920)",
e),
());
TEST_EQUAL(e.m_from, "0", ());
TEST_EQUAL(e.m_to, "98", ());
}
class TmpDir
{
std::string const m_path = "./addrs";
public:
std::string const & Get() const { return m_path; }
TmpDir()
{
(void)Platform::RmDirRecursively(m_path);
TEST(Platform::MkDirChecked(m_path), ());
}
~TmpDir() { TEST(Platform::RmDirRecursively(m_path), ()); }
};
UNIT_TEST(Processor_Smoke)
{
std::string const kTestFileName = "temp.txt";
TmpDir outPath;
{
std::ofstream of(kTestFileName);
of << "698;600;all;Boston St;Berkeley;WV;25401;LINESTRING(-77.970484 39.464604,-77.970540 39.464630)" << "\n"
<< "901;975;all;Pease Rd;Sutter;CA;95991;LINESTRING(-121.623833 39.171033,-121.626563 39.171015)" << "\n"
<< "7677;7601;all;N Main St;Livingston;NY;14560;LINESTRING(-77.595115 42.645859,-77.595450 42.648883)" << "\n"
<< "1;99;all;Stokes Dr;Pend Oreille;WA;99156;LINESTRING(-117.209595 48.058395,-117.209584 48.058352)" << "\n";
}
addr_generator::Processor processor("./data" /* dataPath */, outPath.Get(), 2 /* numThreads */);
{
std::ifstream ifile(kTestFileName);
processor.Run(ifile);
}
Platform::FilesList files;
Platform::GetFilesByExt(outPath.Get(), TEMP_ADDR_EXTENSION, files);
TEST_EQUAL(files.size(), 4, ());
CHECK(base::DeleteFileX(kTestFileName), ());
}
class TestFixture : public generator::tests_support::TestRawGenerator
{
protected:
TmpDir m_outPath;
std::string m_mwmName;
ftypes::IsAddressInterpolChecker const & m_interpolChecker;
uint32_t m_addrNodeType;
public:
TestFixture() : m_interpolChecker(ftypes::IsAddressInterpolChecker::Instance())
{
auto const & cl = classif();
m_addrNodeType = cl.GetTypeByPath({"building", "address"});
}
void Parse(std::stringstream & ss)
{
addr_generator::Processor processor("./data" /* dataPath */, m_outPath.Get(), 1 /* numThreads */);
processor.Run(ss);
Platform::FilesList files;
Platform::GetFilesByExt(m_outPath.Get(), TEMP_ADDR_EXTENSION, files);
TEST_EQUAL(files.size(), 1, ());
m_mwmName = std::move(files.front());
base::GetNameWithoutExt(m_mwmName);
}
void BuildAndCheck(std::string const & osmFile, int addrInterpolExpected, int addrNodeExpected)
{
// Build mwm.
GetGenInfo().m_addressesDir = m_outPath.Get();
BuildFB(osmFile, m_mwmName, false /* makeWorld */);
int addrInterpolCount = 0, addrNodeCount = 0;
ForEachFB(m_mwmName, [&](feature::FeatureBuilder const & fb)
{
auto const & params = fb.GetParams();
if (m_interpolChecker(fb.GetTypes()))
{
++addrInterpolCount;
TEST(!params.GetStreet().empty(), ());
}
if (fb.HasType(m_addrNodeType))
{
++addrNodeCount;
TEST(!params.GetStreet().empty(), ());
}
});
TEST_EQUAL(addrInterpolCount, addrInterpolExpected, ());
if (addrNodeExpected >= 0)
TEST_EQUAL(addrNodeCount, addrNodeExpected, ());
BuildFeatures(m_mwmName);
}
};
UNIT_CLASS_TEST(TestFixture, Generator_Smoke)
{
std::stringstream ss;
ss << "698;600;all;Boston St;Berkeley;WV;25401;LINESTRING(-77.970484 39.464604,-77.970540 39.464630)" << "\n"
<< "798;700;all;Boston St;Berkeley;WV;25401;LINESTRING(-77.968929 39.463906,-77.969118 39.463990,-77.969427 "
"39.464129,-77.969946 39.464353,-77.970027 39.464389)"
<< "\n";
Parse(ss);
BuildAndCheck("./data/test_data/osm/us_tiger_1.osm", 1 /* addrInterpolExpected */, 3 /* addrNodeExpected */);
}
UNIT_CLASS_TEST(TestFixture, Generator_Filter_SF1)
{
std::stringstream ss;
ss << "601;699;all;Francisco St;San Francisco;CA;94133;LINESTRING(-122.416189 37.804256,-122.416526 37.804215)"
<< "\n";
Parse(ss);
BuildAndCheck("./data/test_data/osm/us_tiger_2.osm", 0 /* addrInterpolExpected */, 0 /* addrNodeExpected */);
}
UNIT_CLASS_TEST(TestFixture, Generator_Filter_NY)
{
std::stringstream ss;
ss << "600;698;even;E 14th St;New York;NY;10009;LINESTRING(-73.977910 40.729298,-73.975890 40.728462)" << "\n";
Parse(ss);
BuildAndCheck("./data/test_data/osm/us_tiger_3.osm", 0 /* addrInterpolExpected */, -1 /* addrNodeExpected */);
}
UNIT_CLASS_TEST(TestFixture, Generator_Filter_SF2)
{
std::stringstream ss;
ss << "744;752;all;Francisco St;San Francisco;CA;94133;LINESTRING(-122.417593 37.804248,-122.417686 37.804236)"
<< "\n"
<< "754;798;even;Francisco St;San Francisco;CA;94133;LINESTRING(-122.417933 37.804205,-122.418204 37.804171)"
<< "\n";
Parse(ss);
BuildAndCheck("./data/test_data/osm/us_tiger_4.osm", 0 /* addrInterpolExpected */, 0 /* addrNodeExpected */);
}
UNIT_CLASS_TEST(TestFixture, Generator_Street_Name)
{
std::stringstream ss;
ss << "599;501;odd;Seventh St;Marshall;MN;56757;LINESTRING(-96.878165 48.451707,-96.878047 48.451722,-96.877150 "
"48.451844,-96.877034 48.451860)"
<< "\n"
<< "598;500;even;Seventh St;Marshall;MN;56757;LINESTRING(-96.878214 48.451868,-96.878097 48.451884,-96.877200 "
"48.452006,-96.877084 48.452022)"
<< "\n";
Parse(ss);
BuildAndCheck("./data/test_data/osm/us_tiger_5.osm", 2 /* addrInterpolExpected */, 4 /* addrNodeExpected */);
BuildSearch(m_mwmName);
FrozenDataSource dataSource;
auto const res = dataSource.RegisterMap(platform::LocalCountryFile::MakeTemporary(GetMwmPath(m_mwmName)));
CHECK_EQUAL(res.second, MwmSet::RegResult::Success, ());
FeaturesLoaderGuard guard(dataSource, res.first);
size_t const numFeatures = guard.GetNumFeatures();
size_t count = 0;
for (size_t id = 0; id < numFeatures; ++id)
{
auto ft = guard.GetFeatureByIndex(id);
if (m_interpolChecker(*ft))
{
++count;
auto value = guard.GetHandle().GetValue();
if (!value->m_house2street)
value->m_house2street = search::LoadHouseToStreetTable(*value);
auto res = value->m_house2street->Get(id);
TEST(res, ());
auto street = guard.GetFeatureByIndex(res->m_streetId);
TEST_EQUAL(street->GetName(StringUtf8Multilang::kDefaultCode), "7th Street", ());
}
}
TEST_EQUAL(count, 2, ());
}
} // namespace addr_parser_tests

View file

@ -0,0 +1,30 @@
#include "generator/utils.hpp"
#include "processor.hpp"
#include "platform/platform.hpp"
#include <gflags/gflags.h>
#include <iostream>
DEFINE_string(data_path, "./data", "Data path with 'borders' folder inside");
DEFINE_string(output_path, "", "Output files path");
DEFINE_uint64(threads_count, 0, "Desired number of threads. 0 - number of threads is set automatically");
MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
{
std::string usage("Tiger addresses processor. Sample usage:\n");
gflags::SetUsageMessage(usage + "tar -xOzf tiger-nominatim-preprocessed-latest.csv.tar.gz | " + argv[0] +
" --output_path=... ");
gflags::ParseCommandLineFlags(&argc, &argv, true);
size_t const threadsNum = FLAGS_threads_count != 0 ? FLAGS_threads_count : GetPlatform().CpuCores();
CHECK(!FLAGS_output_path.empty(), ("Set output path!"));
addr_generator::Processor processor(FLAGS_data_path, FLAGS_output_path, threadsNum);
processor.Run(std::cin);
return 0;
})

View file

@ -0,0 +1,91 @@
#include "processor.hpp"
#include "tiger_parser.hpp"
#include "geometry/mercator.hpp"
#include "coding/point_coding.hpp"
#include "base/file_name_utils.hpp"
#include "defines.hpp"
namespace addr_generator
{
Processor::Processor(std::string const & dataPath, std::string const & outputPath, size_t numThreads)
: m_affiliation(dataPath, true /* haveBordersForWholeWorld */)
, m_workers(numThreads)
, m_outputPath(outputPath)
{}
FileWriter & Processor::GetWriter(std::string const & country)
{
auto res = m_country2writer.try_emplace(country, base::JoinPath(m_outputPath, country) + TEMP_ADDR_EXTENSION);
return res.first->second;
}
void Processor::Run(std::istream & is)
{
std::atomic<size_t> incomplete = 0;
size_t total = 0;
std::string line;
while (std::getline(is, line))
{
++total;
m_workers.SubmitWork([this, &incomplete, copy = std::move(line)]() mutable
{
std::string_view line = copy;
// Remove possible trailing "\t\n" (pipe reading from tar).
size_t const i = line.rfind(')');
if (i == std::string::npos)
{
LOG(LWARNING, ("Invalid string:", line));
return;
}
line = line.substr(0, i + 1);
tiger::AddressEntry e;
if (!tiger::ParseLine(line, e))
{
LOG(LWARNING, ("Bad entry:", line));
return;
}
if (e.m_from.empty() || e.m_to.empty() || e.m_street.empty())
{
++incomplete;
return;
}
std::vector<m2::PointD> points;
points.reserve(e.m_geom.size());
for (auto const & ll : e.m_geom)
points.push_back(mercator::FromLatLon(ll));
auto const countries = m_affiliation.GetAffiliations(points);
std::vector<int64_t> iPoints;
iPoints.reserve(points.size());
for (auto const & p : points)
iPoints.push_back(PointToInt64Obsolete(p, kPointCoordBits));
std::lock_guard guard(m_mtx);
for (auto const & country : countries)
{
auto & writer = GetWriter(country);
e.Save(writer);
rw::Write(writer, iPoints);
}
});
}
m_workers.WaitingStop();
LOG(LINFO, ("Total entries:", total, "Incomplete:", incomplete));
}
} // namespace addr_generator

View file

@ -0,0 +1,32 @@
#pragma once
#include "generator/affiliation.hpp"
#include "coding/file_writer.hpp"
#include "base/thread_pool_computational.hpp"
#include <istream>
#include <mutex>
namespace addr_generator
{
class Processor
{
feature::CountriesFilesAffiliation m_affiliation;
base::ComputationalThreadPool m_workers;
std::mutex m_mtx;
std::map<std::string, FileWriter> m_country2writer;
std::string m_outputPath;
FileWriter & GetWriter(std::string const & country);
public:
explicit Processor(std::string const & dataPath, std::string const & outputPath, size_t numThreads);
void Run(std::istream & is);
};
} // namespace addr_generator

View file

@ -0,0 +1,123 @@
#include "tiger_parser.hpp"
#include "geometry/distance_on_sphere.hpp"
#include <algorithm>
namespace tiger
{
using namespace strings;
void ParseGeometry(std::string_view s, std::vector<ms::LatLon> & geom)
{
std::string_view constexpr prefix = "LINESTRING(";
if (!s.ends_with(')') || !s.starts_with(prefix))
return;
s.remove_prefix(prefix.size());
s.remove_suffix(1);
ms::LatLon last;
auto const skipPoint = [&last, &geom]()
{
// Check simple distance (meters) threshold.
return !geom.empty() && ms::DistanceOnEarth(geom.back(), last) < 10.0;
};
Tokenize(s, ",", [&](std::string_view s)
{
auto i = s.find(' ');
CHECK(i != std::string_view::npos, (s));
CHECK(to_double(s.substr(0, i), last.m_lon), (s));
CHECK(to_double(s.substr(i + 1), last.m_lat), (s));
if (!skipPoint())
geom.push_back(last);
});
if (skipPoint())
{
auto & back = geom.back();
if (geom.size() == 1)
{
// Set one middle point address.
back = ms::LatLon{(back.m_lat + last.m_lat) / 2.0, (back.m_lon + last.m_lon) / 2.0};
}
else
{
// Replace last point.
back = last;
}
}
}
feature::InterpolType ParseInterpolation(std::string_view s)
{
if (s == "all")
return feature::InterpolType::Any;
if (s == "odd")
return feature::InterpolType::Odd;
if (s == "even")
return feature::InterpolType::Even;
return feature::InterpolType::None;
}
bool ParseLine(std::string_view line, AddressEntry & e)
{
// from;to;interpolation;street;city;state;postcode;geometry
// Basic check.
if (std::count(line.begin(), line.end(), ';') != 7)
return false;
TokenizeIterator<SimpleDelimiter, std::string_view::const_iterator, true /* KeepEmptyTokens */> it(line.begin(),
line.end(), ";");
e.m_from = *it;
++it;
e.m_to = *it;
++it;
e.m_interpol = ParseInterpolation(*it);
++it;
e.m_street = *it;
++it;
++it;
++it;
e.m_postcode = *it;
++it;
ParseGeometry(*it, e.m_geom);
if (e.m_interpol == feature::InterpolType::None || e.m_geom.empty())
return false;
// Check and order house numbers.
auto const range = e.GetHNRange();
if (range == AddressEntry::kInvalidRange)
return false;
if (range.second < range.first)
{
std::swap(e.m_from, e.m_to);
std::reverse(e.m_geom.begin(), e.m_geom.end());
}
return true;
}
} // namespace tiger
namespace feature
{
std::string DebugPrint(InterpolType type)
{
switch (type)
{
case InterpolType::None: return "Interpol::None";
case InterpolType::Any: return "Interpol::Any";
case InterpolType::Odd: return "Interpol::Odd";
case InterpolType::Even: return "Interpol::Even";
}
UNREACHABLE();
}
} // namespace feature

View file

@ -0,0 +1,26 @@
#pragma once
#include "generator/address_enricher.hpp"
#include "geometry/latlon.hpp"
#include <string>
#include <vector>
namespace tiger
{
struct AddressEntry : public generator::AddressEnricher::RawEntryBase
{
std::vector<ms::LatLon> m_geom;
};
void ParseGeometry(std::string_view s, std::vector<ms::LatLon> & geom);
feature::InterpolType ParseInterpolation(std::string_view s);
bool ParseLine(std::string_view line, AddressEntry & e);
} // namespace tiger
namespace feature
{
std::string DebugPrint(InterpolType type);
} // namespace feature

View file

@ -0,0 +1,232 @@
#include "addresses_collector.hpp"
#include "generator/feature_builder.hpp"
#include "generator/osm_element.hpp"
#include "search/house_numbers_matcher.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "coding/read_write_utils.hpp"
namespace generator
{
using namespace feature;
using namespace strings;
namespace
{
uint64_t GetOsmID(FeatureBuilder const & fb)
{
return fb.GetMostGenericOsmId().GetSerialId();
}
AddressesHolder::AddressInfo FromFB(FeatureBuilder const & fb)
{
auto const & params = fb.GetParams();
return {params.house.Get(), std::string(params.GetStreet()), std::string(params.GetPostcode()), {}};
}
// Current stats:
// - Total "addr:interpolation:" errors = 270524
// - "addr:interpolation: Invalid range" = 144594
// - "addr:interpolation: No beg/end address point" = 89774
void LogWarning(std::string const & msg, uint64_t id)
{
LOG(LDEBUG, ("addr:interpolation: " + msg, id));
}
} // namespace
std::string AddressesHolder::AddressInfo::FormatRange() const
{
if (m_house.empty() || m_house2.empty())
return {};
using namespace search::house_numbers;
std::vector<TokensT> left, right;
ParseHouseNumber(MakeUniString(m_house), left);
ParseHouseNumber(MakeUniString(m_house2), right);
/// @todo Or try to take min/max range. See example here:
/// https://www.openstreetmap.org/way/1118117172
if (left.size() != 1 || right.size() != 1)
return {};
CHECK(!left[0].empty() && !right[0].empty(), (m_house, m_house2));
auto & l = left[0][0];
auto & r = right[0][0];
if (l.m_type != Token::TYPE_NUMBER || r.m_type != Token::TYPE_NUMBER)
return {};
uint64_t const li = ToUInt(l.m_value);
uint64_t const ri = ToUInt(r.m_value);
// Zero is a valid HN.
if (li == ri)
return {};
UniString sep(1, ':');
if (li < ri)
{
l.m_value += sep;
l.m_value += r.m_value;
return ToUtf8(l.m_value);
}
else
{
r.m_value += sep;
r.m_value += l.m_value;
return ToUtf8(r.m_value);
}
}
void AddressesHolder::Add(FeatureBuilder const & fb)
{
CHECK(fb.IsPoint(), ());
CHECK(m_addresses.emplace(GetOsmID(fb), FromFB(fb)).second, ());
}
bool AddressesHolder::Update(feature::FeatureBuilder & fb) const
{
uint64_t const id = GetOsmID(fb);
auto const i = m_addresses.find(id);
if (i == m_addresses.end())
return false;
// Get existing params (street, postcode).
auto const info = FromFB(fb);
auto & params = fb.GetParams();
// Force update ref as house's range.
if (!params.ref.empty())
LogWarning("Ref is not empty", id);
params.ref = i->second.FormatRange();
if (params.ref.empty())
{
LogWarning("Invalid range", id);
return false;
}
// Update from saved params if needed (prefer existing).
if (!i->second.m_street.empty() && i->second.m_street != info.m_street)
{
if (!info.m_street.empty())
LogWarning("Different streets", id);
else
params.SetStreet(i->second.m_street);
}
/// @todo I suspect that we should skip these features because of possible valid 3party addresses.
/// 28561 entries for now.
if (params.GetStreet().empty())
LogWarning("Empty street", id);
if (!i->second.m_postcode.empty() && i->second.m_postcode != info.m_postcode)
{
if (!info.m_postcode.empty())
LogWarning("Different postcodes", id);
else
params.SetPostcode(i->second.m_postcode);
}
return true;
}
void AddressesHolder::MergeInto(AddressesHolder & holder) const
{
for (auto const & e : m_addresses)
holder.m_addresses.emplace(e.first, e.second);
}
void AddressesHolder::Deserialize(std::string const & filePath)
{
FileReader reader(filePath);
ReaderSource<FileReader> src(reader);
while (src.Size())
{
uint64_t const id = ReadVarUint<uint64_t>(src);
AddressInfo info;
rw::Read(src, info.m_house);
rw::Read(src, info.m_house2);
rw::Read(src, info.m_street);
rw::Read(src, info.m_postcode);
CHECK(m_addresses.emplace(id, std::move(info)).second, (id));
}
}
AddressesCollector::AddressesCollector(std::string const & filename) : CollectorInterface(filename) {}
std::shared_ptr<CollectorInterface> AddressesCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<AddressesCollector>(GetFilename());
}
void AddressesCollector::CollectFeature(FeatureBuilder const & fb, OsmElement const & element)
{
if (element.m_type == OsmElement::EntityType::Node)
{
CHECK(fb.IsPoint(), ());
if (element.HasTag("addr:housenumber"))
m_nodeAddresses.Add(fb);
}
else if (element.m_type == OsmElement::EntityType::Way)
{
/// @todo Should merge interpolation ways somehow.
/// Looks easy at first glance, but need to refactor Collector<->FeatureBuilder routine ..
/// Example: https://www.openstreetmap.org/way/237469988
if (fb.IsLine() && ftypes::IsAddressInterpolChecker::Instance()(fb.GetTypes()))
{
CHECK_GREATER(element.m_nodes.size(), 1, (fb));
WayInfo info{element.m_nodes.front(), element.m_nodes.back()};
CHECK(m_interpolWays.emplace(element.m_id, std::move(info)).second, (fb));
}
}
}
void AddressesCollector::Save()
{
LOG(LINFO, ("Saving addresses to", GetFilename()));
FileWriter writer(GetFilename());
for (auto const & e : m_interpolWays)
{
auto beg = m_nodeAddresses.Get(e.second.m_beg);
auto end = m_nodeAddresses.Get(e.second.m_end);
if (beg == nullptr || end == nullptr)
{
LogWarning("No beg/end address point", e.first);
continue;
}
if (beg->m_street != end->m_street && !beg->m_street.empty() && !end->m_street.empty())
LogWarning("Different streets for beg/end points", e.first);
if (beg->m_postcode != end->m_postcode && !beg->m_postcode.empty() && !end->m_postcode.empty())
LogWarning("Different postcodes for beg/end points", e.first);
WriteVarUint(writer, e.first);
rw::Write(writer, beg->m_house);
rw::Write(writer, end->m_house);
rw::Write(writer, !beg->m_street.empty() ? beg->m_street : end->m_street);
rw::Write(writer, !beg->m_postcode.empty() ? beg->m_postcode : end->m_postcode);
}
LOG(LINFO, ("Finished saving addresses"));
}
void AddressesCollector::MergeInto(AddressesCollector & collector) const
{
m_nodeAddresses.MergeInto(collector.m_nodeAddresses);
for (auto const & e : m_interpolWays)
collector.m_interpolWays.emplace(e.first, e.second);
}
} // namespace generator

View file

@ -0,0 +1,67 @@
#pragma once
#include "generator/collector_interface.hpp"
#include <unordered_map>
namespace generator
{
class AddressesHolder
{
public:
struct AddressInfo
{
std::string m_house, m_street, m_postcode;
std::string m_house2; // end point house number for the 2nd stage
std::string FormatRange() const;
};
private:
std::unordered_map<uint64_t, AddressInfo> m_addresses;
public:
void Add(feature::FeatureBuilder const & fb);
bool Update(feature::FeatureBuilder & fb) const;
void MergeInto(AddressesHolder & holder) const;
void Deserialize(std::string const & filePath);
AddressInfo const * Get(uint64_t id) const
{
auto const i = m_addresses.find(id);
return (i != m_addresses.end()) ? &i->second : nullptr;
}
};
class AddressesCollector : public CollectorInterface
{
AddressesHolder m_nodeAddresses;
struct WayInfo
{
// OSM Node IDs here.
uint64_t m_beg, m_end;
};
// OSM Way ID is a key here.
std::unordered_map<uint64_t, WayInfo> m_interpolWays;
public:
explicit AddressesCollector(std::string const & filename);
/// @todo We can simplify this (and some others) Collector's logic if we will have strict
/// processing order (like in o5m): Nodes, Ways, Relations.
/// And make mutable CollectFeature(FeatureBuilder & fb).
/// @name CollectorInterface overrides:
/// @{
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const override;
void CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & element) override;
void Save() override;
/// @}
IMPLEMENT_COLLECTOR_IFACE(AddressesCollector);
void MergeInto(AddressesCollector & collector) const;
};
} // namespace generator

318
generator/affiliation.cpp Normal file
View file

@ -0,0 +1,318 @@
#include "generator/affiliation.hpp"
#include "generator/cells_merger.hpp"
#include "platform/platform.hpp"
#include "geometry/mercator.hpp"
#include "base/thread_pool_computational.hpp"
#include <functional>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/geometry/geometries/register/ring.hpp>
#include "std/boost_geometry.hpp"
BOOST_GEOMETRY_REGISTER_POINT_2D(m2::PointD, double, boost::geometry::cs::cartesian, x, y)
BOOST_GEOMETRY_REGISTER_RING(std::vector<m2::PointD>)
namespace feature
{
namespace affiliation
{
template <typename T>
struct RemoveCvref
{
typedef std::remove_cv_t<std::remove_reference_t<T>> type;
};
template <typename T>
using RemoveCvrefT = typename RemoveCvref<T>::type;
template <typename T>
m2::RectD GetLimitRect(T && t)
{
using Type = RemoveCvrefT<T>;
if constexpr (std::is_same_v<Type, FeatureBuilder>)
return t.GetLimitRect();
if constexpr (std::is_same_v<Type, std::vector<m2::PointD>>)
{
m2::RectD r;
for (auto const & p : t)
r.Add(p);
return r;
}
if constexpr (std::is_same_v<Type, m2::PointD>)
return m2::RectD(t, t);
UNREACHABLE();
}
template <typename T, typename F>
bool ForAnyPoint(T && t, F && f)
{
using Type = RemoveCvrefT<T>;
if constexpr (std::is_same_v<Type, FeatureBuilder>)
return t.ForAnyPoint(f);
if constexpr (std::is_same_v<Type, std::vector<m2::PointD>>)
return base::AnyOf(t, f);
if constexpr (std::is_same_v<Type, m2::PointD>)
return f(t);
UNREACHABLE();
}
template <typename T, typename F>
void ForEachPoint(T && t, F && f)
{
using Type = RemoveCvrefT<T>;
if constexpr (std::is_same_v<Type, FeatureBuilder>)
t.ForEachPoint(std::forward<F>(f));
else if constexpr (std::is_same_v<Type, m2::PointD>)
f(std::forward<T>(t));
else
UNREACHABLE();
}
// An implementation for CountriesFilesAffiliation class.
template <typename T>
std::vector<std::string> GetAffiliations(T const & t, borders::CountryPolygonsCollection const & countryPolygonsTree,
bool haveBordersForWholeWorld)
{
std::vector<std::string> countries;
std::vector<std::reference_wrapper<borders::CountryPolygons const>> countriesContainer;
countryPolygonsTree.ForEachCountryInRect(
GetLimitRect(t), [&](auto const & countryPolygons) { countriesContainer.emplace_back(countryPolygons); });
// todo(m.andrianov): We need to explore this optimization better. There is a hypothesis: some
// elements belong to a rectangle, but do not belong to the exact boundary.
if (haveBordersForWholeWorld && countriesContainer.size() == 1)
{
borders::CountryPolygons const & countryPolygons = countriesContainer.front();
countries.emplace_back(countryPolygons.GetName());
return countries;
}
for (borders::CountryPolygons const & countryPolygons : countriesContainer)
{
auto const need = ForAnyPoint(t, [&](auto const & point) { return countryPolygons.Contains(point); });
if (need)
countries.emplace_back(countryPolygons.GetName());
}
return countries;
}
// An implementation for CountriesFilesIndexAffiliation class.
using IndexSharedPtr = std::shared_ptr<CountriesFilesIndexAffiliation::Tree>;
CountriesFilesIndexAffiliation::Box MakeBox(m2::RectD const & rect)
{
return {rect.LeftBottom(), rect.RightTop()};
}
std::optional<std::string> IsOneCountryForLimitRect(m2::RectD const & limitRect, IndexSharedPtr const & index)
{
borders::CountryPolygons const * country = nullptr;
std::vector<CountriesFilesIndexAffiliation::Value> values;
auto const bbox = MakeBox(limitRect);
boost::geometry::index::query(*index, boost::geometry::index::covers(bbox), std::back_inserter(values));
for (auto const & v : values)
{
for (borders::CountryPolygons const & c : v.second)
if (!country)
country = &c;
else if (country != &c)
return {};
}
return country ? country->GetName() : std::optional<std::string>{};
}
template <typename T>
std::vector<std::string> GetHonestAffiliations(T && t, IndexSharedPtr const & index)
{
std::vector<std::string> affiliations;
std::unordered_set<borders::CountryPolygons const *> countires;
ForEachPoint(t, [&](auto const & point)
{
std::vector<CountriesFilesIndexAffiliation::Value> values;
boost::geometry::index::query(*index, boost::geometry::index::covers(point), std::back_inserter(values));
for (auto const & v : values)
{
if (v.second.size() == 1)
{
borders::CountryPolygons const & cp = v.second.front();
if (countires.insert(&cp).second)
affiliations.emplace_back(cp.GetName());
}
else
{
for (borders::CountryPolygons const & cp : v.second)
if (cp.Contains(point) && countires.insert(&cp).second)
affiliations.emplace_back(cp.GetName());
}
}
});
return affiliations;
}
template <typename T>
std::vector<std::string> GetAffiliations(T && t, IndexSharedPtr const & index)
{
auto const oneCountry = IsOneCountryForLimitRect(GetLimitRect(t), index);
return oneCountry ? std::vector<std::string>{*oneCountry} : GetHonestAffiliations(t, index);
}
} // namespace affiliation
CountriesFilesAffiliation::CountriesFilesAffiliation(std::string const & borderPath, bool haveBordersForWholeWorld)
: m_countryPolygonsTree(borders::GetOrCreateCountryPolygonsTree(borderPath))
, m_haveBordersForWholeWorld(haveBordersForWholeWorld)
{}
std::vector<std::string> CountriesFilesAffiliation::GetAffiliations(FeatureBuilder const & fb) const
{
return affiliation::GetAffiliations(fb, m_countryPolygonsTree, m_haveBordersForWholeWorld);
}
std::vector<std::string> CountriesFilesAffiliation::GetAffiliations(m2::PointD const & point) const
{
return affiliation::GetAffiliations(point, m_countryPolygonsTree, m_haveBordersForWholeWorld);
}
std::vector<std::string> CountriesFilesAffiliation::GetAffiliations(std::vector<m2::PointD> const & points) const
{
return affiliation::GetAffiliations(points, m_countryPolygonsTree, m_haveBordersForWholeWorld);
}
bool CountriesFilesAffiliation::HasCountryByName(std::string const & name) const
{
return m_countryPolygonsTree.HasRegionByName(name);
}
CountriesFilesIndexAffiliation::CountriesFilesIndexAffiliation(std::string const & borderPath,
bool haveBordersForWholeWorld)
: CountriesFilesAffiliation(borderPath, haveBordersForWholeWorld)
{
static std::mutex cacheMutex;
static std::unordered_map<std::string, std::shared_ptr<Tree>> cache;
auto const key = borderPath + std::to_string(haveBordersForWholeWorld);
std::lock_guard<std::mutex> lock(cacheMutex);
auto const it = cache.find(key);
if (it != std::cend(cache))
{
m_index = it->second;
return;
}
auto const net = generator::cells_merger::MakeNet(0.2 /* step */, mercator::Bounds::kMinX, mercator::Bounds::kMinY,
mercator::Bounds::kMaxX, mercator::Bounds::kMaxY);
auto const index = BuildIndex(net);
m_index = index;
cache.emplace(key, index);
}
std::vector<std::string> CountriesFilesIndexAffiliation::GetAffiliations(FeatureBuilder const & fb) const
{
return affiliation::GetAffiliations(fb, m_index);
}
std::vector<std::string> CountriesFilesIndexAffiliation::GetAffiliations(m2::PointD const & point) const
{
return affiliation::GetAffiliations(point, m_index);
}
std::shared_ptr<CountriesFilesIndexAffiliation::Tree> CountriesFilesIndexAffiliation::BuildIndex(
std::vector<m2::RectD> const & net)
{
std::unordered_map<borders::CountryPolygons const *, std::vector<m2::RectD>> countriesRects;
std::mutex countriesRectsMutex;
std::vector<Value> treeCells;
std::mutex treeCellsMutex;
auto const numThreads = GetPlatform().CpuCores();
{
base::ComputationalThreadPool pool(numThreads);
for (auto const & rect : net)
{
pool.SubmitWork([&, rect]()
{
std::vector<std::reference_wrapper<borders::CountryPolygons const>> countries;
m_countryPolygonsTree.ForEachCountryInRect(rect,
[&](auto const & country) { countries.emplace_back(country); });
if (m_haveBordersForWholeWorld && countries.size() == 1)
{
borders::CountryPolygons const & country = countries.front();
std::lock_guard<std::mutex> lock(countriesRectsMutex);
countriesRects[&country].emplace_back(rect);
}
else
{
auto const box = affiliation::MakeBox(rect);
std::vector<std::reference_wrapper<borders::CountryPolygons const>> interCountries;
for (borders::CountryPolygons const & cp : countries)
{
cp.ForAnyPolygon([&](auto const & polygon)
{
if (!boost::geometry::intersects(polygon.Data(), box))
return false;
interCountries.emplace_back(cp);
return true;
});
}
if (interCountries.empty())
return;
if (interCountries.size() == 1)
{
borders::CountryPolygons const & country = interCountries.front();
std::lock_guard<std::mutex> lock(countriesRectsMutex);
countriesRects[&country].emplace_back(rect);
}
else
{
std::lock_guard<std::mutex> lock(treeCellsMutex);
treeCells.emplace_back(box, std::move(interCountries));
}
}
});
}
}
{
base::ComputationalThreadPool pool(numThreads);
for (auto & pair : countriesRects)
{
pool.SubmitWork([&, countryPtr{pair.first}, rects{std::move(pair.second)}]() mutable
{
generator::cells_merger::CellsMerger merger(std::move(rects));
auto const merged = merger.Merge();
for (auto const & rect : merged)
{
std::vector<std::reference_wrapper<borders::CountryPolygons const>> interCountries{*countryPtr};
std::lock_guard<std::mutex> lock(treeCellsMutex);
treeCells.emplace_back(affiliation::MakeBox(rect), std::move(interCountries));
}
});
}
}
return std::make_shared<Tree>(treeCells);
}
SingleAffiliation::SingleAffiliation(std::string const & filename) : m_filename(filename) {}
std::vector<std::string> SingleAffiliation::GetAffiliations(FeatureBuilder const &) const
{
return {m_filename};
}
bool SingleAffiliation::HasCountryByName(std::string const & name) const
{
return name == m_filename;
}
std::vector<std::string> SingleAffiliation::GetAffiliations(m2::PointD const &) const
{
return {m_filename};
}
} // namespace feature

78
generator/affiliation.hpp Normal file
View file

@ -0,0 +1,78 @@
#pragma once
#include "generator/borders.hpp"
#include "generator/feature_builder.hpp"
#include <string>
#include <vector>
#include <boost/geometry/index/rtree.hpp>
namespace feature
{
class AffiliationInterface
{
public:
virtual ~AffiliationInterface() = default;
// The method will return the names of the buckets to which the fb belongs.
virtual std::vector<std::string> GetAffiliations(FeatureBuilder const & fb) const = 0;
virtual std::vector<std::string> GetAffiliations(m2::PointD const & point) const = 0;
virtual bool HasCountryByName(std::string const & name) const = 0;
};
class CountriesFilesAffiliation : public AffiliationInterface
{
public:
CountriesFilesAffiliation(std::string const & borderPath, bool haveBordersForWholeWorld);
// AffiliationInterface overrides:
std::vector<std::string> GetAffiliations(FeatureBuilder const & fb) const override;
std::vector<std::string> GetAffiliations(m2::PointD const & point) const override;
std::vector<std::string> GetAffiliations(std::vector<m2::PointD> const & points) const;
bool HasCountryByName(std::string const & name) const override;
protected:
borders::CountryPolygonsCollection const & m_countryPolygonsTree;
bool m_haveBordersForWholeWorld;
};
class CountriesFilesIndexAffiliation : public CountriesFilesAffiliation
{
public:
using Box = boost::geometry::model::box<m2::PointD>;
using Value = std::pair<Box, std::vector<std::reference_wrapper<borders::CountryPolygons const>>>;
using Tree = boost::geometry::index::rtree<Value, boost::geometry::index::quadratic<16>>;
CountriesFilesIndexAffiliation(std::string const & borderPath, bool haveBordersForWholeWorld);
// AffiliationInterface overrides:
std::vector<std::string> GetAffiliations(FeatureBuilder const & fb) const override;
std::vector<std::string> GetAffiliations(m2::PointD const & point) const override;
private:
std::shared_ptr<Tree> BuildIndex(std::vector<m2::RectD> const & net);
std::shared_ptr<Tree> m_index;
};
class SingleAffiliation : public AffiliationInterface
{
public:
explicit SingleAffiliation(std::string const & filename);
// AffiliationInterface overrides:
std::vector<std::string> GetAffiliations(FeatureBuilder const &) const override;
std::vector<std::string> GetAffiliations(m2::PointD const & point) const override;
bool HasCountryByName(std::string const & name) const override;
private:
std::string m_filename;
};
} // namespace feature
using AffiliationInterfacePtr = std::shared_ptr<feature::AffiliationInterface>;

View file

@ -0,0 +1,214 @@
#include "generator/altitude_generator.hpp"
#include "generator/srtm_parser.hpp"
#include "routing/routing_helpers.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_altitude.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_processor.hpp"
#include "coding/files_container.hpp"
#include "coding/succinct_mapper.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include "base/logging.hpp"
#include "base/scope_guard.hpp"
#include "base/stl_helpers.hpp"
#include "defines.hpp"
#include "3party/succinct/elias_fano.hpp"
#include "3party/succinct/rs_bit_vector.hpp"
namespace routing
{
using namespace feature;
using namespace geometry;
namespace
{
class SrtmGetter : public AltitudeGetter
{
public:
explicit SrtmGetter(std::string const & srtmDir) : m_srtmManager(srtmDir) {}
// AltitudeGetter overrides:
Altitude GetAltitude(m2::PointD const & p) override { return m_srtmManager.GetAltitude(mercator::ToLatLon(p)); }
void PrintStatsAndPurge() override
{
LOG(LINFO, ("Srtm tiles number (x26Mb):", m_srtmManager.GeTilesNumber()));
m_srtmManager.Purge();
}
private:
generator::SrtmTileManager m_srtmManager;
};
class Processor
{
public:
struct FeatureAltitude
{
FeatureAltitude(uint32_t featureId, geometry::Altitudes && altitudes)
: m_featureId(featureId)
, m_altitudes(std::move(altitudes))
{}
uint32_t m_featureId;
feature::Altitudes m_altitudes;
};
explicit Processor(AltitudeGetter & altitudeGetter)
: m_minAltitude(geometry::kInvalidAltitude)
, m_altitudeGetter(altitudeGetter)
{}
void operator()(FeatureType & f, uint32_t id)
{
CHECK_EQUAL(f.GetID().m_index, id, ());
CHECK_EQUAL(id, m_altitudeAvailabilityBuilder.size(), ());
bool hasAltitude = false;
SCOPE_GUARD(altitudeAvailabilityBuilding, [&]() { m_altitudeAvailabilityBuilder.push_back(hasAltitude); });
if (!routing::IsRoad(feature::TypesHolder(f)))
return;
f.ParseGeometry(FeatureType::BEST_GEOMETRY);
size_t const pointsCount = f.GetPointsCount();
if (pointsCount == 0)
return;
geometry::Altitudes altitudes;
altitudes.reserve(pointsCount);
Altitude minFeatureAltitude = geometry::kInvalidAltitude;
for (size_t i = 0; i < pointsCount; ++i)
{
auto const & pt = f.GetPoint(i);
Altitude const a = m_altitudeGetter.GetAltitude(pt);
if (a == geometry::kInvalidAltitude)
{
// Print warning for missing altitude point (if not a ferry or so).
auto const type = CarModel::AllLimitsInstance().GetHighwayType(feature::TypesHolder(f));
if (type && *type != HighwayType::RouteFerry && *type != HighwayType::RouteShuttleTrain)
LOG(LWARNING, ("Invalid altitude at:", mercator::ToLatLon(pt)));
// One invalid point invalidates the whole feature.
return;
}
if (minFeatureAltitude == geometry::kInvalidAltitude)
minFeatureAltitude = a;
else
minFeatureAltitude = std::min(minFeatureAltitude, a);
altitudes.push_back(a);
}
hasAltitude = true;
m_featureAltitudes.emplace_back(id, std::move(altitudes));
if (m_minAltitude == geometry::kInvalidAltitude)
m_minAltitude = minFeatureAltitude;
else
m_minAltitude = std::min(minFeatureAltitude, m_minAltitude);
}
bool HasAltitudeInfo() const { return !m_featureAltitudes.empty(); }
public:
std::vector<FeatureAltitude> m_featureAltitudes;
succinct::bit_vector_builder m_altitudeAvailabilityBuilder;
Altitude m_minAltitude;
AltitudeGetter & m_altitudeGetter;
};
} // namespace
void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGetter)
{
try
{
// Preparing altitude information.
Processor processor(altitudeGetter);
feature::ForEachFeature(mwmPath, processor);
processor.m_altitudeGetter.PrintStatsAndPurge();
if (!processor.HasAltitudeInfo())
{
// Possible for small islands like Bouvet or Willis.
LOG(LWARNING, ("No altitude information for road features of mwm:", mwmPath));
return;
}
FilesContainerW cont(mwmPath, FileWriter::OP_WRITE_EXISTING);
auto w = cont.GetWriter(ALTITUDES_FILE_TAG);
AltitudeHeader header;
header.m_minAltitude = processor.m_minAltitude;
auto const startOffset = w->Pos();
header.Serialize(*w);
{
// Altitude availability serialization.
coding::FreezeVisitor<Writer> visitor(*w);
succinct::rs_bit_vector(&processor.m_altitudeAvailabilityBuilder).map(visitor);
}
header.m_featureTableOffset = base::checked_cast<uint32_t>(w->Pos() - startOffset);
std::vector<uint32_t> offsets;
std::vector<uint8_t> deltas;
{
// Altitude info serialization to memory.
MemWriter<std::vector<uint8_t>> writer(deltas);
for (auto const & a : processor.m_featureAltitudes)
{
offsets.push_back(base::checked_cast<uint32_t>(writer.Pos()));
a.m_altitudes.Serialize(header.m_minAltitude, writer);
}
}
{
// Altitude offsets serialization.
CHECK(base::IsSortedAndUnique(offsets), ());
succinct::elias_fano::elias_fano_builder builder(offsets.back(), offsets.size());
for (uint32_t offset : offsets)
builder.push_back(offset);
coding::FreezeVisitor<Writer> visitor(*w);
succinct::elias_fano(&builder).map(visitor);
}
// Writing altitude info.
header.m_altitudesOffset = base::checked_cast<uint32_t>(w->Pos() - startOffset);
w->Write(deltas.data(), deltas.size());
w->WritePaddingByEnd(8);
header.m_endOffset = base::checked_cast<uint32_t>(w->Pos() - startOffset);
// Rewriting header info.
auto const endOffset = w->Pos();
w->Seek(startOffset);
header.Serialize(w);
w->Seek(endOffset);
LOG(LINFO, (ALTITUDES_FILE_TAG, "section is ready. The size is", header.m_endOffset));
if (processor.HasAltitudeInfo())
LOG(LINFO, ("Min altitude is", processor.m_minAltitude));
else
LOG(LINFO, ("Min altitude isn't defined."));
}
catch (RootException const & e)
{
LOG(LERROR, ("An exception happened while creating", ALTITUDES_FILE_TAG, "section:", e.what()));
}
}
void BuildRoadAltitudes(std::string const & mwmPath, std::string const & srtmDir)
{
LOG(LINFO, ("mwmPath =", mwmPath, "srtmDir =", srtmDir));
SrtmGetter srtmGetter(srtmDir);
BuildRoadAltitudes(mwmPath, srtmGetter);
}
} // namespace routing

View file

@ -0,0 +1,29 @@
#pragma once
#include "geometry/point2d.hpp"
#include "geometry/point_with_altitude.hpp"
#include <string>
namespace routing
{
class AltitudeGetter
{
public:
virtual geometry::Altitude GetAltitude(m2::PointD const & p) = 0;
virtual void PrintStatsAndPurge() {}
};
/// \brief Adds altitude section to mwm. It has the following format:
/// File offset (bytes) Field name Field size (bytes)
/// 0 version 2
/// 2 min altitude 2
/// 4 feature table offset 4
/// 8 altitude info offset 4
/// 12 end of section 4
/// 16 altitude availability feat. table offset - 16
/// feat. table offset feature table alt. info offset - feat. table offset
/// alt. info offset altitude info end of section - alt. info offset
void BuildRoadAltitudes(std::string const & mwmPath, AltitudeGetter & altitudeGetter);
void BuildRoadAltitudes(std::string const & mwmPath, std::string const & srtmDir);
} // namespace routing

275
generator/borders.cpp Normal file
View file

@ -0,0 +1,275 @@
#include "generator/borders.hpp"
#include "generator/borders.hpp"
#include "platform/platform.hpp"
#include "storage/country_decl.hpp"
#include "indexer/scales.hpp"
#include "coding/files_container.hpp"
#include "coding/read_write_utils.hpp"
#include "coding/varint.hpp"
#include "geometry/mercator.hpp"
#include "geometry/simplification.hpp"
#include "base/assert.hpp"
#include "base/exception.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <vector>
#include "defines.hpp"
namespace borders
{
namespace
{
template <class ToDo>
void ForEachCountry(std::string const & baseDir, ToDo && toDo)
{
std::string const bordersDir = base::JoinPath(baseDir, BORDERS_DIR);
CHECK(Platform::IsFileExistsByFullPath(bordersDir), ("Cannot read borders directory", bordersDir));
Platform::FilesList files;
Platform::GetFilesByExt(bordersDir, BORDERS_EXTENSION, files);
for (std::string file : files)
{
PolygonsList polygons;
if (LoadBorders(bordersDir + file, polygons))
{
base::GetNameWithoutExt(file);
toDo(std::move(file), std::move(polygons));
}
}
}
class PackedBordersGenerator
{
public:
explicit PackedBordersGenerator(std::string const & baseDir) : m_writer(baseDir + PACKED_POLYGONS_FILE) {}
void operator()(std::string name, PolygonsList && borders)
{
// use index in vector as tag
auto w = m_writer.GetWriter(strings::to_string(m_polys.size()));
serial::GeometryCodingParams cp;
// calc rect
m2::RectD rect;
for (m2::RegionD const & border : borders)
rect.Add(border.GetRect());
// store polygon info
m_polys.push_back(storage::CountryDef(name, rect));
// write polygons as paths
WriteVarUint(w, borders.size());
for (m2::RegionD const & border : borders)
{
std::vector<m2::PointD> const & in = border.Data();
std::vector<m2::PointD> out;
/// @todo Choose scale level for simplification.
SimplifyDefault(in.begin(), in.end(), math::Pow2(scales::GetEpsilonForSimplify(10)), out);
serial::SaveOuterPath(out, cp, *w);
}
}
void WritePolygonsInfo()
{
auto w = m_writer.GetWriter(PACKED_POLYGONS_INFO_TAG);
rw::Write(*w, m_polys);
}
private:
FilesContainerW m_writer;
std::vector<storage::CountryDef> m_polys;
};
bool ReadPolygon(std::istream & stream, Polygon & poly, std::string const & filename)
{
std::string line, name;
double lon, lat;
// read ring id, fail if it's empty
std::getline(stream, name);
if (name.empty() || name == "END")
return false;
while (stream.good())
{
std::getline(stream, line);
strings::Trim(line);
if (line.empty())
continue;
if (line == "END")
break;
std::istringstream iss(line);
iss >> lon >> lat;
CHECK(!iss.fail(), ("Incorrect data in", filename));
poly.AddPoint(mercator::FromLatLon(lat, lon));
}
// drop inner rings
return name[0] != '!';
}
} // namespace
bool CountryPolygons::Contains(m2::PointD const & point) const
{
return m_polygons.ForAnyInRect(m2::RectD(point, point), [&](auto const & rgn)
{ return rgn.Contains(point, ContainsCompareFn(GetContainsEpsilon())); });
}
bool LoadBorders(std::string const & borderFile, PolygonsList & outBorders)
{
std::ifstream stream(borderFile);
std::string line;
if (!std::getline(stream, line).good()) // skip title
{
LOG(LERROR, ("Polygon file is empty:", borderFile));
return false;
}
Polygon currentPolygon;
while (ReadPolygon(stream, currentPolygon, borderFile))
{
CHECK(currentPolygon.IsValid(), ("Invalid region in", borderFile));
outBorders.emplace_back(std::move(currentPolygon));
currentPolygon = {};
}
CHECK(!outBorders.empty(), ("No borders were loaded from", borderFile));
return true;
}
bool GetBordersRect(std::string const & baseDir, std::string const & country, m2::RectD & bordersRect)
{
auto const bordersFile = base::JoinPath(baseDir, BORDERS_DIR, country + BORDERS_EXTENSION);
if (!Platform::IsFileExistsByFullPath(bordersFile))
{
LOG(LWARNING, ("File with borders does not exist:", bordersFile));
return false;
}
PolygonsList borders;
CHECK(LoadBorders(bordersFile, borders), ());
bordersRect.MakeEmpty();
for (auto const & border : borders)
bordersRect.Add(border.GetRect());
return true;
}
CountryPolygonsCollection LoadCountriesList(std::string const & baseDir)
{
LOG(LINFO, ("Loading countries in", BORDERS_DIR, "folder in", baseDir));
CountryPolygonsCollection countryPolygonsCollection;
ForEachCountry(baseDir, [&](std::string name, PolygonsList && borders)
{
PolygonsTree polygons;
for (Polygon & border : borders)
{
auto const rect = border.GetRect();
polygons.Add(std::move(border), rect);
}
countryPolygonsCollection.Add(CountryPolygons(std::move(name), std::move(polygons)));
});
LOG(LINFO, ("Countries loaded:", countryPolygonsCollection.GetSize()));
CHECK_NOT_EQUAL(countryPolygonsCollection.GetSize(), 0, (baseDir));
return countryPolygonsCollection;
}
void GeneratePackedBorders(std::string const & baseDir)
{
PackedBordersGenerator generator(baseDir);
ForEachCountry(baseDir, generator);
generator.WritePolygonsInfo();
}
void DumpBorderToPolyFile(std::string const & targetDir, storage::CountryId const & mwmName,
PolygonsList const & polygons)
{
CHECK(!polygons.empty(), ());
std::string const filePath = base::JoinPath(targetDir, mwmName + ".poly");
std::ofstream poly(filePath);
CHECK(poly.good(), ());
// Used to have fixed precicion with 6 digits. And Alaska has 4 digits after comma :) Strange, but as is.
poly << std::setprecision(6) << std::fixed;
poly << mwmName << std::endl;
size_t polygonId = 1;
for (auto const & points : polygons)
{
poly << polygonId << std::endl;
++polygonId;
for (auto const & point : points.Data())
{
ms::LatLon const ll = mercator::ToLatLon(point);
poly << " " << std::scientific << ll.m_lon << " " << ll.m_lat << std::endl;
}
poly << "END" << std::endl;
}
poly << "END" << std::endl;
poly.close();
}
void UnpackBorders(std::string const & baseDir, std::string const & targetDir)
{
if (!Platform::IsFileExistsByFullPath(targetDir) && !Platform::MkDirChecked(targetDir))
MYTHROW(FileSystemException, ("Unable to find or create directory", targetDir));
std::vector<storage::CountryDef> countries;
FilesContainerR reader(base::JoinPath(baseDir, PACKED_POLYGONS_FILE));
ReaderSource<ModelReaderPtr> src(reader.GetReader(PACKED_POLYGONS_INFO_TAG));
rw::Read(src, countries);
for (size_t id = 0; id < countries.size(); id++)
{
storage::CountryId const mwmName = countries[id].m_countryId;
src = reader.GetReader(strings::to_string(id));
auto const polygons = ReadPolygonsOfOneBorder(src);
DumpBorderToPolyFile(targetDir, mwmName, polygons);
}
}
CountryPolygonsCollection const & GetOrCreateCountryPolygonsTree(std::string const & baseDir)
{
/// @todo Are there many different paths with polygons, that we have to store map?
static std::mutex mutex;
static std::unordered_map<std::string, CountryPolygonsCollection> countriesMap;
std::lock_guard<std::mutex> lock(mutex);
auto const it = countriesMap.find(baseDir);
if (it != countriesMap.cend())
return it->second;
auto const eIt = countriesMap.emplace(baseDir, LoadCountriesList(baseDir));
return eIt.first->second;
}
} // namespace borders

168
generator/borders.hpp Normal file
View file

@ -0,0 +1,168 @@
#pragma once
#include "storage/storage_defines.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/reader.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/region2d.hpp"
#include "geometry/tree4d.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define BORDERS_DIR "borders/"
#define BORDERS_EXTENSION ".poly"
namespace borders
{
// The raw borders that we have somehow obtained (probably downloaded from
// the OSM and then manually tweaked, but the exact knowledge is lost) are
// stored in the BORDERS_DIR.
// These are the sources of the mwm borders: each file there corresponds
// to exactly one mwm and all mwms except for World and WorldCoasts must
// have a borders file to be generated from.
//
// The file format for raw borders is described at
// https://wiki.openstreetmap.org/wiki/Osmosis/Polygon_Filter_File_Format
//
// The borders for all mwm files are shipped with the application in
// the mwm binary format for geometry data (see coding/geometry_coding.hpp).
// However, storing every single point turned out to take too much space,
// therefore the borders are simplified. This simplification may lead to
// unwanted consequences (for example, empty spaces may occur between mwms)
// but currently we do not take any action against them.
using Polygon = m2::RegionD;
using PolygonsTree = m4::Tree<Polygon>;
class CountryPolygons
{
public:
CountryPolygons() = default;
explicit CountryPolygons(std::string && name, PolygonsTree && regions)
: m_name(std::move(name))
, m_polygons(std::move(regions))
{}
std::string const & GetName() const { return m_name; }
bool IsEmpty() const { return m_polygons.IsEmpty(); }
void Clear()
{
m_polygons.Clear();
m_name.clear();
}
class ContainsCompareFn
{
double m_eps, m_squareEps;
public:
explicit ContainsCompareFn(double eps) : m_eps(eps), m_squareEps(eps * eps) {}
bool EqualPoints(m2::PointD const & p1, m2::PointD const & p2) const
{
return AlmostEqualAbs(p1.x, p2.x, m_eps) && AlmostEqualAbs(p1.y, p2.y, m_eps);
}
bool EqualZeroSquarePrecision(double val) const { return AlmostEqualAbs(val, 0.0, m_squareEps); }
};
static double GetContainsEpsilon() { return 1.0E-4; }
bool Contains(m2::PointD const & point) const;
template <typename Do>
void ForEachPolygon(Do && fn) const
{
m_polygons.ForEach(std::forward<Do>(fn));
}
template <typename Do>
bool ForAnyPolygon(Do && fn) const
{
return m_polygons.ForAny(std::forward<Do>(fn));
}
private:
std::string m_name;
/// @todo Is it an overkill to store Tree4D for each country's polygon?
PolygonsTree m_polygons;
};
class CountryPolygonsCollection
{
public:
CountryPolygonsCollection() = default;
void Add(CountryPolygons && countryPolygons)
{
auto const res = m_countryPolygonsMap.emplace(countryPolygons.GetName(), std::move(countryPolygons));
CHECK(res.second, ());
auto const & inserted = res.first->second;
inserted.ForEachPolygon([&inserted, this](Polygon const & polygon)
{ m_regionsTree.Add(inserted, polygon.GetRect()); });
}
size_t GetSize() const { return m_countryPolygonsMap.size(); }
template <typename ToDo>
void ForEachCountryInRect(m2::RectD const & rect, ToDo && toDo) const
{
std::unordered_set<CountryPolygons const *> uniq;
m_regionsTree.ForEachInRect(rect, [&](CountryPolygons const & cp)
{
if (uniq.insert(&cp).second)
toDo(cp);
});
}
bool HasRegionByName(std::string const & name) const { return m_countryPolygonsMap.count(name) != 0; }
CountryPolygons const & GetRegionByName(std::string const & name) const
{
ASSERT(HasRegionByName(name), ());
return m_countryPolygonsMap.at(name);
}
private:
m4::Tree<std::reference_wrapper<CountryPolygons const>> m_regionsTree;
std::unordered_map<std::string, CountryPolygons> m_countryPolygonsMap;
};
using PolygonsList = std::vector<Polygon>;
/// @return false if borderFile can't be opened
bool LoadBorders(std::string const & borderFile, PolygonsList & outBorders);
bool GetBordersRect(std::string const & baseDir, std::string const & country, m2::RectD & bordersRect);
bool LoadCountriesList(std::string const & baseDir, CountryPolygonsCollection & countries);
void GeneratePackedBorders(std::string const & baseDir);
template <typename Source>
PolygonsList ReadPolygonsOfOneBorder(Source & src)
{
auto const count = ReadVarUint<uint32_t>(src);
PolygonsList result(count);
for (size_t i = 0; i < count; ++i)
{
std::vector<m2::PointD> points;
serial::LoadOuterPath(src, serial::GeometryCodingParams(), points);
result[i] = m2::RegionD(std::move(points));
}
return result;
}
void DumpBorderToPolyFile(std::string const & filePath, storage::CountryId const & mwmName,
PolygonsList const & polygons);
void UnpackBorders(std::string const & baseDir, std::string const & targetDir);
CountryPolygonsCollection const & GetOrCreateCountryPolygonsTree(std::string const & baseDir);
} // namespace borders

View file

@ -0,0 +1,61 @@
#include "generator/boundary_postcodes_enricher.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "platform/platform.hpp"
#include "coding/read_write_utils.hpp"
#include "geometry/point2d.hpp"
namespace generator
{
BoundaryPostcodesEnricher::BoundaryPostcodesEnricher(std::string const & boundaryPostcodesFilename)
{
// May be absent for tests because TestMwmBuilder cannot collect data from osm elements.
if (!Platform::IsFileExistsByFullPath(boundaryPostcodesFilename))
return;
FileReader reader(boundaryPostcodesFilename);
ReaderSource<FileReader> src(reader);
while (src.Size() > 0)
{
std::string postcode;
rw::ReadNonEmpty(src, postcode);
std::vector<m2::PointD> geometry;
rw::ReadVectorOfPOD(src, geometry);
CHECK(!postcode.empty() && !geometry.empty(), ());
m_boundaryPostcodes.emplace_back(std::move(postcode), std::move(geometry));
m_boundariesTree.Add(m_boundaryPostcodes.size() - 1, m_boundaryPostcodes.back().second.GetRect());
}
}
void BoundaryPostcodesEnricher::Enrich(feature::FeatureBuilder & fb) const
{
auto & params = fb.GetParams();
if (!params.GetPostcode().empty() || !ftypes::IsAddressObjectChecker::Instance()(fb.GetTypes()))
return;
auto const hasName = !fb.GetMultilangName().IsEmpty();
auto const hasHouseNumber = !params.house.IsEmpty();
// We do not save postcodes for unnamed features without house number to reduce amount of data.
// For example with this filter we have +100Kb for Turkey_Marmara Region_Istanbul.mwm, without
// filter we have +3Mb because of huge amount of unnamed houses without number.
if (!hasName && !hasHouseNumber)
return;
auto const center = fb.GetKeyPoint();
m_boundariesTree.ForAnyInRect(m2::RectD(center, center), [&](size_t i)
{
CHECK_LESS(i, m_boundaryPostcodes.size(), ());
if (!m_boundaryPostcodes[i].second.Contains(center))
return false;
params.SetPostcode(m_boundaryPostcodes[i].first);
return true;
});
}
} // namespace generator

View file

@ -0,0 +1,25 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "geometry/region2d.hpp"
#include "geometry/tree4d.hpp"
#include <string>
#include <utility>
#include <vector>
namespace generator
{
class BoundaryPostcodesEnricher
{
public:
explicit BoundaryPostcodesEnricher(std::string const & boundaryPostcodesFilename);
void Enrich(feature::FeatureBuilder & fb) const;
private:
std::vector<std::pair<std::string, m2::RegionD>> m_boundaryPostcodes;
m4::Tree<size_t> m_boundariesTree;
};
} // namespace generator

159
generator/brands_loader.cpp Normal file
View file

@ -0,0 +1,159 @@
#include "generator/brands_loader.hpp"
#include "indexer/brands_holder.hpp"
#include "platform/platform.hpp"
#include "base/exception.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include "cppjansson/cppjansson.hpp"
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
namespace generator
{
using base::GeoObjectId;
using std::pair, std::string, std::unordered_map, std::vector;
DECLARE_EXCEPTION(ParsingError, RootException);
static void ParseFeatureToBrand(json_t * root, string const & field, GeoObjectId::Type type,
vector<pair<GeoObjectId, uint32_t>> & result)
{
auto arr = base::GetJSONOptionalField(root, field);
if (arr == nullptr)
return;
char const * key;
json_t * value;
json_object_foreach(arr, key, value)
{
result.emplace_back();
uint64_t id;
if (!strings::to_uint64(key, id))
MYTHROW(ParsingError, ("Incorrect OSM id:", key));
result.back().first = GeoObjectId(type, id);
FromJSON(value, result.back().second);
}
}
void ParseTranslations(json_t * root, std::set<string> const & keys, unordered_map<uint32_t, string> & idToKey)
{
string const empty;
auto getKey = [&](string & translation) -> string const &
{
strings::MakeLowerCaseInplace(translation);
translation = strings::Normalize(translation);
replace(translation.begin(), translation.end(), ' ', '_');
replace(translation.begin(), translation.end(), '-', '_');
replace(translation.begin(), translation.end(), '\'', '_');
auto const it = keys.find(translation);
if (it != keys.end())
return *it;
return empty;
};
char const * key;
json_t * value;
json_object_foreach(root, key, value)
{
vector<string> enTranslations;
if (!FromJSONObjectOptional(value, "en", enTranslations))
continue;
for (auto & translation : enTranslations)
{
auto const & indexKey = getKey(translation);
if (!indexKey.empty())
{
uint32_t id;
if (!strings::to_uint(key, id))
MYTHROW(ParsingError, ("Incorrect brand id:", key));
idToKey.emplace(id, indexKey);
break;
}
}
}
}
bool LoadBrands(string const & brandsFilename, string const & translationsFilename,
unordered_map<GeoObjectId, string> & brands)
{
string jsonBuffer;
try
{
GetPlatform().GetReader(brandsFilename)->ReadAsString(jsonBuffer);
}
catch (RootException const & e)
{
LOG(LERROR, ("Can't open", brandsFilename, e.Msg()));
return false;
}
vector<pair<GeoObjectId, uint32_t>> objects;
try
{
base::Json root(jsonBuffer.c_str());
CHECK(root.get() != nullptr, ("Cannot parse the json file:", brandsFilename));
ParseFeatureToBrand(root.get(), "nodes", GeoObjectId::Type::ObsoleteOsmNode, objects);
ParseFeatureToBrand(root.get(), "ways", GeoObjectId::Type::ObsoleteOsmWay, objects);
ParseFeatureToBrand(root.get(), "relations", GeoObjectId::Type::ObsoleteOsmRelation, objects);
}
catch (base::Json::Exception const &)
{
LOG(LERROR, ("Cannot create base::Json from", brandsFilename));
return false;
}
catch (ParsingError const & e)
{
LOG(LERROR, ("Invalid data in", brandsFilename, e.Msg()));
return false;
}
try
{
GetPlatform().GetReader(translationsFilename)->ReadAsString(jsonBuffer);
}
catch (RootException const & e)
{
LOG(LERROR, ("Can't open", translationsFilename, e.Msg()));
return false;
}
unordered_map<uint32_t, string> idToKey;
try
{
base::Json root(jsonBuffer.c_str());
CHECK(root.get() != nullptr, ("Cannot parse the json file:", translationsFilename));
auto const & keys = indexer::GetDefaultBrands().GetKeys();
ParseTranslations(root.get(), keys, idToKey);
}
catch (base::Json::Exception const &)
{
LOG(LERROR, ("Cannot create base::Json from", translationsFilename));
return false;
}
catch (ParsingError const & e)
{
LOG(LERROR, ("Invalid data in", translationsFilename, e.Msg()));
return false;
}
for (auto const & o : objects)
{
auto const keyIt = idToKey.find(o.second);
if (keyIt != idToKey.end())
brands.emplace(o.first, keyIt->second);
}
LOG(LINFO, (idToKey.size(), "brands for", brands.size(), "objects successfully loaded.",
objects.size() - brands.size(), "objects not matched to known brands."));
return true;
}
} // namespace generator

View file

@ -0,0 +1,56 @@
#pragma once
#include "base/geo_object_id.hpp"
#include <string>
#include <unordered_map>
namespace generator
{
// Loads brand GeoObjectId to brand key map from json.
// |brandsFilename| file example:
//
//------------------------------------------------------
// {
// "nodes": {
// "2132500347": 13,
// "5321137826": 12
// },
// "ways: {
// "440527172": 13,
// "149816366": 12
// },
// "relations": {
// "6018309": 13,
// "6228042": 12
// }
// }
//------------------------------------------------------
//
// |translationsFilename| example:
//
//------------------------------------------------------
// {
// "12": {
// "en": ["Subway"],
// "ru": ["Сабвей", "Сабвэй"]
// },
// "13": {
// "en": ["McDonalds", "Mc Donalds"],
// "ru": ["Макдоналдс", "Мак Дональдс", "Мак Дональд'с"]
// }
// }
//------------------------------------------------------
//
// Which means OSM node 2132500347, OSM way 440527172 and OSM relation 6018309 belong to
// brand with id 13.
// OSM node 5321137826, OSM way 149816366 and OSM relation 6228042 belong to brand with id 12.
// Brand with id 13 has names in English and Russian. The most popular English name is "McDonalds",
// the second most popluar one is "Mc Donalds". The most popular Russian name is "Макдоналдс", then
// "Мак Дональдс", then "Мак Дональд'с".
// Brand with id 12 has names in English and Russian. Name in English is "Subway". The most popular Russian
// name is "Сабвей", then "Сабвэй".
bool LoadBrands(std::string const & brandsFilename, std::string const & translationsFilename,
std::unordered_map<base::GeoObjectId, std::string> & brands);
} // namespace generator

View file

@ -0,0 +1,337 @@
#include "generator/camera_info_collector.hpp"
#include "routing/speed_camera_ser_des.hpp"
#include "platform/local_country_file.hpp"
#include "geometry/mercator.hpp"
#include "coding/point_coding.hpp"
#include "coding/reader.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include "base/logging.hpp"
#include <algorithm>
#include <limits>
namespace generator
{
CamerasInfoCollector::CamerasInfoCollector(std::string const & dataFilePath, std::string const & camerasInfoPath,
std::string const & osmIdsToFeatureIdsPath)
{
routing::OsmIdToFeatureIds osmIdToFeatureIds;
routing::ParseWaysOsmIdToFeatureIdMapping(osmIdsToFeatureIdsPath, osmIdToFeatureIds);
if (!ParseIntermediateInfo(camerasInfoPath, osmIdToFeatureIds))
LOG(LCRITICAL, ("Unable to parse intermediate file(", camerasInfoPath, ") about cameras info"));
platform::LocalCountryFile file = platform::LocalCountryFile::MakeTemporary(dataFilePath);
FrozenDataSource dataSource;
auto registerResult = dataSource.RegisterMap(file);
if (registerResult.second != MwmSet::RegResult::Success)
LOG(LCRITICAL, ("Unable to RegisterMap:", dataFilePath));
std::vector<Camera> goodCameras;
for (auto & camera : m_cameras)
{
camera.ParseDirection();
camera.FindClosestSegment(dataSource, registerResult.first);
// Don't match camera as good if we couldn't find any way.
if (!camera.m_data.m_ways.empty())
goodCameras.emplace_back(camera);
}
m_cameras = std::move(goodCameras);
}
void CamerasInfoCollector::Serialize(FileWriter & writer) const
{
// Some cameras have several ways related to them.
// We create N cameras with one way attached from a camera that has N ways attached.
std::vector<CamerasInfoCollector::Camera> flattenedCameras;
for (auto const & camera : m_cameras)
{
for (auto const & way : camera.m_data.m_ways)
{
flattenedCameras.emplace_back(camera.m_data.m_center, camera.m_data.m_maxSpeedKmPH,
std::vector<routing::SpeedCameraMwmPosition>{way});
}
}
routing::SpeedCameraMwmHeader header;
header.SetVersion(routing::SpeedCameraMwmHeader::kLatestVersion);
header.SetAmount(base::asserted_cast<uint32_t>(flattenedCameras.size()));
header.Serialize(writer);
std::sort(flattenedCameras.begin(), flattenedCameras.end(), [](auto const & a, auto const & b)
{
CHECK_EQUAL(a.m_data.m_ways.size(), 1, ());
CHECK_EQUAL(b.m_data.m_ways.size(), 1, ());
auto const & aWay = a.m_data.m_ways.back();
auto const & bWay = b.m_data.m_ways.back();
if (aWay.m_featureId != bWay.m_featureId)
return aWay.m_featureId < bWay.m_featureId;
if (aWay.m_segmentId != bWay.m_segmentId)
return aWay.m_segmentId < bWay.m_segmentId;
return aWay.m_coef < bWay.m_coef;
});
// Now each camera has only 1 way.
uint32_t prevFeatureId = 0;
for (auto const & camera : flattenedCameras)
camera.Serialize(writer, prevFeatureId);
}
bool CamerasInfoCollector::ParseIntermediateInfo(std::string const & camerasInfoPath,
routing::OsmIdToFeatureIds const & osmIdToFeatureIds)
{
FileReader reader(camerasInfoPath);
ReaderSource<FileReader> src(reader);
uint32_t maxSpeedKmPH = 0;
uint32_t relatedWaysNumber = 0;
uint32_t latInt = 0;
double lat = 0;
uint32_t lonInt = 0;
double lon = 0;
m2::PointD center;
while (src.Size() > 0)
{
/// @todo Take out intermediate camera info serialization code.
/// Should be equal with CameraCollector::CameraInfo::Read.
ReadPrimitiveFromSource(src, latInt);
ReadPrimitiveFromSource(src, lonInt);
lat = Uint32ToDouble(latInt, ms::LatLon::kMinLat, ms::LatLon::kMaxLat, kPointCoordBits);
lon = Uint32ToDouble(lonInt, ms::LatLon::kMinLon, ms::LatLon::kMaxLon, kPointCoordBits);
ReadPrimitiveFromSource(src, maxSpeedKmPH);
ReadPrimitiveFromSource(src, relatedWaysNumber);
center = mercator::FromLatLon(lat, lon);
if (maxSpeedKmPH >= routing::kMaxCameraSpeedKmpH)
{
LOG(LINFO, ("Bad SpeedCamera max speed:", maxSpeedKmPH));
maxSpeedKmPH = 0;
}
bool badCamera = false;
if (relatedWaysNumber > std::numeric_limits<uint8_t>::max())
{
badCamera = true;
LOG(LERROR,
("Number of related to camera ways should be interval from 0 to 255.", "lat(", lat, "), lon(", lon, ")"));
}
std::vector<routing::SpeedCameraMwmPosition> ways;
uint64_t wayOsmId = 0;
for (uint32_t i = 0; i < relatedWaysNumber; ++i)
{
ReadPrimitiveFromSource(src, wayOsmId);
auto const it = osmIdToFeatureIds.find(base::MakeOsmWay(wayOsmId));
if (it != osmIdToFeatureIds.cend())
{
auto const & featureIdsVec = it->second;
// Note. One |wayOsmId| may correspond several feature ids.
for (auto const featureId : featureIdsVec)
ways.emplace_back(featureId, 0 /* segmentId */, 0 /* coef */);
}
}
auto const speed = base::asserted_cast<uint8_t>(maxSpeedKmPH);
if (!badCamera)
m_cameras.emplace_back(center, speed, std::move(ways));
}
return true;
}
void CamerasInfoCollector::Camera::FindClosestSegment(FrozenDataSource const & dataSource, MwmSet::MwmId const & mwmId)
{
if (!m_data.m_ways.empty() && FindClosestSegmentInInnerWays(dataSource, mwmId))
return;
FindClosestSegmentWithGeometryIndex(dataSource);
}
bool CamerasInfoCollector::Camera::FindClosestSegmentInInnerWays(FrozenDataSource const & dataSource,
MwmSet::MwmId const & mwmId)
{
// If m_ways is not empty. It means, that one of point it is our camera.
// So we should find it in feature's points.
for (auto it = m_data.m_ways.begin(); it != m_data.m_ways.end();)
{
if (auto id = FindMyself(it->m_featureId, dataSource, mwmId))
{
it->m_segmentId = (*id).second;
it->m_coef = (*id).first; // Camera starts at the beginning or the end of a segment.
CHECK(it->m_coef == 0.0 || it->m_coef == 1.0, ("Coefficient must be 0.0 or 1.0 here"));
++it;
}
else
{
it = m_data.m_ways.erase(it);
}
}
return !m_data.m_ways.empty();
}
void CamerasInfoCollector::Camera::FindClosestSegmentWithGeometryIndex(FrozenDataSource const & dataSource)
{
uint32_t bestFeatureId = 0;
uint32_t bestSegmentId = 0;
auto bestMinDist = kMaxDistFromCameraToClosestSegmentMeters;
bool found = false;
double bestCoef = 0.0;
// Look at each segment of roads and find the closest.
auto const updateClosestFeatureCallback = [&](FeatureType & ft)
{
if (ft.GetGeomType() != feature::GeomType::Line)
return;
if (!routing::IsCarRoad(feature::TypesHolder(ft)))
return;
auto curMinDist = kMaxDistFromCameraToClosestSegmentMeters;
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
std::vector<m2::PointD> points(ft.GetPointsCount());
for (size_t i = 0; i < points.size(); ++i)
points[i] = ft.GetPoint(i);
routing::FollowedPolyline polyline(points.begin(), points.end());
m2::RectD const rect = mercator::RectByCenterXYAndSizeInMeters(m_data.m_center, kSearchCameraRadiusMeters);
auto const curSegment = polyline.UpdateProjection(rect);
if (!curSegment.IsValid())
return;
CHECK_LESS(curSegment.m_ind + 1, polyline.GetPolyline().GetSize(), ());
static double constexpr kEps = 1e-6;
auto const & p1 = polyline.GetPolyline().GetPoint(curSegment.m_ind);
auto const & p2 = polyline.GetPolyline().GetPoint(curSegment.m_ind + 1);
if (AlmostEqualAbs(p1, p2, kEps))
return;
m2::ParametrizedSegment<m2::PointD> st(p1, p2);
auto const cameraProjOnSegment = st.ClosestPointTo(m_data.m_center);
curMinDist = mercator::DistanceOnEarth(cameraProjOnSegment, m_data.m_center);
if (curMinDist < bestMinDist)
{
bestMinDist = curMinDist;
bestFeatureId = ft.GetID().m_index;
bestSegmentId = static_cast<uint32_t>(curSegment.m_ind);
bestCoef = mercator::DistanceOnEarth(p1, cameraProjOnSegment) / mercator::DistanceOnEarth(p1, p2);
found = true;
}
};
dataSource.ForEachInRect(updateClosestFeatureCallback,
mercator::RectByCenterXYAndSizeInMeters(m_data.m_center, kSearchCameraRadiusMeters),
scales::GetUpperScale());
if (found)
m_data.m_ways.emplace_back(bestFeatureId, bestSegmentId, bestCoef);
}
std::optional<std::pair<double, uint32_t>> CamerasInfoCollector::Camera::FindMyself(uint32_t wayFeatureId,
FrozenDataSource const & dataSource,
MwmSet::MwmId const & mwmId) const
{
double coef = 0.0;
bool isRoad = true;
uint32_t result = 0;
bool cannotFindMyself = false;
auto const readFeature = [&](FeatureType & ft)
{
bool found = false;
isRoad = routing::IsRoad(feature::TypesHolder(ft));
if (!isRoad)
return;
auto const findPoint = [&result, &found, this](m2::PointD const & pt)
{
if (found)
return;
if (AlmostEqualAbs(m_data.m_center, pt, kCoordEqualityEps))
found = true;
else
++result;
};
ft.ForEachPoint(findPoint, scales::GetUpperScale());
if (!found)
{
cannotFindMyself = true;
return;
}
// If point with number - N, is end of feature, we cannot say: segmentId = N,
// because number of segments is N - 1, so we say segmentId = N - 1, and coef = 1
// which means, that camera placed at the end of (N - 1)'th segment.
if (result + 1 == ft.GetPointsCount())
{
CHECK_NOT_EQUAL(result, 0, ("Feature consists of one point!"));
--result;
coef = 1.0;
}
};
FeatureID featureID(mwmId, wayFeatureId);
dataSource.ReadFeature(readFeature, featureID);
if (cannotFindMyself)
{
// Note. Speed camera with coords |m_data.m_center| is not located at any point of |wayFeatureId|.
// It may happens if osm feature corresponds to |wayFeatureId| is split by a mini_roundabout
// or turning_loop. There are two cases:
// * |m_data.m_center| is located at a point of another part of the whole osm feature. In
// that case it will be found on another call of FindMyself() method.
// * |m_data.m_center| coincides with point of mini_roundabout or turning_loop. It means
// there on feature point which coincides with speed camera (|m_data.m_center|).
// These camera (notification) will be lost.
return {};
}
if (isRoad)
return std::optional<std::pair<double, uint32_t>>({coef, result});
return {};
}
void CamerasInfoCollector::Camera::Serialize(FileWriter & writer, uint32_t & prevFeatureId) const
{
routing::SerializeSpeedCamera(writer, m_data, prevFeatureId);
}
void BuildCamerasInfo(std::string const & dataFilePath, std::string const & camerasInfoPath,
std::string const & osmIdsToFeatureIdsPath)
{
LOG(LINFO, ("Generating cameras info for", dataFilePath));
generator::CamerasInfoCollector collector(dataFilePath, camerasInfoPath, osmIdsToFeatureIdsPath);
FilesContainerW cont(dataFilePath, FileWriter::OP_WRITE_EXISTING);
auto writer = cont.GetWriter(CAMERAS_INFO_FILE_TAG);
collector.Serialize(*writer);
}
} // namespace generator

View file

@ -0,0 +1,82 @@
#pragma once
#include "generator/routing_helpers.hpp"
#include "routing/speed_camera_ser_des.hpp"
#include "indexer/data_source.hpp"
#include "coding/file_writer.hpp"
#include "geometry/point2d.hpp"
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace generator
{
class CamerasInfoCollector
{
public:
CamerasInfoCollector(std::string const & dataFilePath, std::string const & camerasInfoPath,
std::string const & osmIdsToFeatureIdsPath);
void Serialize(FileWriter & writer) const;
private:
struct Camera
{
inline static double constexpr kCoordEqualityEps = 1e-5;
Camera(m2::PointD const & center, uint8_t maxSpeed, std::vector<routing::SpeedCameraMwmPosition> && ways)
: m_data(center, maxSpeed, std::move(ways))
{}
void ParseDirection()
{
// After some research we understood, that camera's direction is a random option.
// This fact was checked with randomly chosen cameras and google maps 3d view.
// The real direction and the one indicated in the OSM differed.
// So we left space in mwm for this feature and wait until good data appears in OSM
}
// Modify m_ways vector. Set for each way - id of the closest segment.
void FindClosestSegment(FrozenDataSource const & dataSource, MwmSet::MwmId const & mwmId);
/// \brief Try to FindClosestSegment in |m_ways|. Look to FindMyself() for more details.
/// \return true if we could find closest segment. And false if we should use geometry index.
bool FindClosestSegmentInInnerWays(FrozenDataSource const & dataSource, MwmSet::MwmId const & mwmId);
/// \brief Use when |m_ways| is empty. Try to FindClosestSegment using geometry index.
void FindClosestSegmentWithGeometryIndex(FrozenDataSource const & dataSource);
// Returns empty object, if current feature - |wayId| is not the car road.
// Otherwise returns id of segment from feature with id - |wayId|, which starts (or ends) at camera's
// center and coefficient - where it placed at the segment: 0.0 (or 1.0).
std::optional<std::pair<double, uint32_t>> FindMyself(uint32_t wayFeatureId, FrozenDataSource const & dataSource,
MwmSet::MwmId const & mwmId) const;
void Serialize(FileWriter & writer, uint32_t & prevFeatureId) const;
routing::SpeedCameraMetadata m_data;
};
inline static double constexpr kMaxDistFromCameraToClosestSegmentMeters = 20.0;
inline static double constexpr kSearchCameraRadiusMeters = 10.0;
bool ParseIntermediateInfo(std::string const & camerasInfoPath, routing::OsmIdToFeatureIds const & osmIdToFeatureIds);
std::vector<Camera> m_cameras;
};
// To start building camera info, the following data must be ready:
// 1. GenerateIntermediateData(). Cached data about camera node to ways.
// 2. GenerateFeatures(). Data about cameras from OSM.
// 3. GenerateFinalFeatures(). Calculate some data for features.
// 4. BuildOffsetsTable().
// 5. BuildIndexFromDataFile() - for doing search in rect.
void BuildCamerasInfo(std::string const & dataFilePath, std::string const & camerasInfoPath,
std::string const & osmIdsToFeatureIdsPath);
} // namespace generator

248
generator/cells_merger.cpp Normal file
View file

@ -0,0 +1,248 @@
#include "generator/cells_merger.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <cmath>
namespace
{
double GetMinX(std::vector<m2::RectD> const & cells)
{
CHECK(!cells.empty(), ());
auto const minElementX = std::min_element(std::cbegin(cells), std::cend(cells),
[](auto const & l, auto const & r) { return l.minX() < r.minX(); });
return minElementX->minX();
}
double GetMinY(std::vector<m2::RectD> const & cells)
{
CHECK(!cells.empty(), ());
auto const minElementY = std::min_element(std::cbegin(cells), std::cend(cells),
[](auto const & l, auto const & r) { return l.minY() < r.minY(); });
return minElementY->minY();
}
} // namespace
namespace generator
{
namespace cells_merger
{
// static
CellWrapper const CellWrapper::kEmpty;
CellsMerger::CellsMerger(std::vector<m2::RectD> && cells)
{
if (cells.empty())
return;
auto const minX = GetMinX(cells);
auto const minY = GetMinY(cells);
auto const sideSize = cells.front().SizeX();
for (auto const & cell : cells)
{
int32_t const xIdx = std::lround((cell.minX() - minX) / sideSize);
int32_t const yIdx = std::lround((cell.minY() - minY) / sideSize);
m_matrix.emplace(m2::PointI{xIdx, yIdx}, cell);
m_maxX = std::max(m_maxX, xIdx);
m_maxY = std::max(m_maxY, yIdx);
}
}
std::vector<m2::RectD> CellsMerger::Merge()
{
CalcSum();
std::vector<m2::RectD> cells;
while (auto const max = FindMax())
cells.emplace_back(Union(*max));
return cells;
}
void CellsMerger::CalcSum()
{
// Bottom left
for (int32_t x = 0; x <= m_maxX; ++x)
{
for (int32_t y = 0; y <= m_maxY; ++y)
{
if (!Has(x, y))
continue;
auto & cell = Get(x, y);
cell.SetBottomLeft(std::min({TryGet(x - 1, y).GetBottomLeft(), TryGet(x, y - 1).GetBottomLeft(),
TryGet(x - 1, y - 1).GetBottomLeft()}) +
1);
}
}
// Bottom right
for (int32_t x = m_maxX; x >= 0; --x)
{
for (int32_t y = 0; y <= m_maxY; ++y)
{
if (!Has(x, y))
continue;
auto & cell = Get(x, y);
cell.SetBottomRight(std::min({TryGet(x + 1, y).GetBottomRight(), TryGet(x, y - 1).GetBottomRight(),
TryGet(x + 1, y - 1).GetBottomRight()}) +
1);
}
}
// Top left
for (int32_t x = 0; x <= m_maxX; ++x)
{
for (int32_t y = m_maxY; y >= 0; --y)
{
if (!Has(x, y))
continue;
auto & cell = Get(x, y);
cell.SetTopLeft(
std::min({TryGet(x - 1, y).GetTopLeft(), TryGet(x, y + 1).GetTopLeft(), TryGet(x - 1, y + 1).GetTopLeft()}) +
1);
}
}
// Top right
for (int32_t x = m_maxX; x >= 0; --x)
{
for (int32_t y = m_maxY; y >= 0; --y)
{
if (!Has(x, y))
continue;
auto & cell = Get(x, y);
cell.SetTopRight(std::min({TryGet(x + 1, y).GetTopRight(), TryGet(x, y + 1).GetTopRight(),
TryGet(x + 1, y + 1).GetTopRight()}) +
1);
}
}
for (auto & pair : m_matrix)
pair.second.CalcSum();
}
CellWrapper & CellsMerger::Get(m2::PointI const & xy)
{
auto it = m_matrix.find(xy);
CHECK(it != std::cend(m_matrix), ());
return it->second;
}
CellWrapper & CellsMerger::Get(int32_t x, int32_t y)
{
return Get({x, y});
}
CellWrapper const & CellsMerger::TryGet(int32_t x, int32_t y, CellWrapper const & defaultValue) const
{
auto const it = m_matrix.find({x, y});
return it == std::cend(m_matrix) ? defaultValue : it->second;
}
m2::PointI CellsMerger::FindBigSquare(m2::PointI const & xy, m2::PointI const & direction) const
{
m2::PointI prevXy = xy;
auto currXy = prevXy + direction;
while (true)
{
auto const xMinMax = std::minmax(currXy.x, xy.x);
for (int32_t x = xMinMax.first; x <= xMinMax.second; ++x)
if (!Has({x, currXy.y}))
return prevXy;
auto const yMinMax = std::minmax(currXy.y, xy.y);
for (int32_t y = yMinMax.first; y <= yMinMax.second; ++y)
if (!Has({currXy.x, y}))
return prevXy;
prevXy = currXy;
currXy += direction;
}
}
std::optional<m2::PointI> CellsMerger::FindDirection(m2::PointI const & startXy) const
{
std::array<std::pair<size_t, m2::PointI>, 4> directionsWithWeight;
std::array<m2::PointI, 4> const directions{{{1, 1}, {-1, 1}, {1, -1}, {-1, -1}}};
base::Transform(directions, std::begin(directionsWithWeight), [&](auto const & direction)
{
return std::make_pair(TryGet(startXy.x + direction.x, startXy.y).GetSum() +
TryGet(startXy.x, startXy.y + direction.y).GetSum() +
TryGet(startXy.x + direction.x, startXy.y + direction.y).GetSum(),
direction);
});
auto const direction = std::max_element(std::cbegin(directionsWithWeight), std::cend(directionsWithWeight))->second;
return Has(startXy + direction) ? direction : std::optional<m2::PointI>{};
}
void CellsMerger::Remove(m2::PointI const & minXy, m2::PointI const & maxXy)
{
auto const xMinMax = std::minmax(minXy.x, maxXy.x);
auto const yMinMax = std::minmax(minXy.y, maxXy.y);
for (int32_t x = xMinMax.first; x <= xMinMax.second; ++x)
for (int32_t y = yMinMax.first; y <= yMinMax.second; ++y)
m_matrix.erase({x, y});
}
bool CellsMerger::Has(int32_t x, int32_t y) const
{
return m_matrix.count({x, y}) != 0;
}
bool CellsMerger::Has(m2::PointI const & xy) const
{
return Has(xy.x, xy.y);
}
std::optional<m2::PointI> CellsMerger::FindMax() const
{
m2::PointI max;
size_t sum = 0;
for (auto const & pair : m_matrix)
{
auto const cellSum = pair.second.GetSum();
if (cellSum > sum)
{
sum = cellSum;
max = pair.first;
}
}
return sum != 0 ? max : std::optional<m2::PointI>{};
}
m2::RectD CellsMerger::Union(m2::PointI const & startXy)
{
auto const direction = FindDirection(startXy);
if (!direction)
{
auto const united = Get(startXy).GetCell();
Remove(startXy, startXy);
return united;
}
auto nextXy = FindBigSquare(startXy, *direction);
auto cell1 = Get(startXy).GetCell();
auto const & cell2 = Get(nextXy).GetCell();
cell1.Add(cell2);
Remove(startXy, nextXy);
return cell1;
}
std::vector<m2::RectD> MakeNet(double step, double minX, double minY, double maxX, double maxY)
{
CHECK_LESS(minX, maxX, ());
CHECK_LESS(minY, maxY, ());
std::vector<m2::RectD> net;
auto xmin = minX;
auto ymin = minY;
auto x = xmin;
auto y = ymin;
while (xmin < maxX)
{
x += step;
while (ymin < maxY)
{
y += step;
net.emplace_back(m2::RectD{{xmin, ymin}, {x, y}});
ymin = y;
}
ymin = minY;
y = ymin;
xmin = x;
}
return net;
}
} // namespace cells_merger
} // namespace generator

View file

@ -0,0 +1,77 @@
#pragma once
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include <cstddef>
#include <cstdint>
#include <map>
#include <optional>
#include <utility>
#include <vector>
namespace generator
{
namespace cells_merger
{
struct CellWrapper
{
static CellWrapper const kEmpty;
CellWrapper() = default;
CellWrapper(m2::RectD const & cell) : m_cell(cell) {}
m2::RectD const & GetCell() const { return m_cell; }
void SetTopLeft(size_t val) { m_topLeft = val; }
size_t GetTopLeft() const { return m_topLeft; }
void SetTopRight(size_t val) { m_topRight = val; }
size_t GetTopRight() const { return m_topRight; }
void SetBottomLeft(size_t val) { m_bottomLeft = val; }
size_t GetBottomLeft() const { return m_bottomLeft; }
void SetBottomRight(size_t val) { m_bottomRight = val; }
size_t GetBottomRight() const { return m_bottomRight; }
void CalcSum() { m_sum = m_topLeft + m_topRight + m_bottomLeft + m_bottomRight; }
size_t GetSum() const { return m_sum; }
private:
m2::RectD m_cell;
size_t m_topLeft = 0;
size_t m_topRight = 0;
size_t m_bottomLeft = 0;
size_t m_bottomRight = 0;
size_t m_sum = 0;
};
// CellsMerger combines square cells to large square cells, if possible. This is necessary to reduce
// the number of cells. For example, later they can be used to build the index.
// Important: input cells are disjoint squares that have the same size.
// The algorithm does not guarantee that the optimal solution will be found.
class CellsMerger
{
public:
explicit CellsMerger(std::vector<m2::RectD> && cells);
std::vector<m2::RectD> Merge();
private:
bool Has(int32_t x, int32_t y) const;
bool Has(m2::PointI const & xy) const;
CellWrapper & Get(m2::PointI const & xy);
CellWrapper & Get(int32_t x, int32_t y);
CellWrapper const & TryGet(int32_t x, int32_t y, CellWrapper const & defaultValue = CellWrapper::kEmpty) const;
void CalcSum();
std::optional<m2::PointI> FindMax() const;
std::optional<m2::PointI> FindDirection(m2::PointI const & startXy) const;
m2::PointI FindBigSquare(m2::PointI const & xy, m2::PointI const & direction) const;
m2::RectD Union(m2::PointI const & startXy);
void Remove(m2::PointI const & minXy, m2::PointI const & maxXy);
std::map<m2::PointI, CellWrapper> m_matrix;
int32_t m_maxX = 0;
int32_t m_maxY = 0;
};
std::vector<m2::RectD> MakeNet(double step, double minX, double minY, double maxX, double maxY);
} // namespace cells_merger
} // namespace generator

View file

@ -0,0 +1,55 @@
#include "generator/centers_table_builder.hpp"
#include "indexer/centers_table.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/features_offsets_table.hpp"
#include "indexer/features_vector.hpp"
#include "coding/files_container.hpp"
#include "base/exception.hpp"
#include "defines.hpp"
namespace indexer
{
bool BuildCentersTableFromDataFile(std::string const & filename, bool forceRebuild)
{
try
{
search::CentersTableBuilder builder;
{
FilesContainerR rcont(filename);
if (!forceRebuild && rcont.IsExist(CENTERS_FILE_TAG))
return true;
auto const table = feature::FeaturesOffsetsTable::Load(rcont, FEATURE_OFFSETS_FILE_TAG);
if (!table)
{
LOG(LERROR, ("Can't load offsets table from:", filename));
return false;
}
feature::DataHeader const header(rcont);
FeaturesVector const features(rcont, header, table.get(), nullptr, nullptr);
builder.SetGeometryParams(header.GetBounds());
features.ForEach([&](FeatureType & ft, uint32_t featureId) { builder.Put(featureId, feature::GetCenter(ft)); });
}
{
FilesContainerW writeContainer(filename, FileWriter::OP_WRITE_EXISTING);
auto writer = writeContainer.GetWriter(CENTERS_FILE_TAG);
builder.Freeze(*writer);
}
}
catch (RootException const & e)
{
LOG(LERROR, ("Failed to build centers table:", e.Msg()));
return false;
}
return true;
}
} // namespace indexer

View file

@ -0,0 +1,13 @@
#pragma once
#include <string>
class FilesContainerR;
class Writer;
namespace indexer
{
// Builds the latest version of the centers table section and writes
// it to the mwm file.
bool BuildCentersTableFromDataFile(std::string const & filename, bool forceRebuild = false);
} // namespace indexer

47
generator/check_model.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "generator/check_model.hpp"
#include "defines.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/features_vector.hpp"
#include "base/logging.hpp"
#include <vector>
namespace check_model
{
using namespace feature;
void ReadFeatures(std::string const & fName)
{
Classificator const & c = classif();
FeaturesVectorTest(fName).GetVector().ForEach([&](FeatureType & ft, uint32_t)
{
TypesHolder types(ft);
std::vector<uint32_t> vTypes;
for (uint32_t t : types)
{
CHECK_EQUAL(c.GetTypeForIndex(c.GetIndexForType(t)), t, ());
vTypes.push_back(t);
}
sort(vTypes.begin(), vTypes.end());
CHECK(unique(vTypes.begin(), vTypes.end()) == vTypes.end(), ());
m2::RectD const r = ft.GetLimitRect(FeatureType::BEST_GEOMETRY);
CHECK(r.IsValid(), ());
GeomType const type = ft.GetGeomType();
if (type == GeomType::Line)
CHECK_GREATER(ft.GetPointsCount(), 1, ());
CHECK(CanGenerateLike(vTypes, ft.GetGeomType()), ());
});
LOG(LINFO, ("OK"));
}
} // namespace check_model

View file

@ -0,0 +1,8 @@
#pragma once
#include <string>
namespace check_model
{
void ReadFeatures(std::string const & fName);
} // namespace check_model

View file

@ -0,0 +1,163 @@
#include "generator/cities_boundaries_builder.hpp"
#include "generator/utils.hpp"
#include "search/cbv.hpp"
#include "indexer/cities_boundaries_serdes.hpp"
#include "indexer/city_boundary.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/reader.hpp"
#include "base/assert.hpp"
#include "base/checked_cast.hpp"
#include "base/logging.hpp"
#include <memory>
#include <unordered_map>
#include <vector>
#include "defines.hpp"
namespace generator
{
using namespace indexer;
using namespace search;
using std::string, std::vector;
namespace
{
template <class BoundariesTable, class MappingT>
bool BuildCitiesBoundaries(string const & dataPath, BoundariesTable & table, MappingT const & mapping)
{
auto const localities = GetLocalities(dataPath);
vector<vector<CityBoundary>> all;
localities.ForEach([&](uint64_t fid)
{
vector<CityBoundary> bs;
auto const it = mapping.find(base::asserted_cast<uint32_t>(fid));
if (it != mapping.end())
{
auto const & b = table.Get(it->second);
bs.insert(bs.end(), b.begin(), b.end());
}
all.emplace_back(std::move(bs));
});
FilesContainerW container(dataPath, FileWriter::OP_WRITE_EXISTING);
auto sink = container.GetWriter(CITIES_BOUNDARIES_FILE_TAG);
CitiesBoundariesSerDes::Serialize(*sink, all);
return true;
}
} // namespace
bool BuildCitiesBoundaries(string const & dataPath, OsmIdToBoundariesTable & table)
{
std::unordered_map<uint32_t, base::GeoObjectId> mapping;
if (!ParseFeatureIdToOsmIdMapping(dataPath + OSM2FEATURE_FILE_EXTENSION, mapping))
{
LOG(LERROR, ("Can't parse feature id to osm id mapping."));
return false;
}
return BuildCitiesBoundaries(dataPath, table, mapping);
}
bool BuildCitiesBoundariesForTesting(string const & dataPath, TestIdToBoundariesTable & table)
{
std::unordered_map<uint32_t, uint64_t> mapping;
if (!ParseFeatureIdToTestIdMapping(dataPath, mapping))
{
LOG(LERROR, ("Can't parse feature id to test id mapping."));
return false;
}
return BuildCitiesBoundaries(dataPath, table, mapping);
}
void SerializeBoundariesTable(std::string const & path, OsmIdToBoundariesTable & table)
{
using GeoIDsT = vector<base::GeoObjectId>;
using BoundariesT = vector<CityBoundary>;
vector<GeoIDsT> allIds;
vector<BoundariesT> allBoundaries;
table.ForEachCluster([&](GeoIDsT & ids, BoundariesT const & boundaries)
{
CHECK(!ids.empty(), ());
CHECK(!boundaries.empty(), ());
allIds.push_back(std::move(ids));
allBoundaries.push_back(boundaries);
});
LOG(LINFO, ("Saved boundary clusters count =", allIds.size()));
FileWriter sink(path);
CitiesBoundariesSerDes::Serialize(sink, allBoundaries);
for (auto const & ids : allIds)
{
WriteToSink(sink, static_cast<uint64_t>(ids.size()));
for (auto const & id : ids)
WriteToSink(sink, id.GetEncodedId());
}
}
bool DeserializeBoundariesTable(std::string const & path, OsmIdToBoundariesTable & table)
{
vector<vector<base::GeoObjectId>> allIds;
vector<vector<CityBoundary>> allBoundaries;
size_t count = 0;
try
{
FileReader reader(path);
NonOwningReaderSource source(reader);
double precision;
CitiesBoundariesSerDes::Deserialize(source, allBoundaries, precision);
count = allBoundaries.size();
allIds.resize(count);
for (auto & ids : allIds)
{
auto const m = ReadPrimitiveFromSource<uint64_t>(source);
ids.resize(m);
CHECK(m != 0, ());
for (auto & id : ids)
{
auto const encodedId = ReadPrimitiveFromSource<uint64_t>(source);
id = base::GeoObjectId(encodedId);
}
}
}
catch (Reader::Exception const & e)
{
LOG(LERROR, ("Can't deserialize boundaries table:", e.what()));
return false;
}
table.Clear();
for (size_t i = 0; i < count; ++i)
{
auto const & ids = allIds[i];
CHECK(!ids.empty(), ());
auto const & id = ids.front();
for (auto & b : allBoundaries[i])
table.Append(id, std::move(b));
for (size_t j = 1; j < ids.size(); ++j)
table.Union(id, ids[j]);
}
return true;
}
} // namespace generator

View file

@ -0,0 +1,21 @@
#pragma once
#include "indexer/city_boundary.hpp"
#include "base/clustering_map.hpp"
#include "base/geo_object_id.hpp"
#include <string>
namespace generator
{
// todo(@m) Make test ids a new source in base::GeoObjectId?
using OsmIdToBoundariesTable = base::ClusteringMap<base::GeoObjectId, indexer::CityBoundary>;
using TestIdToBoundariesTable = base::ClusteringMap<uint64_t, indexer::CityBoundary>;
bool BuildCitiesBoundaries(std::string const & dataPath, OsmIdToBoundariesTable & table);
bool BuildCitiesBoundariesForTesting(std::string const & dataPath, TestIdToBoundariesTable & table);
void SerializeBoundariesTable(std::string const & path, OsmIdToBoundariesTable & table);
bool DeserializeBoundariesTable(std::string const & path, OsmIdToBoundariesTable & table);
} // namespace generator

View file

@ -0,0 +1,26 @@
#include "generator/cities_boundaries_checker.hpp"
#include "geometry/rect2d.hpp"
namespace generator
{
CitiesBoundariesChecker::CitiesBoundariesChecker(CitiesBoundaries const & citiesBoundaries)
{
for (auto const & cb : citiesBoundaries)
m_tree.Add(cb, cb.m_bbox.ToRect());
}
bool CitiesBoundariesChecker::InCity(m2::PointD const & point) const
{
bool result = false;
m_tree.ForEachInRect(m2::RectD(point, point), [&](indexer::CityBoundary const & cityBoundary)
{
if (result)
return;
result = cityBoundary.HasPoint(point);
});
return result;
}
} // namespace generator

View file

@ -0,0 +1,26 @@
#pragma once
#include "indexer/city_boundary.hpp"
#include "geometry/point2d.hpp"
#include "geometry/tree4d.hpp"
#include <vector>
namespace generator
{
/// \brief Checks whether a given point belongs to a city or a town.
class CitiesBoundariesChecker
{
public:
using CitiesBoundaries = std::vector<indexer::CityBoundary>;
explicit CitiesBoundariesChecker(CitiesBoundaries const & citiesBoundaries);
/// \returns true if |point| is inside a city or a town and false otherwise.
bool InCity(m2::PointD const & point) const;
private:
m4::Tree<indexer::CityBoundary> m_tree;
};
} // namespace generator

View file

@ -0,0 +1,120 @@
#include "generator/cities_ids_builder.hpp"
#include "generator/utils.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/data_header.hpp"
#include "indexer/feature_to_osm.hpp"
#include "search/categories_cache.hpp"
#include "search/cbv.hpp"
#include "search/localities_source.hpp"
#include "search/mwm_context.hpp"
#include "coding/file_writer.hpp"
#include "coding/files_container.hpp"
#include "base/cancellable.hpp"
#include "base/checked_cast.hpp"
#include "base/geo_object_id.hpp"
#include "base/logging.hpp"
#include <cstdint>
#include <unordered_map>
#include "defines.hpp"
namespace
{
bool IsWorldMwm(std::string const & path)
{
try
{
feature::DataHeader const header(path);
return header.GetType() == feature::DataHeader::MapType::World;
}
catch (Reader::OpenException const & e)
{
return false;
}
}
void WriteCitiesIdsSectionToFile(std::string const & dataPath,
std::unordered_map<uint32_t, base::GeoObjectId> const & mapping)
{
indexer::FeatureIdToGeoObjectIdBimapMem map;
auto const localities = generator::GetLocalities(dataPath);
localities.ForEach([&](uint64_t fid64)
{
auto const fid = base::checked_cast<uint32_t>(fid64);
auto const it = mapping.find(fid);
if (it == mapping.end())
return;
auto const osmId = it->second;
if (!map.Add(fid, osmId))
{
uint32_t oldFid;
base::GeoObjectId oldOsmId;
auto const hasOldOsmId = map.GetValue(fid, oldOsmId);
auto const hasOldFid = map.GetKey(osmId, oldFid);
LOG(LWARNING, ("Could not add the pair (", fid, ",", osmId,
") to the cities ids section; old fid:", (hasOldFid ? DebugPrint(oldFid) : "none"),
"old osmId:", (hasOldOsmId ? DebugPrint(oldOsmId) : "none")));
}
});
FilesContainerW container(dataPath, FileWriter::OP_WRITE_EXISTING);
// Note that we only store cities ids but nothing stops us from
// generalizing the section if we need, so a more generic tag is used.
auto sink = container.GetWriter(FEATURE_TO_OSM_FILE_TAG);
auto const pos0 = sink->Pos();
indexer::FeatureIdToGeoObjectIdSerDes::Serialize(*sink, map);
auto const pos1 = sink->Pos();
LOG(LINFO, ("Serialized cities ids. Number of entries:", map.Size(), "Size in bytes:", pos1 - pos0));
}
} // namespace
namespace generator
{
bool BuildCitiesIds(std::string const & dataPath, std::string const & osmToFeaturePath)
{
if (!IsWorldMwm(dataPath))
{
LOG(LINFO, ("Skipping generation of cities ids for the non-world mwm file at", dataPath));
return false;
}
classificator::Load();
std::unordered_map<uint32_t, base::GeoObjectId> mapping;
if (!ParseFeatureIdToOsmIdMapping(osmToFeaturePath, mapping))
{
LOG(LERROR, ("Can't parse feature id to osm id mapping."));
return false;
}
WriteCitiesIdsSectionToFile(dataPath, mapping);
return true;
}
bool BuildCitiesIdsForTesting(std::string const & dataPath)
{
CHECK(IsWorldMwm(dataPath), ());
std::unordered_map<uint32_t, uint64_t> mapping;
if (!ParseFeatureIdToTestIdMapping(dataPath, mapping))
return false;
std::unordered_map<uint32_t, base::GeoObjectId> mappingToGeoObjects;
for (auto const & entry : mapping)
{
// todo(@m) Make test ids a new source in base::GeoObjectId?
mappingToGeoObjects.emplace(entry.first, base::MakeOsmNode(entry.second));
}
WriteCitiesIdsSectionToFile(dataPath, mappingToGeoObjects);
return true;
}
} // namespace generator

View file

@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace generator
{
// Takes the FeatureID <-> base::GeoObjectId bidirectional map for features
// corresponding to cities from |osmToFeaturePath| and serializes it
// as a section in the mwm file at |dataPath|.
// For World.mwm returns true iff the section was successfully built.
// For non-world mwms returns false.
bool BuildCitiesIds(std::string const & dataPath, std::string const & osmToFeaturePath);
// Takes the FeatureID <-> base::GeoObjectId bidirectional map for features
// corresponding to cities from the (test) features metadata and serializes the map
// as a section in the mwm file at |dataPath|.
// For World.mwm returns true iff the section was successfully built.
// For non-world mwms returns false.
bool BuildCitiesIdsForTesting(std::string const & dataPath);
} // namespace generator

View file

@ -0,0 +1,125 @@
#include "generator/city_roads_generator.hpp"
#include "generator/cities_boundaries_checker.hpp"
#include "generator/collector_routing_city_boundaries.hpp"
#include "routing/city_roads_serialization.hpp"
#include "routing/routing_helpers.hpp"
#include "platform/platform.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/feature_processor.hpp"
#include "geometry/circle_on_earth.hpp"
#include "base/logging.hpp"
#include "defines.hpp"
namespace routing_builder
{
using generator::CitiesBoundariesChecker;
using std::string, std::vector;
void LoadCitiesBoundariesGeometry(string const & boundariesPath, CitiesBoundariesChecker::CitiesBoundaries & result)
{
if (!Platform::IsFileExistsByFullPath(boundariesPath))
{
LOG(LWARNING, ("No city boundaries file:", boundariesPath));
return;
}
generator::PlaceBoundariesHolder holder;
holder.Deserialize(boundariesPath);
size_t points = 0, areas = 0;
holder.ForEachLocality([&](generator::PlaceBoundariesHolder::Locality & loc)
{
CHECK(loc.TestValid(), ());
if (loc.IsPoint() || !loc.IsHonestCity())
{
++points;
double const radiusM = ftypes::GetRadiusByPopulationForRouting(loc.GetPopulation(), loc.GetPlace());
result.emplace_back(
ms::CreateCircleGeometryOnEarth(mercator::ToLatLon(loc.m_center), radiusM, 30.0 /* angleStepDegree */));
}
else
{
++areas;
for (auto & poly : loc.m_boundary)
result.emplace_back(std::move(poly));
}
});
LOG(LINFO, ("Read", points, "point places and", areas, "area places from:", boundariesPath));
}
/// \brief Fills |cityRoadFeatureIds| with road feature ids if more then
/// |kInCityPointsRatio| * <feature point number> points of the feature belongs to a city or a town
/// according to |table|.
vector<uint32_t> CalcRoadFeatureIds(string const & dataPath, string const & boundariesPath)
{
CitiesBoundariesChecker::CitiesBoundaries citiesBoundaries;
LoadCitiesBoundariesGeometry(boundariesPath, citiesBoundaries);
CitiesBoundariesChecker const checker(citiesBoundaries);
vector<uint32_t> cityRoadFeatureIds;
feature::ForEachFeature(dataPath, [&cityRoadFeatureIds, &checker](FeatureType & ft, uint32_t)
{
feature::TypesHolder types(ft);
if (!routing::IsCarRoad(types) && !routing::IsBicycleRoad(types))
return;
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
size_t inCityPointsCounter = 0;
size_t const count = ft.GetPointsCount();
for (size_t i = 0; i < count; ++i)
if (checker.InCity(ft.GetPoint(i)))
++inCityPointsCounter;
// Our approximation of boundary overestimates it, because of different
// bounding boxes (in order to increase performance). So we don't want
// match some long roads as city roads, because they pass near city, but
// not though it.
double constexpr kPointsRatioInCity = 0.8;
if (inCityPointsCounter > kPointsRatioInCity * count)
cityRoadFeatureIds.push_back(ft.GetID().m_index);
});
return cityRoadFeatureIds;
}
void SerializeCityRoads(string const & dataPath, vector<uint32_t> && cityRoadFeatureIds)
{
if (cityRoadFeatureIds.empty())
return;
FilesContainerW cont(dataPath, FileWriter::OP_WRITE_EXISTING);
auto w = cont.GetWriter(CITY_ROADS_FILE_TAG);
routing::CityRoadsSerializer::Serialize(*w, std::move(cityRoadFeatureIds));
}
bool BuildCityRoads(string const & mwmPath, string const & boundariesPath)
{
try
{
// The generation city roads section process is based on two stages now:
// * dumping cities boundaries on feature generation step
// * calculating feature ids and building section when feature ids are available
// As a result of dumping cities boundaries instances of indexer::CityBoundary objects
// are generated and dumped. These objects are used for generating city roads section.
SerializeCityRoads(mwmPath, CalcRoadFeatureIds(mwmPath, boundariesPath));
}
catch (Reader::Exception const & e)
{
LOG(LERROR, ("Error while building section city_roads in", mwmPath, ". Message:", e.Msg()));
return false;
}
return true;
}
} // namespace routing_builder

View file

@ -0,0 +1,18 @@
#pragma once
#include "generator/cities_boundaries_builder.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace routing_builder
{
/// \brief Write |cityRoadFeatureIds| to city_roads section to mwm with |dataPath|.
/// \param cityRoadFeatureIds a vector of road feature ids in cities.
void SerializeCityRoads(std::string const & mwmPath, std::vector<uint32_t> && cityRoadFeatureIds);
/// \brief Marks road-features as city roads if they covered some boundary from data dumped in
/// |boundariesPath|. Then serialize ids of such features to "city_roads" section.
bool BuildCityRoads(std::string const & mwmPath, std::string const & boundariesPath);
} // namespace routing_builder

View file

@ -0,0 +1,101 @@
#pragma once
#include "geometry/mercator.hpp"
#include "geometry/rect2d.hpp"
#include "geometry/tree4d.hpp"
#include <functional>
#include <queue>
#include <set>
#include <utility>
#include <vector>
namespace generator
{
// The class ClustersFinder finds clusters of objects for which IsSameFunc returns true.
// RadiusFunc should return the same radius for all objects in one cluster.
template <class T>
class ClustersFinder
{
public:
using PtrT = T const *;
using RadiusFunc = std::function<double(T const &)>;
using IsSameFunc = std::function<bool(T const &, T const &)>;
ClustersFinder(std::vector<T> const & container, RadiusFunc radiusFunc, IsSameFunc isSameFunc)
: m_container(container)
, m_radiusFunc(std::move(radiusFunc))
, m_isSameFunc(std::move(isSameFunc))
{
for (auto const & e : m_container)
m_tree.Add(&e);
}
std::vector<std::vector<PtrT>> Find() const
{
std::vector<std::vector<PtrT>> clusters;
std::set<PtrT> unviewed;
for (auto const & e : m_container)
unviewed.insert(&e);
while (!unviewed.empty())
{
auto const it = *std::cbegin(unviewed);
clusters.emplace_back(FindOneCluster(it, unviewed));
}
return clusters;
}
private:
struct TraitsDef
{
m2::RectD const LimitRect(PtrT p) const { return GetLimitRect(*p); }
};
std::vector<PtrT> FindOneCluster(PtrT p, std::set<PtrT> & unviewed) const
{
std::vector<PtrT> cluster{p};
std::queue<PtrT> queue;
queue.emplace(p);
unviewed.erase(p);
while (!queue.empty())
{
auto const current = queue.front();
queue.pop();
auto const queryBbox = GetBboxFor(current);
m_tree.ForEachInRect(queryBbox, [&](PtrT candidate)
{
if (unviewed.count(candidate) == 0 || !m_isSameFunc(*p, *candidate))
return;
unviewed.erase(candidate);
queue.emplace(candidate);
cluster.emplace_back(candidate);
});
}
return cluster;
}
m2::RectD GetBboxFor(PtrT p) const
{
m2::RectD bbox;
auto const dist = m_radiusFunc(*p);
GetLimitRect(*p).ForEachCorner([&](auto const & p) { bbox.Add(mercator::RectByCenterXYAndSizeInMeters(p, dist)); });
return bbox;
}
std::vector<T> const & m_container;
RadiusFunc m_radiusFunc;
IsSameFunc m_isSameFunc;
m4::Tree<PtrT, TraitsDef> m_tree;
};
/// @return Vector of equal place clusters, like pointers from input \a container.
template <class T, class RadiusFnT, class IsSameFnT>
std::vector<std::vector<T const *>> GetClusters(std::vector<T> const & container, RadiusFnT && radiusFunc,
IsSameFnT && isSameFunc)
{
return ClustersFinder<T>(container, std::forward<RadiusFnT>(radiusFunc), std::forward<IsSameFnT>(isSameFunc)).Find();
}
} // namespace generator

View file

@ -0,0 +1,309 @@
#include "generator/coastlines_generator.hpp"
#include "generator/feature_builder.hpp"
#include "indexer/cell_id.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "coding/point_coding.hpp"
#include "geometry/region2d/binary_operators.hpp"
#include "base/logging.hpp"
#include <condition_variable>
#include <functional>
#include <thread>
namespace coastlines_generator
{
using RegionT = m2::RegionI;
using PointT = m2::PointI;
using RectT = m2::RectI;
m2::RectD GetLimitRect(RegionT const & rgn)
{
RectT const r = rgn.GetRect();
return m2::RectD(r.minX(), r.minY(), r.maxX(), r.maxY());
}
PointT D2I(m2::PointD const & p)
{
m2::PointU const pu = PointDToPointU(p, kPointCoordBits);
return PointT(static_cast<int32_t>(pu.x), static_cast<int32_t>(pu.y));
}
m2::RegionI CreateRegionI(std::vector<m2::PointD> const & poly)
{
CHECK_GREATER(poly.size(), 2, ());
CHECK(poly.front().EqualDxDy(poly.back(), 1.0E-12), (poly.front(), poly.back()));
m2::RegionI rgn;
for (auto it = poly.begin() + 1; it != poly.end(); ++it)
rgn.AddPoint(D2I(*it));
return rgn;
}
class DoAddToTree : public FeatureEmitterIFace
{
CoastlineFeaturesGenerator & m_rMain;
size_t m_notMergedCoastsCount;
size_t m_totalNotMergedCoastsPoints;
public:
explicit DoAddToTree(CoastlineFeaturesGenerator & rMain)
: m_rMain(rMain)
, m_notMergedCoastsCount(0)
, m_totalNotMergedCoastsPoints(0)
{}
virtual void operator()(feature::FeatureBuilder const & fb)
{
if (fb.IsGeometryClosed())
m_rMain.AddRegionToTree(fb);
else
{
base::GeoObjectId const firstWay = fb.GetFirstOsmId();
base::GeoObjectId const lastWay = fb.GetLastOsmId();
if (firstWay == lastWay)
LOG(LINFO, ("Not merged coastline, way", firstWay.GetSerialId(), "(", fb.GetPointsCount(), "points)"));
else
LOG(LINFO, ("Not merged coastline, ways", firstWay.GetSerialId(), "to", lastWay.GetSerialId(), "(",
fb.GetPointsCount(), "points)"));
++m_notMergedCoastsCount;
m_totalNotMergedCoastsPoints += fb.GetPointsCount();
}
}
bool HasNotMergedCoasts() const { return m_notMergedCoastsCount != 0; }
size_t GetNotMergedCoastsCount() const { return m_notMergedCoastsCount; }
size_t GetNotMergedCoastsPoints() const { return m_totalNotMergedCoastsPoints; }
};
class DoDifference
{
RectT m_src;
std::vector<RegionT> m_res;
public:
explicit DoDifference(RegionT const & rgn)
{
m_res.push_back(rgn);
m_src = rgn.GetRect();
}
void operator()(RegionT const & r)
{
// if r is fully inside source rect region,
// put it to the result vector without any intersection
if (m_src.IsRectInside(r.GetRect()))
m_res.push_back(r);
else
m2::IntersectRegions(m_res.front(), r, m_res);
}
size_t GetPointsCount() const
{
size_t count = 0;
for (size_t i = 0; i < m_res.size(); ++i)
count += m_res[i].GetPointsCount();
return count;
}
void AssignGeometry(feature::FeatureBuilder & fb)
{
for (size_t i = 0; i < m_res.size(); ++i)
{
std::vector<m2::PointD> points;
points.reserve(m_res[i].Size() + 1);
m_res[i].ForEachPoint([&points](PointT const & p)
{
points.push_back(
PointUToPointD(m2::PointU(static_cast<uint32_t>(p.x), static_cast<uint32_t>(p.y)), kPointCoordBits));
});
fb.AddPolygon(std::move(points));
}
}
};
} // namespace coastlines_generator
CoastlineFeaturesGenerator::CoastlineFeaturesGenerator() : m_merger(kPointCoordBits) {}
void CoastlineFeaturesGenerator::AddRegionToTree(feature::FeatureBuilder const & fb)
{
ASSERT(fb.IsGeometryClosed(), ());
fb.ForEachPolygon([&](auto const & polygon)
{
if (polygon.empty())
return;
using namespace coastlines_generator;
RegionT rgn = CreateRegionI(polygon);
auto const limitRect = GetLimitRect(rgn);
m_tree.Add(std::move(rgn), limitRect);
});
}
void CoastlineFeaturesGenerator::Process(feature::FeatureBuilder const & fb)
{
if (fb.IsGeometryClosed())
AddRegionToTree(fb);
else
m_merger(fb);
}
bool CoastlineFeaturesGenerator::Finish()
{
coastlines_generator::DoAddToTree doAdd(*this);
m_merger.DoMerge(doAdd);
if (doAdd.HasNotMergedCoasts())
{
LOG(LINFO, ("Total not merged coasts:", doAdd.GetNotMergedCoastsCount()));
LOG(LINFO, ("Total points in not merged coasts:", doAdd.GetNotMergedCoastsPoints()));
return false;
}
return true;
}
class RegionInCellSplitter final
{
public:
using TCell = RectId;
using TIndex = m4::Tree<m2::RegionI>;
using TProcessResultFunc = std::function<void(TCell const &, coastlines_generator::DoDifference &)>;
static int constexpr kStartLevel = 4;
static int constexpr kHighLevel = 10;
static int constexpr kMaxPoints = 20000;
protected:
struct Context
{
std::mutex mutexTasks;
std::list<TCell> listTasks;
std::condition_variable listCondVar;
size_t inWork = 0;
TProcessResultFunc processResultFunc;
};
Context & m_ctx;
TIndex const & m_index;
RegionInCellSplitter(Context & ctx, TIndex const & index) : m_ctx(ctx), m_index(index) {}
public:
static bool Process(size_t numThreads, size_t baseScale, TIndex const & index, TProcessResultFunc funcResult)
{
/// @todo Replace with base::ComputationalThreadPool
Context ctx;
for (size_t i = 0; i < TCell::TotalCellsOnLevel(baseScale); ++i)
ctx.listTasks.push_back(TCell::FromBitsAndLevel(i, static_cast<int>(baseScale)));
ctx.processResultFunc = funcResult;
std::vector<RegionInCellSplitter> instances;
std::vector<std::thread> threads;
for (size_t i = 0; i < numThreads; ++i)
{
instances.emplace_back(RegionInCellSplitter(ctx, index));
threads.emplace_back(instances.back());
}
for (auto & thread : threads)
thread.join();
// return true if listTask has no error cells
return ctx.listTasks.empty();
}
bool ProcessCell(TCell const & cell)
{
// get rect cell
double minX, minY, maxX, maxY;
CellIdConverter<mercator::Bounds, TCell>::GetCellBounds(cell, minX, minY, maxX, maxY);
using namespace coastlines_generator;
// create rect region
PointT arr[] = {D2I(m2::PointD(minX, minY)), D2I(m2::PointD(minX, maxY)), D2I(m2::PointD(maxX, maxY)),
D2I(m2::PointD(maxX, minY))};
RegionT rectR(arr, arr + ARRAY_SIZE(arr));
// Do 'and' with all regions and accumulate the result, including bound region.
// In 'odd' parts we will have an ocean.
DoDifference doDiff(rectR);
m_index.ForEachInRect(GetLimitRect(rectR), std::bind<void>(std::ref(doDiff), std::placeholders::_1));
// Check if too many points for feature.
if (cell.Level() < kHighLevel && doDiff.GetPointsCount() >= kMaxPoints)
return false;
m_ctx.processResultFunc(cell, doDiff);
return true;
}
void operator()()
{
// thread main loop
while (true)
{
std::unique_lock lock(m_ctx.mutexTasks);
m_ctx.listCondVar.wait(lock, [&] { return (!m_ctx.listTasks.empty() || m_ctx.inWork == 0); });
if (m_ctx.listTasks.empty())
break;
TCell currentCell = m_ctx.listTasks.front();
m_ctx.listTasks.pop_front();
++m_ctx.inWork;
lock.unlock();
bool const done = ProcessCell(currentCell);
lock.lock();
// return to queue not ready cells
if (!done)
for (int8_t i = 0; i < TCell::MAX_CHILDREN; ++i)
m_ctx.listTasks.push_back(currentCell.Child(i));
--m_ctx.inWork;
m_ctx.listCondVar.notify_all();
}
}
};
std::vector<feature::FeatureBuilder> CoastlineFeaturesGenerator::GetFeatures(size_t maxThreads)
{
uint32_t const coastType = ftypes::IsCoastlineChecker::Instance().GetCoastlineType();
std::vector<feature::FeatureBuilder> features;
std::mutex featuresMutex;
RegionInCellSplitter::Process(
maxThreads, RegionInCellSplitter::kStartLevel, m_tree,
[&](RegionInCellSplitter::TCell const & cell, coastlines_generator::DoDifference & cellData)
{
feature::FeatureBuilder fb;
fb.SetCoastCell(cell.ToInt64(RegionInCellSplitter::kHighLevel + 1));
cellData.AssignGeometry(fb);
fb.SetArea();
fb.AddType(coastType);
// Should represent non-empty geometry
CHECK_GREATER(fb.GetPolygonsCount(), 0, ());
CHECK_GREATER_OR_EQUAL(fb.GetPointsCount(), 3, ());
std::lock_guard lock(featuresMutex);
features.emplace_back(std::move(fb));
});
return features;
}

View file

@ -0,0 +1,38 @@
#pragma once
#include "generator/feature_merger.hpp"
#include "geometry/region2d.hpp"
#include "geometry/tree4d.hpp"
#include <vector>
namespace feature
{
class FeatureBuilder;
} // namespace feature
class CoastlineFeaturesGenerator
{
FeatureMergeProcessor m_merger;
using TTree = m4::Tree<m2::RegionI>;
TTree m_tree;
public:
CoastlineFeaturesGenerator();
void AddRegionToTree(feature::FeatureBuilder const & fb);
void Process(feature::FeatureBuilder const & fb);
/// @return false if coasts are not merged and FLAG_fail_on_coasts is set
bool Finish();
std::vector<feature::FeatureBuilder> GetFeatures(size_t maxThreads);
};
namespace coastlines_generator
{
/// @param[in] poly Closed polygon where poly.frotn() == poly.back() like in FeatureBuilder.
m2::RegionI CreateRegionI(std::vector<m2::PointD> const & poly);
} // namespace coastlines_generator

View file

@ -0,0 +1,25 @@
#pragma once
#include <algorithm>
#include <iterator>
#include <vector>
// Implementing this base class allows an object to be collection of objects.
template <typename T>
class CollectionBase
{
public:
void Append(T const & collector) { m_collection.push_back(collector); }
void AddCollection(CollectionBase<T> const & collection)
{
std::copy(std::begin(collection.m_collection), std::end(collection.m_collection), std::back_inserter(m_collection));
}
std::vector<T> const & GetCollection() const { return m_collection; }
bool Empty() const { return m_collection.empty(); }
protected:
std::vector<T> m_collection;
};

View file

@ -0,0 +1,72 @@
#include "generator/collector_boundary_postcode.hpp"
#include "generator/feature_builder.hpp"
#include "generator/intermediate_data.hpp"
#include "coding/read_write_utils.hpp"
#include "coding/string_utf8_multilang.hpp"
#include <algorithm>
namespace generator
{
BoundaryPostcodeCollector::BoundaryPostcodeCollector(std::string const & filename, IDRInterfacePtr const & cache)
: CollectorInterface(filename)
, m_cache(cache)
, m_featureMakerSimple(cache)
{}
std::shared_ptr<CollectorInterface> BoundaryPostcodeCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<BoundaryPostcodeCollector>(GetFilename(), cache ? cache : m_cache);
}
void BoundaryPostcodeCollector::Collect(OsmElement const & el)
{
/// @todo Add postal_code for highways processing (along a street).
// https://wiki.openstreetmap.org/wiki/Key:postal_code
if (el.m_type != OsmElement::EntityType::Relation || el.GetTag("type") != "boundary")
return;
auto postcode = el.GetTag("postal_code");
if (postcode.empty())
postcode = el.GetTag("addr:postcode");
// Filter dummy tags like here: https://www.openstreetmap.org/relation/7444
if (postcode.empty() || postcode.find_first_of(";,") != std::string::npos)
return;
/// @todo We don't override CollectFeature instead, because of FeatureMakerSimple?
auto osmElementCopy = el;
feature::FeatureBuilder feature;
m_featureMakerSimple.Add(osmElementCopy);
while (m_featureMakerSimple.GetNextFeature(feature))
{
/// @todo Make move geometry?
if (feature.IsGeometryClosed())
m_data.emplace_back(postcode, feature.GetOuterGeometry());
}
}
void BoundaryPostcodeCollector::Save()
{
LOG(LINFO, ("Saving postcode boundaries to", GetFilename()));
std::sort(m_data.begin(), m_data.end());
FileWriter writer(GetFilename());
for (auto const & p : m_data)
{
rw::WriteNonEmpty(writer, p.first);
rw::WriteVectorOfPOD(writer, p.second);
}
LOG(LINFO, ("Finished saving postcode boundaries"));
}
void BoundaryPostcodeCollector::MergeInto(BoundaryPostcodeCollector & collector) const
{
collector.m_data.insert(collector.m_data.end(), m_data.begin(), m_data.end());
}
} // namespace generator

View file

@ -0,0 +1,36 @@
#pragma once
#include "generator/collector_interface.hpp"
#include "generator/feature_maker.hpp"
#include "generator/osm_element.hpp"
#include <memory>
#include <string>
namespace generator
{
// Saves pairs (postal code, outer geometry) to file for OsmElements that
// have all of the following: relation=boundary, boundary=postal_code, postal_code=*.
class BoundaryPostcodeCollector : public CollectorInterface
{
public:
BoundaryPostcodeCollector(std::string const & filename, IDRInterfacePtr const & cache);
// CollectorInterface overrides:
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & = {}) const override;
void Collect(OsmElement const & el) override;
IMPLEMENT_COLLECTOR_IFACE(BoundaryPostcodeCollector);
void MergeInto(BoundaryPostcodeCollector & collector) const;
protected:
void Save() override;
private:
std::vector<std::pair<std::string, feature::FeatureBuilder::PointSeq>> m_data;
std::shared_ptr<cache::IntermediateDataReaderInterface> m_cache;
FeatureMakerSimple m_featureMakerSimple;
};
} // namespace generator

View file

@ -0,0 +1,219 @@
#include "generator/collector_building_parts.hpp"
#include "generator/feature_builder.hpp"
#include "generator/gen_mwm_info.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/intermediate_elements.hpp"
#include "generator/osm_element.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "coding/file_reader.hpp"
#include "coding/internal/file_data.hpp"
#include "coding/reader.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "base/checked_cast.hpp"
#include "base/control_flow.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <unordered_map>
#include <utility>
namespace
{
using IdRelationVec = std::vector<std::pair<uint64_t, RelationElement>>;
class RelationFetcher
{
public:
RelationFetcher(IdRelationVec & elements) : m_elements(elements) {}
void operator()(uint64_t id, generator::cache::OSMElementCacheReaderInterface & reader)
{
RelationElement element;
if (reader.Read(id, element))
m_elements.emplace_back(id, std::move(element));
else
LOG(LWARNING, ("Cannot read relation with id", id));
}
private:
IdRelationVec & m_elements;
};
} // namespace
namespace generator
{
// static
void BuildingPartsCollector::BuildingParts::Write(FileWriter & writer, BuildingParts const & pb)
{
WriteToSink(writer, pb.m_id.m_mainId.GetEncodedId());
WriteToSink(writer, pb.m_id.m_additionalId.GetEncodedId());
auto const contSize = base::checked_cast<uint32_t>(pb.m_buildingParts.size());
WriteVarUint(writer, contSize);
for (auto const & id : pb.m_buildingParts)
WriteToSink(writer, id.GetEncodedId());
}
// static
BuildingPartsCollector::BuildingParts BuildingPartsCollector::BuildingParts::Read(ReaderSource<FileReader> & src)
{
BuildingParts bp;
auto const first = base::GeoObjectId(ReadPrimitiveFromSource<uint64_t>(src));
auto const second = base::GeoObjectId(ReadPrimitiveFromSource<uint64_t>(src));
bp.m_id = CompositeId(first, second);
auto contSize = ReadVarUint<uint32_t>(src);
bp.m_buildingParts.reserve(contSize);
while (contSize--)
bp.m_buildingParts.emplace_back(base::GeoObjectId(ReadPrimitiveFromSource<uint64_t>(src)));
return bp;
}
BuildingPartsCollector::BuildingPartsCollector(std::string const & filename, IDRInterfacePtr const & cache)
: CollectorInterface(filename)
, m_cache(cache)
, m_writer(std::make_unique<FileWriter>(GetTmpFilename()))
{}
std::shared_ptr<CollectorInterface> BuildingPartsCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<BuildingPartsCollector>(GetFilename(), cache ? cache : m_cache);
}
void BuildingPartsCollector::CollectFeature(feature::FeatureBuilder const & fb, OsmElement const &)
{
static auto const & buildingChecker = ftypes::IsBuildingChecker::Instance();
if (!fb.IsArea() || !buildingChecker(fb.GetTypes()))
return;
auto const topId = FindTopRelation(fb.GetMostGenericOsmId());
if (topId == base::GeoObjectId())
return;
auto parts = FindAllBuildingParts(topId);
if (!parts.empty())
{
BuildingParts bp;
bp.m_id = MakeCompositeId(fb);
bp.m_buildingParts = std::move(parts);
BuildingParts::Write(*m_writer, bp);
}
}
std::vector<base::GeoObjectId> BuildingPartsCollector::FindAllBuildingParts(base::GeoObjectId const & id)
{
std::vector<base::GeoObjectId> buildingParts;
RelationElement relation;
if (!m_cache->GetRelation(id.GetSerialId(), relation))
{
LOG(LWARNING, ("Cannot read relation with id", id));
return buildingParts;
}
for (auto const & v : relation.m_ways)
if (v.second == "part")
buildingParts.emplace_back(base::MakeOsmWay(v.first));
for (auto const & v : relation.m_relations)
if (v.second == "part")
buildingParts.emplace_back(base::MakeOsmRelation(v.first));
return buildingParts;
}
base::GeoObjectId BuildingPartsCollector::FindTopRelation(base::GeoObjectId elId)
{
IdRelationVec elements;
RelationFetcher fetcher(elements);
cache::IntermediateDataReaderInterface::ForEachRelationFn wrapper =
base::ControlFlowWrapper<RelationFetcher>(fetcher);
IdRelationVec::const_iterator it;
auto const serialId = elId.GetSerialId();
if (elId.GetType() == base::GeoObjectId::Type::ObsoleteOsmWay)
{
m_cache->ForEachRelationByWayCached(serialId, wrapper);
it = base::FindIf(elements,
[&](auto const & idRelation) { return idRelation.second.GetWayRole(serialId) == "outline"; });
}
else if (elId.GetType() == base::GeoObjectId::Type::ObsoleteOsmRelation)
{
m_cache->ForEachRelationByRelationCached(serialId, wrapper);
it = base::FindIf(
elements, [&](auto const & idRelation) { return idRelation.second.GetRelationRole(serialId) == "outline"; });
}
return it != std::end(elements) ? base::MakeOsmRelation(it->first) : base::GeoObjectId();
}
void BuildingPartsCollector::Finish()
{
m_writer.reset();
}
void BuildingPartsCollector::Save()
{
CHECK(!m_writer, ("Finish() has not been called."));
if (Platform::IsFileExistsByFullPath(GetTmpFilename()))
CHECK(base::CopyFileX(GetTmpFilename(), GetFilename()), ());
}
void BuildingPartsCollector::OrderCollectedData()
{
std::vector<BuildingParts> collectedData;
{
FileReader reader(GetFilename());
ReaderSource src(reader);
while (src.Size() > 0)
collectedData.emplace_back(BuildingParts::Read(src));
}
std::sort(std::begin(collectedData), std::end(collectedData));
FileWriter writer(GetFilename());
for (auto const & bp : collectedData)
BuildingParts::Write(writer, bp);
}
void BuildingPartsCollector::MergeInto(BuildingPartsCollector & collector) const
{
CHECK(!m_writer || !collector.m_writer, ("Finish() has not been called."));
base::AppendFileToFile(GetTmpFilename(), collector.GetTmpFilename());
}
BuildingToBuildingPartsMap::BuildingToBuildingPartsMap(std::string const & filename)
{
FileReader reader(filename);
ReaderSource<FileReader> src(reader);
while (src.Size() > 0)
{
auto const buildingParts = BuildingPartsCollector::BuildingParts::Read(src);
m_buildingParts.insert(std::end(m_buildingParts), std::begin(buildingParts.m_buildingParts),
std::end(buildingParts.m_buildingParts));
m_outlineToBuildingPart.emplace_back(buildingParts.m_id, std::move(buildingParts.m_buildingParts));
}
m_outlineToBuildingPart.shrink_to_fit();
m_buildingParts.shrink_to_fit();
std::sort(std::begin(m_outlineToBuildingPart), std::end(m_outlineToBuildingPart));
std::sort(std::begin(m_buildingParts), std::end(m_buildingParts));
}
bool BuildingToBuildingPartsMap::HasBuildingPart(base::GeoObjectId const & id)
{
return std::binary_search(std::cbegin(m_buildingParts), std::cend(m_buildingParts), id);
}
std::vector<base::GeoObjectId> const & BuildingToBuildingPartsMap::GetBuildingPartsByOutlineId(CompositeId const & id)
{
auto const it = std::lower_bound(std::cbegin(m_outlineToBuildingPart), std::cend(m_outlineToBuildingPart), id,
[](auto const & lhs, auto const & rhs) { return lhs.first < rhs; });
if (it != std::cend(m_outlineToBuildingPart) && it->first == id)
return it->second;
static std::vector<base::GeoObjectId> const kEmpty;
return kEmpty;
}
} // namespace generator

View file

@ -0,0 +1,84 @@
#pragma once
#include "generator/collector_interface.hpp"
#include "generator/composite_id.hpp"
#include "coding/file_writer.hpp"
#include "base/geo_object_id.hpp"
#include <fstream>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
struct OsmElement;
class FileReader;
template <typename>
class ReaderSource;
namespace feature
{
class FeatureBuilder;
} // namespace feature
namespace generator
{
// BuildingPartsCollector collects ids of building parts from relations.
class BuildingPartsCollector : public CollectorInterface
{
public:
struct BuildingParts
{
friend bool operator<(BuildingParts const & lhs, BuildingParts const & rhs)
{
return std::tie(lhs.m_id, lhs.m_buildingParts) < std::tie(rhs.m_id, rhs.m_buildingParts);
}
static void Write(FileWriter & writer, BuildingParts const & pb);
static BuildingParts Read(ReaderSource<FileReader> & src);
CompositeId m_id;
std::vector<base::GeoObjectId> m_buildingParts;
};
explicit BuildingPartsCollector(std::string const & filename, IDRInterfacePtr const & cache);
// CollectorInterface overrides:
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache) const override;
void CollectFeature(feature::FeatureBuilder const & fb, OsmElement const &) override;
void Finish() override;
IMPLEMENT_COLLECTOR_IFACE(BuildingPartsCollector);
void MergeInto(BuildingPartsCollector & collector) const;
protected:
void Save() override;
void OrderCollectedData() override;
private:
base::GeoObjectId FindTopRelation(base::GeoObjectId elId);
std::vector<base::GeoObjectId> FindAllBuildingParts(base::GeoObjectId const & id);
std::shared_ptr<cache::IntermediateDataReaderInterface> m_cache;
std::unique_ptr<FileWriter> m_writer;
};
class BuildingToBuildingPartsMap
{
public:
explicit BuildingToBuildingPartsMap(std::string const & filename);
bool HasBuildingPart(base::GeoObjectId const & id);
std::vector<base::GeoObjectId> const & GetBuildingPartsByOutlineId(CompositeId const & id);
private:
std::vector<base::GeoObjectId> m_buildingParts;
std::vector<std::pair<CompositeId, std::vector<base::GeoObjectId>>> m_outlineToBuildingPart;
};
} // namespace generator

View file

@ -0,0 +1,184 @@
#include "generator/collector_camera.hpp"
#include "generator/feature_builder.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/maxspeeds_parser.hpp"
#include "generator/osm_element.hpp"
#include "routing/routing_helpers.hpp"
#include "routing_common/maxspeed_conversion.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "platform/measurement_utils.hpp"
#include "platform/platform.hpp"
#include "coding/point_coding.hpp"
#include "coding/reader.hpp"
#include "coding/write_to_sink.hpp"
#include "coding/writer.hpp"
#include "geometry/latlon.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <algorithm>
namespace routing_builder
{
std::optional<double> GetMaxSpeedKmPH(std::string const & maxSpeedString)
{
routing::SpeedInUnits speed;
if (!generator::ParseMaxspeedTag(maxSpeedString, speed) || !speed.IsNumeric())
return {};
auto const speedKmPh = measurement_utils::ToSpeedKmPH(speed.GetSpeed(), speed.GetUnits());
if (speedKmPh < 0.0)
return {};
return {speedKmPh};
}
CameraCollector::CameraInfo::CameraInfo(OsmElement const & element)
: m_id(element.m_id)
, m_lon(element.m_lon)
, m_lat(element.m_lat)
{
// Filter long "maxspeed" dummy strings.
auto const maxspeed = element.GetTag("maxspeed");
if (maxspeed.empty() || maxspeed.size() > 32)
return;
if (auto const validatedMaxspeed = GetMaxSpeedKmPH(maxspeed))
m_speedKmPH = static_cast<uint32_t>(*validatedMaxspeed);
else
LOG(LWARNING, ("Bad speed format of camera:", maxspeed, ", osmId:", element.m_id));
}
// static
CameraCollector::CameraInfo CameraCollector::CameraInfo::Read(ReaderSource<FileReader> & src)
{
/// @todo Take out intermediate camera info serialization code.
/// Should be equal with CamerasInfoCollector::ParseIntermediateInfo.
CameraInfo camera;
auto const latInt = ReadPrimitiveFromSource<uint32_t>(src);
auto const lonInt = ReadPrimitiveFromSource<uint32_t>(src);
camera.m_lat = Uint32ToDouble(latInt, ms::LatLon::kMinLat, ms::LatLon::kMaxLat, kPointCoordBits);
camera.m_lon = Uint32ToDouble(lonInt, ms::LatLon::kMinLon, ms::LatLon::kMaxLon, kPointCoordBits);
ReadPrimitiveFromSource(src, camera.m_speedKmPH);
auto relatedWaysNumber = ReadPrimitiveFromSource<uint32_t>(src);
camera.m_ways.reserve(relatedWaysNumber);
while (relatedWaysNumber--)
camera.m_ways.push_back(ReadPrimitiveFromSource<uint64_t>(src));
return camera;
}
// static
void CameraCollector::CameraInfo::Write(FileWriter & writer, CameraInfo const & camera)
{
uint32_t const lat = DoubleToUint32(camera.m_lat, ms::LatLon::kMinLat, ms::LatLon::kMaxLat, kPointCoordBits);
WriteToSink(writer, lat);
uint32_t const lon = DoubleToUint32(camera.m_lon, ms::LatLon::kMinLon, ms::LatLon::kMaxLon, kPointCoordBits);
WriteToSink(writer, lon);
WriteToSink(writer, camera.m_speedKmPH);
auto const size = static_cast<uint32_t>(camera.m_ways.size());
WriteToSink(writer, size);
for (auto wayId : camera.m_ways)
WriteToSink(writer, wayId);
}
void CameraCollector::FillCameraInWays()
{
for (uint64_t id : m_roadOsmIDs)
{
WayElement way(id);
CHECK(m_cache->GetWay(id, way), ());
for (auto const & node : way.m_nodes)
{
auto it = m_speedCameras.find(node);
if (it != m_speedCameras.cend())
it->second.m_ways.push_back(id);
}
}
size_t detachedCameras = 0;
ForEachCamera([&detachedCameras](auto & c)
{
if (c.m_ways.empty())
++detachedCameras;
else
base::SortUnique(c.m_ways);
});
LOG(LINFO, ("Total cameras count:", m_speedCameras.size(), "Detached cameras count:", detachedCameras));
}
void CameraCollector::MergeInto(CameraCollector & collector) const
{
collector.m_speedCameras.insert(m_speedCameras.begin(), m_speedCameras.end());
collector.m_roadOsmIDs.insert(collector.m_roadOsmIDs.end(), m_roadOsmIDs.begin(), m_roadOsmIDs.end());
}
void CameraCollector::Save()
{
LOG(LINFO, ("Associating speed cameras with ways..."));
FillCameraInWays();
LOG(LINFO, ("Saving speed cameras to", GetFilename()));
FileWriter writer(GetFilename());
ForEachCamera([&](auto const & camera) { CameraInfo::Write(writer, camera); });
LOG(LINFO, ("Finished saving speed cameras"));
}
void CameraCollector::OrderCollectedData()
{
std::vector<CameraInfo> collectedData;
{
FileReader reader(GetFilename());
ReaderSource src(reader);
while (src.Size() > 0)
collectedData.emplace_back(CameraInfo::Read(src));
}
std::sort(std::begin(collectedData), std::end(collectedData));
FileWriter writer(GetFilename());
for (auto const & camera : collectedData)
CameraInfo::Write(writer, camera);
}
CameraCollector::CameraCollector(std::string const & filename, IDRInterfacePtr cache)
: generator::CollectorInterface(filename)
, m_cache(std::move(cache))
{}
std::shared_ptr<generator::CollectorInterface> CameraCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<CameraCollector>(GetFilename(), cache);
}
void CameraCollector::CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & element)
{
if (element.m_type == OsmElement::EntityType::Node)
{
if (ftypes::IsSpeedCamChecker::Instance()(fb.GetTypes()))
m_speedCameras.emplace(element.m_id, element);
}
else if (element.m_type == OsmElement::EntityType::Way)
{
if (fb.IsLine() && routing::IsCarRoad(fb.GetTypes()))
m_roadOsmIDs.push_back(element.m_id);
}
}
} // namespace routing_builder

View file

@ -0,0 +1,87 @@
#pragma once
#include "generator/collector_interface.hpp"
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
template <typename T>
class ReaderSource;
class FileReader;
class FileWriter;
namespace routing_builder
{
class TestCameraCollector;
/// \brief Returns formatted speed in km per hour.
/// \param maxSpeedString - text with speed. Possible format:
/// "130" - means 130 km per hour.
/// "130 mph" - means 130 miles per hour.
/// "130 kmh" - means 130 km per hour.
/// See https://wiki.openstreetmap.org/wiki/Key:maxspeed
/// for more details about input string.
std::optional<double> GetMaxSpeedKmPH(std::string const & maxSpeedString);
class CameraCollector : public generator::CollectorInterface
{
friend class TestCameraCollector;
struct CameraInfo
{
CameraInfo() = default;
explicit CameraInfo(OsmElement const & element);
friend bool operator<(CameraInfo const & lhs, CameraInfo const & rhs)
{
return std::tie(lhs.m_id, lhs.m_lon, lhs.m_lat, lhs.m_speedKmPH, lhs.m_ways) <
std::tie(rhs.m_id, rhs.m_lon, rhs.m_lat, rhs.m_speedKmPH, rhs.m_ways);
}
static CameraInfo Read(ReaderSource<FileReader> & src);
static void Write(FileWriter & writer, CameraInfo const & camera);
uint64_t m_id = 0;
double m_lon = 0.0;
double m_lat = 0.0;
uint32_t m_speedKmPH = 0;
std::vector<uint64_t> m_ways;
};
public:
CameraCollector(std::string const & filename, IDRInterfacePtr cache);
/// @name CollectorInterface overrides:
/// @{
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const override;
void CollectFeature(feature::FeatureBuilder const & feature, OsmElement const & element) override;
/// @}
IMPLEMENT_COLLECTOR_IFACE(CameraCollector);
void MergeInto(CameraCollector & collector) const;
protected:
void Save() override;
void OrderCollectedData() override;
void FillCameraInWays();
template <typename Fn>
void ForEachCamera(Fn && toDo)
{
for (auto & p : m_speedCameras)
toDo(p.second);
}
private:
IDRInterfacePtr m_cache;
std::vector<uint64_t> m_roadOsmIDs;
std::unordered_map<uint64_t, CameraInfo> m_speedCameras;
};
} // namespace routing_builder

View file

@ -0,0 +1,62 @@
#include "generator/collector_collection.hpp"
#include "generator/feature_builder.hpp"
#include "generator/intermediate_elements.hpp"
#include "generator/osm_element.hpp"
using namespace feature;
namespace generator
{
std::shared_ptr<CollectorInterface> CollectorCollection::Clone(IDRInterfacePtr const & cache) const
{
auto p = std::make_shared<CollectorCollection>();
for (auto const & c : m_collection)
p->Append(c->Clone(cache));
return p;
}
void CollectorCollection::Collect(OsmElement const & element)
{
for (auto & c : m_collection)
c->Collect(element);
}
void CollectorCollection::CollectRelation(RelationElement const & element)
{
for (auto & c : m_collection)
c->CollectRelation(element);
}
void CollectorCollection::CollectFeature(FeatureBuilder const & feature, OsmElement const & element)
{
for (auto & c : m_collection)
c->CollectFeature(feature, element);
}
void CollectorCollection::Finish()
{
for (auto & c : m_collection)
c->Finish();
}
void CollectorCollection::Save()
{
for (auto & c : m_collection)
c->Save();
}
void CollectorCollection::OrderCollectedData()
{
for (auto & c : m_collection)
c->OrderCollectedData();
}
void CollectorCollection::MergeInto(CollectorCollection & collector) const
{
auto & otherCollection = collector.m_collection;
CHECK_EQUAL(m_collection.size(), otherCollection.size(), ());
for (size_t i = 0; i < m_collection.size(); ++i)
otherCollection[i]->Merge(*m_collection[i]);
}
} // namespace generator

View file

@ -0,0 +1,40 @@
#pragma once
#include "generator/collection_base.hpp"
#include "generator/collector_interface.hpp"
#include <memory>
struct OsmElement;
class RelationElement;
namespace feature
{
class FeatureBuilder;
} // namespace feature
namespace generator
{
// This class allows you to work with a group of collectors as with one.
class CollectorCollection
: public CollectionBase<std::shared_ptr<CollectorInterface>>
, public CollectorInterface
{
public:
// CollectorInterface overrides:
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const override;
void Collect(OsmElement const & element) override;
void CollectRelation(RelationElement const & element) override;
void CollectFeature(feature::FeatureBuilder const & feature, OsmElement const & element) override;
void Finish() override;
IMPLEMENT_COLLECTOR_IFACE(CollectorCollection);
void MergeInto(CollectorCollection & collector) const;
protected:
void Save() override;
void OrderCollectedData() override;
};
} // namespace generator

View file

@ -0,0 +1,89 @@
#pragma once
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <atomic>
#include <fstream>
#include <memory>
#include <string>
struct OsmElement;
class RelationElement;
namespace feature
{
class FeatureBuilder;
} // namespace feature
namespace base
{
class GeoObjectId;
} // namespace base
namespace feature
{
class MetalinesBuilder;
} // namespace feature
namespace generator
{
namespace cache
{
class IntermediateDataReaderInterface;
} // namespace cache
// Implementing this interface allows an object to collect data from RelationElement,
// OsmElement and FeatureBuilder elements.
class CollectorInterface
{
public:
friend class CollectorCollection;
CollectorInterface(std::string const & filename = {}) : m_id(CreateId()), m_filename(filename) {}
virtual ~CollectorInterface() { CHECK(Platform::RemoveFileIfExists(GetTmpFilename()), (GetTmpFilename())); }
using IDRInterfacePtr = std::shared_ptr<cache::IntermediateDataReaderInterface>;
/// @param[in] cache Use passed (not saved) cache to create a clone, because cache also may (should) be cloned.
/// Empty \a cache used in unit tests only. Generator's cache is always valid.
virtual std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const = 0;
virtual void Collect(OsmElement const &) {}
virtual void CollectRelation(RelationElement const &) {}
virtual void CollectFeature(feature::FeatureBuilder const &, OsmElement const &) {}
virtual void Finish() {}
virtual void Merge(CollectorInterface const &) = 0;
virtual void Finalize(bool isStable = false)
{
Save();
if (isStable)
OrderCollectedData();
}
std::string GetTmpFilename() const { return m_filename + "." + std::to_string(m_id); }
std::string const & GetFilename() const { return m_filename; }
protected:
virtual void Save() = 0;
virtual void OrderCollectedData() {}
private:
int CreateId()
{
static std::atomic_int id{0};
return id++;
}
int m_id;
std::string m_filename;
};
} // namespace generator
#define IMPLEMENT_COLLECTOR_IFACE(className) \
void Merge(CollectorInterface const & ci) override \
{ \
dynamic_cast<className const &>(ci).MergeInto(*this); \
}

View file

@ -0,0 +1,80 @@
#include "generator/collector_mini_roundabout.hpp"
#include "base/assert.hpp"
#include <algorithm>
#include <iterator>
namespace generator
{
using namespace feature;
MiniRoundaboutCollector::MiniRoundaboutCollector(std::string const & filename, IDRInterfacePtr cache)
: generator::CollectorInterface(filename)
, m_cache(std::move(cache))
{}
std::shared_ptr<generator::CollectorInterface> MiniRoundaboutCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<MiniRoundaboutCollector>(GetFilename(), cache);
}
void MiniRoundaboutCollector::Collect(OsmElement const & element)
{
if (element.IsNode() && element.HasTag("highway", "mini_roundabout"))
{
CHECK(m_miniRoundabouts.emplace(element.m_id, element).second, ());
}
else if (element.IsRelation())
{
// Skip mini_roundabouts with role="via" in restrictions
for (auto const & member : element.m_members)
{
if (member.m_type == OsmElement::EntityType::Node && member.m_role == "via")
{
m_miniRoundaboutsExceptions.insert(member.m_ref);
break;
}
}
}
}
void MiniRoundaboutCollector::CollectFeature(FeatureBuilder const & feature, OsmElement const & element)
{
if (MiniRoundaboutInfo::IsProcessRoad(feature))
m_roads.AddWay(element);
}
void MiniRoundaboutCollector::Save()
{
LOG(LINFO, ("Saving mini roundabouts to", GetFilename()));
/// @todo We assign only car roads here into MiniRoundaboutInfo.m_ways.
/// Should also collect other highways (like path or pedestrian) in very general case.
/// https://www.openstreetmap.org/way/220672898
m_roads.ForEachWay([this](uint64_t id, std::vector<uint64_t> const & nodes)
{
for (uint64_t node : nodes)
{
auto it = m_miniRoundabouts.find(node);
if (it != m_miniRoundabouts.end())
it->second.m_ways.push_back(id);
}
}, m_cache);
FileWriter writer(GetFilename());
ForEachMiniRoundabout([&writer](MiniRoundaboutInfo & miniRoundabout)
{
if (miniRoundabout.Normalize())
WriteMiniRoundabout(writer, miniRoundabout);
});
LOG(LINFO, ("Finished saving mini roundabouts"));
}
void MiniRoundaboutCollector::MergeInto(MiniRoundaboutCollector & collector) const
{
m_roads.MergeInto(collector.m_roads);
collector.m_miniRoundabouts.insert(m_miniRoundabouts.begin(), m_miniRoundabouts.end());
collector.m_miniRoundaboutsExceptions.insert(m_miniRoundaboutsExceptions.begin(), m_miniRoundaboutsExceptions.end());
}
} // namespace generator

View file

@ -0,0 +1,45 @@
#pragma once
#include "generator/collector_interface.hpp"
#include "generator/mini_roundabout_info.hpp"
#include "generator/way_nodes_mapper.hpp"
#include <unordered_map>
#include <unordered_set>
namespace generator
{
class MiniRoundaboutCollector : public generator::CollectorInterface
{
public:
MiniRoundaboutCollector(std::string const & filename, IDRInterfacePtr cache);
// CollectorInterface overrides:
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & = {}) const override;
void Collect(OsmElement const & element) override;
void CollectFeature(feature::FeatureBuilder const & feature, OsmElement const & element) override;
IMPLEMENT_COLLECTOR_IFACE(MiniRoundaboutCollector);
void MergeInto(MiniRoundaboutCollector & collector) const;
protected:
void Save() override;
template <typename Fn>
void ForEachMiniRoundabout(Fn && toDo)
{
for (auto & p : m_miniRoundabouts)
if (m_miniRoundaboutsExceptions.count(p.first) == 0)
toDo(p.second);
}
private:
IDRInterfacePtr m_cache;
WaysIDHolder m_roads;
std::unordered_map<uint64_t, MiniRoundaboutInfo> m_miniRoundabouts;
std::unordered_set<uint64_t> m_miniRoundaboutsExceptions;
};
} // namespace generator

View file

@ -0,0 +1,456 @@
#include "generator/collector_routing_city_boundaries.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/osm_element.hpp"
#include "generator/osm_element_helpers.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "geometry/mercator.hpp"
#include "coding/read_write_utils.hpp"
#include "base/assert.hpp"
#include <algorithm>
#include <iterator>
namespace generator
{
using namespace feature;
using ftypes::LocalityType;
// PlaceBoundariesHolder::Locality ----------------------------------------------------------------
PlaceBoundariesHolder::Locality::Locality(std::string const & placeType, OsmElement const & elem)
: m_adminLevel(osm_element::GetAdminLevel(elem))
, m_name(elem.GetTag("name"))
, m_population(osm_element::GetPopulation(elem))
, m_place(ftypes::LocalityFromString(placeType))
{}
template <class Sink>
void PlaceBoundariesHolder::Locality::Serialize(Sink & sink) const
{
CHECK(TestValid(), ());
WriteToSink(sink, static_cast<int8_t>(m_place));
WriteToSink(sink, static_cast<int8_t>(m_placeFromNode));
WriteVarUint(sink, m_population);
WriteVarUint(sink, m_populationFromNode);
WriteToSink(sink, m_adminLevel);
rw::WritePOD(sink, m_center);
// Same as FeatureBuilder::SerializeAccuratelyForIntermediate.
WriteVarUint(sink, static_cast<uint32_t>(m_boundary.size()));
for (auto const & e : m_boundary)
rw::WriteVectorOfPOD(sink, e);
}
template <class Source>
void PlaceBoundariesHolder::Locality::Deserialize(Source & src)
{
m_place = static_cast<LocalityType>(ReadPrimitiveFromSource<int8_t>(src));
m_placeFromNode = static_cast<LocalityType>(ReadPrimitiveFromSource<int8_t>(src));
m_population = ReadVarUint<uint64_t>(src);
m_populationFromNode = ReadVarUint<uint64_t>(src);
m_adminLevel = ReadPrimitiveFromSource<uint8_t>(src);
rw::ReadPOD(src, m_center);
// Same as FeatureBuilder::DeserializeAccuratelyFromIntermediate.
m_boundary.resize(ReadVarUint<uint32_t>(src));
for (auto & e : m_boundary)
rw::ReadVectorOfPOD(src, e);
RecalcBoundaryRect();
CHECK(TestValid(), ());
}
bool PlaceBoundariesHolder::Locality::IsBetterBoundary(Locality const & rhs, std::string const & placeName) const
{
// This function is called for boundary relations only.
CHECK(!m_boundary.empty() && !rhs.m_boundary.empty(), ());
if (IsHonestCity() && !rhs.IsHonestCity())
return true;
if (!IsHonestCity() && rhs.IsHonestCity())
return false;
if (!placeName.empty())
{
if (m_name == placeName && rhs.m_name != placeName)
return true;
if (m_name != placeName && rhs.m_name == placeName)
return false;
}
// Heuristic: Select boundary with bigger admin_level or smaller boundary.
if (m_adminLevel == rhs.m_adminLevel)
return GetApproxArea() < rhs.GetApproxArea();
return m_adminLevel > rhs.m_adminLevel;
}
double PlaceBoundariesHolder::Locality::GetApproxArea() const
{
double res = 0;
for (auto const & poly : m_boundary)
{
m2::RectD r;
CalcRect(poly, r);
res += r.Area();
}
return res;
}
ftypes::LocalityType PlaceBoundariesHolder::Locality::GetPlace() const
{
return (m_placeFromNode != ftypes::LocalityType::None ? m_placeFromNode : m_place);
}
uint64_t PlaceBoundariesHolder::Locality::GetPopulation() const
{
uint64_t p = m_populationFromNode;
if (p == 0)
p = m_population;
// Same as ftypes::GetPopulation.
return (p < 10 ? ftypes::GetDefPopulation(GetPlace()) : p);
}
void PlaceBoundariesHolder::Locality::AssignNodeParams(Locality const & node)
{
m_center = node.m_center;
m_placeFromNode = node.m_place;
m_populationFromNode = node.m_population;
}
bool PlaceBoundariesHolder::Locality::TestValid() const
{
if (IsPoint())
return IsHonestCity() && !m_center.IsAlmostZero();
if (m_boundary.empty() || !m_boundaryRect.IsValid())
return false;
if (!IsHonestCity())
{
// This Locality is from boundary relation, based on some Node.
return !m_center.IsAlmostZero() && m_placeFromNode >= ftypes::LocalityType::City && m_adminLevel > 0;
}
return true;
}
bool PlaceBoundariesHolder::Locality::IsInBoundary(m2::PointD const & pt) const
{
CHECK(!m_boundary.empty(), ());
return m_boundaryRect.IsPointInside(pt);
}
void PlaceBoundariesHolder::Locality::RecalcBoundaryRect()
{
m_boundaryRect.MakeEmpty();
for (auto const & pts : m_boundary)
feature::CalcRect(pts, m_boundaryRect);
}
std::string DebugPrint(PlaceBoundariesHolder::Locality const & l)
{
std::string rectStr;
if (l.m_boundaryRect.IsValid())
rectStr = DebugPrint(mercator::ToLatLon(l.m_boundaryRect));
else
rectStr = "Invalid";
return "Locality { Rect = " + rectStr + "; Name = " + l.m_name + " }";
}
// PlaceBoundariesHolder --------------------------------------------------------------------------
void PlaceBoundariesHolder::Serialize(std::string const & fileName) const
{
FileWriter writer(fileName);
WriteVarUint(writer, static_cast<uint64_t>(m_id2index.size()));
for (auto const & e : m_id2index)
{
WriteToSink(writer, e.first.GetEncodedId());
WriteVarUint(writer, e.second);
}
WriteVarUint(writer, static_cast<uint64_t>(m_data.size()));
for (auto const & e : m_data)
e.Serialize(writer);
}
void PlaceBoundariesHolder::Deserialize(std::string const & fileName)
{
FileReader reader(fileName);
ReaderSource src(reader);
uint64_t count = ReadVarUint<uint64_t>(src);
while (count > 0)
{
IDType const id(ReadPrimitiveFromSource<uint64_t>(src));
CHECK(m_id2index.emplace(id, ReadVarUint<uint64_t>(src)).second, (id));
--count;
}
count = ReadVarUint<uint64_t>(src);
m_data.resize(count);
for (auto & e : m_data)
e.Deserialize(src);
}
void PlaceBoundariesHolder::Add(IDType id, Locality && loc, IDType nodeID)
{
auto [it, added] = m_id2index.emplace(id, m_data.size());
if (added)
{
CHECK(loc.TestValid(), (id));
m_data.push_back(std::move(loc));
}
if (nodeID != IDType())
CHECK(m_id2index.emplace(nodeID, it->second).second, (id));
}
int PlaceBoundariesHolder::GetIndex(IDType id) const
{
auto it = m_id2index.find(id);
if (it != m_id2index.end())
return it->second;
LOG(LWARNING, ("Place without locality:", id));
return -1;
}
PlaceBoundariesHolder::Locality const * PlaceBoundariesHolder::GetBestBoundary(std::vector<IDType> const & ids,
m2::PointD const & center) const
{
Locality const * bestLoc = nullptr;
auto const isBetter = [&bestLoc](Locality const & loc)
{
if (loc.m_boundary.empty())
return false;
if (bestLoc == nullptr)
return true;
return loc.IsBetterBoundary(*bestLoc);
};
for (auto id : ids)
{
int const idx = GetIndex(id);
if (idx < 0)
continue;
Locality const & loc = m_data[idx];
if (isBetter(loc) && loc.IsInBoundary(center))
bestLoc = &loc;
}
return bestLoc;
}
// PlaceBoundariesBuilder -------------------------------------------------------------------------
void PlaceBoundariesBuilder::Add(Locality && loc, IDType id, std::vector<uint64_t> const & nodes)
{
// Heuristic: store Relation by name only if "connection" nodes are empty.
// See Place_CityRelations_IncludePoint.
if (nodes.empty() && !loc.m_name.empty() && id.GetType() == base::GeoObjectId::Type::ObsoleteOsmRelation)
m_name2rel[loc.m_name].insert(id);
CHECK(m_id2loc.emplace(id, std::move(loc)).second, ());
for (uint64_t nodeID : nodes)
m_node2rel[base::MakeOsmNode(nodeID)].insert(id);
}
/// @todo Make move logic in MergeInto functions?
void PlaceBoundariesBuilder::MergeInto(PlaceBoundariesBuilder & dest) const
{
for (auto const & e : m_id2loc)
dest.m_id2loc.emplace(e.first, e.second);
for (auto const & e : m_node2rel)
dest.m_node2rel[e.first].insert(e.second.begin(), e.second.end());
for (auto const & e : m_name2rel)
dest.m_name2rel[e.first].insert(e.second.begin(), e.second.end());
}
void PlaceBoundariesBuilder::Save(std::string const & fileName)
{
// Find best Locality Relation for Node.
std::vector<std::pair<IDType, IDType>> node2rel;
for (auto const & e : m_id2loc)
{
// Iterate via honest Node place localities to select best Relation for them.
if (!e.second.IsPoint())
continue;
IDsSetT ids;
if (auto it = m_node2rel.find(e.first); it != m_node2rel.end())
ids.insert(it->second.begin(), it->second.end());
if (auto it = m_name2rel.find(e.second.m_name); it != m_name2rel.end())
ids.insert(it->second.begin(), it->second.end());
if (ids.empty())
continue;
CHECK(e.second.IsHonestCity(), (e.first));
Locality const * best = nullptr;
IDType bestID;
for (auto const & relID : ids)
{
auto const & loc = m_id2loc[relID];
if ((best == nullptr || loc.IsBetterBoundary(*best, e.second.m_name)) && loc.IsInBoundary(e.second.m_center))
{
best = &loc;
bestID = relID;
}
}
if (best)
node2rel.emplace_back(e.first, bestID);
}
PlaceBoundariesHolder holder;
// Add Relation localities with Node refs.
for (auto const & e : node2rel)
{
auto itRelation = m_id2loc.find(e.second);
CHECK(itRelation != m_id2loc.end(), (e.second));
auto itNode = m_id2loc.find(e.first);
CHECK(itNode != m_id2loc.end(), (e.first));
/// @todo Node params will be assigned form the "first" valid Node now. Implement Update?
itRelation->second.AssignNodeParams(itNode->second);
holder.Add(e.second, std::move(itRelation->second), e.first);
// Do not delete Relation from m_id2loc, because some other Node also can point on it (and we will add reference),
// but zero this Locality after move to avoid adding below.
itRelation->second = {};
CHECK(!itRelation->second.IsHonestCity(), ());
m_id2loc.erase(itNode);
}
// Add remaining localities.
for (auto & e : m_id2loc)
if (e.second.IsHonestCity())
holder.Add(e.first, std::move(e.second), IDType());
holder.Serialize(fileName);
}
// RoutingCityBoundariesCollector ------------------------------------------------------------------
RoutingCityBoundariesCollector::RoutingCityBoundariesCollector(std::string const & filename,
IDRInterfacePtr const & cache)
: CollectorInterface(filename)
, m_cache(cache)
, m_featureMakerSimple(cache)
{}
std::shared_ptr<CollectorInterface> RoutingCityBoundariesCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<RoutingCityBoundariesCollector>(GetFilename(), cache ? cache : m_cache);
}
void RoutingCityBoundariesCollector::Collect(OsmElement const & elem)
{
bool const isRelation = elem.IsRelation();
// Fast check: is it a place or a boundary candidate.
std::string place = elem.GetTag("place");
if (place.empty())
place = elem.GetTag("de:place");
if (place.empty() && !isRelation)
return;
Locality loc(place, elem);
std::vector<uint64_t> nodes;
if (isRelation)
nodes = osm_element::GetPlaceNodeFromMembers(elem);
if (!loc.IsHonestCity())
{
if (!isRelation)
return;
// Accumulate boundaries, where relations doesn't have exact place=city/town/.. tags, like:
// * Turkey "major" cities have admin_level=4 (like state) with "subtowns" with admin_level=6.
// - Istanbul (Kadikoy, Fatih, Beyoglu, ...)
// - Ankara (Altindag, ...)
// - Izmir
// * Capitals (like Минск, Москва, Berlin) have admin_level=4.
// * Canberra's boundary has place=territory
// * Riviera Beach boundary has place=suburb
// * Hong Kong boundary has place=region
if (loc.m_adminLevel < 4)
return;
// Skip Relations like boundary=religious_administration.
// Also "boundary" == "administrative" is missing sometimes.
auto const boundary = elem.GetTag("boundary");
if (boundary != "administrative")
return;
// Should be at least one reference.
if (nodes.empty() && loc.m_name.empty())
return;
}
auto copy = elem;
m_featureMakerSimple.Add(copy);
bool isGoodFB = false;
FeatureBuilder fb;
while (m_featureMakerSimple.GetNextFeature(fb))
{
switch (fb.GetGeomType())
{
case GeomType::Point: loc.m_center = fb.GetKeyPoint(); break;
case GeomType::Area:
/// @todo Move geometry or make parsing geometry without FeatureBuilder class.
loc.m_boundary.push_back(fb.GetOuterGeometry());
loc.RecalcBoundaryRect();
break;
default: // skip non-closed ways
continue;
}
isGoodFB = true;
}
if (isGoodFB)
m_builder.Add(std::move(loc), fb.GetMostGenericOsmId(), nodes);
}
void RoutingCityBoundariesCollector::Save()
{
LOG(LINFO, ("Saving routing city boundaries to", GetFilename()));
m_builder.Save(GetFilename());
LOG(LINFO, ("Finished saving routing city boundaries"));
}
void RoutingCityBoundariesCollector::MergeInto(RoutingCityBoundariesCollector & collector) const
{
m_builder.MergeInto(collector.m_builder);
}
} // namespace generator

View file

@ -0,0 +1,139 @@
#pragma once
#include "generator/collector_interface.hpp"
#include "generator/feature_builder.hpp"
#include "generator/feature_maker.hpp"
#include "generator/intermediate_data.hpp"
#include "indexer/ftypes_matcher.hpp"
#include <memory>
namespace generator
{
class PlaceBoundariesHolder
{
public:
struct Locality
{
Locality() = default;
Locality(std::string const & placeType, OsmElement const & elem);
// Used in cpp module only.
template <class Sink>
void Serialize(Sink & sink) const;
template <class Source>
void Deserialize(Source & src);
/// @param[in] placeName Original Node place's name if available to match.
/// @return Is this boundary better than rhs.
bool IsBetterBoundary(Locality const & rhs, std::string const & placeName = {}) const;
bool IsPoint() const { return m_boundary.empty(); }
bool IsHonestCity() const { return m_place >= ftypes::LocalityType::City; }
double GetApproxArea() const;
ftypes::LocalityType GetPlace() const;
uint64_t GetPopulation() const;
void AssignNodeParams(Locality const & node);
bool TestValid() const;
bool IsInBoundary(m2::PointD const & pt) const;
void RecalcBoundaryRect();
// Valid if m_boundary is empty or m_adminLevel > 0.
m2::PointD m_center{0, 0};
/// @todo Do not take into account holes, store only outer geometry.
using PointSeq = feature::FeatureBuilder::PointSeq;
std::vector<PointSeq> m_boundary;
m2::RectD m_boundaryRect;
uint8_t m_adminLevel = 0;
std::string m_name;
friend std::string DebugPrint(Locality const & l);
private:
uint64_t m_population = 0;
uint64_t m_populationFromNode = 0;
ftypes::LocalityType m_placeFromNode = ftypes::LocalityType::None;
ftypes::LocalityType m_place = ftypes::LocalityType::None;
};
void Serialize(std::string const & fileName) const;
void Deserialize(std::string const & fileName);
using IDType = base::GeoObjectId;
void Add(IDType id, Locality && loc, IDType nodeID);
/// @note Mutable function!
template <class FnT>
void ForEachLocality(FnT && fn)
{
for (auto & loc : m_data)
fn(loc);
}
m2::RectD GetBoundaryRect(IDType id) const
{
auto const it = m_id2index.find(id);
if (it != m_id2index.end())
return m_data[it->second].m_boundaryRect;
return {};
}
int GetIndex(IDType id) const;
Locality const * GetBestBoundary(std::vector<IDType> const & ids, m2::PointD const & center) const;
private:
// Value is an index in m_data vector.
std::unordered_map<IDType, uint64_t> m_id2index;
std::vector<Locality> m_data;
};
class PlaceBoundariesBuilder
{
public:
using Locality = PlaceBoundariesHolder::Locality;
using IDType = PlaceBoundariesHolder::IDType;
void Add(Locality && loc, IDType id, std::vector<uint64_t> const & nodes);
void MergeInto(PlaceBoundariesBuilder & dest) const;
void Save(std::string const & fileName);
private:
std::unordered_map<IDType, Locality> m_id2loc;
using IDsSetT = std::unordered_set<IDType>;
std::unordered_map<IDType, IDsSetT> m_node2rel;
std::unordered_map<std::string, IDsSetT> m_name2rel;
};
class RoutingCityBoundariesCollector : public CollectorInterface
{
public:
RoutingCityBoundariesCollector(std::string const & filename, IDRInterfacePtr const & cache);
/// @name CollectorInterface overrides:
/// @{
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const override;
void Collect(OsmElement const & elem) override;
void Save() override;
/// @}
IMPLEMENT_COLLECTOR_IFACE(RoutingCityBoundariesCollector);
void MergeInto(RoutingCityBoundariesCollector & collector) const;
using Locality = PlaceBoundariesBuilder::Locality;
private:
PlaceBoundariesBuilder m_builder;
std::shared_ptr<cache::IntermediateDataReaderInterface> m_cache;
FeatureMakerSimple m_featureMakerSimple;
};
} // namespace generator

View file

@ -0,0 +1,61 @@
#include "generator/collector_tag.hpp"
#include "generator/final_processor_utils.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/osm_element.hpp"
#include "platform/platform.hpp"
#include "coding/internal/file_data.hpp"
#include "base/assert.hpp"
#include "base/geo_object_id.hpp"
#include "base/logging.hpp"
namespace generator
{
CollectorTag::CollectorTag(std::string const & filename, std::string const & tagKey, Validator const & validator)
: CollectorInterface(filename)
, m_tagKey(tagKey)
, m_validator(validator)
{
m_stream.exceptions(std::fstream::failbit | std::fstream::badbit);
m_stream.open(GetTmpFilename());
}
std::shared_ptr<CollectorInterface> CollectorTag::Clone(IDRInterfacePtr const &) const
{
return std::make_shared<CollectorTag>(GetFilename(), m_tagKey, m_validator);
}
void CollectorTag::Collect(OsmElement const & el)
{
auto const tag = el.GetTag(m_tagKey);
if (!tag.empty() && m_validator(tag))
m_stream << GetGeoObjectId(el).GetEncodedId() << "\t" << tag << "\n";
}
void CollectorTag::Finish()
{
if (m_stream.is_open())
m_stream.close();
}
void CollectorTag::Save()
{
CHECK(!m_stream.is_open(), ("Finish() has not been called."));
if (Platform::IsFileExistsByFullPath(GetTmpFilename()))
CHECK(base::CopyFileX(GetTmpFilename(), GetFilename()), ());
}
void CollectorTag::OrderCollectedData()
{
OrderTextFileByLine(GetFilename());
}
void CollectorTag::MergeInto(CollectorTag & collector) const
{
CHECK(!m_stream.is_open() || !collector.m_stream.is_open(), ("Finish() has not been called."));
base::AppendFileToFile(GetTmpFilename(), collector.GetTmpFilename());
}
} // namespace generator

View file

@ -0,0 +1,46 @@
#pragma once
#include "generator/collector_interface.hpp"
#include <fstream>
#include <functional>
#include <memory>
#include <string>
struct OsmElement;
namespace base
{
class GeoObjectId;
} // namespace base
namespace generator
{
// CollectorTag class collects validated value of a tag and saves it to file with following
// format: osmId<tab>tagValue.
class CollectorTag : public CollectorInterface
{
public:
using Validator = std::function<bool(std::string const & tagValue)>;
explicit CollectorTag(std::string const & filename, std::string const & tagKey, Validator const & validator);
// CollectorInterface overrides:
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & = {}) const override;
void Collect(OsmElement const & el) override;
void Finish() override;
IMPLEMENT_COLLECTOR_IFACE(CollectorTag);
void MergeInto(CollectorTag & collector) const;
protected:
void Save() override;
void OrderCollectedData() override;
private:
std::ofstream m_stream;
std::string m_tagKey;
Validator m_validator;
};
} // namespace generator

View file

@ -0,0 +1,10 @@
project(complex_generator)
set(SRC complex_generator.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
gflags::gflags
)

View file

@ -0,0 +1,127 @@
// This is a program that generates complexes on the basis of the last generation of maps.
// Complexes are a hierarchy of interesting geographical features.
// For the program to work correctly, you need to have in your file system:
// top_directory
// |_ planet.o5m
// |_maps_build
// |_190223(for example 2019 Feb. 23rd)
// |_intermediate_data
// |_osm2ft
//
// It's easy if you use maps_generator. For example:
//
// $ python -m maps_generator --skip="coastline" --countries="Russia_Moscow"
//
// $ ./complex_generator --maps_build_path=path/to/maps_build --user_resource_path=path/to/omim/data --output=output.txt
#include "generator/filter_complex.hpp"
#include "generator/filter_interface.hpp"
#include "generator/final_processor_complex.hpp"
#include "generator/generate_info.hpp"
#include "generator/hierarchy.hpp"
#include "generator/hierarchy_entry.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/processor_factory.hpp"
#include "generator/raw_generator.hpp"
#include "generator/translator_factory.hpp"
#include "generator/utils.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/map_style_reader.hpp"
#include "platform/platform.hpp"
#include "coding/endianness.hpp"
#include "base/assert.hpp"
#include "base/exception.hpp"
#include <csignal>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <boost/regex.hpp>
#include "defines.hpp"
#include <gflags/gflags.h>
DEFINE_string(node_storage, "map", "Type of storage for intermediate points representation. Available: raw, map, mem.");
DEFINE_string(user_resource_path, "", "User defined resource path for classificator.txt and etc.");
DEFINE_string(maps_build_path, "",
"Directory of any of the previous map generations. It is assumed that it will "
"contain a directory with mwm(for example 190423) and a directory with mappings from "
"osm is to a feature id.");
DEFINE_bool(popularity, false, "Build complexes for calculation of popularity of objects.");
DEFINE_string(output, "", "Output filename");
DEFINE_bool(debug, false, "Debug mode.");
MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
{
CHECK(IsLittleEndian(), ("Only little-endian architectures are supported."));
Platform & pl = GetPlatform();
gflags::SetUsageMessage(
"complex_generator is a program that generates complexes on the basis of "
"the last generation of maps. Complexes are a hierarchy of interesting "
"geographical features.");
gflags::SetVersionString(pl.Version());
gflags::ParseCommandLineFlags(&argc, &argv, true);
auto threadsCount = pl.CpuCores();
CHECK(!FLAGS_user_resource_path.empty(), ());
pl.SetResourceDir(FLAGS_user_resource_path);
classificator::Load();
feature::GenerateInfo genInfo;
genInfo.m_osmFileName = base::JoinPath(FLAGS_maps_build_path, "..", "planet.o5m");
genInfo.SetOsmFileType("o5m");
genInfo.SetNodeStorageType(FLAGS_node_storage);
genInfo.m_intermediateDir = base::JoinPath(FLAGS_maps_build_path, "intermediate_data");
genInfo.m_tmpDir = base::JoinPath(FLAGS_maps_build_path, "complex", "tmp");
CHECK(Platform::MkDirRecursively(genInfo.m_tmpDir), ());
generator::hierarchy::PrintFn print;
generator::hierarchy::GetMainTypeFn getMainType = generator::hierarchy::GetMainType;
std::shared_ptr<generator::FilterInterface> filter = std::make_shared<generator::FilterComplex>();
if (FLAGS_debug)
print = static_cast<std::string (*)(generator::HierarchyEntry const &)>(generator::DebugPrint);
else
print = [](auto const & entry) { return generator::hierarchy::HierarchyEntryToCsvString(entry); };
generator::RawGenerator rawGenerator(genInfo, threadsCount);
auto processor = CreateProcessor(generator::ProcessorType::Complex, rawGenerator.GetQueue(),
genInfo.m_intermediateDir, false /* haveBordersForWholeWorld */);
generator::cache::IntermediateDataObjectsCache objectsCache;
auto const cache = std::make_shared<generator::cache::IntermediateData>(objectsCache, genInfo);
auto translator = CreateTranslator(generator::TranslatorType::Complex, processor, cache, genInfo);
auto finalProcessor =
std::make_shared<generator::ComplexFinalProcessor>(genInfo.m_tmpDir, FLAGS_output, threadsCount);
finalProcessor->SetPrintFunction(print);
finalProcessor->SetGetMainTypeFunction(getMainType);
finalProcessor->SetGetNameFunction(generator::hierarchy::GetName);
finalProcessor->SetFilter(filter);
finalProcessor->UseBuildingPartsInfo(genInfo.GetIntermediateFileName(BUILDING_PARTS_MAPPING_FILE));
if (FLAGS_popularity)
{
// Directory FLAGS_maps_build_path must contain 'osm2ft' directory with *.mwm.osm2ft
auto const osm2FtPath = base::JoinPath(FLAGS_maps_build_path, "osm2ft");
// Find directory with *.mwm. Directory FLAGS_maps_build_path must contain directory with *.mwm,
// whose name must consist of six digits.
Platform::FilesList files;
static boost::regex const regexp("[0-9]{6}");
pl.GetFilesByRegExp(FLAGS_maps_build_path, regexp, files);
CHECK_EQUAL(files.size(), 1, ());
auto const mwmPath = base::JoinPath(FLAGS_maps_build_path, files[0]);
finalProcessor->UseCentersEnricher(mwmPath, osm2FtPath);
}
rawGenerator.GenerateCustom(translator, finalProcessor);
CHECK(rawGenerator.Execute(), ());
return EXIT_SUCCESS;
});

View file

@ -0,0 +1,72 @@
#include "generator/complex_loader.hpp"
#include "indexer/classificator.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "coding/csv_reader.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <memory>
#include <mutex>
#include <utility>
namespace generator
{
bool IsComplex(tree_node::types::Ptr<HierarchyEntry> const & tree)
{
size_t constexpr kNumRequiredTypes = 3;
return tree_node::CountIf(tree, [&](auto const & e)
{
auto const & isAttraction = ftypes::AttractionsChecker::Instance();
return isAttraction(e.m_type);
}) >= kNumRequiredTypes;
}
storage::CountryId GetCountry(tree_node::types::Ptr<HierarchyEntry> const & tree)
{
return tree->GetData().m_country;
}
ComplexLoader::ComplexLoader(std::string const & filename)
{
auto trees = hierarchy::LoadHierachy(filename);
base::EraseIf(trees, [](auto const & e) { return !IsComplex(e); });
for (auto const & tree : trees)
m_forests[GetCountry(tree)].Append(tree);
}
tree_node::Forest<HierarchyEntry> const & ComplexLoader::GetForest(storage::CountryId const & country) const
{
static tree_node::Forest<HierarchyEntry> const kEmpty;
auto const it = m_forests.find(country);
return it == std::cend(m_forests) ? kEmpty : it->second;
}
std::unordered_set<CompositeId> ComplexLoader::GetIdsSet() const
{
std::unordered_set<CompositeId> set;
ForEach([&](auto const &, auto const & forest)
{
forest.ForEachTree([&](auto const & tree)
{ tree_node::ForEach(tree, [&](auto const & entry) { set.emplace(entry.m_id); }); });
});
return set;
}
ComplexLoader const & GetOrCreateComplexLoader(std::string const & filename)
{
static std::mutex m;
static std::unordered_map<std::string, ComplexLoader> complexLoaders;
std::lock_guard<std::mutex> lock(m);
auto const it = complexLoaders.find(filename);
if (it != std::cend(complexLoaders))
return it->second;
auto const eIt = complexLoaders.emplace(filename, ComplexLoader(filename));
return eIt.first->second;
}
} // namespace generator

View file

@ -0,0 +1,63 @@
#pragma once
#include "generator/hierarchy_entry.hpp"
#include "indexer/complex/tree_node.hpp"
#include "storage/storage_defines.hpp"
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace generator
{
namespace complex
{
// Feature ids.
using Ids = std::vector<uint32_t>;
} // namespace complex
// ComplexLoader loads complexes from hierarchy source file and provides easy access.
class ComplexLoader
{
public:
explicit ComplexLoader(std::string const & filename);
tree_node::Forest<HierarchyEntry> const & GetForest(storage::CountryId const & country) const;
// fn accepts country name and tree.
template <typename Fn>
void ForEach(Fn && fn) const
{
for (auto const & pair : m_forests)
fn(pair.first, pair.second);
}
std::unordered_set<CompositeId> GetIdsSet() const;
private:
std::unordered_map<storage::CountryId, tree_node::Forest<HierarchyEntry>> m_forests;
};
// Returns true if hierarchy tree is complex; otherwise returns false.
// Complex is defined at https://confluence.mail.ru/display/MAPSME/Complexes.
bool IsComplex(tree_node::types::Ptr<HierarchyEntry> const & tree);
// Returns country id of hierarchy.
storage::CountryId GetCountry(tree_node::types::Ptr<HierarchyEntry> const & tree);
// Returns initilized ComplexLoader by filename.
// It loads only at the time of the first call.
ComplexLoader const & GetOrCreateComplexLoader(std::string const & filename);
template <typename Fn>
tree_node::Forest<complex::Ids> TraformToIdsForest(tree_node::Forest<HierarchyEntry> const & forest, Fn && fn)
{
tree_node::Forest<complex::Ids> res;
forest.ForEachTree([&](auto const & tree) { res.Append(tree_node::TransformToTree(tree, std::forward<Fn>(fn))); });
return res;
}
} // namespace generator

View file

@ -0,0 +1,50 @@
#include "generator/composite_id.hpp"
#include <sstream>
#include <tuple>
namespace generator
{
CompositeId::CompositeId(std::string const & str)
{
std::stringstream stream(str);
stream.exceptions(std::ios::failbit);
stream >> m_mainId;
stream >> m_additionalId;
}
CompositeId::CompositeId(base::GeoObjectId mainId, base::GeoObjectId additionalId)
: m_mainId(mainId)
, m_additionalId(additionalId)
{}
CompositeId::CompositeId(base::GeoObjectId mainId) : CompositeId(mainId, base::GeoObjectId() /* additionalId */) {}
bool CompositeId::operator<(CompositeId const & other) const
{
return std::tie(m_mainId, m_additionalId) < std::tie(other.m_mainId, other.m_additionalId);
}
bool CompositeId::operator==(CompositeId const & other) const
{
return std::tie(m_mainId, m_additionalId) == std::tie(other.m_mainId, other.m_additionalId);
}
bool CompositeId::operator!=(CompositeId const & other) const
{
return !(*this == other);
}
std::string CompositeId::ToString() const
{
std::stringstream stream;
stream.exceptions(std::ios::failbit);
stream << m_mainId << " " << m_additionalId;
return stream.str();
}
std::string DebugPrint(CompositeId const & id)
{
return DebugPrint(id.m_mainId) + "|" + DebugPrint(id.m_additionalId);
}
} // namespace generator

View file

@ -0,0 +1,39 @@
#pragma once
#include "base/geo_object_id.hpp"
#include "base/math.hpp"
#include <string>
namespace generator
{
// This struct represents a composite Id.
// This will be useful if we want to distinguish between polygons in a multipolygon.
struct CompositeId
{
CompositeId() = default;
explicit CompositeId(std::string const & str);
explicit CompositeId(base::GeoObjectId mainId, base::GeoObjectId additionalId);
explicit CompositeId(base::GeoObjectId mainId);
bool operator<(CompositeId const & other) const;
bool operator==(CompositeId const & other) const;
bool operator!=(CompositeId const & other) const;
std::string ToString() const;
base::GeoObjectId m_mainId;
base::GeoObjectId m_additionalId;
};
std::string DebugPrint(CompositeId const & id);
} // namespace generator
namespace std
{
template <>
struct hash<generator::CompositeId>
{
size_t operator()(generator::CompositeId const & id) const { return math::Hash(id.m_mainId, id.m_additionalId); }
};
} // namespace std

View file

@ -0,0 +1,192 @@
#include "generator/cross_mwm_osm_ways_collector.hpp"
#include "generator/feature_builder.hpp"
#include "generator/final_processor_utils.hpp"
#include "generator/osm_element.hpp"
#include "routing/routing_helpers.hpp"
#include "platform/platform.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include <utility>
namespace generator
{
// CrossMwmOsmWaysCollector ------------------------------------------------------------------------
CrossMwmOsmWaysCollector::CrossMwmOsmWaysCollector(std::string intermediateDir, AffiliationInterfacePtr affiliation)
: m_intermediateDir(std::move(intermediateDir))
, m_affiliation(std::move(affiliation))
{}
std::shared_ptr<CollectorInterface> CrossMwmOsmWaysCollector::Clone(IDRInterfacePtr const &) const
{
return std::make_shared<CrossMwmOsmWaysCollector>(m_intermediateDir, m_affiliation);
}
void CrossMwmOsmWaysCollector::CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & element)
{
if (element.m_type != OsmElement::EntityType::Way)
return;
if (!routing::IsRoad(fb.GetTypes()))
return;
auto const & affiliations = m_affiliation->GetAffiliations(fb);
if (affiliations.empty())
return;
auto const & featurePoints = fb.GetOuterGeometry();
std::map<std::string, std::vector<bool>> featurePointsEntriesToMwm;
for (auto const & mwmName : affiliations)
featurePointsEntriesToMwm[mwmName] = std::vector<bool>(featurePoints.size(), false);
std::vector<size_t> pointsAffiliationsNumber(featurePoints.size());
for (size_t pointNumber = 0; pointNumber < featurePoints.size(); ++pointNumber)
{
auto const & point = featurePoints[pointNumber];
auto const & pointAffiliations = m_affiliation->GetAffiliations(point);
for (auto const & mwmName : pointAffiliations)
{
// Skip mwms which are not present in the map: these are GetAffiliations() false positives.
auto it = featurePointsEntriesToMwm.find(mwmName);
if (it == featurePointsEntriesToMwm.end())
continue;
it->second[pointNumber] = true;
}
// TODO (@gmoryes)
// Uncomment this check after: https://github.com/mapsme/omim/pull/10996
// It could happend when point places between mwm's borders and doesn't belong to any of them.
// CHECK_GREATER(pointAffiliations.size(), 0, ());
pointsAffiliationsNumber[pointNumber] = pointAffiliations.size();
}
for (auto const & item : featurePointsEntriesToMwm)
{
auto const & mwmName = item.first;
auto const & entries = item.second;
std::vector<CrossMwmInfo::SegmentInfo> crossMwmSegments;
bool prevPointIn = entries[0];
for (size_t i = 1; i < entries.size(); ++i)
{
bool curPointIn = entries[i];
// We must be sure below that either both points belong to mwm either one of them belongs.
if (!curPointIn && !prevPointIn)
continue;
// If pointsAffiliationsNumber[i] is more than 1, that means point lies on the mwms' borders
// And belongs to both. So we consider such segment as cross mwm segment.
// So the condition that segment certainly lies inside of mwm is:
// both points inside and both points belong to only this mwm.
if (prevPointIn && curPointIn && pointsAffiliationsNumber[i] == 1 && pointsAffiliationsNumber[i - 1] == 1)
continue;
bool forwardIsEnter;
if (prevPointIn != curPointIn)
forwardIsEnter = curPointIn;
else if (pointsAffiliationsNumber[i - 1] != 1)
forwardIsEnter = curPointIn;
else if (pointsAffiliationsNumber[i] != 1)
forwardIsEnter = !prevPointIn;
else
UNREACHABLE();
prevPointIn = curPointIn;
crossMwmSegments.emplace_back(i - 1 /* segmentId */, forwardIsEnter);
}
if (crossMwmSegments.empty())
return;
m_mwmToCrossMwmOsmIds[mwmName].emplace_back(element.m_id, std::move(crossMwmSegments));
}
}
void CrossMwmOsmWaysCollector::Save()
{
auto const & crossMwmOsmWaysDir = base::JoinPath(m_intermediateDir, CROSS_MWM_OSM_WAYS_DIR);
CHECK(Platform::MkDirChecked(crossMwmOsmWaysDir), (crossMwmOsmWaysDir));
for (auto const & item : m_mwmToCrossMwmOsmIds)
{
auto const & mwmName = item.first;
auto const & waysInfo = item.second;
auto const & pathToCrossMwmOsmIds = base::JoinPath(crossMwmOsmWaysDir, mwmName);
std::ofstream output;
output.exceptions(std::fstream::failbit | std::fstream::badbit);
output.open(pathToCrossMwmOsmIds);
for (auto const & wayInfo : waysInfo)
CrossMwmInfo::Dump(wayInfo, output);
}
}
void CrossMwmOsmWaysCollector::OrderCollectedData()
{
auto const & crossMwmOsmWaysDir = base::JoinPath(m_intermediateDir, CROSS_MWM_OSM_WAYS_DIR);
for (auto const & item : m_mwmToCrossMwmOsmIds)
OrderTextFileByLine(base::JoinPath(crossMwmOsmWaysDir, item.first));
}
void CrossMwmOsmWaysCollector::MergeInto(CrossMwmOsmWaysCollector & collector) const
{
for (auto const & item : m_mwmToCrossMwmOsmIds)
{
auto const & mwmName = item.first;
auto const & osmIds = item.second;
auto & otherOsmIds = collector.m_mwmToCrossMwmOsmIds[mwmName];
otherOsmIds.insert(otherOsmIds.end(), osmIds.cbegin(), osmIds.cend());
}
}
// CrossMwmOsmWaysCollector::Info ------------------------------------------------------------------
bool CrossMwmOsmWaysCollector::CrossMwmInfo::operator<(CrossMwmInfo const & rhs) const
{
return m_osmId < rhs.m_osmId;
}
// static
void CrossMwmOsmWaysCollector::CrossMwmInfo::Dump(CrossMwmInfo const & info, std::ofstream & output)
{
output << base::MakeOsmWay(info.m_osmId) << " " << info.m_crossMwmSegments.size() << " ";
for (auto const & segmentInfo : info.m_crossMwmSegments)
output << segmentInfo.m_segmentId << " " << segmentInfo.m_forwardIsEnter << " ";
output << std::endl;
}
// static
std::set<CrossMwmOsmWaysCollector::CrossMwmInfo> CrossMwmOsmWaysCollector::CrossMwmInfo::LoadFromFileToSet(
std::string const & path)
{
std::ifstream input(path);
if (!input)
{
LOG(LWARNING, ("No info about cross mwm ways:", path));
return {};
}
input.exceptions(std::fstream::badbit);
std::set<CrossMwmInfo> result;
uint64_t osmId;
size_t segmentsNumber;
while (input >> osmId >> segmentsNumber)
{
std::vector<SegmentInfo> segments;
CHECK_NOT_EQUAL(segmentsNumber, 0, ());
segments.resize(segmentsNumber);
for (size_t j = 0; j < segmentsNumber; ++j)
input >> segments[j].m_segmentId >> segments[j].m_forwardIsEnter;
result.emplace(osmId, std::move(segments));
}
return result;
}
} // namespace generator

View file

@ -0,0 +1,67 @@
#pragma once
#include "generator/affiliation.hpp"
#include "generator/collector_interface.hpp"
#include "generator/generate_info.hpp"
#include <cstdint>
#include <fstream>
#include <map>
#include <memory>
#include <set>
#include <string>
namespace generator
{
class CrossMwmOsmWaysCollector : public generator::CollectorInterface
{
public:
struct CrossMwmInfo
{
struct SegmentInfo
{
SegmentInfo() = default;
SegmentInfo(uint32_t id, bool forwardIsEnter) : m_segmentId(id), m_forwardIsEnter(forwardIsEnter) {}
uint32_t m_segmentId = 0;
bool m_forwardIsEnter = false;
};
explicit CrossMwmInfo(uint64_t osmId) : m_osmId(osmId) {}
CrossMwmInfo(uint64_t osmId, std::vector<SegmentInfo> crossMwmSegments)
: m_osmId(osmId)
, m_crossMwmSegments(std::move(crossMwmSegments))
{}
bool operator<(CrossMwmInfo const & rhs) const;
static void Dump(CrossMwmInfo const & info, std::ofstream & output);
static std::set<CrossMwmInfo> LoadFromFileToSet(std::string const & path);
uint64_t m_osmId;
std::vector<SegmentInfo> m_crossMwmSegments;
};
CrossMwmOsmWaysCollector(std::string intermediateDir, AffiliationInterfacePtr affiliation);
// generator::CollectorInterface overrides:
// @{
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & = {}) const override;
void CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & element) override;
IMPLEMENT_COLLECTOR_IFACE(CrossMwmOsmWaysCollector);
void MergeInto(CrossMwmOsmWaysCollector & collector) const;
// @}
protected:
// generator::CollectorInterface overrides:
void Save() override;
void OrderCollectedData() override;
private:
std::string m_intermediateDir;
std::map<std::string, std::vector<CrossMwmInfo>> m_mwmToCrossMwmOsmIds;
AffiliationInterfacePtr m_affiliation;
};
} // namespace generator

View file

@ -0,0 +1,242 @@
#include "generator/descriptions_section_builder.hpp"
#include "generator/utils.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_processor.hpp"
#include "platform/platform.hpp"
#include "coding/file_writer.hpp"
#include "coding/files_container.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "base/file_name_utils.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include "defines.hpp"
#include <fstream>
#include <iterator>
#include <sstream>
namespace generator
{
namespace
{
bool IsValidDir(std::string const & path)
{
return Platform::IsFileExistsByFullPath(path) && Platform::IsDirectory(path);
}
} // namespace
WikidataHelper::WikidataHelper(std::string const & mwmPath, std::string const & idToWikidataPath)
{
if (idToWikidataPath.empty())
return;
std::string const osmIdsToFeatureIdsPath = mwmPath + OSM2FEATURE_FILE_EXTENSION;
if (!ParseFeatureIdToOsmIdMapping(osmIdsToFeatureIdsPath, m_featureIdToOsmId))
LOG(LCRITICAL, ("Mapping parse error for file ", osmIdsToFeatureIdsPath));
std::ifstream stream(idToWikidataPath);
if (!stream)
LOG(LERROR, ("File ", idToWikidataPath, " not found. Consider skipping Descriptions stage."));
stream.exceptions(std::fstream::badbit);
uint64_t id;
std::string wikidataId;
while (stream)
{
stream >> id >> wikidataId;
strings::Trim(wikidataId);
m_osmIdToWikidataId.emplace(base::GeoObjectId(id), wikidataId);
}
}
std::optional<std::string> WikidataHelper::GetWikidataId(uint32_t featureId) const
{
auto const itFeatureIdToOsmId = m_featureIdToOsmId.find(featureId);
if (itFeatureIdToOsmId == std::end(m_featureIdToOsmId))
return {};
auto const osmId = itFeatureIdToOsmId->second;
auto const itOsmIdToWikidataId = m_osmIdToWikidataId.find(osmId);
return itOsmIdToWikidataId == std::end(m_osmIdToWikidataId) ? std::optional<std::string>()
: itOsmIdToWikidataId->second;
}
std::string DescriptionsCollectionBuilderStat::LangStatisticsToString() const
{
std::stringstream stream;
stream << "Language statistics - ";
if (m_langsStat.empty())
stream << "(empty)";
for (size_t code = 0; code < m_langsStat.size(); ++code)
{
if (m_langsStat[code] == 0)
continue;
stream << StringUtf8Multilang::GetLangByCode(static_cast<int8_t>(code)) << ":" << m_langsStat[code] << " ";
}
return stream.str();
}
void DescriptionsCollector::operator()(FeatureType & ft, uint32_t featureId)
{
// auto const & attractionsChecker = ftypes::AttractionsChecker::Instance();
// if (!attractionsChecker(ft))
// return;
(*this)(ft.GetMetadata().GetWikiURL(), featureId);
}
void DescriptionsCollector::operator()(std::string const & wikiUrl, uint32_t featureId)
{
descriptions::LangMeta langsMeta;
size_t size = 0;
// First try to get wikipedia url.
if (!wikiUrl.empty())
size = FindPageAndFill(MakePathForWikipedia(m_wikipediaDir, wikiUrl), langsMeta);
// Second try to get wikidata id.
bool const isWikiUrl = !langsMeta.empty();
if (!isWikiUrl)
{
auto const wikidataId = m_wikidataHelper.GetWikidataId(featureId);
if (wikidataId)
size = FindPageAndFill(MakePathForWikidata(m_wikipediaDir, *wikidataId), langsMeta);
}
if (langsMeta.empty())
return;
if (size > 0)
{
// Add only new loaded pages (not from cache).
m_stat.AddSize(size);
m_stat.IncPage();
}
if (isWikiUrl)
m_stat.IncNumberWikipediaUrls();
else
m_stat.IncNumberWikidataIds();
m_collection.m_features.push_back({featureId, std::move(langsMeta)});
}
// static
std::string DescriptionsCollector::MakePathForWikipedia(std::string const & wikipediaDir, std::string wikipediaUrl)
{
strings::Trim(wikipediaUrl);
strings::ReplaceFirst(wikipediaUrl, "http://", "");
strings::ReplaceFirst(wikipediaUrl, "https://", "");
if (wikipediaUrl.ends_with('/'))
wikipediaUrl.pop_back();
return base::JoinPath(wikipediaDir, wikipediaUrl);
}
// static
std::string DescriptionsCollector::MakePathForWikidata(std::string const & wikipediaDir, std::string const & wikidataId)
{
return base::JoinPath(wikipediaDir, "wikidata", wikidataId);
}
// static
std::string DescriptionsCollector::FillStringFromFile(std::string const & fullPath)
{
std::ifstream stream;
stream.exceptions(std::fstream::failbit | std::fstream::badbit);
stream.open(fullPath);
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
}
size_t DescriptionsCollector::FindPageAndFill(std::string const & path, descriptions::LangMeta & meta)
{
size_t size = 0;
if (path.empty() || !IsValidDir(path))
return size;
Platform::FilesList filelist;
Platform::GetFilesByExt(path, ".html", filelist);
for (auto const & filename : filelist)
{
auto const lang = base::FilenameWithoutExt(filename);
auto const code = StringUtf8Multilang::GetLangIndex(lang);
if (code == StringUtf8Multilang::kUnsupportedLanguageCode)
{
LOG(LWARNING, (lang, "is an unsupported language."));
continue;
}
auto res = m_path2Index.try_emplace(base::JoinPath(path, filename), 0);
if (res.second)
{
auto const & filePath = res.first->first;
auto content = FillStringFromFile(filePath);
size_t const sz = content.size();
if (sz == 0)
{
LOG(LWARNING, ("Empty descriptions file:", filePath));
m_path2Index.erase(res.first);
continue;
}
auto & strings = m_collection.m_strings;
res.first->second = strings.size();
strings.push_back(std::move(content));
size += sz;
}
m_stat.IncCode(code);
meta.emplace_back(code, res.first->second);
}
return size;
}
// static
void DescriptionsSectionBuilder::CollectAndBuild(std::string const & wikipediaDir, std::string const & mwmFile,
std::string const & idToWikidataPath)
{
DescriptionsCollector collector(wikipediaDir, mwmFile, idToWikidataPath);
feature::ForEachFeature(mwmFile, collector);
BuildSection(mwmFile, collector);
}
// static
void DescriptionsSectionBuilder::BuildSection(std::string const & mwmFile, DescriptionsCollector & collector)
{
auto const & stat = collector.m_stat;
size_t const size = stat.GetTotalSize();
LOG(LINFO, ("Wiki descriptions for", mwmFile, "Wikipedia urls =", stat.GetNumberOfWikipediaUrls(),
"Wikidata ids =", stat.GetNumberOfWikidataIds(), "Total number of pages =", stat.GetNumberOfPages(),
"Total size of added pages (before writing to section) =", size, "bytes"));
if (size == 0)
{
LOG(LWARNING, ("Section", DESCRIPTIONS_FILE_TAG, "was not created."));
return;
}
FilesContainerW cont(mwmFile, FileWriter::OP_WRITE_EXISTING);
/// @todo Should we override FilesContainerWriter::GetSize() to return local size (not container size)?
uint64_t sectionSize;
{
auto writer = cont.GetWriter(DESCRIPTIONS_FILE_TAG);
sectionSize = writer->Pos();
descriptions::Serializer serializer(std::move(collector.m_collection));
serializer.Serialize(*writer);
sectionSize = writer->Pos() - sectionSize;
}
LOG(LINFO, ("Section", DESCRIPTIONS_FILE_TAG, "is built.", "Disk size =", sectionSize, "bytes",
"Compression ratio =", size / double(sectionSize), stat.LangStatisticsToString()));
}
} // namespace generator

View file

@ -0,0 +1,107 @@
#pragma once
#include "descriptions/serdes.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "base/assert.hpp"
#include "base/geo_object_id.hpp"
#include <array>
#include <string>
#include <unordered_map>
class FeatureType;
namespace generator
{
class WikidataHelper
{
public:
WikidataHelper(std::string const & mwmPath, std::string const & idToWikidataPath);
std::optional<std::string> GetWikidataId(uint32_t featureId) const;
private:
std::unordered_map<uint32_t, base::GeoObjectId> m_featureIdToOsmId;
std::unordered_map<base::GeoObjectId, std::string> m_osmIdToWikidataId;
};
class DescriptionsCollectionBuilderStat
{
public:
using LangStatistics = std::array<size_t, StringUtf8Multilang::kMaxSupportedLanguages>;
DescriptionsCollectionBuilderStat()
{
CHECK_EQUAL(m_langsStat.size(), StringUtf8Multilang::kMaxSupportedLanguages, ());
}
std::string LangStatisticsToString() const;
void IncCode(int8_t code)
{
CHECK(code < StringUtf8Multilang::kMaxSupportedLanguages, (code));
CHECK(code >= 0, (code));
m_langsStat[static_cast<size_t>(code)] += 1;
}
void AddSize(size_t size) { m_size += size; }
void IncPage() { ++m_pages; }
void IncNumberWikipediaUrls() { ++m_numberWikipediaUrls; }
void IncNumberWikidataIds() { ++m_numberWikidataIds; }
size_t GetTotalSize() const { return m_size; }
size_t GetNumberOfPages() const { return m_pages; }
size_t GetNumberOfWikipediaUrls() const { return m_numberWikipediaUrls; }
size_t GetNumberOfWikidataIds() const { return m_numberWikidataIds; }
LangStatistics const & GetLangStatistics() const { return m_langsStat; }
private:
size_t m_size = 0;
size_t m_pages = 0;
size_t m_numberWikipediaUrls = 0;
size_t m_numberWikidataIds = 0;
LangStatistics m_langsStat = {};
};
class DescriptionsCollector
{
public:
DescriptionsCollector(std::string const & wikipediaDir, std::string const & mwmFile,
std::string const & idToWikidataPath = {})
: m_wikidataHelper(mwmFile, idToWikidataPath)
, m_wikipediaDir(wikipediaDir)
, m_mwmFile(mwmFile)
{}
void operator()(FeatureType & ft, uint32_t featureId);
void operator()(std::string const & wikiUrl, uint32_t featureId);
static std::string MakePathForWikipedia(std::string const & wikipediaDir, std::string wikipediaUrl);
static std::string MakePathForWikidata(std::string const & wikipediaDir, std::string const & wikidataId);
static std::string FillStringFromFile(std::string const & fullPath);
/// @return Aggregated loaded from disk page's size.
size_t FindPageAndFill(std::string const & wikipediaUrl, descriptions::LangMeta & meta);
public:
DescriptionsCollectionBuilderStat m_stat;
descriptions::DescriptionsCollection m_collection;
private:
std::unordered_map<std::string, descriptions::StringIndex> m_path2Index;
WikidataHelper m_wikidataHelper;
std::string m_wikipediaDir;
std::string m_mwmFile;
};
struct DescriptionsSectionBuilder
{
/// @param[in] idToWikidataPath Maybe empty.
static void CollectAndBuild(std::string const & wikipediaDir, std::string const & mwmFile,
std::string const & idToWikidataPath);
static void BuildSection(std::string const & mwmFile, DescriptionsCollector & collector);
};
} // namespace generator

210
generator/dumper.cpp Normal file
View file

@ -0,0 +1,210 @@
#include "generator/dumper.hpp"
#include "search/search_index_values.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_processor.hpp"
#include "indexer/search_string_utils.hpp"
#include "indexer/trie_reader.hpp"
#include "coding/string_utf8_multilang.hpp"
#include <algorithm>
#include <functional>
#include <iostream>
#include <map>
#include <vector>
#include "defines.hpp"
namespace features_dumper
{
using std::cout, std::endl, std::pair, std::string, std::vector;
template <typename Value>
struct SearchTokensCollector
{
SearchTokensCollector() : m_currentS(), m_currentCount(0) {}
void operator()(strings::UniString const & s, Value const & /* value */)
{
if (m_currentS != s)
{
if (m_currentCount > 0)
m_tokens.emplace_back(m_currentCount, m_currentS);
m_currentS = s;
m_currentCount = 0;
}
++m_currentCount;
}
void Finish()
{
if (m_currentCount > 0)
m_tokens.emplace_back(m_currentCount, m_currentS);
sort(m_tokens.begin(), m_tokens.end(), std::greater<pair<uint32_t, strings::UniString>>());
}
vector<pair<uint32_t, strings::UniString>> m_tokens;
strings::UniString m_currentS;
uint32_t m_currentCount;
};
class TypesCollector
{
vector<uint32_t> m_currFeatureTypes;
public:
typedef std::map<vector<uint32_t>, size_t> value_type;
value_type m_stats;
size_t m_namesCount;
size_t m_totalCount;
TypesCollector() : m_namesCount(0), m_totalCount(0) {}
void operator()(FeatureType & f, uint32_t)
{
++m_totalCount;
auto const primary = f.GetReadableName();
if (!primary.empty())
++m_namesCount;
m_currFeatureTypes.clear();
f.ForEachType([this](uint32_t type) { m_currFeatureTypes.push_back(type); });
CHECK(!m_currFeatureTypes.empty(), ("Feature without any type???"));
auto found = m_stats.insert(make_pair(m_currFeatureTypes, 1));
if (!found.second)
found.first->second++;
}
};
template <class T>
static bool SortFunc(T const & first, T const & second)
{
return first.second > second.second;
}
void DumpTypes(string const & fPath)
{
TypesCollector doClass;
feature::ForEachFeature(fPath, doClass);
typedef pair<vector<uint32_t>, size_t> stats_elem_type;
typedef vector<stats_elem_type> vec_to_sort;
vec_to_sort vecToSort(doClass.m_stats.begin(), doClass.m_stats.end());
sort(vecToSort.begin(), vecToSort.end(), &SortFunc<stats_elem_type>);
for (auto const & el : vecToSort)
{
cout << el.second << " ";
for (uint32_t i : el.first)
cout << classif().GetFullObjectName(i) << " ";
cout << endl;
}
cout << "Total features: " << doClass.m_totalCount << endl;
cout << "Features with names: " << doClass.m_namesCount << endl;
}
///////////////////////////////////////////////////////////////////
typedef std::map<int8_t, std::map<strings::UniString, pair<unsigned int, string>>> TokensContainerT;
class PrefixesCollector
{
public:
TokensContainerT m_stats;
void operator()(int8_t langCode, std::string_view name)
{
CHECK(!name.empty(), ("Feature name is empty"));
auto const tokens = search::NormalizeAndTokenizeString(name);
for (size_t i = 1; i < tokens.size(); ++i)
{
strings::UniString s;
for (size_t numTokens = 0; numTokens < i; ++numTokens)
{
s.append(tokens[numTokens].begin(), tokens[numTokens].end());
s.push_back(' ');
}
auto [iter, found] = m_stats[langCode].emplace(s, std::make_pair(1U, name));
if (!found)
iter->second.first++;
}
}
void operator()(FeatureType & f, uint32_t) { f.ForEachName(*this); }
};
static size_t constexpr MIN_OCCURRENCE = 3;
void Print(int8_t langCode, TokensContainerT::mapped_type const & container)
{
typedef pair<strings::UniString, pair<unsigned int, string>> NameElemT;
typedef vector<NameElemT> VecToSortT;
VecToSortT v(container.begin(), container.end());
std::sort(v.begin(), v.end(), &SortFunc<NameElemT>);
// do not display prefixes with low occurrences
if (v[0].second.first > MIN_OCCURRENCE)
{
cout << "Language code: " << StringUtf8Multilang::GetLangByCode(langCode) << endl;
for (auto const & el : v)
{
if (el.second.first <= MIN_OCCURRENCE)
break;
cout << el.second.first << " " << strings::ToUtf8(el.first);
cout << " \"" << el.second.second << "\"" << endl;
}
}
}
void DumpPrefixes(string const & fPath)
{
PrefixesCollector doClass;
feature::ForEachFeature(fPath, doClass);
for (auto const & [langCode, container] : doClass.m_stats)
Print(langCode, container);
}
void DumpSearchTokens(string const & fPath, size_t maxTokensToShow)
{
using Value = Uint64IndexValue;
FilesContainerR container(std::make_unique<FileReader>(fPath));
feature::DataHeader header(container);
auto const trieRoot = trie::ReadTrie<ModelReaderPtr, ValueList<Value>>(container.GetReader(SEARCH_INDEX_FILE_TAG),
SingleValueSerializer<Value>());
SearchTokensCollector<Value> f;
trie::ForEachRef(*trieRoot, f, strings::UniString());
f.Finish();
for (size_t i = 0; i < std::min(maxTokensToShow, f.m_tokens.size()); ++i)
{
auto const & s = f.m_tokens[i].second;
cout << f.m_tokens[i].first << " " << strings::ToUtf8(s) << endl;
}
}
void DumpFeatureNames(string const & fPath, string const & lang)
{
int8_t const langIndex = StringUtf8Multilang::GetLangIndex(lang);
feature::ForEachFeature(fPath, [&](FeatureType & f, uint32_t)
{
f.ForEachName([&](int8_t langCode, std::string_view name)
{
CHECK(!name.empty(), ("Feature name is empty"));
if (langIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
cout << StringUtf8Multilang::GetLangByCode(langCode) << ' ' << name << endl;
else if (langCode == langIndex)
cout << name << endl;
});
});
}
} // namespace features_dumper

19
generator/dumper.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace features_dumper
{
void DumpTypes(std::string const & fPath);
void DumpPrefixes(std::string const & fPath);
// Writes top maxTokensToShow tokens sorted by their
// frequency, i.e. by the number of features in
// an mwm that contain the token in their name.
void DumpSearchTokens(std::string const & fPath, size_t maxTokensToShow);
// Writes the names of all features in the locale provided by lang
// (e.g. "en", "ru", "sv"). If the locale is not recognized, writes all names
// preceded by their locales.
void DumpFeatureNames(std::string const & fPath, std::string const & lang);
} // namespace features_dumper

View file

@ -0,0 +1,20 @@
#pragma once
#include <memory>
#include <type_traits>
namespace generator
{
template <typename T, typename... Args>
std::enable_if_t<std::is_constructible<T, Args...>::value, std::shared_ptr<T>> create(Args &&... args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
}
// impossible to construct
template <typename T, typename... Args>
std::enable_if_t<!std::is_constructible<T, Args...>::value, std::shared_ptr<T>> create(Args &&...)
{
return nullptr;
}
} // namespace generator

View file

@ -0,0 +1,774 @@
#include "generator/feature_builder.hpp"
#include "routing/routing_helpers.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "indexer/search_string_utils.hpp"
#include "coding/bit_streams.hpp"
#include "coding/byte_stream.hpp"
#include "coding/geometry_coding.hpp"
#include "coding/read_write_utils.hpp"
#include "geometry/region2d.hpp"
#include "base/logging.hpp"
#include "base/math.hpp"
#include <algorithm>
#include <vector>
namespace feature
{
namespace
{
bool IsEqual(double d1, double d2)
{
return AlmostEqualAbs(d1, d2, kMwmPointAccuracy);
}
bool IsEqual(m2::PointD const & p1, m2::PointD const & p2)
{
return p1.EqualDxDy(p2, kMwmPointAccuracy);
}
bool IsEqual(m2::RectD const & r1, m2::RectD const & r2)
{
return (IsEqual(r1.minX(), r2.minX()) && IsEqual(r1.minY(), r2.minY()) && IsEqual(r1.maxX(), r2.maxX()) &&
IsEqual(r1.maxY(), r2.maxY()));
}
bool IsEqual(std::vector<m2::PointD> const & v1, std::vector<m2::PointD> const & v2)
{
return equal(cbegin(v1), cend(v1), cbegin(v2), cend(v2),
[](m2::PointD const & p1, m2::PointD const & p2) { return IsEqual(p1, p2); });
}
} // namespace
FeatureBuilder::FeatureBuilder() : m_coastCell(-1) {}
bool FeatureBuilder::IsGeometryClosed() const
{
PointSeq const & poly = GetOuterGeometry();
return (poly.size() > 2 && poly.front() == poly.back());
}
m2::PointD FeatureBuilder::GetGeometryCenter(PointSeq const & poly)
{
m2::PointD ret(0.0, 0.0);
size_t const count = poly.size();
for (size_t i = 0; i < count; ++i)
ret += poly[i];
return ret / count;
}
m2::PointD FeatureBuilder::GetKeyPoint() const
{
switch (GetGeomType())
{
case GeomType::Point: return m_center;
case GeomType::Line:
case GeomType::Area: return GetGeometryCenter();
default: CHECK(false, ()); return m2::PointD();
}
}
void FeatureBuilder::SetCenter(m2::PointD const & p)
{
ResetGeometry();
m_center = p;
m_params.SetGeomType(GeomType::Point);
m_limitRect.Add(p);
}
void FeatureBuilder::AssignPoints(PointSeq points)
{
ResetGeometry();
CalcRect(points, m_limitRect);
m_polygons.emplace_back(std::move(points));
}
void FeatureBuilder::SetLinear(bool reverseGeometry)
{
m_params.SetGeomType(GeomType::Line);
if (reverseGeometry)
{
auto & cont = m_polygons.front();
ASSERT(!cont.empty(), ());
reverse(cont.begin(), cont.end());
}
}
void FeatureBuilder::AssignArea(PointSeq && outline, Geometry const & holes)
{
AssignPoints(std::move(outline));
if (holes.empty())
return;
PointSeq const & poly = GetOuterGeometry();
m2::Region<m2::PointD> rgn(poly.begin(), poly.end());
for (PointSeq const & points : holes)
{
ASSERT(!points.empty(), (*this));
size_t j = 0;
size_t const count = points.size();
for (; j < count; ++j)
if (!rgn.Contains(points[j]))
break;
if (j == count)
m_polygons.push_back(points);
}
}
void FeatureBuilder::AddPolygon(PointSeq && poly)
{
// check for closing
if (poly.size() < 3)
return;
if (poly.front() != poly.back())
poly.push_back(poly.front());
CalcRect(poly, m_limitRect);
m_polygons.push_back(std::move(poly));
}
void FeatureBuilder::ResetGeometry()
{
m_polygons.clear();
m_limitRect.MakeEmpty();
}
bool FeatureBuilder::RemoveInvalidTypes()
{
if (!m_params.FinishAddingTypes())
return false;
return RemoveUselessTypes(m_params.m_types, m_params.GetGeomType(), m_params.IsEmptyNames());
}
TypesHolder FeatureBuilder::GetTypesHolder() const
{
CHECK(IsValid(), (*this));
TypesHolder holder(m_params.GetGeomType());
for (auto const t : m_params.m_types)
holder.Add(t);
return holder;
}
bool FeatureBuilder::PreSerialize()
{
/// @todo Seems like we should put CHECK(IsValid()) here.
if (!m_params.IsValid())
return false;
auto const checkHouseNumber = [this]()
{
if (!m_params.house.IsEmpty())
{
// Hack/Patch here. Convert non-number into default name for the search index.
// Happens with building-address: https://github.com/organicmaps/organicmaps/issues/4994
/// @todo Refactor to store raw name: and addr: values in FeatureBuilderParams and make one
/// _finalization_ function here.
auto const & hn = m_params.house.Get();
if (FeatureParams::LooksLikeHouseNumber(hn) || !m_params.SetDefaultNameIfEmpty(hn))
return true;
else
m_params.house.Clear();
}
return false;
};
// Conform serialization logic (see HeaderMask::HEADER_MASK_HAS_ADDINFO):
// - rank (city) is stored only for Point
// - ref (road number, address range) is stored only for Line
// - house is stored for PointEx and Area
switch (m_params.GetGeomType())
{
case GeomType::Point:
if (checkHouseNumber())
{
// Store house number like HeaderGeomType::PointEx.
m_params.SetGeomTypePointEx();
m_params.rank = 0;
}
if (!m_params.ref.empty())
{
auto const & types = GetTypes();
if (ftypes::IsMotorwayJunctionChecker::Instance()(types) ||
(m_params.name.IsEmpty() &&
(ftypes::IsPostPoiChecker::Instance()(types) || ftypes::IsRailwaySubwayEntranceChecker::Instance()(types) ||
ftypes::IsEntranceChecker::Instance()(types) || ftypes::IsAerowayGateChecker::Instance()(types) ||
ftypes::IsPlatformChecker::Instance()(types))))
{
m_params.name.AddString(StringUtf8Multilang::kDefaultCode, m_params.ref);
}
else if (!m_params.name.IsEmpty() && ftypes::IsRailwaySubwayEntranceChecker::Instance()(types))
{
StringUtf8Multilang nameWithRef;
m_params.name.ForEach([&nameWithRef, this](int8_t code, std::string_view name)
{
nameWithRef.AddString(code, std::string(name) + " (" + m_params.ref + ")");
});
m_params.name = std::move(nameWithRef);
}
else if (ftypes::IsEmergencyAccessPointChecker::Instance()(types))
{
m_params.name.Clear();
m_params.name.AddString(StringUtf8Multilang::kDefaultCode, m_params.ref);
}
m_params.ref.clear();
}
break;
case GeomType::Line:
{
// Refs are used for road and piste numbers and house number ranges.
if (!routing::IsRoad(GetTypes()) && !ftypes::IsAddressInterpolChecker::Instance()(GetTypes()) &&
!ftypes::IsPisteChecker::Instance()(GetTypes()))
m_params.ref.clear();
m_params.rank = 0;
m_params.house.Clear();
break;
}
case GeomType::Area:
checkHouseNumber();
if (!m_params.ref.empty())
{
auto const & types = GetTypes();
if (m_params.name.IsEmpty() && (ftypes::IsPlatformChecker::Instance()(types)))
m_params.name.AddString(StringUtf8Multilang::kDefaultCode, m_params.ref);
m_params.ref.clear();
}
m_params.rank = 0;
break;
default: return false;
}
// Stats shows that 1706197 POIs out of 2258011 have name == brand.
// Can remove duplicates, since we use "brand" only in search.
/// @todo Remove, when we will make valid localized brands and store brand-id instead raw name.
auto & meta = GetMetadata();
auto const brand = meta.Get(Metadata::FMD_BRAND);
if (!brand.empty())
{
m_params.name.ForEach([brand, &meta](int8_t, std::string_view name)
{
if (brand == name)
{
meta.Drop(Metadata::FMD_BRAND);
return base::ControlFlow::Break;
}
return base::ControlFlow::Continue;
});
}
return true;
}
bool FeatureBuilder::PreSerializeAndRemoveUselessNamesForIntermediate()
{
if (!PreSerialize())
return false;
// Clear name for features with invisible texts.
// AlexZ: Commented this line to enable captions on subway exits, which
// are not drawn but should be visible in balloons and search results
// RemoveNameIfInvisible();
RemoveUselessNames();
return true;
}
void FeatureBuilder::RemoveUselessNames()
{
if (!m_params.name.IsEmpty() && !IsCoastCell())
{
// Remove names for boundary-administrative-* features.
// AFAIR, they were very messy in search because they contain places' names.
auto const typeRemover = [](uint32_t type)
{
static TypeSetChecker const checkBoundary({"boundary", "administrative"});
return checkBoundary.IsEqual(type);
};
auto types = GetTypesHolder();
if (types.RemoveIf(typeRemover))
{
// Remove only if there are no other text-style types in feature (e.g. highway).
std::pair<int, int> const range = GetDrawableScaleRangeForRules(types, RULE_ANY_TEXT);
if (range.first == -1)
m_params.name.Clear();
}
// Skip the alt_name which is equal to the default name.
std::string_view name, altName;
if (m_params.name.GetString(StringUtf8Multilang::kAltNameCode, altName) &&
m_params.name.GetString(StringUtf8Multilang::kDefaultCode, name) &&
search::NormalizeAndSimplifyString(altName) == search::NormalizeAndSimplifyString(name))
{
m_params.name.RemoveString(StringUtf8Multilang::kAltNameCode);
}
}
}
void FeatureBuilder::RemoveNameIfInvisible(int minS, int maxS)
{
if (!m_params.name.IsEmpty() && !IsCoastCell())
{
std::pair<int, int> const range = GetDrawableScaleRangeForRules(GetTypesHolder(), RULE_ANY_TEXT);
if (range.first > maxS || range.second < minS)
m_params.name.Clear();
}
}
bool FeatureBuilder::operator==(FeatureBuilder const & fb) const
{
if (!(m_params == fb.m_params))
return false;
if (m_coastCell != fb.m_coastCell)
return false;
if (m_params.GetGeomType() == GeomType::Point && !IsEqual(m_center, fb.m_center))
return false;
if (!IsEqual(m_limitRect, fb.m_limitRect))
return false;
if (m_polygons.size() != fb.m_polygons.size())
return false;
if (m_osmIds != fb.m_osmIds)
return false;
for (auto i = m_polygons.cbegin(), j = fb.m_polygons.cbegin(); i != m_polygons.cend(); ++i, ++j)
if (!IsEqual(*i, *j))
return false;
return true;
}
bool FeatureBuilder::IsExactEq(FeatureBuilder const & fb) const
{
if (m_params.GetGeomType() == GeomType::Point && m_center != fb.m_center)
return false;
return (m_polygons == fb.m_polygons && m_limitRect == fb.m_limitRect && m_osmIds == fb.m_osmIds &&
m_params == fb.m_params && m_coastCell == fb.m_coastCell);
}
void FeatureBuilder::SerializeForIntermediate(Buffer & data) const
{
CHECK(IsValid(), (*this));
data.clear();
serial::GeometryCodingParams cp;
PushBackByteSink<Buffer> sink(data);
m_params.Write(sink);
if (m_params.GetGeomType() == GeomType::Point)
{
serial::SavePoint(sink, m_center, cp);
}
else
{
WriteVarUint(sink, static_cast<uint32_t>(m_polygons.size()));
for (PointSeq const & points : m_polygons)
serial::SaveOuterPath(points, cp, sink);
WriteVarInt(sink, m_coastCell);
}
// Save OSM IDs to link meta information with sorted features later.
rw::WriteVectorOfPOD(sink, m_osmIds);
// Check for correct serialization.
#ifdef DEBUG
Buffer tmp(data);
FeatureBuilder fb;
fb.DeserializeFromIntermediate(tmp);
ASSERT(fb == *this, ("Source feature: ", *this, "Deserialized feature: ", fb));
#endif
}
void FeatureBuilder::DeserializeFromIntermediate(Buffer & data)
{
serial::GeometryCodingParams cp;
ArrayByteSource source(&data[0]);
m_params.Read(source);
m_limitRect.MakeEmpty();
GeomType const type = m_params.GetGeomType();
if (type == GeomType::Point)
{
m_center = serial::LoadPoint(source, cp);
m_limitRect.Add(m_center);
}
else
{
uint32_t const count = ReadVarUint<uint32_t>(source);
ASSERT_GREATER(count, 0, (*this));
m_polygons.resize(count);
for (auto & poly : m_polygons)
{
poly.clear();
serial::LoadOuterPath(source, cp, poly);
CalcRect(poly, m_limitRect);
}
m_coastCell = ReadVarInt<int64_t>(source);
}
rw::ReadVectorOfPOD(source, m_osmIds);
CHECK(IsValid(), (*this));
}
void FeatureBuilder::SerializeAccuratelyForIntermediate(Buffer & data) const
{
CHECK(IsValid(), (*this));
data.clear();
PushBackByteSink<Buffer> sink(data);
m_params.Write(sink);
if (IsPoint())
{
rw::WritePOD(sink, m_center);
}
else
{
WriteVarUint(sink, static_cast<uint32_t>(m_polygons.size()));
for (PointSeq const & points : m_polygons)
rw::WriteVectorOfPOD(sink, points);
WriteVarInt(sink, m_coastCell);
}
// Save OSM IDs to link meta information with sorted features later.
rw::WriteVectorOfPOD(sink, m_osmIds);
// Check for correct serialization.
#ifdef DEBUG
Buffer tmp(data);
FeatureBuilder fb;
fb.DeserializeAccuratelyFromIntermediate(tmp);
ASSERT(fb == *this, ("Source feature: ", *this, "Deserialized feature: ", fb));
#endif
}
void FeatureBuilder::DeserializeAccuratelyFromIntermediate(Buffer & data)
{
ArrayByteSource source(&data[0]);
m_params.Read(source);
m_limitRect.MakeEmpty();
if (IsPoint())
{
rw::ReadPOD(source, m_center);
m_limitRect.Add(m_center);
}
else
{
uint32_t const count = ReadVarUint<uint32_t>(source);
ASSERT_GREATER(count, 0, (*this));
m_polygons.resize(count);
for (auto & poly : m_polygons)
{
poly.clear();
rw::ReadVectorOfPOD(source, poly);
CalcRect(poly, m_limitRect);
}
m_coastCell = ReadVarInt<int64_t>(source);
}
rw::ReadVectorOfPOD(source, m_osmIds);
CHECK(IsValid(), (*this));
}
void FeatureBuilder::AddOsmId(base::GeoObjectId id)
{
m_osmIds.push_back(id);
}
void FeatureBuilder::SetOsmId(base::GeoObjectId id)
{
m_osmIds.assign(1, id);
}
base::GeoObjectId FeatureBuilder::GetFirstOsmId() const
{
ASSERT(!m_osmIds.empty(), ());
return m_osmIds.front();
}
base::GeoObjectId FeatureBuilder::GetLastOsmId() const
{
ASSERT(!m_osmIds.empty(), ());
return m_osmIds.back();
}
base::GeoObjectId FeatureBuilder::GetMostGenericOsmId() const
{
if (m_osmIds.empty())
return base::GeoObjectId();
auto result = m_osmIds.front();
for (auto const & id : m_osmIds)
{
auto const t = id.GetType();
if (t == base::GeoObjectId::Type::ObsoleteOsmRelation)
{
result = id;
break;
}
else if (t == base::GeoObjectId::Type::ObsoleteOsmWay &&
result.GetType() == base::GeoObjectId::Type::ObsoleteOsmNode)
{
result = id;
}
}
return result;
}
std::string FeatureBuilder::DebugPrintIDs() const
{
return ::DebugPrint(m_osmIds);
}
bool FeatureBuilder::AddName(std::string_view lang, std::string_view name)
{
ASSERT(!name.empty(), ());
return m_params.AddName(lang, name);
}
void FeatureBuilder::SetName(int8_t lang, std::string_view name)
{
ASSERT(!name.empty(), ());
m_params.name.AddString(lang, name);
}
std::string_view FeatureBuilder::GetName(int8_t lang) const
{
std::string_view sv;
CHECK(m_params.name.GetString(lang, sv) != sv.empty(), ());
return sv;
}
size_t FeatureBuilder::GetPointsCount() const
{
size_t counter = 0;
for (auto const & p : m_polygons)
counter += p.size();
return counter;
}
bool FeatureBuilder::IsDrawableInRange(int lowScale, int highScale) const
{
auto const types = GetTypesHolder();
while (lowScale <= highScale)
if (IsDrawableForIndex(types, m_limitRect, lowScale++))
return true;
return false;
}
bool FeatureBuilder::PreSerializeAndRemoveUselessNamesForMwm(SupportingData const & data)
{
// Order is important here not to get dummy logs, when there are no classifier types.
// 1 - Check base params.
if (!PreSerializeAndRemoveUselessNamesForIntermediate())
return false;
// 2 - Check for non-empty geometry.
/// @todo Now happens with very thin area buildings like here:
/// https://www.openstreetmap.org/#map=19/48.93804/8.35221
GeomType const geomType = m_params.GetGeomType();
if (geomType == GeomType::Line)
{
if (data.m_ptsMask == 0 && data.m_innerPts.empty())
{
LOG(LWARNING, ("Skip feature with empty geometry", GetMostGenericOsmId()));
return false;
}
}
else if (geomType == GeomType::Area)
{
if (data.m_trgMask == 0 && data.m_innerTrg.empty())
{
LOG(LWARNING, ("Skip feature with empty geometry", GetMostGenericOsmId()));
return false;
}
}
return true;
}
void FeatureBuilder::SerializeForMwm(SupportingData & data, serial::GeometryCodingParams const & params) const
{
data.m_buffer.clear();
PushBackByteSink<Buffer> sink(data.m_buffer);
FeatureParams(m_params).Write(sink);
GeomType const type = m_params.GetGeomType();
CHECK(type != GeomType::Undefined, ());
if (type == GeomType::Point)
{
uint64_t const encoded =
coding::EncodePointDeltaAsUint(PointDToPointU(m_center, params.GetCoordBits()), params.GetBasePoint());
CHECK_GREATER(bits::NumHiZeroBits64(encoded), 1, ());
WriteVarUint(sink, encoded << 1); // Relations control bit
return;
}
uint8_t const ptsCount = base::asserted_cast<uint8_t>(data.m_innerPts.size());
uint8_t trgCount = base::asserted_cast<uint8_t>(data.m_innerTrg.size());
if (trgCount > 0)
{
CHECK_GREATER(trgCount, 2, ());
trgCount -= 2;
}
{
BitWriter<PushBackByteSink<Buffer>> bitSink(sink);
if (type == GeomType::Line)
{
bitSink.Write(ptsCount != 0 ? ptsCount : data.m_ptsMask, 4);
bitSink.Write(ptsCount == 0 ? 1 : 0, 1);
}
else
{
CHECK_EQUAL(type, GeomType::Area, ());
bitSink.Write(trgCount != 0 ? trgCount : data.m_trgMask, 4);
bitSink.Write(trgCount == 0 ? 1 : 0, 1);
}
// Relations control bit
bitSink.Write(0, 1);
}
if (type == GeomType::Line)
{
if (ptsCount > 0)
{
if (ptsCount > 2)
{
uint32_t v = data.m_ptsSimpMask;
int const count = (ptsCount - 2 + 3) / 4;
CHECK_LESS(count, 4, ());
for (int i = 0; i < count; ++i)
{
WriteToSink(sink, static_cast<uint8_t>(v));
v >>= 8;
}
}
serial::SaveInnerPath(data.m_innerPts, params, sink);
}
else
{
auto const & poly = GetOuterGeometry();
CHECK_GREATER(poly.size(), 2, ());
// Store first point once for outer linear features.
serial::SavePoint(sink, poly[0], params);
// offsets was pushed from high scale index to low
reverse(data.m_ptsOffset.begin(), data.m_ptsOffset.end());
WriteVarUintArray(data.m_ptsOffset, sink);
}
}
else
{
CHECK_EQUAL(type, GeomType::Area, ());
if (trgCount > 0)
serial::SaveInnerTriangles(data.m_innerTrg, params, sink);
else
{
// offsets was pushed from high scale index to low
reverse(data.m_trgOffset.begin(), data.m_trgOffset.end());
WriteVarUintArray(data.m_trgOffset, sink);
}
}
}
bool FeatureBuilder::IsValid() const
{
if (!GetParams().IsValid())
return false;
auto const & geom = GetGeometry();
if (IsLine() && (geom.empty() || GetOuterGeometry().size() < 2))
return false;
if (IsArea())
{
if (geom.empty())
return false;
for (auto const & points : geom)
if (points.size() < 3)
return false;
}
return true;
}
std::string DebugPrint(FeatureBuilder const & fb)
{
std::ostringstream out;
switch (fb.GetGeomType())
{
case GeomType::Point: out << DebugPrint(fb.GetKeyPoint()); break;
case GeomType::Line: out << "line with " << fb.GetPointsCount() << " points"; break;
case GeomType::Area: out << "area with " << fb.GetPointsCount() << " points"; break;
default: out << "ERROR: unknown geometry type"; break;
}
out << " " << DebugPrint(mercator::ToLatLon(fb.GetLimitRect())) << " " << DebugPrint(fb.GetParams()) << " "
<< fb.DebugPrintIDs();
return out.str();
}
namespace serialization_policy
{
// static
TypeSerializationVersion const MinSize::kSerializationVersion;
// static
TypeSerializationVersion const MaxAccuracy::kSerializationVersion;
} // namespace serialization_policy
} // namespace feature

View file

@ -0,0 +1,362 @@
#pragma once
#include "indexer/feature_data.hpp"
#include "platform/platform.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/internal/file_data.hpp"
#include "base/geo_object_id.hpp"
#include "base/stl_helpers.hpp"
#include <functional>
#include <list>
#include <string>
#include <vector>
namespace serial
{
class GeometryCodingParams;
} // namespace serial
namespace feature
{
class FeatureBuilder
{
public:
using PointSeq = std::vector<m2::PointD>;
using Geometry = std::list<PointSeq>;
using Buffer = std::vector<char>;
using Offsets = std::vector<uint32_t>;
struct SupportingData
{
Offsets m_ptsOffset;
Offsets m_trgOffset;
uint8_t m_ptsMask = 0;
uint8_t m_trgMask = 0;
uint32_t m_ptsSimpMask = 0;
PointSeq m_innerPts;
PointSeq m_innerTrg;
Buffer m_buffer;
};
FeatureBuilder();
// Checks for equality. The error of coordinates is allowed.
bool operator==(FeatureBuilder const & fb) const;
// Checks for equality. The error of coordinates isn't allowed. Binary equality check of
// coordinates is used.
bool IsExactEq(FeatureBuilder const & fb) const;
bool IsValid() const;
/// @name To work with geometry.
///@{
void AssignPoints(PointSeq points);
void AssignArea(PointSeq && outline, Geometry const & holes);
void AddPolygon(PointSeq && poly);
void ResetGeometry();
m2::RectD const & GetLimitRect() const { return m_limitRect; }
Geometry const & GetGeometry() const { return m_polygons; }
PointSeq const & GetOuterGeometry() const
{
CHECK(!m_polygons.empty(), ());
return m_polygons.front();
}
GeomType GetGeomType() const { return m_params.GetGeomType(); }
bool IsGeometryClosed() const;
static m2::PointD GetGeometryCenter(PointSeq const & pts);
m2::PointD GetGeometryCenter() const { return GetGeometryCenter(GetOuterGeometry()); }
m2::PointD GetKeyPoint() const;
size_t GetPointsCount() const;
size_t GetPolygonsCount() const { return m_polygons.size(); }
///@}
template <class ToDo>
void ForEachPoint(ToDo && toDo) const
{
if (IsPoint())
{
toDo(m_center);
}
else
{
for (auto const & points : m_polygons)
for (auto const & pt : points)
toDo(pt);
}
}
template <class ToDo>
bool ForAnyPoint(ToDo && toDo) const
{
if (IsPoint())
return toDo(m_center);
for (auto const & points : m_polygons)
if (base::AnyOf(points, std::forward<ToDo>(toDo)))
return true;
return false;
}
template <class ToDo>
void ForEachPolygon(ToDo && toDo) const
{
for (auto const & points : m_polygons)
toDo(points);
}
// To work with geometry type.
void SetCenter(m2::PointD const & p);
void SetLinear(bool reverseGeometry = false);
void SetArea() { m_params.SetGeomType(GeomType::Area); }
bool IsPoint() const { return GetGeomType() == GeomType::Point; }
bool IsLine() const { return GetGeomType() == GeomType::Line; }
bool IsArea() const { return GetGeomType() == GeomType::Area; }
/// @name To work with types.
///@{
void SetType(uint32_t type) { m_params.SetType(type); }
void AddType(uint32_t type) { m_params.AddType(type); }
bool PopExactType(uint32_t type) { return m_params.PopExactType(type); }
template <class Fn>
bool RemoveTypesIf(Fn && fn)
{
base::EraseIf(m_params.m_types, std::forward<Fn>(fn));
return m_params.m_types.empty();
}
bool HasType(uint32_t t) const { return m_params.IsTypeExist(t); }
bool HasType(uint32_t t, uint8_t level) const { return m_params.IsTypeExist(t, level); }
FeatureParams::Types const & GetTypes() const { return m_params.m_types; }
size_t GetTypesCount() const { return m_params.m_types.size(); }
///@}
// Actually, "SetName" is a better name for this function ...
bool AddName(std::string_view lang, std::string_view name);
void SetName(int8_t lang, std::string_view name);
void SetParams(FeatureBuilderParams const & params) { m_params.SetParams(params); }
FeatureBuilderParams const & GetParams() const { return m_params; }
FeatureBuilderParams & GetParams() { return m_params; }
std::string_view GetName(int8_t lang = StringUtf8Multilang::kDefaultCode) const;
StringUtf8Multilang const & GetMultilangName() const { return m_params.name; }
uint8_t GetRank() const { return m_params.rank; }
Metadata const & GetMetadata() const { return m_params.GetMetadata(); }
Metadata & GetMetadata() { return m_params.GetMetadata(); }
// To work with types and names based on drawing.
// Check classificator types for their compatibility with feature geometry type.
// Need to call when using any classificator types manipulating.
// Return false If no any valid types.
bool RemoveInvalidTypes();
// Clear name if it's not visible in scale range [minS, maxS].
void RemoveNameIfInvisible(int minS = 0, int maxS = 1000);
void RemoveUselessNames();
bool IsDrawableInRange(int lowScale, int highScale) const;
/// @name Serialization.
///@{
bool PreSerialize();
bool PreSerializeAndRemoveUselessNamesForIntermediate();
void SerializeForIntermediate(Buffer & data) const;
void DeserializeFromIntermediate(Buffer & data);
// These methods use geometry without loss of accuracy.
void SerializeAccuratelyForIntermediate(Buffer & data) const;
void DeserializeAccuratelyFromIntermediate(Buffer & data);
bool PreSerializeAndRemoveUselessNamesForMwm(SupportingData const & data);
void SerializeForMwm(SupportingData & data, serial::GeometryCodingParams const & params) const;
///@}
// Get common parameters of feature.
TypesHolder GetTypesHolder() const;
/// @name To work with osm ids.
///@{
void AddOsmId(base::GeoObjectId id);
void SetOsmId(base::GeoObjectId id);
base::GeoObjectId GetFirstOsmId() const;
base::GeoObjectId GetLastOsmId() const;
// Returns an id of the most general element: node's one if there is no area or relation,
// area's one if there is no relation, and relation id otherwise.
base::GeoObjectId GetMostGenericOsmId() const;
bool HasOsmIds() const { return !m_osmIds.empty(); }
std::string DebugPrintIDs() const;
///@}
// To work with coasts.
void SetCoastCell(int64_t iCell) { m_coastCell = iCell; }
bool IsCoastCell() const { return (m_coastCell != -1); }
friend std::string DebugPrint(FeatureBuilder const & fb);
protected:
// Can be one of the following:
// - point in point-feature
// - origin point of text [future] in line-feature
// - origin point of text or symbol in area-feature
m2::PointD m_center; // Check HEADER_HAS_POINT
// List of geometry polygons.
Geometry m_polygons; // Check HEADER_IS_AREA
m2::RectD m_limitRect;
std::vector<base::GeoObjectId> m_osmIds;
FeatureBuilderParams m_params;
/// Not used in GEOM_POINTs
int64_t m_coastCell;
};
// SerializationPolicy serialization and deserialization.
namespace serialization_policy
{
enum class SerializationVersion : uint32_t
{
Undefined,
MinSize,
MaxAccuracy
};
using TypeSerializationVersion = typename std::underlying_type<SerializationVersion>::type;
struct MinSize
{
auto static const kSerializationVersion = static_cast<TypeSerializationVersion>(SerializationVersion::MinSize);
static void Serialize(FeatureBuilder const & fb, FeatureBuilder::Buffer & data) { fb.SerializeForIntermediate(data); }
static void Deserialize(FeatureBuilder & fb, FeatureBuilder::Buffer & data) { fb.DeserializeFromIntermediate(data); }
};
struct MaxAccuracy
{
auto static const kSerializationVersion = static_cast<TypeSerializationVersion>(SerializationVersion::MinSize);
static void Serialize(FeatureBuilder const & fb, FeatureBuilder::Buffer & data)
{
fb.SerializeAccuratelyForIntermediate(data);
}
static void Deserialize(FeatureBuilder & fb, FeatureBuilder::Buffer & data)
{
fb.DeserializeAccuratelyFromIntermediate(data);
}
};
} // namespace serialization_policy
// TODO(maksimandrianov): I would like to support the verification of serialization versions,
// but this requires reworking of FeatureCollector class and its derived classes. It is in future
// plans
// template <class SerializationPolicy, class Source>
// void TryReadAndCheckVersion(Source & src)
//{
// if (src.Size() - src.Pos() >= sizeof(serialization_policy::TypeSerializationVersion))
// {
// auto const type = ReadVarUint<serialization_policy::TypeSerializationVersion>(src);
// CHECK_EQUAL(type, SerializationPolicy::kSerializationVersion, ());
// }
// else
// {
// LOG(LWARNING, ("Unable to read file version."))
// }
//}
// Read feature from feature source.
template <class SerializationPolicy = serialization_policy::MaxAccuracy, class Source>
void ReadFromSourceRawFormat(Source & src, FeatureBuilder & fb)
{
uint32_t const sz = ReadVarUint<uint32_t>(src);
typename FeatureBuilder::Buffer buffer(sz);
src.Read(&buffer[0], sz);
SerializationPolicy::Deserialize(fb, buffer);
}
// Process features in features file.
template <class SerializationPolicy = serialization_policy::MaxAccuracy, class ToDo>
void ForEachFeatureRawFormat(std::string const & filename, ToDo && toDo)
{
FileReader reader(filename);
ReaderSource<FileReader> src(reader);
// TryReadAndCheckVersion<SerializationPolicy>(src);
auto const fileSize = reader.Size();
auto currPos = src.Pos();
// read features one by one
while (currPos < fileSize)
{
FeatureBuilder fb;
ReadFromSourceRawFormat<SerializationPolicy>(src, fb);
toDo(std::move(fb), currPos);
currPos = src.Pos();
}
}
template <class SerializationPolicy = serialization_policy::MaxAccuracy>
std::vector<FeatureBuilder> ReadAllDatRawFormat(std::string const & fileName)
{
std::vector<FeatureBuilder> fbs;
// Happens in tests when World or Country file is empty (no valid Features to emit).
if (Platform::IsFileExistsByFullPath(fileName))
{
ForEachFeatureRawFormat<SerializationPolicy>(
fileName, [&](FeatureBuilder && fb, uint64_t) { fbs.emplace_back(std::move(fb)); });
}
return fbs;
}
template <class SerializationPolicy = serialization_policy::MaxAccuracy, class Writer = FileWriter>
class FeatureBuilderWriter
{
public:
explicit FeatureBuilderWriter(std::string const & filename, bool mangleName = false,
FileWriter::Op op = FileWriter::Op::OP_WRITE_TRUNCATE)
: m_filename(filename)
, m_mangleName(mangleName)
, m_writer(std::make_unique<Writer>(m_mangleName ? m_filename + "_" : m_filename, op))
{
// TODO(maksimandrianov): I would like to support the verification of serialization versions,
// but this requires reworking of FeatureCollector class and its derived classes. It is in
// future plans WriteVarUint(m_writer,
// static_cast<serialization_policy::TypeSerializationVersion>(SerializationPolicy::kSerializationVersion));
}
explicit FeatureBuilderWriter(std::string const & filename, FileWriter::Op op)
: FeatureBuilderWriter(filename, false /* mangleName */, op)
{}
~FeatureBuilderWriter()
{
if (m_mangleName)
{
// Flush and close.
auto const currentFilename = m_writer->GetName();
m_writer.reset();
CHECK(base::RenameFileX(currentFilename, m_filename), (currentFilename, m_filename));
}
}
void Write(FeatureBuilder const & fb) { Write(*m_writer, fb); }
template <typename Sink>
static void Write(Sink & writer, FeatureBuilder const & fb)
{
FeatureBuilder::Buffer buffer;
SerializationPolicy::Serialize(fb, buffer);
WriteVarUint(writer, static_cast<uint32_t>(buffer.size()));
writer.Write(buffer.data(), buffer.size() * sizeof(FeatureBuilder::Buffer::value_type));
}
private:
std::string m_filename;
bool m_mangleName = false;
std::unique_ptr<Writer> m_writer;
};
} // namespace feature

View file

@ -0,0 +1,17 @@
#pragma once
namespace feature
{
class FeatureBuilder;
} // namespace feature
class FeatureEmitterIFace
{
// Disable deletion via this interface, because some dtors in derived classes are noexcept(false).
protected:
~FeatureEmitterIFace() = default;
public:
virtual void operator()(feature::FeatureBuilder const &) = 0;
};

View file

@ -0,0 +1,146 @@
#include "generator/feature_generator.hpp"
#include "generator/feature_builder.hpp"
#include "generator/generate_info.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/intermediate_elements.hpp"
#include "geometry/mercator.hpp"
#include "indexer/cell_id.hpp"
#include "indexer/data_header.hpp"
#include "coding/varint.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <functional>
#include <unordered_map>
#include <utility>
#include "std/target_os.hpp"
///////////////////////////////////////////////////////////////////////////////////////////////////
// FeaturesCollector implementation
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace feature
{
FeaturesCollector::FeaturesCollector(std::string const & fName, FileWriter::Op op)
: m_dataFile(fName, op)
, m_writeBuffer(kBufferSize)
{}
FeaturesCollector::~FeaturesCollector()
{
FlushBuffer();
}
template <typename ValueT, size_t ValueSizeT = sizeof(ValueT) + 1>
std::pair<char[ValueSizeT], uint8_t> PackValue(ValueT v)
{
static_assert(std::is_integral<ValueT>::value, "Non integral value");
static_assert(std::is_unsigned<ValueT>::value, "Non unsigned value");
std::pair<char[ValueSizeT], uint8_t> res;
res.second = 0;
while (v > 127)
{
res.first[res.second++] = static_cast<uint8_t>((v & 127) | 128);
v >>= 7;
}
res.first[res.second++] = static_cast<uint8_t>(v);
return res;
}
void FeaturesCollector::FlushBuffer()
{
m_dataFile.Write(m_writeBuffer.data(), m_writePosition);
m_writePosition = 0;
}
void FeaturesCollector::Flush()
{
FlushBuffer();
m_dataFile.Flush();
}
void FeaturesCollector::Write(char const * src, size_t size)
{
do
{
if (m_writePosition == kBufferSize)
FlushBuffer();
size_t const part_size = std::min(size, kBufferSize - m_writePosition);
memcpy(&m_writeBuffer[m_writePosition], src, part_size);
m_writePosition += part_size;
size -= part_size;
src += part_size;
}
while (size > 0);
}
uint32_t FeaturesCollector::WriteFeatureBase(std::vector<char> const & bytes, FeatureBuilder const & fb)
{
size_t const sz = bytes.size();
CHECK(sz != 0, ("Empty feature not allowed here!"));
auto const & packedSize = PackValue(sz);
Write(packedSize.first, packedSize.second);
Write(&bytes[0], sz);
m_bounds.Add(fb.GetLimitRect());
return m_featureID++;
}
uint32_t FeaturesCollector::Collect(FeatureBuilder const & fb)
{
FeatureBuilder::Buffer bytes;
fb.SerializeAccuratelyForIntermediate(bytes);
uint32_t const featureId = WriteFeatureBase(bytes, fb);
CHECK_LESS(0, m_featureID, ());
return featureId;
}
FeaturesAndRawGeometryCollector::FeaturesAndRawGeometryCollector(std::string const & featuresFileName,
std::string const & rawGeometryFileName)
: FeaturesCollector(featuresFileName)
, m_rawGeometryFileStream(rawGeometryFileName)
{}
FeaturesAndRawGeometryCollector::~FeaturesAndRawGeometryCollector()
{
uint64_t terminator = 0;
m_rawGeometryFileStream.Write(&terminator, sizeof(terminator));
LOG(LINFO, ("Write", m_rawGeometryCounter, "geometries into", m_rawGeometryFileStream.GetName()));
}
uint32_t FeaturesAndRawGeometryCollector::Collect(FeatureBuilder const & fb)
{
uint32_t const featureId = FeaturesCollector::Collect(fb);
FeatureBuilder::Geometry const & geom = fb.GetGeometry();
if (geom.empty())
return featureId;
++m_rawGeometryCounter;
uint64_t numGeometries = geom.size();
m_rawGeometryFileStream.Write(&numGeometries, sizeof(numGeometries));
for (FeatureBuilder::PointSeq const & points : geom)
{
uint64_t numPoints = points.size();
m_rawGeometryFileStream.Write(&numPoints, sizeof(numPoints));
m_rawGeometryFileStream.Write(points.data(), sizeof(FeatureBuilder::PointSeq::value_type) * points.size());
}
return featureId;
}
uint32_t CheckedFilePosCast(FileWriter const & f)
{
uint64_t pos = f.Pos();
CHECK_LESS_OR_EQUAL(pos, static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()),
("Feature offset is out of 32bit boundary!"));
return static_cast<uint32_t>(pos);
}
} // namespace feature

View file

@ -0,0 +1,62 @@
#pragma once
#include "geometry/rect2d.hpp"
#include "coding/file_writer.hpp"
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
namespace feature
{
class FeatureBuilder;
// Writes features to file.
class FeaturesCollector
{
public:
static size_t constexpr kBufferSize = 48000;
FeaturesCollector(std::string const & fName, FileWriter::Op op = FileWriter::Op::OP_WRITE_TRUNCATE);
virtual ~FeaturesCollector();
static uint64_t GetCurrentPosition();
std::string const & GetFilePath() const { return m_dataFile.GetName(); }
/// \brief Serializes |f|.
/// \returns Feature id of serialized feature.
virtual uint32_t Collect(FeatureBuilder const & f);
virtual void Finish() {}
protected:
/// \return Feature offset in the file, which is used as an ID later
uint32_t WriteFeatureBase(std::vector<char> const & bytes, FeatureBuilder const & fb);
void Flush();
FileWriter m_dataFile;
m2::RectD m_bounds;
private:
void Write(char const * src, size_t size);
void FlushBuffer();
std::vector<char> m_writeBuffer;
size_t m_writePosition = 0;
uint32_t m_featureID = 0;
};
class FeaturesAndRawGeometryCollector : public FeaturesCollector
{
FileWriter m_rawGeometryFileStream;
size_t m_rawGeometryCounter = 0;
public:
FeaturesAndRawGeometryCollector(std::string const & featuresFileName, std::string const & rawGeometryFileName);
~FeaturesAndRawGeometryCollector() override;
uint32_t Collect(FeatureBuilder const & f) override;
};
uint32_t CheckedFilePosCast(FileWriter const & f);
} // namespace feature

View file

@ -0,0 +1,62 @@
#include "generator/feature_helpers.hpp"
#include "generator/feature_builder.hpp"
#include "indexer/feature_visibility.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
namespace feature
{
CalculateMidPoints::CalculateMidPoints()
{
m_minDrawableScaleFn = [](FeatureBuilder const & fb)
{ return GetMinDrawableScale(fb.GetTypesHolder(), fb.GetLimitRect()); };
}
void CalculateMidPoints::operator()(FeatureBuilder const & ft, uint64_t pos)
{
// Reset state.
m_midLoc = m2::PointD::Zero();
m_locCount = 0;
ft.ForEachPoint(*this);
CHECK_GREATER(m_locCount, 0, ());
m_midLoc = m_midLoc / m_locCount;
uint64_t const pointAsInt64 = PointToInt64Obsolete(m_midLoc, m_coordBits);
int const minScale = m_minDrawableScaleFn(ft);
/// May be invisible if it's small area object with [0-9] scales.
/// @todo Probably, we need to keep that objects if 9 scale (as we do in 17 scale).
if (minScale != -1)
{
uint64_t const order = (static_cast<uint64_t>(minScale) << 59) | (pointAsInt64 >> 5);
m_vec.emplace_back(order, pos);
}
}
bool CalculateMidPoints::operator()(m2::PointD const & p)
{
m_midLoc += p;
m_midAll += p;
++m_locCount;
++m_allCount;
return true;
}
m2::PointD CalculateMidPoints::GetCenter() const
{
// Possible in unit tests.
if (m_allCount == 0)
return {0, 0};
return m_midAll / m_allCount;
}
void CalculateMidPoints::Sort()
{
std::sort(m_vec.begin(), m_vec.end(), base::LessBy(&CellAndOffset::first));
}
} // namespace feature

View file

@ -0,0 +1,106 @@
#pragma once
#include "coding/geometry_coding.hpp"
#include "geometry/parametrized_segment.hpp"
#include "geometry/point2d.hpp"
#include "geometry/simplification.hpp"
#include "indexer/feature_data.hpp"
#include "indexer/scales.hpp"
#include "base/assert.hpp"
#include "base/math.hpp"
#include <cmath>
#include <functional>
#include <limits>
#include <vector>
namespace feature
{
class FeatureBuilder;
class CalculateMidPoints
{
public:
using CellAndOffset = std::pair<uint64_t, uint64_t>;
using MinDrawableScaleFn = std::function<int(FeatureBuilder const & fb)>;
CalculateMidPoints();
void operator()(FeatureBuilder const & ft, uint64_t pos);
bool operator()(m2::PointD const & p);
m2::PointD GetCenter() const;
std::vector<CellAndOffset> const & GetVector() const { return m_vec; }
void Sort();
private:
m2::PointD m_midLoc;
m2::PointD m_midAll = m2::PointD::Zero();
size_t m_locCount = 0;
size_t m_allCount = 0;
uint8_t m_coordBits = serial::GeometryCodingParams().GetCoordBits();
MinDrawableScaleFn m_minDrawableScaleFn;
std::vector<CellAndOffset> m_vec;
};
template <typename Point>
inline bool ArePointsEqual(Point const & p1, Point const & p2)
{
return p1 == p2;
}
template <>
inline bool ArePointsEqual<m2::PointD>(m2::PointD const & p1, m2::PointD const & p2)
{
return AlmostEqualULPs(p1, p2);
}
class DistanceToSegmentWithRectBounds
{
public:
explicit DistanceToSegmentWithRectBounds(m2::RectD const & rect) : m_rect(rect) {}
// Returns squared distance from the segment [a, b] to the point p unless
// p is close to the borders of |m_rect|, in which case returns a very large number.
double operator()(m2::PointD const & a, m2::PointD const & b, m2::PointD const & p) const
{
if (AlmostEqualAbs(p.x, m_rect.minX(), m_eps) || AlmostEqualAbs(p.x, m_rect.maxX(), m_eps) ||
AlmostEqualAbs(p.y, m_rect.minY(), m_eps) || AlmostEqualAbs(p.y, m_rect.maxY(), m_eps))
{
// Points near rect should be in a result simplified vector.
return std::numeric_limits<double>::max();
}
return m2::SquaredDistanceFromSegmentToPoint()(a, b, p);
}
double GetEpsilon() const { return m_eps; }
private:
m2::RectD const & m_rect;
// 5.0E-7 is near with minimal epsilon when integer points are different
// PointDToPointU(x, y) != PointDToPointU(x + m_eps, y + m_eps)
double m_eps = 5.0E-7;
};
template <typename DistanceFn, typename PointsContainer>
void SimplifyPoints(DistanceFn distFn, int level, PointsContainer const & in, PointsContainer & out)
{
if (in.size() < 2)
return;
double const eps = math::Pow2(scales::GetEpsilonForSimplify(level));
SimplifyNearOptimal(20, in.begin(), in.end(), eps, distFn,
AccumulateSkipSmallTrg<DistanceFn, m2::PointD>(distFn, out, eps));
CHECK_GREATER(out.size(), 1, ());
CHECK(ArePointsEqual(in.front(), out.front()), ());
CHECK(ArePointsEqual(in.back(), out.back()), ());
}
} // namespace feature

233
generator/feature_maker.cpp Normal file
View file

@ -0,0 +1,233 @@
#include "generator/feature_maker.hpp"
#include "generator/holes.hpp"
#include "generator/osm2type.hpp"
#include "generator/osm_element_helpers.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "geometry/mercator.hpp"
namespace generator
{
using namespace feature;
std::shared_ptr<FeatureMakerBase> FeatureMakerSimple::Clone() const
{
return std::make_shared<FeatureMakerSimple>();
}
void FeatureMakerSimple::ParseParams(FeatureBuilderParams & params, OsmElement & p) const
{
auto const & cl = classif();
ftype::GetNameAndType(&p, params, [&cl](uint32_t type) { return cl.IsTypeValid(type); },
[this](OsmElement const * p) { return GetOrigin(*p); });
}
std::optional<m2::PointD> FeatureMakerSimple::ReadNode(uint64_t id) const
{
m2::PointD res;
if (m_cache->GetNode(id, res.y, res.x))
return res;
return {};
}
std::optional<m2::PointD> FeatureMakerSimple::GetOrigin(OsmElement const & e) const
{
if (e.IsNode())
{
CHECK(e.m_lat != 0 || e.m_lon != 0, (e.m_id));
return mercator::FromLatLon(e.m_lat, e.m_lon);
}
else if (e.IsWay())
{
CHECK(!e.m_nodes.empty(), (e.m_id));
return ReadNode(e.m_nodes.front());
}
else
{
if (e.m_members.empty())
{
// Such relations are considered invalid but could be present in OSM data still,
// see https://wiki.openstreetmap.org/wiki/Empty_relations
LOG(LWARNING, ("Invalid relation with no members", e.m_id));
return {};
}
for (auto const & m : e.m_members)
if (m.m_type == OsmElement::EntityType::Node)
return ReadNode(m.m_ref);
for (auto const & m : e.m_members)
{
if (m.m_type == OsmElement::EntityType::Way)
{
WayElement way(m.m_ref);
if (m_cache->GetWay(m.m_ref, way))
{
CHECK(!way.m_nodes.empty(), (m.m_ref));
return ReadNode(way.m_nodes.front());
}
}
}
LOG(LWARNING, ("No geometry members for relation", e.m_id));
return {};
}
}
bool FeatureMakerSimple::BuildFromNode(OsmElement & p, FeatureBuilderParams const & params)
{
FeatureBuilder fb;
fb.SetCenter(mercator::FromLatLon(p.m_lat, p.m_lon));
fb.SetOsmId(base::MakeOsmNode(p.m_id));
fb.SetParams(params);
m_queue.push(std::move(fb));
return true;
}
bool FeatureMakerSimple::BuildFromWay(OsmElement & p, FeatureBuilderParams const & params)
{
auto const & nodes = p.Nodes();
if (nodes.size() < 2)
return false;
FeatureBuilder fb;
std::vector<m2::PointD> points;
points.reserve(nodes.size());
for (uint64_t ref : nodes)
{
m2::PointD pt;
if (!m_cache->GetNode(ref, pt.y, pt.x))
return false;
points.push_back(pt);
}
fb.AssignPoints(std::move(points));
fb.SetOsmId(base::MakeOsmWay(p.m_id));
fb.SetParams(params);
if (fb.IsGeometryClosed())
fb.SetArea();
else
fb.SetLinear(params.GetReversedGeometry());
m_queue.push(std::move(fb));
return true;
}
bool FeatureMakerSimple::BuildFromRelation(OsmElement & p, FeatureBuilderParams const & params)
{
HolesRelation helper(m_cache);
helper.Build(&p);
auto const & holesGeometry = helper.GetHoles();
auto const size = m_queue.size();
auto const createFB = [&](auto && pts, auto const & ids)
{
FeatureBuilder fb;
fb.AssignArea(std::move(pts), holesGeometry);
CHECK(fb.IsGeometryClosed(), ());
for (uint64_t id : ids)
fb.AddOsmId(base::MakeOsmWay(id));
fb.AddOsmId(base::MakeOsmRelation(p.m_id));
fb.SetParams(params);
fb.SetArea();
m_queue.push(std::move(fb));
};
helper.GetOuter().ForEachArea(true /* collectID */, [&](auto && pts, auto && ids) { createFB(std::move(pts), ids); });
return size != m_queue.size();
}
FeatureMaker::FeatureMaker(IDRInterfacePtr const & cache) : FeatureMakerSimple(cache)
{
m_placeClass = classif().GetTypeByPath({"place"});
}
std::shared_ptr<FeatureMakerBase> FeatureMaker::Clone() const
{
return std::make_shared<FeatureMaker>();
}
void FeatureMaker::ParseParams(FeatureBuilderParams & params, OsmElement & p) const
{
ftype::GetNameAndType(&p, params, &feature::IsUsefulType, [this](OsmElement const * p) { return GetOrigin(*p); });
}
bool FeatureMaker::BuildFromRelation(OsmElement & p, FeatureBuilderParams const & params)
{
uint32_t const placeType = params.FindType(m_placeClass, 1);
if (placeType != ftype::GetEmptyValue())
{
std::optional<m2::PointD> center;
auto const nodes = osm_element::GetPlaceNodeFromMembers(p);
if (!nodes.empty())
{
// Patch to avoid multiple instances from Node and Relation. Node should inherit all needed tags (population).
// This is ok, because "canonical" OSM assumes that country/state place tag presents in Nodes only.
/// @todo Store boundaries like for cities?
if (ftypes::IsCountryChecker::Instance()(placeType) || ftypes::IsStateChecker::Instance()(placeType))
return false;
// admin_centre will be the first
center = ReadNode(nodes.front());
}
else
{
// Calculate center of the biggest outer polygon.
HolesRelation helper(m_cache);
helper.Build(&p);
double maxArea = 0;
helper.GetOuter().ForEachArea(false /* collectID */, [&](auto && pts, auto &&)
{
m2::RectD rect;
CalcRect(pts, rect);
double const currArea = rect.Area();
if (currArea > maxArea)
{
center = FeatureBuilder::GetGeometryCenter(pts);
maxArea = currArea;
}
});
}
if (!center)
return false;
// Make separate place Point FeatureFuilder.
FeatureBuilder fb;
fb.SetCenter(*center);
fb.SetOsmId(base::MakeOsmRelation(p.m_id));
FeatureBuilderParams copyParams(params);
copyParams.SetType(placeType);
fb.SetParams(copyParams);
m_queue.push(std::move(fb));
// Default processing without place type.
copyParams = params;
copyParams.PopExactType(placeType);
if (copyParams.FinishAddingTypes())
FeatureMakerSimple::BuildFromRelation(p, copyParams);
return true;
}
return FeatureMakerSimple::BuildFromRelation(p, params);
}
} // namespace generator

View file

@ -0,0 +1,53 @@
#pragma once
#include "generator/feature_maker_base.hpp"
struct OsmElement;
namespace generator
{
// FeatureMakerSimple is suitable for most cases for simple features.
// It filters features for bad geometry only.
class FeatureMakerSimple : public FeatureMakerBase
{
public:
using FeatureMakerBase::FeatureMakerBase;
/// @name FeatureMakerBase overrides:
/// @{
std::shared_ptr<FeatureMakerBase> Clone() const override;
protected:
void ParseParams(FeatureBuilderParams & params, OsmElement & element) const override;
bool BuildFromNode(OsmElement & element, FeatureBuilderParams const & params) override;
bool BuildFromWay(OsmElement & element, FeatureBuilderParams const & params) override;
bool BuildFromRelation(OsmElement & element, FeatureBuilderParams const & params) override;
/// @}
/// @return Any origin mercator point (prefer nodes) that belongs to \a e.
std::optional<m2::PointD> GetOrigin(OsmElement const & e) const;
/// @return Mercator point from intermediate cache storage.
std::optional<m2::PointD> ReadNode(uint64_t id) const;
};
// FeatureMaker additionally filters the types using feature::IsUsefulType.
class FeatureMaker : public FeatureMakerSimple
{
public:
explicit FeatureMaker(IDRInterfacePtr const & cache = {});
/// @name FeatureMakerBase overrides:
/// @{
std::shared_ptr<FeatureMakerBase> Clone() const override;
protected:
void ParseParams(FeatureBuilderParams & params, OsmElement & element) const override;
bool BuildFromRelation(OsmElement & p, FeatureBuilderParams const & params) override;
/// @}
private:
uint32_t m_placeClass;
};
} // namespace generator

View file

@ -0,0 +1,81 @@
#include "generator/feature_maker_base.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/osm_element.hpp"
#include "base/assert.hpp"
namespace generator
{
using namespace feature;
bool FeatureMakerBase::Add(OsmElement & element)
{
ASSERT(m_cache, ());
FeatureBuilderParams params;
ParseParams(params, element);
switch (element.m_type)
{
case OsmElement::EntityType::Node: return BuildFromNode(element, params);
case OsmElement::EntityType::Way: return BuildFromWay(element, params);
case OsmElement::EntityType::Relation: return BuildFromRelation(element, params);
default: return false;
}
}
size_t FeatureMakerBase::Size() const
{
return m_queue.size();
}
bool FeatureMakerBase::Empty() const
{
return m_queue.empty();
}
bool FeatureMakerBase::GetNextFeature(FeatureBuilder & feature)
{
if (m_queue.empty())
return false;
feature = std::move(m_queue.front());
m_queue.pop();
return true;
}
void TransformToPoint(FeatureBuilder & feature)
{
if (feature.IsPoint())
return;
feature.SetCenter(feature.GetGeometryCenter());
auto & params = feature.GetParams();
if (!params.house.IsEmpty())
params.SetGeomTypePointEx();
}
void TransformToLine(FeatureBuilder & feature)
{
if (feature.IsLine())
return;
CHECK(feature.IsArea(), (feature));
feature.SetLinear(feature.GetParams().GetReversedGeometry());
}
FeatureBuilder MakePoint(FeatureBuilder const & feature)
{
FeatureBuilder tmp(feature);
TransformToPoint(tmp);
return tmp;
}
FeatureBuilder MakeLine(FeatureBuilder const & feature)
{
FeatureBuilder tmp(feature);
TransformToLine(tmp);
return tmp;
}
} // namespace generator

View file

@ -0,0 +1,51 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "generator/intermediate_data.hpp"
#include <queue>
struct OsmElement;
namespace generator
{
// Abstract class FeatureMakerBase is responsible for the conversion OsmElement to FeatureBuilder.
// The main task of this class is to create features of the necessary types.
// At least one feature should turn out from one OSM element. You can get several features from one element.
class FeatureMakerBase
{
public:
using IDRInterfacePtr = std::shared_ptr<cache::IntermediateDataReaderInterface>;
explicit FeatureMakerBase(IDRInterfacePtr const & cache = {}) : m_cache(cache) {}
void SetCache(IDRInterfacePtr const & cache) { m_cache = cache; }
virtual ~FeatureMakerBase() = default;
virtual std::shared_ptr<FeatureMakerBase> Clone() const = 0;
// Reference on element is non const because ftype::GetNameAndType will be call.
virtual bool Add(OsmElement & element);
// The function returns true when the receiving feature was successful and a false when not successful.
bool GetNextFeature(feature::FeatureBuilder & feature);
size_t Size() const;
bool Empty() const;
protected:
virtual bool BuildFromNode(OsmElement & element, FeatureBuilderParams const & params) = 0;
virtual bool BuildFromWay(OsmElement & element, FeatureBuilderParams const & params) = 0;
virtual bool BuildFromRelation(OsmElement & element, FeatureBuilderParams const & params) = 0;
virtual void ParseParams(FeatureBuilderParams & params, OsmElement & element) const = 0;
IDRInterfacePtr m_cache;
std::queue<feature::FeatureBuilder> m_queue;
};
void TransformToPoint(feature::FeatureBuilder & feature);
void TransformToLine(feature::FeatureBuilder & feature);
feature::FeatureBuilder MakePoint(feature::FeatureBuilder const & feature);
feature::FeatureBuilder MakeLine(feature::FeatureBuilder const & feature);
} // namespace generator

View file

@ -0,0 +1,412 @@
#include "generator/feature_merger.hpp"
#include "indexer/classificator.hpp"
#include "indexer/feature_algo.hpp"
#include "indexer/feature_visibility.hpp"
#include "coding/point_coding.hpp"
using namespace feature;
MergedFeatureBuilder::MergedFeatureBuilder(FeatureBuilder const & fb) : FeatureBuilder(fb), m_isRound(false)
{
m_params.FinishAddingTypes();
}
void MergedFeatureBuilder::SetRound()
{
m_isRound = true;
m_roundBounds[0] = m_roundBounds[1] = GetOuterGeometry();
}
void MergedFeatureBuilder::AppendFeature(MergedFeatureBuilder const & fb, bool fromBegin, bool toBack)
{
// Also merge Osm IDs for debugging
m_osmIds.insert(m_osmIds.end(), fb.m_osmIds.begin(), fb.m_osmIds.end());
PointSeq & thisG = m_polygons.front();
PointSeq const & fbG = fb.GetOuterGeometry();
if (fb.m_isRound)
{
if (toBack)
m_roundBounds[1] = fbG;
else
m_roundBounds[0] = fbG;
return;
}
if (toBack)
m_roundBounds[1].clear();
else
m_roundBounds[0].clear();
m_isRound = false;
using namespace feature;
if (fromBegin)
{
if (toBack)
{
thisG.insert(thisG.end(), fbG.begin() + 1, fbG.end());
CalcRect(fbG.begin() + 1, fbG.end(), m_limitRect);
}
else
{
thisG.insert(thisG.begin(), fbG.begin(), fbG.end() - 1);
CalcRect(fbG.begin(), fbG.end() - 1, m_limitRect);
}
}
else if (toBack)
{
thisG.insert(thisG.end(), fbG.rbegin() + 1, fbG.rend());
CalcRect(fbG.rbegin() + 1, fbG.rend(), m_limitRect);
}
else
{
thisG.insert(thisG.begin(), fbG.rbegin(), fbG.rend() - 1);
CalcRect(fbG.rbegin(), fbG.rend() - 1, m_limitRect);
}
}
bool MergedFeatureBuilder::EqualGeometry(MergedFeatureBuilder const & fb) const
{
return (GetOuterGeometry() == fb.GetOuterGeometry());
}
std::pair<m2::PointD, bool> MergedFeatureBuilder::GetKeyPoint(size_t i) const
{
// 1. check first rounds
size_t sz = m_roundBounds[0].size();
if (i < sz)
return std::make_pair(m_roundBounds[0][i], false);
i -= sz;
// 2. check first point
if (i == 0)
return std::make_pair(FirstPoint(), false);
// 3. check last rounds
--i;
sz = m_roundBounds[1].size();
if (i < sz)
return std::make_pair(m_roundBounds[1][i], true);
i -= sz;
// 4. return last point
ASSERT_EQUAL(i, 0, ());
return std::make_pair(LastPoint(), true);
}
size_t MergedFeatureBuilder::GetKeyPointsCount() const
{
return m_roundBounds[0].size() + m_roundBounds[1].size() + 2;
}
double MergedFeatureBuilder::GetSquaredLength() const
{
PointSeq const & poly = GetOuterGeometry();
double sqLen = 0.0;
for (size_t i = 1; i < poly.size(); ++i)
sqLen += poly[i - 1].SquaredLength(poly[i]);
return sqLen;
}
FeatureMergeProcessor::Key FeatureMergeProcessor::GetKey(m2::PointD const & p)
{
return PointToInt64Obsolete(p, m_coordBits);
}
FeatureMergeProcessor::FeatureMergeProcessor(uint32_t coordBits) : m_coordBits(coordBits) {}
void FeatureMergeProcessor::operator()(FeatureBuilder const & fb)
{
this->operator()(new MergedFeatureBuilder(fb));
}
void FeatureMergeProcessor::operator()(MergedFeatureBuilder * p)
{
Key const k1 = GetKey(p->FirstPoint());
Key const k2 = GetKey(p->LastPoint());
m_map[k1].push_back(p);
if (k1 != k2)
m_map[k2].push_back(p);
else
{
// All of roundabout's points are considered for possible continuation of the line.
// Effectively a roundabout itself is discarded and is used only for merging adjoining lines together.
///@ todo Do it only for small round features!
p->SetRound();
p->ForEachMiddlePoints(std::bind(&FeatureMergeProcessor::Insert, this, std::placeholders::_1, p));
}
}
void FeatureMergeProcessor::Insert(m2::PointD const & pt, MergedFeatureBuilder * p)
{
m_map[GetKey(pt)].push_back(p);
}
void FeatureMergeProcessor::Remove(Key key, MergedFeatureBuilder const * p)
{
auto i = m_map.find(key);
if (i != m_map.end())
{
MergedFeatureBuilders & v = i->second;
v.erase(remove(v.begin(), v.end(), p), v.end());
if (v.empty())
m_map.erase(i);
}
}
void FeatureMergeProcessor::Remove(MergedFeatureBuilder const * p)
{
Key const k1 = GetKey(p->FirstPoint());
Key const k2 = GetKey(p->LastPoint());
Remove(k1, p);
if (k1 != k2)
Remove(k2, p);
else
{
ASSERT(p->IsRound(), ());
p->ForEachMiddlePoints(std::bind(&FeatureMergeProcessor::Remove1, this, std::placeholders::_1, p));
}
}
void FeatureMergeProcessor::DoMerge(FeatureEmitterIFace & emitter)
{
while (!m_map.empty())
{
// Get any starting feature.
MergedFeatureBuilders & vS = m_map.begin()->second;
CHECK(!vS.empty(), ());
MergedFeatureBuilder * p = vS.front(); // may be 'back' is better
// Remove next processing type. If it's a last type - remove from map.
uint32_t type;
bool isRemoved = false;
if (p->PopAnyType(type))
{
isRemoved = true;
Remove(p);
}
// We will merge to the copy of p.
MergedFeatureBuilder curr(*p);
curr.SetType(type);
// Iterate through key points while merging.
// Key points are either ends of the line or any point on the "roundabout" if the line ends with it.
size_t ind = 0;
while (ind < curr.GetKeyPointsCount()) // GetKeyPointsCount() can be different on each iteration
{
std::pair<m2::PointD, bool> const pt = curr.GetKeyPoint(ind++);
auto it = m_map.find(GetKey(pt.first));
MergedFeatureBuilder * pp = 0;
if (it != m_map.end())
{
// Find the shortest connected line feature to continue,
// it helps to spread points more evenly between the features and to avoid producing too long lines.
double bestPr = std::numeric_limits<double>::max();
for (size_t i = 0; i < it->second.size(); ++i)
{
MergedFeatureBuilder * pTest = it->second[i];
if (pTest->HasType(type))
{
double const pr = pTest->GetSquaredLength();
// It's not necessery assert, because it's possible in source data
// TODO(pastk) : likely caused by degenerate closed lines.
// ASSERT_GREATER(pr, 0.0, ());
if (pr < bestPr)
{
pp = pTest;
bestPr = pr;
}
}
}
// Merge the current feature with the best connected feature.
if (pp)
{
bool const toBack = pt.second;
bool fromBegin = true;
if ((pt.first.SquaredLength(pp->FirstPoint()) > pt.first.SquaredLength(pp->LastPoint())) == toBack)
fromBegin = false;
curr.AppendFeature(*pp, fromBegin, toBack);
if (pp->PopExactType(type))
{
Remove(pp);
delete pp;
}
// start from the beginning if we have a successful merge
ind = 0;
}
}
}
if (m_last.NotEmpty() && m_last.EqualGeometry(curr))
{
// curr is equal with m_last by geometry - just add new type to m_last
if (!m_last.HasType(type))
m_last.AddType(type);
}
else
{
// emit m_last and set curr as last processed feature (m_last)
if (m_last.NotEmpty())
emitter(m_last);
m_last = curr;
}
// Delete if the feature was removed from map.
if (isRemoved)
delete p;
}
if (m_last.NotEmpty())
emitter(m_last);
}
uint32_t FeatureTypesProcessor::GetType(char const * arr[], size_t n)
{
uint32_t const type = classif().GetTypeByPath(std::vector<std::string>(arr, arr + n));
CHECK_NOT_EQUAL(type, ftype::GetEmptyValue(), ());
return type;
}
void FeatureTypesProcessor::CorrectType(uint32_t & t) const
{
if (m_dontNormalize.count(t) > 0)
return;
// 1. get normalized type:
// highway-motorway-bridge => highway-motorway
ftype::TruncValue(t, 2);
// 2. get mapping type:
auto i = m_mapping.find(t);
if (i != m_mapping.end())
t = i->second;
}
void FeatureTypesProcessor::SetMappingTypes(char const * arr1[2], char const * arr2[2])
{
m_mapping[GetType(arr1, 2)] = GetType(arr2, 2);
}
MergedFeatureBuilder * FeatureTypesProcessor::operator()(FeatureBuilder const & fb)
{
MergedFeatureBuilder * p = new MergedFeatureBuilder(fb);
p->ForEachChangeTypes(do_change_types(*this));
// do preprocessing after types correction
if (!feature::PreprocessForWorldMap(*p))
{
delete p;
return 0;
}
// zero all additional params for world merged features (names, ranks, ...)
p->ZeroParams();
return p;
}
class TypeCheckBase
{
protected:
static uint32_t GetRegionType()
{
static uint32_t regionType = classif().GetTypeByPath({"place", "region"});
return regionType;
}
public:
int m_lowScale, m_upScale;
TypeCheckBase(int lowScale, int upScale) : m_lowScale(lowScale), m_upScale(upScale) {}
using RangeT = std::pair<int, int>;
static RangeT GetScaleRange(uint32_t type) { return feature::GetDrawableScaleRange(type); }
static bool IsEmptyRange(RangeT const & range) { return range.first == -1; }
bool IsBadRange(RangeT const & range) const { return (range.first > m_upScale || range.second < m_lowScale); }
};
class TypeCheckWorld : public TypeCheckBase
{
public:
bool m_isRegion = false;
TypeCheckWorld() : TypeCheckBase(0, scales::GetUpperWorldScale()) {}
/// @return true If |type| should be removed.
bool operator()(uint32_t type)
{
// Keep place=region types in World.mwm for search, even when they are not visible.
if (type == GetRegionType())
{
m_isRegion = true;
return false;
}
auto const range = GetScaleRange(type);
return IsEmptyRange(range) || IsBadRange(range);
}
};
class TypeCheckCountry : public TypeCheckBase
{
public:
TypeCheckCountry() : TypeCheckBase(scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()) {}
/// @return true If |type| should be removed.
bool operator()(uint32_t type) const
{
// Do not keep place=region in countries.
if (type == GetRegionType())
return true;
auto const range = GetScaleRange(type);
// Don't remove non-drawable types here, since this case is processed before
// feature::TypeAlwaysExists or FeatureBuilder::RemoveInvalidTypes.
if (IsEmptyRange(range))
return false;
return IsBadRange(range);
}
};
namespace feature
{
bool PreprocessForWorldMap(FeatureBuilder & fb)
{
TypeCheckWorld checker;
if (fb.RemoveTypesIf(checker))
return false;
if (!checker.m_isRegion)
fb.RemoveNameIfInvisible(checker.m_lowScale, checker.m_upScale);
/// @todo Do we need all Metadata for point/area World features? We delete meta for linear in ZeroParams.
return true;
}
bool PreprocessForCountryMap(FeatureBuilder & fb)
{
return !fb.RemoveTypesIf(TypeCheckCountry());
}
} // namespace feature

View file

@ -0,0 +1,129 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "generator/feature_emitter_iface.hpp"
#include <map>
#include <set>
#include <utility>
#include <vector>
/// Feature builder class that used while feature type processing and merging.
class MergedFeatureBuilder : public feature::FeatureBuilder
{
bool m_isRound;
PointSeq m_roundBounds[2];
public:
MergedFeatureBuilder() : m_isRound(false) {}
MergedFeatureBuilder(feature::FeatureBuilder const & fb);
void SetRound();
bool IsRound() const { return m_isRound; }
void ZeroParams() { m_params.MakeZero(); }
void AppendFeature(MergedFeatureBuilder const & fb, bool fromBegin, bool toBack);
bool EqualGeometry(MergedFeatureBuilder const & fb) const;
inline bool NotEmpty() const { return !GetGeometry().empty(); }
inline m2::PointD FirstPoint() const { return GetOuterGeometry().front(); }
inline m2::PointD LastPoint() const { return GetOuterGeometry().back(); }
inline bool PopAnyType(uint32_t & type) { return m_params.PopAnyType(type); }
template <class ToDo>
void ForEachChangeTypes(ToDo toDo)
{
for_each(m_params.m_types.begin(), m_params.m_types.end(), toDo);
m_params.FinishAddingTypes();
}
template <class ToDo>
void ForEachMiddlePoints(ToDo toDo) const
{
PointSeq const & poly = GetOuterGeometry();
for (size_t i = 1; i < poly.size() - 1; ++i)
toDo(poly[i]);
}
std::pair<m2::PointD, bool> GetKeyPoint(size_t i) const;
size_t GetKeyPointsCount() const;
// Used to determine which connected line to merge.
double GetSquaredLength() const;
};
/// Feature merger.
class FeatureMergeProcessor
{
using Key = int64_t;
Key GetKey(m2::PointD const & p);
MergedFeatureBuilder m_last;
using MergedFeatureBuilders = std::vector<MergedFeatureBuilder *>;
using KeyToMergedFeatureBuilders = std::map<Key, MergedFeatureBuilders>;
KeyToMergedFeatureBuilders m_map;
void Insert(m2::PointD const & pt, MergedFeatureBuilder * p);
void Remove(Key key, MergedFeatureBuilder const * p);
inline void Remove1(m2::PointD const & pt, MergedFeatureBuilder const * p) { Remove(GetKey(pt), p); }
void Remove(MergedFeatureBuilder const * p);
uint8_t m_coordBits;
public:
FeatureMergeProcessor(uint32_t coordBits);
void operator()(feature::FeatureBuilder const & fb);
void operator()(MergedFeatureBuilder * p);
void DoMerge(FeatureEmitterIFace & emitter);
};
/// Feature types corrector.
class FeatureTypesProcessor
{
std::set<uint32_t> m_dontNormalize;
std::map<uint32_t, uint32_t> m_mapping;
static uint32_t GetType(char const * arr[], size_t n);
void CorrectType(uint32_t & t) const;
class do_change_types
{
FeatureTypesProcessor const & m_pr;
public:
do_change_types(FeatureTypesProcessor const & pr) : m_pr(pr) {}
void operator()(uint32_t & t) { m_pr.CorrectType(t); }
};
public:
/// For example: highway-motorway_link-* => highway-motorway.
void SetMappingTypes(char const * arr1[2], char const * arr2[2]);
/// Leave original types, for example: boundary-administrative-2.
template <size_t N>
void SetDontNormalizeType(char const * (&arr)[N])
{
m_dontNormalize.insert(GetType(arr, N));
}
MergedFeatureBuilder * operator()(feature::FeatureBuilder const & fb);
};
namespace feature
{
/// @return false If fb became invalid (no any suitable types).
//@{
bool PreprocessForWorldMap(FeatureBuilder & fb);
bool PreprocessForCountryMap(FeatureBuilder & fb);
//@}
} // namespace feature

View file

@ -0,0 +1,291 @@
#include "generator/feature_processing_layers.hpp"
#include "generator/feature_maker_base.hpp"
#include "generator/feature_merger.hpp"
#include "generator/generate_info.hpp"
#include "generator/place_processor.hpp"
#include "indexer/feature_visibility.hpp"
#include "indexer/ftypes_matcher.hpp"
#include "base/assert.hpp"
#include "base/geo_object_id.hpp"
namespace generator
{
using namespace feature;
namespace
{
void FixLandType(FeatureBuilder & fb)
{
auto const & types = fb.GetTypes();
auto const & isIslandChecker = ftypes::IsIslandChecker::Instance();
auto const & isLandChecker = ftypes::IsLandChecker::Instance();
auto const & isCoastlineChecker = ftypes::IsCoastlineChecker::Instance();
if (isCoastlineChecker(types))
{
fb.PopExactType(isLandChecker.GetLandType());
fb.PopExactType(isCoastlineChecker.GetCoastlineType());
}
else if (isIslandChecker(types) && fb.IsArea())
{
fb.AddType(isLandChecker.GetLandType());
}
}
} // namespace
void LayerBase::Handle(FeatureBuilder & fb)
{
if (m_next)
m_next->Handle(fb);
}
size_t LayerBase::GetChainSize() const
{
size_t size = 0;
auto current = shared_from_this();
while (current)
{
++size;
current = current->m_next;
}
return size;
}
void LayerBase::Add(std::shared_ptr<LayerBase> next)
{
if (m_next)
m_next->Add(next);
else
m_next = next;
}
void RepresentationLayer::Handle(FeatureBuilder & fb)
{
// if (m_complexFeaturesMixer)
// m_complexFeaturesMixer->Process([&](FeatureBuilder & fb){ LayerBase::Handle(fb); }, fb);
auto const sourceType = fb.GetMostGenericOsmId().GetType();
auto const geomType = fb.GetGeomType();
// There is a copy of params here, if there is a reference here, then the params can be
// implicitly changed at other layers.
auto const params = fb.GetParams();
switch (sourceType)
{
case base::GeoObjectId::Type::ObsoleteOsmNode: LayerBase::Handle(fb); break;
case base::GeoObjectId::Type::ObsoleteOsmWay:
{
switch (geomType)
{
case GeomType::Area:
{
// All closed geometry OsmWays fall through here.
// E.g. a closed way with tags [amenity=restaurant][wifi=yes][cuisine=*]
// should be added as an areal feature with corresponding types
// and no extra linear/point features.
// A closed way with a tag [highway=pedestrian] should be added as a line feature only
// (because there are no areal and/or point drules for the highway-pedestrian type).
// But a closed way tagged [highway=pedestrian][area=yes] should be added as both area and line features
// (because there are areal and line drules for the highway-primary-area type).
// Also an [leisure=playground][barrier=fence] should be added
// as an areal amenity-playground and a linear barrier=fence.
// Try to create an areal object first (if there are corresponding drules),
// otherwise try to create a point object.
HandleArea(fb, params);
// CanBeLine ignores exceptional types from TypeAlwaysExists / IsUsefulNondrawableType.
if (CanBeLine(params))
{
auto featureLine = MakeLine(fb);
LayerBase::Handle(featureLine);
}
break;
}
case GeomType::Line: LayerBase::Handle(fb); break;
default: UNREACHABLE(); break;
}
break;
}
case base::GeoObjectId::Type::ObsoleteOsmRelation:
{
switch (geomType)
{
case GeomType::Area: HandleArea(fb, params); break;
// We transform place relations into points (see BuildFromRelation).
case GeomType::Point: LayerBase::Handle(fb); break;
default: UNREACHABLE(); break;
}
break;
}
default: UNREACHABLE(); break;
}
}
void RepresentationLayer::HandleArea(FeatureBuilder & fb, FeatureBuilderParams const & params)
{
LayerBase::Handle(fb);
fb.SetParams(params);
}
// static
bool RepresentationLayer::CanBeArea(FeatureParams const & params)
{
return CanGenerateLike(params.m_types, GeomType::Area);
}
// static
bool RepresentationLayer::CanBePoint(FeatureParams const & params)
{
return CanGenerateLike(params.m_types, GeomType::Point);
}
// static
bool RepresentationLayer::CanBeLine(FeatureParams const & params)
{
return CanGenerateLike(params.m_types, GeomType::Line);
}
void PrepareFeatureLayer::Handle(FeatureBuilder & fb)
{
if (RemoveUselessTypes(fb.GetParams().m_types, fb.GetGeomType()))
{
fb.PreSerializeAndRemoveUselessNamesForIntermediate();
FixLandType(fb);
LayerBase::Handle(fb);
}
}
void RepresentationCoastlineLayer::Handle(FeatureBuilder & fb)
{
auto const sourceType = fb.GetMostGenericOsmId().GetType();
auto const geomType = fb.GetGeomType();
switch (sourceType)
{
case base::GeoObjectId::Type::ObsoleteOsmNode: break;
case base::GeoObjectId::Type::ObsoleteOsmWay:
{
switch (geomType)
{
case GeomType::Area:
case GeomType::Line: LayerBase::Handle(fb); break;
default: UNREACHABLE(); break;
}
break;
}
case base::GeoObjectId::Type::ObsoleteOsmRelation: break;
default: UNREACHABLE(); break;
}
}
void PrepareCoastlineFeatureLayer::Handle(FeatureBuilder & fb)
{
if (fb.IsArea())
{
auto & params = fb.GetParams();
RemoveUselessTypes(params.m_types, fb.GetGeomType());
}
fb.PreSerializeAndRemoveUselessNamesForIntermediate();
auto const & isCoastlineChecker = ftypes::IsCoastlineChecker::Instance();
auto const kCoastType = isCoastlineChecker.GetCoastlineType();
fb.SetType(kCoastType);
LayerBase::Handle(fb);
}
WorldLayer::WorldLayer(std::string const & popularityFilename) : m_filter(popularityFilename) {}
void WorldLayer::Handle(FeatureBuilder & fb)
{
if (fb.RemoveInvalidTypes() && m_filter.IsAccepted(fb))
LayerBase::Handle(fb);
}
void CountryLayer::Handle(FeatureBuilder & fb)
{
if (fb.RemoveInvalidTypes() && PreprocessForCountryMap(fb))
LayerBase::Handle(fb);
}
/*
void PreserializeLayer::Handle(FeatureBuilder & fb)
{
if (fb.PreSerialize())
LayerBase::Handle(fb);
}
ComplexFeaturesMixer::ComplexFeaturesMixer(std::unordered_set<CompositeId> const & hierarchyNodesSet)
: m_hierarchyNodesSet(hierarchyNodesSet)
, m_complexEntryType(classif().GetTypeByPath({"complex_entry"}))
{
}
std::shared_ptr<ComplexFeaturesMixer> ComplexFeaturesMixer::Clone()
{
return std::make_shared<ComplexFeaturesMixer>(m_hierarchyNodesSet);
}
void ComplexFeaturesMixer::Process(std::function<void(FeatureBuilder &)> next,
FeatureBuilder const & fb)
{
if (!next)
return;
// For rendering purposes all hierarchy objects with closed geometry except building parts must
// be stored as one areal object and/or one linear object.
if (fb.IsPoint() || !fb.IsGeometryClosed())
return;
auto const id = MakeCompositeId(fb);
if (m_hierarchyNodesSet.count(id) == 0)
return;
static auto const & buildingPartChecker = ftypes::IsBuildingPartChecker::Instance();
if (buildingPartChecker(fb.GetTypes()))
return;
auto const canBeArea = RepresentationLayer::CanBeArea(fb.GetParams());
auto const canBeLine = RepresentationLayer::CanBeLine(fb.GetParams());
if (!canBeArea)
{
LOG(LINFO, ("Adding an areal complex feature for", fb.GetMostGenericOsmId()));
auto complexFb = MakeComplexAreaFrom(fb);
next(complexFb);
}
if (!canBeLine)
{
LOG(LINFO, ("Adding a linear complex feature for", fb.GetMostGenericOsmId()));
auto complexFb = MakeComplexLineFrom(fb);
next(complexFb);
}
}
FeatureBuilder ComplexFeaturesMixer::MakeComplexLineFrom(FeatureBuilder const & fb)
{
CHECK(fb.IsArea() || fb.IsLine(), ());
CHECK(fb.IsGeometryClosed(), ());
auto lineFb = MakeLine(fb);
auto & params = lineFb.GetParams();
params.ClearName();
params.ClearMetadata();
params.SetType(m_complexEntryType);
return lineFb;
}
FeatureBuilder ComplexFeaturesMixer::MakeComplexAreaFrom(FeatureBuilder const & fb)
{
CHECK(fb.IsArea() || fb.IsLine(), ());
CHECK(fb.IsGeometryClosed(), ());
auto areaFb = fb;
areaFb.SetArea();
auto & params = areaFb.GetParams();
params.ClearName();
params.ClearMetadata();
params.SetType(m_complexEntryType);
return areaFb;
}
*/
} // namespace generator

View file

@ -0,0 +1,169 @@
#pragma once
#include "generator/affiliation.hpp"
#include "generator/feature_builder.hpp"
#include "generator/features_processing_helpers.hpp"
#include "generator/filter_world.hpp"
#include "generator/processor_interface.hpp"
#include <functional>
#include <memory>
#include <string>
#include <unordered_set>
namespace generator
{
// This is the base layer class. Inheriting from it allows you to create a chain of layers.
class LayerBase : public std::enable_shared_from_this<LayerBase>
{
public:
LayerBase() = default;
virtual ~LayerBase() = default;
// The function works in linear time from the number of layers that exist after that.
virtual void Handle(feature::FeatureBuilder & fb);
size_t GetChainSize() const;
void Add(std::shared_ptr<LayerBase> next);
private:
std::shared_ptr<LayerBase> m_next;
};
// Responsibility of class RepresentationLayer is converting features from one form to another for countries.
// Here we can use the knowledge of the rules for drawing objects.
// Osm object can be represented as feature of following geometry types: point, line, area depending on
// its types and geometry. Sometimes one osm object can be represented as two features e.g. object with
// closed geometry with types "leisure=playground" and "barrier=fence" splits into two objects: area object
// with type "leisure=playground" and line object with type "barrier=fence".
class RepresentationLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
static bool CanBeArea(FeatureParams const & params);
static bool CanBePoint(FeatureParams const & params);
static bool CanBeLine(FeatureParams const & params);
private:
void HandleArea(feature::FeatureBuilder & fb, FeatureBuilderParams const & params);
// std::shared_ptr<ComplexFeaturesMixer> m_complexFeaturesMixer;
};
// Responsibility of class PrepareFeatureLayer is the removal of unused types and names,
// as well as the preparation of features for further processing for countries.
class PrepareFeatureLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
};
// Responsibility of class RepresentationCoastlineLayer is converting features from one form to
// another for coastlines. Here we can use the knowledge of the rules for drawing objects.
class RepresentationCoastlineLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
};
// Responsibility of class PrepareCoastlineFeatureLayer is the removal of unused types and names,
// as well as the preparation of features for further processing for coastlines.
class PrepareCoastlineFeatureLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
};
class WorldLayer : public LayerBase
{
public:
explicit WorldLayer(std::string const & popularityFilename);
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
private:
FilterWorld m_filter;
};
class CountryLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
};
/*
class PreserializeLayer : public LayerBase
{
public:
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override;
};
*/
class AffiliationsFeatureLayer : public LayerBase
{
public:
AffiliationsFeatureLayer(size_t bufferSize, AffiliationInterfacePtr affiliation,
std::shared_ptr<FeatureProcessorQueue> queue)
: m_bufferSize(bufferSize)
, m_affiliation(std::move(affiliation))
, m_queue(std::move(queue))
{
m_buffer.reserve(m_bufferSize);
}
// LayerBase overrides:
void Handle(feature::FeatureBuilder & fb) override
{
feature::FeatureBuilder::Buffer buffer;
feature::serialization_policy::MaxAccuracy::Serialize(fb, buffer);
m_buffer.emplace_back(std::move(buffer), m_affiliation->GetAffiliations(fb));
if (m_buffer.size() >= m_bufferSize)
AddBufferToQueue();
}
bool AddBufferToQueue()
{
if (m_buffer.empty())
return false;
m_queue->Push(std::move(m_buffer));
m_buffer.clear();
m_buffer.reserve(m_bufferSize);
return true;
}
private:
size_t const m_bufferSize;
std::vector<ProcessedData> m_buffer;
AffiliationInterfacePtr m_affiliation;
std::shared_ptr<FeatureProcessorQueue> m_queue;
};
/*
class ComplexFeaturesMixer
{
public:
explicit ComplexFeaturesMixer(std::unordered_set<CompositeId> const & hierarchyNodesSet);
void Process(std::function<void(feature::FeatureBuilder &)> next,
feature::FeatureBuilder const & fb);
std::shared_ptr<ComplexFeaturesMixer> Clone();
private:
feature::FeatureBuilder MakeComplexLineFrom(feature::FeatureBuilder const & fb);
feature::FeatureBuilder MakeComplexAreaFrom(feature::FeatureBuilder const & fb);
std::unordered_set<CompositeId> const & m_hierarchyNodesSet;
uint32_t const m_complexEntryType;
};
*/
} // namespace generator

View file

@ -0,0 +1,10 @@
project(feature_segments_checker)
set(SRC feature_segments_checker.cpp)
omim_add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME}
generator
gflags::gflags
)

View file

@ -0,0 +1,367 @@
#include "generator/srtm_parser.hpp"
#include "routing_common/bicycle_model.hpp"
#include "indexer/classificator_loader.hpp"
#include "indexer/feature.hpp"
#include "indexer/feature_processor.hpp"
#include "geometry/mercator.hpp"
#include "geometry/point2d.hpp"
#include "geometry/point_with_altitude.hpp"
#include "base/checked_cast.hpp"
#include "base/logging.hpp"
#include "base/math.hpp"
#include <cmath>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <gflags/gflags.h>
DEFINE_string(srtm_dir_path, "", "Path to directory with SRTM files");
DEFINE_string(mwm_file_path, "", "Path to an mwm file.");
namespace feature_segments_checker
{
using namespace feature;
routing::BicycleModel const & GetBicycleModel()
{
static routing::BicycleModel const instance;
return instance;
}
int32_t Coord2RoughCoord(double d)
{
int32_t constexpr kFactor = 100000;
return static_cast<int32_t>(d * kFactor);
}
struct RoughPoint
{
explicit RoughPoint(m2::PointD const & point) : x(Coord2RoughCoord(point.x)), y(Coord2RoughCoord(point.y)) {}
int32_t x;
int32_t y;
};
bool operator<(RoughPoint const & l, RoughPoint const & r)
{
if (l.x != r.x)
return l.x < r.x;
return l.y < r.y;
}
template <typename Cont>
void PrintCont(Cont const & cont, std::string const & title, std::string const & msgText1, std::string const & msgText2)
{
std::cout << std::endl << title << std::endl;
for (auto const & a : cont)
std::cout << a.second << msgText1 << a.first << msgText2 << std::endl;
}
template <typename Cont>
void WriteCSV(Cont const & cont, std::string const & fileName)
{
std::ofstream fout(fileName);
for (auto const & a : cont)
fout << a.first << "," << a.second << std::endl;
}
/// \returns y = k * x + b. It's the expected altitude in meters.
double GetY(double k, double b, double x)
{
return k * x + b;
}
/// \brief Calculates factors |k| and |b| of a line using linear least squares method.
/// \returns false in case of error (e.g. if the line is parallel to the vertical axis)
/// and true otherwise.
bool LinearLeastSquaresFactors(std::vector<double> const & xs, std::vector<double> const & ys, double & k, double & b)
{
double constexpr kEpsilon = 1e-6;
size_t const n = xs.size();
double mx = 0, my = 0, mxy = 0, mx2 = 0;
for (size_t i = 0; i < n; ++i)
{
mx += xs[i] / n;
my += ys[i] / n;
mxy += xs[i] * ys[i] / n;
mx2 += xs[i] * xs[i] / n;
}
if (AlmostEqualAbs(mx * mx, mx2, kEpsilon))
return false;
k = (my * mx - mxy) / (mx * mx - mx2);
b = my - k * mx;
return true;
}
class Processor
{
public:
generator::SrtmTileManager & m_srtmManager;
std::set<RoughPoint> m_uniqueRoadPoints;
/// Key is an altitude difference for a feature in meters. If a feature goes up the key is greater then 0.
/// Value is a number of features.
std::map<geometry::Altitude, uint32_t> m_altitudeDiffs;
/// Key is a length of a feature in meters. Value is a number of features.
std::map<uint32_t, uint32_t> m_featureLength;
/// Key is a length of a feature segment in meters. Value is a segment counter.
std::map<uint32_t, uint32_t> m_segLength;
/// Key is difference between two values:
/// 1. how many meters it's necessary to go up following the feature. If feature wavy
/// calculates only raise meters.
/// 2. the difference in altitude between end and start of features.
/// Value is a segment counter.
std::map<int32_t, uint32_t> m_featureWave;
/// Key is number of meters which is necessary to go up following the feature.
/// Value is a number of features.
std::map<int32_t, uint32_t> m_featureUp;
/// Key is number of meters which is necessary to go down following the feature.
/// Value is a number of features.
std::map<int32_t, uint32_t> m_featureDown;
/// Key is number of meters. It shows altitude deviation of intermediate feature points
/// from linear model.
/// Value is a number of features.
std::map<geometry::Altitude, uint32_t> m_diffFromLinear;
/// Key is number of meters. It shows altitude deviation of intermediate feature points
/// from line calculated base on least squares method for all feature points.
/// Value is a number of features.
std::map<geometry::Altitude, uint32_t> m_leastSquaresDiff;
/// Number of features for GetBicycleModel().IsRoad(feature) == true.
uint32_t m_roadCount;
/// Number of features for empty features with GetBicycleModel().IsRoad(feature).
uint32_t m_emptyRoadCount;
/// Point counter for feature where GetBicycleModel().IsRoad(feature) == true.
uint32_t m_roadPointCount;
/// Number of features for GetBicycleModel().IsRoad(feature) != true.
uint32_t m_notRoadCount;
geometry::Altitude m_minAltitude = geometry::kInvalidAltitude;
geometry::Altitude m_maxAltitude = geometry::kInvalidAltitude;
explicit Processor(generator::SrtmTileManager & manager)
: m_srtmManager(manager)
, m_roadCount(0)
, m_emptyRoadCount(0)
, m_roadPointCount(0)
, m_notRoadCount(0)
{}
void operator()(FeatureType & f, uint32_t const & id)
{
f.ParseAllBeforeGeometry();
if (!GetBicycleModel().IsRoad(feature::TypesHolder(f)))
{
++m_notRoadCount;
return;
}
f.ParseGeometry(FeatureType::BEST_GEOMETRY);
uint32_t const numPoints = base::asserted_cast<uint32_t>(f.GetPointsCount());
if (numPoints == 0)
{
++m_emptyRoadCount;
return;
}
++m_roadCount;
m_roadPointCount += numPoints;
for (uint32_t i = 0; i < numPoints; ++i)
m_uniqueRoadPoints.insert(RoughPoint(f.GetPoint(i)));
// Preparing feature altitude and length.
geometry::Altitudes pointAltitudes(numPoints);
std::vector<double> pointDists(numPoints);
double distFromStartMeters = 0;
for (uint32_t i = 0; i < numPoints; ++i)
{
// Feature segment altitude.
geometry::Altitude altitude = m_srtmManager.GetAltitude(mercator::ToLatLon(f.GetPoint(i)));
pointAltitudes[i] = altitude == geometry::kInvalidAltitude ? 0 : altitude;
if (i == 0)
{
pointDists[i] = 0;
continue;
}
// Feature segment length.
double const segmentLengthMeters = mercator::DistanceOnEarth(f.GetPoint(i - 1), f.GetPoint(i));
distFromStartMeters += segmentLengthMeters;
pointDists[i] = distFromStartMeters;
}
// Min and max altitudes.
for (auto const a : pointAltitudes)
{
if (m_minAltitude == geometry::kInvalidAltitude || a < m_minAltitude)
m_minAltitude = a;
if (m_maxAltitude == geometry::kInvalidAltitude || a > m_maxAltitude)
m_maxAltitude = a;
}
// Feature length and feature segment length.
double realFeatureLengthMeters = 0.0;
for (uint32_t i = 0; i + 1 < numPoints; ++i)
{
// Feature segment length.
double const realSegmentLengthMeters = pointDists[i + 1] - pointDists[i];
m_segLength[static_cast<uint32_t>(floor(realSegmentLengthMeters))]++;
// Feature length.
realFeatureLengthMeters += realSegmentLengthMeters;
}
m_featureLength[static_cast<uint32_t>(realFeatureLengthMeters)]++;
// Feature altitude difference.
geometry::Altitude const startAltitude = pointAltitudes[0];
geometry::Altitude const endAltitude = pointAltitudes[numPoints - 1];
int16_t const altitudeDiff = endAltitude - startAltitude;
m_altitudeDiffs[altitudeDiff]++;
// Wave feature factor. Climb minus altitude difference.
int32_t climb = 0;
int32_t up = 0;
int32_t down = 0;
for (uint32_t i = 0; i + 1 < numPoints; ++i)
{
auto const segAltDiff = pointAltitudes[i + 1] - pointAltitudes[i];
if (altitudeDiff >= 0)
{
// If the feature goes up generaly calculates how many meters it's necessary
// to go up following the feature.
if (segAltDiff > 0)
climb += segAltDiff;
}
else
{
// If the feature goes down generaly calculates how many meters it's necessary
// to go down following the feature.
if (segAltDiff < 0)
climb += segAltDiff;
}
if (segAltDiff >= 0)
up += segAltDiff;
else
down += segAltDiff;
}
// Estimates monotony of the feature. If climb == altitudeDiff it's monotonous.
// If not it's wavy. The more abs(climb - altitudeDiff) the more wavy the feature.
int32_t const waveFactor = climb - altitudeDiff;
m_featureWave[waveFactor]++;
m_featureUp[up]++;
m_featureDown[down]++;
// Altitude deviation of internal feature points from linear model.
if (realFeatureLengthMeters == 0.0)
return;
double const k = (endAltitude - startAltitude) / realFeatureLengthMeters;
for (uint32_t i = 1; i + 1 < numPoints; ++i)
{
int32_t const deviation =
static_cast<geometry::Altitude>(GetY(k, startAltitude, pointDists[i])) - pointAltitudes[i];
m_diffFromLinear[deviation]++;
}
// Linear least squares for feature points.
{
double k;
double b;
std::vector<double> const pointAltitudesMeters(pointAltitudes.begin(), pointAltitudes.end());
if (!LinearLeastSquaresFactors(pointDists, pointAltitudesMeters, k, b))
return;
for (uint32_t i = 0; i < numPoints; ++i)
{
geometry::Altitude const deviation =
static_cast<geometry::Altitude>(GetY(k, b, pointDists[i])) - pointAltitudes[i];
m_leastSquaresDiff[deviation]++;
}
}
}
};
double CalculateEntropy(std::map<geometry::Altitude, uint32_t> const & diffFromLinear)
{
uint32_t innerPointCount = 0;
for (auto const & f : diffFromLinear)
innerPointCount += f.second;
if (innerPointCount == 0)
return 0.0;
double entropy = 0;
for (auto const & f : diffFromLinear)
{
double const p = static_cast<double>(f.second) / innerPointCount;
entropy += -p * log2(p);
}
return entropy;
}
} // namespace feature_segments_checker
int main(int argc, char ** argv)
{
using namespace feature_segments_checker;
gflags::SetUsageMessage("This tool extracts some staticstics about features and its altitudes.");
gflags::ParseCommandLineFlags(&argc, &argv, true);
LOG(LINFO, ("srtm_dir_path =", FLAGS_srtm_dir_path));
LOG(LINFO, ("mwm_file_path =", FLAGS_mwm_file_path));
classificator::Load();
generator::SrtmTileManager manager(FLAGS_srtm_dir_path);
Processor processor(manager);
ForEachFeature(FLAGS_mwm_file_path, processor);
PrintCont(processor.m_altitudeDiffs, "Altitude difference between start and end of features.",
" feature(s) with altitude difference ", " meter(s)");
WriteCSV(processor.m_altitudeDiffs, "altitude_difference.csv");
PrintCont(processor.m_featureLength, "Feature length.", " feature(s) with length ", " meter(s)");
WriteCSV(processor.m_featureLength, "feature_length.csv");
PrintCont(processor.m_segLength, "Feature segment length.", " segment(s) with length ", " meter(s)");
PrintCont(processor.m_featureWave, "Wave factor", " feature(s) with wave factor ", "");
WriteCSV(processor.m_featureWave, "feature_wave.csv");
PrintCont(processor.m_featureUp, "Feature go up in meters", " feature(s) go up ", " meter(s)");
WriteCSV(processor.m_featureUp, "feature_up.csv");
PrintCont(processor.m_featureDown, "Feature go down in meters", " feature(s) go down ", " meter(s)");
WriteCSV(processor.m_featureDown, "feature_down.csv");
PrintCont(processor.m_diffFromLinear, "Altitude deviation of internal feature points from linear model.",
" internal feature point(s) deviate from linear model with ", " meter(s)");
PrintCont(processor.m_leastSquaresDiff, "Altitude deviation of feature points from least squares line.",
" internal feature point(s) deviate from linear model with ", " meter(s)");
using std::cout, std::endl;
cout << endl << FLAGS_mwm_file_path << endl;
cout << "Road feature count = " << processor.m_roadCount << endl;
cout << "Empty road feature count = " << processor.m_emptyRoadCount << endl;
cout << "Unique road points count = " << processor.m_uniqueRoadPoints.size() << endl;
cout << "All road point count = " << processor.m_roadPointCount << endl;
cout << "Not road feature count = " << processor.m_notRoadCount << endl;
cout << "Entropy for altitude deviation of internal feature points from linear model = "
<< CalculateEntropy(processor.m_diffFromLinear) << endl;
cout << "Entropy for altitude deviation of feature points from least squares line = "
<< CalculateEntropy(processor.m_leastSquaresDiff) << endl;
cout << "Min altitude of the mwm = " << processor.m_minAltitude << endl;
cout << "Max altitude of the mwm = " << processor.m_maxAltitude << endl;
return 0;
}

View file

@ -0,0 +1,438 @@
#include "generator/feature_sorter.hpp"
#include "generator/borders.hpp"
#include "generator/boundary_postcodes_enricher.hpp"
#include "generator/feature_builder.hpp"
#include "generator/feature_generator.hpp"
#include "generator/gen_mwm_info.hpp"
#include "generator/geometry_holder.hpp"
#include "generator/region_meta.hpp"
#include "generator/search_index_builder.hpp"
#include "routing/routing_helpers.hpp"
#include "indexer/dat_section_header.hpp"
#include "indexer/feature_impl.hpp"
#include "indexer/scales.hpp"
#include "indexer/scales_patch.hpp"
#include "platform/mwm_version.hpp"
#include "platform/platform.hpp"
#include "coding/files_container.hpp"
#include "coding/point_coding.hpp"
#include "coding/succinct_mapper.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/scope_guard.hpp"
#include "base/string_utils.hpp"
#include "defines.hpp"
#include <limits>
#include <list>
#include <memory>
#include <vector>
namespace feature
{
class FeaturesCollector2 : public FeaturesCollector
{
public:
FeaturesCollector2(std::string const & name, feature::GenerateInfo const & info, DataHeader const & header,
RegionData const & regionData, uint32_t versionDate)
: FeaturesCollector(info.GetTargetFileName(name, FEATURES_FILE_TAG))
, m_filename(info.GetTargetFileName(name))
, m_boundaryPostcodesEnricher(info.GetIntermediateFileName(BOUNDARY_POSTCODES_FILENAME))
, m_header(header)
, m_regionData(regionData)
, m_versionDate(versionDate)
{
for (size_t i = 0; i < m_header.GetScalesCount(); ++i)
{
std::string const postfix = strings::to_string(i);
m_geoFile.push_back(std::make_unique<TmpFile>(info.GetIntermediateFileName(name, GEOMETRY_FILE_TAG + postfix)));
m_trgFile.push_back(std::make_unique<TmpFile>(info.GetIntermediateFileName(name, TRIANGLE_FILE_TAG + postfix)));
}
m_addrFile =
std::make_unique<FileWriter>(info.GetIntermediateFileName(name + DATA_FILE_EXTENSION, TEMP_ADDR_EXTENSION));
}
void Finish() override
{
// write version information
{
FilesContainerW writer(m_filename);
auto w = writer.GetWriter(VERSION_FILE_TAG);
version::WriteVersion(*w, m_versionDate);
}
// write own mwm header
m_header.SetBounds(m_bounds);
{
FilesContainerW writer(m_filename, FileWriter::OP_WRITE_EXISTING);
auto w = writer.GetWriter(HEADER_FILE_TAG);
m_header.Save(*w);
}
// write region info
{
FilesContainerW writer(m_filename, FileWriter::OP_WRITE_EXISTING);
auto w = writer.GetWriter(REGION_INFO_FILE_TAG);
m_regionData.Serialize(*w);
}
// assume like we close files
Flush();
{
FilesContainerW writer(m_filename, FileWriter::OP_WRITE_EXISTING);
auto w = writer.GetWriter(FEATURES_FILE_TAG);
size_t const startOffset = w->Pos();
CHECK(coding::IsAlign8(startOffset), ());
feature::DatSectionHeader header;
header.Serialize(*w);
uint64_t bytesWritten = static_cast<uint64_t>(w->Pos());
coding::WritePadding(*w, bytesWritten);
header.m_featuresOffset = base::asserted_cast<uint32_t>(w->Pos() - startOffset);
ReaderSource<ModelReaderPtr> src(std::make_unique<FileReader>(m_dataFile.GetName()));
rw::ReadAndWrite(src, *w);
header.m_featuresSize = base::asserted_cast<uint32_t>(w->Pos() - header.m_featuresOffset - startOffset);
auto const endOffset = w->Pos();
w->Seek(startOffset);
header.Serialize(*w);
w->Seek(endOffset);
}
// File Writer finalization function with adding section to the main mwm file.
auto const finalizeFn = [this](std::unique_ptr<TmpFile> && tmpFile, std::string const & tag)
{
auto & w = tmpFile->GetWriter();
w.Flush();
FilesContainerW writer(m_filename, FileWriter::OP_WRITE_EXISTING);
writer.Write(w.GetName(), tag);
};
for (size_t i = 0; i < m_header.GetScalesCount(); ++i)
{
finalizeFn(std::move(m_geoFile[i]), GetTagForIndex(GEOMETRY_FILE_TAG, i));
finalizeFn(std::move(m_trgFile[i]), GetTagForIndex(TRIANGLE_FILE_TAG, i));
}
{
FilesContainerW writer(m_filename, FileWriter::OP_WRITE_EXISTING);
auto w = writer.GetWriter(METADATA_FILE_TAG);
m_metadataBuilder.Freeze(*w);
}
if (m_header.GetType() == DataHeader::MapType::Country || m_header.GetType() == DataHeader::MapType::World)
{
FileWriter osm2ftWriter(m_filename + OSM2FEATURE_FILE_EXTENSION);
m_osm2ft.Write(osm2ftWriter);
}
}
void SetBounds(m2::RectD const & bounds) { m_bounds = bounds; }
void operator()(FeatureBuilder & fb)
{
GeometryHolder holder([this](int i) -> FileWriter & { return m_geoFile[i]->GetWriter(); },
[this](int i) -> FileWriter & { return m_trgFile[i]->GetWriter(); }, fb, m_header);
if (!fb.IsPoint())
{
bool const isLine = fb.IsLine();
bool const isArea = fb.IsArea();
CHECK(!isLine || !isArea, (fb.GetMostGenericOsmId()));
m2::RectD const & rect = fb.GetLimitRect();
Polygons const & polys = fb.GetGeometry();
bool const isCoast = fb.IsCoastCell();
int const scalesStart = static_cast<int>(m_header.GetScalesCount()) - 1;
for (int i = scalesStart; i >= 0; --i)
{
int level = m_header.GetScale(i);
/// @todo: Re-checks geom limit rect size via IsDrawableForIndexGeometryOnly()
/// which was already checked in CalculateMidPoints.
if (fb.IsDrawableInRange(scales::PatchMinDrawableScale(i > 0 ? m_header.GetScale(i - 1) + 1 : 0),
scales::PatchMaxDrawableScale(level)))
{
// Increment zoom level for coastline polygons (check and simplification)
// for better visual quality in the first geometry batch or whole WorldCoasts.
/// @todo Probably, better to keep 3 zooms (skip trg0 with fallback to trg1)?
if (isCoast)
{
if (level <= scales::GetUpperWorldScale())
++level;
if (i == 0)
++level;
}
// Simplify and serialize geometry.
// The same line simplification algo is used both for lines
// and areas. For the latter polygon's outline is simplified and
// tesselated afterwards. But e.g. simplifying a circle from 50
// to 10 points will not produce 5 times less triangles.
// Hence difference between numbers of triangles between trg0 and trg1
// is much higher than between trg1 and trg2.
Points points;
// Do not change linear geometry for the upper scale.
if (isLine && i == scalesStart && IsCountry() && routing::IsRoad(fb.GetTypes()))
points = holder.GetSourcePoints();
else if (isLine || holder.NeedProcessTriangles())
SimplifyPoints(level, isCoast, rect, holder.GetSourcePoints(), points);
if (isLine)
holder.AddPoints(points, i);
if (isArea && holder.NeedProcessTriangles())
{
// Simplify and serialize triangles.
Polygons simplified;
bool const isGood = isCoast || scales::IsGoodOutlineForLevel(level, points);
if (isGood)
{
// A polygon's closed outline has 3 points and the 4th should be same as the first.
CHECK_GREATER_OR_EQUAL(points.size(), 4, ());
points.pop_back();
if (polys.size() == 1 && holder.TryToMakeStrip(points))
{
// No need to iterate lower zooms if we made a valid strip.
break;
}
simplified.push_back(std::move(points));
}
else
{
LOG(LDEBUG, ("Area: too small or degenerate 1st (outer) polygon of", polys.size(), ", points count",
points.size(), "at scale", i, DebugPrint(fb)));
continue;
}
auto iH = polys.begin();
for (++iH; iH != polys.end(); ++iH)
{
points.clear();
SimplifyPoints(level, isCoast, rect, *iH, points);
if (scales::IsGoodOutlineForLevel(level, points))
{
// A polygon's closed outline has 3 points and the 4th should be same as the first.
CHECK_GREATER_OR_EQUAL(points.size(), 4, ());
points.pop_back();
simplified.push_back(std::move(points));
}
else
{
LOG(LDEBUG, ("Area: too small or degenerate 2nd+ (inner) polygon of", polys.size(), ", points count",
points.size(), "at scale", i, DebugPrint(fb)));
}
}
if (!simplified.empty())
holder.AddTriangles(simplified, i);
}
}
}
}
// Override "alt_name" with synonym for Country or State for better search matching.
/// @todo Probably, we should store and index OSM's short_name tag.
if (indexer::SynonymsHolder::CanApply(fb.GetTypes()))
{
int8_t const langs[] = {StringUtf8Multilang::kDefaultCode, StringUtf8Multilang::kEnglishCode,
StringUtf8Multilang::kInternationalCode};
bool added = false;
for (int8_t lang : langs)
{
m_synonyms.ForEach(std::string(fb.GetName(lang)), [&fb, &added](std::string const & synonym)
{
// Assign first synonym, skip others.
if (added)
return;
auto oldName = fb.GetName(StringUtf8Multilang::kAltNameCode);
if (!oldName.empty())
LOG(LWARNING, ("Replace", oldName, "with", synonym, "for", fb.GetMostGenericOsmId()));
fb.SetName(StringUtf8Multilang::kAltNameCode, synonym);
added = true;
});
if (added)
break;
}
}
auto & buffer = holder.GetBuffer();
if (fb.PreSerializeAndRemoveUselessNamesForMwm(buffer))
{
fb.SerializeForMwm(buffer, m_header.GetDefGeometryCodingParams());
uint32_t const featureId = WriteFeatureBase(buffer.m_buffer, fb);
// Order is important here:
// 1. Update postcode info.
m_boundaryPostcodesEnricher.Enrich(fb);
// 2. Write address to a file (with possible updated postcode above).
fb.GetParams().SerializeAddress(*m_addrFile);
// 3. Save metadata.
if (!fb.GetMetadata().Empty())
m_metadataBuilder.Put(featureId, fb.GetMetadata());
if (fb.HasOsmIds())
m_osm2ft.AddIds(generator::MakeCompositeId(fb), featureId);
}
}
private:
using Points = std::vector<m2::PointD>;
using Polygons = std::list<Points>;
class TmpFile
{
std::unique_ptr<FileWriter> m_writer;
public:
explicit TmpFile(std::string const & filePath) : m_writer(std::make_unique<FileWriter>(filePath)) {}
FileWriter & GetWriter() { return *m_writer; }
~TmpFile()
{
auto const name = m_writer->GetName();
m_writer.reset();
FileWriter::DeleteFileX(name);
}
};
using TmpFiles = std::vector<std::unique_ptr<TmpFile>>;
bool IsCountry() const { return m_header.GetType() == feature::DataHeader::MapType::Country; }
static void SimplifyPoints(int level, bool isCoast, m2::RectD const & rect, Points const & in, Points & out)
{
if (isCoast)
feature::SimplifyPoints(DistanceToSegmentWithRectBounds(rect), level, in, out);
else
feature::SimplifyPoints(m2::SquaredDistanceFromSegmentToPoint(), level, in, out);
}
std::string m_filename;
// File used for postcodes and search sections build.
std::unique_ptr<FileWriter> m_addrFile;
// Temporary files for sections.
TmpFiles m_geoFile, m_trgFile;
generator::BoundaryPostcodesEnricher m_boundaryPostcodesEnricher;
indexer::MetadataBuilder m_metadataBuilder;
DataHeader m_header;
RegionData m_regionData;
uint32_t m_versionDate;
generator::OsmID2FeatureID m_osm2ft;
indexer::SynonymsHolder m_synonyms;
DISALLOW_COPY_AND_MOVE(FeaturesCollector2);
};
bool GenerateFinalFeatures(feature::GenerateInfo const & info, std::string const & name,
feature::DataHeader::MapType mapType)
{
std::string const srcFilePath = info.GetTmpFileName(name);
std::string const dataFilePath = info.GetTargetFileName(name);
LOG(LINFO, ("Calculating middle points"));
// Store cellIds for middle points.
CalculateMidPoints midPoints;
ForEachFeatureRawFormat(srcFilePath, [&midPoints](FeatureBuilder const & fb, uint64_t pos) { midPoints(fb, pos); });
// Sort features by their middle point.
midPoints.Sort();
// Store sorted features.
{
FileReader reader(srcFilePath);
// Fill mwm header.
DataHeader header;
bool const isWorldOrWorldCoasts = (mapType != DataHeader::MapType::Country);
uint8_t coordBits = kFeatureSorterPointCoordBits;
if (isWorldOrWorldCoasts)
coordBits -= ((scales::GetUpperScale() - scales::GetUpperWorldScale()) / 2);
header.SetType(static_cast<DataHeader::MapType>(mapType));
header.SetGeometryCodingParams(serial::GeometryCodingParams(coordBits, midPoints.GetCenter()));
if (isWorldOrWorldCoasts)
header.SetScales(g_arrWorldScales);
else
header.SetScales(g_arrCountryScales);
RegionData regionData;
if (!ReadRegionData(name, regionData))
LOG(LWARNING, ("No extra data for country:", name));
// Transform features from raw format to optimized format.
try
{
// FeaturesCollector2 will create temporary file `dataFilePath + FEATURES_FILE_TAG`.
// Can't remove file in ~FeaturesCollector2() because of using it in base class dtor logic.
SCOPE_GUARD(_, [&]() { Platform::RemoveFileIfExists(info.GetTargetFileName(name, FEATURES_FILE_TAG)); });
LOG(LINFO, ("Simplifying and filtering geometry for all geom levels"));
FeaturesCollector2 collector(name, info, header, regionData, info.m_versionDate);
for (auto const & point : midPoints.GetVector())
{
ReaderSource<FileReader> src(reader);
src.Skip(point.second);
FeatureBuilder fb;
ReadFromSourceRawFormat(src, fb);
collector(fb);
}
LOG(LINFO, ("Writing features' data to", dataFilePath));
// Update bounds with the limit rect corresponding to region borders.
// Bounds before update can be too big because of big invisible features like a
// relation that contains an entire country's border.
// Borders file may be unavailable when building test mwms.
/// @todo m_targetDir is a final MWM folder like 220702, so there is NO borders folder inside,
/// and the next function call always returns false.
m2::RectD bordersRect;
if (borders::GetBordersRect(info.m_targetDir, name, bordersRect))
collector.SetBounds(bordersRect);
collector.Finish();
}
catch (RootException const & ex)
{
LOG(LCRITICAL, ("MWM writing error:", ex.Msg()));
return false;
}
}
return true;
}
} // namespace feature

View file

@ -0,0 +1,16 @@
#pragma once
#include "generator/generate_info.hpp"
#include "indexer/data_header.hpp"
#include <string>
namespace feature
{
/// Final generation of data from input feature-file.
/// @param path - path to folder with countries;
/// @param name - name of generated country;
bool GenerateFinalFeatures(feature::GenerateInfo const & info, std::string const & name,
feature::DataHeader::MapType mapType);
} // namespace feature

View file

@ -0,0 +1,30 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "base/thread_safe_queue.hpp"
#include <cstddef>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace generator
{
size_t static const kAffiliationsBufferSize = 512;
struct ProcessedData
{
explicit ProcessedData(feature::FeatureBuilder::Buffer && buffer, std::vector<std::string> && affiliations)
: m_buffer(std::move(buffer))
, m_affiliations(std::move(affiliations))
{}
feature::FeatureBuilder::Buffer m_buffer;
std::vector<std::string> m_affiliations;
};
using FeatureProcessorChunk = std::optional<std::vector<ProcessedData>>;
using FeatureProcessorQueue = threads::ThreadSafeQueue<FeatureProcessorChunk>;
} // namespace generator

View file

@ -0,0 +1,24 @@
#include "generator/filter_collection.hpp"
#include "base/stl_helpers.hpp"
namespace generator
{
std::shared_ptr<FilterInterface> FilterCollection::Clone() const
{
auto p = std::make_shared<FilterCollection>();
for (auto const & c : m_collection)
p->Append(c->Clone());
return p;
}
bool FilterCollection::IsAccepted(OsmElement const & element) const
{
return base::AllOf(m_collection, [&](auto const & filter) { return filter->IsAccepted(element); });
}
bool FilterCollection::IsAccepted(feature::FeatureBuilder const & feature) const
{
return base::AllOf(m_collection, [&](auto const & filter) { return filter->IsAccepted(feature); });
}
} // namespace generator

View file

@ -0,0 +1,20 @@
#pragma once
#include "generator/collection_base.hpp"
#include "generator/filter_interface.hpp"
namespace generator
{
// This class allows you to work with a group of filters as with one.
class FilterCollection
: public CollectionBase<std::shared_ptr<FilterInterface>>
, public FilterInterface
{
public:
// FilterInterface overrides:
std::shared_ptr<FilterInterface> Clone() const override;
bool IsAccepted(OsmElement const & element) const override;
bool IsAccepted(feature::FeatureBuilder const & feature) const override;
};
} // namespace generator

View file

@ -0,0 +1,22 @@
#include "generator/filter_complex.hpp"
#include "generator/feature_builder.hpp"
#include "generator/hierarchy.hpp"
#include "indexer/ftypes_matcher.hpp"
namespace generator
{
std::shared_ptr<FilterInterface> FilterComplex::Clone() const
{
return std::make_shared<FilterComplex>();
}
bool FilterComplex::IsAccepted(feature::FeatureBuilder const & fb) const
{
if (!fb.IsArea() && !fb.IsPoint())
return false;
return hierarchy::GetMainType(fb.GetTypes()) != ftype::GetEmptyValue();
}
} // namespace generator

View file

@ -0,0 +1,18 @@
#pragma once
#include "generator/filter_interface.hpp"
#include <memory>
namespace generator
{
// The filter will leave only elements for complexes.
class FilterComplex : public FilterInterface
{
public:
// FilterInterface overrides:
std::shared_ptr<FilterInterface> Clone() const override;
bool IsAccepted(feature::FeatureBuilder const & fb) const override;
};
} // namespace generator

View file

@ -0,0 +1,206 @@
#include "generator/filter_elements.hpp"
#include "base/logging.hpp"
#include "base/stl_helpers.hpp"
#include <algorithm>
#include <fstream>
#include <iterator>
namespace
{
template <typename T>
class Set
{
public:
bool Add(T const & key)
{
if (Contains(key))
return false;
m_vec.push_back(key);
return true;
}
bool Contains(T const & key) const { return base::IsExist(m_vec, key); }
private:
std::vector<T> m_vec;
};
} // namespace
namespace generator
{
bool FilterData::IsMatch(Tags const & elementTags, Tags const & tags)
{
return base::AllOf(tags, [&](OsmElement::Tag const & t)
{
auto const it = base::FindIf(elementTags, [&](OsmElement::Tag const & tag) { return tag.m_key == t.m_key; });
return it == std::end(elementTags) ? false : t.m_value == "*" || it->m_value == t.m_value;
});
}
void FilterData::AddSkippedId(uint64_t id)
{
m_skippedIds.insert(id);
}
void FilterData::AddSkippedTags(Tags const & tags)
{
m_rulesStorage.push_back(tags);
for (auto const & t : tags)
m_skippedTags.emplace(t.m_key, m_rulesStorage.back());
}
bool FilterData::NeedSkipWithId(uint64_t id) const
{
return m_skippedIds.find(id) != std::end(m_skippedIds);
}
bool FilterData::NeedSkipWithTags(Tags const & tags) const
{
Set<Tags const *> s;
for (auto const & tag : tags)
{
auto const range = m_skippedTags.equal_range(tag.m_key);
for (auto it = range.first; it != range.second; ++it)
{
Tags const & t = it->second;
if (s.Add(&t) && IsMatch(tags, t))
return true;
}
}
return false;
}
bool FilterElements::ParseSection(json_t * json, FilterData & fdata)
{
if (!json_is_object(json))
return false;
char const * key = nullptr;
json_t * value = nullptr;
json_object_foreach(json, key, value)
{
if (std::strcmp("ids", key) == 0 && !ParseIds(value, fdata))
return false;
else if (std::strcmp("tags", key) == 0 && !ParseTags(value, fdata))
return false;
}
return true;
}
bool FilterElements::ParseIds(json_t * json, FilterData & fdata)
{
if (!json_is_array(json))
return false;
size_t const sz = json_array_size(json);
for (size_t i = 0; i < sz; ++i)
{
auto const * o = json_array_get(json, i);
if (!json_is_integer(o))
return false;
auto const val = json_integer_value(o);
if (val < 0)
return false;
fdata.AddSkippedId(static_cast<uint64_t>(val));
}
return true;
}
bool FilterElements::ParseTags(json_t * json, FilterData & fdata)
{
if (!json_is_array(json))
return false;
size_t const sz = json_array_size(json);
for (size_t i = 0; i < sz; ++i)
{
auto * o = json_array_get(json, i);
if (!json_is_object(o))
return false;
char const * key = nullptr;
json_t * value = nullptr;
FilterData::Tags tags;
json_object_foreach(o, key, value)
{
if (!json_is_string(value))
return false;
auto const val = json_string_value(value);
tags.emplace_back(key, val);
}
if (!tags.empty())
fdata.AddSkippedTags(tags);
}
return true;
}
FilterElements::FilterElements(std::string const & filename) : m_filename(filename)
{
std::ifstream stream(m_filename);
std::string str((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
if (!ParseString(str))
LOG(LERROR, ("Cannot parse file", m_filename));
}
std::shared_ptr<FilterInterface> FilterElements::Clone() const
{
return std::make_shared<FilterElements>(m_filename);
}
bool FilterElements::IsAccepted(OsmElement const & element) const
{
return !NeedSkip(element);
}
bool FilterElements::NeedSkip(OsmElement const & element) const
{
switch (element.m_type)
{
case OsmElement::EntityType::Node: return NeedSkip(element, m_nodes);
case OsmElement::EntityType::Way: return NeedSkip(element, m_ways);
case OsmElement::EntityType::Relation: return NeedSkip(element, m_relations);
default: return false;
}
}
bool FilterElements::NeedSkip(OsmElement const & element, FilterData const & fdata) const
{
return fdata.NeedSkipWithId(element.m_id) || fdata.NeedSkipWithTags(element.Tags());
}
bool FilterElements::ParseString(std::string const & str)
{
base::Json json(str);
if (!json_is_object(json.get()))
return false;
char const * key = nullptr;
json_t * value = nullptr;
json_object_foreach(json.get(), key, value)
{
bool res = true;
if (std::strcmp("node", key) == 0)
res = ParseSection(value, m_nodes);
else if (std::strcmp("way", key) == 0)
res = ParseSection(value, m_ways);
else if (std::strcmp("relation", key) == 0)
res = ParseSection(value, m_relations);
if (!res)
return false;
}
return true;
}
} // namespace generator

View file

@ -0,0 +1,97 @@
// This file contains the FilterData and FilterElements classes.
// With the help of them, a mechanism is implemented to skip elements from processing according to
// the rules from the configuration file.
// The configuration file is in json format.
// File example:
// {
// "node": {
// "ids": [
// 1435,
// 436
// ],
// "tags": [
// {
// "place": "city"
// },
// {
// "place": "town",
// "capital": "*"
// }
// ]
// },
// "way": {},
// "relation": {}
// }
// This means that we will skip node element if one of the following is true:
// 1. its id equals 1435
// 2. its id equals 436
// 3. if it contains {place:city} in tags
// 4. if it contains {place:town} and {capital:*} in tags.
// '*' - means any value.
// Record format for way and relation is the same as for node.
// This implementation does not support processing of multiple values
// (https://wiki.openstreetmap.org/wiki/Multiple_values).
#pragma once
#include "generator/filter_interface.hpp"
#include <cstdint>
#include <functional>
#include <list>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "cppjansson/cppjansson.hpp"
namespace generator
{
// This is a helper class for the FilterElements class.
// It works with identifiers and tags at a low level.
class FilterData
{
public:
using Tags = std::vector<OsmElement::Tag>;
void AddSkippedId(uint64_t id);
void AddSkippedTags(Tags const & tags);
bool NeedSkipWithId(uint64_t id) const;
bool NeedSkipWithTags(Tags const & tags) const;
private:
static bool IsMatch(Tags const & elementTags, Tags const & tags);
std::unordered_set<uint64_t> m_skippedIds;
std::unordered_multimap<std::string, std::reference_wrapper<Tags const>> m_skippedTags;
std::list<Tags> m_rulesStorage;
};
// This is the main class that implements the element skipping mechanism.
class FilterElements : public FilterInterface
{
public:
explicit FilterElements(std::string const & filename);
// FilterInterface overrides:
std::shared_ptr<FilterInterface> Clone() const override;
bool IsAccepted(OsmElement const & element) const override;
bool NeedSkip(OsmElement const & element) const;
private:
static bool ParseSection(json_t * json, FilterData & fdata);
static bool ParseIds(json_t * json, FilterData & fdata);
static bool ParseTags(json_t * json, FilterData & fdata);
bool NeedSkip(OsmElement const & element, FilterData const & fdata) const;
bool ParseString(std::string const & str);
std::string m_filename;
FilterData m_nodes;
FilterData m_ways;
FilterData m_relations;
};
} // namespace generator

View file

@ -0,0 +1,21 @@
#pragma once
#include "generator/feature_builder.hpp"
#include "generator/osm_element.hpp"
#include <memory>
namespace generator
{
// Implementing this interface allows an object to filter OsmElement and FeatureBuilder elements.
class FilterInterface
{
public:
virtual ~FilterInterface() = default;
virtual std::shared_ptr<FilterInterface> Clone() const = 0;
virtual bool IsAccepted(OsmElement const &) const { return true; }
virtual bool IsAccepted(feature::FeatureBuilder const &) const { return true; }
};
} // namespace generator

View file

@ -0,0 +1,28 @@
#include "generator/filter_planet.hpp"
namespace generator
{
std::shared_ptr<FilterInterface> FilterPlanet::Clone() const
{
return std::make_shared<FilterPlanet>();
}
bool FilterPlanet::IsAccepted(OsmElement const & element) const
{
if (element.IsRelation())
{
auto const type = element.GetTag("type");
return (type == "multipolygon" || type == "boundary");
}
if (element.IsNode())
return !element.m_tags.empty();
return true;
}
bool FilterPlanet::IsAccepted(feature::FeatureBuilder const & feature) const
{
auto const & params = feature.GetParams();
return params.IsValid();
}
} // namespace generator

Some files were not shown because too many files have changed in this diff Show more