开发者学堂课程【2020版大数据实战项目之DMP广告系统(第六阶段):商圈库_功能_UD F 实现功能】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/681/detail/11832
商圈库_功能_UD F 实现功能
内容介绍:
一、投影经纬度
二、生成范围 id
三、根据经纬度查询高 德 api
四、实现逻辑,得到商圈信息
第一个逻辑可以实现这个部分的逻辑,如图所示:
也就是这个 ods 表存在,但是商圈表不存在,直接处 理 ods 层表,再读取高 德 api 生成对应的商圈信息。
先去写第二个功能也是可以的,第二个功能就 是 ods 存在,商圈表也存在。如图所示:
先去处 理 ods 表,再和商圈表做一个差集。还是先选择第一个功能来编写,因为第一个功能比较简单,所以先写简单的这个功能。这个功能整体的方式应该是怎么样的,还要先进行一次梳理。
整体上现在拿到了一个 ods层表,这个 ods 层的这个表只需要两列。一列是经度,一列是纬度。拿到这两列以后就可以根据经度和纬度生成一个范围,生成范围以后就可以这个根据经纬度查询高德 api,从而得到商圈信息。整体上的步骤就是这样。
1. 第一步先去投影经纬度。
2. 第二步进行经纬度的合并,或者经纬度合并和这个高 德 ap i 一起来做,都是一样的,所以第二步就先暂定为根据经纬度查询高 德 api。
3. 第三步就要根据这个经纬度,生成一个范 围 id。
4. 第四步去合并数据,处理数据并落地。
落地就在底下页面落,整体上的步骤就是这样,按照这个步骤去写,但是就不再一一对应序号。
一.投影经纬度
可以先去拿 到 dataframe,其实就 是 ods 层的这个表 和 area 这个表,只需要一 个 O D S 层的表。 之后就可以通过代 码 valods=odsOption.get 直接获取到,拿 到 ods 的数据以后就可以直 接 select 其中的精度和纬度。select 也是比较简单的,直接输入以下代码:
ods .select(cols=longitude,latitude)。找到精 度 longitude,再找到纬 度 latitude,这样 就 select 过了。
二.生成范 围 id
第一步应该先去生成范围值,生成范围值该怎么去做呢?第一种办法在这里写一个 map,所有的逻辑都写到这个 ma p 里,这种方法是好,但是写起来特别繁琐,所以在实际的工作当中很少这样去写。还是采用一个更简洁的写法,这个写法就是去定义一个 udf,通过这个 udf 去做整个逻辑就会变得特别简单。
先做第一件事就是生成经纬度,所对应的范围值。创建出来 def location to geo hash,生成的范围值就叫 做 geo hash。
它需要接收两个参数,第一个 是 longitude,第二个 是 latitude。第一个值 是 double 类型的,第二个值也 是 double 类型的。这个 double 类型没错,但是最终要返回一个什么类型呢?要返回一个 string 类型,这样就做完了。整个代码如下所示:
def locationToGeoHash(longitude:double
,latitude:double):string={
在这个方法当中,需要做的事情是什么呢?假如给一 个 116.12312 3 这是精度,再给一个 31.456456。它是一个特别具体的点,可能精确到几十米以内了,如果后面再多来几个小数点,那可能就是精确到几米以内,甚至是一米以内。
如果把它作 为 Key 存在这个库当中,那么商圈库就失去了它应有的意义,因为必须和它精准匹配站在同一个点上,差一米内,才能够匹配到商圈信息。
制造一个商圈库没有任何意思。所以还是要生成范围信息。要生成范围信息,假如这是第一个经纬度,把第一个经纬度括上括号,再输入一个经纬度,将其中数字改一改,一位数改成五,一位数改成二。
那么这时如果能让这两个经纬度的信息同时对应一个,比 如 JDHFS75 7 这样的一串字符串,这两个经纬度如果最终都能够映射到这个字符串当中,那么下一次再来其它的范围内的值也映射到这个字符串当中,整体上的这个性能会更好一些。
把范围表示出来后,在这一步当中就要借助一个开源的工具,这个开源的工具叫 做 geohash,它是一种算法,这种算法能够将一定范围内所有经纬度,都对照一个这个 string 值,这样就能够间接的实现范围的概,那么这就 是 geohash。
如果想去使 用 geohash,要先打 开 pom 文件,在后面追加上一个新 的 dependency,这个 dependency 就叫做 geohash,这 个 1.3. 0 版本是没问题的,相对来说不算新,但也不算特别老,应该是比较新的一个版本。
等待这个走条加载完以后就可以实现相应的功能了。拿到一个类叫 做 geohash,拿 到 geohash 这个类以后就可以使用一 个 GeoHash.withCharacterPrecision,接下来第一个接收的应该是纬度,第二个接收经度,第三个是接收一 个 Character 的长度,这个长度就是生成的字符串的长度。
再继续向下看,把纬度先传进来,再传入经度,八位就够了,再输 入 to bass32,按 照base 3 2 进行一个混淆加密,给它生成一 个 string 返回,这是第一个函数就 是 geohash 这样的一个函数。代码如下所示: GeoHash.withCharacterPrecision(latitude,longitude,numberofcharacters=8).to Base32
接下来使用外层,走到如下这个位置,放在这里进行输入
之后就可以去注 册 UDF,注 册 UD F 可以这么做,首先拿 到 spark.udf.register 这样的一个函数,这个函数可以先注册一 个 geohash 函数,对应这样 的 UDF,对应的函数为:locationToGeoHash,直接将这个函数传进来,当然这个地方有可能会报错,传一个下划线,把它转成一个函数就没问题了。
第一 个 UD F 注册好了之后,缓慢滚动,找到第一个 case。
第一个 case 前面的这些也必须保留着,以后可以去查看。再往下走,走 到 ods,select(cols=longitude,latitude)这个位置以后,就可以生成对应的这 个 geohash, 再 select 一下。
在 select 时不能直接去写这个列名,因为这样只能写列名,就需要换一个方法,换一个叫 做 selectExpr 里面就可以写上对应的表达式了,比如使 用geohash 把精度和纬度传进来,那么第一列就生成了,还可以 再 as 一下,as 一个 geohash。代码如下所示:
selectExp(exprs=
’’geohash(longitude,latitude)as geohash’’)
三.根据经纬度查询高德api
接下来再来看第二列,第二列要去请求高德 的 api,这个处理完了以后才能够去落地到商圈库当中,所以再去注册一个 U D F,这个 UD F 就命名 为fetchAMapAPI,其实也可 以 FetchArea。
接下来传入两个值,第一个是经度,这个参数就直接拷背过来,返回一 个 string,这 个 string,其实就是商圈信息。代码如下所示:
Def fetchArea(longitude:Double,latitude:double):string=
那么这时就可以使 用 httputils当 中 getlocation info 这样的一个方法,把经度和纬度传进去。 那 get location inf o 返回的是什么内容?这 个 get location inf o 也是 在 http 那一章节写的一个方法,其实就是去访问高德 的 api,来得到相应的数据。
这样就能得到对应的一个response string。那么这其实就是一个 jsons string,给它命名 为 jsons string 也可以。但是这个 jsons string,它是一个 option,所以还是要再 去 get or else,这个 get or else,可以给一个空的返回值,其实这样做不是特别好,但是没有关系,可以就先这样做。
这时就可以返回这样的一 个 get or else 拿到一个 Jsonstring。这样写会有一点问题,如果它确实为空的话,那么这个 Jsonstring拿到一个空字符串往下去进行传递时,就会出现一些问题。
所以还是 把 get or else 去掉,遵循这个规范来进行编写。知道可以这样做,但是还是按照最严谨的态度去编写这段代码。
拿到的是一个 jsonoption,拿 到 jsonoption 以后,就可以 去 ma p 一下。ma p 就是如果里面有值,就会走这个 map,如果这 个 optio n 里面没有值,那这个 ma p 就不走。那么再继续向下看,这个 ma p 当中就能拿到一个 json,拿 到 json 以后就可以进行相应的逻辑了。
四.实现逻辑,生成商圈信息
这个逻辑就是使 用 httpUtils.parse json(json), 把 json 传进来,传进来以后就可以拿到一个相应的对象。
这个对象拿到后,其实就是一个 AMaploction 这样的一个信息。拿到这样的信息以后就可以对这个信息来进行相应的操作,当然可以先将这个信息返回掉,现在先返回也可以,给一个 amap location。
如果这个代码是这样写的:
Val amap loction=httpUtils.parsejson(json)
那么可以只拷贝=httpUtils.parsejson这一行, 把 val amap loction amaploction 这些代码都减掉,让这 个 jason 直接返回对应 的 amap loction 的对象。
先进到这个 parsejson当中,能注意到它返回的是一个 a map location 的对象,其实就是解 析 json 然后呢生成对应 的 result。
前面有一个方法叫 做 get location info,这个 get location inf o 就是 把 longitude 和 latitude 拼到 location=$longitude,$latitude 这个地方,接下来去发送请求,获取结果,然后返回。
但是有可能它会返回一个None 回来,所以要在这个 arer runner 当中去使 用 map,这个 map 是什么意思?这个 map 本质上,这 个 jason option 它的类型是一 个 option string 的类型。
那么这 个 optionstrin g 类型 点 map,就可以生成另外的类型,生成哪个类型呢?现在返回的是一 个 amap location 的对象,那么就生成了一 个 A map location 这样的类型。
但是它还是一 个 option,如果这个 optio n 是一个 none,那 么 ma p 过以后,它里面还 是 None。而这段 httputils.parsejson 这个代码就不会再走了。
所以在这个 option上进 行 map,是非常安全的,不会出现空的问题。接下来拿到这个对象以后还要再进行一 次 map,这一 次 ma p 其实拿到的就是这 个 location 对象。这时就一定要写一个划括号,拿到这个 location 的时候要去拿到其中 的 business areas。那么输 入 location,Regeocode,regeocode 这些属性其实返回的也是一 个 option,所以在这个地方又拿到了一 个 option,这个地方就比较不好处理,会比较麻烦一点,但是这样是最严谨的。
拿到这个 regeocode以后要去判 断 regeocode,如果它 是 isdefined,如果它没有这个命名或者它没有这个值,那就判断它是不 是 empty,如果 是 empty 的话,就返回一个 none 回去。当然是不能直接返回这个 non e 的,所以还是把这个 is empty 改 为 is defined。
如果它 是 is defined,就继续向下进行,接着就通过这 个 regeocode,然后再 去 get ,get 完以后再去获取这 个 address component,这 个 address component 其实也是一 个 option,那 么 address 这 个 option 也要再进行一次简单的判断,那么怎么进行判断呢?
就判断这 个 address is defined,判 断 isdefined 以后就要拿到其 中 businessarea,历经千辛万苦终于拿 到 businessarea。
但是这个代码太复杂,看起来不清晰,所以将这段代码删除。如下图:
那 么 regeocode直 接 get,不考虑这个空的问题,事实证明部分情况下是不会为空的。那么再 去 get address Component 再去直 接 ge t 不考虑空的问题,然后就拿到了一 个 business areas,这 个 business areas,也是一 个 option 类型的,但是在这里就不再继续直 接 get 了,就直接拿到这 个 area option。
起码要处理一个这个 option,所以拿到这个 area option 以后就可以进行相应的处理了,处理也非常的简单,可以直接拿 到area, 在 business area s 这里可 以 get or else。
business areas 这个属性可以点开看看,它是一 个 option,里面是一 个 list,如果它里面没有值,就可以直接返回一个 空 List 出去,所以在这个位 置 get or else 就生成一个新的空 的 List,这样就可以了。
接下来拿到这个 are a 以后要把这些值给它拼起来,它现在是一个 list area,要给它转成什么形式呢?比如说转成东单,西单,中间用逗号分隔起来。然后再转成其他的,比如说西二旗,转成这种形式的字符串,那么拿到这 个 area,可以把这 个 area 当中的这 个 business areas 转 成 name,转成字符串的形式,直 接 ma p 一下, 那 map 之后输入下划线.name,直接返回,返回了以后可以直 接 mskstring,那么这 个 msktring 可以传进来一个逗号,这样就分隔开了。代码如下所示:
Area.map(_.name)mskstring(‘’,’’)
这个代码会有一点绕,但是相对来说不难。首先这 个 area 它是一 个 list,这 个list 是 business area,所以通 过 map 直接把里面每一 个 business are a 对象 的 name 取出来,那么这一 段 area.map(_.name)代码,其实是一 个 list,但是里面存的 是 string。
接下来再 去 make 一个 strin g 就可以 把 Lis t 当中 的 strin g 的拼起来,以逗号作为分隔。最终把这个内容返回出去就完成了这个功能。
但是有可能是返回了一 个None,所以还 是 get or else,如果里面没有任何内容就返回一个空字符串出去,那么整段代码就已经写完了。
并且在这段代码里面还考虑了什么问题?就是判空的问题。这 个 fitch are a 也要给它注册为一 个 udf 表,先拿 到 spark udf,然后输 入 register ,register 就命名 为 fetch,接下来就接收 到 fetch 这个函数,然后给一个下划线,这时就可以使用这样的一个函数。
在刚才 的 odsselect这里, 去 fetcharea,往里面传入经纬度,再 去 as area,那么整个的数据集就生成了。
那么这个数据集就可以把它赋值给这 个 result,那 么 result 就变得有值了。result 这一行代码在报错,,原因是什么呢?原因是虽然让这个 result 为空,在后面去设置,但是 将 val 改 成 var 这个问题就解决了,那么第一个逻辑就已经处理完了,并且在其中写了两个 udf。
这个过程比较复杂,下去一定要去理顺,这个章节就暂时告一段落。