FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour Experiment: MapControl
Posts: 375
Joined: Tue Feb 10, 2015 09:48 AM
Re: Experiment: MapControl
Posted: Thu Mar 21, 2019 08:43 AM
Silvio, thank you for your feedback.
Here how draw the marker you post:

Code (fw): Select all Collapse
#include <fivewin.ch>

********** TEST CODE **********
proc main
    LOCAL oWnd, oMap, oMarker, bDraw
    DEFINE WINDOW oWnd TITLE "Map Test"
    SetWndDefault(oWnd)
    oMarker := FW_ReadImage(oWnd,"667px-Map_marker.svg.png")
    oMap := TMapControl():New() 
    oMap:SetCenter(13.7,42.6,10)
    bDraw := {|hDC,x,y| FW_DrawImage(hDC, oMarker, {y-30,x-10,y,x+10},.t.) }
    oMap:AddMarker(13.7025,42.6582,30,bDraw)
    oWnd:oClient := oMap    
    ACTIVATE WINDOW oWnd
*******************************

/// <!-- m --><a class="postlink" href="https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames">https://wiki.openstreetmap.org/wiki/Sli ... _tilenames</a><!-- m -->
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
    CLASSDATA bDefaultMarkerDraw as CodeBlock
    DATA oTimer
    DATA aHttps             // Queue of downloading images, array of { "Msxml2.XMLHTTP.6.0", zoom, x, y, Seconds(), cUrl }
    DATA nZoom, nLat, nLon  // current zoom and center of the screen
    DATA nServer            // Last used server Id (from aServers)
    DATA aMarkers AS ARRAY INIT {}   // Couples of lat+lon to mark on map
    DATA aImages AS ARRAY INIT {}    // Array of loaded images
    DATA aTopLeftTileInfo            // Information about tile at top left corner { xpos, ypos, x-tile, y-tile }
    DATA lastMousePos, lastRenderTime   // mouse down position and seconds of last rendering

    METHOD New( nRow, nCol, oWnd, nWidth, nHeight ) CONSTRUCTOR
    METHOD End()
    METHOD Display() INLINE ::BeginPaint(),::Paint(),::EndPaint(),0
    METHOD EraseBkGnd( ) INLINE 1
    METHOD Paint()
    
    Method SetCenter(nLon,nLat,zoom)
    Method AddMarker(nLon,nLat,pixelSize,bDraw

    METHOD RButtonDown(nRow, nCol, nKeyFlags, lTouch)
    METHOD LButtonDown(nRow, nCol, nKeyFlags, lTouch)
    METHOD MouseMove( nRow, nCol, nKeyFlags )
    METHOD MouseWheel( nKey, nDelta, nXPos, nYPos )

    METHOD TimerEvent()
    
    METHOD GetImage(x,y,zoom,lQueue) HIDDEN
    METHOD GetTileNumber(lon,lat,pixelSize,zoom)
    METHOD GetCoordsFromTile(x,y,zoom)  
    METHOD GetCoordsFromPixel(x,y)
   
   METHOD PaintTile(hDCMem,hDCBmp,l,t,tx,ty,zoom) HIDDEN
       
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()

    if empty(::bDefaultMarkerDraw)
        ::bDefaultMarkerDraw := {|hDC,x,y| MoveTo(hDC,x-5,y-5), LineTo(hDC,x+5,y+5),MoveTo(hDC,x-5,y+5),LineTo(hDC,x+5,y-5) }
    endif
return Self

METHOD End() class TMapControl
    if .not. empty(::oTimer)
        ::oTimer:Deactivate()
        ::oTimer:End()
    endif
return ::Super:End()

Method SetCenter(nLon,nLat,zoom) class TMapControl
    local r := {::nLon,::nLat}
    ::nLon := nLon
    ::nLat := nLat
    ::nZoom := zoom
return r

Method AddMarker(nLon,nLat,pixelSize,bDraw) class TMapControl
    default bDraw := ::bDefaultMarkerDraw
    default pixelSize := 5
return aAdd(::aMarkers, {nLon,nLat, pixelSize,bDraw})

#define SRCCOPY 13369376

METHOD Paint() class TMapControl
    LOCAL x,y, img, ix,iy, top, left, sx,sy,  hBmpMem
    LOCAL w := ::nWidth,  h := ::nHeight, hDCMem, hDCBmp
   hDCMem  = CreateCompatibleDC( ::hDC )
   hBmpMem = CreateCompatibleBitmap( ::hDC, w, h )
   SelectObject( hDCMem, hBmpMem )
   FillRect( hDCMem, {0,0,h,w}, ::oBrush:hBrush )
    // 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
   ::lastRenderTime := Seconds()
   hDCBmp := CreateCompatibleDC( ::hDC )
    for iy:=0 to ceiling((h-top)/256)
        for ix:=0 to ceiling((w-left)/256)
         ::PaintTile(hDCMem,hDCBmp,left+ix*256,top+iy*256 ,sx+ix,sy+iy,::nZoom)
        next
    next
   DeleteDC( hDCBmp )
    // 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
        if x>-::aMarkers[ix,3] .and. x<w+::aMarkers[ix,3] .and. ;
            y>-::aMarkers[ix,3] .and. Y<h+::aMarkers[ix,3]
            Eval(::aMarkers[ix,4],hDCMem,x,y )
        endif
    next
    // save these infos
    ::aTopLeftTileInfo := {top,left,sx,sy}
   
   //img := "Queue len: " + alltrim(str(len(::aHttps)))
   //TextOut( hDCMem, 4, 4, img, Len( img ) )
   BitBlt( ::hDC, 0,0, w, h, hDCMem, 0,0, SRCCOPY )
   DeleteDC(hDCMem)
return nil

METHOD PaintTile(hDCMem,hDCBmp,l,t,tx,ty,zoom)
   LOCAL img, sx,sy, ix, iy, n
   img := ::GetImage(tx,ty,zoom)
   if .not. empty(img)
         SelectObject(hDCBmp, img)
         BitBlt( hDCMem, l,t, 256, 256, hDCBmp, 0,0, SRCCOPY )
            return nil
   endif
   
   // try less zoomed images (if they are not in cache it are not downloaded)
   img := ::GetImage(hb_bitShift(tx,-1),hb_bitShift(ty,-1),zoom-1,.F.)
   if .not. empty(img)
      SelectObject(hDCBmp, img)
      StretchBlt( hDCMem, l,t, 256, 256, hDCBmp, hb_bitAnd(tx,1)*128,hb_bitAnd(ty,1)*128,128,128, SRCCOPY )
      return nil
   endif
   
   
   sx := hb_bitShift(tx,1)
   sy := hb_bitShift(ty,1)
   for iy:=0 to 1 
      for ix:=0 to 1 
         img := ::GetImage(sx+ix,sy+iy,zoom+1,.F.)
         if .not. empty(img)
            SelectObject(hDCBmp, img)
            StretchBlt( hDCMem, l+ix*128,t+iy*128, 128, 128, hDCBmp, 0,0,255,255, SRCCOPY )
         endif            
      next
   next
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
         // downloaded a missing image!
            img := GDIP_ImageFromStr(oHttp[1]:ResponseBody(), .t., .f.)
            if .not. empty(img) // correctly created
                lRedraw := .T.
                aAdd(::aImages, { img, oHttp[2], oHttp[3], oHttp[4] })
            endif
            hb_ADel(::aHttps,idx,.t.)
      else 
         // stop the unfinisched download that are not relative of current view
         if oHttp[5]<::lastRenderTime
            oHttp[1]:abort()
            hb_ADel(::aHttps,idx,.t.)
         endif
        endif
    next
    
    if lRedraw
        ::Refresh()
    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 to tile, with decimal, using aTopLeftTileInfo
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) .or. empty(::lastMousePos)
        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()
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()
return ::Super:MouseWheel( nKey, nDelta, nXPos, nYPos )


METHOD GetImage(x,y,zoom,lQueue) class TMapControl
    local n, cUrl, img
    LOCAL oHttp
    DEFAULT zoom := ::nZoom
   DEFAULT lQueue := .T.
    
    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
        // move the last returned image on top of cache
      img := ::aImages[n][1]
      //aDel(::aImages,n)
      //::aImages[len(::aImages)] := img
        return img
    endif
   if .not. lQueue
      return nil
   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 (n:=aScan(::aHttps, {|v| v[2] = zoom .and. v[3] = x .and. v[4] = y})) > 0
      // already in download, update last query time
      ::aHttps[n,5]:=Seconds()
        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, Seconds(), cUrl })
            endif
        else
            img := GDIP_ImageFromStr(oHttp:ResponseBody(), .t., .f.)
            if .not. empty(img)
                aAdd(::aImages, { img, zoom, x, y })
            endif
        endif
    end sequence
return img


Posts: 7317
Joined: Thu Oct 18, 2012 07:17 PM
Re: Experiment: MapControl
Posted: Thu Mar 21, 2019 09:08 AM

thanks ..
and If I wish found a specific street ?
Do you saw also for the routers I wrote above?

Since from 1991/1992 ( fw for clipper Rel. 14.4 - Momos)

I use : FiveWin for Harbour March-April 2024 - Harbour 3.2.0dev (harbour_bcc770_32_20240309) - Bcc7.70 - xMate ver. 1.15.3 - PellesC - mail: silvio[dot]falconi[at]gmail[dot]com
Posts: 375
Joined: Tue Feb 10, 2015 09:48 AM
Re: Experiment: MapControl
Posted: Thu Mar 21, 2019 09:34 AM
nageswaragunupudi wrote:This is very good.
But as a user from India, it is not of much use for us. This is still not as universally useful as Google. For all practical purposes, we still need Google. The same may be the case with many countries.

According to this https://gis.stackexchange.com/questions/228162/giving-google-api-key-in-google-street-map-used-by-leaflet-map/228163 and https://stackoverflow.com/questions/50672846/how-to-load-a-google-maps-baselayer-in-leaflet-after-june-2018 it is possible use Google maps.
But I did not study it enough

Code (fw): Select all Collapse
********** TEST CODE **********
proc main
   LOCAL oWnd, oMap, oMarker, bDraw
   DEFINE WINDOW oWnd TITLE "Map Test"
   SetWndDefault(oWnd)
   oMarker := FW_ReadImage(oWnd,"667px-Map_marker.svg.png")
   oMap := TMapControl():New()   
   oMap:aServers := {'http://mt0.google.com/vt/lyrs=s,h&x={x}&y={y}&z={zoom}', ;
                     'http://mt1.google.com/vt/lyrs=s,h&x={x}&y={y}&z={zoom}', ;
                     'http://mt2.google.com/vt/lyrs=s,h&x={x}&y={y}&z={zoom}', ;
                     'http://mt3.google.com/vt/lyrs=s,h&x={x}&y={y}&z={zoom}' ;
                     }
   oMap:nMaxZoom := 20
   oMap:SetCenter(13.7,42.6,10)
   bDraw := {|hDC,x,y| FW_DrawImage(hDC, oMarker, {y-30,x-10,y,x+10},.t.) }
   oMap:AddMarker(13.7025,42.6582,30,bDraw)
   oWnd:oClient := oMap   
   ACTIVATE WINDOW oWnd
*******************************


And use other providers: https://leaflet-extras.github.io/leafle ... s/preview/
Posts: 7317
Joined: Thu Oct 18, 2012 07:17 PM
Re: Experiment: MapControl
Posted: Mon Mar 25, 2019 09:50 AM

How make sample to search a address ?
sample : Avda. del Rosario 34-A 29604 Marbella - Spain
it can be usefull utility for show a customer address

Since from 1991/1992 ( fw for clipper Rel. 14.4 - Momos)

I use : FiveWin for Harbour March-April 2024 - Harbour 3.2.0dev (harbour_bcc770_32_20240309) - Bcc7.70 - xMate ver. 1.15.3 - PellesC - mail: silvio[dot]falconi[at]gmail[dot]com
Posts: 7317
Joined: Thu Oct 18, 2012 07:17 PM
Re: Experiment: MapControl
Posted: Wed Feb 02, 2022 12:36 PM

Antonino, any solution ?

Since from 1991/1992 ( fw for clipper Rel. 14.4 - Momos)

I use : FiveWin for Harbour March-April 2024 - Harbour 3.2.0dev (harbour_bcc770_32_20240309) - Bcc7.70 - xMate ver. 1.15.3 - PellesC - mail: silvio[dot]falconi[at]gmail[dot]com
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
Re: Experiment: MapControl
Posted: Thu Feb 03, 2022 09:05 PM
Posts: 7317
Joined: Thu Oct 18, 2012 07:17 PM
Re: Experiment: MapControl
Posted: Sat Feb 19, 2022 08:01 AM
Antonino,
on your class there is the possibility to add lang and Lot
but need a query method to search the address, city and country

for a sample

https://www.openstreetmap.org/search?qu ... %20Pescara
Since from 1991/1992 ( fw for clipper Rel. 14.4 - Momos)

I use : FiveWin for Harbour March-April 2024 - Harbour 3.2.0dev (harbour_bcc770_32_20240309) - Bcc7.70 - xMate ver. 1.15.3 - PellesC - mail: silvio[dot]falconi[at]gmail[dot]com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Experiment: MapControl
Posted: Sat Feb 19, 2022 04:18 PM

Dear Silvio,

Do you mean to allow the user write an address and look for it ?

If so, you could use webview and the same URL that you have provided

If you are just interested in the result, no need to paint the map, then you could use curl with the google API
directly but you need to pay for a key

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 7317
Joined: Thu Oct 18, 2012 07:17 PM
Re: Experiment: MapControl
Posted: Wed Feb 23, 2022 07:54 AM
Antonio Linares wrote:Dear Silvio,

Do you mean to allow the user write an address and look for it ?

If so, you could use webview and the same URL that you have provided

If you are just interested in the result, no need to paint the map, then you could use curl with the google API
directly but you need to pay for a key



Dear Antonio,
Yes, I wanted to find an address and show the map
I have a Google API key you don't have to pay so much that I use the API key in many websites that have the ability to see the google map by searching for an address.

Then here we are talking about the Map Control Class which loads the maps
from https://www.openstreetmap.org/about

"OpenStreetMap is open data: you are free to use it for any purpose as long as you attribute it to OpenStreetMap and its contributors."

The information to create applications with this FREE service are reported on this web page https://wiki.openstreetmap.org/wiki/Develop
Since from 1991/1992 ( fw for clipper Rel. 14.4 - Momos)

I use : FiveWin for Harbour March-April 2024 - Harbour 3.2.0dev (harbour_bcc770_32_20240309) - Bcc7.70 - xMate ver. 1.15.3 - PellesC - mail: silvio[dot]falconi[at]gmail[dot]com

Continue the discussion