Category: GIS

  • QGIS 导出 mbtiles 作为离线地图,并在 Laravel 中使用的实践

    QGIS 导出 mbtiles 作为离线地图,并在 Laravel 中使用的实践

    将 QGIS 可见图层导出为离线地图、并与 Laravel 这样的 Web 后端结合使用,MBTiles 格式配合前端的 Leaflet.js 或 OpenLayers 是目前比较常见的一套组合。

    如果地图包含栅格数据(DEM/地形晕渲)和矢量数据(河流),直接导出为 Web 切片(瓦片)是个可行的方向——QGIS 的切片工具只会渲染当前处于勾选(可见)状态的图层,省去了手动筛选的麻烦。

    以下是完整的工作流记录:


    第一阶段:在 QGIS 中导出数据 (MBTiles)

    为什么选 MBTiles?
    相比于导出成千上万个碎小的 .png 图片文件夹(XYZ 目录结构),MBTiles 本质上是一个单文件的 SQLite 数据库,里面存储了所有层级的瓦片图片。它极大地简化了文件的传输、存储,并且 Laravel(PHP)原生支持极速读取 SQLite。

    具体步骤:

    1. 检查图层与投影: 确保左侧图层树中,只有你需要导出的图层被勾选。建议将 QGIS 右下角的项目坐标系(CRS)设置为 Web 地图通用的 EPSG:3857 (WGS 84 / Pseudo-Mercator)。在 Web 离线地图开发中,使用非 EPSG:3857 作为切片坐标系,容易打破 Web 地图的”默认规则”,引发一系列兼容性问题。条件允许的话,尽量统一采用 EPSG:3857。
    2. 打开切片工具: 在顶部菜单栏选择 处理 (Processing) -> 工具箱 (Toolbox)
    3. 搜索工具: 在右侧弹出的工具箱中搜索 XYZ,找到 栅格工具 (Raster tools) 下的 生成 XYZ 瓦片 (MBTiles) / Generate XYZ tiles (MBTiles)
    4. 配置参数:
      • 范围 (Extent): 点击右侧的 ...,选择”使用当前画布范围 (Use Map Canvas Extent)”,或者选择某个图层的范围。
      • 最小缩放级别 (Minimum zoom): 通常设为 03
      • 最大缩放级别 (Maximum zoom): ⚠️ 注意: 如果地图范围较大(比如覆盖整个东亚这样的区域),级别设得太高会导致生成文件呈指数级暴增且耗时极长。建议先从较低的缩放级别(如 78)开始测试,确认文件大小和生成时间可接受后,再酌情提高到 10 左右。
      • 输出文件: 选择一个保存路径,命名为 offline_map.mbtiles
    5. 点击 运行 (Run),等待生成完毕。

    第二阶段:在 Laravel 中提供服务

    现在你有了一个 offline_map.mbtiles(SQLite 数据库文件)。你需要让 Laravel 读取它并输出图片流。

    1. 放置文件: 将生成的 offline_map.mbtiles 放入 Laravel 项目中,例如 storage/app/maps/offline_map.mbtiles
    2. 配置数据库连接:config/database.php 中添加一个新的 SQLite 连接:

      'connections' => [
          // ... 其他连接
          'mbtiles' => [
              'driver'   => 'sqlite',
              'database' => storage_path('app/maps/offline_map.mbtiles'),
              'prefix'   => '',
          ],
      ],
    3. 创建路由与控制器: MBTiles 数据库里通常有一张名为 tiles 的表,包含 zoom_leveltile_columntile_rowtile_data(BLOB 格式的图片)。在 routes/web.php 中添加一个路由:

      use Illuminate\Support\Facades\Route;
      use Illuminate\Support\Facades\DB;
      use Illuminate\Http\Response;
      
      Route::get('/map-tiles/{z}/{x}/{y}', function ($z, $x, $y) {
          // MBTiles 使用的是 TMS 坐标系,Y 轴与标准的 XYZ 相反,需要转换
          $tms_y = (pow(2, $z) - 1) - $y;
      
          $tile = DB::connection('mbtiles')
              ->table('tiles')
              ->where('zoom_level', $z)
              ->where('tile_column', $x)
              ->where('tile_row', $tms_y)
              ->first();
      
          if (!$tile) {
              abort(404); // 或者返回一张透明的占位图片
          }
      
          // 假设导出的格式是 PNG
          return response($tile->tile_data)->header('Content-Type', 'image/png');
      });

    第三阶段:前端渲染 (Leaflet.js)

    在你的 Laravel Blade 视图(或 Vue/React 组件)中,使用常用的开源地图库 Leaflet.js 来加载这些离线瓦片。

    1. 引入 Leaflet 的 CSS 和 JS:

      <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
      <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
      <style>
          #map { height: 600px; width: 100%; }
      </style>
    2. 初始化地图并接入 Laravel 接口:

      <div id="map"></div>
      
      <script>
          // 根据实际地图区域调整中心点坐标和初始缩放级别
          var map = L.map('map').setView([35.0, 105.0], 4);
      
          // 添加离线瓦片图层,指向 Laravel 的路由
          L.tileLayer('/map-tiles/{z}/{x}/{y}', {
              minZoom: 3,
              maxZoom: 8, // 必须与你在 QGIS 中导出的层级匹配
              attribution: 'Offline Map Data'
          }).addTo(map);
      </script>

    替代方案(最简单,但文件多):导出目录结构 (Directory)

    如果你的服务器对 SQLite 拓展支持有问题,或者你想直接用 Nginx 代理静态文件来追求极致性能:

    • 在 QGIS 中选择 生成 XYZ 瓦片 (目录)
    • 将生成的包含层级数字(如 012…)的文件夹整个复制到 Laravel 的 public/tiles/ 目录下。
    • 前端直接写静态路径:L.tileLayer('/tiles/{z}/{x}/{y}.png', {...})
    • 缺点: 高层级地图会产生几十万个碎片文件,非常难移动和删除。
  • 溥仪亲迎体心

    读完陳熙遠《共和國裡的皇室婚禮─宣統大婚與帝制王朝的最後掙扎》,特别在意溥仪这位十七岁少年在遗老翘首期盼和各自的算盘下,在民国众冷嘲热讽下,在民国政府小心运作下,在家族至亲各怀心思下,在仪式感的感染下,在中二少年荷尔蒙的驱动下,到底是怎么是怎么想的。于是决定顺着候仁之《北京历史地图集》宣统大婚迎娶淑妃往返图走了一次。花絮 1:在什刹海附近收到友人的微信“你还活着么?”望了望难得的北京清澈夜空的冬季大三角:“溥仪君,你还是死了之后更开心吧XD”。花絮 2:零下十度骑车的保暖按照零下十到十五度搭配保暖装备准备完全没问题(面罩非常重要),虽然骑起来风大,但是高速运动出汗基本能抵消掉快速散失的热量。不过特别要当心擦鼻涕的纸巾在口袋里被冻成硬质纸板。花絮 3:警车上的警官大人很凶的

    Song 题诗:銀錠橋邊盡酒吧,淑妃邸外憶嬌娃。多情最是王經理,寒夜騎車繞京華;

    Oopus 和诗:只眼橋邊識酒吧,癡人橋上冢中娃。務觀畫外前經理,海角天外笑拈華

  • The only corrected tutorial of connecting points to lines in CartoDB

    https://groups.google.com/forum/#!msg/cartodb/PHKbPWKvMmc/NKe18KSt-pUJ

    Hi Amanda,

    Are you doing the tutorial but with your own data? Did you get the tutorial to work just on its own first? It might help to run through it as is first so you can get a hang of what it does.
    To do it with the table map_1, I would just run this query,
    SELECT 0 as cartodb_id, ST_MakeLine(the_geom_webmercator) the_geom_webmercator FROM map_1
    UNION ALL
    SELECT cartodb_id, the_geom_webmercator FROM map_1
    The first half (before the UNION) makes a line from all your points. The second (after the UNION), just selects the points as is.
    Next, on your map you are probably only going to see points. That is because there is no style set for drawing a line. So I would use the following CSS,
    #map_1 {
     //points
     [mapnik-geometry-type=point] {
        marker-fill: #FF6600;
        marker-opacity: 1;
        marker-width: 12;
        marker-line-color: white;
        marker-line-width: 3;
        marker-line-opacity: 0.9;
        marker-placement: point;
        marker-type: ellipse;marker-allow-overlap: true;
      }
     //lines
     [mapnik-geometry-type=linestring] {
        line-color: #FF6600;
        line-width: 2;
        line-opacity: 0.7;
      }
    }
    hope that helps!
    Andrew