火力规则:装备匹配规则实现,目标规则实现,阵位规则、航迹规则【初版】
This commit is contained in:
@@ -45,6 +45,8 @@ public class FireRuleExecuteTargetItemVO {
|
||||
|
||||
private String cruiseRouteId;
|
||||
|
||||
private String moveRouteId;
|
||||
|
||||
private List<FireRuleCruiseRouteOffsetItemVO> cruiseRouteOffset;
|
||||
|
||||
private String fireType;
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火力规则输出根文档(与 {@code 火力规则输出.json} 字段一一对应)。
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleOutputVO {
|
||||
|
||||
/**
|
||||
* 来源场景文件名
|
||||
*/
|
||||
private String sourceFile;
|
||||
|
||||
/**
|
||||
* 火力规则任务列表(可含防区等输出字段)
|
||||
*/
|
||||
private List<FireRuleTaskInputVO> fireRuleInputs;
|
||||
|
||||
/**
|
||||
* 场景任务节点列表
|
||||
*/
|
||||
@JsonProperty("Tasks")
|
||||
private List<FireRuleSceneTaskNodeVO> tasks;
|
||||
@JsonProperty("TrackParam")
|
||||
private FireRuleTrackParamVO trackParam;
|
||||
|
||||
/**
|
||||
* 编组列表
|
||||
*/
|
||||
@JsonProperty("Groups")
|
||||
private List<FireRuleSceneGroupNodeVO> groups;
|
||||
@JsonIgnore
|
||||
public List<FireRuleSceneGroupNodeVO> getGroups() {
|
||||
return trackParam == null ? null : trackParam.getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* 红方装备列表
|
||||
*/
|
||||
private List<FireRuleRedWeaponEquipmentVO> redWeapons;
|
||||
@JsonIgnore
|
||||
public void setGroups(List<FireRuleSceneGroupNodeVO> groups) {
|
||||
if (trackParam == null) {
|
||||
trackParam = new FireRuleTrackParamVO();
|
||||
}
|
||||
trackParam.setGroups(groups);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<FireRuleRedWeaponEquipmentVO> getRedWeapons() {
|
||||
return trackParam == null ? null : trackParam.getRedWeapons();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setRedWeapons(List<FireRuleRedWeaponEquipmentVO> redWeapons) {
|
||||
if (trackParam == null) {
|
||||
trackParam = new FireRuleTrackParamVO();
|
||||
}
|
||||
trackParam.setRedWeapons(redWeapons);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 火力规则输出中 redWeapons 数组的单项(红方装备/平台)
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleRedWeaponEquipmentVO {
|
||||
@@ -23,15 +20,10 @@ public class FireRuleRedWeaponEquipmentVO {
|
||||
private String platformType;
|
||||
|
||||
private Boolean isStrikeTarget;
|
||||
|
||||
private Boolean isReconTarget;
|
||||
|
||||
private Boolean isInterferenceTarget;
|
||||
|
||||
private Boolean isDefendImportantPlace;
|
||||
//命中率
|
||||
private Double successTargetRad;
|
||||
|
||||
private String groupType;
|
||||
|
||||
@JsonProperty("EquipmentID")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleRouteTrackPointVO {
|
||||
private String index;
|
||||
private String longitude;
|
||||
private String latitude;
|
||||
private String height;
|
||||
private String speed;
|
||||
private String psia;
|
||||
private Integer time;
|
||||
private String active;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleTrackParamVO {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private Map<String, FireRuleTrackRouteVO> routeMap = new LinkedHashMap<>();
|
||||
|
||||
@JsonProperty("Groups")
|
||||
private List<FireRuleSceneGroupNodeVO> groups;
|
||||
|
||||
@JsonProperty("redWeapons")
|
||||
private List<FireRuleRedWeaponEquipmentVO> redWeapons;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putRoute(String key, JsonNode value) {
|
||||
if ("Groups".equals(key) || "redWeapons".equals(key) || value == null) {
|
||||
return;
|
||||
}
|
||||
routeMap.put(key, MAPPER.convertValue(value, FireRuleTrackRouteVO.class));
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, FireRuleTrackRouteVO> getRouteMap() {
|
||||
return routeMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleTrackRouteVO {
|
||||
private String name;
|
||||
@JsonProperty("StartTime")
|
||||
private Integer startTime;
|
||||
@JsonProperty("EndTime")
|
||||
private Integer endTime;
|
||||
@JsonProperty("TrackType")
|
||||
private String trackType;
|
||||
@JsonProperty("HeightType")
|
||||
private String heightType;
|
||||
private String seaType;
|
||||
@JsonProperty("TrackPoints")
|
||||
private List<FireRuleRouteTrackPointVO> trackPoints;
|
||||
@JsonProperty("Color")
|
||||
private String color;
|
||||
@JsonProperty("PointCount")
|
||||
private Integer pointCount;
|
||||
}
|
||||
@@ -18,8 +18,12 @@ import com.solution.rule.domain.ultimately.vo.FireRuleRedSubComponentsVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponEquipmentVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedPlatformSlotVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponSlotVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRouteTrackPointVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskNodeVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskPayloadVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleTrackParamVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleTrackRouteVO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -170,6 +174,399 @@ public final class FireRuleRedWeaponOutputFillHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 航迹规则:按蓝方 trackPoints 生成 {@link FireRuleTrackParamVO#routeMap} 条目(JSON 顶层 key=航迹 id),
|
||||
* 并将同一 id 写入对应红方任务 {@code execute[0].targetList[*].moveRouteId}。多蓝方任务循环时 merge,不覆盖已有 id。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void fillTrackParamAndBindMoveRoute(FireRuleOutputVO out, FireRuleTaskInputDTO blueTask, Map params) {
|
||||
if (out == null || blueTask == null || out.getTasks() == null || out.getTasks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ensureTrackParam(out);
|
||||
List<FireRuleSceneTaskNodeVO> tasks = out.getTasks();
|
||||
List<FireRuleRedWeaponEquipmentVO> reds = out.getRedWeapons();
|
||||
if (reds == null) {
|
||||
reds = Collections.emptyList();
|
||||
}
|
||||
List<Coord> warPoly = toWarZonePolygon(blueTask.getWarZoneLocation());
|
||||
Coord blueAnchor = computeBlueAnchor(blueTask);
|
||||
Coord clampAnchor = resolveClampAnchor(blueAnchor, warPoly);
|
||||
boolean zoneClamp = Boolean.parseBoolean(String.valueOf(params.getOrDefault("enableTrackWarZoneClamp", true)));
|
||||
|
||||
String dirMode = str(params, "trackPointDirectionMode", "head2next");
|
||||
double fallbackBrg = readDouble(params, "trackFallbackBearingDeg", readDouble(params, "fallbackBearingDeg", 0d));
|
||||
double mainBearing = computeTrackBearingDeg(blueTask, dirMode, fallbackBrg);
|
||||
|
||||
String algo = str(params, "trackRouteAlgorithm", "followBlue");
|
||||
String nameSuffix = str(params, "trackRouteNameSuffix", "航迹");
|
||||
String groundType = str(params, "trackGroundTrackType", "routeLineGround");
|
||||
String blueIdSeg = sanitizeRouteIdSegment(nz(blueTask.getId()));
|
||||
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
FireRuleSceneTaskNodeVO node = tasks.get(i);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
FireRuleRedWeaponEquipmentVO red = i < reds.size() ? reds.get(i) : null;
|
||||
List<TrackNode> nodes = extractSortedBlueTrackNodes(blueTask);
|
||||
if (nodes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<TrackNode> transformed = applyTrackRouteAlgorithm(nodes, algo, params, mainBearing, i);
|
||||
int extraMax = readInt(params, "trackExtraNodesMax", 0);
|
||||
if (extraMax > 0) {
|
||||
transformed = injectExtraTrackNodes(transformed, extraMax);
|
||||
}
|
||||
if (zoneClamp && warPoly.size() >= 3) {
|
||||
transformed = clampTrackNodesToWarZone(transformed, warPoly, clampAnchor);
|
||||
}
|
||||
boolean air = resolveAirTrack(blueTask, red, params);
|
||||
String routeName = nz(node.getDrawName()) + nameSuffix;
|
||||
String routeId = buildUniqueRouteId(out.getTrackParam().getRouteMap(), blueIdSeg, sanitizeRouteIdSegment(nz(node.getId())), i);
|
||||
|
||||
FireRuleTrackRouteVO route = buildTrackRouteVo(transformed, routeName, air, groundType);
|
||||
out.getTrackParam().getRouteMap().put(routeId, route);
|
||||
bindMoveRouteIdToFirstExecute(node, routeId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureTrackParam(FireRuleOutputVO out) {
|
||||
if (out.getTrackParam() == null) {
|
||||
out.setTrackParam(new FireRuleTrackParamVO());
|
||||
}
|
||||
}
|
||||
|
||||
private static String buildUniqueRouteId(Map<String, FireRuleTrackRouteVO> existing, String blueSeg, String nodeSeg, int index) {
|
||||
String base = "route_" + blueSeg + "_" + nodeSeg + "_" + index;
|
||||
if (existing == null || !existing.containsKey(base)) {
|
||||
return base;
|
||||
}
|
||||
int k = 1;
|
||||
while (existing.containsKey(base + "_" + k)) {
|
||||
k++;
|
||||
}
|
||||
return base + "_" + k;
|
||||
}
|
||||
|
||||
private static void bindMoveRouteIdToFirstExecute(FireRuleSceneTaskNodeVO node, String routeId) {
|
||||
if (node == null || isBlank(routeId)) {
|
||||
return;
|
||||
}
|
||||
FireRuleSceneTaskPayloadVO payload = node.getTask();
|
||||
if (payload == null || payload.getExecute() == null || payload.getExecute().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
FireRuleExecuteBlockVO block = payload.getExecute().get(0);
|
||||
if (block == null || block.getTargetList() == null) {
|
||||
return;
|
||||
}
|
||||
for (FireRuleExecuteTargetItemVO item : block.getTargetList()) {
|
||||
if (item != null) {
|
||||
item.setMoveRouteId(routeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static FireRuleTrackRouteVO buildTrackRouteVo(List<TrackNode> nodes, String name, boolean air, String groundType) {
|
||||
FireRuleTrackRouteVO vo = new FireRuleTrackRouteVO();
|
||||
vo.setName(name);
|
||||
vo.setStartTime(null);
|
||||
vo.setEndTime(null);
|
||||
vo.setTrackType(air ? "routeLineAir" : groundType);
|
||||
vo.setHeightType("msl");
|
||||
vo.setSeaType("seaLevel");
|
||||
List<FireRuleRouteTrackPointVO> pts = new ArrayList<>(nodes.size());
|
||||
for (int j = 0; j < nodes.size(); j++) {
|
||||
TrackNode tn = nodes.get(j);
|
||||
FireRuleRouteTrackPointVO p = new FireRuleRouteTrackPointVO();
|
||||
p.setIndex(String.valueOf(j + 1));
|
||||
p.setLongitude(formatCoordNumber(tn.coord.lon));
|
||||
p.setLatitude(formatCoordNumber(tn.coord.lat));
|
||||
p.setHeight(tn.coord.height != null ? formatCoordNumber(tn.coord.height) : "0");
|
||||
p.setSpeed(tn.speed != null ? formatCoordNumber(tn.speed) : null);
|
||||
pts.add(p);
|
||||
}
|
||||
vo.setTrackPoints(pts);
|
||||
vo.setPointCount(pts.size());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private static String formatCoordNumber(double v) {
|
||||
return String.valueOf(v);
|
||||
}
|
||||
|
||||
private static boolean resolveAirTrack(FireRuleTaskInputDTO blue, FireRuleRedWeaponEquipmentVO red, Map params) {
|
||||
String dt = blue != null ? nz(blue.getDataType()) : "";
|
||||
if (csvContainsAny(str(params, "trackAirDataTypeCsv", ""), dt, false)) {
|
||||
return true;
|
||||
}
|
||||
String blob = nz(blue != null ? blue.getDrawName() : "");
|
||||
if (red != null) {
|
||||
blob += " " + nz(red.getName()) + " " + nz(red.getPlatformType());
|
||||
}
|
||||
return csvContainsAny(str(params, "trackAirKeywordsCsv", ""), blob, false);
|
||||
}
|
||||
|
||||
private static boolean csvContainsAny(String csv, String text, boolean tokenAsEquals) {
|
||||
if (isBlank(csv) || text == null) {
|
||||
return false;
|
||||
}
|
||||
String t = text.toLowerCase();
|
||||
for (String part : csv.split(",")) {
|
||||
String k = part.trim();
|
||||
if (k.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (tokenAsEquals) {
|
||||
if (t.equalsIgnoreCase(k)) {
|
||||
return true;
|
||||
}
|
||||
} else if (text.contains(k) || t.contains(k.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String sanitizeRouteIdSegment(String raw) {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return "x";
|
||||
}
|
||||
String s = raw.replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||
return s.isEmpty() ? "x" : s;
|
||||
}
|
||||
|
||||
private static class TrackNode {
|
||||
Coord coord;
|
||||
Double speed;
|
||||
TrackNode(Coord coord, Double speed) {
|
||||
this.coord = coord;
|
||||
this.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TrackNode> extractSortedBlueTrackNodes(FireRuleTaskInputDTO blueTask) {
|
||||
List<TrackNode> list = new ArrayList<>();
|
||||
if (blueTask == null || blueTask.getTrackPoints() == null) {
|
||||
return list;
|
||||
}
|
||||
List<FireRuleTrackPointDTO> pts = new ArrayList<>(blueTask.getTrackPoints());
|
||||
pts.sort((a, b) -> {
|
||||
int ia = a != null && a.getIndex() != null ? a.getIndex() : 0;
|
||||
int ib = b != null && b.getIndex() != null ? b.getIndex() : 0;
|
||||
return Integer.compare(ia, ib);
|
||||
});
|
||||
for (FireRuleTrackPointDTO p : pts) {
|
||||
if (p == null || p.getLongitude() == null || p.getLatitude() == null) {
|
||||
continue;
|
||||
}
|
||||
list.add(new TrackNode(new Coord(p.getLongitude(), p.getLatitude(), p.getHeight()), p.getSpeed()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyTrackRouteAlgorithm(List<TrackNode> nodes, String algo, Map params, double mainBearing, int taskIndex) {
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return nodes;
|
||||
}
|
||||
String a = algo == null ? "followBlue" : algo.trim();
|
||||
if ("shortestPath".equalsIgnoreCase(a)) {
|
||||
return applyShortestPath(nodes, params);
|
||||
}
|
||||
if ("flank".equalsIgnoreCase(a)) {
|
||||
return applyFlank(nodes, params, taskIndex);
|
||||
}
|
||||
if ("jam".equalsIgnoreCase(a)) {
|
||||
return applyJam(nodes, params, mainBearing);
|
||||
}
|
||||
return copyTrackNodes(nodes);
|
||||
}
|
||||
|
||||
private static List<TrackNode> copyTrackNodes(List<TrackNode> nodes) {
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (TrackNode n : nodes) {
|
||||
out.add(new TrackNode(new Coord(n.coord.lon, n.coord.lat, n.coord.height), n.speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyShortestPath(List<TrackNode> nodes, Map params) {
|
||||
int segN = readInt(params, "trackShortPathSegments", 3);
|
||||
if (segN < 1) {
|
||||
segN = 1;
|
||||
}
|
||||
List<TrackNode> out = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
if (i == 0) {
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
continue;
|
||||
}
|
||||
Coord a = nodes.get(i - 1).coord;
|
||||
Coord b = nodes.get(i).coord;
|
||||
Double spPrev = nodes.get(i - 1).speed;
|
||||
Double spNext = nodes.get(i).speed;
|
||||
for (int s = 1; s < segN; s++) {
|
||||
double t = (double) s / (double) segN;
|
||||
double lon = a.lon + (b.lon - a.lon) * t;
|
||||
double lat = a.lat + (b.lat - a.lat) * t;
|
||||
Double h = null;
|
||||
if (a.height != null && b.height != null) {
|
||||
h = a.height + (b.height - a.height) * t;
|
||||
} else if (b.height != null) {
|
||||
h = b.height;
|
||||
} else {
|
||||
h = a.height;
|
||||
}
|
||||
Double spd = null;
|
||||
if (spPrev != null && spNext != null) {
|
||||
spd = spPrev + (spNext - spPrev) * t;
|
||||
} else if (spNext != null) {
|
||||
spd = spNext;
|
||||
} else {
|
||||
spd = spPrev;
|
||||
}
|
||||
out.add(new TrackNode(new Coord(lon, lat, h), spd));
|
||||
}
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static TrackNode copyNode(TrackNode n) {
|
||||
return new TrackNode(new Coord(n.coord.lon, n.coord.lat, n.coord.height), n.speed);
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyFlank(List<TrackNode> nodes, Map params, int taskIndex) {
|
||||
double offsetM = readDouble(params, "trackFlankOffsetMeters", 800d);
|
||||
String sideMode = str(params, "trackFlankSideMode", "alternate");
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Coord c = nodes.get(i).coord;
|
||||
double brg;
|
||||
if (i < nodes.size() - 1) {
|
||||
Coord nxt = nodes.get(i + 1).coord;
|
||||
brg = bearingDeg(c.lon, c.lat, nxt.lon, nxt.lat);
|
||||
} else if (i > 0) {
|
||||
Coord prev = nodes.get(i - 1).coord;
|
||||
brg = bearingDeg(prev.lon, prev.lat, c.lon, c.lat);
|
||||
} else {
|
||||
brg = 0d;
|
||||
}
|
||||
int sign = flankSign(sideMode, i, taskIndex);
|
||||
Coord moved = moveByMeters(c, sign * offsetM, brg + 90d);
|
||||
out.add(new TrackNode(moved, nodes.get(i).speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static int flankSign(String mode, int pointIndex, int taskIndex) {
|
||||
if ("left".equalsIgnoreCase(mode)) {
|
||||
return 1;
|
||||
}
|
||||
if ("right".equalsIgnoreCase(mode)) {
|
||||
return -1;
|
||||
}
|
||||
return (pointIndex + taskIndex) % 2 == 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyJam(List<TrackNode> nodes, Map params, double mainBearing) {
|
||||
double wobble = readDouble(params, "trackJamWobbleMeters", 400d);
|
||||
double periods = readDouble(params, "trackJamSegments", 4d);
|
||||
if (periods < 0.5d) {
|
||||
periods = 0.5d;
|
||||
}
|
||||
int n = nodes.size();
|
||||
List<TrackNode> out = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
double frac = n <= 1 ? 0d : (double) i / (double) (n - 1);
|
||||
double lateral = wobble * Math.sin(2d * Math.PI * periods * frac);
|
||||
Coord c = moveByMeters(nodes.get(i).coord, lateral, mainBearing + 90d);
|
||||
out.add(new TrackNode(c, nodes.get(i).speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> injectExtraTrackNodes(List<TrackNode> nodes, int extraMax) {
|
||||
if (nodes == null || nodes.size() < 2 || extraMax <= 0) {
|
||||
return nodes;
|
||||
}
|
||||
int segs = nodes.size() - 1;
|
||||
List<Integer> perSeg = new ArrayList<>(Collections.nCopies(segs, 0));
|
||||
int base = extraMax / segs;
|
||||
int rem = extraMax % segs;
|
||||
for (int s = 0; s < segs; s++) {
|
||||
perSeg.set(s, base + (s < rem ? 1 : 0));
|
||||
}
|
||||
List<TrackNode> out = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
if (i >= nodes.size() - 1) {
|
||||
break;
|
||||
}
|
||||
int ins = perSeg.get(i);
|
||||
Coord a = nodes.get(i).coord;
|
||||
Coord b = nodes.get(i + 1).coord;
|
||||
Double sa = nodes.get(i).speed;
|
||||
Double sb = nodes.get(i + 1).speed;
|
||||
for (int k = 1; k <= ins; k++) {
|
||||
double t = (double) k / (double) (ins + 1);
|
||||
double lon = a.lon + (b.lon - a.lon) * t;
|
||||
double lat = a.lat + (b.lat - a.lat) * t;
|
||||
Double h = null;
|
||||
if (a.height != null && b.height != null) {
|
||||
h = a.height + (b.height - a.height) * t;
|
||||
} else if (b.height != null) {
|
||||
h = b.height;
|
||||
} else {
|
||||
h = a.height;
|
||||
}
|
||||
Double spd = null;
|
||||
if (sa != null && sb != null) {
|
||||
spd = sa + (sb - sa) * t;
|
||||
} else if (sb != null) {
|
||||
spd = sb;
|
||||
} else {
|
||||
spd = sa;
|
||||
}
|
||||
out.add(new TrackNode(new Coord(lon, lat, h), spd));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> clampTrackNodesToWarZone(List<TrackNode> nodes, List<Coord> poly, Coord anchor) {
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (TrackNode n : nodes) {
|
||||
Coord c = n.coord;
|
||||
if (!isPointInPolygon(c, poly)) {
|
||||
c = projectInsidePolygon(c, anchor, poly);
|
||||
}
|
||||
out.add(new TrackNode(c, n.speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Coord resolveClampAnchor(Coord blueAnchor, List<Coord> poly) {
|
||||
if (poly.size() >= 3) {
|
||||
if (blueAnchor != null && isPointInPolygon(blueAnchor, poly)) {
|
||||
return blueAnchor;
|
||||
}
|
||||
return polygonCentroid(poly);
|
||||
}
|
||||
return blueAnchor != null ? blueAnchor : new Coord(0d, 0d, null);
|
||||
}
|
||||
|
||||
private static Coord polygonCentroid(List<Coord> poly) {
|
||||
double sx = 0, sy = 0;
|
||||
for (Coord c : poly) {
|
||||
sx += c.lon;
|
||||
sy += c.lat;
|
||||
}
|
||||
int n = poly.size();
|
||||
return new Coord(sx / n, sy / n, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标分配:为每个红方 Tasks 节点写入 task.execute[0].targetList[*].targetId。\n
|
||||
* targetId 来源:当前蓝方任务 {@link FireRuleTaskInputDTO#getTaskWeapons()} 下每条武器的 equipmentId。\n
|
||||
|
||||
@@ -225,6 +225,26 @@ public final class RuleFunction {
|
||||
FireRuleRedWeaponOutputFillHelper.fillPlatformPositions(out.getRedWeapons(), fact.getTask(), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 航迹规则:根据蓝方 trackPoints 与作战区生成 TrackParam 航迹,并绑定 execute.targetList.moveRouteId。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void trackRoute(DroolsFact fact, Map globalParams) {
|
||||
if (fact == null || fact.getTask() == null || fact.getFireRuleOutputVO() == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> p = castParams(globalParams);
|
||||
boolean enabled = Boolean.parseBoolean(String.valueOf(p.getOrDefault("trackRuleEnabled", true)));
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
FireRuleOutputVO out = fact.getFireRuleOutputVO();
|
||||
if (out.getTasks() == null || out.getTasks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
FireRuleRedWeaponOutputFillHelper.fillTrackParamAndBindMoveRoute(out, fact.getTask(), p);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> castParams(Map raw) {
|
||||
return raw == null ? new java.util.HashMap<>() : (Map<String, Object>) raw;
|
||||
|
||||
392
auto-solution-rule/src/main/resources/json/new火力规则输出.json
Normal file
392
auto-solution-rule/src/main/resources/json/new火力规则输出.json
Normal file
@@ -0,0 +1,392 @@
|
||||
{
|
||||
"sourceFile": "区域防空31111_2026-04-02 15_29_03.json",
|
||||
"fireRuleInputs": [
|
||||
{
|
||||
"taskWeapons": [
|
||||
{
|
||||
"equipmentId": "40b341f6-d60a-4a29-b84d-2973a3065124",
|
||||
"name": "导弹",
|
||||
"supportType": "2001",
|
||||
"components": [
|
||||
{
|
||||
"deviceId": "81c7e7da-cb7b-4435-bba3-1648e946d2b2",
|
||||
"deviceName": "导弹机动组件",
|
||||
"componentParams": [
|
||||
{
|
||||
"uuid": "b47f34c2-3cba-4be3-a1d5-be0a986dff6f",
|
||||
"attDefaultValue": "",
|
||||
"attExplain": "",
|
||||
"number": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"deviceId": "4cf26cd0-bd5b-4d4c-a2eb-bca6184be5a1",
|
||||
"deviceName": "导弹平台",
|
||||
"componentParams": [
|
||||
{
|
||||
"uuid": "32beea38-ffc2-445a-9687-e9bdb087727f",
|
||||
"attDefaultValue": "",
|
||||
"attExplain": "",
|
||||
"number": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"coordinate": {
|
||||
"longitude": 124.74107151788,
|
||||
"latitude": 26.74143394432,
|
||||
"height": -498.06469972014
|
||||
},
|
||||
"number": 10,
|
||||
"targetId": "0c058874-0c12-4902-8fd0-2cde015965e1"
|
||||
}
|
||||
],
|
||||
"targetId": "0c058874-0c12-4902-8fd0-2cde015965e1",
|
||||
"warZoneLocation": [
|
||||
{
|
||||
"longitude": 122.18971775079,
|
||||
"latitude": 28.64177652916,
|
||||
"height": -99.95248993318
|
||||
},
|
||||
{
|
||||
"longitude": 127.22874089381,
|
||||
"latitude": 28.54075963352,
|
||||
"height": -1063.0224849918
|
||||
},
|
||||
{
|
||||
"longitude": 127.99831970891,
|
||||
"latitude": 23.81306022325,
|
||||
"height": -5900.62524601637
|
||||
},
|
||||
{
|
||||
"longitude": 120.14866648107,
|
||||
"latitude": 23.90272134296,
|
||||
"height": -22.17614107099
|
||||
}
|
||||
],
|
||||
"defZoneLocation": []
|
||||
}
|
||||
],
|
||||
"TrackParam": {
|
||||
"routeLine_220e5b3c-270d-4006-87f0-a0ab4b22deda": {
|
||||
"name": "F-22航线1",
|
||||
"StartTime": 0,
|
||||
"EndTime": 1657,
|
||||
"TrackType": "routeLineAir",
|
||||
"HeightType": "msl",
|
||||
"seaType": "seaLevel",
|
||||
"TrackPoints": [
|
||||
{
|
||||
"index": "1",
|
||||
"longitude": "124.69258218394617",
|
||||
"latitude": "27.801527639000142",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "2",
|
||||
"longitude": "122.9223606765063",
|
||||
"latitude": "27.585793910609084",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "3",
|
||||
"longitude": "121.59995205693548",
|
||||
"latitude": "27.41556157737817",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "4",
|
||||
"longitude": "121.00161216798338",
|
||||
"latitude": "27.07354384591234",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "5",
|
||||
"longitude": "121.0190476615353",
|
||||
"latitude": "26.599336790536423",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "6",
|
||||
"longitude": "121.67869100949231",
|
||||
"latitude": "26.183061747705537",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "7",
|
||||
"longitude": "123.7900051477299",
|
||||
"latitude": "25.733670416880216",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
},
|
||||
{
|
||||
"index": "8",
|
||||
"longitude": "125.47545066085917",
|
||||
"latitude": "26.136857380879235",
|
||||
"height": "6000",
|
||||
"speed": "600",
|
||||
"psia": "0",
|
||||
"time": 0,
|
||||
"active": "null"
|
||||
}
|
||||
],
|
||||
"Color": "rgb(4,161,246)",
|
||||
"PointCount": 8
|
||||
},
|
||||
"Groups": [
|
||||
{
|
||||
"allAngle": 0,
|
||||
"drawName": "J15编组1",
|
||||
"editPermission": [],
|
||||
"groupType": "addGroup",
|
||||
"id": "5dea9ff7-5e45-4f8e-a67f-3ff4187c39ed",
|
||||
"idKey": "id",
|
||||
"isSelected": false,
|
||||
"isShow": false,
|
||||
"leader": "7a16c098-ceec-4c4d-8a24-8f44976a90ca",
|
||||
"name": "addGroup",
|
||||
"parentId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_batFormation",
|
||||
"permission": [
|
||||
"14bc8ff9-3c93-4218-b01a-e144add196f9"
|
||||
],
|
||||
"show": false,
|
||||
"sort": 1774271497726,
|
||||
"wingmanData": [
|
||||
{
|
||||
"alt": 40,
|
||||
"angle": "50",
|
||||
"distance": 100,
|
||||
"key": 0,
|
||||
"name": "9aa9e5aa-9273-4c27-88e1-e582ff561685"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"redWeapons": [
|
||||
{
|
||||
"SupportType": "car",
|
||||
"TroopsDetail": {},
|
||||
"Platform_type": "HQ-9发射车",
|
||||
"isStrikeTarget": false,
|
||||
"isReconTarget": false,
|
||||
"isInterferenceTarget": false,
|
||||
"isDefendImportantPlace": false,
|
||||
"groupType": "equipment",
|
||||
"EquipmentID": "b3c6de29-2b27-4500-a9ce-95d0bebc5cb9",
|
||||
"Name": "HQ-9发射车--5",
|
||||
"OwnerForceSide": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831",
|
||||
"PlatID": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_equipmentPlane",
|
||||
"SubComponents": {
|
||||
"communication": [
|
||||
{
|
||||
"ObjectHandle": "3ed86636-a33a-41ac-aff2-0627eb1f9b28",
|
||||
"arithmetic": {
|
||||
"id": "66cd29fdce08f520f1d9bf0e",
|
||||
"name": "短波电台"
|
||||
},
|
||||
"device": {
|
||||
"id": "5e0e5b064f1fae2ee9fa1000",
|
||||
"name": "3MHZ通信电台",
|
||||
"refId": "5e0e5b064f1fae2ee9fa1000"
|
||||
},
|
||||
"deviceId": "0b700ca6-290e-422a-b4e3-73b222809938",
|
||||
"deviceName": "3MHZ通信电台",
|
||||
"employLabel": false,
|
||||
"facilityName": "3MHZ通信电台",
|
||||
"soleId": "3ed86636-a33a-41ac-aff2-0627eb1f9b28",
|
||||
"twiceModified": {},
|
||||
"zLists": [],
|
||||
"ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e"
|
||||
}
|
||||
],
|
||||
"motorized_assembly": [
|
||||
{
|
||||
"ObjectHandle": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea",
|
||||
"arithmetic": {},
|
||||
"device": {},
|
||||
"deviceId": "55915bc4-c825-43c4-93eb-6a76ab345e42",
|
||||
"deviceName": "",
|
||||
"soleId": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea",
|
||||
"ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e"
|
||||
}
|
||||
],
|
||||
"platform": [
|
||||
{
|
||||
"ObjectHandle": "04a96dbc-837a-48be-8c04-e4f53586c47e",
|
||||
"arithmetic": {
|
||||
"id": "68876d0fd41989f086e905b8",
|
||||
"name": "通用发射车算法"
|
||||
},
|
||||
"device": {
|
||||
"id": "68876d2dd41989f086e905b9",
|
||||
"name": "发射车平台",
|
||||
"refId": "68876d2dd41989f086e905b9"
|
||||
},
|
||||
"deviceId": "164c75ec-6db5-48f9-8285-a7e726fd2a11",
|
||||
"deviceName": "发射车平台",
|
||||
"employLabel": false,
|
||||
"facilityName": "发射车平台",
|
||||
"soleId": "87905d6b-857a-4c5a-a922-5e2d191588d4",
|
||||
"twiceModified": {},
|
||||
"zLists": [],
|
||||
"TrackParamId": "",
|
||||
"positions": [
|
||||
119.28585462691,
|
||||
25.67974332623,
|
||||
34.28352933882
|
||||
]
|
||||
}
|
||||
],
|
||||
"weapon": [
|
||||
{
|
||||
"ObjectHandle": "4e575d4d-2f36-436b-9939-0b96cde96b89",
|
||||
"arithmetic": {
|
||||
"id": "669dd6356bc286bd64e5d66c",
|
||||
"name": "发射架算法"
|
||||
},
|
||||
"codedQueue": "weapon.launcher",
|
||||
"configuration": {
|
||||
"classifyName": "导弹平台",
|
||||
"isMount": 1,
|
||||
"mountedWeapon": {
|
||||
"_id": "68f681794ddde62a52c7e569",
|
||||
"name": "HQ-9"
|
||||
},
|
||||
"number": 4
|
||||
},
|
||||
"device": {
|
||||
"id": "67ff279dafa7ea5aaa3a1236",
|
||||
"name": "通用发射架",
|
||||
"refId": "b9665788-9437-40fb-8a24-eff3d4d3b529"
|
||||
},
|
||||
"deviceId": "4e910ccf-d937-4a93-9b68-116167eda202",
|
||||
"deviceName": "通用发射架",
|
||||
"employLabel": false,
|
||||
"facilityName": "通用发射架",
|
||||
"serialNumber": "launcher",
|
||||
"soleId": "4e575d4d-2f36-436b-9939-0b96cde96b89",
|
||||
"twiceModified": {
|
||||
"launcherType": "sam"
|
||||
},
|
||||
"zLists": [],
|
||||
"ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Tasks": [
|
||||
{
|
||||
"color": "rgb(220,39,39)",
|
||||
"dataType": "taskPlane",
|
||||
"drawName": "HQ-9发射车--3打击任务",
|
||||
"groupType": "tasks",
|
||||
"id": "41d3c5ce-3fda-4dee-a38f-fd3ae7e15e69",
|
||||
"idKey": "id",
|
||||
"isSelected": false,
|
||||
"name": "HQ-9发射车--3打击任务",
|
||||
"parentId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_taskPlane",
|
||||
"permission": [
|
||||
"14bc8ff9-3c93-4218-b01a-e144add196f9",
|
||||
"56a96b1b-14a8-4daf-a2d0-47c7faa4b831"
|
||||
],
|
||||
"show": true,
|
||||
"side": "红方",
|
||||
"sort": 1774271151846,
|
||||
"task": {
|
||||
"at_time": "180",
|
||||
"attackId": "267c5d87-bf36-42f2-b0a8-09c608d94b62",
|
||||
"color": "rgb(220,39,39)",
|
||||
"departureAirport": "",
|
||||
"execute": [
|
||||
{
|
||||
"targetList": [
|
||||
{
|
||||
"ID": "fbddc49c-f7b0-4d07-8000-1b5a2633c461",
|
||||
"arrayPositionId": 4,
|
||||
"arrayPositionIdBak": "",
|
||||
"at_time": {
|
||||
"timeUp": true,
|
||||
"value": 180
|
||||
},
|
||||
"attackType": "",
|
||||
"boost": "",
|
||||
"bootTime": "",
|
||||
"companion": "",
|
||||
"cruiseRouteId": "routeLine_f01be820-33ca-4843-8354-3f93a9986fe3",
|
||||
"cruiseRouteOffset": [
|
||||
{
|
||||
"UpOrDown": true,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"BeforeOrAfter": true,
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"LeftOrRight": true,
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
"moveRouteId": "routeLine_de092f0b-d8a3-4382-8e46-398efae3649c",
|
||||
"fireType": "absolute",
|
||||
"strategy": [],
|
||||
"targetId": "dd20d7d9-7ce8-4531-aa0c-0b2056e7fbcd",
|
||||
"times_interval": 1,
|
||||
"weaponId": "HQ-9",
|
||||
"weaponRelease": "",
|
||||
"weaponType": "",
|
||||
"weaponUseCount": 3
|
||||
}
|
||||
],
|
||||
"type": "assault"
|
||||
}
|
||||
],
|
||||
"landAirport": "",
|
||||
"missionList": [
|
||||
{
|
||||
"label": "HQ-9(4)",
|
||||
"launcherType": "sam",
|
||||
"number": 4,
|
||||
"value": "HQ-9"
|
||||
}
|
||||
],
|
||||
"name": "HQ-9发射车--3打击任务",
|
||||
"side": "红方",
|
||||
"sideId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831",
|
||||
"speed": 600,
|
||||
"type": "assault",
|
||||
"weaponId": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import static com.solution.rule.utils.RuleFunction.equipmentRule;
|
||||
import static com.solution.rule.utils.RuleFunction.target;
|
||||
import static com.solution.rule.utils.RuleFunction.position;
|
||||
import static com.solution.rule.utils.RuleFunction.trackRoute;
|
||||
|
||||
|
||||
global java.util.Map globalParams;
|
||||
@@ -147,6 +148,34 @@ function Map buildParam(){
|
||||
// minInterPlatformDistanceMeters:平台最小间距(米)下限,用于抑制平台点位过度重叠。
|
||||
param.put("minInterPlatformDistanceMeters", 80);
|
||||
|
||||
// ===================== 航迹规则参数(写入 TrackParam 动态 key + execute.targetList.moveRouteId) =====================
|
||||
// trackRuleEnabled:是否启用航迹生成与 moveRouteId 绑定。
|
||||
param.put("trackRuleEnabled", true);
|
||||
// trackRouteAlgorithm:航迹变形算法。followBlue(默认) / shortestPath / flank / jam
|
||||
param.put("trackRouteAlgorithm", "followBlue");
|
||||
// trackRouteNameSuffix:航迹名称 = 红方任务 drawName + 此后缀(默认「航迹」→ 如 xxx打击任务航迹)
|
||||
param.put("trackRouteNameSuffix", "航迹");
|
||||
// trackAirDataTypeCsv:蓝方 dataType 命中任一子串(忽略大小写)则 TrackType=routeLineAir
|
||||
param.put("trackAirDataTypeCsv", "taskPlane,air,plane,flight");
|
||||
// trackAirKeywordsCsv:蓝方 drawName 或红方 name/platformType 命中任一子串则视为飞行航迹
|
||||
param.put("trackAirKeywordsCsv", "机,飞,空,J-,F-,无人机,直升机");
|
||||
// trackGroundTrackType:非飞行类时 TrackType 取值(可先占位,后续再接地面路网)
|
||||
param.put("trackGroundTrackType", "routeLineGround");
|
||||
// 航迹侧向算法复用上方「阵位规则」中的 trackPointDirectionMode(head2next / tail2prev);缺省回退见 trackFallbackBearingDeg
|
||||
param.put("trackFallbackBearingDeg", 0);
|
||||
// enableTrackWarZoneClamp:航迹点是否约束在 warZoneLocation 多边形内
|
||||
param.put("enableTrackWarZoneClamp", true);
|
||||
// trackExtraNodesMax:在蓝方航迹点基础上最多额外插入的点数(0=与蓝方点数持平;>0 时在中段均匀插值)
|
||||
param.put("trackExtraNodesMax", 0);
|
||||
// shortestPath:相邻两点间直线插值分段数(>=1),越大折线越平滑(非真实路网最短路径)
|
||||
param.put("trackShortPathSegments", 3);
|
||||
// flank:侧向偏移距离(米);trackFlankSideMode:alternate / left / right
|
||||
param.put("trackFlankOffsetMeters", 800);
|
||||
param.put("trackFlankSideMode", "alternate");
|
||||
// jam:正弦扰动振幅(米)、沿航迹起伏周期数(越大摆动越密)
|
||||
param.put("trackJamWobbleMeters", 400);
|
||||
param.put("trackJamSegments", 4);
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
@@ -179,3 +208,13 @@ then
|
||||
globalParams.putAll(buildParam());
|
||||
position($fact, globalParams);
|
||||
end
|
||||
|
||||
rule "航迹匹配"
|
||||
salience 70
|
||||
when
|
||||
$fact : DroolsFact(task != null)
|
||||
then
|
||||
// 显式航迹规则:填充 TrackParam 下各航迹 id,并绑定 execute[0].targetList[*].moveRouteId
|
||||
globalParams.putAll(buildParam());
|
||||
trackRoute($fact, globalParams);
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user