-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathreverse-geocode.R
157 lines (124 loc) · 4.61 KB
/
reverse-geocode.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Intersection matches are only returned when featureTypes=StreetInt is included in the request.
# The locationType parameter only affects the location object in the geocode JSON response. It does not change the X/Y or DisplayX/DisplayY attribute values.
# loation_type
# Specifies whether the output geometry of PointAddress and Subaddress matches should be the rooftop point or street entrance location. Valid values are rooftop and street. The default value is rooftop.
reverse_geocode <- function(
locations,
crs = sf::st_crs(locations),
...,
lang_code = NULL,
feature_type = NULL,
for_storage = TRUE,
location_type = c("rooftop", "street"),
preferred_label_values = c("postalCity", "localCity"),
geocoder = default_geocoder(),
token = arc_token(),
.progress = TRUE
) {
# Tokens are required for reverseGeocoding
obj_check_token(token)
# TODO ask users to verify their `for_storage` use
# This is super important and can lead to contractual violations
check_bool(for_storage)
# check feature type if not missing
if (!is.null(feature_type)) {
rlang::arg_match(
feature_type,
c(
"StreetInt", "DistanceMarker", "StreetAddress",
"StreetName", "POI", "Subaddress",
"PointAddress", "Postal", "Locality"
)
)
}
# verify location type argument
location_type <- rlang::arg_match(location_type, values = c("rooftop", "street"))
# verify label value arg
preferred_label_values <- rlang::arg_match(
preferred_label_values,
values = c("postalCity", "localCity")
)
# TODO use wk to use any wk_handle-able points
if (!rlang::inherits_all(locations, c("sfc_POINT", "sfc"))) {
stop_input_type(locations, "sfc_POINT")
}
# ensure lang_code a single string
check_string(lang_code, allow_null = TRUE)
# if not missing and not valid, error
if (!is.null(lang_code) && !is_iso3166(lang_code)) {
cli::cli_abort(
c(
"{.arg lang_code} is not a recognized Country Code",
"i" = "See {.fn iso_3166_codes} for ISO 3166 codes"
)
)
}
# validate output CRS:
out_crs <- validate_crs(crs)[[1]]
# create list of provided parameters
query_params <- compact(list(
langCode = lang_code,
outSR = jsonify::to_json(out_crs, unbox = TRUE),
featureType = feature_type,
forStorage = for_storage,
locationType = location_type,
preferredLabelValues = preferred_label_values
))
# validate the input CRS
in_crs <- validate_crs(sf::st_crs(locations))[[1]]
# convert to EsriPoint JSON
locs_json <- as_esri_point_json(locations, in_crs)
b_req <- httr2::req_url_path_append(
arc_base_req(geocoder, token),
"reverseGeocode"
)
# f = json needs to be a url parameter
# b_req <- httr2::req_url_query(b_req, f = "json")
all_reqs <- vector(mode = "list", length = length(locs_json))
# fill requests with for loop
for (i in seq_along(locs_json)) {
all_reqs[[i]] <- httr2::req_body_form(
f = "json",
b_req,
!!!query_params,
location = locs_json[[i]]
)
}
# Run requests in parallel
all_resps <- httr2::req_perform_parallel(
all_reqs,
on_error = "continue",
progress = .progress
)
# TODO capture which locations had an error and either return
# requests or points
# also, should return missing values or IDs to associate with the points
# so that they can be merged back on to the input locations?
# TODO check for errors which will be encoded as json
resps_json <- httr2::resps_data(all_resps, httr2::resp_body_string)
# process the raw json using rust
res_raw <- parse_rev_geocode_resp(resps_json)
# TODO incorporate squish DF into arcgisutils. This is stopgap solution
# https://github.com/R-ArcGIS/arcgislayers/pull/167
res_attr <- data_frame(do.call(rbind.data.frame, res_raw$attributes))
# cast into sf object
res_sf <- sf::st_sf(
res_attr,
geometry = sf::st_sfc(res_raw[["geometry"]], crs = crs)
)
res_sf
# Return the errors as an attribute this will let people
# handle the failures later on if they need to do an iterative / recursive
# approach to it.
# or use tokio....
# I'll try both
}
# notes -------------------------------------------------------------------
# We need to have an object for GeocoderService
# Much like we have one for FeatureLayer etc
# These store so much metadata that will be needed.
# What is the workflow?
# arc_open("geocoder-url") or `geocoder("url"/"id")`
# We can use rust to create the JSON from the points
# Or do we want to have Rust do it _all_? I think that might be nice..
# would need to inherit the arc_agent() and X-Esri-Authorization headerre