DemandTag.java
package org.opentrafficsim.road.network.factory.xml.demand;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.djunits.unit.FrequencyUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.value.StorageType;
import org.djunits.value.vdouble.scalar.Time;
import org.djunits.value.vdouble.vector.FrequencyVector;
import org.djunits.value.vdouble.vector.TimeVector;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.opentrafficsim.core.network.factory.xml.units.DurationUnits;
import org.opentrafficsim.core.network.factory.xml.units.TimeUnits;
import org.opentrafficsim.road.gtu.strategical.od.Category;
import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
import org.opentrafficsim.road.network.factory.xml.XmlParserException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Demand.
* <p>
* Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
* <p>
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 25 mei 2018 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
* @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
*/
public class DemandTag implements Serializable
{
/** */
private static final long serialVersionUID = 20180525L;
/** Unit for storage. */
private final static FrequencyUnit UNIT = FrequencyUnit.PER_HOUR;
/** Origin. */
org.opentrafficsim.core.network.Node origin;
/** Destination. */
org.opentrafficsim.core.network.Node destination;
/** Interpolation. */
Interpolation interpolation;
/** Category. */
Category category;
/** Category name. */
String categoryName;
/** Demand type. */
DemandType demandType = null;
/** Time vector. */
TimeVector timeVector;
/** Demand vector. */
FrequencyVector demandVector;
/** Fraction. */
Double factor;
/** Fractions. */
double[] factors;
/**
* Parse demand nodes.
* @param nodeList NodeList; node list
* @param parser XmlOdParser; parser
* @throws XmlParserException if category cannot be parsed
*/
static void parse(final NodeList nodeList, final XmlOdParser parser) throws XmlParserException
{
for (Node node : XMLParser.getNodesSorted(nodeList, "DEMAND", "ORIGIN", "DESTINATION", "CATEGORY"))
{
NamedNodeMap attributes = node.getAttributes();
DemandTag tag = new DemandTag();
Node originNode = attributes.getNamedItem("ORIGIN");
Throw.when(originNode == null, XmlParserException.class, "Missing ORIGIN attribute in DEMAND tag.");
String originId = originNode.getNodeValue().trim();
tag.origin = parser.network.getNode(originId);
Throw.when(tag.origin == null, XmlParserException.class, "Origin %s is not available.", originId);
Node destinationNode = attributes.getNamedItem("DESTINATION");
Throw.when(destinationNode == null, XmlParserException.class, "Missing DESTINATION attribute in DEMAND tag.");
String destinationId = destinationNode.getNodeValue().trim();
tag.destination = parser.network.getNode(destinationId);
Throw.when(tag.destination == null, XmlParserException.class, "Destination %s is not available.", destinationId);
Node interpolationNode = attributes.getNamedItem("INTERPOLATION");
if (interpolationNode != null)
{
String interpolation = interpolationNode.getNodeValue().trim();
try
{
tag.interpolation = Interpolation.valueOf(interpolation);
}
catch (IllegalArgumentException exception)
{
throw new XmlParserException("INTERPOLATION " + interpolation + " does not exist.", exception);
}
}
Node factorNode = attributes.getNamedItem("FACTOR");
if (factorNode != null)
{
tag.factor = parseFactor(factorNode.getNodeValue().trim());
}
Node categoryNode = attributes.getNamedItem("CATEGORY");
if (categoryNode != null)
{
String categoryName = categoryNode.getNodeValue().trim();
Throw.when(!parser.categories.containsKey(categoryName), XmlParserException.class,
"Category %s is not available.", categoryName);
tag.category = parser.categories.get(categoryName).getCategory(parser.categorization);
tag.factor = XmlOdParser.nullMultiply(tag.factor, parser.categories.get(categoryName).factor);
}
NodeList childList = node.getChildNodes();
List<Double> timeList = new ArrayList<>();
List<Double> valueList = new ArrayList<>();
List<Node> demandNodes = XMLParser.getNodes(childList, "LEVEL");
Throw.when(categoryNode == null && demandNodes.isEmpty(), XmlParserException.class,
"DEMAND without CATEGORY attribute should contain demand data.");
if (demandNodes.size() == 0)
{
tag.demandType = DemandType.FACTOR;
}
for (Node level : demandNodes)
{
if (tag.demandType == null)
{
tag.demandType = DemandType.fromLevelNode(level);
Throw.when(
categoryNode == null && !tag.demandType.equals(DemandType.FREQUENCIES)
&& !tag.demandType.equals(DemandType.TIMED_FREQUENCIES),
XmlParserException.class,
"DEMAND without CATEGORY attribute should contain non-factoral demand data.");
}
NamedNodeMap levelAttributes = level.getAttributes();
if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.TIMED_FREQUENCIES))
{
Node timeNode = levelAttributes.getNamedItem("TIME");
Throw.when(timeNode == null, XmlParserException.class, "A LEVEL tag is missing attribute TIME.");
String timeString = timeNode.getNodeValue().trim();
Time time = Try.assign(() -> TimeUnits.parseTime(timeString), XmlParserException.class,
"Unable to parse %s as time.", timeString);
timeList.add(time.si);
}
Node valueNode = levelAttributes.getNamedItem("VALUE");
Throw.when(valueNode == null, XmlParserException.class, "A LEVEL tag is missing attribute VALUE.");
if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.FACTORS))
{
valueList.add(parseFactor(valueNode.getNodeValue().trim()));
}
if (tag.demandType.equals(DemandType.TIMED_FREQUENCIES) || tag.demandType.equals(DemandType.FREQUENCIES))
{
String valueString = valueNode.getNodeValue().trim().toLowerCase();
valueList.add(Try.assign(() -> DurationUnits.parseFrequency(valueString.replace("veh", "")).getInUnit(UNIT),
"Unable to parse %s as frequency.", valueString));
}
}
// time vector
if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.TIMED_FREQUENCIES))
{
tag.timeVector = Try.assign(
() -> new TimeVector(timeList.stream().mapToDouble(d -> d).toArray(), TimeUnit.BASE, StorageType.DENSE),
"Unexpected exception while converting list of time values to an array.");
}
// factor or demand vector
if (tag.demandType.equals(DemandType.TIMED_FACTORS) || tag.demandType.equals(DemandType.FACTORS))
{
tag.factors = valueList.stream().mapToDouble(d -> d).toArray();
}
else if (tag.demandType.equals(DemandType.TIMED_FREQUENCIES) || tag.demandType.equals(DemandType.FREQUENCIES))
{
tag.demandVector = Try.assign(
() -> new FrequencyVector(valueList.stream().mapToDouble(d -> d).toArray(), UNIT, StorageType.DENSE),
"Unexpected exception while converting list of time values to an array.");
}
parser.demand.getValue(() -> new LinkedHashSet<>(), tag.origin, tag.destination).add(tag);
}
}
/**
* Parses a String of percentage or value to a double.
* @param value String; value
* @return double
* @throws XmlParserException if factor is not in range from 0 to 1
*/
public static double parseFactor(final String value) throws XmlParserException
{
double f;
if (value.endsWith("%"))
{
f = Double.parseDouble(value.substring(0, value.length() - 1).trim()) / 100.0;
}
else
{
f = Double.parseDouble(value);
}
Throw.when(f < 0.0, XmlParserException.class, "Factor %d is not positive.", f);
return f;
}
/**
* Demand type. Demand may be defined in several tags. For instance, 1 tag may define time and demand, while other tags only
* specify a factor of this demand applicable to a specific category.
* <p>
* Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* <br>
* BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
* <p>
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 25 mei 2018 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
* @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
*/
public enum DemandType
{
/** Demand given in other tag to which a factor applies. */
FACTOR,
/** Demand given in other tag to which a factors apply. */
FACTORS,
/** Timed factors that apply to demand in another tag. */
TIMED_FACTORS,
/** Frequencies that apply to the global time vector. */
FREQUENCIES,
/** Timed frequencies. */
TIMED_FREQUENCIES;
/**
* Returns the demand type based on a LEVEL tag. This does not work on FACTOR, as no LEVEL tag should be defined then.
* @param level Node; level of the node
* @return demand type
* @throws XmlParserException if the VALUE attribute is missing
*/
public static DemandType fromLevelNode(final Node level) throws XmlParserException
{
NamedNodeMap attributes = level.getAttributes();
Node timeNode = attributes.getNamedItem("TIME");
Node valueNode = attributes.getNamedItem("VALUE");
Throw.when(valueNode == null, XmlParserException.class, "LEVEL tag in DEMAND is missing attribute VALUE.");
boolean frequency = valueNode.getNodeValue().trim().toLowerCase().contains("veh");
if (timeNode != null)
{
if (frequency)
{
return TIMED_FREQUENCIES;
}
return TIMED_FACTORS;
}
if (frequency)
{
return FREQUENCIES;
}
return FACTORS;
}
}
}