expo-arcgis 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/expo/modules/arcgis/AnalysisOverlayRef.kt +66 -0
- package/android/src/main/java/expo/modules/arcgis/AuthChallengeHandler.kt +9 -3
- package/android/src/main/java/expo/modules/arcgis/DynamicEntityLayerRef.kt +53 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisExtrasModule.kt +15 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +12 -2
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +44 -6
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +20 -0
- package/android/src/main/java/expo/modules/arcgis/GeocoderFunctions.kt +45 -3
- package/android/src/main/java/expo/modules/arcgis/GeometryEngineFunctions.kt +79 -0
- package/android/src/main/java/expo/modules/arcgis/GeoprocessingFunctions.kt +41 -0
- package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +184 -25
- package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +94 -1
- package/android/src/main/java/expo/modules/arcgis/MapRef.kt +61 -3
- package/android/src/main/java/expo/modules/arcgis/OfflineFunctions.kt +76 -2
- package/android/src/main/java/expo/modules/arcgis/QueryCodec.kt +25 -2
- package/android/src/main/java/expo/modules/arcgis/SceneRef.kt +29 -3
- package/android/src/main/java/expo/modules/arcgis/UtilityNetworkRef.kt +13 -0
- package/build/DynamicEntityLayer.d.ts +6 -2
- package/build/DynamicEntityLayer.d.ts.map +1 -1
- package/build/DynamicEntityLayer.js +10 -3
- package/build/DynamicEntityLayer.js.map +1 -1
- package/build/ExpoArcgis.types.d.ts +495 -3
- package/build/ExpoArcgis.types.d.ts.map +1 -1
- package/build/ExpoArcgis.types.js.map +1 -1
- package/build/ExpoArcgisExtrasModule.d.ts +7 -1
- package/build/ExpoArcgisExtrasModule.d.ts.map +1 -1
- package/build/ExpoArcgisExtrasModule.js.map +1 -1
- package/build/ExpoArcgisGeometryModule.d.ts +8 -2
- package/build/ExpoArcgisGeometryModule.d.ts.map +1 -1
- package/build/ExpoArcgisGeometryModule.js.map +1 -1
- package/build/ExpoArcgisModule.d.ts +22 -3
- package/build/ExpoArcgisModule.d.ts.map +1 -1
- package/build/ExpoArcgisModule.js.map +1 -1
- package/build/FeatureLayer.d.ts +11 -0
- package/build/FeatureLayer.d.ts.map +1 -1
- package/build/LineOfSight.d.ts +17 -5
- package/build/LineOfSight.d.ts.map +1 -1
- package/build/LineOfSight.js +36 -13
- package/build/LineOfSight.js.map +1 -1
- package/build/Viewshed.d.ts +14 -4
- package/build/Viewshed.d.ts.map +1 -1
- package/build/Viewshed.js +27 -8
- package/build/Viewshed.js.map +1 -1
- package/build/auth.d.ts +17 -1
- package/build/auth.d.ts.map +1 -1
- package/build/auth.js +21 -2
- package/build/auth.js.map +1 -1
- package/build/geocoder.d.ts +12 -1
- package/build/geocoder.d.ts.map +1 -1
- package/build/geocoder.js +10 -1
- package/build/geocoder.js.map +1 -1
- package/build/geometryEngine.d.ts +31 -1
- package/build/geometryEngine.d.ts.map +1 -1
- package/build/geometryEngine.js +30 -0
- package/build/geometryEngine.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/offline.d.ts +14 -2
- package/build/offline.d.ts.map +1 -1
- package/build/offline.js +14 -1
- package/build/offline.js.map +1 -1
- package/ios/AnalysisOverlayRef.swift +74 -0
- package/ios/AuthChallengeHandler.swift +7 -2
- package/ios/DynamicEntityLayerRef.swift +68 -0
- package/ios/ExpoArcgisExtrasModule.swift +19 -0
- package/ios/ExpoArcgisGeometryModule.swift +12 -2
- package/ios/ExpoArcgisModule.swift +41 -4
- package/ios/ExpoArcgisSceneView.swift +24 -0
- package/ios/GeocoderFunctions.swift +63 -2
- package/ios/GeometryEngineFunctions.swift +100 -0
- package/ios/GeoprocessingFunctions.swift +34 -0
- package/ios/GraphicsOverlayRef.swift +107 -5
- package/ios/LayerRef.swift +89 -1
- package/ios/MapRef.swift +70 -4
- package/ios/OfflineFunctions.swift +85 -2
- package/ios/QueryCodec.swift +25 -0
- package/ios/SceneRef.swift +27 -4
- package/ios/UtilityNetworkRef.swift +13 -0
- package/package.json +1 -1
- package/src/DynamicEntityLayer.tsx +14 -3
- package/src/ExpoArcgis.types.ts +507 -5
- package/src/ExpoArcgisExtrasModule.ts +17 -0
- package/src/ExpoArcgisGeometryModule.ts +14 -1
- package/src/ExpoArcgisModule.ts +25 -2
- package/src/LineOfSight.tsx +53 -17
- package/src/Viewshed.tsx +36 -11
- package/src/auth.ts +31 -2
- package/src/geocoder.ts +12 -1
- package/src/geometryEngine.ts +42 -0
- package/src/index.ts +2 -0
- package/src/offline.ts +24 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
3
|
import com.arcgismaps.analysis.interactive.Analysis
|
|
4
|
+
import com.arcgismaps.analysis.interactive.ExploratoryGeoElementLineOfSight
|
|
5
|
+
import com.arcgismaps.analysis.interactive.ExploratoryGeoElementViewshed
|
|
4
6
|
import com.arcgismaps.analysis.interactive.ExploratoryLineOfSightTargetVisibility
|
|
5
7
|
import com.arcgismaps.analysis.interactive.ExploratoryLocationDistanceMeasurement
|
|
6
8
|
import com.arcgismaps.analysis.interactive.ExploratoryLocationLineOfSight
|
|
@@ -75,6 +77,42 @@ class ViewshedRef(appContext: AppContext, props: Map<String, Any?>) : AnalysisRe
|
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
/**
|
|
81
|
+
* SharedObject wrapping an [ExploratoryGeoElementViewshed] — a viewshed whose observer tracks a
|
|
82
|
+
* [GraphicRef]'s native [Graphic] (a [GeoElement]) as it moves.
|
|
83
|
+
*/
|
|
84
|
+
class GeoElementViewshedRef(appContext: AppContext, graphic: GraphicRef, props: Map<String, Any?>) : AnalysisRef(appContext) {
|
|
85
|
+
private val viewshed = ExploratoryGeoElementViewshed(
|
|
86
|
+
graphic.graphic,
|
|
87
|
+
numOr(props["horizontalAngle"], 45.0),
|
|
88
|
+
numOr(props["verticalAngle"], 45.0),
|
|
89
|
+
numOr(props["headingOffset"], 0.0),
|
|
90
|
+
numOr(props["pitchOffset"], 0.0),
|
|
91
|
+
(props["minDistance"] as? Number)?.toDouble(),
|
|
92
|
+
(props["maxDistance"] as? Number)?.toDouble(),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
override val analysis: Analysis get() = viewshed
|
|
96
|
+
|
|
97
|
+
init {
|
|
98
|
+
applyProps(props)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fun applyProps(changed: Map<String, Any?>) {
|
|
102
|
+
changed.forEach { (key, value) ->
|
|
103
|
+
when (key) {
|
|
104
|
+
"headingOffset" -> (value as? Number)?.toDouble()?.let { viewshed.headingOffset = it }
|
|
105
|
+
"pitchOffset" -> (value as? Number)?.toDouble()?.let { viewshed.pitchOffset = it }
|
|
106
|
+
"horizontalAngle" -> (value as? Number)?.toDouble()?.let { viewshed.horizontalAngle = it }
|
|
107
|
+
"verticalAngle" -> (value as? Number)?.toDouble()?.let { viewshed.verticalAngle = it }
|
|
108
|
+
"minDistance" -> viewshed.minDistance = (value as? Number)?.toDouble()
|
|
109
|
+
"maxDistance" -> viewshed.maxDistance = (value as? Number)?.toDouble()
|
|
110
|
+
"frustumOutlineVisible" -> (value as? Boolean)?.let { viewshed.frustumOutlineVisible = it }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
78
116
|
/** SharedObject wrapping an [ExploratoryLocationLineOfSight] — streams target visibility to JS. */
|
|
79
117
|
class LineOfSightRef(appContext: AppContext, props: Map<String, Any?>) : AnalysisRef(appContext) {
|
|
80
118
|
private val lineOfSight = ExploratoryLocationLineOfSight(
|
|
@@ -151,6 +189,34 @@ class DistanceMeasurementRef(appContext: AppContext, props: Map<String, Any?>) :
|
|
|
151
189
|
}
|
|
152
190
|
}
|
|
153
191
|
|
|
192
|
+
/**
|
|
193
|
+
* SharedObject wrapping an [ExploratoryGeoElementLineOfSight] — a line of sight whose observer
|
|
194
|
+
* and target both track a [GraphicRef]'s native [Graphic] (a [GeoElement]) as it moves.
|
|
195
|
+
* Streams the target's visibility back to JS via `onTargetVisibilityChange`.
|
|
196
|
+
*/
|
|
197
|
+
class GeoElementLineOfSightRef(appContext: AppContext, observer: GraphicRef, target: GraphicRef) : AnalysisRef(appContext) {
|
|
198
|
+
private val lineOfSight = ExploratoryGeoElementLineOfSight(
|
|
199
|
+
observer.graphic,
|
|
200
|
+
target.graphic,
|
|
201
|
+
)
|
|
202
|
+
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
203
|
+
|
|
204
|
+
override val analysis: Analysis get() = lineOfSight
|
|
205
|
+
|
|
206
|
+
init {
|
|
207
|
+
scope.launch {
|
|
208
|
+
lineOfSight.targetVisibility.collect { visibility ->
|
|
209
|
+
emit("onTargetVisibilityChange", mapOf("visibility" to visibilityString(visibility)))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
override fun deallocate() {
|
|
215
|
+
scope.cancel()
|
|
216
|
+
super.deallocate()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
154
220
|
/** Decodes a JS point dict into a [Point] (returns null if the value is not a point geometry). */
|
|
155
221
|
internal fun analysisPoint(value: Any?): Point? =
|
|
156
222
|
(value as? Map<*, *>)?.let { geometryFromDict(it) } as? Point
|
|
@@ -22,11 +22,17 @@ import kotlinx.coroutines.launch
|
|
|
22
22
|
object AuthChallengeHandler : ArcGISAuthenticationChallengeHandler {
|
|
23
23
|
private var username: String? = null
|
|
24
24
|
private var password: String? = null
|
|
25
|
+
private var tokenExpirationMinutes: Int? = null
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Stores (or clears, when null) the login the handler uses to mint token credentials on demand.
|
|
29
|
+
* [tokenExpirationMinutes] is forwarded to [TokenCredential.createWithChallenge]; pass null to
|
|
30
|
+
* use the server's default expiry.
|
|
31
|
+
*/
|
|
32
|
+
fun setCredentials(username: String?, password: String?, tokenExpirationMinutes: Int? = null) {
|
|
28
33
|
this.username = username
|
|
29
34
|
this.password = password
|
|
35
|
+
this.tokenExpirationMinutes = tokenExpirationMinutes
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
override suspend fun handleArcGISAuthenticationChallenge(
|
|
@@ -34,7 +40,7 @@ object AuthChallengeHandler : ArcGISAuthenticationChallengeHandler {
|
|
|
34
40
|
): ArcGISAuthenticationChallengeResponse {
|
|
35
41
|
val user = username ?: return ArcGISAuthenticationChallengeResponse.ContinueAndFail
|
|
36
42
|
val pass = password ?: return ArcGISAuthenticationChallengeResponse.ContinueAndFail
|
|
37
|
-
return TokenCredential.
|
|
43
|
+
return TokenCredential.createWithChallenge(challenge, user, pass, tokenExpirationMinutes)
|
|
38
44
|
.map { ArcGISAuthenticationChallengeResponse.ContinueWithCredential(it) }
|
|
39
45
|
.getOrElse { ArcGISAuthenticationChallengeResponse.ContinueAndFail }
|
|
40
46
|
}
|
|
@@ -39,6 +39,18 @@ class DynamicEntityLayerRef(appContext: AppContext, props: Map<String, Any?>) :
|
|
|
39
39
|
emit("onConnectionStatusChange", mapOf("status" to connectionStatusString(status)))
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
// Emit entity-received events (new/updated observation arrived for an entity).
|
|
43
|
+
scope.launch {
|
|
44
|
+
dataSource.dynamicEntityReceivedEvent.collect { info ->
|
|
45
|
+
emit("onDynamicEntityChange", dynamicEntityPayload("received", info.dynamicEntity))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Emit entity-purged events (entity evicted by purge rules).
|
|
49
|
+
scope.launch {
|
|
50
|
+
dataSource.dynamicEntityPurgedEvent.collect { info ->
|
|
51
|
+
emit("onDynamicEntityChange", dynamicEntityPayload("purged", info.dynamicEntity))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
/** Pushes an observation into a custom data source (no-op for a stream service). */
|
|
@@ -48,6 +60,24 @@ class DynamicEntityLayerRef(appContext: AppContext, props: Map<String, Any?>) :
|
|
|
48
60
|
flow.tryEmit(CustomDynamicEntityDataSource.FeedEvent.NewObservation(geom, attributes))
|
|
49
61
|
}
|
|
50
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Returns the observation history for the entity with the given track id, newest first,
|
|
65
|
+
* capped at [max] entries (default 100). Each observation carries its own `attributes` and
|
|
66
|
+
* `geometry` snapshot at the time it was received.
|
|
67
|
+
*/
|
|
68
|
+
suspend fun queryObservations(entityId: String, max: Int = 100): List<Map<String, Any?>> {
|
|
69
|
+
val result = dataSource.queryDynamicEntities(listOf(entityId)).getOrThrow()
|
|
70
|
+
val entity = result.toList().firstOrNull()
|
|
71
|
+
?: return emptyList()
|
|
72
|
+
return entity.getObservations(max).map { obs ->
|
|
73
|
+
val entry = mutableMapOf<String, Any?>(
|
|
74
|
+
"attributes" to obs.attributes.toMap(),
|
|
75
|
+
)
|
|
76
|
+
obs.geometry?.let { entry["geometry"] = dictFromGeometry(it) }
|
|
77
|
+
entry
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
51
81
|
/** Returns the data source's currently-tracked dynamic entities (attributes + geometry). */
|
|
52
82
|
suspend fun queryDynamicEntities(): Map<String, Any?> {
|
|
53
83
|
val result = dataSource.queryDynamicEntities(DynamicEntityQueryParameters()).getOrThrow()
|
|
@@ -81,6 +111,11 @@ class DynamicEntityLayerRef(appContext: AppContext, props: Map<String, Any?>) :
|
|
|
81
111
|
}
|
|
82
112
|
(dataSource as? ArcGISStreamService)?.filter = filter
|
|
83
113
|
}
|
|
114
|
+
(changed["purgeOptions"] as? Map<*, *>)?.let { purgeDict ->
|
|
115
|
+
val opts = dataSource.purgeOptions
|
|
116
|
+
(purgeDict["maximumObservations"] as? Number)?.let { opts.maximumObservations = it.toLong() }
|
|
117
|
+
(purgeDict["maximumDuration"] as? Number)?.let { opts.maximumDuration = it.toDouble() }
|
|
118
|
+
}
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
override fun deallocate() {
|
|
@@ -97,6 +132,24 @@ fun connectionStatusString(status: ConnectionStatus): String = when (status) {
|
|
|
97
132
|
is ConnectionStatus.Failed -> "failed"
|
|
98
133
|
}
|
|
99
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Builds a compact payload for the `onDynamicEntityChange` event.
|
|
137
|
+
* Only `received` and `purged` change types are emitted (observation-only updates are
|
|
138
|
+
* captured within `dynamicEntityReceivedEvent` which fires per-entity arrival, not per
|
|
139
|
+
* observation, so the event rate is bounded to entity lifecycle changes).
|
|
140
|
+
* Geometry is serialized only when present; attributes are passed as-is (the map returned
|
|
141
|
+
* by the SDK is a shallow snapshot of the current entity attributes).
|
|
142
|
+
*/
|
|
143
|
+
private fun dynamicEntityPayload(changeType: String, entity: com.arcgismaps.realtime.DynamicEntity): Map<String, Any?> {
|
|
144
|
+
val payload = mutableMapOf<String, Any?>(
|
|
145
|
+
"changeType" to changeType,
|
|
146
|
+
"entityId" to entity.id,
|
|
147
|
+
"attributes" to entity.attributes.toMap(),
|
|
148
|
+
)
|
|
149
|
+
entity.geometry?.let { payload["geometry"] = dictFromGeometry(it) }
|
|
150
|
+
return payload
|
|
151
|
+
}
|
|
152
|
+
|
|
100
153
|
/** Builds the real-time data source: a custom feed (push from JS) or a stream service. */
|
|
101
154
|
private fun buildDataSource(
|
|
102
155
|
props: Map<String, Any?>,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
|
+
import com.arcgismaps.ArcGISEnvironment
|
|
4
|
+
import com.arcgismaps.httpcore.authentication.TokenCredential
|
|
3
5
|
import expo.modules.kotlin.functions.Coroutine
|
|
4
6
|
import expo.modules.kotlin.modules.Module
|
|
5
7
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
@@ -71,6 +73,19 @@ class ExpoArcgisExtrasModule : Module() {
|
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// Per-service token credential — mint a token for a specific URL and add it to the store.
|
|
77
|
+
AsyncFunction("setServiceCredential") Coroutine { serviceUrl: String, username: String, password: String, tokenExpirationMinutes: Int? ->
|
|
78
|
+
val credential = TokenCredential.create(serviceUrl, username, password, tokenExpirationMinutes)
|
|
79
|
+
.getOrThrow()
|
|
80
|
+
ArcGISEnvironment.authenticationManager.arcGISCredentialStore.add(credential, serviceUrl)
|
|
81
|
+
.getOrThrow()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Tile-cache size estimation — quick estimate before committing to a download.
|
|
85
|
+
AsyncFunction("estimateTileCacheSize") Coroutine { tileServiceUrl: String, areaOfInterest: Map<String, Any?>, options: Map<String, Any?>? ->
|
|
86
|
+
estimateTileCacheSize(tileServiceUrl, areaOfInterest, options)
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
// Turn-by-turn navigation — solve a route and track device locations against it.
|
|
75
90
|
AsyncFunction("createRouteTracker") Coroutine { stops: List<Map<String, Any?>>, params: Map<String, Any?> ->
|
|
76
91
|
createRouteTracker(appContext, stops, params)
|
|
@@ -59,6 +59,11 @@ class ExpoArcgisGeometryModule : Module() {
|
|
|
59
59
|
Function("geMove", ::geMove)
|
|
60
60
|
Function("geRotate", ::geRotate)
|
|
61
61
|
Function("geScale", ::geScale)
|
|
62
|
+
Function("geEllipseGeodesic", ::geEllipseGeodesic)
|
|
63
|
+
Function("geSectorGeodesic", ::geSectorGeodesic)
|
|
64
|
+
Function("geWithZ", ::geWithZ)
|
|
65
|
+
Function("geWithM", ::geWithM)
|
|
66
|
+
Function("geWithZAndM", ::geWithZAndM)
|
|
62
67
|
|
|
63
68
|
// CoordinateFormatter — point <-> notation strings, exposed as the JS `coordinateFormatter` namespace.
|
|
64
69
|
Function("cfToLatLong", ::cfToLatLong)
|
|
@@ -80,6 +85,11 @@ class ExpoArcgisGeometryModule : Module() {
|
|
|
80
85
|
AsyncFunction("suggest") Coroutine { searchText: String, params: Map<String, Any?> ->
|
|
81
86
|
suggest(searchText, params)
|
|
82
87
|
}
|
|
88
|
+
// FLAG: ExpoArcgisGeometry is approaching 64 KB; if the module's definition() grows further,
|
|
89
|
+
// relocate geocodeSuggestion (and the other geocoder AsyncFunctions) to ExpoArcgisExtrasModule.
|
|
90
|
+
AsyncFunction("geocodeSuggestion") Coroutine { suggestionId: Int, params: Map<String, Any?> ->
|
|
91
|
+
geocodeSuggestion(suggestionId, params)
|
|
92
|
+
}
|
|
83
93
|
|
|
84
94
|
// Routing — solve a route between stops, exposed as the JS `router` namespace.
|
|
85
95
|
AsyncFunction("solveRoute") Coroutine { stops: List<Map<String, Any?>>, params: Map<String, Any?> ->
|
|
@@ -172,8 +182,8 @@ class ExpoArcgisGeometryModule : Module() {
|
|
|
172
182
|
}
|
|
173
183
|
|
|
174
184
|
// Offline — take maps/data offline, exposed as the JS `offline` namespace.
|
|
175
|
-
AsyncFunction("generateOfflineMap") Coroutine { portalItemId: String, areaOfInterest: Map<String, Any?>, downloadName: String ->
|
|
176
|
-
generateOfflineMap(appContext, appContext.reactContext?.filesDir, portalItemId, areaOfInterest, downloadName)
|
|
185
|
+
AsyncFunction("generateOfflineMap") Coroutine { portalItemId: String, areaOfInterest: Map<String, Any?>, downloadName: String, overrides: Map<String, Any?>? ->
|
|
186
|
+
generateOfflineMap(appContext, appContext.reactContext?.filesDir, portalItemId, areaOfInterest, downloadName, overrides)
|
|
177
187
|
}
|
|
178
188
|
AsyncFunction("syncOfflineMap") Coroutine { mobileMapPackagePath: String ->
|
|
179
189
|
syncOfflineMap(appContext, mobileMapPackagePath)
|
|
@@ -4,6 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import com.arcgismaps.ApiKey
|
|
5
5
|
import com.arcgismaps.ArcGISEnvironment
|
|
6
6
|
import com.arcgismaps.httpcore.authentication.OAuthApplicationCredential
|
|
7
|
+
import com.arcgismaps.httpcore.authentication.OAuthUserCredential
|
|
7
8
|
import com.arcgismaps.httpcore.authentication.TokenCredential
|
|
8
9
|
import expo.modules.kotlin.Promise
|
|
9
10
|
import expo.modules.kotlin.functions.Coroutine
|
|
@@ -34,14 +35,21 @@ class ExpoArcgisModule : Module() {
|
|
|
34
35
|
|
|
35
36
|
// Token auth for secured services (e.g. utility-network feature services) — store the login;
|
|
36
37
|
// the challenge handler mints a TokenCredential for the exact challenged resource on demand.
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
// `tokenExpirationMinutes` is optional; the server's default expiry is used when omitted.
|
|
39
|
+
Function("setTokenCredential") { username: String, password: String, tokenExpirationMinutes: Int? ->
|
|
40
|
+
AuthChallengeHandler.setCredentials(username, password, tokenExpirationMinutes)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
//
|
|
43
|
+
// Revokes any OAuth user credentials on the server, then clears all cached credentials.
|
|
42
44
|
AsyncFunction("signOut") Coroutine { ->
|
|
45
|
+
val store = ArcGISEnvironment.authenticationManager.arcGISCredentialStore
|
|
46
|
+
for (credential in store.getCredentials()) {
|
|
47
|
+
if (credential is OAuthUserCredential) {
|
|
48
|
+
credential.revokeToken()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
AuthChallengeHandler.setCredentials(null, null)
|
|
44
|
-
|
|
52
|
+
store.removeAll()
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
// OAuth user sign-in (Android): JS opens the browser between these two steps.
|
|
@@ -234,18 +242,22 @@ class ExpoArcgisModule : Module() {
|
|
|
234
242
|
Function("applyProps") { ref: OgcFeatureLayerRef, changed: Map<String, Any?> -> ref.applyProps(changed) }
|
|
235
243
|
}
|
|
236
244
|
|
|
237
|
-
// Real-time DynamicEntityLayer (stream service) — emits onConnectionStatusChange
|
|
245
|
+
// Real-time DynamicEntityLayer (stream service) — emits onConnectionStatusChange +
|
|
246
|
+
// onDynamicEntityChange (received/purged entity events).
|
|
238
247
|
Class(DynamicEntityLayerRef::class) {
|
|
239
248
|
Constructor { props: Map<String, Any?> ->
|
|
240
249
|
DynamicEntityLayerRef(appContext, props).also { it.applyProps(props) }
|
|
241
250
|
}
|
|
242
|
-
Events("onConnectionStatusChange")
|
|
251
|
+
Events("onConnectionStatusChange", "onDynamicEntityChange")
|
|
243
252
|
Function("applyProps") { ref: DynamicEntityLayerRef, changed: Map<String, Any?> ->
|
|
244
253
|
ref.applyProps(changed)
|
|
245
254
|
}
|
|
246
255
|
AsyncFunction("queryDynamicEntities") Coroutine { ref: DynamicEntityLayerRef ->
|
|
247
256
|
ref.queryDynamicEntities()
|
|
248
257
|
}
|
|
258
|
+
AsyncFunction("queryObservations") Coroutine { ref: DynamicEntityLayerRef, entityId: String, max: Int ->
|
|
259
|
+
ref.queryObservations(entityId, max)
|
|
260
|
+
}
|
|
249
261
|
Function("pushObservation") { ref: DynamicEntityLayerRef, attributes: Map<String, Any?>, geometry: Map<String, Any?> ->
|
|
250
262
|
ref.pushObservation(attributes, geometry)
|
|
251
263
|
}
|
|
@@ -291,6 +303,16 @@ class ExpoArcgisModule : Module() {
|
|
|
291
303
|
Function("applyProps") { ref: ViewshedRef, changed: Map<String, Any?> -> ref.applyProps(changed) }
|
|
292
304
|
}
|
|
293
305
|
|
|
306
|
+
// GeoElement-anchored viewshed — observer tracks a Graphic as it moves.
|
|
307
|
+
Class(GeoElementViewshedRef::class) {
|
|
308
|
+
Constructor { graphic: GraphicRef, props: Map<String, Any?> ->
|
|
309
|
+
GeoElementViewshedRef(appContext, graphic, props)
|
|
310
|
+
}
|
|
311
|
+
Function("applyProps") { ref: GeoElementViewshedRef, changed: Map<String, Any?> ->
|
|
312
|
+
ref.applyProps(changed)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
294
316
|
// Line of sight — emits onTargetVisibilityChange as the target's visibility changes.
|
|
295
317
|
Class(LineOfSightRef::class) {
|
|
296
318
|
Constructor { props: Map<String, Any?> -> LineOfSightRef(appContext, props) }
|
|
@@ -298,6 +320,15 @@ class ExpoArcgisModule : Module() {
|
|
|
298
320
|
Function("applyProps") { ref: LineOfSightRef, changed: Map<String, Any?> -> ref.applyProps(changed) }
|
|
299
321
|
}
|
|
300
322
|
|
|
323
|
+
// GeoElement-anchored line of sight — observer and target each track a Graphic as it moves.
|
|
324
|
+
// FLAG: added to the main module — integrator may need to relocate for the 64 KB budget.
|
|
325
|
+
Class(GeoElementLineOfSightRef::class) {
|
|
326
|
+
Constructor { observer: GraphicRef, target: GraphicRef ->
|
|
327
|
+
GeoElementLineOfSightRef(appContext, observer, target)
|
|
328
|
+
}
|
|
329
|
+
Events("onTargetVisibilityChange")
|
|
330
|
+
}
|
|
331
|
+
|
|
301
332
|
Class(DistanceMeasurementRef::class) {
|
|
302
333
|
Constructor { props: Map<String, Any?> -> DistanceMeasurementRef(appContext, props) }
|
|
303
334
|
Events("onMeasurementChange")
|
|
@@ -328,6 +359,9 @@ class ExpoArcgisModule : Module() {
|
|
|
328
359
|
AsyncFunction("associations") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String ->
|
|
329
360
|
ref.associations(tableName, whereClause)
|
|
330
361
|
}
|
|
362
|
+
Function("getTerminalConfigurations") { ref: UtilityNetworkRef ->
|
|
363
|
+
ref.getTerminalConfigurations()
|
|
364
|
+
}
|
|
331
365
|
AsyncFunction("getState") Coroutine { ref: UtilityNetworkRef -> ref.getState() }
|
|
332
366
|
Function("validateNetworkTopology") { ref: UtilityNetworkRef, extent: Map<String, Any?> ->
|
|
333
367
|
ref.validateNetworkTopology(extent)
|
|
@@ -405,6 +439,10 @@ class ExpoArcgisModule : Module() {
|
|
|
405
439
|
view.setCamera(camera)
|
|
406
440
|
}
|
|
407
441
|
|
|
442
|
+
Prop("cameraController") { view: ExpoArcgisSceneView, value: Map<String, Any?>? ->
|
|
443
|
+
view.setCameraController(value)
|
|
444
|
+
}
|
|
445
|
+
|
|
408
446
|
Prop("sunLighting") { view: ExpoArcgisSceneView, value: String? ->
|
|
409
447
|
view.setSunLighting(value)
|
|
410
448
|
}
|
|
@@ -9,7 +9,9 @@ import com.arcgismaps.geometry.Point
|
|
|
9
9
|
import com.arcgismaps.geometry.SpatialReference
|
|
10
10
|
import com.arcgismaps.mapping.view.AtmosphereEffect
|
|
11
11
|
import com.arcgismaps.mapping.view.Camera
|
|
12
|
+
import com.arcgismaps.mapping.view.GlobeCameraController
|
|
12
13
|
import com.arcgismaps.mapping.view.LightingMode
|
|
14
|
+
import com.arcgismaps.mapping.view.OrbitLocationCameraController
|
|
13
15
|
import com.arcgismaps.mapping.view.SceneView
|
|
14
16
|
import com.arcgismaps.mapping.view.ScreenCoordinate
|
|
15
17
|
import expo.modules.kotlin.AppContext
|
|
@@ -147,6 +149,24 @@ class ExpoArcgisSceneView(context: Context, appContext: AppContext) : ExpoView(c
|
|
|
147
149
|
scope.launch { sceneView.setViewpointCameraAnimated(camera, 0.5f) }
|
|
148
150
|
}
|
|
149
151
|
|
|
152
|
+
/** Sets or clears the scene's camera controller (orbit/globe). `null` restores the SDK default. */
|
|
153
|
+
fun setCameraController(c: Map<String, Any?>?) {
|
|
154
|
+
sceneView.cameraController = when (c?.get("type") as? String) {
|
|
155
|
+
"orbitLocation" -> {
|
|
156
|
+
val target = c["target"] as? Map<*, *>
|
|
157
|
+
val x = (target?.get("x") as? Number)?.toDouble() ?: 0.0
|
|
158
|
+
val y = (target?.get("y") as? Number)?.toDouble() ?: 0.0
|
|
159
|
+
val z = (target?.get("z") as? Number)?.toDouble()
|
|
160
|
+
val point = if (z != null) Point(x, y, z, SpatialReference.wgs84())
|
|
161
|
+
else Point(x, y, SpatialReference.wgs84())
|
|
162
|
+
val distance = (c["distance"] as? Number)?.toDouble() ?: 1500.0
|
|
163
|
+
OrbitLocationCameraController(point, distance)
|
|
164
|
+
}
|
|
165
|
+
"globe" -> GlobeCameraController()
|
|
166
|
+
else -> GlobeCameraController() // null/absent → restore SDK default (GlobeCameraController)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
/** Sun lighting mode (shadows). */
|
|
151
171
|
fun setSunLighting(s: String?) {
|
|
152
172
|
sceneView.sunLighting = when (s) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
3
|
import com.arcgismaps.geometry.Point
|
|
4
|
+
import com.arcgismaps.geometry.SpatialReference
|
|
4
5
|
import com.arcgismaps.tasks.geocode.GeocodeParameters
|
|
5
6
|
import com.arcgismaps.tasks.geocode.GeocodeResult
|
|
6
7
|
import com.arcgismaps.tasks.geocode.LocatorTask
|
|
@@ -12,8 +13,31 @@ import java.util.concurrent.ConcurrentHashMap
|
|
|
12
13
|
/**
|
|
13
14
|
* Free functions backing the JS `geocoder` namespace — forward / reverse geocoding via a
|
|
14
15
|
* [LocatorTask]. Registered as `AsyncFunction`s in `ExpoArcgisGeometryModule`.
|
|
16
|
+
*
|
|
17
|
+
* SuggestResult round-trip
|
|
18
|
+
* ─────────────────────────
|
|
19
|
+
* [suggest] attaches a `suggestionId` (Int) to each returned item and stashes the native
|
|
20
|
+
* [SuggestResult] (and its locator URL) in [SuggestRegistry]. The registry is REPLACED on each
|
|
21
|
+
* new [suggest] call (ids restart from 0) so memory never grows unboundedly.
|
|
22
|
+
* [geocodeSuggestion] looks up the held result and calls `LocatorTask.geocode(suggestResult)` —
|
|
23
|
+
* the SDK's precise overload that avoids a text re-search.
|
|
15
24
|
*/
|
|
16
25
|
|
|
26
|
+
/** Holds the native [SuggestResult]s from the most recent [suggest] call. */
|
|
27
|
+
private object SuggestRegistry {
|
|
28
|
+
@Volatile private var locatorUrl: String = ""
|
|
29
|
+
@Volatile private var results: Map<Int, SuggestResult> = emptyMap()
|
|
30
|
+
|
|
31
|
+
/** Replaces the registry with a fresh set of results (called at the start of each [suggest]). */
|
|
32
|
+
@Synchronized fun reset(url: String, items: List<SuggestResult>) {
|
|
33
|
+
locatorUrl = url
|
|
34
|
+
results = items.mapIndexed { id, r -> id to r }.toMap()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Synchronized fun lookup(id: Int): Pair<SuggestResult, String>? =
|
|
38
|
+
results[id]?.let { it to locatorUrl }
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
private const val WORLD_GEOCODER =
|
|
18
42
|
"https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"
|
|
19
43
|
|
|
@@ -49,6 +73,8 @@ private fun buildGeocodeParameters(params: Map<String, Any?>): GeocodeParameters
|
|
|
49
73
|
(params["maxResults"] as? Number)?.toInt()?.let { maxResults = it }
|
|
50
74
|
(params["countryCode"] as? String)?.let { countryCode = it }
|
|
51
75
|
(params["categories"] as? List<*>)?.filterIsInstance<String>()?.let { categories.addAll(it) }
|
|
76
|
+
(params["resultAttributeNames"] as? List<*>)?.filterIsInstance<String>()?.let { resultAttributeNames.addAll(it) }
|
|
77
|
+
(params["outputSpatialReference"] as? Number)?.toInt()?.let { outputSpatialReference = SpatialReference(it) }
|
|
52
78
|
((params["preferredSearchLocation"] as? Map<*, *>)?.let { geometryFromDict(it) } as? Point)
|
|
53
79
|
?.let { preferredSearchLocation = it }
|
|
54
80
|
}
|
|
@@ -59,9 +85,25 @@ private fun buildReverseGeocodeParameters(params: Map<String, Any?>): ReverseGeo
|
|
|
59
85
|
(params["maxDistance"] as? Number)?.toDouble()?.let { maxDistance = it }
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
internal suspend fun suggest(searchText: String, params: Map<String, Any?>): List<Map<String, Any?>>
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
internal suspend fun suggest(searchText: String, params: Map<String, Any?>): List<Map<String, Any?>> {
|
|
89
|
+
val url = params["locatorUrl"] as? String ?: WORLD_GEOCODER
|
|
90
|
+
val results = locatorTask(params).suggest(searchText, buildSuggestParameters(params)).getOrThrow()
|
|
91
|
+
// Stash all results in the registry (replaces any prior batch; ids restart from 0).
|
|
92
|
+
SuggestRegistry.reset(url, results)
|
|
93
|
+
return results.mapIndexed { id, r ->
|
|
94
|
+
serializeSuggestResult(r) + mapOf("suggestionId" to id)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
internal suspend fun geocodeSuggestion(suggestionId: Int, params: Map<String, Any?>): List<Map<String, Any?>> {
|
|
99
|
+
val (suggestResult, storedUrl) = SuggestRegistry.lookup(suggestionId)
|
|
100
|
+
?: error("suggestionId $suggestionId not found — call suggest() first")
|
|
101
|
+
// Allow the caller to override the locator URL, but fall back to the one stored by suggest().
|
|
102
|
+
val url = params["locatorUrl"] as? String ?: storedUrl
|
|
103
|
+
return locators.getOrPut(url) { LocatorTask(url) }
|
|
104
|
+
.geocode(suggestResult).getOrThrow()
|
|
105
|
+
.map { serializeGeocodeResult(it) }
|
|
106
|
+
}
|
|
65
107
|
|
|
66
108
|
private fun buildSuggestParameters(params: Map<String, Any?>): SuggestParameters = SuggestParameters().apply {
|
|
67
109
|
(params["maxResults"] as? Number)?.toInt()?.let { maxResults = it }
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
3
|
import com.arcgismaps.geometry.Envelope
|
|
4
|
+
import com.arcgismaps.geometry.GeodesicEllipseParameters
|
|
5
|
+
import com.arcgismaps.geometry.GeodesicSectorParameters
|
|
4
6
|
import com.arcgismaps.geometry.Geometry
|
|
5
7
|
import com.arcgismaps.geometry.GeometryEngine
|
|
6
8
|
import com.arcgismaps.geometry.Multipart
|
|
@@ -256,3 +258,80 @@ internal fun geScale(
|
|
|
256
258
|
else GeometryEngine.scale(geometry, factorX, factorY),
|
|
257
259
|
)
|
|
258
260
|
}
|
|
261
|
+
|
|
262
|
+
// region Z / M builders
|
|
263
|
+
|
|
264
|
+
internal fun geWithZ(g: Map<String, Any?>, z: Double): Map<String, Any?>? =
|
|
265
|
+
parseGeo(g)?.let { encode(GeometryEngine.createWithZOrNull(it, z)) }
|
|
266
|
+
|
|
267
|
+
internal fun geWithM(g: Map<String, Any?>, m: Double): Map<String, Any?>? =
|
|
268
|
+
parseGeo(g)?.let { encode(GeometryEngine.createWithMOrNull(it, m)) }
|
|
269
|
+
|
|
270
|
+
internal fun geWithZAndM(g: Map<String, Any?>, z: Double, m: Double): Map<String, Any?>? =
|
|
271
|
+
parseGeo(g)?.let { encode(GeometryEngine.createWithZAndMOrNull(it, z, m)) }
|
|
272
|
+
|
|
273
|
+
// region Geodesic construction
|
|
274
|
+
|
|
275
|
+
internal fun geEllipseGeodesic(params: Map<String, Any?>): Map<String, Any?>? {
|
|
276
|
+
val centerDict = params["center"] as? Map<String, Any?> ?: return null
|
|
277
|
+
val center = parsePoint(centerDict) ?: return null
|
|
278
|
+
|
|
279
|
+
val semiAxis1Length = (params["semiAxis1Length"] as? Number)?.toDouble() ?: 0.0
|
|
280
|
+
val semiAxis2Length = (params["semiAxis2Length"] as? Number)?.toDouble() ?: 0.0
|
|
281
|
+
val axisDirection = (params["axisDirection"] as? Number)?.toDouble() ?: 0.0
|
|
282
|
+
val angUnit = angularUnit(params["angularUnit"] as? String)
|
|
283
|
+
val linUnit = linearUnit(params["linearUnit"] as? String)
|
|
284
|
+
val maxSegLen = (params["maxSegmentLength"] as? Number)?.toDouble() ?: 0.0
|
|
285
|
+
val maxPtCount = (params["maxPointCount"] as? Number)?.toLong() ?: 10L
|
|
286
|
+
val geoType = params["geometryType"] as? String
|
|
287
|
+
|
|
288
|
+
val p: GeodesicEllipseParameters = when (geoType) {
|
|
289
|
+
"polyline" -> GeodesicEllipseParameters.Companion.createForPolyline()
|
|
290
|
+
"multipoint" -> GeodesicEllipseParameters.Companion.createForMultipoint()
|
|
291
|
+
else -> GeodesicEllipseParameters.Companion.createForPolygon()
|
|
292
|
+
}
|
|
293
|
+
p.center = center
|
|
294
|
+
p.semiAxis1Length = semiAxis1Length
|
|
295
|
+
p.semiAxis2Length = semiAxis2Length
|
|
296
|
+
p.axisDirection = axisDirection
|
|
297
|
+
p.angularUnit = angUnit
|
|
298
|
+
p.linearUnit = linUnit
|
|
299
|
+
p.maxSegmentLength = maxSegLen
|
|
300
|
+
p.maxPointCount = maxPtCount
|
|
301
|
+
|
|
302
|
+
return encode(GeometryEngine.ellipseGeodesicOrNull(p))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
internal fun geSectorGeodesic(params: Map<String, Any?>): Map<String, Any?>? {
|
|
306
|
+
val centerDict = params["center"] as? Map<String, Any?> ?: return null
|
|
307
|
+
val center = parsePoint(centerDict) ?: return null
|
|
308
|
+
|
|
309
|
+
val semiAxis1Length = (params["semiAxis1Length"] as? Number)?.toDouble() ?: 0.0
|
|
310
|
+
val semiAxis2Length = (params["semiAxis2Length"] as? Number)?.toDouble() ?: 0.0
|
|
311
|
+
val axisDirection = (params["axisDirection"] as? Number)?.toDouble() ?: 0.0
|
|
312
|
+
val sectorAngle = (params["sectorAngle"] as? Number)?.toDouble() ?: 0.0
|
|
313
|
+
val startDirection = (params["startDirection"] as? Number)?.toDouble() ?: 0.0
|
|
314
|
+
val angUnit = angularUnit(params["angularUnit"] as? String)
|
|
315
|
+
val linUnit = linearUnit(params["linearUnit"] as? String)
|
|
316
|
+
val maxSegLen = (params["maxSegmentLength"] as? Number)?.toDouble() ?: 0.0
|
|
317
|
+
val maxPtCount = (params["maxPointCount"] as? Number)?.toLong() ?: 10L
|
|
318
|
+
val geoType = params["geometryType"] as? String
|
|
319
|
+
|
|
320
|
+
val p: GeodesicSectorParameters = when (geoType) {
|
|
321
|
+
"polyline" -> GeodesicSectorParameters.Companion.createForPolyline()
|
|
322
|
+
"multipoint" -> GeodesicSectorParameters.Companion.createForMultipoint()
|
|
323
|
+
else -> GeodesicSectorParameters.Companion.createForPolygon()
|
|
324
|
+
}
|
|
325
|
+
p.center = center
|
|
326
|
+
p.semiAxis1Length = semiAxis1Length
|
|
327
|
+
p.semiAxis2Length = semiAxis2Length
|
|
328
|
+
p.axisDirection = axisDirection
|
|
329
|
+
p.sectorAngle = sectorAngle
|
|
330
|
+
p.startDirection = startDirection
|
|
331
|
+
p.angularUnit = angUnit
|
|
332
|
+
p.linearUnit = linUnit
|
|
333
|
+
p.maxSegmentLength = maxSegLen
|
|
334
|
+
p.maxPointCount = maxPtCount
|
|
335
|
+
|
|
336
|
+
return encode(GeometryEngine.sectorGeodesicOrNull(p))
|
|
337
|
+
}
|
|
@@ -4,12 +4,16 @@ import com.arcgismaps.data.FeatureCollectionTable
|
|
|
4
4
|
import com.arcgismaps.mapping.view.Graphic
|
|
5
5
|
import com.arcgismaps.tasks.geoprocessing.GeoprocessingTask
|
|
6
6
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingBoolean
|
|
7
|
+
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingDataFile
|
|
7
8
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingDate
|
|
9
|
+
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingRaster
|
|
8
10
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingDouble
|
|
9
11
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingFeatures
|
|
10
12
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingLinearUnit
|
|
11
13
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingLong
|
|
14
|
+
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingMultiValue
|
|
12
15
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingParameter
|
|
16
|
+
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingParameterType
|
|
13
17
|
import com.arcgismaps.tasks.geoprocessing.geoprocessingparameters.GeoprocessingString
|
|
14
18
|
import expo.modules.kotlin.AppContext
|
|
15
19
|
import java.time.Instant
|
|
@@ -60,6 +64,36 @@ private fun buildGeoprocessingParameter(d: Map<*, *>): GeoprocessingParameter? =
|
|
|
60
64
|
?.map { Graphic().apply { geometry = it } } ?: emptyList()
|
|
61
65
|
GeoprocessingFeatures(FeatureCollectionTable(graphics, emptyList()))
|
|
62
66
|
}
|
|
67
|
+
"multiValue" -> {
|
|
68
|
+
// JS numbers → GeoprocessingDouble, JS strings → GeoprocessingString.
|
|
69
|
+
val rawValues = d["values"] as? List<*> ?: emptyList<Any>()
|
|
70
|
+
val elements: List<GeoprocessingParameter> = rawValues.mapNotNull { v ->
|
|
71
|
+
when (v) {
|
|
72
|
+
is Number -> GeoprocessingDouble(v.toDouble())
|
|
73
|
+
is String -> GeoprocessingString(v)
|
|
74
|
+
else -> null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Use the element type of the first item to determine the multiValue's parameterType;
|
|
78
|
+
// fall back to GeoprocessingParameterType.GeoprocessingString when the list is empty.
|
|
79
|
+
val paramType = if (elements.firstOrNull() is GeoprocessingDouble)
|
|
80
|
+
GeoprocessingParameterType.GeoprocessingDouble
|
|
81
|
+
else
|
|
82
|
+
GeoprocessingParameterType.GeoprocessingString
|
|
83
|
+
GeoprocessingMultiValue(paramType, elements)
|
|
84
|
+
}
|
|
85
|
+
"dataFile" -> {
|
|
86
|
+
val filePath = d["filePath"] as? String
|
|
87
|
+
if (filePath != null) {
|
|
88
|
+
// Local-file path — use GeoprocessingDataFile.inputFilePath (a settable property).
|
|
89
|
+
val df = GeoprocessingDataFile.Companion.create()
|
|
90
|
+
df.inputFilePath = filePath
|
|
91
|
+
df
|
|
92
|
+
} else {
|
|
93
|
+
val url = d["url"] as? String ?: return null
|
|
94
|
+
GeoprocessingDataFile.Companion.createWithUrl(url)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
63
97
|
else -> null
|
|
64
98
|
}
|
|
65
99
|
|
|
@@ -80,5 +114,12 @@ private suspend fun serializeGeoprocessingParameter(param: GeoprocessingParamete
|
|
|
80
114
|
if (param.canFetchOutputFeatures) param.fetchOutputFeatures().getOrThrow()
|
|
81
115
|
param.features?.map { serializeFeature(it) } ?: emptyList<Map<String, Any?>>()
|
|
82
116
|
}
|
|
117
|
+
// GeoprocessingRaster extends GeoprocessingDataFile — must come before any GeoprocessingDataFile branch.
|
|
118
|
+
is GeoprocessingRaster -> {
|
|
119
|
+
val result = mutableMapOf<String, Any?>("type" to "raster")
|
|
120
|
+
param.url?.let { result["url"] = it }
|
|
121
|
+
param.inputFilePath?.let { result["filePath"] = it }
|
|
122
|
+
result
|
|
123
|
+
}
|
|
83
124
|
else -> null
|
|
84
125
|
}
|