diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/OsmFactory.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/OsmFactory.kt index 5d410f14..f105e624 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/OsmFactory.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/OsmFactory.kt @@ -64,7 +64,7 @@ open class OsmFactory( viewId, provider, keyUUID, - customTile = customTile as CustomTile, + customTile = customTile as CustomTile?, isEnabledRotationGesture = enableRotationGesture, isStaticMap = staticMap ) diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterMapLibreView.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterMapLibreView.kt index 5d32f979..b912b510 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterMapLibreView.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterMapLibreView.kt @@ -12,7 +12,11 @@ import android.widget.FrameLayout import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import hamza.dali.flutter_osm_plugin.ProviderLifecycle +import hamza.dali.flutter_osm_plugin.models.Anchor import hamza.dali.flutter_osm_plugin.models.FlutterGeoPoint +import hamza.dali.flutter_osm_plugin.models.FlutterMapLibreOSMRoad +import hamza.dali.flutter_osm_plugin.models.FlutterMarker +import hamza.dali.flutter_osm_plugin.models.FlutterRoad import hamza.dali.flutter_osm_plugin.models.MapMethodChannelCall import hamza.dali.flutter_osm_plugin.models.OSMTile import hamza.dali.flutter_osm_plugin.models.OnClickSymbols @@ -29,6 +33,7 @@ import hamza.dali.flutter_osm_plugin.models.where import hamza.dali.flutter_osm_plugin.utilities.toBitmap import hamza.dali.flutter_osm_plugin.utilities.toGeoPoint import hamza.dali.flutter_osm_plugin.utilities.toHashMap +import hamza.dali.flutter_osm_plugin.utilities.toPolyline import org.maplibre.android.plugins.annotation.* import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding.OnSaveInstanceStateListener import io.flutter.plugin.common.BinaryMessenger @@ -37,6 +42,7 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.platform.PlatformView +import kotlinx.coroutines.launch import org.maplibre.android.MapLibre import org.maplibre.android.camera.CameraPosition import org.maplibre.android.camera.CameraUpdate @@ -44,8 +50,10 @@ import org.maplibre.android.maps.MapLibreMap import org.maplibre.android.maps.MapView import org.maplibre.android.maps.Style import org.osmdroid.api.IGeoPoint +import org.osmdroid.bonuspack.utils.PolylineEncoder import org.osmdroid.util.BoundingBox import org.osmdroid.util.GeoPoint +import java.util.ArrayList class FlutterMapLibreView( @@ -73,8 +81,9 @@ class FlutterMapLibreView( private var mapClick: OnClickSymbols? = null private var mapMove: OnMapMove? = null private var userLocationChanged: OnClickSymbols? = null - + private val roads: MutableList = mutableListOf() private var zoomStep = 1.0 + private var initZoom = 3.0 private var mainLinearLayout: FrameLayout = FrameLayout(context).apply { this.layoutParams = @@ -102,7 +111,13 @@ class FlutterMapLibreView( methodChannel = MethodChannel(binaryMessenger, "plugins.dali.hamza/osmview_$idView") methodChannel.setMethodCallHandler(this) - init(OSMInitConfiguration(GeoPoint(0.0, 0.0))) + init( + OSMInitConfiguration( + GeoPoint(0.0, 0.0), + customTile = customTile, + initZoom = 3.0 + ) + ) mapView?.onCreate(null) } @@ -138,7 +153,7 @@ class FlutterMapLibreView( mapView?.getMapAsync { val args = call.arguments!! as HashMap<*, *> val geoPoint = GeoPoint(args["lat"]!! as Double, args["lon"]!! as Double) - moveTo(geoPoint, true) + moveTo(geoPoint, initZoom, true) methodChannel.invokeMethod("map#init", true) result.success(200) } @@ -154,7 +169,12 @@ class FlutterMapLibreView( } MapMethodChannelCall.LocationMarkers -> { + val args = call.arguments!! as HashMap<*, *> // update user marker and direction marker + val personIcon = (args["personIcon"] as ByteArray) + val arrowIcon = (args["arrowDirectionIcon"] as ByteArray) + customPersonMarkerIcon = personIcon.toBitmap() + customArrowMarkerIcon = arrowIcon.toBitmap() result.success(200) } @@ -175,6 +195,48 @@ class FlutterMapLibreView( } MapMethodChannelCall.ChangeMarker -> { + val args = call.arguments as HashMap<*, *> + val oldLocation = (args["old_location"] as HashMap).toGeoPoint() + val newLocation = (args["new_location"] as HashMap).toGeoPoint() + val oldSymbol = + markerManager?.annotations?.where { it.latLng.toGeoPoint() == oldLocation } + val oldIconKey = oldSymbol?.iconImage + val angle = when (args.containsKey("angle") && args["angle"] != null) { + true -> args["angle"] as Double + else -> oldSymbol?.iconRotate?.toDouble() ?: 0.0 + } + val anchor = when (args.containsKey("iconAnchor")) { + true -> + Anchor(args["iconAnchor"] as HashMap) + + else -> + Anchor(0.5f, 0.5f, "center") + } + val icon = when (args.containsKey("new_icon")) { + true -> args["new_icon"] as ByteArray + else -> null + }.let { byteArray -> + val bitmap = byteArray?.toBitmap() + bitmap + } + val factorSize = when (args.containsKey("new_factorSize")) { + true -> args["new_factorSize"] as Double + else -> 48.0 + } + markerManager?.delete(oldSymbol) + addMarker( + newLocation, MarkerConfiguration( + markerIcon = icon ?: when { + oldIconKey != null -> mapLibre?.style?.getImage(oldIconKey) + ?: customMarkerIcon!! + + else -> customMarkerIcon!! + }, + markerRotate = angle, + markerAnchor = anchor, + factorSize = factorSize + ) + ) result.success(200) } @@ -182,6 +244,14 @@ class FlutterMapLibreView( result.success(200) } + MapMethodChannelCall.DrawRoad -> { + val roadConfig = OSMRoadConfiguration.fromArgs(call.arguments as HashMap<*, *>) + val id = drawPolyline(roadConfig, true) + result.success(mapOf( + "id" to id + )) + } + MapMethodChannelCall.ClearRoads -> { result.success(200) } @@ -266,7 +336,7 @@ class FlutterMapLibreView( val args = call.arguments!! as HashMap<*, *> val geoPoint = GeoPoint(args["lat"]!! as Double, args["lon"]!! as Double) val animate = args["animate"] as Boolean? == true - moveTo(geoPoint, animate) + moveTo(geoPoint, null, animate) result.success(200) } @@ -347,8 +417,9 @@ class FlutterMapLibreView( try { val key = (hashMap["id"] as String) + val size = (hashMap["factorSize"] as Double) val bytes = (hashMap["bitmap"] as ByteArray) - setStaticMarkerIcons(key, bytes) + setStaticMarkerIcons(key, bytes, size) } catch (e: java.lang.Exception) { Log.e("id", hashMap["id"].toString()) @@ -372,6 +443,17 @@ class FlutterMapLibreView( } MapMethodChannelCall.UpdateMarker -> { + val args = call.arguments as HashMap<*, *> + val point = (args["point"] as HashMap).toGeoPoint() + var bitmap = customMarkerIcon + if (args.containsKey("icon")) { + bitmap = (args["icon"] as ByteArray).toBitmap() + mapLibre?.style?.removeImage(point.toString()) + mapLibre?.style?.addImage(point.toString(), bitmap) + markerManager?.updateSource() + mapLibre?.triggerRepaint() + } + result.success(200) } @@ -380,7 +462,16 @@ class FlutterMapLibreView( } MapMethodChannelCall.ZoomConfiguration -> { + val args = call.arguments as HashMap<*, *> + zoomConfig( + OSMZoomConfiguration( + minZoom = args["minZoomLevel"] as Double, + maxZoom = args["maxZoomLevel"] as Double, + zoomStep = args["stepZoom"] as Double, + initZoom = args["initZoom"] as Double + ) + ) result.success(200) } @@ -421,7 +512,6 @@ class FlutterMapLibreView( mapLibre!!.setLatLngBoundsForCameraTarget(configuration.bounds.toBoundsLibre()) } val styleURL = when (configuration.customTile) { - is VectorOSMTile -> configuration.customTile.style else -> "https://tiles.openfreemap.org/styles/liberty" } @@ -486,16 +576,18 @@ class FlutterMapLibreView( zoomStep = zoomConfig.zoomStep mapLibre?.setMinZoomPreference(zoomConfig.minZoom) mapLibre?.setMaxZoomPreference(zoomConfig.maxZoom) + initZoom = zoomConfig.initZoom } override fun setBoundingBox(bounds: BoundingBox) { mapLibre?.setLatLngBoundsForCameraTarget(bounds.toBoundsLibre()) } - override fun moveTo(point: IGeoPoint, animate: Boolean) { + override fun moveTo(point: IGeoPoint, zoom: Double?, animate: Boolean) { mapView?.getMapAsync { map -> val cameraPosition = - CameraPosition.Builder().target(point.toLngLat()).zoom(map.cameraPosition.zoom) + CameraPosition.Builder().target(point.toLngLat()) + .zoom(zoom ?: map.cameraPosition.zoom) .build() when (animate) { true -> map.animateCamera(object : CameraUpdate { @@ -524,9 +616,14 @@ class FlutterMapLibreView( } override fun addMarker(point: IGeoPoint, markerConfiguration: MarkerConfiguration) { - mapLibre!!.style!!.addImage(point.toString(), markerConfiguration.markerIcon.toBitmap()) + mapLibre!!.style!!.addImage(point.toString(), markerConfiguration.markerIcon) val symbolOp = SymbolOptions().withLatLng(point.toLngLat()).withIconImage(point.toString()) - .withIconAnchor("center") + .withIconAnchor(markerConfiguration.markerAnchor.name) + .withIconSize( + markerConfiguration.factorSize + .toFloat() + ) + markerManager?.create(symbolOp) markerManager?.addClickListener(object : OnSymbolClickListener { override fun onAnnotationClick(t: Symbol?): Boolean { @@ -539,7 +636,7 @@ class FlutterMapLibreView( markerManager?.addLongClickListener(object : OnSymbolLongClickListener { override fun onAnnotationLongClick(t: Symbol?): Boolean { if (t != null) { - singleClickMarker?.invoke(t.latLng.toGeoPoint()) + longClickMarker?.invoke(t.latLng.toGeoPoint()) } return true } @@ -556,8 +653,9 @@ class FlutterMapLibreView( markerManager?.delete(symbol) } - override fun setStaticMarkerIcons(id: String, icon: ByteArray) { - val bitmapIcon = icon.toBitmap() + override fun setStaticMarkerIcons(id: String, icon: ByteArray, factorSize: Double?) { + var bitmapIcon = icon.toBitmap()//.resize(factorSize?:1.0) + mapLibre?.style?.addImage(id, bitmapIcon) staticMarkerIcon[id] = bitmapIcon if (staticPoints.containsKey(id)) { @@ -581,13 +679,37 @@ class FlutterMapLibreView( } override fun drawPolyline( - polyline: List, animate: Boolean, + roadConfig: OSMRoadConfiguration, animate: Boolean, ): String { - TODO("Not yet implemented") + val road = FlutterMapLibreOSMRoad(idRoad = roadConfig.id, lineManager!!) + for ((i,line) in roadConfig.linesConfig.withIndex()) { + val linePoints = line.encodedPolyline.toPolyline() + road.addSegment("${road.idRoad}-seg-$i",linePoints,line.roadOption) + } + road.onRoadClickListener = object : FlutterRoad.OnRoadClickListener { + override fun onClick( + idRoad: String, + lineId: String, + lineDecoded: String + ) { + + } + + } + roads.add(road) +// val bounds = BoundingBox.fromGeoPoints(roadConfig.linesConfig.map { +// it.encodedPolyline.toPolyline() +// }.reduce { a, b -> (a + b) as ArrayList }) +// moveToBounds(bounds, roadConfig.zoomInto) + return road.idRoad } override fun removePolyline(id: String) { - TODO("Not yet implemented") + val road = roads.first { + it.idRoad == id + } + road.remove() + roads.remove(road) } override fun drawEncodedPolyline(polylineEncoded: String): String { diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterOsmView.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterOsmView.kt index 2ecf4452..eb101cec 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterOsmView.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/FlutterOsmView.kt @@ -31,14 +31,12 @@ import hamza.dali.flutter_osm_plugin.models.Anchor import hamza.dali.flutter_osm_plugin.models.CustomTile import hamza.dali.flutter_osm_plugin.models.FlutterGeoPoint import hamza.dali.flutter_osm_plugin.models.FlutterMarker +import hamza.dali.flutter_osm_plugin.models.FlutterOSMRoad +import hamza.dali.flutter_osm_plugin.models.FlutterOSMRoadFolder import hamza.dali.flutter_osm_plugin.models.FlutterRoad import hamza.dali.flutter_osm_plugin.models.OSMShape -import hamza.dali.flutter_osm_plugin.models.RoadConfig -import hamza.dali.flutter_osm_plugin.models.RoadGeoPointInstruction import hamza.dali.flutter_osm_plugin.models.Shape import hamza.dali.flutter_osm_plugin.models.VoidCallback -import hamza.dali.flutter_osm_plugin.models.toRoadConfig -import hamza.dali.flutter_osm_plugin.models.toRoadInstruction import hamza.dali.flutter_osm_plugin.models.toRoadOption import hamza.dali.flutter_osm_plugin.overlays.CustomLocationManager import hamza.dali.flutter_osm_plugin.utilities.Constants @@ -91,9 +89,6 @@ import kotlin.collections.get import kotlin.collections.set - - - fun FlutterOsmView.configZoomMap(call: MethodCall, result: MethodChannel.Result) { val args = call.arguments as HashMap<*, *> this.map?.minZoomLevel = (args["minZoomLevel"] as Double) @@ -125,7 +120,7 @@ class FlutterOsmView( private val isEnabledRotationGesture: Boolean = false, private val isStaticMap: Boolean = false ) : OnSaveInstanceStateListener, MethodCallHandler, - DefaultLifecycleObserver,OSM { + DefaultLifecycleObserver, OSM { internal var map: MapView? = null @@ -155,7 +150,7 @@ class FlutterOsmView( } } - private var flutterRoad: FlutterRoad? = null + private var flutterRoad: FlutterOSMRoadFolder? = null private var job: Job? = null private var scope: CoroutineScope? = null private var skipCheckLocation: Boolean = false @@ -173,7 +168,6 @@ class FlutterOsmView( } - private var roadManager: OSRMRoadManager? = null internal var stepZoom = Constants.stepZoom internal var initZoom = 10.0 private var isTracking = false @@ -194,7 +188,7 @@ class FlutterOsmView( } - override fun setActivity(activity: Activity) { + override fun setActivity(activity: Activity) { this.activity = activity } @@ -482,7 +476,7 @@ class FlutterOsmView( } "draw#multi#road" -> { - drawMultiRoad(call, result) +// drawMultiRoad(call, result) } "clear#roads" -> { @@ -494,7 +488,7 @@ class FlutterOsmView( } "drawRoad#manually" -> { - drawRoadManually(call, result) + // drawRoadManually(call, result) } "staticPosition" -> { @@ -1095,97 +1089,6 @@ class FlutterOsmView( result.success(200) } - private fun drawMultiRoad(call: MethodCall, result: MethodChannel.Result) { - val args = call.arguments!! as List> - val listConfigRoad = emptyList().toMutableList() - - for (arg in args) { - val waypoints = (arg["wayPoints"] as List>).map { map -> - GeoPoint(map["lat"]!!, map["lon"]!!) - }.toList() - listConfigRoad.add( - RoadConfig( - meanUrl = when (arg["roadType"] as String) { - "car" -> OSRMRoadManager.MEAN_BY_CAR - "bike" -> OSRMRoadManager.MEAN_BY_BIKE - "foot" -> OSRMRoadManager.MEAN_BY_FOOT - else -> OSRMRoadManager.MEAN_BY_CAR - }, roadOption = arg.toRoadOption(), - - wayPoints = waypoints, interestPoints = when (arg.containsKey("middlePoints")) { - true -> arg["middlePoints"] as List> - false -> emptyList() - }.map { g -> - GeoPoint(g["lat"]!!, g["lon"]!!) - }.toList(), roadID = arg["key"] as String - ) - ) - } - - checkRoadFolderAboveUserOverlay() - - map!!.invalidate() - - val resultRoads = emptyList>().toMutableList() - job = scope?.launch(Default) { - withContext(IO) { - for (config in listConfigRoad) { - if (roadManager == null) roadManager = - OSRMRoadManager(context, "json/application") - roadManager?.let { manager -> - manager.setMean(config.meanUrl) - var routePointsEncoded: String - // this part to remove marker of interest points - withContext(Main) { - folderMarkers.items.removeAll { - (it is FlutterMarker && config.wayPoints.contains(it.position)) || (it is FlutterMarker && config.interestPoints.contains( - it.position - )) - } - } - val roadPoints = ArrayList(config.wayPoints) - if (config.interestPoints.isNotEmpty()) { - roadPoints.addAll(1, config.interestPoints) - } - val road = manager.getRoad(roadPoints) - withContext(Main) { - if (road.mRouteHigh.size > 2) { - routePointsEncoded = PolylineEncoder.encode(road.mRouteHigh, 10) - val polyLine = RoadManager.buildRoadOverlay(road) - polyLine.setStyle( - borderColor = config.roadOption.roadBorderColor, - borderWidth = config.roadOption.roadBorderWidth, - color = config.roadOption.roadColor ?: Color.GREEN, - width = config.roadOption.roadWidth, - ) - createRoad( - polyLine = polyLine, - roadID = config.roadID, - roadDuration = road.mDuration, - roadDistance = road.mLength - ) - val instructions = road.mNodes.toRoadInstruction() - - resultRoads.add( - road.toMap( - config.roadID, routePointsEncoded, instructions - ) - ) - } - } - delay(100) - - } - } - - } - withContext(Main) { - map!!.invalidate() - result.success(resultRoads.toList()) - } - } - - } private fun checkRoadFolderAboveUserOverlay() { if (!map!!.overlays.contains(folderRoad)) { @@ -1198,7 +1101,7 @@ class FlutterOsmView( when (roadKey != null) { true -> { val road = folderRoad.items.map { - it as FlutterRoad + it as FlutterOSMRoad }.first { road -> road.idRoad == roadKey } @@ -1228,146 +1131,122 @@ class FlutterOsmView( val args = call.arguments!! as HashMap - val meanUrl = when (args["roadType"] as String) { - "car" -> OSRMRoadManager.MEAN_BY_CAR - "bike" -> OSRMRoadManager.MEAN_BY_BIKE - "foot" -> OSRMRoadManager.MEAN_BY_FOOT - else -> OSRMRoadManager.MEAN_BY_CAR - } + val roadId = args["key"] as String + val linesMap = args["segments"] as List> + var lines = mutableListOf() val zoomToRegion = args["zoomIntoRegion"] as Boolean - val roadConfig = args.toRoadConfig() - checkRoadFolderAboveUserOverlay() - var instructions = emptyList() - if (roadManager == null) roadManager = OSRMRoadManager(context, "json/application") - roadManager?.let { manager -> - manager.setMean(meanUrl) - var routePointsEncoded = "" - job = scope?.launch(Default) { + for (arg in linesMap) { + val encoded = arg["polylineEncoded"] as String + val roadOption = (arg["option"] as HashMap<*, *>).toRoadOption() + checkRoadFolderAboveUserOverlay() - val roadPoints = ArrayList(roadConfig.wayPoints) - if (roadConfig.interestPoints.isNotEmpty()) { - roadPoints.addAll(1, roadConfig.interestPoints) - } - val road = manager.getRoad(roadPoints) - withContext(Main) { - if (road.mRouteHigh.size > 2) { - routePointsEncoded = PolylineEncoder.encode(road.mRouteHigh, 10) - val polyLine = Polyline(map!!, false, false).apply { - setStyle( - borderColor = roadConfig.roadOption.roadBorderColor, - borderWidth = roadConfig.roadOption.roadBorderWidth, - color = roadConfig.roadOption.roadColor ?: Color.GREEN, - width = roadConfig.roadOption.roadWidth, - isDottedPolyline = roadConfig.roadOption.isDotted - ) - setPoints(RoadManager.buildRoadOverlay(road).actualPoints) - - } - flutterRoad = createRoad( - polyLine = polyLine, - roadID = roadConfig.roadID, - roadDuration = road.mDuration, - roadDistance = road.mLength - - ) - instructions = road.mNodes.toRoadInstruction() - - if (zoomToRegion) { - map!!.zoomToBoundingBox( - BoundingBox.fromGeoPoints(road.mRouteHigh), - true, - 64, - ) - } - - map!!.invalidate() - } - result.success( - road.toMap( - roadConfig.roadID, routePointsEncoded, instructions - ) - ) - } + val routePointsEncoded = PolylineEncoder.decode(encoded, 5, false) + val polyLine = Polyline(map!!, false, false).apply { + setStyle( + borderColor = roadOption.roadBorderColor, + borderWidth = roadOption.roadBorderWidth, + color = roadOption.roadColor ?: Color.GREEN, + width = roadOption.roadWidth, + isDottedPolyline = roadOption.isDotted + ) + setPoints(routePointsEncoded) } + lines.add(polyLine) } - } - - - private fun drawRoadManually(call: MethodCall, result: MethodChannel.Result) { - val args: HashMap = call.arguments as HashMap - val roadId = args["key"] as String - val encodedWayPoints = (args["road"] as String) - val roadColor = (args["roadColor"] as List).toRGB() - val roadWidth = (args["roadWidth"] as Double).toFloat() - val roadBorderWidth = (args["roadBorderWidth"] as Double? ?: 0).toFloat() - val roadBorderColor = (args["roadBorderColor"] as List?)?.toRGB() ?: 0 - val zoomToRegion = args["zoomIntoRegion"] as Boolean - - checkRoadFolderAboveUserOverlay() - - - val route = PolylineEncoder.decode(encodedWayPoints, 10, false) - - - val polyLine = Polyline(map!!) - polyLine.setPoints(route) - polyLine.setStyle( - borderWidth = roadBorderWidth, - borderColor = roadBorderColor, - color = roadColor, - width = roadWidth - ) - - createRoad( + flutterRoad = createRoad( + polyLines = lines, roadID = roadId, - polyLine = polyLine, ) - - if (zoomToRegion) { map!!.zoomToBoundingBox( - BoundingBox.fromGeoPoints(polyLine.actualPoints), + BoundingBox.fromGeoPoints(lines.map { it.actualPoints }.reduce { a, b -> a + b } + .toList()), true, 64, ) } + map!!.invalidate() - result.success(null) + + result.success( + mapOf( + "key" to roadId + ) + ) } + +// private fun drawRoadManually(call: MethodCall, result: MethodChannel.Result) { +// val args: HashMap = call.arguments as HashMap +// val roadId = args["key"] as String +// val encodedWayPoints = (args["road"] as String) +// val roadColor = (args["roadColor"] as List).toRGB() +// val roadWidth = (args["roadWidth"] as Double).toFloat() +// val roadBorderWidth = (args["roadBorderWidth"] as Double? ?: 0).toFloat() +// val roadBorderColor = (args["roadBorderColor"] as List?)?.toRGB() ?: 0 +// val zoomToRegion = args["zoomIntoRegion"] as Boolean +// +// checkRoadFolderAboveUserOverlay() +// +// +// val route = PolylineEncoder.decode(encodedWayPoints, 10, false) +// +// +// val polyLine = Polyline(map!!) +// polyLine.setPoints(route) +// polyLine.setStyle( +// borderWidth = roadBorderWidth, +// borderColor = roadBorderColor, +// color = roadColor, +// width = roadWidth +// ) +// +// createRoad( +// roadID = roadId, +// polyLine = polyLine, +// ) +// +// +// if (zoomToRegion) { +// map!!.zoomToBoundingBox( +// BoundingBox.fromGeoPoints(polyLine.actualPoints), +// true, +// 64, +// ) +// } +// map!!.invalidate() +// result.success(null) +// } + private fun createRoad( roadID: String, - polyLine: Polyline, - roadDuration: Double = 0.0, - roadDistance: Double = 0.0, + polyLines: List, - ): FlutterRoad { + ): FlutterOSMRoadFolder { - val flutterRoad = FlutterRoad( + val flutterRoad = FlutterOSMRoad( roadID, - roadDistance = roadDistance, - roadDuration = roadDuration, +// roadDistance = roadDistance, +// roadDuration = roadDuration, ) flutterRoad.let { roadF -> - roadF.road = polyLine - //roadF.road.setOnClickListener { polyline, mapView, eventPos -> } - roadF.onRoadClickListener = object : FlutterRoad.OnRoadClickListener { - override fun onClick(road: FlutterRoad, geoPointClicked: GeoPoint) { - val map = HashMap() - map["roadPoints"] = road.road?.actualPoints?.map { - it.toHashMap() - } ?: emptyList() - map["distance"] = road.roadDistance - map["duration"] = road.roadDuration - map["key"] = road.idRoad - methodChannel.invokeMethod("receiveRoad", map) + for (polyline in polyLines) { + roadF.addSegment(polyline) + //roadF.road.setOnClickListener { polyline, mapView, eventPos -> } + roadF.onRoadClickListener = object : FlutterRoad.OnRoadClickListener { + override fun onClick(idRoad: String, polyineId: String,polyEncoded:String) { + val map = HashMap() + map["key"] = idRoad + map["segId"] = polyineId + methodChannel.invokeMethod("receiveRoad", map) - } + } + } } folderRoad.items.add(roadF) } diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/OSMBase.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/OSMBase.kt index 0752438d..d7f8b322 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/OSMBase.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/map/OSMBase.kt @@ -4,9 +4,11 @@ import android.graphics.Bitmap import hamza.dali.flutter_osm_plugin.models.Anchor import hamza.dali.flutter_osm_plugin.models.FlutterGeoPoint import hamza.dali.flutter_osm_plugin.models.OSMTile +import hamza.dali.flutter_osm_plugin.models.RoadOption import hamza.dali.flutter_osm_plugin.models.Shape import hamza.dali.flutter_osm_plugin.models.VectorOSMTile -import hamza.dali.flutter_osm_plugin.utilities.toByteArray +import hamza.dali.flutter_osm_plugin.models.toRoadOption +import hamza.dali.flutter_osm_plugin.utilities.toBitmap import hamza.dali.flutter_osm_plugin.utilities.toGeoPoint import org.osmdroid.api.IGeoPoint import org.osmdroid.util.BoundingBox @@ -99,18 +101,19 @@ data class OSMShapeConfiguration( val strokeWidth: Double ) +@Suppress("UNCHECKED_CAST") data class MarkerConfiguration( - val markerIcon: ByteArray, + val markerIcon: Bitmap, val markerRotate: Double, val markerAnchor: Anchor, - val size: Double, + val factorSize: Double, ) { companion object { fun fromArgs(args: HashMap<*, *>, defaultIcon: Bitmap?): MarkerConfiguration { - var iconBytes = defaultIcon.toByteArray() + var iconBitmap = defaultIcon if (args.containsKey("icon")) { - iconBytes = args["icon"] as ByteArray + iconBitmap = (args["icon"] as ByteArray).toBitmap() } val angle = when ((args["point"] as HashMap<*, *>).containsKey("angle")) { @@ -119,16 +122,64 @@ data class MarkerConfiguration( } val anchor = when (args.containsKey("iconAnchor")) { true -> Anchor(args["iconAnchor"] as HashMap) - else -> null + else -> Anchor(0.5f, 0.5f) + } + val factorSize = when (args.containsKey("factorSize")) { + true -> args["factorSize"] as Double + else -> 1.8 } return MarkerConfiguration( - markerIcon = iconBytes!!, angle, Anchor(0.5f, 0.5f), 48.0 + markerIcon = iconBitmap!!, + angle, + anchor, + factorSize + ) + } + } +} + +data class OSMRoadConfiguration( + val id: String, + val linesConfig: List, + val zoomInto: Boolean +) { + companion object { + + fun fromArgs(args: HashMap<*, *>): OSMRoadConfiguration { + val zoomToRegion = args["zoomIntoRegion"] as Boolean + + return OSMRoadConfiguration( + id = args["key"] as String, + zoomInto = zoomToRegion, + linesConfig = when (args.contains("segments") && args["segments"] is List<*>) { + true -> (args["segments"] as List<*>).map { arg -> + OSMLineConfiguration.fromArgs(arg as HashMap<*, *>) + } + + else -> emptyList() + } ) } } } +data class OSMLineConfiguration( + val encodedPolyline: String, + val roadOption: RoadOption +) { + companion object { + fun fromArgs(args: HashMap<*, *>): OSMLineConfiguration { + return OSMLineConfiguration( + encodedPolyline = args["polylineEncoded"] as String, + roadOption = when (args.contains("option") && args["option"] is HashMap<*, *>) { + true -> (args["option"] as HashMap<*, *>).toRoadOption() + else -> RoadOption() + } + ) + } + } +} interface OSMBase : OSM { var customMarkerIcon: Bitmap? @@ -139,13 +190,13 @@ interface OSMBase : OSM { fun init(configuration: OSMInitConfiguration) fun zoomConfig(zoomConfig: OSMZoomConfiguration) fun setBoundingBox(bounds: BoundingBox) - fun moveTo(point: IGeoPoint, animate: Boolean) + fun moveTo(point: IGeoPoint, zoom: Double? = null, animate: Boolean) fun moveToBounds(bounds: BoundingBox, animate: Boolean) fun addMarker(point: IGeoPoint, markerConfiguration: MarkerConfiguration) fun removeMarker(point: IGeoPoint) - fun setStaticMarkerIcons(id: String, icon: ByteArray) + fun setStaticMarkerIcons(id: String, icon: ByteArray, factorSize: Double?) fun setStaticMarkers(id: String, markers: List) - fun drawPolyline(polyline: List, animate: Boolean): String + fun drawPolyline(road: OSMRoadConfiguration, animate: Boolean): String fun removePolyline(id: String) fun drawEncodedPolyline(polylineEncoded: String): String fun removeEncodedPolyline(id: String) diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/ExtensionMapLibre.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/ExtensionMapLibre.kt index 0e9a6501..1b1d1576 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/ExtensionMapLibre.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/ExtensionMapLibre.kt @@ -1,16 +1,25 @@ package hamza.dali.flutter_osm_plugin.models +import android.graphics.Color import androidx.collection.LongSparseArray import androidx.collection.forEach +import hamza.dali.flutter_osm_plugin.map.OSMLineConfiguration +import hamza.dali.flutter_osm_plugin.utilities.setStyle import org.maplibre.android.geometry.LatLng import org.maplibre.android.geometry.LatLngBounds import org.maplibre.android.plugins.annotation.Symbol import org.maplibre.android.plugins.annotation.SymbolOptions import org.osmdroid.api.IGeoPoint +import org.osmdroid.bonuspack.utils.PolylineEncoder import org.osmdroid.util.BoundingBox import org.osmdroid.util.GeoPoint +import org.osmdroid.views.overlay.Polyline +import kotlin.collections.get fun IGeoPoint.toLngLat(): LatLng = LatLng(latitude = latitude, longitude = longitude) +fun List.toLngLats(): List = + map { gp -> gp.toLngLat() } + fun LatLng.toGeoPoint(): IGeoPoint = GeoPoint(latitude, longitude) fun BoundingBox.toBoundsLibre(): LatLngBounds = LatLngBounds.fromLatLngs( arrayOf( @@ -28,12 +37,22 @@ fun Array.toGeoPoints(): List { it.toGeoPoint() }.toList() } - -fun List.toSymbols(iconId: String): List { +fun Collection.toGeoPoints(): List { + return map { + it.toGeoPoint() as GeoPoint + }.toList() +} +fun Collection.toArrayGeoPoints(): java.util.ArrayList { return map { - SymbolOptions().withLatLng(it.geoPoint.toLngLat()) - .withIconRotate(it.angle.toFloat()) + it.toGeoPoint() as GeoPoint + }.toList().toCollection(java.util.ArrayList()) +} +fun List.toSymbols(iconId: String): List { + return map { fGp -> + SymbolOptions().withLatLng(fGp.geoPoint.toLngLat()) + .withIconRotate(fGp.angle.toFloat()) .withIconImage(iconId) + .withIconSize(fGp.factorSize.toFloat()) }.toList() } @@ -55,6 +74,7 @@ fun LongSparseArray.toList(): List { return list.toList() } + fun LongSparseArray.toGeoPoints(): List { val list = mutableListOf() this.forEach { k, symbol -> @@ -66,4 +86,4 @@ fun LongSparseArray.toGeoPoints(): List { fun List.toGeoPoints(): List = map { it.latLng.toGeoPoint() -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterGeoPoint.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterGeoPoint.kt index 15fbc980..ad282c99 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterGeoPoint.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterGeoPoint.kt @@ -5,6 +5,7 @@ import org.osmdroid.util.GeoPoint data class FlutterGeoPoint( val geoPoint: GeoPoint, val angle: Double = 0.0, + val factorSize: Double = 1.8, val anchor: Anchor? = null, val icon: ByteArray? = null ) { diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterMaker.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterMaker.kt index 835758ad..a5086d45 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterMaker.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterMaker.kt @@ -268,12 +268,12 @@ open class FlutterMarker(private var mapView: MapView, var scope: CoroutineScope } -data class Anchor(val x: Float, val y: Float) { +data class Anchor(val x: Float, val y: Float,var name: String = "center") { private var offset: Pair? = null constructor(map: HashMap) : this( (map["x"]!! as Double).toFloat(), - (map["y"]!! as Double).toFloat() + (map["y"]!! as Double).toFloat(),map["anchor"]!! as String ) { if (map.containsKey("offset")) { val offsetMap = map["offset"]!! as HashMap diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterRoad.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterRoad.kt index cff31163..d79a6cb6 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterRoad.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/FlutterRoad.kt @@ -1,35 +1,87 @@ package hamza.dali.flutter_osm_plugin.models -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Paint -import hamza.dali.flutter_osm_plugin.utilities.Constants +import android.graphics.Color +import hamza.dali.flutter_osm_plugin.utilities.encodePolyline +import hamza.dali.flutter_osm_plugin.utilities.toPolylineEncode +import org.maplibre.android.plugins.annotation.Line +import org.maplibre.android.plugins.annotation.LineManager +import org.maplibre.android.plugins.annotation.LineOptions +import org.maplibre.android.plugins.annotation.OnLineClickListener +import org.maplibre.android.utils.ColorUtils +import org.osmdroid.bonuspack.utils.PolylineEncoder import org.osmdroid.util.GeoPoint -import org.osmdroid.views.MapView import org.osmdroid.views.overlay.FolderOverlay import org.osmdroid.views.overlay.Polyline -open class FlutterRoad( - val idRoad: String, - val roadDuration: Double, - val roadDistance: Double, -) : FolderOverlay() { - var road: Polyline? = null - set(value) { - if (value != null) { - field = value - items.add(value) - field?.setOnClickListener { _, _, geoPointClicked -> - onRoadClickListener?.onClick(this,geoPointClicked) - true +sealed interface FlutterRoad { + val idRoad: String + + interface OnRoadClickListener { + fun onClick(idRoad: String,lineId:String, lineDecoded: String) + } +} + +open class FlutterMapLibreOSMRoad( + override val idRoad: String, + val lineManager: LineManager +) : FlutterRoad { + var onRoadClickListener: FlutterRoad.OnRoadClickListener? = null + private val lines: MutableList = mutableListOf() + private val linesIdPair: MutableList> = mutableListOf() + val roadSegments = lines.toList() + fun addSegment(id:String,polyline: List, polylineOption: RoadOption) { + val lineOptions = LineOptions() + .withLatLngs(polyline.toLngLats()) + .withLineColor(ColorUtils.colorToRgbaString(polylineOption.roadColor ?: Color.BLUE)) + .withLineWidth(polylineOption.roadWidth) + .withDraggable(false) + + val line = lineManager.create(lineOptions) + lineManager.updateSource() + linesIdPair.add(Pair(line.id, id)) + + lineManager.addClickListener(object : OnLineClickListener { + override fun onAnnotationClick(t: Line?): Boolean { + if (t != null && lines.contains(t)) { + + this@FlutterMapLibreOSMRoad.onRoadClickListener?.onClick( + idRoad, + linesIdPair.first { it.first == t.id }.second, + t.latLngs.encodePolyline() + ) } + + return true } - } - var onRoadClickListener: OnRoadClickListener? = null + }) + lines.add(line) + } + fun remove() { + lineManager.delete(lines) + lineManager.updateSource() + } +} - interface OnRoadClickListener { - fun onClick(road: FlutterRoad,geoPointClicked:GeoPoint) +abstract class FlutterOSMRoadFolder : FolderOverlay(), FlutterRoad +open class FlutterOSMRoad( + override val idRoad: String, +) : FlutterOSMRoadFolder() { + private val segments: MutableList = mutableListOf() + val roadSegments = segments.toList() + var onRoadClickListener: FlutterRoad.OnRoadClickListener? = null + fun addSegment(seg: Polyline) { + seg.id = "${idRoad}-seg-${segments.size + 1}" + seg.setOnClickListener { poly, _, geoPointClicked -> + val arrays = java.util.ArrayList(poly.actualPoints.size) + arrays.addAll(poly.actualPoints) + val encoded = poly.actualPoints.toPolylineEncode() + onRoadClickListener?.onClick(idRoad,seg.id, encoded) + true + } + segments.add(seg) } -} \ No newline at end of file + + +} diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/MapMedthodCall.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/MapMedthodCall.kt index ec316dfd..725342be 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/MapMedthodCall.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/MapMedthodCall.kt @@ -25,6 +25,7 @@ sealed class MapMethodChannelCall(val methodName: String) { object UserPosition : MapMethodChannelCall("user#position") object MoveTo : MapMethodChannelCall("moveTo#position") object RemoveMarkerPosition : MapMethodChannelCall("user#removeMarkerPosition") + object DrawRoad : MapMethodChannelCall("road") object DeleteRoad : MapMethodChannelCall("delete#road") object DrawMultiRoad : MapMethodChannelCall("draw#multi#road") object ClearRoads : MapMethodChannelCall("clear#roads") @@ -70,6 +71,7 @@ sealed class MapMethodChannelCall(val methodName: String) { Bounds.methodName -> Bounds UserPosition.methodName -> UserPosition MoveTo.methodName -> MoveTo + DrawRoad.methodName -> DrawRoad DeleteRoad.methodName -> DeleteRoad DrawMultiRoad.methodName -> DrawMultiRoad DefaultMarkerIcon.methodName -> DefaultMarkerIcon @@ -93,7 +95,6 @@ sealed class MapMethodChannelCall(val methodName: String) { GetMarkers.methodName -> GetMarkers DeleteMakers.methodName -> DeleteMakers ToggleLayers.methodName -> ToggleLayers - else -> null } } diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/RoadConfig.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/RoadConfig.kt index a1883823..4667b129 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/RoadConfig.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/models/RoadConfig.kt @@ -6,13 +6,7 @@ import org.osmdroid.bonuspack.routing.OSRMRoadManager import org.osmdroid.bonuspack.utils.PolylineEncoder import org.osmdroid.util.GeoPoint -data class RoadConfig( - val wayPoints: List, - val interestPoints: List, - val meanUrl: String = OSRMRoadManager.MEAN_BY_CAR, - val roadOption: RoadOption, - val roadID: String, -) + data class RoadOption( val roadColor: Int? = null, @@ -22,24 +16,24 @@ data class RoadOption( val isDotted: Boolean = false, ) -fun HashMap.toRoadOption(): RoadOption { - val roadColor = when (this.containsKey("roadColor")) { +fun HashMap<*, *>.toRoadOption(): RoadOption { + val roadColor = when (containsKey("roadColor")) { true -> (this["roadColor"] as List<*>).map { it as Int }.toRGB() else -> Color.BLUE } - val roadWidth = when (this.containsKey("roadWidth")) { + val roadWidth = when (containsKey("roadWidth")) { true -> (this["roadWidth"] as Double).toFloat() else -> 5f } - val roadBorderWidth = when (this.containsKey("roadBorderWidth")) { + val roadBorderWidth = when (containsKey("roadBorderWidth")) { true -> (this["roadBorderWidth"] as Double).toFloat() else -> 0f } - val roadBorderColor = when (this.containsKey("roadBorderColor")) { + val roadBorderColor = when (containsKey("roadBorderColor")) { true -> (this["roadBorderColor"] as List<*>).filterIsInstance().toRGB() else -> null } - val isDotted: Boolean = when (this.containsKey("isDotted")) { + val isDotted: Boolean = when (containsKey("isDotted")) { true -> this["isDotted"] as Boolean else -> false } @@ -52,35 +46,4 @@ fun HashMap.toRoadOption(): RoadOption { ) } -fun HashMap.toRoadConfig(): RoadConfig { - val roadId = when (this.containsKey("key")) { - true -> this["key"] as String - else -> "" - } - return RoadConfig( - roadID = roadId, - roadOption = toRoadOption(), - wayPoints = when { - this.containsKey("wayPoints") -> (this["wayPoints"] as List>) - .map { g -> - GeoPoint(g["lat"]!!, g["lon"]!!) - }.toList() - - this.containsKey("road") -> PolylineEncoder.decode( - (this["road"] as String), - 10, - false - ) - else -> emptyList() - }, - interestPoints = when { - this.containsKey("middlePoints") -> (this["middlePoints"] as List>) - .map { g -> - GeoPoint(g["lat"]!!, g["lon"]!!) - }.toList() - - else -> emptyList() - }, - ) -} diff --git a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/utilities/ExtensionOSM.kt b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/utilities/ExtensionOSM.kt index 2f4c86ec..aabe1e80 100644 --- a/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/utilities/ExtensionOSM.kt +++ b/android/src/main/kotlin/hamza/dali/flutter_osm_plugin/utilities/ExtensionOSM.kt @@ -12,9 +12,12 @@ import android.location.Location import android.provider.Settings import hamza.dali.flutter_osm_plugin.map.FlutterOsmView import hamza.dali.flutter_osm_plugin.models.RoadGeoPointInstruction +import hamza.dali.flutter_osm_plugin.models.toGeoPoints import hamza.dali.flutter_osm_plugin.models.toMap +import org.maplibre.android.geometry.LatLng import org.osmdroid.api.IGeoPoint import org.osmdroid.bonuspack.routing.Road +import org.osmdroid.bonuspack.utils.PolylineEncoder import org.osmdroid.tileprovider.tilesource.ITileSource import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase import org.osmdroid.tileprovider.tilesource.TileSourceFactory @@ -144,15 +147,15 @@ fun Polyline.setStyle( width: Float, borderColor: Int?, borderWidth: Float, - isDottedPolyline:Boolean = false + isDottedPolyline: Boolean = false ) { outlinePaint.strokeWidth = width outlinePaint.style = Paint.Style.FILL_AND_STROKE outlinePaint.color = color outlinePaint.strokeCap = Paint.Cap.ROUND - var pathEffect:PathEffect? = null - if(isDottedPolyline){ - pathEffect = DashPathEffect(arrayOf(10f,20f).toFloatArray(),0f) + var pathEffect: PathEffect? = null + if (isDottedPolyline) { + pathEffect = DashPathEffect(arrayOf(10f, 20f).toFloatArray(), 0f) } @@ -201,6 +204,13 @@ fun Road.toMap( } fun ByteArray.toBitmap(): Bitmap = BitmapFactory.decodeByteArray(this, 0, this.size) +fun Bitmap.resize(scale: Double): Bitmap = Bitmap.createScaledBitmap( + this, + (width * scale).toInt(), + (height * scale).toInt(), + false +) + fun Bitmap?.toByteArray(): ByteArray? { if (this == null) { return null @@ -224,9 +234,20 @@ fun createPaintPolyline( paint.strokeCap = Paint.Cap.ROUND paint.strokeJoin = Paint.Join.ROUND paint.isAntiAlias = true - if(pathEffect != null){ + if (pathEffect != null) { paint.pathEffect = pathEffect } return paint } + +fun String.toPolyline(): ArrayList { + return PolylineEncoder.decode(this, 10, false) +} + +fun List.toPolylineEncode(): String { + return PolylineEncoder.encode(this.toCollection(ArrayList()), 10) +} +fun List.encodePolyline(): String { + return PolylineEncoder.encode(this.toGeoPoints().toCollection(ArrayList()), 10) +} \ No newline at end of file