Hive——UDF函数:高德地图API逆地理编码,实现离线解析经纬度转换省市区(离线地址库,非调用高德API)

文章目录

  • 1. 需求背景
    • 数据现状
    • 业务需求
    • 面临技术问题
    • 寻求其他方案
  • 2. 运行环境
    • 软件版本
    • Maven依赖
  • 3. 获取离线地址库
  • 4. Hive UDF函数实现
  • 5. 创建Hive UDF函数
  • 6. 参考

1. 需求背景

数据现状

目前业务系统某数据库表中记录了约3亿条用户行为数据,其中两列记录了用户触发某个业务动作时所在的经度和纬度数值,但是没有记录经纬度对应的省市区编码和名称信息。

业务需求

现在业务方提出一个数据需求,想要统计省市区对应的用户数等相关指标。

面临技术问题

因为历史数据量较大,如果通过调用高德API把所有历史数据中的经纬度对应的省市区请求回来,会面临一个个问题:在查看公司的高德API账户后,发现每天提供的最大调用量是300W次,那么要把历史3亿数据初始化调用完,需要30000W/300w=100天,要3个多月,这完全是不可接受的。

寻求其他方案

既然不能通过调用高德API的方式获取省市区,那有没有一个离线的地址库,然后从这个地址库获取历史数据的省市区呢。然后通过搜索引擎还真的找到了某个大神写的一个第三方库,可以从一个地址库文件来获取经纬度对应的省市区。
这个第三方库的Github地址:https://github.com/hsp8712/addrparser

2. 运行环境

软件版本

  • Java 1.8
  • Hive 3.1.0
  • Hadoop 3.1.1

Maven依赖

        <dependency><groupId>tech.spiro</groupId><artifactId>addrparser</artifactId><version>1.1</version></dependency><dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>3.1.0</version><scope>provided</scope></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-common</artifactId><version>3.1.1</version><scope>provided</scope></dependency>

3. 获取离线地址库

这个第三方库的作者提供了一个2019年9月份的离线地址库文件,考虑到这个文件的数据已经比较旧了,然后去翻看作者的源码文件,发现提供了爬取地址库的源码,直接拿来改改就可以用了(但前提是你要有请求高德API的API Key)。

下面是本人调整后并经过测试后可以正常请求地址库的代码(这个类中还引用了作者编写的其他类/接口,请参考作业的Github):

import org.apache.commons.cli.*;
import tech.spiro.addrparser.crawler.GetRegionException;
import tech.spiro.addrparser.crawler.RegionDataCrawler;
import tech.spiro.addrparser.io.RegionDataOutput;
import tech.spiro.addrparser.io.file.JSONFileRegionDataOutput;import java.io.IOException;
import java.util.Arrays;/*
*
* A command-line tool to crawl region data.
* */
public class CrawlerServer {private static Options options = new Options();static {options.addOption("k", "key", true, "Amap enterprise dev key");options.addOption("l", "level", true, "Root region level: 0-country, 1-province, 2-city");options.addOption("c", "code", true, "Root region code");options.addOption("o", "out", true, "Output file.");}private static void printHelp() {HelpFormatter formatter = new HelpFormatter();formatter.printHelp("CrawlerServer", options );}public static void main(String[] args) throws IOException, GetRegionException {CommandLineParser parser = new BasicParser();try {CommandLine cmd = parser.parse(options, args);
//            String key  = cmd.getOptionValue("k");
//            String level = cmd.getOptionValue('l');
//            String code = cmd.getOptionValue('c');
//            String outputFile = cmd.getOptionValue('o');String key  = "xxxxxxxxxxxxxxxxxxx";String level = "0";String code = "100000";String outputFile = "/Users/name/Desktop/china-region.json";if (!Arrays.asList("0", "1", "2").contains(level)) {throw new ParseException("option:level invalid.");}int _code = 0;try {_code = Integer.parseInt(code);} catch (NumberFormatException e) {throw new ParseException("code must be numeric.");}execute(key, level, _code, outputFile);} catch (ParseException e) {System.out.println(e.getMessage());printHelp();System.exit(-1);}}private static void execute(String amapKey, String level, int code, String out) throws IOException, GetRegionException {try (RegionDataOutput regionOutput = new JSONFileRegionDataOutput(out)) {RegionDataCrawler infoLoader = new RegionDataCrawler(regionOutput, amapKey);if ("0".equals(level)) {infoLoader.loadCountry();} else if ("1".equals(level)) {infoLoader.loadProv(code);} else if ("2".equals(level)) {infoLoader.loadCity(code);}}}
}

运行上面这段代码获取全国省市区地址库实践会比较久,大概要30分钟左右,生产的json文件china-region.json大小约160M。

4. Hive UDF函数实现

在获取到地址库数据之后,为了实现输入经度、维度输出省市区编码和名称的UDF函数,我们需要先把这个地址库文件china-region.json上传到一个指定的HDFS目录下面,这样在Hive中使用UDF函数的时候可以从HDFS目录下直接查询这个文件。

下面代码就是UDF函数的实现逻辑:输入经度、维度数值,然后查询离线地址库文件china-region.json,最终输出对应的省市区信息的json字符串。

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.io.Text;
import tech.spiro.addrparser.common.RegionInfo;
import tech.spiro.addrparser.io.RegionDataInput;
import tech.spiro.addrparser.io.file.JSONFileRegionDataInput;
import tech.spiro.addrparser.parser.Location;
import tech.spiro.addrparser.parser.LocationParserEngine;
import tech.spiro.addrparser.parser.ParserEngineException;import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;@Description(name = "GetRegionInfo",value = "_FUNC_(latitude, longitude) - Returns the province, city, and district names and codes based on latitude and longitude"
)
public class LgtLttUDF extends GenericUDF {// 经纬度-省市区基础库文件private static final String RESOURCE_FILE = "hdfs://nameservice/user/username/udf/china-region.json";// 位置解析引擎private static volatile LocationParserEngine sharedEngine;private static final Object lock = new Object();/*** 1. UDF函数入参校验* 2. 创建并初始化位置解析引擎* 3. 设置UDF函数返回值数据类型* @param arguments* @return ObjectInspector* @throws UDFArgumentException*/@Overridepublic ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {// 参数数量校验if (arguments.length != 2) {throw new UDFArgumentException("The function requires two arguments.");}// 参数类型校验if (!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE) ||!arguments[1].getCategory().equals(ObjectInspector.Category.PRIMITIVE)) {throw new UDFArgumentException("GetRegionInfoUDF only accepts primitive types as arguments.");}// 创建并初始化位置解析引擎initializeSharedEngine();// 返回值return ObjectInspectorFactory.getStandardStructObjectInspector(Arrays.asList("province_name", "province_code", "city_name", "city_code", "district_name", "district_code"),Arrays.asList(PrimitiveObjectInspectorFactory.javaStringObjectInspector,PrimitiveObjectInspectorFactory.javaStringObjectInspector,PrimitiveObjectInspectorFactory.javaStringObjectInspector,PrimitiveObjectInspectorFactory.javaStringObjectInspector,PrimitiveObjectInspectorFactory.javaStringObjectInspector,PrimitiveObjectInspectorFactory.javaStringObjectInspector));}/*** 初始化位置解析引擎* @throws UDFArgumentException*/private void initializeSharedEngine() throws UDFArgumentException {if (sharedEngine == null) {synchronized (lock) {if (sharedEngine == null) {try {// china-region.json文件作为基础数据InputStreamReader reader = getJsonFileInputStreamFromHDFS(RESOURCE_FILE);RegionDataInput regionDataInput = new JSONFileRegionDataInput(reader);// 创建位置解析引擎sharedEngine = new LocationParserEngine(regionDataInput);// 初始化,加载数据,比较耗时sharedEngine.init();} catch (ParserEngineException | IOException e) {throw new UDFArgumentException("Failed to initialize LocationParserEngine: " + e.getMessage());}}}}}/*** 从HDFS路径读取JSON文件并返回InputStreamReader。** @param hdfsPath HDFS上的JSON文件路径* @return 文件的InputStreamReader对象,用于进一步读取内容* @throws IOException 如果发生I/O错误*/public static InputStreamReader getJsonFileInputStreamFromHDFS(String hdfsPath) throws IOException {// 创建Hadoop配置对象Configuration conf = new Configuration();// 根据配置获取文件系统实例FileSystem fs = FileSystem.get(conf);// 构建HDFS路径对象Path path = new Path(hdfsPath);// 检查文件是否存在if (!fs.exists(path)) {throw new IOException("File " + hdfsPath + " does not exist on HDFS.");}// 打开文件并获取输入流return new InputStreamReader(fs.open(path), "UTF-8");}/*** 通过经度、维度获取对应省市区的编码和名称* @param args* @return* @throws HiveException*/@Overridepublic Object evaluate(DeferredObject[] args) throws HiveException {if (args == null || args.length != 2) {return null;}try {// 经度double longitude = Double.parseDouble(args[1].get().toString());// 纬度double latitude = Double.parseDouble(args[0].get().toString());// 位置信息Location location = sharedEngine.parse(latitude, longitude);// 省市区信息RegionInfo provInfo = location.getProv();RegionInfo cityInfo = location.getCity();RegionInfo districtInfo = location.getDistrict();// 返回省市区编码、名称return new Object[]{new Text(provInfo.getName()),new Text(String.valueOf(provInfo.getCode())),new Text(cityInfo.getName()),new Text(String.valueOf(cityInfo.getCode())),new Text(districtInfo.getName()),new Text(String.valueOf(districtInfo.getCode()))};} catch (Exception e) {throw new HiveException("Error processing coordinates", e);}}@Overridepublic String getDisplayString(String[] children) {return "ltt_lgt_region(" + children[0] + ", " + children[1] + ")";}
}

5. 创建Hive UDF函数

将第四步的UDF实现代码打jar包:china_region.jar,Hive和Hadoop依赖不需要打进去,因为集群上都是有的,只需要把这个第三方的addrparser打进去就可以了。

  1. 将jar包china_region.jar上传到HDFS指定目录下

  2. 将jar包添加到hive的classpath。在Hive的cli中执行如下命令

    hive> add jar china_region.jar
    
  3. 创建UDF函数(永久性UDF函数)

    hive> create function ltt_lgt as 'com.hive.udf.LgtLttUDF ' using jar 'hdfs://nameservice/user/username/udf/china_region.jar';
    
  4. 测试UDF函数

    hive> select ltt_lgt(100.750934, 26.038634)
    

6. 参考

  1. https://github.com/hsp8712/addrparser

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51259.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java1.2标准之重要特性及用法实例(十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

SciPy 与 MATLAB 数组

SciPy 与 MATLAB 数组 SciPy 是一个开源的 Python 库,广泛用于科学和工程计算。它构建在 NumPy 数组的基础之上,提供了许多高级科学计算功能。MATLAB 是一个高性能的数值计算环境,它也使用数组作为其基础数据结构。在这篇文章中,我们将探讨 SciPy 和 MATLAB 在数组操作上的…

征服 Docker 镜像访问限制:KubeSphere v3.4.1 成功部署全攻略

近期&#xff0c;KubeSphere 社区的讨论中频繁出现关于 Docker 官方镜像仓库访问受限的问题。 本文旨在为您提供一个详细的指南&#xff0c; 展示在 Docker 官方镜像访问受限的情况下&#xff0c;如何通过 KubeKey v3.1.2 一次性成功部署 KubeSphere v3.4.1 以及 Kubernetes …

深入剖析:GaussDB与MySQL在COUNT查询中的并行化技术

引言 数据库查询性能优化是数据库管理和开发中的一个重要议题。在处理大数据量的COUNT查询时&#xff0c;传统的单线程处理方式可能无法满足现代应用的性能需求。GaussDB(for MySQL)和MySQL作为流行的数据库系统&#xff0c;它们在并行查询优化方面有着各自的策略和技术。本文…

Python批量采集某东评论,实现可视化分析

女朋友没事就喜欢网购&#xff0c;买一大堆又不用&#xff0c;总说不合适&#xff0c;为了不让她花冤枉钱&#xff0c;于是我决定用Python写一个采集商品评论的脚本&#xff0c;然后对商品进行分析&#xff0c;这样就不怕踩到坑了&#xff01; 让我们直接开始本次操作 准备工作…

Python及Jupyter-Notebook安装

来源&#xff1a; “码农不会写诗”公众号 链接&#xff1a;Python及Jupyter-Notebook安装 文章目录 01 Python安装1.1 下载安装包1.2 双击安装包&#xff0c;开始安装1.3 选择安装配置1.4 选择需要安装的Optional Feature&#xff0c;点击Next1.5 选择需要安装的Advanced Feat…

Apache DolphinScheduler Worker Task执行原理解析

大家好&#xff0c;我是蔡顺峰&#xff0c;是白鲸开源的高级数据工程师&#xff0c;同时也是Apache DolphinScheduler社区的committer和PMC member。今天我要分享的主题是《Worker Task执行原理》。 整个分享会分为三个章节&#xff1a; Apache DolphinScheduler的介绍Apache …

Python3网络爬虫开发实战(3)网页数据的解析提取

文章目录 一、XPath1. 选取节点2. 查找某个特定的节点或者包含某个指定的值的节点3. XPath 运算符4. 节点轴5. 利用 lxml 使用 XPath 二、CSS三、Beautiful Soup1. 信息提取2. 嵌套选择3. 关联选择4. 方法选择器5. css 选择器 四、PyQuery1. 初始化2. css 选择器3. 信息提取4. …

一步步理清开源路径规划导航引擎GraphHopper

一步步理清开源路径规划导航引擎GraphHopper&#xff0c;学习应用 GraphHopper简介 源码用 Java 编写&#xff0c;开发人员可以通过 Maven 轻松上手。可以用作** Java 库**来计算两个或多个点之间路线的距离、时间、逐向指示和许多道路属性。可以用作独立的 Web 服务器来计算…

CLI是命令行界面(Command-Line Interface)的缩写

CLI是命令行界面&#xff08;Command-Line Interface&#xff09;的缩写。它是一种用户界面&#xff0c;允许用户通过键盘输入文本命令来与计算机进行交互。CLI在图形用户界面&#xff08;GUI&#xff09;普及之前是最常用的用户界面类型。 CLI的特点&#xff1a; 高效性&…

CentOS下原生态安装mysql,推荐yum

mysql 是一个关系型数据库管理系统&#xff0c;是最流行的关系型数据库管理系统之一&#xff0c;本文将介绍如何在 CentOS 7.x 中安装 mysql 。 安装mysql mysql 的安装方法可以通过以下几种方式进行&#xff1a; 1、通过第三方面板安装 mysql 2、通过 yum 命令安装 mysql 3、…

计算R velocity的方法和流程(CellRank2)

愿武艺晴小朋友一定得每天都开心 第一步:拿到cellranger count的文件夹; 运行cellranger count(这个得提前准备:R1和R2的fastq.gz文件 & 鼠的参考基因组文件) wget https://cf.10xgenomics.com/supp/cell-exp/refdata-gex-mm10-2020-A.tar.gz 最后会出现这个:Pipes…

C++简单实现消息队列

简介 消息队列是一种应用间的通讯方式&#xff0c;消息发送后可以立即放回&#xff0c;由消息系统来确保消息的可靠传递。消息发布者只需要将消息发布到消息队列中&#xff0c;而不需要管谁来取。消息使用者只管从消息队列中取消息而不管谁发布的。这样发布者和使用者都不同知…

uniapp文件上传前后端代码展示

使用uniapp做文件上传的时候&#xff0c;可以使用系统提供的接口进行文件上传 前端部分 前端组件代码 使用的是uview的上传组件 <u-upload id"selectPhoto" :style"{display:selectPhotoDisplay}" :useBeforeRead"true"afterRead"aft…

21.发布确认模式-高级

问题 生产环境中由于一些不明原因&#xff0c;导致rabbitmq重启&#xff0c;在重启的期间生产者消息投递失败&#xff0c;导致消息丢失&#xff0c;需要手动处理恢复。那么如何才能进行rabbitmq的消息可靠性投递&#xff1f;特别是在极端的情况&#xff0c;rabbitmq集群不可用…

Optuna包中TPE算法(代码解析)

前言 Optuna是一个自动超参数优化软件框架&#xff0c;专为机器学习而设计。Github项目地址 TPE&#xff08;Tree-structured Parzen Estimator&#xff09;是一种用于超参数优化的算法&#xff0c;它被广泛应用于机器学习模型中。TPE 算法基于贝叶斯优化技术&#xff0c;特别…

Python 教程(六):函数式编程

目录 专栏列表前言函数定义参数返回值 示例函数类型普通函数空函数匿名函数&#xff08;Lambda 函数&#xff09;嵌套函数函数装饰器高阶函数 函数参数位置参数默认参数可变位置参数可变关键字参数 函数属性和方法__name____doc__func.__dict__func.__defaults__func.__annotat…

算法工程师第二十二天(491.递增子序列 46.全排列 47.全排列 II )

参考文献 代码随想录 一、非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作…

解析大数据分析行业的现状与前景:全球视角下的中国力量

目录 写在开头1、全球大数据分析趋势&#xff1a;引领潮流的先行者2、中国大数据分析现状&#xff1a;后起之秀的强劲崛起3、岗位分类与技能需求&#xff1a;大数据从业者的职业版图3.1. 数据科学家 (Data Scientist)3.2. 数据产品和项目管理岗位3.3. 数据工程师 (Data Enginee…