B4J Code Snippet [PyBridge] Using folium (OpenStreetMap) in a webview

As requested in another thread here is how to setup a B4XPages app to use PyBridge and Folium in a webview.

Download latest beta version of B4J with PyBridge.

Make sure PyBridge is selected in the Libraries Manager Tab

Add folium to Python (pip install folium in a command prompt window for ypur Python, or use the Open local Python shell line below if python not installed)

B4X Code (B4XMainPage)
B4X:
#CustomBuildAction: after packager, %WINDIR%\System32\robocopy.exe, Python temp\build\bin\python /E /XD __pycache__ Doc pip setuptools tests

'Export as zip: ide://run?File=%B4X%\Zipper.jar&Args=Project.zip
'Create a local Python runtime:   ide://run?File=%WINDIR%\System32\Robocopy.exe&args=%B4X%\libraries\Python&args=Python&args=/E
'Open local Python shell: ide://run?File=%PROJECT%\Objects\Python\WinPython+Command+Prompt.exe
'Open global Python shell - make sure to set the path under Tools - Configure Paths. Do not update the internal package.
'ide://run?File=%B4J_PYTHON%\..\WinPython+Command+Prompt.exe

Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Public Py As PyBridge
    Private WebView1 As WebView
    Private Button1 As Button
End Sub

Public Sub Initialize
   
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    Py.Initialize(Me, "Py")
    Dim opt As PyOptions = Py.CreateOptions("d:/python.3.12.8/python.exe") ' change to where your python is if required
    Py.Start(opt)
    Wait For Py_Connected (Success As Boolean)
    If Success = False Then
        LogError("Failed to start Python process.")
        Return
    End If
End Sub

Sub Button1_Click
    folium_test
End Sub

Private Sub B4XPage_Background
    Py.KillProcess
End Sub

Private Sub Py_Disconnected
    Log("PyBridge disconnected")
End Sub

Sub folium_test
    ' I read from dir assets but the code could be a string in-line.
    ' change the Array to contain a string of where you want the map to open eg "London, UK" from "Toronto, CA"
    wait for ((Py.RunCode("start",Array("Toronto, CA"),File.ReadString(File.DirAssets,"folium_test.py")).Fetch)) Complete (res As PyWrapper)
    ' load the produced html file into a webview (it is fully functional web page and will open in  browser too if you want)
    WebView1.LoadHtml(File.ReadString("../objects",res.value))
End Sub

The Python code (either in-line or in a file)
Note in the following code the line with 'an email address' - the lookup requires an email address
B4X:
import folium
import requests
def get_lat_lon(place_name):
    url = f"https://nominatim.openstreetmap.org/search?q={place_name}&format=json"
    headers = {
        'User-Agent': 'AddressLookup/1.0 (an email address)'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        try:
            data = response.json()
            if data:
                lat = data[0]['lat']
                lon = data[0]['lon']
                return lat, lon
            else:
                print("No data found for the specified place name.")
                return None
        except ValueError:
            print("Invalid JSON response.")
            return None
    else:
        print(f"Error: {response.status_code}")
        return
def start(place):
    lat,lon = get_lat_lon(place)
    # Create a map centered around Lat/Lon
    map_london = folium.Map(location=[lat,lon], zoom_start=12)
    # Add a marker for Home
    folium.Marker(
        location=[lat,lon],
        popup="Marker",
        icon=folium.Icon(icon="Marker")).add_to(map_london)
   
    # Save the map to an HTML file -- change the name to what you want, it will be saved in /Objects folder
    map_london.save("london_map.html")
    # return the name of the file to B4J
    return "london_map.html"
 
Last edited:

Daestrum

Expert
Licensed User
Longtime User
Also the address you search for ie 'London, UK' can be street level too. 'Oxford Street, London, UK' is valid too.
 
Last edited:

Daestrum

Expert
Licensed User
Longtime User
Ok slight improvement, to allow multiple markers to be added. (I left my debug comments in the Python code)

NOTE: make opt a Class_Globals variable for this to work.

B4X Code
B4X:
Sub folium_test
    'IMPORTANT (took hours to track down map_london was Nulled between calls)
    ' must have this as persistent variable between calls
    opt.EnvironmentVars.put("map_london",Null)

    wait for ((Py.RunCode("start", Array("Oxford Street, London, UK"), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)

    wait for (add_new_marker("Royal Observatory, Greenwich, UK"))  Complete (success As Boolean)
    wait for (add_new_marker("Nelson Road, Greenwich, UK"))  Complete (success As Boolean)
  
    wait for ((Py.RunCode("save_map", Array(), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)
    If res.Value <> Null Then
        WebView1.LoadHtml(File.ReadString("../objects", res.Value))
    End If
End Sub

Sub add_new_marker(place_name As String)As ResumableSub
    wait for ((Py.RunCode("get_lat_lon", Array(place_name), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)
    Dim latlon(2) As Object = res.Value
    Log($"Position: ${latlon(0)},${latlon(1)}"$)
    wait for ((Py.RunCode("add_marker", Array(latlon(0).As(Double), latlon(1).As(Double)), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)
    Return True
End Sub

Python Code

Python:
import folium
import requests

# Initialize the global map variable
#map_london = None

def get_lat_lon(place_name):
    global map_london
    url = f"https://nominatim.openstreetmap.org/search?q={place_name}&format=json"
    headers = {
        'User-Agent': 'AddressLookup/1.0 (an email address)'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        try:
            data = response.json()
            if data:
                lat = data[0]['lat']
                lon = data[0]['lon']
                return lat, lon
            else:
                print("No data found for the specified place name.")
                return None, None
        except ValueError:
            print("Invalid JSON response.")
            return None, None
    else:
        print(f"Error: {response.status_code}")
        return None, None

def start(place):
    global map_london
    lat, lon = get_lat_lon(place)
    if lat is not None and lon is not None:
        map_london = folium.Map(location=[lat, lon], zoom_start=12)
        print(f"Map created successfully: {map_london}")
    else:
        print("Failed to create map. Invalid latitude/longitude.")
    return

def add_marker(lat, lon):
    global map_london
    #print(f"State of map before adding marker: {map_london}")
    if map_london is not None:
        folium.Marker(
            location=[lat, lon],
            popup="Marker",
            icon=folium.Icon(icon="home")).add_to(map_london)
        #print("Marker added successfully.")
    else:
        print("Error: Map object is None. Cannot add marker.")
    #print(f"State of map after adding marker: {map_london}")
    return

def save_map():
    global map_london
    #print(f"State of map before saving: {map_london}")
    if map_london is not None:
        map_london.save("london_map.html")
        print("Map saved successfully.")
        return "london_map.html"
    else:
        print("Error: Map object is None. Cannot save map.")
        return None
 
Last edited:

javiers

Active Member
Licensed User
Longtime User
Hello, I appreciate the effort you are making. I can't install folium (I don't know how to do it, really). After putting pip install folium in the python window I get this error


install folium ...:
C:\Python\Notebooks>pip install folium
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Python\python\Scripts\pip.exe\__main__.py", line 7, in <module>
  File "C:\Python\python\Lib\site-packages\pip\_internal\cli\main.py", line 78, in main
    command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python\python\Lib\site-packages\pip\_internal\commands\__init__.py", line 114, in create_command
    module = importlib.import_module(module_path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python\python\Lib\importlib\__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 999, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\Python\python\Lib\site-packages\pip\_internal\commands\install.py", line 16, in <module>
    from pip._internal.cli.req_command import (
  File "C:\Python\python\Lib\site-packages\pip\_internal\cli\req_command.py", line 18, in <module>
    from pip._internal.index.collector import LinkCollector
  File "C:\Python\python\Lib\site-packages\pip\_internal\index\collector.py", line 31, in <module>
    from pip._vendor import requests
  File "C:\Python\python\Lib\site-packages\pip\_vendor\requests\__init__.py", line 159, in <module>
    from .api import delete, get, head, options, patch, post, put, request
  File "C:\Python\python\Lib\site-packages\pip\_vendor\requests\api.py", line 11, in <module>
    from . import sessions
  File "C:\Python\python\Lib\site-packages\pip\_vendor\requests\sessions.py", line 15, in <module>
    from .adapters import HTTPAdapter
  File "C:\Python\python\Lib\site-packages\pip\_vendor\requests\adapters.py", line 80, in <module>
    _preloaded_ssl_context = create_urllib3_context()
                             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python\python\Lib\site-packages\pip\_vendor\urllib3\util\ssl_.py", line 359, in create_urllib3_context
    context.keylog_filename = sslkeylogfile
    ^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\Javier\\AppData\\Local\\Netfixs\\Netfixs.log'

C:\Python\Notebooks>


and in the log...

log:
Waiting for debugger to connect...
Program started.
Server is listening on port: 53996
Python path: C:\Python\python\python.exe
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
Error occurred on line: 194 (PyBridge)
java.lang.RuntimeException: Class instance was not initialized (b4xset)
    at anywheresoftware.b4a.debug.Debug.shouldDelegate(Debug.java:45)
    at b4j.example.b4xset._contains(b4xset.java:110)
    at b4j.example.pybridge._registermember(pybridge.java:943)
    at b4j.example.pybridge._runcode(pybridge.java:361)
    at b4j.example.b4xmainpage$ResumableSub_folium_test.resume(b4xmainpage.java:192)
    at b4j.example.b4xmainpage._folium_test(b4xmainpage.java:166)
    at b4j.example.b4xmainpage._button1_click(b4xmainpage.java:156)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:629)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:100)
    at anywheresoftware.b4a.BA$1.run(BA.java:236)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
    at java.base/java.lang.Thread.run(Thread.java:1589)
 

Daestrum

Expert
Licensed User
Longtime User
I will try using the internal python (I have python istalled so I just normally use that)

Ok it installed folium ok.

1, Click on the Create a local python runtime (should have lots of messages in the log window in B4J(PyBridge beta #3)
you should get something like this at the end

------------------------------------------------------------------------------
Total Copied Skipped Mismatch FAILED Extras
Dirs : 245 245 0 0 0 0
Files : 3179 3179 0 0 0 0
Bytes : 56.27 m 56.27 m 0 0 0 0
Times : 0:00:04 0:00:03 0:00:00 0:00:00
Speed : 15,177,555 Bytes/sec.
Speed : 868.467 MegaBytes/min.
Ended : 02 March 2025 16:57:54
Completed. Exit code: 1
Completed. Exit code: 0

2, click on the Open local Python shell

3, type pip install folium - hit enter (should produce lots of lines)
 
Last edited:

javiers

Active Member
Licensed User
Longtime User
Hi again, I would like to replicate the polyline function, but I can't get the list of points... In folium

folium:
m = folium.Map(location=[-71.38, -73.9], zoom_start=11)

trail_coordinates = [
    (-71.351871840295871, -73.655963711222626),
    (-71.374144382613707, -73.719861619751498),
    (-71.391042575973145, -73.784922248007007),
    (-71.400964450973134, -73.851042243124397),
    (-71.402411391077322, -74.050048183880477),
]

folium.PolyLine(trail_coordinates, tooltip="Coast").add_to(m)

m


folium_test.py:
def polyline(puntos):
    global map_london
    #print(f"State of map before adding marker: {map_london}")
    if map_london is not None:
        folium.PolyLine(puntos, tooltip="Coast").add_to(map_london)
        #print("Marker added successfully.")
    else:
        print("Error: Map object is None. Cannot add marker.")
    #print(f"State of map after adding marker: {map_london}")
    return

B4X:
    Private Sub Button2_Click
    Dim punto As MyType       'Type MyType (lat As Double, lon As Double)
    punto.Initialize
    Dim trail_coordinates As List
    trail_coordinates.Initialize   
    
    punto.lat=-71.351871840295871
    punto.lon=-73.655963711222626
    trail_coordinates.Add(punto)
    punto.lat=-71.374144382613707
    punto.lon=-73.719861619751498
    trail_coordinates.Add(punto)
    punto.lat=-71.391042575973145
    punto.lon=-73.784922248007007
    trail_coordinates.Add(punto)
        wait for ((Py.RunCode("polyline", Array(trail_coordinates), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)
    wait for ((Py.RunCode("save_map", Array(), File.ReadString(File.DirAssets, "folium_test.py")).Fetch)) Complete (res As PyWrapper)
    
    
    
    If res.Value <> Null Then
        WebView1.LoadHtml(File.ReadString("../objects", res.Value))
    End If
End Sub

Error...

Error...:
Waiting for debugger to connect...
Program started.
Server is listening on port: 52087
Python path: C:\Python\python\python.exe
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
connected
starting PyBridge v0.50
watchdog set to 30 seconds
Connecting to port: 52087
Map created successfully: <folium.folium.Map object at 0x00000242D8A216A0>
Position: 51.47736225,-0.000845593094199643
Position: 51.48097895416076,-0.008688515600210398
Map saved successfully.
Position: 43.2630018,-2.9350039
Map saved successfully.
Unhandled exception in task: KeyError('mytype')
{'message': 'Exception in callback task_done_callback(<Task cancell...anager.py:28>>) at C:\\Users\\Javier\\AppData\\Roaming\\pybridge\\b4x_bridge_0.50.zip\\b4x_bridge\\bridge.py:205', 'exception': CancelledError(), 'handle': <Handle task_done_callback(<Task cancell...anager.py:28>>) at C:\Users\Javier\AppData\Roaming\pybridge\b4x_bridge_0.50.zip\b4x_bridge\bridge.py:205>}
{'message': 'Exception in callback task_done_callback(<Task cancell...anager.py:54>>) at C:\\Users\\Javier\\AppData\\Roaming\\pybridge\\b4x_bridge_0.50.zip\\b4x_bridge\\bridge.py:205', 'exception': CancelledError(), 'handle': <Handle task_done_callback(<Task cancell...anager.py:54>>) at C:\Users\Javier\AppData\Roaming\pybridge\b4x_bridge_0.50.zip\b4x_bridge\bridge.py:205>}
java.net.SocketException: Connection reset
    at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:313)
    at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:340)
    at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:789)
    at java.base/java.net.Socket$SocketInputStream.read(Socket.java:1025)
    at anywheresoftware.b4a.randomaccessfile.AsyncStreams$AIN.readNumberOfBytes(AsyncStreams.java:330)
    at anywheresoftware.b4a.randomaccessfile.AsyncStreams$AIN.run(AsyncStreams.java:254)
    at java.base/java.lang.Thread.run(Thread.java:1589)
disconnected
Process completed. ExitCode: 0
PyBridge disconnected
 

Daestrum

Expert
Licensed User
Longtime User
ok using your coords I got the line to show.
B4X:
Sub add_polyline()As ResumableSub
    Dim coords As List
    
    coords.Initialize
    
    coords.Add(Array(-71.351871840295871,-73.655963711222626))
    
    coords.Add(Array(-71.374144382613707,-73.719861619751498))
    
    coords.Add(Array(-71.391042575973145,-73.784922248007007))
    
    coords.Add(Array(-71.400964450973134,-73.851042243124397))
    
    coords.add(Array(-71.402411391077322,-74.050048183880477))
    
    wait for ((Py.RunCode("add_polyline",Array(coords),File.ReadString(File.DirAssets, "folium_test.py"))).fetch) Complete (res As PyWrapper)
    Return True
End Sub

Python code
B4X:
def add_polyline(points):
    global map_london
    if map_london is not None:
        folium.PolyLine(
        locations=points,
        color="#FF0000",
        weight=3,
        tooltip="Coast",
        ).add_to(map_london)

You must add the polyline before you save the map.
 

javiers

Active Member
Licensed User
Longtime User
ok using your coords I got the line to show.
B4X:
Sub add_polyline()As ResumableSub
    Dim coords As List
   
    coords.Initialize
   
    coords.Add(Array(-71.351871840295871,-73.655963711222626))
   
    coords.Add(Array(-71.374144382613707,-73.719861619751498))
   
    coords.Add(Array(-71.391042575973145,-73.784922248007007))
   
    coords.Add(Array(-71.400964450973134,-73.851042243124397))
   
    coords.add(Array(-71.402411391077322,-74.050048183880477))
   
    wait for ((Py.RunCode("add_polyline",Array(coords),File.ReadString(File.DirAssets, "folium_test.py"))).fetch) Complete (res As PyWrapper)
    Return True
End Sub

Python code
B4X:
def add_polyline(points):
    global map_london
    if map_london is not None:
        folium.PolyLine(
        locations=points,
        color="#FF0000",
        weight=3,
        tooltip="Coast",
        ).add_to(map_london)

You must add the polyline before you save the map.
Thanks for responding so quickly!
I'll keep testing... circles, polygons,...
You're a great help!
 

javiers

Active Member
Licensed User
Longtime User
Again me...I can't get any other map other than OpenStreetMap.
I would like to be able to have satellite images. For reference see https://leaflet-extras.github.io/leaflet-providers/preview/

I'm sorry to bother you so much, but I've done a lot of tests and I can't do it...

I don't know if it would be better to open a new thread for each question, but doing it here is very similar to a tutorial!
Thanks in advance!!

folium_test.py:
def save_map():
    global map_london
    #print(f"State of map before saving: {map_london}")
    if map_london is not None:
        #folium.Map(tiles='https://{s}.tiles.example.com/{z}/{x}/{y}.png', attr='My Data Attribution') 
        #folium.TileLayer('MapQuest Open Aerial').add_to(map_london)       
        folium.Map(tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Mapbox attribution')       
        map_london.save("london_map.html")
        print("Map saved successfully.")

        return "london_map.html"
    else:
        print("Error: Map object is None. Cannot save map.")
        return None
 

Daestrum

Expert
Licensed User
Longtime User
I changed the code a bit (the attr is wrong - I just copied some other one to test - you will need to make sure it applies to the tile maker).

Note: This is in where it creates the map, not in the routine to save it.

Python code:
def start2(place):
    global map_london
    print("in start2")
    attr = (
    '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> '
    'contributors, &copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
    )
    lat , lon = get_lat_lon(place)
    tiles = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png"
    map_london = folium.Map(location=[lat, lon], tiles=tiles, attr=attr, zoom_start=12)
    return save_map()
 

Daestrum

Expert
Licensed User
Longtime User
Just for your reference this is the current python script I have


Python code:
import folium
import requests
import time

# Initialize the global map variable
#map_london = None

def get_lat_lon(place_name):
    global map_london
    url = f"https://nominatim.openstreetmap.org/search?q={place_name}&format=json"
    headers = {
        'User-Agent': 'AddressLookup/1.0 (emai-address)'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        try:
            data = response.json()
            if data:
                lat = data[0]['lat']
                lon = data[0]['lon']
                return lat, lon
            else:
                print("No data found for the specified place name.")
                return None, None
        except ValueError:
            print("Invalid JSON response.")
            return None, None
    else:
        print(f"Error: {response.status_code}")
        return None, None

def start(place):
    global map_london
    lat, lon = get_lat_lon(place)
    if lat is not None and lon is not None:
        map_london = folium.Map(location=[lat, lon], zoom_start=12)
        #print(f"Map created successfully: {map_london}")
    else:
        print("Failed to create map. Invalid latitude/longitude.")
    return

def add_marker(name, lat, lon):
    global map_london
    #print(f"State of map before adding marker: {map_london}")
    if map_london is not None:
        folium.Marker(
            location=[lat, lon],
            popup=name,
            icon=folium.Icon(icon="Home")).add_to(map_london)
        #print("Marker added successfully.")
    else:
        print("Error: Map object is None. Cannot add marker.")
    #print(f"State of map after adding marker: {map_london}")
    return

def save_map():
    global map_london
    #print(f"State of map before saving: {map_london}")
    if map_london is not None:
        map_london.save("london_map.html")
        #print("Map saved successfully.")
        return "london_map.html"
    else:
        print("Error: Map object is None. Cannot save map.")
        return None

def add_polyline(points):
    global map_london
    if map_london is not None:
        folium.PolyLine(
        locations=points,
        color="#FF0000",
        weight=3,
        tooltip="Coast",
        ).add_to(map_london)

def start2(place):
    global map_london
    print("in start2")
    attr = (
    '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> '
    'contributors, &copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
    )
    lat , lon = get_lat_lon(place)
    tiles = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png"
    map_london = folium.Map(location=[lat, lon], tiles=tiles, attr=attr, zoom_start=12)
    return save_map()
 
Last edited:

Daestrum

Expert
Licensed User
Longtime User
I will try the overlays next where a map can have sattelite etc, over the top.
 

Ralph Parkhurst

Member
Licensed User
Longtime User
I am also following this (as I am sure many others are), and am learning heaps by trying each of your code examples. It certainly highlights the exciting world ahead with PyBridge, whilst also giving me exposure to Folium and OSM.

Thanks to Javiers for asking good questions and Daestrum for great clarity in your answers :).
 

javiers

Active Member
Licensed User
Longtime User
It's not much, but a small contribution, legend with mouse coordinates and drawing tools. For this you need to import the MousePosition and Draw plugins. in the header of the python file...

Python:
import folium
import requests
import time
from folium.plugins import MousePosition
from folium.plugins import Draw

and when defining the map.
Python:
    MousePosition().add_to(map_london)
    Draw(export=True).add_to(map_london)

folium_test.py:
import folium
import requests
import time
from folium.plugins import MousePosition
from folium.plugins import Draw
# Initialize the global map variable
#map_london = None

def get_lat_lon(place_name):
    global map_london
    url = f"https://nominatim.openstreetmap.org/search?q={place_name}&format=json"
    headers = {
        'User-Agent': 'AddressLookup/1.0 (spomple@gmail.com)'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        try:
            data = response.json()
            if data:
                lat = data[0]['lat']
                lon = data[0]['lon']
                return lat, lon
            else:
                print("No data found for the specified place name.")
                return None, None
        except ValueError:
            print("Invalid JSON response.")
            return None, None
    else:
        print(f"Error: {response.status_code}")
        return None, None

def start(place):
    global map_london
    lat, lon = get_lat_lon(place)
    if lat is not None and lon is not None:
    map_london = folium.Map(location=[lat, lon], zoom_start=12)
    MousePosition().add_to(map_london)
    Draw(export=True).add_to(map_london)
        #print(f"Map created successfully: {map_london}")
    else:
        print("Failed to create map. Invalid latitude/longitude.")
    return

def add_marker(name, lat, lon):
    global map_london
    #print(f"State of map before adding marker: {map_london}")
    if map_london is not None:
        folium.Marker(
            location=[lat, lon],
            popup=name,
            icon=folium.Icon(icon="Home")).add_to(map_london)
        #print("Marker added successfully.")
    else:
        print("Error: Map object is None. Cannot add marker.")
    #print(f"State of map after adding marker: {map_london}")
    return

def save_map():
    global map_london
    #print(f"State of map before saving: {map_london}")
    if map_london is not None:
        map_london.save("london_map.html")
        #print("Map saved successfully.")
        return "london_map.html"
    else:
        print("Error: Map object is None. Cannot save map.")
        return None

def add_polyline(points):
    global map_london
    if map_london is not None:
        folium.PolyLine(
        locations=points,
        color="#FF0000",
        weight=3,
        tooltip="Coast",
        ).add_to(map_london)

def start2(place):
    global map_london
    print("in start2")
    attr = (
    '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> '
    'contributors, &copy; <a href="https://cartodb.com/attributions">CartoDB</a>'
    )
    lat , lon = get_lat_lon(place)
    tiles = "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png"
    map_london = folium.Map(location=[lat, lon], tiles=tiles, attr=attr, zoom_start=12)
    #map_london = folium.Map((45.5236, -122.6750), tiles="cartodb positron")
    MousePosition().add_to(map_london)
    Draw(export=True).add_to(map_london)
    return save_map()
   
def add_circle(radio, lat, lon):
    global map_london  
    if map_london is not None:
        radius = radio
        folium.Circle(
        location=[lat, lon],
        radius=radius,
        color="black",
        weight=1,
        fill_opacity=0.6,
        opacity=1,
        fill_color="green",
        fill=False,  # gets overridden by fill_color
        popup="{} meters".format(radius),
        tooltip="I am in meters",
        ).add_to(map_london)
    else:
        print("Error: Map object is None. Cannot add marker.")
    #print(f"State of map after adding marker: {map_london}")
        return
 

Attachments

  • PruebaPython.zip
    5.4 KB · Views: 17
  • test.jpg
    test.jpg
    36 KB · Views: 20
Last edited:

javiers

Active Member
Licensed User
Longtime User
The interesting thing, at least for me (I have no idea how to do it) would be to use the Draw tools to draw lines, polygons, etc., but be able to save the geometry in a database. But do it every time you draw an element.
 

Daestrum

Expert
Licensed User
Longtime User
this is what I came up with for layered maps

layered maps:
def layered_map(place):
    global map_london
    lat, lon = get_lat_lon(place)
    # Create a base map
    map_london = folium.Map(location=[lat, lon], zoom_start=12, tiles='OpenStreetMap', name='OSM')

    # Add ESRI Satellite layer
    folium.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
                 name='ESRI Satellite', attr='ESRI').add_to(map_london)

       
    folium.TileLayer('mapquestopen', attr='MapQuest Open').add_to(map_london)

    folium.TileLayer("MapQuest Open Aerial",attr="MapQuest Open Aerial").add_to(map_london)

    # Add layer control to the map
    folium.LayerControl().add_to(map_london)

    # return the map as an HTML string.
    if hasattr(map_london, '_repr_html_'):
        return map_london._repr_html_()
    else:
        return None

Notice the end part of the code, it removes the need to save the map as a real file and simply passes the html back as a string to go into the webview, just remember to check for null on the returned value.

B4X:
Sub layered_map(place As String) As ResumableSub
    wait for ((Py.RunCode("layered_map",Array(place),File.ReadString(File.DirAssets, "folium_test.py"))).fetch) Complete (res As PyWrapper)
    If res.Value <> Null Then
        WebView1.LoadHtml(res.Value)
    Else
        Log("res value was null")
    End If
    Return True
End Sub
 
Last edited:

Ralph Parkhurst

Member
Licensed User
Longtime User
If I understand correctly, the save_map() function is called each time a change is made, ensuring updates (such as adding a new marker) are reflected.

However, calling save_map() also resets the map to its initial location and zoom level. This isn't an issue if the user hasn’t interacted with the map, but if they have—by panning or zooming, for example—resetting the view can be disruptive.

My question: Is there a way to save the map without resetting its location and zoom level?
 
Last edited:
Top