Salvar posição do GPS na imagem

Estava estudando as formas disponíveis de ler as propriedades de uma imagem numa aplicação Windows Store e me deparei com as propriedades de Longitude e Latitude. Essas informações ficam salvas no formato de metadado dentro de uma foto quando a mesma é salva num dispositivo que permite a localização via GPS. Então tive a curiosidade de verificar com o aplicativo de câmera do Windows 8 poderia fazer isso, uma vez que ele não salva essa informação na imagem.

Lendo as propriedades da imagem

O primeiro passo para alterar é na verdade ler as propriedades disponíveis. Isso porque precisamos entender que essas propriedades do GPS não podem ser alteradas diretamente. As propriedades da imagem são listadas a partir do método GetImagePropertiesAsync que está na propriedade Properties da classe StorageFile. Da seguinte forma:

StorageFile file = await KnownFolders.PicturesLibrary.GetFileAsync(filename);
ImageProperties properties = await file.Properties.GetImagePropertiesAsync();

Note que o método GetImagePropertiesAsync retorna uma instancia de ImageProperties. As propriedades dessa classe são listadas a seguir:

Property Access Description
CameraManufacturer Read/Write Gets or sets the manufacturer of the camera that took the photo.
CameraModel Read/Write Gets or sets the model of the camera that took the photo.
DateTaken Read/Write Gets or sets the date when the image was taken.
Height Read Gets the height of the image.
Keywords Read Gets the collection of keywords associated with the image.
Latitude Read Gets the latitude coordinate where the photo was taken.
Longitude Read Gets the longitude coordinate where the photo was taken.
Orientation Read Gets the Exchangeable Image File (EXIF) orientation flag.
PeopleNames Read Gets the names of people who are tagged in the photo.
Rating Read/Write Gets the rating associated with the image.
Title Read/Write Gets or sets the title of the image.
Width Read Gets the width of the image.

Alterando a Longitude e a Latitude

Ambas as propriedades de Longitude e Latitude possuem permissão apenas para leitura. Isso não quer dizer que não temos permissão para alterar essas propriedades, apenas que elas não podem ser alteradas diretamente pois tratam-se de campos calculados. Todas essas propriedades possuem um metadado equivalente da tabela de propriedades de arquivos do Windows listadas aqui nessa página. No caso de Longitude e Latitude, suas dependências estão listadas a seguir:

Propriedade Dependência
System.GPS.Latitude System.GPS.LatitudeNumerator
System.GPS.LatitudeDenominator
System.GPS.LatitudeRef
System.GPS.Longitude System.GPS.LongitudeNumerator
System.GPS.LongitudeDenominator
System.GPS.LongitudeRef

Essas propriedades do sistema podem ser alteradas a partir do método SavePropertiesAsync que se encontra na propriedade Properties do StorageFile. Segue um exemplo de código:

List<KeyValuePair<string, object>> p = new List<KeyValuePair<string, object>>();
p.Add(new KeyValuePair<string, object>("System.GPS.LongitudeNumerator", lon));
p.Add(new KeyValuePair<string, object>("System.GPS.LongitudeDenominator", d));
p.Add(new KeyValuePair<string, object>("System.GPS.LongitudeRef", direction));

p.Add(new KeyValuePair<string, object>("System.GPS.LatitudeNumerator", lat));
p.Add(new KeyValuePair<string, object>("System.GPS.LatitudeDenominator", d));
p.Add(new KeyValuePair<string, object>("System.GPS.LatitudeRef", direction));

await file.Properties.SavePropertiesAsync(p);

Obtendo os valores do sensor

Obter as coordenadas do GPS a partir da API do Windows Store é bem simples:

Geolocator locator = new Geolocator();
Geoposition position = await locator.GetGeopositionAsync();
double? altitude = position.Coordinate.Altitude;
double? longitude = position.Coordinate.Longitude;

Infelizmente como você pode perceber, os valores de Longitude e Latitude estão no formato double e não destrinchados entre Numerador e Denominador, que é o formato esperado pelas propriedades do Windows. Depois de alguma pesquisa, montei a seguinte classe que faz a conversão entre o formato double e o formato DMS (Degrees, Minutes, Seconds).

public enum Coordenate {
    Latitude,
    Longitude
}

public enum Direction {
    N, S, E, W
}

public class DMS {
    public DMS(double value, Coordenate coordenate) {
        this.SetValue(value, coordenate);
    }

    public double Value { get; private set; }
    public int Seconds { get; private set; }
    public int Degrees { get; private set; }
    public int Minutes { get; private set; }
    public Direction Direction { get; private set; }

    private void SetValue(double value, Coordenate coordenate) {
        this.Value = value;
        if (coordenate == Coordenate.Latitude) {
            if (value >= 0)
                this.Direction = Direction.N;
            else {
                this.Direction = Direction.S;
                value = Math.Abs(value);
            }
        }
        else {
            if (value >= 0)
                this.Direction = Direction.E;
            else {
                this.Direction = Direction.W;
                value = Math.Abs(value);
            }
        }

        this.Degrees = (int)Math.Floor(value);
        this.Minutes = (int)Math.Floor((value % 1) * 60);
        this.Seconds = (int)Math.Round((((value % 1) * 60) % 1) * 60);
    }
}

Encapsulei o código para dentro de um helper para ser utilizando sempre que for necessário salvar as coordenadas do GPS num arquivo:

public static class LocationHelper {
    public static async Task<Geoposition> GetCurrentPosition() {
        Geolocator locator = new Geolocator();
        return await locator.GetGeopositionAsync();
    }

    public static async Task SavePosition(StorageFile file, Geoposition position) {
        DMS latDms = new DMS(position.Coordinate.Latitude, Coordenate.Latitude);
        DMS lonDms = new DMS(position.Coordinate.Longitude, Coordenate.Longitude);

        int[] lat = new int[] { latDms.Degrees, latDms.Minutes, latDms.Seconds };
        int[] lon = new int[] { lonDms.Degrees, lonDms.Minutes, lonDms.Seconds };
        int[] den = new int[3] { 1, 1, 1 };

        List<KeyValuePair<string, object>> ep = new List<KeyValuePair<string, object>>();
        ep.Add(new KeyValuePair<string, object>("System.GPS.LongitudeNumerator", lon));
        ep.Add(new KeyValuePair<string, object>("System.GPS.LongitudeDenominator", den));
        ep.Add(new KeyValuePair<string, object>("System.GPS.LongitudeRef", latDms.Direction.ToString()));

        ep.Add(new KeyValuePair<string, object>("System.GPS.LatitudeNumerator", lat));
        ep.Add(new KeyValuePair<string, object>("System.GPS.LatitudeDenominator", den));
        ep.Add(new KeyValuePair<string, object>("System.GPS.LatitudeRef", lonDms.Direction.ToString()));

        await file.Properties.SavePropertiesAsync(ep);
    }
}

Espero que seja útil para vocês. Estou usando esse código num aplicativo de câmera que estou fazendo.

Código completo no Gist:

Anúncios
%d blogueiros gostam disto: