|  | @@ -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;
 |