TerrainExplorer / index.html
gauthamnairy's picture
Update index.html
3972736 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Terrain Analysis Explorer</title>
<!-- Esri ArcGIS Maps SDK for JavaScript -->
<link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css">
<script src="https://js.arcgis.com/4.28/"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #ffffff;
color: #000000;
overflow: hidden;
}
.app-container {
display: flex;
height: 100vh;
width: 100vw;
}
/* Clean White Left Panel */
.left-panel {
width: 420px;
background: #ffffff;
padding: 25px;
display: flex;
flex-direction: column;
box-shadow: 2px 0 15px rgba(0, 0, 0, 0.1);
border-right: 2px solid #f0f0f0;
overflow-y: auto;
}
.app-header {
margin-bottom: 30px;
text-align: center;
}
.app-title {
font-size: 38px;
font-weight: 800;
margin-bottom: 10px;
background: linear-gradient(45deg, #dc2626, #000000);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
}
.version-badge {
display: inline-block;
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: #ffffff;
padding: 6px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
margin-left: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
}
/* Clean Input Section */
.input-section {
margin-bottom: 30px;
border: 2px solid #ffcccc;
border-radius: 12px;
padding: 20px;
background: #ffffff;
}
.input-group {
margin-bottom: 25px;
}
.input-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: #000000;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Add these visual improvements */
.control-section {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-left: 4px solid #dc3545;
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 12px;
font-weight: 600;
color: #2c3e50;
}
.section-icon {
margin-right: 8px;
color: #dc3545;
font-size: 18px;
}
/* Make the sidebar more visually appealing */
.sidebar {
background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);
border-right: 3px solid #dc3545;
}
.form-group label {
font-weight: 600;
color: #2c3e50;
margin-bottom: 8px;
display: flex;
align-items: center;
}
.btn-analyze {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
border: none;
padding: 12px 24px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
}
.location-input {
width: 100%;
padding: 15px 18px;
font-size: 14px;
background: #ffffff;
border: 2px solid #e5e5e5;
border-radius: 10px;
color: #000000;
transition: all 0.3s ease;
font-family: 'Segoe UI', sans-serif;
}
.location-input:focus {
outline: none;
border-color: #dc2626;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
background: #ffffff;
transform: translateY(-1px);
}
.location-input::placeholder {
color: #666666;
font-style: italic;
}
/* Clean Radius Control */
.radius-control {
display: flex;
align-items: center;
gap: 20px;
margin-top: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
border: 1px solid #e5e5e5;
}
.radius-slider {
flex: 1;
height: 8px;
background: #e5e5e5;
border-radius: 4px;
outline: none;
-webkit-appearance: none;
cursor: pointer;
}
.radius-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px;
height: 22px;
background: linear-gradient(135deg, #dc2626, #b91c1c);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
transition: all 0.3s ease;
}
.radius-slider::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
}
.radius-slider::-moz-range-thumb {
width: 22px;
height: 22px;
background: linear-gradient(135deg, #dc2626, #b91c1c);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
}
.radius-value {
min-width: 70px;
font-size: 14px;
color: #dc2626;
font-weight: 700;
text-align: center;
padding: 5px 10px;
background: rgba(220, 38, 38, 0.1);
border-radius: 6px;
border: 1px solid rgba(220, 38, 38, 0.2);
}
/* Modern Red Analyze Button */
.analyze-btn {
width: 100%;
padding: 16px;
margin-top: 25px;
background: linear-gradient(135deg, #dc2626, #b91c1c);
color: white;
border: none;
border-radius: 12px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(220, 38, 38, 0.3);
}
.analyze-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.analyze-btn:hover:not(:disabled)::before {
left: 100%;
}
.analyze-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(220, 38, 38, 0.4);
}
.analyze-btn:active:not(:disabled) {
transform: translateY(0px);
}
.analyze-btn:disabled {
background: linear-gradient(135deg, #9ca3af, #6b7280);
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.analyze-btn .btn-text {
transition: opacity 0.3s ease;
position: relative;
z-index: 1;
}
.analyze-btn.loading .btn-text {
opacity: 0;
}
/* Clean Loading Animation */
.loading {
display: none;
align-items: center;
justify-content: center;
gap: 15px;
color: #dc2626;
margin: 25px 0;
padding: 20px;
background: rgba(220, 38, 38, 0.05);
border-radius: 10px;
border: 2px solid #ffcccc;
}
.loading.active {
display: flex;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.spinner {
width: 28px;
height: 28px;
border: 3px solid rgba(220, 38, 38, 0.2);
border-top: 3px solid #dc2626;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 14px;
font-weight: 600;
}
/* Clean Results Section */
.results-section {
flex-grow: 1;
border: 2px solid #ffcccc;
border-radius: 12px;
padding: 20px;
background: #ffffff;
}
.results-title {
font-size: 20px;
font-weight: 700;
color: #000000;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid rgba(220, 38, 38, 0.2);
display: flex;
align-items: center;
gap: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.processing-time {
font-size: 12px;
color: #dc2626;
font-weight: 500;
margin-left: auto;
}
.result-item {
background: #ffffff;
padding: 18px;
margin-bottom: 15px;
border-radius: 12px;
border-left: 4px solid #dc2626;
transition: all 0.3s ease;
border: 2px solid #ffcccc;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.result-item:hover {
background: #f8f9fa;
transform: translateX(5px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.result-item.highlight {
border-left-color: #dc2626;
background: rgba(220, 38, 38, 0.02);
}
.result-label {
font-size: 12px;
color: #666666;
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.result-value {
font-size: 17px;
font-weight: 700;
color: #000000;
word-break: break-word;
line-height: 1.3;
}
.coordinates {
font-family: 'Courier New', monospace;
font-size: 15px;
color: #dc2626;
}
.confidence-indicator {
display: inline-block;
padding: 3px 8px;
border-radius: 6px;
font-size: 10px;
font-weight: 600;
margin-left: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.confidence-high {
background: rgba(34, 197, 94, 0.1);
color: #22c55e;
border: 1px solid rgba(34, 197, 94, 0.2);
}
.confidence-medium {
background: rgba(245, 158, 11, 0.1);
color: #f59e0b;
border: 1px solid rgba(245, 158, 11, 0.2);
}
.confidence-low {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.2);
}
/* Clean Land Cover Analysis Section */
.landcover-section {
background: rgba(220, 38, 38, 0.02);
border-radius: 12px;
padding: 20px;
margin-top: 25px;
border: 2px solid #ffcccc;
}
.landcover-title {
font-size: 16px;
font-weight: 700;
color: #dc2626;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.landcover-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 20px;
}
.stat-item {
text-align: center;
padding: 15px;
background: #ffffff;
border-radius: 10px;
border: 1px solid #e5e5e5;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.stat-item:hover {
background: #f8f9fa;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.stat-value {
font-size: 22px;
font-weight: 700;
color: #dc2626;
margin-bottom: 5px;
}
.stat-label {
font-size: 11px;
color: #666666;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.classification-breakdown {
margin-top: 20px;
}
.breakdown-title {
font-size: 13px;
color: #000000;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.classification-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e5e5e5;
transition: all 0.3s ease;
}
.classification-item:hover {
background: rgba(220, 38, 38, 0.02);
padding-left: 10px;
border-radius: 6px;
}
.classification-item:last-child {
border-bottom: none;
}
.classification-name {
font-size: 14px;
color: #000000;
font-weight: 500;
}
.classification-percentage {
font-size: 14px;
font-weight: 700;
color: #dc2626;
padding: 2px 8px;
background: rgba(220, 38, 38, 0.1);
border-radius: 4px;
}
/* Clean Status Messages */
.message {
padding: 15px 18px;
margin: 20px 0;
border-radius: 10px;
font-size: 14px;
display: none;
position: relative;
font-weight: 500;
}
.message.error {
background: rgba(239, 68, 68, 0.1);
border: 1px solid #ef4444;
color: #ef4444;
}
.message.success {
background: rgba(34, 197, 94, 0.1);
border: 1px solid #22c55e;
color: #22c55e;
}
.message.info {
background: rgba(220, 38, 38, 0.1);
border: 1px solid #dc2626;
color: #dc2626;
}
.message.show {
display: block;
animation: slideInFade 0.5s ease;
}
@keyframes slideInFade {
from {
opacity: 0;
transform: translateY(-15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Clean Examples Section */
.examples-section {
margin-top: 25px;
padding-top: 20px;
border-top: 2px solid #f0f0f0;
border: 2px solid #ffcccc;
border-radius: 12px;
padding: 20px;
background: #ffffff;
}
.examples-title {
font-size: 14px;
font-weight: 700;
color: #000000;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.example-btn {
display: inline-block;
margin: 4px 6px 4px 0;
padding: 8px 12px;
background: #ffffff;
border: 1px solid rgba(220, 38, 38, 0.2);
border-radius: 8px;
color: #dc2626;
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.example-btn:hover {
background: rgba(220, 38, 38, 0.05);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.2);
border-color: #dc2626;
}
/* Map Container */
.map-container {
flex-grow: 1;
position: relative;
background: #f8f9fa;
}
#mapView {
width: 100%;
height: 100%;
}
/* Clean Map Controls */
.map-controls {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 12px;
}
.map-control-btn {
background: #ffffff;
border: 1px solid #e5e5e5;
color: #000000;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
font-size: 12px;
font-weight: 600;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.map-control-btn:hover {
background: rgba(220, 38, 38, 0.05);
border-color: #dc2626;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.2);
}
.map-control-btn.active {
background: #dc2626;
border-color: #dc2626;
color: #ffffff;
box-shadow: 0 4px 15px rgba(220, 38, 38, 0.3);
}
/* Land Cover Button Special Styling */
.map-control-btn.landcover-active {
background: #22c55e;
border-color: #22c55e;
color: #ffffff;
box-shadow: 0 4px 15px rgba(34, 197, 94, 0.3);
}
/* Clean Sampling Radius Visualization */
.radius-info {
position: absolute;
bottom: 20px;
left: 20px;
background: #ffffff;
padding: 15px 20px;
border-radius: 10px;
font-size: 12px;
color: #000000;
border: 1px solid #e5e5e5;
display: none;
min-width: 200px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.radius-info.show {
display: block;
animation: slideUp 0.5s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.radius-info-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-weight: 500;
}
.radius-info-item:last-child {
margin-bottom: 0;
}
.radius-info-value {
color: #dc2626;
font-weight: 700;
}
/* Land Cover Legend */
.landcover-legend {
position: absolute;
bottom: 80px;
left: 20px;
background: #ffffff;
padding: 15px;
border-radius: 10px;
font-size: 11px;
color: #000000;
border: 1px solid #e5e5e5;
display: none;
max-width: 200px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.landcover-legend.show {
display: block;
animation: slideUp 0.5s ease;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 8px;
border-radius: 2px;
}
/* Terrain Type Styling */
.terrain-woodland { color: #228B22; }
.terrain-jungle { color: #006400; }
.terrain-shrubland { color: #9ACD32; }
.terrain-grassland { color: #7CFC00; }
.terrain-swamp { color: #2F4F4F; }
.terrain-tundra { color: #B0C4DE; }
.terrain-desert { color: #F4A460; }
.terrain-farmland { color: #DAA520; }
.terrain-urban { color: #696969; }
.terrain-arctic { color: #E0FFFF; }
.terrain-lake { color: #4169E1; }
.terrain-tidal { color: #20B2AA; }
.terrain-closed-forest { color: #013220; }
.terrain-open-forest { color: #228B22; }
/* Responsive Design */
@media (max-width: 1200px) {
.left-panel {
width: 380px;
}
}
@media (max-width: 1024px) {
.left-panel {
width: 350px;
}
.app-title {
font-size: 32px;
}
}
@media (max-width: 768px) {
.app-container {
flex-direction: column;
}
.left-panel {
width: 100%;
height: auto;
max-height: 60vh;
overflow-y: auto;
}
.map-container {
height: 40vh;
}
.landcover-stats {
grid-template-columns: 1fr;
}
.map-controls {
top: 10px;
right: 10px;
flex-direction: row;
gap: 8px;
}
.map-control-btn {
padding: 8px 10px;
font-size: 10px;
}
}
/* Custom Scrollbar */
.left-panel::-webkit-scrollbar {
width: 8px;
}
.left-panel::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 4px;
}
.left-panel::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #dc2626, #b91c1c);
border-radius: 4px;
}
.left-panel::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #b91c1c, #991b1b);
}
/* Icons */
.icon {
width: 16px;
height: 16px;
fill: currentColor;
flex-shrink: 0;
}
</style>
</head>
<body>
<div class="app-container">
<!-- Enhanced Left Panel -->
<div class="left-panel">
<div class="app-header">
<h1 class="app-title">
Terrain Explorer
</h1>
</div>
<!-- Enhanced Input Section -->
<div class="input-section">
<div class="input-group">
<label class="input-label" for="locationInput">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
Location Input
</label>
<input type="text" id="locationInput" class="location-input" placeholder="e.g., 40.7128, -74.0060 or New York City">
</div>
<div class="input-group">
<label class="input-label" for="radiusSlider">
<svg class="icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
<circle cx="12" cy="12" r="3" fill="currentColor"/>
</svg>
Sampling Radius
</label>
<div class="radius-control">
<input type="range" id="radiusSlider" class="radius-slider" min="50" max="1000" value="100" step="25">
<span id="radiusValue" class="radius-value">100m</span>
</div>
</div>
<button id="analyzeBtn" class="analyze-btn">
<span class="btn-text">πŸ” Analyze Terrain</span>
</button>
</div>
<!-- Enhanced Loading Indicator -->
<div id="loadingIndicator" class="loading">
<div class="spinner"></div>
<span class="loading-text">Analyzing terrain data...</span>
</div>
<!-- Status Messages -->
<div id="statusMessage" class="message"></div>
<!-- Enhanced Results Section -->
<div class="results-section">
<h2 class="results-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>
Analysis Results
<span id="processingTime" class="processing-time"></span>
</h2>
<div class="result-item">
<div class="result-label">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
Coordinates
</div>
<div id="coordinatesResult" class="result-value coordinates">
Click "Analyze Terrain" to get started
</div>
</div>
<div class="result-item">
<div class="result-label">
<svg class="icon" viewBox="0 0 24 24">
<path d="M14,6L10.25,11L13.1,14.8L11.5,16C9.81,13.75 7,10 7,10L1,18H23L14,6Z"/>
</svg>
Elevation
<span id="elevationConfidence" class="confidence-indicator"></span>
</div>
<div id="elevationResult" class="result-value">--</div>
</div>
<div class="result-item highlight">
<div class="result-label">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"/>
</svg>
Primary Terrain Type
</div>
<div id="terrainResult" class="result-value">--</div>
</div>
<div class="result-item" id="coastalIndicator" style="display: none;">
<div class="result-label">
<svg class="icon" viewBox="0 0 24 24">
<path d="M12,18.5C16.14,18.5 19.5,15.14 19.5,11C19.5,6.86 16.14,3.5 12,3.5C7.86,3.5 4.5,6.86 4.5,11C4.5,15.14 7.86,18.5 12,18.5Z"/>
</svg>
Coastal Area Detected
</div>
<div class="result-value" style="color: #22c55e;">βœ“ Confirmed</div>
</div>
<!-- Enhanced Land Cover Analysis -->
<div id="landcoverSection" class="landcover-section" style="display: none;">
<div class="landcover-title">
<svg class="icon" viewBox="0 0 24 24">
<path d="M17,8C8,10 5.9,16.17 3.82,21.34L5.71,22L6.66,19.7C7.14,19.87 7.64,20 8,20C19,20 22,3 22,3C21,5 14,5.25 9,6.25C4,7.25 2,11.5 2,13.5C2,15.5 3.75,17.25 3.75,17.25C7,8 17,8 17,8Z"/>
</svg>
Land Cover Analysis
</div>
<div class="landcover-stats">
<div class="stat-item">
<div id="confidenceValue" class="stat-value">--</div>
<div class="stat-label">Confidence</div>
</div>
<div class="stat-item">
<div id="samplesValue" class="stat-value">--</div>
<div class="stat-label">Valid Samples</div>
</div>
</div>
<div class="classification-breakdown">
<div class="breakdown-title">
Terrain Classification Breakdown
</div>
<div id="classificationList"></div>
</div>
</div>
<!-- Examples Section -->
<div class="examples-section">
<div class="examples-title">🌍 Try These Examples:</div>
<div id="exampleButtons"></div>
</div>
</div>
</div>
<!-- Map Container with Controls -->
<div class="map-container">
<div id="mapView"></div>
<!-- Enhanced Map Controls -->
<div class="map-controls">
<button id="satelliteBtn" class="map-control-btn active">πŸ“‘ Satellite</button>
<button id="terrainBtn" class="map-control-btn">πŸ”οΈ Terrain</button>
<button id="hybridBtn" class="map-control-btn">πŸ—ΊοΈ Hybrid</button>
<button id="landCoverBtn" class="map-control-btn">🌿 Land Cover</button>
</div>
<!-- Enhanced Radius Info -->
<div id="radiusInfo" class="radius-info">
<div class="radius-info-item">
<span>Sampling Radius:</span>
<span id="currentRadius" class="radius-info-value">100m</span>
</div>
<div class="radius-info-item">
<span>Sample Points:</span>
<span id="samplePoints" class="radius-info-value">25</span>
</div>
<div class="radius-info-item">
<span>Coverage Area:</span>
<span id="coverageArea" class="radius-info-value">3.14 ha</span>
</div>
</div>
<!-- Land Cover Legend -->
<div id="landcoverLegend" class="landcover-legend">
<div style="font-weight: 700; margin-bottom: 10px; color: #22c55e;">🌿 Land Cover Types</div>
<div class="legend-item">
<div class="legend-color" style="background: #4169E1;"></div>
<span>Lake</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #013220;"></div>
<span>Closed Forest</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #228B22;"></div>
<span>Woodland/Open Forest</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #006400;"></div>
<span>Jungle</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #9ACD32;"></div>
<span>Shrubland</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #7CFC00;"></div>
<span>Grassland</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #DAA520;"></div>
<span>Farmland</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #dc2626;"></div>
<span>Urban</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #F4A460;"></div>
<span>Desert</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #2F4F4F;"></div>
<span>Swamp</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #B0C4DE;"></div>
<span>Tundra</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #E0FFFF;"></div>
<span>Arctic/Mountain</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #20B2AA;"></div>
<span>Tidal Zone</span>
</div>
</div>
</div>
</div>
<script>
// Global variables
let map, view, currentMarker, radiusGraphic, landCoverLayer;
const API_BASE_URL = window.location.origin;
let currentRadius = 100;
// Enhanced example locations with diverse terrain types (limited to 4)
const exampleLocations = [
{
name: 'πŸ”οΈ Mount Everest',
coords: '27.9881, 86.9250',
description: 'Arctic/mountain terrain'
},
{
name: '🏜️ Death Valley',
coords: '36.5323, -117.0143',
description: 'Desert terrain'
},
{
name: '🌳 Amazon Rainforest',
coords: '-3.4653, -62.2159',
description: 'Dense jungle'
},
{
name: 'πŸ™οΈ Manhattan, NYC',
coords: '40.7831, -73.9712',
description: 'Urban center'
}
];
// Terrain type color mapping
const terrainColors = {
'Woodland': '#228B22',
'Jungle': '#006400',
'Shrubland': '#9ACD32',
'Grassland': '#7CFC00',
'Swamp': '#2F4F4F',
'Tundra': '#B0C4DE',
'Desert': '#F4A460',
'Farmland': '#DAA520',
'Urban': '#696969',
'Arctic/mountain': '#E0FFFF',
'Lake': '#4169E1',
'Tidal Zone': '#20B2AA',
'Closed forest': '#013220',
'Open forest': '#228B22'
};
// Initialize the map when the page loads
require([
"esri/Map",
"esri/views/MapView",
"esri/Graphic",
"esri/geometry/Point",
"esri/geometry/Circle",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleFillSymbol",
"esri/layers/ImageryTileLayer",
"esri/layers/ImageryLayer"
], function(Map, MapView, Graphic, Point, Circle, SimpleMarkerSymbol, SimpleFillSymbol, ImageryTileLayer, ImageryLayer) {
// Create satellite imagery layer
const imageryLayer = new ImageryTileLayer({
url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
title: "World Imagery"
});
// Create ESRI Land Cover Layer (FREE!)
landCoverLayer = new ImageryLayer({
url: "https://ic.imagery1.arcgis.com/arcgis/rest/services/Sentinel2_10m_LandCover/ImageServer",
title: "Land Cover",
opacity: 0.7,
visible: false // Start hidden, user can toggle
});
// Create the map with multiple layers
map = new Map({
basemap: "satellite",
layers: [imageryLayer, landCoverLayer]
});
// Create the map view
view = new MapView({
container: "mapView",
map: map,
center: [0, 20],
zoom: 2,
ui: {
components: ["zoom", "compass"]
}
});
// Set up event listeners after map is loaded
view.when(() => {
console.log("Enhanced Terrain Explorer v2.0 Pro with Land Cover loaded successfully");
setupEventListeners();
setupEnhancedMapControls();
});
// Handle map click events
view.on("click", function(event) {
const lat = event.mapPoint.latitude;
const lon = event.mapPoint.longitude;
// Update input field with clicked coordinates
document.getElementById('locationInput').value = `${lat.toFixed(6)}, ${lon.toFixed(6)}`;
// Show radius info
updateRadiusInfo();
// Automatically analyze the clicked location
analyzeLocation();
});
});
function setupEventListeners() {
const analyzeBtn = document.getElementById('analyzeBtn');
const locationInput = document.getElementById('locationInput');
const radiusSlider = document.getElementById('radiusSlider');
// Button click handler
analyzeBtn.addEventListener('click', analyzeLocation);
// Enter key handler for input field
locationInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
analyzeLocation();
}
});
// Radius slider handler
radiusSlider.addEventListener('input', function(event) {
currentRadius = parseInt(event.target.value);
document.getElementById('radiusValue').textContent = currentRadius + 'm';
updateRadiusInfo();
updateRadiusVisualization();
});
// Add example buttons
addExampleButtons();
}
function setupEnhancedMapControls() {
const satelliteBtn = document.getElementById('satelliteBtn');
const terrainBtn = document.getElementById('terrainBtn');
const hybridBtn = document.getElementById('hybridBtn');
const landCoverBtn = document.getElementById('landCoverBtn');
satelliteBtn.addEventListener('click', () => {
map.basemap = "satellite";
toggleLandCoverLayer(false);
toggleLandCoverLegend(false);
setActiveMapControl(satelliteBtn);
});
terrainBtn.addEventListener('click', () => {
map.basemap = "terrain";
toggleLandCoverLayer(false);
toggleLandCoverLegend(false);
setActiveMapControl(terrainBtn);
});
hybridBtn.addEventListener('click', () => {
map.basemap = "hybrid";
toggleLandCoverLayer(false);
toggleLandCoverLegend(false);
setActiveMapControl(hybridBtn);
});
// New land cover control
landCoverBtn.addEventListener('click', () => {
map.basemap = "dark-gray"; // Use dark base for better contrast
toggleLandCoverLayer(true);
toggleLandCoverLegend(true);
setActiveMapControl(landCoverBtn, true);
});
}
function setActiveMapControl(activeBtn, isLandCover = false) {
document.querySelectorAll('.map-control-btn').forEach(btn => {
btn.classList.remove('active', 'landcover-active');
});
if (isLandCover) {
activeBtn.classList.add('landcover-active');
} else {
activeBtn.classList.add('active');
}
}
function toggleLandCoverLayer(show) {
if (landCoverLayer) {
landCoverLayer.visible = show;
}
}
function toggleLandCoverLegend(show) {
const legend = document.getElementById('landcoverLegend');
if (show) {
legend.classList.add('show');
} else {
legend.classList.remove('show');
}
}
async function analyzeLocation() {
const locationInput = document.getElementById('locationInput');
const inputValue = locationInput.value.trim();
if (!inputValue) {
showMessage('Please enter a location or coordinates', 'error');
return;
}
// Show loading state
setLoadingState(true);
clearMessage();
try {
// Corrected API call with proper request body
const response = await fetch(`${API_BASE_URL}/api/analyze`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
input: inputValue, // βœ… Correct field name
radius_meters: currentRadius, // βœ… Correct
detailed_analysis: true // βœ… Correct
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || 'Failed to analyze location');
}
const data = await response.json();
// Update results display
updateEnhancedResults(data);
// Update map with marker and radius
updateMapVisualization(data.latitude, data.longitude);
// Show success message
const processingTime = data.processing_time || 'N/A';
showMessage(`βœ… Analysis completed successfully! Processing time: ${processingTime}s`, 'success');
} catch (error) {
console.error('Error analyzing location:', error);
showMessage(`❌ Error: ${error.message}`, 'error');
} finally {
setLoadingState(false);
}
}
function updateEnhancedResults(data) {
// Handle coordinates - backend returns latitude/longitude directly
const coordsElement = document.getElementById('coordinatesResult');
coordsElement.textContent = `${data.latitude.toFixed(6)}, ${data.longitude.toFixed(6)}`;
// Update elevation
const elevationElement = document.getElementById('elevationResult');
const elevationConfidence = document.getElementById('elevationConfidence');
if (data.elevation !== null && data.elevation !== undefined) {
elevationElement.textContent = `${data.elevation.toFixed(1)} m`;
elevationConfidence.textContent = data.elevation_confidence || 'Medium';
elevationConfidence.className = `confidence-indicator confidence-${(data.elevation_confidence || 'medium').toLowerCase()}`;
} else {
elevationElement.textContent = 'No data available';
elevationConfidence.textContent = 'No Data';
elevationConfidence.className = 'confidence-indicator confidence-low';
}
// Update terrain type
const terrainElement = document.getElementById('terrainResult');
terrainElement.textContent = data.terrain_type || 'Unknown';
// Apply terrain-specific color
if (terrainColors[data.terrain_type]) {
terrainElement.style.color = terrainColors[data.terrain_type];
}
// Update processing time
const processingTimeElement = document.getElementById('processingTime');
if (data.processing_time) {
processingTimeElement.textContent = `⚑ ${data.processing_time}s`;
}
// Handle coastal detection
const coastalIndicator = document.getElementById('coastalIndicator');
if (data.coastal_detection) {
coastalIndicator.style.display = 'block';
} else {
coastalIndicator.style.display = 'none';
}
// Update land cover analysis
updateLandCoverAnalysis(data.landcover_analysis);
}
function updateLandCoverAnalysis(analysis) {
const landcoverSection = document.getElementById('landcoverSection');
if (!analysis) {
landcoverSection.style.display = 'none';
return;
}
landcoverSection.style.display = 'block';
// Update confidence and samples
const confidence = analysis.confidence || 0;
const validSamples = analysis.valid_samples || 0;
const totalSamples = analysis.total_samples || 0;
document.getElementById('confidenceValue').textContent = `${confidence}%`;
document.getElementById('samplesValue').textContent = `${validSamples}/${totalSamples}`;
// Update classification breakdown
const classificationList = document.getElementById('classificationList');
classificationList.innerHTML = '';
if (analysis.percentages) {
// Sort classifications by percentage (descending)
const sortedClassifications = Object.entries(analysis.percentages)
.sort(([,a], [,b]) => b - a);
sortedClassifications.forEach(([classification, percentage]) => {
const item = document.createElement('div');
item.className = 'classification-item';
const name = document.createElement('span');
name.className = 'classification-name';
name.textContent = classification;
// Apply terrain-specific color
if (terrainColors[classification]) {
name.style.color = terrainColors[classification];
}
const percent = document.createElement('span');
percent.className = 'classification-percentage';
percent.textContent = `${percentage}%`;
item.appendChild(name);
item.appendChild(percent);
classificationList.appendChild(item);
});
}
}
function updateMapVisualization(latitude, longitude) {
require([
"esri/Graphic",
"esri/geometry/Point",
"esri/geometry/Circle",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleFillSymbol"
], function(Graphic, Point, Circle, SimpleMarkerSymbol, SimpleFillSymbol) {
// Remove existing graphics
if (currentMarker) {
view.graphics.remove(currentMarker);
}
if (radiusGraphic) {
view.graphics.remove(radiusGraphic);
}
// Create center point
const point = new Point({
longitude: longitude,
latitude: latitude
});
// Create enhanced marker symbol with red color
const markerSymbol = new SimpleMarkerSymbol({
color: [220, 38, 38, 0.9],
size: 14,
outline: {
color: [255, 255, 255],
width: 3
},
style: "circle"
});
// Create marker graphic
currentMarker = new Graphic({
geometry: point,
symbol: markerSymbol
});
// Create radius circle with red theme
const circle = new Circle({
center: point,
radius: currentRadius,
radiusUnit: "meters"
});
const fillSymbol = new SimpleFillSymbol({
color: [220, 38, 38, 0.15],
outline: {
color: [220, 38, 38, 0.8],
width: 2,
style: "dash"
}
});
radiusGraphic = new Graphic({
geometry: circle,
symbol: fillSymbol
});
// Add graphics to map
view.graphics.addMany([radiusGraphic, currentMarker]);
// Center map on the location with appropriate zoom
view.goTo({
center: [longitude, latitude],
zoom: Math.max(10, 16 - Math.log2(currentRadius / 100))
}, {
duration: 2000,
easing: "ease-in-out"
});
// Show radius info
updateRadiusInfo();
});
}
function updateRadiusVisualization() {
if (radiusGraphic && currentMarker) {
const center = currentMarker.geometry;
require([
"esri/geometry/Circle",
"esri/symbols/SimpleFillSymbol"
], function(Circle, SimpleFillSymbol) {
const circle = new Circle({
center: center,
radius: currentRadius,
radiusUnit: "meters"
});
const fillSymbol = new SimpleFillSymbol({
color: [220, 38, 38, 0.15],
outline: {
color: [220, 38, 38, 0.8],
width: 2,
style: "dash"
}
});
radiusGraphic.geometry = circle;
radiusGraphic.symbol = fillSymbol;
});
}
}
function updateRadiusInfo() {
const radiusInfo = document.getElementById('radiusInfo');
const currentRadiusSpan = document.getElementById('currentRadius');
const samplePointsSpan = document.getElementById('samplePoints');
const coverageAreaSpan = document.getElementById('coverageArea');
currentRadiusSpan.textContent = currentRadius + 'm';
// Calculate approximate sample points based on radius
let samplePoints;
if (currentRadius <= 100) {
samplePoints = 25;
} else if (currentRadius <= 300) {
samplePoints = 49;
} else {
samplePoints = 81;
}
samplePointsSpan.textContent = samplePoints;
// Calculate coverage area in hectares
const areaM2 = Math.PI * currentRadius * currentRadius;
const areaHa = (areaM2 / 10000).toFixed(2);
coverageAreaSpan.textContent = areaHa + ' ha';
radiusInfo.classList.add('show');
// Hide after 4 seconds
setTimeout(() => {
radiusInfo.classList.remove('show');
}, 4000);
}
function setLoadingState(isLoading) {
const loadingIndicator = document.getElementById('loadingIndicator');
const analyzeBtn = document.getElementById('analyzeBtn');
if (isLoading) {
loadingIndicator.classList.add('active');
analyzeBtn.disabled = true;
analyzeBtn.classList.add('loading');
analyzeBtn.querySelector('.btn-text').textContent = 'πŸ”„ Analyzing...';
} else {
loadingIndicator.classList.remove('active');
analyzeBtn.disabled = false;
analyzeBtn.classList.remove('loading');
analyzeBtn.querySelector('.btn-text').textContent = 'πŸ” Analyze Terrain';
}
}
function showMessage(message, type) {
const messageElement = document.getElementById('statusMessage');
messageElement.textContent = message;
messageElement.className = `message ${type} show`;
// Auto-hide success messages after 6 seconds
if (type === 'success') {
setTimeout(() => {
clearMessage();
}, 6000);
}
}
function clearMessage() {
const messageElement = document.getElementById('statusMessage');
messageElement.className = 'message';
}
function addExampleButtons() {
const exampleButtonsContainer = document.getElementById('exampleButtons');
exampleLocations.forEach(location => {
const btn = document.createElement('button');
btn.className = 'example-btn';
btn.textContent = location.name;
btn.title = location.description;
btn.addEventListener('click', () => {
document.getElementById('locationInput').value = location.coords;
analyzeLocation();
});
exampleButtonsContainer.appendChild(btn);
});
}
// Handle window resize
window.addEventListener('resize', () => {
if (view) {
view.when(() => {
view.resize();
});
}
});
// Initialize when page loads
document.addEventListener('DOMContentLoaded', () => {
console.log('🌍 Enhanced Terrain Analysis Explorer v2.0 Pro loaded successfully!');
});
</script>
</body>
</html>