Bug 366286 - feature request: improved reverse geolocation
Summary: feature request: improved reverse geolocation
Status: RESOLVED FIXED
Alias: None
Product: digikam
Classification: Applications
Component: Geolocation-ReverseGeoCoding (show other bugs)
Version: 7.8.0
Platform: Ubuntu Linux
: NOR wishlist
Target Milestone: ---
Assignee: Digikam Developers
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-07-30 20:50 UTC by georg.lipps
Modified: 2022-12-04 08:40 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed In: 8.0.0


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description georg.lipps 2016-07-30 20:50:00 UTC
Reverse geolocation is a nice tool and I got it working now. Previously I used Geotag for it. However comparing outputs I discovered that Geotag gives a more complete information.
It uses up to four fields: Ort (location), Stadt (town), Provinz (Province) and Land (Country).

In contrast digikam only uses  Country and Place (which is town) actually.

It would be great if the reverse location would allow more fields, such as Country, Province/State, Town and finally Location (which would be the particular Mountain, Beach, sightseeing spot etc.).

It would be perfect it the respective fields of IPTC and XMP would be filled with the reverse geolocation information.

Thanks for considering.


Reproducible: Always
Comment 1 Jens 2016-08-31 17:03:47 UTC
See also #351987.
Comment 2 georg.lipps 2022-11-08 21:19:36 UTC
This is a super-old feature request from myself. I meanwhile coded a python script to reverse geocode outside of digikam. I include the code here for further use. Unfortunately I am not able to code in C++ and thus to contribute directly. The reverse geocoding is not super-perfect but gives reasonable results for sightseeing and hiking.

It uses  gmaps to set or correct the gps determined elevation.  Nominatim and Geonames services are used to collect relevant information for tagging. The two most abundant entries are then selected for tagging into the IPTC fields.



def reverse(path_file, force=False):  # Force will overwrite existing definitions
    APIKEY = "please order by yourself"
    gmaps = googlemaps.Client(key=APIKEY)
    lang = "de"  # language used for Nominatin Service
    tolerance_elevation = 100
    slowdown = 1  # interval to slowdown request

    metadata = GExiv2.Metadata(path_file)
    if 'Xmp.photoshop.CaptionWriter' in metadata and force == False:  
        if 'Nominatim & GeoNames' in metadata['Xmp.photoshop.CaptionWriter']:
            logging.info('Skipping already processed file %s' % path_file)
            return

    lat = metadata.try_get_gps_latitude()
    lon = metadata.try_get_gps_longitude()
    if lat == 0 and lon == 0:
        logging.warning("Nothing to do since no valid GPS coordinates in file %s" % path_file)
        return

    logging.info('Processing file %s' % path_file)

    elevation = round(gmaps.elevation((lat, lon))[0]["elevation"], 0)
    gps_elevation = metadata.try_get_gps_altitude()
    true_elevation = gps_elevation  # assume first picture data
    logging.info('gps elevation: %s \tgmaps elevation: %s' % (gps_elevation, elevation))
    if gps_elevation <= 0.0:  # no valid elevation
        true_elevation = elevation
        logging.info("No valid elevation data in file, elevation set to gmaps value %s", elevation)
        metadata.try_set_gps_info(lon, lat, true_elevation)
    elif abs(gps_elevation - elevation) > tolerance_elevation:  # too strong deviation
        true_elevation = int(elevation)
        logging.info("Too strong deviation, elevation corrected to gmaps value %s", elevation)
        metadata.set_gps_info(lon, lat, true_elevation)

    query = "%15.12f,%15.12f" % (lat, lon)

    # Nominatim service
    nominatim_dict = {}
    geolocator = Nominatim(timeout=60, user_agent="reverse")
    location = geolocator.reverse(query, language=lang)  # work-around

    for attribute in ['country', 'village', 'city', 'town', 'historic', 'tourism', 'neighbourhood', 'amenity',
                      'locality']:
        try:
            location.raw["address"][attribute]
        except:
            nominatim_dict[attribute] = ''
        else:
            nominatim_dict[attribute] = location.raw["address"][attribute]
    logging.info('Nomatim %s %s' % (location.raw["address"], nominatim_dict))

    # Geonames service
    geonames_dict = {}
    geolocator = GeoNames(username="biokomiker")
    try:
        location = geolocator.reverse(query, find_nearby_type="findNearby", exactly_one=False)
    except GeocoderTimedOut as e:
        logging.debug("Error: geocode failed on input %s with message %s" % (query, e.message))
        return

    for attribute in ['toponymName', 'name']:
        try:
            location[0].raw[attribute]
        except:
            geonames_dict[attribute] = ''
        else:
            geonames_dict[attribute] = location[0].raw[attribute]

    logging.info('Geonames %s %s' % (location[0].raw, geonames_dict))

    city_village = nominatim_dict['village'] + nominatim_dict['city'] + nominatim_dict['town']

    ort = Counter(
        [nominatim_dict[k] for k in ['historic', 'tourism', 'neighbourhood', 'amenity', 'locality']] + [geonames_dict[k]
                                                                                                        for k in
                                                                                                        ['toponymName',
                                                                                                         'name']])
    ort.pop('')
    try:
        final_ort = ort.most_common(2)[0][0] + ' / ' + ort.most_common(2)[1][0]
    except:
        final_ort = ort.most_common(1)[0][0]

    IPTC = 'Iptc.Application2.'

    metadata[IPTC + 'City'] = city_village
    metadata['Xmp.photoshop.City'] = city_village
    metadata[IPTC + 'CountryName'] = nominatim_dict['country']
    metadata['Xmp.photoshop.Country'] = nominatim_dict['country']

    metadata.try_clear_tag(IPTC + 'LocationName')
    metadata.try_clear_tag('Xmp.iptc.Location')
    metadata.set_tag_string(IPTC + 'LocationName', final_ort)
    metadata.set_tag_string('Xmp.iptc.Location', final_ort)

    now = datetime.datetime.now()
    metadata['Xmp.photoshop.CaptionWriter'] = 'Nominatim & GeoNames @ %s-%s-%s' % (now.year, now.month, now.day)
    metadata.save_file()
    logging.info("\nFinal annotation:\nOrt:          %s \nCity/Village: %s \nCountry:      %s \nElevation:    %s " % (
    metadata[IPTC + 'LocationName'], metadata[IPTC + 'City'], metadata[IPTC + 'CountryName'], true_elevation))
    print("\nFinal annotation\nOrt:          %s \nCity/Village: %s \nCountry:      %s \nElevation:    %s " % (
    metadata[IPTC + 'LocationName'], metadata[IPTC + 'City'], metadata[IPTC + 'CountryName'], true_elevation))
    sleep(slowdown)

    return
Comment 3 Maik Qualmann 2022-12-04 08:35:50 UTC
Git commit 8c6b332ee8c9dc0465d2fa9532b29f5996c094c5 by Maik Qualmann.
Committed on 04/12/2022 at 08:34.
Pushed by mqualmann into branch 'master'.

add support for country codes
Related: bug 410425, bug 351987

M  +6    -1    core/utilities/geolocation/geoiface/items/gpsitemcontainer.cpp
M  +309  -52   core/utilities/geolocation/geoiface/reversegeocoding/rgwidget.cpp
M  +4    -0    core/utilities/geolocation/geoiface/reversegeocoding/rgwidget.h

https://invent.kde.org/graphics/digikam/commit/8c6b332ee8c9dc0465d2fa9532b29f5996c094c5