Implement main Points of Interest system

This commit is contained in:
Pyro Nicampt committed 2024-12-28 22:01:42 -06:00
1 parent c713ba2a77
commit 62a1072a65
5 files changed
+332 -116

No files matched your search

+212 -39
View File
@@ -58,6 +58,14 @@ map.appendChild(map_rails);
//map_markers.setAttribute('id', 'map_markers');
//map.appendChild(map_markers);
const map_markers = document.getElementById('mapMarkers');
const poiLayers = {
container: document.createElementNS(svgns, 'g'),
stations: document.createElementNS(svgns, 'g'),
service: document.createElementNS(svgns, 'g'),
features: document.createElementNS(svgns, 'g'),
landmarks: document.createElementNS(svgns, 'g')
};
let mapData = [];
let trackNameCounting = {};
@@ -68,8 +76,17 @@ let markers = [];
let currentMapNav = {};
let previousMapScale = -1;
let MapMatrix = {};
document.addEventListener('DOMContentLoaded', async () => {
let dynamicStyles = [];
for(let i=0; i<scaleCullThresholds.length; i++){
dynamicStyles.push(`.maxZoomCull_${i}{display:var(--maxZoomCull_state_${i});}`);
dynamicStyles.push(`.minZoomCull_${i}{display:var(--minZoomCull_state_${i});}`);
}
document.getElementById('zoomStyle').innerHTML = dynamicStyles.join('\n');
await loadTrackData('trackdata_dv.json');
await loadPoiData('poi_dv.json');
mapScrollSetup();
});
@@ -79,6 +96,21 @@ async function loadTrackData(file){
fillSvg();
}
async function loadPoiData(file){
poiData = await (await fetch(new Request(file))).json();
for(let layerIdx in poiLayers){
poiLayers[layerIdx].setAttribute('id', 'poi_'+layerIdx);
if(layerIdx == 'container') continue;
poiLayers.container.appendChild(poiLayers[layerIdx]);
}
map_markers.appendChild(poiLayers.container);
for(let poi of poiData){
addPoi(poi);
}
}
/** Handling for the map scrolling and zooming */
function mapScrollSetup(){
let mapNav = {x:currentMapNav.x, y:currentMapNav.y, scale:currentMapNav.scale};
@@ -150,10 +182,11 @@ function updateMapview(navData = {x:0, y:0, scale:1}){
}
}
map_rails.setAttribute('stroke-width', Utils.clamp(trackWidth.base/navData.scale, trackWidth.min, trackWidth.max));
styleRoot.style.setProperty('--signScale', Utils.clamp(signSize.base/navData.scale, signSize.min, signSize.max));
styleRoot.style.setProperty('--markerScale', 1/navData.scale);
if(dirtyScale){
for(let i=0; i<scaleCullThresholds.length; i++){
styleRoot.style.setProperty(`--cullThreshold_state_${i}`, navData.scale >= scaleCullThresholds[i].scale ? 'initial' : 'none')
styleRoot.style.setProperty(`--minZoomCull_state_${i}`, navData.scale >= scaleCullThresholds[i].scale ? 'initial' : 'none');
styleRoot.style.setProperty(`--maxZoomCull_state_${i}`, navData.scale <= scaleCullThresholds[i].scale ? 'initial' : 'none');
}
}
previousMapScale = navData.scale;
@@ -182,6 +215,17 @@ function establishDimensions(){
}
let width = maxX-minX;
let height = maxY-minY;
MapMatrix = {
x1: minX,
x2: maxX,
y1: minY,
y2: maxY,
width: width,
height: height,
fixY: yIn => {
return minY+maxY-yIn;
}
}
map.setAttribute('viewBox', `${minX-padding} ${minY-padding} ${width+2*padding} ${height+2*padding}`);
map_markers.setAttribute('viewBox', map.getAttribute('viewBox'));
updateMapview({
@@ -211,7 +255,7 @@ function fillSvg(){
}
}
sortTracks();
setTrackColorMode();
setTrackColorMode('Grade');
drawMarkers();
}
@@ -241,11 +285,11 @@ function drawTracks(bezierData){
bezierData.isPoint = true;
}
for(let i=0; i+1<bezierData.points.length; i++){
let newBezier = document.createElementNS(svgns, 'path');
const bezStart = bezierData.points[i].position;
const bezHandle1 = bezierData.points[i].h2;
const bezHandle2 = bezierData.points[i+1].h1;
const bezEnd = bezierData.points[i+1].position;
bezierData.points[i].element = document.createElementNS(svgns, 'path');
let genPath = 'M ';
genPath += `${bezStart.x} ${bezStart.z} `;
genPath += `C ${bezHandle1.x} ${bezHandle1.z} ${bezHandle2.x} ${bezHandle2.z} ${bezEnd.x} ${bezEnd.z} `;
@@ -254,20 +298,20 @@ function drawTracks(bezierData){
bezierData.points[i].bezLength = Bezier.estimateLength(bezStart, bezHandle1, bezHandle2, bezEnd, bezierLengthResolution);
bezierData.points[i].curvature = Bezier.estimateCurvature(bezStart, bezHandle1, bezHandle2, bezEnd, bezierCurvatureResolution);
bezierData.points[i].postedSpeed = Utils.radiusToSpeed(1/bezierData.points[i].curvature);
newBezier.setAttribute('d', genPath);
if(bezierData.isYard || bezierData.isTurntable) bezierData.points[i].postedSpeed = Math.min(bezierData.points[i].postedSpeed, 50);
bezierData.points[i].element.setAttribute('d', genPath);
bezierData.points[i].element.classList.add('rail');
//bezierData.points[i].element.setAttribute('stroke', Config.gradeColors[bezierData.points[i].gradeClass ? 'grade_'+bezierData.points[i].gradeClass : 'grade_flat']);
//bezierData.points[i].element.classList.add(bezierData.points[i].gradeClass != null ? 'grade_'+bezierData.points[i].gradeClass : 'grade_flat');
//newBezier.setAttribute('stroke', Utils.rgba2hex(bezierData.points[i].grade*50*0,(bezStart.y-113)/(252-113),bezierData.points[i].bezLength*0.01*0,1.0));
//newBezier.setAttribute('stroke', Utils.rgba2hex((1/bezierData.points[i].curvature) * 100,0,0,1.0));
//newBezier.setAttribute('stroke', trackCol);
//newBezier.setAttribute('data-grade', bezierData.points[i].grade);
//newBezier.setAttribute('data-length', bezierData.points[i].bezLength);
//newBezier.setAttribute('data-maxradius', 1/bezierData.points[i].curvature);
tracks.push([(bezStart.y+bezEnd.y)*0.5, newBezier]);
//bezierData.points[i].element.setAttribute('stroke', Utils.rgba2hex(bezierData.points[i].grade*50*0,(bezStart.y-113)/(252-113),bezierData.points[i].bezLength*0.01*0,1.0));
//bezierData.points[i].element.setAttribute('stroke', Utils.rgba2hex((1/bezierData.points[i].curvature) * 100,0,0,1.0));
//bezierData.points[i].element.setAttribute('stroke', trackCol);
//bezierData.points[i].element.setAttribute('data-grade', bezierData.points[i].grade);
//bezierData.points[i].element.setAttribute('data-length', bezierData.points[i].bezLength);
//bezierData.points[i].element.setAttribute('data-maxradius', 1/bezierData.points[i].curvature);
tracks.push([(bezStart.y+bezEnd.y)*0.5, bezierData.points[i].element]);
// Curve tooltip
let title = document.createElementNS(svgns, 'title');
@@ -279,7 +323,7 @@ function drawTracks(bezierData){
`Length: ${Math.round(bezierData.points[i].bezLength*10)/10} meters`,
`Altitude: ${Math.round((bezStart.y+bezEnd.y)*0.5)} meters`,
].join('\n');
newBezier.appendChild(title);
bezierData.points[i].element.appendChild(title);
}
// Add Signage
@@ -351,7 +395,7 @@ function drawTracks(bezierData){
value: trackZoneData.speed,
position: Bezier.evaluatePoint(b1, b2, b3, b4, center),
sectionLength: trackZoneData.speedSectionLength,
cullLevel: cullLevel,
cullLevel: cullLevel == 1 ? 2 : cullLevel,
tangent: Bezier.evaluateVelocity(b1, b2, b3, b4, center)
});
resetTzdSpeed();
@@ -377,18 +421,33 @@ function sortTracks(){
function setTrackColorMode(mode){
let modeFunction = null;
switch(mode){
default:
case 'Track Type':
modeFunction = (section, curve) => {
let finalColor = '#aaa';
if(curve.isYard && curve.yardDesignation) finalColor = '#4bf';
else if(curve.isYard) finalColor = '#4fb';
else if(curve.isPoint) finalColor = '#fb4';
else if(curve.isTurntable) finalColor = '#b4f';
section.element.setAttribute('stroke', finalColor);
}
break;
case 'Grade':
modeFunction = section => {
if(!section.element) return;
section.element.setAttribute('stroke', Config.gradeColors['grade_'+section.gradeClass]);
};
break;
default:
modeFunction = section => {
section.element.setAttribute('stroke', '#fff');
}
break;
}
if(modeFunction !== null){
for(let curve of mapData){
for(let section of curve.points){
modeFunction(section);
if(!section.element) continue;
modeFunction(section, curve);
}
}
}
@@ -396,43 +455,157 @@ function setTrackColorMode(mode){
/** Create the signage */
function drawMarkers(){
const signage_container = document.createElementNS(svgns, 'g');
const gradeSigns = document.createElementNS(svgns, 'g');
gradeSigns.setAttribute('id', 'grade_signs');
map_markers.appendChild(gradeSigns);
const speedSigns = document.createElementNS(svgns, 'g');
speedSigns.setAttribute('id', 'speed_signs');
map_markers.appendChild(speedSigns);
signage_container.setAttribute('id', 'signage_container');
gradeSigns.setAttribute('id', 'signage_grade');
speedSigns.setAttribute('id', 'signage_speed');
map_markers.appendChild(signage_container);
signage_container.appendChild(gradeSigns);
signage_container.appendChild(speedSigns);
for(const markerData of markers){
//if(markerData.skip) continue;
let marker = document.createElementNS(svgns, 'g');
let markerElement = document.createElementNS(svgns, 'g');
let markerImg = document.createElementNS(svgns, 'use');
let title = document.createElementNS(svgns, 'title');
marker.appendChild(markerImg);
marker.appendChild(title);
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
let markerTooltip = document.createElementNS(svgns, 'title');
markerElement.appendChild(markerImg);
markerElement.appendChild(markerTooltip);
markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
markerImg.classList.add('fixedScale');
// Grade signs
if(markerData.type == 'grade'){
markerImg.setAttribute('href', '#gradeArrow');
markerImg.classList.add('sign', 'gradeSign');
markerImg.setAttribute('fill', Config.gradeColors['grade_'+markerData.gradeClass])
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);
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z}) rotate(${rot})`);
title.innerHTML = `${Math.round(markerData.value*1000)/10}% Grade\nSection Length: ${markerData.sectionLength.toFixed(1)} meters`;
gradeSigns.appendChild(marker);
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`;
gradeSigns.appendChild(markerElement);
// Speed Signs
}else if(markerData.type == 'speed'){
markerImg.setAttribute('href', `#speedSign_${Math.round(markerData.value/10)}`);
markerImg.classList.add('sign', 'speedSign');
title.innerHTML = `${Math.round(markerData.value)} km/h\nSection Length: ${markerData.sectionLength.toFixed(1)} meters`;
/*let offset = Vector.normalize({x:markerData.tangent.z, y:0, z:-markerData.tangent.x});
marker.setAttribute('transform', `translate(${markerData.position.x + offset.x*100} ${markerData.position.z + offset.z*50})`);*/
marker.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
speedSigns.appendChild(marker);
markerTooltip.innerHTML = `${Math.round(markerData.value)} km/h\nSection Length: ${markerData.sectionLength.toFixed(1)} meters`;
markerElement.setAttribute('transform', `translate(${markerData.position.x} ${markerData.position.z})`);
speedSigns.appendChild(markerElement);
}
if(markerData.cullLevel){
marker.classList.add(`cullThreshold_${Math.min(scaleCullThresholds.length-1, markerData.cullLevel)}`);
markerElement.classList.add(`minZoomCull_${Math.min(scaleCullThresholds.length-1, markerData.cullLevel)}`);
}
}
}
function addPoi(poi, level = 0){
let poiElement = document.createElementNS(svgns, 'g');
let poiDisplay = document.createElementNS(svgns, 'g');
let poiIcon = document.createElementNS(svgns, poi.type == 'station' ? 'text' : 'use');
let poiTooltip = document.createElementNS(svgns, 'title');
poiElement.appendChild(poiDisplay);
poiDisplay.appendChild(poiIcon);
poiElement.appendChild(poiTooltip);
poiElement.setAttribute('transform', `translate(${poi.position.x} ${MapMatrix.fixY(poi.position.z)})`);
poiElement.classList.add('poi');
poiDisplay.classList.add('fixedScale');
if(level > 0) poiElement.classList.add('minZoomCull_2');
switch(poi.type){
case 'station':
poiDisplay.classList.remove('fixedScale');
poiElement.setAttribute('fill', poi.color ?? '#fff');
poiTooltip.innerHTML = poi.name;
poiIcon.innerHTML = poi.shorthand;
poiIcon.setAttribute('style', `font-size:400px; stroke:${poi.stroke ?? '#000'}; stroke-width:40px; paint-order:stroke; text-anchor:middle;`);
poiLayers.stations.prepend(poiElement);
break;
case 'office':
poiIcon.setAttribute('href', '#mrk_office');
if(poi.military){
poiDisplay.setAttribute('fill', '#978e47');
poiTooltip.innerHTML = 'Military Office';
}else if(poi.museum){
poiDisplay.setAttribute('fill', '#b44');
poiTooltip.innerHTML = 'Museum Office';
}else{
poiDisplay.setAttribute('fill', '#c4693e');
poiTooltip.innerHTML = 'Station Office';
}
poiLayers.service.prepend(poiElement);
break;
case 'coal':
poiIcon.setAttribute('href', '#mrk_coal');
poiDisplay.setAttribute('fill', '#202020');
poiTooltip.innerHTML = 'Coal Tower';
poiLayers.service.prepend(poiElement);
break;
case 'water':
poiIcon.setAttribute('href', '#mrk_water');
poiDisplay.setAttribute('fill', '#3fa5ff');
poiTooltip.innerHTML = 'Water Tower';
poiLayers.service.prepend(poiElement);
break;
case 'garage':
poiIcon.setAttribute('href', '#mrk_garage');
poiDisplay.setAttribute('fill', '#8b5dd7');
poiTooltip.innerHTML = poi.name ?? 'Unknown Garage';
poiLayers.service.prepend(poiElement);
poiElement.classList.add('minZoomCull_1');
break;
case 'landmark':
poiIcon.setAttribute('href', '#mrk_landmark');
poiDisplay.setAttribute('fill', '#af5757');
poiTooltip.innerHTML = poi.name ?? 'Unknown Landmark';
poiLayers.service.prepend(poiElement);
poiElement.classList.add('minZoomCull_1');
break;
case 'shop':
poiIcon.setAttribute('href', '#mrk_shop');
poiDisplay.setAttribute('fill', '#4f54e9');
poiTooltip.innerHTML = 'Shop';
poiLayers.service.prepend(poiElement);
poiElement.classList.add('minZoomCull_1');
break;
case 'service':
let serviceTypes = [];
if(poi.repair) serviceTypes.push('Repair');
if(poi.diesel) serviceTypes.push('Diesel');
if(poi.charger) serviceTypes.push('Charger');
if(serviceTypes.length == 0){
poiIcon.setAttribute('href', '#mrk_service');
poiDisplay.setAttribute('fill', '#239a96');
poiTooltip.innerHTML = 'Unknown Service';
}else{
poiIcon.setAttribute('href', '#mrk_service_'+serviceTypes[0].toLowerCase());
poiDisplay.setAttribute('fill', '#239a96');
let bumpOffset = -200*(serviceTypes.length-1);
poiIcon.setAttribute('transform', `translate(${bumpOffset} 0)`);
poiTooltip.innerHTML = serviceTypes[0];
for(let i=1; i<serviceTypes.length; i++){
let subIcon = document.createElementNS(svgns, 'use');
subIcon.setAttribute('href', '#mrk_service_'+serviceTypes[i].toLowerCase());
subIcon.setAttribute('transform', `translate(${i*400+bumpOffset} 0)`);
poiDisplay.appendChild(subIcon);
poiTooltip.innerHTML += (i == serviceTypes.length-1 ? (serviceTypes.length == 2 ? ' & ' : ', & ') : ', ')+serviceTypes[i];
}
poiTooltip.innerHTML += ' Service';
if(serviceTypes.length > 1){
let bgrect = document.createElementNS(svgns, 'rect');
bgrect.setAttribute('width', 400*(serviceTypes.length-1));
bgrect.setAttribute('height', 400);
bgrect.setAttribute('transform', `translate(${-200*(serviceTypes.length-1)} -200)`);
poiDisplay.prepend(bgrect);
}
}
poiLayers.service.prepend(poiElement);
break;
}
if(poi.children){
for(let child of poi.children){
addPoi(child, level+1);
}
}
}