Free Reverse Geocoder for Luxemburg

Georges Goebel5 years ago

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

Georges Goebel5 years ago

Sorry, there was some infomation missing in the last sentence

The bad hack, I had to change the method 'getAddress' in the class JsonGeocoder in order to get it working. Probably you have a more elegant way to solve it.

Anton Tananaev5 years ago

Feel free to send a pull request on GitHub.