dk.dma.ais.abnormal.analyzer.analysis.ShipTypeAndSizeAnalysis.java Source code

Java tutorial

Introduction

Here is the source code for dk.dma.ais.abnormal.analyzer.analysis.ShipTypeAndSizeAnalysis.java

Source

/* Copyright (c) 2011 Danish Maritime Authority
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

package dk.dma.ais.abnormal.analyzer.analysis;

import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import dk.dma.ais.abnormal.analyzer.AppStatisticsService;
import dk.dma.ais.abnormal.analyzer.behaviour.BehaviourManager;
import dk.dma.ais.abnormal.analyzer.behaviour.EventCertainty;
import dk.dma.ais.abnormal.analyzer.behaviour.events.AbnormalEventLower;
import dk.dma.ais.abnormal.analyzer.behaviour.events.AbnormalEventMaintain;
import dk.dma.ais.abnormal.analyzer.behaviour.events.AbnormalEventRaise;
import dk.dma.ais.abnormal.event.db.EventRepository;
import dk.dma.ais.abnormal.event.db.domain.Event;
import dk.dma.ais.abnormal.event.db.domain.ShipSizeOrTypeEvent;
import dk.dma.ais.abnormal.event.db.domain.TrackingPoint;
import dk.dma.ais.abnormal.stat.db.StatisticDataRepository;
import dk.dma.ais.abnormal.stat.db.data.ShipTypeAndSizeStatisticData;
import dk.dma.ais.abnormal.stat.db.data.StatisticData;
import dk.dma.ais.abnormal.util.Categorizer;
import dk.dma.ais.tracker.eventEmittingTracker.EventEmittingTracker;
import dk.dma.ais.tracker.eventEmittingTracker.InterpolatedTrackingReport;
import dk.dma.ais.tracker.eventEmittingTracker.Track;
import dk.dma.ais.tracker.eventEmittingTracker.events.CellChangedEvent;
import dk.dma.ais.tracker.eventEmittingTracker.events.TrackStaleEvent;
import dk.dma.enav.model.geometry.Position;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

import static dk.dma.ais.abnormal.analyzer.config.Configuration.CONFKEY_ANALYSIS_TYPESIZE_CELL_SHIPCOUNT_MIN;
import static dk.dma.ais.abnormal.analyzer.config.Configuration.CONFKEY_ANALYSIS_TYPESIZE_PD;
import static dk.dma.ais.abnormal.analyzer.config.Configuration.CONFKEY_ANALYSIS_TYPESIZE_PREDICTIONTIME_MAX;
import static dk.dma.ais.abnormal.analyzer.config.Configuration.CONFKEY_ANALYSIS_TYPESIZE_SHIPLENGTH_MIN;
import static dk.dma.ais.abnormal.event.db.domain.builders.ShipSizeOrTypeEventBuilder.ShipSizeOrTypeEvent;
import static dk.dma.ais.abnormal.util.AisDataHelper.nameOrMmsi;
import static dk.dma.ais.abnormal.util.TrackPredicates.isClassB;
import static dk.dma.ais.abnormal.util.TrackPredicates.isEngagedInTowing;
import static dk.dma.ais.abnormal.util.TrackPredicates.isFishingVessel;
import static dk.dma.ais.abnormal.util.TrackPredicates.isSmallVessel;
import static dk.dma.ais.abnormal.util.TrackPredicates.isSpecialCraft;
import static dk.dma.ais.abnormal.util.TrackPredicates.isUnknownTypeOrSize;

/**
 * This analysis manages events where the presence of a vessel of the given type
 * and size is "abnormal" for the current position (grid cell) relative to previous
 * observations for vessels in the same grid cell. Statistics for previous observations
 * are stored in the StatisticDataRepository.
 *
 * @author Thomas Borg Salling <tbsalling@tbsalling.dk>
 */
public class ShipTypeAndSizeAnalysis extends StatisticBasedAnalysis {
    private static final Logger LOG = LoggerFactory.getLogger(ShipTypeAndSizeAnalysis.class);

    private final AppStatisticsService statisticsService;

    private final int TOTAL_SHIP_COUNT_THRESHOLD;
    private final float PD;
    private final int SHIP_LENGTH_MIN;

    @Inject
    public ShipTypeAndSizeAnalysis(Configuration configuration, AppStatisticsService statisticsService,
            StatisticDataRepository statisticsRepository, EventEmittingTracker trackingService,
            EventRepository eventRepository, BehaviourManager behaviourManager) {
        super(eventRepository, statisticsRepository, trackingService, behaviourManager);
        this.statisticsService = statisticsService;

        setTrackPredictionTimeMax(configuration.getInteger(CONFKEY_ANALYSIS_TYPESIZE_PREDICTIONTIME_MAX, -1));

        TOTAL_SHIP_COUNT_THRESHOLD = configuration.getInt(CONFKEY_ANALYSIS_TYPESIZE_CELL_SHIPCOUNT_MIN, 1000);
        PD = configuration.getFloat(CONFKEY_ANALYSIS_TYPESIZE_PD, 0.001f);
        SHIP_LENGTH_MIN = configuration.getInt(CONFKEY_ANALYSIS_TYPESIZE_SHIPLENGTH_MIN, 50);
        LOG.info(getAnalysisName() + " created (" + this + ").");
    }

    @Override
    public String toString() {
        return "ShipTypeAndSizeAnalysis{" + "TOTAL_SHIP_COUNT_THRESHOLD=" + TOTAL_SHIP_COUNT_THRESHOLD + ", PD="
                + PD + ", SHIP_LENGTH_MIN=" + SHIP_LENGTH_MIN + "} " + super.toString();
    }

    @AllowConcurrentEvents
    @Subscribe
    public void onCellIdChanged(CellChangedEvent trackEvent) {
        statisticsService.incAnalysisStatistics(getAnalysisName(), "Events received");

        Track track = trackEvent.getTrack();

        if (isClassB.test(track) || isUnknownTypeOrSize.test(track) || isFishingVessel.test(track)
                || isSmallVessel.test(track) || isSpecialCraft.test(track) || isEngagedInTowing.test(track)) {
            return;
        }

        /* Skip analysis if track has been predicted forward for too long */
        if (isLastAisTrackingReportTooOld(track, track.getTimeOfLastPositionReport())) {
            LOG.debug("Skipping analysis: MMSI " + track.getMmsi() + " was predicted for too long.");
            return;
        }

        Long cellId = (Long) track.getProperty(Track.CELL_ID);
        Integer shipType = track.getShipType();
        Integer shipLength = track.getVesselLength();

        if (cellId == null) {
            statisticsService.incAnalysisStatistics(getAnalysisName(), "Unknown mmsi");
            return;
        }

        if (shipType == null) {
            statisticsService.incAnalysisStatistics(getAnalysisName(), "Unknown ship type");
            return;
        }

        if (shipLength == null) {
            statisticsService.incAnalysisStatistics(getAnalysisName(), "Unknown ship length");
            return;
        }

        if (shipLength < SHIP_LENGTH_MIN) {
            statisticsService.incAnalysisStatistics(getAnalysisName(), "LOA < " + SHIP_LENGTH_MIN);
            return;
        }

        int shipTypeKey = Categorizer.mapShipTypeToCategory(shipType) - 1;
        int shipLengthKey = Categorizer.mapShipLengthToCategory(shipLength) - 1;

        if (isAbnormalCellForShipTypeAndSize(cellId, shipTypeKey, shipLengthKey)) {
            getBehaviourManager().abnormalBehaviourDetected(ShipSizeOrTypeEvent.class, track);
        } else {
            getBehaviourManager().normalBehaviourDetected(ShipSizeOrTypeEvent.class, track);
        }
    }

    @AllowConcurrentEvents
    @Subscribe
    public void onTrackStale(TrackStaleEvent trackEvent) {
        getBehaviourManager().trackStaleDetected(ShipSizeOrTypeEvent.class, trackEvent.getTrack());
        lowerExistingAbnormalEventIfExists(ShipSizeOrTypeEvent.class, trackEvent.getTrack());
    }

    @Subscribe
    public void onAbnormalEventRaise(AbnormalEventRaise behaviourEvent) {
        LOG.debug("onAbnormalEventRaise " + behaviourEvent.getTrack().getMmsi());
        if (behaviourEvent.getEventClass().equals(ShipSizeOrTypeEvent.class)) {
            raiseOrMaintainAbnormalEvent(ShipSizeOrTypeEvent.class, behaviourEvent.getTrack());
        }
    }

    @Subscribe
    public void onAbnormalEventMaintain(AbnormalEventMaintain behaviourEvent) {
        LOG.debug("onAbnormalEventMaintain " + behaviourEvent.getTrack().getMmsi());
        if (behaviourEvent.getEventClass().equals(ShipSizeOrTypeEvent.class)) {
            raiseOrMaintainAbnormalEvent(ShipSizeOrTypeEvent.class, behaviourEvent.getTrack());
        }
    }

    @Subscribe
    public void onAbnormalEventLower(AbnormalEventLower behaviourEvent) {
        LOG.debug("onAbnormalEventLower " + behaviourEvent.getTrack().getMmsi());
        if (behaviourEvent.getEventClass().equals(ShipSizeOrTypeEvent.class)) {
            lowerExistingAbnormalEventIfExists(ShipSizeOrTypeEvent.class, behaviourEvent.getTrack());
        }
    }

    /**
     * If the probability p(d)<PD and total count>TOTAL_SHIP_COUNT_THRESHOLD then abnormal. p(d)=sum(count)/count for all sog_intervals for
     * that shiptype and size.
     *
     * @param cellId
     * @param shipTypeKey
     * @param shipSizeKey
     * @return true if the presence of size/type in this cell is abnormal. False otherwise.
     */
    boolean isAbnormalCellForShipTypeAndSize(Long cellId, int shipTypeKey, int shipSizeKey) {
        float pd = 1.0f;

        StatisticData shipSizeAndTypeData = getStatisticDataRepository()
                .getStatisticData("ShipTypeAndSizeStatistic", cellId);

        if (shipSizeAndTypeData instanceof ShipTypeAndSizeStatisticData) {
            Integer totalCount = ((ShipTypeAndSizeStatisticData) shipSizeAndTypeData).getSumFor("shipCount");
            if (totalCount > TOTAL_SHIP_COUNT_THRESHOLD) {
                Integer shipCount = ((ShipTypeAndSizeStatisticData) shipSizeAndTypeData).getValue(shipTypeKey,
                        shipSizeKey, ShipTypeAndSizeStatisticData.STAT_SHIP_COUNT);
                if (shipCount == null) {
                    shipCount = 0;
                }
                pd = (float) shipCount / (float) totalCount;
                LOG.debug("cellId=" + cellId + ", shipType=" + shipTypeKey + ", shipSize=" + shipSizeKey
                        + ", shipCount=" + shipCount + ", totalCount=" + totalCount + ", pd=" + pd);
            } else {
                LOG.debug("totalCount of " + totalCount + " is not enough statistical data for cell " + cellId);
            }
        }

        LOG.debug("pd = " + pd);

        boolean isAbnormalCellForShipTypeAndSize = pd < PD;
        if (isAbnormalCellForShipTypeAndSize) {
            LOG.debug("Abnormal event detected.");
        } else {
            LOG.debug("Normal or inconclusive event detected.");
        }

        statisticsService.incAnalysisStatistics(getAnalysisName(), "Analyses performed");

        return isAbnormalCellForShipTypeAndSize;
    }

    @Override
    protected Event buildEvent(Track track, Track... otherTracks) {
        if (otherTracks != null && otherTracks.length > 0) {
            throw new IllegalArgumentException("otherTracks not supported.");
        }

        Integer mmsi = track.getMmsi();
        Integer imo = track.getIMO();
        String callsign = track.getCallsign();
        String name = nameOrMmsi(track.getShipName(), mmsi);
        Integer shipType = track.getShipType();
        Integer shipLength = track.getVesselLength();
        Integer shipDimensionToBow = track.getShipDimensionBow();
        Integer shipDimensionToStern = track.getShipDimensionStern();
        Integer shipDimensionToPort = track.getShipDimensionPort();
        Integer shipDimensionToStarboard = track.getShipDimensionStarboard();
        Date positionTimestamp = new Date(track.getTimeOfLastPositionReport());
        Position position = track.getPosition();
        Float cog = track.getCourseOverGround();
        Float sog = track.getSpeedOverGround();
        Float hdg = track.getTrueHeading();
        Boolean interpolated = track.getNewestTrackingReport() instanceof InterpolatedTrackingReport;

        TrackingPoint.EventCertainty certainty = TrackingPoint.EventCertainty.UNDEFINED;
        EventCertainty eventCertainty = getBehaviourManager()
                .getEventCertaintyAtCurrentPosition(ShipSizeOrTypeEvent.class, track);
        if (eventCertainty != null) {
            certainty = TrackingPoint.EventCertainty.create(eventCertainty.getCertainty());
        }

        short shipTypeCategory = Categorizer.mapShipTypeToCategory(shipType);
        short shipLengthCategory = Categorizer.mapShipLengthToCategory(shipLength);

        String shipTypeAsString = Categorizer.mapShipTypeCategoryToString(shipTypeCategory);
        String shipLengthAsString = Categorizer.mapShipSizeCategoryToString(shipLengthCategory);

        String title = "Abnormal type and size of ship";
        String description = String.format(
                "Abnormal type and size of " + name + " (" + shipTypeAsString + ") on position " + position + " at "
                        + DATE_FORMAT.format(positionTimestamp) + ": type:%d(%s) size:%d(%s).",
                shipType, shipTypeAsString, shipLength, shipLengthAsString);

        LOG.info(description);

        Event event = ShipSizeOrTypeEvent().shipType(shipTypeCategory).shipLength(shipLengthCategory).title(title)
                .description(description).startTime(positionTimestamp).behaviour().isPrimary(true).vessel()
                .mmsi(mmsi).imo(imo).callsign(callsign).type(shipType /* shipTypeCategory */)
                .toBow(shipDimensionToBow).toStern(shipDimensionToStern).toPort(shipDimensionToPort)
                .toStarboard(shipDimensionToStarboard).name(name).trackingPoint().timestamp(positionTimestamp)
                .positionInterpolated(interpolated).eventCertainty(certainty).speedOverGround(sog)
                .courseOverGround(cog).trueHeading(hdg).latitude(position.getLatitude())
                .longitude(position.getLongitude()).getEvent();

        addPreviousTrackingPoints(event, track);

        statisticsService.incAnalysisStatistics(getAnalysisName(), "Events raised");

        return event;
    }
}