Patrick Brosi 4 gadi atpakaļ
vecāks
revīzija
5d548486cb

+ 1 - 1
src/osmfixer/CMakeLists.txt

@@ -20,7 +20,7 @@ add_custom_command(
20 20
 
21 21
 add_custom_command(
22 22
 	OUTPUT build.h
23
-	COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && cat leaflet-heat.js script.js > build.js && xxd -i build.js > ${CMAKE_CURRENT_BINARY_DIR}/build.h && rm build.js
23
+	COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && cat leaflet.js leaflet-heat.js script.js > merged.js && java -jar closurec/compiler.jar -O SIMPLE leaflet.js leaflet-heat.js script.js > build.js && xxd -i build.js > ${CMAKE_CURRENT_BINARY_DIR}/build.h && rm build.js
24 24
 	DEPENDS "${PROJECT_BINARY_DIR}/../web/script.js"
25 25
 	DEPENDS "${PROJECT_BINARY_DIR}/../web/leaflet-heat.js"
26 26
 	VERBATIM

+ 2 - 1
src/osmfixer/FixerMain.cpp

@@ -56,6 +56,7 @@ int main(int argc, char** argv) {
56 56
   std::vector<std::pair<IdRange, StatIdx>> idx(inputPaths.size());
57 57
   IdRange idRange{0, 0, 0, 0, 0, 0};
58 58
   for (size_t i = 0; i < inputPaths.size(); i++) {
59
+    LOG(INFO) << "Processing " << inputPaths[i];
59 60
     idx[i].first = idRange;
60 61
     if (i > 0) {
61 62
       idx[i].first.sidStart = idx[i - 1].first.sidEnd + 1;
@@ -70,7 +71,7 @@ int main(int argc, char** argv) {
70 71
   }
71 72
 
72 73
   LOG(INFO) << "Building search index...";
73
-  osmfixer::SearchIdx searchIdx(idx.front().second);
74
+  osmfixer::SearchIdx searchIdx(idx);
74 75
 
75 76
   LOG(INFO) << "Starting server...";
76 77
   StatServer serv(idx, searchIdx);

+ 67 - 58
src/osmfixer/index/SearchIdx.cpp

@@ -14,57 +14,64 @@
14 14
 #include "osmfixer/index/SearchIdx.h"
15 15
 
16 16
 using osmfixer::SearchIdx;
17
-using osmfixer::TupleList;
18 17
 using osmfixer::TripleList;
18
+using osmfixer::TupleList;
19 19
 
20 20
 // _____________________________________________________________________________
21
-void SearchIdx::build() {
21
+void SearchIdx::build(std::vector<std::pair<IdRange, StatIdx>>& idxs) {
22 22
   _qGramIndex.clear();
23 23
 
24 24
   size_t nameid = 0;
25 25
 
26 26
   std::map<std::wstring, size_t> tokenIds;
27 27
 
28
-  for (size_t gid = 0; gid < _stats.getGroups().size(); gid++) {
29
-		auto group = _stats.getGroup(gid);
30
-
31
-		// dont index empty groups
32
-		if (group->stations.size() == 0) continue;
33
-		if (group->polyStations.size() == 0) continue;
34
-
35
-    for (const auto& name : _stats.getGroup(gid)->uniqueNames) {
36
-      // use wstring to get UTF-8 chars right
37
-      std::wstring wname =
38
-          std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(name);
39
-
40
-      _nameToGroup[nameid] = gid;
41
-			_names.push_back(name);
42
-
43
-      for (const auto& token : tokenize(wname)) {
44
-        if (tokenIds.count(token)) {
45
-          if (_inv[tokenIds.find(token)->second].size() == 0 ||
46
-              _inv[tokenIds.find(token)->second].back().first != nameid) {
47
-            // only use a token once per station
48
-            _inv[tokenIds.find(token)->second].push_back({nameid, 1});
28
+  for (const auto& idx : idxs) {
29
+    for (size_t gid = 0; gid < idx.second.getGroups().size(); gid++) {
30
+      auto group = idx.second.getGroup(gid);
31
+
32
+      // slightly prefer larger stations
33
+      double groupScore = 1.0 + log(group->stations.size()) / 20;
34
+
35
+      // dont index empty groups
36
+      if (group->stations.size() == 0) continue;
37
+      if (group->polyStations.size() == 0) continue;
38
+
39
+      for (const auto& namep : group->uniqueNames) {
40
+        // use wstring to get UTF-8 chars right
41
+        const auto& name = namep.second;
42
+        std::wstring wname =
43
+            std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(name);
44
+
45
+        _nameToGroup[nameid] = idx.first.gidStart + gid;
46
+        _names.push_back(name);
47
+
48
+        for (const auto& token : tokenize(wname)) {
49
+          if (tokenIds.count(token)) {
50
+            if (_inv[tokenIds.find(token)->second].size() == 0 ||
51
+                _inv[tokenIds.find(token)->second].back().first != nameid) {
52
+              // only use a token once per station
53
+              _inv[tokenIds.find(token)->second].push_back(
54
+                  {nameid, groupScore});
55
+            }
56
+          } else {
57
+            tokenIds[token] = _tokens.size();
58
+            _tokens.push_back(token);
59
+            _inv.push_back(TupleList());
60
+            _inv.back().push_back({nameid, groupScore});
49 61
           }
50
-        } else {
51
-          tokenIds[token] = _tokens.size();
52
-          _tokens.push_back(token);
53
-          _inv.push_back(TupleList());
54
-          _inv.back().push_back({nameid, 1});
55
-        }
56 62
 
57
-        size_t tokenId = tokenIds.find(token)->second;
58
-        for (const auto& qGram : getQGrams(token)) {
59
-          if (_qGramIndex[qGram].size() &&
60
-              _qGramIndex[qGram].back().first == tokenId) {
61
-            _qGramIndex[qGram].back().second++;
62
-          } else {
63
-            _qGramIndex[qGram].push_back({tokenId, 1});
63
+          size_t tokenId = tokenIds.find(token)->second;
64
+          for (const auto& qGram : getQGrams(token)) {
65
+            if (_qGramIndex[qGram].size() &&
66
+                _qGramIndex[qGram].back().first == tokenId) {
67
+              _qGramIndex[qGram].back().second++;
68
+            } else {
69
+              _qGramIndex[qGram].push_back({tokenId, 1});
70
+            }
64 71
           }
65 72
         }
73
+        nameid++;
66 74
       }
67
-      nameid++;
68 75
     }
69 76
   }
70 77
 
@@ -101,7 +108,7 @@ std::vector<std::wstring> SearchIdx::tokenize(const std::wstring& str) {
101 108
   std::vector<std::wstring> ret;
102 109
   std::wstring cur;
103 110
 
104
-  const wchar_t* seps = L"_-?'\"|!@#$%^&*()_+}|><.,\\";
111
+  const wchar_t* seps = L"_-?'\"|!@#$%^&*()_+{}[]|<>.:,\\/";
105 112
 
106 113
   for (size_t i = 0; i < str.size(); i++) {
107 114
     if (std::iswspace(str[i]) || wcschr(seps, str[i])) {
@@ -147,42 +154,43 @@ TripleList SearchIdx::find(const std::string& qry) const {
147 154
     double delta = token.size() / 4.0;
148 155
     auto res = tokenFind(token);
149 156
 
150
-    std::partial_sort(res.begin(), res.begin() + std::min<size_t>(100, res.size()), res.end(), resComp);
157
+    std::partial_sort(res.begin(),
158
+                      res.begin() + std::min<size_t>(100, res.size()),
159
+                      res.end(), resComp);
151 160
 
152 161
     std::map<size_t, double> bests;
153 162
     std::map<size_t, size_t> bestToken;
154 163
 
155
-		size_t TOPK = 100;
164
+    size_t TOPK = 100;
156 165
 
157
-		// res contains the 100 best token matches
166
+    // res contains the 100 best token matches
158 167
     for (size_t i = 0; i < res.size() && i < TOPK; i++) {
159 168
       for (size_t j = 0; j < _inv[res[i].first].size(); j++) {
169
+        double score =
170
+            _inv[res[i].first][j].second * (delta / (1.0 + res[i].second));
160 171
 
161
-				double score = _inv[res[i].first][j].second * (delta / (1.0 + res[i].second));
162
-
163
-				if (score > bests[_inv[res[i].first][j].first]) {
164
-						bests[_inv[res[i].first][j].first] = score;
165
-						bestToken[_inv[res[i].first][j].first] = res[i].first;
166
-				}
172
+        if (score > bests[_inv[res[i].first][j].first]) {
173
+          bests[_inv[res[i].first][j].first] = score;
174
+          bestToken[_inv[res[i].first][j].first] = res[i].first;
175
+        }
167 176
       }
168 177
     }
169 178
 
170 179
     for (size_t i = 0; i < res.size() && i < TOPK; i++) {
171
-			// inv[res[i]] contains all the names the token res[i] occurs in
180
+      // inv[res[i]] contains all the names the token res[i] occurs in
172 181
 
173 182
       lists.push_back(_inv[res[i].first]);
174 183
 
175 184
       // give them a score based on their PED
176
-			for (size_t j = 0; j < lists.back().size(); j++) {
177
-				double score = lists.back()[j].second *
178
-                                 (delta / (1.0 + res[i].second));
179
-				// best is the token for this name that matched best for the input token
180
-				size_t best = bestToken[lists.back()[j].first];
181
-
182
-				// if it is not this token, we dont count it
183
-				if (res[i].first != best) score = 0;
185
+      for (size_t j = 0; j < lists.back().size(); j++) {
186
+        double score = lists.back()[j].second * (delta / (1.0 + res[i].second));
187
+        // best is the token for this name that matched best for the input token
188
+        size_t best = bestToken[lists.back()[j].first];
189
+
190
+        // if it is not this token, we dont count it
191
+        if (res[i].first != best) score = 0;
184 192
         lists.back()[j].second = score;
185
-			}
193
+      }
186 194
     }
187 195
   }
188 196
 
@@ -201,7 +209,8 @@ TripleList SearchIdx::find(const std::string& qry) const {
201 209
 
202 210
   TripleList fin;
203 211
   for (const auto& r : fr) {
204
-    if (fin.size() == 0 || fin.back().first.first != r.first.first) fin.push_back(r);
212
+    if (fin.size() == 0 || fin.back().first.first != r.first.first)
213
+      fin.push_back(r);
205 214
   }
206 215
 
207 216
   std::partial_sort(fin.begin(), fin.begin() + std::min<size_t>(fin.size(), 10),

+ 8 - 5
src/osmfixer/index/SearchIdx.h

@@ -29,15 +29,20 @@ inline bool resCompInv(const Triple& lh, const Triple& rh) {
29 29
 
30 30
 inline bool resGidCompInv(const Triple& lh, const Triple& rh) {
31 31
   if (lh.first.first < rh.first.first) return true;
32
-  if (lh.first.first == rh.first.first) return lh.second > rh.second;
32
+  if (lh.first.first == rh.first.first) {
33
+    // if the scores are equal, order by the name id to prefer the original
34
+    // name id, which is lowest!
35
+    if (fabs(lh.second - rh.second) < 0.01) return lh.first.second < rh.first.second;
36
+    return lh.second > rh.second;
37
+  }
33 38
   return false;
34 39
 }
35 40
 
36 41
 class SearchIdx {
37 42
  public:
38
-  explicit SearchIdx(const StatIdx& sidx) : _stats(sidx) { build(); }
43
+  explicit SearchIdx(std::vector<std::pair<IdRange, StatIdx>>& idxs) { build(idxs); }
39 44
 
40
-  void build();
45
+  void build(std::vector<std::pair<IdRange, StatIdx>>& idxs);
41 46
 
42 47
   TripleList find(const std::string& qry) const;
43 48
 
@@ -54,8 +59,6 @@ class SearchIdx {
54 59
   TupleList tokenFind(const std::wstring& prefix) const;
55 60
   static TupleList lmerge(const std::vector<const TupleList*>& lists);
56 61
 
57
-  const StatIdx& _stats;
58
-
59 62
   Index _qGramIndex;
60 63
 
61 64
   std::unordered_map<size_t, size_t> _nameToGroup;

+ 29 - 11
src/osmfixer/index/StatIdx.cpp

@@ -210,7 +210,6 @@ void StatIdx::addGroup(size_t osmid, const OsmAttrs& attrs) {
210 210
 // _____________________________________________________________________________
211 211
 void StatIdx::initGroups() {
212 212
   for (size_t i = 0; i < _stations.size(); i++) {
213
-
214 213
     // this should be ensured by the input file
215 214
     assert(_stations[i].origGroup < _groups.size());
216 215
     _groups[_stations[i].origGroup].polyStations.push_back(i);
@@ -238,24 +237,26 @@ void StatIdx::initGroups() {
238 237
         if (usedGroupNames.count(name)) continue;
239 238
         usedGroupNames.insert(name);
240 239
 
241
-        _groups[gid].uniqueNames.push_back(name);
240
+        // relation names are preferred
241
+        _groups[gid].uniqueNames.push_back({nameAttrRel(np.first) + 3, name});
242 242
       }
243 243
     }
244 244
 
245
-    for (size_t sid :_groups[gid].stations) {
245
+    for (size_t sid : _groups[gid].stations) {
246 246
       for (const auto& np : _stations[sid].attrs) {
247 247
         for (const auto& name : np.second) {
248 248
           // use every name only once to keep the index small
249 249
           if (usedGroupNames.count(name)) continue;
250 250
           usedGroupNames.insert(name);
251 251
 
252
-          _groups[gid].uniqueNames.push_back(name);
252
+          _groups[gid].uniqueNames.push_back({nameAttrRel(np.first), name});
253 253
         }
254 254
       }
255 255
     }
256 256
 
257 257
     // take the longest name as the identifier
258
-    std::sort(_groups[gid].uniqueNames.begin(), _groups[gid].uniqueNames.end(), byLen);
258
+    std::sort(_groups[gid].uniqueNames.begin(), _groups[gid].uniqueNames.end(),
259
+              byAttrScore);
259 260
   }
260 261
 
261 262
   // build hull polygon
@@ -284,7 +285,6 @@ util::geo::Polygon<double> StatIdx::hull(
284 285
   util::geo::MultiPoint<double> mp;
285 286
   util::geo::Polygon<double> ret;
286 287
   for (const auto& geom : imp) {
287
-    // double rad = 11.0;
288 288
     int n = 20;
289 289
     for (int i = 0; i < n; i++) {
290 290
       double x = rad * cos((2.0 * M_PI / static_cast<double>(n)) *
@@ -311,7 +311,10 @@ void StatIdx::initIndex() {
311 311
                                                               _bbox, false);
312 312
 
313 313
   for (size_t i = 0; i < _stations.size(); i++) _sgrid.add(_stations[i].pos, i);
314
-  for (size_t i = 0; i < _groups.size(); i++) _ggrid.add(_groups[i].poly, i);
314
+  for (size_t i = 0; i < _groups.size(); i++) {
315
+    if (_groups[i].polyStations.size() == 1 && _groups[i].osmid < 2) continue;
316
+    _ggrid.add(_groups[i].poly, i);
317
+  }
315 318
   for (size_t i = 0; i < _suggestions.size(); i++)
316 319
     _suggrid.add(_suggestions[i].arrow, i);
317 320
 
@@ -566,7 +569,7 @@ void StatIdx::initSuggestions() {
566 569
   for (size_t i = 0; i < _groups.size(); i++) {
567 570
     auto& group = _groups[i];
568 571
 
569
-    if (group.attrs.size() == 0) {
572
+    if (group.attrs.count("name") == 0) {
570 573
       Suggestion sug;
571 574
       sug.station = i;
572 575
       sug.type = 7;
@@ -616,7 +619,7 @@ void StatIdx::initSuggestions() {
616 619
   for (size_t i = 0; i < _stations.size(); i++) {
617 620
     auto& stat = _stations[i];
618 621
 
619
-    if (stat.attrs.size() == 0) {
622
+    if (stat.attrs.count("name") == 0 && _groups[stat.group].attrs.count("name") == 0) {
620 623
       Suggestion sug;
621 624
       sug.station = i;
622 625
       sug.type = 7;
@@ -671,7 +674,8 @@ void StatIdx::initSuggestions() {
671 674
       sug.arrow = util::geo::DLine{centroid, centroid};
672 675
 
673 676
       if (getGroup(stat.origGroup)->osmid < 2) {
674
-        if (getGroup(stat.group)->osmid == 1) {
677
+        if (getGroup(stat.group)->osmid == 1 &&
678
+            getGroup(stat.group)->stations.size() > 1) {
675 679
           // move orphan into new group
676 680
           sug.type = 1;
677 681
           sug.target_gid = stat.group;
@@ -739,7 +743,6 @@ void StatIdx::initSuggestions() {
739 743
         }
740 744
       }
741 745
     }
742
-
743 746
   }
744 747
 }
745 748
 
@@ -799,6 +802,21 @@ const Group* StatIdx::getGroup(size_t id) const {
799 802
 }
800 803
 
801 804
 // _____________________________________________________________________________
805
+int StatIdx::nameAttrRel(const std::string& attr) const {
806
+  if (attr == "uic_name") return 5;
807
+  if (attr == "ref_name") return 5;
808
+  if (attr == "nat_name") return 4;
809
+  if (attr == "official_name") return 3;
810
+  if (attr == "gtfs_name") return 3;
811
+  if (attr == "int_name") return 2;
812
+  if (attr == "name") return 1;
813
+
814
+  if (attr == "alt_name") return -1;
815
+
816
+  return 0;
817
+}
818
+
819
+// _____________________________________________________________________________
802 820
 const Suggestion* StatIdx::getSuggestion(size_t id) const {
803 821
   if (id >= _suggestions.size()) return 0;
804 822
   return &_suggestions[id];

+ 12 - 5
src/osmfixer/index/StatIdx.h

@@ -15,10 +15,15 @@ namespace osmfixer {
15 15
 
16 16
 typedef std::map<std::string, std::vector<std::string>> OsmAttrs;
17 17
 
18
-inline bool byLen(const std::string& lh, const std::string& rh) {
19
-  return lh.size() > rh.size();
18
+inline bool byAttrScore(const std::pair<int, std::string>& lh,
19
+                  const std::pair<int, std::string>& rh) {
20
+  return lh.first > rh.first;
20 21
 }
21 22
 
23
+struct IdRange {
24
+  size_t sidStart, sidEnd, gidStart, gidEnd, suggIdStart, suggIdEnd;
25
+};
26
+
22 27
 struct AttrErr {
23 28
   std::string attr;
24 29
   std::string otherAttr;
@@ -60,7 +65,7 @@ struct Group {
60 65
   std::vector<size_t> polyStations;
61 66
   std::vector<AttrErr> attrErrs;
62 67
   std::vector<size_t> suggestions;
63
-  std::vector<std::string> uniqueNames;
68
+  std::vector<std::pair<int, std::string>> uniqueNames;
64 69
 };
65 70
 
66 71
 struct Cluster {
@@ -101,7 +106,6 @@ class StatIdx {
101 106
   const std::vector<Station>& getStations() const { return _stations; }
102 107
   const std::vector<Group>& getGroups() const { return _groups; }
103 108
 
104
-
105 109
  private:
106 110
   void addStation(int64_t osmid, const util::geo::DLine& geom,
107 111
                   const util::geo::DLine& latLngs, size_t origGroup,
@@ -112,9 +116,12 @@ class StatIdx {
112 116
   void initGroups();
113 117
   void initSuggestions();
114 118
 
119
+  int nameAttrRel(const std::string& attr) const;
120
+
115 121
   util::geo::DLine getGroupArrow(size_t stat, const util::geo::DPoint& p) const;
116 122
 
117
-  util::geo::Polygon<double> hull(const util::geo::MultiPoint<double>& imp, double rad) const;
123
+  util::geo::Polygon<double> hull(const util::geo::MultiPoint<double>& imp,
124
+                                  double rad) const;
118 125
 
119 126
   std::vector<Station> _stations;
120 127
   std::vector<Group> _groups;

+ 32 - 13
src/osmfixer/server/StatServer.cpp

@@ -2,9 +2,9 @@
2 2
 // Chair of Algorithms and Data Structures.
3 3
 // Authors: Patrick Brosi <brosi@informatik.uni-freiburg.de>
4 4
 
5
-#include <vector>
6 5
 #include <codecvt>
7 6
 #include <locale>
7
+#include <vector>
8 8
 #include "osmfixer/build.h"
9 9
 #include "osmfixer/index.h"
10 10
 #include "osmfixer/server/StatServer.h"
@@ -36,6 +36,7 @@ util::http::Answer StatServer::handle(const util::http::Req& req,
36 36
           "200 OK", std::string(build_js, build_js + sizeof build_js /
37 37
                                                          sizeof build_js[0]));
38 38
       a.params["Content-Type"] = "application/javascript; charset=utf-8";
39
+      a.params["Cache-Control"] = "public, max-age=10000";
39 40
     } else if (cmd == "/map") {
40 41
       a = handleMapReq(params);
41 42
     } else if (cmd == "/heatmap") {
@@ -211,10 +212,9 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
211 212
 
212 213
     const auto& gret = idx.getGroups(bbox);
213 214
     for (const auto& group : gret) {
214
-      if (group->polyStations.size() == 1 && group->osmid < 2) continue;
215 215
       json << sep;
216 216
       sep = ',';
217
-      printGroup(group, did, z < 15, &json);
217
+      printGroup(group, did, z < 15, false, &json);
218 218
     }
219 219
   }
220 220
 
@@ -295,7 +295,7 @@ void StatServer::printSugg(const Suggestion* sugg, size_t did,
295 295
 
296 296
 // _____________________________________________________________________________
297 297
 void StatServer::printGroup(const Group* group, size_t did, bool simple,
298
-                            std::ostream* out) const {
298
+                            bool aggr, std::ostream* out) const {
299 299
   const auto& range = _idxs[did].first;
300 300
 
301 301
   (*out) << "{\"i\":" << group->id + range.gidStart << ",\"g\":[";
@@ -310,11 +310,19 @@ void StatServer::printGroup(const Group* group, size_t did, bool simple,
310 310
     }
311 311
   }
312 312
   (*out) << "]";
313
-  if (group->osmid == 1) (*out) << ",\"n\":1";
313
+  bool sugg = group->osmid == 1 || group->suggestions.size() > 0;
314
+  int err = group->attrErrs.size();
315
+
316
+  if (aggr) {
317
+    for (const auto sid : group->polyStations) {
318
+      sugg = sugg || _idxs[did].second.getStation(sid)->suggestions.size();
319
+      err += _idxs[did].second.getStation(sid)->attrErrs.size();
320
+    }
321
+  }
314 322
 
315
-  if (group->suggestions.size() > 0) (*out) << ",\"s\":1";
323
+  if (sugg) (*out) << ",\"s\":1";
324
+  if (err) (*out) << ",\"e\":" << err;
316 325
 
317
-  if (group->attrErrs.size()) (*out) << ",\"e\":" << group->attrErrs.size();
318 326
   (*out) << "}";
319 327
 }
320 328
 
@@ -375,8 +383,7 @@ size_t StatServer::getDidByGid(size_t gid) const {
375 383
 
376 384
 // _____________________________________________________________________________
377 385
 util::http::Answer StatServer::handleSearch(const Params& pars) const {
378
-  if (pars.count("q") == 0)
379
-    throw std::invalid_argument("No query given.");
386
+  if (pars.count("q") == 0) throw std::invalid_argument("No query given.");
380 387
   std::string cb;
381 388
   if (pars.count("cb")) cb = pars.find("cb")->second.c_str();
382 389
 
@@ -400,15 +407,27 @@ util::http::Answer StatServer::handleSearch(const Params& pars) const {
400 407
     const auto& idx = _idxs[did].second;
401 408
     size_t idxGid = gid - range.gidStart;
402 409
     const auto group = idx.getGroup(idxGid);
403
-    json << sep << "{\"gid\":" << gid << ",\"score\":" << res.second << ", \"name\":";
404
-    std::string name = group->uniqueNames.front();
410
+
411
+    bool asStat = group->polyStations.size() == 1;
412
+
413
+    json << sep << "{\"n\":";
414
+    std::string name = group->uniqueNames.front().second;
405 415
     json << "\"" << util::jsonStringEscape(name) << "\"";
406 416
     std::string via = _searchIdx.getName(res.first.second);
407 417
     if (name != via) {
408
-      json << ",\"via\":" << "\"" << util::jsonStringEscape(via) << "\"";
418
+      json << ",\"v\":"
419
+           << "\"" << util::jsonStringEscape(via) << "\"";
409 420
     }
410 421
 
411
-    json << ",\"latlng\":{\"lat\":" << group->centroid.getY() << ",\"lng\":" << group->centroid.getX() <<"}";
422
+    if (asStat) {
423
+      auto stat = idx.getStation(group->polyStations.front());
424
+      if (stat->osmid < 0) json << ",\"w\":1";
425
+      json << ",\"s\":";
426
+      printStation(stat, did, true, &json);
427
+    } else {
428
+      json << ",\"g\":";
429
+      printGroup(group, did, true, true, &json);
430
+    }
412 431
 
413 432
     sep = ",";
414 433
     json << "}";

+ 1 - 5
src/osmfixer/server/StatServer.h

@@ -13,10 +13,6 @@
13 13
 
14 14
 namespace osmfixer {
15 15
 
16
-struct IdRange {
17
-  size_t sidStart, sidEnd, gidStart, gidEnd, suggIdStart, suggIdEnd;
18
-};
19
-
20 16
 typedef std::map<std::string, std::string> Params;
21 17
 
22 18
 class StatServer : public util::http::Handler {
@@ -38,7 +34,7 @@ class StatServer : public util::http::Handler {
38 34
   util::http::Answer handleSearch(const Params& pars) const;
39 35
 
40 36
   void printStation(const Station* stat, size_t did, bool simple, std::ostream* out) const;
41
-  void printGroup(const Group* stat, size_t did, bool simple, std::ostream* out) const;
37
+  void printGroup(const Group* stat, size_t did, bool simple, bool aggr, std::ostream* out) const;
42 38
   void printSugg(const Suggestion* stat, size_t did, std::ostream* out) const;
43 39
 
44 40
   size_t getDidBySid(size_t sid) const;

BIN
web/closurec/compiler.jar


+ 7 - 0
web/codes.txt

@@ -0,0 +1,7 @@
1
+U+0061
2
+U+0074
3
+U+0073
4
+U+0079
5
+U+002B
6
+U+002D
7
+U+2212

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 506 - 46
web/index.html


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 5 - 0
web/leaflet.js


BIN
web/nunito-cyr-ext.woff2


BIN
web/nunito-cyr.woff2


BIN
web/nunito-lat-ext.woff2


BIN
web/nunito-lat-full.woff2


BIN
web/nunito-lat.woff2


BIN
web/nunito-viet.woff2


BIN
web/roboto.woff2


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 239 - 303
web/script.js