diff --git a/tools/cooja/apps/arm/build.xml b/tools/cooja/apps/arm/build.xml new file mode 100644 index 000000000..67c9f1228 --- /dev/null +++ b/tools/cooja/apps/arm/build.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/cooja/apps/arm/cooja.config b/tools/cooja/apps/arm/cooja.config new file mode 100644 index 000000000..e5697df94 --- /dev/null +++ b/tools/cooja/apps/arm/cooja.config @@ -0,0 +1,2 @@ +se.sics.cooja.GUI.RADIOMEDIUMS = + se.sics.arm.AdvancedRadioMedium +se.sics.cooja.GUI.JARFILES = + arm.jar jfreechart-1.0.1.jar jcommon-1.0.0.jar diff --git a/tools/cooja/apps/arm/images/antenna.png b/tools/cooja/apps/arm/images/antenna.png new file mode 100644 index 000000000..f3d82e08e Binary files /dev/null and b/tools/cooja/apps/arm/images/antenna.png differ diff --git a/tools/cooja/apps/arm/java/se/sics/arm/AdvancedRadioMedium.java b/tools/cooja/apps/arm/java/se/sics/arm/AdvancedRadioMedium.java new file mode 100644 index 000000000..b3d963b8e --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/AdvancedRadioMedium.java @@ -0,0 +1,658 @@ +package se.sics.arm; + +import java.util.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; + +/** + * This is the main class of the COOJA Advanced Radio Medium (ARM) package. + * + * ARM is meant to be a replacement for the simpler radio mediums available in + * COOJA. It is packet based and uses a 2D ray-tracing approach to approximate + * signal strength attenuations between simulated radios. Currently the + * ray-tracing only supports reflections and refractions through homogeneous + * obstacles. + * + * ARM provides a number of plugins for example a plugin for visualizing radio + * environments, and a plugin for configuring the radio medium. + * + * When a radio transmits data the area of interference around it will be + * occupied for a time depending on the length of the packet sent. If the entire + * transmission is completed without any interference the packet will be + * delivered, otherwise nothing will be delivered. + * + * Future work includes adding diffractions and scattering support. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Advanced Radio Medium (ARM)") +public class AdvancedRadioMedium extends RadioMedium { + private static Logger logger = Logger.getLogger(AdvancedRadioMedium.class); + + private ChannelModel currentChannelModel = null; + private Observer simulationObserver = null; + private Simulation mySimulation = null; + + // Registered members of radio medium + private Vector registeredMembers = new Vector(); + + private Vector allTransmissions = new Vector(); + private Vector allTransfers = new Vector(); + private Vector allInterferences = new Vector(); + + + /** + * Notifies observers when this radio medium is starting or has finished a packet delivery. + */ + private TransmissionsObservable radioActivityObservable = new TransmissionsObservable(); + + /** + * Notifies observers when this radio medium has changed settings. + */ + private SettingsObservable settingsObservable = new SettingsObservable(); + + /** + * Listens to all registered radios, and fetches any new incoming radio packets. + */ + private Observer radioObserver = new Observer() { + public void update(Observable radio, Object obj) { + Radio sendingRadio = (Radio) radio; + if (sendingRadio.getLastEvent() != Radio.RadioEvent.TRANSMISSION_STARTED) + return; + + // Locate corresponding member + ARMMember sendingMember = null; + for (ARMMember member: registeredMembers) { + if (member.radio == radio) { + sendingMember = member; + break; + } + } + if (sendingMember == null) { + logger.fatal("ARM: Could not locate radio member - is radio registered? " + radio); + return; + } + + // Check that member is not receiving data + if (sendingMember.isListeningOnTransmission()) { + logger.fatal("ARM: Radio is trying to send data but is currently receiving! This must be fixed in Contiki!"); + return; + } + + // Check that member is not already sending data + for (RadioTransmission transmission: allTransmissions) { + if (transmission.source == sendingMember) { + logger.fatal("ARM: Radio is trying to send data but is already sending! This must be fixed in Contiki!"); + return; + } + } + + int transmissionEndTime = sendingRadio.getTransmissionEndTime(); + + // Create transmission + byte[] packetToSend = sendingRadio.getLastPacketTransmitted(); + RadioTransmission transmission = new RadioTransmission(sendingMember, transmissionEndTime, packetToSend); + allTransmissions.add(transmission); + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + + double sendingX = sendingMember.position.getXCoordinate(); + double sendingY = sendingMember.position.getYCoordinate(); + Random random = new Random(); + + // Calculate how the other radios will be affected by this packet + for (ARMMember member: registeredMembers) { + // Ignore this sending radio + if (member != sendingMember) { + double receivingX = member.position.getXCoordinate(); + double receivingY = member.position.getYCoordinate(); + + double[] probData = currentChannelModel.getProbability(sendingX, sendingY, receivingX, receivingY, -Double.MAX_VALUE); + + //logger.info("Probability of reception is " + probData[0]); + //logger.info("Signal strength at destination is " + probData[1]); + if (random.nextFloat() < probData[0]) { + // Connection successful (if not interfered later) + //logger.info("OK, creating connection and starting to transmit"); + tryCreateTransmission(transmission, member, probData[1]); + } else if (probData[1] > 100) { // TODO Impossible value, what should it be?! + // Transmission is only interference at destination + tryCreateInterference(transmission, member, probData[1]); + } else { + //logger.info("Signal to low to be considered interference"); + } + } + } + + } + }; + + + /** + * Creates a new Advanced Radio Medium (ARM). + */ + public AdvancedRadioMedium() { + + // Create the channel model + currentChannelModel = new ChannelModel(); + + // Register temporary plugins + logger.info("Registering ARM plugins"); + GUI.currentGUI.registerTemporaryPlugin(AreaViewer.class); + GUI.currentGUI.registerTemporaryPlugin(FormulaViewer.class); + } + + // -- Radio Medium standard methods -- + + public void registerMote(Mote mote, Simulation sim) { + registerRadioInterface(mote.getInterfaces().getRadio(), mote.getInterfaces().getPosition(), sim); + } + + public void unregisterMote(Mote mote, Simulation sim) { + unregisterRadioInterface(mote.getInterfaces().getRadio(), sim); + } + + public void registerRadioInterface(Radio radio, Position position, Simulation sim) { + if (radio == null || position == null) { + logger.fatal("Could not register radio: " + radio + " @ " + position); + return; + } + + // If we are not already tick observering simulation, we should be + if (simulationObserver == null) { + mySimulation = sim; + simulationObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Check if any transmission is active in the radio medium + if (allTransmissions.isEmpty()) + return; + + Vector uncompletedTransmissions = new Vector(); + Vector completedTransmissions = new Vector(); + + // Check if any transmission has completed + for (RadioTransmission transmission: allTransmissions) { + if (transmission.isCompleted()) { + completedTransmissions.add(transmission); + } else { + uncompletedTransmissions.add(transmission); + } + } + + if (completedTransmissions.isEmpty()) + // Nothing to do + return; + + // At least one transmission has completed - deliver data for associated transfers + for (RadioTransmission transmission: completedTransmissions) { + // Unregister interferences of this transmission source + Vector intfToUnregister = new Vector(); + for (RadioInterference interference: allInterferences) { + if (interference.mySource == transmission) { + intfToUnregister.add(interference); + } + } + for (RadioInterference interference: intfToUnregister) + unregisterInterference(interference); + + // Deliver data and unregister transmission + Vector transToUnregister = new Vector(); + for (RadioTransfer transfer: allTransfers) { + if (transfer.mySource == transmission) { + if (!transfer.interferenceDestroyedConnection()) { + // Don't interfer connection + } else { + transfer.myDestination.radio.interferReception(0); + } + transToUnregister.add(transfer); + } + } + for (RadioTransfer transfer: transToUnregister) + unregisterTransmission(transfer); + } + + allTransmissions = uncompletedTransmissions; + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + + // Update all radio signal strengths + for (ARMMember member: registeredMembers) { + member.updateHeardSignalStrength(); + } + } + }; + + sim.addTickObserver(simulationObserver); + } + + // Save both radio and its position, and also register us as an observer to the radio + ARMMember member = new ARMMember(radio, position); + registeredMembers.add(member); + radio.addObserver(radioObserver); + radio.setCurrentSignalStrength(currentChannelModel.getParameterDoubleValue(("bg_noise_mean"))); + + // Settings have changed - notify observers + settingsObservable.notifySettingsChanged(); + } + + public void unregisterRadioInterface(Radio radioToRemove, Simulation sim) { + // Find corresponding radio member and remove it + ARMMember memberToRemove = null; + for (ARMMember member: registeredMembers) { + if (member.radio == radioToRemove) { + memberToRemove = member; + break; + } + } + + if (memberToRemove != null) { + registeredMembers.remove(memberToRemove); + } else + logger.warn("ARM: Could not unregister radio: " + radioToRemove); + + // Settings have changed - notify observers + settingsObservable.notifySettingsChanged(); + } + + public void addRadioMediumObserver(Observer observer) { + // Add radio traffic observer to this radio medium + radioActivityObservable.addObserver(observer); + } + + public void deleteRadioMediumObserver(Observer observer) { + // Remove observer from this radio medium + radioActivityObservable.deleteObserver(observer); + } + + public RadioConnection[] getLastTickConnections() { + logger.fatal("ARM: getLastTickConnections() not implemented"); + return null; + } + + public void setConnectionLogger(ConnectionLogger connection) { + logger.fatal("ARM: setConnectionLogger() not implemented"); + } + + public Collection getConfigXML() { + // Just forwarding to current channel model + return currentChannelModel.getConfigXML(); + } + + public boolean setConfigXML(Collection configXML) { + // Just forwarding to current channel model + return currentChannelModel.setConfigXML(configXML); + } + + + // -- Advanced Radio Medium specific methods -- + + /** + * Adds an observer which is notified when this radio medium has + * changed settings, such as added or removed radios. + * + * @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); + } + + /** + * Returns position of given radio. + * + * @param radio Registered radio + * @return Position of given radio + */ + public Position getRadioPosition(Radio radio) { + // Find radio, and return its position + for (ARMMember member: registeredMembers) { + if (member.radio == radio) { + return member.position; + } + } + logger.fatal("ARM: Given radio is not registered!"); + return null; + } + + /** + * @return Number of registered radios. + */ + public int getRegisteredRadioCount() { + return registeredMembers.size(); + } + + /** + * Returns radio at given index. + * + * @param index Index of registered radio. + * @return Radio at given index + */ + public Radio getRegisteredRadio(int index) { + return registeredMembers.get(index).radio; + } + + /** + * Returns the current channel model object, responsible for + * all probability and transmission calculations. + * + * @return Current channel model + */ + public ChannelModel getChannelModel() { + return currentChannelModel; + } + + + + /** + * Tries to create a new transmission between given transmission and + * destination. The given signal strength should be the incoming signal + * strength at the destination. This value will be used after the transmission + * is completed in order to compare the connection with any interference. + * + * @param source + * @param destination + * @param signalStrength + * @return + */ + public void tryCreateTransmission(RadioTransmission source, ARMMember destination, double signalStrength) { + // Check if destination is already listening to a connection + if (destination.isListeningOnTransmission()) { + RadioInterference newInterference = new RadioInterference(source, destination, signalStrength); + destination.heardInterferences.add(newInterference); + + registerInterference(newInterference); + return; + } + + // Create new transmission + RadioTransfer newTransmission = new RadioTransfer(source, destination, signalStrength); + destination.heardTransmission = newTransmission; + + registerTransmission(newTransmission); + } + + public void tryCreateInterference(RadioTransmission source, ARMMember destination, double signalStrength) { + RadioInterference newInterference = new RadioInterference(source, destination, signalStrength); + destination.heardInterferences.add(newInterference); + + registerInterference(newInterference); + } + + + /** + * ARM radio transmission. + */ + class RadioTransmission { + ARMMember source = null; + int endTime = 0; + byte[] dataToTransfer = null; + + public RadioTransmission(ARMMember source, int endTime, byte[] dataToTransfer) { + this.source = source; + + this.endTime = endTime; + + this.dataToTransfer = dataToTransfer; + } + + /** + * @return True if no longer transmitting. + */ + public boolean isCompleted() { + return mySimulation.getSimulationTime() >= endTime; + } + + + } + + /** + * ARM radio interference + */ + class RadioInterference { + RadioTransmission mySource; + ARMMember myDestination; + double interferenceSignalStrength; + + public RadioInterference(RadioTransmission transmission, ARMMember destination, double signalStrength) { + this.mySource = transmission; + this.myDestination = destination; + this.interferenceSignalStrength = signalStrength; + } + + /** + * @return True if interference is no more. + */ + public boolean isOld() { + return mySource.isCompleted(); + } + + } + + /** + * ARM radio transfers + */ + class RadioTransfer { + RadioTransmission mySource; + ARMMember myDestination; + double transmissionSignalStrength; + double maxInterferenceSignalStrength; + + public RadioTransfer(RadioTransmission source, ARMMember destination, double signalStrength) { + this.mySource = source; + this.myDestination = destination; + this.transmissionSignalStrength = signalStrength; + maxInterferenceSignalStrength = -Double.MAX_VALUE; + + destination.radio.receivePacket(source.dataToTransfer, source.endTime); + destination.radio.setCurrentSignalStrength(signalStrength); + } + + public void addInterference(double signalStrength) { + if (signalStrength > maxInterferenceSignalStrength) { + maxInterferenceSignalStrength = signalStrength; + } + myDestination.radio.setCurrentSignalStrength(Math.max( + maxInterferenceSignalStrength, transmissionSignalStrength)); + } + + /** + * @return True if transmission is completed. + */ + public boolean isOld() { + return mySource.isCompleted(); + } + + /** + * @return True if interference destroyed transmission + */ + public boolean interferenceDestroyedConnection() { + if (maxInterferenceSignalStrength + 30 > transmissionSignalStrength) { + // Recalculating probability of delivery + double[] probData = currentChannelModel.getProbability( + mySource.source.position.getXCoordinate(), + mySource.source.position.getYCoordinate(), + myDestination.position.getXCoordinate(), + myDestination.position.getYCoordinate(), + maxInterferenceSignalStrength); +// logger.info("Transfer was interfered, recalculating probability of success: " + probData[0]); + + if (new Random().nextFloat() >= probData[0]) { + return true; + } + } + + return false; + } + } + + /** + * Inner class used for keeping track transceivers. + */ + class ARMMember { + Radio radio = null; + Position position = null; + + private RadioTransfer heardTransmission; + private Vector heardInterferences = new Vector(); + double currentSignalStrength = -Double.MAX_VALUE; + + public ARMMember(Radio radio, Position position) { + this.radio = radio; + this.position = position; + } + + public boolean isListeningOnTransmission() { + if (heardTransmission == null) + return false; + + if (heardTransmission.isOld()) { + heardTransmission = null; + } + + return heardTransmission != null; + } + + /** + * Calculates current incoming signal strength at this radio. + * Observe, does not alter any transmissions! + */ + public void updateHeardSignalStrength() { + double maxSignalStrength = -Double.MAX_VALUE; + + // Get maximum interference and also update interference list + Vector newInterferences = new Vector(); + for (RadioInterference interference: heardInterferences) { + if (!interference.isOld()) { + newInterferences.add(interference); + maxSignalStrength = Math.max(maxSignalStrength, + interference.interferenceSignalStrength); + } + } + heardInterferences = newInterferences; + + if (heardTransmission != null && !heardTransmission.isOld()) { + maxSignalStrength = Math.max(maxSignalStrength, + heardTransmission.transmissionSignalStrength); + } else + heardTransmission = null; + + // Noise level + maxSignalStrength = Math.max(maxSignalStrength, currentChannelModel + .getParameterDoubleValue("bg_noise_mean")); + + currentSignalStrength = maxSignalStrength; + radio.setCurrentSignalStrength(currentSignalStrength); + } + + } + + + public void registerInterference(RadioInterference interference) { + allInterferences.add(interference); + + updateInterferences(); + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + } + + public void registerTransmission(RadioTransfer transmission) { + allTransfers.add(transmission); + + updateInterferences(); + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + } + + public void unregisterInterference(RadioInterference interference) { + updateInterferences(); + + allInterferences.remove(interference); + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + } + + public void unregisterTransmission(RadioTransfer transmission) { + updateInterferences(); + + allTransfers.remove(transmission); + radioActivityObservable.notifyRadioActivityChanged(); // Need to notify observers + } + + private void updateInterferences() { + // We need to check impact of interferences on transmissions + for (RadioTransfer transmission: allTransfers) { + for (RadioInterference interference: allInterferences) { + if (interference.myDestination == transmission.myDestination) { + transmission.addInterference(interference.interferenceSignalStrength); + } + } + } + } + + class TransmissionsObservable extends Observable { + private void notifyRadioActivityChanged() { + setChanged(); + notifyObservers(); + } + } + + class SettingsObservable extends Observable { + private void notifySettingsChanged() { + setChanged(); + notifyObservers(); + } + } + + /** + * @return Current active transmissions + */ + public Vector getCurrentTransmissions() { + return allTransmissions; + } + + /** + * @return Current active transmissions + */ + public RadioTransmission[] getCurrentTransmissionsArray() { + return allTransmissions.toArray(new RadioTransmission[0]); + } + + /** + * @return Current active interferences + */ + public Vector getCurrentInterferences() { + return allInterferences; + } + + /** + * @return Current active interferences + */ + public RadioInterference[] getCurrentInterferencesArray() { + return allInterferences.toArray(new RadioInterference[0]); + } + + /** + * @return Current active transfers + */ + public Vector getCurrentTransfers() { + return allTransfers; + } + + /** + * @return Current active transfers + */ + public RadioTransfer[] getCurrentTransfersArray() { + return allTransfers.toArray(new RadioTransfer[0]); + } + + + + +} diff --git a/tools/cooja/apps/arm/java/se/sics/arm/AngleInterval.java b/tools/cooja/apps/arm/java/se/sics/arm/AngleInterval.java new file mode 100644 index 000000000..bdd85ffa6 --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/AngleInterval.java @@ -0,0 +1,385 @@ +package se.sics.arm; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.util.Vector; + +import org.apache.log4j.Logger; + +/** + * This class represents an angle interval. + * + * @author Fredrik Osterlind + */ +class AngleInterval { + private static Logger logger = Logger.getLogger(AngleInterval.class); + + // Sub intervals all between 0 and 2*PI + Vector subIntervals; + + /** + * Creates a new angle interval. + * + * @param startAngle Start angle (rad) + * @param endAngle End angle (rad) (> start angle) + */ + public AngleInterval(double startAngle, double endAngle) { + subIntervals = new Vector(); + + if (endAngle < startAngle) { + + } else if (endAngle - startAngle >= 2*Math.PI) { + subIntervals.add(new Interval(0, 2*Math.PI)); + } else { + while (startAngle < 0) + startAngle += 2*Math.PI; + while (endAngle < 0) + endAngle += 2*Math.PI; + startAngle %= 2*Math.PI; + endAngle %= 2*Math.PI; + + Interval tempInterval; + if (startAngle < endAngle) { + tempInterval = new Interval(startAngle, endAngle); + if (!tempInterval.isEmpty()) + subIntervals.add(tempInterval); + } else { + tempInterval = new Interval(startAngle, 2*Math.PI); + if (!tempInterval.isEmpty()) + subIntervals.add(tempInterval); + tempInterval = new Interval(0, endAngle); + if (!tempInterval.isEmpty()) + subIntervals.add(tempInterval); + } + } + } + + /** + * Returns new intervals consisting of this interval with the given interval removed. + * These can either be null (if entire interval was removed), + * one interval (if upper or lower part, or nothing was removed) or two intervals + * (if middle part of interval was removed). + * + * @param intervalToSubtract Other interval + * @return New intervals + */ + public Vector subtract(AngleInterval intervalToSubtract) { + Vector afterSubtractionIntervals = new Vector(); + + // Before subtraction + afterSubtractionIntervals.addAll(subIntervals); + + if (intervalToSubtract == null) { + Vector ret = new Vector(); + ret.add(this); + return ret; + } + + // Subtract every subinterval each + for (int i=0; i < intervalToSubtract.subIntervals.size(); i++) { + Interval subIntervalToSubtract = intervalToSubtract.subIntervals.get(i); + Vector newAfterSubtractionIntervals = new Vector(); + + for (int j=0; j < afterSubtractionIntervals.size(); j++) { + Vector tempIntervals = afterSubtractionIntervals.get(j).subtract(subIntervalToSubtract); + if (tempIntervals != null) + newAfterSubtractionIntervals.addAll(tempIntervals); + } + + afterSubtractionIntervals = newAfterSubtractionIntervals; + } + + Vector newAngleIntervals = new Vector(); + for (int i=0; i < afterSubtractionIntervals.size(); i++) { + if (afterSubtractionIntervals.get(i) != null && !afterSubtractionIntervals.get(i).isEmpty()) + newAngleIntervals.add( + new AngleInterval(afterSubtractionIntervals.get(i).getLow(), afterSubtractionIntervals.get(i).getHigh()) + ); + } + + return newAngleIntervals; + } + + /** + * Returns the intersection of this interval with + * the given. + * + * @param interval Other interval + * @return Intersection + */ + public AngleInterval intersectWith(AngleInterval interval) { + Vector afterIntersectionIntervals = new Vector(); + + // Intersect all subintervals, keep all results + for (int i=0; i < interval.subIntervals.size(); i++) { + for (int j=0; j < subIntervals.size(); j++) { + Interval temp = interval.subIntervals.get(i).intersectWith(subIntervals.get(j)); + if (temp != null && !temp.isEmpty()) + afterIntersectionIntervals.add(temp); + } + } + + if (afterIntersectionIntervals.size() > 2) { + logger.fatal("AngleInterval.intersectWith() error!"); + } else if (afterIntersectionIntervals.size() == 2) { + + // The interval (y-x) is divided into: + // y -> 2*PI + // 0 -> x + Interval interval1 = afterIntersectionIntervals.get(0); + Interval interval2 = afterIntersectionIntervals.get(1); + + if (interval1.getLow() == 0) + return new AngleInterval( + interval2.getLow(), interval1.getHigh() + 2*Math.PI + ); + else + return new AngleInterval( + interval1.getLow(), interval2.getHigh() + 2*Math.PI + ); + + } else if (afterIntersectionIntervals.size() == 1) { + return new AngleInterval( + afterIntersectionIntervals.firstElement().getLow(), + afterIntersectionIntervals.firstElement().getHigh() + ); + } + + return null; + } + + /** + * Returns start angle of this interval. + * This angle is always > 0 and < the end angle. + * + * @return Start angle + */ + public double getStartAngle() { + if (subIntervals == null || subIntervals.isEmpty()) { + logger.warn("Getting start angle of null angle interval!"); + return 0; + } + + if (subIntervals.size() > 2) { + logger.warn("Getting start angle of malformed angle interval!"); + return 0; + } + + if (subIntervals.size() == 1) { + return subIntervals.firstElement().getLow(); + } + + // The interval (y-x) is divided into: + // y -> 2*PI + // 0 -> x + Interval interval1 = subIntervals.get(0); + Interval interval2 = subIntervals.get(1); + + if (interval1.getLow() == 0) + return interval2.getLow(); + else + return interval1.getLow(); + } + + /** + * Returns end angle of this interval. + * This angle is always > start angle, and may be > 2*PI. + * + * @return End angle + */ + public double getEndAngle() { + if (subIntervals == null || subIntervals.isEmpty()) { + logger.warn("Getting start angle of null angle interval!"); + return 0; + } + + if (subIntervals.size() > 2) { + logger.warn("Getting start angle of malformed angle interval!"); + return 0; + } + + if (subIntervals.size() == 1) { + return subIntervals.firstElement().getHigh(); + } + + // The interval (y-x) is divided into: + // y -> 2*PI + // 0 -> x + Interval interval1 = subIntervals.get(0); + Interval interval2 = subIntervals.get(1); + + if (interval1.getLow() == 0) + return interval1.getHigh() + 2*Math.PI; + else + return interval2.getHigh() + 2*Math.PI; + } + + /** + * @return Size of interval (rad) + */ + public double getSize() { + double size = 0; + for (int i=0; i < subIntervals.size(); i++) + size += subIntervals.get(i).getSize(); + + return size; + } + + /** + * Checks if the given interval is a subset of this interval. + * + * @param interval Other interval + * @return True if this interval contains given interval + */ + public boolean contains(AngleInterval interval) { + // Check that all parts of argument is contained by any part of this + for (int i=0; i < interval.subIntervals.size(); i++) { + boolean contained = false; + for (int j=0; j < subIntervals.size(); j++) { + if (subIntervals.get(j).contains(interval.subIntervals.get(i))) { + contained = true; + break; + } + } + if (!contained) + return false; + } + return true; + } + + /** + * Checks if the two intervals intersect. + * + * @param interval Other interval + * @return True if this interval intersects given interval + */ + public boolean intersects(AngleInterval interval) { + return (intersectWith(interval) != null); + } + + /** + * @return True if interval defined is of no size. + */ + public boolean isEmpty() { + if (subIntervals.isEmpty()) + return true; + if (getSize() <= 0.001) + return true; + return false; + } + + public String toString() { + String retString = ""; + for (int i=0; i < subIntervals.size(); i++) { + if (!retString.equals("")) + retString = retString.concat(" && "); + + retString = retString.concat("("); + retString = retString.concat(Math.toDegrees(subIntervals.get(i).getLow()) + " -> " + Math.toDegrees(subIntervals.get(i).getHigh())); + retString = retString.concat(")"); + } + + return retString; + } + + /** + * Returns a line starting at given start point and + * in the given direction. + * This line may be used when calculating intersection points. + * + * @param startPoint Start point + * @param angle Line direction (rad) + * @return Line + */ + public static Line2D getDirectedLine(Point2D startPoint, double angle, double length) { + return new Line2D.Double( + startPoint.getX(), + startPoint.getY(), + startPoint.getX() + length*Math.cos(angle), + startPoint.getY() + length*Math.sin(angle) + ); + } + + /** + * Returns an angle interval of the given line seen from + * the given reference point. + * + * @param refPoint Reference point + * @param line Line to measure angle against + * @return Angle interval (-pi <-> pi) + */ + public static AngleInterval getAngleIntervalOfLine(Point2D refPoint, Line2D line) { + // Create angle interval of this line + double x1 = line.getX1() - refPoint.getX(); + double y1 = line.getY1() - refPoint.getY(); + double x2 = line.getX2() - refPoint.getX(); + double y2 = line.getY2() - refPoint.getY(); + + double angle1 = Math.atan2(y1, x1); + double angle2 = Math.atan2(y2, x2); + + // If interval is bigger than PI, line angles must wrap + if (Math.abs(angle1 - angle2) > Math.PI) { + if (angle1 < 0) + angle1 += 2*Math.PI; + else + angle2 += 2*Math.PI; + } + + return new AngleInterval( + Math.min(angle1, angle2), Math.max(angle1, angle2) + ); + } + + public boolean equals(Object object) { + if (object == null) + return false; + + AngleInterval interval = (AngleInterval) object; + return (interval.getStartAngle() == this.getStartAngle() && interval.getEndAngle() == this.getEndAngle()); + } + + /** + * Subtracts given interval from all intervals in given vector. + * This method never returns null (but empty vectors). + * + * @param initialIntervals Initial intervals + * @param interval Interval to subtract + * @return New intervals + */ + public static Vector subtract(Vector initialIntervals, AngleInterval interval) { + Vector newIntervals = new Vector(); + + for (int i=0; i < initialIntervals.size(); i++) { + Vector tempIntervals = initialIntervals.get(i).subtract(interval); + if (tempIntervals != null) { + newIntervals.addAll(tempIntervals); + } + } + + return newIntervals; + } + + /** + * Intersects given interval with all intervals in given vector. + * This method never returns null (but empty vectors). + * + * @param initialIntervals Initial intervals + * @param interval Interval to intersect + * @return New intervals + */ + public static Vector intersect(Vector initialIntervals, AngleInterval interval) { + Vector newIntervals = new Vector(); + + for (int i=0; i < initialIntervals.size(); i++) { + AngleInterval tempInterval = initialIntervals.get(i).intersectWith(interval); + if (tempInterval != null) { + newIntervals.add(tempInterval); + } + } + + return newIntervals; + } + + +} diff --git a/tools/cooja/apps/arm/java/se/sics/arm/AreaViewer.java b/tools/cooja/apps/arm/java/se/sics/arm/AreaViewer.java new file mode 100644 index 000000000..7e9fac487 --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/AreaViewer.java @@ -0,0 +1,2177 @@ +package se.sics.arm; + +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.*; +import javax.swing.*; +import javax.swing.filechooser.FileFilter; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; +import se.sics.arm.AdvancedRadioMedium.*; + +/** + * The class AreaViewer belongs to the Advanced Radio Medium (ARM) package. + * + * It is used to visualize available radios, traffic between them as well + * as the current radio propagation area of single radios. + * Users may also add background images (such as maps) and color-analyze them + * in order to add simulated obstacles in the radio medium. + * + * For more information about ARM see @link AdvancedRadioMedium. + * + * @see AdvancedRadioMedium + * @author Fredrik Osterlind + */ +@ClassDescription("ARM - Area Viewer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class AreaViewer extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AreaViewer.class); + + private final JPanel canvas; + private final VisPlugin thisPlugin; + + ChannelModel.TransmissionData dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH; + ButtonGroup visTypeSelectionGroup; + + // General drawing parameters + private Point lastHandledPosition = new Point(0,0); + private double zoomCenterX = 0.0; + private double zoomCenterY = 0.0; + private Point zoomCenterPoint = new Point(); + private double currentZoomX = 1.0f; + private double currentZoomY = 1.0f; + private double currentPanX = 0.0f; + private double currentPanY = 0.0f; + + private boolean drawBackgroundImage = true; + private boolean drawCalculatedObstacles = true; + private boolean drawChannelProbabilities = true; + private boolean drawRadios = true; + private boolean drawRadioActivity = true; + private boolean drawScaleArrow = true; + + // Background drawing parameters (meters) + private double backgroundStartX = 0.0; + private double backgroundStartY = 0.0; + private double backgroundWidth = 0.0; + private double backgroundHeight = 0.0; + private Image backgroundImage = null; + private File backgroundImageFile = null; + + // Obstacle drawing parameters (same scale as background) + private boolean needToRepaintObstacleImage = false; + private double obstacleStartX = 0.0; + private double obstacleStartY = 0.0; + private double obstacleWidth = 0.0; + private double obstacleHeight = 0.0; + private Image obstacleImage = null; + + // Channel probabilities drawing parameters (meters) + private double channelStartX = 0.0; + private double channelStartY = 0.0; + private double channelWidth = 0.0; + private double channelHeight = 0.0; + private Image channelImage = null; + + private JSlider resolutionSlider; + private JPanel controlPanel; + private JScrollPane scrollControlPanel; + + private Simulation currentSimulation; + private AdvancedRadioMedium currentRadioMedium; + private ChannelModel currentChannelModel; + + private final String antennaImageFilename = "antenna.png"; + private final Image antennaImage; + + private Radio selectedRadio = null; + private boolean inSelectMode = true; + private boolean inTrackMode = false; + + private Vector trackedComponents = null; + + // Coloring variables + private JPanel coloringIntervalPanel = null; + private double coloringHighest = 0; + private double coloringLowest = 0; + private boolean coloringIsFixed = true; + + private Thread attenuatorThread = null; + + private JCheckBox showSettingsBox; + private JCheckBox backgroundCheckBox; + private JCheckBox obstaclesCheckBox; + private JCheckBox channelCheckBox; + private JCheckBox radiosCheckBox; + private JCheckBox radioActivityCheckBox; + private JCheckBox arrowCheckBox; + + /** + * Initializes an AreaViewer. + * + * @param simulationToVisualize Simulation using Advanced Radio Medium + */ + public AreaViewer(Simulation simulationToVisualize) { + super("Advanced Radio Medium - Area Viewer"); + + currentSimulation = simulationToVisualize; + currentRadioMedium = (AdvancedRadioMedium) currentSimulation.getRadioMedium(); + currentChannelModel = currentRadioMedium.getChannelModel(); + + // We want to listen to changes both in the channel model as well as in the radio medium + currentChannelModel.addSettingsObserver(channelModelSettingsObserver); + currentRadioMedium.addSettingsObserver(radioMediumSettingsObserver); + currentRadioMedium.addRadioMediumObserver(radioMediumActivityObserver); + + // Set initial size etc. + setSize(500, 500); + setVisible(true); + thisPlugin = this; + + // Canvas mode radio buttons + show settings checkbox + showSettingsBox = new JCheckBox ("settings", true); + showSettingsBox.setAlignmentY(Component.TOP_ALIGNMENT); + showSettingsBox.setContentAreaFilled(false); + showSettingsBox.setActionCommand("toggle show settings"); + showSettingsBox.addActionListener(canvasModeHandler); + + JRadioButton selectModeButton = new JRadioButton ("select"); + selectModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); + selectModeButton.setContentAreaFilled(false); + selectModeButton.setActionCommand("set select mode"); + selectModeButton.addActionListener(canvasModeHandler); + selectModeButton.setSelected(true); + + JRadioButton panModeButton = new JRadioButton ("pan"); + panModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); + panModeButton.setContentAreaFilled(false); + panModeButton.setActionCommand("set pan mode"); + panModeButton.addActionListener(canvasModeHandler); + + JRadioButton zoomModeButton = new JRadioButton ("zoom"); + zoomModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); + zoomModeButton.setContentAreaFilled(false); + zoomModeButton.setActionCommand("set zoom mode"); + zoomModeButton.addActionListener(canvasModeHandler); + + JRadioButton trackModeButton = new JRadioButton ("track rays"); + trackModeButton.setAlignmentY(Component.BOTTOM_ALIGNMENT); + trackModeButton.setContentAreaFilled(false); + trackModeButton.setActionCommand("set track rays mode"); + trackModeButton.addActionListener(canvasModeHandler); + + ButtonGroup group = new ButtonGroup(); + group.add(selectModeButton); + group.add(panModeButton); + group.add(zoomModeButton); + group.add(trackModeButton); + + // Create canvas + canvas = new JPanel() { + private static final long serialVersionUID = 1L; + public void paintComponent(Graphics g) { + super.paintComponent(g); + repaintCanvas((Graphics2D) g); + } + }; + canvas.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + canvas.setBackground(Color.WHITE); + canvas.setLayout(new BorderLayout()); + canvas.addMouseListener(canvasMouseHandler); + + // Create canvas mode panel + JPanel canvasModePanel = new JPanel(); + canvasModePanel.setOpaque(false); + canvasModePanel.setLayout(new BoxLayout(canvasModePanel, BoxLayout.Y_AXIS)); + canvasModePanel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + canvasModePanel.add(showSettingsBox); + canvasModePanel.add(Box.createVerticalGlue()); + canvasModePanel.add(selectModeButton); + canvasModePanel.add(panModeButton); + canvasModePanel.add(zoomModeButton); + canvasModePanel.add(trackModeButton); + canvas.add(BorderLayout.EAST, canvasModePanel); + + // Create control graphics panel + JPanel graphicsComponentsPanel = new JPanel(); + graphicsComponentsPanel.setLayout(new BoxLayout(graphicsComponentsPanel, BoxLayout.Y_AXIS)); + graphicsComponentsPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + graphicsComponentsPanel.setAlignmentX(Component.CENTER_ALIGNMENT); + + graphicsComponentsPanel.add(new JLabel("Show components:")); + + backgroundCheckBox = new JCheckBox("Background", true); + backgroundCheckBox.setActionCommand("toggle background"); + backgroundCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(backgroundCheckBox); + + obstaclesCheckBox = new JCheckBox("Obstacles", true); + obstaclesCheckBox.setActionCommand("toggle obstacles"); + obstaclesCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(obstaclesCheckBox); + + channelCheckBox = new JCheckBox("Channel probability", true); + channelCheckBox.setActionCommand("toggle channel"); + channelCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(channelCheckBox); + + radiosCheckBox = new JCheckBox("Radios", true); + radiosCheckBox.setActionCommand("toggle radios"); + radiosCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(radiosCheckBox); + + radioActivityCheckBox = new JCheckBox("Radio Activity", true); + radioActivityCheckBox.setActionCommand("toggle radio activity"); + radioActivityCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(radioActivityCheckBox); + + arrowCheckBox = new JCheckBox("Scale arrow", true); + arrowCheckBox.setActionCommand("toggle arrow"); + arrowCheckBox.addActionListener(selectGraphicsHandler); + graphicsComponentsPanel.add(arrowCheckBox); + + graphicsComponentsPanel.add(Box.createRigidArea(new Dimension(0,20))); + graphicsComponentsPanel.add(new JLabel("Configure attenuating obstacles:")); + + JButton addBackgroundButton = new JButton("Set background image"); + addBackgroundButton.setActionCommand("set background image"); + addBackgroundButton.addActionListener(setBackgroundHandler); + graphicsComponentsPanel.add(addBackgroundButton); + + JButton analyzeObstaclesButton = new JButton("Analyze background for obstacles"); + analyzeObstaclesButton.setActionCommand("analyze for obstacles"); + analyzeObstaclesButton.addActionListener(analyzeObstaclesHandler); + graphicsComponentsPanel.add(analyzeObstaclesButton); + + // Create visualize channel output panel + JPanel visualizeChannelPanel = new JPanel(); + visualizeChannelPanel.setLayout(new BoxLayout(visualizeChannelPanel, BoxLayout.Y_AXIS)); + visualizeChannelPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + visualizeChannelPanel.setAlignmentX(Component.CENTER_ALIGNMENT); + + // Channel coloring intervals + visualizeChannelPanel.add(new JLabel("Color intervals:")); + + JRadioButton fixedColoringButton = new JRadioButton("Fixed channel coloring"); + fixedColoringButton.setSelected(true); + fixedColoringButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + coloringIsFixed = true; + } + }); + visualizeChannelPanel.add(fixedColoringButton); + + JRadioButton relativeColoringButton = new JRadioButton("Relative channel coloring"); + relativeColoringButton.setSelected(true); + relativeColoringButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + coloringIsFixed = false; + } + }); + visualizeChannelPanel.add(relativeColoringButton); + + ButtonGroup coloringGroup = new ButtonGroup(); + coloringGroup.add(fixedColoringButton); + coloringGroup.add(relativeColoringButton); + + coloringIntervalPanel = new JPanel() { + public void paintComponent(Graphics g) { + super.paintComponent(g); + + int width = getWidth(); + int height = getHeight(); + double diff = coloringHighest - coloringLowest; + int textHeight = g.getFontMetrics().getHeight(); + + // If computing + if (attenuatorThread != null && attenuatorThread.isAlive()) { + g.setColor(Color.WHITE); + g.fillRect(0, 0, width, height); + g.setColor(Color.BLACK); + String stringToDraw = "[calculating]"; + g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); + return; + } + + // Check for infinite values + if (Double.isInfinite(coloringHighest) || Double.isInfinite(coloringLowest)) { + g.setColor(Color.WHITE); + g.fillRect(0, 0, width, height); + g.setColor(Color.BLACK); + String stringToDraw = "INFINITE VALUES EXIST"; + g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); + return; + } + + // Check if values are constant + if (diff == 0) { + g.setColor(Color.WHITE); + g.fillRect(0, 0, width, height); + g.setColor(Color.BLACK); + NumberFormat formatter = DecimalFormat.getNumberInstance(); + String stringToDraw = "CONSTANT VALUES (" + formatter.format(coloringHighest) + ")"; + g.drawString(stringToDraw, width/2 - g.getFontMetrics().stringWidth(stringToDraw)/2, height/2 + textHeight/2); + return; + } + + for (int i=0; i < width; i++) { + double paintValue = coloringLowest + (double) i / (double) width * diff; + g.setColor( + new Color( + getColorOfSignalStrength(paintValue, coloringLowest, coloringHighest))); + + g.drawLine(i, 0, i, height); + } + + if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { + NumberFormat formatter = DecimalFormat.getPercentInstance(); + g.setColor(Color.BLACK); + g.drawString(formatter.format(coloringLowest), 3, textHeight); + String stringToDraw = formatter.format(coloringHighest); + g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH || + dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR ) { + NumberFormat formatter = DecimalFormat.getNumberInstance(); + g.setColor(Color.BLACK); + g.drawString(formatter.format(coloringLowest) + "dBm", 3, textHeight); + String stringToDraw = formatter.format(coloringHighest) + "dBm"; + g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR || + dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { + NumberFormat formatter = DecimalFormat.getNumberInstance(); + g.setColor(Color.BLACK); + g.drawString(formatter.format(coloringLowest) + "dB", 3, textHeight); + String stringToDraw = formatter.format(coloringHighest) + "dB"; + g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { + NumberFormat formatter = DecimalFormat.getNumberInstance(); + g.setColor(Color.BLACK); + g.drawString(formatter.format(coloringLowest) + "us", 3, textHeight); + String stringToDraw = formatter.format(coloringHighest) + "us"; + g.drawString(stringToDraw, width - g.getFontMetrics().stringWidth(stringToDraw) - 3, textHeight); + } + + } + }; + coloringIntervalPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + Dimension colorPanelSize = new Dimension(200, 20); + coloringIntervalPanel.setPreferredSize(colorPanelSize); + coloringIntervalPanel.setMinimumSize(colorPanelSize); + coloringIntervalPanel.setMaximumSize(colorPanelSize); + coloringIntervalPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + visualizeChannelPanel.add(coloringIntervalPanel); + + // Choose channel output to visualize + visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); + visualizeChannelPanel.add(new JLabel("Visualize radio output:")); + + JRadioButton signalStrengthButton = new JRadioButton("Signal strength"); + signalStrengthButton.setActionCommand("signalStrengthButton"); + signalStrengthButton.setSelected(true); + signalStrengthButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH; + } + }); + visualizeChannelPanel.add(signalStrengthButton); + + JRadioButton signalStrengthVarButton = new JRadioButton("Signal strength variance"); + signalStrengthVarButton.setActionCommand("signalStrengthVarButton"); + signalStrengthVarButton.setSelected(false); + signalStrengthVarButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR; + } + }); + visualizeChannelPanel.add(signalStrengthVarButton); + + JRadioButton SNRButton = new JRadioButton("Signal to Noise ratio"); + SNRButton.setActionCommand("SNRButton"); + SNRButton.setSelected(false); + SNRButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.SNR; + } + }); + visualizeChannelPanel.add(SNRButton); + + JRadioButton SNRVarButton = new JRadioButton("Signal to Noise variance"); + SNRVarButton.setActionCommand("SNRVarButton"); + SNRVarButton.setSelected(false); + SNRVarButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.SNR_VAR; + } + }); + visualizeChannelPanel.add(SNRVarButton); + + JRadioButton probabilityButton = new JRadioButton("Probability of reception"); + probabilityButton.setActionCommand("probabilityButton"); + probabilityButton.setSelected(false); + probabilityButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.PROB_OF_RECEPTION; + } + }); + visualizeChannelPanel.add(probabilityButton); + + JRadioButton rmsDelaySpreadButton = new JRadioButton("RMS delay spread"); + rmsDelaySpreadButton.setActionCommand("rmsDelaySpreadButton"); + rmsDelaySpreadButton.setSelected(false); + rmsDelaySpreadButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dataTypeToVisualize = ChannelModel.TransmissionData.DELAY_SPREAD_RMS; + } + }); + visualizeChannelPanel.add(rmsDelaySpreadButton); + + visTypeSelectionGroup = new ButtonGroup(); + visTypeSelectionGroup.add(signalStrengthButton); + visTypeSelectionGroup.add(signalStrengthVarButton); + visTypeSelectionGroup.add(SNRButton); + visTypeSelectionGroup.add(SNRVarButton); + visTypeSelectionGroup.add(probabilityButton); + visTypeSelectionGroup.add(rmsDelaySpreadButton); + + visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); + + visualizeChannelPanel.add(new JLabel("Image resolution:")); + + resolutionSlider = new JSlider(JSlider.HORIZONTAL, 30, 600, 200); + resolutionSlider.setMajorTickSpacing(100); + resolutionSlider.setPaintTicks(true); + resolutionSlider.setPaintLabels(true); + visualizeChannelPanel.add(resolutionSlider); + + visualizeChannelPanel.add(Box.createRigidArea(new Dimension(0,20))); + + JButton recalculateVisibleButton = new JButton("Recalculate visible area"); + recalculateVisibleButton.setActionCommand("recalculate visible area"); + recalculateVisibleButton.addActionListener(formulaHandler); + visualizeChannelPanel.add(recalculateVisibleButton); + + // Create control panel + controlPanel = new JPanel(); + controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); + controlPanel.add(graphicsComponentsPanel); + controlPanel.add(new JSeparator()); + controlPanel.add(Box.createRigidArea(new Dimension(0, 5))); + controlPanel.add(visualizeChannelPanel); + controlPanel.setPreferredSize(new Dimension(250,700)); + controlPanel.setAlignmentX(Component.CENTER_ALIGNMENT); + scrollControlPanel = new JScrollPane( + controlPanel, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollControlPanel.setPreferredSize(new Dimension(250,0)); + + // Add everything + this.setLayout(new BorderLayout()); + this.add(BorderLayout.CENTER, canvas); // Add canvas + this.add(BorderLayout.EAST, scrollControlPanel); + + // Load external images (antenna) + Toolkit toolkit = Toolkit.getDefaultToolkit(); + URL imageURL = this.getClass().getClassLoader().getResource(antennaImageFilename); + antennaImage = toolkit.getImage(imageURL); + + MediaTracker tracker = new MediaTracker(canvas); + tracker.addImage(antennaImage, 1); + + try { + tracker.waitForAll(); + } catch (InterruptedException ex) { + logger.fatal("Interrupted during image loading, aborting"); + return; + } + + + // Try to select current plugin + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + } + + /** + * Listens to mouse event on canvas + */ + private MouseListener canvasMouseHandler = new MouseListener() { + public void mouseReleased(MouseEvent e) { + } + public void mouseExited(MouseEvent e) { + } + public void mouseClicked(MouseEvent e) { + if (inSelectMode) { + Vector hitRadios = trackClickedRadio(e.getPoint()); + + if (hitRadios == null || hitRadios.size() == 0) { + if (e.getButton() != MouseEvent.BUTTON1) { + selectedRadio = null; + channelImage = null; + canvas.repaint(); + } + return; + } + + if (hitRadios.size() == 1 && hitRadios.firstElement() == selectedRadio) + return; + + if (selectedRadio == null || !hitRadios.contains(selectedRadio)) { + selectedRadio = hitRadios.firstElement(); + } else { + // Select next in list + selectedRadio = hitRadios.get( + (hitRadios.indexOf(selectedRadio)+1) % hitRadios.size() + ); + } + + channelImage = null; + canvas.repaint(); + } else if (inTrackMode && selectedRadio != null) { + // Calculate real clicked position + double realClickedX = e.getX() / currentZoomX - currentPanX; + double realClickedY = e.getY() / currentZoomY - currentPanY; + + Position radioPosition = currentRadioMedium.getRadioPosition(selectedRadio); + final double radioX = radioPosition.getXCoordinate(); + final double radioY = radioPosition.getYCoordinate(); + + trackedComponents = currentChannelModel.getRaysOfTransmission(radioX, radioY, realClickedX, realClickedY); + + canvas.repaint(); + } + + } + public void mouseEntered(MouseEvent e) { + } + public void mousePressed(MouseEvent e) { + lastHandledPosition = new Point(e.getX(), e.getY()); + + // Set zoom center (real world) + zoomCenterX = e.getX() / currentZoomX - currentPanX; + zoomCenterY = e.getY() / currentZoomY - currentPanY; + zoomCenterPoint = e.getPoint(); + } + }; + + /** + * Listens to mouse movements when in pan mode + */ + private MouseMotionListener canvasPanModeHandler = new MouseMotionListener() { + public void mouseMoved(MouseEvent e) { + } + public void mouseDragged(MouseEvent e) { + if (lastHandledPosition == null) { + lastHandledPosition = e.getPoint(); + return; + } + + // Pan relative to mouse movement and current zoom + // This way the mouse "lock" to the canvas + currentPanX += ((float) (e.getX() - lastHandledPosition.x)) / currentZoomX; + currentPanY += ((float) (e.getY() - lastHandledPosition.y)) / currentZoomY; + lastHandledPosition = e.getPoint(); + + canvas.repaint(); + } + }; + + /** + * Listens to mouse movements when in zoom mode + */ + private MouseMotionListener canvasZoomModeHandler = new MouseMotionListener() { + public void mouseMoved(MouseEvent e) { + } + public void mouseDragged(MouseEvent e) { + if (lastHandledPosition == null) { + lastHandledPosition = e.getPoint(); + return; + } + + // Zoom relative to mouse movement (keep XY-proportions) + currentZoomY += 0.005 * currentZoomY * ((double) (lastHandledPosition.y - e.getY())); + currentZoomY = Math.max(0.05, currentZoomY); + currentZoomY = Math.min(1500, currentZoomY); + currentZoomX = currentZoomY; + + // We also need to update the current pan in order to zoom towards the mouse + currentPanX = zoomCenterPoint.x/currentZoomX - zoomCenterX; + currentPanY = zoomCenterPoint.y/currentZoomY - zoomCenterY; + + lastHandledPosition = e.getPoint(); + canvas.repaint(); + } + }; + + /** + * Selects which mouse mode the canvas should be in (select/pan/zoom) + */ + private ActionListener canvasModeHandler = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("set select mode")) { + // Select mode, no mouse motion listener needed + for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) + canvas.removeMouseMotionListener(reggedListener); + + inTrackMode = false; + inSelectMode = true; + } else if (e.getActionCommand().equals("set pan mode")) { + // Remove all other mouse motion listeners + for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) + canvas.removeMouseMotionListener(reggedListener); + inSelectMode = false; + inTrackMode = false; + + // Add the special pan mouse motion listener + canvas.addMouseMotionListener(canvasPanModeHandler); + } else if (e.getActionCommand().equals("set zoom mode")) { + // Remove all other mouse motion listeners + for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) + canvas.removeMouseMotionListener(reggedListener); + inSelectMode = false; + inTrackMode = false; + + // Add the special zoom mouse motion listener + canvas.addMouseMotionListener(canvasZoomModeHandler); + } else if (e.getActionCommand().equals("set track rays mode")) { + // Remove all other mouse motion listeners + for (MouseMotionListener reggedListener: canvas.getMouseMotionListeners()) + canvas.removeMouseMotionListener(reggedListener); + inSelectMode = false; + inTrackMode = true; + + } else if (e.getActionCommand().equals("toggle show settings")) { + if (((JCheckBox) e.getSource()).isSelected()) + scrollControlPanel.setVisible(true); + else + scrollControlPanel.setVisible(false); + thisPlugin.invalidate(); + thisPlugin.revalidate(); + + } + } + }; + + /** + * Selects which graphical parts should be painted + */ + private ActionListener selectGraphicsHandler = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("toggle background")) + drawBackgroundImage = ((JCheckBox) e.getSource()).isSelected(); + else if (e.getActionCommand().equals("toggle obstacles")) + drawCalculatedObstacles = ((JCheckBox) e.getSource()).isSelected(); + else if (e.getActionCommand().equals("toggle channel")) + drawChannelProbabilities = ((JCheckBox) e.getSource()).isSelected(); + else if (e.getActionCommand().equals("toggle radios")) + drawRadios = ((JCheckBox) e.getSource()).isSelected(); + else if (e.getActionCommand().equals("toggle radio activity")) + drawRadioActivity = ((JCheckBox) e.getSource()).isSelected(); + else if (e.getActionCommand().equals("toggle arrow")) + drawScaleArrow = ((JCheckBox) e.getSource()).isSelected(); + + canvas.repaint(); + } + }; + + /** + * Helps user set a background image which can be analysed for obstacles/freespace. + */ + private ActionListener setBackgroundHandler = new ActionListener() { + + /** + * Choosable file filter that supports tif, gif, jpg, png, bmp. + */ + class ImageFilter extends FileFilter { + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String filename = f.getName(); + if (filename != null) { + if (filename.endsWith(".tiff") || + filename.endsWith(".tif") || + filename.endsWith(".gif") || + filename.endsWith(".jpg") || + filename.endsWith(".jpeg") || + filename.endsWith(".png") || + filename.endsWith(".bmp")) { + return true; + } + } + return false; + } + + public String getDescription() { + return "All supported images"; + } + } + + class ImageSettingsDialog extends JDialog { + + private double + virtualStartX = 0.0, + virtualStartY = 0.0, + virtualWidth = 0.0, + virtualHeight = 0.0; + + private JFormattedTextField + virtualStartXField, + virtualStartYField, + virtualWidthField, + virtualHeightField; + + private boolean terminatedOK = false; + + private NumberFormat doubleFormat = NumberFormat.getNumberInstance(); + + /** + * Creates a new dialog for settings background parameters + */ + protected ImageSettingsDialog(File imageFile, Image image, Frame owner) { + super(owner, "Image settings"); + + JPanel tempPanel; + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + // Set layout and add components + doubleFormat.setMinimumIntegerDigits(1); + setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS)); + + tempPanel = new JPanel(new GridLayout(1, 2)); + tempPanel.add(new JLabel("Start X (m) ")); + virtualStartXField = new JFormattedTextField(doubleFormat); + virtualStartXField.setValue(new Double(0.0)); + tempPanel.add(virtualStartXField); + add(tempPanel); + + tempPanel = new JPanel(new GridLayout(1, 2)); + tempPanel.add(new JLabel("Start Y (m)")); + virtualStartYField = new JFormattedTextField(doubleFormat); + virtualStartYField.setValue(new Double(0.0)); + tempPanel.add(virtualStartYField); + add(tempPanel); + + tempPanel = new JPanel(new GridLayout(1, 2)); + tempPanel.add(new JLabel("Width (m)")); + virtualWidthField = new JFormattedTextField(doubleFormat); + virtualWidthField.setValue(new Double(100.0)); + tempPanel.add(virtualWidthField); + add(tempPanel); + + tempPanel = new JPanel(new GridLayout(1, 2)); + tempPanel.add(new JLabel("Height (m)")); + virtualHeightField = new JFormattedTextField(doubleFormat); + virtualHeightField.setValue(new Double(100.0)); + tempPanel.add(virtualHeightField); + add(tempPanel); + + add(Box.createVerticalGlue()); + add(Box.createVerticalStrut(10)); + + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(Box.createHorizontalGlue()); + + final JButton okButton = new JButton("OK"); + this.getRootPane().setDefaultButton(okButton); + final JButton cancelButton = new JButton("Cancel"); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + virtualStartX = ((Number) virtualStartXField.getValue()).doubleValue(); + virtualStartY = ((Number) virtualStartYField.getValue()).doubleValue(); + virtualWidth = ((Number) virtualWidthField.getValue()).doubleValue(); + virtualHeight = ((Number) virtualHeightField.getValue()).doubleValue(); + + terminatedOK = true; + dispose(); +} + }); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + terminatedOK = false; + dispose(); + } + }); + + tempPanel.add(okButton); + tempPanel.add(cancelButton); + add(tempPanel); + + // Show dialog + setModal(true); + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + public boolean terminatedOK() { + return terminatedOK; + } + public double getVirtualStartX() { + return virtualStartX; + } + public double getVirtualStartY() { + return virtualStartY; + } + public double getVirtualWidth() { + return virtualWidth; + } + public double getVirtualHeight() { + return virtualHeight; + } + + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("set background image")) { + + // Let user select image file + JFileChooser fileChooser = new JFileChooser(); + ImageFilter filter = new ImageFilter(); + fileChooser.addChoosableFileFilter(filter); + + int returnVal = fileChooser.showOpenDialog(canvas); + + if (returnVal != JFileChooser.APPROVE_OPTION) { + // User cancelled + return; + } + + File file = fileChooser.getSelectedFile(); + + // User selected non-supported file, aborting + if (!filter.accept(file)) { + logger.fatal("Non-supported file type, aborting"); + return; + } + + logger.info("Opening: " + file.getName() + "."); + + // Load image using toolkit and media tracker + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Image image = toolkit.getImage(file.getAbsolutePath()); + + MediaTracker tracker = new MediaTracker(canvas); + tracker.addImage(image, 1); + + try { + tracker.waitForAll(); + } catch (InterruptedException ex) { + logger.fatal("Interrupted during image loading, aborting"); + return; + } + + // Let user set virtual size of loaded image + ImageSettingsDialog dialog = new ImageSettingsDialog(file, image, GUI.frame); + + if (!dialog.terminatedOK()) { + logger.fatal("User cancelled, aborting"); + return; + } + + // Add background image + backgroundStartX = dialog.getVirtualStartX(); + backgroundStartY = dialog.getVirtualStartY(); + backgroundWidth = dialog.getVirtualWidth(); + backgroundHeight = dialog.getVirtualHeight(); + + backgroundImage = image; + backgroundImageFile = file; + } + } + }; + + /** + * Helps user analyze a background for obstacles. + */ + private ActionListener analyzeObstaclesHandler = new ActionListener() { + + class AnalyzeImageDialog extends JDialog { + + private NumberFormat intFormat = NumberFormat.getIntegerInstance(); + private BufferedImage imageToAnalyze = null; + private BufferedImage obstacleImage = null; + private JPanel canvasPanel = null; + private boolean[][] obstacleArray = null; + private boolean exitedOK = false; + + private JSlider redSlider, greenSlider, blueSlider, toleranceSlider, sizeSlider; + + /** + * Listens to preview mouse motion event (when picking color) + */ + private MouseMotionListener myMouseMotionListener = new MouseMotionListener() { + public void mouseDragged(MouseEvent e) { + + } + public void mouseMoved(MouseEvent e) { + // Convert from mouse to image pixel position + Point pixelPoint = new Point( + (int) (e.getX() * (((double) imageToAnalyze.getWidth()) / ((double) canvasPanel.getWidth()))), + (int) (e.getY() * (((double) imageToAnalyze.getHeight()) / ((double) canvasPanel.getHeight()))) + ); + + // Fetch color + int color = imageToAnalyze.getRGB(pixelPoint.x, pixelPoint.y); + int red = (color & 0x00ff0000) >> 16; + int green = (color & 0x0000ff00) >> 8; + int blue = color & 0x000000ff; + + // Update sliders + redSlider.setValue(red); + redSlider.repaint(); + greenSlider.setValue(green); + greenSlider.repaint(); + blueSlider.setValue(blue); + blueSlider.repaint(); + } + }; + + /** + * Listens to preview mouse event (when picking color) + */ + private MouseListener myMouseListener = new MouseListener() { + public void mouseClicked(MouseEvent e) { + + } + public void mouseReleased(MouseEvent e) { + + } + public void mouseEntered(MouseEvent e) { + + } + public void mouseExited(MouseEvent e) { + + } + public void mousePressed(MouseEvent e) { + // Stop picking color again; remove mouse listeners and reset mouse cursor + MouseListener[] allMouseListeners = canvasPanel.getMouseListeners(); + for (MouseListener mouseListener: allMouseListeners) + canvasPanel.removeMouseListener(mouseListener); + + MouseMotionListener[] allMouseMotionListeners = canvasPanel.getMouseMotionListeners(); + for (MouseMotionListener mouseMotionListener: allMouseMotionListeners) + canvasPanel.removeMouseMotionListener(mouseMotionListener); + + canvasPanel.setCursor(Cursor.getDefaultCursor()); + } + }; + + /** + * Creates a new dialog for settings background parameters + */ + protected AnalyzeImageDialog(Image currentImage, ChannelModel currentChannelModel, Frame owner) { + super(owner, "Analyze for obstacles"); + + JPanel tempPanel; + JLabel tempLabel; + JSlider tempSlider; + JButton tempButton; + + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + Dimension labelDimension = new Dimension(100, 20); + + // Convert Image to BufferedImage + imageToAnalyze = new BufferedImage( + currentImage.getWidth(this), + currentImage.getHeight(this), + BufferedImage.TYPE_INT_ARGB + ); + Graphics2D g = imageToAnalyze.createGraphics(); + g.drawImage(currentImage, 0, 0, null); + + // Prepare initial obstacle image + obstacleImage = new BufferedImage( + currentImage.getWidth(this), + currentImage.getHeight(this), + BufferedImage.TYPE_INT_ARGB + ); + + // Set layout and add components + intFormat.setMinimumIntegerDigits(1); + intFormat.setMaximumIntegerDigits(3); + intFormat.setParseIntegerOnly(true); + setLayout(new BoxLayout(this.getContentPane(), BoxLayout.Y_AXIS)); + + // Obstacle color + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Obstacle")); + tempLabel.setPreferredSize(labelDimension); + add(tempPanel); + + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Red")); + tempLabel.setPreferredSize(labelDimension); + tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); + tempSlider.setMajorTickSpacing(50); + tempSlider.setPaintTicks(true); + tempSlider.setPaintLabels(true); + add(tempPanel); + redSlider = tempSlider; + + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Green")); + tempLabel.setPreferredSize(labelDimension); + tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); + tempSlider.setMajorTickSpacing(50); + tempSlider.setPaintTicks(true); + tempSlider.setPaintLabels(true); + add(tempPanel); + greenSlider = tempSlider; + + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Blue")); + tempLabel.setPreferredSize(labelDimension); + tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0)); + tempSlider.setMajorTickSpacing(50); + tempSlider.setPaintTicks(true); + tempSlider.setPaintLabels(true); + add(tempPanel); + blueSlider = tempSlider; + + // Tolerance + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Tolerance")); + tempLabel.setPreferredSize(labelDimension); + tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 0, 128, 0)); + tempSlider.setMajorTickSpacing(25); + tempSlider.setPaintTicks(true); + tempSlider.setPaintLabels(true); + add(tempPanel); + toleranceSlider = tempSlider; + + // Obstacle size + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(tempLabel = new JLabel("Obstacle size")); + tempLabel.setPreferredSize(labelDimension); + tempLabel.setAlignmentY(Component.BOTTOM_ALIGNMENT); + tempPanel.add(tempSlider = new JSlider(JSlider.HORIZONTAL, 1, 40, 40)); + tempSlider.setInverted(true); + tempSlider.setMajorTickSpacing(5); + tempSlider.setPaintTicks(true); + tempSlider.setPaintLabels(true); + add(tempPanel); + sizeSlider = tempSlider; + + // Buttons: Pick color, Preview obstacles etc. + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(Box.createHorizontalGlue()); + tempPanel.add(tempButton = new JButton("Pick color")); + tempButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Set to color picker mode (if not already there) + if (canvasPanel.getMouseMotionListeners().length == 0) { + canvasPanel.addMouseListener(myMouseListener); + canvasPanel.addMouseMotionListener(myMouseMotionListener); + canvasPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); + } + } + }); + tempPanel.add(Box.createHorizontalStrut(5)); + tempPanel.add(tempButton = new JButton("Preview obstacles")); + tempButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + obstacleImage = createObstacleImage(); + canvasPanel.repaint(); + } + }); + add(tempPanel); + + add(Box.createVerticalStrut(10)); + + // Preview image + tempPanel = new JPanel() { + public void paintComponent(Graphics g) { + super.paintComponent(g); + g.drawImage(imageToAnalyze, 0, 0, getWidth(), getHeight(), this); + g.drawImage(obstacleImage, 0, 0, getWidth(), getHeight(), this); + } + }; + tempPanel.setBorder( + BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(Color.BLACK), "Preview")); + tempPanel.setPreferredSize(new Dimension(400, 400)); + tempPanel.setBackground(Color.CYAN); + add(tempPanel); + canvasPanel = tempPanel; // Saved in canvasPanel + + // Buttons: Cancel, OK + tempPanel = new JPanel(); + tempPanel.setLayout(new BoxLayout(tempPanel, BoxLayout.X_AXIS)); + tempPanel.add(Box.createHorizontalGlue()); + tempPanel.add(tempButton = new JButton("Cancel")); + tempButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + tempPanel.add(Box.createHorizontalStrut(5)); + tempPanel.add(tempButton = new JButton("OK")); + tempButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + obstacleImage = createObstacleImage(); + exitedOK = true; + dispose(); + } + }); + add(tempPanel); + + + add(Box.createVerticalGlue()); + add(Box.createVerticalStrut(10)); + + // Show dialog + setModal(true); + pack(); + setLocationRelativeTo(owner); + setVisible(true); + } + + /** + * Create obstacle image by analyzing current background image + * and using the current obstacle color, size and tolerance. + * This method also creates the boolean array obstacleArray. + * + * @return New obstacle image + */ + private BufferedImage createObstacleImage() { + int nrObstacles = 0; + + // Create new obstacle image all transparent (no obstacles) + BufferedImage newObstacleImage = new BufferedImage( + imageToAnalyze.getWidth(), + imageToAnalyze.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + for (int x=0; x < imageToAnalyze.getWidth(); x++) { + for (int y=0; y < imageToAnalyze.getHeight(); y++) { + newObstacleImage.setRGB(x, y, 0x00000000); + } + } + + // Get target color to match against + int targetRed = redSlider.getValue(); + int targetGreen = greenSlider.getValue(); + int targetBlue = blueSlider.getValue(); + + // Get obstacle resolution and size + int boxSize = sizeSlider.getValue(); + int tolerance = toleranceSlider.getValue(); + + // Divide image into boxes and check each box for obstacles + int arrayWidth = (int) Math.ceil((double) imageToAnalyze.getWidth() / (double) boxSize); + int arrayHeight = (int) Math.ceil((double) imageToAnalyze.getHeight() / (double) boxSize); + + obstacleArray = new boolean[arrayWidth][arrayHeight]; + for (int x=0; x < imageToAnalyze.getWidth(); x+=boxSize) { + for (int y=0; y < imageToAnalyze.getHeight(); y+=boxSize) { + boolean boxIsObstacle = false; + + // Check all pixels in box for obstacles + for (int xx=x; xx < x + boxSize && xx < imageToAnalyze.getWidth(); xx++) { + for (int yy=y; yy < y + boxSize && yy < imageToAnalyze.getHeight(); yy++) { + + // Get current pixel color + int color = imageToAnalyze.getRGB(xx, yy); + int red = (color & 0x00ff0000) >> 16; + int green = (color & 0x0000ff00) >> 8; + int blue = color & 0x000000ff; + + // Calculate difference from target color + int difference = + Math.abs(red - targetRed) + + Math.abs(green - targetGreen) + + Math.abs(blue - targetBlue); + + // If difference is small enough make this box an obstacle + if (difference <= tolerance) { + boxIsObstacle = true; + break; + } + } + if (boxIsObstacle) + break; + } + + // If box is obstacle, colorize it + if (boxIsObstacle) { + obstacleArray[x/boxSize][y/boxSize] = true; + nrObstacles++; + + // Colorize all pixels in the box + for (int xx=x; xx < x + boxSize && xx < imageToAnalyze.getWidth(); xx++) { + for (int yy=y; yy < y + boxSize && yy < imageToAnalyze.getHeight(); yy++) { + newObstacleImage.setRGB(xx, yy, 0x9922ff22); + } + } + } else + obstacleArray[x/boxSize][y/boxSize] = false; + + + } + } // End of "divide into boxes" for-loop + + return newObstacleImage; + } + + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("analyze for obstacles")) { + if (backgroundImage == null) + return; + + AnalyzeImageDialog analyzer = new AnalyzeImageDialog( + backgroundImage, + currentChannelModel, + GUI.frame + ); + + // Check return value from analyzer + if (analyzer.exitedOK) { + // Remove old obstacle and register new + final boolean[][] obstacleArray = analyzer.obstacleArray; + + final int boxSize = analyzer.sizeSlider.getValue(); + + // Create progress monitor + final ProgressMonitor pm = new ProgressMonitor( + GUI.frame, + "Registering obstacles", + null, + 0, + obstacleArray.length - 1 + ); + + // Thread that will perform the work + final Runnable runnable = new Runnable() { + public void run() { + try { + int foundObstacles = 0; + + // Clear all old obstacles + currentChannelModel.removeAllObstacles(); + + for (int x=0; x < obstacleArray.length; x++) { + for (int y=0; y < (obstacleArray[0]).length; y++) { + // If obstacle, register it + if (obstacleArray[x][y]) { + double realWidth = ((double) boxSize * backgroundWidth) / (double) backgroundImage.getWidth(null); + double realHeight = ((double) boxSize * backgroundHeight) / (double) backgroundImage.getHeight(null); + double realStartX = backgroundStartX + (double) x * realWidth; + double realStartY = backgroundStartY + (double) y * realHeight; + + foundObstacles++; + + if (realStartX + realWidth > backgroundStartX + backgroundWidth) + realWidth = backgroundStartX + backgroundWidth - realStartX; + if (realStartY + realHeight > backgroundStartY + backgroundHeight) + realHeight = backgroundStartY + backgroundHeight - realStartY; + + currentChannelModel.addRectObstacle( + realStartX, + realStartY, + realWidth, + realHeight, + false + ); + } + } + // Check if the dialog has been cancelled + if (pm.isCanceled()) return; + + // Update progress + pm.setProgress(x); + pm.setNote("After/Before merging: " + + currentChannelModel.getNumberOfObstacles() + + "/" + + foundObstacles); + } + currentChannelModel.notifySettingsChanged(); + + thisPlugin.repaint(); + + } catch (Exception ex) { + if (pm.isCanceled()) return; + logger.fatal("Obstacle adding aborted..: "); + ex.printStackTrace(); + pm.close(); + } + pm.close(); + } + }; + + // Start thread + Thread thread = new Thread(runnable); + thread.start(); + } + } + } + }; + + /** + * Listens to settings changes in the radio medium. + */ + private Observer radioMediumSettingsObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Clear selected radio (if any selected) and radio medium coverage + selectedRadio = null; + channelImage = null; + canvas.repaint(); + } + }; + + /** + * Listens to settings changes in the radio medium. + */ + private Observer radioMediumActivityObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Just remove any selected radio (it may have been removed) + canvas.repaint(); + } + }; + + /** + * Listens to settings changes in the channel model. + */ + private Observer channelModelSettingsObserver = new Observer() { + public void update(Observable obs, Object obj) { + needToRepaintObstacleImage = true; + canvas.repaint(); + } + }; + + /** + * Returns a color corresponding to given value where higher values are more green, and lower values are more red. + * + * @param value Signal strength of received signal (dB) + * @param lowBorder + * @param highBorder + * @return Integer containing standard ARGB color. + */ + private int getColorOfSignalStrength(double value, double lowBorder, double highBorder) { + double upperLimit = highBorder; // Best signal adds green + double lowerLimit = lowBorder; // Bad signal adds red + double intervalSize = (upperLimit - lowerLimit) / 2; + double middleValue = lowerLimit + (upperLimit - lowerLimit) / 2; + + if (value > highBorder) { + return 0xCC00FF00; + } + + if (value < lowerLimit) { + return 0xCCFF0000; + } + + int red = 0, green = 0, blue = 0, alpha = 0xCC; + + // Upper limit (green) + if (value > upperLimit - intervalSize) { + green = (int) (255 - 255*(upperLimit - value)/intervalSize); + } + + // Medium signal adds blue + if (value > middleValue - intervalSize && value < middleValue + intervalSize) { + blue = (int) (255 - 255*Math.abs(middleValue - value)/intervalSize); + } + + // Bad signal adds red + if (value < lowerLimit + intervalSize) { + red = (int) (255 - 255*(value - lowerLimit)/intervalSize); + } + + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + /** + * Helps user adjust and calculate the channel propagation formula + */ + private ActionListener formulaHandler = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("recalculate visible area")) { + + // Get resolution of new image + final Dimension resolution = new Dimension( + resolutionSlider.getValue(), + resolutionSlider.getValue() + ); + + // Abort if no radio selected + if (selectedRadio == null) { + channelImage = null; + canvas.repaint(); + return; + } + + // Get new location/size of area to attenuate + final double startX = -currentPanX; + final double startY = -currentPanY; + final double width = canvas.getWidth() / currentZoomX; + final double height = canvas.getHeight() / currentZoomY; + + // Get sending radio position + Position radioPosition = currentRadioMedium.getRadioPosition(selectedRadio); + final double radioX = radioPosition.getXCoordinate(); + final double radioY = radioPosition.getYCoordinate(); + + // Create temporary image + final BufferedImage tempChannelImage = new BufferedImage(resolution.width, resolution.height, BufferedImage.TYPE_INT_ARGB); + + // Save time for later analysis + final long timeBeforeCalculating = System.currentTimeMillis(); + + // Create progress monitor + final ProgressMonitor pm = new ProgressMonitor( + GUI.frame, + "Calculating channel attenuation", + null, + 0, + resolution.width - 1 + ); + + // Thread that will perform the work + final Runnable runnable = new Runnable() { + public void run() { + try { + + // Available signal strength intervals + double lowestImageValue = Double.MAX_VALUE; + double highestImageValue = -Double.MAX_VALUE; + + // Create image values (calculate each pixel) + double[][] imageValues = new double[resolution.width][resolution.height]; + for (int x=0; x < resolution.width; x++) { + for (int y=0; y < resolution.height; y++) { + + if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH) { + // Attenuate + double[] signalStrength = currentChannelModel.getReceivedSignalStrength( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height + ); + + // Collecting signal strengths + if (signalStrength[0] < lowestImageValue) + lowestImageValue = signalStrength[0]; + if (signalStrength[0] > highestImageValue) + highestImageValue = signalStrength[0]; + + imageValues[x][y] = signalStrength[0]; + + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR) { + // Attenuate + double[] signalStrength = currentChannelModel.getReceivedSignalStrength( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height + ); + + // Collecting variances + if (signalStrength[1] < lowestImageValue) + lowestImageValue = signalStrength[1]; + if (signalStrength[1] > highestImageValue) + highestImageValue = signalStrength[1]; + + imageValues[x][y] = signalStrength[1]; + + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR) { + // Get signal to noise ratio + double[] snr = currentChannelModel.getSINR( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height, + -Double.MAX_VALUE + ); + + // Collecting signal to noise ratio + if (snr[0] < lowestImageValue) + lowestImageValue = snr[0]; + if (snr[0] > highestImageValue) + highestImageValue = snr[0]; + + imageValues[x][y] = snr[0]; + + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { + // Get signal to noise ratio + double[] snr = currentChannelModel.getSINR( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height, + -Double.MAX_VALUE + ); + + // Collecting variances + if (snr[1] < lowestImageValue) + lowestImageValue = snr[1]; + if (snr[1] > highestImageValue) + highestImageValue = snr[1]; + + imageValues[x][y] = snr[1]; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { + // Get probability of receiving a packet TODO What size? Does it matter? + double probability = currentChannelModel.getProbability( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height, + -Double.MAX_VALUE + )[0]; + + // Collecting variances + if (probability < lowestImageValue) + lowestImageValue = probability; + if (probability > highestImageValue) + highestImageValue = probability; + + imageValues[x][y] = probability; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { + // Get RMS delay spread of receiving a packet + double rmsDelaySpread = currentChannelModel.getRMSDelaySpread( + radioX, + radioY, + startX + width * x/(double) resolution.width, + startY + height * y/(double) resolution.height + ); + + // Collecting variances + if (rmsDelaySpread < lowestImageValue) + lowestImageValue = rmsDelaySpread; + if (rmsDelaySpread > highestImageValue) + highestImageValue = rmsDelaySpread; + + imageValues[x][y] = rmsDelaySpread; + } + + // Check if the dialog has been cancelled + if (pm.isCanceled()) return; + + // Update progress + pm.setProgress(x); + + } + } + + // Adjust coloring signal strength limit + if (coloringIsFixed) { + if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH) { + lowestImageValue = -100; + highestImageValue = 0; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SIGNAL_STRENGTH_VAR) { + lowestImageValue = 0; + highestImageValue = 20; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR) { + lowestImageValue = -10; + highestImageValue = 30; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.SNR_VAR) { + lowestImageValue = 0; + highestImageValue = 20; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.PROB_OF_RECEPTION) { + lowestImageValue = 0; + highestImageValue = 1; + } else if (dataTypeToVisualize == ChannelModel.TransmissionData.DELAY_SPREAD_RMS) { + lowestImageValue = 0; + highestImageValue = 5; + } + } + + // Save coloring high-low interval + coloringHighest = highestImageValue; + coloringLowest = lowestImageValue; + + // Create image + for (int x=0; x < resolution.width; x++) { + for (int y=0; y < resolution.height; y++) { + + tempChannelImage.setRGB( + x, + y, + getColorOfSignalStrength(imageValues[x][y], lowestImageValue, highestImageValue) + ); + } + } + logger.info("Attenuating area done, time=" + (System.currentTimeMillis() - timeBeforeCalculating)); + + // Repaint to show the new channel propagation + channelStartX = startX; + channelStartY = startY; + channelWidth = width; + channelHeight = height; + channelImage = tempChannelImage; + + thisPlugin.repaint(); + coloringIntervalPanel.repaint(); + + } catch (Exception ex) { + if (pm.isCanceled()) return; + logger.fatal("Attenuation aborted: " + ex); + ex.printStackTrace(); + pm.close(); + } + pm.close(); + } + }; + + // Start thread + attenuatorThread = new Thread(runnable); + attenuatorThread.start(); + } + } + }; + + /** + * Repaint the canvas + * @param g2d Current graphics to paint on + */ + protected void repaintCanvas(Graphics2D g2d) { + AffineTransform originalTransform = g2d.getTransform(); + + // Create "real-world" transformation (scaled 100 times to reduce double->int rounding errors) + g2d.scale(currentZoomX, currentZoomY); + g2d.translate(currentPanX, currentPanY); + AffineTransform realWorldTransform = g2d.getTransform(); + g2d.scale(0.01, 0.01); + AffineTransform realWorldTransformScaled = g2d.getTransform(); + + // -- Draw background image if any -- + if (drawBackgroundImage && backgroundImage != null) { + g2d.setTransform(realWorldTransformScaled); + + g2d.drawImage(backgroundImage, + (int) ((double) backgroundStartX * 100.0), + (int) ((double) backgroundStartY * 100.0), + (int) ((double) backgroundWidth * 100.0), + (int) ((double) backgroundHeight * 100.0), + this); + } + + // -- Draw calculated obstacles -- + if (drawCalculatedObstacles) { + + // (Re)create obstacle image if needed + if (obstacleImage == null || needToRepaintObstacleImage) { + + // Abort if no obstacles exist + if (currentChannelModel.getNumberOfObstacles() > 0) { + + // Get bounds of obstacles + obstacleStartX = currentChannelModel.getObstacle(0).getMinX(); + obstacleStartY = currentChannelModel.getObstacle(0).getMinY(); + obstacleWidth = currentChannelModel.getObstacle(0).getMaxX(); + obstacleHeight = currentChannelModel.getObstacle(0).getMaxY(); + + double tempVal = 0; + for (int i=0; i < currentChannelModel.getNumberOfObstacles(); i++) { + if ((tempVal = currentChannelModel.getObstacle(i).getMinX()) < obstacleStartX) + obstacleStartX = tempVal; + if ((tempVal = currentChannelModel.getObstacle(i).getMinY()) < obstacleStartY) + obstacleStartY = tempVal; + if ((tempVal = currentChannelModel.getObstacle(i).getMaxX()) > obstacleWidth) + obstacleWidth = tempVal; + if ((tempVal = currentChannelModel.getObstacle(i).getMaxY()) > obstacleHeight) + obstacleHeight = tempVal; + } + obstacleWidth -= obstacleStartX; + obstacleHeight -= obstacleStartY; + + // Create new obstacle image + BufferedImage tempObstacleImage; + if (backgroundImage != null) + tempObstacleImage = new BufferedImage( + Math.max(600, backgroundImage.getWidth(null)), + Math.max(600, backgroundImage.getHeight(null)), + BufferedImage.TYPE_INT_ARGB + ); + else + tempObstacleImage = new BufferedImage( + 600, + 600, + BufferedImage.TYPE_INT_ARGB + ); + + Graphics2D obstacleGraphics = (Graphics2D) tempObstacleImage.getGraphics(); + + // Set real world transform + obstacleGraphics.scale( + (double) tempObstacleImage.getWidth()/obstacleWidth, + (double) tempObstacleImage.getHeight()/obstacleHeight + ); + obstacleGraphics.translate(-obstacleStartX, -obstacleStartY); + + + // Paint all obstacles + obstacleGraphics.setColor(new Color(0, 0, 0, 128)); + + // DEBUG: Use random obstacle color to distinguish different obstacles + //Random random = new Random(); + + for (int i=0; i < currentChannelModel.getNumberOfObstacles(); i++) { + //obstacleGraphics.setColor((new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255), 128))); + obstacleGraphics.fill(currentChannelModel.getObstacle(i)); + } + obstacleImage = tempObstacleImage; + + } else { + + // No obstacles exist - create dummy obstacle image + obstacleStartX = 0; + obstacleStartY = 0; + obstacleWidth = 1; + obstacleHeight = 1; + obstacleImage = new BufferedImage( + 1, + 1, + BufferedImage.TYPE_INT_ARGB + ); + } + + needToRepaintObstacleImage = false; + } + + // Painting in real world coordinates + g2d.setTransform(realWorldTransformScaled); + + g2d.drawImage(obstacleImage, + (int) ((double) obstacleStartX * 100.0), + (int) ((double) obstacleStartY * 100.0), + (int) ((double) obstacleWidth * 100.0), + (int) ((double) obstacleHeight * 100.0), + this); + } + + // -- Draw channel probabilities if calculated -- + if (drawChannelProbabilities && channelImage != null) { + g2d.setTransform(realWorldTransformScaled); + + g2d.drawImage(channelImage, + (int) ((double) channelStartX * 100.0), + (int) ((double) channelStartY * 100.0), + (int) ((double) channelWidth * 100.0), + (int) ((double) channelHeight * 100.0), + this); + } + + // -- Draw radios -- + if (drawRadios) { + for (int i=0; i < currentRadioMedium.getRegisteredRadioCount(); i++) { + g2d.setStroke(new BasicStroke((float) 0.0)); + g2d.setTransform(realWorldTransform); + + // Translate to real world radio position + Radio radio = currentRadioMedium.getRegisteredRadio(i); + Position radioPosition = currentRadioMedium.getRadioPosition(radio); + g2d.translate( + radioPosition.getXCoordinate(), + radioPosition.getYCoordinate() + ); + + // Fetch current translation + double xPos = g2d.getTransform().getTranslateX(); + double yPos = g2d.getTransform().getTranslateY(); + + // Jump to identity transform and paint without scaling + g2d.setTransform(new AffineTransform()); + + if (selectedRadio == radio) { + g2d.setColor(new Color(255, 0, 0, 100)); + g2d.fillRect( + (int) xPos - antennaImage.getWidth(this)/2, + (int) yPos - antennaImage.getHeight(this)/2, + (int) antennaImage.getWidth(this), + (int) antennaImage.getHeight(this) + ); + g2d.setColor(Color.BLUE); + g2d.drawRect( + (int) xPos - antennaImage.getWidth(this)/2, + (int) yPos - antennaImage.getHeight(this)/2, + (int) antennaImage.getWidth(this), + (int) antennaImage.getHeight(this) + ); + } + + g2d.drawImage(antennaImage, (int) xPos - antennaImage.getWidth(this)/2, (int) yPos - antennaImage.getHeight(this)/2, this); + + } + } + + // -- Draw radio activity -- + if (drawRadioActivity) { + // Paint scaled (otherwise bad rounding to integers may occur) + g2d.setTransform(realWorldTransformScaled); + g2d.setStroke(new BasicStroke((float) 0.0)); + + for (RadioInterference interference: currentRadioMedium.getCurrentInterferencesArray()) { + g2d.setColor(Color.RED); + + // Get source and destination coordinates + Position sourcePosition = interference.mySource.source.position; + Position destinationPosition = interference.myDestination.position; + + g2d.draw(new Line2D.Double( + sourcePosition.getXCoordinate()*100.0, + sourcePosition.getYCoordinate()*100.0, + destinationPosition.getXCoordinate()*100.0, + destinationPosition.getYCoordinate()*100.0 + )); + } + for (RadioTransfer transfer: currentRadioMedium.getCurrentTransfersArray()) { + g2d.setColor(Color.GREEN); + + // Get source and destination coordinates + Position sourcePosition = transfer.mySource.source.position; + Position destinationPosition = transfer.myDestination.position; + + g2d.draw(new Line2D.Double( + sourcePosition.getXCoordinate()*100.0, + sourcePosition.getYCoordinate()*100.0, + destinationPosition.getXCoordinate()*100.0, + destinationPosition.getYCoordinate()*100.0 + )); + } + for (RadioTransmission transmission: currentRadioMedium.getCurrentTransmissionsArray()) { + g2d.setColor(Color.BLUE); + + g2d.setTransform(realWorldTransform); + Position sourcePosition = transmission.source.position; + + g2d.translate( + sourcePosition.getXCoordinate(), + sourcePosition.getYCoordinate() + ); + + // Fetch current translation + double xPos = g2d.getTransform().getTranslateX(); + double yPos = g2d.getTransform().getTranslateY(); + + // Jump to identity transform and paint without scaling + g2d.setTransform(new AffineTransform()); + + g2d.fillOval( + (int) xPos, + (int) yPos, + (int) 5, + (int) 5 + ); + + + } + } + + // -- Draw scale arrow -- + if (drawScaleArrow) { + g2d.setStroke(new BasicStroke((float) .0)); + + g2d.setColor(Color.BLACK); + + // Decide on scale comparator + double currentArrowDistance = 0.1; // in meters + if (currentZoomX < canvas.getWidth() / 2) + currentArrowDistance = 0.1; // 0.1m + if (currentZoomX < canvas.getWidth() / 2) + currentArrowDistance = 1; // 1m + if (10 * currentZoomX < canvas.getWidth() / 2) + currentArrowDistance = 10; // 10m + if (100 * currentZoomX < canvas.getWidth() / 2) + currentArrowDistance = 100; // 100m + if (1000 * currentZoomX < canvas.getWidth() / 2) + currentArrowDistance = 1000; // 100m + + // "Arrow" points + int pixelArrowLength = (int) (currentArrowDistance * currentZoomX); + int xPoints[] = new int[] { -pixelArrowLength, -pixelArrowLength, -pixelArrowLength, 0, 0, 0 }; + int yPoints[] = new int[] { -5, 5, 0, 0, -5, 5 }; + + // Paint arrow and text + g2d.setTransform(originalTransform); + g2d.translate(canvas.getWidth() - 120, canvas.getHeight() - 20); + g2d.drawString(currentArrowDistance + "m", -30, -10); + g2d.drawPolyline(xPoints, yPoints, xPoints.length); + } + + // -- Draw tracked components (if any) -- + if (inTrackMode && trackedComponents != null) { + g2d.setTransform(realWorldTransformScaled); + g2d.setStroke(new BasicStroke((float) 0.0)); + + Random random = new Random(); + for (int i=0; i < trackedComponents.size(); i++) { + g2d.setColor(new Color(255, random.nextInt(255), random.nextInt(255), 255)); + Line2D originalLine = trackedComponents.get(i); + Line2D newLine = new Line2D.Double( + originalLine.getX1()*100.0, + originalLine.getY1()*100.0, + originalLine.getX2()*100.0, + originalLine.getY2()*100.0 + ); + + g2d.draw(newLine); + } + } + + g2d.setTransform(originalTransform); + } + + /** + * Tracks an on-screen position and returns all hit radios. + * May for example be used by a mouse listener to determine + * if user clicked on a radio. + * + * @param clickedPoint On-screen position + * @return All hit radios + */ + protected Vector trackClickedRadio(Point clickedPoint) { + Vector hitRadios = new Vector(); + if (currentRadioMedium.getRegisteredRadioCount() == 0) + return null; + + double realIconHalfWidth = antennaImage.getWidth(this) / (currentZoomX*2.0); + double realIconHalfHeight = antennaImage.getHeight(this) / (currentZoomY*2.0); + double realClickedX = clickedPoint.x / currentZoomX - currentPanX; + double realClickedY = clickedPoint.y / currentZoomY - currentPanY; + + for (int i=0; i < currentRadioMedium.getRegisteredRadioCount(); i++) { + Radio testRadio = currentRadioMedium.getRegisteredRadio(i); + Position testPosition = currentRadioMedium.getRadioPosition(testRadio); + + if (realClickedX > testPosition.getXCoordinate() - realIconHalfWidth && + realClickedX < testPosition.getXCoordinate() + realIconHalfWidth && + realClickedY > testPosition.getYCoordinate() - realIconHalfHeight && + realClickedY < testPosition.getYCoordinate() + realIconHalfHeight) { + hitRadios.add(testRadio); + } + } + + if (hitRadios.size() == 0) + return null; + return hitRadios; + } + + public void closePlugin() { + // Remove all our observers + + if (currentChannelModel != null && channelModelSettingsObserver != null) { + currentChannelModel.deleteSettingsObserver(channelModelSettingsObserver); + } else { + logger.fatal("Could not remove observer: " + channelModelSettingsObserver); + } + + if (currentRadioMedium != null && radioMediumSettingsObserver != null) { + currentRadioMedium.deleteSettingsObserver(radioMediumSettingsObserver); + } else { + logger.fatal("Could not remove observer: " + radioMediumSettingsObserver); + } + + if (currentRadioMedium != null && radioMediumActivityObserver != null) { + currentRadioMedium.deleteRadioMediumObserver(radioMediumActivityObserver); + } else { + logger.fatal("Could not remove observer: " + radioMediumActivityObserver); + } + } + + + /** + * Returns XML elements representing the current configuration. + * + * @see #setConfigXML(Collection) + * @return XML element collection + */ + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // Controls visible + element = new Element("controls_visible"); + element.setText(Boolean.toString(showSettingsBox.isSelected())); + config.add(element); + + // Viewport + element = new Element("zoom_x"); + element.setText(Double.toString(currentZoomX)); + config.add(element); + element = new Element("zoom_y"); + element.setText(Double.toString(currentZoomY)); + config.add(element); + element = new Element("pan_x"); + element.setText(Double.toString(currentPanX)); + config.add(element); + element = new Element("pan_y"); + element.setText(Double.toString(currentPanY)); + config.add(element); + + // Components shown + element = new Element("show_background"); + element.setText(Boolean.toString(drawBackgroundImage)); + config.add(element); + element = new Element("show_obstacles"); + element.setText(Boolean.toString(drawCalculatedObstacles)); + config.add(element); + element = new Element("show_channel"); + element.setText(Boolean.toString(drawChannelProbabilities)); + config.add(element); + element = new Element("show_radios"); + element.setText(Boolean.toString(drawRadios)); + config.add(element); + element = new Element("show_activity"); + element.setText(Boolean.toString(drawRadioActivity)); + config.add(element); + element = new Element("show_arrow"); + element.setText(Boolean.toString(drawScaleArrow)); + config.add(element); + + // Visualization type + element = new Element("vis_type"); + element.setText(visTypeSelectionGroup.getSelection().getActionCommand()); + config.add(element); + + // Background image + if (backgroundImageFile != null) { + element = new Element("background_image"); + element.setText(backgroundImageFile.getPath()); + config.add(element); + + element = new Element("back_start_x"); + element.setText(Double.toString(backgroundStartX)); + config.add(element); + element = new Element("back_start_y"); + element.setText(Double.toString(backgroundStartY)); + config.add(element); + element = new Element("back_width"); + element.setText(Double.toString(backgroundWidth)); + config.add(element); + element = new Element("back_height"); + element.setText(Double.toString(backgroundHeight)); + config.add(element); + } + + // Resolution + element = new Element("resolution"); + element.setText(Integer.toString(resolutionSlider.getValue())); + 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 configXML) { + for (Element element : configXML) { + if (element.getName().equals("controls_visible")) { + showSettingsBox.setSelected(Boolean.parseBoolean(element.getText())); + canvasModeHandler.actionPerformed(new ActionEvent(showSettingsBox, + ActionEvent.ACTION_PERFORMED, showSettingsBox.getActionCommand())); + } else if (element.getName().equals("zoom_x")) { + currentZoomX = Double.parseDouble(element.getText()); + } else if (element.getName().equals("zoom_y")) { + currentZoomY = Double.parseDouble(element.getText()); + } else if (element.getName().equals("pan_x")) { + currentPanX = Double.parseDouble(element.getText()); + } else if (element.getName().equals("pan_y")) { + currentPanY = Double.parseDouble(element.getText()); + } else if (element.getName().equals("show_background")) { + backgroundCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(backgroundCheckBox, + ActionEvent.ACTION_PERFORMED, backgroundCheckBox.getActionCommand())); + } else if (element.getName().equals("show_obstacles")) { + obstaclesCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(obstaclesCheckBox, + ActionEvent.ACTION_PERFORMED, obstaclesCheckBox.getActionCommand())); + } else if (element.getName().equals("show_channel")) { + channelCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(channelCheckBox, + ActionEvent.ACTION_PERFORMED, channelCheckBox.getActionCommand())); + } else if (element.getName().equals("show_radios")) { + radiosCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(radiosCheckBox, + ActionEvent.ACTION_PERFORMED, radiosCheckBox.getActionCommand())); + } else if (element.getName().equals("show_activity")) { + radioActivityCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(radioActivityCheckBox, + ActionEvent.ACTION_PERFORMED, radioActivityCheckBox.getActionCommand())); + } else if (element.getName().equals("show_arrow")) { + arrowCheckBox.setSelected(Boolean.parseBoolean(element.getText())); + selectGraphicsHandler.actionPerformed(new ActionEvent(arrowCheckBox, + ActionEvent.ACTION_PERFORMED, arrowCheckBox.getActionCommand())); + } else if (element.getName().equals("vis_type")) { + String visTypeIdentifier = element.getText(); + Enumeration buttonEnum = visTypeSelectionGroup.getElements(); + while (buttonEnum.hasMoreElements()) { + AbstractButton button = buttonEnum.nextElement(); + if (button.getActionCommand().equals(visTypeIdentifier)) { + visTypeSelectionGroup.setSelected(button.getModel(), true); + button.getActionListeners()[0] + .actionPerformed(new ActionEvent(button, + ActionEvent.ACTION_PERFORMED, button.getActionCommand())); + } + } + } else if (element.getName().equals("background_image")) { + backgroundImageFile = new File(element.getText()); + if (backgroundImageFile.exists()) { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + backgroundImage = toolkit.getImage(backgroundImageFile.getAbsolutePath()); + + MediaTracker tracker = new MediaTracker(canvas); + tracker.addImage(backgroundImage, 1); + + try { + tracker.waitForAll(); + } catch (InterruptedException ex) { + logger.fatal("Interrupted during image loading, aborting"); + backgroundImage = null; + } + + } + } else if (element.getName().equals("back_start_x")) { + backgroundStartX = Double.parseDouble(element.getText()); + } else if (element.getName().equals("back_start_y")) { + backgroundStartY = Double.parseDouble(element.getText()); + } else if (element.getName().equals("back_width")) { + backgroundWidth = Double.parseDouble(element.getText()); + } else if (element.getName().equals("back_height")) { + backgroundHeight = Double.parseDouble(element.getText()); + } else if (element.getName().equals("resolution")) { + resolutionSlider.setValue(Integer.parseInt(element.getText())); + } else { + logger.fatal("Unknown configuration value: " + element.getName()); + } + } + + canvas.repaint(); + return true; + } + + +} diff --git a/tools/cooja/apps/arm/java/se/sics/arm/ChannelModel.java b/tools/cooja/apps/arm/java/se/sics/arm/ChannelModel.java new file mode 100644 index 000000000..56332bb3f --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/ChannelModel.java @@ -0,0 +1,1693 @@ +package se.sics.arm; + +import java.awt.geom.*; +import java.util.*; +import javax.swing.tree.DefaultMutableTreeNode; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import statistics.GaussianWrapper; + + +/** + * The channel model object in ARM 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); + + enum TransmissionData { SIGNAL_STRENGTH, SIGNAL_STRENGTH_VAR, SNR, SNR_VAR, PROB_OF_RECEPTION, DELAY_SPREAD, DELAY_SPREAD_RMS} + + private Properties parameters = new Properties(); + 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(); + + // Ray tracing components temporary vector + private boolean inLoggingMode = false; + private Vector savedRays = null; + private Vector> calculatedVisibleSides = new Vector>(); + private Vector calculatedVisibleSidesSources = new Vector(); + private Vector calculatedVisibleSidesLines = new Vector(); + private Vector calculatedVisibleSidesAngleIntervals = new Vector(); + 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(); + + // A random number generator + private Random random = new Random(); + + public ChannelModel() { + // - Set initial parameter values - + + // Using random variables + parameters.put("apply_random", new Boolean(false)); // TODO Should not use random variables as default + parameterDescriptions.put("apply_random", "Apply random values immediately"); + + // Signal to noise reception threshold + parameters.put("snr_threshold", new Double(6)); + parameterDescriptions.put("snr_threshold", "SNR reception threshold (dB)"); + + // Background noise mean + parameters.put("bg_noise_mean", new Double(-150)); + parameterDescriptions.put("bg_noise_mean", "Background noise mean (dBm)"); + + // Background noise variance + parameters.put("bg_noise_var", new Double(1)); + parameterDescriptions.put("bg_noise_var", "Background noise variance (dB)"); + + // Extra system gain mean + parameters.put("system_gain_mean", new Double(0)); + parameterDescriptions.put("system_gain_mean", "Extra system gain mean (dB)"); + + // Extra system gain variance + parameters.put("system_gain_var", new Double(4)); // TODO Should probably be default 0 or 1 + parameterDescriptions.put("system_gain_var", "Extra system gain variance (dB)"); + + // Transmission wavelength + parameters.put("wavelength", new Double(0.346)); // ~868 MHz (RFM TR1001) + parameterDescriptions.put("wavelength", "Wavelength w (m)"); + + // Transmitter output power + parameters.put("tx_power", new Double(1.5)); // dBm (deciBel milliwatts) + parameterDescriptions.put("tx_power", "Transmitter output power (dBm)"); + + // Transmitter antenna gain + parameters.put("tx_antenna_gain", new Double(0)); // TODO Should use angle + parameterDescriptions.put("tx_antenna_gain", "Transmitter antenna gain (dB)"); + + // Receiver sensitivity + parameters.put("rx_sensitivity", new Double(-100)); + parameterDescriptions.put("rx_sensitivity", "Receiver sensitivity (dBm)"); + + // Receiver antenna gain + parameters.put("rx_antenna_gain", new Double(0)); // TODO Should use angle + parameterDescriptions.put("rx_antenna_gain", "Receiver antenna gain (dB)"); + + // Ray Tracer - Disallow direct path + parameters.put("rt_disallow_direct_path", new Boolean(false)); + parameterDescriptions.put("rt_disallow_direct_path", "Disallow direct path"); + + // Ray Tracer - If direct path exists, ignore the non-direct (used for debugging) + parameters.put("rt_ignore_non_direct", new Boolean(false)); + parameterDescriptions.put("rt_ignore_non_direct", "If existing, only use direct path"); + + // Ray Tracer - Use FSPL on total length only + parameters.put("rt_fspl_on_total_length", new Boolean(true)); + parameterDescriptions.put("rt_fspl_on_total_length", "Use FSPL on total path lengths only"); + + // Ray Tracer - Max number of subrays + parameters.put("rt_max_rays", new Integer(1)); + parameterDescriptions.put("rt_max_rays", "Max path rays"); + + // Ray Tracer - Max number of refractions + parameters.put("rt_max_refractions", new Integer(1)); + parameterDescriptions.put("rt_max_refractions", "Max refractions"); + + // Ray Tracer - Max number of reflections + parameters.put("rt_max_reflections", new Integer(1)); + parameterDescriptions.put("rt_max_reflections", "Max reflections"); + + // Ray Tracer - Max number of diffractions + parameters.put("rt_max_diffractions", new Integer(0)); + parameterDescriptions.put("rt_max_diffractions", "Max diffractions"); + + // Ray Tracer - Use scattering + //parameters.put("rt_use_scattering", new Boolean(false)); // TODO Not used yet + //parameterDescriptions.put("rt_use_scattering", "Use simple scattering"); + + // Ray Tracer - Refraction coefficient + parameters.put("rt_refrac_coefficient", new Double(-3)); + parameterDescriptions.put("rt_refrac_coefficient", "Refraction coefficient (dB)"); + + // Ray Tracer - Reflection coefficient + parameters.put("rt_reflec_coefficient", new Double(-5)); + parameterDescriptions.put("rt_reflec_coefficient", "Reflection coefficient (dB)"); + + // Ray Tracer - Diffraction coefficient + parameters.put("rt_diffr_coefficient", new Double(-10)); + parameterDescriptions.put("rt_diffr_coefficient", "Diffraction coefficient (dB)"); + + // Ray Tracer - Scattering coefficient + //parameters.put("rt_scatt_coefficient", new Double(-20)); // TODO Not used yet + //parameterDescriptions.put("rt_scatt_coefficient", "!! Scattering coefficient (dB)"); + + // Shadowing - Obstacle Attenuation constant + parameters.put("obstacle_attenuation", new Double(-3)); + parameterDescriptions.put("obstacle_attenuation", "Obstacle attenuation (dB/m)"); + } + + /** + * 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(String 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(String id) { + return ((Double) getParameterValue(id)).doubleValue(); + } + + /** + * Returns an integer parameter value + * + * @param identifier Parameter identifier + * @return Current parameter value + */ + public int getParameterIntegerValue(String id) { + return ((Integer) getParameterValue(id)).intValue(); + } + + /** + * Returns a boolean parameter value + * + * @param identifier Parameter identifier + * @return Current parameter value + */ + public boolean getParameterBooleanValue(String id) { + return ((Boolean) getParameterValue(id)).booleanValue(); + } + + /** + * Saves a new parameter value + * + * @param id Parameter identifier + * @param newValue New parameter value + */ + public void setParameterValue(String 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(); + } + + /** + * Returns a parameter description + * + * @param identifier Parameter identifier + * @return Current parameter description + */ + public String getParameterDescription(String id) { + Object value = parameterDescriptions.get(id); + if (value == null) { + logger.fatal("No parameter description with id:" + id + ", aborting"); + return null; + } + return ((String) value); + } + + /** + * When this method is called all settings observers + * will be notified. + */ + public void notifySettingsChanged() { + settingsObservable.notifySettingsChanged(); + } + + /** + * Returns the Free Space Path Loss factor (in dB), by using + * parts of the Friis equation. (FSPL <= 0) + * + * @param distance Distance from transmitter to receiver + * @return FSPL factor + */ + protected double getFSPL(double distance) { + // From Friis equation: + // Pr(d) = Pt * (Gt * Gr * w2) / ( (4*PI)2 * d2 * L) + // For FSPL, ignoring Pt, Gt, Gr, L: + // Pr(d) = 1 * (1 * 1 * w2) / ( (4*PI)2 * d2 * 1) + // Pr(d) = w2 / ( (4*PI)2 * d2) + // Pr_dB(d) = 20*log10(w) - 20*log10(4*PI) - 20*log10(d) + + if (needToPrecalculateFSPL) { + double w = getParameterDoubleValue("wavelength"); + paramFSPL = 20*Math.log10(w) - 20*Math.log10(4*Math.PI); + needToPrecalculateFSPL = false; + } + + return Math.min(0.0, paramFSPL - 20*Math.log10(distance)); + } + + + /** + * 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 intersectedSides = new Vector(); + + 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 intersectingLinePoints = new Vector(); + + 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 visibleSides = getAllVisibleSides( + source.getX(), + source.getY(), + null, + line + ); + + // Create refracted subtrees + if (rayData.getRefractedSubRaysLimit() > 0 && visibleSides != null) { + Enumeration 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 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 diffractionSources = null; + if (rayData.getDiffractedSubRaysLimit() > 0) { + diffractionSources = getAllDiffractionSources(visibleSides); + } + + // Create diffraction subtrees + if (rayData.getDiffractedSubRaysLimit() > 0 && diffractionSources != null) { + Enumeration 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 getConnectingPaths(Point2D origin, Point2D dest, DefaultMutableTreeNode visibleLinesTree) { + Vector allPaths = new Vector(); + + // 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("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("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 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 getAllDiffractionSources(Vector allVisibleLines) { + Vector allDiffractionSources = new Vector(); + Enumeration 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 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 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 visibleLines = new Vector(); + Vector unhandledAngles = new Vector(); + + 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 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 visibleLineCandidates = new Vector(); + 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 croppedVisibleLineCandidates = new Vector(); + 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 newIntervalsToAdd = new Vector(); + + // Create angle interval of intersection between shadow and visible candidate + AngleInterval intersectedInterval = + visibleLineCandidateAngleInterval.intersectWith(shadowLineCandidateAngleInterval); + if (intersectedInterval != null) { + Vector 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 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(double sourceX, double sourceY, double destX, double destY) { + return getTransmissionData(sourceX, sourceY, destX, destY, TransmissionData.SIGNAL_STRENGTH); + } + + // TODO Fix better data type support + private double[] getTransmissionData(double sourceX, double sourceY, double destX, double destY, TransmissionData dataType) { + Point2D source = new Point2D.Double(sourceX, sourceY); + Point2D dest = new Point2D.Double(destX, destY); + double accumulatedVariance = 0; + + // - Get all ray paths from source to destination - + RayData originRayData = new RayData( + RayData.RayType.ORIGIN, + source, + null, + getParameterIntegerValue("rt_max_rays"), + getParameterIntegerValue("rt_max_refractions"), + getParameterIntegerValue("rt_max_reflections"), + getParameterIntegerValue("rt_max_diffractions") + ); + + // TODO Current (changing) signal strength should be built into 'build visible lines' to speed up things! + + // Check if origin tree is already calculated and saved + DefaultMutableTreeNode visibleLinesTree = null; + visibleLinesTree = + buildVisibleLinesTree(originRayData); + + // Calculate all paths from source to destination, using above calculated tree + Vector allPaths = getConnectingPaths(source, dest, visibleLinesTree); + + if (inLoggingMode) { + logger.info("Saved rays:"); + Enumeration pathsEnum = allPaths.elements(); + while (pathsEnum.hasMoreElements()) { + RayPath currentPath = pathsEnum.nextElement(); + logger.info("* " + currentPath); + for (int i=0; i < currentPath.getSubPathCount(); i++) { + savedRays.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("rt_refrac_coefficient"); + } else if (subPathStartType == RayData.RayType.REFLECTION) { + pathGain[i] += getParameterDoubleValue("rt_reflec_coefficient"); + + // Add FSPL from last subpaths (if FSPL on individual rays) + if (!getParameterBooleanValue("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("rt_diffr_coefficient"); + + // Add FSPL from last subpaths (if FSPL on individual rays) + if (!getParameterBooleanValue("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("obstacle_attenuation"); + + Vector 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("rt_fspl_on_total_length") && accumulatedStraightLength > 0) { + pathGain[i] += getFSPL(accumulatedStraightLength); + } + + // Free space path loss on total path length? + if (getParameterBooleanValue("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 wavelength = getParameterDoubleValue("wavelength"); + 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 (inLoggingMode) { + logger.info("Adding ray path with gain " + pathGain[i] + " and phase " + (2*Math.PI * pathModdedLengths[i]/wavelength)); + } + } else if (inLoggingMode) { + pathModdedLengths[i] = (pathLengths[i] - pathLengths[bestSignalNr]) % wavelength; + logger.info("Not adding ray path with gain " + pathGain[i] + " and phase " + (2*Math.PI * pathModdedLengths[i]/wavelength)); + } + + } + + // Calculate resulting RMS delay spread + delaySpread /= speedOfLight; + delaySpreadRMS /= delaySpreadTotalWeight; + + + // Convert back to dB + totalPathGain = 10*Math.log10(Math.abs(totalPathGain)); + + if (inLoggingMode) { + logger.info("Total path gain:\t" + totalPathGain); + logger.info("Delay spread:\t" + delaySpread); + logger.info("RMS Delay spread:\t" + delaySpreadRMS); + } + + // - Calculate received power - + // Using formula (dB) + // Received power = Output power + System gain + Transmitter gain + Path Loss + Receiver gain + // TODO Update formulas + Random random = new Random(); + double outputPower = getParameterDoubleValue("tx_power"); + double systemGain = getParameterDoubleValue("system_gain_mean"); + if (getParameterBooleanValue("apply_random")) { + systemGain += Math.sqrt(getParameterDoubleValue("system_gain_var")) * random.nextGaussian(); + } else { + accumulatedVariance += getParameterDoubleValue("system_gain_var"); + } + double transmitterGain = getParameterDoubleValue("tx_antenna_gain"); // TODO Should depend on angle + + double receivedPower = outputPower + systemGain + transmitterGain + totalPathGain; + if (inLoggingMode) { + logger.info("Resulting received signal strength:\t" + receivedPower + " (" + accumulatedVariance + ")"); + } + + if (dataType == TransmissionData.DELAY_SPREAD || dataType == TransmissionData.DELAY_SPREAD_RMS) + return new double[] {delaySpread, delaySpreadRMS}; + + return new double[] {receivedPower, accumulatedVariance}; + } + + /** + * 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 All resulting rays of a simulated transmission from source to destination + */ + public Vector getRaysOfTransmission(double sourceX, double sourceY, double destX, double destY) { + + // Reset current rays vector + inLoggingMode = true; + savedRays = new Vector(); + + // Calculate rays, ignore power + getProbability(sourceX, sourceY, destX, destY, -Double.MAX_VALUE); + + inLoggingMode = false; + + return savedRays; + } + + /** + * 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 is the random + * variable mean, and 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(double sourceX, double sourceY, double destX, double destY, double interference) { + + // Calculate received signal strength + double[] signalStrength = getReceivedSignalStrength(sourceX, sourceY, destX, destY); + + double[] snrData = + new double[] { signalStrength[0], signalStrength[1], signalStrength[0] }; + + // Add antenna gain TODO Should depend on angle + snrData[0] += getParameterDoubleValue("rx_antenna_gain"); + + double noiseVariance = getParameterDoubleValue("bg_noise_var"); + double noiseMean = getParameterDoubleValue("bg_noise_mean"); + + if (interference > noiseMean) + noiseMean = interference; + + if (getParameterBooleanValue("apply_random")) { + noiseMean += Math.sqrt(noiseVariance) * random.nextGaussian(); + noiseVariance = 0; + } + + // Applying noise to calculate SNR + snrData[0] -= noiseMean; + snrData[1] += noiseVariance; + + if (inLoggingMode) { + logger.info("SNR at receiver:\t" + snrData[0] + " (" + snrData[1] + ")"); + } + return snrData; + } + + + /** + * Calculates and returns 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 Interfering signal strength + * + * @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(double sourceX, double sourceY, double destX, double destY, double interference) { + double[] snrData = getSINR(sourceX, sourceY, destX, destY, interference); + double snrMean = snrData[0]; + double snrVariance = snrData[1]; + double signalStrength = snrData[2]; + double threshold = getParameterDoubleValue("snr_threshold"); + double rxSensitivity = getParameterDoubleValue("rx_sensitivity"); + + // Check signal strength against receiver sensitivity and interference + if (rxSensitivity > signalStrength - snrMean && threshold < rxSensitivity + snrMean - signalStrength) { + if (inLoggingMode) { + logger.info("Signal to low for receiver sensitivity, increasing threshold"); + } + + // 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 (inLoggingMode) { + logger.info("Probability of reception: " + probReception); + } + + // 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(double sourceX, double sourceY, double destX, double destY) { + return getTransmissionData(sourceX, sourceY, destX, destY, TransmissionData.DELAY_SPREAD)[1]; + } + + /** + * Returns XML elements representing the current configuration. + * + * @see #setConfigXML(Collection) + * @return XML element collection + */ + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + Enumeration paramEnum = parameters.keys(); + while (paramEnum.hasMoreElements()) { + String name = (String) paramEnum.nextElement(); + element = new Element(name); + element.setText(parameters.get(name).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 configXML) { + for (Element element : configXML) { + if (element.getName().equals("obstacles")) { + myObstacleWorld = new ObstacleWorld(); + myObstacleWorld.setConfigXML(element.getChildren()); + } else { + // Assuming parameter value + + // Fetch current class before applying saved value + Object obj = parameters.get(element.getName()); + Class paramClass = obj.getClass(); + + if (paramClass == Double.class) { + parameters.put(element.getName(), new Double(Double.parseDouble(element.getText()))); + } else if (paramClass == Boolean.class) { + parameters.put(element.getName(), Boolean.parseBoolean(element.getText())); + } else if (paramClass == Integer.class) { + parameters.put(element.getName(), Integer.parseInt(element.getText())); + } else { + logger.fatal("Unsupported class type: " + paramClass); + } + } + } + needToPrecalculateFSPL = true; + needToPrecalculateOutputPower = true; + settingsObservable.notifySettingsChanged(); + return true; + } + +} + + diff --git a/tools/cooja/apps/arm/java/se/sics/arm/FormulaViewer.java b/tools/cooja/apps/arm/java/se/sics/arm/FormulaViewer.java new file mode 100644 index 000000000..e798bd825 --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/FormulaViewer.java @@ -0,0 +1,583 @@ +package se.sics.arm; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.NumberFormat; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * This plugin allows a user to reconfigure current radio channel parameters. + * + * @author Fredrik Osterlind + */ +@ClassDescription("ARM - Formula Viewer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class FormulaViewer extends se.sics.cooja.VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(FormulaViewer.class); + + private Simulation currentSimulation; + private AdvancedRadioMedium currentRadioMedium; + private ChannelModel currentChannelModel; + + private static Dimension labelDimension = new Dimension(240, 20); + private static NumberFormat doubleFormat = NumberFormat.getNumberInstance(); + private static NumberFormat integerFormat = NumberFormat.getIntegerInstance(); + + private Vector allIntegerParameters = new Vector(); + private Vector allDoubleParameters = new Vector(); + private Vector allBooleanParameters = new Vector(); + + private JPanel areaGeneral; + private JPanel areaTransmitter; + private JPanel areaReceiver; + private JPanel areaRayTracer; + private JPanel areaShadowing; + + /** + * Creates a new formula viewer. + * + * @param simulationToVisualize Simulation which holds the ARM channel model. + */ + public FormulaViewer(Simulation simulationToVisualize) { + super("Advanced Radio Medium - Formula Viewer"); + + currentSimulation = simulationToVisualize; + currentRadioMedium = (AdvancedRadioMedium) currentSimulation.getRadioMedium(); + currentChannelModel = currentRadioMedium.getChannelModel(); + + // -- Create and add GUI components -- + JPanel allComponents = new JPanel(); + allComponents.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + allComponents.setLayout(new BoxLayout(allComponents, BoxLayout.Y_AXIS)); + + JScrollPane scrollPane = new JScrollPane(allComponents); + scrollPane.setPreferredSize(new Dimension(500,400)); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + setContentPane(scrollPane); + + JPanel collapsableArea; + + // General parameters + collapsableArea = createCollapsableArea("General parameters", allComponents); + areaGeneral = collapsableArea; + + addBooleanParameter( + "apply_random", + currentChannelModel.getParameterDescription("apply_random"), + collapsableArea, + currentChannelModel.getParameterBooleanValue("apply_random") + ); + + addDoubleParameter( + "snr_threshold", + currentChannelModel.getParameterDescription("snr_threshold"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("snr_threshold") + ); + + addDoubleParameter( + "bg_noise_mean", + currentChannelModel.getParameterDescription("bg_noise_mean"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("bg_noise_mean") + ); + + addDoubleParameter( + "bg_noise_var", + currentChannelModel.getParameterDescription("bg_noise_var"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("bg_noise_var") + ); + + addDoubleParameter( + "system_gain_mean", + currentChannelModel.getParameterDescription("system_gain_mean"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("system_gain_mean") + ); + + addDoubleParameter( + "system_gain_var", + currentChannelModel.getParameterDescription("system_gain_var"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("system_gain_var") + ); + + addDoubleParameter( + "wavelength", + currentChannelModel.getParameterDescription("wavelength"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("wavelength") + ); + + // Transmitter parameters + collapsableArea = createCollapsableArea("Transmitter parameters", allComponents); + areaTransmitter = collapsableArea; + + addDoubleParameter( + "tx_power", + currentChannelModel.getParameterDescription("tx_power"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("tx_power") + ); + + addDoubleParameter( + "tx_antenna_gain", + currentChannelModel.getParameterDescription("tx_antenna_gain"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("tx_antenna_gain") + ); + + // Receiver parameters + collapsableArea = createCollapsableArea("Receiver parameters", allComponents); + areaReceiver = collapsableArea; + + addDoubleParameter( + "rx_sensitivity", + currentChannelModel.getParameterDescription("rx_sensitivity"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rx_sensitivity") + ); + + addDoubleParameter( + "rx_antenna_gain", + currentChannelModel.getParameterDescription("rx_antenna_gain"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rx_antenna_gain") + ); + + // Ray Tracer parameters + collapsableArea = createCollapsableArea("Ray Tracer parameters", allComponents); + areaRayTracer = collapsableArea; + + addBooleanParameter( + "rt_disallow_direct_path", + currentChannelModel.getParameterDescription("rt_disallow_direct_path"), + collapsableArea, + currentChannelModel.getParameterBooleanValue("rt_disallow_direct_path") + ); + + addBooleanParameter( + "rt_ignore_non_direct", + currentChannelModel.getParameterDescription("rt_ignore_non_direct"), + collapsableArea, + currentChannelModel.getParameterBooleanValue("rt_ignore_non_direct") + ); + + addBooleanParameter( + "rt_fspl_on_total_length", + currentChannelModel.getParameterDescription("rt_fspl_on_total_length"), + collapsableArea, + currentChannelModel.getParameterBooleanValue("rt_fspl_on_total_length") + ); + + addIntegerParameter( + "rt_max_rays", + currentChannelModel.getParameterDescription("rt_max_rays"), + collapsableArea, + currentChannelModel.getParameterIntegerValue("rt_max_rays") + ); + + addIntegerParameter( + "rt_max_refractions", + currentChannelModel.getParameterDescription("rt_max_refractions"), + collapsableArea, + currentChannelModel.getParameterIntegerValue("rt_max_refractions") + ); + + addIntegerParameter( + "rt_max_reflections", + currentChannelModel.getParameterDescription("rt_max_reflections"), + collapsableArea, + currentChannelModel.getParameterIntegerValue("rt_max_reflections") + ); + + addIntegerParameter( + "rt_max_diffractions", + currentChannelModel.getParameterDescription("rt_max_diffractions"), + collapsableArea, + currentChannelModel.getParameterIntegerValue("rt_max_diffractions") + ); + +/* addBooleanParameter( + "rt_use_scattering", + currentChannelModel.getParameterDescription("rt_use_scattering"), + collapsableArea, + currentChannelModel.getParameterBooleanValue("rt_use_scattering") + ); +*/ + addDoubleParameter( + "rt_refrac_coefficient", + currentChannelModel.getParameterDescription("rt_refrac_coefficient"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rt_refrac_coefficient") + ); + + addDoubleParameter( + "rt_reflec_coefficient", + currentChannelModel.getParameterDescription("rt_reflec_coefficient"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rt_reflec_coefficient") + ); + + addDoubleParameter( + "rt_diffr_coefficient", + currentChannelModel.getParameterDescription("rt_diffr_coefficient"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rt_diffr_coefficient") + ); + +/* addDoubleParameter( + "rt_scatt_coefficient", + currentChannelModel.getParameterDescription("rt_scatt_coefficient"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("rt_scatt_coefficient") + ); +*/ + // Shadowing parameters + collapsableArea = createCollapsableArea("Shadowing parameters", allComponents); + areaShadowing = collapsableArea; + + addDoubleParameter( + "obstacle_attenuation", + currentChannelModel.getParameterDescription("obstacle_attenuation"), + collapsableArea, + currentChannelModel.getParameterDoubleValue("obstacle_attenuation") + ); + + + + // Add channel model observer responsible to keep all GUI components synched + currentChannelModel.addSettingsObserver(channelModelSettingsObserver); + + // Set initial size etc. + pack(); + setVisible(true); + + // Tries to select this plugin + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + /** + * Creates a new collapsable area which may be used for holding model parameters. + * @param title Title of area + * @param contentPane Where this area should be added + * @return New empty collapsable area + */ + private JPanel createCollapsableArea(String title, Container contentPane) { + // Create panels + JPanel holdingPanel = new JPanel() { + public Dimension getMaximumSize() { + return new Dimension(super.getMaximumSize().width, getPreferredSize().height); + } + }; + holdingPanel.setLayout(new BoxLayout(holdingPanel, BoxLayout.Y_AXIS)); + + final JPanel collapsableArea = new JPanel() { + public Dimension getMaximumSize() { + return new Dimension(super.getMaximumSize().width, getPreferredSize().height); + } + }; + collapsableArea.setLayout(new BoxLayout(collapsableArea, BoxLayout.Y_AXIS)); + collapsableArea.setVisible(false); + + JPanel titlePanel = new JPanel(new BorderLayout()) { + public Dimension getMaximumSize() { + return new Dimension(super.getMaximumSize().width, getPreferredSize().height); + } + }; + + titlePanel.add(BorderLayout.WEST, new JLabel(title)); + JCheckBox collapseCheckBox = new JCheckBox("show settings", false); + collapseCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (((JCheckBox) e.getSource()).isSelected()) + collapsableArea.setVisible(true); + else + collapsableArea.setVisible(false); + } + }); + collapsableArea.putClientProperty("my_checkbox", collapseCheckBox); + + titlePanel.add(BorderLayout.EAST, collapseCheckBox); + + collapsableArea.setBorder( + BorderFactory.createLineBorder(Color.LIGHT_GRAY) + ); + collapsableArea.setAlignmentY(Component.TOP_ALIGNMENT); + + holdingPanel.add(titlePanel); + holdingPanel.add(collapsableArea); + + contentPane.add(holdingPanel); + return collapsableArea; + } + + /** + * Creates and adds a panel with a label and a + * text field which accepts doubles. + * + * @param id Identifier of new parameter + * @param description Description of new parameter + * @param contentPane Where to add created panel + * @param initialValue Initial value + * @return Text field in created panel + */ + private JFormattedTextField addDoubleParameter(String id, String description, Container contentPane, double initialValue) { + JPanel panel = new JPanel(); + JLabel label; + JFormattedTextField textField; + + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setAlignmentY(Component.TOP_ALIGNMENT); + panel.add(Box.createHorizontalStrut(10)); + panel.add(label = new JLabel(description)); + label.setPreferredSize(labelDimension); + panel.add(Box.createHorizontalGlue()); + panel.add(textField = new JFormattedTextField(doubleFormat)); + textField.setValue(new Double(initialValue)); + textField.setColumns(4); + textField.putClientProperty("id", id); + textField.addPropertyChangeListener("value", new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + Object sourceObject = e.getSource(); + Double newValue = ((Number) e.getNewValue()).doubleValue(); + String id = (String) ((JFormattedTextField) sourceObject).getClientProperty("id"); + currentChannelModel.setParameterValue(id, newValue); + } + }); + allDoubleParameters.add(textField); + + contentPane.add(panel); + + return textField; + } + + /** + * Creates and adds a panel with a label and a + * text field which accepts integers. + * + * @param id Identifier of new parameter + * @param description Description of new parameter + * @param contentPane Where to add created panel + * @param initialValue Initial value + * @return Text field in created panel + */ + private JFormattedTextField addIntegerParameter(String id, String description, Container contentPane, int initialValue) { + JPanel panel = new JPanel(); + JLabel label; + JFormattedTextField textField; + + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setAlignmentY(Component.TOP_ALIGNMENT); + panel.add(Box.createHorizontalStrut(10)); + panel.add(label = new JLabel(description)); + label.setPreferredSize(labelDimension); + panel.add(Box.createHorizontalGlue()); + panel.add(textField = new JFormattedTextField(integerFormat)); + textField.setValue(new Double(initialValue)); + textField.setColumns(4); + textField.putClientProperty("id", id); + textField.addPropertyChangeListener("value", new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + Object sourceObject = e.getSource(); + Integer newValue = ((Number) e.getNewValue()).intValue(); + String id = (String) ((JFormattedTextField) sourceObject).getClientProperty("id"); + currentChannelModel.setParameterValue(id, newValue); + } + }); + + allIntegerParameters.add(textField); + + contentPane.add(panel); + + return textField; + } + + /** + * Creates and adds a panel with a label and a + * boolean checkbox. + * + * @param id Identifier of new parameter + * @param description Description of new parameter + * @param contentPane Where to add created panel + * @param initialValue Initial value + * @return Checkbox in created panel + */ + private JCheckBox addBooleanParameter(String id, String description, Container contentPane, boolean initialValue) { + JPanel panel = new JPanel(); + JLabel label; + JCheckBox checkBox; + + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setAlignmentY(Component.TOP_ALIGNMENT); + panel.add(Box.createHorizontalStrut(10)); + panel.add(label = new JLabel(description)); + label.setPreferredSize(labelDimension); + panel.add(Box.createHorizontalGlue()); + panel.add(checkBox = new JCheckBox()); + checkBox.setSelected(initialValue); + checkBox.putClientProperty("id", id); + checkBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JCheckBox source = (JCheckBox) e.getSource(); + currentChannelModel.setParameterValue( + (String) source.getClientProperty("id"), + new Boolean(source.isSelected()) + ); + } + }); + + allBooleanParameters.add(checkBox); + + contentPane.add(panel); + + return checkBox; + } + + /** + * Creates and adds a panel with a description label. + * + * @param description Description of new parameter + * @param contentPane Where to add created panel + * @return Created label + */ + private JLabel addLabelParameter(String description, Container contentPane) { + JPanel panel = new JPanel(); + JLabel label; + + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.setAlignmentY(Component.TOP_ALIGNMENT); + panel.add(Box.createHorizontalStrut(10)); + panel.add(label = new JLabel(description)); + label.setPreferredSize(labelDimension); + panel.add(Box.createHorizontalGlue()); + + contentPane.add(panel); + + return label; + } + + /** + * Listens to settings changes in the channel model. + * If it changes, all GUI parameters are updated accordingly. + */ + private Observer channelModelSettingsObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Update all integers + for (int i=0; i < allIntegerParameters.size(); i++) { + JFormattedTextField textField = (JFormattedTextField) allIntegerParameters.get(i); + String id = (String) textField.getClientProperty("id"); + textField.setValue(currentChannelModel.getParameterValue(id)); + } + + // Update all doubles + for (int i=0; i < allDoubleParameters.size(); i++) { + JFormattedTextField textField = (JFormattedTextField) allDoubleParameters.get(i); + String id = (String) textField.getClientProperty("id"); + textField.setValue(currentChannelModel.getParameterValue(id)); + } + + // Update all booleans + for (int i=0; i < allBooleanParameters.size(); i++) { + JCheckBox checkBox = (JCheckBox) allBooleanParameters.get(i); + String id = (String) checkBox.getClientProperty("id"); + checkBox.setSelected(currentChannelModel.getParameterBooleanValue(id)); + } + + repaint(); + } + }; + + public void closePlugin() { + // Remove the channel model observer + if (currentChannelModel != null && channelModelSettingsObserver != null) { + currentChannelModel.deleteSettingsObserver(channelModelSettingsObserver); + } else { + logger.fatal("Can't remove channel model observer: " + channelModelSettingsObserver); + } + } + + /** + * Returns XML elements representing the current configuration. + * + * @see #setConfigXML(Collection) + * @return XML element collection + */ + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + element = new Element("show_general"); + element.setText(Boolean.toString(areaGeneral.isVisible())); + config.add(element); + element = new Element("show_transmitter"); + element.setText(Boolean.toString(areaTransmitter.isVisible())); + config.add(element); + element = new Element("show_receiver"); + element.setText(Boolean.toString(areaReceiver.isVisible())); + config.add(element); + element = new Element("show_raytracer"); + element.setText(Boolean.toString(areaRayTracer.isVisible())); + config.add(element); + element = new Element("show_shadowing"); + element.setText(Boolean.toString(areaShadowing.isVisible())); + 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 configXML) { + for (Element element : configXML) { + if (element.getName().equals("show_general")) { + JCheckBox checkBox = (JCheckBox) areaGeneral.getClientProperty("my_checkbox"); + checkBox.setSelected(Boolean.parseBoolean(element.getText())); + checkBox.getActionListeners()[0].actionPerformed(new ActionEvent(checkBox, + ActionEvent.ACTION_PERFORMED, "")); + } else if (element.getName().equals("show_transmitter")) { + JCheckBox checkBox = (JCheckBox) areaTransmitter.getClientProperty("my_checkbox"); + checkBox.setSelected(Boolean.parseBoolean(element.getText())); + checkBox.getActionListeners()[0].actionPerformed(new ActionEvent(checkBox, + ActionEvent.ACTION_PERFORMED, "")); + } else if (element.getName().equals("show_receiver")) { + JCheckBox checkBox = (JCheckBox) areaReceiver.getClientProperty("my_checkbox"); + checkBox.setSelected(Boolean.parseBoolean(element.getText())); + checkBox.getActionListeners()[0].actionPerformed(new ActionEvent(checkBox, + ActionEvent.ACTION_PERFORMED, "")); + } else if (element.getName().equals("show_raytracer")) { + JCheckBox checkBox = (JCheckBox) areaRayTracer.getClientProperty("my_checkbox"); + checkBox.setSelected(Boolean.parseBoolean(element.getText())); + checkBox.getActionListeners()[0].actionPerformed(new ActionEvent(checkBox, + ActionEvent.ACTION_PERFORMED, "")); + } else if (element.getName().equals("show_shadowing")) { + JCheckBox checkBox = (JCheckBox) areaShadowing.getClientProperty("my_checkbox"); + checkBox.setSelected(Boolean.parseBoolean(element.getText())); + checkBox.getActionListeners()[0].actionPerformed(new ActionEvent(checkBox, + ActionEvent.ACTION_PERFORMED, "")); + } + } + return true; + } + +} diff --git a/tools/cooja/apps/arm/java/se/sics/arm/Interval.java b/tools/cooja/apps/arm/java/se/sics/arm/Interval.java new file mode 100644 index 000000000..0af30610b --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/Interval.java @@ -0,0 +1,205 @@ +package se.sics.arm; + +import java.util.Vector; +import org.apache.log4j.Logger; + +/** + * This class represents a interval. Some operations on these intervals exist, + * such as intersecting a interval with another and subtracting an interval from + * another. + * + * @author Fredrik Osterlind + */ +class Interval { + private static Logger logger = Logger.getLogger(Interval.class); + + private double lowValue; + private double highValue; + + /** + * Creates a new double interval. + * The given low value must be lower than the given high value. + * + * @param lowValue Low interval border (< End interval border) + * @param highValue High interval border + */ + public Interval(double lowValue, double highValue) { + this.lowValue = Math.min(lowValue, highValue); + this.highValue = highValue; + } + + /** + * Set new values of interval + * + * @param newLow New low value + * @param newHigh New high value + */ + public void setInterval(double newLow, double newHigh) { + lowValue = newLow; + highValue = newHigh; + } + + /** + * @return Low border value + */ + public double getLow() { + return lowValue; + } + + /** + * @return High border value + */ + public double getHigh() { + return highValue; + } + + /** + * @return Size of interval + */ + public double getSize() { + return highValue - lowValue; + } + + /** + * Returns the intersection between this interval and the given + * interval or null if no intersection exists. + * + * @param interval Other interval + * @return Intersection interval + */ + public Interval intersectWith(Interval interval) { + // Given interval higher than this interval + if (highValue <= interval.getLow()) + return null; + + // Given interval lower than this interval + if (lowValue >= interval.getHigh()) + return null; + + // Given interval covers this interval + if (lowValue >= interval.getLow() && + highValue <= interval.getHigh()) + return this; + + // Given interval inside this interval + if (lowValue <= interval.getLow() && + highValue >= interval.getHigh()) + return interval; + + // Given interval overlaps lower part of this interval + if (lowValue >= interval.getLow() && + highValue >= interval.getHigh()) + return new Interval(lowValue, interval.getHigh()); + + // Given interval overlaps upper part of this interval + if (lowValue <= interval.getLow() && + highValue <= interval.getHigh()) + return new Interval(interval.getLow(), highValue); + + logger.fatal("DoubleInterval.intersectWithInterval(), error!"); + return null; + } + + /** + * Checks if the given interval is a subset of this interval. + * + * @param interval Other interval + * @return True if this interval contains the given interval + */ + public boolean contains(Interval interval) { + return getLow() <= interval.getLow() && getHigh() >= interval.getHigh(); + } + + /** + * Returns new intervals consisting of this interval with the given interval removed. + * These can either be null (if entire interval was removed), + * one interval (if upper or lower part, or nothing was removed) or two intervals + * (if middle part of interval was removed). + * + * @param interval Other interval + * @return New intervals + */ + public Vector subtract(Interval interval) { + Vector returnIntervals = new Vector(); + + // Given interval higher than this interval + if (highValue <= interval.getLow()) { + returnIntervals.add(this); + return returnIntervals; + } + + // Given interval lower than this interval + if (lowValue >= interval.getHigh()) { + returnIntervals.add(this); + return returnIntervals; + } + + // Given interval covers this interval + if (lowValue >= interval.getLow() && + highValue <= interval.getHigh()) { + return null; + } + + // Given interval inside this interval + if (lowValue <= interval.getLow() && + highValue >= interval.getHigh()) { + returnIntervals.add(new Interval(lowValue, interval.getLow())); + returnIntervals.add(new Interval(interval.getHigh(), highValue)); + return returnIntervals; + } + + // Given interval overlaps lower part of this interval + if (lowValue >= interval.getLow() && + highValue >= interval.getHigh()) { + returnIntervals.add(new Interval(interval.getHigh(), highValue)); + return returnIntervals; + } + + // Given interval overlaps upper part of this interval + if (lowValue <= interval.getLow() && + highValue <= interval.getHigh()) { + returnIntervals.add(new Interval(lowValue, interval.getLow())); + return returnIntervals; + } + + logger.fatal("DoubleInterval.subtractInterval(), error!"); + return null; + } + + /** + * Subtracts given interval from all intervals in given vector. + * This method never returns null (but empty vectors). + * + * @param initialIntervals Initial intervals + * @param interval Interval to subtract + * @return New intervals + */ + static public Vector subtract(Vector initialIntervals, Interval interval) { + Vector newIntervals = new Vector(); + for (int i=0; i < initialIntervals.size(); i++) { + Vector tempIntervals = initialIntervals.get(i).subtract(interval); + if (tempIntervals != null) + newIntervals.addAll(tempIntervals); + } + return newIntervals; + } + + /** + * @return True if interval does not have a length. + */ + public boolean isEmpty() { + if (highValue <= lowValue) + return true; + return false; + } + + public String toString() { + if (isEmpty()) + return "Double interval: (null)"; + else + return "Double interval: " + + getLow() + + " -> " + + getHigh(); + } +} \ No newline at end of file diff --git a/tools/cooja/apps/arm/java/se/sics/arm/ObstacleWorld.java b/tools/cooja/apps/arm/java/se/sics/arm/ObstacleWorld.java new file mode 100644 index 000000000..3a0d288f4 --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/ObstacleWorld.java @@ -0,0 +1,621 @@ +package se.sics.arm; + +import java.awt.Point; +import java.awt.geom.*; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.log4j.Logger; +import org.jdom.Element; + +/** + * This class represents an area with obstacles. + * Obstacles may only be of rectangular shape. + * + * @author Fredrik Osterlind + */ +class ObstacleWorld { + private static Logger logger = Logger.getLogger(ObstacleWorld.class); + + // All registered obstacles + private Vector allObstacles = null; + + // All registered obstacles, with spatial information + private int spatialResolution = 10; + private Vector[][] allObstaclesSpatial = new Vector[spatialResolution][spatialResolution]; + private boolean obstaclesOrganized = false; + + // Outer bounds of all obstacles + private Rectangle2D outerBounds = null; + + + /** + * Creates a new obstacle world without any obstacles. + */ + public ObstacleWorld() { + // No obstacles present so far + allObstacles = new Vector(); + + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) + allObstaclesSpatial[x][y] = new Vector(); + + outerBounds = new Rectangle2D.Double(0,0,0,0); + } + + /** + * @return The total number of registered obstacles + */ + public int getNrObstacles() { + return allObstacles.size(); + } + + /** + * This method can be used to find extreme coordinates of all obstacles. + * + * @return Outer bounds of all registered obstacles + */ + public Rectangle2D getOuterBounds() { + return outerBounds; + } + + /** + * Returns obstacle registered at given position. + * The coordinates of an obstacles should never + * be changed directly on an object returned by this method. + * + * @param i Obstacle position + * @return Obstacle at given position + */ + public Rectangle2D getObstacle(int i) { + return allObstacles.get(i); + } + + /** + * @return All registered obstacles + */ + public Vector getAllObstacles() { + return allObstacles; + } + + /** + * Returns at least all registered obstacles that contains given point. + * Note that obstacles close to but not containing the point may also + * be returned. + * + * @param center Center point + * @return All obstacles containing or near center + */ + public Vector getAllObstaclesNear(Point2D center) { + double boxWidth = outerBounds.getWidth() / (double) spatialResolution; + double boxHeight = outerBounds.getHeight() / (double) spatialResolution; + double areaStartX = outerBounds.getMinX(); + double areaStartY = outerBounds.getMinY(); + + double centerX = (center.getX() - areaStartX)/boxWidth; + double centerY = (center.getY() - areaStartY)/boxHeight; + + Vector allNearObstacles = new Vector(); + + Point pointToAdd = new Point((int) centerX, (int) centerY); + if (pointToAdd.x >= 0 && + pointToAdd.x < allObstaclesSpatial.length && + pointToAdd.y >= 0 && + pointToAdd.y < allObstaclesSpatial[0].length) + allNearObstacles.addAll(allObstaclesSpatial[pointToAdd.x][pointToAdd.y]); + + // Add borders if needed + boolean addedXBorder = false; + boolean addedYBorder = false; + if (Math.floor(centerX) == centerX) { + pointToAdd = new Point((int) centerX-1, (int) centerY); + if (pointToAdd.x >= 0 && + pointToAdd.x < allObstaclesSpatial.length && + pointToAdd.y >= 0 && + pointToAdd.y < allObstaclesSpatial[0].length) { + allNearObstacles.addAll(allObstaclesSpatial[pointToAdd.x][pointToAdd.y]); + addedXBorder = true; + } + } + + if (Math.floor(centerY) == centerY) { + pointToAdd = new Point((int) centerX, (int) centerY-1); + if (pointToAdd.x >= 0 && + pointToAdd.x < allObstaclesSpatial.length && + pointToAdd.y >= 0 && + pointToAdd.y < allObstaclesSpatial[0].length) { + allNearObstacles.addAll(allObstaclesSpatial[pointToAdd.x][pointToAdd.y]); + addedYBorder = true; + } + } + + if (addedXBorder && addedYBorder) { + pointToAdd = new Point((int) centerX-1, (int) centerY-1); + allNearObstacles.addAll(allObstaclesSpatial[pointToAdd.x][pointToAdd.y]); + } + + return allNearObstacles; + } + + /** + * Returns at least all registered obstacles inside the given angle + * interval when at the given center point. Note that obstacles partly or + * completely outside the interval may also be returned. + * All obstacles are preferably returned in order of distance from given + * center point, although this is not guaranteed. + * + * @param center Center point + * @param angleInterval Angle interval + * @return All obstacles in given angle interval + */ + public Vector getAllObstaclesInAngleInterval(Point2D center, AngleInterval angleInterval) { + Vector obstaclesToReturn = new Vector(); + if (!obstaclesOrganized) { + reorganizeSpatialObstacles(); + } + + double boxWidth = outerBounds.getWidth() / (double) spatialResolution; + double boxHeight = outerBounds.getHeight() / (double) spatialResolution; + double areaStartX = outerBounds.getMinX(); + double areaStartY = outerBounds.getMinY(); + + // Calculate which boxes to check (and in which order) + Point centerInArray = new Point( + (int) ((center.getX() - areaStartX)/boxWidth), + (int) ((center.getY() - areaStartY)/boxHeight) + ); + Vector pointsToCheck = new Vector(); + + int currentDistance = 0; + while (currentDistance < 2*spatialResolution) { + + if (currentDistance > 0) { + int currentX = centerInArray.x - currentDistance; + int currentY = centerInArray.y - currentDistance; + + // Step right + while (currentX < centerInArray.x + currentDistance) { + if (currentX >= 0 && + currentX < allObstaclesSpatial.length && + currentY >= 0 && + currentY < allObstaclesSpatial[0].length) + pointsToCheck.add(new Point(currentX, currentY)); + currentX++; + } + + // Step right + while (currentY < centerInArray.y + currentDistance) { + if (currentX >= 0 && + currentX < allObstaclesSpatial.length && + currentY >= 0 && + currentY < allObstaclesSpatial[0].length) + pointsToCheck.add(new Point(currentX, currentY)); + currentY++; + } + + // Step left + while (currentX > centerInArray.x - currentDistance) { + if (currentX >= 0 && + currentX < allObstaclesSpatial.length && + currentY >= 0 && + currentY < allObstaclesSpatial[0].length) + pointsToCheck.add(new Point(currentX, currentY)); + currentX--; + } + + // Step up + while (currentY > centerInArray.y - currentDistance) { + if (currentX >= 0 && + currentX < allObstaclesSpatial.length && + currentY >= 0 && + currentY < allObstaclesSpatial[0].length) + pointsToCheck.add(new Point(currentX, currentY)); + currentY--; + } + + } else { + if (centerInArray.x >= 0 && + centerInArray.x < allObstaclesSpatial.length && + centerInArray.y >= 0 && + centerInArray.y < allObstaclesSpatial[0].length) { + pointsToCheck.add(new Point(centerInArray.x, centerInArray.y)); + } + } + currentDistance++; + } + + for (int pointNr=0; pointNr < pointsToCheck.size(); pointNr++) { + // Check which obstacles should be in this box + boolean hit = false; + int x = pointsToCheck.get(pointNr).x; + int y = pointsToCheck.get(pointNr).y; + + // Test if we are inside test box + if (!hit) { + if (new Rectangle2D.Double( + areaStartX + x*boxWidth, + areaStartY + y*boxHeight, + boxWidth, + boxHeight).contains(center)) { + hit = true; + for (int i=0; i < allObstaclesSpatial[x][y].size(); i++) { + if (!obstaclesToReturn.contains(allObstaclesSpatial[x][y].get(i))) + obstaclesToReturn.add(allObstaclesSpatial[x][y].get(i)); + } + } + } + + // Test first diagonal + if (!hit) { + AngleInterval testInterval = AngleInterval.getAngleIntervalOfLine( + center, + new Line2D.Double( + areaStartX + x*boxWidth, + areaStartY + y*boxHeight, + areaStartX + (x+1)*boxWidth, + areaStartY + (y+1)*boxHeight) + ); + if (testInterval.intersects(angleInterval)) { + hit = true; + for (int i=0; i < allObstaclesSpatial[x][y].size(); i++) { + if (!obstaclesToReturn.contains(allObstaclesSpatial[x][y].get(i))) + obstaclesToReturn.add(allObstaclesSpatial[x][y].get(i)); + } + } + } + + // Test second diagonal + if (!hit) { + AngleInterval testInterval = AngleInterval.getAngleIntervalOfLine( + center, + new Line2D.Double( + areaStartX + x*boxWidth, + areaStartY + (y+1)*boxHeight, + areaStartX + (x+1)*boxWidth, + areaStartY + y*boxHeight) + ); + if (testInterval.intersects(angleInterval)) { + hit = true; + for (int i=0; i < allObstaclesSpatial[x][y].size(); i++) { + if (!obstaclesToReturn.contains(allObstaclesSpatial[x][y].get(i))) + obstaclesToReturn.add(allObstaclesSpatial[x][y].get(i)); + } + } + } + } + + return obstaclesToReturn; + } + + /** + * Removes all registered obstacles. + */ + public void removeAll() { + allObstacles.removeAllElements(); + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) + allObstaclesSpatial[x][y].removeAllElements(); + + outerBounds = new Rectangle2D.Double(0,0,0,0); + } + + /** + * Returns true of given point is on a corner of + * any of the structures build from the obstacles. + * Internally this method checks how many of four point + * close to and located around given point (diagonally) are + * inside any obstacle. + * This method returns true if exactly one point is inside an obstacle. + * + * @param point Point to check + * @return True of point is on a corner, false otherwise + */ + public boolean pointIsNearCorner(Point2D point) { + double boxWidth = outerBounds.getWidth() / (double) spatialResolution; + double boxHeight = outerBounds.getHeight() / (double) spatialResolution; + double areaStartX = outerBounds.getMinX(); + double areaStartY = outerBounds.getMinY(); + + // Which obstacles should be checked + Point centerInArray = new Point( + (int) ((point.getX() - areaStartX)/boxWidth), + (int) ((point.getY() - areaStartY)/boxHeight) + ); + Vector allObstaclesToCheck = null; + if (centerInArray.x < 0) + centerInArray.x = 0; + if (centerInArray.x >= spatialResolution) + centerInArray.x = spatialResolution-1; + if (centerInArray.y < 0) + centerInArray.y = 0; + if (centerInArray.y >= spatialResolution) + centerInArray.y = spatialResolution-1; + + allObstaclesToCheck = allObstaclesSpatial[centerInArray.x][centerInArray.y]; + + if (allObstaclesToCheck.size() == 0) { + return false; + } + + // Create the four point to check + double deltaDistance = 0.01; // 1 cm TODO Change this? + Point2D point1 = new Point2D.Double(point.getX() - deltaDistance, point.getY() - deltaDistance); + Point2D point2 = new Point2D.Double(point.getX() - deltaDistance, point.getY() + deltaDistance); + Point2D point3 = new Point2D.Double(point.getX() + deltaDistance, point.getY() - deltaDistance); + Point2D point4 = new Point2D.Double(point.getX() + deltaDistance, point.getY() + deltaDistance); + + int containedPoints = 0; + Enumeration allObstaclesToCheckEnum = allObstaclesToCheck.elements(); + while (allObstaclesToCheckEnum.hasMoreElements()) { + Rectangle2D obstacleToCheck = allObstaclesToCheckEnum.nextElement(); + if (obstacleToCheck.contains(point1)) + containedPoints++; + if (obstacleToCheck.contains(point2)) + containedPoints++; + if (obstacleToCheck.contains(point3)) + containedPoints++; + if (obstacleToCheck.contains(point4)) + containedPoints++; + + // Abort if already to many contained points + if (containedPoints > 1) { + return false; + } + } + + return (containedPoints == 1); + } + + /** + * Checks if specified obstacle can be merged with any existing obstacle + * in order to reduce the total number of obstacles. And in that case a merge + * is performed and this method returns the new obstacle object. + * The checking is performed by looping through all existing obstacles and + * for each one comparing the union area of it and the given obstacle to the + * area sum of the two. And since obstacles are not allowed to overlap, if the + * union area is equal to the area sum, they can be merged. + * If a merge is performed, another may be made possible so this method + * should be looped until returning null. + * + * This method does not notify observers of changes made! + * + * @return New object of a merge was performed, null otherwise + */ + private Rectangle2D mergeObstacle(Rectangle2D mergeObstacle) { + double mergeObstacleArea = mergeObstacle.getWidth() * mergeObstacle.getHeight(); + double mergeObstacleTolerance = mergeObstacleArea * 0.01; // 1% + + // Loop through all existing obstacles (but ignore itself) + for (int i=0; i < getNrObstacles(); i++) { + Rectangle2D existingObstacle = getObstacle(i); + if (!existingObstacle.equals(mergeObstacle)) { + double existingObstacleArea = existingObstacle.getWidth() * existingObstacle.getHeight(); + Rectangle2D unionObstacle = existingObstacle.createUnion(mergeObstacle); + double unionArea = unionObstacle.getWidth() * unionObstacle.getHeight(); + + // Fault-tolerance + double faultTolerance = Math.min(mergeObstacleTolerance, existingObstacleArea*0.01); + + // Compare areas + if (unionArea - faultTolerance <= existingObstacleArea + mergeObstacleArea) { + // Remove both old obstacles, add union + removeObstacle(mergeObstacle); + removeObstacle(existingObstacle); + addObstacle(unionObstacle, false); + + obstaclesOrganized = false; + return unionObstacle; + } + } + } + + return null; + } + + /** + * Register new obstacle with given attributes. + * This method will try to merge this obstacle with other already existing obstacles. + * + * @param startX Start X coordinate + * @param startY Start Y coordinate + * @param width Width + * @param height Height + */ + public void addObstacle(double startX, double startY, double width, double height) { + addObstacle(startX, startY, width, height, true); + } + + /** + * Register new obstacle with given attributes. + * This method will, depending on given argument, try to merge + * this obstacle with other already existing obstacles. + * + * @param startX Start X coordinate + * @param startY Start Y coordinate + * @param width Width + * @param height Height + * @param merge Should this obstacle, if possible, be merged with existing obstacles + */ + public void addObstacle(double startX, double startY, double width, double height, boolean merge) { + Rectangle2D newRect = new Rectangle2D.Double(startX, startY, width, height); + addObstacle(newRect, merge); + } + + /** + * Registers a given obstacle. + * This method will try to merge this obstacle with other already existing obstacles. + * + * @param obstacle New obstacle + */ + public void addObstacle(Rectangle2D obstacle) { + addObstacle(obstacle, true); + } + + /** + * Registers a given obstacle. + * This method will, depending on the given argument, try to + * merge this obstacle with other already existing obstacles. + * + * @param obstacle New obstacle + */ + public void addObstacle(Rectangle2D obstacle, boolean merge) { + // TODO Should we keep the rounding? + obstacle.setRect( + Math.round(obstacle.getMinX()*1000.0) / 1000.0, + Math.round(obstacle.getMinY()*1000.0) / 1000.0, + Math.round(obstacle.getWidth()*1000.0) / 1000.0, + Math.round(obstacle.getHeight()*1000.0) / 1000.0 + ); + + allObstacles.add(obstacle); + outerBounds = outerBounds.createUnion(obstacle); + + if (merge) { + // Check if obstacle can be merged with another obstacle + Rectangle2D mergedObstacle = mergeObstacle(obstacle); + + // Keep merging... + while (mergedObstacle != null) + mergedObstacle = mergeObstacle(mergedObstacle); + } + + obstaclesOrganized = false; + } + + /** + * Remove the given obstacle, if it exists. + * + * @param obstacle Obstacle to remove + */ + public void removeObstacle(Rectangle2D obstacle) { + allObstacles.remove(obstacle); + + recreateOuterBounds(); + obstaclesOrganized = false; + } + + /** + * This method recreates the outer bounds of + * this obstacle area by checking all registered + * obstacles. + * This method should never have to be called directly + * by a user. + */ + public void recreateOuterBounds() { + outerBounds = new Rectangle2D.Double(0,0,0,0); + for (int i=0; i < allObstacles.size(); i++) { + outerBounds = outerBounds.createUnion(allObstacles.get(i)); + } + obstaclesOrganized = false; + } + + /** + * Reorganizes all registered obstacles in order to speed up + * searches for obstacles in spatial areas. + * This method is run automatically + */ + public void reorganizeSpatialObstacles() { + // Remove all spatial obstacles + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) + allObstaclesSpatial[x][y].removeAllElements(); + + double boxWidth = outerBounds.getWidth() / (double) spatialResolution; + double boxHeight = outerBounds.getHeight() / (double) spatialResolution; + double currentBoxMinX = outerBounds.getMinX(); + double currentBoxMinY = outerBounds.getMinY(); + + // For each box, add obstacles that belong there + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) { + // Check which obstacles should be in this box + Rectangle2D boxToCheck = new Rectangle2D.Double(currentBoxMinX + x*boxWidth, currentBoxMinY + y*boxHeight, boxWidth, boxHeight); + for (int i=0; i < allObstacles.size(); i++) { + if (allObstacles.get(i).intersects(boxToCheck)) { + allObstaclesSpatial[x][y].add(allObstacles.get(i)); + } + } + } + + obstaclesOrganized = true; + + //printObstacleGridToConsole(); + } + + /** + * Prints a description of all obstacles to the console + */ + public void printObstacleGridToConsole() { + logger.info("<<<<<<< printObstacleGridToConsole >>>>>>>"); + logger.info(". Number of obstacles:\t" + getNrObstacles()); + logger.info(". Outer boundary min:\t" + getOuterBounds().getMinX() + ", " + getOuterBounds().getMinY()); + logger.info(". Outer boundary max:\t" + getOuterBounds().getMaxX() + ", " + getOuterBounds().getMaxY()); + + Vector uniqueSpatialObstacles = new Vector(); + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) + for (int i=0; i < allObstaclesSpatial[x][y].size(); i++) + if (!uniqueSpatialObstacles.contains(allObstaclesSpatial[x][y].get(i))) + uniqueSpatialObstacles.add(allObstaclesSpatial[x][y].get(i)); + logger.info(". Unique spatial obstacles:\t" + uniqueSpatialObstacles.size()); + + int allSpatialObstacles = 0; + for (int x=0; x < spatialResolution; x++) + for (int y=0; y < spatialResolution; y++) + for (int i=0; i < allObstaclesSpatial[x][y].size(); i++) + allSpatialObstacles++; + logger.debug(". All spatial obstacles:\t" + allSpatialObstacles); + + logger.info(". Spatial map counts:"); + for (int y=0; y < spatialResolution; y++) { + for (int x=0; x < spatialResolution; x++) { + System.out.print(allObstaclesSpatial[x][y].size() + " "); + } + System.out.println(""); + } + + } + + /** + * Returns XML elements representing the current obstacles. + * + * @see #setConfigXML(Collection) + * @return XML elements representing the obstacles + */ + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + for (Rectangle2D rect: allObstacles) { + element = new Element("obst"); + element.setText(rect.getMinX() + ";" + rect.getMinY() + ";" + rect.getWidth() + ";" + rect.getHeight()); + config.add(element); + } + return config; + } + + /** + * Sets the current obstacles 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 configXML) { + for (Element element : configXML) { + if (element.getName().equals("obst")) { + String rectValues[] = element.getText().split(";"); + Rectangle2D newObst = new Rectangle2D.Double( + Double.parseDouble(rectValues[0]), + Double.parseDouble(rectValues[1]), + Double.parseDouble(rectValues[2]), + Double.parseDouble(rectValues[3])); + this.addObstacle(newObst, false); + } + } + return true; + } + +} + diff --git a/tools/cooja/apps/arm/java/se/sics/arm/RayData.java b/tools/cooja/apps/arm/java/se/sics/arm/RayData.java new file mode 100644 index 000000000..9bd0a8d6d --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/RayData.java @@ -0,0 +1,89 @@ +package se.sics.arm; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; + +/** + * @author Fredrik Osterlind + */ +class RayData { + + enum RayType { ORIGIN, REFRACTION, REFLECTION, DIFFRACTION, DESTINATION } + RayType type; + + private Point2D sourcePoint = null; + private Line2D line = null; + + private int limitOverall = 0; + private int limitRefracted = 0; + private int limitReflected = 0; + private int limitDiffracted = 0; + + /** + * Creates a new ray data instance. + * A ray can be of the following types: + * + * ORIGIN - starting at source point, line should be null + * REFRACTED - ray was refracted at an obstacle, + * source is intersection point with obstacle, line should be null + * REFLECTED - an interval of rays is reflected at given line, + * the source point should be a "psuedo-source" is located behind it + * (as if one was looking from given source point through given line) + * DIFFRACTED - a ray is diffracted at given source point, + * line should be null + * DESTINATION + * @param type Type of ray (one of the above) + * @param sourcePoint See above + * @param line See above (may be null) + * @param limitOverall Maximum numbers of sub rays this ray may produce + * @param limitRefracted Maximum numbers of refracted sub rays this ray may produce + * @param limitReflected Maximum numbers of reflected sub rays this ray may produce + * @param limitDiffracted Maximum numbers of diffracted sub rays this ray may produce + */ + public RayData( + RayType type, + Point2D sourcePoint, + Line2D line, + int limitOverall, + int limitRefracted, + int limitReflected, + int limitDiffracted + ) { + this.type = type; + this.sourcePoint = sourcePoint; + this.line = line; + this.limitOverall = limitOverall; + this.limitRefracted = limitRefracted; + this.limitReflected = limitReflected; + this.limitDiffracted = limitDiffracted; + } + + public RayType getType() { + return type; + } + + public Point2D getSourcePoint() { + return sourcePoint; + } + + public Line2D getLine() { + return line; + } + + public int getSubRaysLimit() { + return Math.min(limitOverall, limitRefracted + limitReflected + limitDiffracted); + } + + public int getRefractedSubRaysLimit() { + return Math.min(limitOverall, limitRefracted); + } + + public int getReflectedSubRaysLimit() { + return Math.min(limitOverall, limitReflected); + } + + public int getDiffractedSubRaysLimit() { + return Math.min(limitOverall, limitDiffracted); + } + +} diff --git a/tools/cooja/apps/arm/java/se/sics/arm/RayPath.java b/tools/cooja/apps/arm/java/se/sics/arm/RayPath.java new file mode 100644 index 000000000..dffb1ffb7 --- /dev/null +++ b/tools/cooja/apps/arm/java/se/sics/arm/RayPath.java @@ -0,0 +1,71 @@ +package se.sics.arm; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.util.Vector; + +/** + * @author Fredrik Osterlind + */ +public class RayPath { + private Vector points = new Vector(); + private Vector types = new Vector(); + + public void addPoint(Point2D point, RayData.RayType type) { + points.insertElementAt(point, 0); + types.insertElementAt(type, 0); + } + + public int getSubPathCount() { + return points.size() - 1; + } + + public Line2D getSubPath(int pos) { + return new Line2D.Double(points.get(pos), points.get(pos + 1)); + } + + public Point2D getPoint(int i) { + return points.get(i); + } + + public RayData.RayType getType(int i) { + return types.get(i); + } + + public String toString() { + if (points.size() != types.size()) + return "Malformed ray path (differing sizes)"; + + if (points.size() == 0) + return "Empty ray path"; + + if (types.firstElement() != RayData.RayType.ORIGIN && types.lastElement() != RayData.RayType.ORIGIN) + return "Malformed ray path (not closed)"; + + if (types.firstElement() != RayData.RayType.DESTINATION && types.lastElement() != RayData.RayType.DESTINATION) + return "Malformed ray path (not closed)"; + + if (types.firstElement() == types.lastElement()) + return "Malformed ray path (last == first element)"; + + String retVal = ""; + for (int i=0; i < types.size(); i++) { + RayData.RayType currentType = types.get(i); + if (currentType == RayData.RayType.DESTINATION) + retVal = retVal + " DEST "; + else if (currentType == RayData.RayType.DIFFRACTION) + retVal = retVal + " DIFF "; + else if (currentType == RayData.RayType.ORIGIN) + retVal = retVal + " ORIG "; + else if (currentType == RayData.RayType.REFLECTION) + retVal = retVal + " REFL "; + else if (currentType == RayData.RayType.REFRACTION) + retVal = retVal + " REFR "; + else + retVal = retVal + " ???? "; + } + return retVal; + + } + +} diff --git a/tools/cooja/apps/arm/java/statistics/CDF_Normal.java b/tools/cooja/apps/arm/java/statistics/CDF_Normal.java new file mode 100644 index 000000000..ccea768d4 --- /dev/null +++ b/tools/cooja/apps/arm/java/statistics/CDF_Normal.java @@ -0,0 +1,216 @@ +package statistics; + +//Gaussian CDF Taylor approximation +//Code borrowed from http://www1.fpl.fs.fed.us/distributions.html 19/9 2006 + +/** +* +*This class contains routines to calculate the +*normal cumulative distribution function (CDF) and +*its inverse. +* +*@version .5 --- June 7, 1996 +*@version .6 --- January 10, 2001 (normcdf added) +* +*/ + +public class CDF_Normal extends Object { + +/** +* +*This method calculates the normal cdf inverse function. +*

+*Let PHI(x) be the normal cdf. Suppose that Q calculates +*1.0 - PHI(x), and that QINV calculates QINV(p) for p in (0.0,.5]. +*Then for p .le. .5, x = PHIINV(p) = -QINV(p). +*For p .gt. .5, x = PHIINV(p) = QINV(1.0 - p). +*The formula for approximating QINV is taken from Abramowitz and Stegun, +*Handbook of Mathematical Functions, Dover, 9th printing, +*formula 26.2.3, page 933. The error in x is claimed to +*be less than 4.5e-4 in absolute value. +* +*@param p p must lie between 0 and 1. xnormi returns +* the normal cdf inverse evaluated at p. +* +*@author Steve Verrill +*@version .5 --- June 7, 1996 +* +*/ + +// FIX: Eventually I should build in a check that p lies in (0,1) + + public static double xnormi(double p) { + + double arg,t,t2,t3,xnum,xden,qinvp,x,pc; + + final double c[] = {2.515517, + .802853, + .010328}; + + final double d[] = {1.432788, + .189269, + .001308}; + + if (p <= .5) { + + arg = -2.0*Math.log(p); + t = Math.sqrt(arg); + t2 = t*t; + t3 = t2*t; + + xnum = c[0] + c[1]*t + c[2]*t2; + xden = 1.0 + d[0]*t + d[1]*t2 + d[2]*t3; + qinvp = t - xnum/xden; + x = -qinvp; + + return x; + + } + + else { + + pc = 1.0 - p; + arg = -2.0*Math.log(pc); + t = Math.sqrt(arg); + t2 = t*t; + t3 = t2*t; + + xnum = c[0] + c[1]*t + c[2]*t2; + xden = 1.0 + d[0]*t + d[1]*t2 + d[2]*t3; + x = t - xnum/xden; + + return x; + + } + + } + + +/** +* +*This method calculates the normal cumulative distribution function. +*

+*It is based upon algorithm 5666 for the error function, from:

+*

+*       Hart, J.F. et al, 'Computer Approximations', Wiley 1968
+*
+*

+*The FORTRAN programmer was Alan Miller. The documentation +*in the FORTRAN code claims that the function is "accurate +*to 1.e-15."

+*Steve Verrill +*translated the FORTRAN code (the March 30, 1986 version) +*into Java. This translation was performed on January 10, 2001. +* +*@param z The method returns the value of the normal +* cumulative distribution function at z. +* +*@version .5 --- January 10, 2001 +* +*/ + + +/* + +Here is a copy of the documentation in the FORTRAN code: + + SUBROUTINE NORMP(Z, P, Q, PDF) +C +C Normal distribution probabilities accurate to 1.e-15. +C Z = no. of standard deviations from the mean. +C P, Q = probabilities to the left & right of Z. P + Q = 1. +C PDF = the probability density. +C +C Based upon algorithm 5666 for the error function, from: +C Hart, J.F. et al, 'Computer Approximations', Wiley 1968 +C +C Programmer: Alan Miller +C +C Latest revision - 30 March 1986 +C + +*/ + + public static double normp(double z) { + + double zabs; + double p; + double expntl,pdf; + + final double p0 = 220.2068679123761; + final double p1 = 221.2135961699311; + final double p2 = 112.0792914978709; + final double p3 = 33.91286607838300; + final double p4 = 6.373962203531650; + final double p5 = .7003830644436881; + final double p6 = .3526249659989109E-01; + + final double q0 = 440.4137358247522; + final double q1 = 793.8265125199484; + final double q2 = 637.3336333788311; + final double q3 = 296.5642487796737; + final double q4 = 86.78073220294608; + final double q5 = 16.06417757920695; + final double q6 = 1.755667163182642; + final double q7 = .8838834764831844E-1; + + final double cutoff = 7.071; + final double root2pi = 2.506628274631001; + + zabs = Math.abs(z); + +// |z| > 37 + + if (z > 37.0) { + + p = 1.0; + + return p; + + } + + if (z < -37.0) { + + p = 0.0; + + return p; + + } + +// |z| <= 37. + + expntl = Math.exp(-.5*zabs*zabs); + + pdf = expntl/root2pi; + +// |z| < cutoff = 10/sqrt(2). + + if (zabs < cutoff) { + + p = expntl*((((((p6*zabs + p5)*zabs + p4)*zabs + p3)*zabs + + p2)*zabs + p1)*zabs + p0)/(((((((q7*zabs + q6)*zabs + + q5)*zabs + q4)*zabs + q3)*zabs + q2)*zabs + q1)*zabs + + q0); + + } else { + + p = pdf/(zabs + 1.0/(zabs + 2.0/(zabs + 3.0/(zabs + 4.0/ + (zabs + 0.65))))); + + } + + if (z < 0.0) { + + return p; + + } else { + + p = 1.0 - p; + + return p; + + } + + } + +} \ No newline at end of file diff --git a/tools/cooja/apps/arm/java/statistics/Gaussian.java b/tools/cooja/apps/arm/java/statistics/Gaussian.java new file mode 100644 index 000000000..4a302fb2b --- /dev/null +++ b/tools/cooja/apps/arm/java/statistics/Gaussian.java @@ -0,0 +1,63 @@ +package statistics; + +// Gaussian CDF Taylor approximation +// Code borrowed from http://www.cs.princeton.edu/introcs/21function/Gaussian.java.html 19/9 2006 + +/************************************************************************* +* Compilation: javac Gaussian.java +* Execution: java Gaussian x mu sigma +* +* Function to compute the Gaussian pdf (probability density function) +* and the Gaussian cdf (cumulative density function) +* +* % java Gaussian 820 1019 209 +* 0.17050966869132111 +* +* % java Gaussian 1500 1019 209 +* 0.9893164837383883 +* +* % java Gaussian 1500 1025 231 +* 0.9801220907365489 +* +*************************************************************************/ + +public class Gaussian { + + // return phi(x) = standard Gaussian pdf + public static double phi(double x) { + return Math.exp(-x*x / 2) / Math.sqrt(2 * Math.PI); + } + + // return phi(x) = Gaussian pdf with mean mu and stddev sigma + public static double phi(double x, double mu, double sigma) { + return phi((x - mu) / sigma) / sigma; + } + + // return Phi(z) = standard Gaussian cdf using Taylor approximation + public static double Phi(double z) { + if (z < -8.0) return 0.0; + if (z > 8.0) return 1.0; + double sum = 0.0, term = z; + for (int i = 3; sum + term != sum; i += 2) { + sum = sum + term; + term = term * z * z / i; + } + return 0.5 + sum * phi(z); + } + + + + // return Phi(z, mu, sigma) = Gaussian cdf with mean mu and stddev sigma + public static double Phi(double z, double mu, double sigma) { + return Phi((z - mu) / sigma); + } + + + public static void main(String[] args) { + double z = Double.parseDouble(args[0]); + double mu = Double.parseDouble(args[1]); + double sigma = Double.parseDouble(args[2]); + System.out.println(Phi(z, mu, sigma)); + } + +} diff --git a/tools/cooja/apps/arm/java/statistics/GaussianWrapper.java b/tools/cooja/apps/arm/java/statistics/GaussianWrapper.java new file mode 100644 index 000000000..5d196fa28 --- /dev/null +++ b/tools/cooja/apps/arm/java/statistics/GaussianWrapper.java @@ -0,0 +1,50 @@ +package statistics; + +public class GaussianWrapper { + + /** + * Returns standard Gaussian cdf approximation based on algortihm for error function. + * + * @param value Value + * @return Probability + */ + public static double cdfErrorAlgo(double value) { + return CDF_Normal.normp(value); + } + + /** + * Returns Gaussian cdf approximation based on algorithm for error function. + * + * @param value Value + * @param mean Mean value + * @param stdDev Standard deviance + * @return Probability + */ + public static double cdfErrorAlgo(double value, double mean, double stdDev) { + return CDF_Normal.normp((value - mean) / stdDev); + } + + /** + * Returns standard Gaussian cdf using Taylor approximation. + * + * @param value Value + * @return Probability + */ + public static double cdfTaylor(double value) { + return Gaussian.Phi(value); + } + + /** + * Returns Gaussian cdf using Taylor approximation . + * + * @param value Value + * @param mean Mean value + * @param stdDev Standard deviance + * @return Probability + */ + public static double cdfTaylor(double value, double mean, double stdDev) { + return Gaussian.Phi(value, mean, stdDev); + } + +} + diff --git a/tools/cooja/apps/arm/lib/arm.jar b/tools/cooja/apps/arm/lib/arm.jar new file mode 100644 index 000000000..a8d90ad19 Binary files /dev/null and b/tools/cooja/apps/arm/lib/arm.jar differ diff --git a/tools/cooja/apps/arm/lib/jcommon-1.0.0.jar b/tools/cooja/apps/arm/lib/jcommon-1.0.0.jar new file mode 100644 index 000000000..c5d23f4ae Binary files /dev/null and b/tools/cooja/apps/arm/lib/jcommon-1.0.0.jar differ diff --git a/tools/cooja/apps/arm/lib/jfreechart-1.0.1.jar b/tools/cooja/apps/arm/lib/jfreechart-1.0.1.jar new file mode 100644 index 000000000..6a015249b Binary files /dev/null and b/tools/cooja/apps/arm/lib/jfreechart-1.0.1.jar differ diff --git a/tools/cooja/config/external_tools_linux.config b/tools/cooja/config/external_tools_linux.config index d0a65c076..2d8fc189f 100644 --- a/tools/cooja/config/external_tools_linux.config +++ b/tools/cooja/config/external_tools_linux.config @@ -20,3 +20,4 @@ COMPILER_ARGS = CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process;tcpip_process;uip_fw_process;cfs_cooja_process CONTIKI_MAIN_TEMPLATE_FILENAME = code_main_template MANTIS_MAIN_TEMPLATE_FILENAME = mantis_template.c +DEFAULT_USERPLATFORMS = ../apps/arm diff --git a/tools/cooja/config/external_tools_win32.config b/tools/cooja/config/external_tools_win32.config index 33a0ab045..b7fb0796f 100644 --- a/tools/cooja/config/external_tools_win32.config +++ b/tools/cooja/config/external_tools_win32.config @@ -20,3 +20,4 @@ COMPILER_ARGS = -mno-cygwin -I'C:/Program Files/Java/jdk1.5.0_06/include' -I'C:/ CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process;tcpip_process;uip_fw_process;cfs_cooja_process CONTIKI_MAIN_TEMPLATE_FILENAME = code_main_template MANTIS_MAIN_TEMPLATE_FILENAME = mantis_template.c +DEFAULT_USERPLATFORMS = ../apps/arm