GeographicalLayoutFactory.java
/**
* Copyright (c) 2024, 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.nad.layout;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Substation;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.iidm.network.extensions.Coordinate;
import com.powsybl.iidm.network.extensions.SubstationPosition;
import com.powsybl.nad.model.Point;
import org.jgrapht.alg.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Sophie Frasnedo {@literal <sophie.frasnedo at rte-france.com>}
*/
public class GeographicalLayoutFactory extends FixedLayoutFactory implements LayoutFactory {
protected static final Logger LOGGER = LoggerFactory.getLogger(GeographicalLayoutFactory.class);
private static final int SCALING_FACTOR = 150000;
private static final double RADIUS_FACTOR = 150;
public GeographicalLayoutFactory(Network network) {
this(network, SCALING_FACTOR, RADIUS_FACTOR, () -> new BasicForceLayout(false, false));
}
public GeographicalLayoutFactory(Network network, int scalingFactor, double radiusFactor, LayoutFactory layoutFactory) {
super(getFixedNodePosition(network, scalingFactor, radiusFactor), layoutFactory);
}
private static Map<String, Point> getFixedNodePosition(Network network, int scalingFactor, double radiusFactor) {
Map<String, Point> fixedNodePositionMap = new HashMap<>();
network.getSubstationStream().forEach(substation -> fillPositionMap(substation, fixedNodePositionMap, scalingFactor, radiusFactor));
int voltageLevelCount = network.getVoltageLevelCount();
int missingPositions = voltageLevelCount - fixedNodePositionMap.size();
double missingPositionsRatio = (double) missingPositions / voltageLevelCount;
LOGGER.atLevel(missingPositionsRatio > 0.3 ? Level.WARN : Level.INFO)
.log("{} missing voltage level positions out of {} voltage levels", missingPositions, voltageLevelCount);
return fixedNodePositionMap;
}
private static void fillPositionMap(Substation substation, Map<String, Point> fixedNodePositionMap, int scalingFactor, double radiusFactor) {
SubstationPosition substationPosition = substation.getExtension(SubstationPosition.class);
if (substationPosition != null) {
Coordinate coordinate = substationPosition.getCoordinate();
double latitude = coordinate.getLatitude();
double longitude = coordinate.getLongitude();
Pair<Double, Double> mercatorCoordinates = useMercatorLikeProjection(longitude, latitude);
List<VoltageLevel> voltageLevelList = substation.getVoltageLevelStream().toList();
int voltageLevelListSize = voltageLevelList.size();
if (voltageLevelListSize == 1) {
String voltageLevelId = voltageLevelList.get(0).getId();
fixedNodePositionMap.put(voltageLevelId, new Point(scalingFactor * mercatorCoordinates.getFirst(), scalingFactor * mercatorCoordinates.getSecond()));
} else if (voltageLevelListSize > 1) {
//Deal with voltage levels within the same substation (and thus with the same coordinates)
double angle = 2 * Math.PI / voltageLevelListSize;
int i = 0;
for (VoltageLevel voltageLevel : voltageLevelList) {
double angleVoltageLevel = angle * i;
fixedNodePositionMap.put(voltageLevel.getId(), new Point(scalingFactor * mercatorCoordinates.getFirst() + radiusFactor * Math.cos(angleVoltageLevel), scalingFactor * mercatorCoordinates.getSecond() + radiusFactor * Math.sin(angleVoltageLevel)));
i++;
}
}
}
}
private static Pair<Double, Double> useMercatorLikeProjection(double longitude, double latitude) {
double x = longitude * Math.PI / 180;
double y = -Math.log(Math.tan(Math.PI / 4 + latitude * Math.PI / 180 / 2));
return new Pair<>(x, y);
}
}