| | |
| | | import java.util.*; |
| | | import java.text.SimpleDateFormat; |
| | | import java.text.ParseException; |
| | | import java.io.IOException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | |
| | | import com.ruoyi.system.domain.*; |
| | | import com.ruoyi.system.service.*; |
| | | import com.ruoyi.common.config.TencentMapConfig; |
| | | import com.ruoyi.common.config.BaiduMapConfig; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | |
| | | import com.ruoyi.common.enums.BusinessType; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.common.core.page.TableDataInfo; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | |
| | | /** |
| | | * 车辆GPS坐标Controller |
| | |
| | | |
| | | @Autowired |
| | | private ICmsGpsCollectService cmsGpsCollectService; |
| | | |
| | | @Autowired |
| | | private TencentMapConfig tencentMapConfig; |
| | | |
| | | @Autowired |
| | | private BaiduMapConfig baiduMapConfig; |
| | | |
| | | /** |
| | | * 查询车辆GPS坐标列表 |
| | |
| | | } |
| | | |
| | | // 处理开始时间 |
| | | beginTime = beginTime.replace("T", " ").replace(" ","%20"); |
| | | beginTime = beginTime.replace("T", " "); |
| | | if (beginTime.split(":").length == 2) { // 只有小时和分钟 |
| | | beginTime += ":00"; |
| | | } |
| | | |
| | | // 处理结束时间 |
| | | endTime = endTime.replace("T", " ").replace(" ","%20"); |
| | | // endTime = endTime.replace("T", " ").replace(" ","%20"); |
| | | endTime = endTime.replace("T", " "); |
| | | if (endTime.split(":").length == 2) { // 只有小时和分钟 |
| | | endTime += ":59"; |
| | | } |
| | |
| | | throw new Error("查询车辆轨迹失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 腾讯地图地址搜索接口代理 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/address/search") |
| | | public AjaxResult searchAddress(String keyword, String region) { |
| | | try { |
| | | // 构建腾讯地图搜索API URL |
| | | String url = "https://apis.map.qq.com/ws/place/v1/search"; |
| | | String params = "keyword=" + URLEncoder.encode(keyword, StandardCharsets.UTF_8.toString()) + |
| | | "&boundary=region(" + (region != null ? region : "广州") + ")" + |
| | | "&key=" + tencentMapConfig.getKey(); |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | |
| | | // 返回结果 |
| | | return AjaxResult.success("查询成功", response); |
| | | } catch (Exception e) { |
| | | logger.error("地址搜索失败", e); |
| | | return AjaxResult.error("地址搜索失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 腾讯地图逆地址解析接口代理 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/address/geocoder") |
| | | public AjaxResult reverseGeocoder(Double lat, Double lng) { |
| | | try { |
| | | // 检查参数 |
| | | logger.info("逆地址解析请求参数: lat={}, lng={}", lat, lng); |
| | | |
| | | if (lat == null || lng == null) { |
| | | logger.warn("参数不完整,缺少经纬度坐标: lat={}, lng={}", lat, lng); |
| | | return AjaxResult.error("参数不完整,缺少经纬度坐标"); |
| | | } |
| | | |
| | | // 检查参数有效性 |
| | | if (Double.isNaN(lat) || Double.isNaN(lng) || |
| | | Double.isInfinite(lat) || Double.isInfinite(lng)) { |
| | | logger.warn("参数无效,经纬度坐标包含非法值: lat={}, lng={}", lat, lng); |
| | | return AjaxResult.error("参数无效,经纬度坐标格式错误"); |
| | | } |
| | | |
| | | // 构建腾讯地图逆地址解析API URL |
| | | String url = "https://apis.map.qq.com/ws/geocoder/v1/"; |
| | | String params = "location=" + lat + "," + lng + |
| | | "&key=" + tencentMapConfig.getKey() + |
| | | "&get_poi=1"; |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | |
| | | // 返回结果 |
| | | return AjaxResult.success("查询成功", response); |
| | | } catch (Exception e) { |
| | | logger.error("逆地址解析失败: lat={}, lng={}", lat, lng, e); |
| | | return AjaxResult.error("逆地址解析失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 腾讯地图路线规划接口代理(计算两点间距离) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/route/distance") |
| | | public AjaxResult calculateDistance(Double fromLat, Double fromLng, Double toLat, Double toLng) { |
| | | try { |
| | | // 检查参数 |
| | | if (fromLat == null || fromLng == null || toLat == null || toLng == null) { |
| | | return AjaxResult.error("参数不完整,缺少起点或终点坐标"); |
| | | } |
| | | |
| | | // 构建腾讯地图路线规划API URL |
| | | String url = "https://apis.map.qq.com/ws/distance/v1/"; |
| | | String params = "mode=driving" + |
| | | "&from=" + fromLat + "," + fromLng + |
| | | "&to=" + toLat + "," + toLng + |
| | | "&key=" + tencentMapConfig.getKey(); |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | |
| | | // 返回结果 |
| | | return AjaxResult.success("计算成功", response); |
| | | } catch (Exception e) { |
| | | logger.error("距离计算失败", e); |
| | | return AjaxResult.error("距离计算失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 百度地图地理编码接口代理(地址转坐标) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/baidu/geocoding") |
| | | public AjaxResult baiduGeocoding(String address, String city) { |
| | | try { |
| | | // 检查参数 |
| | | if (address == null || address.trim().isEmpty()) { |
| | | return AjaxResult.error("参数不完整,缺少地址信息"); |
| | | } |
| | | |
| | | // 构建百度地图地理编码API URL |
| | | String url = "https://api.map.baidu.com/geocoding/v3/"; |
| | | String params = "address=" + URLEncoder.encode(address, StandardCharsets.UTF_8.toString()) + |
| | | (city != null && !city.trim().isEmpty() ? |
| | | "&city=" + URLEncoder.encode(city, StandardCharsets.UTF_8.toString()) : "") + |
| | | "&output=json" + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | logger.info("百度地图地理编码请求: address={}, city={}", address, city); |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | |
| | | // 返回结果 |
| | | return AjaxResult.success("查询成功", response); |
| | | } catch (Exception e) { |
| | | logger.error("百度地图地理编码失败", e); |
| | | return AjaxResult.error("地理编码失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 百度地图路线规划接口代理(计算两个坐标之间的驾车距离) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/baidu/route/driving") |
| | | public AjaxResult baiduRouteDriving(String origin, String destination) { |
| | | try { |
| | | // 检查参数 |
| | | if (origin == null || origin.trim().isEmpty() || |
| | | destination == null || destination.trim().isEmpty()) { |
| | | return AjaxResult.error("参数不完整,缺少起点或终点坐标"); |
| | | } |
| | | |
| | | // 验证坐标格式(纬度,经度) |
| | | String[] originParts = origin.split(","); |
| | | String[] destParts = destination.split(","); |
| | | if (originParts.length != 2 || destParts.length != 2) { |
| | | return AjaxResult.error("坐标格式错误,应为:纬度,经度"); |
| | | } |
| | | |
| | | // 构建百度地图路线规划API URL |
| | | String url = "https://api.map.baidu.com/directionlite/v1/driving"; |
| | | String params = "origin=" + origin + |
| | | "&destination=" + destination + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | logger.info("百度地图路线规划请求: origin={}, destination={}", origin, destination); |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | |
| | | // 返回结果 |
| | | return AjaxResult.success("计算成功", response); |
| | | } catch (Exception e) { |
| | | logger.error("百度地图路线规划失败", e); |
| | | return AjaxResult.error("路线规划失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 百度地图计算两个地址之间的距离(组合接口:地址转坐标 + 路线规划) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/baidu/distance/byAddress") |
| | | public AjaxResult baiduDistanceByAddress(String fromAddress, String fromCity, |
| | | String toAddress, String toCity) { |
| | | try { |
| | | // 检查参数 |
| | | if (fromAddress == null || fromAddress.trim().isEmpty() || |
| | | toAddress == null || toAddress.trim().isEmpty()) { |
| | | return AjaxResult.error("参数不完整,缺少起点或终点地址"); |
| | | } |
| | | |
| | | logger.info("开始计算地址距离: fromAddress={}, fromCity={}, toAddress={}, toCity={}", |
| | | fromAddress, fromCity, toAddress, toCity); |
| | | |
| | | // 第一步:起点地址转坐标 |
| | | String geocodingUrl1 = "https://api.map.baidu.com/geocoding/v3/"; |
| | | String geocodingParams1 = "address=" + URLEncoder.encode(fromAddress, StandardCharsets.UTF_8.toString()) + |
| | | (fromCity != null && !fromCity.trim().isEmpty() ? |
| | | "&city=" + URLEncoder.encode(fromCity, StandardCharsets.UTF_8.toString()) : "") + |
| | | "&output=json" + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | String geocodingResponse1 = HttpUtils.sendGet(geocodingUrl1, geocodingParams1); |
| | | logger.info("起点地理编码响应: {}", geocodingResponse1); |
| | | |
| | | // 解析起点坐标 |
| | | com.alibaba.fastjson2.JSONObject geocodingJson1 = com.alibaba.fastjson2.JSONObject.parseObject(geocodingResponse1); |
| | | if (geocodingJson1.getInteger("status") != 0) { |
| | | logger.error("起点地理编码失败: {}", geocodingResponse1); |
| | | return AjaxResult.error("起点地址解析失败"); |
| | | } |
| | | com.alibaba.fastjson2.JSONObject location1 = geocodingJson1.getJSONObject("result").getJSONObject("location"); |
| | | double fromLat = location1.getDouble("lat"); |
| | | double fromLng = location1.getDouble("lng"); |
| | | logger.info("起点坐标: lat={}, lng={}", fromLat, fromLng); |
| | | |
| | | // 第二步:终点地址转坐标 |
| | | String geocodingUrl2 = "https://api.map.baidu.com/geocoding/v3/"; |
| | | String geocodingParams2 = "address=" + URLEncoder.encode(toAddress, StandardCharsets.UTF_8.toString()) + |
| | | (toCity != null && !toCity.trim().isEmpty() ? |
| | | "&city=" + URLEncoder.encode(toCity, StandardCharsets.UTF_8.toString()) : "") + |
| | | "&output=json" + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | String geocodingResponse2 = HttpUtils.sendGet(geocodingUrl2, geocodingParams2); |
| | | logger.info("终点地理编码响应: {}", geocodingResponse2); |
| | | |
| | | // 解析终点坐标 |
| | | com.alibaba.fastjson2.JSONObject geocodingJson2 = com.alibaba.fastjson2.JSONObject.parseObject(geocodingResponse2); |
| | | if (geocodingJson2.getInteger("status") != 0) { |
| | | logger.error("终点地理编码失败: {}", geocodingResponse2); |
| | | return AjaxResult.error("终点地址解析失败"); |
| | | } |
| | | com.alibaba.fastjson2.JSONObject location2 = geocodingJson2.getJSONObject("result").getJSONObject("location"); |
| | | double toLat = location2.getDouble("lat"); |
| | | double toLng = location2.getDouble("lng"); |
| | | logger.info("终点坐标: lat={}, lng={}", toLat, toLng); |
| | | |
| | | // 第三步:调用路线规划接口计算距离 |
| | | String routeUrl = "https://api.map.baidu.com/directionlite/v1/driving"; |
| | | String origin = fromLat + "," + fromLng; |
| | | String destination = toLat + "," + toLng; |
| | | String routeParams = "origin=" + origin + |
| | | "&destination=" + destination + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | logger.info("路线规划请求: origin={}, destination={}", origin, destination); |
| | | String routeResponse = HttpUtils.sendGet(routeUrl, routeParams); |
| | | logger.info("路线规划响应: {}", routeResponse); |
| | | |
| | | // 解析距离结果 |
| | | com.alibaba.fastjson2.JSONObject routeJson = com.alibaba.fastjson2.JSONObject.parseObject(routeResponse); |
| | | if (routeJson.getInteger("status") != 0) { |
| | | logger.error("路线规划失败: {}", routeResponse); |
| | | return AjaxResult.error("路线规划失败"); |
| | | } |
| | | |
| | | // 提取距离信息(单位:米) |
| | | com.alibaba.fastjson2.JSONObject result = routeJson.getJSONObject("result"); |
| | | com.alibaba.fastjson2.JSONArray routes = result.getJSONArray("routes"); |
| | | if (routes == null || routes.isEmpty()) { |
| | | logger.error("未找到路线信息"); |
| | | return AjaxResult.error("未找到路线信息"); |
| | | } |
| | | |
| | | com.alibaba.fastjson2.JSONObject route = routes.getJSONObject(0); |
| | | int distance = route.getInteger("distance"); // 距离,单位:米 |
| | | int duration = route.getInteger("duration"); // 时长,单位:秒 |
| | | |
| | | logger.info("计算成功: 距离={}米, 时长={}秒", distance, duration); |
| | | |
| | | // 构建返回结果 |
| | | Map<String, Object> resultMap = new HashMap<>(); |
| | | resultMap.put("distance", distance); // 距离(米) |
| | | resultMap.put("duration", duration); // 时长(秒) |
| | | resultMap.put("distanceKm", String.format("%.1f", distance / 1000.0)); // 距离(公里) |
| | | resultMap.put("durationMin", duration / 60); // 时长(分钟) |
| | | |
| | | // 起点坐标 |
| | | Map<String, Object> fromLocation = new HashMap<>(); |
| | | fromLocation.put("lat", fromLat); |
| | | fromLocation.put("lng", fromLng); |
| | | resultMap.put("fromLocation", fromLocation); |
| | | |
| | | // 终点坐标 |
| | | Map<String, Object> toLocation = new HashMap<>(); |
| | | toLocation.put("lat", toLat); |
| | | toLocation.put("lng", toLng); |
| | | resultMap.put("toLocation", toLocation); |
| | | |
| | | return AjaxResult.success("计算成功", resultMap); |
| | | } catch (Exception e) { |
| | | logger.error("计算地址距离失败", e); |
| | | return AjaxResult.error("计算距离失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 百度地图地址搜索提示API(输入联想) |
| | | * Place Suggestion API - 用于地址输入时的智能提示 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/baidu/place/suggestion") |
| | | public AjaxResult baiduPlaceSuggestion(String query, String region) { |
| | | try { |
| | | // 检查参数 |
| | | if (query == null || query.trim().isEmpty()) { |
| | | return AjaxResult.error("参数不完整,缺少搜索关键词"); |
| | | } |
| | | |
| | | // 构建百度地图 Place Suggestion API URL |
| | | String url = "https://api.map.baidu.com/place/v2/suggestion"; |
| | | String params = "query=" + URLEncoder.encode(query, StandardCharsets.UTF_8.toString()) + |
| | | (region != null && !region.trim().isEmpty() ? |
| | | "®ion=" + URLEncoder.encode(region, StandardCharsets.UTF_8.toString()) : "") + |
| | | "&output=json" + |
| | | "&ak=" + baiduMapConfig.getAk(); |
| | | |
| | | logger.info("百度地图地址搜索提示请求: query={}, region={}", query, region); |
| | | |
| | | // 发送HTTP请求 |
| | | String response = HttpUtils.sendGet(url, params); |
| | | logger.debug("百度地图地址搜索提示响应: {}", response); |
| | | |
| | | // 解析响应 |
| | | com.alibaba.fastjson2.JSONObject jsonResponse = com.alibaba.fastjson2.JSONObject.parseObject(response); |
| | | if (jsonResponse.getInteger("status") != 0) { |
| | | logger.error("地址搜索提示失败: {}", response); |
| | | return AjaxResult.error("地址搜索失败"); |
| | | } |
| | | |
| | | // 提取提示列表 |
| | | com.alibaba.fastjson2.JSONArray results = jsonResponse.getJSONArray("result"); |
| | | if (results == null || results.isEmpty()) { |
| | | logger.info("未找到匹配的地址"); |
| | | return AjaxResult.success("查询成功", new ArrayList<>()); |
| | | } |
| | | |
| | | // 构建返回结果 |
| | | List<Map<String, Object>> suggestions = new ArrayList<>(); |
| | | for (int i = 0; i < results.size(); i++) { |
| | | com.alibaba.fastjson2.JSONObject item = results.getJSONObject(i); |
| | | |
| | | Map<String, Object> suggestion = new HashMap<>(); |
| | | suggestion.put("name", item.getString("name")); // 名称 |
| | | suggestion.put("address", item.getString("address")); // 地址 |
| | | suggestion.put("province", item.getString("province")); // 省 |
| | | suggestion.put("city", item.getString("city")); // 市 |
| | | suggestion.put("district", item.getString("district")); // 区 |
| | | suggestion.put("uid", item.getString("uid")); // 地点UID |
| | | |
| | | // 经纬度信息 |
| | | com.alibaba.fastjson2.JSONObject location = item.getJSONObject("location"); |
| | | if (location != null) { |
| | | Map<String, Object> locationMap = new HashMap<>(); |
| | | locationMap.put("lat", location.getDouble("lat")); |
| | | locationMap.put("lng", location.getDouble("lng")); |
| | | suggestion.put("location", locationMap); |
| | | } |
| | | |
| | | suggestions.add(suggestion); |
| | | } |
| | | |
| | | logger.info("地址搜索提示成功: 找到{} 条结果", suggestions.size()); |
| | | return AjaxResult.success("查询成功", suggestions); |
| | | } catch (Exception e) { |
| | | logger.error("地址搜索提示失败", e); |
| | | return AjaxResult.error("地址搜索失败:" + e.getMessage()); |
| | | } |
| | | } |
| | | } |