expo-arcgis 0.2.6 → 0.3.0

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.
Files changed (57) hide show
  1. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisExtrasModule.kt +3 -5
  2. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +8 -0
  3. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +9 -0
  4. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +20 -1
  5. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +10 -0
  6. package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +14 -0
  7. package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +85 -6
  8. package/android/src/main/java/expo/modules/arcgis/NetworkTrustHandler.kt +26 -0
  9. package/android/src/main/java/expo/modules/arcgis/OfflineFunctions.kt +12 -4
  10. package/android/src/main/java/expo/modules/arcgis/ServiceAreaFunctions.kt +61 -0
  11. package/build/DynamicEntityLayer.d.ts.map +1 -1
  12. package/build/ExpoArcgis.types.d.ts +101 -2
  13. package/build/ExpoArcgis.types.d.ts.map +1 -1
  14. package/build/ExpoArcgis.types.js.map +1 -1
  15. package/build/ExpoArcgisGeometryModule.d.ts +3 -1
  16. package/build/ExpoArcgisGeometryModule.d.ts.map +1 -1
  17. package/build/ExpoArcgisGeometryModule.js.map +1 -1
  18. package/build/ExpoArcgisModule.d.ts +23 -1
  19. package/build/ExpoArcgisModule.d.ts.map +1 -1
  20. package/build/ExpoArcgisModule.js.map +1 -1
  21. package/build/FeatureLayer.d.ts.map +1 -1
  22. package/build/auth.d.ts +12 -0
  23. package/build/auth.d.ts.map +1 -1
  24. package/build/auth.js +14 -0
  25. package/build/auth.js.map +1 -1
  26. package/build/index.d.ts +2 -1
  27. package/build/index.d.ts.map +1 -1
  28. package/build/index.js +2 -1
  29. package/build/index.js.map +1 -1
  30. package/build/layers.d.ts +1 -0
  31. package/build/layers.d.ts.map +1 -1
  32. package/build/offline.d.ts +10 -2
  33. package/build/offline.d.ts.map +1 -1
  34. package/build/offline.js +5 -2
  35. package/build/offline.js.map +1 -1
  36. package/build/serviceArea.d.ts +15 -0
  37. package/build/serviceArea.d.ts.map +1 -0
  38. package/build/serviceArea.js +15 -0
  39. package/build/serviceArea.js.map +1 -0
  40. package/ios/ExpoArcgisExtrasModule.swift +3 -5
  41. package/ios/ExpoArcgisGeometryModule.swift +8 -0
  42. package/ios/ExpoArcgisMapView.swift +19 -1
  43. package/ios/ExpoArcgisModule.swift +22 -1
  44. package/ios/ExpoArcgisSceneView.swift +14 -0
  45. package/ios/GraphicsOverlayRef.swift +15 -0
  46. package/ios/LayerRef.swift +85 -5
  47. package/ios/NetworkTrustHandler.swift +18 -0
  48. package/ios/OfflineFunctions.swift +15 -4
  49. package/ios/ServiceAreaFunctions.swift +68 -0
  50. package/package.json +1 -1
  51. package/src/ExpoArcgis.types.ts +103 -2
  52. package/src/ExpoArcgisGeometryModule.ts +12 -0
  53. package/src/ExpoArcgisModule.ts +20 -1
  54. package/src/auth.ts +15 -0
  55. package/src/index.ts +2 -0
  56. package/src/offline.ts +16 -7
  57. package/src/serviceArea.ts +17 -0
@@ -47,6 +47,9 @@ class ExpoArcgisExtrasModule : Module() {
47
47
  AsyncFunction("addFeatureWithSubtype") Coroutine { ref: FeatureLayerRef, subtypeName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean? ->
48
48
  ref.addFeatureWithSubtype(subtypeName, attributes, geometry, apply)
49
49
  }
50
+ AsyncFunction("contingentValues") Coroutine { ref: FeatureLayerRef, attributes: Map<String, Any?>, fieldName: String ->
51
+ ref.contingentValues(attributes, fieldName)
52
+ }
50
53
  AsyncFunction("getContingentValues") Coroutine { ref: FeatureLayerRef, objectId: Long, fieldName: String ->
51
54
  ref.getContingentValues(objectId, fieldName)
52
55
  }
@@ -93,11 +96,6 @@ class ExpoArcgisExtrasModule : Module() {
93
96
  .getOrThrow()
94
97
  }
95
98
 
96
- // Tile-cache size estimation — quick estimate before committing to a download.
97
- AsyncFunction("estimateTileCacheSize") Coroutine { tileServiceUrl: String, areaOfInterest: Map<String, Any?>, options: Map<String, Any?>? ->
98
- estimateTileCacheSize(tileServiceUrl, areaOfInterest, options)
99
- }
100
-
101
99
  // Turn-by-turn navigation — solve a route and track device locations against it.
102
100
  AsyncFunction("createRouteTracker") Coroutine { stops: List<Map<String, Any?>>, params: Map<String, Any?> ->
103
101
  createRouteTracker(appContext, stops, params)
@@ -96,6 +96,11 @@ class ExpoArcgisGeometryModule : Module() {
96
96
  solveRoute(stops, params)
97
97
  }
98
98
 
99
+ // Service Area — compute reachable areas from facilities, exposed as the JS `serviceArea` namespace.
100
+ AsyncFunction("serviceAreaSolve") Coroutine { params: Map<String, Any?> ->
101
+ serviceAreaSolve(params)
102
+ }
103
+
99
104
  // Geoprocessing — run a geoprocessing service, exposed as the JS `geoprocessor` namespace.
100
105
  AsyncFunction("executeGeoprocessing") Coroutine { serviceUrl: String, inputs: Map<String, Any?> ->
101
106
  executeGeoprocessing(appContext, serviceUrl, inputs)
@@ -215,5 +220,8 @@ class ExpoArcgisGeometryModule : Module() {
215
220
  AsyncFunction("exportVectorTiles") Coroutine { vectorTileServiceUrl: String, areaOfInterest: Map<String, Any?>, downloadName: String ->
216
221
  exportVectorTiles(appContext, appContext.reactContext?.filesDir, vectorTileServiceUrl, areaOfInterest, downloadName)
217
222
  }
223
+ AsyncFunction("estimateTileCacheSize") Coroutine { tileServiceUrl: String, areaOfInterest: Map<String, Any?>, minScale: Double?, maxScale: Double? ->
224
+ estimateTileCacheSize(tileServiceUrl, areaOfInterest, minScale, maxScale)
225
+ }
218
226
  }
219
227
  }
@@ -15,6 +15,7 @@ import com.arcgismaps.location.SimulatedLocationDataSource
15
15
  import com.arcgismaps.location.SimulationParameters
16
16
  import com.arcgismaps.location.SystemLocationDataSource
17
17
  import java.time.Instant
18
+ import com.arcgismaps.mapping.TimeExtent
18
19
  import com.arcgismaps.mapping.Viewpoint
19
20
  import com.arcgismaps.mapping.view.MapView
20
21
  import com.arcgismaps.mapping.view.ScreenCoordinate
@@ -151,6 +152,14 @@ class ExpoArcgisMapView(context: Context, appContext: AppContext) : ExpoView(con
151
152
  ?: com.arcgismaps.mapping.view.LatitudeLongitudeGrid().apply { isVisible = false }
152
153
  }
153
154
 
155
+ /** Filters time-aware layers to a time window from JS (null shows all time steps). */
156
+ fun setTimeExtent(config: Map<String, Any?>?) {
157
+ if (config == null) { mapView.setTimeExtent(null); return }
158
+ val startMs = (config["startTime"] as? Number)?.toLong() ?: return
159
+ val endMs = (config["endTime"] as? Number)?.toLong() ?: return
160
+ mapView.setTimeExtent(TimeExtent(Instant.ofEpochMilli(startMs), Instant.ofEpochMilli(endMs)))
161
+ }
162
+
154
163
  /** Enables/configures the device location display from JS (null disables it). */
155
164
  fun setLocationDisplay(config: Map<String, Any?>?) {
156
165
  val locationDisplay = mapView.locationDisplay
@@ -40,6 +40,13 @@ class ExpoArcgisModule : Module() {
40
40
  AuthChallengeHandler.setCredentials(username, password, tokenExpirationMinutes)
41
41
  }
42
42
 
43
+ // Network-layer server-trust override — for on-prem ArcGIS Enterprise with self-signed certs.
44
+ // NEVER enable in production: disabling TLS validation exposes connections to MITM attacks.
45
+ Function("setAllowUntrustedHosts") { allow: Boolean ->
46
+ ArcGISEnvironment.authenticationManager.networkAuthenticationChallengeHandler =
47
+ if (allow) NetworkTrustHandler else null
48
+ }
49
+
43
50
  // Revokes any OAuth user credentials on the server, then clears all cached credentials.
44
51
  AsyncFunction("signOut") Coroutine { ->
45
52
  val store = ArcGISEnvironment.authenticationManager.arcGISCredentialStore
@@ -210,7 +217,8 @@ class ExpoArcgisModule : Module() {
210
217
  Constructor { props: Map<String, Any?> ->
211
218
  @Suppress("UNCHECKED_CAST")
212
219
  val source = props["source"] as? Map<String, Any?> ?: emptyMap()
213
- RasterLayerRef(appContext, source).also { it.applyProps(props) }
220
+ val rasterFunctionJson = props["rasterFunction"] as? String
221
+ RasterLayerRef(appContext, source, rasterFunctionJson).also { it.applyProps(props) }
214
222
  }
215
223
  Function("applyProps") { ref: RasterLayerRef, changed: Map<String, Any?> ->
216
224
  ref.applyProps(changed)
@@ -225,6 +233,9 @@ class ExpoArcgisModule : Module() {
225
233
  ref.applyProps(changed)
226
234
  }
227
235
  AsyncFunction("getNodes") Coroutine { ref: KmlLayerRef -> ref.getNodes() }
236
+ Function("playTour") { ref: KmlLayerRef -> ref.playTour() }
237
+ Function("pauseTour") { ref: KmlLayerRef -> ref.pauseTour() }
238
+ Function("resetTour") { ref: KmlLayerRef -> ref.resetTour() }
228
239
  }
229
240
 
230
241
  // WFS (Web Feature Service) + OGC API - Features — feature layers over async-populating tables.
@@ -385,6 +396,10 @@ class ExpoArcgisModule : Module() {
385
396
  view.setGrid(grid)
386
397
  }
387
398
 
399
+ Prop("timeExtent") { view: ExpoArcgisMapView, value: Map<String, Any?>? ->
400
+ view.setTimeExtent(value)
401
+ }
402
+
388
403
  AsyncFunction("identify") { view: ExpoArcgisMapView, screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise ->
389
404
  view.identify(screenPoint, options, promise)
390
405
  }
@@ -451,6 +466,10 @@ class ExpoArcgisModule : Module() {
451
466
  view.setSunTime(value)
452
467
  }
453
468
 
469
+ Prop("timeExtent") { view: ExpoArcgisSceneView, value: Map<String, Any?>? ->
470
+ view.setTimeExtent(value)
471
+ }
472
+
454
473
  AsyncFunction("retryLoad") { view: ExpoArcgisSceneView, promise: Promise ->
455
474
  view.retryLoad(promise)
456
475
  }
@@ -7,7 +7,9 @@ import androidx.lifecycle.findViewTreeLifecycleOwner
7
7
  import com.arcgismaps.geometry.GeometryEngine
8
8
  import com.arcgismaps.geometry.Point
9
9
  import com.arcgismaps.geometry.SpatialReference
10
+ import com.arcgismaps.mapping.TimeExtent
10
11
  import com.arcgismaps.mapping.view.AtmosphereEffect
12
+ import java.time.Instant
11
13
  import com.arcgismaps.mapping.view.Camera
12
14
  import com.arcgismaps.mapping.view.GlobeCameraController
13
15
  import com.arcgismaps.mapping.view.LightingMode
@@ -230,6 +232,14 @@ class ExpoArcgisSceneView(context: Context, appContext: AppContext) : ExpoView(c
230
232
  sceneView.sunTime = java.time.Instant.ofEpochMilli(ms.toLong())
231
233
  }
232
234
 
235
+ /** Filters time-aware layers to a time window from JS (null shows all time steps). */
236
+ fun setTimeExtent(config: Map<String, Any?>?) {
237
+ if (config == null) { sceneView.setTimeExtent(null); return }
238
+ val startMs = (config["startTime"] as? Number)?.toLong() ?: return
239
+ val endMs = (config["endTime"] as? Number)?.toLong() ?: return
240
+ sceneView.setTimeExtent(TimeExtent(Instant.ofEpochMilli(startMs), Instant.ofEpochMilli(endMs)))
241
+ }
242
+
233
243
  override fun onAttachedToWindow() {
234
244
  super.onAttachedToWindow()
235
245
  val lifecycle = findViewTreeLifecycleOwner()?.lifecycle
@@ -18,8 +18,10 @@ import com.arcgismaps.mapping.symbology.SimpleLineSymbol
18
18
  import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
19
19
  import com.arcgismaps.mapping.symbology.DistanceCompositeSceneSymbol
20
20
  import com.arcgismaps.mapping.symbology.DistanceSymbolRange
21
+ import com.arcgismaps.mapping.symbology.ModelSceneSymbol
21
22
  import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
22
23
  import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
24
+ import com.arcgismaps.mapping.symbology.SymbolSizeUnits
23
25
  import com.arcgismaps.mapping.symbology.MultilayerPointSymbol
24
26
  import com.arcgismaps.mapping.symbology.MultilayerPolygonSymbol
25
27
  import com.arcgismaps.mapping.symbology.MultilayerPolylineSymbol
@@ -346,6 +348,18 @@ private fun buildSymbol(s: Map<*, *>): Symbol? = when (s["type"]) {
346
348
  num(s["depth"], 100.0),
347
349
  sceneSymbolAnchor(s["anchor"]),
348
350
  )
351
+ "model-scene" -> (s["url"] as? String)?.let { uri ->
352
+ // ModelSceneSymbol(uri, scale) — 3D glTF/model marker. Set heading/pitch/roll (Float)
353
+ // and symbolSizeUnits after construction; the symbol is Loadable.
354
+ val scale = (s["scale"] as? Number)?.toFloat() ?: 1f
355
+ ModelSceneSymbol(uri, scale).apply {
356
+ symbolSizeUnits = if (s["sizeUnits"] == "meters") SymbolSizeUnits.Meters else SymbolSizeUnits.Dips
357
+ heading = (s["heading"] as? Number)?.toFloat() ?: 0f
358
+ pitch = (s["pitch"] as? Number)?.toFloat() ?: 0f
359
+ roll = (s["roll"] as? Number)?.toFloat() ?: 0f
360
+ anchorPosition = sceneSymbolAnchor(s["anchor"])
361
+ }
362
+ }
349
363
  "picture-marker" -> (s["url"] as? String)?.let { url ->
350
364
  PictureMarkerSymbol(url).apply {
351
365
  (s["width"] as? Number)?.toFloat()?.let { width = it }
@@ -57,9 +57,12 @@ import com.arcgismaps.mapping.layers.WmtsLayer
57
57
  import com.arcgismaps.mapping.kml.KmlContainer
58
58
  import com.arcgismaps.mapping.kml.KmlDataset
59
59
  import com.arcgismaps.mapping.kml.KmlNode
60
+ import com.arcgismaps.mapping.kml.KmlTour
61
+ import com.arcgismaps.mapping.kml.KmlTourController
60
62
  import com.arcgismaps.mapping.view.Graphic
61
63
  import com.arcgismaps.raster.ImageServiceRaster
62
64
  import com.arcgismaps.raster.Raster
65
+ import com.arcgismaps.raster.RasterFunction
63
66
  import expo.modules.kotlin.AppContext
64
67
  import expo.modules.kotlin.sharedobjects.SharedObject
65
68
 
@@ -290,6 +293,38 @@ class FeatureLayerRef(appContext: AppContext, private val table: FeatureTable) :
290
293
  persistEdits()
291
294
  }
292
295
 
296
+ /**
297
+ * Returns the valid coded values for [fieldName] given a feature's current [attributes].
298
+ * Use this to drive editing-form dropdowns: pass the feature's current attribute state and the
299
+ * name of the field being edited; returns `[{ name, code }]` for coded values that satisfy all
300
+ * contingent-value constraints defined on the table.
301
+ *
302
+ * Loads the table and its [ContingentValuesDefinition], builds a temporary [ArcGISFeature] from
303
+ * [attributes] (via `FeatureTable.createFeature(attributes, null)` + `applyAttributes`), then
304
+ * calls `getContingentValuesOrNull(feature, fieldName)`.
305
+ * Non-coded contingent values ([ContingentRangeValue], null, any) are omitted — only
306
+ * [ContingentCodedValue] entries are returned.
307
+ *
308
+ * Requires an [ArcGISFeatureTable]; throws for shapefiles and WFS tables.
309
+ * Returns an empty list when no constraints are defined for [fieldName].
310
+ */
311
+ suspend fun contingentValues(attributes: Map<String, Any?>, fieldName: String): List<Map<String, Any?>> {
312
+ table.load().getOrThrow()
313
+ val arcGISTable = table as? ArcGISFeatureTable
314
+ ?: throw IllegalStateException("contingentValues requires an ArcGIS feature table (not a shapefile or WFS table)")
315
+ arcGISTable.contingentValuesDefinition.load().getOrThrow()
316
+ val feature = table.createFeature(attributes, null) as ArcGISFeature
317
+ val result = arcGISTable.getContingentValuesOrNull(feature, fieldName)
318
+ ?: return emptyList()
319
+ return result.byFieldGroup.flatMap { (_, values) ->
320
+ values.mapNotNull { cv ->
321
+ if (cv !is ContingentCodedValue) return@mapNotNull null
322
+ val coded = cv.codedValue
323
+ mapOf("name" to coded.name, "code" to coded.code)
324
+ }
325
+ }
326
+ }
327
+
293
328
  /**
294
329
  * Returns the valid contingent values for [fieldName] on the feature with [objectId].
295
330
  * Requires the table to be an [ArcGISFeatureTable]; throws otherwise. Returns an empty list when
@@ -537,21 +572,41 @@ class WmtsLayerRef(appContext: AppContext, url: String, layerId: String) : Layer
537
572
  }
538
573
 
539
574
  /** Operational raster layer from a remote image service or a local raster file. */
540
- class RasterLayerRef(appContext: AppContext, source: Map<String, Any?>) : LayerRef(appContext) {
541
- override val layer: RasterLayer = RasterLayer(rasterFromSource(source))
575
+ class RasterLayerRef(
576
+ appContext: AppContext,
577
+ source: Map<String, Any?>,
578
+ rasterFunctionJson: String? = null,
579
+ ) : LayerRef(appContext) {
580
+ override val layer: RasterLayer = RasterLayer(rasterFromSource(source, rasterFunctionJson))
542
581
 
543
582
  override fun applyProps(changed: Map<String, Any?>) = applyCommonProps(changed)
544
583
  }
545
584
 
546
- /** Builds a [Raster] from a JS source dict: `{type:"imageService",url}` or `{type:"file",path}`. */
547
- private fun rasterFromSource(s: Map<String, Any?>): Raster =
548
- if (s["type"] == "file") Raster.createWithPath(s["path"] as? String ?: "")
549
- else ImageServiceRaster(s["url"] as? String ?: "")
585
+ /**
586
+ * Builds a [Raster] from a JS source dict: `{type:"imageService",url}` or `{type:"file",path}`.
587
+ * When [rasterFunctionJson] is supplied, wraps the base raster in a [RasterFunction] pipeline:
588
+ * `RasterFunction.fromJsonOrNull` → wire source raster as first raster argument →
589
+ * `Raster.createWithRasterFunction`. Falls back to the plain source raster if parsing fails
590
+ * or no raster argument is found.
591
+ */
592
+ private fun rasterFromSource(s: Map<String, Any?>, rasterFunctionJson: String? = null): Raster {
593
+ val base: Raster =
594
+ if (s["type"] == "file") Raster.createWithPath(s["path"] as? String ?: "")
595
+ else ImageServiceRaster(s["url"] as? String ?: "")
596
+ if (rasterFunctionJson == null) return base
597
+ val fn = RasterFunction.fromJsonOrNull(rasterFunctionJson) ?: return base
598
+ val args = fn.arguments ?: return base
599
+ val firstName = args.rasterNames.firstOrNull() ?: return base
600
+ args.setRaster(firstName, base)
601
+ return Raster.createWithRasterFunction(fn)
602
+ }
550
603
 
551
604
  /** Operational KML layer from a remote .kml/.kmz URL or local file. */
552
605
  class KmlLayerRef(appContext: AppContext, url: String) : LayerRef(appContext) {
553
606
  private val dataset = KmlDataset(url)
554
607
  override val layer: KmlLayer = KmlLayer(dataset)
608
+ /** Lazily-created controller; held for the lifetime of the ref so play/pause/reset are idempotent. */
609
+ private var tourController: KmlTourController? = null
555
610
 
556
611
  override fun applyProps(changed: Map<String, Any?>) = applyCommonProps(changed)
557
612
 
@@ -560,6 +615,21 @@ class KmlLayerRef(appContext: AppContext, url: String) : LayerRef(appContext) {
560
615
  dataset.load().getOrThrow()
561
616
  return dataset.rootNodes.map { serializeKmlNode(it) }
562
617
  }
618
+
619
+ /** Returns the existing controller, or creates one attached to the first [KmlTour] in the node tree.
620
+ * Returns `null` if the dataset has no tour. */
621
+ private fun controller(): KmlTourController? {
622
+ tourController?.let { return it }
623
+ val tour = findFirstTour(dataset.rootNodes) ?: return null
624
+ return KmlTourController().also { it.tour = tour; tourController = it }
625
+ }
626
+
627
+ /** Starts or resumes playback of the first KML tour. No-ops if no tour exists. */
628
+ fun playTour() { controller()?.play() }
629
+ /** Pauses playback of the first KML tour. No-ops if no tour exists. */
630
+ fun pauseTour() { controller()?.pause() }
631
+ /** Resets playback of the first KML tour to the beginning. No-ops if no tour exists. */
632
+ fun resetTour() { controller()?.reset() }
563
633
  }
564
634
 
565
635
  /** Serializes a KML node, recursing into container nodes (documents / folders). */
@@ -575,6 +645,15 @@ private fun serializeKmlNode(node: KmlNode): Map<String, Any?> {
575
645
  return dict
576
646
  }
577
647
 
648
+ /** Depth-first search for the first [KmlTour] in a list of KML nodes. */
649
+ private fun findFirstTour(nodes: List<KmlNode>): KmlTour? {
650
+ for (node in nodes) {
651
+ if (node is KmlTour) return node
652
+ if (node is KmlContainer) findFirstTour(node.childNodes)?.let { return it }
653
+ }
654
+ return null
655
+ }
656
+
578
657
  /** Operational WFS layer — a [FeatureLayer] over a [WfsFeatureTable] (Web Feature Service). */
579
658
  class WfsLayerRef(appContext: AppContext, url: String, tableName: String) : LayerRef(appContext) {
580
659
  override val layer: FeatureLayer = FeatureLayer.createWithFeatureTable(
@@ -0,0 +1,26 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.httpcore.authentication.NetworkAuthenticationChallenge
4
+ import com.arcgismaps.httpcore.authentication.NetworkAuthenticationChallengeHandler
5
+ import com.arcgismaps.httpcore.authentication.NetworkAuthenticationChallengeResponse
6
+ import com.arcgismaps.httpcore.authentication.NetworkAuthenticationType
7
+ import com.arcgismaps.httpcore.authentication.ServerTrust
8
+
9
+ /**
10
+ * Network-layer trust handler that accepts every server-trust challenge unconditionally.
11
+ * Only install this for development or on-prem ArcGIS Enterprise with self-signed certificates —
12
+ * it disables TLS certificate validation, which makes the connection vulnerable to
13
+ * man-in-the-middle attacks. Never enable in production.
14
+ */
15
+ object NetworkTrustHandler : NetworkAuthenticationChallengeHandler {
16
+ override suspend fun handleNetworkAuthenticationChallenge(
17
+ challenge: NetworkAuthenticationChallenge,
18
+ ): NetworkAuthenticationChallengeResponse {
19
+ if (challenge.networkAuthenticationType is NetworkAuthenticationType.ServerTrust) {
20
+ return NetworkAuthenticationChallengeResponse.ContinueWithCredential(ServerTrust)
21
+ }
22
+ // Other challenge kinds (basic / digest / NTLM / client-cert) aren't ours to satisfy — continue
23
+ // without a credential (mirrors Swift's `.continueWithoutCredential`) instead of cancelling them.
24
+ return NetworkAuthenticationChallengeResponse.ContinueAndFail
25
+ }
26
+ }
@@ -209,20 +209,28 @@ internal suspend fun exportTileCache(
209
209
 
210
210
  /**
211
211
  * Estimates the on-disk file size and tile count for an [exportTileCache] download WITHOUT
212
- * downloading. Runs the [EstimateTileCacheSizeJob] to completion and returns `{fileSize, tileCount}`.
212
+ * downloading. Passes [minScale]/[maxScale] (0.0 = no limit) to
213
+ * [ExportTileCacheTask.createDefaultExportTileCacheParameters] to narrow the estimation to the
214
+ * same scale window that [exportTileCache] would use. Runs the [EstimateTileCacheSizeJob] to
215
+ * completion and returns `{fileSizeBytes, tileCount}`.
213
216
  */
214
217
  internal suspend fun estimateTileCacheSize(
215
218
  tileServiceUrl: String,
216
219
  areaOfInterest: Map<String, Any?>,
217
- @Suppress("UNUSED_PARAMETER") options: Map<String, Any?>?,
220
+ minScale: Double?,
221
+ maxScale: Double?,
218
222
  ): Map<String, Any?> {
219
223
  val area = geometryFromDict(areaOfInterest) ?: throw IllegalArgumentException("Invalid area of interest")
220
224
  val task = ExportTileCacheTask(tileServiceUrl)
221
- val parameters = task.createDefaultExportTileCacheParameters(area, 0.0, 0.0).getOrThrow()
225
+ val parameters = task.createDefaultExportTileCacheParameters(
226
+ area,
227
+ minScale ?: 0.0,
228
+ maxScale ?: 0.0,
229
+ ).getOrThrow()
222
230
  val job = task.createEstimateTileCacheSizeJob(parameters)
223
231
  job.start()
224
232
  val estimate = job.result().getOrThrow()
225
- return mapOf("fileSize" to estimate.fileSize, "tileCount" to estimate.tileCount)
233
+ return mapOf("fileSizeBytes" to estimate.fileSize.toDouble(), "tileCount" to estimate.tileCount.toDouble())
226
234
  }
227
235
 
228
236
  internal suspend fun exportVectorTiles(
@@ -0,0 +1,61 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.tasks.networkanalysis.ServiceAreaFacility
4
+ import com.arcgismaps.tasks.networkanalysis.ServiceAreaPolygon
5
+ import com.arcgismaps.tasks.networkanalysis.ServiceAreaResult
6
+ import com.arcgismaps.tasks.networkanalysis.ServiceAreaTask
7
+ import com.arcgismaps.geometry.Point
8
+ import java.util.concurrent.ConcurrentHashMap
9
+
10
+ /**
11
+ * Free functions backing the JS `serviceArea` namespace — computing reachable areas from
12
+ * facilities via a [ServiceAreaTask]. Registered as an `AsyncFunction` in
13
+ * `ExpoArcgisGeometryModule`.
14
+ */
15
+
16
+ /** Caches one [ServiceAreaTask] per service URL so repeated solves don't reload service metadata. */
17
+ private val serviceAreaTasks = ConcurrentHashMap<String, ServiceAreaTask>()
18
+
19
+ private fun serviceAreaTask(url: String): ServiceAreaTask =
20
+ serviceAreaTasks.getOrPut(url) { ServiceAreaTask(url) }
21
+
22
+ internal suspend fun serviceAreaSolve(params: Map<String, Any?>): Map<String, Any?> {
23
+ val serviceUrl = params["serviceUrl"] as? String
24
+ ?: error("serviceUrl is required")
25
+ val task = serviceAreaTask(serviceUrl)
26
+ // createDefaultParameters() loads the task so the service metadata is populated afterwards.
27
+ val parameters = task.createDefaultParameters().getOrThrow()
28
+ val facilitiesDicts = params["facilities"] as? List<*> ?: emptyList<Any?>()
29
+ parameters.setFacilities(buildFacilities(facilitiesDicts))
30
+ (params["cutoffs"] as? List<*>)?.let { rawCutoffs ->
31
+ val cutoffs = rawCutoffs.mapNotNull { (it as? Number)?.toDouble() }
32
+ if (cutoffs.isNotEmpty()) {
33
+ // ServiceAreaParameters.defaultImpedanceCutoffs is a mutable live list (no setter / add API).
34
+ parameters.defaultImpedanceCutoffs.clear()
35
+ parameters.defaultImpedanceCutoffs.addAll(cutoffs)
36
+ }
37
+ }
38
+ val result = task.solveServiceArea(parameters).getOrThrow()
39
+ return serializeServiceAreaResult(result)
40
+ }
41
+
42
+ private fun buildFacilities(dicts: List<*>): List<ServiceAreaFacility> =
43
+ dicts.filterIsInstance<Map<*, *>>().mapNotNull { dict ->
44
+ (geometryFromDict(dict) as? Point)?.let { ServiceAreaFacility(it) }
45
+ }
46
+
47
+ private fun serializeServiceAreaResult(result: ServiceAreaResult): Map<String, Any?> {
48
+ val polygons = mutableListOf<Map<String, Any?>>()
49
+ for ((index, _) in result.facilities.withIndex()) {
50
+ for (polygon in result.getResultPolygons(index)) {
51
+ polygons.add(serializeServiceAreaPolygon(polygon))
52
+ }
53
+ }
54
+ return mapOf("polygons" to polygons)
55
+ }
56
+
57
+ private fun serializeServiceAreaPolygon(polygon: ServiceAreaPolygon): Map<String, Any?> = mapOf(
58
+ "geometry" to dictFromGeometry(polygon.geometry),
59
+ "fromCutoff" to polygon.fromImpedanceCutoff,
60
+ "toCutoff" to polygon.toImpedanceCutoff,
61
+ )
@@ -1 +1 @@
1
- {"version":3,"file":"DynamicEntityLayer.d.ts","sourceRoot":"","sources":["../src/DynamicEntityLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EAEzB,MAAM,oBAAoB,CAAC;AAQ5B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;mBAwD42qC,CAAC;gBAAkB,CAAC;;;;;;;4DAD95qC,CAAC"}
1
+ {"version":3,"file":"DynamicEntityLayer.d.ts","sourceRoot":"","sources":["../src/DynamicEntityLayer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EAEzB,MAAM,oBAAoB,CAAC;AAQ5B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;mBAwDoguC,CAAC;gBAAkB,CAAC;;;;;;;4DADtjuC,CAAC"}
@@ -131,6 +131,15 @@ export type MapViewProps = {
131
131
  viewpoint?: Viewpoint;
132
132
  /** Device-location display. When set, the view shows the device's GPS location. */
133
133
  locationDisplay?: LocationDisplay;
134
+ /**
135
+ * Filters time-aware layers to this time window. Both values are epoch milliseconds (UTC).
136
+ * Pass `null` / omit to show all time steps (no filter). Maps to `GeoView.timeExtent` /
137
+ * `MapView(timeExtent:)` on both platforms.
138
+ */
139
+ timeExtent?: {
140
+ startTime: number;
141
+ endTime: number;
142
+ } | null;
134
143
  /** Called once the map has finished loading successfully. */
135
144
  onMapLoaded?: (event: {
136
145
  nativeEvent: MapLoadedEventPayload;
@@ -640,6 +649,26 @@ export type FeatureLayerHandle = {
640
649
  * feature layer. The same handle is returned on repeat calls.
641
650
  */
642
651
  getServiceGeodatabase(): Promise<ServiceGeodatabaseHandle>;
652
+ /**
653
+ * Returns the valid coded values for `fieldName` given the feature's current `attributes`.
654
+ * Use this to drive editing-form dropdowns: pass the feature's current attribute state and the
655
+ * name of the field being edited; returns `[{ name, code }]` for coded values that satisfy all
656
+ * contingent-value constraints defined on the table.
657
+ *
658
+ * The table and its `ContingentValuesDefinition` are loaded automatically. A temporary feature
659
+ * is built from `attributes` (no server round-trip for the feature itself); only `ArcGISFeature`
660
+ * tables are supported — rejects for shapefiles and WFS tables.
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * const options = await layer.current.contingentValues({ habitat: 'oak' }, 'species');
665
+ * // options → [{ name: 'Red Oak', code: 1 }, { name: 'White Oak', code: 2 }]
666
+ * ```
667
+ */
668
+ contingentValues(attributes: Record<string, unknown>, fieldName: string): Promise<{
669
+ name: string;
670
+ code: unknown;
671
+ }[]>;
643
672
  /**
644
673
  * Returns the valid contingent values for `fieldName` on the feature with `objectId`, given
645
674
  * the feature's current attribute state. Contingent values constrain which attribute values
@@ -811,6 +840,14 @@ export type RasterSource = {
811
840
  /** Props for a `<RasterLayer>` — mirror the native `RasterLayer` (image service or local raster). */
812
841
  export type RasterLayerProps = LayerProps & {
813
842
  source: RasterSource;
843
+ /**
844
+ * On-the-fly raster function chain to apply to the layer's raster, serialised as JSON
845
+ * (e.g. a hillshade / colormap / NDVI chain). When set, the SDK builds a
846
+ * `RasterFunction.fromJSON` → `Raster(rasterFunction:)` pipeline with the source raster
847
+ * wired in as the first raster argument. Construction-time only — changing this prop
848
+ * requires remounting the layer.
849
+ */
850
+ rasterFunction?: string;
814
851
  };
815
852
  /** Props for a `<KmlLayer>` — mirror `KMLLayer` (remote `.kml`/`.kmz` URL or local file). */
816
853
  export type KmlLayerProps = LayerProps & {
@@ -1058,6 +1095,12 @@ export type KmlNodeInfo = {
1058
1095
  export type KmlLayerHandle = {
1059
1096
  /** Loads the KML and returns its node tree (recursing into container nodes). */
1060
1097
  getNodes(): Promise<KmlNodeInfo[]>;
1098
+ /** Starts or resumes playback of the first KML tour found in the layer. No-ops if no tour exists. */
1099
+ playTour(): void;
1100
+ /** Pauses playback of the first KML tour found in the layer. No-ops if no tour exists. */
1101
+ pauseTour(): void;
1102
+ /** Resets playback of the first KML tour found in the layer to the beginning. No-ops if no tour exists. */
1103
+ resetTour(): void;
1061
1104
  };
1062
1105
  /**
1063
1106
  * A geometry value. The `type` discriminator mirrors the ArcGIS web API
@@ -1343,6 +1386,32 @@ export type RouteTrackerHandle = {
1343
1386
  trackLocation(location: TrackedLocation): Promise<RouteTrackingStatus>;
1344
1387
  switchToNextDestination(): Promise<void>;
1345
1388
  };
1389
+ /** Parameters for `serviceArea.solve`. */
1390
+ export type ServiceAreaSolveParams = {
1391
+ /** URL of the network analysis service (e.g. the ArcGIS World Service Areas service). */
1392
+ serviceUrl: string;
1393
+ /** Facility locations to compute service areas from. */
1394
+ facilities: PointGeometry[];
1395
+ /**
1396
+ * Impedance cutoff values (e.g. travel-time minutes or distance) that define each polygon ring.
1397
+ * When omitted the service's default cutoffs are used.
1398
+ */
1399
+ cutoffs?: number[];
1400
+ };
1401
+ /** One service-area polygon returned from `serviceArea.solve`. Mirrors the native `ServiceAreaPolygon`. */
1402
+ export type ServiceAreaPolygon = {
1403
+ /** The polygon geometry of the reachable area. */
1404
+ geometry: Geometry;
1405
+ /** Lower impedance bound for this polygon ring. */
1406
+ fromCutoff: number;
1407
+ /** Upper impedance bound for this polygon ring. */
1408
+ toCutoff: number;
1409
+ };
1410
+ /** Result of `serviceArea.solve`. */
1411
+ export type ServiceAreaResult = {
1412
+ /** All result polygons across all facilities, ordered by facility then cutoff. */
1413
+ polygons: ServiceAreaPolygon[];
1414
+ };
1346
1415
  /** Props for `<AnalysisOverlay>` — a container for visual analyses, hosted by a `<SceneView>`. */
1347
1416
  export type AnalysisOverlayProps = {
1348
1417
  /** Whether the overlay's analyses are drawn. Defaults to `true`. */
@@ -1687,7 +1756,7 @@ export type OfflineTileResult = {
1687
1756
  /** Result of `offline.estimateTileCacheSize` — an estimate of the download footprint. */
1688
1757
  export type TileCacheSizeEstimate = {
1689
1758
  /** Estimated on-disk size of the tile cache in bytes. */
1690
- fileSize: number;
1759
+ fileSizeBytes: number;
1691
1760
  /** Estimated number of tiles in the tile cache. */
1692
1761
  tileCount: number;
1693
1762
  };
@@ -1818,6 +1887,27 @@ export type SimpleMarkerSceneSymbol = {
1818
1887
  /** Which part of the solid sits on the point. Defaults to `bottom`. */
1819
1888
  anchor?: 'center' | 'bottom' | 'top' | 'origin';
1820
1889
  };
1890
+ /**
1891
+ * A 3D model symbol (glTF / `.glb`) for point graphics in a `<SceneView>`. Mirrors the native
1892
+ * `ModelSceneSymbol` (loads the model at `url` at runtime — symbol is `Loadable`).
1893
+ */
1894
+ export type ModelSceneSymbol = {
1895
+ type: 'model-scene';
1896
+ /** URL of the glTF / `.glb` model file. Required. */
1897
+ url: string;
1898
+ /** Uniform scale factor applied to the model. Defaults to `1`. */
1899
+ scale?: number;
1900
+ /** Whether the model dimensions are expressed in device-independent pixels or geographic meters. Defaults to `dips`. */
1901
+ sizeUnits?: 'dips' | 'meters';
1902
+ /** Rotation around the vertical (z) axis, in degrees. Defaults to `0`. */
1903
+ heading?: number;
1904
+ /** Rotation around the lateral (x) axis, in degrees. Defaults to `0`. */
1905
+ pitch?: number;
1906
+ /** Rotation around the longitudinal (y) axis, in degrees. Defaults to `0`. */
1907
+ roll?: number;
1908
+ /** Which part of the model sits on the point. Defaults to `bottom`. */
1909
+ anchor?: 'center' | 'bottom' | 'top' | 'origin';
1910
+ };
1821
1911
  /** A marker drawn from an image at a URL. Mirrors `PictureMarkerSymbol`. */
1822
1912
  export type PictureMarkerSymbol = {
1823
1913
  type: 'picture-marker';
@@ -1982,7 +2072,7 @@ export type MultilayerPointSymbolType = {
1982
2072
  symbolLayers: SymbolLayerSpec[];
1983
2073
  };
1984
2074
  /** Any symbol usable by a `<Graphic>`. Mirrors the native `Symbol` hierarchy. */
1985
- export type Symbol = SimpleMarkerSymbol | SimpleLineSymbol | SimpleFillSymbol | TextSymbol | SimpleMarkerSceneSymbol | PictureMarkerSymbol | PictureFillSymbol | DistanceCompositeSceneSymbol | CompositeSymbolType | MultilayerPointSymbolType;
2075
+ export type Symbol = SimpleMarkerSymbol | SimpleLineSymbol | SimpleFillSymbol | TextSymbol | SimpleMarkerSceneSymbol | ModelSceneSymbol | PictureMarkerSymbol | PictureFillSymbol | DistanceCompositeSceneSymbol | CompositeSymbolType | MultilayerPointSymbolType;
1986
2076
  /** One stop in a `SizeVisualVariable` stops array. */
1987
2077
  export type SizeStop = {
1988
2078
  value: number;
@@ -2228,6 +2318,15 @@ export type SceneViewProps = {
2228
2318
  atmosphereEffect?: AtmosphereEffect;
2229
2319
  /** Sun position, as epoch milliseconds (affects shadow direction). */
2230
2320
  sunTime?: number;
2321
+ /**
2322
+ * Filters time-aware layers to this time window. Both values are epoch milliseconds (UTC).
2323
+ * Pass `null` / omit to show all time steps (no filter). Maps to `GeoView.timeExtent` /
2324
+ * `SceneView(timeExtent:)` on both platforms.
2325
+ */
2326
+ timeExtent?: {
2327
+ startTime: number;
2328
+ endTime: number;
2329
+ } | null;
2231
2330
  /** Called once the scene has finished loading successfully. */
2232
2331
  onSceneLoaded?: (event: {
2233
2332
  nativeEvent: MapLoadedEventPayload;