Java坐标转换技术详解
本文将详细探讨Java中实现坐标转换的多种技术路径,包括在线API调用、主流地图服务商集成以及纯Java算法实现。
1. 在线API调用实现坐标转换
1.1 基于公开服务的在线转换
java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;public class OnlineCoordinateConverter {private static final String CONVERT_API = "https://api.example.com/convert";private static final ObjectMapper mapper = new ObjectMapper();/*** 在线坐标转换* @param lng 经度* @param lat 纬度* @param from 原坐标系* @param to 目标坐标系* @return 转换后的坐标*/public static Coordinate onlineConvert(double lng, double lat, String from, String to) {try {String urlStr = String.format("%s?lng=%f&lat=%f&from=%s&to=%s",CONVERT_API, lng, lat, from, to);URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000);conn.setReadTimeout(5000);BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));StringBuilder response = new StringBuilder();String line;while ((line = reader.readLine()) != null) {response.append(line);}reader.close();JsonNode json = mapper.readTree(response.toString());if (json.get("status").asInt() == 200) {JsonNode data = json.get("data");return new Coordinate(data.get("lng").asDouble(),data.get("lat").asDouble());}} catch (Exception e) {e.printStackTrace();}return null;}public static void main(String[] args) {Coordinate result = onlineConvert(116.404, 39.915, "WGS84", "GCJ02");if (result != null) {System.out.printf("转换结果: 经度=%.6f, 纬度=%.6f\n", result.getLongitude(), result.getLatitude());}}
}2. 百度地图API集成
2.1 百度坐标转换实现
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;public class BaiduMapCoordinateConverter {private final String ak; // 百度API Keyprivate final HttpClient httpClient;public BaiduMapCoordinateConverter(String apiKey) {this.ak = apiKey;this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();}/*** 百度坐标转换(支持WGS84/GCJ02转BD09)*/public Coordinate convertToBaidu(double lng, double lat, String from) {try {String coords = lng + "," + lat;String fromParam = "wgs84".equalsIgnoreCase(from) ? "1" : "3";String url = String.format("http://api.map.baidu.com/geoconv/v1/?coords=%s&from=%s&to=5&ak=%s",coords, fromParam, ak);HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(10)).build();HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());return parseBaiduResponse(response.body());} catch (Exception e) {throw new RuntimeException("百度坐标转换失败", e);}}private Coordinate parseBaiduResponse(String response) {try {JsonNode json = mapper.readTree(response);if (json.get("status").asInt() == 0) {JsonNode result = json.get("result").get(0);return new Coordinate(result.get("x").asDouble(),result.get("y").asDouble());}} catch (Exception e) {e.printStackTrace();}return null;}/*** 批量坐标转换*/public List<Coordinate> batchConvert(List<Coordinate> coords, String from) {StringBuilder coordStr = new StringBuilder();for (Coordinate coord : coords) {if (coordStr.length() > 0) coordStr.append(";");coordStr.append(coord.getLongitude()).append(",").append(coord.getLatitude());}// 实现批量转换逻辑return batchConvert(coordStr.toString(), from);}
}3. 高德地图API集成
3.1 高德坐标转换实现
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;public class AmapCoordinateConverter {private final String key;private final String secret; // 用于签名验证public AmapCoordinateConverter(String key, String secret) {this.key = key;this.secret = secret;}/*** 高德坐标转换* 支持GPS/Mapbar/Baidu等坐标系转高德坐标系*/public Coordinate convertToAmap(double lng, double lat, String locations) {try {String originalCoords = lng + "," + lat;// 构建参数Map<String, String> params = new HashMap<>();params.put("key", key);params.put("locations", originalCoords);params.put("coordsys", locations); // gps,mapbar,baidu// 添加签名String signature = generateSignature(params);params.put("sig", signature);String url = buildRequestUrl("https://restapi.amap.com/v3/assistant/coordinate/convert", params);HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(10)).build();HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());return parseAmapResponse(response.body());} catch (Exception e) {throw new RuntimeException("高德坐标转换失败", e);}}/*** 生成API签名*/private String generateSignature(Map<String, String> params) throws Exception {// 参数按key排序List<String> keys = new ArrayList<>(params.keySet());Collections.sort(keys);StringBuilder sb = new StringBuilder();for (String key : keys) {sb.append(key).append("=").append(params.get(key));}sb.append(secret);Mac mac = Mac.getInstance("HmacSHA1");SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), "HmacSHA1");mac.init(signingKey);byte[] rawHmac = mac.doFinal(sb.toString().getBytes());return Base64.getEncoder().encodeToString(rawHmac);}private Coordinate parseAmapResponse(String response) {try {JsonNode json = mapper.readTree(response);if ("1".equals(json.get("status").asText())) {String[] locations = json.get("locations").asText().split(",");return new Coordinate(Double.parseDouble(locations[0]),Double.parseDouble(locations[1]));}} catch (Exception e) {e.printStackTrace();}return null;}
}4. 纯Java算法实现
4.1 核心坐标转换算法
java
public class PureJavaCoordinateConverter {/*** WGS84转GCJ02(火星坐标系)*/public static Coordinate wgs84ToGcj02(double wgsLng, double wgsLat) {if (outOfChina(wgsLng, wgsLat)) {return new Coordinate(wgsLng, wgsLat);}double[] delta = calculateDelta(wgsLng, wgsLat);return new Coordinate(wgsLng + delta[0], wgsLat + delta[1]);}/*** GCJ02转WGS84*/public static Coordinate gcj02ToWgs84(double gcjLng, double gcjLat) {if (outOfChina(gcjLng, gcjLat)) {return new Coordinate(gcjLng, gcjLat);}double[] delta = calculateDelta(gcjLng, gcjLat);return new Coordinate(gcjLng - delta[0], gcjLat - delta[1]);}/*** GCJ02转BD09(百度坐标系)*/public static Coordinate gcj02ToBd09(double gcjLng, double gcjLat) {double x = gcjLng, y = gcjLat;double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI);double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI);double bdLng = z * Math.cos(theta) + 0.0065;double bdLat = z * Math.sin(theta) + 0.006;return new Coordinate(bdLng, bdLat);}/*** BD09转GCJ02*/public static Coordinate bd09ToGcj02(double bdLng, double bdLat) {double x = bdLng - 0.0065, y = bdLat - 0.006;double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI);double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI);double gcjLng = z * Math.cos(theta);double gcjLat = z * Math.sin(theta);return new Coordinate(gcjLng, gcjLat);}/*** 计算偏移量*/private static double[] calculateDelta(double lng, double lat) {double dLat = transformLat(lng - 105.0, lat - 35.0);double dLng = transformLng(lng - 105.0, lat - 35.0);double radLat = lat / 180.0 * Math.PI;double magic = Math.sin(radLat);magic = 1 - EE * magic * magic;double sqrtMagic = Math.sqrt(magic);dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI);dLng = (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI);return new double[]{dLng, dLat};}private static boolean outOfChina(double lng, double lat) {return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;}// 转换参数private static final double A = 6378245.0;private static final double EE = 0.00669342162296594323;private static double transformLat(double x, double y) {double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;return ret;}private static double transformLng(double x, double y) {double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;return ret;}
}5. 统一转换服务封装
5.1 统一的坐标转换服务
java
public enum CoordinateSystem {WGS84, // GPS标准坐标系GCJ02, // 火星坐标系BD09 // 百度坐标系
}public class UnifiedCoordinateService {private BaiduMapCoordinateConverter baiduConverter;private AmapCoordinateConverter amapConverter;private boolean useOnlineService = true;public UnifiedCoordinateService(String baiduKey, String amapKey, String amapSecret) {this.baiduConverter = new BaiduMapCoordinateConverter(baiduKey);this.amapConverter = new AmapCoordinateConverter(amapKey, amapSecret);}/*** 统一坐标转换入口*/public Coordinate convert(Coordinate coord, CoordinateSystem from, CoordinateSystem to) {try {// 相同坐标系直接返回if (from == to) {return coord;}// 根据转换路径选择最优方案return selectConversionPath(coord, from, to);} catch (Exception e) {// 降级方案:使用纯Java算法return fallbackToPureJava(coord, from, to);}}private Coordinate selectConversionPath(Coordinate coord, CoordinateSystem from, CoordinateSystem to) {double lng = coord.getLongitude();double lat = coord.getLatitude();switch (from) {case WGS84:switch (to) {case GCJ02:return useOnlineService ? amapConverter.convertToAmap(lng, lat, "gps") :PureJavaCoordinateConverter.wgs84ToGcj02(lng, lat);case BD09:Coordinate gcj02 = wgs84ToGcj02(lng, lat);return gcj02ToBd09(gcj02.getLongitude(), gcj02.getLatitude());}break;case GCJ02:switch (to) {case WGS84:return PureJavaCoordinateConverter.gcj02ToWgs84(lng, lat);case BD09:return useOnlineService ?baiduConverter.convertToBaidu(lng, lat, "gcj02") :PureJavaCoordinateConverter.gcj02ToBd09(lng, lat);}break;case BD09:switch (to) {case GCJ02:return useOnlineService ?baiduConverter.convertToBaidu(lng, lat, "bd09") :PureJavaCoordinateConverter.bd09ToGcj02(lng, lat);case WGS84:Coordinate gcj02 = bd09ToGcj02(lng, lat);return gcj02ToWgs84(gcj02.getLongitude(), gcj02.getLatitude());}break;}throw new IllegalArgumentException("不支持的坐标系转换: " + from + " -> " + to);}private Coordinate fallbackToPureJava(Coordinate coord, CoordinateSystem from, CoordinateSystem to) {double lng = coord.getLongitude();double lat = coord.getLatitude();// 纯Java算法降级方案// 实现逻辑与selectConversionPath类似,但强制使用纯Java算法return PureJavaCoordinateConverter.wgs84ToGcj02(lng, lat); // 示例}/*** 批量转换*/public List<Coordinate> batchConvert(List<Coordinate> coords,CoordinateSystem from,CoordinateSystem to) {return coords.stream().map(coord -> convert(coord, from, to)).collect(Collectors.toList());}
}6. 性能优化和最佳实践
6.1 缓存和连接池优化
java
@Component
public class OptimizedCoordinateService {private final Cache<CoordinateCacheKey, Coordinate> coordinateCache;private final HttpClient httpClient;public OptimizedCoordinateService() {// 初始化缓存(使用Caffeine)this.coordinateCache = Caffeine.newBuilder().maximumSize(10000).expireAfterWrite(1, TimeUnit.HOURS).build();// 初始化HTTP连接池this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).executor(Executors.newFixedThreadPool(10)).build();}public Coordinate convertWithCache(Coordinate coord, CoordinateSystem from, CoordinateSystem to) {CoordinateCacheKey key = new CoordinateCacheKey(coord, from, to);return coordinateCache.get(key, k -> {// 缓存未命中时的实际转换逻辑return doConvert(coord, from, to);});}// 缓存键类private static class CoordinateCacheKey {private final Coordinate coord;private final CoordinateSystem from;private final CoordinateSystem to;public CoordinateCacheKey(Coordinate coord, CoordinateSystem from, CoordinateSystem to) {this.coord = coord;this.from = from;this.to = to;}// 实现equals和hashCode方法@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;CoordinateCacheKey that = (CoordinateCacheKey) o;return Double.compare(that.coord.getLongitude(), coord.getLongitude()) == 0 &&Double.compare(that.coord.getLatitude(), coord.getLatitude()) == 0 &&from == that.from && to == that.to;}@Overridepublic int hashCode() {return Objects.hash(coord.getLongitude(), coord.getLatitude(), from, to);}}
}总结
本文提供了Java中坐标转换的完整解决方案:
在线API调用:适合需要高精度和官方数据的场景
地图服务商集成:百度、高德等提供稳定的坐标转换服务
纯Java算法:不依赖网络,适合离线环境和高并发场景
选择建议:
对精度要求高:优先使用官方API
需要离线使用:选择纯Java算法
高并发场景:结合缓存和连接池优化
生产环境:建议提供降级方案,API失败时自动切换到本地算法
根据具体业务需求和技术约束,可以选择最适合的实现路径或组合使用多种方案。
。
