Spaces:
Sleeping
Sleeping
| <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> |