From da29b60da377d0bc4a456b0386bb7735fbb23a22 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 20 May 2025 09:54:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9C=B0=E5=9B=BE=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentService.kt | 1 - .../amap_service/android/build.gradle | 3 + .../amap_service/AmapServicePlugin.kt | 41 +- .../amap_service/NavigationService.kt | 2 + .../amap_service/NavigationServiceStarter.kt | 72 +++ .../amap_service/lib/amap_controller.dart | 174 +++++++ .../amap_service/lib/amap_service_map.dart | 20 + .../amap_service/lib/amap_widget.dart | 425 ++++++++++++++++++ .../lib/core/amap_flutter_platform.dart | 41 ++ .../amap_service/lib/core/map_event.dart | 77 ++++ .../core/method_channel_amap_flutter_map.dart | 274 +++++++++++ .../amap_service/lib/types/base_overlay.dart | 29 ++ .../amap_service/lib/types/bitmap.dart | 140 ++++++ .../amap_service/lib/types/camera.dart | 150 +++++++ .../amap_service/lib/types/marker.dart | 249 ++++++++++ .../lib/types/marker_updates.dart | 105 +++++ .../amap_service/lib/types/polygon.dart | 120 +++++ .../lib/types/polygon_updates.dart | 108 +++++ .../amap_service/lib/types/polyline.dart | 198 ++++++++ .../lib/types/polyline_updates.dart | 108 +++++ .../amap_service/lib/types/types.dart | 11 + local_plugins/amap_service/lib/types/ui.dart | 257 +++++++++++ local_plugins/amap_service/pubspec.yaml | 4 + .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/cache.properties | 4 +- 25 files changed, 2608 insertions(+), 5 deletions(-) create mode 100644 local_plugins/amap_service/lib/amap_controller.dart create mode 100644 local_plugins/amap_service/lib/amap_service_map.dart create mode 100644 local_plugins/amap_service/lib/amap_widget.dart create mode 100644 local_plugins/amap_service/lib/core/amap_flutter_platform.dart create mode 100644 local_plugins/amap_service/lib/core/map_event.dart create mode 100644 local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart create mode 100644 local_plugins/amap_service/lib/types/base_overlay.dart create mode 100644 local_plugins/amap_service/lib/types/bitmap.dart create mode 100644 local_plugins/amap_service/lib/types/camera.dart create mode 100644 local_plugins/amap_service/lib/types/marker.dart create mode 100644 local_plugins/amap_service/lib/types/marker_updates.dart create mode 100644 local_plugins/amap_service/lib/types/polygon.dart create mode 100644 local_plugins/amap_service/lib/types/polygon_updates.dart create mode 100644 local_plugins/amap_service/lib/types/polyline.dart create mode 100644 local_plugins/amap_service/lib/types/polyline_updates.dart create mode 100644 local_plugins/amap_service/lib/types/types.dart create mode 100644 local_plugins/amap_service/lib/types/ui.dart diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 41c2aa990..f95b4a559 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -1106,7 +1106,6 @@ object AgentService : CoroutineScope { /** * 处理导航 * 将ai返回的音乐连接 在后台启动播放 - * * @param musicUrl 图片文件路径 * @param name 歌曲名称,默认为空 * @param singer 歌手,默认为false diff --git a/local_plugins/amap_service/android/build.gradle b/local_plugins/amap_service/android/build.gradle index 4bd0a8a1d..d9568dd2d 100644 --- a/local_plugins/amap_service/android/build.gradle +++ b/local_plugins/amap_service/android/build.gradle @@ -65,3 +65,6 @@ android { } } } +dependencies { + implementation(project(":deepvoice_utils")) +} diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt index 020d98a61..941d65cd5 100644 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/AmapServicePlugin.kt @@ -3,24 +3,34 @@ package com.yunqiinnovation.amap_service import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat -// import com.yunqiinnovation.deepvoice_utils.FileLogger import com.yunqiinnovation.amap_service.NavigationServiceStarter import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import org.json.JSONObject /** MusicServicePlugin */ -class AmapServicePlugin: FlutterPlugin, MethodCallHandler { +class AmapServicePlugin: FlutterPlugin, MethodCallHandler,EventChannel.StreamHandler, NavigationListener { private val TAG = "MusicServicePlugin" private lateinit var context: Context private lateinit var channel : MethodChannel + private lateinit var eventChannel: EventChannel + private var eventSink: EventChannel.EventSink? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service") channel.setMethodCallHandler(this) + + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.music_service/events") + eventChannel.setStreamHandler(this) + // 添加监听器 + NavigationServiceStarter.addListener(this) } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { @@ -51,4 +61,31 @@ class AmapServicePlugin: FlutterPlugin, MethodCallHandler { } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} + + /** + * 实现AgentServiceListener接口 + * 接收来自AgentService的事件,并转发到Flutter + */ + override fun onEvent(eventName: String, data: Map) { + // 将事件转换为JSON字符串 + val eventData = JSONObject().apply { + put("event", eventName) + put("data", JSONObject(data)) + } + + // 在主线程发送事件到Flutter + MainScope().launch { + eventSink?.success(eventData.toString()) + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + TODO("Not yet implemented") + } + + override fun onCancel(arguments: Any?) { + TODO("Not yet implemented") + } + + } diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt index 04b4e07b7..093e14137 100644 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationService.kt @@ -67,6 +67,7 @@ class NavigationService: Service() { Log.w(TAG, "开始导航 startpos:$startpos endpos:$endpos naviType:$naviType") // 初始化导航 aMapNavi = AMapNavi.getInstance(applicationContext) + aMapNavi.setUseInnerVoice(true) aMapNavi.setBroadcastMode(1) aMapNavi.addAMapNaviListener(naviListener) @@ -261,6 +262,7 @@ class NavigationService: Service() { override fun onCalculateRouteSuccess(ids: IntArray?) { Log.e(TAG, "onCalculateRouteSuccess! $ids") + AMapNavi.getInstance(this).startNavi(NaviType.GPS); aMapNavi.startNavi(NaviType.EMULATOR) } diff --git a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt index 788495e9b..980edb166 100644 --- a/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt +++ b/local_plugins/amap_service/android/src/main/kotlin/com/yunqiinnovation/amap_service/NavigationServiceStarter.kt @@ -2,11 +2,29 @@ package com.yunqiinnovation.amap_service import android.content.Context import android.content.Intent +import android.os.Handler +import android.os.Looper import android.util.Log import androidx.core.content.ContextCompat +import com.yunqiinnovation.amap_service.NavigationService.Companion +import java.util.Collections + +/** + * 代理服务事件监听接口 + */ +interface NavigationListener { + /** + * 当有事件发生时调用此方法 + * + * @param eventName 事件名称 + * @param data 事件数据 + */ + fun onEvent(eventName: String, data: Map) +} object NavigationServiceStarter { private val TAG = "NavigationServiceStarter" + private val listeners = Collections.synchronizedSet(mutableSetOf()) //播放单曲 fun startServiceWithCommand(context: Context, command: String, startpos: String? = null, endpos: String? = null, ntype: String? = null) { Log.i(TAG, "liwei------------------- startServiceWithCommand: $command $startpos $endpos $ntype") @@ -31,4 +49,58 @@ object NavigationServiceStarter { } Log.d(TAG, "liwei------------------- Service start success: $success") } + + + /** + * 添加事件监听器 + * @param listener 要添加的监听器 + */ + fun addListener(listener: NavigationListener) { + listeners.add(listener) + } + + /** + * 移除事件监听器 + * @param listener 要移除的监听器 + */ + fun removeListener(listener: NavigationListener) { + listeners.remove(listener) + } + + /** + * 移除所有事件监听器 + */ + fun clearListeners() { + listeners.clear() + } + + /** + * 发送事件 + */ + fun sendEvent(eventName: String, data: Map) { + // 检查当前是否在主线程 + if (Looper.myLooper() == Looper.getMainLooper()) { + // 已在主线程,直接发送 + listeners.forEach { listener -> + try { + listener.onEvent(eventName, data) + } catch (e: Exception) { +// FileLogger.e(NavigationService.TAG, "向监听器发送事件失败", e) + } + } + + } else { + // 不在主线程,切换到主线程 + Handler(Looper.getMainLooper()).post { + listeners.forEach { listener -> + try { + listener.onEvent(eventName, data) + } catch (e: Exception) { +// FileLogger.e(NavigationService.TAG, "向监听器发送事件失败", e) + } + } + } + } + } + } \ No newline at end of file diff --git a/local_plugins/amap_service/lib/amap_controller.dart b/local_plugins/amap_service/lib/amap_controller.dart new file mode 100644 index 000000000..48db2802d --- /dev/null +++ b/local_plugins/amap_service/lib/amap_controller.dart @@ -0,0 +1,174 @@ +part of amap_service_map; + +final MethodChannelAMapFlutterMap _methodChannel = + AMapFlutterPlatform.instance as MethodChannelAMapFlutterMap; + +/// 地图通信中心 +class AMapController { + final int mapId; + final _MapState _mapState; + + AMapController._( + CameraPosition initCameraPosition, + this._mapState, { + required this.mapId, + }) { + _connectStreams(mapId); + } + + ///根据传入的id初始化[AMapController] + /// 主要用于在[AMapWidget]初始化时在[AMapWidget.onMapCreated]中初始化controller + static Future init( + int id, + CameraPosition initialCameration, + _MapState mapState, + ) async { + await _methodChannel.init(id); + return AMapController._(initialCameration, mapState, mapId: id); + } + + ///只用于测试 + ///用于与native的通信 + @visibleForTesting + MethodChannel get channel { + return _methodChannel.channel(mapId); + } + + void _connectStreams(int mapId) { + if (_mapState.widget.onLocationChanged != null) { + _methodChannel + .onLocationChanged(mapId: mapId) + .listen( + (LocationChangedEvent e) => + _mapState.widget.onLocationChanged!(e.value), + ); + } + + if (_mapState.widget.onCameraMove != null) { + _methodChannel + .onCameraMove(mapId: mapId) + .listen( + (CameraPositionMoveEvent e) => + _mapState.widget.onCameraMove!(e.value), + ); + } + if (_mapState.widget.onCameraMoveEnd != null) { + _methodChannel + .onCameraMoveEnd(mapId: mapId) + .listen( + (CameraPositionMoveEndEvent e) => + _mapState.widget.onCameraMoveEnd!(e.value), + ); + } + if (_mapState.widget.onTap != null) { + _methodChannel + .onMapTap(mapId: mapId) + .listen(((MapTapEvent e) => _mapState.widget.onTap!(e.value))); + } + if (_mapState.widget.onLongPress != null) { + _methodChannel + .onMapLongPress(mapId: mapId) + .listen( + ((MapLongPressEvent e) => _mapState.widget.onLongPress!(e.value)), + ); + } + + if (_mapState.widget.onPoiTouched != null) { + _methodChannel + .onPoiTouched(mapId: mapId) + .listen( + ((MapPoiTouchEvent e) => _mapState.widget.onPoiTouched!(e.value)), + ); + } + + _methodChannel + .onMarkerTap(mapId: mapId) + .listen((MarkerTapEvent e) => _mapState.onMarkerTap(e.value)); + + _methodChannel + .onMarkerDragEnd(mapId: mapId) + .listen( + (MarkerDragEndEvent e) => + _mapState.onMarkerDragEnd(e.value, e.position), + ); + + _methodChannel + .onPolylineTap(mapId: mapId) + .listen((PolylineTapEvent e) => _mapState.onPolylineTap(e.value)); + } + + void disponse() { + _methodChannel.dispose(id: mapId); + } + + Future _updateMapOptions(Map optionsUpdate) { + return _methodChannel.updateMapOptions(optionsUpdate, mapId: mapId); + } + + Future _updateMarkers(MarkerUpdates markerUpdates) { + return _methodChannel.updateMarkers(markerUpdates, mapId: mapId); + } + + Future _updatePolylines(PolylineUpdates polylineUpdates) { + return _methodChannel.updatePolylines(polylineUpdates, mapId: mapId); + } + + Future _updatePolygons(PolygonUpdates polygonUpdates) { + return _methodChannel.updatePolygons(polygonUpdates, mapId: mapId); + } + + ///改变地图视角 + /// + ///通过[CameraUpdate]对象设置新的中心点、缩放比例、放大缩小、显示区域等内容 + /// + ///(注意:iOS端设置显示区域时,不支持duration参数,动画时长使用iOS地图默认值350毫秒) + /// + ///可选属性[animated]用于控制是否执行动画移动 + /// + ///可选属性[duration]用于控制执行动画的时长,默认250毫秒,单位:毫秒 + Future moveCamera( + CameraUpdate cameraUpdate, { + bool animated = true, + int duration = 250, + }) { + return _methodChannel.moveCamera( + cameraUpdate, + mapId: mapId, + animated: animated, + duration: duration, + ); + } + + ///设置地图每秒渲染的帧数 + Future setRenderFps(int fps) { + return _methodChannel.setRenderFps(fps, mapId: mapId); + } + + ///地图截屏 + Future takeSnapshot() { + return _methodChannel.takeSnapshot(mapId: mapId); + } + + /// 获取地图审图号(普通地图) + /// + /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 + /// + /// 如高德地图在"关于"中体现 + Future getMapContentApprovalNumber() { + return _methodChannel.getMapContentApprovalNumber(mapId: mapId); + } + + /// 获取地图审图号(卫星地图) + /// + /// 任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号 + /// + /// 如高德地图在"关于"中体现 + Future getSatelliteImageApprovalNumber() { + return _methodChannel.getSatelliteImageApprovalNumber(mapId: mapId); + } + + /// 清空缓存 + Future clearDisk() { + return _methodChannel.clearDisk(mapId: mapId); + } +} diff --git a/local_plugins/amap_service/lib/amap_service_map.dart b/local_plugins/amap_service/lib/amap_service_map.dart new file mode 100644 index 000000000..d8404d6d7 --- /dev/null +++ b/local_plugins/amap_service/lib/amap_service_map.dart @@ -0,0 +1,20 @@ +/// 高德地图Flutter插件入口文件 +library amap_service_map; + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; +import 'package:amap_flutter_map/src/core/map_event.dart'; +import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; +import 'package:amap_flutter_map/src/types/types.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +export 'package:amap_flutter_map/src/types/types.dart'; + +part 'amap_controller.dart'; +part 'amap_widget.dart'; diff --git a/local_plugins/amap_service/lib/amap_widget.dart b/local_plugins/amap_service/lib/amap_widget.dart new file mode 100644 index 000000000..90c7b4407 --- /dev/null +++ b/local_plugins/amap_service/lib/amap_widget.dart @@ -0,0 +1,425 @@ +part of amap_service_map; + +typedef void MapCreatedCallback(AMapController controller); + +///用于展示高德地图的Widget +class AMapWidget extends StatefulWidget { + ///高德开放平台的key + /// + final AMapApiKey? apiKey; + + /// 初始化时的地图中心点 + final CameraPosition initialCameraPosition; + + ///地图类型 + final MapType mapType; + + ///自定义地图样式 + final CustomStyleOptions? customStyleOptions; + + ///定位小蓝点 + final MyLocationStyleOptions? myLocationStyleOptions; + + ///缩放级别范围 + final MinMaxZoomPreference? minMaxZoomPreference; + + ///地图显示范围 + final LatLngBounds? limitBounds; + + ///显示路况开关 + final bool trafficEnabled; + + /// 地图poi是否允许点击 + final bool touchPoiEnabled; + + ///是否显示3D建筑物 + final bool buildingsEnabled; + + ///是否显示底图文字标注 + final bool labelsEnabled; + + ///是否显示指南针 + final bool compassEnabled; + + ///是否显示比例尺 + final bool scaleEnabled; + + ///是否支持缩放手势 + final bool zoomGesturesEnabled; + + ///是否支持滑动手势 + final bool scrollGesturesEnabled; + + ///是否支持旋转手势 + final bool rotateGesturesEnabled; + + ///是否支持倾斜手势 + final bool tiltGesturesEnabled; + + /// 地图上显示的Marker + final Set markers; + + /// 地图上显示的polyline + final Set polylines; + + /// 地图上显示的polygon + final Set polygons; + + /// 地图创建成功的回调, 收到此回调之后才可以操作地图 + final MapCreatedCallback? onMapCreated; + + /// 相机视角持续移动的回调 + final ArgumentCallback? onCameraMove; + + /// 相机视角移动结束的回调 + final ArgumentCallback? onCameraMoveEnd; + + /// 地图单击事件的回调 + final ArgumentCallback? onTap; + + /// 地图长按事件的回调 + final ArgumentCallback? onLongPress; + + /// 地图POI的点击回调,需要`touchPoiEnabled`true,才能回调 + final ArgumentCallback? onPoiTouched; + + ///位置回调 + final ArgumentCallback? onLocationChanged; + + ///需要应用到地图上的手势集合 + final Set> gestureRecognizers; + + ///高德合规声明配置 + /// + /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy + final AMapPrivacyStatement? privacyStatement; + + /// 创建一个展示高德地图的widget + /// + /// 如果使用的高德地图SDK的版本是8.1.0及以上版本, + /// 在app首次启动时必须传入高德合规声明配置[privacyStatement],后续如果没有变化不需要重复设置 + ///
  • [privacyStatement.hasContains] 隐私权政策是否包含高德开平隐私权政策
  • + ///
  • [privacyStatement.hasShow] 是否已经弹窗展示给用户
  • + ///
  • [privacyStatement.hasAgree] 隐私权政策是否已经取得用户同意
  • + /// 以上三个值,任何一个为false都会造成地图插件不工作(白屏情况) + /// + /// 高德SDK合规使用方案请参考:https://lbs.amap.com/news/sdkhgsy + /// + /// [AssertionError] will be thrown if [initialCameraPosition] is null; + const AMapWidget({ + Key? key, + this.privacyStatement, + this.apiKey, + this.initialCameraPosition = const CameraPosition( + target: LatLng(39.909187, 116.397451), + zoom: 10, + ), + this.mapType = MapType.normal, + this.buildingsEnabled = true, + this.compassEnabled = false, + this.labelsEnabled = true, + this.limitBounds, + this.minMaxZoomPreference, + this.rotateGesturesEnabled = true, + this.scaleEnabled = true, + this.scrollGesturesEnabled = true, + this.tiltGesturesEnabled = true, + this.touchPoiEnabled = true, + this.trafficEnabled = false, + this.zoomGesturesEnabled = true, + this.onMapCreated, + this.gestureRecognizers = const >{}, + this.customStyleOptions, + this.myLocationStyleOptions, + this.onCameraMove, + this.onCameraMoveEnd, + this.onLocationChanged, + this.onTap, + this.onLongPress, + this.onPoiTouched, + this.markers = const {}, + this.polylines = const {}, + this.polygons = const {}, + }) : super(key: key); + + /// + @override + State createState() => _MapState(); +} + +class _MapState extends State { + Map _markers = {}; + Map _polylines = {}; + Map _polygons = {}; + + final Completer _controller = Completer(); + late _AMapOptions _mapOptions; + + @override + Widget build(BuildContext context) { + AMapUtil.init(context); + final Map creationParams = { + 'privacyStatement': widget.privacyStatement?.toMap(), + 'apiKey': widget.apiKey?.toMap(), + 'initialCameraPosition': widget.initialCameraPosition.toMap(), + 'options': _mapOptions.toMap(), + 'markersToAdd': serializeOverlaySet(widget.markers), + 'polylinesToAdd': serializeOverlaySet(widget.polylines), + 'polygonsToAdd': serializeOverlaySet(widget.polygons), + }; + Widget mapView = _methodChannel.buildView( + creationParams, + widget.gestureRecognizers, + onPlatformViewCreated, + ); + return mapView; + } + + @override + void initState() { + super.initState(); + _mapOptions = _AMapOptions.fromWidget(widget); + _markers = keyByMarkerId(widget.markers); + _polygons = keyByPolygonId(widget.polygons); + _polylines = keyByPolylineId(widget.polylines); + print('initState AMapWidget'); + } + + @override + void dispose() async { + super.dispose(); + AMapController controller = await _controller.future; + controller.disponse(); + print('dispose AMapWidget with mapId: ${controller.mapId}'); + } + + @override + void reassemble() { + super.reassemble(); + print('reassemble AMapWidget'); + } + + @override + void deactivate() async { + super.deactivate(); + print('deactivate AMapWidget}'); + } + + @override + void didUpdateWidget(covariant AMapWidget oldWidget) { + super.didUpdateWidget(oldWidget); + _updateOptions(); + _updateMarkers(); + _updatePolylines(); + _updatePolygons(); + } + + Future onPlatformViewCreated(int id) async { + final AMapController controller = await AMapController.init( + id, + widget.initialCameraPosition, + this, + ); + _controller.complete(controller); + final MapCreatedCallback? _onMapCreated = widget.onMapCreated; + if (_onMapCreated != null) { + _onMapCreated(controller); + } + } + + void onMarkerTap(String markerId) { + final Marker? _marker = _markers[markerId]; + if (_marker != null) { + final ArgumentCallback? _onTap = _marker.onTap; + if (_onTap != null) { + _onTap(markerId); + } + } + } + + void onMarkerDragEnd(String markerId, LatLng position) { + final Marker? _marker = _markers[markerId]; + if (_marker != null) { + final MarkerDragEndCallback? _onDragEnd = _marker.onDragEnd; + if (_onDragEnd != null) { + _onDragEnd(markerId, position); + } + } + } + + void onPolylineTap(String polylineId) { + final Polyline? _polyline = _polylines[polylineId]; + if (_polyline != null) { + final ArgumentCallback? _onTap = _polyline.onTap; + if (_onTap != null) { + _onTap(polylineId); + } + } + } + + void _updateOptions() async { + final _AMapOptions newOptions = _AMapOptions.fromWidget(widget); + final Map updates = _mapOptions._updatesMap(newOptions); + if (updates.isEmpty) { + return; + } + final AMapController controller = await _controller.future; + // ignore: unawaited_futures + controller._updateMapOptions(updates); + _mapOptions = newOptions; + } + + void _updateMarkers() async { + final AMapController controller = await _controller.future; + // ignore: unawaited_futures + controller._updateMarkers( + MarkerUpdates.from(_markers.values.toSet(), widget.markers), + ); + _markers = keyByMarkerId(widget.markers); + } + + void _updatePolylines() async { + final AMapController controller = await _controller.future; + controller._updatePolylines( + PolylineUpdates.from(_polylines.values.toSet(), widget.polylines), + ); + _polylines = keyByPolylineId(widget.polylines); + } + + void _updatePolygons() async { + final AMapController controller = await _controller.future; + controller._updatePolygons( + PolygonUpdates.from(_polygons.values.toSet(), widget.polygons), + ); + _polygons = keyByPolygonId(widget.polygons); + } +} + +//高德地图参数设置 +class _AMapOptions { + ///地图类型 + final MapType mapType; + + ///自定义地图样式 + final CustomStyleOptions? customStyleOptions; + + ///定位小蓝点 + final MyLocationStyleOptions? myLocationStyleOptions; + + //缩放级别范围 + final MinMaxZoomPreference? minMaxZoomPreference; + + ///地图显示范围 + final LatLngBounds? limitBounds; + + ///显示路况开关 + final bool? trafficEnabled; + + /// 地图poi是否允许点击 + final bool? touchPoiEnabled; + + ///是否显示3D建筑物 + final bool? buildingsEnabled; + + ///是否显示底图文字标注 + final bool? labelsEnabled; + + ///是否显示指南针 + final bool? compassEnabled; + + ///是否显示比例尺 + final bool? scaleEnabled; + + ///是否支持缩放手势 + final bool? zoomGesturesEnabled; + + ///是否支持滑动手势 + final bool? scrollGesturesEnabled; + + ///是否支持旋转手势 + final bool? rotateGesturesEnabled; + + ///是否支持仰角手势 + final bool? tiltGesturesEnabled; + + _AMapOptions({ + this.mapType = MapType.normal, + this.buildingsEnabled, + this.customStyleOptions, + this.myLocationStyleOptions, + this.compassEnabled, + this.labelsEnabled, + this.limitBounds, + this.minMaxZoomPreference, + this.scaleEnabled, + this.touchPoiEnabled, + this.trafficEnabled, + this.rotateGesturesEnabled, + this.scrollGesturesEnabled, + this.tiltGesturesEnabled, + this.zoomGesturesEnabled, + }); + + static _AMapOptions fromWidget(AMapWidget map) { + return _AMapOptions( + mapType: map.mapType, + buildingsEnabled: map.buildingsEnabled, + compassEnabled: map.compassEnabled, + labelsEnabled: map.labelsEnabled, + limitBounds: map.limitBounds, + minMaxZoomPreference: map.minMaxZoomPreference, + scaleEnabled: map.scaleEnabled, + touchPoiEnabled: map.touchPoiEnabled, + trafficEnabled: map.trafficEnabled, + rotateGesturesEnabled: map.rotateGesturesEnabled, + scrollGesturesEnabled: map.scrollGesturesEnabled, + tiltGesturesEnabled: map.tiltGesturesEnabled, + zoomGesturesEnabled: map.zoomGesturesEnabled, + customStyleOptions: map.customStyleOptions?.clone(), + myLocationStyleOptions: map.myLocationStyleOptions?.clone(), + ); + } + + Map toMap() { + final Map optionsMap = {}; + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + optionsMap[fieldName] = value; + } + } + + addIfNonNull('mapType', mapType.index); + addIfNonNull('buildingsEnabled', buildingsEnabled); + addIfNonNull('customStyleOptions', customStyleOptions?.clone().toMap()); + addIfNonNull('compassEnabled', compassEnabled); + addIfNonNull('labelsEnabled', labelsEnabled); + addIfNonNull('limitBounds', limitBounds?.toJson()); + addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?.toJson()); + addIfNonNull('scaleEnabled', scaleEnabled); + addIfNonNull('touchPoiEnabled', touchPoiEnabled); + addIfNonNull('trafficEnabled', trafficEnabled); + addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); + addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); + addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled); + addIfNonNull('zoomGesturesEnabled', zoomGesturesEnabled); + addIfNonNull('myLocationStyle', myLocationStyleOptions?.clone().toMap()); + return optionsMap; + } + + Map _updatesMap(_AMapOptions newOptions) { + final Map prevOptionsMap = toMap(); + + return newOptions.toMap()..removeWhere( + (String key, dynamic value) => + (_checkChange(key, prevOptionsMap[key], value)), + ); + } + + bool _checkChange(String key, dynamic preValue, dynamic newValue) { + if (key == 'myLocationStyle' || key == 'customStyleOptions') { + return preValue?.toString() == newValue?.toString(); + } else { + return preValue == newValue; + } + } +} diff --git a/local_plugins/amap_service/lib/core/amap_flutter_platform.dart b/local_plugins/amap_service/lib/core/amap_flutter_platform.dart new file mode 100644 index 000000000..eaef547f3 --- /dev/null +++ b/local_plugins/amap_service/lib/core/amap_flutter_platform.dart @@ -0,0 +1,41 @@ +import 'package:amap_flutter_map/src/core/method_channel_amap_flutter_map.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// “amap_flutter_map”平台特定实现必须扩展的接口 +abstract class AMapFlutterPlatform extends PlatformInterface { + static final Object _token = Object(); + AMapFlutterPlatform() : super(token: _token); + static AMapFlutterPlatform _instance = MethodChannelAMapFlutterMap() as AMapFlutterPlatform; + + /// The default instance of [AMapFlutterPlatform] to use. + /// + /// Defaults to [MethodChannelAMapFlutterMap]. + static AMapFlutterPlatform get instance => _instance; + + static set instance(AMapFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// /// Initializes the platform interface with [id]. + /// + /// This method is called when the plugin is first initialized. + Future init(int id) { + throw UnimplementedError('init() has not been implemented.'); + } + + void dispose({required int id}) { + throw UnimplementedError('dispose() has not been implemented.'); + } + + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + PlatformViewCreatedCallback onPlatformViewCreated) { + throw UnimplementedError('buildView() has not been implemented.'); + } +} diff --git a/local_plugins/amap_service/lib/core/map_event.dart b/local_plugins/amap_service/lib/core/map_event.dart new file mode 100644 index 000000000..8e217b07c --- /dev/null +++ b/local_plugins/amap_service/lib/core/map_event.dart @@ -0,0 +1,77 @@ +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/amap_flutter_map.dart'; + +///地图事件处理 +class MapEvent { + /// 地图id + final int mapId; + + ///返回的内容,对应的[MethodCall]中的[[arguments]] + final T value; + + /// 构造一个event + /// + /// `mapId` 当前地图的id + /// `value` 需要传输的值,可以为`null`. + MapEvent(this.mapId, this.value); +} + +///定位回调接口 +class LocationChangedEvent extends MapEvent { + LocationChangedEvent(int mapId, AMapLocation value) : super(mapId, value); +} + +///地图移动回调 +class CameraPositionMoveEvent extends MapEvent { + CameraPositionMoveEvent(int mapId, CameraPosition value) + : super(mapId, value); +} + +///地图移动结束回调 +class CameraPositionMoveEndEvent extends MapEvent { + CameraPositionMoveEndEvent(int mapId, CameraPosition value) + : super(mapId, value); +} + +///点击地图回调 +class MapTapEvent extends MapEvent { + MapTapEvent(int mapId, LatLng value) : super(mapId, value); +} + +///长按地图回调 +class MapLongPressEvent extends MapEvent { + MapLongPressEvent(int mapId, LatLng value) : super(mapId, value); +} + +/// 带位置回调的地图事件 +class _PositionedMapEvent extends MapEvent { + /// 事件中带的位置信息 + final LatLng position; + + /// 构造一个带位置的地图事件, + /// + /// `mapId` 当前地图的id + /// `value` 需要传输的值,可以为`null`. + _PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); +} + +/// [Marker] 的点击事件 +class MarkerTapEvent extends MapEvent { + MarkerTapEvent(int mapId, String markerId) : super(mapId, markerId); +} + +/// [Marker] 的拖拽结束事件,附带拖拽结束时的位置信息[LatLng]. +class MarkerDragEndEvent extends _PositionedMapEvent { + MarkerDragEndEvent(int mapId, LatLng position, String markerId) + : super(mapId, position, markerId); +} + +/// [Polyline] 的点击事件 +class PolylineTapEvent extends MapEvent { + PolylineTapEvent(int mapId, String polylineId) : super(mapId, polylineId); +} + +/// Poi点击事件 +class MapPoiTouchEvent extends MapEvent { + MapPoiTouchEvent(int mapId, AMapPoi poi) : super(mapId, poi); +} diff --git a/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart b/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart new file mode 100644 index 000000000..59c561c5f --- /dev/null +++ b/local_plugins/amap_service/lib/core/method_channel_amap_flutter_map.dart @@ -0,0 +1,274 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/src/core/amap_flutter_platform.dart'; +import 'package:amap_flutter_map/src/types/types.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; +import 'package:stream_transform/stream_transform.dart'; + +import 'map_event.dart'; + +const VIEW_TYPE = 'com.amap.flutter.map'; + +/// 使用[MethodChannel]与Native代码通信的[AMapFlutterPlatform]的实现。 +class MethodChannelAMapFlutterMap implements AMapFlutterPlatform { + final Map _channels = {}; + + MethodChannel channel(int mapId) { + return _channels[mapId]!; + } + + @override + Future init(int mapId) { + MethodChannel? channel = _channels[mapId]; + if (channel == null) { + channel = MethodChannel('amap_flutter_map_$mapId'); + channel.setMethodCallHandler((call) => _handleMethodCall(call, mapId)); + _channels[mapId] = channel; + } + return channel.invokeMethod('map#waitForMap'); + } + + ///更新地图参数 + Future updateMapOptions( + Map newOptions, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'map#update', + { + 'options': newOptions, + }, + ); + } + + /// 更新Marker的数据 + Future updateMarkers( + MarkerUpdates markerUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'markers#update', + markerUpdates.toMap(), + ); + } + + /// 更新polyline的数据 + Future updatePolylines( + PolylineUpdates polylineUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'polylines#update', + polylineUpdates.toMap(), + ); + } + + /// 更新polygon的数据 + Future updatePolygons( + PolygonUpdates polygonUpdates, { + required int mapId, + }) { + return channel(mapId).invokeMethod( + 'polygons#update', + polygonUpdates.toMap(), + ); + } + + @override + void dispose({required int id}) { + if (_channels.containsKey(id)) { + _channels.remove(id); + } + } + + @override + Widget buildView( + Map creationParams, + Set> gestureRecognizers, + void Function(int id) onPlatformViewCreated) { + if (defaultTargetPlatform == TargetPlatform.android) { + creationParams['debugMode'] = kDebugMode; + return AndroidView( + viewType: VIEW_TYPE, + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: VIEW_TYPE, + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return Text('当前平台:$defaultTargetPlatform, 不支持使用高德地图插件'); + } + + // handleMethodCall的`broadcast` + final StreamController _mapEventStreamController = + StreamController.broadcast(); + + // 根据mapid返回相应的event. + Stream _events(int mapId) => + _mapEventStreamController.stream.where((event) => event.mapId == mapId); + + //定位回调 + Stream onLocationChanged({required int mapId}) { + return _events(mapId).whereType(); + } + + //Camera 移动回调 + Stream onCameraMove({required int mapId}) { + return _events(mapId).whereType(); + } + + ///Camera 移动结束回调 + Stream onCameraMoveEnd({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMapTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMapLongPress({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onPoiTouched({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMarkerTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onMarkerDragEnd({required int mapId}) { + return _events(mapId).whereType(); + } + + Stream onPolylineTap({required int mapId}) { + return _events(mapId).whereType(); + } + + Future _handleMethodCall(MethodCall call, int mapId) async { + switch (call.method) { + case 'location#changed': + try { + _mapEventStreamController.add(LocationChangedEvent( + mapId, AMapLocation.fromMap(call.arguments['location'])!)); + } catch (e) { + print("location#changed error=======>" + e.toString()); + } + break; + + case 'camera#onMove': + try { + _mapEventStreamController.add(CameraPositionMoveEvent( + mapId, CameraPosition.fromMap(call.arguments['position'])!)); + } catch (e) { + print("camera#onMove error===>" + e.toString()); + } + break; + case 'camera#onMoveEnd': + try { + _mapEventStreamController.add(CameraPositionMoveEndEvent( + mapId, CameraPosition.fromMap(call.arguments['position'])!)); + } catch (e) { + print("camera#onMoveEnd error===>" + e.toString()); + } + break; + case 'map#onTap': + _mapEventStreamController + .add(MapTapEvent(mapId, LatLng.fromJson(call.arguments['latLng'])!)); + break; + case 'map#onLongPress': + _mapEventStreamController.add(MapLongPressEvent( + mapId, LatLng.fromJson(call.arguments['latLng'])!)); + break; + + case 'marker#onTap': + _mapEventStreamController.add(MarkerTapEvent( + mapId, + call.arguments['markerId'], + )); + break; + case 'marker#onDragEnd': + _mapEventStreamController.add(MarkerDragEndEvent( + mapId, + LatLng.fromJson(call.arguments['position'])!, + call.arguments['markerId'])); + break; + case 'polyline#onTap': + _mapEventStreamController + .add(PolylineTapEvent(mapId, call.arguments['polylineId'])); + break; + case 'map#onPoiTouched': + try { + _mapEventStreamController.add( + MapPoiTouchEvent(mapId, AMapPoi.fromJson(call.arguments['poi'])!)); + } catch (e) { + print('map#onPoiTouched error===>' + e.toString()); + } + break; + } + } + + ///移动镜头到一个新的位置 + Future moveCamera( + CameraUpdate cameraUpdate, { + required int mapId, + bool animated = true, + int duration = 0, + }) { + return channel(mapId).invokeMethod('camera#move', { + 'cameraUpdate': cameraUpdate.toJson(), + 'animated': animated, + 'duration': duration + }); + } + + ///设置地图每秒渲染的帧数 + Future setRenderFps(int fps, {required int mapId}) { + return channel(mapId) + .invokeMethod('map#setRenderFps', { + 'fps': fps, + }); + } + + ///截屏 + Future takeSnapshot({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#takeSnapshot'); + } + + //获取地图审图号(普通地图) + Future getMapContentApprovalNumber({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#contentApprovalNumber'); + } + + //获取地图审图号(卫星地图) + Future getSatelliteImageApprovalNumber({ + required int mapId, + }) { + return channel(mapId) + .invokeMethod('map#satelliteImageApprovalNumber'); + } + + Future clearDisk({ + required int mapId, + }) { + return channel(mapId).invokeMethod('map#clearDisk'); + } +} diff --git a/local_plugins/amap_service/lib/types/base_overlay.dart b/local_plugins/amap_service/lib/types/base_overlay.dart new file mode 100644 index 000000000..f8dbf47f2 --- /dev/null +++ b/local_plugins/amap_service/lib/types/base_overlay.dart @@ -0,0 +1,29 @@ +/// 地图覆盖物基类 +class BaseOverlay { + /// overlay id + late String _id; + + String get id => _id; + + BaseOverlay() { + this._id = this.hashCode.toString(); + } + + void setIdForCopy(String copyId) => _id = copyId; + + BaseOverlay clone() { + throw UnimplementedError( + 'BaseOverlay subClass should implement this methed.'); + } + + Map toMap() { + throw UnimplementedError( + 'BaseOverlay subClass should implement this methed.'); + } +} + +List>? serializeOverlaySet(Set overlays) { + return overlays + .map>((BaseOverlay overlay) => overlay.toMap()) + .toList(); +} diff --git a/local_plugins/amap_service/lib/types/bitmap.dart b/local_plugins/amap_service/lib/types/bitmap.dart new file mode 100644 index 000000000..47e2579c4 --- /dev/null +++ b/local_plugins/amap_service/lib/types/bitmap.dart @@ -0,0 +1,140 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async' show Future; +import 'dart:typed_data' show Uint8List; +import 'dart:ui'; + +import 'package:amap_flutter_base/amap_flutter_base.dart' show AMapUtil; +import 'package:flutter/material.dart' + show ImageConfiguration, AssetImage, AssetBundleImageKey; +import 'package:flutter/services.dart' show AssetBundle; + +import 'package:flutter/foundation.dart' show kIsWeb; + +/// Bitmap工具类 +class BitmapDescriptor { + const BitmapDescriptor._(this._json); + + /// 红色. + static const double hueRed = 0.0; + + /// 橙色. + static const double hueOrange = 30.0; + + /// 黄色. + static const double hueYellow = 60.0; + + /// 绿色. + static const double hueGreen = 120.0; + + /// 青色. + static const double hueCyan = 180.0; + + /// 天蓝色. + static const double hueAzure = 210.0; + + /// 蓝色. + static const double hueBlue = 240.0; + + /// 紫色. + static const double hueViolet = 270.0; + + /// 酒红色. + static const double hueMagenta = 300.0; + + /// 玫瑰红. + static const double hueRose = 330.0; + + /// 创建默认的marker 图标的 bitmap 描述信息对象. + static const BitmapDescriptor defaultMarker = + BitmapDescriptor._(['defaultMarker']); + + /// 创建引用默认着色的BitmapDescriptor + static BitmapDescriptor defaultMarkerWithHue(double hue) { + assert(0.0 <= hue && hue < 360.0); + String filename = "BLUE.png"; + if (hue == hueRed) { + filename = "RED.png"; + } else if (hue == hueOrange) { + filename = "ORANGE.png"; + } else if (hue == hueYellow) { + filename = "YELLOW.png"; + } else if (hue == hueGreen) { + filename = "GREEN.png"; + } else if (hue == hueCyan) { + filename = "CYAN.png"; + } else if (hue == hueAzure) { + filename = "AZURE.png"; + } else if (hue == hueBlue) { + filename = "BLUE.png"; + } else if (hue == hueViolet) { + filename = "VIOLET.png"; + } else if (hue == hueMagenta) { + filename = "MAGENTA.png"; + } else if (hue == hueRose) { + filename = "ROSE.png"; + } + return BitmapDescriptor._([ + 'fromAssetImage', + "packages/amap_flutter_map/res/$filename", + AMapUtil.devicePixelRatio + ]); + } + + ///根据输入的icon路径[iconPath]创建[BitmapDescriptor] + static BitmapDescriptor fromIconPath(String iconPath) { + return BitmapDescriptor._([ + 'fromAsset', + iconPath, + ]); + } + + ///从资源图像创建[BitmapDescriptor]。 + /// + ///Flutter中的assert的资产图像按以下方式存储: + /// https://flutter.dev/docs/development/ui/assets and images声明-分辨率感知图像资源 + ///该方法考虑了各种资产解决方案 + ///并根据dpi将图像缩放到正确的分辨率。 + ///将`mipmaps1设置为false可加载图像的精确dpi版本,默认情况下,`mipmap`为true。 + static Future fromAssetImage( + ImageConfiguration configuration, + String assetName, { + AssetBundle? bundle, + String? package, + bool mipmaps = true, + }) async { + if (!mipmaps && configuration.devicePixelRatio != null) { + return BitmapDescriptor._([ + 'fromAssetImage', + assetName, + configuration.devicePixelRatio, + ]); + } + final AssetImage assetImage = + AssetImage(assetName, package: package, bundle: bundle); + final AssetBundleImageKey assetBundleImageKey = + await assetImage.obtainKey(configuration); + final Size? size = configuration.size; + return BitmapDescriptor._([ + 'fromAssetImage', + assetBundleImageKey.name, + assetBundleImageKey.scale, + if (kIsWeb && size != null) + [ + size.width, + size.height, + ], + ]); + } + + /// 根据将PNG图片转换后的二进制数据[byteData]创建BitmapDescriptor + static BitmapDescriptor fromBytes(Uint8List byteData) { + return BitmapDescriptor._(['fromBytes', byteData]); + } + + final dynamic _json; + + dynamic toMap() => _json; +} diff --git a/local_plugins/amap_service/lib/types/camera.dart b/local_plugins/amap_service/lib/types/camera.dart new file mode 100644 index 000000000..2a4b5bc3c --- /dev/null +++ b/local_plugins/amap_service/lib/types/camera.dart @@ -0,0 +1,150 @@ +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:flutter/cupertino.dart'; + +/// 相机位置,包含可视区域的位置参数。 +class CameraPosition { + /// 构造一个CameraPosition 对象 + /// + /// 如果[bearing], [target], [tilt], 或者 [zoom] 为null时会返回[AssertionError] + const CameraPosition({ + this.bearing = 0.0, + required this.target, + this.tilt = 0.0, + this.zoom = 10, + }); + + /// 可视区域指向的方向,以角度为单位,从正北向逆时针方向计算,从0 度到360 度。 + final double bearing; + + /// 目标位置的屏幕中心点经纬度坐标。 + final LatLng target; + + /// 目标可视区域的倾斜度,以角度为单位。范围从0到360度 + final double tilt; + + /// 目标可视区域的缩放级别 + final double zoom; + + /// 将[CameraPosition]装换成Map + /// + /// 主要在插件内部使用 + dynamic toMap() => { + 'bearing': bearing, + 'target': target.toJson(), + 'tilt': tilt, + 'zoom': zoom, + }; + + /// 从Map转换成[CameraPosition] + /// + /// 主要在插件内部使用 + static CameraPosition? fromMap(dynamic json) { + if (json == null || !(json is Map)) { + return null; + } + final LatLng? target = LatLng.fromJson(json['target']); + if (target == null) { + return null; + } + return CameraPosition( + bearing: json['bearing'], + target: target, + tilt: json['tilt'], + zoom: json['zoom'], + ); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraPosition typedOther = other; + return bearing == typedOther.bearing && + target == typedOther.target && + tilt == typedOther.tilt && + zoom == typedOther.zoom; + } + + @override + int get hashCode => Object.hash(bearing, target, tilt, zoom); + + @override + String toString() => + 'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; +} + +/// 描述地图状态将要发生的变化 +class CameraUpdate { + CameraUpdate._(this._json); + + ///返回根据新的[CameraPosition]移动后的[CameraUpdate]. + /// + ///主要用于改变地图的中心点、缩放级别、倾斜角、角度等信息 + static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { + return CameraUpdate._([ + 'newCameraPosition', + cameraPosition.toMap(), + ]); + } + + ///移动到一个新的位置点[latLng] + /// + ///主要用于改变地图的中心点 + static CameraUpdate newLatLng(LatLng latLng) { + return CameraUpdate._(['newLatLng', latLng.toJson()]); + } + + ///根据指定到摄像头显示范围[bounds]和边界值[padding]创建一个CameraUpdate对象 + /// + ///主要用于根据指定的显示范围[bounds]以最佳的视野显示地图 + static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { + return CameraUpdate._([ + 'newLatLngBounds', + bounds.toJson(), + padding, + ]); + } + + /// 根据指定的新的位置[latLng]和缩放级别[zoom]创建一个CameraUpdate对象 + /// + /// 主要用于同时改变中心点和缩放级别 + static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { + return CameraUpdate._(['newLatLngZoom', latLng.toJson(), zoom]); + } + + /// 按照指定到像素点[dx]和[dy]移动地图中心点 + /// + /// [dx]是水平移动的像素数。正值代表可视区域向右移动,负值代表可视区域向左移动 + /// + /// [dy]是垂直移动的像素数。正值代表可视区域向下移动,负值代表可视区域向上移动 + /// + /// 返回包含x,y方向上移动像素数的cameraUpdate对象。 + static CameraUpdate scrollBy(double dx, double dy) { + return CameraUpdate._(['scrollBy', dx, dy]); + } + + /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 + /// + ///主要用于放大地图缩放级别,在当前地图显示的级别基础上加1 + static CameraUpdate zoomIn() { + return CameraUpdate._(['zoomIn']); + } + + /// 创建一个在当前地图显示的级别基础上加1的CameraUpdate对象 + /// + /// 主要用于减少地图缩放级别,在当前地图显示的级别基础上减1 + static CameraUpdate zoomOut() { + return CameraUpdate._(['zoomOut']); + } + + /// 创建一个指定缩放级别[zoom]的CameraUpdate对象 + /// + /// 主要用于设置地图缩放级别 + static CameraUpdate zoomTo(double zoom) { + return CameraUpdate._(['zoomTo', zoom]); + } + + final dynamic _json; + + dynamic toJson() => _json; +} diff --git a/local_plugins/amap_service/lib/types/marker.dart b/local_plugins/amap_service/lib/types/marker.dart new file mode 100644 index 000000000..2c5190452 --- /dev/null +++ b/local_plugins/amap_service/lib/types/marker.dart @@ -0,0 +1,249 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show Offset; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; +import 'bitmap.dart'; + +/// Marker拖动回调 +typedef void MarkerDragEndCallback(String id, LatLng endPosition); + +///Marker的气泡 +/// +///Android和iOS的实现机制有差异,仅在接口层面拉齐,效果一致 +class InfoWindow { + /// 为 [Marker] 产生一个不可修改的文本气泡. + const InfoWindow({this.title, this.snippet}); + + /// 无文本的气泡 + static const InfoWindow noText = InfoWindow(); + + /// 气泡的title + final String? title; + + /// 气泡的详细信息 + final String? snippet; + + /// 气泡copy方法 + /// + InfoWindow copyWith({String? titleParam, String? snippetParam}) { + return InfoWindow( + title: titleParam ?? title, + snippet: snippetParam ?? snippet, + ); + } + + Map _toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('title', title); + addIfPresent('snippet', snippet); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! InfoWindow) { + return false; + } + final InfoWindow typedOther = other; + return title == typedOther.title && snippet == typedOther.snippet; + } + + @override + int get hashCode => Object.hash(title, snippet); + + @override + String toString() { + return 'InfoWindow{title: $title, snippet: $snippet}'; + } +} + +/// 点覆盖物的类 +class Marker extends BaseOverlay { + Marker({ + required this.position, + double alpha = 1.0, + Offset anchor = const Offset(0.5, 1.0), + this.clickable = true, + this.draggable = false, + this.icon = BitmapDescriptor.defaultMarker, + this.infoWindowEnable = true, + this.infoWindow = InfoWindow.noText, + this.rotation = 0.0, + this.visible = true, + this.zIndex = 0.0, + this.onTap, + this.onDragEnd, + }) : this.alpha = + // ignore: unnecessary_null_comparison + (alpha != null ? (alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)) : alpha), + // ignore: unnecessary_null_comparison + this.anchor = + (anchor == null + ? Offset(0.5, 1.0) + : ((anchor.dx < 0 || + anchor.dx > 1 || + anchor.dy < 0 || + anchor.dy > 1) + ? Offset(0.5, 1.0) + : anchor)), + super(); + + /// 透明度 + final double alpha; + + /// 覆盖物视图相对地图上的经纬度位置的锚点 + final Offset anchor; + + /// 是否可点击,默认为true + final bool clickable; + + /// 是否可拖拽,默认为false + final bool draggable; + + /// 覆盖物的图标 + final BitmapDescriptor icon; + + /// 是否显示气泡,如果为true,则点击[Marker]后,会显示该气泡[InfoWindow] + /// 如果为false,则始终不会显示该气泡 + final bool infoWindowEnable; + + /// 覆盖物上的气泡,当被点击时,如果infoWindowEnable为true,则会显示出来 + final InfoWindow infoWindow; + + /// 位置,不能为空 + final LatLng position; + + /// 旋转角度,以锚点为中心,顺时针旋转(单位:度数) + /// + /// 注意:iOS端目前仅支持绕marker中心点旋转 + final double rotation; + + /// 是否可见 + final bool visible; + + /// z轴的值,用于调整该覆盖物的相对绘制层级关系 + /// 值越小,图层越靠下,iOS该值不支持动态修改,仅能在初始化时指定 + final double zIndex; + + /// 回调的参数是对应的id + final ArgumentCallback? onTap; + + /// Marker被拖拽结束的回调 + final MarkerDragEndCallback? onDragEnd; + + /// copy的真正复制的参数,主要用于需要修改某个属性参数时使用 + Marker copyWith({ + double? alphaParam, + Offset? anchorParam, + bool? clickableParam, + bool? draggableParam, + BitmapDescriptor? iconParam, + bool? infoWindowEnableParam, + InfoWindow? infoWindowParam, + LatLng? positionParam, + double? rotationParam, + bool? visibleParam, + ArgumentCallback? onTapParam, + MarkerDragEndCallback? onDragEndParam, + }) { + Marker copyMark = Marker( + alpha: alphaParam ?? alpha, + anchor: anchorParam ?? anchor, + clickable: clickableParam ?? clickable, + draggable: draggableParam ?? draggable, + icon: iconParam ?? icon, + infoWindowEnable: infoWindowEnableParam ?? infoWindowEnable, + infoWindow: infoWindowParam ?? infoWindow, + position: positionParam ?? position, + rotation: rotationParam ?? rotation, + visible: visibleParam ?? visible, + zIndex: zIndex, + onTap: onTapParam ?? onTap, + onDragEnd: onDragEndParam ?? onDragEnd, + ); + copyMark.setIdForCopy(id); + return copyMark; + } + + Marker clone() => copyWith(); + + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + addIfPresent('alpha', alpha); + addIfPresent('anchor', _offsetToJson(anchor)); + addIfPresent('clickable', clickable); + addIfPresent('draggable', draggable); + addIfPresent('icon', icon.toMap()); + addIfPresent('infoWindowEnable', infoWindowEnable); + addIfPresent('infoWindow', infoWindow._toMap()); + addIfPresent('position', position.toJson()); + addIfPresent('rotation', rotation); + addIfPresent('visible', visible); + addIfPresent('zIndex', zIndex); + return json; + } + + dynamic _offsetToJson(Offset offset) { + return [offset.dx, offset.dy]; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! Marker) return false; + final Marker typedOther = other; + return id == typedOther.id && + alpha == typedOther.alpha && + anchor == typedOther.anchor && + clickable == typedOther.clickable && + draggable == typedOther.draggable && + icon == typedOther.icon && + infoWindowEnable == typedOther.infoWindowEnable && + infoWindow == typedOther.infoWindow && + position == typedOther.position && + rotation == typedOther.rotation && + visible == typedOther.visible && + zIndex == typedOther.zIndex; + } + + @override + int get hashCode => super.hashCode; + + @override + String toString() { + return 'Marker{id: $id, alpha: $alpha, anchor: $anchor, ' + 'clickable: $clickable, draggable: $draggable,' + 'icon: $icon, infoWindowEnable: $infoWindowEnable, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' + 'visible: $visible, zIndex: $zIndex, onTap: $onTap}'; + } +} + +Map keyByMarkerId(Iterable markers) { + return Map.fromEntries( + markers.map( + (Marker marker) => MapEntry(marker.id, marker.clone()), + ), + ); +} diff --git a/local_plugins/amap_service/lib/types/marker_updates.dart b/local_plugins/amap_service/lib/types/marker_updates.dart new file mode 100644 index 000000000..e69ad4b1c --- /dev/null +++ b/local_plugins/amap_service/lib/types/marker_updates.dart @@ -0,0 +1,105 @@ +import 'dart:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; + +import 'types.dart'; +import 'marker.dart'; + +/// 用以描述Marker的更新项 +class MarkerUpdates { + /// 根据之前的marker列表[previous]和当前的marker列表[current]创建[MakerUpdates]. + MarkerUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousMarkers = keyByMarkerId(previous); + final Map currentMarkers = keyByMarkerId(current); + + final Set prevMarkerIds = previousMarkers.keys.toSet(); + final Set currentMarkerIds = currentMarkers.keys.toSet(); + + Marker idToCurrentMarker(String id) { + return currentMarkers[id]!; + } + + final Set _markerIdsToRemove = prevMarkerIds.difference( + currentMarkerIds, + ); + + final Set _markersToAdd = + currentMarkerIds + .difference(prevMarkerIds) + .map(idToCurrentMarker) + .toSet(); + + bool hasChanged(Marker current) { + final Marker? previous = previousMarkers[current.id]; + return current != previous; + } + + final Set _markersToChange = + currentMarkerIds + .intersection(prevMarkerIds) + .map(idToCurrentMarker) + .where(hasChanged) + .toSet(); + + markersToAdd = _markersToAdd; + markerIdsToRemove = _markerIdsToRemove; + markersToChange = _markersToChange; + } + + /// 想要添加的marker集合. + Set? markersToAdd; + + /// 想要删除的marker的id集合 + Set? markerIdsToRemove; + + /// 想要更新的marker集合. + Set? markersToChange; + + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('markersToAdd', serializeOverlaySet(markersToAdd!)); + addIfNonNull('markersToChange', serializeOverlaySet(markersToChange!)); + addIfNonNull('markerIdsToRemove', markerIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! MarkerUpdates) return false; + final MarkerUpdates typedOther = other; + return setEquals(markersToAdd, typedOther.markersToAdd) && + setEquals(markerIdsToRemove, typedOther.markerIdsToRemove) && + setEquals(markersToChange, typedOther.markersToChange); + } + + @override + int get hashCode => + Object.hash(markersToAdd, markerIdsToRemove, markersToChange); + + @override + String toString() { + return '_MarkerUpdates{markersToAdd: $markersToAdd, ' + 'markerIdsToRemove: $markerIdsToRemove, ' + 'markersToChange: $markersToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/polygon.dart b/local_plugins/amap_service/lib/types/polygon.dart new file mode 100644 index 000000000..a780cfe98 --- /dev/null +++ b/local_plugins/amap_service/lib/types/polygon.dart @@ -0,0 +1,120 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show listEquals; +import 'package:flutter/material.dart' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; +import 'polyline.dart'; + +/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 +class Polygon extends BaseOverlay { + /// 默认构造函数 + Polygon( + {required this.points, + double strokeWidth = 10, + this.strokeColor = const Color(0xCC00BFFF), + this.fillColor = const Color(0xC487CEFA), + this.visible = true, + this.joinType = JoinType.bevel}) + : assert(points.length > 0), + this.strokeWidth = (strokeWidth <= 0 ? 10 : strokeWidth), + super(); + + /// 覆盖物的坐标点数组,不能为空 + final List points; + + /// 边框宽度,单位为逻辑像素,同Android中的dp,iOS中的point + final double strokeWidth; + + /// 边框颜色,默认值为(0xCCC4E0F0) + final Color strokeColor; + + /// 填充颜色,默认值为(0xC4E0F0CC) + final Color fillColor; + + /// 是否可见 + final bool visible; + + /// 连接点类型,该参数不支持copy时修改,仅能在初始化时设置一次 + final JoinType joinType; + + /// 实际copy函数 + Polygon copyWith({ + List? pointsParam, + double? strokeWidthParam, + Color? strokeColorParam, + Color? fillColorParam, + bool? visibleParam, + }) { + Polygon copyPolyline = Polygon( + points: pointsParam ?? points, + strokeWidth: strokeWidthParam ?? strokeWidth, + strokeColor: strokeColorParam ?? strokeColor, + fillColor: fillColorParam ?? fillColor, + visible: visibleParam ?? visible, + joinType: joinType, + ); + copyPolyline.setIdForCopy(id); + return copyPolyline; + } + + Polygon clone() => copyWith(); + + /// 转换成可以序列化的map + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + json['points'] = _pointsToJson(); + addIfPresent('strokeWidth', strokeWidth); + addIfPresent('strokeColor', strokeColor.value); + addIfPresent('fillColor', fillColor.value); + addIfPresent('visible', visible); + addIfPresent('joinType', joinType.index); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is !Polygon) return false; + final Polygon typedOther = other; + return id == typedOther.id && + listEquals(points, typedOther.points) && + strokeWidth == typedOther.strokeWidth && + strokeColor == typedOther.strokeColor && + fillColor == typedOther.fillColor && + visible == typedOther.visible && + joinType == typedOther.joinType; + } + + @override + int get hashCode => super.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } +} + +Map keyByPolygonId(Iterable polylines) { + // ignore: unnecessary_null_comparison + if (polylines == null) { + return {}; + } + return Map.fromEntries(polylines.map((Polygon polyline) => + MapEntry(polyline.id, polyline.clone()))); +} diff --git a/local_plugins/amap_service/lib/types/polygon_updates.dart b/local_plugins/amap_service/lib/types/polygon_updates.dart new file mode 100644 index 000000000..0584b0d9a --- /dev/null +++ b/local_plugins/amap_service/lib/types/polygon_updates.dart @@ -0,0 +1,108 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show hashValues; + +import 'package:flutter/foundation.dart' show setEquals; +import 'types.dart'; + +/// 该类主要用以描述[Polygon]的增删改等更新操作 +class PolygonUpdates { + /// 通过Polygon的前后更新集合构造一个PolygonUpdates + PolygonUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousPolygons = keyByPolygonId(previous); + final Map currentPolygons = keyByPolygonId(current); + + final Set prevPolygonIds = previousPolygons.keys.toSet(); + final Set currentPolygonIds = currentPolygons.keys.toSet(); + + Polygon idToCurrentPolygon(String id) { + return currentPolygons[id]!; + } + + final Set _polygonIdsToRemove = prevPolygonIds.difference( + currentPolygonIds, + ); + + final Set _polygonsToAdd = + currentPolygonIds + .difference(prevPolygonIds) + .map(idToCurrentPolygon) + .toSet(); + + bool hasChanged(Polygon current) { + final Polygon previous = previousPolygons[current.id]!; + return current != previous; + } + + final Set _polygonsToChange = + currentPolygonIds + .intersection(prevPolygonIds) + .map(idToCurrentPolygon) + .where(hasChanged) + .toSet(); + + polygonsToAdd = _polygonsToAdd; + polygonIdsToRemove = _polygonIdsToRemove; + polygonsToChange = _polygonsToChange; + } + + /// 想要添加的polygon对象集合. + Set? polygonsToAdd; + + /// 想要删除的polygon的id集合 + Set? polygonIdsToRemove; + + /// 想要更新的polygon对象集合 + Set? polygonsToChange; + + /// 转换成可以序列化的map + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polygonsToAdd', serializeOverlaySet(polygonsToAdd!)); + addIfNonNull('polygonsToChange', serializeOverlaySet(polygonsToChange!)); + addIfNonNull('polygonIdsToRemove', polygonIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! PolygonUpdates) return false; + final PolygonUpdates typedOther = other; + return setEquals(polygonsToAdd, typedOther.polygonsToAdd) && + setEquals(polygonIdsToRemove, typedOther.polygonIdsToRemove) && + setEquals(polygonsToChange, typedOther.polygonsToChange); + } + + @override + int get hashCode => + Object.hash(polygonsToAdd, polygonIdsToRemove, polygonsToChange); + + @override + String toString() { + return '_PolygonUpdates{polygonsToAdd: $polygonsToAdd, ' + 'polygonIdsToRemove: $polygonIdsToRemove, ' + 'polygonsToChange: $polygonsToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/polyline.dart b/local_plugins/amap_service/lib/types/polyline.dart new file mode 100644 index 000000000..58506ec1a --- /dev/null +++ b/local_plugins/amap_service/lib/types/polyline.dart @@ -0,0 +1,198 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:amap_flutter_map/src/types/bitmap.dart'; +import 'package:flutter/foundation.dart' show listEquals; +import 'package:flutter/material.dart' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'base_overlay.dart'; + +/// 虚线类型 +enum DashLineType { + /// 不画虚线 + none, + + /// 方块样式 + square, + + /// 圆点样式 + circle, +} + +/// 线头类型 +enum CapType { + /// 普通头 + butt, + + /// 扩展头 + square, + + /// 箭头 + arrow, + + /// 圆形头 + round, +} + +/// 连接点类型 +enum JoinType { + /// 斜面连接点 + bevel, + + /// 斜接连接点 + miter, + + /// 圆角连接点 + round, +} + +/// 线相关的覆盖物类,内部的属性,描述了覆盖物的纹理、颜色、线宽等特征 +class Polyline extends BaseOverlay { + /// 默认构造函数 + Polyline({ + required this.points, + double width = 10, + this.visible = true, + this.geodesic = false, + double alpha = 1.0, + this.dashLineType = DashLineType.none, + this.capType = CapType.butt, + this.joinType = JoinType.bevel, + this.customTexture, + this.onTap, + this.color = const Color(0xCCC4E0F0), + }) : assert(points.length > 0), + this.width = (width <= 0 ? 10 : width), + this.alpha =(alpha < 0 ? 0 : (alpha > 1 ? 1 : alpha)), + super(); + + /// 覆盖物的坐标点数组,points不能为空 + final List points; + + /// 线宽,单位为逻辑像素,同Android中的dp,iOS中的point + final double width; + + /// 是否可见 + final bool visible; + + /// 透明度 + final double alpha; + + /// 覆盖物颜色,默认值为(0xCCC4E0F0). + final Color color; + + /// 自定义纹理图片,注意: 如果设置了自定义纹理图片,则color的设置将无效; + final BitmapDescriptor? customTexture; + + /// 是否为大地曲线 + final bool geodesic; + + /// 虚线类型 + final DashLineType dashLineType; + + /// 连接点类型 + final JoinType joinType; + + /// 线头类型 + final CapType capType; + + /// 点击回调(回调参数为id) + final ArgumentCallback? onTap; + + /// 实际copy函数 + Polyline copyWith({ + List? pointsParam, + double? widthParam, + int? zIndexParam, + bool? visibleParam, + double? alphaParam, + DashLineType? dashLineTypeParam, + CapType? capTypeParam, + JoinType? joinTypeParam, + BitmapDescriptor? customTextureParam, + ArgumentCallback? onTapParam, + Color? colorParam, + }) { + Polyline copyPolyline = Polyline( + points: pointsParam ?? points, + width: widthParam ?? width, + visible: visibleParam ?? visible, + geodesic: geodesic, + alpha: alphaParam ?? alpha, + dashLineType: dashLineTypeParam ?? dashLineType, + capType: capTypeParam ?? capType, + joinType: joinTypeParam ?? joinType, + customTexture: customTextureParam ?? customTexture, + onTap: onTapParam ?? onTap, + color: colorParam ?? color, + ); + copyPolyline.setIdForCopy(id); + return copyPolyline; + } + + Polyline clone() => copyWith(); + + /// 将对象转换为可序列化的map. + @override + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('id', id); + json['points'] = _pointsToJson(); + addIfPresent('width', width); + addIfPresent('visible', visible); + addIfPresent('geodesic', geodesic); + addIfPresent('alpha', alpha); + addIfPresent('dashLineType', dashLineType.index); + addIfPresent('capType', capType.index); + addIfPresent('joinType', joinType.index); + addIfPresent('customTexture', customTexture?.toMap()); + addIfPresent('color', color.value); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is !Polyline) return false; + final Polyline typedOther = other; + return id == typedOther.id && + listEquals(points, typedOther.points) && + width == typedOther.width && + visible == typedOther.visible && + geodesic == typedOther.geodesic && + alpha == typedOther.alpha && + dashLineType == typedOther.dashLineType && + capType == typedOther.capType && + joinType == typedOther.joinType && + color == typedOther.color; + } + + @override + int get hashCode => super.hashCode; + + dynamic _pointsToJson() { + final List result = []; + for (final LatLng point in points) { + result.add(point.toJson()); + } + return result; + } +} + +Map keyByPolylineId(Iterable polylines) { + // ignore: unnecessary_null_comparison + if (polylines == null) { + return {}; + } + return Map.fromEntries(polylines.map((Polyline polyline) => + MapEntry(polyline.id, polyline.clone()))); +} diff --git a/local_plugins/amap_service/lib/types/polyline_updates.dart b/local_plugins/amap_service/lib/types/polyline_updates.dart new file mode 100644 index 000000000..29d485d15 --- /dev/null +++ b/local_plugins/amap_service/lib/types/polyline_updates.dart @@ -0,0 +1,108 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show setEquals; + +import 'polyline.dart'; +import 'types.dart'; + +/// 该类主要用以描述[Polyline]的增删改等更新操作 +class PolylineUpdates { + /// 通过polyline的前后更新集合构造一个polylineUpdates + PolylineUpdates.from(Set previous, Set current) { + // ignore: unnecessary_null_comparison + if (previous == null) { + previous = Set.identity(); + } + + // ignore: unnecessary_null_comparison + if (current == null) { + current = Set.identity(); + } + + final Map previousPolylines = keyByPolylineId(previous); + final Map currentPolylines = keyByPolylineId(current); + + final Set prevPolylineIds = previousPolylines.keys.toSet(); + final Set currentPolylineIds = currentPolylines.keys.toSet(); + + Polyline idToCurrentPolyline(String id) { + return currentPolylines[id]!; + } + + final Set _polylineIdsToRemove = prevPolylineIds.difference( + currentPolylineIds, + ); + + final Set _polylinesToAdd = + currentPolylineIds + .difference(prevPolylineIds) + .map(idToCurrentPolyline) + .toSet(); + + bool hasChanged(Polyline current) { + final Polyline previous = previousPolylines[current.id]!; + return current != previous; + } + + final Set _polylinesToChange = + currentPolylineIds + .intersection(prevPolylineIds) + .map(idToCurrentPolyline) + .where(hasChanged) + .toSet(); + + polylinesToAdd = _polylinesToAdd; + polylineIdsToRemove = _polylineIdsToRemove; + polylinesToChange = _polylinesToChange; + } + + /// 用于添加polyline的集合 + Set? polylinesToAdd; + + /// 需要删除的plyline的id集合 + Set? polylineIdsToRemove; + + /// 用于更新polyline的集合 + Set? polylinesToChange; + + /// 将对象装换为可序列化的对象 + Map toMap() { + final Map updateMap = {}; + + void addIfNonNull(String fieldName, dynamic value) { + if (value != null) { + updateMap[fieldName] = value; + } + } + + addIfNonNull('polylinesToAdd', serializeOverlaySet(polylinesToAdd!)); + addIfNonNull('polylinesToChange', serializeOverlaySet(polylinesToChange!)); + addIfNonNull('polylineIdsToRemove', polylineIdsToRemove?.toList()); + + return updateMap; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + if (other is! PolylineUpdates) return false; + final PolylineUpdates typedOther = other; + return setEquals(polylinesToAdd, typedOther.polylinesToAdd) && + setEquals(polylineIdsToRemove, typedOther.polylineIdsToRemove) && + setEquals(polylinesToChange, typedOther.polylinesToChange); + } + + @override + int get hashCode => + Object.hash(polylinesToAdd, polylineIdsToRemove, polylinesToChange); + + @override + String toString() { + return '_PolylineUpdates{polylinesToAdd: $polylinesToAdd, ' + 'polylineIdsToRemove: $polylineIdsToRemove, ' + 'polylinesToChange: $polylinesToChange}'; + } +} diff --git a/local_plugins/amap_service/lib/types/types.dart b/local_plugins/amap_service/lib/types/types.dart new file mode 100644 index 000000000..e62300549 --- /dev/null +++ b/local_plugins/amap_service/lib/types/types.dart @@ -0,0 +1,11 @@ +export 'camera.dart'; +export 'ui.dart'; +export 'base_overlay.dart'; +export 'marker.dart'; +export 'marker_updates.dart'; +export 'polyline.dart'; +export 'polyline_updates.dart'; +export 'polygon.dart'; +export 'polygon_updates.dart'; +export 'camera.dart'; +export 'bitmap.dart'; diff --git a/local_plugins/amap_service/lib/types/ui.dart b/local_plugins/amap_service/lib/types/ui.dart new file mode 100644 index 000000000..e33409197 --- /dev/null +++ b/local_plugins/amap_service/lib/types/ui.dart @@ -0,0 +1,257 @@ +import 'dart:typed_data'; +import 'dart:ui' show Color; +import 'package:amap_flutter_base/amap_flutter_base.dart'; +import 'package:amap_flutter_map/amap_flutter_map.dart'; + +/// 地图类型 +enum MapType { + /// 普通地图 + normal, + + /// 卫星地图 + satellite, + + /// 夜间视图 + night, + + /// 导航视图 + navi, + + /// 公交视图 + bus, +} + +// 设置摄像机的边界. +class CameraTargetBounds { + /// 使用指定的边界框或空值创建摄影机目标边界 + /// + /// 设置为null时代表不指定边界 + const CameraTargetBounds(this.bounds); + + /// 摄像机的边界. + /// + /// null代表不指定边界 + final LatLngBounds? bounds; + + /// 取消指定边界 + static const CameraTargetBounds unbounded = CameraTargetBounds(null); + + /// 转换成json对象 + dynamic toJson() => [bounds?.toJson()]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final CameraTargetBounds typedOther = other; + return bounds == typedOther.bounds; + } + + @override + int get hashCode => bounds.hashCode; + + @override + String toString() { + return 'CameraTargetBounds(bounds: $bounds)'; + } +} + +/// 地图最大最小缩放级别的封装对象 +class MinMaxZoomPreference { + /// 为地图创建一个不可变的最大最小缩放范围 + /// + /// 缩放级别范围为[3, 20],超出范围取边界值 + /// + const MinMaxZoomPreference(double minZoom, double maxZoom) + : this.minZoom = + ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) + ? maxZoom + : minZoom), + this.maxZoom = + ((minZoom < 3 ? 3 : minZoom) > (maxZoom > 20 ? 20 : maxZoom) + ? minZoom + : maxZoom); + + /// 最小zoomLevel + final double? minZoom; + + /// 最大zoomLevel + final double? maxZoom; + + /// 高德地图默认zoomLevel的范围. + static const MinMaxZoomPreference defaultPreference = MinMaxZoomPreference( + 3, + 20, + ); + + /// JSON序列化. + dynamic toJson() => [minZoom, maxZoom]; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + final MinMaxZoomPreference typedOther = other; + return minZoom == typedOther.minZoom && maxZoom == typedOther.maxZoom; + } + + @override + int get hashCode => Object.hash(minZoom, maxZoom); + + @override + String toString() { + return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; + } +} + +///定位小蓝点配置项 +class MyLocationStyleOptions { + ///是否显示定位小蓝点 + bool enabled; + + ///精度圈填充色 + Color? circleFillColor; + + ///精度圈边框色 + Color? circleStrokeColor; + + ///精度圈边框宽度 + double? circleStrokeWidth; + + ///小蓝点图标 + BitmapDescriptor? icon; + + MyLocationStyleOptions( + this.enabled, { + this.circleFillColor, + this.circleStrokeColor, + this.circleStrokeWidth, + this.icon, + }); + + MyLocationStyleOptions clone() { + return MyLocationStyleOptions( + enabled, + circleFillColor: circleFillColor, + circleStrokeColor: circleStrokeColor, + circleStrokeWidth: circleStrokeWidth, + icon: icon, + ); + } + + static MyLocationStyleOptions? fromMap(dynamic json) { + if (null == json) { + return null; + } + return MyLocationStyleOptions( + json['enabled'] ?? false, + circleFillColor: json['circleFillColor'] ?? null, + circleStrokeColor: json['circleStrokeColor'] ?? null, + circleStrokeWidth: json['circleStrokeWidth'] ?? null, + icon: json['icon'] ?? null, + ); + } + + Map toMap() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('enabled', enabled); + addIfPresent('circleFillColor', circleFillColor?.value); + addIfPresent('circleStrokeColor', circleStrokeColor?.value); + addIfPresent('circleStrokeWidth', circleStrokeWidth); + addIfPresent('icon', icon?.toMap()); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + if (other is! MyLocationStyleOptions) return false; + final MyLocationStyleOptions typedOther = other; + return enabled == typedOther.enabled && + circleFillColor == typedOther.circleFillColor && + circleStrokeColor == typedOther.circleStrokeColor && + icon == typedOther.icon; + } + + @override + String toString() { + return 'MyLocationOptionsStyle{' + 'enabled: $enabled,' + 'circleFillColor: $circleFillColor,' + 'circleStrokeColor: $circleStrokeColor,' + 'icon: $icon, }'; + } + + @override + int get hashCode => + Object.hash(enabled, circleFillColor, circleStrokeColor, icon); +} + +///地图自定义样式 +class CustomStyleOptions { + ///开关项,是否开启自定义地图 + bool enabled; + + ///自定义样式的二进制数据,对应下载的自定义地图文件中的style.data中的二进制数据 + Uint8List? styleData; + + ///自定义扩展样式的二进制数据,对应下载的自定义地图文件中的style_extra.data中的二进制数据 + Uint8List? styleExtraData; + + CustomStyleOptions(this.enabled, {this.styleData, this.styleExtraData}); + + static CustomStyleOptions? fromMap(dynamic json) { + if (json == null) { + return null; + } + return CustomStyleOptions( + json['enabled'] ?? false, + styleData: json['styleData'] ?? null, + styleExtraData: json['styleExtraData'] ?? null, + ); + } + + dynamic toMap() { + final Map json = {}; + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('enabled', enabled); + addIfPresent('styleData', styleData); + addIfPresent('styleExtraData', styleExtraData); + return json; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (runtimeType != other.runtimeType) return false; + if (other is! CustomStyleOptions) return false; + final CustomStyleOptions typedOther = other; + return enabled == typedOther.enabled && + styleData == typedOther.styleData && + styleExtraData == typedOther.styleExtraData; + } + + @override + int get hashCode => Object.hash(enabled, styleData, styleExtraData); + + CustomStyleOptions clone() { + return CustomStyleOptions( + enabled, + styleData: styleData, + styleExtraData: styleExtraData, + ); + } +} diff --git a/local_plugins/amap_service/pubspec.yaml b/local_plugins/amap_service/pubspec.yaml index 5cb50340b..f7999f100 100644 --- a/local_plugins/amap_service/pubspec.yaml +++ b/local_plugins/amap_service/pubspec.yaml @@ -16,6 +16,10 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 + amap_flutter_base: ^3.0.0 + amap_flutter_map: ^3.0.0 + deepvoice_utils: + path: ../deepvoice_utils # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 48bc9bac10dbdc56e568879768292aa37ce50fe6..e43ebb023b9841b545f849e66b8205d102fc46e3 100644 GIT binary patch literal 17 TcmZQ}Dp$CADo{Lz0Rr>^DKZ0Z literal 17 TcmZQ}Dp$CADo{Lz0Rr>@DKP_X diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties index d3c4e6543..8aeb8c861 100644 --- a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties +++ b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties @@ -1,2 +1,2 @@ -#Sun May 11 13:39:53 IST 2025 -gradle.version=8.10 +#Tue May 20 09:53:38 CST 2025 +gradle.version=8.9