Commit 73eed249 authored by p x's avatar p x
Browse files

高德车辆移动

parent a3f59a6e
......@@ -46,11 +46,6 @@
android:name=".ui.ControlActivity"
android:exported="false" />
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<service android:name="com.amap.api.location.APSService" />
<provider
android:name="androidx.core.content.FileProvider"
......
......@@ -137,7 +137,7 @@ class MainActivity : AppCompatActivity() {
*
* @param type MINE=四维 AMAP=高德
*/
fun initMap(type: MAP_TYPE = MAP_TYPE.MINE) {
fun initMap(type: MAP_TYPE = MAP_TYPE.AMAP) {
var url = "http://192.168.60.73:9999/tiles/{z}/{x}/{y}.png?layer=yizhuang:yizhuang_avp"
// var url = "http://192.168.60.73:9999/tiles/{z}/{x}/{y}.png?"
// var url = "http://192.168.59.216:8080/smartmap/yizhuang/wms?service=WMS&version=1.1.0&request=GetMap&layers=yizhuang:yizhuang_avp&bbox=116.49796295166,39.8062019348145,116.505592346191,39.8120422363281&width=256&height=256&srs=EPSG:3857&styles=&format=image/png"
......
......@@ -8,13 +8,11 @@ import com.sd.api.maps.MSCalcuMapUtil
import com.sd.api.maps.MethodAdv
import com.sd.api.maps.cdata.MSLatLng
import com.sd.api.scenario.CucsVehicle
import com.sd.api.scenario.VehicleModel
import com.sd.api.scenario.bean.VehiclePos
import com.sd.api.ui.MapMultiView.OnMapReadyLis
import com.sd.api.ui.MapReadyView
import com.sd.demo.bean.mock.MRoutes
import com.sd.demo.databinding.ActivityOnLineMapBinding
import com.sd.demo.ui.PartRoadActivity
import com.sd.demo.utils.FileIoUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
......@@ -38,15 +36,25 @@ class OnLineMapActivity : AppCompatActivity() {
this@OnLineMapActivity.mapReadView = mapReadyView
}
})
setListener()
}
private fun setListener() {
binding.bt1.setOnClickListener {
// //移动网联车
//移动网联车
mockFzLine()
}
binding.bt2.setOnClickListener {
// 停止移动
CucsVehicle.stopCar()
}
binding.bt3.setOnClickListener {
// 删除小车
CucsVehicle.deleteCarModel()
}
binding.bt4.setOnClickListener {
// 恢复移动
CucsVehicle.resumeCarMove()
}
}
......@@ -68,6 +76,7 @@ class OnLineMapActivity : AppCompatActivity() {
MSLatLng(p1[1], p1[0]),
MSLatLng(pc[1], pc[0])
)
//车辆位置数据
VehiclePos.instance.apply {
lat = pc[1]
lng = pc[0]
......@@ -75,10 +84,11 @@ class OnLineMapActivity : AppCompatActivity() {
heading = head
}
}
//移动车辆
CucsVehicle.moveCar(VehiclePos.instance, 3)
MethodAdv.setMapCenter(VehiclePos.instance.lat, VehiclePos.instance.lng)
// MethodAdv.setMapCenter(VehiclePos.instance.lat, VehiclePos.instance.lng)
oldHead = head
delay(600)
delay(300)
}
}
} catch (e: CancellationException) {
......
......@@ -129,7 +129,7 @@ class TDriveRouteActivity : AppCompatActivity(), OnNaviPresenterListener {
this
)
}
//模拟导航
binding.simnai.setOnClickListener {
// 起点坐标
val startPoint = MSLatLng(39.80715003487552, 116.49872416608724)
......@@ -140,6 +140,10 @@ class TDriveRouteActivity : AppCompatActivity(), OnNaviPresenterListener {
this
)
}
//退出导航
binding.stopnai.setOnClickListener {
MSNavi.endNavi()
}
}
/**
......
......@@ -49,17 +49,14 @@ class WmsActivity : AppCompatActivity() {
this@WmsActivity.mapReadView = mapReadyView
// SDKInitializer.setStyleUrl(MineMap.UrlType.satellite, url)
WmsLayer.loadWmsLayer(aMapurl)
//改变地图中心点,第二个参数是纬度,第三个参数是经度
// MethodAdv.setMapCenter(mapReadView, 39.80913878, 116.50166926)
//改变地图中心点
MethodAdv.setMapCenter(39.80913878, 116.50166926)
}
})
// initMap()
binding.bt1.setOnClickListener {
MethodAdv.setMapCenter(39.80913878, 116.50166926)
binding.bt2.setOnClickListener {
//删除wms图层
WmsLayer.removeWmsLayer()
}
}
fun initMap() {
......
......@@ -13,6 +13,24 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="车辆移动" />
<Button
android:id="@+id/bt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止移动" />
<Button
android:id="@+id/bt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除小车" />
<Button
android:id="@+id/bt4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="恢复移动" />
</LinearLayout>
<com.sd.api.ui.MapMultiView
......
......@@ -26,12 +26,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始导航" />
<Button
android:id="@+id/simnai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟导航" />
<Button
android:id="@+id/stopnai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="退出导航" />
</LinearLayout>
......
......@@ -8,11 +8,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- <Button-->
<!-- android:id="@+id/bt1"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="加载"/>-->
<Button
android:id="@+id/bt1"
android:id="@+id/bt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载"/>
android:text="删除wms图层"/>
</LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
<!-- <domain-config cleartextTrafficPermitted="true">-->
<!-- <domain includeSubdomains="true">minedata.cn</domain>-->
<!-- </domain-config>-->
</network-security-config>
......@@ -17,9 +17,9 @@ android {
namespace = "com.sd.api"
compileSdk = 35
defaultConfig {
minSdk = 29
version = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
......@@ -45,6 +45,17 @@ android {
// dataBinding = true
viewBinding = true
}
android.libraryVariants.all {
outputs.all {
if (this is com.android.build.gradle.internal.api.LibraryVariantOutputImpl) {
// val config = project.android.defaultConfig
// val versionName = config.versionName
// val formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd_HHmm")
// val createTime = LocalDateTime.now().format(formatter)
outputFileName = "mapapi_${version}.aar"
}
}
}
}
......
......@@ -12,6 +12,12 @@
<application>
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
<service android:name="com.amap.api.location.APSService" />
<activity
android:name=".ui.MineNaiDirActivity"
android:exported="false"
......
......@@ -15,7 +15,7 @@ import com.sd.api.ui.MapReadyView
*/
object MSNavi {
/**是否开启导航**/
var mStartNav = false
var isStartNai = false
fun init() {
when (MSDKInitializer.getMapType()) {
......@@ -32,11 +32,11 @@ object MSNavi {
/**
* 开始导航
* @param nType 1=自己实现回调 2=直接调用导航组件
* @param starPoint
* @param startName
* @param starPoint 起点坐标
* @param startName 起点名称
* @param endPoint
* @param endName
* @param ways
* @param ways 途经点列表
*/
fun startNavi(
mapReadView: MapReadyView?,
......@@ -48,6 +48,7 @@ object MSNavi {
ways: List<WayPoi>,
onNaviPresenterListener: OnNaviPresenterListener?
) {
isStartNai = true
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
if (nType == 1) {
......@@ -67,11 +68,12 @@ object MSNavi {
}
}
/**结束导航**/
/**结束导航(模拟导航)**/
fun endNavi() {
isStartNai = false
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
MineNai.stopNavi()
}
MAP_TYPE.AMAP -> {
......@@ -87,6 +89,7 @@ object MSNavi {
driverRouteBean: DriverRouteBean?,
onNaviPresenterListener: OnNaviPresenterListener?
) {
isStartNai = true
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
MineNai.startSimNavi(
......
......@@ -11,7 +11,7 @@ import com.sd.api.maps.mine.MineMethodAdv
*
* @constructor Create empty Method adv pro
*/
object MethodAdv : MsOperationParent(){
object MethodAdv : MsOperationParent() {
/**
* 改变地图中心点
......@@ -22,7 +22,10 @@ object MethodAdv : MsOperationParent(){
fun setMapCenter(
lat: Double,
lng: Double
){
) {
if (lat == 0.0 || lng == 0.0) {
return
}
var mapReadView = getMapReadView()
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
......@@ -41,7 +44,7 @@ object MethodAdv : MsOperationParent(){
* @param mapReadView 地图准备就绪的视图对象
* @param zoom 缩放级别,值越小站得越高,默认值为11f
* */
fun setMapZoom(zoom: Float = 11f){
fun setMapZoom(zoom: Float = 11f) {
var mapReadView = getMapReadView()
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
......
......@@ -4,16 +4,13 @@ import com.amap.api.maps.model.TileOverlay
import com.amap.api.maps.model.TileOverlayOptions
import com.sd.api.MAP_TYPE
import com.sd.api.MSDKInitializer
import com.sd.api.location.MineGpsLocation
import com.sd.api.maps.amap.AmapGpsLocation
import com.sd.api.maps.amap.HeritageScopeTileProvider
import com.sd.api.ui.MapReadyView
/**
* 加载Wms地图
*/
object WmsLayer : MsOperationParent() {
//高德图层
private var wmsOverlay: TileOverlay? = null
/**
......@@ -36,7 +33,17 @@ object WmsLayer : MsOperationParent() {
}
/**删除wms图层***/
fun removeWmsLayer() {
wmsOverlay?.remove()
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
}
MAP_TYPE.AMAP -> {
wmsOverlay?.remove()
}
}
}
}
\ No newline at end of file
......@@ -134,7 +134,14 @@ object MineNai {
}
fun stopNavi() {
mNaviSession?.removeRoute()
var mRouteBase = MineRoutePlans.mRouteBase
if (mRouteBase != null) {
if (mNaviSession?.isInSimulation() == true) {
// mNaviSession!!.pauseSimulation()
mNaviSession?.endSimulation()
}
mNaviSession?.removeRoute()
}
}
......
......@@ -188,7 +188,6 @@ object MsParkRoad : MsOperationParent() {
}
}
/**更新参与者***/
fun upDatePreTarget(prelist: List<PtcBean>) {
when (MSDKInitializer.getMapType()) {
......@@ -232,7 +231,7 @@ object MsParkRoad : MsOperationParent() {
}
/**预加载主车
* @param fileName 模型文件路径
* @param fileName 模型文件路径 glb格式
*/
fun preloadMainCar(assets: AssetManager, fileName: String) {
when (MSDKInitializer.getMapType()) {
......@@ -249,7 +248,7 @@ object MsParkRoad : MsOperationParent() {
/**
* 预加载感知物
* @param fileName 模型文件路径
* @param fileName 模型文件路径 glb格式
*/
fun preloadParticipant(assets: AssetManager, fileName: String, ptcType: PartType) {
when (MSDKInitializer.getMapType()) {
......
package com.sd.api.scenario
import com.minedata.minenavi.mapdal.LatLng
import com.sd.api.MAP_TYPE
import com.sd.api.MSDKInitializer
import com.sd.api.highmap.CarNavPath
import com.sd.api.highmap.HighMap
import com.sd.api.highmap.HighPos
import com.sd.api.parkroad.MsParkRoad
import com.sd.api.parkroad.RoadPos
import com.sd.api.maps.MSNavi
import com.sd.api.scenario.bean.VehiclePos
import com.sd.api.utils.SmoothMoveUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.sd.api.scenario.mine.AmapVehMove
import com.sd.api.scenario.mine.MineVehMove
import com.sd.api.scenario.mine.MineVehicleModel
/**车辆管理类***/
object CucsVehicle {
......@@ -20,134 +16,69 @@ object CucsVehicle {
//控制车辆是否暂停移动
private var stopCar = false
//动画是否开始
private var isAniStart = false
//前一次位置
private var fromLoc: VehiclePos? = null
//当前位置
private var cvPos: VehiclePos? = null
//保存上一次位置的时间
private var oldTime = 0L
//2次定位的时间差,默认1000毫秒
private var vehTimeDiff = 1000L
/**
* 移动车辆
* @param vPos 车辆位置
* @param moveType 1=高精地图 2= 局部地图 3=在线地图
*/
fun moveCar(vPos: VehiclePos, moveType: Int) {
cvPos = vPos
if (stopCar)
return
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
mineCarMove(vPos, moveType)
MineVehMove.mineCarMove(vPos, moveType)
}
MAP_TYPE.AMAP -> {
AmapVehMove.aMapCarMove(vPos, moveType)
}
}
}
//四维实现
private fun mineCarMove(vPos: VehiclePos, moveType: Int) {
if (moveType == 1) {
var highPos = HighPos.instance.apply {
heading = vPos.heading
lat = vPos.lat
lon = vPos.lng
evel = vPos.evel
}
HighMap.setCarPosition(highPos)
} else if (moveType == 2) {
var roadPos = RoadPos.instance.apply {
lat = vPos.lat
lng = vPos.lng
bearing = vPos.heading.toFloat()
}
MsParkRoad.updateMainCar(roadPos, null)
} else if (moveType == 3) {
if (fromLoc == null || fromLoc?.lng == 0.0) {
setFromMyLoc(vPos)//设置起始位置
}
//计算2次定位时间差
calTimeDiff()
if (isAniStart == false) {
isAniStart = true
SmoothMoveUtils.startSmoothMove(
LatLng(fromLoc!!.lat, fromLoc!!.lng),
LatLng(vPos.lat, vPos.lng),
vehTimeDiff,
sCarSmooth
)
/**停止车辆移动*/
fun stopCar() {
stopCar = true
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
}
}
}
//在线地图主车平滑移动
private var sCarSmooth = object : SmoothMoveUtils.OnPositionUpdateListener {
override fun onUpdate(
iLatLng: LatLng,
bearing: Float
) {
//在线地图主车平滑移动
VehicleModel.upMyLocCarModel(
iLatLng.latitude,
iLatLng.longitude,
cvPos?.heading?.toFloat() ?: 0f
)
}
override fun onFinish() {
// println("--------------动画完成")
setFromMyLoc(cvPos!!)
isAniStart = false
MAP_TYPE.AMAP -> {
AmapVehMove.stopMove()
}
}
}
//计算2次拿到车辆定位的时间差
fun calTimeDiff() {
CoroutineScope(Dispatchers.Default).launch {
if (oldTime != 0L) {
vehTimeDiff = System.currentTimeMillis() - oldTime
// println("-------定位时间差 = ${vehTimeDiff}")
/**恢复车辆移动*/
fun resumeCarMove() {
stopCar = false
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
MineVehicleModel.loadAddCar = false
}
oldTime = System.currentTimeMillis()
}
}
//设置前一个点
private fun setFromMyLoc(myloc: VehiclePos) {
if (fromLoc == null) {
fromLoc = VehiclePos()
}
fromLoc!!.run {
this.lat = myloc.lat
this.lng = myloc.lng
this.heading = myloc.heading
this.evel = myloc.evel
MAP_TYPE.AMAP -> {
}
}
}
/**停止车辆移动*/
fun stopCar() {
/**删除在线地图车辆***/
fun deleteCarModel() {
stopCar = true
}
when (MSDKInitializer.getMapType()) {
MAP_TYPE.MINE -> {
VehicleModel.deleteMyLocModel()
// stopCar = false
}
fun resumeCarMove() {
stopCar = false
MAP_TYPE.AMAP -> {
AmapVehMove.deleteCarModel()
}
}
}
/**
* 绘制车辆移动路径
* 设置车辆移动路径
* @param carNavPath
*/
fun setCarNaiPath(carNavPath: CarNavPath) {
......@@ -162,4 +93,5 @@ object CucsVehicle {
HighMap.setCarNavPath(carNavPath)
}
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ import com.sd.api.MSDKInitializer
import com.sd.api.scenario.mine.MineVehicleModel
/**网联车模型(在线地图)**/
object VehicleModel {
internal object VehicleModel {
/****更新模型位置**/
fun upMyLocCarModel(
......
package com.sd.api.scenario.mine
import com.amap.api.maps.CameraUpdateFactory
import com.amap.api.maps.model.BitmapDescriptorFactory
import com.amap.api.maps.model.LatLng
import com.amap.api.maps.model.LatLngBounds
import com.amap.api.maps.model.Marker
import com.amap.api.maps.model.MarkerOptions
import com.amap.api.maps.utils.overlay.MovingPointOverlay
import com.sd.api.R
import com.sd.api.maps.MSNavi
import com.sd.api.maps.MsOperationParent
import com.sd.api.scenario.CucsVehicle
import com.sd.api.scenario.bean.VehiclePos
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
//高德实现移动
internal object AmapVehMove : MsOperationParent() {
private var smoothMarker: MovingPointOverlay? = null
private var marker: Marker? = null
//轨迹点
private var subList = mutableListOf<LatLng>()
//保存上一次位置的时间
// private var oldTime = 0L
//
// //2次定位的时间差,默认1000毫秒
// private var vehTimeDiff = 1000L
fun aMapCarMove(vPos: VehiclePos, moveType: Int) {
if (moveType == 3) {
if (MSNavi.isStartNai) {//导航中就删除在线地图小车
CucsVehicle.deleteCarModel()
} else {
//计算2次定位时间差
// calTimeDiff()
var mAMap = getMapReadView()?.aMap
// 实例 MovingPointOverlay 对象
if (smoothMarker == null) {
// 设置 平滑移动的 图标
marker = mAMap?.addMarker(
MarkerOptions().icon(BitmapDescriptorFactory.fromResource(R.drawable.icon_car))
.anchor(0.5f, 0.5f)
)
smoothMarker = MovingPointOverlay(mAMap, marker)
}
subList.add(LatLng(vPos.lat, vPos.lng))
if (subList.count() > 3) {
// 构建 轨迹的显示区域
// val builder = LatLngBounds.Builder()
// builder.include(subList.first())
// builder.include(subList.get(subList.count() - 2))
// mAMap?.animateCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 30))
mAMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(vPos.lat, vPos.lng), 16f))
// 设置轨迹点
smoothMarker?.setPoints(subList)
// 设置平滑移动的总时间 单位 秒
smoothMarker?.setTotalDuration(1)
//开始移动
smoothMarker?.startSmoothMove()
CoroutineScope(Dispatchers.Default).launch {
delay(1000)
subList.clear()
}
}
// smoothMarker?.setMoveListener { distance->
// if (distance.toInt() in 0..5) {
// smoothMarker?.stopMove()
// subList.clear()
// }
// }
}
}
}
fun stopMove() {
smoothMarker?.stopMove()
}
fun deleteCarModel() {
smoothMarker?.removeMarker()
smoothMarker = null
marker?.remove()
marker = null
}
}
package com.sd.api.scenario.mine
import com.minedata.minenavi.mapdal.LatLng
import com.sd.api.highmap.HighMap
import com.sd.api.highmap.HighPos
import com.sd.api.maps.MSNavi
import com.sd.api.maps.MethodAdv
import com.sd.api.parkroad.MsParkRoad
import com.sd.api.parkroad.RoadPos
import com.sd.api.scenario.CucsVehicle
import com.sd.api.scenario.VehicleModel
import com.sd.api.scenario.bean.VehiclePos
import com.sd.api.utils.SmoothMoveUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
//四维实现移动
internal object MineVehMove {
//动画是否开始
private var isAniStart = false
//前一次位置
private var fromLoc: VehiclePos? = null
//当前位置
private var cvPos: VehiclePos? = null
//保存上一次位置的时间
private var oldTime = 0L
//2次定位的时间差,默认1000毫秒
private var vehTimeDiff = 1000L
//移动地图中心点的此时
private var mapCenCount = 0
fun mineCarMove(vPos: VehiclePos, moveType: Int) {
cvPos = vPos
if (moveType == 1) {
var highPos = HighPos.instance.apply {
heading = vPos.heading
lat = vPos.lat
lon = vPos.lng
evel = vPos.evel
}
HighMap.setCarPosition(highPos)
} else if (moveType == 2) {
var roadPos = RoadPos.instance.apply {
lat = vPos.lat
lng = vPos.lng
bearing = vPos.heading.toFloat()
}
MsParkRoad.updateMainCar(roadPos, null)
} else if (moveType == 3) {
if (MSNavi.isStartNai) {//导航中就删除在线地图小车
CucsVehicle.deleteCarModel()
} else {
if (fromLoc == null || fromLoc?.lng == 0.0) {
setFromMyLoc(vPos)//设置起始位置
}
//计算2次定位时间差
calTimeDiff()
if (isAniStart == false) {
isAniStart = true
SmoothMoveUtils.startSmoothMove(
LatLng(fromLoc!!.lat, fromLoc!!.lng),
LatLng(vPos.lat, vPos.lng),
vehTimeDiff,
sCarSmooth
)
mapCenCount++
if (mapCenCount % 10 == 0) {
MethodAdv.setMapCenter(cvPos?.lat ?: 0.0, cvPos?.lng ?: 0.0)
mapCenCount = 0
}
}
}
}
}
//在线地图主车平滑移动
private var sCarSmooth = object : SmoothMoveUtils.OnPositionUpdateListener {
override fun onUpdate(
iLatLng: LatLng,
bearing: Float
) {
//在线地图主车平滑移动
VehicleModel.upMyLocCarModel(
iLatLng.latitude,
iLatLng.longitude,
cvPos?.heading?.toFloat() ?: 0f
)
}
override fun onFinish() {
// println("--------------动画完成")
setFromMyLoc(cvPos!!)
isAniStart = false
}
}
//计算2次拿到车辆定位的时间差
fun calTimeDiff() {
// CoroutineScope(Dispatchers.Default).launch {
if (oldTime != 0L) {
vehTimeDiff = System.currentTimeMillis() - oldTime
// println("-------定位时间差 = ${vehTimeDiff}")
}
oldTime = System.currentTimeMillis()
// }
}
//设置前一个点
private fun setFromMyLoc(myloc: VehiclePos) {
if (fromLoc == null) {
fromLoc = VehiclePos()
}
fromLoc!!.run {
this.lat = myloc.lat
this.lng = myloc.lng
this.heading = myloc.heading
this.evel = myloc.evel
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment