SmartCane/webapps/geojson_viewer/index.html
2026-02-10 11:34:29 +01:00

525 lines
17 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoJSON Viewer</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" />
<link rel="stylesheet" href="../theme.css">
<link rel="icon" type="image/x-icon" href="../res/pin.png">
<style>
/* Layout styling adapted from sugar_mill_locator to match theme */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #faf8f3;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Header matches the locator tool */
header {
background: linear-gradient(135deg, var(--sc-primary) 0%, var(--sc-primary-light) 100%);
color: white;
padding: 15px 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
font-size: 20px;
font-weight: 600;
margin: 0;
flex: 1;
text-align: center;
}
.header-controls {
display: flex;
gap: 10px;
align-items: center;
}
/* Main Container */
.container {
display: flex;
flex: 1;
overflow: hidden;
height: calc(100vh - 60px);
background: white;
}
/* Map takes remaining space */
#map {
flex: 1;
position: relative;
background: #e0e0e0;
width: 100%;
height: 100%;
z-index: 1;
}
/* Sidebar Panel styling */
.panel {
width: 360px;
background: var(--sc-light);
border-left: 1px solid var(--sc-border);
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: -2px 0 8px rgba(0,0,0,0.08);
z-index: 2;
}
.panel-header {
background: linear-gradient(135deg, var(--sc-primary) 0%, var(--sc-primary-light) 100%);
color: white;
padding: 15px;
font-weight: 600;
font-size: 14px;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-content {
flex: 1;
overflow-y: auto;
padding: 15px;
display: flex;
flex-direction: column;
gap: 20px;
}
.panel-section {
background: white;
padding: 15px;
border-radius: 8px;
border: 1px solid var(--sc-border);
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.section-title {
font-weight: 600;
font-size: 13px;
color: var(--sc-text);
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
}
/* File Input Styling */
.file-input-wrapper {
position: relative;
margin-bottom: 10px;
}
.file-input-label {
display: block;
padding: 12px;
background: #f8f9fa;
border: 2px dashed var(--sc-primary-light);
border-radius: 6px;
text-align: center;
cursor: pointer;
color: var(--sc-primary);
font-weight: 600;
font-size: 13px;
transition: all 0.3s;
}
.file-input-label:hover {
background: #eef7f6;
transform: translateY(-1px);
}
input[type="file"] {
display: none;
}
.file-name {
font-size: 12px;
color: var(--sc-text-muted);
margin-top: 5px;
text-align: center;
word-break: break-all;
}
/* Stats & Summary */
.stat-item {
display: flex;
justify-content: space-between;
font-size: 13px;
padding: 6px 0;
border-bottom: 1px solid #f0f0f0;
}
.stat-item:last-child { border-bottom: none; }
.stat-label { color: var(--sc-text-muted); }
.stat-value { font-weight: 600; color: var(--sc-text); }
/* Feature List */
.features-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #eee;
border-radius: 4px;
}
.feature-item {
padding: 8px 10px;
font-size: 12px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background 0.2s;
}
.feature-item:hover { background: #f5f5f5; }
.feature-item.active {
background: var(--sc-primary-light);
color: white;
}
/* Buttons */
.btn-group {
display: flex;
gap: 8px;
}
button {
padding: 8px 12px;
border: none;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
flex: 1;
}
/* Use theme colors via variables where possible, or match hex */
.btn-primary { background: var(--sc-primary); color: white; }
.btn-primary:hover { background: var(--sc-primary-light); }
.btn-secondary { background: #f0f0f0; color: #333; border: 1px solid #ddd; }
.btn-secondary:hover { background: #e0e0e0; }
.btn-danger { background: var(--sc-danger); color: white; }
.btn-danger:hover { background: #c0392b; }
/* Map Controls (Satellite Toggle) */
.map-toggle-wrapper {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,0.2);
padding: 5px 10px;
border-radius: 4px;
font-size: 13px;
}
/* Modal Styling (Matching Locator) */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 2000;
align-items: center;
justify-content: center;
}
.modal.active { display: flex; }
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.modal-header {
background: linear-gradient(135deg, var(--sc-primary) 0%, var(--sc-primary-light) 100%);
color: white;
padding: 15px 20px;
border-radius: 8px 8px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h2 { font-size: 16px; margin: 0; }
.modal-body {
padding: 20px;
overflow-y: auto;
}
.modal-property {
display: flex; /* Enables side-by-side layout */
align-items: baseline;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.modal-property:last-child {
border-bottom: none;
}
.modal-property-key {
flex: 0 0 40%; /* First column: fixed 40% width */
font-weight: 700; /* Bold text */
color: var(--sc-primary);
padding-right: 15px;
font-size: 13px;
}
.modal-property-value {
flex: 1; /* Second column: takes remaining space */
color: #333;
word-break: break-word;
font-size: 13px;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 24px;
padding: 0;
cursor: pointer;
opacity: 0.8;
flex: 0;
}
.close-btn:hover { opacity: 1; }
/* Property Table in Modal/Sidebar */
.property-row {
border-bottom: 1px solid #eee;
padding: 8px 0;
font-size: 13px;
}
.property-key { font-weight: 600; color: var(--sc-primary); display: block; margin-bottom: 2px; }
.property-value { color: #333; word-break: break-word; }
/* Alert Messages */
.alert {
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
font-size: 12px;
display: none;
}
.alert.active { display: block; }
.alert-error { background: #ffebee; color: #c62828; border: 1px solid #ef9a9a; }
.alert-success { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
/* Search Input */
.search-input {
width: 100%;
padding: 8px;
border: 1px solid var(--sc-border);
border-radius: 4px;
font-size: 13px;
}
.search-input:focus {
outline: none;
border-color: var(--sc-primary);
box-shadow: 0 0 0 2px rgba(42, 171, 149, 0.1);
}
/* Attribute Table */
.table-wrapper {
overflow-x: auto;
border: 1px solid #eee;
border-radius: 4px;
}
.attribute-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.attribute-table th {
background: #f8f9fa;
text-align: left;
padding: 8px;
font-weight: 600;
color: var(--sc-text);
border-bottom: 2px solid #eee;
}
.attribute-table td {
padding: 8px;
border-bottom: 1px solid #eee;
color: var(--sc-text);
}
/* Scrollbar Styling */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #f1f1f1; }
::-webkit-scrollbar-thumb { background: #ccc; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #bbb; }
</style>
</head>
<body>
<script>
// if (sessionStorage.getItem('authenticated') !== 'true') {
// window.location.href = '../login.html';
// }
</script>
<header>
<button class="back-btn" onclick="window.location.href='../';" title="Back to main tools">← Back</button>
<h1>📍 GeoJSON Viewer</h1>
<div class="header-controls">
<label class="map-toggle-wrapper" style="cursor: pointer;">
<input type="checkbox" id="mapToggle" aria-label="Toggle satellite view">
<span>🛰️ Satellite</span>
</label>
<label class="map-toggle-wrapper" style="cursor: pointer;">
<input type="checkbox" id="labelsToggle">
<span>Labels</span>
</label>
<label class="map-toggle-wrapper" style="cursor: pointer;">
<input type="checkbox" id="millsToggle">
<span>🏭 Mills</span>
</label>
</div>
</header>
<div class="container">
<div id="map"></div>
<div class="panel">
<div class="panel-header">Controls & Data</div>
<div class="panel-content">
<div class="alert alert-error" id="errorMessage"></div>
<div class="alert alert-success" id="successMessage"></div>
<div class="panel-section">
<div class="section-title">📁 Upload File</div>
<div class="file-input-wrapper">
<label for="geojsonFile" class="file-input-label">
Choose GeoJSON File
</label>
<input type="file" id="geojsonFile" accept=".geojson,.json" />
<div class="file-name" id="fileName">No file selected</div>
</div>
<div class="btn-group">
<button id="clearBtn" class="btn-secondary">Clear</button>
<button id="downloadBtn" class="btn-primary" style="display: none;">Download</button>
</div>
</div>
<div class="panel-section" id="summarySection" style="display: none;">
<div class="section-title">📊 Project Summary</div>
<div class="stat-item">
<span class="stat-label">Client:</span>
<span class="stat-value" id="clientName">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Fields:</span>
<span class="stat-value" id="totalFields">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Area (ha):</span>
<span class="stat-value" id="totalHectares">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Area (ac):</span>
<span class="stat-value" id="totalAcres">0</span>
</div>
</div>
<div class="panel-section" id="statsSection" style="display: none;">
<div class="section-title"> Data Stats</div>
<div class="stat-item">
<span class="stat-label">Features:</span>
<span class="stat-value" id="featureCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Type:</span>
<span class="stat-value" id="geomType">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Bounds:</span>
<span class="stat-value" id="bounds">-</span>
</div>
</div>
<div class="panel-section" id="featuresSection" style="display: none;">
<div class="section-title">🗺️ Features</div>
<div style="margin-bottom: 10px;">
<input
type="text"
id="featureSearch"
class="search-input"
placeholder="Search by code..."
/>
</div>
<div class="features-list" id="featuresList"></div>
</div>
<div class="panel-section" id="propertiesSection" style="display: none;">
<div class="section-title">📋 Selected Properties</div>
<div id="propertiesContent" style="font-size: 13px; color: #666;">
Select a feature to view details
</div>
</div>
<div class="panel-section" id="tableSection" style="display: none;">
<div class="section-title">📋 Data Table</div>
<div class="table-wrapper">
<table class="attribute-table" id="attributeTable">
<thead><tr id="tableHeader"></tr></thead>
<tbody id="tableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="featureModal">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Feature Details</h2>
<button class="close-btn" id="modalCloseBtn">&times;</button>
</div>
<div class="modal-body" id="modalBody">
</div>
<div style="padding: 15px; border-top: 1px solid #eee; text-align: right;">
<button class="btn-secondary" onclick="closeFeatureModal()">Close</button>
</div>
</div>
</div>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js" integrity="sha512-puJW3E/qXDqYp9IfhAI54BJEaWIfloJ7JWs7OeD5i6ruC9JZL1gERT1wjtwXFlh7CjE7ZJ+/vcRZRkIYIb6p4g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6.5.0/dist/turf.min.js"></script>
<script src="google-sheets-config.js"></script>
<script src="script.js"></script>
</body>
</html>