package com.xiaopeng.map.utils;
|
|
import com.google.flatbuffers.FlatBufferBuilder;
|
import com.xiaopeng.map.dto.AreaInput;
|
import com.xiaopeng.map.dto.MapAreaDataDto;
|
import com.xiaopeng.map.dto.OddAreaDto;
|
import com.xiaopeng.map.dto.OddPointDto;
|
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.nio.ByteBuffer;
|
import java.util.List;
|
|
/**
|
* 基于坐标生成 XNGP 区域相关数据文件的工具类,包括:
|
* - MapAreaData proto 源数据(map_area_data.proto)
|
* - RTree flatbuffer 索引数据(map_area_rtree.fbs)
|
*/
|
public class XngpAreaRTreeFbsUtil {
|
|
/**
|
* 简单经纬度点结构。
|
*/
|
public static class LonLat {
|
private final double lon;
|
private final double lat;
|
|
public LonLat(double lon, double lat) {
|
this.lon = lon;
|
this.lat = lat;
|
}
|
|
public double getLon() {
|
return lon;
|
}
|
|
public double getLat() {
|
return lat;
|
}
|
}
|
|
/**
|
* 将形如 \"lon_lat,lon_lat,...\" 的字符串解析为坐标点列表。
|
* 例如:104.06249_30.58322,104.06244_30.58049, ...
|
*/
|
public static List<LonLat> parseGcl(String gcl) {
|
if (gcl == null || gcl.isEmpty()) {
|
return java.util.Collections.emptyList();
|
}
|
String[] pairs = gcl.split(",");
|
java.util.List<LonLat> result = new java.util.ArrayList<>(pairs.length);
|
for (String pair : pairs) {
|
if (pair == null || pair.isEmpty()) {
|
continue;
|
}
|
String[] parts = pair.split("_");
|
if (parts.length != 2) {
|
continue;
|
}
|
double lon = Double.parseDouble(parts[0]);
|
double lat = Double.parseDouble(parts[1]);
|
result.add(new LonLat(lon, lat));
|
}
|
return result;
|
}
|
|
/**
|
* 基于单个 GCL 字符串构造 MapAreaData,并写入指定文件。
|
*
|
* @param gcl GCL 字符串:形如 "lon_lat,lon_lat,..."
|
* @param oddId 区域 ID,对应 OddArea.odd_id
|
* @param isBlacklist 是否黑名单区域,对应 OddArea.is_blacklist
|
* @param version MapAreaData 的版本号,对应 MapAreaData.version
|
* @param outFile 输出的 MapAreaData 二进制文件路径
|
* @return 写入是否成功
|
*/
|
public static boolean buildMapAreaDataFromGcl(String gcl,
|
int oddId,
|
boolean isBlacklist,
|
int version,
|
String outFile) {
|
List<LonLat> points = parseGcl(gcl);
|
if (points == null || points.isEmpty()) {
|
return false;
|
}
|
|
// 构造 OddAreaDto
|
OddAreaDto oddArea = OddAreaDto.builder()
|
.oddId(oddId)
|
.isBlacklist(isBlacklist)
|
.build();
|
for (LonLat p : points) {
|
oddArea.getOddPoints().add(OddPointDto.builder()
|
.latitude(p.getLat())
|
.longitude(p.getLon())
|
.build());
|
}
|
|
// 构造 MapAreaDataDto
|
MapAreaDataDto data = MapAreaDataDto.builder()
|
.version(version)
|
.oddAreas(java.util.Collections.singletonList(oddArea))
|
.build();
|
|
try (FileOutputStream fos = new FileOutputStream(outFile)) {
|
MapAreaDataProtoWriter.writeTo(data, fos);
|
} catch (IOException e) {
|
return false;
|
}
|
|
return true;
|
}
|
|
/**
|
* 从一组多边形坐标点生成 RTree fbs 文件(单区域版本)。
|
* 等价于 C++ 流程:单个 OddArea → PbRTreeCreater → PbRTreeToFlatBuffer。
|
* Node.type 由 R-Tree 算法自动赋值(1=LEAF),不再由调用方指定。
|
*
|
* @param points 多边形顶点(经度、纬度),顺序与原始区域一致
|
* @param id 对应 proto 里的 odd_id
|
* @param isBlacklist 是否黑名单区域
|
* @param outFile 输出的 fbs 二进制文件路径
|
* @return 生成是否成功
|
*/
|
public static boolean buildRTreeFromPoints(List<LonLat> points,
|
int id,
|
boolean isBlacklist,
|
String outFile) {
|
java.util.List<PbRTreeBuilder.RTreeNode> leafNodes = new java.util.ArrayList<>();
|
PbRTreeBuilder.RTreeNode leaf = buildLeafNode(points, id, isBlacklist);
|
if (leaf == null) {
|
return false;
|
}
|
leafNodes.add(leaf);
|
return writeRTreeFbs(leafNodes, outFile);
|
}
|
|
/**
|
* 从坐标点生成 RTree fbs 字节数组(内存版,供上传等场景使用)。
|
*
|
* @param points 多边形顶点
|
* @param areaId 区域 odd_id
|
* @param isBlacklist 是否黑名单
|
* @return fbs 二进制,失败返回 null
|
*/
|
public static byte[] buildRTreeBytesFromPoints(List<LonLat> points, int areaId, boolean isBlacklist) {
|
java.util.List<PbRTreeBuilder.RTreeNode> leafNodes = new java.util.ArrayList<>();
|
PbRTreeBuilder.RTreeNode leaf = buildLeafNode(points, areaId, isBlacklist);
|
if (leaf == null) {
|
return null;
|
}
|
leafNodes.add(leaf);
|
return buildRTreeBytes(leafNodes);
|
}
|
|
/**
|
* 从多个区域(AreaInput)生成 RTree fbs 字节数组。
|
*
|
* @param areas 区域列表,每个 AreaInput 含 points、areaId、isBlacklist
|
* @return fbs 二进制,失败返回 null
|
*/
|
public static byte[] buildRTreeBytesFromMultiAreas(List<AreaInput> areas) {
|
if (areas == null || areas.isEmpty()) {
|
return null;
|
}
|
java.util.List<PbRTreeBuilder.RTreeNode> leafNodes = new java.util.ArrayList<>();
|
for (AreaInput area : areas) {
|
PbRTreeBuilder.RTreeNode leaf = buildLeafNode(
|
area.getPoints(),
|
area.getAreaId(),
|
area.isBlacklist()
|
);
|
if (leaf != null) {
|
leafNodes.add(leaf);
|
}
|
}
|
if (leafNodes.isEmpty()) {
|
return null;
|
}
|
return buildRTreeBytes(leafNodes);
|
}
|
|
/**
|
* 从多个区域的坐标点列表生成 RTree fbs 文件(多区域版本,与 C++ 完全对齐)。
|
*
|
* @param areaPoints 每个区域对应的坐标点列表
|
* @param ids 每个区域的 odd_id
|
* @param isBlacklists 每个区域是否黑名单
|
* @param outFile 输出的 fbs 二进制文件路径
|
* @return 生成是否成功
|
*/
|
public static boolean buildRTreeFromMultiAreas(java.util.List<List<LonLat>> areaPoints,
|
java.util.List<Integer> ids,
|
java.util.List<Boolean> isBlacklists,
|
String outFile) {
|
if (areaPoints == null || areaPoints.isEmpty()) {
|
return false;
|
}
|
java.util.List<PbRTreeBuilder.RTreeNode> leafNodes = new java.util.ArrayList<>();
|
for (int i = 0; i < areaPoints.size(); i++) {
|
PbRTreeBuilder.RTreeNode leaf = buildLeafNode(
|
areaPoints.get(i),
|
ids.get(i),
|
isBlacklists.get(i)
|
);
|
if (leaf != null) {
|
leafNodes.add(leaf);
|
}
|
}
|
return writeRTreeFbs(leafNodes, outFile);
|
}
|
|
private static PbRTreeBuilder.RTreeNode buildLeafNode(List<LonLat> points, int id, boolean isBlacklist) {
|
if (points == null || points.isEmpty()) {
|
return null;
|
}
|
float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE;
|
float maxX = -Float.MAX_VALUE, maxY = -Float.MAX_VALUE;
|
|
java.util.List<float[]> ptList = new java.util.ArrayList<>(points.size());
|
for (LonLat p : points) {
|
float x = (float) p.getLon(); // lon → x
|
float y = (float) p.getLat(); // lat → y
|
ptList.add(new float[]{x, y});
|
minX = Math.min(minX, x);
|
minY = Math.min(minY, y);
|
maxX = Math.max(maxX, x);
|
maxY = Math.max(maxY, y);
|
}
|
|
PbRTreeBuilder.RTreeNode leaf = new PbRTreeBuilder.RTreeNode();
|
leaf.minX = minX;
|
leaf.minY = minY;
|
leaf.maxX = maxX;
|
leaf.maxY = maxY;
|
leaf.points = ptList;
|
leaf.id = id;
|
leaf.isBlacklist = isBlacklist;
|
return leaf;
|
}
|
|
private static byte[] buildRTreeBytes(java.util.List<PbRTreeBuilder.RTreeNode> leafNodes) {
|
java.util.List<PbRTreeBuilder.RTreeNode> rtreeNodes = PbRTreeBuilder.createRTree(leafNodes);
|
|
FlatBufferBuilder builder = new FlatBufferBuilder(1024);
|
int rtreeOffset = PbRTreeBuilder.writeFlatBuffer(builder, rtreeNodes);
|
builder.finish(rtreeOffset);
|
|
ByteBuffer buffer = builder.dataBuffer();
|
byte[] bytes = new byte[buffer.remaining()];
|
buffer.get(bytes);
|
return bytes;
|
}
|
|
private static boolean writeRTreeFbs(java.util.List<PbRTreeBuilder.RTreeNode> leafNodes, String outFile) {
|
byte[] bytes = buildRTreeBytes(leafNodes);
|
try (FileOutputStream fos = new FileOutputStream(outFile)) {
|
fos.write(bytes);
|
} catch (IOException e) {
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* 简单命令行测试入口。
|
* 示例:构造一个矩形区域的四个顶点,生成 RTree fbs 文件到指定目录。
|
*/
|
public static void main(String[] args) {
|
// 示例 GCL 字符串:lon_lat,lon_lat,...
|
String gcl;
|
if (args != null && args.length > 0) {
|
// 如果通过命令行传入,则优先使用参数中的 GCL 字符串
|
gcl = args[0];
|
} else {
|
// 默认示例,来源于用户提供的坐标
|
gcl = "104.06249657558965_30.583221674855963," +
|
"104.06244328689024_30.58049965528362," +
|
"104.06640444191817_30.580606702069797," +
|
"104.06496563619123_30.58420035984882," +
|
"104.06249657558965_30.583221674855963";
|
}
|
|
List<LonLat> pts = parseGcl(gcl);
|
|
int id = 1;
|
boolean isBlacklist = false;
|
|
String outFile;
|
if (args != null && args.length > 1) {
|
// 如果有第二个参数,则视为输出文件路径
|
outFile = args[1];
|
} else {
|
outFile = "xngp_area_data_test.bin";
|
}
|
|
boolean ok = buildRTreeFromPoints(pts, id, isBlacklist, outFile);
|
System.out.println("buildRTreeFromPoints result=" + ok + ", outFile=" + outFile);
|
}
|
|
|
|
}
|