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';
+ }
+ });
+ });