forked from organicmaps/organicmaps
[search] Added a json-serializable struct for search data samples.
This commit is contained in:
parent
1c0c7b7a3a
commit
6a37fd2ee0
6 changed files with 414 additions and 5 deletions
|
@ -6,10 +6,8 @@
|
|||
|
||||
#include <jansson.h>
|
||||
|
||||
|
||||
namespace my
|
||||
{
|
||||
|
||||
class Json
|
||||
{
|
||||
JsonHandle m_handle;
|
||||
|
@ -27,5 +25,4 @@ public:
|
|||
|
||||
json_t * get() const { return m_handle.get(); }
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace my
|
||||
|
|
191
search/sample.cpp
Normal file
191
search/sample.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include "sample.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "std/sstream.hpp"
|
||||
#include "std/string.hpp"
|
||||
|
||||
#include "3party/jansson/myjansson.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
void ParsePosition(json_t * json, m2::PointD & pos)
|
||||
{
|
||||
if (!json_is_object(json))
|
||||
MYTHROW(my::Json::Exception, ("Position must be a json object."));
|
||||
|
||||
json_t * posX = json_object_get(json, "x");
|
||||
json_t * posY = json_object_get(json, "y");
|
||||
if (!posX || !posY)
|
||||
MYTHROW(my::Json::Exception, ("Position must have both x and y set."));
|
||||
if (!json_is_number(posX) || !json_is_number(posY))
|
||||
MYTHROW(my::Json::Exception, ("Position's x and y must be numbers."));
|
||||
pos.x = json_number_value(posX);
|
||||
pos.y = json_number_value(posY);
|
||||
}
|
||||
|
||||
string ParseObligatoryString(json_t * root, string const & fieldName)
|
||||
{
|
||||
json_t * str = json_object_get(root, fieldName.c_str());
|
||||
if (!str)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", fieldName, "is absent."));
|
||||
if (!json_is_string(str))
|
||||
MYTHROW(my::Json::Exception, ("The field", fieldName, "must contain a json string."));
|
||||
return string(json_string_value(str));
|
||||
}
|
||||
|
||||
void ParseResult(json_t * root, search::Sample::Result & result)
|
||||
{
|
||||
json_t * position = json_object_get(root, "position");
|
||||
if (!position)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", "position", "is absent."));
|
||||
ParsePosition(position, result.m_pos);
|
||||
|
||||
result.m_name = strings::MakeUniString(ParseObligatoryString(root, "name"));
|
||||
|
||||
result.m_houseNumber = ParseObligatoryString(root, "houseNumber");
|
||||
|
||||
json_t * types = json_object_get(root, "types");
|
||||
if (!types)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", "types", "is absent."));
|
||||
if (!json_is_array(types))
|
||||
MYTHROW(my::Json::Exception, ("The field", "types", "must contain a json array."));
|
||||
size_t const numTypes = json_array_size(types);
|
||||
result.m_types.resize(numTypes);
|
||||
for (size_t i = 0; i < numTypes; ++i)
|
||||
{
|
||||
json_t * type = json_array_get(types, i);
|
||||
if (!json_is_string(type))
|
||||
MYTHROW(my::Json::Exception, ("Result types must be strings."));
|
||||
result.m_types[i] = json_string_value(type);
|
||||
}
|
||||
|
||||
string relevance = ParseObligatoryString(root, "relevancy");
|
||||
if (relevance == "vital")
|
||||
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_VITAL;
|
||||
else if (relevance == "relevant")
|
||||
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_RELEVANT;
|
||||
else
|
||||
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_IRRELEVANT;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace search
|
||||
{
|
||||
bool Sample::DeserializeFromJSON(string const & jsonStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
my::Json root(jsonStr.c_str());
|
||||
DeserializeFromJSONImpl(root.get());
|
||||
return true;
|
||||
}
|
||||
catch (my::Json::Exception const & e)
|
||||
{
|
||||
LOG(LDEBUG, ("Can't parse sample:", e.Msg(), jsonStr));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool Sample::DeserializeFromJSON(string const & jsonStr, vector<Sample> & samples)
|
||||
{
|
||||
try
|
||||
{
|
||||
my::Json root(jsonStr.c_str());
|
||||
if (!json_is_array(root.get()))
|
||||
MYTHROW(my::Json::Exception, ("The field", "samples", "must contain a json array."));
|
||||
size_t numSamples = json_array_size(root.get());
|
||||
samples.resize(numSamples);
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
samples[i].DeserializeFromJSONImpl(json_array_get(root.get(), i));
|
||||
return true;
|
||||
}
|
||||
catch (my::Json::Exception const & e)
|
||||
{
|
||||
LOG(LERROR, ("Can't parse samples:", e.Msg(), jsonStr));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string Sample::ToStringDebug() const
|
||||
{
|
||||
ostringstream oss;
|
||||
oss << "[";
|
||||
oss << "query: " << DebugPrint(m_query) << " ";
|
||||
oss << "locale: " << m_locale << " ";
|
||||
oss << "pos: " << DebugPrint(m_pos) << " ";
|
||||
oss << "viewport: " << DebugPrint(m_viewport) << " ";
|
||||
oss << "results: [";
|
||||
for (size_t i = 0; i < m_results.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
oss << " ";
|
||||
oss << DebugPrint(m_results[i]);
|
||||
}
|
||||
oss << "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void Sample::DeserializeFromJSONImpl(json_t * root)
|
||||
{
|
||||
m_query = strings::MakeUniString(ParseObligatoryString(root, "query"));
|
||||
m_locale = ParseObligatoryString(root, "locale");
|
||||
|
||||
json_t * position = json_object_get(root, "position");
|
||||
if (!position)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", "position", "is absent."));
|
||||
ParsePosition(position, m_pos);
|
||||
|
||||
json_t * viewport = json_object_get(root, "viewport");
|
||||
if (!viewport)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", "viewport", "is absent."));
|
||||
m_viewport.setMinX(json_number_value(json_object_get(viewport, "minx")));
|
||||
m_viewport.setMinY(json_number_value(json_object_get(viewport, "miny")));
|
||||
m_viewport.setMaxX(json_number_value(json_object_get(viewport, "maxx")));
|
||||
m_viewport.setMaxY(json_number_value(json_object_get(viewport, "maxy")));
|
||||
|
||||
json_t * results = json_object_get(root, "results");
|
||||
if (!results)
|
||||
MYTHROW(my::Json::Exception, ("Obligatory field", "results", "is absent."));
|
||||
if (!json_is_array(results))
|
||||
MYTHROW(my::Json::Exception, ("The field", "results", "must contain a json array."));
|
||||
size_t numResults = json_array_size(results);
|
||||
m_results.resize(numResults);
|
||||
for (size_t i = 0; i < numResults; ++i)
|
||||
ParseResult(json_array_get(results, i), m_results[i]);
|
||||
}
|
||||
|
||||
string DebugPrint(Sample::Result::Relevance r)
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case Sample::Result::RELEVANCE_IRRELEVANT: return "Irrelevant";
|
||||
case Sample::Result::RELEVANCE_RELEVANT: return "Relevant";
|
||||
case Sample::Result::RELEVANCE_VITAL: return "Vital";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
string DebugPrint(Sample::Result const & r)
|
||||
{
|
||||
ostringstream oss;
|
||||
oss << "relevance: " << DebugPrint(r.m_relevance) << " ";
|
||||
oss << "name: " << DebugPrint(r.m_name) << " ";
|
||||
oss << "house number: " << r.m_houseNumber << " ";
|
||||
oss << "pos: " << r.m_pos << " ";
|
||||
oss << "types: [";
|
||||
for (size_t i = 0; i < r.m_types.size(); ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
oss << " ";
|
||||
oss << r.m_types[i];
|
||||
}
|
||||
oss << "]";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
string DebugPrint(Sample const & s) { return s.ToStringDebug(); }
|
||||
|
||||
} // namespace search
|
54
search/sample.hpp
Normal file
54
search/sample.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include "std/string.hpp"
|
||||
|
||||
#include "3party/jansson/myjansson.hpp"
|
||||
|
||||
namespace search
|
||||
{
|
||||
class Sample
|
||||
{
|
||||
public:
|
||||
struct Result
|
||||
{
|
||||
enum Relevance
|
||||
{
|
||||
RELEVANCE_IRRELEVANT,
|
||||
RELEVANCE_RELEVANT,
|
||||
RELEVANCE_VITAL
|
||||
};
|
||||
|
||||
m2::PointD m_pos;
|
||||
strings::UniString m_name;
|
||||
string m_houseNumber;
|
||||
vector<string> m_types; // MAPS.ME types, not OSM types.
|
||||
Relevance m_relevance;
|
||||
};
|
||||
|
||||
bool DeserializeFromJSON(string const &);
|
||||
|
||||
static bool DeserializeFromJSON(string const &, vector<Sample> &);
|
||||
|
||||
string ToStringDebug() const;
|
||||
|
||||
private:
|
||||
void DeserializeFromJSONImpl(json_t *);
|
||||
|
||||
strings::UniString m_query;
|
||||
string m_locale;
|
||||
m2::PointD m_pos = m2::PointD(0, 0);
|
||||
m2::RectD m_viewport = m2::RectD(0, 0, 0, 0);
|
||||
vector<Result> m_results;
|
||||
};
|
||||
|
||||
string DebugPrint(Sample::Result::Relevance);
|
||||
|
||||
string DebugPrint(Sample::Result const &);
|
||||
|
||||
string DebugPrint(Sample const &);
|
||||
} // namespace search
|
|
@ -8,6 +8,8 @@ ROOT_DIR = ..
|
|||
|
||||
include($$ROOT_DIR/common.pri)
|
||||
|
||||
INCLUDEPATH += $$ROOT_DIR/3party/jansson/src
|
||||
|
||||
HEADERS += \
|
||||
algos.hpp \
|
||||
approximate_string_match.hpp \
|
||||
|
@ -32,6 +34,7 @@ HEADERS += \
|
|||
result.hpp \
|
||||
retrieval.hpp \
|
||||
reverse_geocoder.hpp \
|
||||
sample.hpp \
|
||||
search_common.hpp \
|
||||
search_engine.hpp \
|
||||
search_index_values.hpp \
|
||||
|
@ -78,6 +81,7 @@ SOURCES += \
|
|||
result.cpp \
|
||||
retrieval.cpp \
|
||||
reverse_geocoder.cpp \
|
||||
sample.cpp \
|
||||
search_engine.cpp \
|
||||
search_query.cpp \
|
||||
search_query_params.cpp \
|
||||
|
|
160
search/search_tests/sample_test.cpp
Normal file
160
search/search_tests/sample_test.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "search/sample.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
using namespace search;
|
||||
|
||||
UNIT_TEST(Sample_Smoke)
|
||||
{
|
||||
auto const jsonStr = R"EOF(
|
||||
{
|
||||
"query": "cuba",
|
||||
"locale": "en",
|
||||
"position": {
|
||||
"x": 37.618706,
|
||||
"y": 99.53730574302003
|
||||
},
|
||||
"viewport": {
|
||||
"minx": 37.1336,
|
||||
"miny": 67.1349,
|
||||
"maxx": 38.0314,
|
||||
"maxy": 67.7348
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"name": "Cuba",
|
||||
"relevancy": "relevant",
|
||||
"types": [
|
||||
"place-country"
|
||||
],
|
||||
"position": {
|
||||
"x": -80.832886,
|
||||
"y": 15.521132748163712
|
||||
},
|
||||
"houseNumber": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
)EOF";
|
||||
|
||||
Sample s;
|
||||
TEST(s.DeserializeFromJSON(jsonStr), ());
|
||||
|
||||
LOG(LINFO, (DebugPrint(s)));
|
||||
}
|
||||
|
||||
UNIT_TEST(Sample_BadViewport)
|
||||
{
|
||||
auto const jsonStr = R"EOF(
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"houseNumber": "",
|
||||
"position": {
|
||||
"y": 15.521132748163712,
|
||||
"x": -80.832886
|
||||
},
|
||||
"types": [
|
||||
"place-country"
|
||||
],
|
||||
"relevancy": "relevant",
|
||||
"name": "Cuba"
|
||||
}
|
||||
],
|
||||
"viewport": {
|
||||
"maxy": 67.7348,
|
||||
"maxx": 38.0314,
|
||||
},
|
||||
"position": {
|
||||
"y": 99.53730574302003,
|
||||
"x": 37.618706
|
||||
},
|
||||
"locale": "en",
|
||||
"query": "cuba"
|
||||
}
|
||||
)EOF";
|
||||
|
||||
Sample s;
|
||||
TEST(!s.DeserializeFromJSON(jsonStr), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Sample_Arrays)
|
||||
{
|
||||
auto const jsonStr = R"EOF(
|
||||
[
|
||||
{
|
||||
"query": "cuba",
|
||||
"locale": "en",
|
||||
"position": {
|
||||
"x": 37.618706,
|
||||
"y": 99.53730574302003
|
||||
},
|
||||
"viewport": {
|
||||
"minx": 37.1336,
|
||||
"miny": 67.1349,
|
||||
"maxx": 38.0314,
|
||||
"maxy": 67.7348
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"name": "Cuba",
|
||||
"relevancy": "relevant",
|
||||
"types": [
|
||||
"place-country"
|
||||
],
|
||||
"position": {
|
||||
"x": -80.832886,
|
||||
"y": 15.521132748163712
|
||||
},
|
||||
"houseNumber": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"query": "riga",
|
||||
"locale": "en",
|
||||
"position": {
|
||||
"x": 37.65376,
|
||||
"y": 98.51110651930014
|
||||
},
|
||||
"viewport": {
|
||||
"minx": 37.5064,
|
||||
"miny": 67.0476,
|
||||
"maxx": 37.7799,
|
||||
"maxy": 67.304
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"name": "R\u012bga",
|
||||
"relevancy": "vital",
|
||||
"types": [
|
||||
"place-city-capital-2"
|
||||
],
|
||||
"position": {
|
||||
"x": 24.105186,
|
||||
"y": 107.7819569220319
|
||||
},
|
||||
"houseNumber": ""
|
||||
},
|
||||
{
|
||||
"name": "R\u012bga",
|
||||
"relevancy": "vital",
|
||||
"types": [
|
||||
"place-city-capital-2"
|
||||
],
|
||||
"position": {
|
||||
"x": 24.105186,
|
||||
"y": 107.7819569220319
|
||||
},
|
||||
"houseNumber": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
)EOF";
|
||||
|
||||
vector<Sample> samples;
|
||||
TEST(Sample::DeserializeFromJSON(jsonStr, samples), ());
|
||||
}
|
|
@ -6,10 +6,12 @@ CONFIG -= app_bundle
|
|||
TEMPLATE = app
|
||||
|
||||
ROOT_DIR = ../..
|
||||
DEPENDENCIES = search indexer platform editor geometry coding base protobuf tomcrypt succinct pugixml
|
||||
DEPENDENCIES = search indexer platform editor geometry coding base protobuf jansson tomcrypt succinct pugixml
|
||||
|
||||
include($$ROOT_DIR/common.pri)
|
||||
|
||||
INCLUDEPATH += $$ROOT_DIR/3party/jansson/src
|
||||
|
||||
QT *= core
|
||||
|
||||
macx-*: LIBS *= "-framework IOKit"
|
||||
|
@ -27,6 +29,7 @@ SOURCES += \
|
|||
locality_scorer_test.cpp \
|
||||
query_saver_tests.cpp \
|
||||
ranking_tests.cpp \
|
||||
sample_test.cpp \
|
||||
string_intersection_test.cpp \
|
||||
string_match_test.cpp \
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue