diff --git a/src/components/draw-app/DrawProperties.vue b/src/components/draw-app/DrawProperties.vue index b8b0662..ffeaaba 100644 --- a/src/components/draw-app/DrawProperties.vue +++ b/src/components/draw-app/DrawProperties.vue @@ -81,6 +81,11 @@ + @@ -90,6 +95,8 @@ diff --git a/src/components/draw-app/properties/PlatformProperty.vue b/src/components/draw-app/properties/PlatformProperty.vue index f94a60e..dd0984b 100644 --- a/src/components/draw-app/properties/PlatformProperty.vue +++ b/src/components/draw-app/properties/PlatformProperty.vue @@ -1,6 +1,6 @@ @@ -69,7 +78,7 @@ import { Platform } from 'src/graphics/platform/Platform'; import { Section } from 'src/graphics/section/Section'; import { Station } from 'src/graphics/station/Station'; import { useDrawStore } from 'src/stores/draw-store'; -import { computed, ref } from 'vue'; +import { computed, onMounted, ref } from 'vue'; const drawStore = useDrawStore(); const { data: platformModel, onUpdate } = useFormData( @@ -108,4 +117,20 @@ const optionsUpAndDown = [ { label: '上行', value: true }, { label: '下行', value: false }, ]; +const centralizedStations = ref<{ label: string; value: number }[]>([]); + +onMounted(() => { + const stations = drawStore + .getDrawApp() + .queryStore.queryByType(Station.Type); + centralizedStations.value = [{ label: '', value: 0 }]; + stations.forEach((station) => { + if (station.datas.concentrationStations) { + centralizedStations.value.push({ + label: station.datas.name, + value: station.datas.id, + }); + } + }); +}); diff --git a/src/components/draw-app/properties/SectionProperty.vue b/src/components/draw-app/properties/SectionProperty.vue index 19ab547..3ce3c50 100644 --- a/src/components/draw-app/properties/SectionProperty.vue +++ b/src/components/draw-app/properties/SectionProperty.vue @@ -1,6 +1,6 @@ + @@ -77,9 +83,10 @@ import { useFormData } from 'src/components/DrawAppFormUtils'; import { SectionData } from 'src/drawApp/graphics/SectionInteraction'; import { AxleCounting } from 'src/graphics/axleCounting/AxleCounting'; import { Section, SectionType } from 'src/graphics/section/Section'; +import { Station } from 'src/graphics/station/Station'; import { Turnout } from 'src/graphics/turnout/Turnout'; import { useDrawStore } from 'src/stores/draw-store'; -import { computed } from 'vue'; +import { computed, onMounted, ref } from 'vue'; const drawStore = useDrawStore(); const { data: sectionModel, onUpdate } = useFormData( @@ -140,4 +147,21 @@ const axleCountingRelations = computed(() => { (relation) => relation.getOtherGraphic(section).datas.code ); }); + +const centralizedStations = ref<{ label: string; value: number }[]>([]); + +onMounted(() => { + const stations = drawStore + .getDrawApp() + .queryStore.queryByType(Station.Type); + centralizedStations.value = [{ label: '', value: 0 }]; + stations.forEach((station) => { + if (station.datas.concentrationStations) { + centralizedStations.value.push({ + label: station.datas.name, + value: station.datas.id, + }); + } + }); +}); diff --git a/src/components/draw-app/properties/SignalProperty.vue b/src/components/draw-app/properties/SignalProperty.vue index a9abedd..d7614c6 100644 --- a/src/components/draw-app/properties/SignalProperty.vue +++ b/src/components/draw-app/properties/SignalProperty.vue @@ -1,6 +1,6 @@ @@ -61,10 +65,11 @@ import { useFormData } from 'src/components/DrawAppFormUtils'; import { SignalData } from 'src/drawApp/graphics/SignalInteraction'; import { Section } from 'src/graphics/section/Section'; +import { Station } from 'src/graphics/station/Station'; import { Turnout } from 'src/graphics/turnout/Turnout'; import { graphicData } from 'src/protos/stationLayoutGraphics'; import { useDrawStore } from 'src/stores/draw-store'; -import { computed } from 'vue'; +import { computed, onMounted, ref } from 'vue'; const drawStore = useDrawStore(); const { data: signalModel, onUpdate } = useFormData( @@ -107,4 +112,20 @@ const CoordinateSystemOptions = [ { label: '正线', value: 'MAIN_LINE' }, { label: '换线', value: 'TRANSFER' }, ]; +const centralizedStations = ref<{ label: string; value: number }[]>([]); + +onMounted(() => { + const stations = drawStore + .getDrawApp() + .queryStore.queryByType(Station.Type); + centralizedStations.value = [{ label: '', value: 0 }]; + stations.forEach((station) => { + if (station.datas.concentrationStations) { + centralizedStations.value.push({ + label: station.datas.name, + value: station.datas.id, + }); + } + }); +}); diff --git a/src/components/draw-app/properties/StationProperty.vue b/src/components/draw-app/properties/StationProperty.vue index e834499..91c546c 100644 --- a/src/components/draw-app/properties/StationProperty.vue +++ b/src/components/draw-app/properties/StationProperty.vue @@ -38,22 +38,27 @@ label="公里标(mm):" /> - + @@ -61,17 +66,16 @@ diff --git a/src/components/draw-app/properties/TurnoutProperty.vue b/src/components/draw-app/properties/TurnoutProperty.vue index 4cdd50e..ebd7858 100644 --- a/src/components/draw-app/properties/TurnoutProperty.vue +++ b/src/components/draw-app/properties/TurnoutProperty.vue @@ -1,6 +1,6 @@ + @@ -74,9 +79,10 @@ import { useFormData } from 'src/components/DrawAppFormUtils'; import { TurnoutData } from 'src/drawApp/graphics/TurnoutInteraction'; import { Section } from 'src/graphics/section/Section'; +import { Station } from 'src/graphics/station/Station'; import { Turnout } from 'src/graphics/turnout/Turnout'; import { useDrawStore } from 'src/stores/draw-store'; -import { computed } from 'vue'; +import { computed, onMounted, ref } from 'vue'; const drawStore = useDrawStore(); const { data: turnoutModel, onUpdate } = useFormData( @@ -124,4 +130,21 @@ const turnoutRelations = computed(() => { }(${relation.getOtherRelationParam(turnout).param})` ); }); + +const centralizedStations = ref<{ label: string; value: number }[]>([]); + +onMounted(() => { + const stations = drawStore + .getDrawApp() + .queryStore.queryByType(Station.Type); + centralizedStations.value = [{ label: '', value: 0 }]; + stations.forEach((station) => { + if (station.datas.concentrationStations) { + centralizedStations.value.push({ + label: station.datas.name, + value: station.datas.id, + }); + } + }); +}); diff --git a/src/drawApp/graphics/ConcentrationDividingLineInteraction.ts b/src/drawApp/graphics/ConcentrationDividingLineInteraction.ts new file mode 100644 index 0000000..b6bd21b --- /dev/null +++ b/src/drawApp/graphics/ConcentrationDividingLineInteraction.ts @@ -0,0 +1,77 @@ +import * as pb_1 from 'google-protobuf'; +import { GraphicDataBase } from './GraphicDataBase'; +import { + IConcentrationDividingLineData, + ConcentrationDividingLine, +} from 'src/graphics/concentrationDividingLine/ConcentrationDividingLine'; +import { graphicData } from 'src/protos/stationLayoutGraphics'; +import { IPointData } from 'pixi.js'; + +export class ConcentrationDividingLineData + extends GraphicDataBase + implements IConcentrationDividingLineData +{ + constructor(data?: graphicData.ConcentrationDividingLine) { + let concentrationDividingLine; + if (!data) { + concentrationDividingLine = new graphicData.ConcentrationDividingLine({ + common: GraphicDataBase.defaultCommonInfo( + ConcentrationDividingLine.Type + ), + }); + } else { + concentrationDividingLine = data; + } + super(concentrationDividingLine); + } + public get data(): graphicData.ConcentrationDividingLine { + return this.getData(); + } + get code(): string { + return this.data.code; + } + set code(v: string) { + this.data.code = v; + } + get points(): IPointData[] { + return this.data.points; + } + set points(points: IPointData[]) { + this.data.points = points.map( + (p) => new graphicData.Point({ x: p.x, y: p.y }) + ); + } + get refLeftStationId(): number { + return this.data.refLeftStationId; + } + set refLeftStationId(v: number) { + this.data.refLeftStationId = v; + } + get refRightStationId(): number { + return this.data.refRightStationId; + } + set refRightStationId(v: number) { + this.data.refRightStationId = v; + } + get nodeConWithSecs(): graphicData.NodeConWithSec[] { + return this.data.nodeConWithSecs; + } + set nodeConWithSecs(nodes: graphicData.NodeConWithSec[]) { + this.data.nodeConWithSecs = nodes; + } + get isOtherLineConcentrationDividingLine(): boolean { + return this.data.isOtherLineConcentrationDividingLine; + } + set isOtherLineConcentrationDividingLine(v: boolean) { + this.data.isOtherLineConcentrationDividingLine = v; + } + clone(): ConcentrationDividingLineData { + return new ConcentrationDividingLineData(this.data.cloneMessage()); + } + copyFrom(data: ConcentrationDividingLineData): void { + pb_1.Message.copyInto(data.data, this.data); + } + eq(other: ConcentrationDividingLineData): boolean { + return pb_1.Message.equals(this.data, other.data); + } +} diff --git a/src/drawApp/graphics/PlatformInteraction.ts b/src/drawApp/graphics/PlatformInteraction.ts index 4fc37d6..19e0cdc 100644 --- a/src/drawApp/graphics/PlatformInteraction.ts +++ b/src/drawApp/graphics/PlatformInteraction.ts @@ -71,6 +71,12 @@ export class PlatformData extends GraphicDataBase implements IPlatformData { set refSectionId(v: number) { this.data.refSectionId = v; } + get centralizedStation(): number { + return this.data.centralizedStationId; + } + set centralizedStation(v: number) { + this.data.centralizedStationId = v; + } clone(): PlatformData { return new PlatformData(this.data.cloneMessage()); diff --git a/src/drawApp/graphics/SectionInteraction.ts b/src/drawApp/graphics/SectionInteraction.ts index 852978b..2906369 100644 --- a/src/drawApp/graphics/SectionInteraction.ts +++ b/src/drawApp/graphics/SectionInteraction.ts @@ -77,6 +77,12 @@ export class SectionData extends GraphicDataBase implements ISectionData { set turning(v: boolean) { this.data.turning = v; } + get centralizedStation(): number { + return this.data.centralizedStationId; + } + set centralizedStation(v: number) { + this.data.centralizedStationId = v; + } clone(): SectionData { return new SectionData(this.data.cloneMessage()); } diff --git a/src/drawApp/graphics/SignalInteraction.ts b/src/drawApp/graphics/SignalInteraction.ts index a5e6a0f..402a84d 100644 --- a/src/drawApp/graphics/SignalInteraction.ts +++ b/src/drawApp/graphics/SignalInteraction.ts @@ -62,6 +62,12 @@ export class SignalData extends GraphicDataBase implements ISignalData { set kilometerSystem(v: KilometerSystem) { this.data.kilometerSystem = new graphicData.KilometerSystem(v); } + get centralizedStation(): number { + return this.data.centralizedStationId; + } + set centralizedStation(v: number) { + this.data.centralizedStationId = v; + } clone(): SignalData { return new SignalData(this.data.cloneMessage()); } diff --git a/src/drawApp/graphics/StationInteraction.ts b/src/drawApp/graphics/StationInteraction.ts index 95fc6c1..b72d1b7 100644 --- a/src/drawApp/graphics/StationInteraction.ts +++ b/src/drawApp/graphics/StationInteraction.ts @@ -68,6 +68,12 @@ export class StationData extends GraphicDataBase implements IStationData { set name(v: string) { this.data.name = v; } + get manageStations(): number[] { + return this.data.manageStations; + } + set manageStations(v: number[]) { + this.data.manageStations = v; + } clone(): StationData { return new StationData(this.data.cloneMessage()); } diff --git a/src/drawApp/graphics/TurnoutInteraction.ts b/src/drawApp/graphics/TurnoutInteraction.ts index 8e0fd4c..2a1b808 100644 --- a/src/drawApp/graphics/TurnoutInteraction.ts +++ b/src/drawApp/graphics/TurnoutInteraction.ts @@ -333,6 +333,12 @@ export class TurnoutData extends GraphicDataBase implements ITurnoutData { (v) => new graphicData.KilometerSystem(v) ); } + get centralizedStation(): number { + return this.data.centralizedStationId; + } + set centralizedStation(v: number) { + this.data.centralizedStationId = v; + } clone(): TurnoutData { return new TurnoutData(this.data.cloneMessage()); } diff --git a/src/drawApp/index.ts b/src/drawApp/index.ts index efeb577..2592cde 100644 --- a/src/drawApp/index.ts +++ b/src/drawApp/index.ts @@ -24,6 +24,12 @@ import { SignalState, } from './graphics/SignalInteraction'; import { graphicData } from 'src/protos/stationLayoutGraphics'; +import { + ConcentrationDividingLine, + ConcentrationDividingLineTemplate, +} from 'src/graphics/concentrationDividingLine/ConcentrationDividingLine'; +import { ConcentrationDividingLineData } from './graphics/ConcentrationDividingLineInteraction'; +import { ConcentrationDividingLineDraw } from 'src/graphics/concentrationDividingLine/ConcentrationDividingLineDrawAssistant'; import { Rect, RectTemplate } from 'src/graphics/rect/Rect'; import { RectDraw } from 'src/graphics/rect/RectDrawAssistant'; import { RectData } from './graphics/RectInteraction'; @@ -257,7 +263,13 @@ export function initDrawApp(): IDrawApp { new AxleCountingTemplate(new AxleCountingData()) ), new SeparatorDraw(app, new SeparatorTemplate(new SeparatorData())), - DrawSignalInteraction.init(app); + new ConcentrationDividingLineDraw( + app, + new ConcentrationDividingLineTemplate( + new ConcentrationDividingLineData() + ) + ); + DrawSignalInteraction.init(app); } else { new StationLineDraw(app, new StationLineTemplate(new StationLineData())), new RectDraw(app, new RectTemplate(new RectData())), @@ -364,6 +376,11 @@ export function saveDrawDatas(app: IDrawApp) { } else if (LogicSection.Type === g.type) { const logicSectionData = (g as LogicSection).saveData(); storage.logicSections.push((logicSectionData as LogicSectionData).data); + } else if (g instanceof ConcentrationDividingLine) { + const concentrationDividingLineData = g.saveData(); + storage.concentrationDividingLines.push( + (concentrationDividingLineData as ConcentrationDividingLineData).data + ); } }); // storage.Platforms.forEach((item) => { @@ -517,6 +534,9 @@ export async function loadDrawDatas(): Promise { storage.trainWindows.forEach((trainWindow) => { datas.push(new TrainWindowData(trainWindow)); }); + storage.concentrationDividingLines.forEach((concentrationDividingLine) => { + datas.push(new ConcentrationDividingLineData(concentrationDividingLine)); + }); return Promise.resolve({ canvasProperty: storage.canvas, datas: datas, diff --git a/src/graphics/axleCounting/AxleCountingDrawAssistant.ts b/src/graphics/axleCounting/AxleCountingDrawAssistant.ts index 42b2f94..519a30d 100644 --- a/src/graphics/axleCounting/AxleCountingDrawAssistant.ts +++ b/src/graphics/axleCounting/AxleCountingDrawAssistant.ts @@ -43,7 +43,7 @@ export class AxleCountingDraw extends GraphicDrawAssistant< > { codeGraph: AxleCounting; constructor(app: IDrawApp, template: AxleCountingTemplate) { - super(app, template, 'sym_o_circle', '不展示'); + super(app, template, 'sym_o_circle', '计轴'); this.codeGraph = this.graphicTemplate.new(); this.container.addChild(this.codeGraph); AxleCountingInteraction.init(app); diff --git a/src/graphics/concentrationDividingLine/ConcentrationDividingLine.ts b/src/graphics/concentrationDividingLine/ConcentrationDividingLine.ts new file mode 100644 index 0000000..eb38268 --- /dev/null +++ b/src/graphics/concentrationDividingLine/ConcentrationDividingLine.ts @@ -0,0 +1,224 @@ +import { IPointData } from 'pixi.js'; +import { + GraphicData, + JlGraphic, + JlGraphicTemplate, + calculateDistanceFromPointToLine, + getRectangleCenter, + ILineGraphic, +} from 'jl-graphic'; +import { SectionGraphic } from './SectionGraphic'; +import { graphicData } from 'src/protos/stationLayoutGraphics'; +import { Section, SectionType } from '../section/Section'; +import { arePolylinesIntersect } from './ConcentrationDividingLineUtils'; +import { createRelatedRefProto } from '../CommonGraphics'; +import { Turnout,TurnoutPort } from '../turnout/Turnout'; + +export interface IConcentrationDividingLineData extends GraphicData { + get code(): string; // 编号 + set code(v: string); + get points(): IPointData[]; // 线坐标点 + set points(points: IPointData[]); + get refLeftStationId(): number; //左边关联的集中站id + set refLeftStationId(v: number); + get refRightStationId(): number; //右边关联的集中站id + set refRightStationId(v: number); + get nodeConWithSecs(): graphicData.NodeConWithSec[]; // 集中区分割线与区段的交点 + set nodeConWithSecs(nodes: graphicData.NodeConWithSec[]); + get isOtherLineConcentrationDividingLine(): boolean; //集中区分割线绘制在其它线的边界处 + set isOtherLineConcentrationDividingLine(v: boolean); + clone(): IConcentrationDividingLineData; + copyFrom(data: IConcentrationDividingLineData): void; + eq(other: IConcentrationDividingLineData): boolean; +} + +export const ConcentrationDividingLineConsts = { + lineColor: '#f00', + lineWidth: 2, +}; + +enum devicePort { + 'A', + 'B', + 'C', +} + +export class ConcentrationDividingLine + extends JlGraphic + implements ILineGraphic +{ + static Type = 'ConcentrationDividingLine'; + lineGraphic: SectionGraphic; + + constructor() { + super(ConcentrationDividingLine.Type); + this.lineGraphic = new SectionGraphic(); + this.transformSave = true; + this.addChild(this.lineGraphic); + } + + get datas(): IConcentrationDividingLineData { + return this.getDatas(); + } + + get linePoints(): IPointData[] { + return this.datas.points; + } + set linePoints(points: IPointData[]) { + const old = this.datas.clone(); + old.points = points; + this.updateData(old); + } + + doRepaint() { + if (this.datas.points.length < 2) { + throw new Error('Link坐标数据异常'); + } + this.lineGraphic.clear(); + this.lineGraphic.points = this.datas.points; + this.lineGraphic.lineStyle( + ConcentrationDividingLineConsts.lineWidth, + ConcentrationDividingLineConsts.lineColor + ); + this.lineGraphic.paint(); + } + buildRelation() { + const nodeConWithSecs: graphicData.NodeConWithSec[] = []; + const sections = this.queryStore + .queryByType
(Section.Type) + .filter((g) => g.datas.sectionType == SectionType.Physical); + const hasNodeSection = new Map(); + sections.forEach((section) => { + const changeSectionData = section.datas.points.map((point) => + section.localToCanvasPoint(point) + ); + const changeConcentrationDividingLineData = this.datas.points.map( + (point) => this.localToCanvasPoint(point) + ); + const hasNode = arePolylinesIntersect( + changeSectionData, + changeConcentrationDividingLineData + ); + if (hasNode) { + const minA = calculateDistanceFromPointToLine( + hasNode.segment2[0], + hasNode.segment2[1], + section.localToCanvasPoint(section.getStartPoint()) + ); + const minB = calculateDistanceFromPointToLine( + hasNode.segment2[0], + hasNode.segment2[1], + section.localToCanvasPoint(section.getEndPoint()) + ); + const relationParam = minA > minB ? TurnoutPort.B : TurnoutPort.A; + const portRefOtherDevice = + relationParam == 'A' ? section.datas.paRef : section.datas.pbRef; + if ( + portRefOtherDevice?.id && + !hasNodeSection.get(section.id) && + !hasNodeSection.get(portRefOtherDevice.id) + ) { + const refDevice = this.queryStore.queryById( + portRefOtherDevice?.id + ); + const [leftDevice, rightDevice] = + refDevice.localToCanvasPoint( + getRectangleCenter(refDevice.getLocalBounds()) + ).x < + section.localToCanvasPoint( + getRectangleCenter(section.getLocalBounds()) + ).x + ? [ + { + device: refDevice, + port: devicePort[ + portRefOtherDevice.devicePort + ] as TurnoutPort, + }, + { device: section, port: relationParam }, + ] + : [ + { device: section, port: relationParam }, + { + device: refDevice, + port: devicePort[ + portRefOtherDevice.devicePort + ] as TurnoutPort, + }, + ]; + hasNodeSection.set(leftDevice.device.id, '1'); + hasNodeSection.set(rightDevice.device.id, '1'); + nodeConWithSecs.push( + new graphicData.NodeConWithSec({ + leftSection: createRelatedRefProto( + leftDevice.device.type, + leftDevice.device.id, + leftDevice.port + ), + rightSection: createRelatedRefProto( + rightDevice.device.type, + rightDevice.device.id, + rightDevice.port + ), + }) + ); + } else if (!hasNodeSection.get(section.id) && !portRefOtherDevice?.id) { + const [leftSectionId, rightSectionId] = + relationParam === 'A' + ? [undefined, section.id] + : [section.id, undefined]; + hasNodeSection.set(section.id, '1'); + if (leftSectionId == undefined) { + nodeConWithSecs.push( + new graphicData.NodeConWithSec({ + leftSection: undefined, + rightSection: createRelatedRefProto( + Section.Type, + rightSectionId, + TurnoutPort.A + ), + }) + ); + } else { + nodeConWithSecs.push( + new graphicData.NodeConWithSec({ + leftSection: createRelatedRefProto( + Section.Type, + leftSectionId, + TurnoutPort.B + ), + rightSection: undefined, + }) + ); + } + } + } + }); + nodeConWithSecs.sort((a, b) => { + const sectionAId = a.leftSection ? a.leftSection.id : a.rightSection.id; + const sectionA = this.queryStore.queryById
(sectionAId); + const sectionBId = b.leftSection ? b.leftSection.id : b.rightSection.id; + const sectionB = this.queryStore.queryById
(sectionBId); + return ( + sectionA.localToCanvasPoint( + getRectangleCenter(sectionA.getLocalBounds()) + ).y - + sectionB.localToCanvasPoint( + getRectangleCenter(sectionB.getLocalBounds()) + ).y + ); + }); + this.datas.nodeConWithSecs = nodeConWithSecs; + } +} + +export class ConcentrationDividingLineTemplate extends JlGraphicTemplate { + constructor(dataTemplate: IConcentrationDividingLineData) { + super(ConcentrationDividingLine.Type, { dataTemplate }); + } + new() { + const g = new ConcentrationDividingLine(); + g.loadData(this.datas); + return g; + } +} diff --git a/src/graphics/concentrationDividingLine/ConcentrationDividingLineDrawAssistant.ts b/src/graphics/concentrationDividingLine/ConcentrationDividingLineDrawAssistant.ts new file mode 100644 index 0000000..2245a2d --- /dev/null +++ b/src/graphics/concentrationDividingLine/ConcentrationDividingLineDrawAssistant.ts @@ -0,0 +1,212 @@ +import { + IGraphicApp, + GraphicDrawAssistant, + GraphicInteractionPlugin, + IDrawApp, + JlGraphic, + linePoint, + PolylineEditPlugin, + addWayPoint, + clearWayPoint, + MenuItemOptions, + ContextMenu +} from 'jl-graphic'; +import { + IConcentrationDividingLineData, + ConcentrationDividingLine, + ConcentrationDividingLineConsts, + ConcentrationDividingLineTemplate, +} from './ConcentrationDividingLine'; +import { + DisplayObject, + FederatedMouseEvent, + Graphics, + IHitArea, + Point, +} from 'pixi.js'; +import { getWayLineIndex } from '../polygon/PolygonUtils'; + +export class ConcentrationDividingLineDraw extends GraphicDrawAssistant< + ConcentrationDividingLineTemplate, + IConcentrationDividingLineData +> { + points: Point[] = []; + graphic = new Graphics(); + + constructor(app: IDrawApp, template: ConcentrationDividingLineTemplate) { + super(app, template, 'sym_o_timeline', '集中区分割线'); + this.container.addChild(this.graphic); + + ConcentrationDividingLinePointEditPlugin.init(app, this); + } + + bind(): void { + super.bind(); + } + unbind(): void { + super.unbind(); + } + + onLeftDown(e: FederatedMouseEvent): void { + const { x, y } = this.toCanvasCoordinates(e.global); + const p = new Point(x, y); + this.points.push(p); + } + + onRightClick(): void { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + + onEsc(): void { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + + redraw(p: Point): void { + if (this.points.length < 1) return; + this.graphic.clear(); + this.graphic.lineStyle( + ConcentrationDividingLineConsts.lineWidth, + ConcentrationDividingLineConsts.lineColor + ); + + const ps = [...this.points]; + ps.push(p); + ps.forEach((p, i) => { + if (i !== 0) { + this.graphic.lineTo(p.x, p.y); + } else { + this.graphic.moveTo(p.x, p.y); + } + }); + } + + prepareData(data: IConcentrationDividingLineData): boolean { + if (this.points.length < 2) { + console.log('ConcentrationDividingLine绘制因点不够取消绘制'); + return false; + } + data.points = this.points; + return true; + } + + clearCache(): void { + this.points = []; + this.graphic.clear(); + } +} + +export class ConcentrationDividingLineGraphicHitArea implements IHitArea { + concentrationDividingLine: ConcentrationDividingLine; + constructor(concentrationDividingLine: ConcentrationDividingLine) { + this.concentrationDividingLine = concentrationDividingLine; + } + contains(x: number, y: number): boolean { + for ( + let i = 1; + i < this.concentrationDividingLine.datas.points.length; + i++ + ) { + const p1 = this.concentrationDividingLine.datas.points[i - 1]; + const p2 = this.concentrationDividingLine.datas.points[i]; + if ( + linePoint(p1, p2, { x, y }, ConcentrationDividingLineConsts.lineWidth) + ) { + return true; + } + } + return false; + } +} + +const addWaypointConfig: MenuItemOptions = { + name: '添加路径点', +}; +const clearWaypointsConfig: MenuItemOptions = { + name: '清除所有路径点', +}; +const ConcentrationDividingLineEditMenu: ContextMenu = ContextMenu.init({ + name: '集中区分割线编辑菜单', + groups: [ + { + items: [addWaypointConfig, clearWaypointsConfig], + }, + ], +}); + +export class ConcentrationDividingLinePointEditPlugin extends GraphicInteractionPlugin { + static Name = 'ConcentrationDividingLinePointDrag'; + drawAssistant: ConcentrationDividingLineDraw; + + constructor(app: IGraphicApp, da: ConcentrationDividingLineDraw) { + super(ConcentrationDividingLinePointEditPlugin.Name, app); + this.drawAssistant = da; + app.registerMenu(ConcentrationDividingLineEditMenu); + } + static init(app: IGraphicApp, da: ConcentrationDividingLineDraw) { + return new ConcentrationDividingLinePointEditPlugin(app, da); + } + filter(...grahpics: JlGraphic[]): ConcentrationDividingLine[] | undefined { + return grahpics.filter( + (g) => g.type == ConcentrationDividingLine.Type + ) as ConcentrationDividingLine[]; + } + bind(g: ConcentrationDividingLine): void { + g.lineGraphic.eventMode = 'static'; + g.lineGraphic.cursor = 'pointer'; + g.lineGraphic.hitArea = new ConcentrationDividingLineGraphicHitArea(g); + g.transformSave = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnselected, this); + g.on('_rightclick', this.onContextMenu, this); + } + unbind(g: ConcentrationDividingLine): void { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnselected, this); + g.off('_rightclick', this.onContextMenu, this); + } + onContextMenu(e: FederatedMouseEvent) { + const target = e.target as DisplayObject; + const concentrationDividingLine = + target.getGraphic() as ConcentrationDividingLine; + this.app.updateSelected(concentrationDividingLine); + const p = concentrationDividingLine.screenToLocalPoint(e.global); + addWaypointConfig.handler = () => { + const linePoints = concentrationDividingLine.linePoints; + const { start, end } = getWayLineIndex(linePoints, p); + addWayPoint(concentrationDividingLine, false, start, end, p); + }; + clearWaypointsConfig.handler = () => { + clearWayPoint(concentrationDividingLine, false); + }; + ConcentrationDividingLineEditMenu.open(e.global); + } + onSelected(g: DisplayObject): void { + const concentrationDividingLine = g as ConcentrationDividingLine; + let lep = concentrationDividingLine.getAssistantAppend( + PolylineEditPlugin.Name + ); + if (!lep) { + lep = new PolylineEditPlugin(concentrationDividingLine); + concentrationDividingLine.addAssistantAppend(lep); + } + lep.showAll(); + } + onUnselected(g: DisplayObject): void { + const concentrationDividingLine = g as ConcentrationDividingLine; + const lep = + concentrationDividingLine.getAssistantAppend( + PolylineEditPlugin.Name + ); + if (lep) { + lep.hideAll(); + } + } +} diff --git a/src/graphics/concentrationDividingLine/ConcentrationDividingLineUtils.ts b/src/graphics/concentrationDividingLine/ConcentrationDividingLineUtils.ts new file mode 100644 index 0000000..bf6da02 --- /dev/null +++ b/src/graphics/concentrationDividingLine/ConcentrationDividingLineUtils.ts @@ -0,0 +1,192 @@ +import { IPointData } from 'pixi.js'; +import { Section } from '../section/Section'; +import { Turnout } from '../turnout/Turnout'; +import { graphicData } from 'src/protos/stationLayoutGraphics'; +import { IDrawApp, JlGraphic } from 'jl-graphic'; +import { GraphicDataBase } from 'src/drawApp/graphics/GraphicDataBase'; +import { TurnoutData } from 'src/drawApp/graphics/TurnoutInteraction'; +import { SectionData } from 'src/drawApp/graphics/SectionInteraction'; +import { SignalData } from 'src/drawApp/graphics/SignalInteraction'; +import { Signal } from '../signal/Signal'; + +//判断线段与线段有木有交点 +export function isSegmentsIntersect( + segment1: IPointData[], + segment2: IPointData[] +) { + const [p1, p2] = segment1; + const [p3, p4] = segment2; + // 判断包围盒是否相交 + if ( + Math.max(p1.x, p2.x) < Math.min(p3.x, p4.x) || + Math.min(p1.x, p2.x) > Math.max(p3.x, p4.x) || + Math.max(p1.y, p2.y) < Math.min(p3.y, p4.y) || + Math.min(p1.y, p2.y) > Math.max(p3.y, p4.y) + ) { + return false; + } + // 计算向量叉积 + const cross1 = crossProduct(p3, p1, p4); + const cross2 = crossProduct(p3, p2, p4); + const cross3 = crossProduct(p1, p3, p2); + const cross4 = crossProduct(p1, p4, p2); + if (cross1 * cross2 < 0 && cross3 * cross4 < 0) { + return true; + } + + return false; +} + +function crossProduct(p1: IPointData, p2: IPointData, p3: IPointData) { + return (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y); +} + +export function getSegmentsFromPolyline(polyline: IPointData[]) { + const segments = []; + + for (let i = 0; i < polyline.length - 1; i++) { + const segment = [polyline[i], polyline[i + 1]]; + segments.push(segment); + } + + return segments; +} + +//判断折线与折线有木有交点 +export function arePolylinesIntersect( + polyline1: IPointData[], + polyline2: IPointData[] +) { + const segments1 = getSegmentsFromPolyline(polyline1); + const segments2 = getSegmentsFromPolyline(polyline2); + + for (const segment1 of segments1) { + for (const segment2 of segments2) { + if (isSegmentsIntersect(segment1, segment2)) { + return { hasnode: true, segment1, segment2 }; + } + } + } + + return false; +} + +//获取指定区间内的物理区段和道岔 +export function findContainDevice( + refDevice: Section | Turnout, + refDevicePort: graphicData.RelatedRef.DevicePort, + containDeviceIds: number[], + drawApp: IDrawApp +) { + const devicePort = graphicData.RelatedRef.DevicePort; + containDeviceIds.push(refDevice.id); + switch (true) { + case refDevice instanceof Section: + const sectionPaorbRef = + refDevicePort == devicePort.B + ? refDevice.datas.paRef + : refDevice.datas.pbRef; + if (sectionPaorbRef && !containDeviceIds.includes(sectionPaorbRef.id)) { + const pbRefDevice = drawApp.queryStore.queryById
( + sectionPaorbRef.id + ); + findContainDevice( + pbRefDevice, + sectionPaorbRef.devicePort, + containDeviceIds, + drawApp + ); + } + break; + //道岔需要分路--实际的走向 + case refDevice instanceof Turnout: + const otherPorts = [devicePort.A, devicePort.B, devicePort.C].filter( + (port) => port !== refDevicePort + ); + otherPorts.forEach((port) => { + switch (port) { + case devicePort.A: + const turnoutPaRef = refDevice.datas.paRef; + if (turnoutPaRef && !containDeviceIds.includes(turnoutPaRef.id)) { + const paRefDevice = drawApp.queryStore.queryById< + Section | Turnout + >(turnoutPaRef.id); + findContainDevice( + paRefDevice, + turnoutPaRef.devicePort, + containDeviceIds, + drawApp + ); + } + break; + case devicePort.B: + const turnoutPbRef = refDevice.datas.pbRef; + if (turnoutPbRef && !containDeviceIds.includes(turnoutPbRef.id)) { + const pbRefDevice = drawApp.queryStore.queryById< + Section | Turnout + >(turnoutPbRef.id); + findContainDevice( + pbRefDevice, + turnoutPbRef.devicePort, + containDeviceIds, + drawApp + ); + } + break; + case devicePort.C: + const turnoutPcRef = (refDevice as Turnout).datas.pcRef; + if (turnoutPcRef && !containDeviceIds.includes(turnoutPcRef.id)) { + const pcRefDevice = drawApp.queryStore.queryById< + Section | Turnout + >(turnoutPcRef.id); + findContainDevice( + pcRefDevice, + turnoutPcRef.devicePort, + containDeviceIds, + drawApp + ); + } + break; + } + }); + break; + } +} + +export function handleCentralizedStationsData( + devices: JlGraphic[], + centralizedStations: number[] +) { + interface GraphicData { + centralizedStations: number[]; + } + const dataMap = new Map([ + [Turnout.Type, new TurnoutData()], + [Section.Type, new SectionData()], + [Signal.Type, new SignalData()], + ]); + devices.forEach((device) => { + const data = dataMap.get(device.type); + if (data) { + data.copyFrom(device.saveData()); + const dataCopy = data as GraphicDataBase & GraphicData; + dataCopy.centralizedStations = centralizedStations; + device.updateData(data); + } + }); +} + +//找到公共的元素 +type findType = string | number; +export function findCommonElements(arrays: findType[][]) { + if (arrays.length === 0) { + return []; + } + const commonElements: findType[] = []; + arrays[0].forEach((element) => { + if (arrays.every((arr) => arr.includes(element))) { + commonElements.push(element); + } + }); + return commonElements; +} diff --git a/src/graphics/concentrationDividingLine/SectionGraphic.ts b/src/graphics/concentrationDividingLine/SectionGraphic.ts new file mode 100644 index 0000000..c0b13e0 --- /dev/null +++ b/src/graphics/concentrationDividingLine/SectionGraphic.ts @@ -0,0 +1,57 @@ +import { Graphics, IPointData } from 'pixi.js'; +import { assertBezierPoints, convertToBezierParams } from 'jl-graphic'; + +export enum DevicePort { + A = 'A', + B = 'B', + C = 'C', +} + +export class SectionGraphic extends Graphics { + static Type = 'SectionGraphic'; + private _points: IPointData[] = []; + public get points(): IPointData[] { + return this._points; + } + public set points(value: IPointData[]) { + if (!this.isCurve) { + if (value.length < 2) { + throw Error('Polyline must have at least 2 points'); + } + } else { + assertBezierPoints(value); + } + this._points = value; + } + + private _segmentsCount = 10; + public get segmentsCount(): number { + return this._segmentsCount; + } + public set segmentsCount(value: number) { + if (value < 1) { + throw Error('segmentsCount must be at least 1'); + } + this._segmentsCount = value; + } + + isCurve = false; + + constructor() { + super(); + } + + paint() { + if (this.isCurve) { + const bps = convertToBezierParams(this.points); + bps.forEach((bp) => { + this.drawBezierCurve(bp.p1, bp.p2, bp.cp1, bp.cp2, this.segmentsCount); + }); + } else { + this.moveTo(this.points[0].x, this.points[0].y); + for (let i = 1; i < this.points.length; i++) { + this.lineTo(this.points[i].x, this.points[i].y); + } + } + } +} diff --git a/src/graphics/logicSection/LogicSectionDrawAssistant.ts b/src/graphics/logicSection/LogicSectionDrawAssistant.ts index 1320f8c..88f70d6 100644 --- a/src/graphics/logicSection/LogicSectionDrawAssistant.ts +++ b/src/graphics/logicSection/LogicSectionDrawAssistant.ts @@ -18,7 +18,7 @@ export class LogicSectionDraw extends GraphicDrawAssistant< points: Point[] = []; graphic = new Graphics(); constructor(app: IDrawApp, template: LogicSectionTemplate) { - super(app, template, 'sym_o_timeline', '不展示'); + super(app, template, 'sym_o_timeline', '逻辑区段'); this.container.addChild(this.graphic); LogicSectionEditPlugin.init(app); } diff --git a/src/graphics/pathLine/PathLineDrawAssistant.ts b/src/graphics/pathLine/PathLineDrawAssistant.ts index 12e8f6f..9ea384b 100644 --- a/src/graphics/pathLine/PathLineDrawAssistant.ts +++ b/src/graphics/pathLine/PathLineDrawAssistant.ts @@ -38,7 +38,7 @@ export class PathLineDraw extends GraphicDrawAssistant< graphic: Graphics = new Graphics(); constructor(app: IDrawApp, template: PathLineTemplate) { - super(app, template, 'sym_o_horizontal_rule', '不展示'); + super(app, template, 'sym_o_horizontal_rule', 'PathLine'); this.container.addChild(this.graphic); PathLinePointsEditPlugin.init(app); } diff --git a/src/graphics/platform/Platform.ts b/src/graphics/platform/Platform.ts index b2b9d80..2311a5d 100644 --- a/src/graphics/platform/Platform.ts +++ b/src/graphics/platform/Platform.ts @@ -25,6 +25,8 @@ export interface IPlatformData extends GraphicData { set refStation(v: number); get refSectionId(): number; // 关联的物理区段 set refSectionId(v: number); + get centralizedStation(): number; //所属集中站 + set centralizedStation(v: number); clone(): IPlatformData; copyFrom(data: IPlatformData): void; eq(other: IPlatformData): boolean; diff --git a/src/graphics/section/Section.ts b/src/graphics/section/Section.ts index 5cfcc9f..499121d 100644 --- a/src/graphics/section/Section.ts +++ b/src/graphics/section/Section.ts @@ -48,6 +48,8 @@ export interface ISectionData extends GraphicData { set destinationCode(destinationCode: string); get turning(): boolean; set turning(v: boolean); + get centralizedStation(): number; //所属集中站 + set centralizedStation(v: number); clone(): ISectionData; copyFrom(data: ISectionData): void; eq(other: ISectionData): boolean; diff --git a/src/graphics/separator/SeparatorDrawAssistant.ts b/src/graphics/separator/SeparatorDrawAssistant.ts index c477f42..0d0fd65 100644 --- a/src/graphics/separator/SeparatorDrawAssistant.ts +++ b/src/graphics/separator/SeparatorDrawAssistant.ts @@ -26,7 +26,7 @@ export class SeparatorDraw extends GraphicDrawAssistant< > { SeparatorGraph: Separator; constructor(app: IDrawApp, template: SeparatorTemplate) { - super(app, template, 'sym_o_square', '不展示'); + super(app, template, 'sym_o_square', '分隔符Separator'); this.SeparatorGraph = this.graphicTemplate.new(); this.container.addChild(this.SeparatorGraph); SeparatorInteraction.init(app); diff --git a/src/graphics/signal/Signal.ts b/src/graphics/signal/Signal.ts index 2cd20a6..dc28c48 100644 --- a/src/graphics/signal/Signal.ts +++ b/src/graphics/signal/Signal.ts @@ -36,6 +36,8 @@ export interface ISignalData extends GraphicData { set kilometerSystem(v: KilometerSystem); get refDevice(): IRelatedRefData | undefined; set refDevice(v: IRelatedRefData | undefined); + get centralizedStation(): number; //所属集中站 + set centralizedStation(v: number); clone(): ISignalData; copyFrom(data: ISignalData): void; eq(other: ISignalData): boolean; diff --git a/src/graphics/station/Station.ts b/src/graphics/station/Station.ts index e6935fe..3952e05 100644 --- a/src/graphics/station/Station.ts +++ b/src/graphics/station/Station.ts @@ -23,6 +23,8 @@ export interface IStationData extends GraphicData { set concentrationStations(v: boolean); get name(): string; //车站名称 set name(v: string); + get manageStations(): number[]; //集中站管理的车站 + set manageStations(v: number[]); clone(): IStationData; copyFrom(data: IStationData): void; eq(other: IStationData): boolean; diff --git a/src/graphics/train/TrainDrawAssistant.ts b/src/graphics/train/TrainDrawAssistant.ts index 16f42ef..a569c3c 100644 --- a/src/graphics/train/TrainDrawAssistant.ts +++ b/src/graphics/train/TrainDrawAssistant.ts @@ -16,7 +16,7 @@ export class TrainDraw extends GraphicDrawAssistant { _Train: Train | null = null; constructor(app: IDrawApp, template: TrainTemplate) { - super(app, template, 'directions_bus_filled', '不展示'); + super(app, template, 'directions_bus_filled', '车Train'); trainInteraction.init(app); } diff --git a/src/graphics/trainWindow/TrainWindowDrawAssistant.ts b/src/graphics/trainWindow/TrainWindowDrawAssistant.ts index 105a72c..c82cb7d 100644 --- a/src/graphics/trainWindow/TrainWindowDrawAssistant.ts +++ b/src/graphics/trainWindow/TrainWindowDrawAssistant.ts @@ -70,7 +70,7 @@ export class TrainWindowDraw extends GraphicDrawAssistant< > { trainWindowGraph: TrainWindow; constructor(app: IDrawApp, template: TrainWindowTemplate) { - super(app, template, 'sym_o_square', '不展示'); + super(app, template, 'sym_o_square', '车次窗'); this.trainWindowGraph = this.graphicTemplate.new(); this.container.addChild(this.trainWindowGraph); TrainWindowInteraction.init(app); diff --git a/src/graphics/trainWindow/oneClickDrawAssistant.ts b/src/graphics/trainWindow/oneClickDrawAssistant.ts index 9019a50..48ccda4 100644 --- a/src/graphics/trainWindow/oneClickDrawAssistant.ts +++ b/src/graphics/trainWindow/oneClickDrawAssistant.ts @@ -47,7 +47,7 @@ export class OneClickGenerateDraw extends GraphicDrawAssistant< > { lineGraph: OneClickGenerate; constructor(app: JlDrawApp, template: OneClickGenerateTemplate) { - super(app, template, 'sym_o_square', '不展示'); + super(app, template, 'sym_o_square', '辅助线'); this.lineGraph = this.graphicTemplate.new(); this.container.addChild(this.lineGraph); } diff --git a/src/graphics/turnout/Turnout.ts b/src/graphics/turnout/Turnout.ts index 6971f89..c938cb0 100644 --- a/src/graphics/turnout/Turnout.ts +++ b/src/graphics/turnout/Turnout.ts @@ -39,6 +39,8 @@ export interface ITurnoutData extends GraphicData { set pcRef(ref: IRelatedRefData | undefined); get kilometerSystem(): KilometerSystem[]; set kilometerSystem(v: KilometerSystem[]); + get centralizedStation(): number; //所属集中站 + set centralizedStation(v: number); clone(): ITurnoutData; copyFrom(data: ITurnoutData): void; eq(other: ITurnoutData): boolean; diff --git a/src/layouts/DrawLayout.vue b/src/layouts/DrawLayout.vue index 4bb7d19..95fc9fa 100644 --- a/src/layouts/DrawLayout.vue +++ b/src/layouts/DrawLayout.vue @@ -6,26 +6,14 @@ - - 保存 - - - 另存为 - - - 一键关联 - - - 一键生成车次窗 - - - 一键生成分隔符 - - - 一键生成计轴 - - - 一键生成道岔区段 + + {{ item.label }} @@ -174,7 +162,7 @@