expo-arcgis 0.1.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 (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/android/build.gradle +39 -0
  4. package/android/src/main/AndroidManifest.xml +2 -0
  5. package/android/src/main/java/expo/modules/arcgis/AnalysisOverlayRef.kt +165 -0
  6. package/android/src/main/java/expo/modules/arcgis/AuthChallengeHandler.kt +78 -0
  7. package/android/src/main/java/expo/modules/arcgis/CoordinateFormatterFunctions.kt +44 -0
  8. package/android/src/main/java/expo/modules/arcgis/DynamicEntityLayerRef.kt +133 -0
  9. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +119 -0
  10. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +237 -0
  11. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +458 -0
  12. package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +174 -0
  13. package/android/src/main/java/expo/modules/arcgis/GeocoderFunctions.kt +71 -0
  14. package/android/src/main/java/expo/modules/arcgis/GeometryCodec.kt +194 -0
  15. package/android/src/main/java/expo/modules/arcgis/GeometryEditorRef.kt +76 -0
  16. package/android/src/main/java/expo/modules/arcgis/GeometryEngineFunctions.kt +223 -0
  17. package/android/src/main/java/expo/modules/arcgis/GeoprocessingFunctions.kt +84 -0
  18. package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +280 -0
  19. package/android/src/main/java/expo/modules/arcgis/JobRef.kt +41 -0
  20. package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +261 -0
  21. package/android/src/main/java/expo/modules/arcgis/MapRef.kt +93 -0
  22. package/android/src/main/java/expo/modules/arcgis/OfflineFunctions.kt +171 -0
  23. package/android/src/main/java/expo/modules/arcgis/QueryCodec.kt +111 -0
  24. package/android/src/main/java/expo/modules/arcgis/RouterFunctions.kt +100 -0
  25. package/android/src/main/java/expo/modules/arcgis/SceneRef.kt +109 -0
  26. package/android/src/main/java/expo/modules/arcgis/UtilityNetworkRef.kt +198 -0
  27. package/app.plugin.js +3 -0
  28. package/build/AnalysisOverlay.d.ts +9 -0
  29. package/build/AnalysisOverlay.d.ts.map +1 -0
  30. package/build/AnalysisOverlay.js +30 -0
  31. package/build/AnalysisOverlay.js.map +1 -0
  32. package/build/DistanceMeasurement.d.ts +8 -0
  33. package/build/DistanceMeasurement.d.ts.map +1 -0
  34. package/build/DistanceMeasurement.js +47 -0
  35. package/build/DistanceMeasurement.js.map +1 -0
  36. package/build/DynamicEntityLayer.d.ts +18 -0
  37. package/build/DynamicEntityLayer.d.ts.map +1 -0
  38. package/build/DynamicEntityLayer.js +50 -0
  39. package/build/DynamicEntityLayer.js.map +1 -0
  40. package/build/ExpoArcgis.types.d.ts +1090 -0
  41. package/build/ExpoArcgis.types.d.ts.map +1 -0
  42. package/build/ExpoArcgis.types.js +2 -0
  43. package/build/ExpoArcgis.types.js.map +1 -0
  44. package/build/ExpoArcgisGeometryModule.d.ts +75 -0
  45. package/build/ExpoArcgisGeometryModule.d.ts.map +1 -0
  46. package/build/ExpoArcgisGeometryModule.js +3 -0
  47. package/build/ExpoArcgisGeometryModule.js.map +1 -0
  48. package/build/ExpoArcgisModule.d.ts +202 -0
  49. package/build/ExpoArcgisModule.d.ts.map +1 -0
  50. package/build/ExpoArcgisModule.js +3 -0
  51. package/build/ExpoArcgisModule.js.map +1 -0
  52. package/build/ExpoArcgisModule.web.d.ts +39 -0
  53. package/build/ExpoArcgisModule.web.d.ts.map +1 -0
  54. package/build/ExpoArcgisModule.web.js +38 -0
  55. package/build/ExpoArcgisModule.web.js.map +1 -0
  56. package/build/FeatureLayer.d.ts +14 -0
  57. package/build/FeatureLayer.d.ts.map +1 -0
  58. package/build/FeatureLayer.js +42 -0
  59. package/build/FeatureLayer.js.map +1 -0
  60. package/build/GeometryEditor.d.ts +11 -0
  61. package/build/GeometryEditor.d.ts.map +1 -0
  62. package/build/GeometryEditor.js +58 -0
  63. package/build/GeometryEditor.js.map +1 -0
  64. package/build/Graphic.d.ts +4 -0
  65. package/build/Graphic.d.ts.map +1 -0
  66. package/build/Graphic.js +36 -0
  67. package/build/Graphic.js.map +1 -0
  68. package/build/GraphicsOverlay.d.ts +12 -0
  69. package/build/GraphicsOverlay.d.ts.map +1 -0
  70. package/build/GraphicsOverlay.js +29 -0
  71. package/build/GraphicsOverlay.js.map +1 -0
  72. package/build/LineOfSight.d.ts +8 -0
  73. package/build/LineOfSight.d.ts.map +1 -0
  74. package/build/LineOfSight.js +50 -0
  75. package/build/LineOfSight.js.map +1 -0
  76. package/build/Map.d.ts +8 -0
  77. package/build/Map.d.ts.map +1 -0
  78. package/build/Map.js +34 -0
  79. package/build/Map.js.map +1 -0
  80. package/build/MapImageLayer.d.ts +4 -0
  81. package/build/MapImageLayer.d.ts.map +1 -0
  82. package/build/MapImageLayer.js +36 -0
  83. package/build/MapImageLayer.js.map +1 -0
  84. package/build/MapSettings.d.ts +17 -0
  85. package/build/MapSettings.d.ts.map +1 -0
  86. package/build/MapSettings.js +20 -0
  87. package/build/MapSettings.js.map +1 -0
  88. package/build/MapView.d.ts +10 -0
  89. package/build/MapView.d.ts.map +1 -0
  90. package/build/MapView.js +30 -0
  91. package/build/MapView.js.map +1 -0
  92. package/build/MapView.web.d.ts +4 -0
  93. package/build/MapView.web.d.ts.map +1 -0
  94. package/build/MapView.web.js +5 -0
  95. package/build/MapView.web.js.map +1 -0
  96. package/build/Scene.d.ts +8 -0
  97. package/build/Scene.d.ts.map +1 -0
  98. package/build/Scene.js +33 -0
  99. package/build/Scene.js.map +1 -0
  100. package/build/SceneLayer.d.ts +4 -0
  101. package/build/SceneLayer.d.ts.map +1 -0
  102. package/build/SceneLayer.js +36 -0
  103. package/build/SceneLayer.js.map +1 -0
  104. package/build/SceneView.d.ts +10 -0
  105. package/build/SceneView.d.ts.map +1 -0
  106. package/build/SceneView.js +29 -0
  107. package/build/SceneView.js.map +1 -0
  108. package/build/SceneView.web.d.ts +4 -0
  109. package/build/SceneView.web.d.ts.map +1 -0
  110. package/build/SceneView.web.js +5 -0
  111. package/build/SceneView.web.js.map +1 -0
  112. package/build/TileLayer.d.ts +4 -0
  113. package/build/TileLayer.d.ts.map +1 -0
  114. package/build/TileLayer.js +36 -0
  115. package/build/TileLayer.js.map +1 -0
  116. package/build/UtilityNetwork.d.ts +8 -0
  117. package/build/UtilityNetwork.d.ts.map +1 -0
  118. package/build/UtilityNetwork.js +38 -0
  119. package/build/UtilityNetwork.js.map +1 -0
  120. package/build/Viewshed.d.ts +7 -0
  121. package/build/Viewshed.d.ts.map +1 -0
  122. package/build/Viewshed.js +39 -0
  123. package/build/Viewshed.js.map +1 -0
  124. package/build/auth.d.ts +36 -0
  125. package/build/auth.d.ts.map +1 -0
  126. package/build/auth.js +47 -0
  127. package/build/auth.js.map +1 -0
  128. package/build/contexts.d.ts +34 -0
  129. package/build/contexts.d.ts.map +1 -0
  130. package/build/contexts.js +42 -0
  131. package/build/contexts.js.map +1 -0
  132. package/build/coordinateFormatter.d.ts +26 -0
  133. package/build/coordinateFormatter.d.ts.map +1 -0
  134. package/build/coordinateFormatter.js +26 -0
  135. package/build/coordinateFormatter.js.map +1 -0
  136. package/build/createLayerComponent.d.ts +8 -0
  137. package/build/createLayerComponent.d.ts.map +1 -0
  138. package/build/createLayerComponent.js +41 -0
  139. package/build/createLayerComponent.js.map +1 -0
  140. package/build/geocoder.d.ts +16 -0
  141. package/build/geocoder.d.ts.map +1 -0
  142. package/build/geocoder.js +16 -0
  143. package/build/geocoder.js.map +1 -0
  144. package/build/geometryEngine.d.ts +87 -0
  145. package/build/geometryEngine.d.ts.map +1 -0
  146. package/build/geometryEngine.js +87 -0
  147. package/build/geometryEngine.js.map +1 -0
  148. package/build/geoprocessor.d.ts +13 -0
  149. package/build/geoprocessor.d.ts.map +1 -0
  150. package/build/geoprocessor.js +12 -0
  151. package/build/geoprocessor.js.map +1 -0
  152. package/build/hooks/usePrevious.d.ts +4 -0
  153. package/build/hooks/usePrevious.d.ts.map +1 -0
  154. package/build/hooks/usePrevious.js +11 -0
  155. package/build/hooks/usePrevious.js.map +1 -0
  156. package/build/hooks/useUpdateEffect.d.ts +6 -0
  157. package/build/hooks/useUpdateEffect.d.ts.map +1 -0
  158. package/build/hooks/useUpdateEffect.js +16 -0
  159. package/build/hooks/useUpdateEffect.js.map +1 -0
  160. package/build/index.d.ts +31 -0
  161. package/build/index.d.ts.map +1 -0
  162. package/build/index.js +31 -0
  163. package/build/index.js.map +1 -0
  164. package/build/layers.d.ts +26 -0
  165. package/build/layers.d.ts.map +1 -0
  166. package/build/layers.js +27 -0
  167. package/build/layers.js.map +1 -0
  168. package/build/offline.d.ts +44 -0
  169. package/build/offline.d.ts.map +1 -0
  170. package/build/offline.js +39 -0
  171. package/build/offline.js.map +1 -0
  172. package/build/router.d.ts +12 -0
  173. package/build/router.d.ts.map +1 -0
  174. package/build/router.js +12 -0
  175. package/build/router.js.map +1 -0
  176. package/build/utils/getPropsDiffs.d.ts +3 -0
  177. package/build/utils/getPropsDiffs.d.ts.map +1 -0
  178. package/build/utils/getPropsDiffs.js +23 -0
  179. package/build/utils/getPropsDiffs.js.map +1 -0
  180. package/expo-module.config.json +9 -0
  181. package/ios/AnalysisOverlayRef.swift +184 -0
  182. package/ios/AuthChallengeHandler.swift +30 -0
  183. package/ios/CoordinateFormatterFunctions.swift +52 -0
  184. package/ios/DynamicEntityLayerRef.swift +136 -0
  185. package/ios/ExpoArcgis.podspec +35 -0
  186. package/ios/ExpoArcgisGeometryModule.swift +113 -0
  187. package/ios/ExpoArcgisMapView.swift +258 -0
  188. package/ios/ExpoArcgisModule.swift +488 -0
  189. package/ios/ExpoArcgisSceneView.swift +200 -0
  190. package/ios/GeocoderFunctions.swift +90 -0
  191. package/ios/GeometryCodec.swift +199 -0
  192. package/ios/GeometryEditorRef.swift +66 -0
  193. package/ios/GeometryEngineFunctions.swift +201 -0
  194. package/ios/GeoprocessingFunctions.swift +88 -0
  195. package/ios/GraphicsOverlayRef.swift +292 -0
  196. package/ios/JobRef.swift +37 -0
  197. package/ios/LayerRef.swift +258 -0
  198. package/ios/MapRef.swift +88 -0
  199. package/ios/OfflineFunctions.swift +132 -0
  200. package/ios/QueryCodec.swift +126 -0
  201. package/ios/RouterFunctions.swift +109 -0
  202. package/ios/SceneRef.swift +111 -0
  203. package/ios/UtilityNetworkRef.swift +182 -0
  204. package/package.json +75 -0
  205. package/src/AnalysisOverlay.tsx +37 -0
  206. package/src/DistanceMeasurement.tsx +58 -0
  207. package/src/DynamicEntityLayer.tsx +65 -0
  208. package/src/ExpoArcgis.types.ts +1248 -0
  209. package/src/ExpoArcgisGeometryModule.ts +140 -0
  210. package/src/ExpoArcgisModule.ts +258 -0
  211. package/src/ExpoArcgisModule.web.ts +46 -0
  212. package/src/FeatureLayer.tsx +50 -0
  213. package/src/GeometryEditor.tsx +68 -0
  214. package/src/Graphic.tsx +41 -0
  215. package/src/GraphicsOverlay.tsx +41 -0
  216. package/src/LineOfSight.tsx +55 -0
  217. package/src/Map.tsx +39 -0
  218. package/src/MapImageLayer.tsx +41 -0
  219. package/src/MapSettings.tsx +33 -0
  220. package/src/MapView.tsx +71 -0
  221. package/src/MapView.web.tsx +8 -0
  222. package/src/Scene.tsx +38 -0
  223. package/src/SceneLayer.tsx +41 -0
  224. package/src/SceneView.tsx +72 -0
  225. package/src/SceneView.web.tsx +8 -0
  226. package/src/TileLayer.tsx +41 -0
  227. package/src/UtilityNetwork.tsx +43 -0
  228. package/src/Viewshed.tsx +44 -0
  229. package/src/auth.ts +74 -0
  230. package/src/contexts.ts +76 -0
  231. package/src/coordinateFormatter.ts +62 -0
  232. package/src/createLayerComponent.tsx +46 -0
  233. package/src/geocoder.ts +29 -0
  234. package/src/geometryEngine.ts +162 -0
  235. package/src/geoprocessor.ts +17 -0
  236. package/src/hooks/usePrevious.ts +12 -0
  237. package/src/hooks/useUpdateEffect.ts +17 -0
  238. package/src/index.ts +60 -0
  239. package/src/layers.tsx +76 -0
  240. package/src/offline.ts +85 -0
  241. package/src/router.ts +14 -0
  242. package/src/utils/getPropsDiffs.ts +23 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # expo-arcgis
2
+
3
+ Native **ArcGIS Maps SDK** for React Native, as an [Expo module](https://docs.expo.dev/modules/overview/).
4
+ It wraps the ArcGIS Maps SDK for **Kotlin** (Android) and **Swift** (iOS) and exposes a declarative,
5
+ SDK-faithful component API to JS/TS — `<MapView>` / `<SceneView>` with layers, graphics, geometry,
6
+ editing, query, analysis, geocoding, routing, geoprocessing, utility networks, offline, real-time and
7
+ authentication.
8
+
9
+ > **Status — early / pre-1.0.** The full surface below is implemented and **compile-verified** on both
10
+ > platforms (TypeScript · Android `compileDebugKotlin` · iOS pod build). On-device **runtime** validation
11
+ > (rendering, network calls, device sensors) should be done in your own app. The API may still change
12
+ > before 1.0. Pulls ArcGIS Maps SDK **300.0.0**.
13
+
14
+ ## Features
15
+
16
+ | Area | What's covered |
17
+ |---|---|
18
+ | **2D / 3D** | `<MapView>` + `<Map>`; `<SceneView>` + `<Scene>` (surface, camera, web scenes, light/shadows) |
19
+ | **Layers** | Feature, Tile, MapImage, Vector-tile, Raster, WMS, WMTS, KML, Scene, IntegratedMesh, PointCloud, OGC 3D Tiles, WebTiled, OpenStreetMap, **WFS**, **OGC API Features**, **DynamicEntity** (stream) |
20
+ | **Graphics** | `<GraphicsOverlay>` + `<Graphic>`, symbols (simple marker/line/fill, text, 3D scene symbol, picture-marker), renderers (simple / unique-value / class-breaks), labels, clustering |
21
+ | **Geometry** | `geometryEngine` (buffer, project, distance, intersect, …), `coordinateFormatter`, codec for all geometry types |
22
+ | **Query** | feature query / count / extent / statistics on a `<FeatureLayer>` ref; `identify` on a view ref |
23
+ | **Editing** | add / update / delete features, `<GeometryEditor>` (tools), feature templates |
24
+ | **Location** | device location, simulated location data source, `onLocationChange` |
25
+ | **Geocoding** | `geocoder.geocode` / `reverseGeocode` / `suggest`, offline `.loc` locators |
26
+ | **Routing** | `router.solveRoute` / directions, travel modes, point barriers, curb approach |
27
+ | **Analysis (3D)** | `<AnalysisOverlay>` + `<Viewshed>` / `<LineOfSight>` / `<DistanceMeasurement>` |
28
+ | **Geoprocessing** | `geoprocessor.execute` → `JobRef` (progress + cancel), typed parameters |
29
+ | **Utility network** | `<UtilityNetwork>` load + trace, named configs, associations, `describeNetwork` |
30
+ | **Offline** | `offline.*` generate offline map, preplanned areas, geodatabase, tile / vector-tile export, sync — all as a cancellable `JobRef`; mobile map / scene packages |
31
+ | **Real-time** | `<DynamicEntityLayer>` (stream service), query, custom data source, stream filter |
32
+ | **Auth** | API key, token (challenge handler), OAuth user sign-in, app credential |
33
+
34
+ ## Requirements
35
+
36
+ ArcGIS Maps SDK 300.0 sets the floor for any app using this module:
37
+
38
+ | | Minimum |
39
+ |---|---|
40
+ | iOS | **17.0**, built with **Xcode 26** |
41
+ | Android | **API 28** (Android 9), compileSdk **36** |
42
+ | Expo | SDK **54+** (New Architecture). Verified on Expo **56** / RN **0.82** / React **19** |
43
+ | Auth | An [ArcGIS API key](https://developers.arcgis.com/documentation/security-and-authentication/api-key-authentication/) (or token / OAuth) |
44
+
45
+ This is a native module — it does **not** run in Expo Go. Use a development build (`expo prebuild` + `run:ios`/`run:android`).
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ npx expo install expo-arcgis
51
+ ```
52
+
53
+ Add the config plugin to your app config. It wires the Esri Maven repository (Android), raises
54
+ `minSdk` / `compileSdk` and the iOS deployment target, and (optionally) injects your API key.
55
+
56
+ ```js
57
+ // app.config.js
58
+ module.exports = {
59
+ expo: {
60
+ plugins: [
61
+ ['expo-arcgis', { apiKey: process.env.ARCGIS_API_KEY }],
62
+ ],
63
+ },
64
+ };
65
+ ```
66
+
67
+ Then regenerate the native projects:
68
+
69
+ ```sh
70
+ npx expo prebuild --clean
71
+ ```
72
+
73
+ ### Config plugin options
74
+
75
+ | Option | Type | Default | Purpose |
76
+ |---|---|---|---|
77
+ | `apiKey` | `string` | – | Written to `strings.xml` (`arcgis_api_key`) / `Info.plist` (`ArcGISAPIKey`) and read at init. |
78
+ | `androidMavenUrl` | `string` | Esri artifactory | Override the ArcGIS Maven repository. |
79
+ | `androidMinSdkVersion` | `number` | `28` | Minimum Android SDK. |
80
+ | `androidCompileSdkVersion` | `number` | `36` | Android compileSdk. |
81
+ | `iosDeploymentTarget` | `string` | `17.0` | Minimum iOS deployment target. |
82
+ | `locationWhenInUseUsageDescription` | `string` | – | Opt into location permissions for showing device position. |
83
+
84
+ ## Quick start
85
+
86
+ The API is declarative and mirrors the ArcGIS SDK object model — a `<Map>` model inside a `<MapView>`
87
+ host, wrapped in `<MapSettings>`:
88
+
89
+ ```tsx
90
+ import { MapSettings, Map, MapView, FeatureLayer } from 'expo-arcgis';
91
+
92
+ export function Screen() {
93
+ return (
94
+ <MapSettings config={{ apiKey: process.env.EXPO_PUBLIC_ARCGIS_API_KEY }}>
95
+ <Map
96
+ basemap="arcGISTopographic"
97
+ initialViewpoint={{ latitude: 34.027, longitude: -118.805, scale: 72_000 }}
98
+ >
99
+ <FeatureLayer url="https://services.arcgis.com/.../FeatureServer/0" />
100
+ <MapView
101
+ style={{ flex: 1 }}
102
+ onMapLoaded={() => console.log('loaded')}
103
+ onMapLoadError={(e) => console.warn(e.nativeEvent.message)}
104
+ />
105
+ </Map>
106
+ </MapSettings>
107
+ );
108
+ }
109
+ ```
110
+
111
+ 3D works the same way with `<Scene>` + `<SceneView>`. Imperative namespaces don't need a view:
112
+
113
+ ```ts
114
+ import { geometryEngine, geocoder } from 'expo-arcgis';
115
+
116
+ const buffered = geometryEngine.buffer(point, 500, 'meters');
117
+ const [hit] = await geocoder.geocode('Los Angeles');
118
+ ```
119
+
120
+ ## API overview
121
+
122
+ - **Views & models** — `MapSettings`, `Map`, `Scene`, `MapView`, `SceneView`
123
+ - **Layers** — `FeatureLayer`, `TileLayer`, `MapImageLayer`, `SceneLayer`, `VectorTileLayer`,
124
+ `IntegratedMeshLayer`, `PointCloudLayer`, `Ogc3DTilesLayer`, `WebTiledLayer`, `OpenStreetMapLayer`,
125
+ `WmsLayer`, `WmtsLayer`, `RasterLayer`, `KmlLayer`, `WfsLayer`, `OgcFeatureLayer`, `DynamicEntityLayer`
126
+ - **Graphics & analysis** — `GraphicsOverlay`, `Graphic`, `AnalysisOverlay`, `Viewshed`, `LineOfSight`,
127
+ `DistanceMeasurement`, `GeometryEditor`, `UtilityNetwork`
128
+ - **Namespaces** — `geometryEngine`, `coordinateFormatter`, `geocoder`, `router`, `geoprocessor`, `offline`
129
+ - **Auth** — `setTokenCredential`, `signInWithOAuth`, `setAppCredential`, `signOut`
130
+ - **Hooks** — `useMapSettings`, `useGeoModel`, `useGeoView`, `useGraphicsOverlay`
131
+
132
+ You can also set the key imperatively: `import ExpoArcgis from 'expo-arcgis'; ExpoArcgis.setApiKey('KEY')`.
133
+
134
+ ## Example app
135
+
136
+ ```sh
137
+ cd example
138
+ npm install
139
+ ARCGIS_API_KEY=your_key npx expo run:android # or run:ios (needs Xcode 26)
140
+ ```
141
+
142
+ ## License
143
+
144
+ MIT © krassavin. See [LICENSE](./LICENSE).
@@ -0,0 +1,39 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'expo.modules.arcgis'
7
+ version = '0.1.0'
8
+
9
+ // ArcGIS Maps SDK for Kotlin (300.0.0) is published to Esri's Maven repository.
10
+ // The consuming app must expose this repository as well — the config plugin adds it to
11
+ // the app's allprojects.repositories so dependency resolution succeeds during the app build.
12
+ repositories {
13
+ maven {
14
+ url = uri('https://esri.jfrog.io/artifactory/arcgis')
15
+ }
16
+ }
17
+
18
+ android {
19
+ namespace "expo.modules.arcgis"
20
+ defaultConfig {
21
+ versionCode 1
22
+ versionName "0.1.0"
23
+ }
24
+ lintOptions {
25
+ abortOnError false
26
+ }
27
+
28
+ kotlinOptions {
29
+ // ArcGIS Maps SDK 300.0 is built with Kotlin 2.3.0. Allow this module to read its newer
30
+ // metadata even when the app's Kotlin compiler is older (Expo SDK 56 ships Kotlin 2.1.0).
31
+ freeCompilerArgs += ["-Xskip-metadata-version-check"]
32
+ }
33
+ }
34
+
35
+ dependencies {
36
+ // ArcGIS Maps SDK for Kotlin — core MapView/GeoView, basemaps, layers, graphics.
37
+ // Requires the consuming app to set minSdk >= 28 and compileSdk >= 36 (config plugin).
38
+ implementation 'com.esri:arcgis-maps-kotlin:300.0.0'
39
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,165 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.analysis.interactive.Analysis
4
+ import com.arcgismaps.analysis.interactive.ExploratoryLineOfSightTargetVisibility
5
+ import com.arcgismaps.analysis.interactive.ExploratoryLocationDistanceMeasurement
6
+ import com.arcgismaps.analysis.interactive.ExploratoryLocationLineOfSight
7
+ import com.arcgismaps.analysis.interactive.ExploratoryLocationViewshed
8
+ import com.arcgismaps.geometry.Point
9
+ import com.arcgismaps.geometry.SpatialReference
10
+ import com.arcgismaps.mapping.view.AnalysisOverlay
11
+ import expo.modules.kotlin.AppContext
12
+ import expo.modules.kotlin.sharedobjects.SharedObject
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.SupervisorJob
16
+ import kotlinx.coroutines.cancel
17
+ import kotlinx.coroutines.launch
18
+
19
+ /**
20
+ * SharedObject wrapping a native [AnalysisOverlay] owned by a SceneView. Holds the visual analyses
21
+ * (`<Viewshed>` / `<LineOfSight>`) declared as children of `<AnalysisOverlay>`.
22
+ */
23
+ class AnalysisOverlayRef(appContext: AppContext) : SharedObject(appContext) {
24
+ val overlay = AnalysisOverlay(emptyList())
25
+
26
+ fun addAnalysis(ref: AnalysisRef) {
27
+ overlay.analyses.add(ref.analysis)
28
+ }
29
+
30
+ fun removeAnalysis(ref: AnalysisRef) {
31
+ overlay.analyses.remove(ref.analysis)
32
+ }
33
+
34
+ fun setVisible(visible: Boolean) {
35
+ overlay.isVisible = visible
36
+ }
37
+ }
38
+
39
+ /** Base SharedObject wrapping an exploratory [Analysis] (viewshed / line-of-sight). */
40
+ abstract class AnalysisRef(appContext: AppContext) : SharedObject(appContext) {
41
+ abstract val analysis: Analysis
42
+ }
43
+
44
+ /** SharedObject wrapping an [ExploratoryLocationViewshed] — the area visible from an observer. */
45
+ class ViewshedRef(appContext: AppContext, props: Map<String, Any?>) : AnalysisRef(appContext) {
46
+ private val viewshed = ExploratoryLocationViewshed(
47
+ analysisPoint(props["location"]) ?: Point(0.0, 0.0, SpatialReference.wgs84()),
48
+ numOr(props["heading"], 0.0),
49
+ numOr(props["pitch"], 90.0),
50
+ numOr(props["horizontalAngle"], 45.0),
51
+ numOr(props["verticalAngle"], 45.0),
52
+ (props["minDistance"] as? Number)?.toDouble(),
53
+ (props["maxDistance"] as? Number)?.toDouble(),
54
+ )
55
+
56
+ override val analysis: Analysis get() = viewshed
57
+
58
+ init {
59
+ applyProps(props)
60
+ }
61
+
62
+ fun applyProps(changed: Map<String, Any?>) {
63
+ changed.forEach { (key, value) ->
64
+ when (key) {
65
+ "location" -> analysisPoint(value)?.let { viewshed.location = it }
66
+ "heading" -> (value as? Number)?.toDouble()?.let { viewshed.heading = it }
67
+ "pitch" -> (value as? Number)?.toDouble()?.let { viewshed.pitch = it }
68
+ "horizontalAngle" -> (value as? Number)?.toDouble()?.let { viewshed.horizontalAngle = it }
69
+ "verticalAngle" -> (value as? Number)?.toDouble()?.let { viewshed.verticalAngle = it }
70
+ "minDistance" -> viewshed.minDistance = (value as? Number)?.toDouble()
71
+ "maxDistance" -> viewshed.maxDistance = (value as? Number)?.toDouble()
72
+ "frustumOutlineVisible" -> (value as? Boolean)?.let { viewshed.frustumOutlineVisible = it }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ /** SharedObject wrapping an [ExploratoryLocationLineOfSight] — streams target visibility to JS. */
79
+ class LineOfSightRef(appContext: AppContext, props: Map<String, Any?>) : AnalysisRef(appContext) {
80
+ private val lineOfSight = ExploratoryLocationLineOfSight(
81
+ analysisPoint(props["observer"]) ?: Point(0.0, 0.0, SpatialReference.wgs84()),
82
+ analysisPoint(props["target"]) ?: Point(0.0, 0.0, SpatialReference.wgs84()),
83
+ )
84
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
85
+
86
+ override val analysis: Analysis get() = lineOfSight
87
+
88
+ init {
89
+ scope.launch {
90
+ lineOfSight.targetVisibility.collect { visibility ->
91
+ emit("onTargetVisibilityChange", mapOf("visibility" to visibilityString(visibility)))
92
+ }
93
+ }
94
+ }
95
+
96
+ override fun deallocate() {
97
+ scope.cancel()
98
+ super.deallocate()
99
+ }
100
+
101
+ fun applyProps(changed: Map<String, Any?>) {
102
+ changed.forEach { (key, value) ->
103
+ when (key) {
104
+ "observer" -> analysisPoint(value)?.let { lineOfSight.observerLocation = it }
105
+ "target" -> analysisPoint(value)?.let { lineOfSight.targetLocation = it }
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * SharedObject wrapping an [ExploratoryLocationDistanceMeasurement] — direct/horizontal/vertical
113
+ * distance between two 3D points. Streams the measurements to JS via `onMeasurementChange`.
114
+ */
115
+ class DistanceMeasurementRef(appContext: AppContext, props: Map<String, Any?>) : AnalysisRef(appContext) {
116
+ private val measurement = ExploratoryLocationDistanceMeasurement(
117
+ analysisPoint(props["startLocation"]) ?: Point(0.0, 0.0, SpatialReference.wgs84()),
118
+ analysisPoint(props["endLocation"]) ?: Point(0.0, 0.0, SpatialReference.wgs84()),
119
+ )
120
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
121
+
122
+ override val analysis: Analysis get() = measurement
123
+
124
+ init {
125
+ scope.launch {
126
+ measurement.measurementChanged.collect { m ->
127
+ emit(
128
+ "onMeasurementChange",
129
+ mapOf(
130
+ "directDistance" to m.directDistance.value,
131
+ "horizontalDistance" to m.horizontalDistance.value,
132
+ "verticalDistance" to m.verticalDistance.value,
133
+ ),
134
+ )
135
+ }
136
+ }
137
+ }
138
+
139
+ override fun deallocate() {
140
+ scope.cancel()
141
+ super.deallocate()
142
+ }
143
+
144
+ fun applyProps(changed: Map<String, Any?>) {
145
+ changed.forEach { (key, value) ->
146
+ when (key) {
147
+ "startLocation" -> analysisPoint(value)?.let { measurement.startLocation = it }
148
+ "endLocation" -> analysisPoint(value)?.let { measurement.endLocation = it }
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ /** Decodes a JS point dict into a [Point] (returns null if the value is not a point geometry). */
155
+ internal fun analysisPoint(value: Any?): Point? =
156
+ (value as? Map<*, *>)?.let { geometryFromDict(it) } as? Point
157
+
158
+ private fun numOr(value: Any?, default: Double): Double = (value as? Number)?.toDouble() ?: default
159
+
160
+ /** Maps the native line-of-sight visibility to the JS `TargetVisibility` union. */
161
+ private fun visibilityString(v: ExploratoryLineOfSightTargetVisibility): String = when (v) {
162
+ is ExploratoryLineOfSightTargetVisibility.Visible -> "visible"
163
+ is ExploratoryLineOfSightTargetVisibility.Obstructed -> "obstructed"
164
+ else -> "unknown"
165
+ }
@@ -0,0 +1,78 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.ArcGISEnvironment
4
+ import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallenge
5
+ import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeHandler
6
+ import com.arcgismaps.httpcore.authentication.ArcGISAuthenticationChallengeResponse
7
+ import com.arcgismaps.httpcore.authentication.OAuthUserConfiguration
8
+ import com.arcgismaps.httpcore.authentication.OAuthUserCredential
9
+ import com.arcgismaps.httpcore.authentication.OAuthUserSignIn
10
+ import com.arcgismaps.httpcore.authentication.TokenCredential
11
+ import kotlinx.coroutines.CompletableDeferred
12
+ import kotlinx.coroutines.CoroutineScope
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.SupervisorJob
15
+ import kotlinx.coroutines.launch
16
+
17
+ /**
18
+ * Reactive token-auth handler — the pattern the docs recommend over pre-adding credentials by hand.
19
+ * The SDK calls this whenever a secured resource needs credentials, passing the exact [challenge]
20
+ * (URL / referer), so the right token is minted for the right resource at the right time.
21
+ */
22
+ object AuthChallengeHandler : ArcGISAuthenticationChallengeHandler {
23
+ private var username: String? = null
24
+ private var password: String? = null
25
+
26
+ /** Stores (or clears, when null) the login the handler uses to mint token credentials on demand. */
27
+ fun setCredentials(username: String?, password: String?) {
28
+ this.username = username
29
+ this.password = password
30
+ }
31
+
32
+ override suspend fun handleArcGISAuthenticationChallenge(
33
+ challenge: ArcGISAuthenticationChallenge,
34
+ ): ArcGISAuthenticationChallengeResponse {
35
+ val user = username ?: return ArcGISAuthenticationChallengeResponse.ContinueAndFail
36
+ val pass = password ?: return ArcGISAuthenticationChallengeResponse.ContinueAndFail
37
+ return TokenCredential.create(challenge.requestUrl, user, pass)
38
+ .map { ArcGISAuthenticationChallengeResponse.ContinueWithCredential(it) }
39
+ .getOrElse { ArcGISAuthenticationChallengeResponse.ContinueAndFail }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Drives an OAuth user sign-in across two JS calls (the SDK can't present a browser itself on
45
+ * Android): `start` kicks off the flow and returns the authorize URL for JS to open in a browser;
46
+ * `complete` finishes it with the redirect URL and caches the credential.
47
+ */
48
+ object OAuthController {
49
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
50
+ private var signIn: OAuthUserSignIn? = null
51
+ private var completion: CompletableDeferred<Unit>? = null
52
+
53
+ /** Starts the flow; resolves with the authorize URL once the SDK produces the sign-in request. */
54
+ suspend fun start(portalUrl: String, clientId: String, redirectUrl: String): String {
55
+ val configuration = OAuthUserConfiguration(portalUrl, clientId, redirectUrl)
56
+ val authorizeUrl = CompletableDeferred<String>()
57
+ scope.launch {
58
+ OAuthUserCredential.create(configuration) { request ->
59
+ signIn = request
60
+ authorizeUrl.complete(request.authorizeUrl)
61
+ }.onSuccess { credential ->
62
+ ArcGISEnvironment.authenticationManager.arcGISCredentialStore.add(credential)
63
+ completion?.complete(Unit)
64
+ }.onFailure { error ->
65
+ completion?.completeExceptionally(error)
66
+ }
67
+ }
68
+ return authorizeUrl.await()
69
+ }
70
+
71
+ /** Completes the flow with the browser redirect URL; suspends until the credential is cached. */
72
+ suspend fun complete(redirectUrl: String) {
73
+ val done = CompletableDeferred<Unit>()
74
+ completion = done
75
+ signIn?.complete(redirectUrl)
76
+ done.await()
77
+ }
78
+ }
@@ -0,0 +1,44 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.geometry.CoordinateFormatter
4
+ import com.arcgismaps.geometry.Point
5
+
6
+ /**
7
+ * Free functions backing the JS `coordinateFormatter` namespace. They convert a `point`
8
+ * geometry to/from latitude-longitude, MGRS, USNG and UTM notation strings via
9
+ * [CoordinateFormatter] (a Kotlin object). Registered as module `Function`s in `ExpoArcgisModule`.
10
+ */
11
+
12
+ private fun cfPoint(dict: Map<String, Any?>?): Point? = dict?.let { geometryFromDict(it) } as? Point
13
+
14
+ // region Latitude-longitude
15
+
16
+ internal fun cfToLatLong(p: Map<String, Any?>, format: String?, decimalPlaces: Int): String? =
17
+ cfPoint(p)?.let { CoordinateFormatter.toLatitudeLongitudeOrNull(it, latitudeLongitudeFormat(format), decimalPlaces) }
18
+
19
+ internal fun cfFromLatLong(coordinates: String, wkid: Int): Map<String, Any?>? =
20
+ CoordinateFormatter.fromLatitudeLongitudeOrNull(coordinates, spatialReference(wkid))?.let { dictFromGeometry(it) }
21
+
22
+ // region MGRS
23
+
24
+ internal fun cfToMgrs(p: Map<String, Any?>, mode: String?, precision: Int, addSpaces: Boolean): String? =
25
+ cfPoint(p)?.let { CoordinateFormatter.toMgrsOrNull(it, mgrsConversionMode(mode), precision, addSpaces) }
26
+
27
+ internal fun cfFromMgrs(coordinates: String, wkid: Int, mode: String?): Map<String, Any?>? =
28
+ CoordinateFormatter.fromMgrsOrNull(coordinates, spatialReference(wkid), mgrsConversionMode(mode))?.let { dictFromGeometry(it) }
29
+
30
+ // region USNG
31
+
32
+ internal fun cfToUsng(p: Map<String, Any?>, precision: Int, addSpaces: Boolean): String? =
33
+ cfPoint(p)?.let { CoordinateFormatter.toUsngOrNull(it, precision, addSpaces) }
34
+
35
+ internal fun cfFromUsng(coordinates: String, wkid: Int): Map<String, Any?>? =
36
+ CoordinateFormatter.fromUsngOrNull(coordinates, spatialReference(wkid))?.let { dictFromGeometry(it) }
37
+
38
+ // region UTM
39
+
40
+ internal fun cfToUtm(p: Map<String, Any?>, mode: String?, addSpaces: Boolean): String? =
41
+ cfPoint(p)?.let { CoordinateFormatter.toUtmOrNull(it, utmConversionMode(mode), addSpaces) }
42
+
43
+ internal fun cfFromUtm(coordinates: String, wkid: Int, mode: String?): Map<String, Any?>? =
44
+ CoordinateFormatter.fromUtmOrNull(coordinates, spatialReference(wkid), utmConversionMode(mode))?.let { dictFromGeometry(it) }
@@ -0,0 +1,133 @@
1
+ package expo.modules.arcgis
2
+
3
+ import com.arcgismaps.data.Field
4
+ import com.arcgismaps.data.FieldType
5
+ import com.arcgismaps.mapping.layers.DynamicEntityLayer
6
+ import com.arcgismaps.mapping.layers.Layer
7
+ import com.arcgismaps.realtime.ArcGISStreamService
8
+ import com.arcgismaps.realtime.ArcGISStreamServiceFilter
9
+ import com.arcgismaps.realtime.ConnectionStatus
10
+ import com.arcgismaps.realtime.CustomDynamicEntityDataSource
11
+ import com.arcgismaps.realtime.DynamicEntityDataSource
12
+ import com.arcgismaps.realtime.DynamicEntityDataSourceInfo
13
+ import com.arcgismaps.realtime.DynamicEntityQueryParameters
14
+ import expo.modules.kotlin.AppContext
15
+ import kotlinx.coroutines.CoroutineScope
16
+ import kotlinx.coroutines.Dispatchers
17
+ import kotlinx.coroutines.SupervisorJob
18
+ import kotlinx.coroutines.cancel
19
+ import kotlinx.coroutines.flow.MutableSharedFlow
20
+ import kotlinx.coroutines.flow.SharedFlow
21
+ import kotlinx.coroutines.flow.asSharedFlow
22
+ import kotlinx.coroutines.launch
23
+
24
+ /**
25
+ * Operational [DynamicEntityLayer] backed by a real-time data source (a stream service — moving
26
+ * entities that update live). Emits `onConnectionStatusChange` as the data source connects.
27
+ */
28
+ class DynamicEntityLayerRef(appContext: AppContext, props: Map<String, Any?>) : LayerRef(appContext) {
29
+ // Non-null only in custom-source mode — observations pushed from JS are emitted into this flow.
30
+ private val pushFlow: MutableSharedFlow<CustomDynamicEntityDataSource.FeedEvent>? =
31
+ if (props["customSource"] != null) MutableSharedFlow(extraBufferCapacity = 64) else null
32
+ val dataSource: DynamicEntityDataSource = buildDataSource(props, pushFlow)
33
+ override val layer: Layer = DynamicEntityLayer(dataSource)
34
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
35
+
36
+ init {
37
+ scope.launch {
38
+ dataSource.connectionStatus.collect { status ->
39
+ emit("onConnectionStatusChange", mapOf("status" to connectionStatusString(status)))
40
+ }
41
+ }
42
+ }
43
+
44
+ /** Pushes an observation into a custom data source (no-op for a stream service). */
45
+ fun pushObservation(attributes: Map<String, Any?>, geometry: Map<String, Any?>) {
46
+ val flow = pushFlow ?: return
47
+ val geom = geometryFromDict(geometry) ?: return
48
+ flow.tryEmit(CustomDynamicEntityDataSource.FeedEvent.NewObservation(geom, attributes))
49
+ }
50
+
51
+ /** Returns the data source's currently-tracked dynamic entities (attributes + geometry). */
52
+ suspend fun queryDynamicEntities(): Map<String, Any?> {
53
+ val result = dataSource.queryDynamicEntities(DynamicEntityQueryParameters()).getOrThrow()
54
+ val entities = result.toList()
55
+ return mapOf(
56
+ "count" to entities.size,
57
+ "entities" to entities.map { entity ->
58
+ mapOf(
59
+ "attributes" to entity.attributes,
60
+ "geometry" to entity.geometry?.let { dictFromGeometry(it) },
61
+ )
62
+ },
63
+ )
64
+ }
65
+
66
+ override fun applyProps(changed: Map<String, Any?>) {
67
+ applyCommonProps(changed)
68
+ val entityLayer = layer as? DynamicEntityLayer ?: return
69
+ (changed["trackDisplay"] as? Map<*, *>)?.let { track ->
70
+ (track["maximumObservations"] as? Number)?.toInt()?.let {
71
+ entityLayer.trackDisplayProperties.maximumObservations = it
72
+ }
73
+ (track["showsPreviousObservations"] as? Boolean)?.let {
74
+ entityLayer.trackDisplayProperties.showPreviousObservations = it
75
+ }
76
+ }
77
+ (changed["filter"] as? Map<*, *>)?.let { filterDict ->
78
+ val filter = ArcGISStreamServiceFilter().apply {
79
+ (filterDict["whereClause"] as? String)?.let { whereClause = it }
80
+ (filterDict["geometry"] as? Map<*, *>)?.let { g -> geometryFromDict(g)?.let { geometry = it } }
81
+ }
82
+ (dataSource as? ArcGISStreamService)?.filter = filter
83
+ }
84
+ }
85
+
86
+ override fun deallocate() {
87
+ scope.cancel()
88
+ super.deallocate()
89
+ }
90
+ }
91
+
92
+ /** Maps [ConnectionStatus] to the JS string union. */
93
+ fun connectionStatusString(status: ConnectionStatus): String = when (status) {
94
+ is ConnectionStatus.Disconnected -> "disconnected"
95
+ is ConnectionStatus.Connecting -> "connecting"
96
+ is ConnectionStatus.Connected -> "connected"
97
+ is ConnectionStatus.Failed -> "failed"
98
+ }
99
+
100
+ /** Builds the real-time data source: a custom feed (push from JS) or a stream service. */
101
+ private fun buildDataSource(
102
+ props: Map<String, Any?>,
103
+ pushFlow: MutableSharedFlow<CustomDynamicEntityDataSource.FeedEvent>?,
104
+ ): DynamicEntityDataSource {
105
+ val custom = props["customSource"] as? Map<*, *>
106
+ if (custom != null && pushFlow != null) {
107
+ val info = DynamicEntityDataSourceInfo(
108
+ custom["entityIdField"] as? String ?: "id",
109
+ buildDynamicEntityFields(custom["fields"] as? List<*>),
110
+ )
111
+ return CustomDynamicEntityDataSource(object : CustomDynamicEntityDataSource.EntityFeedProvider {
112
+ override val feed: SharedFlow<CustomDynamicEntityDataSource.FeedEvent> = pushFlow.asSharedFlow()
113
+ override suspend fun onLoad(): DynamicEntityDataSourceInfo = info
114
+ override suspend fun onConnect() {}
115
+ override suspend fun onDisconnect() {}
116
+ })
117
+ }
118
+ return ArcGISStreamService(props["streamServiceUrl"] as? String ?: "")
119
+ }
120
+
121
+ /** Builds [Field]s from JS `{ name, type }` defs for a custom data source. */
122
+ private fun buildDynamicEntityFields(defs: List<*>?): List<Field> =
123
+ (defs ?: emptyList<Any?>()).filterIsInstance<Map<*, *>>().map { def ->
124
+ Field(dynamicEntityFieldType(def["type"] as? String), def["name"] as? String ?: "", "", 0, null, true, true)
125
+ }
126
+
127
+ private fun dynamicEntityFieldType(type: String?): FieldType = when (type) {
128
+ "int32" -> FieldType.Int32
129
+ "int64" -> FieldType.Int64
130
+ "float64" -> FieldType.Float64
131
+ "date" -> FieldType.Date
132
+ else -> FieldType.Text
133
+ }