计算贝塞尔曲线的长度

贝塞尔曲线是工业上经常用的一种曲线,经常用用来汽车的外观设计。贝塞尔曲线根据控制点的不同可以分为:

  1. 一阶贝塞尔曲线(2 个控制点)
  2. 二阶贝塞尔曲线(3 个控制点)
  3. 三阶贝塞尔曲线(4 个控制点)
  4. n 阶贝塞尔曲线(n+1 个控制点)

# 二阶贝塞尔曲线

这次讲述的是二阶贝塞尔曲线的长度计算。首先计算曲线的长度之前,我们需要知道曲线的数学方程表达式,由于目前博客还未支持数学表达式的显示,所以我只能帖出wiki 链接。求曲线的长度,本质上是很难计算出精确值的,但只要近似值的误差绝对小,在实际使用中也是足够的。求曲线的长度本质上是进行定积分的计算。

# 高斯求积

在定积分的数值计算中,高斯求积可以说是一个精度非常高的公式。

我们只需要把二阶贝塞尔曲线代入高斯求积公式中便可以计算出结果,求积公式的节点个数 n 越大,精度就越高。

不过高斯求积公式中节点个数对应的位置和权重表的计算,我还是没弄明白。

# 代码实现

已经有人给出了代码实现,所以大家可以直接去 Github 上查看bezier.js

并且有详细的解释:Arc length

# 总结

曲线的计算都可以归纳成对定积分的计算,只要知道曲线的数学方程式,就可以使用定积分的数值计算来计算出结果。

# 引用

A Primer on Bézier Curves

使用Docker运行node项目

使用 Docker 容器化开发和部署,是当今的主流。因为程序跑在了容器之内,我们再也不用担心安装各种依赖和版本管理。接下来就来介绍如何使用 Docker 开发自己的 node 项目。

# 使用 docker-compose 管理多个 Docker

很多情况下,一个 Docker 是满足不了需求的,有时候还要使用 MySQL 数据库,有时候还要使用 redis。这时候就需要使用 docker-compose 来管理多个 Docker。docker-compose 的使用也很简单,只要在项目根目录下建立一个docker-compose.yml文件即可。关于 YAML 的学习,可以参考Learn YAML in five minutes!

# docker-compose 内容定义

首先我们需要声明版本号version,不同的 Docker 版本对 docker-compose 的版本支持也不一样,可以参考官方的文档进行对照。 然后声明services,service 下的每一个声明都是一个 Docker 的实例。下面介绍一个实际例子:

version: '3.7'
services:
  pg:
    image: postgres:9.5
    ports:
      - '${DB_EXPORT_PORT-54320}:5432'
    environment:
      POSTGRES_USER: '${DB_USER-postgres}'
      POSTGRES_PASSWORD: '${DB_PASS-123456}'
      POSTGRES_DB: '${DB_NAME-dmhy_indexer}'

  main:
    build: .
    cap_add:
      - SYS_ADMIN # ref https://github.com/GoogleChrome/puppeteer/blob/v1.12.1/docs/troubleshooting.md#running-puppeteer-in-docker
    image: 'indexer'
    command: yarn start
    ports:
      - '9229:9229'
    environment:
      INDEXER_MODE: '${INDEXER_MODE-dmhy}'
      DB_HOST: '${DB_HOST-pg}'
      DB_PORT: '${DB_PORT-5432}'
      DB_USER: '${DB_USER-postgres}'
      DB_NAME: '${DB_NAME-dmhy_indexer}'
      DB_PASS: '${DB_PASS-123456}'
    volumes:
      - '.:/irohalab/indexer'
      - '/irohalab/indexer/node_modules'
      - '/irohalab/indexer/dist'
    depends_on:
      - pg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

这个配置来源于indexer。我们定义了两个 Service。第一个是数据库 Postgres,第二个便是我们的主项目。首先,我们来看数据库配置,声明了使用官方的postgres:9.5镜像,并映射数据库 5432 端口到主机。这里${DB_EXPORT_PORT-54320}的意思是读取环境变量DB_EXPORT_PORT,如果没有的话就使用默认值54320,这也是 YAML 语法的一部分。接下来又定义了数据库启动需要的环境变量参数。

重点是配置的 main 项目,由于 main 项目使用了自定义的 Dockerfile,所以我们需要结合 Dockerfile 的配置来讲述。

FROM node:10.16.3-stretch
RUN apt-get update -qq && apt-get install -y gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
WORKDIR /irohalab/indexer
RUN chown -R node:node /irohalab/indexer
RUN usermod -a -G audio,video node
USER node
COPY package.json yarn.lock ./
RUN yarn install
RUN mkdir dist
COPY . .
1
2
3
4
5
6
7
8
9
10

Dockerfile 的第一句话一定是 FROM,代表着是基于哪个官方镜像来自定义。我们基于 node 的 10.16.3 版本来定制镜像,第一步是安装项目额外需要的依赖,第二步是设定工作目录。由于当前项目需要非 root 权限的用户来运行,所以我们需要之后改变权限,ndoe 镜像官方提供了非 root 用户node,所以我们就把工作目录的权限改成了ndoe。接下来是添加到需要的组,之后就是切换用户到node。 切换之前默认 Docker 是使用 root 权限运行的,切换之后就是node用户了,我们 copy 需要的文件之后,执行yarn install来安装,由于存在主机是 Mac,但是 Docker 是 Linux 的情况,所以主机的node_modules并不能直接用,所以我们需要在 Docker 中安装依赖。接下来又创建了一个 dist 目录,然后复制了整个项目。为什么创建dist目录接下来要讲。

# 使用非 root 权限运行

这部分是 Docker 配置最折腾的地方。因为 Docker 默认是用 root 权限运行的,所以切换到非 root 权限执行程序就会经常遇到 permission denied 的问题。接下来就要讲述 main 部分的配置参数,build: .在当前目录下 build,就是寻找当前目录下的 Dockerfile 并进行 build,cap_add参数是添加Linux capabilities。这部分可以参考Runtime privilege and Linux capabilities。之所以添加是因为puppeteer需要,apt-get 安装的依赖也是。重点是volumes配置,第一行代表当前目录映射到/irohalab/indexer也就是我们设定的工作目录位置。第二行和第三行是为了移除对node_modules文件夹和dist文件夹的映射。但如果 Docker 里没有这两个文件夹就会由 root 来创建,所以我才特地mkdir dist。否则普通用户又会没有权限了。depends_on代表当前镜像的依赖,同时声明依赖之后,Docker 内部之间可以通讯,主机名就是services里定义的名字。

# 使用 dockerignore

dockerignore 和 gitignore 的用法非常类似,当指定了某些文件或文件夹的时候,在执行 COPY 等命令的时候就会忽略这些文件或文件夹。

# 清理 Docker

由于开发过程中各种调试和重新 build,导致产生的无用的镜像数据特别多,所以需要经常清理。官方文档有详细的清理说明

$ docker image prune -a
$ docker system prune --volumes
1
2

第一个命令会清除所有的镜像,不管有没有使用。第二个命令会清除所有不相关或不使用的 Docker 数据。

# 总结

Docker 很强大,但是用起来也没有那么容易。需要不断的尝试,去摸索各种选项的配置和使用。

# 引用

Containerizing a Node.js Application for Development With Docker Compose

How To Build a Node.js Application with Docker

Add a volume to Docker, but exclude a sub-folder

.dockerignore file

求经纬度两点距离

最近一直在做和地图相关的开发,所以一直在研究坐标系,投影相关的知识。整理下,供自己查阅。接下来就讲述描述地球的几种坐标系。

# 世界大地测量系统

世界大地测量系统(World Geodetic System)就是我们常说的经纬度,维度指得是当前地点和赤道的夹角。所以赤道的纬度(Latitude)是 0,北极是正 90,南极是负 90. 经度是本初子午线(Prime meridian)为分界,东边是东经(正值),西边是西经(负值)。当经度为 180 度的时候,我们称之为对向子午线(antimeridian)。当跨过对向子午线的时候,很多相关计算都需要特殊处理,需要注意。这里计算两个坐标的距离的话,由于是球面,其实计算的是两个坐标沿着球面的弧线。本质思想是求出两个坐标的夹角,然后乘以地球半径得出结果。详细细节可以参考Calculate distance, bearing and more between Latitude/Longitude points

# 麦卡托投影

世界大地测量系统是三维描述地球的位置信息,对于导航或者显示来说很不方便。所以在平面上使用麦卡托投影(Mercator projection),可以把三维平铺到二维。但是注意,麦卡托投影会使面积发生变形。所以投影出来的坐标是不能直接用来计算距离的。维度越高,变形就越大。所以需要计算比例因子来纠正。但对于两个坐标纬度相差比较大的情况下,计算就更复杂了。所以不推荐使用麦卡托投影来计算距离。

# 笛卡尔坐标系

笛卡尔坐标系(Cartesian coordinate system)也称直角坐标系,是我们高中时学过的坐标系。我们可以通过经纬度和地球半径来计算出直接坐标系的 X,Y,Z。然后通过直角坐标系中求两点距离公式,计算出两点距离,不过这个距离是直线距离。如果两点距离很远的情况下,可以看作是我们穿过地球的直线距离。可以借助 Cesium 的Cartesian3库方便的完成笛卡尔坐标系的转换和距离计算。

# MapKit 投影

苹果的 MapKit,基于麦卡托投影把经纬度转换成二维坐标。而且做了相关的数学运算,保证投影之后的数值可以像在平面坐标系一样计算距离。不过苹果的实现未知,我也无法在 Web 端使用。

# 总结

坐标系计算中有时候需要考虑坐标的高度信息计算距离,有时候需要计算已知坐标,角度和距离,求另一个坐标, 求两个坐标的插值。 目前还没有找到合适的计算库来解决这些问题。如果大家有相关的建议或者意见,欢迎评论。

参考:

geodesy

Displaying Maps

WGS84 To Mercator