From 3fa7071a1b57145da7559144f2316ad3be555d39 Mon Sep 17 00:00:00 2001 From: Vladimir Byko-Ianko Date: Tue, 19 Jul 2016 14:49:38 +0300 Subject: [PATCH] Implementation index for altitude data with the help of succinct strutures. --- generator/altitude_generator.cpp | 191 ++++++++++++++++++++++++++++--- indexer/altitude_loader.cpp | 101 ++++++++++++---- indexer/altitude_loader.hpp | 26 +++-- indexer/feature.hpp | 4 - indexer/feature_altitude.hpp | 65 ++++++----- routing/bicycle_model.cpp | 2 +- routing/features_road_graph.cpp | 28 ++--- routing/features_road_graph.hpp | 5 + routing/routing_algorithm.cpp | 7 +- 9 files changed, 330 insertions(+), 99 deletions(-) diff --git a/generator/altitude_generator.cpp b/generator/altitude_generator.cpp index 85480d083f..df807e3936 100644 --- a/generator/altitude_generator.cpp +++ b/generator/altitude_generator.cpp @@ -3,6 +3,7 @@ #include "routing/routing_helpers.hpp" +#include "indexer/altitude_loader.hpp" #include "indexer/feature.hpp" #include "indexer/feature_altitude.hpp" #include "indexer/feature_data.hpp" @@ -10,6 +11,8 @@ #include "coding/file_container.hpp" #include "coding/file_name_utils.hpp" +#include "coding/read_write_utils.hpp" +#include "coding/reader.hpp" #include "coding/varint.hpp" #include "coding/internal/file_data.hpp" @@ -21,6 +24,10 @@ #include "defines.hpp" +#include "3party/succinct/elias_fano.hpp" +#include "3party/succinct/mapper.hpp" +#include "3party/succinct/rs_bit_vector.hpp" + #include "std/algorithm.hpp" #include "std/type_traits.hpp" #include "std/utility.hpp" @@ -30,29 +37,79 @@ using namespace feature; namespace { +TAltitudeSectionVersion constexpr kAltitudeSectionVersion = 1; + class Processor { public: - using TFeatureAltitude = pair; + using TFeatureAltitude = pair; using TFeatureAltitudes = vector; - Processor(string const & srtmPath) : m_srtmManager(srtmPath) {} + Processor(string const & srtmPath) : m_srtmManager(srtmPath), m_minAltitude(kInvalidAltitude) {} TFeatureAltitudes const & GetFeatureAltitudes() const { return m_featureAltitudes; } + vector const & GetAltitudeAvailability() const { return m_altitudeAvailability; } + + TAltitude GetMinAltitude() const { return m_minAltitude; } + void operator()(FeatureType const & f, uint32_t const & id) { - if (!routing::IsRoad(feature::TypesHolder(f))) + if (id != m_altitudeAvailability.size()) + { + LOG(LERROR, ("There's a gap in feature id order.")); return; + } - f.ParseGeometry(FeatureType::BEST_GEOMETRY); - size_t const pointsCount = f.GetPointsCount(); - if (pointsCount == 0) - return; + bool hasAltitude = false; + do + { + if (!routing::IsRoad(feature::TypesHolder(f))) + break; - Altitudes alts(m_srtmManager.GetHeight(MercatorBounds::ToLatLon(f.GetPoint(0))), - m_srtmManager.GetHeight(MercatorBounds::ToLatLon(f.GetPoint(pointsCount - 1)))); - m_featureAltitudes.push_back(make_pair(id, alts)); + f.ParseGeometry(FeatureType::BEST_GEOMETRY); + size_t const pointsCount = f.GetPointsCount(); + if (pointsCount == 0) + break; + + TAltitudes altitudes; + bool allPointsValidAltFlag = true; + TAltitude minFeatureAltitude = kInvalidAltitude; + for (size_t i = 0; i < pointsCount; ++i) + { + TAltitude const a = m_srtmManager.GetHeight(MercatorBounds::ToLatLon(f.GetPoint(i))); + if (a == kInvalidAltitude) + { + allPointsValidAltFlag = false; + break; + } + + if (minFeatureAltitude == kInvalidAltitude) + minFeatureAltitude = a; + else + minFeatureAltitude = min(minFeatureAltitude, a); + + altitudes.push_back(a); + } + if (!allPointsValidAltFlag) + break; + + hasAltitude = true; + m_featureAltitudes.push_back(make_pair(id, Altitude(move(altitudes)))); + + if (m_minAltitude == kInvalidAltitude) + m_minAltitude = minFeatureAltitude; + else + m_minAltitude = min(minFeatureAltitude, m_minAltitude); + } + while(false); + + m_altitudeAvailability.push_back(hasAltitude); + } + + bool HasAltitudeInfo() + { + return !m_featureAltitudes.empty(); } void SortFeatureAltitudes() @@ -63,32 +120,128 @@ public: private: generator::SrtmTileManager m_srtmManager; TFeatureAltitudes m_featureAltitudes; + vector m_altitudeAvailability; + TAltitude m_minAltitude; }; + +uint32_t GetFileSize(string const & filePath) +{ + uint64_t size; + if (!my::GetFileSize(filePath, size)) + { + LOG(LWARNING, ("altitudeAvailability", filePath, "size = 0")); + return 0; + } + + LOG(LINFO, ("altitudeAvailability ", filePath, "size =", size, "bytes")); + return size; +} + +void MoveFileToAltitudeSection(string const & filePath, uint32_t fileSize, FileWriter & w) +{ + { + ReaderSource r = FileReader(filePath); + w.Write(&fileSize, sizeof(fileSize)); + rw::ReadAndWrite(r, w); + LOG(LINFO, (filePath, "size is", fileSize)); + } + FileWriter::DeleteFileX(filePath); +} + +void SerializeHeader(TAltitudeSectionVersion version, TAltitude minAltitude, + TAltitudeSectionOffset altitudeInfoOffset, FileWriter & w) +{ + w.Write(&version, sizeof(version)); + + w.Write(&minAltitude, sizeof(minAltitude)); + LOG(LINFO, ("altitudeAvailability writing minAltitude =", minAltitude)); + + w.Write(&altitudeInfoOffset, sizeof(altitudeInfoOffset)); + LOG(LINFO, ("altitudeAvailability writing altitudeInfoOffset =", altitudeInfoOffset)); +} } // namespace namespace routing { void BuildRoadAltitudes(string const & srtmPath, string const & baseDir, string const & countryName) { - LOG(LINFO, ("srtmPath =", srtmPath, "baseDir =", baseDir, "countryName =", countryName)); - string const mwmPath = my::JoinFoldersToPath(baseDir, countryName + DATA_FILE_EXTENSION); - - // Writing section with altitude information. + try { - FilesContainerW cont(mwmPath, FileWriter::OP_WRITE_EXISTING); - FileWriter w = cont.GetWriter(ALTITUDE_FILE_TAG); + // Preparing altitude information. + LOG(LINFO, ("srtmPath =", srtmPath, "baseDir =", baseDir, "countryName =", countryName)); + string const mwmPath = my::JoinFoldersToPath(baseDir, countryName + DATA_FILE_EXTENSION); Processor processor(srtmPath); feature::ForEachFromDat(mwmPath, processor); processor.SortFeatureAltitudes(); Processor::TFeatureAltitudes const & featureAltitudes = processor.GetFeatureAltitudes(); - for (auto const & a : featureAltitudes) + if (!processor.HasAltitudeInfo()) { - Altitude altitude(a.first /* feature id */, a.second /* feature altitudes */); - altitude.Serialize(w); + LOG(LINFO, ("No altitude information for road features of mwm", countryName)); + return; + } + + // Writing compressed bit vector with features which have altitude information. + succinct::rs_bit_vector altitudeAvailability(processor.GetAltitudeAvailability()); + string const altitudeAvailabilityPath = my::JoinFoldersToPath(baseDir, "altitude_availability.bitvector"); + LOG(LINFO, ("altitudeAvailability succinct::mapper::size_of(altitudeAvailability) =", succinct::mapper::size_of(altitudeAvailability))); + succinct::mapper::freeze(altitudeAvailability, altitudeAvailabilityPath.c_str()); + + // Writing feature altitude information to a file and memorizing the offsets. + string const altitudeInfoPath = my::JoinFoldersToPath(baseDir, "altitude_info"); + vector offsets; + TAltitude const minAltitude = processor.GetMinAltitude(); + offsets.reserve(featureAltitudes.size()); + { + FileWriter altitudeInfoW(altitudeInfoPath); + for (auto const & a : featureAltitudes) + { + offsets.push_back(altitudeInfoW.Pos()); + // Feature altitude serializing. + a.second.Serialize(minAltitude, altitudeInfoW); + } } LOG(LINFO, ("Altitude was written for", featureAltitudes.size(), "features.")); + + // Writing feature altitude offsets. + CHECK(is_sorted(offsets.begin(), offsets.end()), ()); + CHECK(adjacent_find(offsets.begin(), offsets.end()) == offsets.end(), ()); + LOG(LINFO, ("Max altitude info offset =", offsets.back(), "offsets.size() =", offsets.size())); + succinct::elias_fano::elias_fano_builder builder(offsets.back(), offsets.size()); + for (uint32_t offset : offsets) + builder.push_back(offset); + + succinct::elias_fano featureTable(&builder); + string const featuresTablePath = my::JoinFoldersToPath(baseDir, "altitude_offsets.elias_fano"); + succinct::mapper::freeze(featureTable, featuresTablePath.c_str()); + + uint32_t const altitudeAvailabilitySize = GetFileSize(altitudeAvailabilityPath); + uint32_t const altitudeInfoSize = GetFileSize(altitudeInfoPath); + uint32_t const featuresTableSize = GetFileSize(featuresTablePath); + + FilesContainerW cont(mwmPath, FileWriter::OP_WRITE_EXISTING); + FileWriter w = cont.GetWriter(ALTITUDE_FILE_TAG); + + // Writing section with altitude information. + // Writing altitude section header. + TAltitudeSectionOffset const headerSize = sizeof(kAltitudeSectionVersion) + + sizeof(minAltitude) + sizeof(TAltitudeSectionOffset); + TAltitudeSectionOffset const featuresTableOffset = headerSize + + sizeof(altitudeAvailabilitySize) + altitudeAvailabilitySize; + TAltitudeSectionOffset const altitudeInfoOffset = featuresTableOffset + + sizeof(featuresTableSize) + featuresTableSize; + SerializeHeader(kAltitudeSectionVersion, processor.GetMinAltitude(), + altitudeInfoOffset + sizeof(TAltitudeSectionOffset) /* for altitude info size */, w); + + // Coping parts of altitude sections to mwm. + MoveFileToAltitudeSection(altitudeAvailabilityPath, altitudeAvailabilitySize, w); + MoveFileToAltitudeSection(featuresTablePath, featuresTableSize, w); + MoveFileToAltitudeSection(altitudeInfoPath, altitudeInfoSize, w); + } + catch (RootException const & e) + { + LOG(LERROR, ("An exception happend while creating", ALTITUDE_FILE_TAG, "section. ", e.what())); } } } // namespace routing diff --git a/indexer/altitude_loader.cpp b/indexer/altitude_loader.cpp index 66fa124bf8..21c25ac9b0 100644 --- a/indexer/altitude_loader.cpp +++ b/indexer/altitude_loader.cpp @@ -1,49 +1,110 @@ #include "indexer/altitude_loader.hpp" +#include "coding/reader.hpp" + #include "base/logging.hpp" #include "base/stl_helpers.hpp" +#include "base/thread.hpp" #include "defines.hpp" +#include "3party/succinct/mapper.hpp" + +namespace +{ +void ReadBuffer(ReaderSource & rs, vector & buf) +{ + uint32_t bufSz = 0; + rs.Read(&bufSz, sizeof(bufSz)); + if (bufSz > rs.Size() + rs.Pos()) + { + ASSERT(false, ()); + return; + } + buf.clear(); + buf.resize(bufSz); + rs.Read(buf.data(), bufSz); +} +} // namespace + namespace feature { AltitudeLoader::AltitudeLoader(MwmValue const * mwmValue) + : reader(mwmValue->m_cont.GetReader(ALTITUDE_FILE_TAG)), m_altitudeInfoOffset(0), m_minAltitude(kInvalidAltitude) { if (!mwmValue || mwmValue->GetHeader().GetFormat() < version::Format::v8 ) return; try { - m_idx = make_unique> - (mwmValue->m_cont.GetReader(ALTITUDE_FILE_TAG)); + ReaderSource rs(reader); + DeserializeHeader(rs); + + // Reading rs_bit_vector with altitude availability information. + ReadBuffer(rs, m_altitudeAvailabilitBuf); + m_altitudeAvailability = make_unique(); + succinct::mapper::map(*m_altitudeAvailability, m_altitudeAvailabilitBuf.data()); + + // Reading table with altitude ofsets for features. + ReadBuffer(rs, m_featureTableBuf); + m_featureTable = make_unique(); + succinct::mapper::map(*m_featureTable, m_featureTableBuf.data()); } - catch (Reader::OpenException const &) + catch (Reader::OpenException const & e) { - LOG(LINFO, ("MWM does not contain", ALTITUDE_FILE_TAG, "section.")); + m_altitudeInfoOffset = 0; + m_minAltitude = kInvalidAltitude; + LOG(LINFO, ("MWM does not contain", ALTITUDE_FILE_TAG, "section.", e.Msg())); } } -Altitudes AltitudeLoader::GetAltitudes(uint32_t featureId) const +void AltitudeLoader::DeserializeHeader(ReaderSource & rs) { - if (!m_idx || m_idx->size() == 0) - return Altitudes(); + TAltitudeSectionVersion version; + rs.Read(&version, sizeof(version)); + LOG(LINFO, ("Reading version =", version)); + rs.Read(&m_minAltitude, sizeof(m_minAltitude)); + LOG(LINFO, ("Reading m_minAltitude =", m_minAltitude)); + rs.Read(&m_altitudeInfoOffset, sizeof(m_altitudeInfoOffset)); + LOG(LINFO, ("Reading m_altitudeInfoOffset =", m_altitudeInfoOffset)); +} - auto it = lower_bound(m_idx->begin(), m_idx->end(), - TAltitudeIndexEntry{static_cast(featureId), 0, 0}, - my::LessBy(&TAltitudeIndexEntry::featureId)); - - if (it == m_idx->end()) - return Altitudes(); - - if (featureId != it->featureId) +TAltitudes AltitudeLoader::GetAltitude(uint32_t featureId, size_t pointCount) const +{ + if (m_altitudeInfoOffset == 0) { - ASSERT(false, ()); - return Altitudes(); + // The version of mwm is less then version::Format::v8 or there's no altitude section in mwm. + return TAltitudes(); } - if (it->beginAlt == kInvalidAltitude || it->endAlt == kInvalidAltitude) - return Altitudes(); + if (!(*m_altitudeAvailability)[featureId]) + { + LOG(LINFO, ("Feature featureId =", featureId, "does not contain any altitude information.")); + return TAltitudes(); + } - return Altitudes(it->beginAlt, it->endAlt); + uint64_t const r = m_altitudeAvailability->rank(featureId); + CHECK_LESS(r, m_altitudeAvailability->size(), (featureId)); + uint64_t const offset = m_featureTable->select(r); + CHECK_LESS(offset, m_featureTable->size(), (featureId)); + + uint64_t const m_altitudeInfoOffsetInSection = m_altitudeInfoOffset + offset; + CHECK_LESS(m_altitudeInfoOffsetInSection, reader.Size(), ()); + + try + { + Altitude a; + ReaderSource rs(reader); + rs.Skip(m_altitudeInfoOffsetInSection); + a.Deserialize(m_minAltitude, pointCount, rs); + + // @TODO(bykoianko) Considers using move semantic for returned value here. + return a.GetAltitudes(); + } + catch (Reader::OpenException const & e) + { + LOG(LERROR, ("Error while getting mwm data", e.Msg())); + return TAltitudes(); + } } } // namespace feature diff --git a/indexer/altitude_loader.hpp b/indexer/altitude_loader.hpp index d792b28467..d7a27785de 100644 --- a/indexer/altitude_loader.hpp +++ b/indexer/altitude_loader.hpp @@ -2,24 +2,32 @@ #include "indexer/feature_altitude.hpp" #include "indexer/index.hpp" -#include "coding/dd_vector.hpp" +#include "3party/succinct/rs_bit_vector.hpp" + +#include "std/unique_ptr.hpp" +#include "std/vector.hpp" namespace feature { +using TAltitudeSectionVersion = uint16_t; +using TAltitudeSectionOffset = uint32_t; + class AltitudeLoader { public: AltitudeLoader(MwmValue const * mwmValue); - Altitudes GetAltitudes(uint32_t featureId) const; + + TAltitudes GetAltitude(uint32_t featureId, size_t pointCount) const; private: - struct TAltitudeIndexEntry - { - uint32_t featureId; - feature::TAltitude beginAlt; - feature::TAltitude endAlt; - }; + void DeserializeHeader(ReaderSource & rs); - unique_ptr> m_idx; + vector m_altitudeAvailabilitBuf; + vector m_featureTableBuf; + unique_ptr m_altitudeAvailability; + unique_ptr m_featureTable; + FilesContainerR::TReader reader; + TAltitudeSectionOffset m_altitudeInfoOffset; + TAltitude m_minAltitude; }; } // namespace feature diff --git a/indexer/feature.hpp b/indexer/feature.hpp index fe5f30c206..4ecbb79357 100644 --- a/indexer/feature.hpp +++ b/indexer/feature.hpp @@ -362,10 +362,6 @@ private: mutable points_t m_points, m_triangles; mutable feature::Metadata m_metadata; - // @TODO |m_altitudes| should be exchanged with vector. - // If the vector is empty no altitude information is available for this feature. - feature::Altitudes m_altitudes; - mutable bool m_header2Parsed = false; mutable bool m_pointsParsed = false; mutable bool m_trianglesParsed = false; diff --git a/indexer/feature_altitude.hpp b/indexer/feature_altitude.hpp index 4d59135342..84582fac7c 100644 --- a/indexer/feature_altitude.hpp +++ b/indexer/feature_altitude.hpp @@ -11,44 +11,55 @@ using TAltitude = int16_t; using TAltitudes = vector; TAltitude constexpr kInvalidAltitude = numeric_limits::min(); -struct Altitudes -{ - Altitudes() = default; - Altitudes(TAltitude b, TAltitude e) : begin(b), end(e) {} - - TAltitude begin = kInvalidAltitude; - TAltitude end = kInvalidAltitude; -}; - class Altitude { public: Altitude() = default; - Altitude(uint32_t featureId, Altitudes const & altitudes) - : m_featureId(featureId), m_altitudes(altitudes) - { - } + Altitude(TAltitudes const & altitudes) : m_pointAlt(altitudes) {} template - void Serialize(TSink & sink) const + void Serialize(TAltitude minAltitude, TSink & sink) const { - sink.Write(&m_featureId, sizeof(uint32_t)); - sink.Write(&m_altitudes.begin, sizeof(TAltitude)); - sink.Write(&m_altitudes.end, sizeof(TAltitude)); + CHECK(!m_pointAlt.empty(), ()); + + TAltitude prevPntAltitude = minAltitude; + for (size_t i = 0; i < m_pointAlt.size(); ++i) + { + WriteVarInt(sink, static_cast(static_cast(m_pointAlt[i] - prevPntAltitude))); + prevPntAltitude = m_pointAlt[i]; + } } - /// @TODO template void Deserialize(TSource & src) should be implement here. - /// But now for test purposes deserialization is done with DDVector construction. + template + void Deserialize(TAltitude minAltitude, size_t pointCount, TSource & src) + { + m_pointAlt.clear(); + if (pointCount == 0) + { + ASSERT(false, ()); + return; + } - uint32_t GetFeatureId() const { return m_featureId; } - Altitudes const & GetAltitudes() const { return m_altitudes; } + m_pointAlt.resize(pointCount); + TAltitude prevPntAltitude = minAltitude; + for (size_t i = 0; i < pointCount; ++i) + { + m_pointAlt[i] = static_cast(ReadVarInt(src) + prevPntAltitude); + prevPntAltitude = m_pointAlt[i]; + } + } + + TAltitudes GetAltitudes() const + { + return m_pointAlt; + } private: - /// @TODO Note. Feature id is located here because there's no index for altitudes. - /// There's only pairs sorted by feature id. Before merging to master some index has to be - /// implemented. - /// Don't forget to remove |m_featureId|. - uint32_t m_featureId = 0; - Altitudes m_altitudes; + /// \note |m_pointAlt| is a vector of feature point altitudes. There's two posibilities: + /// * |m_pointAlt| is empty. It means no altitude information defines. + /// * size of |m_pointAlt| is equal to feature point number. In that case every item of + /// |m_pointAlt| defines altitude in meters for every feature point. If altitude is not defined + /// for some feature point corresponding vector items are equel to |kInvalidAltitude| + TAltitudes m_pointAlt; }; } // namespace feature diff --git a/routing/bicycle_model.cpp b/routing/bicycle_model.cpp index 5d629bf18a..ac8a867d43 100644 --- a/routing/bicycle_model.cpp +++ b/routing/bicycle_model.cpp @@ -686,7 +686,7 @@ shared_ptr BicycleModelFactory::GetVehicleModelForCountry(string LOG(LDEBUG, ("Bicycle model was found:", country)); return itr->second; } - LOG(LDEBUG, ("Bicycle model wasn't found, default model is used instead:", country)); + LOG(LDEBUG, ("Bicycle model wasn't found, default bicycle model is used instead:", country)); return BicycleModelFactory::GetVehicleModel(); } } // routing diff --git a/routing/features_road_graph.cpp b/routing/features_road_graph.cpp index 7458a9142d..0c4ed59023 100644 --- a/routing/features_road_graph.cpp +++ b/routing/features_road_graph.cpp @@ -263,30 +263,23 @@ void FeaturesRoadGraph::ExtractRoadInfo(FeatureID const & featureId, FeatureType ri.m_speedKMPH = speedKMPH; ft.ParseGeometry(FeatureType::BEST_GEOMETRY); - feature::Altitudes altitudes = value.altitudeLoader.GetAltitudes(featureId.m_index); - LOG(LINFO, ("Feature idx =", featureId.m_index, "altitudes.begin =", altitudes.begin, - "altitudes.end =", altitudes.end)); + feature::TAltitudes altitudes = value.altitudeLoader.GetAltitude(featureId.m_index, ft.GetPointsCount()); - // @TODO It's a temprarery solution until a vector of feature altitudes is saved in mwm. - bool const isAltidudeValid = altitudes.begin != feature::kInvalidAltitude && - altitudes.end != feature::kInvalidAltitude; - feature::TAltitude pointAlt = altitudes.begin; size_t const pointsCount = ft.GetPointsCount(); - feature::TAltitude const diffAlt = - isAltidudeValid ? (altitudes.end - altitudes.begin) / pointsCount : 0; + if (altitudes.size() != pointsCount) + { + ASSERT(false, ("altitudes.size is different from ft.GetPointsCount()")); + altitudes.clear(); + } ri.m_junctions.clear(); ri.m_junctions.resize(pointsCount); for (size_t i = 0; i < pointsCount; ++i) { - if (!isAltidudeValid) - { + if (altitudes.empty()) ri.m_junctions[i] = Junction(ft.GetPoint(i), feature::kInvalidAltitude); - continue; - } - - ri.m_junctions[i] = Junction(ft.GetPoint(i), pointAlt); - pointAlt += diffAlt; + else + ri.m_junctions[i] = Junction(ft.GetPoint(i), altitudes[i]); } } @@ -343,8 +336,7 @@ FeaturesRoadGraph::Value const & FeaturesRoadGraph::LockFeatureMwm(FeatureID con if (mwmHandle.IsAlive()) mwmValue = mwmHandle.GetValue(); - Value value = {move(mwmHandle), feature::AltitudeLoader(mwmValue)}; - + Value value(move(mwmHandle), mwmValue); return m_mwmLocks.insert(make_pair(move(mwmId), move(value))).first->second; } } // namespace routing diff --git a/routing/features_road_graph.hpp b/routing/features_road_graph.hpp index 1503023f40..3c82ca2f1e 100644 --- a/routing/features_road_graph.hpp +++ b/routing/features_road_graph.hpp @@ -81,6 +81,11 @@ private: struct Value { + Value(MwmSet::MwmHandle && handle, MwmValue const * MwmValue) + : mwmHandle(move(handle)), altitudeLoader(MwmValue) + { + } + MwmSet::MwmHandle mwmHandle; feature::AltitudeLoader altitudeLoader; }; diff --git a/routing/routing_algorithm.cpp b/routing/routing_algorithm.cpp index 6f2fb26410..3495ef8b02 100644 --- a/routing/routing_algorithm.cpp +++ b/routing/routing_algorithm.cpp @@ -20,7 +20,12 @@ inline double TimeBetweenSec(Junction const & j1, Junction const & j2, double sp { ASSERT(speedMPS > 0.0, ()); double const distanceM = MercatorBounds::DistanceOnEarth(j1.GetPoint(), j2.GetPoint()); - double const altidudeDiffM = j2.GetAltitude() - j1.GetAltitude(); + feature::TAltitude const j1Altitude = j1.GetAltitude(); + feature::TAltitude const j2Altitude = j2.GetAltitude(); + if (j1Altitude == feature::kInvalidAltitude || j2Altitude == feature::kInvalidAltitude) + return distanceM / speedMPS; + + feature::TAltitude const altidudeDiffM = j2Altitude - j1Altitude; return sqrt(distanceM * distanceM + altidudeDiffM * altidudeDiffM) / speedMPS; }