Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0301fde1a9
|
No files matched your search
@@ -46,10 +46,15 @@ jobs:
|
|||||||
js/
|
js/
|
||||||
terrains/
|
terrains/
|
||||||
|
|
||||||
|
- name: Package Web Map Zip
|
||||||
|
run: |
|
||||||
|
zip -r dv-community-map-web.zip index.html style.css config.js legend.js index.js mapSprites.svg favicon.png opengraph.png catenary_dv.json trackdata_dv.json poi_dv.json shop_items.json js/ terrains/
|
||||||
|
|
||||||
- name: Create Gitea Release
|
- name: Create Gitea Release
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: akkuman/gitea-release-action@v1
|
uses: akkuman/gitea-release-action@v1
|
||||||
with:
|
with:
|
||||||
files: |-
|
files: |-
|
||||||
|
dv-community-map-web.zip
|
||||||
catenary_dv.json
|
catenary_dv.json
|
||||||
trackdata_dv.json
|
trackdata_dv.json
|
||||||
@@ -25,9 +25,9 @@ Interactive vector web map of Derail Valley railway network with DC electrificat
|
|||||||
- `Wire Type`: Color-coded by physical suspension category.
|
- `Wire Type`: Color-coded by physical suspension category.
|
||||||
- **Markers & Tooltips**:
|
- **Markers & Tooltips**:
|
||||||
- Traction Substations: `⚡` badge with nominal voltage, supply voltage, max load (kW), and regenerative inverter presence.
|
- Traction Substations: `⚡` badge with nominal voltage, supply voltage, max load (kW), and regenerative inverter presence.
|
||||||
- Neutral Sections: `⛔` badge with section ID, length, connecting substations, and disconnect traction warning.
|
- Neutral Sections: `⛔ 0V` center badge with section ID, length, connecting substations, disconnect traction warning, and entrance/exit boundary signs (`NeutralBegin`, `NeutralEnd`, `NeutralWarning`) on high zoom.
|
||||||
- Interruption Signs: `⤓` diamond badge for pantograph drop/raise warnings.
|
- Interruption Signs: Typed diamond badges distinguishing Drop Pantographs (`⤓`), Advance Warning (`⚠`), and Raise Pantographs (`⤒`).
|
||||||
- Contact Wires: Nearest-point hovering displaying voltage, substation feed, category, and segment length.
|
- Integrated Tooltips: Hovering over electrified track curves provides unified telemetry displaying both track geometry (grade, curve radius, speed limit, altitude) and catenary network specs (voltage, suspension type, feeding substation, section length).
|
||||||
|
|
||||||
## CommunityMapLocation Mod Integration
|
## CommunityMapLocation Mod Integration
|
||||||
|
|
||||||
|
|||||||
@@ -639,8 +639,8 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
const pixelRatio = MapData.view.pixelRatio;
|
const pixelRatio = MapData.view.pixelRatio;
|
||||||
const scale = MapData.view.scale;
|
const scale = MapData.view.scale;
|
||||||
|
|
||||||
// 1. Contact Wires
|
// 1. Contact Wires & Neutral Lines (decoupled toggles)
|
||||||
if(MapData.layers.catenaryWires && MapData.catenaryPolylines){
|
if(MapData.catenaryPolylines && (MapData.layers.catenaryWires || MapData.layers.catenaryNeutrals)){
|
||||||
let minX = MapData.view.unconvertX(0) - Config.viewCullMargin / scale;
|
let minX = MapData.view.unconvertX(0) - Config.viewCullMargin / scale;
|
||||||
let maxX = MapData.view.unconvertX(mapCanvas.width) + Config.viewCullMargin / scale;
|
let maxX = MapData.view.unconvertX(mapCanvas.width) + Config.viewCullMargin / scale;
|
||||||
let minZ = MapData.view.unconvertY(mapCanvas.height) - Config.viewCullMargin / scale;
|
let minZ = MapData.view.unconvertY(mapCanvas.height) - Config.viewCullMargin / scale;
|
||||||
@@ -650,6 +650,12 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
|
|
||||||
for(const poly of MapData.catenaryPolylines){
|
for(const poly of MapData.catenaryPolylines){
|
||||||
|
if(poly.is_neutral){
|
||||||
|
if(!MapData.layers.catenaryNeutrals) continue;
|
||||||
|
} else {
|
||||||
|
if(!MapData.layers.catenaryWires) continue;
|
||||||
|
}
|
||||||
|
|
||||||
let b = poly.bounds;
|
let b = poly.bounds;
|
||||||
if(b[2] < minX || b[0] > maxX || b[3] < minZ || b[1] > maxZ) continue;
|
if(b[2] < minX || b[0] > maxX || b[3] < minZ || b[1] > maxZ) continue;
|
||||||
|
|
||||||
@@ -663,7 +669,7 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(poly.is_neutral){
|
if(poly.is_neutral){
|
||||||
let w = Math.max(3.5 * pixelRatio, trackWidth * 0.7);
|
let w = Math.max(3.8 * pixelRatio, trackWidth * 0.75);
|
||||||
ctx.lineWidth = w;
|
ctx.lineWidth = w;
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
ctx.strokeStyle = '#ffffff';
|
ctx.strokeStyle = '#ffffff';
|
||||||
@@ -674,20 +680,23 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
} else if(poly.category === 'side_rail'){
|
} else if(poly.category === 'side_rail'){
|
||||||
ctx.lineWidth = Math.max(2.5 * pixelRatio, trackWidth * 0.45);
|
ctx.lineWidth = Math.max(3.0 * pixelRatio, trackWidth * 0.55);
|
||||||
ctx.setLineDash([8 * pixelRatio, 4 * pixelRatio]);
|
ctx.setLineDash([10 * pixelRatio, 5 * pixelRatio]);
|
||||||
|
ctx.strokeStyle = '#111111';
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.lineWidth = Math.max(1.8 * pixelRatio, trackWidth * 0.35);
|
||||||
ctx.strokeStyle = poly.color;
|
ctx.strokeStyle = poly.color;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
} else if(poly.category === 'quad'){
|
} else if(poly.category === 'quad'){
|
||||||
ctx.lineWidth = Math.max(4 * pixelRatio, trackWidth * 0.85);
|
ctx.lineWidth = Math.max(4.2 * pixelRatio, trackWidth * 0.85);
|
||||||
ctx.strokeStyle = '#111111';
|
ctx.strokeStyle = '#111111';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.lineWidth = Math.max(2.2 * pixelRatio, trackWidth * 0.55);
|
ctx.lineWidth = Math.max(2.4 * pixelRatio, trackWidth * 0.55);
|
||||||
ctx.strokeStyle = poly.color;
|
ctx.strokeStyle = poly.color;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
} else if(poly.category === 'dual'){
|
} else if(poly.category === 'dual'){
|
||||||
ctx.lineWidth = Math.max(3.2 * pixelRatio, trackWidth * 0.7);
|
ctx.lineWidth = Math.max(3.4 * pixelRatio, trackWidth * 0.7);
|
||||||
ctx.strokeStyle = poly.color;
|
ctx.strokeStyle = poly.color;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
} else if(poly.category === 'trolley'){
|
} else if(poly.category === 'trolley'){
|
||||||
@@ -702,64 +711,117 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Neutral Section Markers
|
// 2. Neutral Section Markers & Detail Signs
|
||||||
if(MapData.layers.catenaryNeutrals && MapData.catenaryNeutrals){
|
if(MapData.layers.catenaryNeutrals && MapData.catenaryNeutrals){
|
||||||
for(const n of MapData.catenaryNeutrals){
|
for(const n of MapData.catenaryNeutrals){
|
||||||
let nx = MapData.view.convertX(n.center.x);
|
let nx = MapData.view.convertX(n.center.x);
|
||||||
let ny = MapData.view.convertY(n.center.z);
|
let ny = MapData.view.convertY(n.center.z);
|
||||||
if(nx < -50 || nx > mapCanvas.width + 50 || ny < -50 || ny > mapCanvas.height + 50) continue;
|
if(nx >= -50 && nx <= mapCanvas.width + 50 && ny >= -50 && ny <= mapCanvas.height + 50){
|
||||||
|
let badgeSize = 13 * pixelRatio;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(nx, ny, badgeSize, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = '#e63946';
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = 2 * pixelRatio;
|
||||||
|
ctx.strokeStyle = '#ffffff';
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
let badgeSize = 13 * pixelRatio;
|
ctx.fillStyle = '#ffffff';
|
||||||
ctx.beginPath();
|
ctx.font = `bold ${10 * pixelRatio}px "Noto Sans"`;
|
||||||
ctx.arc(nx, ny, badgeSize, 0, Math.PI * 2);
|
ctx.textAlign = 'center';
|
||||||
ctx.fillStyle = '#e63946';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fill();
|
ctx.fillText('0V', nx, ny);
|
||||||
ctx.lineWidth = 2 * pixelRatio;
|
|
||||||
ctx.strokeStyle = '#ffffff';
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.fillStyle = '#ffffff';
|
if(scale >= 0.035){
|
||||||
ctx.font = `bold ${10 * pixelRatio}px "Noto Sans"`;
|
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
|
||||||
ctx.textAlign = 'center';
|
ctx.strokeStyle = '#000000';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.lineWidth = 2.5 * pixelRatio;
|
||||||
ctx.fillText('0V', nx, ny);
|
ctx.strokeText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
|
||||||
|
ctx.fillStyle = '#ffcccc';
|
||||||
|
ctx.fillText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(scale >= 0.04){
|
// High zoom boundary signs (NeutralWarning, NeutralBegin, NeutralEnd)
|
||||||
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
|
if(scale >= 0.04 && n.signs){
|
||||||
ctx.strokeStyle = '#000000';
|
for(const sign of n.signs){
|
||||||
ctx.lineWidth = 2.5 * pixelRatio;
|
let sx = MapData.view.convertX(sign.x);
|
||||||
ctx.strokeText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
|
let sy = MapData.view.convertY(sign.z);
|
||||||
ctx.fillStyle = '#ffcccc';
|
if(sx < -20 || sx > mapCanvas.width + 20 || sy < -20 || sy > mapCanvas.height + 20) continue;
|
||||||
ctx.fillText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
|
|
||||||
|
let isWarn = sign.type.includes('Warning');
|
||||||
|
let isBegin = sign.type.includes('Begin');
|
||||||
|
let signColor = isWarn ? '#f59e0b' : (isBegin ? '#e63946' : '#10b981');
|
||||||
|
let signSymbol = isWarn ? '⚠' : (isBegin ? '⛔' : '✓');
|
||||||
|
|
||||||
|
let r = 8 * pixelRatio;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(sx, sy, r, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = signColor;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = 1.2 * pixelRatio;
|
||||||
|
ctx.strokeStyle = '#ffffff';
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.fillStyle = isWarn ? '#000000' : '#ffffff';
|
||||||
|
ctx.font = `bold ${8 * pixelRatio}px "Noto Sans"`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(signSymbol, sx, sy);
|
||||||
|
|
||||||
|
if(scale >= 0.08){
|
||||||
|
ctx.font = `${9 * pixelRatio}px "Noto Sans"`;
|
||||||
|
ctx.strokeStyle = '#000000';
|
||||||
|
ctx.lineWidth = 2 * pixelRatio;
|
||||||
|
ctx.strokeText(sign.type, sx, sy - 12 * pixelRatio);
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.fillText(sign.type, sx, sy - 12 * pixelRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Interruption Markers
|
// 3. Interruption Markers
|
||||||
if(MapData.layers.catenaryInterruptions && MapData.catenaryInterruptions){
|
if(MapData.layers.catenaryInterruptions && MapData.catenaryInterruptions && scale >= 0.015){
|
||||||
for(const inter of MapData.catenaryInterruptions){
|
for(const inter of MapData.catenaryInterruptions){
|
||||||
let ix = MapData.view.convertX(inter.center.x);
|
let ix = MapData.view.convertX(inter.center.x);
|
||||||
let iy = MapData.view.convertY(inter.center.z);
|
let iy = MapData.view.convertY(inter.center.z);
|
||||||
if(ix < -50 || ix > mapCanvas.width + 50 || iy < -50 || iy > mapCanvas.height + 50) continue;
|
if(ix < -50 || ix > mapCanvas.width + 50 || iy < -50 || iy > mapCanvas.height + 50) continue;
|
||||||
|
|
||||||
let s = 10 * pixelRatio;
|
let kind = inter.kind || 'drop';
|
||||||
|
let fillColor = kind === 'warning' ? '#f59e0b' : (kind === 'raise' ? '#10b981' : '#f97316');
|
||||||
|
let textColor = kind === 'warning' ? '#000000' : '#ffffff';
|
||||||
|
let icon = kind === 'warning' ? '⚠' : (kind === 'raise' ? '⤒' : '⤓');
|
||||||
|
|
||||||
|
let s = Math.max(7 * pixelRatio, Math.min(11 * pixelRatio, 220 * scale * pixelRatio));
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(ix, iy - s);
|
ctx.moveTo(ix, iy - s);
|
||||||
ctx.lineTo(ix + s, iy);
|
ctx.lineTo(ix + s, iy);
|
||||||
ctx.lineTo(ix, iy + s);
|
ctx.lineTo(ix, iy + s);
|
||||||
ctx.lineTo(ix - s, iy);
|
ctx.lineTo(ix - s, iy);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.fillStyle = '#ff9f1c';
|
ctx.fillStyle = fillColor;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.lineWidth = 1.5 * pixelRatio;
|
ctx.lineWidth = 1.5 * pixelRatio;
|
||||||
ctx.strokeStyle = '#000000';
|
ctx.strokeStyle = '#000000';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = '#000000';
|
ctx.fillStyle = textColor;
|
||||||
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
|
ctx.font = `bold ${Math.round(s * 1.05)}px "Noto Sans"`;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText('⤓', ix, iy);
|
ctx.fillText(icon, ix, iy);
|
||||||
|
|
||||||
|
if(scale >= 0.045){
|
||||||
|
let desc = kind === 'warning' ? '⚠ Разрыв КС' : (kind === 'raise' ? '⤒ Подъем пантографа' : '⤓ Опустить пантограф');
|
||||||
|
ctx.font = `bold ${10 * pixelRatio}px "Noto Sans"`;
|
||||||
|
ctx.strokeStyle = '#000000';
|
||||||
|
ctx.lineWidth = 2.5 * pixelRatio;
|
||||||
|
ctx.strokeText(desc, ix, iy + s + 7 * pixelRatio);
|
||||||
|
ctx.fillStyle = fillColor;
|
||||||
|
ctx.fillText(desc, ix, iy + s + 7 * pixelRatio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +854,7 @@ function drawCatenaryLayer(ctx, trackWidth) {
|
|||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText('⚡', sx, sy - 1 * pixelRatio);
|
ctx.fillText('⚡', sx, sy - 1 * pixelRatio);
|
||||||
|
|
||||||
if(scale >= 0.03){
|
if(scale >= 0.025){
|
||||||
let labelText = `${sub.id} (${sub.nominal_voltage}V)`;
|
let labelText = `${sub.id} (${sub.nominal_voltage}V)`;
|
||||||
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
|
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
|
|||||||
+90
-48
@@ -565,6 +565,35 @@ export function testTooltip(cursorX, cursorY){
|
|||||||
}
|
}
|
||||||
if(finalTooltip != '') return finalTooltip;
|
if(finalTooltip != '') return finalTooltip;
|
||||||
|
|
||||||
|
// 1. Catenary Point badges (substations, neutrals, interruptions) with clamped screen radius
|
||||||
|
if(layers.catenary && layers.catenarySubstations && view.scale >= 0.012){
|
||||||
|
let subRadius = Math.min(22 / view.scale, 40);
|
||||||
|
for(const sub of catenarySubstations){
|
||||||
|
if(Math.pow(sub.x - cursorX, 2) + Math.pow(sub.z - cursorY, 2) <= subRadius * subRadius){
|
||||||
|
return sub.tooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(layers.catenary && layers.catenaryNeutrals && view.scale >= 0.012){
|
||||||
|
let neutralRadius = Math.min(30 / view.scale, 45);
|
||||||
|
for(const n of catenaryNeutrals){
|
||||||
|
if(Math.pow(n.center.x - cursorX, 2) + Math.pow(n.center.z - cursorY, 2) <= neutralRadius * neutralRadius){
|
||||||
|
return n.tooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(layers.catenary && layers.catenaryInterruptions && view.scale >= 0.018){
|
||||||
|
let interRadius = Math.min(18 / view.scale, 35);
|
||||||
|
for(const inter of catenaryInterruptions){
|
||||||
|
if(Math.pow(inter.center.x - cursorX, 2) + Math.pow(inter.center.z - cursorY, 2) <= interRadius * interRadius){
|
||||||
|
return inter.tooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Static Markers (stations, POIs, speed signs, grade arrows, yards)
|
||||||
for(const marker of markers){
|
for(const marker of markers){
|
||||||
if(!marker.visible) continue;
|
if(!marker.visible) continue;
|
||||||
if(marker.boundsCheck != null){
|
if(marker.boundsCheck != null){
|
||||||
@@ -591,38 +620,35 @@ export function testTooltip(cursorX, cursorY){
|
|||||||
}
|
}
|
||||||
if(finalTooltip != '') return finalTooltip;
|
if(finalTooltip != '') return finalTooltip;
|
||||||
|
|
||||||
if(layers.catenary && layers.catenarySubstations){
|
// 2. Rail tracks curve check
|
||||||
for(const sub of catenarySubstations){
|
let bestDistance = Infinity;
|
||||||
let radius = 24 / view.scale;
|
let trackTooltip = '';
|
||||||
if(Math.pow(sub.x - cursorX, 2) + Math.pow(sub.z - cursorY, 2) <= radius * radius){
|
let boundsPadding = 40 / view.scale;
|
||||||
return sub.tooltip;
|
for(const railTrack of railTracks){
|
||||||
|
if(cursorX >= railTrack.bounds.min.x - boundsPadding && cursorX <= railTrack.bounds.max.x + boundsPadding && cursorY >= railTrack.bounds.min.z - boundsPadding && cursorY <= railTrack.bounds.max.z + boundsPadding){
|
||||||
|
for(const curve of railTrack.curves){
|
||||||
|
if(cursorX >= curve.bounds.min.x - boundsPadding && cursorX <= curve.bounds.max.x + boundsPadding && cursorY >= curve.bounds.min.z - boundsPadding && cursorY <= curve.bounds.max.z + boundsPadding){
|
||||||
|
let test = Bezier.getNearestPoint2D(curve.start, curve.h1, curve.h2, curve.end, {x:cursorX, y:cursorY}, Math.floor(curve.length*2));
|
||||||
|
if(test.distance < bestDistance){
|
||||||
|
bestDistance = test.distance;
|
||||||
|
trackTooltip = curve.tooltip;
|
||||||
|
testPoint = test.point;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let trackHit = bestDistance <= Math.min(Math.max(0.9, 12 / view.scale), 120);
|
||||||
|
|
||||||
if(layers.catenary && layers.catenaryNeutrals){
|
// 3. Catenary wire check
|
||||||
for(const n of catenaryNeutrals){
|
let wireTooltip = '';
|
||||||
let radius = 35 / view.scale;
|
let wireTooltipCompact = '';
|
||||||
if(Math.pow(n.center.x - cursorX, 2) + Math.pow(n.center.z - cursorY, 2) <= radius * radius){
|
if(layers.catenary && (layers.catenaryWires || layers.catenaryNeutrals)){
|
||||||
return n.tooltip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(layers.catenary && layers.catenaryInterruptions){
|
|
||||||
for(const inter of catenaryInterruptions){
|
|
||||||
let radius = 24 / view.scale;
|
|
||||||
if(Math.pow(inter.center.x - cursorX, 2) + Math.pow(inter.center.z - cursorY, 2) <= radius * radius){
|
|
||||||
return inter.tooltip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(layers.catenary && layers.catenaryWires){
|
|
||||||
let bestWireDistSq = Infinity;
|
let bestWireDistSq = Infinity;
|
||||||
let wireTooltip = '';
|
|
||||||
let wireBoundsPadding = 25 / view.scale;
|
let wireBoundsPadding = 25 / view.scale;
|
||||||
for(const poly of catenaryPolylines){
|
for(const poly of catenaryPolylines){
|
||||||
|
if(poly.is_neutral && !layers.catenaryNeutrals) continue;
|
||||||
|
if(!poly.is_neutral && !layers.catenaryWires) continue;
|
||||||
let b = poly.bounds;
|
let b = poly.bounds;
|
||||||
if(cursorX >= b[0] - wireBoundsPadding && cursorX <= b[2] + wireBoundsPadding && cursorY >= b[1] - wireBoundsPadding && cursorY <= b[3] + wireBoundsPadding){
|
if(cursorX >= b[0] - wireBoundsPadding && cursorX <= b[2] + wireBoundsPadding && cursorY >= b[1] - wireBoundsPadding && cursorY <= b[3] + wireBoundsPadding){
|
||||||
let pts = poly.points;
|
let pts = poly.points;
|
||||||
@@ -631,35 +657,30 @@ export function testTooltip(cursorX, cursorY){
|
|||||||
if(d2 < bestWireDistSq){
|
if(d2 < bestWireDistSq){
|
||||||
bestWireDistSq = d2;
|
bestWireDistSq = d2;
|
||||||
wireTooltip = poly.tooltip;
|
wireTooltip = poly.tooltip;
|
||||||
|
wireTooltipCompact = poly.tooltipCompact || poly.tooltip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let maxWireDist = Math.min(Math.max(1.2, 12 / view.scale), 80);
|
let maxWireDist = Math.min(Math.max(1.2, 12 / view.scale), 50);
|
||||||
if(bestWireDistSq <= maxWireDist * maxWireDist){
|
if(bestWireDistSq > maxWireDist * maxWireDist){
|
||||||
return wireTooltip;
|
wireTooltip = '';
|
||||||
|
wireTooltipCompact = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bestDistance = Infinity;
|
// 4. Combined or individual resolution
|
||||||
let test = {};
|
if(trackHit && wireTooltipCompact){
|
||||||
let boundsPadding = 40/view.scale;
|
return `${trackTooltip}\n<hr style="margin: 4px 0;">\n${wireTooltipCompact}`;
|
||||||
for(const railTrack of railTracks){
|
}
|
||||||
if(cursorX >= railTrack.bounds.min.x - boundsPadding && cursorX <= railTrack.bounds.max.x + boundsPadding && cursorY >= railTrack.bounds.min.z - boundsPadding && cursorY <= railTrack.bounds.max.z + boundsPadding){
|
if(trackHit){
|
||||||
for(const curve of railTrack.curves){
|
return trackTooltip;
|
||||||
if(cursorX >= curve.bounds.min.x - boundsPadding && cursorX <= curve.bounds.max.x + boundsPadding && cursorY >= curve.bounds.min.z - boundsPadding && cursorY <= curve.bounds.max.z + boundsPadding){
|
}
|
||||||
test = Bezier.getNearestPoint2D(curve.start, curve.h1, curve.h2, curve.end, {x:cursorX, y:cursorY}, Math.floor(curve.length*2));
|
if(wireTooltip){
|
||||||
if(test.distance < bestDistance){
|
return wireTooltip;
|
||||||
bestDistance = test.distance;
|
|
||||||
finalTooltip = curve.tooltip;
|
|
||||||
testPoint = test.point;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestDistance <= Math.min(Math.max(0.9, 12/view.scale), 120) ? finalTooltip : '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setTrackColorMode(mode){
|
export function setTrackColorMode(mode){
|
||||||
@@ -797,9 +818,27 @@ export function addCatenaryData(data){
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(const inter of catenaryInterruptions){
|
for(const inter of catenaryInterruptions){
|
||||||
inter.tooltip = `<h1>⤓ Прерывание контактной сети</h1>` +
|
let hasDrop = (inter.signs || []).some(s => s.includes('Drop') && !s.includes('Warning'));
|
||||||
`Знаки: ${(inter.signs || []).join(', ')}\n` +
|
let hasRaise = (inter.signs || []).some(s => s.includes('Raise'));
|
||||||
`Внимание: Опустить токоприемники (Drop Pantographs)!`;
|
let hasWarn = (inter.signs || []).some(s => s.includes('Warning'));
|
||||||
|
|
||||||
|
let title = '⤓ Прерывание контактной сети';
|
||||||
|
let action = 'Опустить токоприемники (Drop Pantographs)!';
|
||||||
|
if(hasWarn && !hasDrop && !hasRaise){
|
||||||
|
title = '⚠ Предупреждение: Разрыв контактной сети';
|
||||||
|
action = 'Внимание: Подготовиться к опусканию токоприемников (Drop Warning ahead)!';
|
||||||
|
inter.kind = 'warning';
|
||||||
|
} else if(hasRaise && !hasDrop){
|
||||||
|
title = '⤒ Конец прерывания контактной сети';
|
||||||
|
action = 'Конец обесточенного участка: Поднять токоприемники (Raise Pantographs)!';
|
||||||
|
inter.kind = 'raise';
|
||||||
|
} else {
|
||||||
|
inter.kind = 'drop';
|
||||||
|
}
|
||||||
|
|
||||||
|
inter.tooltip = `<h1>${title}</h1>` +
|
||||||
|
`Действие: ${action}\n` +
|
||||||
|
`Знаки: ${(inter.signs || []).join(', ')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const p of catenaryPolylines){
|
for(const p of catenaryPolylines){
|
||||||
@@ -814,6 +853,9 @@ export function addCatenaryData(data){
|
|||||||
`Тип подвески: ${catDesc}\n` +
|
`Тип подвески: ${catDesc}\n` +
|
||||||
`Тяговая подстанция: ${p.substation}\n` +
|
`Тяговая подстанция: ${p.substation}\n` +
|
||||||
`Длина участка: ${p.length} м (${p.wire_count} звеньев)`;
|
`Длина участка: ${p.length} м (${p.wire_count} звеньев)`;
|
||||||
|
|
||||||
|
p.tooltipCompact = `<div style="font-weight:bold;color:${p.is_neutral ? '#e63946' : '#00d2ff'}">⚡ Контактная сеть: ${p.is_neutral ? 'Токораздел (0 В)' : p.voltage + ' В DC'}</div>` +
|
||||||
|
`Подвеска: ${catDesc}\nПодстанция: ${p.substation} | Длина: ${p.length} м`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCatenaryColorMode(catenaryColorMode);
|
setCatenaryColorMode(catenaryColorMode);
|
||||||
|
|||||||
@@ -675,9 +675,11 @@ function populateCatenaryKey(){
|
|||||||
const catKey = document.getElementById('legendCatenaryKey');
|
const catKey = document.getElementById('legendCatenaryKey');
|
||||||
if(!catKey) return;
|
if(!catKey) return;
|
||||||
catKey.innerHTML = `
|
catKey.innerHTML = `
|
||||||
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.2em;height:1.2em;background:#0077b6;border-radius:50%;text-align:center;line-height:1.2em;font-size:11px;color:#fff;border:1px solid #fff;">⚡</span> Traction Substation</div>
|
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.3em;height:1.3em;background:#0077b6;border-radius:50%;text-align:center;line-height:1.3em;font-size:11px;color:#fff;border:1px solid #fff;">⚡</span> Traction Substation (Тяговая подстанция)</div>
|
||||||
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.2em;height:1.2em;background:#e63946;border-radius:3px;text-align:center;line-height:1.2em;font-size:10px;color:#fff;border:1px dashed #fff;">⛔</span> Neutral Section (0V Токораздел)</div>
|
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.3em;height:1.3em;background:#e63946;border-radius:3px;text-align:center;line-height:1.3em;font-size:10px;color:#fff;border:1px dashed #fff;">⛔</span> Neutral Section (0V Токораздел)</div>
|
||||||
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.2em;height:1.2em;background:#ff9f1c;border-radius:3px;text-align:center;line-height:1.2em;font-size:11px;color:#000;border:1px solid #000;">⤓</span> Lower Pantographs Warning</div>
|
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.3em;height:1.3em;background:#f97316;border-radius:3px;text-align:center;line-height:1.3em;font-size:11px;color:#ffffff;border:1px solid #000;">⤓</span> Lower Pantographs (Опустить пантограф)</div>
|
||||||
|
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.3em;height:1.3em;background:#f59e0b;border-radius:3px;text-align:center;line-height:1.3em;font-size:11px;color:#000;border:1px solid #000;">⚠</span> Interruption Warning (Предупреждение)</div>
|
||||||
|
<div style="margin: 3px 0;"><span style="display:inline-block;width:1.3em;height:1.3em;background:#10b981;border-radius:3px;text-align:center;line-height:1.3em;font-size:11px;color:#fff;border:1px solid #fff;">⤒</span> Raise Pantographs (Поднять пантограф)</div>
|
||||||
`;
|
`;
|
||||||
MapData.updateCatenaryLegend();
|
MapData.updateCatenaryLegend();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user