前言:实际项目中,地图数据往往来自外部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 最佳实践建议
- 数据验证:始终验证外部数据的格式和完整性
- 错误处理:提供友好的错误提示和重试机制
- 性能优化:大数据量时使用分块加载和缓存
- 用户体验:显示加载进度和状态信息
相关推荐:
如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注👆,你的支持是我创作的动力!
839

被折叠的 条评论
为什么被折叠?



