feat(positioning): 添加封闭区域匹配功能

master
zhaokai 2026-08-18 16:17:36 +08:00
parent 961a87226e
commit c70864998e
3 changed files with 128 additions and 0 deletions

1
.gitignore vendored
View File

@ -42,3 +42,4 @@ build/
# Local smoke-test logs and extracted dependency metadata # Local smoke-test logs and extracted dependency metadata
/tmp/ /tmp/
/logs/ /logs/
/start/src/main/resources/templates/

View File

@ -0,0 +1,114 @@
package com.zcloud.personnel.positioning.command.query;
import com.alibaba.cola.exception.BizException;
import com.zcloud.personnel.positioning.persistence.dataobject.ClosedAreaDO;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Component
class ClosedAreaMatcher {
List<AreaPolygon> prepare(List<ClosedAreaDO> areas) {
if (areas == null || areas.isEmpty()) {
return Collections.emptyList();
}
List<AreaPolygon> result = new ArrayList<>(areas.size());
for (ClosedAreaDO area : areas) {
result.add(new AreaPolygon(area, parseLocation(area)));
}
return result;
}
AreaPolygon match(BigDecimal lon, BigDecimal lat, List<AreaPolygon> areas) {
if (lon == null || lat == null || areas == null) {
return null;
}
for (AreaPolygon area : areas) {
if (contains(lon, lat, area)) {
return area;
}
}
return null;
}
boolean contains(BigDecimal lon, BigDecimal lat, AreaPolygon area) {
if (lon == null || lat == null || area == null || area.points.size() < 3) {
return false;
}
boolean inside = false;
double x = lon.doubleValue();
double y = lat.doubleValue();
for (int i = 0, j = area.points.size() - 1; i < area.points.size(); j = i++) {
double xi = area.points.get(i)[0].doubleValue();
double yi = area.points.get(i)[1].doubleValue();
double xj = area.points.get(j)[0].doubleValue();
double yj = area.points.get(j)[1].doubleValue();
if ((yi > y) != (yj > y)
&& x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
inside = !inside;
}
}
return inside;
}
private List<BigDecimal[]> parseLocation(ClosedAreaDO area) {
List<BigDecimal[]> points = new ArrayList<>();
if (area == null || !StringUtils.hasText(area.getLocation())) {
throw locationFormatException(area);
}
for (String coordinate : area.getLocation().split(";")) {
if (!StringUtils.hasText(coordinate)) {
continue;
}
String[] values = coordinate.trim().split(",");
if (values.length != 2) {
throw locationFormatException(area);
}
try {
points.add(new BigDecimal[]{
new BigDecimal(values[0].trim()),
new BigDecimal(values[1].trim())
});
} catch (NumberFormatException e) {
throw locationFormatException(area);
}
}
if (points.size() < 3) {
throw locationFormatException(area);
}
return points;
}
private BizException locationFormatException(ClosedAreaDO area) {
return new BizException("封闭区域经纬度格式错误区域id=" + (area == null ? null : area.getId()));
}
static class AreaPolygon {
private final ClosedAreaDO area;
private final List<BigDecimal[]> points;
private AreaPolygon(ClosedAreaDO area, List<BigDecimal[]> points) {
this.area = area;
this.points = points;
}
ClosedAreaDO getArea() {
return area;
}
BigDecimal center(int coordinateIndex) {
BigDecimal total = BigDecimal.ZERO;
for (BigDecimal[] point : points) {
total = total.add(point[coordinateIndex]);
}
return total.divide(BigDecimal.valueOf(points.size()), 12, RoundingMode.HALF_UP)
.stripTrailingZeros();
}
}
}

View File

@ -0,0 +1,13 @@
package com.zcloud.personnel.positioning.persistence.dataobject;
import lombok.Data;
@Data
public class ClosedAreaDO {
private Long id;
private String closedAreaName;
private String closedAreaType;
private Long jurisdictionalCorpId;
private String jurisdictionalCorpName;
private String location;
}