// http://code.google.com/apis/maps/documentation/geocoding/index.html
  public GeoPoint getGeocode(String address, String country) {

    /* URL */
    String url = this.getGeoPointGeocodeURL(address, country);
    Print.logDebug("Google GC URL: " + url);

    /* create XML document */
    Document xmlDoc = GetXMLDocument(url, this.getGeocodeTimeout());
    if (xmlDoc == null) {
      return null;
    }

    /* vars */
    String errCode = null;
    GeoPoint geoPoint = null;

    /* parse "xml" */
    Element kml = xmlDoc.getDocumentElement();
    if (kml.getTagName().equalsIgnoreCase(TAG_kml)) {
      NodeList ResponseList = XMLTools.getChildElements(kml, TAG_Response);
      for (int g = 0; (g < ResponseList.getLength()); g++) {
        Element response = (Element) ResponseList.item(g);
        NodeList responseNodes = response.getChildNodes();
        for (int n = 0; n < responseNodes.getLength(); n++) {
          Node responseNode = responseNodes.item(n);
          if (!(responseNode instanceof Element)) {
            continue;
          }
          Element responseElem = (Element) responseNode;
          String responseNodeName = responseElem.getNodeName();
          if (responseNodeName.equalsIgnoreCase(TAG_name)) {
            // <name>1600 Amphitheatre Parkway, Mountain View, CA</name>
          } else if (responseNodeName.equalsIgnoreCase(TAG_Status)) {
            // <Status> ... </Status>
            NodeList statusNodes = responseElem.getChildNodes();
            for (int st = 0; st < statusNodes.getLength(); st++) {
              Node statusNode = statusNodes.item(st);
              if (!(statusNode instanceof Element)) {
                continue;
              }
              Element statusElem = (Element) statusNode;
              String statusNodeName = statusElem.getNodeName();
              if (statusNodeName.equalsIgnoreCase(TAG_code)) {
                errCode = StringTools.trim(GoogleGeocodeV2.GetNodeText(statusElem)); // expect "200"
                break; // we only care about the 'code'
              }
            }
          } else if (responseNodeName.equalsIgnoreCase(TAG_Placemark)) {
            // <Placemark> ... </Placemark>
            String id = responseElem.getAttribute(ATTR_id);
            if ((id != null) && id.equals("p1")) {
              NodeList placemarkNodes = responseElem.getChildNodes();
              for (int pm = 0; (geoPoint == null) && (pm < placemarkNodes.getLength()); pm++) {
                Node placemarkNode = placemarkNodes.item(pm);
                if (!(placemarkNode instanceof Element)) {
                  continue;
                }
                Element placemarkElem = (Element) placemarkNode;
                String placemarkNodeName = placemarkElem.getNodeName();
                if (placemarkNodeName.equalsIgnoreCase(TAG_Point)) {
                  NodeList pointNodes = placemarkElem.getChildNodes();
                  for (int ptn = 0; (geoPoint == null) && (ptn < pointNodes.getLength()); ptn++) {
                    Node pointNode = pointNodes.item(ptn);
                    if (!(pointNode instanceof Element)) {
                      continue;
                    }
                    Element pointElem = (Element) pointNode;
                    String pointNodeName = pointElem.getNodeName();
                    if (pointNodeName.equalsIgnoreCase(TAG_coordinates)) {
                      String ll[] = StringTools.split(GoogleGeocodeV2.GetNodeText(pointElem), ',');
                      if (ll.length >= 2) {
                        double lon = StringTools.parseDouble(ll[0], 0.0); // longitude is first
                        double lat = StringTools.parseDouble(ll[1], 0.0);
                        if (GeoPoint.isValid(lat, lon)) {
                          geoPoint = new GeoPoint(lat, lon);
                          break; // we only care about the 'GeoPoint'
                        }
                      }
                    }
                  }
                }
              }
            } else {
              // Print.logInfo("Skipping Placemark ID = %s", id);
            }
          }
        }
      }
    }

    /* create address */
    if (geoPoint != null) {
      // GeoPoint found
      return geoPoint;
    }

    /* check for Google reverse-geocode limit exceeded */
    if ((errCode != null) && errCode.equals("620")) {
      Print.logError("!!!! Google Reverse-Geocode Limit Exceeded [Error 620] !!!!");
    }

    /* no reverse-geocode available */
    return null;
  }
  /* return reverse-geocode using nearest address */
  public ReverseGeocode getAddressReverseGeocode(GeoPoint gp, String localeStr, boolean cache) {

    /* check for failover mode */
    if (this.isReverseGeocodeFailoverMode()) {
      ReverseGeocodeProvider frgp = this.getFailoverReverseGeocodeProvider();
      return frgp.getReverseGeocode(gp, localeStr, cache);
    }

    /* URL */
    String url = this.getAddressReverseGeocodeURL(gp, localeStr);
    Print.logDebug("Google RG URL: " + url);
    // byte xmlBytes[] = HTMLTools.readPage(url);

    /* create XML document */
    Document xmlDoc = GetXMLDocument(url, this.getReverseGeocodeTimeout());
    if (xmlDoc == null) {
      return null;
    }

    /* vars */
    String errCode = null;
    String address = null;

    /* parse "xml" */
    Element kml = xmlDoc.getDocumentElement();
    if (kml.getTagName().equalsIgnoreCase(TAG_kml)) {
      NodeList ResponseList = XMLTools.getChildElements(kml, TAG_Response);
      for (int g = 0; (g < ResponseList.getLength()); g++) {
        Element response = (Element) ResponseList.item(g);
        NodeList responseNodes = response.getChildNodes();
        for (int n = 0; n < responseNodes.getLength(); n++) {
          Node responseNode = responseNodes.item(n);
          if (!(responseNode instanceof Element)) {
            continue;
          }
          Element responseElem = (Element) responseNode;
          String responseNodeName = responseElem.getNodeName();
          if (responseNodeName.equalsIgnoreCase(TAG_name)) {
            // <name>40.479581,-117.773438</name>
          } else if (responseNodeName.equalsIgnoreCase(TAG_Status)) {
            // <Status> ... </Status>
            NodeList statusNodes = responseElem.getChildNodes();
            for (int st = 0; st < statusNodes.getLength(); st++) {
              Node statusNode = statusNodes.item(st);
              if (!(statusNode instanceof Element)) {
                continue;
              }
              Element statusElem = (Element) statusNode;
              String statusNodeName = statusElem.getNodeName();
              if (statusNodeName.equalsIgnoreCase(TAG_code)) {
                errCode = StringTools.trim(GoogleGeocodeV2.GetNodeText(statusElem)); // expect "200"
                break; // we only care about the 'code'
              }
            }
          } else if (responseNodeName.equalsIgnoreCase(TAG_Placemark)) {
            // <Placemark> ... </Placemark>
            String id = responseElem.getAttribute(ATTR_id);
            if ((id != null) && id.equals("p1")) {
              NodeList placemarkNodes = responseElem.getChildNodes();
              for (int pm = 0; pm < placemarkNodes.getLength(); pm++) {
                Node placemarkNode = placemarkNodes.item(pm);
                if (!(placemarkNode instanceof Element)) {
                  continue;
                }
                Element placemarkElem = (Element) placemarkNode;
                String placemarkNodeName = placemarkElem.getNodeName();
                if (placemarkNodeName.equalsIgnoreCase(TAG_address)) {
                  address = GoogleGeocodeV2.GetNodeText(placemarkElem);
                  break; // we only care about the 'address'
                }
              }
            } else {
              // Print.logInfo("Skipping Placemark ID = %s", id);
            }
          }
        }
      }
    }

    /* create address */
    if (FAILOVER_DEBUG) {
      errCode = "620";
    } else if (!StringTools.isBlank(address)) {
      // address found
      ReverseGeocode rg = new ReverseGeocode();
      rg.setFullAddress(address);
      return rg;
    }

    /* check for Google reverse-geocode limit exceeded */
    if ((errCode != null) && errCode.equals("620")) {
      Print.logError("!!!! Google Reverse-Geocode Limit Exceeded [Error 620] !!!!");
      if (this.hasFailoverReverseGeocodeProvider()) {
        this.startReverseGeocodeFailoverMode();
        ReverseGeocodeProvider frgp = this.getFailoverReverseGeocodeProvider();
        Print.logWarn("Failing over to '" + frgp.getName() + "'");
        return frgp.getReverseGeocode(gp, localeStr, cache);
      }
    }

    /* no reverse-geocode available */
    return null;
  }