; ******************************************************************** ; Program: PBMap ; Description: Permits the use of tiled maps like ; OpenStreetMap in a handy PureBASIC module ; Author: Thyphoon, djes, Idle, yves86 ; Date: Jan, 2021 ; License: PBMap : Free, unrestricted, credit ; appreciated but not required. ; OSM : see http://www.openstreetmap.org/copyright ; Note: Please share improvement ! ; Thanks: Progi1984, falsam ; HowToRun: Include this code ; ******************************************************************** ; ; Track bugs with the following options with debugger enabled (see in the example) ; PBMap::SetOption(#Map, "ShowDebugInfos", "1") ; PBMap::SetDebugLevel(5) ; PBMap::SetOption(#Map, "Verbose", "1") ; ; or with the OnError() PB capabilities : ; ; CompilerIf #PB_Compiler_LineNumbering = #False ; MessageRequester("Warning !", "You must enable 'OnError lines support' in compiler options", #PB_MessageRequester_Ok ) ; End ; CompilerEndIf ; ; Declare ErrorHandler() ; ; OnErrorCall(@ErrorHandler()) ; ; Procedure ErrorHandler() ; MessageRequester("Ooops", "The following error happened : " + ErrorMessage(ErrorCode()) + #CRLF$ +"line : " + Str(ErrorLine())) ; EndProcedure ; ; ******************************************************************** CompilerIf #PB_Compiler_Thread = #False MessageRequester("Warning !", "You must enable 'Create ThreadSafe Executable' in compiler options", #PB_MessageRequester_Ok ) End CompilerEndIf EnableExplicit UsePNGImageDecoder() UseJPEGImageDecoder() UsePNGImageEncoder() UseJPEGImageEncoder() ;- Module declaration DeclareModule PBMap #PBMAPNAME = "PBMap" #PBMAPVERSION = "0.98" #USERAGENT = #PBMAPNAME + "/" + #PBMAPVERSION + " (https://github.com/djes/PBMap)" CompilerIf #PB_Compiler_OS = #PB_OS_Linux #Red = 255 CompilerEndIf #SCALE_NAUTICAL = 1 #SCALE_KM = 0 #MODE_DEFAULT = 0 #MODE_HAND = 1 #MODE_SELECT = 2 #MODE_EDIT = 3 #MARKER_EDIT_EVENT = #PB_Event_FirstCustomValue #PB_MAP_REDRAW = #PB_EventType_FirstCustomValue + 1 #PB_MAP_RETRY = #PB_EventType_FirstCustomValue + 2 #PB_MAP_TILE_CLEANUP = #PB_EventType_FirstCustomValue + 3 ;-*** Public structures Structure GeographicCoordinates Longitude.d Latitude.d EndStructure Structure Marker GeographicCoordinates.GeographicCoordinates ; Marker's latitude and longitude Identifier.s Legend.s Color.l ; Marker's color Focus.i Selected.i ; Is the marker selected ? CallBackPointer.i ; @Procedure(X.i, Y.i) to DrawPointer (you must use VectorDrawing lib) EditWindow.i EndStructure ;*** ;Declare SelectPBMap(Gadget.i) ; Could be used to have multiple PBMaps in one window Declare SetDebugLevel(Level.i) Declare SetOption(MapGadget.i, Option.s, Value.s) Declare.s GetOption(MapGadget.i, Option.s) Declare LoadOptions(MapGadget.i, PreferencesFile.s = "PBMap.prefs") Declare SaveOptions(MapGadget.i, PreferencesFile.s = "PBMap.prefs") Declare.i AddOSMServerLayer(MapGadget.i, LayerName.s, Order.i, ServerURL.s = "https://tile.openstreetmap.org/") Declare.i AddHereServerLayer(MapGadget.i, LayerName.s, Order.i, APP_ID.s = "", APP_CODE.s = "", ServerURL.s = "aerial.maps.api.here.com", path.s = "/maptile/2.1/", ressource.s = "maptile", id.s = "newest", scheme.s = "satellite.day", format.s = "jpg", lg.s = "eng", lg2.s = "eng", param.s = "") Declare.i AddGeoServerLayer(MapGadget.i, LayerName.s, Order.i, ServerLayerName.s, ServerURL.s = "http://localhost:8080/", path.s = "geowebcache/service/gmaps", format.s = "image/png") Declare IsLayer(MapGadget.i, Name.s) Declare DeleteLayer(MapGadget.i, Name.s) Declare EnableLayer(MapGadget.i, Name.s) Declare DisableLayer(MapGadget.i, Name.s) Declare SetLayerAlpha(MapGadget.i, Name.s, Alpha.d) Declare.d GetLayerAlpha(MapGadget.i, Name.s) Declare BindMapGadget(MapGadget.i, TimerNB = 1, Window = -1) Declare SetCallBackLocation(MapGadget.i, *CallBackLocation) Declare SetCallBackMainPointer(MapGadget.i, CallBackMainPointer.i) Declare SetCallBackDrawTile(MapGadget.i, *CallBackLocation) Declare SetCallBackMarker(MapGadget.i, *CallBackLocation) Declare SetCallBackLeftClic(MapGadget.i, *CallBackLocation) Declare SetCallBackModifyTileFile(MapGadget.i, *CallBackLocation) Declare.i MapGadget(MapGadget.i, X.i, Y.i, Width.i, Height.i, TimerNB = 1, Window = -1) ; Returns Gadget NB if #PB_Any is used for gadget Declare FreeMapGadget(MapGadget.i) Declare.d GetLatitude(MapGadget.i) Declare.d GetLongitude(MapGadget.i) Declare.d GetMouseLatitude(MapGadget.i) Declare.d GetMouseLongitude(MapGadget.i) Declare.d GetAngle(MapGadget.i) Declare.i GetZoom(MapGadget.i) Declare.i GetMode(MapGadget.i) Declare SetMode(MapGadget.i, Mode.i = #MODE_DEFAULT) Declare SetMapScaleUnit(MapGadget.i, ScaleUnit=PBMAP::#SCALE_KM) Declare SetLocation(MapGadget.i, Latitude.d, Longitude.d, Zoom = -1, Mode.i = #PB_Absolute) Declare SetAngle(MapGadget.i, Angle.d, Mode = #PB_Absolute) Declare SetZoom(MapGadget.i, Zoom.i, Mode.i = #PB_Relative) Declare SetZoomToArea(MapGadget.i, MinY.d, MaxY.d, MinX.d, MaxX.d) Declare SetZoomToTracks(MapGadget.i, *Tracks) Declare NominatimGeoLocationQuery(MapGadget.i, Address.s, *ReturnPosition = 0) ; Send back the position *ptr.GeographicCoordinates Declare.i LoadGpxFile(MapGadget.i, FileName.s) ; Declare.i SaveGpxFile(MapGadget.i, FileName.s, *Track) ; Declare ClearTracks(MapGadget.i) Declare DeleteTrack(MapGadget.i, *Ptr) Declare DeleteSelectedTracks(MapGadget.i) Declare SetTrackColour(MapGadget.i, *Ptr, Colour.i) Declare.i AddMarker(MapGadget.i, Latitude.d, Longitude.d, Identifier.s = "", Legend.s = "", color.l=-1, CallBackPointer.i = -1) Declare ClearMarkers(MapGadget.i) Declare DeleteMarker(MapGadget.i, *Ptr) Declare DeleteSelectedMarkers(MapGadget.i) Declare Drawing(MapGadget.i) Declare FatalError(MapGadget.i, msg.s) Declare Error(MapGadget.i, msg.s) Declare Refresh(MapGadget.i) Declare.i ClearDiskCache(MapGadget.i) EndDeclareModule Module PBMap EnableExplicit ;-*** Prototypes Prototype.i ProtoDrawTile(x.i, y.i, image.i, alpha.d = 1) Prototype.s ProtoModifyTileFile(Filename.s, OriginalURL.s) ;-*** Internal Structures Structure PixelCoordinates x.d y.d EndStructure Structure Coordinates x.d y.d EndStructure Structure Tile nImage.i key.s URL.s CacheFile.s GetImageThread.i Download.i Time.i Size.i Window.i ; Parent Window Gadget.i *PBMap.PBMap ; Back-reference for cooperative shutdown EndStructure Structure BoundingBox NorthWest.GeographicCoordinates SouthEast.GeographicCoordinates BottomRight.PixelCoordinates TopLeft.PixelCoordinates EndStructure Structure DrawingParameters Canvas.i RadiusX.d ; Canvas radius, or center in pixels RadiusY.d GeographicCoordinates.GeographicCoordinates ; Real center in lat/lon TileCoordinates.Coordinates ; Center coordinates in tile.decimal Bounds.BoundingBox ; Drawing boundaries in lat/lon Width.d ; Drawing width in degrees Height.d ; Drawing height in degrees PBMapZoom.i DeltaX.i ; Screen relative pixels tile shift DeltaY.i Dirty.i End.i EndStructure Structure ImgMemCach nImage.i Size.i *Tile.Tile *TimeStackPtr Alpha.i EndStructure Structure ImgMemCachKey MapKey.s EndStructure Structure TileMemCach Map Images.ImgMemCach(4096) List ImagesTimeStack.ImgMemCachKey() ; Usage of the tile (first = older) EndStructure ;-Options Structure Structure Option HDDCachePath.s ; Path where to load and save tiles downloaded from server DefaultOSMServer.s ; Base layer OSM server WheelMouseRelative.i ScaleUnit.i ; Scale unit to use for measurements Proxy.i ; Proxy ON/OFF ProxyURL.s ProxyPort.s ProxyUser.s ProxyPassword.s ShowDegrees.i ShowZoom.i ShowDebugInfos.i ShowScale.i ShowTrack.i ShowTrackKms.i ShowMarkers.i ShowPointer.i TimerInterval.i MaxMemCache.i ; in MiB MaxThreads.i ; Maximum simultaneous web loading threads MaxDownloadSlots.i ; Maximum simultaneous download slots TileLifetime.i Verbose.i ; Maximum debug informations Warning.i ; Warning requesters ShowMarkersNb.i ShowMarkersLegend.i ShowTrackSelection.i ; YA to show or not track selection EnableClusters.i ; Enable marker clustering ClusterPixelSize.i ; Cluster size in pixels (screen grid) ClusterMinCount.i ; Min markers to display a cluster ; Drawing stuff StrokeWidthTrackDefault.i ; Colours ColourFocus.i ColourSelected.i ColourTrackDefault.i ; HERE specific appid.s appcode.s EndStructure Structure Layer Order.i ; Layer nb Name.s ServerURL.s ; Web URL ex: http://tile.openstreetmap.org/ path.s LayerType.i ; OSM : 0 ; Here : 1 Enabled.i Alpha.d ; 1 : opaque ; 0 : transparent format.s ; > HERE specific params APP_ID.s APP_CODE.s ressource.s param.s id.s scheme.s lg.s lg2.s ; < ; > GeoServer specific params ServerLayerName.s ; < EndStructure Structure Box x1.i y1.i x2.i y2.i EndStructure ; --------------------------------------------------------------------------- ; SECTION : Clustering des marqueurs ; OBJECTIF : Regrouper les marqueurs proches pour la lisibilite ; POURQUOI : Eviter la surcharge visuelle a faible zoom ; COMMENT : Regroupement par grille en pixels ecran ; --------------------------------------------------------------------------- Structure Cluster CenterX.d CenterY.d CenterLat.d CenterLon.d SumX.d SumY.d SumLat.d SumLon.d Count.i CellX.i CellY.i MarkerX.d MarkerY.d *Marker.Marker Focus.i EndStructure Structure Tracks List Track.GeographicCoordinates() ; To display a GPX track BoundingBox.Box Visible.i Focus.i Selected.i Colour.i StrokeWidth.i EndStructure ;- PBMap Structure Structure PBMap Window.i ; Parent Window Gadget.i ; Canvas Gadget Id StandardFont.i ; Font to use when writing on the map UnderlineFont.i Timer.i ; Redraw/update timer GeographicCoordinates.GeographicCoordinates ; Latitude and Longitude from focus point Drawing.DrawingParameters ; Drawing parameters based on focus point CallBackLocation.i ; @Procedure(latitude.d, longitude.d) CallBackMainPointer.i ; @Procedure(X.i, Y.i) to DrawPointer (you must use VectorDrawing lib) CallBackMarker.i ; @Procedure (latitude.d, longitude.d) to know the marker position (YA) CallBackLeftClic.i ; @Procedure (latitude.d, longitude.d) to know the position on left click (YA) CallBackDrawTile.ProtoDrawTile ; @Procedure (x.i, y.i, nImage.i) to customise tile drawing CallBackModifyTileFile.ProtoModifyTileFile ; @Procedure (Filename.s, Original URL) to customise image file => New Filename PixelCoordinates.PixelCoordinates ; Actual focus point coords in pixels (global) MoveStartingPoint.PixelCoordinates ; Start mouse position coords when dragging the map List LayersList.Layer() Map *Layers.Layer() Angle.d ZoomMin.i ; Min Zoom supported by server ZoomMax.i ; Max Zoom supported by server Zoom.i ; Current zoom TileSize.i ; Tile size downloaded on the server ex : 256 MemCache.TileMemCach ; Images in memory cache ThreadsNB.i ; Current web threads nb Mode.i ; User mode : 0 (default)->hand (moving map) and select markers, 1->hand, 2->select only (moving objects), 3->drawing (todo) Redraw.i Dragging.i Dirty.i ; To signal that drawing need a refresh MemoryCacheAccessMutex.i ; Memorycache access variable mutual exclusion DownloadSlots.i ; Actual nb of used download slots List TracksList.Tracks() ; To display a GPX track List Markers.Marker() ; To diplay marker List Clusters.Cluster() ; Marker clusters (computed each draw) EditMarker.l ImgLoading.i ; Image Loading Tile ImgNothing.i ; Image Nothing Tile Options.option ; Options StopDownloads.b ; Stop flag for cooperative thread shutdown EndStructure ; --------------------------------------------------------------------------- ; SECTION : Declarations internes ; OBJECTIF : Permettre l'appel de procedures definies plus bas ; POURQUOI : Eviter les erreurs de compilation sur l'ordre des procedures ; --------------------------------------------------------------------------- ;-*** Module's global variables ;-Show debug infos Global MyDebugLevel = 5 Global NewMap PBMaps() Global slash.s CompilerSelect #PB_Compiler_OS CompilerCase #PB_OS_Windows Global slash = "\" CompilerDefault Global slash = "/" CompilerEndSelect ;- *** GetText - Translation purpose ; TODO use this IncludeFile "gettext.pbi" ;-*** Misc tools Macro Min(a, b) (Bool((a) <= (b)) * (a) + Bool((b) < (a)) * (b)) EndMacro Macro Max(a, b) (Bool((a) >= (b)) * (a) + Bool((b) > (a)) * (b)) EndMacro ;-Error management ; Shows an error msg and terminates the program Procedure FatalError(MapGadget, msg.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *PBMap\Options\Warning MessageRequester("PBMap", msg, #PB_MessageRequester_Ok) EndIf End EndProcedure ; Shows an error msg Procedure Error(MapGadget, msg.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *PBMap\Options\Warning MessageRequester("PBMap", msg, #PB_MessageRequester_Ok) EndIf EndProcedure ; Set the debug level allowing more or less debug infos Procedure SetDebugLevel(level.i) MyDebugLevel = level EndProcedure ; Send debug infos to stdout (allowing mixed debug infos with curl or other libs) Procedure MyDebug(*PBMap.PBMap, msg.s, DbgLevel = 0) ;Directly pass the PBMap structure (faster) If *PBMap\Options\Verbose And DbgLevel <= MyDebugLevel PrintN(msg) ; Debug msg EndIf EndProcedure ; Creates a full tree ; by Thomas (ts-soft) Schulz ; http://www.purebasic.fr/english/viewtopic.php?f=12&t=58657&hilit=createdirectory&view=unread#unread CompilerSelect #PB_Compiler_OS CompilerCase #PB_OS_Windows #FILE_ATTRIBUTE_DEVICE = 64 ; (0x40) #FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768 ; (0x8000) #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192; (0x2000) #FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072; (0x20000) #FILE_ATTRIBUTE_VIRTUAL = 65536; (0x10000) #FILE_ATTRIBUTE_DONTSETFLAGS = ~(#FILE_ATTRIBUTE_DIRECTORY| #FILE_ATTRIBUTE_SPARSE_FILE| #FILE_ATTRIBUTE_OFFLINE| #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED| #FILE_ATTRIBUTE_VIRTUAL| 0) Macro SetFileAttributesEx(Name, Attribs) SetFileAttributes(Name, Attribs & #FILE_ATTRIBUTE_DONTSETFLAGS) EndMacro CompilerDefault Macro SetFileAttributesEx(Name, Attribs) SetFileAttributes(Name, Attribs) EndMacro CompilerEndSelect ; --------------------------------------------------------------------------- ; SECTION : Creation de dossiers en cascade ; OBJECTIF : Creer un chemin complet sans echec sur les sous-dossiers ; POURQUOI : Le cache doit toujours disposer d'un chemin valide ; COMMENT : Creation progressive + verification de chaque niveau ; --------------------------------------------------------------------------- Procedure CreateDirectoryEx(DirectoryName.s, FileAttribute = #PB_Default) Protected i, c, tmp.s If Right(DirectoryName, 1) = slash DirectoryName = Left(DirectoryName, Len(DirectoryName) -1) EndIf c = CountString(DirectoryName, slash) + 1 For i = 1 To c tmp + StringField(DirectoryName, i, slash) If FileSize(tmp) <> -2 CreateDirectory(tmp) EndIf tmp + slash Next If FileAttribute <> #PB_Default SetFileAttributesEx(DirectoryName, FileAttribute) EndIf If FileSize(DirectoryName) = -2 ProcedureReturn #True EndIf EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Validation de chemin de cache ; OBJECTIF : Eviter la suppression de dossiers critiques (racine) ; POURQUOI : ClearDiskCache() utilise une suppression recursive dangereuse ; COMMENT : Rejeter les chemins vides ou de type racine ; --------------------------------------------------------------------------- Procedure.b IsSafeCachePath(Path.s) Protected Normalized.s = Trim(Path) If Normalized = "" ProcedureReturn #False EndIf While Right(Normalized, 1) = slash Normalized = Left(Normalized, Len(Normalized) - 1) Wend If Normalized = "" ProcedureReturn #False EndIf CompilerIf #PB_Compiler_OS = #PB_OS_Windows If Len(Normalized) <= 3 And Mid(Normalized, 2, 1) = ":" ProcedureReturn #False EndIf CompilerElse If Normalized = "/" ProcedureReturn #False EndIf CompilerEndIf ProcedureReturn #True EndProcedure Procedure TechnicalImagesCreation(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ; "Loading" image Protected LoadingText$ = "Loading" Protected NothingText$ = "Nothing" *PBMap\ImgLoading = CreateImage(#PB_Any, 256, 256) If *PBMap\ImgLoading StartVectorDrawing(ImageVectorOutput(*PBMap\Imgloading)) BeginVectorLayer() VectorSourceColor(RGBA(255, 255, 255, 128)) AddPathBox(0, 0, 256, 256) FillPath() MovePathCursor(0, 0) VectorFont(FontID(*PBMap\StandardFont), 256 / 20) VectorSourceColor(RGBA(150, 150, 150, 255)) MovePathCursor(0 + (256 - VectorTextWidth(LoadingText$)) / 2, 0 + (256 - VectorTextHeight(LoadingText$)) / 2) DrawVectorText(LoadingText$) EndVectorLayer() StopVectorDrawing() EndIf ; "Nothing" tile *PBMap\ImgNothing = CreateImage(#PB_Any, 256, 256) If *PBMap\ImgNothing StartVectorDrawing(ImageVectorOutput(*PBMap\ImgNothing)) ; BeginVectorLayer() VectorSourceColor(RGBA(220, 230, 255, 255)) AddPathBox(0, 0, 256, 256) FillPath() ; MovePathCursor(0, 0) ; VectorFont(FontID(*PBMap\StandardFont), 256 / 20) ; VectorSourceColor(RGBA(150, 150, 150, 255)) ; MovePathCursor(0 + (256 - VectorTextWidth(NothingText$)) / 2, 0 + (256 - VectorTextHeight(NothingText$)) / 2) ; DrawVectorText(NothingText$) ; EndVectorLayer() StopVectorDrawing() EndIf EndProcedure Procedure.d Distance(x1.d, y1.d, x2.d, y2.d) Protected Result.d Result = Sqr( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) ProcedureReturn Result EndProcedure ; *** Converts coords to tile.decimal ; Warning, structures used in parameters are not tested Procedure LatLon2TileXY(*Location.GeographicCoordinates, *Coords.Coordinates, Zoom) Protected n.d = Pow(2.0, Zoom) Protected LatRad.d = Radian(*Location\Latitude) *Coords\x = n * (Mod( *Location\Longitude + 180.0, 360) / 360.0 ) *Coords\y = n * ( 1.0 - Log(Tan(LatRad) + (1.0/Cos(LatRad))) / #PI ) / 2.0 ;MyDebug(*PBMap, "Latitude : " + StrD(*Location\Latitude) + " ; Longitude : " + StrD(*Location\Longitude), 5) ;MyDebug(*PBMap, "Coords X : " + Str(*Coords\x) + " ; Y : " + Str(*Coords\y), 5) EndProcedure ; *** Converts tile.decimal to coords ; Warning, structures used in parameters are not tested Procedure TileXY2LatLon(*Coords.Coordinates, *Location.GeographicCoordinates, Zoom) Protected n.d = Pow(2.0, Zoom) ; Ensures the longitude to be in the range [-180; 180[ *Location\Longitude = Mod(Mod(*Coords\x / n * 360.0, 360.0) + 360.0, 360.0) - 180 *Location\Latitude = Degree(ATan(SinH(#PI * (1.0 - 2.0 * *Coords\y / n)))) If *Location\Latitude <= -89 *Location\Latitude = -89 EndIf If *Location\Latitude >= 89 *Location\Latitude = 89 EndIf EndProcedure ; Ensures the longitude to be in the range [-180; 180[ Procedure.d ClipLongitude(Longitude.d) ProcedureReturn Mod(Mod(Longitude + 180, 360.0) + 360.0, 360.0) - 180 EndProcedure ; Lat Lon coordinates 2 pixel absolute [0 to 2^Zoom * TileSize [ Procedure LatLon2Pixel(MapGadget.i, *Location.GeographicCoordinates, *Pixel.PixelCoordinates, Zoom) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected tilemax = Pow(2.0, Zoom) * *PBMap\TileSize Protected LatRad.d = Radian(*Location\Latitude) *Pixel\x = tilemax * (Mod( *Location\Longitude + 180.0, 360) / 360.0 ) *Pixel\y = tilemax * ( 1.0 - Log(Tan(LatRad) + (1.0/Cos(LatRad))) / #PI ) / 2.0 EndProcedure ; Lat Lon coordinates 2 pixel relative to the center of view Procedure LatLon2PixelRel(MapGadget.i, *Location.GeographicCoordinates, *Pixel.PixelCoordinates, Zoom) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected tilemax = Pow(2.0, Zoom) * *PBMap\TileSize Protected cx.d = *PBMap\Drawing\RadiusX Protected dpx.d = *PBMap\PixelCoordinates\x Protected LatRad.d = Radian(*Location\Latitude) Protected px.d = tilemax * (Mod( *Location\Longitude + 180.0, 360) / 360.0 ) Protected py.d = tilemax * ( 1.0 - Log(Tan(LatRad) + (1.0/Cos(LatRad))) / #PI ) / 2.0 ; check the x boundaries of the map to adjust the position (coz of the longitude wrapping) If dpx - px >= tilemax / 2 ; Debug "c1" *Pixel\x = cx + (px - dpx + tilemax) ElseIf px - dpx > tilemax / 2 ; Debug "c2" *Pixel\x = cx + (px - dpx - tilemax) ElseIf px - dpx < 0 ; Debug "c3" *Pixel\x = cx - (dpx - px) Else ; Debug "c0" *Pixel\x = cx + (px - dpx) EndIf *Pixel\y = *PBMap\Drawing\RadiusY + (py - *PBMap\PixelCoordinates\y) EndProcedure Procedure Pixel2LatLon(MapGadget.i, *Coords.PixelCoordinates, *Location.GeographicCoordinates, Zoom) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected n.d = *PBMap\TileSize * Pow(2.0, Zoom) ; Ensures the longitude to be in the range [-180; 180[ *Location\Longitude = Mod((1 + *Coords\x / n) * 360, 360) - 180 ; Mod(Mod(*Coords\x / n * 360.0, 360.0) + 360.0, 360.0) - 180 *Location\Latitude = Degree(ATan(SinH(#PI * (1.0 - 2.0 * *Coords\y / n)))) If *Location\Latitude <= -89 *Location\Latitude = -89 EndIf If *Location\Latitude >= 89 *Location\Latitude = 89 EndIf EndProcedure Procedure.d Pixel2Lon(MapGadget.i, x) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected n.d = *PBMap\TileSize * Pow(2.0, *PBMap\Zoom) ProcedureReturn Mod((1 + x / n) * 360.0, 360.0) - 180 EndProcedure Procedure.d Pixel2Lat(MapGadget.i, y) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected n.d = *PBMap\TileSize * Pow(2.0, *PBMap\Zoom) Protected latitude.d = Degree(ATan(SinH(#PI * (1.0 - 2.0 * y / n)))) If latitude <= -89 latitude = -89 EndIf If latitude >= 89 latitude = 89 EndIf ProcedureReturn latitude EndProcedure ; HaversineAlgorithm ; http://andrew.hedges.name/experiments/haversine/ Procedure.d HaversineInKM(*posA.GeographicCoordinates, *posB.GeographicCoordinates) Protected eQuatorialEarthRadius.d = 6378.1370; 6372.795477598; Protected dlong.d = (*posB\Longitude - *posA\Longitude); Protected dlat.d = (*posB\Latitude - *posA\Latitude) ; Protected alpha.d=dlat/2 Protected beta.d=dlong/2 Protected a.d = Sin(Radian(alpha)) * Sin(Radian(alpha)) + Cos(Radian(*posA\Latitude)) * Cos(Radian(*posB\Latitude)) * Sin(Radian(beta)) * Sin(Radian(beta)) Protected c.d = ASin(Min(1,Sqr(a))); Protected distance.d = 2*eQuatorialEarthRadius * c ProcedureReturn distance ; EndProcedure Procedure.d HaversineInM(*posA.GeographicCoordinates, *posB.GeographicCoordinates) ProcedureReturn (1000 * HaversineInKM(@*posA, @*posB)); EndProcedure ; No more used, see LatLon2PixelRel Procedure GetPixelCoordFromLocation(MapGadget.i, *Location.GeographicCoordinates, *Pixel.PixelCoordinates, Zoom) ; TODO to Optimize Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected mapWidth.l = Pow(2, Zoom + 8) Protected mapHeight.l = Pow(2, Zoom + 8) Protected x1.l,y1.l x1 = (*Location\Longitude+180)*(mapWidth/360) ; convert from degrees To radians Protected latRad.d = *Location\Latitude*#PI/180; Protected mercN.d = Log(Tan((#PI/4)+(latRad/2))); y1 = (mapHeight/2)-(mapWidth*mercN/(2*#PI)) ; Protected x2.l, y2.l x2 = (*PBMap\GeographicCoordinates\Longitude+180)*(mapWidth/360) ; convert from degrees To radians latRad = *PBMap\GeographicCoordinates\Latitude*#PI/180; mercN = Log(Tan((#PI/4)+(latRad/2))) y2 = (mapHeight/2)-(mapWidth*mercN/(2*#PI)); *Pixel\x=*PBMap\Drawing\RadiusX - (x2-x1) *Pixel\y=*PBMap\Drawing\RadiusY - (y2-y1) EndProcedure Procedure IsInDrawingPixelBoundaries(MapGadget.i, *Drawing.DrawingParameters, *Position.GeographicCoordinates) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Pixel.PixelCoordinates LatLon2PixelRel(MapGadget, *Position, @Pixel, *PBMap\Zoom) If Pixel\x >= 0 And Pixel\y >= 0 And Pixel\x < *Drawing\RadiusX * 2 And Pixel\y < *Drawing\RadiusY * 2 ProcedureReturn #True Else ProcedureReturn #False EndIf EndProcedure Procedure.i IsInDrawingBoundaries(*Drawing.DrawingParameters, *Position.GeographicCoordinates) Protected Lat.d = *Position\Latitude, Lon.d = *Position\Longitude Protected LatNW.d = *Drawing\Bounds\NorthWest\Latitude, LonNW.d = *Drawing\Bounds\NorthWest\Longitude Protected LatSE.d = *Drawing\Bounds\SouthEast\Latitude, LonSE.d = *Drawing\Bounds\SouthEast\Longitude If Lat >= LatSE And Lat <= LatNW If *Drawing\Width >= 360 ProcedureReturn #True Else If LonNW < LonSE If Lon >= LonNW And Lon <= LonSE ProcedureReturn #True Else ProcedureReturn #False EndIf Else If (Lon >= -180 And Lon <= LonSE) Or (Lon >= LonNW And Lon <= 180) ProcedureReturn #True Else ProcedureReturn #False EndIf EndIf EndIf Else ProcedureReturn #False EndIf EndProcedure ; TODO : best cleaning of the string from bad behaviour Procedure.s StringCheck(String.s) ProcedureReturn Trim(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(RemoveString(String, Chr(0)), Chr(32)), Chr(39)), Chr(33)), Chr(34)), "@"), "/"), "\"), "$"), "%")) EndProcedure Procedure.i ColourString2Value(Value.s) ; TODO : better string check Protected Col.s = RemoveString(Value, " ") If Left(Col, 1) = "$" Protected r.i, g.i, b.i, a.i = 255 Select Len(Col) Case 4 ; RGB (eg : "$9BC" r = Val("$"+Mid(Col, 2, 1)) : g = Val("$"+Mid(Col, 3, 1)) : b = Val("$"+Mid(Col, 4, 1)) Case 5 ; RGBA (eg : "$9BC5") r = Val("$"+Mid(Col, 2, 1)) : g = Val("$"+Mid(Col, 3, 1)) : b = Val("$"+Mid(Col, 4, 1)) : a = Val("$"+Mid(Col, 5, 1)) Case 7 ; RRGGBB (eg : "$95B4C2") r = Val("$"+Mid(Col, 2, 2)) : g = Val("$"+Mid(Col, 4, 2)) : b = Val("$"+Mid(Col, 6, 2)) Case 9 ; RRGGBBAA (eg : "$95B4C249") r = Val("$"+Mid(Col, 2, 2)) : g = Val("$"+Mid(Col, 4, 2)) : b = Val("$"+Mid(Col, 6, 2)) : a = Val("$"+Mid(Col, 8, 2)) EndSelect ProcedureReturn RGBA(r, g, b, a) Else ProcedureReturn Val(Value) EndIf EndProcedure Procedure.s Value2ColourString(Value.i) ProcedureReturn "$" + StrU(Red(Value), #PB_Byte) + StrU(Green(Value), #PB_Byte) + StrU(Blue(Value), #PB_Byte) EndProcedure ;-*** Cluster ; --------------------------------------------------------------------------- ; SECTION : Dessin d'un cluster ; OBJECTIF : Afficher un regroupement de marqueurs ; POURQUOI : Representer visuellement plusieurs points proches ; COMMENT : Cercle avec degrade continu + compteur ; --------------------------------------------------------------------------- Procedure DrawCluster(MapGadget.i, *Cluster.Cluster) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Radius.i Protected Text.s = Str(*Cluster\Count) Protected Width.d Protected Height.d Protected t.d Protected r.i, g.i, b.i Protected MaxCount.i = 50 ; Degrade continu (petit -> grand) avec compression logarithmique t = Log(*Cluster\Count + 1) / Log(MaxCount + 1) If t > 1.0 t = 1.0 EndIf If t < 0.0 t = 0.0 EndIf ; Rayon max 22 => diametre max 44 px Radius = 8 + Int(14 * t) ; Couleur du bleu vers le rouge r = Int(40 + 200 * t) g = Int(160 - 120 * t) b = Int(220 - 200 * t) VectorSourceColor(RGBA(r, g, b, 220)) AddPathCircle(*Cluster\CenterX, *Cluster\CenterY, Radius) FillPath() If *Cluster\Focus VectorSourceColor(RGBA(255, 255, 255, 200)) AddPathCircle(*Cluster\CenterX, *Cluster\CenterY, Radius + 2) StrokePath(2) EndIf VectorFont(FontID(*PBMap\StandardFont), 14) VectorSourceColor(RGBA(255, 255, 255, 255)) Width = VectorTextWidth(Text) Height = VectorTextHeight(Text) MovePathCursor(*Cluster\CenterX - Width / 2, *Cluster\CenterY - Height / 2) DrawVectorText(Text) EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Construction des clusters ; OBJECTIF : Regrouper les marqueurs selon une grille en pixels ecran ; POURQUOI : Minimiser le chevauchement des marqueurs ; COMMENT : Map de cellules -> pointeur de cluster ; --------------------------------------------------------------------------- Procedure BuildClusters(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Pixel.PixelCoordinates Protected cellX.i, cellY.i, key.s Protected *Cluster.Cluster Protected NewMap ClusterIndex.i() ClearList(*PBMap\Clusters()) If *PBMap\Options\EnableClusters = #False ProcedureReturn EndIf If *PBMap\Options\ClusterPixelSize <= 0 ProcedureReturn EndIf ForEach *PBMap\Markers() If IsInDrawingPixelBoundaries(MapGadget, *Drawing, @*PBMap\Markers()\GeographicCoordinates) LatLon2PixelRel(MapGadget, @*PBMap\Markers()\GeographicCoordinates, @Pixel, *PBMap\Zoom) cellX = Int(Pixel\x / *PBMap\Options\ClusterPixelSize) cellY = Int(Pixel\y / *PBMap\Options\ClusterPixelSize) key = Str(cellX) + ":" + Str(cellY) If FindMapElement(ClusterIndex(), key) *Cluster = ClusterIndex() Else AddElement(*PBMap\Clusters()) *Cluster = @*PBMap\Clusters() *Cluster\Count = 0 *Cluster\Focus = #False *Cluster\CellX = cellX *Cluster\CellY = cellY AddMapElement(ClusterIndex(), key) ClusterIndex() = *Cluster EndIf *Cluster\Count + 1 *Cluster\SumX + Pixel\x *Cluster\SumY + Pixel\y *Cluster\SumLat + *PBMap\Markers()\GeographicCoordinates\Latitude *Cluster\SumLon + *PBMap\Markers()\GeographicCoordinates\Longitude If *Cluster\Count = 1 *Cluster\Marker = @*PBMap\Markers() *Cluster\MarkerX = Pixel\x *Cluster\MarkerY = Pixel\y Else *Cluster\Marker = 0 EndIf EndIf Next ForEach *PBMap\Clusters() If *PBMap\Clusters()\Count > 0 *PBMap\Clusters()\CenterX = *PBMap\Clusters()\SumX / *PBMap\Clusters()\Count *PBMap\Clusters()\CenterY = *PBMap\Clusters()\SumY / *PBMap\Clusters()\Count *PBMap\Clusters()\CenterLat = *PBMap\Clusters()\SumLat / *PBMap\Clusters()\Count *PBMap\Clusters()\CenterLon = *PBMap\Clusters()\SumLon / *PBMap\Clusters()\Count EndIf Next EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Detection de cluster sous la souris ; OBJECTIF : Identifier un cluster dans les coordonnees ecran ; POURQUOI : Synchroniser le survol et les clics avec le rendu ; COMMENT : Test de distance sur le centre de cluster ; --------------------------------------------------------------------------- Procedure.i FindClusterAt(MapGadget.i, MouseX.d, MouseY.d) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Radius.i Protected cellX.i Protected cellY.i If *PBMap\Options\ClusterPixelSize <= 0 ProcedureReturn 0 EndIf cellX = Int(MouseX / *PBMap\Options\ClusterPixelSize) cellY = Int(MouseY / *PBMap\Options\ClusterPixelSize) ForEach *PBMap\Clusters() If *PBMap\Clusters()\CellX = cellX And *PBMap\Clusters()\CellY = cellY Radius = 10 + Min(20, *PBMap\Clusters()\Count) If Distance(*PBMap\Clusters()\CenterX, *PBMap\Clusters()\CenterY, MouseX, MouseY) <= Radius ProcedureReturn @*PBMap\Clusters() EndIf EndIf Next ProcedureReturn 0 EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Test d'appartenance a un cluster ; OBJECTIF : Savoir si un marqueur est masqué par un cluster ; POURQUOI : Eviter la selection d'un marqueur non visible ; COMMENT : Recalcule la cellule du marqueur et compare aux clusters ; --------------------------------------------------------------------------- Procedure.b IsMarkerClustered(MapGadget.i, *Marker.Marker) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Pixel.PixelCoordinates Protected cellX.i, cellY.i If *PBMap\Options\EnableClusters = #False ProcedureReturn #False EndIf If *PBMap\Options\ClusterPixelSize <= 0 ProcedureReturn #False EndIf LatLon2PixelRel(MapGadget, @*Marker\GeographicCoordinates, @Pixel, *PBMap\Zoom) cellX = Int(Pixel\x / *PBMap\Options\ClusterPixelSize) cellY = Int(Pixel\y / *PBMap\Options\ClusterPixelSize) ForEach *PBMap\Clusters() If *PBMap\Clusters()\CellX = cellX And *PBMap\Clusters()\CellY = cellY If *PBMap\Clusters()\Count >= *PBMap\Options\ClusterMinCount ProcedureReturn #True EndIf Break EndIf Next ProcedureReturn #False EndProcedure ;-*** Options Procedure SetOptions(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) With *PBMap\Options If \Proxy HTTPProxy(*PBMap\Options\ProxyURL + ":" + *PBMap\Options\ProxyPort, *PBMap\Options\ProxyUser, *PBMap\Options\ProxyPassword) EndIf If \Verbose OpenConsole() EndIf CreateDirectoryEx(\HDDCachePath) If \DefaultOSMServer <> "" And IsLayer(MapGadget, "OSM") = #False ; First time creation of the basis OSM layer AddOSMServerLayer(MapGadget, "OSM", 1, \DefaultOSMServer) EndIf EndWith EndProcedure Macro SelBool(Name) Select UCase(Value) Case "0", "FALSE", "DISABLE" *PBMap\Options\Name = #False Default *PBMap\Options\Name = #True EndSelect EndMacro Procedure SetOption(MapGadget.i, Option.s, Value.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Option = StringCheck(Option) Select LCase(Option) Case "proxy" SelBool(Proxy) Case "proxyurl" *PBMap\Options\ProxyURL = Value Case "proxyport" *PBMap\Options\ProxyPort = Value Case "proxyuser" *PBMap\Options\ProxyUser = Value Case "appid" *PBMap\Options\appid = Value Case "appcode" *PBMap\Options\appcode = Value Case "tilescachepath" *PBMap\Options\HDDCachePath = Value Case "maxmemcache" *PBMap\Options\MaxMemCache = Val(Value) Case "maxthreads" *PBMap\Options\MaxThreads = Val(Value) Case "maxdownloadslots" *PBMap\Options\MaxDownloadSlots = Val(Value) Case "tilelifetime" *PBMap\Options\TileLifetime = Val(Value) Case "verbose" SelBool(Verbose) Case "warning" SelBool(Warning) Case "wheelmouserelative" SelBool(WheelMouseRelative) Case "showdegrees" SelBool(ShowDegrees) Case "showzoom" SelBool(ShowZoom) Case "showdebuginfos" SelBool(ShowDebugInfos) Case "showscale" SelBool(ShowScale) Case "showmarkers" SelBool(ShowMarkers) Case "showpointer" SelBool(ShowPointer) Case "showtrack" SelBool(ShowTrack) Case "showtrackselection" SelBool(ShowTrackSelection) Case "showmarkersnb" SelBool(ShowMarkersNb) Case "showmarkerslegend" SelBool(ShowMarkersLegend) Case "enableclusters" SelBool(EnableClusters) Case "clusterpixelsize" *PBMap\Options\ClusterPixelSize = Val(Value) Case "clustermincount" *PBMap\Options\ClusterMinCount = Val(Value) Case "showtrackkms" SelBool(ShowTrackKms) Case "strokewidthtrackdefault" SelBool(StrokeWidthTrackDefault) Case "colourfocus" *PBMap\Options\ColourFocus = ColourString2Value(Value) Case "colourselected" *PBMap\Options\ColourSelected = ColourString2Value(Value) Case "colourtrackdefault" *PBMap\Options\ColourTrackDefault = ColourString2Value(Value) EndSelect SetOptions(MapGadget) EndProcedure Procedure.s GetBoolString(Value.i) Select Value Case #False ProcedureReturn "0" Default ProcedureReturn "1" EndSelect EndProcedure Procedure.s GetOption(MapGadget.i, Option.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Option = StringCheck(Option) With *PBMap\Options Select LCase(Option) Case "proxy" ProcedureReturn GetBoolString(\Proxy) Case "proxyurl" ProcedureReturn \ProxyURL Case "proxyport" ProcedureReturn \ProxyPort Case "proxyuser" ProcedureReturn \ProxyUser Case "appid" ProcedureReturn \appid Case "appcode" ProcedureReturn \appcode Case "tilescachepath" ProcedureReturn \HDDCachePath Case "maxmemcache" ProcedureReturn StrU(\MaxMemCache) Case "maxthreads" ProcedureReturn StrU(\MaxThreads) Case "maxdownloadslots" ProcedureReturn StrU(\MaxDownloadSlots) Case "tilelifetime" ProcedureReturn StrU(\TileLifetime) Case "verbose" ProcedureReturn GetBoolString(\Verbose) Case "warning" ProcedureReturn GetBoolString(\Warning) Case "wheelmouserelative" ProcedureReturn GetBoolString(\WheelMouseRelative) Case "showdegrees" ProcedureReturn GetBoolString(\ShowDegrees) Case "showdebuginfos" ProcedureReturn GetBoolString(\ShowDebugInfos) Case "showscale" ProcedureReturn GetBoolString(\ShowScale) Case "showzoom" ProcedureReturn GetBoolString(\ShowZoom) Case "showmarkers" ProcedureReturn GetBoolString(\ShowMarkers) Case "showpointer" ProcedureReturn GetBoolString(\ShowPointer) Case "showtrack" ProcedureReturn GetBoolString(\ShowTrack) Case "showtrackselection" ProcedureReturn GetBoolString(\ShowTrackSelection) Case "showmarkersnb" ProcedureReturn GetBoolString(\ShowMarkersNb) Case "showmarkerslegend" ProcedureReturn GetBoolString(\ShowMarkersLegend) Case "enableclusters" ProcedureReturn GetBoolString(\EnableClusters) Case "clusterpixelsize" ProcedureReturn StrU(\ClusterPixelSize) Case "clustermincount" ProcedureReturn StrU(\ClusterMinCount) Case "showtrackkms" ProcedureReturn GetBoolString(\ShowTrackKms) Case "strokewidthtrackdefault" ProcedureReturn GetBoolString(\StrokeWidthTrackDefault) Case "colourfocus" ProcedureReturn Value2ColourString(\ColourFocus) Case "colourselected" ProcedureReturn Value2ColourString(\ColourSelected) Case "colourtrackdefault" ProcedureReturn Value2ColourString(\ColourTrackDefault) EndSelect EndWith EndProcedure ; By default, save options in the user's home directory Procedure SaveOptions(MapGadget.i, PreferencesFile.s = "PBMap.prefs") Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If PreferencesFile = "PBMap.prefs" CreatePreferences(GetHomeDirectory() + "PBMap.prefs") Else CreatePreferences(PreferencesFile) EndIf With *PBMap\Options PreferenceGroup("PROXY") WritePreferenceInteger("Proxy", \Proxy) WritePreferenceString("ProxyURL", \ProxyURL) WritePreferenceString("ProxyPort", \ProxyPort) WritePreferenceString("ProxyUser", \ProxyUser) PreferenceGroup("HERE") WritePreferenceString("APP_ID", \appid) WritePreferenceString("APP_CODE", \appcode) PreferenceGroup("URL") WritePreferenceString("DefaultOSMServer", \DefaultOSMServer) PreferenceGroup("PATHS") WritePreferenceString("TilesCachePath", \HDDCachePath) PreferenceGroup("OPTIONS") WritePreferenceInteger("WheelMouseRelative", \WheelMouseRelative) WritePreferenceInteger("MaxMemCache", \MaxMemCache) WritePreferenceInteger("MaxThreads", \MaxThreads) WritePreferenceInteger("MaxDownloadSlots", \MaxDownloadSlots) WritePreferenceInteger("TileLifetime", \TileLifetime) WritePreferenceInteger("Verbose", \Verbose) WritePreferenceInteger("Warning", \Warning) WritePreferenceInteger("ShowDegrees", \ShowDegrees) WritePreferenceInteger("ShowDebugInfos", \ShowDebugInfos) WritePreferenceInteger("ShowScale", \ShowScale) WritePreferenceInteger("ShowZoom", \ShowZoom) WritePreferenceInteger("ShowMarkers", \ShowMarkers) WritePreferenceInteger("ShowPointer", \ShowPointer) WritePreferenceInteger("ShowTrack", \ShowTrack) WritePreferenceInteger("ShowTrackSelection", \ShowTrackSelection) WritePreferenceInteger("ShowTrackKms", \ShowTrackKms) WritePreferenceInteger("ShowMarkersNb", \ShowMarkersNb) WritePreferenceInteger("ShowMarkersLegend", \ShowMarkersLegend) WritePreferenceInteger("EnableClusters", \EnableClusters) WritePreferenceInteger("ClusterPixelSize", \ClusterPixelSize) WritePreferenceInteger("ClusterMinCount", \ClusterMinCount) PreferenceGroup("DRAWING") WritePreferenceInteger("StrokeWidthTrackDefault", \StrokeWidthTrackDefault) ; Colours; WritePreferenceInteger("ColourFocus", \ColourFocus) WritePreferenceInteger("ColourSelected", \ColourSelected) WritePreferenceInteger("ColourTrackDefault", \ColourTrackDefault) ClosePreferences() EndWith EndProcedure Procedure LoadOptions(MapGadget.i, PreferencesFile.s = "PBMap.prefs") Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If PreferencesFile = "PBMap.prefs" OpenPreferences(GetHomeDirectory() + "PBMap.prefs") Else OpenPreferences(PreferencesFile) EndIf ; Use this to create and customize your preferences file for the first time ; CreatePreferences(GetHomeDirectory() + "PBMap.prefs") ; ; Or this to modify ; ; OpenPreferences(GetHomeDirectory() + "PBMap.prefs") ; ; Or this ; ; RunProgram("notepad.exe", GetHomeDirectory() + "PBMap.prefs", GetHomeDirectory()) ; PreferenceGroup("PROXY") ; WritePreferenceInteger("Proxy", #True) ; WritePreferenceString("ProxyURL", "myproxy.fr") ; WritePreferenceString("ProxyPort", "myproxyport") ; WritePreferenceString("ProxyUser", "myproxyname") ; WritePreferenceString("ProxyPass", "myproxypass") ; TODO !Warning! !not encoded! ; PreferenceGroup("HERE") ; WritePreferenceString("APP_ID", "myhereid") ; TODO !Warning! !not encoded! ; WritePreferenceString("APP_CODE", "myherecode") ; TODO !Warning! !not encoded! ; ClosePreferences() With *PBMap\Options PreferenceGroup("PROXY") \Proxy = ReadPreferenceInteger("Proxy", #False) If \Proxy \ProxyURL = ReadPreferenceString("ProxyURL", "") ; = InputRequester("ProxyServer", "Do you use a Proxy Server? Then enter the full url:", "") \ProxyPort = ReadPreferenceString("ProxyPort", "") ; = InputRequester("ProxyPort", "Do you use a specific port? Then enter it", "") \ProxyUser = ReadPreferenceString("ProxyUser", "") ; = InputRequester("ProxyUser", "Do you use a user name? Then enter it", "") \ProxyPassword = ReadPreferenceString("ProxyPass", "") ; = InputRequester("ProxyPass", "Do you use a password ? Then enter it", "") ; TODO EndIf PreferenceGroup("HERE") \appid = ReadPreferenceString("APP_ID", "") ; = InputRequester("Here App ID", "Do you use HERE ? Enter app ID", "") ; TODO \appcode = ReadPreferenceString("APP_CODE", "") ; = InputRequester("Here App Code", "Do you use HERE ? Enter app Code", "") ; TODO PreferenceGroup("URL") \DefaultOSMServer = ReadPreferenceString("DefaultOSMServer", "https://tile.openstreetmap.org/") PreferenceGroup("PATHS") \HDDCachePath = ReadPreferenceString("TilesCachePath", GetTemporaryDirectory() + "PBMap" + slash) PreferenceGroup("OPTIONS") \WheelMouseRelative = ReadPreferenceInteger("WheelMouseRelative", #True) \MaxMemCache = ReadPreferenceInteger("MaxMemCache", 20480) ; 20 MiB, about 80 tiles in memory \MaxThreads = ReadPreferenceInteger("MaxThreads", 40) \MaxDownloadSlots = ReadPreferenceInteger("MaxDownloadSlots", 2) \TileLifetime = ReadPreferenceInteger("TileLifetime", 1209600) ; about 2 weeks ;-1 = unlimited \Verbose = ReadPreferenceInteger("Verbose", #False) \Warning = ReadPreferenceInteger("Warning", #False) \ShowDegrees = ReadPreferenceInteger("ShowDegrees", #False) \ShowDebugInfos = ReadPreferenceInteger("ShowDebugInfos", #False) \ShowScale = ReadPreferenceInteger("ShowScale", #False) \ShowZoom = ReadPreferenceInteger("ShowZoom", #True) \ShowMarkers = ReadPreferenceInteger("ShowMarkers", #True) \ShowPointer = ReadPreferenceInteger("ShowPointer", #True) \ShowTrack = ReadPreferenceInteger("ShowTrack", #True) \ShowTrackSelection = ReadPreferenceInteger("ShowTrackSelection", #False) \ShowTrackKms = ReadPreferenceInteger("ShowTrackKms", #False) \ShowMarkersNb = ReadPreferenceInteger("ShowMarkersNb", #True) \ShowMarkersLegend = ReadPreferenceInteger("ShowMarkersLegend", #False) \EnableClusters = ReadPreferenceInteger("EnableClusters", #False) \ClusterPixelSize = ReadPreferenceInteger("ClusterPixelSize", 60) \ClusterMinCount = ReadPreferenceInteger("ClusterMinCount", 2) PreferenceGroup("DRAWING") \StrokeWidthTrackDefault = ReadPreferenceInteger("StrokeWidthTrackDefault", 10) PreferenceGroup("COLOURS") \ColourFocus = ReadPreferenceInteger("ColourFocus", RGBA(255, 255, 0, 255)) \ColourSelected = ReadPreferenceInteger("ColourSelected", RGBA(225, 225, 0, 255)) \ColourTrackDefault = ReadPreferenceInteger("ColourTrackDefault", RGBA(0, 255, 0, 150)) \TimerInterval = 12 ClosePreferences() EndWith SetOptions(MapGadget) EndProcedure ;-*** Layers ; Add a layer to a list (to get things ordered) and to a map (to access things easily) Procedure.i AddLayer(MapGadget.i, Name.s, Order.i, Alpha.d) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Ptr = 0 *Ptr = AddMapElement(*PBMap\Layers(), Name) If *Ptr *PBMap\Layers() = AddElement(*PBMap\LayersList()) ; This map element is a ptr to a linked list element If *PBMap\Layers() *PBMap\LayersList()\Name = Name *PBMap\LayersList()\Order = Order *PBMap\LayersList()\Alpha = Alpha SortStructuredList(*PBMap\LayersList(), #PB_Sort_Ascending, OffsetOf(Layer\Order), TypeOf(Layer\Order)) ProcedureReturn *PBMap\Layers() Else *Ptr = 0 EndIf EndIf ProcedureReturn *Ptr EndProcedure ; "OpenStreetMap" layer Procedure.i AddOSMServerLayer(MapGadget.i, LayerName.s, Order.i, ServerURL.s = "https://tile.openstreetmap.org/") Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Ptr.Layer = AddLayer(MapGadget, LayerName, Order, 1) If *Ptr *Ptr\ServerURL = ServerURL *Ptr\LayerType = 0 ; OSM *Ptr\Enabled = #True *PBMap\Redraw = #True ProcedureReturn *Ptr Else ProcedureReturn #False EndIf EndProcedure ; "Here" layer ; see there for parameters : https://developer.here.com/rest-apis/documentation/enterprise-map-tile/topics/resource-base-maptile.html ; you could use base.maps.api.here.com or aerial.maps.api.here.com or traffic.maps.api.here.com or pano.maps.api.here.com. ; use *.cit.map.api.com For Customer Integration Testing (see https://developer.here.com/rest-apis/documentation/enterprise-Map-tile/common/request-cit-environment-rest.html) Procedure.i AddHereServerLayer(MapGadget.i, LayerName.s, Order.i, APP_ID.s = "", APP_CODE.s = "", ServerURL.s = "aerial.maps.api.here.com", path.s = "/maptile/2.1/", ressource.s = "maptile", id.s = "newest", scheme.s = "satellite.day", format.s = "jpg", lg.s = "eng", lg2.s = "eng", param.s = "") Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Ptr.Layer = AddLayer(MapGadget, LayerName, Order, 1) If *Ptr With *Ptr ; *PBMap\Layers() \ServerURL = ServerURL \path = path \ressource = ressource \LayerType = 1 ; HERE \Enabled = #True If APP_ID = "" APP_ID = *PBMap\Options\appid EndIf If APP_CODE = "" APP_CODE = *PBMap\Options\appcode EndIf \APP_CODE = APP_CODE \APP_ID = APP_ID \format = format \id = id \lg = lg \lg2 = lg2 \param = param \scheme = scheme EndWith *PBMap\Redraw = #True ProcedureReturn *Ptr Else ProcedureReturn #False EndIf EndProcedure ; GeoServer / geowebcache - google maps service ; template 'http://localhost:8080/geowebcache/service/gmaps?layers=layer-name&zoom={Z}&x={X}&y={Y}&format=image/png' Procedure.i AddGeoServerLayer(MapGadget.i, LayerName.s, Order.i, ServerLayerName.s, ServerURL.s = "http://localhost:8080/", path.s = "geowebcache/service/gmaps", format.s = "image/png") Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Ptr.Layer = AddLayer(MapGadget, LayerName, Order, 1) If *Ptr With *Ptr ; *PBMap\Layers() \ServerURL = ServerURL \path = path \LayerType = 2 ; GeoServer \format = format \Enabled = #True \ServerLayerName = ServerLayerName EndWith *PBMap\Redraw = #True ProcedureReturn *Ptr Else ProcedureReturn #False EndIf EndProcedure Procedure.i IsLayer(MapGadget.i, Name.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn FindMapElement(*PBMap\Layers(), Name) EndProcedure Procedure DeleteLayer(MapGadget.i, Name.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) FindMapElement(*PBMap\Layers(), Name) Protected *Ptr = *PBMap\Layers() ; Free the list element ChangeCurrentElement(*PBMap\LayersList(), *Ptr) DeleteElement(*PBMap\LayersList()) ; Free the map element DeleteMapElement(*PBMap\Layers()) *PBMap\Redraw = #True EndProcedure Procedure EnableLayer(MapGadget.i, Name.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Layers(Name)\Enabled = #True *PBMap\Redraw = #True EndProcedure Procedure DisableLayer(MapGadget.i, Name.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Layers(Name)\Enabled = #False *PBMap\Redraw = #True EndProcedure Procedure SetLayerAlpha(MapGadget.i, Name.s, Alpha.d) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Layers(Name)\Alpha = Alpha *PBMap\Redraw = #True EndProcedure Procedure.d GetLayerAlpha(MapGadget.i, Name.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\Layers(Name)\Alpha EndProcedure ;-*** ; If cache size exceeds limit, try to delete the oldest tiles used (first in the time stack) Procedure MemoryCacheManagement(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) LockMutex(*PBMap\MemoryCacheAccessMutex) ; Prevents threads to start or finish Protected CacheSize = MapSize(*PBMap\MemCache\Images()) * Pow(*PBMap\TileSize, 2) * 4 ; Size of a tile = TileSize * TileSize * 4 bytes (RGBA) Protected CacheLimit = *PBMap\Options\MaxMemCache * 1024 Protected EnabledLayers.i Protected MinTiles.i Protected MinCacheBytes.i Protected nx.i = DesktopScaledX(GadgetWidth(*PBMap\Gadget)) / 2 / *PBMap\TileSize Protected ny.i = DesktopScaledY(GadgetHeight(*PBMap\Gadget)) / 2 / *PBMap\TileSize Protected NewMap ProtectedTiles.i() Protected TileCoords.Coordinates Protected tx.i, ty.i, tilex.i, tiley.i, x.i, y.i Protected kq.q Protected key.s Protected tilemax.i = 1 << *PBMap\Zoom ForEach *PBMap\LayersList() If *PBMap\LayersList()\Enabled EnabledLayers + 1 EndIf Next If EnabledLayers > 0 MinTiles = (2 * nx + 3) * (2 * ny + 3) * EnabledLayers MinCacheBytes = MinTiles * Pow(*PBMap\TileSize, 2) * 4 CacheLimit = Max(CacheLimit, MinCacheBytes) LatLon2TileXY(@*PBMap\GeographicCoordinates, @TileCoords, *PBMap\Zoom) tx = Int(TileCoords\x) ty = Int(TileCoords\y) ForEach *PBMap\LayersList() If *PBMap\LayersList()\Enabled For y = -ny - 1 To ny + 1 For x = -nx - 1 To nx + 1 tilex = (tx + x) % tilemax If tilex < 0 tilex + tilemax EndIf tiley = ty + y If tiley >= 0 And tiley < tilemax kq = (*PBMap\Zoom << 8) | (tilex << 16) | (tiley << 36) key = *PBMap\LayersList()\Name + Str(kq) ProtectedTiles(key) = 1 EndIf Next Next EndIf Next EndIf MyDebug(*PBMap, "Cache size : " + Str(CacheSize/1024) + " / CacheLimit : " + Str(CacheLimit/1024), 5) If CacheSize > CacheLimit MyDebug(*PBMap, " Cache full. Trying cache cleaning", 5) ResetList(*PBMap\MemCache\ImagesTimeStack()) ; Try to free half the cache memory (one pass) While NextElement(*PBMap\MemCache\ImagesTimeStack()) And CacheSize > (CacheLimit / 2) ; /2 = half Protected CacheMapKey.s = *PBMap\MemCache\ImagesTimeStack()\MapKey ; Is the loading over If *PBMap\MemCache\Images(CacheMapKey)\Tile <= 0 ;TODO Should not verify this var directly If FindMapElement(ProtectedTiles(), CacheMapKey) = 0 MyDebug(*PBMap, " Delete " + CacheMapKey, 5) If *PBMap\MemCache\Images(CacheMapKey)\nImage;IsImage(*PBMap\MemCache\Images(CacheMapKey)\nImage) FreeImage(*PBMap\MemCache\Images(CacheMapKey)\nImage) MyDebug(*PBMap, " and free image nb " + Str(*PBMap\MemCache\Images(CacheMapKey)\nImage), 5) *PBMap\MemCache\Images(CacheMapKey)\nImage = 0 EndIf DeleteMapElement(*PBMap\MemCache\Images(), CacheMapKey) DeleteElement(*PBMap\MemCache\ImagesTimeStack(), 1) EndIf ; ElseIf *PBMap\MemCache\Images(CacheMapKey)\Tile = 0 ; MyDebug(*PBMap, " Delete " + CacheMapKey, 5) ; DeleteMapElement(*PBMap\MemCache\Images(), CacheMapKey) ; DeleteElement(*PBMap\MemCache\ImagesTimeStack(), 1) ; ElseIf *PBMap\MemCache\Images(CacheMapKey)\Tile > 0 ; ; If the thread is running, try to abort the download ; If *PBMap\MemCache\Images(CacheMapKey)\Tile\Download ; AbortHTTP(*PBMap\MemCache\Images(CacheMapKey)\Tile\Download) ; Could lead to error ; EndIf EndIf CacheSize = MapSize(*PBMap\MemCache\Images()) * Pow(*PBMap\TileSize, 2) * 4 ; Size of a tile = TileSize * TileSize * 4 bytes (RGBA) Wend MyDebug(*PBMap, " New cache size : " + Str(CacheSize/1024) + " / CacheLimit : " + Str(CacheLimit/1024), 5) If CacheSize > CacheLimit MyDebug(*PBMap, " Cache cleaning unsuccessfull, can't add new tiles.", 5) EndIf EndIf UnlockMutex(*PBMap\MemoryCacheAccessMutex) EndProcedure ;- LoadImage workaround ; by idle ; Check that the PNG file is valid Procedure _LoadImage(ImageNumber, File.s) Protected fn, pat, pos, res If UCase(GetExtensionPart(File)) = "PNG" pat = $444E4549 fn= ReadFile(#PB_Any, File) If fn pos = Lof(fn) FileSeek(fn, pos - 8) res = ReadLong(fn) CloseFile(fn) If res = pat ProcedureReturn LoadImage(ImageNumber, File) EndIf EndIf EndIf EndProcedure Procedure.i GetTileFromHDD(*PBMap.PBMap, CacheFile.s) ;Directly pass the PBMap structure (faster) Protected nImage.i, LifeTime.i, MaxLifeTime.i ; Everything is OK, loads the file nImage = _LoadImage(#PB_Any, CacheFile) If nImage MyDebug(*PBMap, " Success loading " + CacheFile + " as nImage " + Str(nImage), 3) ProcedureReturn nImage Else MyDebug(*PBMap, " Failed loading " + CacheFile + " as nImage " + Str(nImage) + " -> not an image !", 3) If DeleteFile(CacheFile) MyDebug(*PBMap, " Deleting faulty image file " + CacheFile, 3) Else MyDebug(*PBMap, " Can't delete faulty image file " + CacheFile, 3) EndIf EndIf ProcedureReturn #False EndProcedure ; **** OLD IMPORTANT NOTICE (please not remove) ; This original catchimage/saveimage method is a double operation (uncompress/recompress PNG) ; and is modifying the original PNG image which could lead to PNG error (Idle has spent hours debunking the 1 bit PNG bug) ; Protected *Buffer ; Protected nImage.i = -1 ; Protected timg ; *Buffer = ReceiveHTTPMemory(TileURL) ; TODO to thread by using #PB_HTTP_Asynchronous ; If *Buffer ; nImage = CatchImage(#PB_Any, *Buffer, MemorySize(*Buffer)) ; If IsImage(nImage) ; If SaveImage(nImage, CacheFile, #PB_ImagePlugin_PNG, 0, 32) ; The 32 is needed !!!! ; MyDebug(*PBMap, "Loaded from web " + TileURL + " as CacheFile " + CacheFile, 3) ; Else ; MyDebug(*PBMap, "Loaded from web " + TileURL + " but cannot save to CacheFile " + CacheFile, 3) ; EndIf ; FreeMemory(*Buffer) ; Else ; MyDebug(*PBMap, "Can't catch image loaded from web " + TileURL, 3) ; nImage = -1 ; EndIf ; Else ; MyDebug(*PBMap, " Problem loading from web " + TileURL, 3) ; EndIf ; **** ;-*** These are threaded Threaded Progress = 0, Quit = #False ; --------------------------------------------------------------------------- ; SECTION : Thread de telechargement de tuiles ; OBJECTIF : Telecharger une tuile en asynchrone avec arret propre ; POURQUOI : Eviter KillThread() et permettre une fermeture cooperative ; COMMENT : Verification du flag StopDownloads + AbortHTTP() si besoin ; --------------------------------------------------------------------------- Procedure GetImageThread(*Tile.Tile) ;LockMutex(*PBMap\MemoryCacheAccessMutex) ;MyDebug(*PBMap, "Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " starting for image " + *Tile\CacheFile, 5) ; If MemoryCache is currently being cleaned, abort ; If *PBMap\MemoryCacheAccessNB = -1 ; MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " canceled because of cleaning.", 5) ; *Tile\Size = 0 ; \Size = 0 signals that the download has failed ; PostEvent(#PB_Event_Gadget, *PBMap\Window, *PBMap\Gadget, #PB_MAP_TILE_CLEANUP, *Tile) ; To free memory outside the thread ; UnlockMutex(*PBMap\MemoryCacheAccessMutex) ; ProcedureReturn ; EndIf ; We're accessing MemoryCache ;UnlockMutex(*PBMap\MemoryCacheAccessMutex) *Tile\Size = 0 If *Tile\PBMap And *Tile\PBMap\StopDownloads PostEvent(#PB_Event_Gadget, *Tile\Window, *Tile\Gadget, #PB_MAP_TILE_CLEANUP, *Tile) ProcedureReturn EndIf *Tile\Download = ReceiveHTTPFile(*Tile\URL, *Tile\CacheFile, #PB_HTTP_Asynchronous, #USERAGENT) ;TODO : obtain original file size to compare and eventually delete truncated file If *Tile\Download Repeat Progress = HTTPProgress(*Tile\Download) Select Progress Case #PB_HTTP_Success *Tile\Size = FinishHTTP(*Tile\Download) ; \Size signals that the download is OK ;MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " finished. Size : " + Str(*Tile\Size), 5) Quit = #True Case #PB_HTTP_Failed FinishHTTP(*Tile\Download) *Tile\Size = 0 ; \Size = 0 signals that the download has failed ;MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " failed.", 5) Quit = #True Case #PB_HTTP_Aborted FinishHTTP(*Tile\Download) *Tile\Size = 0 ; \Size = 0 signals that the download has failed ;MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " aborted.", 5) Quit = #True Default ;MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " downloading " + Str(Progress) + " bytes", 5) If ElapsedMilliseconds() - *Tile\Time > 10000 ;MyDebug(*PBMap, " Thread nb " + Str(*Tile\GetImageThread) + " " + *Tile\key + " for image " + *Tile\CacheFile + " canceled after 10 seconds.", 5) AbortHTTP(*Tile\Download) EndIf If *Tile\PBMap And *Tile\PBMap\StopDownloads AbortHTTP(*Tile\Download) EndIf EndSelect Delay(200) ; Frees CPU Until Quit EndIf ; End of the memory cache access ;LockMutex(*PBMap\MemoryCacheAccessMutex) ; To free memory and eventually delete aborted image file outside the thread PostEvent(#PB_Event_Gadget, *Tile\Window, *Tile\Gadget, #PB_MAP_TILE_CLEANUP, *Tile) ;UnlockMutex(*PBMap\MemoryCacheAccessMutex) EndProcedure ;-*** Procedure.i GetTile(MapGadget.i, key.s, URL.s, CacheFile.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ; MemoryCache access management LockMutex(*PBMap\MemoryCacheAccessMutex) ; Try to find the tile in memory cache Protected *timg.ImgMemCach = FindMapElement(*PBMap\MemCache\Images(), key) If *timg MyDebug(*PBMap, "Key : " + key + " found in memory cache", 4) ; Is the associated image already loaded in memory ? If *timg\nImage ; Yes, returns the image's nb MyDebug(*PBMap, " as image " + *timg\nImage, 4) ; *** Cache management ; Retrieves the image in the time stack, push it to the end (to say it's the lastly used) ChangeCurrentElement(*PBMap\MemCache\ImagesTimeStack(), *timg\TimeStackPtr) MoveElement(*PBMap\MemCache\ImagesTimeStack(), #PB_List_Last) ; *timg\TimeStackPtr = LastElement(*PBMap\MemCache\ImagesTimeStack()) ; *** UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn *timg Else ; No, try to load it from HD (see below) MyDebug(*PBMap, " but not the image.", 4) EndIf Else ; The tile has not been found in the cache, so creates a new cache element *timg = AddMapElement(*PBMap\MemCache\Images(), key) If *timg = 0 MyDebug(*PBMap, " Can't add a new cache element.", 4) UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn #False EndIf ; add a new time stack element at the End LastElement(*PBMap\MemCache\ImagesTimeStack()) ; Stores the time stack ptr *timg\TimeStackPtr = AddElement(*PBMap\MemCache\ImagesTimeStack()) If *timg\TimeStackPtr = 0 MyDebug(*PBMap, " Can't add a new time stack element.", 4) DeleteMapElement(*PBMap\MemCache\Images()) UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn #False EndIf ; Associates the time stack element to the cache element *PBMap\MemCache\ImagesTimeStack()\MapKey = MapKey(*PBMap\MemCache\Images()) MyDebug(*PBMap, "Key : " + key + " added in memory cache", 4) EndIf ; If there's no active downloading thread for this image If *timg\Tile <= 0 *timg\nImage = 0 *timg\Size = FileSize(CacheFile) ; Does a valid file exists on HD ? Try to load it. If *timg\Size >= 0 ; Manage tile file lifetime, delete if too old, or if size = 0 If *PBMap\Options\TileLifetime <> -1 If *timg\Size = 0 Or (Date() - GetFileDate(CacheFile, #PB_Date_Modified) > *PBMap\Options\TileLifetime) ; If Lifetime > MaxLifeTime ; There's a bug with #PB_Date_Created If DeleteFile(CacheFile) MyDebug(*PBMap, " Deleting image file " + CacheFile, 3) *timg\Size = 0 Else MyDebug(*PBMap, " Can't delete image file " + CacheFile, 3) EndIf UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn #False EndIf EndIf ; Try to load tile's image from HD *timg\nImage = GetTileFromHDD(*PBMap, CacheFile.s) If *timg\nImage ; Success : image found and loaded from HDD *timg\Alpha = 0 UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn *timg EndIf EndIf ; If GetTileFromHDD failed, will try to download the image from the web in a thread MyDebug(*PBMap, " Failed loading from HDD " + CacheFile + " -> Filesize = " + FileSize(CacheFile), 3) If *PBMap\ThreadsNB < *PBMap\Options\MaxThreads If *PBMap\DownloadSlots < *PBMap\Options\MaxDownloadSlots Protected *NewTile.Tile = AllocateMemory(SizeOf(Tile)) If *NewTile *timg\Tile = *NewTile ; There's now a loading thread *timg\Alpha = 0 With *NewTile ; New tile parameters \key = key \URL = URL \CacheFile = CacheFile \nImage = 0 \Time = ElapsedMilliseconds() \Window = *PBMap\Window \Gadget = *PBMap\Gadget \PBMap = *PBMap \GetImageThread = CreateThread(@GetImageThread(), *NewTile) If \GetImageThread MyDebug(*PBMap, " Creating get image thread nb " + Str(\GetImageThread) + " to get " + CacheFile + " (key = " + key, 3) *PBMap\ThreadsNB + 1 *PBMap\DownloadSlots + 1 Else ; Thread creation failed this time *timg\Tile = 0 MyDebug(*PBMap, " Can't create get image thread to get " + CacheFile, 3) FreeMemory(*NewTile) EndIf EndWith Else MyDebug(*PBMap, " Error, can't allocate memory for a new tile loading thread", 3) EndIf Else MyDebug(*PBMap, " Thread needed " + key + " for image " + CacheFile + " canceled because no free download slot.", 5) EndIf Else MyDebug(*PBMap, " Error, maximum threads nb reached", 3) EndIf EndIf UnlockMutex(*PBMap\MemoryCacheAccessMutex) ProcedureReturn #False EndProcedure Procedure DrawTiles(MapGadget.i, *Drawing.DrawingParameters, LayerName.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected x.i, y.i, kq.q Protected tx.i = Int(*Drawing\TileCoordinates\x) ; Don't forget the Int() ! Protected ty.i = Int(*Drawing\TileCoordinates\y) Protected nx.i = *Drawing\RadiusX / *PBMap\TileSize ; How many tiles around the point Protected ny.i = *Drawing\RadiusY / *PBMap\TileSize Protected px.i, py.i, *timg.ImgMemCach, tilex.i, tiley.i, key.s Protected URL.s, CacheFile.s Protected tilemax.i = 1<<*PBMap\Zoom Protected HereLoadBalancing.b ; Here is providing a load balancing system FindMapElement(*PBMap\Layers(), LayerName) MyDebug(*PBMap, "Drawing tiles") For y = - ny - 1 To ny + 1 For x = - nx - 1 To nx + 1 px = *Drawing\RadiusX + x * *PBMap\TileSize - *Drawing\DeltaX py = *Drawing\RadiusY + y * *PBMap\TileSize - *Drawing\DeltaY tilex = (tx + x) % tilemax If tilex < 0 tilex + tilemax EndIf tiley = ty + y If tiley >= 0 And tiley < tilemax kq = (*PBMap\Zoom << 8) | (tilex << 16) | (tiley << 36) key = LayerName + Str(kq) ; Creates the cache tree based on the OSM tree+Layer : layer/zoom/x/y.png Protected DirName.s = *PBMap\Options\HDDCachePath + LayerName If FileSize(DirName) <> -2 If CreateDirectory(DirName) = #False ; Creates a directory based on the layer name Error(MapGadget, "Can't create the following layer directory : " + DirName) Else MyDebug(*PBMap, DirName + " successfully created", 4) EndIf EndIf ; Creates the sub-directory based on the zoom DirName + slash + Str(*PBMap\Zoom) If FileSize(DirName) <> -2 If CreateDirectory(DirName) = #False Error(MapGadget, "Can't create the following zoom directory : " + DirName) Else MyDebug(*PBMap, DirName + " successfully created", 4) EndIf EndIf ; Creates the sub-directory based on x DirName.s + slash + Str(tilex) If FileSize(DirName) <> -2 If CreateDirectory(DirName) = #False Error(MapGadget, "Can't create the following x directory : " + DirName) Else MyDebug(*PBMap, DirName + " successfully created", 4) EndIf EndIf With *PBMap\Layers() Select \LayerType ;---- OSM tiles Case 0 URL = \ServerURL + Str(*PBMap\Zoom) + "/" + Str(tilex) + "/" + Str(tiley) + ".png" ; Tile cache name based on y CacheFile = DirName + slash + Str(tiley) + ".png" ;---- Here tiles Case 1 HereLoadBalancing = 1 + ((tiley + tilex) % 4) ; {Base URL}{Path}{resource (tile type)}/{Map id}/{scheme}/{zoom}/{column}/{row}/{size}/{format}?app_id={YOUR_APP_ID}&app_code={YOUR_APP_CODE}&{param}={value} URL = "https://" + StrU(HereLoadBalancing, #PB_Byte) + "." + \ServerURL + \path + \ressource + "/" + \id + "/" + \scheme + "/" + Str(*PBMap\Zoom) + "/" + Str(tilex) + "/" + Str(tiley) + "/256/" + \format + "?app_id=" + \APP_ID + "&app_code=" + \APP_CODE + "&lg=" + \lg + "&lg2=" + \lg2 If \param <> "" URL + "&" + \param EndIf ; Tile cache name based on y CacheFile = DirName + slash + Str(tiley) + "." + \format ;---- GeoServer / geowebcache - google maps service tiles Case 2 ; template 'http://localhost:8080/geowebcache/service/gmaps?layers=layer-name&zoom={Z}&x={X}&y={Y}&format=image/png' URL = \ServerURL + \path + "?layers=" + \ServerLayerName + "&zoom={" + Str(*PBMap\Zoom) + "}&x={" + Str(tilex) + "}&y={" + Str(tiley) + "}&format=" + \format ; Tile cache name based on y CacheFile = DirName + slash + Str(tiley) + ".png" EndSelect EndWith *timg = GetTile(MapGadget, key, URL, CacheFile) If *timg And *timg\nImage If *PBMap\CallBackDrawTile ;CallFunctionFast(*PBMap\CallBackDrawTile, px, py, *timg\nImage) *PBMap\CallBackDrawTile(px, py, *timg\nImage, *PBMap\Layers()\Alpha) *PBMap\Redraw = #True Else MovePathCursor(px, py) If *timg\Alpha <= 224 DrawVectorImage(ImageID(*timg\nImage), *timg\Alpha * *PBMap\Layers()\Alpha) *timg\Alpha + 32 *PBMap\Redraw = #True Else DrawVectorImage(ImageID(*timg\nImage), 255 * *PBMap\Layers()\Alpha) *timg\Alpha = 256 EndIf EndIf Else MovePathCursor(px, py) DrawVectorImage(ImageID(*PBMap\ImgLoading), 255 * *PBMap\Layers()\Alpha) EndIf Else ; If *PBMap\Layers()\Name = "" MovePathCursor(px, py) DrawVectorImage(ImageID(*PBMap\ImgNothing), 255 * *PBMap\Layers()\Alpha) ; EndIf EndIf If *PBMap\Options\ShowDebugInfos VectorFont(FontID(*PBMap\StandardFont), 16) VectorSourceColor(RGBA(0, 0, 0, 80)) MovePathCursor(px, py) DrawVectorText("x:" + Str(tilex)) MovePathCursor(px, py + 16) DrawVectorText("y:" + Str(tiley)) EndIf Next Next EndProcedure Procedure DrawPointer(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *PBMap\CallBackMainPointer > 0 ; @Procedure(X.i, Y.i) to DrawPointer (you must use VectorDrawing lib) CallFunctionFast(*PBMap\CallBackMainPointer, *Drawing\RadiusX, *Drawing\RadiusY) Else VectorSourceColor(RGBA($FF, 0, 0, $FF)) MovePathCursor(*Drawing\RadiusX, *Drawing\RadiusY) AddPathLine(-8, -16, #PB_Path_Relative) AddPathCircle(8, 0, 8, 180, 0, #PB_Path_Relative) AddPathLine(-8, 16, #PB_Path_Relative) AddPathCircle(0, -16, 5, 0, 360, #PB_Path_Relative) VectorSourceColor(RGBA($FF, 0, 0, $FF)) FillPath(#PB_Path_Preserve):VectorSourceColor(RGBA($FF, 0, 0, $FF)); RGBA(0, 0, 0, 255)) StrokePath(1) EndIf EndProcedure Procedure DrawScale(MapGadget.i, *Drawing.DrawingParameters,x,y,alpha=80) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected sunit.s Protected Scale.d= 40075*Cos(Radian(*PBMap\GeographicCoordinates\Latitude))/Pow(2,*PBMap\Zoom) / 2 Select *PBMap\Options\ScaleUnit Case #SCALE_Nautical Scale * 0.539957 sunit = " Nm" Case #SCALE_KM; sunit = " Km" EndSelect VectorFont(FontID(*PBMap\StandardFont), 10) VectorSourceColor(RGBA(0, 0, 0, alpha)) MovePathCursor(x,y) DrawVectorText(StrD(Scale,3)+sunit) MovePathCursor(x,y+12) AddPathLine(x+128,y+12) StrokePath(1) EndProcedure Procedure DrawDegrees(MapGadget.i, *Drawing.DrawingParameters, alpha=192) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected nx, ny, nx1, ny1, x, y Protected pos1.PixelCoordinates, pos2.PixelCoordinates, Degrees1.GeographicCoordinates, degrees2.GeographicCoordinates CopyStructure(*Drawing\Bounds\NorthWest, @Degrees1, GeographicCoordinates) CopyStructure(*Drawing\Bounds\SouthEast, @Degrees2, GeographicCoordinates) ; ensure we stay positive for the drawing nx = Mod(Mod(Round(Degrees1\Longitude, #PB_Round_Down)-1, 360) + 360, 360) ny = Round(Degrees1\Latitude, #PB_Round_Up) +1 nx1 = Mod(Mod(Round(Degrees2\Longitude, #PB_Round_Up) +1, 360) + 360, 360) ny1 = Round(Degrees2\Latitude, #PB_Round_Down)-1 Degrees1\Longitude = nx Degrees1\Latitude = ny Degrees2\Longitude = nx1 Degrees2\Latitude = ny1 ; Debug "NW : " + StrD(Degrees1\Longitude) + " ; NE : " + StrD(Degrees2\Longitude) LatLon2PixelRel(MapGadget, @Degrees1, @pos1, *PBMap\Zoom) LatLon2PixelRel(MapGadget, @Degrees2, @pos2, *PBMap\Zoom) VectorFont(FontID(*PBMap\StandardFont), 10) VectorSourceColor(RGBA(0, 0, 0, alpha)) ; draw latitudes For y = ny1 To ny Degrees1\Longitude = nx Degrees1\Latitude = y LatLon2PixelRel(MapGadget, @Degrees1, @pos1, *PBMap\Zoom) MovePathCursor(pos1\x, pos1\y) AddPathLine( pos2\x, pos1\y) MovePathCursor(10, pos1\y) DrawVectorText(StrD(y, 1)) Next ; draw longitudes x = nx Repeat Degrees1\Longitude = x Degrees1\Latitude = ny LatLon2PixelRel(MapGadget, @Degrees1, @pos1, *PBMap\Zoom) MovePathCursor(pos1\x, pos1\y) AddPathLine( pos1\x, pos2\y) MovePathCursor(pos1\x,10) DrawVectorText(StrD(Mod(x + 180, 360) - 180, 1)) x = (x + 1)%360 Until x = nx1 StrokePath(1) EndProcedure Procedure DrawZoom(MapGadget.i, x.i, y.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) VectorFont(FontID(*PBMap\StandardFont), 20) VectorSourceColor(RGBA(0, 0, 0,150)) MovePathCursor(x,y) DrawVectorText(Str(GetZoom(MapGadget))) EndProcedure ;-*** Tracks Procedure DrawTrackPointer(MapGadget.i, x.d, y.d, dist.l) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected color.l color=RGBA(0, 0, 0, 255) MovePathCursor(x,y) AddPathLine(-8,-16,#PB_Path_Relative) AddPathLine(16,0,#PB_Path_Relative) AddPathLine(-8,16,#PB_Path_Relative) VectorSourceColor(color) AddPathCircle(x,y-20,14) FillPath() VectorSourceColor(RGBA(255, 255, 255, 255)) AddPathCircle(x,y-20,12) FillPath() VectorFont(FontID(*PBMap\StandardFont), 13) MovePathCursor(x-VectorTextWidth(Str(dist))/2, y-20-VectorTextHeight(Str(dist))/2) VectorSourceColor(RGBA(0, 0, 0, 255)) DrawVectorText(Str(dist)) EndProcedure Procedure DrawTrackPointerFirst(MapGadget.i, x.d, y.d, dist.l) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected color.l color=RGBA(0, 0, 0, 255) MovePathCursor(x,y) AddPathLine(-9,-17,#PB_Path_Relative) AddPathLine(17,0,#PB_Path_Relative) AddPathLine(-9,17,#PB_Path_Relative) VectorSourceColor(color) AddPathCircle(x,y-24,16) FillPath() VectorSourceColor(RGBA(255, 0, 0, 255)) AddPathCircle(x,y-24,14) FillPath() VectorFont(FontID(*PBMap\StandardFont), 14) MovePathCursor(x-VectorTextWidth(Str(dist))/2, y-24-VectorTextHeight(Str(dist))/2) VectorSourceColor(RGBA(0, 0, 0, 255)) DrawVectorText(Str(dist)) EndProcedure Procedure DeleteTrack(MapGadget.i, *Ptr) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *Ptr ChangeCurrentElement(*PBMap\TracksList(), *Ptr) DeleteElement(*PBMap\TracksList()) EndIf EndProcedure Procedure DeleteSelectedTracks(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ForEach *PBMap\TracksList() If *PBMap\TracksList()\Selected DeleteElement(*PBMap\TracksList()) *PBMap\Redraw = #True EndIf Next EndProcedure Procedure ClearTracks(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ClearList(*PBMap\TracksList()) *PBMap\Redraw = #True EndProcedure Procedure SetTrackColour(MapGadget.i, *Ptr, Colour.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *Ptr ChangeCurrentElement(*PBMap\TracksList(), *Ptr) *PBMap\TracksList()\Colour = Colour *PBMap\Redraw = #True EndIf EndProcedure Procedure DrawTracks(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Pixel.PixelCoordinates Protected Location.GeographicCoordinates Protected km.f, memKm.i With *PBMap\TracksList() ; Trace Track If ListSize(*PBMap\TracksList()) > 0 BeginVectorLayer() ForEach *PBMap\TracksList() If ListSize(\Track()) > 0 ; Check visibility \Visible = #False ForEach \Track() If IsInDrawingPixelBoundaries(MapGadget, *Drawing, @*PBMap\TracksList()\Track()) \Visible = #True Break EndIf Next If \Visible ; Draw tracks ForEach \Track() LatLon2PixelRel(MapGadget, @*PBMap\TracksList()\Track(), @Pixel, *PBMap\Zoom) If ListIndex(\Track()) = 0 MovePathCursor(Pixel\x, Pixel\y) Else AddPathLine(Pixel\x, Pixel\y) EndIf Next ; \BoundingBox\x = PathBoundsX() ; \BoundingBox\y = PathBoundsY() ; \BoundingBox\w = PathBoundsWidth() ; \BoundingBox\h = PathBoundsHeight() If \Focus VectorSourceColor(*PBMap\Options\ColourFocus) ElseIf \Selected VectorSourceColor(*PBMap\Options\ColourSelected) Else VectorSourceColor(\Colour) EndIf StrokePath(\StrokeWidth, #PB_Path_RoundEnd|#PB_Path_RoundCorner) ; YA pour marquer chaque point d'un rond ForEach \Track() LatLon2PixelRel(MapGadget, @*PBMap\TracksList()\Track(), @Pixel, *PBMap\Zoom) AddPathCircle(Pixel\x,Pixel\y,(\StrokeWidth / 4)) Next VectorSourceColor(RGBA(255, 255, 0, 255)) StrokePath(1) EndIf EndIf Next EndVectorLayer() ;Draw distances If *PBMap\Options\ShowTrackKms And *PBMap\Zoom > 10 BeginVectorLayer() ForEach *PBMap\TracksList() If \Visible km = 0 : memKm = -1 ForEach *PBMap\TracksList()\Track() ; Test Distance If ListIndex(\Track()) = 0 Location\Latitude = \Track()\Latitude Location\Longitude = \Track()\Longitude Else km = km + HaversineInKM(@Location, @*PBMap\TracksList()\Track()) Location\Latitude = \Track()\Latitude Location\Longitude = \Track()\Longitude EndIf LatLon2PixelRel(MapGadget, @*PBMap\TracksList()\Track(), @Pixel, *PBMap\Zoom) If Int(km) <> memKm memKm = Int(km) If Int(km) = 0 DrawTrackPointerFirst(MapGadget, Pixel\x , Pixel\y, Int(km)) Else DrawTrackPointer(MapGadget, Pixel\x , Pixel\y, Int(km)) EndIf EndIf Next EndIf Next EndVectorLayer() EndIf EndIf EndWith EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Chargement GPX securise ; OBJECTIF : Charger un fichier GPX en verifiant la structure XML ; POURQUOI : Eviter les acces nuls sur des fichiers invalides ; COMMENT : Verification des noeuds + liberation XML systematique ; --------------------------------------------------------------------------- Procedure.i LoadGpxFile(MapGadget.i, FileName.s) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected XmlDoc.i = LoadXML(#PB_Any, FileName.s) If XmlDoc = 0 Error(MapGadget, "Cannot load XML file: " + FileName) ProcedureReturn 0 EndIf Protected Message.s If XMLStatus(XmlDoc) <> #PB_XML_Success Message = "Error in the XML file:" + Chr(13) Message + "Message: " + XMLError(XmlDoc) + Chr(13) Message + "Line: " + Str(XMLErrorLine(XmlDoc)) + " Character: " + Str(XMLErrorPosition(XmlDoc)) Error(MapGadget, Message) FreeXML(XmlDoc) ProcedureReturn 0 EndIf Protected *MainNode, *child, child.l *MainNode = MainXMLNode(XmlDoc) *MainNode = XMLNodeFromPath(*MainNode, "/gpx/trk/trkseg") If *MainNode = 0 Error(MapGadget, "Invalid GPX structure: missing /gpx/trk/trkseg") FreeXML(XmlDoc) ProcedureReturn 0 EndIf Protected *NewTrack.Tracks = AddElement(*PBMap\TracksList()) *PBMap\TracksList()\StrokeWidth = *PBMap\Options\StrokeWidthTrackDefault *PBMap\TracksList()\Colour = *PBMap\Options\ColourTrackDefault For child = 1 To XMLChildCount(*MainNode) *child = ChildXMLNode(*MainNode, child) AddElement(*NewTrack\Track()) If ExamineXMLAttributes(*child) While NextXMLAttribute(*child) Select XMLAttributeName(*child) Case "lat" *NewTrack\Track()\Latitude = ValD(XMLAttributeValue(*child)) Case "lon" *NewTrack\Track()\Longitude = ValD(XMLAttributeValue(*child)) EndSelect Wend EndIf Next SetZoomToTracks(MapGadget, LastElement(*PBMap\TracksList())) ; <-To center the view, and zoom on the tracks FreeXML(XmlDoc) ProcedureReturn *NewTrack EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Sauvegarde GPX securisee ; OBJECTIF : Exporter un track avec verification du resultat XML ; POURQUOI : Eviter un fichier corrompu non detecte ; COMMENT : Creation XML avec identifiant et liberation systematique ; --------------------------------------------------------------------------- Procedure.i SaveGpxFile(MapGadget.i, FileName.s, *Track.Tracks) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Message.s Protected XmlDoc.i = CreateXML(#PB_Any) If XmlDoc Protected *MainNode, *subNode, *child *MainNode = CreateXMLNode(RootXMLNode(XmlDoc), "gpx") *subNode = CreateXMLNode(*MainNode, "trk") *subNode = CreateXMLNode(*subNode, "trkseg") ForEach *Track\Track() *child = CreateXMLNode(*subNode, "trkpt") SetXMLAttribute(*child, "lat", StrD(*Track\Track()\Latitude)) SetXMLAttribute(*child, "lon", StrD(*Track\Track()\Longitude)) Next If SaveXML(XmlDoc, FileName) = 0 Message = "Error in the XML file:" + Chr(13) Message + "Message: " + XMLError(XmlDoc) + Chr(13) Message + "Line: " + Str(XMLErrorLine(XmlDoc)) + " Character: " + Str(XMLErrorPosition(XmlDoc)) Error(MapGadget, Message) FreeXML(XmlDoc) ProcedureReturn #False EndIf If XMLStatus(XmlDoc) <> #PB_XML_Success Message = "Error in the XML file:" + Chr(13) Message + "Message: " + XMLError(XmlDoc) + Chr(13) Message + "Line: " + Str(XMLErrorLine(XmlDoc)) + " Character: " + Str(XMLErrorPosition(XmlDoc)) Error(MapGadget, Message) FreeXML(XmlDoc) ProcedureReturn #False EndIf FreeXML(XmlDoc) ProcedureReturn #True Else ProcedureReturn #False EndIf EndProcedure ;-*** Markers Procedure ClearMarkers(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ClearList(*PBMap\Markers()) *PBMap\Redraw = #True EndProcedure Procedure DeleteMarker(MapGadget.i, *Ptr) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *Ptr ChangeCurrentElement(*PBMap\Markers(), *Ptr) DeleteElement(*PBMap\Markers()) *PBMap\Redraw = #True EndIf EndProcedure Procedure DeleteSelectedMarkers(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ForEach *PBMap\Markers() If *PBMap\Markers()\Selected DeleteElement(*PBMap\Markers()) *PBMap\Redraw = #True EndIf Next EndProcedure Procedure.i AddMarker(MapGadget.i, Latitude.d, Longitude.d, Identifier.s = "", Legend.s = "", Color.l=-1, CallBackPointer.i = -1) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Ptr = AddElement(*PBMap\Markers()) If *Ptr *PBMap\Markers()\GeographicCoordinates\Latitude = Latitude *PBMap\Markers()\GeographicCoordinates\Longitude = ClipLongitude(Longitude) *PBMap\Markers()\Identifier = Identifier *PBMap\Markers()\Legend = Legend *PBMap\Markers()\Color = Color *PBMap\Markers()\CallBackPointer = CallBackPointer *PBMap\Redraw = #True ProcedureReturn *Ptr EndIf EndProcedure Procedure MarkerIdentifierChange() Protected *Marker.Marker = GetGadgetData(EventGadget()) If GetGadgetText(EventGadget()) <> *Marker\Identifier *Marker\Identifier = GetGadgetText(EventGadget()) EndIf EndProcedure Procedure MarkerLegendChange() Protected *Marker.Marker = GetGadgetData(EventGadget()) If GetGadgetText(EventGadget()) <> *Marker\Legend *Marker\Legend = GetGadgetText(EventGadget()) EndIf EndProcedure Procedure MarkerEditCloseWindow(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ForEach *PBMap\Markers() If *PBMap\Markers()\EditWindow = EventWindow() *PBMap\Markers()\EditWindow = 0 EndIf Next CloseWindow(EventWindow()) EndProcedure Procedure MarkerEdit(MapGadget.i, *Marker.Marker) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If *Marker\EditWindow = 0 ; Check that this marker has no already opened window Protected WindowMarkerEdit = OpenWindow(#PB_Any, WindowX(*PBMap\Window) + WindowWidth(*PBMap\Window) / 2 - 150, WindowY(*PBMap\Window)+ WindowHeight(*PBMap\Window) / 2 + 50, 300, 100, "Marker Edit", #PB_Window_SystemMenu | #PB_Window_TitleBar) StickyWindow(WindowMarkerEdit, #True) TextGadget(#PB_Any, 2, 2, 80, 25, gettext("Identifier")) TextGadget(#PB_Any, 2, 27, 80, 25, gettext("Legend")) Protected StringIdentifier = StringGadget(#PB_Any, 84, 2, 120, 25, *Marker\Identifier) : SetGadgetData(StringIdentifier, *Marker) Protected EditorLegend = EditorGadget(#PB_Any, 84, 27, 210, 70) : SetGadgetText(EditorLegend, *Marker\Legend) : SetGadgetData(EditorLegend, *Marker) *Marker\EditWindow = WindowMarkerEdit BindGadgetEvent(StringIdentifier, @MarkerIdentifierChange(), #PB_EventType_Change) BindGadgetEvent(EditorLegend, @MarkerLegendChange(), #PB_EventType_Change) BindEvent(#PB_Event_CloseWindow, @MarkerEditCloseWindow(), WindowMarkerEdit) Else SetActiveWindow(*Marker\EditWindow) EndIf EndProcedure Procedure DrawMarker(MapGadget.i, x.i, y.i, Nb.i, *Marker.Marker) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Text.s VectorSourceColor(*Marker\Color) MovePathCursor(x, y) AddPathLine(-8, -16, #PB_Path_Relative) AddPathCircle(8, 0, 8, 180, 0, #PB_Path_Relative) AddPathLine(-8, 16, #PB_Path_Relative) ; FillPath(#PB_Path_Preserve) ; ClipPath(#PB_Path_Preserve) AddPathCircle(0, -16, 5, 0, 360, #PB_Path_Relative) VectorSourceColor(*Marker\Color) FillPath(#PB_Path_Preserve) If *Marker\Focus VectorSourceColor(*PBMap\Options\ColourFocus) StrokePath(3) ElseIf *Marker\Selected VectorSourceColor(*PBMap\Options\ColourSelected) StrokePath(4) Else VectorSourceColor(*Marker\Color) StrokePath(1) EndIf If *PBMap\Options\ShowMarkersNb If *Marker\Identifier = "" Text.s = Str(Nb) Else Text.s = *Marker\Identifier EndIf VectorFont(FontID(*PBMap\StandardFont), 13) MovePathCursor(x - VectorTextWidth(Text) / 2, y) VectorSourceColor(RGBA(0, 0, 0, 255)) DrawVectorText(Text) EndIf If *PBMap\Options\ShowMarkersLegend And *Marker\Legend <> "" VectorFont(FontID(*PBMap\StandardFont), 13) ; dessin d'un cadre avec fond transparent Protected Height = VectorParagraphHeight(*Marker\Legend, 100, 100) Protected Width.l If Height < 20 ; une ligne Width = VectorTextWidth(*Marker\Legend) Else Width = 100 EndIf AddPathBox(x - (Width / 2), y - 30 - Height, Width, Height) VectorSourceColor(RGBA(168, 255, 255, 100)) FillPath() AddPathBox(x - (Width / 2), y - 30 - Height, Width, Height) VectorSourceColor(RGBA(36, 36, 255, 100)) StrokePath(2) MovePathCursor(x - 50, y - 30 - Height) VectorSourceColor(RGBA(0, 0, 0, 255)) DrawVectorParagraph(*Marker\Legend, 100, Height, #PB_VectorParagraph_Center) EndIf EndProcedure ; Draw all markers Procedure DrawMarkers(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Pixel.PixelCoordinates If *PBMap\Options\EnableClusters BuildClusters(MapGadget, *Drawing) ForEach *PBMap\Clusters() If *PBMap\Clusters()\Count >= *PBMap\Options\ClusterMinCount DrawCluster(MapGadget, @*PBMap\Clusters()) ElseIf *PBMap\Clusters()\Marker ChangeCurrentElement(*PBMap\Markers(), *PBMap\Clusters()\Marker) If *PBMap\Markers()\CallBackPointer > 0 CallFunctionFast(*PBMap\Markers()\CallBackPointer, *PBMap\Clusters()\MarkerX, *PBMap\Clusters()\MarkerY, *PBMap\Markers()\Focus, *PBMap\Markers()\Selected) Else DrawMarker(MapGadget, *PBMap\Clusters()\MarkerX, *PBMap\Clusters()\MarkerY, ListIndex(*PBMap\Markers()), @*PBMap\Markers()) EndIf EndIf Next Else ForEach *PBMap\Markers() If IsInDrawingPixelBoundaries(MapGadget, *Drawing, @*PBMap\Markers()\GeographicCoordinates) LatLon2PixelRel(MapGadget, @*PBMap\Markers()\GeographicCoordinates, @Pixel, *PBMap\Zoom) If *PBMap\Markers()\CallBackPointer > 0 CallFunctionFast(*PBMap\Markers()\CallBackPointer, Pixel\x, Pixel\y, *PBMap\Markers()\Focus, *PBMap\Markers()\Selected) Else DrawMarker(MapGadget, Pixel\x, Pixel\y, ListIndex(*PBMap\Markers()), @*PBMap\Markers()) EndIf EndIf Next EndIf EndProcedure ;-*** Main drawing stuff Procedure DrawDebugInfos(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ; Display how many images in cache VectorFont(FontID(*PBMap\StandardFont), 16) VectorSourceColor(RGBA(0, 0, 0, 80)) MovePathCursor(50, 50) DrawVectorText("Images in cache : " + Str(MapSize(*PBMap\MemCache\Images()))) MovePathCursor(50, 70) Protected ThreadCounter = 0 ForEach *PBMap\MemCache\Images() If *PBMap\MemCache\Images()\Tile > 0 If IsThread(*PBMap\MemCache\Images()\Tile\GetImageThread) ThreadCounter + 1 EndIf EndIf Next DrawVectorText("Threads nb : " + Str(ThreadCounter)) MovePathCursor(50, 90) DrawVectorText("Zoom : " + Str(*PBMap\Zoom)) MovePathCursor(50, 110) DrawVectorText("Lat-Lon 1 : " + StrD(*Drawing\Bounds\NorthWest\Latitude) + "," + StrD(*Drawing\Bounds\NorthWest\Longitude)) MovePathCursor(50, 130) DrawVectorText("Lat-Lon 2 : " + StrD(*Drawing\Bounds\SouthEast\Latitude) + "," + StrD(*Drawing\Bounds\SouthEast\Longitude)) EndProcedure Procedure DrawOSMCopyright(MapGadget.i, *Drawing.DrawingParameters) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Text.s = "© OpenStreetMap contributors" VectorFont(FontID(*PBMap\StandardFont), 12) VectorSourceColor(RGBA(0, 0, 0, 80)) MovePathCursor(DesktopScaledX(GadgetWidth(*PBMap\Gadget)) - VectorTextWidth(Text), GadgetHeight(*PBMap\Gadget) - 20) DrawVectorText(Text) EndProcedure Procedure Drawing(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected *Drawing.DrawingParameters = @*PBMap\Drawing Protected PixelCenter.PixelCoordinates Protected Px.d, Py.d,a, ts = *PBMap\TileSize, nx, ny Protected LayerOrder.i = 0 Protected NW.Coordinates, SE.Coordinates Protected OSMCopyright.i = #False *PBMap\Dirty = #False *PBMap\Redraw = #False ; *** Precalc some values *Drawing\RadiusX = DesktopScaledX(GadgetWidth(*PBMap\Gadget)) / 2 *Drawing\RadiusY = DesktopScaledY(GadgetHeight(*PBMap\Gadget)) / 2 *Drawing\GeographicCoordinates\Latitude = *PBMap\GeographicCoordinates\Latitude *Drawing\GeographicCoordinates\Longitude = *PBMap\GeographicCoordinates\Longitude LatLon2TileXY(*Drawing\GeographicCoordinates, *Drawing\TileCoordinates, *PBMap\Zoom) LatLon2Pixel(MapGadget, *Drawing\GeographicCoordinates, @PixelCenter, *PBMap\Zoom) ; Pixel shift, aka position in the tile Px = *Drawing\TileCoordinates\x Py = *Drawing\TileCoordinates\y *Drawing\DeltaX = Px * ts - (Int(Px) * ts) ; Don't forget the Int() ! *Drawing\DeltaY = Py * ts - (Int(Py) * ts) ; Drawing boundaries nx = *Drawing\RadiusX / ts ; How many tiles around the point ny = *Drawing\RadiusY / ts NW\x = Px - nx - 1 NW\y = Py - ny - 1 SE\x = Px + nx + 2 SE\y = Py + ny + 2 TileXY2LatLon(@NW, *Drawing\Bounds\NorthWest, *PBMap\Zoom) TileXY2LatLon(@SE, *Drawing\Bounds\SouthEast, *PBMap\Zoom) ; *Drawing\Width = (SE\x / Pow(2, *PBMap\Zoom) * 360.0) - (NW\x / Pow(2, *PBMap\Zoom) * 360.0) ; Calculus without clipping ; *Drawing\Height = *Drawing\Bounds\NorthWest\Latitude - *Drawing\Bounds\SouthEast\Latitude ; *** ; Main drawing stuff StartVectorDrawing(CanvasVectorOutput(*PBMap\Gadget)) ; Clearscreen VectorSourceColor(RGBA(150, 150, 150, 255)) FillVectorOutput() ; TODO add in layers of tiles ; this way we can cache them as 0 base 1.n layers ; such as for openseamap tiles which are overlaid. not that efficent from here though. ; Draws layers based on their number ForEach *PBMap\LayersList() If *PBMap\LayersList()\Enabled DrawTiles(MapGadget, *Drawing, *PBMap\LayersList()\Name) EndIf If *PBMap\LayersList()\LayerType = 0 ; OSM OSMCopyright = #True EndIf Next If *PBMap\Options\ShowTrack DrawTracks(MapGadget, *Drawing) EndIf If *PBMap\Options\ShowMarkers DrawMarkers(MapGadget, *Drawing) EndIf If *PBMap\Options\ShowDegrees And *PBMap\Zoom > 2 DrawDegrees(MapGadget, *Drawing, 192) EndIf If *PBMap\Options\ShowPointer DrawPointer(MapGadget, *Drawing) EndIf If *PBMap\Options\ShowDebugInfos DrawDebugInfos(MapGadget, *Drawing) EndIf If *PBMap\Options\ShowScale DrawScale(MapGadget, *Drawing, 10, DesktopScaledY(GadgetHeight(*PBMap\Gadget)) - 20, 192) EndIf If *PBMap\Options\ShowZoom DrawZoom(MapGadget, DesktopScaledX(GadgetWidth(*PBMap\Gadget)) - 30, 5) ; ajout YA - affiche le niveau de zoom EndIf If OSMCopyright DrawOSMCopyright(MapGadget, *Drawing) EndIf StopVectorDrawing() EndProcedure Procedure Refresh(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Redraw = #True ; Drawing() EndProcedure ;-*** Misc functions Procedure.d GetCanvasPixelLon(MapGadget.i, x) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected MouseX.d = (*PBMap\PixelCoordinates\x - *PBMap\Drawing\RadiusX + x) / *PBMap\TileSize Protected n.d = Pow(2.0, *PBMap\Zoom) ; double mod is to ensure the longitude to be in the range [-180; 180[ ProcedureReturn Mod(Mod(MouseX / n * 360.0, 360.0) + 360.0, 360.0) - 180 EndProcedure Procedure.d GetCanvasPixelLat(MapGadget.i, y) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected MouseY.d = (*PBMap\PixelCoordinates\y - *PBMap\Drawing\RadiusY + y) / *PBMap\TileSize Protected n.d = Pow(2.0, *PBMap\Zoom) ProcedureReturn Degree(ATan(SinH(#PI * (1.0 - 2.0 * MouseY / n)))) EndProcedure Procedure.d GetMouseLongitude(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn GetCanvasPixelLon(MapGadget.i, GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_MouseX)) EndProcedure Procedure.d GetMouseLatitude(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn GetCanvasPixelLat(MapGadget.i, GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_MouseY)) EndProcedure Procedure SetLocation(MapGadget.i, latitude.d, longitude.d, Zoom = -1, Mode.i = #PB_Absolute) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Select Mode Case #PB_Absolute *PBMap\GeographicCoordinates\Latitude = latitude *PBMap\GeographicCoordinates\Longitude = longitude If Zoom <> -1 *PBMap\Zoom = Zoom EndIf Case #PB_Relative *PBMap\GeographicCoordinates\Latitude + latitude *PBMap\GeographicCoordinates\Longitude + longitude If Zoom <> -1 *PBMap\Zoom + Zoom EndIf EndSelect *PBMap\GeographicCoordinates\Longitude = ClipLongitude(*PBMap\GeographicCoordinates\Longitude) If *PBMap\GeographicCoordinates\Latitude < -89 *PBMap\GeographicCoordinates\Latitude = -89 EndIf If *PBMap\GeographicCoordinates\Latitude > 89 *PBMap\GeographicCoordinates\Latitude = 89 EndIf If *PBMap\Zoom > *PBMap\ZoomMax : *PBMap\Zoom = *PBMap\ZoomMax : EndIf If *PBMap\Zoom < *PBMap\ZoomMin : *PBMap\Zoom = *PBMap\ZoomMin : EndIf LatLon2TileXY(@*PBMap\GeographicCoordinates, @*PBMap\Drawing\TileCoordinates, *PBMap\Zoom) ; Convert X, Y in tile.decimal into real pixels *PBMap\PixelCoordinates\x = *PBMap\Drawing\TileCoordinates\x * *PBMap\TileSize *PBMap\PixelCoordinates\y = *PBMap\Drawing\TileCoordinates\y * *PBMap\TileSize *PBMap\Redraw = #True If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndProcedure Procedure SetZoomToArea(MapGadget.i, MinY.d, MaxY.d, MinX.d, MaxX.d) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ; Source => http://gis.stackexchange.com/questions/19632/how-to-calculate-the-optimal-zoom-level-to-display-two-or-more-points-on-a-map ; bounding box in long/lat coords (x=long, y=lat) Protected DeltaX.d = MaxX - MinX ; assumption ! In original code DeltaX have no source Protected centerX.d = MinX + DeltaX / 2 ; assumption ! In original code CenterX have no source Protected paddingFactor.f= 1.2 ; paddingFactor: this can be used to get the "120%" effect ThomM refers to. Value of 1.2 would get you the 120%. Protected ry1.d = Log((Sin(Radian(MinY)) + 1) / Cos(Radian(MinY))) Protected ry2.d = Log((Sin(Radian(MaxY)) + 1) / Cos(Radian(MaxY))) Protected ryc.d = (ry1 + ry2) / 2 Protected centerY.d = Degree(ATan(SinH(ryc))) Protected resolutionHorizontal.d = DeltaX / (*PBMap\Drawing\RadiusX * 2) Protected vy0.d = Log(Tan(#PI*(0.25 + centerY/360))); Protected vy1.d = Log(Tan(#PI*(0.25 + MaxY/360))) ; Protected viewHeightHalf.d = *PBMap\Drawing\RadiusY ; Protected zoomFactorPowered.d = viewHeightHalf / (40.7436654315252*(vy1 - vy0)) Protected resolutionVertical.d = 360.0 / (zoomFactorPowered * *PBMap\TileSize) If resolutionHorizontal<>0 And resolutionVertical<>0 Protected resolution.d = Max(resolutionHorizontal, resolutionVertical)* paddingFactor Protected zoom.d = Log(360 / (resolution * *PBMap\TileSize))/Log(2) Protected lon.d = centerX; Protected lat.d = centerY; SetLocation(MapGadget, lat, lon, Round(zoom,#PB_Round_Down)) Else SetLocation(MapGadget, *PBMap\GeographicCoordinates\Latitude, *PBMap\GeographicCoordinates\Longitude, 15) EndIf EndProcedure Procedure SetZoomToTracks(MapGadget.i, *Tracks.Tracks) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected MinY.d, MaxY.d, MinX.d, MaxX.d If ListSize(*Tracks\Track()) > 0 With *Tracks\Track() FirstElement(*Tracks\Track()) MinX = \Longitude : MaxX = MinX : MinY = \Latitude : MaxY = MinY ForEach *Tracks\Track() If \Longitude < MinX MinX = \Longitude EndIf If \Longitude > MaxX MaxX = \Longitude EndIf If \Latitude < MinY MinY = \Latitude EndIf If \Latitude > MaxY MaxY = \Latitude EndIf Next SetZoomToArea(MapGadget, MinY.d, MaxY.d, MinX.d, MaxX.d) EndWith EndIf EndProcedure Procedure SetZoom(MapGadget.i, Zoom.i, mode.i = #PB_Relative) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Select mode Case #PB_Relative *PBMap\Zoom = *PBMap\Zoom + zoom Case #PB_Absolute *PBMap\Zoom = zoom EndSelect If *PBMap\Zoom > *PBMap\ZoomMax : *PBMap\Zoom = *PBMap\ZoomMax : ProcedureReturn : EndIf If *PBMap\Zoom < *PBMap\ZoomMin : *PBMap\Zoom = *PBMap\ZoomMin : ProcedureReturn : EndIf LatLon2TileXY(@*PBMap\GeographicCoordinates, @*PBMap\Drawing\TileCoordinates, *PBMap\Zoom) ; Convert X, Y in tile.decimal into real pixels *PBMap\PixelCoordinates\X = *PBMap\Drawing\TileCoordinates\x * *PBMap\TileSize *PBMap\PixelCoordinates\Y = *PBMap\Drawing\TileCoordinates\y * *PBMap\TileSize ; First drawing *PBMap\Redraw = #True If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndProcedure Procedure SetAngle(MapGadget.i, Angle.d, Mode = #PB_Absolute) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If Mode = #PB_Absolute *PBMap\Angle = Angle Else *PBMap\Angle + Angle *PBMap\Angle = Mod(*PBMap\Angle,360) EndIf *PBMap\Redraw = #True EndProcedure ;-*** Callbacks Procedure SetCallBackLocation(MapGadget.i, CallBackLocation.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackLocation = CallBackLocation EndProcedure Procedure SetCallBackMainPointer(MapGadget.i, CallBackMainPointer.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackMainPointer = CallBackMainPointer EndProcedure Procedure SetCallBackMarker(MapGadget.i, CallBackLocation.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackMarker = CallBackLocation EndProcedure Procedure SetCallBackLeftClic(MapGadget.i, CallBackLocation.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackLeftClic = CallBackLocation EndProcedure Procedure SetCallBackDrawTile(MapGadget.i, CallBackLocation.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackDrawTile = CallBackLocation EndProcedure Procedure SetCallBackModifyTileFile(MapGadget.i, CallBackLocation.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\CallBackModifyTileFile = CallBackLocation EndProcedure ;*** Procedure SetMapScaleUnit(MapGadget.i, ScaleUnit.i = PBMAP::#SCALE_KM) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Options\ScaleUnit = ScaleUnit *PBMap\Redraw = #True ; Drawing() EndProcedure ; User mode ; #MODE_DEFAULT = 0 -> "Hand" (move map) and move objects ; #MODE_HAND = 1 -> Hand only ; #MODE_SELECT = 2 -> Move objects only ; #MODE_EDIT = 3 -> Create objects Procedure SetMode(MapGadget.i, Mode.i = #MODE_DEFAULT) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\Mode = Mode EndProcedure Procedure.i GetMode(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\Mode EndProcedure ; Zoom on x, y pixel position from the center Procedure SetZoomOnPixel(MapGadget.i, x, y, zoom) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ; *** First : Zoom *PBMap\Zoom + zoom If *PBMap\Zoom > *PBMap\ZoomMax : *PBMap\Zoom = *PBMap\ZoomMax : ProcedureReturn : EndIf If *PBMap\Zoom < *PBMap\ZoomMin : *PBMap\Zoom = *PBMap\ZoomMin : ProcedureReturn : EndIf LatLon2Pixel(MapGadget, @*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) If Zoom = 1 *PBMap\PixelCoordinates\x + x *PBMap\PixelCoordinates\y + y ElseIf zoom = -1 *PBMap\PixelCoordinates\x - x/2 *PBMap\PixelCoordinates\y - y/2 EndIf Pixel2LatLon(MapGadget, @*PBMap\PixelCoordinates, @*PBMap\GeographicCoordinates, *PBMap\Zoom) ; Start drawing *PBMap\Redraw = #True ; If CallBackLocation send Location To function If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndProcedure ; Zoom on x, y position relative to the canvas gadget Procedure SetZoomOnPixelRel(MapGadget.i, x, y, zoom) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) SetZoomOnPixel(MapGadget, x - *PBMap\Drawing\RadiusX, y - *PBMap\Drawing\RadiusY, zoom) EndProcedure ; Go to x, y position relative to the canvas gadget left up Procedure GotoPixelRel(MapGadget.i, x, y) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) LatLon2Pixel(MapGadget, @*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) *PBMap\PixelCoordinates\x + x - *PBMap\Drawing\RadiusX *PBMap\PixelCoordinates\y + y - *PBMap\Drawing\RadiusY Pixel2LatLon(MapGadget, @*PBMap\PixelCoordinates, @*PBMap\GeographicCoordinates, *PBMap\Zoom) ; Start drawing *PBMap\Redraw = #True ; If CallBackLocation send Location to function If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndProcedure ; Go to x, y position relative to the canvas gadget Procedure GotoPixel(MapGadget.i, x, y) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) *PBMap\PixelCoordinates\x = x *PBMap\PixelCoordinates\y = y Pixel2LatLon(MapGadget, @*PBMap\PixelCoordinates, @*PBMap\GeographicCoordinates, *PBMap\Zoom) ; Start drawing *PBMap\Redraw = #True ; If CallBackLocation send Location to function If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndProcedure Procedure.d GetLatitude(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\GeographicCoordinates\Latitude EndProcedure Procedure.d GetLongitude(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\GeographicCoordinates\Longitude EndProcedure Procedure.i GetZoom(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\Zoom EndProcedure Procedure.d GetAngle(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) ProcedureReturn *PBMap\Angle EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Requete Nominatim securisee ; OBJECTIF : Geocoder une adresse sans ecriture disque ; POURQUOI : Eviter les fichiers temporaires et traiter les erreurs reseau ; COMMENT : ReceiveHTTPMemory() + ParseJSON() + FreeMemory() ; --------------------------------------------------------------------------- Procedure NominatimGeoLocationQuery(MapGadget.i, Address.s, *ReturnPosition.GeographicCoordinates = 0) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) Protected Query.s = "https://nominatim.openstreetmap.org/search" + "?q=" + URLEncoder(Address) + "&format=json&addressdetails=0&polygon=0&limit=1" If *PBMap\Options\Proxy HTTPProxy(*PBMap\Options\ProxyURL + ":" + *PBMap\Options\ProxyPort, *PBMap\Options\ProxyUser, *PBMap\Options\ProxyPassword) EndIf Protected *Buffer = ReceiveHTTPMemory(Query, 0, #USERAGENT) If *Buffer = 0 MyDebug(*PBMap, "Nominatim query failed: empty response", 1) ProcedureReturn EndIf Protected JsonText.s = PeekS(*Buffer, MemorySize(*Buffer), #PB_UTF8 | #PB_ByteLength) FreeMemory(*Buffer) Protected JsonId.i = ParseJSON(#PB_Any, JsonText) If JsonId = 0 ; Demivec's code MyDebug(*PBMap, JSONErrorMessage() + " at position " + JSONErrorPosition() + " in line " + JSONErrorLine() + " of JSON web Data", 1) ProcedureReturn ElseIf JSONArraySize(JSONValue(JsonId)) > 0 Protected object_val = GetJSONElement(JSONValue(JsonId), 0) Protected object_box = GetJSONMember(object_val, "boundingbox") Protected bbox.BoundingBox bbox\SouthEast\Latitude = ValD(GetJSONString(GetJSONElement(object_box, 0))) bbox\NorthWest\Latitude = ValD(GetJSONString(GetJSONElement(object_box, 1))) bbox\NorthWest\Longitude = ValD(GetJSONString(GetJSONElement(object_box, 2))) bbox\SouthEast\Longitude = ValD(GetJSONString(GetJSONElement(object_box, 3))) Protected lat.s = GetJSONString(GetJSONMember(object_val, "lat")) Protected lon.s = GetJSONString(GetJSONMember(object_val, "lon")) If *ReturnPosition <> 0 *ReturnPosition\Latitude = ValD(lat) *ReturnPosition\Longitude = ValD(lon) EndIf If lat <> "" And lon <> "" SetZoomToArea(MapGadget, bbox\SouthEast\Latitude, bbox\NorthWest\Latitude, bbox\NorthWest\Longitude, bbox\SouthEast\Longitude) ; SetLocation(Position\Latitude, Position\Longitude) EndIf EndIf FreeJSON(JsonId) EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Nettoyage de cache securise ; OBJECTIF : Supprimer le cache sans risque de supprimer une racine ; POURQUOI : DeleteDirectory() est destructif ; COMMENT : Validation du chemin avant suppression recursive ; --------------------------------------------------------------------------- Procedure.i ClearDiskCache(MapGadget.i) Protected *PBMap.PBMap = PBMaps(Str(MapGadget)) If IsSafeCachePath(*PBMap\Options\HDDCachePath) = #False Error(MapGadget, "Unsafe cache path, aborting: " + *PBMap\Options\HDDCachePath) ProcedureReturn #False EndIf If *PBMap\Options\Warning Protected Result.i = MessageRequester("Warning", "You will clear all cache content in " + *PBMap\Options\HDDCachePath + ". Are you sure ?",#PB_MessageRequester_YesNo) If Result = #PB_MessageRequester_No ; Quit if "no" selected ProcedureReturn #False EndIf EndIf If DeleteDirectory(*PBMap\Options\HDDCachePath, "", #PB_FileSystem_Recursive) MyDebug(*PBMap, "Cache in : " + *PBMap\Options\HDDCachePath + " cleared", 3) CreateDirectoryEx(*PBMap\Options\HDDCachePath) ProcedureReturn #True Else MyDebug(*PBMap, "Can't clear cache in " + *PBMap\Options\HDDCachePath, 3) ProcedureReturn #False EndIf EndProcedure ;-*** Main PBMap functions Procedure CanvasEvents() Protected CanvasMouseX.d, CanvasMouseY.d, MouseX.d, MouseY.d Protected MarkerCoords.PixelCoordinates, *Tile.Tile, MapWidth Protected key.s, Touch.i Protected Pixel.PixelCoordinates Protected ImgNB.i, TileNewFilename.s Protected *Cluster.Cluster Protected MouseScreenX.d, MouseScreenY.d Protected FocusChanged.b Protected NewZoom.i Static CtrlKey Protected Location.GeographicCoordinates Protected MapGadget.i = EventGadget() Protected *PBMap.PBmap = PBMaps(Str(MapGadget)) MapWidth = Pow(2, *PBMap\Zoom) * *PBMap\TileSize CanvasMouseX = GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_MouseX) - *PBMap\Drawing\RadiusX CanvasMouseY = GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_MouseY) - *PBMap\Drawing\RadiusY ; rotation wip ; StartVectorDrawing(CanvasVectorOutput(*PBMap\Gadget)) ; RotateCoordinates(0, 0, *PBMap\Angle) ; CanvasMouseX = ConvertCoordinateX(MouseX, MouseY, #PB_Coordinate_Device, #PB_Coordinate_User) ; CanvasMouseY = ConvertCoordinateY(MouseX, MouseY, #PB_Coordinate_Device, #PB_Coordinate_User) ; StopVectorDrawing() Select EventType() Case #PB_EventType_Focus *PBMap\Drawing\RadiusX = DesktopScaledX(GadgetWidth(*PBMap\Gadget)) / 2 *PBMap\Drawing\RadiusY = DesktopScaledY(GadgetHeight(*PBMap\Gadget)) / 2 Case #PB_EventType_KeyUp Select GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Key) Case #PB_Shortcut_Delete DeleteSelectedMarkers(MapGadget) DeleteSelectedTracks(MapGadget) EndSelect *PBMap\Redraw = #True If GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Modifiers)&#PB_Canvas_Control = 0 CtrlKey = #False EndIf Case #PB_EventType_KeyDown With *PBMap\Markers() Select GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Key) Case #PB_Shortcut_Left ForEach *PBMap\Markers() If \Selected \GeographicCoordinates\Longitude = ClipLongitude( \GeographicCoordinates\Longitude - 10* 360 / Pow(2, *PBMap\Zoom + 8)) EndIf Next Case #PB_Shortcut_Up ForEach *PBMap\Markers() If \Selected \GeographicCoordinates\Latitude + 10* 360 / Pow(2, *PBMap\Zoom + 8) EndIf Next Case #PB_Shortcut_Right ForEach *PBMap\Markers() If \Selected \GeographicCoordinates\Longitude = ClipLongitude( \GeographicCoordinates\Longitude + 10* 360 / Pow(2, *PBMap\Zoom + 8)) EndIf Next Case #PB_Shortcut_Down ForEach *PBMap\Markers() If \Selected \GeographicCoordinates\Latitude - 10* 360 / Pow(2, *PBMap\Zoom + 8) EndIf Next EndSelect EndWith *PBMap\Redraw = #True If GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Modifiers)&#PB_Canvas_Control <> 0 CtrlKey = #True EndIf Case #PB_EventType_LeftDoubleClick If *PBMap\Options\EnableClusters *Cluster = FindClusterAt(MapGadget, CanvasMouseX + *PBMap\Drawing\RadiusX, CanvasMouseY + *PBMap\Drawing\RadiusY) If *Cluster And *Cluster\Count >= *PBMap\Options\ClusterMinCount NewZoom = Min(*PBMap\Zoom + 1, *PBMap\ZoomMax) SetLocation(MapGadget, *Cluster\CenterLat, *Cluster\CenterLon, NewZoom) *PBMap\Redraw = #True ProcedureReturn EndIf EndIf LatLon2Pixel(MapGadget, @*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) MouseX = *PBMap\PixelCoordinates\x + CanvasMouseX MouseY = *PBMap\PixelCoordinates\y + CanvasMouseY ; Clip MouseX to the map range (in X, the map is infinite) MouseX = Mod(Mod(MouseX, MapWidth) + MapWidth, MapWidth) Touch = #False ; Check if the mouse touch a marker ForEach *PBMap\Markers() If *PBMap\Options\EnableClusters And IsMarkerClustered(MapGadget, @*PBMap\Markers()) ; Skip markers hidden by a cluster Else LatLon2Pixel(MapGadget, @*PBMap\Markers()\GeographicCoordinates, @MarkerCoords, *PBMap\Zoom) If Distance(MarkerCoords\x, MarkerCoords\y, MouseX, MouseY) < 8 If *PBMap\Mode = #MODE_DEFAULT Or *PBMap\Mode = #MODE_SELECT ; Jump to the marker Touch = #True SetLocation(MapGadget, *PBMap\Markers()\GeographicCoordinates\Latitude, *PBMap\Markers()\GeographicCoordinates\Longitude) ElseIf *PBMap\Mode = #MODE_EDIT ; Edit the legend MarkerEdit(MapGadget, @*PBMap\Markers()) EndIf Break EndIf EndIf Next If Not Touch GotoPixel(MapGadget, MouseX, MouseY) EndIf Case #PB_EventType_MouseWheel If *PBMap\Options\WheelMouseRelative ; Relative zoom (centered on the mouse) SetZoomOnPixel(MapGadget, CanvasMouseX, CanvasMouseY, GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_WheelDelta)) Else ; Absolute zoom (centered on the center of the map) SetZoom(MapGadget, GetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_WheelDelta), #PB_Relative) EndIf ; Case #PB_EventType_RightClick ; Debug GetMouseLongitude(MapGadget) ; Debug GetMouseLatitude(MapGadget) ; Debug GetCanvasPixelLon(MapGadget, CanvasMouseX + *PBMap\Drawing\RadiusX) ; Debug GetCanvasPixelLat(MapGadget, CanvasMouseY + *PBMap\Drawing\RadiusY) Case #PB_EventType_LeftButtonDown ; LatLon2Pixel(@*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) *PBMap\Dragging = #True ; Memorize cursor Coord *PBMap\MoveStartingPoint\x = CanvasMouseX *PBMap\MoveStartingPoint\y = CanvasMouseY ; Clip MouseX to the map range (in X, the map is infinite) *PBMap\MoveStartingPoint\x = Mod(Mod(*PBMap\MoveStartingPoint\x, MapWidth) + MapWidth, MapWidth) If *PBMap\Mode = #MODE_DEFAULT Or *PBMap\Mode = #MODE_SELECT *PBMap\EditMarker = #False ; Check if we select marker(s) ForEach *PBMap\Markers() If CtrlKey = #False *PBMap\Markers()\Selected = #False ; If no CTRL key, deselect everything and select only the focused marker EndIf If *PBMap\Markers()\Focus *PBMap\Markers()\Selected = #True *PBMap\EditMarker = #True; ListIndex(*PBMap\Markers()) *PBMap\Markers()\Focus = #False EndIf Next ; Check if we select track(s) ForEach *PBMap\TracksList() If CtrlKey = #False *PBMap\TracksList()\Selected = #False ; If no CTRL key, deselect everything and select only the focused track EndIf If *PBMap\TracksList()\Focus *PBMap\TracksList()\Selected = #True *PBMap\TracksList()\Focus = #False EndIf Next EndIf ; YA To select a track with LMB If *PBMap\EditMarker = #False Location\Latitude = GetMouseLatitude(MapGadget) Location\Longitude = GetMouseLongitude(MapGadget) If *PBMap\CallBackLeftClic > 0 CallFunctionFast(*PBMap\CallBackLeftClic, @Location) EndIf ; ajout YA // Mouse pointer when moving map SetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Cursor, #PB_Cursor_Hand) Else SetGadgetAttribute(*PBMap\Gadget, #PB_Canvas_Cursor, #PB_Cursor_Default) ; ajout YA pour remettre le pointeur souris en normal EndIf Case #PB_EventType_MouseMove ; Drag If *PBMap\Dragging ; If *PBMap\MoveStartingPoint\x <> - 1 MouseX = CanvasMouseX - *PBMap\MoveStartingPoint\x MouseY = CanvasMouseY - *PBMap\MoveStartingPoint\y *PBMap\MoveStartingPoint\x = CanvasMouseX *PBMap\MoveStartingPoint\y = CanvasMouseY ; Move selected markers If *PBMap\EditMarker And (*PBMap\Mode = #MODE_DEFAULT Or *PBMap\Mode = #MODE_SELECT) ForEach *PBMap\Markers() If *PBMap\Markers()\Selected LatLon2Pixel(MapGadget, @*PBMap\Markers()\GeographicCoordinates, @MarkerCoords, *PBMap\Zoom) MarkerCoords\x + MouseX MarkerCoords\y + MouseY Pixel2LatLon(MapGadget, @MarkerCoords, @*PBMap\Markers()\GeographicCoordinates, *PBMap\Zoom) EndIf Next ElseIf *PBMap\Mode = #MODE_DEFAULT Or *PBMap\Mode = #MODE_HAND ; Move map only LatLon2Pixel(MapGadget, @*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) ; This line could be removed as the coordinates don't have to change but I want to be sure we rely only on geographic coordinates *PBMap\PixelCoordinates\x - MouseX ; Ensures that pixel position stay in the range [0..2^Zoom**PBMap\TileSize[ coz of the wrapping of the map *PBMap\PixelCoordinates\x = Mod(Mod(*PBMap\PixelCoordinates\x, MapWidth) + MapWidth, MapWidth) *PBMap\PixelCoordinates\y - MouseY Pixel2LatLon(MapGadget, @*PBMap\PixelCoordinates, @*PBMap\GeographicCoordinates, *PBMap\Zoom) ; If CallBackLocation send Location to function If *PBMap\CallBackLocation > 0 CallFunctionFast(*PBMap\CallBackLocation, @*PBMap\GeographicCoordinates) EndIf EndIf *PBMap\Redraw = #True Else ; Touch test LatLon2Pixel(MapGadget, @*PBMap\GeographicCoordinates, @*PBMap\PixelCoordinates, *PBMap\Zoom) MouseX = *PBMap\PixelCoordinates\x + CanvasMouseX MouseY = *PBMap\PixelCoordinates\y + CanvasMouseY ; Clip MouseX to the map range (in X, the map is infinite) MouseX = Mod(Mod(MouseX, MapWidth) + MapWidth, MapWidth) If *PBMap\Mode = #MODE_DEFAULT Or *PBMap\Mode = #MODE_SELECT Or *PBMap\Mode = #MODE_EDIT ; Check if mouse touch markers or clusters If *PBMap\Options\EnableClusters MouseScreenX = CanvasMouseX + *PBMap\Drawing\RadiusX MouseScreenY = CanvasMouseY + *PBMap\Drawing\RadiusY *Cluster = FindClusterAt(MapGadget, MouseScreenX, MouseScreenY) FocusChanged = #False ForEach *PBMap\Markers() If *PBMap\Markers()\Focus *PBMap\Markers()\Focus = #False FocusChanged = #True EndIf Next ForEach *PBMap\Clusters() If *PBMap\Clusters()\Focus *PBMap\Clusters()\Focus = #False FocusChanged = #True EndIf Next If FocusChanged *PBMap\Redraw = #True EndIf If *Cluster If *Cluster\Count >= *PBMap\Options\ClusterMinCount *Cluster\Focus = #True *PBMap\Redraw = #True ElseIf *Cluster\Marker ChangeCurrentElement(*PBMap\Markers(), *Cluster\Marker) *PBMap\Markers()\Focus = #True *PBMap\Redraw = #True EndIf EndIf Else ForEach *PBMap\Markers() LatLon2Pixel(MapGadget, @*PBMap\Markers()\GeographicCoordinates, @MarkerCoords, *PBMap\Zoom) If Distance(MarkerCoords\x, MarkerCoords\y, MouseX, MouseY) < 8 *PBMap\Markers()\Focus = #True *PBMap\Redraw = #True ElseIf *PBMap\Markers()\Focus ; If CtrlKey = #False *PBMap\Markers()\Focus = #False *PBMap\Redraw = #True EndIf Next EndIf ; Check if mouse touch tracks If *PBMap\Options\ShowTrackSelection ; YA to avoid selecting track With *PBMap\TracksList() ; Trace Track If ListSize(*PBMap\TracksList()) > 0 ForEach *PBMap\TracksList() If ListSize(\Track()) > 0 If \Visible StartVectorDrawing(CanvasVectorOutput(*PBMap\Gadget)) ; Simulates track drawing ForEach \Track() LatLon2Pixel(MapGadget, @*PBMap\TracksList()\Track(), @Pixel, *PBMap\Zoom) If ListIndex(\Track()) = 0 MovePathCursor(Pixel\x, Pixel\y) Else AddPathLine(Pixel\x, Pixel\y) EndIf Next If IsInsideStroke(MouseX, MouseY, \StrokeWidth) \Focus = #True *PBMap\Redraw = #True ElseIf \Focus \Focus = #False *PBMap\Redraw = #True EndIf StopVectorDrawing() EndIf EndIf Next EndIf EndWith EndIf EndIf EndIf Case #PB_EventType_LeftButtonUp SetGadgetAttribute(*PBMap\Gadget,#PB_Canvas_Cursor,#PB_Cursor_Default) ; YA normal mouse pointer ; *PBMap\MoveStartingPoint\x = - 1 *PBMap\Dragging = #False *PBMap\Redraw = #True ;YA to knows marker coordinates after moving ForEach *PBMap\Markers() If *PBMap\Markers()\Selected = #True If *PBMap\CallBackMarker > 0 CallFunctionFast(*PBMap\CallBackMarker, @*PBMap\Markers()); EndIf EndIf Next Case #PB_MAP_REDRAW *PBMap\Redraw = #True Case #PB_MAP_RETRY *PBMap\Redraw = #True ;- *** Tile web loading thread cleanup ; After a Web tile loading thread, cleans the tile structure memory, see GetImageThread() Case #PB_MAP_TILE_CLEANUP LockMutex(*PBMap\MemoryCacheAccessMutex) ; Prevents threads to start or finish *Tile = EventData() key = *Tile\key *Tile\Download = 0 If FindMapElement(*PBMap\MemCache\Images(), key) <> 0 ; If the map element has not been deleted during the thread lifetime (should not occur) ;*PBMap\MemCache\Images(key)\Tile = *Tile\Size If *Tile\Size ;TODO : check if file size = server file size ;and eventually use pngcheck to avoid problematic files http://www.libpng.org/pub/png/apps/pngcheck.html *PBMap\MemCache\Images(key)\Tile = -1 ; Web loading thread has finished successfully ; Allows to post edit the tile image file with a customised code If *PBMap\CallBackModifyTileFile TileNewFilename = *PBMap\CallBackModifyTileFile(*Tile\CacheFile, *Tile\URL) If TileNewFilename ;TODO : Not used by now, a new filename is sent back *Tile\CacheFile = TileNewFilename EndIf EndIf Else *PBMap\MemCache\Images(key)\Tile = 0 If DeleteFile(*Tile\CacheFile) MyDebug(*PBMap, " Deleting not fully loaded image file " + *Tile\CacheFile, 3) Else MyDebug(*PBMap, " Can't delete not fully loaded image file " + *Tile\CacheFile, 3) EndIf EndIf EndIf FreeMemory(*Tile) ; Frees the data needed for the thread (*tile=*PBMap\MemCache\Images(key)\Tile) *PBMap\ThreadsNB - 1 *PBMap\DownloadSlots - 1 *PBMap\Redraw = #True UnlockMutex(*PBMap\MemoryCacheAccessMutex) EndSelect EndProcedure ;-*** Main timer : Cache management and drawing Procedure TimerEvents() Protected *PBMap.PBMap ForEach PBMaps() *PBMap = PBMaps() If EventTimer() = *PBMap\Timer And (*PBMap\Redraw Or *PBMap\Dirty) MemoryCacheManagement(*PBMap\Gadget) Drawing(*PBMap\Gadget) EndIf Next EndProcedure ; Could be called directly to attach our map to an existing canvas ; --------------------------------------------------------------------------- ; SECTION : Initialisation du gadget ; OBJECTIF : Initialiser un PBMap et ses ressources critiques ; POURQUOI : Garantir des valeurs par defaut coherentes ; COMMENT : Mutex, options, images techniques et timers ; --------------------------------------------------------------------------- Procedure BindMapGadget(MapGadget.i, TimerNB = 1, Window = -1) Protected *PBMap.PBMap *PBMap.PBMap = AllocateStructure(PBMap) If *PBMap = 0 FatalError(MapGadget, "Cannot initialize PBMap memory") EndIf If Window = -1 Window = GetActiveWindow() EndIf PBMaps(Str(MapGadget)) = *PBMap With *PBMap Protected Result.i \ZoomMin = 1 \ZoomMax = 18 \Dragging = #False \TileSize = 256 \Dirty = #False \EditMarker = #False \StandardFont = LoadFont(#PB_Any, "Arial", 20, #PB_Font_Bold) \UnderlineFont = LoadFont(#PB_Any, "Arial", 20, #PB_Font_Underline) \Window = Window \Timer = TimerNB \Mode = #MODE_DEFAULT \MemoryCacheAccessMutex = CreateMutex() If \MemoryCacheAccessMutex = #False MyDebug(*PBMap, "Cannot create a mutex", 0) End EndIf \StopDownloads = #False EndWith LoadOptions(MapGadget) TechnicalImagesCreation(MapGadget) SetLocation(MapGadget, 0, 0) *PBMap\Gadget = MapGadget BindGadgetEvent(*PBMap\Gadget, @CanvasEvents()) AddWindowTimer(*PBMap\Window, *PBMap\Timer, *PBMap\Options\TimerInterval) BindEvent(#PB_Event_Timer, @TimerEvents()) *PBMap\Drawing\RadiusX = DesktopScaledX(GadgetWidth(*PBMap\Gadget)) / 2 *PBMap\Drawing\RadiusY = DesktopScaledY(GadgetHeight(*PBMap\Gadget)) / 2 EndProcedure ; Creates a canvas and attach our map Procedure MapGadget(MapGadget.i, X.i, Y.i, Width.i, Height.i, TimerNB = 1, Window.i = -1) If Window = -1 Window = GetActiveWindow() EndIf If MapGadget = #PB_Any Protected GadgetNB.i GadgetNB = CanvasGadget(#PB_Any, X, Y, Width, Height, #PB_Canvas_Keyboard) ; #PB_Canvas_Keyboard has to be set for mousewheel to work on windows BindMapGadget(GadgetNB, TimerNB, Window) ProcedureReturn GadgetNB Else If CanvasGadget(MapGadget, X, Y, Width, Height, #PB_Canvas_Keyboard) BindMapGadget(MapGadget, TimerNB, Window) Else FatalError(MapGadget, "Cannot create the map gadget") EndIf EndIf EndProcedure ; --------------------------------------------------------------------------- ; SECTION : Arret propre des threads ; OBJECTIF : Fermer proprement sans KillThread() ; POURQUOI : Eviter les corruptions et fuites de ressources ; COMMENT : Stop flag + AbortHTTP() + attente des threads ; --------------------------------------------------------------------------- Procedure Quit(*PBMap.PBMap) *PBMap\Drawing\End = #True *PBMap\StopDownloads = #True ; Wait for loading threads to finish nicely. AbortHTTP is used to stop downloads. Protected TimeCounter = ElapsedMilliseconds() Protected Warned.b = #False Repeat ForEach *PBMap\MemCache\Images() If *PBMap\MemCache\Images()\Tile > 0 If IsThread(*PBMap\MemCache\Images()\Tile\GetImageThread) If *PBMap\MemCache\Images()\Tile\Download AbortHTTP(*PBMap\MemCache\Images()\Tile\Download) EndIf If ElapsedMilliseconds() - TimeCounter > 20000 And Warned = #False MyDebug(*PBMap, "Waiting for download threads to finish...", 1) Warned = #True EndIf Else FreeMemory(*PBMap\MemCache\Images()\Tile) *PBMap\MemCache\Images()\Tile = 0 EndIf Else DeleteMapElement(*PBMap\MemCache\Images()) EndIf Next Delay(10) Until MapSize(*PBMap\MemCache\Images()) = 0 RemoveWindowTimer(*PBMap\Window, *PBMap\Timer) UnbindGadgetEvent(*PBMap\Gadget, @CanvasEvents()) If *PBMap\MemoryCacheAccessMutex FreeMutex(*PBMap\MemoryCacheAccessMutex) *PBMap\MemoryCacheAccessMutex = 0 EndIf FreeStructure(*PBMap) EndProcedure Procedure FreeMapGadget(MapGadget.i) Protected *PBMap.PBMap ForEach PBMaps() *PBMap = PBMaps() If *PBMap\Gadget = MapGadget Quit(*PBMap) DeleteMapElement(PBMaps()) EndIf Next EndProcedure EndModule ; IDE Options = PureBasic 6.12 LTS (Windows - x86) ; CursorPosition = 484 ; FirstLine = 468 ; Folding = --------------------- ; EnableThread ; EnableXP