gauthamnairy commited on
Commit
35ea519
·
verified ·
1 Parent(s): 593ca52

Upload 9 files

Browse files
frontend/area.html ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Area Calculator - GeoTools Hub</title>
7
+
8
+ <!-- Font Awesome -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
10
+
11
+ <!-- Google Fonts -->
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
15
+
16
+ <!-- Custom CSS -->
17
+ <link rel="stylesheet" href="/frontend/styles.css">
18
+ </head>
19
+ <body>
20
+ <div class="app-container">
21
+ <!-- Header -->
22
+ <header>
23
+ <div class="header-content">
24
+ <div class="title-section">
25
+ <h1 class="main-title">Area Calculator</h1>
26
+ <p class="subtitle">Calculate the area of a polygon defined by coordinates</p>
27
+ </div>
28
+ <a href="index.html" class="nav-link home-btn"><i class="fas fa-home"></i> Home</a>
29
+ </div>
30
+ </header>
31
+
32
+ <!-- Main Content -->
33
+ <main>
34
+ <div class="content-wrapper">
35
+ <div class="input-panel">
36
+ <div class="coordinates-section">
37
+ <div class="section-header">
38
+ <h2 class="section-title">Coordinates</h2>
39
+ <button id="add-coords" class="btn-action">
40
+ <i class="fas fa-plus"></i> Add Coordinate
41
+ </button>
42
+ </div>
43
+
44
+ <div id="coordinates-container">
45
+ <!-- Initial coordinate input -->
46
+ <div class="coordinate-group" data-index="0">
47
+ <div class="coord-header">
48
+ <h3>Point 1</h3>
49
+ <button class="remove-coord" title="Remove" disabled>
50
+ <i class="fas fa-times"></i>
51
+ </button>
52
+ </div>
53
+ <div class="form-row">
54
+ <div class="form-group">
55
+ <label for="latitude-0">Latitude</label>
56
+ <input type="number" id="latitude-0" class="latitude-input" step="any" placeholder="e.g. 37.7749">
57
+ </div>
58
+ <div class="form-group">
59
+ <label for="longitude-0">Longitude</label>
60
+ <input type="number" id="longitude-0" class="longitude-input" step="any" placeholder="e.g. -122.4194">
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <button id="calculate-area" class="btn-primary">
67
+ <i class="fas fa-calculator"></i> Calculate Area
68
+ </button>
69
+ </div>
70
+
71
+ <!-- Results Section -->
72
+ <div id="results-container" class="hidden">
73
+ <h2 class="section-title">Results</h2>
74
+ <div id="area-results">
75
+ <!-- Results will be populated here by JavaScript -->
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </main>
81
+
82
+ <!-- Footer -->
83
+ <footer>
84
+ <p>&copy; 2025 GeoTools Hub | <a href="index.html">Back to Tools</a></p>
85
+ </footer>
86
+ </div>
87
+
88
+ <!-- Loading Overlay -->
89
+ <div id="loading-overlay" class="hidden">
90
+ <div class="loader"></div>
91
+ </div>
92
+
93
+ <!-- Custom JS -->
94
+ <script src="/frontend/script.js"></script>
95
+ </body>
96
+ </html>
frontend/coordinate_converter.html ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Coordinate Converter - GeoTools Hub</title>
7
+
8
+ <!-- Font Awesome -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
10
+
11
+ <!-- Google Fonts -->
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
15
+
16
+ <!-- Custom CSS -->
17
+ <link rel="stylesheet" href="/frontend/styles.css">
18
+ </head>
19
+ <body>
20
+ <div class="app-container">
21
+ <!-- Header -->
22
+ <header>
23
+ <div class="header-content">
24
+ <div class="title-section">
25
+ <h1 class="main-title">Coordinate Converter</h1>
26
+ <p class="subtitle">Convert coordinates between different CRS</p>
27
+ </div>
28
+ <a href="index.html" class="nav-link home-btn"><i class="fas fa-home"></i> Home</a>
29
+ </div>
30
+ </header>
31
+
32
+ <!-- Main Content -->
33
+ <main>
34
+ <div class="content-wrapper">
35
+ <div class="input-panel">
36
+ <h2 class="section-title">Convert Coordinates</h2>
37
+ <div class="form-row">
38
+ <div class="form-group">
39
+ <label for="latitude">Latitude</label>
40
+ <input type="number" id="latitude" class="latitude-input" step="any" placeholder="e.g. 37.7749">
41
+ </div>
42
+ <div class="form-group">
43
+ <label for="longitude">Longitude</label>
44
+ <input type="number" id="longitude" class="longitude-input" step="any" placeholder="e.g. -122.4194">
45
+ </div>
46
+ </div>
47
+ <div class="form-row">
48
+ <div class="form-group">
49
+ <label for="source-crs">Source CRS</label>
50
+ <select id="source-crs" class="crs-select">
51
+ <!-- Will be populated by JavaScript -->
52
+ </select>
53
+ </div>
54
+ <div class="form-group">
55
+ <label for="target-crs">Target CRS</label>
56
+ <select id="target-crs" class="crs-select">
57
+ <!-- Will be populated by JavaScript -->
58
+ </select>
59
+ </div>
60
+ </div>
61
+ <button id="convert-coords" class="btn-primary">
62
+ <i class="fas fa-exchange-alt"></i> Convert Coordinates
63
+ </button>
64
+
65
+ <div id="conversion-result" class="hidden">
66
+ <h3>Converted Coordinates</h3>
67
+ <p>Latitude: <span id="converted-latitude">--</span></p>
68
+ <p>Longitude: <span id="converted-longitude">--</span></p>
69
+ <p>Original CRS: <span id="original-crs">--</span></p>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </main>
74
+
75
+ <!-- Footer -->
76
+ <footer>
77
+ <p>&copy; 2025 GeoTools Hub | <a href="index.html">Back to Tools</a></p>
78
+ </footer>
79
+ </div>
80
+
81
+ <!-- Custom JS -->
82
+ <script src="/frontend/script.js"></script>
83
+ </body>
84
+ </html>
frontend/distance.html ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Distance Calculator - GeoTools Hub</title>
7
+
8
+ <!-- Font Awesome -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
10
+
11
+ <!-- Google Fonts -->
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
15
+
16
+ <!-- Custom CSS -->
17
+ <link rel="stylesheet" href="/frontend/styles.css">
18
+ </head>
19
+ <body>
20
+ <div class="app-container">
21
+ <!-- Header -->
22
+ <header>
23
+ <div class="header-content">
24
+ <div class="title-section">
25
+ <h1 class="main-title">Distance Calculator</h1>
26
+ <p class="subtitle">Calculate the distance between two points on the Earth</p>
27
+ </div>
28
+ <a href="index.html" class="nav-link home-btn"><i class="fas fa-home"></i> Home</a>
29
+ </div>
30
+ </header>
31
+
32
+ <!-- Main Content -->
33
+ <main>
34
+ <div class="content-wrapper">
35
+ <div class="input-panel">
36
+ <h2 class="section-title">Input Coordinates</h2>
37
+ <div class="form-row">
38
+ <div class="form-group">
39
+ <label for="latitude1">Latitude 1</label>
40
+ <input type="number" id="latitude1" step="any" placeholder="e.g. 37.7749">
41
+ </div>
42
+ <div class="form-group">
43
+ <label for="longitude1">Longitude 1</label>
44
+ <input type="number" id="longitude1" step="any" placeholder="e.g. -122.4194">
45
+ </div>
46
+ </div>
47
+ <div class="form-row">
48
+ <div class="form-group">
49
+ <label for="latitude2">Latitude 2</label>
50
+ <input type="number" id="latitude2" step="any" placeholder="e.g. 34.0522">
51
+ </div>
52
+ <div class="form-group">
53
+ <label for="longitude2">Longitude 2</label>
54
+ <input type="number" id="longitude2" step="any" placeholder="e.g. -118.2437">
55
+ </div>
56
+ </div>
57
+ <button id="calculate-distance" class="btn-primary">
58
+ <i class="fas fa-calculator"></i> Calculate Distance
59
+ </button>
60
+ </div>
61
+
62
+ <!-- Results Section -->
63
+ <div id="results-container" class="hidden">
64
+ <h2 class="section-title">Results</h2>
65
+ <div id="distance-result">
66
+ <p>The distance between the two points is <span id="distance-value">0</span> kilometers.</p>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </main>
71
+
72
+ <!-- Footer -->
73
+ <footer>
74
+ <p>&copy; 2025 GeoTools Hub | <a href="index.html">Back to Tools</a></p>
75
+ </footer>
76
+ </div>
77
+
78
+ <!-- Custom JS -->
79
+ <script src="/frontend/script.js"></script>
80
+ </body>
81
+ </html>
frontend/index.html CHANGED
@@ -1,116 +1,129 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>GeoTools Hub - All-in-One GIS Calculation Tool</title>
7
-
8
- <!-- Font Awesome -->
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
10
-
11
- <!-- Google Fonts -->
12
- <link rel="preconnect" href="https://fonts.googleapis.com">
13
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
15
-
16
- <!-- Custom CSS -->
17
- <link rel="stylesheet" href="/frontend/landing.css">
18
- </head>
19
- <body>
20
- <div class="map-background"></div>
21
-
22
- <div class="background-text">MAP</div>
23
-
24
- <div class="hero-container">
25
- <header>
26
- <div class="logo">
27
- <i class="fas fa-globe-americas"></i>
28
- <h1>GeoTools Hub</h1>
29
- </div>
30
- </header>
31
-
32
- <main>
33
- <div class="hero-content">
34
- <h1 class="main-title">All-in-One GIS Calculation Tool</h1>
35
- <p class="subtitle">Powerful geospatial analysis tools for professionals and enthusiasts</p>
36
- <button id="open-drawer" class="btn-primary">
37
- <i class="fas fa-th-large"></i> Explore Tools
38
- </button>
39
- </div>
40
- </main>
41
- </div>
42
-
43
- <!-- App Drawer Overlay -->
44
- <div id="app-drawer" class="app-drawer">
45
- <div class="drawer-content">
46
- <div class="drawer-header">
47
- <h2>GIS Tools</h2>
48
- <button id="close-drawer" class="btn-close">
49
- <i class="fas fa-times"></i>
50
- </button>
51
- </div>
52
-
53
- <div class="tools-grid">
54
- <!-- Elevation Tool -->
55
- <a href="/frontend/elevation.html" class="tool-card">
56
- <div class="tool-icon">
57
- <i class="fas fa-mountain"></i>
58
- </div>
59
- <div class="tool-info">
60
- <h3>Ground Elevation Finder</h3>
61
- <p>Get precise elevation data for any location on Earth</p>
62
- </div>
63
- </a>
64
-
65
- <!-- Future Tools (Coming Soon) -->
66
- <div class="tool-card coming-soon">
67
- <div class="tool-icon">
68
- <i class="fas fa-route"></i>
69
- </div>
70
- <div class="tool-info">
71
- <h3>Distance Calculator</h3>
72
- <p>Coming soon</p>
73
- </div>
74
- </div>
75
-
76
- <div class="tool-card coming-soon">
77
- <div class="tool-icon">
78
- <i class="fas fa-draw-polygon"></i>
79
- </div>
80
- <div class="tool-info">
81
- <h3>Area Calculator</h3>
82
- <p>Coming soon</p>
83
- </div>
84
- </div>
85
-
86
- <div class="tool-card coming-soon">
87
- <div class="tool-icon">
88
- <i class="fas fa-map-marked-alt"></i>
89
- </div>
90
- <div class="tool-info">
91
- <h3>Coordinate Converter</h3>
92
- <p>Coming soon</p>
93
- </div>
94
- </div>
95
- </div>
96
- </div>
97
- </div>
98
-
99
- <footer>
100
- <p>&copy; 2025 GeoTools Hub | Made with <i class="fas fa-heart"></i> for GIS professionals</p>
101
- </footer>
102
-
103
- <script>
104
- // Simple script for app drawer functionality
105
- document.getElementById('open-drawer').addEventListener('click', function() {
106
- document.getElementById('app-drawer').classList.add('open');
107
- document.body.style.overflow = 'hidden'; // Prevent scrolling when drawer is open
108
- });
109
-
110
- document.getElementById('close-drawer').addEventListener('click', function() {
111
- document.getElementById('app-drawer').classList.remove('open');
112
- document.body.style.overflow = ''; // Restore scrolling
113
- });
114
- </script>
115
- </body>
116
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GeoTools Hub - All-in-One GIS Calculation Tool</title>
7
+
8
+ <!-- Font Awesome -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
10
+
11
+ <!-- Google Fonts -->
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
15
+
16
+ <!-- Custom CSS -->
17
+ <link rel="stylesheet" href="/frontend/landing.css">
18
+ </head>
19
+ <body>
20
+ <div class="map-background"></div>
21
+
22
+ <div class="background-text">MAP</div>
23
+
24
+ <div class="hero-container">
25
+ <header>
26
+ <div class="logo">
27
+ <i class="fas fa-globe-americas"></i>
28
+ <h1>GeoTools Hub</h1>
29
+ </div>
30
+ </header>
31
+
32
+ <main>
33
+ <div class="hero-content">
34
+ <h1 class="main-title">All-in-One GIS Calculation Tool</h1>
35
+ <p class="subtitle">Powerful geospatial analysis tools for professionals and enthusiasts</p>
36
+ <button id="open-drawer" class="btn-primary">
37
+ <i class="fas fa-th-large"></i> Explore Tools
38
+ </button>
39
+ </div>
40
+ </main>
41
+ </div>
42
+
43
+ <!-- App Drawer Overlay -->
44
+ <div id="app-drawer" class="app-drawer">
45
+ <div class="drawer-content">
46
+ <div class="drawer-header">
47
+ <h2>GIS Tools</h2>
48
+ <button id="close-drawer" class="btn-close">
49
+ <i class="fas fa-times"></i>
50
+ </button>
51
+ </div>
52
+
53
+ <div class="tools-grid">
54
+ <!-- Elevation Tool -->
55
+ <a href="/frontend/elevation.html" class="tool-card">
56
+ <div class="tool-icon">
57
+ <i class="fas fa-mountain"></i>
58
+ </div>
59
+ <div class="tool-info">
60
+ <h3>Ground Elevation Finder</h3>
61
+ <p>Get precise elevation data for any location on Earth</p>
62
+ </div>
63
+ </a>
64
+
65
+ <!-- Distance Calculator Tool -->
66
+ <a href="/frontend/distance.html" class="tool-card">
67
+ <div class="tool-icon">
68
+ <i class="fas fa-ruler-combined"></i>
69
+ </div>
70
+ <div class="tool-info">
71
+ <h3>Distance Calculator</h3>
72
+ <p>Calculate the distance between two points on the map</p>
73
+ </div>
74
+ </a>
75
+
76
+ <!-- Area Calculator Tool -->
77
+ <a href="/frontend/area.html" class="tool-card">
78
+ <div class="tool-icon">
79
+ <i class="fas fa-draw-polygon"></i>
80
+ </div>
81
+ <div class="tool-info">
82
+ <h3>Area Calculator</h3>
83
+ <p>Calculate the area of a polygon defined by coordinates</p>
84
+ </div>
85
+ </a>
86
+
87
+ <!-- Coordinate Converter Tool -->
88
+ <a href="/frontend/coordinate_converter.html" class="tool-card">
89
+ <div class="tool-icon">
90
+ <i class="fas fa-exchange-alt"></i>
91
+ </div>
92
+ <div class="tool-info">
93
+ <h3>Coordinate Converter</h3>
94
+ <p>Convert coordinates between different reference systems</p>
95
+ </div>
96
+ </a>
97
+
98
+ <!-- Terrain Data Extractor Tool -->
99
+ <a href="/frontend/terrain.html" class="tool-card">
100
+ <div class="tool-icon">
101
+ <i class="fas fa-mountain"></i>
102
+ </div>
103
+ <div class="tool-info">
104
+ <h3>Terrain Data Extractor</h3>
105
+ <p>Extract terrain data such as slope and aspect</p>
106
+ </div>
107
+ </a>
108
+ </div>
109
+ </div>
110
+ </div>
111
+
112
+ <footer>
113
+ <p>&copy; 2025 GeoTools Hub | Made with <i class="fas fa-heart"></i> for GIS professionals</p>
114
+ </footer>
115
+
116
+ <script>
117
+ // Simple script for app drawer functionality
118
+ document.getElementById('open-drawer').addEventListener('click', function() {
119
+ document.getElementById('app-drawer').classList.add('open');
120
+ document.body.style.overflow = 'hidden'; // Prevent scrolling when drawer is open
121
+ });
122
+
123
+ document.getElementById('close-drawer').addEventListener('click', function() {
124
+ document.getElementById('app-drawer').classList.remove('open');
125
+ document.body.style.overflow = ''; // Restore scrolling
126
+ });
127
+ </script>
128
+ </body>
129
+ </html>
frontend/landing.css CHANGED
@@ -1,432 +1,127 @@
1
- :root {
2
- /* Color palette */
3
- --primary-blue: #4776e6;
4
- --primary-purple: #8e54e9;
5
- --secondary-blue: #00c6ff;
6
- --secondary-teal: #0072ff;
7
- --dark-blue: #2a2d3e;
8
- --text-color: #333;
9
- --text-light: #fff;
10
- --background-start: #f0f4ff;
11
- --background-end: #ffffff;
12
- --card-bg: #fff;
13
- --border-color: #e1e4e8;
14
- --shadow-color: rgba(0, 0, 0, 0.1);
15
-
16
- /* Typography */
17
- --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
18
- --heading-font: 'Montserrat', var(--font-family);
19
- }
20
-
21
- * {
22
- margin: 0;
23
- padding: 0;
24
- box-sizing: border-box;
25
- }
26
-
27
- body {
28
- font-family: var(--font-family);
29
- color: var(--text-color);
30
- line-height: 1.6;
31
- min-height: 100vh;
32
- position: relative;
33
- overflow-x: hidden;
34
- background: linear-gradient(135deg, var(--background-start), var(--background-end));
35
- }
36
-
37
- /* Use the local world map image with proper opacity */
38
- body::before {
39
- content: '';
40
- position: fixed;
41
- top: 0;
42
- left: 0;
43
- width: 100%;
44
- height: 100%;
45
- background-image: url('static/images/landing_back.jpg');
46
- background-size: cover;
47
- background-position: center;
48
- background-repeat: no-repeat;
49
- opacity: 0.12; /* Low opacity to ensure text visibility */
50
- z-index: -2;
51
- pointer-events: none;
52
- }
53
-
54
- /* Add a subtle map grid pattern overlay */
55
- body::after {
56
- content: '';
57
- position: fixed;
58
- top: 0;
59
- left: 0;
60
- width: 100%;
61
- height: 100%;
62
- background-image: linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
63
- linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
64
- background-size: 50px 50px;
65
- opacity: 0.5;
66
- z-index: -1;
67
- pointer-events: none;
68
- }
69
-
70
- /* Remove the alternative map source since we're using local image */
71
- @media (max-width: 768px) {
72
- body::before {
73
- background-image: url('static/images/landing_back.jpg');
74
- background-position: center;
75
- }
76
- }
77
-
78
- .background-text {
79
- position: absolute;
80
- font-family: var(--heading-font);
81
- font-weight: 900;
82
- font-size: 30vw; /* Slightly smaller */
83
- opacity: 0.04; /* Slightly more visible */
84
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
85
- -webkit-background-clip: text;
86
- background-clip: text;
87
- -webkit-text-fill-color: transparent;
88
- top: 50%;
89
- left: 50%;
90
- transform: translate(-50%, -50%);
91
- z-index: -1;
92
- letter-spacing: -0.05em;
93
- white-space: nowrap;
94
- }
95
-
96
- .hero-container {
97
- display: flex;
98
- flex-direction: column;
99
- min-height: 100vh;
100
- padding-bottom: 80px; /* Add padding to bottom to make room for footer */
101
- }
102
-
103
- /* Header Styles */
104
- header {
105
- padding: 2rem;
106
- display: flex;
107
- justify-content: center;
108
- }
109
-
110
- .logo {
111
- display: flex;
112
- align-items: center;
113
- gap: 1rem;
114
- }
115
-
116
- .logo i {
117
- font-size: 2.5rem;
118
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
119
- -webkit-background-clip: text;
120
- background-clip: text;
121
- -webkit-text-fill-color: transparent;
122
- }
123
-
124
- .logo h1 {
125
- font-family: var(--heading-font);
126
- font-size: 2.5rem;
127
- font-weight: 800;
128
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
129
- -webkit-background-clip: text;
130
- background-clip: text;
131
- -webkit-text-fill-color: transparent;
132
- }
133
-
134
- /* Main Content Styles */
135
- main {
136
- flex: 1;
137
- display: flex;
138
- justify-content: center;
139
- align-items: center;
140
- padding: 0 2rem;
141
- }
142
-
143
- /* Add subtle floating animation to the hero content */
144
- @keyframes float {
145
- 0% { transform: translateY(0px); }
146
- 50% { transform: translateY(-10px); }
147
- 100% { transform: translateY(0px); }
148
- }
149
-
150
- .hero-content {
151
- max-width: 800px;
152
- text-align: center;
153
- animation: fadeIn 1s ease, float 6s ease-in-out infinite;
154
- position: relative;
155
- z-index: 1;
156
- }
157
-
158
- .main-title {
159
- font-family: var(--heading-font);
160
- font-size: 3.5rem;
161
- font-weight: 800;
162
- margin-bottom: 1.5rem;
163
- line-height: 1.2;
164
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
165
- -webkit-background-clip: text;
166
- background-clip: text;
167
- -webkit-text-fill-color: transparent;
168
- }
169
-
170
- .subtitle {
171
- font-size: 1.5rem;
172
- margin-bottom: 2.5rem;
173
- color: var(--dark-blue);
174
- opacity: 0.8;
175
- }
176
-
177
- .btn-primary {
178
- display: inline-flex;
179
- align-items: center;
180
- justify-content: center;
181
- gap: 0.75rem;
182
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
183
- color: var(--text-light);
184
- border: none;
185
- border-radius: 50px;
186
- padding: 1rem 2.5rem;
187
- font-size: 1.1rem;
188
- font-weight: 600;
189
- cursor: pointer;
190
- transition: all 0.3s ease;
191
- box-shadow: 0 4px 15px rgba(71, 118, 230, 0.3);
192
- }
193
-
194
- .btn-primary:hover {
195
- transform: translateY(-5px);
196
- box-shadow: 0 10px 20px rgba(71, 118, 230, 0.4);
197
- }
198
-
199
- .btn-primary:active {
200
- transform: translateY(-2px);
201
- }
202
-
203
- /* App Drawer Styles */
204
- .app-drawer {
205
- position: fixed;
206
- top: 0;
207
- right: 0;
208
- bottom: 0;
209
- width: 100%;
210
- max-width: 100%;
211
- background-color: rgba(0, 0, 0, 0.5);
212
- z-index: 1000;
213
- display: flex;
214
- justify-content: flex-end;
215
- transform: translateX(100%);
216
- transition: transform 0.4s cubic-bezier(0.4, 0.0, 0.2, 1);
217
- visibility: hidden;
218
- }
219
-
220
- .app-drawer.open {
221
- transform: translateX(0);
222
- visibility: visible;
223
- }
224
-
225
- .drawer-content {
226
- width: 90%;
227
- max-width: 1200px;
228
- height: 100%;
229
- background-color: white;
230
- padding: 2rem;
231
- overflow-y: auto;
232
- box-shadow: -5px 0 25px rgba(0, 0, 0, 0.15);
233
- animation: slideIn 0.4s ease forwards;
234
- }
235
-
236
- .drawer-header {
237
- display: flex;
238
- justify-content: space-between;
239
- align-items: center;
240
- margin-bottom: 2rem;
241
- padding-bottom: 1rem;
242
- border-bottom: 1px solid var(--border-color);
243
- }
244
-
245
- .drawer-header h2 {
246
- font-family: var(--heading-font);
247
- font-size: 2rem;
248
- font-weight: 700;
249
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
250
- -webkit-background-clip: text;
251
- background-clip: text;
252
- -webkit-text-fill-color: transparent;
253
- }
254
-
255
- .btn-close {
256
- background: none;
257
- border: none;
258
- font-size: 1.5rem;
259
- color: var(--text-color);
260
- cursor: pointer;
261
- transition: transform 0.3s ease;
262
- }
263
-
264
- .btn-close:hover {
265
- transform: rotate(90deg);
266
- }
267
-
268
- .tools-grid {
269
- display: grid;
270
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
271
- gap: 2rem;
272
- }
273
-
274
- .tool-card {
275
- text-decoration: none;
276
- color: inherit;
277
- background-color: var(--card-bg);
278
- border-radius: 16px;
279
- box-shadow: 0 10px 25px -5px var(--shadow-color);
280
- padding: 2rem;
281
- display: flex;
282
- flex-direction: column;
283
- gap: 1.5rem;
284
- transition: all 0.3s ease;
285
- position: relative;
286
- overflow: hidden;
287
- cursor: pointer;
288
- }
289
-
290
- .tool-card::before {
291
- content: '';
292
- position: absolute;
293
- top: 0;
294
- left: 0;
295
- width: 100%;
296
- height: 100%;
297
- background-image: url('https://cdn.jsdelivr.net/npm/world-map-image@1.0.2/images/world-map.png');
298
- background-size: 400%;
299
- background-position: center;
300
- opacity: 0.05;
301
- z-index: 0;
302
- transition: opacity 0.3s ease;
303
- }
304
-
305
- .tool-card:hover {
306
- transform: translateY(-10px);
307
- box-shadow: 0 20px 35px -10px var(--shadow-color);
308
- }
309
-
310
- .tool-card:hover::before {
311
- opacity: 0.08;
312
- }
313
-
314
- .tool-card-content {
315
- position: relative;
316
- z-index: 1;
317
- }
318
-
319
- .tool-icon {
320
- font-size: 3rem;
321
- height: 80px;
322
- width: 80px;
323
- display: flex;
324
- align-items: center;
325
- justify-content: center;
326
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
327
- color: white;
328
- border-radius: 50%;
329
- margin: 0 auto;
330
- }
331
-
332
- .tool-info {
333
- text-align: center;
334
- }
335
-
336
- .tool-info h3 {
337
- font-family: var(--heading-font);
338
- font-size: 1.5rem;
339
- margin-bottom: 0.5rem;
340
- font-weight: 700;
341
- }
342
-
343
- .tool-info p {
344
- color: var(--text-color);
345
- opacity: 0.8;
346
- }
347
-
348
- .coming-soon {
349
- position: relative;
350
- opacity: 0.7;
351
- cursor: not-allowed;
352
- }
353
-
354
- .coming-soon::after {
355
- content: '';
356
- position: absolute;
357
- top: 0;
358
- left: 0;
359
- right: 0;
360
- bottom: 0;
361
- background-color: rgba(255, 255, 255, 0.5);
362
- z-index: 1;
363
- border-radius: 16px;
364
- }
365
-
366
- /* Footer Styles */
367
- footer {
368
- padding: 1rem;
369
- text-align: center;
370
- color: var(--text-color);
371
- opacity: 0.8;
372
- position: absolute;
373
- bottom: 20px; /* Position 20px from bottom instead of using margin-top */
374
- left: 0;
375
- right: 0;
376
- margin-top: -3rem; /* Negative margin to bring it up */
377
- }
378
-
379
- footer i {
380
- color: #e25555;
381
- }
382
-
383
- /* Animations */
384
- @keyframes fadeIn {
385
- from { opacity: 0; transform: translateY(20px); }
386
- to { opacity: 1; transform: translateY(0); }
387
- }
388
-
389
- @keyframes slideIn {
390
- from { transform: translateX(50px); opacity: 0; }
391
- to { transform: translateX(0); opacity: 1; }
392
- }
393
-
394
- /* Responsive Styles */
395
- @media (max-width: 768px) {
396
- .main-title {
397
- font-size: 2.5rem;
398
- }
399
-
400
- .subtitle {
401
- font-size: 1.2rem;
402
- }
403
-
404
- .drawer-content {
405
- width: 100%;
406
- padding: 1.5rem;
407
- }
408
-
409
- .tools-grid {
410
- grid-template-columns: 1fr;
411
- }
412
-
413
- body::before {
414
- background-size: 200% auto;
415
- }
416
-
417
- .background-text {
418
- font-size: 40vw;
419
- }
420
- }
421
-
422
- /* Update the map-background div to use the local image */
423
- .map-background {
424
- position: fixed;
425
- width: 100%;
426
- height: 100%;
427
- top: 0;
428
- left: 0;
429
- z-index: -3;
430
- background-image: url('static/images/landing_back.jpg');
431
- opacity: 0;
432
- }
 
1
+ body {
2
+ font-family: 'Inter', sans-serif;
3
+ background-color: #f4f4f4;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ .map-background {
9
+ position: absolute;
10
+ top: 0;
11
+ left: 0;
12
+ width: 100%;
13
+ height: 100%;
14
+ background-image: url('/path/to/your/map-background.jpg');
15
+ background-size: cover;
16
+ z-index: -1;
17
+ }
18
+
19
+ .background-text {
20
+ position: absolute;
21
+ top: 50%;
22
+ left: 50%;
23
+ transform: translate(-50%, -50%);
24
+ font-size: 5rem;
25
+ color: rgba(255, 255, 255, 0.1);
26
+ text-align: center;
27
+ }
28
+
29
+ .hero-container {
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ justify-content: center;
34
+ height: 100vh;
35
+ text-align: center;
36
+ color: #333;
37
+ }
38
+
39
+ .hero-content {
40
+ max-width: 600px;
41
+ }
42
+
43
+ .main-title {
44
+ font-size: 2.5rem;
45
+ margin: 0;
46
+ }
47
+
48
+ .subtitle {
49
+ font-size: 1.2rem;
50
+ margin: 10px 0 20px;
51
+ }
52
+
53
+ .btn-primary {
54
+ background-color: #007bff;
55
+ color: white;
56
+ border: none;
57
+ padding: 10px 20px;
58
+ font-size: 1rem;
59
+ cursor: pointer;
60
+ border-radius: 5px;
61
+ transition: background-color 0.3s;
62
+ }
63
+
64
+ .btn-primary:hover {
65
+ background-color: #0056b3;
66
+ }
67
+
68
+ .app-drawer {
69
+ position: fixed;
70
+ top: 0;
71
+ right: -300px;
72
+ width: 300px;
73
+ height: 100%;
74
+ background-color: white;
75
+ box-shadow: -2px 0 5px rgba(0, 0, 0, 0.5);
76
+ transition: right 0.3s;
77
+ z-index: 1000;
78
+ }
79
+
80
+ .app-drawer.open {
81
+ right: 0;
82
+ }
83
+
84
+ .drawer-content {
85
+ padding: 20px;
86
+ }
87
+
88
+ .drawer-header {
89
+ display: flex;
90
+ justify-content: space-between;
91
+ align-items: center;
92
+ }
93
+
94
+ .tools-grid {
95
+ display: grid;
96
+ grid-template-columns: repeat(2, 1fr);
97
+ gap: 10px;
98
+ }
99
+
100
+ .tool-card {
101
+ background-color: #f8f9fa;
102
+ border: 1px solid #dee2e6;
103
+ border-radius: 5px;
104
+ padding: 15px;
105
+ text-align: center;
106
+ transition: transform 0.2s;
107
+ }
108
+
109
+ .tool-card:hover {
110
+ transform: scale(1.05);
111
+ }
112
+
113
+ .tool-icon {
114
+ font-size: 2rem;
115
+ margin-bottom: 10px;
116
+ }
117
+
118
+ .coming-soon {
119
+ opacity: 0.6;
120
+ }
121
+
122
+ footer {
123
+ text-align: center;
124
+ padding: 20px;
125
+ background-color: #343a40;
126
+ color: white;
127
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/script.js CHANGED
@@ -1,593 +1,125 @@
1
- // API Configuration
2
- const API_BASE_URL = window.location.hostname === 'localhost'
3
- ? 'http://localhost:8000/api'
4
- : `${window.location.protocol}//${window.location.host}/api`;
5
-
6
- // DOM Elements
7
- const coordinatesContainer = document.getElementById('coordinates-container');
8
- const addCoordsBtn = document.getElementById('add-coords');
9
- const calculateAllBtn = document.getElementById('calculate-all');
10
- const resultsContainer = document.getElementById('results-container');
11
- const elevationResultsDiv = document.getElementById('elevation-results');
12
- const loadingOverlay = document.getElementById('loading-overlay');
13
-
14
- // Global State
15
- let map, markers = [];
16
- const defaultMapView = [0, 0];
17
- const defaultZoom = 2;
18
- let coordinateCounter = 1;
19
- let crsList = [];
20
-
21
- // Initialize app
22
- document.addEventListener('DOMContentLoaded', async () => {
23
- try {
24
- // Check if Leaflet is loaded
25
- if (!window.L) {
26
- throw new Error('Leaflet library failed to load');
27
- }
28
-
29
- // Initialize map
30
- initializeMap();
31
-
32
- // Load CRS list
33
- crsList = await fetchCrsList();
34
-
35
- // Setup event listeners
36
- setupEventListeners();
37
-
38
- // Hide loading overlay
39
- showLoading(false);
40
- } catch (error) {
41
- console.error('Initialization error:', error);
42
- showNotification(`Initialization failed: ${error.message}`, 'error');
43
- showLoading(false);
44
- }
45
- });
46
-
47
- // Initialize map with custom controls
48
- function initializeMap() {
49
- // Create map
50
- map = L.map('map').setView(defaultMapView, defaultZoom);
51
-
52
- // Add satellite imagery
53
- L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
54
- attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
55
- }).addTo(map);
56
-
57
- // Add custom reset map button
58
- const resetControl = L.control({position: 'topright'});
59
- resetControl.onAdd = function() {
60
- const div = L.DomUtil.create('div', 'map-controls');
61
- div.innerHTML = '<button id="reset-map-btn" class="reset-map-btn" title="Reset Map"><i class="fas fa-sync-alt"></i></button>';
62
- return div;
63
- };
64
- resetControl.addTo(map);
65
-
66
- // Map click event to capture coordinates
67
- map.on('click', function(e) {
68
- const lat = e.latlng.lat.toFixed(6);
69
- const lng = e.latlng.lng.toFixed(6);
70
-
71
- // Find first empty coordinate input or use the first one
72
- const emptyInput = document.querySelector('.coordinate-group:not(.has-value)');
73
- if (emptyInput) {
74
- const latInput = emptyInput.querySelector('.latitude-input');
75
- const lngInput = emptyInput.querySelector('.longitude-input');
76
- latInput.value = lat;
77
- lngInput.value = lng;
78
- emptyInput.classList.add('has-value');
79
- } else {
80
- // If no empty inputs, add a new coordinate group
81
- addCoordinateGroup();
82
-
83
- // Fill the new group
84
- const groups = document.querySelectorAll('.coordinate-group');
85
- const newGroup = groups[groups.length - 1];
86
- const latInput = newGroup.querySelector('.latitude-input');
87
- const lngInput = newGroup.querySelector('.longitude-input');
88
- latInput.value = lat;
89
- lngInput.value = lng;
90
- newGroup.classList.add('has-value');
91
- }
92
- });
93
-
94
- // Add event listener for reset map button
95
- document.addEventListener('click', function(e) {
96
- if (e.target.id === 'reset-map-btn' || e.target.closest('#reset-map-btn')) {
97
- resetMap();
98
- }
99
- });
100
- }
101
-
102
- // Setup event listeners
103
- function setupEventListeners() {
104
- // Add coordinates button
105
- addCoordsBtn.addEventListener('click', addCoordinateGroup);
106
-
107
- // Calculate all button
108
- calculateAllBtn.addEventListener('click', calculateAllElevations);
109
-
110
- // Initial CRS dropdown
111
- const initialSelect = document.querySelector('.crs-select');
112
- populateCrsDropdown(initialSelect);
113
- }
114
-
115
- // Add new coordinate group
116
- function addCoordinateGroup() {
117
- const newGroupIndex = coordinateCounter++;
118
-
119
- const newGroup = document.createElement('div');
120
- newGroup.className = 'coordinate-group';
121
- newGroup.dataset.index = newGroupIndex;
122
-
123
- newGroup.innerHTML = `
124
- <div class="coord-header">
125
- <h3>Point ${coordinateCounter}</h3>
126
- <button class="remove-coord" title="Remove">
127
- <i class="fas fa-times"></i>
128
- </button>
129
- </div>
130
- <div class="form-row">
131
- <div class="form-group">
132
- <label for="latitude-${newGroupIndex}">Latitude</label>
133
- <input type="number" id="latitude-${newGroupIndex}" class="latitude-input" step="any" placeholder="e.g. 37.7749">
134
- </div>
135
- <div class="form-group">
136
- <label for="longitude-${newGroupIndex}">Longitude</label>
137
- <input type="number" id="longitude-${newGroupIndex}" class="longitude-input" step="any" placeholder="e.g. -122.4194">
138
- </div>
139
- </div>
140
- <div class="form-group">
141
- <label for="crs-${newGroupIndex}">Coordinate Reference System</label>
142
- <select id="crs-${newGroupIndex}" class="crs-select">
143
- <!-- Will be populated by JavaScript -->
144
- </select>
145
- </div>
146
- <div class="elevation-result hidden">
147
- <div class="result-badge">
148
- <div class="elevation-metrics">
149
- <div class="metric">
150
- <span class="value meters">0</span>
151
- <span class="unit">meters</span>
152
- </div>
153
- <div class="metric">
154
- <span class="value feet">0</span>
155
- <span class="unit">feet</span>
156
- </div>
157
- </div>
158
- </div>
159
- <div class="coord-detail">
160
- <small>WGS84: <span class="wgs84-coords">--</span></small>
161
- <small>Original: <span class="original-coords">--</span></small>
162
- </div>
163
- </div>
164
- `;
165
-
166
- coordinatesContainer.appendChild(newGroup);
167
-
168
- // Populate CRS dropdown
169
- const select = newGroup.querySelector('.crs-select');
170
- populateCrsDropdown(select);
171
-
172
- // Add event listener for remove button
173
- const removeBtn = newGroup.querySelector('.remove-coord');
174
- removeBtn.addEventListener('click', function() {
175
- removeCoordinateGroup(newGroup);
176
- });
177
-
178
- // Enable all remove buttons if we have more than one coordinate group
179
- if (document.querySelectorAll('.coordinate-group').length > 1) {
180
- document.querySelectorAll('.remove-coord').forEach(btn => {
181
- btn.disabled = false;
182
- });
183
- }
184
- }
185
-
186
- // Remove coordinate group
187
- function removeCoordinateGroup(group) {
188
- // Remove the corresponding marker
189
- const index = parseInt(group.dataset.index);
190
- if (markers[index]) {
191
- map.removeLayer(markers[index]);
192
- markers[index] = null;
193
- }
194
-
195
- // Remove the group element
196
- group.remove();
197
-
198
- // Renumber the remaining groups
199
- const groups = document.querySelectorAll('.coordinate-group');
200
- groups.forEach((g, i) => {
201
- g.querySelector('h3').textContent = `Point ${i + 1}`;
202
- });
203
-
204
- // Disable the last remove button if only one group remains
205
- if (groups.length === 1) {
206
- groups[0].querySelector('.remove-coord').disabled = true;
207
- }
208
- }
209
-
210
- // Fetch CRS list from API
211
- async function fetchCrsList() {
212
- showLoading(true);
213
- try {
214
- const response = await fetch(`${API_BASE_URL}/crs-list`);
215
- if (!response.ok) {
216
- throw new Error('Failed to fetch coordinate systems');
217
- }
218
-
219
- const data = await response.json();
220
-
221
- // Populate initial dropdown
222
- const initialSelect = document.querySelector('.crs-select');
223
- populateCrsDropdown(initialSelect);
224
-
225
- return data;
226
- } catch (error) {
227
- console.error('Error fetching CRS list:', error);
228
- showNotification('Failed to load coordinate systems', 'error');
229
- throw error;
230
- } finally {
231
- showLoading(false);
232
- }
233
- }
234
-
235
- // Populate CRS dropdown
236
- function populateCrsDropdown(selectElement) {
237
- selectElement.innerHTML = '';
238
-
239
- // Add WGS84 as default
240
- const defaultOption = document.createElement('option');
241
- defaultOption.value = 'EPSG:4326';
242
- defaultOption.textContent = 'WGS 84 (EPSG:4326)';
243
- selectElement.appendChild(defaultOption);
244
-
245
- // Add other CRS options
246
- if (crsList && crsList.length) {
247
- crsList.forEach(crs => {
248
- if (crs.code !== 'EPSG:4326') { // Skip WGS84 since we already added it
249
- const option = document.createElement('option');
250
- option.value = crs.code;
251
- option.textContent = `${crs.name} (${crs.code})`;
252
- selectElement.appendChild(option);
253
- }
254
- });
255
- }
256
- }
257
-
258
- // Calculate all elevations function with improved error handling
259
- async function calculateAllElevations() {
260
- try {
261
- // Show loading overlay
262
- showLoading(true);
263
-
264
- // Get all coordinate groups
265
- const groups = document.querySelectorAll('.coordinate-group');
266
- if (groups.length === 0) {
267
- throw new Error('No coordinates to calculate');
268
- }
269
-
270
- // Clear previous markers
271
- markers.forEach(marker => {
272
- if (marker) map.removeLayer(marker);
273
- });
274
- markers = [];
275
-
276
- // Store all results
277
- const results = [];
278
-
279
- // Flag to check if we need to update results summary
280
- let hasSuccessfulCalculations = false;
281
-
282
- console.log(`Processing ${groups.length} coordinate groups`);
283
-
284
- // Process each coordinate group
285
- for (let i = 0; i < groups.length; i++) {
286
- const group = groups[i];
287
- const latInput = group.querySelector('.latitude-input');
288
- const lngInput = group.querySelector('.longitude-input');
289
- const crsSelect = group.querySelector('.crs-select');
290
-
291
- // Skip empty inputs
292
- if (!latInput.value || !lngInput.value) {
293
- console.log(`Skipping point ${i+1}: empty coordinates`);
294
- continue;
295
- }
296
-
297
- try {
298
- const lat = parseFloat(latInput.value);
299
- const lng = parseFloat(lngInput.value);
300
- const crs = crsSelect.value;
301
-
302
- console.log(`Processing point ${i+1}: ${lat}, ${lng} (${crs})`);
303
-
304
- // Validate coordinates
305
- if (isNaN(lat) || isNaN(lng)) {
306
- throw new Error('Invalid coordinates');
307
- }
308
-
309
- // Get elevation data
310
- const elevationData = await getElevation(lat, lng, crs);
311
- console.log('Elevation API response:', elevationData);
312
-
313
- if (!elevationData || !('elevation' in elevationData)) {
314
- throw new Error('Invalid elevation data received');
315
- }
316
-
317
- // Convert elevation to feet
318
- const elevationFeet = elevationData.elevation * 3.28084;
319
-
320
- // Add marker to map
321
- addMarker(
322
- elevationData.wgs84_latitude || elevationData.latitude,
323
- elevationData.wgs84_longitude || elevationData.longitude,
324
- elevationData.elevation,
325
- elevationFeet,
326
- i
327
- );
328
-
329
- // Display result in the coordinate group
330
- displayElevationResult(group, elevationData, elevationFeet);
331
-
332
- // Add to results
333
- results.push(elevationData);
334
- hasSuccessfulCalculations = true;
335
-
336
- // Mark as having a value
337
- group.classList.add('has-value');
338
-
339
- } catch (error) {
340
- console.error(`Error calculating elevation for point ${i+1}:`, error);
341
- showNotification(`Failed to calculate elevation for Point ${i+1}: ${error.message}`, 'error');
342
-
343
- // Display error in the coordinate group
344
- displayElevationError(group);
345
- }
346
- }
347
-
348
- // Update summary if we have results
349
- if (hasSuccessfulCalculations) {
350
- // Display summary
351
- displayResultsSummary(results);
352
-
353
- // Show results container
354
- resultsContainer.classList.remove('hidden');
355
- } else {
356
- resultsContainer.classList.add('hidden');
357
- }
358
-
359
- } catch (error) {
360
- console.error('Error processing elevations:', error);
361
- showNotification(`Failed to process elevations: ${error.message}`, 'error');
362
- } finally {
363
- showLoading(false);
364
- }
365
- }
366
-
367
- // Display elevation result with improved formatting
368
- function displayElevationResult(group, data, elevationFeet) {
369
- const resultDiv = group.querySelector('.elevation-result');
370
- if (!resultDiv) return;
371
-
372
- // Get WGS84 coordinates
373
- const wgs84Lat = data.wgs84_latitude || data.latitude;
374
- const wgs84Lng = data.wgs84_longitude || data.longitude;
375
-
376
- // Build the HTML content
377
- resultDiv.innerHTML = `
378
- <div class="result-badge">
379
- <div class="elevation-metrics">
380
- <div class="metric">
381
- <span class="value meters">${data.elevation.toFixed(2)}</span>
382
- <span class="unit">meters</span>
383
- </div>
384
- <div class="metric">
385
- <span class="value feet">${elevationFeet.toFixed(2)}</span>
386
- <span class="unit">feet</span>
387
- </div>
388
- </div>
389
- </div>
390
- <div class="coord-detail">
391
- <small>WGS84: ${wgs84Lat.toFixed(6)}, ${wgs84Lng.toFixed(6)}</small><br>
392
- <small>Original: ${data.latitude.toFixed(6)}, ${data.longitude.toFixed(6)} (${data.original_crs})</small>
393
- </div>
394
- `;
395
-
396
- // Show the result
397
- resultDiv.classList.remove('hidden');
398
- }
399
-
400
- // Display error in elevation result
401
- function displayElevationError(group) {
402
- const resultDiv = group.querySelector('.elevation-result');
403
- if (!resultDiv) return;
404
-
405
- resultDiv.innerHTML = `
406
- <div class="result-badge error">
407
- <div class="elevation-metrics">
408
- <div class="metric">
409
- <span class="value">Error</span>
410
- <span class="unit">Failed to calculate</span>
411
- </div>
412
- </div>
413
- </div>
414
- `;
415
-
416
- resultDiv.classList.remove('hidden');
417
- }
418
-
419
- // Display results summary
420
- function displayResultsSummary(results) {
421
- if (!results || results.length === 0) {
422
- elevationResultsDiv.innerHTML = '<p>No valid elevation results found.</p>';
423
- return;
424
- }
425
-
426
- // Calculate statistics
427
- const elevations = results.map(r => r.elevation);
428
- const elevationsFeet = elevations.map(e => e * 3.28084);
429
-
430
- const max = Math.max(...elevations);
431
- const min = Math.min(...elevations);
432
- const avg = elevations.reduce((sum, el) => sum + el, 0) / elevations.length;
433
-
434
- const maxFt = max * 3.28084;
435
- const minFt = min * 3.28084;
436
- const avgFt = avg * 3.28084;
437
-
438
- // Create HTML content
439
- const html = `
440
- <div class="results-grid">
441
- <div class="result-card">
442
- <div class="result-label">Points</div>
443
- <div class="result-value">${results.length}</div>
444
- </div>
445
- <div class="result-card">
446
- <div class="result-label">Highest</div>
447
- <div class="result-value">${max.toFixed(2)} m</div>
448
- <div class="result-value-secondary">${maxFt.toFixed(2)} ft</div>
449
- </div>
450
- <div class="result-card">
451
- <div class="result-label">Lowest</div>
452
- <div class="result-value">${min.toFixed(2)} m</div>
453
- <div class="result-value-secondary">${minFt.toFixed(2)} ft</div>
454
- </div>
455
- <div class="result-card">
456
- <div class="result-label">Average</div>
457
- <div class="result-value">${avg.toFixed(2)} m</div>
458
- <div class="result-value-secondary">${avgFt.toFixed(2)} ft</div>
459
- </div>
460
- </div>
461
- `;
462
-
463
- elevationResultsDiv.innerHTML = html;
464
- }
465
-
466
- // Add marker to map
467
- function addMarker(lat, lng, elevationMeters, elevationFeet, index) {
468
- const marker = L.marker([lat, lng]).addTo(map);
469
-
470
- marker.bindPopup(`
471
- <div class="popup-content">
472
- <strong>Point ${index + 1}</strong><br>
473
- Lat: ${lat.toFixed(6)}<br>
474
- Lng: ${lng.toFixed(6)}<br>
475
- Elevation: ${elevationMeters.toFixed(2)} m / ${elevationFeet.toFixed(2)} ft
476
- </div>
477
- `);
478
-
479
- markers[index] = marker;
480
-
481
- // If this is the first valid marker, center map on it
482
- if (markers.filter(m => m !== null).length === 1) {
483
- map.setView([lat, lng], 10);
484
- }
485
- // If we have multiple markers, fit the map to include all markers
486
- else if (markers.filter(m => m !== null).length > 1) {
487
- const validMarkers = markers.filter(m => m !== null);
488
- const bounds = L.featureGroup(validMarkers).getBounds();
489
- map.fitBounds(bounds, { padding: [50, 50] });
490
- }
491
- }
492
-
493
- // Reset map
494
- function resetMap() {
495
- // Reset map view
496
- map.setView(defaultMapView, defaultZoom);
497
-
498
- // Clear all markers
499
- markers.forEach(marker => {
500
- if (marker) map.removeLayer(marker);
501
- });
502
- markers = [];
503
-
504
- // Clear input fields and results
505
- const groups = document.querySelectorAll('.coordinate-group');
506
- groups.forEach(group => {
507
- group.querySelector('.latitude-input').value = '';
508
- group.querySelector('.longitude-input').value = '';
509
- group.querySelector('.elevation-result').classList.add('hidden');
510
- group.classList.remove('has-value');
511
- });
512
-
513
- // Hide results container
514
- resultsContainer.classList.add('hidden');
515
- elevationResultsDiv.innerHTML = '';
516
-
517
- showNotification('Map has been reset', 'info');
518
- }
519
-
520
- // Get elevation data from API
521
- async function getElevation(latitude, longitude, crs) {
522
- try {
523
- const response = await fetch(`${API_BASE_URL}/elevation`, {
524
- method: 'POST',
525
- headers: {
526
- 'Content-Type': 'application/json'
527
- },
528
- body: JSON.stringify({
529
- latitude: latitude,
530
- longitude: longitude,
531
- crs: crs
532
- })
533
- });
534
-
535
- if (!response.ok) {
536
- const errorData = await response.json();
537
- throw new Error(errorData.detail || 'Failed to get elevation data');
538
- }
539
-
540
- return await response.json();
541
- } catch (error) {
542
- console.error('Error getting elevation:', error);
543
- throw error;
544
- }
545
- }
546
-
547
- // Function to show/hide loading overlay
548
- function showLoading(show) {
549
- if (show) {
550
- loadingOverlay.classList.remove('hidden');
551
- loadingOverlay.style.display = 'flex';
552
- } else {
553
- loadingOverlay.classList.add('hidden');
554
- loadingOverlay.style.display = 'none';
555
- }
556
- }
557
-
558
- // Function to show notification
559
- function showNotification(message, type = 'info') {
560
- // Create notification element
561
- const notification = document.createElement('div');
562
- notification.className = `notification ${type}`;
563
- notification.innerHTML = `
564
- <div class="notification-content">
565
- <i class="fas ${type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
566
- <span>${message}</span>
567
- </div>
568
- <button class="notification-close"><i class="fas fa-times"></i></button>
569
- `;
570
-
571
- // Add to document
572
- document.body.appendChild(notification);
573
-
574
- // Add click event to close button
575
- notification.querySelector('.notification-close').addEventListener('click', () => {
576
- notification.remove();
577
- });
578
-
579
- // Auto-remove after 5 seconds
580
- setTimeout(() => {
581
- notification.remove();
582
- }, 5000);
583
-
584
- // Animate in
585
- setTimeout(() => {
586
- notification.classList.add('show');
587
- }, 10);
588
- }
589
-
590
- // Hide loading on window load
591
- window.onload = function() {
592
- showLoading(false);
593
- };
 
1
+ // filepath: /gis-tools-app/gis-tools-app/frontend/script.js
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ // Initialize tool-specific functionalities
4
+ if (document.getElementById('calculate-distance')) {
5
+ setupDistanceCalculator();
6
+ }
7
+ if (document.getElementById('calculate-area')) {
8
+ setupAreaCalculator();
9
+ }
10
+ if (document.getElementById('convert-coordinates')) {
11
+ setupCoordinateConverter();
12
+ }
13
+ if (document.getElementById('extract-terrain')) {
14
+ setupTerrainDataExtractor();
15
+ }
16
+ if (document.getElementById('calculate-elevation')) {
17
+ setupElevationCalculator();
18
+ }
19
+ });
20
+
21
+ function setupDistanceCalculator() {
22
+ document.getElementById('calculate-distance').addEventListener('click', async function() {
23
+ const lat1 = parseFloat(document.getElementById('latitude-1').value);
24
+ const lon1 = parseFloat(document.getElementById('longitude-1').value);
25
+ const lat2 = parseFloat(document.getElementById('latitude-2').value);
26
+ const lon2 = parseFloat(document.getElementById('longitude-2').value);
27
+
28
+ const response = await fetch('/api/distance', {
29
+ method: 'POST',
30
+ headers: {
31
+ 'Content-Type': 'application/json'
32
+ },
33
+ body: JSON.stringify({ lat1, lon1, lat2, lon2 })
34
+ });
35
+
36
+ const data = await response.json();
37
+ document.getElementById('distance-result').innerText = `Distance: ${data.distance} km`;
38
+ });
39
+ }
40
+
41
+ function setupAreaCalculator() {
42
+ document.getElementById('calculate-area').addEventListener('click', async function() {
43
+ const coordinates = getPolygonCoordinates();
44
+
45
+ const response = await fetch('/api/area', {
46
+ method: 'POST',
47
+ headers: {
48
+ 'Content-Type': 'application/json'
49
+ },
50
+ body: JSON.stringify({ coordinates })
51
+ });
52
+
53
+ const data = await response.json();
54
+ document.getElementById('area-result').innerText = `Area: ${data.area} sq km`;
55
+ });
56
+ }
57
+
58
+ function getPolygonCoordinates() {
59
+ const coords = [];
60
+ const coordElements = document.querySelectorAll('.coordinate-input');
61
+ coordElements.forEach(input => {
62
+ const lat = parseFloat(input.querySelector('.latitude-input').value);
63
+ const lon = parseFloat(input.querySelector('.longitude-input').value);
64
+ if (!isNaN(lat) && !isNaN(lon)) {
65
+ coords.push([lat, lon]);
66
+ }
67
+ });
68
+ return coords;
69
+ }
70
+
71
+ function setupCoordinateConverter() {
72
+ document.getElementById('convert-coordinates').addEventListener('click', async function() {
73
+ const lat = parseFloat(document.getElementById('coordinate-lat').value);
74
+ const lon = parseFloat(document.getElementById('coordinate-lon').value);
75
+ const sourceCRS = document.getElementById('source-crs').value;
76
+ const targetCRS = document.getElementById('target-crs').value;
77
+
78
+ const response = await fetch('/api/coordinate-convert', {
79
+ method: 'POST',
80
+ headers: {
81
+ 'Content-Type': 'application/json'
82
+ },
83
+ body: JSON.stringify({ lat, lon, sourceCRS, targetCRS })
84
+ });
85
+
86
+ const data = await response.json();
87
+ document.getElementById('converted-coords').innerText = `Converted: ${data.lat}, ${data.lon}`;
88
+ });
89
+ }
90
+
91
+ function setupTerrainDataExtractor() {
92
+ document.getElementById('extract-terrain').addEventListener('click', async function() {
93
+ const lat = parseFloat(document.getElementById('terrain-lat').value);
94
+ const lon = parseFloat(document.getElementById('terrain-lon').value);
95
+
96
+ const response = await fetch('/api/terrain', {
97
+ method: 'POST',
98
+ headers: {
99
+ 'Content-Type': 'application/json'
100
+ },
101
+ body: JSON.stringify({ lat, lon })
102
+ });
103
+
104
+ const data = await response.json();
105
+ document.getElementById('terrain-result').innerText = `Slope: ${data.slope}, Aspect: ${data.aspect}`;
106
+ });
107
+ }
108
+
109
+ function setupElevationCalculator() {
110
+ document.getElementById('calculate-elevation').addEventListener('click', async function() {
111
+ const lat = parseFloat(document.getElementById('elevation-lat').value);
112
+ const lon = parseFloat(document.getElementById('elevation-lon').value);
113
+
114
+ const response = await fetch('/api/elevation', {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json'
118
+ },
119
+ body: JSON.stringify({ latitude: lat, longitude: lon })
120
+ });
121
+
122
+ const data = await response.json();
123
+ document.getElementById('elevation-result').innerText = `Elevation: ${data.elevation} meters`;
124
+ });
125
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/styles.css CHANGED
@@ -1,504 +1,152 @@
1
- :root {
2
- /* Color palette */
3
- --primary-blue: #4776e6;
4
- --primary-purple: #8e54e9;
5
- --light-purple: #b48ef9;
6
- --secondary-blue: #00c6ff;
7
- --secondary-teal: #0072ff;
8
- --dark-blue: #2a2d3e;
9
- --text-color: #333;
10
- --text-light: #fff;
11
- --background-color: #f5f7fa;
12
- --card-bg: #fff;
13
- --input-bg: #f5f7fa;
14
- --border-color: #e1e4e8;
15
- --shadow-color: rgba(0, 0, 0, 0.1);
16
- --success-color: #10b981;
17
-
18
- /* Typography */
19
- --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
20
- --heading-font: 'Montserrat', var(--font-family);
21
- }
22
-
23
- * {
24
- margin: 0;
25
- padding: 0;
26
- box-sizing: border-box;
27
- }
28
-
29
- body {
30
- font-family: var(--font-family);
31
- background-color: var(--background-color);
32
- color: var(--text-color);
33
- line-height: 1.6;
34
- }
35
-
36
- .app-container {
37
- display: flex;
38
- flex-direction: column;
39
- min-height: 100vh;
40
- }
41
-
42
- /* Updated Header Styles */
43
- header {
44
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
45
- color: var(--text-light);
46
- padding: 0.9rem 2rem;
47
- box-shadow: 0 4px 6px var(--shadow-color);
48
- }
49
-
50
- .header-content {
51
- max-width: 1400px;
52
- margin: 0 auto;
53
- display: flex;
54
- flex-direction: column;
55
- justify-content: center;
56
- align-items: center;
57
- text-align: center;
58
- }
59
-
60
- .title-section {
61
- text-align: center;
62
- margin-bottom: 0.25rem;
63
- }
64
-
65
- .main-title {
66
- font-family: var(--heading-font);
67
- font-size: 1.7rem;
68
- font-weight: 700;
69
- margin-bottom: 0.15rem;
70
- }
71
-
72
- .subtitle {
73
- font-size: 0.85rem;
74
- opacity: 0.9;
75
- }
76
-
77
- .home-btn {
78
- padding: 0.35rem 0.8rem;
79
- background-color: rgba(255, 255, 255, 0.2);
80
- border-radius: 6px;
81
- transition: background-color 0.3s ease;
82
- font-size: 0.9rem;
83
- position: absolute;
84
- right: 20px;
85
- top: 1rem;
86
- }
87
-
88
- .home-btn:hover {
89
- background-color: rgba(255, 255, 255, 0.3);
90
- }
91
-
92
- /* Updated Layout Styles */
93
- main {
94
- flex: 1;
95
- padding: 1.5rem;
96
- }
97
-
98
- .content-wrapper {
99
- display: grid;
100
- grid-template-columns: 1fr 1fr;
101
- gap: 1.5rem;
102
- max-width: 1440px;
103
- margin: 0 auto;
104
- }
105
-
106
- .map-panel {
107
- background: white;
108
- border-radius: 8px;
109
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
110
- overflow: hidden;
111
- position: relative;
112
- }
113
-
114
- #map {
115
- height: calc(100vh - 200px);
116
- width: 100%;
117
- z-index: 1;
118
- border-radius: 0 0 8px 8px;
119
- }
120
-
121
- .section-title {
122
- font-family: var(--heading-font);
123
- font-size: 1.2rem;
124
- padding: 1rem;
125
- margin: 0;
126
- background-color: var(--card-bg);
127
- border-bottom: 1px solid var(--border-color);
128
- }
129
-
130
- /* Custom map controls */
131
- .map-controls {
132
- position: absolute;
133
- top: 70px;
134
- right: 10px;
135
- z-index: 1000;
136
- }
137
-
138
- .reset-map-btn {
139
- background: white;
140
- border: none;
141
- border-radius: 4px;
142
- width: 30px;
143
- height: 30px;
144
- box-shadow: 0 0 0 2px rgba(0,0,0,0.2);
145
- cursor: pointer;
146
- display: flex;
147
- align-items: center;
148
- justify-content: center;
149
- font-size: 16px;
150
- color: #333;
151
- }
152
-
153
- .reset-map-btn:hover {
154
- background-color: #f4f4f4;
155
- }
156
-
157
- /* Input Panel Styles */
158
- .input-panel {
159
- display: flex;
160
- flex-direction: column;
161
- gap: 1.5rem;
162
- height: 100%;
163
- }
164
-
165
- .coordinates-section {
166
- background: white;
167
- border-radius: 8px;
168
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
169
- padding-bottom: 1rem;
170
- }
171
-
172
- .section-header {
173
- display: flex;
174
- justify-content: space-between;
175
- align-items: center;
176
- padding-right: 1rem;
177
- border-bottom: 1px solid var(--border-color);
178
- }
179
-
180
- #coordinates-container {
181
- padding: 1rem;
182
- max-height: calc(100vh - 400px);
183
- overflow-y: auto;
184
- }
185
-
186
- .coordinate-group {
187
- background: var(--input-bg);
188
- border-radius: 8px;
189
- padding: 1rem;
190
- margin-bottom: 1rem;
191
- position: relative;
192
- border-left: 3px solid var(--primary-purple);
193
- }
194
-
195
- .coord-header {
196
- display: flex;
197
- justify-content: space-between;
198
- align-items: center;
199
- margin-bottom: 0.75rem;
200
- }
201
-
202
- .coord-header h3 {
203
- font-size: 1rem;
204
- font-weight: 600;
205
- margin: 0;
206
- }
207
-
208
- .form-row {
209
- display: flex;
210
- gap: 0.75rem;
211
- margin-bottom: 0.75rem;
212
- }
213
-
214
- .form-group {
215
- flex: 1;
216
- margin-bottom: 0.75rem;
217
- }
218
-
219
- label {
220
- display: block;
221
- font-size: 0.85rem;
222
- margin-bottom: 0.25rem;
223
- color: var(--text-color);
224
- opacity: 0.8;
225
- }
226
-
227
- input, select {
228
- width: 100%;
229
- padding: 0.6rem;
230
- border: 1px solid var(--border-color);
231
- border-radius: 4px;
232
- font-family: inherit;
233
- font-size: 0.9rem;
234
- }
235
-
236
- input:focus, select:focus {
237
- outline: none;
238
- border-color: var(--primary-purple);
239
- box-shadow: 0 0 0 2px rgba(142, 84, 233, 0.2);
240
- }
241
-
242
- .btn-action {
243
- background: rgba(255, 255, 255, 0.3);
244
- color: var(--primary-purple);
245
- border: 1px solid var(--primary-purple);
246
- border-radius: 4px;
247
- padding: 0.3rem 0.6rem;
248
- font-size: 0.8rem;
249
- cursor: pointer;
250
- transition: all 0.3s;
251
- }
252
-
253
- .btn-action:hover {
254
- background-color: rgba(142, 84, 233, 0.1);
255
- }
256
-
257
- .btn-primary {
258
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
259
- color: white;
260
- border: none;
261
- border-radius: 4px;
262
- padding: 0.8rem 1.2rem;
263
- font-size: 0.95rem;
264
- font-weight: 500;
265
- cursor: pointer;
266
- transition: all 0.3s;
267
- margin: 0 1rem;
268
- width: calc(100% - 2rem);
269
- }
270
-
271
- .btn-primary:hover {
272
- background: linear-gradient(135deg, var(--primary-blue), var(--light-purple));
273
- box-shadow: 0 4px 12px rgba(142, 84, 233, 0.2);
274
- }
275
-
276
- .remove-coord {
277
- background: none;
278
- border: none;
279
- color: #9ca3af;
280
- cursor: pointer;
281
- font-size: 0.95rem;
282
- padding: 0.2rem;
283
- border-radius: 4px;
284
- transition: all 0.2s;
285
- }
286
-
287
- .remove-coord:not([disabled]):hover {
288
- background-color: #f3f4f6;
289
- color: #ef4444;
290
- }
291
-
292
- .remove-coord[disabled] {
293
- opacity: 0.5;
294
- cursor: not-allowed;
295
- }
296
-
297
- /* Results Styles */
298
- .elevation-result {
299
- margin-top: 1rem;
300
- border-top: 1px dashed #e1e4e8;
301
- padding-top: 1rem;
302
- }
303
-
304
- .result-badge {
305
- background: linear-gradient(135deg, var(--primary-blue), var(--primary-purple));
306
- padding: 1rem;
307
- border-radius: 8px;
308
- color: white;
309
- margin-bottom: 0.5rem;
310
- }
311
-
312
- .result-badge.error {
313
- background: linear-gradient(135deg, #e53e3e, #d00000);
314
- }
315
-
316
- .elevation-metrics {
317
- display: flex;
318
- justify-content: space-between;
319
- align-items: center;
320
- }
321
-
322
- .metric {
323
- text-align: center;
324
- }
325
-
326
- .metric .value {
327
- font-size: 1.4rem;
328
- font-weight: 600;
329
- display: block;
330
- }
331
-
332
- .metric .unit {
333
- font-size: 0.8rem;
334
- opacity: 0.9;
335
- }
336
-
337
- .coord-detail {
338
- font-size: 0.75rem;
339
- color: #718096;
340
- line-height: 1.4;
341
- }
342
-
343
- .results-grid {
344
- display: grid;
345
- grid-template-columns: repeat(2, 1fr);
346
- gap: 1rem;
347
- margin-top: 1rem;
348
- }
349
-
350
- .result-card {
351
- background-color: white;
352
- padding: 1rem;
353
- border-radius: 8px;
354
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
355
- text-align: center;
356
- }
357
-
358
- .result-label {
359
- font-size: 0.9rem;
360
- color: #718096;
361
- margin-bottom: 0.5rem;
362
- }
363
-
364
- .result-value {
365
- font-size: 1.5rem;
366
- font-weight: 600;
367
- color: var(--primary-purple);
368
- }
369
-
370
- .result-value-secondary {
371
- font-size: 0.9rem;
372
- color: #718096;
373
- }
374
-
375
- /* Results Container */
376
- #results-container {
377
- background: white;
378
- border-radius: 8px;
379
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
380
- overflow: hidden;
381
- }
382
-
383
- .result-card {
384
- background: var(--input-bg);
385
- padding: 1rem;
386
- border-radius: 8px;
387
- text-align: center;
388
- }
389
-
390
- .result-label {
391
- font-size: 0.9rem;
392
- color: var(--text-color);
393
- opacity: 0.7;
394
- margin-bottom: 0.5rem;
395
- }
396
-
397
- .result-value {
398
- font-size: 1.5rem;
399
- font-weight: 600;
400
- color: var(--primary-purple);
401
- }
402
-
403
- .result-value-secondary {
404
- font-size: 0.9rem;
405
- color: var(--primary-blue);
406
- opacity: 0.8;
407
- }
408
-
409
- .dual-units {
410
- display: flex;
411
- flex-direction: column;
412
- align-items: center;
413
- gap: 0.25rem;
414
- }
415
-
416
- /* Utility Classes */
417
- .hidden {
418
- display: none !important;
419
- }
420
-
421
- /* Loading Overlay */
422
- #loading-overlay {
423
- position: fixed;
424
- top: 0;
425
- left: 0;
426
- right: 0;
427
- bottom: 0;
428
- background-color: rgba(0, 0, 0, 0.6);
429
- display: flex;
430
- justify-content: center;
431
- align-items: center;
432
- z-index: 9999;
433
- }
434
-
435
- .loader {
436
- width: 50px;
437
- height: 50px;
438
- border: 5px solid var(--text-light);
439
- border-top: 5px solid var(--primary-purple);
440
- border-radius: 50%;
441
- animation: spin 1s linear infinite;
442
- }
443
-
444
- @keyframes spin {
445
- 0% { transform: rotate(0deg); }
446
- 100% { transform: rotate(360deg); }
447
- }
448
-
449
- /* Footer */
450
- footer {
451
- background-color: var(--dark-blue);
452
- color: var(--text-light);
453
- text-align: center;
454
- padding: 1rem;
455
- margin-top: auto;
456
- }
457
-
458
- footer a {
459
- color: var(--text-light);
460
- text-decoration: none;
461
- }
462
-
463
- footer a:hover {
464
- text-decoration: underline;
465
- }
466
-
467
- /* Responsive Styles */
468
- @media (max-width: 992px) {
469
- .content-wrapper {
470
- grid-template-columns: 1fr;
471
- }
472
-
473
- #map {
474
- height: 400px;
475
- }
476
-
477
- #coordinates-container {
478
- max-height: 400px;
479
- }
480
- }
481
-
482
- @media (max-width: 768px) {
483
- .home-btn {
484
- position: static;
485
- margin-top: 1rem;
486
- }
487
-
488
- .header-content {
489
- padding: 1rem;
490
- }
491
-
492
- .form-row {
493
- flex-direction: column;
494
- gap: 0.5rem;
495
- }
496
-
497
- .main-title {
498
- font-size: 1.5rem;
499
- }
500
-
501
- main {
502
- padding: 1rem;
503
- }
504
  }
 
1
+ body {
2
+ font-family: 'Inter', sans-serif;
3
+ margin: 0;
4
+ padding: 0;
5
+ background-color: #f4f4f4;
6
+ }
7
+
8
+ .app-container {
9
+ max-width: 1200px;
10
+ margin: 0 auto;
11
+ padding: 20px;
12
+ }
13
+
14
+ header {
15
+ background-color: #007bff;
16
+ color: white;
17
+ padding: 10px 20px;
18
+ text-align: center;
19
+ }
20
+
21
+ .main-title {
22
+ font-size: 2.5em;
23
+ margin: 0;
24
+ }
25
+
26
+ .subtitle {
27
+ font-size: 1.2em;
28
+ }
29
+
30
+ .nav-link {
31
+ color: white;
32
+ text-decoration: none;
33
+ }
34
+
35
+ .map-panel, .input-panel {
36
+ margin: 20px 0;
37
+ padding: 20px;
38
+ background-color: white;
39
+ border-radius: 8px;
40
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
41
+ }
42
+
43
+ .section-title {
44
+ font-size: 1.5em;
45
+ margin-bottom: 10px;
46
+ }
47
+
48
+ .btn-action, .btn-primary {
49
+ background-color: #007bff;
50
+ color: white;
51
+ border: none;
52
+ padding: 10px 15px;
53
+ border-radius: 5px;
54
+ cursor: pointer;
55
+ }
56
+
57
+ .btn-action:hover, .btn-primary:hover {
58
+ background-color: #0056b3;
59
+ }
60
+
61
+ .coordinate-group {
62
+ margin-bottom: 15px;
63
+ }
64
+
65
+ .form-row {
66
+ display: flex;
67
+ justify-content: space-between;
68
+ }
69
+
70
+ .form-group {
71
+ flex: 1;
72
+ margin-right: 10px;
73
+ }
74
+
75
+ .form-group:last-child {
76
+ margin-right: 0;
77
+ }
78
+
79
+ .elevation-result {
80
+ margin-top: 10px;
81
+ }
82
+
83
+ .result-badge {
84
+ background-color: #e9ecef;
85
+ padding: 10px;
86
+ border-radius: 5px;
87
+ }
88
+
89
+ .metric {
90
+ display: inline-block;
91
+ margin-right: 15px;
92
+ }
93
+
94
+ .hidden {
95
+ display: none;
96
+ }
97
+
98
+ footer {
99
+ text-align: center;
100
+ padding: 10px 0;
101
+ background-color: #007bff;
102
+ color: white;
103
+ position: relative;
104
+ bottom: 0;
105
+ width: 100%;
106
+ }
107
+
108
+ .loader {
109
+ border: 8px solid #f3f3f3;
110
+ border-top: 8px solid #3498db;
111
+ border-radius: 50%;
112
+ width: 40px;
113
+ height: 40px;
114
+ animation: spin 1s linear infinite;
115
+ }
116
+
117
+ @keyframes spin {
118
+ 0% { transform: rotate(0deg); }
119
+ 100% { transform: rotate(360deg); }
120
+ }
121
+
122
+ /* Styles for new tools */
123
+ .tool-card {
124
+ display: flex;
125
+ align-items: center;
126
+ background-color: #f8f9fa;
127
+ border: 1px solid #dee2e6;
128
+ border-radius: 5px;
129
+ padding: 10px;
130
+ margin: 10px 0;
131
+ text-decoration: none;
132
+ color: #333;
133
+ }
134
+
135
+ .tool-card:hover {
136
+ background-color: #e2e6ea;
137
+ }
138
+
139
+ .tool-icon {
140
+ font-size: 2em;
141
+ margin-right: 10px;
142
+ }
143
+
144
+ .tool-info h3 {
145
+ margin: 0;
146
+ font-size: 1.2em;
147
+ }
148
+
149
+ .tool-info p {
150
+ margin: 5px 0 0;
151
+ font-size: 0.9em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
frontend/terrain.html ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Terrain Data Extractor - GeoTools Hub</title>
7
+
8
+ <!-- Leaflet CSS -->
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" />
10
+
11
+ <!-- Font Awesome -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
13
+
14
+ <!-- Google Fonts -->
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@700;800;900&display=swap" rel="stylesheet">
18
+
19
+ <!-- Custom CSS -->
20
+ <link rel="stylesheet" href="/frontend/styles.css">
21
+ </head>
22
+ <body>
23
+ <div class="app-container">
24
+ <!-- Header -->
25
+ <header>
26
+ <div class="header-content">
27
+ <div class="title-section">
28
+ <h1 class="main-title">Terrain Data Extractor</h1>
29
+ <p class="subtitle">Extract terrain data such as slope and aspect</p>
30
+ </div>
31
+ <a href="index.html" class="nav-link home-btn"><i class="fas fa-home"></i> Home</a>
32
+ </div>
33
+ </header>
34
+
35
+ <!-- Main Content -->
36
+ <main>
37
+ <div class="content-wrapper">
38
+ <div class="input-panel">
39
+ <div class="coordinates-section">
40
+ <h2 class="section-title">Coordinates</h2>
41
+ <div class="form-row">
42
+ <div class="form-group">
43
+ <label for="latitude">Latitude</label>
44
+ <input type="number" id="latitude" class="latitude-input" step="any" placeholder="e.g. 37.7749">
45
+ </div>
46
+ <div class="form-group">
47
+ <label for="longitude">Longitude</label>
48
+ <input type="number" id="longitude" class="longitude-input" step="any" placeholder="e.g. -122.4194">
49
+ </div>
50
+ </div>
51
+ <button id="extract-terrain" class="btn-primary">
52
+ <i class="fas fa-extract"></i> Extract Terrain Data
53
+ </button>
54
+ </div>
55
+
56
+ <!-- Results Section -->
57
+ <div id="terrain-results" class="hidden">
58
+ <h2 class="section-title">Terrain Data</h2>
59
+ <div id="terrain-data">
60
+ <!-- Results will be populated here by JavaScript -->
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </main>
66
+
67
+ <!-- Footer -->
68
+ <footer>
69
+ <p>&copy; 2025 GeoTools Hub | <a href="index.html">Back to Tools</a></p>
70
+ </footer>
71
+ </div>
72
+
73
+ <!-- Loading Overlay -->
74
+ <div id="loading-overlay" class="hidden">
75
+ <div class="loader"></div>
76
+ </div>
77
+
78
+ <!-- Leaflet JS -->
79
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
80
+
81
+ <!-- Custom JS -->
82
+ <script src="/frontend/script.js"></script>
83
+ </body>
84
+ </html>