RoyAalekh commited on
Commit
a26c316
·
1 Parent(s): 89d61f2

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

Files changed (6) hide show
  1. app.py +3 -7
  2. static/index.html +2 -2
  3. static/map.html +9 -9
  4. static/map.js +263 -156
  5. static/sw.js +1 -1
  6. 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
- # Add signed URLs for files
565
- processed_trees = []
566
- for tree in trees:
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 = '1761508675'; // Cache-busting bump
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=1761508675"></script>
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 = '1761508675'; // Current timestamp for cache busting
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 Categories:</h4>
1024
  <div class="legend-items">
1025
- <div class="legend-item"><span class="legend-color" style="background: #059669;"></span> Ficus (Banyan/Fig)</div>
1026
- <div class="legend-item"><span class="legend-color" style="background: #d97706;"></span> Mangifera (Mango)</div>
1027
- <div class="legend-item"><span class="legend-color" style="background: #16a34a;"></span> Bambusa (Bamboo)</div>
1028
- <div class="legend-item"><span class="legend-color" style="background: #84cc16;"></span> Azadirachta (Neem)</div>
1029
- <div class="legend-item"><span class="legend-color" style="background: #dc2626;"></span> Tectona (Teak)</div>
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=1761508675"></script>
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
- // Filter out specific trees (e.g., ID 18 as requested)
709
- const filteredTrees = allTrees.filter(tree => {
710
- if (tree.id === 18) {
711
- console.log('🔄 Excluding tree ID 18 from display');
712
- return false;
713
- }
714
- return true;
715
- });
716
 
717
- console.log(`📊 Total trees to display: ${filteredTrees.length}`);
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
- addTreeMarker(tree) {
765
- // Determine tree category and colors based on species
766
- const getTreeCategoryAndColors = (tree) => {
767
- const scientificName = (tree.scientific_name || '').toLowerCase();
768
- const commonName = (tree.common_name || '').toLowerCase();
769
- const localName = (tree.local_name || '').toLowerCase();
770
 
771
- // Categorize trees by families/types with distinct colors
772
- if (scientificName.includes('ficus') || commonName.includes('banyan') || commonName.includes('fig')) {
773
- return {
774
- category: 'Ficus (Banyan/Fig)',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
  colors: {
776
- canopy1: '#059669', // Emerald green for Ficus
777
  canopy2: '#047857',
778
  canopy3: '#065f46',
779
  trunk: '#7c6643',
780
  shadow: 'rgba(5, 150, 105, 0.3)'
781
  }
782
- };
783
- } else if (scientificName.includes('mangifera') || commonName.includes('mango')) {
784
- return {
785
- category: 'Mangifera (Mango)',
786
  colors: {
787
- canopy1: '#d97706', // Amber for Mango
788
- canopy2: '#b45309',
789
- canopy3: '#92400e',
790
  trunk: '#78716c',
791
- shadow: 'rgba(217, 119, 6, 0.3)'
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
- } else if (scientificName.includes('azadirachta') || commonName.includes('neem')) {
806
- return {
807
- category: 'Azadirachta (Neem)',
808
  colors: {
809
- canopy1: '#84cc16', // Lime green for Neem
810
- canopy2: '#65a30d',
811
- canopy3: '#4d7c0f',
812
- trunk: '#a8a29e',
813
- shadow: 'rgba(132, 204, 22, 0.3)'
814
  }
815
- };
816
- } else if (scientificName.includes('tectona') || commonName.includes('teak')) {
817
- return {
818
- category: 'Tectona (Teak)',
819
  colors: {
820
- canopy1: '#dc2626', // Red-brown for Teak
821
- canopy2: '#b91c1c',
822
- canopy3: '#991b1b',
823
- trunk: '#8b5a3c',
824
- shadow: 'rgba(220, 38, 38, 0.3)'
825
  }
826
- };
827
- } else {
828
- // Default for other/unknown species
829
- return {
830
- category: 'Other Species',
831
  colors: {
832
- canopy1: '#8ab070', // primary-500
833
- canopy2: '#739b5a', // primary-600
834
- canopy3: '#5d7f49', // primary-700
835
- trunk: '#7f5a44', // accent-700
836
- shadow: 'rgba(93, 127, 73, 0.3)'
837
  }
838
- };
839
- }
 
 
 
 
840
  };
841
 
842
- const treeInfo = getTreeCategoryAndColors(tree);
843
  const colors = treeInfo.colors;
844
 
845
- const treeIcon = L.divIcon({
846
- html: `
847
- <div class="map-marker tree-marker" style="filter: drop-shadow(2px 3px 6px ${colors.shadow});">
848
- <svg width="40" height="48" viewBox="0 0 40 48" fill="none" style="transition: transform 0.2s ease;">
849
- <!-- Tree Shadow/Base -->
850
- <ellipse cx="20" cy="46" rx="8" ry="2" fill="${colors.shadow}" opacity="0.4"/>
851
-
852
- <!-- Tree Trunk -->
853
- <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"/>
854
-
855
- <!-- Tree Trunk Texture -->
856
- <path d="M18.5 32 Q18.5 35 18.5 38" stroke="#5a3e32" stroke-width="0.3" opacity="0.6"/>
857
- <path d="M21.5 33 Q21.5 36 21.5 39" stroke="#5a3e32" stroke-width="0.3" opacity="0.6"/>
858
-
859
- <!-- Main Canopy (Back Layer) -->
860
- <circle cx="20" cy="22" r="12" fill="${colors.canopy3}" opacity="0.8"/>
861
-
862
- <!-- Secondary Canopy Clusters -->
863
- <circle cx="15" cy="20" r="8" fill="${colors.canopy2}" opacity="0.85"/>
864
- <circle cx="25" cy="19" r="7" fill="${colors.canopy2}" opacity="0.85"/>
865
- <circle cx="18" cy="15" r="6" fill="${colors.canopy1}" opacity="0.9"/>
866
- <circle cx="23" cy="25" r="6.5" fill="${colors.canopy2}" opacity="0.85"/>
867
-
868
- <!-- Top Canopy (Brightest) -->
869
- <circle cx="20" cy="18" r="7" fill="${colors.canopy1}"/>
870
-
871
- <!-- Highlight clusters for 3D effect -->
872
- <circle cx="16" cy="15" r="3" fill="#a8b9a0" opacity="0.7"/>
873
- <circle cx="24" cy="20" r="2.5" fill="#a8b9a0" opacity="0.6"/>
874
- <circle cx="18" cy="24" r="2" fill="#a8b9a0" opacity="0.5"/>
875
-
876
- <!-- Small light spots -->
877
- <circle cx="14" cy="14" r="1" fill="#c0d4b2" opacity="0.8"/>
878
- <circle cx="22" cy="16" r="0.8" fill="#c0d4b2" opacity="0.9"/>
879
- <circle cx="26" cy="22" r="0.6" fill="#c0d4b2" opacity="0.7"/>
880
-
881
- <!-- Optional: Small leaves/details -->
882
- <path d="M12 18 Q11 17 11.5 19 Q12.5 20 13 19" fill="${colors.canopy1}" opacity="0.6"/>
883
- <path d="M28 25 Q29 24 28.5 26 Q27.5 27 27 26" fill="${colors.canopy1}" opacity="0.6"/>
884
- </svg>
885
- </div>
886
- `,
887
- className: 'custom-marker-icon tree-pin realistic-tree',
888
- iconSize: [40, 48],
889
- iconAnchor: [20, 46],
890
- popupAnchor: [0, -48]
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 = 1761508675; // 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}`;
 
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": 1761508675
4
  }
 
1
  {
2
  "version": "5.1.1",
3
+ "timestamp": 1761509700
4
  }