ハイブリッドアプリ開発では、端末の位置情報取得精度や地図サービス間の座標系差異により、表示位置のずれが発生することがあります。特に百度地図(Baidu Maps)を利用する際は、標準的なWGS84座標系ではなく独自のBD-09座標系を採用しているため、変換処理が不可欠です。
HTML5のGeolocation APIで取得できる位置情報はWGS84基準ですが、百度地図上にそのままプロットすると大幅なオフセットが生じます。この問題に対応するには、取得した座標を百度地図用に変換する必要があります。以下は、Plus API(5+ Runtime)による端末位置取得後、百度地図対応座標へ変換する実装例です。
const getCurrentLocation = () => {
return new Promise((resolve, reject) => {
plus.geolocation.getCurrentPosition((position) => {
const wgsLng = position.coords.longitude;
const wgsLat = position.coords.latitude;
const addressDetail = [
position.address.province,
position.address.city,
position.address.district,
position.address.street,
position.address.streetNum
].join('');
// WGS84 → 百度座標 (BD-09) 変換
const sourcePoint = new BMap.Point(wgsLng, wgsLat);
const converter = new BMap.Convertor();
converter.translate([sourcePoint], 1, 5, (result) => {
if (result.status === 0 && result.points && result.points.length) {
const bdPoint = result.points[0];
resolve({
lng: bdPoint.lng,
lat: bdPoint.lat,
address: addressDetail
});
} else {
resolve({ lng: wgsLng, lat: wgsLat, address: addressDetail });
}
});
}, (error) => {
reject(error);
});
});
};
逆に、外部マップアプリ(例:百度マップや高徳地図)を起動してナビゲーションを呼び出す場合は、百度座標からWGS84またはGCJ-02形式への変換が必要になります。多くのモバイルマップアプリはGCJ-02(中国国家測量基準)を採用しているため、互換性を確保するために変換ロジックを実装します。
// BD-09 を WGS84 に変換するユーティリティ関数
function convertBdToWgs(bdLng, bdLat) {
// BD-09 → GCJ-02 変換
const xPi = (Math.PI * 3000.0) / 180.0;
let x = bdLng - 0.0065;
let y = bdLat - 0.006;
let z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * xPi);
let theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * xPi);
let gcjLng = z * Math.cos(theta);
let gcjLat = z * Math.sin(theta);
// GCJ-02 → WGS84 へ近似変換
if (isOutOfChina(gcjLng, gcjLat)) {
return [bdLng, bdLat];
}
const a = 6378245.0;
const ee = 0.006693421622965943;
const dLat = transformLatitude(gcjLng - 105.0, gcjLat - 35.0);
const dLng = transformLongitude(gcjLng - 105.0, gcjLat - 35.0);
const radLat = gcjLat / 180.0 * Math.PI;
const magic = Math.sin(radLat);
const sqrtMagic = Math.sqrt(1 - ee * magic * magic);
const invRatio = (a * (1 - ee)) / (sqrtMagic * sqrtMagic * sqrtMagic);
const deltaLatRad = (dLat / 180.0) * Math.PI * invRatio;
const deltaLngRad = (dLng / 180.0) * Math.PI * (a / sqrtMagic * Math.cos(radLat));
const correctedLat = gcjLat - (deltaLatRad * 180.0 / Math.PI);
const correctedLng = gcjLng - (deltaLngRad * 180.0 / Math.PI);
return [correctedLng, correctedLat];
}
function isOutOfChina(lng, lat) {
return (lng < 72.004 || lng > 137.8347) || (lat < 0.8293 || lat > 55.8271);
}
function transformLatitude(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320.0 * Math.sin(lat / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}
function transformLongitude(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
}
ナビゲーション機能の呼び出しでは、変換後の座標をplus.maps.Pointオブジェクトに設定し、システムマップアプリを起動します。目的地と出発地の両方を適切な座標系で渡すことで、正確なルート案内が可能になります。
function launchNavigation(destinationLng, destinationLat, startLng, startLat, label) {
if (window.plus) {
const destPoint = new plus.maps.Point(...convertBdToWgs(destinationLng, destinationLat));
const startPoint = new plus.maps.Point(startLng, startLat);
plus.maps.openSysMap(destPoint, label, startPoint);
setTimeout(() => {
plus.nativeUI.toast('マップアプリが起動しない場合はインストールしてください');
}, 600);
}
}