Add Catenary DC electrification layer, CommunityMapLocation fixes, and CI/CD
Deploy & Release / build-and-deploy (push) Successful in 1m3s

This commit is contained in:
m4xx1m committed 2026-10-21 04:55:00 +03:00
1 parent 8252fd6ba9
commit 68d5058106
11 files changed
+660 -20

No files matched your search

+190
View File
@@ -28,6 +28,7 @@ document.addEventListener('DOMContentLoaded', async () => {
await loadShopData('shop_items.json');
await loadTrackData('trackdata_dv.json');
await loadPoiData('poi_dv.json');
await loadCatenaryData('catenary_dv.json');
MapData.sortMarkers();
Utils.generateGradeArrows();
@@ -275,6 +276,15 @@ async function loadShopData(file) {
MapData.sortShops();
}
async function loadCatenaryData(file) {
try {
let catData = await (await fetch(new Request(file))).json();
MapData.addCatenaryData(catData);
} catch(e) {
console.error('Failed to load catenary data:', e);
}
}
let railTrack = {};
let curve = {};
@@ -313,6 +323,9 @@ function redrawMap(){
mapctx.stroke();
}
}
drawCatenaryLayer(mapctx, trackWidth);
let curSprite;
let spriteSize;
let textMeasure;
@@ -619,3 +632,180 @@ function redrawDynamics(){
}
MapData.view.dynDirty = false;
}
function drawCatenaryLayer(ctx, trackWidth) {
if(!MapData.layers.catenary) return;
const pixelRatio = MapData.view.pixelRatio;
const scale = MapData.view.scale;
// 1. Contact Wires
if(MapData.layers.catenaryWires && MapData.catenaryPolylines){
let minX = MapData.view.unconvertX(0) - Config.viewCullMargin / scale;
let maxX = MapData.view.unconvertX(mapCanvas.width) + Config.viewCullMargin / scale;
let minZ = MapData.view.unconvertY(mapCanvas.height) - Config.viewCullMargin / scale;
let maxZ = MapData.view.unconvertY(0) + Config.viewCullMargin / scale;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
for(const poly of MapData.catenaryPolylines){
let b = poly.bounds;
if(b[2] < minX || b[0] > maxX || b[3] < minZ || b[1] > maxZ) continue;
let pts = poly.points;
if(!pts || pts.length < 2) continue;
ctx.beginPath();
ctx.moveTo(MapData.view.convertX(pts[0][0]), MapData.view.convertY(pts[0][1]));
for(let i = 1; i < pts.length; i++){
ctx.lineTo(MapData.view.convertX(pts[i][0]), MapData.view.convertY(pts[i][1]));
}
if(poly.is_neutral){
let w = Math.max(3.5 * pixelRatio, trackWidth * 0.7);
ctx.lineWidth = w;
ctx.setLineDash([]);
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.setLineDash([12 * pixelRatio, 12 * pixelRatio]);
ctx.strokeStyle = '#e63946';
ctx.stroke();
ctx.setLineDash([]);
} else if(poly.category === 'side_rail'){
ctx.lineWidth = Math.max(2.5 * pixelRatio, trackWidth * 0.45);
ctx.setLineDash([8 * pixelRatio, 4 * pixelRatio]);
ctx.strokeStyle = poly.color;
ctx.stroke();
ctx.setLineDash([]);
} else if(poly.category === 'quad'){
ctx.lineWidth = Math.max(4 * pixelRatio, trackWidth * 0.85);
ctx.strokeStyle = '#111111';
ctx.stroke();
ctx.lineWidth = Math.max(2.2 * pixelRatio, trackWidth * 0.55);
ctx.strokeStyle = poly.color;
ctx.stroke();
} else if(poly.category === 'dual'){
ctx.lineWidth = Math.max(3.2 * pixelRatio, trackWidth * 0.7);
ctx.strokeStyle = poly.color;
ctx.stroke();
} else if(poly.category === 'trolley'){
ctx.lineWidth = Math.max(1.8 * pixelRatio, trackWidth * 0.4);
ctx.strokeStyle = poly.color;
ctx.stroke();
} else {
ctx.lineWidth = Math.max(2.4 * pixelRatio, trackWidth * 0.55);
ctx.strokeStyle = poly.color;
ctx.stroke();
}
}
}
// 2. Neutral Section Markers
if(MapData.layers.catenaryNeutrals && MapData.catenaryNeutrals){
for(const n of MapData.catenaryNeutrals){
let nx = MapData.view.convertX(n.center.x);
let ny = MapData.view.convertY(n.center.z);
if(nx < -50 || nx > mapCanvas.width + 50 || ny < -50 || ny > mapCanvas.height + 50) continue;
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();
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${10 * pixelRatio}px "Noto Sans"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('0V', nx, ny);
if(scale >= 0.04){
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2.5 * pixelRatio;
ctx.strokeText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
ctx.fillStyle = '#ffcccc';
ctx.fillText(`⛔ ${n.id}`, nx, ny + 18 * pixelRatio);
}
}
}
// 3. Interruption Markers
if(MapData.layers.catenaryInterruptions && MapData.catenaryInterruptions){
for(const inter of MapData.catenaryInterruptions){
let ix = MapData.view.convertX(inter.center.x);
let iy = MapData.view.convertY(inter.center.z);
if(ix < -50 || ix > mapCanvas.width + 50 || iy < -50 || iy > mapCanvas.height + 50) continue;
let s = 10 * pixelRatio;
ctx.beginPath();
ctx.moveTo(ix, iy - s);
ctx.lineTo(ix + s, iy);
ctx.lineTo(ix, iy + s);
ctx.lineTo(ix - s, iy);
ctx.closePath();
ctx.fillStyle = '#ff9f1c';
ctx.fill();
ctx.lineWidth = 1.5 * pixelRatio;
ctx.strokeStyle = '#000000';
ctx.stroke();
ctx.fillStyle = '#000000';
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('⤓', ix, iy);
}
}
// 4. Traction Substations
if(MapData.layers.catenarySubstations && MapData.catenarySubstations){
for(const sub of MapData.catenarySubstations){
let sx = MapData.view.convertX(sub.x);
let sy = MapData.view.convertY(sub.z);
if(sx < -80 || sx > mapCanvas.width + 80 || sy < -80 || sy > mapCanvas.height + 80) continue;
let radius = 14 * pixelRatio;
let subColor = sub.nominal_voltage === 1500 ? '#0077b6' : (sub.nominal_voltage === 1000 ? '#e85d04' : '#2b9348');
ctx.beginPath();
ctx.arc(sx, sy, radius + 2 * pixelRatio, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fill();
ctx.beginPath();
ctx.arc(sx, sy, radius, 0, Math.PI * 2);
ctx.fillStyle = subColor;
ctx.fill();
ctx.lineWidth = 2 * pixelRatio;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.fillStyle = '#ffe600';
ctx.font = `bold ${15 * pixelRatio}px "Noto Sans"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('⚡', sx, sy - 1 * pixelRatio);
if(scale >= 0.03){
let labelText = `${sub.id} (${sub.nominal_voltage}V)`;
ctx.font = `bold ${11 * pixelRatio}px "Noto Sans"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.strokeStyle = '#000000';
ctx.lineWidth = 3 * pixelRatio;
ctx.strokeText(labelText, sx, sy + radius + 3 * pixelRatio);
ctx.fillStyle = '#ffffff';
ctx.fillText(labelText, sx, sy + radius + 3 * pixelRatio);
}
}
}
ctx.textAlign = 'start';
ctx.textBaseline = 'alphabetic';
}