Add Catenary DC electrification layer, CommunityMapLocation fixes, and CI/CD
Deploy & Release / build-and-deploy (push) Successful in 1m3s
Deploy & Release / build-and-deploy (push) Successful in 1m3s
This commit is contained in:
11 files changed
+660
-20
No files matched your search
@@ -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';
|
||||
}
|
||||
Reference in new issue
Block a user