Atlaslog
Atlaslog / Examples / Leaflet / Custom Crs
Leaflet · Runnable Example

Custom Crs

Runnable Leaflet example: Custom Crs. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.

Open standalone → Chinese notes All Leaflet examples
/demos/leaflet-custom-crs.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/leaflet-custom-crs.html
<!--
  示例:自定义投影
  要点:L.CRS.Simple 派生平面坐标系、GridLayer 生成对应瓦片、平面坐标与经纬度两套读数的区别
  依赖:Leaflet 1.9.4 · jsDelivr CDN
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>自定义投影</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css">
<style>
  html,body{height:100%;margin:0}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif}
  .bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
       display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12px;color:#58606e}
  .bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;
       border:1px solid #ccd1d9;background:#fff;color:#16181d}
  .bar button:hover{background:#eef0f3}
  .bar b{color:#16181d;font-weight:600}
  #stage{flex:1 1 auto;min-height:0;position:relative}
  #map{position:absolute;inset:0}
  #ref{position:absolute;right:12px;bottom:12px;width:190px;height:132px;
       border:2px solid #fff;box-shadow:0 6px 18px rgba(0,0,0,.25);z-index:500}
  #ref-label{position:absolute;right:12px;bottom:150px;z-index:500;font-size:11px;
       color:#58606e;background:rgba(255,255,255,.9);padding:2px 6px;border-radius:3px}
  .site{width:18px;height:18px;border-radius:4px;background:#1f5fd0;
        box-shadow:0 0 0 3px rgba(31,95,208,.22);transform:rotate(45deg)}
</style>
</head>
<body>
<div class="bar">
  <button id="fit">适配全图</button>
  <button id="toggle">隐藏网格</button>
  <span id="out">平面坐标 —</span>
  <b>CRS: X-PLANE(1 单位 = 1 米)</b>
</div>
<div id="stage">
  <div id="map"></div>
  <div id="ref"></div>
  <div id="ref-label">对照:Web 墨卡托 + OSM</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
(function () {
  'use strict';

  /* 自定义 CRS:沿用 Simple 的经纬度投影与 y 轴翻转,改代号与缩放基数 */
  var PlaneCRS = L.extend({}, L.CRS.Simple, {
    code: 'X-PLANE',
    /* 0 级时 1 像素 = 1 米,每升一级放大两倍 */
    scale: function (zoom) { return Math.pow(2, zoom); },
    zoom: function (scale) { return Math.log(scale) / Math.LN2; },
    distance: function (a, b) {
      var dx = a.lng - b.lng;
      var dy = a.lat - b.lat;
      return Math.sqrt(dx * dx + dy * dy);
    }
  });

  var AREA = [[0, 0], [1200, 2000]];   /* [y, x] 米 */

  var map = L.map('map', {
    crs: PlaneCRS,
    minZoom: -1,
    maxZoom: 4,
    zoomSnap: 1,
    zoomControl: true,
    attributionControl: false,
    maxBounds: [[-200, -200], [1400, 2200]],
    maxBoundsViscosity: 0.8
  });

  map.fitBounds(AREA);

  /* 平面坐标下的瓦片只能自己生成,OSM 的 3857 瓦片在这里没有对应编号 */
  var GridTiles = L.GridLayer.extend({
    createTile: function (coords) {
      var tile = L.DomUtil.create('canvas', 'leaflet-tile');
      var size = this.getTileSize();
      tile.width = size.x;
      tile.height = size.y;

      var ctx = tile.getContext('2d');
      ctx.fillStyle = '#fbfcfd';
      ctx.fillRect(0, 0, size.x, size.y);

      ctx.strokeStyle = '#e5e9ee';
      ctx.lineWidth = 1;
      for (var i = 0; i <= size.x; i += 64) {
        ctx.beginPath();
        ctx.moveTo(i + 0.5, 0);
        ctx.lineTo(i + 0.5, size.y);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(0, i + 0.5);
        ctx.lineTo(size.x, i + 0.5);
        ctx.stroke();
      }

      var start = this._map.unproject(coords.scaleBy(size), coords.z);
      ctx.fillStyle = '#9aa3b0';
      ctx.font = '11px sans-serif';
      ctx.fillText('x ' + Math.round(start.lng) + ' · y ' + Math.round(start.lat),
                   8, size.y - 8);

      return tile;
    }
  });

  var grid = new GridTiles({ pane: 'tilePane', minZoom: -2, maxZoom: 5 }).addTo(map);

  L.rectangle([[220, 300], [620, 1080]], {
    color: '#17694a',
    weight: 2,
    fillColor: '#22c55e',
    fillOpacity: 0.15,
    renderer: L.svg()
  }).bindTooltip('地块 A · 400 × 780 米', { permanent: true, direction: 'center' }).addTo(map);

  L.rectangle(AREA, {
    color: '#c2410c',
    weight: 1,
    dashArray: '6 4',
    fill: false
  }).addTo(map);

  L.marker([600, 1000], {
    icon: L.divIcon({ className: '', html: '<div class="site"></div>', iconSize: [18, 18], iconAnchor: [9, 9] })
  }).bindPopup('厂区中心<br>平面坐标 x=1000, y=600').addTo(map);

  map.on('mousemove', function (e) {
    document.getElementById('out').textContent =
      '平面坐标 x=' + Math.round(e.latlng.lng) + ',y=' + Math.round(e.latlng.lat) +
      ' 像素 ' + Math.round(e.containerPoint.x) + ',' + Math.round(e.containerPoint.y);
  });

  map.on('mouseout', function () {
    document.getElementById('out').textContent = '平面坐标 —';
  });

  document.getElementById('fit').addEventListener('click', function () {
    map.fitBounds(AREA, { padding: [20, 20] });
  });

  var shown = true;
  document.getElementById('toggle').addEventListener('click', function () {
    shown = !shown;
    if (shown) {
      grid.addTo(map);
      this.textContent = '隐藏网格';
    } else {
      map.removeLayer(grid);
      this.textContent = '显示网格';
    }
  });

  /* 右下角对照图:同一套 API,换成 OSM 的 3857 瓦片 */
  var ref = L.map('ref', {
    center: [39.907359, 116.391263],
    zoom: 9,
    zoomControl: false,
    attributionControl: false,
    dragging: false,
    scrollWheelZoom: false,
    doubleClickZoom: false,
    keyboard: false
  });

  L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', { maxZoom: 19 }).addTo(ref);
  L.circleMarker([39.907359, 116.391263], { radius: 5, color: '#fff', weight: 1,
    fillColor: '#c2410c', fillOpacity: 1 }).addTo(ref);
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More Leaflet examples

Leaflet · EXAMPLEDiv Icon Leaflet · EXAMPLEDraw Circle Leaflet · EXAMPLEDraw Polygon