Android Question Converting map lat/lng to screen point when close to 180 degrees meridian (Solved)

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Using OSM viewer as posted here:

I noticed problems (lines not being drawn as the clicks on the map) when drawing on the map when close to the 180 degrees meridian (date line) and especially when crossing that line, so for example when a line starts at Lng 178 and moves to Lng -178.

The problem seems to be with this Sub in the above mentioned project. I made an alteration and this has definitely helped, in that now I can draw a polygon across the date line and it shows on the map as intended. There are still problem though for example when moving up the zoom level or just moving the map.

B4X:
'return  TMapPoint from TMapLatLng
Public Sub LatLngToPoint(aLatLng As TMapLatLng, strCallingSub As String) As TMapPoint
    
    Dim d As Double
    Dim dTileSize As Double = MapUtilities.cTileSize
    Dim bLog As Boolean = (strCallingSub = "clsMapShapePolygon, Draw")
    
    'https://www.netzwolf.info/osm/tilebrowser.html?marker.x=12&marker.y=189&tx=1&ty=0&tz=1&ts=256#tile
    Dim v As B4XView = PointToTile(0, 0)
    Dim txy As TMapTileXY = v.tag
    Dim gpx As Double = (txy.fX + (-v.Left / dTileSize)) / fTilesCount
    Dim gpy As Double = (txy.fy + (-v.top / dTileSize)) / fTilesCount
    Dim tx As TMapTileNumberOffset = LngToTileXPlusOffset(aLatLng.fLng, fMap.fZoomLevel)
    Dim ty As TMapTileNumberOffset = LatToTileYPlusOffset(aLatLng.fLat, fMap.fZoomLevel)
    Dim ppx As Double = (tx.fTile + (tx.fOffset / dTileSize)) / fTilesCount
    Dim ppy As Double = (ty.fTile + (ty.fOffset / dTileSize)) / fTilesCount
    
    d = ppx - gpx
    '------------------------------------------------------------------------------------------------------------------
    'this has been added/altered to avoid wrong values (much too high) for p.fX when we have a large longitude eg > 175
    'note that there still can be problems eg when moving up zoom levels or when moving the map
    '------------------------------------------------------------------------------------------------------------------
    If d > 1 Then
        d = d - 1
    End If
    'this was the old code
    '------------------------------
'    Dim p As TMapPoint = MapUtilities.initPoint(fTilesCount * MapUtilities.cTileSize * (ppx - gpx), _
'                                                 fTilesCount * MapUtilities.cTileSize * (ppy - gpy))
    Dim p As TMapPoint = MapUtilities.initPoint(fTilesCount * dTileSize * d, _
                                                fTilesCount * dTileSize * (ppy - gpy))
    
    If bLog Then
        Log("==== LatLngToPoint, Calling sub: " & strCallingSub)
        Log("==== aLatLng.fLng: " & aLatLng.fLng & ", aLatLng.fLat: " & aLatLng.fLat)
        Log("==== fMap.fZoomLevel: " & fMap.fZoomLevel)
        Log("==== v.Left: " & v.Left)
        Log("==== v.Width: " & v.Width)
        Log("==== dTileSize: " & dTileSize)
        Log("==== txy.fX: " & txy.fX & ", txy.fY: " & txy.fY)
        Log("==== gpx: " & gpx)
        Log("==== gpy: " & gpy)
        Log("==== tx.fTile : " & tx.fTile & ", tx.fOffset : " & tx.fOffset)
        Log("==== ty.fTile : " & ty.fTile & ", ty.fOffset : " & ty.fOffset)
        Log("==== ppx: " & ppx)
        Log("==== ppy: " & ppy)
        Log("==== ppx - gpx: " & (ppx - gpx))
        Log("==== ppy - gpy: " & (ppy - gpy))
        Log("==== d: " & d)
        Log("==== fTilesCount: " & fTilesCount)
        Log("==== p.fX: " & p.fX & ", p.fY: " & p.fY)
        Log("=====================================================")
        
    End If
    
    Return p
    
End Sub

There also was a problem with the below Sub (used in Sub LatLngToPoint) in that it produced wrong tile numbers if the Lng was again close to the 180 degrees meridian.
This was fixed with a simple function, CorrectTileNumber, posted below as well.

B4X:
Public Sub LngToTileXPlusOffset(aLng As Double, aZoom As Int) As TMapTileNumberOffset
    
    Dim v As Double = (aLng + 180) / 360 * Power(2, aZoom)
    'Log("cvMap, LngToTileX, v: " & v)
    Dim t As TMapTileNumberOffset
    t.Initialize
    Log("LngToTileXPlusOffset, aLng: " & aLng & ", Floor(" & v & "): " & Floor(v))
    t.fTile = MapUtilities.CorrectTileNumber(Floor(v), aZoom)
    Log("LngToTileXPlusOffset (after correcting), t.fTile: " & t.fTile)
    t.fOffset = (v - Floor(v)) * MapUtilities.cTileSize
    
    Return t
    
End Sub

Sub CorrectTileNumber(iX As Int, iZoom As Int) As Int
    
    Dim iLimit As Int = Power(2, iZoom)
    
    If iX > iLimit - 1 Then
        Return iLimit - iX
    End If
    
    If iX < 0 Then
        Return iLimit + iX
    End If
    
    Return iX
    
End Sub

Altogether it seems dealing with map locations close to the 180 degrees meridian is quite tricky and I would be very interested if somebody
has dealt with this successfully or has some general idea how to deal with this.
Note that I am not using Google maps, but mentioned OSM viewer instead.

RBS
 

emexes

Expert
Licensed User
My first port of call would be this test:

B4X:
Sub AppStart (Args() As String)

    for Mid = 0 to 256 step 256
        For iX = Mid - 5 To Mid + 5
            Log(iX & TAB & CorrectTileNumber(iX, 8))
        Next 'iX
    Next 'Mid

End Sub

Sub CorrectTileNumber(iX As Int, iZoom As Int) As Int
    
    Dim iLimit As Int = Power(2, iZoom)
    
    If iX > iLimit - 1 Then
        Return iLimit - iX
    End If
    
    If iX < 0 Then
        Return iLimit + iX
    End If
    
    Return iX
    
End Sub
Log output:
Waiting for debugger to connect...
Program started.
-5    251
-4    252
-3    253
-2    254
-1    255
0    0
1    1
2    2
3    3
4    4
5    5
251    251
252    252
253    253
254    254
255    255
256    0
257    -1
258    -2
259    -3
260    -4
261    -5
Program terminated (StartMessageLoop was not called).
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
My CorrectTileNumber is only used in that Sub LngToTileXPlusOffset and I haven't seen it fail yet in that situation.
What is the idea of your AppStart test Sub?

RBS
 
Upvote 0

emexes

Expert
Licensed User
What is the idea of your AppStart test Sub?

Are negative tile numbers valid? Feels a bit chancy having that possibility exist, even if you haven't seen it fail yet.

Log output:
253    253
254    254
255    255
256    0
257    -1
258    -2
259    -3
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
seen it fail

Do you have any "bLog" dumps from when the failure occurs?

I did consider that maybe Power(double, double) As double was possibly using natural (2.718...) logarithms and exponentiation and perhaps occasionally it was coming back with a non-integer (non-exact) result eg maybe Power(2, 8) would return 255.9999999999999 thanks to the internal base being "unreal" but I tested it up to 2**40 and it works just perfectly.
 
Upvote 0

emexes

Expert
Licensed User
I noticed problems (lines not being drawn as the clicks on the map) when drawing on the map when close to the 180 degrees meridian (date line) and especially when crossing that line, so for example when a line starts at Lng 178 and moves to Lng -178.

One of the problems is to do with lines that cross the wraparound meridian - is this correct? Is the problem that no line is drawn?

I am imagining that the user is clicking on your map, that the click points are added to a list, and then you are (re)drawing a bounding polygon around all the points on the list - is this correct?

And that sometimes when the user clicks on the map, no new line is drawn (as in the polygon is unchanged, and the number of bounding edges doesn't change). - is this correct?

When that happens, is the clicked point still added to the point list? ie does the number of points increase by one, even though the number of bounding edges doesn't?
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
> Do you have any "bLog" dumps from when the failure occurs?

Yes, forgot to post that.
Attached a file with such a log dump.
This is after I drew a polygon at zoom level 5, moving around a point on the 180 Meridian.
The dumps are triggered by increasing the zoom levels.
Up to zoom level 7 all is fine, but then it goes wrong, which you can see from there being values of p.fX that are too high.
Even though the polygon shape might be OK on the map, moving the map may change the lines and indeed this is wrapping
the wrong way around the globe.

Indeed the polygon is made by user map clicks, adding to a list of lat/lng types.
Every map click will add an item to the list, unless the previous type holds the same lat/lng as the new type.

This is the code that draws the polygon:

B4X:
'draw the shape
'---------------------------------------------------------
Public Sub Draw
    
    Dim tLL1 As TMapLatLng
    Dim tLL2 As TMapLatLng
    Dim p As B4XPath
    
    'Log("clsMapShapePolygon, Public Sub Draw")
    
    '>>> RBS altered 05/03/22
    '------------------------

    If fShape.fLatLng.IsInitialized = False Then Return
    'Log("fShape.fLatLng.Size: " & fShape.fLatLng.Size)
    If fShape.fLatLng.Size = 0 Then Return

    tLL1 = fShape.fLatLng.Get(0)
    
    'very unlikely first point will be at the 180 meridian, but may have to accomodate that
    '--------------------------------------------------------------------------------------
    Dim ptStart As TMapPoint = fcvMap.LatLngToPoint(tLL1, "clsMapShapePolygon, Draw") 'first polygon point
    p.Initialize(ptStart.fX, ptStart.fY)
    
    'Log(">>> cMSP, Draw(0), tLL1.fLng: " & tLL1.fLng & ", ptStart.fX: " & ptStart.fX)
    
    For i = 1 To fShape.fLatLng.Size - 1
        tLL2 = fShape.fLatLng.Get(i)
        Dim ptNext As TMapPoint = fcvMap.LatLngToPoint(tLL2, "clsMapShapePolygon, Draw")
        p.LineTo(ptNext.fX, ptNext.fY)
        'Log(">>> cMSP, Draw(" & i & "), tLL2.fLng: " & tLL2.fLng & ", ptNext.fX: " & ptNext.fX)
        tLL1 = tLL2
    Next
    
    'Dim pt As TMapPoint = fcvMap.LatLngToPoint(fShape.fLatLng.Get(0)) '<<<<<<< duplicate, was already set above
    
    'p.LineTo(ptStart.fx, ptStart.fy) 'we don't need to add a closing line as a route shape, doesn't have to be closed
    'and an area shape has already the duplicate starting/ending Lat/Lon
    'Log("clsMapShapePolygon, Draw")
    fcvMap.ShapeCanvas.DrawPath(p, fShape.fColor, fShape.fFilled, fShape.fStrokeWidth)

End Sub

RBS
 

Attachments

  • LatLng2Point_LogDump.zip
    2.4 KB · Views: 23
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Just had look at the sent log file and this is not quite right:

==== txy.fX: -2, txy.fY: 19

txy.fX should have a value between 0 and a max Long value depending on the zoom level.
For zoom 5 that max value is 31 that is:

B4X:
lMaxTileIDX = Power(2, iZoom)

The reason that -2 showed up is due a bug that I introduced, leaving off a tile check when the tile image is not added to the DB.
That was wrong as now that value is needed, even if there is no tile image (my map doesn't show images for NE Siberia!).
Showing the right tile index (not -2 for example) unfortunately doesn't fix the discussed problem straight away, but at least I am starting now
with the right data.

RBS
 
Upvote 0

emexes

Expert
Licensed User
My memory is not what it used to be and I am still trying to get my head around which x-y variables refer to screen coordinates, tile coordinates, or offset-within-tile coordinates, and whether the offsets are in pixels or proportion-of-tile (or maybe degrees).
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Agree this is confusing and some comments in the OSM Viewer code would have been very helpful.
I am not quite sure myself now what is what.
Will have a look.

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Now with the right tile numbers (added as a type to the view tag) this is the log dump I get from clicking just left of the 180 meridian and then
just right of the 180 meridian:


-----------------------------------------------------
==== LatLngToPoint, Calling sub: clsMapShapePolygon, Draw
==== aLatLng.fLng: 177.861328125, aLatLng.fLat: 53.40079915056907
==== fMap.fZoomLevel: 5
==== v.Left: -470
==== v.Width: 768
==== dTileSize: 768
==== txy.fX: 30, txy.fY: 9
==== gpx: 0.9566243489583334
==== gpy: 0.2869466145833333
==== tx.fTile : 31, tx.fOffset : 621.9999999999991
==== ty.fTile : 10, ty.fOffset : 280.0000000000018
==== ppx: 0.9940592447916666
==== ppy: 0.32389322916666674
==== ppx - gpx: 0.03743489583333326
==== ppy - gpy: 0.036946614583333426
==== d: 0.03743489583333326
==== fTilesCount: 32
==== p.fX: 919, p.fY: 908
-----------------------------------------------------
=====================================================
-----------------------------------------------------
==== LatLngToPoint, Calling sub: clsMapShapePolygon, Draw
==== aLatLng.fLng: -177.2900390625, aLatLng.fLat: 53.52289393456801
==== fMap.fZoomLevel: 5
==== v.Left: -470
==== v.Width: 768
==== dTileSize: 768
==== txy.fX: 30, txy.fY: 9
==== gpx: 0.9566243489583334
==== gpy: 0.2869466145833333
==== tx.fTile : 0, tx.fOffset : 185
==== ty.fTile : 10, ty.fOffset : 265.99999999999955
==== ppx: 0.007527669270833333
==== ppy: 0.3233235677083333
==== ppx - gpx: -0.9490966796875
==== ppy - gpy: 0.036376953125
==== d: -0.9490966796875
==== fTilesCount: 32
==== p.fX: -23325, p.fY: 894
-----------------------------------------------------

The result is clearly wrong as the line starts at the right point, but moves off the screen to the left, way too far.
The above initial values look fine, but clearly p.fX is wrong.
Note that now I don't need the Sub CorrectTileNumber anymore and also left out the d correction, so this is the code
of Sub LatLngToPoint:

B4X:
'return  TMapPoint from TMapLatLng
Public Sub LatLngToPoint(aLatLng As TMapLatLng, strCallingSub As String) As TMapPoint
    
    Dim d As Double
    Dim dTileSize As Double = MapUtilities.cTileSize
    Dim bLog As Boolean = (strCallingSub = "clsMapShapePolygon, Draw")
    
    'https://www.netzwolf.info/osm/tilebrowser.html?marker.x=12&marker.y=189&tx=1&ty=0&tz=1&ts=256#tile
    Dim v As B4XView = PointToTile(0, 0)
    Dim txy As TMapTileXY = v.tag
    Dim gpx As Double = (txy.fX + (-v.Left / dTileSize)) / fTilesCount
    Dim gpy As Double = (txy.fy + (-v.top / dTileSize)) / fTilesCount
    Dim tx As TMapTileNumberOffset = LngToTileXPlusOffset(aLatLng.fLng, fMap.fZoomLevel)
    Dim ty As TMapTileNumberOffset = LatToTileYPlusOffset(aLatLng.fLat, fMap.fZoomLevel)
    Dim ppx As Double = (tx.fTile + (tx.fOffset / dTileSize)) / fTilesCount
    Dim ppy As Double = (ty.fTile + (ty.fOffset / dTileSize)) / fTilesCount
    
    d = ppx - gpx
    '------------------------------------------------------------------------------------------------------------------
    'this has been added/altered to avoid wrong values (much too high) for p.fX when we have a large longitude eg > 175
    'note that there still can be problems eg when moving up zoom levels or when moving the map
    '------------------------------------------------------------------------------------------------------------------
'    If d > 1 Then
'        d = d - 1
'    Else
'        If d < 0 Then
'            d = 0 - d
'        End If
'    End If
    
    'this was the old code
    '------------------------------
'    Dim p As TMapPoint = MapUtilities.initPoint(fTilesCount * MapUtilities.cTileSize * (ppx - gpx), _
'                                                 fTilesCount * MapUtilities.cTileSize * (ppy - gpy))
    Dim p As TMapPoint = MapUtilities.initPoint(fTilesCount * dTileSize * d, _
                                                fTilesCount * dTileSize * (ppy - gpy))
    
    If bLog Then
        Log("-----------------------------------------------------")
        Log("==== LatLngToPoint, Calling sub: " & strCallingSub)
        Log("==== aLatLng.fLng: " & aLatLng.fLng & ", aLatLng.fLat: " & aLatLng.fLat)
        Log("==== fMap.fZoomLevel: " & fMap.fZoomLevel)
        Log("==== v.Left: " & v.Left)
        Log("==== v.Width: " & v.Width)
        Log("==== dTileSize: " & dTileSize)
        Log("==== txy.fX: " & txy.fX & ", txy.fY: " & txy.fY)
        Log("==== gpx: " & gpx)
        Log("==== gpy: " & gpy)
        Log("==== tx.fTile : " & tx.fTile & ", tx.fOffset : " & tx.fOffset)
        Log("==== ty.fTile : " & ty.fTile & ", ty.fOffset : " & ty.fOffset)
        Log("==== ppx: " & ppx)
        Log("==== ppy: " & ppy)
        Log("==== ppx - gpx: " & (ppx - gpx))
        Log("==== ppy - gpy: " & (ppy - gpy))
        Log("==== d: " & d)
        Log("==== fTilesCount: " & fTilesCount)
        Log("==== p.fX: " & p.fX & ", p.fY: " & p.fY)             '
        Log("-----------------------------------------------------")
    End If
    
    Return p
    
End Sub

RBS
 
Upvote 0

emexes

Expert
Licensed User
This is a more-arse-than-class fix, but:

if p.FX < 0 then add dTileSize * fTileCount 'ie add number of pixels of full 360 degrees

so for the example bLog in post #14

p.FX is -23325, so add 768 * 32 = 1251

I'm not 100% happy with it but it's after midnight here so I'm giving it a break for a while.

the conversion of degrees longitude to tx fTile and fOffset appears to be correct
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
This is the best I can do for now, still not 100% OK.
For example when we have a polygon, drawn at zoom 5, closely around a point of the 180 meridian then all looks fine.
However if we move the map east then at about Lng -164 we will see the lines going around the globe.
Moving back west will make it all OK again.
This doesn't happen when I draw the polygon at somewhere in the UK and then move east of west.

B4X:
'return  TMapPoint from TMapLatLng
Public Sub LatLngToPoint(aLatLng As TMapLatLng, strCallingSub As String) As TMapPoint
    
    Dim iZoom As Int = fMap.fZoomLevel
    Dim lTilePosOfLatLng As Long
    Dim iTileSize As Int = MapUtilities.cTileSize
    Dim bLog As Boolean = (strCallingSub = "clsMapShapePolygon, Draw")
    
    'https://www.netzwolf.info/osm/tilebrowser.html?marker.x=12&marker.y=189&tx=1&ty=0&tz=1&ts=256#tile
    
    'top-left tile
    '-------------
    Dim v As B4XView = PointToTile(0, 0) 
    Dim txy As TMapTileXY = v.tag
    Dim gpx As Double = (txy.fX + (-v.Left / iTileSize)) / fTilesCount
    Dim gpy As Double = (txy.fy + (-v.top / iTileSize)) / fTilesCount
    
    'tile holding the lat/lng of the provided TMapLatLng
    '---------------------------------------------------
    Dim tx As TMapTileNumberOffset = LngToTileXPlusOffset(aLatLng.fLng, iZoom)
    Dim ty As TMapTileNumberOffset = LatToTileYPlusOffset(aLatLng.fLat, iZoom)
    
    'correct for if the tile number is lower than the tile number of the top-left tile
    If tx.fTile < txy.fX Then
        lTilePosOfLatLng = Power(2, fMap.fZoomLevel) + tx.fTile
    Else
        lTilePosOfLatLng = tx.fTile
    End If
    
    Dim ppx As Double = (lTilePosOfLatLng + (tx.fOffset / iTileSize)) / fTilesCount
    Dim ppy As Double = (ty.fTile + (ty.fOffset / iTileSize)) / fTilesCount

    Dim p As TMapPoint = MapUtilities.initPoint(fTilesCount * iTileSize * (ppx - gpx), _
                                                 fTilesCount * iTileSize * (ppy - gpy))

    If bLog Then
        Log("-----------------------------------------------------")
        Log("==== LatLngToPoint, Calling sub: " & strCallingSub)
        Log("==== aLatLng.fLng: " & aLatLng.fLng & ", aLatLng.fLat: " & aLatLng.fLat)
        Log("==== iZoom: " & iZoom)
        Log("==== v.Left: " & v.Left)
        Log("==== v.Width: " & v.Width)
        Log("==== iTileSize: " & iTileSize)
        Log("==== txy.fX: " & txy.fX & ", txy.fY: " & txy.fY)
        Log("==== gpx: " & gpx)
        Log("==== gpy: " & gpy)
        Log("==== tx.fTile : " & tx.fTile & ", tx.fOffset : " & tx.fOffset)
        Log("==== ty.fTile : " & ty.fTile & ", ty.fOffset : " & ty.fOffset)
        Log("==== lTilePosOfLatLng: " & lTilePosOfLatLng)
        Log("==== ppx: " & ppx)
        Log("==== ppy: " & ppy)
        Log("==== ppx - gpx: " & (ppx - gpx))
        Log("==== ppy - gpy: " & (ppy - gpy))
        Log("==== fTilesCount: " & fTilesCount)
        Log("==== p.fX: " & p.fX & ", p.fY: " & p.fY)             '
        Log("-----------------------------------------------------")
    End If
    
    Return p
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Thanks, will look at that.
I suppose it might all be more relevant for you.
I know you are in Australia, but New Zealand is not that far from that dreaded date line.

RBS


 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Thanks, will look at that.
I suppose it might all be more relevant for you.
I know you are in Australia, but New Zealand is not that far from that dreaded date line.

RBS
Managed to draw a polygon (purple) around a point on the 180 meridian.
Managed to (kind of) get random points (red) in that polygon.
Less so managed to get the convexhull (black) around those points.
Quite a way to go.
See attached image.

RBS
 

Attachments

  • DateLine.zip
    182 KB · Views: 29
Upvote 0

emexes

Expert
Licensed User
An overnight epiphany (maybe!)

How about creating and drawing the bounding polygon using the screen pixel (x, y) values rather than the (longitude, latitude) values?

The pixel x axis would be minus-half to plus-half rather than 0 to full.

Eg at zoom 5 with 768-wide tiles, the full "width" (circle around) of earth is 2**5 * 768 = 24576 pixels, and you'd use x values -12288 to +12287 rather than 0 to 24575
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…