From 025762f73fb3ec42dd7f019a71ada3662d76c721 Mon Sep 17 00:00:00 2001 From: Mikko Korpela Date: Thu, 7 Dec 2017 23:22:26 +0200 Subject: [PATCH] Modified get_openstreetmap() to stitch tiles, ... Stamen maps and OpenStreetMaps are now cached in color, converted to black and white when necessary. get_openstreetmap() now requires a persistent cache directory to be set. get_openstreetmap() now uses a User-Agent string identifying as ggmap. Should check if ggmap/ get_openstreetmap() is now acceptable with respect to OSM tile usage policy https://operations.osmfoundation.org/policies/tiles/. Should probably ask for "permission from the System Administrators". --- DESCRIPTION | 3 +- NAMESPACE | 4 + R/get_openstreetmap.R | 294 +++++++++++++++++++++++---------------- R/get_stamenmap.R | 40 +++--- R/help.R | 4 +- R/helpers.R | 17 +++ man/get_openstreetmap.Rd | 45 +++--- man/get_stamenmap.Rd | 3 +- 8 files changed, 242 insertions(+), 168 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index fc2e510..b7a2915 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,7 +25,8 @@ Imports: digest, scales, dplyr, - bitops + bitops, + curl Suggests: MASS, stringr, diff --git a/NAMESPACE b/NAMESPACE index 9d60d4c..dc58928 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -61,10 +61,13 @@ import(png) import(proto) import(reshape2) import(rjson) +importFrom(curl,curl_download) +importFrom(curl,new_handle) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) importFrom(dplyr,filter) importFrom(grDevices,as.raster) +importFrom(grDevices,col2rgb) importFrom(grDevices,extendrange) importFrom(grDevices,gray) importFrom(grDevices,rgb) @@ -80,4 +83,5 @@ importFrom(stats,asOneSidedFormula) importFrom(stats,time) importFrom(utils,URLencode) importFrom(utils,download.file) +importFrom(utils,packageVersion) importFrom(utils,tail) diff --git a/R/get_openstreetmap.R b/R/get_openstreetmap.R index 1db22c0..661c5c2 100644 --- a/R/get_openstreetmap.R +++ b/R/get_openstreetmap.R @@ -1,42 +1,30 @@ #' Get an OpenStreetMap #' #' \code{get_openstreetmap} accesses a tile server for OpenStreetMap and -#' downloads/formats a map image. This is simply a wrapper for the web-based -#' version at \url{http://www.openstreetmap.org/}. If you don't know how to -#' get the map you want, go there, navigate to the map extent that you want, -#' click the export tab at the top of the page, and copy the information into -#' this function. -#' -# In some cases the OSM server is unavailable, in these cases you will -#' receive an error message from \code{download.file} with the message HTTP status -#' '503 Service Unavailable'. You can confirm this by setting urlonly = TRUE, -#' and then entering the URL in a web browser. the solution is either (1) -#' change sources or (2) wait for the OSM servers to come back up. +#' downloads/stitches map tiles/formats a map image. If you don't know how to +#' get the map you want, go to \url{http://www.openstreetmap.org/}, navigate to +#' the map extent that you want, click the export tab at the top of the page, +#' and copy the information into this function. #' #' See \url{http://www.openstreetmap.org/copyright} for license and copyright #' information. #' +#' As a prerequisite for downloading OpenStreetMap tiles with this function, +#' persistent tile caching must be enabled by setting the +#' \code{"ggmap.file_drawer"} \link[=options]{option}. +#' See \code{\link{file_drawer}}. +#' #' @param bbox a bounding box in the format c(lowerleftlon, lowerleftlat, #' upperrightlon, upperrightlat) -#' @param scale scale parameter, see -#' \url{http://wiki.openstreetmap.org/wiki/MinScaleDenominator}. smaller -#' scales provide a finer degree of detail, where larger scales produce more -#' coarse detail. -#' -#' The scale argument is a tricky number to correctly specify. In most cases, -#' if you get an error when downloading an openstreetmap the error is -#' attributable to an improper scale specification. -#' \code{\link{OSM_scale_lookup}} can help; but the best way to get in the -#' correct range is to go to \url{http://www.openstreetmap.org/}, navigate to -#' the map of interest, click export at the top of the page, click 'map image' -#' and then copy down the scale listed. +#' @param zoom a zoom level +#' @param crop crop raw map tiles to specified bounding box #' @param format character string providing image format - png, jpeg, svg, pdf, #' and ps formats #' @param messaging turn messaging on/off #' @param urlonly return url only -#' @param filename destination file for download (file extension added according -#' to format). Default \code{NULL} means a random \code{\link{tempfile}}. #' @param color color or black-and-white +#' @param force if the map is on file, should a new map be looked +#' up? Use responsibly. #' @param ... ... #' @return a ggmap object (a classed raster object with a bounding box attribute) #' @author David Kahle \email{david.kahle@@gmail.com} @@ -49,6 +37,7 @@ #' # osm servers get overloaded, which can result in #' # erroneous failed checks #' +#' # "ggmap.file_drawer" option must be set #' osm <- get_openstreetmap() #' ggmap(osm) #' @@ -56,8 +45,8 @@ #' get_openstreetmap <- function( bbox = c(left = -95.80204, bottom = 29.38048, right = -94.92313, top = 30.14344), - scale = 606250, format = c('png', 'jpeg', 'svg', 'pdf', 'ps'), messaging = FALSE, - urlonly = FALSE, filename = NULL, color = c('color','bw'), ... + zoom = 10, crop = TRUE, format = c('png', 'jpeg', 'svg', 'pdf', 'ps'), messaging = FALSE, + urlonly = FALSE, color = c('color','bw'), force = FALSE, ... ){ # enumerate argument checking (added in lieu of checkargs function) @@ -73,10 +62,10 @@ get_openstreetmap <- function( } } - if('scale' %in% argsgiven){ - if(!(is.numeric(scale) && length(scale) == 1 && - scale == round(scale) && scale > 0)){ - stop('scale must be a positive integer.', call. = F) + if("zoom" %in% argsgiven){ + if(!(is.numeric(zoom) && length(zoom) == 1 && + zoom == round(zoom) && zoom >= 0 && zoom <= 16)){ + stop("zoom must be a positive integer 0-16, see ?get_openstreetmap.", call. = F) } } @@ -87,15 +76,6 @@ get_openstreetmap <- function( format <- match.arg(format) if(format != 'png') stop('currently only the png format is supported.', call. = F) - if(is.null(filename)){ - destfile <- tempfile(fileext = paste(".", format, sep = "")) - } else{ - filename_stop <- TRUE - if(is.character(filename) && length(filename) == 1) filename_stop <- FALSE - if(filename_stop) stop('improper filename specification, see ?get_openstreetmap.', call. = F) - destfile <- paste(filename, format, sep = '.') - } - # color arg checked by match.arg color <- match.arg(color) @@ -103,118 +83,198 @@ get_openstreetmap <- function( .Deprecated(msg = 'checkargs argument deprecated, args are always checked after v2.1.') } + # determine tiles to get + fourCorners <- expand.grid( + lon = c(bbox["left"], bbox["right"]), + lat = c(bbox["bottom"], bbox["top"]) + ) + fourCorners$zoom <- zoom + row.names(fourCorners) <- c("lowerleft","lowerright","upperleft","upperright") + fourCornersTiles <- apply(fourCorners, 1, function(v) LonLat2XY(v[1],v[2],v[3])) + + xsNeeded <- Reduce(":", sort(unique(as.numeric(sapply(fourCornersTiles, function(df) df$X))))) + ysNeeded <- Reduce(":", sort(unique(as.numeric(sapply(fourCornersTiles, function(df) df$Y))))) + tilesNeeded <- expand.grid(x = xsNeeded, y = ysNeeded) + if(nrow(tilesNeeded) > 40){ + message(paste0(nrow(tilesNeeded), " tiles needed, this may take a while ", + "(try a smaller zoom).")) + } + if(messaging) message(nrow(tilesNeeded), " tiles required.") + if(urlonly) { + # make urls + urls <- sprintf("http://%s.tile.openstreetmap.org/", + sample(letters[1:3], nrow(tilesNeeded), + replace = TRUE)) + urls <- paste0(urls, zoom) + urls <- paste(urls, + apply(tilesNeeded, 1, paste, collapse = "/"), sep = "/") + urls <- paste0(urls, ".png") + return(urls) + } + if (is.null(getOption("ggmap.file_drawer"))) { + stop("option 'ggmap.file_drawer' must be set") + } + uagent <- ggmap_useragent() - # url segments - base_url <- 'http://tile.openstreetmap.org/cgi-bin/export?' - bbox_url <- paste('bbox=', paste(bbox, collapse = ','), sep = '') - scale_url <- paste('scale=', as.integer(scale), sep = '') - format_url <- paste('format=', format, sep = '') + # make list of tiles + listOfTiles <- lapply(split(tilesNeeded, 1:nrow(tilesNeeded)), function(v){ + v <- as.numeric(v) + get_openstreetmap_tile(zoom, v[1], v[2], color, uagent, force = force, + messaging = messaging) + }) - # format url proper - post_url <- paste(bbox_url, scale_url, format_url, sep = '&') - url <- paste(base_url, post_url, sep = '') - url <- URLencode(url) - if(urlonly) return(url) + # stitch tiles together + map <- stitch(listOfTiles) - # read in file - m <- try(download.file(url, destfile = destfile, quiet = !messaging, mode = 'wb'), silent = T) - if(class(m) == 'try-error'){ - stop('map grabbing failed - see details in ?get_openstreetmap.', - call. = FALSE) - } - map <- try(readPNG(destfile), silent = T) - if(class(map) == 'try-error'){ - stop('map grabbing failed - see details in ?get_openstreetmap.', - call. = FALSE) - } - # format file - if(color == 'color'){ - map <- as.raster(apply(map, 2, rgb)) - } else if(color == 'bw'){ - mapd <- dim(map) - map <- gray(.30 * map[,,1] + .59 * map[,,2] + .11 * map[,,3]) - dim(map) <- mapd[1:2] - map <- as.raster(map) - } - class(map) <- c('ggmap','raster') + # format map and return if not cropping + if(!crop) { + # additional map meta-data + attr(map, "source") <- "osm" + attr(map, "maptype") <- "openstreetmap" + attr(map, "zoom") <- zoom - # map spatial info - attr(map, 'bb') <- data.frame( - ll.lat = bbox[2], ll.lon = bbox[1], - ur.lat = bbox[4], ur.lon = bbox[3] - ) - - # additional map meta-data - attr(map, "source") <- "osm" - attr(map, "maptype") <- "openstreetmap" - attr(map, "scale") <- scale + # return + return(map) + } - # return - map -} + # crop map + if(crop){ + mbbox <- attr(map, "bb") + size <- 256 * c(length(xsNeeded), length(ysNeeded)) + # slon is the sequence of lons corresponding to the pixels + # left to right + slon <- seq(mbbox$ll.lon, mbbox$ur.lon, length.out = size[1]) + # slat is the sequence of lats corresponding to the pixels + # bottom to top + # slat is more complicated due to the mercator projection + slat <- vector("double", length = 256*length(ysNeeded)) + for(k in seq_along(ysNeeded)){ + slat[(k-1)*256 + 1:256] <- + sapply(as.list(0:255), function(y){ + XY2LonLat(X = xsNeeded[1], Y = ysNeeded[k], zoom, x = 0, y = y)$lat + }) + } + slat <- rev(slat) + ##slat <- seq(mbbox$ll.lat, mbbox$ur.lat, length.out = size[2]) + keep_x_ndcs <- which(bbox["left"] <= slon & slon <= bbox["right"]) + keep_y_ndcs <- sort( size[2] - which(bbox["bottom"] <= slat & slat <= bbox["top"]) ) + croppedmap <- map[keep_y_ndcs, keep_x_ndcs] + } + # format map + croppedmap <- as.raster(croppedmap) + class(croppedmap) <- c("ggmap","raster") + attr(croppedmap, "bb") <- data.frame( + ll.lat = bbox["bottom"], ll.lon = bbox["left"], + ur.lat = bbox["top"], ur.lon = bbox["right"] + ) + # additional map meta-data + attr(croppedmap, "source") <- "osm" + attr(croppedmap, "maptype") <- "openstreetmap" + attr(croppedmap, "zoom") <- zoom + # return + croppedmap +} +get_openstreetmap_tile <- function(zoom, x, y, color, ua, force = FALSE, + messaging = TRUE) { + # check arguments + is.wholenumber <- function (x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol + stopifnot(is.wholenumber(zoom) || !(zoom %in% 1:16)) + stopifnot(is.wholenumber(x) || !(0 <= x && x < 2^zoom)) + stopifnot(is.wholenumber(y) || !(0 <= y && y < 2^zoom)) + # format url http://[abc].tile.openstreetmap.org/${z}/${x}/${y}.png + url <- sprintf("http://a.tile.openstreetmap.org/%i/%i/%i.png", zoom, x, y) + # lookup in archive + tile <- file_drawer_get(url) + if (!is.null(tile) && !force) { + if (color == "color") { + return(tile) + } else { + return(tile_to_bw(tile)) + } + } + # grab if not in archive + url2 <- sub("//a", paste0("//", sample(letters[1:3], 1)), url, fixed = TRUE) + tmp <- tempfile() + downloaded <- suppressWarnings(try( + curl_download(url2, destfile = tmp, quiet = !messaging, mode = "wb", + handle = new_handle(useragent = ua)), silent = TRUE + )) + + # message url + download_error <- inherits(downloaded, "try-error") + if(download_error) { + message(paste0("Source FAILED : ", url2)) + } else { + message(paste0("Source : ", url2)) + } -get_openstreetmap_checkargs <- function(args){ - eargs <- lapply(args, eval) - argsgiven <- names(args) + # read in/format tile + if (download_error) { - with(eargs,{ + tile <- array(NA, dim = c(256L, 256L)) - # bbox arg - if('bbox' %in% argsgiven){ - if(!(is.numeric(bbox) && length(bbox) == 4)){ - stop('bounding box improperly specified. see ?get_openstreetmap', call. = F) - } - } + } else { - # scale arg - if('scale' %in% argsgiven){ - if(!(is.numeric(scale) && length(scale) == 1 && - scale == round(scale) && scale > 0)){ - stop('scale must be a positive integer.', call. = F) - } - } + # read in + tile <- readPNG(tmp) - # messaging arg - if('messaging' %in% argsgiven){ - stopifnot(is.logical(messaging)) - } + tile <- t(apply(tile, 2, rgb)) + } - # urlonly arg - if('urlonly' %in% argsgiven){ - stopifnot(is.logical(urlonly)) - } - # filename arg - if('filename' %in% argsgiven){ - filename_stop <- TRUE - if(is.character(filename) && length(filename) == 1) style_stop <- FALSE - if(filename_stop) stop('improper filename specification, see ?get_googlemap.', call. = F) - } + # determine bbox of map. note : not the same as the argument bounding box - + # the map is only a covering of the bounding box extent the idea is to get + # the lower left tile and the upper right tile and compute their bounding boxes + # tiles are referenced by top left of tile, starting at 0,0 + # see http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames + lonlat_upperleft <- XY2LonLat(x, y, zoom) + lonlat_lowerright <- XY2LonLat(x, y, zoom, 255, 255) + bbox <- c( + left = lonlat_upperleft$lon, + bottom = lonlat_lowerright$lat, + right = lonlat_lowerright$lon, + top = lonlat_upperleft$lat + ) + bb <- data.frame( + ll.lat = unname(bbox["bottom"]), + ll.lon = unname(bbox["left"]), + ur.lat = unname(bbox["top"]), + ur.lon = unname(bbox["right"]) + ) - # color arg checked by match.arg + # format + class(tile) <- c("ggmap", "raster") + attr(tile, "bb") <- bb + # store + if(!download_error) file_drawer_set(url, tile) - }) # end with + # return + if (color == "color") { + tile + } else { + tile_to_bw(tile) + } } diff --git a/R/get_stamenmap.R b/R/get_stamenmap.R index d109317..4031d10 100644 --- a/R/get_stamenmap.R +++ b/R/get_stamenmap.R @@ -15,8 +15,7 @@ #' @param crop crop raw map tiles to specified bounding box #' @param messaging turn messaging on/off #' @param urlonly return url only -#' @param color color or black-and-white (use force = TRUE if you've -#' already downloaded the images) +#' @param color color or black-and-white #' @param force if the map is on file, should a new map be looked #' up? #' @param where where should the file drawer be located (without @@ -236,7 +235,7 @@ get_stamenmap <- function( if("zoom" %in% argsgiven){ if(!(is.numeric(zoom) && length(zoom) == 1 && zoom == round(zoom) && zoom >= 0 && zoom <= 18)){ - stop("scale must be a positive integer 0-18, see ?get_stamenmap.", call. = F) + stop("zoom must be a positive integer 0-18, see ?get_stamenmap.", call. = F) } } @@ -451,11 +450,19 @@ get_stamenmap_tile <- function(maptype, zoom, x, y, color, force = FALSE, messag } else { filetype <- "png" } + bw_types <- c("toner-hybrid", "toner-labels", "toner-lines", + "terrain-labels", "terrain-lines") url <- sprintf("http://tile.stamen.com/%s/%i/%i/%i.%s", maptype, zoom, x, y, filetype) # lookup in archive tile <- file_drawer_get(url) - if (!is.null(tile) && !force) return(tile) + if (!is.null(tile) && !force) { + if (color == "color" || maptype %in% bw_types) { + return(tile) + } else { + return(tile_to_bw(tile)) + } + } # grab if not in archive tmp <- tempfile() @@ -487,22 +494,11 @@ get_stamenmap_tile <- function(maptype, zoom, x, y, color, force = FALSE, messag # convert to colors # toner-lines treated differently for alpha - if(maptype %in% c("toner-hybrid", "toner-labels", "toner-lines", - "terrain-labels", "terrain-lines")){ - if(color == "color") { - tile <- t(apply(tile, 1:2, function(x) rgb(x[1], x[2], x[3], x[4]))) - } else { # color == "bw" (all these are black and white naturally) - tile <- t(apply(tile, 1:2, function(x) rgb(x[1], x[2], x[3], x[4]))) - } + if(maptype %in% bw_types) { + # (all these are black and white naturally) + tile <- t(apply(tile, 1:2, function(x) rgb(x[1], x[2], x[3], x[4]))) } else { - if(color == "color") { - tile <- t(apply(tile, 2, rgb)) - } else { # color == "bw" - tiled <- dim(tile) - tile <- gray(.30 * tile[,,1] + .59 * tile[,,2] + .11 * tile[,,3]) - dim(tile) <- tiled[1:2] - tile <- t(tile) - } + tile <- t(apply(tile, 2, rgb)) } } @@ -536,7 +532,11 @@ get_stamenmap_tile <- function(maptype, zoom, x, y, color, force = FALSE, messag if(!download_error) file_drawer_set(url, tile) # return - tile + if (color == "color" || maptype %in% bw_types) { + tile + } else { + tile_to_bw(tile) + } } diff --git a/R/help.R b/R/help.R index 52a5fef..3141ea5 100644 --- a/R/help.R +++ b/R/help.R @@ -2,11 +2,13 @@ #' jpeg geosphere bitops #' @docType package #' @name ggmap -#' @importFrom grDevices as.raster extendrange gray rgb +#' @importFrom grDevices as.raster extendrange gray rgb col2rgb #' @importFrom stats time asOneSidedFormula #' @importFrom utils URLencode download.file tail #' @importFrom grid rasterGrob seekViewport grid.locator upViewport downViewport current.vpTree current.vpPath #' @importFrom scales expand_range #' @importFrom dplyr bind_cols filter bind_rows +#' @importFrom utils packageVersion +#' @importFrom curl curl_download new_handle #' @aliases ggmap package-ggmap NULL diff --git a/R/helpers.R b/R/helpers.R index 2e3c4b2..533cc17 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -10,3 +10,20 @@ fmteq <- function (x, f = function(.) ., ...) { paste0(deparse(substitute(x)), "=", f(x, ...)) } + +# convert color tile (as cached in the drawer) to grayscale +tile_to_bw <- function(color_tile) { + bw <- col2rgb(color_tile) + bw <- gray((0.30 * bw[1, ] + 0.59 * bw[2, ] + 0.11 * bw[3, ]) / 255) + attributes(bw) <- attributes(color_tile) + bw +} + +# HTTP User-Agent string. Can be used with curl_download(). +ggmap_useragent <- function() { + paste0("ggmap/", packageVersion("ggmap"), + sprintf(" R/%s.%s (%s)", + R.version[["major"]], + R.version[["minor"]], + R.version[["platform"]])) +} diff --git a/man/get_openstreetmap.Rd b/man/get_openstreetmap.Rd index e9151c7..4718cfb 100644 --- a/man/get_openstreetmap.Rd +++ b/man/get_openstreetmap.Rd @@ -5,26 +5,17 @@ \title{Get an OpenStreetMap} \usage{ get_openstreetmap(bbox = c(left = -95.80204, bottom = 29.38048, right = - -94.92313, top = 30.14344), scale = 606250, format = c("png", "jpeg", - "svg", "pdf", "ps"), messaging = FALSE, urlonly = FALSE, - filename = NULL, color = c("color", "bw"), ...) + -94.92313, top = 30.14344), zoom = 10, crop = TRUE, format = c("png", + "jpeg", "svg", "pdf", "ps"), messaging = FALSE, urlonly = FALSE, + color = c("color", "bw"), force = FALSE, ...) } \arguments{ \item{bbox}{a bounding box in the format c(lowerleftlon, lowerleftlat, upperrightlon, upperrightlat)} -\item{scale}{scale parameter, see - \url{http://wiki.openstreetmap.org/wiki/MinScaleDenominator}. smaller - scales provide a finer degree of detail, where larger scales produce more - coarse detail. +\item{zoom}{a zoom level} - The scale argument is a tricky number to correctly specify. In most cases, - if you get an error when downloading an openstreetmap the error is - attributable to an improper scale specification. - \code{\link{OSM_scale_lookup}} can help; but the best way to get in the - correct range is to go to \url{http://www.openstreetmap.org/}, navigate to - the map of interest, click export at the top of the page, click 'map image' - and then copy down the scale listed.} +\item{crop}{crop raw map tiles to specified bounding box} \item{format}{character string providing image format - png, jpeg, svg, pdf, and ps formats} @@ -33,11 +24,11 @@ and ps formats} \item{urlonly}{return url only} -\item{filename}{destination file for download (file extension added according -to format). Default \code{NULL} means a random \code{\link{tempfile}}.} - \item{color}{color or black-and-white} +\item{force}{if the map is on file, should a new map be looked +up? Use responsibly.} + \item{...}{...} } \value{ @@ -45,20 +36,19 @@ a ggmap object (a classed raster object with a bounding box attribute) } \description{ \code{get_openstreetmap} accesses a tile server for OpenStreetMap and -downloads/formats a map image. This is simply a wrapper for the web-based -version at \url{http://www.openstreetmap.org/}. If you don't know how to -get the map you want, go there, navigate to the map extent that you want, -click the export tab at the top of the page, and copy the information into -this function. +downloads/stitches map tiles/formats a map image. If you don't know how to +get the map you want, go to \url{http://www.openstreetmap.org/}, navigate to +the map extent that you want, click the export tab at the top of the page, +and copy the information into this function. } \details{ -receive an error message from \code{download.file} with the message HTTP status -'503 Service Unavailable'. You can confirm this by setting urlonly = TRUE, -and then entering the URL in a web browser. the solution is either (1) -change sources or (2) wait for the OSM servers to come back up. - See \url{http://www.openstreetmap.org/copyright} for license and copyright information. + +As a prerequisite for downloading OpenStreetMap tiles with this function, +persistent tile caching must be enabled by setting the +\code{"ggmap.file_drawer"} \link[=options]{option}. +See \code{\link{file_drawer}}. } \examples{ @@ -67,6 +57,7 @@ get_openstreetmap(urlonly = TRUE) # osm servers get overloaded, which can result in # erroneous failed checks +# "ggmap.file_drawer" option must be set osm <- get_openstreetmap() ggmap(osm) diff --git a/man/get_stamenmap.Rd b/man/get_stamenmap.Rd index 5cc8c02..d05b1fe 100644 --- a/man/get_stamenmap.Rd +++ b/man/get_stamenmap.Rd @@ -29,8 +29,7 @@ watercolor.} \item{urlonly}{return url only} -\item{color}{color or black-and-white (use force = TRUE if you've -already downloaded the images)} +\item{color}{color or black-and-white} \item{force}{if the map is on file, should a new map be looked up?}