liujie
2026-03-09 bbdd5f4f286fc593c5c30bd1bccd1f39af135dfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
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);
    }
 
 
    
}