expo-arcgis 0.1.9 → 0.2.1

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 (72) hide show
  1. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisExtrasModule.kt +15 -0
  2. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +8 -0
  3. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +5 -0
  4. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +9 -0
  5. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +11 -0
  6. package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +30 -10
  7. package/android/src/main/java/expo/modules/arcgis/ImageOverlayRef.kt +28 -0
  8. package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +94 -1
  9. package/android/src/main/java/expo/modules/arcgis/PortalFunctions.kt +95 -0
  10. package/build/DynamicEntityLayer.d.ts.map +1 -1
  11. package/build/ExpoArcgis.types.d.ts +169 -11
  12. package/build/ExpoArcgis.types.d.ts.map +1 -1
  13. package/build/ExpoArcgis.types.js.map +1 -1
  14. package/build/ExpoArcgisExtrasModule.d.ts +3 -1
  15. package/build/ExpoArcgisExtrasModule.d.ts.map +1 -1
  16. package/build/ExpoArcgisExtrasModule.js.map +1 -1
  17. package/build/ExpoArcgisGeometryModule.d.ts +3 -1
  18. package/build/ExpoArcgisGeometryModule.d.ts.map +1 -1
  19. package/build/ExpoArcgisGeometryModule.js.map +1 -1
  20. package/build/ExpoArcgisModule.d.ts +19 -2
  21. package/build/ExpoArcgisModule.d.ts.map +1 -1
  22. package/build/ExpoArcgisModule.js.map +1 -1
  23. package/build/ImageOverlay.d.ts +8 -0
  24. package/build/ImageOverlay.d.ts.map +1 -0
  25. package/build/ImageOverlay.js +30 -0
  26. package/build/ImageOverlay.js.map +1 -0
  27. package/build/MapView.d.ts.map +1 -1
  28. package/build/MapView.js +4 -1
  29. package/build/MapView.js.map +1 -1
  30. package/build/SceneView.d.ts.map +1 -1
  31. package/build/SceneView.js +3 -0
  32. package/build/SceneView.js.map +1 -1
  33. package/build/contexts.d.ts +7 -2
  34. package/build/contexts.d.ts.map +1 -1
  35. package/build/contexts.js.map +1 -1
  36. package/build/createLayerComponent.d.ts +3 -2
  37. package/build/createLayerComponent.d.ts.map +1 -1
  38. package/build/createLayerComponent.js +9 -4
  39. package/build/createLayerComponent.js.map +1 -1
  40. package/build/index.d.ts +2 -0
  41. package/build/index.d.ts.map +1 -1
  42. package/build/index.js +2 -0
  43. package/build/index.js.map +1 -1
  44. package/build/layers.d.ts +63 -20
  45. package/build/layers.d.ts.map +1 -1
  46. package/build/layers.js.map +1 -1
  47. package/build/portal.d.ts +42 -0
  48. package/build/portal.d.ts.map +1 -0
  49. package/build/portal.js +42 -0
  50. package/build/portal.js.map +1 -0
  51. package/ios/ExpoArcgisExtrasModule.swift +15 -0
  52. package/ios/ExpoArcgisGeometryModule.swift +8 -0
  53. package/ios/ExpoArcgisMapView.swift +10 -1
  54. package/ios/ExpoArcgisModule.swift +9 -0
  55. package/ios/ExpoArcgisSceneView.swift +6 -0
  56. package/ios/GraphicsOverlayRef.swift +27 -9
  57. package/ios/ImageOverlayRef.swift +23 -0
  58. package/ios/LayerRef.swift +104 -1
  59. package/ios/PortalFunctions.swift +88 -0
  60. package/package.json +1 -1
  61. package/src/ExpoArcgis.types.ts +173 -11
  62. package/src/ExpoArcgisExtrasModule.ts +8 -1
  63. package/src/ExpoArcgisGeometryModule.ts +8 -0
  64. package/src/ExpoArcgisModule.ts +28 -1
  65. package/src/ImageOverlay.tsx +36 -0
  66. package/src/MapView.tsx +14 -1
  67. package/src/SceneView.tsx +3 -0
  68. package/src/contexts.ts +11 -1
  69. package/src/createLayerComponent.tsx +13 -5
  70. package/src/index.ts +2 -0
  71. package/src/layers.tsx +2 -1
  72. package/src/portal.ts +46 -0
@@ -44,6 +44,12 @@ class ExpoArcgisExtrasModule : Module() {
44
44
  AsyncFunction("addFeatureWithTemplate") Coroutine { ref: FeatureLayerRef, templateName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean? ->
45
45
  ref.addFeatureWithTemplate(templateName, attributes, geometry, apply)
46
46
  }
47
+ AsyncFunction("addFeatureWithSubtype") Coroutine { ref: FeatureLayerRef, subtypeName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean? ->
48
+ ref.addFeatureWithSubtype(subtypeName, attributes, geometry, apply)
49
+ }
50
+ AsyncFunction("getContingentValues") Coroutine { ref: FeatureLayerRef, objectId: Long, fieldName: String ->
51
+ ref.getContingentValues(objectId, fieldName)
52
+ }
47
53
  AsyncFunction("updateFeature") Coroutine { ref: FeatureLayerRef, objectId: Long, changes: Map<String, Any?>, apply: Boolean? ->
48
54
  ref.updateFeature(objectId, changes, apply)
49
55
  }
@@ -180,5 +186,14 @@ class ExpoArcgisExtrasModule : Module() {
180
186
  ref.validateNetworkTopology(extent)
181
187
  }
182
188
  }
189
+
190
+ // Georeferenced image overlay (added to a <MapView> via <ImageOverlay>).
191
+ Class(ImageOverlayRef::class) {
192
+ Constructor { ImageOverlayRef(appContext) }
193
+ Function("setFrame") { ref: ImageOverlayRef, imagePath: String, extent: Map<String, Any?>, opacity: Double? ->
194
+ ref.setFrame(imagePath, extent, opacity)
195
+ }
196
+ Function("setOpacity") { ref: ImageOverlayRef, opacity: Double -> ref.setOpacity(opacity) }
197
+ }
183
198
  }
184
199
  }
@@ -181,6 +181,14 @@ class ExpoArcgisGeometryModule : Module() {
181
181
  ArcGISEnvironment.authenticationManager.arcGISCredentialStore.removeAll()
182
182
  }
183
183
 
184
+ // Portal — search a Portal and fetch basemaps, exposed as the JS `portal` namespace.
185
+ AsyncFunction("portalFindItems") Coroutine { params: Map<String, Any?> ->
186
+ portalFindItems(params)
187
+ }
188
+ AsyncFunction("portalFetchBasemaps") Coroutine { params: Map<String, Any?> ->
189
+ portalFetchBasemaps(params)
190
+ }
191
+
184
192
  // Offline — take maps/data offline, exposed as the JS `offline` namespace.
185
193
  AsyncFunction("generateOfflineMap") Coroutine { portalItemId: String, areaOfInterest: Map<String, Any?>, downloadName: String, overrides: Map<String, Any?>? ->
186
194
  generateOfflineMap(appContext, appContext.reactContext?.filesDir, portalItemId, areaOfInterest, downloadName, overrides)
@@ -131,6 +131,11 @@ class ExpoArcgisMapView(context: Context, appContext: AppContext) : ExpoView(con
131
131
  mapView.graphicsOverlays.addAll(refs.map { it.overlay })
132
132
  }
133
133
 
134
+ fun setImageOverlays(refs: List<ImageOverlayRef>) {
135
+ mapView.imageOverlays.clear()
136
+ mapView.imageOverlays.addAll(refs.map { it.overlay })
137
+ }
138
+
134
139
  /** Animates the view to a runtime viewpoint sent from JS. */
135
140
  fun setViewpoint(vp: Map<String, Any?>?) {
136
141
  vp ?: return
@@ -224,6 +224,7 @@ class ExpoArcgisModule : Module() {
224
224
  Function("applyProps") { ref: KmlLayerRef, changed: Map<String, Any?> ->
225
225
  ref.applyProps(changed)
226
226
  }
227
+ AsyncFunction("getNodes") Coroutine { ref: KmlLayerRef -> ref.getNodes() }
227
228
  }
228
229
 
229
230
  // WFS (Web Feature Service) + OGC API - Features — feature layers over async-populating tables.
@@ -364,6 +365,10 @@ class ExpoArcgisModule : Module() {
364
365
  view.setGraphicsOverlays(refs)
365
366
  }
366
367
 
368
+ Prop("imageOverlays") { view: ExpoArcgisMapView, refs: List<ImageOverlayRef> ->
369
+ view.setImageOverlays(refs)
370
+ }
371
+
367
372
  Prop("viewpoint") { view: ExpoArcgisMapView, vp: Map<String, Any?>? ->
368
373
  view.setViewpoint(vp)
369
374
  }
@@ -450,6 +455,10 @@ class ExpoArcgisModule : Module() {
450
455
  view.retryLoad(promise)
451
456
  }
452
457
 
458
+ AsyncFunction("getElevation") { view: ExpoArcgisSceneView, point: Map<String, Any?>, promise: Promise ->
459
+ view.getElevation(point, promise)
460
+ }
461
+
453
462
  AsyncFunction("identify") { view: ExpoArcgisSceneView, screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise ->
454
463
  view.identify(screenPoint, options, promise)
455
464
  }
@@ -132,6 +132,17 @@ class ExpoArcgisSceneView(context: Context, appContext: AppContext) : ExpoView(c
132
132
  }
133
133
  }
134
134
 
135
+ /** Returns the terrain elevation (meters) at a point on the scene's base surface, or null. */
136
+ fun getElevation(point: Map<String, Any?>, promise: Promise) {
137
+ val scene = sceneView.scene ?: run { promise.resolve(null); return }
138
+ val p = geometryFromDict(point) as? Point ?: run { promise.resolve(null); return }
139
+ scope.launch {
140
+ scene.baseSurface.getElevation(p)
141
+ .onSuccess { promise.resolve(it) }
142
+ .onFailure { e -> promise.reject("ELEVATION_ERROR", e.message ?: "Elevation query failed", e) }
143
+ }
144
+ }
145
+
135
146
  /** Animates the view to a runtime camera sent from JS. */
136
147
  fun setCamera(c: Map<String, Any?>?) {
137
148
  val position = c?.get("position") as? Map<*, *> ?: return
@@ -22,6 +22,7 @@ import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
22
22
  import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
23
23
  import com.arcgismaps.mapping.symbology.MultilayerPointSymbol
24
24
  import com.arcgismaps.mapping.symbology.MultilayerPolygonSymbol
25
+ import com.arcgismaps.mapping.symbology.MultilayerPolylineSymbol
25
26
  import com.arcgismaps.mapping.symbology.PictureFillSymbol
26
27
  import com.arcgismaps.mapping.symbology.PictureMarkerSymbol
27
28
  import com.arcgismaps.mapping.symbology.PictureMarkerSymbolLayer
@@ -397,20 +398,39 @@ private fun buildSymbol(s: Map<*, *>): Symbol? = when (s["type"]) {
397
398
  }
398
399
  }
399
400
  "vector-marker" -> {
400
- // DEFER: polyline / multipoint element geometries are not yet supported — only polygon.
401
401
  val geomMap = ldMap["geometry"] as? Map<*, *> ?: return@mapNotNull null
402
- if (geomMap["type"] != "polygon") return@mapNotNull null
403
402
  val geom = geometryFromDict(geomMap) ?: return@mapNotNull null
404
- val fillColor = colorOf(ldMap["fillColor"]) ?: Color.fromRgba(255, 0, 0, 255)
405
- val elementLayers = buildList {
406
- add(SolidFillSymbolLayer(fillColor))
407
- colorOf(ldMap["outlineColor"])?.let { outlineColor ->
408
- val outlineWidth = (ldMap["outlineWidth"] as? Number)?.toDouble() ?: 1.0
409
- add(SolidStrokeSymbolLayer(outlineWidth, outlineColor))
403
+ val elementSymbol = when (geomMap["type"]) {
404
+ "polygon" -> {
405
+ val fillColor = colorOf(ldMap["fillColor"]) ?: Color.fromRgba(255, 0, 0, 255)
406
+ val elementLayers = buildList {
407
+ add(SolidFillSymbolLayer(fillColor))
408
+ colorOf(ldMap["outlineColor"])?.let { outlineColor ->
409
+ val outlineWidth = (ldMap["outlineWidth"] as? Number)?.toDouble() ?: 1.0
410
+ add(SolidStrokeSymbolLayer(outlineWidth, outlineColor))
411
+ }
412
+ }
413
+ MultilayerPolygonSymbol(elementLayers)
410
414
  }
415
+ "polyline" -> {
416
+ val strokeColor = colorOf(ldMap["fillColor"]) ?: colorOf(ldMap["outlineColor"]) ?: Color.fromRgba(255, 0, 0, 255)
417
+ val strokeWidth = (ldMap["outlineWidth"] as? Number)?.toDouble() ?: 1.0
418
+ MultilayerPolylineSymbol(listOf(SolidStrokeSymbolLayer(strokeWidth, strokeColor)))
419
+ }
420
+ "multipoint" -> {
421
+ val fillColor = colorOf(ldMap["fillColor"]) ?: Color.fromRgba(255, 0, 0, 255)
422
+ val elementLayers = buildList {
423
+ add(SolidFillSymbolLayer(fillColor))
424
+ colorOf(ldMap["outlineColor"])?.let { outlineColor ->
425
+ val outlineWidth = (ldMap["outlineWidth"] as? Number)?.toDouble() ?: 1.0
426
+ add(SolidStrokeSymbolLayer(outlineWidth, outlineColor))
427
+ }
428
+ }
429
+ MultilayerPointSymbol(elementLayers)
430
+ }
431
+ else -> return@mapNotNull null // unsupported geometry type — skip gracefully
411
432
  }
412
- val fillSymbol = MultilayerPolygonSymbol(elementLayers)
413
- val element = VectorMarkerSymbolElement(geom, fillSymbol)
433
+ val element = VectorMarkerSymbolElement(geom, elementSymbol)
414
434
  VectorMarkerSymbolLayer(listOf(element)).apply {
415
435
  (ldMap["size"] as? Number)?.toDouble()?.let { size = it }
416
436
  }
@@ -0,0 +1,28 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.geometry.Envelope
4
+ import com.arcgismaps.mapping.view.ImageFrame
5
+ import com.arcgismaps.mapping.view.ImageOverlay
6
+ import expo.modules.kotlin.AppContext
7
+ import expo.modules.kotlin.sharedobjects.SharedObject
8
+
9
+ /**
10
+ * An [ImageOverlay] that displays a single georeferenced image frame — a local image file shown at a
11
+ * map extent, with adjustable opacity. Added to a `<MapView>` via the `<ImageOverlay>` component.
12
+ */
13
+ class ImageOverlayRef(appContext: AppContext) : SharedObject(appContext) {
14
+ val overlay = ImageOverlay()
15
+
16
+ /** Sets the displayed frame from a local image file path and its geographic extent (clears on null). */
17
+ fun setFrame(imagePath: String, extent: Map<String, Any?>, opacity: Double?) {
18
+ val envelope = geometryFromDict(extent + ("type" to "envelope")) as? Envelope
19
+ if (envelope == null) {
20
+ overlay.imageFrame = null
21
+ return
22
+ }
23
+ overlay.imageFrame = ImageFrame(imagePath, envelope)
24
+ opacity?.let { overlay.opacity = it.toFloat() }
25
+ }
26
+
27
+ fun setOpacity(opacity: Double) { overlay.opacity = opacity.toFloat() }
28
+ }
@@ -3,6 +3,9 @@ package expo.modules.arcgis
3
3
  import android.util.Base64
4
4
  import com.arcgismaps.data.ArcGISFeature
5
5
  import com.arcgismaps.data.ArcGISFeatureTable
6
+ import com.arcgismaps.data.ContingentCodedValue
7
+ import com.arcgismaps.data.ContingentRangeValue
8
+ import com.arcgismaps.data.ContingentValue
6
9
  import com.arcgismaps.data.Feature
7
10
  import com.arcgismaps.data.FeatureCollection
8
11
  import com.arcgismaps.data.FeatureCollectionTable
@@ -51,7 +54,9 @@ import com.arcgismaps.mapping.layers.WebTiledLayer
51
54
  import com.arcgismaps.mapping.layers.KmlLayer
52
55
  import com.arcgismaps.mapping.layers.WmsLayer
53
56
  import com.arcgismaps.mapping.layers.WmtsLayer
57
+ import com.arcgismaps.mapping.kml.KmlContainer
54
58
  import com.arcgismaps.mapping.kml.KmlDataset
59
+ import com.arcgismaps.mapping.kml.KmlNode
55
60
  import com.arcgismaps.mapping.view.Graphic
56
61
  import com.arcgismaps.raster.ImageServiceRaster
57
62
  import com.arcgismaps.raster.Raster
@@ -136,6 +141,26 @@ class FeatureLayerRef(appContext: AppContext, private val table: FeatureTable) :
136
141
  return persistEdits()
137
142
  }
138
143
 
144
+ /**
145
+ * Adds a feature created from the named subtype (sets the subtype field and inherits its
146
+ * default attribute values), then optionally applies [attributes] on top and sets [geometry].
147
+ * When [apply] is not `false`, pushes the edit and returns the new object id; pass
148
+ * `apply = false` for a local-only edit.
149
+ */
150
+ suspend fun addFeatureWithSubtype(subtypeName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean?): Long? {
151
+ table.load().getOrThrow()
152
+ val arcGISTable = table as? ArcGISFeatureTable
153
+ ?: throw IllegalStateException("addFeatureWithSubtype requires an ArcGIS feature table (not a shapefile or WFS table)")
154
+ val subtype = arcGISTable.featureSubtypes.firstOrNull { it.name == subtypeName }
155
+ ?: throw IllegalArgumentException("No feature subtype named '$subtypeName' found in the table")
156
+ val geom = geometry?.let { geometryFromDict(it) }
157
+ val feature = arcGISTable.createFeature(subtype, geom)
158
+ if (attributes != null) applyAttributes(feature, attributes)
159
+ table.addFeature(feature).getOrThrow()
160
+ if (apply == false) return null
161
+ return persistEdits()
162
+ }
163
+
139
164
  /**
140
165
  * Adds a feature. When `apply` is not `false`, pushes the edit and returns the new object id;
141
166
  * pass `apply = false` to make a local-only edit (batch with `applyEdits`).
@@ -265,6 +290,54 @@ class FeatureLayerRef(appContext: AppContext, private val table: FeatureTable) :
265
290
  persistEdits()
266
291
  }
267
292
 
293
+ /**
294
+ * Returns the valid contingent values for [fieldName] on the feature with [objectId].
295
+ * Requires the table to be an [ArcGISFeatureTable]; throws otherwise. Returns an empty list when
296
+ * the feature is not found or the table has no contingent-values result for that field.
297
+ * Result shape: `{ fieldName, contingentValues: [{ type, ... }] }` where each entry is either
298
+ * `{ type: "coded", fieldGroupName, codedValue: { name, code } }` or
299
+ * `{ type: "range", fieldGroupName, min, max }`.
300
+ * Exotic subtypes are serialized as `{ type: "any" }` / `{ type: "null" }`.
301
+ */
302
+ suspend fun getContingentValues(objectId: Long, fieldName: String): Map<String, Any?> {
303
+ table.load().getOrThrow()
304
+ val arcGISTable = table as? ArcGISFeatureTable
305
+ ?: throw IllegalStateException("getContingentValues requires an ArcGIS feature table (not a shapefile or WFS table)")
306
+ val feature = featureByObjectId(objectId) as? ArcGISFeature
307
+ ?: throw IllegalArgumentException("Feature not found for objectId $objectId")
308
+ val result = arcGISTable.getContingentValuesOrNull(feature, fieldName)
309
+ ?: return mapOf("fieldName" to fieldName, "contingentValues" to emptyList<Map<String, Any?>>())
310
+ val serialized = result.byFieldGroup.flatMap { (groupName, values) ->
311
+ values.map { cv -> serializeContingentValue(cv, groupName) }
312
+ }
313
+ return mapOf("fieldName" to fieldName, "contingentValues" to serialized)
314
+ }
315
+
316
+ private fun serializeContingentValue(cv: ContingentValue, groupName: String): Map<String, Any?> {
317
+ val dict = mutableMapOf<String, Any?>("fieldGroupName" to groupName)
318
+ when (cv) {
319
+ is ContingentCodedValue -> {
320
+ dict["type"] = "coded"
321
+ val coded = cv.codedValue
322
+ dict["codedValue"] = mapOf("name" to coded.name, "code" to coded.code)
323
+ }
324
+ is ContingentRangeValue -> {
325
+ dict["type"] = "range"
326
+ dict["min"] = cv.minValue
327
+ dict["max"] = cv.maxValue
328
+ }
329
+ else -> {
330
+ // ContingentNullValue, ContingentAnyValue, or unknown future subtype
331
+ val typeName = cv::class.simpleName ?: ""
332
+ dict["type"] = when {
333
+ typeName.contains("Null", ignoreCase = true) -> "null"
334
+ else -> "any"
335
+ }
336
+ }
337
+ }
338
+ return dict
339
+ }
340
+
268
341
  private suspend fun featureByObjectId(objectId: Long): Feature? {
269
342
  val params = QueryParameters().apply { objectIds.add(objectId) }
270
343
  return table.queryFeatures(params).getOrThrow().firstOrNull()
@@ -477,9 +550,29 @@ private fun rasterFromSource(s: Map<String, Any?>): Raster =
477
550
 
478
551
  /** Operational KML layer from a remote .kml/.kmz URL or local file. */
479
552
  class KmlLayerRef(appContext: AppContext, url: String) : LayerRef(appContext) {
480
- override val layer: KmlLayer = KmlLayer(KmlDataset(url))
553
+ private val dataset = KmlDataset(url)
554
+ override val layer: KmlLayer = KmlLayer(dataset)
481
555
 
482
556
  override fun applyProps(changed: Map<String, Any?>) = applyCommonProps(changed)
557
+
558
+ /** Loads the KML and returns its node tree (recursing into container nodes like documents / folders). */
559
+ suspend fun getNodes(): List<Map<String, Any?>> {
560
+ dataset.load().getOrThrow()
561
+ return dataset.rootNodes.map { serializeKmlNode(it) }
562
+ }
563
+ }
564
+
565
+ /** Serializes a KML node, recursing into container nodes (documents / folders). */
566
+ private fun serializeKmlNode(node: KmlNode): Map<String, Any?> {
567
+ val dict = mutableMapOf<String, Any?>(
568
+ "name" to node.name,
569
+ "visible" to node.isVisible,
570
+ "type" to (node::class.simpleName ?: "KmlNode"),
571
+ )
572
+ if (node is KmlContainer) {
573
+ dict["children"] = node.childNodes.map { serializeKmlNode(it) }
574
+ }
575
+ return dict
483
576
  }
484
577
 
485
578
  /** Operational WFS layer — a [FeatureLayer] over a [WfsFeatureTable] (Web Feature Service). */
@@ -0,0 +1,95 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.mapping.Basemap
4
+ import com.arcgismaps.mapping.PortalItem
5
+ import com.arcgismaps.portal.Portal
6
+ import com.arcgismaps.portal.PortalQueryParameters
7
+
8
+ /**
9
+ * Free functions backing the JS `portal` namespace — search a Portal and fetch its basemaps.
10
+ * Registered as `AsyncFunction`s in `ExpoArcgisGeometryModule`.
11
+ *
12
+ * Both functions run anonymously (no auth). Authenticated portals would need a credential
13
+ * already present in `ArcGISEnvironment.authenticationManager.arcGISCredentialStore`
14
+ * (e.g. set via `setServiceCredential` / the auth namespace). That is out of scope here.
15
+ */
16
+
17
+ /**
18
+ * Serialises a [PortalItem] to the wire map `{ id, title, type, snippet, owner, thumbnailUrl }`.
19
+ *
20
+ * Kotlin SDK notes:
21
+ * - `PortalItem.itemId: String` — the item's portal id
22
+ * - `PortalItem.title: String` — inherited from `Item`
23
+ * - `PortalItem.typeName: String` — human-readable type string (e.g. `"Web Map"`)
24
+ * - `PortalItem.snippet: String` — inherited from `Item`
25
+ * - `PortalItem.owner: String`
26
+ * - `PortalItem.thumbnail: LoadableImage?` — has `getUri(): String?` (relative or absolute URL)
27
+ *
28
+ * Thumbnail handling: `LoadableImage.uri` may be a relative path (e.g.
29
+ * `"thumbnail/thumbnail.png"`) or an absolute URL. We build the canonical ArcGIS REST
30
+ * thumbnail URL from the portal URL + item id so callers always get an absolute URL.
31
+ * We do NOT load the image here — that would require an extra async fetch per item;
32
+ * callers that need the bitmap should load the URL themselves.
33
+ */
34
+ private fun portalItemToMap(item: PortalItem): Map<String, Any?> {
35
+ val portalUrl = item.portal.url.trimEnd('/')
36
+ val thumbnailUrl = if (item.itemId.isNotEmpty()) {
37
+ "$portalUrl/sharing/rest/content/items/${item.itemId}/info/thumbnail"
38
+ } else {
39
+ null
40
+ }
41
+ return mapOf(
42
+ "id" to item.itemId,
43
+ "title" to item.title,
44
+ "type" to item.typeName,
45
+ "snippet" to item.snippet,
46
+ "owner" to item.owner,
47
+ "thumbnailUrl" to thumbnailUrl,
48
+ )
49
+ }
50
+
51
+ /**
52
+ * Searches the portal at [portalUrl] (default ArcGIS Online) with the given [query] string,
53
+ * returning up to [max] results (default 10).
54
+ *
55
+ * Kotlin SDK signatures used:
56
+ * - `Portal(url: String, connection: Portal.Connection)` — `Portal.Connection.Anonymous`
57
+ * - `portal.load(): Result<Unit>` — must be called before queries
58
+ * - `portal.findItems(PortalQueryParameters): Result<PortalQueryResultSet<PortalItem>>`
59
+ * - `PortalQueryParameters(query: String, boundingBox: Envelope?, limit: Int)`
60
+ * where the primary ctor is `(String, Envelope?, Int)` and `limit` defaults to 10.
61
+ */
62
+ internal suspend fun portalFindItems(params: Map<String, Any?>): List<Map<String, Any?>> {
63
+ val urlString = params["portalUrl"] as? String ?: "https://www.arcgis.com"
64
+ val query = params["query"] as? String ?: ""
65
+ val max = (params["max"] as? Number)?.toInt() ?: 10
66
+
67
+ val portal = Portal(urlString, Portal.Connection.Anonymous)
68
+ portal.load().getOrThrow()
69
+
70
+ val queryParams = PortalQueryParameters(query, null, max)
71
+ val resultSet = portal.findItems(queryParams).getOrThrow()
72
+ return resultSet.results.map { portalItemToMap(it) }
73
+ }
74
+
75
+ /**
76
+ * Fetches the organisation basemaps from the portal at [portalUrl] (default ArcGIS Online).
77
+ * Returns `[{ name, itemId }]`.
78
+ *
79
+ * Kotlin SDK signatures used:
80
+ * - `portal.fetchBasemaps(): Result<List<Basemap>>`
81
+ * - `Basemap.name: String`
82
+ * - `Basemap.item: Item?` — cast to `PortalItem` to get the item id
83
+ */
84
+ internal suspend fun portalFetchBasemaps(params: Map<String, Any?>): List<Map<String, Any?>> {
85
+ val urlString = params["portalUrl"] as? String ?: "https://www.arcgis.com"
86
+
87
+ val portal = Portal(urlString, Portal.Connection.Anonymous)
88
+ portal.load().getOrThrow()
89
+
90
+ val basemaps: List<Basemap> = portal.fetchBasemaps().getOrThrow()
91
+ return basemaps.map { basemap ->
92
+ val itemId = (basemap.item as? PortalItem)?.itemId
93
+ mapOf("name" to basemap.name, "itemId" to itemId)
94
+ }
95
+ }
@@ -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;AAO5B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;mBAwD2nkC,CAAC;gBAAkB,CAAC;;;;;;;4DAD7qkC,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;AAO5B;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;mBAwDw7qC,CAAC;gBAAkB,CAAC;;;;;;;4DAD1+qC,CAAC"}
@@ -466,6 +466,11 @@ export type SceneViewHandle = {
466
466
  }): Promise<PopupResult[]>;
467
467
  /** Retries loading the scene after a failure (e.g. a network outage). Re-fires `onSceneLoaded`/`onSceneLoadError`. */
468
468
  retryLoad(): Promise<void>;
469
+ /**
470
+ * The terrain elevation (in meters) on the scene's base surface at a geographic point, or `null`
471
+ * if no surface/elevation is available there.
472
+ */
473
+ getElevation(point: Point): Promise<number | null>;
469
474
  };
470
475
  /** Imperative query handle exposed by `<FeatureLayer>` via `ref`. */
471
476
  /** An editing template published with a feature table. Mirrors `FeatureTemplate`. */
@@ -500,6 +505,46 @@ export type AttachmentInfo = {
500
505
  /** Size in bytes. */
501
506
  size: number;
502
507
  };
508
+ /**
509
+ * One valid contingent value entry, discriminated by `type`:
510
+ * - `"coded"` — a specific coded-value domain entry is valid; `codedValue.code` is the domain code.
511
+ * - `"range"` — any numeric value in `[min, max]` is valid.
512
+ * - `"null"` — a null / empty value is explicitly allowed for this field.
513
+ * - `"any"` — any value is allowed (corresponds to `ContingentAnyValue`).
514
+ *
515
+ * `fieldGroupName` identifies which contingent-values field group this constraint belongs to,
516
+ * allowing callers to match constraints to the group they were defined under.
517
+ */
518
+ export type ContingentValueEntry = {
519
+ type: 'coded';
520
+ fieldGroupName: string;
521
+ codedValue: {
522
+ name: string;
523
+ code: unknown;
524
+ };
525
+ } | {
526
+ type: 'range';
527
+ fieldGroupName: string;
528
+ min: unknown;
529
+ max: unknown;
530
+ } | {
531
+ type: 'null';
532
+ fieldGroupName: string;
533
+ } | {
534
+ type: 'any';
535
+ fieldGroupName: string;
536
+ };
537
+ /**
538
+ * Result of `FeatureLayerHandle.getContingentValues` — the field name that was queried plus
539
+ * all valid contingent values for that field given the feature's current attribute state.
540
+ * An empty `contingentValues` array means no constraints are defined (all values are valid).
541
+ */
542
+ export type ContingentValuesResult = {
543
+ /** The field name that was queried. */
544
+ fieldName: string;
545
+ /** The valid contingent values, organised across all field groups that reference this field. */
546
+ contingentValues: ContingentValueEntry[];
547
+ };
503
548
  export type FeatureLayerHandle = {
504
549
  /** Returns the features matching `query` (all features when omitted). */
505
550
  queryFeatures(query?: QueryParameters): Promise<Feature[]>;
@@ -535,6 +580,24 @@ export type FeatureLayerHandle = {
535
580
  * ```
536
581
  */
537
582
  addFeatureWithTemplate(templateName: string, attributes?: Record<string, unknown>, geometry?: Geometry, apply?: boolean): Promise<number | null>;
583
+ /**
584
+ * Creates a feature from the named feature subtype (sets the subtype field and inherits the
585
+ * subtype's default attribute values), then optionally overrides `attributes` on top and sets
586
+ * `geometry`. By default pushes the edit to the service and resolves to the new object id;
587
+ * pass `apply: false` to make a local-only edit. Rejects if no subtype with that name exists.
588
+ * Requires an ArcGIS feature table (service or mobile geodatabase) — rejects for shapefiles
589
+ * and WFS tables, which do not expose feature subtypes.
590
+ *
591
+ * @example
592
+ * ```ts
593
+ * const id = await layer.current.addFeatureWithSubtype(
594
+ * 'Arterial',
595
+ * { ROADNAME: 'Main St' },
596
+ * { type: 'point', x: -117.19, y: 34.05 }
597
+ * );
598
+ * ```
599
+ */
600
+ addFeatureWithSubtype(subtypeName: string, attributes?: Record<string, unknown>, geometry?: Geometry, apply?: boolean): Promise<number | null>;
538
601
  /** Updates the feature with `objectId`. Pass `apply: false` for a local-only edit. */
539
602
  updateFeature(objectId: number, changes: {
540
603
  attributes?: Record<string, unknown>;
@@ -577,6 +640,22 @@ export type FeatureLayerHandle = {
577
640
  * feature layer. The same handle is returned on repeat calls.
578
641
  */
579
642
  getServiceGeodatabase(): Promise<ServiceGeodatabaseHandle>;
643
+ /**
644
+ * Returns the valid contingent values for `fieldName` on the feature with `objectId`, given
645
+ * the feature's current attribute state. Contingent values constrain which attribute values
646
+ * are valid based on the values of other fields (e.g. "species" options depend on "habitat").
647
+ *
648
+ * Requires an ArcGIS feature table (service or mobile geodatabase) — rejects for shapefiles
649
+ * and WFS tables. Resolves with an empty `contingentValues` list when no constraints apply.
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * const result = await layer.current.getContingentValues(42, 'species');
654
+ * // result.contingentValues[0] → { type: 'coded', fieldGroupName: 'HabitatGroup',
655
+ * // codedValue: { name: 'Oak', code: 1 } }
656
+ * ```
657
+ */
658
+ getContingentValues(objectId: number, fieldName: string): Promise<ContingentValuesResult>;
580
659
  };
581
660
  /** Branch-version access level. */
582
661
  export type VersionAccess = 'public' | 'protected' | 'private';
@@ -955,6 +1034,31 @@ export type Envelope = {
955
1034
  /** Coordinate system WKID. Defaults to `4326` (WGS84). */
956
1035
  spatialReference?: SpatialReference;
957
1036
  };
1037
+ /** Props for the `<ImageOverlay>` component — a georeferenced image displayed on a `<MapView>`. */
1038
+ export type ImageOverlayProps = {
1039
+ /** Local image file path to display (e.g. a downloaded or bundled PNG/JPEG). */
1040
+ imagePath: string;
1041
+ /** The geographic extent (envelope) the image is stretched to fill. */
1042
+ extent: Envelope;
1043
+ /** Overlay opacity, 0–1. Defaults to `1`. */
1044
+ opacity?: number;
1045
+ };
1046
+ /** A node in a KML document tree, returned by `KmlLayerHandle.getNodes()`. */
1047
+ export type KmlNodeInfo = {
1048
+ /** Node display name. */
1049
+ name: string;
1050
+ /** Whether the node is currently visible. */
1051
+ visible: boolean;
1052
+ /** Node class name (e.g. `KMLDocument` / `KMLFolder` / `KMLPlacemark` / `KMLNetworkLink` / `KMLTour`). */
1053
+ type: string;
1054
+ /** Child nodes, present for container nodes (documents / folders). */
1055
+ children?: KmlNodeInfo[];
1056
+ };
1057
+ /** Imperative handle exposed by `<KmlLayer>` via `ref`. */
1058
+ export type KmlLayerHandle = {
1059
+ /** Loads the KML and returns its node tree (recursing into container nodes). */
1060
+ getNodes(): Promise<KmlNodeInfo[]>;
1061
+ };
958
1062
  /**
959
1063
  * A geometry value. The `type` discriminator mirrors the ArcGIS web API
960
1064
  * (`"point"` / `"multipoint"` / `"polyline"` / `"polygon"` / `"envelope"`) and
@@ -1507,6 +1611,55 @@ export type UtilityNetworkHandle = {
1507
1611
  /** Returns the associations of a feature queried from `tableName`. */
1508
1612
  associations(tableName: string, whereClause: string): Promise<UtilityAssociationSummary>;
1509
1613
  };
1614
+ /** Parameters for `portal.findItems`. */
1615
+ export type PortalQuery = {
1616
+ /**
1617
+ * ArcGIS portal search query string (e.g. `'type:"Web Map" owner:esri'`).
1618
+ * Forwarded directly to `PortalQueryParameters(query:limit:)` on both platforms.
1619
+ */
1620
+ query: string;
1621
+ /** Maximum number of items to return (default 10). */
1622
+ max?: number;
1623
+ /** Portal URL (default `https://www.arcgis.com`). */
1624
+ portalUrl?: string;
1625
+ };
1626
+ /** One portal item returned by `portal.findItems`. */
1627
+ export type PortalItemInfo = {
1628
+ /** Portal item id (pass to `<Map portalItem={{ itemId }}>` to display the web map). */
1629
+ id: string;
1630
+ /** Human-readable title. */
1631
+ title: string;
1632
+ /**
1633
+ * Human-readable type string (e.g. `"Web Map"`, `"Feature Service"`).
1634
+ * Maps to `PortalItem.typeName` on both platforms.
1635
+ */
1636
+ type: string;
1637
+ /** Short description / abstract. */
1638
+ snippet: string;
1639
+ /** Portal username of the item owner. */
1640
+ owner: string;
1641
+ /**
1642
+ * Absolute HTTPS URL of the item's thumbnail, or `null` when none is available.
1643
+ * Constructed as `<portalUrl>/sharing/rest/content/items/<id>/info/thumbnail`.
1644
+ * Load it in an `<Image>` component; no auth is required for public items.
1645
+ */
1646
+ thumbnailUrl: string | null;
1647
+ };
1648
+ /** Parameters for `portal.fetchBasemaps`. */
1649
+ export type BasemapQuery = {
1650
+ /** Portal URL (default `https://www.arcgis.com`). */
1651
+ portalUrl?: string;
1652
+ };
1653
+ /** One basemap returned by `portal.fetchBasemaps`. */
1654
+ export type BasemapInfo = {
1655
+ /** Human-readable basemap name (e.g. `"World Topographic Map"`). */
1656
+ name: string;
1657
+ /**
1658
+ * Portal item id of the underlying basemap item, or `null` when the basemap has no item.
1659
+ * Pass to `<Map portalItem={{ itemId }}>` to use it as the map's basemap.
1660
+ */
1661
+ itemId: string | null;
1662
+ };
1510
1663
  /** Result of an offline-map download. `path` is the local mobile map package directory. */
1511
1664
  export type OfflineMapResult = {
1512
1665
  /** Local filesystem path of the downloaded mobile map package (pass to `<Map mobileMapPackagePath>`). */
@@ -1753,17 +1906,21 @@ export type PictureMarkerSymbolLayerSpec = {
1753
1906
  /**
1754
1907
  * One vector-marker symbol layer within a `MultilayerPointSymbolType`.
1755
1908
  * Mirrors the native `VectorMarkerSymbolLayer` (a shape drawn from a geometry with fill/stroke,
1756
- * requiring no image). The geometry is defined by a `polygon` (for a filled shape) or a
1757
- * `polyline` (for a stroked shape); polygon is the common case for icons.
1909
+ * requiring no image).
1758
1910
  *
1759
- * Internally the codec builds a `VectorMarkerSymbolElement(geometry, MultilayerPolygonSymbol)`
1760
- * and wraps it in a `VectorMarkerSymbolLayer`.
1911
+ * Supported `geometry` types and how they are symbolised:
1912
+ * - `polygon` `MultilayerPolygonSymbol([SolidFillSymbolLayer, SolidStrokeSymbolLayer?])`;
1913
+ * `fillColor` is the fill, `outlineColor`/`outlineWidth` add an optional stroke.
1914
+ * - `polyline` — `MultilayerPolylineSymbol([SolidStrokeSymbolLayer])`; `fillColor` is used as the
1915
+ * stroke color (falls back to `outlineColor`, then red), `outlineWidth` sets the stroke width.
1916
+ * - `multipoint` — `MultilayerPointSymbol([SolidFillSymbolLayer, SolidStrokeSymbolLayer?])`;
1917
+ * same color/width props as `polygon`.
1761
1918
  *
1762
- * NOTE: `polyline` and `multipoint` element geometries are DEFERRED only `polygon` is
1763
- * currently supported. Supplying a non-polygon `geometry` type results in the layer being
1764
- * skipped gracefully.
1919
+ * Any other `geometry` type is silently skipped (the layer is omitted from the symbol).
1920
+ * Geometries use a local coordinate space (not geographic) use small integer-scale coordinates
1921
+ * (e.g. `x` / `y` in the range `[-1, 1]`).
1765
1922
  *
1766
- * @example
1923
+ * @example — filled triangle (polygon)
1767
1924
  * ```ts
1768
1925
  * { type: 'multilayer-point', symbolLayers: [
1769
1926
  * { type: 'vector-marker', size: 24,
@@ -1777,9 +1934,10 @@ export type PictureMarkerSymbolLayerSpec = {
1777
1934
  export type VectorMarkerSymbolLayerSpec = {
1778
1935
  type: 'vector-marker';
1779
1936
  /**
1780
- * The geometry that defines the marker shape. Only `polygon` is supported; other geometry
1781
- * types are ignored. The polygon is defined in a local coordinate space (not geographic) —
1782
- * use small integer-scale coordinates (e.g. `x` / `y` in the range `[-1, 1]`).
1937
+ * The geometry that defines the marker shape. Supported types: `polygon`, `polyline`,
1938
+ * `multipoint`. Unsupported types are silently skipped. The geometry is defined in a local
1939
+ * coordinate space (not geographic) use small integer-scale coordinates
1940
+ * (e.g. `x` / `y` in the range `[-1, 1]`).
1783
1941
  */
1784
1942
  geometry: Geometry;
1785
1943
  /** Overall size of the marker, in points. Scales the geometry uniformly. Defaults to 12. */