Spaces:
Runtime error
Runtime error
Implement ID-based color binning for tree markers and add multiple marker style options
Browse files- Replace species-based coloring with 5 rotating color groups based on tree IDs
- Add localStorage caching system to prevent reloading trees on every visit
- Implement 5 different marker styles: realistic-tree, simple-circle, pin-style, geometric, minimalist
- Update legend to show ID-based color groups instead of species categories
- Add cache management functions with 30-minute expiration
- Remove unnecessary cache status indicator from UI
- Colors cycle through: Forest Green, Ocean Blue, Sunset Orange, Royal Purple, Crimson Red
- app.py +3 -7
- static/index.html +2 -2
- static/map.html +9 -9
- static/map.js +263 -156
- static/sw.js +1 -1
- version.json +1 -1
app.py
CHANGED
|
@@ -561,13 +561,9 @@ async def get_trees(
|
|
| 561 |
try:
|
| 562 |
trees = await db.get_trees(limit=limit, offset=offset, species=species, health_status=health_status)
|
| 563 |
|
| 564 |
-
#
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
processed_tree = storage.process_tree_files(tree)
|
| 568 |
-
processed_trees.append(processed_tree)
|
| 569 |
-
|
| 570 |
-
return processed_trees
|
| 571 |
|
| 572 |
except RuntimeError as e:
|
| 573 |
if "Database not connected" in str(e):
|
|
|
|
| 561 |
try:
|
| 562 |
trees = await db.get_trees(limit=limit, offset=offset, species=species, health_status=health_status)
|
| 563 |
|
| 564 |
+
# For map performance: Skip image processing for bulk tree requests
|
| 565 |
+
# Images are not needed for map markers and clustering
|
| 566 |
+
return trees
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
|
| 568 |
except RuntimeError as e:
|
| 569 |
if "Database not connected" in str(e):
|
static/index.html
CHANGED
|
@@ -947,7 +947,7 @@
|
|
| 947 |
// Force refresh if we detect cached version
|
| 948 |
(function() {
|
| 949 |
const currentVersion = '5.1.1';
|
| 950 |
-
const timestamp = '
|
| 951 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 952 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
| 953 |
|
|
@@ -1193,7 +1193,7 @@
|
|
| 1193 |
</div>
|
| 1194 |
</div>
|
| 1195 |
|
| 1196 |
-
<script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=
|
| 1197 |
|
| 1198 |
<script>
|
| 1199 |
// Idle-time prefetch of map assets to speed up first navigation
|
|
|
|
| 947 |
// Force refresh if we detect cached version
|
| 948 |
(function() {
|
| 949 |
const currentVersion = '5.1.1';
|
| 950 |
+
const timestamp = '1761509700'; // Cache-busting bump
|
| 951 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 952 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
| 953 |
|
|
|
|
| 1193 |
</div>
|
| 1194 |
</div>
|
| 1195 |
|
| 1196 |
+
<script type="module" src="/static/js/tree-track-app.js?v=5.1.1&t=1761509700"></script>
|
| 1197 |
|
| 1198 |
<script>
|
| 1199 |
// Idle-time prefetch of map assets to speed up first navigation
|
static/map.html
CHANGED
|
@@ -950,7 +950,7 @@
|
|
| 950 |
// Force refresh if we detect cached version
|
| 951 |
(function() {
|
| 952 |
const currentVersion = '5.1.1';
|
| 953 |
-
const timestamp = '
|
| 954 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 955 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
| 956 |
|
|
@@ -1020,15 +1020,15 @@ const timestamp = '1761508675'; // Current timestamp for cache busting
|
|
| 1020 |
<p>Zoom in and out to explore data collected from various locations.</p>
|
| 1021 |
<p>Click on tree icons to view their names.</p>
|
| 1022 |
<div class="tree-legend">
|
| 1023 |
-
<h4>Tree
|
| 1024 |
<div class="legend-items">
|
| 1025 |
-
<div class="legend-item"><span class="legend-color" style="background: #059669;"></span>
|
| 1026 |
-
<div class="legend-item"><span class="legend-color" style="background: #
|
| 1027 |
-
<div class="legend-item"><span class="legend-color" style="background: #
|
| 1028 |
-
<div class="legend-item"><span class="legend-color" style="background: #
|
| 1029 |
-
<div class="legend-item"><span class="legend-color" style="background: #
|
| 1030 |
-
<div class="legend-item"><span class="legend-color" style="background: #8ab070;"></span> Other Species</div>
|
| 1031 |
</div>
|
|
|
|
| 1032 |
</div>
|
| 1033 |
</div>
|
| 1034 |
</div>
|
|
@@ -1103,7 +1103,7 @@ const timestamp = '1761508675'; // Current timestamp for cache busting
|
|
| 1103 |
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
| 1104 |
<!-- Leaflet MarkerCluster JS for performance and grouping -->
|
| 1105 |
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
| 1106 |
-
<script src="/static/map.js?v=5.1.1&t=
|
| 1107 |
<script>
|
| 1108 |
console.log('🗺️ Map script loaded successfully');
|
| 1109 |
</script>
|
|
|
|
| 950 |
// Force refresh if we detect cached version
|
| 951 |
(function() {
|
| 952 |
const currentVersion = '5.1.1';
|
| 953 |
+
const timestamp = '1761509700'; // Current timestamp for cache busting
|
| 954 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 955 |
const lastTimestamp = sessionStorage.getItem('treetrack_timestamp');
|
| 956 |
|
|
|
|
| 1020 |
<p>Zoom in and out to explore data collected from various locations.</p>
|
| 1021 |
<p>Click on tree icons to view their names.</p>
|
| 1022 |
<div class="tree-legend">
|
| 1023 |
+
<h4>Tree ID Color Groups:</h4>
|
| 1024 |
<div class="legend-items">
|
| 1025 |
+
<div class="legend-item"><span class="legend-color" style="background: #059669;"></span> Forest Green (Group 1)</div>
|
| 1026 |
+
<div class="legend-item"><span class="legend-color" style="background: #0ea5e9;"></span> Ocean Blue (Group 2)</div>
|
| 1027 |
+
<div class="legend-item"><span class="legend-color" style="background: #f97316;"></span> Sunset Orange (Group 3)</div>
|
| 1028 |
+
<div class="legend-item"><span class="legend-color" style="background: #a855f7;"></span> Royal Purple (Group 4)</div>
|
| 1029 |
+
<div class="legend-item"><span class="legend-color" style="background: #ef4444;"></span> Crimson Red (Group 5)</div>
|
|
|
|
| 1030 |
</div>
|
| 1031 |
+
<p style="font-size: 12px; color: #666; margin-top: 10px;">Colors are assigned based on Tree ID numbers in rotating groups</p>
|
| 1032 |
</div>
|
| 1033 |
</div>
|
| 1034 |
</div>
|
|
|
|
| 1103 |
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
| 1104 |
<!-- Leaflet MarkerCluster JS for performance and grouping -->
|
| 1105 |
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
| 1106 |
+
<script src="/static/map.js?v=5.1.1&t=1761509700"></script>
|
| 1107 |
<script>
|
| 1108 |
console.log('🗺️ Map script loaded successfully');
|
| 1109 |
</script>
|
static/map.js
CHANGED
|
@@ -647,6 +647,28 @@ class TreeTrackMap {
|
|
| 647 |
return;
|
| 648 |
}
|
| 649 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
// Load trees in batches (restored simple approach)
|
| 651 |
let allTrees = [];
|
| 652 |
let offset = 0;
|
|
@@ -705,190 +727,275 @@ class TreeTrackMap {
|
|
| 705 |
}
|
| 706 |
}
|
| 707 |
|
| 708 |
-
//
|
| 709 |
-
const
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
}
|
| 714 |
-
return true;
|
| 715 |
-
});
|
| 716 |
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
// Add tree markers with optimized batch processing
|
| 720 |
-
let loadedCount = 0;
|
| 721 |
-
const uiBatchSize = 100; // UI batch size for smooth rendering
|
| 722 |
-
|
| 723 |
-
for (let i = 0; i < filteredTrees.length; i += uiBatchSize) {
|
| 724 |
-
const batch = filteredTrees.slice(i, i + uiBatchSize);
|
| 725 |
-
|
| 726 |
-
// Process batch with small delay to prevent UI blocking
|
| 727 |
-
setTimeout(() => {
|
| 728 |
-
batch.forEach((tree) => {
|
| 729 |
-
try {
|
| 730 |
-
this.addTreeMarker(tree);
|
| 731 |
-
loadedCount++;
|
| 732 |
-
} catch (error) {
|
| 733 |
-
console.warn(`Failed to add marker for tree ${tree.id}:`, error);
|
| 734 |
-
}
|
| 735 |
-
});
|
| 736 |
-
|
| 737 |
-
// Update progress
|
| 738 |
-
document.getElementById('treeCount').textContent = `${loadedCount} trees`;
|
| 739 |
-
|
| 740 |
-
// If this is the last batch, finalize
|
| 741 |
-
if (i + uiBatchSize >= filteredTrees.length) {
|
| 742 |
-
console.log(`🎉 Map loading complete: ${loadedCount} tree markers added`);
|
| 743 |
-
this.showMessage(`Loaded ${loadedCount} trees successfully`, 'success');
|
| 744 |
-
|
| 745 |
-
// Auto-center on trees with clustering view
|
| 746 |
-
setTimeout(() => {
|
| 747 |
-
if (loadedCount > 0) {
|
| 748 |
-
const group = new L.featureGroup(this.treeMarkers);
|
| 749 |
-
const bounds = group.getBounds();
|
| 750 |
-
this.map.fitBounds(bounds.pad(0.1), { maxZoom: 15 });
|
| 751 |
-
console.log('🎯 Map auto-centered with clustering view');
|
| 752 |
-
}
|
| 753 |
-
}, 100);
|
| 754 |
-
}
|
| 755 |
-
}, (i / uiBatchSize) * 50); // Stagger UI batches by 50ms
|
| 756 |
-
}
|
| 757 |
|
| 758 |
} catch (error) {
|
| 759 |
console.error('❌ Error loading trees:', error);
|
| 760 |
this.showMessage('Failed to load trees. Please refresh the page.', 'error');
|
| 761 |
}
|
| 762 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
const
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
const
|
| 770 |
|
| 771 |
-
//
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
colors: {
|
| 776 |
-
canopy1: '#059669', // Emerald green
|
| 777 |
canopy2: '#047857',
|
| 778 |
canopy3: '#065f46',
|
| 779 |
trunk: '#7c6643',
|
| 780 |
shadow: 'rgba(5, 150, 105, 0.3)'
|
| 781 |
}
|
| 782 |
-
}
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
category: 'Mangifera (Mango)',
|
| 786 |
colors: {
|
| 787 |
-
canopy1: '#
|
| 788 |
-
canopy2: '#
|
| 789 |
-
canopy3: '#
|
| 790 |
trunk: '#78716c',
|
| 791 |
-
shadow: 'rgba(
|
| 792 |
-
}
|
| 793 |
-
};
|
| 794 |
-
} else if (scientificName.includes('bambusa') || commonName.includes('bamboo')) {
|
| 795 |
-
return {
|
| 796 |
-
category: 'Bambusa (Bamboo)',
|
| 797 |
-
colors: {
|
| 798 |
-
canopy1: '#16a34a', // Green for Bamboo
|
| 799 |
-
canopy2: '#15803d',
|
| 800 |
-
canopy3: '#166534',
|
| 801 |
-
trunk: '#6b7280',
|
| 802 |
-
shadow: 'rgba(22, 163, 74, 0.3)'
|
| 803 |
}
|
| 804 |
-
}
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
category: 'Azadirachta (Neem)',
|
| 808 |
colors: {
|
| 809 |
-
canopy1: '#
|
| 810 |
-
canopy2: '#
|
| 811 |
-
canopy3: '#
|
| 812 |
-
trunk: '#
|
| 813 |
-
shadow: 'rgba(
|
| 814 |
}
|
| 815 |
-
}
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
category: 'Tectona (Teak)',
|
| 819 |
colors: {
|
| 820 |
-
canopy1: '#
|
| 821 |
-
canopy2: '#
|
| 822 |
-
canopy3: '#
|
| 823 |
-
trunk: '#
|
| 824 |
-
shadow: 'rgba(
|
| 825 |
}
|
| 826 |
-
}
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
return {
|
| 830 |
-
category: 'Other Species',
|
| 831 |
colors: {
|
| 832 |
-
canopy1: '#
|
| 833 |
-
canopy2: '#
|
| 834 |
-
canopy3: '#
|
| 835 |
-
trunk: '#
|
| 836 |
-
shadow: 'rgba(
|
| 837 |
}
|
| 838 |
-
}
|
| 839 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
};
|
| 841 |
|
| 842 |
-
const treeInfo =
|
| 843 |
const colors = treeInfo.colors;
|
| 844 |
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
<
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
-
<
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
<
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
<
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 892 |
|
| 893 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon });
|
| 894 |
|
|
|
|
| 647 |
return;
|
| 648 |
}
|
| 649 |
|
| 650 |
+
// Check for cached tree data first
|
| 651 |
+
const cacheKey = `treetrack_trees_${this.currentUser.username}`;
|
| 652 |
+
const cachedData = localStorage.getItem(cacheKey);
|
| 653 |
+
const cacheTimestamp = localStorage.getItem(`${cacheKey}_timestamp`);
|
| 654 |
+
|
| 655 |
+
// Use cached data if it's less than 30 minutes old
|
| 656 |
+
const cacheAge = Date.now() - parseInt(cacheTimestamp || '0');
|
| 657 |
+
const maxCacheAge = 30 * 60 * 1000; // 30 minutes
|
| 658 |
+
|
| 659 |
+
if (cachedData && cacheAge < maxCacheAge) {
|
| 660 |
+
console.log('⚡ Using cached tree data from localStorage');
|
| 661 |
+
this.showCacheStatus(true, cacheAge);
|
| 662 |
+
const allTrees = JSON.parse(cachedData);
|
| 663 |
+
return this.renderCachedTrees(allTrees);
|
| 664 |
+
} else {
|
| 665 |
+
console.log('📊 Cache miss or expired, loading fresh data...');
|
| 666 |
+
if (cachedData) {
|
| 667 |
+
localStorage.removeItem(cacheKey);
|
| 668 |
+
localStorage.removeItem(`${cacheKey}_timestamp`);
|
| 669 |
+
}
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
// Load trees in batches (restored simple approach)
|
| 673 |
let allTrees = [];
|
| 674 |
let offset = 0;
|
|
|
|
| 727 |
}
|
| 728 |
}
|
| 729 |
|
| 730 |
+
// Cache the fresh tree data
|
| 731 |
+
const cacheKey = `treetrack_trees_${this.currentUser.username}`;
|
| 732 |
+
localStorage.setItem(cacheKey, JSON.stringify(allTrees));
|
| 733 |
+
localStorage.setItem(`${cacheKey}_timestamp`, Date.now().toString());
|
| 734 |
+
console.log(`💾 Cached ${allTrees.length} trees to localStorage`);
|
|
|
|
|
|
|
|
|
|
| 735 |
|
| 736 |
+
// Use the new rendering method
|
| 737 |
+
this.renderTreesToMap(allTrees);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
|
| 739 |
} catch (error) {
|
| 740 |
console.error('❌ Error loading trees:', error);
|
| 741 |
this.showMessage('Failed to load trees. Please refresh the page.', 'error');
|
| 742 |
}
|
| 743 |
}
|
| 744 |
+
|
| 745 |
+
async renderCachedTrees(allTrees) {
|
| 746 |
+
console.log(`⚡ Rendering ${allTrees.length} cached trees...`);
|
| 747 |
+
|
| 748 |
+
try {
|
| 749 |
+
this.renderTreesToMap(allTrees);
|
| 750 |
+
console.log(`🎉 Successfully rendered ${allTrees.length} cached trees`);
|
| 751 |
+
this.showMessage(`Loaded ${allTrees.length} trees from cache`, 'success');
|
| 752 |
+
|
| 753 |
+
} catch (error) {
|
| 754 |
+
console.error('❌ Failed to render cached trees:', error);
|
| 755 |
+
this.showMessage('Failed to load cached trees. Fetching fresh data...', 'error');
|
| 756 |
+
|
| 757 |
+
// Clear bad cache and try fresh load
|
| 758 |
+
const cacheKey = `treetrack_trees_${this.currentUser.username}`;
|
| 759 |
+
localStorage.removeItem(cacheKey);
|
| 760 |
+
localStorage.removeItem(`${cacheKey}_timestamp`);
|
| 761 |
+
|
| 762 |
+
// Retry with fresh data
|
| 763 |
+
return this.loadTrees();
|
| 764 |
+
}
|
| 765 |
+
}
|
| 766 |
+
|
| 767 |
+
renderTreesToMap(allTrees) {
|
| 768 |
+
// Filter out specific trees (e.g., ID 18 as requested)
|
| 769 |
+
const filteredTrees = allTrees.filter(tree => {
|
| 770 |
+
if (tree.id === 18) {
|
| 771 |
+
console.log('🔄 Excluding tree ID 18 from display');
|
| 772 |
+
return false;
|
| 773 |
+
}
|
| 774 |
+
return true;
|
| 775 |
+
});
|
| 776 |
+
|
| 777 |
+
console.log(`📊 Total trees to display: ${filteredTrees.length}`);
|
| 778 |
|
| 779 |
+
// Add tree markers with optimized batch processing
|
| 780 |
+
let loadedCount = 0;
|
| 781 |
+
const uiBatchSize = 100; // UI batch size for smooth rendering
|
| 782 |
+
|
| 783 |
+
for (let i = 0; i < filteredTrees.length; i += uiBatchSize) {
|
| 784 |
+
const batch = filteredTrees.slice(i, i + uiBatchSize);
|
| 785 |
|
| 786 |
+
// Process batch with small delay to prevent UI blocking
|
| 787 |
+
setTimeout(() => {
|
| 788 |
+
batch.forEach((tree) => {
|
| 789 |
+
try {
|
| 790 |
+
this.addTreeToMap(tree);
|
| 791 |
+
loadedCount++;
|
| 792 |
+
} catch (error) {
|
| 793 |
+
console.warn(`Failed to add marker for tree ${tree.id}:`, error);
|
| 794 |
+
}
|
| 795 |
+
});
|
| 796 |
+
|
| 797 |
+
// Update progress
|
| 798 |
+
if (document.getElementById('treeCount')) {
|
| 799 |
+
document.getElementById('treeCount').textContent = `${loadedCount} trees`;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
// If this is the last batch, finalize
|
| 803 |
+
if (i + uiBatchSize >= filteredTrees.length) {
|
| 804 |
+
console.log(`🎉 Map loading complete: ${loadedCount} tree markers added`);
|
| 805 |
+
|
| 806 |
+
// Auto-center on trees with clustering view
|
| 807 |
+
setTimeout(() => {
|
| 808 |
+
if (loadedCount > 0) {
|
| 809 |
+
const group = new L.featureGroup(this.treeMarkers);
|
| 810 |
+
const bounds = group.getBounds();
|
| 811 |
+
this.map.fitBounds(bounds.pad(0.1), { maxZoom: 15 });
|
| 812 |
+
console.log('🎯 Map auto-centered with clustering view');
|
| 813 |
+
}
|
| 814 |
+
}, 100);
|
| 815 |
+
}
|
| 816 |
+
}, (i / uiBatchSize) * 50); // Stagger UI batches by 50ms
|
| 817 |
+
}
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
// Method to clear cached tree data (useful for debugging or force refresh)
|
| 821 |
+
clearTreeCache() {
|
| 822 |
+
if (this.currentUser) {
|
| 823 |
+
const cacheKey = `treetrack_trees_${this.currentUser.username}`;
|
| 824 |
+
localStorage.removeItem(cacheKey);
|
| 825 |
+
localStorage.removeItem(`${cacheKey}_timestamp`);
|
| 826 |
+
console.log('🗑️ Tree cache cleared');
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
addTreeToMap(tree) {
|
| 831 |
+
// Determine tree colors based on ID bins (5 color categories)
|
| 832 |
+
const getColorsByID = (treeId) => {
|
| 833 |
+
// Define 5 distinct color palettes for ID-based binning
|
| 834 |
+
const colorPalettes = [
|
| 835 |
+
{
|
| 836 |
+
name: 'Forest Green',
|
| 837 |
colors: {
|
| 838 |
+
canopy1: '#059669', // Emerald green
|
| 839 |
canopy2: '#047857',
|
| 840 |
canopy3: '#065f46',
|
| 841 |
trunk: '#7c6643',
|
| 842 |
shadow: 'rgba(5, 150, 105, 0.3)'
|
| 843 |
}
|
| 844 |
+
},
|
| 845 |
+
{
|
| 846 |
+
name: 'Ocean Blue',
|
|
|
|
| 847 |
colors: {
|
| 848 |
+
canopy1: '#0ea5e9', // Sky blue
|
| 849 |
+
canopy2: '#0284c7',
|
| 850 |
+
canopy3: '#0369a1',
|
| 851 |
trunk: '#78716c',
|
| 852 |
+
shadow: 'rgba(14, 165, 233, 0.3)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 853 |
}
|
| 854 |
+
},
|
| 855 |
+
{
|
| 856 |
+
name: 'Sunset Orange',
|
|
|
|
| 857 |
colors: {
|
| 858 |
+
canopy1: '#f97316', // Orange
|
| 859 |
+
canopy2: '#ea580c',
|
| 860 |
+
canopy3: '#c2410c',
|
| 861 |
+
trunk: '#92400e',
|
| 862 |
+
shadow: 'rgba(249, 115, 22, 0.3)'
|
| 863 |
}
|
| 864 |
+
},
|
| 865 |
+
{
|
| 866 |
+
name: 'Royal Purple',
|
|
|
|
| 867 |
colors: {
|
| 868 |
+
canopy1: '#a855f7', // Purple
|
| 869 |
+
canopy2: '#9333ea',
|
| 870 |
+
canopy3: '#7c3aed',
|
| 871 |
+
trunk: '#6b21a8',
|
| 872 |
+
shadow: 'rgba(168, 85, 247, 0.3)'
|
| 873 |
}
|
| 874 |
+
},
|
| 875 |
+
{
|
| 876 |
+
name: 'Crimson Red',
|
|
|
|
|
|
|
| 877 |
colors: {
|
| 878 |
+
canopy1: '#ef4444', // Red
|
| 879 |
+
canopy2: '#dc2626',
|
| 880 |
+
canopy3: '#b91c1c',
|
| 881 |
+
trunk: '#991b1b',
|
| 882 |
+
shadow: 'rgba(239, 68, 68, 0.3)'
|
| 883 |
}
|
| 884 |
+
}
|
| 885 |
+
];
|
| 886 |
+
|
| 887 |
+
// Calculate which bin this ID falls into (modulo for cycling)
|
| 888 |
+
const binIndex = (treeId - 1) % colorPalettes.length;
|
| 889 |
+
return colorPalettes[binIndex];
|
| 890 |
};
|
| 891 |
|
| 892 |
+
const treeInfo = getColorsByID(tree.id);
|
| 893 |
const colors = treeInfo.colors;
|
| 894 |
|
| 895 |
+
// Choose marker style - you can change this to test different options
|
| 896 |
+
const markerStyle = 'realistic-tree'; // Options: 'realistic-tree', 'simple-circle', 'pin-style', 'geometric', 'minimalist'
|
| 897 |
+
|
| 898 |
+
let treeIcon;
|
| 899 |
+
|
| 900 |
+
if (markerStyle === 'simple-circle') {
|
| 901 |
+
treeIcon = L.divIcon({
|
| 902 |
+
html: `<div style="width: 20px; height: 20px; background: ${colors.canopy1}; border: 3px solid white; border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"></div>`,
|
| 903 |
+
className: 'custom-marker-icon simple-circle',
|
| 904 |
+
iconSize: [20, 20],
|
| 905 |
+
iconAnchor: [10, 10],
|
| 906 |
+
popupAnchor: [0, -10]
|
| 907 |
+
});
|
| 908 |
+
} else if (markerStyle === 'pin-style') {
|
| 909 |
+
treeIcon = L.divIcon({
|
| 910 |
+
html: `
|
| 911 |
+
<div class="map-marker pin-marker" style="color: ${colors.canopy1};">
|
| 912 |
+
<svg width="25" height="35" viewBox="0 0 25 35" fill="none">
|
| 913 |
+
<path d="M12.5 0C5.6 0 0 5.6 0 12.5C0 21.9 12.5 35 12.5 35C12.5 35 25 21.9 25 12.5C25 5.6 19.4 0 12.5 0Z" fill="${colors.canopy1}" stroke="white" stroke-width="2"/>
|
| 914 |
+
<circle cx="12.5" cy="12.5" r="5" fill="white"/>
|
| 915 |
+
<text x="12.5" y="17" text-anchor="middle" font-family="Arial" font-size="8" fill="${colors.canopy1}">T</text>
|
| 916 |
+
</svg>
|
| 917 |
+
</div>
|
| 918 |
+
`,
|
| 919 |
+
className: 'custom-marker-icon pin-style',
|
| 920 |
+
iconSize: [25, 35],
|
| 921 |
+
iconAnchor: [12, 35],
|
| 922 |
+
popupAnchor: [0, -35]
|
| 923 |
+
});
|
| 924 |
+
} else if (markerStyle === 'geometric') {
|
| 925 |
+
treeIcon = L.divIcon({
|
| 926 |
+
html: `
|
| 927 |
+
<div class="map-marker geometric-marker">
|
| 928 |
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
| 929 |
+
<polygon points="12,2 22,20 2,20" fill="${colors.canopy1}" stroke="white" stroke-width="2"/>
|
| 930 |
+
<circle cx="12" cy="16" r="2" fill="white"/>
|
| 931 |
+
</svg>
|
| 932 |
+
</div>
|
| 933 |
+
`,
|
| 934 |
+
className: 'custom-marker-icon geometric',
|
| 935 |
+
iconSize: [24, 24],
|
| 936 |
+
iconAnchor: [12, 24],
|
| 937 |
+
popupAnchor: [0, -24]
|
| 938 |
+
});
|
| 939 |
+
} else if (markerStyle === 'minimalist') {
|
| 940 |
+
treeIcon = L.divIcon({
|
| 941 |
+
html: `
|
| 942 |
+
<div style="width: 16px; height: 16px; background: ${colors.canopy1}; border: 2px solid white; border-radius: 3px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; font-size: 8px; color: white; font-weight: bold;">T</div>
|
| 943 |
+
`,
|
| 944 |
+
className: 'custom-marker-icon minimalist',
|
| 945 |
+
iconSize: [16, 16],
|
| 946 |
+
iconAnchor: [8, 8],
|
| 947 |
+
popupAnchor: [0, -8]
|
| 948 |
+
});
|
| 949 |
+
} else {
|
| 950 |
+
// Default realistic tree style
|
| 951 |
+
treeIcon = L.divIcon({
|
| 952 |
+
html: `
|
| 953 |
+
<div class="map-marker tree-marker" style="filter: drop-shadow(2px 3px 6px ${colors.shadow});">
|
| 954 |
+
<svg width="40" height="48" viewBox="0 0 40 48" fill="none" style="transition: transform 0.2s ease;">
|
| 955 |
+
<!-- Tree Shadow/Base -->
|
| 956 |
+
<ellipse cx="20" cy="46" rx="8" ry="2" fill="${colors.shadow}" opacity="0.4"/>
|
| 957 |
+
|
| 958 |
+
<!-- Tree Trunk -->
|
| 959 |
+
<path d="M17 38 Q17 40 17.5 42 Q18 44 19 45 Q19.5 45.5 20 45.5 Q20.5 45.5 21 45 Q22 44 22.5 42 Q23 40 23 38 L23 32 Q22.8 31 22 30.5 Q21 30 20 30 Q19 30 18 30.5 Q17.2 31 17 32 Z" fill="${colors.trunk}" stroke="#6b4a39" stroke-width="0.5"/>
|
| 960 |
+
|
| 961 |
+
<!-- Tree Trunk Texture -->
|
| 962 |
+
<path d="M18.5 32 Q18.5 35 18.5 38" stroke="#5a3e32" stroke-width="0.3" opacity="0.6"/>
|
| 963 |
+
<path d="M21.5 33 Q21.5 36 21.5 39" stroke="#5a3e32" stroke-width="0.3" opacity="0.6"/>
|
| 964 |
+
|
| 965 |
+
<!-- Main Canopy (Back Layer) -->
|
| 966 |
+
<circle cx="20" cy="22" r="12" fill="${colors.canopy3}" opacity="0.8"/>
|
| 967 |
+
|
| 968 |
+
<!-- Secondary Canopy Clusters -->
|
| 969 |
+
<circle cx="15" cy="20" r="8" fill="${colors.canopy2}" opacity="0.85"/>
|
| 970 |
+
<circle cx="25" cy="19" r="7" fill="${colors.canopy2}" opacity="0.85"/>
|
| 971 |
+
<circle cx="18" cy="15" r="6" fill="${colors.canopy1}" opacity="0.9"/>
|
| 972 |
+
<circle cx="23" cy="25" r="6.5" fill="${colors.canopy2}" opacity="0.85"/>
|
| 973 |
+
|
| 974 |
+
<!-- Top Canopy (Brightest) -->
|
| 975 |
+
<circle cx="20" cy="18" r="7" fill="${colors.canopy1}"/>
|
| 976 |
+
|
| 977 |
+
<!-- Highlight clusters for 3D effect -->
|
| 978 |
+
<circle cx="16" cy="15" r="3" fill="#a8b9a0" opacity="0.7"/>
|
| 979 |
+
<circle cx="24" cy="20" r="2.5" fill="#a8b9a0" opacity="0.6"/>
|
| 980 |
+
<circle cx="18" cy="24" r="2" fill="#a8b9a0" opacity="0.5"/>
|
| 981 |
+
|
| 982 |
+
<!-- Small light spots -->
|
| 983 |
+
<circle cx="14" cy="14" r="1" fill="#c0d4b2" opacity="0.8"/>
|
| 984 |
+
<circle cx="22" cy="16" r="0.8" fill="#c0d4b2" opacity="0.9"/>
|
| 985 |
+
<circle cx="26" cy="22" r="0.6" fill="#c0d4b2" opacity="0.7"/>
|
| 986 |
+
|
| 987 |
+
<!-- Optional: Small leaves/details -->
|
| 988 |
+
<path d="M12 18 Q11 17 11.5 19 Q12.5 20 13 19" fill="${colors.canopy1}" opacity="0.6"/>
|
| 989 |
+
<path d="M28 25 Q29 24 28.5 26 Q27.5 27 27 26" fill="${colors.canopy1}" opacity="0.6"/>
|
| 990 |
+
</svg>
|
| 991 |
+
</div>
|
| 992 |
+
`,
|
| 993 |
+
className: 'custom-marker-icon tree-pin realistic-tree',
|
| 994 |
+
iconSize: [40, 48],
|
| 995 |
+
iconAnchor: [20, 46],
|
| 996 |
+
popupAnchor: [0, -48]
|
| 997 |
+
});
|
| 998 |
+
}
|
| 999 |
|
| 1000 |
const marker = L.marker([tree.latitude, tree.longitude], { icon: treeIcon });
|
| 1001 |
|
static/sw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
// TreeTrack Service Worker - PWA and Offline Support
|
| 2 |
-
const VERSION =
|
| 3 |
const CACHE_NAME = `treetrack-v${VERSION}`;
|
| 4 |
const STATIC_CACHE = `static-v${VERSION}`;
|
| 5 |
const API_CACHE = `api-v${VERSION}`;
|
|
|
|
| 1 |
// TreeTrack Service Worker - PWA and Offline Support
|
| 2 |
+
const VERSION = 1761509700; // Cache busting bump - force clients to fetch new static assets and header image change
|
| 3 |
const CACHE_NAME = `treetrack-v${VERSION}`;
|
| 4 |
const STATIC_CACHE = `static-v${VERSION}`;
|
| 5 |
const API_CACHE = `api-v${VERSION}`;
|
version.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
{
|
| 2 |
"version": "5.1.1",
|
| 3 |
-
"timestamp":
|
| 4 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"version": "5.1.1",
|
| 3 |
+
"timestamp": 1761509700
|
| 4 |
}
|