private Geometry parseMultiPolygon(int dimension, CoordinateReferenceSystem crs)
      throws XmlPullParserException, IOException, NoSuchAuthorityCodeException, FactoryException {

    parser.require(START_TAG, GML.NAMESPACE, GML.MultiPolygon.getLocalPart());

    Geometry geom;
    List<Polygon> polygons = new ArrayList<Polygon>(2);
    parser.nextTag();
    while (true) {
      parser.require(START_TAG, GML.NAMESPACE, GML.polygonMember.getLocalPart());
      parser.nextTag();
      parser.require(START_TAG, GML.NAMESPACE, GML.Polygon.getLocalPart());
      Polygon p = parsePolygon(dimension, crs);
      polygons.add(p);
      parser.nextTag();
      parser.require(END_TAG, GML.NAMESPACE, GML.polygonMember.getLocalPart());
      parser.nextTag();
      if (END_TAG == parser.getEventType()
          && GML.MultiPolygon.getLocalPart().equals(parser.getName())) {
        // we're done
        break;
      }
    }
    parser.require(END_TAG, GML.NAMESPACE, GML.MultiPolygon.getLocalPart());

    geom = geomFac.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
    return geom;
  }
  /**
   * Precondition: parser cursor positioned on a geometry property (ej, {@code gml:Point}, etc)
   *
   * <p>Postcondition: parser gets positioned at the end tag of the element it started parsing the
   * geometry at
   *
   * @return
   * @throws FactoryException
   * @throws NoSuchAuthorityCodeException
   * @throws IOException
   * @throws XmlPullParserException
   */
  private Geometry parseGeom()
      throws NoSuchAuthorityCodeException, FactoryException, XmlPullParserException, IOException {
    final QName startingGeometryTagName = new QName(parser.getNamespace(), parser.getName());
    int dimension = crsDimension(2);
    CoordinateReferenceSystem crs = crs(DefaultGeographicCRS.WGS84);

    Geometry geom;
    if (GML.Point.equals(startingGeometryTagName)) {
      geom = parsePoint(dimension, crs);
    } else if (GML.LineString.equals(startingGeometryTagName)) {
      geom = parseLineString(dimension, crs);
    } else if (GML.Polygon.equals(startingGeometryTagName)) {
      geom = parsePolygon(dimension, crs);
    } else if (GML.MultiPoint.equals(startingGeometryTagName)) {
      geom = parseMultiPoint(dimension, crs);
    } else if (GML.MultiLineString.equals(startingGeometryTagName)) {
      geom = parseMultiLineString(dimension, crs);
    } else if (GML.MultiSurface.equals(startingGeometryTagName)) {
      geom = parseMultiSurface(dimension, crs);
    } else if (GML.MultiPolygon.equals(startingGeometryTagName)) {
      geom = parseMultiPolygon(dimension, crs);
    } else {
      throw new IllegalStateException("Unrecognized geometry element " + startingGeometryTagName);
    }

    parser.require(
        END_TAG, startingGeometryTagName.getNamespaceURI(), startingGeometryTagName.getLocalPart());

    return geom;
  }
  /**
   * Parses a polygon.
   *
   * <p>Precondition: parser positioned at a {@link GML#Polygon Polygon} start tag
   *
   * <p>Postcondition: parser positioned at the {@link GML#Polygon Polygon} end tag of the starting
   * tag
   *
   * @param dimension
   * @param crs
   * @return
   * @throws XmlPullParserException
   * @throws IOException
   * @throws NoSuchAuthorityCodeException
   * @throws FactoryException
   */
  private Polygon parsePolygon(int dimension, CoordinateReferenceSystem crs)
      throws XmlPullParserException, IOException, NoSuchAuthorityCodeException, FactoryException {
    Polygon geom;
    LinearRing shell;
    List<LinearRing> holes = null;

    parser.nextTag();
    parser.require(START_TAG, GML.NAMESPACE, null);

    QName name = new QName(parser.getNamespace(), parser.getName());

    if (GML.exterior.equals(name)) {
      parser.nextTag();
      shell = parseLinearRing(dimension, crs);
      parser.nextTag();
      parser.require(END_TAG, GML.NAMESPACE, GML.exterior.getLocalPart());
    } else if (GML.outerBoundaryIs.equals(name)) {
      parser.nextTag();
      parser.require(START_TAG, GML.NAMESPACE, GML.LinearRing.getLocalPart());
      shell = parseLinearRing(dimension, crs);
      parser.nextTag();
      parser.require(END_TAG, GML.NAMESPACE, GML.outerBoundaryIs.getLocalPart());
    } else {
      throw new IllegalStateException("Unknown polygon boundary element: " + name);
    }

    parser.nextTag();

    name = new QName(parser.getNamespace(), parser.getName());

    if (START_TAG == parser.getEventType()) {
      if (GML.interior.equals(name) || GML.innerBoundaryIs.equals(name)) {
        // parse interior rings
        holes = new ArrayList<LinearRing>(2);
        while (true) {
          parser.require(START_TAG, GML.NAMESPACE, name.getLocalPart());
          parser.nextTag();
          parser.require(START_TAG, GML.NAMESPACE, GML.LinearRing.getLocalPart());

          LinearRing hole = parseLinearRing(dimension, crs);

          parser.require(END_TAG, GML.NAMESPACE, GML.LinearRing.getLocalPart());

          holes.add(hole);

          parser.nextTag();
          parser.require(END_TAG, GML.NAMESPACE, name.getLocalPart());
          parser.nextTag();
          if (END_TAG == parser.getEventType()) {
            // we're done
            parser.require(END_TAG, GML.NAMESPACE, GML.Polygon.getLocalPart());
            break;
          }
        }
      }
    }

    parser.require(END_TAG, GML.NAMESPACE, GML.Polygon.getLocalPart());

    LinearRing[] holesArray = null;
    if (holes != null) {
      holesArray = holes.toArray(new LinearRing[holes.size()]);
    }
    geom = geomFac.createPolygon(shell, holesArray);
    geom.setUserData(crs);
    return geom;
  }