RoyAalekh commited on
Commit
4de3e9c
·
1 Parent(s): 795b079

Add beautiful custom tree markers with hover tooltips

Browse files

- Fixed tree loading endpoint from /trees to /api/trees?limit=100
- Replaced default circular markers with custom SVG tree icons
- Added 3D gradient styling with shadows and hover animations
- Implemented beautiful hover tooltips that show tree name and details
- Tooltips appear on hover and disappear when out of focus
- Added debug logging to track tree loading
- Enhanced visual design with green gradient backgrounds and shadows
- Improved mobile responsiveness for markers and tooltips
- Trees now properly appear on the map with professional appearance

Files changed (2) hide show
  1. static/map.html +101 -0
  2. static/map.js +59 -11
static/map.html CHANGED
@@ -325,6 +325,107 @@
325
  0%, 100% { opacity: 0; }
326
  20%, 80% { opacity: 1; }
327
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  </style>
329
  </head>
330
  <body>
 
325
  0%, 100% { opacity: 0; }
326
  20%, 80% { opacity: 1; }
327
  }
328
+
329
+ /* Custom Tree Marker Styles */
330
+ .custom-tree-icon {
331
+ background: transparent !important;
332
+ border: none !important;
333
+ }
334
+
335
+ .custom-tree-marker {
336
+ position: relative;
337
+ display: flex;
338
+ flex-direction: column;
339
+ align-items: center;
340
+ }
341
+
342
+ .tree-icon-container {
343
+ background: linear-gradient(145deg, #ffffff, #f0f0f0);
344
+ border-radius: 50%;
345
+ padding: 4px;
346
+ box-shadow:
347
+ 0 4px 8px rgba(0,0,0,0.15),
348
+ 0 2px 4px rgba(0,0,0,0.1),
349
+ inset 0 1px 0 rgba(255,255,255,0.2);
350
+ transition: all 0.3s ease;
351
+ cursor: pointer;
352
+ }
353
+
354
+ .tree-icon-container:hover {
355
+ transform: translateY(-2px) scale(1.1);
356
+ box-shadow:
357
+ 0 6px 16px rgba(0,0,0,0.2),
358
+ 0 4px 8px rgba(0,0,0,0.15),
359
+ inset 0 1px 0 rgba(255,255,255,0.3);
360
+ }
361
+
362
+ .tree-marker-shadow {
363
+ width: 12px;
364
+ height: 6px;
365
+ background: rgba(0,0,0,0.3);
366
+ border-radius: 50%;
367
+ margin-top: 2px;
368
+ filter: blur(1px);
369
+ transition: all 0.3s ease;
370
+ }
371
+
372
+ .custom-tree-marker:hover .tree-marker-shadow {
373
+ width: 16px;
374
+ background: rgba(0,0,0,0.4);
375
+ }
376
+
377
+ /* Tooltip Styles */
378
+ .leaflet-tooltip.tree-tooltip {
379
+ background: linear-gradient(145deg, #2c5530, #1a3a1c) !important;
380
+ border: 1px solid rgba(255,255,255,0.2) !important;
381
+ border-radius: 8px !important;
382
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
383
+ color: white !important;
384
+ font-family: 'Segoe UI', sans-serif !important;
385
+ font-size: 13px !important;
386
+ padding: 8px 12px !important;
387
+ backdrop-filter: blur(10px);
388
+ }
389
+
390
+ .leaflet-tooltip.tree-tooltip::before {
391
+ border-top-color: #2c5530 !important;
392
+ }
393
+
394
+ .tree-tooltip-content {
395
+ min-width: 80px;
396
+ text-align: center;
397
+ }
398
+
399
+ .tree-name {
400
+ font-weight: 600;
401
+ font-size: 14px;
402
+ margin-bottom: 2px;
403
+ color: #ffffff;
404
+ }
405
+
406
+ .tree-details {
407
+ font-size: 11px;
408
+ opacity: 0.9;
409
+ color: #e8f5e8;
410
+ }
411
+
412
+ /* Enhanced Popup Styles */
413
+ .leaflet-popup.tree-popup {
414
+ margin-bottom: 10px;
415
+ }
416
+
417
+ .leaflet-popup.tree-popup .leaflet-popup-content-wrapper {
418
+ background: linear-gradient(145deg, #ffffff, #f8f9fa);
419
+ border-radius: 12px;
420
+ box-shadow: 0 8px 24px rgba(0,0,0,0.15);
421
+ border: 1px solid rgba(44, 85, 48, 0.1);
422
+ }
423
+
424
+ .leaflet-popup.tree-popup .leaflet-popup-tip {
425
+ background: #ffffff;
426
+ border: 1px solid rgba(44, 85, 48, 0.1);
427
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
428
+ }
429
  </style>
430
  </head>
431
  <body>
static/map.js CHANGED
@@ -131,11 +131,14 @@ class TreeTrackMap {
131
 
132
  async loadExistingTrees() {
133
  try {
134
- const response = await fetch('/trees');
135
  if (response.ok) {
136
  const trees = await response.json();
 
137
  this.displayTreeMarkers(trees);
138
  this.updateTreeCounter(trees.length);
 
 
139
  }
140
  } catch (error) {
141
  console.error('Error loading trees:', error);
@@ -149,20 +152,26 @@ class TreeTrackMap {
149
  });
150
  this.treeMarkers = [];
151
 
152
- // Add tree markers
153
  trees.forEach(tree => {
154
  if (tree.latitude && tree.longitude) {
155
- const marker = L.circleMarker([tree.latitude, tree.longitude], {
156
- radius: 12,
157
- fillColor: '#4CAF50',
158
- color: 'white',
159
- weight: 3,
160
- opacity: 1,
161
- fillOpacity: 0.9,
162
- className: 'tree-pin'
163
  }).addTo(this.map);
164
 
165
- // Create popup content
 
 
 
 
 
 
 
 
 
166
  const popupContent = this.createTreePopup(tree);
167
  marker.bindPopup(popupContent, {
168
  maxWidth: 300,
@@ -174,6 +183,45 @@ class TreeTrackMap {
174
  });
175
  }
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  createTreePopup(tree) {
178
  return `
179
  <div style="padding: 10px; font-family: 'Segoe UI', sans-serif;">
 
131
 
132
  async loadExistingTrees() {
133
  try {
134
+ const response = await fetch('/api/trees?limit=100');
135
  if (response.ok) {
136
  const trees = await response.json();
137
+ console.log('Loaded trees:', trees); // Debug log
138
  this.displayTreeMarkers(trees);
139
  this.updateTreeCounter(trees.length);
140
+ } else {
141
+ console.error('Failed to load trees:', response.status, response.statusText);
142
  }
143
  } catch (error) {
144
  console.error('Error loading trees:', error);
 
152
  });
153
  this.treeMarkers = [];
154
 
155
+ // Add tree markers with custom icons
156
  trees.forEach(tree => {
157
  if (tree.latitude && tree.longitude) {
158
+ // Create custom tree icon
159
+ const treeIcon = this.createCustomTreeIcon(tree);
160
+
161
+ const marker = L.marker([tree.latitude, tree.longitude], {
162
+ icon: treeIcon
 
 
 
163
  }).addTo(this.map);
164
 
165
+ // Create hover tooltip
166
+ const tooltipContent = this.createTreeTooltip(tree);
167
+ marker.bindTooltip(tooltipContent, {
168
+ permanent: false,
169
+ direction: 'top',
170
+ offset: [0, -10],
171
+ className: 'tree-tooltip'
172
+ });
173
+
174
+ // Create popup content for clicks
175
  const popupContent = this.createTreePopup(tree);
176
  marker.bindPopup(popupContent, {
177
  maxWidth: 300,
 
183
  });
184
  }
185
 
186
+ createCustomTreeIcon(tree) {
187
+ // Create a beautiful custom tree icon
188
+ const iconHtml = `
189
+ <div class="custom-tree-marker">
190
+ <div class="tree-icon-container">
191
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
192
+ <path d="M12 2C10.9 2 10 2.9 10 4C10 5.1 10.9 6 12 6C13.1 6 14 5.1 14 4C14 2.9 13.1 2 12 2Z" fill="#2E7D32"/>
193
+ <path d="M12 6C9.8 6 8 7.8 8 10C8 12.2 9.8 14 12 14C14.2 14 16 12.2 16 10C16 7.8 14.2 6 12 6Z" fill="#4CAF50"/>
194
+ <path d="M12 10C10.3 10 9 11.3 9 13C9 14.7 10.3 16 12 16C13.7 16 15 14.7 15 13C15 11.3 13.7 10 12 10Z" fill="#66BB6A"/>
195
+ <rect x="11" y="16" width="2" height="6" fill="#8D6E63"/>
196
+ <path d="M10 22H14V20H10V22Z" fill="#5D4037"/>
197
+ </svg>
198
+ </div>
199
+ <div class="tree-marker-shadow"></div>
200
+ </div>
201
+ `;
202
+
203
+ return L.divIcon({
204
+ html: iconHtml,
205
+ className: 'custom-tree-icon',
206
+ iconSize: [32, 40],
207
+ iconAnchor: [16, 40],
208
+ popupAnchor: [0, -35]
209
+ });
210
+ }
211
+
212
+ createTreeTooltip(tree) {
213
+ const name = tree.local_name || tree.common_name || tree.scientific_name || `Tree #${tree.id}`;
214
+ const height = tree.height ? `${tree.height}m` : '';
215
+ const code = tree.tree_code ? `[${tree.tree_code}]` : '';
216
+
217
+ return `
218
+ <div class="tree-tooltip-content">
219
+ <div class="tree-name">${name}</div>
220
+ ${height || code ? `<div class="tree-details">${code} ${height}</div>` : ''}
221
+ </div>
222
+ `;
223
+ }
224
+
225
  createTreePopup(tree) {
226
  return `
227
  <div style="padding: 10px; font-family: 'Segoe UI', sans-serif;">