001/* *********************************************************************** *
002 * project: matsim
003 * AgentSnapshotInfoFactory.java
004 *                                                                         *
005 * *********************************************************************** *
006 *                                                                         *
007 * copyright       : (C) 2010 by the members listed in the COPYING,        *
008 *                   LICENSE and WARRANTY file.                            *
009 * email           : info at matsim dot org                                *
010 *                                                                         *
011 * *********************************************************************** *
012 *                                                                         *
013 *   This program is free software; you can redistribute it and/or modify  *
014 *   it under the terms of the GNU General Public License as published by  *
015 *   the Free Software Foundation; either version 2 of the License, or     *
016 *   (at your option) any later version.                                   *
017 *   See also COPYING, LICENSE and WARRANTY file                           *
018 *                                                                         *
019 * *********************************************************************** */
020
021package org.matsim.vis.snapshotwriters;
022
023import org.matsim.api.core.v01.Coord;
024import org.matsim.api.core.v01.Id;
025import org.matsim.api.core.v01.network.Link;
026import org.matsim.api.core.v01.population.Person;
027import org.matsim.core.gbl.Gbl;
028
029/**
030 * translation of physical position (e.g. odometer distance on link, lane) into visualization position
031 * 
032 * @author nagel
033 *
034 */
035public class AgentSnapshotInfoFactory {
036
037        private static final double TWO_PI = 2.0 * Math.PI;
038        private static final double PI_HALF = Math.PI / 2.0;
039        private SnapshotLinkWidthCalculator linkWidthCalculator;
040
041        public AgentSnapshotInfoFactory(SnapshotLinkWidthCalculator widthCalculator) {
042                this.linkWidthCalculator = widthCalculator;
043        }
044
045        /**
046         * @param elevation  
047         */
048        @SuppressWarnings("static-method")
049        public AgentSnapshotInfo createAgentSnapshotInfo(Id<Person> agentId, double easting, double northing, double elevation, double azimuth) {
050                PositionInfo info = new PositionInfo() ;
051                info.setId( agentId ) ;
052                info.setEasting( easting ) ;
053                info.setNorthing( northing ) ;
054                info.setAzimuth( azimuth ) ;
055                return info ;
056        }
057
058        
059        /**
060         * Generate snapshot info based on Link. 
061         * 
062         *  Comments:<ul>
063         *  <li>One could argue that this method should not know about Links at all,
064         * but it shortens code at several places, and since Link is a standard interface, I see no reason to not provide this
065         * as a service.
066         * </ul>
067         */
068        public AgentSnapshotInfo createAgentSnapshotInfo(Id<Person> agentId, Link link, double distanceOnLink, int lane) {
069                PositionInfo info = new PositionInfo() ;
070                info.setId(agentId) ;
071                double lanePosition = this.linkWidthCalculator.calculateLanePosition(lane);
072                calculateAndSetPosition(info, link.getFromNode().getCoord(), link.getToNode().getCoord(), distanceOnLink, link.getLength(), lanePosition );
073                return info;
074        }
075        
076        /**
077         *  creator based on Coord
078         * @param curveLength lengths are usually different (usually longer) than the euclidean distances between the startCoord and endCoord
079         */
080        public AgentSnapshotInfo createAgentSnapshotInfo(Id<Person> agentId, Coord startCoord, Coord endCoord, double distanceOnLink, 
081                        Integer lane, double curveLength) {
082                PositionInfo info = new PositionInfo() ;
083                info.setId(agentId) ;
084                double lanePosition = this.linkWidthCalculator.calculateLanePosition(lane);
085                Gbl.assertNotNull( startCoord );
086                Gbl.assertNotNull( endCoord );
087                calculateAndSetPosition(info, startCoord, endCoord, distanceOnLink, curveLength, lanePosition) ;
088                return info;
089        }
090        
091        
092        /**
093         * 
094         * @param lanePosition may be null
095         */
096        private final static void calculateAndSetPosition(PositionInfo info, Coord startCoord, Coord endCoord, double odometerOnLink, 
097                        double lengthOfCurve, double lanePosition){
098
099                double dx = -startCoord.getX() + endCoord.getX();
100                double dy = -startCoord.getY() + endCoord.getY();
101                double theta = 0.0;
102                if (dx > 0) {
103                        theta = Math.atan(dy/dx);
104                } else if (dx < 0) {
105                        theta = Math.PI + Math.atan(dy/dx);
106                } else { // i.e. DX==0
107                        if (dy > 0) {
108                                theta = PI_HALF;
109                        } else if ( dy < 0 ) {
110                                theta = -PI_HALF;
111                        } else { // i.e. DX==0 && DY==0
112                                theta = 0.833*Math.PI ; // some default direction towards north north east 
113                        }
114                }
115                if (theta < 0.0) theta += TWO_PI;
116
117                double euclideanLength = Math.sqrt( dx*dx + dy*dy ) ;
118                // since we already have two atan, two cos and two sin in the method, it seems to make little sense to save on the sqrt. (?) kai, apr'16
119
120                // "correction" is needed because link lengths are usually different (usually longer) than the Euklidean distances.
121                // For the visualization, however, the vehicles are distributed in a straight line between the end points.
122                // Since the simulation, on the other hand, reports odometer distances, this needs to be corrected.  kai, apr'10
123                //
124                // The same correction can be used for the orthogonal offsets.  kai, aug'10
125                // That did not work so well in some cases.  Since the link width also seems to be plotted without that correction, let's
126                // try to also do the "distance from link" without correction.   kai, feb'13
127                double correction = 0. ;
128                if ( lengthOfCurve != 0 ){
129                        correction = euclideanLength / lengthOfCurve;
130                }
131                
132                info.setEasting( startCoord.getX() 
133                                + (Math.cos(theta) * odometerOnLink * correction)
134                                + (Math.sin(theta) * lanePosition ) ) ;
135                
136                info.setNorthing( startCoord.getY() 
137                                + Math.sin(theta) * odometerOnLink  * correction 
138                                - Math.cos(theta) * lanePosition  );
139                
140                info.setAzimuth( theta / TWO_PI * 360. ) ;
141        }
142
143        /**
144         * A helper class to store information about agents (id, position, speed), mainly used to create
145         * {@link SnapshotWriter snapshots}.  It also provides a way to convert graph coordinates (linkId, offset) into
146         * Euclidean coordinates.  Also does some additional coordinate shifting (e.g. to the "right") to improve visualization.
147         * In contrast to earlier versions of this comment, it does _not_ define a physical position of particles in the queue model;
148         * that functionality needs to be provided elsewhere.
149         *
150         * @author mrieser, knagel
151         */
152         private static class PositionInfo implements AgentSnapshotInfo {
153
154                private Id<Person> agentId = null;
155                private double easting = Double.NaN;
156                private double northing = Double.NaN;
157                private double azimuth = Double.NaN;
158                private double colorValue = 0;
159                private AgentState agentState = null;
160                private Id<Link> linkId = null;
161                private int user = 0;
162
163                /* package-private */ PositionInfo() { }
164
165                @Override
166                public final Id<Person> getId() {
167                        return this.agentId;
168                }
169                public final void setId( Id<Person> tmp ) {
170                        this.agentId = tmp ;
171                }
172
173                @Override
174                public final double getEasting() {
175                        return this.easting;
176                }
177                public final void setEasting( double tmp ) {
178                        this.easting = tmp ;
179                }
180
181                @Override
182                public final double getNorthing() {
183                        return this.northing;
184                }
185                public final void setNorthing( double tmp ) {
186                        this.northing = tmp ;
187                }
188
189                @Override
190                public final double getAzimuth() {
191                        return this.azimuth;
192                }
193                public final void setAzimuth( double tmp ) {
194                        this.azimuth = tmp ;
195                }
196
197                @Override
198                public final double getColorValueBetweenZeroAndOne() {
199                        return this.colorValue;
200                }
201                @Override
202                public final void setColorValueBetweenZeroAndOne( double tmp ) {
203                        this.colorValue = tmp ;
204                }
205
206                @Override
207                public final AgentState getAgentState(){
208                        return this.agentState;
209                }
210                @Override
211                public final void setAgentState( AgentState state ) {
212                        this.agentState = state ;
213                }
214
215                public final Id<Link> getLinkId() {
216                        return this.linkId;
217                }
218                public final void setLinkId( Id<Link> tmp ) {
219                        this.linkId = tmp ;
220                }
221
222                @Override
223                public int getUserDefined() {
224                        return this.user;
225                }
226                @Override
227                public void setUserDefined( int tmp ) {
228                        this.user = tmp ;
229                }
230
231                @Override
232                public String toString() {
233                        return "PositionInfo; agentId: " + this.agentId.toString()
234                        + " easting: " + this.easting
235                        + " northing: " + this.northing ;
236                }
237
238        }
239
240}