From 8969b53e4879a344e3691536ae02dbe84cd6e890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=B6rl?= Date: Fri, 15 Nov 2024 15:32:47 +0100 Subject: [PATCH] feat: make chargers attributable (#3565) --- .../contrib/ev/infrastructure/Charger.java | 3 +- .../ev/infrastructure/ChargerDefaultImpl.java | 6 + .../ev/infrastructure/ChargerReader.java | 44 ++++++- .../infrastructure/ChargerSpecification.java | 3 +- .../ev/infrastructure/ChargerWriter.java | 34 +++++- .../ChargingInfrastructureModule.java | 23 +++- .../ImmutableChargerSpecification.java | 15 +++ .../ChargerReaderWriterTest.java | 108 ++++++++++++++++++ matsim/src/main/resources/dtd/chargers_v1.dtd | 9 +- 9 files changed, 231 insertions(+), 14 deletions(-) create mode 100644 contribs/ev/src/test/java/org/matsim/contrib/ev/infrastructure/ChargerReaderWriterTest.java diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/Charger.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/Charger.java index 9bd2ef5bde9..921b950e41c 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/Charger.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/Charger.java @@ -24,8 +24,9 @@ import org.matsim.api.core.v01.Identifiable; import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.ev.charging.ChargingLogic; +import org.matsim.utils.objectattributes.attributable.Attributable; -public interface Charger extends BasicLocation, Identifiable { +public interface Charger extends BasicLocation, Identifiable, Attributable { ChargerSpecification getSpecification(); ChargingLogic getLogic(); diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerDefaultImpl.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerDefaultImpl.java index d2efb68578d..7ceb1007f92 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerDefaultImpl.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerDefaultImpl.java @@ -26,6 +26,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.ev.charging.ChargingLogic; +import org.matsim.utils.objectattributes.attributable.Attributes; import com.google.common.base.Preconditions; @@ -77,6 +78,11 @@ public int getPlugCount() { return specification.getPlugCount(); } + @Override + public Attributes getAttributes() { + return specification.getAttributes(); + } + //TODO in order to add a separate coord: adapt DTD, ChargerSpecification and ChargerReader/Writer // Additionally, the reader and writer should convert coordinates if CRS different than that of the network @Override diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerReader.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerReader.java index 9864622b4cb..b3d6a4d6c36 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerReader.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerReader.java @@ -20,19 +20,33 @@ package org.matsim.contrib.ev.infrastructure; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.Stack; import org.matsim.api.core.v01.Id; import org.matsim.contrib.ev.EvUnits; +import org.matsim.contrib.ev.infrastructure.ImmutableChargerSpecification.ChargerSpecificationBuilder; import org.matsim.core.utils.io.MatsimXmlParser; +import org.matsim.utils.objectattributes.AttributeConverter; +import org.matsim.utils.objectattributes.attributable.AttributesImpl; +import org.matsim.utils.objectattributes.attributable.AttributesXmlReaderDelegate; import org.xml.sax.Attributes; public final class ChargerReader extends MatsimXmlParser { private final static String CHARGER = "charger"; + private final static String ATTRIBUTES = "attributes"; + private final static String ATTRIBUTE = "attribute"; private final ChargingInfrastructureSpecification chargingInfrastructure; + private Map, AttributeConverter> attributeConverters = new HashMap<>(); + private final AttributesXmlReaderDelegate attributesReader = new AttributesXmlReaderDelegate(); + + private ChargerSpecificationBuilder currentBuilder = null; + private AttributesImpl currentAttributes = null; + public ChargerReader(ChargingInfrastructureSpecification chargingInfrastructure) { super(ValidationType.DTD_ONLY); this.chargingInfrastructure = chargingInfrastructure; @@ -41,15 +55,30 @@ public ChargerReader(ChargingInfrastructureSpecification chargingInfrastructure) @Override public void startTag(String name, Attributes atts, Stack context) { if (CHARGER.equals(name)) { - chargingInfrastructure.addChargerSpecification(createSpecification(atts)); + currentBuilder = createSpecification(atts); + } else if (ATTRIBUTES.equals(name)) { + currentAttributes = new AttributesImpl(); + attributesReader.startTag(name, atts, context, currentAttributes); + } else if (ATTRIBUTE.equals(name)) { + attributesReader.startTag(name, atts, context, currentAttributes); } } @Override public void endTag(String name, String content, Stack context) { + if (CHARGER.equals(name)) { + chargingInfrastructure.addChargerSpecification(currentBuilder.build()); + currentBuilder = null; + } else if (ATTRIBUTES.equals(name)) { + attributesReader.endTag(name, content, context); + currentBuilder.attributes(currentAttributes); + currentAttributes = null; + } else if (ATTRIBUTE.equals(name)) { + attributesReader.endTag(name, content, context); + } } - private ChargerSpecification createSpecification(Attributes atts) { + private ChargerSpecificationBuilder createSpecification(Attributes atts) { return ImmutableChargerSpecification.newBuilder() .id(Id.create(atts.getValue("id"), Charger.class)) .linkId(Id.createLinkId(atts.getValue("link"))) @@ -58,7 +87,14 @@ private ChargerSpecification createSpecification(Attributes atts) { .plugPower(EvUnits.kW_to_W(Double.parseDouble(atts.getValue("plug_power")))) .plugCount(Optional.ofNullable(atts.getValue("plug_count")) .map(Integer::parseInt) - .orElse(ChargerSpecification.DEFAULT_PLUG_COUNT)) - .build(); + .orElse(ChargerSpecification.DEFAULT_PLUG_COUNT)); } + + public void putAttributeConverters(Map, AttributeConverter> converters) { + this.attributeConverters.putAll(converters); + } + + public void putAttributeConverter(Class key, AttributeConverter converter) { + this.attributeConverters.put(key, converter); + } } diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerSpecification.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerSpecification.java index 14aafb14d12..73272087d0a 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerSpecification.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerSpecification.java @@ -23,6 +23,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Identifiable; import org.matsim.api.core.v01.network.Link; +import org.matsim.utils.objectattributes.attributable.Attributable; /** * ChargerSpecification is assumed to be immutable. @@ -34,7 +35,7 @@ * * @author Michal Maciejewski (michalm) */ -public interface ChargerSpecification extends Identifiable { +public interface ChargerSpecification extends Identifiable, Attributable { String DEFAULT_CHARGER_TYPE = "default"; int DEFAULT_PLUG_COUNT = 1; diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerWriter.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerWriter.java index 8955b266984..f62ee0729f6 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerWriter.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargerWriter.java @@ -20,18 +20,27 @@ package org.matsim.contrib.ev.infrastructure; +import java.io.IOException; +import java.io.UncheckedIOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.matsim.contrib.ev.EvUnits; import org.matsim.core.utils.collections.Tuple; import org.matsim.core.utils.io.MatsimXmlWriter; +import org.matsim.utils.objectattributes.AttributeConverter; +import org.matsim.utils.objectattributes.attributable.AttributesXmlWriterDelegate; public final class ChargerWriter extends MatsimXmlWriter { private final Stream chargerSpecifications; + private Map, AttributeConverter> attributeConverters = new HashMap<>(); + private final AttributesXmlWriterDelegate attributesWriter = new AttributesXmlWriterDelegate(); + public ChargerWriter(Stream chargerSpecifications) { this.chargerSpecifications = chargerSpecifications; } @@ -45,13 +54,34 @@ public void write(String file) { close(); } - private void writeChargers() { + private void writeChargers() throws UncheckedIOException { chargerSpecifications.forEach(c -> { List> atts = Arrays.asList(Tuple.of("id", c.getId().toString()), Tuple.of("link", c.getLinkId() + ""), Tuple.of("type", c.getChargerType()), Tuple.of("plug_power", EvUnits.W_to_kW(c.getPlugPower()) + ""), Tuple.of("plug_count", c.getPlugCount() + "")); - writeStartTag("charger", atts, true); + if (c.getAttributes().size() == 0) { + writeStartTag("charger", atts, true); + } else { + writeStartTag("charger", atts, false); + + try { + this.writer.write("\n"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + attributesWriter.writeAttributes("\t\t", this.writer, c.getAttributes(), false); + writeEndTag("charger"); + } }); } + + public void putAttributeConverters(Map, AttributeConverter> converters) { + this.attributeConverters.putAll(converters); + } + + public void putAttributeConverter(Class key, AttributeConverter converter) { + this.attributeConverters.put(key, converter); + } } diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargingInfrastructureModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargingInfrastructureModule.java index f44bee53f11..dd85c2bb83b 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargingInfrastructureModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ChargingInfrastructureModule.java @@ -20,12 +20,16 @@ package org.matsim.contrib.ev.infrastructure; +import java.util.Collections; +import java.util.Map; + import org.matsim.api.core.v01.network.Network; import org.matsim.contrib.ev.EvConfigGroup; import org.matsim.contrib.ev.charging.ChargingLogic; import org.matsim.core.config.ConfigGroup; import org.matsim.core.controler.AbstractModule; import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.utils.objectattributes.AttributeConverter; import com.google.inject.Inject; import com.google.inject.Key; @@ -55,11 +59,20 @@ public ChargingInfrastructureModule(Key networkKey) { public void install() { bind(Network.class).annotatedWith(Names.named(CHARGERS)).to(networkKey).asEagerSingleton(); - bind(ChargingInfrastructureSpecification.class).toProvider(() -> { - ChargingInfrastructureSpecification chargingInfrastructureSpecification = new ChargingInfrastructureSpecificationDefaultImpl(); - new ChargerReader(chargingInfrastructureSpecification).parse( - ConfigGroup.getInputFileURL(getConfig().getContext(), evCfg.chargersFile)); - return chargingInfrastructureSpecification; + bind(ChargingInfrastructureSpecification.class).toProvider(new Provider<>() { + @Inject + private Map,AttributeConverter> attributeConverters = Collections.emptyMap(); + + public ChargingInfrastructureSpecification get() { + ChargingInfrastructureSpecification chargingInfrastructureSpecification = new ChargingInfrastructureSpecificationDefaultImpl(); + + ChargerReader reader = new ChargerReader(chargingInfrastructureSpecification); + reader.putAttributeConverters(attributeConverters); + reader.parse( + ConfigGroup.getInputFileURL(getConfig().getContext(), evCfg.chargersFile)); + + return chargingInfrastructureSpecification; + } }).asEagerSingleton(); installQSimModule(new AbstractQSimModule() { diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ImmutableChargerSpecification.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ImmutableChargerSpecification.java index a3d7192b793..2987fe88677 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ImmutableChargerSpecification.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/infrastructure/ImmutableChargerSpecification.java @@ -24,6 +24,8 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; +import org.matsim.utils.objectattributes.attributable.Attributes; +import org.matsim.utils.objectattributes.attributable.AttributesImpl; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -39,6 +41,7 @@ public class ImmutableChargerSpecification implements ChargerSpecification { private final String chargerType; private final double plugPower; private final int plugCount; + private final Attributes attributes; private ImmutableChargerSpecification( ChargerSpecificationBuilder builder ) { id = Objects.requireNonNull(builder.id); @@ -46,6 +49,7 @@ private ImmutableChargerSpecification( ChargerSpecificationBuilder builder ) { chargerType = Objects.requireNonNull(builder.chargerType); plugPower = Objects.requireNonNull(builder.plugPower); plugCount = Objects.requireNonNull(builder.plugCount); + attributes = builder.attributes != null ? builder.attributes : new AttributesImpl(); Preconditions.checkArgument(plugPower >= 0, "Negative plugPower of charger: %s", id); Preconditions.checkArgument(plugCount >= 0, "Negative plugCount of charger: %s", id); @@ -90,6 +94,11 @@ public int getPlugCount() { return plugCount; } + @Override + public Attributes getAttributes() { + return attributes; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -107,6 +116,7 @@ public static final class ChargerSpecificationBuilder{ private String chargerType; private Double plugPower; private Integer plugCount; + private Attributes attributes; private ChargerSpecificationBuilder() { } @@ -136,6 +146,11 @@ public ChargerSpecificationBuilder plugCount( int val ) { return this; } + public ChargerSpecificationBuilder attributes( Attributes val ) { + attributes = val; + return this; + } + public ImmutableChargerSpecification build() { return new ImmutableChargerSpecification(this); } diff --git a/contribs/ev/src/test/java/org/matsim/contrib/ev/infrastructure/ChargerReaderWriterTest.java b/contribs/ev/src/test/java/org/matsim/contrib/ev/infrastructure/ChargerReaderWriterTest.java new file mode 100644 index 00000000000..eb6e0f5c65f --- /dev/null +++ b/contribs/ev/src/test/java/org/matsim/contrib/ev/infrastructure/ChargerReaderWriterTest.java @@ -0,0 +1,108 @@ +package org.matsim.contrib.ev.infrastructure; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.matsim.api.core.v01.Id; +import org.matsim.testcases.MatsimTestUtils; +import org.matsim.utils.objectattributes.attributable.AttributesImpl; + +public class ChargerReaderWriterTest { + @RegisterExtension + MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public void testReadWriteChargers() { + ChargingInfrastructureSpecificationDefaultImpl infrastructure = new ChargingInfrastructureSpecificationDefaultImpl(); + + infrastructure.addChargerSpecification(ImmutableChargerSpecification.newBuilder() + .id(Id.create("charger1", Charger.class)) // + .chargerType("type1") // + .linkId(Id.createLinkId("link1")) // + .plugCount(1) // + .plugPower(1000.0) // + .build()); + + infrastructure.addChargerSpecification(ImmutableChargerSpecification.newBuilder() + .id(Id.create("charger2", Charger.class)) // + .chargerType("type2") // + .linkId(Id.createLinkId("link2")) // + .plugCount(2) // + .plugPower(2000.0) // + .build()); + + String path = utils.getOutputDirectory() + "/chargers.xml"; + new ChargerWriter(infrastructure.getChargerSpecifications().values().stream()).write(path); + + ChargingInfrastructureSpecificationDefaultImpl readInfrastructure = new ChargingInfrastructureSpecificationDefaultImpl(); + new ChargerReader(readInfrastructure).readFile(path); + + ChargerSpecification spec1 = readInfrastructure.getChargerSpecifications() + .get(Id.create("charger1", Charger.class)); + assertEquals("type1", spec1.getChargerType()); + assertEquals(Id.createLinkId("link1"), spec1.getLinkId()); + assertEquals(1, spec1.getPlugCount()); + assertEquals(1000.0, spec1.getPlugPower()); + + ChargerSpecification spec2 = readInfrastructure.getChargerSpecifications() + .get(Id.create("charger2", Charger.class)); + assertEquals("type2", spec2.getChargerType()); + assertEquals(Id.createLinkId("link2"), spec2.getLinkId()); + assertEquals(2, spec2.getPlugCount()); + assertEquals(2000.0, spec2.getPlugPower()); + } + + @Test + public void testReadWriteChargersWithAttributes() { + ChargingInfrastructureSpecificationDefaultImpl infrastructure = new ChargingInfrastructureSpecificationDefaultImpl(); + + AttributesImpl attributes1 = new AttributesImpl(); + attributes1.putAttribute("attribute1", "value1"); + + infrastructure.addChargerSpecification(ImmutableChargerSpecification.newBuilder() + .id(Id.create("charger1", Charger.class)) // + .chargerType("type1") // + .linkId(Id.createLinkId("link1")) // + .plugCount(1) // + .plugPower(1000.0) // + .attributes(attributes1) // + .build()); + + AttributesImpl attributes2 = new AttributesImpl(); + attributes2.putAttribute("attribute2", "value2"); + + infrastructure.addChargerSpecification(ImmutableChargerSpecification.newBuilder() + .id(Id.create("charger2", Charger.class)) // + .chargerType("type2") // + .linkId(Id.createLinkId("link2")) // + .plugCount(2) // + .plugPower(2000.0) // + .attributes(attributes2) // + .build()); + + String path = utils.getOutputDirectory() + "/chargers_with_attributes.xml"; + new ChargerWriter(infrastructure.getChargerSpecifications().values().stream()).write(path); + + ChargingInfrastructureSpecificationDefaultImpl readInfrastructure = new ChargingInfrastructureSpecificationDefaultImpl(); + new ChargerReader(readInfrastructure).readFile(path); + + ChargerSpecification spec1 = readInfrastructure.getChargerSpecifications() + .get(Id.create("charger1", Charger.class)); + assertEquals("type1", spec1.getChargerType()); + assertEquals(Id.createLinkId("link1"), spec1.getLinkId()); + assertEquals(1, spec1.getPlugCount()); + assertEquals(1000.0, spec1.getPlugPower()); + + assertEquals("value1", (String) spec1.getAttributes().getAttribute("attribute1")); + + ChargerSpecification spec2 = readInfrastructure.getChargerSpecifications() + .get(Id.create("charger2", Charger.class)); + assertEquals("type2", spec2.getChargerType()); + assertEquals(Id.createLinkId("link2"), spec2.getLinkId()); + assertEquals(2, spec2.getPlugCount()); + assertEquals(2000.0, spec2.getPlugPower()); + + assertEquals("value2", (String) spec2.getAttributes().getAttribute("attribute2")); + } +} diff --git a/matsim/src/main/resources/dtd/chargers_v1.dtd b/matsim/src/main/resources/dtd/chargers_v1.dtd index 5e849dddd91..c9c4f13184d 100644 --- a/matsim/src/main/resources/dtd/chargers_v1.dtd +++ b/matsim/src/main/resources/dtd/chargers_v1.dtd @@ -9,7 +9,7 @@ - + @@ -21,3 +21,10 @@ plug_power CDATA #REQUIRED plug_count CDATA #IMPLIED type CDATA #IMPLIED > + + + + +