Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/android-for-arcgis.api
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public final class com.mapconductor.arcgis.ArcGISMapView2DHolder implements com.
public java.lang.Object getMap();
}
public final class com.mapconductor.arcgis.ArcGISMapView2DKt {
public static final void ArcGISMapView2D(com.mapconductor.arcgis.ArcGISMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2<? super android.content.Context, ? super kotlin.coroutines.Continuation<? super java.lang.Boolean>, ? extends java.lang.Object>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapViewStateInterface<?>, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function3<? super com.mapconductor.arcgis.ArcGISMapViewScope, ? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int, int, int);
public static final void ArcGISMapView2D(com.mapconductor.arcgis.ArcGISMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2<? super android.content.Context, ? super kotlin.coroutines.Continuation<? super java.lang.Boolean>, ? extends java.lang.Object>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapViewStateInterface<?>, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, com.mapconductor.core.map.MapViewStyle, kotlin.jvm.functions.Function1<? super java.util.List<java.lang.String>, kotlin.Unit>, kotlin.jvm.functions.Function3<? super com.mapconductor.arcgis.ArcGISMapViewScope, ? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int, int, int);
}
public final class com.mapconductor.arcgis.ArcGISMapViewController extends com.mapconductor.core.controller.BaseMapViewController implements com.mapconductor.arcgis.ArcGISMapViewControllerInterface {
public com.mapconductor.arcgis.ArcGISMapViewController(com.mapconductor.arcgis.ArcGISMapViewHolder, com.mapconductor.arcgis.marker.ArcGISMarkerController, com.mapconductor.arcgis.polyline.ArcGISPolylineOverlayController, com.mapconductor.arcgis.polygon.ArcGISPolygonOverlayController, com.mapconductor.arcgis.circle.ArcGISCircleOverlayController, com.mapconductor.arcgis.groundimage.ArcGISGroundImageController, com.mapconductor.arcgis.raster.ArcGISRasterLayerController, kotlinx.coroutines.CoroutineScope, kotlinx.coroutines.CoroutineScope);
Expand Down Expand Up @@ -287,7 +287,7 @@ public final class com.mapconductor.arcgis.ArcGISMapViewInitOptions {
public boolean equals(java.lang.Object);
}
public final class com.mapconductor.arcgis.ArcGISMapViewKt {
public static final void ArcGISMapView(com.mapconductor.arcgis.ArcGISMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2<? super android.content.Context, ? super kotlin.coroutines.Continuation<? super java.lang.Boolean>, ? extends java.lang.Object>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapViewStateInterface<?>, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function3<? super com.mapconductor.arcgis.ArcGISMapViewScope, ? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int, int, int);
public static final void ArcGISMapView(com.mapconductor.arcgis.ArcGISMapViewState, androidx.compose.ui.Modifier, com.mapconductor.core.marker.MarkerTilingOptions, com.mapconductor.core.map.CameraRestriction, kotlin.jvm.functions.Function2<? super android.content.Context, ? super kotlin.coroutines.Continuation<? super java.lang.Boolean>, ? extends java.lang.Object>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapViewStateInterface<?>, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.map.MapCameraPosition, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, kotlin.jvm.functions.Function1<? super com.mapconductor.core.features.GeoPoint, kotlin.Unit>, com.mapconductor.core.map.MapViewStyle, kotlin.jvm.functions.Function1<? super java.util.List<java.lang.String>, kotlin.Unit>, kotlin.jvm.functions.Function3<? super com.mapconductor.arcgis.ArcGISMapViewScope, ? super androidx.compose.runtime.Composer, ? super java.lang.Integer, kotlin.Unit>, androidx.compose.runtime.Composer, int, int, int);
public static final com.mapconductor.arcgis.circle.ArcGISCircleOverlayController getCircleController(com.mapconductor.arcgis.ArcGISGeoViewHolder<?, ?>, boolean);
public static final com.mapconductor.arcgis.polyline.ArcGISPolylineOverlayController getPolylineController(com.mapconductor.arcgis.ArcGISGeoViewHolder<?, ?>, boolean);
public static final com.mapconductor.arcgis.polygon.ArcGISPolygonOverlayController getPolygonController(com.mapconductor.arcgis.ArcGISGeoViewHolder<?, ?>, boolean);
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/com/mapconductor/arcgis/ArcGIS2DTiltEmulation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ internal object ArcGIS2DTiltEmulation {
val tiltAbsRad = Math.toRadians(tiltAbsDeg)
val altitude = converter.zoomLevelToAltitude(position.zoom, position.position.latitude, 0.0)
val distanceForward = altitude * cos(tiltAbsRad) * tan(tiltAbsRad) * TARGET_DISTANCE_SCALE
val target = Spherical.computeOffset(position.position, distanceForward, CameraBearing.toNativeHeading(position.bearing))
val target =
Spherical
.computeOffset(position.position, distanceForward, CameraBearing.toNativeHeading(position.bearing))
return target to zoom
}

Expand All @@ -82,7 +84,11 @@ internal object ArcGIS2DTiltEmulation {
val tiltAbsRad = Math.toRadians(tiltAbsDeg)
val altitude = converter.zoomLevelToAltitude(originalZoom, center.latitude, 0.0)
val distanceBackward = altitude * cos(tiltAbsRad) * tan(tiltAbsRad) * TARGET_DISTANCE_SCALE
val originalPosition: GeoPoint = Spherical.computeOffset(center, distanceBackward, CameraBearing.toNativeHeading(bearing) + 180.0)
val originalPosition: GeoPoint =
Spherical.computeOffset(
center, distanceBackward,
CameraBearing.toNativeHeading(bearing) + 180.0,
)
return originalPosition to originalZoom
}
}
49 changes: 43 additions & 6 deletions src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.LoadStatus
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.ArcGISTiledElevationSource
import com.arcgismaps.mapping.Basemap
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.GraphicsRenderingMode
import com.arcgismaps.mapping.view.SceneView
Expand All @@ -36,16 +37,17 @@ import com.mapconductor.core.OnMapLoadedHandler
import com.mapconductor.core.map.CameraRestriction
import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapViewStyle
import com.mapconductor.core.marker.MarkerEventControllerInterface
import com.mapconductor.core.marker.MarkerManager
import com.mapconductor.core.marker.MarkerOverlayRendererInterface
import com.mapconductor.core.marker.MarkerRenderingStrategyInterface
import com.mapconductor.core.marker.MarkerRenderingSupport
import com.mapconductor.core.marker.MarkerRenderingSupportKey
import com.mapconductor.core.marker.MarkerTilingOptions
import com.mapconductor.core.marker.StrategyMarkerController
import com.mapconductor.core.raster.RasterTilePreference
import com.mapconductor.core.raster.RasterTilePreferenceKey
import com.mapconductor.core.marker.StrategyMarkerController
import com.mapconductor.core.tileserver.TileServerRegistry
import java.util.concurrent.atomic.AtomicLong
import android.content.Context
Expand All @@ -70,13 +72,24 @@ fun ArcGISMapView(
onCameraMoveEnd: OnCameraMoveHandler? = null,
onMapClick: OnMapEventHandler? = null,
onMapLongClick: OnMapEventHandler? = null,
/**
* How the map looks, when the app states it rather than naming a design.
*
* A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds
* one; what happens underneath depends on this backend and the app does
* not have to know.
*/
style: MapViewStyle? = null,
onStyleDiagnostics: ((List<String>) -> Unit)? = null,
content: (@Composable ArcGISMapViewScope.() -> Unit)? = null,
) {
val scope = remember { ArcGISMapViewScope() } // Use specific scope
val context = LocalContext.current // Context will be available from MapViewBase too if needed
val registry = remember { scope.buildRegistry() }
val owner = LocalLifecycleOwner.current
val basemapStyle = remember { ArcGISDesign.toBasemapStyleOrNull(state.mapDesignType) }
// The design the scene was built with; a design set on the state before
// the controller existed is applied once it does.
val createdDesign = remember { Ref<String>() }
val cameraState = remember { mutableStateOf<MapCameraPositionInterface?>(state.cameraPosition) }
val controllerRef = remember { Ref<ArcGISMapViewController>() }
val controllerGeneration = remember { AtomicLong(0L) }
Expand All @@ -101,21 +114,28 @@ fun ArcGISMapView(
scope = scope,
registry = registry,
holderProvider = { wrapView ->
val design = state.mapDesignType
createdDesign.value = design.getValue()
val options =
ArcGISMapViewInitOptions(
basemapStyle = basemapStyle,
elevationSources = state.mapDesignType.elevationSources,
basemapStyle = ArcGISDesign.toBasemapStyleOrNull(design),
elevationSources = design.elevationSources,
)

// No basemap is a scene with only what the app puts on it.
val scene = options.basemapStyle?.let { ArcGISScene(it) } ?: ArcGISScene()
// No basemap is a scene with only what the app puts on it. The
// basemap is set on an empty scene rather than built into it, so
// a scene whose basemap is out of reach is still a scene (a
// global one is always WGS84) that can draw its own layers.
val scene = ArcGISScene()
options.basemapStyle?.let { scene.setBasemap(Basemap(it)) }

options.elevationSources.forEach {
val source = ArcGISTiledElevationSource(it)
scene.baseSurface.elevationSources.add(source)
}

wrapView.sceneView.scene = scene
var retried = false

val coroutine = CoroutineScope(Dispatchers.Default)

Expand All @@ -133,6 +153,16 @@ fun ArcGISMapView(
cont.resume(holder) { _, _, _ -> }
}
is LoadStatus.FailedToLoad -> {
Log.w("ArcGISMapView", "scene failed to load: ${it.error.message}")
// The basemap was taken away while it was being
// fetched (the app went basemap-less, say for an
// offline raster): without one the scene needs no
// network, so it gets one more go.
if (scene.basemap.value == null && !retried) {
retried = true
scene.retryLoad()
return@collect
}
// Offline or network error: resume with a holder anyway so
// the scene view is displayed (possibly with cached tiles)
// without crashing. The ArcGIS view may be blank but usable.
Expand Down Expand Up @@ -238,6 +268,11 @@ fun ArcGISMapView(
mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange)
cameraRestriction?.let { mapController.setCameraRestriction(it) }
state.setController(mapController)
if (state.mapDesignType.getValue() !=
createdDesign.value
) {
mapController.setMapDesignType(state.mapDesignType)
}

// Set camera listeners immediately so they are ready to receive
// camera updates from external sources (e.g. camera sync scenarios).
Expand Down Expand Up @@ -309,6 +344,8 @@ fun ArcGISMapView(
}
}
},
style = style,
onStyleDiagnostics = onStyleDiagnostics,
content = content,
)
}
Expand Down
47 changes: 45 additions & 2 deletions src/main/java/com/mapconductor/arcgis/ArcGISMapView2D.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import androidx.compose.ui.node.Ref
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.arcgismaps.LoadStatus
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.Basemap
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.GraphicsRenderingMode
import com.arcgismaps.mapping.view.MapView
Expand All @@ -20,6 +22,7 @@ import com.mapconductor.core.OnMapLoadedHandler
import com.mapconductor.core.map.CameraRestriction
import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapViewStyle
import com.mapconductor.core.marker.MarkerEventControllerInterface
import com.mapconductor.core.marker.MarkerOverlayRendererInterface
import com.mapconductor.core.marker.MarkerRenderingStrategyInterface
Expand All @@ -29,6 +32,7 @@ import com.mapconductor.core.marker.MarkerTilingOptions
import com.mapconductor.core.marker.StrategyMarkerController
import java.util.concurrent.atomic.AtomicLong
import android.content.Context
import android.util.Log
import android.widget.FrameLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -49,13 +53,24 @@ fun ArcGISMapView2D(
onCameraMoveEnd: OnCameraMoveHandler? = null,
onMapClick: OnMapEventHandler? = null,
onMapLongClick: OnMapEventHandler? = null,
/**
* How the map looks, when the app states it rather than naming a design.
*
* A vector style *is* the basemap. `com.mapconductor:vectorstyle` builds
* one; what happens underneath depends on this backend and the app does
* not have to know.
*/
style: MapViewStyle? = null,
onStyleDiagnostics: ((List<String>) -> Unit)? = null,
content: (@Composable ArcGISMapViewScope.() -> Unit)? = null,
) {
val scope = remember { ArcGISMapViewScope() }
val context = LocalContext.current
val registry = remember { scope.buildRegistry() }
val owner = LocalLifecycleOwner.current
val basemapStyle = remember { ArcGISDesign.toBasemapStyleOrNull(state.mapDesignType) }
// The design the map was built with; a design set on the state before the
// controller existed is applied once it does.
val createdDesign = remember { Ref<String>() }
val cameraState = remember { mutableStateOf<MapCameraPositionInterface?>(state.cameraPosition) }
val controllerRef = remember { Ref<ArcGISMapView2DController>() }
val controllerGeneration = remember { AtomicLong(0L) }
Expand All @@ -78,8 +93,19 @@ fun ArcGISMapView2D(
scope = scope,
registry = registry,
holderProvider = { wrapView ->
val map = basemapStyle?.let { ArcGISMap(it) } ?: ArcGISMap()
// The map is told its spatial reference up front instead of
// learning it from the basemap: a basemap style lives on
// arcgis.com, and a map that waited for it to find out where it
// is can draw nothing while the network is away. Told, it loads
// at once with no basemap, and with one it keeps drawing its own
// layers (a raster layer served on the device, say) when the
// basemap is out of reach.
val design = state.mapDesignType
createdDesign.value = design.getValue()
val map = ArcGISMap(SpatialReference.webMercator())
ArcGISDesign.toBasemapStyleOrNull(design)?.let { map.setBasemap(Basemap(it)) }
wrapView.arcGISMapView.map = map
var retried = false

val coroutine = CoroutineScope(Dispatchers.Default)
suspendCancellableCoroutine { cont ->
Expand All @@ -96,6 +122,16 @@ fun ArcGISMapView2D(
) { _, _, _ -> }
}
is LoadStatus.FailedToLoad -> {
Log.w("ArcGISMapView2D", "map failed to load: ${it.error.message}")
// The basemap was taken away while it was being
// fetched (the app went basemap-less, say for an
// offline raster): without one the map needs no
// network, so it gets one more go.
if (map.basemap.value == null && !retried) {
retried = true
map.retryLoad()
return@collect
}
if (cont.isActive) {
cont.resume(
ArcGISMapView2DHolder(
Expand Down Expand Up @@ -170,6 +206,11 @@ fun ArcGISMapView2D(
mapController.setMapLongClickListener(onMapLongClick)
mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange)
state.setController(mapController)
if (state.mapDesignType.getValue() !=
createdDesign.value
) {
mapController.setMapDesignType(state.mapDesignType)
}
// 他プロバイダの *MapView と同じく、コントローラ生成直後に適用する。
cameraRestriction?.let { mapController.setCameraRestriction(it) }

Expand Down Expand Up @@ -226,6 +267,8 @@ fun ArcGISMapView2D(
}
}
},
style = style,
onStyleDiagnostics = onStyleDiagnostics,
content = content,
)
}
Loading
Loading