BLUE

BLUE 查看完整档案

成都编辑  |  填写毕业院校成都汉默地理资讯科技有限公司  |  软件研发工程师 编辑填写个人主网站
编辑

技术丰富人生,微笑面对未来.....

个人动态

BLUE 收藏了文章 · 2月7日

关于反爬虫,看这一篇就够了

编者:本文来自携程酒店研发部研发经理崔广宇在第三期【携程技术微分享】上的分享,以下为整理的内容概要。墙裂建议点击视频回放,“现场”围观段子手攻城狮大崔,如何高智商&高情商地完美碾压爬虫。。。关注携程技术中心微信公号ctriptech,可第一时间获知微分享信息~

你被爬虫侵扰过么?当你看到“爬虫”两个字的时候,是不是已经有点血脉贲张的感觉了?千万要忍耐,稍稍做点什么,就可以在名义上让他们胜利,实际上让他们受损失。

一、为什么要反爬虫

1、爬虫占总PV比例较高,这样浪费钱(尤其是三月份爬虫)。

三月份爬虫是个什么概念呢?每年的三月份我们会迎接一次爬虫高峰期。

最初我们百思不得其解。直到有一次,四月份的时候,我们删除了一个url,然后有个爬虫不断的爬取url,导致大量报错,测试开始找我们麻烦。我们只好特意为这个爬虫发布了一次站点,把删除的url又恢复回去了。

但是当时我们的一个组员表示很不服,说,我们不能干掉爬虫,也就罢了,还要专门为它发布,这实在是太没面子了。于是出了个主意,说:url可以上,但是,绝对不给真实数据。

于是我们就把一个静态文件发布上去了。报错停止了,爬虫没有停止,也就是说对方并不知道东西都是假的。这个事情给了我们一个很大的启示,也直接成了我们反爬虫技术的核心:变更。

后来有个学生来申请实习。我们看了简历发现她爬过携程。后来面试的时候确认了下,果然她就是四月份害我们发布的那个家伙。不过因为是个妹子,技术也不错,后来就被我们招安了。现在已经快正式入职了。

后来我们一起讨论的时候,她提到了,有大量的硕士在写论文的时候会选择爬取OTA数据,并进行舆情分析。因为五月份交论文,所以嘛,大家都是读过书的,你们懂的,前期各种DotA,LOL,到了三月份了,来不及了,赶紧抓数据,四月份分析一下,五月份交论文。

就是这么个节奏。

2、公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。

OTA的价格可以在非登录状态下直接被查询,这个是底线。如果强制登陆,那么可以通过封杀账号的方式让对方付出代价,这也是很多网站的做法。但是我们不能强制对方登录。那么如果没有反爬虫,对方就可以批量复制我们的信息,我们的竞争力就会大大减少。

竞争对手可以抓到我们的价格,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来携程。这对我们是不利的。

3、爬虫是否涉嫌违法? 如果是的话,是否可以起诉要求赔偿?这样可以赚钱。

这个问题我特意咨询了法务,最后发现这在国内还是个擦边球,就是有可能可以起诉成功,也可能完全无效。所以还是需要用技术手段来做最后的保障。

二、反什么样的爬虫

1、十分低级的应届毕业生

开头我们提到的三月份爬虫,就是一个十分明显的例子。应届毕业生的爬虫通常简单粗暴,根本不管服务器压力,加上人数不可预测,很容易把站点弄挂。

顺便说下,通过爬携程来获取offer这条路已经行不通了。因为我们都知道,第一个说漂亮女人像花的人,是天才。而第二个。。。你们懂的吧?

2、十分低级的创业小公司

现在的创业公司越来越多,也不知道是被谁忽悠的然后大家创业了发现不知道干什么好,觉得大数据比较热,就开始做大数据。

分析程序全写差不多了,发现自己手头没有数据。

怎么办?写爬虫爬啊。于是就有了不计其数的小爬虫,出于公司生死存亡的考虑,不断爬取数据。

3、不小心写错了没人去停止的失控小爬虫

携程上的点评有的时候可能高达60%的访问量是爬虫。我们已经选择直接封锁了,它们依然孜孜不倦地爬取。

什么意思呢?就是说,他们根本爬不到任何数据,除了http code是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。

4、成型的商业对手

这个是最大的对手,他们有技术,有钱,要什么有什么,如果和你死磕,你就只能硬着头皮和他死磕。

5、抽风的搜索引擎

大家不要以为搜索引擎都是好人,他们也有抽风的时候,而且一抽风就会导致服务器性能下降,请求量跟网络攻击没什么区别。

三、什么是爬虫和反爬虫

因为反爬虫暂时是个较新的领域,因此有些定义要自己下。我们内部定义是这样的:

  • 爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。

  • 反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。

  • 误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。

  • 拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。

  • 资源:机器成本与人力成本的总和。

这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,让对方加班才是王道,机器成本并不是特别值钱。

四、知己知彼:如何编写简单爬虫

要想做反爬虫,我们首先需要知道如何写个简单的爬虫。

目前网络上搜索到的爬虫资料十分有限,通常都只是给一段python代码。python是一门很好的语言,但是用来针对有反爬虫措施的站点做爬虫,真的不是最优选择。

更讽刺的是,通常搜到的python爬虫代码都会使用一个lynx的user-agent。你们应该怎么处理这个user-agent,就不用我来说了吧?

通常编写爬虫需要经过这么几个过程:

  • 分析页面请求格式

  • 创建合适的http请求

  • 批量发送http请求,获取数据

举个例子,直接查看携程生产url。在详情页点击“确定”按钮,会加载价格。假设价格是你想要的,那么抓出网络请求之后,哪个请求才是你想要的结果呢?

答案出乎意料的简单,你只需要用根据网络传输数据量进行倒序排列即可。因为其他的迷惑性的url再多再复杂,开发人员也不会舍得加数据量给他。

五、知己知彼:如何编写高级爬虫

那么爬虫进阶应该如何做呢?通常所谓的进阶有以下几种:

分布式

通常会有一些教材告诉你,为了爬取效率,需要把爬虫分布式部署到多台机器上。这完全是骗人的。分布式唯一的作用是:防止对方封IP。封IP是终极手段,效果非常好,当然,误伤起用户也是非常爽的。

模拟JavaScript

有些教程会说,模拟javascript,抓取动态网页,是进阶技巧。但是其实这只是个很简单的功能。因为,如果对方没有反爬虫,你完全可以直接抓ajax本身,而无需关心js怎么处理的。如果对方有反爬虫,那么javascript必然十分复杂,重点在于分析,而不仅仅是简单的模拟。

换句话说:这应该是基本功。

PhantomJs

这个是一个极端的例子。这个东西本意是用来做自动测试的,结果因为效果很好,很多人拿来做爬虫。但是这个东西有个硬伤,就是:效率。此外PhantomJs也是可以被抓到的,出于多方面原因,这里暂时不讲。 

六、不同级别爬虫的优缺点

越是低级的爬虫,越容易被封锁,但是性能好,成本低。越是高级的爬虫,越难被封锁,但是性能低,成本也越高。

当成本高到一定程度,我们就可以无需再对爬虫进行封锁。经济学上有个词叫边际效应。付出成本高到一定程度,收益就不是很多了。

那么如果对双方资源进行对比,我们就会发现,无条件跟对方死磕,是不划算的。应该有个黄金点,超过这个点,那就让它爬好了。毕竟我们反爬虫不是为了面子,而是为了商业因素。

七、如何设计一个反爬虫系统(常规架构)

有个朋友曾经给过我这样一个架构:

1、对请求进行预处理,便于识别;
2、识别是否是爬虫;
3、针对识别结果,进行适当的处理;

当时我觉得,听起来似乎很有道理,不愧是架构,想法就是和我们不一样。后来我们真正做起来反应过来不对了。因为:

如果能识别出爬虫,哪还有那么多废话?想怎么搞它就怎么搞它。如果识别不出来爬虫,你对谁做适当处理?

三句话里面有两句是废话,只有一句有用的,而且还没给出具体实施方式。那么:这种架构(师)有什么用?

因为当前存在一个架构师崇拜问题,所以很多创业小公司以架构师名义招开发。给出的title都是:初级架构师,架构师本身就是个高级岗位,为什么会有初级架构。这就相当于:初级将军/初级司令。

最后去了公司,发现十个人,一个CTO,九个架构师,而且可能你自己是初级架构师,其他人还是高级架构师。不过初级架构师还不算坑爹了,有些小创业公司还招CTO做开发呢。

传统反爬虫手段

1、后台对访问进行统计,如果单个IP访问超过阈值,予以封锁。

这个虽然效果还不错,但是其实有两个缺陷,一个是非常容易误伤普通用户,另一个就是,IP其实不值钱,几十块钱甚至有可能买到几十万个IP。所以总体来说是比较亏的。不过针对三月份呢爬虫,这点还是非常有用的。

2、后台对访问进行统计,如果单个session访问超过阈值,予以封锁。

这个看起来更高级了一些,但是其实效果更差,因为session完全不值钱,重新申请一个就可以了。

3、后台对访问进行统计,如果单个userAgent访问超过阈值,予以封锁。

这个是大招,类似于抗生素之类的,效果出奇的好,但是杀伤力过大,误伤非常严重,使用的时候要非常小心。至今为止我们也就只短暂封杀过mac下的火狐。

4、以上的组合

组合起来能力变大,误伤率下降,在遇到低级爬虫的时候,还是比较好用的。

由以上我们可以看出,其实爬虫反爬虫是个游戏,RMB玩家才最牛逼。因为上面提到的方法,效果均一般,所以还是用JavaScript比较靠谱。

也许有人会说:javascript做的话,不是可以跳掉前端逻辑,直接拉服务吗?怎么会靠谱呢?因为啊,我是一个标题党啊。JavaScript不仅仅是做前端。跳过前端不等于跳过JavaScript。也就是说:我们的服务器是nodejs做的。

思考题:我们写代码的时候,最怕碰到什么代码?什么代码不好调试?

eval

eval已经臭名昭著了,它效率低下,可读性糟糕。正是我们所需要的。

goto

js对goto支持并不好,因此需要自己实现goto。

混淆

目前的minify工具通常是minify成abcd之类简单的名字,这不符合我们的要求。我们可以minify成更好用的,比如阿拉伯语。为什么呢? 因为阿拉伯语有的时候是从左向右写,有的时候是从右向左写,还有的时候是从下向上写。除非对方雇个阿拉伯程序员,否则非头疼死不可。

不稳定代码

什么bug不容易修?不容易重现的bug不好修。因此,我们的代码要充满不确定性,每次都不一样。

代码演示

下载代码本身,可以更容易理解。这里简短介绍下思路:

  1. 纯JAVASCRIPT反爬虫DEMO,通过更改连接地址,来让对方抓取到错误价格。这种方法简单,但是如果对方针对性的来查看,十分容易被发现。

  2. 纯JAVASCRIPT反爬虫DEMO,更改key。这种做法简单,不容易被发现。但是可以通过有意爬取错误价格的方式来实现。

  3. 纯JAVASCRIPT反爬虫DEMO,更改动态key。这种方法可以让更改key的代价变为0,因此代价更低。

  4. 纯JAVASCRIPT反爬虫DEMO,十分复杂的更改key。这种方法,可以让对方很难分析,如果加了后续提到的浏览器检测,更难被爬取。

到此为止。

前面我们提到了边际效应,就是说,可以到此为止了。后续再投入人力就得不偿失了。除非有专门的对手与你死磕。不过这个时候就是为了尊严而战,不是为了商业因素了。

浏览器检测

针对不同的浏览器,我们的检测方式是不一样的。

  • IE 检测bug;

  • FF 检测对标准的严格程度;

  • Chrome 检测强大特性。

八、我抓到你了——然后该怎么办

不会引发生产事件——直接拦截

可能引发生产事件——给假数据(也叫投毒)

此外还有一些发散性的思路。例如是不是可以在响应里做SQL注入?毕竟是对方先动的手。不过这个问题法务没有给具体回复,也不容易和她解释。因此暂时只是设想而已。

1、技术压制

我们都知道,DotA AI里有个de命令,当AI被击杀后,它获取经验的倍数会提升。因此,前期杀AI太多,AI会一身神装,无法击杀。

正确的做法是,压制对方等级,但是不击杀。反爬虫也是一样的,不要一开始就搞太过分,逼人家和你死磕。

2、心理战

挑衅、怜悯、嘲讽、猥琐。

以上略过不提,大家领会精神即可。

3、放水

这个可能是是最高境界了。

程序员都不容易,做爬虫的尤其不容易。可怜可怜他们给他们一小口饭吃吧。没准过几天你就因为反爬虫做得好,改行做爬虫了。

比如,前一阵子就有人找我问我会不会做爬虫。。。。。我这么善良的人,能说不会吗????

查看原文

BLUE 赞了文章 · 2019-11-05

Linux运维必会的100道MySql面试题之(三)

接上一篇:Linux运维必会的100道MySql面试题之(二)

001:请解释关系型数据库概念及主要特点?

关系型数据库模型是把复杂的数据结构归结为简单的二元关系,对数据的操作都是建立一个 或多个关系表格上

最大的特点就是二维的表格,通过SQL结构查询语句存取数据,保持数据 一致性方面很强大

002:请说出关系型数据库的典型产品、特点及应用场景?

mysql 互联网企业常用

oracle 大型传统企业应用软件

如数据备份、复杂连接查询、一致性数据存储等,还是使用MySQL或者其他传统的关系型数据库最合适

003:请解释非关系型数据库概念及主要特点?

非关系型数据库也被称为NoSQL数据库,数据存储不需有特有固定的表结构

特点:高性能、高并发、简单易安装

004:请说出非关系型数据库的典型产品、特点及应用场景?

memcaced 纯内存

redis 持久化缓存

mongodb 面向文档

如果需要短时间响应的查询操作,没有良好模式定义的数据存储,或者模式更改频繁的数据存储还是用NoSQL

005:请详细描述SQL语句分类及对应代表性关键字

sql语句分类如下

DDL 数据定义语言,用来定义数据库对象:库、表、列

代表性关键字:create alter drop

DML 数据操作语言,用来定义数据库记录

代表性关键字:insert delete update

DCL 数据控制语言,用来定义访问权限和安全级别

代表性关键字:grant deny revoke

DQL 数据查询语言,用来查询记录数据

代表性关键字:select 


006:请详细描述char(4)和varchar(4)的差别

char长度是固定不可变的,varchar长度是可变的(在设定内)

比如同样写入cn字符,char类型对应的长度是4(cn+两个空格),但varchar类型对应长度是2

007:如何创建一个utf8字符集的数据库mingongge?

create database mingongge default character utf8 collate utf8_general_ci;

008:如何授权mingongge用户从172.16.1.0/24访问数据库

grant all on . to mingongge@'172.16.1.0/24' identified by '123456';

009:什么是MySQL多实例,如何配置MySQL多实例?

mysql多实例就是在同一台服务器上启用多个mysql服务,它们监听不同的端口,运行多个服务进程

它们相互独立,互不影响的对外提供服务,便于节约服务器资源与后期架构扩展

多实例的配置方法有两种:

1、一个实例一个配置文件,不同端口

2、同一配置文件(my.cnf)下配置不同实例,基于mysqld_multi工具

具体配置请参考之前的文章

010:如何加强MySQL安全,请给出可行的具体措施?

1、删除数据库不使用的默认用户

2、配置相应的权限(包括远程连接)

3、不可在命令行界面下输入数据库的密码

4、定期修改密码与加强密码的复杂度

011:MySQL root密码忘了如何找回?

mysqld_safe --skip-grant-tables &   #启动数据库服务

mysql -uroot -ppassowrd -e "use mysql;update user set passowrd = PASSWORD('newpassword') where user = 'root';flush privileges;"

012:delete和truncate删除数据的区别?

前者删除数据可以恢复,它是逐条删除速度慢

后者是物理删除,不可恢复,它是整体删除速度快

013:MySQL Sleep线程过多如何解决?

1、可以杀掉sleep进程,kill PID

2、修改配置,重启服务

[mysqld]

wait_timeout = 600

interactive_timeout=30

如果生产服务器不可随便重启可以使用下面的方法解决

set global wait_timeout=600

set global interactive_timeout=30;

014:sort_buffer_size参数作用?如何在线修改生效?

在每个connection(session)第一次连接时需要使用到,来提访问性能

set global sort_buffer_size = 2M

015:如何在线正确清理MySQL binlog?

MySQL中的binlog日志记录了数据中的数据变动,便于对数据的基于时间点和基于位置的恢复,但日志文件的大小会越来越大,点用大量的磁盘空间,因此需要定时清理一部分日志信息

手工删除:

首先查看主从库正在使用的binlog文件名称 


show master(slave) status\G


删除之前一定要备份


purge master logs before'2017-09-01 00:00:00'; 


#删除指定时间前的日志

purge master logs to'mysql-bin.000001';

#删除指定的日志文件

自动删除:

通过设置binlog的过期时间让系统自动删除日志

show variables like 'expire_logs_days'; 

set global expire_logs_days = 30;

#查看过期时间与设置过期时间

016:Binlog工作模式有哪些?各什么特点,企业如何选择?

1.Row(行模式)

日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改

2.Statement(语句模式)

每一条修改的数据都会完整的记录到主库master的binlog里面,在slave上完整执行在master执行的sql语句

3.mixed(混合模式)

结合前面的两种模式,如果在工作中有使用函数 或者触发器等特殊功能需求的时候,使用混合模式

数据量达到比较高时候,它就会选择 statement模式,而不会选择Row Level行模式

017:误操作执行了一个drop库SQL语句,如何完整恢复?

1、停止主从复制,在主库上执行锁表并刷新binlog操作,接着恢复之前的全备文件(比如0点的全备)

2、将0点时的binlog文件与全备到故障期间的binlog文件合并导出成sql语句

mysqlbinlog --no-defaults mysql-bin.000011 mysql-bin.000012 >bin.sql

3、将导出的sql语句中drop语句删除,恢复到数据库中

mysql -uroot -pmysql123 < bin.sql

018:mysqldump备份使用了-A -B参数,如何实现恢复单表?

-A 此参数作用是备份所有数据库(相当于--all-databases)

-B databasename 备份指定数据(单库备份使用)

备份时指定数据库与表名即可在恢复时只恢复单表

019:详述MySQL主从复制原理及配置主从的完整步骤

主从复制的原理如下:

主库开启binlog功能并授权从库连接主库,从库通过change master得到主库的相关同步信息然后连接主库进行验证,主库IO线程根据从库slave线程的请求,从master.info开始记录的位置点向下开始取信息,同时把取到的位置点和最新的位置与binlog信息一同发给从库IO线程,从库将相关的sql语句存放在relay-log里面,最终从库的sql线程将relay-log里的sql语句应用到从库上,至此整个同步过程完成,之后将是无限重复上述过程

完整步骤如下:

1、主库开启binlog功能,并进行全备,将全备文件推送到从库服务器上

2、show master statusG 记录下当前的位置信息及二进制文件名

3、登陆从库恢复全备文件

4、执行change master to 语句

5、执行start slave and show slave statusG

点击关注民工哥技术之路 微信公众号对话框回复关键字:1024 可以获取一份最新整理的技术干货:包括系统运维、数据库、redis、MogoDB、电子书、Java基础课程、Java实战项目、架构师综合教程、架构师实战项目、大数据、Docker容器、ELK Stack、机器学习、BAT面试精讲视频等。

查看原文

赞 43 收藏 34 评论 0

BLUE 发布了文章 · 2019-08-19

【GIS数学基础】-06圆锥投影

一. 基本概念

1.1 定义

设想用一个圆锥套在地球椭球体上,而把地球椭球上经纬网投影到圆锥面上,然后沿着某一条母线(经线)将圆锥面切开而展成平面,就得到圆锥投影。圆锥面和地球椭球体相切称为切圆锥投影,圆锥面和地球椭球相割时称为割圆锥投影。

1.2 分类

按圆锥面与地球椭球体的相对位置分

  • 正轴圆锥投影
    圆锥轴与地球椭球体的旋转轴相一致
  • 横轴圆锥投影
    圆锥轴与地球椭球体的长轴相一致
  • 斜轴圆锥投影
    圆锥轴既不和椭球体的旋转轴重合, 也不与它的长轴相重合

    图片描述

按变形性质分

  • 等角圆锥投影
    正轴等角圆锥投影也称为Lambert正形投影
  • 等面积圆锥投影
    正轴等面积割圆锥投影也称为Albers投影
  • 任意投影
    特例是等距离投影

二. 基本公式

2.1 正轴圆锥的基本公式

极坐标公式为:
图片描述
其中δ表示两条经线夹角在平面上的投影

α表示δ与λ的比值,小于1;λ表示地球椭球体上两经线的夹角。

直角坐标公式为:
图片描述
其中ρs表示制图区域最低纬线的投影半径

在该投影中,经纬线投影后呈正交,故a、b就是是m、n, 即经纬线方向就是主方向

2.2 正等角圆锥投影

基本公式:
根据等角条件 a=b或 m=n,得:
图片描述
将M,N 公式带入上式,并取积分可得:
图片描述

K,α称为投影常数
图片描述
当ϕ=0时,K=ρ,故K的几何意义是赤道的投影半径
正等角圆锥投影的一般公式如下:

clipboard.png

投影常数α,K的确定方法

  1. 单标准纬线正等角圆锥投影:指定制图区域中某一条纬线无长度变形
  2. 双标准纬线正等角圆锥投影:指定制图区域中两条纬线无长度变形
  3. 定域等面积正等角圆锥投影:使制图区域各部分面积变形的总和为零,即制图区域总面积和原来的大小保持不变

下图分别对应上述123
图片描述

双标准纬线正等角圆锥投影:
经纬线的表象:其经线表现为辐射的直线束,纬线投影成同心圆圆弧。圆锥面与椭球面相割的两条纬线圈,称为标准纬线(ϕ1,ϕ2)

标准纬线的位置:

clipboard.png

ϕs:制图区域最南边的纬度
ϕN:制图区域最北边的纬度

![图片描述][9]

双标准纬线正等角圆锥投影投影公式:

clipboard.png
其中:

clipboard.png
其他的公式同前。

  1. 角度没有变形,即投影前后对应的图形保持相似,故也可称为正形投影;
  2. 两条标准纬线上没有任何变形;
  3. 等变形线和纬线一致,同一条纬线上的变形处处相等;
  4. 在同一经线上,两标准纬线外侧为正变形(长度比>1),而两标准纬线之
  5. 为负变形(长度比<1),因此变形较均匀,绝对值也较小;
  6. 同一纬线上等经差的线段长度相等,两条纬线间的经线线段长度处处相等。

图片描述

我国的1:100万地图采用该投影,为了提高精度,1:100万地图的投影按百万之一地图的纬度划分原则—从赤道00开始,纬差40一幅,从南向北共分成15个投影带,每个投影带单独计算,建立数学基础。由于采用分带投影,每带纬度较小,我国范围内的1:100万地图变形值几乎相等,其长度变形最大不超过0.03%,面积变形约为长度变形的2倍

三. 圆锥投影的变形分析及应用

图片描述
在切圆锥投影中,标准纬线ϕ0处的长度比n01,其余纬线长度比均大于1,并向南、北方向增加;

在割圆锥投影中,标准纬线ϕ1ϕ2处长度比n1=n2=1,变形自标准纬线ϕ1ϕ2向内和向外增大,在ϕ1和ϕ2
之间n<1,在ϕ1和ϕ2以外n>1。

【结论】
圆锥投影最适用于中纬度处沿纬线伸展的制图区域

查看原文

赞 0 收藏 0 评论 0

BLUE 发布了文章 · 2019-08-19

【GIS数学基础】-05地图投影分类

1. 根据地图投影的变形(内蕴的特征)分类

  • 等角投影(conformal projection)

    地球表面上无穷小图形投影后仍保持相似,或两微分线段所组成的角度在投影后仍保持不变 。在等角投影中,ω=0, a=b,θ=900

  • 等面积投影(equivalent projection)

    地球面上的图形在投影后保持面积不变。在该投影中,vp=0,p=a· b =1

  • 任意投影

    既不具备等角性质,又没有等面积性质的投影。其中一特例是等距离投影(equidistant projection),即该投影只在某些特定方向上没有变形,如 a=1或b=1

2. 根据投影面与地球面的相关位置分类

  • 正轴投影

    投影面的中心线与地轴一致

  • 斜轴投影

    投影面的中心线与地轴斜交

  • 横轴投影

    投影面的中心线与地轴垂直

3. 根据正轴投影时经纬网的形状分类

  • 圆锥投影

    投影中纬线为同心圆圆弧,经线为圆的半经

  • 圆柱投影

    投影中纬线为一组平行直线,经线为垂直于纬线的另一组平行直线,且两相邻经线之间的距离相等

  • 方位投影

    投影中纬线为同心圆,经线为圆的半径,且经线间的夹角等于地球面上相应的经差

  • 伪圆锥投影

    投影中纬线为同心圆圆弧,经线为交于圆心的曲线

  • 伪圆柱投影

    投影中纬线为一组平行直线,而经线为某种曲线

  • 伪方位投影

    投影中纬线为同心圆,而经线为交于圆心的曲线

  • 多圆锥投影

    投影中纬线为同轴圆圆弧,而经线为对称中央直径线的曲线

图片描述

查看原文

赞 1 收藏 1 评论 0

BLUE 发布了文章 · 2019-08-16

【GIS数学基础】-04投影基础

1. 地图投影基本概念

地图投影是将地球椭球面上的点投影到平面上的数学方法。其实质就是建立地球椭球面上点与平面上对应点之间的函数关系。

其数学公式为:
图片描述

当给定不同的具体条件时,就可以得到不同种类的投影。下面是简单的投影示意图
图片描述

2. 投影变形

长度变形(distance distortion):即长度比与1之差值
图片描述

长度比(distance scale):地面上微分线段投影后的长度ds’与其相应的实地长度ds之比
图片描述

极值长度比:一点上不同方向上的长度比是不同的,其中最大值和最小值,称为极值长度比,用a和b来表示。

主方向:极值长度比的方向

经线和纬线长度比:沿经线和纬线方向的长度比,用m和n表示

任意一点与经线成α角方向上的长度比μα的计算公式:
图片描述
式中,M为子午圈曲率半径,r为纬圈半径,E,F,G称为一阶基本量(或高斯系数)

图片描述

3. 补充知识

变形椭圆(indicatrix ellipse):地面上的一个无穷小圆(微分圆)投影后一般成为一个微分椭圆,这个微分椭圆称为变形椭圆

图片描述

  • 投影后为不同大小的圆形,则该投影为等角投影
  • 投影后为面积相等而形状不同的椭圆,则该投影为等面积投影
  • 投影后为面积不相等的椭圆,则该投影为任意投影。如果椭圆的某一半轴与微分圆的半径相等,如a=r,或b=r,则该投影为等距离投影

下面是几种特殊投影的示例:
图片描述

查看原文

赞 1 收藏 1 评论 0

BLUE 发布了文章 · 2019-08-16

【GIS数学基础】-03基本要素和公式

1. 地球椭球体的基本要素

地球椭球体的基本要素分为:

长半径a(赤道半径)

短半径b(极轴半径)

扁率α

第一偏心率和第二偏心率e, e’

图片描述

【数学公式】

图片描述

扁率和偏心率反映地球椭球体的扁平程度。

2. 子午圈曲率半径和卯酉圈曲率半径

【法截面】:设过椭球面上任一点A作法线AL,通过A点法线的平面所截成的截面。
【主法截面】:通过AL两个相互垂直法截面。含有极值意义的两个主法截面是: 子午圈截面(AE1P1EP) 卯酉圈截面(QAW)如图:

图片描述

计算公式:
图片描述

上述公式中:a:长半径 e:第一偏心率 ϕ:纬度

当椭球体选定后,a,e为常数;M,N随纬度的变化而变化。

当ϕ=0度时有如下结论:
图片描述

当ϕ=90度时
图片描述

图片描述

3. 平均曲率半径和维圈半径

平均曲率半径(R)
主法截面曲率半径的几何中数。
图片描述
纬圈半径(r)
图片描述

4. 子午线弧长和纬线弧长

子午线弧长:就是椭圆的弧长
图片描述

图片描述

纬线(平行圈)的弧长:由于纬线为圆弧,故可应用圆周弧长的公式。

【结论】

  1. 同纬差的子午线弧长由赤道向两极逐渐增加,例如纬差 10的子午线弧长在赤道为110576米,而在两极为111695米;
  2. 同经差的纬线弧长则由赤道向两极缩短。例如经差10的纬线弧长在赤道处为111321米,在纬度450处缩短为78848米,至两极时则为零。

5. 地球椭球体表面上的梯形面积

它是指二条子午线和两条纬线所围成的面积。
图片描述
图片描述

查看原文

赞 0 收藏 0 评论 0

BLUE 发布了文章 · 2019-08-14

【GIS数学基础】-02坐标系

一. 地理坐标系

1.1 地球椭球体

地球椭球体是长短半径为a和b的椭球绕短半轴(地轴)PP'旋转而成,如下图:
图片描述

1.2 相关概念

经线(子午线):过地轴(旋转轴)的平面与椭球面的截线
零(首)子午线:国际上公认通过英国格林尼治天文台的经线,也就是本初子午线
经度计算:过该点的子午圈界面与零子午面之间的夹角。由零子午线起,向东为正,称为东京,由0度到+180度。由零子午线起,向西为负,称西经,由0度到-180度
赤道平面:垂直于地轴并通过地心的平面
赤道:赤道平面与椭球面相交的交线
纬线(平行圈):过某一点与赤道面平行的平面与椭球面的交线
维度的计算:定义为地球面上一点到球心的连线与赤道平面的夹角;从赤道起,向北为正,称“北纬”。纬度由0度到+90度; 从赤道起,向南为负,称“南纬”。纬度由0度到-90度。

【划重点】地面上任意点M的地理位置可由经度和纬度来决定,其地理坐标记成M(纬度,经度)

1.3 地理坐标的获取方法

经纬度的测地主要有两种方法:天文测量大地测量

  • 天文测量:以大地水准面和铅垂线为依据,用天文测量的方法,可获得地面点的天文经纬度
  • 大地测量:以旋转椭球体和法线为基准,用大地测量的方法,获得的大地经纬度称为大地坐标,它们构成的地理坐标系称为大地坐标系

1.4 54北京坐标系和80国家坐标系

  • 54北京坐标系:我国过去采用的大地坐标系,其原点在苏联西部的普尔科夫,采用克拉索夫斯基椭球元素,称1954年北京坐标系
  • 80国家坐标系:1980年新测定位于陕西省的坐标原点,采用1975年国家椭球元素,取代“1954年北京坐标系”,称1980年国家坐标系

二. 平面坐标系

2.1 平面坐标系定义

平面坐标系就是指用某点至极点的距离和方向表示该点的位置,例如:
图片描述

2.2 平面直角坐标系

平面直角坐标系是按直角坐标原理确定某点的平面位置

极坐标与平面直角坐标之间可建立一定的关系式,直角坐标系的X轴与极轴重合,二坐标系原点间距离OQ用q 表示。则有:

图片描述

查看原文

赞 1 收藏 0 评论 0

BLUE 发布了文章 · 2019-08-14

【GIS数学基础】-01地球的椭球体之地球的形状和大小

一. 基本概念

1.1 大地水准面

假想将静止的平均海水面,延伸到大陆内部,形成一个连续不断的,与地球比较接近的形状,其表面称之为大地水准面,由它包围的球体叫“大地体”。

1.2 地球椭球体

形状同大地体相近,并能用数学方法来表达的旋转椭球体。

由于地球内部物质分布不均匀和地面的高低起伏,使各处的重力方向发生局部变异,铅垂线成不规则的变化,使大地水准面仍有起伏,不可能是一个规则的表面,因此不能用简单的数学公式来表达。为了测量成果的计算和制图工作的需要,通常用地球椭球体来代替大地体。

地球自然表面,大地水准面及旋转椭球面的关系如图:
图片描述

二. 参考椭球

与局部地区(一个或几个国家)的大地水准面符合得最好的旋转椭球。它是许多旋转椭球体中的一个。

  • 我国1953年以前采用美国海福特椭球
  • 从1953年起改用克拉索夫斯基椭球
  • 1978年我国决定采用国际大地测量协会所推荐的“1975年基本大地数据”中给定的椭球参数
    即1975年国家椭球,并以此建立了我国新的,独立的大地坐标系

图片描述

查看原文

赞 2 收藏 2 评论 0

BLUE 发布了文章 · 2019-07-13

ArcGis for JavaScript SDK

ArcGis for JavaScript SDK

作者:BLUE

日期:2019-07-09

描述:arcgis for js 开发包,基于arcgis for js 3.9

点击【这里】下载SDK

该开发包是基于arcgis for javascript 3.9,是对原始API的一个扩充,原始API正常使用,该SDK仅封装了开发过程中常用的方法,开发包内使用瓦片下载的方式对全国天地图底图进行加载,默认坐标系为WGS 84,如果你是CGCS2000坐标系,那你可以放心使用;如果需要使用其他REST服务作为底图,你可以构造BaseMap实例的时候自己指定坐标系。

目录

    • 使用步骤
    • BaseMap类(基础底图类)

      • initTdt() -- 初始化全国天地图底图
      • initTiledMap(sldtserver?,slbzserver?,yxdtserver?) -- 初始化缓存映射服务资源作为底图
      • changeBaseMap(id) -- 切换底图类型
      • toggleLable() -- 切换标注
      • goto(lng, lat , zoom?) -- 定位点,将点拉到屏幕中心
      • adbLayer(layer, index?) -- 附加图层,可用于图层管理
      • hideAttachLayer(name) -- 隐藏附加图层
      • showAttachLayer(name) -- 显示附加图层
      • destoryAttachLayer(name) -- 销毁附加图层
      • hasLayer(name) -- 判断图层是否存在
      • zoomIn(zoom) -- 放大地图
      • zoomOut(zoom) -- 缩小地图
      • addWMSLayer(url, name, extent, callback?) -- 添加WMS图层
      • addPointGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?) -- 添加GeoJson点图层
      • addLineGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?) -- 添加GeoJson线图层
      • addGonGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?) -- 添加GeoJson线图层
    • DrawLayer类(绘制类)

      • drawPoint(callback?) -- 绘制点
      • drawMultiPoint(callback?) --绘制多点
      • drawLine(callback?) -- 绘制直线段
      • drawPolyLine(callback?) -- 绘制折线
      • drawPolyGon(callback?) -- 绘制多边形
      • drawFreePolyGon(callback?) -- 手绘多边形
      • drawArrow(callback?) -- 绘制箭头
      • drawTrianGle(callback?) -- 绘制三角形
      • drawCircle(callback?) -- 绘制圆形
      • drawEllipse(callback?) -- 绘制椭圆
      • drawRectangle(callback?) -- 绘制矩形
      • clear() -- 清除绘制并释放绘制状态
      • clsAndte() -- 清除绘制并保持绘制状态
      • deactivate() -- 释放绘制状态不清空绘制要素
    • GraphicLayer类(要素图层类)

      • addPoint(lng, lat, infoTemplate?, attr?, symbol?, localAnim?) -- 添加点要素
      • addMultiPoint(points, infoTemplate?, attr?, symbol?) -- 添加多点
      • addLine(path, infoTemplate?, attr?, symbol?) -- 添加线要素
      • addGon(path, infoTemplate?, attr?, symbol?) -- 添加面要素
      • addCricle(lng, lat, radius?, infoTemplate?, attr?, symbol?) -- 添加圆
      • addText(text, lng, lat, symbol?, offset?) -- 添加文字要素
      • addFlowText(text, path, symbol?, offset?) -- 添加流式文字/沿线文字(均分字间距)
      • addSupFlowText(text, path, symbol?, offset?) -- 添加流式文字/沿线文字(路径点坐标)
    • DijitLayer类(工具类)

      • measureDis() -- 测距
      • measureArea() -- 测面
      • clearMeas() -- 清空测量数据
      • activateToolbar(graphic) -- 编辑要素
      • deactivate() -- 释放编辑状态
      • getCurrentState() --获取当前编辑的一些状态
      • refresh() -- 刷新编辑的内部状态

    1. 使用步骤

    • 将该SDK包放置于项目工程根目录,如果使用的静态化资源的情况,请静态化该SDK包以保证路径“/arcgisdk”可以访问SDK包内文件
    • 在html页面中添加资源引用,如果你是用的是模块化进行开发,请暂时将地图界面抽离成单独的html,该SDK可能会引起模块化关键字冲突

    简洁版资源:

    <link rel="stylesheet" type="text/css" href="/arcgisdk/3.9compact/js/dojo/dijit/themes/dijit.css" />
    <link rel="stylesheet" type="text/css" href="/arcgisdk/3.9compact/js/esri/css/esri.css" />
    <script type="text/javascript" data-original="/arcgisdk/3.9compact/init.js"></script>

    完整版资源:

    <link rel="stylesheet" type="text/css" href="/arcgisdk/3.9compact/js/dojo/dijit/themes/dijit.css" />
    <link rel="stylesheet" type="text/css" href="/arcgisdk/3.9/js/esri/css/esri.css" />
    <script type="text/javascript" data-original="/arcgisdk/3.9/init.js"></script>
    • 修改SDK包源码引用
    若你是使用的完整版请将 /arcgisdk/3.9compact/init.js/arcgisdk/3.9compact/js/dojo/dojo/dojo.js 中的 localhost:8009 修改成你的项目地址和端口

    若你是使用的是完整版请将 /arcgisdk/3.9/init.js/arcgisdk/3.9/js/dojo/dojo/dojo.js 中的 localhost:8009 修改成你的项目地址和端口

    • 请开始你的代码,下面是个初始化例子
    <!--创建一个具有id属性的div-->
    <div id='map' style="height: 100%;width:100%;background-color:darkgrey"></div>
    
    <script>
        // 创建一个全局对象用于存储基础地图对象
        var baseMap = null;
        // 引入BaseMap模块
        require(["BAMAP/BaseMap"
        ], function (BaseMap) {
            // 实例化一个基础底图类
            baseMap = new BaseMap("map", { center: [116.39, 39.91],zoom:9 }).init();
        })
    </script>

    2. Class: BaseMap(基础底图类)

    基础底图类,该类继承于Map 对象,有关Map对象的资料请看【这里】

    【提示】该类所有的非原始方法都可以链式调用

    2.1 AMD Module Require

    require(["BAMAP/BaseMap"], function(BaseMap) { /* code goes here */ });

    2.2 Constructors

    new BaseMap(divId, options?);
    // 一般建议使用下面这样进行实例化的同时进行初始化,这样可以直接呈现底图
    new BaseMap("map", { center: [116.39, 39.91],zoom:9 }).initTdt();

    options参数请点击这里

    2.3 Method Details

    # initTdt()

    初始化全国天地图作为底图


    # initTiledMap(sldtserver?,slbzserver?,yxdtserver?)

    初始化有公开的缓存映射服务资源作为底图

    【提示】默认加载成都天地图作为底图,不保证永远能成功,如果服务被关闭则加载会失败

    • sldtserver [String] 电子底图服务地址
    • slbzserver [String] 矢量标注服务地址
    • yxdtserver [String] 影像底图服务地址

    # changeBaseMap(id)

    切换底图类型

    • id [String] 底图类型标识 sldt-矢量底图,imgdt-影像底图,dxdt-地形底图

    # toggleLable()

    切换标注


    # goto(lng, lat, zoom?)

    将底图中心定位到指定位置

    • lng [Float] 经度
    • lat [Float] 纬度
    • zoom [Int] 缩放层级 默认当前层级

    # adbLayer(layer, index?)

    特殊的附加图层,可用于图层管理,不建议直接使用原型的添加图层方法

    • layer [Layer] 所有继承于 esri/layers 的实例
    • index [Int] 图层层级,值越大越靠近用户

    # hideAttachLayer(name)

    隐藏附加图层,只针对 adbLayer 方法添加的图层有效

    • name [String] 图层名称或者id

    # showAttachLayer(name)

    显示附加图层,只针对 adbLayer 方法添加的图层有效

    • name [String] 图层名称或者id

    # destoryAttachLayer(name)

    销毁附加图层,只针对 adbLayer 方法添加的图层有效

    • name [String] 图层名称或者id

    # hasLayer(name)

    判断图层是否存在,只针对 adbLayer 方法添加的图层有效

    • name [String] 图层名称或者id

    # zoomIn(zoom)

    地图放大

    • zoom [Number] 放大层级

    # zoomOut(zoom)

    地图缩小

    • zoom [Number] 缩小层级

    # addWMSLayer(url, name, extent, callback?)

    添加WMS图层

    • url [String] WMS图层服务地址
    • name [String] 命名空间:图层名称
    • extent [Object] 坐标范围{xmin, ymin, xmax, ymax}
    • callback [Function] 回调函数

    # addPointGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?)

    添加GeoJson点图层

    • name [String] 图层名称或id
    • url [String] 服务或GeoJson文件的地址(同域)
    • infoTemplate1 [Object] 信息框内容
    • symbol [Object] imgurl & heigth & width
    • callback [Function] 回调函数
    • options [Object] GraphicsLayer类的所有构造参数,点击查看
    • maxdraw [Int] 最大绘制量 default 1,000,000

    【注意】调用此API时候请添加以下两个引用

    <script data-original="/arcgisdk/vendor/terraformer/terraformer.min.js"></script>
    <script data-original="/arcgisdk/vendor/terraformer-arcgis-parser/terraformer-arcgis-parser.min.js"></script>
    var infoTemplate1 = {
        title: "地块信息",
        content: "地块编码:${DKBM}[br/]面积(亩):${面积亩}"
    }
    var symbol={
        imgurl:"/static/map/img/local-marker.png",
        width:10,
        height:10
    }
    baseMap.addPointGeoJsonLayer('dk', '/main/map/geodata/xinjingdk.json', infoTemplate1, symbol, function (layer) {/* code goes here */ });

    # addLineGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?)

    添加GeoJson点图层

    • name [String] 图层名称或id
    • url [String] 服务或GeoJson文件的地址(同域)
    • infoTemplate1 [Object] 信息框内容
    • symbol [Object] color & width
    • callback [Function] 回调函数
    • options [Object] GraphicsLayer类的所有构造参数,点击查看
    • maxdraw [Int] 最大绘制量 default 1,000,000

    【注意】调用此API时候请添加以下两个引用

    <script data-original="/arcgisdk/vendor/terraformer/terraformer.min.js"></script>
    <script data-original="/arcgisdk/vendor/terraformer-arcgis-parser/terraformer-arcgis-parser.min.js"></script>

    # addGonGeoJsonLayer(name, url, infoTemplate1?, symbol?, callback?, options?, maxdraw?)

    添加GeoJson面图层

    • name [String] 图层名称或id
    • url [String] 服务或GeoJson文件的地址(同域)
    • infoTemplate1 [Object] 信息框内容
    • symbol [Object] color & width & fillcolor [255, 255, 0, 0.25]
    • callback [Function] 回调函数
    • options [Object] GraphicsLayer类的所有构造参数,点击查看
    • maxdraw [Int] 最大绘制量 default 1,000,000

    【注意】调用此API时候请添加以下两个引用

    <script data-original="/arcgisdk/vendor/terraformer/terraformer.min.js"></script>
    <script data-original="/arcgisdk/vendor/terraformer-arcgis-parser/terraformer-arcgis-parser.min.js"></script>

    3. Class: DrawLayer(绘制类)

    图形绘制类,该类中封装了一些常用的绘制方法;实例化是时候传入BaseMap实例对象,不要将该类的实例作为图层叠加。

    3.1 AMD Module Require

    require(["BAMAP/DrawLayer"], function(DrawLayer) { /* code goes here */ });

    3.2 Constructors

    new DrawLayer(Map)
    // Map为BaseMap类的实例

    3.3 Method Details

    # drawPoint(callback?)

    绘制点

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawMultiPoint(callback?)

    绘制多点

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawLine(callback?)

    绘制直线段

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawPolyLine(callback?)

    绘制折线

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawPolyGon(callback?)

    绘制多边形

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawFreePolyGon(callback?)

    手绘多边形

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawArrow(callback?)

    绘制箭头

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawTrianGle(callback?)

    绘制三角形

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawCircle(callback?)

    绘制圆形

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawEllipse(callback?)

    绘制椭圆

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # drawRectangle(callback?)

    绘制矩形

    • callback [Function] 绘制完成的回调函数,相关的绘制信息以回调参数对象传入

    # clear()

    清除绘制图层并释放绘制状态


    # clsAndte()

    清除绘制并保持绘制状态


    # deactivate()

    释放绘制状态不清空绘制要素

    4. Class: GraphicLayer(要素图层类)

    该图层类可根据坐标点绘制各种各样的要素,常用于绘制专题图层,该对象继承于GraphicsLayer对象,有关GraphicsLayer对象的信息,你可以戳【这里】

    【注意】该图层的方法需要调在实例添加到BaseMap中后才可以使用,也就是调用BaseMap的adbLayer方法之后

    4.1 AMD Module Require

    require(["BAMAP/GraphicLayer"], function(GraphicLayer) { /* code goes here */ });

    4.2 Constructors

    new GraphicLayer(options?)

    有关options的详情,戳【这里】

    4.3 Method Details

    # addPoint(lng, lat, infoTemplate?, attr?, symbol?, localAnim?)

    添加点要素

    • lng [Float] 经度
    • lat [Float] 纬度
    • infoTemplate [Object] 信息框格式{title:标题,content:正文}
    • attr [Object] 该要素的属性值,供infoTemplate使用
    • symbol [Object] 点样式{url:图片路径,width:宽,height:高}
    • localAnim [Boolean] 动画,可用于定位的时候,默认为false
    var infoTemplate = {title:"测试",content:"<h3>${foo}</h3>"};
    var attr = {foo:"hello"}

    # addMultiPoint(points, infoTemplate?, attr?, symbol?)

    添加多点要素

    • points [Array] 二维经纬度数组
    • infoTemplate [Object] 信息框格式{title:标题,content:正文}
    • attr [Object] 该要素的属性值,供infoTemplate使用
    • symbol [Object] 点样式{url:图片路径,width:宽,height:高}

    # addLine(path, infoTemplate?, attr?, symbol?)

    添加线要素

    • path [Array] 路径二维经纬度数组
    • infoTemplate [Object] 信息框格式{title:标题,content:正文}
    • attr [Object] 该要素的属性值,供infoTemplate使用
    • symbol [Object] 要素样式{color:颜色,width:线宽}

    # addGon(path, infoTemplate?, attr?, symbol?)

    添加面要素

    • path [Array] 路径二维经纬度数组
    • infoTemplate [Object] 信息框格式{title:标题,content:正文}
    • attr [Object] 该要素的属性值,供infoTemplate使用
    • symbol [Object] 要素样式{color:边线颜色,width:边线线宽,fillColor:填充色}

    【提示】这儿得颜色最好使用[R,G,B,A]的形式


    # addCricle(lng, lat, radius, infoTemplate?, attr?, symbol?)

    添加圆要素

    • lng [Float] 经度
    • lat [Float] 纬度
    • radius [Number] 半径(米) 默认1000
    • infoTemplate [Object] 信息框格式{title:标题,content:正文}
    • attr [Object] 该要素的属性值,供infoTemplate使用
    • symbol [Object] 要素样式{color:边线颜色,width:边线线宽,fillColor:填充色}

    【提示】这儿得颜色最好使用[R,G,B,A]的形式


    # addText(text, lng, lat, symbol?,offset?)

    添加文字要素

    • text [String] 文字内容
    • lng [Float] 经度
    • lat [Float] 纬度
    • symbol [Object] 要素样式{size:文字大小,family:文字字体,color:文字颜色}
    • offset [Object] 文字坐标偏移 {x:横向偏移,y:纵向偏移}

    # addFlowText(text, paths, symbol?,offset?)

    添加流式文字/沿线文字,均分文字间距

    • text [String] 文字内容
    • paths [Array] 线段坐标三维数组[[[x1,y1],[x2,y2]]]
    • symbol [Object] 要素样式{size:文字大小,family:文字字体,color:文字颜色}
    • offset [Object] 文字坐标偏移 {x:横向偏移,y:纵向偏移}

    【注意】该方法不要大数据量调用,大数据量有内存溢出风险


    # addSupFlowText(text, paths, symbol?,offset?)

    添加流式文字/沿线文字
    该方法适用于线的坐标点数量大于文字数量时候,并不是均分文字间距

    • text [String] 文字内容
    • paths [Array] 线段坐标三维数组[[[x1,y1],[x2,y2]]]
    • symbol [Object] 要素样式{size:文字大小,family:文字字体,color:文字颜色}
    • offset [Object] 文字坐标偏移 {x:横向偏移,y:纵向偏移}

    5. Class: DijitLayer(工具类)

    工具图层,比如测距,侧面,当然可以使用arcgis提供的原生的。
    实例化是时候传入BaseMap实例对象,不要将该类的实例作为图层叠加。

    5.1 AMD Module Require

    require(["BAMAP/DijitLayer"], function(DijitLayer) { /* code goes here */ });

    5.2 Constructors

    new DijitLayer(Map,options?)
    // Map为BaseMap类的实例
    • options是一个键值对对象,值都为函数,在对应状态的时候会被回调,回调参数都为一个对象

      • activate [Function] 激活编辑状态时候触发 {graphic,tool}
      • deactivate [Function] 释放编辑状态时候触发 {graphic,info,tool}
      • graphic-click [Function] 单击要素时触发 {graphic,info}
      • graphic-first-move [Function] 开始移动要素时触发 {graphic}
      • graphic-move [Function] 要素移动持续触发 {graphic,transform}
      • graphic-move-start [Function] 在要素上按下鼠标按钮时触发,通常在移动要素时触发{graphic}
      • graphic-move-stop [Function] 在释放鼠标按钮时触发,通常在移动图形后触发 {graphic,transform}
      • rotate [Function] 图形旋转时连续触发 {graphic,info}
      • rotate-first-move [Function] 当用户开始拖动句柄以旋转要素时触发 {graphic}
      • rotate-start [Function] 当用户单击手柄开始旋转要素时触发 {graphic}
      • rotate-stop [Function] 从旋转手柄释放鼠标按钮以完成要素旋转时触发 {graphic,info}
      • scale [Function] 在缩放要要素时持续触发 {graphic,info}
      • scale-first-move [Function] 当用户开始拖动句柄以缩放图形时触发 {graphic}
      • scale-start [Function] 当用户单击手柄以缩放或调整图形大小时触发 {graphic}
      • scale-stop [Function] 从缩放手柄释放鼠标按钮以完成对图形的缩放时触发 {graphic,info}
      • vertex-add [Function] 在将新顶点添加到多段线或多边形或将新点添加到多点后触发 {graphic,vertexinfo}
      • vertex-click [Function] 在多段线或多边形的顶点或多点中的点上单击鼠标按钮时触发 {graphic,vertexinfo}
      • vertex-delete [Function] 删除顶点(多段线、多边形)或点(多点)后触发 {graphic,vertexinfo}
      • vertex-first-move [Function] 当用户开始移动多段线、多边形或多点的顶点时触发 {graphic,vertexinfo}
      • vertex-mouse-out [Function] 当鼠标退出顶点(多段线、多边形)或点(多点)时触发 {graphic,vertexinfo}
      • vertex-mouse-over [Function] 当鼠标移过顶点(多段线、多边形)或点(多点)时触发 {graphic,vertexinfo}
      • vertex-move [Function] 在用户移动顶点(多段线、多边形)或点(多点)时连续触发 {graphic,vertexinfo,transform}
      • vertex-move-start [Function] 在顶点(多段线、多边形)或点(多点)上按下鼠标按钮时触发 {graphic,vertexinfo}
      • vertex-move-stop [Function] 当鼠标按钮从顶点(多段线、多边形)或点(多点)释放时触发 {graphic,vertexinfo,transform}

    5.3 Method Details

    # measureDis()

    测距


    # measureArea()

    测面


    # clearMeas()

    清空测量数据


    # activateToolbar(graphic)

    编辑要素

    • graphic [Object] 图形要素

    # deactivate()

    释放编辑状态


    # getCurrentState()

    获取当前编辑的一些状态
    {tool:当前工具,graphic:正在编辑的要素,isModified:要素是否被修改}


    # refresh()

    刷新工具类的内部状态

    查看原文

    赞 1 收藏 1 评论 0

    BLUE 收藏了文章 · 2019-06-13

    另辟蹊径:vue单页面,多路由,前进刷新,后退不刷新

    目的:vue-cli构建的vue单页面应用,某些特定的页面,实现前进刷新,后退不刷新,类似app般的用户体验。
    注: 此处的刷新特指当进入此页面时,触发ajax请求,向服务器获取数据。不刷新特指当进入此页面时,不触发ajax请求,而是使用之前缓存的数据,以便减少服务器请求,用户体验更流畅。

    项目需求:

    任何技术的探索,都来自项目的需求。之前经手的一个项目是微信端商城,使用的是传统的mvc模式,利用的是jq+js,因此对于商城的项目需求比较熟悉。目前在学习vue,练手一个商城,遇到之前经常提及而无法很好解决的需求。有些页面需要前进刷新,后退不刷新。比如,从商城的【首页】-->【详情页】-->【订单提交页】,每次打开新页面都需要获取新数据,但是按下返回键后,就不需要再获取新数据了,而滚动条还保留在之前的位置。最常见的操作是从【首页】-->【详情页】,然后在从【详情页】-->【首页】,如此反复。
    实例如图:
    首页详情页订单提交页

    前人经验:

    前人栽树,后人好乘凉。技术圈的分享一直都在蓬勃发展。遇到问题,我们可以尽情去搜索,去寻找大佬的足迹。针对上述需求,看到一个分享vue-router 之 keep-alive,比较符合我的需求,但是使用到我的项目上发现,稍微有点不适合。此分享技术要点,比较适合两个页面之前的跳转,返回。而我的页面是多个路由(2+)之间的跳转,返回。无奈,只能去自己探索发现。不过此技术要点给了我很好的启发,特此感谢作者。@ RoamIn

    实现思路:

    注:demo中,index页面包含三个链接导航。page1-->page2-->page3.依次前进,每次前进到一个新页面都需要获取数据,而按下后退键后,从page3返回到page2,page2不再获取新数据,而是使用之前缓存的数据。从page2返回到page1时,page1不再获取新数据,而是使用之前的数据。所以,page1和page2需要缓存,page3不需要缓存。可以把page1想象成首页,page2想象成详情页,page3想象成订单提交页。这样方便理解。

    • 利用keep-alive 缓存需要缓存的页面

      • 在app.vue中改写router-view

        <keep-alive>
            <router-view v-if="$route.meta.keepAlive">
                <!-- 这里是会被缓存的视图组件,比如 page1,page2 -->
            </router-view>
        </keep-alive>
        
        <router-view v-if="!$route.meta.keepAlive">
            <!-- 这里是不被缓存的视图组件,比如 page3 -->
        </router-view>
      • 在router/index.js中添加路由元信息,设置需要缓存的页面

        routes: [{
                path: '/',
                name: 'index',
                component: index,
                meta: {
                    keepAlive: false, //此组件不需要被缓存
                }
            },
            {
                path: '/page1',
                name: 'page1',
                component: page1,
                meta: {
                    keepAlive: true, //此组件需要被缓存
                    
                }
            },
            {
                path: '/page2',
                name: 'page2',
                component: page2,
                meta: {
                    keepAlive: true, // 此组件需要被缓存
                   
                }
            },
            {
                path: '/page3',
                name: 'page3',
                component: page3,
                meta: {
                    keepAlive: false, // 此组件不需要被缓存
                }
            }
        ]
      • 钩子函数的执行顺序

        • 不使用keep-alive
          beforeRouteEnter --> created --> mounted --> destroyed

        • 使用keep-alive
          beforeRouteEnter --> created --> mounted --> activated --> deactivated
          再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated 。created和mounted不会再执行。我们可以利用不同的钩子函数,做不同的事。务必理解上述钩子函数的执行时机和执行顺序,本教程的核心就依赖于此钩子函数
          activated和deactivated是使用keep-alive后,vue中比较重要的两个钩子函数,建议详细了解下

    • 需缓存的页面的写法

      注:demo中的page1和page2,这两个页面都需要缓存,思路一样,以下以page1为例,page2不再赘述。
      示例文件:components/page1.vue

      • data中初始化一个str字符串,存放从后台获取的数据

           data() {
             return {
               msg: "我是第一个页面",
               str: ""  // 加载页面后执行获取数据的方法,插入到此
             };
           }
      • methods中创建一个方法,模拟从后台获取数据

           methods: {
             getData() {
               // getData方法,模拟从后台请求数据
               this.str = "我是通过调用方法加载的数据。。。";
             }
           }
      • 修改router/index.js中的配置

        • 每次进入页面,我们都需要知晓是从哪个页面进来的,用以判断是否需要获取数据。以这个page1页面为例,当我们知晓是从page2过来的,我们就可以认为是用户操作了返回键,这时page1页面就不需要再获取新数据了,使用之前缓存的数据就可以了。如果是从别的页面过来的,我们就需要获取数据。

        • 我们可以通过beforeRouteEnter这个钩子函数中的from参数判断是从哪个页面过来的,这个参数执行时,组件实例还没创建,所有不能在data中定义变量。我们可以在路由中定义一个变量,用来判断。

        • 在router/index.js的meta中添加isBack变量,默认false

             {
                  path: '/page1',
                  name: 'page1',
                  component: page1,
                  meta: {
                      keepAlive: true, //此组件需要被缓存
                      isBack:false, //用于判断上一个页面是哪个
                  }
              },
              {
                  path: '/page2',
                  name: 'page2',
                  component: page2,
                  meta: {
                      keepAlive: true, // 此组件需要被缓存
                      isBack:false, //用于判断上一个页面是哪个
                  }
              },
      • beforeRouteEnter中判断是从哪个页面过来的

        • 判断是从哪个路由过来的,如果是page2过来的,表明当前页面不需要刷新获取新数据,直接用之前缓存的数据即可

            beforeRouteEnter(to, from, next) {
              // 路由导航钩子,此时还不能获取组件实例 `this`,所以无法在data中定义变量(利用vm除外)
              // 参考 https://router.vuejs.org/zh-cn/advanced/navigation-guards.html
              // 所以,利用路由元信息中的meta字段设置变量,方便在各个位置获取。这就是为什么在meta中定义isBack
              // 参考 https://router.vuejs.org/zh-cn/advanced/meta.html
              if(from.name=='page2'){
                  to.meta.isBack=true;
                  //判断是从哪个路由过来的,
                  //如果是page2过来的,表明当前页面不需要刷新获取新数据,直接用之前缓存的数据即可
              }
          
              next();
            },
      • activated中执行getData这个获取数据的方法

        • 因为这个页面需要缓存。只有第一次进入时才会执行created和mounted方法,再次进入就不执行了。而activated每次进入都执行,所以在这个钩子函数中获取数据。

          activated() {
            if(!this.$route.meta.isBack){
              // 如果isBack是false,表明需要获取新数据,否则就不再请求,直接使用缓存的数据
              this.getData();
            }
            // 恢复成默认的false,避免isBack一直是true,导致下次无法获取数据
            this.$route.meta.isBack=false
          
          },
      • 这样就可以了?

        • 当这样设置完毕后,你执行起来,貌似是可以了。第一次进入page1,能获取新数据,从page2返回时,不再获取新数据了,而是使用之前缓存的数据。但这样还有一个问题,当用户从page1进入page2后,因为某种原因,手动刷新了page2的页面。这时再返回到page1,发现之前缓存的数据丢失了,而且也没有再重新获取。所以我们还需要再添加一个判断条件,当用户手动刷新页面后,再返回时就需要重新获取数据了。

        • 如何添加这个条件,判断用户是否刷新了页面呢?我们知道,当使用keep-alive后,只有第一次进入后会触发created钩子函数,再次进入就不再执行了。当用户刷新了页面,这个钩子函数就会又执行,所以,我们可以利用这个小技巧来做点文章。

        • data中定义变量isFirstEnter用来判断是否第一次进入,或是否刷新了页面,默认false

             data() {
               return {
                 msg: "我是第一个页面",
                 str: "",  // 加载页面后执行获取数据的方法,插入到此
                 isFirstEnter:false // 是否第一次进入,默认false
               };
             },
        • created中把isFirstEnter变为true,说明是第一次进入或刷新了页面

             created() {
               this.isFirstEnter=true;
               // 只有第一次进入或者刷新页面后才会执行此钩子函数
               // 使用keep-alive后(2+次)进入不会再执行此钩子函数
             },
        • activated中增加判断条件

             activated() {
               if(!this.$route.meta.isBack || this.isFirstEnter){
                   // 如果isBack是false,表明需要获取新数据,否则就不再请求,直接使用缓存的数据
                   // 如果isFirstEnter是true,表明是第一次进入此页面或用户刷新了页面,需获取新数据
                   this.str=''// 把数据清空,可以稍微避免让用户看到之前缓存的数据
                   this.getData();
               }
               // 恢复成默认的false,避免isBack一直是true,导致下次无法获取数据
               this.$route.meta.isBack=false
               // 恢复成默认的false,避免isBack一直是true,导致每次都获取新数据
               this.isFirstEnter=false;
           
             },
        • 这样应该就可以了

    • 不需要缓存页面的写法

      注:demo中的page3,这个页面不需要缓存,该怎么写就怎么写,不需要做特别的设置。

    其它设置:

    使用keep-alive后,可能有点小问题:第二个页面可能继承第一个页面的滚动条的高度。(在我项目中遇到的)
    比如:page1向下滚动后,再进入page2,这时page2的滚动条可能是之前的高度,可能不会在顶部。

    • 解决方法一
      每次离开记录滚动条的高度,再次进入时根据项目需要再恢复之前的高度,或者置顶。

    • 解决方法二(推荐)
      router/index.js中添加如下代码(如不理解,请看参考链接)
      参考:HTML5 History 模式     滚动行为

        mode: 'history',
        scrollBehavior(to, from, savedPosition) {
            if (savedPosition) {
                return savedPosition
            } else {
                return {
                    x: 0,
                    y: 0
                }
            }
        }

    疑问点:

    在此次demo练习中,打印了一下钩子函数的执行顺序,发现一个疑问点(我对钩子函数理解也很浅显):
    从page1进入page2时,先执行了page2的beforeRouteEnter和created方法,然后才执行page1的deactivated方法。
    所以我把这两个初始化设置,放在了activated里面,而没有放在deactivated中

        this.$route.meta.isBack=false;
        this.isFirstEnter=false;

    钩子函数执行顺序

    结束语:

    为了解决这个前进刷新后退不刷新问题,让我整整苦恼了一周时间,想了很多方法,也没能解决。最后综合各个大佬经验,试验了很多次,才归结出这个比较‘low’的方法。
    目前,我也是vue小白,也在探索着前进,如果这个方法能解决你遇到的难题,我很高兴。如果你认为的确很low,求轻喷。
    demo在下方的GitHub中,欢迎star。
    也欢迎大家提供意见和建议,谢谢大家

    GitHub

    查看原文

    认证与成就

    • 获得 39 次点赞
    • 获得 6 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 5 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2017-08-02
    个人主页被 630 人浏览