|
@@ -1,334 +1,334 @@
|
|
|
-package com.css.simulation.resource.scheduler.service;
|
|
|
-
|
|
|
-import api.common.pojo.po.scene.VehicleTypePO;
|
|
|
-import api.common.pojo.vo.model.VehicleVO;
|
|
|
-import api.common.util.FileUtil;
|
|
|
-import api.common.util.LinuxUtil;
|
|
|
-import com.css.simulation.resource.scheduler.mapper.ConfigMapper;
|
|
|
-import com.css.simulation.resource.scheduler.mapper.SimulationAutomaticProjectMapper;
|
|
|
-import com.css.simulation.resource.scheduler.mapper.VehicleMapper;
|
|
|
-import com.css.simulation.resource.scheduler.util.MinioUtil;
|
|
|
-import io.minio.MinioClient;
|
|
|
-import lombok.SneakyThrows;
|
|
|
-import org.dom4j.Attribute;
|
|
|
-import org.dom4j.Document;
|
|
|
-import org.dom4j.Element;
|
|
|
-import org.dom4j.io.OutputFormat;
|
|
|
-import org.dom4j.io.SAXReader;
|
|
|
-import org.dom4j.io.XMLWriter;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.stereotype.Service;
|
|
|
-
|
|
|
-import javax.annotation.Resource;
|
|
|
-import java.io.File;
|
|
|
-import java.io.OutputStreamWriter;
|
|
|
-import java.io.Writer;
|
|
|
-import java.math.BigDecimal;
|
|
|
-import java.nio.file.Files;
|
|
|
-import java.nio.file.Paths;
|
|
|
-import java.util.Iterator;
|
|
|
-
|
|
|
-@Service
|
|
|
-public class VideoService {
|
|
|
- @Resource
|
|
|
- VehicleMapper vehicleMapper;
|
|
|
- @Resource
|
|
|
- ConfigMapper configMapper;
|
|
|
- @Resource
|
|
|
- SimulationAutomaticProjectMapper simulationAutomaticProjectMapper;
|
|
|
- @Resource
|
|
|
- MinioClient minioClient;
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- public static final String oldXoscName = "simulation_my0.xosc";
|
|
|
- public static final String newXoscName = "simulation_my05.xosc";
|
|
|
- public static final String oldXoscRelativePath = "xosc/simulation_my0.xosc";
|
|
|
- public static final String[] csvNameArray = {"Ego.csv", "evaluation.csv"};
|
|
|
-
|
|
|
-
|
|
|
- @Value("${scheduler.linux-path.temp}")
|
|
|
- String linuxTempPath;
|
|
|
- @Value("${scheduler.minio-path.project-result}")
|
|
|
- String projectResultPathOfMinio;
|
|
|
- @Value("${minio.bucket-name}")
|
|
|
- String bucketName;
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- * 生成视频
|
|
|
- */
|
|
|
- @SneakyThrows
|
|
|
- public void generateVideo(String projectId, String projectType, String taskId) {
|
|
|
-
|
|
|
- String csvDirectoryPath = linuxTempPath + "video/" + projectId + "/" + taskId + "/";
|
|
|
- String xodrPath = FileUtil.listAbsolutePathByFiletype(csvDirectoryPath, ".xodr").get(0);
|
|
|
- String osgbPath = FileUtil.listAbsolutePathByFiletype(csvDirectoryPath, ".osgb").get(0);
|
|
|
-
|
|
|
- for (String csvName : csvNameArray) {
|
|
|
- MinioUtil.downloadToFile(minioClient, bucketName, projectResultPathOfMinio + projectId + "/" + taskId + "/" + csvName, csvDirectoryPath + "/" + csvName);
|
|
|
- }
|
|
|
-
|
|
|
- String xoscPath = generateXosc(csvDirectoryPath, xodrPath, osgbPath, projectId, projectType);
|
|
|
-
|
|
|
- String pictureDirectoryPath = csvDirectoryPath + "picture";
|
|
|
- FileUtil.createDirectory(pictureDirectoryPath);
|
|
|
- String esminiCommand = "/root/disk1/simulation-cloud/esmini/build/EnvironmentSimulator/code-examples/image-capture/image-capture "
|
|
|
- + xoscPath + " " + pictureDirectoryPath + "/screenshot";
|
|
|
- String esminiResult = LinuxUtil.execute(esminiCommand);
|
|
|
-
|
|
|
- String videoName = "simulation_output.mp4";
|
|
|
- String videoTargetPathOfLinux = csvDirectoryPath + "video";
|
|
|
- FileUtil.createDirectory(videoTargetPathOfLinux);
|
|
|
- String videoTargetPathOfMinio = projectResultPathOfMinio + projectId + "/" + taskId + "/" + videoName;
|
|
|
-
|
|
|
- String execute = LinuxUtil.execute("ffmpeg"
|
|
|
- + " -f image2 -framerate 30 "
|
|
|
- + " -i " + pictureDirectoryPath + "/screenshot_%05d.tga"
|
|
|
- + " -c:v libx264 -vf format=yuv420p -crf 20 "
|
|
|
- + videoTargetPathOfLinux + "/" + videoName
|
|
|
- );
|
|
|
-
|
|
|
- MinioUtil.uploadFromFile(minioClient, videoTargetPathOfLinux + videoName, bucketName, videoTargetPathOfMinio);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- * Xosc
|
|
|
- */
|
|
|
- @SneakyThrows
|
|
|
- private String generateXosc(String csvDirectoryPath, String xodrPath, String osgbPath, String projectId, String projectType) {
|
|
|
-
|
|
|
- String command = "D:\\Users\\anaconda3\\envs\\flaskProject\\python.exe C:/Users/CSS/Desktop/simulation/gqSw/simulation_my.py " + csvDirectoryPath;
|
|
|
- String execute = LinuxUtil.execute(command);
|
|
|
- String oldXoscPath = csvDirectoryPath + oldXoscRelativePath;
|
|
|
-
|
|
|
- return modifyXosc(oldXoscPath, xodrPath, osgbPath, projectId, projectType);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- * 修改 xosc 文件
|
|
|
- */
|
|
|
- @SneakyThrows
|
|
|
- private String modifyXosc(String oldXoscPath, String xodrPath, String osgbPath, String projectId, String projectType) {
|
|
|
- String newXoscPath = oldXoscPath.replace(oldXoscName, newXoscName);
|
|
|
-
|
|
|
- VehicleTypePO po = vehicleById(projectId, projectType);
|
|
|
-
|
|
|
-
|
|
|
- SAXReader reader = new SAXReader();
|
|
|
-
|
|
|
-
|
|
|
- Document document = reader.read(new File(oldXoscPath));
|
|
|
-
|
|
|
- Element root = document.getRootElement();
|
|
|
- Iterator<Element> iterator0 = root.elementIterator();
|
|
|
- while (iterator0.hasNext()) {
|
|
|
- Element node1 = iterator0.next();
|
|
|
- if ("Entities".equals(node1.getName())) {
|
|
|
- Iterator<Element> iterator1 = node1.elementIterator();
|
|
|
- while (iterator1.hasNext()) {
|
|
|
- Element node2 = iterator1.next();
|
|
|
-
|
|
|
- if ("ScenarioObject".equals(node2.getName())) {
|
|
|
- String scenarioObjectName = node2.attribute("name").getValue();
|
|
|
- if (scenarioObjectName.equals("Ego")) {
|
|
|
- Iterator<Element> iterator2 = node2.elementIterator();
|
|
|
- while (iterator2.hasNext()) {
|
|
|
- Element node3 = iterator2.next();
|
|
|
- if ("Vehicle".equals(node3.getName())) {
|
|
|
- Attribute name = node3.attribute("name");
|
|
|
- name.setText("Audi_A3_2009_red");
|
|
|
- Attribute vehicleCategory = node3.attribute("vehicleCategory");
|
|
|
- vehicleCategory.setText(po.getVehicleCategory());
|
|
|
- node3.addAttribute("model3d",po.getModel3d());
|
|
|
- Iterator<Element> iterator4 = node3.elementIterator();
|
|
|
- while (iterator4.hasNext()) {
|
|
|
- Element node4 = iterator4.next();
|
|
|
- if ("Performance".equals(node4.getName())) {
|
|
|
- Attribute maxSpeed = node4.attribute("maxSpeed");
|
|
|
- maxSpeed.setText(po.getPerformanceMaxSpeed() + "");
|
|
|
- Attribute maxDeceleration = node4.attribute("maxDeceleration");
|
|
|
- maxDeceleration.setText(po.getPerformanceMaxDeceleration() + "");
|
|
|
- Attribute maxAcceleration = node4.attribute("maxAcceleration");
|
|
|
- maxAcceleration.setText(po.getPerformanceMaxAcceleration());
|
|
|
- } else if ("BoundingBox".equals(node4.getName())) {
|
|
|
- Iterator<Element> iterator5 = node4.elementIterator();
|
|
|
- while (iterator5.hasNext()) {
|
|
|
- Element node5 = iterator5.next();
|
|
|
- if ("Center".equals(node5.getName())) {
|
|
|
- Attribute x = node5.attribute("x");
|
|
|
- x.setText(po.getCenterX() + "");
|
|
|
- Attribute y = node5.attribute("y");
|
|
|
- y.setText(po.getCenterY() + "");
|
|
|
- Attribute z = node5.attribute("z");
|
|
|
- z.setText(po.getCenterZ() + "");
|
|
|
- }
|
|
|
- if ("Dimensions".equals(node5.getName())) {
|
|
|
- Attribute width = node5.attribute("width");
|
|
|
- width.setText(po.getDimensionsWidth() + "");
|
|
|
- Attribute length = node5.attribute("length");
|
|
|
- length.setText(po.getDimensionsHeight() + "");
|
|
|
- Attribute height = node5.attribute("height");
|
|
|
- height.setText(po.getDimensionsHeight() + "");
|
|
|
- }
|
|
|
- }
|
|
|
- } else if ("Axles".equals(node4.getName())) {
|
|
|
- Iterator<Element> iterator5 = node4.elementIterator();
|
|
|
- while (iterator5.hasNext()) {
|
|
|
- Element node5 = iterator5.next();
|
|
|
- if ("FrontAxle".equals(node5.getName())) {
|
|
|
- Attribute maxSteering = node5.attribute("maxSteering");
|
|
|
- maxSteering.setText(po.getFrontAxleMaxSteering() + "");
|
|
|
- Attribute wheelDiameter = node5.attribute("wheelDiameter");
|
|
|
- wheelDiameter.setText(po.getFrontAxleWheelDiameter() + "");
|
|
|
- Attribute trackWidth = node5.attribute("trackWidth");
|
|
|
- trackWidth.setText(po.getFrontAxleTrackWidth() + "");
|
|
|
- Attribute positionX = node5.attribute("positionX");
|
|
|
- positionX.setText(po.getFrontAxlePositionX() + "");
|
|
|
- Attribute positionZ = node5.attribute("positionZ");
|
|
|
- positionZ.setText(po.getFrontAxlePositionZ() + "");
|
|
|
- }
|
|
|
- if ("RearAxle".equals(node5.getName())) {
|
|
|
- Attribute maxSteering = node5.attribute("maxSteering");
|
|
|
- maxSteering.setText(po.getRearAxleMaxSteering());
|
|
|
- Attribute wheelDiameter = node5.attribute("wheelDiameter");
|
|
|
- wheelDiameter.setText(po.getRearAxleWheelDiameter() + "");
|
|
|
- Attribute trackWidth = node5.attribute("trackWidth");
|
|
|
- trackWidth.setText(po.getRearAxleTrackWidth() + "");
|
|
|
- Attribute positionX = node5.attribute("positionX");
|
|
|
- positionX.setText(po.getRearAxlePositionX() + "");
|
|
|
- Attribute positionZ = node5.attribute("positionZ");
|
|
|
- positionZ.setText(po.getRearAxlePositionZ() + "");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if ("RoadNetwork".equals(node1.getName())) {
|
|
|
- Iterator<Element> iterator1 = node1.elementIterator();
|
|
|
- while (iterator1.hasNext()) {
|
|
|
- Element node2 = iterator1.next();
|
|
|
-
|
|
|
- if ("LogicFile".equals(node2.getName())) {
|
|
|
- Attribute name = node2.attribute("filepath");
|
|
|
- name.setText(xodrPath);
|
|
|
- }
|
|
|
- if ("SceneGraphFile".equals(node2.getName())) {
|
|
|
- Attribute name = node2.attribute("filepath");
|
|
|
- name.setText(osgbPath);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Writer osWrite = new OutputStreamWriter(Files.newOutputStream(Paths.get(newXoscPath)));
|
|
|
- OutputFormat format = OutputFormat.createPrettyPrint();
|
|
|
- format.setEncoding("UTF-8");
|
|
|
- XMLWriter writer = new XMLWriter(osWrite, format);
|
|
|
- writer.write(document);
|
|
|
- writer.flush();
|
|
|
- writer.close();
|
|
|
- }
|
|
|
- return newXoscPath;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- @SneakyThrows
|
|
|
- private VehicleTypePO vehicleById(String projectId, String projectType) {
|
|
|
- VehicleTypePO po = new VehicleTypePO();
|
|
|
-
|
|
|
- String vehicle = "";
|
|
|
- if (projectType.equals("1")) {
|
|
|
- vehicle = simulationAutomaticProjectMapper.vehicleBySdId(projectId);
|
|
|
- } else if (projectType.equals("2")) {
|
|
|
- vehicle = simulationAutomaticProjectMapper.vehicleByZdId(projectId);
|
|
|
- }
|
|
|
- String vehicleId = configMapper.getVehicleId(vehicle);
|
|
|
-
|
|
|
- VehicleVO vehicleInfo = vehicleMapper.getVehicleInfo(vehicleId);
|
|
|
-
|
|
|
-
|
|
|
- String car = vehicleInfo.getVehicleTypeStr().substring(0, vehicleInfo.getVehicleTypeStr().indexOf(","));
|
|
|
- car = car.substring(0, 1).toLowerCase() + car.substring(1);
|
|
|
- if (car.contains("truck")) {
|
|
|
- po.setVehicleCategory("truck");
|
|
|
- } else if (car.contains("trailer")) {
|
|
|
- po.setVehicleCategory("trailer");
|
|
|
- } else if (car.contains("van")) {
|
|
|
- po.setVehicleCategory("van");
|
|
|
- } else if (car.contains("semitrailer")) {
|
|
|
- po.setVehicleCategory("semitrailer");
|
|
|
- } else if (car.contains("bus")) {
|
|
|
- po.setVehicleCategory("bus");
|
|
|
- } else if (car.contains("motorbike")) {
|
|
|
- po.setVehicleCategory("motorbike");
|
|
|
- } else if (car.contains("bicycle")) {
|
|
|
- po.setVehicleCategory("bicycle");
|
|
|
- } else if (car.contains("bicycle")) {
|
|
|
- po.setVehicleCategory("bicycle");
|
|
|
- } else if (car.contains("train")) {
|
|
|
- po.setVehicleCategory("train");
|
|
|
- } else if (car.contains("tram")) {
|
|
|
- po.setVehicleCategory("tram");
|
|
|
- } else {
|
|
|
- po.setVehicleCategory("car");
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- BigDecimal frontDistance = vehicleInfo.getFrontDistance();
|
|
|
-
|
|
|
- BigDecimal mo2 = new BigDecimal("2");
|
|
|
- BigDecimal rearDistance = vehicleInfo.getRearDistance();
|
|
|
- if (frontDistance.compareTo(rearDistance) > 0) {
|
|
|
- BigDecimal CenterX = (frontDistance.add(rearDistance)).divide(mo2).subtract(rearDistance);
|
|
|
- po.setCenterX(CenterX);
|
|
|
- } else {
|
|
|
- BigDecimal CenterX = (frontDistance.add(rearDistance)).divide(mo2).subtract(frontDistance);
|
|
|
- po.setCenterX(CenterX);
|
|
|
- }
|
|
|
-
|
|
|
- BigDecimal mo3 = new BigDecimal("3");
|
|
|
- po.setCenterZ(vehicleInfo.getHeightDistance().divide(mo3));
|
|
|
-
|
|
|
- BigDecimal LeftDistance = vehicleInfo.getLeftDistance();
|
|
|
- BigDecimal RightDistance = vehicleInfo.getRightDistance();
|
|
|
- po.setDimensionsWidth(LeftDistance.add(RightDistance));
|
|
|
- po.setDimensionsHeight(vehicleInfo.getHeightDistance());
|
|
|
- po.setDimensionsLength(frontDistance.add(rearDistance));
|
|
|
-
|
|
|
- po.setPerformanceMaxSpeed(vehicleInfo.getMaxSpeed());
|
|
|
- po.setPerformanceMaxDeceleration(vehicleInfo.getMaxDeceleration());
|
|
|
- BigDecimal moπ = new BigDecimal("3.141516");
|
|
|
- BigDecimal mo180 = new BigDecimal("180");
|
|
|
- po.setFrontAxleMaxSteering(vehicleInfo.getMaxSteeringAngle().multiply(moπ).divide(mo180));
|
|
|
- po.setFrontAxleWheelDiameter(vehicleInfo.getWheelDiameter());
|
|
|
- BigDecimal mo4 = new BigDecimal("4");
|
|
|
- BigDecimal mo5 = new BigDecimal("5");
|
|
|
- po.setFrontAxleTrackWidth(LeftDistance.add(RightDistance).multiply(mo4).divide(mo5));
|
|
|
- po.setFrontAxlePositionX(po.getCenterX());
|
|
|
- po.setFrontAxlePositionZ(po.getCenterZ());
|
|
|
-
|
|
|
- po.setRearAxleWheelDiameter(vehicleInfo.getWheelDiameter());
|
|
|
- po.setRearAxleTrackWidth(po.getFrontAxleTrackWidth());
|
|
|
- po.setRearAxlePositionX(po.getFrontAxlePositionX());
|
|
|
- po.setRearAxlePositionZ(po.getFrontAxlePositionZ());
|
|
|
- return po;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|