python-visualization / folium

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

Possibility to show all points from geojson file with the realtime plugin. #1929

Closed shyney7 closed 7 months ago

shyney7 commented 7 months ago

Is your feature request related to a problem? Please describe. If I use the Realtime Plugin to show new datapoints as they are added to my geojson file, only the last point is shown. foliumRT

This is the geojson file: https://gist.github.com/shyney7/ac65a2fbecf72dcd730387bc1b851ac0 New points are added at the end.

Describe the solution you'd like I would like to be able to show all datapoints included inside the geojson current ones and future ones not only the last one.

Additional context

import csv
import json
import folium
from folium import JsCode
from folium.plugins import Realtime
import time
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
import threading
import atexit

# Read CSV file
#df = pd.read_csv(r'./gpslive/gpstobfilt.csv')
initloc = {}
with open(r'./gpslive/gpstobfilt.csv', 'r') as csvfile:
    reader = csv.DictReader(csvfile)
    initloc = next(reader)

# Initialize folium map centered at the first coordinates
m = folium.Map(location=[float(initloc['latitude']), float(initloc['longitude'])], zoom_start=13)
folium.TileLayer(
    tiles='https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
    attr='OpenStreetMap.DE',
    name='OpenStreetMap.DE',
).add_to(m)

source = "http://localhost:8877/data.geojson"

Realtime(
    source,
    get_feature_id=JsCode("(f) => { return f.properties.id}"),
    point_to_layer=JsCode("""
        (f, latlng) => {
            var color = 'red';
            if (f.properties.total_counts < 5) {
                color = 'green';
            } else if (f.properties.total_counts < 10) {
                color = 'yellow';
            }
            return L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2, color: color});
        }
        """),
    interval=1000,
).add_to(m)

m.save(r'./gpslive/realtime.html')

def create_geojson():
    with open(r'./gpslive/gpstobfilt.csv', 'r') as csvfile:
        reader = csv.DictReader(csvfile)

        # init an empty list to store GeoJSON features
        features = []

        # loop through each row in the CSV
        for row in reader:
            # create a GeoJSON feature
            feature = {
                "type": "Feature",
                "geometry": {
                    "type": "Point",
                    "coordinates": [float(row['longitude']), float(row['latitude'])]
                },
                "properties": {
                    "total_counts": float(row['total_counts']),
                    "altitude": float(row['altitude'])
                }
            }

            # add the feature to the list
            features.append(feature)

            # write the features to a new GeoJSON file
            with open(r'./gpslive/data.geojson', 'w') as geojsonfile:
                json.dump({'type': 'FeatureCollection', 'features': features}, geojsonfile)

            #wait for 1 second
            time.sleep(1)

class MapHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        f = open(r'./gpslive/data.geojson', 'rb').read()
        #Set the referrer policy header
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Referrer-Policy', 'no-referrer')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(f)

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

# run both functions in separate threads
t_geo = threading.Thread(target=create_geojson)
#tserver = threading.Thread(target=run_server)

t_geo.start()
#tserver.start()

# stop the server when the main thread exits
atexit.register(t_geo.join)
#atexit.register(tserver.join)
run_server()
hansthen commented 7 months ago

You need to add an id property to the geojson's features (or use a JsCode object to generate an id). Perhaps your input csv contains a column that provides a unique key for each row?

shyney7 commented 7 months ago

@hansthen thank you! I've changed the create_geojson() function to this:

def create_geojson():
    with open(r'./gpslive/gpstobfilt.csv', 'r') as csvfile:
        reader = csv.DictReader(csvfile)

        # init an empty list to store GeoJSON features
        features = []

        # loop through each row in the CSV
        for index, row in enumerate(reader):
            # create a GeoJSON feature
            feature = {
                "type": "Feature",
                "geometry": {
                    "type": "Point",
                    "coordinates": [float(row['longitude']), float(row['latitude'])]
                },
                "properties": {
                    "id": index,
                    "total_counts": float(row['total_counts']),
                    "altitude": float(row['altitude'])
                }
            }

            # add the feature to the list
            features.append(feature)

            # write the features to a new GeoJSON file
            with open(r'./gpslive/data.geojson', 'w') as geojsonfile:
                json.dump({'type': 'FeatureCollection', 'features': features}, geojsonfile)

            #wait for 1 second
            time.sleep(1)

That fixed only the last point to be shown. And I also changed the Realtime class initiation to this:

Realtime(
    source,
    get_feature_id=JsCode("(f) => { return f.properties.id}"),
    point_to_layer=JsCode("""
        (f, latlng) => {
            var color = 'red';
            if (f.properties.total_counts < 5) {
                color = 'green';
            } else if (f.properties.total_counts < 10) {
                color = 'yellow';
            }
            var marker = L.circleMarker(latlng, {radius: 8, fillOpacity: 0.2, color: color});
            marker.bindPopup(
                'ID: ' + f.properties.id + '<br>' +
                'Total Counts: ' + f.properties.total_counts + '<br>' +
                'Altitude: ' + f.properties.altitude
            );
            return marker;
        }
        """),
    interval=1000,
).add_to(m)

To be able to see the porperties data when I click on a Point.