【Leaflet.js实战】加载外部数据:GeoJSON/TIFF/CSV等格式的完整集成方案

前言:实际项目中,地图数据往往来自外部API或文件。本文将深入讲解如何加载和处理各种格式的地理数据,从GeoJSON到CSV,从静态数据到实时数据,助你构建数据驱动的地图应用。

一、GeoJSON数据集成

1.1 GeoJSON基础概念

特性说明优势
标准格式基于JSON的地理数据交换格式跨平台兼容
几何类型支持Point、LineString、Polygon等类型丰富
属性数据可携带任意属性信息数据完整
坐标系默认使用WGS84坐标系标准统一

1.2 基础GeoJSON加载

// 创建GeoJSON数据
const geojsonData = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [116.4074, 39.9042]
            },
            "properties": {
                "name": "天安门",
                "type": "landmark",
                "rating": 4.5
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [116.4074, 39.9042],
                    [116.4174, 39.9142],
                    [116.4274, 39.9242]
                ]
            },
            "properties": {
                "name": "长安街",
                "type": "road"
            }
        }
    ]
};

// 创建GeoJSON图层
const geojsonLayer = L.geoJSON(geojsonData, {
    style: {
        color: '#ff6b6b',
        weight: 2,
        opacity: 0.8,
        fillColor: '#ff6b6b',
        fillOpacity: 0.3
    },
    onEachFeature: (feature, layer) => {
        if (feature.properties && feature.properties.name) {
            layer.bindPopup(feature.properties.name);
        }
    }
}).addTo(map);

1.3 动态样式配置

// 动态样式配置器
class GeoJSONStyleManager {
    constructor() {
        this.styleRules = [];
    }
    
    // 添加样式规则
    addStyleRule(condition, style) {
        this.styleRules.push({ condition, style });
    }
    
    // 获取要素样式
    getFeatureStyle(feature) {
        for (const rule of this.styleRules) {
            if (rule.condition(feature)) {
                return rule.style;
            }
        }
        return this.getDefaultStyle();
    }
    
    // 默认样式
    getDefaultStyle() {
        return {
            color: '#666',
            weight: 2,
            opacity: 0.8,
            fillColor: '#666',
            fillOpacity: 0.3
        };
    }
}

// 使用样式管理器
const styleManager = new GeoJSONStyleManager();

// 添加样式规则
styleManager.addStyleRule(
    feature => feature.properties.type === 'landmark',
    { color: '#ff6b6b', weight: 3, fillColor: '#ff6b6b' }
);

styleManager.addStyleRule(
    feature => feature.properties.type === 'road',
    { color: '#4ecdc4', weight: 2, fillColor: '#4ecdc4' }
);

// 创建带动态样式的GeoJSON图层
const styledLayer = L.geoJSON(geojsonData, {
    style: (feature) => styleManager.getFeatureStyle(feature),
    onEachFeature: (feature, layer) => {
        const popupContent = `
            <div>
                <h3>${feature.properties.name}</h3>
                <p>类型: ${feature.properties.type}</p>
                ${feature.properties.rating ? `<p>评分: ${feature.properties.rating}/5</p>` : ''}
            </div>
        `;
        layer.bindPopup(popupContent);
    }
}).addTo(map);

1.4 从URL加载GeoJSON

// 异步加载GeoJSON数据
class GeoJSONLoader {
    constructor(map) {
        this.map = map;
        this.loadedLayers = new Map();
    }
    
    // 从URL加载GeoJSON
    async loadFromURL(url, options = {}) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            const data = await response.json();
            return this.createLayer(data, options);
        } catch (error) {
            console.error('加载GeoJSON失败:', error);
            throw error;
        }
    }
    
    // 创建图层
    createLayer(data, options) {
        const layer = L.geoJSON(data, {
            style: options.style || this.getDefaultStyle(),
            onEachFeature: options.onEachFeature || this.defaultOnEachFeature,
            ...options
        });
        
        if (options.addToMap !== false) {
            layer.addTo(this.map);
        }
        
        return layer;
    }
    
    // 默认样式
    getDefaultStyle() {
        return {
            color: '#3388ff',
            weight: 2,
            opacity: 0.8,
            fillColor: '#3388ff',
            fillOpacity: 0.3
        };
    }
    
    // 默认要素处理
    defaultOnEachFeature(feature, layer) {
        if (feature.properties && feature.properties.name) {
            layer.bindPopup(feature.properties.name);
        }
    }
}

// 使用GeoJSON加载器
const loader = new GeoJSONLoader(map);

// 加载外部GeoJSON数据
loader.loadFromURL('/api/geojson/stations', {
    style: {
        color: '#4CAF50',
        weight: 3,
        fillColor: '#4CAF50',
        fillOpacity: 0.5
    },
    onEachFeature: (feature, layer) => {
        const props = feature.properties;
        layer.bindPopup(`
            <div>
                <h3>${props.name}</h3>
                <p>地址: ${props.address}</p>
                <p>类型: ${props.type}</p>
                <p>状态: ${props.status}</p>
            </div>
        `);
    }
});

二、CSV数据转换

2.1 CSV数据解析

// CSV数据解析器
class CSVDataParser {
    constructor() {
        this.separator = ',';
        this.headers = [];
    }
    
    // 解析CSV文本
    parseCSV(csvText) {
        const lines = csvText.split('\n').filter(line => line.trim());
        if (lines.length === 0) return [];
        
        // 解析表头
        this.headers = this.parseLine(lines[0]);
        
        // 解析数据行
        const data = [];
        for (let i = 1; i < lines.length; i++) {
            const values = this.parseLine(lines[i]);
            if (values.length === this.headers.length) {
                const row = {};
                this.headers.forEach((header, index) => {
                    row[header.trim()] = values[index].trim();
                });
                data.push(row);
            }
        }
        
        return data;
    }
    
    // 解析单行
    parseLine(line) {
        const result = [];
        let current = '';
        let inQuotes = false;
        
        for (let i = 0; i < line.length; i++) {
            const char = line[i];
            
            if (char === '"') {
                inQuotes = !inQuotes;
            } else if (char === this.separator && !inQuotes) {
                result.push(current);
                current = '';
            } else {
                current += char;
            }
        }
        
        result.push(current);
        return result;
    }
}

// 使用CSV解析器
const parser = new CSVDataParser();
const csvData = parser.parseCSV(csvText);
console.log('解析的CSV数据:', csvData);

2.2 CSV转GeoJSON

// CSV到GeoJSON转换器
class CSVToGeoJSONConverter {
    constructor(options = {}) {
        this.options = {
            latField: 'latitude',
            lngField: 'longitude',
            idField: 'id',
            nameField: 'name',
            ...options
        };
    }
    
    // 转换CSV数据为GeoJSON
    convert(csvData) {
        const features = csvData.map(row => {
            const lat = parseFloat(row[this.options.latField]);
            const lng = parseFloat(row[this.options.lngField]);
            
            if (isNaN(lat) || isNaN(lng)) {
                console.warn('无效坐标:', row);
                return null;
            }
            
            return {
                type: 'Feature',
                geometry: {
                    type: 'Point',
                    coordinates: [lng, lat]
                },
                properties: this.extractProperties(row)
            };
        }).filter(feature => feature !== null);
        
        return {
            type: 'FeatureCollection',
            features: features
        };
    }
    
    // 提取属性
    extractProperties(row) {
        const properties = {};
        
        Object.keys(row).forEach(key => {
            if (key !== this.options.latField && key !== this.options.lngField) {
                properties[key] = row[key];
            }
        });
        
        return properties;
    }
}

// 使用转换器
const converter = new CSVToGeoJSONConverter({
    latField: 'lat',
    lngField: 'lng',
    nameField: 'station_name'
});

const geojsonData = converter.convert(csvData);
const layer = L.geoJSON(geojsonData).addTo(map);

2.3 CSV数据过滤

// CSV数据过滤器
class CSVDataFilter {
    constructor(data) {
        this.data = data;
        this.filters = [];
    }
    
    // 添加过滤条件
    addFilter(condition) {
        this.filters.push(condition);
        return this;
    }
    
    // 按数值范围过滤
    filterByRange(field, min, max) {
        return this.addFilter(row => {
            const value = parseFloat(row[field]);
            return value >= min && value <= max;
        });
    }
    
    // 按文本匹配过滤
    filterByText(field, pattern) {
        return this.addFilter(row => {
            const value = row[field].toLowerCase();
            return value.includes(pattern.toLowerCase());
        });
    }
    
    // 按地理位置过滤
    filterByLocation(centerLat, centerLng, maxDistance) {
        return this.addFilter(row => {
            const lat = parseFloat(row.latitude);
            const lng = parseFloat(row.longitude);
            
            if (isNaN(lat) || isNaN(lng)) return false;
            
            const distance = this.getDistance(centerLat, centerLng, lat, lng);
            return distance <= maxDistance;
        });
    }
    
    // 应用所有过滤器
    apply() {
        return this.data.filter(row => {
            return this.filters.every(filter => filter(row));
        });
    }
    
    // 计算距离
    getDistance(lat1, lng1, lat2, lng2) {
        const R = 6371000; // 地球半径(米)
        const dLat = (lat2 - lat1) * Math.PI / 180;
        const dLng = (lng2 - lng1) * Math.PI / 180;
        
        const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                  Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
                  Math.sin(dLng/2) * Math.sin(dLng/2);
        
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return R * c;
    }
}

// 使用过滤器
const filter = new CSVDataFilter(csvData);
const filteredData = filter
    .filterByRange('rating', 4.0, 5.0)
    .filterByText('type', 'restaurant')
    .filterByLocation(39.9042, 116.4074, 5000)
    .apply();

console.log('过滤后的数据:', filteredData);

三、TIFF栅格数据

3.1 使用Leaflet-GeoTIFF插件

// 安装:npm install geotiff leaflet-geotiff
import * as GeoTIFF from 'geotiff';
import 'leaflet-geotiff';

// TIFF数据加载器
class TIFFDataLoader {
    constructor(map) {
        this.map = map;
        this.tiffLayers = new Map();
    }
    
    // 加载TIFF数据
    async loadTIFF(url, options = {}) {
        try {
            const response = await fetch(url);
            const arrayBuffer = await response.arrayBuffer();
            
            const tiff = await GeoTIFF.fromArrayBuffer(arrayBuffer);
            const image = await tiff.getImage();
            const rasters = await image.readRasters();
            
            // 创建栅格图层
            const rasterLayer = L.leafletGeotiff(url, {
                opacity: 0.7,
                colorScale: 'viridis',
                clampLow: false,
                clampHigh: false,
                displayMin: 0,
                displayMax: 100,
                ...options
            }).addTo(this.map);
            
            this.tiffLayers.set(url, rasterLayer);
            return rasterLayer;
            
        } catch (error) {
            console.error('加载TIFF数据失败:', error);
            throw error;
        }
    }
    
    // 移除TIFF图层
    removeTIFF(url) {
        const layer = this.tiffLayers.get(url);
        if (layer) {
            this.map.removeLayer(layer);
            this.tiffLayers.delete(url);
        }
    }
}

// 使用TIFF加载器
const tiffLoader = new TIFFDataLoader(map);

// 加载地形数据
tiffLoader.loadTIFF('/data/elevation.tiff', {
    opacity: 0.8,
    colorScale: 'terrain',
    displayMin: 0,
    displayMax: 3000
});

3.2 栅格数据样式

// 栅格数据样式配置器
class RasterStyleConfigurator {
    constructor() {
        this.colorScales = {
            viridis: {
                0: '#440154',
                0.2: '#482777',
                0.4: '#3f4a8a',
                0.6: '#31678e',
                0.8: '#26838f',
                1: '#1f9d8a'
            },
            terrain: {
                0: '#8B4513',
                0.3: '#228B22',
                0.6: '#32CD32',
                0.8: '#FFD700',
                1: '#FFFFFF'
            },
            heat: {
                0: '#000080',
                0.25: '#0000FF',
                0.5: '#00FFFF',
                0.75: '#FFFF00',
                1: '#FF0000'
            }
        };
    }
    
    // 创建自定义颜色映射
    createCustomColorScale(colors) {
        const scale = {};
        const keys = Object.keys(colors).sort((a, b) => parseFloat(a) - parseFloat(b));
        
        keys.forEach(key => {
            scale[key] = colors[key];
        });
        
        return scale;
    }
    
    // 获取颜色比例
    getColorScale(name) {
        return this.colorScales[name] || this.colorScales.viridis;
    }
}

// 使用样式配置器
const styleConfig = new RasterStyleConfigurator();

// 创建自定义颜色映射
const customColors = {
    0: '#000080',
    0.2: '#0000FF',
    0.4: '#00FFFF',
    0.6: '#FFFF00',
    0.8: '#FF8000',
    1: '#FF0000'
};

const customScale = styleConfig.createCustomColorScale(customColors);

// 应用自定义样式
tiffLoader.loadTIFF('/data/heatmap.tiff', {
    colorScale: customScale,
    opacity: 0.8
});

四、实时数据更新

4.1 WebSocket实时数据

// WebSocket数据管理器
class WebSocketDataManager {
    constructor(map, options = {}) {
        this.map = map;
        this.options = {
            url: 'ws://localhost:8080/ws',
            reconnectInterval: 5000,
            maxReconnectAttempts: 10,
            ...options
        };
        this.ws = null;
        this.reconnectAttempts = 0;
        this.dataLayers = new Map();
        this.isConnected = false;
    }
    
    // 连接WebSocket
    connect() {
        try {
            this.ws = new WebSocket(this.options.url);
            
            this.ws.onopen = () => {
                console.log('WebSocket连接已建立');
                this.isConnected = true;
                this.reconnectAttempts = 0;
                this.onConnect();
            };
            
            this.ws.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    this.handleMessage(data);
                } catch (error) {
                    console.error('解析WebSocket消息失败:', error);
                }
            };
            
            this.ws.onclose = () => {
                console.log('WebSocket连接已关闭');
                this.isConnected = false;
                this.onDisconnect();
                this.attemptReconnect();
            };
            
            this.ws.onerror = (error) => {
                console.error('WebSocket错误:', error);
                this.onError(error);
            };
            
        } catch (error) {
            console.error('创建WebSocket连接失败:', error);
            this.attemptReconnect();
        }
    }
    
    // 处理消息
    handleMessage(data) {
        switch (data.type) {
            case 'update':
                this.updateData(data.payload);
                break;
            case 'add':
                this.addData(data.payload);
                break;
            case 'remove':
                this.removeData(data.payload);
                break;
            default:
                console.log('未知消息类型:', data.type);
        }
    }
    
    // 更新数据
    updateData(payload) {
        const layerId = payload.layerId;
        const layer = this.dataLayers.get(layerId);
        
        if (layer) {
            layer.clearLayers();
            const newLayer = L.geoJSON(payload.data);
            newLayer.addTo(this.map);
            this.dataLayers.set(layerId, newLayer);
        }
    }
    
    // 添加数据
    addData(payload) {
        const layer = L.geoJSON(payload.data);
        layer.addTo(this.map);
        this.dataLayers.set(payload.layerId, layer);
    }
    
    // 移除数据
    removeData(payload) {
        const layer = this.dataLayers.get(payload.layerId);
        if (layer) {
            this.map.removeLayer(layer);
            this.dataLayers.delete(payload.layerId);
        }
    }
    
    // 尝试重连
    attemptReconnect() {
        if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
            this.reconnectAttempts++;
            console.log(`尝试重连 (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`);
            
            setTimeout(() => {
                this.connect();
            }, this.options.reconnectInterval);
        } else {
            console.error('达到最大重连次数,停止重连');
        }
    }
    
    // 断开连接
    disconnect() {
        if (this.ws) {
            this.ws.close();
            this.ws = null;
        }
    }
    
    // 事件回调
    onConnect() {}
    onDisconnect() {}
    onError(error) {}
}

// 使用WebSocket数据管理器
const wsManager = new WebSocketDataManager(map, {
    url: 'ws://localhost:8080/ws',
    reconnectInterval: 3000
});

wsManager.onConnect = () => {
    console.log('WebSocket连接成功');
};

wsManager.onDisconnect = () => {
    console.log('WebSocket连接断开');
};

wsManager.connect();

4.2 定时数据更新

// 定时数据更新器
class ScheduledDataUpdater {
    constructor(map, options = {}) {
        this.map = map;
        this.options = {
            interval: 30000, // 30秒
            url: '/api/data',
            method: 'GET',
            ...options
        };
        this.intervalId = null;
        this.isRunning = false;
        this.dataLayers = new Map();
    }
    
    // 开始更新
    start() {
        if (this.isRunning) return;
        
        this.isRunning = true;
        this.updateData(); // 立即执行一次
        
        this.intervalId = setInterval(() => {
            this.updateData();
        }, this.options.interval);
        
        console.log('定时数据更新已启动');
    }
    
    // 停止更新
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
        this.isRunning = false;
        console.log('定时数据更新已停止');
    }
    
    // 更新数据
    async updateData() {
        try {
            const response = await fetch(this.options.url, {
                method: this.options.method,
                headers: {
                    'Content-Type': 'application/json',
                    ...this.options.headers
                }
            });
            
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            
            const data = await response.json();
            this.processData(data);
            
        } catch (error) {
            console.error('更新数据失败:', error);
        }
    }
    
    // 处理数据
    processData(data) {
        // 更新现有图层
        data.forEach(item => {
            const layerId = item.layerId;
            const layer = this.dataLayers.get(layerId);
            
            if (layer) {
                // 更新现有图层
                layer.clearLayers();
                const newLayer = L.geoJSON(item.data);
                newLayer.addTo(this.map);
                this.dataLayers.set(layerId, newLayer);
            } else {
                // 创建新图层
                const newLayer = L.geoJSON(item.data);
                newLayer.addTo(this.map);
                this.dataLayers.set(layerId, newLayer);
            }
        });
    }
    
    // 设置更新间隔
    setInterval(interval) {
        this.options.interval = interval;
        
        if (this.isRunning) {
            this.stop();
            this.start();
        }
    }
}

// 使用定时更新器
const updater = new ScheduledDataUpdater(map, {
    interval: 60000, // 1分钟
    url: '/api/realtime-data'
});

updater.start();

五、数据缓存与性能优化

5.1 数据缓存系统

// 数据缓存管理器
class DataCacheManager {
    constructor(options = {}) {
        this.options = {
            maxSize: 100,
            ttl: 3600000, // 1小时
            ...options
        };
        this.cache = new Map();
        this.accessTimes = new Map();
        this.startCleanupTimer();
    }
    
    // 获取缓存数据
    get(key) {
        const item = this.cache.get(key);
        if (!item) return null;
        
        // 检查是否过期
        if (Date.now() - item.timestamp > this.options.ttl) {
            this.cache.delete(key);
            this.accessTimes.delete(key);
            return null;
        }
        
        // 更新访问时间
        this.accessTimes.set(key, Date.now());
        return item.data;
    }
    
    // 设置缓存数据
    set(key, data) {
        // 如果缓存已满,移除最久未访问的项
        if (this.cache.size >= this.options.maxSize) {
            this.evictLeastRecentlyUsed();
        }
        
        this.cache.set(key, {
            data: data,
            timestamp: Date.now()
        });
        this.accessTimes.set(key, Date.now());
    }
    
    // 移除最久未访问的项
    evictLeastRecentlyUsed() {
        let oldestKey = null;
        let oldestTime = Date.now();
        
        for (const [key, time] of this.accessTimes) {
            if (time < oldestTime) {
                oldestTime = time;
                oldestKey = key;
            }
        }
        
        if (oldestKey) {
            this.cache.delete(oldestKey);
            this.accessTimes.delete(oldestKey);
        }
    }
    
    // 清理过期缓存
    cleanup() {
        const now = Date.now();
        for (const [key, item] of this.cache) {
            if (now - item.timestamp > this.options.ttl) {
                this.cache.delete(key);
                this.accessTimes.delete(key);
            }
        }
    }
    
    // 启动清理定时器
    startCleanupTimer() {
        setInterval(() => {
            this.cleanup();
        }, this.options.ttl / 2);
    }
    
    // 清空所有缓存
    clear() {
        this.cache.clear();
        this.accessTimes.clear();
    }
}

// 使用缓存管理器
const cacheManager = new DataCacheManager({
    maxSize: 50,
    ttl: 1800000 // 30分钟
});

// 缓存数据加载
const loadCachedData = async (url) => {
    const cachedData = cacheManager.get(url);
    if (cachedData) {
        return cachedData;
    }
    
    const data = await fetch(url).then(response => response.json());
    cacheManager.set(url, data);
    return data;
};

5.2 分块加载优化

// 分块加载管理器
class ChunkedDataLoader {
    constructor(map, options = {}) {
        this.map = map;
        this.options = {
            chunkSize: 1000,
            loadDelay: 100,
            maxConcurrentChunks: 3,
            ...options
        };
        this.loadingQueue = [];
        this.activeChunks = new Set();
        this.loadedChunks = new Set();
    }
    
    // 分块加载数据
    async loadDataInChunks(data, onProgress) {
        const totalChunks = Math.ceil(data.length / this.options.chunkSize);
        const allData = [];
        
        for (let i = 0; i < totalChunks; i++) {
            if (this.loadedChunks.has(i)) continue;
            
            const chunk = data.slice(i * this.options.chunkSize, (i + 1) * this.options.chunkSize);
            
            // 等待可用槽位
            await this.waitForAvailableSlot();
            
            // 加载数据块
            this.loadChunk(i, chunk, allData, onProgress);
        }
        
        return allData;
    }
    
    // 等待可用槽位
    async waitForAvailableSlot() {
        while (this.activeChunks.size >= this.options.maxConcurrentChunks) {
            await new Promise(resolve => setTimeout(resolve, this.options.loadDelay));
        }
    }
    
    // 加载数据块
    async loadChunk(chunkIndex, chunk, allData, onProgress) {
        this.activeChunks.add(chunkIndex);
        
        try {
            // 模拟异步处理
            await new Promise(resolve => setTimeout(resolve, this.options.loadDelay));
            
            // 处理数据块
            const processedChunk = chunk.map(item => ({
                ...item,
                processed: true
            }));
            
            allData.push(...processedChunk);
            this.loadedChunks.add(chunkIndex);
            
            // 更新进度
            if (onProgress) {
                const progress = (chunkIndex + 1) / Math.ceil(allData.length / this.options.chunkSize) * 100;
                onProgress(progress);
            }
            
        } finally {
            this.activeChunks.delete(chunkIndex);
        }
    }
}

// 使用分块加载
const chunkedLoader = new ChunkedDataLoader(map, {
    chunkSize: 500,
    loadDelay: 50,
    maxConcurrentChunks: 2
});

const loadLargeDataset = async (data) => {
    const processedData = await chunkedLoader.loadDataInChunks(data, (progress) => {
        console.log(`加载进度: ${progress.toFixed(1)}%`);
    });
    
    return processedData;
};

六、实战案例:多数据源地图应用

// 完整的多数据源地图应用
class MultiDataSourceMap {
    constructor(containerId) {
        this.map = L.map(containerId).setView([39.9042, 116.4074], 13);
        this.dataSources = new Map();
        this.cacheManager = new DataCacheManager();
        this.init();
    }
    
    init() {
        // 添加底图
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '© OpenStreetMap contributors'
        }).addTo(this.map);
        
        // 添加图层控制
        this.layerControl = L.control.layers(null, null, {
            position: 'topright'
        }).addTo(this.map);
    }
    
    // 添加数据源
    async addDataSource(id, config) {
        try {
            let data;
            
            // 检查缓存
            const cachedData = this.cacheManager.get(id);
            if (cachedData) {
                data = cachedData;
            } else {
                // 加载数据
                data = await this.loadData(config);
                this.cacheManager.set(id, data);
            }
            
            // 创建图层
            const layer = this.createLayer(data, config);
            this.dataSources.set(id, layer);
            
            // 添加到图层控制
            this.layerControl.addOverlay(layer, config.name);
            
            return layer;
            
        } catch (error) {
            console.error(`加载数据源 ${id} 失败:`, error);
            throw error;
        }
    }
    
    // 加载数据
    async loadData(config) {
        switch (config.type) {
            case 'geojson':
                return this.loadGeoJSON(config.url);
            case 'csv':
                return this.loadCSV(config.url);
            case 'tiff':
                return this.loadTIFF(config.url);
            default:
                throw new Error(`不支持的数据类型: ${config.type}`);
        }
    }
    
    // 加载GeoJSON
    async loadGeoJSON(url) {
        const response = await fetch(url);
        return response.json();
    }
    
    // 加载CSV
    async loadCSV(url) {
        const response = await fetch(url);
        const csvText = await response.text();
        const parser = new CSVDataParser();
        return parser.parseCSV(csvText);
    }
    
    // 加载TIFF
    async loadTIFF(url) {
        // TIFF加载逻辑
        return { url, type: 'tiff' };
    }
    
    // 创建图层
    createLayer(data, config) {
        switch (config.type) {
            case 'geojson':
                return this.createGeoJSONLayer(data, config);
            case 'csv':
                return this.createCSVLayer(data, config);
            case 'tiff':
                return this.createTIFFLayer(data, config);
            default:
                throw new Error(`不支持的图层类型: ${config.type}`);
        }
    }
    
    // 创建GeoJSON图层
    createGeoJSONLayer(data, config) {
        return L.geoJSON(data, {
            style: config.style || this.getDefaultStyle(),
            onEachFeature: config.onEachFeature || this.defaultOnEachFeature
        });
    }
    
    // 创建CSV图层
    createCSVLayer(data, config) {
        const converter = new CSVToGeoJSONConverter(config.converterOptions);
        const geojsonData = converter.convert(data);
        return this.createGeoJSONLayer(geojsonData, config);
    }
    
    // 创建TIFF图层
    createTIFFLayer(data, config) {
        return L.leafletGeotiff(data.url, config.tiffOptions);
    }
    
    // 默认样式
    getDefaultStyle() {
        return {
            color: '#3388ff',
            weight: 2,
            opacity: 0.8,
            fillColor: '#3388ff',
            fillOpacity: 0.3
        };
    }
    
    // 默认要素处理
    defaultOnEachFeature(feature, layer) {
        if (feature.properties && feature.properties.name) {
            layer.bindPopup(feature.properties.name);
        }
    }
    
    // 移除数据源
    removeDataSource(id) {
        const layer = this.dataSources.get(id);
        if (layer) {
            this.map.removeLayer(layer);
            this.layerControl.removeLayer(layer);
            this.dataSources.delete(id);
        }
    }
}

// 使用多数据源地图
const mapApp = new MultiDataSourceMap('map');

// 添加GeoJSON数据源
mapApp.addDataSource('stations', {
    type: 'geojson',
    name: '充电桩',
    url: '/api/stations.geojson',
    style: {
        color: '#4CAF50',
        weight: 3,
        fillColor: '#4CAF50',
        fillOpacity: 0.5
    }
});

// 添加CSV数据源
mapApp.addDataSource('restaurants', {
    type: 'csv',
    name: '餐厅',
    url: '/api/restaurants.csv',
    converterOptions: {
        latField: 'latitude',
        lngField: 'longitude',
        nameField: 'name'
    }
});

七、常见问题与解决方案

7.1 数据加载失败

// 问题:数据加载失败
// 解决方案:添加错误处理和重试机制
const loadDataWithRetry = async (url, maxRetries = 3) => {
    for (let i = 0; i < maxRetries; i++) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            return await response.json();
        } catch (error) {
            if (i === maxRetries - 1) throw error;
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
    }
};

7.2 大数据量性能问题

// 问题:大数据量导致性能问题
// 解决方案:使用分页加载和聚合
const loadLargeDataset = async (url, pageSize = 1000) => {
    const allData = [];
    let page = 1;
    
    while (true) {
        const response = await fetch(`${url}?page=${page}&size=${pageSize}`);
        const data = await response.json();
        
        if (data.length === 0) break;
        
        allData.push(...data);
        page++;
        
        // 显示加载进度
        updateLoadingProgress((page - 1) * pageSize);
    }
    
    return allData;
};

7.3 坐标系转换错误

// 问题:坐标系转换错误
// 解决方案:使用正确的坐标系转换
const convertCoordinates = (lat, lng, fromCRS, toCRS) => {
    // 坐标系转换逻辑
    if (fromCRS === 'WGS84' && toCRS === 'WebMercator') {
        return {
            x: lng * 20037508.34 / 180,
            y: Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180) * 20037508.34 / 180
        };
    }
    return { x: lng, y: lat };
};

八、总结与最佳实践

8.1 核心要点

  • ✅ GeoJSON集成:掌握标准地理数据格式的处理
  • ✅ CSV转换:学会表格数据到地理数据的转换
  • ✅ 实时数据:实现WebSocket和定时更新机制
  • ✅ 性能优化:使用缓存和分块加载处理大数据量

8.2 最佳实践建议

  1. 数据验证:始终验证外部数据的格式和完整性
  2. 错误处理:提供友好的错误提示和重试机制
  3. 性能优化:大数据量时使用分块加载和缓存
  4. 用户体验:显示加载进度和状态信息

相关推荐

如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注👆,你的支持是我创作的动力!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

立方世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值