diff --git a/config.js b/config.js index 6e99844..2797701 100644 --- a/config.js +++ b/config.js @@ -28,4 +28,15 @@ export const altitudeGradient = [ new Color('#235f7b'), new Color('#51c34e'), new Color('#fce51e'), -] \ No newline at end of file +] + +export const sidingUsageMeanings = { + I:'Inbound', + O:'Outbound', + L:'Loading', + P:'Locomotive Parking', + S:'Car Storage', + LP:'Passenger Transfer', + SP:'Passenger Car Storage', + D:'Service & Repair', +} \ No newline at end of file diff --git a/index.html b/index.html index 018e7a7..1dee6c5 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,10 @@ stroke-width: 6px; paint-order: stroke fill; } + .yardSignage{ + font: 200px "Noto Sans Mono"; + text-anchor: middle; + } .rail{ fill: none; } diff --git a/index.js b/index.js index 3b25c79..85ae8f5 100644 --- a/index.js +++ b/index.js @@ -78,6 +78,7 @@ let mapData = []; let trackNameCounting = {}; let poiData = []; let tracks = []; +let yardMarkers = {}; let markers = []; //let currentMapNav = {}; @@ -321,22 +322,24 @@ function fillSvg(){ switch(obj.type){ case 'bezier': drawTracks(obj); + createTrackSignage(obj); break; } } + createYardSignage(); sortTracks(); setTrackColorMode('Grade'); - drawMarkers(); + drawSignage(); } -/** Insert individual rail paths, as well as creating signage data for each path*/ +/** Insert individual rail paths into the SVG */ function drawTracks(bezierData){ if(trackNameCounting[bezierData.name] == null){ trackNameCounting[bezierData.name] = 0; }else{ trackNameCounting[bezierData.name]++; } - let yardData = /\[(#)\]|\[(Y)\][_-]\[(.*?)\][_-]\[((.*?)[_-]*(.*?)[_-]*(.*?))\]/.exec(bezierData.name); + let yardData = /\[(#)\]|\[(Y)\][_-]\[(.*?)\][_-]\[((.*?)[_-](.*?)[_-](.*?)|.*)\]/.exec(bezierData.name); if(yardData){ if(yardData[1]){ // track is [#], thus in yard limits, but not a specific designated yard. bezierData.isYard = true; @@ -396,8 +399,10 @@ function drawTracks(bezierData){ ].join('\n'); bezierData.points[i].element.appendChild(title); } +} - // Add Signage +/** Create signage associated with the track section */ +function createTrackSignage(bezierData){ let trackZoneData = {}; let finalSegment = false; for(let i=0; i { + let resetTzdSpeed = () => { trackZoneData.prevSpeed = trackZoneData.speed; trackZoneData.speed = bezierData.points[i].postedSpeed; trackZoneData.speedCount = 0; @@ -490,6 +495,53 @@ function drawTracks(bezierData){ trackZoneData.speedCount++; trackZoneData.speedSectionLength += bezierData.points[curSeg].bezLength; } + + // Yard Signage + if(bezierData.yardDesignation && bezierData.yardDesignation != '--'){ + if(!yardMarkers[bezierData.yardStation]) yardMarkers[bezierData.yardStation] = {}; + let centerIndex = Math.floor((bezierData.points.length-1)/2); + let yardSignPos = bezierData.points[centerIndex].position; + if(bezierData.points.length % 2 == 0){ + yardSignPos = Bezier.evaluatePoint( + bezierData.points[centerIndex].position, + bezierData.points[centerIndex].h2, + bezierData.points[centerIndex+1].h1, + bezierData.points[centerIndex].position, + 0.5); + } + if(bezierData.yardName){ + if(!yardMarkers[bezierData.yardStation][bezierData.yardName]) yardMarkers[bezierData.yardStation][bezierData.yardName] = {accCenter:{x:0,y:0,z:0}, accCount:0}; + yardMarkers[bezierData.yardStation][bezierData.yardName].accCenter.x += yardSignPos.x; + yardMarkers[bezierData.yardStation][bezierData.yardName].accCenter.y += yardSignPos.y; + yardMarkers[bezierData.yardStation][bezierData.yardName].accCenter.z += yardSignPos.z; + yardMarkers[bezierData.yardStation][bezierData.yardName].accCount++; + } + markers.push({ + type: 'yardSiding', + name: bezierData.yardDesignation, + station: bezierData.yardStation, + yard: bezierData.yardName, + siding: bezierData.yardSidingNumber, + usage: bezierData.yardUsage, + position: yardSignPos + }); + } +} + +function createYardSignage(){ + for(let station in yardMarkers){ + for(let yard in yardMarkers[station]){ + markers.push({ + type: 'yard', + value: yard, + position: { + x: yardMarkers[station][yard].accCenter.x/yardMarkers[station][yard].accCount, + y: yardMarkers[station][yard].accCenter.y/yardMarkers[station][yard].accCount, + z: yardMarkers[station][yard].accCenter.z/yardMarkers[station][yard].accCount, + } + }); + } + } } /** Sort the tracks and put them into the svg */ @@ -594,21 +646,23 @@ export function setTrackColorMode(mode){ } /** Create the signage */ -function drawMarkers(){ +function drawSignage(){ const signage_container = document.createElementNS(svgns, 'g'); const gradeSigns = document.createElementNS(svgns, 'g'); const speedSigns = document.createElementNS(svgns, 'g'); + const yardSigns = document.createElementNS(svgns, 'g'); signage_container.setAttribute('id', 'signage_container'); gradeSigns.setAttribute('id', 'signage_grade'); speedSigns.setAttribute('id', 'signage_speed'); + yardSigns.setAttribute('id', 'signage_yard'); map_markers.appendChild(signage_container); signage_container.appendChild(gradeSigns); signage_container.appendChild(speedSigns); + signage_container.appendChild(yardSigns); for(const markerData of markers){ - //if(markerData.skip) continue; let markerElement = document.createElementNS(svgns, 'g'); let markerImg = document.createElementNS(svgns, 'use'); let markerTooltip = document.createElementNS(svgns, 'title'); @@ -616,22 +670,89 @@ function drawMarkers(){ markerElement.appendChild(markerTooltip); markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`); markerImg.classList.add('fixedScale', 'sign'); - // Grade signs - if(markerData.type == 'grade'){ - markerImg.classList.add('gradeArrow'); - markerImg.setAttribute('href', '#gradeArrow'); - markerImg.setAttribute('fill', Config.gradeColors['grade_'+markerData.gradeClass]); - const tanLen = Math.sqrt(markerData.tangent.x*markerData.tangent.x + markerData.tangent.z*markerData.tangent.z) * Math.sign(markerData.tangent.y); - const rot = Math.atan2(markerData.tangent.x/tanLen, -markerData.tangent.z/tanLen) * (180/Math.PI); - markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z}) rotate(${rot})`); - markerTooltip.innerHTML = `${Math.round(markerData.value*1000)/10}% Grade\nSection Length: ${markerData.sectionLength.toFixed(1)} meters\nCovers ${markerData.sectionCount} sections`; - gradeSigns.appendChild(markerElement); - // Speed Signs - }else if(markerData.type == 'speed'){ - markerImg.setAttribute('href', `#speedSign_${Math.round(markerData.value/10)}`); - markerTooltip.innerHTML = `${Math.round(markerData.value)} km/h\nSection Length: ${markerData.sectionLength.toFixed(1)} meters\nCovers ${markerData.sectionCount} sections`; - markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`); - speedSigns.appendChild(markerElement); + switch(markerData.type){ + case 'grade': + markerImg.classList.add('gradeArrow'); + markerImg.setAttribute('href', '#gradeArrow'); + markerImg.setAttribute('fill', Config.gradeColors['grade_'+markerData.gradeClass]); + const tanLen = Math.sqrt(markerData.tangent.x*markerData.tangent.x + markerData.tangent.z*markerData.tangent.z) * Math.sign(markerData.tangent.y); + const rot = Math.atan2(markerData.tangent.x/tanLen, -markerData.tangent.z/tanLen) * (180/Math.PI); + markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z}) rotate(${rot})`); + markerTooltip.innerHTML = `${Math.round(markerData.value*1000)/10}% Grade\nSection Length: ${markerData.sectionLength.toFixed(1)} meters\nCovers ${markerData.sectionCount} sections`; + gradeSigns.appendChild(markerElement); + break; + case 'speed': + markerImg.setAttribute('href', `#speedSign_${Math.round(markerData.value/10)}`); + markerTooltip.innerHTML = `${Math.round(markerData.value)} km/h\nSection Length: ${markerData.sectionLength.toFixed(1)} meters\nCovers ${markerData.sectionCount} sections`; + markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`); + speedSigns.appendChild(markerElement); + break; + case 'yardSiding': + markerImg.remove(); + let yardSidingSign = document.createElementNS(svgns,'g'); + yardSidingSign.classList.add('fixedScale', 'sign', 'yardSignage', 'minZoomCull_5'); + markerElement.appendChild(yardSidingSign); + + let yardSidingBoard = document.createElementNS(svgns, 'rect'); + let sidingBoardHeight = 230; + let sidingBoardWidth = (markerData.name.length+1) * 121; + yardSidingBoard.setAttribute('width', sidingBoardWidth); + yardSidingBoard.setAttribute('height', sidingBoardHeight); + yardSidingBoard.setAttribute('fill', '#fff'); + yardSidingBoard.setAttribute('stroke', '#000'); + yardSidingBoard.setAttribute('stroke-width', 30); + yardSidingBoard.setAttribute('rx', 45); + yardSidingBoard.setAttribute('transform', `translate(-${sidingBoardWidth/2}, -${sidingBoardHeight/2})`); + yardSidingBoard.appendChild(markerTooltip); + yardSidingSign.appendChild(yardSidingBoard); + + let yardSidingText = document.createElementNS(svgns, 'text'); + yardSidingText.innerHTML = markerData.name; + yardSidingText.setAttribute('transform', 'translate(0, 70)'); + yardSidingText.setAttribute('pointer-events', 'none'); + yardSidingSign.appendChild(yardSidingText); + if(markerData.yard && markerData.siding && markerData.usage){ + if(!Config.sidingUsageMeanings[markerData.usage]) console.log(markerData.station+' '+markerData.name); + markerTooltip.innerHTML = `${markerData.yard}-${markerData.siding}, ${Config.sidingUsageMeanings[markerData.usage]}`; + }else{ + markerTooltip.innerHTML = markerData.name; + } + + yardSigns.appendChild(markerElement); + break; + case 'yard': + markerImg.remove(); + let yardSign = document.createElementNS(svgns,'g'); + yardSign.classList.add('fixedScale', 'sign', 'yardSignage', 'maxZoomCull_5', 'minZoomCull_2'); + markerElement.appendChild(yardSign); + + let signText = markerData.value+' Yard'; + + let yardBoard = document.createElementNS(svgns, 'rect'); + let boardHeight = 230; + let boardWidth = (signText.length+1) * 121; + yardBoard.setAttribute('width', boardWidth); + yardBoard.setAttribute('height', boardHeight); + yardBoard.setAttribute('fill', '#fff'); + yardBoard.setAttribute('stroke', '#000'); + yardBoard.setAttribute('stroke-width', 30); + yardBoard.setAttribute('rx', 45); + yardBoard.setAttribute('transform', `translate(-${boardWidth/2}, -${boardHeight/2})`); + yardBoard.appendChild(markerTooltip); + yardSign.appendChild(yardBoard); + + let yardText = document.createElementNS(svgns, 'text'); + yardText.innerHTML = signText; + yardText.setAttribute('transform', 'translate(0, 70)'); + yardText.setAttribute('pointer-events', 'none'); + yardSign.appendChild(yardText); + markerTooltip.innerHTML = signText; + + yardSigns.appendChild(markerElement); + break; + default: + markerElement.remove(); + continue; } if(markerData.debug != null) markerTooltip.innerHTML += '\n'+markerData.debug; diff --git a/legend.js b/legend.js index 0ad15d9..53b01ec 100644 --- a/legend.js +++ b/legend.js @@ -46,6 +46,14 @@ let settingEntries = [ func: state =>{ document.getElementById('signage_grade').style.display = state ? 'unset' : 'none'; }, + }, + { + label: 'Yard Signage', + id: 'toggle_yardSigns', + state: true, + func: state =>{ + document.getElementById('signage_yard').style.display = state ? 'unset' : 'none'; + }, } ], }, diff --git a/style.css b/style.css index 55e196d..7819516 100644 --- a/style.css +++ b/style.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wdth,wght@0,62.5..100,100..900;1,62.5..100,100..900&family=Protest+Strike&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&family=Noto+Sans:ital,wdth,wght@0,62.5..100,100..900;1,62.5..100,100..900&family=Protest+Strike&display=swap'); :root{ --absScale: 1;