RssiFingerprint.java

/*
 * Copyright (C) 2018 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.navigation.indoor;

import java.util.List;

/**
 * Contains RSSI readings from several radio sources for an unknown location to be
 * determined.
 *
 * @param <R> a {@link RssiReading} type.
 * @param <S> a {@link RadioSource} type.
 */
public class RssiFingerprint<S extends RadioSource, R extends RssiReading<S>> extends Fingerprint<S, R> {

    /**
     * Constructor.
     */
    public RssiFingerprint() {
    }

    /**
     * Constructor.
     *
     * @param readings non-located RSSI readings.
     * @throws IllegalArgumentException if provided readings is null.
     */
    public RssiFingerprint(final List<R> readings) {
        super(readings);
    }

    /**
     * Gets Euclidean distance of signal readings from another fingerprint.
     *
     * @param otherFingerprint other fingerprint to compare.
     * @return Euclidean distance of signal readings from another fingerprint.
     */
    public double distanceTo(final RssiFingerprint<S, R> otherFingerprint) {
        return Math.sqrt(sqrDistanceTo(otherFingerprint));
    }

    /**
     * Gets squared Euclidean distance of signal readings from another fingerprint.
     *
     * @param otherFingerprint other fingerprint to compare.
     * @return squared Euclidean distance of signal readings from another
     * fingerprint.
     */
    @SuppressWarnings("Duplicates")
    public double sqrDistanceTo(final RssiFingerprint<S, R> otherFingerprint) {
        if (otherFingerprint == null) {
            return Double.MAX_VALUE;
        }

        final var otherReadings = otherFingerprint.getReadings();
        var numAccessPoints = 0;
        var result = 0.0;
        double diff;
        for (final var reading : readings) {
            for (final var otherReading : otherReadings) {
                if (reading.hasSameSource(otherReading)) {
                    diff = reading.getRssi() - otherReading.getRssi();
                    result += diff * diff;
                    numAccessPoints++;
                }
            }
        }

        if (numAccessPoints == 0) {
            return Double.MAX_VALUE;
        }

        return result;
    }

    /**
     * Gets average RSSI (received signal strength indicator) of all readings contained in this fingerprint
     * expressed in dB's.
     *
     * @return average RSSI of all readings.
     */
    public double getMeanRssi() {
        if (readings == null || readings.isEmpty()) {
            return Double.MAX_VALUE;
        }

        var result = 0.0;
        for (final var reading : readings) {
            result += reading.getRssi() / readings.size();
        }

        return result;
    }

    /**
     * Gets Euclidean distance of signal readings from another fingerprint.
     *
     * @param otherFingerprint other fingerprint to compare.
     * @return Euclidean distance of signal readings from another fingerprint.
     */
    public double noMeanDistanceTo(final RssiFingerprint<S, R> otherFingerprint) {
        return Math.sqrt(noMeanSqrDistanceTo(otherFingerprint));
    }

    /**
     * Gets squared Euclidean distance of signal readings with mean RSSI removed from another fingerprint.
     * Mean RSSI's are taken into account so that bias effects introduced by different device's hardware is
     * partially removed.
     *
     * @param otherFingerprint other fingerprint to compare.
     * @return squared Euclidean distance of signal readings from another
     * fingerprint with average RSSI's removed.
     */
    @SuppressWarnings("Duplicates")
    public double noMeanSqrDistanceTo(final RssiFingerprint<S, R> otherFingerprint) {
        if (otherFingerprint == null) {
            return Double.MAX_VALUE;
        }

        final var otherReadings = otherFingerprint.getReadings();
        var numAccessPoints = 0;
        var avgRssiThis = 0.0;
        var avgRssiOther = 0.0;
        for (final var reading : readings) {
            for (final var otherReading : otherReadings) {
                if (reading.hasSameSource(otherReading)) {
                    avgRssiThis += reading.getRssi();
                    avgRssiOther += otherReading.getRssi();

                    numAccessPoints++;
                }
            }
        }

        if (numAccessPoints == 0) {
            return Double.MAX_VALUE;
        }

        avgRssiThis /= numAccessPoints;
        avgRssiOther /= numAccessPoints;

        var result = 0.0;
        for (final var reading : readings) {
            for (final var otherReading : otherReadings) {
                if (reading.hasSameSource(otherReading)) {
                    final var diff = (reading.getRssi() - avgRssiThis) - (otherReading.getRssi() - avgRssiOther);
                    result += diff * diff;
                }
            }
        }

        return result;
    }
}