Repository navigation
Expand file tree
/
Copy pathmap.html
More file actions
193 lines (180 loc) · 8.57 KB
/
Copy pathmap.html
File metadata and controls
193 lines (180 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Geoduck — Spatial Map</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="anonymous"/>
</head>
<body>
<div class="app-shell">
<header class="app-header">
<a href="index.html" class="app-logo">
Geoduck
<span class="app-logo-divider"></span>
<span class="app-logo-subtitle">by Geosyntec</span>
</a>
<nav class="app-nav">
<a href="index.html" class="nav-link">Data Analysis</a>
<a href="map.html" class="nav-link active">Spatial Map</a>
<a href="hru_analysis.html" class="nav-link">HRU Analysis</a>
<a href="debug.html" class="nav-link muted">Debug</a>
</nav>
</header>
<main class="app-main">
<div class="page-heading">
<h1>Spatial GeoJSON Analysis</h1>
<p>Run spatial SQL queries on GeoJSON files and visualize results on an interactive map.</p>
</div>
<!-- Source & search -->
<div class="card">
<div class="card-header">
<span class="card-title">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="7" r="5" stroke="#8A7A72" stroke-width="1.4"/><path d="M8 4v3l2 1.5" stroke="#8A7A72" stroke-width="1.3" stroke-linecap="round"/><path d="M4 13l2-2" stroke="#8A7A72" stroke-width="1.4" stroke-linecap="round"/></svg>
Data Source
</span>
</div>
<div class="card-body">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
<div class="form-group" style="margin:0">
<label for="geojson-source">GeoJSON source</label>
<input type="text" id="geojson-source" value="data/cig_grid_wgs.geojson"
placeholder="URL or local path to GeoJSON…" class="mono">
<span class="form-hint">Local path or remote URL</span>
</div>
<div class="form-group" style="margin:0">
<label for="address-search">Search location</label>
<div style="position:relative">
<div class="input-row">
<input type="text" id="address-search"
placeholder="Address, city, or place…" autocomplete="off">
<button id="search-button" class="btn btn-ghost btn-sm">Search</button>
</div>
<div id="suggestions-dropdown" class="suggestions-dropdown"></div>
</div>
<span class="form-hint">Centers the map and provides coordinates</span>
</div>
</div>
</div>
</div>
<!-- Query -->
<div class="card" style="margin-top:1rem">
<div class="card-header">
<span class="card-title">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M3 8h7M3 12h5" stroke="#8A7A72" stroke-width="1.5" stroke-linecap="round"/></svg>
SQL Query
</span>
</div>
<div class="card-body">
<div class="form-group">
<label for="sql-query">Spatial SQL</label>
<textarea id="sql-query" rows="5"
placeholder="WITH datasource AS (SELECT * FROM ST_Read({SOURCE}))
SELECT grid_id, lat_vector, lon_vector,
ST_Area(geom) as area_sq_degrees,
ST_AsGeoJSON(geom) as geometry
FROM datasource
ORDER BY area_sq_degrees DESC LIMIT 10;"></textarea>
<span class="form-hint">Use <code class="mono" style="background:var(--code-bg);padding:.1em .3em;border-radius:3px;font-size:.78rem">{SOURCE}</code> as a placeholder for the GeoJSON path above.</span>
</div>
<div style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap">
<button id="execute-button" class="btn btn-primary" disabled>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><polygon points="2,1 12,6.5 2,12" fill="white"/></svg>
Run query
</button>
<div class="btn-group">
<button class="chip" id="query1-button"
data-query="WITH datasource AS (SELECT * FROM ST_Read({SOURCE})) SELECT grid_id, lat_vector, lon_vector, ST_Area(geom) as area_sq_degrees, ST_AsGeoJSON(geom) as geometry FROM datasource ORDER BY area_sq_degrees DESC LIMIT 10;">
Largest cells
</button>
<button class="chip" id="query2-button"
data-query="WITH datasource AS (SELECT * FROM ST_Read({SOURCE})) SELECT grid_id, ghs_vector, ST_AsText(ST_Centroid(geom)) as centroid FROM datasource WHERE lat_vector BETWEEN 48.0 AND 49.0 LIMIT 10;">
Grid centroids
</button>
<button class="chip" id="query3-button"
data-query="WITH datasource AS (SELECT * FROM ST_Read({SOURCE})) SELECT grid_id, lat_vector, lon_vector, ST_AsGeoJSON(geom) as geometry FROM datasource WHERE ST_Within(geom, ST_MakeEnvelope(-123, 48, -121, 49)) ORDER BY grid_id LIMIT 15;">
Bounding box filter
</button>
</div>
</div>
<div class="status-bar" style="margin-top:.6rem">
<span class="status-dot loading" id="status-dot"></span>
<span id="status-message">Initializing DuckDB…</span>
</div>
<div style="margin-top:1rem">
<button class="collapsible-trigger" id="spatial-ref-toggle">
<span class="collapsible-arrow">▶</span> Spatial functions reference
</button>
<div class="collapsible-body" id="spatial-ref-body">
<h6>Spatial functions</h6>
<ul>
<li><code>ST_Read('url')</code> — read GeoJSON, Shapefile, etc.</li>
<li><code>ST_Area(geom)</code> — area of geometry</li>
<li><code>ST_Centroid(geom)</code> — centroid point</li>
<li><code>ST_AsText(geom)</code> — WKT representation</li>
<li><code>ST_AsGeoJSON(geom)</code> — GeoJSON string</li>
<li><code>ST_Within(g1, g2)</code> — containment test</li>
<li><code>ST_MakeEnvelope(xmin,ymin,xmax,ymax)</code> — bounding box</li>
<li><code>ST_Distance(g1, g2)</code> — distance between geometries</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Results: table + map -->
<div style="margin-top:1rem" id="results-container">
<div class="results-split">
<div class="card">
<div class="card-header">
<span class="card-title">Results</span>
<button id="download-csv-button" class="btn btn-success btn-sm" style="display:none">↓ CSV</button>
</div>
<div class="card-body" style="padding:0">
<div id="table-panel">
<div id="table-container" class="table-wrap">
<div style="padding:2rem;text-align:center;color:var(--text-light);font-size:.85rem">
Run a query to see results here.
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">Map</span>
</div>
<div class="card-body" style="padding:.75rem">
<div id="map-container" style="height:460px"></div>
</div>
</div>
</div>
</div>
</main>
<footer class="app-footer">
© 2025 Geosyntec Consultants
</footer>
</div>
<script type="module">
import * as duckdb from 'https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.1-dev106.0/+esm';
window.duckdb = duckdb;
</script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script type="module" src="js/map.js"></script>
<script>
document.getElementById('spatial-ref-toggle').addEventListener('click', function() {
this.classList.toggle('open');
document.getElementById('spatial-ref-body').classList.toggle('open');
});
const obs = new MutationObserver(() => {
const msg = document.getElementById('status-message').textContent;
const dot = document.getElementById('status-dot');
if (msg.includes('ready') || msg.includes('Ready')) dot.className = 'status-dot ready';
else if (msg.includes('Error') || msg.includes('error')) dot.className = 'status-dot error';
else dot.className = 'status-dot loading';
});
obs.observe(document.getElementById('status-message'), { childList:true, characterData:true, subtree:true });
</script>
</body>
</html>