Patrick Brosi 4 lat temu
rodzic
commit
3126ce4b12

+ 161 - 16
src/osmfixer/index/StatIdx.cpp

@@ -12,6 +12,7 @@
12 12
 #include "util/log/Log.h"
13 13
 
14 14
 using osmfixer::Group;
15
+using osmfixer::MetaGroup;
15 16
 using osmfixer::OsmAttrs;
16 17
 using osmfixer::StatIdx;
17 18
 using osmfixer::Station;
@@ -170,8 +171,26 @@ void StatIdx::readFromFile(const std::string& path) {
170 171
   }
171 172
   LOG(INFO) << "Done.";
172 173
 
174
+  // fifth, parse meta groups
175
+  LOG(INFO) << "Parsing meta groups... ";
176
+  while (std::getline(f, line)) {
177
+    // empty line is separator between blocks
178
+    if (line.size() == 0) break;
179
+
180
+    std::stringstream rec(line);
181
+    size_t gid, osmid;
182
+
183
+    rec >> gid;
184
+    rec >> osmid;
185
+
186
+    addGroupToMeta(gid, osmid);
187
+  }
188
+
189
+  LOG(INFO) << "Done.";
190
+
173 191
   LOG(INFO) << "Building indices...";
174 192
   initGroups();
193
+  initMetaGroups();
175 194
   initSuggestions();
176 195
 
177 196
   initIndex();
@@ -179,6 +198,13 @@ void StatIdx::readFromFile(const std::string& path) {
179 198
 }
180 199
 
181 200
 // _____________________________________________________________________________
201
+void StatIdx::addGroupToMeta(size_t gid, size_t metaId) {
202
+  _groups[gid].metaGroupId = metaId;
203
+  _metaGroups[metaId].osmid = metaId;
204
+  _metaGroups[metaId].groups.push_back(gid);
205
+}
206
+
207
+// _____________________________________________________________________________
182 208
 void StatIdx::addStation(int64_t osmid, const util::geo::DLine& geom,
183 209
                          const util::geo::DLine& latLngs, size_t origGroup,
184 210
                          size_t group, const OsmAttrs& attrs) {
@@ -203,11 +229,33 @@ void StatIdx::addGroup(size_t osmid, const OsmAttrs& attrs) {
203 229
   Group g;
204 230
   g.id = _groups.size();
205 231
   g.osmid = osmid;
232
+  g.metaGroupId = 0;
233
+  g.mergeId = 0;
206 234
   g.attrs = attrs;
207 235
   _groups.emplace_back(g);
208 236
 }
209 237
 
210 238
 // _____________________________________________________________________________
239
+void StatIdx::initMetaGroups() {
240
+  // build hull polygons
241
+  for (auto& p : _metaGroups) {
242
+    auto& mg = p.second;
243
+    util::geo::MultiPoint<double> mp;
244
+    for (size_t gid : mg.groups) {
245
+      for (auto p : _groups[gid].poly.getOuter()) mp.push_back(p);
246
+      mg.poly = hull(mp, 11);
247
+    }
248
+
249
+    for (auto p : mg.poly.getOuter()) {
250
+      mg.llPoly.getOuter().push_back(
251
+          util::geo::webMercToLatLng<double>(p.getX(), p.getY()));
252
+    }
253
+
254
+    mg.centroid = util::geo::centroid(mg.llPoly);
255
+  }
256
+}
257
+
258
+// _____________________________________________________________________________
211 259
 void StatIdx::initGroups() {
212 260
   for (size_t i = 0; i < _stations.size(); i++) {
213 261
     // this should be ensured by the input file
@@ -220,6 +268,16 @@ void StatIdx::initGroups() {
220 268
       _groups[_stations[i].origGroup].stations.push_back(i);
221 269
     }
222 270
 
271
+    if (_stations[i].group != _stations[i].origGroup &&
272
+        _groups[_stations[i].group].osmid > 2 &&
273
+        _groups[_stations[i].origGroup].osmid > 2) {
274
+      auto& orGr = _groups[_stations[i].origGroup];
275
+      if (orGr.mergeId == 0)
276
+        orGr.mergeId = _stations[i].group;
277
+      else if (orGr.mergeId != _stations[i].group)
278
+        orGr.mergeId = _stations[i].group;
279
+    }
280
+
223 281
     assert(_stations[i].group < _groups.size());
224 282
     if (_stations[i].group != _stations[i].origGroup &&
225 283
         _groups[_stations[i].group].osmid == 1) {
@@ -259,6 +317,14 @@ void StatIdx::initGroups() {
259 317
               byAttrScore);
260 318
   }
261 319
 
320
+  ByErrId byErrId(_stations);
321
+
322
+  // sort stations by error or sugg occurance
323
+  for (size_t gid = 0; gid < _groups.size(); gid++) {
324
+    std::sort(_groups[gid].stations.begin(), _groups[gid].stations.end(),
325
+              byErrId);
326
+  }
327
+
262 328
   // build hull polygon
263 329
   for (size_t i = 0; i < _groups.size(); i++) {
264 330
     util::geo::MultiPoint<double> mp;
@@ -307,6 +373,8 @@ void StatIdx::initIndex() {
307 373
                                                                _bbox, false);
308 374
   _ggrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
309 375
                                                                _bbox, false);
376
+  _mgrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
377
+                                                               _bbox, false);
310 378
   _suggrid = util::geo::Grid<size_t, util::geo::Line, double>(gSize, gSize,
311 379
                                                               _bbox, false);
312 380
 
@@ -315,6 +383,10 @@ void StatIdx::initIndex() {
315 383
     if (_groups[i].polyStations.size() == 1 && _groups[i].osmid < 2) continue;
316 384
     _ggrid.add(_groups[i].poly, i);
317 385
   }
386
+  for (const auto& gp : _metaGroups) {
387
+    if (gp.second.poly.getOuter().size() < 2) continue;
388
+    _mgrid.add(gp.second.poly, gp.first);
389
+  }
318 390
   for (size_t i = 0; i < _suggestions.size(); i++)
319 391
     _suggrid.add(_suggestions[i].arrow, i);
320 392
 
@@ -417,6 +489,27 @@ void StatIdx::initIndex() {
417 489
 }
418 490
 
419 491
 // _____________________________________________________________________________
492
+std::vector<const MetaGroup*> StatIdx::getMetaGroups(
493
+    const util::geo::DBox bbox) const {
494
+  std::vector<const MetaGroup*> ret;
495
+  auto ll = util::geo::latLngToWebMerc<double>(bbox.getLowerLeft().getX(),
496
+                                               bbox.getLowerLeft().getY());
497
+  auto ur = util::geo::latLngToWebMerc<double>(bbox.getUpperRight().getX(),
498
+                                               bbox.getUpperRight().getY());
499
+
500
+  std::set<size_t> tmp;
501
+  auto reqBox = util::geo::DBox(ll, ur);
502
+  _mgrid.get(reqBox, &tmp);
503
+
504
+  for (auto i : tmp) {
505
+    if (util::geo::intersects(_metaGroups.find(i)->second.poly, reqBox))
506
+      ret.push_back(&_metaGroups.find(i)->second);
507
+  }
508
+
509
+  return ret;
510
+}
511
+
512
+// _____________________________________________________________________________
420 513
 std::vector<const Suggestion*> StatIdx::getSuggestions(
421 514
     const util::geo::DBox bbox) const {
422 515
   std::vector<const Suggestion*> ret;
@@ -582,6 +675,37 @@ void StatIdx::initSuggestions() {
582 675
     Suggestion sug;
583 676
     sug.station = i;
584 677
 
678
+    if (group.mergeId != 0 && group.mergeId != group.id) {
679
+      if (getGroup(group.mergeId)->metaGroupId) {
680
+        // move group into meta group
681
+        sug.type = 10;
682
+        sug.station = -group.id;
683
+        sug.orig_gid = 0;
684
+        sug.orig_osm_rel_id = 0;
685
+        sug.target_gid = getGroup(group.mergeId)->metaGroupId;
686
+        sug.target_osm_rel_id = getGroup(group.mergeId)->metaGroupId;
687
+
688
+        auto a = util::geo::centroid(group.poly);
689
+        auto b = util::geo::centroid(
690
+            getMetaGroup(getGroup(group.mergeId)->metaGroupId)->poly);
691
+        sug.arrow = getGroupArrow(a, b);
692
+      } else {
693
+        // merge two groups
694
+        sug.type = 9;
695
+        sug.station = -group.id;
696
+        sug.orig_gid = 0;
697
+        sug.orig_osm_rel_id = 0;
698
+        sug.target_gid = group.mergeId;
699
+        sug.target_osm_rel_id = getGroup(group.mergeId)->osmid;
700
+        auto a = util::geo::centroid(group.poly);
701
+        auto b = util::geo::centroid(getGroup(group.mergeId)->poly);
702
+        sug.arrow = getGroupArrow(a, b);
703
+      }
704
+
705
+      _suggestions.push_back(sug);
706
+      group.suggestions.push_back(_suggestions.size() - 1);
707
+    }
708
+
585 709
     std::set<std::pair<std::string, uint16_t>> suggested;
586 710
     for (auto attrErr : group.attrErrs) {
587 711
       if (!attrErr.fromRel) continue;
@@ -620,8 +744,10 @@ void StatIdx::initSuggestions() {
620 744
   // station suggestions
621 745
   for (size_t i = 0; i < _stations.size(); i++) {
622 746
     auto& stat = _stations[i];
747
+    auto centroid = util::geo::centroid(stat.pos);
623 748
 
624
-    if (stat.attrs.count("name") == 0 && _groups[stat.group].attrs.count("name") == 0) {
749
+    if (stat.attrs.count("name") == 0 &&
750
+        _groups[stat.group].attrs.count("name") == 0) {
625 751
       Suggestion sug;
626 752
       sug.station = i;
627 753
       sug.type = 7;
@@ -672,8 +798,6 @@ void StatIdx::initSuggestions() {
672 798
     if (stat.group != stat.origGroup || getGroup(stat.group)->osmid == 1) {
673 799
       Suggestion sug;
674 800
       sug.station = i;
675
-      auto centroid = util::geo::centroid(stat.pos);
676
-      sug.arrow = util::geo::DLine{centroid, centroid};
677 801
 
678 802
       if (getGroup(stat.origGroup)->osmid < 2) {
679 803
         if (getGroup(stat.group)->osmid == 1 &&
@@ -692,7 +816,7 @@ void StatIdx::initSuggestions() {
692 816
           sug.target_osm_rel_id = getGroup(stat.group)->osmid;
693 817
 
694 818
           auto b = util::geo::centroid(getGroup(stat.group)->poly);
695
-          sug.arrow = getGroupArrow(i, b);
819
+          sug.arrow = getGroupArrow(centroid, b);
696 820
 
697 821
           _suggestions.push_back(sug);
698 822
           stat.suggestions.push_back(_suggestions.size() - 1);
@@ -707,20 +831,37 @@ void StatIdx::initSuggestions() {
707 831
           sug.target_gid = stat.group;
708 832
 
709 833
           auto b = util::geo::centroid(getGroup(stat.group)->poly);
710
-          sug.arrow = getGroupArrow(i, b);
834
+          sug.arrow = getGroupArrow(centroid, b);
711 835
 
712 836
           _suggestions.push_back(sug);
713 837
           stat.suggestions.push_back(_suggestions.size() - 1);
714 838
         } else if (getGroup(stat.group)->osmid > 1) {
715
-          // move station from relation into existing group
716
-          sug.type = 4;
839
+          if (stat.group != getGroup(stat.origGroup)->mergeId) {
840
+            // move station from relation into existing group
841
+            sug.type = 4;
842
+            sug.target_gid = stat.group;
843
+            sug.target_osm_rel_id = getGroup(stat.group)->osmid;
844
+
845
+            // dont output arrow if move is part of a group merge
846
+            auto b = util::geo::centroid(getGroup(stat.group)->poly);
847
+            sug.arrow = getGroupArrow(centroid, b);
848
+          } else {
849
+            continue;
850
+            // size_t metaId =
851
+            // getGroup(getGroup(stat.origGroup)->mergeId)->metaGroupId;
852
+            // if (metaId) {
853
+            // sug.type = 10;
854
+            // sug.target_gid = metaId;
855
+            // sug.target_osm_rel_id = metaId;
856
+            // } else {
857
+            // sug.type = 9;
858
+            // sug.target_gid = stat.group;
859
+            // sug.target_osm_rel_id = getGroup(stat.group)->osmid;
860
+            // }
861
+          }
862
+
717 863
           sug.orig_gid = stat.origGroup;
718 864
           sug.orig_osm_rel_id = getGroup(stat.origGroup)->osmid;
719
-          sug.target_gid = stat.group;
720
-          sug.target_osm_rel_id = getGroup(stat.group)->osmid;
721
-
722
-          auto b = util::geo::centroid(getGroup(stat.group)->poly);
723
-          sug.arrow = getGroupArrow(i, b);
724 865
 
725 866
           _suggestions.push_back(sug);
726 867
           stat.suggestions.push_back(_suggestions.size() - 1);
@@ -738,7 +879,7 @@ void StatIdx::initSuggestions() {
738 879
           b.setX(b.getX() + 50);
739 880
           b.setY(b.getY() + 50);
740 881
 
741
-          sug.arrow = getGroupArrow(i, b);
882
+          sug.arrow = getGroupArrow(centroid, b);
742 883
 
743 884
           _suggestions.push_back(sug);
744 885
           stat.suggestions.push_back(_suggestions.size() - 1);
@@ -749,10 +890,8 @@ void StatIdx::initSuggestions() {
749 890
 }
750 891
 
751 892
 // _____________________________________________________________________________
752
-util::geo::DLine StatIdx::getGroupArrow(size_t stat,
893
+util::geo::DLine StatIdx::getGroupArrow(const util::geo::DPoint& a,
753 894
                                         const util::geo::DPoint& b) const {
754
-  auto a = util::geo::centroid(getStation(stat)->pos);
755
-
756 895
   auto pl = util::geo::PolyLine<double>(a, b);
757 896
   auto bb = pl.getPointAtDist(fmax(pl.getLength() / 2, pl.getLength() - 5)).p;
758 897
 
@@ -804,6 +943,12 @@ const Group* StatIdx::getGroup(size_t id) const {
804 943
 }
805 944
 
806 945
 // _____________________________________________________________________________
946
+const MetaGroup* StatIdx::getMetaGroup(size_t id) const {
947
+  if (_metaGroups.count(id)) return &_metaGroups.find(id)->second;
948
+  return 0;
949
+}
950
+
951
+// _____________________________________________________________________________
807 952
 int StatIdx::nameAttrRel(const std::string& attr) const {
808 953
   if (attr == "uic_name") return 5;
809 954
   if (attr == "ref_name") return 5;

+ 32 - 3
src/osmfixer/index/StatIdx.h

@@ -16,7 +16,7 @@ namespace osmfixer {
16 16
 typedef std::map<std::string, std::vector<std::string>> OsmAttrs;
17 17
 
18 18
 inline bool byAttrScore(const std::pair<int, std::string>& lh,
19
-                  const std::pair<int, std::string>& rh) {
19
+                        const std::pair<int, std::string>& rh) {
20 20
   return lh.first > rh.first;
21 21
 }
22 22
 
@@ -34,13 +34,21 @@ struct AttrErr {
34 34
 
35 35
 struct Suggestion {
36 36
   uint16_t type;
37
-  size_t station;
37
+  int64_t station;
38 38
   // if negative, it is a way!
39 39
   int64_t target_gid, orig_gid, target_osm_rel_id, orig_osm_rel_id;
40 40
   std::string attrErrName;
41 41
   util::geo::DLine arrow;
42 42
 };
43 43
 
44
+struct MetaGroup {
45
+  size_t osmid;
46
+  util::geo::DPolygon poly;
47
+  util::geo::DPolygon llPoly;
48
+  util::geo::DPoint centroid;
49
+  std::vector<size_t> groups;
50
+};
51
+
44 52
 struct Station {
45 53
   size_t id;
46 54
   // if negative, it is a way!
@@ -60,9 +68,23 @@ inline bool byErr(const Station* lh, const Station* rh) {
60 68
   return a < b;
61 69
 }
62 70
 
71
+struct ByErrId {
72
+  ByErrId(const std::vector<Station>& stats) : stats(stats) {}
73
+  bool operator()(int64_t lhId, int64_t rhId) {
74
+    const auto& lh = stats[lhId];
75
+    const auto& rh = stats[rhId];
76
+    size_t a = lh.attrErrs.size() ? 2 : lh.suggestions.size() ? 1 : 0;
77
+    size_t b = rh.attrErrs.size() ? 2 : rh.suggestions.size() ? 1 : 0;
78
+    return a > b;
79
+  }
80
+  const std::vector<Station>& stats;
81
+};
82
+
63 83
 struct Group {
64 84
   size_t id;
65 85
   int64_t osmid;
86
+  size_t mergeId;
87
+  size_t metaGroupId;
66 88
   util::geo::DPolygon poly;
67 89
   util::geo::DPolygon llPoly;
68 90
   util::geo::DPoint centroid;
@@ -95,10 +117,12 @@ class StatIdx {
95 117
   std::vector<const Group*> getGroups(const util::geo::DBox bbox) const;
96 118
   std::vector<const Suggestion*> getSuggestions(
97 119
       const util::geo::DBox bbox) const;
120
+  std::vector<const MetaGroup*> getMetaGroups(const util::geo::DBox bbox) const;
98 121
 
99 122
   const Station* getStation(size_t id) const;
100 123
   const Group* getGroup(size_t id) const;
101 124
   const Suggestion* getSuggestion(size_t id) const;
125
+  const MetaGroup* getMetaGroup(size_t id) const;
102 126
 
103 127
   std::vector<Cluster> getHeatGridOk(const util::geo::DBox bbox,
104 128
                                      size_t z) const;
@@ -117,14 +141,17 @@ class StatIdx {
117 141
                   const util::geo::DLine& latLngs, size_t origGroup,
118 142
                   size_t group, const OsmAttrs& attrs);
119 143
   void addGroup(size_t id, const OsmAttrs& attrs);
144
+  void addGroupToMeta(size_t gid, size_t metaId);
120 145
 
121 146
   void initIndex();
122 147
   void initGroups();
148
+  void initMetaGroups();
123 149
   void initSuggestions();
124 150
 
125 151
   int nameAttrRel(const std::string& attr) const;
126 152
 
127
-  util::geo::DLine getGroupArrow(size_t stat, const util::geo::DPoint& p) const;
153
+  util::geo::DLine getGroupArrow(const util::geo::DPoint& a,
154
+                                 const util::geo::DPoint& p) const;
128 155
 
129 156
   util::geo::Polygon<double> hull(const util::geo::MultiPoint<double>& imp,
130 157
                                   double rad) const;
@@ -132,10 +159,12 @@ class StatIdx {
132 159
   std::vector<Station> _stations;
133 160
   std::vector<Group> _groups;
134 161
   std::vector<Suggestion> _suggestions;
162
+  std::map<size_t, MetaGroup> _metaGroups;
135 163
   util::geo::DBox _bbox;
136 164
 
137 165
   util::geo::Grid<size_t, util::geo::Polygon, double> _sgrid;
138 166
   util::geo::Grid<size_t, util::geo::Polygon, double> _ggrid;
167
+  util::geo::Grid<size_t, util::geo::Polygon, double> _mgrid;
139 168
   util::geo::Grid<size_t, util::geo::Line, double> _suggrid;
140 169
 
141 170
   std::vector<util::geo::Grid<Cluster, util::geo::Point, double>> _heatGridsOk;

+ 155 - 2
src/osmfixer/server/StatServer.cpp

@@ -45,6 +45,8 @@ util::http::Answer StatServer::handle(const util::http::Req& req,
45 45
       a = handleStatReq(params);
46 46
     } else if (cmd == "/group") {
47 47
       a = handleGroupReq(params);
48
+    } else if (cmd == "/mgroup") {
49
+      a = handleMGroupReq(params);
48 50
     } else if (cmd == "/search") {
49 51
       a = handleSearch(params);
50 52
     } else {
@@ -228,6 +230,7 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
228 230
 
229 231
       const auto& suggs = idx.getSuggestions(bbox);
230 232
       for (const auto& sugg : suggs) {
233
+        if (sugg->arrow.size() < 2) continue;
231 234
         json << sep;
232 235
         sep = ',';
233 236
         printSugg(sugg, did, &json);
@@ -235,6 +238,21 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
235 238
     }
236 239
   }
237 240
 
241
+  json << "], \"mgroups\":[";
242
+  sep = ' ';
243
+  if (z > 13) {
244
+    for (size_t did = 0; did < _idxs.size(); did++) {
245
+      const auto& idx = _idxs[did].second;
246
+
247
+      const auto& mgroups = idx.getMetaGroups(bbox);
248
+      for (const auto& mgroup : mgroups) {
249
+        json << sep;
250
+        sep = ',';
251
+        printMetaGroup(mgroup, did, z < 15, &json);
252
+      }
253
+    }
254
+  }
255
+
238 256
   json << "]}";
239 257
 
240 258
   if (cb.size()) json << ")";
@@ -284,8 +302,13 @@ void StatServer::printSugg(const Suggestion* sugg, size_t did,
284 302
     projArrow.push_back(util::geo::webMercToLatLng<double>(p.getX(), p.getY()));
285 303
   }
286 304
 
287
-  (*out) << "{\"i\":" << sugg->station + range.sidStart
288
-         << ",\"t\":" << sugg->type << ",\"a\":[";
305
+  if (sugg->station >= 0) {
306
+    (*out) << "{\"i\":" << sugg->station + range.sidStart
307
+           << ",\"t\":" << sugg->type << ",\"a\":[";
308
+  } else {
309
+    (*out) << "{\"i\":" << -(-sugg->station + (int64_t)range.gidStart)
310
+           << ",\"t\":" << sugg->type << ",\"a\":[";
311
+  }
289 312
   char sep = ' ';
290 313
   for (const auto& p : projArrow) {
291 314
     (*out) << sep << "[" << p.getY() << "," << p.getX() << "]";
@@ -296,6 +319,28 @@ void StatServer::printSugg(const Suggestion* sugg, size_t did,
296 319
 }
297 320
 
298 321
 // _____________________________________________________________________________
322
+void StatServer::printMetaGroup(const MetaGroup* mg, size_t did, bool simple,
323
+                            std::ostream* out) const {
324
+  (*out) << "{\"i\":" << mg->osmid  << ",\"g\":[";
325
+  char sep = ' ';
326
+  if (simple) {
327
+    (*out) << sep << "[" << mg->centroid.getY() << ","
328
+           << mg->centroid.getX() << "]";
329
+  } else {
330
+    for (const auto& p : mg->llPoly.getOuter()) {
331
+      (*out) << sep << "[" << p.getY() << "," << p.getX() << "]";
332
+      sep = ',';
333
+    }
334
+  }
335
+  (*out) << "]";
336
+  bool sugg = mg->osmid == 1;// || mg->suggestions.size() > 0;
337
+
338
+  if (sugg) (*out) << ",\"s\":1";
339
+
340
+  (*out) << "}";
341
+}
342
+
343
+// _____________________________________________________________________________
299 344
 void StatServer::printGroup(const Group* group, size_t did, bool simple,
300 345
                             bool aggr, std::ostream* out) const {
301 346
   const auto& range = _idxs[did].first;
@@ -374,6 +419,15 @@ size_t StatServer::getDidBySuggid(size_t suggid) const {
374 419
 }
375 420
 
376 421
 // _____________________________________________________________________________
422
+size_t StatServer::getDidByMGid(size_t mgid) const {
423
+  for (size_t i = 0; i < _idxs.size(); i++) {
424
+    if (_idxs[i].second.getMetaGroup(mgid)) return i;
425
+  }
426
+
427
+  return 0;
428
+}
429
+
430
+// _____________________________________________________________________________
377 431
 size_t StatServer::getDidByGid(size_t gid) const {
378 432
   for (size_t i = 0; i < _idxs.size(); i++) {
379 433
     auto range = _idxs[i].first;
@@ -446,6 +500,81 @@ util::http::Answer StatServer::handleSearch(const Params& pars) const {
446 500
 }
447 501
 
448 502
 // _____________________________________________________________________________
503
+util::http::Answer StatServer::handleMGroupReq(const Params& pars) const {
504
+  if (pars.count("id") == 0 || pars.find("id")->second.empty())
505
+    throw std::invalid_argument("No ID specified.");
506
+  std::string cb;
507
+  if (pars.count("cb")) cb = pars.find("cb")->second.c_str();
508
+
509
+  size_t mgid = atol(pars.find("id")->second.c_str());
510
+
511
+  size_t did = getDidByMGid(mgid);
512
+
513
+  const auto& range = _idxs[did].first;
514
+  const auto& idx = _idxs[did].second;
515
+
516
+  const auto& mgroup = idx.getMetaGroup(mgid);
517
+
518
+  if (!mgroup) return util::http::Answer("404 Not Found", "Group not found.");
519
+
520
+  std::stringstream json;
521
+
522
+  json << std::setprecision(10);
523
+
524
+  auto centroid = mgroup->centroid;
525
+
526
+  if (cb.size()) json << cb << "(";
527
+  json << "{\"id\":" << mgid << ","
528
+       << "\"lat\":" << centroid.getY() << ","
529
+       << "\"lon\":" << centroid.getX() << ","
530
+       << "\"osmid\":" << mgid
531
+       << ",\"groups\":[";
532
+
533
+  char sep = ' ';
534
+  for (const auto& gid : mgroup->groups) {
535
+    json << sep;
536
+    sep = ',';
537
+    const auto& group = idx.getGroup(gid);
538
+    json << "{\"id\":" << gid + range.gidStart << ","
539
+         << "\"osmid\":" << group->osmid << ","
540
+        << "\"attrs\":{";
541
+
542
+    char sep = ' ';
543
+
544
+    for (const auto& par : group->attrs) {
545
+      json << sep;
546
+      sep = ',';
547
+      json << "\"" << util::jsonStringEscape(par.first) << "\":[";
548
+
549
+      char sep2 = ' ';
550
+      for (const auto& val : par.second) {
551
+        json << sep2;
552
+        sep2 = ',';
553
+        json << "\"" << util::jsonStringEscape(val) << "\"";
554
+      }
555
+      json << "]";
556
+    }
557
+
558
+    json << "}";
559
+    if (group->suggestions.size() > 0) json << ",\"s\":1";
560
+    if (group->attrErrs.size()) json << ",\"e\":" << group->attrErrs.size();
561
+
562
+    json << "}";
563
+  }
564
+
565
+  json << "]";
566
+
567
+  json << "}";
568
+
569
+  if (cb.size()) json << ")";
570
+
571
+  auto answ = util::http::Answer("200 OK", json.str(), true);
572
+  answ.params["Content-Type"] = "application/javascript; charset=utf-8";
573
+
574
+  return answ;
575
+}
576
+
577
+// _____________________________________________________________________________
449 578
 util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
450 579
   if (pars.count("id") == 0 || pars.find("id")->second.empty())
451 580
     throw std::invalid_argument("No ID specified.");
@@ -468,9 +597,13 @@ util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
468 597
 
469 598
   json << std::setprecision(10);
470 599
 
600
+  auto centroid = group->centroid;
601
+
471 602
   if (cb.size()) json << cb << "(";
472 603
   json << "{\"id\":" << gid << ","
473 604
        << "\"osmid\":" << group->osmid << ","
605
+       << "\"lat\":" << centroid.getY() << ","
606
+       << "\"lon\":" << centroid.getX() << ","
474 607
        << "\"attrs\":{";
475 608
 
476 609
   char sep = ' ';
@@ -573,6 +706,12 @@ util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
573 706
     } else if (sugg->type == 8) {
574 707
       json << seper << "{\"type\":8,\"attr\":\"" << sugg->attrErrName << "\""
575 708
            << "}";
709
+    } else if (sugg->type == 9) {
710
+      json << seper << "{\"type\":9,\"target_gid\":" << sugg->target_gid << ",\"target_osm_rel_id\":" << sugg->target_osm_rel_id
711
+           << "}";
712
+    } else if (sugg->type == 10) {
713
+      json << seper << "{\"type\":10,\"target_gid\":" << sugg->target_gid << ",\"target_osm_rel_id\":" << sugg->target_osm_rel_id
714
+           << "}";
576 715
     } else {
577 716
       json << seper << "{\"type\":" << sugg->type << "}";
578 717
     }
@@ -713,6 +852,20 @@ util::http::Answer StatServer::handleStatReq(const Params& pars) const {
713 852
     } else if (sugg->type == 8) {
714 853
       json << seper << "{\"type\":8,\"attr\":\"" << sugg->attrErrName << "\""
715 854
            << "}";
855
+    } else if (sugg->type == 9) {
856
+      json << seper
857
+           << "{\"type\":9,\"orig_gid\":" << stat->origGroup + range.gidStart
858
+           << ",\"orig_osm_rel_id\":" << idx.getGroup(stat->origGroup)->osmid
859
+           << ",\"target_gid\":" << stat->group + range.gidStart
860
+           << ",\"target_osm_rel_id\":" << idx.getGroup(stat->group)->osmid
861
+           << "}";
862
+    } else if (sugg->type == 10) {
863
+      json << seper
864
+           << "{\"type\":10,\"orig_gid\":" << stat->origGroup + range.gidStart
865
+           << ",\"orig_osm_rel_id\":" << idx.getGroup(stat->origGroup)->osmid
866
+           << ",\"target_gid\":" << stat->group + range.gidStart
867
+           << ",\"target_osm_rel_id\":" << idx.getGroup(stat->group)->osmid
868
+           << "}";
716 869
     } else {
717 870
       json << seper << "{\"type\":" << sugg->type << "}";
718 871
     }

+ 3 - 0
src/osmfixer/server/StatServer.h

@@ -31,15 +31,18 @@ class StatServer : public util::http::Handler {
31 31
   util::http::Answer handleHeatMapReq(const Params& pars) const;
32 32
   util::http::Answer handleStatReq(const Params& pars) const;
33 33
   util::http::Answer handleGroupReq(const Params& pars) const;
34
+  util::http::Answer handleMGroupReq(const Params& pars) const;
34 35
   util::http::Answer handleSearch(const Params& pars) const;
35 36
 
36 37
   void printStation(const Station* stat, size_t did, bool simple, std::ostream* out) const;
37 38
   void printGroup(const Group* stat, size_t did, bool simple, bool aggr, std::ostream* out) const;
38 39
   void printSugg(const Suggestion* stat, size_t did, std::ostream* out) const;
40
+  void printMetaGroup(const MetaGroup* stat, size_t did, bool simple, std::ostream* out) const;
39 41
 
40 42
   size_t getDidBySid(size_t sid) const;
41 43
   size_t getDidBySuggid(size_t suggid) const;
42 44
   size_t getDidByGid(size_t gid) const;
45
+  size_t getDidByMGid(size_t mgid) const;
43 46
 
44 47
   const std::vector<std::pair<IdRange, osmfixer::StatIdx>>& _idxs;
45 48
   const SearchIdx& _searchIdx;

Plik diff jest za duży
+ 1 - 596
web/index.html


+ 110 - 28
web/script.js

@@ -5,13 +5,16 @@ var stCols = ['#78f378', '#0000c3', 'red'];
5 5
 
6 6
 var osmUrl = "//www.openstreetmap.org/";
7 7
 
8
-var grIdx, stIdx, selectedRes, prevSearch, delayTimer;
8
+var grIdx, mGrIdx, stIdx, selectedRes, prevSearch, delayTimer;
9 9
 
10 10
 var reqs = {};
11 11
 
12 12
 var openedGr = -1;
13
+var openedMGr = -1;
13 14
 var openedSt = -1;
14 15
 
16
+
17
+// station sugg messages
15 18
 var sgMvOrNew = "Move into a <span class='grouplink' onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})'>new relation</span> <tt>public_transport=stop_area</tt>.";
16 19
 var sgMvOrEx = "Move into relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>.";
17 20
 var sgMvRelNew = "Move from relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a> into a <span class='grouplink' onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})'>new relation</span> <tt>public_transport=stop_area</tt>.";
@@ -20,8 +23,19 @@ var sgMvOutRel = "Move out of relation <a onmouseover='grHl(${oid})' onmouseout=
20 23
 var sgFixAttr = "Fix attribute <tt>${attr}</tt>.";
21 24
 var sgAddName = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute.";
22 25
 var sgAttrTr = "Attribute <tt>${attr}</tt> seems to be a track number. Use <tt>ref</tt> for this and set <tt>${attr}</tt> to the station name.";
26
+var sgMergeRel = "Merge parent relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a> with relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>, or move them into a new relation <tt>public_transport=stop_area_group</tt>";
27
+var sgMergeMetaGr = "Move parent relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a> into meta <tt>public_transport=stop_area_group</tt> relation <a onmouseover='mGrHl(${tid})' onmouseout='mGrUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>";
28
+
29
+var suggsMsg = [sgMvOrNew, sgMvOrEx, sgMvRelNew, sgMvRelRel, sgMvOutRel, sgFixAttr, sgAddName, sgAttrTr, sgMergeRel, sgMergeMetaGr];
23 30
 
24
-var suggsMsg = [sgMvOrNew, sgMvOrEx, sgMvRelNew, sgMvRelRel, sgMvOutRel, sgFixAttr, sgAddName, sgAttrTr];
31
+// group sugg messages
32
+var sgGrFixAttr = "Fix attribute <tt>${attr}</tt>.";
33
+var sgGrAddName = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute.";
34
+var sgGrAttrTr = "Attribute <tt>${attr}</tt> seems to be a track number. Use <tt>ref</tt> for this and set <tt>${attr}</tt> to the station name.";
35
+var sgGrMergeRel = "Merge with relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>, or move both into a new relation <tt>public_transport=stop_area_group</tt>";
36
+var sgGrMergeMeta = "Move relation into <tt>public_transport=stop_area_group</tt> relation <a onmouseover='mGrHl(${tid})' onmouseout='mGrUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>";
37
+
38
+var groupSuggMsg = [sgGrFixAttr, sgGrAddName, sgGrAttrTr, sgGrMergeRel, sgGrMergeMeta];
25 39
 
26 40
 function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)}
27 41
 function $$(t){return document.createElement(t) }
@@ -77,7 +91,7 @@ function marker(stat, z) {
77 91
     }
78 92
 }
79 93
 
80
-function poly(group, z) {
94
+function poly(group, z, dotted) {
81 95
     var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385';
82 96
     var style = {
83 97
         color: col,
@@ -86,6 +100,14 @@ function poly(group, z) {
86 100
         fillOpacity: 0.2,
87 101
         id: group.i
88 102
     };
103
+    if (dotted) {
104
+        var col = group.e ? 'red' : group.s ? '#0000c3' : '#a3b750';
105
+        style.dashArray = '8, 8';
106
+        style.fillOpacity = 0.15;
107
+        style.color = col;
108
+        style.fillColor = col;
109
+        style.weight = 2;
110
+    }
89 111
     if (z < 16) {
90 112
         style.weight = 11;
91 113
         style.opacity = 0.5;
@@ -197,7 +219,7 @@ function rndrSt(stat) {
197 219
 
198 220
 function openSt(id) {req("s", "/stat?id=" + id, function(c) {rndrSt(c)});}
199 221
 
200
-function rndrGr(grp, ll) {
222
+function rndrGr(grp) {
201 223
     openedGr = grp.id;
202 224
     var attrrows = {};
203 225
     grHl(grp.id);
@@ -269,6 +291,28 @@ function rndrGr(grp, ll) {
269 291
         row.childNodes[1].appendChild(info);
270 292
     }
271 293
 
294
+    var suggList = $$('ul');
295
+
296
+    if (grp.su.length) {
297
+        var a = $$('span');
298
+        addCl(a, "sugtit");
299
+        a.innerHTML = "Suggestions";
300
+        suggD.appendChild(a);
301
+    }
302
+
303
+    suggD.appendChild(suggList);
304
+    var mergeGroup = false;
305
+
306
+    for (var i = 0; i < grp.su.length; i++) {
307
+        var sg = grp.su[i];
308
+        var sgDiv = $$('li');
309
+
310
+        if (sg.type == 9 || sg.type == 10) mergeGroup = true;
311
+
312
+        sgDiv.innerHTML = tmpl(groupSuggMsg[sg.type - 6], {"attr" : sg.attr, "tid" : sg.target_gid, "ooid" : sg.orig_osm_rel_id, "toid" : sg.target_osm_rel_id, "oid" : sg.orig_gid});
313
+        suggList.appendChild(sgDiv);
314
+    }
315
+
272 316
     con.appendChild(newMembers);
273 317
     if (grp.osmid != 1) con.appendChild(oldMembers);
274 318
 
@@ -286,43 +330,70 @@ function rndrGr(grp, ll) {
286 330
         if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row);
287 331
         else {
288 332
             oldMembers.appendChild(row);
289
-            if (stat.group != grp.id) addCl(row, "del-stat");
333
+            if (stat.group != grp.id && !mergeGroup) addCl(row, "del-stat");
290 334
         }
291 335
     }
292 336
 
293
-    var suggList = $$('ul');
337
+    con.appendChild(suggD);
294 338
 
295
-    if (grp.su.length) {
296
-        var a = $$('span');
297
-        addCl(a, "sugtit");
298
-        a.innerHTML = "Suggestions";
299
-        suggD.appendChild(a);
300
-    }
339
+    L.popup({opacity: 0.8})
340
+        .setLatLng(grp)
341
+        .setContent(con)
342
+        .openOn(map)
343
+        .on('remove', function() {if (openedGr == grp.id) {openedGr = -1; grUnHl(grp.id)}});
344
+}
301 345
 
302
-    suggD.appendChild(suggList);
346
+function rndrMGr(grp) {
347
+    openedMGr = grp.id;
348
+    mGrHl(grp.id);
303 349
 
304
-    for (var i = 0; i < grp.su.length; i++) {
305
-        var sugg = grp.su[i];
306
-        var suggDiv = $$('li');
350
+    var con = $$('div');
351
+    con.setAttribute("id", "nav");
307 352
 
308
-        if (sugg.type == 6) suggDiv.innerHTML = "Fix attribute <tt>" + sugg.attr + "</tt>.";
309
-        else if (sugg.type == 7) suggDiv.innerHTML = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute.";
310
-        else if (sugg.type == 8) suggDiv.innerHTML = "Attribute <tt>" + sugg.attr + "</tt> seems to be a track number. Use <tt>ref</tt> for this and set <tt>" + sugg.attr + "</tt> to the station name.";
353
+    var oldMembers = $$('div');
354
+    oldMembers.setAttribute("id", "group-stations-old")
355
+    oldMembers.innerHTML = "<span class='oldmemberstit'>Members</span>";
311 356
 
312
-        suggList.appendChild(suggDiv);
313
-    }
357
+    con.innerHTML = "OSM relation <a target='_blank' href='https://www.openstreetmap.org/relation/" + grp.osmid + "'>" + grp.osmid + " </a> (<tt>public_transport=stop_area_group</tt>)";
358
+    con.innerHTML += "<a class='ebut' target='_blank' href='https://www.openstreetmap.org/edit?relation=" + grp.osmid +"'>&#9998;</a>";
314 359
 
315
-    con.appendChild(suggD);
360
+    con.appendChild(oldMembers);
361
+
362
+    for (var key in grp.groups) {
363
+        var gr = grp.groups[key];
364
+        var row = $$('div');
365
+
366
+        row.innerHTML = "Relation <a onmouseover='grHl( " + gr.id + ")' onmouseout='grUnHl( " + gr.id + ")' target='_blank' href='" + osmUrl + "relations/" + gr.osmid + "'>" + gr.osmid + "</a>";
367
+        if (gr.attrs.name) row.innerHTML += " (<b>\"" + gr.attrs.name[0] + "\"</b>)";
368
+        row.style.backgroundColor = gr.e ? '#f58d8d' : gr.s ? '#b6b6e4' : '#c0f7c0';
369
+
370
+        oldMembers.appendChild(row);
371
+    }
316 372
 
317 373
     L.popup({opacity: 0.8})
318
-        .setLatLng(ll)
374
+        .setLatLng(grp)
319 375
         .setContent(con)
320 376
         .openOn(map)
321
-        .on('remove', function() {if (openedGr == grp.id) {openedGr = -1; grUnHl(grp.id)}});
377
+        .on('remove', function() {if (openedMGr == grp.id) {openedMGr = -1; mGrUnHl(grp.id)}});
378
+}
379
+
380
+function openGr(id) {
381
+    req("g", "/group?id=" + id, function(c) {rndrGr(c)});
382
+}
383
+
384
+function openMGr(id) {
385
+    req("g", "/mgroup?id=" + id, function(c) {rndrMGr(c)});
386
+}
387
+
388
+function mGrHl(id) {
389
+    !mGrIdx[id] || mGrIdx[id].setStyle({'weight': 6, 'color': "#eecc00"});
322 390
 }
323 391
 
324
-function openGr(id, ll) {
325
-    req("g", "/group?id=" + id, function(c) {rndrGr(c, ll)});
392
+function mGrUnHl(id) {
393
+    !mGrIdx[id] || mGrIdx[id].setStyle({
394
+        'weight': 2,
395
+        'color': mGrIdx[id].options["fillColor"]
396
+    });
326 397
 }
327 398
 
328 399
 function grHl(id) {
@@ -448,6 +519,7 @@ function render() {
448 519
             function(re) {
449 520
                 l.clearLayers();
450 521
                 grIdx = {};
522
+                mGrIdx = {};
451 523
                 stIdx = {};
452 524
 
453 525
                 var stats = [];
@@ -457,7 +529,12 @@ function render() {
457 529
 
458 530
                 var groups = [];
459 531
                 for (var i = 0; i < re.groups.length; i++) {
460
-                    grIdx[re.groups[i].i] = groups[groups.push(poly(re.groups[i], map.getZoom())) - 1];;
532
+                    grIdx[re.groups[i].i] = groups[groups.push(poly(re.groups[i], map.getZoom())) - 1];
533
+                }
534
+
535
+                var mgroups = [];
536
+                for (var i = 0; i < re.mgroups.length; i++) {
537
+                    mGrIdx[re.mgroups[i].i] = mgroups[mgroups.push(poly(re.mgroups[i], map.getZoom(), 1)) - 1];
461 538
                 }
462 539
 
463 540
                 var suggs = [];
@@ -466,6 +543,9 @@ function render() {
466 543
                 }
467 544
 
468 545
                 if (map.getZoom() > 13) {
546
+                    l.addLayer(L.featureGroup(mgroups).on('click', function(a) {
547
+                        openMGr(a.layer.options.id, a.layer.getBounds().getCenter());
548
+                    }));
469 549
                     l.addLayer(L.featureGroup(groups).on('click', function(a) {
470 550
                         openGr(a.layer.options.id, a.layer.getBounds().getCenter());
471 551
                     }));
@@ -477,11 +557,13 @@ function render() {
477 557
 
478 558
                 if (map.getZoom() > 15) {
479 559
                     l.addLayer(L.featureGroup(suggs).on('click', function(a) {
480
-                        openSt(a.layer.options.id);
560
+                        if (a.layer.options.id < 0) openGr(-a.layer.options.id);
561
+                        else openSt(a.layer.options.id);
481 562
                     }));
482 563
                 }
483 564
 
484 565
                 grHl(openedGr);
566
+                mGrHl(openedMGr);
485 567
                 stHl(openedSt);
486 568
             }
487 569
         )