|  | @@ -68,8 +68,14 @@ void StatIdx::readFromFile(const std::string& path) {
 | 
	
		
			
			| 68 | 68 |        }
 | 
	
		
			
			| 69 | 69 |      }
 | 
	
		
			
			| 70 | 70 |  
 | 
	
		
			
			|  | 71 | +    if (geom.size() != 1 && util::geo::dist(geom.front(), geom.back()) > 1) {
 | 
	
		
			
			|  | 72 | +      // transform lines into polygons
 | 
	
		
			
			|  | 73 | +      geom = hull(geom, 2).getOuter();
 | 
	
		
			
			|  | 74 | +    }
 | 
	
		
			
			|  | 75 | +
 | 
	
		
			
			| 71 | 76 |      if (geom.size() > 1) geom = util::geo::simplify(geom, 0.5);
 | 
	
		
			
			| 72 |  | -    for (const auto& p : geom) latLngs.push_back(util::geo::webMercToLatLng<double>(p.getX(), p.getY()));
 | 
	
		
			
			|  | 77 | +    for (const auto& p : geom)
 | 
	
		
			
			|  | 78 | +      latLngs.push_back(util::geo::webMercToLatLng<double>(p.getX(), p.getY()));
 | 
	
		
			
			| 73 | 79 |  
 | 
	
		
			
			| 74 | 80 |      addStation(osmid, geom, latLngs, origGroup, group, attrs);
 | 
	
		
			
			| 75 | 81 |    }
 | 
	
	
		
			
			|  | @@ -87,13 +93,21 @@ void StatIdx::readFromFile(const std::string& path) {
 | 
	
		
			
			| 87 | 93 |  
 | 
	
		
			
			| 88 | 94 |      rec >> osmid;
 | 
	
		
			
			| 89 | 95 |  
 | 
	
		
			
			|  | 96 | +    std::string key;
 | 
	
		
			
			|  | 97 | +    std::getline(rec, key, '\t');
 | 
	
		
			
			|  | 98 | +    while (std::getline(rec, key, '\t')) {
 | 
	
		
			
			|  | 99 | +      std::string val;
 | 
	
		
			
			|  | 100 | +      std::getline(rec, val, '\t');
 | 
	
		
			
			|  | 101 | +      attrs[key].push_back(val);
 | 
	
		
			
			|  | 102 | +    }
 | 
	
		
			
			|  | 103 | +
 | 
	
		
			
			| 90 | 104 |      addGroup(osmid, attrs);
 | 
	
		
			
			| 91 | 105 |    }
 | 
	
		
			
			| 92 | 106 |  
 | 
	
		
			
			| 93 | 107 |    LOG(INFO) << "Done.";
 | 
	
		
			
			| 94 | 108 |  
 | 
	
		
			
			| 95 | 109 |    // third, parse attr errs
 | 
	
		
			
			| 96 |  | -  LOG(INFO) << "Parsing errors... ";
 | 
	
		
			
			|  | 110 | +  LOG(INFO) << "Parsing station errors... ";
 | 
	
		
			
			| 97 | 111 |    while (std::getline(f, line)) {
 | 
	
		
			
			| 98 | 112 |      // empty line is separator between blocks
 | 
	
		
			
			| 99 | 113 |      if (line.size() == 0) break;
 | 
	
	
		
			
			|  | @@ -102,7 +116,7 @@ void StatIdx::readFromFile(const std::string& path) {
 | 
	
		
			
			| 102 | 116 |      size_t id;
 | 
	
		
			
			| 103 | 117 |      std::string attr, otherAttr;
 | 
	
		
			
			| 104 | 118 |      double conf;
 | 
	
		
			
			| 105 |  | -    int64_t otherId;
 | 
	
		
			
			|  | 119 | +    int64_t otherIdRaw;
 | 
	
		
			
			| 106 | 120 |  
 | 
	
		
			
			| 107 | 121 |      rec >> id;
 | 
	
		
			
			| 108 | 122 |  
 | 
	
	
		
			
			|  | @@ -111,9 +125,48 @@ void StatIdx::readFromFile(const std::string& path) {
 | 
	
		
			
			| 111 | 125 |      std::getline(rec, otherAttr, '\t');
 | 
	
		
			
			| 112 | 126 |  
 | 
	
		
			
			| 113 | 127 |      rec >> conf;
 | 
	
		
			
			| 114 |  | -    rec >> otherId;
 | 
	
		
			
			|  | 128 | +    rec >> otherIdRaw;
 | 
	
		
			
			| 115 | 129 |  
 | 
	
		
			
			| 116 |  | -    _stations[id].attrErrs.push_back({attr, otherAttr, conf, otherId});
 | 
	
		
			
			|  | 130 | +    size_t otherId;
 | 
	
		
			
			|  | 131 | +    bool fromRel = false;
 | 
	
		
			
			|  | 132 | +
 | 
	
		
			
			|  | 133 | +    if (otherIdRaw < 0) fromRel = true;
 | 
	
		
			
			|  | 134 | +
 | 
	
		
			
			|  | 135 | +    otherId = labs(otherIdRaw);
 | 
	
		
			
			|  | 136 | +
 | 
	
		
			
			|  | 137 | +    _stations[id].attrErrs.push_back({attr, otherAttr, conf, otherId, fromRel});
 | 
	
		
			
			|  | 138 | +  }
 | 
	
		
			
			|  | 139 | +  LOG(INFO) << "Done.";
 | 
	
		
			
			|  | 140 | +
 | 
	
		
			
			|  | 141 | +  // fourth, parse attr errs in groups
 | 
	
		
			
			|  | 142 | +  LOG(INFO) << "Parsing group errors... ";
 | 
	
		
			
			|  | 143 | +  while (std::getline(f, line)) {
 | 
	
		
			
			|  | 144 | +    // empty line is separator between blocks
 | 
	
		
			
			|  | 145 | +    if (line.size() == 0) break;
 | 
	
		
			
			|  | 146 | +
 | 
	
		
			
			|  | 147 | +    std::stringstream rec(line);
 | 
	
		
			
			|  | 148 | +    size_t id;
 | 
	
		
			
			|  | 149 | +    std::string attr, otherAttr;
 | 
	
		
			
			|  | 150 | +    double conf;
 | 
	
		
			
			|  | 151 | +    int64_t otherIdRaw;
 | 
	
		
			
			|  | 152 | +
 | 
	
		
			
			|  | 153 | +    rec >> id;
 | 
	
		
			
			|  | 154 | +
 | 
	
		
			
			|  | 155 | +    std::getline(rec, attr, '\t');
 | 
	
		
			
			|  | 156 | +    std::getline(rec, attr, '\t');
 | 
	
		
			
			|  | 157 | +    std::getline(rec, otherAttr, '\t');
 | 
	
		
			
			|  | 158 | +
 | 
	
		
			
			|  | 159 | +    rec >> conf;
 | 
	
		
			
			|  | 160 | +    rec >> otherIdRaw;
 | 
	
		
			
			|  | 161 | +
 | 
	
		
			
			|  | 162 | +    size_t otherId;
 | 
	
		
			
			|  | 163 | +    bool fromRel = false;
 | 
	
		
			
			|  | 164 | +
 | 
	
		
			
			|  | 165 | +    if (otherIdRaw < 0) fromRel = true;
 | 
	
		
			
			|  | 166 | +
 | 
	
		
			
			|  | 167 | +    otherId = labs(otherIdRaw);
 | 
	
		
			
			|  | 168 | +
 | 
	
		
			
			|  | 169 | +    _groups[id].attrErrs.push_back({attr, otherAttr, conf, otherId, fromRel});
 | 
	
		
			
			| 117 | 170 |    }
 | 
	
		
			
			| 118 | 171 |    LOG(INFO) << "Done.";
 | 
	
		
			
			| 119 | 172 |  
 | 
	
	
		
			
			|  | @@ -175,24 +228,17 @@ void StatIdx::initGroups() {
 | 
	
		
			
			| 175 | 228 |      }
 | 
	
		
			
			| 176 | 229 |    }
 | 
	
		
			
			| 177 | 230 |  
 | 
	
		
			
			|  | 231 | +
 | 
	
		
			
			| 178 | 232 |    // build hull polygon
 | 
	
		
			
			| 179 | 233 |    for (size_t i = 0; i < _groups.size(); i++) {
 | 
	
		
			
			| 180 | 234 |      util::geo::MultiPoint<double> mp;
 | 
	
		
			
			| 181 | 235 |      for (size_t stid : _groups[i].polyStations) {
 | 
	
		
			
			| 182 |  | -      double rad = 11.0;
 | 
	
		
			
			| 183 |  | -      int n = 20;
 | 
	
		
			
			| 184 |  | -      for (int i = 0; i < n; i++) {
 | 
	
		
			
			| 185 |  | -        double x = rad * cos((2.0 * M_PI / static_cast<double>(n)) *
 | 
	
		
			
			| 186 |  | -                             static_cast<double>(i));
 | 
	
		
			
			| 187 |  | -        double y = rad * sin((2.0 * M_PI / static_cast<double>(n)) *
 | 
	
		
			
			| 188 |  | -                             static_cast<double>(i));
 | 
	
		
			
			| 189 |  | -
 | 
	
		
			
			| 190 |  | -        for (const auto& geom : _stations[stid].pos) {
 | 
	
		
			
			| 191 |  | -          mp.push_back(util::geo::DPoint(geom.getX() + x, geom.getY() + y));
 | 
	
		
			
			| 192 |  | -        }
 | 
	
		
			
			|  | 236 | +      for (const auto& geom : _stations[stid].pos) {
 | 
	
		
			
			|  | 237 | +        mp.push_back(util::geo::DPoint(geom.getX(), geom.getY()));
 | 
	
		
			
			| 193 | 238 |        }
 | 
	
		
			
			| 194 | 239 |      }
 | 
	
		
			
			| 195 |  | -    _groups[i].poly = util::geo::simplify(util::geo::convexHull(mp).getOuter(), 0.5);
 | 
	
		
			
			|  | 240 | +
 | 
	
		
			
			|  | 241 | +    _groups[i].poly = hull(mp, 11);
 | 
	
		
			
			| 196 | 242 |  
 | 
	
		
			
			| 197 | 243 |      for (auto p : _groups[i].poly.getOuter()) {
 | 
	
		
			
			| 198 | 244 |        _groups[i].llPoly.getOuter().push_back(
 | 
	
	
		
			
			|  | @@ -204,6 +250,28 @@ void StatIdx::initGroups() {
 | 
	
		
			
			| 204 | 250 |  }
 | 
	
		
			
			| 205 | 251 |  
 | 
	
		
			
			| 206 | 252 |  // _____________________________________________________________________________
 | 
	
		
			
			|  | 253 | +util::geo::Polygon<double> StatIdx::hull(
 | 
	
		
			
			|  | 254 | +    const util::geo::MultiPoint<double>& imp, double rad) const {
 | 
	
		
			
			|  | 255 | +  util::geo::MultiPoint<double> mp;
 | 
	
		
			
			|  | 256 | +  util::geo::Polygon<double> ret;
 | 
	
		
			
			|  | 257 | +  for (const auto& geom : imp) {
 | 
	
		
			
			|  | 258 | +    // double rad = 11.0;
 | 
	
		
			
			|  | 259 | +    int n = 20;
 | 
	
		
			
			|  | 260 | +    for (int i = 0; i < n; i++) {
 | 
	
		
			
			|  | 261 | +      double x = rad * cos((2.0 * M_PI / static_cast<double>(n)) *
 | 
	
		
			
			|  | 262 | +                           static_cast<double>(i));
 | 
	
		
			
			|  | 263 | +      double y = rad * sin((2.0 * M_PI / static_cast<double>(n)) *
 | 
	
		
			
			|  | 264 | +                           static_cast<double>(i));
 | 
	
		
			
			|  | 265 | +
 | 
	
		
			
			|  | 266 | +      mp.push_back(util::geo::DPoint(geom.getX() + x, geom.getY() + y));
 | 
	
		
			
			|  | 267 | +    }
 | 
	
		
			
			|  | 268 | +  }
 | 
	
		
			
			|  | 269 | +  ret = util::geo::simplify(util::geo::convexHull(mp).getOuter(), 0.5);
 | 
	
		
			
			|  | 270 | +
 | 
	
		
			
			|  | 271 | +  return ret;
 | 
	
		
			
			|  | 272 | +}
 | 
	
		
			
			|  | 273 | +
 | 
	
		
			
			|  | 274 | +// _____________________________________________________________________________
 | 
	
		
			
			| 207 | 275 |  void StatIdx::initIndex() {
 | 
	
		
			
			| 208 | 276 |    double gSize = 10000;
 | 
	
		
			
			| 209 | 277 |    _sgrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
 | 
	
	
		
			
			|  | @@ -465,21 +533,80 @@ std::vector<osmfixer::Cluster> StatIdx::getHeatGridSugg(
 | 
	
		
			
			| 465 | 533 |  }
 | 
	
		
			
			| 466 | 534 |  // _____________________________________________________________________________
 | 
	
		
			
			| 467 | 535 |  void StatIdx::initSuggestions() {
 | 
	
		
			
			|  | 536 | +  // group suggestions
 | 
	
		
			
			|  | 537 | +  for (size_t i = 0; i < _groups.size(); i++) {
 | 
	
		
			
			|  | 538 | +    auto& group = _groups[i];
 | 
	
		
			
			|  | 539 | +
 | 
	
		
			
			|  | 540 | +    if (group.attrs.size() == 0) {
 | 
	
		
			
			|  | 541 | +      Suggestion sug;
 | 
	
		
			
			|  | 542 | +      sug.station = i;
 | 
	
		
			
			|  | 543 | +      sug.type = 7;
 | 
	
		
			
			|  | 544 | +      _suggestions.push_back(sug);
 | 
	
		
			
			|  | 545 | +      group.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			|  | 546 | +    }
 | 
	
		
			
			|  | 547 | +
 | 
	
		
			
			|  | 548 | +    Suggestion sug;
 | 
	
		
			
			|  | 549 | +    sug.station = i;
 | 
	
		
			
			|  | 550 | +
 | 
	
		
			
			|  | 551 | +    std::set<std::pair<std::string, uint16_t>> suggested;
 | 
	
		
			
			|  | 552 | +    for (auto attrErr : group.attrErrs) {
 | 
	
		
			
			|  | 553 | +      if (!attrErr.fromRel) continue;
 | 
	
		
			
			|  | 554 | +
 | 
	
		
			
			|  | 555 | +      const auto otherGrp = getGroup(attrErr.otherId);
 | 
	
		
			
			|  | 556 | +      if (group.id == attrErr.otherId && attrErr.attr == attrErr.otherAttr) {
 | 
	
		
			
			|  | 557 | +        if (suggested.count({attrErr.attr, 8})) continue;
 | 
	
		
			
			|  | 558 | +        // fix track number as name
 | 
	
		
			
			|  | 559 | +        suggested.insert({attrErr.attr, 8});
 | 
	
		
			
			|  | 560 | +        sug.type = 8;
 | 
	
		
			
			|  | 561 | +        sug.orig_gid = 0;
 | 
	
		
			
			|  | 562 | +        sug.orig_osm_rel_id = 0;
 | 
	
		
			
			|  | 563 | +        sug.target_gid = 0;
 | 
	
		
			
			|  | 564 | +        sug.target_osm_rel_id = 0;
 | 
	
		
			
			|  | 565 | +        sug.attrErrName = attrErr.attr;
 | 
	
		
			
			|  | 566 | +
 | 
	
		
			
			|  | 567 | +        _suggestions.push_back(sug);
 | 
	
		
			
			|  | 568 | +        group.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			|  | 569 | +      } else if (otherGrp->osmid == group.osmid) {
 | 
	
		
			
			|  | 570 | +        if (suggested.count({attrErr.attr, 6})) continue;
 | 
	
		
			
			|  | 571 | +        // fix attributes
 | 
	
		
			
			|  | 572 | +        suggested.insert({attrErr.attr, 6});
 | 
	
		
			
			|  | 573 | +        sug.type = 6;
 | 
	
		
			
			|  | 574 | +        sug.orig_gid = 0;
 | 
	
		
			
			|  | 575 | +        sug.orig_osm_rel_id = 0;
 | 
	
		
			
			|  | 576 | +        sug.target_gid = 0;
 | 
	
		
			
			|  | 577 | +        sug.target_osm_rel_id = 0;
 | 
	
		
			
			|  | 578 | +        sug.attrErrName = attrErr.attr;
 | 
	
		
			
			|  | 579 | +
 | 
	
		
			
			|  | 580 | +        _suggestions.push_back(sug);
 | 
	
		
			
			|  | 581 | +        group.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			|  | 582 | +      }
 | 
	
		
			
			|  | 583 | +    }
 | 
	
		
			
			|  | 584 | +  }
 | 
	
		
			
			|  | 585 | +
 | 
	
		
			
			|  | 586 | +  // station suggestions
 | 
	
		
			
			| 468 | 587 |    for (size_t i = 0; i < _stations.size(); i++) {
 | 
	
		
			
			| 469 | 588 |      auto& stat = _stations[i];
 | 
	
		
			
			|  | 589 | +
 | 
	
		
			
			|  | 590 | +    if (stat.attrs.size() == 0) {
 | 
	
		
			
			|  | 591 | +      Suggestion sug;
 | 
	
		
			
			|  | 592 | +      sug.station = i;
 | 
	
		
			
			|  | 593 | +      sug.type = 7;
 | 
	
		
			
			|  | 594 | +      _suggestions.push_back(sug);
 | 
	
		
			
			|  | 595 | +      stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			|  | 596 | +    }
 | 
	
		
			
			|  | 597 | +
 | 
	
		
			
			| 470 | 598 |      if (stat.group != stat.origGroup || getGroup(stat.group)->osmid == 1) {
 | 
	
		
			
			| 471 | 599 |        Suggestion sug;
 | 
	
		
			
			| 472 | 600 |        sug.station = i;
 | 
	
		
			
			| 473 | 601 |        auto centroid = util::geo::centroid(stat.pos);
 | 
	
		
			
			| 474 | 602 |        sug.arrow = util::geo::DLine{centroid, centroid};
 | 
	
		
			
			|  | 603 | +
 | 
	
		
			
			| 475 | 604 |        if (getGroup(stat.origGroup)->osmid < 2) {
 | 
	
		
			
			| 476 | 605 |          if (getGroup(stat.group)->osmid == 1) {
 | 
	
		
			
			| 477 | 606 |            // move orphan into new group
 | 
	
		
			
			| 478 | 607 |            sug.type = 1;
 | 
	
		
			
			| 479 | 608 |            sug.target_gid = stat.group;
 | 
	
		
			
			| 480 | 609 |  
 | 
	
		
			
			| 481 |  | -          // sug.arrow = getGroupArrow(i, stat.group);
 | 
	
		
			
			| 482 |  | -
 | 
	
		
			
			| 483 | 610 |            _suggestions.push_back(sug);
 | 
	
		
			
			| 484 | 611 |            stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			| 485 | 612 |  
 | 
	
	
		
			
			|  | @@ -539,21 +666,42 @@ void StatIdx::initSuggestions() {
 | 
	
		
			
			| 539 | 666 |            stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			| 540 | 667 |          }
 | 
	
		
			
			| 541 | 668 |        }
 | 
	
		
			
			|  | 669 | +    }
 | 
	
		
			
			| 542 | 670 |  
 | 
	
		
			
			| 543 |  | -      for (auto attrErr : stat.attrErrs) {
 | 
	
		
			
			| 544 |  | -        const auto otherStat = getStation(attrErr.otherId);
 | 
	
		
			
			| 545 |  | -        if (otherStat->osmid == stat.osmid) {
 | 
	
		
			
			| 546 |  | -          // fix attributes
 | 
	
		
			
			| 547 |  | -          sug.type = 6;
 | 
	
		
			
			| 548 |  | -          sug.orig_gid = 0;
 | 
	
		
			
			| 549 |  | -          sug.orig_osm_rel_id = 0;
 | 
	
		
			
			| 550 |  | -          sug.target_gid = 0;
 | 
	
		
			
			| 551 |  | -          sug.target_osm_rel_id = 0;
 | 
	
		
			
			| 552 |  | -          sug.attrErrName = attrErr.attr;
 | 
	
		
			
			| 553 |  | -
 | 
	
		
			
			| 554 |  | -          _suggestions.push_back(sug);
 | 
	
		
			
			| 555 |  | -          stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			| 556 |  | -        }
 | 
	
		
			
			|  | 671 | +    Suggestion sug;
 | 
	
		
			
			|  | 672 | +    sug.station = i;
 | 
	
		
			
			|  | 673 | +
 | 
	
		
			
			|  | 674 | +    std::set<std::pair<std::string, uint16_t>> suggested;
 | 
	
		
			
			|  | 675 | +    for (auto attrErr : stat.attrErrs) {
 | 
	
		
			
			|  | 676 | +      if (attrErr.fromRel) continue;
 | 
	
		
			
			|  | 677 | +
 | 
	
		
			
			|  | 678 | +      const auto otherStat = getStation(attrErr.otherId);
 | 
	
		
			
			|  | 679 | +      if (stat.id == attrErr.otherId && attrErr.attr == attrErr.otherAttr) {
 | 
	
		
			
			|  | 680 | +        if (suggested.count({attrErr.attr, 8})) continue;
 | 
	
		
			
			|  | 681 | +        suggested.insert({attrErr.attr, 8});
 | 
	
		
			
			|  | 682 | +        // fix track number as name
 | 
	
		
			
			|  | 683 | +        sug.type = 8;
 | 
	
		
			
			|  | 684 | +        sug.orig_gid = 0;
 | 
	
		
			
			|  | 685 | +        sug.orig_osm_rel_id = 0;
 | 
	
		
			
			|  | 686 | +        sug.target_gid = 0;
 | 
	
		
			
			|  | 687 | +        sug.target_osm_rel_id = 0;
 | 
	
		
			
			|  | 688 | +        sug.attrErrName = attrErr.attr;
 | 
	
		
			
			|  | 689 | +
 | 
	
		
			
			|  | 690 | +        _suggestions.push_back(sug);
 | 
	
		
			
			|  | 691 | +        stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			|  | 692 | +      } else if (otherStat->osmid == stat.osmid) {
 | 
	
		
			
			|  | 693 | +        if (suggested.count({attrErr.attr, 6})) continue;
 | 
	
		
			
			|  | 694 | +        suggested.insert({attrErr.attr, 6});
 | 
	
		
			
			|  | 695 | +        // fix attributes
 | 
	
		
			
			|  | 696 | +        sug.type = 6;
 | 
	
		
			
			|  | 697 | +        sug.orig_gid = 0;
 | 
	
		
			
			|  | 698 | +        sug.orig_osm_rel_id = 0;
 | 
	
		
			
			|  | 699 | +        sug.target_gid = 0;
 | 
	
		
			
			|  | 700 | +        sug.target_osm_rel_id = 0;
 | 
	
		
			
			|  | 701 | +        sug.attrErrName = attrErr.attr;
 | 
	
		
			
			|  | 702 | +
 | 
	
		
			
			|  | 703 | +        _suggestions.push_back(sug);
 | 
	
		
			
			|  | 704 | +        stat.suggestions.push_back(_suggestions.size() - 1);
 | 
	
		
			
			| 557 | 705 |        }
 | 
	
		
			
			| 558 | 706 |      }
 | 
	
		
			
			| 559 | 707 |    }
 |