Skip to content

Commit a92ba67

Browse files
committed
fix: Linestring corrupt PBF issue
1 parent 89c3e25 commit a92ba67

File tree

2 files changed

+173
-9
lines changed

2 files changed

+173
-9
lines changed

src/spatial/modules/mvt/mvt_module.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -609,15 +609,15 @@ class MVTFeatureBuilder {
609609
const auto y = CastDouble(cursor.Read<double>());
610610
cursor.Skip(vertex_space); // Skip z and m if present
611611

612-
if (vertex_idx == 0) {
613-
geometry.push_back((1 & 0x7) | (1 << 3)); // MoveTo, 1 part
614-
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
615-
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
616-
geometry.push_back((2 & 0x7) | ((vertex_count - 2) << 3)); // LineTo, part count
617-
} else {
618-
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
619-
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
620-
}
612+
if (vertex_idx == 0) {
613+
geometry.push_back((1 & 0x7) | (1 << 3)); // MoveTo, 1 part
614+
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
615+
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
616+
geometry.push_back((2 & 0x7) | ((vertex_count - 1) << 3)); // LineTo, part count
617+
} else {
618+
geometry.push_back(protozero::encode_zigzag32(x - cursor_x));
619+
geometry.push_back(protozero::encode_zigzag32(y - cursor_y));
620+
}
621621

622622
cursor_x = x;
623623
cursor_y = y;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# name: test/sql/mvt/st_asmvt_linestring.test
2+
# group: [mvt]
3+
4+
require spatial
5+
6+
# Test LINESTRING encoding
7+
statement ok
8+
COPY (
9+
SELECT st_asmvt(
10+
{"geom": geom},
11+
'lines'
12+
) as mvt
13+
FROM (
14+
SELECT
15+
st_geomfromtext('LINESTRING(0 0, 100 100, 200 0)') as geom
16+
)
17+
) TO '__TEST_DIR__/test_linestring.mvt' (FORMAT BLOB);
18+
19+
query I
20+
select count(*) from st_read('__TEST_DIR__/test_linestring.mvt');
21+
----
22+
1
23+
24+
# Test MULTI_LINESTRING encoding
25+
statement ok
26+
COPY (
27+
SELECT st_asmvt(
28+
{"geom": geom},
29+
'multilines'
30+
) as mvt
31+
FROM (
32+
SELECT
33+
st_geomfromtext('MULTILINESTRING((0 0, 100 100, 200 0), (300 0, 400 100, 500 0))') as geom
34+
)
35+
) TO '__TEST_DIR__/test_multilinestring.mvt' (FORMAT BLOB);
36+
37+
query I
38+
select count(*) from st_read('__TEST_DIR__/test_multilinestring.mvt');
39+
----
40+
1
41+
42+
# Test LINESTRING with ST_AsMVTGeom (clipping can produce MULTI_LINESTRING)
43+
statement ok
44+
COPY (
45+
SELECT st_asmvt(
46+
{"geom": ST_AsMVTGeom(
47+
geom,
48+
ST_MakeEnvelope(0, 0, 1000, 1000),
49+
4096,
50+
256,
51+
true
52+
)},
53+
'clipped_lines'
54+
) as mvt
55+
FROM (
56+
SELECT
57+
st_geomfromtext('LINESTRING(100 100, 500 500, 900 100)') as geom
58+
)
59+
) TO '__TEST_DIR__/test_clipped_linestring.mvt' (FORMAT BLOB);
60+
61+
query I
62+
select count(*) from st_read('__TEST_DIR__/test_clipped_linestring.mvt');
63+
----
64+
1
65+
66+
# Test LINESTRING crossing tile boundary (produces MULTI_LINESTRING after clipping)
67+
statement ok
68+
COPY (
69+
SELECT st_asmvt(
70+
{"geom": ST_AsMVTGeom(
71+
geom,
72+
ST_MakeEnvelope(0, 0, 1000, 1000),
73+
4096,
74+
256,
75+
true
76+
)},
77+
'crossing_lines'
78+
) as mvt
79+
FROM (
80+
SELECT
81+
st_geomfromtext('LINESTRING(-500 500, 500 500, 1500 500)') as geom
82+
)
83+
) TO '__TEST_DIR__/test_crossing_linestring.mvt' (FORMAT BLOB);
84+
85+
query I
86+
select count(*) from st_read('__TEST_DIR__/test_crossing_linestring.mvt');
87+
----
88+
1
89+
90+
# Test multiple LINESTRINGs with various lengths
91+
statement ok
92+
COPY (
93+
SELECT st_asmvt(
94+
{"geom": geom, "id": id},
95+
'various_lines',
96+
4096,
97+
'geom',
98+
'id'
99+
) as mvt
100+
FROM (
101+
SELECT
102+
row_number() over () as id,
103+
st_geomfromtext('LINESTRING(' || (x*100) || ' ' || (y*100) || ', ' || (x*100+50) || ' ' || (y*100+50) || ', ' || (x*100+100) || ' ' || (y*100) || ')') as geom
104+
FROM range(0, 10) as r(x),
105+
range(0, 10) as rr(y)
106+
)
107+
) TO '__TEST_DIR__/test_various_linestrings.mvt' (FORMAT BLOB);
108+
109+
query I
110+
select count(*) from st_read('__TEST_DIR__/test_various_linestrings.mvt');
111+
----
112+
100
113+
114+
# Test global scale dataset scenario (like Natural Earth roads)
115+
# This simulates the case where geometries at low zoom levels span large areas
116+
statement ok
117+
COPY (
118+
SELECT st_asmvt(
119+
{"geom": ST_AsMVTGeom(
120+
geom,
121+
ST_TileEnvelope(2, 1, 1),
122+
4096,
123+
256,
124+
false
125+
)},
126+
'global_lines'
127+
) as mvt
128+
FROM (
129+
SELECT
130+
st_geomfromtext('LINESTRING(-10000000 5000000, 0 0, 10000000 -5000000)') as geom
131+
)
132+
) TO '__TEST_DIR__/test_global_linestring.mvt' (FORMAT BLOB);
133+
134+
query I
135+
select count(*) from st_read('__TEST_DIR__/test_global_linestring.mvt');
136+
----
137+
1
138+
139+
# Test that clipped MULTI_LINESTRING can be read back
140+
statement ok
141+
COPY (
142+
SELECT st_asmvt(
143+
{"geom": ST_AsMVTGeom(
144+
geom,
145+
ST_TileEnvelope(5, 10, 12),
146+
4096,
147+
256,
148+
true
149+
), "name": name},
150+
'roads'
151+
) as mvt
152+
FROM (
153+
VALUES
154+
(st_geomfromtext('MULTILINESTRING((100 100, 500 500), (600 600, 900 900))'), 'road1'),
155+
(st_geomfromtext('LINESTRING(200 200, 800 800)'), 'road2')
156+
) t(geom, name)
157+
WHERE ST_Intersects(geom, ST_TileEnvelope(5, 10, 12))
158+
) TO '__TEST_DIR__/test_roads.mvt' (FORMAT BLOB);
159+
160+
query II
161+
select count(*), count(name) from st_read('__TEST_DIR__/test_roads.mvt');
162+
----
163+
2 2
164+

0 commit comments

Comments
 (0)