package com.xiaopeng.map.service.serviceimpl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.collect.ImmutableMap; import com.xiaopeng.api.dto.operation.ElectronicFenceAddDto; import com.xiaopeng.api.dto.operation.ElectronicFenceUpdateDto; import com.xiaopeng.api.model.map.XpGeoFencingPolygon; import com.xiaopeng.api.model.map.XpGeoFencingProhibitedPolygon; import com.xiaopeng.api.model.operation.*; import com.xiaopeng.api.query.operation.ElectronicPageListQuery; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.DateUtils; import com.xiaopeng.common.core.utils.StringUtils; import com.xiaopeng.common.core.utils.bean.BeanUtils; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.map.constant.I18nKeyConstant; import com.xiaopeng.map.job.base.QuartzManager; import com.xiaopeng.map.job.base.TimeJobType; import com.xiaopeng.map.job.jobs.ElectronicEffectJob; import com.xiaopeng.map.mapper.XpElectronicFenceAreaMapper; import com.xiaopeng.map.mapper.XpElectronicFenceMapper; import com.xiaopeng.map.mapper.XpPointMapper; import com.xiaopeng.map.mapper.XpRegionMapper; import com.xiaopeng.map.service.XpElectronicFenceAreaService; import com.xiaopeng.map.service.XpElectronicFenceService; import com.xiaopeng.map.utils.GDMapGeocodingUtil; import com.xiaopeng.map.utils.PolygonOverlapService; import lombok.extern.slf4j.Slf4j; import net.sourceforge.pinyin4j.PinyinHelper; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.awt.geom.Point2D; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** *

* 电子围栏 服务实现类 *

* * @author liu * @since 2026-02-03 */ @Service @Slf4j public class XpElectronicFenceServiceImpl extends ServiceImpl implements XpElectronicFenceService { @Resource private PolygonOverlapService polygonOverlapService; @Resource private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper; @Autowired private MongoTemplate mongoTemplate; @Autowired private XpPointMapper xpPointMapper; @Autowired private XpRegionMapper xpRegionMapper; @Autowired private GDMapGeocodingUtil gdMapGeocodingUtil; @Resource private XpElectronicFenceAreaService xpElectronicFenceAreaService; @Override public PageInfo pageList(ElectronicPageListQuery query) { PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize()); if (StringUtils.isEmpty(query.getCreateSort())&& StringUtils.isEmpty(query.getUpdateSort())){ query.setCreateSort("2"); } List list = this.baseMapper.pageList(pageInfo, query); if (list.isEmpty()) { return new PageInfo<>(); } List electronicIds = list.stream().map(XpElectronicFence::getId).collect(Collectors.toList()); Map> electronicFenceAreaMap = xpElectronicFenceAreaMapper.selectList(new LambdaQueryWrapper() .in(XpElectronicFenceArea::getElectronicId, electronicIds)).stream() .collect(Collectors.groupingBy(XpElectronicFenceArea::getElectronicId)); for (XpElectronicFence xpElectronicFence : list) { List xpElectronicFenceAreas = electronicFenceAreaMap.get(xpElectronicFence.getId()); if (xpElectronicFenceAreas != null) { xpElectronicFence.setOperatingAreaCount(xpElectronicFenceAreas.stream().filter(xpElectronicFenceArea -> xpElectronicFenceArea.getAreaType() == 1).count()); xpElectronicFence.setProhibitedAreaCount(xpElectronicFenceAreas.stream().filter(xpElectronicFenceArea -> xpElectronicFenceArea.getAreaType() == 2).count()); }else{ xpElectronicFence.setOperatingAreaCount(0L); xpElectronicFence.setProhibitedAreaCount(0L); } } pageInfo.setRecords(list); return pageInfo; } public static String getPinyinInitials(String chinese) { StringBuilder pinyinInitials = new StringBuilder(); for (char c : chinese.toCharArray()) { // 获取拼音数组(多音字会返回多个拼音) String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c); if (pinyinArray != null && pinyinArray.length > 0) { // 取第一个拼音的首字母 pinyinInitials.append(pinyinArray[0].charAt(0)); } } return pinyinInitials.toString(); } @Override public R addElectronic(ElectronicFenceAddDto dto) { // 获取现有的运营区域(areaType=1) LambdaQueryWrapper xpElectronicFenceAreaLambdaQueryWrapper = new LambdaQueryWrapper<>(); xpElectronicFenceAreaLambdaQueryWrapper.eq(XpElectronicFenceArea::getAreaType, 1); List existingOperationAreas = xpElectronicFenceAreaMapper.selectList(xpElectronicFenceAreaLambdaQueryWrapper); // 获取待添加的新区域 List newElectronicFenceAreas = dto.getElectronicFenceAreas(); // 检查新区域与现有运营区域是否有重叠 for (XpElectronicFenceArea newArea : newElectronicFenceAreas) { //处理围栏闭合 String[] split = newArea.getFencePoints().split(","); if(!split[0].equals(split[split.length - 1])){ newArea.setFencePoints(newArea.getFencePoints() + "," + split[0]); } if (newArea.getAreaType() == 1) { // 只检查运营区域 for (XpElectronicFenceArea existingArea : existingOperationAreas) { if (polygonOverlapService.checkPolygonOverlap( newArea.getFencePoints(), existingArea.getFencePoints())) { throw new ServiceException( "运营区域{}与现有运营区域{}发生重叠", I18nKeyConstant.EDIT_ELECTRONIC_ERROR, new Object[]{existingArea.getAreaName(), existingArea.getAreaName()} ); } } } } // 保存主表数据 XpElectronicFence xpElectronicFence = new XpElectronicFence(); BeanUtils.copyProperties(dto, xpElectronicFence); xpElectronicFence.setStatus(0); if (dto.getStatus() == 0) { xpElectronicFence.setStatus(0); } else if (dto.getStatus() == 1) { if (dto.getEffectType() == 1) { // 设置为启用中 xpElectronicFence.setStatus(2); xpElectronicFence.setEffectTime(LocalDateTime.now()); } else { if (dto.getEffectTime().isBefore(LocalDateTime.now())) { // 设置为启用中 xpElectronicFence.setStatus(2); } else { xpElectronicFence.setStatus(1); } } } //根据经纬度获取当前城市code XpElectronicFenceArea xpElectronicFenceArea = dto.getElectronicFenceAreas().get(0); String[] split = xpElectronicFenceArea.getFencePoints().split(","); Coordinate[] coordinates = new Coordinate[split.length + 1]; for (int i = 0; i < split.length; i++) { String[] split1 = split[i].split("_"); coordinates[i] = new Coordinate(Double.parseDouble(split1[0]), Double.parseDouble(split1[1])); } org.locationtech.jts.geom.Point point = calculateCentroid(coordinates); Map geocode = gdMapGeocodingUtil.geocode(point.getX() + "", point.getY() + ""); if(null == geocode){ return R.fail("经纬度转换城市code失败"); } String cityCode = geocode.get("cityCode"); dto.setCityCode(cityCode); xpElectronicFence.setCityCode(dto.getCityCode()); // 生成围栏id 规则:城市首字母-类型简写-4位自增流水号(如 SZ-OP-001),支持复制;数据停用或删除后,流水号不回收 XpRegion xpRegion = xpRegionMapper.selectOne(new LambdaQueryWrapper() .eq(XpRegion::getCode, dto.getCityCode())); String name = xpRegion.getName(); this.save(xpElectronicFence); // 根据城市中文名,截取前两位的首字母并且大写 // 截取前两个字符 String prefix = name.substring(0, 2); // 获取拼音首字母并大写 String result = getPinyinInitials(prefix).toUpperCase(); xpElectronicFence.setElectronicNumber(result + "-OP-" + String.format("%04d", xpElectronicFence.getId())); if (dto.getStatus() == 0) { xpElectronicFence.setStatus(0); } else if (dto.getStatus() == 1) { if (dto.getEffectType() == 1) { // 设置为启用中 xpElectronicFence.setStatus(2); xpElectronicFence.setEffectTime(LocalDateTime.now()); } else { if (dto.getEffectTime().isBefore(LocalDateTime.now())) { // 设置为启用中 xpElectronicFence.setStatus(2); } else { // 添加定时任务job type=1电子围栏生效 type=2电子围栏失效 Map maps = new ImmutableMap.Builder(). put("type", 1L) .put("id", xpElectronicFence.getId()) .build(); QuartzManager.addJob( ElectronicEffectJob.class, (ElectronicEffectJob.effect + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_EFFECT, DateUtils.localDateTimeToDate(dto.getEffectTime()), maps ); xpElectronicFence.setStatus(1); } } if (dto.getFailureType() != null && dto.getFailureType() == 2) { if (dto.getFailureTime().isBefore(LocalDateTime.now())) { // 设置待启用 xpElectronicFence.setStatus(1); } else { // 添加定时任务job type=1电子围栏生效 type=2电子围栏失效 Map maps = new ImmutableMap.Builder(). put("type", 2L) .put("id", xpElectronicFence.getId()) .build(); QuartzManager.addJob( ElectronicEffectJob.class, (ElectronicEffectJob.failure + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_FAILURE, DateUtils.localDateTimeToDate(dto.getEffectTime()), maps ); xpElectronicFence.setStatus(1); } } } this.updateById(xpElectronicFence); if (xpElectronicFence.getStatus() == 2) { // 设置电子围栏ID并保存区域数据 for (XpElectronicFenceArea area : newElectronicFenceAreas) { area.setElectronicId(xpElectronicFence.getId()); area.setAreaStatus(1); area.setArea(area.getArea()); area.setCityCode(dto.getCityCode()); xpElectronicFenceAreaMapper.insert(area); } }else{ // 设置电子围栏ID并保存区域数据 for (XpElectronicFenceArea area : newElectronicFenceAreas) { area.setElectronicId(xpElectronicFence.getId()); area.setAreaStatus(2); area.setArea(area.getArea()); area.setCityCode(dto.getCityCode()); xpElectronicFenceAreaMapper.insert(area); } } if (xpElectronicFence.getStatus() == 1 || xpElectronicFence.getStatus() == 2) { for (XpElectronicFenceArea newElectronicFenceArea : newElectronicFenceAreas) { if (newElectronicFenceArea.getAreaType() == 1 && newElectronicFenceArea.getAreaStatus() == 1) { XpGeoFencingPolygon xpGeoFencingPolygon = new XpGeoFencingPolygon(); xpGeoFencingPolygon.setFenceId(String.valueOf(xpElectronicFence.getId())); xpGeoFencingPolygon.setName(xpElectronicFence.getElectronicName()); xpGeoFencingPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId())); xpGeoFencingPolygon.setCityCode(xpElectronicFence.getCityCode()); xpGeoFencingPolygon.setOperationStrategy(xpElectronicFence.getOperationStrategy()); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); xpGeoFencingPolygon.setPoints(geoJsonPolygon); mongoTemplate.insert(xpGeoFencingPolygon); } if (newElectronicFenceArea.getAreaType() == 2 && newElectronicFenceArea.getAreaStatus() == 1) { XpGeoFencingProhibitedPolygon xpGeoFencingProhibitedPolygon = new XpGeoFencingProhibitedPolygon(); xpGeoFencingProhibitedPolygon.setFenceId(String.valueOf(xpElectronicFence.getId())); xpGeoFencingProhibitedPolygon.setName(xpElectronicFence.getElectronicName()); xpGeoFencingProhibitedPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId())); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); xpGeoFencingProhibitedPolygon.setPoints(geoJsonPolygon); mongoTemplate.save(xpGeoFencingProhibitedPolygon); } } } return R.ok(); } @Override public R editElectronic(ElectronicFenceUpdateDto dto) { // 获取现有的运营区域(areaType=1),排除当前正在编辑的电子围栏 LambdaQueryWrapper xpElectronicFenceAreaLambdaQueryWrapper = new LambdaQueryWrapper<>(); xpElectronicFenceAreaLambdaQueryWrapper.eq(XpElectronicFenceArea::getAreaType, 1); xpElectronicFenceAreaLambdaQueryWrapper.eq(XpElectronicFenceArea::getAreaStatus, 1); xpElectronicFenceAreaLambdaQueryWrapper.ne(XpElectronicFenceArea::getElectronicId, dto.getId()); List existingOperationAreas = xpElectronicFenceAreaMapper.selectList(xpElectronicFenceAreaLambdaQueryWrapper); // 获取待更新的区域 List updatedElectronicFenceAreas = dto.getElectronicFenceAreas(); // 检查更新后的区域与现有运营区域是否有重叠 for (XpElectronicFenceArea updatedArea : updatedElectronicFenceAreas) { //处理围栏闭合 String[] split = updatedArea.getFencePoints().split(","); if(!split[0].equals(split[split.length - 1])){ updatedArea.setFencePoints(updatedArea.getFencePoints() + "," + split[0]); } if (updatedArea.getAreaType() == 1) { // 只检查运营区域 for (XpElectronicFenceArea existingArea : existingOperationAreas) { if (polygonOverlapService.checkPolygonOverlap( updatedArea.getFencePoints(), existingArea.getFencePoints())) { List res = new ArrayList<>(); res.add(updatedArea.getAreaName()); res.add(existingArea.getAreaName()); throw new ServiceException( "运营区域{}与现有运营区域{}发生重叠", I18nKeyConstant.AREA_OVERLAP_ERROR, res.toArray() ); } } } } // 更新主表数据 XpElectronicFence xpElectronicFence = new XpElectronicFence(); BeanUtils.copyProperties(dto, xpElectronicFence); if (dto.getStatus() == 0) { xpElectronicFence.setStatus(0); } else if (dto.getStatus() == 1) { if (dto.getEffectType() == 1) { // 设置为启用中 xpElectronicFence.setStatus(2); xpElectronicFence.setEffectTime(LocalDateTime.now()); } else { if (dto.getEffectTime().isBefore(LocalDateTime.now())) { // 设置为启用中 xpElectronicFence.setStatus(2); } else { // 先删除以前的定时任务 QuartzManager.removeJob((ElectronicEffectJob.effect + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_EFFECT); // 添加定时任务job type=1电子围栏生效 type=2电子围栏失效 Map maps = new ImmutableMap.Builder(). put("type", 1L) .put("id", xpElectronicFence.getId()) .build(); QuartzManager.addJob( ElectronicEffectJob.class, (ElectronicEffectJob.effect + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_EFFECT, DateUtils.localDateTimeToDate(dto.getEffectTime()), maps ); xpElectronicFence.setStatus(1); } } if (dto.getFailureType() != null && dto.getFailureType() == 2) { if (dto.getFailureTime().isBefore(LocalDateTime.now())) { // 设置待启用 xpElectronicFence.setStatus(1); } else { // 先删除以前的定时任务 QuartzManager.removeJob((ElectronicEffectJob.failure + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_FAILURE); // 添加定时任务job type=1电子围栏生效 type=2电子围栏失效 Map maps = new ImmutableMap.Builder(). put("type", 2L) .put("id", xpElectronicFence.getId()) .build(); QuartzManager.addJob( ElectronicEffectJob.class, (ElectronicEffectJob.failure + xpElectronicFence.getId()).toUpperCase(), TimeJobType.ELECTRONIC_FAILURE, DateUtils.localDateTimeToDate(dto.getEffectTime()), maps ); xpElectronicFence.setStatus(1); } } } //根据经纬度获取当前城市code XpElectronicFenceArea electronicFenceArea = dto.getElectronicFenceAreas().get(0); String[] split = electronicFenceArea.getFencePoints().split(","); Coordinate[] coordinates = new Coordinate[split.length]; for (int i = 0; i < split.length; i++) { String[] split1 = split[i].split("_"); coordinates[i] = new Coordinate(Double.parseDouble(split1[0]), Double.parseDouble(split1[1])); } org.locationtech.jts.geom.Point point = calculateCentroid(coordinates); Map geocode = gdMapGeocodingUtil.geocode(point.getX() + "", point.getY() + ""); if(null == geocode){ return R.fail("经纬度转换城市code失败"); } String cityCode = geocode.get("cityCode"); dto.setCityCode(cityCode); xpElectronicFence.setCityCode(dto.getCityCode()); this.updateById(xpElectronicFence); // 禁行区(areaType=2)差异删除:对比原有禁行区与本次编辑入参禁行区(按id) List existingProhibitedAreas = xpElectronicFenceAreaService.lambdaQuery() .eq(XpElectronicFenceArea::getElectronicId, dto.getId()) .eq(XpElectronicFenceArea::getAreaType, 2) .list(); Set incomingProhibitedIds = updatedElectronicFenceAreas.stream() .filter(a -> a != null && a.getAreaType() != null && a.getAreaType() == 2 && a.getId() != null) .map(XpElectronicFenceArea::getId) .collect(Collectors.toSet()); List toDeleteProhibitedIds = existingProhibitedAreas.stream() .map(XpElectronicFenceArea::getId) .filter(id -> id != null && !incomingProhibitedIds.contains(id)) .collect(Collectors.toList()); if (!toDeleteProhibitedIds.isEmpty()) { // 逻辑删除MySQL禁行区 xpElectronicFenceAreaService.removeByIds(toDeleteProhibitedIds); // 同步删除Mongo禁行区多边形 List toDeleteAreaIdStrings = toDeleteProhibitedIds.stream().map(String::valueOf).collect(Collectors.toList()); Query delMongoQuery = new Query(); delMongoQuery.addCriteria(Criteria.where("fenceId").is(String.valueOf(dto.getId()))); delMongoQuery.addCriteria(Criteria.where("areaId").in(toDeleteAreaIdStrings)); mongoTemplate.remove(delMongoQuery, XpGeoFencingProhibitedPolygon.class); } // 删除旧的区域数据,然后保存新的区域数据 mongoTemplate.remove(new Query(Criteria.where("fenceId").is(String.valueOf(dto.getId()))), XpGeoFencingPolygon.class); if (xpElectronicFence.getStatus() == 2) { // 设置电子围栏ID并保存区域数据 for (XpElectronicFenceArea area : updatedElectronicFenceAreas) { area.setElectronicId(xpElectronicFence.getId()); area.setAreaStatus(1); area.setArea(area.getArea()); area.setCityCode(dto.getCityCode()); if(null != area.getId()){ xpElectronicFenceAreaService.updateById(area); }else{ xpElectronicFenceAreaService.save(area); } } }else{ // 设置电子围栏ID并保存区域数据 for (XpElectronicFenceArea area : updatedElectronicFenceAreas) { area.setElectronicId(xpElectronicFence.getId()); area.setAreaStatus(2); area.setArea(area.getArea()); area.setCityCode(dto.getCityCode()); if(null != area.getId()){ xpElectronicFenceAreaService.updateById(area); }else{ xpElectronicFenceAreaService.save(area); } } } if (dto.getStatus() == 1 || dto.getStatus() == 2) { for (XpElectronicFenceArea newElectronicFenceArea : updatedElectronicFenceAreas) { if (newElectronicFenceArea.getAreaType() == 1 && newElectronicFenceArea.getAreaStatus() == 1) { // 先查询出mongo里围栏相同的数据 如果有 那么更新数据 Query query = new Query(); query.addCriteria(Criteria.where("fenceId").is(String.valueOf(xpElectronicFence.getId()))); query.addCriteria(Criteria.where("areaId").is(String.valueOf(newElectronicFenceArea.getId()))); XpGeoFencingPolygon old = mongoTemplate.findOne(query, XpGeoFencingPolygon.class); if (old == null) { XpGeoFencingPolygon xpGeoFencingPolygon = new XpGeoFencingPolygon(); xpGeoFencingPolygon.setFenceId(String.valueOf(xpElectronicFence.getId())); xpGeoFencingPolygon.setName(xpElectronicFence.getElectronicName()); xpGeoFencingPolygon.setCityCode(cityCode); xpGeoFencingPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId())); xpGeoFencingPolygon.setOperationStrategy(xpElectronicFence.getOperationStrategy()); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); xpGeoFencingPolygon.setPoints(geoJsonPolygon); mongoTemplate.save(xpGeoFencingPolygon); } else { // 如果存在,则更新已有文档 old.setFenceId(String.valueOf(xpElectronicFence.getId())); old.setName(xpElectronicFence.getElectronicName()); old.setCityCode(cityCode); old.setAreaId(String.valueOf(newElectronicFenceArea.getId())); old.setOperationStrategy(xpElectronicFence.getOperationStrategy()); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); old.setPoints(geoJsonPolygon); // 执行更新操作 mongoTemplate.save(old); // save() 会根据 _id 自动判断是插入还是更新 } } if (newElectronicFenceArea.getAreaType() == 2 && newElectronicFenceArea.getAreaStatus() == 1) { // 先查询出mongo里围栏相同的数据 如果有 那么更新数据 Query query = new Query(); query.addCriteria(Criteria.where("fenceId").is(String.valueOf(xpElectronicFence.getId()))); query.addCriteria(Criteria.where("areaId").is(String.valueOf(newElectronicFenceArea.getId()))); XpGeoFencingProhibitedPolygon old = mongoTemplate.findOne(query, XpGeoFencingProhibitedPolygon.class); if (old == null) { XpGeoFencingProhibitedPolygon xpGeoFencingProhibitedPolygon = new XpGeoFencingProhibitedPolygon(); xpGeoFencingProhibitedPolygon.setFenceId(String.valueOf(xpElectronicFence.getId())); xpGeoFencingProhibitedPolygon.setName(xpElectronicFence.getElectronicName()); xpGeoFencingProhibitedPolygon.setAreaId(String.valueOf(newElectronicFenceArea.getId())); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); xpGeoFencingProhibitedPolygon.setPoints(geoJsonPolygon); mongoTemplate.save(xpGeoFencingProhibitedPolygon); } else { old.setFenceId(String.valueOf(xpElectronicFence.getId())); old.setName(xpElectronicFence.getElectronicName()); old.setAreaId(String.valueOf(newElectronicFenceArea.getId())); // 将围栏点转换为GeoJSON多边形 GeoJsonPolygon geoJsonPolygon = convertToGeoJsonPolygon(newElectronicFenceArea.getFencePoints()); old.setPoints(geoJsonPolygon); mongoTemplate.save(old); } } } } // 判断区域下的所有运营点位是否处于运营区域内 如果不是则更新为测试点位 List areaIds = updatedElectronicFenceAreas.stream().map(XpElectronicFenceArea::getId).collect(Collectors.toList()); LambdaQueryWrapper pointLambdaQueryWrapper = new LambdaQueryWrapper<>(); pointLambdaQueryWrapper.in(XpPoint::getElectronicAreaId, areaIds).eq(XpPoint::getPointType, 1); List xpPoints = xpPointMapper.selectList(pointLambdaQueryWrapper); List updatePoints = new ArrayList<>(); if (!xpPoints.isEmpty()) { List electronicAreaId = xpPoints.stream().map(XpPoint::getElectronicAreaId).distinct().collect(Collectors.toList()); if (!electronicAreaId.isEmpty()) { Map longXpElectronicFenceAreaMap = updatedElectronicFenceAreas .stream().collect(Collectors.toMap(XpElectronicFenceArea::getId, e -> e)); for (XpPoint xpPoint : xpPoints) { XpElectronicFenceArea xpElectronicFenceArea = longXpElectronicFenceAreaMap.get(xpPoint.getElectronicAreaId()); if (xpElectronicFenceArea != null) { String fencePoints = xpElectronicFenceArea.getFencePoints(); Double pointLat = xpPoint.getPointLat(); Double pointLon = xpPoint.getPointLon(); // 判断这个点位有没有处于当前区域 由多个经纬度点构成的面里 if (fencePoints != null && !fencePoints.trim().isEmpty() && pointLat != null && pointLon != null) { boolean isInside = isPointInPolygon(fencePoints, pointLon, pointLat); // 如果点不在区域内,更新为测试点位 if (!isInside) { xpPoint.setPointType(2); // log.info("点位ID: {} 经纬度: ({}, {}) 不在电子围栏区域内,已更新为测试点位", xpPoint.getId(), pointLon, pointLat); UpdateWrapper xpPointUpdateWrapper = new UpdateWrapper<>(); xpPointUpdateWrapper.eq("id", xpPoint.getId()); xpPointUpdateWrapper.set("point_type", 2); xpPointUpdateWrapper.set("electronic_areaId", null); xpPointMapper.update(xpPoint, xpPointUpdateWrapper); } } } } } } return R.ok(); } /** * 判断点是否在多边形内 * * @param fencePointsStr 多边形顶点字符串,格式:经度_维度,经度_维度 * @param pointLon 待检测点的经度 * @param pointLat 待检测点的纬度 * @return true表示在多边形内,false表示在多边形外 */ private boolean isPointInPolygon(String fencePointsStr, double pointLon, double pointLat) { if (fencePointsStr == null || fencePointsStr.trim().isEmpty()) { return false; } // 解析多边形顶点 String[] pointPairs = fencePointsStr.split(","); List polygonVertices = new ArrayList<>(); for (String pointPair : pointPairs) { String[] lngLat = pointPair.trim().split("_"); if (lngLat.length >= 2) { try { double longitude = Double.parseDouble(lngLat[0]); double latitude = Double.parseDouble(lngLat[1]); polygonVertices.add(new double[]{longitude, latitude}); } catch (NumberFormatException e) { log.error("解析多边形顶点坐标失败: {}", pointPair, e); return false; } } } if (polygonVertices.size() < 3) { return false; // 至少需要3个点才能构成多边形 } return isPointInPolygon(polygonVertices, pointLon, pointLat); } /** * 使用射线法判断点是否在多边形内 * * @param polygonVertices 多边形顶点列表 * @param pointLon 待检测点的经度 * @param pointLat 待检测点的纬度 * @return true表示在多边形内,false表示在多边形外 */ private boolean isPointInPolygon(List polygonVertices, double pointLon, double pointLat) { int n = polygonVertices.size(); boolean inside = false; // 射线法判断点是否在多边形内 double x = pointLon, y = pointLat; for (int i = 0, j = n - 1; i < n; j = i++) { double xi = polygonVertices.get(i)[0], yi = polygonVertices.get(i)[1]; double xj = polygonVertices.get(j)[0], yj = polygonVertices.get(j)[1]; // 判断当前边是否与水平射线相交 boolean intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) { inside = !inside; } } return inside; } /** * 将围栏点字符串转换为GeoJsonPolygon * 格式:经度_维度,经度_维度 */ private GeoJsonPolygon convertToGeoJsonPolygon(String fencePointsStr) { if (fencePointsStr == null || fencePointsStr.trim().isEmpty()) { return null; } // 分割多个点对 String[] pointPairs = fencePointsStr.split(","); List points = new ArrayList<>(); for (String pointPair : pointPairs) { String[] lngLat = pointPair.trim().split("_"); if (lngLat.length >= 2) { try { double longitude = Double.parseDouble(lngLat[0]); double latitude = Double.parseDouble(lngLat[1]); points.add(new Point(longitude, latitude)); // Point 构造函数是 (x, y) -> (longitude, latitude) } catch (NumberFormatException e) { log.error("解析经纬度坐标失败: {}", pointPair, e); throw new IllegalArgumentException("经纬度格式错误: " + pointPair); } } } // 如果没有足够的点构成多边形,至少需要3个点 if (points.size() < 3) { throw new IllegalArgumentException("围栏点数量不足,至少需要3个点构成多边形"); } // 确保多边形是闭合的(如果最后一个点不等于第一个点,则添加第一个点) if (!points.get(0).equals(points.get(points.size() - 1))) { points.add(points.get(0)); // 添加第一个点作为最后一个点,闭合多边形 } return new GeoJsonPolygon(points); } /** * 检查新添加的区域之间是否有重叠 */ private boolean hasOverlapWithinNewAreas(List newAreas) { for (int i = 0; i < newAreas.size(); i++) { XpElectronicFenceArea area1 = newAreas.get(i); if (area1.getAreaType() != 1) continue; // 只检查运营区域 for (int j = i + 1; j < newAreas.size(); j++) { XpElectronicFenceArea area2 = newAreas.get(j); if (area2.getAreaType() == 1) { if (polygonOverlapService.checkPolygonOverlap( area1.getFencePoints(), area2.getFencePoints())) { return true; } } } } return false; } /** * @return */ @Override public List getAvailableList() { return lambdaQuery().eq(XpElectronicFence::getStatus, 2).list(); //查询已启用的围栏 } @Override public List getList() { return list(); } /** * 计算围栏中心点 * @param coordinates * @return */ private org.locationtech.jts.geom.Point calculateCentroid(Coordinate[] coordinates) { GeometryFactory factory = new GeometryFactory(); // 创建线性环 (LinearRing) LinearRing ring = factory.createLinearRing(coordinates); // 创建多边形 (Polygon) Polygon polygon = factory.createPolygon(ring); // 计算质心 (Centroid) org.locationtech.jts.geom.Point centroid = polygon.getCentroid(); System.out.println("中心点经度: " + centroid.getX()); System.out.println("中心点纬度: " + centroid.getY()); return centroid; } }