Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >WebWorker 在文本标注中的应用

WebWorker 在文本标注中的应用

作者头像
ConardLi
发布于 2019-10-10 03:18:46
发布于 2019-10-10 03:18:46
5.1K00
代码可运行
举报
文章被收录于专栏:code秘密花园code秘密花园
运行总次数:0
代码可运行

作者:潘与其 - 蚂蚁金服前端工程师 - 喜欢图形学、可视化

在之前数据瓦片方案的介绍中,我们提到过希望将瓦片裁剪放入 WebWorker 中进行,以保证主线程中用户流畅的地图交互(缩放、平移、旋转)。

之前我们的例子没有使用 WebWorker,似乎也并不影响交互。但是本文介绍的针对 Polygon 要素的文本标注方案,将涉及复杂的多边形难抵极运算,如果不放在 WebWorker 中运算将完全卡死无法交互。题图为全球海洋文本的标注效果,数据来自 geojson.xyz,DEMO 地址如下:

https://xiaoiver.github.io/custom-mapbox-layer/?path=/story/textlayer--polygon-feature

首先我们来看看如何确定一个多边形的文本标注锚点,即难抵极的计算方法。

难抵极算法

难抵极(Pole of inaccessibility / PIA)[1]顾名思义,就是从海岸线出发大陆上最难到达的点。直观上来看就是陆地上距离海岸线最远的点(下图的红点)。从几何角度看就是以形状内的各个点为圆心作圆,这些圆不能与边界(海岸线)相交,以难抵极为圆心的圆半径最大。要注意难抵极和 centroid几何中心不是一个概念。

陆地上的难抵极(红点)

论文「Poles of Inaccessibility: A Calculation Algorithm for the Remotest Places on Earth」[2]提出的是一种基于蒙特卡洛方法的算法。核心思路是迭代计算候选区域(经纬度),平均分成 21 * 21 个候选点,分别计算到海岸线的最大距离,然后以该点为中心,以

比例缩小得到新的区域。

而 Mapbox Polylabel [3]使用了基于网格的算法,同样使用迭代找到指定精度下的 PIA。相比上面的方法更快而且是 global optimum [4]的。

基于网格的 PIA 算法

算法步骤如下:

  1. 以多边形的包围盒作为初始网格,使用 ray casting 计算网格中心到多边形边界的有向距离(下图的 dist 负数表示在形外)。
  2. 按照该有向距离排序,将网格加入优先级队列,同时计算该网格内的最大距离 max = dist + radius 其中radius = cell_size * sqrt(2) / 2
  3. 如果当前网格有向距离比之前最佳网格更大,更新最佳网格
  4. 网格出队,如果网格距离大于目前最大距离(指定精度下 max - best_dist > precision ),继续划分网格,将 4 个子网格入队,继续迭代回到 1。
  5. 当优先级队列为空时,迭代终止。此时找到指定精度下的 PIA

代码细节如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function polylabel(polygon, precision, debug) {
  precision = precision || 1.0; // 精度

  // 计算多边形的最小包围盒
  var minX, minY, maxX, maxY;
  for (var i = 0; i < polygon[0].length; i++) {
    var p = polygon[0][i];
    if (!i || p[0] < minX) minX = p[0];
    if (!i || p[1] < minY) minY = p[1];
    if (!i || p[0] > maxX) maxX = p[0];
    if (!i || p[1] > maxY) maxY = p[1];
  }

  var width = maxX - minX;
  var height = maxY - minY;
  // 网格尺寸
  var cellSize = Math.min(width, height);
  var h = cellSize / 2;
  
  // 优先级队列
  var cellQueue = new Queue(null, compareMax);

  // 划分初始网格,全部入队
  for (var x = minX; x < maxX; x += cellSize) {
    for (var y = minY; y < maxY; y += cellSize) {
      // Cell 构造函数中会调用 pointToPolygonDist 计算中点到多边形的距离
      // @see https://github.com/mapbox/polylabel/blob/master/polylabel.js#L90-L109
      cellQueue.push(new Cell(x + h, y + h, h, polygon));
    }
  }
  
  // 初始状态以多边形几何中心作为候选网格
  // @see https://math.stackexchange.com/a/700059
  var bestCell = getCentroidCell(polygon);
  
  while (cellQueue.length) {
    // 网格出队
    var cell = cellQueue.pop();

    // 发现距离多边形边界更远的网格,更新最佳网格
    if (cell.d > bestCell.d) {
      bestCell = cell;
      if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
    }

    // 小于精度阈值,不需要继续划分
    // Cell 构造函数中会计算 max this.max = this.d + this.h * Math.SQRT2;
    if (cell.max - bestCell.d <= precision) continue;

    // 划分成 4 个子网格
    h = cell.h / 2;
    cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
    cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
    cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
    cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
    numProbes += 4;
  }
  
  // 返回 PIA,以最佳网格中心点
  return [bestCell.x, bestCell.y];
}

现在我们解决了给定多边形中找到锚点的问题,但是 GeoJSON 的 Polygon 要素可能由多个子多边形组成(下图中的空洞),我们需要找到多边形的 outer ring 最外层边界,以此作为目标多边形供后续应用上述难抵极算法。

GeoJSON Polygon

多边形分类

一个多边形可能由多个环组成,对于这些环首先需要进行分类:exterior ring & interior ring[5]

多边形中的环

分类涉及到多边形的有向面积计算,正数代表顺时针方向的 exterior ring,而负数代表逆时针方向的 interior ring:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// mapbox/utils/classify_rings.js
export function calculateSignedArea(ring: Array<Point>): number {
    let sum = 0;
    for (let i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
        p1 = ring[i];
        p2 = ring[j];
        sum += (p2.x - p1.x) * (p1.y + p2.y);
    }
    return sum;
}

根据环的方向计算,需要确保 exterior ring 在 interior 之前,在寻找难抵极时只使用 exterior ring 作为锚点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// mapbox/utils/classify_rings.js
const polygons = [];
let polygon,
    ccw;

for (let i = 0; i < len; i++) {
  // 计算有向面积
  const area = calculateSignedArea(rings[i]);
  if (area === 0) continue;

  (rings[i]: any).area = Math.abs(area);

  if (ccw === undefined) ccw = area < 0;
  
  // 下次出现逆时针 interior ring 时再添加
  if (ccw === area < 0) {
    if (polygon) polygons.push(polygon);
    polygon = [rings[i]];

  } else {
    // exterior ring 直接添加
    (polygon: any).push(rings[i]);
  }
}
if (polygon) polygons.push(polygon);

现在我们就找到了难抵极作为多边形的锚点,使用之前我们介绍过的文字渲染方法就能完成标注了。但显然计算难抵极十分复杂,每次发生地图交互尤其是连续缩放、平移、旋转时,都需要重新计算,我亲测会导致主线程完全卡住,为了保证主线程流畅的交互,需要将这部分计算挪到 WebWorker 中进行。

引入 WebWorker

关于 WebWorker 的基本知识以及动态创建方法(Mapbox 目前的 rollup 打包方案会用到),推荐阅读 的文章:

https://zhuanlan.zhihu.com/p/59981684

我们需要定义好主线程与 WebWorker 通信的数据格式,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// https://github.com/xiaoiver/custom-mapbox-layer/blob/master/src/worker/loadData.ts#L10
interface IPayload {
  command: string;
  params: any;
  status: 'success'|'failure';
}

const ctx: Worker = self as any;
ctx.addEventListener('message', async ({ data: payload }) => {
  const { command, params } = payload;
  switch (command) {
// 加载数据 & 创建瓦片索引
    case 'loadData': {
      ctx.postMessage({
        command: 'tileIndexLoaded',
        status: 'success'
      });
    }
// 获取需要渲染的瓦片信息
    case 'getTiles': {
    }
  }
});

我们将 GeoJSON 数据请求和数据瓦片索引的创建都放在 WebWorker 中进行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// https://github.com/xiaoiver/custom-mapbox-layer/blob/master/src/layers/TextLayer.ts#L105-L118
import * as Worker from 'worker-loader!../worker/worker';

// 主线程初始化
initWorker() {
    this.worker = new Worker();
    this.worker.addEventListener('message', this.handleWorkerMessage);
    // 通知 Worker 请求 GeoJSON 数据并创建数据瓦片索引
    this.worker.postMessage({
      command: 'loadData',
      params: {
        url: this.url,
        type: 'geojson',
        isCluster: false
      }
    });
  }

WebWorker 中使用 fetch API 获取 GeoJSON,随后创建数据瓦片索引,这部分之前文章介绍过就不再赘述了。

在我们的例子中,当主线程请求 WebWorker 返回当前视口包含的数据瓦片时,WebWorker 会计算出瓦片包含的 Polygon 要素的难抵极,不影响主线程的交互:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// https://github.com/xiaoiver/custom-mapbox-layer/blob/master/src/worker/feature/text.ts#L15-L26
// 多边形环分类
for (const polygon of classifyRings(rings, 0)) {
  // 计算多边形的难抵极
  const poi = findPoleOfInaccessibility(polygon, 16);
  // 组装该瓦片供文本渲染的结构
  tile.textFeatures.push({
    position: [poi.x, poi.y], // 锚点位置
    text, // 文本内容
  });
}

后续改进

关于 WebWorker 还有很大的改进空间,例如以下三个方面:

  1. 考虑线程间 Transferable 数据传输
  2. 合并连续请求
  3. 在运行时拼接公共代码,减少构建打包大小

现在我们将数据瓦片的索引以及查询都放在了 WebWorker 中完成,如果要进一步解放主线程,顶点数据的组装、包括之前介绍过的顶点压缩方案也可以挪进来。事实上 Mapbox 也是这么做的,另外为了加快线程间数据传输速度,数据格式在设计上也需要考虑 Transferable[6],由于线程上下文转移时不需要拷贝操作,在大数据量传输时将获得较大的效率提升。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// web_worker_transfer.js
type SerializedObject = { [string]: Serialized };
export type Serialized =
    | void
    | boolean
//...省略其他类型
    | ArrayBuffer
    | Array<Serialized>
    | SerializedObject;
// 向 Worker 传递对象前,需要进行序列化
export function serialize(input: mixed, transferables?: Array<Transferable>): Serialized {}

由于相机更新时都需要向 Worker 发送更新瓦片消息,在用户连续 zoomIn/Out 时,会连续发送大量消息到 Worker。我们必须要处理这种情况以减轻 Worker 压力。最简单的办法就是 throttle 节流,但缺点是阈值无法根据数据量动态设定,有可能 Worker 海量数据还没有处理完,下一条更新请求已经到了。因此 Mapbox 的做法是合并多条请求,在主线程中维护一个简单的状态机:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * While processing `loadData`, we coalesce all further
     * `loadData` messages into a single call to _loadData
     * that will happen once we've finished processing the
     * first message. {@link GeoJSONSource#_updateWorkerData}
     * is responsible for sending us the `coalesce` message
     * at the time it receives a response from `loadData`
     *
     *          State: Idle
     *          ↑          |
     *     'coalesce'   'loadData'
     *          |     (triggers load)
     *          |          ↓
     *        State: Coalescing
     *          ↑          |
     *   (triggers load)   |
     *     'coalesce'   'loadData'
     *          |          ↓
     *        State: NeedsLoadData
     */
    coalesce() {
        if (this._state === 'Coalescing') {
            this._state = 'Idle';
        } else if (this._state === 'NeedsLoadData') {
            this._state = 'Coalescing';
            this._loadData();
        }
    }

最后,从构建打包的角度看,很明显 WebWorker 和主线程代码存在大量共用代码,将公共代码抽出并在运行时拼接,动态创建 WebWorker 是不错的方案。但目前 Webpack4 暂时还不支持多种 target(web + webworker)混合的输出模式,相关 ISSUE。如果后续支持,配合 SplitChunksPlugin 应该能解决在 Worker 和不同 entry 之间共享代码的问题。

因此 Mapbox 选择了 rollup 构建[7]出三个 chunk(main,worker 以及 shared),在运行时拼接共用代码动态创建 WebWorker:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// https://github.com/mapbox/mapbox-gl-js/blob/master/rollup/bundle_prelude.js
var shared, worker, mapboxgl;
// define gets called three times: one for each chunk. we rely on the order
// they're imported to know which is which
function define(_, chunk) {
if (!shared) {
    shared = chunk;
} else if (!worker) {
    worker = chunk;
} else {
    var workerBundleString = 'var sharedChunk = {}; (' + shared + ')(sharedChunk); (' + worker + ')(sharedChunk);'

    var sharedChunk = {};
    shared(sharedChunk);
    mapboxgl = chunk(sharedChunk);
    mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' }));
}
}

介绍完了 Point 和 Polygon 的文本标注方案,下篇文章中最复杂的 LineString 终于要登场了。这也是我认为 Mapbox 的一个最佳实践,甚至要优于很多论文中的方案。

https://zhuanlan.zhihu.com/p/74899909

参考

  1. ^Wiki - Pole_of_inaccessibility https://en.wikipedia.org/wiki/Pole_of_inaccessibility
  2. ^Poles of Inaccessibility: A Calculation Algorithm for the Remotest Places on Earth https://docs.google.com/uc?id=0B_xuyENh5ksFOGE1ZmU5ZjQtNjZmNi00ZTRlLWIyZGUtNmRiNDhhNzRkYTA1&export=download&hl=en
  3. ^Mapbox - Polylabel https://github.com/mapbox/polylabel
  4. ^Wiki - Local_optimum https://en.wikipedia.org/wiki/Local_optimum
  5. ^ArcGIS -IRing_IsExterior https://enterprise.arcgis.com/en/sdk/latest/windows/IRing_IsExterior.html
  6. ^MDN - Transferable https://developer.mozilla.org/en-US/docs/Web/API/Transferable
  7. ^Mapbox Rollup 构建方案 https://github.com/mapbox/mapbox-gl-js/blob/master/rollup.config.js
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[1025]python地理处理包shapely
github:https://github.com/Toblerity/Shapely
周小董
2021/07/20
4.7K0
[1025]python地理处理包shapely
OpenLayers入门(二)
好久不见,距离OpenLayers入门第一篇已经过了很久,为什么迟迟没有后续呢,主要有两个原因,一是因为近期项目里使用地图的部分比较少,二是因为很多时候即使功能做出来了,但是还是不能完全理解,不是很明白的东西除了贴代码之外也写不了啥,其实第一篇也是很基础很简单的,但是意外的是看的人是最多的,这让我意识到可能即使是贴一下代码对一些人也是有帮助的,这就是这一篇的主要目的,可能有一些地方会看不懂,但是不要问,问我也不知道,如果你恰好了解的话十分欢迎在评论里分享,感谢~
街角小林
2022/06/15
2.9K0
OpenLayers入门(二)
MySQL中地理位置数据扩展geometry的使用心得
  geometry推荐在5.6版本以上使用,尽管大部分功能在5.5已经可用,除了距离计算函数st_distance等新增函数。
星哥玩云
2022/08/17
3.3K0
MySQL中地理位置数据扩展geometry的使用心得
OpenCV源码系列|图像自选融合
应用场景:挖取a图小块放在b图中,美图秀秀呀,抠图软件制作等 视觉效果: 代码实现: #include "opencv2/photo.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/core.hpp" #include <iostream> // we're NOT "using namespace std;" he
用户9831583
2022/06/16
3390
OpenCV源码系列|图像自选融合
GIS拓扑讲解点线面几何体的拓扑关系判断及运算分析_turf案例
Turf.js是JavaScript  空间分析库,由Mapbox 提供,Turf 实现了
周陆军博客
2023/04/09
2.7K0
GIS常用npm包:GeoJSON文件合并与元素过滤\属性过滤\图形合并
普通的geoJSON文件合并,只需geojson-merge插件就够了,https://www.npmjs.com/package/@mapbox/geojson-merge
周陆军博客
2023/04/09
1.6K0
GeoJson格式标准规范
2016 年 8 月发布,取代了 2008 年的 GeoJSON规范成为 GeoJSON 格式的新标准规范。
用户8860231
2023/04/30
3.2K0
(数据科学学习手札74)基于geopandas的空间数据分析——数据结构篇
geopandas是建立在GEOS、GDAL、PROJ等开源地理空间计算相关框架之上的,类似pandas语法风格的空间数据分析Python库,其目标是尽可能地简化Python中的地理空间数据处理,减少对Arcgis、PostGIS等工具的依赖,使得处理地理空间数据变得更加高效简洁,打造纯Python式的空间数据处理工作流。本系列文章就将围绕geopandas及其使用过程中涉及到的其他包进行系统性的介绍说明,每一篇将尽可能全面具体地介绍geopandas对应方面的知识,计划涵盖geopandas的数据结构、投影坐标系管理、文件IO、基础地图制作、集合操作、空间连接与聚合。   作为基于geopandas的空间数据分析系列文章的第一篇,通过本文你将会学习到geopandas中的数据结构。 geopandas的安装和使用需要若干依赖包,如果不事先妥善安装好这些依赖包而直接使用pip install geopandas或conda install geopandas可能会引发依赖包相关错误导致安装失败,官方文档中的推荐安装方式为:
Feffery
2020/02/15
2.9K0
【翻译】GeoJSON格式规范-RFC7946
本文翻译自https://tools.ietf.org/html/rfc7946 ,2018年1月27,28日两个大雪的周末,以序纪念。
囚兔
2018/01/28
7.1K0
基于均值坐标(Mean-Value Coordinates)的图像融合算法的优化实现
我在之前的文章《基于均值坐标(Mean-Value Coordinates)的图像融合算法的具体实现》中,根据《Coordinates for Instant Image Cloning》这篇论文,详细论述了图像融合中泊松融合算法的优化算法——均值坐标(Mean-Value Coordinates)融合算法的具体实现。其实在这篇论文中,还提出了两种优化实现,能够进一步提升效率,这里就论述一下其优化算法的具体实现。
charlee44
2020/03/21
1.2K0
Arcgis for js之WKT和GEOMETRY的相互转换
WKT(Well-known text)是一种文本标记语言,用于表示矢量几何对象、空间参照系统及空间参照系统之间的转换。它的二进制表示方式,亦即WKB(well-known-binary)则胜于在传输和在数据库中存储相同的信息。该格式由开放地理空间联盟(OGC)制定。WKT可以表示的几何对象包括:点,线,多边形,TIN(不规则三角网)及多面体。以下为几何WKT字串样例: POINT(6 10) LINESTRING(3 4,10 50,20 25) POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)) MULTIPOINT(3.5 5.6, 4.8 10.5) MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4)) MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),((6 3,9 2,9 4,6 3))) GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) POINT ZM (1 1 5 60) POINT M (1 1 80) POINT EMPTY MULTIPOLYGON EMPTY
牛老师讲GIS
2018/10/23
2.4K0
(数据科学学习手札77)基于geopandas的空间数据分析——文件IO
  在上一篇文章中我们对geopandas中的坐标参考系有了较为深入的学习,而在日常空间数据分析工作中矢量文件的读入和写出,是至关重要的环节。
Feffery
2020/02/26
2.2K0
烧脑!JS+Canvas 带你体验「偶消奇不消」的智商挑战
看似简单却具有极大的挑战性和趣味性,这就是其魅力所在!温馨提示,体验后再阅读此文体验更佳哦!
苏南
2020/12/16
1.5K0
烧脑!JS+Canvas 带你体验「偶消奇不消」的智商挑战
百度地图API开发指南(二)
除了指定停靠位置外,还可以通过偏移量来指示控件距离地图边界有多少像素。如果两个控件的停靠位置相同,那么控件可能会重叠在一起,这时就可以通过偏移值使二者分开显示。
幽鸿
2020/04/01
1.8K0
百度地图API开发指南(二)
geotools中等值面的生成与OL3中的展示
本文讲述如何在geotools中IDW插值生成等值面,并根据给定shp进行裁剪,并生成geojson数据,以及Openlayers3中展示。
牛老师讲GIS
2018/10/23
2.4K0
geotools中等值面的生成与OL3中的展示
关于Python可视化Dash工具
Dash是基于Flask的Python可视化工具,严格说来由三个部分组成,首先是Flask提供了标准web环境,再次是plotly这个图表可视化工具,最后是与dash相配套的html、图表等交互式组件。本人也陆续试过pyechart,但就集成性和可视化而言,与dash还是有一定差距。
python与大数据分析
2022/03/11
3.4K0
OpenCV+OpenGL 双目立体视觉三维重建
这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重建。我在做双目立体视觉问题时,主要关注的点是立体匹配,本文主要关注最后一个步骤三维重建中的:三角剖分和纹理贴图以及对应的OpenCV+OpenGL代码实现。
流川疯
2019/01/18
5.5K0
一篇文章带你玩转PostGIS空间数据库
人类理解世界其实是按照三维的角度,而传统的关系型数据库是二维的,要想描述空间地理位置,点、线、面,我们就需要一个三维数据库,即所谓空间数据库。
半旧518
2023/10/17
8.8K0
一篇文章带你玩转PostGIS空间数据库
JAVA智能设备基于OpenGL的3D开发技术 之AABB碰撞检测算法论述
摘要:无论是PC机的3D还是智能设备应用上,碰撞检测始终是程序开发的难点,甚至可以用碰撞检测作为衡量3D引擎是否完善的标准。现有许多3D碰撞检测算法,其中AABB碰撞检测是一种卓有成效而又经典的检测算法,本文将为读者详细论述AABB碰撞检测的各各技术点。 关键词:J2ME;Open GL;JSR-184;M3G;CLDC2.0;3D引擎;Swerve引擎;AABB碰撞检测; 第一部分、前述: 对于移动 终端有限的运算能力,几乎不可能检测每个物体的多边形和顶点的穿透,那样的运算量对手机等设备来讲是不可完成的,
庞小明
2018/03/07
1.2K0
JAVA智能设备基于OpenGL的3D开发技术 之AABB碰撞检测算法论述
geopandas:Python绘制数据地图
GeoPandas是一个Python开源项目,旨在提供丰富而简单的地理空间数据处理接口。 GeoPandas扩展了Pandas的数据类型,并使用matplotlib进行绘图。GeoPandas官方仓库地址为:GeoPandas。 GeoPandas的官方文档地址为:GeoPandas-doc。 本文主要参考GeoPandas Examples Gallery。 GeoPandas的基础使用见Python绘制数据地图1-GeoPandas入门指北。 GeoPandas的可视化入门见Python绘制数据地图2-GeoPandas地图可视化。
luckpunk
2023/09/14
3.9K0
geopandas:Python绘制数据地图
推荐阅读
相关推荐
[1025]python地理处理包shapely
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验