完成了土方量计算
This commit is contained in:
@@ -28,8 +28,11 @@ import com.mapbox.maps.extension.style.sources.addSource
|
||||
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.openrndr.extra.shapes.splines.CatmullRomChain2
|
||||
@@ -54,7 +57,7 @@ class ContoursManager(
|
||||
private var contourSize: Int = 6
|
||||
private var heightRange: ClosedFloatingPointRange<Double> = 0.0..100.0
|
||||
private var cellSize: Double? = 10.0
|
||||
private val simplePalette = SimplePalette(
|
||||
val simplePalette = SimplePalette(
|
||||
range = 0.0..100.0
|
||||
)
|
||||
|
||||
@@ -103,18 +106,21 @@ class ContoursManager(
|
||||
}
|
||||
|
||||
private var isGridVisible: Boolean = true
|
||||
private var gridModel: GridModel? = null
|
||||
private var _gridModel = MutableStateFlow<GridModel?>(null)
|
||||
val gridModel = _gridModel.asStateFlow()
|
||||
|
||||
fun setGridVisible(visible: Boolean) {
|
||||
if (visible != isGridVisible) {
|
||||
isGridVisible = visible
|
||||
if (visible) {
|
||||
if (gridModel != null) mapView.displayGridModel(
|
||||
grid = gridModel!!,
|
||||
sourceId = gridSourceId,
|
||||
layerId = gridLayerId,
|
||||
palette = simplePalette::palette
|
||||
)
|
||||
_gridModel.value?.let { gridModel ->
|
||||
mapView.displayGridModel(
|
||||
grid = gridModel,
|
||||
sourceId = gridSourceId,
|
||||
layerId = gridLayerId,
|
||||
palette = simplePalette::palette
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mapView.mapboxMap.getStyle { style ->
|
||||
try {
|
||||
@@ -149,12 +155,15 @@ class ContoursManager(
|
||||
}
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
fun refresh() {
|
||||
val points = points
|
||||
if (points.size <= 3) {
|
||||
context.toast("points size ${points.size}")
|
||||
return
|
||||
}
|
||||
job?.cancel()
|
||||
scope.launch {
|
||||
mapView.mapboxMap.getStyle { style ->
|
||||
val step = heightRange.endInclusive / contourSize
|
||||
@@ -175,7 +184,7 @@ class ContoursManager(
|
||||
delaunator = triangulation,
|
||||
cellSize = cellSize,
|
||||
)
|
||||
this@ContoursManager.gridModel = gridModel
|
||||
this@ContoursManager._gridModel.value = gridModel
|
||||
if (isGridVisible) mapView.displayGridModel(
|
||||
grid = gridModel,
|
||||
sourceId = gridSourceId,
|
||||
@@ -183,7 +192,7 @@ class ContoursManager(
|
||||
palette = simplePalette::palette
|
||||
)
|
||||
}
|
||||
scope.launch(Dispatchers.Default) {
|
||||
job = scope.launch(Dispatchers.Default) {
|
||||
val lineFeatures = mutableListOf<List<Feature>>()
|
||||
val features = zip.mapIndexed { index, range ->
|
||||
async {
|
||||
@@ -264,10 +273,10 @@ class ContoursManager(
|
||||
style.addSource(source)
|
||||
|
||||
val layer = lineLayer(layerId, sourceId) {
|
||||
lineColor(Expression.Companion.toColor(Expression.Companion.get("color"))) // 从属性获取颜色
|
||||
lineColor(Expression.toColor(Expression.Companion.get("color"))) // 从属性获取颜色
|
||||
lineWidth(1.0)
|
||||
lineCap(LineCap.Companion.ROUND)
|
||||
lineJoin(LineJoin.Companion.ROUND)
|
||||
lineCap(LineCap.ROUND)
|
||||
lineJoin(LineJoin.ROUND)
|
||||
lineOpacity(0.8)
|
||||
}
|
||||
style.addLayer(layer)
|
||||
@@ -288,7 +297,7 @@ class ContoursManager(
|
||||
style.addSource(source)
|
||||
|
||||
val layer = fillLayer(layerId, sourceId) {
|
||||
fillColor(Expression.Companion.toColor(Expression.Companion.get("color"))) // 从属性获取颜色
|
||||
fillColor(Expression.Companion.toColor(Expression.get("color"))) // 从属性获取颜色
|
||||
fillOpacity(0.5)
|
||||
fillAntialias(true)
|
||||
}
|
||||
|
||||
197
android/src/main/java/com/icegps/orx/ControllableArrow.kt
Normal file
197
android/src/main/java/com/icegps/orx/ControllableArrow.kt
Normal file
@@ -0,0 +1,197 @@
|
||||
package com.icegps.orx
|
||||
|
||||
import com.icegps.math.geometry.Angle
|
||||
import com.icegps.math.geometry.Vector2D
|
||||
import com.icegps.orx.ktx.toMapboxPoint
|
||||
import com.mapbox.geojson.Feature
|
||||
import com.mapbox.geojson.FeatureCollection
|
||||
import com.mapbox.geojson.LineString
|
||||
import com.mapbox.geojson.Point
|
||||
import com.mapbox.geojson.Polygon
|
||||
import com.mapbox.maps.MapView
|
||||
import com.mapbox.maps.Style
|
||||
import com.mapbox.maps.extension.style.expressions.generated.Expression
|
||||
import com.mapbox.maps.extension.style.layers.addLayer
|
||||
import com.mapbox.maps.extension.style.layers.generated.FillLayer
|
||||
import com.mapbox.maps.extension.style.layers.generated.LineLayer
|
||||
import com.mapbox.maps.extension.style.layers.properties.generated.LineCap
|
||||
import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin
|
||||
import com.mapbox.maps.extension.style.sources.addSource
|
||||
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* 设置趋势箭头图层
|
||||
*/
|
||||
fun setupTrendLayer(
|
||||
style: Style,
|
||||
trendSourceId: String,
|
||||
trendLayerId: String,
|
||||
features: List<Feature>
|
||||
) {
|
||||
val trendSource = geoJsonSource(trendSourceId) {
|
||||
featureCollection(FeatureCollection.fromFeatures(features))
|
||||
}
|
||||
|
||||
try {
|
||||
style.removeStyleLayer(trendLayerId)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
style.removeStyleLayer("$trendLayerId-head")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
if (style.styleSourceExists(trendSourceId)) {
|
||||
style.removeStyleSource(trendSourceId)
|
||||
}
|
||||
|
||||
style.addSource(trendSource)
|
||||
|
||||
val lineLayer = LineLayer(trendLayerId, trendSourceId).apply {
|
||||
lineColor(Expression.toColor(Expression.get("color")))
|
||||
lineWidth(4.0)
|
||||
lineCap(LineCap.ROUND)
|
||||
lineJoin(LineJoin.ROUND)
|
||||
}
|
||||
style.addLayer(lineLayer)
|
||||
|
||||
val headLayer = FillLayer("$trendLayerId-head", trendSourceId).apply {
|
||||
fillColor(Expression.toColor(Expression.get("color")))
|
||||
}
|
||||
style.addLayer(headLayer)
|
||||
}
|
||||
|
||||
fun MapView.displayControllableArrow(
|
||||
grid: GridModel,
|
||||
sourceId: String = "controllable-source-id-0",
|
||||
layerId: String = "controllable-layer-id-0",
|
||||
arrowScale: Double = 0.4,
|
||||
angle: Angle,
|
||||
onHeadArrowChange: (List<Point>) -> Unit
|
||||
) {
|
||||
mapboxMap.getStyle { style ->
|
||||
val centerX = (grid.minX + grid.maxX) / 2
|
||||
val centerY = (grid.minY + grid.maxY) / 2
|
||||
|
||||
val regionWidth = grid.maxX - grid.minX
|
||||
val regionHeight = grid.maxY - grid.minY
|
||||
val arrowLength = min(regionWidth, regionHeight) * arrowScale * 1.0
|
||||
|
||||
val arrowDirectionRad = angle.radians
|
||||
val endX = centerX + sin(arrowDirectionRad) * arrowLength
|
||||
val endY = centerY + cos(arrowDirectionRad) * arrowLength
|
||||
|
||||
val arrowLine = LineString.fromLngLats(
|
||||
listOf(
|
||||
Vector2D(centerX, centerY),
|
||||
Vector2D(endX, endY)
|
||||
).map { it.toMapboxPoint() }
|
||||
)
|
||||
|
||||
val arrowFeature = Feature.fromGeometry(arrowLine)
|
||||
arrowFeature.addStringProperty("color", "#0000FF")
|
||||
arrowFeature.addStringProperty("type", "overall-trend")
|
||||
|
||||
// 创建箭头头部
|
||||
val headSize = arrowLength * 0.2
|
||||
val leftRad = arrowDirectionRad + Math.PI * 0.8
|
||||
val rightRad = arrowDirectionRad - Math.PI * 0.8
|
||||
|
||||
val leftX = endX + sin(leftRad) * headSize
|
||||
val leftY = endY + cos(leftRad) * headSize
|
||||
val rightX = endX + sin(rightRad) * headSize
|
||||
val rightY = endY + cos(rightRad) * headSize
|
||||
|
||||
val headRing = listOf(
|
||||
Vector2D(endX, endY),
|
||||
Vector2D(leftX, leftY),
|
||||
Vector2D(rightX, rightY),
|
||||
Vector2D(endX, endY)
|
||||
).map { it.toMapboxPoint() }
|
||||
onHeadArrowChange(headRing)
|
||||
val headPolygon = Polygon.fromLngLats(listOf(headRing))
|
||||
val headFeature = Feature.fromGeometry(headPolygon)
|
||||
headFeature.addStringProperty("color", "#0000FF")
|
||||
headFeature.addStringProperty("type", "overall-trend")
|
||||
|
||||
val features = listOf(arrowFeature, headFeature)
|
||||
|
||||
// 设置图层
|
||||
setupTrendLayer(style, sourceId, layerId, features)
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateArrowData(
|
||||
grid: GridModel,
|
||||
angle: Angle,
|
||||
arrowScale: Double = 0.4
|
||||
): ArrowData {
|
||||
val centerX = (grid.minX + grid.maxX) / 2
|
||||
val centerY = (grid.minY + grid.maxY) / 2
|
||||
|
||||
val regionWidth = grid.maxX - grid.minX
|
||||
val regionHeight = grid.maxY - grid.minY
|
||||
val arrowLength = min(regionWidth, regionHeight) * arrowScale * 1.0
|
||||
|
||||
val arrowDirectionRad = angle.radians
|
||||
val endX = centerX + sin(arrowDirectionRad) * arrowLength
|
||||
val endY = centerY + cos(arrowDirectionRad) * arrowLength
|
||||
|
||||
val arrowLine = listOf(
|
||||
Vector2D(centerX, centerY),
|
||||
Vector2D(endX, endY)
|
||||
)
|
||||
|
||||
// 创建箭头头部
|
||||
val headSize = arrowLength * 0.2
|
||||
val leftRad = arrowDirectionRad + Math.PI * 0.8
|
||||
val rightRad = arrowDirectionRad - Math.PI * 0.8
|
||||
|
||||
val leftX = endX + sin(leftRad) * headSize
|
||||
val leftY = endY + cos(leftRad) * headSize
|
||||
val rightX = endX + sin(rightRad) * headSize
|
||||
val rightY = endY + cos(rightRad) * headSize
|
||||
|
||||
val headRing = listOf(
|
||||
Vector2D(endX, endY),
|
||||
Vector2D(leftX, leftY),
|
||||
Vector2D(rightX, rightY),
|
||||
Vector2D(endX, endY)
|
||||
)
|
||||
return ArrowData(
|
||||
arrowLine = arrowLine,
|
||||
headRing = headRing
|
||||
)
|
||||
}
|
||||
|
||||
data class ArrowData(
|
||||
val arrowLine: List<Vector2D>,
|
||||
val headRing: List<Vector2D>
|
||||
)
|
||||
|
||||
fun MapView.displayControllableArrow(
|
||||
sourceId: String = "controllable-source-id-0",
|
||||
layerId: String = "controllable-layer-id-0",
|
||||
arrowData: ArrowData
|
||||
) {
|
||||
mapboxMap.getStyle { style ->
|
||||
val (arrowLine, headRing) = arrowData
|
||||
val arrowFeature = Feature.fromGeometry(LineString.fromLngLats(arrowLine.map { it.toMapboxPoint() }))
|
||||
arrowFeature.addStringProperty("color", "#0000FF")
|
||||
arrowFeature.addStringProperty("type", "overall-trend")
|
||||
|
||||
val headPolygon = Polygon.fromLngLats(listOf(headRing.map { it.toMapboxPoint() }))
|
||||
val headFeature = Feature.fromGeometry(headPolygon)
|
||||
headFeature.addStringProperty("color", "#0000FF")
|
||||
headFeature.addStringProperty("type", "overall-trend")
|
||||
|
||||
val features = listOf(arrowFeature, headFeature)
|
||||
|
||||
// 设置图层
|
||||
setupTrendLayer(style, sourceId, layerId, features)
|
||||
}
|
||||
}
|
||||
144
android/src/main/java/com/icegps/orx/DisplaySlopeResult.kt
Normal file
144
android/src/main/java/com/icegps/orx/DisplaySlopeResult.kt
Normal file
@@ -0,0 +1,144 @@
|
||||
package com.icegps.orx
|
||||
|
||||
import android.util.Log
|
||||
import com.icegps.math.geometry.Vector2D
|
||||
import com.icegps.orx.ktx.toMapboxPoint
|
||||
import com.mapbox.geojson.Feature
|
||||
import com.mapbox.geojson.FeatureCollection
|
||||
import com.mapbox.geojson.Polygon
|
||||
import com.mapbox.maps.MapView
|
||||
import com.mapbox.maps.Style
|
||||
import com.mapbox.maps.extension.style.expressions.generated.Expression
|
||||
import com.mapbox.maps.extension.style.layers.addLayer
|
||||
import com.mapbox.maps.extension.style.layers.generated.FillLayer
|
||||
import com.mapbox.maps.extension.style.layers.generated.LineLayer
|
||||
import com.mapbox.maps.extension.style.sources.addSource
|
||||
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
|
||||
|
||||
/**
|
||||
* @author tabidachinokaze
|
||||
* @date 2025/11/26
|
||||
*/
|
||||
/**
|
||||
* 绘制斜坡设计结果
|
||||
*/
|
||||
fun MapView.displaySlopeResult(
|
||||
originalGrid: GridModel,
|
||||
slopeResult: SlopeResult,
|
||||
sourceId: String = "slope-result",
|
||||
layerId: String = "slope-layer",
|
||||
palette: (Double?) -> String,
|
||||
showDesignHeight: Boolean
|
||||
) {
|
||||
val elevationList = mutableListOf<Double>()
|
||||
mapboxMap.getStyle { style ->
|
||||
val features = mutableListOf<Feature>()
|
||||
val designGrid = slopeResult.designSurface
|
||||
|
||||
// 对比测试,将绘制到原来图形的左边
|
||||
// val minX = originalGrid.minX * 2 - originalGrid.maxX
|
||||
val minX = originalGrid.minX
|
||||
val maxY = originalGrid.maxY
|
||||
|
||||
val cellSize = originalGrid.cellSize
|
||||
|
||||
for (r in 0 until originalGrid.rows) {
|
||||
for (c in 0 until originalGrid.cols) {
|
||||
val originalElev = originalGrid.getValue(r, c) ?: continue
|
||||
val designElev = designGrid.getValue(r, c) ?: continue
|
||||
elevationList.add(designElev)
|
||||
|
||||
// 计算填挖高度
|
||||
val heightDiff = designElev - originalElev
|
||||
|
||||
// 计算栅格边界
|
||||
val x0 = minX + c * cellSize
|
||||
val y0 = maxY - r * cellSize
|
||||
val x1 = x0 + cellSize
|
||||
val y1 = y0 - cellSize
|
||||
|
||||
// 1. 创建多边形要素(背景色)
|
||||
val ring = listOf(
|
||||
Vector2D(x0, y0),
|
||||
Vector2D(x1, y0),
|
||||
Vector2D(x1, y1),
|
||||
Vector2D(x0, y1),
|
||||
Vector2D(x0, y0)
|
||||
).map { it.toMapboxPoint() }
|
||||
val poly = Polygon.fromLngLats(listOf(ring))
|
||||
val feature = Feature.fromGeometry(poly)
|
||||
|
||||
if (showDesignHeight) {
|
||||
// 显示设计高度,测试坡向是否正确,和高度是否计算正确
|
||||
feature.addStringProperty("color", palette(designElev))
|
||||
} else {
|
||||
// 显示高差
|
||||
feature.addStringProperty("color", palette(heightDiff))
|
||||
}
|
||||
// 显示原始高度
|
||||
// feature.addStringProperty("color", palette(originalElev))
|
||||
features.add(feature)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("displayGridWithDirectionArrows", "对比区域的土方量计算: ${elevationList.sum()}, 平均值:${elevationList.average()}")
|
||||
|
||||
// 设置图层
|
||||
setupEarthworkLayer(style, sourceId, layerId, features)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整的土方工程图层设置 - 修正版
|
||||
*/
|
||||
private fun setupEarthworkLayer(
|
||||
style: Style,
|
||||
sourceId: String,
|
||||
layerId: String,
|
||||
features: List<Feature>,
|
||||
) {
|
||||
// 创建数据源
|
||||
val source = geoJsonSource(sourceId) {
|
||||
featureCollection(FeatureCollection.fromFeatures(features))
|
||||
}
|
||||
|
||||
// 清理旧图层
|
||||
try {
|
||||
style.removeStyleLayer(layerId)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
try {
|
||||
style.removeStyleLayer("$layerId-arrow")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
try {
|
||||
style.removeStyleLayer("$layerId-outline")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
try {
|
||||
style.removeStyleLayer("$layerId-text")
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
if (style.styleSourceExists(sourceId)) {
|
||||
style.removeStyleSource(sourceId)
|
||||
}
|
||||
|
||||
// 添加数据源
|
||||
style.addSource(source)
|
||||
|
||||
// 主填充图层
|
||||
val fillLayer = FillLayer(layerId, sourceId).apply {
|
||||
fillColor(Expression.toColor(Expression.get("color")))
|
||||
fillOpacity(0.7)
|
||||
}
|
||||
style.addLayer(fillLayer)
|
||||
|
||||
// 边框图层
|
||||
val outlineLayer = LineLayer("$layerId-outline", sourceId).apply {
|
||||
lineColor("#333333")
|
||||
lineWidth(1.0)
|
||||
lineOpacity(0.5)
|
||||
}
|
||||
style.addLayer(outlineLayer)
|
||||
}
|
||||
438
android/src/main/java/com/icegps/orx/EarthworkManager.kt
Normal file
438
android/src/main/java/com/icegps/orx/EarthworkManager.kt
Normal file
@@ -0,0 +1,438 @@
|
||||
package com.icegps.orx
|
||||
|
||||
import android.graphics.PointF
|
||||
import android.util.Log
|
||||
import com.icegps.common.helper.GeoHelper
|
||||
import com.icegps.math.geometry.Angle
|
||||
import com.icegps.math.geometry.Vector2D
|
||||
import com.icegps.math.geometry.degrees
|
||||
import com.icegps.shared.ktx.TAG
|
||||
import com.mapbox.android.gestures.MoveGestureDetector
|
||||
import com.mapbox.geojson.Point
|
||||
import com.mapbox.maps.MapView
|
||||
import com.mapbox.maps.ScreenCoordinate
|
||||
import com.mapbox.maps.plugin.gestures.OnMoveListener
|
||||
import com.mapbox.maps.plugin.gestures.addOnMoveListener
|
||||
import com.mapbox.maps.plugin.gestures.removeOnMoveListener
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* @author tabidachinokaze
|
||||
* @date 2025/11/26
|
||||
*/
|
||||
object SlopeCalculator {
|
||||
fun calculateSlope(
|
||||
grid: GridModel,
|
||||
slopeDirection: Double,
|
||||
slopePercentage: Double,
|
||||
baseHeightOffset: Double = 0.0
|
||||
): SlopeResult {
|
||||
val centerX = (grid.minX + grid.maxX) / 2
|
||||
val centerY = (grid.minY + grid.maxY) / 2
|
||||
|
||||
val elevations = grid.cells.filterNotNull()
|
||||
val baseElevation = elevations.average() + baseHeightOffset
|
||||
|
||||
val basePoint = Triple(centerX, centerY, baseElevation)
|
||||
|
||||
val earthworkResult = EarthworkCalculator.calculateForSlopeDesign(
|
||||
grid = grid,
|
||||
basePoint = basePoint,
|
||||
slope = slopePercentage,
|
||||
aspect = slopeDirection
|
||||
)
|
||||
|
||||
return SlopeResult(
|
||||
slopeDirection = slopeDirection,
|
||||
slopePercentage = slopePercentage,
|
||||
baseHeightOffset = baseHeightOffset,
|
||||
baseElevation = baseElevation,
|
||||
earthworkResult = earthworkResult,
|
||||
designSurface = generateSlopeDesignGrid(
|
||||
grid = grid,
|
||||
basePoint = basePoint,
|
||||
slopePercentage = slopePercentage,
|
||||
slopeDirection = slopeDirection
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成斜坡设计面网格(用于可视化)
|
||||
*/
|
||||
private fun generateSlopeDesignGrid(
|
||||
grid: GridModel,
|
||||
basePoint: Triple<Double, Double, Double>,
|
||||
slopePercentage: Double,
|
||||
slopeDirection: Double
|
||||
): GridModel {
|
||||
val designCells = Array<Double?>(grid.rows * grid.cols) { null }
|
||||
val (baseX, baseY, baseElev) = basePoint
|
||||
val slopeRatio = slopePercentage / 100.0
|
||||
|
||||
for (r in 0 until grid.rows) {
|
||||
for (c in 0 until grid.cols) {
|
||||
if (grid.getValue(r, c) != null) {
|
||||
val cellX = grid.minX + (c + 0.5) * (grid.maxX - grid.minX) / grid.cols
|
||||
val cellY = grid.minY + (r + 0.5) * (grid.maxY - grid.minY) / grid.rows
|
||||
|
||||
val designElev = calculateSlopeElevation(
|
||||
pointX = cellX,
|
||||
pointY = cellY,
|
||||
baseX = baseX,
|
||||
baseY = baseY,
|
||||
baseElev = baseElev,
|
||||
slopeRatio = slopeRatio,
|
||||
slopeDirection = slopeDirection
|
||||
)
|
||||
designCells[r * grid.cols + c] = designElev
|
||||
}
|
||||
}
|
||||
}
|
||||
return GridModel(
|
||||
minX = grid.minX,
|
||||
maxX = grid.maxX,
|
||||
minY = grid.minY,
|
||||
maxY = grid.maxY,
|
||||
rows = grid.rows,
|
||||
cols = grid.cols,
|
||||
cellSize = grid.cellSize,
|
||||
cells = designCells
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 斜坡高程计算
|
||||
*/
|
||||
fun calculateSlopeElevation(
|
||||
pointX: Double,
|
||||
pointY: Double,
|
||||
baseX: Double,
|
||||
baseY: Double,
|
||||
baseElev: Double,
|
||||
slopeRatio: Double,
|
||||
slopeDirection: Double
|
||||
): Double {
|
||||
val dx = (pointX - baseX) * cos(Math.toRadians(baseY))
|
||||
val dy = (pointY - baseY)
|
||||
|
||||
val slopeRad = (slopeDirection.degrees - 90.degrees).normalized.radians
|
||||
|
||||
val projection = dx * cos(slopeRad) + dy * sin(slopeRad)
|
||||
val heightDiff = projection * slopeRatio
|
||||
|
||||
return baseElev + heightDiff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 斜面设计
|
||||
*
|
||||
* @property slopeDirection 坡向 (度)
|
||||
* @property slopePercentage 坡度 (%)
|
||||
* @property baseHeightOffset 基准面高度偏移 (m)
|
||||
* @property baseElevation 基准点高程 (m)
|
||||
* @property earthworkResult 土方量结果
|
||||
* @property designSurface 设计面网格(用于可视化)
|
||||
*/
|
||||
data class SlopeResult(
|
||||
val slopeDirection: Double,
|
||||
val slopePercentage: Double,
|
||||
val baseHeightOffset: Double,
|
||||
val baseElevation: Double,
|
||||
val earthworkResult: EarthworkResult,
|
||||
val designSurface: GridModel
|
||||
)
|
||||
|
||||
object EarthworkCalculator {
|
||||
/**
|
||||
* @param grid 栅格网模型
|
||||
* @param designElevation 设计高程
|
||||
*/
|
||||
fun calculateForFlatDesign(
|
||||
grid: GridModel,
|
||||
designElevation: Double
|
||||
): EarthworkResult {
|
||||
var cutVolume = 0.0
|
||||
var fillVolume = 0.0
|
||||
var cutArea = 0.0
|
||||
var fillArea = 0.0
|
||||
val cellArea = grid.cellSize * grid.cellSize
|
||||
|
||||
for (r in 0 until grid.rows) {
|
||||
for (c in 0 until grid.cols) {
|
||||
val originalElev = grid.getValue(r, c) ?: continue
|
||||
|
||||
val heightDiff = designElevation - originalElev
|
||||
|
||||
val volume = heightDiff * cellArea
|
||||
|
||||
if (volume > 0) {
|
||||
fillVolume += volume
|
||||
fillArea += cellArea
|
||||
} else if (volume < 0) {
|
||||
cutVolume += abs(volume)
|
||||
cutArea += cellArea
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EarthworkResult(
|
||||
cutVolume = cutVolume,
|
||||
fillVolume = fillVolume,
|
||||
netVolume = fillVolume - cutVolume,
|
||||
cutArea = cutArea,
|
||||
fillArea = fillArea,
|
||||
totalArea = cutArea + fillArea
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算斜面设计的土方量
|
||||
*/
|
||||
fun calculateForSlopeDesign(
|
||||
grid: GridModel,
|
||||
basePoint: Triple<Double, Double, Double>,
|
||||
slope: Double,
|
||||
aspect: Double
|
||||
): EarthworkResult {
|
||||
var cutVolume = 0.0
|
||||
var fillVolume = 0.0
|
||||
var cutArea = 0.0
|
||||
var fillArea = 0.0
|
||||
val cellArea = grid.cellSize * grid.cellSize
|
||||
|
||||
val (baseX, baseY, baseElev) = basePoint
|
||||
val slopeRatio = slope / 100.0
|
||||
|
||||
for (r in 0 until grid.rows) {
|
||||
for (c in 0 until grid.cols) {
|
||||
val originalElev = grid.getValue(r, c) ?: continue
|
||||
|
||||
val cellX = grid.minX + (c + 0.5) * (grid.maxX - grid.minX) / grid.cols
|
||||
val cellY = grid.minY + (r + 0.5) * (grid.maxY - grid.minY) / grid.rows
|
||||
|
||||
val designElev = SlopeCalculator.calculateSlopeElevation(
|
||||
pointX = cellX,
|
||||
pointY = cellY,
|
||||
baseX = baseX,
|
||||
baseY = baseY,
|
||||
baseElev = baseElev,
|
||||
slopeRatio = slopeRatio,
|
||||
slopeDirection = aspect
|
||||
)
|
||||
|
||||
val heightElev = designElev - originalElev
|
||||
val volume = heightElev * cellArea
|
||||
|
||||
if (volume > 0) {
|
||||
fillVolume += volume
|
||||
fillArea += cellArea
|
||||
} else if (volume < 0) {
|
||||
cutVolume += abs(volume)
|
||||
cutArea += cellArea
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EarthworkResult(
|
||||
cutVolume = cutVolume,
|
||||
fillVolume = fillVolume,
|
||||
netVolume = fillVolume - cutVolume,
|
||||
cutArea = cutArea,
|
||||
fillArea = fillArea,
|
||||
totalArea = cutArea + fillArea
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 土方量计算结果
|
||||
* @property cutVolume 挖方量 (m³)
|
||||
* @property fillVolume 填方量 (m³)
|
||||
* @property netVolume 净土方量 (m³)
|
||||
* @property cutArea 挖方面积 (m²)
|
||||
* @property fillArea 填方面积 (m²)
|
||||
* @property totalArea 总面积 (m²)
|
||||
*/
|
||||
data class EarthworkResult(
|
||||
val cutVolume: Double,
|
||||
val fillVolume: Double,
|
||||
val netVolume: Double,
|
||||
val cutArea: Double,
|
||||
val fillArea: Double,
|
||||
val totalArea: Double
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return buildString {
|
||||
appendLine("EarthworkResult")
|
||||
appendLine("挖方: ${"%.1f".format(cutVolume)} m³")
|
||||
appendLine("填方: ${"%.1f".format(fillVolume)} m³")
|
||||
appendLine("净土方: ${"%.1f".format(netVolume)} m³")
|
||||
appendLine("挖方面积: ${"%.1f".format(cutArea)} m²")
|
||||
appendLine("填方面积: ${"%.1f".format(fillArea)} m²")
|
||||
appendLine("总面积:${"%.1f".format(totalArea)} m²")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EarthworkManager(
|
||||
private val mapView: MapView,
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
private val arrowSourceId: String = "controllable-source-id-0"
|
||||
private val arrowLayerId: String = "controllable-layer-id-0"
|
||||
private var listener: OnMoveListener? = null
|
||||
|
||||
private var gridModel = MutableStateFlow<GridModel?>(null)
|
||||
private val arrowHead = MutableStateFlow(emptyList<Vector2D>())
|
||||
private var arrowCenter = MutableStateFlow(Vector2D(0.0, 0.0))
|
||||
private var arrowEnd = MutableStateFlow(Vector2D(0.0, 1.0))
|
||||
private var _slopeDirection = MutableStateFlow(0.degrees)
|
||||
val slopeDirection = _slopeDirection.asStateFlow()
|
||||
private val _slopePercentage = MutableStateFlow(90.0)
|
||||
val slopePercentage = _slopePercentage.asStateFlow()
|
||||
private val _baseHeightOffset = MutableStateFlow(0.0)
|
||||
val baseHeightOffset = _baseHeightOffset.asStateFlow()
|
||||
|
||||
init {
|
||||
combine(
|
||||
arrowCenter,
|
||||
arrowEnd,
|
||||
gridModel
|
||||
) { center, arrow, gridModel ->
|
||||
gridModel?.let { gridModel ->
|
||||
// _slopeDirection.value = angle
|
||||
displayControllableArrow(gridModel, getSlopeDirection(arrow, center))
|
||||
}
|
||||
}.launchIn(scope)
|
||||
combine(
|
||||
_slopeDirection,
|
||||
gridModel
|
||||
) { slopeDirection, gridModel ->
|
||||
gridModel?.let {
|
||||
displayControllableArrow(it, slopeDirection)
|
||||
}
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
private fun getSlopeDirection(
|
||||
arrow: Vector2D,
|
||||
center: Vector2D
|
||||
): Angle {
|
||||
val direction = (arrow - center)
|
||||
val atan2 = Angle.atan2(direction.x, direction.y, Vector2D.UP)
|
||||
val angle = atan2.normalized
|
||||
return angle
|
||||
}
|
||||
|
||||
private fun displayControllableArrow(gridModel: GridModel, slopeDirection: Angle) {
|
||||
val arrowData = calculateArrowData(
|
||||
grid = gridModel,
|
||||
angle = slopeDirection,
|
||||
)
|
||||
arrowHead.value = arrowData.headRing
|
||||
mapView.displayControllableArrow(
|
||||
sourceId = arrowSourceId,
|
||||
layerId = arrowLayerId,
|
||||
arrowData = arrowData,
|
||||
)
|
||||
}
|
||||
|
||||
fun Point.toVector2D(): Vector2D {
|
||||
val geoHelper = GeoHelper.getSharedInstance()
|
||||
val enu = geoHelper.wgs84ToENU(lon = longitude(), lat = latitude(), hgt = 0.0)
|
||||
return Vector2D(enu.x, enu.y)
|
||||
}
|
||||
|
||||
fun removeOnMoveListener() {
|
||||
listener?.let(mapView.mapboxMap::removeOnMoveListener)
|
||||
listener = null
|
||||
}
|
||||
|
||||
fun setupOnMoveListener() {
|
||||
listener = object : OnMoveListener {
|
||||
private var beginning: Boolean = false
|
||||
private var isDragging: Boolean = false
|
||||
private fun getCoordinate(focalPoint: PointF): Point {
|
||||
return mapView.mapboxMap.coordinateForPixel(ScreenCoordinate(focalPoint.x.toDouble(), focalPoint.y.toDouble()))
|
||||
}
|
||||
|
||||
override fun onMove(detector: MoveGestureDetector): Boolean {
|
||||
val focalPoint = detector.focalPoint
|
||||
val point = mapView.mapboxMap
|
||||
.coordinateForPixel(ScreenCoordinate(focalPoint.x.toDouble(), focalPoint.y.toDouble()))
|
||||
.toVector2D()
|
||||
|
||||
val isPointInPolygon = RayCastingAlgorithm.isPointInPolygon(
|
||||
point = point,
|
||||
polygon = arrowHead.value
|
||||
)
|
||||
|
||||
if (isPointInPolygon) {
|
||||
isDragging = true
|
||||
}
|
||||
if (isDragging) {
|
||||
arrowEnd.value = point
|
||||
}
|
||||
return isDragging
|
||||
}
|
||||
|
||||
override fun onMoveBegin(detector: MoveGestureDetector) {
|
||||
Log.d(TAG, "onMoveBegin: $detector")
|
||||
beginning = true
|
||||
}
|
||||
|
||||
override fun onMoveEnd(detector: MoveGestureDetector) {
|
||||
Log.d(TAG, "onMoveEnd: $detector")
|
||||
val point = getCoordinate(detector.focalPoint)
|
||||
val arrow = point.toVector2D()
|
||||
if (beginning && isDragging) {
|
||||
arrowEnd.value = arrow
|
||||
val center = arrowCenter.value
|
||||
_slopeDirection.value = getSlopeDirection(arrow, center)
|
||||
}
|
||||
Log.d(
|
||||
TAG,
|
||||
buildString {
|
||||
appendLine("onMoveEnd: ")
|
||||
appendLine("${point.longitude()}, ${point.latitude()}")
|
||||
}
|
||||
)
|
||||
isDragging = false
|
||||
beginning = false
|
||||
}
|
||||
}.also(mapView.mapboxMap::addOnMoveListener)
|
||||
}
|
||||
|
||||
fun updateGridModel(gridModel: GridModel) {
|
||||
this.gridModel.value = gridModel
|
||||
calculateArrowCenter(gridModel)
|
||||
}
|
||||
|
||||
private fun calculateArrowCenter(gridModel: GridModel) {
|
||||
val centerX = (gridModel.minX + gridModel.maxX) / 2
|
||||
val centerY = (gridModel.minY + gridModel.maxY) / 2
|
||||
arrowCenter.value = Vector2D(centerX, centerY)
|
||||
}
|
||||
|
||||
fun updateSlopeDirection(angle: Angle) {
|
||||
_slopeDirection.value = angle
|
||||
}
|
||||
|
||||
fun updateSlopePercentage(value: Double) {
|
||||
_slopePercentage.value = value
|
||||
}
|
||||
|
||||
fun updateDesignHeight(value: Double) {
|
||||
_baseHeightOffset.value = value
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,14 @@ data class GridModel(
|
||||
val cols: Int,
|
||||
val cellSize: Double,
|
||||
val cells: Array<Double?>
|
||||
)
|
||||
) {
|
||||
fun getValue(row: Int, col: Int): Double? {
|
||||
if (row !in 0..<rows || col < 0 || col >= cols) {
|
||||
return null
|
||||
}
|
||||
return cells[row * cols + col]
|
||||
}
|
||||
}
|
||||
|
||||
fun triangulationToGrid(
|
||||
delaunator: DelaunayTriangulation3D,
|
||||
|
||||
@@ -10,12 +10,16 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.icegps.common.helper.GeoHelper
|
||||
import com.icegps.math.geometry.degrees
|
||||
import com.icegps.orx.databinding.ActivityMainBinding
|
||||
import com.icegps.shared.model.GeoPoint
|
||||
import com.mapbox.geojson.Point
|
||||
import com.mapbox.maps.CameraOptions
|
||||
import com.mapbox.maps.MapView
|
||||
import com.mapbox.maps.plugin.gestures.addOnMapClickListener
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@@ -26,6 +30,7 @@ class MainActivity : AppCompatActivity() {
|
||||
ViewModelProvider(this)[MainViewModel::class.java]
|
||||
}
|
||||
private lateinit var contoursManager: ContoursManager
|
||||
private lateinit var earthworkManager: EarthworkManager
|
||||
|
||||
init {
|
||||
initGeoHelper()
|
||||
@@ -36,6 +41,7 @@ class MainActivity : AppCompatActivity() {
|
||||
enableEdgeToEdge()
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
mapView = binding.mapView
|
||||
earthworkManager = EarthworkManager(mapView, lifecycleScope)
|
||||
setContentView(binding.root)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
@@ -52,27 +58,17 @@ class MainActivity : AppCompatActivity() {
|
||||
.build()
|
||||
)
|
||||
|
||||
val points = coordinateGenerate1()
|
||||
val points = coordinateGenerate()
|
||||
|
||||
val polygonTest = PolygonTest(mapView)
|
||||
polygonTest.clear()
|
||||
val innerPoints = points.map { it[0] }
|
||||
val outerPoints = points.map { it[1] }
|
||||
if (false) polygonTest.update(
|
||||
outer = outerPoints,
|
||||
inner = innerPoints,
|
||||
other = points.map { it[2] }
|
||||
)
|
||||
// divider
|
||||
contoursManager = ContoursManager(
|
||||
context = this,
|
||||
mapView = mapView,
|
||||
scope = lifecycleScope
|
||||
)
|
||||
val points2 = points.flatten()
|
||||
contoursManager.updateContourSize(6)
|
||||
contoursManager.updatePoints(points2)
|
||||
val height = points2.map { it.z }
|
||||
contoursManager.updatePoints(points)
|
||||
val height = points.map { it.z }
|
||||
val min = height.min()
|
||||
val max = height.max()
|
||||
contoursManager.updateHeightRange((min / 2)..max)
|
||||
@@ -125,6 +121,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
contoursManager.updateCellSize(slider.value.toDouble())
|
||||
contoursManager.refresh()
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -135,13 +132,78 @@ class MainActivity : AppCompatActivity() {
|
||||
binding.clearPoints.setOnClickListener {
|
||||
viewModel.clearPoints()
|
||||
}
|
||||
binding.slopeDirection.addOnSliderTouchListener(
|
||||
object : Slider.OnSliderTouchListener {
|
||||
override fun onStartTrackingTouch(slider: Slider) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
earthworkManager.updateSlopeDirection(slider.value.degrees)
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.slopePercentage.addOnSliderTouchListener(
|
||||
object : Slider.OnSliderTouchListener {
|
||||
override fun onStartTrackingTouch(slider: Slider) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
earthworkManager.updateSlopePercentage(slider.value.toDouble())
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.designHeight.addOnSliderTouchListener(
|
||||
object : Slider.OnSliderTouchListener {
|
||||
override fun onStartTrackingTouch(slider: Slider) {
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
earthworkManager.updateDesignHeight(slider.value.toDouble())
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.switchDesignSurface.setOnCheckedChangeListener { button, isChecked ->
|
||||
showDesignHeight.value = isChecked
|
||||
}
|
||||
earthworkManager.setupOnMoveListener()
|
||||
initData()
|
||||
}
|
||||
|
||||
private val showDesignHeight = MutableStateFlow(false)
|
||||
|
||||
private fun initData() {
|
||||
viewModel.points.onEach {
|
||||
contoursManager.updatePoints(it)
|
||||
contoursManager.updateHeightRange()
|
||||
contoursManager.refresh()
|
||||
}.launchIn(lifecycleScope)
|
||||
contoursManager.gridModel.filterNotNull().onEach {
|
||||
earthworkManager.updateGridModel(it)
|
||||
}.launchIn(lifecycleScope)
|
||||
earthworkManager.slopeDirection.onEach {
|
||||
binding.slopeDirection.value = it.degrees.toFloat()
|
||||
}.launchIn(lifecycleScope)
|
||||
combine(
|
||||
earthworkManager.slopeDirection,
|
||||
earthworkManager.slopePercentage,
|
||||
earthworkManager.baseHeightOffset,
|
||||
contoursManager.gridModel,
|
||||
showDesignHeight
|
||||
) { slopeDirection, slopePercentage, baseHeightOffset, gridModel, showDesignHeight ->
|
||||
gridModel?.let { gridModel ->
|
||||
val slopeResult: SlopeResult = SlopeCalculator.calculateSlope(
|
||||
grid = gridModel,
|
||||
slopeDirection = slopeDirection.degrees,
|
||||
slopePercentage = slopePercentage,
|
||||
baseHeightOffset = baseHeightOffset
|
||||
)
|
||||
mapView.displaySlopeResult(
|
||||
originalGrid = gridModel,
|
||||
slopeResult = slopeResult,
|
||||
palette = contoursManager.simplePalette::palette,
|
||||
showDesignHeight = showDesignHeight
|
||||
)
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
}
|
||||
|
||||
44
android/src/main/java/com/icegps/orx/RayCastingAlgorithm.kt
Normal file
44
android/src/main/java/com/icegps/orx/RayCastingAlgorithm.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.icegps.orx
|
||||
|
||||
import com.icegps.math.geometry.Vector2D
|
||||
import com.icegps.math.geometry.Vector3D
|
||||
import com.icegps.math.geometry.toVector2D
|
||||
|
||||
/**
|
||||
* @author tabidachinokaze
|
||||
* @date 2025/11/26
|
||||
*/
|
||||
object RayCastingAlgorithm {
|
||||
/**
|
||||
* 使用射线法判断点是否在多边形内
|
||||
* @param point 测试点
|
||||
* @param polygon 多边形顶点列表
|
||||
* @return true如果在多边形内
|
||||
*/
|
||||
fun isPointInPolygon(point: Vector2D, polygon: List<Vector2D>): Boolean {
|
||||
if (polygon.size < 3) return false
|
||||
|
||||
val x = point.x
|
||||
val y = point.y
|
||||
var inside = false
|
||||
|
||||
var j = polygon.size - 1
|
||||
for (i in polygon.indices) {
|
||||
val xi = polygon[i].x
|
||||
val yi = polygon[i].y
|
||||
val xj = polygon[j].x
|
||||
val yj = polygon[j].y
|
||||
|
||||
val intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)
|
||||
|
||||
if (intersect) inside = !inside
|
||||
j = i
|
||||
}
|
||||
|
||||
return inside
|
||||
}
|
||||
|
||||
fun isPointInPolygon(point: Vector3D, polygon: List<Vector3D>): Boolean {
|
||||
return isPointInPolygon(point.toVector2D(), polygon.map { it.toVector2D() })
|
||||
}
|
||||
}
|
||||
15
android/src/main/java/com/icegps/orx/ktx/Vector2D.kt
Normal file
15
android/src/main/java/com/icegps/orx/ktx/Vector2D.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.icegps.orx.ktx
|
||||
|
||||
import com.icegps.common.helper.GeoHelper
|
||||
import com.icegps.math.geometry.Vector2D
|
||||
import com.mapbox.geojson.Point
|
||||
|
||||
/**
|
||||
* @author tabidachinokaze
|
||||
* @date 2025/11/26
|
||||
*/
|
||||
fun Vector2D.toMapboxPoint(): Point {
|
||||
val geoHelper = GeoHelper.getSharedInstance()
|
||||
val wgs84 = geoHelper.enuToWGS84Object(GeoHelper.ENU(x = x, y = y))
|
||||
return Point.fromLngLat(wgs84.lon, wgs84.lat)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.openrndr.shape.path3D
|
||||
* Kotlin/OPENRNDR idiomatic interface to `Delaunay`
|
||||
*/
|
||||
class DelaunayTriangulation3D(val points: List<Vector3>) {
|
||||
val delaunay: Delaunay = Delaunay.Companion.from(points.map { it.xy })
|
||||
val delaunay: Delaunay = Delaunay.from(points.map { it.xy })
|
||||
|
||||
fun neighbors(pointIndex: Int): Sequence<Int> {
|
||||
return delaunay.neighbors(pointIndex)
|
||||
|
||||
@@ -112,6 +112,72 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="清除所有点" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="坡向(角度):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slope_direction"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="360" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="坡度(%):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slope_percentage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="设计面高度(m):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/design_height"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:value="0"
|
||||
android:valueFrom="-100"
|
||||
android:valueTo="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switch_design_surface"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:switchPadding="16dp"
|
||||
android:text="显示设计面" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -103,6 +103,72 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="清除所有点" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="坡向(角度):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slope_direction"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="360" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="坡度(%):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slope_percentage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="设计面高度(m):" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/design_height"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:value="0"
|
||||
android:valueFrom="-100"
|
||||
android:valueTo="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switch_design_surface"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:switchPadding="16dp"
|
||||
android:text="显示设计面" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.mapbox.maps.MapView
|
||||
|
||||
Reference in New Issue
Block a user