|
@@ -0,0 +1,108 @@
|
|
|
|
|
+package com.ruoyi.app.map;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.ruoyi.common.exception.ServiceException;
|
|
|
|
|
+import com.ruoyi.common.utils.MessageUtils;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
+
|
|
|
|
|
+/** 旧选址入口的自部署适配;只访问自部署客户端,失败不跨源请求 Google 或百度。 */
|
|
|
|
|
+@Service
|
|
|
|
|
+public class MapLegacyLocationService {
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(MapLegacyLocationService.class);
|
|
|
|
|
+ private final SelfHostedMapClient client;
|
|
|
|
|
+
|
|
|
|
|
+ public MapLegacyLocationService(SelfHostedMapClient client) { this.client = client; }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject reverse(String latlng) {
|
|
|
|
|
+ BigDecimal[] point = coordinates(latlng);
|
|
|
|
|
+ return lookup(() -> reverse(point));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public JSONObject nearby(String latlng, Integer type) {
|
|
|
|
|
+ // type=0 的旧百度页面会再次转换坐标;不能把 WGS84 伪装成百度坐标返回。
|
|
|
|
|
+ if (!Integer.valueOf(1).equals(type)) throw new ServiceException(MessageUtils.message("map.location.invalid"));
|
|
|
|
|
+ BigDecimal[] point = coordinates(latlng);
|
|
|
|
|
+ return lookup(() -> {
|
|
|
|
|
+ JSONObject address = reverse(point);
|
|
|
|
|
+ var rows = client.getArray("/nearby", Map.of("lat", point[0], "lng", point[1],
|
|
|
|
|
+ "radius", 10000, "limit", 20));
|
|
|
|
|
+ if (rows == null) throw new IllegalStateException();
|
|
|
|
|
+ JSONArray results = new JSONArray();
|
|
|
|
|
+ for (int index = 0; index < Math.min(rows.size(), 20); index++) {
|
|
|
|
|
+ var row = rows.getJSONObject(index);
|
|
|
|
|
+ if (row == null) throw new IllegalStateException();
|
|
|
|
|
+ BigDecimal lat = row.getBigDecimal("lat"), lng = row.getBigDecimal("lng");
|
|
|
|
|
+ if (!valid(lat, lng) || row.getString("id") == null || row.getString("id").isBlank())
|
|
|
|
|
+ throw new IllegalStateException();
|
|
|
|
|
+ String name = row.getString("name"), vicinity = row.getString("address");
|
|
|
|
|
+ if (name == null || name.isBlank()) name = vicinity;
|
|
|
|
|
+ if (name == null || name.isBlank()) throw new IllegalStateException();
|
|
|
|
|
+ if (vicinity == null) vicinity = "";
|
|
|
|
|
+ JSONObject candidate = new JSONObject();
|
|
|
|
|
+ candidate.put("place_id", "self_hosted:" + row.getString("id"));
|
|
|
|
|
+ candidate.put("name", name);
|
|
|
|
|
+ candidate.put("vicinity", vicinity);
|
|
|
|
|
+ candidate.put("geometry", Map.of("location", Map.of("lat", lat, "lng", lng)));
|
|
|
|
|
+ candidate.put("source", "SELF_HOSTED");
|
|
|
|
|
+ candidate.put("precision", row.getString("precision"));
|
|
|
|
|
+ candidate.put("requiresConfirmation", !"address".equals(row.getString("precision"))
|
|
|
|
|
+ || Boolean.TRUE.equals(row.getBoolean("requires_confirmation")));
|
|
|
|
|
+ results.add(candidate);
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject response = new JSONObject();
|
|
|
|
|
+ response.put("address", address);
|
|
|
|
|
+ response.put("results", results);
|
|
|
|
|
+ return response;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JSONObject reverse(BigDecimal[] point) {
|
|
|
|
|
+ var row = client.getObject("/reverse_geocode", Map.of("lat", point[0], "lng", point[1]));
|
|
|
|
|
+ if (row == null) throw new IllegalStateException();
|
|
|
|
|
+ String name = row.getString("name");
|
|
|
|
|
+ JSONObject result = new JSONObject();
|
|
|
|
|
+ result.put("name", name == null || name.isBlank() ? point[0].toPlainString() + "," + point[1].toPlainString() : name);
|
|
|
|
|
+ result.put("address", row.getString("address") == null ? "" : row.getString("address"));
|
|
|
|
|
+ // 近似道路名不是精确门牌;返回的坐标始终为请求原点,不替换为附近地点。
|
|
|
|
|
+ result.put("latitude", point[0]); result.put("longitude", point[1]);
|
|
|
|
|
+ result.put("source", "SELF_HOSTED");
|
|
|
|
|
+ result.put("precision", row.getString("precision") == null ? "unknown" : row.getString("precision"));
|
|
|
|
|
+ result.put("requiresConfirmation", true);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private BigDecimal[] coordinates(String text) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (text == null || text.length() > 120) throw new IllegalArgumentException();
|
|
|
|
|
+ String[] parts = text.split(",", -1);
|
|
|
|
|
+ if (parts.length != 2) throw new IllegalArgumentException();
|
|
|
|
|
+ BigDecimal lat = new BigDecimal(parts[0].trim()), lng = new BigDecimal(parts[1].trim());
|
|
|
|
|
+ // 拒绝超大指数,防止匿名请求在展开坐标文本时分配过量内存。
|
|
|
|
|
+ if (!valid(lat, lng) || Math.abs((long) lat.scale()) > 30 || Math.abs((long) lng.scale()) > 30)
|
|
|
|
|
+ throw new IllegalArgumentException();
|
|
|
|
|
+ return new BigDecimal[]{lat, lng};
|
|
|
|
|
+ } catch (RuntimeException failure) {
|
|
|
|
|
+ throw new ServiceException(MessageUtils.message("map.location.invalid"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean valid(BigDecimal lat, BigDecimal lng) {
|
|
|
|
|
+ return lat != null && lng != null && lat.abs().compareTo(BigDecimal.valueOf(90)) <= 0
|
|
|
|
|
+ && lng.abs().compareTo(BigDecimal.valueOf(180)) <= 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JSONObject lookup(Supplier<JSONObject> request) {
|
|
|
|
|
+ try { return request.get(); }
|
|
|
|
|
+ catch (RuntimeException failure) {
|
|
|
|
|
+ log.warn("Legacy map location lookup failed provider=SELF_HOSTED");
|
|
|
|
|
+ throw new ServiceException(MessageUtils.message("map.location.unavailable"));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|