maven 依赖:
<dependency>
<groupId>org.locationtech.proj4j</groupId>
<artifactId>proj4j</artifactId>
<version>1.2.3</version>
</dependency>
GeoUtils.java:
import org.locationtech.proj4j.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
public class GeoUtils {
private static final CoordinateTransformFactory ctf = new CoordinateTransformFactory();
private static final CRSFactory crsFactory = new CRSFactory();
public static final String ellips_code_WGS84 = "WGS84";
public static final String ellips_code_beijing1954 = "krass";
public static final String ellips_code_xian1980 = "IAU76";
private static final Map<String,String> ellips_name_code_map = new HashMap<>();
static {
ellips_name_code_map.put( "WGS84",ellips_code_WGS84 );
ellips_name_code_map.put( "beijing1954",ellips_code_beijing1954 );
ellips_name_code_map.put( "xian1980 ",ellips_code_xian1980 );
}
/**
* 高斯投影坐标转经纬度坐标
* @param gaussCoordinateVO
*/
public static LonLatCoordinateVO gaussProjectionCoordinate2LonLatCoordinate(GaussProjectionCoordinateVO gaussCoordinateVO){
String ellipsCode = null;
String ellipsName = MyStringUtils.null2EmptyWithTrim( gaussCoordinateVO.getEllipsName() );
if( ellipsName.length() == 0 ){
ellipsCode = ellips_code_WGS84;
ellipsName = "WGS84";
}else {
ellipsCode = ellips_name_code_map.get(gaussCoordinateVO.getEllipsName());
}
if( ellipsCode == null ){
throw new BusinessLogicException( "不支持的大地基准面( 椭球 )" );
}
// 计算带号
Long zoneNum = (long) (gaussCoordinateVO.getX() / 1000000L);
// 中央经度( 即中央子午线 )
double lon_0 = 0;
Integer zoneWidth = gaussCoordinateVO.getZoneWidth();
if( zoneWidth == null ){
zoneWidth = 3;
}
if ( zoneWidth == 3 ) {
lon_0 = 3 * zoneNum;
} else if ( zoneWidth == 6 ) {
lon_0 = 6 * zoneNum - 3;
}else {
throw new BusinessLogicException( "目前只支持 3度带宽和6度带宽" );
}
// 东经偏移量
Long x_0 = zoneNum * 1000000L + 500000L;
String crs_params_source="+proj=tmerc +lat_0=0 +lon_0=" + lon_0 + " +k=1 +x_0=" + x_0 + " +y_0=0 +ellps=" + ellipsCode + " +units=m +no_defs";
System.out.println( "crs_params_source = " + crs_params_source );
CoordinateReferenceSystem crs_source = crsFactory.createFromParameters(null, crs_params_source);
// String crs_params_target="+proj=latlong +datum=" + ellipsoidName;
String crs_params_target="+proj=latlong";
System.out.println( "crs_params_target = " + crs_params_target );
CoordinateReferenceSystem crs_target = crsFactory.createFromParameters(null, crs_params_target);
CoordinateTransform transform = ctf.createTransform(crs_source, crs_target);
//坐标系转换
ProjCoordinate projCoordinate = new ProjCoordinate(gaussCoordinateVO.getX(), gaussCoordinateVO.getY());
transform.transform(projCoordinate, projCoordinate);
LonLatCoordinateVO lonLatCoordinateVO = new LonLatCoordinateVO();
lonLatCoordinateVO.setLongitude(BigDecimal.valueOf( projCoordinate.x ).setScale( 6, RoundingMode.HALF_UP ).doubleValue());
lonLatCoordinateVO.setLatitude( BigDecimal.valueOf( projCoordinate.y ).setScale( 6,RoundingMode.HALF_UP ).doubleValue());
lonLatCoordinateVO.setZoneWidth( zoneWidth );
lonLatCoordinateVO.setEllpsName( ellipsName );
lonLatCoordinateVO.setX0( x_0 );
lonLatCoordinateVO.setLon0( lon_0 );
return lonLatCoordinateVO;
}
public static void main(String[] args) {
GaussProjectionCoordinateVO gaussCoordinateVO = new GaussProjectionCoordinateVO();
gaussCoordinateVO.setX( 39458073.6d );
gaussCoordinateVO.setY( 4632485.2d );
LonLatCoordinateVO lonLatCoordinateVO = GeoUtils.gaussProjectionCoordinate2LonLatCoordinate(gaussCoordinateVO);
System.out.println( lonLatCoordinateVO );
}
}
LonLatCoordinateVO.java:
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Getter
@Setter
@ApiModel( value = "LonLatCoordinateVO",description = "LonLatCoordinateVO")
public class LonLatCoordinateVO implements Serializable {
@ApiModelProperty( "大地经纬度坐标下的经度" )
private Double longitude;
@ApiModelProperty( "大地经纬度坐标下的纬度" )
private Double latitude;
private String ellpsName;
private Integer zoneWidth;
private Long x0;
private Double lon0;
@Override
public String toString() {
return "LonLatCoordinateVO{" +
"longitude=" + longitude +
", latitude=" + latitude +
'}';
}
}
GaussProjectionCoordinateVO.java:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.Serializable;
@Getter
@Setter
@ApiModel( value = "GaussProjectionCoordinateVO",description = "GaussProjectionCoordinateVO")
public class GaussProjectionCoordinateVO implements Serializable {
/**
* 例如:x = 39458073.6, y = 4632485.2
*/
@ApiModelProperty( value = "高斯投影坐标之x坐标,例如:39458073.6,必填",required = true )
private double x;
@ApiModelProperty( value = "高斯投影坐标之y坐标,例如:4632485.2,必填",required = true )
private double y;
@ApiModelProperty( value = "大地基准面( 即椭球,可选值为 'WGS84'、'beijing1954'、'xian1980' ),选填,不填默认为 WGS84",required = false )
private String ellipsName;
@ApiModelProperty( value = "带宽,目前值支持 3度和6度,选填,不填默认为3度",required = false )
private Integer zoneWidth;
@ApiModelProperty( value = "是否保存将计算出的结果保存到数据库中( true、false ),选填,默认为 false",required = false )
private Boolean save2Db;
@Override
public String toString() {
return "GaussProjectionCoordinateVO{" +
"x=" + x +
", y=" + y +
'}';
}
}
ps:
1. 使用例如如下的工具验证此程序转换的准确性时记得填写正确的中央经度( 有时候不为117哦 )。
程序中在如下所示计算出了中央子午线( 即中央经度 ):
2. 如果你使用了 北京1954、西安1984、WGS84 之外的大地基准面( 即椭球 ),可以修改代码,自行往 ellips_name_code_map 中添加 ellips_code( 即转换参数字符串中的 ellps ),如果不知道填什么,可以先随便填一个不存在的,让代码报错,如下所示:
点击报错信息中的 "d(Proj4Parser.java:255)",调到如下代码:
进入 registry.getEllipsoid(code) 方法:
比如你不清楚 "2000国家大地坐标系" 的 ellps 该填什么,百度 2000国家大地坐标系 的椭球信息:
在代码中搜索该长半轴和扁率:
于是往 ellips_name_code_map 中加入下入信息:
测试转换结果:
有的网站( 例如:https://www.lddgo.net/coordinate/projection )还需要传 x_0( 好像是东经偏移值 ),也是可以在代码中找到 x_0的计算结果的:
注意输入坐标x,y不要写反了,你会发现上述网站转换时没让填写带宽(3度还是6度),其实带宽是用来计算中央子午线(中央经度)的,具体计算逻辑很简单,在代码中有体现,这里就不截图了,但是为什么我上面截图的一个转换工具需要填写中央经度呢?个人猜测是自定义坐标系使用,就是你想怎么指定中央经度都行