rodazuero / gmapsdistance

Future
GNU General Public License v3.0
75 stars 40 forks source link

Distance Matrix Advanced used instead of standard Distance Matrix #54

Open kieranzu opened 5 years ago

kieranzu commented 5 years ago

I have encountered an issue where trying to do a standard API call results in google thinking the call is for the advanced distance matrix and thus charging twice as much. Having spoken to google maps support and reviewed your package documentation I am unclear as to why this is. I wondered if you could shed some light on this. If there is no time or traffic condition stated does it use a default value. If so is this causing the issue? Below is an example of the code that will cause the issue to arise.

PostCodes <- c("HG14TB", "HG29DH", "HG33BS", "HG42RN")
Results<- gmapsdistance(origin = PostCodes,
              destination = "LS97TF",
              mode = "driving",
              key = "YOURAPIKEY")
kieranzu commented 5 years ago

I have identified what the issue here is. The current Google Distance Matrix API will be billed at $0.01 for an advanced API call and $0.005 for a standard call. If no date/time/traffic model is provided the gmapsdistance function will send the API call with Traffic model = "None" and the current time and date. The inclusion of a traffic model and specified time and date however is classified as an advanced call and so is billed at twice the price. A simple api call requires simply the origin, destination, mode of travel and API Key. This then is billed at the lower tariff. I have changed the function to include fewer variables to ensure it results in only an standard API call. I suggest that this is either incorporated as a separate function in the package or there is a way to specify whether a simple or advance API call is required withing the gmapsdistance function.

Also note that the "&sensorfalse element of the api call is now also defunct

require("pacman")

p_load("dplyr",
       "gmapsdistance",
       "RODBC",
       "data.table",
       "XML",
       "RCurl")

gmapsdistancesimple = function(origin, destination, combinations = "all", mode, key = get.api.key(), shape = "wide") {

  # If mode of transportation not recognized:
  if (!(mode %in% c("driving",  "walking",  "bicycling",  "transit"))) {
    stop(
      "Mode of transportation not recognized. Mode should be one of ",
      "'bicycling', 'transit', 'driving', 'walking' "
    )
  }

  # If combinations not recognized:
  if (!(combinations %in% c("all",  "pairwise"))) {
    stop(
      "Combinations between origin and destination not recognized. Combinations should be one of ",
      "'all', 'pairwise' "
    )
  }

  if(combinations == "pairwise" && length(origin) != length(destination)){
    stop("Size of origin and destination vectors must be the same when using the option: combinations == 'pairwise'")
  }

  if(combinations == "all"){
    data = expand.grid(or = origin, de = destination)
  } else if(combinations == "pairwise"){
    data = data.frame(or = origin, de = destination)
  }

  n = dim(data)
  n = n[1]

  data$Time = NA
  data$Distance = NA
  data$status = "OK"

   for (i in 1:1:n){

    # Set up URL
    url = paste0("maps.googleapis.com/maps/api/distancematrix/xml?origins=", data$or[i],
                 "&destinations=", data$de[i],
                 "&mode=", mode,
                 "&units=metric")

    # Add Google Maps API key if it exists
    if (!is.null(key)) {
      # use https and google maps key (after replacing spaces just in case)
      key = gsub(" ", "", key)
      url = paste0("https://", url, "&key=", key)
    } else {
      # use http otherwise
      url = paste0("http://", url)
    }

    # Call the Google Maps Webservice and store the XML output in webpageXML
    webpageXML = xmlParse(getURL(url));

    # Extract the results from webpageXML
    results = xmlChildren(xmlRoot(webpageXML))

    # Check the status of the request and throw an error if the request was denied
    request.status = as(unlist(results$status[[1]]), "character")

    # Check for google API errors
    if (!is.null(results$error_message)) {
      stop(paste(c("Google API returned an error: ", xmlValue(results$error_message)), sep = ""))
    }

    if (request.status == "REQUEST_DENIED") {
      set.api.key(NULL)
      data$status[i] = "REQUEST_DENIED"
      # stop(as(results$error_message[1]$text, "character"))
    }

    # Extract results from results$row
    rowXML = xmlChildren(results$row[[1L]])
    Status = as(rowXML$status[1]$text, "character")

    if (Status == "ZERO_RESULTS") {
      # stop(paste0("Google Maps is not able to find a route between ", data$or[i]," and ", data$de[i]))
      data$status[i] = "ROUTE_NOT_FOUND"
    }

    if (Status == "NOT_FOUND") {
      # stop("Google Maps is not able to find the origin (", data$or[i],") or destination (", data$de[i], ")")
      data$status[i] = "PLACE_NOT_FOUND"
    }

    # Check whether the user is over their query limit
    if (Status == "OVER_QUERY_LIMIT") {
      stop("You have exceeded your allocation of API requests for today.")
    }

    if(data$status[i] == "OK"){
      data$Distance[i] = as(rowXML$distance[1]$value[1]$text, "numeric")
      data$Time[i] = as(rowXML[['duration']][1L]$value[1L]$text, "numeric")
    }
  }

  datadist = data[c("or", "de", "Distance")]
  datatime = data[c("or", "de", "Time")]
  datastat = data[c("or", "de", "status")]

  if(n > 1){
    if(shape == "wide" && combinations == "all"){
      Distance = reshape(datadist,
                         timevar = "de",
                         idvar = c("or"),
                         direction = "wide")

      Time = reshape(datatime,
                     timevar = "de",
                     idvar = c("or"),
                     direction = "wide")

      Stat = reshape(datastat,
                     timevar = "de",
                     idvar = c("or"),
                     direction = "wide")

    } else{
      Distance = datadist
      Time = datatime
      Stat = datastat
    }

  } else{
    Distance = data$Distance[i]
    Time = data$Time[i]
    Stat = data$status[i]
  }

  # Make a list with the results
  output = list(
    Time = Time,
    Distance = Distance,
    Status = Stat
  )

  return(output)
}
jlacko commented 2 years ago

this issue has been resolved in version 4.0.0 / on CRAN as of 2022-04-26; the cheaper call is given priority whenever possible