python-visualization / folium

Python Data. Leaflet.js Maps.
https://python-visualization.github.io/folium/
MIT License
6.94k stars 2.23k forks source link

Realtime plugin: load local GeoJson file #1909

Closed aniforatos closed 7 months ago

aniforatos commented 8 months ago

I want to use the Realtime plugin to load a geojson file that is local to my computer. The only examples I see are those that deal with URLs or a local jscript function.

I would like an option to include a geojson file path to monitor.

Describe alternatives you've considered I have tried a custom javascript function that fetches the .geojson file locally but I cant seem to get that to work. My map just doesn't display.

I want to do something like this:

from folium import JsCode
m = folium.Map(location=[40.73, -73.94], zoom_start=12)
rt = folium.plugins.Realtime(
    "local.geojson",
    get_feature_id=JsCode("(f) => { return f.properties.objectid; }"),
    interval=10000,
)
rt.add_to(m)
m
aniforatos commented 8 months ago

So in the meantime, there is a work-around I have. It isn't pretty but it works. I basically use python to create a local HTTP server and I set some headers that make the folium plugin happy. Run this with your geojson file in the same directory:

from http.server import HTTPServer, BaseHTTPRequestHandler

class MapHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        f = open("./test.geojson", "rb").read()
        # Set the referrer policy header
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.send_header('Referrer-Policy', 'no-referrer')  # Change 'no-referrer' to your desired policy
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(f)

def run_server(server_class=HTTPServer, handler_class=MapHTTPRequestHandler, port=8000):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print(f'Starting server on port {port}...')
    httpd.serve_forever()

if __name__ == '__main__':
    run_server()

Then in another terminal run this script:


import folium
from folium.plugins import Realtime
from folium import JsCode

m = folium.Map(location=[40.73, -73.94], zoom_start=12)
file = "http://localhost:8000/tracks.geojson"

rt = Realtime(
    file,
    interval=10000,
    get_feature_id=JsCode("(f) => { return f.properties.identifier }"),
    point_to_layer=JsCode("(f, latlng) => { return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2})}")
)
rt.add_to(m)
m.show_in_browser()
Conengmo commented 7 months ago

I see your point, and thanks for sharing that work-around.

In the GeoJson class we deal with this more flexibly by allowing urls, local files, or geojson like objects such as dicts. Perhaps we could adopt some of that logic in Realtime as well. I'm thinking of using something like GeoJson.process_data(). But I don't want repeated code, and I don't want to grow the code of Realtime that much, since that will become a maintenance burden itself.

aniforatos commented 7 months ago

Thanks for responding! I am curious to know what path you end up going with this implementation. This realtime plugin is extremely useful to me and what I am currently doing for my work.

I dont have any good input as to what the best way to accomplish this is, but I will be watching this thread!

Conengmo commented 7 months ago

Thanks for responding! I am curious to know what path you end up going with this implementation.

Sorry for the confusion, I'm not planning on working on this, but wanted to share some implementation ideas if somebody comes along who wants to work on this.

This realtime plugin is extremely useful to me and what I am currently doing for my work.

That's nice to hear!

hansthen commented 7 months ago

A (slightly) less hacky way would be to read the data inside your python code. Something like this:

file_data_json = open(filename).read().decode("utf-8")

source = JsCode("""
function(responseHandler, errorHandler) {
    const data = async () => { return """ + file_data_json + """};

    data()
    .then(responseHandler)
    .catch(errorHandler);
}
""") 

Out of curiosity, what is the reason you have to use Realtime? It seems using a GeoJson layer for local file data would be easier.

aniforatos commented 7 months ago

Thanks @hansthen ! However, looking at it, it seems like this file gets read in once, keeping my markers static. So I dont think this is accomplishing the same as my original hack. That geojson file will be updated constantly.

So I am basically monitoring an SQL database that contains information on aircraft positions. I am creating tracks out of these utilizing my own classes and then constantly converting them to be written to a geojson file for the Realtime plugin to capture and update from.

I am not sure of a better way to accomplish this. My entire project is in python so I want to utilize folium, but it doesnt let me show these tracks updating in real-time, until I found this Realtime plugin that was added a couple of months back.

hansthen commented 7 months ago

Yes, I was afraid of that. There is no way for the leaflet-realtime plugin to poll local files, since the browser does not allow it.

Your chosen approach is currently best (to start an HTTP server that serves the required files). Although, you may want to swapping the simple HTTP server to an application server that generates the geojson on the fly. You could try something simple like Flask or FastAPI.

hansthen commented 7 months ago

Since it will not be possible to poll local files from inside the Realtime layer and since for non-polling behavior the GeoJson layer can be used, I am closing this issue.