M0r13n / pyais

AIS message decoding and encoding in Python (AIVDM/AIVDO)
MIT License
176 stars 61 forks source link

how to apply a filter to a single message #141

Closed Mobilitysensing closed 2 months ago

Mobilitysensing commented 2 months ago

Hello,

I'm trying to use a filter on the tcp teststream from Norway. See the code below. What is contained in msg_filter and how do i access the messages which pass the filter ?

I am not very fluent in python so i might miss the obvious.

S.



# Define the filter chain with various criteria
chain = FilterChain([
    # Filter out messages based on the 'turn' attribute or lack thereof
    #AttributeFilter(lambda x: not hasattr(x, 'turn') or x.turn == -128.0),

    # Ensure 'lon', 'lat', and 'mmsi2' attributes are not None
    #NoneFilter('lon', 'lat', 'mmsi2'),

    # Include only messages of type 1, 2, or 3
    MessageTypeFilter(1, 2, 3, 5),

    # Limit messages to within 1000 km of a specific point
    DistanceFilter((51.900, 5.320), distance_km=100),

    # Restrict messages to a specific geographic grid
    GridFilter(lat_min=50, lon_min=0, lat_max=52, lon_max=5),
])

 for ais_msg in TCPConnection(host, port=port):
        decoded_message = ais_msg.decode()
        msg_filter = chain.filter(ais_msg)
        print(msg_filter)

        if decoded_message.msg_type == 1 :
            print(decoded_message.mmsi, decoded_message.lon, decoded_message.lat)
        if decoded_message.msg_type == 5 :
            print(decoded_message.mmsi, decoded_message.shipname)             

...

Output
<generator object FilterChain.filter at 0x000001E18E0ACDD0>
<generator object FilterChain.filter at 0x000001E18E0ACF20>
311001445 7.47812 55.872227
<generator object FilterChain.filter at 0x000001E18E0ACF90>
258714000 11.237552 57.657952
Mobilitysensing commented 2 months ago

Found a workaround, adding a mock message makes it a list


for ais_msg in TCPConnection(host, port=port):
        decoded_message = list((ais_msg.decode(),MockAISMessage(lat=1, lon=1)))

        msg_filtered = chain.filter(decoded_message)

        for msg in msg_filtered:         
            #print(msg)
            if msg.msg_type == 1 :
                print(msg.mmsi, msg.lon, msg.lat, msg.speed, msg.status)
            if msg.msg_type == 5 :
                #print(msg)
                print(msg.mmsi, msg.shipname, msg.callsign, msg.ship_type, msg.destination)
M0r13n commented 2 months ago

Hey @Mobilitysensing,

the following code is based on your input and should work:

# Define the filter chain with various criteria
from pyais.filter import DistanceFilter, FilterChain, GridFilter, MessageTypeFilter
from pyais.stream import TCPConnection

chain = FilterChain([
    MessageTypeFilter(1, 2, 3, 5),
    DistanceFilter((51.900, 5.320), distance_km=100),
    GridFilter(lat_min=50, lon_min=0, lat_max=52, lon_max=5),
])

with TCPConnection('153.44.253.27', port=5631) as ais_stream:
    for ais_msg in chain.filter(map(lambda x: x.decode(), ais_stream)):
        print(ais_msg)
M0r13n commented 2 months ago

Hey @Mobilitysensing,

your comment made it clear, that the API of FilterChain was somewhat lacking. Therefore, I improved the API slightly. Using the newest version of pyais, v2.6.6, the code from above can be simplified:

# Define the filter chain with various criteria
from pyais.filter import DistanceFilter, FilterChain, GridFilter, MessageTypeFilter
from pyais.stream import TCPConnection

chain = FilterChain([
    MessageTypeFilter(1, 2, 3, 5),
    DistanceFilter((51.900, 5.320), distance_km=100),
    GridFilter(lat_min=50, lon_min=0, lat_max=52, lon_max=5),
])

with TCPConnection('153.44.253.27', port=5631) as ais_stream:
    for ais_msg in chain.filter(ais_stream):
        print(ais_msg)

You no longer have to create a stream of decoded messages by yourself. Instead, you can just pass the stream instance directly to filter().

NOTE: This change breaks backwards compatibility. The previous example using map(lambda x: x.decode(), ais_stream) will no longer work.

Mobilitysensing commented 2 months ago

Hi Morten,

Works perfectly, thx !

The distance filter obviously will only be effective on messages which contain coordinates. Would there be an easy way to filter e.g. message type 5 on distance ? Currently i store shipinfo and positions in separate tables and could decide to insert / not insert message 5 based on the last position message. Works but could be more elegant.

Sebastiaan

M0r13n commented 2 months ago

@Mobilitysensing Nope. The filters provided by this library are stateless. This is up to the application developer to implement. However, you may find the included tracker helpful for this.