Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(charging): provide the charging stations within a certain area #446

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ buildNumber.properties
*.log
credentials.cached
*.lcs
.tiles
.tiles
schwepmo marked this conversation as resolved.
Show resolved Hide resolved
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,12 @@ public ApplicationAmbassador(AmbassadorParameter ambassadorParameter) {
SimulationKernel.SimulationKernel.setCentralPerceptionComponent(centralPerceptionComponent);
}

if (SimulationKernel.SimulationKernel.chargingStationIndex == null) {
// use same bucketsize as TrafficLightTree (see: CPercetion.java) (bucketsize := number of direct children per tree node)
ChargingStationIndex chargingStationIndex = new ChargingStationIndex();
SimulationKernel.SimulationKernel.setChargingStationIndex(chargingStationIndex);
}

// add all application jar files
addJarFiles();
}
Expand Down Expand Up @@ -368,6 +376,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 +460,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;
}
schwepmo marked this conversation as resolved.
Show resolved Hide resolved

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) {
schwepmo marked this conversation as resolved.
Show resolved Hide resolved
return SimulationKernel.SimulationKernel.getChargingStationIndex().getChargingStationsInCircle(searchArea)
.stream().map(ChargingStationObject::getChargingStationData)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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: [email protected]
*/

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> {
/**
* The data object that stores all static and dynamic information of the charging station.
*/
private ChargingStationData chargingStationData;
schwepmo marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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)
kschrab marked this conversation as resolved.
Show resolved Hide resolved
.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,106 @@
/*
* 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: [email protected]
*/

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.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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the two blank spaces at the beginning of each line

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 updateChargingStation(ChargingStationData chargingStationData).
*
* The CS is inserted into the tree when it is queried (e.g. getChargingStationsInCircle(...) or getNumberOfChargingStations(...))
* @param id
* @param position
*/
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) {
indexedChargingStations.get(chargingStationData.getName())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there actually anything we want to update about charging stations?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, e.g. availability

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But didn't we say that this was not of interest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more specific Moritz? Also if we don't use availability in the workshop now, the tree has to return the (correctly updated) values.

Copy link
Contributor

@schwepmo schwepmo Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from my point of view (and I think what we agreed on in an early discussion) the charging station index/tree is just supposed to hold the static information of where charging stations are (at least for now).
I'm fine with also storing availability and charging capabilities (if we actually need it), but just using the ChargingStationData is potentially dangerous imo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll postpone this discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, now I understand, so it's not about the update itself, but the differentiation between Data vs Object. Then maybe let's shift to use either the Object or the pure position... it makes no sense to postpone this, because then if we do a later change, then also later the workshop code has to be adjusted (again)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

.setChargingStationData(chargingStationData);
}

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);
schwepmo marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading