npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mapjar

v0.4.2

Published

基于 WebGL2 的高性能地图渲染引擎,支持 EPSG:3857 投影。

Readme

Mapjar - WebGL2 地图引擎

基于 WebGL2 的高性能地图渲染引擎,支持 EPSG:3857 投影。

✨ 功能特性

核心渲染

  • WebGL2 渲染:高性能 GPU 加速渲染
  • EPSG:3857 投影:Web Mercator 标准投影
  • 高 DPR 支持:完美适配 Retina 和高分辨率屏幕,自动 DPI 缩放
  • 跨世界渲染:水平无限循环,无缝拼接

相机系统

  • 平移、缩放、旋转:流畅的交互体验
  • 平滑动画:flyTo、fitBounds 带缓动效果
  • 纬度限制:自动锁定在 ±85.05° 范围内
  • 右键旋转:支持地图旋转(可选)

图层系统

  • 瓦片图层 (TileLayer):支持任意瓦片源,Web Worker 并发加载,LRU 缓存,智能取消机制
  • 矢量图层 (VectorLayer):点、线、面要素渲染,圆形点,Earcut 多边形三角化
  • GeoJSON 图层 (GeoJSONLayer):完整支持 GeoJSON 格式数据加载和渲染
  • 图像图层 (ImageLayer):渲染单张带地理坐标的图像,支持历史地图叠加、卫星影像等
  • 风场图层 (WindLayer):基于 WebGL2 的高性能风场可视化,粒子系统实时动画
  • 热力图层 (HeatmapLayer):温度、降水等连续数值场可视化,支持自定义颜色映射
  • 覆盖层图层 (OverlayLayer):在地图上叠加 HTML 元素,支持自定义样式和交互
  • Canvas 图层 (CanvasLayer):纯 Canvas 2D 渲染,适合自定义绘制

交互与事件

  • 鼠标/触摸交互:拖拽、滚轮、双击放大(带平滑动画)
  • 点击事件:监听地图点击,获取经纬度坐标
  • 鼠标移动事件:实时获取鼠标位置的经纬度(可选启用)
  • 事件系统:统一的事件发射器,类型安全的事件订阅/发布

高级功能

  • 文字渲染:为矢量要素添加文字标注,支持自定义字体、颜色、描边、偏移等
  • 数据驱动样式:根据要素属性动态设置样式
  • 空间查询:点查询、范围查询、最近邻查询
  • 视锥剔除:自动剔除视口外的要素,提升性能
  • 批量渲染:减少 GPU 状态切换,提升渲染效率
  • 资源管理:自动管理 WebGL 资源,防止内存泄漏

🚀 快速开始

安装依赖

bun add mapjar
# 或
npm install mapjar

📖 使用指南

基础示例

import { MapEngine, TileLayer, VectorLayer } from 'mapjar';

// 创建地图引擎(自动初始化并开始渲染)
const engine = new MapEngine('#map', {
  center: [116.4074, 39.9042], // 北京 [经度, 纬度]
  zoom: 10,
  rotation: 0,
  enableRotation: true // 启用右键旋转(默认 true)
});

// 添加瓦片图层
const tileLayer = new TileLayer(
  'osm',
  'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  { 
    tileScale: 1.0,        // 瓦片缩放比例(0.5 - 3.0)
    wrapX: true,           // 启用水平跨世界渲染
    fadeInDuration: 200    // 瓦片淡入动画时长(毫秒)
  }
);
engine.addLayer(tileLayer);

// 添加矢量图层
const vectorLayer = new VectorLayer('vector', {
  fillColor: [0.2, 0.6, 1.0, 0.4],
  strokeColor: [0.0, 0.4, 0.8, 1.0],
  strokeWidth: 2.0,
  pointSize: 10.0
});
engine.addLayer(vectorLayer);

// 添加点要素
vectorLayer.addFeature({
  type: 'point',
  coordinates: [116.4074, 39.9042],
  properties: { name: '北京' }
});

// 监听点击事件
engine.on('click', (event) => {
  console.log('点击位置:', event.lon, event.lat);
});

// 使用 flyTo 飞到上海
setTimeout(() => {
  engine.flyTo(121.4737, 31.2304, 10, { duration: 2000 });
}, 2000);

// 可选:手动控制渲染循环
// engine.stop();  // 暂停渲染
// engine.start(); // 恢复渲染

核心概念

1. 投影系统

使用 EPSG:3857 (Web Mercator) 投影:

import { WebMercatorProjection } from 'mapjar';

// 经纬度转 Web Mercator 坐标(米)
const pos = WebMercatorProjection.lonLatToMeters(116.4074, 39.9042);

// Web Mercator 坐标转经纬度
const lonLat = WebMercatorProjection.metersToLonLat(pos.x, pos.y);

// 获取瓦片坐标
const tile = WebMercatorProjection.getTileCoord(116.4074, 39.9042, 10);

2. 相机控制

const camera = engine.getCamera();

// 设置中心点(经纬度)
camera.setCenterLonLat(116.4074, 39.9042);

// 设置缩放级别(0-22)
camera.setZoom(10);

// 平移(像素)
camera.pan(100, 100);

// 缩放到指定点
camera.zoomTo(1, screenPos);

// 飞行到指定位置(带平滑动画)
engine.flyTo(116.4074, 39.9042, 10, { duration: 1500 });

// 适配到指定边界(带平滑动画)
engine.fitBounds(
  {
    minLon: 73.5,
    minLat: 18.2,
    maxLon: 135.0,
    maxLat: 53.5
  },
  { duration: 2000, padding: 50 }
);

3. 瓦片图层 (TileLayer)

const tileLayer = new TileLayer(
  'osm',
  'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
  { 
    tileScale: 1.0,        // 瓦片缩放比例(0.5 - 3.0,默认 1.0)
    wrapX: true,           // 启用水平跨世界渲染(默认 true)
    fadeInDuration: 200    // 瓦片淡入动画时长(毫秒,默认 200)
  }
);
engine.addLayer(tileLayer);

// 动态调整瓦片缩放比例
tileLayer.setTileScale(1.5); // 放大 50%,让文字更易读

// 特性:
// - Web Worker 并发加载(自动启用)
// - LRU 缓存(最多 500 个瓦片)
// - 智能取消机制(视口外的瓦片自动取消加载)
// - 淡入动画(平滑加载效果)
// - 跨世界渲染(水平无限循环)

4. 图像图层 (ImageLayer)

import { ImageLayer } from 'mapjar';

// 创建图像图层(用于历史地图叠加、卫星影像等)
const imageLayer = new ImageLayer('custom-overlay', {
  url: 'https://example.com/historical-map.png',
  bounds: {
    minLon: 116.2,  // 左下角经度
    minLat: 39.8,   // 左下角纬度
    maxLon: 116.6,  // 右上角经度
    maxLat: 40.1,   // 右上角纬度
  },
  useMipmap: true,  // 启用 Mipmap(默认 true)
});

imageLayer.setOpacity(0.7); // 半透明叠加
imageLayer.setZIndex(10);   // 在底图之上
engine.addLayer(imageLayer);

// 异步加载图像
await imageLayer.loadFromURL();

// 或直接提供图像
const imageLayer2 = new ImageLayer('overlay2', {
  image: imageBitmap, // HTMLImageElement 或 ImageBitmap
  bounds: { minLon: 116.2, minLat: 39.8, maxLon: 116.6, maxLat: 40.1 }
});

// 特性:
// - 支持 URL 或直接提供图像
// - Mipmap 优化(多级纹理)
// - 各向异性过滤(提升清晰度)
// - 自动地理配准

5. 矢量图层 (VectorLayer)

const vectorLayer = new VectorLayer('vector', {
  fillColor: [0.2, 0.6, 1.0, 0.4],
  strokeColor: [0.0, 0.4, 0.8, 1.0],
  strokeWidth: 2.0,
  pointSize: 10.0,
  // 文字样式
  textField: 'name',              // 显示 properties.name
  textFont: '14px Arial',
  textColor: [0, 0, 0, 1],
  textHaloColor: [1, 1, 1, 1],
  textHaloWidth: 2,
  textOffset: [0, -15],
  textAnchor: 'center'
});

// 添加点(渲染为圆形,带抗锯齿)
vectorLayer.addFeature({
  type: 'point',
  coordinates: [116.4074, 39.9042],
  properties: { name: '北京' }
});

// 添加线
vectorLayer.addFeature({
  type: 'line',
  coordinates: [[116.4074, 39.9042], [121.4737, 31.2304]],
  properties: { name: '路线' }
});

// 添加面(使用 Earcut 三角化,支持凹多边形和带洞多边形)
vectorLayer.addFeature({
  type: 'polygon',
  coordinates: [[[116, 39], [117, 39], [117, 40], [116, 40], [116, 39]]],
  properties: { name: '区域' }
});

// 添加带洞的多边形
vectorLayer.addFeature({
  type: 'polygon',
  coordinates: [
    [[116, 39], [117, 39], [117, 40], [116, 40], [116, 39]], // 外环
    [[116.3, 39.3], [116.7, 39.3], [116.7, 39.7], [116.3, 39.7], [116.3, 39.3]] // 洞
  ],
  properties: { name: '带洞区域' }
});

// 空间查询
const nearbyFeatures = vectorLayer.queryNearby([116.4, 39.9], 10000); // 10km 范围内
const featuresInBounds = vectorLayer.queryBBox({ minX: 116, minY: 39, maxX: 117, maxY: 40 });

// 特性:
// - 圆形点渲染(片段着色器实现,带抗锯齿)
// - Earcut 多边形三角化(支持凹多边形和带洞多边形)
// - 文字标注(支持自定义字体、颜色、描边)
// - 空间查询(点查询、范围查询、最近邻查询)
// - 视锥剔除(自动剔除视口外的要素)
// - 批量渲染(减少 GPU 状态切换)

6. GeoJSON 图层 (GeoJSONLayer)

import { MapEngine, GeoJSONLayer, StyleFunction } from 'mapjar';

// 创建地图引擎
const engine = new MapEngine('#map', {
  center: [105, 35],
  zoom: 4,
});

// 从 URL 加载 GeoJSON
const geoJSONLayer = new GeoJSONLayer('geojson', {
  url: 'https://example.com/data.geojson',
  style: {
    fillColor: [0.2, 0.6, 1.0, 0.4],
    strokeColor: [0.0, 0.4, 0.8, 1.0],
    strokeWidth: 2.0,
    pointSize: 10.0,
    textField: 'name',
    textFont: '14px Arial'
  }
});

engine.addLayer(geoJSONLayer);
await geoJSONLayer.loadFromURL();

// 或直接加载数据
const geoJSONLayer2 = new GeoJSONLayer('geojson2', {
  data: {
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: [116.4074, 39.9042]
        },
        properties: { name: '北京', population: 21540000 }
      }
    ]
  }
});

// 数据驱动样式
geoJSONLayer.setDataDrivenStyle({
  fillColor: StyleFunction.createPropertyColorMap(
    'type',
    {
      'residential': [0.8, 0.8, 0.6, 0.5],
      'commercial': [1.0, 0.6, 0.6, 0.5],
      'park': [0.4, 0.8, 0.4, 0.5],
    },
    [0.5, 0.5, 0.5, 0.5] // 默认颜色
  )
});

// 支持所有 GeoJSON 几何类型
// Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection

7. 风场动画图层 (WindLayer)

import { MapEngine, WindLayer } from 'mapjar';

// 创建地图引擎
const engine = new MapEngine('#map', {
  center: [105, 35],
  zoom: 4,
});

// 创建风场图层
const windLayer = new WindLayer('wind', {
  particleCount: 5000,      // 粒子数量(默认 5000)
  particleAge: 100,         // 粒子生命周期(帧数,默认 100)
  speedFactor: 0.5,         // 速度因子(默认 0.5)
  lineWidth: 1.0,           // 线宽(默认 1.0)
  fadeOpacity: 0.97,        // 拖尾透明度(默认 0.97)
  wrapX: true,              // 启用跨世界渲染(默认 true)
  colorRamp: [              // 颜色渐变(可选)
    '#3288bd',
    '#66c2a5',
    '#abdda4',
    '#e6f598',
    '#fee08b',
    '#fdae61',
    '#f46d43',
    '#d53e4f',
  ],
});

// 方式1:从图片加载风场数据(推荐)
// 图片格式:R 通道存储归一化的 U,G 通道存储归一化的 V,A 通道存储有效性
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = async () => {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d')!;
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  
  const uv = new Float32Array(canvas.width * canvas.height * 2);
  const alpha = new Float32Array(canvas.width * canvas.height);
  
  for (let i = 0; i < canvas.width * canvas.height; i++) {
    const r = pixels[i * 4] / 255;
    const g = pixels[i * 4 + 1] / 255;
    const a = pixels[i * 4 + 3] / 255;
    
    alpha[i] = a;
    
    if (a === 0) {
      uv[i * 2] = 0;
      uv[i * 2 + 1] = 0;
    } else {
      uv[i * 2] = minU + r * (maxU - minU);
      uv[i * 2 + 1] = minV + g * (maxV - minV);
    }
  }
  
  windLayer.setData({
    uv,
    alpha,
    width: canvas.width,
    height: canvas.height,
    minU: -12.35,
    maxU: 22.81,
    minV: -22.71,
    maxV: 14.65,
    bounds: {
      minLon: 55,
      minLat: 1,
      maxLon: 155,
      maxLat: 57,
    },
  });
};
img.src = 'wind-data.png';

// 方式2:直接使用数值数组
const windData = {
  uv: new Float32Array([...]),  // UV 数据数组 [u0, v0, u1, v1, ...]
  width: 100,                    // 数据宽度
  height: 50,                    // 数据高度
  minU: -10,                     // U 分量最小值
  maxU: 10,                      // U 分量最大值
  minV: -10,                     // V 分量最小值
  maxV: 10,                      // V 分量最大值
  alpha: new Float32Array([...]), // 可选:透明度数组
  bounds: {                      // 地理边界(可选)
    minLon: 73.5,
    minLat: 18.0,
    maxLon: 135.0,
    maxLat: 53.5,
  },
};

windLayer.setData(windData);
engine.addLayer(windLayer);

// 特性:
// - 粒子系统渲染,GPU 加速
// - 支持从图片加载数据(R/G 通道存储 U/V,A 通道存储有效性)
// - 根据风速自动映射颜色
// - 平滑的拖尾效果
// - 缩放自适应(粒子大小和速度)
// - 跨世界渲染支持
// - 交互时自动暂停渲染

8. 热力图层 (HeatmapLayer)

import { MapEngine, HeatmapLayer } from 'mapjar';

// 创建地图引擎
const engine = new MapEngine('#map', {
  center: [105, 35],
  zoom: 4,
});

// 创建热力图层
const heatmapLayer = new HeatmapLayer('temperature', {
  colorRamp: [
    { value: 0.0, color: '#313695' },  // 最冷
    { value: 0.5, color: '#ffffbf' },  // 中间
    { value: 1.0, color: '#a50026' },  // 最热
  ],
  wrapX: true,  // 启用跨世界渲染(默认 true)
});

// 方式1:直接使用图片(推荐,更高效)
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = async () => {
  const bitmap = await createImageBitmap(img);
  heatmapLayer.setData({
    image: bitmap,
    bounds: {
      minLon: 55,
      minLat: 1,
      maxLon: 155,
      maxLat: 57,
    },
  });
  engine.addLayer(heatmapLayer);
};
img.src = 'temperature.png';

// 方式2:使用数值数组(向后兼容)
heatmapLayer.setData({
  values: new Float32Array([...]),  // 温度值数组
  width: 100,
  height: 50,
  min: -10,
  max: 40,
  alpha: new Float32Array([...]),   // 可选:透明度数组
  bounds: {
    minLon: 55,
    minLat: 1,
    maxLon: 155,
    maxLat: 57,
  },
});

// 动态切换颜色方案
heatmapLayer.setColorRamp([
  { value: 0.0, color: '#0000FF' },
  { value: 0.5, color: '#00FF00' },
  { value: 1.0, color: '#FF0000' },
]);

// 特性:
// - 直接使用图片作为纹理,GPU 加速
// - 支持自定义颜色渐变(ColorStop 格式)
// - 自动 Y 轴翻转以匹配地理坐标系
// - 支持透明度通道(A 通道)
// - 跨世界渲染支持
// - 动态切换颜色方案

颜色映射格式:

// 格式1:字符串数组(均匀分布)
colorRamp: ['#0000FF', '#00FF00', '#FF0000']

// 格式2:ColorStop 数组(精确控制,推荐)
colorRamp: [
  { value: 0.0, color: '#0000FF' },  // 0% 位置
  { value: 0.3, color: '#00FF00' },  // 30% 位置
  { value: 1.0, color: '#FF0000' },  // 100% 位置
]

应用场景:

  • 温度场可视化
  • 降水量分布
  • 气压场显示
  • 污染物浓度
  • 海拔高度图
  • 任何连续数值场

9. 覆盖层图层 (OverlayLayer)

import { OverlayLayer } from 'mapjar';

// 创建覆盖层图层(一个图层只包含一个覆盖层)
const overlayLayer = new OverlayLayer('overlay');
engine.addLayer(overlayLayer);

// 创建 HTML 元素
const element = document.createElement('div');
element.innerHTML = `
  <div style="
    background: white;
    padding: 10px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.3);
    font-family: Arial;
  ">
    <h3 style="margin: 0 0 5px 0;">北京</h3>
    <p style="margin: 0; color: #666;">中国首都</p>
  </div>
`;

// 设置覆盖层
overlayLayer.setOverlay({
  element: element,
  position: {
    lon: 116.4074,
    lat: 39.9042,
    offset: [0, -20],      // 向上偏移 20px
    anchor: [0.5, 1.0],    // 底部中心锚点
  },
  properties: { name: '北京' },
});

// 更新位置
overlayLayer.updateOverlay({
  position: {
    lon: 116.5,
    lat: 40.0,
  }
});

// 更新元素
const newElement = document.createElement('div');
newElement.textContent = '新内容';
overlayLayer.updateOverlay({
  element: newElement
});

// 更新多个参数
overlayLayer.updateOverlay({
  element: newElement,
  position: { lon: 116.5, lat: 40.0 },
  visible: false
});

// 清空覆盖层
overlayLayer.clearOverlay();

// 特性:
// - 在地图上叠加单个 HTML 元素
// - 自动跟随地图平移和缩放
// - 支持自定义锚点和偏移
// - 支持完整的 CSS 样式和交互
// - 支持更新所有参数(element、position、visible、properties)
// - 统一的 DPI 管理,在高分辨率屏幕上位置准确
// - 适合复杂的标注、弹窗、自定义控件等

位置配置:

interface OverlayPosition {
  lon: number;           // 经度
  lat: number;           // 纬度
  offset?: [number, number];  // 偏移 [x, y],默认 [0, 0]
  anchor?: [number, number];  // 锚点 [x, y],默认 [0.5, 0.5](中心)
}

// 锚点示例:
// [0, 0]     - 左上角
// [0.5, 0]   - 顶部中心
// [1, 0]     - 右上角
// [0, 0.5]   - 左侧中心
// [0.5, 0.5] - 中心(默认)
// [1, 0.5]   - 右侧中心
// [0, 1]     - 左下角
// [0.5, 1]   - 底部中心
// [1, 1]     - 右下角

更新参数:

// 可以更新任意参数组合
overlayLayer.updateOverlay({
  element?: HTMLElement,           // 更新 HTML 元素
  position?: OverlayPosition,      // 更新位置
  visible?: boolean,               // 更新可见性
  properties?: Record<string, unknown>  // 更新属性
});

应用场景:

  • 地图标注(Marker)
  • 信息弹窗(Popup)
  • 自定义控件
  • 复杂的交互式标签
  • 富文本内容展示
  • 图片、视频等媒体内容
  • 动态更新的内容展示

10. Canvas 图层 (CanvasLayer)

import { CanvasLayer } from 'mapjar';

// 创建自定义 Canvas 图层
class MyCanvasLayer extends CanvasLayer {
  constructor(id: string) {
    super(id, 512, 512); // 指定 Canvas 尺寸
  }

  // 实现自定义渲染逻辑
  render(gl: WebGL2RenderingContext, viewMatrix: Float32Array): void {
    if (!this.visible) return;

    const ctx = this.getContext();
    
    // 清空 Canvas
    this.clear();
    
    // 使用 Canvas 2D API 绘制
    ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
    ctx.fillRect(100, 100, 200, 200);
    
    ctx.strokeStyle = 'blue';
    ctx.lineWidth = 3;
    ctx.strokeRect(150, 150, 100, 100);
    
    // Canvas 内容会自动转换为 WebGL 纹理渲染
  }
}

const canvasLayer = new MyCanvasLayer('custom');
engine.addLayer(canvasLayer);

// 特性:
// - 纯 Canvas 2D 渲染
// - 自动转换为 WebGL 纹理
// - 适合自定义绘制逻辑
// - 支持所有 Canvas 2D API

11. 数据驱动样式

根据要素属性动态设置样式:

import { VectorLayer, StyleFunction } from 'mapjar';

const layer = new VectorLayer('vector');

// 基于属性的颜色映射
layer.setDataDrivenStyle({
  fillColor: StyleFunction.createPropertyColorMap(
    'type',
    {
      'residential': [0.8, 0.8, 0.6, 0.5],  // 住宅区 - 米黄色
      'commercial': [1.0, 0.6, 0.6, 0.5],   // 商业区 - 粉红色
      'park': [0.4, 0.8, 0.4, 0.5],         // 公园 - 绿色
    },
    [0.5, 0.5, 0.5, 0.5] // 默认颜色
  )
});

// 数值范围的颜色插值
layer.setDataDrivenStyle({
  fillColor: StyleFunction.createNumericColorScale(
    'population',
    [
      [0, [0.2, 0.4, 1.0, 0.5]],      // 低密度 - 蓝色
      [5000, [0.4, 0.8, 0.8, 0.5]],   // 中密度 - 青色
      [10000, [0.8, 0.8, 0.4, 0.5]],  // 中高密度 - 黄色
      [20000, [1.0, 0.4, 0.2, 0.5]],  // 高密度 - 橙色
    ],
    [0.5, 0.5, 0.5, 0.5]
  )
});

// 自定义样式函数
layer.setDataDrivenStyle({
  fillColor: (properties) => {
    const value = properties.temperature as number;
    if (value < 0) return [0.2, 0.4, 1.0, 0.6];      // 冷 - 蓝色
    if (value < 20) return [0.4, 0.8, 0.4, 0.6];     // 温和 - 绿色
    if (value < 30) return [1.0, 0.8, 0.2, 0.6];     // 温暖 - 黄色
    return [1.0, 0.2, 0.2, 0.6];                     // 热 - 红色
  },
  pointSize: (properties) => {
    const importance = properties.importance as number || 1;
    return importance * 2;
  }
});

样式函数工具:

  • createPropertyColorMap - 基于分类属性的颜色映射
  • createNumericColorScale - 基于数值范围的颜色插值
  • createNumericSizeScale - 基于数值范围的大小插值
  • createConditionalStyle - 基于条件的样式选择

应用场景:

  • 人口密度热力图
  • 交通流量可视化
  • POI 分类显示
  • 建筑高度可视化

12. 图层管理

// 获取图层
const layer = engine.getLayer('osm');

// 设置可见性
layer?.setVisible(false);

// 设置透明度
layer?.setOpacity(0.5);

// 设置层级
layer?.setZIndex(10);

// 移除图层
engine.removeLayer('osm');

13. 事件系统

// 新的事件 API(推荐)
engine.on('click', (event) => {
  console.log('点击:', event.lon, event.lat);
});

engine.on('mousemove', (event) => {
  console.log('鼠标:', event.lon, event.lat);
});

// 一次性事件监听
engine.once('click', (event) => {
  console.log('只触发一次');
});

// 移除事件监听
const removeListener = engine.on('click', handler);
removeListener(); // 调用返回的函数移除监听

// 或使用 off 方法
engine.off('click', handler);

// 移除所有监听器
engine.removeAllListeners('click');
engine.removeAllListeners(); // 移除所有事件的监听器

// 事件类型
type MapEventMap = {
  click: MapClickEvent;
  mousemove: MapMouseMoveEvent;
};

交互操作

鼠标操作

  • 拖拽平移:按住鼠标左键拖动
  • 滚轮缩放:鼠标滚轮上下滚动(每次 1 级,带动画)
  • 双击放大:双击鼠标左键(Shift + 双击缩小,带动画)

触摸操作

  • 单指拖拽:单指按住拖动
  • 双指轻触:快速连续轻触两次放大(带动画)

动画效果

  • 平滑过渡:所有缩放操作都带有流畅的动画效果
  • 缓动函数:使用 easeOutQuad 缓动,柔和自然
  • flyTo 动画:平滑飞行到目标位置和缩放级别
  • fitBounds 动画:自动适配边界并平滑过渡
  • 可自定义:支持自定义动画时长和缓动函数

🎯 性能优化

Web Worker 瓦片加载

| 指标 | 主线程 | Worker | 提升 | |------|--------|--------|------| | 首屏加载 | 2.5s | 1.8s | 28% ↓ | | 主线程阻塞 | 150ms | 20ms | 87% ↓ | | 帧率下降 | 15 fps | 3 fps | 80% ↓ |

瓦片清晰度优化

| 指标 | 优化前 | 优化后 | 提升 | |------|--------|--------|------| | 纹理上传次数 | 每帧 | 仅一次 | 99% ↓ | | 渲染时间 | 8ms | 3ms | 62% ↓ | | 视觉质量 | ⭐⭐ | ⭐⭐⭐⭐⭐ | 显著提升 |

优化建议

  1. 瓦片图层:使用 CDN 加速瓦片加载,启用 Worker 并发加载
  2. 瓦片缩放:根据需求调整 tileScale,平衡清晰度和性能
  3. 跨世界渲染:全球地图启用 wrapX: true,区域地图禁用
  4. 矢量图层:避免添加过多要素(建议 < 10000)
  5. 渲染循环:不需要时调用 engine.stop() 停止渲染
  6. 图层层级:合理设置 zIndex,减少重绘

🌐 浏览器兼容性

WebGL2 支持

  • ✅ Chrome 56+
  • ✅ Firefox 51+
  • ✅ Safari 15+
  • ✅ Edge 79+

覆盖 97%+ 的浏览器

📚 API 参考

MapEngine

flyTo(lon, lat, zoom?, options?)

平滑飞行到指定位置和缩放级别。

参数:

  • lon (number): 目标经度
  • lat (number): 目标纬度
  • zoom (number, 可选): 目标缩放级别,默认保持当前缩放
  • options (object, 可选):
    • duration (number): 动画时长(毫秒),默认自动计算
    • maxDuration (number): 最大动画时长(毫秒),默认 3000

示例:

// 飞到北京,缩放到 10 级
engine.flyTo(116.4074, 39.9042, 10);

// 自定义动画时长
engine.flyTo(121.4737, 31.2304, 12, { duration: 2000 });

// 只改变位置,保持当前缩放
engine.flyTo(113.2644, 23.1291);

fitBounds(bounds, options?)

自动适配到指定边界,并平滑过渡。

参数:

  • bounds (object): 边界对象
    • minLon (number): 最小经度
    • minLat (number): 最小纬度
    • maxLon (number): 最大经度
    • maxLat (number): 最大纬度
  • options (object, 可选):
    • duration (number): 动画时长(毫秒),默认自动计算
    • maxDuration (number): 最大动画时长(毫秒),默认 3000
    • padding (number): 边界填充(像素),默认 50

示例:

// 适配中国边界
engine.fitBounds({
  minLon: 73.5,
  minLat: 18.2,
  maxLon: 135.0,
  maxLat: 53.5
});

// 自定义填充和动画时长
engine.fitBounds(
  {
    minLon: 110,
    minLat: 30,
    maxLon: 120,
    maxLat: 40
  },
  { padding: 100, duration: 1500 }
);

Camera

flyTo(lon, lat, zoom?, options?)

相机级别的 flyTo 方法,与 MapEngine.flyTo 相同。

fitBounds(bounds, options?)

相机级别的 fitBounds 方法,与 MapEngine.fitBounds 相同。

📁 项目结构

mapjar/
├── lib/                          # 库源代码
│   ├── animation/                # 动画系统
│   │   ├── Easing.ts            # 缓动函数
│   │   ├── FlyToAnimation.ts    # 飞行动画
│   │   └── ZoomAnimation.ts     # 缩放动画
│   ├── core/                     # 核心模块
│   │   ├── Camera.ts            # 相机系统
│   │   └── WebGL2Renderer.ts    # WebGL2 渲染器
│   ├── layer/                    # 图层系统
│   │   ├── Layer.ts             # 图层基类
│   │   ├── RasterLayer.ts       # 栅格图层基类
│   │   ├── TileLayer.ts         # 瓦片图层
│   │   ├── ImageLayer.ts        # 图像图层
│   │   ├── VectorLayer.ts       # 矢量图层
│   │   ├── GeoJSONLayer.ts      # GeoJSON 图层
│   │   ├── WindLayer.ts         # 风场动画图层
│   │   ├── HeatmapLayer.ts      # 热力图层
│   │   ├── OverlayLayer.ts      # 覆盖层图层
│   │   └── CanvasLayer.ts       # Canvas 图层
│   ├── math/                     # 数学工具
│   │   ├── Vec2.ts              # 二维向量
│   │   └── Projection.ts        # 投影系统
│   ├── spatial/                  # 空间查询
│   │   └── SpatialQuery.ts      # 空间查询工具
│   ├── style/                    # 样式系统
│   │   └── StyleFunction.ts     # 数据驱动样式
│   ├── utils/                    # 工具类
│   │   ├── BatchRenderer.ts     # 批量渲染器
│   │   ├── EventEmitter.ts      # 事件发射器
│   │   ├── FrustumCulling.ts    # 视锥剔除
│   │   ├── Loader.ts            # 资源加载器
│   │   ├── ResourceManager.ts   # 资源管理器
│   │   ├── TextRenderer.ts      # 文字渲染器
│   │   └── WebGLUtils.ts        # WebGL 工具函数
│   ├── workers/                  # Web Workers
│   │   ├── TileLoader.worker.ts # 瓦片加载 Worker
│   │   └── TileLoader.worker.factory.ts # Worker 工厂
│   ├── MapEngine.ts             # 地图引擎主类
│   └── main.ts                  # 导出入口
├── examples/                     # 示例应用
│   ├── views/                   # 示例页面
│   │   ├── Home.vue             # 首页
│   │   ├── TileLayerExample.vue # 瓦片图层示例
│   │   ├── VectorLayerExample.vue # 矢量图层示例
│   │   ├── GeoJSONLayerExample.vue # GeoJSON 图层示例
│   │   ├── ImageLayerExample.vue # 图像图层示例
│   │   ├── WindLayerExample.vue # 风场图层示例
│   │   ├── WindLayerExample2.vue # 风场图层示例 2
│   │   ├── HeatmapLayerExample.vue # 热力图层示例
│   │   ├── OverlayLayerExample.vue # 覆盖层图层示例
│   │   └── CombinedExample.vue  # 综合示例
│   ├── router/                  # 路由配置
│   ├── components/              # 公共组件
│   ├── App.vue                  # 应用根组件
│   └── main.ts                  # 应用入口
└── index.d.ts                    # TypeScript 类型声明

❓ 常见问题

Q: 瓦片加载失败?
A: 检查瓦片 URL 是否正确,是否需要 API Key,是否有跨域问题(CORS)。

Q: 矢量要素不显示?
A: 确保坐标在可见范围内,检查图层的 zIndex 是否被其他图层遮挡,检查样式的透明度是否为 0。

Q: 性能问题?
A: 减少矢量要素数量(建议 < 10000),使用瓦片图层代替大量矢量数据,调整 tileScale 平衡清晰度和性能,启用视锥剔除。

Q: 瓦片显示模糊?
A: 瓦片已自动启用 Mipmap 和各向异性过滤。如果文字太小,可以增加 tileScale 参数(1.0 - 3.0)。

Q: 地图边界有缝隙?
A: 确保瓦片服务器支持跨越 180° 经线的瓦片,或禁用 wrapX 选项。

Q: 如何添加自定义瓦片源?
A: 只需提供符合 {z}/{x}/{y} 格式的 URL 模板即可。

Q: 风场/热力图数据如何准备?
A: 推荐使用图片格式:风场数据用 R/G 通道存储 U/V 分量,A 通道存储有效性;热力图直接使用灰度图或彩色图。

Q: 移动端显示太小?
A: 引擎已自动适配高 DPI 屏幕(通过 Camera.getResolution() 的 DPI 缩放),无需额外配置。

Q: 如何自定义图层?
A: 继承 LayerRasterLayerCanvasLayer 基类,实现 render() 方法即可。

🗺️ 开发路线图

Phase 1: 基础渲染 ✅

  • [x] WebGL2 初始化
  • [x] 渲染循环
  • [x] 响应式 canvas 大小
  • [x] 高 DPR 支持

Phase 2: 地图基础 ✅

  • [x] EPSG:3857 投影系统
  • [x] 相机系统(平移、缩放、旋转)
  • [x] 相机动画(flyTo、fitBounds)
  • [x] 坐标转换
  • [x] 瓦片加载和渲染
  • [x] Web Worker 并发加载
  • [x] 矢量图层(点、线、面)
  • [x] 鼠标/触摸交互
  • [x] 事件系统(点击、鼠标移动)

Phase 3: 高级功能 ✅

  • [x] GeoJSON 支持(完整实现)
  • [x] 多边形三角化(Earcut,支持凹多边形和带洞多边形)
  • [x] 圆形点渲染(片段着色器实现,带抗锯齿)
  • [x] 数据驱动样式(基于属性的动态样式)
  • [x] 空间查询(点查询、范围查询、最近邻查询)
  • [x] 图像图层(ImageLayer,支持历史地图叠加等)
  • [x] 栅格图层基类(RasterLayer,统一渲染管线)
  • [x] 文字渲染(TextRenderer,支持自定义字体、颜色、描边)
  • [x] 跨世界渲染(水平无限循环)
  • [x] 视锥剔除(FrustumCulling)
  • [x] 批量渲染(BatchRenderer)

Phase 4: 科学可视化 ✅

  • [x] 风场动画图层(WindLayer,粒子系统)
  • [x] 热力图层(HeatmapLayer,温度、降水等)
  • [x] Canvas 图层(CanvasLayer,自定义绘制)
  • [x] 从图片加载数据(支持 Alpha 通道)
  • [x] 颜色映射(ColorStop 格式)

Phase 5: 标注和覆盖层 ✅

  • [x] 覆盖层图层(OverlayLayer,HTML 元素叠加)
  • [ ] 标注系统(Label/Marker,优化的文字标注)
  • [ ] 符号化渲染(Symbol Layer)
  • [ ] 要素选择和高亮
  • [ ] 矢量瓦片支持(MVT/PBF)
  • [ ] 更多栅格图层(WMS、WMTS、Video)
  • [ ] 3D 地形渲染
  • [ ] 聚类(Clustering)

🛠️ 技术栈

  • TypeScript
  • WebGL2
  • Web Workers
  • ImageBitmap API
  • earcut - 多边形三角化
  • Vue 3
  • Vite

📄 License

MIT

🤝 贡献

欢迎提交 Issue 和 Pull Request!