Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
280
generator/CMakeLists.txt
Normal file
280
generator/CMakeLists.txt
Normal 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
5
generator/TODO.txt
Normal 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.
|
||||
358
generator/address_enricher.cpp
Normal file
358
generator/address_enricher.cpp
Normal 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
|
||||
104
generator/address_enricher.hpp
Normal file
104
generator/address_enricher.hpp
Normal 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
|
||||
23
generator/address_parser/CMakeLists.txt
Normal file
23
generator/address_parser/CMakeLists.txt
Normal 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)
|
||||
|
|
@ -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
|
||||
)
|
||||
260
generator/address_parser/address_parser_tests/parser_tests.cpp
Normal file
260
generator/address_parser/address_parser_tests/parser_tests.cpp
Normal 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
|
||||
30
generator/address_parser/parser_tool.cpp
Normal file
30
generator/address_parser/parser_tool.cpp
Normal 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;
|
||||
})
|
||||
91
generator/address_parser/processor.cpp
Normal file
91
generator/address_parser/processor.cpp
Normal 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
|
||||
32
generator/address_parser/processor.hpp
Normal file
32
generator/address_parser/processor.hpp
Normal 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
|
||||
123
generator/address_parser/tiger_parser.cpp
Normal file
123
generator/address_parser/tiger_parser.cpp
Normal 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
|
||||
26
generator/address_parser/tiger_parser.hpp
Normal file
26
generator/address_parser/tiger_parser.hpp
Normal 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
|
||||
232
generator/addresses_collector.cpp
Normal file
232
generator/addresses_collector.cpp
Normal 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
|
||||
67
generator/addresses_collector.hpp
Normal file
67
generator/addresses_collector.hpp
Normal 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
318
generator/affiliation.cpp
Normal 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
78
generator/affiliation.hpp
Normal 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>;
|
||||
214
generator/altitude_generator.cpp
Normal file
214
generator/altitude_generator.cpp
Normal 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
|
||||
29
generator/altitude_generator.hpp
Normal file
29
generator/altitude_generator.hpp
Normal 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
275
generator/borders.cpp
Normal 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
168
generator/borders.hpp
Normal 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
|
||||
61
generator/boundary_postcodes_enricher.cpp
Normal file
61
generator/boundary_postcodes_enricher.cpp
Normal 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
|
||||
25
generator/boundary_postcodes_enricher.hpp
Normal file
25
generator/boundary_postcodes_enricher.hpp
Normal 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
159
generator/brands_loader.cpp
Normal 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
|
||||
56
generator/brands_loader.hpp
Normal file
56
generator/brands_loader.hpp
Normal 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
|
||||
337
generator/camera_info_collector.cpp
Normal file
337
generator/camera_info_collector.cpp
Normal 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
|
||||
82
generator/camera_info_collector.hpp
Normal file
82
generator/camera_info_collector.hpp
Normal 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
248
generator/cells_merger.cpp
Normal 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
|
||||
77
generator/cells_merger.hpp
Normal file
77
generator/cells_merger.hpp
Normal 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
|
||||
55
generator/centers_table_builder.cpp
Normal file
55
generator/centers_table_builder.cpp
Normal 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
|
||||
13
generator/centers_table_builder.hpp
Normal file
13
generator/centers_table_builder.hpp
Normal 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
47
generator/check_model.cpp
Normal 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
|
||||
8
generator/check_model.hpp
Normal file
8
generator/check_model.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace check_model
|
||||
{
|
||||
void ReadFeatures(std::string const & fName);
|
||||
} // namespace check_model
|
||||
163
generator/cities_boundaries_builder.cpp
Normal file
163
generator/cities_boundaries_builder.cpp
Normal 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
|
||||
21
generator/cities_boundaries_builder.hpp
Normal file
21
generator/cities_boundaries_builder.hpp
Normal 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
|
||||
26
generator/cities_boundaries_checker.cpp
Normal file
26
generator/cities_boundaries_checker.cpp
Normal 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
|
||||
26
generator/cities_boundaries_checker.hpp
Normal file
26
generator/cities_boundaries_checker.hpp
Normal 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
|
||||
120
generator/cities_ids_builder.cpp
Normal file
120
generator/cities_ids_builder.cpp
Normal 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
|
||||
20
generator/cities_ids_builder.hpp
Normal file
20
generator/cities_ids_builder.hpp
Normal 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
|
||||
125
generator/city_roads_generator.cpp
Normal file
125
generator/city_roads_generator.cpp
Normal 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
|
||||
18
generator/city_roads_generator.hpp
Normal file
18
generator/city_roads_generator.hpp
Normal 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
|
||||
101
generator/cluster_finder.hpp
Normal file
101
generator/cluster_finder.hpp
Normal 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
|
||||
309
generator/coastlines_generator.cpp
Normal file
309
generator/coastlines_generator.cpp
Normal 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;
|
||||
}
|
||||
38
generator/coastlines_generator.hpp
Normal file
38
generator/coastlines_generator.hpp
Normal 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
|
||||
25
generator/collection_base.hpp
Normal file
25
generator/collection_base.hpp
Normal 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;
|
||||
};
|
||||
72
generator/collector_boundary_postcode.cpp
Normal file
72
generator/collector_boundary_postcode.cpp
Normal 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
|
||||
36
generator/collector_boundary_postcode.hpp
Normal file
36
generator/collector_boundary_postcode.hpp
Normal 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
|
||||
219
generator/collector_building_parts.cpp
Normal file
219
generator/collector_building_parts.cpp
Normal 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
|
||||
84
generator/collector_building_parts.hpp
Normal file
84
generator/collector_building_parts.hpp
Normal 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
|
||||
184
generator/collector_camera.cpp
Normal file
184
generator/collector_camera.cpp
Normal 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
|
||||
87
generator/collector_camera.hpp
Normal file
87
generator/collector_camera.hpp
Normal 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
|
||||
62
generator/collector_collection.cpp
Normal file
62
generator/collector_collection.cpp
Normal 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
|
||||
40
generator/collector_collection.hpp
Normal file
40
generator/collector_collection.hpp
Normal 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
|
||||
89
generator/collector_interface.hpp
Normal file
89
generator/collector_interface.hpp
Normal 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); \
|
||||
}
|
||||
80
generator/collector_mini_roundabout.cpp
Normal file
80
generator/collector_mini_roundabout.cpp
Normal 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
|
||||
45
generator/collector_mini_roundabout.hpp
Normal file
45
generator/collector_mini_roundabout.hpp
Normal 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
|
||||
456
generator/collector_routing_city_boundaries.cpp
Normal file
456
generator/collector_routing_city_boundaries.cpp
Normal 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
|
||||
139
generator/collector_routing_city_boundaries.hpp
Normal file
139
generator/collector_routing_city_boundaries.hpp
Normal 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
|
||||
61
generator/collector_tag.cpp
Normal file
61
generator/collector_tag.cpp
Normal 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
|
||||
46
generator/collector_tag.hpp
Normal file
46
generator/collector_tag.hpp
Normal 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
|
||||
10
generator/complex_generator/CMakeLists.txt
Normal file
10
generator/complex_generator/CMakeLists.txt
Normal 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
|
||||
)
|
||||
127
generator/complex_generator/complex_generator.cpp
Normal file
127
generator/complex_generator/complex_generator.cpp
Normal 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;
|
||||
});
|
||||
72
generator/complex_loader.cpp
Normal file
72
generator/complex_loader.cpp
Normal 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
|
||||
63
generator/complex_loader.hpp
Normal file
63
generator/complex_loader.hpp
Normal 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
|
||||
50
generator/composite_id.cpp
Normal file
50
generator/composite_id.cpp
Normal 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
|
||||
39
generator/composite_id.hpp
Normal file
39
generator/composite_id.hpp
Normal 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
|
||||
192
generator/cross_mwm_osm_ways_collector.cpp
Normal file
192
generator/cross_mwm_osm_ways_collector.cpp
Normal 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
|
||||
67
generator/cross_mwm_osm_ways_collector.hpp
Normal file
67
generator/cross_mwm_osm_ways_collector.hpp
Normal 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
|
||||
242
generator/descriptions_section_builder.cpp
Normal file
242
generator/descriptions_section_builder.cpp
Normal 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
|
||||
107
generator/descriptions_section_builder.hpp
Normal file
107
generator/descriptions_section_builder.hpp
Normal 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
210
generator/dumper.cpp
Normal 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
19
generator/dumper.hpp
Normal 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
|
||||
20
generator/factory_utils.hpp
Normal file
20
generator/factory_utils.hpp
Normal 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
|
||||
774
generator/feature_builder.cpp
Normal file
774
generator/feature_builder.cpp
Normal 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
|
||||
362
generator/feature_builder.hpp
Normal file
362
generator/feature_builder.hpp
Normal 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
|
||||
17
generator/feature_emitter_iface.hpp
Normal file
17
generator/feature_emitter_iface.hpp
Normal 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;
|
||||
};
|
||||
146
generator/feature_generator.cpp
Normal file
146
generator/feature_generator.cpp
Normal 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
|
||||
62
generator/feature_generator.hpp
Normal file
62
generator/feature_generator.hpp
Normal 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
|
||||
62
generator/feature_helpers.cpp
Normal file
62
generator/feature_helpers.cpp
Normal 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
|
||||
106
generator/feature_helpers.hpp
Normal file
106
generator/feature_helpers.hpp
Normal 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
233
generator/feature_maker.cpp
Normal 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
|
||||
53
generator/feature_maker.hpp
Normal file
53
generator/feature_maker.hpp
Normal 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
|
||||
81
generator/feature_maker_base.cpp
Normal file
81
generator/feature_maker_base.cpp
Normal 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
|
||||
51
generator/feature_maker_base.hpp
Normal file
51
generator/feature_maker_base.hpp
Normal 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
|
||||
412
generator/feature_merger.cpp
Normal file
412
generator/feature_merger.cpp
Normal 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
|
||||
129
generator/feature_merger.hpp
Normal file
129
generator/feature_merger.hpp
Normal 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
|
||||
291
generator/feature_processing_layers.cpp
Normal file
291
generator/feature_processing_layers.cpp
Normal 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
|
||||
169
generator/feature_processing_layers.hpp
Normal file
169
generator/feature_processing_layers.hpp
Normal 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
|
||||
10
generator/feature_segments_checker/CMakeLists.txt
Normal file
10
generator/feature_segments_checker/CMakeLists.txt
Normal 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
|
||||
)
|
||||
367
generator/feature_segments_checker/feature_segments_checker.cpp
Normal file
367
generator/feature_segments_checker/feature_segments_checker.cpp
Normal 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;
|
||||
}
|
||||
438
generator/feature_sorter.cpp
Normal file
438
generator/feature_sorter.cpp
Normal 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
|
||||
16
generator/feature_sorter.hpp
Normal file
16
generator/feature_sorter.hpp
Normal 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
|
||||
30
generator/features_processing_helpers.hpp
Normal file
30
generator/features_processing_helpers.hpp
Normal 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
|
||||
24
generator/filter_collection.cpp
Normal file
24
generator/filter_collection.cpp
Normal 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
|
||||
20
generator/filter_collection.hpp
Normal file
20
generator/filter_collection.hpp
Normal 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
|
||||
22
generator/filter_complex.cpp
Normal file
22
generator/filter_complex.cpp
Normal 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
|
||||
18
generator/filter_complex.hpp
Normal file
18
generator/filter_complex.hpp
Normal 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
|
||||
206
generator/filter_elements.cpp
Normal file
206
generator/filter_elements.cpp
Normal 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
|
||||
97
generator/filter_elements.hpp
Normal file
97
generator/filter_elements.hpp
Normal 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
|
||||
21
generator/filter_interface.hpp
Normal file
21
generator/filter_interface.hpp
Normal 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
|
||||
28
generator/filter_planet.cpp
Normal file
28
generator/filter_planet.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue