From c70864998e9d58b41222d7f355ac6575b160f749 Mon Sep 17 00:00:00 2001 From: zhaokai Date: Tue, 18 Aug 2026 16:17:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(positioning):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B0=81=E9=97=AD=E5=8C=BA=E5=9F=9F=E5=8C=B9=E9=85=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../command/query/ClosedAreaMatcher.java | 114 ++++++++++++++++++ .../persistence/dataobject/ClosedAreaDO.java | 13 ++ 3 files changed, 128 insertions(+) create mode 100644 web-app/src/main/java/com/zcloud/personnel/positioning/command/query/ClosedAreaMatcher.java create mode 100644 web-infrastructure/src/main/java/com/zcloud/personnel/positioning/persistence/dataobject/ClosedAreaDO.java diff --git a/.gitignore b/.gitignore index d5a5837..399d75c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ build/ # Local smoke-test logs and extracted dependency metadata /tmp/ /logs/ +/start/src/main/resources/templates/ diff --git a/web-app/src/main/java/com/zcloud/personnel/positioning/command/query/ClosedAreaMatcher.java b/web-app/src/main/java/com/zcloud/personnel/positioning/command/query/ClosedAreaMatcher.java new file mode 100644 index 0000000..a1b470a --- /dev/null +++ b/web-app/src/main/java/com/zcloud/personnel/positioning/command/query/ClosedAreaMatcher.java @@ -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 prepare(List areas) { + if (areas == null || areas.isEmpty()) { + return Collections.emptyList(); + } + List 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 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 parseLocation(ClosedAreaDO area) { + List 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 points; + + private AreaPolygon(ClosedAreaDO area, List 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(); + } + } +} diff --git a/web-infrastructure/src/main/java/com/zcloud/personnel/positioning/persistence/dataobject/ClosedAreaDO.java b/web-infrastructure/src/main/java/com/zcloud/personnel/positioning/persistence/dataobject/ClosedAreaDO.java new file mode 100644 index 0000000..ca5a552 --- /dev/null +++ b/web-infrastructure/src/main/java/com/zcloud/personnel/positioning/persistence/dataobject/ClosedAreaDO.java @@ -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; +}