CgmesControlAreasSerDe.java
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.cgmes.extensions.compatibility;
import com.google.auto.service.AutoService;
import com.google.common.base.Strings;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.extensions.AbstractExtensionSerDe;
import com.powsybl.commons.extensions.Extension;
import com.powsybl.commons.extensions.ExtensionSerDe;
import com.powsybl.commons.io.DeserializerContext;
import com.powsybl.commons.io.SerializerContext;
import com.powsybl.commons.io.TreeDataReader;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.serde.NetworkDeserializerContext;
import com.powsybl.iidm.serde.TerminalRefSerDe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.OptionalDouble;
/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
@AutoService(ExtensionSerDe.class)
public class CgmesControlAreasSerDe extends AbstractExtensionSerDe<Network, CgmesControlAreasSerDe.DummyExt> {
private static final Logger LOGGER = LoggerFactory.getLogger(CgmesControlAreasSerDe.class);
private static final String CONTROL_AREA_ROOT_ELEMENT = "controlArea";
private static final String CONTROL_AREA_ARRAY_ELEMENT = "controlAreas";
public static final String TERMINAL_ROOT_ELEMENT = "terminal";
public static final String TERMINAL_ARRAY_ELEMENT = "terminals";
public static final String BOUNDARY_ROOT_ELEMENT = "boundary";
public static final String BOUNDARY_ARRAY_ELEMENT = "boundaries";
public CgmesControlAreasSerDe() {
super("cgmesControlAreas", "network", CgmesControlAreasSerDe.DummyExt.class, "cgmesControlAreas.xsd",
"http://www.powsybl.org/schema/iidm/ext/cgmes_control_areas/1_0", "cca");
}
@Override
public Map<String, String> getArrayNameToSingleNameMap() {
return Map.of(CONTROL_AREA_ARRAY_ELEMENT, CONTROL_AREA_ROOT_ELEMENT,
TERMINAL_ARRAY_ELEMENT, TERMINAL_ROOT_ELEMENT,
BOUNDARY_ARRAY_ELEMENT, BOUNDARY_ROOT_ELEMENT);
}
@Override
public void write(DummyExt extension, SerializerContext context) {
throw new IllegalStateException("Should not happen");
}
@Override
public DummyExt read(Network extendable, DeserializerContext context) {
NetworkDeserializerContext networkContext = (NetworkDeserializerContext) context;
TreeDataReader reader = networkContext.getReader();
reader.readChildNodes(elementName -> {
if (elementName.equals(CONTROL_AREA_ROOT_ELEMENT)) {
String id = reader.readStringAttribute("id");
if (extendable.getArea(id) == null) {
Area area = extendable.newArea()
.setAreaType(CgmesNames.CONTROL_AREA_TYPE_KIND_INTERCHANGE)
.setId(id)
.setName(reader.readStringAttribute("name"))
.setInterchangeTarget(reader.readDoubleAttribute("netInterchange"))
.add();
OptionalDouble pTolerance = reader.readOptionalDoubleAttribute("pTolerance");
pTolerance.ifPresent(t -> area.setProperty(CgmesNames.P_TOLERANCE, Double.toString(t)));
String energyIdentificationCodeEic = reader.readStringAttribute("energyIdentificationCodeEic");
if (!Strings.isNullOrEmpty(energyIdentificationCodeEic)) {
area.addAlias(energyIdentificationCodeEic, CgmesNames.ENERGY_IDENT_CODE_EIC);
}
readBoundariesAndTerminals(networkContext, area, extendable);
} else {
LOGGER.warn("Area with id {} already exists. Skipping this CgmesControlArea.", id);
reader.skipNode();
}
} else {
throw new PowsyblException("Unknown element name '" + elementName + "' in 'cgmesControlArea'");
}
});
return null;
}
private void readBoundariesAndTerminals(NetworkDeserializerContext networkContext, Area area, Network network) {
TreeDataReader reader = networkContext.getReader();
reader.readChildNodes(elementName -> {
switch (elementName) {
case BOUNDARY_ROOT_ELEMENT -> {
String id = networkContext.getAnonymizer().deanonymizeString(reader.readStringAttribute("id"));
Identifiable<?> identifiable = network.getIdentifiable(id);
boolean isAc = true; // Set to "true" because this piece of data is not available
if (identifiable instanceof DanglingLine dl) {
area.newAreaBoundary()
.setAc(isAc)
.setBoundary(dl.getBoundary())
.add();
} else if (identifiable instanceof TieLine tl) {
TwoSides side = reader.readEnumAttribute("side", TwoSides.class);
area.newAreaBoundary()
.setAc(isAc)
.setBoundary(tl.getDanglingLine(side).getBoundary())
.add();
} else {
throw new PowsyblException("Unexpected Identifiable instance: " + identifiable.getClass());
}
reader.readEndNode();
}
case TERMINAL_ROOT_ELEMENT -> {
Terminal terminal = TerminalRefSerDe.readTerminal(networkContext, network);
area.newAreaBoundary()
.setAc(terminal.getConnectable().getType() != IdentifiableType.HVDC_CONVERTER_STATION)
.setTerminal(terminal)
.add();
}
default -> throw new PowsyblException("Unknown element name '" + elementName + "' in 'controlArea'");
}
});
}
@Override
public boolean isSerializable(CgmesControlAreasSerDe.DummyExt ext) {
return false; // Backward writing compatibility is not yet supported.
}
// An extension is needed to define the SerDe. But it shouldn't be used otherwise.
public static class DummyExt implements Extension<Network> {
@Override
public String getName() {
return "cgmesControlAreas";
}
@Override
public Network getExtendable() {
throw new UnsupportedOperationException("This extension should not be used.");
}
@Override
public void setExtendable(Network extendable) {
throw new UnsupportedOperationException("This extension should not be used.");
}
}
}