expo-arcgis 0.1.1 → 0.1.2
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/README.md +3 -3
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisExtrasModule.kt +87 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisGeometryModule.kt +38 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisMapView.kt +16 -0
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisModule.kt +14 -32
- package/android/src/main/java/expo/modules/arcgis/ExpoArcgisSceneView.kt +16 -0
- package/android/src/main/java/expo/modules/arcgis/GeocoderFunctions.kt +13 -3
- package/android/src/main/java/expo/modules/arcgis/GeometryEngineFunctions.kt +35 -0
- package/android/src/main/java/expo/modules/arcgis/GraphicsOverlayRef.kt +25 -0
- package/android/src/main/java/expo/modules/arcgis/LayerRef.kt +189 -7
- package/android/src/main/java/expo/modules/arcgis/QueryCodec.kt +21 -0
- package/android/src/main/java/expo/modules/arcgis/RouterFunctions.kt +61 -0
- package/android/src/main/java/expo/modules/arcgis/UtilityNetworkRef.kt +24 -0
- package/build/DynamicEntityLayer.d.ts.map +1 -1
- package/build/ExpoArcgis.types.d.ts +232 -8
- package/build/ExpoArcgis.types.d.ts.map +1 -1
- package/build/ExpoArcgis.types.js.map +1 -1
- package/build/ExpoArcgisExtrasModule.d.ts +16 -0
- package/build/ExpoArcgisExtrasModule.d.ts.map +1 -0
- package/build/ExpoArcgisExtrasModule.js +3 -0
- package/build/ExpoArcgisExtrasModule.js.map +1 -0
- package/build/ExpoArcgisGeometryModule.d.ts +11 -1
- package/build/ExpoArcgisGeometryModule.d.ts.map +1 -1
- package/build/ExpoArcgisGeometryModule.js.map +1 -1
- package/build/ExpoArcgisModule.d.ts +18 -4
- package/build/ExpoArcgisModule.d.ts.map +1 -1
- package/build/ExpoArcgisModule.js.map +1 -1
- package/build/FeatureLayer.d.ts.map +1 -1
- package/build/FeatureLayer.js +2 -2
- package/build/FeatureLayer.js.map +1 -1
- package/build/auth.d.ts +25 -0
- package/build/auth.d.ts.map +1 -1
- package/build/auth.js +30 -0
- package/build/auth.js.map +1 -1
- package/build/geometryEngine.d.ts +12 -0
- package/build/geometryEngine.d.ts.map +1 -1
- package/build/geometryEngine.js +12 -0
- package/build/geometryEngine.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/build/layers.d.ts +9 -1
- package/build/layers.d.ts.map +1 -1
- package/build/layers.js +8 -0
- package/build/layers.js.map +1 -1
- package/build/router.d.ts +6 -1
- package/build/router.d.ts.map +1 -1
- package/build/router.js +6 -0
- package/build/router.js.map +1 -1
- package/expo-module.config.json +6 -2
- package/ios/ExpoArcgisExtrasModule.swift +85 -0
- package/ios/ExpoArcgisGeometryModule.swift +39 -0
- package/ios/ExpoArcgisMapView.swift +15 -0
- package/ios/ExpoArcgisModule.swift +14 -34
- package/ios/ExpoArcgisSceneView.swift +15 -0
- package/ios/GeocoderFunctions.swift +9 -1
- package/ios/GeometryEngineFunctions.swift +39 -0
- package/ios/GraphicsOverlayRef.swift +20 -0
- package/ios/LayerRef.swift +201 -7
- package/ios/QueryCodec.swift +21 -1
- package/ios/RouterFunctions.swift +57 -0
- package/ios/UtilityNetworkRef.swift +21 -0
- package/package.json +1 -1
- package/src/ExpoArcgis.types.ts +244 -8
- package/src/ExpoArcgisExtrasModule.ts +22 -0
- package/src/ExpoArcgisGeometryModule.ts +15 -0
- package/src/ExpoArcgisModule.ts +23 -3
- package/src/FeatureLayer.tsx +3 -2
- package/src/auth.ts +32 -0
- package/src/geometryEngine.ts +22 -0
- package/src/index.ts +4 -0
- package/src/layers.tsx +16 -0
- package/src/router.ts +16 -1
package/README.md
CHANGED
|
@@ -18,14 +18,14 @@ authentication.
|
|
|
18
18
|
| Area | What's covered |
|
|
19
19
|
|---|---|
|
|
20
20
|
| **2D / 3D** | `<MapView>` + `<Map>`; `<SceneView>` + `<Scene>` (surface, camera, web scenes, light/shadows) |
|
|
21
|
-
| **Layers** | Feature, Tile, MapImage, Vector-tile, Raster, WMS, WMTS, KML, Scene, IntegratedMesh, PointCloud, OGC 3D Tiles, WebTiled, OpenStreetMap, WFS, OGC API Features, DynamicEntity (stream), Annotation, Dimension, BuildingScene (3D), OrientedImagery, SubtypeFeature, **Group** (container) |
|
|
21
|
+
| **Layers** | Feature, Tile, MapImage, Vector-tile, Raster, WMS, WMTS, KML, Scene, IntegratedMesh, PointCloud, OGC 3D Tiles, WebTiled, OpenStreetMap, WFS, OGC API Features, DynamicEntity (stream), Annotation, Dimension, BuildingScene (3D), OrientedImagery, SubtypeFeature, **Group** (container), **FeatureCollection** (in-memory), **GeoPackage** (local `.gpkg`) |
|
|
22
22
|
| **Graphics** | `<GraphicsOverlay>` + `<Graphic>`, symbols (simple marker/line/fill, text, 3D scene symbol, picture-marker), renderers (simple / unique-value / class-breaks), labels, clustering |
|
|
23
23
|
| **Geometry** | `geometryEngine` (buffer, project, distance, intersect, …), `coordinateFormatter`, codec for all geometry types |
|
|
24
24
|
| **Query** | feature query / count / extent / statistics on a `<FeatureLayer>` ref; `identify` on a view ref |
|
|
25
25
|
| **Editing** | add / update / delete features, `<GeometryEditor>` (tools), feature templates |
|
|
26
26
|
| **Location** | device location, simulated location data source, `onLocationChange` |
|
|
27
27
|
| **Geocoding** | `geocoder.geocode` / `reverseGeocode` / `suggest`, offline `.loc` locators |
|
|
28
|
-
| **Routing** | `router.solveRoute` / directions, travel modes, point barriers, curb approach |
|
|
28
|
+
| **Routing** | `router.solveRoute` / directions, travel modes, point barriers, curb approach; `router.createRouteTracker` turn-by-turn navigation |
|
|
29
29
|
| **Analysis (3D)** | `<AnalysisOverlay>` + `<Viewshed>` / `<LineOfSight>` / `<DistanceMeasurement>` |
|
|
30
30
|
| **Geoprocessing** | `geoprocessor.execute` → `JobRef` (progress + cancel), typed parameters |
|
|
31
31
|
| **Utility network** | `<UtilityNetwork>` load + trace, named configs, associations, `describeNetwork` |
|
|
@@ -126,7 +126,7 @@ const [hit] = await geocoder.geocode('Los Angeles');
|
|
|
126
126
|
`IntegratedMeshLayer`, `PointCloudLayer`, `Ogc3DTilesLayer`, `WebTiledLayer`, `OpenStreetMapLayer`,
|
|
127
127
|
`WmsLayer`, `WmtsLayer`, `RasterLayer`, `KmlLayer`, `WfsLayer`, `OgcFeatureLayer`, `DynamicEntityLayer`,
|
|
128
128
|
`AnnotationLayer`, `DimensionLayer`, `BuildingSceneLayer`, `OrientedImageryLayer`, `SubtypeFeatureLayer`,
|
|
129
|
-
`GroupLayer`
|
|
129
|
+
`GroupLayer`, `FeatureCollectionLayer`, `GeoPackageLayer`
|
|
130
130
|
- **Graphics & analysis** — `GraphicsOverlay`, `Graphic`, `AnalysisOverlay`, `Viewshed`, `LineOfSight`,
|
|
131
131
|
`DistanceMeasurement`, `GeometryEditor`, `UtilityNetwork`
|
|
132
132
|
- **Namespaces** — `geometryEngine`, `coordinateFormatter`, `geocoder`, `router`, `geoprocessor`, `offline`
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
package expo.modules.arcgis
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.functions.Coroutine
|
|
4
|
+
import expo.modules.kotlin.modules.Module
|
|
5
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Third native module, hosting the heavier operational-layer SharedObject classes (currently
|
|
9
|
+
* [FeatureLayerRef]), exposed to JS as `ExpoArcgisExtras`. Split out of [ExpoArcgisGeometryModule]
|
|
10
|
+
* so that no module's `definition()` exceeds the JVM 64 KB method-size limit. SharedObjects are
|
|
11
|
+
* global, so a ref constructed here attaches to a `<MapView>` from the main module fine.
|
|
12
|
+
*/
|
|
13
|
+
class ExpoArcgisExtrasModule : Module() {
|
|
14
|
+
override fun definition() = ModuleDefinition {
|
|
15
|
+
Name("ExpoArcgisExtras")
|
|
16
|
+
|
|
17
|
+
Class(FeatureLayerRef::class) {
|
|
18
|
+
Constructor { props: Map<String, Any?> ->
|
|
19
|
+
FeatureLayerRef(appContext, props).also { it.applyProps(props) }
|
|
20
|
+
}
|
|
21
|
+
Function("applyProps") { ref: FeatureLayerRef, changed: Map<String, Any?> ->
|
|
22
|
+
ref.applyProps(changed)
|
|
23
|
+
}
|
|
24
|
+
AsyncFunction("queryFeatures") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
25
|
+
ref.queryFeatures(query)
|
|
26
|
+
}
|
|
27
|
+
AsyncFunction("queryFeatureCount") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
28
|
+
ref.queryFeatureCount(query)
|
|
29
|
+
}
|
|
30
|
+
AsyncFunction("queryExtent") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
31
|
+
ref.queryExtent(query)
|
|
32
|
+
}
|
|
33
|
+
AsyncFunction("queryStatistics") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?> ->
|
|
34
|
+
ref.queryStatistics(query)
|
|
35
|
+
}
|
|
36
|
+
AsyncFunction("queryFeatureTemplates") Coroutine { ref: FeatureLayerRef ->
|
|
37
|
+
ref.queryFeatureTemplates()
|
|
38
|
+
}
|
|
39
|
+
AsyncFunction("addFeature") Coroutine { ref: FeatureLayerRef, attributes: Map<String, Any?>, geometry: Map<String, Any?>?, apply: Boolean? ->
|
|
40
|
+
ref.addFeature(attributes, geometry, apply)
|
|
41
|
+
}
|
|
42
|
+
AsyncFunction("updateFeature") Coroutine { ref: FeatureLayerRef, objectId: Long, changes: Map<String, Any?>, apply: Boolean? ->
|
|
43
|
+
ref.updateFeature(objectId, changes, apply)
|
|
44
|
+
}
|
|
45
|
+
AsyncFunction("deleteFeature") Coroutine { ref: FeatureLayerRef, objectId: Long, apply: Boolean? ->
|
|
46
|
+
ref.deleteFeature(objectId, apply)
|
|
47
|
+
}
|
|
48
|
+
AsyncFunction("applyEdits") Coroutine { ref: FeatureLayerRef ->
|
|
49
|
+
ref.applyEdits()
|
|
50
|
+
}
|
|
51
|
+
AsyncFunction("undoLocalEdits") Coroutine { ref: FeatureLayerRef ->
|
|
52
|
+
ref.undoLocalEdits()
|
|
53
|
+
}
|
|
54
|
+
AsyncFunction("queryRelatedFeatures") Coroutine { ref: FeatureLayerRef, objectId: Long ->
|
|
55
|
+
ref.queryRelatedFeatures(objectId)
|
|
56
|
+
}
|
|
57
|
+
AsyncFunction("queryAttachments") Coroutine { ref: FeatureLayerRef, objectId: Long ->
|
|
58
|
+
ref.queryAttachments(objectId)
|
|
59
|
+
}
|
|
60
|
+
AsyncFunction("addAttachment") Coroutine { ref: FeatureLayerRef, objectId: Long, name: String, contentType: String, dataBase64: String ->
|
|
61
|
+
ref.addAttachment(objectId, name, contentType, dataBase64)
|
|
62
|
+
}
|
|
63
|
+
AsyncFunction("fetchAttachment") Coroutine { ref: FeatureLayerRef, objectId: Long, attachmentId: Long ->
|
|
64
|
+
ref.fetchAttachment(objectId, attachmentId)
|
|
65
|
+
}
|
|
66
|
+
AsyncFunction("deleteAttachment") Coroutine { ref: FeatureLayerRef, objectId: Long, attachmentId: Long ->
|
|
67
|
+
ref.deleteAttachment(objectId, attachmentId)
|
|
68
|
+
}
|
|
69
|
+
AsyncFunction("updateAttachment") Coroutine { ref: FeatureLayerRef, objectId: Long, attachmentId: Long, name: String, contentType: String, dataBase64: String ->
|
|
70
|
+
ref.updateAttachment(objectId, attachmentId, name, contentType, dataBase64)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Turn-by-turn navigation — solve a route and track device locations against it.
|
|
75
|
+
AsyncFunction("createRouteTracker") Coroutine { stops: List<Map<String, Any?>>, params: Map<String, Any?> ->
|
|
76
|
+
createRouteTracker(appContext, stops, params)
|
|
77
|
+
}
|
|
78
|
+
Class(RouteTrackerRef::class) {
|
|
79
|
+
AsyncFunction("trackLocation") Coroutine { ref: RouteTrackerRef, location: Map<String, Any?> ->
|
|
80
|
+
ref.trackLocation(location)
|
|
81
|
+
}
|
|
82
|
+
AsyncFunction("switchToNextDestination") Coroutine { ref: RouteTrackerRef ->
|
|
83
|
+
ref.switchToNextDestination()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
|
+
import com.arcgismaps.ArcGISEnvironment
|
|
4
|
+
import com.arcgismaps.httpcore.authentication.ArcGISCredentialStore
|
|
3
5
|
import expo.modules.kotlin.functions.Coroutine
|
|
4
6
|
import expo.modules.kotlin.modules.Module
|
|
5
7
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
@@ -30,6 +32,12 @@ class ExpoArcgisGeometryModule : Module() {
|
|
|
30
32
|
Function("geClip", ::geClip)
|
|
31
33
|
Function("geCut", ::geCut)
|
|
32
34
|
Function("geConvexHull", ::geConvexHull)
|
|
35
|
+
Function("geLabelPoint", ::geLabelPoint)
|
|
36
|
+
Function("geNormalizeCentralMeridian", ::geNormalizeCentralMeridian)
|
|
37
|
+
Function("geReshape", ::geReshape)
|
|
38
|
+
Function("geIntersections", ::geIntersections)
|
|
39
|
+
Function("geExtend", ::geExtend)
|
|
40
|
+
Function("geAutoComplete", ::geAutoComplete)
|
|
33
41
|
Function("geBoundary", ::geBoundary)
|
|
34
42
|
Function("geSimplify", ::geSimplify)
|
|
35
43
|
Function("geDensify", ::geDensify)
|
|
@@ -132,6 +140,36 @@ class ExpoArcgisGeometryModule : Module() {
|
|
|
132
140
|
Function("addLayer") { ref: GroupLayerRef, layer: LayerRef -> ref.addLayer(layer) }
|
|
133
141
|
Function("removeLayer") { ref: GroupLayerRef, layer: LayerRef -> ref.removeLayer(layer) }
|
|
134
142
|
}
|
|
143
|
+
// FeatureLayerRef moved to the third module (ExpoArcgisExtras) to keep this module's
|
|
144
|
+
// definition() under the 64 KB limit. SharedObjects are global, so it stays cross-module.
|
|
145
|
+
// In-memory FeatureCollectionLayer — built from a client-side schema + features (no service).
|
|
146
|
+
Class(FeatureCollectionLayerRef::class) {
|
|
147
|
+
Constructor { props: Map<String, Any?> ->
|
|
148
|
+
FeatureCollectionLayerRef(appContext, props).also { it.applyProps(props) }
|
|
149
|
+
}
|
|
150
|
+
Function("applyProps") { ref: FeatureCollectionLayerRef, changed: Map<String, Any?> -> ref.applyProps(changed) }
|
|
151
|
+
}
|
|
152
|
+
// GeoPackage layer — async-loads a local .gpkg, picks the feature table, wraps it in a FeatureLayer.
|
|
153
|
+
Class(GeoPackageLayerRef::class) {
|
|
154
|
+
Constructor { props: Map<String, Any?> ->
|
|
155
|
+
GeoPackageLayerRef(
|
|
156
|
+
appContext,
|
|
157
|
+
props["path"] as? String ?: "",
|
|
158
|
+
props["tableName"] as? String,
|
|
159
|
+
).also { it.applyProps(props) }
|
|
160
|
+
}
|
|
161
|
+
Function("applyProps") { ref: GeoPackageLayerRef, changed: Map<String, Any?> -> ref.applyProps(changed) }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Auth — persistent credential store (survives app restarts via Android encrypted storage).
|
|
165
|
+
// Registered here (not in the main module) because the main module is at the JVM 64 KB limit.
|
|
166
|
+
AsyncFunction("enablePersistentCredentialStore") Coroutine { ->
|
|
167
|
+
val store = ArcGISCredentialStore.createWithPersistence().getOrThrow()
|
|
168
|
+
ArcGISEnvironment.authenticationManager.arcGISCredentialStore = store
|
|
169
|
+
}
|
|
170
|
+
AsyncFunction("clearCredentialStore") Coroutine { ->
|
|
171
|
+
ArcGISEnvironment.authenticationManager.arcGISCredentialStore.removeAll()
|
|
172
|
+
}
|
|
135
173
|
|
|
136
174
|
// Offline — take maps/data offline, exposed as the JS `offline` namespace.
|
|
137
175
|
AsyncFunction("generateOfflineMap") Coroutine { portalItemId: String, areaOfInterest: Map<String, Any?>, downloadName: String ->
|
|
@@ -196,6 +196,22 @@ class ExpoArcgisMapView(context: Context, appContext: AppContext) : ExpoView(con
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/** Identifies popups under a screen point — evaluates each and returns `{ title, fields }`. */
|
|
200
|
+
fun identifyPopups(screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise) {
|
|
201
|
+
val x = (screenPoint["x"] as? Number)?.toDouble() ?: 0.0
|
|
202
|
+
val y = (screenPoint["y"] as? Number)?.toDouble() ?: 0.0
|
|
203
|
+
val tolerance = (options?.get("tolerance") as? Number)?.toDouble() ?: 12.0
|
|
204
|
+
val maxResults = (options?.get("maxResults") as? Number)?.toInt() ?: 1
|
|
205
|
+
scope.launch {
|
|
206
|
+
try {
|
|
207
|
+
val results = mapView.identifyLayers(ScreenCoordinate(x, y), tolerance, false, maxResults).getOrThrow()
|
|
208
|
+
promise.resolve(serializePopups(results))
|
|
209
|
+
} catch (e: Exception) {
|
|
210
|
+
promise.reject("IDENTIFY_ERROR", e.message ?: "Identify failed", e)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
199
215
|
/** Retries loading the map (Loadable pattern) — useful after a network outage. Re-emits the result. */
|
|
200
216
|
fun retryLoad(promise: Promise) {
|
|
201
217
|
val map = mapView.map ?: run { promise.resolve(null); return }
|
|
@@ -96,38 +96,8 @@ class ExpoArcgisModule : Module() {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Operational layers — SharedObjects the JS <FeatureLayer>/<TileLayer> construct.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
FeatureLayerRef(appContext, props).also { it.applyProps(props) }
|
|
102
|
-
}
|
|
103
|
-
Function("applyProps") { ref: FeatureLayerRef, changed: Map<String, Any?> ->
|
|
104
|
-
ref.applyProps(changed)
|
|
105
|
-
}
|
|
106
|
-
AsyncFunction("queryFeatures") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
107
|
-
ref.queryFeatures(query)
|
|
108
|
-
}
|
|
109
|
-
AsyncFunction("queryFeatureCount") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
110
|
-
ref.queryFeatureCount(query)
|
|
111
|
-
}
|
|
112
|
-
AsyncFunction("queryExtent") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?>? ->
|
|
113
|
-
ref.queryExtent(query)
|
|
114
|
-
}
|
|
115
|
-
AsyncFunction("queryStatistics") Coroutine { ref: FeatureLayerRef, query: Map<String, Any?> ->
|
|
116
|
-
ref.queryStatistics(query)
|
|
117
|
-
}
|
|
118
|
-
AsyncFunction("queryFeatureTemplates") Coroutine { ref: FeatureLayerRef ->
|
|
119
|
-
ref.queryFeatureTemplates()
|
|
120
|
-
}
|
|
121
|
-
AsyncFunction("addFeature") Coroutine { ref: FeatureLayerRef, attributes: Map<String, Any?>, geometry: Map<String, Any?>? ->
|
|
122
|
-
ref.addFeature(attributes, geometry)
|
|
123
|
-
}
|
|
124
|
-
AsyncFunction("updateFeature") Coroutine { ref: FeatureLayerRef, objectId: Long, changes: Map<String, Any?> ->
|
|
125
|
-
ref.updateFeature(objectId, changes)
|
|
126
|
-
}
|
|
127
|
-
AsyncFunction("deleteFeature") Coroutine { ref: FeatureLayerRef, objectId: Long ->
|
|
128
|
-
ref.deleteFeature(objectId)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
99
|
+
// FeatureLayerRef is registered on the ExpoArcgisGeometry module to keep this module's
|
|
100
|
+
// definition() under the Android JVM 64 KB method-size limit (SharedObjects cross modules).
|
|
131
101
|
|
|
132
102
|
Class(TiledLayerRef::class) {
|
|
133
103
|
Constructor { props: Map<String, Any?> ->
|
|
@@ -358,6 +328,10 @@ class ExpoArcgisModule : Module() {
|
|
|
358
328
|
AsyncFunction("associations") Coroutine { ref: UtilityNetworkRef, tableName: String, whereClause: String ->
|
|
359
329
|
ref.associations(tableName, whereClause)
|
|
360
330
|
}
|
|
331
|
+
AsyncFunction("getState") Coroutine { ref: UtilityNetworkRef -> ref.getState() }
|
|
332
|
+
Function("validateNetworkTopology") { ref: UtilityNetworkRef, extent: Map<String, Any?> ->
|
|
333
|
+
ref.validateNetworkTopology(extent)
|
|
334
|
+
}
|
|
361
335
|
}
|
|
362
336
|
|
|
363
337
|
// Interactive GeometryEditor — bound to a <MapView> for sketching; emits onGeometryChange.
|
|
@@ -401,6 +375,10 @@ class ExpoArcgisModule : Module() {
|
|
|
401
375
|
view.identify(screenPoint, options, promise)
|
|
402
376
|
}
|
|
403
377
|
|
|
378
|
+
AsyncFunction("identifyPopups") { view: ExpoArcgisMapView, screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise ->
|
|
379
|
+
view.identifyPopups(screenPoint, options, promise)
|
|
380
|
+
}
|
|
381
|
+
|
|
404
382
|
AsyncFunction("retryLoad") { view: ExpoArcgisMapView, promise: Promise ->
|
|
405
383
|
view.retryLoad(promise)
|
|
406
384
|
}
|
|
@@ -446,6 +424,10 @@ class ExpoArcgisModule : Module() {
|
|
|
446
424
|
AsyncFunction("identify") { view: ExpoArcgisSceneView, screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise ->
|
|
447
425
|
view.identify(screenPoint, options, promise)
|
|
448
426
|
}
|
|
427
|
+
|
|
428
|
+
AsyncFunction("identifyPopups") { view: ExpoArcgisSceneView, screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise ->
|
|
429
|
+
view.identifyPopups(screenPoint, options, promise)
|
|
430
|
+
}
|
|
449
431
|
}
|
|
450
432
|
}
|
|
451
433
|
}
|
|
@@ -101,6 +101,22 @@ class ExpoArcgisSceneView(context: Context, appContext: AppContext) : ExpoView(c
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/** Identifies popups under a screen point — evaluates each and returns `{ title, fields }`. */
|
|
105
|
+
fun identifyPopups(screenPoint: Map<String, Any?>, options: Map<String, Any?>?, promise: Promise) {
|
|
106
|
+
val x = (screenPoint["x"] as? Number)?.toDouble() ?: 0.0
|
|
107
|
+
val y = (screenPoint["y"] as? Number)?.toDouble() ?: 0.0
|
|
108
|
+
val tolerance = (options?.get("tolerance") as? Number)?.toDouble() ?: 12.0
|
|
109
|
+
val maxResults = (options?.get("maxResults") as? Number)?.toInt() ?: 1
|
|
110
|
+
scope.launch {
|
|
111
|
+
try {
|
|
112
|
+
val results = sceneView.identifyLayers(ScreenCoordinate(x, y), tolerance, false, maxResults).getOrThrow()
|
|
113
|
+
promise.resolve(serializePopups(results))
|
|
114
|
+
} catch (e: Exception) {
|
|
115
|
+
promise.reject("IDENTIFY_ERROR", e.message ?: "Identify failed", e)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
104
120
|
/** Retries loading the scene (Loadable pattern) — useful after a network outage. Re-emits the result. */
|
|
105
121
|
fun retryLoad(promise: Promise) {
|
|
106
122
|
val scene = sceneView.scene ?: run { promise.resolve(null); return }
|
|
@@ -25,9 +25,19 @@ private fun locatorTask(params: Map<String, Any?>): LocatorTask {
|
|
|
25
25
|
return locators.getOrPut(url) { LocatorTask(url) }
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
internal suspend fun geocode(searchText: String, params: Map<String, Any?>): List<Map<String, Any?>>
|
|
29
|
-
locatorTask(params)
|
|
30
|
-
|
|
28
|
+
internal suspend fun geocode(searchText: String, params: Map<String, Any?>): List<Map<String, Any?>> {
|
|
29
|
+
val locator = locatorTask(params)
|
|
30
|
+
val parameters = buildGeocodeParameters(params)
|
|
31
|
+
@Suppress("UNCHECKED_CAST")
|
|
32
|
+
val searchValues = (params["searchValues"] as? Map<*, *>)
|
|
33
|
+
?.mapNotNull { (k, v) -> if (k is String && v is String) k to v else null }
|
|
34
|
+
?.toMap()
|
|
35
|
+
return if (!searchValues.isNullOrEmpty()) {
|
|
36
|
+
locator.geocode(searchValues, parameters).getOrThrow()
|
|
37
|
+
} else {
|
|
38
|
+
locator.geocode(searchText, parameters).getOrThrow()
|
|
39
|
+
}.map { serializeGeocodeResult(it) }
|
|
40
|
+
}
|
|
31
41
|
|
|
32
42
|
internal suspend fun reverseGeocode(point: Map<String, Any?>, params: Map<String, Any?>): List<Map<String, Any?>> {
|
|
33
43
|
val location = geometryFromDict(point) as? Point ?: return emptyList()
|
|
@@ -3,7 +3,9 @@ package expo.modules.arcgis
|
|
|
3
3
|
import com.arcgismaps.geometry.Envelope
|
|
4
4
|
import com.arcgismaps.geometry.Geometry
|
|
5
5
|
import com.arcgismaps.geometry.GeometryEngine
|
|
6
|
+
import com.arcgismaps.geometry.Multipart
|
|
6
7
|
import com.arcgismaps.geometry.Point
|
|
8
|
+
import com.arcgismaps.geometry.Polygon
|
|
7
9
|
import com.arcgismaps.geometry.Polyline
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -104,6 +106,39 @@ internal fun geCut(g: Map<String, Any?>, cutter: Map<String, Any?>): List<Map<St
|
|
|
104
106
|
internal fun geConvexHull(g: Map<String, Any?>): Map<String, Any?>? =
|
|
105
107
|
parseGeo(g)?.let { encode(GeometryEngine.convexHullOrNull(it)) }
|
|
106
108
|
|
|
109
|
+
internal fun geLabelPoint(g: Map<String, Any?>): Map<String, Any?>? =
|
|
110
|
+
(parseGeo(g) as? Polygon)?.let { encode(GeometryEngine.labelPointOrNull(it)) }
|
|
111
|
+
|
|
112
|
+
internal fun geNormalizeCentralMeridian(g: Map<String, Any?>): Map<String, Any?>? =
|
|
113
|
+
parseGeo(g)?.let { encode(GeometryEngine.normalizeCentralMeridian(it)) }
|
|
114
|
+
|
|
115
|
+
internal fun geReshape(g: Map<String, Any?>, reshaper: Map<String, Any?>): Map<String, Any?>? {
|
|
116
|
+
val multipart = parseGeo(g) as? Multipart ?: return null
|
|
117
|
+
val line = parseGeo(reshaper) as? Polyline ?: return null
|
|
118
|
+
return encode(GeometryEngine.reshape(multipart, line))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
internal fun geIntersections(a: Map<String, Any?>, b: Map<String, Any?>): List<Map<String, Any?>> {
|
|
122
|
+
val g1 = parseGeo(a) ?: return emptyList()
|
|
123
|
+
val g2 = parseGeo(b) ?: return emptyList()
|
|
124
|
+
return GeometryEngine.tryIntersections(g1, g2).mapNotNull { encode(it) }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
internal fun geExtend(p: Map<String, Any?>, extender: Map<String, Any?>): Map<String, Any?>? {
|
|
128
|
+
val polyline = parseGeo(p) as? Polyline ?: return null
|
|
129
|
+
val ext = parseGeo(extender) as? Polyline ?: return null
|
|
130
|
+
return encode(GeometryEngine.extend(polyline, ext, emptySet()))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
internal fun geAutoComplete(
|
|
134
|
+
existing: List<Map<String, Any?>>,
|
|
135
|
+
boundaries: List<Map<String, Any?>>,
|
|
136
|
+
): List<Map<String, Any?>> {
|
|
137
|
+
val polygons = existing.mapNotNull { parseGeo(it) as? Polygon }
|
|
138
|
+
val lines = boundaries.mapNotNull { parseGeo(it) as? Polyline }
|
|
139
|
+
return GeometryEngine.tryAutoComplete(polygons, lines).mapNotNull { encode(it) }
|
|
140
|
+
}
|
|
141
|
+
|
|
107
142
|
internal fun geBoundary(g: Map<String, Any?>): Map<String, Any?>? =
|
|
108
143
|
parseGeo(g)?.let { encode(GeometryEngine.boundaryOrNull(it)) }
|
|
109
144
|
|
|
@@ -15,8 +15,11 @@ import com.arcgismaps.mapping.symbology.SimpleFillSymbol
|
|
|
15
15
|
import com.arcgismaps.mapping.symbology.SimpleFillSymbolStyle
|
|
16
16
|
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
|
|
17
17
|
import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
|
|
18
|
+
import com.arcgismaps.mapping.symbology.DistanceCompositeSceneSymbol
|
|
19
|
+
import com.arcgismaps.mapping.symbology.DistanceSymbolRange
|
|
18
20
|
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbol
|
|
19
21
|
import com.arcgismaps.mapping.symbology.SimpleMarkerSceneSymbolStyle
|
|
22
|
+
import com.arcgismaps.mapping.symbology.PictureFillSymbol
|
|
20
23
|
import com.arcgismaps.mapping.symbology.PictureMarkerSymbol
|
|
21
24
|
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol
|
|
22
25
|
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle
|
|
@@ -188,6 +191,28 @@ private fun buildSymbol(s: Map<*, *>): Symbol? = when (s["type"]) {
|
|
|
188
191
|
(s["height"] as? Number)?.toFloat()?.let { height = it }
|
|
189
192
|
}
|
|
190
193
|
}
|
|
194
|
+
"picture-fill" -> (s["url"] as? String)?.let { url ->
|
|
195
|
+
PictureFillSymbol(url).apply {
|
|
196
|
+
(s["width"] as? Number)?.toFloat()?.let { width = it }
|
|
197
|
+
(s["height"] as? Number)?.toFloat()?.let { height = it }
|
|
198
|
+
outline = outlineOf(s["outline"])
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
"distance-composite-scene" -> {
|
|
202
|
+
val composite = DistanceCompositeSceneSymbol()
|
|
203
|
+
val rangeList = s["ranges"] as? List<*> ?: emptyList<Any>()
|
|
204
|
+
for (rd in rangeList) {
|
|
205
|
+
val rdMap = rd as? Map<*, *> ?: continue
|
|
206
|
+
val sym = (rdMap["symbol"] as? Map<*, *>)?.let(::buildSymbol) ?: continue
|
|
207
|
+
val range = DistanceSymbolRange(
|
|
208
|
+
sym,
|
|
209
|
+
(rdMap["minDistance"] as? Number)?.toDouble(),
|
|
210
|
+
(rdMap["maxDistance"] as? Number)?.toDouble(),
|
|
211
|
+
)
|
|
212
|
+
composite.ranges.add(range)
|
|
213
|
+
}
|
|
214
|
+
composite
|
|
215
|
+
}
|
|
191
216
|
else -> null
|
|
192
217
|
}
|
|
193
218
|
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
package expo.modules.arcgis
|
|
2
2
|
|
|
3
|
+
import android.util.Base64
|
|
4
|
+
import com.arcgismaps.data.ArcGISFeature
|
|
3
5
|
import com.arcgismaps.data.ArcGISFeatureTable
|
|
4
6
|
import com.arcgismaps.data.Feature
|
|
7
|
+
import com.arcgismaps.data.FeatureCollection
|
|
8
|
+
import com.arcgismaps.data.FeatureCollectionTable
|
|
5
9
|
import com.arcgismaps.data.FeatureRequestMode
|
|
6
10
|
import com.arcgismaps.data.FeatureTable
|
|
11
|
+
import com.arcgismaps.data.Field
|
|
12
|
+
import com.arcgismaps.data.FieldType
|
|
13
|
+
import com.arcgismaps.data.GeoPackage
|
|
7
14
|
import com.arcgismaps.data.QueryFeatureFields
|
|
8
15
|
import com.arcgismaps.data.QueryParameters
|
|
9
16
|
import com.arcgismaps.data.ServiceFeatureTable
|
|
@@ -11,12 +18,17 @@ import com.arcgismaps.data.ShapefileFeatureTable
|
|
|
11
18
|
import com.arcgismaps.data.OgcFeatureCollectionTable
|
|
12
19
|
import com.arcgismaps.data.WfsFeatureTable
|
|
13
20
|
import com.arcgismaps.mapping.layers.AnnotationLayer
|
|
21
|
+
import kotlinx.coroutines.CoroutineScope
|
|
22
|
+
import kotlinx.coroutines.Dispatchers
|
|
23
|
+
import kotlinx.coroutines.SupervisorJob
|
|
24
|
+
import kotlinx.coroutines.launch
|
|
14
25
|
import com.arcgismaps.mapping.layers.ArcGISMapImageLayer
|
|
15
26
|
import com.arcgismaps.mapping.layers.ArcGISSceneLayer
|
|
16
27
|
import com.arcgismaps.mapping.layers.ArcGISTiledLayer
|
|
17
28
|
import com.arcgismaps.mapping.layers.ArcGISVectorTiledLayer
|
|
18
29
|
import com.arcgismaps.mapping.layers.BuildingSceneLayer
|
|
19
30
|
import com.arcgismaps.mapping.layers.DimensionLayer
|
|
31
|
+
import com.arcgismaps.mapping.layers.FeatureCollectionLayer
|
|
20
32
|
import com.arcgismaps.mapping.layers.FeatureLayer
|
|
21
33
|
import com.arcgismaps.mapping.layers.GroupLayer
|
|
22
34
|
import com.arcgismaps.mapping.layers.IntegratedMeshLayer
|
|
@@ -32,6 +44,7 @@ import com.arcgismaps.mapping.layers.KmlLayer
|
|
|
32
44
|
import com.arcgismaps.mapping.layers.WmsLayer
|
|
33
45
|
import com.arcgismaps.mapping.layers.WmtsLayer
|
|
34
46
|
import com.arcgismaps.mapping.kml.KmlDataset
|
|
47
|
+
import com.arcgismaps.mapping.view.Graphic
|
|
35
48
|
import com.arcgismaps.raster.ImageServiceRaster
|
|
36
49
|
import com.arcgismaps.raster.Raster
|
|
37
50
|
import expo.modules.kotlin.AppContext
|
|
@@ -87,28 +100,115 @@ class FeatureLayerRef(appContext: AppContext, props: Map<String, Any?>) : LayerR
|
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Adds a feature. When `apply` is not `false`, pushes the edit and returns the new object id;
|
|
105
|
+
* pass `apply = false` to make a local-only edit (batch with `applyEdits`).
|
|
106
|
+
*/
|
|
107
|
+
suspend fun addFeature(attributes: Map<String, Any?>, geometry: Map<String, Any?>?, apply: Boolean?): Long? {
|
|
92
108
|
val feature = table.createFeature()
|
|
93
109
|
applyAttributes(feature, attributes)
|
|
94
110
|
geometry?.let { dict -> geometryFromDict(dict)?.let { feature.geometry = it } }
|
|
95
111
|
table.addFeature(feature).getOrThrow()
|
|
112
|
+
if (apply == false) return null
|
|
96
113
|
return persistEdits()
|
|
97
114
|
}
|
|
98
115
|
|
|
99
|
-
/** Updates the feature with `objectId
|
|
100
|
-
suspend fun updateFeature(objectId: Long, changes: Map<String, Any
|
|
116
|
+
/** Updates the feature with `objectId`. Pass `apply = false` for a local-only edit. */
|
|
117
|
+
suspend fun updateFeature(objectId: Long, changes: Map<String, Any?>, apply: Boolean?) {
|
|
101
118
|
val feature = featureByObjectId(objectId) ?: return
|
|
102
119
|
(changes["attributes"] as? Map<*, *>)?.let { applyAttributes(feature, it) }
|
|
103
120
|
(changes["geometry"] as? Map<*, *>)?.let { geometryFromDict(it)?.let { g -> feature.geometry = g } }
|
|
104
121
|
table.updateFeature(feature).getOrThrow()
|
|
105
|
-
persistEdits()
|
|
122
|
+
if (apply != false) persistEdits()
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
/** Deletes the feature with `objectId`
|
|
109
|
-
suspend fun deleteFeature(objectId: Long) {
|
|
125
|
+
/** Deletes the feature with `objectId`. Pass `apply = false` for a local-only edit. */
|
|
126
|
+
suspend fun deleteFeature(objectId: Long, apply: Boolean?) {
|
|
110
127
|
val feature = featureByObjectId(objectId) ?: return
|
|
111
128
|
table.deleteFeature(feature).getOrThrow()
|
|
129
|
+
if (apply != false) persistEdits()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Pushes all pending local edits to the service in one batch; returns each edit's result. */
|
|
133
|
+
suspend fun applyEdits(): List<Map<String, Any?>> {
|
|
134
|
+
val serviceTable = table as? ServiceFeatureTable ?: return emptyList()
|
|
135
|
+
return serviceTable.applyEdits().getOrThrow().map {
|
|
136
|
+
mapOf("objectId" to it.objectId, "completedWithErrors" to it.completedWithErrors)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Discards all pending local edits (since the last `applyEdits`). */
|
|
141
|
+
suspend fun undoLocalEdits() {
|
|
142
|
+
(table as? ServiceFeatureTable)?.undoLocalEdits()?.getOrThrow()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Queries features related to `objectId` (across all relationships); returns groups by relationship. */
|
|
146
|
+
suspend fun queryRelatedFeatures(objectId: Long): List<Map<String, Any?>> {
|
|
147
|
+
val arcgisTable = table as? ArcGISFeatureTable ?: return emptyList()
|
|
148
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature ?: return emptyList()
|
|
149
|
+
return arcgisTable.queryRelatedFeatures(feature).getOrThrow().map { result ->
|
|
150
|
+
mapOf(
|
|
151
|
+
"relationshipId" to (result.relationshipInfo?.id ?: -1L),
|
|
152
|
+
"features" to result.map { serializeFeature(it) },
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Queries the attachments for the feature with `objectId`; returns `[{id, name, contentType, size}]`. */
|
|
158
|
+
suspend fun queryAttachments(objectId: Long): List<Map<String, Any?>> {
|
|
159
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature ?: return emptyList()
|
|
160
|
+
return feature.fetchAttachments().getOrThrow().map { attachment ->
|
|
161
|
+
mapOf(
|
|
162
|
+
"id" to attachment.id,
|
|
163
|
+
"name" to attachment.name,
|
|
164
|
+
"contentType" to attachment.contentType,
|
|
165
|
+
"size" to attachment.size,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Decodes `dataBase64`, adds it as an attachment to the feature, then persists the edit. */
|
|
171
|
+
suspend fun addAttachment(objectId: Long, name: String, contentType: String, dataBase64: String) {
|
|
172
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature ?: return
|
|
173
|
+
val bytes = Base64.decode(dataBase64, Base64.NO_WRAP)
|
|
174
|
+
feature.addAttachment(name, contentType, bytes).getOrThrow()
|
|
175
|
+
persistEdits()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Fetches the binary data for the attachment with `attachmentId` and returns it as base64. */
|
|
179
|
+
suspend fun fetchAttachment(objectId: Long, attachmentId: Long): String {
|
|
180
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature
|
|
181
|
+
?: error("Feature not found: $objectId")
|
|
182
|
+
val attachment = feature.fetchAttachments().getOrThrow()
|
|
183
|
+
.firstOrNull { it.id == attachmentId }
|
|
184
|
+
?: error("Attachment not found: $attachmentId")
|
|
185
|
+
val bytes = attachment.fetchData().getOrThrow()
|
|
186
|
+
return Base64.encodeToString(bytes, Base64.NO_WRAP)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Deletes the attachment with `attachmentId` from the feature with `objectId`, then persists. */
|
|
190
|
+
suspend fun deleteAttachment(objectId: Long, attachmentId: Long) {
|
|
191
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature
|
|
192
|
+
?: error("Feature not found: $objectId")
|
|
193
|
+
val attachment = feature.fetchAttachments().getOrThrow()
|
|
194
|
+
.firstOrNull { it.id == attachmentId }
|
|
195
|
+
?: error("Attachment not found: $attachmentId")
|
|
196
|
+
feature.deleteAttachment(attachment).getOrThrow()
|
|
197
|
+
persistEdits()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Decodes `dataBase64` and updates the attachment with `attachmentId`, then persists. */
|
|
201
|
+
suspend fun updateAttachment(
|
|
202
|
+
objectId: Long, attachmentId: Long,
|
|
203
|
+
name: String, contentType: String, dataBase64: String,
|
|
204
|
+
) {
|
|
205
|
+
val feature = featureByObjectId(objectId) as? ArcGISFeature
|
|
206
|
+
?: error("Feature not found: $objectId")
|
|
207
|
+
val attachment = feature.fetchAttachments().getOrThrow()
|
|
208
|
+
.firstOrNull { it.id == attachmentId }
|
|
209
|
+
?: error("Attachment not found: $attachmentId")
|
|
210
|
+
val bytes = Base64.decode(dataBase64, Base64.NO_WRAP)
|
|
211
|
+
feature.updateAttachment(attachment, name, contentType, bytes).getOrThrow()
|
|
112
212
|
persistEdits()
|
|
113
213
|
}
|
|
114
214
|
|
|
@@ -314,3 +414,85 @@ class GroupLayerRef(appContext: AppContext) : LayerRef(appContext) {
|
|
|
314
414
|
|
|
315
415
|
override fun applyProps(changed: Map<String, Any?>) = applyCommonProps(changed)
|
|
316
416
|
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* In-memory [FeatureCollectionLayer] — a layer built from a client-side schema (`fields`) and
|
|
420
|
+
* `features` (no service). Features become graphics in a [FeatureCollectionTable].
|
|
421
|
+
*/
|
|
422
|
+
class FeatureCollectionLayerRef(appContext: AppContext, props: Map<String, Any?>) : LayerRef(appContext) {
|
|
423
|
+
private val table: FeatureCollectionTable = run {
|
|
424
|
+
val fields = (props["fields"] as? List<*> ?: emptyList<Any?>())
|
|
425
|
+
.mapNotNull { (it as? Map<*, *>)?.let(::makeFeatureCollectionField) }
|
|
426
|
+
val graphics = (props["features"] as? List<*> ?: emptyList<Any?>()).mapNotNull { spec ->
|
|
427
|
+
val s = spec as? Map<*, *> ?: return@mapNotNull null
|
|
428
|
+
Graphic().apply {
|
|
429
|
+
geometry = (s["geometry"] as? Map<*, *>)?.let { geometryFromDict(it) }
|
|
430
|
+
(s["attributes"] as? Map<*, *>)?.forEach { (k, v) -> attributes[k.toString()] = v }
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
FeatureCollectionTable(graphics, fields).apply {
|
|
434
|
+
renderer = (props["renderer"] as? Map<*, *>)?.let { buildRenderer(it) }
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
override val layer: Layer = FeatureCollectionLayer(FeatureCollection().apply { tables.add(table) })
|
|
438
|
+
|
|
439
|
+
override fun applyProps(changed: Map<String, Any?>) {
|
|
440
|
+
applyCommonProps(changed)
|
|
441
|
+
if (changed.containsKey("renderer")) {
|
|
442
|
+
table.renderer = (changed["renderer"] as? Map<*, *>)?.let { buildRenderer(it) }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Operational layer loaded from a local GeoPackage (`.gpkg`) file. Opens the GeoPackage
|
|
449
|
+
* asynchronously, picks the feature table by [tableName] (or the first when null), wraps it in a
|
|
450
|
+
* [FeatureLayer], and attaches it to the placeholder [GroupLayer] once ready.
|
|
451
|
+
*/
|
|
452
|
+
class GeoPackageLayerRef(appContext: AppContext, path: String, tableName: String?) :
|
|
453
|
+
LayerRef(appContext) {
|
|
454
|
+
|
|
455
|
+
private val group = GroupLayer(emptyList())
|
|
456
|
+
override val layer: Layer = group
|
|
457
|
+
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
458
|
+
|
|
459
|
+
init {
|
|
460
|
+
scope.launch {
|
|
461
|
+
val pkg = GeoPackage(path)
|
|
462
|
+
pkg.load().onFailure { return@launch }
|
|
463
|
+
val tables = pkg.geoPackageFeatureTables
|
|
464
|
+
if (tables.isEmpty()) return@launch
|
|
465
|
+
val table = if (tableName != null) {
|
|
466
|
+
tables.firstOrNull { it.tableName == tableName } ?: return@launch
|
|
467
|
+
} else {
|
|
468
|
+
tables[0]
|
|
469
|
+
}
|
|
470
|
+
val featureLayer = FeatureLayer.createWithFeatureTable(table)
|
|
471
|
+
group.layers.add(featureLayer)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
override fun applyProps(changed: Map<String, Any?>) = applyCommonProps(changed)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private fun makeFeatureCollectionField(d: Map<*, *>): Field {
|
|
479
|
+
val name = d["name"] as? String ?: ""
|
|
480
|
+
return Field(
|
|
481
|
+
featureCollectionFieldType(d["type"] as? String),
|
|
482
|
+
name,
|
|
483
|
+
d["alias"] as? String ?: name,
|
|
484
|
+
(d["length"] as? Number)?.toInt() ?: 255,
|
|
485
|
+
null,
|
|
486
|
+
true,
|
|
487
|
+
true,
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private fun featureCollectionFieldType(value: String?): FieldType = when (value) {
|
|
492
|
+
"int16" -> FieldType.Int16
|
|
493
|
+
"integer" -> FieldType.Int32
|
|
494
|
+
"long" -> FieldType.Int64
|
|
495
|
+
"double" -> FieldType.Float64
|
|
496
|
+
"date" -> FieldType.Date
|
|
497
|
+
else -> FieldType.Text
|
|
498
|
+
}
|