some time ago I navigated on this page https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames where are present all information how to access to OpenStreetMap from any application/language.
So during some lunch pauses I created a fiveWin Control, it is only an rough version:
- Code: Select all Expand view RUN
- #include <fivewin.ch>
********** TEST CODE **********
proc main
LOCAL oWnd, oMap
DEFINE WINDOW oWnd TITLE "3D objects"
SetWndDefault(oWnd)
oMap := TMapControl():New()
oMap:SetCenter(-4.806640,36.505522)
oMap:AddMarker(-4.806640,36.505522)
oWnd:oClient := oMap
ACTIVATE WINDOW oWnd
*******************************
/// https://wiki.openstreetmap.org/wiki/Sli ... _tilenames
class TMapControl FROM TControl
CLASSDATA lRegistered AS LOGICAL
CLASSDATA aServers AS ARRAY INIT {'https://a.tile.openstreetmap.org/{zoom}/{x}/{y}.png', ;
'https://b.tile.openstreetmap.org/{zoom}/{x}/{y}.png', ;
'https://c.tile.openstreetmap.org/{zoom}/{x}/{y}.png'}
CLASSDATA nMaxZoom AS NUMERIC INIT 19
DATA aHttps, oTimer
DATA nZoom, nLat, nLon
DATA nServer
DATA aMarkers AS ARRAY INIT {}
DATA aImages AS ARRAY INIT {}
DATA aTopLeftTileInfo
DATA lastMousePos
METHOD New( nRow, nCol, oWnd, nWidth, nHeight ) CONSTRUCTOR
METHOD End()
METHOD Display() INLINE ::BeginPaint(),::Paint(),::EndPaint(),0
METHOD Paint()
Method SetCenter(nLon,nLat)
Method AddMarker(nLon,nLat)
METHOD RButtonDown()
METHOD LButtonDown()
METHOD MouseMove()
METHOD MouseWheel( )
METHOD TimerEvent()
METHOD GetImage(x,y,zoom) HIDDEN
METHOD GetTileNumber(lon,lat,zoom)
METHOD GetCoordsFromTile(x,y,zoom)
METHOD GetCoordsFromPixel(x,y)
endclass
METHOD New( nRow, nCol, oWnd, nWidth, nHeight ) CLASS TMapControl
DEFAULT nRow := 10, nCol := 10, oWnd := GetWndDefault()
DEFAULT nWidth := 500
DEFAULT nHeight := 300
::nZoom := 15
::nLon := 0
::nLat := 0
::nServer := 1
::aMarkers := {}
::oWnd := oWnd
::nId := ::GetNewId()
::nStyle := nOR( WS_CHILD, WS_VISIBLE, WS_TABSTOP, WS_BORDER )
::nTop := nRow
::nLeft := nCol
::nBottom := ::nTop + nHeight - 1
::nRight := ::nLeft + nWidth
::aHttps := {}
::Register( nOR( CS_VREDRAW, CS_HREDRAW ) )
if ! Empty( oWnd:hWnd )
::Create( )
oWnd:AddControl( Self )
else
oWnd:DefControl( Self )
endif
DEFINE TIMER ::oTimer OF SELF INTERVAL 0.1 ACTION ::TimerEvent()
::oTimer:Activate()
return Self
METHOD End() class TMapControl
if .not. empty(::oTimer)
::oTimer:Deactivate()
::oTimer:End()
endif
return ::Super:End()
Method SetCenter(nLon,nLat) class TMapControl
local r := {::nLon,::nLat}
::nLon := nLon
::nLat := nLat
return r
Method AddMarker(nLon,nLat) class TMapControl
return aAdd(::aMarkers, {nLon,nLat})
METHOD Paint() class TMapControl
LOCAL x,y, img, ix,iy, top, left, t,l, sx, sy
LOCAL w := ::nWidth, h := ::nHeight
// get tile of center
x := ::GetTileNumber(::nLon,::nLat)
y := x[2]
x := x[1]
// move the desired pixel in the centre of canvas
sx := floor(x)
sy := floor(y)
top := h/2 - int((y-sy)*256)
left := w/2 - int((x-sx)*256)
// check for fill all area
do while top>0
sy-=1
top-=256
enddo
do while left>0
sx-=1
left-=256
enddo
// draw the map
for iy:=0 to ceiling(h/256)
for ix:=0 to ceiling(w/256)
img := ::GetImage(sx+ix,sy+iy)
if valtype(img)="A" .and. .not. empty(img[1])
t := top+iy*256
l := left+ix*256
FW_DrawImage(::hDC, img, {t,l,t+256,l+256})
endif
next
next
// draw the markers
for ix:=1 to len(::aMarkers)
x := ::GetTileNumber(::aMarkers[ix,1],::aMarkers[ix,2])
y := (x[2] - sy) * 256 + top
x := (x[1] - sx) * 256 + left
MoveTo(::hDC,x-5,y-5)
LineTo(::hDC,x+5,y+5)
MoveTo(::hDC,x-5,y+5)
LineTo(::hDC,x+5,y-5)
next
// save these infos
::aTopLeftTileInfo := {top,left,sx,sy}
return nil
METHOD TimerEvent() class TMapControl
LOCAL lRedraw := .F., oHttp, img, idx
for idx:=1 to len(::aHttps)
oHttp := ::aHttps[idx]
if oHttp[1]:readyState = 4
img := FW_ReadImage(Self, oHttp[1]:ResponseBody())
if .not. empty(img) .and. .not. empty(img)
lRedraw := .T.
aAdd(::aImages, { img, oHttp[2], oHttp[3], oHttp[4] })
endif
hb_ADel(::aHttps,idx,.t.)
endif
next
if lRedraw
::Refresh(.F.)
endif
return nil
// directly from OpenStreetMap wiki
METHOD GetTileNumber(lon,lat,zoom) class TMapControl
LOCAL x,y,n, latRad
DEFAULT zoom := ::nZoom
n := hb_bitShift(1, zoom)
latRad := lat * PI() / 180
x := n * (lon + 180) / 360
y := n * (1-(log(tan(latRad) + 1/cos(latRad)) / PI())) / 2
do while(x<0)
x+=n
enddo
do while(x>=n)
x-=n
enddo
if y<0
y:=0
endif
if y>=n
y:=n-1
endif
return {x,y}
// directly from OpenStreetMap wiki
METHOD GetCoordsFromTile(x,y,zoom) class TMapControl
LOCAL lon, lat, n, lat_rad
DEFAULT zoom := ::nZoom
n := hb_bitShift(1, zoom)
lon = x / n * 360.0 - 180.0
lat_rad = atan(sinh(PI() * (1 - 2 * y / n)))
lat = lat_rad * 180.0 / PI()
return {lon,lat}
// screen
METHOD GetCoordsFromPixel(x,y) class TMapControl
LOCAL top := ::aTopLeftTileInfo[1]
LOCAL left := ::aTopLeftTileInfo[2]
LOCAL sx := ::aTopLeftTileInfo[3]
LOCAL sy := ::aTopLeftTileInfo[4]
return ::GetCoordsFromTile(sx+(x-left)/256,sy+(y-top)/256)
METHOD LButtonDown(nRow, nCol, nKeyFlags, lTouch) class TMapControl
::lastMousePos := {nRow,nCol}
return ::Super:LButtonDown( nRow, nCol, nKeyFlags, lTouch )
METHOD RButtonDown(nRow, nCol, nKeyFlags, lTouch) class TMapControl
::lastMousePos := {nRow,nCol}
return ::Super:RButtonDown( nRow, nCol, nKeyFlags, lTouch )
METHOD MouseMove( nRow, nCol, nKeyFlags ) class TMapControl
LOCAL oldMouseCoords,newMouseCoords
if nKeyFlags<>1 .and. nKeyFlags<>2
return 0
endif
oldMouseCoords := ::GetCoordsFromPixel(::lastMousePos[2],::lastMousePos[1])
newMouseCoords := ::GetCoordsFromPixel(nCol,nRow)
::nLon += oldMouseCoords[1] - newMouseCoords[1]
::nLat += oldMouseCoords[2] - newMouseCoords[2]
::lastMousePos := {nRow,nCol}
::Refresh(.F.)
return ::Super:MouseMove( nRow, nCol, nKeyFlags )
METHOD MouseWheel( nKey, nDelta, nXPos, nYPos ) class TMapControl
if nDelta>0 .and. ::nZoom<::nMaxZoom
::nZoom+=1
endif
if nDelta<0 .and. ::nZoom>0
::nZoom-=1
endif
::Refresh(.T.)
return ::Super:MouseWheel( nKey, nDelta, nXPos, nYPos )
METHOD GetImage(x,y,zoom) class TMapControl
local n, cUrl, img
LOCAL oHttp
DEFAULT zoom := ::nZoom
x:=int(x)
y:=int(y)
// looking for the image in the "cache"
n:= aScan(::aImages, {|v| v[2]=zoom .and. v[3]=x .and. v[4]=y })
if n>0
// TODO: move the last returned image on top of cache
return ::aImages[n][1]
endif
// TODO: Limit cache size
cUrl := ::aServers[::nServer]
::nServer++
if ::nServer>len(::aServers)
::nServer:=1
endif
cUrl := StrTran(cUrl,"{zoom}", allTrim(str(zoom)))
cUrl := StrTran(cUrl,"{x}", allTrim(str(x)))
cUrl := StrTran(cUrl,"{y}", allTrim(str(y)))
if aScan(::aHttps, {|x| x[5] = cUrl}) > 0
return nil
endif
begin sequence
oHttp := win_oleCreateObject( "Msxml2.XMLHTTP.6.0" )
oHttp:Open("GET", cUrl, .T. )
oHttp:Send()
if oHttp:readyState <> 4
if oHttp:readyState=1 .or. oHttp:readyState=3
aAdd(::aHttps,{ oHttp, zoom, x, y, cUrl })
endif
else
img := FW_ReadImage(Self, oHttp:ResponseBody())
if .not. empty(img) .and. .not. empty(img)
aAdd(::aImages, { img, zoom, x, y })
endif
endif
end sequence
return img
I think it is pretty cool