expo-arcgis 0.1.8 → 0.2.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 (52) hide show
  1. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisExtrasModule.kt +48 -0
  2. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +31 -0
  3. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +14 -31
  4. package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +45 -3
  5. package/android/src/main/java/expo/modules/arcgis/ImageOverlayRef.kt +28 -0
  6. package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +39 -0
  7. package/build/DynamicEntityLayer.d.ts.map +1 -1
  8. package/build/ExpoArcgis.types.d.ts +111 -7
  9. package/build/ExpoArcgis.types.d.ts.map +1 -1
  10. package/build/ExpoArcgis.types.js.map +1 -1
  11. package/build/ExpoArcgisExtrasModule.d.ts +7 -1
  12. package/build/ExpoArcgisExtrasModule.d.ts.map +1 -1
  13. package/build/ExpoArcgisExtrasModule.js.map +1 -1
  14. package/build/ExpoArcgisModule.d.ts +8 -4
  15. package/build/ExpoArcgisModule.d.ts.map +1 -1
  16. package/build/ExpoArcgisModule.js.map +1 -1
  17. package/build/ImageOverlay.d.ts +8 -0
  18. package/build/ImageOverlay.d.ts.map +1 -0
  19. package/build/ImageOverlay.js +30 -0
  20. package/build/ImageOverlay.js.map +1 -0
  21. package/build/MapView.d.ts.map +1 -1
  22. package/build/MapView.js +4 -1
  23. package/build/MapView.js.map +1 -1
  24. package/build/SceneView.d.ts.map +1 -1
  25. package/build/SceneView.js +3 -0
  26. package/build/SceneView.js.map +1 -1
  27. package/build/UtilityNetwork.d.ts.map +1 -1
  28. package/build/UtilityNetwork.js +3 -2
  29. package/build/UtilityNetwork.js.map +1 -1
  30. package/build/contexts.d.ts +7 -2
  31. package/build/contexts.d.ts.map +1 -1
  32. package/build/contexts.js.map +1 -1
  33. package/build/index.d.ts +1 -0
  34. package/build/index.d.ts.map +1 -1
  35. package/build/index.js +1 -0
  36. package/build/index.js.map +1 -1
  37. package/ios/ExpoArcgisExtrasModule.swift +48 -0
  38. package/ios/ExpoArcgisMapView.swift +27 -1
  39. package/ios/ExpoArcgisModule.swift +14 -31
  40. package/ios/GraphicsOverlayRef.swift +35 -3
  41. package/ios/ImageOverlayRef.swift +23 -0
  42. package/ios/LayerRef.swift +47 -0
  43. package/package.json +1 -1
  44. package/src/ExpoArcgis.types.ts +123 -7
  45. package/src/ExpoArcgisExtrasModule.ts +10 -1
  46. package/src/ExpoArcgisModule.ts +20 -1
  47. package/src/ImageOverlay.tsx +36 -0
  48. package/src/MapView.tsx +14 -1
  49. package/src/SceneView.tsx +3 -0
  50. package/src/UtilityNetwork.tsx +4 -2
  51. package/src/contexts.ts +11 -1
  52. package/src/index.ts +1 -0
@@ -41,6 +41,12 @@ class ExpoArcgisExtrasModule : Module() {
41
41
  AsyncFunction("addFeature") Coroutine { ref: FeatureLayerRef, attributes: Map<String, Any?>, geometry: Map<String, Any?>?, apply: Boolean? ->
42
42
  ref.addFeature(attributes, geometry, apply)
43
43
  }
44
+ AsyncFunction("addFeatureWithTemplate") Coroutine { ref: FeatureLayerRef, templateName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean? ->
45
+ ref.addFeatureWithTemplate(templateName, attributes, geometry, apply)
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
+ }
44
50
  AsyncFunction("updateFeature") Coroutine { ref: FeatureLayerRef, objectId: Long, changes: Map<String, Any?>, apply: Boolean? ->
45
51
  ref.updateFeature(objectId, changes, apply)
46
52
  }
@@ -144,5 +150,47 @@ class ExpoArcgisExtrasModule : Module() {
144
150
  Function("getFeatureTableNames") { ref: GeodatabaseRef -> ref.getFeatureTableNames() }
145
151
  Function("getFeatureLayer") { ref: GeodatabaseRef, tableName: String -> ref.getFeatureLayer(tableName) }
146
152
  }
153
+
154
+ // Moved from the main module to stay under the Android 64 KB method-size limit on definition().
155
+ Class(UtilityNetworkRef::class) {
156
+ Constructor { props: Map<String, Any?> ->
157
+ UtilityNetworkRef(appContext, props["serviceGeodatabaseUrl"] as? String ?: "")
158
+ }
159
+ AsyncFunction("load") Coroutine { ref: UtilityNetworkRef, map: MapRef ->
160
+ ref.load(map)
161
+ }
162
+ Function("describeNetwork") { ref: UtilityNetworkRef -> ref.describeNetwork() }
163
+ AsyncFunction("trace") Coroutine { ref: UtilityNetworkRef, traceType: String, startingLocations: List<Map<String, Any?>> ->
164
+ ref.trace(traceType, startingLocations)
165
+ }
166
+ AsyncFunction("traceFromQuery") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String, traceType: String ->
167
+ ref.traceFromQuery(tableName, whereClause, traceType)
168
+ }
169
+ AsyncFunction("queryNamedTraceConfigurations") Coroutine { ref: UtilityNetworkRef ->
170
+ ref.queryNamedTraceConfigurations()
171
+ }
172
+ AsyncFunction("traceWithConfiguration") Coroutine { ref: UtilityNetworkRef, configGlobalId: String, tableName: String, whereClause: String ->
173
+ ref.traceWithConfiguration(configGlobalId, tableName, whereClause)
174
+ }
175
+ AsyncFunction("associations") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String ->
176
+ ref.associations(tableName, whereClause)
177
+ }
178
+ Function("getTerminalConfigurations") { ref: UtilityNetworkRef ->
179
+ ref.getTerminalConfigurations()
180
+ }
181
+ AsyncFunction("getState") Coroutine { ref: UtilityNetworkRef -> ref.getState() }
182
+ Function("validateNetworkTopology") { ref: UtilityNetworkRef, extent: Map<String, Any?> ->
183
+ ref.validateNetworkTopology(extent)
184
+ }
185
+ }
186
+
187
+ // Georeferenced image overlay (added to a <MapView> via <ImageOverlay>).
188
+ Class(ImageOverlayRef::class) {
189
+ Constructor { ImageOverlayRef(appContext) }
190
+ Function("setFrame") { ref: ImageOverlayRef, imagePath: String, extent: Map<String, Any?>, opacity: Double? ->
191
+ ref.setFrame(imagePath, extent, opacity)
192
+ }
193
+ Function("setOpacity") { ref: ImageOverlayRef, opacity: Double -> ref.setOpacity(opacity) }
194
+ }
147
195
  }
148
196
  }
@@ -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
@@ -231,6 +236,32 @@ class ExpoArcgisMapView(context: Context, appContext: AppContext) : ExpoView(con
231
236
  }
232
237
  }
233
238
 
239
+ /** Returns the names of the displayed map's bookmarks (e.g. those saved in a loaded web map). */
240
+ fun getBookmarkNames(promise: Promise) {
241
+ val map = mapView.map ?: run { promise.resolve(emptyList<String>()); return }
242
+ scope.launch {
243
+ map.load()
244
+ .onSuccess { promise.resolve(map.bookmarks.map { it.name }) }
245
+ .onFailure { e -> promise.reject("BOOKMARK_ERROR", e.message ?: "Failed to load map", e) }
246
+ }
247
+ }
248
+
249
+ /** Navigates to the named bookmark's viewpoint; resolves whether a matching bookmark was found. */
250
+ fun setBookmark(name: String, promise: Promise) {
251
+ val map = mapView.map ?: run { promise.resolve(false); return }
252
+ scope.launch {
253
+ try {
254
+ map.load().getOrThrow()
255
+ val viewpoint = map.bookmarks.firstOrNull { it.name == name }?.viewpoint
256
+ ?: run { promise.resolve(false); return@launch }
257
+ mapView.setViewpointAnimated(viewpoint, 0.5f)
258
+ promise.resolve(true)
259
+ } catch (e: Exception) {
260
+ promise.reject("BOOKMARK_ERROR", e.message ?: "Failed", e)
261
+ }
262
+ }
263
+ }
264
+
234
265
  override fun onAttachedToWindow() {
235
266
  super.onAttachedToWindow()
236
267
  // The view-based MapView renders only while observing a lifecycle.
@@ -336,37 +336,8 @@ class ExpoArcgisModule : Module() {
336
336
  }
337
337
 
338
338
  // Utility network — loaded from a feature service, attached to a <Map>; runs traces.
339
- Class(UtilityNetworkRef::class) {
340
- Constructor { props: Map<String, Any?> ->
341
- UtilityNetworkRef(appContext, props["serviceGeodatabaseUrl"] as? String ?: "")
342
- }
343
- AsyncFunction("load") Coroutine { ref: UtilityNetworkRef, map: MapRef ->
344
- ref.load(map)
345
- }
346
- Function("describeNetwork") { ref: UtilityNetworkRef -> ref.describeNetwork() }
347
- AsyncFunction("trace") Coroutine { ref: UtilityNetworkRef, traceType: String, startingLocations: List<Map<String, Any?>> ->
348
- ref.trace(traceType, startingLocations)
349
- }
350
- AsyncFunction("traceFromQuery") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String, traceType: String ->
351
- ref.traceFromQuery(tableName, whereClause, traceType)
352
- }
353
- AsyncFunction("queryNamedTraceConfigurations") Coroutine { ref: UtilityNetworkRef ->
354
- ref.queryNamedTraceConfigurations()
355
- }
356
- AsyncFunction("traceWithConfiguration") Coroutine { ref: UtilityNetworkRef, configGlobalId: String, tableName: String, whereClause: String ->
357
- ref.traceWithConfiguration(configGlobalId, tableName, whereClause)
358
- }
359
- AsyncFunction("associations") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String ->
360
- ref.associations(tableName, whereClause)
361
- }
362
- Function("getTerminalConfigurations") { ref: UtilityNetworkRef ->
363
- ref.getTerminalConfigurations()
364
- }
365
- AsyncFunction("getState") Coroutine { ref: UtilityNetworkRef -> ref.getState() }
366
- Function("validateNetworkTopology") { ref: UtilityNetworkRef, extent: Map<String, Any?> ->
367
- ref.validateNetworkTopology(extent)
368
- }
369
- }
339
+ // UtilityNetworkRef is registered in ExpoArcgisExtras (this module's definition() hit the
340
+ // Android 64 KB method-size limit). SharedObjects are global, so it still attaches to a <Map> here.
370
341
 
371
342
  // Interactive GeometryEditor — bound to a <MapView> for sketching; emits onGeometryChange.
372
343
  Class(GeometryEditorRef::class) {
@@ -393,6 +364,10 @@ class ExpoArcgisModule : Module() {
393
364
  view.setGraphicsOverlays(refs)
394
365
  }
395
366
 
367
+ Prop("imageOverlays") { view: ExpoArcgisMapView, refs: List<ImageOverlayRef> ->
368
+ view.setImageOverlays(refs)
369
+ }
370
+
396
371
  Prop("viewpoint") { view: ExpoArcgisMapView, vp: Map<String, Any?>? ->
397
372
  view.setViewpoint(vp)
398
373
  }
@@ -420,6 +395,14 @@ class ExpoArcgisModule : Module() {
420
395
  AsyncFunction("retryLoad") { view: ExpoArcgisMapView, promise: Promise ->
421
396
  view.retryLoad(promise)
422
397
  }
398
+
399
+ AsyncFunction("getBookmarkNames") { view: ExpoArcgisMapView, promise: Promise ->
400
+ view.getBookmarkNames(promise)
401
+ }
402
+
403
+ AsyncFunction("setBookmark") { view: ExpoArcgisMapView, name: String, promise: Promise ->
404
+ view.setBookmark(name, promise)
405
+ }
423
406
  }
424
407
 
425
408
  // 3D scene host — named so JS resolves it via requireNativeView('ExpoArcgis', 'ExpoArcgisSceneView').
@@ -21,9 +21,15 @@ import com.arcgismaps.mapping.symbology.DistanceSymbolRange
21
21
  import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
22
22
  import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
23
23
  import com.arcgismaps.mapping.symbology.MultilayerPointSymbol
24
+ import com.arcgismaps.mapping.symbology.MultilayerPolygonSymbol
25
+ import com.arcgismaps.mapping.symbology.MultilayerPolylineSymbol
24
26
  import com.arcgismaps.mapping.symbology.PictureFillSymbol
25
27
  import com.arcgismaps.mapping.symbology.PictureMarkerSymbol
26
28
  import com.arcgismaps.mapping.symbology.PictureMarkerSymbolLayer
29
+ import com.arcgismaps.mapping.symbology.SolidFillSymbolLayer
30
+ import com.arcgismaps.mapping.symbology.SolidStrokeSymbolLayer
31
+ import com.arcgismaps.mapping.symbology.VectorMarkerSymbolElement
32
+ import com.arcgismaps.mapping.symbology.VectorMarkerSymbolLayer
27
33
  import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol
28
34
  import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle
29
35
  import com.arcgismaps.mapping.symbology.SimpleRenderer
@@ -375,10 +381,8 @@ private fun buildSymbol(s: Map<*, *>): Symbol? = when (s["type"]) {
375
381
  CompositeSymbol(symbolList)
376
382
  }
377
383
  "multilayer-point" -> {
378
- // Build each picture-marker symbol layer and assemble a MultilayerPointSymbol.
384
+ // Build each symbol layer and assemble a MultilayerPointSymbol.
379
385
  // symbolLayers on MultilayerSymbol is a mutable List — populated via the constructor.
380
- // DEFER: vector-marker layers (VectorMarkerSymbolLayer) require VectorMarkerSymbolElement
381
- // from geometry+symbol objects and cannot be cleanly constructed from a plain dict; skip.
382
386
  val layerDicts = s["symbolLayers"] as? List<*> ?: emptyList<Any>()
383
387
  val symbolLayers = layerDicts.mapNotNull { ld ->
384
388
  val ldMap = ld as? Map<*, *> ?: return@mapNotNull null
@@ -393,6 +397,44 @@ private fun buildSymbol(s: Map<*, *>): Symbol? = when (s["type"]) {
393
397
  (ldMap["offsetY"] as? Number)?.toDouble()?.let { offsetY = it }
394
398
  }
395
399
  }
400
+ "vector-marker" -> {
401
+ val geomMap = ldMap["geometry"] as? Map<*, *> ?: return@mapNotNull null
402
+ val geom = geometryFromDict(geomMap) ?: return@mapNotNull null
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)
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
432
+ }
433
+ val element = VectorMarkerSymbolElement(geom, elementSymbol)
434
+ VectorMarkerSymbolLayer(listOf(element)).apply {
435
+ (ldMap["size"] as? Number)?.toDouble()?.let { size = it }
436
+ }
437
+ }
396
438
  else -> null
397
439
  }
398
440
  }
@@ -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
+ }
@@ -117,6 +117,45 @@ class FeatureLayerRef(appContext: AppContext, private val table: FeatureTable) :
117
117
  }
118
118
  }
119
119
 
120
+ /**
121
+ * Adds a feature created from the named template (inheriting its prototype attributes), then
122
+ * optionally applies [attributes] on top and sets [geometry]. When [apply] is not `false`,
123
+ * pushes the edit and returns the new object id; pass `apply = false` for a local-only edit.
124
+ */
125
+ suspend fun addFeatureWithTemplate(templateName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean?): Long? {
126
+ table.load().getOrThrow()
127
+ val arcGISTable = table as? ArcGISFeatureTable
128
+ ?: throw IllegalStateException("addFeatureWithTemplate requires an ArcGIS feature table (not a shapefile or WFS table)")
129
+ val template = arcGISTable.getFeatureTemplate(templateName)
130
+ ?: throw IllegalArgumentException("No feature template named '$templateName' found in the table")
131
+ val geom = geometry?.let { geometryFromDict(it) }
132
+ val feature = arcGISTable.createFeature(template, geom)
133
+ if (attributes != null) applyAttributes(feature, attributes)
134
+ table.addFeature(feature).getOrThrow()
135
+ if (apply == false) return null
136
+ return persistEdits()
137
+ }
138
+
139
+ /**
140
+ * Adds a feature created from the named subtype (sets the subtype field and inherits its
141
+ * default attribute values), then optionally applies [attributes] on top and sets [geometry].
142
+ * When [apply] is not `false`, pushes the edit and returns the new object id; pass
143
+ * `apply = false` for a local-only edit.
144
+ */
145
+ suspend fun addFeatureWithSubtype(subtypeName: String, attributes: Map<String, Any?>?, geometry: Map<String, Any?>?, apply: Boolean?): Long? {
146
+ table.load().getOrThrow()
147
+ val arcGISTable = table as? ArcGISFeatureTable
148
+ ?: throw IllegalStateException("addFeatureWithSubtype requires an ArcGIS feature table (not a shapefile or WFS table)")
149
+ val subtype = arcGISTable.featureSubtypes.firstOrNull { it.name == subtypeName }
150
+ ?: throw IllegalArgumentException("No feature subtype named '$subtypeName' found in the table")
151
+ val geom = geometry?.let { geometryFromDict(it) }
152
+ val feature = arcGISTable.createFeature(subtype, geom)
153
+ if (attributes != null) applyAttributes(feature, attributes)
154
+ table.addFeature(feature).getOrThrow()
155
+ if (apply == false) return null
156
+ return persistEdits()
157
+ }
158
+
120
159
  /**
121
160
  * Adds a feature. When `apply` is not `false`, pushes the edit and returns the new object id;
122
161
  * pass `apply = false` to make a local-only edit (batch with `applyEdits`).
@@ -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;;;;;mBAwD48hC,CAAC;gBAAkB,CAAC;;;;;;;4DAD9/hC,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;;;;;mBAwDkhmC,CAAC;gBAAkB,CAAC;;;;;;;4DADpkmC,CAAC"}
@@ -441,6 +441,10 @@ export type MapViewHandle = {
441
441
  }): Promise<PopupResult[]>;
442
442
  /** Retries loading the map after a failure (e.g. a network outage). Re-fires `onMapLoaded`/`onMapLoadError`. */
443
443
  retryLoad(): Promise<void>;
444
+ /** Names of the displayed map's bookmarks (e.g. those saved in a loaded web map). */
445
+ getBookmarkNames(): Promise<string[]>;
446
+ /** Navigates to the named bookmark's viewpoint; resolves to whether a matching bookmark was found. */
447
+ setBookmark(name: string): Promise<boolean>;
444
448
  };
445
449
  /** Imperative handle exposed by `<SceneView>` via `ref`. */
446
450
  export type SceneViewHandle = {
@@ -513,6 +517,42 @@ export type FeatureLayerHandle = {
513
517
  * edit, then batch with `applyEdits`. Resolves to `null` for non-service tables or local edits.
514
518
  */
515
519
  addFeature(attributes: Record<string, unknown>, geometry?: Geometry, apply?: boolean): Promise<number | null>;
520
+ /**
521
+ * Creates a feature from the named editing template (so the new feature inherits the template's
522
+ * prototype attributes), then optionally overrides `attributes` on top and sets `geometry`.
523
+ * By default pushes the edit to the service and resolves to the new object id; pass
524
+ * `apply: false` to make a local-only edit. Rejects if no template with that name exists.
525
+ * Requires an ArcGIS feature table (service or mobile geodatabase) — rejects for shapefiles
526
+ * and WFS tables, which do not expose editing templates.
527
+ *
528
+ * @example
529
+ * ```ts
530
+ * const id = await layer.current.addFeatureWithTemplate(
531
+ * 'Residential',
532
+ * { ADDRESS: '1 Main St' },
533
+ * { type: 'point', x: -117.19, y: 34.05 }
534
+ * );
535
+ * ```
536
+ */
537
+ addFeatureWithTemplate(templateName: string, attributes?: Record<string, unknown>, geometry?: Geometry, apply?: boolean): Promise<number | null>;
538
+ /**
539
+ * Creates a feature from the named feature subtype (sets the subtype field and inherits the
540
+ * subtype's default attribute values), then optionally overrides `attributes` on top and sets
541
+ * `geometry`. By default pushes the edit to the service and resolves to the new object id;
542
+ * pass `apply: false` to make a local-only edit. Rejects if no subtype with that name exists.
543
+ * Requires an ArcGIS feature table (service or mobile geodatabase) — rejects for shapefiles
544
+ * and WFS tables, which do not expose feature subtypes.
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * const id = await layer.current.addFeatureWithSubtype(
549
+ * 'Arterial',
550
+ * { ROADNAME: 'Main St' },
551
+ * { type: 'point', x: -117.19, y: 34.05 }
552
+ * );
553
+ * ```
554
+ */
555
+ addFeatureWithSubtype(subtypeName: string, attributes?: Record<string, unknown>, geometry?: Geometry, apply?: boolean): Promise<number | null>;
516
556
  /** Updates the feature with `objectId`. Pass `apply: false` for a local-only edit. */
517
557
  updateFeature(objectId: number, changes: {
518
558
  attributes?: Record<string, unknown>;
@@ -933,6 +973,15 @@ export type Envelope = {
933
973
  /** Coordinate system WKID. Defaults to `4326` (WGS84). */
934
974
  spatialReference?: SpatialReference;
935
975
  };
976
+ /** Props for the `<ImageOverlay>` component — a georeferenced image displayed on a `<MapView>`. */
977
+ export type ImageOverlayProps = {
978
+ /** Local image file path to display (e.g. a downloaded or bundled PNG/JPEG). */
979
+ imagePath: string;
980
+ /** The geographic extent (envelope) the image is stretched to fill. */
981
+ extent: Envelope;
982
+ /** Overlay opacity, 0–1. Defaults to `1`. */
983
+ opacity?: number;
984
+ };
936
985
  /**
937
986
  * A geometry value. The `type` discriminator mirrors the ArcGIS web API
938
987
  * (`"point"` / `"multipoint"` / `"polyline"` / `"polygon"` / `"envelope"`) and
@@ -1712,10 +1761,6 @@ export type CompositeSymbolType = {
1712
1761
  * NOTE: `size` sets a uniform size (overrides `width`/`height` when all three are supplied).
1713
1762
  * `width` and `height` are applied in order, so supplying both sets size to the last one;
1714
1763
  * use `size` for a uniform value. `offsetX`/`offsetY` shift the layer in points.
1715
- *
1716
- * DEFER: `VectorMarkerSymbolLayer` (the vector-marker kind) requires constructing
1717
- * `VectorMarkerSymbolElement` objects from geometry + symbol objects, which cannot be
1718
- * expressed as a plain flat dict. It is not yet implemented.
1719
1764
  */
1720
1765
  export type PictureMarkerSymbolLayerSpec = {
1721
1766
  type: 'picture-marker';
@@ -1732,19 +1777,78 @@ export type PictureMarkerSymbolLayerSpec = {
1732
1777
  /** Vertical offset of the layer, in points. */
1733
1778
  offsetY?: number;
1734
1779
  };
1780
+ /**
1781
+ * One vector-marker symbol layer within a `MultilayerPointSymbolType`.
1782
+ * Mirrors the native `VectorMarkerSymbolLayer` (a shape drawn from a geometry with fill/stroke,
1783
+ * requiring no image).
1784
+ *
1785
+ * Supported `geometry` types and how they are symbolised:
1786
+ * - `polygon` — `MultilayerPolygonSymbol([SolidFillSymbolLayer, SolidStrokeSymbolLayer?])`;
1787
+ * `fillColor` is the fill, `outlineColor`/`outlineWidth` add an optional stroke.
1788
+ * - `polyline` — `MultilayerPolylineSymbol([SolidStrokeSymbolLayer])`; `fillColor` is used as the
1789
+ * stroke color (falls back to `outlineColor`, then red), `outlineWidth` sets the stroke width.
1790
+ * - `multipoint` — `MultilayerPointSymbol([SolidFillSymbolLayer, SolidStrokeSymbolLayer?])`;
1791
+ * same color/width props as `polygon`.
1792
+ *
1793
+ * Any other `geometry` type is silently skipped (the layer is omitted from the symbol).
1794
+ * Geometries use a local coordinate space (not geographic) — use small integer-scale coordinates
1795
+ * (e.g. `x` / `y` in the range `[-1, 1]`).
1796
+ *
1797
+ * @example — filled triangle (polygon)
1798
+ * ```ts
1799
+ * { type: 'multilayer-point', symbolLayers: [
1800
+ * { type: 'vector-marker', size: 24,
1801
+ * geometry: { type: 'polygon', points: [
1802
+ * { x: -1, y: -1 }, { x: 0, y: 1 }, { x: 1, y: -1 },
1803
+ * ] },
1804
+ * fillColor: '#e63946', outlineColor: '#1d3557', outlineWidth: 1 },
1805
+ * ] }
1806
+ * ```
1807
+ */
1808
+ export type VectorMarkerSymbolLayerSpec = {
1809
+ type: 'vector-marker';
1810
+ /**
1811
+ * The geometry that defines the marker shape. Supported types: `polygon`, `polyline`,
1812
+ * `multipoint`. Unsupported types are silently skipped. The geometry is defined in a local
1813
+ * coordinate space (not geographic) — use small integer-scale coordinates
1814
+ * (e.g. `x` / `y` in the range `[-1, 1]`).
1815
+ */
1816
+ geometry: Geometry;
1817
+ /** Overall size of the marker, in points. Scales the geometry uniformly. Defaults to 12. */
1818
+ size?: number;
1819
+ /** Fill color as a `#RRGGBB` / `#RRGGBBAA` hex string. Defaults to red. */
1820
+ fillColor?: string;
1821
+ /** Outline stroke color as a `#RRGGBB` / `#RRGGBBAA` hex string. Omit to suppress outline. */
1822
+ outlineColor?: string;
1823
+ /** Outline stroke width in points. Defaults to 1. */
1824
+ outlineWidth?: number;
1825
+ };
1735
1826
  /** Union of all supported symbol layer kinds within a `MultilayerPointSymbolType`. */
1736
- export type SymbolLayerSpec = PictureMarkerSymbolLayerSpec;
1827
+ export type SymbolLayerSpec = PictureMarkerSymbolLayerSpec | VectorMarkerSymbolLayerSpec;
1737
1828
  /**
1738
1829
  * A multilayer point symbol composed of one or more symbol layers.
1739
1830
  * Mirrors the native `MultilayerPointSymbol`. Useful for rich point icons built from stacked
1740
- * picture images (e.g. a pin body + a badge overlay at different offsets).
1831
+ * picture images or vector shapes (e.g. a filled triangle marker, or a pin body + badge).
1741
1832
  *
1742
- * @example
1833
+ * Supported layer kinds: `picture-marker` and `vector-marker`.
1834
+ *
1835
+ * @example — picture marker
1743
1836
  * ```ts
1744
1837
  * { type: 'multilayer-point', symbolLayers: [
1745
1838
  * { type: 'picture-marker', url: 'https://example.com/pin.png', width: 30, height: 30 },
1746
1839
  * ] }
1747
1840
  * ```
1841
+ *
1842
+ * @example — vector marker (filled triangle)
1843
+ * ```ts
1844
+ * { type: 'multilayer-point', symbolLayers: [
1845
+ * { type: 'vector-marker', size: 24,
1846
+ * geometry: { type: 'polygon', points: [
1847
+ * { x: 0, y: 1 }, { x: 1, y: -1 }, { x: -1, y: -1 },
1848
+ * ] },
1849
+ * fillColor: '#e63946', outlineColor: '#1d3557', outlineWidth: 1.5 },
1850
+ * ] }
1851
+ * ```
1748
1852
  */
1749
1853
  export type MultilayerPointSymbolType = {
1750
1854
  type: 'multilayer-point';