/**
   * Requires vn to point to a relation element. From there it iterates over all matches of the ap.
   * Puts an empty list for each referenced way.
   */
  private List<Tuple2<WayRole, Long>> extractWayRefs(VTDNav vn, Hashtable<Long, List<Long>> ways)
      throws NavException, XPathEvalException {
    vn.push();

    List<Tuple2<WayRole, Long>> refs = new ArrayList<>();
    for (int j = MEMBER_WAY_PATH.evalXPath(); j != -1; j = MEMBER_WAY_PATH.evalXPath()) {
      long ref = Long.parseLong(vn.toString(vn.getAttrVal("ref")));
      String roleAsString = vn.toString(vn.getAttrVal("role")).toLowerCase();
      WayRole role;
      switch (roleAsString) {
        case "inner":
          role = WayRole.INNER;
          break;
        case "outer":
          role = WayRole.OUTER;
          break;
        default:
          continue;
      }
      refs.add(Tuple.tuple(role, ref));
      ways.put(ref, Collections.emptyList());
    }
    MEMBER_WAY_PATH.resetXPath();

    vn.pop();

    return refs;
  }
  private void extractReferencedWays(
      VTDNav vn, Hashtable<Long, List<Long>> ways, Hashtable<Long, Point2D> nodes)
      throws XPathEvalException, NavException {
    vn.push();

    for (int i = WAY_PATH.evalXPath(); i != -1; i = WAY_PATH.evalXPath()) {
      long id = Long.parseLong(vn.toString(vn.getAttrVal("id")));
      // By checking that we already referenced the id we can reduce memory pressure
      if (ways.containsKey(id)) {
        ways.put(id, extractNodeRefs(vn, nodes));
      }
    }

    vn.pop();
  }
  /**
   * Requires vn to point to a way element. From there it iterates over all matches of the ap. Puts
   * a dummy point into nodes for every node it finds.
   */
  private List<Long> extractNodeRefs(VTDNav vn, Hashtable<Long, Point2D> nodes)
      throws NavException, XPathEvalException {
    vn.push();

    List<Long> refs = new ArrayList<>();
    for (int j = NODE_REF_PATH.evalXPath(); j != -1; j = NODE_REF_PATH.evalXPath()) {
      long ref = Long.parseLong(vn.toString(j + 1));
      refs.add(ref);
      nodes.put(ref, new Point2D(0, 0));
    }
    NODE_REF_PATH.resetXPath();

    vn.pop();

    return refs;
  }
  private List<List<Long>> extractWaysOfBuildings(VTDNav vn, Hashtable<Long, Point2D> nodes)
      throws XPathEvalException, NavException {
    vn.push();

    List<List<Long>> ways = new ArrayList<>();
    for (int i = BUILDING_WAY_PATH.evalXPath(); i != -1; i = BUILDING_WAY_PATH.evalXPath()) {
      // The lambda will put in a dummy value for each encountered node,
      // so that we know later which nodes we need to parse.
      ways.add(extractNodeRefs(vn, nodes));
    }

    vn.pop();

    BUILDING_WAY_PATH.resetXPath();
    return ways;
  }
  private List<List<Tuple2<WayRole, Long>>> extractWayRefsOfMultipolygons(
      VTDNav vn, Hashtable<Long, List<Long>> ways) throws NavException, XPathEvalException {
    vn.push();

    List<List<Tuple2<WayRole, Long>>> multipolygons = new ArrayList<>();
    for (int i = BUILDING_MULTIPOLYGON_PATH.evalXPath();
        i != -1;
        i = BUILDING_MULTIPOLYGON_PATH.evalXPath()) {
      // For an explanation for the lambda see extractWaysOfBuildings
      multipolygons.add(extractWayRefs(vn, ways));
    }

    vn.pop();

    BUILDING_WAY_PATH.resetXPath();

    return multipolygons;
  }
  private void extractReferencedNodes(VTDNav vn, Hashtable<Long, Point2D> nodes)
      throws XPathEvalException, NavException {
    vn.push();

    for (int i = NODE_PATH.evalXPath(); i != -1; i = NODE_PATH.evalXPath()) {
      long id = Long.parseLong(vn.toString(vn.getAttrVal("id")));
      // By checking that we already referenced the id we can reduce memory pressure
      if (nodes.containsKey(id)) {
        nodes.put(
            id,
            new Point2D(
                Double.parseDouble(vn.toString(vn.getAttrVal("lon"))) * (1 << 10),
                Double.parseDouble(vn.toString(vn.getAttrVal("lat"))) * (1 << 10)));
      }
    }

    // Make sure we have all referenced nodes extracted
    // assert !nodes.containsValue(DUMMY_POINT);

    vn.pop();
  }
 public OSMGeometryParser() {
   try {
     NODE_PATH.selectXPath("/osm/node");
     NODE_REF_PATH.selectXPath("nd/@ref");
     BUILDING_WAY_PATH.selectXPath("/osm/way[./tag[@k='building']]");
     BUILDING_MULTIPOLYGON_PATH.selectXPath(
         "/osm/relation[./tag[@k='type' and @v='multipolygon'] and ./tag/@k='building']");
     MEMBER_WAY_PATH.selectXPath("member[@type='way']");
     WAY_PATH.selectXPath("/osm/way");
   } catch (XPathParseException e) {
     e.printStackTrace();
   }
 }
  public List<Triangle> parseFile(InputStream is) {
    try {
      VTDGen vg = new VTDGen();
      vg.setDoc(IOUtils.toByteArray(is));
      vg.parse(false);
      VTDNav vn = vg.getNav();
      NODE_PATH.resetXPath();
      NODE_PATH.bind(vn); // This is important state for the later method calls!
      NODE_REF_PATH.resetXPath();
      NODE_REF_PATH.bind(vn);
      BUILDING_WAY_PATH.resetXPath();
      BUILDING_WAY_PATH.bind(vn);
      BUILDING_MULTIPOLYGON_PATH.resetXPath();
      BUILDING_MULTIPOLYGON_PATH.bind(vn);
      MEMBER_WAY_PATH.resetXPath();
      MEMBER_WAY_PATH.bind(vn);
      WAY_PATH.resetXPath();
      WAY_PATH.bind(vn);

      // A hash from node ids to actual node positions
      Hashtable<Long, Point2D> nodes = new Hashtable<>();
      // A hash from way refs referenced from multipolygons to their actual list of node refs
      Hashtable<Long, List<Long>> multipolygonWays = new Hashtable<>();

      // The following call initializes accessed node refs in nodes to a dummy value.
      List<List<Long>> buildingWays = extractWaysOfBuildings(vn, nodes);
      // The following call initializes accessed way refs from multipolygons to a dummy value.
      List<List<Tuple2<WayRole, Long>>> multipolygonWayRefs =
          extractWayRefsOfMultipolygons(vn, multipolygonWays);
      // This will extract all referenced multipolygon multipolygonWays, excluding the building
      // multipolygonWays
      // Also adds referenced nodes to nodes
      extractReferencedWays(vn, multipolygonWays, nodes);
      // This will extract all referenced nodes, but no more.
      extractReferencedNodes(vn, nodes);
      // Finally build the polygon list by following the node refs in wayRefs.
      // Triangulate each polygon and return the flattened list of triangles.
      // This way, poly2tri's types will not leak out of this class and we operate
      // on triangles anyway.
      return buildPolygons(nodes, buildingWays, multipolygonWays, multipolygonWayRefs)
          .flatMap(
              p -> {
                try {
                  p.ComplexToSimplePolygon();
                  EarClipping ec = new EarClipping(p.SimplePolygon);
                  return ec.Triangulation().stream();
                } catch (RuntimeException ignored) {
                }
                return Stream.empty();
              })
          .toList();
    } catch (XPathEvalException | NavException | IOException | ParseException e) {
      e.printStackTrace();
      return new ArrayList<>();
    }
  }