增加电子围栏逻辑

master
guochaojie 5 months ago
parent 37ab806a31
commit 838a02108b

@ -12,6 +12,7 @@ import cc.yunxi.domain.query.RecycleStationQuery;
import cc.yunxi.domain.vo.priceproduct.ProductRespVO;
import cc.yunxi.domain.vo.recyclestation.RecycleStationRespVO;
import cc.yunxi.service.IRecycleStationService;
import cc.yunxi.service.IStationAreaService;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
@ -43,6 +44,7 @@ import java.util.List;
public class RecycleStationController {
private final IRecycleStationService recycleStationService;
private final IStationAreaService stationAreaService;
@ApiOperation("分页查询回收站")
@PostMapping("/page")
@ -112,7 +114,9 @@ public class RecycleStationController {
@ApiOperation("附近的回收站")
@GetMapping("/nearby")
public CommonResult<RecycleStationRespVO> stationNearby(@Valid LocationDTO location) {
RecycleStation nearbyStation = recycleStationService.getNearbyStation(location);
// RecycleStation nearbyStation = recycleStationService.getNearbyStation(location);
// 增加区域筛选条件
RecycleStation nearbyStation = recycleStationService.getNearbyInArea(location);
RecycleStationRespVO stationRespVO = null;
if (ObjectUtil.isNotEmpty(nearbyStation)) {
stationRespVO = BeanUtils.copyBean(nearbyStation, RecycleStationRespVO.class);

@ -0,0 +1,37 @@
package cc.yunxi.controller;
import cc.yunxi.common.domain.CommonResult;
import cc.yunxi.domain.dto.LocationDTO;
import cc.yunxi.domain.po.StationArea;
import cc.yunxi.service.IStationAreaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@Api(tags = "商户经营区域")
@RequiredArgsConstructor
@RestController
@RequestMapping("/station-area")
@Slf4j
@Validated
public class StationAreaController {
private final IStationAreaService stationAreaService;
@GetMapping("/inArea")
@ApiOperation("获取当前位置所属的经营区域")
public CommonResult<StationArea> getInArea(@Valid LocationDTO location) {
StationArea area = stationAreaService.getBelongArea(location);
if (null != area) return CommonResult.success(area);
return CommonResult.error(400, "当前位置不在经营区域内");
}
}

@ -0,0 +1,36 @@
package cc.yunxi.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PositionDTO {
@ApiModelProperty(value = "当前经度", required = true, example = "121.404032")
@NotNull(message = "未知定位")
private BigDecimal longitude;
@ApiModelProperty(value = "当前纬度", required = true, example = "31.163973")
@NotNull(message = "未知定位")
private BigDecimal latitude;
@ApiModelProperty(value = "所属商户", required = true, example = "543049974737277765")
@NotNull(message = "未知商户")
private String companyId;
@ApiModelProperty(value = "转换为pos")
public static PositionDTO transformFromLoc(LocationDTO locationDTO, @NotNull String companyId) {
PositionDTO dto = new PositionDTO();
dto.setLongitude(new BigDecimal(locationDTO.getLongitude()));
dto.setLatitude(new BigDecimal(locationDTO.getLatitude()));
dto.setCompanyId(companyId);
return dto;
}
}

@ -0,0 +1,60 @@
package cc.yunxi.domain.po;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
*
*
* @ V3.5
* @ https://www.jnpfsoft.com
* @ JNPF
* @ 2024-05-22
*/
@Data
@TableName("nx_station_area")
public class StationArea {
@TableId(value ="ID" )
private String id;
@TableField("STATION_CODE")
private String stationCode;
@TableField(value = "AREA_TYPE" , updateStrategy = FieldStrategy.IGNORED)
private Integer areaType;
@TableField(value = "AREA_NAME" , updateStrategy = FieldStrategy.IGNORED)
private String areaName;
@TableField(value = "AREA_DESC" , updateStrategy = FieldStrategy.IGNORED)
private String areaDesc;
@TableField(value = "AREA_MAP_INFO" , updateStrategy = FieldStrategy.IGNORED)
private String areaMapInfo;
@TableField(value = "STATUS" , updateStrategy = FieldStrategy.IGNORED)
private Integer status;
@TableField("F_CREATOR_TIME")
private Date creatorTime;
@TableField("F_CREATOR_USER_ID")
private String creatorUserId;
@TableField("F_LAST_MODIFY_TIME")
private Date lastModifyTime;
@TableField("F_LAST_MODIFY_USER_ID")
private String lastModifyUserId;
@TableField("F_DELETE_TIME")
private Date deleteTime;
@TableField("F_DELETE_USER_ID")
private String deleteUserId;
@TableField("F_DELETE_MARK")
private String deleteMark;
@TableField("F_TENANT_ID")
private String tenantId;
@TableField(value = "COMPANY_ID" , updateStrategy = FieldStrategy.IGNORED)
private String companyId;
@TableField("DEPARTMENT_ID")
private String departmentId;
@TableField("ORGANIZE_JSON_ID")
private String organizeJsonId;
@TableField("F_FLOW_ID")
private String flowId;
}

@ -1,6 +1,7 @@
package cc.yunxi.mapper;
import cc.yunxi.domain.dto.LocationDTO;
import cc.yunxi.domain.dto.PositionDTO;
import cc.yunxi.domain.po.RecycleStation;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@ -27,6 +28,7 @@ public interface RecycleStationMapper extends BaseMapper<RecycleStation> {
/**
* sql xml
*
* @param page
* @param queryWrapper
* @return Page<RecycleStation>
@ -38,9 +40,18 @@ public interface RecycleStationMapper extends BaseMapper<RecycleStation> {
/**
*
*
* @param locationDTO
* @return RecycleStation
*/
RecycleStation getNearbyStation(@Param("location") LocationDTO locationDTO);
/**
*
*
* @param positionDTO
* @return RecycleStation
*/
RecycleStation getNearbyInArea(@Param("position") PositionDTO positionDTO);
}

@ -0,0 +1,16 @@
package cc.yunxi.mapper;
import cc.yunxi.domain.po.StationArea;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
*
* V3.5
* https://www.jnpfsoft.com
* JNPF
* 2024-05-23
*/
public interface StationAreaMapper extends BaseMapper<StationArea> {
}

@ -24,6 +24,7 @@ public interface IRecycleStationService extends IService<RecycleStation> {
/**
*
*
* @param recycleStationQuery
* @return Page<RecycleStation>
*/
@ -37,8 +38,17 @@ public interface IRecycleStationService extends IService<RecycleStation> {
*/
RecycleStation getNearbyStation(LocationDTO locationDTO);
/**
*
*
* @param locationDTO
* @return RecycleStation
*/
RecycleStation getNearbyInArea(LocationDTO locationDTO);
/**
*
*
* @param id
* @return RecycleOrder
*/
@ -46,6 +56,7 @@ public interface IRecycleStationService extends IService<RecycleStation> {
/**
* id
*
* @param ids
* @return RecycleOrder
*/
@ -54,6 +65,7 @@ public interface IRecycleStationService extends IService<RecycleStation> {
/**
*
*
* @param stationId
* @return RecycleOrder
*/
@ -62,6 +74,7 @@ public interface IRecycleStationService extends IService<RecycleStation> {
/**
*
*
* @param stationId
* @return RecycleOrder
*/
@ -70,11 +83,11 @@ public interface IRecycleStationService extends IService<RecycleStation> {
/**
*
*
* @param stationId
* @return RecycleOrder
*/
List<ProductRespVO> getStationProductFirstByStationId(String stationId);
}

@ -0,0 +1,21 @@
package cc.yunxi.service;
import cc.yunxi.domain.dto.LocationDTO;
import cc.yunxi.domain.po.StationArea;
import com.baomidou.mybatisplus.extension.service.IService;
import javax.validation.Valid;
/**
*
* V3.5
* https://www.jnpfsoft.com
* JNPF
* 2024-05-23
*/
public interface IStationAreaService extends IService<StationArea> {
//获取位置所属区域
StationArea getBelongArea(@Valid LocationDTO locationDTO);
}

@ -0,0 +1,49 @@
package cc.yunxi.service.impl;
import cc.yunxi.domain.dto.LocationDTO;
import cc.yunxi.domain.po.StationArea;
import cc.yunxi.mapper.StationAreaMapper;
import cc.yunxi.service.IStationAreaService;
import cc.yunxi.utils.AreaUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
/**
*
*
* V3.5
* https://www.jnpfsoft.com
* JNPF
* 2024-05-23
*/
@Service
public class IStationAreaServiceImpl extends ServiceImpl<StationAreaMapper, StationArea> implements IStationAreaService {
@Resource
private StationAreaMapper stationAreaMapper;
@Override
public StationArea getBelongArea(LocationDTO location) {
//获取所有的围栏信息
QueryWrapper<StationArea> wrapper = new QueryWrapper<>();
// wrapper.eq("area_type","1");//1接单 2不接单
wrapper.eq("status","1");//0禁用 1启用
List<StationArea> areas = stationAreaMapper.selectList(wrapper);
BigDecimal lat = new BigDecimal(location.getLatitude());//纬度
BigDecimal lng = new BigDecimal(location.getLongitude());//经度
for (StationArea area : areas) {
String mapInfo = area.getAreaMapInfo();
if(StringUtils.hasText(mapInfo)&& AreaUtil.isInsidePolygon(mapInfo, lng, lat)){
return area;
}
}
return null;
}
}

@ -5,6 +5,7 @@ import cc.yunxi.common.exception.BizIllegalException;
import cc.yunxi.common.utils.BeanUtils;
import cc.yunxi.common.utils.CollUtils;
import cc.yunxi.domain.dto.LocationDTO;
import cc.yunxi.domain.dto.PositionDTO;
import cc.yunxi.domain.po.*;
import cc.yunxi.domain.query.RecycleStationQuery;
import cc.yunxi.domain.vo.priceproduct.ProductRespVO;
@ -12,6 +13,7 @@ import cc.yunxi.domain.vo.recyclestation.RecycleStationRespVO;
import cc.yunxi.enums.GlobalStatusEnum;
import cc.yunxi.mapper.*;
import cc.yunxi.service.IRecycleStationService;
import cc.yunxi.service.IStationAreaService;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -49,6 +51,8 @@ public class RecycleStationServiceImpl extends ServiceImpl<RecycleStationMapper,
private PriceProductMapper priceProductMapper;
@Resource
private ProductMapper productMapper;
@Resource
private IStationAreaService stationAreaService;
@Override
@ -82,6 +86,21 @@ public class RecycleStationServiceImpl extends ServiceImpl<RecycleStationMapper,
return recycleStationMapper.getNearbyStation(locationDTO);
}
@Override
public RecycleStation getNearbyInArea(LocationDTO locationDTO) {
RecycleStation station = null;
//获取位置所属区域 区域绑定的商户
StationArea area = stationAreaService.getBelongArea(locationDTO);
if (area != null) {//已绑定区域
PositionDTO position = PositionDTO.transformFromLoc(locationDTO, area.getCompanyId());
//商户名下 距离最近的回收站
station = recycleStationMapper.getNearbyInArea(position);
}else {//未绑定区域 获取不超接单范围的最近的回收站
station = getNearbyStation(locationDTO);
}
return station;
}
@Override
public RecycleStation getStationById(String id) {

@ -0,0 +1,151 @@
package cc.yunxi.utils;
import cn.hutool.json.JSONUtil;
import java.math.BigDecimal;
import java.util.*;
/**
* <p>
* 线线线
* <p>
*
* <p>
* 1.使
* 2.线线
* 3.
* 4.线
* </p>
*
* @author guochaojie
* @version V1.0.0
* @date 202452109:49:29
* @description
*/
public class AreaUtil {
/**
*
*
* @param polygonStr [x1,y1,x2,y2,...]
* x1,y1
* @param lat
* @param lon
* @return
*/
public static boolean isInsidePolygonStr(String polygonStr, double lat, double lon) {
double[][] polygon = parsePolygonFromArray(polygonStr);
int j = polygon.length - 1; // 多边形最后一个点的索引
boolean oddNodes = false;
for (int i = 0; i < polygon.length; i++) {
if ((polygon[i][1] < lon && polygon[j][1] >= lon) // 检查点是否在多边形边的右侧
|| (polygon[j][1] < lon && polygon[i][1] >= lon)) { // 或在多边形边的左侧
if (polygon[i][0] + (lon - polygon[i][1]) / (polygon[j][1] - polygon[i][1])
* (polygon[j][0] - polygon[i][0]) < lat) { // 检查交点是否在线段上
oddNodes = !oddNodes; // 如果是,则翻转状态
}
}
j = i; // 移动到下一个边
}
return oddNodes; // 如果状态为true则点在多边形内
}
/**
*
*
* @param polygonStr
* @return
*/
public static double[][] parsePolygonFromArray(String polygonStr) {
// 移除可能的空格,并用逗号分割字符串`
String[] coordinatePairs = polygonStr.replaceAll(" ", "")
.replaceAll("\\[", "")
.replaceAll("\\]", "")
.split(",");
// 计算有多少个坐标对
int pairCount = coordinatePairs.length / 2;
double[][] polygon = new double[pairCount][2];
// 遍历坐标对数组,并填充二维数组
for (int i = 0, j = 0; i < coordinatePairs.length; i += 2, j++) {
// 将字符串转换为double
polygon[j][0] = Double.parseDouble(coordinatePairs[i]); // 纬度
polygon[j][1] = Double.parseDouble(coordinatePairs[i + 1]); // 经度
}
// 由于多边形应该是闭合的,通常我们需要复制第一个点作为最后一个点
// 判读最后一个点是否与第一个点相同
if (polygon[0][0] != polygon[pairCount - 1][0] && polygon[0][1] != polygon[pairCount - 1][1]) {
//追加一个点
polygon = Arrays.copyOf(polygon, polygon.length + 1);
polygon[pairCount] = new double[]{polygon[0][0], polygon[0][1]};
}
return polygon;
}
/**
* @param json
* @return
*/
public static List<Map<String, Object>> parsePolygonFromJson(String json) {
List<HashMap> list = JSONUtil.toList(json, HashMap.class);
HashMap start = list.get(0);
HashMap end = list.get(list.size() - 1);
if (!start.equals(end)) {
list.add(start);
}
List<Map<String, Object>> maps = new ArrayList<>();
for (HashMap map : list) {
maps.add(map);
}
return maps;
}
/**
* @param polygonJson
* @param lat
* @param lng
* @return
*/
public static boolean isInsidePolygon(String polygonJson, BigDecimal lat, BigDecimal lng) {
List<Map<String, Object>> polygon = parsePolygonFromJson(polygonJson);
boolean isInside = false;
for (int i = 0, j = polygon.size() - 1; i < polygon.size(); j = i++) {
Map<String, Object> vertex1 = polygon.get(i);
Map<String, Object> vertex2 = polygon.get(j);
BigDecimal lat1 = new BigDecimal(vertex1.get("lat").toString());
BigDecimal lng1 = new BigDecimal(vertex1.get("lng").toString());
BigDecimal lat2 = new BigDecimal(vertex2.get("lat").toString());
BigDecimal lng2 = new BigDecimal(vertex2.get("lng").toString());
boolean intersect = (lng1.compareTo(lng) > 0) != (lng2.compareTo(lng) > 0) &&
lat.compareTo(lat1.add(lat2.subtract(lat1)
.multiply(lng.subtract(lng1))
.divide(lng2.subtract(lng1), 20, BigDecimal.ROUND_HALF_UP))) < 0;
if (intersect) {
isInside = !isInside;
}
}
return isInside;
}
public static void main(String[] args) {
String json = "[{\"lat\":39.98423552382614,\"lng\":116.3059581286434},{\"lat\":39.986865947052266,\"lng\":116.3783954344508},{\"lat\":39.94897808062252,\"lng\":116.37942533460955},{\"lat\":39.94818852953558,\"lng\":116.3093911759388},{\"lat\":39.960820297511674,\"lng\":116.30801795711886}]";
BigDecimal testLat = new BigDecimal(39.94397741373967);
BigDecimal testLng = new BigDecimal(116.21807210599582);
BigDecimal testLat2 = new BigDecimal(39.97292354297105);
BigDecimal testLng2 = new BigDecimal(116.34818459585586);
boolean isInside = isInsidePolygon(json, testLat, testLng);
boolean isInside2 = isInsidePolygon(json, testLat2, testLng2);
System.out.println(isInside);
System.out.println(isInside2);
}
}

@ -50,4 +50,26 @@
LIMIT 1;
</select>
//区域所属商户名下距离最近的回收站
<select id="getNearbyInArea" resultType="RecycleStation">
SELECT a.*,
ROUND(
6378.138 * 2 * ASIN(
SQRT(
POW(SIN((#{position.latitude} - latitude) * PI() / 360), 2) +
COS(#{position.latitude} * PI() / 180) * COS(latitude * PI() / 180) *
POW(SIN((#{postion.longitude} - longitude) * PI() / 360), 2))) * 1000
) AS distance
FROM nx_enterprise_recycle_station a
WHERE a.latitude IS NOT NULL
AND a.longitude IS NOT NULL
<if test="position.companyId != null">
AND a.company_id = #{position.companyId}
</if>
HAVING distance &lt;= a.accept_range
ORDER BY distance ASC LIMIT 1;
</select>
</mapper>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.yunxi.mapper.StationAreaMapper">
</mapper>
Loading…
Cancel
Save