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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/android/build.gradle +39 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/arcgis/AnalysisOverlayRef.kt +165 -0
- package/android/src/main/java/expo/modules/arcgis/AuthChallengeHandler.kt +78 -0
- package/android/src/main/java/expo/modules/arcgis/CoordinateFormatterFunctions.kt +44 -0
- package/android/src/main/java/expo/modules/arcgis/DynamicEntityLayerRef.kt +133 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +119 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +237 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +458 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +174 -0
- package/android/src/main/java/expo/modules/arcgis/GeocoderFunctions.kt +71 -0
- package/android/src/main/java/expo/modules/arcgis/GeometryCodec.kt +194 -0
- package/android/src/main/java/expo/modules/arcgis/GeometryEditorRef.kt +76 -0
- package/android/src/main/java/expo/modules/arcgis/GeometryEngineFunctions.kt +223 -0
- package/android/src/main/java/expo/modules/arcgis/GeoprocessingFunctions.kt +84 -0
- package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +280 -0
- package/android/src/main/java/expo/modules/arcgis/JobRef.kt +41 -0
- package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +261 -0
- package/android/src/main/java/expo/modules/arcgis/MapRef.kt +93 -0
- package/android/src/main/java/expo/modules/arcgis/OfflineFunctions.kt +171 -0
- package/android/src/main/java/expo/modules/arcgis/QueryCodec.kt +111 -0
- package/android/src/main/java/expo/modules/arcgis/RouterFunctions.kt +100 -0
- package/android/src/main/java/expo/modules/arcgis/SceneRef.kt +109 -0
- package/android/src/main/java/expo/modules/arcgis/UtilityNetworkRef.kt +198 -0
- package/app.plugin.js +3 -0
- package/build/AnalysisOverlay.d.ts +9 -0
- package/build/AnalysisOverlay.d.ts.map +1 -0
- package/build/AnalysisOverlay.js +30 -0
- package/build/AnalysisOverlay.js.map +1 -0
- package/build/DistanceMeasurement.d.ts +8 -0
- package/build/DistanceMeasurement.d.ts.map +1 -0
- package/build/DistanceMeasurement.js +47 -0
- package/build/DistanceMeasurement.js.map +1 -0
- package/build/DynamicEntityLayer.d.ts +18 -0
- package/build/DynamicEntityLayer.d.ts.map +1 -0
- package/build/DynamicEntityLayer.js +50 -0
- package/build/DynamicEntityLayer.js.map +1 -0
- package/build/ExpoArcgis.types.d.ts +1090 -0
- package/build/ExpoArcgis.types.d.ts.map +1 -0
- package/build/ExpoArcgis.types.js +2 -0
- package/build/ExpoArcgis.types.js.map +1 -0
- package/build/ExpoArcgisGeometryModule.d.ts +75 -0
- package/build/ExpoArcgisGeometryModule.d.ts.map +1 -0
- package/build/ExpoArcgisGeometryModule.js +3 -0
- package/build/ExpoArcgisGeometryModule.js.map +1 -0
- package/build/ExpoArcgisModule.d.ts +202 -0
- package/build/ExpoArcgisModule.d.ts.map +1 -0
- package/build/ExpoArcgisModule.js +3 -0
- package/build/ExpoArcgisModule.js.map +1 -0
- package/build/ExpoArcgisModule.web.d.ts +39 -0
- package/build/ExpoArcgisModule.web.d.ts.map +1 -0
- package/build/ExpoArcgisModule.web.js +38 -0
- package/build/ExpoArcgisModule.web.js.map +1 -0
- package/build/FeatureLayer.d.ts +14 -0
- package/build/FeatureLayer.d.ts.map +1 -0
- package/build/FeatureLayer.js +42 -0
- package/build/FeatureLayer.js.map +1 -0
- package/build/GeometryEditor.d.ts +11 -0
- package/build/GeometryEditor.d.ts.map +1 -0
- package/build/GeometryEditor.js +58 -0
- package/build/GeometryEditor.js.map +1 -0
- package/build/Graphic.d.ts +4 -0
- package/build/Graphic.d.ts.map +1 -0
- package/build/Graphic.js +36 -0
- package/build/Graphic.js.map +1 -0
- package/build/GraphicsOverlay.d.ts +12 -0
- package/build/GraphicsOverlay.d.ts.map +1 -0
- package/build/GraphicsOverlay.js +29 -0
- package/build/GraphicsOverlay.js.map +1 -0
- package/build/LineOfSight.d.ts +8 -0
- package/build/LineOfSight.d.ts.map +1 -0
- package/build/LineOfSight.js +50 -0
- package/build/LineOfSight.js.map +1 -0
- package/build/Map.d.ts +8 -0
- package/build/Map.d.ts.map +1 -0
- package/build/Map.js +34 -0
- package/build/Map.js.map +1 -0
- package/build/MapImageLayer.d.ts +4 -0
- package/build/MapImageLayer.d.ts.map +1 -0
- package/build/MapImageLayer.js +36 -0
- package/build/MapImageLayer.js.map +1 -0
- package/build/MapSettings.d.ts +17 -0
- package/build/MapSettings.d.ts.map +1 -0
- package/build/MapSettings.js +20 -0
- package/build/MapSettings.js.map +1 -0
- package/build/MapView.d.ts +10 -0
- package/build/MapView.d.ts.map +1 -0
- package/build/MapView.js +30 -0
- package/build/MapView.js.map +1 -0
- package/build/MapView.web.d.ts +4 -0
- package/build/MapView.web.d.ts.map +1 -0
- package/build/MapView.web.js +5 -0
- package/build/MapView.web.js.map +1 -0
- package/build/Scene.d.ts +8 -0
- package/build/Scene.d.ts.map +1 -0
- package/build/Scene.js +33 -0
- package/build/Scene.js.map +1 -0
- package/build/SceneLayer.d.ts +4 -0
- package/build/SceneLayer.d.ts.map +1 -0
- package/build/SceneLayer.js +36 -0
- package/build/SceneLayer.js.map +1 -0
- package/build/SceneView.d.ts +10 -0
- package/build/SceneView.d.ts.map +1 -0
- package/build/SceneView.js +29 -0
- package/build/SceneView.js.map +1 -0
- package/build/SceneView.web.d.ts +4 -0
- package/build/SceneView.web.d.ts.map +1 -0
- package/build/SceneView.web.js +5 -0
- package/build/SceneView.web.js.map +1 -0
- package/build/TileLayer.d.ts +4 -0
- package/build/TileLayer.d.ts.map +1 -0
- package/build/TileLayer.js +36 -0
- package/build/TileLayer.js.map +1 -0
- package/build/UtilityNetwork.d.ts +8 -0
- package/build/UtilityNetwork.d.ts.map +1 -0
- package/build/UtilityNetwork.js +38 -0
- package/build/UtilityNetwork.js.map +1 -0
- package/build/Viewshed.d.ts +7 -0
- package/build/Viewshed.d.ts.map +1 -0
- package/build/Viewshed.js +39 -0
- package/build/Viewshed.js.map +1 -0
- package/build/auth.d.ts +36 -0
- package/build/auth.d.ts.map +1 -0
- package/build/auth.js +47 -0
- package/build/auth.js.map +1 -0
- package/build/contexts.d.ts +34 -0
- package/build/contexts.d.ts.map +1 -0
- package/build/contexts.js +42 -0
- package/build/contexts.js.map +1 -0
- package/build/coordinateFormatter.d.ts +26 -0
- package/build/coordinateFormatter.d.ts.map +1 -0
- package/build/coordinateFormatter.js +26 -0
- package/build/coordinateFormatter.js.map +1 -0
- package/build/createLayerComponent.d.ts +8 -0
- package/build/createLayerComponent.d.ts.map +1 -0
- package/build/createLayerComponent.js +41 -0
- package/build/createLayerComponent.js.map +1 -0
- package/build/geocoder.d.ts +16 -0
- package/build/geocoder.d.ts.map +1 -0
- package/build/geocoder.js +16 -0
- package/build/geocoder.js.map +1 -0
- package/build/geometryEngine.d.ts +87 -0
- package/build/geometryEngine.d.ts.map +1 -0
- package/build/geometryEngine.js +87 -0
- package/build/geometryEngine.js.map +1 -0
- package/build/geoprocessor.d.ts +13 -0
- package/build/geoprocessor.d.ts.map +1 -0
- package/build/geoprocessor.js +12 -0
- package/build/geoprocessor.js.map +1 -0
- package/build/hooks/usePrevious.d.ts +4 -0
- package/build/hooks/usePrevious.d.ts.map +1 -0
- package/build/hooks/usePrevious.js +11 -0
- package/build/hooks/usePrevious.js.map +1 -0
- package/build/hooks/useUpdateEffect.d.ts +6 -0
- package/build/hooks/useUpdateEffect.d.ts.map +1 -0
- package/build/hooks/useUpdateEffect.js +16 -0
- package/build/hooks/useUpdateEffect.js.map +1 -0
- package/build/index.d.ts +31 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +31 -0
- package/build/index.js.map +1 -0
- package/build/layers.d.ts +26 -0
- package/build/layers.d.ts.map +1 -0
- package/build/layers.js +27 -0
- package/build/layers.js.map +1 -0
- package/build/offline.d.ts +44 -0
- package/build/offline.d.ts.map +1 -0
- package/build/offline.js +39 -0
- package/build/offline.js.map +1 -0
- package/build/router.d.ts +12 -0
- package/build/router.d.ts.map +1 -0
- package/build/router.js +12 -0
- package/build/router.js.map +1 -0
- package/build/utils/getPropsDiffs.d.ts +3 -0
- package/build/utils/getPropsDiffs.d.ts.map +1 -0
- package/build/utils/getPropsDiffs.js +23 -0
- package/build/utils/getPropsDiffs.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AnalysisOverlayRef.swift +184 -0
- package/ios/AuthChallengeHandler.swift +30 -0
- package/ios/CoordinateFormatterFunctions.swift +52 -0
- package/ios/DynamicEntityLayerRef.swift +136 -0
- package/ios/ExpoArcgis.podspec +35 -0
- package/ios/ExpoArcgisGeometryModule.swift +113 -0
- package/ios/ExpoArcgisMapView.swift +258 -0
- package/ios/ExpoArcgisModule.swift +488 -0
- package/ios/ExpoArcgisSceneView.swift +200 -0
- package/ios/GeocoderFunctions.swift +90 -0
- package/ios/GeometryCodec.swift +199 -0
- package/ios/GeometryEditorRef.swift +66 -0
- package/ios/GeometryEngineFunctions.swift +201 -0
- package/ios/GeoprocessingFunctions.swift +88 -0
- package/ios/GraphicsOverlayRef.swift +292 -0
- package/ios/JobRef.swift +37 -0
- package/ios/LayerRef.swift +258 -0
- package/ios/MapRef.swift +88 -0
- package/ios/OfflineFunctions.swift +132 -0
- package/ios/QueryCodec.swift +126 -0
- package/ios/RouterFunctions.swift +109 -0
- package/ios/SceneRef.swift +111 -0
- package/ios/UtilityNetworkRef.swift +182 -0
- package/package.json +75 -0
- package/src/AnalysisOverlay.tsx +37 -0
- package/src/DistanceMeasurement.tsx +58 -0
- package/src/DynamicEntityLayer.tsx +65 -0
- package/src/ExpoArcgis.types.ts +1248 -0
- package/src/ExpoArcgisGeometryModule.ts +140 -0
- package/src/ExpoArcgisModule.ts +258 -0
- package/src/ExpoArcgisModule.web.ts +46 -0
- package/src/FeatureLayer.tsx +50 -0
- package/src/GeometryEditor.tsx +68 -0
- package/src/Graphic.tsx +41 -0
- package/src/GraphicsOverlay.tsx +41 -0
- package/src/LineOfSight.tsx +55 -0
- package/src/Map.tsx +39 -0
- package/src/MapImageLayer.tsx +41 -0
- package/src/MapSettings.tsx +33 -0
- package/src/MapView.tsx +71 -0
- package/src/MapView.web.tsx +8 -0
- package/src/Scene.tsx +38 -0
- package/src/SceneLayer.tsx +41 -0
- package/src/SceneView.tsx +72 -0
- package/src/SceneView.web.tsx +8 -0
- package/src/TileLayer.tsx +41 -0
- package/src/UtilityNetwork.tsx +43 -0
- package/src/Viewshed.tsx +44 -0
- package/src/auth.ts +74 -0
- package/src/contexts.ts +76 -0
- package/src/coordinateFormatter.ts +62 -0
- package/src/createLayerComponent.tsx +46 -0
- package/src/geocoder.ts +29 -0
- package/src/geometryEngine.ts +162 -0
- package/src/geoprocessor.ts +17 -0
- package/src/hooks/usePrevious.ts +12 -0
- package/src/hooks/useUpdateEffect.ts +17 -0
- package/src/index.ts +60 -0
- package/src/layers.tsx +76 -0
- package/src/offline.ts +85 -0
- package/src/router.ts +14 -0
- 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,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
|
+
}
|