Skip to content

Commit

Permalink
feat(charging): provide the charging stations within a certain area (#…
Browse files Browse the repository at this point in the history
…446)

* provide the charging stations within a certain area
* ChargingStations discovery is synchronous
* updates in ChargingStationIndex.java, tree is re-construced when the tree is queried IF a CS was added
- implement KdTree.size() (returned constant 0)
- unit test for ChargingStationIndex
---------

Co-authored-by: William Knöpp <william.anton.knoepp@fokus.fraunhofer.de>
  • Loading branch information
kschrab and iwillitried authored Feb 19, 2025
1 parent 7d0ab09 commit b1840be
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.mosaic.fed.application.ambassador.simulation.TrafficLightGroupUnit;
import org.eclipse.mosaic.fed.application.ambassador.simulation.TrafficManagementCenterUnit;
import org.eclipse.mosaic.fed.application.ambassador.simulation.communication.ReceivedV2xMessage;
import org.eclipse.mosaic.fed.application.ambassador.simulation.electric.providers.ChargingStationIndex;
import org.eclipse.mosaic.fed.application.ambassador.simulation.navigation.CentralNavigationComponent;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.CentralPerceptionComponent;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.DefaultLidarSensorModule;
Expand Down Expand Up @@ -55,6 +56,7 @@
import org.eclipse.mosaic.interactions.traffic.VehicleUpdates;
import org.eclipse.mosaic.interactions.trafficsigns.VehicleSeenTrafficSignsUpdate;
import org.eclipse.mosaic.interactions.vehicle.VehicleRouteRegistration;
import org.eclipse.mosaic.lib.geo.GeoPoint;
import org.eclipse.mosaic.lib.objects.electricity.ChargingStationData;
import org.eclipse.mosaic.lib.objects.environment.EnvironmentEvent;
import org.eclipse.mosaic.lib.objects.traffic.InductionLoopInfo;
Expand Down Expand Up @@ -158,6 +160,11 @@ public ApplicationAmbassador(AmbassadorParameter ambassadorParameter) {
SimulationKernel.SimulationKernel.setCentralPerceptionComponent(centralPerceptionComponent);
}

if (SimulationKernel.SimulationKernel.chargingStationIndex == null) {
ChargingStationIndex chargingStationIndex = new ChargingStationIndex();
SimulationKernel.SimulationKernel.setChargingStationIndex(chargingStationIndex);
}

// add all application jar files
addJarFiles();
}
Expand Down Expand Up @@ -368,6 +375,9 @@ private void process(final ServerRegistration serverRegistration) {

private void process(final ChargingStationRegistration chargingStationRegistration) {
UnitSimulator.UnitSimulator.registerChargingStation(chargingStationRegistration);
String id = chargingStationRegistration.getMapping().getName();
GeoPoint position = chargingStationRegistration.getMapping().getPosition();
SimulationKernel.SimulationKernel.getChargingStationIndex().addChargingStation(id, position);
}

private void process(final TrafficLightRegistration trafficLightRegistration) {
Expand Down Expand Up @@ -449,6 +459,8 @@ private void process(final ChargingStationUpdate chargingStationUpdate) {
final AbstractSimulationUnit simulationUnit =
UnitSimulator.UnitSimulator.getUnitFromId(chargingStationData.getName());

SimulationKernel.SimulationKernel.getChargingStationIndex().updateChargingStation(chargingStationData);

if (simulationUnit == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public enum ErrorRegister {
SIMULATION_KERNEL_CentralNavigationComponentAlreadySet(0x01000029, "The CentralNavigationComponent was already set."),
SIMULATION_KERNEL_CentralPerceptionComponentNotSet(0x01000200, "The CentralPerceptionComponent was not set."),
SIMULATION_KERNEL_CentralPerceptionComponentAlreadySet(0x01000201, "The CentralPerceptionComponent was already set."),
SIMULATION_KERNEL_ChargingStationIndexAlreadySet(0x01000202, "The ChargingStationIndex was already set"),
SIMULATION_KERNEL_ChargingStationIndexNotSet(0x01000203, "The ChargingStationIndex was not set"),

// 0x01000030 to 0x0100003F unit simulator
UNIT_SIMULATOR_IdAlreadyAssigned(0x01000030, "The id is already assigned."),
UNIT_SIMULATOR_IdFromUnitIsNotInMap(0x01000031, "The unit with the id couldn't be found."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.eclipse.mosaic.fed.application.ambassador;

import org.eclipse.mosaic.fed.application.ambassador.simulation.AbstractSimulationUnit;
import org.eclipse.mosaic.fed.application.ambassador.simulation.electric.providers.ChargingStationIndex;
import org.eclipse.mosaic.fed.application.ambassador.simulation.navigation.CentralNavigationComponent;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.CentralPerceptionComponent;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.EnvironmentBasicSensorModule;
Expand Down Expand Up @@ -94,6 +95,8 @@ public enum SimulationKernel {
*/
transient CentralPerceptionComponent centralPerceptionComponent;

transient ChargingStationIndex chargingStationIndex;

/**
* Map containing all the routes with the corresponding edge-id's.
*/
Expand Down Expand Up @@ -259,6 +262,22 @@ public void setCentralPerceptionComponent(CentralPerceptionComponent centralPerc
this.centralPerceptionComponent = centralPerceptionComponent;
}

public void setChargingStationIndex(ChargingStationIndex chargingStationIndex) {
if (this.chargingStationIndex != null) {
throw new RuntimeException(ErrorRegister.SIMULATION_KERNEL_ChargingStationIndexAlreadySet.toString());
}

this.chargingStationIndex = chargingStationIndex;
}

public ChargingStationIndex getChargingStationIndex() {
if (this.chargingStationIndex == null) {
throw new RuntimeException(ErrorRegister.SIMULATION_KERNEL_ChargingStationIndexNotSet.toString());
}

return this.chargingStationIndex;
}

public CentralPerceptionComponent getCentralPerceptionComponent() {
if (centralPerceptionComponent == null) {
throw new RuntimeException(ErrorRegister.SIMULATION_KERNEL_CentralPerceptionComponentNotSet.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@
package org.eclipse.mosaic.fed.application.ambassador.simulation;

import org.eclipse.mosaic.fed.application.ambassador.SimulationKernel;
import org.eclipse.mosaic.fed.application.ambassador.simulation.electric.objects.ChargingStationObject;
import org.eclipse.mosaic.fed.application.app.api.ElectricVehicleApplication;
import org.eclipse.mosaic.fed.application.app.api.os.ElectricVehicleOperatingSystem;
import org.eclipse.mosaic.interactions.electricity.VehicleChargingDenial;
import org.eclipse.mosaic.interactions.electricity.VehicleChargingStartRequest;
import org.eclipse.mosaic.interactions.electricity.VehicleChargingStopRequest;
import org.eclipse.mosaic.lib.geo.GeoCircle;
import org.eclipse.mosaic.lib.geo.GeoPoint;
import org.eclipse.mosaic.lib.objects.electricity.ChargingStationData;
import org.eclipse.mosaic.lib.objects.vehicle.BatteryData;
import org.eclipse.mosaic.lib.objects.vehicle.VehicleType;

import java.util.List;
import java.util.stream.Collectors;

/**
* This class represents an electric vehicle in the application simulator. It extends {@link VehicleUnit}
* with further functionality.
Expand Down Expand Up @@ -103,4 +109,10 @@ public void sendChargingStopRequest() {
);
sendInteractionToRti(vehicleChargingStopRequest);
}

public List<ChargingStationData> getChargingStationsInArea(GeoCircle searchArea) {
return SimulationKernel.SimulationKernel.getChargingStationIndex().getChargingStationsInCircle(searchArea)
.stream().map(ChargingStationObject::getChargingStationData)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2025 Fraunhofer FOKUS and others. All rights reserved.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contact: mosaic@fokus.fraunhofer.de
*/

package org.eclipse.mosaic.fed.application.ambassador.simulation.electric.objects;

import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.index.objects.PointBoundingBox;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.index.objects.SpatialObject;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.index.objects.SpatialObjectBoundingBox;
import org.eclipse.mosaic.lib.geo.CartesianPoint;
import org.eclipse.mosaic.lib.objects.electricity.ChargingStationData;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class ChargingStationObject extends SpatialObject<ChargingStationObject> {
private static final long serialVersionUID = 1L;
/**
* The data object that stores all static and dynamic information of the charging station.
*/
private ChargingStationData chargingStationData;

/**
* The bounding box of a charging station is represented by a single point.
*/
private transient PointBoundingBox boundingBox;

public ChargingStationObject(String id) {
super(id);
}

public ChargingStationObject setChargingStationData(ChargingStationData chargingStationData) {
this.chargingStationData = chargingStationData;
return this;
}

public ChargingStationData getChargingStationData() {
return chargingStationData;
}

@Override
public ChargingStationObject setPosition(CartesianPoint position) {
cartesianPosition.set(position);
position.toVector3d(this);
return this;
}

@Override
public SpatialObjectBoundingBox getBoundingBox() {
if (boundingBox == null) {
boundingBox = new PointBoundingBox(this);
}
return boundingBox;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass()) {
return false;
}

ChargingStationObject that = (ChargingStationObject) o;

return new EqualsBuilder()
.appendSuper(super.equals(o))
.append(chargingStationData, that.chargingStationData)
.append(boundingBox, that.boundingBox)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(5, 11)
.appendSuper(super.hashCode())
.append(chargingStationData)
.toHashCode();
}

/**
* Returns a hard copy of the {@link ChargingStationObject}, this should be used
* when the data of a perceived traffic light is to be altered or stored in memory.
*
* @return a copy of the {@link ChargingStationObject}
*/
@Override
public ChargingStationObject copy() {
return new ChargingStationObject(getId())
.setChargingStationData(chargingStationData)
.setPosition(getProjectedPosition());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2025 Fraunhofer FOKUS and others. All rights reserved.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contact: mosaic@fokus.fraunhofer.de
*/

package org.eclipse.mosaic.fed.application.ambassador.simulation.electric.providers;

import org.eclipse.mosaic.fed.application.ambassador.simulation.electric.objects.ChargingStationObject;
import org.eclipse.mosaic.fed.application.ambassador.simulation.perception.index.objects.SpatialObjectAdapter;
import org.eclipse.mosaic.lib.geo.CartesianPoint;
import org.eclipse.mosaic.lib.geo.GeoCircle;
import org.eclipse.mosaic.lib.geo.GeoPoint;
import org.eclipse.mosaic.lib.objects.electricity.ChargingStationData;
import org.eclipse.mosaic.lib.spatial.KdTree;
import org.eclipse.mosaic.lib.spatial.SpatialTreeTraverser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* A {@link ChargingStationIndex} holds Charging Stations in a tree structure, sorted by their position.
* The tree is initialized and gets updated lazily when a search is performed.
*/
public class ChargingStationIndex {
private final int bucketSize;

/**
* Stores {@link ChargingStationObject}s for fast removal and position update.
*/
final Map<String, ChargingStationObject> indexedChargingStations = new HashMap<>();

private KdTree<ChargingStationObject> chargingStationTree;

private SpatialTreeTraverser.InRadius<ChargingStationObject> treeTraverser;

private boolean needsTreeUpdate = false;

public ChargingStationIndex(int bucketSize) {
this.bucketSize = bucketSize;
}

/**
* Inits a {@link ChargingStationIndex} with default bucket size of 20.
* Bucket size describes the item capacity of one tree node.
*/
public ChargingStationIndex() {
this.bucketSize = 20;
}

/**
* Adds a Charging Station to the tree.
* Be sure to add {@link ChargingStationData} using {@link ChargingStationIndex#updateChargingStation(ChargingStationData)}.
* <p>The CS is inserted into the tree when it is queried (e.g. {@link ChargingStationIndex#getChargingStationsInCircle(GeoCircle)}or
* {@link ChargingStationIndex#getNumberOfChargingStations()}</p>
*
* @param id ID of the ChargingStation
* @param position Position of the ChargingStation
*/
public void addChargingStation(String id, GeoPoint position) {
needsTreeUpdate = true;
indexedChargingStations.computeIfAbsent(id, ChargingStationObject::new).setPosition(position.toCartesian());
}

/**
* Replaces the stations data object.
*/
public void updateChargingStation(ChargingStationData chargingStationData) {
CartesianPoint newPosition = chargingStationData.getPosition().toCartesian();
ChargingStationObject cs = indexedChargingStations.get(chargingStationData.getName());

if (cs.getPosition().toCartesian().equals(newPosition)) {
cs.setChargingStationData(chargingStationData);
return;
}

cs.setChargingStationData(chargingStationData).setPosition(newPosition);
needsTreeUpdate = true;
}

private void updateSearchTree() {
if (!needsTreeUpdate) {
return;
}

List<ChargingStationObject> allChargingStations = new ArrayList<>(indexedChargingStations.values());
chargingStationTree = new KdTree<>(new SpatialObjectAdapter<>(), allChargingStations, bucketSize);
treeTraverser = new SpatialTreeTraverser.InRadius<>();
needsTreeUpdate = false;
}

public List<ChargingStationObject> getChargingStationsInCircle(GeoCircle circle) {
updateSearchTree();
treeTraverser.setup(circle.getCenter().toVector3d(), circle.getRadius());
treeTraverser.traverse(chargingStationTree);
return treeTraverser.getResult();
}

public int getNumberOfChargingStations() {
updateSearchTree();
return chargingStationTree.getRoot().size();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@

package org.eclipse.mosaic.fed.application.app.api.os;

import org.eclipse.mosaic.lib.geo.GeoCircle;
import org.eclipse.mosaic.lib.objects.electricity.ChargingStationData;
import org.eclipse.mosaic.lib.objects.vehicle.BatteryData;

import java.util.List;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -45,4 +48,11 @@ public interface ElectricVehicleOperatingSystem extends VehicleOperatingSystem {
* Sends a request to stop charging the battery of the vehicle.
*/
void sendChargingStopRequest();

/**
* Locate all charging stations in the provided area.
*
* @param searchArea The area where the charging stations are searched
*/
List<ChargingStationData> getChargingStationsInArea(GeoCircle searchArea);
}
Loading

0 comments on commit b1840be

Please sign in to comment.