Patrick Brosi 4 years ago
parent
commit
3126ce4b12

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

@@ -12,6 +12,7 @@
12
 #include "util/log/Log.h"
12
 #include "util/log/Log.h"
13
 
13
 
14
 using osmfixer::Group;
14
 using osmfixer::Group;
15
+using osmfixer::MetaGroup;
15
 using osmfixer::OsmAttrs;
16
 using osmfixer::OsmAttrs;
16
 using osmfixer::StatIdx;
17
 using osmfixer::StatIdx;
17
 using osmfixer::Station;
18
 using osmfixer::Station;
@@ -170,8 +171,26 @@ void StatIdx::readFromFile(const std::string& path) {
170
   }
171
   }
171
   LOG(INFO) << "Done.";
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
   LOG(INFO) << "Building indices...";
191
   LOG(INFO) << "Building indices...";
174
   initGroups();
192
   initGroups();
193
+  initMetaGroups();
175
   initSuggestions();
194
   initSuggestions();
176
 
195
 
177
   initIndex();
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
 void StatIdx::addStation(int64_t osmid, const util::geo::DLine& geom,
208
 void StatIdx::addStation(int64_t osmid, const util::geo::DLine& geom,
183
                          const util::geo::DLine& latLngs, size_t origGroup,
209
                          const util::geo::DLine& latLngs, size_t origGroup,
184
                          size_t group, const OsmAttrs& attrs) {
210
                          size_t group, const OsmAttrs& attrs) {
@@ -203,11 +229,33 @@ void StatIdx::addGroup(size_t osmid, const OsmAttrs& attrs) {
203
   Group g;
229
   Group g;
204
   g.id = _groups.size();
230
   g.id = _groups.size();
205
   g.osmid = osmid;
231
   g.osmid = osmid;
232
+  g.metaGroupId = 0;
233
+  g.mergeId = 0;
206
   g.attrs = attrs;
234
   g.attrs = attrs;
207
   _groups.emplace_back(g);
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
 void StatIdx::initGroups() {
259
 void StatIdx::initGroups() {
212
   for (size_t i = 0; i < _stations.size(); i++) {
260
   for (size_t i = 0; i < _stations.size(); i++) {
213
     // this should be ensured by the input file
261
     // this should be ensured by the input file
@@ -220,6 +268,16 @@ void StatIdx::initGroups() {
220
       _groups[_stations[i].origGroup].stations.push_back(i);
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
     assert(_stations[i].group < _groups.size());
281
     assert(_stations[i].group < _groups.size());
224
     if (_stations[i].group != _stations[i].origGroup &&
282
     if (_stations[i].group != _stations[i].origGroup &&
225
         _groups[_stations[i].group].osmid == 1) {
283
         _groups[_stations[i].group].osmid == 1) {
@@ -259,6 +317,14 @@ void StatIdx::initGroups() {
259
               byAttrScore);
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
   // build hull polygon
328
   // build hull polygon
263
   for (size_t i = 0; i < _groups.size(); i++) {
329
   for (size_t i = 0; i < _groups.size(); i++) {
264
     util::geo::MultiPoint<double> mp;
330
     util::geo::MultiPoint<double> mp;
@@ -307,6 +373,8 @@ void StatIdx::initIndex() {
307
                                                                _bbox, false);
373
                                                                _bbox, false);
308
   _ggrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
374
   _ggrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
309
                                                                _bbox, false);
375
                                                                _bbox, false);
376
+  _mgrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
377
+                                                               _bbox, false);
310
   _suggrid = util::geo::Grid<size_t, util::geo::Line, double>(gSize, gSize,
378
   _suggrid = util::geo::Grid<size_t, util::geo::Line, double>(gSize, gSize,
311
                                                               _bbox, false);
379
                                                               _bbox, false);
312
 
380
 
@@ -315,6 +383,10 @@ void StatIdx::initIndex() {
315
     if (_groups[i].polyStations.size() == 1 && _groups[i].osmid < 2) continue;
383
     if (_groups[i].polyStations.size() == 1 && _groups[i].osmid < 2) continue;
316
     _ggrid.add(_groups[i].poly, i);
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
   for (size_t i = 0; i < _suggestions.size(); i++)
390
   for (size_t i = 0; i < _suggestions.size(); i++)
319
     _suggrid.add(_suggestions[i].arrow, i);
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
 std::vector<const Suggestion*> StatIdx::getSuggestions(
513
 std::vector<const Suggestion*> StatIdx::getSuggestions(
421
     const util::geo::DBox bbox) const {
514
     const util::geo::DBox bbox) const {
422
   std::vector<const Suggestion*> ret;
515
   std::vector<const Suggestion*> ret;
@@ -582,6 +675,37 @@ void StatIdx::initSuggestions() {
582
     Suggestion sug;
675
     Suggestion sug;
583
     sug.station = i;
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
     std::set<std::pair<std::string, uint16_t>> suggested;
709
     std::set<std::pair<std::string, uint16_t>> suggested;
586
     for (auto attrErr : group.attrErrs) {
710
     for (auto attrErr : group.attrErrs) {
587
       if (!attrErr.fromRel) continue;
711
       if (!attrErr.fromRel) continue;
@@ -620,8 +744,10 @@ void StatIdx::initSuggestions() {
620
   // station suggestions
744
   // station suggestions
621
   for (size_t i = 0; i < _stations.size(); i++) {
745
   for (size_t i = 0; i < _stations.size(); i++) {
622
     auto& stat = _stations[i];
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
       Suggestion sug;
751
       Suggestion sug;
626
       sug.station = i;
752
       sug.station = i;
627
       sug.type = 7;
753
       sug.type = 7;
@@ -672,8 +798,6 @@ void StatIdx::initSuggestions() {
672
     if (stat.group != stat.origGroup || getGroup(stat.group)->osmid == 1) {
798
     if (stat.group != stat.origGroup || getGroup(stat.group)->osmid == 1) {
673
       Suggestion sug;
799
       Suggestion sug;
674
       sug.station = i;
800
       sug.station = i;
675
-      auto centroid = util::geo::centroid(stat.pos);
676
-      sug.arrow = util::geo::DLine{centroid, centroid};
677
 
801
 
678
       if (getGroup(stat.origGroup)->osmid < 2) {
802
       if (getGroup(stat.origGroup)->osmid < 2) {
679
         if (getGroup(stat.group)->osmid == 1 &&
803
         if (getGroup(stat.group)->osmid == 1 &&
@@ -692,7 +816,7 @@ void StatIdx::initSuggestions() {
692
           sug.target_osm_rel_id = getGroup(stat.group)->osmid;
816
           sug.target_osm_rel_id = getGroup(stat.group)->osmid;
693
 
817
 
694
           auto b = util::geo::centroid(getGroup(stat.group)->poly);
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
           _suggestions.push_back(sug);
821
           _suggestions.push_back(sug);
698
           stat.suggestions.push_back(_suggestions.size() - 1);
822
           stat.suggestions.push_back(_suggestions.size() - 1);
@@ -707,20 +831,37 @@ void StatIdx::initSuggestions() {
707
           sug.target_gid = stat.group;
831
           sug.target_gid = stat.group;
708
 
832
 
709
           auto b = util::geo::centroid(getGroup(stat.group)->poly);
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
           _suggestions.push_back(sug);
836
           _suggestions.push_back(sug);
713
           stat.suggestions.push_back(_suggestions.size() - 1);
837
           stat.suggestions.push_back(_suggestions.size() - 1);
714
         } else if (getGroup(stat.group)->osmid > 1) {
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
           sug.orig_gid = stat.origGroup;
863
           sug.orig_gid = stat.origGroup;
718
           sug.orig_osm_rel_id = getGroup(stat.origGroup)->osmid;
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
           _suggestions.push_back(sug);
866
           _suggestions.push_back(sug);
726
           stat.suggestions.push_back(_suggestions.size() - 1);
867
           stat.suggestions.push_back(_suggestions.size() - 1);
@@ -738,7 +879,7 @@ void StatIdx::initSuggestions() {
738
           b.setX(b.getX() + 50);
879
           b.setX(b.getX() + 50);
739
           b.setY(b.getY() + 50);
880
           b.setY(b.getY() + 50);
740
 
881
 
741
-          sug.arrow = getGroupArrow(i, b);
882
+          sug.arrow = getGroupArrow(centroid, b);
742
 
883
 
743
           _suggestions.push_back(sug);
884
           _suggestions.push_back(sug);
744
           stat.suggestions.push_back(_suggestions.size() - 1);
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
                                         const util::geo::DPoint& b) const {
894
                                         const util::geo::DPoint& b) const {
754
-  auto a = util::geo::centroid(getStation(stat)->pos);
755
-
756
   auto pl = util::geo::PolyLine<double>(a, b);
895
   auto pl = util::geo::PolyLine<double>(a, b);
757
   auto bb = pl.getPointAtDist(fmax(pl.getLength() / 2, pl.getLength() - 5)).p;
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
 int StatIdx::nameAttrRel(const std::string& attr) const {
952
 int StatIdx::nameAttrRel(const std::string& attr) const {
808
   if (attr == "uic_name") return 5;
953
   if (attr == "uic_name") return 5;
809
   if (attr == "ref_name") return 5;
954
   if (attr == "ref_name") return 5;

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

@@ -16,7 +16,7 @@ namespace osmfixer {
16
 typedef std::map<std::string, std::vector<std::string>> OsmAttrs;
16
 typedef std::map<std::string, std::vector<std::string>> OsmAttrs;
17
 
17
 
18
 inline bool byAttrScore(const std::pair<int, std::string>& lh,
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
   return lh.first > rh.first;
20
   return lh.first > rh.first;
21
 }
21
 }
22
 
22
 
@@ -34,13 +34,21 @@ struct AttrErr {
34
 
34
 
35
 struct Suggestion {
35
 struct Suggestion {
36
   uint16_t type;
36
   uint16_t type;
37
-  size_t station;
37
+  int64_t station;
38
   // if negative, it is a way!
38
   // if negative, it is a way!
39
   int64_t target_gid, orig_gid, target_osm_rel_id, orig_osm_rel_id;
39
   int64_t target_gid, orig_gid, target_osm_rel_id, orig_osm_rel_id;
40
   std::string attrErrName;
40
   std::string attrErrName;
41
   util::geo::DLine arrow;
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
 struct Station {
52
 struct Station {
45
   size_t id;
53
   size_t id;
46
   // if negative, it is a way!
54
   // if negative, it is a way!
@@ -60,9 +68,23 @@ inline bool byErr(const Station* lh, const Station* rh) {
60
   return a < b;
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
 struct Group {
83
 struct Group {
64
   size_t id;
84
   size_t id;
65
   int64_t osmid;
85
   int64_t osmid;
86
+  size_t mergeId;
87
+  size_t metaGroupId;
66
   util::geo::DPolygon poly;
88
   util::geo::DPolygon poly;
67
   util::geo::DPolygon llPoly;
89
   util::geo::DPolygon llPoly;
68
   util::geo::DPoint centroid;
90
   util::geo::DPoint centroid;
@@ -95,10 +117,12 @@ class StatIdx {
95
   std::vector<const Group*> getGroups(const util::geo::DBox bbox) const;
117
   std::vector<const Group*> getGroups(const util::geo::DBox bbox) const;
96
   std::vector<const Suggestion*> getSuggestions(
118
   std::vector<const Suggestion*> getSuggestions(
97
       const util::geo::DBox bbox) const;
119
       const util::geo::DBox bbox) const;
120
+  std::vector<const MetaGroup*> getMetaGroups(const util::geo::DBox bbox) const;
98
 
121
 
99
   const Station* getStation(size_t id) const;
122
   const Station* getStation(size_t id) const;
100
   const Group* getGroup(size_t id) const;
123
   const Group* getGroup(size_t id) const;
101
   const Suggestion* getSuggestion(size_t id) const;
124
   const Suggestion* getSuggestion(size_t id) const;
125
+  const MetaGroup* getMetaGroup(size_t id) const;
102
 
126
 
103
   std::vector<Cluster> getHeatGridOk(const util::geo::DBox bbox,
127
   std::vector<Cluster> getHeatGridOk(const util::geo::DBox bbox,
104
                                      size_t z) const;
128
                                      size_t z) const;
@@ -117,14 +141,17 @@ class StatIdx {
117
                   const util::geo::DLine& latLngs, size_t origGroup,
141
                   const util::geo::DLine& latLngs, size_t origGroup,
118
                   size_t group, const OsmAttrs& attrs);
142
                   size_t group, const OsmAttrs& attrs);
119
   void addGroup(size_t id, const OsmAttrs& attrs);
143
   void addGroup(size_t id, const OsmAttrs& attrs);
144
+  void addGroupToMeta(size_t gid, size_t metaId);
120
 
145
 
121
   void initIndex();
146
   void initIndex();
122
   void initGroups();
147
   void initGroups();
148
+  void initMetaGroups();
123
   void initSuggestions();
149
   void initSuggestions();
124
 
150
 
125
   int nameAttrRel(const std::string& attr) const;
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
   util::geo::Polygon<double> hull(const util::geo::MultiPoint<double>& imp,
156
   util::geo::Polygon<double> hull(const util::geo::MultiPoint<double>& imp,
130
                                   double rad) const;
157
                                   double rad) const;
@@ -132,10 +159,12 @@ class StatIdx {
132
   std::vector<Station> _stations;
159
   std::vector<Station> _stations;
133
   std::vector<Group> _groups;
160
   std::vector<Group> _groups;
134
   std::vector<Suggestion> _suggestions;
161
   std::vector<Suggestion> _suggestions;
162
+  std::map<size_t, MetaGroup> _metaGroups;
135
   util::geo::DBox _bbox;
163
   util::geo::DBox _bbox;
136
 
164
 
137
   util::geo::Grid<size_t, util::geo::Polygon, double> _sgrid;
165
   util::geo::Grid<size_t, util::geo::Polygon, double> _sgrid;
138
   util::geo::Grid<size_t, util::geo::Polygon, double> _ggrid;
166
   util::geo::Grid<size_t, util::geo::Polygon, double> _ggrid;
167
+  util::geo::Grid<size_t, util::geo::Polygon, double> _mgrid;
139
   util::geo::Grid<size_t, util::geo::Line, double> _suggrid;
168
   util::geo::Grid<size_t, util::geo::Line, double> _suggrid;
140
 
169
 
141
   std::vector<util::geo::Grid<Cluster, util::geo::Point, double>> _heatGridsOk;
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
       a = handleStatReq(params);
45
       a = handleStatReq(params);
46
     } else if (cmd == "/group") {
46
     } else if (cmd == "/group") {
47
       a = handleGroupReq(params);
47
       a = handleGroupReq(params);
48
+    } else if (cmd == "/mgroup") {
49
+      a = handleMGroupReq(params);
48
     } else if (cmd == "/search") {
50
     } else if (cmd == "/search") {
49
       a = handleSearch(params);
51
       a = handleSearch(params);
50
     } else {
52
     } else {
@@ -228,6 +230,7 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
228
 
230
 
229
       const auto& suggs = idx.getSuggestions(bbox);
231
       const auto& suggs = idx.getSuggestions(bbox);
230
       for (const auto& sugg : suggs) {
232
       for (const auto& sugg : suggs) {
233
+        if (sugg->arrow.size() < 2) continue;
231
         json << sep;
234
         json << sep;
232
         sep = ',';
235
         sep = ',';
233
         printSugg(sugg, did, &json);
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
   json << "]}";
256
   json << "]}";
239
 
257
 
240
   if (cb.size()) json << ")";
258
   if (cb.size()) json << ")";
@@ -284,8 +302,13 @@ void StatServer::printSugg(const Suggestion* sugg, size_t did,
284
     projArrow.push_back(util::geo::webMercToLatLng<double>(p.getX(), p.getY()));
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
   char sep = ' ';
312
   char sep = ' ';
290
   for (const auto& p : projArrow) {
313
   for (const auto& p : projArrow) {
291
     (*out) << sep << "[" << p.getY() << "," << p.getX() << "]";
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
 void StatServer::printGroup(const Group* group, size_t did, bool simple,
344
 void StatServer::printGroup(const Group* group, size_t did, bool simple,
300
                             bool aggr, std::ostream* out) const {
345
                             bool aggr, std::ostream* out) const {
301
   const auto& range = _idxs[did].first;
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
 size_t StatServer::getDidByGid(size_t gid) const {
431
 size_t StatServer::getDidByGid(size_t gid) const {
378
   for (size_t i = 0; i < _idxs.size(); i++) {
432
   for (size_t i = 0; i < _idxs.size(); i++) {
379
     auto range = _idxs[i].first;
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
 util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
578
 util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
450
   if (pars.count("id") == 0 || pars.find("id")->second.empty())
579
   if (pars.count("id") == 0 || pars.find("id")->second.empty())
451
     throw std::invalid_argument("No ID specified.");
580
     throw std::invalid_argument("No ID specified.");
@@ -468,9 +597,13 @@ util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
468
 
597
 
469
   json << std::setprecision(10);
598
   json << std::setprecision(10);
470
 
599
 
600
+  auto centroid = group->centroid;
601
+
471
   if (cb.size()) json << cb << "(";
602
   if (cb.size()) json << cb << "(";
472
   json << "{\"id\":" << gid << ","
603
   json << "{\"id\":" << gid << ","
473
        << "\"osmid\":" << group->osmid << ","
604
        << "\"osmid\":" << group->osmid << ","
605
+       << "\"lat\":" << centroid.getY() << ","
606
+       << "\"lon\":" << centroid.getX() << ","
474
        << "\"attrs\":{";
607
        << "\"attrs\":{";
475
 
608
 
476
   char sep = ' ';
609
   char sep = ' ';
@@ -573,6 +706,12 @@ util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
573
     } else if (sugg->type == 8) {
706
     } else if (sugg->type == 8) {
574
       json << seper << "{\"type\":8,\"attr\":\"" << sugg->attrErrName << "\""
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
     } else {
715
     } else {
577
       json << seper << "{\"type\":" << sugg->type << "}";
716
       json << seper << "{\"type\":" << sugg->type << "}";
578
     }
717
     }
@@ -713,6 +852,20 @@ util::http::Answer StatServer::handleStatReq(const Params& pars) const {
713
     } else if (sugg->type == 8) {
852
     } else if (sugg->type == 8) {
714
       json << seper << "{\"type\":8,\"attr\":\"" << sugg->attrErrName << "\""
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
     } else {
869
     } else {
717
       json << seper << "{\"type\":" << sugg->type << "}";
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
   util::http::Answer handleHeatMapReq(const Params& pars) const;
31
   util::http::Answer handleHeatMapReq(const Params& pars) const;
32
   util::http::Answer handleStatReq(const Params& pars) const;
32
   util::http::Answer handleStatReq(const Params& pars) const;
33
   util::http::Answer handleGroupReq(const Params& pars) const;
33
   util::http::Answer handleGroupReq(const Params& pars) const;
34
+  util::http::Answer handleMGroupReq(const Params& pars) const;
34
   util::http::Answer handleSearch(const Params& pars) const;
35
   util::http::Answer handleSearch(const Params& pars) const;
35
 
36
 
36
   void printStation(const Station* stat, size_t did, bool simple, std::ostream* out) const;
37
   void printStation(const Station* 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;
38
   void printGroup(const Group* stat, size_t did, bool simple, bool aggr, std::ostream* out) const;
38
   void printSugg(const Suggestion* stat, size_t did, std::ostream* out) const;
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
   size_t getDidBySid(size_t sid) const;
42
   size_t getDidBySid(size_t sid) const;
41
   size_t getDidBySuggid(size_t suggid) const;
43
   size_t getDidBySuggid(size_t suggid) const;
42
   size_t getDidByGid(size_t gid) const;
44
   size_t getDidByGid(size_t gid) const;
45
+  size_t getDidByMGid(size_t mgid) const;
43
 
46
 
44
   const std::vector<std::pair<IdRange, osmfixer::StatIdx>>& _idxs;
47
   const std::vector<std::pair<IdRange, osmfixer::StatIdx>>& _idxs;
45
   const SearchIdx& _searchIdx;
48
   const SearchIdx& _searchIdx;

File diff suppressed because it is too large
+ 1 - 596
web/index.html


+ 110 - 28
web/script.js

@@ -5,13 +5,16 @@ var stCols = ['#78f378', '#0000c3', 'red'];
5
 
5
 
6
 var osmUrl = "//www.openstreetmap.org/";
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
 var reqs = {};
10
 var reqs = {};
11
 
11
 
12
 var openedGr = -1;
12
 var openedGr = -1;
13
+var openedMGr = -1;
13
 var openedSt = -1;
14
 var openedSt = -1;
14
 
15
 
16
+
17
+// station sugg messages
15
 var sgMvOrNew = "Move into a <span class='grouplink' onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})'>new relation</span> <tt>public_transport=stop_area</tt>.";
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
 var sgMvOrEx = "Move into relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>.";
19
 var sgMvOrEx = "Move into relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>.";
17
 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
 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
 var sgFixAttr = "Fix attribute <tt>${attr}</tt>.";
23
 var sgFixAttr = "Fix attribute <tt>${attr}</tt>.";
21
 var sgAddName = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute.";
24
 var sgAddName = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute.";
22
 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.";
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
 function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)}
40
 function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)}
27
 function $$(t){return document.createElement(t) }
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
     var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385';
95
     var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385';
82
     var style = {
96
     var style = {
83
         color: col,
97
         color: col,
@@ -86,6 +100,14 @@ function poly(group, z) {
86
         fillOpacity: 0.2,
100
         fillOpacity: 0.2,
87
         id: group.i
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
     if (z < 16) {
111
     if (z < 16) {
90
         style.weight = 11;
112
         style.weight = 11;
91
         style.opacity = 0.5;
113
         style.opacity = 0.5;
@@ -197,7 +219,7 @@ function rndrSt(stat) {
197
 
219
 
198
 function openSt(id) {req("s", "/stat?id=" + id, function(c) {rndrSt(c)});}
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
     openedGr = grp.id;
223
     openedGr = grp.id;
202
     var attrrows = {};
224
     var attrrows = {};
203
     grHl(grp.id);
225
     grHl(grp.id);
@@ -269,6 +291,28 @@ function rndrGr(grp, ll) {
269
         row.childNodes[1].appendChild(info);
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
     con.appendChild(newMembers);
316
     con.appendChild(newMembers);
273
     if (grp.osmid != 1) con.appendChild(oldMembers);
317
     if (grp.osmid != 1) con.appendChild(oldMembers);
274
 
318
 
@@ -286,43 +330,70 @@ function rndrGr(grp, ll) {
286
         if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row);
330
         if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row);
287
         else {
331
         else {
288
             oldMembers.appendChild(row);
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
     L.popup({opacity: 0.8})
373
     L.popup({opacity: 0.8})
318
-        .setLatLng(ll)
374
+        .setLatLng(grp)
319
         .setContent(con)
375
         .setContent(con)
320
         .openOn(map)
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
 function grHl(id) {
399
 function grHl(id) {
@@ -448,6 +519,7 @@ function render() {
448
             function(re) {
519
             function(re) {
449
                 l.clearLayers();
520
                 l.clearLayers();
450
                 grIdx = {};
521
                 grIdx = {};
522
+                mGrIdx = {};
451
                 stIdx = {};
523
                 stIdx = {};
452
 
524
 
453
                 var stats = [];
525
                 var stats = [];
@@ -457,7 +529,12 @@ function render() {
457
 
529
 
458
                 var groups = [];
530
                 var groups = [];
459
                 for (var i = 0; i < re.groups.length; i++) {
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
                 var suggs = [];
540
                 var suggs = [];
@@ -466,6 +543,9 @@ function render() {
466
                 }
543
                 }
467
 
544
 
468
                 if (map.getZoom() > 13) {
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
                     l.addLayer(L.featureGroup(groups).on('click', function(a) {
549
                     l.addLayer(L.featureGroup(groups).on('click', function(a) {
470
                         openGr(a.layer.options.id, a.layer.getBounds().getCenter());
550
                         openGr(a.layer.options.id, a.layer.getBounds().getCenter());
471
                     }));
551
                     }));
@@ -477,11 +557,13 @@ function render() {
477
 
557
 
478
                 if (map.getZoom() > 15) {
558
                 if (map.getZoom() > 15) {
479
                     l.addLayer(L.featureGroup(suggs).on('click', function(a) {
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
                 grHl(openedGr);
565
                 grHl(openedGr);
566
+                mGrHl(openedMGr);
485
                 stHl(openedSt);
567
                 stHl(openedSt);
486
             }
568
             }
487
         )
569
         )