Hi,
I know there won't be a lot of people who are interested in reverse geocoding for Luxemburg (small country in the EU) but there is a free reverse geocoding service available to get the address for a given position.
I did a quick and dirty implementation to see if it works, and it does. I would be pleased if you would implement it in your code, even when only few people will use it.
Here is the URL with the JSON result. (No ID or password required)
https://api.geoportail.lu/geocoder/reverseGeocode?lat=49.680678&lon=6.117233
Here is the code I did
Implementation of a GeoCoder class
package org.traccar.geocoder;
import javax.json.JsonArray;
import javax.json.JsonObject;
public class GeoPortailGeocoder extends JsonGeocoder {
public GeoPortailGeocoder(String url, int cacheSize, AddressFormat addressFormat) {
super("https://api.geoportail.lu/geocoder/reverseGeocode?lat=%f&lon=%f", cacheSize, new AddressFormat("%h %r, %p, %t, %s, %c"));
}
@Override
public Address parseAddress(JsonObject json) {
JsonArray result = json.getJsonArray("results");
if (result != null && !result.isEmpty()) {
JsonObject location = result.getJsonObject(0).getJsonObject("AddressDetails");
Address address = new Address();
address.setCountry("LU");
if (location.containsKey("zip")) {
address.setPostcode(location.getString("zip"));
}
if (location.containsKey("context")) {
address.setDistrict(location.getString("context"));
}
if (location.containsKey("street")) {
address.setStreet(location.getString("street"));
}
if (location.containsKey("locality")) {
address.setSettlement(location.getString("locality"));
}
if (location.containsKey("postnumber")) {
address.setHouse(location.getString("postnumber"));
}
if (result.getJsonObject(0).containsKey("address")) {
address.setFormattedAddress(result.getJsonObject(0).getString("address"));
}
return address;
}
return null;
}
}
Adding the new Geocoder to the MainModule
@Singleton
@Provides
public static Geocoder provideGeocoder(Config config) {
if (config.getBoolean(Keys.GEOCODER_ENABLE)) {
String type = config.getString(Keys.GEOCODER_TYPE, "google");
String url = config.getString(Keys.GEOCODER_URL);
String id = config.getString(Keys.GEOCODER_ID);
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);
switch (type) {
case "nominatim":
return new NominatimGeocoder(url, key, language, cacheSize, addressFormat);
case "gisgraphy":
return new GisgraphyGeocoder(url, cacheSize, addressFormat);
case "mapquest":
return new MapQuestGeocoder(url, key, cacheSize, addressFormat);
case "opencage":
return new OpenCageGeocoder(url, key, cacheSize, addressFormat);
case "bingmaps":
return new BingMapsGeocoder(url, key, cacheSize, addressFormat);
case "factual":
return new FactualGeocoder(url, key, cacheSize, addressFormat);
case "geocodefarm":
return new GeocodeFarmGeocoder(key, language, cacheSize, addressFormat);
case "geocodexyz":
return new GeocodeXyzGeocoder(key, cacheSize, addressFormat);
case "ban":
return new BanGeocoder(cacheSize, addressFormat);
case "here":
return new HereGeocoder(id, key, language, cacheSize, addressFormat);
case "mapmyindia":
return new MapmyIndiaGeocoder(url, key, cacheSize, addressFormat);
case "geoPortail":
return new GeoPortailGeocoder(url,cacheSize,addressFormat);
default:
return new GoogleGeocoder(key, language, cacheSize, addressFormat);
}
}
return null;
}
The bad hack, I had to change the method '' in the class in order to get it working. Probably you have a more elegant way to solve it.
@Override
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;
}
}
Invocation.Builder request = Context.getClient().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 {
String replyString = request.get().readEntity(String.class);
JsonObject testObj = Json.createReader(new StringReader(replyString)).readObject();
return handleResponse(latitude, longitude, testObj, null);
} catch (ClientErrorException e) {
LOGGER.warn("Geocoder network error", e);
}
}
return null;
}
Feel free to use or change the code
Regards
Georges Goebel
Hi,
I know there won't be a lot of people who are interested in reverse geocoding for Luxemburg (small country in the EU) but there is a free reverse geocoding service available to get the address for a given position.
I did a quick and dirty implementation to see if it works, and it does. I would be pleased if you would implement it in your code, even when only few people will use it.
Here is the URL with the JSON result. (No ID or password required)
https://api.geoportail.lu/geocoder/reverseGeocode?lat=49.680678&lon=6.117233
Here is the code I did
Implementation of a GeoCoder class
package org.traccar.geocoder; import javax.json.JsonArray; import javax.json.JsonObject; public class GeoPortailGeocoder extends JsonGeocoder { public GeoPortailGeocoder(String url, int cacheSize, AddressFormat addressFormat) { super("https://api.geoportail.lu/geocoder/reverseGeocode?lat=%f&lon=%f", cacheSize, new AddressFormat("%h %r, %p, %t, %s, %c")); } @Override public Address parseAddress(JsonObject json) { JsonArray result = json.getJsonArray("results"); if (result != null && !result.isEmpty()) { JsonObject location = result.getJsonObject(0).getJsonObject("AddressDetails"); Address address = new Address(); address.setCountry("LU"); if (location.containsKey("zip")) { address.setPostcode(location.getString("zip")); } if (location.containsKey("context")) { address.setDistrict(location.getString("context")); } if (location.containsKey("street")) { address.setStreet(location.getString("street")); } if (location.containsKey("locality")) { address.setSettlement(location.getString("locality")); } if (location.containsKey("postnumber")) { address.setHouse(location.getString("postnumber")); } if (result.getJsonObject(0).containsKey("address")) { address.setFormattedAddress(result.getJsonObject(0).getString("address")); } return address; } return null; } }
Adding the new Geocoder to the MainModule
@Singleton @Provides public static Geocoder provideGeocoder(Config config) { if (config.getBoolean(Keys.GEOCODER_ENABLE)) { String type = config.getString(Keys.GEOCODER_TYPE, "google"); String url = config.getString(Keys.GEOCODER_URL); String id = config.getString(Keys.GEOCODER_ID); 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); switch (type) { case "nominatim": return new NominatimGeocoder(url, key, language, cacheSize, addressFormat); case "gisgraphy": return new GisgraphyGeocoder(url, cacheSize, addressFormat); case "mapquest": return new MapQuestGeocoder(url, key, cacheSize, addressFormat); case "opencage": return new OpenCageGeocoder(url, key, cacheSize, addressFormat); case "bingmaps": return new BingMapsGeocoder(url, key, cacheSize, addressFormat); case "factual": return new FactualGeocoder(url, key, cacheSize, addressFormat); case "geocodefarm": return new GeocodeFarmGeocoder(key, language, cacheSize, addressFormat); case "geocodexyz": return new GeocodeXyzGeocoder(key, cacheSize, addressFormat); case "ban": return new BanGeocoder(cacheSize, addressFormat); case "here": return new HereGeocoder(id, key, language, cacheSize, addressFormat); case "mapmyindia": return new MapmyIndiaGeocoder(url, key, cacheSize, addressFormat); case "geoPortail": return new GeoPortailGeocoder(url,cacheSize,addressFormat); default: return new GoogleGeocoder(key, language, cacheSize, addressFormat); } } return null; }
The bad hack, I had to change the method '' in the class in order to get it working. Probably you have a more elegant way to solve it.
@Override 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; } } Invocation.Builder request = Context.getClient().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 { String replyString = request.get().readEntity(String.class); JsonObject testObj = Json.createReader(new StringReader(replyString)).readObject(); return handleResponse(latitude, longitude, testObj, null); // return handleResponse(latitude, longitude, request.get(JsonObject.class), null); } catch (ClientErrorException e) { LOGGER.warn("Geocoder network error", e); } } return null; }
Feel free to use or change the code
Regards
Georges Goebel