Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit a345e775 authored by Daniel Lehmberg's avatar Daniel Lehmberg
Browse files

Merge branch 'master' into...

Merge branch 'master' into 241-new-flag-to-compute-metric-for-the-quality-of-the-stepcircleoptimizer
parents 18aaa1f8 d6a4b916
......@@ -45,6 +45,7 @@ VadereModelCalibration/**/output
VadereModelTests/**/legacy
VadereUtils/output/**
VadereModelTests/*_private
**/current_commit_hash.txt
# Operating system files
.DS_Store
......@@ -54,3 +55,7 @@ VadereModelTests/*_private
**/output/
**/legacy/
**/*_private/
# Vadere Cache
**/__cache__
**/vadere-server-output
\ No newline at end of file
......@@ -25,6 +25,7 @@ variables:
VADERE_DEPLOYMENT_BASE_URL: "http://www.vadere.org/builds"
VADERE_PACKAGE_NAME_BRANCHES: "vadere.${CI_COMMIT_REF_NAME}.${CI_RUNNER_TAGS}.zip"
VADERE_PACKAGE_NAME_RELEASES: "vadere.${CI_COMMIT_TAG}.${CI_RUNNER_TAGS}.zip"
EIKMESH_PACKAGE_NAME_BRANCHES: "eikmesh.${CI_COMMIT_REF_NAME}.${CI_RUNNER_TAGS}.zip"
# Stage Definitions
# Watch out: integration tests and the seed tests run after deployment, because
......@@ -82,6 +83,8 @@ stages:
- mvn -Dmaven.test.skip=true package
- python3 -m zipfile -c ${VADERE_PACKAGE_NAME_BRANCHES} CHANGELOG.md README.md VadereModelTests/ VadereGui/target/vadere-gui.jar VadereSimulator/target/vadere-console.jar
- scp ${VADERE_PACKAGE_NAME_BRANCHES} di49mur@webdev-mwn.lrz.de:~/webserver/htdocs/builds/master/${VADERE_PACKAGE_NAME_BRANCHES}
- python3 -m zipfile -c ${EIKMESH_PACKAGE_NAME_BRANCHES} VadereMeshing/README.md VadereMeshing/target/meshing-0.1-SNAPSHOT.jar
- scp ${EIKMESH_PACKAGE_NAME_BRANCHES} di49mur@webdev-mwn.lrz.de:~/webserver/htdocs/builds/master/eikmesh/${EIKMESH_PACKAGE_NAME_BRANCHES}
only:
refs:
- master
......
......@@ -6,15 +6,46 @@
### Added
- single step mode in GUI: Allows the user to step through the simulation one
### Changed
## v1.2 (2019-07-13)
### Added
- VadereServer:
- Introducing TraCI server implementation for Vadere to allow remote control
of Vaderes simulation loop.
- VadereManager/target/vadere-server.jar will open a TCP socket and waits
for connection request.
- FloorField Caching:
- CellGrid based floorfields can be loaded from a persisted cache file.
- Added attributes:
- `cacheType: [NO_CACHE|TXT_CACHE|BIN_CACHE]`
- `cacheDir: ""` relative path
- Cache files will be saved in a `__cache__` directory beside (sibling) the
scenario file. With `cacheDir` it is possible to create some structure within
the cache directory. Important: If `cacheDir` is an absolute path the cache
file will not be placed in `__cache__`.
- A TXT_CACHE type will save the CellGrid in a human readable form (CSV) and
BIN_CACHE will use a simple binary format for better performance and space
reasons.
- TikzGenerator:
- add configuration to show all traces of pedestrians, even if they
left the simulation. Config: PostVis -> `Show all Trajectories on Snapshot`
- add named coordinate for each scenario element. The coordinate represents the
centroid point of the polygon and can be used as a point for relative placement
of labels.
- introduced id for reference: Source `src_XXX`, Target `trg_XXX`, AbsorbingArea `absorb_XXX`
Obstacels `obs_XXX`, Stairs `str_XXX`, MeasurementArea `mrmtA_XXX`
- single step mode in GUI: Allows the user to step through the simulation one
step at a time to identify bugs.
- simplify obstacles (with undo support): Merge multiple obstacles based on the
- simplify obstacles (with undo support): Merge multiple obstacles based on the
convex hull their points create. The merge can be undon
- add features to open street map (osm) importer:
- add features to open street map (osm) importer:
- import 'open' paths as polygons with a specified width. With this it is
possible to create walls or subway entrance
- add option to include osm ids into each obstacle created
`PostVis` added functionalities:
- the PostVis works now on the basis of simulation time instead of time steps. Agents' position will be interpolated.
- the user can jump to a specific simulation time
......@@ -24,6 +55,11 @@
### Changed
- version migration ensures that new attributes in the scenario file will be added
with default values if no specific actions are defined in a Transformation class.
- TikzGenerator: style information for pedestrians are moved to dedicated style
classes to simplify changes in generated tikz files.
## v1.0 (2019-06-13)
### Added
......@@ -56,7 +92,7 @@
- `VadereConsole`: Add option `--logname <filename>` to specify the name for the log file.
Please note that the log file `log.out` is always written (because this file is configured
in the `log4j.properties` of each Vadere module (i.e., "gui", "meshing", ...). (c61a3946: Simulator)
- New outputprocessors
- New outputprocessors
* mainly for the BHM: QueueWidthProcessor (to evaluate queueWidth) and PedestrianBehaviorProcessor (evaluate behavior: step / tangential step / sideways step / wait)
* solely for the OSM: PedestrianFootStepProcessor (logs every step instead of the positions at each time step )
......@@ -84,7 +120,7 @@
- `GUI`:
- improved coloring
- `OutputFile`: Distinguish between indices (rows) and headers (columns), in code and with a new checkbox that when enables allow to write meta-data into output files. Furthermore, resolve naming conflicts (if they occur) in headers of output files (see #201).
- `OutputFile`: Distinguish between indices (rows) and headers (columns), in code and with a new checkbox that when enables allow to write meta-data into output files. Furthermore, resolve naming conflicts (if they occur) in headers of output files (see #201).
### Changed
......@@ -95,7 +131,7 @@
- Header in output file have now the following form "[NAME]-PID[ID]". This avoids name conflicts and makes mapping to the relevant processor easy and fast.
- Migration to Java 11 (OpenJDK).
- Removed directory `Documentation/version-control` which contained the Git hooks. The Git hooks are not required anymore. Instead, added `git rev-parse HEAD` to file `VadereSimulator/pom.xml` to create `VadereSimulator/resources/current_commit_hash.txt` during each build via `mvn compile`.
**Note:** The file `current_commit_hash.txt` is created during Maven's validation phase, i.e., before the actual build.
**Note:** The file `current_commit_hash.txt` is created during Maven's validation phase, i.e., before the actual build.
## v0.6 (2018-09-07)
......
......@@ -55,7 +55,7 @@ With the following steps, you can run a simulation with one of the built-in exam
- Start Vadere
- Click *Project* > *Open*
- Choose `vadere.project` of one of the test projects, e.g. [TestOSM](https://gitlab.lrz.de/vadere/vadere/tree/master/VadereModelTests/TestOSM) and click *open*
- Select tahe scenario on the left and press *run selected scenario*
- Select the scenario on the left and press *run selected scenario*
## Build from Source
......
package org.vadere.annotation.traci.client;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypeException;
import javax.tools.JavaFileObject;
@SupportedAnnotationTypes("org.vadere.annotation.traci.client.TraCIApi")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class ClientAnnotationProcessor extends AbstractProcessor {
private StringBuilder apiMembers;
private StringBuilder apiInit;
private StringBuilder apiMapping;
private StringBuilder apiAbstract;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
apiMembers = new StringBuilder();
apiInit = new StringBuilder();
apiMapping = new StringBuilder();
apiAbstract = new StringBuilder();
// see SupportedAnnotationTypes (here only TraCIApi)
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements =
roundEnv.getElementsAnnotatedWith(annotation)
.stream()
.filter(e -> e.getKind().isClass()
&& !e.getKind().equals(ElementKind.ENUM)
&& !e.getModifiers().contains(Modifier.ABSTRACT)
)
.collect(Collectors.toSet());
if (annotatedElements.isEmpty())
continue;
for (Element annotatedElement : annotatedElements) {
try {
writeApiClass(annotatedElement);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
TypeElement element = processingEnv.getElementUtils().
getTypeElement("org.vadere.manager.client.AbstractTestClient");
if (element == null)
writeAbstractTestClient();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
protected void writeAbstractTestClient() throws IOException {
JavaFileObject jFile = processingEnv.getFiler().createSourceFile("AbstractTestClient");
try (PrintWriter writer = new PrintWriter(jFile.openWriter())){
writer.append("package org.vadere.manager.client; ").println();
writer.println();
writer.append("import org.vadere.manager.TraCISocket;").println();
writer.append("import org.vadere.manager.client.ConsoleReader;").println();
writer.append("import java.io.IOException;").println();
writer.println();
writer.append("public abstract class AbstractTestClient {").println();
writer.append(apiMembers.toString()).println();
writer.append("\tpublic AbstractTestClient() { }").println();
writer.println();
writer.append("\tpublic void init(TraCISocket socket, ConsoleReader consoleReader){").println();
writer.append(apiInit.toString());
writer.println();
writer.append(apiMapping.toString());
writer.append("\t}").println();
writer.println();
writer.append(apiAbstract.toString());
writer.println();
writer.append("}").println();
}
}
protected void writeApiClass(Element apiClass) throws IOException {
TraCiApiWrapper traCIApi = new TraCiApiWrapper(apiClass);
JavaFileObject jFile = processingEnv.getFiler().createSourceFile(traCIApi.name);
apiMembers.append(String.format("\tprotected %s.%s %s;\n", traCIApi.packageName, traCIApi.name, traCIApi.name.toLowerCase()));
apiInit.append(String.format("\t\t%s = new %s.%s(socket);\n", traCIApi.name.toLowerCase(), traCIApi.packageName, traCIApi.name));
try (PrintWriter writer = new PrintWriter(jFile.openWriter())){
writer.append("package ").append(traCIApi.packageName).append(";").println();
writer.println();
for (String anImport : traCIApi.imports) {
writer.append("import ").append(anImport).append(";").println();
}
writer.append("import ").append(traCIApi.cmdEnum).append(";").println();
writer.append("import ").append(traCIApi.varEnum).append(";").println();
writer.println();
// start API class
writer.append("public class ").append(traCIApi.name).append(" extends ")
.append(traCIApi.extendedClassName).append(" {").println();
// constructor
writer.append("\tpublic ").append(traCIApi.name).append("(TraCISocket socket) {").println();
writer.append("\t\tsuper(socket, \"").append(traCIApi.name).append("\");").println();
writer.append("\t}").println();
writer.println();
for (Element element : apiClass.getEnclosedElements()) {
List<? extends AnnotationMirror> anMirrors = element.getAnnotationMirrors();
if (anMirrors != null){
for (AnnotationMirror anMirror : anMirrors) {
String anName = anMirror.getAnnotationType().asElement().getSimpleName().toString();
String singeAn = traCIApi.singleAnnotation
.substring(traCIApi.singleAnnotation.lastIndexOf(".") + 1).trim();
if (anName.equals(singeAn)){
ApiHandler apiHandler = new ApiHandler(traCIApi, element, anMirror);
apiMapping.append(String.format("\t\tconsoleReader.addCommand(\"%s.%s\", \"\", this::%s_%s);\n", traCIApi.name.toLowerCase(), apiHandler.name, traCIApi.name.toLowerCase(), apiHandler.name));
apiAbstract.append(String.format("\t\tabstract public void %s_%s (String args[]) throws IOException;\n",traCIApi.name.toLowerCase(), apiHandler.name));
switch (apiHandler.apiType){
case "GET":
writeGET(writer, apiHandler);
break;
case "SET":
writeSET(writer, apiHandler);
break;
}
}
}
}
}
writer.append("}").println(); // end API class
}
}
protected void writeGET(PrintWriter writer, ApiHandler apiHandler){
if (apiHandler.ignoreElementId){
writer.append("\tpublic TraCIGetResponse ").append(apiHandler.name).append("() throws IOException {").println();
writer.append("\t\tTraCIPacket p = TraCIGetCommand.build(").append(apiHandler.cmd).append(", ").append(apiHandler.varId).append(", \"-1\");").println();
}
else {
writer.append("\tpublic TraCIGetResponse ").append(apiHandler.name).append("(String elementID) throws IOException {").println();
writer.append("\t\tTraCIPacket p = TraCIGetCommand.build(").append(apiHandler.cmd).append(", ").append(apiHandler.varId).append(", elementID);").println();
}
writer.append("\n\t\tsocket.sendExact(p);\n").println();
writer.append("\t\treturn (TraCIGetResponse) socket.receiveResponse();").println();
writer.append("\t}").println();
writer.println();
}
protected void writeSET(PrintWriter writer, ApiHandler apiHandler){
writer.append("\tpublic TraCIResponse ").append(apiHandler.name).append("(String elementId, ").append(apiHandler.dataTypeStr).append(" data) throws IOException {").println();
writer.append("\t\tTraCIPacket p = TraCISetCommand.build(")
.append(apiHandler.cmd).append(", elementId, ").append(apiHandler.varId).append(", ").append(apiHandler.varType).append(", data);").println();
writer.append("\n\t\tsocket.sendExact(p);\n").println();
writer.append("\t\treturn socket.receiveResponse();").println();
writer.append("\t}").println();
writer.println();
}
class TraCiApiWrapper {
String name;
String singleAnnotation;
String multipleAnnotation;
String cmdEnum;
String varEnum;
String packageName;
String[] imports;
String extendedClassName;
TraCiApiWrapper(Element apiClass){
TraCIApi traCIApi = apiClass.getAnnotation(TraCIApi.class);
name = traCIApi.name();
packageName = traCIApi.packageName();
imports = traCIApi.imports();
extendedClassName = traCIApi.extendedClassName();
try {
singleAnnotation = traCIApi.singleAnnotation().getCanonicalName();
} catch (MirroredTypeException e){
singleAnnotation = e.getTypeMirror().toString();
}
try {
multipleAnnotation = traCIApi.multipleAnnotation().getCanonicalName();
} catch (MirroredTypeException e){
multipleAnnotation = e.getTypeMirror().toString();
}
try {
cmdEnum = traCIApi.cmdEnum().getCanonicalName();
} catch (MirroredTypeException e){
cmdEnum = e.getTypeMirror().toString();
}
try {
varEnum = traCIApi.varEnum().getCanonicalName();
} catch (MirroredTypeException e){
varEnum = e.getTypeMirror().toString();
}
}
}
class ApiHandler {
String cmd;
String varId;
String varType;
String name;
String dataTypeStr;
boolean ignoreElementId;
String apiType; //SET, GET, SUB
public ApiHandler(TraCiApiWrapper traCIApi, Element method, AnnotationMirror annotationMirror){
ignoreElementId = false; //default
dataTypeStr = "";
String cmdPrefix = traCIApi.cmdEnum;
cmdPrefix = cmdPrefix.substring(cmdPrefix.lastIndexOf('.') + 1).trim();
String varPrefix = traCIApi.varEnum;
varPrefix = varPrefix.substring(varPrefix.lastIndexOf('.') + 1).trim();
Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap = annotationMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : valueMap.entrySet()) {
String key = entry.getKey().getSimpleName().toString();
Object value = entry.getValue().getValue();
switch (key){
case "cmd":
this.cmd = cmdPrefix+ "." + value.toString();
this.apiType = value.toString().substring(0, 3);
break;
case "var":
this.varId = varPrefix + "." + value.toString() + ".id";
this.varType = varPrefix + "." + value.toString() + ".type";
break;
case "name":
this.name = (String) value;
break;
case "ignoreElementId":
this.ignoreElementId = (boolean) value;
break;
case "dataTypeStr":
this.dataTypeStr = value.toString();
}
}
}
@Override
public String toString() {
return "ApiHandler{" +
"cmd='" + cmd + '\'' +
", varId='" + varId + '\'' +
", varType='" + varType + '\'' +
", name='" + name + '\'' +
", ignoreElementId=" + ignoreElementId +
", apiType='" + apiType + '\'' +
'}';
}
}
}
package org.vadere.annotation.traci.client;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface TraCIApi {
String packageName() default "org.vadere.manager.client.traci";
String[] imports() default {
"org.vadere.manager.client.traci.TraCIClientApi",
"org.vadere.manager.TraCISocket",
"org.vadere.manager.traci.commands.TraCIGetCommand",
"org.vadere.manager.traci.commands.TraCISetCommand",
"org.vadere.manager.traci.respons.TraCIGetResponse",
"org.vadere.manager.traci.writer.TraCIPacket",
"org.vadere.manager.traci.respons.TraCIResponse",
"java.io.IOException",
"java.util.ArrayList"
};
String extendedClassName() default "TraCIClientApi";
String name();
Class singleAnnotation();
Class multipleAnnotation();
Class cmdEnum();
Class varEnum();
}
......@@ -303,9 +303,10 @@ ProjectView.btnCancel=Cancel
ProjectView.btnExpertCSV=Export as csv
ProjectView.label.simResults=Simulation Results
TopographyCreator.btnGeneratePoly.tooltip=Generate a Planar Straight-Line Graph (PSLG)
TopographyCreator.btnMergeObstacles.tooltip=Merge Overlapping Obstacles into One Polygon
TopographyCreator.btnMinimizeTopography.tooltip=Select Viewport area
TopographyCreator.btnMaximizeTopography.tooltip=Maximize Viewport area
TopographyCreator.btnMaximizeTopography.tooltip=Maximize Viewport Area
TopographyCreator.btnQuickSave.tooltip=Quicksave
TopographyCreator.btnNewTopography.tooltip=New Scenario
TopographyCreator.btnScroll.tooltip=Scroll Around
......@@ -317,14 +318,14 @@ TopographyCreator.btnCutTopography.tooltip=Cut Scenario
TopographyCreator.btnInsertPedestrian.tooltip=Pedestrian
TopographyCreator.btnTopographyBound.tooltip=Topography Bound
TopographyCreator.btnMergeWithConvexHull.tooltip=Merge With Convex Hull
TopographyCreator.btnTranslation.tooltip=Translate topography
TopographyCreator.btnElementTranslation.tooltip=Translate topography elements
TopographyCreator.btnTranslation.tooltip=Translate Topography
TopographyCreator.btnElementTranslation.tooltip=Translate Topography Elements
TopographyCreator.btnInsertObstacle.tooltip=Obstacle
TopographyCreator.btnInsertTarget.tooltip=Target
TopographyCreator.btnInsertAbsorbingArea.tooltip=Absorbing Area
TopographyCreator.btnInsertSource.tooltip=Source
TopographyCreator.btnInsertStairs.tooltip=Stairs
TopographyCreator.btnInsertMeasurementArea.tooltip=MeasurmentArea
TopographyCreator.btnInsertMeasurementArea.tooltip=Measurment Area
TopographyCreator.btnErase.tooltip=Eraser
TopographyCreator.btnConvexHull.label=Convex Hull
TopographyCreator.btnSimplePolygon.label=Simple Polygon
......
......@@ -297,6 +297,7 @@ ProjectView.btnCancel=Abbrechen
ProjectView.btnDrawVoronoiDiagram.tooltip=Voronoi-Diagramm zeichnen und anzeigen
ProjectView.btnDrawMesh.tooltip=Mesh zeichnen und anzeigen
TopographyCreator.btnGeneratePoly.tooltip=Generiere einen PSLG
TopographyCreator.btnMergeObstacles.tooltip=\u00dcberlappende Hindernisse zusammenf\u00fchren
TopographyCreator.btnMinimizeTopography.tooltip=Select Viewport area
TopographyCreator.btnMaximizeTopography.tooltip=Anzeigebereich maximieren
......
package org.vadere.gui.components.control.simulation;
package org.vadere.gui.components.control;
import org.apache.commons.configuration2.Configuration;
import org.vadere.gui.components.control.simulation.ActionGeneratePNG;
import org.vadere.gui.components.model.DefaultModel;
import org.vadere.gui.components.model.DefaultSimulationConfig;
import org.vadere.gui.components.model.SimulationModel;
import org.vadere.gui.components.utils.Messages;
import org.vadere.gui.components.utils.Resources;
import org.vadere.gui.components.view.SimulationRenderer;
import org.vadere.gui.postvisualization.PostVisualisation;
import org.vadere.meshing.mesh.impl.PSLG;
import org.vadere.meshing.utils.io.poly.PSLGGenerator;
import org.vadere.state.scenario.Obstacle;
import org.vadere.util.config.VadereConfig;
import org.vadere.util.geometry.shapes.VPolygon;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.logging.Logger;
......@@ -21,30 +22,29 @@ import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import javax.swing.*;
public class ActionGeneratePoly extends AbstractAction {
private static Logger logger = Logger.getLogger(ActionGeneratePNG.class);
private static Resources resources = Resources.getInstance("global");
private final SimulationModel<? extends DefaultSimulationConfig> model;
private static final Configuration CONFIG = VadereConfig.getConfig();
private final DefaultModel<? extends DefaultSimulationConfig> model;
public ActionGeneratePoly(final String name, Icon icon, final SimulationRenderer renderer,
final SimulationModel<? extends DefaultSimulationConfig> model) {
public ActionGeneratePoly(final String name, Icon icon, final DefaultModel<? extends DefaultSimulationConfig> model) {
super(name, icon);
this.model = model;
}
@Override
public void actionPerformed(final ActionEvent e) {
JFileChooser fileChooser = new JFileChooser(Preferences.userNodeForPackage(PostVisualisation.class).get("SettingsDialog.snapshotDirectory.path", "."));
JFileChooser fileChooser = new JFileChooser(CONFIG.getString("SettingsDialog.snapshotDirectory.path"));
Date todaysDate = new java.util.Date();
SimpleDateFormat formatter = new SimpleDateFormat(resources.getProperty("SettingsDialog.dataFormat"));
SimpleDateFormat formatter = new SimpleDateFormat(CONFIG.getString("SettingsDialog.dataFormat"));
String formattedDate = formatter.format(todaysDate);
......@@ -67,17 +67,27 @@ public class ActionGeneratePoly extends AbstractAction {
List<Obstacle> obstacles = new ArrayList<>(model.getTopography().getObstacles());
obstacles.removeAll(model.getTopography().getBoundaryObstacles());
List<VPolygon> obsShapes = obstacles.stream()
.map(obs -> obs.getShape())
.map(shape -> new VPolygon(shape))
.collect(Collectors.toList());
// this computes the union of intersecting obstacles.
obsShapes = PSLG.constructHoles(obsShapes);
// this will help to construct a valid non-rectangular bound.
List<VPolygon> polygons = PSLG.constructBound(new VPolygon(bound), obsShapes);
String polyString = PSLGGenerator.toPSLG(
new VPolygon(bound),
obstacles.stream()
.map(obs -> obs.getShape())
.map(shape -> new VPolygon(shape))
.collect(Collectors.toList()));
polygons.get(0),
polygons.size() > 1 ? polygons.subList(1, polygons.size()) : Collections.emptyList());
try {
outputFile.createNewFile();
Writer out = new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8");
out.write(polyString);
out.flush();
VadereConfig.getConfig().setProperty("SettingsDialog.snapshotDirectory.path", outputFile.getParentFile().getAbsolutePath());