function getObjectCoordData(ge, kmlObject) {
  var out = {
    'coords': [],
    'altitudeMode': -1,
    'maxAltitude': -9999
  };

  function pushLatLngAlt(lat, lng, alt) {
    out.coords.push({
      lat: lat,
      lng: lng,
      alt: alt
    });

    out.maxAltitude = Math.max(out.maxAltitude, alt);
  }
  
  function pushCoord(pointOrCoord) {
    pushLatLngAlt(pointOrCoord.getLatitude(), pointOrCoord.getLongitude(), pointOrCoord.getAltitude());
  }
  
  function pushChildCall(retval, altitudeMode) {
    out.coords = out.coords.concat(retval.coords);
    out.maxAltitude = Math.max(out.maxAltitude, retval.maxAltitude);
    
    // stop at first altitudeMode, so if it's already set, don't update it
    if (out.altitudeMode == -1)
      out.altitudeMode = retval.altitudeMode;
  }
  
  // extract the points from the given object
  if (kmlObject && 'getType' in kmlObject) {
    if (out.altitudeMode == -1 && 'getAltitudeMode' in kmlObject)
      out.altitudeMode = kmlObject.getAltitudeMode();
    
    switch (kmlObject.getType()) {
      // features
      case 'KmlFolder':
      case 'KmlDocument':
        var children = kmlObject.getFeatures().getChildNodes();
        var numChildren = children.getLength();
        for (var i = 0; i < numChildren; i++)
          pushChildCall(getObjectCoordData(ge, children.item(i)));
        break;
      
      case 'KmlPlacemark':
        if ('getGeometry' in kmlObject)
          return getObjectCoordData(ge, kmlObject.getGeometry());
        break;

      case 'KmlGroundOverlay':
        var latLonBox = kmlObject.getLatLonBox();
        var alt = kmlObject.getAltitude();
        pushLatLngAlt(latLonBox.getNorth(), latLonBox.getEast(), alt);
        pushLatLngAlt(latLonBox.getNorth(), latLonBox.getWest(), alt);
        pushLatLngAlt(latLonBox.getSouth(), latLonBox.getEast(), alt);
        pushLatLngAlt(latLonBox.getSouth(), latLonBox.getWest(), alt);
        break;
      
      // geometries
      case 'KmlMultiGeometry':
        var children = kmlObject.getGeometries().getChildNodes();
        var numChildren = children.getLength();
        for (var i = 0; i < numChildren; i++)
          pushChildCall(getObjectCoordData(ge, children.item(i)));
        break;

      case 'KmlModel':
        pushCoord(kmlObject.getLocation());
        break;
      
      case 'KmlPolygon':
        pushChildCall(getObjectCoordData(ge, kmlObject.getOuterBoundary()));
        break;
      
      case 'KmlLinearRing':
      case 'KmlLineString':
        var coordsObj = kmlObject.getCoordinates();
        var n = coordsObj.getLength();
        for (var i = 0; i < n; i++)
          pushCoord(coordsObj.get(i));
        break;
      
      case 'KmlCoord': // coordinates
      case 'KmlLocation': // models
      case 'KmlPoint': // points
        pushCoord(kmlObject);
        break;
    }
  }
  
  return out;
}

function computeFitLookAt(ge, obj, aspectRatio) {
  var DEGREES = Math.PI / 180;
  var EARTH_RADIUS = 6378137;
  
  var coordData = getObjectCoordData(ge, obj);

  if ('getAbstractView' in obj) {
    var la = obj.getAbstractView();
    if (la != null)
      return la;
  }
  
  if (coordData.coords.length) {
    // range calculation -- the hard part
    var center = null;
    var range = 0.0;
    if (coordData.coords.length == 1) {
      center = new google.maps.LatLng(coordData.coords[0].lat, coordData.coords[0].lng);
      range = 1000;
    } else {
      // compute bbox
      var bounds = new google.maps.LatLngBounds();
      for (var i = 0; i < coordData.coords.length; i++)
        bounds.extend(new google.maps.LatLng(coordData.coords[i].lat, coordData.coords[i].lng));
      
      // find center
      center = bounds.getCenter();
      var sw = bounds.getSouthWest();
      var ne = bounds.getNorthEast();
    
      var lngSpan = new google.maps.LatLng(center.lat(), sw.lng()).
        distanceFrom(new google.maps.LatLng(center.lat(), ne.lng()));
      var latSpan = new google.maps.LatLng(sw.lat(), center.lng()).
        distanceFrom(new google.maps.LatLng(ne.lat(), center.lng()));
    
      if (!aspectRatio)
        aspectRatio = 1.0;
    
      var PAD_FACTOR = 1.5; // add 50% to the computed range for padding
      var beta;
      
      var aspectUse = Math.max(aspectRatio, Math.min(1.0, lngSpan / latSpan));
      var alpha = (45.0 / (aspectUse + 0.4) - 2.0) * DEGREES; // computed experimentally;
      
      // create LookAt using distance formula
      if (lngSpan > latSpan) {
        // polygon is wide
        beta = Math.min(90 * DEGREES, alpha + lngSpan / 2 / EARTH_RADIUS);
      } else {
        // polygon is taller
        beta = Math.min(90 * DEGREES, alpha + latSpan / 2 / EARTH_RADIUS);
      }
    
      range = PAD_FACTOR * EARTH_RADIUS * (Math.sin(beta) *
        Math.sqrt(1 / Math.pow(Math.tan(alpha),2) + 1) - 1);
    }
    
    var la = ge.createLookAt('');
    la.set(center.lat(), center.lng(), coordData.maxAltitude, coordData.altitudeMode, 0, 0, range);
    return la;
  }
  
  return null;
}

