Land Surveyors United Logo

Construction Layout Simulation

Project Setup

Equipment Setup

Layout Controls

Real-time Data

Horizontal Precision: ±0.003m
Vertical Precision: ±0.005m
PDOP: 2.1
Satellites: 14
N E S W `; document.getElementById('simulation-canvas').appendChild(compassElement); document.querySelector('.fa-compass').parentElement.addEventListener('click', () => { viewState.compass = !viewState.compass; compassElement.classList.toggle('hidden'); }); // Fullscreen toggle document.querySelector('.fa-maximize').parentElement.addEventListener('click', () => { if (!document.fullscreenElement) { document.getElementById('simulation-canvas').requestFullscreen(); } else { document.exitFullscreen(); } }); // Measuring tool document.querySelector('.fa-ruler').parentElement.addEventListener('click', () => { viewState.measuring = !viewState.measuring; const button = document.querySelector('.fa-ruler').parentElement; button.classList.toggle('bg-[#ff9900]'); if (viewState.measuring) { enableMeasuringMode(); } else { disableMeasuringMode(); } }); } // Measuring mode functions let measurePoints = []; let measureLine = null; let measureLabel = null; function enableMeasuringMode() { const canvas = document.getElementById('simulation-canvas'); canvas.style.cursor = 'crosshair'; canvas.addEventListener('click', handleMeasureClick); } function disableMeasuringMode() { const canvas = document.getElementById('simulation-canvas'); canvas.style.cursor = 'default'; canvas.removeEventListener('click', handleMeasureClick); clearMeasurement(); } function handleMeasureClick(event) { const canvas = document.getElementById('simulation-canvas'); const rect = canvas.getBoundingClientRect(); const x = ((event.clientX - rect.left) / canvas.clientWidth) * 2 - 1; const y = -((event.clientY - rect.top) / canvas.clientHeight) * 2 + 1; const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(new THREE.Vector2(x, y), camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { const point = intersects[0].point; measurePoints.push(point); if (measurePoints.length === 2) { displayMeasurement(); } } } function displayMeasurement() { if (measureLine) clearMeasurement(); const geometry = new THREE.BufferGeometry().setFromPoints(measurePoints); const material = new THREE.LineBasicMaterial({ color: 0xff9900 }); measureLine = new THREE.Line(geometry, material); scene.add(measureLine); const distance = measurePoints[0].distanceTo(measurePoints[1]); // Create floating label const midPoint = new THREE.Vector3().addVectors( measurePoints[0], measurePoints[1] ).multiplyScalar(0.5); const labelDiv = document.createElement('div'); labelDiv.className = 'absolute bg-gray-800 px-2 py-1 rounded text-white text-sm'; labelDiv.textContent = `${distance.toFixed(3)}m`; document.getElementById('simulation-canvas').appendChild(labelDiv); measureLabel = labelDiv; // Update label position in animation loop updateMeasurementLabel(); measurePoints = []; } function updateMeasurementLabel() { if (measureLabel && measureLine) { const midPoint = new THREE.Vector3().addVectors( measureLine.geometry.attributes.position.array[0], measureLine.geometry.attributes.position.array[3] ).multiplyScalar(0.5); const screenPosition = midPoint.clone().project(camera); const x = (screenPosition.x * 0.5 + 0.5) * canvas.clientWidth; const y = (-screenPosition.y * 0.5 + 0.5) * canvas.clientHeight; measureLabel.style.left = `${x}px`; measureLabel.style.top = `${y}px`; } } function clearMeasurement() { if (measureLine) { scene.remove(measureLine); measureLine = null; } if (measureLabel) { measureLabel.remove(); measureLabel = null; } } // Animation loop function animate() { requestAnimationFrame(animate); if (measureLabel) updateMeasurementLabel(); renderer.render(scene, camera); } // Initialize controls initializeViewControls(); animate(); // Helper functions function toggleHelpPanel() { document.getElementById('helpPanel').classList.toggle('hidden'); } function validateCoordinates(northing, easting, elevation) { return !isNaN(northing) && !isNaN(easting) && !isNaN(elevation); } function handleInstrumentInitialization() { const height = document.querySelector('input[placeholder="Height (m)"]').value; const temp = document.querySelector('input[placeholder="Temperature (°C)"]').value; if (!height || !temp) { alert('Please enter instrument height and temperature'); return; } projectState.instrument.height = parseFloat(height); projectState.instrument.temperature = parseFloat(temp); projectState.instrument.initialized = true; // Update UI to show initialized state const initButton = document.querySelector('button:has(.fa-circle-check)'); initButton.classList.add('bg-green-600'); initButton.innerHTML = 'Initialized'; } function handleStakePoint() { if (!projectState.instrument.initialized) { alert('Please initialize instrument first'); return; } const northing = parseFloat(document.querySelector('input[placeholder="Northing"]').value); const easting = parseFloat(document.querySelector('input[placeholder="Easting"]').value); const elevation = parseFloat(document.querySelector('input[placeholder="Elevation"]').value); if (!validateCoordinates(northing, easting, elevation)) { alert('Please enter valid coordinates'); return; } const point = addStakePoint(easting, elevation, -northing); projectState.points.push({ id: Date.now(), northing, easting, elevation, mesh: point }); // Add point label const label = addPointLabel(point.position, projectState.points.length); updateRealTimeData(); } function addPointLabel(position, number) { const div = document.createElement('div'); div.className = 'absolute bg-[#ff9900] px-2 py-1 rounded-full text-xs text-white'; div.textContent = `P${number}`; document.getElementById('simulation-canvas').appendChild(div); // Position label in 3D space const vector = position.clone(); vector.project(camera); const x = (vector.x * 0.5 + 0.5) * canvas.clientWidth; const y = (-(vector.y * 0.5) + 0.5) * canvas.clientHeight; div.style.left = `${x}px`; div.style.top = `${y}px`; return div; } function addStakePoint(x, y, z) { const geometry = new THREE.SphereGeometry(0.1); const material = new THREE.MeshBasicMaterial({ color: 0xff9900 }); const point = new THREE.Mesh(geometry, material); point.position.set(x, y, z); scene.add(point); return point; } function checkMeasurement() { if (!projectState.activePoint) { alert('Please select a point to check'); return; } const tolerance = 0.005; // 5mm tolerance const current = projectState.activePoint; const measured = { northing: parseFloat(document.querySelector('input[placeholder="Northing"]').value), easting: parseFloat(document.querySelector('input[placeholder="Easting"]').value), elevation: parseFloat(document.querySelector('input[placeholder="Elevation"]').value) }; const delta = { northing: Math.abs(current.northing - measured.northing), easting: Math.abs(current.easting - measured.easting), elevation: Math.abs(current.elevation - measured.elevation) }; const withinTolerance = Object.values(delta).every(d => d <= tolerance); // Show check results const resultDisplay = document.createElement('div'); resultDisplay.className = `fixed top-4 right-4 p-4 rounded ${withinTolerance ? 'bg-green-600' : 'bg-red-600'}`; resultDisplay.innerHTML = `

Check Results

ΔN: ${delta.northing.toFixed(4)}m

ΔE: ${delta.easting.toFixed(4)}m

ΔZ: ${delta.elevation.toFixed(4)}m

${withinTolerance ? 'Within Tolerance' : 'Exceeds Tolerance'}

`; document.body.appendChild(resultDisplay); setTimeout(() => resultDisplay.remove(), 5000); } // Event Listeners document.querySelector('button:has(.fa-circle-check)').addEventListener('click', handleInstrumentInitialization); document.querySelector('button:has(.fa-crosshairs)').addEventListener('click', handleStakePoint); document.querySelector('button:has(.fa-check-double)').addEventListener('click', checkMeasurement); // Resize handler window.addEventListener('resize', () => { camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(canvas.clientWidth, canvas.clientHeight); }); // Update display with real-time data function updateRealTimeData() { // Update precision displays const horizontalPrecision = `±${projectState.instrument.height * 0.001}m`; const verticalPrecision = `±${projectState.instrument.height * 0.0015}m`; document.querySelector('[data-precision="horizontal"]').textContent = horizontalPrecision; document.querySelector('[data-precision="vertical"]').textContent = verticalPrecision; if (projectState.instrument.pdop) { document.querySelector('[data-value="pdop"]').textContent = projectState.instrument.pdop.toFixed(1); } if (projectState.instrument.satellites) { document.querySelector('[data-value="satellites"]').textContent = projectState.instrument.satellites; } } -->