emilhe / dash-leaflet

MIT License
204 stars 33 forks source link

Unexpected Polyline Color Behavior: Polyline Color Mismatch #226

Open JohnBioinf opened 5 months ago

JohnBioinf commented 5 months ago

I have discovered a bug when I was trying to use the dash-leaflet library. I have tried to build a simple example but this is smallest example I could come up with.

The idea of the dash board is to display a map with some markers of trains (green) and cars (red). The user can select an area on the map and the markers inside the area will be displayed.

The problem is that after some callback (not all) that returns a list of dl.Polyline objects, some of the lines can have wrong colors. I added a print statement to the get_stations, which prints the dl.Polyline objects before returning it. The print output is as expected, but the color of the lines on the map are different.

Here is a little video that shows the problem: https://youtu.be/8crKNfDrTq0

from dash import html, Input, Output, Dash
import dash_leaflet as dl
from dash.exceptions import PreventUpdate

app = Dash(prevent_initial_callbacks=True)

car1_markers = [
    [48.20768696144312, 16.385829448699955],
    [48.20698625228953, 16.387889385223392],
    [48.20687184988469, 16.388854980468754],
    [48.20687184988469, 16.390099525451664],
    [48.20648573988175, 16.391365528106693],
    [48.20609962696796, 16.392545700073246],
    [48.205727811781394, 16.393747329711918],
    [48.205613406565554, 16.394541263580326],
    [48.20584221674167, 16.396365165710453],
    [48.20592802029419, 16.3976526260376],
    [48.20621403109773, 16.400077342987064],
    [48.206428538152274, 16.401751041412357],
]

car2_markers = [
    [48.2059995231448, 16.38246059417725],
    [48.20831616149326, 16.38396263122559],
    [48.21010361946129, 16.38490676879883],
    [48.211104568672305, 16.38490676879883],
    [48.21171942777283, 16.38623714447022],
    [48.212605955804925, 16.38690233230591],
    [48.21302061687404, 16.389348506927494],
    [48.21284903339063, 16.391065120697025],
    [48.212062601735006, 16.3931679725647],
    [48.2104039062789, 16.39471292495728],
    [48.20838766100986, 16.39589309692383],
    [48.2059995231448, 16.396365165710453],
]

train_markers = [
    [48.207665511305166, 16.38644099235535],
    [48.20827326173542, 16.387256383895878],
    [48.21068274246144, 16.390389204025272],
    [48.21179807200897, 16.391880512237552],
    [48.21306351265506, 16.39248132705689],
    [48.214235983421254, 16.39267444610596],
    [48.21559430013005, 16.39222383499146],
]

db_mock = [
    {
        "name": "car1",
        "markers": car1_markers,
        "station_type": 2,
    },
    {
        "name": "car2",
        "markers": car2_markers,
        "station_type": 2,
    },
    {
        "name": "train",
        "markers": train_markers,
        "station_type": 3,
    },
]

def is_inside_bounds(marker, lat_min, lat_max, lng_min, lng_max):
    lat, lng = marker
    return lat_min <= lat <= lat_max and lng_min <= lng <= lng_max

def construct_marker(station):
    if station["station_type"] == 2:
        return dl.Polyline(positions=station["markers"], color="red", weight=10)
    if station["station_type"] == 3:
        return dl.Polyline(positions=station["markers"], color="green", weight=10)

def get_stations(area_geojson):
    """Get the data."""
    try:
        bounds = area_geojson["features"][0]["properties"]["_bounds"]
        lat_min = min(bounds[0]["lat"], bounds[1]["lat"])
        lat_max = max(bounds[0]["lat"], bounds[1]["lat"])
        lng_min = min(bounds[0]["lng"], bounds[1]["lng"])
        lng_max = max(bounds[0]["lng"], bounds[1]["lng"])
    except IndexError:
        bounds = None
    markers = []
    for station in db_mock:
        if (
            bounds is None
            # check if any marker is inside the bounds
            or any(
                [
                    is_inside_bounds(marker, lat_min, lat_max, lng_min, lng_max)
                    for marker in station["markers"]
                ]
            )
        ):
            markers.append(construct_marker(station))
            print(markers[-1])

    return markers

draw_options = {
    "polyline": False,
    "polygon": False,
    "circlemarker": False,
    "circle": False,
    "marker": False,
    "rectangle": {"shapeOptions": {"clickable": False}},
}

app.layout = html.Div(
    dl.Map(
        [
            dl.TileLayer(zIndex=1),
            dl.FeatureGroup(
                [
                    dl.EditControl(
                        id="edit-control", position="topleft", draw=draw_options
                    )
                ]
            ),
            dl.LayerGroup(id="markers"),
        ],
        center=[48.20958883796303, 16.39065742492676],
        zoom=15,
        style={"height": "100%"},
        id="map",
    ),
    style={"height": "100vh"},
)

@app.callback(Output("markers", "children"), Input("edit-control", "geojson"))
def select_stations_area(area_geojson):
    """Select station by selected area."""
    if area_geojson is None:
        raise PreventUpdate
    markers = get_stations(area_geojson)
    return markers

if __name__ == "__main__":
    app.run_server(debug=True)