maplibre-preview 1.3.9 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 576fc35a947103556613fb983552a7fea7045b5109762b666ff4541dc08a3fb1
4
- data.tar.gz: 91d004357c42439be1206b8d732834f5ed4b176ca88d2461c1a243a53c2a2564
3
+ metadata.gz: a800d72c98751ed645b6c3e49dfcaa5d5560e258412d6d360cdf4ce85b747384
4
+ data.tar.gz: 555af4aac0846b6e50aea699bab368cb4ab9585ade0d2e2510d209af8aa892ae
5
5
  SHA512:
6
- metadata.gz: be4c9c01fa9268120f9faa29001b571774dcadc03d190c3bf619753c06a62cd76da891d89c44f9a23eb7255863ac24c98778c9a7cbb6bc7cee86705451ada748
7
- data.tar.gz: a0ca0a43e7fe67801a51e730303559eb6af9718e15d145bdd14a3d77e0e22f6eed48ab355203cb6088462954ac57d47d2dcd1de121d2fa96ee76acf38b3fbc6e
6
+ metadata.gz: e2052b52a76ca7071c52bb989f80d52c153bae0fd0ba9bf4d1ca4c8cc8705795c0784e8949b2f320bb9d826210a6ce58f1f4e78684ac30923fc1a999efedc7c2
7
+ data.tar.gz: 1c4560519a20b975f31796394ab36d0213eb5b94596ccfdc37772943f40733eeb0a95a662797963c2010b89886504e7466188ea562c9c3ea27b51cc02ba45604
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0] - 2025-12-04
4
+
5
+ ### Added
6
+ - **Tile Grid visualization** - TileGridManager class for displaying tile boundaries and statistics
7
+ - **Tile count tracking** - real-time count of loaded tiles with fallback estimation
8
+ - **Tile Grid panel** - UI panel showing number of loaded tiles with toggle for tile borders
9
+ - **Tile Grid button** - control button in layer controls to show/hide tile grid visualization
10
+
11
+ ### Technical Changes
12
+ - **tilegrid.js** - new JavaScript module for tile grid management
13
+ - **MapLibre showTileBoundaries integration** - uses native debug feature for tile boundary visualization
14
+ - **Internal API tile counting** - attempts to count tiles via sourceCaches with fallback estimation
15
+
16
+ ## [1.3.10] - 2025-11-19
17
+
18
+ ### Changed
19
+ - **Panel styling** - unified background, transparency and edge attachment for all panels
20
+ - **Toggle buttons** - changed arrow directions, replaced performance panel close button with collapse
21
+ - **Performance panel** - partial collapse mode showing only FPS and Memory
22
+
3
23
  ## [1.3.9] - 2025-11-19
4
24
 
5
25
  ### Changed
@@ -0,0 +1,174 @@
1
+ /**
2
+ * TileGridManager - manages tile boundaries visualization and tile statistics
3
+ */
4
+ class TileGridManager {
5
+ constructor(options) {
6
+ this.map = options.map;
7
+ this.panelContainer = null;
8
+ this.isVisible = false;
9
+ this.showBorders = true;
10
+ this.tilesLoaded = 0;
11
+ this.updateInterval = null;
12
+ }
13
+
14
+ init() {
15
+ if (!this.map) return;
16
+
17
+ this.createPanel();
18
+ this.setupEventListeners();
19
+ this.startTileTracking();
20
+ }
21
+
22
+ createPanel() {
23
+ this.panelContainer = document.createElement('div');
24
+ this.panelContainer.id = 'tilegrid-panel';
25
+ this.panelContainer.className = 'tilegrid-panel';
26
+ this.panelContainer.style.display = 'none';
27
+
28
+ this.panelContainer.innerHTML = `
29
+ <div class="tilegrid-header">
30
+ <span class="tilegrid-title">Tile Grid</span>
31
+ <button class="tilegrid-close" onclick="tileGridManager.toggle()">×</button>
32
+ </div>
33
+ <div class="tilegrid-stats">
34
+ <div class="tilegrid-stat-label">NO. OF TILES LOADED</div>
35
+ <div class="tilegrid-stat-value" id="tilegrid-count">0</div>
36
+ </div>
37
+ <div class="tilegrid-controls">
38
+ <label class="tilegrid-checkbox-label">
39
+ <span>Show tile borders</span>
40
+ <input type="checkbox" id="tilegrid-borders-checkbox" checked onchange="tileGridManager.toggleBorders(this.checked)">
41
+ <span class="tilegrid-checkbox-custom"></span>
42
+ </label>
43
+ </div>
44
+ `;
45
+
46
+ document.getElementById('map-container').appendChild(this.panelContainer);
47
+ }
48
+
49
+ setupEventListeners() {
50
+ this.map.on('sourcedata', () => this.updateTileCount());
51
+ this.map.on('data', () => this.updateTileCount());
52
+ this.map.on('moveend', () => this.updateTileCount());
53
+ this.map.on('zoomend', () => this.updateTileCount());
54
+ }
55
+
56
+ startTileTracking() {
57
+ this.updateInterval = setInterval(() => {
58
+ if (this.isVisible) {
59
+ this.updateTileCount();
60
+ }
61
+ }, 1000);
62
+ }
63
+
64
+ updateTileCount() {
65
+ if (!this.map || !this.isVisible) return;
66
+
67
+ try {
68
+ const style = this.map.getStyle();
69
+ if (!style || !style.sources) return;
70
+
71
+ let totalTiles = 0;
72
+
73
+ // Count tiles from all raster and vector sources
74
+ Object.keys(style.sources).forEach(sourceName => {
75
+ const sourceCache = this.map.style?.sourceCaches?.[sourceName];
76
+ if (sourceCache) {
77
+ const tiles = sourceCache.getVisibleCoordinates?.() || [];
78
+ totalTiles += tiles.length;
79
+ }
80
+ });
81
+
82
+ // Fallback: estimate tiles based on zoom and viewport
83
+ if (totalTiles === 0) {
84
+ totalTiles = this.estimateTileCount();
85
+ }
86
+
87
+ this.tilesLoaded = totalTiles;
88
+ const countElement = document.getElementById('tilegrid-count');
89
+ if (countElement) {
90
+ countElement.textContent = this.tilesLoaded;
91
+ }
92
+ } catch (e) {
93
+ console.warn('TileGridManager: could not count tiles', e);
94
+ }
95
+ }
96
+
97
+ estimateTileCount() {
98
+ const bounds = this.map.getBounds();
99
+ const nw = this.map.project(bounds.getNorthWest());
100
+ const se = this.map.project(bounds.getSouthEast());
101
+
102
+ const viewportWidth = Math.abs(se.x - nw.x);
103
+ const viewportHeight = Math.abs(se.y - nw.y);
104
+
105
+ // Fallback estimation, assume standard 256px tiles
106
+ const tilesX = Math.ceil(viewportWidth / 256) + 1;
107
+ const tilesY = Math.ceil(viewportHeight / 256) + 1;
108
+
109
+ return tilesX * tilesY;
110
+ }
111
+
112
+ toggle() {
113
+ this.isVisible = !this.isVisible;
114
+
115
+ if (this.panelContainer) {
116
+ this.panelContainer.style.display = this.isVisible ? 'block' : 'none';
117
+ }
118
+
119
+ const btn = document.getElementById('tilegrid-mode-btn');
120
+ if (btn) {
121
+ btn.textContent = this.isVisible ? 'Hide Tile Grid' : 'Tile Grid';
122
+ btn.className = `control-button ${this.isVisible ? 'active' : ''}`;
123
+ }
124
+
125
+ if (this.isVisible) {
126
+ this.updateTileCount();
127
+ this.toggleBorders(true);
128
+ } else {
129
+ this.toggleBorders(false);
130
+ }
131
+ }
132
+
133
+ toggleBorders(show) {
134
+ this.showBorders = show;
135
+
136
+ if (this.map) {
137
+ this.map.showTileBoundaries = show;
138
+ }
139
+
140
+ // Update checkbox state
141
+ const checkbox = document.getElementById('tilegrid-borders-checkbox');
142
+ if (checkbox && checkbox.checked !== show) {
143
+ checkbox.checked = show;
144
+ }
145
+ }
146
+
147
+ show() {
148
+ if (!this.isVisible) {
149
+ this.toggle();
150
+ }
151
+ }
152
+
153
+ hide() {
154
+ if (this.isVisible) {
155
+ this.toggle();
156
+ }
157
+ }
158
+
159
+ cleanup() {
160
+ if (this.updateInterval) {
161
+ clearInterval(this.updateInterval);
162
+ this.updateInterval = null;
163
+ }
164
+
165
+ if (this.panelContainer && this.panelContainer.parentNode) {
166
+ this.panelContainer.parentNode.removeChild(this.panelContainer);
167
+ }
168
+
169
+ if (this.map) {
170
+ this.map.showTileBoundaries = false;
171
+ }
172
+ }
173
+ }
174
+
@@ -1,3 +1,3 @@
1
1
  module MapLibrePreview
2
- VERSION = '1.3.9'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -11,6 +11,7 @@ html
11
11
  script[src="https://d3js.org/d3.v#{MapLibrePreview::D3_VERSION}.min.js"]
12
12
  script[src='/js/filters.js']
13
13
  script[src='/js/contour.js']
14
+ script[src='/js/tilegrid.js']
14
15
  style
15
16
  | * { margin: 0; padding: 0; box-sizing: border-box; }
16
17
  | html { height: 100%; }
@@ -106,23 +107,40 @@ html
106
107
  | .success-message { color: #6a9955; font-size: 12px; margin-top: 4px; }
107
108
  | .performance-overlay {
108
109
  | position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 1000;
109
- | background: rgba(43, 43, 43, 0.95); border: 1px solid #555555;
110
- | border-radius: 0 0 6px 6px; padding: 0; min-width: 400px;
111
- | backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
112
- | transition: all 0.3s ease;
110
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
111
+ | border-top: none; border-radius: 0 0 4px 4px; padding: 0; width: fit-content;
112
+ | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: all 0.3s ease;
113
113
  | }
114
114
  | .performance-toggle {
115
- | position: absolute; top: 4px; right: 4px; z-index: 1001;
115
+ | position: absolute; top: 0; right: 0; bottom: 0; z-index: 1001;
116
116
  | background: none; border: none; color: #808080; cursor: pointer;
117
- | font-size: 14px; padding: 0; width: 16px; height: 16px;
117
+ | font-size: 12px; padding: 0; width: 24px; height: 100%;
118
118
  | display: flex; align-items: center; justify-content: center;
119
- | border-radius: 2px; transition: all 0.2s ease;
119
+ | border-radius: 0 0 4px 0; transition: all 0.2s ease;
120
120
  | }
121
121
  | .performance-toggle:hover {
122
122
  | background: #4b4d4f; color: #a9b7c6;
123
123
  | }
124
+ | .performance-toggle-icon {
125
+ | font-size: 12px; font-weight: bold; transition: transform 0.2s;
126
+ | display: flex; align-items: center; justify-content: center;
127
+ | }
128
+ | .performance-overlay.collapsed .performance-toggle-icon {
129
+ | transform: rotate(180deg);
130
+ | }
124
131
  | .performance-content {
125
- | padding: 8px 12px;
132
+ | padding: 8px 32px 8px 12px; position: relative;
133
+ | }
134
+ | .performance-overlay.collapsed .performance-secondary-metrics,
135
+ | .performance-overlay.collapsed #terrain-row {
136
+ | display: none;
137
+ | }
138
+ | .performance-overlay.collapsed .performance-main-metrics .metric {
139
+ | flex: 0 1 auto;
140
+ | }
141
+ | .performance-overlay.collapsed .performance-main-metrics .metric:nth-child(2),
142
+ | .performance-overlay.collapsed .performance-main-metrics .metric:nth-child(4) {
143
+ | display: none;
126
144
  | }
127
145
  | .metric-row {
128
146
  | display: flex; justify-content: space-between; align-items: center;
@@ -298,10 +316,14 @@ html
298
316
  | /* MapLibre Controls */
299
317
  | .maplibregl-ctrl-scale, .maplibregl-ctrl-group, .maplibregl-ctrl-terrain, .maplibregl-ctrl-globe {
300
318
  | background: rgba(60, 63, 65, 0.95) !important; border: 1px solid #555555 !important;
301
- | border-radius: 4px !important; backdrop-filter: blur(10px) !important;
302
- | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
319
+ | border-radius: 4px !important;
320
+ | }
321
+ | .maplibregl-ctrl-scale {
322
+ | color: #a9b7c6 !important; font-size: 11px !important; padding: 4px 8px !important;
323
+ | border-bottom: none !important; border-left: none !important;
324
+ | border-radius: 0 4px 0 0 !important;
325
+ | margin: 0 !important;
303
326
  | }
304
- | .maplibregl-ctrl-scale { color: rgb(100, 185, 61) !important; font-size: 11px !important; padding: 4px 8px !important; }
305
327
  | .maplibregl-ctrl-scale-line { background: #6897bb !important; border: 1px solid #7aa8cc !important; }
306
328
  | .maplibregl-ctrl-group button { background: rgba(75, 105, 61, 0.8) !important; border: 1px solid rgba(72, 139, 92, 0.4) !important; color: #ffffff !important; font-size: 14px !important; font-weight: bold !important; transition: all 0.2s ease !important; }
307
329
  | .maplibregl-ctrl-group button:hover { background: rgba(255, 198, 109, 0.9) !important; border-color: #ffd87d !important; color: #2b2b2b !important; transform: scale(1.05) !important; }
@@ -351,8 +373,8 @@ html
351
373
  | .profile-tooltip-text { text-anchor: middle; fill: #ff0000; font-size: 14px; }
352
374
  | .version-info {
353
375
  | position: fixed; bottom: 0; right: 0; z-index: 1000;
354
- | background: rgba(60, 63, 65, 0.85); border: none; border-radius: 4px 0 0 0;
355
- | padding: 4px 8px; backdrop-filter: blur(5px);
376
+ | background: rgba(60, 63, 65, 0.95); border: none; border-radius: 4px 0 0 0;
377
+ | padding: 4px 8px;
356
378
  | line-height: 1.2; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
357
379
  | }
358
380
  | .version-info-version {
@@ -364,6 +386,62 @@ html
364
386
  | .version-info-version:hover {
365
387
  | color: #a9b7c6; text-decoration: none;
366
388
  | }
389
+ | /* TileGrid Panel Styles */
390
+ | .tilegrid-panel {
391
+ | position: fixed; top: 50%; right: 10px; transform: translateY(-50%); z-index: 1000;
392
+ | background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
393
+ | border-radius: 4px; padding: 12px; width: 200px;
394
+ | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
395
+ | }
396
+ | .tilegrid-header {
397
+ | display: flex; justify-content: space-between; align-items: center;
398
+ | margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #555555;
399
+ | }
400
+ | .tilegrid-title {
401
+ | color: #ffc66d; font-weight: bold; font-size: 12px;
402
+ | }
403
+ | .tilegrid-close {
404
+ | background: none; border: none; color: #808080; cursor: pointer;
405
+ | font-size: 14px; padding: 0; width: 16px; height: 16px;
406
+ | display: flex; align-items: center; justify-content: center;
407
+ | border-radius: 2px; transition: all 0.2s ease;
408
+ | }
409
+ | .tilegrid-close:hover {
410
+ | background: #4b4d4f; color: #a9b7c6;
411
+ | }
412
+ | .tilegrid-stats {
413
+ | margin-bottom: 12px; padding: 10px; background: #313335; border-radius: 3px;
414
+ | border: 1px solid #464749;
415
+ | }
416
+ | .tilegrid-stat-label {
417
+ | color: #808080; font-size: 10px; font-weight: 500; letter-spacing: 0.5px;
418
+ | text-transform: uppercase; margin-bottom: 4px;
419
+ | }
420
+ | .tilegrid-stat-value {
421
+ | color: #6897bb; font-size: 28px; font-weight: bold; line-height: 1;
422
+ | font-family: 'Courier New', monospace;
423
+ | }
424
+ | .tilegrid-controls {
425
+ | padding-top: 8px; border-top: 1px solid #555555;
426
+ | }
427
+ | .tilegrid-checkbox-label {
428
+ | display: flex; align-items: center; justify-content: space-between;
429
+ | cursor: pointer; user-select: none; color: #a9b7c6; font-size: 11px;
430
+ | }
431
+ | .tilegrid-checkbox-label input[type="checkbox"] {
432
+ | display: none;
433
+ | }
434
+ | .tilegrid-checkbox-custom {
435
+ | width: 16px; height: 16px; border: 1px solid #555555; border-radius: 2px;
436
+ | display: flex; align-items: center; justify-content: center;
437
+ | transition: all 0.2s ease; background: #3c3f41;
438
+ | }
439
+ | .tilegrid-checkbox-label input[type="checkbox"]:checked + .tilegrid-checkbox-custom {
440
+ | background: #6a9955; border-color: #7bb366;
441
+ | }
442
+ | .tilegrid-checkbox-label input[type="checkbox"]:checked + .tilegrid-checkbox-custom::after {
443
+ | content: '✓'; color: white; font-size: 10px; font-weight: bold;
444
+ | }
367
445
  body
368
446
  .container
369
447
  == yield
@@ -18,6 +18,7 @@ ruby:
18
18
  button.control-button onclick="toggleHoverMode()" id="hover-mode-btn" Hover Mode
19
19
  button.control-button onclick="toggleProfileMode()" id="profile-mode-btn" style="display: none;" Elevation Profile
20
20
  button.control-button onclick="toggleAntialias()" id="antialias-btn" Antialias
21
+ button.control-button onclick="toggleTileGrid()" id="tilegrid-mode-btn" Tile Grid
21
22
 
22
23
  #filters-panel.control-panel.active
23
24
  .control-panel-header
@@ -33,7 +34,7 @@ ruby:
33
34
  .control-panel-content
34
35
  #layer-buttons
35
36
  button.layer-controls-toggle id="layer-controls-toggle" onclick="toggleLayerControls()" title="Collapse/Expand Panel"
36
- span.toggle-icon
37
+ span.toggle-icon
37
38
 
38
39
  #map-container
39
40
  #map.map-layer data-style-url="#{style_url}"
@@ -42,9 +43,8 @@ ruby:
42
43
  a.version-info-version href="https://github.com/artyomb/maplibre-preview" target="_blank" v#{MapLibrePreview::VERSION}
43
44
 
44
45
  #performance-panel.performance-overlay
45
- button.performance-toggle onclick="togglePerformancePanel()" ×
46
46
  .performance-content
47
- .metric-row
47
+ .metric-row.performance-main-metrics
48
48
  .metric
49
49
  span.metric-label FPS:
50
50
  span.metric-value#fps-value 0
@@ -57,7 +57,7 @@ ruby:
57
57
  .metric
58
58
  span.metric-label Zoom:
59
59
  span.metric-value#zoom-level-value 0
60
- .metric-row
60
+ .metric-row.performance-secondary-metrics
61
61
  .metric
62
62
  span.metric-label Tiles:
63
63
  span.metric-value#tiles-loaded-value 0
@@ -71,6 +71,8 @@ ruby:
71
71
  .metric
72
72
  span.metric-label Terrain:
73
73
  span.metric-value#terrain-status-value -
74
+ button.performance-toggle onclick="togglePerformancePanel()" title="Collapse/Expand Panel"
75
+ span.performance-toggle-icon ◀
74
76
 
75
77
  javascript:
76
78
  const mapEl = document.getElementById('map');
@@ -90,6 +92,7 @@ javascript:
90
92
  let profileLine = null;
91
93
  let currentProfile = null;
92
94
  let contourManager = null;
95
+ let tileGridManager = null;
93
96
 
94
97
  const toDomId = (prefix, id) => `${prefix}-${String(id).replace(/[^a-zA-Z0-9_-]/g, '_')}`;
95
98
 
@@ -304,6 +307,16 @@ javascript:
304
307
  contourManager.init();
305
308
  };
306
309
 
310
+ const initializeTileGridManager = () => {
311
+ tileGridManager = new TileGridManager({map});
312
+ tileGridManager.init();
313
+ window.tileGridManager = tileGridManager;
314
+ };
315
+
316
+ const toggleTileGrid = () => {
317
+ tileGridManager?.toggle();
318
+ };
319
+
307
320
  const onStyleReady = () => {
308
321
  const hasTerrain = currentStyle?.terrain;
309
322
  const projectionType = hasTerrain ? 'mercator' : 'globe';
@@ -329,6 +342,7 @@ javascript:
329
342
  initializeContourManager();
330
343
  }
331
344
 
345
+ initializeTileGridManager();
332
346
  updateTerrainIndicator();
333
347
  };
334
348
 
@@ -744,12 +758,12 @@ javascript:
744
758
 
745
759
  let fpsCounter = 0, lastFpsTime = 0, frameCount = 0;
746
760
  let performanceMonitor = null;
747
- let performancePanelVisible = true;
761
+ let performancePanelCollapsed = false;
748
762
 
749
763
  const togglePerformancePanel = () => {
750
764
  const panel = document.getElementById('performance-panel');
751
- performancePanelVisible = !performancePanelVisible;
752
- panel.style.display = performancePanelVisible ? 'block' : 'none';
765
+ performancePanelCollapsed = !performancePanelCollapsed;
766
+ panel.classList.toggle('collapsed', performancePanelCollapsed);
753
767
  };
754
768
 
755
769
  const startPerformanceMonitoring = () => {
@@ -879,5 +893,7 @@ javascript:
879
893
  window.toggleAntialias = toggleAntialias;
880
894
  window.toggleLayerControls = toggleLayerControls;
881
895
  window.hideProfile = hideProfile;
896
+ window.toggleTileGrid = toggleTileGrid;
897
+ window.tileGridManager = null;
882
898
 
883
899
  initializeMap();
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maplibre-preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.9
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Ludov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-19 00:00:00.000000000 Z
11
+ date: 2025-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -183,6 +183,7 @@ files:
183
183
  - lib/maplibre-preview.rb
184
184
  - lib/maplibre-preview/public/js/contour.js
185
185
  - lib/maplibre-preview/public/js/filters.js
186
+ - lib/maplibre-preview/public/js/tilegrid.js
186
187
  - lib/maplibre-preview/version.rb
187
188
  - lib/maplibre-preview/views/maplibre_layout.slim
188
189
  - lib/maplibre-preview/views/maplibre_map.slim