AboutFE
AboutFE copied to clipboard
21、第三方相关API接入
百度地图SDK接入指南
在自组网设备管理后台项目中,需要接入百度地图显示设备地理位置,在接入过程中遇到了地理坐标偏移的问题,故有此文。
SDK脚本引入
百度地图SDK是通过链接给出的,而不是npm package,所以不支持require/import
,而只能通过<script>
引入。
静态引入
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak={您的密钥}">
如果在
中加载(异步),要注意必须载入完成之后才能使用SDK。缺点:页面载入时就加载脚本。
对于只有部分页面使用到地图的,可以使用动态加载的方式,即动态引入的方式
动态引入
通过类似于scriptjs
等工具可以方便的动态载入脚本,且能知道何时载入完毕。
但是百度地图脚本实际上是分成了两步加载的,请求脚本时,第一次请求到的脚本内容为:
(function () {
window.BMap_loadScriptTime = (new Date).getTime();
document.write('<script type="text/javascript" src="http://api.map.baidu.com/getscript?v=2.0&ak={密钥}&services=&t=20171031174121"></script>');
})();
这个脚本中会再次载入实际的脚本。
因此在动态载入的时候,无从知道第二个脚本何时载入完成,也就不能再scriptjs
的完成回调中去执行相关对地图的操作,因为此时只载入了第一个脚本。
幸好,脚本链接还支持callback
参数回调(类似于jsonp),这点在官方文档中并未给出。
于是可以这么做:
window.onBDMapScriptLoaded = () => {
// 载入完毕
console.log(window.BMap.version) // 2.0
}
scriptjs(`http://api.map.baidu.com/api?v=2.0&ak=${密钥}&callback=onBDMapScriptLoaded`)
callback
参数的原理
在脚本链接后加上callback
参数之后,返回的脚本内容变成了:
(function () {
window.BMap_loadScriptTime = (new Date).getTime();
window.BMap = window.BMap || {};
window.BMap.apiLoad = function () {
delete window.BMap.apiLoad;
if (typeof onBDMapScriptLoaded == "function") {
onBDMapScriptLoaded()
}
};
var s = document.createElement('script');
s.src = 'http://api.map.baidu.com/getscript?v=2.0&ak={密钥}&services=&t=20171031174121';
document.body.appendChild(s);
})();
即在BMap.apiLoad
函数中调用了我们注册的onBDMapScriptLoaded
函数。
而BMap.apiLoad
则是在地图SDK中调用的。
以上便是百度地图SDK脚本引入的两种方式。
API(略)
关于API的使用,百度提供了完善的文档,这里不赘述。
地图坐标显示
接入了SDK后,愉快的从服务端获取设备gps坐标,迫不及待的想看看设备在地图上打出的点,结果本来在六一北路亚太中心的设备,位置显示在了开元寺附近了,傻眼。
经过一通搜索研究,发现是坐标系搞的鬼。
不同地图产品所使用的坐标系不一样,当前主流地图产品使用的坐标系有:
地球坐标 (WGS84 / WGS1984)
- 学名:世界大地测量系统,World Geodetic System
- 国际标准,从 GPS 设备中取出的数据的坐标系
- 国际地图提供商使用的坐标系
火星坐标 (GCJ-02)
- 也叫国测局坐标系
- 中国标准,从国行移动设备中定位获取的坐标数据使用这个坐标系
- 国家规定: 国内出版的各种地图系统(包括电子形式),必须至少采用GCJ-02对地理位置进行首次加密
百度坐标 (BD-09)
- 百度标准,看百度的解释
- 百度 SDK,百度地图,Geocoding 使用
吐槽:本来就够乱的坐标系,百度又在火星坐标上来个二次加密。
因为设备采集时,采集到的是wgs84坐标,而我没有对坐标进行转换,直接当成百度坐标来显示了,所以出现了偏移。
坐标转换
在地图上绘制坐标点时,如果gps数据源使用的不是百度的坐标系,则需要将gps数据源转换成百度的坐标。有两种装换方法:
使用百度SDK转换
百度地图SDK提供Convertor
类来完成坐标转换
const convertor = new BMap.Convertor()
const srcPoints = [...] // BMap.Point数组
const from = 3 // 源坐标系,国测局gcj02
const to = 5 // 目的坐标系,百度BD09ll
convertor.translate(srcPoints, from, to, ({ status, points }) => {
if (status !== 0) {
// 转换成功
}
})
其中,源坐标系与目的坐标系的常量定义,见地址转换服务文档
SDK在转换坐标时,实际是将坐标点发送至服务端转换完成的,并且接口调用次数每个密钥是有限制的。
当坐标点特别多的时候,就需要更快的方式完成转换。
使用开源工具转换
开源库coordtransform提供了地球坐标、火星坐标、百度坐标系之间的互相转换,而且是本地的直接计算的。
使用方法:
import coordtransform from 'coordtransform'
const original = [26.095860123663496, 119.30967186026581] // 六一北路亚太中心的坐标,地球坐标系
// wgs84转国测局
const gcj02 = coordtransform.wgs84togcj02(original[1], original[0])
// 国测局坐标转百度坐标
const bd09 = coordtransform.gcj02tobd09.apply(null, gcj02)
转换结果对比
数据来源 | 纬度 | 经度 |
---|---|---|
原始数据 | 26.095860123663496 | 119.30967186026581 |
百度接口转换输出 | 26.098684141955 | 119.32102500817 |
coordtransform输出 | 26.098684033393084 | 119.32102111551119 |
工具转换结果和百度SDK转换结果略有误差,但是通过在线查询工具查询验证,误差肉眼基本是看不出来的。
相关工具
Draft.js学习小结(基础篇)
综述
Draft.js是一个基于React的用来构建富文本编辑器的框架,由一个不变的模型和跨浏览器的差异的抽象来驱动。
Draft.js使得构建任何类型的富文本输入变得很容易,无论你是只想支持一些内联文本样式或者是构建一个用于写长篇的文章的复杂的文本编辑器。
Draft.js在2016年2月的React.js Conf中被介绍。
- 安装
当前的Draft.js被分布在npm。它依赖于React和ReactDOM,所以它们也必须被安装。
npm install --save draft-js react react-dom
- API公告
在开始之前,请注意,我们正在通过API改变存储在Draft中的实体。目前,master分支支持新旧API。我们希望release分支也能尽快在v0.10.0支持。这样一来,我们将在v0.11.0删除旧API。这次更新还包括文档如何升级。如果你有兴趣来及时帮忙,或者跟踪进度,请在issue 839下面跟帖。
- 例子
import React from 'react';
import ReactDOM from 'react-dom';
import {Editor, EditorState} from 'draft-js';
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {editorState: EditorState.createEmpty()};
this.onChange = (editorState) => this.setState({editorState});
}
render() {
return (
<Editor editorState={this.state.editorState} onChange={this.onChange} />
);
}
}
ReactDOM.render(
<MyEditor />,
document.getElementById('container')
);
因为Draft.js支持unicode编码,所以在你的HTML文件的<head></head>
模块里面必须有下面这个标签。
<meta charset="utf-8" />
API基础知识
本文档提供了Draft API的基本的概述。以及一个可用的工作示例。
控制输入
React组件Editor被构建为一个控制ContentEditable的组件,这样做的目的是在常见的React控制输入API里面提供了一个高层API模型。
简单概括,控制输入涉及两个关键部分:
- 一个state对象value,用来表示用户输入的状态。
- 一个props方法onChange(),用来接收更新用户输入的方法。
这种方式允许组件组装在输入的state里面有严格控制的输入,同时仍然允许更新DOM提供用户写入的文本相关的信息。
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.onChange = (evt) => this.setState({value: evt.target.value});
}
render() {
return <input value={this.state.value} onChange={this.onChange} />;
}
}
顶级组件可以通过value这个state属性来保持控制输入的状态。
控制富文本
在React中富文本的场景,有两个明显的问题:
- 一连串的纯文本是无法表示出富文本编辑器中复杂的状态。
- 没有onChange事件可以用来改变ContentEditable对象。
因此state被表示为一个immutable的EditorState对象,onChange是实现在Editor内核中用来在顶级提供给这个state需要的值。EditorState对象是一个editor的state的完整的快照,包含内容,光标,撤销/恢复历史。在编辑器中所有的内容改变和选中状态变化都会创建新的EditorState对象。请注意,这对于通过immutable对象持久化的数据仍然是有效的。
import {Editor, EditorState} from 'draft-js';
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {editorState: EditorState.createEmpty()};
this.onChange = (editorState) => this.setState({editorState});
}
render() {
return <Editor editorState={this.state.editorState} onChange={this.onChange} />;
}
}
对于出现在编辑器DOM中的任何编辑或选择变化,你的onChange方法处理的参数都将是基于这些变化的最新EditorState对象。
富文本样式
现在我们已经建立了顶层API的基础知识,我们可以更进一步地检测如何添加基础的富文本样式到一个Draft编辑器当中。 接下来给出一个可用的富文本例子。
EditorState:你的命令
上一篇文章介绍到EditorState对象是编辑器里面一个完整的state快照,在Editor核心通过onChange提供这个state。
然而,你的顶级React组件是负责维护state,你也可以自由的应用在你认为合适的任何方式EditorState对象的改变。
对于内联样式和块状样式的行为,例如,RichUtils模块提供许多有用的功能用来帮助操纵state。
类似地,Modifier模块也提供学过通用的操作,允许你应用到编辑,包括改变文本,样式,或者更多。这个模块是一套组合简单的编辑功能,小的编辑功能返回渴望的EditorState对象。
下面这个例子,我们会用RichUtils来演示如何在顶级组件使用基础的富文本样式。
RichUtils 和 Key Commands
RichUtils有(在web编辑器核心的可用的)的信息,例如Cmd+B(加粗),Cmd+I(斜体)或者移除要求的样式。
import {Editor, EditorState, RichUtils} from 'draft-js';
class MyEditor extends React.Component {
constructor(props) {
super(props);
this.state = {editorState: EditorState.createEmpty()};
this.onChange = (editorState) => this.setState({editorState});
this.handleKeyCommand = this.handleKeyCommand.bind(this);
}
handleKeyCommand(command) {
const newState = RichUtils.handleKeyCommand(this.state.editorState, command);
if (newState) {
this.onChange(newState);
return 'handled';
}
return 'not-handled';
}
render() {
return (
<Editor
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
/>
);
}
}
handleKeyCommand handleKeyCommand提供的command参数是一个字符串值,要执行的命令的名称。这是来自一个DOM映射关键事件。更过相关信息请查看Advanced Topics-Key Binding,以及为什么函数返回处理或处理的细节。
样式控制界面
在你的React组件里面,你可以添加按钮,或者其他控件允许用户修改样式到编辑器里面。在上面的示例中,我们使用已知的key commands,但是我们能添加更多的复杂UI用来提供这些丰富的功能。
这是一个非常基础的示例,一个“Blod”按钮触发BLOD(加粗)样式。
class MyEditor extends React.Component {
// …
_onBoldClick() {
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
}
render() {
return (
<div>
<button onClick={this._onBoldClick.bind(this)}>Bold</button>
<Editor
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
/>
</div>
);
}
}
RichUtils
RichUtils模块是一组静态的实用函数用来做富文本编辑。
在每种情况下,这些方法接受EditorState对象与相关参数,然后返回新的EditorState对象。
静态方法
toggleBlockType 修改块状样式
toggleBlockType(
editorState: EditorState,//EditorState对象
blockType: string//块状样式名称
): EditorState//返回修改后的EditorState结果
toggleInlineStyle 修改内联样式
toggleInlineStyle(
editorState: EditorState,//EditorState对象
inlineStyle: string//块状样式名称
): EditorState//返回修改后的EditorState结果
handleKeyCommand 处理键盘命令
handleKeyCommand(
editorState: EditorState,//EditorState对象
command: string//键盘命令,draft有内置了命令:ctrl+B,ctrl+I等等
): ?EditorState//根据键盘命令返回新的EditorState对象,也有可能没有返回,没有返回表示没有生成新的对象。
EditorState
EditorState是编辑器的顶级状态对象。
它是Draft编辑器的整体对象,包含以下几点:
- 当前的文本内容ContentState
- 当前的选择SelectionState
- 内容的所有装饰
- 撤销/恢复的堆栈
- 最近变化的内容
注意: 当你使用EditorState对象的时候,你不能使用Immutable API。取而代之的,应该是使用下面的getters实例方法和静态的方法。
综述
- 常见的getters实例方法
下面的列表包含了供EditorState对象使用的最常见的实例方法。
getCurrentContent(): ContentState //当前的文本内容ContentState
getSelection(): SelectionState //当前的选择SelectionState
getCurrentInlineStyle(): DraftInlineStyle //当前的内联样式
getBlockTree(): OrderedMap //块树
- 静态方法
static createEmpty(?decorator): EditorState //创建EditorState对象。可选参数是装饰器。
static createWithContent(contentState, ?decorator): EditorState //根据content创建EditorState对象。
static create(config): EditorState //根据config创建EditorState对象。
static push(editorState, contentState, changeType): EditorState //根据ContentState和ChangeType插入EditorState。
static undo(editorState): EditorState //撤销EditorState对象。获取EditorState的undo栈,top出第一个元素,同时把原来的元素放到redo栈。
static redo(editorState): EditorState //恢复EditorState对象。获取EditorState的redo栈,top出第一个元素,同时把原来的元素放到undo栈。
static acceptSelection(editorState, selectionState): EditorState //根据SelectionState对象,改变EditorState。
static forceSelection(editorState, selectionState): EditorState //强制根据SelectionState对象,改变EditorState。
static moveSelectionToEnd(editorState): EditorState //移动选总范围到末尾,然后返回新的EditorState对象。
static moveFocusToEnd(editorState): EditorState //移动焦点到末尾,然后返回新的EditorState对象。
static setInlineStyleOverride(editorState, inlineStyleOverride): EditorState //设置内联样式覆盖方式。
static set(editorState, EditorStateRecordType): EditorState //设置EditorState记录类型。
- 属性
注意:使用静态EditorState方法来设置属性,而不是直接使用Immutable的API。
allowUndo
currentContent
decorator
directionMap
forceSelection
inCompositionMode
inlineStyleOverride
lastChangeType
nativelyRenderedContent
redoStack
selection
treeMap
undoStack
ContentState
ContentState是一个Immutable对象,他表示了以下的状态:
- 编辑器的全部内容:文本,块样式和内联样式,实体范围。
- 编辑器的两个SelectionStates对象:选中之前和选中之后的内容。
最常见的使用ContentState对象的是通过EditorState.getCurrentContent()来获得的,ContentState提供了当前编辑器的状态。
ContentState对象组成了EditorState对象中的撤销/恢复栈。
综述
- 静态方法
static createFromText(text: string): ContentState //通过text创建ContentState对象
static createFromBlockArray(blocks: Array): ContentState //通过Array创建ContentState对象
- 方法
getEntityMap() //获取Entity
getBlockMap() //获取Block
getSelectionBefore() //获取选择之前的对象
getSelectionAfter() //获取选择之后的对象
getBlockForKey(key) //通过key获取Block
getKeyBefore(key) //
getKeyAfter(key)
getBlockBefore(key)
getBlockAfter(key)
getBlocksAsArray()
getFirstBlock()
getLastBlock()
getPlainText(delimiter)
getLastCreatedEntityKey()
hasText()//返回内容是否包含任何文本。
createEntity(...)
getEntity(...)
mergeEntityData(...)
replaceEntityData(...)
addEntity(...)
- 属性
注意:使用Immutable Map API来设置属性
blockMap
selectionBefore
selectionAfter
ContentBlock
ContentBlock是编辑器的内容一个Block的完整状态的Immutable对象,包括:
- 纯文本内容块
- 类型,如段落、头部、列表项
- 实体,内联样式,和深度的信息
一个ContentState对象是包含了那些ContentBlock里面的OrderMap,他们一起组成了编辑器的完整content。
ContentBlock对象在很大程度上类似于HTML块级元素,如段落和列表项。
新ContentBlock可以直接使用构造函数创建对象。下面会进行详细介绍。
- 代表风格和实体
characterList字段是一个Immutable List包含在Block的每个字符的CharacterMetadata对象。这就是我们如何对一个给定的Block进行样式设置。
通过大量的使用immutableility和数据持久化对那些lists和CharacterMetadata对象,编辑内容通常对编辑器的内存占用几乎没有影响。
用这种方式通过编码内联样式和实体,函数在一个ContentBlock编辑时可以在一个List对象中执行slices、concats和其他List方法。
综述
- 方法
getKey(): string
getType(): DraftBlockType
getText(): string
getCharacterList(): List //每个字符的样式列表
getLength(): number
getDepth(): number
getInlineStyleAt(offset: number): DraftInlineStyle //第number个字符的内联样式
getEntityAt(offset: number): ?string //第number个字符的entity
getData(): Map
findStyleRanges(filterFn: Function, callback: Function): void
findEntityRanges(filterFn: Function, callback: Function): void
- 属性
注意:使用Immutable的API来构建ContentBlock或者设置属性。
key: string
type: DraftBlockType
text: string
characterList: List //每个字符的列表
depth: number
data: Map
CharacterMetadata
CharacterMetadata是代表单个Character的内联样式和实体信息的immutable对象。
CharacterMetadata对象是积极汇集和共享的。如果两个Characters有相同的内联样式和实体,他们是表示使用相同的CharacterMetadata对象。因此我们只需要尽可能多地组合inline style集与entity keys,保持我们的内存占用较小,只当在内容的规模和复杂度增加的时候跟着增加。
为此,你应该通过提供的静态方法集创建或者应用你的改变到CharacterMetadata对象,这将确保资源池被利用。
大多数的Draft用例不可能使用那些静态方法,因为最常见的编辑操作已经被实现或者也可以通过使用工具模块来获得。Getter方法可以在在render时派上用场。
可以查看ContentBlock模块的API信息,如何在ContentBlock使用CharacterMetadata。
综述
- 静态方法
static create(...): CharacterMetadata
static applyStyle(...): CharacterMetadata
static removeStyle(...): CharacterMetadata
static applyEntity(...): CharacterMetadata
- 方法
getStyle(): DraftInlineStyle
hasStyle(style: string): boolean
getEntity(): ?string