/// For algorithm details, see http://wiki.openstreetmap.org/wiki/Relation:multipolygon/Algorithm
void MultipolygonProcessor::process() {
  bool allClosed = true;
  // NOTE do not count multipolygon tag itself
  bool hasNoTags = relation_.tags.size() < 2;
  Ints outerIndecies;
  Ints innerIndecies;
  CoordinateSequences sequences;
  for (const auto &member : members_) {

    if (member.type!="w")
      continue;

    Coordinates coordinates;
    auto wayPair = context_.wayMap.find(member.refId);
    if (wayPair!=context_.wayMap.end()) {
      coordinates = wayPair->second->coordinates;
    } else {
      auto areaPair = context_.areaMap.find(member.refId);
      if (areaPair!=context_.areaMap.end()) {
        coordinates = areaPair->second->coordinates;

        // NOTE make coordinates to be closed ring
        coordinates.push_back(coordinates[0]);

        // NOTE merge tags to relation
        // hasNoTags prevents the case where relation has members with their own tags
        // which should be processed independently (geometry reusage)
        if (member.role=="outer" && hasNoTags)
          mergeTags(areaPair->second->tags);
      } else {
        auto relationPair = context_.relationMap.find(member.refId);
        if (relationPair==context_.relationMap.end())
          return; //  NOTE cannot fill relation: incomplete data

        resolve_(*relationPair->second);
      }
    }

    if (coordinates.empty())
      continue;

    if (member.role=="outer")
      outerIndecies.push_back(static_cast<int>(sequences.size()));
    else if (member.role=="inner")
      innerIndecies.push_back(static_cast<int>(sequences.size()));
    else
      continue;

    auto sequence = std::make_shared<CoordinateSequence>(member.refId, coordinates);
    if (!sequence->isClosed())
      allClosed = false;

    sequences.push_back(sequence);
  }

  if (outerIndecies.size()==1 && allClosed)
    simpleCase(sequences, outerIndecies, innerIndecies);
  else
    complexCase(sequences);
}
std::vector<std::shared_ptr<MultipolygonProcessor::CoordinateSequence>> MultipolygonProcessor::createRings(
    CoordinateSequences &sequences) const {
  CoordinateSequences closedRings;
  std::shared_ptr<MultipolygonProcessor::CoordinateSequence> currentRing = nullptr;
  while (!sequences.empty()) {
    if (currentRing==nullptr) {
      // start a new ring with any remaining node sequence
      auto lastIndex = sequences.size() - 1;
      currentRing = sequences[lastIndex];
      sequences.erase(sequences.begin() + lastIndex);
    } else {
      // try to continue the ring by appending a node sequence
      bool isFound = false;
      for (auto it = sequences.begin(); it!=sequences.end(); ++it) {
        if (!currentRing->tryAdd(**it)) continue;
        isFound = true;
        sequences.erase(it);
        break;
      }

      if (!isFound)
        return CoordinateSequences();
    }

    // check whether the ring under construction is closed
    if (currentRing!=nullptr && currentRing->isClosed()) {
      // TODO check that it isn't self-intersecting!
      closedRings.push_back(currentRing);
      currentRing = nullptr;
    }
  }

  return std::move(closedRings);
}
// see http://wiki.openstreetmap.org/wiki/Relation:multipolygon/Algorithm
void MultipolygonProcessor::process()
{
    bool allClosed = true;

    Ints outerIndecies;
    Ints innerIndecies;
    CoordinateSequences sequences;
    for (const auto& member : members_) {

        if (member.type != "way") 
            continue;

        Coordinates coordinates;
        auto wayPair = context_.wayMap.find(member.refId);
        if (wayPair != context_.wayMap.end()) {
            coordinates = wayPair->second->coordinates;
        }
        else {
            auto areaPair = context_.areaMap.find(member.refId);
            if (areaPair != context_.areaMap.end()) {
                coordinates = areaPair->second->coordinates;
                // NOTE merge tags to relation
                if (member.role == "outer")
                    mergeTags(areaPair->second->tags);
            }
            else {
                auto relationPair = context_.relationMap.find(member.refId);
                if (relationPair == context_.relationMap.end())
                    return; //  NOTE cannot fill relation: incomplete data

                resolve_(*relationPair->second);
            }
        }

        if (coordinates.empty()) 
            continue;

        if (member.role == "outer")
            outerIndecies.push_back(static_cast<int>(sequences.size()));
        else if (member.role == "inner")
            innerIndecies.push_back(static_cast<int>(sequences.size()));
        else
            continue;

        auto sequence = std::make_shared<CoordinateSequence>(member.refId, coordinates);
        if (!sequence->isClosed()) 
            allClosed = false;

        sequences.push_back(sequence);
    }

    if (outerIndecies.size() == 1 && allClosed)
        simpleCase(sequences, outerIndecies, innerIndecies);
    else
        complexCase(sequences);
}
void MultipolygonProcessor::fillRelation(CoordinateSequences& rings)
{
    while (!rings.empty()) {
        // find an outer ring
        std::shared_ptr<CoordinateSequence> outer = nullptr;
        for (auto candidate = rings.begin(); candidate != rings.end(); ++candidate) {
            bool containedInOtherRings = false;
            for (auto other = rings.begin(); other != rings.end(); ++other) {
                if (other != candidate && (*other)->containsRing((*candidate)->coordinates)) {
                    containedInOtherRings = true;
                    break;
                }
            }
            if (containedInOtherRings) continue;
            outer = *candidate;
            rings.erase(candidate);
            break;
        }

        // find inner rings of that ring
        CoordinateSequences inners;
        for (auto ring = rings.begin(); ring != rings.end();) {
            if (outer->containsRing((*ring)->coordinates)) {
                bool containedInOthers = false;
                for (auto other = rings.begin(); other != rings.end(); ++other) {
                    if (other != ring && (*other)->containsRing((*ring)->coordinates)) {
                        containedInOthers = true;
                        break;
                    }
                }
                if (!containedInOthers) {
                    inners.push_back(*ring);
                    ring = rings.erase(ring);
                    continue;
                }
            }
            ++ring;
        }

        // outer
        auto outerArea = std::make_shared<Area>();
        outerArea->id = outer->id;
        insertCoordinates(outer->coordinates, outerArea->coordinates, true);
        relation_.elements.push_back(outerArea);

        // inner: create a new area and remove the used rings
        for (const auto& innerRing : inners) {
            auto innerArea = std::make_shared<Area>();
            insertCoordinates(innerRing->coordinates, innerArea->coordinates, false);
            relation_.elements.push_back(innerArea);
        }
    }
}