World Bank Survey Solutions supports offline satellite basemaps and vector boundary overlays on Interviewer and Supervisor tablets (powered by the ArcGIS Maps SDK for .NET v200.x from Survey Solutions v24.09 onwards).
However, preparing satellite imagery and vector files that pass Survey Solutions Headquarters server-side validation (Survey Setup -> Maps -> Upload Map) and render accurately on offline Android tablets requires strict adherence to Esri and Survey Solutions packaging conventions:
| Resource Type | Format Uploaded to Survey Solutions HQ | Required Coordinate System | Key Survey Solutions Requirements |
|---|---|---|---|
| Zoomable Tile Basemap | .tpk inside a flat .zip (<name>_tpk.zip) |
Web Mercator (WKID: 102100 / EPSG:3857) |
Must contain v101/Layers/conf.xml, conf.cdi, esriinfo/iteminfo.xml, and servicedescriptions/mapserver/mapserver.json with a top-level "contents" object (fullExtent, minScale, maxScale). |
| Single-Resolution Raster Basemap | .tif inside a flat .zip |
Web Mercator (EPSG:3857 / WGS 84) |
Valid GeoTIFF header tags (GeoTiffInfoReader); automatically reprojected to EPSG:3857 by vantor_offline_package(). |
| Vector Boundary / Point Overlay | .shp, .shx, .dbf, .prj inside a flat .zip (<name>_shapes.zip) |
Geographic WGS 84 (EPSG:4326) |
Mandatory .shp, .shx, .dbf at the archive root; an optional label attribute column is rendered automatically as feature labels on the tablet map. |
| Assignment Preload Table | Tab-delimited .tab (<name>_suso_preload.tab) |
Geographic WGS 84 (EPSG:4326) |
Composite <gps_var>__Latitude & <gps_var>__Longitude columns for Map Dashboard household pins, _target_area (from cell_id), plus_code for offline cover-page routing, and imagery_acq_date. |
vantorr handles all of the upload-requirements automatically in pure R—without requiring ArcGIS Pro, ArcMap, or ArcGIS Online credits—using vantor_write_tpk(), vantor_export_capi(), and vantor_offline_package(), and integrates directly with the SurveySolutionsAPIv2 package for programmatic server uploads and assignment creation.
.tab Assignment PreloadsSurvey Solutions Headquarters requires tab-delimited (.tab) preload files with specific column naming conventions:
<gps_var>__Latitude and <gps_var>__Longitude: To display sampled households as interactive pins on the Interviewer tablet’s Map Dashboard (overlaid directly on top of the offline .tpk basemap), your questionnaire in Survey Solutions Designer should include an Identifying Geography (GPS) question (e.g., variable name hh_gps). Survey Solutions preloads GPS questions via composite column headers separated by a double underscore (__): hh_gps__Latitude, hh_gps__Longitude, hh_gps__Accuracy, hh_gps__Altitude, and hh_gps__Timestamp._target_area: Survey Solutions’ system column (up to 64 characters) that groups assignments by geographic sector (cell_id, e.g., "A1", "B2") on the Interviewer Map Dashboard.plus_code & imagery_acq_date: Preloaded as Identifying text questions on the questionnaire cover page. Because .tpk satellite basemaps do not perform turn-by-turn road routing, enumerators can copy the 11-character Open Location Code (plus_code) from the cover page into Google Maps or OSM to get offline turn-by-turn driving/walking directions to within 3 meters of the dwelling in case they are not using preloaded GPS coordinates, then switch back to the Survey Solutions Map Dashboard and .tpk basemap for the final approach. Meanwhile, imagery_acq_date tells the enumerator when the Vantor satellite image was captured so they can distinguish newly constructed buildings from older structures, which may be useful in household enumeration operations.Specifying output_suso_tab and suso_gps_var = "hh_gps" in vantor_export_capi() generates this exact .tab file automatically:
library(vantorr)
library(sf)
# Define a small Enumeration Area bounding box in WGS84 (EPSG:4326)
ea_aoi <- st_bbox(
c(xmin = -105.004, ymin = 39.736, xmax = -105.000, ymax = 39.740),
crs = st_crs(4326)
)
# 1. Generate 250m Enumeration Area sub-cells (A1, A2, B1, B2)
ea_grid <- vantor_survey_grid(
aoi = ea_aoi,
cell_size = 250,
shape = "square",
verbose = FALSE
)
# 2. Define sampled household locations inside the EA
hh_samples <- st_as_sf(
data.frame(
hh_id = c("EA01-HH01", "EA01-HH02", "EA01-HH03"),
lon = c(-105.0030, -105.0015, -105.0010),
lat = c(39.7388, 39.7382, 39.7370)
),
coords = c("lon", "lat"),
crs = 4326
)
suso_tab_file <- tempfile(pattern = "suso_assignments_", fileext = ".tab")
capi_pts <- vantor_export_capi(
samples = hh_samples,
id_col = "hh_id",
grid = ea_grid,
output_suso_tab = suso_tab_file,
suso_gps_var = "hh_gps",
verbose = FALSE
)
# Inspect the returned sf object (coordinate columns use <suso_gps_var>__Latitude / __Longitude)
st_drop_geometry(capi_pts)[, c(
"sample_id", "cell_id", "plus_code",
"hh_gps__Latitude", "hh_gps__Longitude"
)]
#> sample_id cell_id plus_code hh_gps__Latitude hh_gps__Longitude
#> 1 EA01-HH01 B1 85FPPXQW+GR 39.7388 -105.0030
#> 2 EA01-HH02 B2 85FPPXQX+7C 39.7382 -105.0015
#> 3 EA01-HH03 C2 85FPPXPX+RJ 39.7370 -105.0010
# Inspect the generated Survey Solutions .tab preload table
read.delim(suso_tab_file, check.names = FALSE)[, c(
"sample_id", "cell_id", "plus_code",
"hh_gps__Latitude", "hh_gps__Longitude",
"_target_area"
)]
#> sample_id cell_id plus_code hh_gps__Latitude hh_gps__Longitude _target_area
#> 1 EA01-HH01 B1 85FPPXQW+GR 39.7388 -105.0030 B1
#> 2 EA01-HH02 B2 85FPPXQX+7C 39.7382 -105.0015 B2
#> 3 EA01-HH03 C2 85FPPXPX+RJ 39.7370 -105.0010 C2
.tpk Basemap (vantor_write_tpk())Each Web Mercator zoom level quadruples the number of \(256 \times 256\) pixel tiles required to cover an Area of Interest:
For testing or small Enumeration Areas ($\approx 200\text{ m} \times 200\text{ m}$), zoom = 15:17 requires only 6 to 9 tiles in total. vantor_write_tpk() enforces a safety cap (max_tiles = 2500L) so you never accidentally trigger a massive download.
When authenticated via vantor_auth(), pass your AOI (x or aoi) and set zip_for_suso = TRUE:
# Authenticate with your Vantor API Key
vantor_auth(api_key = Sys.getenv("VANTOR_API_KEY"))
# Define a compact ~200m x 200m test AOI
tiny_aoi <- st_bbox(
c(xmin = -105.002, ymin = 39.738, xmax = -105.000, ymax = 39.740),
crs = st_crs(4326)
)
# Generate both test_vantor_suso.tpk and the clean test_vantor_suso_tpk.zip upload archive
tpk_file <- vantor_write_tpk(
aoi = tiny_aoi,
output_file = "test_vantor_suso.tpk",
zoom = 15:17,
format = "tpk",
product = "VIVID_STANDARD_30",
zip_for_suso = TRUE
)
SpatRaster / GeoTIFF)If you have already downloaded a GeoTIFF using get_maxar_wms_basemap(), you can slice it offline into a .tpk package without making any additional API requests:
if (requireNamespace("terra", quietly = TRUE)) {
# Create a synthetic 3-band RGB raster over our EA (in WGS84)
r_ea <- terra::rast(
xmin = -105.004, xmax = -105.000,
ymin = 39.736, ymax = 39.740,
nrows = 128, ncols = 128, nlyrs = 3,
crs = "EPSG:4326"
)
set.seed(42)
terra::values(r_ea) <- sample(60:220, terra::ncell(r_ea) * 3, replace = TRUE)
out_tpk <- file.path(tempdir(), "ea_01_basemap.tpk")
tpk_res <- vantor_write_tpk(
raster = r_ea,
output_file = out_tpk,
zoom = 15:16,
format = "tpk",
zip_for_suso = TRUE,
verbose = FALSE
)
# The clean Survey Solutions upload archive is named *_tpk.zip:
suso_zip <- attr(tpk_res, "suso_zip_path")
basename(c(tpk_res, suso_zip))
# Verify the outer _tpk.zip contains only the .tpk at the root (no __MACOSX metadata)
utils::unzip(suso_zip, list = TRUE)$Name
# Verify the internal Esri Compact Cache V1 structure inside the .tpk
utils::unzip(tpk_res, list = TRUE)$Name
}
#> [1] "esriinfo/item.pkinfo"
#> [2] "esriinfo/iteminfo.xml"
#> [3] "servicedescriptions/mapserver/mapserver.json"
#> [4] "v101/Layers/_alllayers/L15/R0000C0000.bundle"
#> [5] "v101/Layers/_alllayers/L15/R0000C0000.bundlx"
#> [6] "v101/Layers/_alllayers/L16/R0000C0000.bundle"
#> [7] "v101/Layers/_alllayers/L16/R0000C0000.bundlx"
#> [8] "v101/Layers/conf.cdi"
#> [9] "v101/Layers/conf.xml"
WKID: 102100, MinScale, and MaxScale in Survey Solutions HQOnce you upload ea_01_basemap_tpk.zip to Survey Solutions Headquarters (Survey Setup -> Maps -> Upload Map) and click on the uploaded map to view its Details / Preview, you will see metadata extracted from servicedescriptions/mapserver/mapserver.json:
WKID: 102100: This is Esri’s canonical authority code for WGS_1984_Web_Mercator_Auxiliary_Sphere (EPSG:3857). In mapserver.json, Esri stores "spatialReference": { "wkid": 102100, "latestWkid": 3857 }. Survey Solutions Headquarters reads fullExtent.spatialReference.wkid (102100) and uses {"wkid": 102100} in its ArcGIS JS MapPreview widget. Spatially, 102100 and 3857 are identical.MinScale and MaxScale: For zoom = 15:17, Survey Solutions reports MinScale = 18055.99 (zoom 15 overview scale) and MaxScale = 4513.99 (zoom 17 close-up scale). On the interviewer tablet, Survey Solutions configures the ArcGISTiledLayer so enumerators can pan and zoom smoothly across the cached pyramid..tpk, .tif, .tab, and Label-Ready Shapefiles (_shapes.zip)In addition to a .tpk raster basemap, field supervisors often overlay Enumeration Area polygon boundaries or household sample points as Esri Shapefiles in Survey Solutions.
When Survey Solutions imports a Shapefile (.shp, .shx, .dbf, .prj), it checks the .dbf attribute table for a column named label (case-insensitive). If present, Survey Solutions renders those labels directly on top of the map in both Headquarters Map Preview and the Interviewer app ($feature.label).
vantor_offline_package() automates this entire bundle in a single call:
label attribute column on grid (from cell_id, e.g., "A1", "A2") and samples (from sample_id, e.g., "EA01-HH01").EPSG:4326 Shapefiles and packages them into <package_name>_shapes.zip at the root level (no subfolders, no __MACOSX forks).<package_name>_suso_preload.tab (with <suso_gps_var>__Latitude, <suso_gps_var>__Longitude, and _target_area) alongside <package_name>_samples.csv.EPSG:3857) .tif.<package_name>.tpk and <package_name>_tpk.zip.if (requireNamespace("terra", quietly = TRUE)) {
suso_bundle <- vantor_offline_package(
grid = ea_grid,
samples = capi_pts,
raster = r_ea,
create_tpk = "tpk",
tpk_zoom = 15:16,
create_shapefiles = TRUE,
suso_gps_var = "hh_gps",
package_name = "suso_ea01",
verbose = FALSE
)
# Files ready for Survey Solutions Headquarters upload:
basename(c(
suso_bundle$tpk_zip_path,
suso_bundle$shapefile_zip_path,
suso_bundle$suso_tab_path,
suso_bundle$raster_path,
suso_bundle$csv_path
))
# Inspect the root-level files inside suso_ea01_shapes.zip:
utils::unzip(suso_bundle$shapefile_zip_path, list = TRUE)$Name
}
#> [1] "suso_ea01_grid.dbf" "suso_ea01_grid.prj" "suso_ea01_grid.shp"
#> [4] "suso_ea01_grid.shx" "suso_ea01_samples.dbf" "suso_ea01_samples.prj"
#> [7] "suso_ea01_samples.shp" "suso_ea01_samples.shx"
Administrator or Headquarter user.suso_ea01_tpk.zip (and optionally suso_ea01_shapes.zip). Click on the uploaded map entry to verify its bounding box in Map Preview and confirm its MinScale, MaxScale, and WKID (102100).suso_ea01_suso_preload.tab.usermaps.tab): You can link uploaded .tpk and .shp maps to specific Interviewer accounts by uploading a tab-separated usermaps.tab file (Survey Setup -> Maps -> Upload User-to-Map Assignments) with two columns (map and users):usermaps_df <- data.frame(
map = c("suso_ea01.tpk", "suso_ea01_grid.shp"),
users = c("interviewer_01, interviewer_02", "interviewer_01, interviewer_02"),
stringsAsFactors = FALSE
)
usermaps_tab <- file.path(tempdir(), "usermaps.tab")
utils::write.table(
usermaps_df,
file = usermaps_tab,
sep = "\t",
row.names = FALSE,
quote = FALSE
)
cat(readLines(usermaps_tab), sep = "\n")
#> map users
#> suso_ea01.tpk interviewer_01, interviewer_02
#> suso_ea01_grid.shp interviewer_01, interviewer_02
SurveySolutionsAPIv2Instead of manually uploading .zip map archives and .tab assignment files through the web browser, you can combine vantorr with the SurveySolutionsAPIv2 R package (httr2 + GraphQL API v2 client for World Bank Survey Solutions) to automate the entire fieldwork deployment pipeline directly from R:
| Step | vantorr Output |
SurveySolutionsAPIv2 Function |
Action on Survey Solutions HQ Server |
|---|---|---|---|
1. Upload .tpk Basemap |
suso_bundle$tpk_zip_path (suso_ea01_tpk.zip) |
suso_mapupload(workspace, path_to_zip) |
Uploads & validates the .tpk pyramid via GraphQL (suso_gql_uploadmap). |
2. Upload .shp Overlay |
suso_bundle$shapefile_zip_path (suso_ea01_shapes.zip) |
suso_mapupload(workspace, path_to_zip) |
Uploads Enumeration Area polygons & labeled points to HQ Maps. |
| 3. Assign Maps to Interviewers | "suso_ea01.tpk", "suso_ea01_grid.shp" |
suso_mapassign(workspace, fileName, userName) |
Links the offline basemap and vector boundaries to the interviewer’s tablet sync queue. |
| 4. Create Preloaded Assignments | capi_pts (sample_id, cell_id, plus_code, imagery_acq_date) |
suso_createASS(df, questID, version) |
Creates preloaded assignments in parallel (httr2::req_perform_parallel()). |
# Install SurveySolutionsAPIv2 from GitHub if not already installed:
# remotes::install_github("michael-cw/SurveySolutionsAPIv2")
library(SurveySolutionsAPIv2)
library(data.table)
# 1. Authenticate with Survey Solutions Headquarters
suso_set_key(
suso_server = "https://your-suso-server.mysurvey.solutions",
suso_user = Sys.getenv("SUSO_API_USER"),
suso_password = Sys.getenv("SUSO_API_PASS")
)
ws <- "primary"
# 2. Upload the Vantor .tpk basemap and Enumeration Area Shapefiles to HQ
suso_mapupload(
workspace = ws,
path_to_zip = suso_bundle$tpk_zip_path
)
suso_mapupload(
workspace = ws,
path_to_zip = suso_bundle$shapefile_zip_path
)
# Verify the uploaded maps and scales on the server
suso_mapinfo(workspace = ws)
# 3. Assign the .tpk basemap and EA grid boundary overlay to field interviewers
# (Assuming these interviewers are created on the server already)
for (int_user in c("interviewer01", "interviewer02")) {
suso_mapassign(
workspace = ws,
fileName = "suso_ea01.tpk",
userName = int_user,
assignUser = TRUE
)
suso_mapassign(
workspace = ws,
fileName = "suso_ea01_grid.shp",
userName = int_user,
assignUser = TRUE
)
}
# 4. Create preloaded assignments via REST API v1 (suso_createASS)
# (Assuming this questionnaire has the required ID fields and is loaded)
preload_dt <- data.frame(
sample_id = capi_pts$sample_id,
cell_id = capi_pts$cell_id,
plus_code = capi_pts$plus_code,
ResponsibleName = rep("interviewer01", nrow(capi_pts)),
Quantity = rep(1L, nrow(capi_pts))
)
suso_createASS(
df = preload_dt,
workspace = ws,
questID = "00000000-0000-0000-0000-000000000000",
version = 1L
)
When interviewer_01 taps Synchronize on their Android tablet:
suso_ea01.tpk and suso_ea01_grid.shp into the tablet’s offline Map Dashboard.plus_code (for offline turn-by-turn routing in Google Maps / OSM), and cell_id (A1, B2).