1972 lines
74 KiB
Java
1972 lines
74 KiB
Java
/*
|
|
* Copyright (c) 2011, Swedish Institute of Computer Science.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
package se.sics.mrm;
|
|
|
|
import java.awt.geom.GeneralPath;
|
|
import java.awt.geom.Line2D;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Enumeration;
|
|
import java.util.Hashtable;
|
|
import java.util.Observable;
|
|
import java.util.Observer;
|
|
import java.util.Properties;
|
|
import java.util.Random;
|
|
import java.util.Vector;
|
|
|
|
import javax.swing.tree.DefaultMutableTreeNode;
|
|
|
|
import org.apache.log4j.Logger;
|
|
import org.jdom.Element;
|
|
|
|
import se.sics.cooja.Simulation;
|
|
import se.sics.cooja.interfaces.DirectionalAntennaRadio;
|
|
import se.sics.cooja.interfaces.Radio;
|
|
import se.sics.cooja.radiomediums.AbstractRadioMedium;
|
|
import statistics.GaussianWrapper;
|
|
|
|
/**
|
|
* The channel model object in MRM is responsible for calulating propagation
|
|
* impact on packets being sent in the radio medium.
|
|
*
|
|
* By registering as a settings observer on this channel model, other parts will
|
|
* be notified if the settings change.
|
|
*
|
|
* TODO Add better support for different signal strengths
|
|
*
|
|
* @author Fredrik Osterlind
|
|
*/
|
|
public class ChannelModel {
|
|
private static Logger logger = Logger.getLogger(ChannelModel.class);
|
|
|
|
private static final double C = 299792458; /* m/s */
|
|
|
|
enum TransmissionData { SIGNAL_STRENGTH, SIGNAL_STRENGTH_VAR, SNR, SNR_VAR, PROB_OF_RECEPTION, DELAY_SPREAD, DELAY_SPREAD_RMS}
|
|
|
|
private Hashtable<Parameter,Object> parametersDefaults = new Hashtable<Parameter,Object>();
|
|
private Hashtable<Parameter,Object> parameters = new Hashtable<Parameter,Object>();
|
|
private Properties parameterDescriptions = new Properties();
|
|
|
|
// Parameters used for speeding up calculations
|
|
private boolean needToPrecalculateFSPL = true;
|
|
private static double paramFSPL = 0;
|
|
private boolean needToPrecalculateOutputPower = true;
|
|
private static double paramOutputPower = 0;
|
|
|
|
private ObstacleWorld myObstacleWorld = new ObstacleWorld();
|
|
|
|
/* Log mode: visualize signal components */
|
|
private boolean logMode = false;
|
|
private StringBuilder logInfo = null;
|
|
private ArrayList<Line2D> loggedRays = null;
|
|
|
|
private Simulation simulation;
|
|
|
|
|
|
// Ray tracing components temporary vector
|
|
private Vector<Vector<Line2D>> calculatedVisibleSides = new Vector<Vector<Line2D>>();
|
|
private Vector<Point2D> calculatedVisibleSidesSources = new Vector<Point2D>();
|
|
private Vector<Line2D> calculatedVisibleSidesLines = new Vector<Line2D>();
|
|
private Vector<AngleInterval> calculatedVisibleSidesAngleIntervals = new Vector<AngleInterval>();
|
|
private static int maxSavedVisibleSides = 30; // Max size of lists above
|
|
|
|
/**
|
|
* Notifies observers when this channel model has changed settings.
|
|
*/
|
|
private class SettingsObservable extends Observable {
|
|
private void notifySettingsChanged() {
|
|
setChanged();
|
|
notifyObservers();
|
|
}
|
|
}
|
|
private SettingsObservable settingsObservable = new SettingsObservable();
|
|
public enum Parameter {
|
|
apply_random,
|
|
snr_threshold,
|
|
bg_noise_mean,
|
|
bg_noise_var,
|
|
system_gain_mean,
|
|
system_gain_var,
|
|
frequency,
|
|
tx_power,
|
|
tx_with_gain,
|
|
rx_sensitivity,
|
|
rx_with_gain,
|
|
rt_disallow_direct_path,
|
|
rt_ignore_non_direct,
|
|
rt_fspl_on_total_length,
|
|
rt_max_rays,
|
|
rt_max_refractions,
|
|
rt_max_reflections,
|
|
rt_max_diffractions,
|
|
rt_use_scattering,
|
|
rt_refrac_coefficient,
|
|
rt_reflec_coefficient,
|
|
rt_diffr_coefficient,
|
|
rt_scatt_coefficient,
|
|
obstacle_attenuation,
|
|
captureEffect,
|
|
captureEffectPreambleDuration,
|
|
captureEffectSignalTreshold;
|
|
|
|
public static Object getDefaultValue(Parameter p) {
|
|
switch (p) {
|
|
case apply_random:
|
|
return new Boolean(false);
|
|
case snr_threshold:
|
|
return new Double(6);
|
|
case bg_noise_mean:
|
|
return new Double(AbstractRadioMedium.SS_NOTHING);
|
|
case bg_noise_var:
|
|
return new Double(1);
|
|
case system_gain_mean:
|
|
return new Double(0);
|
|
case system_gain_var:
|
|
return new Double(4);
|
|
case frequency: /* MHz */
|
|
return new Double(2400);
|
|
case tx_power:
|
|
return new Double(1.5);
|
|
case tx_with_gain:
|
|
return new Boolean(true);
|
|
case rx_sensitivity:
|
|
return new Double(-100);
|
|
case rx_with_gain:
|
|
return new Boolean(false);
|
|
case rt_disallow_direct_path:
|
|
return new Boolean(false);
|
|
case rt_ignore_non_direct:
|
|
return new Boolean(false);
|
|
case rt_fspl_on_total_length:
|
|
return new Boolean(true);
|
|
case rt_max_rays:
|
|
return new Integer(1);
|
|
case rt_max_refractions:
|
|
return new Integer(1);
|
|
case rt_max_reflections:
|
|
return new Integer(1);
|
|
case rt_max_diffractions:
|
|
return new Integer(0);
|
|
case rt_use_scattering:
|
|
return new Boolean(false);
|
|
case rt_refrac_coefficient:
|
|
return new Double(-3);
|
|
case rt_reflec_coefficient:
|
|
return new Double(-5);
|
|
case rt_diffr_coefficient:
|
|
return new Double(-10);
|
|
case rt_scatt_coefficient:
|
|
return new Double(-20);
|
|
case obstacle_attenuation:
|
|
return new Double(-3);
|
|
case captureEffect:
|
|
return true;
|
|
case captureEffectPreambleDuration:
|
|
return (double) (1000*1000*4*0.5*8/250000); /* 2 bytes, 250kbit/s, us */
|
|
case captureEffectSignalTreshold:
|
|
return (double) 3; /* dB, according to previous 802.15.4 studies */
|
|
}
|
|
throw new RuntimeException("Unknown default value: " + p);
|
|
}
|
|
|
|
public static Parameter fromString(String name) {
|
|
/* Backwards compatability */
|
|
if (name.equals("apply_random")) {
|
|
return apply_random;
|
|
} else if (name.equals("snr_threshold")) {
|
|
return snr_threshold;
|
|
} else if (name.equals("bg_noise_mean")) {
|
|
return bg_noise_mean;
|
|
} else if (name.equals("bg_noise_var")) {
|
|
return bg_noise_var;
|
|
} else if (name.equals("system_gain_mean")) {
|
|
return system_gain_mean;
|
|
} else if (name.equals("system_gain_var")) {
|
|
return system_gain_var;
|
|
} else if (name.equals("tx_power")) {
|
|
return tx_power;
|
|
} else if (name.equals("rx_sensitivity")) {
|
|
return rx_sensitivity;
|
|
} else if (name.equals("rt_disallow_direct_path")) {
|
|
return rt_disallow_direct_path;
|
|
} else if (name.equals("rt_ignore_non_direct")) {
|
|
return rt_ignore_non_direct;
|
|
} else if (name.equals("rt_fspl_on_total_length")) {
|
|
return rt_fspl_on_total_length;
|
|
} else if (name.equals("rt_max_rays")) {
|
|
return rt_max_rays;
|
|
} else if (name.equals("rt_max_refractions")) {
|
|
return rt_max_refractions;
|
|
} else if (name.equals("rt_max_reflections")) {
|
|
return rt_max_reflections;
|
|
} else if (name.equals("rt_max_diffractions")) {
|
|
return rt_max_diffractions;
|
|
} else if (name.equals("rt_use_scattering")) {
|
|
return rt_use_scattering;
|
|
} else if (name.equals("rt_refrac_coefficient")) {
|
|
return rt_refrac_coefficient;
|
|
} else if (name.equals("rt_reflec_coefficient")) {
|
|
return rt_reflec_coefficient;
|
|
} else if (name.equals("rt_diffr_coefficient")) {
|
|
return rt_diffr_coefficient;
|
|
} else if (name.equals("rt_scatt_coefficient")) {
|
|
return rt_scatt_coefficient;
|
|
} else if (name.equals("obstacle_attenuation")) {
|
|
return obstacle_attenuation;
|
|
} else if (name.equals("captureEffect")) {
|
|
return captureEffect;
|
|
} else if (name.equals("captureEffectPreambleDuration")) {
|
|
return captureEffectPreambleDuration;
|
|
} else if (name.equals("captureEffectSignalTreshold")) {
|
|
return captureEffectSignalTreshold;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static String getDescription(Parameter p) {
|
|
switch (p) {
|
|
case apply_random: return "(DEBUG) Apply random values";
|
|
case snr_threshold: return "SNR reception threshold (dB)";
|
|
case bg_noise_mean: return "Background noise mean (dBm)";
|
|
case bg_noise_var: return "Background noise variance (dB)";
|
|
case system_gain_mean: return "Extra system gain mean (dB)";
|
|
case system_gain_var: return "Extra system gain variance (dB)";
|
|
case frequency: return "Frequency (MHz)";
|
|
case tx_power: return "Default transmitter output power (dBm)";
|
|
case tx_with_gain: return "Directional antennas: with TX gain";
|
|
case rx_sensitivity: return "Receiver sensitivity (dBm)";
|
|
case rx_with_gain: return "Directional antennas: with RX gain";
|
|
case rt_disallow_direct_path: return "Disallow direct path";
|
|
case rt_ignore_non_direct: return "If existing: return only use direct path";
|
|
case rt_fspl_on_total_length: return "Use FSPL on total path lengths only";
|
|
case rt_max_rays: return "Max path rays";
|
|
case rt_max_refractions: return "Max refractions";
|
|
case rt_max_reflections: return "Max reflections";
|
|
case rt_max_diffractions: return "Max diffractions";
|
|
case rt_refrac_coefficient: return "Refraction coefficient (dB)";
|
|
case rt_reflec_coefficient: return "Reflection coefficient (dB)";
|
|
case rt_diffr_coefficient: return "Diffraction coefficient (dB)";
|
|
case obstacle_attenuation: return "Obstacle attenuation (dB/m)";
|
|
case captureEffect: return "Use Capture Effect";
|
|
case captureEffectPreambleDuration: return "Capture effect preamble (us)";
|
|
case captureEffectSignalTreshold: return "Capture effect threshold (dB)";
|
|
}
|
|
throw new RuntimeException("Unknown decrption: " + p);
|
|
}
|
|
}
|
|
|
|
public ChannelModel(Simulation simulation) {
|
|
this.simulation = simulation;
|
|
|
|
/* Default values */
|
|
for (Parameter p: Parameter.values()) {
|
|
parameters.put(p, Parameter.getDefaultValue(p));
|
|
}
|
|
|
|
parametersDefaults = (Hashtable<Parameter,Object>) parameters.clone();
|
|
|
|
// Ray Tracer - Use scattering
|
|
//parameters.put(Parameters.rt_use_scattering, Parameter.getDefaultValue(Parameters.rt_use_scattering)); // TODO Not used yet
|
|
//parameterDescriptions.put(Parameters.rt_use_scattering, "Use simple scattering");
|
|
|
|
// Ray Tracer - Scattering coefficient
|
|
//parameters.put(Parameters.rt_scatt_coefficient, Parameter.getDefaultValue(Parameters.rt_scatt_coefficient)); // TODO Not used yet
|
|
//parameterDescriptions.put(Parameters.rt_scatt_coefficient, "!! Scattering coefficient (dB)");
|
|
}
|
|
|
|
/**
|
|
* Adds a settings observer to this channel model.
|
|
* Every time the settings are changed all observers
|
|
* will be notified.
|
|
*
|
|
* @param obs New observer
|
|
*/
|
|
public void addSettingsObserver(Observer obs) {
|
|
settingsObservable.addObserver(obs);
|
|
}
|
|
|
|
/**
|
|
* Deletes an earlier registered setting observer.
|
|
*
|
|
* @param osb
|
|
* Earlier registered observer
|
|
*/
|
|
public void deleteSettingsObserver(Observer obs) {
|
|
settingsObservable.deleteObserver(obs);
|
|
}
|
|
|
|
/**
|
|
* Remove all previously registered obstacles
|
|
*/
|
|
public void removeAllObstacles() {
|
|
myObstacleWorld.removeAll();
|
|
settingsObservable.notifySettingsChanged();
|
|
}
|
|
|
|
/**
|
|
* Add new obstacle with a rectangle shape.
|
|
* Notifies observers of the new obstacle.
|
|
*
|
|
* @param startX Low X coordinate
|
|
* @param startY Low Y coordinate
|
|
* @param width Width of obstacle
|
|
* @param height Height of obstacle
|
|
*/
|
|
public void addRectObstacle(double startX, double startY, double width, double height) {
|
|
addRectObstacle(startX, startY, width, height, true);
|
|
}
|
|
|
|
/**
|
|
* Add new obstacle with a rectangle shape.
|
|
* Notifies observers depending on given notify argument.
|
|
*
|
|
* @param startX Low X coordinate
|
|
* @param startY Low Y coordinate
|
|
* @param width Width of obstacle
|
|
* @param height Height of obstacle
|
|
* @param notify If true, notifies all observers of this new obstacle
|
|
*/
|
|
public void addRectObstacle(double startX, double startY, double width, double height, boolean notify) {
|
|
myObstacleWorld.addObstacle(startX, startY, width, height);
|
|
|
|
if (notify) {
|
|
settingsObservable.notifySettingsChanged();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Number of registered obstacles
|
|
*/
|
|
public int getNumberOfObstacles() {
|
|
return myObstacleWorld.getNrObstacles();
|
|
}
|
|
|
|
/**
|
|
* Returns an obstacle at given position
|
|
* @param i Obstacle position
|
|
* @return Obstacle
|
|
*/
|
|
public Rectangle2D getObstacle(int i) {
|
|
return myObstacleWorld.getObstacle(i);
|
|
}
|
|
|
|
/**
|
|
* Returns a parameter value
|
|
*
|
|
* @param identifier Parameter identifier
|
|
* @return Current parameter value
|
|
*/
|
|
public Object getParameterValue(Parameter id) {
|
|
Object value = parameters.get(id);
|
|
if (value == null) {
|
|
logger.fatal("No parameter with id:" + id + ", aborting");
|
|
return null;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Returns a double parameter value
|
|
*
|
|
* @param identifier Parameter identifier
|
|
* @return Current parameter value
|
|
*/
|
|
public double getParameterDoubleValue(Parameter id) {
|
|
return ((Double) getParameterValue(id)).doubleValue();
|
|
}
|
|
|
|
/**
|
|
* Returns an integer parameter value
|
|
*
|
|
* @param identifier Parameter identifier
|
|
* @return Current parameter value
|
|
*/
|
|
public int getParameterIntegerValue(Parameter id) {
|
|
return ((Integer) getParameterValue(id)).intValue();
|
|
}
|
|
|
|
/**
|
|
* Returns a boolean parameter value
|
|
*
|
|
* @param identifier Parameter identifier
|
|
* @return Current parameter value
|
|
*/
|
|
public boolean getParameterBooleanValue(Parameter id) {
|
|
return ((Boolean) getParameterValue(id)).booleanValue();
|
|
}
|
|
|
|
/**
|
|
* Saves a new parameter value
|
|
*
|
|
* @param id Parameter identifier
|
|
* @param newValue New parameter value
|
|
*/
|
|
public void setParameterValue(Parameter id, Object newValue) {
|
|
if (!parameters.containsKey(id)) {
|
|
logger.fatal("No parameter with id:" + id + ", aborting");
|
|
return;
|
|
}
|
|
parameters.put(id, newValue);
|
|
|
|
// Guessing we need to recalculate input to FSPL+Output power
|
|
needToPrecalculateFSPL = true;
|
|
needToPrecalculateOutputPower = true;
|
|
|
|
settingsObservable.notifySettingsChanged();
|
|
}
|
|
|
|
/**
|
|
* When this method is called all settings observers
|
|
* will be notified.
|
|
*/
|
|
public void notifySettingsChanged() {
|
|
settingsObservable.notifySettingsChanged();
|
|
}
|
|
|
|
/**
|
|
* Path loss component from Friis' transmission equation.
|
|
* Uses frequency and distance only.
|
|
*
|
|
* @param distance Transmitter-receiver distance
|
|
* @return Path loss (dB)
|
|
*/
|
|
protected double getFSPL(double distance) {
|
|
if (needToPrecalculateFSPL) {
|
|
double f = getParameterDoubleValue(Parameter.frequency);
|
|
paramFSPL = -32.44 -20*Math.log10(f /*mhz*/);
|
|
needToPrecalculateFSPL = false;
|
|
}
|
|
|
|
return Math.min(0.0, paramFSPL - 20*Math.log10(distance/1000.0 /*km*/));
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the subset of a given line, that is intersecting the given rectangle.
|
|
* This method returns null if the line does not intersect the rectangle.
|
|
* The given line is defined by the given (x1, y1) -> (x2, y2).
|
|
*
|
|
* @param x1 Line start point X
|
|
* @param y1 Line start point Y
|
|
* @param x2 Line end point X
|
|
* @param y2 Line epoint Y
|
|
* @param rectangle Rectangle which line may intersect
|
|
* @return Intersection line of given line and rectangle (or null)
|
|
*/
|
|
private Line2D getIntersectionLine(double x1, double y1, double x2, double y2, Rectangle2D rectangle) {
|
|
|
|
// Check if entire line is inside rectangle
|
|
if (rectangle.contains(x1, y1) && rectangle.contains(x2, y2)) {
|
|
return new Line2D.Double(x1, y1, x2, y2);
|
|
}
|
|
|
|
// Get rectangle and test lines
|
|
Line2D rectangleLower = new Line2D.Double(rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMinY());
|
|
Line2D rectangleUpper = new Line2D.Double(rectangle.getMinX(), rectangle.getMaxY(), rectangle.getMaxX(), rectangle.getMaxY());
|
|
Line2D rectangleLeft = new Line2D.Double(rectangle.getMinX(), rectangle.getMinY(), rectangle.getMinX(), rectangle.getMaxY());
|
|
Line2D rectangleRight = new Line2D.Double(rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY());
|
|
Line2D testLine = new Line2D.Double(x1, y1, x2, y2);
|
|
|
|
// Check which sides of the rectangle the test line passes through
|
|
Vector<Line2D> intersectedSides = new Vector<Line2D>();
|
|
|
|
if (rectangleLower.intersectsLine(testLine)) {
|
|
intersectedSides.add(rectangleLower);
|
|
}
|
|
|
|
if (rectangleUpper.intersectsLine(testLine)) {
|
|
intersectedSides.add(rectangleUpper);
|
|
}
|
|
|
|
if (rectangleLeft.intersectsLine(testLine)) {
|
|
intersectedSides.add(rectangleLeft);
|
|
}
|
|
|
|
if (rectangleRight.intersectsLine(testLine)) {
|
|
intersectedSides.add(rectangleRight);
|
|
}
|
|
|
|
// If no sides are intersected, return null (no intersection)
|
|
if (intersectedSides.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
// Calculate all resulting line points (should be 2)
|
|
Vector<Point2D> intersectingLinePoints = new Vector<Point2D>();
|
|
|
|
for (int i=0; i < intersectedSides.size(); i++) {
|
|
intersectingLinePoints.add(
|
|
getIntersectionPoint(testLine, intersectedSides.get(i))
|
|
);
|
|
}
|
|
|
|
// If only one side was intersected, one point must be inside rectangle
|
|
if (intersectingLinePoints.size() == 1) {
|
|
if (rectangle.contains(x1, y1)) {
|
|
intersectingLinePoints.add(new Point2D.Double(x1, y1));
|
|
} else if (rectangle.contains(x2, y2)) {
|
|
intersectingLinePoints.add(new Point2D.Double(x2, y2));
|
|
} else {
|
|
// Border case, no intersection line
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (intersectingLinePoints.size() != 2) {
|
|
// We should have 2 line points!
|
|
logger.warn("Intersecting points != 2");
|
|
return null;
|
|
}
|
|
|
|
if (intersectingLinePoints.get(0).distance(intersectingLinePoints.get(1)) < 0.001) {
|
|
return null;
|
|
}
|
|
|
|
return new Line2D.Double(
|
|
intersectingLinePoints.get(0),
|
|
intersectingLinePoints.get(1)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the intersection point of the two given lines.
|
|
*
|
|
* @param firstLine First line
|
|
* @param secondLine Second line
|
|
* @return Intersection point of the two lines or null
|
|
*/
|
|
private Point2D getIntersectionPoint(Line2D firstLine, Line2D secondLine) {
|
|
double dx1 = firstLine.getX2() - firstLine.getX1();
|
|
double dy1 = firstLine.getY2() - firstLine.getY1();
|
|
double dx2 = secondLine.getX2() - secondLine.getX1();
|
|
double dy2 = secondLine.getY2() - secondLine.getY1();
|
|
double det = (dx2*dy1-dy2*dx1);
|
|
|
|
if (det == 0.0) {
|
|
// Lines parallell, not intersecting
|
|
return null;
|
|
}
|
|
|
|
double mu = ((firstLine.getX1() - secondLine.getX1())*dy1 - (firstLine.getY1() - secondLine.getY1())*dx1)/det;
|
|
if (mu >= 0.0 && mu <= 1.0) {
|
|
Point2D.Double intersectionPoint = new Point2D.Double((secondLine.getX1() + mu*dx2),
|
|
(secondLine.getY1() + mu*dy2));
|
|
|
|
return intersectionPoint;
|
|
}
|
|
|
|
// Lines not intersecting withing segments
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the intersection point of the two given lines when streched to infinity.
|
|
*
|
|
* @param firstLine First line
|
|
* @param secondLine Second line
|
|
* @return Intersection point of the two infinite lines or null if parallell
|
|
*/
|
|
private Point2D getIntersectionPointInfinite(Line2D firstLine, Line2D secondLine) {
|
|
double dx1 = firstLine.getX2() - firstLine.getX1();
|
|
double dy1 = firstLine.getY2() - firstLine.getY1();
|
|
double dx2 = secondLine.getX2() - secondLine.getX1();
|
|
double dy2 = secondLine.getY2() - secondLine.getY1();
|
|
double det = (dx2*dy1-dy2*dx1);
|
|
|
|
if (det == 0.0) {
|
|
// Lines parallell, not intersecting
|
|
return null;
|
|
}
|
|
|
|
double mu = ((firstLine.getX1() - secondLine.getX1())*dy1 - (firstLine.getY1() - secondLine.getY1())*dx1)/det;
|
|
Point2D.Double intersectionPoint = new Point2D.Double((secondLine.getX1() + mu*dx2),
|
|
(secondLine.getY1() + mu*dy2));
|
|
|
|
return intersectionPoint;
|
|
}
|
|
|
|
/**
|
|
* This method builds a tree structure with all visible lines from a given source.
|
|
* It is recursive and depends on the given ray data argument, which holds information
|
|
* about maximum number of recursions.
|
|
* Each element in the tree is either produced from a refraction, reflection or a diffraction
|
|
* (except for the absolute source which is neither), and holds a point and a line.
|
|
*
|
|
* @param rayData Holds information about the incident ray
|
|
* @return Tree of all visibles lines
|
|
*/
|
|
private DefaultMutableTreeNode buildVisibleLinesTree(RayData rayData) {
|
|
DefaultMutableTreeNode thisTree = new DefaultMutableTreeNode();
|
|
thisTree.setUserObject(rayData);
|
|
|
|
// If no more rays may be produced there if no need to search for visible lines
|
|
if (rayData.getSubRaysLimit() <= 0) {
|
|
return thisTree;
|
|
}
|
|
|
|
Point2D source = rayData.getSourcePoint();
|
|
Line2D line = rayData.getLine();
|
|
|
|
// Find all visible lines
|
|
Vector<Line2D> visibleSides = getAllVisibleSides(
|
|
source.getX(),
|
|
source.getY(),
|
|
null,
|
|
line
|
|
);
|
|
|
|
// Create refracted subtrees
|
|
if (rayData.getRefractedSubRaysLimit() > 0 && visibleSides != null) {
|
|
Enumeration<Line2D> visibleSidesEnum = visibleSides.elements();
|
|
while (visibleSidesEnum.hasMoreElements()) {
|
|
Line2D refractingSide = visibleSidesEnum.nextElement();
|
|
|
|
// Keeping old source, but looking through this line to see behind it
|
|
|
|
// Recursively build and add subtrees
|
|
RayData newRayData = new RayData(
|
|
RayData.RayType.REFRACTION,
|
|
source,
|
|
refractingSide,
|
|
rayData.getSubRaysLimit() - 1,
|
|
rayData.getRefractedSubRaysLimit() - 1,
|
|
rayData.getReflectedSubRaysLimit(),
|
|
rayData.getDiffractedSubRaysLimit()
|
|
);
|
|
DefaultMutableTreeNode subTree = buildVisibleLinesTree(newRayData);
|
|
|
|
thisTree.add(subTree);
|
|
}
|
|
}
|
|
|
|
// Create reflection subtrees
|
|
if (rayData.getReflectedSubRaysLimit() > 0 && visibleSides != null) {
|
|
Enumeration<Line2D> visibleSidesEnum = visibleSides.elements();
|
|
while (visibleSidesEnum.hasMoreElements()) {
|
|
Line2D reflectingSide = visibleSidesEnum.nextElement();
|
|
|
|
// Create new pseudo-source
|
|
Rectangle2D bounds = reflectingSide.getBounds2D();
|
|
double newPsuedoSourceX = source.getX();
|
|
double newPsuedoSourceY = source.getY();
|
|
if (bounds.getHeight() > bounds.getWidth()) {
|
|
newPsuedoSourceX = 2*reflectingSide.getX1() - newPsuedoSourceX;
|
|
} else {
|
|
newPsuedoSourceY = 2*reflectingSide.getY1() - newPsuedoSourceY;
|
|
}
|
|
|
|
// Recursively build and add subtrees
|
|
RayData newRayData = new RayData(
|
|
RayData.RayType.REFLECTION,
|
|
new Point2D.Double(newPsuedoSourceX, newPsuedoSourceY),
|
|
reflectingSide,
|
|
rayData.getSubRaysLimit() - 1,
|
|
rayData.getRefractedSubRaysLimit(),
|
|
rayData.getReflectedSubRaysLimit() - 1,
|
|
rayData.getDiffractedSubRaysLimit()
|
|
);
|
|
DefaultMutableTreeNode subTree = buildVisibleLinesTree(newRayData);
|
|
|
|
thisTree.add(subTree);
|
|
}
|
|
}
|
|
|
|
// Get possible diffraction sources
|
|
Vector<Point2D> diffractionSources = null;
|
|
if (rayData.getDiffractedSubRaysLimit() > 0) {
|
|
diffractionSources = getAllDiffractionSources(visibleSides);
|
|
}
|
|
|
|
// Create diffraction subtrees
|
|
if (rayData.getDiffractedSubRaysLimit() > 0 && diffractionSources != null) {
|
|
Enumeration<Point2D> diffractionSourcesEnum = diffractionSources.elements();
|
|
while (diffractionSourcesEnum.hasMoreElements()) {
|
|
Point2D diffractionSource = diffractionSourcesEnum.nextElement();
|
|
|
|
// Recursively build and add subtrees
|
|
RayData newRayData = new RayData(
|
|
RayData.RayType.DIFFRACTION,
|
|
diffractionSource,
|
|
null,
|
|
rayData.getSubRaysLimit() - 1,
|
|
rayData.getRefractedSubRaysLimit(),
|
|
rayData.getReflectedSubRaysLimit(),
|
|
rayData.getDiffractedSubRaysLimit() - 1
|
|
);
|
|
DefaultMutableTreeNode subTree = buildVisibleLinesTree(newRayData);
|
|
|
|
thisTree.add(subTree);
|
|
}
|
|
}
|
|
|
|
return thisTree;
|
|
}
|
|
|
|
/**
|
|
* Returns a vector of ray paths from given origin to given destination.
|
|
* Each ray path consists of a vector of points (including source and destination).
|
|
*
|
|
* @param origin Ray paths origin
|
|
* @param dest Ray paths destination
|
|
* @param visibleLinesTree Information about all visible lines generated by buildVisibleLinesTree()
|
|
* @see #buildVisibleLinesTree(RayData)
|
|
* @return All ray paths from origin to destnation
|
|
*/
|
|
private Vector<RayPath> getConnectingPaths(Point2D origin, Point2D dest, DefaultMutableTreeNode visibleLinesTree) {
|
|
Vector<RayPath> allPaths = new Vector<RayPath>();
|
|
|
|
// Analyse the possible paths to find which actually reached destination
|
|
Enumeration treeEnum = visibleLinesTree.breadthFirstEnumeration();
|
|
while (treeEnum.hasMoreElements()) {
|
|
// For every element,
|
|
// check if it is the origin, a diffraction, refraction or a reflection source
|
|
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) treeEnum.nextElement();
|
|
RayData rayData = (RayData) treeNode.getUserObject();
|
|
Point2D sourcePoint = rayData.getSourcePoint();
|
|
Line2D line = rayData.getLine();
|
|
RayData.RayType type = rayData.getType();
|
|
|
|
Line2D pseudoSourceToDest = new Line2D.Double(sourcePoint, dest);
|
|
boolean directPathExists = false;
|
|
Point2D justBeforeDestination = null;
|
|
|
|
// Get ray path point just before destination (if path exists at all)
|
|
if (type == RayData.RayType.ORIGIN) {
|
|
|
|
// Check if direct path exists
|
|
justBeforeDestination = sourcePoint;
|
|
|
|
if (!getParameterBooleanValue(Parameter.rt_disallow_direct_path)) {
|
|
directPathExists = isDirectPath(justBeforeDestination, dest);
|
|
} else {
|
|
directPathExists = false;
|
|
}
|
|
|
|
} else if (type == RayData.RayType.REFRACTION && pseudoSourceToDest.intersectsLine(line)) {
|
|
|
|
// Destination is inside refraction interval
|
|
justBeforeDestination = getIntersectionPoint(pseudoSourceToDest, line);
|
|
|
|
// Check if direct path exists (but ignore when leaving obstacle)
|
|
directPathExists = isDirectPath(justBeforeDestination, dest);
|
|
|
|
} else if (type == RayData.RayType.REFLECTION && pseudoSourceToDest.intersectsLine(line)) {
|
|
|
|
// Destination is inside reflection interval
|
|
justBeforeDestination = getIntersectionPoint(pseudoSourceToDest, line);
|
|
|
|
// Check if direct path exists (ignore reflection line)
|
|
directPathExists = isDirectPath(justBeforeDestination, dest);
|
|
|
|
} else if (type == RayData.RayType.DIFFRACTION) {
|
|
|
|
// Check if direct path exists (travelling through object not allowed
|
|
justBeforeDestination = sourcePoint;
|
|
directPathExists = isDirectPath(justBeforeDestination, dest);
|
|
|
|
}
|
|
|
|
// If a direct path exists, traverse up tree to find entire path
|
|
if (directPathExists) {
|
|
|
|
// Create new empty ray path
|
|
boolean pathBroken = false;
|
|
RayPath currentPath = new RayPath();
|
|
|
|
// Add those parts we already know
|
|
currentPath.addPoint(dest, RayData.RayType.DESTINATION);
|
|
currentPath.addPoint(justBeforeDestination, type);
|
|
|
|
Point2D lastPoint = dest;
|
|
Point2D newestPoint = justBeforeDestination;
|
|
|
|
// Check that this ray subpath is long enough to be considered
|
|
if (newestPoint.distance(lastPoint) < 0.01 && type != RayData.RayType.ORIGIN) {
|
|
pathBroken = true;
|
|
}
|
|
|
|
// Subpath must be double-direct if from diffraction
|
|
if (type == RayData.RayType.DIFFRACTION && !isDirectPath(lastPoint, newestPoint)) {
|
|
pathBroken = true;
|
|
}
|
|
|
|
// Data used when traversing path
|
|
DefaultMutableTreeNode currentlyTracedNode = treeNode;
|
|
RayData currentlyTracedRayData = (RayData) currentlyTracedNode.getUserObject();
|
|
RayData.RayType currentlyTracedNodeType = currentlyTracedRayData.getType();
|
|
Point2D currentlyTracedSource = currentlyTracedRayData.getSourcePoint();
|
|
Line2D currentlyTracedLine = currentlyTracedRayData.getLine();
|
|
|
|
|
|
// Traverse upwards until origin found
|
|
while (!pathBroken && currentlyTracedNodeType != RayData.RayType.ORIGIN) {
|
|
|
|
// Update new ray data
|
|
currentlyTracedNode = (DefaultMutableTreeNode) currentlyTracedNode.getParent();
|
|
currentlyTracedRayData = (RayData) currentlyTracedNode.getUserObject();
|
|
currentlyTracedNodeType = currentlyTracedRayData.getType();
|
|
currentlyTracedSource = currentlyTracedRayData.getSourcePoint();
|
|
currentlyTracedLine = currentlyTracedRayData.getLine();
|
|
|
|
if (currentlyTracedNodeType == RayData.RayType.ORIGIN) {
|
|
// We finally found the path origin, path ends here
|
|
lastPoint = newestPoint;
|
|
newestPoint = origin;
|
|
|
|
currentPath.addPoint(newestPoint, currentlyTracedNodeType);
|
|
|
|
// Check that this ray subpath is long enough to be considered
|
|
if (newestPoint.distance(lastPoint) < 0.01) {
|
|
pathBroken = true;
|
|
}
|
|
|
|
} else {
|
|
// Trace further up in the tree
|
|
|
|
if (currentlyTracedNodeType == RayData.RayType.REFRACTION || currentlyTracedNodeType == RayData.RayType.REFLECTION) {
|
|
// Traced tree element is a reflection/refraction - get intersection point and keep climbing
|
|
lastPoint = newestPoint;
|
|
|
|
Line2D newToOldIntersection = new Line2D.Double(currentlyTracedSource, lastPoint);
|
|
newestPoint = getIntersectionPointInfinite(newToOldIntersection, currentlyTracedLine);
|
|
|
|
} else {
|
|
// Traced tree element is a diffraction - save point and keep climbing
|
|
lastPoint = newestPoint;
|
|
newestPoint = currentlyTracedSource;
|
|
}
|
|
|
|
currentPath.addPoint(newestPoint, currentlyTracedNodeType);
|
|
|
|
// Check that this ray subpath is long enough to be considered
|
|
if (newestPoint == null || lastPoint == null || newestPoint.distance(lastPoint) < 0.01) {
|
|
pathBroken = true;
|
|
}
|
|
}
|
|
|
|
// Subpath must be double-direct if from diffraction
|
|
if (currentlyTracedNodeType == RayData.RayType.DIFFRACTION && !isDirectPath(lastPoint, newestPoint)) {
|
|
pathBroken = true;
|
|
}
|
|
|
|
if (pathBroken) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Save ray path
|
|
if (!pathBroken) {
|
|
allPaths.add(currentPath);
|
|
|
|
// Stop here if no other paths should be considered
|
|
if (type == RayData.RayType.ORIGIN && getParameterBooleanValue(Parameter.rt_ignore_non_direct)) {
|
|
return allPaths;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return allPaths;
|
|
}
|
|
|
|
/**
|
|
* True if a line drawn from the given source and given destination does
|
|
* not intersect with any obstacle outer lines in the current obstacle world.
|
|
* This method only checks for intersection with the obstacles lines "visible"
|
|
* from source. Hence, if source is inside an obstacle, that obstacles will
|
|
* not cause this method to return false. (Note that method is not symmetric)
|
|
*
|
|
* @param source Source
|
|
* @param dest Destination
|
|
* @return True if no obstacles between source and destination
|
|
*/
|
|
private boolean isDirectPath(Point2D source, Point2D dest) {
|
|
Line2D sourceToDest = new Line2D.Double(source, dest);
|
|
|
|
// Get angle
|
|
double deltaX = dest.getX() - source.getX();
|
|
double deltaY = dest.getY() - source.getY();
|
|
double angleSourceToDest = Math.atan2(deltaY, deltaX);
|
|
|
|
// Get all visible sides near angle
|
|
Vector<Line2D> visibleSides = getAllVisibleSides(
|
|
source.getX(),
|
|
source.getY(),
|
|
new AngleInterval(angleSourceToDest - 0.1, angleSourceToDest + 0.1),
|
|
null
|
|
);
|
|
|
|
// Check for intersections
|
|
if (visibleSides != null) {
|
|
for (int i=0; i < visibleSides.size(); i++) {
|
|
if (visibleSides.get(i).intersectsLine(sourceToDest)) {
|
|
// Check that intersection point is not destination
|
|
Point2D intersectionPoint = getIntersectionPointInfinite(visibleSides.get(i), sourceToDest);
|
|
if (dest.distance(intersectionPoint) > 0.01) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the Fast fading factor (in dB), which depends on
|
|
* the multiple paths from source to destination via reflections
|
|
* on registered obstacles.
|
|
* TODO Only first-order multipath...
|
|
*
|
|
* @param sourceX Transmitter X coordinate
|
|
* @param sourceY Transmitter Y coordinate
|
|
* @param destX Receiver X coordinate
|
|
* @param destY Receiver Y coordinate
|
|
* @return Slow fading factor
|
|
*/
|
|
protected double getFastFading(double sourceX, double sourceY, double destX, double destY) {
|
|
Point2D dest = new Point2D.Double(destX, destY);
|
|
Point2D source = new Point2D.Double(sourceX, sourceY);
|
|
|
|
// Destination inside an obstacle? => no reflection factor
|
|
for (int i=0; i < myObstacleWorld.getNrObstacles(); i++) {
|
|
if (myObstacleWorld.getObstacle(i).contains(dest)) {
|
|
//logger.debug("Destination inside obstacle, aborting fast fading");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns all possible diffraction sources, by checking which
|
|
* of the endpoints of the given visible lines that are on a corner
|
|
* of a obstacle structure.
|
|
*
|
|
* @param allVisibleLines Lines which may hold diffraction sources
|
|
* @return All diffraction sources
|
|
*/
|
|
private Vector<Point2D> getAllDiffractionSources(Vector<Line2D> allVisibleLines) {
|
|
Vector<Point2D> allDiffractionSources = new Vector<Point2D>();
|
|
Enumeration<Line2D> allVisibleLinesEnum = allVisibleLines.elements();
|
|
|
|
while (allVisibleLinesEnum.hasMoreElements()) {
|
|
Line2D visibleLine = allVisibleLinesEnum.nextElement();
|
|
|
|
// Check both end points of line for possible diffraction point
|
|
if (myObstacleWorld.pointIsNearCorner(visibleLine.getP1())) {
|
|
allDiffractionSources.add(visibleLine.getP1());
|
|
}
|
|
if (myObstacleWorld.pointIsNearCorner(visibleLine.getP2())) {
|
|
allDiffractionSources.add(visibleLine.getP2());
|
|
}
|
|
}
|
|
|
|
return allDiffractionSources;
|
|
}
|
|
|
|
/**
|
|
* Return all obstacle sides visible from given source when looking
|
|
* in the given angle interval.
|
|
* The sides may partly be shadowed by other obstacles.
|
|
* If the angle interval is null, it will be regarded as the entire interval
|
|
* If the line argument is non-null, all returned lines will be on the far side
|
|
* of this line, as if one was looking through that line.
|
|
*
|
|
* @param sourceX Source X
|
|
* @param sourceY Source Y
|
|
* @param angleInterval Angle interval (or null)
|
|
* @param lookThrough Line to look through (or null)
|
|
* @return All visible sides
|
|
*/
|
|
private Vector<Line2D> getAllVisibleSides(double sourceX, double sourceY, AngleInterval angleInterval, Line2D lookThrough) {
|
|
Point2D source = new Point2D.Double(sourceX, sourceY);
|
|
|
|
// Check if results were already calculated earlier
|
|
for (int i=0; i < calculatedVisibleSidesSources.size(); i++) {
|
|
if (
|
|
// Compare sources
|
|
source.equals(calculatedVisibleSidesSources.get(i)) &&
|
|
|
|
// Compare angle intervals
|
|
(angleInterval == calculatedVisibleSidesAngleIntervals.get(i) ||
|
|
angleInterval != null && angleInterval.equals(calculatedVisibleSidesAngleIntervals.get(i)) ) &&
|
|
|
|
// Compare lines
|
|
(lookThrough == calculatedVisibleSidesLines.get(i) ||
|
|
lookThrough != null && lookThrough.equals(calculatedVisibleSidesLines.get(i)) )
|
|
) {
|
|
// Move to top of list
|
|
Point2D oldSource = calculatedVisibleSidesSources.remove(i);
|
|
Line2D oldLine = calculatedVisibleSidesLines.remove(i);
|
|
AngleInterval oldAngleInterval = calculatedVisibleSidesAngleIntervals.remove(i);
|
|
Vector<Line2D> oldVisibleLines = calculatedVisibleSides.remove(i);
|
|
|
|
calculatedVisibleSidesSources.add(0, oldSource);
|
|
calculatedVisibleSidesLines.add(0, oldLine);
|
|
calculatedVisibleSidesAngleIntervals.add(0, oldAngleInterval);
|
|
calculatedVisibleSides.add(0, oldVisibleLines);
|
|
|
|
// Return old results
|
|
return oldVisibleLines;
|
|
}
|
|
}
|
|
|
|
Vector<Line2D> visibleLines = new Vector<Line2D>();
|
|
Vector<AngleInterval> unhandledAngles = new Vector<AngleInterval>();
|
|
|
|
if (lookThrough != null) {
|
|
if (angleInterval == null) {
|
|
unhandledAngles.add(AngleInterval.getAngleIntervalOfLine(source, lookThrough));
|
|
} else {
|
|
unhandledAngles.add(AngleInterval.getAngleIntervalOfLine(source, lookThrough).intersectWith(angleInterval));
|
|
}
|
|
} else {
|
|
if (angleInterval == null) {
|
|
unhandledAngles.add(new AngleInterval(0, 2*Math.PI));
|
|
} else {
|
|
unhandledAngles.add(angleInterval);
|
|
}
|
|
}
|
|
|
|
// Do forever (will break when no more unhandled angles exist)
|
|
while (!unhandledAngles.isEmpty()) {
|
|
|
|
// While unhandled angles still exist, keep searching for visible lines
|
|
while (!unhandledAngles.isEmpty()) {
|
|
//logger.info("Beginning of while-loop, unhandled angles left = " + unhandledAngles.size());
|
|
AngleInterval angleIntervalToCheck = unhandledAngles.firstElement();
|
|
|
|
// Check that interval is not empty or "infinite small"
|
|
if (angleIntervalToCheck == null || angleIntervalToCheck.isEmpty()) {
|
|
//logger.info("Angle interval (almost) empty, ignoring");
|
|
unhandledAngles.remove(angleIntervalToCheck);
|
|
break;
|
|
}
|
|
|
|
// <<<< Get visible obstacle candidates inside this angle interval >>>>
|
|
Vector<Rectangle2D> visibleObstacleCandidates =
|
|
myObstacleWorld.getAllObstaclesInAngleInterval(source, angleIntervalToCheck);
|
|
|
|
//logger.info("Obstacle candidates count = " + visibleObstacleCandidates.size());
|
|
if (visibleObstacleCandidates.isEmpty()) {
|
|
//logger.info("Visible obstacles candidates empty");
|
|
unhandledAngles.remove(angleIntervalToCheck);
|
|
break; // Restart without this angle
|
|
}
|
|
|
|
// <<<< Get visible line candidates of these obstacles >>>>
|
|
Vector<Line2D> visibleLineCandidates = new Vector<Line2D>();
|
|
for (int i=0; i < visibleObstacleCandidates.size(); i++) {
|
|
Rectangle2D obstacle = visibleObstacleCandidates.get(i);
|
|
int outcode = obstacle.outcode(source);
|
|
|
|
if ((outcode & Rectangle2D.OUT_BOTTOM) != 0) {
|
|
visibleLineCandidates.add(
|
|
new Line2D.Double(obstacle.getMinX(), obstacle.getMaxY(), obstacle.getMaxX(), obstacle.getMaxY()));
|
|
}
|
|
|
|
if ((outcode & Rectangle2D.OUT_TOP) != 0) {
|
|
visibleLineCandidates.add(
|
|
new Line2D.Double(obstacle.getMinX(), obstacle.getMinY(), obstacle.getMaxX(), obstacle.getMinY()));
|
|
}
|
|
|
|
if ((outcode & Rectangle2D.OUT_LEFT) != 0) {
|
|
visibleLineCandidates.add(
|
|
new Line2D.Double(obstacle.getMinX(), obstacle.getMinY(), obstacle.getMinX(), obstacle.getMaxY()));
|
|
}
|
|
|
|
if ((outcode & Rectangle2D.OUT_RIGHT) != 0) {
|
|
visibleLineCandidates.add(
|
|
new Line2D.Double(obstacle.getMaxX(), obstacle.getMinY(), obstacle.getMaxX(), obstacle.getMaxY()));
|
|
}
|
|
}
|
|
//logger.info("Line candidates count = " + visibleLineCandidates.size());
|
|
if (visibleLineCandidates.isEmpty()) {
|
|
//logger.info("Visible line candidates empty");
|
|
unhandledAngles.remove(angleIntervalToCheck);
|
|
break; // Restart without this angle
|
|
}
|
|
|
|
// <<<< Get cropped visible line candidates of these lines >>>>
|
|
Vector<Line2D> croppedVisibleLineCandidates = new Vector<Line2D>();
|
|
for (int i=0; i < visibleLineCandidates.size(); i++) {
|
|
Line2D lineCandidate = visibleLineCandidates.get(i);
|
|
|
|
// Create angle interval of this line
|
|
AngleInterval lineAngleInterval = AngleInterval.getAngleIntervalOfLine(source, lineCandidate);
|
|
|
|
AngleInterval intersectionInterval = null;
|
|
|
|
// Add entire line if it is fully inside our visible angle interval
|
|
if (angleIntervalToCheck.contains(lineAngleInterval)) {
|
|
|
|
if (lookThrough != null) {
|
|
// Check if the candidate is "equal" to the see through line
|
|
if (Math.abs(lineCandidate.getX1() - lookThrough.getX1()) +
|
|
Math.abs(lineCandidate.getY1() - lookThrough.getY1()) +
|
|
Math.abs(lineCandidate.getX2() - lookThrough.getX2()) +
|
|
Math.abs(lineCandidate.getY2() - lookThrough.getY2()) < 0.01) {
|
|
// See through line and candidate line are the same - skip this candidate
|
|
}
|
|
|
|
// Check if the candidate is on our side of the see through line
|
|
else if (new Line2D.Double(
|
|
lineCandidate.getBounds2D().getCenterX(),
|
|
lineCandidate.getBounds2D().getCenterY(),
|
|
sourceX,
|
|
sourceY
|
|
).intersectsLine(lookThrough)) {
|
|
croppedVisibleLineCandidates.add(lineCandidate);
|
|
} // else Skip line
|
|
} else {
|
|
croppedVisibleLineCandidates.add(lineCandidate);
|
|
}
|
|
|
|
}
|
|
|
|
// Add part of line if it is partly inside our visible angle interval
|
|
else if ((intersectionInterval = lineAngleInterval.intersectWith(angleIntervalToCheck)) != null) {
|
|
|
|
// Get lines towards the visible segment
|
|
Line2D lineToStartAngle = AngleInterval.getDirectedLine(
|
|
source,
|
|
intersectionInterval.getStartAngle(),
|
|
1.0
|
|
);
|
|
Line2D lineToEndAngle = AngleInterval.getDirectedLine(
|
|
source,
|
|
intersectionInterval.getEndAngle(),
|
|
1.0
|
|
);
|
|
|
|
// Calculate intersection points
|
|
Point2D intersectionStart = getIntersectionPointInfinite(
|
|
lineCandidate,
|
|
lineToStartAngle
|
|
);
|
|
Point2D intersectionEnd = getIntersectionPointInfinite(
|
|
lineCandidate,
|
|
lineToEndAngle
|
|
);
|
|
|
|
if (
|
|
intersectionStart != null &&
|
|
intersectionEnd != null &&
|
|
intersectionStart.distance(intersectionEnd) > 0.001 // Rounding error limit (1 mm)
|
|
) {
|
|
|
|
Line2D newCropped = new Line2D.Double(intersectionStart, intersectionEnd);
|
|
|
|
if (lookThrough != null) {
|
|
// Check if the candidate is "equal" to the see through line
|
|
if (Math.abs(newCropped.getX1() - lookThrough.getX1()) +
|
|
Math.abs(newCropped.getY1() - lookThrough.getY1()) +
|
|
Math.abs(newCropped.getX2() - lookThrough.getX2()) +
|
|
Math.abs(newCropped.getY2() - lookThrough.getY2()) < 0.01) {
|
|
// See through line and candidate line are the same - skip this candidate
|
|
}
|
|
|
|
// Check if the candidate is on our side of the see through line
|
|
else if (new Line2D.Double(
|
|
newCropped.getBounds2D().getCenterX(),
|
|
newCropped.getBounds2D().getCenterY(),
|
|
sourceX,
|
|
sourceY
|
|
).intersectsLine(lookThrough)) {
|
|
croppedVisibleLineCandidates.add(newCropped);
|
|
} // else Skip line
|
|
} else {
|
|
croppedVisibleLineCandidates.add(newCropped);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Skip line completely if not in our visible angle interval
|
|
else {
|
|
}
|
|
}
|
|
//logger.info("Cropped line candidates count = " + croppedVisibleLineCandidates.size());
|
|
if (croppedVisibleLineCandidates.isEmpty()) {
|
|
//logger.info("Cropped visible line candidates empty");
|
|
unhandledAngles.remove(angleIntervalToCheck);
|
|
break; // Restart without this angle
|
|
}
|
|
|
|
// <<<< Get visible lines from these line candidates >>>>
|
|
for (int i=0; i < croppedVisibleLineCandidates.size(); i++) {
|
|
Line2D visibleLineCandidate = croppedVisibleLineCandidates.get(i);
|
|
AngleInterval visibleLineCandidateAngleInterval =
|
|
AngleInterval.getAngleIntervalOfLine(source, visibleLineCandidate).intersectWith(angleIntervalToCheck);
|
|
|
|
//logger.info("Incoming angle interval " + angleIntervalToCheck);
|
|
//logger.info(". => line interval " + visibleLineCandidateAngleInterval);
|
|
|
|
// Area to test for shadowing objects
|
|
GeneralPath testArea = new GeneralPath();
|
|
testArea.moveTo((float) sourceX, (float) sourceY);
|
|
testArea.lineTo((float) visibleLineCandidate.getX1(), (float) visibleLineCandidate.getY1());
|
|
testArea.lineTo((float) visibleLineCandidate.getX2(), (float) visibleLineCandidate.getY2());
|
|
testArea.closePath();
|
|
|
|
// Does any other line shadow this line?
|
|
boolean unshadowed = true;
|
|
boolean unhandledAnglesChanged = false;
|
|
for (int j=0; j < croppedVisibleLineCandidates.size(); j++) {
|
|
|
|
// Create shadow rectangle
|
|
Line2D shadowLineCandidate = croppedVisibleLineCandidates.get(j);
|
|
Rectangle2D shadowRectangleCandidate = shadowLineCandidate.getBounds2D();
|
|
double minDelta = 0.01*Math.max(
|
|
shadowRectangleCandidate.getWidth(),
|
|
shadowRectangleCandidate.getHeight()
|
|
);
|
|
shadowRectangleCandidate.add(
|
|
shadowRectangleCandidate.getCenterX() + minDelta,
|
|
shadowRectangleCandidate.getCenterY() + minDelta
|
|
);
|
|
|
|
// Find the shortest of the two
|
|
double shadowDistance =
|
|
shadowLineCandidate.getP1().distance(source) +
|
|
shadowLineCandidate.getP2().distance(source);
|
|
|
|
double visibleDistance =
|
|
visibleLineCandidate.getP1().distance(source) +
|
|
visibleLineCandidate.getP2().distance(source);
|
|
|
|
double shadowCloseDistance =
|
|
Math.min(
|
|
shadowLineCandidate.getP1().distance(source),
|
|
shadowLineCandidate.getP2().distance(source));
|
|
|
|
double visibleFarDistance =
|
|
Math.max(
|
|
visibleLineCandidate.getP1().distance(source),
|
|
visibleLineCandidate.getP2().distance(source));
|
|
|
|
// Does shadow rectangle intersect test area?
|
|
if (visibleLineCandidate != shadowLineCandidate &&
|
|
testArea.intersects(shadowRectangleCandidate) &&
|
|
shadowCloseDistance <= visibleFarDistance) {
|
|
|
|
// Shadow line candidate seems to shadow (part of) our visible candidate
|
|
AngleInterval shadowLineCandidateAngleInterval =
|
|
AngleInterval.getAngleIntervalOfLine(source, shadowLineCandidate).intersectWith(angleIntervalToCheck);
|
|
|
|
if (shadowLineCandidateAngleInterval.contains(visibleLineCandidateAngleInterval)) {
|
|
// Covers us entirely, do nothing
|
|
|
|
// Special case, both shadow and visible candidate have the same interval
|
|
if (visibleLineCandidateAngleInterval.contains(shadowLineCandidateAngleInterval)) {
|
|
|
|
if (visibleDistance > shadowDistance) {
|
|
unshadowed = false;
|
|
break;
|
|
}
|
|
} else {
|
|
unshadowed = false;
|
|
break;
|
|
}
|
|
|
|
} else if (visibleLineCandidateAngleInterval.intersects(shadowLineCandidateAngleInterval)) {
|
|
// Covers us partly, split angle interval
|
|
Vector<AngleInterval> newIntervalsToAdd = new Vector<AngleInterval>();
|
|
|
|
// Create angle interval of intersection between shadow and visible candidate
|
|
AngleInterval intersectedInterval =
|
|
visibleLineCandidateAngleInterval.intersectWith(shadowLineCandidateAngleInterval);
|
|
if (intersectedInterval != null) {
|
|
Vector<AngleInterval> tempVector1 =
|
|
AngleInterval.intersect(unhandledAngles, intersectedInterval);
|
|
|
|
if (tempVector1 != null) {
|
|
for (int k=0; k < tempVector1.size(); k++) {
|
|
if (tempVector1.get(k) != null && !tempVector1.get(k).isEmpty()) {
|
|
newIntervalsToAdd.add(tempVector1.get(k));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add angle interval of visible candidate without shadow candidate
|
|
Vector<AngleInterval> tempVector2 =
|
|
visibleLineCandidateAngleInterval.subtract(shadowLineCandidateAngleInterval);
|
|
if (tempVector2 != null) {
|
|
for (int k=0; k < tempVector2.size(); k++) {
|
|
if (tempVector2.get(k) != null && !tempVector2.get(k).isEmpty()) {
|
|
newIntervalsToAdd.addAll(AngleInterval.intersect(unhandledAngles, tempVector2.get(k)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subtract angle interval of visible candidate
|
|
unhandledAngles = AngleInterval.subtract(unhandledAngles, visibleLineCandidateAngleInterval);
|
|
unhandledAnglesChanged = true;
|
|
|
|
// Add new angle intervals
|
|
//logger.info("Split angle interval: " + visibleLineCandidateAngleInterval);
|
|
for (int k=0; k < newIntervalsToAdd.size(); k++) {
|
|
if (newIntervalsToAdd.get(k) != null && !newIntervalsToAdd.get(k).isEmpty()) {
|
|
//logger.info("> into: " + newIntervalsToAdd.get(k));
|
|
unhandledAngles.add(newIntervalsToAdd.get(k));
|
|
unhandledAnglesChanged = true;
|
|
}
|
|
}
|
|
|
|
unshadowed = false;
|
|
break;
|
|
} else {
|
|
// Not intersecting after all, just ignore this
|
|
}
|
|
}
|
|
|
|
if (!unshadowed) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (unhandledAnglesChanged) {
|
|
//logger.info("Unhandled angles changed, restarting..");
|
|
break;
|
|
}
|
|
|
|
if (unshadowed) {
|
|
// No other lines shadow this line => this line must be visible!
|
|
|
|
unhandledAngles = AngleInterval.subtract(unhandledAngles, visibleLineCandidateAngleInterval);
|
|
visibleLines.add(visibleLineCandidate);
|
|
|
|
//logger.info("Added visible line and removed angle interval: " + visibleLineCandidateAngleInterval);
|
|
//logger.info("Number of visible lines sofar: " + visibleLines.size());
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // End of outer loop
|
|
|
|
// Save results in order to speed up later calculations
|
|
int size = calculatedVisibleSides.size();
|
|
// Crop saved sides vectors
|
|
if (size >= maxSavedVisibleSides) {
|
|
calculatedVisibleSides.remove(size-1);
|
|
calculatedVisibleSidesSources.remove(size-1);
|
|
calculatedVisibleSidesAngleIntervals.remove(size-1);
|
|
calculatedVisibleSidesLines.remove(size-1);
|
|
}
|
|
|
|
calculatedVisibleSides.add(0, visibleLines);
|
|
calculatedVisibleSidesSources.add(0, source);
|
|
calculatedVisibleSidesAngleIntervals.add(0, angleInterval);
|
|
calculatedVisibleSidesLines.add(0, lookThrough);
|
|
|
|
return visibleLines;
|
|
}
|
|
|
|
/**
|
|
* Calculates and returns the received signal strength (dBm) of a signal sent
|
|
* from the given source position to the given destination position as a
|
|
* random variable. This method uses current parameters such as transmitted
|
|
* power, obstacles, overall system loss etc.
|
|
*
|
|
* @param sourceX
|
|
* Source position X
|
|
* @param sourceY
|
|
* Source position Y
|
|
* @param destX
|
|
* Destination position X
|
|
* @param destY
|
|
* Destination position Y
|
|
* @return Received signal strength (dBm) random variable. The first value is
|
|
* the random variable mean, and the second is the variance.
|
|
*/
|
|
public double[] getReceivedSignalStrength(TxPair txPair) {
|
|
return getTransmissionData(txPair, TransmissionData.SIGNAL_STRENGTH);
|
|
}
|
|
|
|
|
|
// TODO Fix better data type support
|
|
private double[] getTransmissionData(TxPair txPair, TransmissionData dataType) {
|
|
Point2D source = txPair.getFrom();
|
|
Point2D dest = txPair.getTo();
|
|
double accumulatedVariance = 0;
|
|
|
|
// - Get all ray paths from source to destination -
|
|
RayData originRayData = new RayData(
|
|
RayData.RayType.ORIGIN,
|
|
source,
|
|
null,
|
|
getParameterIntegerValue(Parameter.rt_max_rays),
|
|
getParameterIntegerValue(Parameter.rt_max_refractions),
|
|
getParameterIntegerValue(Parameter.rt_max_reflections),
|
|
getParameterIntegerValue(Parameter.rt_max_diffractions)
|
|
);
|
|
|
|
// Check if origin tree is already calculated and saved
|
|
DefaultMutableTreeNode visibleLinesTree = buildVisibleLinesTree(originRayData);
|
|
|
|
// Calculate all paths from source to destination, using above calculated tree
|
|
Vector<RayPath> allPaths = getConnectingPaths(source, dest, visibleLinesTree);
|
|
|
|
if (logMode) {
|
|
logInfo.append("Signal components:\n");
|
|
Enumeration<RayPath> pathsEnum = allPaths.elements();
|
|
while (pathsEnum.hasMoreElements()) {
|
|
RayPath currentPath = pathsEnum.nextElement();
|
|
logInfo.append("* " + currentPath + "\n");
|
|
for (int i=0; i < currentPath.getSubPathCount(); i++) {
|
|
loggedRays.add(currentPath.getSubPath(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
// - Extract length and losses of each path -
|
|
double[] pathLengths = new double[allPaths.size()];
|
|
double[] pathGain = new double[allPaths.size()];
|
|
int bestSignalNr = -1;
|
|
double bestSignalPathLoss = 0;
|
|
for (int i=0; i < allPaths.size(); i++) {
|
|
RayPath currentPath = allPaths.get(i);
|
|
double accumulatedStraightLength = 0;
|
|
|
|
for (int j=0; j < currentPath.getSubPathCount(); j++) {
|
|
Line2D subPath = currentPath.getSubPath(j);
|
|
double subPathLength = subPath.getP1().distance(subPath.getP2());
|
|
RayData.RayType subPathStartType = currentPath.getType(j);
|
|
|
|
// Type specific losses
|
|
// TODO Type specific losses depends on angles as well!
|
|
if (subPathStartType == RayData.RayType.REFRACTION) {
|
|
pathGain[i] += getParameterDoubleValue(Parameter.rt_refrac_coefficient);
|
|
} else if (subPathStartType == RayData.RayType.REFLECTION) {
|
|
pathGain[i] += getParameterDoubleValue(Parameter.rt_reflec_coefficient);
|
|
|
|
// Add FSPL from last subpaths (if FSPL on individual rays)
|
|
if (!getParameterBooleanValue(Parameter.rt_fspl_on_total_length) && accumulatedStraightLength > 0) {
|
|
pathGain[i] += getFSPL(accumulatedStraightLength);
|
|
}
|
|
accumulatedStraightLength = 0; // Reset straight length
|
|
} else if (subPathStartType == RayData.RayType.DIFFRACTION) {
|
|
pathGain[i] += getParameterDoubleValue(Parameter.rt_diffr_coefficient);
|
|
|
|
// Add FSPL from last subpaths (if FSPL on individual rays)
|
|
if (!getParameterBooleanValue(Parameter.rt_fspl_on_total_length) && accumulatedStraightLength > 0) {
|
|
pathGain[i] += getFSPL(accumulatedStraightLength);
|
|
}
|
|
accumulatedStraightLength = 0; // Reset straight length
|
|
}
|
|
accumulatedStraightLength += subPathLength; // Add length, FSPL should be calculated on total straight length
|
|
|
|
// If ray starts with a refraction, calculate obstacle attenuation
|
|
if (subPathStartType == RayData.RayType.REFRACTION) {
|
|
// Ray passes through a wall, calculate distance through that wall
|
|
|
|
// Fetch attenuation constant
|
|
double attenuationConstant = getParameterDoubleValue(Parameter.obstacle_attenuation);
|
|
|
|
Vector<Rectangle2D> allPossibleObstacles = myObstacleWorld.getAllObstaclesNear(subPath.getP1());
|
|
|
|
for (int k=0; k < allPossibleObstacles.size(); k++) {
|
|
Rectangle2D obstacle = allPossibleObstacles.get(k);
|
|
|
|
// Calculate the intersection distance
|
|
Line2D line = getIntersectionLine(
|
|
subPath.getP1().getX(),
|
|
subPath.getP1().getY(),
|
|
subPath.getP2().getX(),
|
|
subPath.getP2().getY(),
|
|
obstacle
|
|
);
|
|
|
|
if (line != null) {
|
|
pathGain[i] += attenuationConstant * line.getP1().distance(line.getP2());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to total path length
|
|
pathLengths[i] += subPathLength;
|
|
}
|
|
|
|
// Add FSPL from last rays (if FSPL on individual rays)
|
|
if (!getParameterBooleanValue(Parameter.rt_fspl_on_total_length) && accumulatedStraightLength > 0) {
|
|
pathGain[i] += getFSPL(accumulatedStraightLength);
|
|
}
|
|
|
|
// Free space path loss on total path length?
|
|
if (getParameterBooleanValue(Parameter.rt_fspl_on_total_length)) {
|
|
pathGain[i] += getFSPL(pathLengths[i]);
|
|
}
|
|
|
|
if (bestSignalNr < 0 || pathGain[i] > bestSignalPathLoss) {
|
|
bestSignalNr = i;
|
|
bestSignalPathLoss = pathGain[i];
|
|
}
|
|
}
|
|
|
|
// - Calculate total path loss (using simple Rician) -
|
|
double[] pathModdedLengths = new double[allPaths.size()];
|
|
double delaySpread = 0;
|
|
double delaySpreadRMS = 0;
|
|
double freq = getParameterDoubleValue(Parameter.frequency);
|
|
double wavelength = C/(freq*1000000d);
|
|
double totalPathGain = 0;
|
|
double delaySpreadTotalWeight = 0;
|
|
double speedOfLight = 300; // Approximate value (m/us)
|
|
for (int i=0; i < pathModdedLengths.length; i++) {
|
|
// Ignore insignificant interfering signals
|
|
if (pathGain[i] > pathGain[bestSignalNr] - 30) {
|
|
double pathLengthDiff = Math.abs(pathLengths[i] - pathLengths[bestSignalNr]);
|
|
|
|
// Update delay spread TODO Now considering best signal, should be first or mean?
|
|
if (pathLengthDiff > delaySpread) {
|
|
delaySpread = pathLengthDiff;
|
|
}
|
|
|
|
|
|
// Update root-mean-square delay spread TODO Now considering best signal time, should be mean delay?
|
|
delaySpreadTotalWeight += pathGain[i]*pathGain[i];
|
|
double rmsDelaySpreadComponent = pathLengthDiff/speedOfLight;
|
|
rmsDelaySpreadComponent *= rmsDelaySpreadComponent * pathGain[i]*pathGain[i];
|
|
delaySpreadRMS += rmsDelaySpreadComponent;
|
|
|
|
// OK since cosinus is even function
|
|
pathModdedLengths[i] = pathLengthDiff % wavelength;
|
|
|
|
// Using Rician fading approach, TODO Only one best signal considered - combine these? (need two limits)
|
|
totalPathGain += Math.pow(10, pathGain[i]/10.0)*Math.cos(2*Math.PI * pathModdedLengths[i]/wavelength);
|
|
if (logMode) {
|
|
logInfo.append("Signal component: " + String.format("%2.3f", pathGain[i]) + " dB, phase " + String.format("%2.3f", (2*/*Math.PI* */ pathModdedLengths[i]/wavelength)) + " pi\n");
|
|
}
|
|
} else if (logMode) {
|
|
/* TODO Log mode affects result? */
|
|
pathModdedLengths[i] = (pathLengths[i] - pathLengths[bestSignalNr]) % wavelength;
|
|
logInfo.append("(IGNORED) Signal component: " + String.format("%2.3f", pathGain[i]) + " dB, phase " + String.format("%2.3f", (2*/*Math.PI* */ pathModdedLengths[i]/wavelength)) + " pi\n");
|
|
}
|
|
|
|
}
|
|
|
|
// Calculate resulting RMS delay spread
|
|
delaySpread /= speedOfLight;
|
|
delaySpreadRMS /= delaySpreadTotalWeight;
|
|
|
|
|
|
// Convert back to dB
|
|
totalPathGain = 10*Math.log10(Math.abs(totalPathGain));
|
|
|
|
if (logMode) {
|
|
logInfo.append("\nTotal path gain: " + String.format("%2.3f", totalPathGain) + " dB\n");
|
|
logInfo.append("Delay spread: " + String.format("%2.3f", delaySpread) + "\n");
|
|
logInfo.append("RMS delay spread: " + String.format("%2.3f", delaySpreadRMS) + "\n");
|
|
}
|
|
|
|
// - Calculate received power -
|
|
// Using formula (dB)
|
|
// Received power = Output power + System gain + Transmitter gain + Path Loss + Receiver gain
|
|
// TODO Update formulas
|
|
double outputPower = txPair.getTxPower();
|
|
double systemGain = getParameterDoubleValue(Parameter.system_gain_mean);
|
|
if (getParameterBooleanValue(Parameter.apply_random)) {
|
|
Random random = new Random(); /* TODO Use main random generator? */
|
|
systemGain += Math.sqrt(getParameterDoubleValue(Parameter.system_gain_var)) * random.nextGaussian();
|
|
} else {
|
|
accumulatedVariance += getParameterDoubleValue(Parameter.system_gain_var);
|
|
}
|
|
|
|
double transmitterGain = 0;
|
|
if (getParameterBooleanValue(Parameter.tx_with_gain)) {
|
|
transmitterGain = txPair.getTxGain();
|
|
}
|
|
|
|
double receivedPower = outputPower + systemGain + transmitterGain + totalPathGain;
|
|
if (logMode) {
|
|
logInfo.append("\nReceived signal strength: " + String.format("%2.3f", receivedPower) + " dB (variance " + accumulatedVariance + ")\n");
|
|
}
|
|
|
|
if (dataType == TransmissionData.DELAY_SPREAD || dataType == TransmissionData.DELAY_SPREAD_RMS) {
|
|
return new double[] {delaySpread, delaySpreadRMS};
|
|
}
|
|
|
|
return new double[] {receivedPower, accumulatedVariance};
|
|
}
|
|
|
|
public class TrackedSignalComponents {
|
|
ArrayList<Line2D> components;
|
|
String log;
|
|
}
|
|
|
|
/**
|
|
* Returns all rays from given source to given destination if a transmission
|
|
* were to be made. The resulting rays depend on the current settings and may
|
|
* include rays through obstacles, reflected rays or scattered rays.
|
|
*
|
|
* @param sourceX Source position X
|
|
* @param sourceY Source position Y
|
|
* @param destX Destination position X
|
|
* @param destY Destination position Y
|
|
* @return Signal components and printable description
|
|
*/
|
|
public TrackedSignalComponents getRaysOfTransmission(TxPair txPair) {
|
|
TrackedSignalComponents tsc = new TrackedSignalComponents();
|
|
|
|
logInfo = new StringBuilder();
|
|
loggedRays = new ArrayList<Line2D>();
|
|
|
|
/* TODO Include background noise? */
|
|
logMode = true;
|
|
getProbability(txPair, -Double.MAX_VALUE);
|
|
logMode = false;
|
|
|
|
tsc.log = logInfo.toString();
|
|
tsc.components = loggedRays;
|
|
|
|
logInfo = null;
|
|
loggedRays = null;
|
|
|
|
return tsc;
|
|
}
|
|
|
|
/**
|
|
* Calculates and returns the signal to noise ratio (dB) of a signal sent from
|
|
* the given source position to the given destination position as a random
|
|
* variable. This method uses current parameters such as transmitted power,
|
|
* obstacles, overall system loss etc.
|
|
*
|
|
* @param sourceX Source position X
|
|
* @param sourceY Source position Y
|
|
* @param destX Destination position X
|
|
* @param destY Destination position Y
|
|
* @return Received SNR (dB) random variable:
|
|
* The first value in the array is the random variable mean.
|
|
* The second is the variance.
|
|
* The third value is the received signal strength which may be used in comparison with interference etc.
|
|
*/
|
|
public double[] getSINR(TxPair txPair, double interference) {
|
|
/* TODO Cache values: called repeatedly with noise sources. */
|
|
|
|
// Calculate received signal strength
|
|
double[] signalStrength = getReceivedSignalStrength(txPair);
|
|
double[] snrData = new double[] { signalStrength[0], signalStrength[1], signalStrength[0] };
|
|
|
|
// Add antenna gain
|
|
if (getParameterBooleanValue(Parameter.rx_with_gain)) {
|
|
snrData[0] += txPair.getRxGain();
|
|
}
|
|
|
|
double noiseVariance = getParameterDoubleValue(Parameter.bg_noise_var);
|
|
double noiseMean = getParameterDoubleValue(Parameter.bg_noise_mean);
|
|
|
|
if (interference > noiseMean) {
|
|
noiseMean = interference;
|
|
}
|
|
|
|
if (getParameterBooleanValue(Parameter.apply_random)) {
|
|
Random random = new Random(); /* TODO Use main random generator? */
|
|
noiseMean += Math.sqrt(noiseVariance) * random.nextGaussian();
|
|
noiseVariance = 0;
|
|
}
|
|
|
|
// Applying noise to calculate SNR
|
|
snrData[0] -= noiseMean;
|
|
snrData[1] += noiseVariance;
|
|
|
|
if (logMode) {
|
|
logInfo.append("\nReceived SNR: " + String.format("%2.3f", snrData[0]) + " dB (variance " + snrData[1] + ")\n");
|
|
}
|
|
return snrData;
|
|
}
|
|
|
|
|
|
/**
|
|
* Calculates probability that a receiver at given destination receives
|
|
* a packet from a transmitter at given source.
|
|
* This method uses current parameters such as transmitted power,
|
|
* obstacles, overall system loss, packet size etc.
|
|
*
|
|
* TODO Packet size
|
|
* TODO External interference/Background noise
|
|
*
|
|
* @param sourceX Source position X
|
|
* @param sourceY Source position Y
|
|
* @param destX Destination position X
|
|
* @param destY Destination position Y
|
|
* @param interference Current interference at destination (dBm)
|
|
* @return [Probability of reception, signal strength at destination]
|
|
*/
|
|
public double[] getProbability(TxPair txPair, double interference) {
|
|
double[] snrData = getSINR(txPair, interference);
|
|
double snrMean = snrData[0];
|
|
double snrVariance = snrData[1];
|
|
double signalStrength = snrData[2];
|
|
double threshold = getParameterDoubleValue(Parameter.snr_threshold);
|
|
double rxSensitivity = getParameterDoubleValue(Parameter.rx_sensitivity);
|
|
|
|
// Check signal strength against receiver sensitivity and interference
|
|
if (rxSensitivity > signalStrength - snrMean &&
|
|
threshold < rxSensitivity + snrMean - signalStrength) {
|
|
if (logMode) {
|
|
logInfo.append("Weak signal: increasing threshold\n");
|
|
}
|
|
|
|
// Keeping snr variance but increasing theshold to sensitivity
|
|
threshold = rxSensitivity + snrMean - signalStrength;
|
|
}
|
|
|
|
// If not random varianble, probability is either 1 or 0
|
|
if (snrVariance == 0) {
|
|
return new double[] {
|
|
threshold - snrMean > 0 ? 0:1, signalStrength
|
|
};
|
|
}
|
|
double snrStdDev = Math.sqrt(snrVariance);
|
|
|
|
|
|
// "Missing" signal strength in order to receive packet is probability that
|
|
// random variable with mean snrMean and standard deviance snrStdDev is above
|
|
// current threshold.
|
|
|
|
// (Using error algorithm method, much faster than taylor approximation!)
|
|
double probReception = 1 - GaussianWrapper.cdfErrorAlgo(threshold, snrMean, snrStdDev);
|
|
|
|
if (logMode) {
|
|
logInfo.append("Reception probability: " + String.format("%1.1f%%", 100*probReception) + "\n");
|
|
}
|
|
|
|
// Returns probabilities
|
|
return new double[] { probReception, signalStrength };
|
|
}
|
|
|
|
/**
|
|
* Calculates and returns root-mean-square delay spread when given destination receives a packet from a transmitter at given source.
|
|
* This method uses current parameters such as transmitted power,
|
|
* obstacles, overall system loss, packet size etc. TODO Packet size?!
|
|
*
|
|
* @param sourceX
|
|
* Source position X
|
|
* @param sourceY
|
|
* Source position Y
|
|
* @param destX
|
|
* Destination position X
|
|
* @param destY
|
|
* Destination position Y
|
|
* @return RMS delay spread
|
|
*/
|
|
public double getRMSDelaySpread(TxPair tx) {
|
|
return getTransmissionData(tx, TransmissionData.DELAY_SPREAD)[1];
|
|
}
|
|
|
|
/**
|
|
* Returns XML elements representing the current configuration.
|
|
*
|
|
* @see #setConfigXML(Collection)
|
|
* @return XML element collection
|
|
*/
|
|
public Collection<Element> getConfigXML() {
|
|
ArrayList<Element> config = new ArrayList<Element>();
|
|
Element element;
|
|
|
|
Enumeration<Parameter> paramEnum = parameters.keys();
|
|
while (paramEnum.hasMoreElements()) {
|
|
Parameter p = (Parameter) paramEnum.nextElement();
|
|
element = new Element(p.toString());
|
|
if (parametersDefaults.get(p).equals(parameters.get(p))) {
|
|
/* Default value */
|
|
continue;
|
|
}
|
|
element.setAttribute("value", parameters.get(p).toString());
|
|
config.add(element);
|
|
}
|
|
|
|
element = new Element("obstacles");
|
|
element.addContent(myObstacleWorld.getConfigXML());
|
|
config.add(element);
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Sets the configuration depending on the given XML elements.
|
|
*
|
|
* @see #getConfigXML()
|
|
* @param configXML
|
|
* Config XML elements
|
|
* @return True if config was set successfully, false otherwise
|
|
*/
|
|
public boolean setConfigXML(Collection<Element> configXML) {
|
|
for (Element element : configXML) {
|
|
if (element.getName().equals("obstacles")) {
|
|
myObstacleWorld = new ObstacleWorld();
|
|
myObstacleWorld.setConfigXML(element.getChildren());
|
|
} else /* Parameter values */ {
|
|
String name = element.getName();
|
|
String value;
|
|
Parameter param = null;
|
|
|
|
if (name.equals("wavelength")) {
|
|
/* Backwards compatability: ignored parameters */
|
|
value = element.getAttributeValue("value");
|
|
if (value == null) {
|
|
value = element.getText();
|
|
}
|
|
// private static final double C = 299792458; /* m/s */
|
|
double frequency = C/Double.parseDouble(value);
|
|
frequency /= 1000000.0; /* mhz */
|
|
parameters.put(Parameter.frequency, frequency); /* mhz */
|
|
|
|
logger.warn("MRM parameter converted from wavelength to frequency: " + String.format("%1.1f MHz", frequency));
|
|
continue;
|
|
} else if (name.equals("tx_antenna_gain") || name.equals("rx_antenna_gain")) {
|
|
logger.warn("MRM parameter \"" + name + "\" was removed");
|
|
continue;
|
|
} else if (Parameter.fromString(name) != null) {
|
|
/* Backwards compatability: renamed parameters */
|
|
param = Parameter.fromString(name);
|
|
} else {
|
|
param = Parameter.valueOf(name);
|
|
}
|
|
|
|
value = element.getAttributeValue("value");
|
|
if (value == null || value.isEmpty()) {
|
|
/* Backwards compatability: renamed parameters */
|
|
value = element.getText();
|
|
}
|
|
|
|
Class<?> paramClass = parameters.get(param).getClass();
|
|
if (paramClass == Double.class) {
|
|
parameters.put(param, new Double(Double.parseDouble(value)));
|
|
} else if (paramClass == Boolean.class) {
|
|
parameters.put(param, Boolean.parseBoolean(value));
|
|
} else if (paramClass == Integer.class) {
|
|
parameters.put(param, Integer.parseInt(value));
|
|
} else {
|
|
logger.fatal("Unsupported class type: " + paramClass);
|
|
}
|
|
}
|
|
}
|
|
needToPrecalculateFSPL = true;
|
|
needToPrecalculateOutputPower = true;
|
|
settingsObservable.notifySettingsChanged();
|
|
return true;
|
|
}
|
|
|
|
public static abstract class TxPair {
|
|
public abstract double getFromX();
|
|
public abstract double getFromY();
|
|
public abstract double getToX();
|
|
public abstract double getToY();
|
|
public abstract double getTxPower();
|
|
|
|
public double getDistance() {
|
|
double w = getFromX() - getToX();
|
|
double h = getFromY() - getToY();
|
|
return Math.sqrt(w*w+h*h);
|
|
}
|
|
|
|
/**
|
|
* @return Radians
|
|
*/
|
|
public double getAngle() {
|
|
return Math.atan2(getToY()-getFromY(), getToX()-getFromX());
|
|
}
|
|
public Point2D getFrom() {
|
|
return new Point2D.Double(getFromX(), getFromY());
|
|
}
|
|
public Point2D getTo() {
|
|
return new Point2D.Double(getToX(), getToY());
|
|
}
|
|
|
|
/**
|
|
* @return Relative transmitter gain (zero for omnidirectional radios)
|
|
*/
|
|
public abstract double getTxGain();
|
|
|
|
/**
|
|
* @return Relative receiver gain (zero for omnidirectional radios)
|
|
*/
|
|
public abstract double getRxGain();
|
|
}
|
|
public static abstract class RadioPair extends TxPair {
|
|
public abstract Radio getFromRadio();
|
|
public abstract Radio getToRadio();
|
|
|
|
public double getDistance() {
|
|
double w = getFromX() - getToX();
|
|
double h = getFromY() - getToY();
|
|
return Math.sqrt(w*w+h*h);
|
|
}
|
|
public double getFromX() {
|
|
return getFromRadio().getPosition().getXCoordinate();
|
|
}
|
|
public double getFromY() {
|
|
return getFromRadio().getPosition().getYCoordinate();
|
|
}
|
|
public double getToX() {
|
|
return getToRadio().getPosition().getXCoordinate();
|
|
}
|
|
public double getToY() {
|
|
return getToRadio().getPosition().getYCoordinate();
|
|
}
|
|
public double getTxPower() {
|
|
return getFromRadio().getCurrentOutputPower();
|
|
}
|
|
public double getTxGain() {
|
|
if (!(getFromRadio() instanceof DirectionalAntennaRadio)) {
|
|
return 0;
|
|
}
|
|
DirectionalAntennaRadio r = (DirectionalAntennaRadio)getFromRadio();
|
|
double txGain = r.getRelativeGain(r.getDirection() + getAngle(), getAngle());
|
|
//logger.debug("tx gain: " + txGain + " (angle " + String.format("%1.1f", Math.toDegrees(r.getDirection() + getAngle())) + ")");
|
|
return txGain;
|
|
}
|
|
public double getRxGain() {
|
|
if (!(getToRadio() instanceof DirectionalAntennaRadio)) {
|
|
return 0;
|
|
}
|
|
DirectionalAntennaRadio r = (DirectionalAntennaRadio)getFromRadio();
|
|
double txGain = r.getRelativeGain(r.getDirection() + getAngle() + Math.PI, getDistance());
|
|
//logger.debug("rx gain: " + txGain + " (angle " + String.format("%1.1f", Math.toDegrees(r.getDirection() + getAngle() + Math.PI)) + ")");
|
|
return txGain;
|
|
}
|
|
}
|
|
|
|
}
|