火力规则:装备匹配规则实现,目标规则实现,阵位规则、航迹规则【初版】

This commit is contained in:
MHW
2026-04-09 10:22:53 +08:00
parent 2fafd931cc
commit 6add28fdfb
11 changed files with 967 additions and 32 deletions

View File

@@ -45,6 +45,8 @@ public class FireRuleExecuteTargetItemVO {
private String cruiseRouteId;
private String moveRouteId;
private List<FireRuleCruiseRouteOffsetItemVO> cruiseRouteOffset;
private String fireType;

View File

@@ -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);
}
}

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View 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": ""
}
}
]
}

View File

@@ -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");
// 航迹侧向算法复用上方「阵位规则」中的 trackPointDirectionModehead2next / 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侧向偏移距离trackFlankSideModealternate / 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