一 、 首先说下业务需求背景,因业务需求需要转换经纬度坐标,完全转换为utm 50N的格式。由于中国跨越了多个带区,在把utm 50N的格式的坐标转为wgs84经纬度的时候在西藏和新疆地区出现偏差,精确度下降。这个udf函数就是为了把全国的utm转换为wgs84经纬度的之后都是争取的或者说误差很小很小。
二、hive UDF的局限的地方是无法提前初始化开发这想要的对象,尤其是大的对象;如何是一个固定键值的文件的话还可以做map join,但是这个文件是没有键值的。在解决问题之前我我尝试看多种方式,看看源码是不是有我不知道的初始化方式,结果会是UDF真的太局限了,处理evaluate函数之外某用户别的东西了。也尝试把数据采用初始化数组的方式写在代码内,结果也不行,java根本不允许单个对象变量超过65536 个,更何况了我需要的是两百多万条数据。
最后是想到了java的static {} 静态代码块的方式初始化一个静态数组,静态代码只会初始化一次。到这个也还有一个问题就是文件挡在哪里的问题,刚开始时放在jar内,分布式计算之后无法用相同的路径去完成;放在本地客户端上可以,但是需要进入hive shell的当前目录存在需要读取的文件,很多人用的情况下很麻烦。最后是采用了hadoop的api读取hdfs的方式。最终是 UDF + 静态代码+读取hdfs的方式解决了这个问题。执行过程中add jar的时候会去初始化g这个数组,需要几秒钟时间,这个是可以忍受的。
三、下面是完整代码
----------------------------------------------------------------------------
package cn.smartstep.extract.tables;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Serializable;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.ql.exec.UDF;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import cn.smartstep.extract.utm.Cast;@SuppressWarnings("all")
public class UTMToLatLngFile extends UDF implements Serializable { public static Cast[][] g = new Cast[1860][1420];
public static void main(String[] args) {
}
/**
* 静态代码只执行一次,可以通过Class.forName("classPath")的方式唤醒代码的static代码块。
* 采用静态代码块初始化需要的查询数组,将数组的内容放在hdfs上,这种方式避免了从本地读取文件的麻烦。
* 从本地读取文件是需要在每一个节点读要配置,而起进入hive shell的时候需要在当前目录存在数组配置文件。
*/
static {
try { Configuration conf = new Configuration();// 读取配置文件
// 流读入和写入
InputStream in = null;
// 获取HDFS的conf
// 读取HDFS上的文件系统
FileSystem hdfs = FileSystem.get(conf);
// 使用缓冲流,进行按行读取的功能
BufferedReader buff = null;
// 获取日志文件的根目录
Path listf = new Path("hdfs:///user/ss_deploy/jars/grid_whome");
// 获取根目录下的所有2级子文件目录
FileStatus stats[] = hdfs.listStatus(listf);
// 自定义j,方便查看插入信息
//int j = 0;
for (int i = 0; i < stats.length; i++) {
// 获取子目录下的文件路径
FileStatus temp[] = hdfs.listStatus(new Path(stats[i].getPath().toString()));
for (int k = 0; k < temp.length; k++) {
//System.out.println("文件路径名:" + temp[k].getPath().toString());
// 获取Path
Path p = new Path(temp[k].getPath().toString());
// 打开文件流
in = hdfs.open(p);
// BufferedReader包装一个流
buff = new BufferedReader(new InputStreamReader(in));
String str = null;
while ((str = buff.readLine()) != null) {
String[] o = str.split("\\|");
int ii = Integer.parseInt(o[0]);
int jj = Integer.parseInt(o[1]);
Double x = Double.parseDouble(o[2]);
Double y = Double.parseDouble(o[3]);
Double lat = Double.parseDouble(o[4]);
Double lng = Double.parseDouble(o[5]);
g[ii][jj] = new Cast(x, y, lat, lng); }
buff.close();
in.close();
}
}
hdfs.close(); /* 读入TXT文件 */
// String pathname = "src/main/resources/test.txt"; //
// 绝对路径或相对路径都可以,这里是绝对路径,写入文件时演示相对路径
// String pathname =
// "src/main/resources/fixssutm-1860x1420-00000.txt"; /*
* String pathname = "fixssutm-1860x1420-00000.txt"; File filename =
* new File(pathname); // 要读取以上路径的input。txt文件 InputStreamReader
* reader = new InputStreamReader( new FileInputStream(filename));
* // 建立一个输入流对象reader BufferedReader br = new
* BufferedReader(reader); // 建立一个对象,它把文件内容转成计算机能读懂的语言 String line =
* ""; line = br.readLine(); while (line != null) { line =
* br.readLine(); // 一次读入一行数据 if (line != null) { String[] o =
* line.split("\\|"); int i = Integer.parseInt(o[0]); int j =
* Integer.parseInt(o[1]); Double x = Double.parseDouble(o[2]);
* Double y = Double.parseDouble(o[3]); Double lat =
* Double.parseDouble(o[4]); Double lng = Double.parseDouble(o[5]);
*
* g[i][j] = new Cast(x, y, lat, lng); } }
*/ } catch (Exception e) {
e.printStackTrace();
} }
/**
*
*@Description :hive udf函数的入口
* @param X (utm的x)
* @param Y (utm的y)
* @throws Exception
* @time : 2019年12月30日下午2:52:39
* @author :wangqj
* @return :String
*/
public String evaluate(int X, int Y) throws Exception {
Double[] latlng = gux1000((double) X, (double) Y);
return latlng[0] + "," + latlng[1]; }
/**
*@Description :divmod函数读取x和y的模和余数
* @param x
* @param y
* @time : 2019年12月30日下午2:55:19
* @author :wangqj
* @return :int[]
*/
public int[] divmod(int x, int y) {
int[] amod = new int[2];
amod[0] = x / y;
amod[1] = x % y;
return amod;
} /**
*@Description :读取差值数组,查询经纬度
* @param x2
* @param y2
* @param grid
* @param x0
* @param y0
* @param xstep
* @param ystep
* @time : 2019年12月30日下午2:56:14
* @author :wangqj
* @return :Double[]
*/
public Double[] gux(Double x2, Double y2, Cast[][] grid, int x0, int y0,
int xstep, int ystep) {
Double[] latlng = new Double[2]; Double xx = (x2 - x0);
int[] irx = divmod(xx.intValue(), xstep);
int i = irx[0];
Double rx = (double) irx[1];
Double yy = (y2 - y0);
int[] jry = divmod(yy.intValue(), ystep);
int j = jry[0];
Double ry = (double) jry[1]; Cast gridij = grid[i][j];
Cast gridi1j = grid[i + 1][j];
Cast gridij1 = grid[i][j + 1]; Double lat = gridij.tlat + rx / xstep * (gridi1j.tlat - gridij.tlat)
+ ry / ystep * (gridij1.tlat - gridij.tlat);
Double lng = gridij.tlon + rx / xstep * (gridi1j.tlon - gridij.tlon)
+ ry / ystep * (gridij1.tlon - gridij.tlon); latlng[0] = lat;
latlng[1] = lng;
return latlng;
} /**
*
* @Description :读取
* @param x2
* @param y2
* @return
* @time : 2019年12月30日下午2:56:36
* @author :wangqj
* @return :Double[]
*/
public Double[] gux1000(Double x2, Double y2) {
//utm坐标x的值域(-6800000, 1600000)
return gux(x2, y2, g, -6800000, 1600000, 5000, 5000);
}}
package cn.smartstep.extract.utm;
import java.io.Serializable;
@SuppressWarnings("all")
public class Cast implements Serializable { public Double tx;
public Double ty;
public Double tlat;
public Double tlon;
public Cast(Double tx, Double ty, Double tlat, Double tlon) {
super();
this.tx = tx;
this.ty = ty;
this.tlat = tlat;
this.tlon = tlon;
}
public Double getTx() {
return tx;
}
public void setTx(Double tx) {
this.tx = tx;
}
public Double getTy() {
return ty;
}
public void setTy(Double ty) {
this.ty = ty;
}
public Double getTlat() {
return tlat;
}
public void setTlat(Double tlat) {
this.tlat = tlat;
}
public Double getTlon() {
return tlon;
}
public void setTlon(Double tlon) {
this.tlon = tlon;
}
}
--------------------------------------------------------------------------------------------------------------------------------------------
需要读取的文件格式:
17|253|-6682870.711377543|2911374.176077393|15.017040630294595|60.07745523400567
17|254|-6682471.324196002|2916379.345332657|15.042378926184604|60.0692926510859
17|255|-6682071.076545545|2921383.9669462577|15.06771097398942|60.06111348512968
17|256|-6681670.070101743|2926388.3746052873|15.093035364394279|60.052925247075486
17|257|-6681268.566914886|2931392.427066926|15.118358336705251|60.04472201675457
17|258|-6680866.221413381|2936395.796781277|15.143676772048403|60.03649840491263
17|259|-6680463.086116834|2941398.774157732|15.168989283657806|60.02826018117868
17|260|-6680059.309639597|2946401.340439431|15.194298266057492|60.02000675986658
17|261|-6679654.907294498|2951403.4194463813|15.21960425900364|60.01173711365137
17|262|-6679249.6075822795|2956405.0461663115|15.244902656073505|60.00345279469018