Welcome to mirror list, hosted at ThFree Co, Russian Federation.

osm_feature_matcher.cpp « editor - github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fe421ba47e67f247dcb1b85578adf0c6855be1c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include "editor/osm_feature_matcher.hpp"

#include "base/logging.hpp"
#include "base/stl_helpers.hpp"

#include "std/algorithm.hpp"

using editor::XMLFeature;

namespace osm
{
using editor::XMLFeature;

constexpr double kPointDiffEps = 1e-5;

bool PointsEqual(m2::PointD const & a, m2::PointD const & b)
{
  return a.EqualDxDy(b, kPointDiffEps);
}

/// @returns value form (-Inf, 1]. Negative values are used as penalty,
/// positive as score.
double ScoreLatLon(XMLFeature const & xmlFt, ms::LatLon const & latLon)
{
  auto const a = MercatorBounds::FromLatLon(xmlFt.GetCenter());
  auto const b = MercatorBounds::FromLatLon(latLon);
  return 1.0 - (a.Length(b) / kPointDiffEps);
}

template <typename TFunc>
void ForEachWaysNode(pugi::xml_document const & osmResponse, pugi::xml_node const & way,
                     TFunc && func)
{
  for (auto const xNodeRef : way.select_nodes("nd/@ref"))
  {
    string const nodeRef = xNodeRef.attribute().value();
    auto const node = osmResponse.select_node(("osm/node[@id='" + nodeRef + "']").data()).node();
    ASSERT(node, ("OSM response have ref", nodeRef, "but have no node with such id.", osmResponse));
    XMLFeature xmlFt(node);
    func(xmlFt);
  }
}

template <typename TFunc>
void ForEachRelationsNode(pugi::xml_document const & osmResponse, pugi::xml_node const & relation,
                          TFunc && func)
{
  for (auto const xNodeRef : relation.select_nodes("member[@type='way']/@ref"))
  {
    string const wayRef = xNodeRef.attribute().value();
    auto const xpath = "osm/way[@id='" + wayRef + "']";
    auto const way = osmResponse.select_node(xpath.data()).node();
    // Some ways can be missed from relation.
    if (!way)
      continue;
    ForEachWaysNode(osmResponse, way, forward<TFunc>(func));
  }
}

vector<m2::PointD> GetWaysGeometry(pugi::xml_document const & osmResponse,
                                   pugi::xml_node const & way)
{
  vector<m2::PointD> result;
  ForEachWaysNode(osmResponse, way, [&result](XMLFeature const & xmlFt)
                  {
                    result.push_back(xmlFt.GetMercatorCenter());
                  });
  return result;
}

vector<m2::PointD> GetRelationsGeometry(pugi::xml_document const & osmResponse,
                                        pugi::xml_node const & relation)
{
  vector<m2::PointD> result;
  ForEachRelationsNode(osmResponse, relation, [&result](XMLFeature const & xmlFt)
                       {
                         result.push_back(xmlFt.GetMercatorCenter());
                       });
  return result;
}

// TODO(mgsergio): XMLFeature should have GetGeometry method.
vector<m2::PointD> GetWaysOrRelationsGeometry(pugi::xml_document const & osmResponse,
                                              pugi::xml_node const & wayOrRelation)
{
  if (strcmp(wayOrRelation.name(), "way") == 0)
    return GetWaysGeometry(osmResponse, wayOrRelation);
  return GetRelationsGeometry(osmResponse, wayOrRelation);
}

/// @returns value form [-0.5, 0.5]. Negative values are used as penalty,
/// positive as score.
/// @param osmResponse - nodes, ways and relations from osm
/// @param wayOrRelation - either way or relation to be compared agains ourGeometry
/// @param outGeometry - geometry of a FeatureType (ourGeometry must be sort-uniqued)
double ScoreGeometry(pugi::xml_document const & osmResponse,
                     pugi::xml_node const & wayOrRelation, vector<m2::PointD> ourGeometry)
{
  ASSERT(!ourGeometry.empty(), ("Our geometry cannot be empty"));
  int matched = 0;

  auto theirGeometry = GetWaysOrRelationsGeometry(osmResponse, wayOrRelation);

  if (theirGeometry.empty())
    return -1;

  my::SortUnique(theirGeometry);

  auto ourIt = begin(ourGeometry);
  auto theirIt = begin(theirGeometry);

  while (ourIt != end(ourGeometry) && theirIt != end(theirGeometry))
  {
    if (PointsEqual(*ourIt, *theirIt))
    {
      ++matched;
      ++ourIt;
      ++theirIt;
    }
    else if (*ourIt < *theirIt)
    {
      ++ourIt;
    }
    else
    {
      ++theirIt;
    }
  }

  auto const wayScore = static_cast<double>(matched) / theirGeometry.size() - 0.5;
  auto const geomScore = static_cast<double>(matched) / ourGeometry.size() - 0.5;
  auto const result = wayScore <= 0 || geomScore <= 0
      ? -1
      : 2 / (1 / wayScore + 1 / geomScore);

  LOG(LDEBUG, ("Type:", wayOrRelation.name(), "Osm score:",
               wayScore, "our feature score:", geomScore, "Total score", result));

  return result;
}

pugi::xml_node GetBestOsmNode(pugi::xml_document const & osmResponse, ms::LatLon const & latLon)
{
  double bestScore = -1;
  pugi::xml_node bestMatchNode;

  for (auto const & xNode : osmResponse.select_nodes("osm/node"))
  {
    try
    {
      XMLFeature xmlFt(xNode.node());

      double const nodeScore = ScoreLatLon(xmlFt, latLon);
      if (nodeScore < 0)
        continue;

      if (bestScore < nodeScore)
      {
        bestScore = nodeScore;
        bestMatchNode = xNode.node();
      }
    }
    catch (editor::NoLatLon const & ex)
    {
      LOG(LWARNING, ("No lat/lon attribute in osm response node.", ex.Msg()));
      continue;
    }
  }

  // TODO(mgsergio): Add a properly defined threshold when more fields will be compared.
  // if (bestScore < kMiniScoreThreshold)
  //   return pugi::xml_node;

  return bestMatchNode;
}

pugi::xml_node GetBestOsmWayOrRelation(pugi::xml_document const & osmResponse,
                                       vector<m2::PointD> const & geometry)
{
  double bestScore = -1;
  pugi::xml_node bestMatchWay;

  auto const xpath = "osm/way|osm/relation[tag[@k='type' and @v='multipolygon']]";
  for (auto const & xWayOrRelation : osmResponse.select_nodes(xpath))
  {
    double const nodeScore = ScoreGeometry(osmResponse, xWayOrRelation.node(), geometry);
    if (nodeScore < 0)
      continue;

    if (bestScore < nodeScore)
    {
      bestScore = nodeScore;
      bestMatchWay = xWayOrRelation.node();
    }
  }

  // TODO(mgsergio): Add a properly defined threshold when more fields will be compared.
  // if (bestScore < kMiniScoreThreshold)
  //   return pugi::xml_node;

  return bestMatchWay;
}
}  // namespace osm