由 BG6RSH » 周三 10月 08, 2025 4:31 pm
一、Java服务器修改
1、src/main/java/org/traccar/geocoder/JsonGeocoder.java,添加高德逆地址解析支持,它传递经纬度数据与其他服务商相反
public String getAddress(
final double latitude, final double longitude, final ReverseGeocoderCallback callback) {
if (cache != null) {
String cachedAddress = cache.get(new AbstractMap.SimpleImmutableEntry<>(latitude, longitude));
if (cachedAddress != null) {
if (callback != null) {
callback.onSuccess(cachedAddress);
}
return cachedAddress;
}
}
if (statisticsManager != null) {
statisticsManager.registerGeocoderRequest();
}
Invocation.Builder request;
if(url.contains("amap.com")) {
// 高德地图
request = client.target(String.format(url, longitude, latitude)).request();
}else {
// 其他地图
request = client.target(String.format(url, latitude, longitude)).request();
}
if (callback != null) {
request.async().get(new InvocationCallback<JsonObject>() {
@Override
public void completed(JsonObject json) {
handleResponse(latitude, longitude, json, callback);
}
@Override
public void failed(Throwable throwable) {
callback.onFailure(throwable);
}
});
} else {
try {
return handleResponse(latitude, longitude, request.get(JsonObject.class), null);
} catch (Exception e) {
LOGGER.warn("Geocoder network error", e);
}
}
return null;
}
2、src/main/java/org/traccar/MainModule.java,添加高德、腾讯逆地址解码协议支持
public static Geocoder provideGeocoder(Config config, Client client, StatisticsManager statisticsManager) {
if (config.getBoolean(Keys.GEOCODER_ENABLE)) {
String type = config.getString(Keys.GEOCODER_TYPE);
String url = config.getString(Keys.GEOCODER_URL);
String key = config.getString(Keys.GEOCODER_KEY);
String language = config.getString(Keys.GEOCODER_LANGUAGE);
String formatString = config.getString(Keys.GEOCODER_FORMAT);
AddressFormat addressFormat = formatString != null ? new AddressFormat(formatString) : new AddressFormat();
int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE);
Geocoder geocoder = switch (type) {
case "pluscodes" -> new PlusCodesGeocoder();
case "amap" -> new AmapGeocoder(client, url, key, cacheSize, addressFormat);// 高德逆地址解码
case "qq" -> new QqGeocoder(client, url, key, cacheSize, addressFormat); // 腾讯逆地址解码
case "nominatim" -> new NominatimGeocoder(client, url, key, language, cacheSize, addressFormat);
case "locationiq" -> new LocationIqGeocoder(client, url, key, language, cacheSize, addressFormat);
case "gisgraphy" -> new GisgraphyGeocoder(client, url, cacheSize, addressFormat);
case "mapquest" -> new MapQuestGeocoder(client, url, key, cacheSize, addressFormat);
case "opencage" -> new OpenCageGeocoder(client, url, key, language, cacheSize, addressFormat);
case "bingmaps" -> new BingMapsGeocoder(client, url, key, cacheSize, addressFormat);
case "factual" -> new FactualGeocoder(client, url, key, cacheSize, addressFormat);
case "geocodefarm" -> new GeocodeFarmGeocoder(client, key, language, cacheSize, addressFormat);
case "geocodexyz" -> new GeocodeXyzGeocoder(client, key, cacheSize, addressFormat);
case "ban" -> new BanGeocoder(client, cacheSize, addressFormat);
case "here" -> new HereGeocoder(client, url, key, language, cacheSize, addressFormat);
case "mapmyindia" -> new MapmyIndiaGeocoder(client, url, key, cacheSize, addressFormat);
case "tomtom" -> new TomTomGeocoder(client, url, key, cacheSize, addressFormat);
case "positionstack" -> new PositionStackGeocoder(client, key, cacheSize, addressFormat);
case "mapbox" -> new MapboxGeocoder(client, key, cacheSize, addressFormat);
case "maptiler" -> new MapTilerGeocoder(client, key, cacheSize, addressFormat);
case "geoapify" -> new GeoapifyGeocoder(client, key, language, cacheSize, addressFormat);
case "geocodejson" -> new GeocodeJsonGeocoder(client, url, key, language, cacheSize, addressFormat);
default -> new GoogleGeocoder(client, url, key, language, cacheSize, addressFormat);
};
geocoder.setStatisticsManager(statisticsManager);
return geocoder;
}
return null;
}
3、src/main/java/org/traccar/geocoder/QqGeocoder.java,添加腾讯逆地址解析文件
package org.traccar.geocoder;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Client;
public class QqGeocoder extends JsonGeocoder {
private static String formatUrl(String url, String key) {
if (url == null) {
url = "https://apis.map.qq.com/ws/geocoder/v1";
}
if (key != null) {
url += "?key=" + key + "&location=%f,%f";
}
return url;
}
public QqGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) {
super(client, formatUrl(url, key), cacheSize, addressFormat);
}
@Override
public Address parseAddress(JsonObject json) {
Address address = new Address();
try {
String value = json.getJsonObject("result").getJsonObject("formatted_addresses").getString("recommend");
address.setFormattedAddress(value);
value = json.getJsonObject("result").getString("address");
address.setStreet(value);
} catch (Exception e) {
return null;
}
return address;
}
}
二、Web程序修改
1、src\resources\l10n\zh.json,添加GCJ02、WGS84坐标名称中文定义
"positionFixTime": "修正时间",
"positionDeviceTime": "设备时间",
"positionServerTime": "服务器时间",
"positionValid": "有效",
"positionAccuracy": "精度",
"positionLatitudeGcj02": "火星坐标纬度",
"positionLongitudeGcj02": "火星坐标经度",
"positionLatitudeWgs84": "GPS坐标纬度",
"positionLongitudeWgs84": "GPS坐标经度",
"positionAltitude": "海拔",
"positionSpeed": "速度",
"positionCourse": "行驶方向",
"positionAddress": "地址",
"positionProtocol": "协议",
"positionDistance": "里程",
2、src\common\attributes\usePositionAttributes.js,添加GCJ02、WGS84坐标名称属性定义
latitude: {
name: t('positionLatitudeGcj02'),
type: 'number',
property: true,
},
longitude: {
name: t('positionLongitudeGcj02'),
type: 'number',
property: true,
},
latitudeWgs84: {
name: t('positionLatitudeWgs84'),
type: 'number',
property: true,
},
longitudeWgs84: {
name: t('positionLongitudeWgs84'),
type: 'number',
property: true,
},
3、src\map\core\useMapStyles.js,添加高德、腾讯瓦片地图支持
{
id: 'autoNavi',
title: t('mapAutoNavi'),
style: styleCustom({
tiles: [1, 2, 3, 4].map((i) => `https://webrd0${i}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}`),
minZoom: 3,
maxZoom: 18,
attribution: '© 高德地图',
}),
available: true,
},
{
id: 'tencentMap',
title: t('mapTencent'), // 需要在src\resources\l10n\zh.json文件中添加对应翻译
style: styleCustom({
tiles: ['tencent://{z}/{x}/{y}'], // ① 自定义协议解析:src\map\core\MapView.jsx
minZoom: 3,
maxZoom: 18,
attribution: '© 腾讯地图',
}),
available: true,
},
4、src\map\core\MapView.jsx,新增腾讯协议拦截
maplibregl.addProtocol('google', googleProtocol);
/* ****** 新增:腾讯协议拦截 ****** */
maplibregl.addProtocol('tencent', async (params) => {
const seg = params.url.split('/');
const z = +seg[2], x = +seg[3], y = +seg[4];
if (z < 0 || x < 0 || y < 0 || y >= (1 << z)) {
return { data: null };
}
const yTMS = (1 << z) - 1 - y;
const host = `rt${(x % 4)}.map.gtimg.com`;
const realUrl = `https://${host}/realtimerender?z=${z}&x=${x}&y=${yTMS}&style=0`;
console.log('[tencent tile]', realUrl);
return fetch(realUrl)
.then((res) => (res.ok ? res.arrayBuffer() : Promise.reject()))
.then((buf) => ({ data: buf }))
.catch(() => ({ data: null }));
});
/* ****** 新增结束 ****** */
5、src\login\LoginPage.jsx,添加论坛链接
<div className={classes.extraContainer}>
{registrationEnabled && (
<Link
onClick={() => navigate('/register')}
className={classes.link}
underline="none"
variant="caption"
>
{t('loginRegister')}
</Link>
)}
{emailEnabled && (
<Link
onClick={() => navigate('/reset-password')}
className={classes.link}
underline="none"
variant="caption"
>
{t('loginReset')}
</Link>
)}
<Link className={classes.link}
underline="none"
variant="caption"
href="http://bbs.atoo.top:8081" target="_blank" rel="noopener noreferrer">
访问论坛
</Link>
</div>
6、index.html,修改网页标题
<title>${title} 火星坐标版</title>
[size=200][color=#0000BF]一、Java服务器修改[/color][/size]
[size=150]1、src/main/java/org/traccar/geocoder/JsonGeocoder.java,添加高德逆地址解析支持,它传递经纬度数据与其他服务商相反[/size]
[Codebox=java file=Untitled.java]
public String getAddress(
final double latitude, final double longitude, final ReverseGeocoderCallback callback) {
if (cache != null) {
String cachedAddress = cache.get(new AbstractMap.SimpleImmutableEntry<>(latitude, longitude));
if (cachedAddress != null) {
if (callback != null) {
callback.onSuccess(cachedAddress);
}
return cachedAddress;
}
}
if (statisticsManager != null) {
statisticsManager.registerGeocoderRequest();
}
Invocation.Builder request;
if(url.contains("amap.com")) {
// 高德地图
request = client.target(String.format(url, longitude, latitude)).request();
}else {
// 其他地图
request = client.target(String.format(url, latitude, longitude)).request();
}
if (callback != null) {
request.async().get(new InvocationCallback<JsonObject>() {
@Override
public void completed(JsonObject json) {
handleResponse(latitude, longitude, json, callback);
}
@Override
public void failed(Throwable throwable) {
callback.onFailure(throwable);
}
});
} else {
try {
return handleResponse(latitude, longitude, request.get(JsonObject.class), null);
} catch (Exception e) {
LOGGER.warn("Geocoder network error", e);
}
}
return null;
}
[/Codebox]
[size=150]2、src/main/java/org/traccar/MainModule.java,添加高德、腾讯逆地址解码协议支持[/size]
[Codebox=java file=Untitled.java]
public static Geocoder provideGeocoder(Config config, Client client, StatisticsManager statisticsManager) {
if (config.getBoolean(Keys.GEOCODER_ENABLE)) {
String type = config.getString(Keys.GEOCODER_TYPE);
String url = config.getString(Keys.GEOCODER_URL);
String key = config.getString(Keys.GEOCODER_KEY);
String language = config.getString(Keys.GEOCODER_LANGUAGE);
String formatString = config.getString(Keys.GEOCODER_FORMAT);
AddressFormat addressFormat = formatString != null ? new AddressFormat(formatString) : new AddressFormat();
int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE);
Geocoder geocoder = switch (type) {
case "pluscodes" -> new PlusCodesGeocoder();
case "amap" -> new AmapGeocoder(client, url, key, cacheSize, addressFormat);// 高德逆地址解码
case "qq" -> new QqGeocoder(client, url, key, cacheSize, addressFormat); // 腾讯逆地址解码
case "nominatim" -> new NominatimGeocoder(client, url, key, language, cacheSize, addressFormat);
case "locationiq" -> new LocationIqGeocoder(client, url, key, language, cacheSize, addressFormat);
case "gisgraphy" -> new GisgraphyGeocoder(client, url, cacheSize, addressFormat);
case "mapquest" -> new MapQuestGeocoder(client, url, key, cacheSize, addressFormat);
case "opencage" -> new OpenCageGeocoder(client, url, key, language, cacheSize, addressFormat);
case "bingmaps" -> new BingMapsGeocoder(client, url, key, cacheSize, addressFormat);
case "factual" -> new FactualGeocoder(client, url, key, cacheSize, addressFormat);
case "geocodefarm" -> new GeocodeFarmGeocoder(client, key, language, cacheSize, addressFormat);
case "geocodexyz" -> new GeocodeXyzGeocoder(client, key, cacheSize, addressFormat);
case "ban" -> new BanGeocoder(client, cacheSize, addressFormat);
case "here" -> new HereGeocoder(client, url, key, language, cacheSize, addressFormat);
case "mapmyindia" -> new MapmyIndiaGeocoder(client, url, key, cacheSize, addressFormat);
case "tomtom" -> new TomTomGeocoder(client, url, key, cacheSize, addressFormat);
case "positionstack" -> new PositionStackGeocoder(client, key, cacheSize, addressFormat);
case "mapbox" -> new MapboxGeocoder(client, key, cacheSize, addressFormat);
case "maptiler" -> new MapTilerGeocoder(client, key, cacheSize, addressFormat);
case "geoapify" -> new GeoapifyGeocoder(client, key, language, cacheSize, addressFormat);
case "geocodejson" -> new GeocodeJsonGeocoder(client, url, key, language, cacheSize, addressFormat);
default -> new GoogleGeocoder(client, url, key, language, cacheSize, addressFormat);
};
geocoder.setStatisticsManager(statisticsManager);
return geocoder;
}
return null;
}
[/Codebox]
[size=150]3、src/main/java/org/traccar/geocoder/QqGeocoder.java,添加腾讯逆地址解析文件[/size]
[Codebox=java file=Untitled.java]package org.traccar.geocoder;
import jakarta.json.JsonObject;
import jakarta.ws.rs.client.Client;
public class QqGeocoder extends JsonGeocoder {
private static String formatUrl(String url, String key) {
if (url == null) {
url = "https://apis.map.qq.com/ws/geocoder/v1";
}
if (key != null) {
url += "?key=" + key + "&location=%f,%f";
}
return url;
}
public QqGeocoder(Client client, String url, String key, int cacheSize, AddressFormat addressFormat) {
super(client, formatUrl(url, key), cacheSize, addressFormat);
}
@Override
public Address parseAddress(JsonObject json) {
Address address = new Address();
try {
String value = json.getJsonObject("result").getJsonObject("formatted_addresses").getString("recommend");
address.setFormattedAddress(value);
value = json.getJsonObject("result").getString("address");
address.setStreet(value);
} catch (Exception e) {
return null;
}
return address;
}
}[/Codebox]
[size=200][color=#0000BF]二、Web程序修改[/color][/size]
[size=150]1、src\resources\l10n\zh.json,添加GCJ02、WGS84坐标名称中文定义[/size]
[Codebox=text file=Untitled.txt] "positionFixTime": "修正时间",
"positionDeviceTime": "设备时间",
"positionServerTime": "服务器时间",
"positionValid": "有效",
"positionAccuracy": "精度",
"positionLatitudeGcj02": "火星坐标纬度",
"positionLongitudeGcj02": "火星坐标经度",
"positionLatitudeWgs84": "GPS坐标纬度",
"positionLongitudeWgs84": "GPS坐标经度",
"positionAltitude": "海拔",
"positionSpeed": "速度",
"positionCourse": "行驶方向",
"positionAddress": "地址",
"positionProtocol": "协议",
"positionDistance": "里程",[/Codebox]
[size=150]2、src\common\attributes\usePositionAttributes.js,添加GCJ02、WGS84坐标名称属性定义[/size]
[Codebox=javascript file=Untitled.js]
latitude: {
name: t('positionLatitudeGcj02'),
type: 'number',
property: true,
},
longitude: {
name: t('positionLongitudeGcj02'),
type: 'number',
property: true,
},
latitudeWgs84: {
name: t('positionLatitudeWgs84'),
type: 'number',
property: true,
},
longitudeWgs84: {
name: t('positionLongitudeWgs84'),
type: 'number',
property: true,
},
[/Codebox]
[size=150]3、src\map\core\useMapStyles.js,添加高德、腾讯瓦片地图支持[/size]
[Codebox=javascript file=Untitled.js]
{
id: 'autoNavi',
title: t('mapAutoNavi'),
style: styleCustom({
tiles: [1, 2, 3, 4].map((i) => `https://webrd0${i}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}`),
minZoom: 3,
maxZoom: 18,
attribution: '© 高德地图',
}),
available: true,
},
{
id: 'tencentMap',
title: t('mapTencent'), // 需要在src\resources\l10n\zh.json文件中添加对应翻译
style: styleCustom({
tiles: ['tencent://{z}/{x}/{y}'], // ① 自定义协议解析:src\map\core\MapView.jsx
minZoom: 3,
maxZoom: 18,
attribution: '© 腾讯地图',
}),
available: true,
},
[/Codebox]
[size=150]4、src\map\core\MapView.jsx,新增腾讯协议拦截[/size]
[Codebox=javascript file=Untitled.js]
maplibregl.addProtocol('google', googleProtocol);
/* ****** 新增:腾讯协议拦截 ****** */
maplibregl.addProtocol('tencent', async (params) => {
const seg = params.url.split('/');
const z = +seg[2], x = +seg[3], y = +seg[4];
if (z < 0 || x < 0 || y < 0 || y >= (1 << z)) {
return { data: null };
}
const yTMS = (1 << z) - 1 - y;
const host = `rt${(x % 4)}.map.gtimg.com`;
const realUrl = `https://${host}/realtimerender?z=${z}&x=${x}&y=${yTMS}&style=0`;
console.log('[tencent tile]', realUrl);
return fetch(realUrl)
.then((res) => (res.ok ? res.arrayBuffer() : Promise.reject()))
.then((buf) => ({ data: buf }))
.catch(() => ({ data: null }));
});
/* ****** 新增结束 ****** */
[/Codebox]
[size=150]5、src\login\LoginPage.jsx,添加论坛链接[/size]
[Codebox=javascript file=Untitled.js]
<div className={classes.extraContainer}>
{registrationEnabled && (
<Link
onClick={() => navigate('/register')}
className={classes.link}
underline="none"
variant="caption"
>
{t('loginRegister')}
</Link>
)}
{emailEnabled && (
<Link
onClick={() => navigate('/reset-password')}
className={classes.link}
underline="none"
variant="caption"
>
{t('loginReset')}
</Link>
)}
<Link className={classes.link}
underline="none"
variant="caption"
href="http://bbs.atoo.top:8081" target="_blank" rel="noopener noreferrer">
访问论坛
</Link>
</div>
[/Codebox]
[size=150]6、index.html,修改网页标题[/size]
[Codebox=html4strict file=Untitled.html]<title>${title} 火星坐标版</title>[/Codebox]