diff --git a/webapps/geojson_viewer.html b/webapps/geojson_viewer.html index df29392..8b766d6 100644 --- a/webapps/geojson_viewer.html +++ b/webapps/geojson_viewer.html @@ -766,6 +766,14 @@ @@ -791,18 +799,11 @@
-
@@ -842,8 +843,7 @@ let labelsLayer = null; let currentGeojsonData = null; let featuresList = []; - let showLabels = true; - let labelPosition = 'center'; // 'center' or 'side' + let showLabels = false; // Toggle map layer const mapToggle = document.getElementById('mapToggle'); @@ -869,24 +869,16 @@ const labelsToggle = document.getElementById('labelsToggle'); labelsToggle.addEventListener('change', () => { showLabels = labelsToggle.checked; - if (labelsLayer) { - if (showLabels) { - labelsLayer.addTo(map); - } else { - map.removeLayer(labelsLayer); - } + if (!labelsLayer) { + console.warn('Labels layer not initialized'); + return; } - }); - - // Toggle label position - const labelPositionToggle = document.getElementById('labelPositionToggle'); - const labelPositionText = document.getElementById('labelPositionText'); - labelPositionToggle.addEventListener('change', () => { - labelPosition = labelPositionToggle.checked ? 'side' : 'center'; - labelPositionText.textContent = labelPositionToggle.checked ? 'Side' : 'Center'; - // Recreate labels with new position - if (currentGeojsonData) { - loadGeojson(currentGeojsonData, ''); + + if (showLabels) { + labelsLayer.addTo(map); + updateLabelsVisibility(); + } else { + map.removeLayer(labelsLayer); } }); @@ -914,6 +906,7 @@ const totalFieldsEl = document.getElementById('totalFields'); const totalHectaresEl = document.getElementById('totalHectares'); const totalAcresEl = document.getElementById('totalAcres'); + const featureSearch = document.getElementById('featureSearch'); // Show message function showMessage(message, type = 'error') { @@ -923,64 +916,6 @@ setTimeout(() => el.classList.remove('active'), 5000); } - // Resolve label overlaps using collision detection algorithm - function resolveLabelOverlaps(labelPositions) { - const labelWidth = 150; // Approximate label width in pixels - const labelHeight = 30; // Approximate label height in pixels - const minDistance = Math.sqrt(labelWidth * labelWidth + labelHeight * labelHeight); - - const adjustedPositions = labelPositions.map(pos => ({ ...pos })); - - // Iteratively adjust positions to avoid overlaps - for (let iteration = 0; iteration < 5; iteration++) { - let hasAdjustment = false; - - for (let i = 0; i < adjustedPositions.length; i++) { - for (let j = i + 1; j < adjustedPositions.length; j++) { - const pos1 = adjustedPositions[i]; - const pos2 = adjustedPositions[j]; - - const p1 = map.project(pos1.latlng); - const p2 = map.project(pos2.latlng); - - const dx = p2.x - p1.x; - const dy = p2.y - p1.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - // If labels are too close, push them apart - if (distance < minDistance && distance > 0) { - hasAdjustment = true; - const angle = Math.atan2(dy, dx); - const pushDistance = (minDistance - distance) / 2 + 5; - - // Move labels away from each other - const offset1 = map.unproject([ - p1.x - Math.cos(angle) * pushDistance, - p1.y - Math.sin(angle) * pushDistance - ]); - const offset2 = map.unproject([ - p2.x + Math.cos(angle) * pushDistance, - p2.y + Math.sin(angle) * pushDistance - ]); - - // Only adjust if original feature hasn't moved too far - const maxDrift = 0.003; - if (Math.abs(offset1.lat - pos1.originalLatlng.lat) < maxDrift) { - pos1.latlng = offset1; - } - if (Math.abs(offset2.lat - pos2.originalLatlng.lat) < maxDrift) { - pos2.latlng = offset2; - } - } - } - } - - if (!hasAdjustment) break; - } - - return adjustedPositions; - } - // Calculate area in square meters using Turf.js function getFeatureArea(feature) { try { @@ -1036,6 +971,98 @@ return { hectares, acres }; } + // Get first property key (field code) + function getFirstPropertyValue(properties) { + if (!properties) return ''; + const keys = Object.keys(properties); + if (keys.length === 0) return ''; + return properties[keys[0]]; + } + + // Create labels layer from features + function createLabelsLayer(features) { + labelsLayer = L.featureGroup([]); + + // First pass: collect all label positions + const labelPositions = []; + features.forEach((feature, index) => { + const props = feature.properties || {}; + const fieldCode = getFirstPropertyValue(props); + const displayLabel = fieldCode || `Feature ${index + 1}`; + + if (feature.geometry && feature.geometry.type !== 'Point') { + // Get centroid for polygon features + const bounds = L.geoJSON(feature).getBounds(); + const center = bounds.getCenter(); + + labelPositions.push({ + fieldName: displayLabel, + latlng: center, + originalLatlng: center, + iconAnchor: [0, 0], + isPoint: false + }); + } else if (feature.geometry && feature.geometry.type === 'Point') { + // For points, add label above the marker + const latlng = L.GeoJSON.coordsToLatLng(feature.geometry.coordinates); + labelPositions.push({ + fieldName: displayLabel, + latlng: latlng, + originalLatlng: latlng, + iconAnchor: [0, 30], + isPoint: true + }); + } + }); + + // Create markers + labelPositions.forEach((pos) => { + const label = L.marker(pos.latlng, { + icon: L.divIcon({ + className: 'field-label', + html: `
${pos.fieldName}
`, + iconSize: [null, null], + iconAnchor: pos.iconAnchor + }), + interactive: false + }); + labelsLayer.addLayer(label); + }); + } + + // Update labels visibility based on zoom level + function updateLabelsVisibility() { + const zoomLevel = map.getZoom(); + const minZoomForLabels = 16; // Only show labels at zoom level 13 and above + + if (labelsLayer && showLabels) { + if (zoomLevel >= minZoomForLabels) { + labelsLayer.eachLayer(layer => { + layer.setOpacity(1); + }); + } else { + labelsLayer.eachLayer(layer => { + layer.setOpacity(0); + }); + } + } + } + // Show message function showMessage(message, type = 'error') { const el = type === 'error' ? errorMessage : successMessage; @@ -1056,8 +1083,10 @@ reader.onload = (event) => { try { const geojson = JSON.parse(event.target.result); - loadGeojson(geojson, file.name); - showMessage('GeoJSON loaded successfully!', 'success'); + const success = loadGeojson(geojson, file.name); + if (success) { + showMessage('GeoJSON loaded successfully!', 'success'); + } } catch (error) { showMessage(`Error parsing file: ${error.message}`, 'error'); } @@ -1068,8 +1097,57 @@ reader.readAsText(file); }); + // Validate CRS + function validateCRS(geojson) { + // If no CRS is specified, assume WGS84 (default for GeoJSON) + if (!geojson.crs) { + return { valid: true, message: 'No CRS specified (default WGS84)' }; + } + + const crs = geojson.crs; + let crsName = ''; + + // Extract CRS name from different possible formats + if (crs.properties && crs.properties.name) { + crsName = crs.properties.name; + } else if (typeof crs === 'string') { + crsName = crs; + } + + // Check if it's WGS84 (various possible names) + const wgs84Variations = [ + 'EPSG:4326', + 'epsg:4326', + 'urn:ogc:def:crs:EPSG::4326', + 'urn:ogc:def:crs:EPSG:4.3:4326', + 'WGS84', + 'wgs84', + 'urn:ogc:def:crs:OGC:1.3:CRS84' + ]; + + const isWGS84 = wgs84Variations.some(variant => + crsName.toUpperCase().includes(variant.toUpperCase()) + ); + + if (isWGS84) { + return { valid: true, message: 'CRS: WGS84' }; + } else { + return { + valid: false, + message: `Non-supported CRS (${JSON.stringify(crs)})` + }; + } + } + // Load and display GeoJSON function loadGeojson(geojson, fileName) { + // Validate CRS first + const crsValidation = validateCRS(geojson); + if (!crsValidation.valid) { + showMessage(crsValidation.message, 'error'); + return false; + } + // Clear previous layer if (geojsonLayer) { map.removeLayer(geojsonLayer); @@ -1090,7 +1168,7 @@ if (features.length === 0) { showMessage('GeoJSON contains no features', 'error'); - return; + return false; } // Create layer with popups @@ -1142,89 +1220,16 @@ }).addTo(map); // Create labels layer - labelsLayer = L.featureGroup([]); + createLabelsLayer(features); - // First pass: collect all label positions - const labelPositions = []; - features.forEach((feature, index) => { - const props = feature.properties || {}; - const fieldName = props.field || props.name || props.field_name || props.fieldName || props.id || `Field ${index + 1}`; - - if (feature.geometry && feature.geometry.type !== 'Point') { - // Get centroid for polygon features - const bounds = L.geoJSON(feature).getBounds(); - const center = bounds.getCenter(); - - let labelLatlng = center; - let iconAnchor = [0, 0]; - - // If side position, offset label to the top-left of the bounds - if (labelPosition === 'side') { - const ne = bounds.getNorthEast(); - labelLatlng = L.latLng(ne.lat, ne.lng); - iconAnchor = [-10, -30]; - } - - labelPositions.push({ - fieldName, - latlng: labelLatlng, - originalLatlng: labelLatlng, - iconAnchor, - isPoint: false - }); - } else if (feature.geometry && feature.geometry.type === 'Point') { - // For points, add label above the marker - const latlng = L.GeoJSON.coordsToLatLng(feature.geometry.coordinates); - labelPositions.push({ - fieldName, - latlng: latlng, - originalLatlng: latlng, - iconAnchor: [0, 30], - isPoint: true - }); - } - }); - - // Apply collision detection to resolve overlaps - const adjustedPositions = resolveLabelOverlaps(labelPositions); - - // Second pass: create markers with adjusted positions - adjustedPositions.forEach((pos) => { - const label = L.marker(pos.latlng, { - icon: L.divIcon({ - className: 'field-label', - html: `
${pos.fieldName}
`, - iconSize: [null, null], - iconAnchor: pos.iconAnchor - }), - interactive: false - }); - labelsLayer.addLayer(label); - }); - - if (showLabels) { - labelsLayer.addTo(map); - } - // Fit bounds const bounds = geojsonLayer.getBounds(); map.fitBounds(bounds, { padding: [50, 50] }); + // Update labels visibility based on zoom level + updateLabelsVisibility(); + map.on('zoom', updateLabelsVisibility); + // Update stats updateStats(geojson, features); @@ -1243,6 +1248,8 @@ featuresSection.style.display = 'block'; tableSection.style.display = 'block'; downloadBtn.style.display = 'block'; + + return true; } // Update statistics @@ -1268,14 +1275,19 @@ // Display features list function displayFeaturesList() { featureLst.innerHTML = ''; + featureSearch.value = ''; + featuresList.forEach((item, index) => { const div = document.createElement('div'); div.className = 'feature-item'; - // Get feature name + // Get first property value (field code) const props = item.feature.properties || {}; - const name = props.name || props.id || `Feature ${index + 1}`; + const fieldCode = getFirstPropertyValue(props); + const name = fieldCode || `Feature ${index + 1}`; + div.textContent = name; + div.dataset.index = index; div.addEventListener('click', () => selectFeature(index)); featureLst.appendChild(div); @@ -1285,35 +1297,43 @@ // Select feature function selectFeature(index) { const item = featuresList[index]; - if (!item) return; + if (!item) { + console.error('Feature not found at index:', index); + return; + } // Update feature list UI document.querySelectorAll('.feature-item').forEach((el, i) => { el.classList.toggle('active', i === index); }); - // Update map - item.layer.fire('click'); - if (item.layer.setStyle) { - item.layer.setStyle({ - weight: 3, - opacity: 1 - }); - } - // Display properties in sidebar displayProperties(item.feature); // Open modal with properties openFeatureModal(item.feature); - // Scroll to layer on map - if (item.feature.geometry.type === 'Point') { - const latlng = item.layer.getLatLng(); - map.setView(latlng, map.getZoom()); - } else { - const bounds = L.geoJSON(item.feature).getBounds(); - map.fitBounds(bounds, { padding: [100, 100] }); + // Zoom to feature on map + setTimeout(() => { + try { + if (item.feature.geometry.type === 'Point') { + const latlng = item.layer.getLatLng(); + map.setView(latlng, 14); + } else { + const bounds = L.geoJSON(item.feature).getBounds(); + map.fitBounds(bounds, { padding: [100, 100] }); + } + } catch (e) { + console.error('Error zooming to feature:', e); + } + }, 100); + + // Update map styling + if (item.layer.setStyle) { + item.layer.setStyle({ + weight: 3, + opacity: 1 + }); } } @@ -1496,6 +1516,10 @@ map.removeLayer(geojsonLayer); geojsonLayer = null; } + if (labelsLayer) { + map.removeLayer(labelsLayer); + labelsLayer = null; + } currentGeojsonData = null; featuresList = []; geojsonInput.value = ''; @@ -1526,6 +1550,21 @@ URL.revokeObjectURL(url); showMessage('GeoJSON downloaded!', 'success'); }); + + // Search features + featureSearch.addEventListener('input', (e) => { + const searchTerm = e.target.value.toLowerCase(); + const items = document.querySelectorAll('.feature-item'); + + items.forEach(item => { + const text = item.textContent.toLowerCase(); + if (text.includes(searchTerm)) { + item.style.display = ''; + } else { + item.style.display = 'none'; + } + }); + });