Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
11
tools/CMakeLists.txt
Normal file
11
tools/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
add_subdirectory(openlr)
|
||||
add_subdirectory(poly_borders)
|
||||
add_subdirectory(track_analyzing)
|
||||
|
||||
omim_add_tool_subdirectory(feature_list)
|
||||
omim_add_tool_subdirectory(topography_generator)
|
||||
omim_add_tool_subdirectory(track_generator)
|
||||
if (NOT SKIP_QT_GUI)
|
||||
omim_add_tool_subdirectory(skin_generator)
|
||||
endif()
|
||||
|
||||
50
tools/android/download_googleplay_metadata.sh
Executable file
50
tools/android/download_googleplay_metadata.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Downloads metadata from Google Play
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../../android"
|
||||
|
||||
check_keys() {
|
||||
if [[ ! -r "google-play.json" ]]; then
|
||||
echo >&2 "Missing $PWD/google-play.json"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
check_screenshots() {
|
||||
if [[ ! -r "../screenshots/android/en-US/graphics/phone-screenshots/1.jpg" ]]; then
|
||||
echo >&2 "Please checkout screenshots to $PWD/../screenshots"
|
||||
exit 3
|
||||
fi
|
||||
}
|
||||
|
||||
download_metadata() {
|
||||
set -x
|
||||
./gradlew bootstrapGoogleRelease
|
||||
|
||||
for locale in src/google/play/listings/??-??; do
|
||||
# Fix wrong file extension (.png instead of .jpg)
|
||||
for png in $locale/graphics/*-screenshots/*.png; do
|
||||
if [[ $(file -b "$png") =~ ^'JPEG ' ]]; then
|
||||
echo "Fixing $png extension"
|
||||
mv -f "$png" "${png%.png}.jpg"
|
||||
fi
|
||||
done
|
||||
# Copy new files to screenshots repository
|
||||
cp -Rpv $locale/graphics ../screenshots/android/$(basename $locale)/
|
||||
# Replace original directory with a symlink to screenshots repository
|
||||
rm -rf $locale/graphics
|
||||
ln -sf ../../../../../../screenshots/android/$(basename $locale)/graphics $locale/graphics
|
||||
done
|
||||
|
||||
# Ignore changelogs from all tracks exception production
|
||||
mv -f src/google/play/release-notes/en-US/production.txt src/google/play/release-notes/en-US/default.txt
|
||||
rm -f src/google/play/release-notes/en-US/alpha.txt src/google/play/release-notes/en-US/beta.txt src/google/play/release-notes/en-US/internal.txt
|
||||
}
|
||||
|
||||
check_keys
|
||||
check_screenshots
|
||||
download_metadata
|
||||
41
tools/android/download_vulkan_validation_layers.py
Executable file
41
tools/android/download_vulkan_validation_layers.py
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import zipfile
|
||||
import os
|
||||
import shutil
|
||||
|
||||
def download_vulkan_validation_layers():
|
||||
# Step 1: Download zip archive
|
||||
url = "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.3.275.0/android-binaries-1.3.275.0.zip"
|
||||
download_path = "vulkan_sdk.zip"
|
||||
|
||||
print("Downloading zip archive...")
|
||||
response = requests.get(url)
|
||||
with open(download_path, "wb") as f:
|
||||
f.write(response.content)
|
||||
print("Download complete.")
|
||||
|
||||
# Step 2: Unzip archive
|
||||
unzip_dir = "vulkan_sdk"
|
||||
print("Unzipping archive...")
|
||||
with zipfile.ZipFile(download_path, "r") as zip_ref:
|
||||
zip_ref.extractall(unzip_dir)
|
||||
print("Unzip complete.")
|
||||
|
||||
# Step 3: Rename unpacked folder to "jniLibs" and move it to "android/app/src/main"
|
||||
unpacked_folder = os.listdir(unzip_dir)[0]
|
||||
jniLibs_path = os.path.join(unzip_dir, "jniLibs")
|
||||
os.rename(os.path.join(unzip_dir, unpacked_folder), jniLibs_path)
|
||||
destination_path = "android/app/src/main"
|
||||
shutil.move(jniLibs_path, destination_path)
|
||||
print("Deploy complete.")
|
||||
|
||||
# Clean up downloaded zip file and empty directories
|
||||
os.remove(download_path)
|
||||
os.rmdir(unzip_dir)
|
||||
print("Clean up complete.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
download_vulkan_validation_layers()
|
||||
96
tools/android/set_up_android.py
Executable file
96
tools/android/set_up_android.py
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
import os, sys, shutil, collections
|
||||
from optparse import OptionParser
|
||||
|
||||
# Fix for python 2
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
def find_recursive(root, subpath, maxdepth=4):
|
||||
queue = collections.deque([(root, 0)])
|
||||
if 'PATH' in os.environ:
|
||||
envpath = os.environ['PATH'].split(':')
|
||||
relpath = ['..'] * (len(subpath) - 1)
|
||||
queue.extendleft([(os.path.join(x, *relpath), maxdepth) for x in envpath if 'android' in x.lower()])
|
||||
while len(queue) > 0:
|
||||
item = queue.popleft()
|
||||
if os.path.isfile(os.path.join(item[0], *subpath)):
|
||||
return os.path.abspath(item[0])
|
||||
if item[1] < maxdepth:
|
||||
for name in os.listdir(item[0]):
|
||||
fullname = os.path.join(item[0], name)
|
||||
if os.path.isdir(fullname) and '.' not in name:
|
||||
queue.append((fullname, item[1] + 1))
|
||||
return None
|
||||
|
||||
def read_local_properties():
|
||||
androidRoot = os.path.join(os.path.dirname(sys.argv[0]), '..', '..', 'android')
|
||||
propsFile = os.path.join(androidRoot, 'local.properties')
|
||||
sdkDir = None
|
||||
if os.path.exists(propsFile):
|
||||
with open(propsFile, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('sdk.dir') and '=' in line:
|
||||
sdkDir = line.split('=')[1].strip()
|
||||
return sdkDir
|
||||
|
||||
def test_path(title, path, subpath):
|
||||
test = os.path.join(path, *subpath)
|
||||
if path and os.path.isfile(test):
|
||||
return os.path.abspath(path)
|
||||
print('Could not find "{0}", it\'s not an {1} path!'.format(test, title))
|
||||
return None
|
||||
|
||||
def query_path(title, option, default, subpath):
|
||||
if option:
|
||||
path = test_path(title, option, subpath)
|
||||
if path:
|
||||
return path
|
||||
default = '' if not default else os.path.abspath(default)
|
||||
searchHint = ', "s" to search'
|
||||
while True:
|
||||
path = input('Path to {0}{1} [{2}]:'.format(title, searchHint, default)) or default
|
||||
if len(searchHint) > 0 and path.lower().strip() == 's':
|
||||
found = find_recursive(os.path.expanduser('~'), subpath)
|
||||
if found:
|
||||
print('Found {0} at "{1}".'.format(title, found))
|
||||
default = found
|
||||
else:
|
||||
print('{0} not found.'.format(title))
|
||||
searchHint = ''
|
||||
else:
|
||||
break
|
||||
return test_path(title, path, subpath)
|
||||
|
||||
def write_local_properties(sdkDir):
|
||||
sdkProp = 'sdk.dir={0}'.format(sdkDir)
|
||||
content = ''.join([x + '\n' for x in [
|
||||
'# Autogenerated file',
|
||||
'# Do not add it to version control',
|
||||
sdkProp,
|
||||
]])
|
||||
|
||||
# Create android/local.properties
|
||||
androidRoot = os.path.join(os.path.dirname(sys.argv[0]), '..', '..', 'android')
|
||||
propsFile = os.path.abspath(os.path.join(androidRoot, 'local.properties'))
|
||||
print('Writing "{0}" to {1}'.format(sdkProp, propsFile))
|
||||
with open(propsFile, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = OptionParser()
|
||||
parser.set_description(' '.join([
|
||||
'Writes Android SDK location into android/local.properties file.',
|
||||
'Prompts for SDK location and offers to search for it if started without options.',
|
||||
]))
|
||||
parser.add_option('-s', '--sdk', help='path to Android SDK')
|
||||
options, _ = parser.parse_args()
|
||||
|
||||
sdkDirOld = read_local_properties()
|
||||
sdkDir = query_path('Android SDK', options.sdk, sdkDirOld, ['platform-tools', 'adb'])
|
||||
|
||||
if sdkDir:
|
||||
write_local_properties(sdkDir)
|
||||
16
tools/autobuild/detect_cmake.sh
Executable file
16
tools/autobuild/detect_cmake.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e -u
|
||||
|
||||
# If CMAKE variable is set, use it
|
||||
[ -n "${CMAKE-}" -a -x "${CMAKE-}" ] && return 0
|
||||
|
||||
# Find cmake, prefer cmake3
|
||||
for name in cmake3 cmake; do
|
||||
if command -v "$name" > /dev/null; then
|
||||
CMAKE="$name"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo 'Error: cmake is not installed.' >&2
|
||||
exit 1
|
||||
46
tools/autobuild/ndk_helper.sh
Normal file
46
tools/autobuild/ndk_helper.sh
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
set -e -u
|
||||
|
||||
MY_PATH="`dirname \"$0\"`" # relative
|
||||
MY_PATH="`( cd \"$MY_PATH\" && pwd )`" # absolutized and normalized
|
||||
|
||||
# Echoes found NDK root path or nothing if not found
|
||||
# return 1 on error and 0 on success
|
||||
GetNdkRoot()
|
||||
{
|
||||
local FILENAME="$MY_PATH/../../android/local.properties"
|
||||
while read line
|
||||
do
|
||||
if [[ "${line:0:7}" == "ndk.dir" ]]; then
|
||||
echo "${line:8}"
|
||||
return 0
|
||||
fi
|
||||
done < $FILENAME
|
||||
|
||||
# Not found in local.properties, try to find using ANDROID_HOME.
|
||||
local NDK_VERSION="`( ls -1 "${ANDROID_HOME}/ndk" | sort | tail -1 )`"
|
||||
if [ ! -z "$NDK_VERSION" ]
|
||||
then
|
||||
echo "$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
GetNdkHost()
|
||||
{
|
||||
if [[ "${OSTYPE:0:5}" == "linux" ]]; then
|
||||
echo "linux-x86_64"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${OSTYPE:0:6}" == "darwin" ]]; then
|
||||
echo "darwin-x86_64"
|
||||
return 0
|
||||
fi
|
||||
if [[ "$OSTYPE" == "cygwin" ]]; then
|
||||
echo windows-x86_64
|
||||
return 0
|
||||
fi
|
||||
echo "ERROR: Can't detect your host OS"
|
||||
return 1
|
||||
}
|
||||
13
tools/feature_list/CMakeLists.txt
Normal file
13
tools/feature_list/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
project(feature_list_generator)
|
||||
|
||||
set(SRC feature_list.cpp)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
mwm_diff
|
||||
generator
|
||||
search
|
||||
platform
|
||||
indexer
|
||||
)
|
||||
436
tools/feature_list/feature_list.cpp
Normal file
436
tools/feature_list/feature_list.cpp
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
#include "generator/utils.hpp"
|
||||
|
||||
#include "search/search_quality/helpers.hpp"
|
||||
|
||||
#include "search/categories_cache.hpp"
|
||||
#include "search/engine.hpp"
|
||||
#include "search/locality_finder.hpp"
|
||||
#include "search/reverse_geocoder.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
#include "storage/storage.hpp"
|
||||
#include "storage/storage_defines.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
#include "indexer/map_object.hpp"
|
||||
#include "indexer/map_style_reader.hpp"
|
||||
|
||||
#include "platform/platform_tests_support/helpers.hpp"
|
||||
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class ClosestPoint
|
||||
{
|
||||
m2::PointD const & m_center;
|
||||
m2::PointD m_best;
|
||||
double m_distance = numeric_limits<double>::max();
|
||||
|
||||
public:
|
||||
explicit ClosestPoint(m2::PointD const & center) : m_center(center), m_best(0, 0) {}
|
||||
|
||||
m2::PointD GetBest() const { return m_best; }
|
||||
|
||||
void operator()(m2::PointD const & point)
|
||||
{
|
||||
double distance = m_center.SquaredLength(point);
|
||||
if (distance < m_distance)
|
||||
{
|
||||
m_distance = distance;
|
||||
m_best = point;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
m2::PointD FindCenter(FeatureType & f)
|
||||
{
|
||||
ClosestPoint closest(f.GetLimitRect(FeatureType::BEST_GEOMETRY).Center());
|
||||
if (f.GetGeomType() == feature::GeomType::Area)
|
||||
{
|
||||
f.ForEachTriangle([&closest](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{ closest((p1 + p2 + p3) / 3); }, FeatureType::BEST_GEOMETRY);
|
||||
}
|
||||
else
|
||||
{
|
||||
f.ForEachPoint(closest, FeatureType::BEST_GEOMETRY);
|
||||
}
|
||||
return closest.GetBest();
|
||||
}
|
||||
|
||||
size_t const kLangCount = StringUtf8Multilang::GetSupportedLanguages().size();
|
||||
|
||||
string GetReadableType(FeatureType & f)
|
||||
{
|
||||
string result;
|
||||
f.ForEachType([&](uint32_t type)
|
||||
{
|
||||
if (ftypes::IsPoiChecker::Instance()(type) || ftypes::IsPlaceChecker::Instance()(type))
|
||||
result = classif().GetReadableObjectName(type);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
string GetWheelchairType(FeatureType & f)
|
||||
{
|
||||
static uint32_t const wheelchair = classif().GetTypeByPath({"wheelchair"});
|
||||
string result;
|
||||
f.ForEachType([&result](uint32_t type)
|
||||
{
|
||||
if (ftype::Trunc(type, 1) == wheelchair)
|
||||
{
|
||||
string fullName = classif().GetReadableObjectName(type);
|
||||
auto pos = fullName.find("-");
|
||||
if (pos != string::npos)
|
||||
result = fullName.substr(pos + 1);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HasAtm(FeatureType & f)
|
||||
{
|
||||
static uint32_t const atm = classif().GetTypeByPath({"amenity", "atm"});
|
||||
bool result = false;
|
||||
f.ForEachType([&result](uint32_t type)
|
||||
{
|
||||
if (type == atm)
|
||||
result = true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
string BuildUniqueId(ms::LatLon const & coords, string const & name)
|
||||
{
|
||||
ostringstream ss;
|
||||
ss << strings::to_string_dac(coords.m_lat, 6) << ',' << strings::to_string_dac(coords.m_lon, 6) << ',' << name;
|
||||
uint32_t hash = 0;
|
||||
for (char const c : ss.str())
|
||||
hash = hash * 101 + c;
|
||||
return strings::to_string(hash);
|
||||
}
|
||||
|
||||
void AppendNames(FeatureType & f, vector<string> & columns)
|
||||
{
|
||||
vector<string> names(kLangCount);
|
||||
f.GetNames().ForEach([&names](int8_t code, string_view name) { names[code] = name; });
|
||||
columns.insert(columns.end(), next(names.begin()), names.end());
|
||||
}
|
||||
|
||||
void PrintAsCSV(vector<string> const & columns, char const delimiter, ostream & out)
|
||||
{
|
||||
bool first = true;
|
||||
for (string value : columns)
|
||||
{
|
||||
// Newlines are hard to process, replace them with spaces. And trim the
|
||||
// string.
|
||||
replace(value.begin(), value.end(), '\r', ' ');
|
||||
replace(value.begin(), value.end(), '\n', ' ');
|
||||
strings::Trim(value);
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
out << delimiter;
|
||||
bool needsQuotes = value.find('"') != string::npos || value.find(delimiter) != string::npos;
|
||||
if (!needsQuotes)
|
||||
{
|
||||
out << value;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t pos = 0;
|
||||
while ((pos = value.find('"', pos)) != string::npos)
|
||||
{
|
||||
value.insert(pos, 1, '"');
|
||||
pos += 2;
|
||||
}
|
||||
out << '"' << value << '"';
|
||||
}
|
||||
}
|
||||
out << endl;
|
||||
}
|
||||
|
||||
class Processor
|
||||
{
|
||||
search::ReverseGeocoder m_geocoder;
|
||||
base::Cancellable m_cancellable;
|
||||
search::CitiesBoundariesTable m_boundariesTable;
|
||||
search::VillagesCache m_villagesCache;
|
||||
search::LocalityFinder m_finder;
|
||||
|
||||
public:
|
||||
explicit Processor(DataSource const & dataSource)
|
||||
: m_geocoder(dataSource)
|
||||
, m_boundariesTable(dataSource)
|
||||
, m_villagesCache(m_cancellable)
|
||||
, m_finder(dataSource, m_boundariesTable, m_villagesCache)
|
||||
{
|
||||
m_boundariesTable.Load();
|
||||
}
|
||||
|
||||
void ClearCache() { m_villagesCache.Clear(); }
|
||||
|
||||
void operator()(FeatureType & f, map<uint32_t, base::GeoObjectId> const & ft2osm) { Process(f, ft2osm); }
|
||||
|
||||
void Process(FeatureType & f, map<uint32_t, base::GeoObjectId> const & ft2osm)
|
||||
{
|
||||
f.ParseAllBeforeGeometry();
|
||||
|
||||
string const & category = GetReadableType(f);
|
||||
auto const & meta = f.GetMetadata();
|
||||
|
||||
auto const metaOperator = meta.Get(feature::Metadata::FMD_OPERATOR);
|
||||
auto const & osmIt = ft2osm.find(f.GetID().m_index);
|
||||
if ((!f.HasName() && metaOperator.empty()) ||
|
||||
(f.GetGeomType() == feature::GeomType::Line && category != "highway-pedestrian") || category.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m2::PointD const & center = FindCenter(f);
|
||||
ms::LatLon const & ll = mercator::ToLatLon(center);
|
||||
osm::MapObject obj;
|
||||
obj.SetFromFeatureType(f);
|
||||
|
||||
string_view city;
|
||||
m_finder.GetLocality(center, [&city](search::LocalityItem const & item)
|
||||
{ item.GetSpecifiedOrDefaultName(StringUtf8Multilang::kDefaultCode, city); });
|
||||
|
||||
string const & mwmName = f.GetID().GetMwmName();
|
||||
|
||||
string name(f.GetName(StringUtf8Multilang::kDefaultCode));
|
||||
if (name.empty())
|
||||
{
|
||||
name = f.GetReadableName();
|
||||
if (name.empty())
|
||||
name = metaOperator;
|
||||
}
|
||||
|
||||
string osmId = osmIt != ft2osm.cend() ? to_string(osmIt->second.GetEncodedId()) : "";
|
||||
string const & uid = BuildUniqueId(ll, name);
|
||||
string const & lat = strings::to_string_dac(ll.m_lat, 6);
|
||||
string const & lon = strings::to_string_dac(ll.m_lon, 6);
|
||||
search::ReverseGeocoder::Address addr;
|
||||
string addrStreet = "";
|
||||
string addrHouse = "";
|
||||
double constexpr kDistanceThresholdMeters = 0.5;
|
||||
if (m_geocoder.GetExactAddress(f, addr))
|
||||
{
|
||||
addrStreet = addr.GetStreetName();
|
||||
addrHouse = addr.GetHouseNumber();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_geocoder.GetNearbyAddress(center, addr);
|
||||
if (addr.GetDistance() < kDistanceThresholdMeters)
|
||||
{
|
||||
addrStreet = addr.GetStreetName();
|
||||
addrHouse = addr.GetHouseNumber();
|
||||
}
|
||||
}
|
||||
string const phone(meta.Get(feature::Metadata::FMD_PHONE_NUMBER));
|
||||
string const website(meta.Get(feature::Metadata::FMD_WEBSITE));
|
||||
string const contact_facebook(meta.Get(feature::Metadata::FMD_CONTACT_FACEBOOK));
|
||||
string const contact_instagram(meta.Get(feature::Metadata::FMD_CONTACT_INSTAGRAM));
|
||||
string const contact_twitter(meta.Get(feature::Metadata::FMD_CONTACT_TWITTER));
|
||||
string const contact_vk(meta.Get(feature::Metadata::FMD_CONTACT_VK));
|
||||
string const contact_line(meta.Get(feature::Metadata::FMD_CONTACT_LINE));
|
||||
string const contact_fediverse(meta.Get(feature::Metadata::FMD_CONTACT_FEDIVERSE));
|
||||
string const contact_bluesky(meta.Get(feature::Metadata::FMD_CONTACT_BLUESKY));
|
||||
string const stars(meta.Get(feature::Metadata::FMD_STARS));
|
||||
string const branch(meta.Get(feature::Metadata::FMD_BRANCH));
|
||||
string const internet(meta.Get(feature::Metadata::FMD_INTERNET));
|
||||
string const denomination(meta.Get(feature::Metadata::FMD_DENOMINATION));
|
||||
string const wheelchair(GetWheelchairType(f));
|
||||
string const opening_hours(meta.Get(feature::Metadata::FMD_OPEN_HOURS));
|
||||
string const check_date(meta.Get(feature::Metadata::FMD_CHECK_DATE));
|
||||
string const check_date_opening_hours(meta.Get(feature::Metadata::FMD_CHECK_DATE_OPEN_HOURS));
|
||||
string const wikipedia(meta.Get(feature::Metadata::FMD_WIKIPEDIA));
|
||||
string const wikimedia_commons(meta.Get(feature::Metadata::FMD_WIKIMEDIA_COMMONS));
|
||||
string const panoramax(meta.Get(feature::Metadata::FMD_PANORAMAX));
|
||||
string const floor(meta.Get(feature::Metadata::FMD_LEVEL));
|
||||
string const fee = category.ends_with("-fee") ? "yes" : "";
|
||||
string const atm = HasAtm(f) ? "yes" : "";
|
||||
|
||||
vector<string> columns = {osmId,
|
||||
uid,
|
||||
lat,
|
||||
lon,
|
||||
mwmName,
|
||||
category,
|
||||
name,
|
||||
std::string(city),
|
||||
addrStreet,
|
||||
addrHouse,
|
||||
phone,
|
||||
website,
|
||||
stars,
|
||||
std::string(metaOperator),
|
||||
branch,
|
||||
internet,
|
||||
denomination,
|
||||
wheelchair,
|
||||
opening_hours,
|
||||
check_date,
|
||||
check_date_opening_hours,
|
||||
wikipedia,
|
||||
floor,
|
||||
fee,
|
||||
atm,
|
||||
contact_facebook,
|
||||
contact_instagram,
|
||||
contact_twitter,
|
||||
contact_vk,
|
||||
contact_line,
|
||||
contact_fediverse,
|
||||
contact_bluesky,
|
||||
wikimedia_commons,
|
||||
panoramax};
|
||||
|
||||
AppendNames(f, columns);
|
||||
PrintAsCSV(columns, ';', cout);
|
||||
}
|
||||
};
|
||||
|
||||
void PrintHeader()
|
||||
{
|
||||
vector<string> columns = {"id",
|
||||
"old_id",
|
||||
"lat",
|
||||
"lon",
|
||||
"mwm",
|
||||
"category",
|
||||
"name",
|
||||
"city",
|
||||
"street",
|
||||
"house",
|
||||
"phone",
|
||||
"website",
|
||||
"cuisines",
|
||||
"stars",
|
||||
"operator",
|
||||
"branch",
|
||||
"internet",
|
||||
"denomination",
|
||||
"wheelchair",
|
||||
"opening_hours",
|
||||
"check_date",
|
||||
"check_date_opening_hours",
|
||||
"wikipedia",
|
||||
"floor",
|
||||
"fee",
|
||||
"atm",
|
||||
"contact_facebook",
|
||||
"contact_instagram",
|
||||
"contact_twitter",
|
||||
"contact_vk",
|
||||
"contact_line",
|
||||
"contact_fediverse",
|
||||
"contact_bluesky",
|
||||
"wikimedia_commons",
|
||||
"panoramax"};
|
||||
// Append all supported name languages in order.
|
||||
for (uint8_t idx = 1; idx < kLangCount; idx++)
|
||||
columns.push_back("name_" + string(StringUtf8Multilang::GetLangByCode(idx)));
|
||||
PrintAsCSV(columns, ';', cout);
|
||||
}
|
||||
|
||||
bool ParseFeatureIdToOsmIdMapping(string const & path, map<uint32_t, base::GeoObjectId> & mapping)
|
||||
{
|
||||
return generator::ForEachOsmId2FeatureId(
|
||||
path, [&](auto const & compositeId, uint32_t const featureId) { mapping[featureId] = compositeId.m_mainId; });
|
||||
}
|
||||
|
||||
void DidDownload(storage::CountryId const & /* countryId */,
|
||||
shared_ptr<platform::LocalCountryFile> const & /* localFile */)
|
||||
{}
|
||||
|
||||
bool WillDelete(storage::CountryId const & /* countryId */,
|
||||
shared_ptr<platform::LocalCountryFile> const & /* localFile */)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
platform::tests_support::ChangeMaxNumberOfOpenFiles(search::search_quality::kMaxOpenFiles);
|
||||
if (argc <= 1)
|
||||
{
|
||||
LOG(LERROR, ("Usage:", argc == 1 ? argv[0] : "feature_list", "<mwm_path> [<data_path>] [<mwm_prefix>]"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
Platform & pl = GetPlatform();
|
||||
pl.SetWritableDirForTests(argv[1]);
|
||||
|
||||
string countriesFile = COUNTRIES_FILE;
|
||||
if (argc > 2)
|
||||
{
|
||||
pl.SetResourceDir(argv[2]);
|
||||
countriesFile = base::JoinPath(argv[2], COUNTRIES_FILE);
|
||||
}
|
||||
|
||||
storage::Storage storage(countriesFile, argv[1]);
|
||||
storage.Init(&DidDownload, &WillDelete);
|
||||
auto infoGetter = storage::CountryInfoReader::CreateCountryInfoGetter(pl);
|
||||
infoGetter->SetAffiliations(storage.GetAffiliations());
|
||||
|
||||
GetStyleReader().SetCurrentStyle(MapStyleMerged);
|
||||
classificator::Load();
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
vector<platform::LocalCountryFile> mwms;
|
||||
platform::FindAllLocalMapsAndCleanup(numeric_limits<int64_t>::max() /* the latest version */, mwms);
|
||||
for (auto & mwm : mwms)
|
||||
{
|
||||
mwm.SyncWithDisk();
|
||||
auto const & p = dataSource.RegisterMap(mwm);
|
||||
CHECK_EQUAL(MwmSet::RegResult::Success, p.second, ("Could not register map", mwm));
|
||||
MwmSet::MwmId const & id = p.first;
|
||||
CHECK(id.IsAlive(), ("Mwm is not alive?", mwm));
|
||||
}
|
||||
|
||||
Processor doProcess(dataSource);
|
||||
PrintHeader();
|
||||
vector<shared_ptr<MwmInfo>> mwmInfos;
|
||||
dataSource.GetMwmsInfo(mwmInfos);
|
||||
for (auto const & mwmInfo : mwmInfos)
|
||||
{
|
||||
if (mwmInfo->GetType() != MwmInfo::COUNTRY)
|
||||
continue;
|
||||
if (argc > 3 && !(mwmInfo->GetCountryName() + DATA_FILE_EXTENSION).starts_with(argv[3]))
|
||||
continue;
|
||||
LOG(LINFO, ("Processing", mwmInfo->GetCountryName()));
|
||||
string osmToFeatureFile =
|
||||
base::JoinPath(argv[1], mwmInfo->GetCountryName() + DATA_FILE_EXTENSION + OSM2FEATURE_FILE_EXTENSION);
|
||||
map<uint32_t, base::GeoObjectId> featureIdToOsmId;
|
||||
ParseFeatureIdToOsmIdMapping(osmToFeatureFile, featureIdToOsmId);
|
||||
MwmSet::MwmId mwmId(mwmInfo);
|
||||
FeaturesLoaderGuard loader(dataSource, mwmId);
|
||||
for (uint32_t ftIndex = 0; ftIndex < loader.GetNumFeatures(); ftIndex++)
|
||||
if (auto ft = loader.GetFeatureByIndex(static_cast<uint32_t>(ftIndex)))
|
||||
doProcess.Process(*ft, featureIdToOsmId);
|
||||
doProcess.ClearCache();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
50
tools/openlr/CMakeLists.txt
Normal file
50
tools/openlr/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
project(openlr)
|
||||
|
||||
set(SRC
|
||||
cache_line_size.hpp
|
||||
candidate_paths_getter.cpp
|
||||
candidate_paths_getter.hpp
|
||||
candidate_points_getter.cpp
|
||||
candidate_points_getter.hpp
|
||||
decoded_path.cpp
|
||||
decoded_path.hpp
|
||||
graph.cpp
|
||||
graph.hpp
|
||||
helpers.cpp
|
||||
helpers.hpp
|
||||
openlr_decoder.cpp
|
||||
openlr_decoder.hpp
|
||||
openlr_model.cpp
|
||||
openlr_model.hpp
|
||||
openlr_model_xml.cpp
|
||||
openlr_model_xml.hpp
|
||||
paths_connector.cpp
|
||||
paths_connector.hpp
|
||||
road_info_getter.cpp
|
||||
road_info_getter.hpp
|
||||
router.cpp
|
||||
router.hpp
|
||||
score_candidate_paths_getter.cpp
|
||||
score_candidate_paths_getter.hpp
|
||||
score_candidate_points_getter.cpp
|
||||
score_candidate_points_getter.hpp
|
||||
score_paths_connector.cpp
|
||||
score_paths_connector.hpp
|
||||
score_types.hpp
|
||||
stats.hpp
|
||||
way_point.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
routing
|
||||
indexer
|
||||
geometry
|
||||
pugixml
|
||||
)
|
||||
|
||||
omim_add_tool_subdirectory(openlr_match_quality)
|
||||
omim_add_tool_subdirectory(openlr_stat)
|
||||
|
||||
omim_add_test_subdirectory(openlr_tests)
|
||||
8
tools/openlr/cache_line_size.hpp
Normal file
8
tools/openlr/cache_line_size.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
size_t constexpr kCacheLineSize = 64;
|
||||
} // namespace openlr
|
||||
275
tools/openlr/candidate_paths_getter.cpp
Normal file
275
tools/openlr/candidate_paths_getter.cpp
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
#include "openlr/candidate_paths_getter.hpp"
|
||||
|
||||
#include "openlr/candidate_points_getter.hpp"
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
using namespace routing;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace cpg
|
||||
{
|
||||
int const kNumBuckets = 256;
|
||||
double const kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
uint32_t Bearing(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK_LESS_OR_EQUAL(angle, 360, ("Angle should be less than or equal to 360."));
|
||||
CHECK_GREATER_OR_EQUAL(angle, 0, ("Angle should be greater than or equal to 0"));
|
||||
return math::Clamp(angle / kAnglesInBucket, 0.0, 255.0);
|
||||
}
|
||||
} // namespace cpg
|
||||
|
||||
// CandidatePathsGetter::Link ----------------------------------------------------------------------
|
||||
Graph::Edge CandidatePathsGetter::Link::GetStartEdge() const
|
||||
{
|
||||
auto * start = this;
|
||||
while (start->m_parent)
|
||||
start = start->m_parent.get();
|
||||
|
||||
return start->m_edge;
|
||||
}
|
||||
|
||||
bool CandidatePathsGetter::Link::IsPointOnPath(geometry::PointWithAltitude const & point) const
|
||||
{
|
||||
for (auto * l = this; l; l = l->m_parent.get())
|
||||
{
|
||||
if (l->m_edge.GetEndJunction() == point)
|
||||
{
|
||||
LOG(LDEBUG, ("A loop detected, skipping..."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// CandidatePathsGetter ----------------------------------------------------------------------------
|
||||
bool CandidatePathsGetter::GetLineCandidatesForPoints(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<Graph::EdgeVector>> & lineCandidates)
|
||||
{
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
if (i != points.size() - 1 && points[i].m_distanceToNextPoint == 0)
|
||||
{
|
||||
LOG(LINFO, ("Distance to next point is zero. Skipping the whole segment"));
|
||||
++m_stats.m_zeroDistToNextPointCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
lineCandidates.emplace_back();
|
||||
auto const isLastPoint = i == points.size() - 1;
|
||||
double const distanceToNextPointM = (isLastPoint ? points[i - 1] : points[i]).m_distanceToNextPoint;
|
||||
|
||||
vector<m2::PointD> pointCandidates;
|
||||
m_pointsGetter.GetCandidatePoints(mercator::FromLatLon(points[i].m_latLon), pointCandidates);
|
||||
GetLineCandidates(points[i], isLastPoint, distanceToNextPointM, pointCandidates, lineCandidates.back());
|
||||
|
||||
if (lineCandidates.back().empty())
|
||||
{
|
||||
LOG(LINFO, ("No candidate lines found for point", points[i].m_latLon, "Giving up"));
|
||||
++m_stats.m_noCandidateFound;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(lineCandidates.size(), points.size(), ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetStartLines(vector<m2::PointD> const & points, bool const isLastPoint,
|
||||
Graph::EdgeVector & edges)
|
||||
{
|
||||
for (auto const & pc : points)
|
||||
{
|
||||
Graph::EdgeListT tmp;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(geometry::PointWithAltitude(pc, 0 /* altitude */), tmp);
|
||||
else
|
||||
m_graph.GetIngoingEdges(geometry::PointWithAltitude(pc, 0 /* altitude */), tmp);
|
||||
|
||||
edges.insert(edges.end(), tmp.begin(), tmp.end());
|
||||
}
|
||||
|
||||
// Same edges may start on different points if those points are close enough.
|
||||
base::SortUnique(edges, less<Graph::Edge>(), EdgesAreAlmostEqual);
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetAllSuitablePaths(Graph::EdgeVector const & startLines, bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, vector<LinkPtr> & allPaths)
|
||||
{
|
||||
queue<LinkPtr> q;
|
||||
|
||||
for (auto const & e : startLines)
|
||||
{
|
||||
auto const u = make_shared<Link>(nullptr /* parent */, e, 0 /* distanceM */);
|
||||
q.push(u);
|
||||
}
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const u = q.front();
|
||||
q.pop();
|
||||
|
||||
auto const & currentEdge = u->m_edge;
|
||||
auto const currentEdgeLen = EdgeLength(currentEdge);
|
||||
|
||||
// TODO(mgsergio): Maybe weak this constraint a bit.
|
||||
if (u->m_distanceM + currentEdgeLen >= bearDistM)
|
||||
{
|
||||
allPaths.push_back(u);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT_LESS(u->m_distanceM + currentEdgeLen, bearDistM, ());
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(currentEdge.GetEndJunction(), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(currentEdge.GetStartJunction(), edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
// Fake edges are allowed only at the start/end of the path.
|
||||
if (e.IsFake())
|
||||
continue;
|
||||
|
||||
if (EdgesAreAlmostEqual(e.GetReverseEdge(), currentEdge))
|
||||
continue;
|
||||
|
||||
ASSERT(currentEdge.HasRealPart(), ());
|
||||
|
||||
if (!PassesRestriction(e, functionalRoadClass, formOfWay, 2 /* kFRCThreshold */, m_infoGetter))
|
||||
continue;
|
||||
|
||||
// TODO(mgsergio): Should we check form of way as well?
|
||||
|
||||
if (u->IsPointOnPath(e.GetEndJunction()))
|
||||
continue;
|
||||
|
||||
auto const p = make_shared<Link>(u, e, u->m_distanceM + currentEdgeLen);
|
||||
q.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetBestCandidatePaths(vector<LinkPtr> const & allPaths, bool const isLastPoint,
|
||||
uint32_t const requiredBearing, double const bearDistM,
|
||||
m2::PointD const & startPoint, vector<Graph::EdgeVector> & candidates)
|
||||
{
|
||||
set<CandidatePath> candidatePaths;
|
||||
set<CandidatePath> fakeEndingsCandidatePaths;
|
||||
|
||||
BearingPointsSelector pointsSelector(bearDistM, isLastPoint);
|
||||
for (auto const & l : allPaths)
|
||||
{
|
||||
auto const bearStartPoint = pointsSelector.GetStartPoint(l->GetStartEdge());
|
||||
auto const startPointsDistance = mercator::DistanceOnEarth(bearStartPoint, startPoint);
|
||||
|
||||
// Number of edges counting from the last one to check bearing on. Accorfing to OpenLR spec
|
||||
// we have to check bearing on a point that is no longer than 25 meters traveling down the path.
|
||||
// But sometimes we may skip the best place to stop and generate a candidate. So we check several
|
||||
// edges before the last one to avoid such a situation. Number of iterations is taken
|
||||
// by intuition.
|
||||
// Example:
|
||||
// o -------- o { Partners segment. }
|
||||
// o ------- o --- o { Our candidate. }
|
||||
// ^ 25m
|
||||
// ^ This one may be better than
|
||||
// ^ this one.
|
||||
// So we want to check them all.
|
||||
uint32_t traceBackIterationsLeft = 3;
|
||||
for (auto part = l; part; part = part->m_parent)
|
||||
{
|
||||
if (traceBackIterationsLeft == 0)
|
||||
break;
|
||||
|
||||
--traceBackIterationsLeft;
|
||||
|
||||
auto const bearEndPoint = pointsSelector.GetEndPoint(part->m_edge, part->m_distanceM);
|
||||
|
||||
auto const bearing = cpg::Bearing(bearStartPoint, bearEndPoint);
|
||||
auto const bearingDiff = AbsDifference(bearing, requiredBearing);
|
||||
auto const pathDistDiff = AbsDifference(part->m_distanceM, bearDistM);
|
||||
|
||||
// TODO(mgsergio): Check bearing is within tolerance. Add to canidates if it is.
|
||||
|
||||
if (part->m_hasFake)
|
||||
fakeEndingsCandidatePaths.emplace(part, bearingDiff, pathDistDiff, startPointsDistance);
|
||||
else
|
||||
candidatePaths.emplace(part, bearingDiff, pathDistDiff, startPointsDistance);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(none_of(begin(candidatePaths), end(candidatePaths), mem_fn(&CandidatePath::HasFakeEndings)), ());
|
||||
ASSERT(fakeEndingsCandidatePaths.empty() || any_of(begin(fakeEndingsCandidatePaths), end(fakeEndingsCandidatePaths),
|
||||
mem_fn(&CandidatePath::HasFakeEndings)),
|
||||
());
|
||||
|
||||
vector<CandidatePath> paths;
|
||||
copy_n(begin(candidatePaths), min(static_cast<size_t>(kMaxCandidates), candidatePaths.size()), back_inserter(paths));
|
||||
|
||||
copy_n(begin(fakeEndingsCandidatePaths),
|
||||
min(static_cast<size_t>(kMaxFakeCandidates), fakeEndingsCandidatePaths.size()), back_inserter(paths));
|
||||
|
||||
LOG(LDEBUG, ("List candidate paths..."));
|
||||
for (auto const & path : paths)
|
||||
{
|
||||
LOG(LDEBUG, ("CP:", path.m_bearingDiff, path.m_pathDistanceDiff, path.m_startPointDistance));
|
||||
Graph::EdgeVector edges;
|
||||
for (auto * p = path.m_path.get(); p; p = p->m_parent.get())
|
||||
edges.push_back(p->m_edge);
|
||||
if (!isLastPoint)
|
||||
reverse(begin(edges), end(edges));
|
||||
|
||||
candidates.emplace_back(std::move(edges));
|
||||
}
|
||||
}
|
||||
|
||||
void CandidatePathsGetter::GetLineCandidates(openlr::LocationReferencePoint const & p, bool const isLastPoint,
|
||||
double const distanceToNextPointM,
|
||||
vector<m2::PointD> const & pointCandidates,
|
||||
vector<Graph::EdgeVector> & candidates)
|
||||
{
|
||||
double const kDefaultBearDistM = 25.0;
|
||||
double const bearDistM = min(kDefaultBearDistM, distanceToNextPointM);
|
||||
|
||||
LOG(LINFO, ("BearDist is", bearDistM));
|
||||
|
||||
Graph::EdgeVector startLines;
|
||||
GetStartLines(pointCandidates, isLastPoint, startLines);
|
||||
|
||||
LOG(LINFO, (startLines.size(), "start line candidates found for point (LatLon)", p.m_latLon));
|
||||
LOG(LDEBUG, ("Listing start lines:"));
|
||||
for (auto const & e : startLines)
|
||||
LOG(LDEBUG, (LogAs2GisPath(e)));
|
||||
|
||||
auto const startPoint = mercator::FromLatLon(p.m_latLon);
|
||||
|
||||
vector<LinkPtr> allPaths;
|
||||
GetAllSuitablePaths(startLines, isLastPoint, bearDistM, p.m_functionalRoadClass, p.m_formOfWay, distanceToNextPointM,
|
||||
allPaths);
|
||||
GetBestCandidatePaths(allPaths, isLastPoint, p.m_bearing, bearDistM, startPoint, candidates);
|
||||
LOG(LDEBUG, (candidates.size(), "candidate paths found for point (LatLon)", p.m_latLon));
|
||||
}
|
||||
} // namespace openlr
|
||||
124
tools/openlr/candidate_paths_getter.hpp
Normal file
124
tools/openlr/candidate_paths_getter.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class CandidatePointsGetter;
|
||||
|
||||
class CandidatePathsGetter
|
||||
{
|
||||
public:
|
||||
CandidatePathsGetter(CandidatePointsGetter & pointsGetter, Graph & graph, RoadInfoGetter & infoGetter,
|
||||
v2::Stats & stat)
|
||||
: m_pointsGetter(pointsGetter)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stats(stat)
|
||||
{}
|
||||
|
||||
bool GetLineCandidatesForPoints(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<Graph::EdgeVector>> & lineCandidates);
|
||||
|
||||
private:
|
||||
struct Link;
|
||||
using LinkPtr = std::shared_ptr<Link>;
|
||||
|
||||
size_t static constexpr kMaxCandidates = 5;
|
||||
size_t static constexpr kMaxFakeCandidates = 2;
|
||||
|
||||
// TODO(mgsergio): Rename to Vertex.
|
||||
struct Link
|
||||
{
|
||||
Link(LinkPtr const & parent, Graph::Edge const & edge, double const distanceM)
|
||||
: m_parent(parent)
|
||||
, m_edge(edge)
|
||||
, m_distanceM(distanceM)
|
||||
, m_hasFake((parent && parent->m_hasFake) || edge.IsFake())
|
||||
{}
|
||||
|
||||
bool IsPointOnPath(geometry::PointWithAltitude const & point) const;
|
||||
|
||||
Graph::Edge GetStartEdge() const;
|
||||
|
||||
LinkPtr const m_parent;
|
||||
Graph::Edge const m_edge;
|
||||
double const m_distanceM;
|
||||
bool const m_hasFake;
|
||||
};
|
||||
|
||||
struct CandidatePath
|
||||
{
|
||||
double static constexpr kBearingDiffFactor = 5;
|
||||
double static constexpr kPathDistanceFactor = 1;
|
||||
double static constexpr kPointDistanceFactor = 2;
|
||||
|
||||
CandidatePath() = default;
|
||||
|
||||
CandidatePath(LinkPtr const path, uint32_t const bearingDiff, double const pathDistanceDiff,
|
||||
double const startPointDistance)
|
||||
: m_path(path)
|
||||
, m_bearingDiff(bearingDiff)
|
||||
, m_pathDistanceDiff(pathDistanceDiff)
|
||||
, m_startPointDistance(startPointDistance)
|
||||
{}
|
||||
|
||||
bool operator<(CandidatePath const & o) const { return GetPenalty() < o.GetPenalty(); }
|
||||
|
||||
double GetPenalty() const
|
||||
{
|
||||
return kBearingDiffFactor * m_bearingDiff + kPathDistanceFactor * m_pathDistanceDiff +
|
||||
kPointDistanceFactor * m_startPointDistance;
|
||||
}
|
||||
|
||||
bool HasFakeEndings() const { return m_path && m_path->m_hasFake; }
|
||||
|
||||
LinkPtr m_path = nullptr;
|
||||
uint32_t m_bearingDiff = std::numeric_limits<uint32_t>::max(); // Domain is roughly [0, 30]
|
||||
double m_pathDistanceDiff = std::numeric_limits<double>::max(); // Domain is roughly [0, 25]
|
||||
double m_startPointDistance = std::numeric_limits<double>::max(); // Domain is roughly [0, 50]
|
||||
};
|
||||
|
||||
// Note: In all methods below if |isLastPoint| is true than algorithm should
|
||||
// calculate all parameters (such as bearing, distance to next point, etc.)
|
||||
// relative to the last point.
|
||||
// o ----> o ----> o <---- o.
|
||||
// 1 2 3 4
|
||||
// ^ isLastPoint = true.
|
||||
// To calculate bearing for points 1 to 3 one have to go beardist from
|
||||
// previous point to the next one (eg. from 1 to 2 and from 2 to 3).
|
||||
// For point 4 one have to go from 4 to 3 reversing directions. And
|
||||
// distance-to-next point is taken from point 3. You can learn more in
|
||||
// TomTom OpenLR spec.
|
||||
|
||||
void GetStartLines(std::vector<m2::PointD> const & points, bool const isLastPoint, Graph::EdgeVector & edges);
|
||||
|
||||
void GetAllSuitablePaths(Graph::EdgeVector const & startLines, bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay, double distanceToNextPointM,
|
||||
std::vector<LinkPtr> & allPaths);
|
||||
|
||||
void GetBestCandidatePaths(std::vector<LinkPtr> const & allPaths, bool const isLastPoint,
|
||||
uint32_t const requiredBearing, double const bearDistM, m2::PointD const & startPoint,
|
||||
std::vector<Graph::EdgeVector> & candidates);
|
||||
|
||||
void GetLineCandidates(openlr::LocationReferencePoint const & p, bool const isLastPoint,
|
||||
double const distanceToNextPointM, std::vector<m2::PointD> const & pointCandidates,
|
||||
std::vector<Graph::EdgeVector> & candidates);
|
||||
|
||||
CandidatePointsGetter & m_pointsGetter;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stats;
|
||||
};
|
||||
} // namespace openlr
|
||||
82
tools/openlr/candidate_points_getter.cpp
Normal file
82
tools/openlr/candidate_points_getter.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "openlr/candidate_points_getter.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
void CandidatePointsGetter::FillJunctionPointCandidates(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
// TODO(mgsergio): Get optimal value using experiments on a sample.
|
||||
// Or start with small radius and scale it up when there are too few points.
|
||||
size_t const kRectSideMeters = 110;
|
||||
|
||||
auto const rect = mercator::RectByCenterXYAndSizeInMeters(p, kRectSideMeters);
|
||||
auto const selectCandidates = [&rect, &candidates](FeatureType & ft)
|
||||
{
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
ft.ForEachPoint([&rect, &candidates](m2::PointD const & candidate)
|
||||
{
|
||||
if (rect.IsPointInside(candidate))
|
||||
candidates.emplace_back(candidate);
|
||||
}, scales::GetUpperScale());
|
||||
};
|
||||
|
||||
m_dataSource.ForEachInRect(selectCandidates, rect, scales::GetUpperScale());
|
||||
|
||||
// TODO: Move this to a separate stage.
|
||||
// 1030292476 Does not match. Some problem occur with points.
|
||||
// Either points duplicate or something alike. Check this
|
||||
// later. The idea to fix this was to move SortUnique to the stage
|
||||
// after enriching with projections.
|
||||
|
||||
base::SortUnique(candidates, [&p](m2::PointD const & a, m2::PointD const & b)
|
||||
{ return mercator::DistanceOnEarth(a, p) < mercator::DistanceOnEarth(b, p); },
|
||||
[](m2::PointD const & a, m2::PointD const & b) { return a == b; });
|
||||
|
||||
candidates.resize(std::min(m_maxJunctionCandidates, candidates.size()));
|
||||
}
|
||||
|
||||
void CandidatePointsGetter::EnrichWithProjectionPoints(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
m_graph.ResetFakes();
|
||||
|
||||
std::vector<std::pair<Graph::Edge, geometry::PointWithAltitude>> vicinities;
|
||||
m_graph.FindClosestEdges(p, static_cast<uint32_t>(m_maxProjectionCandidates), vicinities);
|
||||
for (auto const & v : vicinities)
|
||||
{
|
||||
auto const & edge = v.first;
|
||||
auto const & junction = v.second;
|
||||
|
||||
ASSERT(edge.HasRealPart() && !edge.IsFake(), ());
|
||||
|
||||
if (PointsAreClose(edge.GetStartPoint(), junction.GetPoint()) ||
|
||||
PointsAreClose(edge.GetEndPoint(), junction.GetPoint()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const firstHalf = Edge::MakeFake(edge.GetStartJunction(), junction, edge);
|
||||
auto const secondHalf = Edge::MakeFake(junction, edge.GetEndJunction(), edge);
|
||||
|
||||
m_graph.AddOutgoingFakeEdge(firstHalf);
|
||||
m_graph.AddIngoingFakeEdge(firstHalf);
|
||||
m_graph.AddOutgoingFakeEdge(secondHalf);
|
||||
m_graph.AddIngoingFakeEdge(secondHalf);
|
||||
candidates.push_back(junction.GetPoint());
|
||||
}
|
||||
}
|
||||
} // namespace openlr
|
||||
43
tools/openlr/candidate_points_getter.hpp
Normal file
43
tools/openlr/candidate_points_getter.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class CandidatePointsGetter
|
||||
{
|
||||
public:
|
||||
CandidatePointsGetter(size_t const maxJunctionCandidates, size_t const maxProjectionCandidates,
|
||||
DataSource const & dataSource, Graph & graph)
|
||||
: m_maxJunctionCandidates(maxJunctionCandidates)
|
||||
, m_maxProjectionCandidates(maxProjectionCandidates)
|
||||
, m_dataSource(dataSource)
|
||||
, m_graph(graph)
|
||||
{}
|
||||
|
||||
void GetCandidatePoints(m2::PointD const & p, std::vector<m2::PointD> & candidates)
|
||||
{
|
||||
FillJunctionPointCandidates(p, candidates);
|
||||
EnrichWithProjectionPoints(p, candidates);
|
||||
}
|
||||
|
||||
private:
|
||||
void FillJunctionPointCandidates(m2::PointD const & p, std::vector<m2::PointD> & candidates);
|
||||
void EnrichWithProjectionPoints(m2::PointD const & p, std::vector<m2::PointD> & candidates);
|
||||
|
||||
size_t const m_maxJunctionCandidates;
|
||||
size_t const m_maxProjectionCandidates;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
Graph & m_graph;
|
||||
};
|
||||
} // namespace openlr
|
||||
175
tools/openlr/decoded_path.cpp
Normal file
175
tools/openlr/decoded_path.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#define THROW_IF_NODE_IS_EMPTY(node, exc, msg) \
|
||||
if (!node) \
|
||||
MYTHROW(exc, msg)
|
||||
|
||||
namespace
|
||||
{
|
||||
uint32_t UintFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse uint"));
|
||||
return node.text().as_uint();
|
||||
}
|
||||
|
||||
bool IsForwardFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse IsForward"));
|
||||
return node.text().as_bool();
|
||||
}
|
||||
|
||||
void LatLonToXML(ms::LatLon const & latLon, pugi::xml_node & node)
|
||||
{
|
||||
auto const kDigitsAfterComma = 5;
|
||||
node.append_child("lat").text() = strings::to_string_dac(latLon.m_lat, kDigitsAfterComma).data();
|
||||
node.append_child("lon").text() = strings::to_string_dac(latLon.m_lon, kDigitsAfterComma).data();
|
||||
}
|
||||
|
||||
void LatLonFromXML(pugi::xml_node const & node, ms::LatLon & latLon)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse latLon"));
|
||||
latLon.m_lat = node.child("lat").text().as_double();
|
||||
latLon.m_lon = node.child("lon").text().as_double();
|
||||
}
|
||||
|
||||
void FeatureIdFromXML(pugi::xml_node const & node, DataSource const & dataSource, FeatureID & fid)
|
||||
{
|
||||
THROW_IF_NODE_IS_EMPTY(node, openlr::DecodedPathLoadError, ("Can't parse CountryName"));
|
||||
auto const countryName = node.child("CountryName").text().as_string();
|
||||
fid.m_mwmId = dataSource.GetMwmIdByCountryFile(platform::CountryFile(countryName));
|
||||
CHECK(fid.m_mwmId.IsAlive(), ("Can't get mwm id for country", countryName));
|
||||
fid.m_index = node.child("Index").text().as_uint();
|
||||
}
|
||||
|
||||
void FeatureIdToXML(FeatureID const & fid, pugi::xml_node & node)
|
||||
{
|
||||
node.append_child("CountryName").text() = fid.m_mwmId.GetInfo()->GetCountryName().data();
|
||||
node.append_child("Index").text() = fid.m_index;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
uint32_t UintValueFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
auto const value = node.child("olr:value");
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
return UintFromXML(value);
|
||||
}
|
||||
|
||||
void WriteAsMappingForSpark(std::string const & fileName, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
std::ofstream ofs(fileName);
|
||||
if (!ofs.is_open())
|
||||
MYTHROW(DecodedPathSaveError, ("Can't write to file", fileName, strerror(errno)));
|
||||
|
||||
WriteAsMappingForSpark(ofs, paths);
|
||||
|
||||
if (ofs.fail())
|
||||
MYTHROW(DecodedPathSaveError, ("An error occured while writing file", fileName, strerror(errno)));
|
||||
}
|
||||
|
||||
void WriteAsMappingForSpark(std::ostream & ost, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
auto const flags = ost.flags();
|
||||
ost << std::fixed; // Avoid scientific notation cause '-' is used as fields separator.
|
||||
SCOPE_GUARD(guard, ([&ost, &flags] { ost.flags(flags); }));
|
||||
|
||||
for (auto const & p : paths)
|
||||
{
|
||||
if (p.m_path.empty())
|
||||
continue;
|
||||
|
||||
ost << p.m_segmentId.Get() << '\t';
|
||||
|
||||
auto const kFieldSep = '-';
|
||||
auto const kSegmentSep = '=';
|
||||
for (auto it = std::begin(p.m_path); it != std::end(p.m_path); ++it)
|
||||
{
|
||||
auto const & fid = it->GetFeatureId();
|
||||
ost << fid.m_mwmId.GetInfo()->GetCountryName() << kFieldSep << fid.m_index << kFieldSep << it->GetSegId()
|
||||
<< kFieldSep << (it->IsForward() ? "fwd" : "bwd") << kFieldSep
|
||||
<< mercator::DistanceOnEarth(GetStart(*it), GetEnd(*it));
|
||||
|
||||
if (std::next(it) != std::end(p.m_path))
|
||||
ost << kSegmentSep;
|
||||
}
|
||||
ost << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void PathFromXML(pugi::xml_node const & node, DataSource const & dataSource, Path & p)
|
||||
{
|
||||
auto const edges = node.select_nodes("RoadEdge");
|
||||
for (auto const & xmlE : edges)
|
||||
{
|
||||
auto e = xmlE.node();
|
||||
|
||||
FeatureID fid;
|
||||
FeatureIdFromXML(e.child("FeatureID"), dataSource, fid);
|
||||
|
||||
auto const isForward = IsForwardFromXML(e.child("IsForward"));
|
||||
auto const segmentId = UintFromXML(e.child("SegmentId"));
|
||||
|
||||
ms::LatLon start, end;
|
||||
LatLonFromXML(e.child("StartJunction"), start);
|
||||
LatLonFromXML(e.child("EndJunction"), end);
|
||||
|
||||
p.push_back(
|
||||
Edge::MakeReal(fid, isForward, segmentId,
|
||||
geometry::PointWithAltitude(mercator::FromLatLon(start), geometry::kDefaultAltitudeMeters),
|
||||
geometry::PointWithAltitude(mercator::FromLatLon(end), geometry::kDefaultAltitudeMeters)));
|
||||
}
|
||||
}
|
||||
|
||||
void PathToXML(Path const & path, pugi::xml_node & node)
|
||||
{
|
||||
for (auto const & e : path)
|
||||
{
|
||||
auto edge = node.append_child("RoadEdge");
|
||||
|
||||
{
|
||||
auto fid = edge.append_child("FeatureID");
|
||||
FeatureIdToXML(e.GetFeatureId(), fid);
|
||||
}
|
||||
|
||||
edge.append_child("IsForward").text() = e.IsForward();
|
||||
edge.append_child("SegmentId").text() = e.GetSegId();
|
||||
{
|
||||
auto start = edge.append_child("StartJunction");
|
||||
auto end = edge.append_child("EndJunction");
|
||||
LatLonToXML(mercator::ToLatLon(GetStart(e)), start);
|
||||
LatLonToXML(mercator::ToLatLon(GetEnd(e)), end);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace openlr
|
||||
|
||||
namespace routing
|
||||
{
|
||||
std::vector<m2::PointD> GetPoints(openlr::Path const & p)
|
||||
{
|
||||
CHECK(!p.empty(), ("Path should not be empty"));
|
||||
std::vector<m2::PointD> points;
|
||||
points.push_back(GetStart(p.front()));
|
||||
for (auto const & e : p)
|
||||
points.push_back(GetEnd(e));
|
||||
|
||||
return points;
|
||||
}
|
||||
} // namespace routing
|
||||
55
tools/openlr/decoded_path.hpp
Normal file
55
tools/openlr/decoded_path.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
#include "base/newtype.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
class DataSource;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using Path = routing::RoadGraphBase::EdgeVector;
|
||||
using routing::Edge;
|
||||
|
||||
NEWTYPE(uint32_t, PartnerSegmentId);
|
||||
NEWTYPE_SIMPLE_OUTPUT(PartnerSegmentId);
|
||||
|
||||
DECLARE_EXCEPTION(DecodedPathLoadError, RootException);
|
||||
DECLARE_EXCEPTION(DecodedPathSaveError, RootException);
|
||||
|
||||
struct DecodedPath
|
||||
{
|
||||
PartnerSegmentId m_segmentId;
|
||||
Path m_path;
|
||||
};
|
||||
|
||||
uint32_t UintValueFromXML(pugi::xml_node const & node);
|
||||
|
||||
void WriteAsMappingForSpark(std::string const & fileName, std::vector<DecodedPath> const & paths);
|
||||
void WriteAsMappingForSpark(std::ostream & ost, std::vector<DecodedPath> const & paths);
|
||||
|
||||
void PathFromXML(pugi::xml_node const & node, DataSource const & dataSource, Path & path);
|
||||
void PathToXML(Path const & path, pugi::xml_node & node);
|
||||
} // namespace openlr
|
||||
|
||||
namespace routing
|
||||
{
|
||||
inline m2::PointD GetStart(Edge const & e)
|
||||
{
|
||||
return e.GetStartJunction().GetPoint();
|
||||
}
|
||||
inline m2::PointD GetEnd(Edge const & e)
|
||||
{
|
||||
return e.GetEndJunction().GetPoint();
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetPoints(routing::RoadGraphBase::EdgeVector const & p);
|
||||
} // namespace routing
|
||||
86
tools/openlr/graph.cpp
Normal file
86
tools/openlr/graph.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "openlr/graph.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using EdgeGetter = void (IRoadGraph::*)(geometry::PointWithAltitude const &, RoadGraphBase::EdgeListT &) const;
|
||||
|
||||
void GetRegularEdges(geometry::PointWithAltitude const & junction, IRoadGraph const & graph,
|
||||
EdgeGetter const edgeGetter, Graph::EdgeCacheT & cache, Graph::EdgeListT & edges)
|
||||
{
|
||||
auto const it = cache.find(junction);
|
||||
if (it == end(cache))
|
||||
{
|
||||
auto & es = cache[junction];
|
||||
(graph.*edgeGetter)(junction, es);
|
||||
edges.append(begin(es), end(es));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const & es = it->second;
|
||||
edges.append(begin(es), end(es));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Graph::Graph(DataSource & dataSource, shared_ptr<CarModelFactory> carModelFactory)
|
||||
: m_dataSource(dataSource, nullptr /* numMwmIDs */)
|
||||
, m_graph(m_dataSource, IRoadGraph::Mode::ObeyOnewayTag, carModelFactory)
|
||||
{}
|
||||
|
||||
void Graph::GetOutgoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularOutgoingEdges(junction, edges);
|
||||
m_graph.GetFakeOutgoingEdges(junction, edges);
|
||||
}
|
||||
|
||||
void Graph::GetIngoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularIngoingEdges(junction, edges);
|
||||
m_graph.GetFakeIngoingEdges(junction, edges);
|
||||
}
|
||||
|
||||
void Graph::GetRegularOutgoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularEdges(junction, m_graph, &IRoadGraph::GetRegularOutgoingEdges, m_outgoingCache, edges);
|
||||
}
|
||||
|
||||
void Graph::GetRegularIngoingEdges(Junction const & junction, EdgeListT & edges)
|
||||
{
|
||||
GetRegularEdges(junction, m_graph, &IRoadGraph::GetRegularIngoingEdges, m_ingoingCache, edges);
|
||||
}
|
||||
|
||||
void Graph::FindClosestEdges(m2::PointD const & point, uint32_t const count,
|
||||
vector<pair<Edge, Junction>> & vicinities) const
|
||||
{
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(point, FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
count, vicinities);
|
||||
}
|
||||
|
||||
void Graph::AddIngoingFakeEdge(Edge const & e)
|
||||
{
|
||||
m_graph.AddIngoingFakeEdge(e);
|
||||
}
|
||||
|
||||
void Graph::AddOutgoingFakeEdge(Edge const & e)
|
||||
{
|
||||
m_graph.AddOutgoingFakeEdge(e);
|
||||
}
|
||||
|
||||
void Graph::GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const
|
||||
{
|
||||
m_graph.GetFeatureTypes(featureId, types);
|
||||
}
|
||||
} // namespace openlr
|
||||
62
tools/openlr/graph.hpp
Normal file
62
tools/openlr/graph.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/data_source.hpp"
|
||||
#include "routing/features_road_graph.hpp"
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "routing_common/car_model.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// TODO(mgsergio): Inherit from FeaturesRoadGraph.
|
||||
class Graph
|
||||
{
|
||||
public:
|
||||
using Edge = routing::Edge;
|
||||
using EdgeListT = routing::FeaturesRoadGraph::EdgeListT;
|
||||
using EdgeVector = routing::FeaturesRoadGraph::EdgeVector;
|
||||
using Junction = geometry::PointWithAltitude;
|
||||
|
||||
Graph(DataSource & dataSource, std::shared_ptr<routing::CarModelFactory> carModelFactory);
|
||||
|
||||
// Appends edges such as that edge.GetStartJunction() == junction to the |edges|.
|
||||
void GetOutgoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges);
|
||||
// Appends edges such as that edge.GetEndJunction() == junction to the |edges|.
|
||||
void GetIngoingEdges(geometry::PointWithAltitude const & junction, EdgeListT & edges);
|
||||
|
||||
// Appends edges such as that edge.GetStartJunction() == junction and edge.IsFake() == false
|
||||
// to the |edges|.
|
||||
void GetRegularOutgoingEdges(Junction const & junction, EdgeListT & edges);
|
||||
// Appends edges such as that edge.GetEndJunction() == junction and edge.IsFale() == false
|
||||
// to the |edges|.
|
||||
void GetRegularIngoingEdges(Junction const & junction, EdgeListT & edges);
|
||||
|
||||
void FindClosestEdges(m2::PointD const & point, uint32_t const count,
|
||||
std::vector<std::pair<Edge, Junction>> & vicinities) const;
|
||||
|
||||
void AddIngoingFakeEdge(Edge const & e);
|
||||
void AddOutgoingFakeEdge(Edge const & e);
|
||||
|
||||
void ResetFakes() { m_graph.ResetFakes(); }
|
||||
|
||||
void GetFeatureTypes(FeatureID const & featureId, feature::TypesHolder & types) const;
|
||||
|
||||
using EdgeCacheT = std::map<Junction, EdgeListT>;
|
||||
|
||||
private:
|
||||
routing::MwmDataSource m_dataSource;
|
||||
routing::FeaturesRoadGraph m_graph;
|
||||
EdgeCacheT m_outgoingCache, m_ingoingCache;
|
||||
};
|
||||
} // namespace openlr
|
||||
230
tools/openlr/helpers.cpp
Normal file
230
tools/openlr/helpers.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace openlr;
|
||||
using namespace std;
|
||||
|
||||
openlr::FunctionalRoadClass HighwayClassToFunctionalRoadClass(ftypes::HighwayClass const & hwClass)
|
||||
{
|
||||
switch (hwClass)
|
||||
{
|
||||
case ftypes::HighwayClass::Motorway: return openlr::FunctionalRoadClass::FRC0;
|
||||
case ftypes::HighwayClass::Trunk: return openlr::FunctionalRoadClass::FRC0;
|
||||
case ftypes::HighwayClass::Primary: return openlr::FunctionalRoadClass::FRC1;
|
||||
case ftypes::HighwayClass::Secondary: return openlr::FunctionalRoadClass::FRC2;
|
||||
case ftypes::HighwayClass::Tertiary: return openlr::FunctionalRoadClass::FRC3;
|
||||
case ftypes::HighwayClass::LivingStreet: return openlr::FunctionalRoadClass::FRC4;
|
||||
case ftypes::HighwayClass::Service: return openlr::FunctionalRoadClass::FRC5;
|
||||
case ftypes::HighwayClass::ServiceMinor: return openlr::FunctionalRoadClass::FRC6;
|
||||
default: return openlr::FunctionalRoadClass::FRC7;
|
||||
}
|
||||
}
|
||||
|
||||
/// \returns nullopt if |e| doesn't conform to |functionalRoadClass| and score otherwise.
|
||||
optional<Score> GetFrcScore(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, RoadInfoGetter & infoGetter)
|
||||
{
|
||||
CHECK(!e.IsFake(), ());
|
||||
Score constexpr kMaxScoreForFrc = 25;
|
||||
|
||||
if (functionalRoadClass == FunctionalRoadClass::NotAValue)
|
||||
return nullopt;
|
||||
|
||||
auto const hwClass = infoGetter.Get(e.GetFeatureId()).m_hwClass;
|
||||
|
||||
using ftypes::HighwayClass;
|
||||
|
||||
switch (functionalRoadClass)
|
||||
{
|
||||
case FunctionalRoadClass::FRC0:
|
||||
return hwClass == HighwayClass::Motorway || hwClass == HighwayClass::Trunk ? optional<Score>(kMaxScoreForFrc) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC1:
|
||||
return (hwClass == HighwayClass::Motorway || hwClass == HighwayClass::Trunk || hwClass == HighwayClass::Primary) ? optional<Score>(kMaxScoreForFrc)
|
||||
: nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC2:
|
||||
case FunctionalRoadClass::FRC3:
|
||||
if (hwClass == HighwayClass::Secondary || hwClass == HighwayClass::Tertiary)
|
||||
return optional<Score>(kMaxScoreForFrc);
|
||||
|
||||
return hwClass == HighwayClass::Primary || hwClass == HighwayClass::LivingStreet ? optional<Score>(0) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC4:
|
||||
if (hwClass == HighwayClass::LivingStreet || hwClass == HighwayClass::Service)
|
||||
return optional<Score>(kMaxScoreForFrc);
|
||||
|
||||
return (hwClass == HighwayClass::Tertiary || hwClass == HighwayClass::Secondary) ? optional<Score>(0) : nullopt;
|
||||
|
||||
case FunctionalRoadClass::FRC5:
|
||||
case FunctionalRoadClass::FRC6:
|
||||
case FunctionalRoadClass::FRC7:
|
||||
return (hwClass == HighwayClass::LivingStreet || hwClass == HighwayClass::Service ||
|
||||
hwClass == HighwayClass::ServiceMinor)
|
||||
? optional<Score>(kMaxScoreForFrc)
|
||||
: nullopt;
|
||||
|
||||
case FunctionalRoadClass::NotAValue: UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// BearingPointsSelector ---------------------------------------------------------------------------
|
||||
BearingPointsSelector::BearingPointsSelector(uint32_t bearDistM, bool isLastPoint)
|
||||
: m_bearDistM(bearDistM)
|
||||
, m_isLastPoint(isLastPoint)
|
||||
{}
|
||||
|
||||
m2::PointD BearingPointsSelector::GetStartPoint(Graph::Edge const & e) const
|
||||
{
|
||||
return m_isLastPoint ? e.GetEndPoint() : e.GetStartPoint();
|
||||
}
|
||||
|
||||
m2::PointD BearingPointsSelector::GetEndPoint(Graph::Edge const & e, double distanceM) const
|
||||
{
|
||||
if (distanceM < m_bearDistM && m_bearDistM <= distanceM + EdgeLength(e))
|
||||
{
|
||||
auto const edgeLen = EdgeLength(e);
|
||||
auto const edgeBearDist = min(m_bearDistM - distanceM, edgeLen);
|
||||
CHECK_LESS_OR_EQUAL(edgeBearDist, edgeLen, ());
|
||||
return m_isLastPoint ? PointAtSegmentM(e.GetEndPoint(), e.GetStartPoint(), edgeBearDist)
|
||||
: PointAtSegmentM(e.GetStartPoint(), e.GetEndPoint(), edgeBearDist);
|
||||
}
|
||||
return m_isLastPoint ? e.GetStartPoint() : e.GetEndPoint();
|
||||
}
|
||||
|
||||
bool PointsAreClose(m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
double const kMwmRoadCrossingRadiusMeters = routing::GetRoadCrossingRadiusMeters();
|
||||
return mercator::DistanceOnEarth(p1, p2) < kMwmRoadCrossingRadiusMeters;
|
||||
}
|
||||
|
||||
double EdgeLength(Graph::Edge const & e)
|
||||
{
|
||||
return mercator::DistanceOnEarth(e.GetStartPoint(), e.GetEndPoint());
|
||||
}
|
||||
|
||||
bool EdgesAreAlmostEqual(Graph::Edge const & e1, Graph::Edge const & e2)
|
||||
{
|
||||
// TODO(mgsergio): Do I need to check fields other than points?
|
||||
return PointsAreClose(e1.GetStartPoint(), e2.GetStartPoint()) && PointsAreClose(e1.GetEndPoint(), e2.GetEndPoint());
|
||||
}
|
||||
|
||||
string LogAs2GisPath(Graph::EdgeVector const & path)
|
||||
{
|
||||
CHECK(!path.empty(), ("Paths should not be empty"));
|
||||
|
||||
ostringstream ost;
|
||||
ost << "https://2gis.ru/moscow?queryState=";
|
||||
|
||||
auto ll = mercator::ToLatLon(path.front().GetStartPoint());
|
||||
ost << "center%2F" << ll.m_lon << "%2C" << ll.m_lat << "%2F";
|
||||
ost << "zoom%2F" << 17 << "%2F";
|
||||
ost << "ruler%2Fpoints%2F";
|
||||
for (auto const & e : path)
|
||||
{
|
||||
ll = mercator::ToLatLon(e.GetStartPoint());
|
||||
ost << ll.m_lon << "%20" << ll.m_lat << "%2C";
|
||||
}
|
||||
ll = mercator::ToLatLon(path.back().GetEndPoint());
|
||||
ost << ll.m_lon << "%20" << ll.m_lat;
|
||||
|
||||
return ost.str();
|
||||
}
|
||||
|
||||
string LogAs2GisPath(Graph::Edge const & e)
|
||||
{
|
||||
return LogAs2GisPath(Graph::EdgeVector({e}));
|
||||
}
|
||||
|
||||
bool PassesRestriction(Graph::Edge const & e, FunctionalRoadClass restriction, FormOfWay formOfWay, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter)
|
||||
{
|
||||
if (e.IsFake() || restriction == FunctionalRoadClass::NotAValue)
|
||||
return true;
|
||||
|
||||
auto const frc = HighwayClassToFunctionalRoadClass(infoGetter.Get(e.GetFeatureId()).m_hwClass);
|
||||
return static_cast<int>(frc) <= static_cast<int>(restriction) + frcThreshold;
|
||||
}
|
||||
|
||||
bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
RoadInfoGetter & infoGetter, Score & score)
|
||||
{
|
||||
CHECK(!e.IsFake(), ("Edges should not be fake:", e));
|
||||
auto const frcScore = GetFrcScore(e, functionalRoadClass, infoGetter);
|
||||
if (!frcScore)
|
||||
return false;
|
||||
|
||||
score = *frcScore;
|
||||
Score constexpr kScoreForFormOfWay = 25;
|
||||
if (formOfWay == FormOfWay::Roundabout && infoGetter.Get(e.GetFeatureId()).m_isRoundabout)
|
||||
score += kScoreForFormOfWay;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConformLfrcnp(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter)
|
||||
{
|
||||
if (e.IsFake() || lowestFrcToNextPoint == FunctionalRoadClass::NotAValue)
|
||||
return true;
|
||||
|
||||
auto const frc = HighwayClassToFunctionalRoadClass(infoGetter.Get(e.GetFeatureId()).m_hwClass);
|
||||
return static_cast<int>(frc) <= static_cast<int>(lowestFrcToNextPoint) + frcThreshold;
|
||||
}
|
||||
|
||||
bool ConformLfrcnpV3(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, RoadInfoGetter & infoGetter)
|
||||
{
|
||||
return GetFrcScore(e, lowestFrcToNextPoint, infoGetter).has_value();
|
||||
}
|
||||
|
||||
size_t IntersectionLen(Graph::EdgeVector a, Graph::EdgeVector b)
|
||||
{
|
||||
sort(a.begin(), a.end());
|
||||
sort(b.begin(), b.end());
|
||||
return set_intersection(a.begin(), a.end(), b.begin(), b.end(), CounterIterator()).GetCount();
|
||||
}
|
||||
|
||||
bool SuffixEqualsPrefix(Graph::EdgeVector const & a, Graph::EdgeVector const & b, size_t len)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(len, a.size(), ());
|
||||
CHECK_LESS_OR_EQUAL(len, b.size(), ());
|
||||
return equal(a.end() - len, a.end(), b.begin());
|
||||
}
|
||||
|
||||
// Returns a length of the longest suffix of |a| that matches any prefix of |b|.
|
||||
// Neither |a| nor |b| can contain several repetitions of any edge.
|
||||
// Returns -1 if |a| intersection |b| is not equal to some suffix of |a| and some prefix of |b|.
|
||||
int32_t PathOverlappingLen(Graph::EdgeVector const & a, Graph::EdgeVector const & b)
|
||||
{
|
||||
auto const len = IntersectionLen(a, b);
|
||||
if (SuffixEqualsPrefix(a, b, len))
|
||||
return base::checked_cast<int32_t>(len);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
m2::PointD PointAtSegmentM(m2::PointD const & p1, m2::PointD const & p2, double const distanceM)
|
||||
{
|
||||
auto const v = p2 - p1;
|
||||
auto const l = v.Length();
|
||||
auto const L = mercator::DistanceOnEarth(p1, p2);
|
||||
auto const delta = distanceM * l / L;
|
||||
return PointAtSegment(p1, p2, delta);
|
||||
}
|
||||
} // namespace openlr
|
||||
71
tools/openlr/helpers.hpp
Normal file
71
tools/openlr/helpers.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter;
|
||||
|
||||
// This class is used to get points for further bearing calculations.
|
||||
class BearingPointsSelector
|
||||
{
|
||||
public:
|
||||
BearingPointsSelector(uint32_t bearDistM, bool isLastPoint);
|
||||
|
||||
m2::PointD GetStartPoint(Graph::Edge const & e) const;
|
||||
m2::PointD GetEndPoint(Graph::Edge const & e, double distanceM) const;
|
||||
|
||||
private:
|
||||
double m_bearDistM;
|
||||
bool m_isLastPoint;
|
||||
};
|
||||
|
||||
bool PointsAreClose(m2::PointD const & p1, m2::PointD const & p2);
|
||||
|
||||
double EdgeLength(Graph::Edge const & e);
|
||||
|
||||
bool EdgesAreAlmostEqual(Graph::Edge const & e1, Graph::Edge const & e2);
|
||||
|
||||
// TODO(mgsergio): Remove when unused.
|
||||
std::string LogAs2GisPath(Graph::EdgeVector const & path);
|
||||
std::string LogAs2GisPath(Graph::Edge const & e);
|
||||
|
||||
template <typename T, typename U, std::enable_if_t<!(std::is_signed<T>::value ^ std::is_signed<U>::value), int> = 0>
|
||||
std::common_type_t<T, U> AbsDifference(T const a, U const b)
|
||||
{
|
||||
return a >= b ? a - b : b - a;
|
||||
}
|
||||
|
||||
bool PassesRestriction(Graph::Edge const & e, FunctionalRoadClass restriction, FormOfWay formOfWay, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter);
|
||||
/// \returns true if |e| conforms |functionalRoadClass| and |formOfWay| and false otherwise.
|
||||
/// \note If the method returns true |score| should be considered next.
|
||||
bool PassesRestrictionV3(Graph::Edge const & e, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
RoadInfoGetter & infoGetter, Score & score);
|
||||
|
||||
/// \returns true if edge |e| conforms Lowest Functional Road Class to Next Point.
|
||||
/// \note frc means Functional Road Class. Please see openlr documentation for details:
|
||||
/// http://www.openlr.org/data/docs/whitepaper/1_0/OpenLR-Whitepaper_v1.0.pdf
|
||||
bool ConformLfrcnp(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, int frcThreshold,
|
||||
RoadInfoGetter & infoGetter);
|
||||
bool ConformLfrcnpV3(Graph::Edge const & e, FunctionalRoadClass lowestFrcToNextPoint, RoadInfoGetter & infoGetter);
|
||||
|
||||
size_t IntersectionLen(Graph::EdgeVector a, Graph::EdgeVector b);
|
||||
|
||||
bool SuffixEqualsPrefix(Graph::EdgeVector const & a, Graph::EdgeVector const & b, size_t len);
|
||||
|
||||
// Returns a length of the longest suffix of |a| that matches any prefix of |b|.
|
||||
// Neither |a| nor |b| can contain several repetitions of any edge.
|
||||
// Returns -1 if |a| intersection |b| is not equal to some suffix of |a| and some prefix of |b|.
|
||||
int32_t PathOverlappingLen(Graph::EdgeVector const & a, Graph::EdgeVector const & b);
|
||||
|
||||
m2::PointD PointAtSegmentM(m2::PointD const & p1, m2::PointD const & p2, double const distanceM);
|
||||
} // namespace openlr
|
||||
499
tools/openlr/openlr_decoder.cpp
Normal file
499
tools/openlr/openlr_decoder.cpp
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
#include "openlr/openlr_decoder.hpp"
|
||||
|
||||
#include "openlr/cache_line_size.hpp"
|
||||
#include "openlr/candidate_paths_getter.hpp"
|
||||
#include "openlr/candidate_points_getter.hpp"
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/paths_connector.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/router.hpp"
|
||||
#include "openlr/score_candidate_paths_getter.hpp"
|
||||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
#include "openlr/score_paths_connector.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/way_point.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "routing_common/car_model.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/polyline2d.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct alignas(kCacheLineSize) Stats
|
||||
{
|
||||
void Add(Stats const & rhs)
|
||||
{
|
||||
m_shortRoutes += rhs.m_shortRoutes;
|
||||
m_zeroCanditates += rhs.m_zeroCanditates;
|
||||
m_moreThanOneCandidate += rhs.m_moreThanOneCandidate;
|
||||
m_routesFailed += rhs.m_routesFailed;
|
||||
m_tightOffsets += rhs.m_tightOffsets;
|
||||
m_routesHandled += rhs.m_routesHandled;
|
||||
}
|
||||
|
||||
void Report() const
|
||||
{
|
||||
LOG(LINFO, ("Parsed segments:", m_routesHandled));
|
||||
LOG(LINFO, ("Routes failed:", m_routesFailed));
|
||||
LOG(LINFO, ("Tight offsets:", m_tightOffsets));
|
||||
LOG(LINFO, ("Short routes:", m_shortRoutes));
|
||||
LOG(LINFO, ("Ambiguous routes:", m_moreThanOneCandidate));
|
||||
LOG(LINFO, ("Path is not reconstructed:", m_zeroCanditates));
|
||||
}
|
||||
|
||||
uint32_t m_shortRoutes = 0;
|
||||
uint32_t m_zeroCanditates = 0;
|
||||
uint32_t m_moreThanOneCandidate = 0;
|
||||
uint32_t m_routesFailed = 0;
|
||||
uint32_t m_tightOffsets = 0;
|
||||
uint32_t m_routesHandled = 0;
|
||||
};
|
||||
|
||||
bool IsRealVertex(m2::PointD const & p, FeatureID const & fid, DataSource const & dataSource)
|
||||
{
|
||||
FeaturesLoaderGuard g(dataSource, fid.m_mwmId);
|
||||
auto const ft = g.GetOriginalFeatureByIndex(fid.m_index);
|
||||
bool matched = false;
|
||||
ft->ForEachPoint([&p, &matched](m2::PointD const & fp)
|
||||
{
|
||||
if (p == fp)
|
||||
matched = true;
|
||||
}, FeatureType::BEST_GEOMETRY);
|
||||
return matched;
|
||||
}
|
||||
|
||||
void ExpandFake(Graph::EdgeVector & path, Graph::EdgeVector::iterator edgeIt, DataSource const & dataSource, Graph & g)
|
||||
{
|
||||
if (!edgeIt->IsFake())
|
||||
return;
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
bool startIsFake = true;
|
||||
if (IsRealVertex(edgeIt->GetStartPoint(), edgeIt->GetFeatureId(), dataSource))
|
||||
{
|
||||
g.GetRegularOutgoingEdges(edgeIt->GetStartJunction(), edges);
|
||||
startIsFake = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(IsRealVertex(edgeIt->GetEndPoint(), edgeIt->GetFeatureId(), dataSource), ());
|
||||
g.GetRegularIngoingEdges(edgeIt->GetEndJunction(), edges);
|
||||
}
|
||||
|
||||
CHECK(!edges.empty(), ());
|
||||
|
||||
auto it = find_if(begin(edges), end(edges), [&edgeIt](Graph::Edge const & real)
|
||||
{
|
||||
if (real.GetFeatureId() == edgeIt->GetFeatureId() && real.GetSegId() == edgeIt->GetSegId())
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// For features which cross mwm border FeatureIds may not match. Check geometry.
|
||||
if (it == end(edges))
|
||||
{
|
||||
it = find_if(begin(edges), end(edges), [&edgeIt, &startIsFake](Graph::Edge const & real)
|
||||
{
|
||||
// Features from the same mwm should be already matched.
|
||||
if (real.GetFeatureId().m_mwmId == edgeIt->GetFeatureId().m_mwmId)
|
||||
return false;
|
||||
|
||||
auto const fakePoint = startIsFake ? edgeIt->GetStartPoint() : edgeIt->GetEndPoint();
|
||||
m2::ParametrizedSegment<m2::PointD> const realGeometry(real.GetStartPoint(), real.GetEndPoint());
|
||||
auto const projectedPoint = realGeometry.ClosestPointTo(fakePoint);
|
||||
|
||||
auto constexpr kCrossMwmMatchDistanceM = 1.0;
|
||||
if (mercator::DistanceOnEarth(fakePoint, projectedPoint) < kCrossMwmMatchDistanceM)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
CHECK(it != end(edges), ());
|
||||
|
||||
// If a fake edge is larger than a half of the corresponding real one, substitute
|
||||
// the fake one with real one. Drop the fake one otherwize.
|
||||
if (2 * EdgeLength(*edgeIt) >= EdgeLength(*it))
|
||||
*edgeIt = *it;
|
||||
else
|
||||
path.erase(edgeIt);
|
||||
}
|
||||
|
||||
void ExpandFakes(DataSource const & dataSource, Graph & g, Graph::EdgeVector & path)
|
||||
{
|
||||
ASSERT(!path.empty(), ());
|
||||
|
||||
ExpandFake(path, std::begin(path), dataSource, g);
|
||||
if (path.empty())
|
||||
return;
|
||||
ExpandFake(path, std::end(path) - 1, dataSource, g);
|
||||
}
|
||||
|
||||
// Returns an iterator pointing to the first edge that should not be cut off.
|
||||
// Offsets denote a distance in meters one should travel from the start/end of the path
|
||||
// to some point along that path and drop everything form the start to that point or from
|
||||
// that point to the end.
|
||||
template <typename InputIterator>
|
||||
InputIterator CutOffset(InputIterator start, InputIterator stop, double offset, bool keepEnd)
|
||||
{
|
||||
if (offset == 0.0)
|
||||
return start;
|
||||
|
||||
for (double distance = 0.0; start != stop; ++start)
|
||||
{
|
||||
auto const edgeLen = EdgeLength(*start);
|
||||
if (distance <= offset && offset < distance + edgeLen)
|
||||
{
|
||||
// Throw out this edge if (offset - distance) is greater than edgeLength / 2.
|
||||
if (!keepEnd && offset - distance >= edgeLen / 2.0)
|
||||
++start;
|
||||
break;
|
||||
}
|
||||
distance += edgeLen;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
void CopyWithoutOffsets(InputIterator start, InputIterator stop, OutputIterator out, uint32_t positiveOffset,
|
||||
uint32_t negativeOffset, bool keepEnds)
|
||||
{
|
||||
auto from = start;
|
||||
auto to = stop;
|
||||
|
||||
if (distance(start, stop) > 1)
|
||||
{
|
||||
from = CutOffset(start, stop, positiveOffset, keepEnds);
|
||||
// |to| points past the last edge we need to take.
|
||||
to = CutOffset(reverse_iterator<InputIterator>(stop), reverse_iterator<InputIterator>(start), negativeOffset,
|
||||
keepEnds)
|
||||
.base();
|
||||
}
|
||||
|
||||
if (!keepEnds)
|
||||
CHECK(from <= to, ("From iterator is less or equal than to."));
|
||||
|
||||
if (from >= to)
|
||||
return;
|
||||
|
||||
copy(from, to, out);
|
||||
}
|
||||
|
||||
class SegmentsDecoderV2
|
||||
{
|
||||
public:
|
||||
SegmentsDecoderV2(DataSource & dataSource, unique_ptr<CarModelFactory> cmf)
|
||||
: m_dataSource(dataSource)
|
||||
, m_graph(dataSource, std::move(cmf))
|
||||
, m_infoGetter(dataSource)
|
||||
{}
|
||||
|
||||
bool DecodeSegment(LinearSegment const & segment, DecodedPath & path, v2::Stats & stat)
|
||||
{
|
||||
double constexpr kPathLengthTolerance = 0.30;
|
||||
uint32_t constexpr kMaxJunctionCandidates = 10;
|
||||
uint32_t constexpr kMaxProjectionCandidates = 5;
|
||||
|
||||
m_graph.ResetFakes();
|
||||
|
||||
path.m_segmentId.Set(segment.m_segmentId);
|
||||
|
||||
auto const & points = segment.GetLRPs();
|
||||
CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points"));
|
||||
vector<vector<Graph::EdgeVector>> lineCandidates;
|
||||
lineCandidates.reserve(points.size());
|
||||
LOG(LDEBUG, ("Decoding segment:", segment.m_segmentId, "with", points.size(), "points"));
|
||||
|
||||
CandidatePointsGetter pointsGetter(kMaxJunctionCandidates, kMaxProjectionCandidates, m_dataSource, m_graph);
|
||||
CandidatePathsGetter pathsGetter(pointsGetter, m_graph, m_infoGetter, stat);
|
||||
|
||||
if (!pathsGetter.GetLineCandidatesForPoints(points, lineCandidates))
|
||||
return false;
|
||||
|
||||
vector<Graph::EdgeVector> resultPath;
|
||||
PathsConnector connector(kPathLengthTolerance, m_graph, m_infoGetter, stat);
|
||||
if (!connector.ConnectCandidates(points, lineCandidates, resultPath))
|
||||
return false;
|
||||
|
||||
Graph::EdgeVector route;
|
||||
for (auto const & part : resultPath)
|
||||
route.insert(end(route), begin(part), end(part));
|
||||
|
||||
double requiredRouteDistanceM = 0.0;
|
||||
// Sum app all distances between points. Last point's m_distanceToNextPoint
|
||||
// should be equal to zero, but let's skip it just in case.
|
||||
CHECK(!points.empty(), ());
|
||||
for (auto it = begin(points); it != prev(end(points)); ++it)
|
||||
requiredRouteDistanceM += it->m_distanceToNextPoint;
|
||||
|
||||
double actualRouteDistanceM = 0.0;
|
||||
for (auto const & e : route)
|
||||
actualRouteDistanceM += EdgeLength(e);
|
||||
|
||||
auto const scale = actualRouteDistanceM / requiredRouteDistanceM;
|
||||
LOG(LDEBUG, ("actualRouteDistance:", actualRouteDistanceM, "requiredRouteDistance:", requiredRouteDistanceM,
|
||||
"scale:", scale));
|
||||
|
||||
auto const positiveOffsetM = segment.m_locationReference.m_positiveOffsetMeters * scale;
|
||||
auto const negativeOffsetM = segment.m_locationReference.m_negativeOffsetMeters * scale;
|
||||
|
||||
if (positiveOffsetM + negativeOffsetM >= requiredRouteDistanceM)
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Wrong offsets for segment:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
ExpandFakes(m_dataSource, m_graph, route);
|
||||
ASSERT(none_of(begin(route), end(route), mem_fn(&Graph::Edge::IsFake)), (segment.m_segmentId));
|
||||
CopyWithoutOffsets(begin(route), end(route), back_inserter(path.m_path), positiveOffsetM, negativeOffsetM,
|
||||
false /* keep ends */);
|
||||
|
||||
if (path.m_path.empty())
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Path is empty after offsets cutting. segmentId:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
Graph m_graph;
|
||||
RoadInfoGetter m_infoGetter;
|
||||
};
|
||||
|
||||
// The idea behind the third version of matching algorithm is to collect a lot of candidates (paths)
|
||||
// which correspond an openlr edges with some score. And on the final stage to decide which
|
||||
// candidate is better.
|
||||
class SegmentsDecoderV3
|
||||
{
|
||||
public:
|
||||
SegmentsDecoderV3(DataSource & dataSource, unique_ptr<CarModelFactory> carModelFactory)
|
||||
: m_dataSource(dataSource)
|
||||
, m_graph(dataSource, std::move(carModelFactory))
|
||||
, m_infoGetter(dataSource)
|
||||
{}
|
||||
|
||||
bool DecodeSegment(LinearSegment const & segment, DecodedPath & path, v2::Stats & stat)
|
||||
{
|
||||
LOG(LINFO, ("DecodeSegment(...) seg id:", segment.m_segmentId, ", point num:", segment.GetLRPs().size()));
|
||||
|
||||
uint32_t constexpr kMaxJunctionCandidates = 10;
|
||||
uint32_t constexpr kMaxProjectionCandidates = 5;
|
||||
|
||||
path.m_segmentId.Set(segment.m_segmentId);
|
||||
|
||||
auto const & points = segment.GetLRPs();
|
||||
CHECK_GREATER(points.size(), 1, ("A segment cannot consist of less than two points"));
|
||||
vector<ScorePathVec> lineCandidates;
|
||||
lineCandidates.reserve(points.size());
|
||||
LOG(LINFO, ("Decoding segment:", segment.m_segmentId, "with", points.size(), "points"));
|
||||
|
||||
ScoreCandidatePointsGetter pointsGetter(kMaxJunctionCandidates, kMaxProjectionCandidates, m_dataSource, m_graph);
|
||||
ScoreCandidatePathsGetter pathsGetter(pointsGetter, m_graph, m_infoGetter, stat);
|
||||
|
||||
if (!pathsGetter.GetLineCandidatesForPoints(points, segment.m_source, lineCandidates))
|
||||
return false;
|
||||
|
||||
vector<Graph::EdgeVector> resultPath;
|
||||
ScorePathsConnector connector(m_graph, m_infoGetter, stat);
|
||||
if (!connector.FindBestPath(points, lineCandidates, segment.m_source, resultPath))
|
||||
{
|
||||
LOG(LINFO, ("Connections not found:", segment.m_segmentId));
|
||||
auto const mercatorPoints = segment.GetMercatorPoints();
|
||||
for (auto const & mercatorPoint : mercatorPoints)
|
||||
LOG(LINFO, (mercator::ToLatLon(mercatorPoint)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Graph::EdgeVector route;
|
||||
for (auto const & part : resultPath)
|
||||
route.insert(route.end(), part.begin(), part.end());
|
||||
|
||||
double requiredRouteDistanceM = 0.0;
|
||||
// Sum up all distances between points. Last point's m_distanceToNextPoint
|
||||
// should be equal to zero, but let's skip it just in case.
|
||||
CHECK(!points.empty(), ());
|
||||
for (auto it = points.begin(); it != prev(points.end()); ++it)
|
||||
requiredRouteDistanceM += it->m_distanceToNextPoint;
|
||||
|
||||
double actualRouteDistanceM = 0.0;
|
||||
for (auto const & e : route)
|
||||
actualRouteDistanceM += EdgeLength(e);
|
||||
|
||||
auto const scale = actualRouteDistanceM / requiredRouteDistanceM;
|
||||
LOG(LINFO, ("actualRouteDistance:", actualRouteDistanceM, "requiredRouteDistance:", requiredRouteDistanceM,
|
||||
"scale:", scale));
|
||||
|
||||
if (segment.m_locationReference.m_positiveOffsetMeters + segment.m_locationReference.m_negativeOffsetMeters >=
|
||||
requiredRouteDistanceM)
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Wrong offsets for segment:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const positiveOffsetM = segment.m_locationReference.m_positiveOffsetMeters * scale;
|
||||
auto const negativeOffsetM = segment.m_locationReference.m_negativeOffsetMeters * scale;
|
||||
|
||||
CHECK(none_of(route.begin(), route.end(), mem_fn(&Graph::Edge::IsFake)), (segment.m_segmentId));
|
||||
CopyWithoutOffsets(route.begin(), route.end(), back_inserter(path.m_path), positiveOffsetM, negativeOffsetM,
|
||||
true /* keep ends */);
|
||||
|
||||
if (path.m_path.empty())
|
||||
{
|
||||
++stat.m_wrongOffsets;
|
||||
LOG(LINFO, ("Path is empty after offsets cutting. segmentId:", segment.m_segmentId));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
Graph m_graph;
|
||||
RoadInfoGetter m_infoGetter;
|
||||
};
|
||||
|
||||
size_t constexpr GetOptimalBatchSize()
|
||||
{
|
||||
// This code computes the most optimal (in the sense of cache lines
|
||||
// occupancy) batch size.
|
||||
size_t constexpr a = math::LCM(sizeof(LinearSegment), kCacheLineSize) / sizeof(LinearSegment);
|
||||
size_t constexpr b = math::LCM(sizeof(IRoadGraph::EdgeVector), kCacheLineSize) / sizeof(IRoadGraph::EdgeVector);
|
||||
return math::LCM(a, b);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// OpenLRDecoder::SegmentsFilter -------------------------------------------------------------
|
||||
OpenLRDecoder::SegmentsFilter::SegmentsFilter(string const & idsPath, bool const multipointsOnly)
|
||||
: m_idsSet(false)
|
||||
, m_multipointsOnly(multipointsOnly)
|
||||
{
|
||||
if (idsPath.empty())
|
||||
return;
|
||||
|
||||
ifstream ifs(idsPath);
|
||||
CHECK(ifs, ("Can't find", idsPath));
|
||||
m_ids.insert(istream_iterator<uint32_t>(ifs), istream_iterator<uint32_t>());
|
||||
|
||||
CHECK(!ifs, ("Garbage in", idsPath));
|
||||
m_idsSet = true;
|
||||
}
|
||||
|
||||
bool OpenLRDecoder::SegmentsFilter::Matches(LinearSegment const & segment) const
|
||||
{
|
||||
if (m_multipointsOnly && segment.m_locationReference.m_points.size() == 2)
|
||||
return false;
|
||||
if (m_idsSet && m_ids.count(segment.m_segmentId) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// OpenLRDecoder -----------------------------------------------------------------------------
|
||||
OpenLRDecoder::OpenLRDecoder(vector<FrozenDataSource> & dataSources,
|
||||
CountryParentNameGetter const & countryParentNameGetter)
|
||||
: m_dataSources(dataSources)
|
||||
, m_countryParentNameGetter(countryParentNameGetter)
|
||||
{}
|
||||
|
||||
void OpenLRDecoder::DecodeV2(vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
vector<DecodedPath> & paths)
|
||||
{
|
||||
Decode<SegmentsDecoderV2, v2::Stats>(segments, numThreads, paths);
|
||||
}
|
||||
|
||||
void OpenLRDecoder::DecodeV3(vector<LinearSegment> const & segments, uint32_t numThreads, vector<DecodedPath> & paths)
|
||||
{
|
||||
Decode<SegmentsDecoderV3, v2::Stats>(segments, numThreads, paths);
|
||||
}
|
||||
|
||||
template <typename Decoder, typename Stats>
|
||||
void OpenLRDecoder::Decode(vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
vector<DecodedPath> & paths)
|
||||
{
|
||||
auto const worker = [&](size_t threadNum, DataSource & dataSource, Stats & stat)
|
||||
{
|
||||
size_t constexpr kBatchSize = GetOptimalBatchSize();
|
||||
size_t constexpr kProgressFrequency = 100;
|
||||
|
||||
size_t const numSegments = segments.size();
|
||||
|
||||
Decoder decoder(dataSource, make_unique<CarModelFactory>(m_countryParentNameGetter));
|
||||
base::Timer timer;
|
||||
for (size_t i = threadNum * kBatchSize; i < numSegments; i += numThreads * kBatchSize)
|
||||
{
|
||||
for (size_t j = i; j < numSegments && j < i + kBatchSize; ++j)
|
||||
{
|
||||
if (!decoder.DecodeSegment(segments[j], paths[j], stat))
|
||||
++stat.m_routesFailed;
|
||||
++stat.m_routesHandled;
|
||||
|
||||
if (stat.m_routesHandled % kProgressFrequency == 0 || i == segments.size() - 1)
|
||||
{
|
||||
LOG(LINFO, ("Thread", threadNum, "processed", stat.m_routesHandled, "failed:", stat.m_routesFailed));
|
||||
timer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
base::Timer timer;
|
||||
vector<Stats> stats(numThreads);
|
||||
vector<thread> workers;
|
||||
for (size_t i = 1; i < numThreads; ++i)
|
||||
workers.emplace_back(worker, i, ref(m_dataSources[i]), ref(stats[i]));
|
||||
|
||||
worker(0 /* threadNum */, m_dataSources[0], stats[0]);
|
||||
for (auto & worker : workers)
|
||||
worker.join();
|
||||
|
||||
Stats allStats;
|
||||
for (auto const & s : stats)
|
||||
allStats.Add(s);
|
||||
|
||||
allStats.Report();
|
||||
LOG(LINFO, ("Matching tool:", timer.ElapsedSeconds(), "seconds."));
|
||||
}
|
||||
} // namespace openlr
|
||||
58
tools/openlr/openlr_decoder.hpp
Normal file
58
tools/openlr/openlr_decoder.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct LinearSegment;
|
||||
struct DecodedPath;
|
||||
|
||||
DECLARE_EXCEPTION(DecoderError, RootException);
|
||||
|
||||
class Graph;
|
||||
class RoadInfoGetter;
|
||||
|
||||
class OpenLRDecoder
|
||||
{
|
||||
public:
|
||||
using CountryParentNameGetter = std::function<std::string(std::string const &)>;
|
||||
|
||||
class SegmentsFilter
|
||||
{
|
||||
public:
|
||||
SegmentsFilter(std::string const & idsPath, bool const multipointsOnly);
|
||||
|
||||
bool Matches(LinearSegment const & segment) const;
|
||||
|
||||
private:
|
||||
std::unordered_set<uint32_t> m_ids;
|
||||
bool m_idsSet;
|
||||
bool const m_multipointsOnly;
|
||||
};
|
||||
|
||||
OpenLRDecoder(std::vector<FrozenDataSource> & dataSources, CountryParentNameGetter const & countryParentNameGetter);
|
||||
|
||||
// Maps partner segments to mwm paths. |segments| should be sorted by partner id.
|
||||
void DecodeV2(std::vector<LinearSegment> const & segments, uint32_t const numThreads,
|
||||
std::vector<DecodedPath> & paths);
|
||||
|
||||
void DecodeV3(std::vector<LinearSegment> const & segments, uint32_t numThreads, std::vector<DecodedPath> & paths);
|
||||
|
||||
private:
|
||||
template <typename Decoder, typename Stats>
|
||||
void Decode(std::vector<LinearSegment> const & segments, uint32_t const numThreads, std::vector<DecodedPath> & paths);
|
||||
|
||||
std::vector<FrozenDataSource> & m_dataSources;
|
||||
CountryParentNameGetter m_countryParentNameGetter;
|
||||
};
|
||||
} // namespace openlr
|
||||
5
tools/openlr/openlr_match_quality/CMakeLists.txt
Normal file
5
tools/openlr/openlr_match_quality/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
project(openlr_match_quality)
|
||||
|
||||
if (NOT SKIP_QT_GUI)
|
||||
add_subdirectory(openlr_assessment_tool)
|
||||
endif()
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
project(openlr_assessment_tool)
|
||||
|
||||
set(SRC
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
mainwindow.hpp
|
||||
map_widget.cpp
|
||||
map_widget.hpp
|
||||
points_controller_delegate_base.hpp
|
||||
segment_correspondence.cpp
|
||||
segment_correspondence.hpp
|
||||
traffic_drawer_delegate_base.hpp
|
||||
traffic_mode.cpp
|
||||
traffic_mode.hpp
|
||||
traffic_panel.cpp
|
||||
traffic_panel.hpp
|
||||
trafficmodeinitdlg.cpp
|
||||
trafficmodeinitdlg.h
|
||||
trafficmodeinitdlg.ui
|
||||
)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES AUTOUIC ON AUTOMOC ON)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
openlr
|
||||
qt_common
|
||||
map
|
||||
gflags::gflags
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<plist>
|
||||
<dict>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "mainwindow.hpp"
|
||||
|
||||
#include "qt/qt_common/helpers.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace
|
||||
{
|
||||
DEFINE_string(resources_path, "", "Path to resources directory");
|
||||
DEFINE_string(data_path, "", "Path to data directory");
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
gflags::SetUsageMessage("Visualize and check matched routes.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
Platform & platform = GetPlatform();
|
||||
if (!FLAGS_resources_path.empty())
|
||||
platform.SetResourceDir(FLAGS_resources_path);
|
||||
if (!FLAGS_data_path.empty())
|
||||
platform.SetWritableDirForTests(FLAGS_data_path);
|
||||
|
||||
Q_INIT_RESOURCE(resources_common);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
qt::common::SetDefaultSurfaceFormat(app.platformName());
|
||||
|
||||
FrameworkParams params;
|
||||
|
||||
Framework framework(params);
|
||||
openlr::MainWindow mainWindow(framework);
|
||||
|
||||
mainWindow.showMaximized();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp"
|
||||
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/points_controller_delegate_base.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_drawer_delegate_base.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_panel.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/trafficmodeinitdlg.h"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include "drape_frontend/drape_api.hpp"
|
||||
|
||||
#include "routing/data_source.hpp"
|
||||
#include "routing/features_road_graph.hpp"
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "routing_common/car_model.hpp"
|
||||
|
||||
#include "storage/country_parent_getter.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDockWidget>
|
||||
#include <QFileDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeySequence>
|
||||
#include <QLayout>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class TrafficDrawerDelegate : public TrafficDrawerDelegateBase
|
||||
{
|
||||
static constexpr char const * kEncodedLineId = "encodedPath";
|
||||
static constexpr char const * kDecodedLineId = "decodedPath";
|
||||
static constexpr char const * kGoldenLineId = "goldenPath";
|
||||
|
||||
public:
|
||||
explicit TrafficDrawerDelegate(Framework & framework)
|
||||
: m_framework(framework)
|
||||
, m_drapeApi(m_framework.GetDrapeApi())
|
||||
, m_bm(framework.GetBookmarkManager())
|
||||
{}
|
||||
|
||||
void SetViewportCenter(m2::PointD const & center) override { m_framework.SetViewportCenter(center); }
|
||||
|
||||
void DrawDecodedSegments(std::vector<m2::PointD> const & points) override
|
||||
{
|
||||
CHECK(!points.empty(), ("Points must not be empty."));
|
||||
|
||||
LOG(LINFO, ("Decoded segment", points));
|
||||
m_drapeApi.AddLine(
|
||||
kDecodedLineId,
|
||||
df::DrapeApiLineData(points, dp::Color(0, 0, 255, 255)).Width(3.0f).ShowPoints(true /* markPoints */));
|
||||
}
|
||||
|
||||
void DrawEncodedSegment(std::vector<m2::PointD> const & points) override
|
||||
{
|
||||
LOG(LINFO, ("Encoded segment", points));
|
||||
m_drapeApi.AddLine(
|
||||
kEncodedLineId,
|
||||
df::DrapeApiLineData(points, dp::Color(255, 0, 0, 255)).Width(3.0f).ShowPoints(true /* markPoints */));
|
||||
}
|
||||
|
||||
void DrawGoldenPath(std::vector<m2::PointD> const & points) override
|
||||
{
|
||||
m_drapeApi.AddLine(
|
||||
kGoldenLineId,
|
||||
df::DrapeApiLineData(points, dp::Color(255, 127, 36, 255)).Width(4.0f).ShowPoints(true /* markPoints */));
|
||||
}
|
||||
|
||||
void ClearGoldenPath() override { m_drapeApi.RemoveLine(kGoldenLineId); }
|
||||
|
||||
void ClearAllPaths() override { m_drapeApi.Clear(); }
|
||||
|
||||
void VisualizePoints(std::vector<m2::PointD> const & points) override
|
||||
{
|
||||
auto editSession = m_bm.GetEditSession();
|
||||
editSession.SetIsVisible(UserMark::Type::DEBUG_MARK, true);
|
||||
for (auto const & p : points)
|
||||
editSession.CreateUserMark<DebugMarkPoint>(p);
|
||||
}
|
||||
|
||||
void ClearAllVisualizedPoints() override { m_bm.GetEditSession().ClearGroup(UserMark::Type::DEBUG_MARK); }
|
||||
|
||||
private:
|
||||
Framework & m_framework;
|
||||
df::DrapeApi & m_drapeApi;
|
||||
BookmarkManager & m_bm;
|
||||
};
|
||||
|
||||
bool PointsMatch(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto constexpr kToleranceDistanceM = 1.0;
|
||||
return mercator::DistanceOnEarth(a, b) < kToleranceDistanceM;
|
||||
}
|
||||
|
||||
class PointsControllerDelegate : public PointsControllerDelegateBase
|
||||
{
|
||||
public:
|
||||
explicit PointsControllerDelegate(Framework & framework)
|
||||
: m_framework(framework)
|
||||
, m_dataSource(const_cast<DataSource &>(GetDataSource()), nullptr /* numMwmIDs */)
|
||||
, m_roadGraph(m_dataSource, routing::IRoadGraph::Mode::ObeyOnewayTag,
|
||||
std::make_unique<routing::CarModelFactory>(storage::CountryParentGetter{}))
|
||||
{}
|
||||
|
||||
std::vector<m2::PointD> GetAllJunctionPointsInViewport() const override
|
||||
{
|
||||
std::vector<m2::PointD> points;
|
||||
auto const & rect = m_framework.GetCurrentViewport();
|
||||
auto const pushPoint = [&points, &rect](m2::PointD const & point)
|
||||
{
|
||||
if (!rect.IsPointInside(point))
|
||||
return;
|
||||
for (auto const & p : points)
|
||||
if (PointsMatch(point, p))
|
||||
return;
|
||||
points.push_back(point);
|
||||
};
|
||||
|
||||
auto const pushFeaturePoints = [&pushPoint](FeatureType & ft)
|
||||
{
|
||||
if (ft.GetGeomType() != feature::GeomType::Line)
|
||||
return;
|
||||
|
||||
/// @todo Transported (railway=rail) are also present here :)
|
||||
auto const roadClass = ftypes::GetHighwayClass(feature::TypesHolder(ft));
|
||||
if (roadClass == ftypes::HighwayClass::Undefined || roadClass == ftypes::HighwayClass::Pedestrian)
|
||||
return;
|
||||
ft.ForEachPoint(pushPoint, scales::GetUpperScale());
|
||||
};
|
||||
|
||||
GetDataSource().ForEachInRect(pushFeaturePoints, rect, scales::GetUpperScale());
|
||||
return points;
|
||||
}
|
||||
|
||||
std::pair<std::vector<FeaturePoint>, m2::PointD> GetCandidatePoints(m2::PointD const & p) const override
|
||||
{
|
||||
auto constexpr kInvalidIndex = std::numeric_limits<size_t>::max();
|
||||
|
||||
std::vector<FeaturePoint> points;
|
||||
m2::PointD pointOnFt;
|
||||
indexer::ForEachFeatureAtPoint(GetDataSource(), [&points, &p, &pointOnFt](FeatureType & ft)
|
||||
{
|
||||
if (ft.GetGeomType() != feature::GeomType::Line)
|
||||
return;
|
||||
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
|
||||
auto minDistance = std::numeric_limits<double>::max();
|
||||
auto bestPointIndex = kInvalidIndex;
|
||||
for (size_t i = 0; i < ft.GetPointsCount(); ++i)
|
||||
{
|
||||
auto const & fp = ft.GetPoint(i);
|
||||
auto const distance = mercator::DistanceOnEarth(fp, p);
|
||||
if (PointsMatch(fp, p) && distance < minDistance)
|
||||
{
|
||||
bestPointIndex = i;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestPointIndex != kInvalidIndex)
|
||||
{
|
||||
points.emplace_back(ft.GetID(), bestPointIndex);
|
||||
pointOnFt = ft.GetPoint(bestPointIndex);
|
||||
}
|
||||
}, p);
|
||||
|
||||
return std::make_pair(points, pointOnFt);
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetReachablePoints(m2::PointD const & p) const override
|
||||
{
|
||||
routing::FeaturesRoadGraph::EdgeListT edges;
|
||||
m_roadGraph.GetOutgoingEdges(geometry::PointWithAltitude(p, geometry::kDefaultAltitudeMeters), edges);
|
||||
|
||||
std::vector<m2::PointD> points;
|
||||
for (auto const & e : edges)
|
||||
points.push_back(e.GetEndJunction().GetPoint());
|
||||
return points;
|
||||
}
|
||||
|
||||
ClickType CheckClick(m2::PointD const & clickPoint, m2::PointD const & lastClickedPoint,
|
||||
std::vector<m2::PointD> const & reachablePoints) const override
|
||||
{
|
||||
// == Comparison is safe here since |clickPoint| is adjusted by GetFeaturesPointsByPoint
|
||||
// so to be equal the closest feature's one.
|
||||
if (clickPoint == lastClickedPoint)
|
||||
return ClickType::Remove;
|
||||
for (auto const & p : reachablePoints)
|
||||
if (PointsMatch(clickPoint, p))
|
||||
return ClickType::Add;
|
||||
return ClickType::Miss;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSource const & GetDataSource() const { return m_framework.GetDataSource(); }
|
||||
|
||||
Framework & m_framework;
|
||||
routing::MwmDataSource m_dataSource;
|
||||
routing::FeaturesRoadGraph m_roadGraph;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
MainWindow::MainWindow(Framework & framework) : m_framework(framework)
|
||||
{
|
||||
m_mapWidget = new MapWidget(m_framework, this /* parent */);
|
||||
|
||||
m_layout = new QHBoxLayout();
|
||||
m_layout->addWidget(m_mapWidget);
|
||||
|
||||
auto * window = new QWidget();
|
||||
window->setLayout(m_layout);
|
||||
window->setGraphicsEffect(nullptr);
|
||||
|
||||
setCentralWidget(window);
|
||||
|
||||
setWindowTitle(tr("Organic Maps"));
|
||||
setWindowIcon(QIcon(":/ui/logo.png"));
|
||||
|
||||
QMenu * fileMenu = new QMenu("File", this);
|
||||
menuBar()->addMenu(fileMenu);
|
||||
|
||||
fileMenu->addAction("Open sample", QKeySequence("Ctrl+O"), this, &MainWindow::OnOpenTrafficSample);
|
||||
|
||||
m_closeTrafficSampleAction =
|
||||
fileMenu->addAction("Close sample", QKeySequence("Ctrl+W"), this, &MainWindow::OnCloseTrafficSample);
|
||||
m_saveTrafficSampleAction =
|
||||
fileMenu->addAction("Save sample", QKeySequence("Ctrl+S"), this, &MainWindow::OnSaveTrafficSample);
|
||||
|
||||
fileMenu->addSeparator();
|
||||
|
||||
m_goldifyMatchedPathAction =
|
||||
fileMenu->addAction("Goldify", QKeySequence("Ctrl+G"), [this] { m_trafficMode->GoldifyMatchedPath(); });
|
||||
m_startEditingAction = fileMenu->addAction("Edit", QKeySequence("Ctrl+E"), [this]
|
||||
{
|
||||
m_trafficMode->StartBuildingPath();
|
||||
m_mapWidget->SetMode(MapWidget::Mode::TrafficMarkup);
|
||||
m_commitPathAction->setEnabled(true /* enabled */);
|
||||
m_cancelPathAction->setEnabled(true /* enabled */);
|
||||
});
|
||||
m_commitPathAction = fileMenu->addAction("Accept path", QKeySequence("Ctrl+A"), [this]
|
||||
{
|
||||
m_trafficMode->CommitPath();
|
||||
m_mapWidget->SetMode(MapWidget::Mode::Normal);
|
||||
});
|
||||
m_cancelPathAction = fileMenu->addAction("Revert path", QKeySequence("Ctrl+R"), [this]
|
||||
{
|
||||
m_trafficMode->RollBackPath();
|
||||
m_mapWidget->SetMode(MapWidget::Mode::Normal);
|
||||
});
|
||||
m_ignorePathAction = fileMenu->addAction("Ignore path", QKeySequence("Ctrl+I"), [this]
|
||||
{
|
||||
m_trafficMode->IgnorePath();
|
||||
m_mapWidget->SetMode(MapWidget::Mode::Normal);
|
||||
});
|
||||
|
||||
m_goldifyMatchedPathAction->setEnabled(false /* enabled */);
|
||||
m_closeTrafficSampleAction->setEnabled(false /* enabled */);
|
||||
m_saveTrafficSampleAction->setEnabled(false /* enabled */);
|
||||
m_startEditingAction->setEnabled(false /* enabled */);
|
||||
m_commitPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
m_ignorePathAction->setEnabled(false /* enabled */);
|
||||
}
|
||||
|
||||
void MainWindow::CreateTrafficPanel(std::string const & dataFilePath)
|
||||
{
|
||||
m_trafficMode =
|
||||
new TrafficMode(dataFilePath, m_framework.GetDataSource(), std::make_unique<TrafficDrawerDelegate>(m_framework),
|
||||
std::make_unique<PointsControllerDelegate>(m_framework));
|
||||
|
||||
connect(m_mapWidget, &MapWidget::TrafficMarkupClick, m_trafficMode, &TrafficMode::OnClick);
|
||||
connect(m_trafficMode, &TrafficMode::EditingStopped, this, &MainWindow::OnPathEditingStop);
|
||||
connect(m_trafficMode, &TrafficMode::SegmentSelected,
|
||||
[](int segmentId) { QApplication::clipboard()->setText(QString::number(segmentId)); });
|
||||
|
||||
m_docWidget = new QDockWidget(tr("Routes"), this);
|
||||
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget);
|
||||
|
||||
m_docWidget->setWidget(new TrafficPanel(m_trafficMode, m_docWidget));
|
||||
|
||||
m_docWidget->adjustSize();
|
||||
m_docWidget->setMinimumWidth(400);
|
||||
m_docWidget->show();
|
||||
}
|
||||
|
||||
void MainWindow::DestroyTrafficPanel()
|
||||
{
|
||||
removeDockWidget(m_docWidget);
|
||||
delete m_docWidget;
|
||||
m_docWidget = nullptr;
|
||||
|
||||
delete m_trafficMode;
|
||||
m_trafficMode = nullptr;
|
||||
|
||||
m_mapWidget->SetMode(MapWidget::Mode::Normal);
|
||||
}
|
||||
|
||||
void MainWindow::OnOpenTrafficSample()
|
||||
{
|
||||
TrafficModeInitDlg dlg;
|
||||
dlg.exec();
|
||||
if (dlg.result() != QDialog::DialogCode::Accepted)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
CreateTrafficPanel(dlg.GetDataFilePath());
|
||||
}
|
||||
catch (TrafficModeError const & e)
|
||||
{
|
||||
QMessageBox::critical(this, "Data loading error", QString("Can't load data file."));
|
||||
LOG(LERROR, (e.Msg()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_goldifyMatchedPathAction->setEnabled(true /* enabled */);
|
||||
m_closeTrafficSampleAction->setEnabled(true /* enabled */);
|
||||
m_saveTrafficSampleAction->setEnabled(true /* enabled */);
|
||||
m_startEditingAction->setEnabled(true /* enabled */);
|
||||
m_ignorePathAction->setEnabled(true /* enabled */);
|
||||
}
|
||||
|
||||
void MainWindow::OnCloseTrafficSample()
|
||||
{
|
||||
// TODO(mgsergio):
|
||||
// If not saved, ask a user if he/she wants to save.
|
||||
// OnSaveTrafficSample()
|
||||
|
||||
m_goldifyMatchedPathAction->setEnabled(false /* enabled */);
|
||||
m_saveTrafficSampleAction->setEnabled(false /* enabled */);
|
||||
m_closeTrafficSampleAction->setEnabled(false /* enabled */);
|
||||
m_startEditingAction->setEnabled(false /* enabled */);
|
||||
m_commitPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
m_ignorePathAction->setEnabled(false /* enabled */);
|
||||
|
||||
DestroyTrafficPanel();
|
||||
}
|
||||
|
||||
void MainWindow::OnSaveTrafficSample()
|
||||
{
|
||||
// TODO(mgsergio): Add default filename.
|
||||
auto const & fileName = QFileDialog::getSaveFileName(this, "Save sample");
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
if (!m_trafficMode->SaveSampleAs(fileName.toStdString()))
|
||||
QMessageBox::critical(this, "Saving error", QString("Can't save file: ") + strerror(errno));
|
||||
}
|
||||
|
||||
void MainWindow::OnPathEditingStop()
|
||||
{
|
||||
m_commitPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
m_cancelPathAction->setEnabled(false /* enabled */);
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class Framework;
|
||||
class QHBoxLayout;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class MapWidget;
|
||||
class TrafficMode;
|
||||
class WebView;
|
||||
} // namespace openlr
|
||||
|
||||
namespace df
|
||||
{
|
||||
class DrapeApi;
|
||||
}
|
||||
|
||||
class QDockWidget;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(Framework & framework);
|
||||
|
||||
private:
|
||||
void CreateTrafficPanel(std::string const & dataFilePath);
|
||||
void DestroyTrafficPanel();
|
||||
|
||||
void OnOpenTrafficSample();
|
||||
void OnCloseTrafficSample();
|
||||
void OnSaveTrafficSample();
|
||||
void OnPathEditingStop();
|
||||
|
||||
Framework & m_framework;
|
||||
|
||||
openlr::TrafficMode * m_trafficMode = nullptr;
|
||||
QDockWidget * m_docWidget = nullptr;
|
||||
|
||||
QAction * m_goldifyMatchedPathAction = nullptr;
|
||||
QAction * m_saveTrafficSampleAction = nullptr;
|
||||
QAction * m_closeTrafficSampleAction = nullptr;
|
||||
QAction * m_startEditingAction = nullptr;
|
||||
QAction * m_commitPathAction = nullptr;
|
||||
QAction * m_cancelPathAction = nullptr;
|
||||
QAction * m_ignorePathAction = nullptr;
|
||||
|
||||
openlr::MapWidget * m_mapWidget = nullptr;
|
||||
QHBoxLayout * m_layout = nullptr;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp"
|
||||
|
||||
#include "qt/qt_common/helpers.hpp"
|
||||
|
||||
#include "map/framework.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
MapWidget::MapWidget(Framework & framework, QWidget * parent) : Base(framework, false /* screenshotMode */, parent) {}
|
||||
|
||||
void MapWidget::mousePressEvent(QMouseEvent * e)
|
||||
{
|
||||
Base::mousePressEvent(e);
|
||||
|
||||
if (qt::common::IsRightButton(e))
|
||||
ShowInfoPopup(e, GetDevicePoint(e));
|
||||
|
||||
if (m_mode == Mode::TrafficMarkup)
|
||||
{
|
||||
auto pt = GetDevicePoint(e);
|
||||
emit TrafficMarkupClick(m_framework.PtoG(pt), e->button());
|
||||
}
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "qt/qt_common/map_widget.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class PointsController;
|
||||
} // namespace
|
||||
|
||||
class Framework;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class MapWidget : public qt::common::MapWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Base = qt::common::MapWidget;
|
||||
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Normal,
|
||||
TrafficMarkup
|
||||
};
|
||||
|
||||
MapWidget(Framework & framework, QWidget * parent);
|
||||
~MapWidget() override = default;
|
||||
|
||||
void SetMode(Mode const mode) { m_mode = mode; }
|
||||
|
||||
QSize sizeHint() const override { return QSize(800, 600); }
|
||||
|
||||
signals:
|
||||
void TrafficMarkupClick(m2::PointD const & p, Qt::MouseButton const b);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent * e) override;
|
||||
|
||||
private:
|
||||
Mode m_mode = Mode::Normal;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using FeaturePoint = std::pair<FeatureID, size_t>;
|
||||
|
||||
/// This class is responsible for collecting junction points and
|
||||
/// checking user's clicks.
|
||||
class PointsControllerDelegateBase
|
||||
{
|
||||
public:
|
||||
enum class ClickType
|
||||
{
|
||||
Miss,
|
||||
Add,
|
||||
Remove
|
||||
};
|
||||
|
||||
virtual ~PointsControllerDelegateBase() = default;
|
||||
|
||||
virtual std::vector<m2::PointD> GetAllJunctionPointsInViewport() const = 0;
|
||||
/// Returns all junction points at a given location in the form of feature id and
|
||||
/// point index in the feature.
|
||||
virtual std::pair<std::vector<FeaturePoint>, m2::PointD> GetCandidatePoints(m2::PointD const & p) const = 0;
|
||||
// Returns all points that are one step reachable from |p|.
|
||||
virtual std::vector<m2::PointD> GetReachablePoints(m2::PointD const & p) const = 0;
|
||||
|
||||
virtual ClickType CheckClick(m2::PointD const & clickPoint, m2::PointD const & lastClickedPoint,
|
||||
std::vector<m2::PointD> const & reachablePoints) const = 0;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/segment_correspondence.hpp"
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
SegmentCorrespondence::SegmentCorrespondence(SegmentCorrespondence const & sc)
|
||||
{
|
||||
m_partnerSegment = sc.m_partnerSegment;
|
||||
|
||||
m_positiveOffset = sc.m_positiveOffset;
|
||||
m_negativeOffset = sc.m_negativeOffset;
|
||||
|
||||
m_matchedPath = sc.m_matchedPath;
|
||||
m_fakePath = sc.m_fakePath;
|
||||
m_goldenPath = sc.m_goldenPath;
|
||||
|
||||
m_partnerXMLDoc.reset(sc.m_partnerXMLDoc);
|
||||
m_partnerXMLSegment = m_partnerXMLDoc.child("reportSegments");
|
||||
|
||||
m_status = sc.m_status;
|
||||
}
|
||||
|
||||
SegmentCorrespondence::SegmentCorrespondence(openlr::LinearSegment const & segment, uint32_t positiveOffset,
|
||||
uint32_t negativeOffset, openlr::Path const & matchedPath,
|
||||
openlr::Path const & fakePath, openlr::Path const & goldenPath,
|
||||
pugi::xml_node const & partnerSegmentXML)
|
||||
: m_partnerSegment(segment)
|
||||
, m_positiveOffset(positiveOffset)
|
||||
, m_negativeOffset(negativeOffset)
|
||||
, m_matchedPath(matchedPath)
|
||||
, m_fakePath(fakePath)
|
||||
{
|
||||
SetGoldenPath(goldenPath);
|
||||
|
||||
m_partnerXMLDoc.append_copy(partnerSegmentXML);
|
||||
m_partnerXMLSegment = m_partnerXMLDoc.child("reportSegments");
|
||||
CHECK(m_partnerXMLSegment, ("Node should contain <reportSegments> part"));
|
||||
}
|
||||
|
||||
void SegmentCorrespondence::SetGoldenPath(openlr::Path const & p)
|
||||
{
|
||||
m_goldenPath = p;
|
||||
m_status = p.empty() ? Status::Untouched : Status::Assessed;
|
||||
}
|
||||
|
||||
void SegmentCorrespondence::Ignore()
|
||||
{
|
||||
m_status = Status::Ignored;
|
||||
m_goldenPath.clear();
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class SegmentCorrespondence
|
||||
{
|
||||
public:
|
||||
enum class Status
|
||||
{
|
||||
Untouched,
|
||||
Assessed,
|
||||
Ignored
|
||||
};
|
||||
|
||||
SegmentCorrespondence(SegmentCorrespondence const & sc);
|
||||
SegmentCorrespondence(openlr::LinearSegment const & segment, uint32_t positiveOffset, uint32_t negativeOffset,
|
||||
openlr::Path const & matchedPath, openlr::Path const & fakePath,
|
||||
openlr::Path const & goldenPath, pugi::xml_node const & partnerSegmentXML);
|
||||
|
||||
openlr::Path const & GetMatchedPath() const { return m_matchedPath; }
|
||||
bool HasMatchedPath() const { return !m_matchedPath.empty(); }
|
||||
|
||||
uint32_t GetPositiveOffset() const { return m_positiveOffset; }
|
||||
uint32_t GetNegativeOffset() const { return m_negativeOffset; }
|
||||
|
||||
openlr::Path const & GetFakePath() const { return m_fakePath; }
|
||||
bool HasFakePath() const { return !m_fakePath.empty(); }
|
||||
|
||||
openlr::Path const & GetGoldenPath() const { return m_goldenPath; }
|
||||
bool HasGoldenPath() const { return !m_goldenPath.empty(); }
|
||||
void SetGoldenPath(openlr::Path const & p);
|
||||
|
||||
openlr::LinearSegment const & GetPartnerSegment() const { return m_partnerSegment; }
|
||||
|
||||
uint32_t GetPartnerSegmentId() const { return m_partnerSegment.m_segmentId; }
|
||||
|
||||
pugi::xml_document const & GetPartnerXML() const { return m_partnerXMLDoc; }
|
||||
pugi::xml_node const & GetPartnerXMLSegment() const { return m_partnerXMLSegment; }
|
||||
|
||||
Status GetStatus() const { return m_status; }
|
||||
|
||||
void Ignore();
|
||||
|
||||
private:
|
||||
openlr::LinearSegment m_partnerSegment;
|
||||
|
||||
uint32_t m_positiveOffset = 0;
|
||||
uint32_t m_negativeOffset = 0;
|
||||
|
||||
openlr::Path m_matchedPath;
|
||||
openlr::Path m_fakePath;
|
||||
openlr::Path m_goldenPath;
|
||||
|
||||
// A dirty hack to save back SegmentCorrespondence.
|
||||
// TODO(mgsergio): Consider unifying xml serialization with one used in openlr_stat.
|
||||
pugi::xml_document m_partnerXMLDoc;
|
||||
// This is used by GetPartnerXMLSegment shortcut to return const ref. pugi::xml_node is
|
||||
// just a wrapper so returning by value won't guarantee constness.
|
||||
pugi::xml_node m_partnerXMLSegment;
|
||||
|
||||
Status m_status = Status::Untouched;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <geometry/point2d.hpp>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
/// This class is used to delegate segments drawing to the DrapeEngine.
|
||||
class TrafficDrawerDelegateBase
|
||||
{
|
||||
public:
|
||||
virtual ~TrafficDrawerDelegateBase() = default;
|
||||
|
||||
virtual void SetViewportCenter(m2::PointD const & center) = 0;
|
||||
|
||||
virtual void DrawDecodedSegments(std::vector<m2::PointD> const & points) = 0;
|
||||
virtual void DrawEncodedSegment(std::vector<m2::PointD> const & points) = 0;
|
||||
virtual void DrawGoldenPath(std::vector<m2::PointD> const & points) = 0;
|
||||
|
||||
virtual void ClearGoldenPath() = 0;
|
||||
virtual void ClearAllPaths() = 0;
|
||||
|
||||
virtual void VisualizePoints(std::vector<m2::PointD> const & points) = 0;
|
||||
virtual void ClearAllVisualizedPoints() = 0;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,548 @@
|
|||
#include "traffic_mode.hpp"
|
||||
|
||||
#include "openlr/openlr_model_xml.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/scope_guard.hpp"
|
||||
|
||||
#include <QItemSelection>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void RemovePointFromPull(m2::PointD const & toBeRemoved, std::vector<m2::PointD> & pool)
|
||||
{
|
||||
pool.erase(remove_if(begin(pool), end(pool),
|
||||
[&toBeRemoved](m2::PointD const & p) { return p.EqualDxDy(toBeRemoved, 1e-6); }),
|
||||
end(pool));
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetReachablePoints(m2::PointD const & srcPoint, std::vector<m2::PointD> const path,
|
||||
PointsControllerDelegateBase const & pointsDelegate,
|
||||
size_t const lookbackIndex)
|
||||
{
|
||||
auto reachablePoints = pointsDelegate.GetReachablePoints(srcPoint);
|
||||
if (lookbackIndex < path.size())
|
||||
{
|
||||
auto const & toBeRemoved = path[path.size() - lookbackIndex - 1];
|
||||
RemovePointFromPull(toBeRemoved, reachablePoints);
|
||||
}
|
||||
return reachablePoints;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace impl
|
||||
{
|
||||
// static
|
||||
size_t const RoadPointCandidate::kInvalidId = std::numeric_limits<size_t>::max();
|
||||
|
||||
/// This class denotes a "non-deterministic" feature point.
|
||||
/// I.e. it is a set of all pairs <FeatureID, point index>
|
||||
/// located at a specified coordinate.
|
||||
/// Only one point at a time is considered active.
|
||||
RoadPointCandidate::RoadPointCandidate(std::vector<FeaturePoint> const & points, m2::PointD const & coord)
|
||||
: m_coord(coord)
|
||||
, m_points(points)
|
||||
{
|
||||
LOG(LDEBUG, ("Candidate points:", points));
|
||||
}
|
||||
|
||||
void RoadPointCandidate::ActivateCommonPoint(RoadPointCandidate const & rpc)
|
||||
{
|
||||
for (auto const & fp1 : m_points)
|
||||
{
|
||||
for (auto const & fp2 : rpc.m_points)
|
||||
{
|
||||
if (fp1.first == fp2.first)
|
||||
{
|
||||
SetActivePoint(fp1.first);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(false, ("One common feature id should exist."));
|
||||
}
|
||||
|
||||
FeaturePoint const & RoadPointCandidate::GetPoint() const
|
||||
{
|
||||
CHECK_NOT_EQUAL(m_activePointIndex, kInvalidId, ("No point is active."));
|
||||
return m_points[m_activePointIndex];
|
||||
}
|
||||
|
||||
m2::PointD const & RoadPointCandidate::GetCoordinate() const
|
||||
{
|
||||
return m_coord;
|
||||
}
|
||||
|
||||
void RoadPointCandidate::SetActivePoint(FeatureID const & fid)
|
||||
{
|
||||
for (size_t i = 0; i < m_points.size(); ++i)
|
||||
{
|
||||
if (m_points[i].first == fid)
|
||||
{
|
||||
m_activePointIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
CHECK(false, ("One point should match."));
|
||||
}
|
||||
} // namespace impl
|
||||
|
||||
// TrafficMode -------------------------------------------------------------------------------------
|
||||
TrafficMode::TrafficMode(std::string const & dataFileName, DataSource const & dataSource,
|
||||
std::unique_ptr<TrafficDrawerDelegateBase> drawerDelegate,
|
||||
std::unique_ptr<PointsControllerDelegateBase> pointsDelegate, QObject * parent)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_dataSource(dataSource)
|
||||
, m_drawerDelegate(std::move(drawerDelegate))
|
||||
, m_pointsDelegate(std::move(pointsDelegate))
|
||||
{
|
||||
// TODO(mgsergio): Collect stat how many segments of each kind were parsed.
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(dataFileName.data()))
|
||||
MYTHROW(TrafficModeError, ("Can't load file:", strerror(errno)));
|
||||
|
||||
// Save root node without children.
|
||||
{
|
||||
auto const root = doc.document_element();
|
||||
auto node = m_template.append_child(root.name());
|
||||
for (auto const & attr : root.attributes())
|
||||
node.append_copy(attr);
|
||||
}
|
||||
|
||||
// Select all Segment elements that are direct children of the root.
|
||||
auto const segments = doc.document_element().select_nodes("./Segment");
|
||||
|
||||
try
|
||||
{
|
||||
for (auto const & xpathNode : segments)
|
||||
{
|
||||
auto const xmlSegment = xpathNode.node();
|
||||
|
||||
openlr::Path matchedPath;
|
||||
openlr::Path fakePath;
|
||||
openlr::Path goldenPath;
|
||||
|
||||
openlr::LinearSegment segment;
|
||||
|
||||
// TODO(mgsergio): Unify error handling interface of openlr_xml_mode and decoded_path parsers.
|
||||
auto const partnerSegmentXML = xmlSegment.child("reportSegments");
|
||||
if (!openlr::SegmentFromXML(partnerSegmentXML, segment))
|
||||
MYTHROW(TrafficModeError, ("An error occurred while parsing: can't parse segment"));
|
||||
|
||||
if (auto const route = xmlSegment.child("Route"))
|
||||
openlr::PathFromXML(route, m_dataSource, matchedPath);
|
||||
if (auto const route = xmlSegment.child("FakeRoute"))
|
||||
openlr::PathFromXML(route, m_dataSource, fakePath);
|
||||
if (auto const route = xmlSegment.child("GoldenRoute"))
|
||||
openlr::PathFromXML(route, m_dataSource, goldenPath);
|
||||
|
||||
uint32_t positiveOffsetM = 0;
|
||||
uint32_t negativeOffsetM = 0;
|
||||
if (auto const reportSegmentLRC = partnerSegmentXML.child("ReportSegmentLRC"))
|
||||
{
|
||||
if (auto const method = reportSegmentLRC.child("method"))
|
||||
{
|
||||
if (auto const locationReference = method.child("olr:locationReference"))
|
||||
{
|
||||
if (auto const optionLinearLocationReference = locationReference.child("olr:optionLinearLocationReference"))
|
||||
{
|
||||
if (auto const positiveOffset = optionLinearLocationReference.child("olr:positiveOffset"))
|
||||
positiveOffsetM = UintValueFromXML(positiveOffset);
|
||||
|
||||
if (auto const negativeOffset = optionLinearLocationReference.child("olr:negativeOffset"))
|
||||
negativeOffsetM = UintValueFromXML(negativeOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_segments.emplace_back(segment, positiveOffsetM, negativeOffsetM, matchedPath, fakePath, goldenPath,
|
||||
partnerSegmentXML);
|
||||
if (auto const status = xmlSegment.child("Ignored"))
|
||||
{
|
||||
if (status.text().as_bool())
|
||||
m_segments.back().Ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (openlr::DecodedPathLoadError const & e)
|
||||
{
|
||||
MYTHROW(TrafficModeError, ("An exception occurred while parsing", dataFileName, e.Msg()));
|
||||
}
|
||||
|
||||
LOG(LINFO, (segments.size(), "segments are loaded."));
|
||||
}
|
||||
|
||||
// TODO(mgsergio): Check if a path was committed, or commit it.
|
||||
bool TrafficMode::SaveSampleAs(std::string const & fileName) const
|
||||
{
|
||||
CHECK(!fileName.empty(), ("Can't save to an empty file."));
|
||||
|
||||
pugi::xml_document result;
|
||||
result.reset(m_template);
|
||||
auto root = result.document_element();
|
||||
|
||||
for (auto const & sc : m_segments)
|
||||
{
|
||||
auto segment = root.append_child("Segment");
|
||||
segment.append_copy(sc.GetPartnerXMLSegment());
|
||||
|
||||
if (sc.GetStatus() == SegmentCorrespondence::Status::Ignored)
|
||||
segment.append_child("Ignored").text() = true;
|
||||
if (sc.HasMatchedPath())
|
||||
{
|
||||
auto node = segment.append_child("Route");
|
||||
openlr::PathToXML(sc.GetMatchedPath(), node);
|
||||
}
|
||||
if (sc.HasFakePath())
|
||||
{
|
||||
auto node = segment.append_child("FakeRoute");
|
||||
openlr::PathToXML(sc.GetFakePath(), node);
|
||||
}
|
||||
if (sc.HasGoldenPath())
|
||||
{
|
||||
auto node = segment.append_child("GoldenRoute");
|
||||
openlr::PathToXML(sc.GetGoldenPath(), node);
|
||||
}
|
||||
}
|
||||
|
||||
result.save_file(fileName.data(), " " /* indent */);
|
||||
return true;
|
||||
}
|
||||
|
||||
int TrafficMode::rowCount(QModelIndex const & parent) const
|
||||
{
|
||||
return static_cast<int>(m_segments.size());
|
||||
}
|
||||
|
||||
int TrafficMode::columnCount(QModelIndex const & parent) const
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
QVariant TrafficMode::data(QModelIndex const & index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() >= rowCount())
|
||||
return QVariant();
|
||||
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||
return QVariant();
|
||||
|
||||
if (index.column() == 0)
|
||||
return m_segments[index.row()].GetPartnerSegmentId();
|
||||
|
||||
if (index.column() == 1)
|
||||
return static_cast<int>(m_segments[index.row()].GetStatus());
|
||||
|
||||
if (index.column() == 2)
|
||||
return m_segments[index.row()].GetPositiveOffset();
|
||||
|
||||
if (index.column() == 3)
|
||||
return m_segments[index.row()].GetNegativeOffset();
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant TrafficMode::headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal && role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
switch (section)
|
||||
{
|
||||
case 0: return "Segment id"; break;
|
||||
case 1: return "Status code"; break;
|
||||
case 2: return "Positive offset (Meters)"; break;
|
||||
case 3: return "Negative offset (Meters)"; break;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection const &)
|
||||
{
|
||||
ASSERT(!selected.empty(), ());
|
||||
ASSERT(!m_segments.empty(), ());
|
||||
|
||||
auto const row = selected.front().top();
|
||||
|
||||
CHECK_LESS(static_cast<size_t>(row), m_segments.size(), ());
|
||||
m_currentSegment = &m_segments[row];
|
||||
|
||||
auto const & partnerSegment = m_currentSegment->GetPartnerSegment();
|
||||
auto const & partnerSegmentPoints = partnerSegment.GetMercatorPoints();
|
||||
auto const & viewportCenter = partnerSegmentPoints.front();
|
||||
|
||||
m_drawerDelegate->ClearAllPaths();
|
||||
// TODO(mgsergio): Use a better way to set viewport and scale.
|
||||
m_drawerDelegate->SetViewportCenter(viewportCenter);
|
||||
m_drawerDelegate->DrawEncodedSegment(partnerSegmentPoints);
|
||||
if (m_currentSegment->HasMatchedPath())
|
||||
m_drawerDelegate->DrawDecodedSegments(GetPoints(m_currentSegment->GetMatchedPath()));
|
||||
if (m_currentSegment->HasGoldenPath())
|
||||
m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
|
||||
|
||||
emit SegmentSelected(static_cast<int>(partnerSegment.m_segmentId));
|
||||
}
|
||||
|
||||
Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::ItemIsEnabled;
|
||||
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
void TrafficMode::GoldifyMatchedPath()
|
||||
{
|
||||
if (!m_currentSegment->HasMatchedPath())
|
||||
{
|
||||
QMessageBox::information(nullptr /* parent */, "Error", "The selected segment does not have a matched path");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StartBuildingPathChecks())
|
||||
return;
|
||||
|
||||
m_currentSegment->SetGoldenPath(m_currentSegment->GetMatchedPath());
|
||||
m_goldenPath.clear();
|
||||
m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
|
||||
}
|
||||
|
||||
void TrafficMode::StartBuildingPath()
|
||||
{
|
||||
if (!StartBuildingPathChecks())
|
||||
return;
|
||||
|
||||
m_currentSegment->SetGoldenPath({});
|
||||
|
||||
m_buildingPath = true;
|
||||
m_drawerDelegate->ClearGoldenPath();
|
||||
m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport());
|
||||
}
|
||||
|
||||
void TrafficMode::PushPoint(m2::PointD const & coord, std::vector<FeaturePoint> const & points)
|
||||
{
|
||||
impl::RoadPointCandidate point(points, coord);
|
||||
if (!m_goldenPath.empty())
|
||||
m_goldenPath.back().ActivateCommonPoint(point);
|
||||
m_goldenPath.push_back(point);
|
||||
}
|
||||
|
||||
void TrafficMode::PopPoint()
|
||||
{
|
||||
CHECK(!m_goldenPath.empty(), ("Attempt to pop point from an empty path."));
|
||||
m_goldenPath.pop_back();
|
||||
}
|
||||
|
||||
void TrafficMode::CommitPath()
|
||||
{
|
||||
CHECK(m_currentSegment, ("No segments selected"));
|
||||
|
||||
if (!m_buildingPath)
|
||||
MYTHROW(TrafficModeError, ("Path building is not started"));
|
||||
|
||||
SCOPE_GUARD(guard, [this] { emit EditingStopped(); });
|
||||
|
||||
m_buildingPath = false;
|
||||
m_drawerDelegate->ClearAllVisualizedPoints();
|
||||
|
||||
if (m_goldenPath.size() == 1)
|
||||
{
|
||||
LOG(LDEBUG, ("Golden path is empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_GREATER(m_goldenPath.size(), 1, ("Path cannot consist of only one point"));
|
||||
|
||||
// Activate last point. Since no more points will be availabe we link it to the same
|
||||
// feature as the previous one was linked to.
|
||||
m_goldenPath.back().ActivateCommonPoint(m_goldenPath[GetPointsCount() - 2]);
|
||||
|
||||
openlr::Path path;
|
||||
for (size_t i = 1; i < GetPointsCount(); ++i)
|
||||
{
|
||||
auto const prevPoint = m_goldenPath[i - 1];
|
||||
auto point = m_goldenPath[i];
|
||||
|
||||
// The start and the end of the edge should lie on the same feature.
|
||||
point.ActivateCommonPoint(prevPoint);
|
||||
|
||||
auto const & prevFt = prevPoint.GetPoint();
|
||||
auto const & ft = point.GetPoint();
|
||||
|
||||
path.push_back(Edge::MakeReal(ft.first, prevFt.second < ft.second /* forward */,
|
||||
base::checked_cast<uint32_t>(prevFt.second),
|
||||
geometry::PointWithAltitude(prevPoint.GetCoordinate(), 0 /* altitude */),
|
||||
geometry::PointWithAltitude(point.GetCoordinate(), 0 /* altitude */)));
|
||||
}
|
||||
|
||||
m_currentSegment->SetGoldenPath(path);
|
||||
m_goldenPath.clear();
|
||||
}
|
||||
|
||||
void TrafficMode::RollBackPath()
|
||||
{
|
||||
CHECK(m_currentSegment, ("No segments selected"));
|
||||
CHECK(m_buildingPath, ("No path building is in progress."));
|
||||
|
||||
m_buildingPath = false;
|
||||
|
||||
// TODO(mgsergio): Add a method for common visual manipulations.
|
||||
m_drawerDelegate->ClearAllVisualizedPoints();
|
||||
m_drawerDelegate->ClearGoldenPath();
|
||||
if (m_currentSegment->HasGoldenPath())
|
||||
m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath()));
|
||||
|
||||
m_goldenPath.clear();
|
||||
emit EditingStopped();
|
||||
}
|
||||
|
||||
void TrafficMode::IgnorePath()
|
||||
{
|
||||
CHECK(m_currentSegment, ("No segments selected"));
|
||||
|
||||
if (m_currentSegment->HasGoldenPath())
|
||||
{
|
||||
auto const btn = QMessageBox::question(nullptr /* parent */, "Override warning",
|
||||
"The selected segment has a golden path. Do you want to discard it?");
|
||||
if (btn == QMessageBox::No)
|
||||
return;
|
||||
}
|
||||
|
||||
m_buildingPath = false;
|
||||
|
||||
// TODO(mgsergio): Add a method for common visual manipulations.
|
||||
m_drawerDelegate->ClearAllVisualizedPoints();
|
||||
m_drawerDelegate->ClearGoldenPath();
|
||||
|
||||
m_currentSegment->Ignore();
|
||||
m_goldenPath.clear();
|
||||
emit EditingStopped();
|
||||
}
|
||||
|
||||
size_t TrafficMode::GetPointsCount() const
|
||||
{
|
||||
return m_goldenPath.size();
|
||||
}
|
||||
|
||||
m2::PointD const & TrafficMode::GetPoint(size_t const index) const
|
||||
{
|
||||
return m_goldenPath[index].GetCoordinate();
|
||||
}
|
||||
|
||||
m2::PointD const & TrafficMode::GetLastPoint() const
|
||||
{
|
||||
CHECK(!m_goldenPath.empty(), ("Attempt to get point from an empty path."));
|
||||
return m_goldenPath.back().GetCoordinate();
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> TrafficMode::GetGoldenPathPoints() const
|
||||
{
|
||||
std::vector<m2::PointD> coordinates;
|
||||
for (auto const & roadPoint : m_goldenPath)
|
||||
coordinates.push_back(roadPoint.GetCoordinate());
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
// TODO(mgsergio): Draw the first point when the path size is 1.
|
||||
void TrafficMode::HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button)
|
||||
{
|
||||
if (!m_buildingPath)
|
||||
return;
|
||||
|
||||
auto const currentPathLength = GetPointsCount();
|
||||
auto const lastClickedPoint = currentPathLength != 0 ? GetLastPoint() : m2::PointD::Zero();
|
||||
|
||||
auto const & p = m_pointsDelegate->GetCandidatePoints(clickPoint);
|
||||
auto const & candidatePoints = p.first;
|
||||
clickPoint = p.second;
|
||||
if (candidatePoints.empty())
|
||||
return;
|
||||
|
||||
auto reachablePoints =
|
||||
GetReachablePoints(clickPoint, GetGoldenPathPoints(), *m_pointsDelegate, 0 /* lookBackIndex */);
|
||||
auto const & clickablePoints =
|
||||
currentPathLength != 0
|
||||
? GetReachablePoints(lastClickedPoint, GetGoldenPathPoints(), *m_pointsDelegate, 1 /* lookbackIndex */)
|
||||
// TODO(mgsergio): This is not quite correct since view port can change
|
||||
// since first call to visualize points. But it's ok in general.
|
||||
: m_pointsDelegate->GetAllJunctionPointsInViewport();
|
||||
|
||||
using ClickType = PointsControllerDelegateBase::ClickType;
|
||||
switch (m_pointsDelegate->CheckClick(clickPoint, lastClickedPoint, clickablePoints))
|
||||
{
|
||||
case ClickType::Add:
|
||||
// TODO(mgsergio): Think of refactoring this with if (accumulator.empty)
|
||||
// instead of pushing point first ad then removing last selection.
|
||||
PushPoint(clickPoint, candidatePoints);
|
||||
|
||||
if (currentPathLength > 0)
|
||||
{
|
||||
// TODO(mgsergio): Should I remove lastClickedPoint from clickablePoints
|
||||
// as well?
|
||||
RemovePointFromPull(lastClickedPoint, reachablePoints);
|
||||
m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints());
|
||||
}
|
||||
|
||||
m_drawerDelegate->ClearAllVisualizedPoints();
|
||||
m_drawerDelegate->VisualizePoints(reachablePoints);
|
||||
m_drawerDelegate->VisualizePoints({clickPoint});
|
||||
break;
|
||||
case ClickType::Remove: // TODO(mgsergio): Rename this case.
|
||||
if (button == Qt::MouseButton::LeftButton) // RemovePoint
|
||||
{
|
||||
m_drawerDelegate->ClearAllVisualizedPoints();
|
||||
m_drawerDelegate->ClearGoldenPath();
|
||||
|
||||
PopPoint();
|
||||
if (m_goldenPath.empty())
|
||||
{
|
||||
m_drawerDelegate->VisualizePoints(m_pointsDelegate->GetAllJunctionPointsInViewport());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_drawerDelegate->VisualizePoints(
|
||||
GetReachablePoints(GetLastPoint(), GetGoldenPathPoints(), *m_pointsDelegate, 1 /* lookBackIndex */));
|
||||
}
|
||||
|
||||
if (GetPointsCount() > 1)
|
||||
m_drawerDelegate->DrawGoldenPath(GetGoldenPathPoints());
|
||||
}
|
||||
else if (button == Qt::MouseButton::RightButton)
|
||||
{
|
||||
CommitPath();
|
||||
}
|
||||
break;
|
||||
case ClickType::Miss:
|
||||
// TODO(mgsergio): This situation should be handled by checking candidatePoitns.empty() above.
|
||||
// Not shure though if all cases are handled by that check.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool TrafficMode::StartBuildingPathChecks() const
|
||||
{
|
||||
CHECK(m_currentSegment, ("A segment should be selected before path building is started."));
|
||||
|
||||
if (m_buildingPath)
|
||||
MYTHROW(TrafficModeError, ("Path building already in progress."));
|
||||
|
||||
if (m_currentSegment->HasGoldenPath())
|
||||
{
|
||||
auto const btn = QMessageBox::question(nullptr /* parent */, "Override warning",
|
||||
"The selected segment already has a golden path. Do you want to override?");
|
||||
if (btn == QMessageBox::No)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include "points_controller_delegate_base.hpp"
|
||||
#include "segment_correspondence.hpp"
|
||||
#include "traffic_drawer_delegate_base.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
class QItemSelection;
|
||||
class Selection;
|
||||
|
||||
DECLARE_EXCEPTION(TrafficModeError, RootException);
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
/// This class denotes a "non-deterministic" feature point.
|
||||
/// I.e. it is a set of all pairs <FeatureID, point index>
|
||||
/// located at a specified coordinate.
|
||||
/// Only one point at a time is considered active.
|
||||
class RoadPointCandidate
|
||||
{
|
||||
public:
|
||||
RoadPointCandidate(std::vector<openlr::FeaturePoint> const & points, m2::PointD const & coord);
|
||||
|
||||
void ActivateCommonPoint(RoadPointCandidate const & rpc);
|
||||
openlr::FeaturePoint const & GetPoint() const;
|
||||
m2::PointD const & GetCoordinate() const;
|
||||
|
||||
private:
|
||||
static size_t const kInvalidId;
|
||||
|
||||
void SetActivePoint(FeatureID const & fid);
|
||||
|
||||
m2::PointD m_coord = m2::PointD::Zero();
|
||||
std::vector<openlr::FeaturePoint> m_points;
|
||||
|
||||
size_t m_activePointIndex = kInvalidId;
|
||||
};
|
||||
} // namespace impl
|
||||
|
||||
/// This class is used to map sample ids to real data
|
||||
/// and change sample evaluations.
|
||||
class TrafficMode : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// TODO(mgsergio): Check we are on the right mwm. I.e. right mwm version and everything.
|
||||
TrafficMode(std::string const & dataFileName, DataSource const & dataSource,
|
||||
std::unique_ptr<TrafficDrawerDelegateBase> drawerDelegate,
|
||||
std::unique_ptr<PointsControllerDelegateBase> pointsDelegate, QObject * parent = Q_NULLPTR);
|
||||
|
||||
bool SaveSampleAs(std::string const & fileName) const;
|
||||
|
||||
int rowCount(QModelIndex const & parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
int columnCount(QModelIndex const & parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
|
||||
|
||||
QVariant data(QModelIndex const & index, int role) const Q_DECL_OVERRIDE;
|
||||
|
||||
Qt::ItemFlags flags(QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
|
||||
bool IsBuildingPath() const { return m_buildingPath; }
|
||||
void GoldifyMatchedPath();
|
||||
void StartBuildingPath();
|
||||
void PushPoint(m2::PointD const & coord, std::vector<FeaturePoint> const & points);
|
||||
void PopPoint();
|
||||
void CommitPath();
|
||||
void RollBackPath();
|
||||
void IgnorePath();
|
||||
|
||||
size_t GetPointsCount() const;
|
||||
m2::PointD const & GetPoint(size_t const index) const;
|
||||
m2::PointD const & GetLastPoint() const;
|
||||
std::vector<m2::PointD> GetGoldenPathPoints() const;
|
||||
|
||||
public slots:
|
||||
void OnItemSelected(QItemSelection const & selected, QItemSelection const &);
|
||||
void OnClick(m2::PointD const & clickPoint, Qt::MouseButton const button) { HandlePoint(clickPoint, button); }
|
||||
|
||||
signals:
|
||||
void EditingStopped();
|
||||
void SegmentSelected(int segmentId);
|
||||
|
||||
private:
|
||||
void HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button);
|
||||
bool StartBuildingPathChecks() const;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
std::vector<SegmentCorrespondence> m_segments;
|
||||
// Non-owning pointer to an element of m_segments.
|
||||
SegmentCorrespondence * m_currentSegment = nullptr;
|
||||
|
||||
std::unique_ptr<TrafficDrawerDelegateBase> m_drawerDelegate;
|
||||
std::unique_ptr<PointsControllerDelegateBase> m_pointsDelegate;
|
||||
|
||||
bool m_buildingPath = false;
|
||||
std::vector<impl::RoadPointCandidate> m_goldenPath;
|
||||
|
||||
// Clone this document and add things to its clone when saving sample.
|
||||
pugi::xml_document m_template;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_panel.hpp"
|
||||
#include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp"
|
||||
|
||||
#include <QtCore/QAbstractTableModel>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
#include <QtWidgets/QTableView>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// ComboBoxDelegate --------------------------------------------------------------------------------
|
||||
ComboBoxDelegate::ComboBoxDelegate(QObject * parent) : QStyledItemDelegate(parent) {}
|
||||
|
||||
QWidget * ComboBoxDelegate::createEditor(QWidget * parent, QStyleOptionViewItem const & option,
|
||||
QModelIndex const & index) const
|
||||
{
|
||||
auto * editor = new QComboBox(parent);
|
||||
editor->setFrame(false);
|
||||
editor->setEditable(false);
|
||||
editor->addItems({"Unevaluated", "Positive", "Negative", "RelPositive", "RelNegative", "Ignore"});
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
void ComboBoxDelegate::setEditorData(QWidget * editor, QModelIndex const & index) const
|
||||
{
|
||||
auto const value = index.model()->data(index, Qt::EditRole).toString();
|
||||
static_cast<QComboBox *>(editor)->setCurrentText(value);
|
||||
}
|
||||
|
||||
void ComboBoxDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, QModelIndex const & index) const
|
||||
{
|
||||
model->setData(index, static_cast<QComboBox *>(editor)->currentText(), Qt::EditRole);
|
||||
}
|
||||
|
||||
void ComboBoxDelegate::updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option,
|
||||
QModelIndex const & index) const
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
// TrafficPanel ------------------------------------------------------------------------------------
|
||||
TrafficPanel::TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent) : QWidget(parent)
|
||||
{
|
||||
CreateTable(trafficModel);
|
||||
|
||||
auto * layout = new QVBoxLayout();
|
||||
layout->addWidget(m_table);
|
||||
setLayout(layout);
|
||||
|
||||
// Select first segment by default;
|
||||
auto const & index = m_table->model()->index(0, 0);
|
||||
m_table->selectionModel()->select(index, QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void TrafficPanel::CreateTable(QAbstractItemModel * trafficModel)
|
||||
{
|
||||
m_table = new QTableView();
|
||||
m_table->setFocusPolicy(Qt::NoFocus);
|
||||
m_table->setAlternatingRowColors(true);
|
||||
m_table->setShowGrid(false);
|
||||
m_table->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
|
||||
m_table->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
|
||||
m_table->verticalHeader()->setVisible(false);
|
||||
m_table->horizontalHeader()->setVisible(true);
|
||||
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
|
||||
m_table->setModel(trafficModel);
|
||||
m_table->setItemDelegate(new ComboBoxDelegate());
|
||||
|
||||
connect(m_table->selectionModel(), SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)),
|
||||
trafficModel, SLOT(OnItemSelected(QItemSelection const &, QItemSelection const &)));
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QComboBox;
|
||||
class QTableView;
|
||||
class QWidget;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ComboBoxDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ComboBoxDelegate(QObject * parent = 0);
|
||||
|
||||
QWidget * createEditor(QWidget * parent, QStyleOptionViewItem const & option,
|
||||
QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void setEditorData(QWidget * editor, QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void setModelData(QWidget * editor, QAbstractItemModel * model, QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
|
||||
void updateEditorGeometry(QWidget * editor, QStyleOptionViewItem const & option,
|
||||
QModelIndex const & index) const Q_DECL_OVERRIDE;
|
||||
};
|
||||
|
||||
class TrafficPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrafficPanel(QAbstractItemModel * trafficModel, QWidget * parent);
|
||||
|
||||
private:
|
||||
void CreateTable(QAbstractItemModel * trafficModel);
|
||||
void FillTable();
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
// void OnCheckBoxClicked(int row, int state);
|
||||
|
||||
private:
|
||||
QTableView * m_table = Q_NULLPTR;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#include "openlr/openlr_match_quality/openlr_assessment_tool/trafficmodeinitdlg.h"
|
||||
#include "ui_trafficmodeinitdlg.h"
|
||||
|
||||
#include "platform/settings.hpp"
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kDataFilePath = "LastOpenlrAssessmentDataFilePath";
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
TrafficModeInitDlg::TrafficModeInitDlg(QWidget * parent) : QDialog(parent), m_ui(new Ui::TrafficModeInitDlg)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
std::string lastDataFilePath;
|
||||
if (settings::Get(kDataFilePath, lastDataFilePath))
|
||||
m_ui->dataFileName->setText(QString::fromStdString(lastDataFilePath));
|
||||
|
||||
connect(m_ui->chooseDataFileButton, &QPushButton::clicked,
|
||||
[this](bool) { SetFilePathViaDialog(*m_ui->dataFileName, tr("Choose data file"), "*.xml"); });
|
||||
}
|
||||
|
||||
TrafficModeInitDlg::~TrafficModeInitDlg()
|
||||
{
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void TrafficModeInitDlg::accept()
|
||||
{
|
||||
m_dataFileName = m_ui->dataFileName->text().trimmed().toStdString();
|
||||
settings::Set(kDataFilePath, m_dataFileName);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void TrafficModeInitDlg::SetFilePathViaDialog(QLineEdit & dest, QString const & title, QString const & filter)
|
||||
{
|
||||
QFileDialog openFileDlg(nullptr, title, {} /* directory */, filter);
|
||||
openFileDlg.exec();
|
||||
if (openFileDlg.result() != QDialog::DialogCode::Accepted)
|
||||
return;
|
||||
|
||||
dest.setText(openFileDlg.selectedFiles().first());
|
||||
}
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class QLineEdit;
|
||||
|
||||
namespace Ui {
|
||||
class TrafficModeInitDlg;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class TrafficModeInitDlg : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrafficModeInitDlg(QWidget * parent = nullptr);
|
||||
~TrafficModeInitDlg();
|
||||
|
||||
std::string GetDataFilePath() const { return m_dataFileName; }
|
||||
|
||||
private:
|
||||
void SetFilePathViaDialog(QLineEdit & dest, QString const & title,
|
||||
QString const & filter = {});
|
||||
public slots:
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
Ui::TrafficModeInitDlg * m_ui;
|
||||
|
||||
std::string m_dataFileName;
|
||||
};
|
||||
} // namespace openlr
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TrafficModeInitDlg</class>
|
||||
<widget class="QDialog" name="TrafficModeInitDlg">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>482</width>
|
||||
<height>122</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select traffic files</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="gridLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>441</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="chooseDataFileButton">
|
||||
<property name="text">
|
||||
<string>Choose...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>Ok</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="dataFileName"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="dataLabel">
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Data file:</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>dataFileName</tabstop>
|
||||
<tabstop>chooseDataFileButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancelButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TrafficModeInitDlg</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>290</x>
|
||||
<y>103</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>164</x>
|
||||
<y>98</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>okButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>TrafficModeInitDlg</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>405</x>
|
||||
<y>105</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>59</x>
|
||||
<y>94</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
41
tools/openlr/openlr_model.cpp
Normal file
41
tools/openlr/openlr_model.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// LinearSegment -----------------------------------------------------------------------------------
|
||||
vector<m2::PointD> LinearSegment::GetMercatorPoints() const
|
||||
{
|
||||
vector<m2::PointD> points;
|
||||
points.reserve(m_locationReference.m_points.size());
|
||||
for (auto const & point : m_locationReference.m_points)
|
||||
points.push_back(mercator::FromLatLon(point.m_latLon));
|
||||
return points;
|
||||
}
|
||||
|
||||
vector<LocationReferencePoint> const & LinearSegment::GetLRPs() const
|
||||
{
|
||||
return m_locationReference.m_points;
|
||||
}
|
||||
|
||||
vector<LocationReferencePoint> & LinearSegment::GetLRPs()
|
||||
{
|
||||
return m_locationReference.m_points;
|
||||
}
|
||||
|
||||
string DebugPrint(LinearSegmentSource source)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case LinearSegmentSource::NotValid: return "NotValid";
|
||||
case LinearSegmentSource::FromLocationReferenceTag: return "FromLocationReferenceTag";
|
||||
case LinearSegmentSource::FromCoordinatesTag: return "FromCoordinatesTag";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace openlr
|
||||
124
tools/openlr/openlr_model.hpp
Normal file
124
tools/openlr/openlr_model.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// The form of way (FOW) describes the physical road type of a line.
|
||||
enum class FormOfWay
|
||||
{
|
||||
// The physical road type is unknown.
|
||||
Undefined,
|
||||
// A road permitted for motorized vehicles only in combination with a prescribed minimum speed.
|
||||
// It has two or more physically separated carriageways and no single level-crossings.
|
||||
Motorway,
|
||||
// A road with physically separated carriageways regardless of the number of lanes.
|
||||
// If a road is also a motorway, it should be coded as such and not as a multiple carriageway.
|
||||
MultipleCarriageway,
|
||||
// All roads without separate carriageways are considered as roads with a single carriageway.
|
||||
SingleCarriageway,
|
||||
// A road which forms a ring on which traffic traveling in only one direction is allowed.
|
||||
Roundabout,
|
||||
// An open area (partly) enclosed by roads which is used for non-traffic purposes
|
||||
// and which is not a Roundabout.
|
||||
Trafficsquare,
|
||||
// A road especially designed to enter or leave a line.
|
||||
Sliproad,
|
||||
// The physical road type is known, but does not fit into one of the other categories.
|
||||
Other,
|
||||
// A path only allowed for bikes.
|
||||
BikePath,
|
||||
// A path only allowed for pedestrians.
|
||||
Footpath,
|
||||
NotAValue
|
||||
};
|
||||
|
||||
// The functional road class (FRC) of a line is a road classification based on the importance
|
||||
// of the road represented by the line.
|
||||
enum class FunctionalRoadClass
|
||||
{
|
||||
// Main road, highest importance.
|
||||
FRC0,
|
||||
// First class road.
|
||||
FRC1,
|
||||
|
||||
// Other road classes.
|
||||
|
||||
FRC2,
|
||||
FRC3,
|
||||
FRC4,
|
||||
FRC5,
|
||||
FRC6,
|
||||
FRC7,
|
||||
NotAValue
|
||||
};
|
||||
|
||||
// LinearSegment structure may be filled from olr:locationReference xml tag,
|
||||
// from coordinates tag or not valid.
|
||||
enum class LinearSegmentSource
|
||||
{
|
||||
NotValid,
|
||||
FromLocationReferenceTag,
|
||||
FromCoordinatesTag,
|
||||
};
|
||||
|
||||
struct LocationReferencePoint
|
||||
{
|
||||
// Coordinates of the point of interest.
|
||||
ms::LatLon m_latLon = ms::LatLon::Zero();
|
||||
// The bearing (BEAR) describes the angle between the true North and a line which is defined
|
||||
// by the coordinate of the LocationReferencePoint and a coordinate which is BEARDIST along
|
||||
// the line defined by the LocationReference-point attributes.
|
||||
// For more information see OpenLR-Whitepaper `Bearing' section.
|
||||
uint8_t m_bearing = 0;
|
||||
FunctionalRoadClass m_functionalRoadClass = FunctionalRoadClass::NotAValue;
|
||||
FormOfWay m_formOfWay = FormOfWay::NotAValue;
|
||||
|
||||
// The distance to next point field describes the distance to the next LocationReferencePoint
|
||||
// in the topological connection of the LocationReferencePoints. The distance is measured in meters
|
||||
// and is calculated along the location reference path between two subsequent LR-points.
|
||||
// The last LRP has the distance value 0.
|
||||
// Should not be used in the last point of a segment.
|
||||
uint32_t m_distanceToNextPoint = 0;
|
||||
// The lowest FunctionalRoadClass to the next point (LFRCNP) is the lowest FunctionalRoadClass
|
||||
// value which appears in the location reference path between two consecutive LR-points.
|
||||
// This information could be used to limit the number of road classes which need to be
|
||||
// scanned during the decoding.
|
||||
// Should not be used in the last point of a segment.
|
||||
FunctionalRoadClass m_lfrcnp = FunctionalRoadClass::NotAValue;
|
||||
bool m_againstDrivingDirection = false;
|
||||
};
|
||||
|
||||
struct LinearLocationReference
|
||||
{
|
||||
std::vector<LocationReferencePoint> m_points;
|
||||
uint32_t m_positiveOffsetMeters = 0;
|
||||
uint32_t m_negativeOffsetMeters = 0;
|
||||
};
|
||||
|
||||
struct LinearSegment
|
||||
{
|
||||
static auto constexpr kInvalidSegmentId = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
std::vector<m2::PointD> GetMercatorPoints() const;
|
||||
std::vector<LocationReferencePoint> const & GetLRPs() const;
|
||||
std::vector<LocationReferencePoint> & GetLRPs();
|
||||
|
||||
LinearSegmentSource m_source = LinearSegmentSource::NotValid;
|
||||
// TODO(mgsergio): Think of using openlr::PartnerSegmentId
|
||||
uint32_t m_segmentId = kInvalidSegmentId;
|
||||
// TODO(mgsergio): Make sure that one segment cannot contain
|
||||
// more than one location reference.
|
||||
LinearLocationReference m_locationReference;
|
||||
uint32_t m_segmentLengthMeters = 0;
|
||||
// uint32_t m_segmentRefSpeed; Always null in partners data. (No docs found).
|
||||
};
|
||||
|
||||
std::string DebugPrint(LinearSegmentSource source);
|
||||
} // namespace openlr
|
||||
348
tools/openlr/openlr_model_xml.cpp
Normal file
348
tools/openlr/openlr_model_xml.cpp
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#include "openlr/openlr_model_xml.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace // Primitive utilities to handle simple OpenLR-like XML data.
|
||||
{
|
||||
template <typename Value>
|
||||
bool IntegerFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return false;
|
||||
value = static_cast<Value>(is_signed<Value>::value ? node.text().as_int() : node.text().as_uint());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<double> DoubleFromXML(pugi::xml_node const & node)
|
||||
{
|
||||
return node ? node.text().as_double() : std::optional<double>();
|
||||
}
|
||||
|
||||
bool GetLatLon(pugi::xml_node const & node, int32_t & lat, int32_t & lon)
|
||||
{
|
||||
if (!IntegerFromXML(node.child("olr:latitude"), lat) || !IntegerFromXML(node.child("olr:longitude"), lon))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This helper is used to parse records like this:
|
||||
// <olr:lfrcnp olr:table="olr001_FunctionalRoadClass" olr:code="4"/>
|
||||
template <typename Value>
|
||||
bool TableValueFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return false;
|
||||
value = static_cast<Value>(is_signed<Value>::value ? node.attribute("olr:code").as_int()
|
||||
: node.attribute("olr:code").as_uint());
|
||||
return true;
|
||||
}
|
||||
|
||||
pugi::xml_node GetLinearLocationReference(pugi::xml_node const & node)
|
||||
{
|
||||
// There should be only one linear location reference child of a location reference.
|
||||
return node.select_node(".//olr:locationReference/olr:optionLinearLocationReference").node();
|
||||
}
|
||||
|
||||
pugi::xml_node GetCoordinates(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node(".//coordinates").node();
|
||||
}
|
||||
|
||||
bool IsLocationReferenceTag(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node(".//olr:locationReference").node();
|
||||
}
|
||||
|
||||
bool IsCoordinatesTag(pugi::xml_node const & node)
|
||||
{
|
||||
return node.select_node("coordinates").node();
|
||||
}
|
||||
|
||||
// This helper is used do deal with xml nodes of the form
|
||||
// <node>
|
||||
// <value>integer<value>
|
||||
// </node>
|
||||
template <typename Value>
|
||||
bool ValueFromXML(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
auto const valueNode = node.child("olr:value");
|
||||
return IntegerFromXML(valueNode, value);
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
bool ParseValueIfExists(pugi::xml_node const & node, Value & value)
|
||||
{
|
||||
if (!node)
|
||||
return true;
|
||||
return ValueFromXML(node, value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace // OpenLR tools and abstractions
|
||||
{
|
||||
bool FirstCoordinateFromXML(pugi::xml_node const & node, ms::LatLon & latLon)
|
||||
{
|
||||
int32_t lat, lon;
|
||||
if (!GetLatLon(node.child("olr:coordinate"), lat, lon))
|
||||
return false;
|
||||
|
||||
latLon.m_lat = ((lat - math::Sign(lat) * 0.5) * 360) / (1 << 24);
|
||||
latLon.m_lon = ((lon - math::Sign(lon) * 0.5) * 360) / (1 << 24);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ms::LatLon> LatLonFormXML(pugi::xml_node const & node)
|
||||
{
|
||||
auto const lat = DoubleFromXML(node.child("latitude"));
|
||||
auto const lon = DoubleFromXML(node.child("longitude"));
|
||||
|
||||
return lat && lon ? ms::LatLon(*lat, *lon) : ms::LatLon::Zero();
|
||||
}
|
||||
|
||||
bool CoordinateFromXML(pugi::xml_node const & node, ms::LatLon const & prevCoord, ms::LatLon & latLon)
|
||||
{
|
||||
int32_t lat, lon;
|
||||
if (!GetLatLon(node.child("olr:coordinate"), lat, lon))
|
||||
return false;
|
||||
|
||||
// This constant is provided by the given OpenLR variant
|
||||
// with no special meaning and is used as a factor to store doubles as ints.
|
||||
auto const kOpenlrDeltaFactor = 100000;
|
||||
|
||||
latLon.m_lat = prevCoord.m_lat + static_cast<double>(lat) / kOpenlrDeltaFactor;
|
||||
latLon.m_lon = prevCoord.m_lon + static_cast<double>(lon) / kOpenlrDeltaFactor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LinePropertiesFromXML(pugi::xml_node const & linePropNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!linePropNode)
|
||||
{
|
||||
LOG(LERROR, ("linePropNode is NULL"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(linePropNode.child("olr:frc"), locPoint.m_functionalRoadClass))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse functional road class"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(linePropNode.child("olr:fow"), locPoint.m_formOfWay))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse form of a way"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValueFromXML(linePropNode.child("olr:bearing"), locPoint.m_bearing))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse bearing"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PathPropertiesFromXML(pugi::xml_node const & locPointNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
// Last point does not contain path properties.
|
||||
if (strcmp(locPointNode.name(), "olr:last") == 0)
|
||||
return true;
|
||||
|
||||
auto const propNode = locPointNode.child("olr:pathProperties");
|
||||
if (!propNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't parse path properties"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValueFromXML(propNode.child("olr:dnp"), locPoint.m_distanceToNextPoint))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse dnp"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TableValueFromXML(propNode.child("olr:lfrcnp"), locPoint.m_lfrcnp))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse lfrcnp"));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const directionNode = propNode.child("olr:againstDrivingDirection");
|
||||
if (!directionNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't parse driving direction"));
|
||||
return false;
|
||||
}
|
||||
locPoint.m_againstDrivingDirection = directionNode.text().as_bool();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocationReferencePointFromXML(pugi::xml_node const & locPointNode, openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!FirstCoordinateFromXML(locPointNode, locPoint.m_latLon))
|
||||
{
|
||||
LOG(LERROR, ("Can't get first coordinate"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return LinePropertiesFromXML(locPointNode.child("olr:lineProperties"), locPoint) &&
|
||||
PathPropertiesFromXML(locPointNode, locPoint);
|
||||
}
|
||||
|
||||
bool LocationReferencePointFromXML(pugi::xml_node const & locPointNode, ms::LatLon const & firstPoint,
|
||||
openlr::LocationReferencePoint & locPoint)
|
||||
{
|
||||
if (!CoordinateFromXML(locPointNode, firstPoint, locPoint.m_latLon))
|
||||
{
|
||||
LOG(LERROR, ("Can't get last coordinate"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return LinePropertiesFromXML(locPointNode.child("olr:lineProperties"), locPoint) &&
|
||||
PathPropertiesFromXML(locPointNode, locPoint);
|
||||
}
|
||||
|
||||
bool LinearLocationReferenceFromXML(pugi::xml_node const & locRefNode, openlr::LinearLocationReference & locRef)
|
||||
{
|
||||
if (!locRefNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't get location reference."));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(locRefNode.child("olr:first"), point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
for (auto const pointNode : locRefNode.select_nodes("olr:intermediates"))
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(pointNode.node(), locRef.m_points.back().m_latLon, point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
{
|
||||
openlr::LocationReferencePoint point;
|
||||
if (!LocationReferencePointFromXML(locRefNode.child("olr:last"), locRef.m_points.back().m_latLon, point))
|
||||
return false;
|
||||
locRef.m_points.push_back(point);
|
||||
}
|
||||
|
||||
if (!ParseValueIfExists(locRefNode.child("olr:positiveOffset"), locRef.m_positiveOffsetMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse positive offset"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseValueIfExists(locRefNode.child("olr:negativeOffset"), locRef.m_negativeOffsetMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse negative offset"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoordinatesFromXML(pugi::xml_node const & coordsNode, openlr::LinearLocationReference & locRef)
|
||||
{
|
||||
if (!coordsNode)
|
||||
{
|
||||
LOG(LERROR, ("Can't get <coordinates>."));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const latLonStart = LatLonFormXML(coordsNode.child("start"));
|
||||
auto const latLonEnd = LatLonFormXML(coordsNode.child("end"));
|
||||
if (!latLonStart || !latLonEnd)
|
||||
{
|
||||
LOG(LERROR, ("Can't get <start> or <end> of <coordinates>."));
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(LINFO, ("from:", *latLonStart, "to:", *latLonEnd));
|
||||
locRef.m_points.resize(2);
|
||||
locRef.m_points[0].m_latLon = *latLonStart;
|
||||
locRef.m_points[1].m_latLon = *latLonEnd;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
bool ParseOpenlr(pugi::xml_document const & document, vector<LinearSegment> & segments)
|
||||
{
|
||||
for (auto const segmentXpathNode : document.select_nodes("//reportSegments"))
|
||||
{
|
||||
LinearSegment segment;
|
||||
auto const & node = segmentXpathNode.node();
|
||||
if (!IsLocationReferenceTag(node) && !IsCoordinatesTag(node))
|
||||
{
|
||||
LOG(LWARNING, ("A segment with a strange tag. It is not <coordinates>"
|
||||
" or <optionLinearLocationReference>, skipping..."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SegmentFromXML(node, segment))
|
||||
return false;
|
||||
|
||||
segments.push_back(segment);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentFromXML(pugi::xml_node const & segmentNode, LinearSegment & segment)
|
||||
{
|
||||
CHECK(segmentNode, ());
|
||||
if (!IntegerFromXML(segmentNode.child("ReportSegmentID"), segment.m_segmentId))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse segment id"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IntegerFromXML(segmentNode.child("segmentLength"), segment.m_segmentLengthMeters))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse segment length"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsLocationReferenceTag(segmentNode))
|
||||
{
|
||||
auto const locRefNode = GetLinearLocationReference(segmentNode);
|
||||
auto const result = LinearLocationReferenceFromXML(locRefNode, segment.m_locationReference);
|
||||
if (result)
|
||||
segment.m_source = LinearSegmentSource::FromLocationReferenceTag;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CHECK(IsCoordinatesTag(segmentNode), ());
|
||||
auto const coordsNode = GetCoordinates(segmentNode);
|
||||
if (!CoordinatesFromXML(coordsNode, segment.m_locationReference))
|
||||
return false;
|
||||
|
||||
CHECK_EQUAL(segment.m_locationReference.m_points.size(), 2, ());
|
||||
segment.m_locationReference.m_points[0].m_distanceToNextPoint = segment.m_segmentLengthMeters;
|
||||
segment.m_source = LinearSegmentSource::FromCoordinatesTag;
|
||||
return true;
|
||||
}
|
||||
} // namespace openlr
|
||||
18
tools/openlr/openlr_model_xml.hpp
Normal file
18
tools/openlr/openlr_model_xml.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace pugi
|
||||
{
|
||||
class xml_document;
|
||||
class xml_node;
|
||||
} // namespace pugi
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct LinearSegment;
|
||||
|
||||
bool SegmentFromXML(pugi::xml_node const & segmentNode, LinearSegment & segment);
|
||||
|
||||
bool ParseOpenlr(pugi::xml_document const & document, std::vector<LinearSegment> & segments);
|
||||
} // namespace openlr
|
||||
12
tools/openlr/openlr_stat/CMakeLists.txt
Normal file
12
tools/openlr/openlr_stat/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
project(openlr_stat)
|
||||
|
||||
set(SRC openlr_stat.cpp)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
openlr
|
||||
storage
|
||||
platform
|
||||
gflags::gflags
|
||||
)
|
||||
282
tools/openlr/openlr_stat/openlr_stat.cpp
Normal file
282
tools/openlr/openlr_stat/openlr_stat.cpp
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
#include "openlr/openlr_decoder.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "storage/country_parent_getter.hpp"
|
||||
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/local_country_file_utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/openlr_model_xml.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <clocale>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
DEFINE_string(input, "", "Path to OpenLR file.");
|
||||
DEFINE_string(spark_output, "", "Path to output file in spark-oriented format");
|
||||
DEFINE_string(assessment_output, "", "Path to output file in assessment-tool oriented format");
|
||||
|
||||
DEFINE_string(non_matched_ids, "non-matched-ids.txt", "Path to a file ids of non-matched segments will be saved to");
|
||||
DEFINE_string(mwms_path, "", "Path to a folder with mwms.");
|
||||
DEFINE_string(resources_path, "", "Path to a folder with resources.");
|
||||
DEFINE_int32(limit, -1, "Max number of segments to handle. -1 for all.");
|
||||
DEFINE_bool(multipoints_only, false, "Only segments with multiple points to handle.");
|
||||
DEFINE_int32(num_threads, 1, "Number of threads.");
|
||||
DEFINE_string(ids_path, "", "Path to a file with segment ids to process.");
|
||||
DEFINE_string(countries_filename, "",
|
||||
"Name of countries file which describes mwm tree. Used to get country specific "
|
||||
"routing restrictions.");
|
||||
DEFINE_int32(algo_version, 0, "Use new decoding algorithm");
|
||||
|
||||
using namespace openlr;
|
||||
|
||||
namespace
|
||||
{
|
||||
int32_t const kMinNumThreads = 1;
|
||||
int32_t const kMaxNumThreads = 128;
|
||||
int32_t const kHandleAllSegments = -1;
|
||||
|
||||
void LoadDataSources(std::string const & pathToMWMFolder, std::vector<FrozenDataSource> & dataSources)
|
||||
{
|
||||
CHECK(Platform::IsDirectory(pathToMWMFolder), (pathToMWMFolder, "must be a directory."));
|
||||
|
||||
Platform::FilesList files;
|
||||
Platform::GetFilesByRegExp(pathToMWMFolder, boost::regex(".*\\") + DATA_FILE_EXTENSION, files);
|
||||
|
||||
CHECK(!files.empty(), (pathToMWMFolder, "Contains no .mwm files."));
|
||||
|
||||
size_t const numDataSources = dataSources.size();
|
||||
std::vector<uint64_t> numCountries(numDataSources);
|
||||
|
||||
for (auto const & fileName : files)
|
||||
{
|
||||
auto const fullFileName = base::JoinPath(pathToMWMFolder, fileName);
|
||||
ModelReaderPtr reader(GetPlatform().GetReader(fullFileName, "f"));
|
||||
platform::LocalCountryFile localFile(pathToMWMFolder, platform::CountryFile(base::FilenameWithoutExt(fileName)),
|
||||
version::ReadVersionDate(reader));
|
||||
|
||||
LOG(LINFO, ("Found mwm:", fullFileName));
|
||||
try
|
||||
{
|
||||
localFile.SyncWithDisk();
|
||||
for (size_t i = 0; i < numDataSources; ++i)
|
||||
{
|
||||
auto const result = dataSources[i].RegisterMap(localFile);
|
||||
CHECK_EQUAL(result.second, MwmSet::RegResult::Success, ("Can't register mwm:", localFile));
|
||||
|
||||
auto const & info = result.first.GetInfo();
|
||||
if (info && info->GetType() == MwmInfo::COUNTRY)
|
||||
++numCountries[i];
|
||||
}
|
||||
}
|
||||
catch (RootException const & ex)
|
||||
{
|
||||
CHECK(false, (ex.Msg(), "Bad mwm file:", localFile));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numDataSources; ++i)
|
||||
if (numCountries[i] == 0)
|
||||
LOG(LWARNING, ("No countries for thread", i));
|
||||
}
|
||||
|
||||
bool ValidateLimit(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
LOG(LINFO, ("Valid value for --", std::string(flagname), ":", value, "must be greater or equal to -1."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateNumThreads(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value < kMinNumThreads || value > kMaxNumThreads)
|
||||
{
|
||||
LOG(LINFO, ("Valid value for --", std::string(flagname), ":", value, "must be between", kMinNumThreads, "and",
|
||||
kMaxNumThreads));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateMwmPath(char const * flagname, std::string const & value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be specified."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateVersion(char const * flagname, int32_t value)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be specified."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value != 1 && value != 2 && value != 3)
|
||||
{
|
||||
LOG(LINFO, ("--", std::string(flagname), "should be one of 1, 2 or 3."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool const g_limitDummy = gflags::RegisterFlagValidator(&FLAGS_limit, &ValidateLimit);
|
||||
bool const g_numThreadsDummy = gflags::RegisterFlagValidator(&FLAGS_num_threads, &ValidateNumThreads);
|
||||
bool const g_mwmsPathDummy = gflags::RegisterFlagValidator(&FLAGS_mwms_path, &ValidateMwmPath);
|
||||
bool const g_algoVersion = gflags::RegisterFlagValidator(&FLAGS_algo_version, &ValidateVersion);
|
||||
|
||||
void SaveNonMatchedIds(std::string const & filename, std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
if (filename.empty())
|
||||
return;
|
||||
|
||||
std::ofstream ofs(filename);
|
||||
for (auto const & p : paths)
|
||||
if (p.m_path.empty())
|
||||
ofs << p.m_segmentId << std::endl;
|
||||
}
|
||||
|
||||
std::vector<LinearSegment> LoadSegments(pugi::xml_document & document)
|
||||
{
|
||||
std::vector<LinearSegment> segments;
|
||||
if (!ParseOpenlr(document, segments))
|
||||
{
|
||||
LOG(LERROR, ("Can't parse data."));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
OpenLRDecoder::SegmentsFilter filter(FLAGS_ids_path, FLAGS_multipoints_only);
|
||||
if (FLAGS_limit != kHandleAllSegments && FLAGS_limit >= 0 && static_cast<size_t>(FLAGS_limit) < segments.size())
|
||||
segments.resize(FLAGS_limit);
|
||||
|
||||
base::EraseIf(segments, [&filter](LinearSegment const & segment) { return !filter.Matches(segment); });
|
||||
|
||||
std::sort(segments.begin(), segments.end(), base::LessBy(&LinearSegment::m_segmentId));
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
void WriteAssessmentFile(std::string const fileName, pugi::xml_document const & doc,
|
||||
std::vector<DecodedPath> const & paths)
|
||||
{
|
||||
std::unordered_map<uint32_t, pugi::xml_node> xmlSegments;
|
||||
for (auto const & xpathNode : doc.select_nodes("//reportSegments"))
|
||||
{
|
||||
auto const xmlSegment = xpathNode.node();
|
||||
xmlSegments[xmlSegment.child("ReportSegmentID").text().as_uint()] = xmlSegment;
|
||||
}
|
||||
|
||||
pugi::xml_document result;
|
||||
auto segments = result.append_child("Segments");
|
||||
auto const dict = doc.select_node(".//Dictionary").node();
|
||||
char const xmlns[] = "xmlns";
|
||||
|
||||
// Copy namespaces from <Dictionary> to <Segments>
|
||||
for (auto const & attr : dict.attributes())
|
||||
{
|
||||
if (strncmp(xmlns, attr.name(), sizeof(xmlns) - 1) != 0)
|
||||
continue;
|
||||
|
||||
// Don't copy default namespace.
|
||||
if (strncmp(xmlns, attr.name(), sizeof(xmlns)) == 0)
|
||||
continue;
|
||||
|
||||
segments.append_copy(attr);
|
||||
}
|
||||
|
||||
for (auto const & p : paths)
|
||||
{
|
||||
auto segment = segments.append_child("Segment");
|
||||
{
|
||||
auto const xmlSegment = xmlSegments[p.m_segmentId.Get()];
|
||||
segment.append_copy(xmlSegment);
|
||||
}
|
||||
if (!p.m_path.empty())
|
||||
{
|
||||
auto node = segment.append_child("Route");
|
||||
openlr::PathToXML(p.m_path, node);
|
||||
}
|
||||
}
|
||||
|
||||
result.save_file(fileName.data(), " " /* indent */);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
gflags::SetUsageMessage("OpenLR stats tool.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (!FLAGS_resources_path.empty())
|
||||
GetPlatform().SetResourceDir(FLAGS_resources_path);
|
||||
|
||||
classificator::Load();
|
||||
|
||||
auto const numThreads = static_cast<uint32_t>(FLAGS_num_threads);
|
||||
|
||||
std::vector<FrozenDataSource> dataSources(numThreads);
|
||||
|
||||
LoadDataSources(FLAGS_mwms_path, dataSources);
|
||||
|
||||
OpenLRDecoder decoder(dataSources,
|
||||
storage::CountryParentGetter(FLAGS_countries_filename, GetPlatform().ResourcesDir()));
|
||||
|
||||
pugi::xml_document document;
|
||||
auto const load_result = document.load_file(FLAGS_input.data());
|
||||
if (!load_result)
|
||||
{
|
||||
LOG(LERROR, ("Can't load file", FLAGS_input, ":", load_result.description()));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
std::setlocale(LC_ALL, "en_US.UTF-8");
|
||||
auto const segments = LoadSegments(document);
|
||||
|
||||
std::vector<DecodedPath> paths(segments.size());
|
||||
switch (FLAGS_algo_version)
|
||||
{
|
||||
case 2: decoder.DecodeV2(segments, numThreads, paths); break;
|
||||
case 3: decoder.DecodeV3(segments, numThreads, paths); break;
|
||||
default: CHECK(false, ("Wrong algorithm version."));
|
||||
}
|
||||
|
||||
SaveNonMatchedIds(FLAGS_non_matched_ids, paths);
|
||||
if (!FLAGS_assessment_output.empty())
|
||||
WriteAssessmentFile(FLAGS_assessment_output, document, paths);
|
||||
if (!FLAGS_spark_output.empty())
|
||||
WriteAsMappingForSpark(FLAGS_spark_output, paths);
|
||||
|
||||
return 0;
|
||||
}
|
||||
12
tools/openlr/openlr_tests/CMakeLists.txt
Normal file
12
tools/openlr/openlr_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
project(openlr_tests)
|
||||
|
||||
set(SRC decoded_path_test.cpp)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
generator_tests_support
|
||||
platform_tests_support
|
||||
openlr
|
||||
indexer
|
||||
)
|
||||
208
tools/openlr/openlr_tests/decoded_path_test.cpp
Normal file
208
tools/openlr/openlr_tests/decoded_path_test.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
|
||||
#include "generator/generator_tests_support/test_feature.hpp"
|
||||
#include "generator/generator_tests_support/test_mwm_builder.hpp"
|
||||
|
||||
#include "indexer/classificator_loader.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "platform/country_file.hpp"
|
||||
#include "platform/local_country_file.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/platform_tests_support/scoped_dir.hpp"
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
using namespace generator::tests_support;
|
||||
using namespace platform::tests_support;
|
||||
using namespace platform;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
string const kTestDir = "openlr_decoded_path_test";
|
||||
string const kTestMwm = "test";
|
||||
|
||||
double RoughUpToFive(double d)
|
||||
{
|
||||
stringstream s;
|
||||
s << setprecision(5) << fixed;
|
||||
s << d;
|
||||
s >> d;
|
||||
return d;
|
||||
}
|
||||
|
||||
m2::PointD RoughPoint(m2::PointD const & p)
|
||||
{
|
||||
return {RoughUpToFive(p.x), RoughUpToFive(p.y)};
|
||||
}
|
||||
|
||||
geometry::PointWithAltitude RoughJunction(geometry::PointWithAltitude const & j)
|
||||
{
|
||||
return geometry::PointWithAltitude(RoughPoint(j.GetPoint()), j.GetAltitude());
|
||||
}
|
||||
|
||||
routing::Edge RoughEdgeJunctions(routing::Edge const & e)
|
||||
{
|
||||
return routing::Edge::MakeReal(e.GetFeatureId(), e.IsForward(), e.GetSegId(), RoughJunction(e.GetStartJunction()),
|
||||
RoughJunction(e.GetEndJunction()));
|
||||
}
|
||||
|
||||
void RoughJunctionsInPath(openlr::Path & p)
|
||||
{
|
||||
for (auto & e : p)
|
||||
e = RoughEdgeJunctions(e);
|
||||
}
|
||||
|
||||
void TestSerializeDeserialize(openlr::Path const & path, DataSource const & dataSource)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
openlr::PathToXML(path, doc);
|
||||
|
||||
openlr::Path restoredPath;
|
||||
openlr::PathFromXML(doc, dataSource, restoredPath);
|
||||
|
||||
// Fix mercator::From/ToLatLon floating point error
|
||||
// for we could use TEST_EQUAL on result.
|
||||
RoughJunctionsInPath(restoredPath);
|
||||
|
||||
TEST_EQUAL(path, restoredPath, ());
|
||||
}
|
||||
|
||||
openlr::Path MakePath(FeatureType const & road, bool const forward)
|
||||
{
|
||||
CHECK_EQUAL(road.GetGeomType(), feature::GeomType::Line, ());
|
||||
CHECK_GREATER(road.GetPointsCount(), 0, ());
|
||||
openlr::Path path;
|
||||
|
||||
size_t const maxPointIndex = road.GetPointsCount() - 1;
|
||||
for (size_t i = 0; i < maxPointIndex; ++i)
|
||||
{
|
||||
size_t current{};
|
||||
size_t next{};
|
||||
if (forward)
|
||||
{
|
||||
current = i;
|
||||
next = i + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = maxPointIndex - i;
|
||||
next = current - 1;
|
||||
}
|
||||
|
||||
auto const from = road.GetPoint(current);
|
||||
auto const to = road.GetPoint(next);
|
||||
path.push_back(routing::Edge::MakeReal(
|
||||
road.GetID(), forward, base::checked_cast<uint32_t>(current - static_cast<size_t>(!forward)) /* segId */,
|
||||
geometry::PointWithAltitude(from, 0 /* altitude */), geometry::PointWithAltitude(to, 0 /* altitude */)));
|
||||
}
|
||||
|
||||
RoughJunctionsInPath(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void WithRoad(vector<m2::PointD> const & points, Func && fn)
|
||||
{
|
||||
classificator::Load();
|
||||
auto & platform = GetPlatform();
|
||||
|
||||
auto const mwmPath = base::JoinPath(platform.WritableDir(), kTestDir);
|
||||
|
||||
LocalCountryFile country(mwmPath, CountryFile(kTestMwm), 0 /* version */);
|
||||
ScopedDir testScopedDir(kTestDir);
|
||||
ScopedFile testScopedMwm(base::JoinPath(kTestDir, kTestMwm + DATA_FILE_EXTENSION), ScopedFile::Mode::Create);
|
||||
|
||||
{
|
||||
TestMwmBuilder builder(country, feature::DataHeader::MapType::Country);
|
||||
builder.Add(TestRoad(points, "Interstate 60", "en"));
|
||||
}
|
||||
|
||||
FrozenDataSource dataSource;
|
||||
auto const regResult = dataSource.RegisterMap(country);
|
||||
TEST_EQUAL(regResult.second, MwmSet::RegResult::Success, ());
|
||||
|
||||
MwmSet::MwmHandle mwmHandle = dataSource.GetMwmHandleById(regResult.first);
|
||||
TEST(mwmHandle.IsAlive(), ());
|
||||
|
||||
FeaturesLoaderGuard const guard(dataSource, regResult.first);
|
||||
auto road = guard.GetFeatureByIndex(0);
|
||||
TEST(road, ());
|
||||
|
||||
road->ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
fn(dataSource, *road);
|
||||
}
|
||||
|
||||
UNIT_TEST(MakePath_Test)
|
||||
{
|
||||
std::vector<m2::PointD> const points{{0, 0}, {0, 1}, {1, 0}, {1, 1}};
|
||||
WithRoad(points, [&points](DataSource const & dataSource, FeatureType & road)
|
||||
{
|
||||
auto const & id = road.GetID();
|
||||
{
|
||||
openlr::Path const expected{routing::Edge::MakeReal(id, true /* forward */, 0 /* segId*/, points[0], points[1]),
|
||||
routing::Edge::MakeReal(id, true /* forward */, 1 /* segId*/, points[1], points[2]),
|
||||
routing::Edge::MakeReal(id, true /* forward */, 2 /* segId*/, points[2], points[3])};
|
||||
auto const path = MakePath(road, true /* forward */);
|
||||
TEST_EQUAL(path, expected, ());
|
||||
}
|
||||
{
|
||||
openlr::Path const expected{routing::Edge::MakeReal(id, false /* forward */, 2 /* segId*/, points[3], points[2]),
|
||||
routing::Edge::MakeReal(id, false /* forward */, 1 /* segId*/, points[2], points[1]),
|
||||
routing::Edge::MakeReal(id, false /* forward */, 0 /* segId*/, points[1], points[0])};
|
||||
{
|
||||
auto const path = MakePath(road, false /* forward */);
|
||||
TEST_EQUAL(path, expected, ());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UNIT_TEST(PathSerializeDeserialize_Test)
|
||||
{
|
||||
WithRoad({{0, 0}, {0, 1}, {1, 0}, {1, 1}}, [](DataSource const & dataSource, FeatureType & road)
|
||||
{
|
||||
{
|
||||
auto const path = MakePath(road, true /* forward */);
|
||||
TestSerializeDeserialize(path, dataSource);
|
||||
}
|
||||
{
|
||||
auto const path = MakePath(road, false /* forward */);
|
||||
TestSerializeDeserialize(path, dataSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UNIT_TEST(GetPoints_Test)
|
||||
{
|
||||
vector<m2::PointD> const points{{0, 0}, {0, 1}, {1, 0}, {1, 1}};
|
||||
WithRoad(points, [&points](DataSource const &, FeatureType & road)
|
||||
{
|
||||
{
|
||||
auto path = MakePath(road, true /* forward */);
|
||||
// RoughJunctionsInPath(path);
|
||||
TEST_EQUAL(GetPoints(path), points, ());
|
||||
}
|
||||
{
|
||||
auto path = MakePath(road, false /* forward */);
|
||||
// RoughJunctionsInPath(path);
|
||||
auto reversed = points;
|
||||
reverse(begin(reversed), end(reversed));
|
||||
TEST_EQUAL(GetPoints(path), reversed, ());
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
219
tools/openlr/paths_connector.cpp
Normal file
219
tools/openlr/paths_connector.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#include "openlr/paths_connector.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool ValidatePath(Graph::EdgeVector const & path, double const distanceToNextPoint, double const pathLengthTolerance)
|
||||
{
|
||||
double pathLen = 0.0;
|
||||
for (auto const & e : path)
|
||||
pathLen += EdgeLength(e);
|
||||
|
||||
double pathDiffPercent =
|
||||
AbsDifference(static_cast<double>(distanceToNextPoint), pathLen) / static_cast<double>(distanceToNextPoint);
|
||||
|
||||
LOG(LDEBUG, ("Validating path:", LogAs2GisPath(path)));
|
||||
|
||||
if (pathDiffPercent > pathLengthTolerance)
|
||||
{
|
||||
LOG(LDEBUG, ("Shortest path doest not meet required length constraints, error:", pathDiffPercent));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PathsConnector::PathsConnector(double pathLengthTolerance, Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat)
|
||||
: m_pathLengthTolerance(pathLengthTolerance)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stat(stat)
|
||||
{}
|
||||
|
||||
bool PathsConnector::ConnectCandidates(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<Graph::EdgeVector>> const & lineCandidates,
|
||||
vector<Graph::EdgeVector> & resultPath)
|
||||
{
|
||||
ASSERT(!points.empty(), ());
|
||||
|
||||
resultPath.resize(points.size() - 1);
|
||||
|
||||
// TODO(mgsergio): Discard last point on failure.
|
||||
// TODO(mgsergio): Do not iterate more than kMaxRetries times.
|
||||
// TODO(mgserigio): Make kMaxRetries depend on points number in the segment.
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
auto const & point = points[i - 1];
|
||||
auto const distanceToNextPoint = static_cast<double>(point.m_distanceToNextPoint);
|
||||
auto const & fromCandidates = lineCandidates[i - 1];
|
||||
auto const & toCandidates = lineCandidates[i];
|
||||
auto & resultPathPart = resultPath[i - 1];
|
||||
|
||||
Graph::EdgeVector fakePath;
|
||||
|
||||
for (size_t fromInd = 0; fromInd < fromCandidates.size() && !found; ++fromInd)
|
||||
{
|
||||
for (size_t toInd = 0; toInd < toCandidates.size() && !found; ++toInd)
|
||||
{
|
||||
resultPathPart.clear();
|
||||
|
||||
found = ConnectAdjacentCandidateLines(fromCandidates[fromInd], toCandidates[toInd], point.m_lfrcnp,
|
||||
distanceToNextPoint, resultPathPart);
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
|
||||
found = ValidatePath(resultPathPart, distanceToNextPoint, m_pathLengthTolerance);
|
||||
if (fakePath.empty() && found && (resultPathPart.front().IsFake() || resultPathPart.back().IsFake()))
|
||||
{
|
||||
fakePath = resultPathPart;
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fakePath.empty() && !found)
|
||||
{
|
||||
found = true;
|
||||
resultPathPart = fakePath;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
LOG(LDEBUG, ("No shortest path found"));
|
||||
++m_stat.m_noShortestPathFound;
|
||||
resultPathPart.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(resultPath.size(), points.size() - 1, ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PathsConnector::FindShortestPath(Graph::Edge const & from, Graph::Edge const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength,
|
||||
Graph::EdgeVector & path)
|
||||
{
|
||||
// TODO(mgsergio): Turn Dijkstra to A*.
|
||||
uint32_t const kLengthToleranceM = 10;
|
||||
|
||||
struct State
|
||||
{
|
||||
State(Graph::Edge const & e, uint32_t const s) : m_edge(e), m_score(s) {}
|
||||
|
||||
bool operator>(State const & o) const { return make_tuple(m_score, m_edge) > make_tuple(o.m_score, o.m_edge); }
|
||||
|
||||
Graph::Edge m_edge;
|
||||
uint32_t m_score;
|
||||
};
|
||||
|
||||
ASSERT(from.HasRealPart() && to.HasRealPart(), ());
|
||||
|
||||
priority_queue<State, vector<State>, greater<State>> q;
|
||||
map<Graph::Edge, uint32_t> scores;
|
||||
map<Graph::Edge, Graph::Edge> links;
|
||||
|
||||
q.emplace(from, 0);
|
||||
scores[from] = 0;
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const state = q.top();
|
||||
q.pop();
|
||||
|
||||
auto const & u = state.m_edge;
|
||||
// TODO(mgsergio): Unify names: use either score or distance.
|
||||
auto const us = state.m_score;
|
||||
|
||||
if (us > maxPathLength + kLengthToleranceM)
|
||||
continue;
|
||||
|
||||
if (us > scores[u])
|
||||
continue;
|
||||
|
||||
if (u == to)
|
||||
{
|
||||
for (auto e = u; e != from; e = links[e])
|
||||
path.push_back(e);
|
||||
path.push_back(from);
|
||||
reverse(begin(path), end(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
m_graph.GetOutgoingEdges(u.GetEndJunction(), edges);
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
if (!ConformLfrcnp(e, lowestFrcToNextPoint, 2 /* frcThreshold */, m_infoGetter))
|
||||
continue;
|
||||
// TODO(mgsergio): Use frc to filter edges.
|
||||
|
||||
// Only start and/or end of the route can be fake.
|
||||
// Routes made only of fake edges are no used to us.
|
||||
if (u.IsFake() && e.IsFake())
|
||||
continue;
|
||||
|
||||
auto const it = scores.find(e);
|
||||
auto const eScore = us + EdgeLength(e);
|
||||
if (it == end(scores) || it->second > eScore)
|
||||
{
|
||||
scores[e] = eScore;
|
||||
links[e] = u;
|
||||
q.emplace(e, eScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PathsConnector::ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, double distanceToNextPoint,
|
||||
Graph::EdgeVector & resultPath)
|
||||
|
||||
{
|
||||
ASSERT(!to.empty(), ());
|
||||
|
||||
if (auto const skip = PathOverlappingLen(from, to))
|
||||
{
|
||||
if (skip == -1)
|
||||
return false;
|
||||
copy(begin(from), end(from), back_inserter(resultPath));
|
||||
copy(begin(to) + skip, end(to), back_inserter(resultPath));
|
||||
return true;
|
||||
}
|
||||
|
||||
ASSERT(from.back() != to.front(), ());
|
||||
|
||||
Graph::EdgeVector shortestPath;
|
||||
auto const found = FindShortestPath(from.back(), to.front(), lowestFrcToNextPoint, distanceToNextPoint, shortestPath);
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
// Skip the last edge from |from| because it already took its place at begin(shortestPath).
|
||||
copy(begin(from), prev(end(from)), back_inserter(resultPath));
|
||||
copy(begin(shortestPath), end(shortestPath), back_inserter(resultPath));
|
||||
// Skip the first edge from |to| because it already took its place at prev(end(shortestPath)).
|
||||
copy(next(begin(to)), end(to), back_inserter(resultPath));
|
||||
|
||||
return found;
|
||||
}
|
||||
} // namespace openlr
|
||||
36
tools/openlr/paths_connector.hpp
Normal file
36
tools/openlr/paths_connector.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class PathsConnector
|
||||
{
|
||||
public:
|
||||
PathsConnector(double pathLengthTolerance, Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat);
|
||||
|
||||
bool ConnectCandidates(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<Graph::EdgeVector>> const & lineCandidates,
|
||||
std::vector<Graph::EdgeVector> & resultPath);
|
||||
|
||||
private:
|
||||
bool FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, FunctionalRoadClass lowestFrcToNextPoint,
|
||||
uint32_t maxPathLength, Graph::EdgeVector & path);
|
||||
|
||||
bool ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, double distanceToNextPoint,
|
||||
Graph::EdgeVector & resultPath);
|
||||
|
||||
double m_pathLengthTolerance;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stat;
|
||||
};
|
||||
} // namespace openlr
|
||||
37
tools/openlr/road_info_getter.cpp
Normal file
37
tools/openlr/road_info_getter.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "indexer/classificator.hpp"
|
||||
#include "indexer/data_source.hpp"
|
||||
#include "indexer/feature.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
// RoadInfoGetter::RoadInfo ------------------------------------------------------------------------
|
||||
RoadInfoGetter::RoadInfo::RoadInfo(FeatureType & ft)
|
||||
: m_hwClass(ftypes::GetHighwayClass(feature::TypesHolder(ft)))
|
||||
, m_link(ftypes::IsLinkChecker::Instance()(ft))
|
||||
, m_oneWay(ftypes::IsOneWayChecker::Instance()(ft))
|
||||
, m_isRoundabout(ftypes::IsRoundAboutChecker::Instance()(ft))
|
||||
{}
|
||||
|
||||
// RoadInfoGetter ----------------------------------------------------------------------------------
|
||||
RoadInfoGetter::RoadInfoGetter(DataSource const & dataSource) : m_dataSource(dataSource) {}
|
||||
|
||||
RoadInfoGetter::RoadInfo RoadInfoGetter::Get(FeatureID const & fid)
|
||||
{
|
||||
auto it = m_cache.find(fid);
|
||||
if (it != end(m_cache))
|
||||
return it->second;
|
||||
|
||||
FeaturesLoaderGuard g(m_dataSource, fid.m_mwmId);
|
||||
auto ft = g.GetOriginalFeatureByIndex(fid.m_index);
|
||||
CHECK(ft, ());
|
||||
|
||||
RoadInfo info(*ft);
|
||||
it = m_cache.emplace(fid, info).first;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
} // namespace openlr
|
||||
41
tools/openlr/road_info_getter.hpp
Normal file
41
tools/openlr/road_info_getter.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
class Classificator;
|
||||
class DataSource;
|
||||
|
||||
namespace feature
|
||||
{
|
||||
class TypesHolder;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter final
|
||||
{
|
||||
public:
|
||||
struct RoadInfo
|
||||
{
|
||||
explicit RoadInfo(FeatureType & ft);
|
||||
|
||||
ftypes::HighwayClass m_hwClass = ftypes::HighwayClass::Undefined;
|
||||
bool m_link = false;
|
||||
bool m_oneWay = false;
|
||||
bool m_isRoundabout = false;
|
||||
};
|
||||
|
||||
explicit RoadInfoGetter(DataSource const & dataSource);
|
||||
|
||||
RoadInfo Get(FeatureID const & fid);
|
||||
|
||||
private:
|
||||
DataSource const & m_dataSource;
|
||||
std::map<FeatureID, RoadInfo> m_cache;
|
||||
};
|
||||
} // namespace openlr
|
||||
751
tools/openlr/router.cpp
Normal file
751
tools/openlr/router.cpp
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
#include "openlr/router.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
|
||||
#include "routing/features_road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
|
||||
using boost::make_transform_iterator;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int const kFRCThreshold = 2;
|
||||
size_t const kMaxRoadCandidates = 20;
|
||||
double const kDistanceAccuracyM = 1000;
|
||||
double const kEps = 1e-9;
|
||||
double const kBearingDist = 25;
|
||||
|
||||
int const kNumBuckets = 256;
|
||||
double const kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
uint32_t Bearing(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK_LESS_OR_EQUAL(angle, 360, ("Angle should be less than or equal to 360."));
|
||||
CHECK_GREATER_OR_EQUAL(angle, 0, ("Angle should be greater than or equal to 0"));
|
||||
return math::Clamp(angle / kAnglesInBucket, 0.0, 255.0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Router::Vertex::Score ---------------------------------------------------------------------------
|
||||
void Router::Vertex::Score::AddFakePenalty(double p, bool partOfReal)
|
||||
{
|
||||
m_penalty += (partOfReal ? kFakeCoeff : kTrueFakeCoeff) * p;
|
||||
}
|
||||
|
||||
void Router::Vertex::Score::AddBearingPenalty(int expected, int actual)
|
||||
{
|
||||
ASSERT_LESS(expected, kNumBuckets, ());
|
||||
ASSERT_GREATER_OR_EQUAL(expected, 0, ());
|
||||
|
||||
ASSERT_LESS(actual, kNumBuckets, ());
|
||||
ASSERT_GREATER_OR_EQUAL(actual, 0, ());
|
||||
|
||||
int const diff = abs(expected - actual);
|
||||
double angle = math::DegToRad(std::min(diff, kNumBuckets - diff) * kAnglesInBucket);
|
||||
m_penalty += kBearingErrorCoeff * angle * kBearingDist;
|
||||
}
|
||||
|
||||
bool Router::Vertex::Score::operator<(Score const & rhs) const
|
||||
{
|
||||
auto const ls = GetScore();
|
||||
auto const rs = rhs.GetScore();
|
||||
if (ls != rs)
|
||||
return ls < rs;
|
||||
|
||||
if (m_distance != rhs.m_distance)
|
||||
return m_distance < rhs.m_distance;
|
||||
return m_penalty < rhs.m_penalty;
|
||||
}
|
||||
|
||||
bool Router::Vertex::Score::operator==(Score const & rhs) const
|
||||
{
|
||||
return m_distance == rhs.m_distance && m_penalty == rhs.m_penalty;
|
||||
}
|
||||
|
||||
// Router::Vertex ----------------------------------------------------------------------------------
|
||||
Router::Vertex::Vertex(geometry::PointWithAltitude const & junction, geometry::PointWithAltitude const & stageStart,
|
||||
double stageStartDistance, size_t stage, bool bearingChecked)
|
||||
: m_junction(junction)
|
||||
, m_stageStart(stageStart)
|
||||
, m_stageStartDistance(stageStartDistance)
|
||||
, m_stage(stage)
|
||||
, m_bearingChecked(bearingChecked)
|
||||
{}
|
||||
|
||||
bool Router::Vertex::operator<(Vertex const & rhs) const
|
||||
{
|
||||
if (m_junction != rhs.m_junction)
|
||||
return m_junction < rhs.m_junction;
|
||||
if (m_stageStart != rhs.m_stageStart)
|
||||
return m_stageStart < rhs.m_stageStart;
|
||||
if (m_stageStartDistance != rhs.m_stageStartDistance)
|
||||
return m_stageStartDistance < rhs.m_stageStartDistance;
|
||||
if (m_stage != rhs.m_stage)
|
||||
return m_stage < rhs.m_stage;
|
||||
return m_bearingChecked < rhs.m_bearingChecked;
|
||||
}
|
||||
|
||||
bool Router::Vertex::operator==(Vertex const & rhs) const
|
||||
{
|
||||
return m_junction == rhs.m_junction && m_stageStart == rhs.m_stageStart &&
|
||||
m_stageStartDistance == rhs.m_stageStartDistance && m_stage == rhs.m_stage &&
|
||||
m_bearingChecked == rhs.m_bearingChecked;
|
||||
}
|
||||
|
||||
// Router::Edge ------------------------------------------------------------------------------------
|
||||
Router::Edge::Edge(Vertex const & u, Vertex const & v, routing::Edge const & raw, bool isSpecial)
|
||||
: m_u(u)
|
||||
, m_v(v)
|
||||
, m_raw(raw)
|
||||
, m_isSpecial(isSpecial)
|
||||
{}
|
||||
|
||||
// static
|
||||
Router::Edge Router::Edge::MakeNormal(Vertex const & u, Vertex const & v, routing::Edge const & raw)
|
||||
{
|
||||
return Edge(u, v, raw, false /* isSpecial */);
|
||||
}
|
||||
|
||||
// static
|
||||
Router::Edge Router::Edge::MakeSpecial(Vertex const & u, Vertex const & v)
|
||||
{
|
||||
return Edge(u, v, routing::Edge::MakeFake(u.m_junction, v.m_junction), true /* isSpecial */);
|
||||
}
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> Router::Edge::ToPair() const
|
||||
{
|
||||
auto const & e = m_raw;
|
||||
return std::make_pair(e.GetStartJunction().GetPoint(), e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> Router::Edge::ToPairRev() const
|
||||
{
|
||||
auto const & e = m_raw;
|
||||
return std::make_pair(e.GetEndJunction().GetPoint(), e.GetStartJunction().GetPoint());
|
||||
}
|
||||
|
||||
// Router::Router ----------------------------------------------------------------------------------
|
||||
Router::Router(routing::FeaturesRoadGraph & graph, RoadInfoGetter & roadInfoGetter)
|
||||
: m_graph(graph)
|
||||
, m_roadInfoGetter(roadInfoGetter)
|
||||
{}
|
||||
|
||||
bool Router::Go(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM, Path & path)
|
||||
{
|
||||
if (!Init(points, positiveOffsetM, negativeOffsetM))
|
||||
return false;
|
||||
return FindPath(path);
|
||||
}
|
||||
|
||||
bool Router::Init(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(points.size(), 2, ());
|
||||
|
||||
m_points = points;
|
||||
m_positiveOffsetM = positiveOffsetM;
|
||||
m_negativeOffsetM = negativeOffsetM;
|
||||
|
||||
m_graph.ResetFakes();
|
||||
|
||||
m_pivots.clear();
|
||||
for (size_t i = 1; i + 1 < m_points.size(); ++i)
|
||||
{
|
||||
m_pivots.emplace_back();
|
||||
auto & ps = m_pivots.back();
|
||||
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> vicinity;
|
||||
m_graph.FindClosestEdges(
|
||||
mercator::RectByCenterXYAndSizeInMeters(m_points[i].m_point, routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, vicinity);
|
||||
for (auto const & v : vicinity)
|
||||
{
|
||||
auto const & e = v.first;
|
||||
ps.push_back(e.GetStartJunction().GetPoint());
|
||||
ps.push_back(e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
if (ps.empty())
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pivots.push_back({m_points.back().m_point});
|
||||
CHECK_EQUAL(m_pivots.size() + 1, m_points.size(), ());
|
||||
|
||||
{
|
||||
m_sourceJunction = geometry::PointWithAltitude(m_points.front().m_point, 0 /* altitude */);
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> sourceVicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(m_sourceJunction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, sourceVicinity);
|
||||
m_graph.AddFakeEdges(m_sourceJunction, sourceVicinity);
|
||||
}
|
||||
|
||||
{
|
||||
m_targetJunction = geometry::PointWithAltitude(m_points.back().m_point, 0 /* altitude */);
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> targetVicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(m_targetJunction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, targetVicinity);
|
||||
m_graph.AddFakeEdges(m_targetJunction, targetVicinity);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Router::FindPath(Path & path)
|
||||
{
|
||||
using State = std::pair<Vertex::Score, Vertex>;
|
||||
std::priority_queue<State, std::vector<State>, std::greater<State>> queue;
|
||||
std::map<Vertex, Vertex::Score> scores;
|
||||
Links links;
|
||||
|
||||
auto const pushVertex =
|
||||
[&queue, &scores, &links](Vertex const & u, Vertex const & v, Vertex::Score const & vertexScore, Edge const & e)
|
||||
{
|
||||
if ((scores.count(v) == 0 || scores[v].GetScore() > vertexScore.GetScore() + kEps) && u != v)
|
||||
{
|
||||
scores[v] = vertexScore;
|
||||
links[v] = std::make_pair(u, e);
|
||||
queue.emplace(vertexScore, v);
|
||||
}
|
||||
};
|
||||
|
||||
Vertex const s(m_sourceJunction, m_sourceJunction, 0 /* stageStartDistance */, 0 /* stage */,
|
||||
false /* bearingChecked */);
|
||||
CHECK(!NeedToCheckBearing(s, 0 /* distance */), ());
|
||||
|
||||
scores[s] = Vertex::Score();
|
||||
queue.emplace(scores[s], s);
|
||||
|
||||
double const piS = GetPotential(s);
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
auto const p = queue.top();
|
||||
queue.pop();
|
||||
|
||||
Vertex::Score const & su = p.first;
|
||||
Vertex const & u = p.second;
|
||||
|
||||
if (su != scores[u])
|
||||
continue;
|
||||
|
||||
if (IsFinalVertex(u))
|
||||
{
|
||||
std::vector<Edge> edges;
|
||||
auto cur = u;
|
||||
while (cur != s)
|
||||
{
|
||||
auto const & p = links[cur];
|
||||
|
||||
edges.push_back(p.second);
|
||||
cur = p.first;
|
||||
}
|
||||
reverse(edges.begin(), edges.end());
|
||||
return ReconstructPath(edges, path);
|
||||
}
|
||||
|
||||
size_t const stage = u.m_stage;
|
||||
double const distanceToNextPointM = m_points[stage].m_distanceToNextPointM;
|
||||
double const piU = GetPotential(u);
|
||||
double const ud = su.GetDistance() + piS - piU; // real distance to u
|
||||
|
||||
CHECK_LESS(stage, m_pivots.size(), ());
|
||||
|
||||
// max(kDistanceAccuracyM, m_distanceToNextPointM) is added here
|
||||
// to throw out quite long paths.
|
||||
if (ud > u.m_stageStartDistance + distanceToNextPointM + std::max(kDistanceAccuracyM, distanceToNextPointM))
|
||||
continue;
|
||||
|
||||
if (NearNextStage(u, piU) && !u.m_bearingChecked)
|
||||
{
|
||||
Vertex v = u;
|
||||
|
||||
Vertex::Score sv = su;
|
||||
if (u.m_junction != u.m_stageStart)
|
||||
{
|
||||
int const expected = m_points[stage].m_bearing;
|
||||
int const actual = Bearing(u.m_stageStart.GetPoint(), u.m_junction.GetPoint());
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
v.m_bearingChecked = true;
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeSpecial(u, v));
|
||||
}
|
||||
|
||||
if (MayMoveToNextStage(u, piU))
|
||||
{
|
||||
Vertex v(u.m_junction, u.m_junction, ud /* stageStartDistance */, stage + 1, false /* bearingChecked */);
|
||||
double const piV = GetPotential(v);
|
||||
|
||||
Vertex::Score sv = su;
|
||||
sv.AddDistance(std::max(piV - piU, 0.0));
|
||||
sv.AddIntermediateErrorPenalty(mercator::DistanceOnEarth(v.m_junction.GetPoint(), m_points[v.m_stage].m_point));
|
||||
|
||||
if (IsFinalVertex(v))
|
||||
{
|
||||
int const expected = m_points.back().m_bearing;
|
||||
int const actual = GetReverseBearing(u, links);
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeSpecial(u, v));
|
||||
}
|
||||
|
||||
ForEachEdge(u, true /* outgoing */, m_points[stage].m_lfrcnp, [&](routing::Edge const & edge)
|
||||
{
|
||||
Vertex v = u;
|
||||
v.m_junction = edge.GetEndJunction();
|
||||
|
||||
double const piV = GetPotential(v);
|
||||
|
||||
Vertex::Score sv = su;
|
||||
double const w = GetWeight(edge);
|
||||
sv.AddDistance(std::max(w + piV - piU, 0.0));
|
||||
|
||||
double const vd = ud + w; // real distance to v
|
||||
if (NeedToCheckBearing(v, vd))
|
||||
{
|
||||
CHECK(!NeedToCheckBearing(u, ud), ());
|
||||
|
||||
double const delta = vd - v.m_stageStartDistance - kBearingDist;
|
||||
auto const p = PointAtSegment(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(), delta);
|
||||
if (v.m_stageStart.GetPoint() != p)
|
||||
{
|
||||
int const expected = m_points[stage].m_bearing;
|
||||
int const actual = Bearing(v.m_stageStart.GetPoint(), p);
|
||||
sv.AddBearingPenalty(expected, actual);
|
||||
}
|
||||
v.m_bearingChecked = true;
|
||||
}
|
||||
|
||||
if (vd > v.m_stageStartDistance + distanceToNextPointM)
|
||||
sv.AddDistanceErrorPenalty(std::min(vd - v.m_stageStartDistance - distanceToNextPointM, w));
|
||||
|
||||
if (edge.IsFake())
|
||||
sv.AddFakePenalty(w, edge.HasRealPart());
|
||||
|
||||
pushVertex(u, v, sv, Edge::MakeNormal(u, v, edge));
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Router::NeedToCheckBearing(Vertex const & u, double distanceM) const
|
||||
{
|
||||
if (IsFinalVertex(u) || u.m_bearingChecked)
|
||||
return false;
|
||||
return distanceM >= u.m_stageStartDistance + kBearingDist;
|
||||
}
|
||||
|
||||
double Router::GetPotential(Vertex const & u) const
|
||||
{
|
||||
if (IsFinalVertex(u))
|
||||
return 0.0;
|
||||
|
||||
CHECK_LESS(u.m_stage, m_pivots.size(), ());
|
||||
|
||||
auto const & pivots = m_pivots[u.m_stage];
|
||||
CHECK(!pivots.empty(), ("Empty list of pivots"));
|
||||
|
||||
double potential = std::numeric_limits<double>::max();
|
||||
|
||||
auto const & point = u.m_junction.GetPoint();
|
||||
for (auto const & pivot : pivots)
|
||||
potential = std::min(potential, mercator::DistanceOnEarth(pivot, point));
|
||||
return potential;
|
||||
}
|
||||
|
||||
bool Router::NearNextStage(Vertex const & u, double pi) const
|
||||
{
|
||||
return u.m_stage < m_pivots.size() && pi < kEps;
|
||||
}
|
||||
|
||||
bool Router::MayMoveToNextStage(Vertex const & u, double pi) const
|
||||
{
|
||||
return NearNextStage(u, pi) && u.m_bearingChecked;
|
||||
}
|
||||
|
||||
uint32_t Router::GetReverseBearing(Vertex const & u, Links const & links) const
|
||||
{
|
||||
m2::PointD const a = u.m_junction.GetPoint();
|
||||
m2::PointD b = m2::PointD::Zero();
|
||||
|
||||
Vertex curr = u;
|
||||
double passed = 0;
|
||||
bool found = false;
|
||||
while (true)
|
||||
{
|
||||
auto const it = links.find(curr);
|
||||
if (it == links.end())
|
||||
break;
|
||||
|
||||
auto const & p = it->second;
|
||||
auto const & prev = p.first;
|
||||
auto const & edge = p.second.m_raw;
|
||||
|
||||
if (prev.m_stage != curr.m_stage)
|
||||
break;
|
||||
|
||||
double const weight = GetWeight(edge);
|
||||
|
||||
if (passed + weight >= kBearingDist)
|
||||
{
|
||||
double const delta = kBearingDist - passed;
|
||||
b = PointAtSegment(edge.GetEndJunction().GetPoint(), edge.GetStartJunction().GetPoint(), delta);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
passed += weight;
|
||||
curr = prev;
|
||||
}
|
||||
if (!found)
|
||||
b = curr.m_junction.GetPoint();
|
||||
return Bearing(a, b);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn)
|
||||
{
|
||||
routing::IRoadGraph::EdgeListT edges;
|
||||
if (outgoing)
|
||||
GetOutgoingEdges(u.m_junction, edges);
|
||||
else
|
||||
GetIngoingEdges(u.m_junction, edges);
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
if (!ConformLfrcnp(edge, restriction, kFRCThreshold, m_roadInfoGetter))
|
||||
continue;
|
||||
fn(edge);
|
||||
}
|
||||
}
|
||||
|
||||
void Router::GetOutgoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
GetEdges(u, &routing::IRoadGraph::GetRegularOutgoingEdges, &routing::IRoadGraph::GetFakeOutgoingEdges,
|
||||
m_outgoingCache, edges);
|
||||
}
|
||||
|
||||
void Router::GetIngoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
GetEdges(u, &routing::IRoadGraph::GetRegularIngoingEdges, &routing::IRoadGraph::GetFakeIngoingEdges, m_ingoingCache,
|
||||
edges);
|
||||
}
|
||||
|
||||
void Router::GetEdges(geometry::PointWithAltitude const & u, RoadGraphEdgesGetter getRegular,
|
||||
RoadGraphEdgesGetter getFake, EdgeCacheT & cache, routing::IRoadGraph::EdgeListT & edges)
|
||||
{
|
||||
auto const it = cache.find(u);
|
||||
if (it == cache.end())
|
||||
{
|
||||
auto & es = cache[u];
|
||||
(m_graph.*getRegular)(u, es);
|
||||
edges.append(es.begin(), es.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const & es = it->second;
|
||||
edges.append(es.begin(), es.end());
|
||||
}
|
||||
(m_graph.*getFake)(u, edges);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachNonFakeEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn)
|
||||
{
|
||||
ForEachEdge(u, outgoing, restriction, [&fn](routing::Edge const & edge)
|
||||
{
|
||||
if (!edge.IsFake())
|
||||
fn(edge);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void Router::ForEachNonFakeClosestEdge(Vertex const & u, FunctionalRoadClass const restriction, Fn && fn)
|
||||
{
|
||||
std::vector<std::pair<routing::Edge, geometry::PointWithAltitude>> vicinity;
|
||||
m_graph.FindClosestEdges(mercator::RectByCenterXYAndSizeInMeters(u.m_junction.GetPoint(),
|
||||
routing::FeaturesRoadGraph::kClosestEdgesRadiusM),
|
||||
kMaxRoadCandidates, vicinity);
|
||||
|
||||
for (auto const & p : vicinity)
|
||||
{
|
||||
auto const & edge = p.first;
|
||||
if (edge.IsFake())
|
||||
continue;
|
||||
if (!ConformLfrcnp(edge, restriction, kFRCThreshold, m_roadInfoGetter))
|
||||
continue;
|
||||
fn(edge);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
size_t Router::FindPrefixLengthToConsume(It b, It const e, double lengthM)
|
||||
{
|
||||
size_t n = 0;
|
||||
while (b != e && lengthM > 0.0)
|
||||
{
|
||||
// Need p here to prolongate lifetime of (*b) if iterator
|
||||
// dereferencing returns a temprorary object instead of a
|
||||
// reference.
|
||||
auto const & p = *b;
|
||||
auto const & u = p.first;
|
||||
auto const & v = p.second;
|
||||
double const len = mercator::DistanceOnEarth(u, v);
|
||||
if (2 * lengthM < len)
|
||||
break;
|
||||
|
||||
lengthM -= len;
|
||||
++n;
|
||||
++b;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
double Router::GetCoverage(m2::PointD const & u, m2::PointD const & v, It b, It e)
|
||||
{
|
||||
double const kEps = 1e-5;
|
||||
double const kLengthThresholdM = 1;
|
||||
|
||||
m2::PointD const uv = v - u;
|
||||
double const sqlen = u.SquaredLength(v);
|
||||
|
||||
if (mercator::DistanceOnEarth(u, v) < kLengthThresholdM)
|
||||
return 0;
|
||||
|
||||
std::vector<std::pair<double, double>> covs;
|
||||
for (; b != e; ++b)
|
||||
{
|
||||
auto const s = b->m_u.m_junction.GetPoint();
|
||||
auto const t = b->m_v.m_junction.GetPoint();
|
||||
if (!m2::IsPointOnSegmentEps(s, u, v, kEps) || !m2::IsPointOnSegmentEps(t, u, v, kEps))
|
||||
continue;
|
||||
|
||||
if (DotProduct(uv, t - s) < -kEps)
|
||||
continue;
|
||||
|
||||
double const sp = DotProduct(uv, s - u) / sqlen;
|
||||
double const tp = DotProduct(uv, t - u) / sqlen;
|
||||
|
||||
double const start = math::Clamp(std::min(sp, tp), 0.0, 1.0);
|
||||
double const finish = math::Clamp(std::max(sp, tp), 0.0, 1.0);
|
||||
covs.emplace_back(start, finish);
|
||||
}
|
||||
|
||||
sort(covs.begin(), covs.end());
|
||||
|
||||
double coverage = 0;
|
||||
|
||||
size_t i = 0;
|
||||
while (i != covs.size())
|
||||
{
|
||||
size_t j = i;
|
||||
|
||||
double const first = covs[i].first;
|
||||
double last = covs[i].second;
|
||||
while (j != covs.size() && covs[j].first <= last)
|
||||
{
|
||||
last = std::max(last, covs[j].second);
|
||||
++j;
|
||||
}
|
||||
|
||||
coverage += last - first;
|
||||
i = j;
|
||||
}
|
||||
|
||||
CHECK_LESS_OR_EQUAL(coverage, 1.0 + kEps, ());
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
double Router::GetMatchingScore(m2::PointD const & u, m2::PointD const & v, It b, It e)
|
||||
{
|
||||
double const kEps = 1e-5;
|
||||
|
||||
double const len = mercator::DistanceOnEarth(u, v);
|
||||
|
||||
m2::PointD const uv = v - u;
|
||||
|
||||
double cov = 0;
|
||||
for (; b != e; ++b)
|
||||
{
|
||||
// Need p here to prolongate lifetime of (*b) if iterator
|
||||
// dereferencing returns a temprorary object instead of a
|
||||
// reference.
|
||||
auto const & p = *b;
|
||||
auto const & s = p.first;
|
||||
auto const & t = p.second;
|
||||
if (!m2::IsPointOnSegmentEps(s, u, v, kEps) || !m2::IsPointOnSegmentEps(t, u, v, kEps))
|
||||
break;
|
||||
|
||||
m2::PointD const st = t - s;
|
||||
if (DotProduct(uv, st) < -kEps)
|
||||
break;
|
||||
|
||||
cov += mercator::DistanceOnEarth(s, t);
|
||||
}
|
||||
|
||||
return len == 0 ? 0 : math::Clamp(cov / len, 0.0, 1.0);
|
||||
}
|
||||
|
||||
template <typename It, typename Fn>
|
||||
void Router::ForStagePrefix(It b, It e, size_t stage, Fn && fn)
|
||||
{
|
||||
while (b != e && b->m_raw.IsFake() && b->m_u.m_stage == stage && b->m_v.m_stage == stage)
|
||||
++b;
|
||||
if (b != e && !b->m_raw.IsFake())
|
||||
fn(b);
|
||||
}
|
||||
|
||||
bool Router::ReconstructPath(std::vector<Edge> & edges, Path & path)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(m_points.size(), 2, ());
|
||||
|
||||
using EdgeIt = std::vector<Edge>::iterator;
|
||||
using EdgeItRev = std::vector<Edge>::reverse_iterator;
|
||||
|
||||
double const kFakeCoverageThreshold = 0.5;
|
||||
|
||||
base::EraseIf(edges, [](auto && e) { return e.IsSpecial(); });
|
||||
|
||||
{
|
||||
auto toPair = [](auto && e) { return e.ToPair(); };
|
||||
size_t const n = FindPrefixLengthToConsume(make_transform_iterator(edges.begin(), toPair),
|
||||
make_transform_iterator(edges.end(), toPair), m_positiveOffsetM);
|
||||
CHECK_LESS_OR_EQUAL(n, edges.size(), ());
|
||||
edges.erase(edges.begin(), edges.begin() + n);
|
||||
}
|
||||
|
||||
{
|
||||
auto toPairRev = [](auto && e) { return e.ToPairRev(); };
|
||||
size_t const n = FindPrefixLengthToConsume(make_transform_iterator(edges.rbegin(), toPairRev),
|
||||
make_transform_iterator(edges.rend(), toPairRev), m_negativeOffsetM);
|
||||
CHECK_LESS_OR_EQUAL(n, edges.size(), ());
|
||||
edges.erase(edges.begin() + edges.size() - n, edges.end());
|
||||
}
|
||||
|
||||
double frontEdgeScore = -1.0;
|
||||
routing::Edge frontEdge;
|
||||
ForStagePrefix(edges.begin(), edges.end(), 0, [&](EdgeIt e)
|
||||
{
|
||||
ForEachNonFakeEdge(e->m_u, false /* outgoing */, m_points[0].m_lfrcnp, [&](routing::Edge const & edge)
|
||||
{
|
||||
auto toPairRev = [](auto && e) { return e.ToPairRev(); };
|
||||
double const score = GetMatchingScore(edge.GetEndJunction().GetPoint(), edge.GetStartJunction().GetPoint(),
|
||||
make_transform_iterator(EdgeItRev(e), toPairRev),
|
||||
make_transform_iterator(edges.rend(), toPairRev));
|
||||
if (score > frontEdgeScore)
|
||||
{
|
||||
frontEdgeScore = score;
|
||||
frontEdge = edge.GetReverseEdge();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
double backEdgeScore = -1.0;
|
||||
routing::Edge backEdge;
|
||||
ForStagePrefix(edges.rbegin(), edges.rend(), m_points.size() - 2 /* stage */, [&](EdgeItRev e)
|
||||
{
|
||||
ForEachNonFakeEdge(e->m_v, true /* outgoing */, m_points[m_points.size() - 2].m_lfrcnp,
|
||||
[&](routing::Edge const & edge)
|
||||
{
|
||||
auto toPair = [](auto && e) { return e.ToPair(); };
|
||||
double const score =
|
||||
GetMatchingScore(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(),
|
||||
make_transform_iterator(e.base(), toPair), make_transform_iterator(edges.end(), toPair));
|
||||
if (score > backEdgeScore)
|
||||
{
|
||||
backEdgeScore = score;
|
||||
backEdge = edge;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
path.clear();
|
||||
for (auto const & e : edges)
|
||||
if (!e.IsFake())
|
||||
path.push_back(e.m_raw);
|
||||
|
||||
if (frontEdgeScore >= kFakeCoverageThreshold && !path.empty() && path.front() != frontEdge)
|
||||
path.insert(path.begin(), frontEdge);
|
||||
|
||||
if (backEdgeScore >= kFakeCoverageThreshold && !path.empty() && path.back() != backEdge)
|
||||
path.insert(path.end(), backEdge);
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
// This is the case for routes from fake edges only.
|
||||
FindSingleEdgeApproximation(edges, path);
|
||||
}
|
||||
|
||||
return !path.empty();
|
||||
}
|
||||
|
||||
void Router::FindSingleEdgeApproximation(std::vector<Edge> const & edges, Path & path)
|
||||
{
|
||||
double const kCoverageThreshold = 0.5;
|
||||
|
||||
CHECK(all_of(edges.begin(), edges.end(), [](auto && e) { return e.IsFake(); }), ());
|
||||
|
||||
double expectedLength = 0;
|
||||
for (auto const & edge : edges)
|
||||
expectedLength += GetWeight(edge);
|
||||
|
||||
if (expectedLength < kEps)
|
||||
return;
|
||||
|
||||
double bestCoverage = -1.0;
|
||||
routing::Edge bestEdge;
|
||||
|
||||
auto checkEdge = [&](routing::Edge const & edge)
|
||||
{
|
||||
double const weight = GetWeight(edge);
|
||||
double const fraction =
|
||||
GetCoverage(edge.GetStartJunction().GetPoint(), edge.GetEndJunction().GetPoint(), edges.begin(), edges.end());
|
||||
double const coverage = weight * fraction;
|
||||
if (coverage >= bestCoverage)
|
||||
{
|
||||
bestCoverage = coverage;
|
||||
bestEdge = edge;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
auto const & u = edge.m_u;
|
||||
auto const & v = edge.m_v;
|
||||
CHECK_EQUAL(u.m_stage, v.m_stage, ());
|
||||
auto const stage = u.m_stage;
|
||||
|
||||
ForEachNonFakeClosestEdge(u, m_points[stage].m_lfrcnp, checkEdge);
|
||||
ForEachNonFakeClosestEdge(v, m_points[stage].m_lfrcnp, checkEdge);
|
||||
}
|
||||
|
||||
if (bestCoverage >= expectedLength * kCoverageThreshold)
|
||||
path = {bestEdge};
|
||||
}
|
||||
} // namespace openlr
|
||||
222
tools/openlr/router.hpp
Normal file
222
tools/openlr/router.hpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/decoded_path.hpp"
|
||||
#include "openlr/way_point.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
class FeaturesRoadGraph;
|
||||
}
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class RoadInfoGetter;
|
||||
|
||||
class Router final
|
||||
{
|
||||
public:
|
||||
Router(routing::FeaturesRoadGraph & graph, RoadInfoGetter & roadInfoGetter);
|
||||
|
||||
bool Go(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM, Path & path);
|
||||
|
||||
private:
|
||||
struct Vertex final
|
||||
{
|
||||
class Score final
|
||||
{
|
||||
public:
|
||||
// A weight for total length of true fake edges.
|
||||
static int const kTrueFakeCoeff = 10;
|
||||
|
||||
// A weight for total length of fake edges that are parts of some
|
||||
// real edges.
|
||||
static constexpr double kFakeCoeff = 0.001;
|
||||
|
||||
// A weight for passing too far from pivot points.
|
||||
static int const kIntermediateErrorCoeff = 3;
|
||||
|
||||
// A weight for excess of distance limit.
|
||||
static int const kDistanceErrorCoeff = 3;
|
||||
|
||||
// A weight for deviation from bearing.
|
||||
static int const kBearingErrorCoeff = 5;
|
||||
|
||||
void AddDistance(double p) { m_distance += p; }
|
||||
void AddFakePenalty(double p, bool partOfReal);
|
||||
void AddIntermediateErrorPenalty(double p) { m_penalty += kIntermediateErrorCoeff * p; }
|
||||
void AddDistanceErrorPenalty(double p) { m_penalty += kDistanceErrorCoeff * p; }
|
||||
void AddBearingPenalty(int expected, int actual);
|
||||
|
||||
double GetDistance() const { return m_distance; }
|
||||
double GetPenalty() const { return m_penalty; }
|
||||
double GetScore() const { return m_distance + m_penalty; }
|
||||
|
||||
bool operator<(Score const & rhs) const;
|
||||
bool operator>(Score const & rhs) const { return rhs < *this; }
|
||||
bool operator==(Score const & rhs) const;
|
||||
bool operator!=(Score const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
private:
|
||||
// Reduced length of path in meters.
|
||||
double m_distance = 0.0;
|
||||
|
||||
double m_penalty = 0.0;
|
||||
};
|
||||
|
||||
Vertex() = default;
|
||||
Vertex(geometry::PointWithAltitude const & junction, geometry::PointWithAltitude const & stageStart,
|
||||
double stageStartDistance, size_t stage, bool bearingChecked);
|
||||
|
||||
bool operator<(Vertex const & rhs) const;
|
||||
bool operator==(Vertex const & rhs) const;
|
||||
bool operator!=(Vertex const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
m2::PointD GetPoint() const { return m_junction.GetPoint(); }
|
||||
|
||||
geometry::PointWithAltitude m_junction;
|
||||
geometry::PointWithAltitude m_stageStart;
|
||||
double m_stageStartDistance = 0.0;
|
||||
size_t m_stage = 0;
|
||||
bool m_bearingChecked = false;
|
||||
};
|
||||
|
||||
friend std::string DebugPrint(Vertex const & u)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Vertex [ ";
|
||||
os << "junction: " << DebugPrint(u.m_junction) << ", ";
|
||||
os << "stageStart: " << DebugPrint(u.m_stageStart) << ", ";
|
||||
os << "stageStartDistance: " << u.m_stageStartDistance << ", ";
|
||||
os << "stage: " << u.m_stage << ", ";
|
||||
os << "bearingChecked: " << u.m_bearingChecked;
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
struct Edge final
|
||||
{
|
||||
Edge() = default;
|
||||
Edge(Vertex const & u, Vertex const & v, routing::Edge const & raw, bool isSpecial);
|
||||
|
||||
static Edge MakeNormal(Vertex const & u, Vertex const & v, routing::Edge const & raw);
|
||||
static Edge MakeSpecial(Vertex const & u, Vertex const & v);
|
||||
|
||||
bool IsFake() const { return m_raw.IsFake(); }
|
||||
bool IsSpecial() const { return m_isSpecial; }
|
||||
|
||||
std::pair<m2::PointD, m2::PointD> ToPair() const;
|
||||
std::pair<m2::PointD, m2::PointD> ToPairRev() const;
|
||||
|
||||
Vertex m_u;
|
||||
Vertex m_v;
|
||||
routing::Edge m_raw;
|
||||
bool m_isSpecial = false;
|
||||
};
|
||||
|
||||
friend std::string DebugPrint(Edge const & edge)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Edge [ ";
|
||||
os << "u: " << DebugPrint(edge.m_u) << ", ";
|
||||
os << "v: " << DebugPrint(edge.m_v) << ", ";
|
||||
os << "raw: " << DebugPrint(edge.m_raw) << ", ";
|
||||
os << "isSpecial: " << edge.m_isSpecial;
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
using Links = std::map<Vertex, std::pair<Vertex, Edge>>;
|
||||
|
||||
using RoadGraphEdgesGetter = void (routing::IRoadGraph::*)(geometry::PointWithAltitude const & junction,
|
||||
routing::IRoadGraph::EdgeListT & edges) const;
|
||||
|
||||
bool Init(std::vector<WayPoint> const & points, double positiveOffsetM, double negativeOffsetM);
|
||||
bool FindPath(Path & path);
|
||||
|
||||
// Returns true if the bearing should be checked for |u|, if the
|
||||
// real passed distance from the source vertex is |distanceM|.
|
||||
bool NeedToCheckBearing(Vertex const & u, double distanceM) const;
|
||||
|
||||
double GetPotential(Vertex const & u) const;
|
||||
|
||||
// Returns true if |u| is located near portal to the next stage.
|
||||
// |pi| is the potential of |u|.
|
||||
bool NearNextStage(Vertex const & u, double pi) const;
|
||||
|
||||
// Returns true if it's possible to move to the next stage from |u|.
|
||||
// |pi| is the potential of |u|.
|
||||
bool MayMoveToNextStage(Vertex const & u, double pi) const;
|
||||
|
||||
// Returns true if |u| is a final vertex and the router may stop now.
|
||||
bool IsFinalVertex(Vertex const & u) const { return u.m_stage == m_pivots.size(); }
|
||||
|
||||
double GetWeight(routing::Edge const & e) const
|
||||
{
|
||||
return mercator::DistanceOnEarth(e.GetStartJunction().GetPoint(), e.GetEndJunction().GetPoint());
|
||||
}
|
||||
|
||||
double GetWeight(Edge const & e) const { return GetWeight(e.m_raw); }
|
||||
|
||||
uint32_t GetReverseBearing(Vertex const & u, Links const & links) const;
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn);
|
||||
|
||||
void GetOutgoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges);
|
||||
void GetIngoingEdges(geometry::PointWithAltitude const & u, routing::IRoadGraph::EdgeListT & edges);
|
||||
|
||||
using EdgeCacheT = std::map<geometry::PointWithAltitude, routing::IRoadGraph::EdgeListT>;
|
||||
void GetEdges(geometry::PointWithAltitude const & u, RoadGraphEdgesGetter getRegular, RoadGraphEdgesGetter getFake,
|
||||
EdgeCacheT & cache, routing::IRoadGraph::EdgeListT & edges);
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachNonFakeEdge(Vertex const & u, bool outgoing, FunctionalRoadClass restriction, Fn && fn);
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachNonFakeClosestEdge(Vertex const & u, FunctionalRoadClass const restriction, Fn && fn);
|
||||
|
||||
template <typename It>
|
||||
size_t FindPrefixLengthToConsume(It b, It const e, double lengthM);
|
||||
|
||||
// Finds all edges that are on (u, v) and have the same direction as
|
||||
// (u, v). Then, computes the fraction of the union of these edges
|
||||
// to the total length of (u, v).
|
||||
template <typename It>
|
||||
double GetCoverage(m2::PointD const & u, m2::PointD const & v, It b, It e);
|
||||
|
||||
// Finds the longest prefix of [b, e) that covers edge (u, v).
|
||||
// Returns the fraction of the coverage to the length of the (u, v).
|
||||
template <typename It>
|
||||
double GetMatchingScore(m2::PointD const & u, m2::PointD const & v, It b, It e);
|
||||
|
||||
// Finds the longest prefix of fake edges of [b, e) that have the
|
||||
// same stage as |stage|. If the prefix exists, passes its bounding
|
||||
// iterator to |fn|.
|
||||
template <typename It, typename Fn>
|
||||
void ForStagePrefix(It b, It e, size_t stage, Fn && fn);
|
||||
|
||||
bool ReconstructPath(std::vector<Edge> & edges, Path & path);
|
||||
|
||||
void FindSingleEdgeApproximation(std::vector<Edge> const & edges, Path & path);
|
||||
|
||||
routing::FeaturesRoadGraph & m_graph;
|
||||
EdgeCacheT m_outgoingCache, m_ingoingCache;
|
||||
RoadInfoGetter & m_roadInfoGetter;
|
||||
|
||||
std::vector<WayPoint> m_points;
|
||||
double m_positiveOffsetM = 0.0;
|
||||
double m_negativeOffsetM = 0.0;
|
||||
std::vector<std::vector<m2::PointD>> m_pivots;
|
||||
geometry::PointWithAltitude m_sourceJunction;
|
||||
geometry::PointWithAltitude m_targetJunction;
|
||||
};
|
||||
} // namespace openlr
|
||||
325
tools/openlr/score_candidate_paths_getter.cpp
Normal file
325
tools/openlr/score_candidate_paths_getter.cpp
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#include "openlr/score_candidate_paths_getter.hpp"
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace scpg
|
||||
{
|
||||
int constexpr kNumBuckets = 256;
|
||||
double constexpr kAnglesInBucket = 360.0 / kNumBuckets;
|
||||
|
||||
double ToAngleInDeg(uint32_t angleInBuckets)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(angleInBuckets, 255, ());
|
||||
return math::Clamp(kAnglesInBucket * static_cast<double>(angleInBuckets), 0.0, 360.0);
|
||||
}
|
||||
|
||||
uint32_t BearingInDeg(m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
auto const angle = location::AngleToBearing(math::RadToDeg(ang::AngleTo(a, b)));
|
||||
CHECK(0.0 <= angle && angle <= 360.0, (angle));
|
||||
return angle;
|
||||
}
|
||||
|
||||
double DifferenceInDeg(double a1, double a2)
|
||||
{
|
||||
auto const diff = 180.0 - abs(abs(a1 - a2) - 180.0);
|
||||
CHECK(0.0 <= diff && diff <= 180.0, (diff));
|
||||
return diff;
|
||||
}
|
||||
|
||||
void EdgeSortUniqueByStartAndEndPoints(Graph::EdgeListT & edges)
|
||||
{
|
||||
base::SortUnique(edges,
|
||||
[](Edge const & e1, Edge const & e2)
|
||||
{
|
||||
if (e1.GetStartPoint() != e2.GetStartPoint())
|
||||
return e1.GetStartPoint() < e2.GetStartPoint();
|
||||
return e1.GetEndPoint() < e2.GetEndPoint();
|
||||
}, [](Edge const & e1, Edge const & e2)
|
||||
{ return e1.GetStartPoint() == e2.GetStartPoint() && e1.GetEndPoint() == e2.GetEndPoint(); });
|
||||
}
|
||||
} // namespace scpg
|
||||
|
||||
// ScoreCandidatePathsGetter::Link ----------------------------------------------------------------------
|
||||
Graph::Edge ScoreCandidatePathsGetter::Link::GetStartEdge() const
|
||||
{
|
||||
auto * start = this;
|
||||
while (start->m_parent)
|
||||
start = start->m_parent.get();
|
||||
|
||||
return start->m_edge;
|
||||
}
|
||||
|
||||
bool ScoreCandidatePathsGetter::Link::IsJunctionInPath(geometry::PointWithAltitude const & j) const
|
||||
{
|
||||
for (auto * l = this; l; l = l->m_parent.get())
|
||||
{
|
||||
if (l->m_edge.GetEndJunction().GetPoint() == j.GetPoint())
|
||||
{
|
||||
LOG(LDEBUG, ("A loop detected, skipping..."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ScoreCandidatePathsGetter ----------------------------------------------------------------------------
|
||||
bool ScoreCandidatePathsGetter::GetLineCandidatesForPoints(vector<LocationReferencePoint> const & points,
|
||||
LinearSegmentSource source,
|
||||
vector<ScorePathVec> & lineCandidates)
|
||||
{
|
||||
CHECK_GREATER(points.size(), 1, ());
|
||||
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
if (i != points.size() - 1 && points[i].m_distanceToNextPoint == 0)
|
||||
{
|
||||
LOG(LINFO, ("Distance to next point is zero. Skipping the whole segment"));
|
||||
++m_stats.m_zeroDistToNextPointCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
lineCandidates.emplace_back();
|
||||
auto const isLastPoint = i == points.size() - 1;
|
||||
double const distanceToNextPointM = (isLastPoint ? points[i - 1] : points[i]).m_distanceToNextPoint;
|
||||
|
||||
ScoreEdgeVec edgesCandidates;
|
||||
m_pointsGetter.GetEdgeCandidates(mercator::FromLatLon(points[i].m_latLon), isLastPoint, edgesCandidates);
|
||||
|
||||
GetLineCandidates(points[i], source, isLastPoint, distanceToNextPointM, edgesCandidates, lineCandidates.back());
|
||||
|
||||
if (lineCandidates.back().empty())
|
||||
{
|
||||
LOG(LINFO, ("No candidate lines found for point", points[i].m_latLon, "Giving up"));
|
||||
++m_stats.m_noCandidateFound;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQUAL(lineCandidates.size(), points.size(), ());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetAllSuitablePaths(ScoreEdgeVec const & startLines, LinearSegmentSource source,
|
||||
bool isLastPoint, double bearDistM,
|
||||
FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, vector<shared_ptr<Link>> & allPaths)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
|
||||
queue<shared_ptr<Link>> q;
|
||||
|
||||
for (auto const & e : startLines)
|
||||
{
|
||||
Score roadScore = 0; // Score based on functional road class and form of way.
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!PassesRestrictionV3(e.m_edge, functionalRoadClass, formOfWay, m_infoGetter, roadScore))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
q.push(make_shared<Link>(nullptr /* parent */, e.m_edge, 0 /* distanceM */, e.m_score, roadScore));
|
||||
}
|
||||
|
||||
// Filling |allPaths| staring from |startLines| which have passed functional road class
|
||||
// and form of way restrictions. All paths in |allPaths| are shorter then |bearDistM| plus
|
||||
// one segment length.
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const u = q.front();
|
||||
q.pop();
|
||||
|
||||
auto const & currentEdge = u->m_edge;
|
||||
auto const currentEdgeLen = EdgeLength(currentEdge);
|
||||
|
||||
// The path from the start of the openlr segment to the finish to the openlr segment should be
|
||||
// much shorter then the distance of any connection of openlr segment.
|
||||
// This condition should be checked because otherwise in rare case |q| may be overfilled.
|
||||
if (u->m_distanceM > distanceToNextPointM)
|
||||
continue;
|
||||
|
||||
if (u->m_distanceM + currentEdgeLen >= bearDistM)
|
||||
{
|
||||
allPaths.emplace_back(std::move(u));
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK_LESS(u->m_distanceM + currentEdgeLen, bearDistM, ());
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(currentEdge.GetEndJunction(), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(currentEdge.GetStartJunction(), edges);
|
||||
|
||||
// It's possible that road edges are duplicated a lot of times. It's a error but
|
||||
// a mapper may do that. To prevent a combinatorial explosion while matching
|
||||
// duplicated edges should be removed.
|
||||
scpg::EdgeSortUniqueByStartAndEndPoints(edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
{
|
||||
CHECK(!e.IsFake(), ());
|
||||
|
||||
if (EdgesAreAlmostEqual(e.GetReverseEdge(), currentEdge))
|
||||
continue;
|
||||
|
||||
CHECK(currentEdge.HasRealPart(), ());
|
||||
|
||||
Score roadScore = 0;
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!PassesRestrictionV3(e, functionalRoadClass, formOfWay, m_infoGetter, roadScore))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (u->IsJunctionInPath(e.GetEndJunction()))
|
||||
continue;
|
||||
|
||||
// Road score for a path is minimum value of score of segments based on functional road class
|
||||
// of the segments and form of way of the segments.
|
||||
q.emplace(
|
||||
make_shared<Link>(u, e, u->m_distanceM + currentEdgeLen, u->m_pointScore, min(roadScore, u->m_minRoadScore)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetBestCandidatePaths(vector<shared_ptr<Link>> const & allPaths,
|
||||
LinearSegmentSource source, bool isLastPoint,
|
||||
uint32_t requiredBearing, double bearDistM,
|
||||
m2::PointD const & startPoint, ScorePathVec & candidates)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
CHECK_LESS_OR_EQUAL(requiredBearing, 255, ());
|
||||
|
||||
multiset<CandidatePath, greater<>> candidatePaths;
|
||||
|
||||
BearingPointsSelector pointsSelector(static_cast<uint32_t>(bearDistM), isLastPoint);
|
||||
for (auto const & link : allPaths)
|
||||
{
|
||||
auto const bearStartPoint = pointsSelector.GetStartPoint(link->GetStartEdge());
|
||||
|
||||
// Number of edges counting from the last one to check bearing on. According to OpenLR spec
|
||||
// we have to check bearing on a point that is no longer than 25 meters traveling down the path.
|
||||
// But sometimes we may skip the best place to stop and generate a candidate. So we check several
|
||||
// edges before the last one to avoid such a situation. Number of iterations is taken
|
||||
// by intuition.
|
||||
// Example:
|
||||
// o -------- o { Partners segment. }
|
||||
// o ------- o --- o { Our candidate. }
|
||||
// ^ 25m
|
||||
// ^ This one may be better than
|
||||
// ^ this one.
|
||||
// So we want to check them all.
|
||||
uint32_t traceBackIterationsLeft = 3;
|
||||
for (auto part = link; part; part = part->m_parent)
|
||||
{
|
||||
if (traceBackIterationsLeft == 0)
|
||||
break;
|
||||
|
||||
--traceBackIterationsLeft;
|
||||
|
||||
// Note. No information about bearing if source == LinearSegmentSource::FromCoordinatesTag.
|
||||
Score bearingScore = 0;
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag)
|
||||
{
|
||||
if (!GetBearingScore(pointsSelector, *part, bearStartPoint, requiredBearing, bearingScore))
|
||||
continue;
|
||||
}
|
||||
candidatePaths.emplace(part, part->m_pointScore, part->m_minRoadScore, bearingScore);
|
||||
}
|
||||
}
|
||||
|
||||
size_t constexpr kMaxCandidates = 7;
|
||||
vector<CandidatePath> paths;
|
||||
copy_n(candidatePaths.begin(), min(static_cast<size_t>(kMaxCandidates), candidatePaths.size()), back_inserter(paths));
|
||||
|
||||
for (auto const & path : paths)
|
||||
{
|
||||
Graph::EdgeVector edges;
|
||||
for (auto * p = path.m_path.get(); p; p = p->m_parent.get())
|
||||
edges.push_back(p->m_edge);
|
||||
|
||||
if (!isLastPoint)
|
||||
reverse(edges.begin(), edges.end());
|
||||
|
||||
candidates.emplace_back(path.GetScore(), std::move(edges));
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePathsGetter::GetLineCandidates(openlr::LocationReferencePoint const & p, LinearSegmentSource source,
|
||||
bool isLastPoint, double distanceToNextPointM,
|
||||
ScoreEdgeVec const & edgeCandidates, ScorePathVec & candidates)
|
||||
{
|
||||
double constexpr kDefaultBearDistM = 25.0;
|
||||
double const bearDistM = min(kDefaultBearDistM, distanceToNextPointM);
|
||||
|
||||
ScoreEdgeVec const & startLines = edgeCandidates;
|
||||
LOG(LDEBUG, ("Listing start lines:"));
|
||||
for (auto const & e : startLines)
|
||||
LOG(LDEBUG, (LogAs2GisPath(e.m_edge)));
|
||||
|
||||
auto const startPoint = mercator::FromLatLon(p.m_latLon);
|
||||
|
||||
vector<shared_ptr<Link>> allPaths;
|
||||
GetAllSuitablePaths(startLines, source, isLastPoint, bearDistM, p.m_functionalRoadClass, p.m_formOfWay,
|
||||
distanceToNextPointM, allPaths);
|
||||
|
||||
GetBestCandidatePaths(allPaths, source, isLastPoint, p.m_bearing, bearDistM, startPoint, candidates);
|
||||
// Sorting by increasing order.
|
||||
sort(candidates.begin(), candidates.end(),
|
||||
[](ScorePath const & s1, ScorePath const & s2) { return s1.m_score > s2.m_score; });
|
||||
LOG(LDEBUG, (candidates.size(), "Candidate paths found for point:", p.m_latLon));
|
||||
}
|
||||
|
||||
bool ScoreCandidatePathsGetter::GetBearingScore(BearingPointsSelector const & pointsSelector,
|
||||
ScoreCandidatePathsGetter::Link const & part,
|
||||
m2::PointD const & bearStartPoint, uint32_t requiredBearing,
|
||||
Score & score)
|
||||
{
|
||||
auto const bearEndPoint = pointsSelector.GetEndPoint(part.m_edge, part.m_distanceM);
|
||||
|
||||
auto const bearingDeg = scpg::BearingInDeg(bearStartPoint, bearEndPoint);
|
||||
double const requiredBearingDeg = scpg::ToAngleInDeg(requiredBearing);
|
||||
double const angleDeviationDeg = scpg::DifferenceInDeg(bearingDeg, requiredBearingDeg);
|
||||
|
||||
// If the bearing according to osm segments (|bearingDeg|) is significantly different
|
||||
// from the bearing set in openlr (|requiredBearingDeg|) the candidate should be skipped.
|
||||
double constexpr kMinAngleDeviationDeg = 50.0;
|
||||
if (angleDeviationDeg > kMinAngleDeviationDeg)
|
||||
return false;
|
||||
|
||||
double constexpr kMaxScoreForBearing = 60.0;
|
||||
double constexpr kAngleDeviationFactor = 1.0 / 4.3;
|
||||
score = static_cast<Score>(kMaxScoreForBearing / (1.0 + angleDeviationDeg * kAngleDeviationFactor));
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace openlr
|
||||
119
tools/openlr/score_candidate_paths_getter.hpp
Normal file
119
tools/openlr/score_candidate_paths_getter.hpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/helpers.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScoreCandidatePointsGetter;
|
||||
|
||||
class ScoreCandidatePathsGetter
|
||||
{
|
||||
public:
|
||||
ScoreCandidatePathsGetter(ScoreCandidatePointsGetter & pointsGetter, Graph & graph, RoadInfoGetter & infoGetter,
|
||||
v2::Stats & stat)
|
||||
: m_pointsGetter(pointsGetter)
|
||||
, m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stats(stat)
|
||||
{}
|
||||
|
||||
bool GetLineCandidatesForPoints(std::vector<LocationReferencePoint> const & points, LinearSegmentSource source,
|
||||
std::vector<ScorePathVec> & lineCandidates);
|
||||
|
||||
private:
|
||||
struct Link
|
||||
{
|
||||
Link(std::shared_ptr<Link> const & parent, Graph::Edge const & edge, double distanceM, Score pointScore,
|
||||
Score rfcScore)
|
||||
: m_parent(parent)
|
||||
, m_edge(edge)
|
||||
, m_distanceM(distanceM)
|
||||
, m_pointScore(pointScore)
|
||||
, m_minRoadScore(rfcScore)
|
||||
{
|
||||
CHECK(!edge.IsFake(), ("Edge should not be fake:", edge));
|
||||
}
|
||||
|
||||
bool IsJunctionInPath(geometry::PointWithAltitude const & j) const;
|
||||
|
||||
Graph::Edge GetStartEdge() const;
|
||||
|
||||
std::shared_ptr<Link> const m_parent;
|
||||
Graph::Edge const m_edge;
|
||||
double const m_distanceM;
|
||||
Score const m_pointScore;
|
||||
// Minimum score of segments of the path going along |m_parent| based on functional road class
|
||||
// and form of way.
|
||||
Score const m_minRoadScore;
|
||||
};
|
||||
|
||||
struct CandidatePath
|
||||
{
|
||||
CandidatePath() = default;
|
||||
|
||||
CandidatePath(std::shared_ptr<Link> const path, Score pointScore, Score rfcScore, Score bearingScore)
|
||||
: m_path(path)
|
||||
, m_pointScore(pointScore)
|
||||
, m_roadScore(rfcScore)
|
||||
, m_bearingScore(bearingScore)
|
||||
{}
|
||||
|
||||
bool operator>(CandidatePath const & o) const { return GetScore() > o.GetScore(); }
|
||||
|
||||
Score GetScore() const { return m_pointScore + m_roadScore + m_bearingScore; }
|
||||
|
||||
std::shared_ptr<Link> m_path = nullptr;
|
||||
Score m_pointScore = 0;
|
||||
Score m_roadScore = 0;
|
||||
Score m_bearingScore = 0;
|
||||
};
|
||||
|
||||
// Note: In all methods below if |isLastPoint| is true than algorithm should
|
||||
// calculate all parameters (such as bearing, distance to next point, etc.)
|
||||
// relative to the last point.
|
||||
// o ----> o ----> o <---- o.
|
||||
// 1 2 3 4
|
||||
// ^ isLastPoint = true.
|
||||
// To calculate bearing for points 1 to 3 one have to go beardist from
|
||||
// previous point to the next one (eg. from 1 to 2 and from 2 to 3).
|
||||
// For point 4 one have to go from 4 to 3 reversing directions. And
|
||||
// distance-to-next point is taken from point 3. You can learn more in
|
||||
// TomTom OpenLR spec.
|
||||
|
||||
/// \brief Fills |allPaths| with paths near start or finish point starting from |startLines|.
|
||||
/// To extract a path from |allPaths| a item from |allPaths| should be taken,
|
||||
/// then should be taken the member |m_parent| of the item and so on till the beginning.
|
||||
void GetAllSuitablePaths(ScoreEdgeVec const & startLines, LinearSegmentSource source, bool isLastPoint,
|
||||
double bearDistM, FunctionalRoadClass functionalRoadClass, FormOfWay formOfWay,
|
||||
double distanceToNextPointM, std::vector<std::shared_ptr<Link>> & allPaths);
|
||||
|
||||
void GetBestCandidatePaths(std::vector<std::shared_ptr<Link>> const & allPaths, LinearSegmentSource source,
|
||||
bool isLastPoint, uint32_t requiredBearing, double bearDistM,
|
||||
m2::PointD const & startPoint, ScorePathVec & candidates);
|
||||
|
||||
void GetLineCandidates(openlr::LocationReferencePoint const & p, LinearSegmentSource source, bool isLastPoint,
|
||||
double distanceToNextPointM, ScoreEdgeVec const & edgeCandidates, ScorePathVec & candidates);
|
||||
|
||||
bool GetBearingScore(BearingPointsSelector const & pointsSelector, ScoreCandidatePathsGetter::Link const & part,
|
||||
m2::PointD const & bearStartPoint, uint32_t requiredBearing, Score & score);
|
||||
|
||||
ScoreCandidatePointsGetter & m_pointsGetter;
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stats;
|
||||
};
|
||||
} // namespace openlr
|
||||
137
tools/openlr/score_candidate_points_getter.cpp
Normal file
137
tools/openlr/score_candidate_points_getter.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "openlr/score_candidate_points_getter.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
#include "routing/routing_helpers.hpp"
|
||||
|
||||
#include "storage/country_info_getter.hpp"
|
||||
|
||||
#include "indexer/feature.hpp"
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/feature_decl.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
using namespace routing;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Ends of segments and intermediate points of segments are considered only within this radius.
|
||||
double const kRadius = 30.0;
|
||||
} // namespace
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
void ScoreCandidatePointsGetter::GetJunctionPointCandidates(m2::PointD const & p, bool isLastPoint,
|
||||
ScoreEdgeVec & edgeCandidates)
|
||||
{
|
||||
ScorePointVec pointCandidates;
|
||||
auto const selectCandidates = [&p, &pointCandidates, this](FeatureType & ft)
|
||||
{
|
||||
ft.ParseGeometry(FeatureType::BEST_GEOMETRY);
|
||||
if (ft.GetGeomType() != feature::GeomType::Line || !routing::IsRoad(feature::TypesHolder(ft)))
|
||||
return;
|
||||
|
||||
ft.ForEachPoint([&p, &pointCandidates, this](m2::PointD const & candidate)
|
||||
{
|
||||
if (mercator::DistanceOnEarth(p, candidate) < kRadius)
|
||||
pointCandidates.emplace_back(GetScoreByDistance(p, candidate), candidate);
|
||||
}, scales::GetUpperScale());
|
||||
};
|
||||
|
||||
m_dataSource.ForEachInRect(selectCandidates, mercator::RectByCenterXYAndSizeInMeters(p, kRadius),
|
||||
scales::GetUpperScale());
|
||||
|
||||
base::SortUnique(pointCandidates);
|
||||
std::reverse(pointCandidates.begin(), pointCandidates.end());
|
||||
|
||||
pointCandidates.resize(std::min(m_maxJunctionCandidates, pointCandidates.size()));
|
||||
|
||||
for (auto const & pc : pointCandidates)
|
||||
{
|
||||
Graph::EdgeListT edges;
|
||||
if (!isLastPoint)
|
||||
m_graph.GetOutgoingEdges(geometry::PointWithAltitude(pc.m_point, 0 /* altitude */), edges);
|
||||
else
|
||||
m_graph.GetIngoingEdges(geometry::PointWithAltitude(pc.m_point, 0 /* altitude */), edges);
|
||||
|
||||
for (auto const & e : edges)
|
||||
edgeCandidates.emplace_back(pc.m_score, e);
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreCandidatePointsGetter::EnrichWithProjectionPoints(m2::PointD const & p, ScoreEdgeVec & edgeCandidates)
|
||||
{
|
||||
m_graph.ResetFakes();
|
||||
|
||||
std::vector<std::pair<Graph::Edge, geometry::PointWithAltitude>> vicinities;
|
||||
m_graph.FindClosestEdges(p, static_cast<uint32_t>(m_maxProjectionCandidates), vicinities);
|
||||
for (auto const & v : vicinities)
|
||||
{
|
||||
auto const & edge = v.first;
|
||||
auto const & proj = v.second;
|
||||
|
||||
CHECK(edge.HasRealPart(), ());
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
if (mercator::DistanceOnEarth(p, proj.GetPoint()) >= kRadius)
|
||||
continue;
|
||||
|
||||
edgeCandidates.emplace_back(GetScoreByDistance(p, proj.GetPoint()), edge);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScoreCandidatePointsGetter::IsJunction(m2::PointD const & p)
|
||||
{
|
||||
Graph::EdgeListT outgoing;
|
||||
m_graph.GetRegularOutgoingEdges(geometry::PointWithAltitude(p, 0 /* altitude */), outgoing);
|
||||
|
||||
Graph::EdgeListT ingoing;
|
||||
m_graph.GetRegularIngoingEdges(geometry::PointWithAltitude(p, 0 /* altitude */), ingoing);
|
||||
|
||||
// Note. At mwm borders the size of |ids| may be bigger than two in case of straight
|
||||
// road because of road feature duplication at borders.
|
||||
std::set<std::pair<uint32_t, uint32_t>> ids;
|
||||
for (auto const & e : outgoing)
|
||||
ids.insert(std::make_pair(e.GetFeatureId().m_index, e.GetSegId()));
|
||||
|
||||
for (auto const & e : ingoing)
|
||||
ids.insert(std::make_pair(e.GetFeatureId().m_index, e.GetSegId()));
|
||||
|
||||
// Size of |ids| is number of different pairs of (feature id, segment id) starting from
|
||||
// |p| plus going to |p|. The size 0, 1 or 2 is considered |p| is not a junction of roads.
|
||||
// If the size is 3 or more it means |p| is an intersection of 3 or more roads.
|
||||
return ids.size() >= 3;
|
||||
}
|
||||
|
||||
Score ScoreCandidatePointsGetter::GetScoreByDistance(m2::PointD const & point, m2::PointD const & candidate)
|
||||
{
|
||||
// Maximum possible score for the distance between an openlr segment ends and an osm segments.
|
||||
Score constexpr kMaxScoreForDist = 70;
|
||||
// If the distance between an openlr segments end and an osm segments end is less or equal
|
||||
// |kMaxScoreDistM| the point gets |kMaxScoreForDist| score.
|
||||
double constexpr kMaxScoreDistM = 5.0;
|
||||
// According to the standard openlr edge should be started from a junction. Despite the fact
|
||||
// that openlr and osm are based on different graphs, the score of junction should be increased.
|
||||
double const junctionFactor = IsJunction(candidate) ? 1.1 : 1.0;
|
||||
|
||||
double const distM = mercator::DistanceOnEarth(point, candidate);
|
||||
double const score = distM <= kMaxScoreDistM
|
||||
? kMaxScoreForDist * junctionFactor
|
||||
: static_cast<double>(kMaxScoreForDist) * junctionFactor / (1.0 + distM - kMaxScoreDistM);
|
||||
|
||||
CHECK_GREATER_OR_EQUAL(score, 0.0, ());
|
||||
CHECK_LESS_OR_EQUAL(score, static_cast<double>(kMaxScoreForDist) * junctionFactor, ());
|
||||
return static_cast<Score>(score);
|
||||
}
|
||||
} // namespace openlr
|
||||
48
tools/openlr/score_candidate_points_getter.hpp
Normal file
48
tools/openlr/score_candidate_points_getter.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include "indexer/data_source.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScoreCandidatePointsGetter
|
||||
{
|
||||
public:
|
||||
ScoreCandidatePointsGetter(size_t maxJunctionCandidates, size_t maxProjectionCandidates,
|
||||
DataSource const & dataSource, Graph & graph)
|
||||
: m_maxJunctionCandidates(maxJunctionCandidates)
|
||||
, m_maxProjectionCandidates(maxProjectionCandidates)
|
||||
, m_dataSource(dataSource)
|
||||
, m_graph(graph)
|
||||
{}
|
||||
|
||||
void GetEdgeCandidates(m2::PointD const & p, bool isLastPoint, ScoreEdgeVec & edges)
|
||||
{
|
||||
GetJunctionPointCandidates(p, isLastPoint, edges);
|
||||
EnrichWithProjectionPoints(p, edges);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetJunctionPointCandidates(m2::PointD const & p, bool isLastPoint, ScoreEdgeVec & edgeCandidates);
|
||||
void EnrichWithProjectionPoints(m2::PointD const & p, ScoreEdgeVec & edgeCandidates);
|
||||
|
||||
/// \returns true if |p| is a junction and false otherwise.
|
||||
bool IsJunction(m2::PointD const & p);
|
||||
Score GetScoreByDistance(m2::PointD const & point, m2::PointD const & candidate);
|
||||
|
||||
size_t const m_maxJunctionCandidates;
|
||||
size_t const m_maxProjectionCandidates;
|
||||
|
||||
DataSource const & m_dataSource;
|
||||
Graph & m_graph;
|
||||
};
|
||||
} // namespace openlr
|
||||
349
tools/openlr/score_paths_connector.cpp
Normal file
349
tools/openlr/score_paths_connector.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "openlr/score_paths_connector.hpp"
|
||||
|
||||
#include "openlr/helpers.hpp"
|
||||
|
||||
#include "indexer/feature_data.hpp"
|
||||
#include "indexer/ftypes_matcher.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/stl_iterator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class EdgeContainer
|
||||
{
|
||||
public:
|
||||
explicit EdgeContainer(Graph const & graph) : m_graph(graph) {}
|
||||
|
||||
void ProcessEdge(Graph::Edge const & edge)
|
||||
{
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
feature::TypesHolder types;
|
||||
m_graph.GetFeatureTypes(edge.GetFeatureId(), types);
|
||||
|
||||
ftypes::HighwayClass const hwClass = ftypes::GetHighwayClass(types);
|
||||
if (m_minHwClass == ftypes::HighwayClass::Undefined)
|
||||
{
|
||||
m_minHwClass = hwClass;
|
||||
m_maxHwClass = m_minHwClass;
|
||||
m_oneWay.m_field = ftypes::IsOneWayChecker::Instance()(types);
|
||||
m_roundabout.m_field = ftypes::IsRoundAboutChecker::Instance()(types);
|
||||
m_link.m_field = ftypes::IsLinkChecker::Instance()(types);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK(hwClass != ftypes::HighwayClass::Undefined, (edge));
|
||||
|
||||
m_minHwClass = static_cast<ftypes::HighwayClass>(min(base::Underlying(m_minHwClass), base::Underlying(hwClass)));
|
||||
m_maxHwClass = static_cast<ftypes::HighwayClass>(max(base::Underlying(m_maxHwClass), base::Underlying(hwClass)));
|
||||
|
||||
if (m_oneWay.m_allEqual && m_oneWay.m_field != ftypes::IsOneWayChecker::Instance()(types))
|
||||
m_oneWay.m_allEqual = false;
|
||||
|
||||
if (m_roundabout.m_allEqual && m_roundabout.m_field != ftypes::IsRoundAboutChecker::Instance()(types))
|
||||
m_roundabout.m_allEqual = false;
|
||||
|
||||
if (m_link.m_allEqual && m_link.m_field != ftypes::IsLinkChecker::Instance()(types))
|
||||
m_link.m_allEqual = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GetHighwayClassDiff() const
|
||||
{
|
||||
CHECK_NOT_EQUAL(m_minHwClass, ftypes::HighwayClass::Undefined, ());
|
||||
CHECK_GREATER_OR_EQUAL(m_maxHwClass, m_minHwClass, ());
|
||||
|
||||
uint8_t const hwClassDiff = static_cast<uint8_t>(m_maxHwClass) - static_cast<uint8_t>(m_minHwClass);
|
||||
return hwClassDiff;
|
||||
}
|
||||
|
||||
bool AreAllOneWaysEqual() const { return m_oneWay.m_allEqual; }
|
||||
bool AreAllRoundaboutEqual() const { return m_roundabout.m_allEqual; }
|
||||
bool AreAllLinksEqual() const { return m_link.m_allEqual; }
|
||||
|
||||
private:
|
||||
struct Field
|
||||
{
|
||||
bool m_field = false;
|
||||
bool m_allEqual = true;
|
||||
};
|
||||
|
||||
Graph const & m_graph;
|
||||
|
||||
ftypes::HighwayClass m_minHwClass = ftypes::HighwayClass::Undefined;
|
||||
ftypes::HighwayClass m_maxHwClass = ftypes::HighwayClass::Undefined;
|
||||
Field m_oneWay;
|
||||
Field m_roundabout;
|
||||
Field m_link;
|
||||
};
|
||||
|
||||
/// \returns true if |path| may be used as a candidate. In that case |lenScore| is filled
|
||||
/// with score of this candidate based on length. The closer length of the |path| to
|
||||
/// |distanceToNextPoint| the more score.
|
||||
bool ValidatePathByLength(Graph::EdgeVector const & path, double distanceToNextPoint, LinearSegmentSource source,
|
||||
Score & lenScore)
|
||||
{
|
||||
CHECK(!path.empty(), ());
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
|
||||
Score const kMaxScoreForRouteLen = 110;
|
||||
|
||||
double pathLen = 0.0;
|
||||
for (auto const & e : path)
|
||||
pathLen += EdgeLength(e);
|
||||
|
||||
// 0 <= |pathDiffRatio| <= 1. The more pathDiffRatio the closer |distanceToNextPoint| and |pathLen|.
|
||||
double const pathDiffRatio = 1.0 - abs(distanceToNextPoint - pathLen) / max(distanceToNextPoint, pathLen);
|
||||
|
||||
bool const shortPath = path.size() <= 2;
|
||||
double const kMinValidPathDiffRation = source == LinearSegmentSource::FromLocationReferenceTag ? 0.6 : 0.25;
|
||||
if (pathDiffRatio <= kMinValidPathDiffRation && !shortPath)
|
||||
return false;
|
||||
|
||||
lenScore = static_cast<Score>(kMaxScoreForRouteLen * (pathDiffRatio - kMinValidPathDiffRation) /
|
||||
(1.0 - kMinValidPathDiffRation));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AreEdgesEqualWithoutAltitude(Graph::Edge const & e1, Graph::Edge const & e2)
|
||||
{
|
||||
return make_tuple(e1.GetType(), e1.GetFeatureId(), e1.IsForward(), e1.GetSegId(), e1.GetStartPoint(),
|
||||
e1.GetEndPoint()) == make_tuple(e2.GetType(), e2.GetFeatureId(), e2.IsForward(), e2.GetSegId(),
|
||||
e2.GetStartPoint(), e2.GetEndPoint());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ScorePathsConnector::ScorePathsConnector(Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat)
|
||||
: m_graph(graph)
|
||||
, m_infoGetter(infoGetter)
|
||||
, m_stat(stat)
|
||||
{}
|
||||
|
||||
bool ScorePathsConnector::FindBestPath(vector<LocationReferencePoint> const & points,
|
||||
vector<vector<ScorePath>> const & lineCandidates, LinearSegmentSource source,
|
||||
vector<Graph::EdgeVector> & resultPath)
|
||||
{
|
||||
CHECK_NOT_EQUAL(source, LinearSegmentSource::NotValid, ());
|
||||
CHECK_GREATER_OR_EQUAL(points.size(), 2, ());
|
||||
|
||||
resultPath.resize(points.size() - 1);
|
||||
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
// @TODO It's possible that size of |points| is more then two. In that case two or more edges
|
||||
// should be approximated with routes. If so only |toCandidates| which may be reached from
|
||||
// |fromCandidates| should be used for the next edge.
|
||||
|
||||
auto const & point = points[i - 1];
|
||||
auto const distanceToNextPoint = static_cast<double>(point.m_distanceToNextPoint);
|
||||
auto const & fromCandidates = lineCandidates[i - 1];
|
||||
auto const & toCandidates = lineCandidates[i];
|
||||
auto & resultPathPart = resultPath[i - 1];
|
||||
|
||||
vector<ScorePath> result;
|
||||
for (size_t fromInd = 0; fromInd < fromCandidates.size(); ++fromInd)
|
||||
{
|
||||
for (size_t toInd = 0; toInd < toCandidates.size(); ++toInd)
|
||||
{
|
||||
Graph::EdgeVector path;
|
||||
if (!ConnectAdjacentCandidateLines(fromCandidates[fromInd].m_path, toCandidates[toInd].m_path, source,
|
||||
point.m_lfrcnp, distanceToNextPoint, path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Score pathLenScore = 0;
|
||||
if (!ValidatePathByLength(path, distanceToNextPoint, source, pathLenScore))
|
||||
continue;
|
||||
|
||||
auto const score =
|
||||
pathLenScore + GetScoreForUniformity(path) + fromCandidates[fromInd].m_score + toCandidates[toInd].m_score;
|
||||
result.emplace_back(score, std::move(path));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const & p : result)
|
||||
CHECK(!p.m_path.empty(), ());
|
||||
|
||||
if (result.empty())
|
||||
{
|
||||
LOG(LINFO, ("No shortest path found"));
|
||||
++m_stat.m_noShortestPathFound;
|
||||
resultPathPart.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const it = max_element(result.cbegin(), result.cend(),
|
||||
[](ScorePath const & o1, ScorePath const & o2) { return o1.m_score < o2.m_score; });
|
||||
|
||||
// Note. In case of source == LinearSegmentSource::FromCoordinatesTag there is less
|
||||
// information about a openlr segment so less score is collected.
|
||||
Score const kMinValidScore = source == LinearSegmentSource::FromLocationReferenceTag ? 240 : 165;
|
||||
if (it->m_score < kMinValidScore)
|
||||
{
|
||||
LOG(LINFO, ("The shortest path found but it is not good. The best score:", it->m_score));
|
||||
++m_stat.m_notEnoughScore;
|
||||
return false;
|
||||
}
|
||||
|
||||
resultPathPart = it->m_path;
|
||||
LOG(LDEBUG, ("Best score:", it->m_score, "resultPathPart.size():", resultPathPart.size()));
|
||||
}
|
||||
|
||||
CHECK_EQUAL(resultPath.size(), points.size() - 1, ());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScorePathsConnector::FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength,
|
||||
Graph::EdgeVector & path)
|
||||
{
|
||||
double constexpr kLengthToleranceFactor = 1.1;
|
||||
uint32_t constexpr kMinLengthTolerance = 20;
|
||||
uint32_t const lengthToleranceM =
|
||||
max(static_cast<uint32_t>(kLengthToleranceFactor * maxPathLength), kMinLengthTolerance);
|
||||
|
||||
struct State
|
||||
{
|
||||
State(Graph::Edge const & e, uint32_t const s) : m_edge(e), m_score(s) {}
|
||||
|
||||
bool operator>(State const & o) const { return tie(m_score, m_edge) > tie(o.m_score, o.m_edge); }
|
||||
|
||||
Graph::Edge m_edge;
|
||||
uint32_t m_score;
|
||||
};
|
||||
|
||||
CHECK(from.HasRealPart() && to.HasRealPart(), ());
|
||||
|
||||
priority_queue<State, vector<State>, greater<>> q;
|
||||
map<Graph::Edge, double> scores;
|
||||
map<Graph::Edge, Graph::Edge> links;
|
||||
|
||||
q.emplace(from, 0);
|
||||
scores[from] = 0;
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
auto const state = q.top();
|
||||
q.pop();
|
||||
|
||||
auto const & stateEdge = state.m_edge;
|
||||
// TODO(mgsergio): Unify names: use either score or distance.
|
||||
auto const stateScore = state.m_score;
|
||||
|
||||
if (stateScore > maxPathLength + lengthToleranceM)
|
||||
continue;
|
||||
|
||||
if (stateScore > scores[stateEdge])
|
||||
continue;
|
||||
|
||||
if (AreEdgesEqualWithoutAltitude(stateEdge, to))
|
||||
{
|
||||
for (auto edge = stateEdge; edge != from; edge = links[edge])
|
||||
path.emplace_back(edge);
|
||||
path.emplace_back(from);
|
||||
reverse(begin(path), end(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
Graph::EdgeListT edges;
|
||||
m_graph.GetOutgoingEdges(stateEdge.GetEndJunction(), edges);
|
||||
for (auto const & edge : edges)
|
||||
{
|
||||
if (source == LinearSegmentSource::FromLocationReferenceTag &&
|
||||
!ConformLfrcnpV3(edge, lowestFrcToNextPoint, m_infoGetter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK(!stateEdge.IsFake(), ());
|
||||
CHECK(!edge.IsFake(), ());
|
||||
|
||||
auto const it = scores.find(edge);
|
||||
auto const eScore = stateScore + EdgeLength(edge);
|
||||
if (it == end(scores) || it->second > eScore)
|
||||
{
|
||||
scores[edge] = eScore;
|
||||
links[edge] = stateEdge;
|
||||
q.emplace(edge, eScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScorePathsConnector::ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint,
|
||||
double distanceToNextPoint, Graph::EdgeVector & resultPath)
|
||||
{
|
||||
CHECK(!to.empty(), ());
|
||||
|
||||
if (auto const skip = PathOverlappingLen(from, to))
|
||||
{
|
||||
if (skip == -1)
|
||||
return false;
|
||||
|
||||
resultPath.insert(resultPath.end(), from.cbegin(), from.cend());
|
||||
resultPath.insert(resultPath.end(), to.cbegin() + skip, to.cend());
|
||||
return true;
|
||||
}
|
||||
|
||||
CHECK_NOT_EQUAL(from, to, ());
|
||||
|
||||
Graph::EdgeVector shortestPath;
|
||||
auto const found = FindShortestPath(from.back(), to.front(), source, lowestFrcToNextPoint,
|
||||
static_cast<uint32_t>(ceil(distanceToNextPoint)), shortestPath);
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
CHECK_EQUAL(from.back(), shortestPath.front(), ());
|
||||
resultPath.insert(resultPath.end(), from.cbegin(), prev(from.cend()));
|
||||
resultPath.insert(resultPath.end(), shortestPath.cbegin(), shortestPath.cend());
|
||||
|
||||
CHECK(AreEdgesEqualWithoutAltitude(to.front(), shortestPath.back()), (to.front(), shortestPath.back()));
|
||||
resultPath.insert(resultPath.end(), next(to.begin()), to.end());
|
||||
|
||||
return !resultPath.empty();
|
||||
}
|
||||
|
||||
Score ScorePathsConnector::GetScoreForUniformity(Graph::EdgeVector const & path)
|
||||
{
|
||||
EdgeContainer edgeContainer(m_graph);
|
||||
for (auto const & edge : path)
|
||||
edgeContainer.ProcessEdge(edge);
|
||||
|
||||
auto const hwClassDiff = edgeContainer.GetHighwayClassDiff();
|
||||
Score constexpr kScoreForTheSameHwClass = 40;
|
||||
Score constexpr kScoreForNeighboringHwClasses = 15;
|
||||
Score const hwClassScore = hwClassDiff == 0 ? kScoreForTheSameHwClass
|
||||
: hwClassDiff == 1 ? kScoreForNeighboringHwClasses
|
||||
: 0;
|
||||
|
||||
Score constexpr kScoreForOneWayOnly = 17;
|
||||
Score constexpr kScoreForRoundaboutOnly = 18;
|
||||
Score constexpr kScoreForLinkOnly = 10;
|
||||
Score const allEqualScore = (edgeContainer.AreAllOneWaysEqual() ? kScoreForOneWayOnly : 0) +
|
||||
(edgeContainer.AreAllRoundaboutEqual() ? kScoreForRoundaboutOnly : 0) +
|
||||
(edgeContainer.AreAllLinksEqual() ? kScoreForLinkOnly : 0);
|
||||
|
||||
return hwClassScore + allEqualScore;
|
||||
}
|
||||
} // namespace openlr
|
||||
41
tools/openlr/score_paths_connector.hpp
Normal file
41
tools/openlr/score_paths_connector.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/graph.hpp"
|
||||
#include "openlr/openlr_model.hpp"
|
||||
#include "openlr/road_info_getter.hpp"
|
||||
#include "openlr/score_types.hpp"
|
||||
#include "openlr/stats.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
class ScorePathsConnector
|
||||
{
|
||||
public:
|
||||
ScorePathsConnector(Graph & graph, RoadInfoGetter & infoGetter, v2::Stats & stat);
|
||||
|
||||
/// \brief Connects |lineCandidates| and fills |resultPath| with the path with maximum score
|
||||
/// if there's a good enough.
|
||||
/// \returns true if the best path is found and false otherwise.
|
||||
bool FindBestPath(std::vector<LocationReferencePoint> const & points,
|
||||
std::vector<std::vector<ScorePath>> const & lineCandidates, LinearSegmentSource source,
|
||||
std::vector<Graph::EdgeVector> & resultPath);
|
||||
|
||||
private:
|
||||
bool FindShortestPath(Graph::Edge const & from, Graph::Edge const & to, LinearSegmentSource source,
|
||||
FunctionalRoadClass lowestFrcToNextPoint, uint32_t maxPathLength, Graph::EdgeVector & path);
|
||||
|
||||
bool ConnectAdjacentCandidateLines(Graph::EdgeVector const & from, Graph::EdgeVector const & to,
|
||||
LinearSegmentSource source, FunctionalRoadClass lowestFrcToNextPoint,
|
||||
double distanceToNextPoint, Graph::EdgeVector & resultPath);
|
||||
|
||||
Score GetScoreForUniformity(Graph::EdgeVector const & path);
|
||||
|
||||
Graph & m_graph;
|
||||
RoadInfoGetter & m_infoGetter;
|
||||
v2::Stats & m_stat;
|
||||
};
|
||||
} // namespace openlr
|
||||
52
tools/openlr/score_types.hpp
Normal file
52
tools/openlr/score_types.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/road_graph.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
using Edge = routing::Edge;
|
||||
using EdgeVector = routing::RoadGraphBase::EdgeVector;
|
||||
|
||||
using Score = uint32_t;
|
||||
|
||||
struct ScorePoint
|
||||
{
|
||||
ScorePoint() = default;
|
||||
ScorePoint(Score score, m2::PointD const & point) : m_score(score), m_point(point) {}
|
||||
|
||||
bool operator<(ScorePoint const & o) const { return std::tie(m_score, m_point) < std::tie(o.m_score, o.m_point); }
|
||||
|
||||
bool operator==(ScorePoint const & o) const { return m_score == o.m_score && m_point == o.m_point; }
|
||||
|
||||
Score m_score = 0;
|
||||
m2::PointD m_point;
|
||||
};
|
||||
|
||||
using ScorePointVec = std::vector<ScorePoint>;
|
||||
|
||||
struct ScoreEdge
|
||||
{
|
||||
ScoreEdge(Score score, Edge const & edge) : m_score(score), m_edge(edge) {}
|
||||
|
||||
Score m_score;
|
||||
Edge m_edge;
|
||||
};
|
||||
|
||||
using ScoreEdgeVec = std::vector<ScoreEdge>;
|
||||
|
||||
struct ScorePath
|
||||
{
|
||||
ScorePath(Score score, EdgeVector && path) : m_score(score), m_path(std::move(path)) {}
|
||||
|
||||
Score m_score;
|
||||
EdgeVector m_path;
|
||||
};
|
||||
|
||||
using ScorePathVec = std::vector<ScorePath>;
|
||||
} // namespace openlr
|
||||
48
tools/openlr/stats.hpp
Normal file
48
tools/openlr/stats.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/cache_line_size.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
namespace v2
|
||||
{
|
||||
struct alignas(kCacheLineSize) Stats
|
||||
{
|
||||
void Add(Stats const & s)
|
||||
{
|
||||
m_routesHandled += s.m_routesHandled;
|
||||
m_routesFailed += s.m_routesFailed;
|
||||
m_noCandidateFound += s.m_noCandidateFound;
|
||||
m_noShortestPathFound += s.m_noShortestPathFound;
|
||||
m_notEnoughScore += s.m_notEnoughScore;
|
||||
m_wrongOffsets += s.m_wrongOffsets;
|
||||
m_zeroDistToNextPointCount += s.m_zeroDistToNextPointCount;
|
||||
}
|
||||
|
||||
void Report() const
|
||||
{
|
||||
LOG(LINFO, ("Total routes handled:", m_routesHandled));
|
||||
LOG(LINFO, ("Failed:", m_routesFailed));
|
||||
LOG(LINFO, ("No candidate lines:", m_noCandidateFound));
|
||||
LOG(LINFO, ("Wrong distance to next point:", m_zeroDistToNextPointCount));
|
||||
LOG(LINFO, ("Not enough score for shortest path:", m_notEnoughScore));
|
||||
LOG(LINFO, ("Wrong offsets:", m_wrongOffsets));
|
||||
LOG(LINFO, ("No shortest path:", m_noShortestPathFound));
|
||||
}
|
||||
|
||||
uint32_t m_routesHandled = 0;
|
||||
uint32_t m_routesFailed = 0;
|
||||
uint32_t m_noCandidateFound = 0;
|
||||
uint32_t m_noShortestPathFound = 0;
|
||||
uint32_t m_notEnoughScore = 0;
|
||||
uint32_t m_wrongOffsets = 0;
|
||||
// Number of zeroed distance-to-next point values in the input.
|
||||
uint32_t m_zeroDistToNextPointCount = 0;
|
||||
};
|
||||
} // namespace v2
|
||||
} // namespace openlr
|
||||
26
tools/openlr/way_point.hpp
Normal file
26
tools/openlr/way_point.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "openlr/openlr_model.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace openlr
|
||||
{
|
||||
struct WayPoint final
|
||||
{
|
||||
explicit WayPoint(openlr::LocationReferencePoint const & lrp)
|
||||
: m_point(mercator::FromLatLon(lrp.m_latLon))
|
||||
, m_distanceToNextPointM(lrp.m_distanceToNextPoint)
|
||||
, m_bearing(lrp.m_bearing)
|
||||
, m_lfrcnp(lrp.m_functionalRoadClass)
|
||||
{}
|
||||
|
||||
m2::PointD m_point;
|
||||
double m_distanceToNextPointM = 0.0;
|
||||
uint8_t m_bearing = 0;
|
||||
openlr::FunctionalRoadClass m_lfrcnp = openlr::FunctionalRoadClass::NotAValue;
|
||||
};
|
||||
} // namespace openlr
|
||||
17
tools/poly_borders/CMakeLists.txt
Normal file
17
tools/poly_borders/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
project(poly_borders)
|
||||
|
||||
set(
|
||||
SRC
|
||||
borders_data.cpp
|
||||
borders_data.hpp
|
||||
help_structures.cpp
|
||||
help_structures.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} generator)
|
||||
|
||||
omim_add_test_subdirectory(poly_borders_tests)
|
||||
|
||||
omim_add_tool_subdirectory(poly_borders_tool)
|
||||
447
tools/poly_borders/borders_data.cpp
Normal file
447
tools/poly_borders/borders_data.cpp
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
#include "poly_borders/borders_data.hpp"
|
||||
|
||||
#include "poly_borders/help_structures.hpp"
|
||||
|
||||
#include "generator/borders.hpp"
|
||||
#include "generator/routing_city_boundaries_processor.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
#include "base/thread_pool_computational.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace poly_borders;
|
||||
|
||||
void PrintWithSpaces(std::string const & str, size_t maxN)
|
||||
{
|
||||
std::cout << str;
|
||||
if (maxN <= str.size())
|
||||
return;
|
||||
|
||||
maxN -= str.size();
|
||||
for (size_t i = 0; i < maxN; ++i)
|
||||
std::cout << " ";
|
||||
}
|
||||
|
||||
std::string RemoveIndexFromMwmName(std::string const & mwmName)
|
||||
{
|
||||
auto const pos = mwmName.find(BordersData::kBorderExtension);
|
||||
CHECK_NOT_EQUAL(pos, std::string::npos, ());
|
||||
auto const end = mwmName.begin() + pos + BordersData::kBorderExtension.size();
|
||||
std::string result(mwmName.cbegin(), end);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsReversedIntervals(size_t fromDst, size_t toDst, size_t fromSrc, size_t toSrc)
|
||||
{
|
||||
return (fromDst > toDst) != (fromSrc > toSrc);
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> AppendPointsWithAnyDirection(std::vector<MarkedPoint> const & copyFrom, size_t from, size_t to)
|
||||
{
|
||||
std::vector<m2::PointD> result;
|
||||
if (from > to)
|
||||
std::swap(from, to);
|
||||
|
||||
for (size_t k = from; k <= to; ++k)
|
||||
result.emplace_back(copyFrom[k].m_point);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
double AbsAreaDiff(std::vector<m2::PointD> const & firstPolygon, std::vector<m2::PointD> const & secondPolygon)
|
||||
{
|
||||
auto const firstArea = generator::AreaOnEarth(firstPolygon);
|
||||
auto const secondArea = generator::AreaOnEarth(secondPolygon);
|
||||
return std::abs(secondArea - firstArea);
|
||||
}
|
||||
|
||||
bool NeedReplace(std::vector<m2::PointD> const & curSubpolygon, std::vector<m2::PointD> const & anotherSubpolygon)
|
||||
{
|
||||
auto const areaDiff = AbsAreaDiff(curSubpolygon, anotherSubpolygon);
|
||||
double constexpr kMaxAreaDiffMetersSquared = 20000.0;
|
||||
if (areaDiff > kMaxAreaDiffMetersSquared)
|
||||
return false;
|
||||
|
||||
if (AlmostEqualAbs(areaDiff, 0.0, BordersData::kEqualityEpsilon))
|
||||
return false;
|
||||
|
||||
// We know that |curSize| is always greater than 1, because we construct it such way, but we know
|
||||
// nothing about |anotherSize|, and we do not want to replace current subpolygon of several points
|
||||
// with a subpolygon consisting of one point or consisting of too many points.
|
||||
CHECK_GREATER(curSubpolygon.size(), 1, ());
|
||||
return 1 < anotherSubpolygon.size() && anotherSubpolygon.size() < 10;
|
||||
}
|
||||
|
||||
bool ShouldLog(size_t i, size_t n)
|
||||
{
|
||||
return (i % 100 == 0) || (i + 1 == n);
|
||||
}
|
||||
|
||||
void Append(size_t from, size_t to, bool reversed, std::vector<MarkedPoint> const & copyFrom,
|
||||
std::vector<MarkedPoint> & copyTo)
|
||||
{
|
||||
if (!reversed)
|
||||
{
|
||||
for (size_t k = from; k <= to; ++k)
|
||||
copyTo.emplace_back(copyFrom[k].m_point);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t k = to;
|
||||
while (k >= from)
|
||||
{
|
||||
copyTo.emplace_back(copyFrom[k].m_point);
|
||||
if (k == 0)
|
||||
break;
|
||||
--k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m2::RectD BoundingBox(std::vector<MarkedPoint> const & points)
|
||||
{
|
||||
m2::RectD rect;
|
||||
for (auto const & point : points)
|
||||
rect.Add(point.m_point);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void SwapIfNeeded(size_t & a, size_t & b)
|
||||
{
|
||||
if (a > b)
|
||||
std::swap(a, b);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
|
||||
// BordersData -------------------------------------------------------------------------------------
|
||||
void BordersData::Init(std::string const & bordersDir)
|
||||
{
|
||||
LOG(LINFO, ("Borders path:", bordersDir));
|
||||
|
||||
// key - coordinates
|
||||
// value - {border idx, point idx}
|
||||
std::unordered_map<int64_t, std::vector<std::pair<size_t, size_t>>> index;
|
||||
|
||||
std::vector<std::string> files;
|
||||
Platform::GetFilesByExt(bordersDir, kBorderExtension, files);
|
||||
|
||||
LOG(LINFO, ("Start reading data from .poly files."));
|
||||
size_t prevIndex = 0;
|
||||
for (auto const & file : files)
|
||||
{
|
||||
auto const fullPath = base::JoinPath(bordersDir, file);
|
||||
size_t polygonId = 1;
|
||||
|
||||
borders::PolygonsList borders;
|
||||
borders::LoadBorders(fullPath, borders);
|
||||
for (auto & region : borders)
|
||||
{
|
||||
auto & points = region.MutableData();
|
||||
m_duplicatedPointsCount += RemoveDuplicatingPointImpl(points);
|
||||
CHECK_GREATER(points.size(), 1, (fullPath));
|
||||
|
||||
// Some mwms have several polygons. For example, for Japan_Kanto_Tokyo that has 2 polygons we
|
||||
// will write 2 files:
|
||||
// Japan_Kanto_Tokyo.poly1
|
||||
// Japan_Kanto_Tokyo.poly2
|
||||
auto const fileCopy = file + std::to_string(polygonId);
|
||||
|
||||
m_indexToPolyFileName[prevIndex] = fileCopy;
|
||||
m_polyFileNameToIndex[fileCopy] = prevIndex++;
|
||||
|
||||
size_t const borderIdx = m_bordersPolygons.size();
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
index[PointToInt64Obsolete(points[i], kPointCoordBits)].emplace_back(borderIdx, i);
|
||||
|
||||
++polygonId;
|
||||
m_bordersPolygons.emplace_back(region.GetRect(), points);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const & [_, v] : index)
|
||||
{
|
||||
for (size_t i = 0; i < v.size() - 1; ++i)
|
||||
for (size_t j = i + 1; j < v.size(); ++j)
|
||||
{
|
||||
m_bordersPolygons[v[i].first].m_points[v[i].second].AddLink(v[j].first, v[j].second);
|
||||
m_bordersPolygons[v[j].first].m_points[v[j].second].AddLink(v[i].first, v[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Removed:", m_duplicatedPointsCount, "from input data."));
|
||||
}
|
||||
|
||||
void BordersData::DumpPolyFiles(std::string const & targetDir)
|
||||
{
|
||||
size_t n = m_bordersPolygons.size();
|
||||
for (size_t i = 0; i < n;)
|
||||
{
|
||||
// Russia_Moscow.poly1 -> Russia_Moscow.poly
|
||||
auto name = RemoveIndexFromMwmName(m_indexToPolyFileName.at(i));
|
||||
|
||||
size_t j = i + 1;
|
||||
while (j < n && name == RemoveIndexFromMwmName(m_indexToPolyFileName.at(j)))
|
||||
++j;
|
||||
|
||||
std::vector<m2::RegionD> regions;
|
||||
for (; i < j; i++)
|
||||
{
|
||||
if (ShouldLog(i, n))
|
||||
LOG(LINFO, ("Dumping poly files:", i + 1, "/", n));
|
||||
|
||||
m2::RegionD region;
|
||||
for (auto const & markedPoint : m_bordersPolygons[i].m_points)
|
||||
region.AddPoint(markedPoint.m_point);
|
||||
|
||||
regions.emplace_back(std::move(region));
|
||||
}
|
||||
|
||||
CHECK(strings::ReplaceFirst(name, kBorderExtension, ""), (name));
|
||||
borders::DumpBorderToPolyFile(targetDir, name, regions);
|
||||
}
|
||||
}
|
||||
|
||||
size_t BordersData::RemoveDuplicatePoints()
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto & polygon : m_bordersPolygons)
|
||||
count += RemoveDuplicatingPointImpl(polygon.m_points);
|
||||
return count;
|
||||
}
|
||||
|
||||
void BordersData::PrintDiff()
|
||||
{
|
||||
using Info = std::tuple<double, std::string, size_t, size_t>;
|
||||
|
||||
std::set<Info> info;
|
||||
|
||||
size_t allNumberBeforeCount = 0;
|
||||
size_t maxMwmNameLength = 0;
|
||||
for (size_t i = 0; i < m_bordersPolygons.size(); ++i)
|
||||
{
|
||||
auto const & mwmName = m_indexToPolyFileName[i];
|
||||
|
||||
auto const all = static_cast<int32_t>(m_bordersPolygons[i].m_points.size());
|
||||
auto const allBefore = static_cast<int32_t>(m_prevCopy[i].m_points.size());
|
||||
|
||||
CHECK_GREATER_OR_EQUAL(allBefore, all, ());
|
||||
m_removedPointsCount += allBefore - all;
|
||||
allNumberBeforeCount += allBefore;
|
||||
|
||||
double area = 0.0;
|
||||
double constexpr kAreaEpsMetersSqr = 1e-4;
|
||||
if (m_additionalAreaMetersSqr[i] >= kAreaEpsMetersSqr)
|
||||
area = m_additionalAreaMetersSqr[i];
|
||||
|
||||
maxMwmNameLength = std::max(maxMwmNameLength, mwmName.size());
|
||||
info.emplace(area, mwmName, allBefore, all);
|
||||
}
|
||||
|
||||
for (auto const & [area, name, allBefore, all] : info)
|
||||
{
|
||||
size_t diff = allBefore - all;
|
||||
PrintWithSpaces(name, maxMwmNameLength + 1);
|
||||
PrintWithSpaces("-" + std::to_string(diff) + " points", 17);
|
||||
|
||||
std::cout << " total changed area: " << area << " m^2" << std::endl;
|
||||
}
|
||||
|
||||
CHECK_NOT_EQUAL(allNumberBeforeCount, 0, ("Empty input?"));
|
||||
std::cout << "Number of removed points: " << m_removedPointsCount << std::endl
|
||||
<< "Removed duplicate point: " << m_duplicatedPointsCount << std::endl
|
||||
<< "Total removed points: " << m_removedPointsCount + m_duplicatedPointsCount << std::endl;
|
||||
std::cout << "Points number before processing: " << allNumberBeforeCount << ", remove( "
|
||||
<< static_cast<double>(m_removedPointsCount + m_duplicatedPointsCount) / allNumberBeforeCount * 100.0
|
||||
<< "% )" << std::endl;
|
||||
}
|
||||
|
||||
void BordersData::RemoveEmptySpaceBetweenBorders()
|
||||
{
|
||||
LOG(LINFO, ("Start removing empty space between borders."));
|
||||
|
||||
for (size_t curBorderId = 0; curBorderId < m_bordersPolygons.size(); ++curBorderId)
|
||||
{
|
||||
LOG(LDEBUG, ("Get:", m_indexToPolyFileName[curBorderId]));
|
||||
|
||||
if (ShouldLog(curBorderId, m_bordersPolygons.size()))
|
||||
LOG(LINFO, ("Removing empty spaces:", curBorderId + 1, "/", m_bordersPolygons.size()));
|
||||
|
||||
auto & curPolygon = m_bordersPolygons[curBorderId];
|
||||
for (size_t curPointId = 0; curPointId < curPolygon.m_points.size(); ++curPointId)
|
||||
{
|
||||
if (curPolygon.IsFrozen(curPointId, curPointId) || !HasLinkAt(curBorderId, curPointId))
|
||||
continue;
|
||||
|
||||
size_t constexpr kMaxLookAhead = 5;
|
||||
for (size_t shift = 1; shift <= kMaxLookAhead; ++shift)
|
||||
{
|
||||
if (TryToReplace(curBorderId, curPointId /* curLeftPointId */, curPointId + shift /* curRightPointId */) ==
|
||||
base::ControlFlow::Break)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoReplace();
|
||||
}
|
||||
|
||||
base::ControlFlow BordersData::TryToReplace(size_t curBorderId, size_t & curLeftPointId, size_t curRightPointId)
|
||||
{
|
||||
auto & curPolygon = m_bordersPolygons[curBorderId];
|
||||
if (curRightPointId >= curPolygon.m_points.size())
|
||||
return base::ControlFlow::Break;
|
||||
|
||||
if (curPolygon.IsFrozen(curRightPointId, curRightPointId))
|
||||
{
|
||||
curLeftPointId = curRightPointId - 1;
|
||||
return base::ControlFlow::Break;
|
||||
}
|
||||
|
||||
auto & leftMarkedPoint = curPolygon.m_points[curLeftPointId];
|
||||
auto & rightMarkedPoint = curPolygon.m_points[curRightPointId];
|
||||
|
||||
auto op = rightMarkedPoint.GetLink(curBorderId);
|
||||
if (!op)
|
||||
return base::ControlFlow::Continue;
|
||||
|
||||
Link rightLink = *op;
|
||||
Link leftLink = *leftMarkedPoint.GetLink(curBorderId);
|
||||
|
||||
if (leftLink.m_borderId != rightLink.m_borderId)
|
||||
return base::ControlFlow::Continue;
|
||||
|
||||
auto const anotherBorderId = leftLink.m_borderId;
|
||||
auto const anotherLeftPointId = leftLink.m_pointId;
|
||||
auto const anotherRightPointId = rightLink.m_pointId;
|
||||
auto & anotherPolygon = m_bordersPolygons[anotherBorderId];
|
||||
|
||||
if (anotherPolygon.IsFrozen(std::min(anotherLeftPointId, anotherRightPointId),
|
||||
std::max(anotherLeftPointId, anotherRightPointId)))
|
||||
{
|
||||
return base::ControlFlow::Continue;
|
||||
}
|
||||
|
||||
auto const anotherSubpolygon =
|
||||
AppendPointsWithAnyDirection(anotherPolygon.m_points, anotherLeftPointId, anotherRightPointId);
|
||||
|
||||
auto const curSubpolygon = AppendPointsWithAnyDirection(curPolygon.m_points, curLeftPointId, curRightPointId);
|
||||
|
||||
if (!NeedReplace(curSubpolygon, anotherSubpolygon))
|
||||
return base::ControlFlow::Break;
|
||||
|
||||
// We want to decrease the amount of points in polygons. So we will replace the greater amounts of
|
||||
// points by smaller amounts of points.
|
||||
bool const curLenIsLess = curSubpolygon.size() < anotherSubpolygon.size();
|
||||
|
||||
size_t dstFrom = curLenIsLess ? anotherLeftPointId : curLeftPointId;
|
||||
size_t dstTo = curLenIsLess ? anotherRightPointId : curRightPointId;
|
||||
|
||||
size_t srcFrom = curLenIsLess ? curLeftPointId : anotherLeftPointId;
|
||||
size_t srcTo = curLenIsLess ? curRightPointId : anotherRightPointId;
|
||||
|
||||
size_t const borderIdWhereAreaWillBeChanged = curLenIsLess ? anotherBorderId : curBorderId;
|
||||
size_t const srcBorderId = curLenIsLess ? curBorderId : anotherBorderId;
|
||||
|
||||
bool const reversed = IsReversedIntervals(dstFrom, dstTo, srcFrom, srcTo);
|
||||
|
||||
m_additionalAreaMetersSqr[borderIdWhereAreaWillBeChanged] += AbsAreaDiff(curSubpolygon, anotherSubpolygon);
|
||||
|
||||
SwapIfNeeded(dstFrom, dstTo);
|
||||
SwapIfNeeded(srcFrom, srcTo);
|
||||
|
||||
// Save info for |borderIdWhereAreaWillBeChanged| - where from it should gets info about
|
||||
// replacement.
|
||||
m_bordersPolygons[borderIdWhereAreaWillBeChanged].AddReplaceInfo(dstFrom, dstTo, srcFrom, srcTo, srcBorderId,
|
||||
reversed);
|
||||
|
||||
// And say for |srcBorderId| that points in segment: [srcFrom, srcTo] are frozen and cannot
|
||||
// be used anywhere (because we use them to replace points in segment: [dstFrom, dstTo]).
|
||||
m_bordersPolygons[srcBorderId].MakeFrozen(srcFrom, srcTo);
|
||||
|
||||
CHECK_LESS(curLeftPointId, curRightPointId, ());
|
||||
curLeftPointId = curRightPointId - 1;
|
||||
return base::ControlFlow::Break;
|
||||
}
|
||||
|
||||
void BordersData::DoReplace()
|
||||
{
|
||||
LOG(LINFO, ("Start replacing intervals."));
|
||||
|
||||
std::vector<Polygon> newMwmsPolygons;
|
||||
|
||||
for (size_t borderId = 0; borderId < m_bordersPolygons.size(); ++borderId)
|
||||
{
|
||||
if (ShouldLog(borderId, m_bordersPolygons.size()))
|
||||
LOG(LINFO, ("Replacing:", borderId + 1, "/", m_bordersPolygons.size()));
|
||||
|
||||
auto & polygon = m_bordersPolygons[borderId];
|
||||
|
||||
newMwmsPolygons.emplace_back();
|
||||
auto & newPolygon = newMwmsPolygons.back();
|
||||
|
||||
for (size_t i = 0; i < polygon.m_points.size(); ++i)
|
||||
{
|
||||
auto const replaceDataIter = polygon.FindReplaceData(i);
|
||||
bool const noReplaceData = replaceDataIter == polygon.m_replaceData.cend();
|
||||
if (noReplaceData)
|
||||
{
|
||||
newPolygon.m_points.emplace_back(polygon.m_points[i].m_point);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const srcBorderId = replaceDataIter->m_srcBorderId;
|
||||
size_t const srcFrom = replaceDataIter->m_srcReplaceFrom;
|
||||
size_t const srcTo = replaceDataIter->m_srcReplaceTo;
|
||||
size_t const nextI = replaceDataIter->m_dstTo;
|
||||
bool const reversed = replaceDataIter->m_reversed;
|
||||
|
||||
CHECK_EQUAL(i, replaceDataIter->m_dstFrom, ());
|
||||
|
||||
auto const & srcPolygon = m_bordersPolygons[srcBorderId];
|
||||
|
||||
Append(srcFrom, srcTo, reversed, srcPolygon.m_points, newPolygon.m_points);
|
||||
|
||||
polygon.m_replaceData.erase(replaceDataIter);
|
||||
i = nextI - 1;
|
||||
}
|
||||
|
||||
newPolygon.m_rect = BoundingBox(newPolygon.m_points);
|
||||
}
|
||||
|
||||
m_prevCopy = std::move(m_bordersPolygons);
|
||||
m_bordersPolygons = std::move(newMwmsPolygons);
|
||||
RemoveDuplicatePoints();
|
||||
}
|
||||
|
||||
Polygon const & BordersData::GetBordersPolygonByName(std::string const & name) const
|
||||
{
|
||||
auto id = m_polyFileNameToIndex.at(name);
|
||||
return m_bordersPolygons.at(id);
|
||||
}
|
||||
|
||||
bool BordersData::HasLinkAt(size_t curBorderId, size_t pointId)
|
||||
{
|
||||
auto & leftMarkedPoint = m_bordersPolygons[curBorderId].m_points[pointId];
|
||||
return leftMarkedPoint.GetLink(curBorderId).has_value();
|
||||
}
|
||||
} // namespace poly_borders
|
||||
71
tools/poly_borders/borders_data.hpp
Normal file
71
tools/poly_borders/borders_data.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "poly_borders/help_structures.hpp"
|
||||
|
||||
#include "base/control_flow.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
/// \note All methods of this class are not thread-safe except |MarkPoint()| method.
|
||||
class BordersData
|
||||
{
|
||||
public:
|
||||
inline static double const kEqualityEpsilon = 1.0E-7;
|
||||
inline static std::string const kBorderExtension = ".poly";
|
||||
|
||||
void Init(std::string const & bordersDir);
|
||||
|
||||
void RemoveEmptySpaceBetweenBorders();
|
||||
|
||||
void DumpPolyFiles(std::string const & targetDir);
|
||||
Polygon const & GetBordersPolygonByName(std::string const & name) const;
|
||||
void PrintDiff();
|
||||
|
||||
private:
|
||||
/// \brief Some polygons can have sequentially same points - duplicates. This method removes such
|
||||
/// points and leaves only unique.
|
||||
size_t RemoveDuplicatePoints();
|
||||
|
||||
template <class PointsT>
|
||||
static size_t RemoveDuplicatingPointImpl(PointsT & points)
|
||||
{
|
||||
auto const equalFn = [](auto const & p1, auto const & p2) { return p1.EqualDxDy(p2, kEqualityEpsilon); };
|
||||
|
||||
auto const last = std::unique(points.begin(), points.end(), equalFn);
|
||||
size_t count = std::distance(last, points.end());
|
||||
points.erase(last, points.end());
|
||||
|
||||
while (points.size() > 1 && equalFn(points.front(), points.back()))
|
||||
{
|
||||
++count;
|
||||
points.pop_back();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// \brief Checks whether we can replace points from segment: [curLeftPointId, curRightPointId]
|
||||
/// of |curBorderId| to points from another border in order to get rid of empty space
|
||||
/// between curBorder and anotherBorder.
|
||||
base::ControlFlow TryToReplace(size_t curBorderId, size_t & curLeftPointId, size_t curRightPointId);
|
||||
|
||||
bool HasLinkAt(size_t curBorderId, size_t pointId);
|
||||
|
||||
/// \brief Replace points using |Polygon::ReplaceData| that is filled by
|
||||
/// |RemoveEmptySpaceBetweenBorders()|.
|
||||
void DoReplace();
|
||||
|
||||
size_t m_removedPointsCount = 0;
|
||||
size_t m_duplicatedPointsCount = 0;
|
||||
std::map<size_t, double> m_additionalAreaMetersSqr;
|
||||
|
||||
std::map<std::string, size_t> m_polyFileNameToIndex;
|
||||
std::map<size_t, std::string> m_indexToPolyFileName;
|
||||
std::vector<Polygon> m_bordersPolygons;
|
||||
std::vector<Polygon> m_prevCopy;
|
||||
};
|
||||
} // namespace poly_borders
|
||||
80
tools/poly_borders/help_structures.cpp
Normal file
80
tools/poly_borders/help_structures.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#include "poly_borders/help_structures.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
// Link --------------------------------------------------------------------------------------------
|
||||
bool Link::operator<(Link const & rhs) const
|
||||
{
|
||||
return std::tie(m_borderId, m_pointId) < std::tie(rhs.m_borderId, rhs.m_pointId);
|
||||
}
|
||||
|
||||
// ReplaceData -------------------------------------------------------------------------------------
|
||||
bool ReplaceData::operator<(ReplaceData const & rhs) const
|
||||
{
|
||||
return std::tie(m_dstFrom, m_dstTo) < std::tie(rhs.m_dstFrom, rhs.m_dstTo);
|
||||
}
|
||||
|
||||
// MarkedPoint -------------------------------------------------------------------------------------
|
||||
void MarkedPoint::AddLink(size_t borderId, size_t pointId)
|
||||
{
|
||||
m_links.emplace(borderId, pointId);
|
||||
}
|
||||
|
||||
std::optional<Link> MarkedPoint::GetLink(size_t curBorderId) const
|
||||
{
|
||||
if (m_links.size() != 1)
|
||||
return std::nullopt;
|
||||
|
||||
size_t anotherBorderId = m_links.begin()->m_borderId;
|
||||
if (anotherBorderId == curBorderId)
|
||||
return std::nullopt;
|
||||
|
||||
return *m_links.begin();
|
||||
}
|
||||
|
||||
// Polygon -----------------------------------------------------------------------------------------
|
||||
void Polygon::MakeFrozen(size_t a, size_t b)
|
||||
{
|
||||
CHECK_LESS(a, b, ());
|
||||
|
||||
// Ends of intervals shouldn't be frozen, we freeze only inner points: (a, b).
|
||||
// This condition is needed to drop such cases: a = x, b = x + 1, when
|
||||
// a + 1 will be greater than b - 1.
|
||||
if (b - a + 1 > 2)
|
||||
m_replaced.AddInterval(a + 1, b - 1);
|
||||
}
|
||||
|
||||
bool Polygon::IsFrozen(size_t a, size_t b) const
|
||||
{
|
||||
// We use LESS_OR_EQUAL because we want sometimes to check if
|
||||
// point i (associated with interval: [i, i]) is frozen.
|
||||
CHECK_LESS_OR_EQUAL(a, b, ());
|
||||
|
||||
return m_replaced.Intersects(a, b);
|
||||
}
|
||||
|
||||
void Polygon::AddReplaceInfo(size_t dstFrom, size_t dstTo, size_t srcFrom, size_t srcTo, size_t srcBorderId,
|
||||
bool reversed)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(dstFrom, dstTo, ());
|
||||
CHECK_LESS(srcFrom, srcTo, ());
|
||||
|
||||
CHECK(!IsFrozen(dstFrom, dstTo), ());
|
||||
MakeFrozen(dstFrom, dstTo);
|
||||
|
||||
m_replaceData.emplace(dstFrom, dstTo, srcFrom, srcTo, srcBorderId, reversed);
|
||||
}
|
||||
|
||||
std::set<ReplaceData>::const_iterator Polygon::FindReplaceData(size_t index)
|
||||
{
|
||||
for (auto it = m_replaceData.cbegin(); it != m_replaceData.cend(); ++it)
|
||||
if (it->m_dstFrom <= index && index <= it->m_dstTo)
|
||||
return it;
|
||||
|
||||
return m_replaceData.cend();
|
||||
}
|
||||
} // namespace poly_borders
|
||||
101
tools/poly_borders/help_structures.hpp
Normal file
101
tools/poly_borders/help_structures.hpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/non_intersecting_intervals.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
|
||||
struct Link
|
||||
{
|
||||
inline static auto constexpr kInvalidId = std::numeric_limits<size_t>::max();
|
||||
|
||||
Link() = default;
|
||||
Link(size_t borderId, size_t pointId) : m_borderId(borderId), m_pointId(pointId) {}
|
||||
|
||||
bool operator<(Link const & rhs) const;
|
||||
|
||||
size_t m_borderId = kInvalidId;
|
||||
size_t m_pointId = kInvalidId;
|
||||
};
|
||||
|
||||
/// \note Using next semantic here: [replaceFrom, replaceTo], [replaceFromSrc, replaceToSrc].
|
||||
struct ReplaceData
|
||||
{
|
||||
ReplaceData(size_t replaceFrom, size_t replaceTo, size_t replaceFromSrc, size_t replaceToSrc, size_t borderIdSrc,
|
||||
bool reversed)
|
||||
: m_dstFrom(replaceFrom)
|
||||
, m_dstTo(replaceTo)
|
||||
, m_srcReplaceFrom(replaceFromSrc)
|
||||
, m_srcReplaceTo(replaceToSrc)
|
||||
, m_srcBorderId(borderIdSrc)
|
||||
, m_reversed(reversed)
|
||||
{}
|
||||
|
||||
bool operator<(ReplaceData const & rhs) const;
|
||||
|
||||
size_t m_dstFrom;
|
||||
size_t m_dstTo;
|
||||
|
||||
size_t m_srcReplaceFrom;
|
||||
size_t m_srcReplaceTo;
|
||||
size_t m_srcBorderId;
|
||||
|
||||
// If |m_srcReplaceFrom| was greater than |m_srcReplaceTo|.
|
||||
bool m_reversed;
|
||||
};
|
||||
|
||||
struct MarkedPoint
|
||||
{
|
||||
MarkedPoint() = default;
|
||||
MarkedPoint(m2::PointD const & point) : m_point(point) {}
|
||||
|
||||
void AddLink(size_t borderId, size_t pointId);
|
||||
|
||||
std::optional<Link> GetLink(size_t curBorderId) const;
|
||||
|
||||
bool EqualDxDy(MarkedPoint const & p, double eps) const { return m_point.EqualDxDy(p.m_point, eps); }
|
||||
|
||||
m2::PointD m_point;
|
||||
std::set<Link> m_links;
|
||||
};
|
||||
|
||||
struct Polygon
|
||||
{
|
||||
Polygon() = default;
|
||||
Polygon(m2::RectD const & rect, std::vector<m2::PointD> const & points) : m_rect(rect)
|
||||
{
|
||||
m_points.assign(points.begin(), points.end());
|
||||
}
|
||||
Polygon(m2::RectD const & rect, std::vector<MarkedPoint> && points) : m_rect(rect), m_points(std::move(points)) {}
|
||||
|
||||
Polygon(Polygon &&) = default;
|
||||
Polygon & operator=(Polygon &&) noexcept = default;
|
||||
|
||||
// [a, b]
|
||||
// @{
|
||||
void MakeFrozen(size_t a, size_t b);
|
||||
bool IsFrozen(size_t a, size_t b) const;
|
||||
// @}
|
||||
|
||||
// [replaceFrom, replaceTo], [replaceFromSrc, replaceToSrc]
|
||||
void AddReplaceInfo(size_t replaceFrom, size_t replaceTo, size_t replaceFromSrc, size_t replaceToSrc,
|
||||
size_t borderIdSrc, bool reversed);
|
||||
|
||||
std::set<ReplaceData>::const_iterator FindReplaceData(size_t index);
|
||||
|
||||
m2::RectD m_rect;
|
||||
std::vector<MarkedPoint> m_points;
|
||||
base::NonIntersectingIntervals<size_t> m_replaced;
|
||||
std::set<ReplaceData> m_replaceData;
|
||||
};
|
||||
} // namespace poly_borders
|
||||
15
tools/poly_borders/poly_borders_tests/CMakeLists.txt
Normal file
15
tools/poly_borders/poly_borders_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
project(poly_borders_tests)
|
||||
|
||||
set(SRC
|
||||
mark_points_tests.cpp
|
||||
remove_empty_spaces_tests.cpp
|
||||
tools.cpp
|
||||
tools.hpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
poly_borders
|
||||
platform_tests_support
|
||||
)
|
||||
190
tools/poly_borders/poly_borders_tests/mark_points_tests.cpp
Normal file
190
tools/poly_borders/poly_borders_tests/mark_points_tests.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#include "poly_borders/poly_borders_tests/tools.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "poly_borders/borders_data.hpp"
|
||||
|
||||
#include "platform/platform_tests_support/scoped_dir.hpp"
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
#include "platform/platform_tests_support/writable_dir_changer.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace platform::tests_support;
|
||||
using namespace platform;
|
||||
using namespace poly_borders;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
static string const kTestDir = "borders_poly_dir";
|
||||
|
||||
void TestMarked(Polygon const & polygon, size_t i)
|
||||
{
|
||||
TEST(!polygon.m_points[i].m_links.empty(), (i, "th point point must be marked."));
|
||||
}
|
||||
|
||||
void TestNotMarked(Polygon const & polygon, size_t i)
|
||||
{
|
||||
TEST(polygon.m_points[i].m_links.empty(), (i, "th point must not be marked."));
|
||||
}
|
||||
|
||||
void CheckByMask(Polygon const & polygons, vector<bool> markedMask)
|
||||
{
|
||||
CHECK_EQUAL(polygons.m_points.size(), markedMask.size(), ());
|
||||
for (size_t i = 0; i < polygons.m_points.size(); ++i)
|
||||
if (markedMask[i])
|
||||
TestMarked(polygons, i);
|
||||
else
|
||||
TestNotMarked(polygons, i);
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_1)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(-1.0, -1.0);
|
||||
m2::PointD b(-1.0, 1.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, {1.0, 1.0}, {1.0, -1.0}}};
|
||||
|
||||
vector<vector<bool>> markedMask1 = {{true, true, false, false}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, {2.0, 1.0}, {5.0, -1.0}}};
|
||||
|
||||
vector<vector<bool>> markedMask2 = {{true, true, false, false}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
bordersData.Init(bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon1, markedMask1[0]);
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon2, markedMask2[0]);
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_2)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{{-1.0, -1.0}, {-1.0, 1.0}, {1.0, 1.0}, {1.0, -1.0}}};
|
||||
|
||||
vector<vector<bool>> markedMask1 = {{false, false, false, false}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{{-12.0, -1.0}, {-10.0, 1.0}, {2.0, 1.0}, {5.0, -1.0}}};
|
||||
|
||||
vector<vector<bool>> markedMask2 = {{false, false, false, false}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
bordersData.Init(bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon1, markedMask1[0]);
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon2, markedMask2[0]);
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_3)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(-2.0, 1.0);
|
||||
m2::PointD b(0.0, 3.0);
|
||||
m2::PointD c(3.0, -1.0);
|
||||
m2::PointD d(-1.0, -3.0);
|
||||
m2::PointD e(-4.0, 2.0);
|
||||
m2::PointD f(-1.0, 4.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, {1.0, -3.0}, d}};
|
||||
|
||||
vector<vector<bool>> markedMask1 = {{true, true, true, false, true}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{b, f, {2.0, 5.0}, {6.0, 3.0}, c}};
|
||||
|
||||
vector<vector<bool>> markedMask2 = {{true, true, false, false, true}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons3 = {{a, b, f, {-3.0, 4.0}, e}};
|
||||
|
||||
vector<vector<bool>> markedMask3 = {{true, true, true, false, true}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons4 = {{a, e, {-3.0, -1.0}, d}};
|
||||
|
||||
vector<vector<bool>> markedMask4 = {{true, true, false, true}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Third", polygons3));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Fourth", polygons4));
|
||||
|
||||
BordersData bordersData;
|
||||
bordersData.Init(bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon1, markedMask1[0]);
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon2, markedMask2[0]);
|
||||
|
||||
auto const & bordersPolygon3 = bordersData.GetBordersPolygonByName("Third" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon3, markedMask3[0]);
|
||||
|
||||
auto const & bordersPolygon4 = bordersData.GetBordersPolygonByName("Fourth" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon4, markedMask4[0]);
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_MarkPoints_4)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(6.0, 2.0);
|
||||
m2::PointD b(6.0, 4.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{{-2.0, -2.0}, {-2.0, 2.0}, {2.0, 2.0}, {2.0, -2.0}},
|
||||
{{4.0, 2.0}, {4.0, 4.0}, a, b}};
|
||||
|
||||
vector<vector<bool>> markedMask1 = {{false, false, false, false}, {false, false, true, true}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, {8.0, 6.0}, {8.0, 0.0}}};
|
||||
|
||||
vector<vector<bool>> markedMask2 = {{true, true, false, false}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
bordersData.Init(bordersDir);
|
||||
|
||||
auto const & firstBordersPolygon1 =
|
||||
bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(firstBordersPolygon1, markedMask1[0]);
|
||||
|
||||
auto const & secondBordersPolygon1 =
|
||||
bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "2");
|
||||
CheckByMask(secondBordersPolygon1, markedMask1[1]);
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
CheckByMask(bordersPolygon2, markedMask2[0]);
|
||||
}
|
||||
} // namespace
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
#include "poly_borders/poly_borders_tests/tools.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "poly_borders/borders_data.hpp"
|
||||
|
||||
#include "platform/platform_tests_support/scoped_dir.hpp"
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace remove_empty_spaces_tests
|
||||
{
|
||||
using namespace platform::tests_support;
|
||||
using namespace platform;
|
||||
using namespace poly_borders;
|
||||
using namespace std;
|
||||
|
||||
string const kTestDir = "borders_poly_dir";
|
||||
auto constexpr kSmallShift = 1e-9;
|
||||
auto constexpr kSmallPointShift = m2::PointD(kSmallShift, kSmallShift);
|
||||
|
||||
void Process(BordersData & bordersData, string const & bordersDir)
|
||||
{
|
||||
bordersData.Init(bordersDir);
|
||||
bordersData.RemoveEmptySpaceBetweenBorders();
|
||||
}
|
||||
|
||||
bool ConsistsOf(Polygon const & polygon, vector<m2::PointD> const & points)
|
||||
{
|
||||
CHECK_EQUAL(polygon.m_points.size(), points.size(), ());
|
||||
|
||||
set<size_t> used;
|
||||
for (auto const & point : points)
|
||||
{
|
||||
for (size_t i = 0; i < polygon.m_points.size(); ++i)
|
||||
{
|
||||
static double constexpr kEps = 1e-5;
|
||||
if (AlmostEqualAbs(point, polygon.m_points[i].m_point, kEps) && used.count(i) == 0)
|
||||
{
|
||||
used.emplace(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return used.size() == points.size();
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_1)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(1.0, 0.0);
|
||||
m2::PointD c(2.0, 0.0);
|
||||
m2::PointD d(3.0, 0.0);
|
||||
m2::PointD e(4.0, 0.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, d, e}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, c, d, e}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, b, c, d, e}), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_2)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(1.0, 0.0);
|
||||
// We should make c.y small because in other case changed area
|
||||
// will be so great, that point |c| will not be removed.
|
||||
m2::PointD c(2.0, kSmallShift);
|
||||
m2::PointD d(3.0, 0.0);
|
||||
m2::PointD e(4.0, 0.0);
|
||||
|
||||
// Point |c| is absent from polygons2, algorithm should remove |c| from polygon1.
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, d, e}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, d, e}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, b, d, e}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, b, d, e}), ());
|
||||
}
|
||||
|
||||
// Like |PolyBordersPostprocessor_RemoveEmptySpaces_2| but two points will be
|
||||
// added instead of one.
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_3)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(1.0, 0.0);
|
||||
// We should make c.y (and d.y) small because in other case changed area
|
||||
// will be so great, that point |c| (|d|) will not be removed.
|
||||
m2::PointD c(2.0, kSmallShift);
|
||||
m2::PointD d(2.5, kSmallShift);
|
||||
m2::PointD e(4.0, 0.0);
|
||||
m2::PointD f(5.0, 0.0);
|
||||
|
||||
// Point |c| and |d| is absent from polygons2, algorithm should remove |c| from polygon1.
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, d, e, f}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, e, f}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, b, e, f}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, b, e, f}), ());
|
||||
}
|
||||
|
||||
// Do not remove point |c| because changed area is too big.
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_4)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(1.0, 0.0);
|
||||
m2::PointD c(2.0, 1.0);
|
||||
m2::PointD d(4.0, 0.0);
|
||||
m2::PointD e(5.0, 0.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, d, e}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, b, d, e}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, b, d, e}), ());
|
||||
}
|
||||
|
||||
// Replace {c1, d1, e1} -> {c2, d2}.
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_5)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(9.0, 0.0);
|
||||
|
||||
m2::PointD c1(2.0, 3.0);
|
||||
m2::PointD d1(4.0, 4.0);
|
||||
m2::PointD e1(d1 + kSmallPointShift + kSmallPointShift);
|
||||
|
||||
m2::PointD c2(c1 + kSmallPointShift);
|
||||
m2::PointD d2(d1 + kSmallPointShift);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, c1, d1, e1, b}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, c2, d2, b}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, c2, d2, b}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, c2, d2, b}), ());
|
||||
}
|
||||
|
||||
// Removes duplicates.
|
||||
UNIT_TEST(PolyBordersPostprocessor_RemoveEmptySpaces_6)
|
||||
{
|
||||
ScopedDir const scopedDir(kTestDir);
|
||||
string const & bordersDir = scopedDir.GetFullPath();
|
||||
|
||||
m2::PointD a(0.0, 0.0);
|
||||
m2::PointD b(1.0, 0.0);
|
||||
m2::PointD c(2.0, 1.0);
|
||||
m2::PointD d(4.0, 0.0);
|
||||
m2::PointD e(5.0, 0.0);
|
||||
|
||||
vector<vector<m2::PointD>> polygons1 = {{a, b, c, d, d, d, e, e, e}};
|
||||
|
||||
vector<vector<m2::PointD>> polygons2 = {{a, d, d, d, e}};
|
||||
|
||||
vector<shared_ptr<ScopedFile>> files;
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "First", polygons1));
|
||||
files.emplace_back(CreatePolyBorderFileByPolygon(kTestDir, "Second", polygons2));
|
||||
|
||||
BordersData bordersData;
|
||||
Process(bordersData, bordersDir);
|
||||
|
||||
auto const & bordersPolygon1 = bordersData.GetBordersPolygonByName("First" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon1, {a, b, c, d, e}), ());
|
||||
|
||||
auto const & bordersPolygon2 = bordersData.GetBordersPolygonByName("Second" + BordersData::kBorderExtension + "1");
|
||||
TEST(ConsistsOf(bordersPolygon2, {a, d, e}), ());
|
||||
}
|
||||
} // namespace remove_empty_spaces_tests
|
||||
45
tools/poly_borders/poly_borders_tests/tools.cpp
Normal file
45
tools/poly_borders/poly_borders_tests/tools.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#include "poly_borders/poly_borders_tests/tools.hpp"
|
||||
|
||||
#include "poly_borders/borders_data.hpp"
|
||||
|
||||
#include "generator/borders.hpp"
|
||||
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace platform::tests_support;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::vector<m2::RegionD> ConvertFromPointsVector(std::vector<std::vector<m2::PointD>> const & polygons)
|
||||
{
|
||||
std::vector<m2::RegionD> res;
|
||||
res.reserve(polygons.size());
|
||||
for (auto const & polygon : polygons)
|
||||
res.emplace_back(polygon);
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
std::shared_ptr<ScopedFile> CreatePolyBorderFileByPolygon(std::string const & relativeDirPath, std::string const & name,
|
||||
std::vector<std::vector<m2::PointD>> const & polygons)
|
||||
{
|
||||
std::string path = base::JoinPath(relativeDirPath, name + BordersData::kBorderExtension);
|
||||
|
||||
auto file = std::make_shared<ScopedFile>(path, ScopedFile::Mode::Create);
|
||||
|
||||
auto const targetDir = base::GetDirectory(file->GetFullPath());
|
||||
|
||||
auto const regions = ConvertFromPointsVector(polygons);
|
||||
borders::DumpBorderToPolyFile(targetDir, name, regions);
|
||||
|
||||
return file;
|
||||
}
|
||||
} // namespace poly_borders
|
||||
16
tools/poly_borders/poly_borders_tests/tools.hpp
Normal file
16
tools/poly_borders/poly_borders_tests/tools.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "platform/platform_tests_support/scoped_file.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace poly_borders
|
||||
{
|
||||
std::shared_ptr<platform::tests_support::ScopedFile> CreatePolyBorderFileByPolygon(
|
||||
std::string const & relativeDirPath, std::string const & name,
|
||||
std::vector<std::vector<m2::PointD>> const & polygons);
|
||||
} // namespace poly_borders
|
||||
10
tools/poly_borders/poly_borders_tool/CMakeLists.txt
Normal file
10
tools/poly_borders/poly_borders_tool/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
project(poly_borders_tool)
|
||||
|
||||
set(SRC poly_borders_tool.cpp)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
poly_borders
|
||||
gflags::gflags
|
||||
)
|
||||
64
tools/poly_borders/poly_borders_tool/poly_borders_tool.cpp
Normal file
64
tools/poly_borders/poly_borders_tool/poly_borders_tool.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "poly_borders/borders_data.hpp"
|
||||
#include "poly_borders/help_structures.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
DEFINE_string(borders_path, "", "Path to directory with *.poly files.");
|
||||
DEFINE_string(output_path, "", "Path to target directory where the output *.poly files will be placed.");
|
||||
|
||||
using namespace poly_borders;
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
gflags::SetUsageMessage(
|
||||
"\n\n\tThe tool is used to process *.poly borders files. We use such files\n"
|
||||
"\tto cut the planet into mwms in generator. The problem is that we have\n"
|
||||
"\tempty spaces between neighbouring borders. This tool creates new borders\n"
|
||||
"\tbased on input data by removing points from borders in such a way that the\n"
|
||||
"\tchanged area of each border will not be too large.\n"
|
||||
"\tArguments:\n"
|
||||
"\t\t--borders_path=/path/to/directory/with/borders\n"
|
||||
"\t\t--output_path=/path/to/directory/where/new/borders/will/be/placed\n");
|
||||
|
||||
if (FLAGS_borders_path.empty() || FLAGS_output_path.empty())
|
||||
{
|
||||
gflags::ShowUsageWithFlags("poly_borders_postprocessor");
|
||||
return 0;
|
||||
}
|
||||
|
||||
CHECK_NOT_EQUAL(FLAGS_borders_path, FLAGS_output_path, ());
|
||||
CHECK(Platform::IsDirectory(FLAGS_borders_path), ("Cannot find directory:", FLAGS_borders_path));
|
||||
CHECK(Platform::IsDirectory(FLAGS_output_path), ("Cannot find directory:", FLAGS_output_path));
|
||||
|
||||
try
|
||||
{
|
||||
BordersData data;
|
||||
data.Init(FLAGS_borders_path);
|
||||
data.RemoveEmptySpaceBetweenBorders();
|
||||
data.PrintDiff();
|
||||
data.DumpPolyFiles(FLAGS_output_path);
|
||||
}
|
||||
catch (RootException const & e)
|
||||
{
|
||||
LOG(LERROR, ("Core exception:", e.Msg()));
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LERROR, ("Std exception:", e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG(LERROR, ("Unknown exception."));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
2
tools/python/.gitignore
vendored
Normal file
2
tools/python/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
osm_cache.json
|
||||
transit/18*/
|
||||
57
tools/python/InstrumentsTraceParser.py
Executable file
57
tools/python/InstrumentsTraceParser.py
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import print_function
|
||||
import struct
|
||||
import sys
|
||||
import numpy
|
||||
|
||||
|
||||
class Analyzer:
|
||||
"""
|
||||
The binary format is
|
||||
time since the beginning of the measurement : double
|
||||
unknown and irrelevant field : double
|
||||
momentary consumption calculated for the current time segment : double
|
||||
"""
|
||||
def __init__(self):
|
||||
self.duration = 0.0
|
||||
self.consumption = []
|
||||
self.mean = 0.0
|
||||
self.std = 0.0
|
||||
self.avg = 0.0
|
||||
self.averages = []
|
||||
|
||||
|
||||
def read_file(self, file_path):
|
||||
binary = bytearray()
|
||||
with open(file_path, "r") as f:
|
||||
binary = bytearray(f.read())
|
||||
|
||||
for i in range(0, len(binary) - 24, 24):
|
||||
res = struct.unpack(">ddd", binary[i:i+24])
|
||||
|
||||
current_duration = res[0]
|
||||
if not current_duration > self.duration:
|
||||
print("Unexpected elapsed time value, lower than the previous one.")
|
||||
exit(2) # this should never happen because the file is written sequentially
|
||||
|
||||
current_consumption = res[2]
|
||||
self.averages.append(current_consumption / (current_duration - self.duration))
|
||||
self.duration = current_duration
|
||||
|
||||
self.consumption.append(current_consumption)
|
||||
|
||||
self.calculate_stats()
|
||||
|
||||
|
||||
def calculate_stats(self):
|
||||
self.mean = numpy.mean(self.averages)
|
||||
self.std = numpy.std(self.averages)
|
||||
self.avg = sum(self.consumption) / self.duration
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for file_path in sys.argv[1:]:
|
||||
analyzer = Analyzer()
|
||||
analyzer.read_file(file_path)
|
||||
print("{}\n\tavg: {}\n\tmean: {}\n\tstd: {}".format(file_path, analyzer.avg, analyzer.mean, analyzer.std))
|
||||
14
tools/python/Util.py
Normal file
14
tools/python/Util.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from contextlib import contextmanager
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from tempfile import TemporaryDirectory
|
||||
except ImportError:
|
||||
@contextmanager
|
||||
def TemporaryDirectory():
|
||||
name = tempfile.mkdtemp()
|
||||
try:
|
||||
yield name
|
||||
finally:
|
||||
shutil.rmtree(name)
|
||||
0
tools/python/__init__.py
Normal file
0
tools/python/__init__.py
Normal file
16
tools/python/airmaps/README.md
Normal file
16
tools/python/airmaps/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# airmaps - building of maps using airflow.
|
||||
|
||||
## Storage
|
||||
|
||||
Repository of result and temporary files.
|
||||
Currently, the storage is a webdav server.
|
||||
|
||||
## Description of DAGs:
|
||||
|
||||
1. Update_planet - updates .o5m planet file.
|
||||
|
||||
2. Build_coastline - builds coastline files.
|
||||
|
||||
3. Generate_open_source_maps - builds free maps for maps.me
|
||||
|
||||
All results will be published on the storage.
|
||||
12
tools/python/airmaps/__init__.py
Normal file
12
tools/python/airmaps/__init__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import os
|
||||
|
||||
from airmaps.instruments import settings
|
||||
|
||||
CONFIG_PATH = os.path.join(
|
||||
os.path.dirname(os.path.join(os.path.realpath(__file__))),
|
||||
"var",
|
||||
"etc",
|
||||
"airmaps.ini",
|
||||
)
|
||||
|
||||
settings.init(CONFIG_PATH)
|
||||
93
tools/python/airmaps/dags/build_coastline.py
Normal file
93
tools/python/airmaps/dags/build_coastline.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from datetime import timedelta
|
||||
|
||||
from airflow import DAG
|
||||
from airflow.operators.python_operator import PythonOperator
|
||||
from airflow.utils.dates import days_ago
|
||||
|
||||
from airmaps.instruments import settings
|
||||
from airmaps.instruments import storage
|
||||
from airmaps.instruments.utils import get_latest_filename
|
||||
from airmaps.instruments.utils import make_rm_build_task
|
||||
from airmaps.instruments.utils import put_current_date_in_filename
|
||||
from airmaps.instruments.utils import rm_build
|
||||
from maps_generator.generator import stages_declaration as sd
|
||||
from maps_generator.generator.env import Env
|
||||
from maps_generator.generator.env import WORLD_COASTS_NAME
|
||||
from maps_generator.maps_generator import run_generation
|
||||
|
||||
logger = logging.getLogger("airmaps")
|
||||
|
||||
|
||||
DAG = DAG(
|
||||
"Build_coastline",
|
||||
schedule_interval=timedelta(days=1),
|
||||
default_args={
|
||||
"owner": "OMaps",
|
||||
"depends_on_past": True,
|
||||
"start_date": days_ago(0),
|
||||
"email": settings.EMAILS,
|
||||
"email_on_failure": True,
|
||||
"email_on_retry": False,
|
||||
"retries": 0,
|
||||
"retry_delay": timedelta(minutes=5),
|
||||
"priority_weight": 1,
|
||||
},
|
||||
)
|
||||
|
||||
COASTLINE_STORAGE_PATH = f"{settings.STORAGE_PREFIX}/coasts"
|
||||
|
||||
|
||||
def publish_coastline(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
env = Env(build_name=build_name)
|
||||
for name in (f"{WORLD_COASTS_NAME}.geom", f"{WORLD_COASTS_NAME}.rawgeom"):
|
||||
coastline = put_current_date_in_filename(name)
|
||||
latest = get_latest_filename(name)
|
||||
coastline_full = os.path.join(env.paths.coastline_path, coastline)
|
||||
latest_full = os.path.join(env.paths.coastline_path, latest)
|
||||
shutil.move(os.path.join(env.paths.coastline_path, name), coastline_full)
|
||||
os.symlink(coastline, latest_full)
|
||||
|
||||
storage.wd_publish(coastline_full, f"{COASTLINE_STORAGE_PATH}/{coastline}")
|
||||
storage.wd_publish(latest_full, f"{COASTLINE_STORAGE_PATH}/{latest}")
|
||||
|
||||
|
||||
def build_coastline(**kwargs):
|
||||
env = Env()
|
||||
kwargs["ti"].xcom_push(key="build_name", value=env.build_name)
|
||||
|
||||
run_generation(
|
||||
env,
|
||||
(
|
||||
sd.StageDownloadAndConvertPlanet(),
|
||||
sd.StageCoastline(use_old_if_fail=False),
|
||||
sd.StageCleanup(),
|
||||
),
|
||||
)
|
||||
env.finish()
|
||||
|
||||
|
||||
BUILD_COASTLINE_TASK = PythonOperator(
|
||||
task_id="Build_coastline_task",
|
||||
provide_context=True,
|
||||
python_callable=build_coastline,
|
||||
on_failure_callback=lambda ctx: rm_build(**ctx),
|
||||
dag=DAG,
|
||||
)
|
||||
|
||||
|
||||
PUBLISH_COASTLINE_TASK = PythonOperator(
|
||||
task_id="Publish_coastline_task",
|
||||
provide_context=True,
|
||||
python_callable=publish_coastline,
|
||||
dag=DAG,
|
||||
)
|
||||
|
||||
|
||||
RM_BUILD_TASK = make_rm_build_task(DAG)
|
||||
|
||||
|
||||
BUILD_COASTLINE_TASK >> PUBLISH_COASTLINE_TASK >> RM_BUILD_TASK
|
||||
154
tools/python/airmaps/dags/build_maps.py
Normal file
154
tools/python/airmaps/dags/build_maps.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from airflow import DAG
|
||||
from airflow.operators.python_operator import PythonOperator
|
||||
from airflow.utils.dates import days_ago
|
||||
|
||||
from airmaps.instruments import settings
|
||||
from airmaps.instruments import storage
|
||||
from airmaps.instruments.utils import make_rm_build_task
|
||||
from airmaps.instruments.utils import run_generation_from_first_stage
|
||||
from maps_generator.generator import stages_declaration as sd
|
||||
from maps_generator.generator.env import Env
|
||||
from maps_generator.generator.env import PathProvider
|
||||
from maps_generator.generator.env import get_all_countries_list
|
||||
from maps_generator.maps_generator import run_generation
|
||||
|
||||
logger = logging.getLogger("airmaps")
|
||||
|
||||
|
||||
MAPS_STORAGE_PATH = f"{settings.STORAGE_PREFIX}/maps"
|
||||
|
||||
|
||||
class MapsGenerationDAG(DAG):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
build_prolog_task = PythonOperator(
|
||||
task_id="Build_prolog_task",
|
||||
provide_context=True,
|
||||
python_callable=MapsGenerationDAG.build_prolog,
|
||||
dag=self,
|
||||
)
|
||||
|
||||
build_epilog_task = PythonOperator(
|
||||
task_id="Build_epilog_task",
|
||||
provide_context=True,
|
||||
python_callable=MapsGenerationDAG.build_epilog,
|
||||
dag=self,
|
||||
)
|
||||
|
||||
publish_maps_task = PythonOperator(
|
||||
task_id="Publish_maps_task",
|
||||
provide_context=True,
|
||||
python_callable=MapsGenerationDAG.publish_maps,
|
||||
dag=self,
|
||||
)
|
||||
|
||||
rm_build_task = make_rm_build_task(self)
|
||||
|
||||
build_epilog_task >> publish_maps_task >> rm_build_task
|
||||
for country in get_all_countries_list(PathProvider.borders_path()):
|
||||
build_prolog_task >> self.make_mwm_operator(country) >> build_epilog_task
|
||||
|
||||
@staticmethod
|
||||
def get_params(namespace="env", **kwargs):
|
||||
return kwargs.get("params", {}).get(namespace, {})
|
||||
|
||||
@staticmethod
|
||||
def build_prolog(**kwargs):
|
||||
params = MapsGenerationDAG.get_params(**kwargs)
|
||||
env = Env(**params)
|
||||
kwargs["ti"].xcom_push(key="build_name", value=env.build_name)
|
||||
run_generation(
|
||||
env,
|
||||
(
|
||||
sd.StageDownloadAndConvertPlanet(),
|
||||
sd.StageCoastline(),
|
||||
sd.StagePreprocess(),
|
||||
sd.StageFeatures(),
|
||||
sd.StageDownloadDescriptions(),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def make_build_mwm_func(country):
|
||||
def build_mwm(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
params = MapsGenerationDAG.get_params(**kwargs)
|
||||
params.update({"build_name": build_name, "countries": [country,]})
|
||||
env = Env(**params)
|
||||
# We need to check existing of mwm.tmp. It is needed if we want to
|
||||
# build mwms from part of planet.
|
||||
tmp_mwm_name = env.get_tmp_mwm_names()
|
||||
assert len(tmp_mwm_name) <= 1
|
||||
if not tmp_mwm_name:
|
||||
logger.warning(f"mwm.tmp does not exist for {country}.")
|
||||
return
|
||||
|
||||
run_generation_from_first_stage(env, (sd.StageMwm(),), build_lock=False)
|
||||
|
||||
return build_mwm
|
||||
|
||||
@staticmethod
|
||||
def build_epilog(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
params = MapsGenerationDAG.get_params(**kwargs)
|
||||
params.update({"build_name": build_name})
|
||||
env = Env(**params)
|
||||
run_generation_from_first_stage(
|
||||
env,
|
||||
(
|
||||
sd.StageCountriesTxt(),
|
||||
sd.StageLocalAds(),
|
||||
sd.StageStatistics(),
|
||||
sd.StageCleanup(),
|
||||
),
|
||||
)
|
||||
env.finish()
|
||||
|
||||
@staticmethod
|
||||
def publish_maps(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
params = MapsGenerationDAG.get_params(**kwargs)
|
||||
params.update({"build_name": build_name})
|
||||
env = Env(**params)
|
||||
subdir = MapsGenerationDAG.get_params(namespace="storage", **kwargs)["subdir"]
|
||||
storage_path = f"{MAPS_STORAGE_PATH}/{subdir}"
|
||||
storage.wd_publish(env.paths.mwm_path, f"{storage_path}/{env.mwm_version}/")
|
||||
|
||||
def make_mwm_operator(self, country):
|
||||
normalized_name = "__".join(country.lower().split())
|
||||
return PythonOperator(
|
||||
task_id=f"Build_country_{normalized_name}_task",
|
||||
provide_context=True,
|
||||
python_callable=MapsGenerationDAG.make_build_mwm_func(country),
|
||||
dag=self,
|
||||
)
|
||||
|
||||
|
||||
PARAMS = {"storage": {"subdir": "open_source"}}
|
||||
if settings.DEBUG:
|
||||
PARAMS["env"] = {
|
||||
# The planet file in debug mode does not contain Russia_Moscow territory.
|
||||
# It is needed for testing.
|
||||
"countries": ["Cuba", "Haiti", "Jamaica", "Cayman Islands", "Russia_Moscow"]
|
||||
}
|
||||
|
||||
OPEN_SOURCE_MAPS_GENERATION_DAG = MapsGenerationDAG(
|
||||
"Generate_open_source_maps",
|
||||
schedule_interval=timedelta(days=7),
|
||||
default_args={
|
||||
"owner": "OMaps",
|
||||
"depends_on_past": True,
|
||||
"start_date": days_ago(0),
|
||||
"email": settings.EMAILS,
|
||||
"email_on_failure": True,
|
||||
"email_on_retry": False,
|
||||
"retries": 0,
|
||||
"retry_delay": timedelta(minutes=5),
|
||||
"priority_weight": 1,
|
||||
"params": PARAMS,
|
||||
},
|
||||
)
|
||||
83
tools/python/airmaps/dags/update_planet.py
Normal file
83
tools/python/airmaps/dags/update_planet.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from airflow import DAG
|
||||
from airflow.operators.python_operator import PythonOperator
|
||||
from airflow.utils.dates import days_ago
|
||||
|
||||
from airmaps.instruments import settings
|
||||
from airmaps.instruments import storage
|
||||
from airmaps.instruments.utils import make_rm_build_task
|
||||
from maps_generator.generator import stages_declaration as sd
|
||||
from maps_generator.generator.env import Env
|
||||
from maps_generator.maps_generator import run_generation
|
||||
from maps_generator.utils.md5 import md5_ext
|
||||
|
||||
logger = logging.getLogger("airmaps")
|
||||
|
||||
|
||||
DAG = DAG(
|
||||
"Update_planet",
|
||||
schedule_interval=timedelta(days=1),
|
||||
default_args={
|
||||
"owner": "OMaps",
|
||||
"depends_on_past": True,
|
||||
"start_date": days_ago(0),
|
||||
"email": settings.EMAILS,
|
||||
"email_on_failure": True,
|
||||
"email_on_retry": False,
|
||||
"retries": 0,
|
||||
"retry_delay": timedelta(minutes=5),
|
||||
"priority_weight": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
PLANET_STORAGE_PATH = f"{settings.STORAGE_PREFIX}/planet_regular/planet-latest.o5m"
|
||||
|
||||
|
||||
def update_planet(**kwargs):
|
||||
env = Env()
|
||||
kwargs["ti"].xcom_push(key="build_name", value=env.build_name)
|
||||
|
||||
if settings.DEBUG:
|
||||
env.add_skipped_stage(sd.StageUpdatePlanet)
|
||||
|
||||
run_generation(
|
||||
env,
|
||||
(
|
||||
sd.StageDownloadAndConvertPlanet(),
|
||||
sd.StageUpdatePlanet(),
|
||||
sd.StageCleanup(),
|
||||
),
|
||||
)
|
||||
env.finish()
|
||||
|
||||
|
||||
def publish_planet(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
env = Env(build_name=build_name)
|
||||
storage.wd_publish(env.paths.planet_o5m, PLANET_STORAGE_PATH)
|
||||
storage.wd_publish(md5_ext(env.paths.planet_o5m), md5_ext(PLANET_STORAGE_PATH))
|
||||
|
||||
|
||||
UPDATE_PLANET_TASK = PythonOperator(
|
||||
task_id="Update_planet_task",
|
||||
provide_context=True,
|
||||
python_callable=update_planet,
|
||||
dag=DAG,
|
||||
)
|
||||
|
||||
|
||||
PUBLISH_PLANET_TASK = PythonOperator(
|
||||
task_id="Publish_planet_task",
|
||||
provide_context=True,
|
||||
python_callable=publish_planet,
|
||||
dag=DAG,
|
||||
)
|
||||
|
||||
|
||||
RM_BUILD_TASK = make_rm_build_task(DAG)
|
||||
|
||||
|
||||
UPDATE_PLANET_TASK >> PUBLISH_PLANET_TASK >> RM_BUILD_TASK
|
||||
62
tools/python/airmaps/instruments/settings.py
Normal file
62
tools/python/airmaps/instruments/settings.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import sys
|
||||
from typing import AnyStr
|
||||
|
||||
from maps_generator.generator import settings
|
||||
|
||||
STORAGE_PREFIX = ""
|
||||
|
||||
# Storage settings
|
||||
WD_HOST = ""
|
||||
WD_LOGIN = ""
|
||||
WD_PASSWORD = ""
|
||||
|
||||
# Common section
|
||||
EMAILS = []
|
||||
|
||||
settings.LOGGING["loggers"]["airmaps"] = {
|
||||
"handlers": ["stdout", "file"],
|
||||
"level": "DEBUG",
|
||||
"propagate": True,
|
||||
}
|
||||
|
||||
|
||||
def get_airmaps_emails(emails: AnyStr):
|
||||
if not emails:
|
||||
return []
|
||||
|
||||
for ch in [",", ";", ":"]:
|
||||
emails.replace(ch, " ")
|
||||
|
||||
return list(filter(None, [e.strpip() for e in emails.join(" ")]))
|
||||
|
||||
|
||||
def init(default_settings_path: AnyStr):
|
||||
settings.init(default_settings_path)
|
||||
|
||||
# Try to read a config and to overload default settings
|
||||
cfg = settings.CfgReader(default_settings_path)
|
||||
|
||||
# Storage section
|
||||
global WD_HOST
|
||||
global WD_LOGIN
|
||||
global WD_PASSWORD
|
||||
|
||||
WD_HOST = cfg.get_opt("Storage", "WD_HOST", WD_HOST)
|
||||
WD_LOGIN = cfg.get_opt("Storage", "WD_LOGIN", WD_LOGIN)
|
||||
WD_PASSWORD = cfg.get_opt("Storage", "WD_PASSWORD", WD_PASSWORD)
|
||||
|
||||
# Common section
|
||||
global EMAILS
|
||||
emails = cfg.get_opt("Common", "EMAILS", "")
|
||||
EMAILS = get_airmaps_emails(emails)
|
||||
|
||||
# Import all contains from maps_generator.generator.settings.
|
||||
thismodule = sys.modules[__name__]
|
||||
for name in dir(settings):
|
||||
if not name.startswith("_") and name.isupper():
|
||||
value = getattr(settings, name)
|
||||
setattr(thismodule, name, value)
|
||||
|
||||
global STORAGE_PREFIX
|
||||
if settings.DEBUG:
|
||||
STORAGE_PREFIX = "/tests"
|
||||
27
tools/python/airmaps/instruments/storage.py
Normal file
27
tools/python/airmaps/instruments/storage.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import logging
|
||||
|
||||
import webdav.client as wc
|
||||
|
||||
from airmaps.instruments import settings
|
||||
|
||||
logger = logging.getLogger("airmaps")
|
||||
|
||||
WD_OPTIONS = {
|
||||
"webdav_hostname": settings.WD_HOST,
|
||||
"webdav_login": settings.WD_LOGIN,
|
||||
"webdav_password": settings.WD_PASSWORD,
|
||||
}
|
||||
|
||||
|
||||
def wd_fetch(src, dst):
|
||||
logger.info(f"Fetch form {src} to {dst} with options {WD_OPTIONS}.")
|
||||
client = wc.Client(WD_OPTIONS)
|
||||
client.download_sync(src, dst)
|
||||
|
||||
|
||||
def wd_publish(src, dst):
|
||||
logger.info(f"Publish form {src} to {dst} with options {WD_OPTIONS}.")
|
||||
client = wc.Client(WD_OPTIONS)
|
||||
tmp = f"{dst[:-1]}__/" if dst[-1] == "/" else f"{dst}__"
|
||||
client.upload_sync(local_path=src, remote_path=tmp)
|
||||
client.move(remote_path_from=tmp, remote_path_to=dst)
|
||||
48
tools/python/airmaps/instruments/utils.py
Normal file
48
tools/python/airmaps/instruments/utils.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from typing import Iterable
|
||||
|
||||
from airflow.operators.python_operator import PythonOperator
|
||||
|
||||
from maps_generator.generator.env import Env
|
||||
from maps_generator.generator.stages import Stage
|
||||
from maps_generator.generator.stages import get_stage_name
|
||||
from maps_generator.maps_generator import run_generation
|
||||
|
||||
|
||||
def put_current_date_in_filename(filename):
|
||||
path, name = os.path.split(filename)
|
||||
parts = name.split(".", maxsplit=1)
|
||||
parts[0] += f"__{datetime.today().strftime('%Y_%m_%d')}"
|
||||
return os.path.join(path, ".".join(parts))
|
||||
|
||||
|
||||
def get_latest_filename(filename, prefix=""):
|
||||
path, name = os.path.split(filename)
|
||||
parts = name.split(".", maxsplit=1)
|
||||
assert len(parts) != 0, parts
|
||||
parts[0] = f"{prefix}latest"
|
||||
return os.path.join(path, ".".join(parts))
|
||||
|
||||
|
||||
def rm_build(**kwargs):
|
||||
build_name = kwargs["ti"].xcom_pull(key="build_name")
|
||||
env = Env(build_name=build_name)
|
||||
shutil.rmtree(env.build_path)
|
||||
|
||||
|
||||
def make_rm_build_task(dag):
|
||||
return PythonOperator(
|
||||
task_id="Rm_build_task",
|
||||
provide_context=True,
|
||||
python_callable=rm_build,
|
||||
dag=dag,
|
||||
)
|
||||
|
||||
|
||||
def run_generation_from_first_stage(
|
||||
env: Env, stages: Iterable[Stage], build_lock: bool = True
|
||||
):
|
||||
from_stage = get_stage_name(next(iter(stages)))
|
||||
run_generation(env, stages, from_stage, build_lock)
|
||||
6
tools/python/airmaps/requirements.txt
Normal file
6
tools/python/airmaps/requirements.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
omim-data-all
|
||||
omim-maps_generator
|
||||
apache-airflow [postgres]==1.10.10
|
||||
psycopg2-binary==2.8.4
|
||||
cryptography>=41.0.0
|
||||
webdavclient==1.0.8
|
||||
5
tools/python/airmaps/requirements_dev.txt
Normal file
5
tools/python/airmaps/requirements_dev.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
-r ../maps_generator/requirements_dev.txt
|
||||
apache-airflow [postgres]==1.10.10
|
||||
psycopg2-binary==2.8.4
|
||||
cryptography>=41.0.0
|
||||
webdavclient==1.0.8
|
||||
4
tools/python/airmaps/sandbox/.dockerignore
Normal file
4
tools/python/airmaps/sandbox/.dockerignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
./.git*
|
||||
./android
|
||||
./iphone
|
||||
./xcode
|
||||
92
tools/python/airmaps/sandbox/README.md
Normal file
92
tools/python/airmaps/sandbox/README.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# Sandbox
|
||||
|
||||
This project can show how airmaps works on your computer.
|
||||
|
||||
## Setup
|
||||
|
||||
You must have [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
0. Change working directory:
|
||||
|
||||
```sh
|
||||
$ cd omim/tools/python/airmaps/sandbox
|
||||
```
|
||||
|
||||
1. Build airmaps service:
|
||||
|
||||
```sh
|
||||
sandbox$ ./build.sh
|
||||
```
|
||||
|
||||
2. Create storage(sandbox/storage directory):
|
||||
|
||||
```sh
|
||||
sandbox$ ./create_storage.sh
|
||||
```
|
||||
|
||||
Note: May be you need `sudo`, because `./create_storage.sh` to try change an owner of `sandbox/storage/tests` directory.
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting
|
||||
|
||||
0. Change working directory:
|
||||
|
||||
```sh
|
||||
$ cd omim/tools/python/airmaps/sandbox
|
||||
```
|
||||
|
||||
1. Run all services:
|
||||
|
||||
```sh
|
||||
sandbox$ docker-compose up
|
||||
```
|
||||
|
||||
2. Open http://localhost in your browser.
|
||||
|
||||
Note: You can see the results of airmaps working in `sandbox/storage/tests`.
|
||||
|
||||
### Stopping
|
||||
|
||||
0. Change working directory:
|
||||
|
||||
```sh
|
||||
$ cd omim/tools/python/airmaps/sandbox
|
||||
```
|
||||
|
||||
1. Stop all services:
|
||||
Push Ctrl+C and
|
||||
|
||||
```sh
|
||||
sandbox$ docker-compose down
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
#### Clean storage and intermediate files:
|
||||
|
||||
0. Change working directory:
|
||||
|
||||
```sh
|
||||
$ cd omim/tools/python/airmaps/sandbox
|
||||
```
|
||||
|
||||
1. Clean storage and intermediate files:
|
||||
|
||||
```sh
|
||||
sandbox$ ./clean.sh
|
||||
```
|
||||
|
||||
#### Remove images:
|
||||
|
||||
0. Change working directory:
|
||||
|
||||
```sh
|
||||
$ cd omim/tools/python/airmaps/sandbox
|
||||
```
|
||||
|
||||
1. Remove images:
|
||||
|
||||
```sh
|
||||
sandbox$ docker-compose rm
|
||||
```
|
||||
28
tools/python/airmaps/sandbox/airmaps/Dockerfile
Normal file
28
tools/python/airmaps/sandbox/airmaps/Dockerfile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
FROM python:3.6
|
||||
|
||||
ARG TZ=Etc/UTC
|
||||
|
||||
WORKDIR /omim/
|
||||
|
||||
ADD . .
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
libgl1-mesa-dev \
|
||||
libsqlite3-dev \
|
||||
qt5-default \
|
||||
zlib1g-dev \
|
||||
tzdata \
|
||||
locales-all
|
||||
|
||||
RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
|
||||
RUN ./tools/unix/build_omim.sh -rs generator_tool
|
||||
|
||||
RUN pip install --upgrade pip
|
||||
|
||||
RUN pip install werkzeug==0.16.0 \
|
||||
SQLAlchemy==1.3.15 \
|
||||
-r ./tools/python/airmaps/requirements_dev.txt
|
||||
856
tools/python/airmaps/sandbox/airmaps/airflow.cfg
Normal file
856
tools/python/airmaps/sandbox/airmaps/airflow.cfg
Normal file
|
|
@ -0,0 +1,856 @@
|
|||
[core]
|
||||
# The folder where your airflow pipelines live, most likely a
|
||||
# subfolder in a code repository
|
||||
# This path must be absolute
|
||||
dags_folder = /omim/tools/python/airmaps/dags
|
||||
|
||||
# The folder where airflow should store its log files
|
||||
# This path must be absolute
|
||||
base_log_folder = /airflow_home/logs
|
||||
|
||||
# Airflow can store logs remotely in AWS S3, Google Cloud Storage or Elastic Search.
|
||||
# Users must supply an Airflow connection id that provides access to the storage
|
||||
# location. If remote_logging is set to true, see UPDATING.md for additional
|
||||
# configuration requirements.
|
||||
remote_logging = False
|
||||
remote_log_conn_id =
|
||||
remote_base_log_folder =
|
||||
encrypt_s3_logs = False
|
||||
|
||||
# Logging level
|
||||
logging_level = INFO
|
||||
fab_logging_level = WARN
|
||||
|
||||
# Logging class
|
||||
# Specify the class that will specify the logging configuration
|
||||
# This class has to be on the python classpath
|
||||
# logging_config_class = my.path.default_local_settings.LOGGING_CONFIG
|
||||
logging_config_class =
|
||||
|
||||
# Log format
|
||||
# Colour the logs when the controlling terminal is a TTY.
|
||||
colored_console_log = True
|
||||
colored_log_format = [%%(blue)s%%(asctime)s%%(reset)s] {%%(blue)s%%(filename)s:%%(reset)s%%(lineno)d} %%(log_color)s%%(levelname)s%%(reset)s - %%(log_color)s%%(message)s%%(reset)s
|
||||
colored_formatter_class = airflow.utils.log.colored_log.CustomTTYColoredFormatter
|
||||
|
||||
log_format = [%%(asctime)s] {%%(filename)s:%%(lineno)d} %%(levelname)s - %%(message)s
|
||||
simple_log_format = %%(asctime)s %%(levelname)s - %%(message)s
|
||||
|
||||
# Log filename format
|
||||
log_filename_template = {{ ti.dag_id }}/{{ ti.task_id }}/{{ ts }}/{{ try_number }}.log
|
||||
log_processor_filename_template = {{ filename }}.log
|
||||
dag_processor_manager_log_location = /airflow_home/logs/dag_processor_manager/dag_processor_manager.log
|
||||
|
||||
# Hostname by providing a path to a callable, which will resolve the hostname
|
||||
# The format is "package:function". For example,
|
||||
# default value "socket:getfqdn" means that result from getfqdn() of "socket" package will be used as hostname
|
||||
# No argument should be required in the function specified.
|
||||
# If using IP address as hostname is preferred, use value "airflow.utils.net:get_host_ip_address"
|
||||
hostname_callable = socket:getfqdn
|
||||
|
||||
# Default timezone in case supplied date times are naive
|
||||
# can be utc (default), system, or any IANA timezone string (e.g. Europe/Amsterdam)
|
||||
default_timezone = system
|
||||
|
||||
# The executor class that airflow should use. Choices include
|
||||
# SequentialExecutor, LocalExecutor, CeleryExecutor, DaskExecutor, KubernetesExecutor
|
||||
executor = LocalExecutor
|
||||
|
||||
# The SqlAlchemy connection string to the metadata database.
|
||||
# SqlAlchemy supports many different database engine, more information
|
||||
# their website
|
||||
sql_alchemy_conn = postgresql+psycopg2://postgres:postgres@db:5432/airflow
|
||||
|
||||
# The encoding for the databases
|
||||
sql_engine_encoding = utf-8
|
||||
|
||||
# If SqlAlchemy should pool database connections.
|
||||
sql_alchemy_pool_enabled = True
|
||||
|
||||
# The SqlAlchemy pool size is the maximum number of database connections
|
||||
# in the pool. 0 indicates no limit.
|
||||
sql_alchemy_pool_size = 5
|
||||
|
||||
# The maximum overflow size of the pool.
|
||||
# When the number of checked-out connections reaches the size set in pool_size,
|
||||
# additional connections will be returned up to this limit.
|
||||
# When those additional connections are returned to the pool, they are disconnected and discarded.
|
||||
# It follows then that the total number of simultaneous connections the pool will allow is pool_size + max_overflow,
|
||||
# and the total number of "sleeping" connections the pool will allow is pool_size.
|
||||
# max_overflow can be set to -1 to indicate no overflow limit;
|
||||
# no limit will be placed on the total number of concurrent connections. Defaults to 10.
|
||||
sql_alchemy_max_overflow = 10
|
||||
|
||||
# The SqlAlchemy pool recycle is the number of seconds a connection
|
||||
# can be idle in the pool before it is invalidated. This config does
|
||||
# not apply to sqlite. If the number of DB connections is ever exceeded,
|
||||
# a lower config value will allow the system to recover faster.
|
||||
sql_alchemy_pool_recycle = 1800
|
||||
|
||||
# Check connection at the start of each connection pool checkout.
|
||||
# Typically, this is a simple statement like “SELECT 1”.
|
||||
# More information here: https://docs.sqlalchemy.org/en/13/core/pooling.html#disconnect-handling-pessimistic
|
||||
sql_alchemy_pool_pre_ping = True
|
||||
|
||||
# The schema to use for the metadata database
|
||||
# SqlAlchemy supports databases with the concept of multiple schemas.
|
||||
sql_alchemy_schema =
|
||||
|
||||
# The amount of parallelism as a setting to the executor. This defines
|
||||
# the max number of task instances that should run simultaneously
|
||||
# on this airflow installation
|
||||
parallelism = 32
|
||||
|
||||
# The number of task instances allowed to run concurrently by the scheduler
|
||||
dag_concurrency = 16
|
||||
|
||||
# Are DAGs paused by default at creation
|
||||
dags_are_paused_at_creation = True
|
||||
|
||||
# The maximum number of active DAG runs per DAG
|
||||
max_active_runs_per_dag = 16
|
||||
|
||||
# Whether to load the examples that ship with Airflow. It's good to
|
||||
# get started, but you probably want to set this to False in a production
|
||||
# environment
|
||||
load_examples = False
|
||||
|
||||
# Where your Airflow plugins are stored
|
||||
plugins_folder = /airflow_home/plugins
|
||||
|
||||
# Secret key to save connection passwords in the db
|
||||
fernet_key = uoTKzPCjhVBsERkDylXY5g1hYeg7OAYjk_a_ek2YMwQ=
|
||||
|
||||
# Whether to disable pickling dags
|
||||
donot_pickle = False
|
||||
|
||||
# How long before timing out a python file import
|
||||
dagbag_import_timeout = 30
|
||||
|
||||
# How long before timing out a DagFileProcessor, which processes a dag file
|
||||
dag_file_processor_timeout = 50
|
||||
|
||||
# The class to use for running task instances in a subprocess
|
||||
task_runner = StandardTaskRunner
|
||||
|
||||
# If set, tasks without a `run_as_user` argument will be run with this user
|
||||
# Can be used to de-elevate a sudo user running Airflow when executing tasks
|
||||
default_impersonation =
|
||||
|
||||
# What security module to use (for example kerberos):
|
||||
security =
|
||||
|
||||
# If set to False enables some unsecure features like Charts and Ad Hoc Queries.
|
||||
# In 2.0 will default to True.
|
||||
secure_mode = False
|
||||
|
||||
# Turn unit test mode on (overwrites many configuration options with test
|
||||
# values at runtime)
|
||||
unit_test_mode = False
|
||||
|
||||
# Name of handler to read task instance logs.
|
||||
# Default to use task handler.
|
||||
task_log_reader = task
|
||||
|
||||
# Whether to enable pickling for xcom (note that this is insecure and allows for
|
||||
# RCE exploits). This will be deprecated in Airflow 2.0 (be forced to False).
|
||||
enable_xcom_pickling = True
|
||||
|
||||
# When a task is killed forcefully, this is the amount of time in seconds that
|
||||
# it has to cleanup after it is sent a SIGTERM, before it is SIGKILLED
|
||||
killed_task_cleanup_time = 60
|
||||
|
||||
# Whether to override params with dag_run.conf. If you pass some key-value pairs through `airflow backfill -c` or
|
||||
# `airflow trigger_dag -c`, the key-value pairs will override the existing ones in params.
|
||||
dag_run_conf_overrides_params = False
|
||||
|
||||
# Worker initialisation check to validate Metadata Database connection
|
||||
worker_precheck = False
|
||||
|
||||
# When discovering DAGs, ignore any files that don't contain the strings `DAG` and `airflow`.
|
||||
dag_discovery_safe_mode = True
|
||||
|
||||
# The number of retries each task is going to have by default. Can be overridden at dag or task level.
|
||||
default_task_retries = 0
|
||||
|
||||
|
||||
[cli]
|
||||
# In what way should the cli access the API. The LocalClient will use the
|
||||
# database directly, while the json_client will use the api running on the
|
||||
# webserver
|
||||
api_client = airflow.api.client.local_client
|
||||
|
||||
# If you set web_server_url_prefix, do NOT forget to append it here, ex:
|
||||
# endpoint_url = http://localhost:8080/myroot
|
||||
# So api will look like: http://localhost:8080/myroot/api/experimental/...
|
||||
endpoint_url = http://localhost:8080
|
||||
|
||||
[api]
|
||||
# How to authenticate users of the API
|
||||
auth_backend = airflow.api.auth.backend.default
|
||||
|
||||
[lineage]
|
||||
# what lineage backend to use
|
||||
backend =
|
||||
|
||||
[atlas]
|
||||
sasl_enabled = False
|
||||
host =
|
||||
port = 21000
|
||||
username =
|
||||
password =
|
||||
|
||||
[operators]
|
||||
# The default owner assigned to each new operator, unless
|
||||
# provided explicitly or passed via `default_args`
|
||||
default_owner = airflow
|
||||
default_cpus = 1
|
||||
default_ram = 512
|
||||
default_disk = 512
|
||||
default_gpus = 0
|
||||
|
||||
[hive]
|
||||
# Default mapreduce queue for HiveOperator tasks
|
||||
default_hive_mapred_queue =
|
||||
|
||||
[webserver]
|
||||
# The base url of your website as airflow cannot guess what domain or
|
||||
# cname you are using. This is used in automated emails that
|
||||
# airflow sends to point links to the right web server
|
||||
base_url = http://localhost:8080
|
||||
|
||||
# Default timezone to display all dates in the UI, can be UTC, system, or
|
||||
# any IANA timezone string (e.g. Europe/Amsterdam). If left empty the
|
||||
# default value of core/default_timezone will be used
|
||||
# Example: default_ui_timezone = America/New_York
|
||||
default_ui_timezone = system
|
||||
|
||||
# The ip specified when starting the web server
|
||||
web_server_host = 0.0.0.0
|
||||
|
||||
# The port on which to run the web server
|
||||
web_server_port = 8080
|
||||
|
||||
# Paths to the SSL certificate and key for the web server. When both are
|
||||
# provided SSL will be enabled. This does not change the web server port.
|
||||
web_server_ssl_cert =
|
||||
web_server_ssl_key =
|
||||
|
||||
# Number of seconds the webserver waits before killing gunicorn master that doesn't respond
|
||||
web_server_master_timeout = 120
|
||||
|
||||
# Number of seconds the gunicorn webserver waits before timing out on a worker
|
||||
web_server_worker_timeout = 120
|
||||
|
||||
# Number of workers to refresh at a time. When set to 0, worker refresh is
|
||||
# disabled. When nonzero, airflow periodically refreshes webserver workers by
|
||||
# bringing up new ones and killing old ones.
|
||||
worker_refresh_batch_size = 1
|
||||
|
||||
# Number of seconds to wait before refreshing a batch of workers.
|
||||
worker_refresh_interval = 30
|
||||
|
||||
# Secret key used to run your flask app
|
||||
secret_key = temporary_key
|
||||
|
||||
# Number of workers to run the Gunicorn web server
|
||||
workers = 4
|
||||
|
||||
# The worker class gunicorn should use. Choices include
|
||||
# sync (default), eventlet, gevent
|
||||
worker_class = sync
|
||||
|
||||
# Log files for the gunicorn webserver. '-' means log to stderr.
|
||||
access_logfile = -
|
||||
error_logfile = -
|
||||
|
||||
# Expose the configuration file in the web server
|
||||
# This is only applicable for the flask-admin based web UI (non FAB-based).
|
||||
# In the FAB-based web UI with RBAC feature,
|
||||
# access to configuration is controlled by role permissions.
|
||||
expose_config = False
|
||||
|
||||
# Set to true to turn on authentication:
|
||||
# https://airflow.apache.org/security.html#web-authentication
|
||||
authenticate = False
|
||||
|
||||
# Filter the list of dags by owner name (requires authentication to be enabled)
|
||||
filter_by_owner = False
|
||||
|
||||
# Filtering mode. Choices include user (default) and ldapgroup.
|
||||
# Ldap group filtering requires using the ldap backend
|
||||
#
|
||||
# Note that the ldap server needs the "memberOf" overlay to be set up
|
||||
# in order to user the ldapgroup mode.
|
||||
owner_mode = user
|
||||
|
||||
# Default DAG view. Valid values are:
|
||||
# tree, graph, duration, gantt, landing_times
|
||||
dag_default_view = tree
|
||||
|
||||
# Default DAG orientation. Valid values are:
|
||||
# LR (Left->Right), TB (Top->Bottom), RL (Right->Left), BT (Bottom->Top)
|
||||
dag_orientation = LR
|
||||
|
||||
# Puts the webserver in demonstration mode; blurs the names of Operators for
|
||||
# privacy.
|
||||
demo_mode = False
|
||||
|
||||
# The amount of time (in secs) webserver will wait for initial handshake
|
||||
# while fetching logs from other worker machine
|
||||
log_fetch_timeout_sec = 5
|
||||
|
||||
# By default, the webserver shows paused DAGs. Flip this to hide paused
|
||||
# DAGs by default
|
||||
hide_paused_dags_by_default = False
|
||||
|
||||
# Consistent page size across all listing views in the UI
|
||||
page_size = 100
|
||||
|
||||
# Use FAB-based webserver with RBAC feature
|
||||
rbac = False
|
||||
|
||||
# Define the color of navigation bar
|
||||
navbar_color = #007A87
|
||||
|
||||
# Default dagrun to show in UI
|
||||
default_dag_run_display_number = 25
|
||||
|
||||
# Enable werkzeug `ProxyFix` middleware
|
||||
enable_proxy_fix = False
|
||||
|
||||
# Set secure flag on session cookie
|
||||
cookie_secure = False
|
||||
|
||||
# Set samesite policy on session cookie
|
||||
cookie_samesite =
|
||||
|
||||
# Default setting for wrap toggle on DAG code and TI log views.
|
||||
default_wrap = False
|
||||
|
||||
# Send anonymous user activity to your analytics tool
|
||||
# analytics_tool = # choose from google_analytics, segment, or metarouter
|
||||
# analytics_id = XXXXXXXXXXX
|
||||
|
||||
[email]
|
||||
email_backend = airflow.utils.email.send_email_smtp
|
||||
|
||||
|
||||
[smtp]
|
||||
# If you want airflow to send emails on retries, failure, and you want to use
|
||||
# the airflow.utils.email.send_email_smtp function, you have to configure an
|
||||
# smtp server here
|
||||
smtp_host = localhost
|
||||
smtp_starttls = True
|
||||
smtp_ssl = False
|
||||
# Uncomment and set the user/pass settings if you want to use SMTP AUTH
|
||||
# smtp_user = airflow
|
||||
# smtp_password = airflow
|
||||
smtp_port = 25
|
||||
smtp_mail_from = airflow@example.com
|
||||
|
||||
[sentry]
|
||||
# Sentry (https://docs.sentry.io) integration
|
||||
sentry_dsn =
|
||||
|
||||
|
||||
[celery]
|
||||
# This section only applies if you are using the CeleryExecutor in
|
||||
# [core] section above
|
||||
|
||||
# The app name that will be used by celery
|
||||
celery_app_name = airflow.executors.celery_executor
|
||||
|
||||
# The concurrency that will be used when starting workers with the
|
||||
# "airflow worker" command. This defines the number of task instances that
|
||||
# a worker will take, so size up your workers based on the resources on
|
||||
# your worker box and the nature of your tasks
|
||||
worker_concurrency = 40
|
||||
|
||||
# The maximum and minimum concurrency that will be used when starting workers with the
|
||||
# "airflow worker" command (always keep minimum processes, but grow to maximum if necessary).
|
||||
# Note the value should be "max_concurrency,min_concurrency"
|
||||
# Pick these numbers based on resources on worker box and the nature of the task.
|
||||
# If autoscale option is available, worker_concurrency will be ignored.
|
||||
# http://docs.celeryproject.org/en/latest/reference/celery.bin.worker.html#cmdoption-celery-worker-autoscale
|
||||
# worker_autoscale = 16,12
|
||||
|
||||
# When you start an airflow worker, airflow starts a tiny web server
|
||||
# subprocess to serve the workers local log files to the airflow main
|
||||
# web server, who then builds pages and sends them to users. This defines
|
||||
# the port on which the logs are served. It needs to be unused, and open
|
||||
# visible from the main web server to connect into the workers.
|
||||
worker_log_server_port = 8793
|
||||
|
||||
# The Celery broker URL. Celery supports RabbitMQ, Redis and experimentally
|
||||
# a sqlalchemy database. Refer to the Celery documentation for more
|
||||
# information.
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#broker-settings
|
||||
broker_url = sqla+mysql://airflow:airflow@localhost:3306/airflow
|
||||
|
||||
# The Celery result_backend. When a job finishes, it needs to update the
|
||||
# metadata of the job. Therefore it will post a message on a message bus,
|
||||
# or insert it into a database (depending of the backend)
|
||||
# This status is used by the scheduler to update the state of the task
|
||||
# The use of a database is highly recommended
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-result-backend-settings
|
||||
result_backend = db+mysql://airflow:airflow@localhost:3306/airflow
|
||||
|
||||
# Celery Flower is a sweet UI for Celery. Airflow has a shortcut to start
|
||||
# it `airflow flower`. This defines the IP that Celery Flower runs on
|
||||
flower_host = 0.0.0.0
|
||||
|
||||
# The root URL for Flower
|
||||
# Ex: flower_url_prefix = /flower
|
||||
flower_url_prefix =
|
||||
|
||||
# This defines the port that Celery Flower runs on
|
||||
flower_port = 5555
|
||||
|
||||
# Securing Flower with Basic Authentication
|
||||
# Accepts user:password pairs separated by a comma
|
||||
# Example: flower_basic_auth = user1:password1,user2:password2
|
||||
flower_basic_auth =
|
||||
|
||||
# Default queue that tasks get assigned to and that worker listen on.
|
||||
default_queue = default
|
||||
|
||||
# How many processes CeleryExecutor uses to sync task state.
|
||||
# 0 means to use max(1, number of cores - 1) processes.
|
||||
sync_parallelism = 0
|
||||
|
||||
# Import path for celery configuration options
|
||||
celery_config_options = airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG
|
||||
|
||||
# In case of using SSL
|
||||
ssl_active = False
|
||||
ssl_key =
|
||||
ssl_cert =
|
||||
ssl_cacert =
|
||||
|
||||
# Celery Pool implementation.
|
||||
# Choices include: prefork (default), eventlet, gevent or solo.
|
||||
# See:
|
||||
# https://docs.celeryproject.org/en/latest/userguide/workers.html#concurrency
|
||||
# https://docs.celeryproject.org/en/latest/userguide/concurrency/eventlet.html
|
||||
pool = prefork
|
||||
|
||||
[celery_broker_transport_options]
|
||||
# This section is for specifying options which can be passed to the
|
||||
# underlying celery broker transport. See:
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_transport_options
|
||||
|
||||
# The visibility timeout defines the number of seconds to wait for the worker
|
||||
# to acknowledge the task before the message is redelivered to another worker.
|
||||
# Make sure to increase the visibility timeout to match the time of the longest
|
||||
# ETA you're planning to use.
|
||||
#
|
||||
# visibility_timeout is only supported for Redis and SQS celery brokers.
|
||||
# See:
|
||||
# http://docs.celeryproject.org/en/master/userguide/configuration.html#std:setting-broker_transport_options
|
||||
#
|
||||
#visibility_timeout = 21600
|
||||
|
||||
[dask]
|
||||
# This section only applies if you are using the DaskExecutor in
|
||||
# [core] section above
|
||||
|
||||
# The IP address and port of the Dask cluster's scheduler.
|
||||
cluster_address = 127.0.0.1:8786
|
||||
# TLS/ SSL settings to access a secured Dask scheduler.
|
||||
tls_ca =
|
||||
tls_cert =
|
||||
tls_key =
|
||||
|
||||
|
||||
[scheduler]
|
||||
# Task instances listen for external kill signal (when you clear tasks
|
||||
# from the CLI or the UI), this defines the frequency at which they should
|
||||
# listen (in seconds).
|
||||
job_heartbeat_sec = 5
|
||||
|
||||
# The scheduler constantly tries to trigger new tasks (look at the
|
||||
# scheduler section in the docs for more information). This defines
|
||||
# how often the scheduler should run (in seconds).
|
||||
scheduler_heartbeat_sec = 5
|
||||
|
||||
# after how much time should the scheduler terminate in seconds
|
||||
# -1 indicates to run continuously (see also num_runs)
|
||||
run_duration = -1
|
||||
|
||||
# The number of times to try to schedule each DAG file
|
||||
# -1 indicates unlimited number
|
||||
num_runs = -1
|
||||
|
||||
# The number of seconds to wait between consecutive DAG file processing
|
||||
processor_poll_interval = 1
|
||||
|
||||
# after how much time (seconds) a new DAGs should be picked up from the filesystem
|
||||
min_file_process_interval = 0
|
||||
|
||||
# How often (in seconds) to scan the DAGs directory for new files. Default to 5 minutes.
|
||||
dag_dir_list_interval = 300
|
||||
|
||||
# How often should stats be printed to the logs
|
||||
print_stats_interval = 30
|
||||
|
||||
# If the last scheduler heartbeat happened more than scheduler_health_check_threshold ago (in seconds),
|
||||
# scheduler is considered unhealthy.
|
||||
# This is used by the health check in the "/health" endpoint
|
||||
scheduler_health_check_threshold = 30
|
||||
|
||||
child_process_log_directory = /airflow_home/logs/scheduler
|
||||
|
||||
# Local task jobs periodically heartbeat to the DB. If the job has
|
||||
# not heartbeat in this many seconds, the scheduler will mark the
|
||||
# associated task instance as failed and will re-schedule the task.
|
||||
scheduler_zombie_task_threshold = 300
|
||||
|
||||
# Turn off scheduler catchup by setting this to False.
|
||||
# Default behavior is unchanged and
|
||||
# Command Line Backfills still work, but the scheduler
|
||||
# will not do scheduler catchup if this is False,
|
||||
# however it can be set on a per DAG basis in the
|
||||
# DAG definition (catchup)
|
||||
catchup_by_default = True
|
||||
|
||||
# This changes the batch size of queries in the scheduling main loop.
|
||||
# If this is too high, SQL query performance may be impacted by one
|
||||
# or more of the following:
|
||||
# - reversion to full table scan
|
||||
# - complexity of query predicate
|
||||
# - excessive locking
|
||||
#
|
||||
# Additionally, you may hit the maximum allowable query length for your db.
|
||||
#
|
||||
# Set this to 0 for no limit (not advised)
|
||||
max_tis_per_query = 512
|
||||
|
||||
# Statsd (https://github.com/etsy/statsd) integration settings
|
||||
statsd_on = False
|
||||
statsd_host = localhost
|
||||
statsd_port = 8125
|
||||
statsd_prefix = airflow
|
||||
|
||||
# If you want to avoid send all the available metrics to StatsD,
|
||||
# you can configure an allow list of prefixes to send only the metrics that
|
||||
# start with the elements of the list (e.g: scheduler,executor,dagrun)
|
||||
statsd_allow_list =
|
||||
|
||||
# The scheduler can run multiple threads in parallel to schedule dags.
|
||||
# This defines how many threads will run.
|
||||
max_threads = 8
|
||||
|
||||
authenticate = False
|
||||
|
||||
# Turn off scheduler use of cron intervals by setting this to False.
|
||||
# DAGs submitted manually in the web UI or with trigger_dag will still run.
|
||||
use_job_schedule = True
|
||||
|
||||
[ldap]
|
||||
# set this to ldaps://<your.ldap.server>:<port>
|
||||
uri =
|
||||
user_filter = objectClass=*
|
||||
user_name_attr = uid
|
||||
group_member_attr = memberOf
|
||||
superuser_filter =
|
||||
data_profiler_filter =
|
||||
bind_user = cn=Manager,dc=example,dc=com
|
||||
bind_password = insecure
|
||||
basedn = dc=example,dc=com
|
||||
cacert = /etc/ca/ldap_ca.crt
|
||||
search_scope = LEVEL
|
||||
|
||||
# This setting allows the use of LDAP servers that either return a
|
||||
# broken schema, or do not return a schema.
|
||||
ignore_malformed_schema = False
|
||||
|
||||
[mesos]
|
||||
# Mesos master address which MesosExecutor will connect to.
|
||||
master = localhost:5050
|
||||
|
||||
# The framework name which Airflow scheduler will register itself as on mesos
|
||||
framework_name = Airflow
|
||||
|
||||
# Number of cpu cores required for running one task instance using
|
||||
# 'airflow run <dag_id> <task_id> <execution_date> --local -p <pickle_id>'
|
||||
# command on a mesos slave
|
||||
task_cpu = 1
|
||||
|
||||
# Memory in MB required for running one task instance using
|
||||
# 'airflow run <dag_id> <task_id> <execution_date> --local -p <pickle_id>'
|
||||
# command on a mesos slave
|
||||
task_memory = 256
|
||||
|
||||
# Enable framework checkpointing for mesos
|
||||
# See http://mesos.apache.org/documentation/latest/slave-recovery/
|
||||
checkpoint = False
|
||||
|
||||
# Failover timeout in milliseconds.
|
||||
# When checkpointing is enabled and this option is set, Mesos waits
|
||||
# until the configured timeout for
|
||||
# the MesosExecutor framework to re-register after a failover. Mesos
|
||||
# shuts down running tasks if the
|
||||
# MesosExecutor framework fails to re-register within this timeframe.
|
||||
# failover_timeout = 604800
|
||||
|
||||
# Enable framework authentication for mesos
|
||||
# See http://mesos.apache.org/documentation/latest/configuration/
|
||||
authenticate = False
|
||||
|
||||
# Mesos credentials, if authentication is enabled
|
||||
# default_principal = admin
|
||||
# default_secret = admin
|
||||
|
||||
# Optional Docker Image to run on slave before running the command
|
||||
# This image should be accessible from mesos slave i.e mesos slave
|
||||
# should be able to pull this docker image before executing the command.
|
||||
# docker_image_slave = puckel/docker-airflow
|
||||
|
||||
[kerberos]
|
||||
ccache = /tmp/airflow_krb5_ccache
|
||||
# gets augmented with fqdn
|
||||
principal = airflow
|
||||
reinit_frequency = 3600
|
||||
kinit_path = kinit
|
||||
keytab = airflow.keytab
|
||||
|
||||
|
||||
[github_enterprise]
|
||||
api_rev = v3
|
||||
|
||||
[admin]
|
||||
# UI to hide sensitive variable fields when set to True
|
||||
hide_sensitive_variable_fields = True
|
||||
|
||||
[elasticsearch]
|
||||
# Elasticsearch host
|
||||
host =
|
||||
# Format of the log_id, which is used to query for a given tasks logs
|
||||
log_id_template = {dag_id}-{task_id}-{execution_date}-{try_number}
|
||||
# Used to mark the end of a log stream for a task
|
||||
end_of_log_mark = end_of_log
|
||||
# Qualified URL for an elasticsearch frontend (like Kibana) with a template argument for log_id
|
||||
# Code will construct log_id using the log_id template from the argument above.
|
||||
# NOTE: The code will prefix the https:// automatically, don't include that here.
|
||||
frontend =
|
||||
# Write the task logs to the stdout of the worker, rather than the default files
|
||||
write_stdout = False
|
||||
# Instead of the default log formatter, write the log lines as JSON
|
||||
json_format = False
|
||||
# Log fields to also attach to the json output, if enabled
|
||||
json_fields = asctime, filename, lineno, levelname, message
|
||||
|
||||
[elasticsearch_configs]
|
||||
|
||||
use_ssl = False
|
||||
verify_certs = True
|
||||
|
||||
[kubernetes]
|
||||
# The repository, tag and imagePullPolicy of the Kubernetes Image for the Worker to Run
|
||||
worker_container_repository =
|
||||
worker_container_tag =
|
||||
worker_container_image_pull_policy = IfNotPresent
|
||||
|
||||
# If True (default), worker pods will be deleted upon termination
|
||||
delete_worker_pods = True
|
||||
|
||||
# Number of Kubernetes Worker Pod creation calls per scheduler loop
|
||||
worker_pods_creation_batch_size = 1
|
||||
|
||||
# The Kubernetes namespace where airflow workers should be created. Defaults to `default`
|
||||
namespace = default
|
||||
|
||||
# The name of the Kubernetes ConfigMap Containing the Airflow Configuration (this file)
|
||||
airflow_configmap =
|
||||
|
||||
# For docker image already contains DAGs, this is set to `True`, and the worker will search for dags in dags_folder,
|
||||
# otherwise use git sync or dags volume claim to mount DAGs
|
||||
dags_in_image = False
|
||||
|
||||
# For either git sync or volume mounted DAGs, the worker will look in this subpath for DAGs
|
||||
dags_volume_subpath =
|
||||
|
||||
# For DAGs mounted via a volume claim (mutually exclusive with git-sync and host path)
|
||||
dags_volume_claim =
|
||||
|
||||
# For volume mounted logs, the worker will look in this subpath for logs
|
||||
logs_volume_subpath =
|
||||
|
||||
# A shared volume claim for the logs
|
||||
logs_volume_claim =
|
||||
|
||||
# For DAGs mounted via a hostPath volume (mutually exclusive with volume claim and git-sync)
|
||||
# Useful in local environment, discouraged in production
|
||||
dags_volume_host =
|
||||
|
||||
# A hostPath volume for the logs
|
||||
# Useful in local environment, discouraged in production
|
||||
logs_volume_host =
|
||||
|
||||
# A list of configMapsRefs to envFrom. If more than one configMap is
|
||||
# specified, provide a comma separated list: configmap_a,configmap_b
|
||||
env_from_configmap_ref =
|
||||
|
||||
# A list of secretRefs to envFrom. If more than one secret is
|
||||
# specified, provide a comma separated list: secret_a,secret_b
|
||||
env_from_secret_ref =
|
||||
|
||||
# Git credentials and repository for DAGs mounted via Git (mutually exclusive with volume claim)
|
||||
git_repo =
|
||||
git_branch =
|
||||
git_subpath =
|
||||
# Use git_user and git_password for user authentication or git_ssh_key_secret_name and git_ssh_key_secret_key
|
||||
# for SSH authentication
|
||||
git_user =
|
||||
git_password =
|
||||
git_sync_root = /git
|
||||
git_sync_dest = repo
|
||||
git_dags_folder_mount_point =
|
||||
|
||||
# To get Git-sync SSH authentication set up follow this format
|
||||
#
|
||||
# airflow-secrets.yaml:
|
||||
# ---
|
||||
# apiVersion: v1
|
||||
# kind: Secret
|
||||
# metadata:
|
||||
# name: airflow-secrets
|
||||
# data:
|
||||
# # key needs to be gitSshKey
|
||||
# gitSshKey: <base64_encoded_data>
|
||||
# ---
|
||||
# airflow-configmap.yaml:
|
||||
# apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: airflow-configmap
|
||||
# data:
|
||||
# known_hosts: |
|
||||
# github.com ssh-rsa <...>
|
||||
# airflow.cfg: |
|
||||
# ...
|
||||
#
|
||||
# git_ssh_key_secret_name = airflow-secrets
|
||||
# git_ssh_known_hosts_configmap_name = airflow-configmap
|
||||
git_ssh_key_secret_name =
|
||||
git_ssh_known_hosts_configmap_name =
|
||||
|
||||
# To give the git_sync init container credentials via a secret, create a secret
|
||||
# with two fields: GIT_SYNC_USERNAME and GIT_SYNC_PASSWORD (example below) and
|
||||
# add `git_sync_credentials_secret = <secret_name>` to your airflow config under the kubernetes section
|
||||
#
|
||||
# Secret Example:
|
||||
# apiVersion: v1
|
||||
# kind: Secret
|
||||
# metadata:
|
||||
# name: git-credentials
|
||||
# data:
|
||||
# GIT_SYNC_USERNAME: <base64_encoded_git_username>
|
||||
# GIT_SYNC_PASSWORD: <base64_encoded_git_password>
|
||||
git_sync_credentials_secret =
|
||||
|
||||
# For cloning DAGs from git repositories into volumes: https://github.com/kubernetes/git-sync
|
||||
git_sync_container_repository = k8s.gcr.io/git-sync
|
||||
git_sync_container_tag = v3.1.1
|
||||
git_sync_init_container_name = git-sync-clone
|
||||
git_sync_run_as_user = 65533
|
||||
|
||||
# The name of the Kubernetes service account to be associated with airflow workers, if any.
|
||||
# Service accounts are required for workers that require access to secrets or cluster resources.
|
||||
# See the Kubernetes RBAC documentation for more:
|
||||
# https://kubernetes.io/docs/admin/authorization/rbac/
|
||||
worker_service_account_name =
|
||||
|
||||
# Any image pull secrets to be given to worker pods, If more than one secret is
|
||||
# required, provide a comma separated list: secret_a,secret_b
|
||||
image_pull_secrets =
|
||||
|
||||
# GCP Service Account Keys to be provided to tasks run on Kubernetes Executors
|
||||
# Should be supplied in the format: key-name-1:key-path-1,key-name-2:key-path-2
|
||||
gcp_service_account_keys =
|
||||
|
||||
# Use the service account kubernetes gives to pods to connect to kubernetes cluster.
|
||||
# It's intended for clients that expect to be running inside a pod running on kubernetes.
|
||||
# It will raise an exception if called from a process not running in a kubernetes environment.
|
||||
in_cluster = True
|
||||
|
||||
# When running with in_cluster=False change the default cluster_context or config_file
|
||||
# options to Kubernetes client. Leave blank these to use default behaviour like `kubectl` has.
|
||||
# cluster_context =
|
||||
# config_file =
|
||||
|
||||
|
||||
# Affinity configuration as a single line formatted JSON object.
|
||||
# See the affinity model for top-level key names (e.g. `nodeAffinity`, etc.):
|
||||
# https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#affinity-v1-core
|
||||
affinity =
|
||||
|
||||
# A list of toleration objects as a single line formatted JSON array
|
||||
# See:
|
||||
# https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.12/#toleration-v1-core
|
||||
tolerations =
|
||||
|
||||
# **kwargs parameters to pass while calling a kubernetes client core_v1_api methods from Kubernetes Executor
|
||||
# provided as a single line formatted JSON dictionary string.
|
||||
# List of supported params in **kwargs are similar for all core_v1_apis, hence a single config variable for all apis
|
||||
# See:
|
||||
# https://raw.githubusercontent.com/kubernetes-client/python/master/kubernetes/client/apis/core_v1_api.py
|
||||
# Note that if no _request_timeout is specified, the kubernetes client will wait indefinitely for kubernetes
|
||||
# api responses, which will cause the scheduler to hang. The timeout is specified as [connect timeout, read timeout]
|
||||
kube_client_request_args = {"_request_timeout" : [60,60] }
|
||||
|
||||
# Worker pods security context options
|
||||
# See:
|
||||
# https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
|
||||
|
||||
# Specifies the uid to run the first process of the worker pods containers as
|
||||
run_as_user =
|
||||
|
||||
# Specifies a gid to associate with all containers in the worker pods
|
||||
# if using a git_ssh_key_secret_name use an fs_group
|
||||
# that allows for the key to be read, e.g. 65533
|
||||
fs_group =
|
||||
|
||||
[kubernetes_node_selectors]
|
||||
# The Key-value pairs to be given to worker pods.
|
||||
# The worker pods will be scheduled to the nodes of the specified key-value pairs.
|
||||
# Should be supplied in the format: key = value
|
||||
|
||||
[kubernetes_annotations]
|
||||
# The Key-value annotations pairs to be given to worker pods.
|
||||
# Should be supplied in the format: key = value
|
||||
|
||||
[kubernetes_environment_variables]
|
||||
# The scheduler sets the following environment variables into your workers. You may define as
|
||||
# many environment variables as needed and the kubernetes launcher will set them in the launched workers.
|
||||
# Environment variables in this section are defined as follows
|
||||
# <environment_variable_key> = <environment_variable_value>
|
||||
#
|
||||
# For example if you wanted to set an environment variable with value `prod` and key
|
||||
# `ENVIRONMENT` you would follow the following format:
|
||||
# ENVIRONMENT = prod
|
||||
#
|
||||
# Additionally you may override worker airflow settings with the AIRFLOW__<SECTION>__<KEY>
|
||||
# formatting as supported by airflow normally.
|
||||
|
||||
[kubernetes_secrets]
|
||||
# The scheduler mounts the following secrets into your workers as they are launched by the
|
||||
# scheduler. You may define as many secrets as needed and the kubernetes launcher will parse the
|
||||
# defined secrets and mount them as secret environment variables in the launched workers.
|
||||
# Secrets in this section are defined as follows
|
||||
# <environment_variable_mount> = <kubernetes_secret_object>=<kubernetes_secret_key>
|
||||
#
|
||||
# For example if you wanted to mount a kubernetes secret key named `postgres_password` from the
|
||||
# kubernetes secret object `airflow-secret` as the environment variable `POSTGRES_PASSWORD` into
|
||||
# your workers you would follow the following format:
|
||||
# POSTGRES_PASSWORD = airflow-secret=postgres_credentials
|
||||
#
|
||||
# Additionally you may override worker airflow settings with the AIRFLOW__<SECTION>__<KEY>
|
||||
# formatting as supported by airflow normally.
|
||||
|
||||
[kubernetes_labels]
|
||||
# The Key-value pairs to be given to worker pods.
|
||||
# The worker pods will be given these static labels, as well as some additional dynamic labels
|
||||
# to identify the task.
|
||||
# Should be supplied in the format: key = value
|
||||
82
tools/python/airmaps/sandbox/airmaps/airmaps.ini
Normal file
82
tools/python/airmaps/sandbox/airmaps/airmaps.ini
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
[Main]
|
||||
# The path where the planet will be downloaded and the maps are generated.
|
||||
MAIN_OUT_PATH: /maps_build
|
||||
# If the flag DEBUG is set a special small planet file will be downloaded.
|
||||
DEBUG: 1
|
||||
|
||||
|
||||
[Developer]
|
||||
# The path where the generator_tool will be searched.
|
||||
BUILD_PATH: /omim-build-release
|
||||
# The path to the project directory omim.
|
||||
OMIM_PATH: /omim
|
||||
|
||||
|
||||
[Storage]
|
||||
# Webdaw settings.
|
||||
WD_HOST: webdav
|
||||
WD_LOGIN: alice
|
||||
WD_PASSWORD: secret1234
|
||||
|
||||
|
||||
[Generator tool]
|
||||
# The path to the omim/data.
|
||||
USER_RESOURCE_PATH: ${Developer:OMIM_PATH}/data
|
||||
# Do not change it. This is determined automatically.
|
||||
# NODE_STORAGE: map
|
||||
|
||||
|
||||
[Osm tools]
|
||||
# The path to the osmctools sources.
|
||||
OSM_TOOLS_SRC_PATH: ${Developer:OMIM_PATH}/tools/osmctools
|
||||
# The path where osmctools will be searched or will be built.
|
||||
OSM_TOOLS_PATH: /osmctools
|
||||
|
||||
|
||||
[Stages]
|
||||
# Run osmupdate tool for planet.
|
||||
NEED_PLANET_UPDATE: 0
|
||||
|
||||
|
||||
[Logging]
|
||||
# The path where maps_generator log will be saved.
|
||||
# LOG_FILE_PATH: generation.log
|
||||
|
||||
|
||||
[External]
|
||||
# The url to the planet file.
|
||||
# PLANET_URL:
|
||||
# The url to the file with md5 sum of the planet.
|
||||
# PLANET_MD5_URL:
|
||||
# The base url to WorldCoasts.geom and WorldCoasts.rawgeom (without file name).
|
||||
# Files latest_coasts.geom and latest_coasts.rawgeom must be at this URL.
|
||||
# For example, if PLANET_COASTS_URL = https://somesite.com/download/
|
||||
# The https://somesite.com/download/latest_coasts.geom url will be used to download latest_coasts.geom and
|
||||
# the https://somesite.com/download/latest_coasts.rawgeom url will be used to download latest_coasts.rawgeom.
|
||||
# PLANET_COASTS_URL:
|
||||
# The url to the subway file.
|
||||
SUBWAY_URL: https://cdn.organicmaps.app/subway.json
|
||||
|
||||
# Urls for production maps generation.
|
||||
# UGC_URL:
|
||||
# HOTELS_URL:
|
||||
# PROMO_CATALOG_CITIES:
|
||||
# POPULARITY_URL:
|
||||
# FOOD_URL:
|
||||
# FOOD_TRANSLATIONS_URL:
|
||||
# SRTM_PATH:
|
||||
# ISOLINES_PATH:
|
||||
# UK_POSTCODES_URL:
|
||||
# US_POSTCODES_URL:
|
||||
|
||||
|
||||
[Common]
|
||||
# Auto detection.
|
||||
THREADS_COUNT: 0
|
||||
# Emails for mailing.
|
||||
# EMAILS:
|
||||
|
||||
|
||||
[Stats]
|
||||
# Path to rules for calculating statistics by type
|
||||
STATS_TYPES_CONFIG: ${Developer:OMIM_PATH}/tools/python/maps_generator/var/etc/stats_types_config.txt
|
||||
13
tools/python/airmaps/sandbox/airmaps/run_airmaps_service.sh
Executable file
13
tools/python/airmaps/sandbox/airmaps/run_airmaps_service.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
export PYTHONPATH=/omim/tools/python
|
||||
export AIRFLOW_HOME=/airflow_home
|
||||
|
||||
# Initialize the database.
|
||||
airflow initdb
|
||||
|
||||
# Start the web server, default port is 8880.
|
||||
airflow webserver -p 8880 &
|
||||
|
||||
# Start the scheduler.
|
||||
airflow scheduler
|
||||
14
tools/python/airmaps/sandbox/build.sh
Executable file
14
tools/python/airmaps/sandbox/build.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
BUILD_PATH="$(dirname "$0")"
|
||||
OMIM_PATH="$(cd "${OMIM_PATH:-${BUILD_PATH}/../../../..}"; pwd)"
|
||||
|
||||
echo "Build airmaps service.."
|
||||
|
||||
mv "${OMIM_PATH}/.dockerignore" "${OMIM_PATH}/.dockerignore_" 2> /dev/null
|
||||
cp "${BUILD_PATH}/.dockerignore" ${OMIM_PATH}
|
||||
|
||||
docker-compose build
|
||||
|
||||
rm "${OMIM_PATH}/.dockerignore"
|
||||
mv "${OMIM_PATH}/.dockerignore_" "${OMIM_PATH}/.dockerignore" 2> /dev/null
|
||||
10
tools/python/airmaps/sandbox/clean.sh
Executable file
10
tools/python/airmaps/sandbox/clean.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
BUILD_PATH="$(dirname "$0")"
|
||||
OMIM_PATH="$(cd "${OMIM_PATH:-${BUILD_PATH}/../../../..}"; pwd)"
|
||||
|
||||
echo "Cleaning.."
|
||||
rm "${OMIM_PATH}/.dockerignore" 2> /dev/null
|
||||
mv "${OMIM_PATH}/.dockerignore_" "${OMIM_PATH}/.dockerignore" 2> /dev/null
|
||||
|
||||
rm -r "${BUILD_PATH}/storage" 2> /dev/null
|
||||
10
tools/python/airmaps/sandbox/create_storage.sh
Executable file
10
tools/python/airmaps/sandbox/create_storage.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
BUILD_PATH="$(dirname "$0")"
|
||||
|
||||
echo "Creating storage.."
|
||||
mkdir -p "${BUILD_PATH}/storage/tests/coasts"
|
||||
mkdir -p "${BUILD_PATH}/storage/tests/maps/open_source"
|
||||
mkdir -p "${BUILD_PATH}/storage/tests/planet_regular"
|
||||
|
||||
chown -R www-data:www-data "${BUILD_PATH}/storage/tests"
|
||||
41
tools/python/airmaps/sandbox/docker-compose.yml
Normal file
41
tools/python/airmaps/sandbox/docker-compose.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
version: "3"
|
||||
services:
|
||||
webdav:
|
||||
image: sashgorokhov/webdav
|
||||
container_name: webdav
|
||||
hostname: webdav
|
||||
restart: always
|
||||
environment:
|
||||
USERNAME: alice
|
||||
PASSWORD: secret1234
|
||||
volumes:
|
||||
- ./storage/tests:/media/tests
|
||||
|
||||
db:
|
||||
image: postgres:12.2
|
||||
container_name: db
|
||||
hostname: db
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: airflow
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
airmaps:
|
||||
build:
|
||||
context: ../../../..
|
||||
dockerfile: ./tools/python/airmaps/sandbox/airmaps/Dockerfile
|
||||
args:
|
||||
- TZ=Europe/Moscow
|
||||
container_name: airmaps
|
||||
hostname: airmaps
|
||||
restart: always
|
||||
links:
|
||||
- webdav
|
||||
- db
|
||||
ports:
|
||||
- "80:8880"
|
||||
command: ./tools/python/airmaps/sandbox/airmaps/run_airmaps_service.sh
|
||||
volumes:
|
||||
- ./airmaps/airflow.cfg:/airflow_home/airflow.cfg
|
||||
- ./airmaps/airmaps.ini:/omim/tools/python/airmaps/var/etc/airmaps.ini
|
||||
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