use rustc_hash::{FxHashMap, FxHashSet};
use super::{
GeomType, LayerBuilder, LineMergeScratch, MergeScratch, command, decode_zigzag, zigzag,
};
fn append_geometry(dest: &mut Vec<u32>, src: &[u32], cx: &mut i32, cy: &mut i32) {
let mut src_cx: i32 = 0;
let mut src_cy: i32 = 0;
let mut i = 0;
while i < src.len() {
let cmd = src[i];
let cmd_id = cmd & 0x7;
let cmd_count = cmd >> 3;
i += 1;
match cmd_id {
1 | 2 => {
let cmd_pos = dest.len();
dest.push(cmd);
let mut actual_count = 0u32;
for _ in 0..cmd_count {
if i + 1 >= src.len() {
break;
}
actual_count += 1;
let dx = decode_zigzag(src[i]);
let dy = decode_zigzag(src[i + 1]);
src_cx += dx;
src_cy += dy;
dest.push(zigzag(src_cx - *cx));
dest.push(zigzag(src_cy - *cy));
*cx = src_cx;
*cy = src_cy;
i += 2;
}
if actual_count != cmd_count {
dest[cmd_pos] = (actual_count << 3) | cmd_id;
}
}
7 => {
dest.push(cmd);
}
_ => {
dest.push(cmd);
}
}
}
}
impl LayerBuilder {
#[hotpath::measure]
pub fn merge_same_attr_geometries(
&mut self,
scratch: &mut MergeScratch,
geom_pool: &mut Vec<Vec<u32>>,
tags_pool: &mut Vec<Vec<(u16, u16)>>,
) {
scratch.indices.clear();
for (i, f) in self.features.iter().enumerate() {
if f.geom_type != GeomType::Point {
scratch.indices.push(i);
}
}
if scratch.indices.len() < 2 {
return;
}
scratch.indices.sort_by(|&a, &b| {
let fa = &self.features[a];
let fb = &self.features[b];
fa.geom_type
.cmp(&fb.geom_type)
.then_with(|| fa.tags.cmp(&fb.tags))
});
let mut any_merged = false;
let mut i = 0;
while i < scratch.indices.len() {
let mut j = i + 1;
let fi = scratch.indices[i];
while j < scratch.indices.len() {
let fj = scratch.indices[j];
if self.features[fj].geom_type != self.features[fi].geom_type
|| self.features[fj].tags != self.features[fi].tags
{
break;
}
j += 1;
}
if j - i >= 2 {
any_merged = true;
scratch.geom.clear();
let mut cx: i32 = 0;
let mut cy: i32 = 0;
for k in i..j {
let idx = scratch.indices[k];
append_geometry(
&mut scratch.geom,
&self.features[idx].geometry,
&mut cx,
&mut cy,
);
}
let first = scratch.indices[i];
let mut dest = geom_pool.pop().unwrap_or_default();
dest.clear();
dest.extend_from_slice(&scratch.geom);
let old = std::mem::replace(&mut self.features[first].geometry, dest);
geom_pool.push(old);
self.features[first].id = None;
for k in (i + 1)..j {
let idx = scratch.indices[k];
geom_pool.push(std::mem::take(&mut self.features[idx].geometry));
tags_pool.push(std::mem::take(&mut self.features[idx].tags));
}
}
i = j;
}
if any_merged {
self.features.retain(|f| !f.geometry.is_empty());
}
}
pub fn merge_connected_lines(&mut self, scratch: &mut LineMergeScratch) {
for feature in &mut self.features {
if feature.geom_type != GeomType::LineString {
continue;
}
decode_line_segments(&feature.geometry, &mut scratch.segments);
if scratch.segments.len() < 2 {
continue;
}
merge_line_segments(
&mut scratch.segments,
&mut scratch.merged,
&mut scratch.visited,
&mut scratch.starts,
&mut scratch.chain,
);
encode_line_segments(&scratch.merged, &mut scratch.encode_buf);
std::mem::swap(&mut feature.geometry, &mut scratch.encode_buf);
}
}
}
const MAX_LINE_VERTICES: usize = 6000;
fn decode_line_segments(commands: &[u32], segments: &mut Vec<Vec<(i32, i32)>>) {
segments.clear();
let mut i = 0;
let mut cx: i32 = 0;
let mut cy: i32 = 0;
while i < commands.len() {
let cmd = commands[i];
let cmd_id = cmd & 0x7;
let count = (cmd >> 3) as usize;
i += 1;
match cmd_id {
1 => {
if i + count * 2 > commands.len() {
break;
}
for _ in 0..count {
cx = cx.wrapping_add(unzigzag(commands[i]));
cy = cy.wrapping_add(unzigzag(commands[i + 1]));
i += 2;
}
segments.push(vec![(cx, cy)]);
}
2 => {
if i + count * 2 > commands.len() {
break;
}
if let Some(seg) = segments.last_mut() {
for _ in 0..count {
cx = cx.wrapping_add(unzigzag(commands[i]));
cy = cy.wrapping_add(unzigzag(commands[i + 1]));
i += 2;
seg.push((cx, cy));
}
} else {
i += count * 2;
}
}
_ => {
i += count * 2;
}
}
}
segments.retain(|s| s.len() >= 2);
}
fn encode_line_segments(segments: &[Vec<(i32, i32)>], buf: &mut Vec<u32>) {
buf.clear();
let mut cx: i32 = 0;
let mut cy: i32 = 0;
for seg in segments {
if seg.len() < 2 {
continue;
}
buf.push(command(1, 1));
buf.push(zigzag(seg[0].0 - cx));
buf.push(zigzag(seg[0].1 - cy));
cx = seg[0].0;
cy = seg[0].1;
let lineto_pos = buf.len();
buf.push(0); let mut count = 0u32;
for &(x, y) in &seg[1..] {
if x == cx && y == cy {
continue;
}
buf.push(zigzag(x - cx));
buf.push(zigzag(y - cy));
cx = x;
cy = y;
count += 1;
}
if count < 1 {
buf.truncate(lineto_pos - 3);
continue;
}
#[allow(clippy::cast_possible_truncation)]
{
buf[lineto_pos] = command(2, count);
}
}
}
#[derive(Clone, Copy)]
struct SegEnd {
seg_idx: usize,
is_back: bool,
}
fn merge_line_segments(
segments: &mut Vec<Vec<(i32, i32)>>,
merged: &mut Vec<Vec<(i32, i32)>>,
visited: &mut Vec<bool>,
starts: &mut Vec<(i32, i32, usize, bool)>,
chain: &mut Vec<(i32, i32)>,
) {
merged.clear();
if segments.len() < 2 {
std::mem::swap(segments, merged);
return;
}
dedup_parallel_segments(segments);
if segments.len() < 2 {
std::mem::swap(segments, merged);
return;
}
let mut endpoints: FxHashMap<(i32, i32), Vec<SegEnd>> = FxHashMap::default();
for (i, seg) in segments.iter().enumerate() {
let front = seg[0];
let back = seg[seg.len() - 1];
endpoints.entry(front).or_default().push(SegEnd {
seg_idx: i,
is_back: false,
});
endpoints.entry(back).or_default().push(SegEnd {
seg_idx: i,
is_back: true,
});
}
visited.clear();
visited.resize(segments.len(), false);
starts.clear();
for (&point, ends) in &endpoints {
if ends.len() != 2 {
for &se in ends {
starts.push((point.0, point.1, se.seg_idx, se.is_back));
}
}
}
starts.sort_by(|a, b| {
a.0.cmp(&b.0)
.then(a.1.cmp(&b.1))
.then(a.2.cmp(&b.2))
.then(a.3.cmp(&b.3))
});
for &(_, _, seg_idx, is_back) in starts.iter() {
if visited[seg_idx] {
continue;
}
build_chain(
segments, &endpoints, visited, chain, seg_idx, is_back, merged,
);
}
for i in 0..segments.len() {
if visited[i] {
continue;
}
build_chain(segments, &endpoints, visited, chain, i, false, merged);
}
}
fn dedup_parallel_segments(segments: &mut Vec<Vec<(i32, i32)>>) {
let mut seen: FxHashSet<Vec<(i32, i32)>> = FxHashSet::default();
segments.retain(|segment| {
let mut reversed = segment.clone();
reversed.reverse();
let key = if segment <= &reversed {
segment.clone()
} else {
reversed
};
seen.insert(key)
});
}
fn build_chain(
segments: &[Vec<(i32, i32)>],
endpoints: &FxHashMap<(i32, i32), Vec<SegEnd>>,
visited: &mut [bool],
chain: &mut Vec<(i32, i32)>,
start_seg: usize,
entering_back: bool,
out: &mut Vec<Vec<(i32, i32)>>,
) {
chain.clear();
let mut current_seg = start_seg;
let mut entering_back = entering_back;
loop {
if visited[current_seg] {
break;
}
let seg = &segments[current_seg];
if !chain.is_empty() && chain.len() + seg.len() > MAX_LINE_VERTICES {
break;
}
visited[current_seg] = true;
if entering_back {
if chain.is_empty() {
chain.extend(seg.iter().rev());
} else {
chain.extend(seg.iter().rev().skip(1));
}
} else if chain.is_empty() {
chain.extend_from_slice(seg);
} else {
chain.extend_from_slice(&seg[1..]);
}
let exit_point = if entering_back {
seg[0]
} else {
seg[seg.len() - 1]
};
let Some(ends) = endpoints.get(&exit_point) else {
break;
};
if ends.len() != 2 {
break;
}
let our_exit_is_back = !entering_back;
let other = ends
.iter()
.find(|e| !(e.seg_idx == current_seg && e.is_back == our_exit_is_back));
let Some(&next) = other else {
break;
};
if next.seg_idx == current_seg {
break;
}
if segments[next.seg_idx] == *seg || segments[next.seg_idx].iter().rev().eq(seg.iter()) {
break;
}
current_seg = next.seg_idx;
entering_back = next.is_back;
}
if chain.len() >= 2 {
out.push(std::mem::take(chain));
}
}
#[allow(clippy::cast_possible_wrap)]
fn unzigzag(n: u32) -> i32 {
((n >> 1) as i32) ^ (-((n & 1) as i32))
}
#[cfg(test)]
pub(super) fn test_append_geometry(dest: &mut Vec<u32>, src: &[u32], cx: &mut i32, cy: &mut i32) {
append_geometry(dest, src, cx, cy);
}
#[cfg(test)]
pub(super) fn test_decode_line_segments(commands: &[u32], segments: &mut Vec<Vec<(i32, i32)>>) {
decode_line_segments(commands, segments);
}