@@ -25,7 +25,17 @@ import org.microg.gms.maps.mapbox.model.AnnotationType.LINE
2525import  org.microg.gms.maps.mapbox.utils.toMapbox 
2626import  org.microg.gms.utils.warnOnTransactionIssues 
2727import  java.util.LinkedList 
28+ import  kotlin.math.abs 
29+ import  kotlin.math.acos 
30+ import  kotlin.math.asin 
31+ import  kotlin.math.atan2 
32+ import  kotlin.math.ceil 
33+ import  kotlin.math.cos 
34+ import  kotlin.math.max 
35+ import  kotlin.math.sin 
36+ import  kotlin.math.sqrt 
2837import  com.google.android.gms.maps.model.PolylineOptions  as  GmsLineOptions 
38+ import  com.mapbox.mapboxsdk.geometry.LatLng  as  MapboxLatLng 
2939
3040abstract  class  AbstractPolylineImpl (private  val  id :  String , options :  GmsLineOptions , private val  dpiFactor :  Function0 <Float >) : IPolylineDelegate.Stub() {
3141    internal  var  points:  List <LatLng > =  ArrayList (options.points)
@@ -93,6 +103,7 @@ abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOpti
93103
94104    override  fun  setGeodesic (geod :  Boolean ) {
95105        this .geodesic =  geod
106+         update()
96107    }
97108
98109    override  fun  isGeodesic (): Boolean  =  geodesic
@@ -166,6 +177,108 @@ class PolylineImpl(private val map: GoogleMapImpl, id: String, options: GmsLineO
166177    override  var  annotations =  computeAnnotations()
167178    override  var  removed:  Boolean  =  false 
168179
180+     private  fun  interpolateGeodesic (points :  List <MapboxLatLng >): List <MapboxLatLng > {
181+         val  maxSegmentMeters =  20_000.0 
182+         val  curvatureBoost =  0.75 
183+ 
184+         if  (points.size <=  1 ) return  points.toList()
185+ 
186+         val  r =  6_371_008.8   //  mean Earth radius (meters)
187+ 
188+         fun  toVec (latDeg :  Double , lonDeg :  Double ): DoubleArray  {
189+             val  lat =  Math .toRadians(latDeg)
190+             val  lon =  Math .toRadians(lonDeg)
191+             val  cl =  cos(lat)
192+             return  doubleArrayOf(cl *  cos(lon), cl *  sin(lon), sin(lat))
193+         }
194+ 
195+         fun  norm (v :  DoubleArray ): DoubleArray  {
196+             val  m =  sqrt(v[0 ] *  v[0 ] +  v[1 ] *  v[1 ] +  v[2 ] *  v[2 ])
197+             return  doubleArrayOf(v[0 ] /  m, v[1 ] /  m, v[2 ] /  m)
198+         }
199+ 
200+         fun  toLatLng (v :  DoubleArray ): MapboxLatLng  {
201+             val  x =  v[0 ];
202+             val  y =  v[1 ];
203+             val  z =  v[2 ]
204+             val  lat =  asin(z)
205+             val  lon =  atan2(y, x)
206+             //  wrap to [-180,180)
207+             var  lonDeg =  Math .toDegrees(lon)
208+             lonDeg =  ((lonDeg +  540.0 ) %  360.0 ) -  180.0 
209+             return  MapboxLatLng (Math .toDegrees(lat), lonDeg)
210+         }
211+ 
212+         fun  centralAngle (a :  DoubleArray , b :  DoubleArray ): Double  {
213+             val  dot =  (a[0 ] *  b[0 ] +  a[1 ] *  b[1 ] +  a[2 ] *  b[2 ]).coerceIn(- 1.0 , 1.0 )
214+             return  acos(dot)
215+         }
216+ 
217+         val  out  =  ArrayList <MapboxLatLng >(points.size *  4 )
218+ 
219+         for  (i in  0  until points.lastIndex) {
220+             val  a =  points[i]
221+             val  b =  points[i +  1 ]
222+             val  va =  norm(toVec(a.latitude, a.longitude))
223+             val  vb =  norm(toVec(b.latitude, b.longitude))
224+             val  omega =  centralAngle(va, vb)
225+ 
226+             //  Base segment count from distance
227+             val  distance =  omega *  r
228+             var  steps =  max(1 , ceil(distance /  maxSegmentMeters).toInt())
229+ 
230+             //  Heuristic curvature boost (how "curvy" it *looks* in Web-Mercator)
231+             val  meanLatRad =  Math .toRadians((a.latitude +  b.latitude) /  2.0 )
232+             val  dLonRad =  abs(
233+                 //  shortest ∆lon across antimeridian
234+                 ((Math .toRadians(b.longitude -  a.longitude) +  Math .PI ) %  (2  *  Math .PI )) -  Math .PI 
235+             )
236+             val  mercatorCurviness =  abs(sin(meanLatRad)) *  (dLonRad /  Math .PI ) //  0..1
237+             val  boost =  1.0  +  curvatureBoost *  mercatorCurviness
238+             steps =  max(1 , ceil(steps *  boost).toInt())
239+ 
240+             //  Emit points along the great-circle (slerp)
241+             val  sinOmega =  sin(omega)
242+             //  Add first point (or skip if already added as previous segment's end)
243+             if  (i ==  0 ) out .add(MapboxLatLng (a.latitude, a.longitude))
244+ 
245+             if  (omega ==  0.0  ||  sinOmega ==  0.0 ) {
246+                 //  identical points: skip interpolation
247+                 out .add(MapboxLatLng (b.latitude, b.longitude))
248+                 continue 
249+             }
250+ 
251+             for  (k in  1 .. steps) {
252+                 val  t =  k.toDouble() /  steps
253+                 val  s1 =  sin((1  -  t) *  omega) /  sinOmega
254+                 val  s2 =  sin(t *  omega) /  sinOmega
255+                 val  vx =  s1 *  va[0 ] +  s2 *  vb[0 ]
256+                 val  vy =  s1 *  va[1 ] +  s2 *  vb[1 ]
257+                 val  vz =  s1 *  va[2 ] +  s2 *  vb[2 ]
258+                 var  p =  toLatLng(doubleArrayOf(vx, vy, vz))
259+ 
260+                 if  (out .isNotEmpty() &&  abs(p.longitude -  out .last().longitude) >  180 ) {
261+                     //  Make sure the current point crosses the antimeridian not normalized,
262+                     //  i.e. going from +179° to +181° instead of +179° to -179°.
263+                     //  This avoids a long horizontal line across the map.
264+                     val  lon =  if  (out .last().longitude >  0 ) p.longitude +  360  else  p.longitude -  360 
265+                     p =  MapboxLatLng (p.latitude, lon)
266+                 }
267+ 
268+                 //  Avoid duplicating the joint point on the next segment
269+                 if  (k <  steps ||  i ==  points.lastIndex -  1 ) {
270+                     out .add(p)
271+                 }
272+             }
273+         }
274+         return  out 
275+     }
276+ 
277+     private  fun  List<MapboxLatLng>.mapToGeodesicIfNeeded (): List <MapboxLatLng > {
278+         if  (! geodesic) return  this 
279+         return  interpolateGeodesic(this )
280+     }
281+ 
169282    private  fun  computeAnnotations (): List <AnnotationTracker <Line , LineOptions >> {
170283        val  pointsQueue =  LinkedList (points)
171284        val  result =  mutableListOf<AnnotationTracker <Line , LineOptions >>()
@@ -184,13 +297,21 @@ class PolylineImpl(private val map: GoogleMapImpl, id: String, options: GmsLineO
184297                .withLineColor(ColorUtils .colorToRgbaString(span.style.color))
185298                .withLineOpacity(if  (visible and  span.style.isVisible) 1f  else  0f )
186299                .withLineWidth((span.style.width) /  map.dpiFactor)
187-                 .withLatLngs(spanPoints.map { it.toMapbox() })
300+                 .withLatLngs(
301+                     spanPoints
302+                         .map { it.toMapbox() }
303+                         .mapToGeodesicIfNeeded()
304+                 )
188305            result.add(AnnotationTracker (options))
189306        }
190307
191308        if  (pointsQueue.isNotEmpty()) {
192309            val  options =  baseAnnotationOptions
193-                 .withLatLngs(pointsQueue.map { it.toMapbox() })
310+                 .withLatLngs(
311+                     pointsQueue
312+                         .map { it.toMapbox() }
313+                         .mapToGeodesicIfNeeded()
314+                 )
194315            result.add(AnnotationTracker (options))
195316        }
196317
0 commit comments