From 34c566cbb8613aa3e2c87fcf85034a6e74e26b04 Mon Sep 17 00:00:00 2001 From: Richard Beare Date: Tue, 1 Nov 2016 18:46:31 +1100 Subject: [PATCH 1/4] first pass at signed URL --- DESCRIPTION | 4 +++- R/geocode.R | 15 +++++++++------ R/get_googlemap.R | 12 ++++++++---- R/mapdist.R | 12 +++++++++--- R/revgeocode.R | 12 +++++++++--- R/route.R | 11 ++++++++--- R/trek.R | 11 ++++++++--- R/urlsigning.R | 15 +++++++++++++++ 8 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 R/urlsigning.R diff --git a/DESCRIPTION b/DESCRIPTION index c2fd09f..6b0b210 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,7 +25,9 @@ Imports: digest, scales, dplyr, - bitops + bitops, + base64enc, + urltools Suggests: MASS, stringr, diff --git a/R/geocode.R b/R/geocode.R index 27b7a01..8355eac 100644 --- a/R/geocode.R +++ b/R/geocode.R @@ -99,8 +99,6 @@ geocode <- function(location, output = c("latlon", "latlona", "more", "all"), nameType <- match.arg(nameType) source <- match.arg(source) - - # vectorize for many locations if(length(location) > 1){ # set limit @@ -135,13 +133,15 @@ geocode <- function(location, output = c("latlon", "latlona", "more", "all"), # start constructing the url posturl <- URLencode(location, reserved = TRUE) + NeedToSign <- FALSE if(source == "google"){ # add google account stuff if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - posturl <- paste(posturl, fmteq(client), fmteq(signature), sep = "&") + #signature <- goog_signature() + posturl <- paste(posturl, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() posturl <- paste(posturl, fmteq(key), sep = "&") @@ -165,8 +165,11 @@ geocode <- function(location, output = c("latlon", "latlona", "more", "all"), if(urlonly) return(url_string) url_hash <- digest::digest(url_string) - - + if (NeedToSign) { + # Sign if we are using google client and digital signature + url_string <- signurl(url_string, secret=goog_signature()) + } + # lookup info if on file if(isGeocodedInformationOnFile(url_hash) && force == FALSE){ diff --git a/R/get_googlemap.R b/R/get_googlemap.R index d5904a3..0fa706a 100644 --- a/R/get_googlemap.R +++ b/R/get_googlemap.R @@ -132,7 +132,6 @@ get_googlemap <- function( ##### do argument checking ############################################################ - args <- as.list(match.call(expand.dots = TRUE)[-1]) argsgiven <- names(args) @@ -304,10 +303,11 @@ get_googlemap <- function( sep = "&") # add google account stuff + NeedToSign <- FALSE if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - post_url <- paste(post_url, fmteq(client), fmteq(signature), sep = "&") + post_url <- paste(post_url, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() post_url <- paste(post_url, fmteq(key), sep = "&") @@ -326,7 +326,11 @@ get_googlemap <- function( if(urlonly) return(url) if(nchar(url) > 2048) stop("max url length is 2048 characters.", call. = FALSE) - + if (NeedToSign) { + # Sign if we are using google client and digital signature + url <- signurl(url, secret=goog_signature()) + } + ##### get map ############################################################ diff --git a/R/mapdist.R b/R/mapdist.R index f6cadcf..e9e93d3 100644 --- a/R/mapdist.R +++ b/R/mapdist.R @@ -89,10 +89,11 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" ) # add google account stuff + NeedToSign <- FALSE if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - posturl <- paste(posturl, fmteq(client), fmteq(signature), sep = "&") + posturl <- paste(posturl, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() posturl <- paste(posturl, fmteq(key), sep = "&") @@ -111,7 +112,12 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" url_string <- URLencode( enc2utf8(url_string) ) if(urlonly) return(url_string) - # check if query is too long + if (NeedToSign) { + # Sign if we are using google client and digital signature + url_string <- signurl(url_string, secret=goog_signature()) + } + # check if query is too long - not sure if the signature is included + # in the maximum length - sign before check to be sure. if(nchar(url_string) >= 2048){ n <- nrow(df) half_df <- floor(n/2) diff --git a/R/revgeocode.R b/R/revgeocode.R index 738c9a5..4b4126f 100644 --- a/R/revgeocode.R +++ b/R/revgeocode.R @@ -51,10 +51,11 @@ revgeocode <- function(location, output = c("address","more","all"), ) # do google credentials + NeedToSign <- FALSE if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - url_string <- paste(url_string, fmteq(client), fmteq(signature), sep = "&") + url_string <- paste(url_string, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() url_string <- paste(url_string, fmteq(key), sep = "&") @@ -67,7 +68,12 @@ revgeocode <- function(location, output = c("address","more","all"), url_string <- URLencode( enc2utf8(url_string) ) if(urlonly) return(url_string) - # check/update google query limit + if (NeedToSign) { + # Sign if we are using google client and digital signature + url_string <- signurl(url_string, secret=goog_signature()) + } + + # check/update google query limit check <- checkGeocodeQueryLimit(url_string, elems = 1, override = override_limit, messaging = messaging) if(check == "stop"){ diff --git a/R/route.R b/R/route.R index 1ae9bd1..603b2d2 100644 --- a/R/route.R +++ b/R/route.R @@ -87,10 +87,12 @@ route <- function(from, to, mode = c("driving","walking","bicycling", "transit") ) # add google account stuff + NeedToSign <- FALSE if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - posturl <- paste(posturl, fmteq(client), fmteq(signature), sep = "&") + #signature <- goog_signature() + posturl <- paste(posturl, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() posturl <- paste(posturl, fmteq(key), sep = "&") @@ -112,7 +114,10 @@ route <- function(from, to, mode = c("driving","walking","bicycling", "transit") # check/update google query limit check_route_query_limit(url_string, elems = 1, override = override_limit, messaging = messaging) - + if (NeedToSign) { + # Sign if we are using google client and digital signature + url_string <- signurl(url_string, secret=goog_signature()) + } # distance lookup if(messaging) message("trying url ", url_string) connect <- url(url_string); on.exit(close(connect), add = TRUE) diff --git a/R/trek.R b/R/trek.R index 848a473..2d354dd 100644 --- a/R/trek.R +++ b/R/trek.R @@ -114,10 +114,11 @@ trek <- function(from, to, mode = c("driving","walking","bicycling", "transit"), ) # add google account stuff + NeedToSign <- FALSE if (has_goog_client() && has_goog_signature()) { + NeedToSign <- TRUE client <- goog_client() - signature <- goog_signature() - posturl <- paste(posturl, fmteq(client), fmteq(signature), sep = "&") + posturl <- paste(posturl, fmteq(client), sep = "&") } else if (has_goog_key()) { key <- goog_key() posturl <- paste(posturl, fmteq(key), sep = "&") @@ -141,7 +142,11 @@ trek <- function(from, to, mode = c("driving","walking","bicycling", "transit"), check_route_query_limit(url_string, elems = 1, override = override_limit, messaging = messaging) - + if (NeedToSign) { + # Sign if we are using google client and digital signature + url_string <- signurl(url_string, secret=goog_signature()) + } + # distance lookup if(messaging) message("trying url ", url_string) connect <- url(url_string) diff --git a/R/urlsigning.R b/R/urlsigning.R new file mode 100644 index 0000000..ce1ca1f --- /dev/null +++ b/R/urlsigning.R @@ -0,0 +1,15 @@ + +signurl <- function(input_url, secret) +{ + secret.safe <- chartr("-_", "+/", secret) + decoded_key <- base64enc::base64decode(secret.safe) + # break up the url + urlparsed <- urltools::url_parse(input_url) + url_to_sign <- paste0("/", urlparsed$path, "?", urlparsed$parameter) + signature <- digest::hmac(decoded_key, url_to_sign, algo="sha1", raw=TRUE) + enc1 <- base64enc::base64encode(signature, linewidth=NA) + urlsafesig <- chartr("+/", "-_", enc1) + originalurl <- urltools::url_compose(urlparsed) + finalurl <- paste0(originalurl, "&signature=", urlsafesig) + return(finalurl) +} From 35a838865841ca2ff39f0c4949fe0cd6b3ed042c Mon Sep 17 00:00:00 2001 From: Richard Beare Date: Tue, 1 Nov 2016 21:10:28 +1100 Subject: [PATCH 2/4] docs for signurl --- R/urlsigning.R | 9 ++++++++- man/signurl.Rd | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 man/signurl.Rd diff --git a/R/urlsigning.R b/R/urlsigning.R index ce1ca1f..fbfec38 100644 --- a/R/urlsigning.R +++ b/R/urlsigning.R @@ -1,4 +1,11 @@ - +#' Sign a url using a google digital signature +#' @author Richard Beare +#' @param input_url The url to be signed. Should include a client field. +#' @param secret The private key +#' @return The signed url +#' @details Derived from the python urlsigner: +#' https://raw.githubusercontent.com/googlemaps/js-v2-samples/61b3f58eb1286a428843f8401048226b8648a76b/urlsigning/urlsigner.py +#' signurl <- function(input_url, secret) { secret.safe <- chartr("-_", "+/", secret) diff --git a/man/signurl.Rd b/man/signurl.Rd new file mode 100644 index 0000000..ae1c19a --- /dev/null +++ b/man/signurl.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/urlsigning.R +\name{signurl} +\alias{signurl} +\title{Sign a url using a google digital signature} +\usage{ +signurl(input_url, secret) +} +\arguments{ +\item{input_url}{The url to be signed. Should include a client field.} + +\item{secret}{The private key} +} +\value{ +The signed url +} +\description{ +Sign a url using a google digital signature +} +\details{ +Derived from the python urlsigner: +https://raw.githubusercontent.com/googlemaps/js-v2-samples/61b3f58eb1286a428843f8401048226b8648a76b/urlsigning/urlsigner.py +} +\author{ +Richard Beare +} + From 26d9acca45173c99a37d22957207ab4a298bc42a Mon Sep 17 00:00:00 2001 From: Richard Beare Date: Wed, 2 Nov 2016 14:21:42 +1100 Subject: [PATCH 3/4] started using the mapdist interface in blocks, rather than one to/from pair at a time --- R/mapdist.R | 99 +++++++++++++++++++++++++++----------------------- man/mapdist.Rd | 5 ++- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/R/mapdist.R b/R/mapdist.R index e9e93d3..b7c04e0 100644 --- a/R/mapdist.R +++ b/R/mapdist.R @@ -17,6 +17,8 @@ #' (.GoogleDistQueryCount) #' @param ext domain extension (e.g. "com", "co.nz") #' @param inject character string to add to the url +#' @param usingPlaceIDs indicate that from and to fields contain placeIDs. +#' Turns of URL encoding of addresses. #' @param ... ... #' @return a data frame (output="simple") or all of the geocoded #' information (output="all") @@ -61,7 +63,7 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit"), output = c("simple","all"), messaging = FALSE, language = "en-EN", urlonly = FALSE, override_limit = FALSE, - ext = "com", inject = "", ...) + ext = "com", inject = "", usingPlaceIDs=FALSE, ...) { # check parameters @@ -69,21 +71,28 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" stopifnot(is.character(from)) if(is.numeric(to) && length(to) == 2) to <- revgeocode(to) stopifnot(is.character(to)) - from_to_df <- data.frame(from = from, to = to, stringsAsFactors = FALSE) - origins <- from_to_df$from - destinations <- from_to_df$to # this ensures # from = # to + + # from_to_df <- data.frame(from = from, to = to, stringsAsFactors = FALSE) + # Don't need to ensure that # from = # to - that happens in google + origins <- from + destinations <- to # this ensures # from = # to + mode <- match.arg(mode) output <- match.arg(output) stopifnot(is.logical(messaging)) - getdists <- function(df){ - + getdists <- function(From, To){ # format basic url - origins <- URLencode(df$from[1], reserved = TRUE) - destinations <- URLencode(df$to, reserved = TRUE) + if (usingPlaceIDs) { + origins <- From + destinations <- To + } else { + origins <- sapply(From, URLencode, reserved = TRUE) + destinations <- sapply(To, URLencode, reserved = TRUE) + } posturl <- paste( - fmteq(origins), fmteq(destinations, paste, collapse = "|"), + fmteq(origins, paste, collapse = "|"), fmteq(destinations, paste, collapse = "|"), fmteq(mode), fmteq(language), sep = "&" ) @@ -107,7 +116,6 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" # inject if(inject != "") url_string <- paste(url_string, inject, sep = "&") - # encode url_string <- URLencode( enc2utf8(url_string) ) if(urlonly) return(url_string) @@ -117,8 +125,8 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" url_string <- signurl(url_string, secret=goog_signature()) } # check if query is too long - not sure if the signature is included - # in the maximum length - sign before check to be sure. - if(nchar(url_string) >= 2048){ + # in the maximum length - sign before check to be surTT$e. + if(nchar(url_string) >= 8192){ n <- nrow(df) half_df <- floor(n/2) return( @@ -129,8 +137,8 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" ) } - # check/update google query limit - check_dist_query_limit(url_string, elems = nrow(df), + # check/update google query limit - this is a single query + check_dist_query_limit(url_string, elems = 1, override = override_limit, messaging = messaging) @@ -140,50 +148,51 @@ mapdist <- function(from, to, mode = c("driving","walking","bicycling","transit" tree <- fromJSON(paste(readLines(connect), collapse = "")) check_google_for_error(tree) - # message user message(paste0("Source : ", url_string)) - # label destinations - first check if all were found - if(length(df$to) != length(tree$destination_addresses)){ + if(length(To) != length(tree$destination_addresses)){ message("matching was not perfect, returning what was found.") - names( tree$rows[[c(1,1)]] ) <- tree$destination_addresses output <<- "all" - # stringdist::amatch(df$to, tree$destination_addresses, maxDist = 10) - } else { - names( tree$rows[[c(1,1)]] ) <- df$to - } - - # return - tree$rows[[c(1,1)]] + } + if(length(From) != length(tree$origin_addresses)){ + message("matching was not perfect, returning what was found.") + output <<- "all" + } + return(tree) } - out <- dlply(from_to_df, "from", getdists) + out <- getdists(origins, destinations) # return all if(output == "all") return(out) - - - + + # out$rows has length(from) + # out$rows[[j]]$elements has length(to) # format output - out <- - ldply(out, function(oneFromList){ - ldply(oneFromList, function(oneToList){ - data.frame( - m = oneToList$distance$value, - km = oneToList$distance$value/1000, - miles = 0.0006214 * oneToList$distance$value, - seconds = oneToList$duration$value, - minutes = oneToList$duration$value / 60, - hours = oneToList$duration$value / 3600 - ) - }) + out_df <- plyr::ldply(out$rows, function(aToList){ + res <- plyr::ldply(aToList[[1]], function(oneToList){ + data.frame( + m = oneToList$distance$value, + km = oneToList$distance$value/1000, + miles = 0.0006214 * oneToList$distance$value, + seconds = oneToList$duration$value, + minutes = oneToList$duration$value / 60, + hours = oneToList$duration$value / 3600 + ) }) - - names(out) <- c("from", "to", names(out)[3:ncol(out)]) - + return(res) + }) + + destadd <- rep(out$destination_addresses, length(out$origin_addresses)) + originadd <- rep(out$origin_address, rep(length(out$destination_addresses), length(out$origin_addresses))) + + tos <- rep(to, length(from)) + froms <- rep(from, rep(length(to), length(from))) + + from_to_df <- data.frame(destination.address=destadd, origin.address=originadd, from=froms, to=tos) # "simple" return - suppressMessages(join(from_to_df, out)) + suppressMessages(cbind(from_to_df, out_df)) } diff --git a/man/mapdist.Rd b/man/mapdist.Rd index ecbb2fe..3d01527 100644 --- a/man/mapdist.Rd +++ b/man/mapdist.Rd @@ -7,7 +7,7 @@ mapdist(from, to, mode = c("driving", "walking", "bicycling", "transit"), output = c("simple", "all"), messaging = FALSE, language = "en-EN", urlonly = FALSE, override_limit = FALSE, ext = "com", inject = "", - ...) + usingPlaceIDs = FALSE, ...) } \arguments{ \item{from}{name of origin addresses in a data frame (vector @@ -33,6 +33,9 @@ accepted)} \item{inject}{character string to add to the url} +\item{usingPlaceIDs}{indicate that from and to fields contain placeIDs. +Turns of URL encoding of addresses.} + \item{...}{...} } \value{ From 7b5cbc174a4238ccc187828dd752fdc7e9e085e9 Mon Sep 17 00:00:00 2001 From: Richard Beare Date: Wed, 2 Nov 2016 14:26:24 +1100 Subject: [PATCH 4/4] forgot to save something --- R/mapdist.R | 3 +++ man/mapdist.Rd | 3 +++ 2 files changed, 6 insertions(+) diff --git a/R/mapdist.R b/R/mapdist.R index b7c04e0..e646969 100644 --- a/R/mapdist.R +++ b/R/mapdist.R @@ -26,6 +26,9 @@ #' @details if parameters from and to are specified as geographic #' coordinates, they are reverse geocoded with revgeocode. note #' that the google maps api limits to 2500 element queries a day. +#' +#' mapdist now makes a single query to the mapdist api. The returned +#' data frame will have length(from)*length(to) rows #' @seealso #' \url{http://code.google.com/apis/maps/documentation/distancematrix/} #' @export diff --git a/man/mapdist.Rd b/man/mapdist.Rd index 3d01527..57290fa 100644 --- a/man/mapdist.Rd +++ b/man/mapdist.Rd @@ -51,6 +51,9 @@ Terms of Service at https://developers.google.com/maps/terms. if parameters from and to are specified as geographic coordinates, they are reverse geocoded with revgeocode. note that the google maps api limits to 2500 element queries a day. + + mapdist now makes a single query to the mapdist api. The returned + data frame will have length(from)*length(to) rows } \examples{